基于Android P版本分析
underrun & overrun
我们知道,在Audio模块中数据采用的是生产者-消费者模式,生产者负责生产数据,消费者用于消费数据,针对AudioTrack和AudioRecord,其对应的角色不同;
-
AudioTrack
- 生产者:AudioTrack,向sharedBuffer中添加数据;
- 消费者:PlaybackThread(AudioFlinger),从sharedBuffer中读取数据;
-
AudioRecord
- 生产者:AudioFlinger(输入设备),向指定sharedBuffer中添加数据;
- 消费者:AudioRecord,从sharedBuffer中读取数据;
但是ALSA数据传输是不可控的,有可能生产者生产数据量不够,或者是消费者消费速率不快,这种情况下,就会出现音频数据不正确的情况;
- 欠载 ( UnderRun ) : 播放音频流时 , 如果当前现有数据已经播放完毕 , 新数据还没有来得及写入 , 此时会发生欠载情况,现象为断断续续,一卡一卡;
在Playback中出现EPIPE是因为ALSA驱动buffer没有数据可以丢给codec所致;
- 超限 ( OverRun ) : 录制音频流时 , 如果没有及时读取音频流数据 , 并且这些数据没有妥善保存 , 发生溢出 , 导致数据丢失 , 这种情况叫做超限,现象为"爆破"杂音;
在Capture中出现EPIPE是因为ALSA驱动中有一块专门存储录音数据的buffer已满,没有及时的读取其中的数据将其写入文件中导致的overrun;
underrun 分析
add Active Tracks
我们知道,在AudioFlinger初始化的时候,就创建了PlaybackThread线程,但是thread创建成功之后,不是立马进行处理数据,这样对功耗和CPU是一种浪费。当应用上层创建AudioTrack并调用start之后,PlaybackThread会将对应的AudioTrack对应的Track实例填充到mActiveTracks集合中变更为active状态,然后发送广播,threadLoop就开始工作处理数据;
arduino
status_t AudioFlinger::PlaybackThread::Track::start(AudioSystem::sync_event_t event __unused,
audio_session_t triggerSession __unused)
{
........................
sp<ThreadBase> thread = mThread.promote();
if (thread != 0) {
........................
........................
PlaybackThread *playbackThread = (PlaybackThread *)thread.get();
........................
status = playbackThread->addTrack_l(this);
........................
return status;
}
在调用了Track实例的start函数之后,获取之前初始化中创建的PlaybackThread,其本质就是MixerThread,然后调用MixerThread的addTrack_l函数,将当前Track实例添加到Thread中进行数据处理流程;
ini
// addTrack_l() must be called with ThreadBase::mLock held
status_t AudioFlinger::PlaybackThread::addTrack_l(const sp<Track>& track)
{
status_t status = ALREADY_EXISTS;
// 判断当前mActiveTracks集合中是否存在当前Track实例,< 0代表不存在
if (mActiveTracks.indexOf(track) < 0) {
..................
// set retry count for buffer fill
if (track->isOffloaded()) {
..................
} else {
track->mRetryCount = kMaxTrackStartupRetries;
track->mFillingUpStatus =
track->sharedBuffer() != 0 ? Track::FS_FILLED : Track::FS_FILLING;
}
track->mResetDone = false;
track->mPresentationCompleteFrames = 0;
mActiveTracks.add(track);
..................
}
........................
}
在该函数中,会判断track实例是否属于offloaded,默认是不属于offloaded;
然后对mRetryCount属性进行重新赋值,赋值完之后将其添加到mActiveTracks集合中;
track->mRetryCount
mRetryCount用于描述track最大开始尝试次数,我们看一下track->mRetryCount的赋值:
ini
track->mRetryCount = kMaxTrackStopRetriesOffload;
track->mRetryCount = kMaxTrackStartupRetriesOffload;
track->mRetryCount = kMaxTrackStartupRetries;
track->mRetryCount = kMaxTrackRetries;
track->mRetryCount = kMaxTrackRetriesDirect;
track->mRetryCount = kMaxTrackRetriesOffload;
track->mRetryCount | desc | value |
---|---|---|
kMaxTrackStopRetriesOffload | Offload模式下Track停止工作时的重置值,对应OffloadThread | 2 |
kMaxTrackStartupRetriesOffload | Offload模式下Track开启时初始化的值,对应OffloadThread | 100 |
kMaxTrackStartupRetries | Track开启时初始化的值,对应MixerThread | 50 |
kMaxTrackRetries | Track工作过程中使用的重置值,对应MixerThread | 50 |
kMaxTrackRetriesDirect | Direct模式下Track工作过程中使用的重置值,对应DirectOutputThread | 2 |
kMaxTrackRetriesOffload | Offload模式下Track工作过程中使用的重置值,对应OffloadThread | 20 |
在addTrack_l函数中,首先先将track->mRetryCount值赋值为kMaxTrackStartupRetries,但是这个通过分析代码可知,没有真正使用到,因为在调用AudioTrack的start函数中之后MixerThread开始进行工作,而在MixerThread的prepareTracks_l函数中,又对track->mRetryCount进行了重新赋值;
scss
// prepareTracks_l() must be called with ThreadBase::mLock held
AudioFlinger::PlaybackThread::mixer_state AudioFlinger::MixerThread::prepareTracks_l(
Vector< sp<Track> > *tracksToRemove)
{
........................
mMixerBufferValid = false; // mMixerBuffer has no valid data until appropriate tracks found.
mEffectBufferValid = false; // mEffectBuffer has no valid data until tracks found.
for (size_t i=0 ; i<count ; i++) {
const sp<Track> t = mActiveTracks[i];
// this const just means the local variable doesn't change
Track* const track = t.get();
// process fast tracks
........................
// 判断sharedBuffer可用frame帧数已经大于等于最小规定帧数,且track已准备就绪、track状态不是暂定或者是中断状态,说明当前共享内存中的数据可以进行准备前期的准备工作,创建对应的AudioMixer为后续混音做准备
if ((framesReady >= minFrames) && track->isReady() &&
!track->isPaused() && !track->isTerminated())
{
........................
mAudioMixer->setParameter(
name,
AudioMixer::TRACK,
AudioMixer::AUX_BUFFER, (void *)track->auxBuffer());
// reset retry count
// AudioMixer创建成功且配置完成之后,将track的重计数值重置为kMaxTrackRetries = 50,开始underrun计数统计
track->mRetryCount = kMaxTrackRetries;
// If one track is ready, set the mixer ready if:
// - the mixer was not ready during previous round OR
// - no other track is not ready
if (mMixerStatusIgnoringFastTracks != MIXER_TRACKS_READY ||
mixerStatus != MIXER_TRACKS_ENABLED) {
// 将MixerStatus设置为MIXER_TRACKS_READY状态,后续就可以执行threadLoop_mix函数进行混音操作
mixerStatus = MIXER_TRACKS_READY;
}
} else {
// 能够进入这个分支,说明当前的数据或者是Track状态不满足可以混音的条件
........................
ALOGVV("track %d s=%08x [NOT READY] on thread %p", name, cblk->mServer, this);
if ((track->sharedBuffer() != 0) || track->isTerminated() ||
track->isStopped() || track->isPaused()) {
// 这一块的逻辑,首先先分析track->sharedBuffer() != 0,这个代表的就是mode = MODE_STATIC,一次性数据传递
........................
} else {
// 这个分支中,首先先可以保证,track->sharedBuffer() == 0,代表mode = MODE_STREAM,使用的是AudioFlinger创建的共享内存
// No buffers for this track. Give it a few chances to
// fill a buffer, then remove it from active list.
// 针对track的mRetryCount进行计算,每空执行(代表没有进行数据混音操作)一次,就需要对mRetryCount进行 -- 操作,当 = 0时,则代表可用次数完结,代表空轮询了50次,超时
if (--(track->mRetryCount) <= 0) {
ALOGI("BUFFER TIMEOUT: remove(%d) from active list on thread %p", name, this);
// 将当前对应的track添加到tracksToRemove集合中,用于后续从mActiveTracks集合中移除失效的track实例
tracksToRemove->add(track);
// indicate to client process that the track was disabled because of underrun;
// it will then automatically call start() when data is available
track->disable();
// If one track is not ready, mark the mixer also not ready if:
// - the mixer was ready during previous round OR
// - no other track is ready
} else if (mMixerStatusIgnoringFastTracks == MIXER_TRACKS_READY ||
mixerStatus != MIXER_TRACKS_READY) {
mixerStatus = MIXER_TRACKS_ENABLED;
}
}
mAudioMixer->disable(name);
}
} // local variable scope to avoid goto warning
}
........................
}
我们不考虑track状态的情况,目前只分析frame帧数影响的逻辑:
- 当framesReady >= minFrames时,代表当前的audio数据量满足混音时要求的最小数据量,则进行混音前准备,同时将mRetryCount重置为kMaxTrackRetries,每次进入该分支,都需要重置;
- 当不满足framesReady >= minFrames时,mRetryCount就需要递减,直到满足framesReady >= minFrames,mRetryCount才会重置,否则直到递减为0时,提示track超时,track失效;
framesReady & minFrames
framesReady
framesReady:代表共享Buffer中的帧数;
arduino
size_t framesReady = track->framesReady();
size_t AudioFlinger::PlaybackThread::Track::framesReady() const {
if (mSharedBuffer != 0 && (isStopped() || isStopping())) {
// Static tracks return zero frames immediately upon stopping (for FastTracks).
// The remainder of the buffer is not drained.
return 0;
}
return mAudioTrackServerProxy->framesReady();
}
__attribute__((no_sanitize("integer")))
size_t AudioTrackServerProxy::framesReady()
{
LOG_ALWAYS_FATAL_IF(!mIsOut);
if (mIsShutdown) {
return 0;
}
audio_track_cblk_t* cblk = mCblk;
int32_t flush = cblk->u.mStreaming.mFlush;
if (flush != mFlush) {
// FIXME should return an accurate value, but over-estimate is better than under-estimate
return mFrameCount;
}
const int32_t rear = getRear();
ssize_t filled = rear - cblk->u.mStreaming.mFront;
// pipe should not already be overfull
if (!(0 <= filled && (size_t) filled <= mFrameCount)) {
ALOGE("Shared memory control block is corrupt (filled=%zd, mFrameCount=%zu); shutting down",
filled, mFrameCount);
mIsShutdown = true;
return 0;
}
// cache this value for later use by obtainBuffer(), with added barrier
// and racy if called by normal mixer thread
// ignores flush(), so framesReady() may report a larger mFrameCount than obtainBuffer()
return filled;
}
minFrames
minFrames:代表了AudioMixer支持的最少的混音帧数;
ini
// 初始值为1
uint32_t minFrames = 1;
// 判断是否为mode = MODE_STREAM且状态为MIXER_TRACKS_READY
if ((track->sharedBuffer() == 0) && !track->isStopped() && !track->isPausing() &&
(mMixerStatusIgnoringFastTracks == MIXER_TRACKS_READY)) {
// 条件满足,将desiredFrames赋值给minFrames
minFrames = desiredFrames;
}
desiredFrames
代表了当前track配置下,HAL层正常播放支持的帧数,属于用户层操作;
c
desiredFrames = sourceFramesNeededWithTimestretch(
sampleRate, mNormalFrameCount, mSampleRate, playbackRate.mSpeed);
static inline size_t sourceFramesNeededWithTimestretch(
uint32_t srcSampleRate, size_t dstFramesRequired, uint32_t dstSampleRate,
float speed) {
// required is the number of input frames the resampler needs
size_t required = sourceFramesNeeded(srcSampleRate, dstFramesRequired, dstSampleRate);
// to deliver this, the time stretcher requires:
return required * (double)speed + 1 + 1; // accounting for rounding dependencies
}
static inline size_t sourceFramesNeeded(
uint32_t srcSampleRate, size_t dstFramesRequired, uint32_t dstSampleRate) {
// +1 for rounding - always do this even if matched ratio (resampler may use phases not ratio)
// +1 for additional sample needed for interpolation
return srcSampleRate == dstSampleRate ? dstFramesRequired :
size_t((uint64_t)dstFramesRequired * srcSampleRate / dstSampleRate + 1 + 1);
}
- mNormalFrameCount:当前系统HAL层正常播放支持的帧数,属于原生系统层配置;
- srcSampleRate:采样器的采样率
- dstSampleRate:播放时将音频数据单位时间内处理的数量也是有一个转换率,也可以理解为采样率,应该叫转换率
- dstFramesRequired:播放时转换率能处理完的数据;
如果需要的采样率与硬件支持的采样率相同(源采样率 == 目标采样率),直接就返回硬件的dstFramesRequired,当源采样率 != 目标采样率时,通过size_t((uint64_t)dstFramesRequired * srcSampleRate / dstSampleRate + 1 + 1)这个公式来计算多少的数据才能满足目的帧数;
- speed:倍速,在最终计算需要的帧数的时候,需要乘以speed,再 + 2 两个帧作为缓冲;
解决方式
目前解决思路:
- 延长应用层下发的数据写入时长:参考 - Android系统下解决音频underrun噪声问题;