本文均属自己阅读源码的点滴总结,转账请注明出处谢谢。
欢迎和大家交流。qq:1037701636 email:gzzaigcn2012@gmail.com
Android源码版本Version:4.2.2; 硬件平台 全志A31
前沿:
在前面的博文中,基本提到的是stagefright相关的控制流,具体分析了android架构中的MediaExtractor、AwesomePlayer、StagefrightPlayer、OMXCodec等的创建,底层OMXNodinstance实例的创建。分析了OMX最底层插件库、编解码器组件的架构以及如何创建属于我们自己的OMX Plugin。
分析源码架构的另一个关键是数据流的分析,从这里开始,我们将对stagefright中的编解码缓存区进行分析:
1.
回到OMXCodec的创建过程的源码:
- status_t AwesomePlayer::initVideoDecoder(uint32_t flags) {
- .......
- mVideoSource = OMXCodec::Create(
- mClient.interface(), mVideoTrack->getFormat(),
- false,
- mVideoTrack,
- NULL, flags, USE_SURFACE_ALLOC ? mNativeWindow : NULL);
-
- if (mVideoSource != NULL) {
- int64_t durationUs;
- if (mVideoTrack->getFormat()->findInt64(kKeyDuration, &durationUs)) {
- Mutex::Autolock autoLock(mMiscStateLock);
- if (mDurationUs < 0 || durationUs > mDurationUs) {
- mDurationUs = durationUs;
- }
- }
-
- status_t err = mVideoSource->start();
- .............
- }
在Android4.2.2下Stagefright多媒体架构中的A31的OMX插件和Codec组件 博文我们对于OMXCodec::create已经做了详细的分析,这里来关注mVideoSource->start的相关功能,即OMXCodec::start的处理:
- status_t OMXCodec::start(MetaData *meta) {
- Mutex::Autolock autoLock(mLock);
- ........
- return init();
- }
这里调用init()的过程,将会进行buffer的申请操作,为后续的流操作打下基础:
- status_t OMXCodec::init() {
-
- .........
- err = allocateBuffers();
- if (err != (status_t)OK) {
- return err;
- }
-
- if (mQuirks & kRequiresLoadedToIdleAfterAllocation) {
- err = mOMX->sendCommand(mNode, OMX_CommandStateSet, OMX_StateIdle);
- CHECK_EQ(err, (status_t)OK);
-
- setState(LOADED_TO_IDLE);
- }
- ............
- }
我们来看allocateBuffers的实现
2.关注allocateBuffersOnPort的实现
- status_t OMXCodec::allocateBuffers() {
- status_t err = allocateBuffersOnPort(kPortIndexInput);//输入缓存input口分配
-
- if (err != OK) {
- return err;
- }
-
- return allocateBuffersOnPort(kPortIndexOutput);//输出缓存input口分配
- }
这里分别将对输入和输出口进行Buffer的申请与分配,对于解码器,需要输入口来存储待解码的数据源,需要将解码后的数据源存储到输出口,而这也符合硬件的实现逻辑。以输入缓存区分配为例展开分析:
- status_t OMXCodec::allocateBuffersOnPort(OMX_U32 portIndex) {
- .......
- OMX_PARAM_PORTDEFINITIONTYPE def;
- InitOMXParams(&def);
- def.nPortIndex = portIndex;
-
- err = mOMX->getParameter(
- mNode, OMX_IndexParamPortDefinition, &def, sizeof(def));
- ..........
- err = mOMX->allocateBuffer(
- mNode, portIndex, def.nBufferSize, &buffer,
- &info.mData);
- ........
- info.mBuffer = buffer;
- info.mStatus = OWNED_BY_US;
- info.mMem = mem;
- info.mMediaBuffer = NULL;
- ...........
- mPortBuffers[portIndex].push(info);
上述过程主要分为:
step1:先是获取底层解码器组件的当前的参数熟悉,一般这些参数都在建立OMX_Codec时完成的初始配置,前一博文中已经提到过。
step2:进行allocateBuffer的处理,这个函数的调用最终交给底层的OMX组件来完成,相关的实现将集成到A31的底层OMX编解码组件的处理流中进行分析。
step3:完成对分配好的buffer信息info,维护在mPortBuffers[0]这个端口中。
上述过程完成了输入与输出的Buffer分配,为后续解码操作buffer打下了基础。
3.mediaplay启动播放器
通过start的API调用,进入MediaplayerService::Client,再依次经过stagefrightplayer,AwesomePlayer。触发play的videoevent的发生.
- void AwesomePlayer::postVideoEvent_l(int64_t delayUs) {
- ATRACE_CALL();
-
- if (mVideoEventPending) {
- return;
- }
-
- mVideoEventPending = true;
- mQueue.postEventWithDelay(mVideoEvent, delayUs < 0 ? 10000 : delayUs);
- }
根据前一博文的分析可知,该事件对应的处理函数为AwesomePlayer::onVideoEvent(),该部分代码量较大,提取核心内容read的处理进行分析:
- status_t err = mVideoSource->read(&mVideoBuffer, &options);//循环读数据实际的OMX_CODEC::read,读取到mVideoBuffer
read的核心是获取可以用于render的视频数据,这表明了read函数主要完成了从视频源读取元数据,并调用解码器完成解码生成可送显的数据。
4. read函数的实现
可以想象read函数的应该是一个比较复杂的过程,我们从OMX_Codec的read函数入手来分析:
- status_t OMXCodec::read(
- MediaBuffer **buffer, const ReadOptions *options) {
- status_t err = OK;
- *buffer = NULL;
-
- Mutex::Autolock autoLock(mLock);
-
- drainInputBuffers();//buffer,填充数据源
-
- if (mState == EXECUTING) {
- // Otherwise mState == RECONFIGURING and this code will trigger
- // after the output port is reenabled.
- fillOutputBuffers();
- }
- }
-
- ...........
- }
read的核心逻辑总结为drainInputBuffers()和fillOutputBuffers(),我们对其依次进行深入的分析
5. drainInputBuffers()读取待解码的视频数据源到解码器的Inport
这里贴出其较为复杂的处理过程代码,主要分为以下3个部分进行分析:
(1)
- bool OMXCodec::drainInputBuffer(BufferInfo *info) {<pre code_snippet_id="360665" snippet_file_name="blog_20140523_9_9848139" class="html" name="code"> if (mCodecSpecificDataIndex < mCodecSpecificData.size()) {
- CHECK(!(mFlags & kUseSecureInputBuffers));
-
- const CodecSpecificData *specific =
- mCodecSpecificData[mCodecSpecificDataIndex];
-
- size_t size = specific->mSize;
-
- if (!strcasecmp(MEDIA_MIMETYPE_VIDEO_AVC, mMIME)
- && !(mQuirks & kWantsNALFragments)) {
- static const uint8_t kNALStartCode[4] =
- { 0x00, 0x00, 0x00, 0x01 };
-
- CHECK(info->mSize >= specific->mSize + 4);
-
- size += 4;
-
- memcpy(info->mData, kNALStartCode, 4);
- memcpy((uint8_t *)info->mData + 4,
- specific->mData, specific->mSize);
- } else {
- CHECK(info->mSize >= specific->mSize);
- memcpy(info->mData, specific->mData, specific->mSize);
- }
-
- mNoMoreOutputData = false;
-
- CODEC_LOGV("calling emptyBuffer with codec specific data");
-
- status_t err = mOMX->emptyBuffer(
- mNode, info->mBuffer, 0, size,
- OMX_BUFFERFLAG_ENDOFFRAME | OMX_BUFFERFLAG_CODECCONFIG,
- 0);
- CHECK_EQ(err, (status_t)OK);
-
- info->mStatus = OWNED_BY_COMPONENT;
-
- ++mCodecSpecificDataIndex;
- return true;
- }</pre>...............(1)<br>
这部分的内容主要是提取一部分解码器字段,填充到info->mData的存储空间中去。这部分主要基于视频源的格式,如mp4等在创建OXMCodec病configureCodec时就完成了这个mCodecSpecificData字段的添加,应该些解码需要的特殊字段吧。是否需要要看其视频源的格式。获取完这个字段信息后就是正式读取视频源的数据了。
(2)
- for (;;) {
- MediaBuffer *srcBuffer;
- if (mSeekTimeUs >= 0) {
- if (mLeftOverBuffer) {
- mLeftOverBuffer->release();
- mLeftOverBuffer = NULL;
- }
-
- MediaSource::ReadOptions options;
- options.setSeekTo(mSeekTimeUs, mSeekMode);
-
- mSeekTimeUs = -1;
- mSeekMode = ReadOptions::SEEK_CLOSEST_SYNC;
- mBufferFilled.signal();
-
- err = mSource->read(&srcBuffer, &options);//读取视频源中的真实数据这里是MPEG4Source的read
-
- if (err == OK) {
- int64_t targetTimeUs;
- if (srcBuffer->meta_data()->findInt64(
- kKeyTargetTime, &targetTimeUs)
- && targetTimeUs >= 0) {
- CODEC_LOGV("targetTimeUs = %lld us", targetTimeUs);
- mTargetTimeUs = targetTimeUs;
- } else {
- mTargetTimeUs = -1;
- }
- }
- } else if (mLeftOverBuffer) {
- srcBuffer = mLeftOverBuffer;
- mLeftOverBuffer = NULL;
-
- err = OK;
- } else {
- err = mSource->read(&srcBuffer);
- }
-
- if (err != OK) {
- signalEOS = true;
- mFinalStatus = err;
- mSignalledEOS = true;
- mBufferFilled.signal();
- break;
- }
-
- if (mFlags & kUseSecureInputBuffers) {
- info = findInputBufferByDataPointer(srcBuffer->data());
- CHECK(info != NULL);
- }
-
- size_t remainingBytes = info->mSize - offset;//buffer中剩余的可以存储视频数据的空间
-
- if (srcBuffer->range_length() > remainingBytes) {//当前读取的数据已经达到解码的数据量
- if (offset == 0) {
- CODEC_LOGE(
- "Codec's input buffers are too small to accomodate "
- "buffer read from source (info->mSize = %d, srcLength = %d)",
- info->mSize, srcBuffer->range_length());
-
- srcBuffer->release();
- srcBuffer = NULL;
-
- setState(ERROR);
- return false;
- }
-
- mLeftOverBuffer = srcBuffer;//把没读取的buffer记录下来
- break;
- }
-
- bool releaseBuffer = true;
- if (mFlags & kStoreMetaDataInVideoBuffers) {
- releaseBuffer = false;
- info->mMediaBuffer = srcBuffer;
- }
-
- if (mFlags & kUseSecureInputBuffers) {
- // Data in "info" is already provided at this time.
-
- releaseBuffer = false;
-
- CHECK(info->mMediaBuffer == NULL);
- info->mMediaBuffer = srcBuffer;
- } else {
- CHECK(srcBuffer->data() != NULL) ;
- memcpy((uint8_t *)info->mData + offset,
- (const uint8_t *)srcBuffer->data()
- + srcBuffer->range_offset(),
- srcBuffer->range_length());//copy数据源数据到输入缓存,数据容量srcBuffer->range_length()
- }
-
- int64_t lastBufferTimeUs;
- CHECK(srcBuffer->meta_data()->findInt64(kKeyTime, &lastBufferTimeUs));
- CHECK(lastBufferTimeUs >= 0);
- if (mIsEncoder && mIsVideo) {
- mDecodingTimeList.push_back(lastBufferTimeUs);
- }
-
- if (offset == 0) {
- timestampUs = lastBufferTimeUs;
- }
-
- offset += srcBuffer->range_length();//增加偏移量
-
- if (!strcasecmp(MEDIA_MIMETYPE_AUDIO_VORBIS, mMIME)) {
- CHECK(!(mQuirks & kSupportsMultipleFramesPerInputBuffer));
- CHECK_GE(info->mSize, offset + sizeof(int32_t));
-
- int32_t numPageSamples;
- if (!srcBuffer->meta_data()->findInt32(
- kKeyValidSamples, &numPageSamples)) {
- numPageSamples = -1;
- }
-
- memcpy((uint8_t *)info->mData + offset,
- &numPageSamples,
- sizeof(numPageSamples));
-
- offset += sizeof(numPageSamples);
- }
-
- if (releaseBuffer) {
- srcBuffer->release();
- srcBuffer = NULL;
- }
-
- ++n;
-
- if (!(mQuirks & kSupportsMultipleFramesPerInputBuffer)) {
- break;
- }
-
- int64_t coalescedDurationUs = lastBufferTimeUs - timestampUs;
-
- if (coalescedDurationUs > 250000ll) {
- // Don't coalesce more than 250ms worth of encoded data at once.
- break;
- }
- }...........
该部分是提取视频源数据的关键,主要通过 err = mSource->read(&srcBuffer, &options)来完成,mSource是在创建编解码器传入的,实际是一个对应于视频源格式的一个解析器MediaExtractor。比如在建立MP4的解析器MPEG4Extractor,通过新建一个new MPEG4Source。故最终这里调用的是MPEG4Source的read成员函数,其实际也维护着整个待解码的原始视频流。
我们可以看大在read函数后,会将待解码的数据流以for循环依次读入到底层的buffer空间中,只有当满足当前读取的原始数据片段比底层的input口的buffer剩余空间小srcBuffer->range_length() > remainingBytes,那就可以继续读取,否则直接break后,去进行下一步操作。或者如果一次待解码的数据时张是大于250ms也直接跳出。
这处理体现了处理的高效性。最终视频原始数据存储在info->mData的底层输入空间中。
(3)
- err = mOMX->emptyBuffer(
- mNode, info->mBuffer, 0, offset,
- flags, timestampUs);
触发底层的解码器组件进行处理。这部分留在后续对A31的底层编解码API操作时进行分析。
6.fillOutputBuffers对输出buffer口的填充,即实现解码过程:
- void OMXCodec::fillOutputBuffers() {
- CHECK_EQ((int)mState, (int)EXECUTING);
- ...........
- Vector<BufferInfo> *buffers = &mPortBuffers[kPortIndexOutput];输出端口
- for (size_t i = 0; i < buffers->size(); ++i) {
- BufferInfo *info = &buffers->editItemAt(i);
- if (info->mStatus == OWNED_BY_US) {
- fillOutputBuffer(&buffers->editItemAt(i));
- }
- }
- }
- void OMXCodec::fillOutputBuffer(BufferInfo *info) {
- CHECK_EQ((int)info->mStatus, (int)OWNED_BY_US);
-
- if (mNoMoreOutputData) {
- CODEC_LOGV("There is no more output data available, not "
- "calling fillOutputBuffer");
- return;
- }
-
- CODEC_LOGV("Calling fillBuffer on buffer %p", info->mBuffer);
- status_t err = mOMX->fillBuffer(mNode, info->mBuffer);
-
- if (err != OK) {
- CODEC_LOGE("fillBuffer failed w/ error 0x%08x", err);
-
- setState(ERROR);
- return;
- }
-
- info->mStatus = OWNED_BY_COMPONENT;
- }
从上面的代码看来,fillOutputBuffer的实现比drainInputBuffers简单了很多。但相同的是,两者最终都讲控制权交给底层的解码器来完成。
7.等待解码数据被fill到outbuffer中,OMXCodecObserver完成回调处理
等待解码完成的这部分内容在read函数中通过以下函数来实现:
- while (mState != ERROR && !mNoMoreOutputData && mFilledBuffers.empty()) {
- if ((err = waitForBufferFilled_l()) != OK) {//进入等待buffer被填充
- return err;
- }
- }
上述表明,只要mFilledBuffers为空则进入等待填充pthread_cond_timedwait。而这个线程被唤醒是通过底层的组件回调来完成的,回调函数的注册哎底层编解码器Node完成的,实际最终的回调是交给OMXCodecObserver来完成的:
- struct OMXCodecObserver : public BnOMXObserver {
- OMXCodecObserver() {
- }
-
- void setCodec(const sp<OMXCodec> &target) {
- mTarget = target;
- }
-
- // from IOMXObserver
- virtual void onMessage(const omx_message &msg) {
- sp<OMXCodec> codec = mTarget.promote();
-
- if (codec.get() != NULL) {
- Mutex::Autolock autoLock(codec->mLock);
- codec->on_message(msg);//OMX_Codec的on_message处理
- codec.clear();
- }
- }
最终可以看到是由OMX_Codec->on_message来进行消息的处理,这部分的内容主要包括EMPTY_BUFFER_DONE和FILL_BUFFER_DONE两个message处理,对FILL_BUFFER_DONE完成后的消息回调进行分析:
- void OMXCodec::on_message(const omx_message &msg) {
- if (mState == ERROR) {
-
-
-
-
- if (msg.type == omx_message::EVENT) {
- ALOGW("Dropping OMX EVENT message - we're in ERROR state.");
- return;
- }
- }
-
- switch (msg.type) { case omx_message::FILL_BUFFER_DONE:
- mFilledBuffers.push_back(i);
- mBufferFilled.signal();
可以看到这里对read线程进行了唤醒。
8.提取一个可用的解码后的数据帧
- size_t index = *mFilledBuffers.begin();
- mFilledBuffers.erase(mFilledBuffers.begin());
-
- BufferInfo *info = &mPortBuffers[kPortIndexOutput].editItemAt(index);
- CHECK_EQ((int)info->mStatus, (int)OWNED_BY_US);
- info->mStatus = OWNED_BY_CLIENT;
-
- info->mMediaBuffer->add_ref();
- if (mSkipCutBuffer != NULL) {
- mSkipCutBuffer->submit(info->mMediaBuffer);
- }
- *buffer = info->mMediaBuffer;
获得了线程唤醒后的buffer,从这里获取到输出端口对应的Bufferinfo,作为最终的BufferInfo信息返回给read函数
9
经过5、6、7、8的处理过程,read最终返回可用于显示的mVideoBuffer,接下去就是如何送显的过程了。可以看到下面的代码,将会创建一个渲染器mVideoRenderer来完成这个解码后视频源的显示:
- <p> if ((mNativeWindow != NULL)
- && (mVideoRendererIsPreview || mVideoRenderer == NULL)) {//首次创建渲染器
- mVideoRendererIsPreview = false;</p><p> initRenderer_l();//初始化渲染器,新建一个AwesomeLocalRenderer
- }</p><p> if (mVideoRenderer != NULL) {
- mSinceLastDropped++;
- mVideoRenderer->render(mVideoBuffer);//启动渲染,即显示当前buffer
- if (!mVideoRenderingStarted) {
- mVideoRenderingStarted = true;
- notifyListener_l(MEDIA_INFO, MEDIA_INFO_RENDERING_START);
- }</p><p> }</p>
- void AwesomePlayer::initRenderer_l() {
- ATRACE_CALL();
-
- if (mNativeWindow == NULL) {
- return;
- }
-
- sp<MetaData> meta = mVideoSource->getFormat();
-
- int32_t format;
- const char *component;
- int32_t decodedWidth, decodedHeight;
- CHECK(meta->findInt32(kKeyColorFormat, &format));
- CHECK(meta->findCString(kKeyDecoderComponent, &component));
- CHECK(meta->findInt32(kKeyWidth, &decodedWidth));
- CHECK(meta->findInt32(kKeyHeight, &decodedHeight));
-
- int32_t rotationDegrees;
- if (!mVideoTrack->getFormat()->findInt32(
- kKeyRotation, &rotationDegrees)) {
- rotationDegrees = 0;
- }
-
- mVideoRenderer.clear();
-
- // Must ensure that mVideoRenderer's destructor is actually executed
- // before creating a new one.
- IPCThreadState::self()->flushCommands();
-
- // Even if set scaling mode fails, we will continue anyway
- setVideoScalingMode_l(mVideoScalingMode);
- if (USE_SURFACE_ALLOC
- && !strncmp(component, "OMX.", 4)
- && strncmp(component, "OMX.google.", 11)
- && strcmp(component, "OMX.Nvidia.mpeg2v.decode")) {//使用硬件渲染器,除去上述的解码器
- // Hardware decoders avoid the CPU color conversion by decoding
- // directly to ANativeBuffers, so we must use a renderer that
- // just pushes those buffers to the ANativeWindow.
- mVideoRenderer =
- new AwesomeNativeWindowRenderer(mNativeWindow, rotationDegrees);//一般是使用硬件渲染机制
- } else {
- // Other decoders are instantiated locally and as a consequence
- // allocate their buffers in local address space. This renderer
- // then performs a color conversion and copy to get the data
- // into the ANativeBuffer.
- mVideoRenderer = new AwesomeLocalRenderer(mNativeWindow, meta);
- }
- }
可以看到这里有2个渲染器的创建分支,OMX和OMX.google说明底层的解码器用的是软解码,那么他渲染器也使用所谓的本地渲染器实际是软渲染器。故这里我们使用的是AwesomeNativeWindowRenderer渲染器,其结构如下所述:
- struct AwesomeNativeWindowRenderer : public AwesomeRenderer {
- AwesomeNativeWindowRenderer(
- const sp<ANativeWindow> &nativeWindow,
- int32_t rotationDegrees)
- : mNativeWindow(nativeWindow) {
- applyRotation(rotationDegrees);
- }
-
- virtual void render(MediaBuffer *buffer) {
- ATRACE_CALL();
- int64_t timeUs;
- CHECK(buffer->meta_data()->findInt64(kKeyTime, &timeUs));
- native_window_set_buffers_timestamp(mNativeWindow.get(), timeUs * 1000);
- status_t err = mNativeWindow->queueBuffer(
- mNativeWindow.get(), buffer->graphicBuffer().get(), -1);//直接使用queuebuffer进行渲染显示
- if (err != 0) {
- ALOGE("queueBuffer failed with error %s (%d)", strerror(-err),
- -err);
- return;
- }
-
- sp<MetaData> metaData = buffer->meta_data();
- metaData->setInt32(kKeyRendered, 1);
- }
不是很复杂,只是实现了AwesomeRenderer渲染接口render。最终调用这个函数来实现对buffer的显示。这里看到很熟悉的queueBuffer,大家可以回看我的博文Android4.2.2 SurfaceFlinger之图形渲染queueBuffer实现和VSYNC的存在感 ,这是通过应用程序的本地窗口mNativeWindow(因为播放器videoview继承了sufaceview,surfaceview类会创建一个本地的surface,其继承了本地窗口类)将当前buffer提交给SurfaceFlinger服务进行显示,具体内容不在展开。
至此我们完成了stagefright下的编解码的数据流的相关操作,程序上复杂主要体现在emptybuffer和fillbuffer为主。当然由于能力有限,在很多细节上也没有进行很详细的分析,也希望大家多交流,多学习。