MixerThread是Android音频输出的核心部分,所有Android的音频都需要经过MixerThread进行混音后再输出到音频设备。
MixerThread的继承关系如下:
MixerThread--->PlaybackThread--->ThreadBase--->Thread
在PlaybackThread中,重写了Thread的threadLoop,onFirstRef等方法,因此在调用MixerThread这些方法时,实际上就是调用了PlaybackThread的方法。
1. onFirstRef
在getOutput的时候,我们创建了一个MixerThread对象,由于这个对象继承于Thread,因此在创建对象时,会调用它的onFirstRef函数。
|
1 2 3 4 |
|
在该方法内部,调用了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),基本上都不会跑出循环之外。
|
1 2 3 4 5 6 7 8 |
|
MixerThread创建
在进入处理循环之前,首先会设置standbyTime、sleepTime。如果目前没有音频需要处理,进入睡眠,如果持续的睡眠时间超出了standbyTime,则会进入待机。不过由于standbyTime设置为当前时间,因此第一次肯定会执行待机动作。执行了待机操作后,MixerThread就会进入睡眠,等待被唤醒
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
|
MixerThread处理音频
如上一篇所说,AudioTrack:: start被执行后,就会唤醒MixerThread线程,接下来就会对音频数据进行处理。处理流程如下图:
正常的音频处理时,会在threadLoop循环内不断的进行混音与音频输出,其中分为三个步骤:
- 混音前的准备工作,prepareTracks_l
- 混音,threadLoop_mix
- 音频输出,threadLoop_write
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
① prepareTracks_l
准备混音的过程中,主要的目的有三个:
- 设置混音所需要的参数,包括:音量,混音的源buffer,混音目的buffer,音频格式,是否重采样等。
- 删除被加入tracksToRemove的track
- 返回当前状态mMixerStatus
由于在mActiveTracks中维护的track可能会有多个,因此需要对每个track都执行上述步骤,我们可以依据上述目的来对prepareTrack_l进行分析。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 |
|
从上面的代码来看,有一个需要注意的地方:
|
1 2 3 4 5 |
|
即安卓的MixerThread会对所有的track进行重采样,那么在混音的时候会调用重采样的混音方法。
②threadLoop_mix
在prepareTrack_l返回了mMixerStatus = MIXER_TRACK_READY,那么就可以进入threadLoop_mix进行混音了。有了上面prepareTrack_l设置的参数,在threadLoop_mix所需要做的主要就是调用AudioMixer的process方法进行混音了。不过还需要对某些变量进行更新。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
|
在混音完成过后,混音目的buffer中的数据都会等待输出,mBytesRemaining就代表了又多少数据需要输出,混音完成后需要用mCurrentWriteLength对这个变量进行更新
|
1 2 3 4 5 6 |
|
③threadLoop_write
threadLoop_write用于混音后的音频输出
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|
每次音频输出后,都需要对混音目的buffer内剩余的数据量进行更新,并且记录一共输出了多少音频数据
|
1 2 3 4 5 6 7 |
|
这里也需要注意一点,如果在一次的输出后mBytesRemaining不为0,表明混音目的buffer内的数据并没有被完全输出,那么下一场循环就不能进行混音,而是直接继续输出音频。其实进入threadLoop_mix还有一个条件:
|
1 2 3 4 5 6 7 8 9 10 |
|
MixerThread音频处理结束流程
音频处理结束分为两个阶段:
- sleep
- standby
sleep
在sleep阶段,还会在threadLoop内继续执行循环,但是不会再调用threadLoop_mix进行混音,而prepareTrack_l与threadLoop_write还会继续执行。
一般来说,在音频输出结束时,会执行AudioTrack:: stop,这会导致在prepareTrack_l返回状态MIXER_IDLE
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
|
由于返回的mMixerStatus == MIXER_IDLE,因此并不会走threadLoop_mix进行混音,从而进入另一个分支threadLoop_sleepTime
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
在音频处理结束后的每个循环,threadLoop_sleepTime会取代threadLoop_mix,在threadLoop_sleepTime里面计算出来这次循环需要睡眠多久。threadLoop_sleepTime会交替计算出不同的sleepTime,如: 在音频处理结束后的第一个循环,会算出sleepTime = idleSleepTime;在第二个循环,会计算出sleepTime = 0;第三次又是sleepTime = idleSleepTime; 如此交替下去。(为什么需要这样?)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
|
获得sleepTime后,就可以通过sleepTime是否为0来执行write或者sleep了
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
也就是说在这个时间段还是会去输出音频数据的,虽然说这些数据都是0(没有声音)
sleep的流程可以参考下图
standby
进入standby模式的时候,只需要执行两个步骤:
- 调用threadLoop_standby使音频设备进入待机模式
- 调用mWaitWorkCV.wait(mLock);使threadLoop进入睡眠,等待下一次播放音频数据的时候唤醒
那么如何才会进入standby模式呢?我们来回顾前面MixerThread创建的时候,已经进入过一次standby模式了。没错,在播放音频结束后还是从这里进入standby模式。
那么看一下进入standby模式的条件:
|
1 2 |
|
正常情况会通过!mActiveTracks.size() && systemTime() > standbyTime这个条件进去。其中
- 在sleep模式的prepareTrack_l已经把mActiveTracks中需要删除的track去除,当mActiveTracks被完全清空,就代表没有track需要混音输出了,此时mActiveTracks.size() == 0
- systemTime取得当前时间,standbyTime最后一次被赋值是在threadLoop_mix的时候:standbyTime = systemTime() + standbyDelay; 这就表示在最后一次混音之后过了standbyDelay时间,即可以进入standby模式
分类: Android