audiotrack流程和setLoop学习(mode_static)

简述

由于前面已经学习了mode_stream的场景,所以我们这次不需要完全重新来一遍,我们只列出其中不一样的地方,所以这篇文章我们应该就可以完成全部,因为server端的逻辑是一样,所以我们主要集中在client端部分以及setLoop函数的实现。

正文

下面是测试代码,通过code去产生正弦波音频数据,播放时间为1s,然后loop 1次。这边的代码是从cts的代码中抠出来的,在AudioTrackTest::testPlayStaticData()中。

private void playStaticAudio() {
    int TEST_CONF = AudioFormat.CHANNEL_OUT_MONO;
    int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
    int TEST_SR = 48000;
    int TEST_LOOPS = 1;
    int WAIT_MSEC = 200;
    final int seconds = 1;
    final int channelCount = Integer.bitCount(TEST_CONF);
    final int bufferFrames = seconds * TEST_SR;
    final int bufferSamples = bufferFrames * channelCount;
    final int bufferSize = bufferSamples * 16;
    double frequency = 400;
    final double testFrequency = frequency / channelCount;
    AudioTrack track = new AudioTrack(3, TEST_SR,
            TEST_CONF, TEST_FORMAT, bufferSize, AudioTrack.MODE_STATIC);
    short data[] = createSoundDataInShortArray(
            bufferSamples, TEST_SR,
            testFrequency, 100);
    track.write(data, 0 /*offsetInBytes*/, data.length);
    track.setLoopPoints(0 /*startInFrames*/, bufferFrames, TEST_LOOPS);
    track.play();
    try {
        Thread.sleep(seconds * 1000 * (TEST_LOOPS + 1));
        Thread.sleep(WAIT_MSEC);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }

    // Check position after looping. AudioTrack.getPlaybackHeadPosition() returns
    // the running count of frames played, not the actual static buffer position.
    int position = track.getPlaybackHeadPosition();
    track.stop();
    try {
        Thread.sleep(WAIT_MSEC);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    // -------- tear down --------------
    track.release();
}

mode_static audiotrack

创建

从jni开始看android_media_AudioTrack_setup函数中对于STATIC MODE的处理

case MODE_STATIC:
    // AudioTrack采用share memory
    // 在client这边已经早早的把空间给分配了,而stream的是在server端分配的
    if (!lpJniStorage->allocSharedMem(buffSizeInBytes)) {
        goto native_init_failure;
    }

    status = lpTrack->set(
            AUDIO_STREAM_DEFAULT,
            sampleRateInHertz,
            format,// word length, PCM
            nativeChannelMask,
            frameCount,
            AUDIO_OUTPUT_FLAG_NONE,
            audioCallback, &(lpJniStorage->mCallbackData),//callback, callback data (user));
            0,// notificationFrames == 0 since not using EVENT_MORE_DATA to feed the AudioTrack
            // 内存空间地址
            lpJniStorage->mMemBase,// shared mem
            true,// thread can call Java
            sessionId,// audio session ID
            // transfer 类型
            AudioTrack::TRANSFER_SHARED,
            NULL,                         // default offloadInfo
            -1, -1,                       // default uid, pid values
            paa);
    break;

上面我们可以知道static的内存分配是发生在client这端的,并且类型为transfer_share

那接下来我们可以直接来看audiotrack中的createTrack_l()函数的部分code

else if (mSharedBuffer != 0) {
    // 判断share memory的分配是否符合要求,就是需要做到对齐。
    size_t alignment = audio_bytes_per_sample(mFormat);
    if (alignment & 1) {
        alignment = 1;
    }
    if (mChannelCount > 1) {
        alignment <<= 1;
    }
    // 就是要求必须是0尾的,这种后面处理的效率比较高
    if (((uintptr_t)mSharedBuffer->pointer() & (alignment - 1)) != 0) {
        status = BAD_VALUE;
        goto release;
    }
    // 重新计算framecount
    frameCount = mSharedBuffer->size() / mFrameSize;
}

上面部分主要是完成了内存空间的检测和framecount的计算。

sp<IAudioTrack> track = audioFlinger->createTrack(streamType,
                                                  mSampleRate,
                                                  mFormat,
                                                  mChannelMask,
                                                  &temp,
                                                  &trackFlags,
                                                  mSharedBuffer,
                                                  output,
                                                  tid,
                                                  &mSessionId,
                                                  mClientUid,
                                                  &status);

这个将会决定在server端创建对应的track,采用了static的话,在server端所创建的serverproxy也会发生变化。这个后面再来说,先看下client端创建的staticclientproxy。

// update proxy
if (mSharedBuffer == 0) {
    mStaticProxy.clear();
    mProxy = new AudioTrackClientProxy(cblk, buffers, frameCount, mFrameSize);
} else {
    mStaticProxy = new StaticAudioTrackClientProxy(cblk, buffers, frameCount, mFrameSize);
    mProxy = mStaticProxy;
}

StaticAudioTrackClientProxy::StaticAudioTrackClientProxy(audio_track_cblk_t* cblk, void *buffers, size_t frameCount, size_t frameSize)
    : AudioTrackClientProxy(cblk, buffers, frameCount, frameSize),
      mMutator(&cblk->u.mStatic.mSingleStateQueue),
      mPosLoopObserver(&cblk->u.mStatic.mPosLoopQueue)
{
    memset(&mState, 0, sizeof(mState));
    memset(&mPosLoop, 0, sizeof(mPosLoop));
}

StaticAudioTrackClientProxy继承自AudioTrackProxy,增加了四个成员mState、mMutator、mPosLoopObserver、mPosLoop,这些都是为了setLoop而存在的,后面我们再来分析。

那我们来看下server端的对于track的创建。
下面是TrackBase构造函数的一部分:

size_t size = sizeof(audio_track_cblk_t);
size_t bufferSize = (buffer == NULL ? roundup(frameCount) : frameCount) * mFrameSize;
if (buffer == NULL && alloc == ALLOC_CBLK) {
    size += bufferSize;
}
// client从audioflinger过来的,这个应该是不为0的。
if (client != 0) {
    // 只需要分配一个结构体audio_track_cblk_t的大小,如果是stream的话,那还需要buffer的大小
    mCblkMemory = client->heap()->allocate(size);
    if (mCblkMemory == 0 ||
            (mCblk = static_cast<audio_track_cblk_t *>(mCblkMemory->pointer())) == NULL) {
        client->heap()->dump("AudioTrack");
        mCblkMemory.clear();
        return;
    }
} else {
    // this syntax avoids calling the audio_track_cblk_t constructor twice
    mCblk = (audio_track_cblk_t *) new uint8_t[size];
    // assume mCblk != NULL
}

if (mCblk != NULL) {
    // 构造函数初始化
    new(mCblk) audio_track_cblk_t();
    switch (alloc) {
    ...
    case ALLOC_CBLK:
        // clear all buffers
        if (buffer == NULL) {
            mBuffer = (char*)mCblk + sizeof(audio_track_cblk_t);
            memset(mBuffer, 0, bufferSize);
        } else {
            // 直接指向share memory的地址
            mBuffer = buffer;//这个buffer是传参为share memory的地址
        }
        break;
     ...
    }
}

TrackBase的构造函数主要是完成了mCblk的创建和初始化,并且确定buffer的地址。

接下来看下Track的构造函数:

if (sharedBuffer == 0) {
    mAudioTrackServerProxy = new AudioTrackServerProxy(mCblk, mBuffer, frameCount,
            mFrameSize, !isExternalTrack(), sampleRate);
} else {
    mAudioTrackServerProxy = new StaticAudioTrackServerProxy(mCblk, mBuffer, frameCount,
            mFrameSize);
}
mServerProxy = mAudioTrackServerProxy;

就是创建了不同的proxy。

这边我们需要来看下proxy的构造函数:

StaticAudioTrackClientProxy::StaticAudioTrackClientProxy(audio_track_cblk_t* cblk, void *buffers,
        size_t frameCount, size_t frameSize)
    : AudioTrackClientProxy(cblk, buffers, frameCount, frameSize),
      // 这个是client这边的state的生产者,是负责将state的信息放到队列中,等待server中获取的,它们共
      // 享shared的对象
      mMutator(&cblk->u.mStatic.mSingleStateQueue),
      // 这个是client这边的posLoop信息的消费者,server是posLoop信息的生产者,它们共享shared的对象
      mPosLoopObserver(&cblk->u.mStatic.mPosLoopQueue)
{
    memset(&mState, 0, sizeof(mState));
    memset(&mPosLoop, 0, sizeof(mPosLoop));
}

StaticAudioTrackServerProxy::StaticAudioTrackServerProxy(audio_track_cblk_t* cblk, void *buffers,
        size_t frameCount, size_t frameSize)
    : AudioTrackServerProxy(cblk, buffers, frameCount, frameSize),
      // 这个是server的state消费者,跟client共享同一个shared对象
      mObserver(&cblk->u.mStatic.mSingleStateQueue),
      // 这个是server的posLoop生产者,跟client共享同一个shared对象
      mPosLoopMutator(&cblk->u.mStatic.mPosLoopQueue),
      // mFramesReadySafe,mFramesReady一开始就设置为frameCount,因为只有填充好数据才能执行play。
      // 但是其填充的数据量是不确定的。
      mFramesReadySafe(frameCount), mFramesReady(frameCount),
      mFramesReadyIsCalledByMultipleThreads(false)
{
    memset(&mState, 0, sizeof(mState));
}

重点就在上面注释的地方了,总共是有两个生产者和消费者的模型的,一个是针对state,另外一个是针对position和loopCount。
state模型,是为了通知server端,上层有对pos和loopCount进行修改的需求,所以生产者是client,消费者是server。

posLoop模型,由于pos和Loop只有server端消费了才算消费,所以需要server端来通知client端消费情况,所以server端为生产者,client端为消费者。

对于StaticAudioTrackServerProxy它的实现就跟AudioTrackServerProxy有一定的差别了,它的obtainBufferreleaseBuffer等一系列的方法都重写了。

play/stop/release

这部分的逻辑跟stream模式下的逻辑是一致的。需要的话请参考下
http://thinks.me/2016/03/18/audiotrack_play/
play的触发,在非share memory,会将mFillingUpStatus设置为FILLING,会等待数据填充满,但是对于static它是要求先填充数据之后再进行播放的,所以一开始mFillingUpStatus是为FILLED的。

http://thinks.me/2016/03/21/audiotrack_pause_stop/

write

write的部分,跟mode_stream的差别是比较大的,static的实现是非常简单的,我们直接看jni的代码:

static jint writeToTrack(const sp<AudioTrack>& track, jint audioFormat, const T *data,
                         jint offsetInSamples, jint sizeInSamples, bool blocking) {
    ssize_t written = 0;
    size_t sizeInBytes = sizeInSamples * sizeof(T);
    // 不是采用share memory
    if (track->sharedBuffer() == 0) {
        written = track->write(data + offsetInSamples, sizeInBytes, blocking);
        // for compatibility with earlier behavior of write(), return 0 in this case
        if (written == (ssize_t) WOULD_BLOCK) {
            written = 0;
        }
    } else {
        // 采用share memory
        if ((size_t)sizeInBytes > track->sharedBuffer()->size()) {
            sizeInBytes = track->sharedBuffer()->size();
        }
        // 直接将数据拷贝到缓冲区了。
        memcpy(track->sharedBuffer()->pointer(), data + offsetInSamples, sizeInBytes);
        written = sizeInBytes;
    }
    if (written > 0) {
        return written / sizeof(T);
    }
    // for compatibility, error codes pass through unchanged
    return written;
}

从上面代码中,我们发现share memory的方法非常简单,直接将数据拷贝到track的缓冲区了,并没像stream mode下面需要obtainBuffer,releaseBuffer,甚至还会有阻塞等情况。但是于此同时,我们就不得不去想,那audioflinger那边是如何判断这边数据准备好了,还是它是否要求必须先write完之后,才能进行play,否则就会报错。其实事实就是这样的,这个操作是在java层完成的,在audiotrack构造函数中,发现如果是static的类型,则对应的state是跟stream不一样的,它会设置为STATE_NO_STATIC_DATA,只有写如数据了,state才会变成STATE_INITIALIZED,这样去执行play的时候才不会报state异常。

对于audioflinger的部分我们就不用再来分析了,我们直接来看serverproxy中的两个关键函数,obtainBufferreleaseBuffer吧。

status_t StaticAudioTrackServerProxy::obtainBuffer(Buffer* buffer, bool ackFlush __unused)
{
    // 如果出现异常,则退出。
    if (mIsShutdown) {
        buffer->mFrameCount = 0;
        buffer->mRaw = NULL;
        buffer->mNonContig = 0;
        mUnreleased = 0;
        return NO_INIT;
    }
    // 去获取client端是否有变更了position或者loopCount信息,如果没有变更则获取mState.mPosition。
    ssize_t positionOrStatus = pollPosition();
    // 如果返回是个异常值,则退出
    if (positionOrStatus < 0) {
        buffer->mFrameCount = 0;
        buffer->mRaw = NULL;
        buffer->mNonContig = 0;
        mUnreleased = 0;
        return (status_t) positionOrStatus;
    }
    // 下面是简单的找到数据在缓冲区的位置
    size_t position = (size_t) positionOrStatus;
    size_t end = mState.mLoopCount != 0 ? mState.mLoopEnd : mFrameCount;
    size_t avail;
    if (position < end) {
        avail = end - position;
        size_t wanted = buffer->mFrameCount;
        if (avail < wanted) {
            buffer->mFrameCount = avail;
        } else {
            avail = wanted;
        }
        buffer->mRaw = &((char *) mBuffers)[position * mFrameSize];
    } else {
        avail = 0;
        buffer->mFrameCount = 0;
        buffer->mRaw = NULL;
    }
    LOG_ALWAYS_FATAL_IF(mFramesReady < (int64_t) avail);
    buffer->mNonContig = mFramesReady == INT64_MAX ? SIZE_MAX : clampToSize(mFramesReady - avail);
    mUnreleased = avail;
    return NO_ERROR;
}

上面的函数,我们需要注意的是里面函数pollPosition的实现,这个我们一会儿结合setLoop再来一起学习吧。剩下的部分是简单的换算buffer的指针和数据的多少。获取到buffer了,那用完就进入releaseBuffer了。

void StaticAudioTrackServerProxy::releaseBuffer(Buffer* buffer)
{
    // 获取真正被消费的帧数
    size_t stepCount = buffer->mFrameCount;
    if (stepCount == 0) {
        buffer->mRaw = NULL;
        buffer->mNonContig = 0;
        return;
    }
    // 计算还剩下多少帧尚未被消费
    mUnreleased -= stepCount;
    audio_track_cblk_t* cblk = mCblk;
    // 获取当前position的位置
    size_t position = mState.mPosition;
    // 更新position
    size_t newPosition = position + stepCount;
    int32_t setFlags = 0;
    if (!(position <= newPosition && newPosition <= mFrameCount)) {
        newPosition = mFrameCount;
    } else if (mState.mLoopCount != 0 && newPosition == mState.mLoopEnd) {
        // 对于有loop的,当新的position已经到达边界了,那就设置为start
        newPosition = mState.mLoopStart;
        // loopCount为1表示为无限循环。
        if (mState.mLoopCount == -1 || --mState.mLoopCount != 0) {
            setFlags = CBLK_LOOP_CYCLE;
        } else {
            setFlags = CBLK_LOOP_FINAL;
        }
    }
    // 如果此时newPosition依然为framecount,那就设置flag为BUFFER_END
    if (newPosition == mFrameCount) {
        setFlags |= CBLK_BUFFER_END;
    }
    // 更新position到mState
    mState.mPosition = newPosition;
    // 更新mFramesReady
    if (mFramesReady != INT64_MAX) {
        mFramesReady -= stepCount;
    }
    // 类型转换吧。。。。
    mFramesReadySafe = clampToSize(mFramesReady);
    // 更新cblk中mServer
    cblk->mServer += stepCount;
    // 将最新pos和loopCount 推入到消费队列中,等待client端消费。
    StaticAudioTrackPosLoop posLoop;
    posLoop.mBufferPosition = mState.mPosition;
    posLoop.mLoopCount = mState.mLoopCount;
    mPosLoopMutator.push(posLoop);
    // 将flag更新到cblk中的flags中
    if (setFlags != 0) {
        (void) android_atomic_or(setFlags, &cblk->mFlags);
        // this would be a good place to wake a futex
    }

    buffer->mFrameCount = 0;
    buffer->mRaw = NULL;
    buffer->mNonContig = 0;
}

从上面的函数中,我们厘清下这个函数的作用:

  1. 更新position,更新flag、更新frameready数
  2. 将pos和loopCount的信息丢给client端

我们还知道mFramesReady和mFramesReadySafe,mFramesReadySafe的出现完全只是为了安全而存在的,正常情况下它们两的值是相等的。

到这边,我们来回想下,整个数据生产消费逻辑:
static track一旦进入play,就会开始去获取数据,然后release。。然后就是又获取数据,release数据,写的逻辑在后面是没意义了。

setLoop

接下来我们来看下loop吧。。

// loopStart、loopEnd都是以帧为单位的
status_t AudioTrack::setLoop(uint32_t loopStart, uint32_t loopEnd, int loopCount)
{
    // 必须是sharebuffer或者time或者offload才可以setLoop
    if (mSharedBuffer == 0 || mIsTimed || isOffloadedOrDirect()) {
        return INVALID_OPERATION;
    }
    // 判断参数是否允许
    // a)loopCount为0的话,没特殊要求
    // b)loopCount大于等于-1,loopStart必须小于loopEnd, loopEnd必须小于等于缓冲区的帧数并且
    // loopEnd和loopStart之间的帧差必须大于等于MIN_LOOP
    // c)其他情况一概拒绝
    if (loopCount == 0) {
        ;
    } else if (loopCount >= -1 && loopStart < loopEnd && loopEnd <= mFrameCount &&
            loopEnd - loopStart >= MIN_LOOP) {
        ;
    } else {
        return BAD_VALUE;
    }

    AutoMutex lock(mLock);
    // 如果当前状态为ACTIVE是不给设置的。
    if (mState == STATE_ACTIVE) {
        return INVALID_OPERATION;
    }
    setLoop_l(loopStart, loopEnd, loopCount);
    return NO_ERROR;
}

void AudioTrack::setLoop_l(uint32_t loopStart, uint32_t loopEnd, int loopCount)
{
    // 将数据进行保存,然后调用proxy进行setLoop
    mLoopCount = loopCount;
    mLoopEnd = loopEnd;
    mLoopStart = loopStart;
    mLoopCountNotified = loopCount;
    mStaticProxy->setLoop(loopStart, loopEnd, loopCount);
}

void StaticAudioTrackClientProxy::setLoop(size_t loopStart, size_t loopEnd, int loopCount)
{
    // 64bit的客户端会发生这个问题。。。
    if (loopStart > UINT32_MAX || loopEnd > UINT32_MAX) {
        return;
    }
    mState.mLoopStart = (uint32_t) loopStart;
    mState.mLoopEnd = (uint32_t) loopEnd;
    mState.mLoopCount = loopCount;
    // max(self, other) + 1
    mState.mLoopSequence = incrementSequence(mState.mLoopSequence, mState.mPositionSequence);
    // 获取下最新的position的信息
    getBufferPositionAndLoopCount(NULL, NULL);
    if (mState.mLoopCount != 0 && mPosLoop.mBufferPosition >= mState.mLoopEnd) {
        mPosLoop.mBufferPosition = mState.mLoopStart;
    }
    mPosLoop.mLoopCount = mState.mLoopCount;
    // state发生了变化,所以将mState放入消费队列中
    (void) mMutator.push(mState);
}

上面的代码逻辑基本上setLoop的参数如何设置到clientproxy中。需要重点关注的是clientproxy中的setLoop函数。

将loop的相关信息更新到mState和mPosLoop,然后push到消费队列中,这样serverproxy那边在pollPosition的时候,就会获取到新的参数,然后重新确定buffer的指针等信息,来实现loop的功能。

下面我们补充下个别函数的实现说明。

void StaticAudioTrackClientProxy::getBufferPositionAndLoopCount(
        size_t *position, int *loopCount)
{
    // 要确保clientproxy发给serverproxy的消息已经被serverproxy处理了,然后再去读取serverproxy信息
    if (mMutator.ack() == StaticAudioTrackSingleStateQueue::SSQ_DONE) {
         if (mPosLoopObserver.poll(mPosLoop)) {
             ; 
         }
    }
    if (position != NULL) {
        *position = mPosLoop.mBufferPosition;
    }
    if (loopCount != NULL) {
        *loopCount = mPosLoop.mLoopCount;
    }
}

有没有消息的话取决于push的过程:

int32_t push(const T& value)
{
    Shared *shared = mShared;
    int32_t sequence = mSequence;
    sequence++;
    // 用sequence来实现原子操作。
    android_atomic_acquire_store(sequence, &shared->mSequence);
    shared->mValue = value;
    sequence++;
    android_atomic_release_store(sequence, &shared->mSequence);
    mSequence = sequence;
    return sequence;
}

这边巧用了sequence来实现原子操作。如果为奇数表示正要开始,为偶数就表示已经push进去了。
如果该item被observer消费的话,会更新ack跟sequence相等。
observer在获取完该item的时候一般会进行done操作,会将该ack进行加1操作。
所以如果有个item被处理了,那下面会返回SSQ_DONE。

// 获取数据之前一般会调用ack来确定是否已经处理好了,如果处理好了,再去poll数据出来
enum SSQ_STATUS ack() const
{
    // 当observer读到一个已经改变的item的时候,ack是会被改为跟mSequence一致的。
    // mSequence是用来当作互斥锁的作用的,赋值之前为奇数、赋值之后为偶数。
    // 当observer已经处理完之后,会执行done,这个时候ack会加1.变成奇数
    // 如果ack等于mSequence,那表示当前已经获取到这个item,正在处理。
    // 然后再确定下当前是处理完了还是在处理过程中,这个时候就判断ack了。
    // 如果ack小于mSequence & ~1,肯定为正,所以为pending,表示数据尚未被observer获取。
    const int32_t ack = android_atomic_acquire_load(&mShared->mAck);
    return ack - mSequence & ~1 ? SSQ_PENDING /* seq differ */ :
            ack & 1 ? SSQ_DONE : SSQ_READ;
}

上面的ack是用来判断当前item的处理状态。

这段时间发现audio这边特别喜欢用生产者消费者的逻辑,除了static mode的信息共享,fast mixer的数据流、stream mode的数据流。

感觉这篇文章讲得逻辑可能有点乱,这边稍微总结下,其实消费数据这块逻辑很简单,就是一个固定大小的缓冲区,我们只要去控制position就可以完成loop或者从那边开始的操作了,它的复杂性,其实并不是在这边,让别人觉得混乱的是SingleStateQueue实现的信息共享,并且还巧妙的采用sequence来实现原子操作。而这部分的代码查阅是需要结合serverproxy和clientproxy一起来看的,因为它们各自有生产者和消费者,clientproxy的生产者和serverproxy的消费者对接,clientproxy的消费者跟serverproxy的生产者对接,这样来实现的消息的互相传递。