一、引言:
使用audiotrack进行pcm数据播放的时候,复杂场景下会遇到underrun问题,所谓underrun,是指生产者提供数据的速度跟不上消费者使用数据的速度。因为网上讲述这类问题的博客很少,所以这里分享一下自己的研究心得,但是由于underrun和audiotrack的buffer机制非常复杂,所以也会遗留一些问题,欢迎大神补充指正。
二、代码分析:
1.添加活跃track:
首先,我们回顾下活跃track的添加,前几篇博文已经分析过,thread起来之后并不是立马不停地去处理数据,这样对功耗和CPU而言是一种浪费,当上层调用audiotrack的start接口之后,线程会添加当前track为活跃track,并且发送广播,threadloop就开始转起来处理数据了,先看一下addTrack_l函数:
AudioFlinger::PlaybackThread::addTrack_l@Threads.cpp
status_t AudioFlinger::PlaybackThread::addTrack_l(const sp<Track>& track)
{
...
// set retry count for buffer fill
track->mRetryCount = kMaxTrackStartupRetries;
...
mActiveTracks.add(track);
...
}
注意看,这个函数进来先对track设置了一个最大开始尝试次数,但是我从代码中来看,这个值并没有使用处,反倒是和它一起定义的kMaxTrackRetries 很有用,这个值会跟underrun扯上关系,假如thread在kMaxTrackRetries 的次数内持续underrun,那么,系统就认为这个track已经“死了”,会将其从mActiveTracks中移除,我们看一下系统设置的这个值大小:
// retry counts for buffer fill timeout
// 50 * ~20msecs = 1 second
static const int8_t kMaxTrackRetries = 50;
static const int8_t kMaxTrackStartupRetries = 50;
注意注释,告诉我们threadloop每执行一次为20ms;
2.prepareTracks_l:
接下来我们去threadloop里面,首先关注的是肯定是prepareTracks_l,因为这个函数会去遍历所有的活跃track,计算buffer中的数据等操作,然后给后续的mixer使用,这里先抛出一个结果,track动起来之后,threadloop每执行一次的值确实是20ms,这个值的计算很复杂,是由threadloop来控制的(不是prepareTracks_l做的),在代码中我没有明显的看到这个值的得出结论。下面简化一下prepareTracks_l的代码:
AudioFlinger::PlaybackThread::mixer_state AudioFlinger::MixerThread::prepareTracks_l(
Vector< sp<Track> > *tracksToRemove)
{
/* 先将mixer状态设置为IDLE */
mixer_state mixerStatus = MIXER_IDLE;
...
/* fasttrack下,设置try值为50 */
case TrackBase::ACTIVE:
if (recentFull > 0 || recentPartial > 0) {
// track has provided at least some frames recently: reset retry count
track->mRetryCount = kMaxTrackRetries;
}
...
audio_track_cblk_t* cblk = track->cblk();
...
size_t desiredFrames;
uint32_t sr = track->sampleRate();
if (sr == mSampleRate) {
desiredFrames = mNormalFrameCount;
} else
{
...
}
minFrames = desiredFrames;
/* 获取共享buffer中的帧数 */
size_t framesReady = track->framesReady();
/* 如果目前帧数大于minFrames */
if ((framesReady >= minFrames) && track->isReady