Android Audio基礎(13)——audiomixer

在 Android 平臺上,音頻混合器 AudioMixer 主要用在 AudioFlinger 里,將多路音頻源數據混音(包括混音、音量處理、重采樣及處理聲道等)。位于 framework 的音頻處理模庫 libaudioprocessing(frameworks/av/media/libaudioprocessing)中。

一、音頻混合器

混音器(AudioMixer)是在混音回放線程類(MixerThread)中的構造函數內創建。

1、混合器創建

源碼位置:/frameworks/av/services/audioflinger/Threads.cpp

AudioFlinger::MixerThread::MixerThread(……) 
{……// 往hal層一次寫數據的大小。hal層設備的采樣率。mAudioMixer = new AudioMixer(mNormalFrameCount, mSampleRate);……
}

這說明了一個 MixerThread 對應一個 AudioMixer,而且 MixerThread 傳了兩個參數給AudioMixer:
mNormalFrameCount:一次輸送數據的長度,把源 buffer 的音頻數據寫入目的 buffer。
mSampleRate:音頻數據輸出的采樣率。

2、功能接口

AudioMixer源碼位置:/frameworks/av/media/libaudioprocessing/AudioMixer.cpp

void AudioMixer::setBufferProvider(int name, AudioBufferProvider* bufferProvider)

此方法允許用戶根據給定的索引設置一個音頻緩沖區提供者,name 作為索引,bufferProvider由 mActiveTracks 取出來的 Track 對象提供。這樣audiomixer混音時就從track對應的IFFO取數據。

void AudioMixer::setParameter(int name, int target, int param, void *value)

該函數主要用于設置特定參數值,涉及到了音頻處理中多種參數的設置,包括通道掩碼、緩沖區指針、格式、重采樣、音量衰減、時間拉伸等。

這里我們主要看一下 target 和 param 的不同參數所對應實現的相關功能:

在這里插入圖片描述
標注基類是直接透傳調用 AudioMixerBase.cpp 中的對應函數。AudioMixer 繼承自 AudioMixerBase,同時基類中也有幾個比較重要的函數。這里我們需要關注的是參數MAIN_BUFFER。audiomixer會將從track獲取的FIFO數據和MAIN_BUFFER混音后存入MAIN_BUFFER。

AudioMixerBase

源碼位置:/frameworks/av/media/libaudioprocessing/AudioMixerBase.cpp

// 啟用指定的音頻軌道
void AudioMixerBase::enable(int name)
// 禁用指定的音頻軌道
void AudioMixerBase::disable(int name)
// 銷毀指定的音頻軌道
void AudioMixerBase::destroy(int name)

這幾個函數主要是用于音頻軌道的控制功能,確保了軌道能夠在不同狀態下正確工作。

刷新函數

上面的接口中大量調用 invalidate() 函數,該函數的主要作用就是刷新 Track。源碼位置:/frameworks/av/media/libaudioprocessing/include/media/AudioMixerBase.h

// 當軌道信息改變,需要確定一個新的進程鉤子時調用下面的函數
void invalidate() {mHook = &AudioMixerBase::process__validate;
}

可以看到該函數用于當軌道信息發生變化時,重新確定一個新的進程鉤子。這個鉤子通常用于驗證和處理軌道信息的變化,調用 AudioMixerBase 中的 process__validate() 函數。

void AudioMixerBase::process__validate()
{……// 遍歷軌道for (const auto &pair : mTracks) {const int name = pair.first;const std::shared_ptr<TrackBase> &t = pair.second;// 判斷該track是否需要混音if (!t->enabled) continue;// 將啟用的軌道添加到 mEnabled 和 mGroups 中mEnabled.emplace_back(name);mGroups[t->mainBuffer].emplace_back(name);// 計算每個軌道的需求 (needs) 并設置相應的處理鉤子 (hook)uint32_t n = 0;// 可以溢出(掩碼只有3位)n |= NEEDS_CHANNEL_1 + t->channelCount - 1;// 設置重采樣 NEEDS_RESAMPLE 標志位if (t->doesResample()) {n |= NEEDS_RESAMPLE;}// 設置輔助通道 NEEDS_AUX 標志位if (t->auxLevel != 0 && t->auxBuffer != NULL) {n |= NEEDS_AUX;}if (t->volumeInc[0]|t->volumeInc[1]) {// 增益正在變化volumeRamp = true;} else if (!t->doesResample() && t->volumeRL == 0) {// 不需要重采樣且增益為零n |= NEEDS_MUTE;}// 將所有設置好的標志位保存到 t->needs 中t->needs = n;if (n & NEEDS_MUTE) { // 需要靜音t->hook = &TrackBase::track__nop;} else {if (n & NEEDS_AUX) { // 處理輔助通道all16BitsStereoNoResample = false;}if (n & NEEDS_RESAMPLE) { // 處理重采樣all16BitsStereoNoResample = false;resampling = true;if ((n & NEEDS_CHANNEL_COUNT__MASK) == NEEDS_CHANNEL_1&& t->channelMask == AUDIO_CHANNEL_OUT_MONO // MONO_HACK&& isAudioChannelPositionMask(t->mMixerChannelMask)) { // 重采樣單聲道t->hook = TrackBase::getTrackHook(TRACKTYPE_RESAMPLEMONO, t->mMixerChannelCount,t->mMixerInFormat, t->mMixerFormat);} else if ((n & NEEDS_CHANNEL_COUNT__MASK) >= NEEDS_CHANNEL_2&& t->useStereoVolume()) { // 重采樣立體聲t->hook = TrackBase::getTrackHook(TRACKTYPE_RESAMPLESTEREO, t->mMixerChannelCount, t->mMixerInFormat, t->mMixerFormat);} else { // 其他情況t->hook = TrackBase::getTrackHook(TRACKTYPE_RESAMPLE, t->mMixerChannelCount,t->mMixerInFormat, t->mMixerFormat);}ALOGV_IF((n & NEEDS_CHANNEL_COUNT__MASK) > NEEDS_CHANNEL_2,"Track %d needs downmix + resample", name);} else { // 處理無需重采樣的情況……}}}// 根據條件選擇合適的處理鉤子mHook = &AudioMixerBase::process__nop;if (mEnabled.size() > 0) {if (resampling) {if (mOutputTemp.get() == nullptr) {mOutputTemp.reset(new int32_t[MAX_NUM_CHANNELS * mFrameCount]);}if (mResampleTemp.get() == nullptr) {mResampleTemp.reset(new int32_t[MAX_NUM_CHANNELS * mFrameCount]);}mHook = &AudioMixerBase::process__genericResampling;} else {mHook = &AudioMixerBase::process__genericNoResampling;if (all16BitsStereoNoResample && !volumeRamp) {if (mEnabled.size() == 1) {const std::shared_ptr<TrackBase> &t = mTracks[mEnabled[0]];if ((t->needs & NEEDS_MUTE) == 0) {mHook = getProcessHook(PROCESSTYPE_NORESAMPLEONETRACK, t->mMixerChannelCount, t->mMixerInFormat, t->mMixerFormat, t->useStereoVolume());}}}}}……process();// 處理音量漸變,設置最優狀態和軌道處理鉤子if (mEnabled.size() > 0) {bool allMuted = true;for (const int name : mEnabled) {const std::shared_ptr<TrackBase> &t = mTracks[name];if (!t->doesResample() && t->volumeRL == 0) {t->needs |= NEEDS_MUTE;t->hook = &TrackBase::track__nop;} else {allMuted = false;}}if (allMuted) {mHook = &AudioMixerBase::process__nop;} else if (all16BitsStereoNoResample) {if (mEnabled.size() == 1) {const std::shared_ptr<TrackBase> &t = mTracks[mEnabled[0]];mHook = getProcessHook(PROCESSTYPE_NORESAMPLEONETRACK, t->mMixerChannelCount, t->mMixerInFormat, t->mMixerFormat, t->useStereoVolume());}}}
}

這些步驟確保了音頻軌道信息變化時能夠正確選擇合理的鉤子函數處理各種情況,包括重采樣、音量漸變等。

二、AudioMixer混音

  關于混音,我們已經知道:混音以 track 為源,mainBuffer 為目標,frameCount 為一次混音長度。AudioMixer 最多能維護 32 個 track。track 可以對應不同 mainBuffer,盡管一般情況下他們的 mainBuffer 都是同一個。

在這里插入圖片描述

調用 AudioMixer 的 process 方法進行混音的,實際上混音的方法是調用 AudioMixerBase 內的 process_xxx 方法,各個 process 方法大同小異。下面來分析 process__genericResampling 這個方法。
1、AudioMixerBase.cppp
rocess__genericResampling

void AudioMixerBase::process__genericResampling()
{ALOGVV("process__genericResampling\n");// 初始化 outTemp 指針int32_t * const outTemp = mOutputTemp.get(); // 獲取當前幀數 numFramessize_t numFrames = mFrameCount;// 遍歷每個音頻組 mGroupsfor (const auto &pair : mGroups) {const auto &group = pair.second;// 獲取第一個軌道 t1const std::shared_ptr<TrackBase> &t1 = mTracks[group[0]];// 清除了臨時緩沖區 outTempmemset(outTemp, 0, sizeof(*outTemp) * t1->mMixerChannelCount * mFrameCount);// 處理每個軌道for (const int name : group) {const std::shared_ptr<TrackBase> &t = mTracks[name];int32_t *aux = NULL;if (CC_UNLIKELY(t->needs & NEEDS_AUX)) {aux = t->auxBuffer;}// 如果軌道需要重采樣,則直接調用重采樣鉤子if (t->needs & NEEDS_RESAMPLE) {(t.get()->*t->hook)(outTemp, numFrames, mResampleTemp.get() /* naked ptr */, aux);} else { // 逐幀獲取數據并調用處理鉤子// 清除臨時緩沖區size_t outFrames = 0;// 獲取每個軌道的數據并調用相應的處理鉤子while (outFrames < numFrames) {t->buffer.frameCount = numFrames - outFrames;t->bufferProvider->getNextBuffer(&t->buffer);t->mIn = t->buffer.raw;// t->mIn == nullptr:啟用混音后剛剛刷新音軌if (t->mIn == nullptr) break;(t.get()->*t->hook)(outTemp + outFrames * t->mMixerChannelCount, t->buffer.frameCount,mResampleTemp.get() /* naked ptr */, aux != nullptr ? aux + outFrames : nullptr);outFrames += t->buffer.frameCount;t->bufferProvider->releaseBuffer(&t->buffer);}}}// 將處理后的數據轉換為混音器所需的格式convertMixerFormat(t1->mainBuffer, t1->mMixerFormat,outTemp, t1->mMixerInFormat, numFrames * t1->mMixerChannelCount);}
}

這些步驟確保了在需要重采樣的情況下,能夠正確處理每個軌道的數據,并將其轉換為所需的格式。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/74363.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/74363.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/74363.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

【React】使用Swiper報錯`Swiper` needs at least one child

問題 聊天頁面的表情面板&#xff0c;滑動效果使用了ant design mobile的Swiper。 原代碼中&#xff0c;Swiper 組件在 isShow 為 false 時渲染的是 <></>&#xff08;空元素&#xff09;&#xff0c;控制臺警告Swiper needs at least one child&#xff0c;Swip…

Matlab教程001:軟件介紹和界面使用

1.1 軟件介紹 1.1.1 Matlab的介紹 MATLAB&#xff08;MATrix LABoratory&#xff09;是一款由 MathWorks 公司開發的高級編程語言和交互式環境&#xff0c;廣泛用于 科學計算、數據分析、機器學習、工程建模、仿真和信號處理 等領域。 1.1.2 主要應用領域 數據分析與可視化…

藍橋杯算法實戰分享:算法進階之路與實戰技巧

引言 藍橋杯作為國內極具影響力的程序設計競賽&#xff0c;為眾多編程愛好者和專業人才提供了展示自我的舞臺。參與藍橋杯不僅能檢驗自身編程水平&#xff0c;還能拓寬技術視野&#xff0c;為未來職業發展積累寶貴經驗。本文將結合歷年真題與參賽經驗&#xff0c;全面分享藍橋…

Android Compose 層疊布局(ZStack、Surface)源碼深度剖析(十三)

Android Compose 層疊布局&#xff08;ZStack、Surface&#xff09;源碼深度剖析 一、引言 在 Android 應用開發領域&#xff0c;用戶界面&#xff08;UI&#xff09;的設計與實現一直是至關重要的環節。隨著技術的不斷演進&#xff0c;Android Compose 作為一種全新的聲明式…

MongoDB 面試備戰指南

MongoDB 面試備戰指南 一、基礎概念 1. MongoDB是什么類型的數據庫&#xff1f;和關系型數據庫有什么區別&#xff1f; 答案&#xff1a; MongoDB是文檔型NoSQL數據庫&#xff0c;核心區別&#xff1a; 數據模型&#xff1a;存儲JSON-like文檔&#xff08;動態schema&#xf…

毫米波雷達標定(2)

1. 前言 前面文章中介紹了產線上毫米波雷達的標定原理和流程,這篇文章則主要介紹其在線標定方法。相對于產線標定,在線標定具備使用自然場景而不是依賴特定標靶的優點,但因此其標定精度會相對差一點。在線標定一般應用于售出產品的維護場景,如果其標定結果精度可以滿足使用…

Linux fority source和__builtin_xxx

這段代碼是用于啟用和配置 GCC/Clang 的 Fortify Source 安全機制的預處理指令。Fortify Source 主要用于在編譯時增強對緩沖區溢出等內存安全問題的檢查。以下是對每一部分的詳細解釋&#xff1a; 1. 最外層條件編譯 # if CONFIG_FORTIFY_SOURCE > 0目的&#xff1a;檢查…

SQL GROUP BY 自定義排序規則

在 SQL 中&#xff0c;GROUP BY 子句用于將結果集按一個或多個列進行分組。默認情況下&#xff0c;GROUP BY 會按照列的自然順序&#xff08;如字母順序或數字順序&#xff09;進行排序。如果你需要按照自定義的排序規則對結果進行分組&#xff0c;可以使用 ORDER BY 子句結合 …

語言模型理論基礎-持續更新-思路清晰

1.預訓練 相似的任務A、B&#xff0c;任務A已經用大數據完成了訓練&#xff0c;得到模型A。 我們利用-特征提取模型的-“淺層參數通用”的特性&#xff0c;使用模型A的淺層參數&#xff0c;其他參數再通過任務B去訓練&#xff08;微調&#xff09;。 2.統計語言模型 通過條件…

ResNet與注意力機制:深度學習中的強強聯合

引言 在深度學習領域&#xff0c;卷積神經網絡&#xff08;CNN&#xff09;一直是圖像處理任務的主流架構。然而&#xff0c;隨著網絡深度的增加&#xff0c;梯度消失和梯度爆炸問題逐漸顯現&#xff0c;限制了網絡的性能。為了解決這一問題&#xff0c;ResNet&#xff08;殘差…

【C++】——C++11新特性

目錄 前言 1.初始化列表 2.std::initializer_list 3.auto 4.decltype 5.nullptr 6.左值引用和右值引用 6.1右值引用的真面目 6.2左值引用和右值引用比較 6.3右值引用的意義 6.3.1移動構造 6.4萬能引用 6.5完美轉發——forward 結語 前言 C&#xff0c;這門在系統…

【C++網絡編程】第5篇:UDP與廣播通信

一、UDP協議核心特性 1. UDP vs TCP ?特性 ?UDP?TCP連接方式無連接面向連接&#xff08;三次握手&#xff09;可靠性不保證數據到達或順序可靠傳輸&#xff08;超時重傳、順序控制&#xff09;傳輸效率低延遲&#xff0c;高吞吐相對較低&#xff08;因握手和確認機制&…

MOSN(Modular Open Smart Network)是一款主要使用 Go 語言開發的云原生網絡代理平臺

前言 大家好&#xff0c;我是老馬。 sofastack 其實出來很久了&#xff0c;第一次應該是在 2022 年左右開始關注&#xff0c;但是一直沒有深入研究。 最近想學習一下 SOFA 對于生態的設計和思考。 sofaboot 系列 SOFABoot-00-sofaboot 概覽 SOFABoot-01-螞蟻金服開源的 s…

微信小程序日常開發問題整理

微信小程序日常開發問題整理 1 切換渲染模式1.1 WebView 的鏈接在模擬器可以打開&#xff0c;手機上無法打開。 1 切換渲染模式 1.1 WebView 的鏈接在模擬器可以打開&#xff0c;手機上無法打開。 在 app.json 中看到如下配置項&#xff0c;那么當前項目就是 keyline 渲染模式…

【Altium Designer】銅皮編輯

一、動態銅皮與靜態銅皮的區分與切換 動態銅皮&#xff08;活銅&#xff09;&#xff1a; 通過快捷鍵 PG 創建&#xff0c;支持自動避讓其他網絡對象&#xff0c;常用于大面積鋪銅&#xff08;如GND或電源網絡&#xff09;。修改動態銅皮后&#xff0c;需通過 Tools → Polygo…

Java「Deque」 方法詳解:從入門到實戰

Java Deque 各種方法解析&#xff1a;從入門到實戰 在 Java 編程中&#xff0c;Deque&#xff08;雙端隊列&#xff09;是一個功能強大的數據結構&#xff0c;允許開發者從隊列的兩端高效地添加、刪除和檢查元素。作為 java.util 包中的一部分&#xff0c;Deque 接口繼承自 Qu…

ffmpeg+QOpenGLWidget顯示視頻

?一個基于 ?FFmpeg 4.x? 和 QOpenGLWidget的簡單視頻播放器代碼示例&#xff0c;實現視頻解碼和渲染到 Qt 窗口的功能。 1&#xff09;ffmpeg庫界面&#xff0c;視頻解碼支持軟解和硬解方式。 硬解后&#xff0c;硬件解碼完成需要將數據從GPU復制到CPU。優先采用av_hwf…

深入解析嵌入式內核:從架構到實踐

一、嵌入式內核概述 嵌入式內核是嵌入式操作系統的核心組件&#xff0c;負責管理硬件資源、調度任務、處理中斷等關鍵功能。其核心目標是在資源受限的環境中提供高效、實時的控制能力。與通用操作系統不同&#xff0c;嵌入式內核通常具有高度可裁剪性、實時性和可靠性&#xff…

20250324-使用 `nltk` 的 `sent_tokenize`, `word_tokenize、WordNetLemmatizer` 方法時報錯

解決使用 nltk 的 sent_tokenize, word_tokenize、WordNetLemmatizer 方法時報錯問題 第 2 節的手動方法的法1可解決大部分問題&#xff0c;可首先嘗試章節 2 的方法 1. nltk.download(‘punkt_tab’) LookupError: *******************************************************…

『 C++ 』多線程同步:條件變量及其接口的應用實踐

文章目錄 條件變量概述條件變量簡介條件變量的基本用法 案例&#xff1a;兩個線程交替打印奇偶數代碼解釋 std::unique_lock::try_lock_until 介紹代碼示例代碼解釋注意事項 std::condition_variable::wait 詳細解析與示例std::condition_variable::wait 接口介紹代碼示例代碼解…