一. 前言
丨1. 行業背景
在現代播放器架構中,音頻後處理已不僅是錦上添花的功能,而是構建差異化聽覺體驗的關鍵組件。尤其在多樣化的播放場景(手機外放、耳機、電視音響等)下,通過定製化的音效增強手段,有效提升聽感表現已成為基礎能力之一。
丨2. 本文概覽
本系列文章將系統介紹我們在播放器音頻後處理模塊中的技術方案與工程實現,主要面向音視頻方向的開發者。我們主要基於 FFmpeg的音頻濾鏡框架,結合自定義模塊,構建了一套可擴展、高性能、易適配的音效處理鏈路。
第一期內容聚焦在兩項核心基礎音效:
- 重低音:通過構建低通濾波器與動態增益控制邏輯,增強低頻段表現,適配小型設備下的聽感優化
- 清晰人聲:結合頻段增強、人聲掩碼與背景音抑制技術,有效提升對白清晰度,在嘈雜或背景音複雜的場景下保持語音主幹突出
我們將分享上述音效的整體處理流程、關鍵濾鏡鏈搭建方式、濾波器設計細節,以及如何在保證延遲與功耗可控的前提下,通過 FFmpeg 的 af(audio filter)機制靈活插拔各類處理節點。
希望本系列文章能為你提供實用的技術參考,也歡迎有 FFmpeg 或音效處理相關實踐經驗的開發者交流碰撞,共同推動播放器音頻後處理技術的深入發展。
二. 走進音頻後處理
丨1. 技術定義和核心目標
對於視聽消費場景,音頻後處理指在音頻信號完成解碼(PCM/DSD數據生成)後,通過數字信號處理技術對原始音頻進行音質增強、效果添加或缺陷修正的過程。核心目標包括:
丨2. 關鍵技術分類
音頻後處理技術主要可分為以下四大類,每類技術的特點和應用如下:
三. 播放內核音頻後處理框架
丨1. 整體架構
音頻後處理模塊在播放pipeline中的位置如上圖所示,位於音頻解碼模塊和音頻幀隊列之間。模塊是否被鏈接於pipeline中是業務可選的,出於性能考慮,只有真正需要用到音頻後處理功能的播放任務才會去鏈接音頻後處理模塊。
丨2. 音效支持
目前,播放內核已開放了包括:音量增強、清晰人聲、重低音、立體環繞、降噪等多種音效,支持業務在播放前以及播放中打開、關閉或變更任意音效。根據業務的設置,音頻後處理模塊承擔了音效濾鏡的初始化、濾鏡鏈組裝、濾鏡鏈變更等任務。該模塊目前不僅提供了FFmpeg原生濾鏡的支持,而且新增了內核自研的熱門音效濾鏡。
四. 落地音效一:重低音
丨1. 重低音
- 重低音效果通常指的是音響系統或音頻設備中低頻段的增強效果,特別是那些頻率在20Hz到250Hz之間的聲音。這種效果使得聲音中的低頻成分(如鼓聲、貝斯聲)更加突出和有力,從而帶來一種震撼和沉浸式的聽覺體驗
- 重低音效果通常應用在音樂、電影和遊戲中,用以增強音效的衝擊力。例如,在觀看動作電影時,重低音效果能夠使爆炸或撞擊聲更加震撼,而在聽音樂時,它能讓鼓點和貝斯的節奏感更強烈。一般來説,這種效果是通過音響設備中的低音炮或音頻處理器來實現的,它們能夠放大並優化低頻聲音,使得音效更加渾厚、深沉和有力量
- 音頻的頻率範圍大致可以劃分為:
- 次低音 (Sub-bass): 20 Hz - 60 Hz
包括極低頻率的聲音,如低音炮的低頻段,能夠帶來深沉的震撼感
- 低音 (Bass): 60 Hz - 250 Hz
涵蓋常規低音部分,如貝斯和低音鼓,帶來厚重感和力量感
- 低中音 (Low midrange): 250 Hz - 500 Hz
涉及一些低頻樂器和男聲的下半部分,增加聲音的温暖感
- 中音 (Midrange): 500 Hz - 2 kHz
包含大部分人聲和主要樂器的頻段,是聲音的核心部分
- 高中音 (Upper midrange): 2 kHz - 4 kHz
涵蓋高頻樂器、人聲的高音部分,決定了聲音的清晰度和穿透力
- 高音 (Treble): 4 kHz - 6 kHz
提供聲音的亮度和細節,涉及高頻樂器和背景環境聲
- 極高音 (Brilliance or Presence): 6 kHz - 20 kHz
涉及非常高頻的聲音,如高頻細節、空氣感和一些環境聲的尾音,影響聲音的開放感和透明度
1.1 設備物理限制
手機或者耳機在體積功率等物理限制下,在中低壓表現較弱。相比之下,電影院音響系統具備大尺寸揚聲器和高功率輸出,能夠產生20Hz或更低的低頻效果,並且其環境設計有助於低音的增強。
- 揚聲器尺寸小:手機和耳機的揚聲器尺寸通常較小,限制了低頻聲波的產生
- 功率輸出低:這些設備的放大器功率有限,無法驅動強勁的低頻振動
1.2 人耳聽覺非線性特性
在不同響度下人耳的等響曲線
- 可以看到,不同響度下的“等響曲線”的走勢都不是完全一致的。響度越小,曲線中各個頻率之間的區別就越大,而響度越大,曲線就越趨於平緩,各個頻率之間的區別就越小
- 人耳對於中頻的敏感度要高於低頻,而這種敏感度之間的差距會隨着響度的增加而減小
丨2. 移動設備重低音方案選型
2.1 基於均衡器的低頻調整
- 原理與實現:
頻段劃分:通過提升均衡器(Equalizer, EQ)低頻段(比如20Hz-200Hz)的增益值直接增強低頻信號幅度,例如,將 50Hz-80Hz 頻段提升 +3dB~+6dB 可顯著增強低音衝擊力
- 技術特點:
線性處理,僅改變幅度,不產生諧波
需結合音頻壓縮器,避免出現削波失真、爆音等case
文件1(原始)
1 bobo1
文件2(低音加強)
2 bobo2
2.2 諧波生成與心理聲學效應
核心原理:
- 一般認為音調應該是由以最低頻率為基波決定的,諧波成分則決定音色
- 通過非線性信號處理生成低頻信號的諧波成分(如60Hz基頻生成120Hz、180Hz諧波),利用人耳的塔替尼效應(Tartini Effect)
- 當多個高頻諧波存在時,大腦會“腦補”出缺失的基頻(如120Hz+180Hz諧波組合可讓人感知到虛擬的60Hz)
音頻文件示例:
下面是兩首曲聽起來只是音色不同,但音調是一致的
y2 y4
2.3 方案對比
丨3. 移動播放器重低音效果實現
3.1 現有技術侷限
- EQ直接提升
- 在低頻段直接增加增益,易引發共振,引發“嗡嗡聲”或“轟鳴感”
- 移動設備喇叭尺寸和功率限制,低頻段增益效果一般
- 動態控制缺失:缺乏壓縮和限制機制,易造成信號削波
3.2 技術目標
開發一款基於FFmpeg的高性能低音增強濾鏡,實現以下功能:
- 分頻處理:精準分離低頻與高頻信號,避免處理干擾
- 諧波增強:通過可控諧波生成方案,提升低頻感知
- 動態均衡:結合前置/後置EQ與壓縮器,優化頻響與動態範圍
- 參數可調:支持分頻點、激勵強度、混合比等靈活配置
3.3 系統架構
3.4 核心模塊詳解
3.4.1 分頻器模塊
功能:將輸入信號分割為低頻和高頻
技術實現:
- 濾波器類型:4階Linkwitz-Riley分頻器,由2個雙二階濾波器級聯
- 相位對齊:低通與高通採用相同極點,保證分頻點處相位連續
- 關鍵代碼:
// 分頻器係數計算(design_crossover函數)
const double w0 = 2.0 * M_PI * s->cutoff / s->sample_rate;
const double alpha = sin(w0) / (2.0 * sqrt(2.0)); // Butterworth Q值
// 低通與高通係數通過頻譜反轉生成
3.4.2 前置EQ模塊
功能:對分頻後的低頻信號進行預增強
技術實現:
- 濾波器類型:低架濾波器(Low Shelf),截止頻率為cutoff * 0.8
- 增益公式:半功率增益計算(pre_gain/40),避免過度提升
- 代碼片段:
const double A = pow(10.0, s->pre_gain / 40.0);
const double omega = 2 * M_PI * s->cutoff * 0.8 / s->sample_rate;
// 低架濾波器係數計算(包含sqrt(A)項)
3.4.3 諧波生成器
功能:通過非線性失真生成奇次諧波,增強低頻感知
算法設計:
- 三階多項式軟化:shaped = x - (x^3)/6,生成3次、5次諧波
- 抗混疊處理:shaped = 1/(1 + |x|2),抑制高頻噪聲
- 直流偏移消除:return shaped - input*0.15
double generate_harmonics(double input, double drive) {
double x = input * drive;
// ...(非線性處理)
}
3.4.4 後置EQ模塊
功能:補償諧波生成後的頻響失衡
技術實現:
- 濾波器類型:高架濾波器(High Shelf),截止頻率為cutoff * 1.2
- 增益公式:全功率增益計算(post_gain/20),直接調整整體電平
- 代碼片段:
const double A = pow(10.0, s->post_gain / 20.0);
const double omega = 2 * M_PI * s->cutoff * 1.2 / s->sample_rate;
// 高架濾波器係數計算(簡化公式)
3.4.5 動態壓縮器
功能:防止增強後的低頻信號過載
算法設計:
- RMS檢測:跟蹤信號包絡,計算動態增益
- 對數域壓縮:閾值(COMP_THRESHOLD)設為-4dB,壓縮比由drive參數動態調整
- 平滑過度:一階IIR濾波器平滑增益變化,避免“抽吸效應”
代碼邏輯:
double compressor_process(...) {
// Attack/Release係數計算
const double coeff = (input^2 > envelope) ? attack_coeff : release_coeff;
// 增益平滑
s->ch_state[ch].gain = 0.2 * old_gain + 0.8 * target_gain;
}
3.4.6 配置參數表
五. 落地音效二:清晰人聲
清晰人聲結合頻段增強、人聲掩碼與背景音抑制技術,能有效提升對白清晰度,在嘈雜或背景音複雜的場景下保持語音主幹突出。為了更好地瞭解清晰人聲的實現,本節將從音效的整體處理流程、濾波器設計細節、關鍵濾鏡鏈搭建方式以及實現效果進行詳細的介紹。
丨1. 技術原理
為了增強音頻中人聲的清晰度,對音頻的處理主要分為四步:
- 識別並隔離主要説話者的聲音,並進行語音放大
- 識別並減少各種類型的背景噪音
- 通過調整頻率平衡來提高語音清晰度
- 應用精細的壓縮和均衡來提高整體音頻質量
要調整音質以使人聲更加清晰,通常需要在EQ中進行一些特定的頻率調整。以下是一些常見的頻率和增益設置及其效果,這些設置有助於提高人聲(通常在300Hz~3kHz)的清晰度:
丨2. Dialoguenhance
FFmpeg中有一個可以用來增強立體聲對話的音頻濾鏡,稱為dialoguenhance。接下來,將對dialoguenhance濾鏡進行一下整體的介紹,隨後便詳細地介紹一下該濾鏡的設計細節,以及播放內核關鍵濾鏡鏈的搭建方式。
2.1 濾鏡介紹
Dialoguenhance濾鏡接受立體聲輸入並生成環繞聲(3.0聲道)的輸出。新生成的前置中置聲道會強化原本在兩個立體聲聲道中都存在的語音對話,同時前置左聲道和右聲道的輸出則與原立體聲輸入相同。
該濾鏡提供了3個選項,分別是original、enhance、voice。下列表格中展現了這三個參數的説明、取值範圍以及其作用。
2.2 實現原理
filter_frame函數是FFmpeg大多數濾鏡的核心處理函數,主要包含以下步驟:
- 第一步,接收原音頻幀輸入,並進行參數初始化。
- 第二步,對輸入進行加窗處理,從而減少頻譜泄露。
- 第三步,執行傅里葉變換,將音頻輸入信號從時域轉化成頻域。
- 第四步,對音頻信號進行算法處理,這個是各個濾鏡的核心部分。
- 第五步,執行傅里葉逆變換,將處理後的音頻信號從頻域轉化為時域。
- 第六步,處理立體聲信號,將其轉換為處理後的信號,處理後的音頻被組合並輸出。協調整個濾鏡過程,管理緩衝區並執行必要的變換和增強。
- 第七步,根據輸入的參數和屬性配置濾鏡。
- 第八步,激活濾鏡。
以dialoguenhance為例,以下展現了filter_frame函數的源代碼。
static int filter_frame(AVFilterLink *inlink, AVFrame *in)
{
AVFilterContext *ctx = inlink->dst;
AVFilterLink *outlink = ctx->outputs[0];
AudioDialogueEnhanceContext *s = ctx->priv;
AVFrame *out;
int ret;
out = ff_get_audio_buffer(outlink, s->overlap);
if (!out) {
ret = AVERROR(ENOMEM);
goto fail;
}
s->in = in;
s->de_stereo(ctx, out);
av_frame_copy_props(out, in);
out->nb_samples = in->nb_samples;
ret = ff_filter_frame(outlink, out);
fail:
av_frame_free(&in);
s->in = NULL;
return ret < 0 ? ret : 0;
}
Dialoguenhance濾鏡是通過處理立體聲音頻信號並提取通常包含在中心聲道中的對話內容來增強音頻對話的,其核心算法處理包括對音頻數據進行中心聲道處理、語音活動檢測、語音增強等步驟。下圖展現了dialoguenhance濾鏡的工作原理及其對應的代碼模塊功能:
丨3. 工程實現
對於播放內核而言,為接入dialoguenhance濾鏡,主要包含以下兩個重要的工程實現:
1. 第一個是dialoguenhance濾鏡的初始化以及獲取濾鏡的上下文。在播放內核的工程實現中,確定好了dialoguenhance各個選項的具體參數。
#include "dialoguenhance_filter.h"
PLAYER_CORE_NAMESPACE_BEGIN
AVFilterContext* DialoguenhanceFilter::getAVFilterContext() {
return _avFilterContext;
}
int DialoguenhanceFilter::initFilter(AVFilterGraph* graph, AudioBaseInfo* audioBaseInfo, const JsonUtils::Value* param) {
AUDIOSCOPEDEBUG();
_avFilter = (AVFilter*)avfilter_get_by_name("dialoguenhance");
if (!_avFilter) {
LOGD("dialoguenhance filter not found");
return -1;
}
_avFilterContext = avfilter_graph_alloc_filter(graph, _avFilter, "dialoguenhance");
if (!_avFilterContext) {
LOGD("dialoguenhance filter context alloc failed");
return -1;
}
// 參數設置
av_opt_set_double(_avFilterContext, "original", 0, AV_OPT_SEARCH_CHILDREN);
av_opt_set_double(_avFilterContext, "enhance", 2, AV_OPT_SEARCH_CHILDREN);
av_opt_set_double(_avFilterContext, "voice", 16, AV_OPT_SEARCH_CHILDREN);
int result = avfilter_init_str(_avFilterContext, nullptr);
if (result < 0) {
LOGD("dialoguenhance filter init failed");
return -1;
}
return 0;
}
PLAYER_CORE_NAMESPACE_END
- 第二個是將dialoguenhance濾鏡接入播放內核音頻後處理模塊的濾鏡鏈中。
void AudioFrameProcessorElement2::init_filter_list(const std::string& filterType,const JsonUtils::Value* param){
std::shared_ptr<IAudioFilter> filterPtr = nullptr;
if (filterType == kAudioSrc){
filterPtr = std::make_shared<SrcFilter>(kAudioSrc);
}
else if (filterType == kAudioSink){
filterPtr = std::make_shared<SinkFilter>(kAudioSink);
}
else if (filterType == kAudioVolume){
filterPtr = std::make_shared<VolumeFilter>(kAudioVolume);
}
else if (filterType == kAudioRaiseVoice){
filterPtr = std::make_shared<DialoguenhanceFilter>(kAudioRaiseVoice);
}
if(filterPtr->initFilter(_filter_graph,_audio_base_info,param) == 0){
_filter_list.push_back(filterPtr);
}
}
丨4. 實現效果
處理前:
input1_1 input2_1
處理後:
res1_1 res2_1
大家可以對比感受上面處理前/後效果。
- 第一組效果
處理前:背景音樂和人聲音量差不多。
處理後:人聲相對於背景音樂更加突出,對話內容更加清晰。
- 第二組效果
處理前:音樂和人聲較平,音量差別不大。
處理後:人聲相對於處理前更加清晰和突出,明顯增強。音樂沉浸感也比處理前效果更好。
六. 小結
本文圍繞播放器音頻後處理中的重低音與清晰人聲兩種典型音效處理,簡要介紹了其實現邏輯、關鍵參數控制策略,以及在播放鏈路中的集成方式。這兩種處理在實際應用中具備較強的通用性,適用於大多數通用內容場景。
後續文章將繼續分享更多音頻後處理技術實踐,敬請關注。