編寫源代碼:
// 標準輸入輸出頭文件,提供printf等函數
#include <stdio.h>
// 文件控制操作頭文件,提供open、fcntl等函數
#include <fcntl.h>
// UNIX標準函數頭文件,提供read、write、close等函數
#include <unistd.h>
// 文件狀態頭文件,提供stat、fstat等函數
#include <sys/stat.h>
// 基本系統數據類型頭文件
#include <sys/types.h>
// WebRTC音頻處理主頭文件
#include "audio_processing.h"
// WebRTC通用類型定義頭文件
#include <module_common_types.h>
// 本地WebRTC AEC頭文件
#include "webrtc_aec.h"
// 功能宏定義,啓用各個音頻處理模塊
#define WEBRTC_AEC // 啓用回聲消除
#define WEBRTC_HP // 啓用高通濾波
#define WEBRTC_LE // 啓用電平估計
#define WEBRTC_NS // 啓用噪聲抑制
#define WEBRTC_VAD // 啓用語音活動檢測
/* WebRTC支持參數説明:
* 採樣率: 8KHZ 和 16KHZ
* 聲道數: 1 (單聲道)
* 位深度: 16 bit
* 幀時長: 10 ms
* */
// 全局變量存儲音頻參數 - 使用單例模式思想管理全局狀態
static unsigned int webrtc_sample_rate = 0; // 採樣率
static unsigned int webrtc_sample_bits = 0; // 位深度
static unsigned int webrtc_sample_time_ms = 0; // 幀時長(毫秒)
static unsigned int webrtc_sample_channel = 0; // 聲道數
using namespace webrtc; // 使用WebRTC命名空間
// AEC設備參數結構體 - 封裝幀相關參數
typedef struct {
unsigned int frame_len; // 幀長度(字節數)
int time; // 時間參數
} AEC_DEV;
AEC_DEV webrtc_dev; // 全局AEC設備實例
bool webrtc_enable = false; // WebRTC使能標誌,控制模塊狀態
// WebRTC音頻處理核心對象指針 - 使用工廠模式創建
AudioProcessing *webrtc_apm = NULL; // 音頻處理管理器
AudioFrame *webrtc_far_frame = NULL; // 遠端幀(揚聲器數據)
AudioFrame *webrtc_near_frame = NULL; // 近端幀(麥克風數據)
/**
* 初始化WebRTC音頻處理模塊
* 功能: 配置音頻參數,創建處理對象,啓用各個處理模塊
* 性能: 一次性初始化,運行時開銷小,但創建對象較耗時
* 設計模式: 工廠模式 + 單例模式思想
*
* @param rate 採樣率指針(支持8000/16000Hz)
* @param channel 聲道數指針(強制為1)
* @param bits 位深度指針(強制為16bit)
* @param time 幀時長(毫秒)
* @return 成功返回0,失敗返回-1
*/
int tang_apm_init(unsigned int *rate, unsigned int *channel, unsigned int *bits, unsigned int time)
{
int32_t sample_rate_hz = 0; // 採樣率(Hz)
int num_render_channels = 0; // 渲染聲道數
int num_capture_input_channels = 0; // 採集輸入聲道數
int num_capture_output_channels = 0; // 採集輸出聲道數
printf("webrtc aec ingenic_apm_init !\n");
// 單例檢查:防止重複初始化
if (webrtc_enable == true) {
printf("webrtc aec has initted!\n");
return 0;
}
// 參數驗證和強制轉換 - 策略模式:參數適配
if ((*rate != 8000) && (*rate != 16000)) {
printf("Error: webrtc not support this rate: %d, force to 8KHZ\n", *rate);
*rate = 8000; // 不支持的採樣率強制設為8KHz
}
if (*channel != 1) {
printf("Error: webrtc not support this channel: %d, force to 1 channel !!\n", *channel);
*channel = 1; // 強制單聲道
}
if (*bits != 16) {
printf("Error: webrtc not support this bits: %d, force to 16 bits\n", *bits);
*bits = 16; // 強制16bit
}
// 參數賦值 - 數據封裝
webrtc_sample_rate = sample_rate_hz = *rate;
webrtc_sample_channel = num_capture_output_channels = num_capture_input_channels = num_render_channels = *channel;
webrtc_sample_bits = *bits;
webrtc_sample_time_ms = time;
// 創建遠端音頻幀對象 - 工廠方法
webrtc_far_frame = new AudioFrame();
if(!webrtc_far_frame) {
printf("webrtc_far_frame new erro\n");
return -1;
}
// 創建近端音頻幀對象 - 工廠方法
webrtc_near_frame = new AudioFrame();
if(!webrtc_far_frame) { // 注意:這裏應該是檢查webrtc_near_frame
printf("webrtc_near_frame new erro\n");
delete webrtc_far_frame; // 資源清理
return -1;
}
// 計算幀長度:採樣率 × 時間(秒) × 聲道數 × 字節數 per sample
webrtc_dev.frame_len = webrtc_sample_rate * webrtc_sample_time_ms *
webrtc_sample_channel * (webrtc_sample_bits / 8) / 1000;
webrtc_dev.time = 1;
// 創建音頻處理管理器 - 工廠模式
webrtc_apm = AudioProcessing::Create(0);
if(webrtc_apm == NULL) {
printf("AudioProcessing::Create() error !\n");
delete webrtc_near_frame;
delete webrtc_far_frame;
return -1;
}
// 配置基礎音頻參數
webrtc_apm->set_sample_rate_hz(sample_rate_hz); // 設置採樣率
webrtc_apm->set_num_reverse_channels(num_render_channels); // 設置反向聲道數
webrtc_apm->set_num_channels(num_capture_input_channels, num_capture_output_channels); // 設置輸入輸出聲道數
// 配置各個音頻處理模塊 - 裝飾器模式:動態添加功能
#ifdef WEBRTC_AEC
// 回聲消除配置
webrtc_apm->echo_cancellation()->Enable(true); // 啓用AEC
webrtc_apm->echo_cancellation()->enable_metrics(true); // 啓用指標計算
webrtc_apm->echo_cancellation()->enable_delay_logging(true); // 啓用延遲日誌
webrtc_apm->echo_cancellation()->enable_drift_compensation(false); // 禁用漂移補償
webrtc_apm->echo_cancellation()->set_suppression_level(EchoCancellation::kLowSuppression); // 設置抑制級別
#endif
#ifdef WEBRTC_HP
// 高通濾波器配置
webrtc_apm->high_pass_filter()->Enable(true); // 啓用高通濾波
#endif
#ifdef WEBRTC_LE
// 電平估計配置
webrtc_apm->level_estimator()->Enable(true); // 啓用電平估計
#endif
#ifdef WEBRTC_NS
// 噪聲抑制配置
webrtc_apm->noise_suppression()->Enable(true); // 啓用噪聲抑制
webrtc_apm->noise_suppression()->set_level(NoiseSuppression::kVeryHigh); // 設置抑制級別為最高
#endif
#ifdef WEBRTC_VAD
// 語音活動檢測配置
webrtc_apm->voice_detection()->Enable(true); // 啓用VAD
webrtc_apm->voice_detection()->set_frame_size_ms(10); // 設置幀大小為10ms
webrtc_apm->voice_detection()->set_likelihood(VoiceDetection::kLowLikelihood); // 設置檢測靈敏度
#endif
webrtc_apm->Initialize(); // 初始化音頻處理器
webrtc_enable = true; // 設置使能標誌
return 0;
}
/**
* 調試信息輸出函數
* 功能: 打印當前音頻處理器的所有配置狀態
* 性能: 僅用於調試,生產環境應禁用
* 設計模式: 訪問者模式 - 遍歷訪問各個處理器狀態
*/
void dump()
{
int ret;
// 獲取基礎配置信息
int sample_rate_hz_dump = webrtc_apm->sample_rate_hz();
int num_input_channels_dump = webrtc_apm->num_input_channels();
int num_output_channels_dump = webrtc_apm->num_output_channels();
int num_reverse_channels_dump = webrtc_apm->num_reverse_channels();
int stream_delay_ms_dump = webrtc_apm->stream_delay_ms();
printf("sample rate : %d\n", sample_rate_hz_dump);
printf("num_input_channels : %d\n", num_input_channels_dump);
printf("num_output_channels : %d\n", num_output_channels_dump);
printf("num_reverse_channels : %d\n", num_reverse_channels_dump);
printf("stream_delay_ms : %d\n", stream_delay_ms_dump);
/* AEC狀態輸出 */
ret = webrtc_apm->echo_cancellation()->is_enabled();
if(ret) {
printf("AEC enable !\n");
ret = webrtc_apm->echo_cancellation()->is_drift_compensation_enabled();
if(ret) {
printf("\t\tenable_drift_compensation");
ret = webrtc_apm->echo_cancellation()->device_sample_rate_hz();
printf("\t\t\tdevice_sample_rate_hz : %d\n", ret);
ret = webrtc_apm->echo_cancellation()->stream_drift_samples();
printf("\t\t\tstream_drift_samples : %d\n", ret);
}
ret = webrtc_apm->echo_cancellation()->suppression_level();
printf("\t\tsuppression_level : %d\n", ret);
ret = webrtc_apm->echo_cancellation()->are_metrics_enabled();
if(ret) {
printf("\t\tenable_metrics\n");
}
ret = webrtc_apm->echo_cancellation()->is_delay_logging_enabled();
if(ret) {
printf("\t\tenable_delay_logging\n");
}
}
/* 高通濾波器狀態輸出 */
ret = webrtc_apm->high_pass_filter()->is_enabled();
if(ret)
printf("HighPassFilter is enabled\n");
/* 電平估計狀態輸出 */
ret = webrtc_apm->level_estimator()->is_enabled();
if(ret) {
printf("LevelEstimator is enable\n");
}
/* 噪聲抑制狀態輸出 */
ret = webrtc_apm->noise_suppression()->is_enabled();
if(ret) {
printf("NoiseSuppression is enabled !\n");
ret = webrtc_apm->noise_suppression()->level();
printf("\t\tNoiseSuppression : %d\n", ret);
}
/* 語音活動檢測狀態輸出 */
ret = webrtc_apm->voice_detection()->is_enabled();
if(ret) {
printf("voice activity detection is enable !\n");
ret = webrtc_apm->voice_detection()->likelihood();
printf("\t\tlikelihood : %d\n", ret);
ret = webrtc_apm->voice_detection()->frame_size_ms();
printf("\t\tframe size per ms : %d\n", ret);
}
}
/**
* 設置遠端幀數據(揚聲器輸出)
* 功能: 將揚聲器數據送入AEC作為參考信號
* 性能: 實時處理,每幀調用一次,計算複雜度中等
* 設計模式: 命令模式 - 封裝數據處理命令
*
* @param buf 揚聲器數據緩衝區
* @return 成功返回0,失敗返回-1
*/
int tang_apm_set_far_frame(short *buf)
{
int i, ret;
// 配置遠端幀參數
webrtc_far_frame->_audioChannel = 1; // 單聲道
webrtc_far_frame->_frequencyInHz = webrtc_sample_rate; // 採樣率
webrtc_far_frame->_payloadDataLengthInSamples = webrtc_far_frame->_frequencyInHz/100; // 每幀樣本數
// 數據預處理:左移1位(可能為了放大信號)
for(i=0; i<webrtc_far_frame->_payloadDataLengthInSamples; i++)
webrtc_far_frame->_payloadData[i] = buf[i] << 1;
// 分析反向流(遠端信號)
ret = webrtc_apm->AnalyzeReverseStream(webrtc_far_frame);
if(ret < 0) {
printf("AnalyzeReverseStream() error : %d\n", ret);
return -1;
}
return 0;
}
/**
* 設置近端幀數據(麥克風輸入)並處理
* 功能: 處理麥克風輸入,應用AEC、NS、VAD等效果
* 性能: 實時處理,計算密集型,性能關鍵路徑
* 設計模式: 模板方法模式 - 定義處理流程
*
* @param input 麥克風輸入數據
* @param output 處理後的輸出數據
* @param time 時間參數
* @return 成功返回0,失敗返回-1
*/
int tang_apm_set_near_frame(short *input, short *output, int time)
{
int i, ret;
// 配置近端幀參數
webrtc_near_frame->_audioChannel = 1; // 單聲道
webrtc_near_frame->_frequencyInHz = webrtc_sample_rate; // 採樣率
webrtc_near_frame->_payloadDataLengthInSamples = webrtc_near_frame->_frequencyInHz/100; // 每幀樣本數
// 拷貝輸入數據到近端幀
for(i = 0; i < webrtc_near_frame->_payloadDataLengthInSamples; i++)
webrtc_near_frame->_payloadData[i] = input[i];
webrtc_apm->set_stream_delay_ms(2); // 設置流延遲為2ms
// 處理音頻流 - 核心處理函數
ret = webrtc_apm->ProcessStream(webrtc_near_frame);
if(ret < 0) {
printf("AnalyzeReverseStream() error : %d\n", ret); // 注意:錯誤信息應該是ProcessStream
return -1;
}
// VAD後處理:如果檢測到無語音,靜音輸出
#ifdef WEBRTC_VAD
ret = webrtc_apm->voice_detection()->stream_has_voice();
if(ret == 0)
for(i = 0; i < webrtc_near_frame->_payloadDataLengthInSamples; i++)
webrtc_near_frame->_payloadData[i] = 0; // 靜音處理
#endif
// 拷貝處理結果到輸出緩衝區
memcpy(output, webrtc_near_frame->_payloadData,
webrtc_near_frame->_payloadDataLengthInSamples * sizeof(short));
return 0;
}
/**
* 銷燬WebRTC音頻處理模塊
* 功能: 清理所有資源,釋放內存
* 性能: 一次性調用,釋放系統資源
* 設計模式: 資源獲取即初始化(RAII)的反向操作
*
* @return 成功返回0
*/
int tang_apm_destroy(void)
{
// 狀態檢查
if (webrtc_enable == false) {
printf("webrtc aec has initted!\n"); // 注意:應該是"not initted"或"already destroyed"
return 0;
}
//dump(); // 調試信息輸出(被註釋)
printf("ingenic_apm_destroy!\n");
// 銷燬音頻處理器 - 工廠模式的銷燬方法
AudioProcessing::Destroy(webrtc_apm);
webrtc_apm = NULL;
// 釋放音頻幀內存 - 注意:第二個條件應該是webrtc_far_frame
if (webrtc_near_frame)
delete webrtc_near_frame;
if (webrtc_near_frame) // BUG:這裏應該是webrtc_far_frame
delete webrtc_far_frame;
webrtc_enable = false; // 重置使能標誌
return 0;
}
/**
* 獲取緩衝區長度
* 功能: 返回每幀音頻數據的字節長度
* 性能: 直接返回預計算值,無計算開銷
*
* @return 幀長度(字節數)
*/
int webrtc_aec_get_buffer_length(void)
{
return webrtc_dev.frame_len;
}
/**
* WebRTC AEC處理入口函數
* 功能: 封裝完整的AEC處理流程
* 性能: 實時處理,組合了兩個核心處理函數
* 設計模式: 外觀模式 - 簡化複雜子系統接口
*
* @param buf_record 錄音數據緩衝區
* @param buf_play 播放數據緩衝區
* @param buf_result 處理結果緩衝區
* @param time 時間參數
*/
void webrtc_aec_calculate(void *buf_record, void *buf_play, void *buf_result, unsigned int time)
{
// 處理流程:先設置遠端參考信號,再處理近端信號
ingenic_apm_set_far_frame((short *)buf_play); // 設置揚聲器數據
ingenic_apm_set_near_frame((short *)buf_record, (short *)buf_result, time); // 處理麥克風數據
}
整體分析和改進建議:
設計模式分析:
- 工廠模式:
AudioProcessing::Create()創建處理器 - 單例模式思想:全局狀態管理,防止重複初始化
- 裝飾器模式:動態組合各種音頻處理功能
- 外觀模式:
webrtc_aec_calculate()提供簡化接口 - 策略模式:參數適配和驗證
性能分析:
- 優點:模塊化設計,實時處理能力
- 缺點:存在內存拷貝,可優化數據流
- 計算複雜度:中等,適合實時音頻處理
發現的問題:
- 內存釋放邏輯有bug(重複檢查webrtc_near_frame)
- 錯誤信息描述不準確
- 缺少錯誤處理和邊界檢查
改進建議:
- 修復內存釋放bug
- 增加參數驗證和錯誤處理
- 考慮使用智能指針管理資源
- 優化數據拷貝,減少內存操作
Makefile
# 獲取當前工作目錄的絕對路徑
DIR_CUR := $(shell pwd)
# 編譯器前綴設置 - 修改為支持多種架構
# 默認使用gcc,但可以通過環境變量覆蓋
CC ?= gcc
CXX ?= g++
STRIP ?= strip
# 根據架構自動設置編譯器標誌
# 檢測當前架構
UNAME_M := $(shell uname -m)
# 設置架構特定的編譯標誌
ifeq ($(UNAME_M), armv7l)
# ARM 32位架構
CFLAGS += -marm -mfpu=neon -mfloat-abi=hard
ARCH = arm32
else ifeq ($(UNAME_M), aarch64)
# ARM 64位架構
CFLAGS += -march=armv8-a
ARCH = arm64
else ifeq ($(UNAME_M), x86_64)
# x86_64架構
ARCH = x86_64
else
# 其他架構
ARCH = unknown
endif
# 安裝路徑配置
DESTDIR = # 目標目錄,通常用於打包或交叉編譯
PREFIX = /usr # 安裝前綴
BINDIR = $(PREFIX)/bin/ # 二進制文件安裝目錄
LIBDIR = $(PREFIX)/lib/ # 庫文件安裝目錄
INCDIR = $(PREFIX)/include/ # 頭文件安裝目錄
# 編譯標誌設置
CFLAGS += -I$(DIR_CUR)/include -O3 # 添加頭文件路徑和優化級別
CFLAGS += -fPIC -g -D_FORTIFY_SOURCE=2 -DBUILDCFG -Wall -Wextra -Wno-unused-parameter #-DBT_ALONE
# -fPIC: 生成位置無關代碼,用於共享庫
# -g: 包含調試信息
# -D_FORTIFY_SOURCE=2: 加強緩衝區溢出保護
# -DBUILDCFG: 自定義構建配置宏
# -Wall -Wextra: 啓用所有警告
# -Wno-unused-parameter: 忽略未使用參數警告
# 鏈接標誌設置
LDFLAGS += -L$(DIR_CUR)/lib/ -lwebrtc_audio_processing -lpthread
# -L: 添加庫搜索路徑
# -l: 鏈接指定的庫
# 源文件和目標文件設置
SRCS = $(wildcard *.cpp) # 使用通配符獲取所有.cpp文件
OBJS = $(patsubst %.cpp,%.c.o,$(SRCS)) # 將.cpp文件名轉換為.o文件名
# 目標輸出文件名
TARGET = libwebrtc.so
# 安裝命令
INSTALL = install
# 導出環境變量給子進程
export CFLAGS LDFLAGS
# 默認目標
all: $(TARGET)
# 主要目標規則:構建共享庫
$(TARGET): $(OBJS)
$(CXX) $(OBJS) $(LDFLAGS) -shared -o $@
# 使用C++編譯器將目標文件鏈接成共享庫
# -shared: 生成共享庫
# $@: 代表目標文件名
# 模式規則:將.cpp文件編譯為.o文件
%.c.o:%.cpp
$(CXX) $(CFLAGS) -c $^ -o $@
# -c: 只編譯不鏈接
# $^: 代表所有依賴文件
# $@: 代表目標文件
# 安裝目標
install:
$(INSTALL) -d $(DESTDIR)$(LIBDIR) # 創建庫目錄
$(INSTALL) -d $(DESTDIR)$(INCDIR) # 創建頭文件目錄
$(INSTALL) -m 755 $(DIR_CUR)/$(TARGET) $(DESTDIR)$(LIBDIR) # 安裝主庫文件
cp -a $(DIR_CUR)/lib/libwebrtc_audio_processing.so* $(DESTDIR)$(LIBDIR) # 複製依賴庫
$(INSTALL) -m 666 $(DIR_CUR)/include/webrtc_aec.h $(DESTDIR)$(INCDIR) # 安裝頭文件
# 卸載目標
uninstall:
-rm -f $(DESTDIR)$(INCDIR)/webrtc_aec.h # 刪除頭文件
-rm -f $(DESTDIR)$(LIBDIR)/$(TARGET) # 刪除主庫文件
-rm -f $(DESTDIR)$(LIBDIR)/libwebrtc_audio_processing.so* # 刪除依賴庫
# 清理目標:刪除編譯生成的目標文件
clean:
-rm -f $(OBJS)
# 深度清理目標:同時刪除最終目標文件
distclean: clean
-rm -f $(TARGET)
# 聲明偽目標,避免與同名文件衝突
.PHONY: all clean install uninstall $(TARGET)
主要修改內容:
- 架構檢測:添加了自動檢測當前系統架構的邏輯
- 編譯器設置:將硬編碼的mipsel編譯器改為通用的gcc/g++,支持通過環境變量覆蓋
CC = mipsel-linux- STRIP = mipsel-linux-strip - 架構特定標誌:為arm32和arm64設置了合適的編譯標誌
- 安裝目錄創建:在install目標中添加了目錄創建步驟
- 變量使用:使用$(TARGET)變量代替硬編碼的文件名
使用方式:
# 自動檢測架構編譯
make
# 交叉編譯(需要在環境中設置CC、CXX等變量)
export CC=arm-linux-gnueabihf-gcc
export CXX=arm-linux-gnueabihf-g++
make
# 安裝到指定目錄
make install DESTDIR=/tmp/install
這樣修改後的Makefile可以自動適應不同的架構環境,包括arm32、arm64和x86_64。
函數調用流程:
tang_apm_init()
↓
webrtc_aec_get_buffer_length() [可選,用於查詢緩衝區大小]
↓
webrtc_aec_calculate() [循環調用,實時處理]
↓
tang_apm_destroy()
使用場景:
- 實時語音通信:消除揚聲器到麥克風的回聲
- 音頻處理應用:需要實時回聲消除的嵌入式系統
- 跨平台開發:C接口便於不同平台集成
本文章為轉載內容,我們尊重原作者對文章享有的著作權。如有內容錯誤或侵權問題,歡迎原作者聯繫我們進行內容更正或刪除文章。