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

解决方式

目前解决思路:

相关推荐
晨春计1 小时前
【git】
android·linux·git
2401_840192271 小时前
ELFK日志分析平台,架构和通信
elk·elasticsearch·架构
标标大人2 小时前
c语言中的局部跳转以及全局跳转
android·c语言·开发语言
木鬼与槐3 小时前
MySQL高阶1783-大满贯数量
android·数据库·mysql
iofomo3 小时前
【Abyss】Android 平台应用级系统调用拦截框架
android·开发工具·移动端
武子康4 小时前
大数据-134 - ClickHouse 集群三节点 安装配置启动
java·大数据·分布式·clickhouse·架构·flink
AirDroid_cn5 小时前
在家找不到手机?除了语音助手,还可以用远程控制!
android·智能手机·远程控制·手机使用技巧·远程控制手机
东城绝神8 小时前
《Linux运维总结:基于ARM64+X86_64架构CPU使用docker-compose一键离线部署mongodb 7.0.14容器版副本集群》
linux·运维·mongodb·架构
Good_tea_h13 小时前
Android中如何处理运行时权限?
android
冬田里的一把火313 小时前
[Android][Reboot/Shutdown] 重启/关机 分析
android·gitee