Android Audio混音实践篇

Android Audio混音实践篇


简介

本篇文章主要介绍Android Audio模块的MixerThread混音业务如何实现的?建议在阅读这篇文章之间先阅读混音理论基础篇,并且在分析源码之前:头脑里构想一个大致的混音过程,多路音频AudioTrack叠加在一起进行混音,多路音频混音的是数据对象是pcm数据,它如何叠加,混音后的音量又是如何处理?

首先回忆一下单路AudioTrack的音频播放流程,如下:

  1. 应用层创建AudioTrack,并配置播放音频的模式mode、采样率、format以及usage、contentType等信息。
  2. 在Framework层,会根据采样率、format以及usage等信息去AudioPolicyManager中寻找到一个合适的output输出通道,寻找的依据就是根据前面的音频配置信息去configuration配置文件中去找到合适stream_type和device,然后根据stream_type和device找到output输出通道。
  3. 这个output输出通道实质就是AudioFlinger里面的回播线程PlaybackThread;使用PlaybackThread为应用层的AudioTrack创建服务端对应的Track,并在Track中根据模式mode去创建匿名共享内存,用于传递音频pcm数据。
  4. 应用层使用AudioTrack的write写入音频数据,通过匿名共享内存传递到回播线程播放,因为本章讲解的是混音,所以就音频数据传到了混音回播线程MixerThread

最后,我们分析的就是MixerThread。


创建MixerThread

创建MixerThread线程位于解析audio的configuration配置文件后,创建输出通道output就会创建各个PlaybackThread,其中就包括MixerThread,源码如下:

AudioFlinger::MixerThread::MixerThread(const sp<AudioFlinger>& audioFlinger, AudioStreamOut* output,
        audio_io_handle_t id, audio_devices_t device, bool systemReady, type_t type)
    :   PlaybackThread(audioFlinger, output, id, device, type, systemReady),
        mFastMixerFutex(0),
        mMasterMono(false)
{
    setMasterBalance(audioFlinger->getMasterBalance_l());
    //混音器AudioMixer
    mAudioMixer = new AudioMixer(mNormalFrameCount, mSampleRate);

    if (type == DUPLICATING) {
        return;
    }
    // 通向HAL层的变量,混音后的数据会通过mOutputSink发送到HAL
    mOutputSink = new AudioStreamOutSink(output->stream);
    .....
}

在MixerThread中,混音的核心就在AudioMixer混音器,混音的相关逻辑就在里面。


PlaybackThread回播线程

为什么要先讲解PlaybackThread线程,因为MixerThread继承PlaybackThread,而PlaybackThread的threadLoop函数尤为重要,主要负责加载音频数据,调用AudioMixer处理混音,然后把混音后的数据写入到HAL层,threadLoop函数相当复杂,这里大致列出其中的几个关键点:

bool AudioFlinger::PlaybackThread::threadLoop()
{
    ......
    //将需要混音的Track赋值到AudioMixer中,并配置相关混音参数Paramter
    mMixerStatus = prepareTracks_l(&tracksToRemove);
    //开始混音
    threadLoop_mix();
    //将混音后的数据写入到HAL中去
    ret = threadLoop_write();
    .....
}

threadLoop是回播线程的循环函数,会一直循环运行,threadLoop_mix由MixerThread类重写了,所以和之前文章中讲解的稍有不同。

因为混音逻辑主要在AudioMixer类中,而音频pcm数据和配置参数又分属在多个Track类中,所以要把大部分信息如音频format、采样率音频读取缓存地址、写入缓冲地址都要弄到AudioMixer中去,并且不同Track中采样率可能不一致还要为AudioMixer配置重采样管理器等等工作,这些工作都是在prepareTracks_l函数中来完成,在开始分析这个函数之前,先看看Track(位于PlaybackTracks.h)中的一些重要成员变量,后续会使用到;

PlaybackThread的Track

class Track : public TrackBase, public VolumeProvider {
    ....
    //mSharedBuffer也就是应用进程客户端传递过来,为0表示AudioFlinger创建共享内存,不为0表示应用进程创建共享内存
     sp<IMemory>         mSharedBuffer;
    //track的音频类型,如AUDIO_STREAM_VOICE_CALL、AUDIO_STREAM_MUSIC等
    const audio_stream_type_t mStreamType;
    //混音后音频数据存放的地址
    effect_buffer_t     *mMainBuffer;
    int32_t             *mAuxBuffer;
    ....
}
//构造函数
AudioFlinger::PlaybackThread::Track::Track(.....):
    mSharedBuffer(sharedBuffer),
    mStreamType(streamType),
    //sinkBuffer是混音后音频数据存放的地址
    mMainBuffer(thread->sinkBuffer()),
    mAuxBuffer(NULL),
    ...{}

mMainBuffer来源于thread->sinkBuffer,sink翻译为输出,实质上也就是混音后数据存放的地址,最后在threadLoop_write函数中时会从sinkBuffer将音频数据输出到HAL层;而mAuxBuffer则是音效Effect那边传入的buffer地址,混音时会将部分音频写到这个mAuxBuffer用于Effect特性使用;

同理,在AudioMixer混音时内部也有一个Track来对应上面的Audio的Track

AudioMixer的Track

混音器内部的Track和外面AudioFlinger的Track是一一对应的,保存了Audio Track的重要信息,看看内部Track的成员变量就知道:

struct Track {
    //needs是一个状态变量,其中每个bit位表示不同的状态
    uint32_t    needs;
    union {
    /* volume代表应用层设置的音量,这里MAX_NUM_VOLUMES是2,表示左右两个通道的音量值,
    * 应用层设置音量0~1的float音量值,转换到这里是int16,转换规则是设置音量float*16bit
    * 的最大值,完成类型且值大小比例转换
    **/
    int16_t     volume[MAX_NUM_VOLUMES]; 
    int32_t     volumeRL;
    };
    /**
     * MAX_NUM_VOLUMES一般是2,即是左右声道的音量值
     * **/
    int32_t     prevVolume[MAX_NUM_VOLUMES];  //上一次设置的音量值,是32bit;是由volume[MAX_NUM_VOLUMES]左移16bit转换的
    int32_t     volumeInc[MAX_NUM_VOLUMES];   //如果是渐变音量ramp,volumeInc代表每次变化的音量值
    ........
    //源track的channelMask
    audio_channel_mask_t channelMask;
    //源track的通道数
    channelCount;

    /**bufferProvider实质是PlaybackThread创建的Track,而Track继承VolumeProvider,
     * 其基类就是这个类型,通过这个provider可以调用音频数据获取接口
     * */
    AudioBufferProvider*                bufferProvider;
    //buffer会从bufferProvider中读取到真实的音频数据
    mutable AudioBufferProvider::Buffer buffer; // 8 bytes
    //hook相当重要,它会保存一个函数指针,指向当前Track混音的函数
    hook_t      hook; 
    //无论是否重采样,buffer的音频裸数据raw会写入到mIn
    const void  *mIn;             // current location in buffer
    //混音器
    std::unique_ptr<AudioResampler> mResampler;
    uint32_t            sampleRate;
    //混音后音频数据输出地址,存放的是真实音频数据,还要乘以mVloume音量大小
    //这个地址一般是外部Track的MainBuffer,而MainBuffer又是PlaybackThread的mSinkBuffer指定
    int32_t*           mainBuffer;
    /** 混音后辅助音频输出地址,这个辅助音频值为真实音频数据的极小部分,
     * 几分之一那种, 最后还要乘以mAuxLevel;
    * 这个地址一般由外部Track的auxBuffer指定,而auxBuffer又是
    **/
    int32_t*           auxBuffer;

    int32_t     sessionId;
    //最终混音之后的输出的格式,分析代码为AUDIO_FORMAT_PCM_16_BIT
    audio_format_t mMixerFormat;     // output mix format: AUDIO_FORMAT_PCM_(FLOAT|16_BIT)
    //track源格式
    audio_format_t mFormat;          // input track format
    //混音内部格式  分析代码统一为AUDIO_FORMAT_PCM_FLOAT
    audio_format_t mMixerInFormat;   // mix internal format AUDIO_FORMAT_PCM_(FLOAT|16_BIT)
    audio_channel_mask_t mMixerChannelMask;         //源通道存储MASK
    uint32_t             mMixerChannelCount;        //源通道数
}

看到这里你可能还不知道每个变量如何使用,在后续的流程中,你会逐步了解混音中如何来使用。
务必记住hook、mainBuffer、volume音量相关的变量

从应用层到混音内部的Track对应框架:

在这里插入图片描述

下面,我们分析混音执行的逻辑:


prepareTracks_l函数混音前准备工作

回到prepareTracks_l函数中,看看在混音之前,为AudioMixer混音器具体做了哪些工作?如下代码:

AudioFlinger::PlaybackThread::mixer_state AudioFlinger::MixerThread::prepareTracks_l(
        Vector< sp<Track> > *tracksToRemove)
{
    //processDeletedTrackIds参数传入的是一个函数指针,而processDeletedTrackIds会遍历mDeletedTrackIds
    (void)mTracks.processDeletedTrackIds([this](int trackId) {
        // 集合,把需要删除的track删除掉,如果混音器AudioMixer也存在此track,把混音器里面对应的track也删除 
        if (mAudioMixer->exists(trackId)) {
            mAudioMixer->destroy(trackId);
        }
    });
    //mDeletedTrackIds集合请空,保存了需要删除的track
    mTracks.clearDeletedTrackIds();
    //初始化混音状态为IDLE
    mixer_state mixerStatus = MIXER_IDLE;
    //mActiveTracks保存了活跃的Track,也就是要被混音的
    size_t count = mActiveTracks.size();
    //materVolume是系统的主音量,还有其他的type类型音量等
    float masterVolume = mMasterVolume;
    //mMasterMute为系统主音量是否静音状态
    bool masterMute = mMasterMute;
    //如果系统为静音则设置主音量为0不发声
    if (masterMute) {
        masterVolume = 0;
    }
    ......

    bool noFastHapticTrack = true;
    //遍历当前MixerThread线程下所有的存活Track
    for (size_t i=0 ; i<count ; i++) {
        const sp<Track> t = mActiveTracks[i];

        // this const just means the local variable doesn't change
        Track* const track = t.get();
        .........
        {

        audio_track_cblk_t* cblk = track->cblk();
        const int trackId = track->id();

        //先检查以下混音器audioMixer是否已经创建内部Track了
        if (!mAudioMixer->exists(trackId)) {
            //在混音前内部AudioMixer创建一个Track,对应外面AudioFlinger的Track
            status_t status = mAudioMixer->create(
                    trackId,
                    track->mChannelMask,
                    track->mFormat,
                    track->mSessionId);
            ......
        }

        //计算一次混音最少需要多少帧
        size_t desiredFrames;
        const uint32_t sampleRate = track->mAudioTrackServerProxy->getSampleRate();
        AudioPlaybackRate playbackRate = track->mAudioTrackServerProxy->getPlaybackRate();
        //desired想要的,mNormalFrameCount是HAL层共享内存缓冲区可接收最少的帧数,配置采样率播
        //放速度计算上层应该供应最少的帧数,防止出现underrun情况
        desiredFrames = sourceFramesNeededWithTimestretch(
                sampleRate, mNormalFrameCount, mSampleRate, playbackRate.mSpeed);
        desiredFrames += mAudioMixer->getUnreleasedFrames(trackId);

        uint32_t minFrames = 1;
        if ((track->sharedBuffer() == 0) && !track->isStopped() && !track->isPausing() &&
                (mMixerStatusIgnoringFastTracks == MIXER_TRACKS_READY)) {
            minFrames = desiredFrames;
        }
        //共享内存提供的音频数据量
        size_t framesReady = track->framesReady();
        //如果已准备的音频帧大于最小帧,这说明有足够的数据进行混音;否则数据不够需要等待
        if ((framesReady >= minFrames) && track->isReady() &&
                !track->isPaused() && !track->isTerminated())
        {
            mixedTracks++;
            //开始计算音量
            int param = AudioMixer::VOLUME;
            if (track->mFillingUpStatus == Track::FS_FILLED) {
                // no ramp for the first volume setting
                track->mFillingUpStatus = Track::FS_ACTIVE;
                if (track->mState == TrackBase::RESUMING) {
                    track->mState = TrackBase::ACTIVE;
                    //由暂停到恢复,且mServer代表提供数据端部位空
                    if (cblk->mServer != 0) {
                        //加入渐变音效,也就是音量恢复时音量大小从小变大
                        param = AudioMixer::RAMP_VOLUME;
                    }
                }
                //重置重采样管理器,将混音器总的重采样管理器重置为空
                mAudioMixer->setParameter(trackId, AudioMixer::RESAMPLE, AudioMixer::RESET, NULL);
                mLeftVolFloat = -1.0;
            // FIXME should not make a decision based on mServer
            } else if (cblk->mServer != 0) {
                // If the track is stopped before the first frame was mixed,
                // do not apply ramp
                param = AudioMixer::RAMP_VOLUME;
            }

            // compute volume for this track
            uint32_t vl, vr;       // in U8.24 integer format
            float vlf, vrf, vaf;   // in [0.0, 1.0] float format
            // read original volumes with volume control
            float typeVolume = mStreamTypes[track->streamType()].volume;
            //为什么是乘法,不是加法呢,type音频音量*master主音量
            float v = masterVolume * typeVolume;
            const sp<AudioTrackServerProxy> proxy = track->mAudioTrackServerProxy;
            //这个是渐变音量,会暴露接口给应用层,由应用层决定音量变化VolumeShaper,根据released已经
            //播放的音频位置,确定渐变VolumeShaper的对应点的音量
            const float vh = track->getVolumeHandler()->getVolume(
                    track->mAudioTrackServerProxy->framesReleased()).first;
            //当前track已经pause  或者 此类型的音频已经设置静音 将把音量设置为0
            if (track->isPausing() || mStreamTypes[track->streamType()].mute
                    || track->isPlaybackRestricted()) {
                vl = vr = 0;
                vlf = vrf = vaf = 0.;
                if (track->isPausing()) {
                    track->setPaused();
                }
            } else {
                //获取当前Track的音量,mCblk内传递过来,用户端上层传入,由用户控制
                gain_minifloat_packed_t vlr = proxy->getVolumeLR();
                //左右声道值合成在vlr中,将其分解为两个单独的声道值
                vlf = float_from_gain(gain_minifloat_unpack_left(vlr));
                vrf = float_from_gain(gain_minifloat_unpack_right(vlr));
                //GAIN_FLOAT_UNITY为1.0,也就是最大不能超过1
                if (vlf > GAIN_FLOAT_UNITY) {
                    ALOGV("Track left volume out of range: %.3g", vlf);
                    vlf = GAIN_FLOAT_UNITY;
                }
                if (vrf > GAIN_FLOAT_UNITY) {
                    ALOGV("Track right volume out of range: %.3g", vrf);
                    vrf = GAIN_FLOAT_UNITY;
                }
                //为啥做乘法,看混音理论派音量分贝叠加
                vlf *= v * vh;
                vrf *= v * vh;
                ......
            }

            track->setFinalVolume((vrf + vlf) / 2.f);
            ......
            // XXX: these things DON'T need to be done each time
            mAudioMixer->setBufferProvider(trackId, track);
            mAudioMixer->enable(trackId);
            //param可能取值VOLUME和RAMP_VOLUME 设置音量或auxlevel到AudioMixer的Track的mAuxLevel或mVloume
            mAudioMixer->setParameter(trackId, param, AudioMixer::VOLUME0, &vlf);
            mAudioMixer->setParameter(trackId, param, AudioMixer::VOLUME1, &vrf);
            mAudioMixer->setParameter(trackId, param, AudioMixer::AUXLEVEL, &vaf);
            mAudioMixer->setParameter(
                trackId,
                AudioMixer::TRACK,
                AudioMixer::FORMAT, (void *)track->format());
            mAudioMixer->setParameter(
                trackId,
                AudioMixer::TRACK,
                AudioMixer::CHANNEL_MASK, (void *)(uintptr_t)track->channelMask());
            mAudioMixer->setParameter(
                trackId,
                AudioMixer::TRACK,
                AudioMixer::MIXER_CHANNEL_MASK,
                (void *)(uintptr_t)(mChannelMask | mHapticChannelMask));
           
            //创建重采样管理器
            mAudioMixer->setParameter(
                trackId,
                AudioMixer::RESAMPLE,
                AudioMixer::SAMPLE_RATE,
                (void *)(uintptr_t)reqSampleRate);

            AudioPlaybackRate playbackRate = proxy->getPlaybackRate();
            //设置回播率
            mAudioMixer->setParameter(
                trackId,
                AudioMixer::TIMESTRETCH,
                AudioMixer::PLAYBACK_RATE,
                &playbackRate);
            //mainBuffer是音频数据来源的地址    
            mAudioMixer->setParameter(
                    trackId,
                    AudioMixer::TRACK,
                    AudioMixer::MAIN_BUFFER, (void *)track->mainBuffer());
            //auxBuffer是音频效果effect那边的buffer        
            mAudioMixer->setParameter(
                trackId,
                AudioMixer::TRACK,
                AudioMixer::AUX_BUFFER, (void *)track->auxBuffer());

           .....
            if (mMixerStatusIgnoringFastTracks != MIXER_TRACKS_READY ||
                    mixerStatus != MIXER_TRACKS_ENABLED) {
                mixerStatus = MIXER_TRACKS_READY;
            }
        } else {
            //音频数据没准备够的处理
            .......
            mixerStatus = MIXER_TRACKS_ENABLED;
        }

        }

    }
    //返回状态
    return mixerStatus;
}

从上面代码可以得知主要分为以下几个工作:

  • 查看音频数据是否充足
  • 计算音量
  • 将外部Track属性、参数等工作设置到AudioMixer内部的Track中

最后,返回mixerStatus状态到threadLoop函数中,会根据返回状态决定是否混音音频数据还是睡眠等待音频数据填满。

计算音频数据准备是否充足

首先,从应用层到AudioFlinger中间通过匿名共享内存(大小固定)传递音频数据,同理AudioFlinger到HAL层也是匿名共享内存来传递音频数据。
其次,HAL层的缓存区在建立时将其大小buffersize传递给了PlaybackThread,计算出来该缓冲区处理音频数据最小帧数为mNormalFrameCount.
最后,由于Kernal层处理音频的采样率和应用层的音频采样率可能不相等,所以要mNormalFrameCount转换一下才行,判断应用层提供的数据大于它才行。

//计算一次混音最少需要多少帧
size_t desiredFrames;
const uint32_t sampleRate = track->mAudioTrackServerProxy->getSampleRate();
AudioPlaybackRate playbackRate = track->mAudioTrackServerProxy->getPlaybackRate();
//desired想要的,mNormalFrameCount是HAL层共享内存缓冲区可接收最少的帧数,配置采样率播
//放速度计算上层应该供应最少的帧数,防止出现underrun情况
desiredFrames = sourceFramesNeededWithTimestretch(
        sampleRate, mNormalFrameCount, mSampleRate, playbackRate.mSpeed);
desiredFrames += mAudioMixer->getUnreleasedFrames(trackId);
//minFrames默认等于1,是假如是STATIC模式,一次写入即可
uint32_t minFrames = 1;
if ((track->sharedBuffer() == 0) && !track->isStopped() && !track->isPausing() &&
        (mMixerStatusIgnoringFastTracks == MIXER_TRACKS_READY)) {
    minFrames = desiredFrames;
}

看看这个sourceFramesNeededWithTimestretch是如何计算的?

static inline size_t sourceFramesNeededWithTimestretch(
        uint32_t srcSampleRate, size_t dstFramesRequired, uint32_t dstSampleRate,
        float speed) {
    // required is the number of input frames the resampler needs
    size_t required = sourceFramesNeeded(srcSampleRate, dstFramesRequired, dstSampleRate);
    // to deliver this, the time stretcher requires:
    return required * (double)speed + 1 + 1; // accounting for rounding dependencies
}

static inline size_t sourceFramesNeeded(
        uint32_t srcSampleRate, size_t dstFramesRequired, uint32_t dstSampleRate) {
    /* 为什么要这么做?可以这么理解:
    * 1. 采样器的采样率sampleRate=count/单位时间h; 
    * 2. 同理,播放时将音频数据单位时间内处理的数量也是有一个转换率,也可以理解为采样率,应该叫转换率,也就是下面的公式dstSampleRate
    * 3. dstFrameRequireed就是播放时,转换率能处理完的数据;然后按照源采样率公式和目标采样率公式就可以计算出源要提供多少的数据
    * 才能满足目的帧数
    **/
    return srcSampleRate == dstSampleRate ? dstFramesRequired :
            size_t((uint64_t)dstFramesRequired * srcSampleRate / dstSampleRate + 1 + 1);
}

应用端提供的大小大致是这个过程,看上面sourceFramesNeeded函数内注释,不同采样率的转换规则,最后要乘上播放速度即可;细节方面还是有许多疑问:

转换后的帧数为什么存在着加1?不担心变大后HAL层缓冲区放不下处理不过来吗?乘speed播放速度也是,不担心HAL缓冲区放不下吗? 对这块理解的可在评论区留言

最后计算出来desiredFrames就是HAL能处理的最小帧数大小,只要应用层提供的数据大于它就说明数据充足,可以进行下一步了。

音量大小计算

在Android的音频系统中,音量可以分为这些音量:

  • 系统音量masterVolume
  • 类型音量typeVolume如music、call、alarm等
  • AudioTrack用户设置的音量volume
    除以上外,AudioTrack还提供了VolumeShape渐变音量,如果用户提供了渐变音量,则最终的音量也会变化;还有一个状态变量mute影响着音量,mute代表着静音,音量大小为0;
    音量计算见上面“prepareTracks_l函数混音前准备工作”章节,这里解锁下这么多音量整合为最终的音量是通过乘法来处理的:
    f i n a l v o l u m = m a s t e r V o l u m e ∗ t y p e V o l u m e ∗ v o l u m e . . . . final volum = masterVolume * typeVolume * volume .... finalvolum=masterVolumetypeVolumevolume....
    因为音量是以分贝为单位计算的,而分贝是无量纲的,所以当多个音量叠加时不能用加法来处理,因为1分贝加1分贝并不等于两分贝,那为什么要用乘法呢? 简单来说按照分贝的定义公式,分贝叠加的效果近似于乘法关系,所以这里是用了乘法,具体解释参考我的另一篇文字混音理论基础

设置AudioMixer内部Track属性

设置的内容很多,包含format、sampleRate、音量volume、音频数据输入地址mainBuffer、重采样器等等,都是通过统一的方法setParameter来设置进去的,我们进去看看函数:

void AudioMixer::setParameter(int name, int target, int param, void *value)
{
    LOG_ALWAYS_FATAL_IF(!exists(name), "invalid name: %d", name);
    const std::shared_ptr<Track> &track = mTracks[name];

    int valueInt = static_cast<int>(reinterpret_cast<uintptr_t>(value));
    int32_t *valueBuf = reinterpret_cast<int32_t*>(value);

    switch (target) {
    case TRACK:
        switch (param) {
        case CHANNEL_MASK: 
        case MAIN_BUFFER:
        case AUX_BUFFER:
        case FORMAT: 
        case MIXER_FORMAT: 
        case MIXER_CHANNEL_MASK:
        case HAPTIC_ENABLED: 
        case HAPTIC_INTENSITY:
        default:ALWAYS_FATAL("setParameter track: bad param %d", param);
    
        break;
        }
    case RESAMPLE:
        switch (param) {
        case SAMPLE_RATE:
            if (track->setResampler(uint32_t(valueInt), mSampleRate)) {
                ALOGV("setParameter(RESAMPLE, SAMPLE_RATE, %u)",
                        uint32_t(valueInt));
                invalidate();
            }
        case RESET:
        case REMOVE:
        default:
            LOG_ALWAYS_FATAL("setParameter resample: bad param %d", param);
        }
        break;

    case RAMP_VOLUME:
    case VOLUME:
        switch (param) {
        case AUXLEVEL:
            if (setVolumeRampVariables(*reinterpret_cast<float*>(value),
                    target == RAMP_VOLUME ? mFrameCount : 0,
                    &track->auxLevel, &track->prevAuxLevel, &track->auxInc,
                    &track->mAuxLevel, &track->mPrevAuxLevel, &track->mAuxInc)) {
                ALOGV("setParameter(%s, AUXLEVEL: %04x)",
                        target == VOLUME ? "VOLUME" : "RAMP_VOLUME", track->auxLevel);
                invalidate();
            }
            break;
        default:
            if ((unsigned)param >= VOLUME0 && (unsigned)param < VOLUME0 + MAX_NUM_VOLUMES) {
                if (setVolumeRampVariables(*reinterpret_cast<float*>(value),
                        target == RAMP_VOLUME ? mFrameCount : 0,
                        &track->volume[param - VOLUME0],
                        &track->prevVolume[param - VOLUME0],
                        &track->volumeInc[param - VOLUME0],
                        &track->mVolume[param - VOLUME0],
                        &track->mPrevVolume[param - VOLUME0],
                        &track->mVolumeInc[param - VOLUME0])) {
                    ALOGV("setParameter(%s, VOLUME%d: %04x)",
                            target == VOLUME ? "VOLUME" : "RAMP_VOLUME", param - VOLUME0,
                                    track->volume[param - VOLUME0]);
                    invalidate();
                }
            } else {
                LOG_ALWAYS_FATAL("setParameter volume: bad param %d", param);
            }
        }
        break;
        case TIMESTRETCH:
            switch (param) {
            case PLAYBACK_RATE: 
            default:
            break;

    default:
        LOG_ALWAYS_FATAL("setParameter: bad target %d", target);
    }
}

setParameter内部就是一大堆的switch-case语法,主要是往AudioMixer内部的Track设置一些参数等,捡重要的几个来讲

设置重采样器

在上面参数设置中name为RESAMPLE,param为SAMPLE_RATE就是设置重采样管理器,分析setResampler函数:


/**
 * trackSampleRate为混音前采样率  devSampleRate为混音后
 * **/
bool AudioMixer::Track::setResampler(uint32_t trackSampleRate, uint32_t devSampleRate)
{
    /** 是否需要重采样的依据:
     * 1. 当前音频采样率和目标采样率是否相等
     * 2. mResampler采样管理器是否为空,mResampler属于Track内部的成员
     * **/
    if (trackSampleRate != devSampleRate || mResampler.get() != nullptr) {
        //sampleRate为Track成员,创建Track时赋值为mSampleRate
        if (sampleRate != trackSampleRate) {
            sampleRate = trackSampleRate;
            if (mResampler.get() == nullptr) {
                ......
                //创建重采样AudioResampler,参数为重采样之后的参数
                mResampler.reset(AudioResampler::create(
                        mMixerInFormat,
                        resamplerChannelCount,
                        devSampleRate, quality));
            }
            return true;
        }
    }
    return false;
}

主要根据重采样之后的声道数、采样率以及质量来确定选择什么样的Resampler,最后设置到Track的mResampler成员

设置Track音量

在具体分析往track设置音量时,先了解以下track内部关于音量volume的成员变量有哪些:

static constexpr uint32_t MAX_NUM_VOLUMES = FCC_2; // stereo volume only 值为2
union {
//volume代表上层设置的音量,float转换为16bit时的音量值,这里数组是2表示左右两个通道
int16_t     volume[MAX_NUM_VOLUMES]; // U4.12 fixed point (top bit should be zero)
int32_t     volumeRL;
};
/**
 * MAX_NUM_VOLUMES一般是2,即是左右声道的音量值
 * **/
int32_t     prevVolume[MAX_NUM_VOLUMES];  //上一次的音量值,是32bit,渐变音量ramp_vloume时会用到
int32_t     volumeInc[MAX_NUM_VOLUMES];   //渐变音量ramp_vloume时会用到,每次音量的增量

//以下三个和上面的三个变量意义是一样的,只是他们保存的是来自应用层设置的原始音量值float类型,
float          mVolume[MAX_NUM_VOLUMES];     
float          mPrevVolume[MAX_NUM_VOLUMES]; 
float          mVolumeInc[MAX_NUM_VOLUMES];

上面的音量参数带m开头表示应用层AudioTrack设置的原始音量值float类型,非m开头是转换为int类型后的音量值,最后在混音计算时都是用非m开头的变量;上面提到了ramp_volume渐变音量,它是一个什么东西呢?简单来说就是有变化的音量,声音从高到低或从低到高,其实现原理就是每次设置音量时都用上一次的音量(preVolume)加上音量增量(volumeInc),就实现了音量的变化

有了上面的认识后,再来看AudioMixer关于音量设置setParameter(trackId, target=VOLUME/RAMP_VOLUME, param = AudioMixer::VOLUME0, value=&vlf)就简单多,这个音量实质就是给上面的volume、preVolume等变量计算赋值:

//确保param是在0~MAX_NUM_VOLUMES也就是左右声道数范围内
if ((unsigned)param >= VOLUME0 && (unsigned)param < VOLUME0 + MAX_NUM_VOLUMES) {
                if (setVolumeRampVariables(*reinterpret_cast<float*>(value),
                        //是否渐变音量
                        target == RAMP_VOLUME ? mFrameCount : 0,
                        //指针读取track音量成员地址,param - VOLUME0就是获取数组的index位置
                        &track->volume[param - VOLUME0],
                        &track->prevVolume[param - VOLUME0],
                        &track->volumeInc[param - VOLUME0],
                        &track->mVolume[param - VOLUME0],
                        &track->mPrevVolume[param - VOLUME0],
                        &track->mVolumeInc[param - VOLUME0])) {
                    ALOGV("setParameter(%s, VOLUME%d: %04x)",
                            target == VOLUME ? "VOLUME" : "RAMP_VOLUME", param - VOLUME0,
                                    track->volume[param - VOLUME0]);
                    invalidate();
                }
            } 

setVolumeRampVariables看看关键的setVolumeRampVariables函数:

newVolume 应用层设置的音量
ramp渐变音量的话就是音频帧数 否则就是0
pIntSetVolum = volume
pIntPreVolume = preVolume
pIntVolumeInc = volumeInc
pSetVolume = mVolume
pPreVolume = mPreVolume
pVolumeInc = mVolumeInc
static inline bool setVolumeRampVariables(float newVolume, int32_t ramp,
        int16_t *pIntSetVolume, int32_t *pIntPrevVolume, int32_t *pIntVolumeInc,
        float *pSetVolume, float *pPrevVolume, float *pVolumeInc) {
    //如果要新设置newValue等于之前设置的音量,就说明音量没变化,无需走下面的逻辑
    if (newVolume == *pSetVolume) {
        return false;
    }
    if (newVolume < 0) {
        newVolume = 0; // 音量不允许有负值
    } else {
        //判断newVolume属于无效值、无穷大、0或者正常数
        switch (fpclassify(newVolume)) {
        //亚正常和无效值    
        case FP_SUBNORMAL:
        case FP_NAN:
            newVolume = 0;
            break;
        //0
        case FP_ZERO:
            break; // zero volume is fine
        //无穷大
        case FP_INFINITE:
            //无穷大去最大值UNITY_GAIN_FLOAT即可,也就是1.0f最大值
            newVolume = AudioMixer::UNITY_GAIN_FLOAT;
            break;
        //正常值
        case FP_NORMAL:
        default:
            if (newVolume > AudioMixer::UNITY_GAIN_FLOAT) {
                newVolume = AudioMixer::UNITY_GAIN_FLOAT;
            }
            break;
        }
    }

    // ramp渐变音量不为0 则为播放的音频帧数count
    if (ramp != 0) {
        //计算渐变音量每次音量增量,除以ramp帧数,也就是每帧的音量变化值        
        const float inc = (newVolume - *pPrevVolume) / ramp; 
        // could be inf, cannot be nan, subnormal
        const float maxv = std::max(newVolume, *pPrevVolume);
        //inc是0不正常 非0正常
        if (isnormal(inc) // inc must be a normal number (no subnormals, infinite, nan)
                && maxv + inc != maxv) { // inc must make forward progress
            *pVolumeInc = inc; //将每次变化的音量赋值给pVolumInc,这里是float类型
        } else {
            ramp = 0; // 不渐变音量
        }
    }
    //设置的音量newVolume是float类型且在0~1之间,把它乘UNITY_GAIN_INT(16bit最大值)转换为16bit整型
    const float scaledVolume = newVolume * AudioMixer::UNITY_GAIN_INT; 
    const int32_t intVolume = (scaledVolume >= (float)AudioMixer::UNITY_GAIN_INT) ?
            AudioMixer::UNITY_GAIN_INT : (int32_t)scaledVolume;  //强转为32bit

    if (ramp != 0) {
        //intVolume是以16bit来强转32bit的,要扩展到真正的32位就移动至高位即可,
        //在减去上一次pIntPrevVolume音量值,除音频帧数ramp就得到音量增量
        const int32_t inc = ((intVolume << 16) - *pIntPrevVolume) / ramp;

        if (inc != 0) { 
            *pIntVolumeInc = inc;  //将音频增量赋值给pVolumInc,这里是int类型
        } else {
            ramp = 0; // ramp not allowed
        }
    }

    // 如果不使用渐变音量ramp,则track相关渐变音量的变量都为0
    if (ramp == 0) {
        *pVolumeInc = 0; //渐变音量的步长音量设置为0,也就是无渐变
        *pPrevVolume = newVolume;  //上一次的初始音量等于设置音量 float类型
        *pIntVolumeInc = 0;         //同pVolumeInc,只是是整型
        //上一次音量整型值,因为intVolume是用newVolume按照16bit转换的,现在要转成32bit,把低16移动到高位即可
        *pIntPrevVolume = intVolume << 16;  
    }
    *pSetVolume = newVolume;            //设置的音量float
    *pIntSetVolume = intVolume;         //设置音量整型int
    return true;
}

总结一下:
此函数就是为Track结构体内相关音量的成员变量赋值,mVolume成员就为设置的新音量值,volume为设置音量float转换的Int类型;如果是ramp_volume渐变音量,则volumeInc=(volume - preVolume)/frameCount,就是每次的音频音量增量,mVolume赋值同理。

关于Track内的其他成员赋值,在AudioMixer的setParameter函数内都会涉及到,这里就不具体展开讲解,只把最终Track内部的各个成员变量的意思介绍即可!

//bufferProvider实质是PlaybackThread创建的Track,AudioBufferProvider是它的基类
AudioBufferProvider*                bufferProvider;
//buffer会从bufferProvider中读取到真实的音频数据
mutable AudioBufferProvider::Buffer buffer; // 8 bytes
//hook函数相当重要,它可以作为函数指针,通常保存混音函数,
//AudioMixer也有一个叫mHook的成员,要注意和它的区别
hook_t      hook;
//无论是否重采样,buffer的音频裸数据raw会写入到mIn,
const void  *mIn;             // current location in buffer
//重采样器
std::unique_ptr<AudioResampler> mResampler;
//此Tack混音前的采样率
uint32_t            sampleRate;
//混音后音频数据输出地址,存放的是真实音频数据
//这个地址一般是外部Track的MainBuffer,而MainBuffer又是PlaybackThread的mSinkBuffer指定
int32_t*           mainBuffer;
/** 混音后辅助音频输出地址,这个辅助音频值为真实音频数据的极小部分,
 * 几分之一那种, 最后还要乘以mAuxLevel;
* 这个地址一般由外部Track的auxBuffer指定,而auxBuffer又是
**/
int32_t*           auxBuffer;
//最终混音之后的输出的格式,分析代码为AUDIO_FORMAT_PCM_16_BIT
audio_format_t mMixerFormat;     // output mix format: AUDIO_FORMAT_PCM_(FLOAT|16_BIT)
//track源格式
audio_format_t mFormat;          // input track format
//混音内部格式  分析代码统一为AUDIO_FORMAT_PCM_FLOAT
audio_format_t mMixerInFormat;   // mix internal format AUDIO_FORMAT_PCM_(FLOAT|16_BIT)

到这里prepareTracks函数差不多就完成了,当然这个函数还有其他部分如处理underrun数据不够的部分,不在此篇混音的重点,暂忽略!
总结一下:
prepareTracks函数就是混音之前做好准备工作,为AudioMixer混音前创建一一对应的内部Track,并为Track赋值音量、buffer、format、重采样管理器等参数,为混音前做好准备工作


开始混音

回看上面的《PlaybackThread回播线程》章节,prepareTracks完成后就会执行到threadLoop_mix混音函数,不难猜想到混音函数肯定是对上个步骤各个Track进行混音,进去看看:

void AudioFlinger::MixerThread::threadLoop_mix()
{
    // mix buffers...
    mAudioMixer->process();
    mCurrentWriteLength = mSinkBufferSize;

    if ((mSleepTimeUs == 0) && (sleepTimeShift > 0)) {
        sleepTimeShift--;
    }
    mSleepTimeUs = 0;
    mStandbyTimeNs = systemTime() + mStandbyDelayNs;
    //TODO: delay standby when effects have a tail

}

很简单,就是执行混音前AudioMixer的process函数:

void        process() {
    for (const auto &pair : mTracks) {
        // 清除缓冲区的buffer
        const std::shared_ptr<Track> &t = pair.second;
        if (t->mKeepContractedChannels) {
            t->clearContractedBuffer();
        }
    }
    //执行混音函数mHook
    (this->*mHook)();
    processHapticData();
}

重点就是执行mHook函数指针保存的函数,mHook是AudioMixer的成员变量,它指向了谁呢?
还记得setParameter时,经常会调用invalidate()函数,这个函数就是给mHook赋值的,如下:

void invalidate() {
    mHook = &AudioMixer::process__validate;
}

直接去看process__validate函数把!

//AudioMixer中name为mainBuffer, value为数组集合存储的name
std::unordered_map<void * /* mainBuffer */, std::vector<int /* name */>> mGroups;
// AudioMixer中保存所有的已经enable的track的name
std::vector<int /* name */> mEnabled;
// AudioMixer中保存所有的Track
std::map<int /* name */, std::shared_ptr<Track>> mTracks;

void AudioMixer::process__validate()
{
    bool all16BitsStereoNoResample = true;
    bool resampling = false;
    bool volumeRamp = false;

    mEnabled.clear();
    //存储此次混音的Tracks
    mGroups.clear();
    //遍历AudioMixer内部所有的Track
    for (const auto &pair : mTracks) {
        const int name = pair.first;
        const std::shared_ptr<Track> &t = pair.second;
        //每个track在prepareTracks时会enable
        if (!t->enabled) continue;
        //emplace_back是往map里面添加name,但是效率push_back高,push_back会依次调用构造函数和复制函数
        //emplace_back根据参数调用构造函数, 
        mEnabled.emplace_back(name);  // we add to mEnabled in order of name.
        //注意mGroup key-mainBuffer混音后的输出地址  value是Track的唯一表示name的集合vector;
        //因为可能有很多Track都使用同一个输出地址
        mGroups[t->mainBuffer].emplace_back(name); // mGroups also in order of name.
        //n是一个状态字段,每个bit代表不同意思
        uint32_t n = 0;
        // FIXME can overflow (mask is only 3 bits)
        n |= NEEDS_CHANNEL_1 + t->channelCount - 1;
        //是否需要重采样,查看t的重采样成员是否为空
        if (t->doesResample()) {
            n |= NEEDS_RESAMPLE;
        }
        //AUX是啥? aux in辅助音频接入接口,外部音源可以通过此接口接入到内部,不知道这里是不是这个意思
        if (t->auxLevel != 0 && t->auxBuffer != NULL) {
            n |= NEEDS_AUX;
        }
        //volumeInc保存渐变音量每次的增量,如果有值说明需要混音的时候要有渐变音量
        if (t->volumeInc[0]|t->volumeInc[1]) {
            volumeRamp = true;
        } else if (!t->doesResample() && t->volumeRL == 0) {
            //没有重采样管理器  音量也为0  就静默此track
            n |= NEEDS_MUTE;
        }
        t->needs = n;  //将状态赋给track的needs

        if (n & NEEDS_MUTE) {
            //沉默静音就不做任何处理,也不读取音频数据
            t->hook = &Track::track__nop; //track__nop函数为空
        } else {
            if (n & NEEDS_AUX) {
                //双声道不采样为false,就是不重采样
                all16BitsStereoNoResample = false;
            }
            //需要重采样
            if (n & NEEDS_RESAMPLE) {
                all16BitsStereoNoResample = false;
                resampling = true;
                //决定track的混音函数hook是什么? mMixerChannelCount为源track的通道数  
                //mMixerInFormat为混音内部格式 mMixerFormat为混音之后的格式
                t->hook = Track::getTrackHook(TRACKTYPE_RESAMPLE, t->mMixerChannelCount,
                        t->mMixerInFormat, t->mMixerFormat);
            //不需要重采样            
            } else {
                //单通道
                if ((n & NEEDS_CHANNEL_COUNT__MASK) == NEEDS_CHANNEL_1){
                    t->hook = Track::getTrackHook(
                        //目的是多通道  源是单通道 type就是TRACKTYPE_NORESAMPLEMONO; 否则就是TRACKTYPE_NORESAMPLE
                            (t->mMixerChannelMask == AUDIO_CHANNEL_OUT_STEREO  // TODO: MONO_HACK
                                    && t->channelMask == AUDIO_CHANNEL_OUT_MONO)
                                ? TRACKTYPE_NORESAMPLEMONO : TRACKTYPE_NORESAMPLE,
                            t->mMixerChannelCount,
                            t->mMixerInFormat, t->mMixerFormat);
                    all16BitsStereoNoResample = false;
                }
                //多通道
                if ((n & NEEDS_CHANNEL_COUNT__MASK) >= NEEDS_CHANNEL_2){
                    //这里只是指定Track的hook混音函数,到底是谁在哪个地方用的呢?在本页内,重采样操作之后就会调用Track的hook函数进行混音
                    t->hook = Track::getTrackHook(TRACKTYPE_NORESAMPLE, t->mMixerChannelCount,
                            t->mMixerInFormat, t->mMixerFormat);
                }
            }
        }
    }

    //mHook重新制定函数指针process__nop,也就是去读取音频数据的函数
    mHook = &AudioMixer::process__nop;
    //mEnabled保存了需要混音track的名字集合
    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是AudioMixerc重采样函数
            mHook = &AudioMixer::process__genericResampling;
        //不需要重采样    
        } else {
            // 指定mHook为不走重采样函数
            mHook = &AudioMixer::process__genericNoResampling;
            if (all16BitsStereoNoResample && !volumeRamp) {
                //如果使用的track数量只有一个
                if (mEnabled.size() == 1) {
                    const std::shared_ptr<Track> &t = mTracks[mEnabled[0]];
                    if ((t->needs & NEEDS_MUTE) == 0) {  //并且还是mute静音
                        mHook = getProcessHook(PROCESSTYPE_NORESAMPLEONETRACK,
                                t->mMixerChannelCount, t->mMixerInFormat, t->mMixerFormat);
                    }
                }
            }
        }
    }
   //因为上面步骤已经重新制定mHook和Track内的hook函数指针,这里process会执行mHook,也就是混音函数
   process();
    .......
}

上面函数关键点有以下几个:

  • getTrackHook函数为当前track指定hook函数指针成员指定专属他自己的混音函数
  • 为AudioMixer指定mHook函数指针成员,处理当前混音器下面所有Track逻辑
  • mHook内部也有可能调用track内部的hook,根据业务不同来
    这种设计模式也比较新颖,留到文章后面去讨论!

getTrackHook确定track自身的混音函数

如下,因为是制定hook函数指针,肯定有一堆if-else或者switch-case来选定,还真的是:

AudioMixer::hook_t AudioMixer::Track::getTrackHook(int trackType, uint32_t channelCount,
        audio_format_t mixerInFormat, audio_format_t mixerOutFormat __unused)
{
    .....
    switch (trackType) {
    case TRACKTYPE_NOP:
        return &Track::track__nop;
    //需要重采样
    case TRACKTYPE_RESAMPLE:
        switch (mixerInFormat) {
        //默认的内部格式mixerInFormat都是FLOAT
        case AUDIO_FORMAT_PCM_FLOAT:
            return (AudioMixer::hook_t) &Track::track__Resample<
                    MIXTYPE_MULTI, float /*TO*/, float /*TI*/, TYPE_AUX>;
        case AUDIO_FORMAT_PCM_16_BIT:
            return (AudioMixer::hook_t) &Track::track__Resample<
                    MIXTYPE_MULTI, int32_t /*TO*/, int16_t /*TI*/, TYPE_AUX>;
        default:
            LOG_ALWAYS_FATAL("bad mixerInFormat: %#x", mixerInFormat);
            break;
        }
        break;
    //单声道且不需要重采样    
    case TRACKTYPE_NORESAMPLEMONO:
        switch (mixerInFormat) {
        case AUDIO_FORMAT_PCM_FLOAT:
            //MIXTYPE_MONOEXPAND单通道要扩展为多通道吗?
            return (AudioMixer::hook_t) &Track::track__NoResample<
                            MIXTYPE_MONOEXPAND, float /*TO*/, float /*TI*/, TYPE_AUX>;
        case AUDIO_FORMAT_PCM_16_BIT:
            return (AudioMixer::hook_t) &Track::track__NoResample<
                            MIXTYPE_MONOEXPAND, int32_t /*TO*/, int16_t /*TI*/, TYPE_AUX>;
        default:
            LOG_ALWAYS_FATAL("bad mixerInFormat: %#x", mixerInFormat);
            break;
        }
        break;
    //多声道不需要重采样
    case TRACKTYPE_NORESAMPLE:
        switch (mixerInFormat) {
        case AUDIO_FORMAT_PCM_FLOAT:
            //源可能有多个通道
            return (AudioMixer::hook_t) &Track::track__NoResample<
                    MIXTYPE_MULTI, float /*TO*/, float /*TI*/, TYPE_AUX>;
        case AUDIO_FORMAT_PCM_16_BIT:
            return (AudioMixer::hook_t) &Track::track__NoResample<
                    MIXTYPE_MULTI, int32_t /*TO*/, int16_t /*TI*/, TYPE_AUX>;
        default:
            LOG_ALWAYS_FATAL("bad mixerInFormat: %#x", mixerInFormat);
            break;
        }
        break;
    default:
        LOG_ALWAYS_FATAL("bad trackType: %d", trackType);
        break;
    }
    return NULL;
}

以上就是制定hook函数指针的地方,最终真的混音逻辑,外层3个case抽一个进去了解即可,它们的实现都是大同小异的!

track真实混音hook
TRACKTYPE_RESAMPLE需要重采样的混音

选取这个hook函数指针:

case AUDIO_FORMAT_PCM_FLOAT:
    return (AudioMixer::hook_t) &Track::track__Resample<
            MIXTYPE_MULTI, float /*TO*/, float /*TI*/, TYPE_AUX>;

track__Resample的泛型参数记住,很关键,决定最终选取哪个函数:

template <int MIXTYPE, typename TO, typename TI, typename TA>
//TO* out混音后输出地址 outFrameCount音频输出帧数  
//temp为缓存地址,保存当前track的数据,但是最终都会累加到out地址上
void AudioMixer::Track::track__Resample(TO* out, size_t outFrameCount, TO* temp, TA* aux)
{
    ALOGVV("track__Resample\n");
    mResampler->setSampleRate(sampleRate);
    const bool ramp = needsRamp();
    if (ramp || aux != NULL) {
        //重采样音量设置为最大
        mResampler->setVolume(UNITY_GAIN_FLOAT, UNITY_GAIN_FLOAT);
        memset(temp, 0, outFrameCount * mMixerChannelCount * sizeof(TO));
        //开始重采样,重采样后的数据输出到temp缓冲区
        mResampler->resample((int32_t*)temp, outFrameCount, bufferProvider);
        //开始混音
        volumeMix<MIXTYPE, is_same<TI, float>::value /* USEFLOATVOL */, true /* ADJUSTVOL */>(
                out, outFrameCount, temp, aux, ramp);

    } else { // constant volume gain
        mResampler->setVolume(mVolume[0], mVolume[1]);
        mResampler->resample((int32_t*)out, outFrameCount, bufferProvider);
    }
}

这里重采样就不展开了,重采样会对track的采样率进行转换,转换为统一采样率,这样才可以混音;mResampler内部也比较复杂,深入进去又会牵扯很多,这里就不继续展开!重点看看volumeMix如何混音! 注意volumeMix的泛型,其中iS_same是对TI和float类型判断,相同为true,不同为false,继续进入volumeMix看看:

template <int MIXTYPE, bool USEFLOATVOL, bool ADJUSTVOL,
    typename TO, typename TI, typename TA>
void AudioMixer::Track::volumeMix(TO *out, size_t outFrames,
        const TI *in, TA *aux, bool ramp)
{
    //USEFLOATVOL根据传值为true
    if (USEFLOATVOL) {
        if (ramp) {
            volumeRampMulti<MIXTYPE>(mMixerChannelCount, out, outFrames, in, aux,
                    mPrevVolume, mVolumeInc,
#ifdef FLOAT_AUX
                    &mPrevAuxLevel, mAuxInc
#else
                    &prevAuxLevel, auxInc
#endif
                );
                ......
        } else {
            //先看看简单的
            volumeMulti<MIXTYPE>(mMixerChannelCount, out, outFrames, in, aux,
                    mVolume,
#ifdef FLOAT_AUX
                    mAuxLevel
#else
                    auxLevel
#endif
            );
        }
    } else {
        if (ramp) {
            volumeRampMulti<MIXTYPE>(mMixerChannelCount, out, outFrames, in, aux,
                    prevVolume, volumeInc, &prevAuxLevel, auxInc);
            if (ADJUSTVOL) {
                adjustVolumeRamp(aux != NULL);
            }
        } else {
            volumeMulti<MIXTYPE>(mMixerChannelCount, out, outFrames, in, aux,
                    volume, auxLevel);
        }
    }
}

不难看出,最终都会走到volumeMulti和volumeRampMulti两个函数,注意传递的参数:
mMixerChannelCount:当前track的声道数
out:混音后数据输出地址
outFrame:混音的音频帧数
in: 混音前的数据地址
aux: 不清楚是啥意思
volume: 混音音量(非ramp渐变音量)
preVolume: 上一次的音量(ramp)
volumeInc: 此次音量的增量(ramp)

普通音量混音

然后看混音函数了,先看看普通音量的混音:

template <int MIXTYPE,
        typename TO, typename TI, typename TV, typename TA, typename TAV>
static void volumeMulti(uint32_t channels, TO* out, size_t frameCount,
        const TI* in, TA* aux, const TV *vol, TAV vola)
{
    //通道数 进入2
    switch (channels) {
    case 1:
        volumeMulti<MIXTYPE, 1>(out, frameCount, in, aux, vol, vola);
        break;
    case 2:
        volumeMulti<MIXTYPE, 2>(out, frameCount, in, aux, vol, vola);
        break;
    case 3:
        volumeMulti<MIXTYPE_MONOVOL(MIXTYPE), 3>(out, frameCount, in, aux, vol, vola);
        break;
    case 4:
        volumeMulti<MIXTYPE_MONOVOL(MIXTYPE), 4>(out, frameCount, in, aux, vol, vola);
        break;
    case 5:
        volumeMulti<MIXTYPE_MONOVOL(MIXTYPE), 5>(out, frameCount, in, aux, vol, vola);
        break;
    case 6:
        volumeMulti<MIXTYPE_MONOVOL(MIXTYPE), 6>(out, frameCount, in, aux, vol, vola);
        break;
    case 7:
        volumeMulti<MIXTYPE_MONOVOL(MIXTYPE), 7>(out, frameCount, in, aux, vol, vola);
        break;
    case 8:
        volumeMulti<MIXTYPE_MONOVOL(MIXTYPE), 8>(out, frameCount, in, aux, vol, vola);
        break;
    }
}

以2声道的为例进入看看,还是挺复杂的,又是一大堆switch-case,不过离我们最终混音已经很接近了:

template <int MIXTYPE, int NCHAN,
        typename TO, typename TI, typename TV, typename TA, typename TAV>
//in地址保存了混音之前的数据  混音之后数据保存在out  
//vol是在AudioMixer的Track中的mVolume,它是一个数组
//vola为AudioMixer的Track的mAuxLevel  
//aux为AudioMixer的Track的auxBuffer
inline void volumeMulti(TO* out, size_t frameCount,
        const TI* in, TA* aux, const TV *vol, TAV vola)
{
#ifdef ALOGVV
    ALOGVV("volumeMulti MIXTYPE:%d\n", MIXTYPE);
#endif
    if (aux != NULL) {
        do {
            TA auxaccum = 0;
            switch (MIXTYPE) {
            case MIXTYPE_MULTI:
            //NCHAN代表有几个声道
                for (int i = 0; i < NCHAN; ++i) {
                    //out就是已经累加的混音数据,in代表的是当前track的音频数据, vol[i]是当前的音量数据
                    //MixMulAux内部就是音频数据与音量相乘,不信就进去看看,还有一个auxaccum是传入地址进去
                    *out++ += MixMulAux<TO, TI, TV, TA>(*in++, vol[i], &auxaccum);
                }
                break;
            case MIXTYPE_MONOEXPAND:
                //声道扩张的情况,out多声道存储的都是单声道的值,此处in并为加加
                for (int i = 0; i < NCHAN; ++i) {
                    *out++ += MixMulAux<TO, TI, TV, TA>(*in, vol[i], &auxaccum);
                }
                in++;
                break;
            case MIXTYPE_MULTI_SAVEONLY:
                for (int i = 0; i < NCHAN; ++i) {
                    //auxaccum传入MixMulAux函数中,会累加自身和in的一部分;auxaccum += (1.0/*in类型<<字节长度-1)
                    *out++ = MixMulAux<TO, TI, TV, TA>(*in++, vol[i], &auxaccum);
                }
                break;
            case MIXTYPE_MULTI_MONOVOL:
                for (int i = 0; i < NCHAN; ++i) {
                    *out++ += MixMulAux<TO, TI, TV, TA>(*in++, vol[0], &auxaccum);
                }
                break;
            case MIXTYPE_MULTI_SAVEONLY_MONOVOL:
                for (int i = 0; i < NCHAN; ++i) {
                    *out++ = MixMulAux<TO, TI, TV, TA>(*in++, vol[0], &auxaccum);
                }
                break;
            default:
                LOG_ALWAYS_FATAL("invalid mixtype %d", MIXTYPE);
                break;
            }
            //多个通道求平均
            auxaccum /= NCHAN;
            //aux作为辅助通道保存输出
            *aux++ += MixMul<TA, TA, TAV>(auxaccum, vola);
        } while (--frameCount);
    } else {
        do {
            switch (MIXTYPE) {
            //正常有多个混音也可能走这里
            case MIXTYPE_MULTI:
                for (int i = 0; i < NCHAN; ++i) {
                    //+= 有一个累加的过程,这里用到vol,它是Track的mVloume成员,2个长度的数组
                    *out++ += MixMul<TO, TI, TV>(*in++, vol[i]);
                }
                break;
            case MIXTYPE_MONOEXPAND:
                for (int i = 0; i < NCHAN; ++i) {
                    *out++ += MixMul<TO, TI, TV>(*in, vol[i]);
                }
                in++;
                break;
            case MIXTYPE_MULTI_SAVEONLY:
                for (int i = 0; i < NCHAN; ++i) {
                    /** 做乘法,in*vol,并让结果不超过out类型的最大值
                     * 还要注意一点,如果NCHAN是2个通道,那out存储会依次
                     * 存储左右左右通道的数据
                     * **/
                    *out++ = MixMul<TO, TI, TV>(*in++, vol[i]);
                }
                break;
            case MIXTYPE_MULTI_MONOVOL:
                for (int i = 0; i < NCHAN; ++i) {
                    *out++ += MixMul<TO, TI, TV>(*in++, vol[0]);
                }
                break;
            case MIXTYPE_MULTI_SAVEONLY_MONOVOL:
                for (int i = 0; i < NCHAN; ++i) {
                    *out++ = MixMul<TO, TI, TV>(*in++, vol[0]);
                }
                break;
            default:
                LOG_ALWAYS_FATAL("invalid mixtype %d", MIXTYPE);
                break;
            }
        } while (--frameCount);
    }
}

inline TO MixMulAux(TI value, TV volume, TA *auxaccum) {
    MixAccum<TA, TI>(auxaccum, value);
    return MixMul<TO, TI, TV>(value, volume);
}
//都是做乘法,音量乘音频数据,还需要考虑数据溢出、大小的问题  大量的重载函数
template <>
inline int32_t MixMul<int32_t, int16_t, int16_t>(int16_t value, int16_t volume) {
    return value * volume;
}

template <>
inline int32_t MixMul<int32_t, int32_t, int16_t>(int32_t value, int16_t volume) {
    return (value >> 12) * volume;
}

template <>
inline int32_t MixMul<int32_t, int16_t, int32_t>(int16_t value, int32_t volume) {
    return value * (volume >> 16);
}
......

看到这里可能有点晕了,用一幅图总结下这个混音流程:

在这里插入图片描述

渐变音量混音

与普通音量混音不同点在AudioMixerOps::volumeRampMulti函数上,如下提取的关键部分:

for (int i = 0; i < NCHAN; ++i) {
    *out++ += MixMulAux<TO, TI, TV, TA>(*in++, vol[i], &auxaccum);
    //每次音量都会增加一个音量增量volinc,这个就是在setParamter时计算的volumeInc音量增量
    vol[i] += volinc[i];
}

所以在播放音频时,ramp方式的混音音量会有渐变效果!

确定AudioMixer的mHook

到这里当前Track的混音流程就完成了!但是我们还不知道如何吊起混音hook函数的,这里就要到AudioMixer的mHook函数指针去找找了;

因为在process__validate函数中,mHook可能是:

//需要重采样
mHook = &AudioMixer::process__genericResampling;
//不需要重采样
mHook = &AudioMixer::process__genericNoResampling;

这两种可能,我们逐一分析

不需要重采样process__genericNoResampling

函数如下:

void AudioMixer::process__genericNoResampling()
{
    ALOGVV("process__genericNoResampling\n");
    int32_t outTemp[BLOCKSIZE * MAX_NUM_CHANNELS] __attribute__((aligned(32)));
    //按mainBuffer遍历mGroup
    for (const auto &pair : mGroups) {
        const auto &group = pair.second;
        //group是一个vector集合,包含了许多Track,他们的输出mainBuffer地址是一致的
        for (const int name : group) {
            const std::shared_ptr<Track> &t = mTracks[name];
            t->buffer.frameCount = mFrameCount;
            //提取AudioTrack客户端共享内存的音频数据
            t->bufferProvider->getNextBuffer(&t->buffer);
            t->frameCount = t->buffer.frameCount;
            t->mIn = t->buffer.raw;
        }
        //out应该是Track的MainBuffer,也就是混音之后的输出地址
        int32_t *out = (int *)pair.first;
        size_t numFrames = 0;
        do {

            const size_t frameCount = std::min((size_t)BLOCKSIZE, mFrameCount - numFrames);
            //outTemp作为临时的混音输出地址,最后会将数据转到out,也就是mainBuffer
            memset(outTemp, 0, sizeof(outTemp));
            for (const int name : group) {
                const std::shared_ptr<Track> &t = mTracks[name];
                int32_t *aux = NULL;
                //auxBuffer地址  在混音时会读取原始音频数据的一部分值
                if (CC_UNLIKELY(t->needs & NEEDS_AUX)) {
                    aux = t->auxBuffer + numFrames;
                }
                for (int outFrames = frameCount; outFrames > 0; ) {
                    if (t->mIn == nullptr) {
                        break;
                    }
                    //inframe作为下面hook此次要混音的音频数据个数;
                    size_t inFrames = (t->frameCount > outFrames)?outFrames:t->frameCount;
                    if (inFrames > 0) {
                        //这里没有把mIn音频数据传入到hook函数中,是因为hook指针指向的函数是Track类自身的函数
                        //,这个函数可以调用Track内部的成员,也就是mIn,拿到mIn的原始音频数据后混音输出到outTemp
                        //完成混音到数据转移;这个hook函数可以理解为混音函数,涉及多音轨音频数据叠加的
                        (t.get()->*t->hook)(
                                outTemp + (frameCount - outFrames) * t->mMixerChannelCount,
                                inFrames, mResampleTemp.get() /* naked ptr */, aux);
                        t->frameCount -= inFrames;
                        outFrames -= inFrames;
                        if (CC_UNLIKELY(aux != NULL)) {
                            aux += inFrames;
                        }
                    }
                    //混音完成后,释放buffer
                    if (t->frameCount == 0 && outFrames) {
                        t->bufferProvider->releaseBuffer(&t->buffer);
                        t->buffer.frameCount = (mFrameCount - numFrames) -
                                (frameCount - outFrames);
                        t->bufferProvider->getNextBuffer(&t->buffer);
                        t->mIn = t->buffer.raw;
                        if (t->mIn == nullptr) {
                            break;
                        }
                        t->frameCount = t->buffer.frameCount;
                    }
                }
            }

            const std::shared_ptr<Track> &t1 = mTracks[group[0]];
            //拷贝混音后的数据out,out也就是音频最终的输出地址
            convertMixerFormat(out, t1->mMixerFormat, outTemp, t1->mMixerInFormat,
                    frameCount * t1->mMixerChannelCount);
            //输出地址改变偏移,因为已经存储了部分数据了
            out = reinterpret_cast<int32_t*>((uint8_t*)out
                    + frameCount * t1->mMixerChannelCount
                    * audio_bytes_per_sample(t1->mMixerFormat));
            numFrames += frameCount;
        } while (numFrames < mFrameCount);

        // 释放每个track的buffer
        for (const int name : group) {
            const std::shared_ptr<Track> &t = mTracks[name];
            t->bufferProvider->releaseBuffer(&t->buffer);
        }
    }
}

代码功能很简单。
首先,从应用端取出音频数据getNextBuffer
其次,调用track的hook混音函数,进行混音并把混音的结果保存在缓存outTemp中
最后,在把混音后数据从outTemp转移到out缓存buffer,也就是混音后输出的mainbuffer地址

需要重采样process__genericResampling
void AudioMixer::process__genericResampling()
{
    //mOutTemp是process__validate函数中创建的缓冲区
    int32_t * const outTemp = mOutputTemp.get(); // naked ptr
    size_t numFrames = mFrameCount;

    for (const auto &pair : mGroups) {
        const auto &group = pair.second;
        //t1表示group中的第一个Track
        const std::shared_ptr<Track> &t1 = mTracks[group[0]];
        // 初始化outtemp缓冲区
        memset(outTemp, 0, sizeof(*outTemp) * t1->mMixerChannelCount * mFrameCount);
        for (const int name : group) {
            const std::shared_ptr<Track> &t = mTracks[name];
            int32_t *aux = NULL;
            if (CC_UNLIKELY(t->needs & NEEDS_AUX)) {
                aux = t->auxBuffer;
            }
            //如果确实要重采样就直接调用hook,因为hook里面自己包含了取数据相关操作
            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 can happen if the track was flushed just after having
                    // been enabled for mixing.
                    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);
                }
            }
        }
        //将混音数据保存在t1的mainBuffer中,因为group中所有的track的mainBuffer是一样
        convertMixerFormat(t1->mainBuffer, t1->mMixerFormat,
                outTemp, t1->mMixerInFormat, numFrames * t1->mMixerChannelCount);
    }
}

功能很简单,看上面代码注释即可;

混音总结

混音业务调用如下:
在这里插入图片描述

mHook、hook不仅仅只会指定图中的几个函数,这里只是针对混音业务时,可能的取值

混音的两个重要点,先确定是否需要重采样Resample,然后确定是否普通音量混音还是渐变音量混音;而且在混音时,如果track音轨数少于目标音轨,还需要进行音轨扩张;


hook设计模式思考

第一次看到这个Hook设计模式感觉还不错,不停的修改Hook函数指针,达到转换业务的逻辑,降低了模块不同业务之间的耦合,这种结构组织松散了代码结构,但是相关业务却紧密连接在一起!如下图:
在这里插入图片描述

如上图,如果不用hook模式,这种业务组合有4种调用方式,可能需要加if-else来判断业务怎么走,但是hook方式的话,就不用了,如下图:
在这里插入图片描述

提前将业务指定到hook,一条业务调用即可!

不同通道channel数时的处理策略

意思就是输入track通道的数量,和输出也就是在HAL层播放的通道数不一致时,android是如何处理的,这里分为两种策略:

  1. 源Track的通道数channel为1,而输出channel为2,处理策略就是简单将一路的音频复制到另一路即可
  2. 源Track通道数大于1,输出channel数量任意,这是另一种处理类别,相对较为复杂,在getNextBuffer函数调用时,也就是数据源拷贝时就做了处理,这种情况处理比较复杂;

源Track的通道数channel为1,而输出channel为2

在函数prepareForDownmix中:

status_t AudioMixer::Track::prepareForDownmix(){
	//如果源channelmask和目的channelmask一致,则不需要downmix,或者源是单通道,目的是多通道,也不需要down mix
     if (channelMask == mMixerChannelMask
             || (channelMask == AUDIO_CHANNEL_OUT_MONO
                     && mMixerChannelMask == AUDIO_CHANNEL_OUT_STEREO)) {
         return NO_ERROR;
    }
    ......
}

这种通道数情况直接返回,不会创建DownmixBufferProvider,同时,在确定此类的混音处理函数也是一样的:

void AudioMixer::process__validate(){
	//如果等于的话说明,源track通道数是1
   if ((n & NEEDS_CHANNEL_COUNT__MASK) == NEEDS_CHANNEL_1){
           //三目运算true表示,源channel1,混音后目的channel为2,false则只有可能目的channel大于2
       t->hook = Track::getTrackHook(
               (t->mMixerChannelMask == AUDIO_CHANNEL_OUT_STEREO  // TODO: MONO_HACK
                      && t->channelMask == AUDIO_CHANNEL_OUT_MONO)
                   ? TRACKTYPE_NORESAMPLEMONO : TRACKTYPE_NORESAMPLE,
               t->mMixerChannelCount,
               t->mMixerInFormat, t->mMixerFormat);
	}
	.....
}

mMixerChannelMask为输出后的掩码,channelMask为Track自身源的掩码,当且仅当这种情况,才会取TRACKTYPE_NORESAMPLEMONO 这种type类型,进入getTrackHook函数,发现只有这种类型的case会指定MIXTYPE_MONOEXPAND这种扩张类型,这种新类型层层执行,就会执行到最终的混音:

case MIXTYPE_MONOEXPAND:
      for (int i = 0; i < NCHAN; ++i) {
          *out++ += MixMul<TO, TI, TV>(*in, vol[i]);
      }
      in++;
      break;

NCHAN是输出后的通道数,在for循环里面发现每个通道的源数据in没有变化,说明out保存的左右声道都是一样的。

源Track的通道数channel大于1的情况

同上,在函数prepareForDownmix中,处理逻辑就会不同:

status_t AudioMixer::Track::prepareForDownmix(){
	....
	mDownmixerBufferProvider.reset(new RemixBufferProvider(channelMask,
	         mMixerChannelMask, mMixerInFormat, kCopyBufferFrameCount));         //最后一个参数默认是256
	// Remix always finds a conversion whereas Downmixer effect above may fail.
	reconfigureBufferProviders();
	....
}

mDownmixerBufferProvider是一个unique类型的指针指针,reset实质就是为这个指针设置为RemixBufferProvider类型,同时reconfigureBufferProviders函数会重新配置一遍bufferProvider,这个配置后执行过程有点类似于责任链的模式,一个接一个的执行,bufferProvider顾名思义就是,也就是数据缓存的提供者,源头也就是我们PlaybackThread里面的Track,它通过责任链的方式一个接一个的串联不同的bufferProvider,而RemixBufferProvider就是其中的一个,责任链启动触发的函数就是getNextBuffer;

RemixBufferProvider类所在的位置:./av/media/libaudioprocessing/BufferProviders.cpp
这个类的构造函数有一个方法:

memcpy_by_index_array_initialization_from_channel_mask(
            mIdxAry, ARRAY_SIZE(mIdxAry), outputChannelMask, inputChannelMask);

mIdxAry是一个数组,长度为8,保存了不同策略下应该读取那个通道的数据,这里说的策略是音频数据的存储类别,如下:

  • AUDIO_CHANNEL_REPRESENTATION_POSITION
    位置表示法,音频数据中每个bit位表示每个位置的音频数据,如前左 前右等音响
  • AUDIO_CHANNEL_REPRESENTATION_INDEX
    序号法表示,每个bit位表示不同的通道,如0位是主通道,1位是辅助通道等
    如果源track和混音后的他们的数据格式是一致的,就用memcpy_by_index_array_initialization函数来处理读取数据位数规则,反之,则用memcpy_by_index_array_initialization_dst/src_index来处理,最后读取的规则数据保存在mIdxAry这个数组中
size_t memcpy_by_index_array_initialization(int8_t *idxary, size_t idxcount,
        uint32_t dst_mask, uint32_t src_mask)
{
    size_t n = 0;
    int srcidx = 0;
    uint32_t bit, ormask = src_mask | dst_mask;

    while (ormask && n < idxcount) {
        bit = ormask & -ormask;          /* get lowest bit */
        ormask ^= bit;                   /* remove lowest bit */
        if (src_mask & dst_mask & bit) { /* matching channel */
            idxary[n++] = srcidx++;
        } else if (src_mask & bit) {     /* source channel only */
            ++srcidx;
        } else {                         /* destination channel only */
            idxary[n++] = -1; 
        }
    }   
    return n + popcount(ormask & dst_mask);
}
size_t memcpy_by_index_array_initialization_src_index(int8_t *idxary, size_t idxcount,
        uint32_t dst_mask, uint32_t src_mask) {
    size_t dst_count = popcount(dst_mask);
    if (idxcount == 0) {
        return dst_count;
    }   
    if (dst_count > idxcount) {
        dst_count = idxcount;
    }   

    size_t src_idx, dst_idx;
//idxary就是取dst_msk和src_msk取交集,保存其中的位子index
    for (src_idx = 0, dst_idx = 0; dst_idx < dst_count; ++dst_idx) {
        if (src_mask & 1) {
            idxary[dst_idx] = src_idx++;
        } else {
            idxary[dst_idx] = -1; 
        }
        src_mask >>= 1;
    }   
    return dst_idx;
}

第一个函数最终在mIdxAry保存的是两个掩码的交集bit位为1的顺序位,如:

1100 和 目的 1010 则mIdxAry: [0]=-1 [1]=1
同理 10101010,则[0]=0,[1]=1
错错错,上面这个例子举得不对,一般来说音频数据格式的channelMask都是这种连续bit位为1,比如:
单通道 0x01  双声道 0x03 四声道 0x0f 等等,所以针对第一个函数,同类型的音频存储格式来说:
源位双声道 0x03 目的四声道 0x0f,则mIdxAry: [0]=0,[1]=1,[2]=-1,[3]=-1
源位双声道 0x03 目的为单声道 0x01,则mIdAry:[0]=0,就存储这一个元素

数据拷贝时,mIdAry中大于或等于0,就是要拷贝对应通道的数据,小于0,就直接给这个通道填充0

第二个函数,代表的是音频格式不同,一个音频格式按照位置存储,一个按照index存储,也就是mIdxAry里面存储的是交集的index,最终计算出来的结果,和上面第一种举得例子结果一致,总之规则:1)以目的channel为准,必须最终mIdxAry中的数据长度与目的channel一致;2)源和目的的channelmask从低位bit开始,相与为1,此bit位对应的mIdxAry填bit的位置数0、1、2、3等,否则就小于0,直到超过目的channel数就不填了

数据拷贝

源头肯定是从Track的getNextBuffer开始的,这里就不展开了

void RemixBufferProvider::copyFrames(void *dst, const void *src, size_t frames)
 {
     memcpy_by_index_array(dst, mOutputChannels,
             src, mInputChannels, mIdxAry, mSampleSize, frames);
}

会用到上个步骤的mIdxAry数组:

void memcpy_by_index_array(void *dst, uint32_t dst_channels,
        const void *src, uint32_t src_channels,
        const int8_t *idxary, size_t sample_size, size_t count)
{
	//sample_size就是采样的深度,一个字节还是两个字节等
    switch (sample_size) {
    case 1: {
        uint8_t *udst = (uint8_t*)dst;
        const uint8_t *usrc = (const uint8_t*)src;
        copy_frame_by_idx(udst, dst_channels, usrc, src_channels, idxary, count, 0);
    } break;
    .....

关键是copy_frame_by_idx:

#define copy_frame_by_idx(dst, dst_channels, src, src_channels, idxary, count, zero) \
{ \
    unsigned i; \
    int index; \
    for (; (count) > 0; --(count)) { \
    	//要以dst_channel为准,因为最终都是要以dst_channel进行播放的
        for (i = 0; i < (dst_channels); ++i) { \
            index = (idxary)[i]; \	
            *(dst)++ = index < 0 ? (zero) : (src)[index]; \	  //小于0说明这个通道或位置没有对应的映射填0,大于0说明有,读取这个位置的数据即可
        } \
        (src) += (src_channels); \
    } \
}

上面的就是基于idxary就是初始化时的mIdxAry数组,它保存了要拷贝src的index位置,index可能为-1、0、1、2、3等,-1就往dst填充0,大于0,就拷贝src对应位置的数据

到这里数据端就准备好,后续也还是会执行volumMutl那块的数据再次叠加,那块才是真正的混音,这里只是准备好混音前的音频数据

  • 29
    点赞
  • 84
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 23
    评论
评论 23
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

帅气好男人_Jack

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值