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線程,接下來就會對音頻數據進行處理。處理流程如下圖:
正常的音頻處理時,會在threadLoop循環內不斷的進行混音與音頻輸出,其中分為三個步驟:
- 混音前的准備工作,prepareTracks_l
- 混音,threadLoop_mix
- 音頻輸出,threadLoop_write
bool AudioFlinger::PlaybackThread::threadLoop()
{
while (!exitPending())
{
mMixerStatus = prepareTracks_l(&tracksToRemove);
if(mMixerStatus == MIXER_TRACKS_READY)
threadLoop_mix();
}
threadLoop_write();
}
}
① prepareTracks_l
准備混音的過程中,主要的目的有三個:
- 設置混音所需要的參數,包括:音量,混音的源buffer,混音目的buffer,音頻格式,是否重采樣等。
- 刪除被加入tracksToRemove的track
- 返回當前狀態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的流程可以參考下圖
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模式



