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());//混音器AudioMixermAudioMixer = new AudioMixer(mNormalFrameCount, mSampleRate);if (type == DUPLICATING) {return;}// 通向HAL层的变量,混音后的数据会通过mOutputSink发送到HALmOutputSink = new AudioStreamOutSink(output->stream);.....
}

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


PlaybackThread回播线程

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

bool AudioFlinger::PlaybackThread::threadLoop()
{......//将需要混音的Track赋值到AudioMixer中,并配置相关混音参数ParamtermMixerStatus = 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的channelMaskaudio_channel_mask_t channelMask;/**bufferProvider实质是PlaybackThread创建的Track,而Track继承VolumeProvider,* 其基类就是这个类型,通过这个provider可以调用音频数据获取接口* */AudioBufferProvider*                bufferProvider;//buffer会从bufferProvider中读取到真实的音频数据mutable AudioBufferProvider::Buffer buffer; // 8 bytes//hook相当重要,它会保存一个函数指针,指向当前Track混音的函数hook_t      hook; //无论是否重采样,buffer的音频裸数据raw会写入到mInconst 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_BITaudio_format_t mMixerFormat;     // output mix format: AUDIO_FORMAT_PCM_(FLOAT|16_BIT)//track源格式audio_format_t mFormat;          // input track format//混音内部格式  分析代码统一为AUDIO_FORMAT_PCM_FLOATaudio_format_t mMixerInFormat;   // mix internal format AUDIO_FORMAT_PCM_(FLOAT|16_BIT)audio_channel_mask_t mMixerChannelMask;         //源通道存储MASKuint32_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集合请空,保存了需要删除的trackmTracks.clearDeletedTrackIds();//初始化混音状态为IDLEmixer_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线程下所有的存活Trackfor (size_t i=0 ; i<count ; i++) {const sp<Track> t = mActiveTracks[i];// this const just means the local variable doesn't changeTrack* 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的Trackstatus_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 settingtrack->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 rampparam = AudioMixer::RAMP_VOLUME;}// compute volume for this trackuint32_t vl, vr;       // in U8.24 integer formatfloat vlf, vrf, vaf;   // in [0.0, 1.0] float format// read original volumes with volume controlfloat 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  或者 此类型的音频已经设置静音 将把音量设置为0if (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,也就是最大不能超过1if (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 timemAudioMixer->setBufferProvider(trackId, track);mAudioMixer->enable(trackId);//param可能取值VOLUME和RAMP_VOLUME 设置音量或auxlevel到AudioMixer的Track的mAuxLevel或mVloumemAudioMixer->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 needssize_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函数混音前准备工作”章节,这里解锁下这么多音量整合为最终的音量是通过乘法来处理的:
    finalvolum=masterVolume∗typeVolume∗volume....final volum = masterVolume * typeVolume * volume ....finalvolum=masterVolume∗typeVolume∗volume....
    因为音量是以分贝为单位计算的,而分贝是无量纲的,所以当多个音量叠加时不能用加法来处理,因为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时赋值为mSampleRateif (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;//0case 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 则为播放的音频帧数countif (ramp != 0) {//计算渐变音量每次音量增量,除以ramp帧数,也就是每帧的音量变化值        const float inc = (newVolume - *pPrevVolume) / ramp; // could be inf, cannot be nan, subnormalconst 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;  //强转为32bitif (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相关渐变音量的变量都为0if (ramp == 0) {*pVolumeInc = 0; //渐变音量的步长音量设置为0,也就是无渐变*pPrevVolume = newVolume;  //上一次的初始音量等于设置音量 float类型*pIntVolumeInc = 0;         //同pVolumeInc,只是是整型//上一次音量整型值,因为intVolume是用newVolume按照16bit转换的,现在要转成32bit,把低16移动到高位即可*pIntPrevVolume = intVolume << 16;  }*pSetVolume = newVolume;            //设置的音量float*pIntSetVolume = intVolume;         //设置音量整型intreturn 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) {// 清除缓冲区的bufferconst 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();//存储此次混音的TracksmGroups.clear();//遍历AudioMixer内部所有的Trackfor (const auto &pair : mTracks) {const int name = pair.first;const std::shared_ptr<Track> &t = pair.second;//每个track在prepareTracks时会enableif (!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  就静默此trackn |= NEEDS_MUTE;}t->needs = n;  //将状态赋给track的needsif (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都是FLOATcase 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 gainmResampler->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根据传值为trueif (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_AUXmAuxLevel
#elseauxLevel
#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)
{//通道数 进入2switch (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 ALOGVVALOGVV("volumeMulti MIXTYPE:%d\n", MIXTYPE);
#endifif (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遍历mGroupfor (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,也就是mainBuffermemset(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;}}//混音完成后,释放bufferif (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的bufferfor (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 ptrsize_t numFrames = mFrameCount;for (const auto &pair : mGroups) {const auto &group = pair.second;//t1表示group中的第一个Trackconst 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,一条业务调用即可!

Android Audio混音实践篇相关推荐

  1. android audio混音

    1.http://blog.csdn.net/azloong/article/details/24385321 2.http://blog.csdn.net/innost/article/detail ...

  2. android oboe 混音_Android之AppBarLayout实现悬停吸附伸缩效果

    前几天看到这样一个UI效果,然后自己也仿照实现了下: 开眼app个人中心 看着挺酷的,也有很多App都用到了这个UI效果,比如开眼App和沪江开心词场就用到了. 所以下面就来简单实现一下这个UI效果吧 ...

  3. MS2131 USB 3.0 高清音视频采集+HDMI 环出+混音处理芯片 应用网络直播一体机

    MS2131 是一款 USB 3.0 高清视频和音频采集处理芯片,内部集成 USB 3.0 Device 控制器. 数据收发模块.音视频处理模块. MS2131 可以通过 USB 3.0 接口将 HD ...

  4. android html5播放器,android Html5播放器混音解决方案

    背景 当一个用户正在听音乐而另一个应用需要通知用户一些重要的事情时,用户可能由于音乐声音大而不能听的通知.从Android2.2开始,平台为应用提供了一个协商它们如何使用设备音频输出的途径,这个机制叫 ...

  5. 【Android FFMPEG 开发】Android 中使用 FFMPEG 对 MP3 文件进行混音操作

    文章目录 一.前置操作 ( 移植 FFMPEG ) 二.FFMPEG 混音命令 三.Android FFMPEG 混音源代码完整示例 四.博客源码 一.前置操作 ( 移植 FFMPEG ) 参考 [A ...

  6. android 混音 源码,FFmpegAndroid android 端基于 FFmpeg 实现音频剪切、拼接、转码、混音、编解码;视频剪切、水印、截图、转码、编 @codeKK c开源站...

    android 端基于 FFmpeg 库的使用 添加编译 ffmpeg.shine.mp3lame.x264 源码的参考脚本 目前音视频相关处理: 音频剪切.拼接 音频混音 音频转码 音视频合成 音频 ...

  7. Android 7.1 高德导航和蓝牙音乐卡顿问题 蓝牙电话和高德语音播报混音问题

    此文章主要解决三个问题 1.高德导航的时候打电话会出现混音问题. 2.蓝牙音乐在播放的时候导航界面语音播报蓝牙音乐会暂停,播报结束会恢复播放不能同时输出问题. 3.蓝牙音乐在播放的时候和导航界面的语音 ...

  8. Android音频焦点及混音策略

    1.前言 1.1 音频焦点官方解读 两个或两个以上的 Android 应用可同时向同一输出流播放音频.系统会将所有音频流混合在一起.虽然这是一项出色的技术,但却会给用户带来很大的困扰.为了避免所有音乐 ...

  9. Android 音视频配音之音频提取、截断、混音、合并、合成(一)——从视频中提取音频文件

    目录 前言 提取前提----了解提取需要用到的工具类:MediaExtractor.MediaCodec 1.MediaExtractor 2.MediaCodec 3.释放 具体提取转码代码 调用 ...

最新文章

  1. 利用Trigger完成WPF 的动画-渐显
  2. 让Ubuntu的ssh保持长时间连接
  3. P3810 【模板】三维偏序(陌上花开)
  4. 使用缓冲字节流:BufferedInputStream与BufferedOutputStream读写数据
  5. 大工17春计算机基础,大工12春《计算机应用基础》在线测试3答案
  6. 以计算机为题写一篇英语作文,请以“未来的钢笔”为题写一篇不少于80词的英语作文...
  7. python多进程间通信
  8. 平安资管罗水权:建设债券投资的智慧中台
  9. 直播预告丨技术干货:易鲸捷HTAP融合型分布式数据库连接服务层介绍
  10. 《Learning_object_interactions_and_descriptions_for_sematic_image》论文阅读
  11. 瑞吉外卖(27)-查看购物车信息、清空购物车功能开发
  12. 什么是favicon.ico,以及如何使用它
  13. ai新视觉:一键解决模糊图片高清精准修复
  14. 最优灵活体系结构(Optimal Flexible Architecture,OFA)
  15. 简单了解latex输出矩阵
  16. 解决wireshark安装mavlink协议插件后解析报错的问题
  17. 腾讯阿里O2O布局:一个偏2C一个偏2B
  18. underscore源码解析
  19. 电脑定时关机怎么设置?win10怎么设置定时关机
  20. 那些年,腾讯、阿里、百度“搞死”的50多种产品清单!

热门文章

  1. python爬取58同城租房信息_python爬虫:找房助手V1.0-爬取58同城租房信息(示例代码)...
  2. 逻辑左移、逻辑右移、算术左移、算术右移区别
  3. python鼠标绘图_python 基于opencv 实现一个鼠标绘图小程序
  4. 【ESP系列】ESP8266-12F
  5. Java 中的泛型是什么,它有什么作用?(十五)
  6. js中关于时间的转化——将秒/毫秒转化成xx小时xx分钟xx秒
  7. 交换式多兆位数据服务(SMDS)--网络大典
  8. word中项目符号自动变小_HTML和Word中的项目符号点类型和创建
  9. 韩语计算机术语大全,韩语学习:韩语计算机、互联网术语 - 英语家园
  10. 谷歌地球和谷歌地图区别