Android Audio基础(13)——audiomixer

发布于:2025-03-28 ⋅ 阅读:(33) ⋅ 点赞:(0)

在 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(); 
    // 获取当前帧数 numFrames
    size_t numFrames = mFrameCount;
 
    // 遍历每个音频组 mGroups
    for (const auto &pair : mGroups) {
        const auto &group = pair.second;
        // 获取第一个轨道 t1
        const std::shared_ptr<TrackBase> &t1 = mTracks[group[0]];
 
        // 清除了临时缓冲区 outTemp
        memset(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);
    }
}

这些步骤确保了在需要重采样的情况下,能够正确处理每个轨道的数据,并将其转换为所需的格式。