08.音频系统:第004课_Android音频系统详解:第010节_音频数据的传递

通过前面的学习,在应用程序中,每创建一个AudioTrack,在AudioFlinger边,某个playbackThread中就会创建一个Track与其对应,Track与AudioTrack之间,通过共享内存传递音频数据,那么怎么传递这个数据呢?其实非常的简单,分为两种情况
1.MODE_STATIC:一次性,提前提供数据。
2.MODE_STREAM:多次传输,是一边播放,一边提供数据

在第一种MODE_STATIC情况下创建共享内存,并且一次性的构造行数据,playbackThread等构造好,取出数据就可以了,不存在同步,或者不同步的问题。其playbackThread工作:获得含有数据的obtainBuffer(APP一次性提交共享内存的数据有可能很多,playbackThread分分开多次进行播放)。播放完之后,再释放buffer。

在第二种MODE_STREAM情况下,他们也是通过共享内存传递数据,不同的是APP在写数据的时候,playbackThread在播放数据。APP怎么去写数据呢?他也是调用obtainBuffer,获取空白buffer,然后填充数据,在释放buffer。playbackThread端与第一种情况一样。
在这里插入图片描述
应用程序的代码在AudioTrack.cpp之中,playbackThread的代码在
Tracks.cpp之中。我们打开MediaAudioTrackTest.java文件,我们先来那看复杂的MODE_STREAM模式,找到

public void testSetLoopPointsStream() throws Exception {
	/*其会导致playbackThread线程中对应的Track与共享内存的创建*/
	AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT, 2*minBuffSize, TEST_MODE);
	/*写数据,该函数在AudioTrack.java中实现*/
	track.write(data, 0, data.length);
		/*可以知道,其最终都是调用到C++中实现的native_write_byte函数*/
		int ret = native_write_byte(audioData, offsetInBytes, sizeInBytes, mAudioFormat,writeMode == WRITE_BLOCKING);

搜索native_write_byte进入android_media_AudioTrack.cpp文件:

static jint android_media_AudioTrack_writeArray(JNIEnv *env, jobject thiz,T javaAudioData,jint offsetInSamples, jint sizeInSamples,jint javaAudioFormat,jboolean isWriteBlocking) {
	/*把java的AudioTrack转化为c++实现的AudioTrack*/
	sp<AudioTrack> lpTrack = getAudioTrack(env, thiz);
	jint samplesWritten = writeToTrack(lpTrack, javaAudioFormat, cAudioData,offsetInSamples, sizeInSamples, isWriteBlocking == JNI_TRUE /* blocking */);
		/*如果应用程序没有提供共享内存*/
	    if (track->sharedBuffer() == 0) {
	    	/*调用AudioTrack中write函数*/
	        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 {//如果应用程序提供了共享内存
	        // writing to shared memory, check for capacity
	        if ((size_t)sizeInBytes > track->sharedBuffer()->size()) {
	            sizeInBytes = track->sharedBuffer()->size();
	        }
	        /*直接使用memcpy写入数据*/
	        memcpy(track->sharedBuffer()->pointer(), data + offsetInSamples, sizeInBytes);
	        written = sizeInBytes;
	    }

从上面我们可以看到,如果应用程序提供了共享内存,直接使用memcpy写入数据,如果没有程序没有提供共享内存,则调用AudioTrack中的write函数如下:

ssize_t AudioTrack::write(const void* buffer, size_t userSize, bool blocking)
	/*获得空白buffer*/
	status_t err = obtainBuffer(&audioBuffer,blocking ? &ClientProxy::kForever : &ClientProxy::kNonBlocking);
	/*把数据写入buffer*/
	memcpy(audioBuffer.i8, buffer, toWrite);
	/*释放buffer*/
	releaseBuffer(&audioBuffer);

现在我们来看看Tracks.cpp中的是怎么使用obtainBuffer读取数据的:

status_t AudioFlinger::PlaybackThread::Track::getNextBuffer(
	status_t status = mServerProxy->obtainBuffer(&buf);

可以看到其会调用mServerProxy中的obtainBuffer,其释放buffer的函数是在其父类TrackBase中实现的:

void AudioFlinger::ThreadBase::TrackBase::releaseBuffer(AudioBufferProvider::Buffer* buffer)
	mServerProxy->releaseBuffer(&buf);

可以看到其调用mServerProxy的releaseBuffer函数。其中ServerProxy* mServerProxy。mServerProxy是什么呢?我们看看上小节我们分析的结果:

AudioFlinger::PlaybackThread::Track::Track(
	if (sharedBuffer == 0) {//如果共享内存为应用程序提供
        mAudioTrackServerProxy = new AudioTrackServerProxy(mCblk, mBuffer, frameCount,
                mFrameSize, !isExternalTrack(), sampleRate);
    } else {//如果共享内存为PlaybackThread提供
        mAudioTrackServerProxy = new StaticAudioTrackServerProxy(mCblk, mBuffer, frameCount,
                mFrameSize);
    }
    mServerProxy = mAudioTrackServerProxy;

Track的构造函数,可以找到如上,知道其根据共享内存的创建者不同,得到不同的mServerProxy对象 :
在这里插入图片描述
我们可以猜测得到,当调用mServerProxy中obtainBuffer与releaseBuffer方法,会导致AudioTrack.cpp:

status_t AudioTrack::set(
	if (mSharedBuffer == 0) {
        mStaticProxy.clear();
        mProxy = new AudioTrackClientProxy(cblk, buffers, frameCount, mFrameSize);
    } else {
        mStaticProxy = new StaticAudioTrackClientProxy(cblk, buffers, frameCount, mFrameSize);
        mProxy = mStaticProxy;

对应的mProxy 中的obtainBuffer与releaseBuffer被调用,mProxy 是给应用程序管理共享内存的,mServerProxy是给playbackThread管理共享内存的。

下面我们做一下总结:

1.APP创建AudioTrack,然后playbackThread创建对应的Track。他们之间通过共享内存传递数据。

2.APP有两种共享内存的方式
MODE_STATIC:APP创建共享内存,APO一次性填充数据
MODE_STREAM:APP使用obtiainBuffer获得空白内存,填充数据。然后使用releaseBuffer释放。

3.playbackThread使用obtiainBuffer获得含有数据的内存,消耗数据之后使用releaseBuffer释放。

d.AudioTrack中含有mProxy,用来管理内存,其中包含了obtainBuffer, releaseBuffer函数。
Track中含有mServerProxy, 它被用来管理共享内存, 里面含有obtainBuffer, releaseBuffer函数。
对于不同的MODE(MODE_STATIC或者MODE_STREAM:APP), 这些Proxy指向不同的对象

MODE_STATIC模式

我们继续往下分析,MODE_STATIC模式下,应用程序直接把数据放入共享内存,他跟playbackThread的Track不需要同步,playbackThread通过obtainBuffer获得buffer,那么我们现在分析一下他是怎么获得buffer的。怎么releaseBuffer的。
我们先看看Track的构造函数:

AudioFlinger::PlaybackThread::Track::Track(
	if (sharedBuffer == 0) {
        mAudioTrackServerProxy = new AudioTrackServerProxy(mCblk, mBuffer, frameCount,mFrameSize, !isExternalTrack(), sampleRate);
    } else {
        mAudioTrackServerProxy = new StaticAudioTrackServerProxy(mCblk, mBuffer, frameCount,mFrameSize);
        	memset(&mState, 0, sizeof(mState));
    }
    mServerProxy = mAudioTrackServerProxy;

对于我们这种情况,其就会使用StaticAudioTrackServerProxy对buffer进行管理:

StaticAudioTrackServerProxy::StaticAudioTrackServerProxy(audio_track_cblk_t* cblk, void *buffers,
        size_t frameCount, size_t frameSize)
    : AudioTrackServerProxy(cblk, buffers, frameCount, frameSize),
      mObserver(&cblk->u.mStatic.mSingleStateQueue),
      mPosLoopMutator(&cblk->u.mStatic.mPosLoopQueue),
      mFramesReadySafe(frameCount), mFramesReady(frameCount),
      mFramesReadyIsCalledByMultipleThreads(false)
{
    memset(&mState, 0, sizeof(mState));
}

可以知道其设置了共享内存的大小,他会通过obtiainBuffer函数获取buffer:

/*该结构体在StaticAudioTrackServerProxy的构造函数中指定*/
struct Buffer {
		/*想去几帧数据*/
        size_t  mFrameCount;            // number of frames available in this buffer
        /*取出的数据指向谁*/
        void*   mRaw;                   // pointer to first frame
        size_t  mNonContig;             // number of additional non-contiguous frames available
};
status_t StaticAudioTrackServerProxy::obtainBuffer(Buffer* buffer, bool ackFlush)
	/*获得位置*/
	ssize_t positionOrStatus = pollPosition();
	/*设置mRaw 指向当前数据的位置(前面提到,去数据的时候,可能因为共享内存数据太多,分多次提取)*/
	buffer->mRaw = &((char *) mBuffers)[position * mFrameSize];

下面我们看看MODE_STREAM模式

MODE_STREAM模式

MODE_STREAM会使用到环形缓存区,一个生产数据,一个消费数据,这个时候使用环形缓冲区是最可靠的。
在这里插入图片描述
比如下面是假设buffer最多有五个数据,最开始的时候都是没有数据的,读写数据都指向为buffe数据0r的初始位置:
1.如何判断buffer为空呢?,当RW位置的时候,则代表空。
2.如何判断buffer满了:当W-R
LEN的时候代表满了。
3.如:有5个数据,我们写入3个数据,那么R=0,w=3。
4.读取两个数据,那么R +=2,此时R==2,此时buffer中只有1个数据了
5.如果现在再写入3个数据,那么R还是2位置,但是W指向1数据位置。其中3,4,0都是被写入了数据(从尾部回到头部)

在音频系统中,对于MODE_STREAM,其也是使用环形缓冲区传递数据,在上一节视频中,我们知道其会创建共享内存,这个共享内存中包括了一个audio_track_cblk_t* cblk = mCblk头部,其中就包含了读写指针,len,len向上取2的n次方(环形缓冲区算法的优化,为了更快的求模运算)

在这里插入图片描述
当我们的应用程序想要获得空白的buffer,根据:

status_t AudioTrack::set(
	if (mSharedBuffer == 0) {
        mStaticProxy.clear();
        mProxy = new AudioTrackClientProxy(cblk, buffers, frameCount, mFrameSize);
    } else {
        mStaticProxy = new StaticAudioTrackClientProxy(cblk, buffers, frameCount, mFrameSize);
        mProxy = mStaticProxy;

我们知道去会调用StaticAudioTrackClientProxy中的obtainBuffer(最终调用到父类的ClientProxy)函数:

struct Buffer {
		/*想取几帧数据*/
        size_t  mFrameCount;            // number of frames available in this buffer
        /*取出的数据指向谁*/
        void*   mRaw;                   // pointer to first frame
        size_t  mNonContig;             // number of additional non-contiguous frames available
};
status_t ClientProxy::obtainBuffer(Buffer* buffer, const struct timespec *requested,struct timespec *elapsed)
	if (mIsOut) {//输出数据
		/*把mFront(读指针)的值读出来*/
		front = android_atomic_acquire_load(&cblk->u.mStreaming.mFront);
		/*写指针*/
		rear = cblk->u.mStreaming.mRear;
		/*现在填充了多少数据*/
		ssize_t filled = rear - front;
		/*剩下多少空的buffer*/
		ssize_t avail =  (mIsOut) ? adjustableSize - filled : filled;
		} else if (avail > 0) {
			/*空白的buffer分成了多个part,与*/
			size_t part1;
            if (mIsOut) {
                rear &= mFrameCountP2 - 1;
                part1 = mFrameCountP2 - rear;
            } else {
                front &= mFrameCountP2 - 1;
                part1 = mFrameCountP2 - front;
            }
            if (part1 > (size_t)avail) {
                part1 = avail;
            }
            if (part1 > buffer->mFrameCount) {
                part1 = buffer->mFrameCount;
            }
            /*返回buffer的指针,则应用程序得到一个空白buffer*/
            buffer->mRaw = part1 > 0 ? &((char *) mBuffers)[(mIsOut ? rear : front) * mFrameSize] : NULL;

这样我们就能得到一个空白buffer,就可以填充数据了。

猜你喜欢

转载自blog.csdn.net/weixin_43013761/article/details/89575847