做百度推广首先要做网站吗,淘宝上的网站怎么做,高端品牌有哪些牌子,wordpress首页缩略图这一节我们将了解 NuPlayer::Decoder#xff0c;学习如何将 MediaCodec wrap 成一个强大的 Decoder。这一节会提前讲到 MediaCodec 相关的内容#xff0c;如果看不大懂可以先跳过此篇。原先觉得 Decoder 部分简单#xff0c;越读越发现自己的无知#xff0c;Android 源码真… 这一节我们将了解 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 spAMessage notify);void configure(const spAMessage format);void init();void setParameters(const spAMessage params);// Synchronous call to ensure decoder will not request or send out data.void pause();void setRenderer(const spRenderer renderer);virtual status_t setVideoSurface(const spSurface ) { return INVALID_OPERATION; }void signalFlush();void signalResume(bool notifyComplete);void initiateShutdown();virtual spAMessage getStats() {return mStats;}
protected:virtual void onMessageReceived(const spAMessage msg);virtual void onConfigure(const spAMessage format) 0;virtual void onSetParameters(const spAMessage params) 0;virtual void onSetRenderer(const spRenderer 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设定 renderdecoder 解出的数据将送到 render 中做 avsync如果是 audio 数据将直接写入到 AudioTracksetVideoSurface重新设定 surfaceaudio decoder 并不需要这个方法signalFlushflush刷新 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, spDecoderBase *decoder, bool checkAudioModeChange) {spAMessage 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() {....spAMessage msg new AMessage(kWhatRequestInputBuffers, this);msg-post(10 * 1000LL);
}继续往下看configure 最终会调用到 onConfigure 方法中
void NuPlayer::Decoder::onConfigure(const spAMessage 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 /* encoder */, NULL /* err */, 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 /* err */, mPid, mUid);}}err mCodec-configure(format, mSurface, crypto, 0 /* flags */);rememberCodecSpecificData(format);spAMessage reply new AMessage(kWhatCodecNotify, this);mCodec-setCallback(reply);err mCodec-start();
}查找 format 中的 mime必须要有这个调用 CreateByType 创建 MediaCodec 实例如果 format 中带有 secure 字段那么就调用 CreateByComponentName 创建 Secure Component调用 configure 配置 MediaCodec需要传入码流的 formatformat 中需要有什么我们后面再看这里传入的 surface 不为 NULL因为 NuPlayer 中有判断如果 surface 为 NULL 就不会为 Video 创建 Codec存储 format 中的 codec specific datacsd buffer这些 buffer 记录了码流的信息例如 h264h265码流中的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 spAMessage msg) {int32_t generation;CHECK(msg-findInt32(generation, generation));return generation ! mBufferGeneration;
}void NuPlayer::Decoder::onMessageReceived(const spAMessage msg) {switch (msg-what()) {case kWhatRenderBuffer:{if (!isStaleReply(msg)) {onRenderBuffer(msg);}break;}}
}video output buffer 在送入 Renderer 做完 avsync 后送回来做渲染时会判断当前的 mBufferGeneration 是否有发生变化这里这么做有什么用意呢
我的理解是这样output buffer 送到 Renderer 中处理完成后Renderer 会调用渲染相关的方法但是这个时候 buffer 的状态可能已经发生了变化例如做了 flush、或者是 shutdownbuffer 不需要再被处理用 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 为例子 spAMessage 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 中并且传递给 RendererRenderer 做完同步后会将消息重新发送到 Decoder。但是如果同步的过程中上层调用了 resetDecoder 也对事件做了处理那么 Decoder 将不能再去处理 Renderer 发过来的渲染消息整个流程已经停止component 已经被释放了。 generation 起着状态记录的作用当状态发生改变后依赖该状态的消息将会不再处理。它和一些具体的状态例如 MediaPlayer.cpp 中的状态使用方法类似但是 generation 的使用更为简单它不关注具体是什么状态只关注影响改变 Decoder 状态的方法是否被调用。
3、Start
我们引用上一小节中关于 start 的描述 开启整个数据读取、数据解码 以及 数据渲染流程start 不仅仅是启动了 MediaCodec还驱动了所有组件的运行一起来看看吧。
先来看关键的 MediaCodec Callback
void NuPlayer::Decoder::onMessageReceived(const spAMessage 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;}spMediaCodecBuffer 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 bufferif (!mCSDsToSubmit.isEmpty()) {spAMessage msg new AMessage();msg-setSize(buffer-ix, index);spABuffer 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()) {spAMessage 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 获取数据填充数据并送回 decoderonRequestInputBuffers();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 datadoRequestBuffersonRequestInputBuffers 的 内部实现onInputBufferFetched成功从Source 获取到数据填充到 input buffer 并返回给 MediaCodecfetchInputDataonInputBufferFetched 的内部实现
我们从 onRequestInputBuffers 看起这个方法实现在 DecoderBase 中权限为 protected
void NuPlayer::DecoderBase::onRequestInputBuffers() {// 判断是否处在 处理不连续码流 的状态if (mRequestInputBuffersPending) {return;}// doRequestBuffers() return true if we should request more data// 从 Source 请求数据如果失败返回 true发送一条延时消息retryif (doRequestBuffers()) {// retry 时不会继续处理 获取数据的调用mRequestInputBuffersPending true;spAMessage 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();spAMessage 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 bufferbreak;}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(spAMessage reply) {spABuffer 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;spAMessage newFormat mSource-getFormat(mIsAudio);// 如果是格式变化if (formatChange) {// 判断当前播放的码流格式是否支持无缝切换seamlessFormatChange supportsSeamlessFormatChange(newFormat);// treat seamless format change separatelyformatChange !seamlessFormatChange;}// For format or time change, return EOS to queue EOS input,// then wait for EOS on output.// 如果不支持无缝切换那么就要向 decoder 填充 eosif (formatChange /* not seamless */) {mFormatChangePending true;err ERROR_END_OF_STREAM;} else if (timeChange) {// 如果pts不连续那么就要向 decoder 填充 eos恢复播放后要 发送 csd bufferrememberCodecSpecificData(newFormat);mTimeChangePending true;err ERROR_END_OF_STREAM;} else if (seamlessFormatChange) {// reuse existing decoder and dont flush// 如果是无缝切换那么仍要发送 csd bufferrememberCodecSpecificData(newFormat);continue;} else {// This stream is unaffected by the discontinuityreturn -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 会直接返回并尝试 retryINFO_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 将不会读到任何数据。
如果是因为码流不连续发送了 eosinput 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) {spMediaCodecBuffer buffer;// 获取 output buffermCodec-getOutputBuffer(index, buffer);int64_t frameIndex;bool frameIndexFound buffer-meta()-findInt64(frameIndex, frameIndex);buffer-setRange(offset, size);// 设置 ptsbuffer-meta()-clear();buffer-meta()-setInt64(timeUs, timeUs);if (frameIndexFound) {buffer-meta()-setInt64(frameIndex, frameIndex);}// 判断 output buffer 是否到达 eosbool eos flags MediaCodec::BUFFER_FLAG_EOS;// we do not expect CODECCONFIG or SYNCFRAME for decoder// 创建 reply设置 generationavsync完成后 renderer 通过该消息 callback 回来spAMessage 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 做 avsyncmRenderer-queueBuffer(mIsAudio, buffer, reply);// 如果到达 eos并且不是因为码流中断调用queueEOSif (eos !isDiscontinuityPending()) {mRenderer-queueEOS(mIsAudio, ERROR_END_OF_STREAM);}}return true;
}获取 output buffer创建 reply设置 generationavsync 完成后 Renderer 通过该消息 callback 到 Decoder判断 output buffer flag 是否是 eos如果是则在 reply 中进行标记queue input buffer 时可能设有开始渲染的 ptsoutput buffer pts 小于该 pts 时直接 drop将 output buffer 和 reply message 一起送到 Renderer如果到达 eos且不是因为码流不连续还要给 Renderer 送一个 EOS
3.3、kWhatRenderBuffer
上一节我们讲到 Renderer 做完 avsync 后会以消息的形式 callback 给 Decoder case kWhatRenderBuffer:{if (!isStaleReply(msg)) {onRenderBuffer(msg);}break;}isStaleReply 我们在上面已经做过解释了这里不再赘述主要来看 onRenderBuffer
void NuPlayer::Decoder::onRenderBuffer(const spAMessage msg) {status_t err;int32_t render;size_t bufferIx;int32_t eos;size_t size;// 查找要渲染的output buffer indexCHECK(msg-findSize(buffer-ix, bufferIx));if (mCodec NULL) {err NO_INIT;} else if (msg-findInt32(render, render) render) { // 判断是否renderint64_t timestampNs;CHECK(msg-findInt64(timestampNs, timestampNs)); // 获取render时间err mCodec-renderOutputBufferAndRelease(bufferIx, timestampNs);} else {// 如果是 eos 或者 不render 则直接 dropif (!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 /* flushOnTimeChange */);}
}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 /* notifyComplete */);signalResume(false /* notifyComplete */);}}// Notify NuPlayer to either shutdown decoder, or rescan sourcesspAMessage msg mNotify-dup();msg-setInt32(what, kWhatInputDiscontinuity);msg-setInt32(formatChange, mFormatChangePending);msg-post();mFormatChangePending false;mTimeChangePending false;
}从注释中我们可以看到针对 format change 和 time change 处理方式是不一样的
format change暂停 buffer 处理流程等待重启 decodertime changeflush 然后调用 resume 恢复
format change 中提到一个暂停将 mPaused 置为 trueonMessageReceived 将不会再处理送来的 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 operatormBufferGeneration;}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 /* flushOnTimeChange */);}spAMessage 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 nownotifyResumeCompleteIfNecessary();if (mCodec ! NULL) {// 释放decodererr mCodec-release();// 释放 MediaCodecmCodec NULL;// 修改 generation 阻止渲染mBufferGeneration;if (mSurface ! NULL) {// reconnect to surface as MediaCodec disconnected from itstatus_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 listreleaseAndResetMediaBuffers();if (err ! OK) {ALOGE(failed to release [%s] (err%d), mComponentName.c_str(), err);handleError(err);// finish with posting kWhatShutdownCompleted.}if (notifyComplete) {spAMessage 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?spm1000.2115.3001.5343