在 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);}
}
這些步驟確保了在需要重采樣的情況下,能夠正確處理每個軌道的數據,并將其轉換為所需的格式。