这一节我们将了解 NuPlayer::Decoder,学习如何将 MediaCodec wrap 成一个强大的 Decoder。这一节会提前讲到 MediaCodec 相关的内容,如果看不大懂可以先跳过此篇。原先觉得 Decoder 部分简单,越读越发现自己的无知,Android 源码真是一个巨大的宝库!
ps:本文中大写的Decoder
指代的是 NuPlayer::Decoder,小写的decoder
指代 mediacodec 以及底层的真正的解码器。
1、DecoderBase
首先看 NuPlayer::Decoder 的基类 DecoderBase
:
struct NuPlayer::DecoderBase : public AHandler { explicit DecoderBase(const sp<AMessage> ¬ify); void configure(const sp<AMessage> &format); void init(); void setParameters(const sp<AMessage> ¶ms); // Synchronous call to ensure decoder will not request or send out data. void pause(); void setRenderer(const sp<Renderer> &renderer); virtual status_t setVideoSurface(const sp<Surface> &) { return INVALID_OPERATION; } void signalFlush(); void signalResume(bool notifyComplete); void initiateShutdown(); virtual sp<AMessage> getStats() { return mStats; }protected:virtual void onMessageReceived(const sp<AMessage> &msg); virtual void onConfigure(const sp<AMessage> &format) = 0; virtual void onSetParameters(const sp<AMessage> ¶ms) = 0; virtual void onSetRenderer(const sp<Renderer> &renderer) = 0; virtual void onResume(bool notifyComplete) = 0; virtual void onFlush() = 0; virtual void onShutdown(bool notifyComplete) = 0; void onRequestInputBuffers(); virtual bool doRequestBuffers() = 0;}
DecoderBase 定义了 NuPlayer 可以调用 Decoder 的所有接口,可以看到接口数量相当稀少,并没有 start、stop、reset、seek 等等方法,这时候可能就会有人有疑问了,我上层调用的这些接口为什么底层却没有了呢?其实在之前的文章中我们已经对部分接口做了解释,这里就不再赘述。
来了解下这些接口都是用来干什么、怎么用的:
构造函数
:传入一个 AMessage 对象,用于上抛事件于状态;configure
:传入 Source 在 prepare 过程中 parse 出的 format 信息,format 信息包括 mime type、surface、secure、width、height、crypto、csd 等等信息;创建 MediaCodec 实例,配置并启动;init
:将自身注册到 ALooper 当中;setParameters
:给 decoder 设定上层传下的参数;pause
:这个方法其实并没有用,后面详细了解它为什么没有用;setRenderer
:设定 render,decoder 解出的数据将送到 render 中做 avsync,如果是 audio 数据将直接写入到 AudioTrack;setVideoSurface
:重新设定 surface,audio decoder 并不需要这个方法;signalFlush
:flush,刷新 decoder 的 input/ouput 缓冲区;signalResume
:恢复 decoder 的解码流程;initiateShutdown
:停止解码流程并释放相关资源;getStats
:获取当前 Decoder 的状态,例如 format 信息,当前解出的帧数,丢弃的帧数等等信息。其他
:onConfigure 等等方法将由具体的 Decoder 来实现,如果 audio 不走 offload, audio / video decoder 会走相同的流程。
2、Decoder 创建与启动
从 NuPlayer 源码中我们可以知道,调用 start 方法后会创建 Decoder,这里的 Decoder
是继承于 DecoderBase 的,接着调用 Decoder.init 和 Decoder.configure,这里Decoder 就完成启动了:
status_t NuPlayer::instantiateDecoder( bool audio, sp<DecoderBase> *decoder, bool checkAudioModeChange) {sp<AMessage> format = mSource->getFormat(audio); *decoder = new Decoder( notify, mSource, mPID, mUID, mRenderer, mSurface, mCCDecoder); (*decoder)->init(); (*decoder)->configure(format);}
init 方法很简单,就做了把 AHandler 注册到 ALooper 中这一件事。在这里我要抛出2个问题, registerHandler 注册的 this
指代的是谁?
void NuPlayer::DecoderBase::init() { mDecoderLooper->registerHandler(this);}
后面 onRequestInputBuffers 中这个消息会先经 Decoder::onMessageReceived 处理还是 先经 DecoderBase::onMessageReceived 来处理呢?如果不太确定答案是什么可以搜索多态。
void NuPlayer::DecoderBase::onRequestInputBuffers() {.... sp<AMessage> msg = new AMessage(kWhatRequestInputBuffers, this); msg->post(10 * 1000LL);}
继续往下看,configure 最终会调用到 onConfigure 方法中:
void NuPlayer::Decoder::onConfigure(const sp<AMessage> &format) { ++mBufferGeneration; AString mime; CHECK(format->findString("mime", &mime)); mIsAudio = !strncasecmp("audio/", mime.c_str(), 6); mComponentName = mime; mComponentName.append(" decoder"); mCodec = MediaCodec::CreateByType( mCodecLooper, mime.c_str(), false , NULL , mPid, mUid, format); int32_t secure = 0; if (format->findInt32("secure", &secure) && secure != 0) { if (mCodec != NULL) { mCodec->getName(&mComponentName); mComponentName.append(".secure"); mCodec->release(); mCodec = MediaCodec::CreateByComponentName( mCodecLooper, mComponentName.c_str(), NULL , mPid, mUid); } } err = mCodec->configure( format, mSurface, crypto, 0 ); rememberCodecSpecificData(format); sp<AMessage> reply = new AMessage(kWhatCodecNotify, this); mCodec->setCallback(reply); err = mCodec->start();}
- 查找 format 中的 mime,必须要有这个;
- 调用 CreateByType 创建 MediaCodec 实例,如果 format 中带有 secure 字段,那么就调用 CreateByComponentName 创建 Secure Component;
- 调用 configure 配置 MediaCodec,需要传入码流的 format,format 中需要有什么我们后面再看,这里传入的 surface 不为 NULL,因为 NuPlayer 中有判断,如果 surface 为 NULL 就不会为 Video 创建 Codec;
- 存储 format 中的 codec specific data(csd buffer),这些 buffer 记录了码流的信息,例如 h264,h265码流中的sps pps 等信息,对于有些 decoder 一定需要传入该信息,而有些 decoder 可以自己从码流中 parse 出来这些信息,具体要看各家的 decoder 实现;
- 给 MediaCodec 注册 callback,让它以
异步
的方式工作; - 调用 start 方法启动 decoder,开启整个数据读取、数据解码 以及 数据渲染流程。
以下内容是个人拙见,讲的比较啰嗦,如不喜欢直接跳过就好。
除了这些,我们还要看一个成员 mBufferGeneration
,这个东西是干什么的呢?其实我们之前已经说过 Media 这边用了跟多 generation 的思想或者说是trick,那这里的 generation 是用来干什么的呢?
我们搜索代码中的 mBufferGeneration,发现它的值会在 onConfigure
、doFlush
、 onShutdown
、handleError
中做修改,这四个方法会有一个共同点,它们都会去操作 MediaCodec,改变 MediaCodec 状态,从而影响到 MediaCodec Buffer 的状态。
我们再看 mBufferGeneration 会在哪里使用:
bool NuPlayer::Decoder::isStaleReply(const sp<AMessage> &msg) { int32_t generation; CHECK(msg->findInt32("generation", &generation)); return generation != mBufferGeneration;}void NuPlayer::Decoder::onMessageReceived(const sp<AMessage> &msg) {switch (msg->what()) { case kWhatRenderBuffer: { if (!isStaleReply(msg)) { onRenderBuffer(msg); } break; } }}
video output buffer 在送入 Renderer 做完 avsync 后送回来做渲染时会判断当前的 mBufferGeneration 是否有发生变化,这里这么做有什么用意呢?
我的理解是这样:output buffer 送到 Renderer 中处理完成后,Renderer 会调用渲染相关的方法,但是这个时候 buffer 的状态可能已经发生了变化,例如做了 flush、或者是 shutdown,buffer 不需要再被处理,用 mBufferGeneration 来判断就可以跳过处理步骤。
将一件事情交由其他组件处理时,记录当前的generation ,当事件处理结束并=返回到当前组件时,根据当前的 generation 决定内容是否需要丢弃。Android 在 ACodec、NuPlayer::Source、Renderer 等实现中都用了 generation 技巧来处理状态转换时的事务。
以上是我看第一遍代码时对 generation 的理解,再次翻阅又有了些新的感悟:
NuPlayer 中大量使用了 ALooper、AHandler 异步消息机制,这里的异步是相对与调用者而言的,譬如 NuPlayer 调用 Decoder 的 configure 方法,NuPlayer 调用完就结束了,这时候 Decoder 内的 MediaCodec 对象可能还没有创建,这就是异步。但是对于 Decoder 来说,所有的调用(发送来的消息)都是由 Looper 中的线程一条一条处理,所以 Decoder 内部是同步处理的。
为什么要说这些呢?我们来看看都有谁会给 Decoder 发送消息:NuPlayer、Renderer、MediaCodec 它们都可能同时,或者先后向 Decoder 发送消息,这会引发什么问题呢?以 Renderer 为例子:
sp<AMessage> reply = new AMessage(kWhatRenderBuffer, this); reply->setSize("buffer-ix", index); reply->setInt32("generation", mBufferGeneration); reply->setSize("size", size);
Decoder 收到 MediaCodec 发送的 CB_OUTPUT_AVAILABLE 事件后,会将 mBufferGeneration 存到 msg 中,并且传递给 Renderer,Renderer 做完同步后会将消息重新发送到 Decoder。但是如果同步的过程中,上层调用了 reset,Decoder 也对事件做了处理,那么 Decoder 将不能再去处理 Renderer 发过来的渲染消息(整个流程已经停止,component 已经被释放了)。
generation 起着状态记录的作用,当状态发生改变后,依赖该状态的消息将会不再处理。它和一些具体的状态,例如 MediaPlayer.cpp 中的状态使用方法类似,但是 generation 的使用更为简单,它不关注具体是什么状态,只关注影响改变 Decoder 状态的方法是否被调用。
3、Start
我们引用上一小节中关于 start 的描述: 开启整个数据读取、数据解码 以及 数据渲染流程
,start 不仅仅是启动了 MediaCodec,还驱动了所有组件的运行,一起来看看吧。
先来看关键的 MediaCodec Callback:
void NuPlayer::Decoder::onMessageReceived(const sp<AMessage> &msg) { switch (msg->what()) { case kWhatCodecNotify: { int32_t cbID; CHECK(msg->findInt32("callbackID", &cbID)); switch (cbID) { case MediaCodec::CB_INPUT_AVAILABLE: { int32_t index; CHECK(msg->findInt32("index", &index)); handleAnInputBuffer(index); break; } case MediaCodec::CB_OUTPUT_AVAILABLE: { int32_t index; size_t offset; size_t size; int64_t timeUs; int32_t flags; CHECK(msg->findInt32("index", &index)); CHECK(msg->findSize("offset", &offset)); CHECK(msg->findSize("size", &size)); CHECK(msg->findInt64("timeUs", &timeUs)); CHECK(msg->findInt32("flags", &flags)); handleAnOutputBuffer(index, offset, size, timeUs, flags); break; } } } }}
MediaCodec 通过 kWhatCodecNotify
将消息发送到 Decoder 来处理,用 callbackID 来区分发送过来的内容,常用的有 CB_INPUT_AVAILABLE
、CB_OUTPUT_AVAILABLE
、CB_ERROR
、CB_OUTPUT_FORMAT_CHANGED
这四个,我们这里只看 input 和 output。
3.1、CB_INPUT_AVAILABLE
收到 MediaCodec 上抛的 input 事件后,会调用 handleAnInputBuffer 方法,传入参数为 input buffer id。
bool NuPlayer::Decoder::handleAnInputBuffer(size_t index) {// 判断是否处在 处理不连续码流 的状态 if (isDiscontinuityPending()) { return false; } sp<MediaCodecBuffer> buffer; mCodec->getInputBuffer(index, &buffer); if (index >= mInputBuffers.size()) { for (size_t i = mInputBuffers.size(); i <= index; ++i) { mInputBuffers.add(); mInputBufferIsDequeued.add(); mMediaBuffers.editItemAt(i) = NULL; mInputBufferIsDequeued.editItemAt(i) = false; } } mInputBuffers.editItemAt(index) = buffer; mInputBufferIsDequeued.editItemAt(index) = true;// 如果有码流不连续的情况,恢复播放后重新发送csd buffer if (!mCSDsToSubmit.isEmpty()) { sp<AMessage> msg = new AMessage(); msg->setSize("buffer-ix", index); sp<ABuffer> buffer = mCSDsToSubmit.itemAt(0); msg->setBuffer("buffer", buffer); mCSDsToSubmit.removeAt(0); if (!onInputBufferFetched(msg)) { handleError(UNKNOWN_ERROR); return false; } return true; }// 如果有 buffer 没有成功写入 mediacodec 的情况,尝试重新写入 while (!mPendingInputMessages.empty()) { sp<AMessage> msg = *mPendingInputMessages.begin(); if (!onInputBufferFetched(msg)) { break; } mPendingInputMessages.erase(mPendingInputMessages.begin()); }// 如果在 尝试重新写入的过程中,把当前 buffer 也顺带处理了,那么就直接返回 if (!mInputBufferIsDequeued.editItemAt(index)) { return true; }// 将 buffer 记录到 mDequeuedInputBuffers 中 mDequeuedInputBuffers.push_back(index);// 尝试从 source 获取数据,填充数据,并送回 decoder onRequestInputBuffers(); return true;}
由于这里涉及到 Source 数据的获取 和 input buffer 写入两部分内容,同时考虑了数据获取失败和数据写入失败的问题,所以 input buffer 的处理流程看起来会比较复杂,不过我们不要着急,我们一步步解析。
首先来看 handleAnInputBuffer 做的事情(省略了部分注释的内容):
- 判断当前是否正在处理码流不连续的情况;
- 从 MediaCodec 获取对应索引的
MediaCodecBuffer
; - 将获取的到的 MediaCodecBuffer 按照索引记录到 mInputBuffers 列表当中;
- 创建一个列表
mInputBufferIsDequeued
,记录索引对应的 input buffer 是否有出队列; - 每次进行了 flush,需要送 csd buffer 给 decoder,记住是每次!其实不是所有的 decoder flush 之后都要 csd buffer 的,之前被坑过!
- 先处理被延迟的 input buffer,为什么延迟后面再说;
- 处理延迟 buffer 时可能会把当前的 input buffer 处理掉,如果记录出队列的列表中对应位置为 false 说明已经被处理过了;
- 如果 input buffer 没有处理,那么再把它加入到一个没有处理的列表当中
mDequeuedInputBuffers
; - 调用 onRequestInputBuffers 从 Source 读取数据;
这里涉及四个方法,名字长得比较像,先来介绍下它们是做什么用的:
- onRequestInputBuffers:从 Source 请求 input data;
- doRequestBuffers:onRequestInputBuffers 的 内部实现;
- onInputBufferFetched:成功从Source 获取到数据,填充到 input buffer 并返回给 MediaCodec;
- fetchInputData:onInputBufferFetched 的内部实现;
我们从 onRequestInputBuffers 看起,这个方法实现在 DecoderBase 中,权限为 protected:
void NuPlayer::DecoderBase::onRequestInputBuffers() {// 判断是否处在 处理不连续码流 的状态 if (mRequestInputBuffersPending) { return; } // doRequestBuffers() return true if we should request more data // 从 Source 请求数据,如果失败返回 true,发送一条延时消息,retry if (doRequestBuffers()) { // retry 时不会继续处理 获取数据的调用 mRequestInputBuffersPending = true; sp<AMessage> msg = new AMessage(kWhatRequestInputBuffers, this); msg->post(10 * 1000LL); }}
可以看到 onRequestInputBuffers 就是封装了 doRequestBuffers,所以它们的作用是相同的,但是一个是基类的方法,一个是子类的方法。这么设计有什么用呢?我的理解是这样:每个 Decoder 都需要从 Source 获取数据,所以把获取数据的方法 onRequestInputBuffers 定义在基类当中,但是每个 Decoder 获取数据的方式或者流程又不一样,所以把 doRequestBuffers 放到子类中实现。子类调用父类的 onRequestInputBuffers 方法使用父类定义的数据读取流程,流程中调用子类的数据读取实现,这样就一举两得,既统一了读取流程又区分了读取方式。
另外这里还有 mRequestInputBuffersPending
的用法值得学习,如果从 Source 获取数据失败了,那么需要做延时等待,并且重新尝试获取,等待的过程中我们并不希望外部能够再调用到 doRequestBuffers 获取数据,所以将 mRequestInputBuffersPending 置为 true,表示等待的状态,这个状态只有处理 retry 消息时才能够解除。
bool NuPlayer::Decoder::doRequestBuffers() { if (isDiscontinuityPending()) { return false; } status_t err = OK; while (err == OK && !mDequeuedInputBuffers.empty()) { size_t bufferIx = *mDequeuedInputBuffers.begin(); sp<AMessage> msg = new AMessage(); msg->setSize("buffer-ix", bufferIx); err = fetchInputData(msg); if (err != OK && err != ERROR_END_OF_STREAM) { // if EOS, need to queue EOS buffer break; } mDequeuedInputBuffers.erase(mDequeuedInputBuffers.begin()); if (!mPendingInputMessages.empty() || !onInputBufferFetched(msg)) { mPendingInputMessages.push_back(msg); } } return err == -EWOULDBLOCK && mSource->feedMoreTSData() == OK;}
doRequestBuffers 里有个循环,会把当前 mDequeuedInputBuffers
中的所有 input buffer 都处理掉。这里有个问题,什么时候 mDequeuedInputBuffers 中 buffer 的数量会大于 1 呢?从 source 读取数据失败时会直接返回,没能调用 erase 方法,这时候 mDequeuedInputBuffers 的数量会大于1。
status_t NuPlayer::Decoder::fetchInputData(sp<AMessage> &reply) { sp<ABuffer> accessUnit; bool dropAccessUnit = true; do { // 从 Source 获取数据 status_t err = mSource->dequeueAccessUnit(mIsAudio, &accessUnit);// 判断返回值,如果是 EWOULDBLOCK 那么说明没读到数据,如果是其他的返回值则说名读到数据 if (err == -EWOULDBLOCK) { return err; } else if (err != OK) { // 如果 error 不等于 OK,说明码流出现了一些情况 if (err == INFO_DISCONTINUITY) { int32_t type; // 获取码流不连续的原因 CHECK(accessUnit->meta()->findInt32("discontinuity", &type)); bool formatChange = (mIsAudio && (type & ATSParser::DISCONTINUITY_AUDIO_FORMAT)) || (!mIsAudio &&(type & ATSParser::DISCONTINUITY_VIDEO_FORMAT)); bool timeChange = (type & ATSParser::DISCONTINUITY_TIME) != 0; ALOGI("%s discontinuity (format=%d, time=%d)", mIsAudio ? "audio" : "video", formatChange, timeChange); bool seamlessFormatChange = false; sp<AMessage> newFormat = mSource->getFormat(mIsAudio); // 如果是格式变化 if (formatChange) { // 判断当前播放的码流格式是否支持无缝切换 seamlessFormatChange = supportsSeamlessFormatChange(newFormat); // treat seamless format change separately formatChange = !seamlessFormatChange; } // For format or time change, return EOS to queue EOS input, // then wait for EOS on output. // 如果不支持无缝切换,那么就要向 decoder 填充 eos if (formatChange ) { mFormatChangePending = true; err = ERROR_END_OF_STREAM; } else if (timeChange) { // 如果pts不连续,那么就要向 decoder 填充 eos,恢复播放后要 发送 csd buffer rememberCodecSpecificData(newFormat); mTimeChangePending = true; err = ERROR_END_OF_STREAM; } else if (seamlessFormatChange) { // reuse existing decoder and don't flush // 如果是无缝切换,那么仍要发送 csd buffer rememberCodecSpecificData(newFormat); continue; } else { // This stream is unaffected by the discontinuity return -EWOULDBLOCK; } } // reply should only be returned without a buffer set // when there is an error (including EOS) CHECK(err != OK); reply->setInt32("err", err); return ERROR_END_OF_STREAM; }// 以下是 drop 机制 dropAccessUnit = false; if (!mIsAudio && !mIsEncrypted) {// 如果视频流慢了 100ms,视频为avc,并且不是参考帧,那么就drop掉当前读取的内容 if (mRenderer->getVideoLateByUs() > 100000LL && mIsVideoAVC && !IsAVCReferenceFrame(accessUnit)) { dropAccessUnit = true; } if (dropAccessUnit) { ++mNumInputFramesDropped; } } } while (dropAccessUnit); reply->setBuffer("buffer", accessUnit); return OK;}
fetchInputData 不仅仅是获取了数据,还对码流的异常情况做了处理。dequeueAccessUnit 有 四种返回值:
- OK:获取到有效数据;
- -EWOULDBLOCK:未能读取到数据;
- INFO_DISCONTINUITY:码流不连续;
- ERROR_END_OF_STREAM:读到文件末尾;
返回值为 OK 和 ERROR_END_OF_STREAM 属于正常情况;-EWOULDBLOCK 会直接返回并尝试 retry;INFO_DISCONTINUITY 说明码流出现了不连续的情况,可能是调用了 selectTrack 或者是 seek。
码流不连续分为两种情况:
- 码流的格式发生变化,error 为
DISCONTINUITY_VIDEO_FORMAT
,格式变化分别宽高变化和mime type变化两种,可能出现在 selectTrack 调用之后; - 码流的pts不连续,error 为
DISCONTINUITY_TIME
,可能出现在 seek 调用后,或者是码流播放结束 pts 回跳时。
一是码流的格式发生变化 DISCONTINUITY_VIDEO_FORMAT
引发的 flush,可能出现在 selectTrack 时;另外一种是码流 pts 回绕 DISCONTINUITY_TIME
,这种情况出现在直播的回播中比较多。
如果是格式发生变化,那么会判断当前播放的码流是否支持 无缝切换(adaptive-playback)
,如果支持则不对该事件做处理,如果不支持把返回值设置为 ERROR_END_OF_STREAM。
如果是 pts不连续 则会直接将返回值设置为 ERROR_END_OF_STREAM。由于设置了 ERROR_END_OF_STREAM,那么重新开始播放之后需要先填充 csd buffer。
fetchInputData 还为 AVC 格式的码流设计了一套 drop 机制,如果视频流慢于音频100ms,并且当前帧不是参考帧,那么就 drop 掉该帧。
fetchInputData 调用成功后就该调用 onInputBufferFetched,把获取到的数据填充到 input buffer 中并且送回到 MediaCodec,这里比较简单,就是做了数据拷贝而已,要看的只有 EOS 一点。EOS 有两种情况,一种是 buffer 为空,说明当前已经收到 ERROR_END_OF_STREAM;另一种是 buffer 不为空,返回值为 OK,但是 bufferMeta 中有 eos 信息。
如果是码流结束,eos 信息送出后 fetchInputData 将不会读到任何数据。
如果是因为码流不连续发送了 eos,input buffer 处理流程将会被 isDiscontinuityPending 中断,等到前面的数据都解码渲染完成,再处理 Discontinuity 事件,处理完成后才会写入下一个序列的数据,这部分我们放到下一小节来看。
bool NuPlayer::Decoder::isDiscontinuityPending() const { return mFormatChangePending || mTimeChangePending;}
3.2、CB_OUTPUT_AVAILABLE
output buffer 的处理流程相对 input 来说会简单很多,主要是调用了 handleAnOutputBuffer 方法:
bool NuPlayer::Decoder::handleAnOutputBuffer( size_t index, size_t offset, size_t size, int64_t timeUs, int32_t flags) { sp<MediaCodecBuffer> buffer; // 获取 output buffer mCodec->getOutputBuffer(index, &buffer); int64_t frameIndex; bool frameIndexFound = buffer->meta()->findInt64("frameIndex", &frameIndex); buffer->setRange(offset, size); // 设置 pts buffer->meta()->clear(); buffer->meta()->setInt64("timeUs", timeUs); if (frameIndexFound) { buffer->meta()->setInt64("frameIndex", frameIndex); }// 判断 output buffer 是否到达 eos bool eos = flags & MediaCodec::BUFFER_FLAG_EOS; // we do not expect CODECCONFIG or SYNCFRAME for decoder// 创建 reply,设置 generation,avsync完成后 renderer 通过该消息 callback 回来 sp<AMessage> reply = new AMessage(kWhatRenderBuffer, this); reply->setSize("buffer-ix", index); reply->setInt32("generation", mBufferGeneration); reply->setSize("size", size);// 如果出现 eos 则在 reply 中也进行标记 if (eos) { ALOGV("[%s] saw output EOS", mIsAudio ? "audio" : "video"); buffer->meta()->setInt32("eos", true); reply->setInt32("eos", true); } mNumFramesTotal += !mIsAudio;// 判断 input buffer 有没有设定起播时间 if (mSkipRenderingUntilMediaTimeUs >= 0) { if (timeUs < mSkipRenderingUntilMediaTimeUs) { ALOGV("[%s] dropping buffer at time %lld as requested.", mComponentName.c_str(), (long long)timeUs); reply->post(); if (eos) { notifyResumeCompleteIfNecessary(); if (mRenderer != NULL && !isDiscontinuityPending()) { mRenderer->queueEOS(mIsAudio, ERROR_END_OF_STREAM); } } return true; } mSkipRenderingUntilMediaTimeUs = -1; } // wait until 1st frame comes out to signal resume complete // 播放停止后重新恢复播放,等待第一帧到达后上抛消息,在seek时用到 notifyResumeCompleteIfNecessary(); if (mRenderer != NULL) { // send the buffer to renderer. // 将 ouput buffer 送到 renderer 做 avsync mRenderer->queueBuffer(mIsAudio, buffer, reply); // 如果到达 eos,并且不是因为码流中断,调用queueEOS if (eos && !isDiscontinuityPending()) { mRenderer->queueEOS(mIsAudio, ERROR_END_OF_STREAM); } } return true;}
- 获取 output buffer;
- 创建 reply,设置 generation,avsync 完成后 Renderer 通过该消息 callback 到 Decoder;
- 判断 output buffer flag 是否是 eos,如果是则在 reply 中进行标记;
- queue input buffer 时可能设有开始渲染的 pts,output buffer pts 小于该 pts 时直接 drop;
- 将 output buffer 和 reply message 一起送到 Renderer,如果到达 eos,且不是因为码流不连续,还要给 Renderer 送一个 EOS;
3.3、CB_OUTPUT_FORMAT_CHANGED
虽然我们使用 decoder 时都会传 input format 下去,但是 decoder 收到数据后仍会自己解析格式,并且上抛 output format change 事件,上层收到事件后需要做对应的处理。
void NuPlayer::Decoder::handleOutputFormatChange(const sp<AMessage> &format) { if (!mIsAudio) { int32_t width, height; if (format->findInt32("width", &width) && format->findInt32("height", &height)) { Mutex::Autolock autolock(mStatsLock); mStats->setInt32("width", width); mStats->setInt32("height", height); } sp<AMessage> notify = mNotify->dup(); notify->setInt32("what", kWhatVideoSizeChanged); notify->setMessage("format", format); notify->post(); } else if (mRenderer != NULL) { uint32_t flags; int64_t durationUs; bool hasVideo = (mSource->getFormat(false ) != NULL); if (getAudioDeepBufferSetting() // override regardless of source duration || (mSource->getDuration(&durationUs) == OK && durationUs > AUDIO_SINK_MIN_DEEP_BUFFER_DURATION_US)) { flags = AUDIO_OUTPUT_FLAG_DEEP_BUFFER; } else { flags = AUDIO_OUTPUT_FLAG_NONE; } sp<AMessage> reply = new AMessage(kWhatAudioOutputFormatChanged, this); reply->setInt32("generation", mBufferGeneration); mRenderer->changeAudioFormat( format, false , hasVideo, flags, mSource->isStreaming(), reply); }}
如果是 Video Format 发生改变,继续上抛事件即可。如果是 Audio Format 发生改变,Decoder 需要调用 Renderer.changeAudioFormat 来重新打开 AudioTrack,具体如何处理在 Renderer 篇中会简单介绍。
3.4、kWhatRenderBuffer
上一节我们讲到 Renderer 做完 avsync 后会以消息的形式 callback 给 Decoder:
case kWhatRenderBuffer: { if (!isStaleReply(msg)) { onRenderBuffer(msg); } break; }
isStaleReply 我们在上面已经做过解释了,这里不再赘述,主要来看 onRenderBuffer:
void NuPlayer::Decoder::onRenderBuffer(const sp<AMessage> &msg) { status_t err; int32_t render; size_t bufferIx; int32_t eos; size_t size; // 查找要渲染的output buffer index CHECK(msg->findSize("buffer-ix", &bufferIx)); if (mCodec == NULL) { err = NO_INIT; } else if (msg->findInt32("render", &render) && render) { // 判断是否render int64_t timestampNs; CHECK(msg->findInt64("timestampNs", ×tampNs));// 获取render时间 err = mCodec->renderOutputBufferAndRelease(bufferIx, timestampNs); } else { // 如果是 eos 或者 不render 则直接 drop if (!msg->findInt32("eos", &eos) || !eos || !msg->findSize("size", &size) || size) { mNumOutputFramesDropped += !mIsAudio; } err = mCodec->releaseOutputBuffer(bufferIx); }// 如果是因为码流不连续造成的eos,则处理不连续事件 if (msg->findInt32("eos", &eos) && eos && isDiscontinuityPending()) { finishHandleDiscontinuity(true ); }}
onRenderBuffer 主要是用来处理 Video 的,Renderer 确定该帧要渲染,那么就调用 renderOutputBufferAndRelease,否则调用 releaseOutputBuffer。
如果 reply message 中包含有 eos,那么会判断是否因为码流不连续而造成的 eos。我们要注意的是,Renderer 真正执行到 EOS 时,事件并不会发送到 Decoder中,Decoder 只处理 buffer 事件。
这里我们回过头来看 finishHandleDiscontinuity 是如何处理码流异常的:
void NuPlayer::Decoder::finishHandleDiscontinuity(bool flushOnTimeChange) { ALOGV("finishHandleDiscontinuity: format %d, time %d, flush %d", mFormatChangePending, mTimeChangePending, flushOnTimeChange); // If we have format change, pause and wait to be killed; // If we have time change only, flush and restart fetching. if (mFormatChangePending) { mPaused = true; } else if (mTimeChangePending) { if (flushOnTimeChange) { doFlush(false ); signalResume(false ); } } // Notify NuPlayer to either shutdown decoder, or rescan sources sp<AMessage> msg = mNotify->dup(); msg->setInt32("what", kWhatInputDiscontinuity); msg->setInt32("formatChange", mFormatChangePending); msg->post(); mFormatChangePending = false; mTimeChangePending = false;}
从注释中我们可以看到针对 format change 和 time change 处理方式是不一样的:
- format change:暂停 buffer 处理流程,等待重启 decoder;
- time change:flush 然后调用 resume 恢复;
format change 中提到一个暂停,将 mPaused 置为 true,onMessageReceived 将不会再处理送来的 buffer,要注意的是,这个 pause 并不是用于播放暂停。format change 的事件要送到 NuPlayer 中:
if (what == DecoderBase::kWhatInputDiscontinuity) { int32_t formatChange; CHECK(msg->findInt32("formatChange", &formatChange)); ALOGV("%s discontinuity: formatChange %d", audio ? "audio" : "video", formatChange); if (formatChange) { mDeferredActions.push_back(new FlushDecoderAction( audio ? FLUSH_CMD_SHUTDOWN : FLUSH_CMD_NONE, audio ? FLUSH_CMD_NONE : FLUSH_CMD_SHUTDOWN)); } mDeferredActions.push_back( new SimpleAction( &NuPlayer::performScanSources)); processDeferredActions(); }
NuPlayer 会执行 FlushDecoderAction,并且进行 shutdown 释放当前 decoder,然后再重新调用 performScanSources 为新的 format 创建 decoder。
4、signalFlush
void NuPlayer::Decoder::doFlush(bool notifyComplete) { if (mCCDecoder != NULL) { mCCDecoder->flush(); } if (mRenderer != NULL) { mRenderer->flush(mIsAudio, notifyComplete); mRenderer->signalTimeDiscontinuity(); } status_t err = OK; if (mCodec != NULL) { err = mCodec->flush(); mCSDsToSubmit = mCSDsForCurrentFormat; // copy operator ++mBufferGeneration; } if (err != OK) { ALOGE("failed to flush [%s] (err=%d)", mComponentName.c_str(), err); handleError(err); // finish with posting kWhatFlushCompleted. // we attempt to release the buffers even if flush fails. } releaseAndResetMediaBuffers(); mPaused = true;}void NuPlayer::Decoder::onFlush() { doFlush(true); if (isDiscontinuityPending()) { // This could happen if the client starts seeking/shutdown // after we queued an EOS for discontinuities. // We can consider discontinuity handled. finishHandleDiscontinuity(false ); } sp<AMessage> notify = mNotify->dup(); notify->setInt32("what", kWhatFlushCompleted); notify->post();}
flush 比较简单,就不多说废话啦。主要工作是调用 Renderer 的 flush,重置 Renderer 的状态,调用 MediaCodec 的 flush,刷新 input buffer 和 output buffer 缓冲区,注意这个方法调用会修改 mBufferGeneration,最后将 Decoder 存储的 buffer 列表都清空。
我们在上一节看到 finishHandleDiscontinuity 中调用的是 doFlush,所以是不会有 kWhatFlushCompleted 事件发送到 NuPlayer 的。
5、initiateShutdown
void NuPlayer::Decoder::onShutdown(bool notifyComplete) { status_t err = OK; // if there is a pending resume request, notify complete now notifyResumeCompleteIfNecessary(); if (mCodec != NULL) { // 释放decoder err = mCodec->release(); // 释放 MediaCodec mCodec = NULL; // 修改 generation 阻止渲染 ++mBufferGeneration; if (mSurface != NULL) { // reconnect to surface as MediaCodec disconnected from it status_t error = nativeWindowConnect(mSurface.get(), "onShutdown"); ALOGW_IF(error != NO_ERROR, "[%s] failed to connect to native window, error=%d", mComponentName.c_str(), error); } mComponentName = "decoder"; }// 释放 buffer list releaseAndResetMediaBuffers(); if (err != OK) { ALOGE("failed to release [%s] (err=%d)", mComponentName.c_str(), err); handleError(err); // finish with posting kWhatShutdownCompleted. } if (notifyComplete) { sp<AMessage> notify = mNotify->dup(); notify->setInt32("what", kWhatShutdownCompleted); notify->post(); // 停止处理 buffer 事件 mPaused = true; }}
shutdown 也很简单:
- 调用 MediaCodec 的 release 方法,释放 decoder;
- 释放掉 MediaCodec 对象;
- 修改 generation,停止处理 render 事件;
- 释放 buffer list;
- 将 mPaused 置为 true,停止处理 buffer 事件;
6、signalResume
void NuPlayer::Decoder::onResume(bool notifyComplete) { mPaused = false; if (notifyComplete) { mResumePending = true; } if (mCodec == NULL) { ALOGE("[%s] onResume without a valid codec", mComponentName.c_str()); handleError(NO_INIT); return; } mCodec->start();}
flush 之后要调用 signalResume 才能启动 MediaCodec 恢复解码流程,核心就是调用 MediaCodec 的 start 方法。这里的 mResumePending 是在 decoder 送过来第一帧 ouput buffer 时来判断是否需要发送 kWhatResumeCompleted 给 NuPlayer 的。
7、总结
没有其他的,感悟是 Android ALooper 机制领悟的还不够深刻,设计模式也不会,接下来会继续加强这部分的学习!
以上内容如果有错误请不要吝啬指导。
如果觉得对您有帮助,还请不要吝啬点赞、收藏与关注哦,您的支持是我更新的最大动力。
如需阅读其他 Android Media 框架内容,还请移步 https://blog.csdn.net/qq_41828351?spm=1000.2115.3001.5343
来源地址:https://blog.csdn.net/qq_41828351/article/details/132589796