http://blog.csdn.net/xuesen_lin/article/details/8805091
1.1.1 PlaybackThread的循環主體
當一個PlaybackThread進入主循環后(threadLoop),音頻事務就正式開啟了。仔細觀察的話,我們會發現這個循環中會不斷地調用以“threadLoop_”開頭的若干接口,比如threadLoop_mix、threadLoop_sleepTime、threadLoop_standby等等。以這樣的前綴開頭,是因為這些函數都是在threadLoop這個主體里被調用的,可以說代表了這個PlaybackThread所需要完成的各個操作步驟。
從上一小節可以了解到,當程序執行到PlaybackThread::onFirstRef時會去真正啟動一個線程承載運行threadLoop,接下來我們具體看下這個循環體的處理流程。
bool AudioFlinger::PlaybackThread::threadLoop()
{ …
while (!exitPending())/*Step1.*/
{ …
processConfigEvents();/*Step 2. */
{ /*把這段代碼框起來的目的是限制自動鎖變量_l的生命期,
從而靈活地實現了自動鎖的控制范圍*/
Mutex::Autolock _l(mLock);
…
/*Step 3. Standby判斷*/
if(CC_UNLIKELY((!mActiveTracks.size() && systemTime() > standbyTime)|| mSuspended> 0)) {
if (!mStandby){
threadLoop_standby();//調用設備的
mStandby =true;
mBytesWritten = 0;
}
…
}
/*Step 4.*/
mMixerStatus =prepareTracks_l(&tracksToRemove);
…
}
/*Step5.*/
if(CC_LIKELY(mMixerStatus == MIXER_TRACKS_READY)) {
threadLoop_mix();
} else {
threadLoop_sleepTime();
}
…
/*Step 6.*/
if (sleepTime == 0) {
threadLoop_write(); //不需要休眠,有數據要寫
…
mStandby = false;
} else {
usleep(sleepTime);//進入休眠,時間長短是sleepTime
}
/*Step 7.*/
threadLoop_removeTracks(tracksToRemove);//移除相關Track
tracksToRemove.clear();…
}//while (!exitPending())結束
…
releaseWakeLock();
return false;
}
Step1@ PlaybackThread::threadLoop, 循環的條件是!exitPending()為true。這個函數屬於Thread類,它主要通過判斷內部變量mExitPending的值來得出是否要結束線程。變量mExitPending在Thread初始化時為fasle,如果后面有人通過requestExit()、requestExitAndWait等等來請求退出,這個值就會改變,從而使得PlaybackThread結束循環。
Step2@ PlaybackThread::threadLoop, 處理config事件。當有配置改變的事件發生時,可以通過sendConfigEvent來通知PlaybackThread。這個函數將把事件添加到mConfigEvents全局變量中,以供processConfigEvents進行處理。配置事件包括如下幾種:
enum io_config_event {
OUTPUT_OPENED,//Output打開
OUTPUT_CLOSED,//Output關閉
OUTPUT_CONFIG_CHANGED,//Output配置改變
INPUT_OPENED, //Input打開
INPUT_CLOSED, //Input關閉
INPUT_CONFIG_CHANGED,//Input配置改變
STREAM_CONFIG_CHANGED,//Stream配置改變
NUM_CONFIG_EVENTS
};
Step3@ PlaybackThread::threadLoop,判斷當前是否符合Standby的條件,如果是的話就調用threadLoop_standby。這個函數最終還是通過HAL層的接口來實現,如下:
mOutput->stream->common.standby(&mOutput->stream->common);
Step4@ PlaybackThread::threadLoop, 進行數據准備,prepareTracks_l這個函數非常長,我們先用偽代碼的形式整理一下它所做的工作,如下所示:
AudioFlinger::PlaybackThread::mixer_stateAudioFlinger::MixerThread::prepareTracks_l(…)
{
/*Step 1. 當前活躍的Track數量*/
size_t count = mActiveTracks.size();
/*Step 2. 循環處理每個Track,這是函數的核心*/
for (size_t i=0; i<count ; i++) {
Track* track =mActiveTracks[i];//偽代碼沒有考慮強指針
/*Step 3. FastTrack下的處理*/
if(track is FastTrack)
{
//dosomething;
}
/*Step 4. 准備數據,分為以下幾個小部來完成*/
audio_track_cblk_t*cblk = track->cblk(); //Step 4.1 數據塊准備
/*Step 4.2 要回放音頻前,至少需要准備多少幀數據?*/
uint32_t minFrames = 1;//初始化
//具體計算minFrames…
/*Step 4.3 如果數據已經准備完畢*/
//調整音量
//其它參數設置
}//for循環結束
/*Step 5. 后續判斷*/
//返回結果,指明當前狀態是否已經ready
}
現在我們針對上面的步驟來做“填空”。
Step1@ MixerThread::prepareTracks_l, mActiveTracks的數據類型是SortedVector,用於記錄當前活躍的Track。它會隨着新的AudioTrack的加入而擴大,也會在必要的情況下(AudioTrack工作結束、或者出錯等等)remove相應的Track。
Step2&3@MixerThread::prepareTracks_l, 循環的條件就是要逐個處理該PlaybackThread中包含的Track。假如當前是一個FastTrack,我們還要做一些其它准備,這里就暫時不去涉及具體細節了。
Step4@ MixerThread::prepareTracks_l, 這一步是准備工作中最重要的,那就是緩沖數據。在學習代碼細節前,我們先來了解數據傳輸時容易出現的underrun情況。
什么是BufferUnderrun呢?
當兩個設備或進程間形成“生產者-消費者”關系時,如果生產的速度不及消費者消耗的速度,就會出現Underrun。以音頻回放為例,此時用戶聽到的聲音就可能是斷斷續續的,或者是重復播放當前buffer中的數據(取決於具體的實現)。
如何避免這種異常的發生?這也是Step4所要解決的問題,以下分為幾個小步驟來看AudioFlinger是如何做到的。
Ø Step4.1,取得數據塊
audio_track_cblk_t*cblk = track->cblk();
關於audio_track_cblk_t的更多描述,可以參見后面數據流小節。
Ø Step4.2 計算正確回放音頻所需的最少幀數,初始值為1。
uint32_tminFrames = 1;
if((track->sharedBuffer() == 0) && !track->isStopped() &&!track->isPausing() &&
(mMixerStatusIgnoringFastTracks == MIXER_TRACKS_READY)) {
if(t->sampleRate() == (int)mSampleRate) {
minFrames = mNormalFrameCount;
} else {
minFrames =(mNormalFrameCount * t->sampleRate()) / mSampleRate + 1 + 1;
minFrames +=mAudioMixer->getUnreleasedFrames(track->name());
ALOG_ASSERT(minFrames <= cblk->frameCount);
}
}
當track->sharedBuffer()為0時,說明AudioTrack不是STATIC模式的,否則數據就是一次性傳送的,可以參見AudioTrack小節的描述。全局變量mSampleRate 是通過mOutput->stream->common.get_sample_rate獲得的,它是由HAL提供的,代表的是設備的Sampling rate。
如果兩者一致的話,就采用mNormalFrameCount,這個值在readOutputParameters函數中進行初始化。如果兩者不一致的話,就要預留多余的量做rounding(+1)和interpolation(+1)。另外,還需要考慮未釋放的空間大小,也就是getUnreleasedFrames得到的。得出的minFrames必需小於數據塊的總大小,因而最后有個ASSERT。通常情況下frameCount分配的是一個buffer的兩倍,可以參見AudioTrack小節的例子。
Ø Step4.3 數據是否准備就緒了?
上一步我們計算出了數據的最小幀值,即minFrames,接下來就該判斷目前的情況是否符合這一指標了,代碼如下所示:
if ((track->framesReady() >=minFrames) && track->isReady() &&!track->isPaused()&& !track->isTerminated())
{//數據准備就緒,並處於ready狀態
mixedTracks++; //需要mix的Track數量增加1
…
/*計算音量值*/
uint32_t vl, vr,va; //三個變量分別表示左、右聲道、Aux level音量
if(track->isMuted() || track->isPausing()||mStreamTypes[track->streamType()].mute) {
vl = vr = va =0; //當靜音時,變量直接賦0
if (track->isPausing()) {
track->setPaused();
}
} else {
/*這里獲得的是針對每個stream類型設置的音量值,也就是后面“音量調節”小節里最
后執行到的地方,在這里就起到作用了*/
float typeVolume =mStreamTypes[track->streamType()].volume;
float v =masterVolume * typeVolume; //主音量和類型音量的乘積
uint32_t vlr = cblk->getVolumeLR(); //這里得到的vlr必須經過驗證是否在合理范圍內
vl = vlr &0xFFFF; //vlr的高低位分別表示vr和vl
vr = vlr>> 16;
if (vl >MAX_GAIN_INT) { //對vl進行合理值判斷
ALOGV("Track left volume out of range: %04X", vl);
vl =MAX_GAIN_INT;
}
if (vr >MAX_GAIN_INT) {//對vr進行合理值判斷
ALOGV("Track right volume out of range: %04X", vr);
vr =MAX_GAIN_INT;
}
// now applythe master volume and stream type volume
vl =(uint32_t)(v * vl) << 12;
vr =(uint32_t)(v * vr) << 12;
uint16_tsendLevel = cblk->getSendLevel_U4_12();
// send levelcomes from shared memory and so may be corrupt
if (sendLevel> MAX_GAIN_INT) {
ALOGV("Track send level out of range:%04X", sendLevel);
sendLevel= MAX_GAIN_INT;
}
va =(uint32_t)(v * sendLevel);
} …
mAudioMixer->setParameter(name, param, AudioMixer::VOLUME0, (void*)vl);
mAudioMixer->setParameter(name, param, AudioMixer::VOLUME1, (void*)vr);
mAudioMixer->setParameter(name, param, AudioMixer::AUXLEVEL, (void*)va);
…
} else {//數據未准備就緒,略過。。。
對於音量的設置還有很多細節,大家有興趣的可以深入研究下。在得到vl、vr和va的值后,還需要把它們應用到AudioMixer中去,不過在prepareTracks_l中還只是調用mAudioMixer->setParameter設置了這些參數,真正的實現是在threadLoop_mix中,我們后面會講到這個函數。
Step5@ MixerThread::prepareTracks_l, 通過對每個Track執行上述的處理后,最后要返回一個結果,這通常取決於:
①是否有activetrack
②active track的數據是否已經准備就緒
返回的最終值將影響到threadLoop的下一步操作。
完成了prepareTracks_l的分析,我們再回到前面的threadLoop。
Step5@ PlaybackThread::threadLoop, 如果上一步的數據准備工作已經完成(即返回值是MIXER_TRACKS_READY),就開始進行真正的混音操作,即threadLoop_mix,否則會休眠一定的時間——如此循環往復直到退出循環體。
void AudioFlinger::MixerThread::threadLoop_mix()
{
int64_t pts;
…
mAudioMixer->process(pts);
…
}
這樣就進入AudioMixer的處理了,我們放在下一小節做統一分析。
假如數據還沒有准備就緒,那么AudioFlinger將調用threadLoop_sleepTime來計算需要休眠多長時間(變量sleepTime),並在threadLoop主循環的末尾(在remove track之前)執行usleep進入休眠。
Step6@ PlaybackThread::threadLoop, 將數據寫到HAL中,從而逐步寫入到硬件設備中。
void AudioFlinger::PlaybackThread::threadLoop_write()
{
mLastWriteTime =systemTime();
mInWrite = true;
int bytesWritten;
if (mNormalSink != 0) {
…
ssize_t framesWritten= mNormalSink->write(mMixBuffer, count);
…
} else {
bytesWritten =(int)mOutput->stream->write(mOutput->stream, mMixBuffer,mixBufferSize);
}
if (bytesWritten > 0)mBytesWritten += mixBufferSize;
mNumWrites++;
mInWrite = false;
}
分為兩種情況:
Ø 如果是采用了NBAIO(Non-blocking AudioI/O),即mNormalSink不為空,則通過它寫入HAL
Ø 否則使用普通的AudioStreamOut(即mOutput變量)將數據輸出
Step7@ PlaybackThread::threadLoop, 移除tracksToRemove中指示的Tracks。是否移除一個Track是在prepareTracks_l中判斷中,可以概括為以下幾種情況:
Ø 對於Fast Track,如果它的狀態(mState)是STOPPING_2、PAUSED、TERMINATED、STOPPED、FLUSHED,或者狀態是ACTIVE但underrun的次數超過限額(mRetryCount),則會被加入tracksToRemove列表中
Ø 當前的Track數據未准備就緒的情況下,且是STATICTRACK或者已經停止/暫停,也會被加入tracksToRemove列表中
在tracksToRemove列表中的Track,與其相關的output將收到stop請求(由AudioSystem::stopOutput發起)。
關於AudioFlinger中與AudioTrack、AudioPolicyService有交互的部分,我們還將在后續小節進行闡述。