[Android] 混音線程MixerThread


MixerThread是Android音頻輸出的核心部分,所有Android的音頻都需要經過MixerThread進行混音后再輸出到音頻設備。

 

MixerThread的繼承關系如下:

MixerThread--->PlaybackThread--->ThreadBase--->Thread

在PlaybackThread中,重寫了Thread的threadLoop,onFirstRef等方法,因此在調用MixerThread這些方法時,實際上就是調用了PlaybackThread的方法。

 

 

 

1. onFirstRef

getOutput的時候,我們創建了一個MixerThread對象,由於這個對象繼承於Thread,因此在創建對象時,會調用它的onFirstRef函數。

void AudioFlinger::PlaybackThread::onFirstRef()
{
    run(mName, ANDROID_PRIORITY_URGENT_AUDIO);
}

在該方法內部,調用了run,即開始運行threadLoop。也就是說,其實在new MixerThread的時候就已經開始啟動PlaybackThread::threadLoop。

 

 

 

 

2. threadLoop

在分析threadLoop之前,我們先來了解MixerThread中的幾種Audio操作。

在Threads.cpp內有幾個threadLoop_xxx方法,這些方法就分別代表不同的Audio操作:

操作 方法 功能
standby threadLoop_standby 待機
mix threadLoop_mix 混音
write threadLoop_write 音頻輸出
exit threadLoop_exit 退出
drain threadLoop_drain 只有offload用到,還不清楚作用
sleep threadLoop_sleepTime 無音頻需要處理,計算睡眠時間

 

另外還有幾個非常重要的變量:

變量 取值 含義
tracksToRemove   需要被移除的Track,一旦所有的Track都被移除,則表明沒有音頻數據需要處理,那么線程會進入睡眠
sleepTime   睡眠時間
standbyTime   如果持續睡眠超出standbyTime,則會進入待機
mStandby   表明當前是否為待機狀態
mActiveTracks   需要進行音頻處理的Track,如果該Track已經播放完成或者被停止,則會被移入tracksToRemove
mMixerStatus MIXER_IDLE Mixer狀態,no active tracks,表明不需要混音,而是進入睡眠
mMixerStatus MIXER_TRACKS_ENABLED Mixer狀態,at least one active track, but no track has any data ready
mMixerStatus MIXER_TRACKS_READY Mixer狀態,at least one active track, and at least one track has data,表明可以進行混音

 

 

 

threadLoop循環

threadLoop內有一個循環,MixerThread是與output(輸出設備)相關的(在openOutput的時候才會新建MixerThread),基本上都不會跑出循環之外。

bool AudioFlinger::PlaybackThread::threadLoop()
{
    while (!exitPending())  
    {
        ....
    }

}

 

 

MixerThread創建

在進入處理循環之前,首先會設置standbyTime、sleepTime。如果目前沒有音頻需要處理,進入睡眠,如果持續的睡眠時間超出了standbyTime,則會進入待機。不過由於standbyTime設置為當前時間,因此第一次肯定會執行待機動作。執行了待機操作后,MixerThread就會進入睡眠,等待被喚醒

bool AudioFlinger::PlaybackThread::threadLoop()
{
    //設置待機時間、睡眠時間
    standbyTime = systemTime();
    sleepTime = idleSleepTime;

    while (!exitPending())  
    {
        //創建MixerThread時,mActiveTracks肯定是空的,並且當前時間會超出standbyTime
        if ((!mActiveTracks.size() && systemTime() > standbyTime) || isSuspended()) {
               if (shouldStandby_l()) {                                                    //創建MixerThread時肯定會進入待機 
                       threadLoop_standby();                        
                       mStandby = true;                                                    }
               }
               if (!mActiveTracks.size() && mConfigEvents.isEmpty()) {
                   //然后MixerThread會在這里睡眠等待,知道AudioTrack:: start發送廣播喚醒
                      mWaitWorkCV.wait(mLock);
                   standbyTime = systemTime() + standbyDelay;
                   sleepTime = idleSleepTime;
               }
    ...
    }
}

 

 

 

MixerThread處理音頻

如上一篇所說,AudioTrack:: start被執行后,就會喚醒MixerThread線程,接下來就會對音頻數據進行處理。處理流程如下圖:

MixerThread_mix

 

 

正常的音頻處理時,會在threadLoop循環內不斷的進行混音與音頻輸出,其中分為三個步驟:

  1. 混音前的准備工作,prepareTracks_l
  2. 混音,threadLoop_mix
  3. 音頻輸出,threadLoop_write

 

 

 

bool AudioFlinger::PlaybackThread::threadLoop()
{
    while (!exitPending())
    {
        mMixerStatus = prepareTracks_l(&tracksToRemove); 

        if(mMixerStatus == MIXER_TRACKS_READY)
            threadLoop_mix();
        }

        threadLoop_write();
    }
}

 

 

 

① prepareTracks_l

准備混音的過程中,主要的目的有三個:

  1. 設置混音所需要的參數,包括:音量,混音的源buffer,混音目的buffer,音頻格式,是否重采樣等。    
  2. 刪除被加入tracksToRemove的track
  3. 返回當前狀態mMixerStatus

由於在mActiveTracks中維護的track可能會有多個,因此需要對每個track都執行上述步驟,我們可以依據上述目的來對prepareTrack_l進行分析。

AudioFlinger::PlaybackThread::mixer_state AudioFlinger::MixerThread::prepareTracks_l(Vector< sp<Track> > *tracksToRemove) 
{
    //默認為空閑狀態
    mixer_state mixerStatus = MIXER_IDLE;
    size_t count = mActiveTracks.size();

    //對於所有在mActiveTracks里面的Track,都需要進行設置
    for (size_t i=0 ; i<count ; i++) {
        const sp<Track> t = mActiveTracks[i].promote();
        Track* const track = t.get();

        //由於不是fastTrack,因此不會跑這里,而且fastTrack也不會在這里進行混音,我是沒有發現有跑進過這個條件里面的
        if (track->isFastTrack()) {
            ...
        }

        //獲取track的name,其實是個索引,AudioMixer會最多維護32個track,分別對飲int的32個bit,如果track的name還沒定下來的話,會自行選擇一個空位
        int name = track->name();

        //查看當前track是否stop,如果track被stop,那么這個track不需要設置AudioMixer參數,即frameReady = 0
        size_t framesReady;                                                     
        if (track->sharedBuffer() == 0) {                                           
            framesReady = track->framesReady();                                 
        } else if (track->isStopped()) {                                            
            framesReady = 0;                               
        } else {             
            framesReady = 1;                                  
        }

        //混音的情況下,frameReady = 1,那么會進入下面的條件,進行AudioMixer參數設置
        if ((framesReady >= minFrames) && track->isReady() && !track->isPaused() && !track->isTerminated())                                 {
            //音量參數
            ...

            //設置AudioMixer參數
            //源buffer
            mAudioMixer->setBufferProvider(name, track);
            //使能該track,即可以混音
            mAudioMixer->enable(name);
            //左音軌
            mAudioMixer->setParameter(name, param, AudioMixer::VOLUME0, (void *)vl);
            //右音軌
            mAudioMixer->setParameter(name, param, AudioMixer::VOLUME1, (void *)vr);    
            //aux                    
            mAudioMixer->setParameter(name, param, AudioMixer::AUXLEVEL, (void *)va); 
            //音頻格式                      
            mAudioMixer->setParameter( 
                           name,                 
                           AudioMixer::TRACK,
                           AudioMixer::FORMAT, (void *)track->format());
            //音軌mask,哪個需要或者不需要混音
            mAudioMixer->setParameter(
                           name,
                           AudioMixer::TRACK,
                           AudioMixer::CHANNEL_MASK, (void *)track->channelMask());  
            //進行重采樣
            mAudioMixer->setParameter(
                           name, 
                           AudioMixer::RESAMPLE,
                           AudioMixer::SAMPLE_RATE, 
                          (void *)reqSampleRate);
            //目的buffer
            mAudioMixer->setParameter(
                           name,
                           AudioMixer::TRACK,
                           AudioMixer::MAIN_BUFFER, (void *)track->mainBuffer());
            //aux
            mAudioMixer->setParameter(
                           name,
                           AudioMixer::TRACK,
                           AudioMixer::AUX_BUFFER, (void *)track->auxBuffer());
            
            //當前狀態為ready,即可以混音
            mixerStatus = MIXER_TRACKS_READY;
        }else{
            //track stop時才會走這里
            ...
        }
  
    }
    //從mActiveTracks刪除需要移除的track
    removeTracks_l(*tracksToRemove);

    //返回mMixerStatus, 正常混音准備時,這里返回的是MIXER_TRACK_READY
}

 

從上面的代碼來看,有一個需要注意的地方:

            mAudioMixer->setParameter(
                           name, 
                           AudioMixer::RESAMPLE,
                           AudioMixer::SAMPLE_RATE, 
                          (void *)reqSampleRate);

即安卓的MixerThread會對所有的track進行重采樣,那么在混音的時候會調用重采樣的混音方法。

 

 

 

②threadLoop_mix

在prepareTrack_l返回了mMixerStatus = MIXER_TRACK_READY,那么就可以進入threadLoop_mix進行混音了。有了上面prepareTrack_l設置的參數,在threadLoop_mix所需要做的主要就是調用AudioMixer的process方法進行混音了。不過還需要對某些變量進行更新。

 

void AudioFlinger::MixerThread::threadLoop_mix()
{
    //首先需要獲取timestamps,即輸出時間戳,用於seek到源buffer的某個位置進行混音?
    if (mNormalSink != 0) {
        status = mNormalSink->getNextWriteTimestamp(&pts); 
    }else{
        status = mOutputSink->getNextWriteTimestamp(&pts);
    }

    //AudioMixer混音
    mAudioMixer->process(pts);

    //混音了多少音頻數據
    mCurrentWriteLength = mixBufferSize;

    //等下不需要睡眠,直接輸出音頻
    sleepTime = 0;

    //待機時間更新
    standbyTime = systemTime() + standbyDelay;
}

 

在混音完成過后,混音目的buffer中的數據都會等待輸出,mBytesRemaining就代表了又多少數據需要輸出,混音完成后需要用mCurrentWriteLength對這個變量進行更新

bool AudioFlinger::PlaybackThread::threadLoop(){
    ...
    threadLoop_mix();
    mBytesRemaining = mCurrentWriteLength;
    ...
}

 

 

 

 

③threadLoop_write

threadLoop_write用於混音后的音頻輸出

ssize_t AudioFlinger::MixerThread::threadLoop_write(){
    //現在先不討論fastMixer
    if (mFastMixer != NULL) {
        ...
    }
    return PlaybackThread::threadLoop_write();
}

ssize_t AudioFlinger::PlaybackThread::threadLoop_write()
{
    //調用write方法輸出音頻
    //如果用fastMixer的話其實會走mNormalSink分支的,現在不討論
    if (mNormalSink != 0) {
        ssize_t framesWritten = mNormalSink->write(mMixBuffer + offset, count);
    }else{
        bytesWritten = mOutput->stream->write(mOutput->stream, mMixBuffer + offset, mBytesRemaining);
    }
    //最后返回輸出了多少音頻數據
    return bytesWritten;
}

 

每次音頻輸出后,都需要對混音目的buffer內剩余的數據量進行更新,並且記錄一共輸出了多少音頻數據

bool AudioFlinger::PlaybackThread::threadLoop(){
    ...
    ssize_t ret = threadLoop_write();
    mBytesWritten += ret;
    mBytesRemaining -= ret;
    ...
}

 

這里也需要注意一點,如果在一次的輸出后mBytesRemaining不為0,表明混音目的buffer內的數據並沒有被完全輸出,那么下一場循環就不能進行混音,而是直接繼續輸出音頻。其實進入threadLoop_mix還有一個條件:

bool AudioFlinger::PlaybackThread::threadLoop()
{
    ...
    if(mBytesRemaining == 0){
        if(mMixerStatus == MIXER_TRACK_READY){
            threadLoop_mix();
        }
    }
    ...
}

 

 

 

MixerThread音頻處理結束流程

音頻處理結束分為兩個階段:

  • sleep
  • standby

 

 

sleep

在sleep階段,還會在threadLoop內繼續執行循環,但是不會再調用threadLoop_mix進行混音,而prepareTrack_l與threadLoop_write還會繼續執行。

一般來說,在音頻輸出結束時,會執行AudioTrack:: stop,這會導致在prepareTrack_l返回狀態MIXER_IDLE

 AudioFlinger::PlaybackThread::mixer_state AudioFlinger::MixerThread::prepareTracks_l(
        Vector< sp<Track> > *tracksToRemove)
{
    mixer_state mixerStatus = MIXER_IDLE;
    
    ...
    
    if (track->sharedBuffer() == 0) {
        framesReady = track->framesReady();
    }else if (track->isStopped()) {
        //在音頻播放完成或者被停止的時候會走這個條件
        framesReady = 0;
    }else{
        framesReady = 1;
    }

    //並不會走設置混音參數的流程
    if ((framesReady >= minFrames) && track->isReady() &&
            !track->isPaused() && !track->isTerminated())
    {
        ...
    }else{
        ...

        //track 被停止就會把track加入tracksToRemove
        if ((track->sharedBuffer() != 0) || track->isTerminated() ||
                track->isStopped() || track->isPaused()) {

            if (mStandby || track->presentationComplete(framesWritten, audioHALFrames)) {
                 if (track->isStopped()) {
                     track->reset();
                 }
                 tracksToRemove->add(track); 
             }
        }
        //disable,通知AudioMixer不需要對這個track進行混音
        mAudioMixer->disable(name);
    }

    ...
    
    //從mActiveTracks刪除該track
    removeTracks_l(*tracksToRemove);

    //返回開頭的MIXER_IDLE
    return mixerStatus;
}

 

 

由於返回的mMixerStatus == MIXER_IDLE,因此並不會走threadLoop_mix進行混音,從而進入另一個分支threadLoop_sleepTime

bool AudioFlinger::PlaybackThread::threadLoop()
{
        if (mBytesRemaining == 0) {
             mCurrentWriteLength = 0;
             if (mMixerStatus == MIXER_TRACKS_READY) {
                 //在結束的時候不會跑這里
                 threadLoop_mix();
             } else if ((mMixerStatus != MIXER_DRAIN_TRACK)
                         && (mMixerStatus != MIXER_DRAIN_ALL)) {
                 //進入這個分支
                 threadLoop_sleepTime();
                 if (sleepTime == 0) {
                     mCurrentWriteLength = mixBufferSize;
                 }
             }
        }
}

 

 

在音頻處理結束后的每個循環,threadLoop_sleepTime會取代threadLoop_mix,在threadLoop_sleepTime里面計算出來這次循環需要睡眠多久。threadLoop_sleepTime會交替計算出不同的sleepTime,如: 在音頻處理結束后的第一個循環,會算出sleepTime = idleSleepTime;在第二個循環,會計算出sleepTime = 0;第三次又是sleepTime = idleSleepTime; 如此交替下去。(為什么需要這樣?)

 void AudioFlinger::MixerThread::threadLoop_sleepTime()
 {
     // If no tracks are ready, sleep once for the duration of an output
     // buffer size, then write 0s to the output
     // 一開始進入這個函數前的前一個循環,是執行的threadLoop_mix,那時候的sleepTime == 0
     // 因此會進入下面這個條件
     if (sleepTime == 0) {
         if (mMixerStatus == MIXER_TRACKS_ENABLED) {
             sleepTime = activeSleepTime >> sleepTimeShift;
             if (sleepTime < kMinThreadSleepTimeUs) {
                 sleepTime = kMinThreadSleepTimeUs;
             }
             // reduce sleep time in case of consecutive application underruns to avoid
             // starving the audio HAL. As activeSleepTimeUs() is larger than a buffer
             // duration we would end up writing less data than needed by the audio HAL if
             // the condition persists.
             if (sleepTimeShift < kMaxThreadSleepTimeShift) {
                 sleepTimeShift++;
             }
         } else {
             // 由於我們現在的狀態時MIXER_IDLE,因此會進入這個條件
             // idleSleepTime我們打印出來的是11500us
             sleepTime = idleSleepTime;
         }
     } else if (mBytesWritten != 0 || (mMixerStatus == MIXER_TRACKS_ENABLED)) {
         // 由於前一次循環賦值sleepTime = idleSpeelTime;第二個循環進來后會進入這個分支,重新設置sleepTime = 0;
         memset (mMixBuffer, 0, mixBufferSize);
         sleepTime = 0;
     }
     // TODO add standby time extension fct of effect tail
 }

 

 

獲得sleepTime后,就可以通過sleepTime是否為0來執行write或者sleep了

void AudioFlinger::MixerThread::threadLoop_sleepTime()
{
    ...
            //可以看到只有sleepTime == 0的時候才會調用write,否則會睡眠
            if (sleepTime == 0) {
                if (mBytesRemaining) {
                    ssize_t ret = threadLoop_write();
                }
            } else {
                 usleep(sleepTime);
            }
    ...
}

也就是說在這個時間段還是會去輸出音頻數據的,雖然說這些數據都是0(沒有聲音)

 

 

sleep的流程可以參考下圖

MixerThread_sleep

 

 

 

 

 

standby

進入standby模式的時候,只需要執行兩個步驟:

  • 調用threadLoop_standby使音頻設備進入待機模式
  • 調用mWaitWorkCV.wait(mLock);使threadLoop進入睡眠,等待下一次播放音頻數據的時候喚醒

那么如何才會進入standby模式呢?我們來回顧前面MixerThread創建的時候,已經進入過一次standby模式了。沒錯,在播放音頻結束后還是從這里進入standby模式。

那么看一下進入standby模式的條件:

if ((!mActiveTracks.size() && systemTime() > standbyTime) ||
                       isSuspended())

 

正常情況會通過!mActiveTracks.size() && systemTime() > standbyTime這個條件進去。其中

  • 在sleep模式的prepareTrack_l已經把mActiveTracks中需要刪除的track去除,當mActiveTracks被完全清空,就代表沒有track需要混音輸出了,此時mActiveTracks.size() == 0
  • systemTime取得當前時間,standbyTime最后一次被賦值是在threadLoop_mix的時候:standbyTime = systemTime() + standbyDelay; 這就表示在最后一次混音之后過了standbyDelay時間,即可以進入standby模式

 

MixerThread_standby


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM