移植webrtc的3a模块至MTK HAL层进行3A的处理

3 篇文章 1 订阅

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


前言


一、思路

创建一个3A服务做输入输出,输入未经处理的音频数据,输出经3A处理的数据。
服务的输入接口在MTK AudioHAL中,处理完数据的输出接口也在MTK 的AudioHAL中。
将录音与播音的音频数据看成两条水管,播音是往外流,录音是往里流。
3A算法服务就是两根水管外的一个混水器,需要对这两根水管进行操作,仅做ns和agc是不需要对播音的那条水管进行导流的,但是如果需要做aec,需要获取播放声音作为参考数据,降低本机自身播放导致的回声。
综上所述,3A服务需要从Audio Capture和Playback里获取音频数据,其中Capture做NS和AGC,Playback做AEC。经3A处理后,返回的数据,走Capture回到上层应用中即可。
在这里插入图片描述

二、步骤

1.3a算法处理流程图(数据流的走向)
2.先从网上下载Webrtc 3A port的c/c++源码
3.将上述获取的源码放入Android原生代码中编译成库
4.创建3A算法处理服务
5.找到audio hal中上行流与下行流的文件
6.从4中获取到对应的流数据,传入3中的3A算法处理服务,返回处理完的数据
7.回声消除效果图
8.一些不足与思考

1.程序流程图

在这里插入图片描述

2.从网上下载Webrtc 3A port的c/c++源码

这部分内容可以上网搜,相关的api可以看具体的demo

3.将上述获取的源码放入Android原生代码中编译成库

算法模块的编译,将降噪/回声消除/自动增益控制这三个算法单独编译成独立的库,下面以回声消除模块来操作一次,其他库的编译类似,具体操作如下:
1.在vendor/mediatek/proprietary/hardware下创建libwebrtc_aecm文件夹
2.在libwebrtc_aecm文件夹下,创建include文件夹和Android.bp文件
3.将模块中头文件放入include,编写Android.bp文件
根据上述3步,系统整编时即可生成对应的算法模块库。
具体的文件放在lib文件夹下。
在这里插入图片描述

4.创建3A算法处理服务

4.1创建一个hidl服务及对应hal接口

1.创建一个hidl服务,该服务创建了3a算法的实现,该实现主要有两个输入的接口,分别是sendCaptureData和sendPlaybackData,还实现了一个输出回调onDataReceived
2.其次,该实现创建了3a算法的主要操作句柄,分别是回声消除aecm/降噪ns/人声检测vad步骤1,创建hidl接口相关的服务,可以参考如下博客:https://blog.csdn.net/jamecer/article/details/123652457?spm=1001.2014.3001.5502步骤2,可以参考模块中的main文件,这个文件是3a算法模块的demo

4.2还有一些对在传入3A处理前,需要对音频数据进行处理

因为每个声卡自身的硬件参数不一样,所以,在数据的格式是不同的,而3A的算法也是与其不一样的,在传入数据前需要做到数据的格式是匹配的。
双通道变单通道、降采样和数据的分割与拼接,具体怎么实现就不说明了,网上有很多

5.Android audio hal层需要处理的类及其其中的方法

1.Android.mk 添加3a服务的hidl接口库
2.获取3a服务接口,并在上行数据流处设置数据处理后回调
3.找到上行数据流与下行数据流的地方
4.通过3a服务接口发送上行数据流与下行数据流到服务中处理
5.回调得到处理后的数据,将该数据替换掉之前传输的上行数据流

5.1添加3a服务的hidl接口库

在之前的服务编译中,我们可以找到3a服务的hidl接口静态库名"vendor.mediatek.hardware.testofhidl@1.0"
注意:是hidl接口库的名字,不是服务的名字
在audio中的Android.mk文件下,做如下添加即可:
LOCAL_SHARED_LIBRARIES += vendor.mediatek.hardware.testofhidl@1.0

具体修改可以参考文件中的patch文件关于vendor/mediatek/proprietary/hardware/audio/mt8168/Android.mk的修改
在这里插入图片描述

5.2需要找到的上行数据流与下行数据流的入口

1.AudioALSAStreamIn.cpp中的read方法
其中,主要的关注点在语句mCaptureHandler->read(buffer, bytes);
mCaptureHandler表示各类型设备(平台声卡等音频输入设备),buffer是读到的上行数据地址,bytes是数据长度(以byte计算)
2.AudioALSAStreamOut.cpp中的write方法
其中,主要的关注点在语句mPlaybackHandler->write(buffer, bytes);
mPlaybackHandler表示各类型设备(平台声卡等音频输出设备),buffer是写入的下行数据地址,bytes是数据长度(以byte计算)
具体的修改,这里直接贴patch

diff --git a/vendor/mediatek/proprietary/hardware/audio/common/V3/aud_drv/AudioALSAStreamIn.cpp b/vendor/mediatek/proprietary/hardware/audio/common/V3/aud_drv/AudioALSAStreamIn.cpp
index f2b0074d8b7..8f61004be76 100644
--- a/vendor/mediatek/proprietary/hardware/audio/common/V3/aud_drv/AudioALSAStreamIn.cpp
+++ b/vendor/mediatek/proprietary/hardware/audio/common/V3/aud_drv/AudioALSAStreamIn.cpp
@@ -20,6 +20,10 @@
 #include "AudioALSAGainController.h"
 #include "AudioALSADriverUtility.h"
 
+//webrtc
+#include <vendor/mediatek/hardware/testofhidl/1.0/ITestOfHidl.h>
+#include <vendor/mediatek/hardware/testofhidl/1.0/IDataCallback.h>
+
 #ifdef MTK_LATENCY_DETECT_PULSE
 #include "AudioDetectPulse.h"
 #endif
@@ -57,6 +61,32 @@ extern "C" {
 
 namespace android {
 
+//emdoor test server
+int16_t input_aec[320];
+static FILE *pOutFile_ed = NULL;
+static FILE *pOutFile_input = NULL;
+sp<vendor::mediatek::hardware::testofhidl::V1_0::IDataCallback> callback = nullptr;
+android::sp<vendor::mediatek::hardware::testofhidl::V1_0::ITestOfHidl> test_server = nullptr;
+bool mFirst = true;
+
+class AudioDataCallback: public vendor::mediatek::hardware::testofhidl::V1_0::IDataCallback {
+public:
+    AudioALSAStreamIn *audioALSAStreamIn;
+    AudioDataCallback(AudioALSAStreamIn *mAudioALSAStreamIn) { audioALSAStreamIn = mAudioALSAStreamIn; }
+
+    ~AudioDataCallback() {}
+
+    hardware::Return<void> onDataReceived(const hardware::hidl_array<int16_t, 320>& data) {
+        ALOGD("onDataReceived: data = %p", &data);
+        memcpy(input_aec, &data, 640);
+        // if (pOutFile_ed != NULL)
+        // {
+        //     fwrite(input_aec, sizeof(int16_t), 320, pOutFile_ed);
+        // }
+        return hardware::Void();
+    }
+};
+
 int AudioALSAStreamIn::mDumpFileNum = 0;
 
 // TODO(Harvey): Query this
@@ -161,6 +191,26 @@ AudioALSAStreamIn::AudioALSAStreamIn() :
     mDumpFile = NULL;
     mBytesWavDumpWritten = 0;
 
+    //emdoor test server
+    test_server = vendor::mediatek::hardware::testofhidl::V1_0::ITestOfHidl::getService();
+    if(test_server != nullptr){
+        ALOGD("test_server exist!!!");
+        callback = new android::AudioDataCallback(this);
+        test_server->setDataCallback(callback);
+    } else {
+        ALOGD("test_server is nullptr, testofhidl server is not exist !!!");
+    }  
+    
+    pOutFile_ed = fopen("/data/vendor/audiohal/RecRaw_ed.pcm", "wb");
+    if (pOutFile_ed == NULL)
+    {
+        ALOGD("%s(), fopen fail", __FUNCTION__);
+    }
+    pOutFile_input = fopen("/data/vendor/audiohal/RecRaw_input.pcm", "wb");
+    if (pOutFile_input == NULL)
+    {
+        ALOGD("%s(), fopen fail", __FUNCTION__);
+    }
 }
 
 
@@ -636,7 +686,22 @@ ssize_t AudioALSAStreamIn::read(void *buffer, ssize_t bytes) {
 #endif
             {
                 ret_size = mCaptureHandler->read(buffer, bytes);
-
+                
+                //webrtc
+                int16_t *input = (int16_t *) buffer;
+
+                //传输上行数据到3a服务处理
+                if (test_server != nullptr) {
+                    // if (pOutFile_input != NULL)
+                    // {
+                    //     fwrite(input, sizeof(int16_t), 320, pOutFile_input);
+                    // }
+                    test_server->sendCaptureData(input);
+                } else {
+                    ALOGD("test_server is nullptr, testofhidl server is not exist !!!");
+                }  
+            
+                memcpy(buffer, input_aec, ret_size);
                 writeWavDumpData(buffer, ret_size);
             }
         }
@@ -1189,7 +1254,6 @@ status_t AudioALSAStreamIn::removeAudioEffect(effect_handle_t effect) {
     return NO_ERROR;
 }
 
-
 status_t AudioALSAStreamIn::open() {
     // call open() only when mLock is locked.
     ASSERT(AL_TRYLOCK(mLock) != 0);
@@ -1271,6 +1335,10 @@ status_t AudioALSAStreamIn::close() {
         // destroy playback handler
         mStreamManager->destroyCaptureHandler(mCaptureHandler);
         mCaptureHandler = NULL;
+
+        if (test_server != nullptr) {
+            test_server->release();
+        }
     }
 
     ASSERT(mCaptureHandler == NULL);
diff --git a/vendor/mediatek/proprietary/hardware/audio/mt8168/Android.mk b/vendor/mediatek/proprietary/hardware/audio/mt8168/Android.mk
index 4311e82f401..01b9d445a50 100644
--- a/vendor/mediatek/proprietary/hardware/audio/mt8168/Android.mk
+++ b/vendor/mediatek/proprietary/hardware/audio/mt8168/Android.mk
@@ -204,7 +204,8 @@ LOCAL_SHARED_LIBRARIES += \
     libhwbinder \
     vendor.mediatek.hardware.mtkpower@1.0 \
     android.hardware.power@1.0 \
-    vendor.mediatek.hardware.power@2.0
+    vendor.mediatek.hardware.power@2.0 \
+    vendor.mediatek.hardware.testofhidl@1.0
 
 LOCAL_SRC_FILES += \
     $(LOCAL_COMMON_PATH)/utility/audio_lock.c \
diff --git a/vendor/mediatek/proprietary/hardware/audio/mt8168/aud_drv/AudioALSAStreamOut.cpp b/vendor/mediatek/proprietary/hardware/audio/mt8168/aud_drv/AudioALSAStreamOut.cpp
index f482aa1fb5d..9abeaa41570 100644
--- a/vendor/mediatek/proprietary/hardware/audio/mt8168/aud_drv/AudioALSAStreamOut.cpp
+++ b/vendor/mediatek/proprietary/hardware/audio/mt8168/aud_drv/AudioALSAStreamOut.cpp
@@ -9,6 +9,7 @@
 
 #include "AudioALSASampleRateController.h"
 #include "AudioALSAFMController.h"
+#include <vendor/mediatek/hardware/testofhidl/1.0/ITestOfHidl.h>
 
 #include <hardware/audio_mtk.h>
 #include <inttypes.h>
@@ -49,6 +50,9 @@
 namespace android
 {
 
+android::sp<vendor::mediatek::hardware::testofhidl::V1_0::ITestOfHidl> test_server1 = nullptr;
+static FILE *pOutFile_output = NULL;
+
 uint32_t AudioALSAStreamOut::mDumpFileNum = 0;
 
 static const audio_format_t       kDefaultOutputSourceFormat      = AUDIO_FORMAT_PCM_16_BIT;
@@ -104,6 +108,13 @@ AudioALSAStreamOut::AudioALSAStreamOut() :
     mScreenOff = false;
     mIsLowPowerEnabled = false;
 #endif
+
+    test_server1 = vendor::mediatek::hardware::testofhidl::V1_0::ITestOfHidl::getService();
+    pOutFile_output = fopen("/data/vendor/audiohal/RecRaw_output.pcm", "wb");
+    if (pOutFile_output == NULL)
+    {
+        ALOGD("%s(), fopen fail", __FUNCTION__);
+    }
 }
 
 AudioALSAStreamOut::~AudioALSAStreamOut()
@@ -394,6 +405,21 @@ ssize_t AudioALSAStreamOut::write(const void *buffer, size_t bytes)
         ASSERT(mPlaybackHandler != NULL);
         if (mPlaybackHandler)
             outputSize = mPlaybackHandler->write(buffer, bytes);
+   
+        if (test_server1 != nullptr) {
+            // if (pOutFile_output != NULL)
+            // {
+            //     fwrite(buffer, sizeof(int8_t), bytes, pOutFile_output);
+            // } 
+            test_server1->sendPlaybackData((int16_t *)buffer);
+        } else {
+            test_server1 = vendor::mediatek::hardware::testofhidl::V1_0::ITestOfHidl::getService();
+            if (test_server1 != nullptr) {
+                test_server1->sendPlaybackData((int16_t *)buffer);
+            } else {
+                TYPE_LOGE("%s() vendor::mediatek::hardware::testofhidl::V1_0::ITestOfHidl::getService() failed \n", __FUNCTION__);
+            }
+        }
     }
     else
     {

6.效果图

6.1aec

在这里插入图片描述
RecRaw_capture是麦克风获取未经处理的音频
RecRaw_playback是播放的原声(经重采样后的原声)
2023-10-31是经回声消除后,录音app得到的音频数据
可以看到通过参考经aec算法的消除,capture中的回声被全部消除

6.2ns

噪声消除效果图(1kHz白噪声)
在这里插入图片描述
RecRaw_capture是麦克风获取未经处理的音频
RecRaw_playback是播放的原声,因为验证环境噪声的消除效果,这里远端声音调至最小,可见为0.
2023-10-31是录取的声音,录音app得到的音频数据
可以看到4秒前,波形一致,经降噪算法后,先出现一个小落差,后面逐渐把噪声降到很小

不足与思考

1.关于playback降采样的问题:
计算一帧下行数据,降采样后的数据长度 1024 * 16000 / (48000 )
注意:这里是一个除不尽的数,得到341.33333333,如果需要除尽,需要等待3/6帧的数据才可以除尽,这里只取341,数据降采样,每一帧都会有数据损失(这个可能会造成aec的效果不好),如果等待3/6帧,需要延迟大概约40/60ms,可能会出现一个情况,就是下行数据延迟于上行数据,如果需要解决这个问题:需要从hal层方面去修改,AudioALSAStreamOut里的setBufferSize计算需要重新去计算使传入的buffer大小满足降采样16K时,不会出现除不尽的情况
如果延迟40~60ms,对于aec的时延模块是一个考验,如果时延过大,aec的消除能力会变得非常差,甚至失效。
2.双讲效果很差,webrtc aecm算法架构缺陷。

如有不足与错误,如果可以指正,万分感谢。

  • 21
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值