08.音频系统:第004课_Android音频系统详解:第011节_PlaybackThread处理流程

上小节我们讲解了音频数据的传输,应用程序会创建一个AudioTrack,然后会到时其与playbackThread之间创建一个共享内存,当然这个共享可以是应用程序创建,也可以是playbackThread创建。应用程序只要把数据写入到共享内存,playbackThread就能从共享内存得到数据,然后播放数据。

那么我们来看一个问题,我们有2个应用程序,同时播放声音,每个应用程序都创建了一个AudioTrack,每个应用程序的在playbackThread端都存在一个Track与其一一对应,他们之间通过共享内存传递数据,其结构如下:
08.音频系统:第004课_Android音频系统详解:第011节_PlaybackThread处理流程
每个应用程序发送的音频数据各不相同,假设:
APP1:发送数据2channel,44KZ,16bit。
APP2:发送数据1channel,22KZ,8bit。
他们需要从同一个硬件播放出来,其声卡会支持某一个格式的数据,所以说这些数据会重新采样,变成硬件支持的格式,那么谁来做这件事情呢?由playbackThread完成,其中使用一个.mAudioMixer完成,进行重采样,混音(把硬件不支持的音频格式转化为硬件支持的音频格式)。
mAudioMixer中存在成员mstate,mstate包含了一个hook函数,其会指向不同的处理函数,有哪些处理函数呢?我们来看看,打开AudioMixer.cpp:

/*如果传递过来的数据,是声卡直接支持的格式,则不需要重新采样*/
// generic code with resampling
void AudioMixer::process__genericResampling(state_t* state)

// no-op case如果静音了不做任何处理
void AudioMixer::process__nop(state_t* state)

/*如果需要重新采样,则会调用该函数*/
// generic code without resampling
void AudioMixer::process__genericNoResampling(state_t* state)
.......

从上面,我们可以知道,hook针对不同的情况,会指向不同的函数。如果需要处理process__genericNoResampling,会有一个outputTermp,为临时缓存区,同时还有一个tracks(AudioMixer.h文件中的mState成员包含),每个track跟应用程序的AudioTrack相对应,该track位于AudioMixer.h文件中的mState成员中:

struct state_t {
	 // FIXME allocate dynamically to save some memory when maxNumTracks < MAX_NUM_TRACKS
    track_t         tracks[MAX_NUM_TRACKS] __attribute__((aligned(32)));
};

state_t  	mState __attribute__((aligned(32)));

其中track_t 定义如下:

struct track_t {
 	AudioResampler*     resampler;//重采样器
 	/*一下为采样的相关,如采样率等等*/
    uint32_t            sampleRate;//
    int32_t*           mainBuffer;//保存最终数据
    int32_t*           auxBuffer;
    hook_t      	   hook; //指向不同的处理函数

从上面的分析,我们知道重采样之后的结果保存在mainBuffer中:
08.音频系统:第004课_Android音频系统详解:第011节_PlaybackThread处理流程
可以知道track_t 中的hook指向不同的处理函数如:

/*不做任何处理*/
void AudioMixer::track__genericResample(track_t* t, int32_t* out, size_t outFrameCount,int32_t* temp, int32_t* aux)

/*进行重采样*/
void AudioMixer::track__nop(track_t* t __unused, int32_t* out __unused,size_t outFrameCount __unused, int32_t* temp __unused,int32_t* aux __unused)

我们可以看到有两个类型的hook,即state_t mState中的hook,与track_t track中的hook。
state_t mState中为总的hook,track_t track中的hook为单个的hook。那么他们的调用关系是怎么的呢?
首先是playbackThread对playbackThread中的mTracks进行格式分析,看是否为硬件支持的格式,如果硬件支持的话,那么我们就要设置hook(确定是否重采样),当设置完成之后,再去设置总的hook函数(mState中),总结如下:
确定hook:
逐个分析mState.tracks[x]的数据, 根据它的格式确定tracks[x].hook
再确定总的mState.hook

调用hook:
调用总的mState.hook即可, 它会再去调用每一gemState.tracks[x].hook

这个重新采样之后的数据会放在那里呢?在共享内存之中有原始数据,前面提到过outputTermp,各个track其处理之后的数据进入临时缓存区(比如重采样)。所以outputTermp存放的是各个track处理完成之后叠加的数据。

这些临时的数据,最终保存到mainBuffer中。每个tracks中的mainBuffer指向playbackThread中混合的mMixerBuffer,mMixerBuffer中的数据已经可以进行播放了,能直接发送给硬件(其实并没有,而是发送给msinkbuffer)。

当然在android系统中,其还做了更多,使用mEffectBuffer对声音进行音效处理:
08.音频系统:第004课_Android音频系统详解:第011节_PlaybackThread处理流程
最终他们都会把buffer发送给硬件mSinkbuffer,如果使用音效,其来源可能是mEffectBuffer与mMixerBuffer。

下面是时序图,有兴趣的同学可以根据时序图更加方便的去阅读源代码:
08.音频系统:第004课_Android音频系统详解:第011节_PlaybackThread处理流程
我们现在粗略的查看一下源码,其既然是一个播放线程,在frameworks\av\services\audioflinger\Threads.cpp文件中,其存在threadLoop函数,平时处于休眠状态,当接受到应用程序发送数据的时候,其就会被换醒:

bool AudioFlinger::PlaybackThread::threadLoop()
	/*处理配置信息*/
	processConfigEvents_l();
	/*当前激活的,有数据的Track*/
	sp<Track> t = mActiveTracks[i].promote();
	/*如果数据为0,则让声卡进入休眠状态*/
	if ((!mActiveTracks.size() && systemTime() > mStandbyTimeNs) ||isSuspended()) {
                // put audio hardware into standby after short delay
                if (shouldStandby_l()) {
                	/*声卡休眠*/
                    threadLoop_standby();
                    mStandby = true;
                }
           	/*线程休眠*/
			mWaitWorkCV.wait(mLock);
			

下面是一些文字的总结:

a. prepareTracks_l : 
   确定enabled track, disabled track
   对于enabled track, 设置mState.tracks[x]中的参数
b. threadLoop_mix : 处理数据(比如重采样)、混音
   确定hook:
   逐个分析mState.tracks[x]的数据, 根据它的格式确定tracks[x].hook
   再确定总的mState.hook

   调用hook:
   调用总的mState.hook即可, 它会再去调用每一个mState.tracks[x].hook
   
   混音后的数据会放在mState.outputTemp临时BUFFER中
   然后转换格式后存入 thread.mMixerBuffer
c. memcpy_by_audio_format : 
   把数据从thread.mMixerBuffer或thread.mEffectBuffer复制到thread.mSinkBuffer
d. threadLoop_write: 
   把thread.mSinkBuffer写到声卡上
e. threadLoop_exit
上一篇:MIT6.828 Lab2 内存管理


下一篇:在mysql客户端显示gtid