Audio underrun&overrun

基于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 两个帧作为缓冲;

解决方式

目前解决思路:

相关推荐
JosieBook34 分钟前
【架构】主流企业架构Zachman、ToGAF、FEA、DoDAF介绍
架构
.生产的驴1 小时前
SpringCloud OpenFeign用户转发在请求头中添加用户信息 微服务内部调用
spring boot·后端·spring·spring cloud·微服务·架构
大白要努力!2 小时前
Android opencv使用Core.hconcat 进行图像拼接
android·opencv
丁总学Java2 小时前
ARM 架构(Advanced RISC Machine)精简指令集计算机(Reduced Instruction Set Computer)
arm开发·架构
天空中的野鸟3 小时前
Android音频采集
android·音视频
ZOMI酱4 小时前
【AI系统】GPU 架构与 CUDA 关系
人工智能·架构
小白也想学C4 小时前
Android 功耗分析(底层篇)
android·功耗
曙曙学编程4 小时前
初级数据结构——树
android·java·数据结构
闲暇部落6 小时前
‌Kotlin中的?.和!!主要区别
android·开发语言·kotlin