audiotrack的pause\stop流程(mode_stream)

简述

前面我们已经完成了,从audiotrack的创建、播放,接下来我们要看下暂停和停止。
pause和stop逻辑基本上是一样的,它们的不同点在于,pause的场景是,立马就暂停了,不管buffer中是否有数据,而stop的场景的话,则会将buffer中的数据排空,然后重置状态。

正文

pause部分

这节我们先来看下pause下面的场景,以及pause之后,发生mediaserver挂掉,然后执行播放的逻辑,主要涉及到client端和server端。

这个依然会牵扯到生产者和消费者。

生产者这边对于pause的处理

我们直接来看audiotrack的pause函数的实现

void AudioTrack::pause()
{
    AutoMutex lock(mLock);
    // 实现状态切换
    if (mState == STATE_ACTIVE) {
        mState = STATE_PAUSED;
    } else if (mState == STATE_STOPPING) {
        mState = STATE_PAUSED_STOPPING;
    } else {
        return;
    }
    // 调用proxy的interrupt函数
    mProxy->interrupt();
    // 调用audioflinger中track的pause操作
    mAudioTrack->pause();
    // Offload的场景,后面找例子学习
    if (isOffloaded_l()) {
        if (mOutput != AUDIO_IO_HANDLE_NONE) {
            // OffloadThread发送hal pause是在threadloop中,时间保存在这边比较合适

            // 一个offload output可以被两个相同配置audiotrack复用。
            // 如果去查询一个paused track的时间,另外一个track一直在跑,可能会返回一个不正确的时间
            // 鉴于此,我们就在paused的时候保存时间,resume的时候,返回这个值。

            uint32_t halFrames;
            AudioSystem::getRenderPosition(mOutput, &halFrames, &mPausedPosition);
        }
    }
}

void ClientProxy::interrupt()
{
    audio_track_cblk_t* cblk = mCblk;
    // 如果之前flags中不存在CBLK_INTERRUPT,则去唤醒proxy等待获取buffer的操作,告诉client不用再填
    // 充数据了
    if (!(android_atomic_or(CBLK_INTERRUPT, &cblk->mFlags) & CBLK_INTERRUPT)) {
        android_atomic_or(CBLK_FUTEX_WAKE, &cblk->mFutex);
        (void) syscall(__NR_futex, &cblk->mFutex, mClientInServer ? FUTEX_WAKE_PRIVATE : FUTEX_WAKE,
                1);
    }
}

上面的逻辑较为简单:

  1. 改下audiotrack的内部状态,pause要生效,必须要求状态为STATE_ACTIVE或者STATE_STOPPING,否则直接返回
  2. 告诉proxy可以终止问server端要数据了
  3. 告诉audioflinger,这个track进入到pause状态了。

1和2都在上面,3我们来看下:

void AudioFlinger::PlaybackThread::Track::pause()
{
    sp<ThreadBase> thread = mThread.promote();
    if (thread != 0) {
        Mutex::Autolock _l(thread->mLock);
        PlaybackThread *playbackThread = (PlaybackThread *)thread.get();
        switch (mState) {
        case STOPPING_1:
        case STOPPING_2:
            if (!isOffloaded()) {
                // STOPPING_1和STOPPING_2是针对offload的场景
                break;
            }
            // Offloaded track正在排数据,在resume的时候我们需要继续排
            mResumeToStopping = true;
            // fall through...
        case ACTIVE:
        case RESUMING:
            // 状态被改成了pausing了。
            mState = PAUSING;
            playbackThread->broadcast_l();
            break;

        default:
            break;
        }
    }
}

上面的逻辑更简单,只是将mState设置为了PAUSING,然后就往对应的线程里面发送广播了。

接下来,我们进入到消费者端来看针对pause场景,它是如何处理的。

消费者对于pause的处理

上面提到了往线程里面发送广播,它的实现如下:

void AudioFlinger::PlaybackThread::broadcast_l()
{
    // 线程可能被阻塞等待异步信号来让它及时的操作状态的变化
    // 为了防止我们当前触发一次唤醒,但是它可能会没被处理到,我们添加了mSignalPending来防止这种情况
    mSignalPending = true;
    mWaitWorkCV.broadcast();
}

接下来,我们来看下prepareTracks_l是如何应付这个pause事件的,这次截取部分代码,因为完整的之前也一起看过了。
// 当前state还是处于pausing,所以这边是可以进来的,此刻的minFrames为1
if ((framesReady >= minFrames) && track->isReady() &&
!track->isPaused() && !track->isTerminated())
{
mixedTracks++;

    int param = AudioMixer::VOLUME;
    // mFillingUpStatus为FS_ACTIVE
    if (track->mFillingUpStatus == Track::FS_FILLED) {
        // no ramp for the first volume setting
        track->mFillingUpStatus = Track::FS_ACTIVE;
        if (track->mState == TrackBase::RESUMING) {
            track->mState = TrackBase::ACTIVE;
            param = AudioMixer::RAMP_VOLUME;
        }
        mAudioMixer->setParameter(name, AudioMixer::RESAMPLE, AudioMixer::RESET, NULL);
    } else if (cblk->mServer != 0) {
        param = AudioMixer::RAMP_VOLUME;
    }

    // compute volume for this track
    uint32_t vl, vr;       // in U8.24 integer format
    float vlf, vrf, vaf;   // in [0.0, 1.0] float format
    // 触发这边的逻辑,会将音量调整为0,可以实现ramp效果
    if (track->isPausing() || mStreamTypes[track->streamType()].mute) {
        vl = vr = 0;
        vlf = vrf = vaf = 0.;
        // 将pausing设置为paused了,那下次,enable audiomixer的逻辑就没办法进入了。
        if (track->isPausing()) {
            track->setPaused();
        }
    } else {
    ...
    }

    // reset retry count
    track->mRetryCount = kMaxTrackRetries;

    if (mMixerStatusIgnoringFastTracks != MIXER_TRACKS_READY ||
            mixerStatus != MIXER_TRACKS_ENABLED) {
        mixerStatus = MIXER_TRACKS_READY;
    }
} else {
    // 第二次循环,此时state为paused了。
    // 会进入到下面的逻辑中
    if ((track->sharedBuffer() != 0) || track->isTerminated() ||
            track->isStopped() || track->isPaused()) {
        // 会在继续填充latency的时间长度的空白数据,这个有优化空间
        size_t audioHALFrames = (latency_l() * mSampleRate) / 1000;
        size_t framesWritten = mBytesWritten / mFrameSize;
        // 进入下面这个流程需要花点时间来排空latency的。
        if (mStandby || track->presentationComplete(framesWritten, audioHALFrames)) {
            // 如果状态是stopped的话,我们会对这个track进行reset操作。
            if (track->isStopped()) {
                track->reset();
            }
            // 移除active track list
            tracksToRemove->add(track);
        }
    } else {
        // 这边的逻辑是对于underrun了。前面有看过了。
        ...
    }
    mAudioMixer->disable(name);
}

从上面的代码来梳理:

  1. paused是有两个状态的,一个是pausing一个是paused
  2. 当设置paused的时候,都会先跑pausing的流程,这个是为了辅助实现volume ramp的,但是也同时意味着会多消耗小于等于framecount的缓冲区的数据。
  3. 完全进入到paused状态之后,该track并没有第一时间被移除active track list,而是还要排latency时间长度的数据。

到这边pause的流程就讲完了。

对于pause后mediaserver挂掉逻辑

我们在创建audiotrack的时候,函数createTrack_l()有如下几行代码:

mDeathNotifier = new DeathNotifier(this);
IInterface::asBinder(mAudioTrack)->linkToDeath(mDeathNotifier, this);

当mediaserver挂掉的话,会触发DeathNotifier::binderDied函数的调用:

void AudioTrack::DeathNotifier::binderDied(const wp<IBinder>& who __unused)
{
    sp<AudioTrack> audioTrack = mAudioTrack.promote();
    if (audioTrack != 0) {
        AutoMutex lock(audioTrack->mLock);
        audioTrack->mProxy->binderDied();
    }
}

可以看到主要的逻辑就是调用ClientProxy::binderDied()

void ClientProxy::binderDied()
{
    audio_track_cblk_t* cblk = mCblk;
    // 如果之前没有CBLK_INVALID,则去唤醒obtainBuffer,同时设置cblk->mFlags添加CBLK_INVALID
    if (!(android_atomic_or(CBLK_INVALID, &cblk->mFlags) & CBLK_INVALID)) {
        android_atomic_or(CBLK_FUTEX_WAKE, &cblk->mFutex);
        (void) syscall(__NR_futex, &cblk->mFutex, mClientInServer ? FUTEX_WAKE_PRIVATE : FUTEX_WAKE, 1);
    }
}

如果此时clientProxy正阻塞在那,那我们让它醒过来,然后处理下CBLK_INVALID事件,其实就是直接让obtainBuffer返回了。

其实到这边,audiotrack的事情就已经干完了,只是flag中多添加了一个CBLK_INVALID标志,而且如果之前有在尝试获取server端的空间的话,则停止。

那接下来我们就重新回到mediaserver起来了,并且我们调用了start的逻辑。start的逻辑我们在第二节的时候就已经分析过了,我们可以直接来看其中对于flag的处理,如果发现带有CBLK_INVALID,则会触发AudioTrack::restoreTrack_l函数:

status_t AudioTrack::restoreTrack_l(const char *from)
{
    ++mSequence;// 这个值就跟track的restore相关的,记录restore的次数

    // 出现这种情况基本上是server端挂了,这个时候output的信息是有可能发生变化的,所以我们需要将
    // 存储在AudioSystem中的一些cache清空,重新获取
    AudioSystem::clearAudioConfigCache();

    // 对于offload或者一开始设置不去重连的,直接返回即可
    if (isOffloadedOrDirect_l() || mDoNotReconnect) {
        return DEAD_OBJECT;
    }

    // save the old static buffer position
    size_t bufferPosition = 0;
    int loopCount = 0;
    if (mStaticProxy != 0) {
        mStaticProxy->getBufferPositionAndLoopCount(&bufferPosition, &loopCount);
    }

    // 如果IAudioTrack被成功创建的话,createTrack_l()将会修改变量,mAudioTrack, mCblkMemory and 
    // mCblk.之前的强引用将会被删掉,如果创建失败的话,之前的对象也将被遗弃。
    status_t result = createTrack_l();

    if (result == NO_ERROR) {
        // mReleased表示的是client往buffer中已经填充了多少数据,这个数据也不是很准确,因为server端
        // 消费了多少,无人能知。
        if (mStaticProxy == 0) {
            mPosition = mReleased;
        }
        // 继续播放上一次所知道的位置,和重置loop,对于mStaticProxy
        if (mStaticProxy != 0) {
            if (loopCount != 0) {
                mStaticProxy->setBufferPositionAndLoop(bufferPosition,
                        mLoopStart, mLoopEnd, loopCount);
            } else {
                mStaticProxy->setBufferPosition(bufferPosition);
                if (bufferPosition == mFrameCount) {
                    ALOGD("restoring track at end of static buffer");
                }
            }
        }
        // 由于我们触发restore是从start开始,这个时候状态已经为STATE_ACTIVE,所以开始跑start流程
        if (mState == STATE_ACTIVE) {
            result = mAudioTrack->start();
        }
    }
    // 如果创建失败的话,则将状态设置为stopped。mReleased设置为0
    if (result != NO_ERROR) {
        mState = STATE_STOPPED;
        mReleased = 0;
    }

    return result;
}

到这边我们就完成了pause后mediaserver挂掉,然后继续播放的逻辑了。

stop部分

看完pause我们接下来看下stop的场景。
基本上逻辑是一样的,它们的不同点在于,pause的场景是,立马就暂停了,不管buffer中是否有数据,而stop的场景的话,则会将buffer中的数据排空,然后重置状态。

也是分为生产者和消费者部分。

生产者这边对于stop的处理

我们直接来看audiotrack的stop函数的实现

void AudioTrack::stop()
{
    AutoMutex lock(mLock);
    // 只接收当前状态为STATE_ACTIVE或者STATE_PAUSED的状态
    if (mState != STATE_ACTIVE && mState != STATE_PAUSED) {
        return;
    }
    // 对于offload的,增加的STATE_STOPPING的状态,其他的则采用STATE_STOPPED
    if (isOffloaded_l()) {
        mState = STATE_STOPPING;
    } else {
        // 调用stop的话,会将mReleased置成0
        mState = STATE_STOPPED;
        mReleased = 0;
    }
    // 这个函数我们在pause中已经见识过了
    mProxy->interrupt();
    mAudioTrack->stop();
    // 播放的头位置已经重置回0了,所以如果有marker被设置,我们需要重新激活
    mMarkerReached = false;

    if (mSharedBuffer != 0) {
        // clear buffer position and loop count.
        mStaticProxy->setBufferPositionAndLoop(0 /* position */,
                0 /* loopStart */, 0 /* loopEnd */, 0 /* loopCount */);
    }

    sp<AudioTrackThread> t = mAudioTrackThread;
    if (t != 0) {
        // 不是offload的,我们将其暂停。如果是offload的话,则不进行处理。
        if (!isOffloaded_l()) {
            t->pause();
        }
    } else {
        setpriority(PRIO_PROCESS, 0, mPreviousPriority);
        set_sched_policy(0, mPreviousSchedulingGroup);
    }
}
  1. 判断当前状态是否符合stop的场景,不符合直接返回
  2. 修改状态
  3. 中断obtainBuffer
  4. 调用IAudioTrack的stop操作
  5. 相关参数重置,mReleased和mMarkerReached等
  6. 有线程的话,不是offlaod的话进行pause操作,不是offlaod的则不管。

接下来我们看下IAudioTrack的stop函数

void AudioFlinger::PlaybackThread::Track::stop()
{
    sp<ThreadBase> thread = mThread.promote();
    if (thread != 0) {
        Mutex::Autolock _l(thread->mLock);
        track_state state = mState;
        if (state == RESUMING || state == ACTIVE || state == PAUSING || state == PAUSED) {
            PlaybackThread *playbackThread = (PlaybackThread *)thread.get();
            // 这个是针对pause之后再stop的场景,因为正常stop会在排空数据之后执行reset,而pause不会
            // 有该操作,所以pause之后再stop的话,没办法利用thread中去reset,所以我们在这边reset。
            if (playbackThread->mActiveTracks.indexOf(this) < 0) {
                reset();
                mState = STOPPED;
            } else if (!isFastTrack() && !isOffloaded() && !isDirect()) {
                mState = STOPPED;
            } else {
                // 这个状态是为fast track和offload track存在的。
                // 对于fast track,只有进入到STOPPING_2才表示完成stop了
                // 对于offload track,STOPPING_1表示开始排数据,当排空之后会切换到STOPPING_2
                // 然后是STOPPED,这部分我们学习offload和fast track再进一步分析吧。
                mState = STOPPING_1;
            }
            // 发送唤醒
            playbackThread->broadcast_l();
        }
    }
}

上面函数补充了对于pause到stop场景,对于track的reset操作。之后也真是简单的切换状态,然后就去唤醒server端的线程了。

接下来,我们进入到消费者端来看针对stop场景,它是如何处理的。

消费者对于stop的处理

发广播在pause中已经见过了,就不在涉及。我们直接来看prepareTracks_l是如何应付这个stop事件的,这次截取部分代码,因为完整的之前也一起看过了。

// 当前state处于stop,如果buffer中还有一些数据的话,这边的逻辑是可以直接进入的。isReady会放行
// STOPPED\PAUSING\PAUSED以及mFillingUpStatus != FS_FILLING,这边的minFrames为1
// 所以下面的逻辑会一直跑到framesReady中的数据完
// 这样就会有个问题,就是app调用了stop,但是还是一直填充数据的话,但是是已病态的形式在输出的
// 记得我们在讲解write函数的时候,在获取缓冲区的时候,如果发现当前是stoped或者paused的话,获取缓冲区
// 就会变成 non blocking,如果client写得够快的话,那就会丢掉非常多的数据
if ((framesReady >= minFrames) && track->isReady() &&
        !track->isPaused() && !track->isTerminated())
{
    mixedTracks++;

    int param = AudioMixer::VOLUME;
    // mFillingUpStatus为FS_ACTIVE
    if (track->mFillingUpStatus == Track::FS_FILLED) {
        // no ramp for the first volume setting
        track->mFillingUpStatus = Track::FS_ACTIVE;
        if (track->mState == TrackBase::RESUMING) {
            track->mState = TrackBase::ACTIVE;
            param = AudioMixer::RAMP_VOLUME;
        }
        mAudioMixer->setParameter(name, AudioMixer::RESAMPLE, AudioMixer::RESET, NULL);
    } else if (cblk->mServer != 0) {
        param = AudioMixer::RAMP_VOLUME;
    }

    // compute volume for this track
    uint32_t vl, vr;       // in U8.24 integer format
    float vlf, vrf, vaf;   // in [0.0, 1.0] float format
    if (track->isPausing() || mStreamTypes[track->streamType()].mute) {
        vl = vr = 0;
        vlf = vrf = vaf = 0.;
        // 将pausing设置为paused了,那下次,enable audiomixer的逻辑就没办法进入了。
        if (track->isPausing()) {
            track->setPaused();
        }
    } else {
    ...
    }

    // reset retry count
    track->mRetryCount = kMaxTrackRetries;

    if (mMixerStatusIgnoringFastTracks != MIXER_TRACKS_READY ||
            mixerStatus != MIXER_TRACKS_ENABLED) {
        mixerStatus = MIXER_TRACKS_READY;
    }
} else {
    // 等数据输出完成了之后,就进入这边了
    if ((track->sharedBuffer() != 0) || track->isTerminated() ||
            track->isStopped() || track->isPaused()) {
        // 会在继续填充latency的时间长度的空白数据,这个有优化空间
        size_t audioHALFrames = (latency_l() * mSampleRate) / 1000;
        size_t framesWritten = mBytesWritten / mFrameSize;
        // 进入下面这个流程需要花点时间来排空latency的。
        if (mStandby || track->presentationComplete(framesWritten, audioHALFrames)) {
            // 如果状态是stopped的话,我们会对这个track进行reset操作。
            if (track->isStopped()) {
                track->reset();
            }
            // 移除active track list
            tracksToRemove->add(track);
        }
    } else {
        // 这边的逻辑是对于underrun了。前面有看过了。
        ...
    }
    mAudioMixer->disable(name);
}

好吧上面的逻辑其实没啥好说的。跟pause基本上一致,只是他们对待数据的方式不一样罢了。

我们再梳理一遍吧:

  1. 如果是stopped状态,minFrames将为1,接下来会一直进入到enable audiomixer
  2. 直到将数据排空了之后,就会进入排空latency的周期
  3. 当排空latency时间长度的时间之后,我们如果发现是stopped的话,我们会对它进行reset操作
  4. 移除active track。

我们来看下reset函数都干嘛了

void AudioFlinger::PlaybackThread::Track::reset()
{
    // mResetDone会被addTrack的时候进行重置为false。
    // Do not reset twice to avoid discarding data written just after a flush and before
    // the audioflinger thread detects the track is stopped.
    if (!mResetDone) {
        // Force underrun condition to avoid false underrun callback until first data is
        // written to buffer
        android_atomic_and(~CBLK_FORCEREADY, &mCblk->mFlags);
        // 填充状态被设置回去了。
        mFillingUpStatus = FS_FILLING;
        mResetDone = true;
        // 如果是flush这个状态会被设置为IDLE。
        if (mState == FLUSHED) {
            mState = IDLE;
        }
    }
}

这边可能需要关注的是mFillingUpStatus状态的重置。其他有关flush的,我们flush那边再分析。
CBLK_FORCEREADY 这个标志主要是使用在prepareTracks_l中判断track isReady的时候会用到,如果是第一次进入的话,数据没能完全填充满申请的空间的话,如果有这个标志位,那也可以切换mFillingUpStatus。但是目前是不采用的,我们还是先把buffer给灌满,才去修改mFillingUpStatus的状态的。

我们来看下PlaybackThread::removeTracks_l的逻辑

void AudioFlinger::PlaybackThread::removeTracks_l(const Vector< sp<Track> >& tracksToRemove)
{
    size_t count = tracksToRemove.size();
    if (count > 0) {
        for (size_t i=0 ; i<count ; i++) {
            const sp<Track>& track = tracksToRemove.itemAt(i);
            // 先从active track list中移掉该track
            mActiveTracks.remove(track);
            // 占有wake lock 可以释放了。
            mWakeLockUids.remove(track->uid());
            mActiveTracksGeneration++;
            // 如果有效果链, 则引用计数更新
            sp<EffectChain> chain = getEffectChain_l(track->sessionId());
            if (chain != 0) {
                chain->decActiveTrackCnt();
            }
            // terminate只针对outputTrack和PatchTrack
            if (track->isTerminated()) {
                removeTrack_l(track);
            }
        }
    }

}

干的事情非常有限,就是移除active track list,然后一些引用计数的更新。

对于希望stop的时候,不会去排空buffer中的数据,我们从代码中知道,可以先pause完之后,在调用stop。