編寫源代碼:

// 標準輸入輸出頭文件,提供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);  // 處理麥克風數據
}

整體分析和改進建議:

設計模式分析:

  1. 工廠模式AudioProcessing::Create() 創建處理器
  2. 單例模式思想:全局狀態管理,防止重複初始化
  3. 裝飾器模式:動態組合各種音頻處理功能
  4. 外觀模式webrtc_aec_calculate() 提供簡化接口
  5. 策略模式:參數適配和驗證

性能分析:

  • 優點:模塊化設計,實時處理能力
  • 缺點:存在內存拷貝,可優化數據流
  • 計算複雜度:中等,適合實時音頻處理

發現的問題:

  1. 內存釋放邏輯有bug(重複檢查webrtc_near_frame)
  2. 錯誤信息描述不準確
  3. 缺少錯誤處理和邊界檢查

改進建議:

  1. 修復內存釋放bug
  2. 增加參數驗證和錯誤處理
  3. 考慮使用智能指針管理資源
  4. 優化數據拷貝,減少內存操作

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)

主要修改內容:

  1. 架構檢測:添加了自動檢測當前系統架構的邏輯
  2. 編譯器設置:將硬編碼的mipsel編譯器改為通用的gcc/g++,支持通過環境變量覆蓋
    CC = mipsel-linux- STRIP = mipsel-linux-strip
  3. 架構特定標誌:為arm32和arm64設置了合適的編譯標誌
  4. 安裝目錄創建:在install目標中添加了目錄創建步驟
  5. 變量使用:使用$(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接口便於不同平台集成