接下来将会从4个角度来记录NuPlayerDecoder部分

相关代码路径:

http://aospxref.com/android-12.0.0_r3/xref/frameworks/av/media/libmediaplayerservice/nuplayer/NuPlayer.cpp

http://aospxref.com/android-12.0.0_r3/xref/frameworks/av/media/libmediaplayerservice/nuplayer/NuPlayerDecoderBase.cpp

http://aospxref.com/android-12.0.0_r3/xref/frameworks/av/media/libmediaplayerservice/nuplayer/NuPlayerDecoder.cpp

1、NuPlayerDecoder封装的是mediacodec,如何创建并且配置启动mediacodec的呢?

a. setCallback构建了NuPlayerDecoder和mediacodec之间沟通的桥梁,当omx有消息发上来,就会通过这个callback通知到NuPlayerDecoder,做相关的动作。

b. rememberCodecSpecificData 会记录下文件中的csd-buffer,等到有seek动作时会重新把这个buffer送给decoder

c. 其他的就是比较普通的对mediacodec的操作流程了 : CreateByType   -->  configure   -->   start

2、mediacodec启动之后,需要解码的数据从哪里获得,数据又是如何写入的,如何驱动这些动作呢?

上图是mediacodec收到omx发来的FillThisBuffer消息之后NuPlayerDecoder做出的相应动作

简单点讲就是拿到buffer,问source获取数据,把数据写给mediacodec,但是实际这部分的代码考虑的情况比较多,造成看起来比较复杂,这里贴一点代码来说明一下:

bool NuPlayer::Decoder::handleAnInputBuffer(size_t index) {
// ......
// 从mediacodec获取input buffer
sp<MediaCodecBuffer> buffer;
mCodec->getInputBuffer(index, &buffer); // ......
// 这里有几个容器要看看他们的用法
// mInputBuffers : 记录获取到的inputbuffer
// mMediaBuffers : 没什么用
// mInputBufferIsDequeued : 用于记录mInputBuffers容器中的buffer是否出队列,buffer送上来时置true,buffer送给decoder时置false
if (index >= mInputBuffers.size()) {
for (size_t i = mInputBuffers.size(); i <= index; ++i) {
mInputBuffers.add();
mMediaBuffers.add();
mInputBufferIsDequeued.add();
mMediaBuffers.editItemAt(i) = NULL;
mInputBufferIsDequeued.editItemAt(i) = false;
}
}
mInputBuffers.editItemAt(index) = buffer;
// ......
mInputBufferIsDequeued.editItemAt(index) = true; // seek之后mCSDsToSubmit不为空,会进入到这个if当中
if (!mCSDsToSubmit.isEmpty()) {
// ......
} // mPendingInputMessages : 用于记录未成功写给decoder的buffer
while (!mPendingInputMessages.empty()) {
sp<AMessage> msg = *mPendingInputMessages.begin();
if (!onInputBufferFetched(msg)) {
break;
}
mPendingInputMessages.erase(mPendingInputMessages.begin());
} // ......
// mDequeuedInputBuffers : 用于顺序记录mediacodec送上来的index
mDequeuedInputBuffers.push_back(index); onRequestInputBuffers();
return true;
}

上面的逻辑比较简单,当有inputbuffer送上来,NuPlayerDecoder就把这个buffer的索引以及buffer记录下来,然后调用onRequestInputBuffers去向Source请求数据。

// NuPlayerDecoderBase.cpp
void NuPlayer::DecoderBase::onRequestInputBuffers() {
if (mRequestInputBuffersPending) {
return;
} // doRequestBuffers() return true if we should request more data
if (doRequestBuffers()) {
mRequestInputBuffersPending = true; sp<AMessage> msg = new AMessage(kWhatRequestInputBuffers, this);
msg->post(10 * 1000LL);
}
} void NuPlayer::DecoderBase::onMessageReceived(const sp<AMessage> &msg) {
// ......
case kWhatRequestInputBuffers:
{
mRequestInputBuffersPending = false;
onRequestInputBuffers();
break;
}
// ......
}

向Source请求数据最终会调用到doRequestBuffers方法当中,当doRequestBuffers方法返回true时,会再次调用onRequestInputBuffers(正如注释所言,当需要请求更多的数据的时候会返回true),接下来看看doRequestBuffers做了什么,在什么时候会返回true

bool NuPlayer::Decoder::doRequestBuffers() {

    // 当队列中有buffer时就会去请求数据,直到把队列中所有的buffer处理结束
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()); // mPendingInputMessages : 用于记录未处理完成的inputbuffer
// 先判断待处理队列是否为空,不为空则把本次处理的buffer加入到队列中
// 如果为空,则直接调用onInputBufferFetched做处理,如果处理失败,则把本次处理的buffer加入到队列当中
if (!mPendingInputMessages.empty()
|| !onInputBufferFetched(msg)) {
mPendingInputMessages.push_back(msg);
}
} return err == -EWOULDBLOCK
&& mSource->feedMoreTSData() == OK;
}

这里包含有几个步骤:

a. 先从未处理的索引队列中取出一个索引,然后为这个索引指向的buffer请求数据(调用fetchInputData)

b. 判断待处理消息的队列是否为空,如果不为空,则把当前消息加入到队列中等待处理,如果队列为空,则直接调用onInputBufferFetched处理当前消息

c. 当fetchInputData返回值为 -EWOULDBLOCK时,doRequestBuffers返回true(请求buffer失败)

接下来要看的就是fetchInputData中做了什么,onInputBufferFetched又做了什么?

status_t NuPlayer::Decoder::fetchInputData(sp<AMessage> &reply) {
sp<ABuffer> accessUnit;
bool dropAccessUnit = true;
do {
// 从Source中请求数据
status_t err = mSource->dequeueAccessUnit(mIsAudio, &accessUnit); if (err == -EWOULDBLOCK) {
return err;
} else if (err != OK) {
// 在这里判断是否有不连续信息,如果有就做对应动作
// 如果发送格式变化,时间不连续的情况就给decoder发eos
if (err == INFO_DISCONTINUITY) {
// ...... // 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;
} dropAccessUnit = false;
if (!mIsAudio && !mIsEncrypted) {
if (mIsEncryptedObservedEarlier) {
ALOGE("fetchInputData: mismatched mIsEncrypted/mIsEncryptedObservedEarlier (0/1)"); return INVALID_OPERATION;
} int32_t layerId = 0;
bool haveLayerId = accessUnit->meta()->findInt32("temporal-layer-id", &layerId);
// 如果当前从render获取的AVSync差距超过100ms,并且是AVC,不是关键帧,那么丢弃本帧,重新读取
if (mRenderer->getVideoLateByUs() > 100000LL
&& mIsVideoAVC
&& !IsAVCReferenceFrame(accessUnit)) {
dropAccessUnit = true;
} else if (haveLayerId && mNumVideoTemporalLayerTotal > 1) {
// Add only one layer each time.
if (layerId > mCurrentMaxVideoTemporalLayerId + 1
|| layerId >= mNumVideoTemporalLayerAllowed) {
dropAccessUnit = true;
ALOGV("dropping layer(%d), speed=%g, allowed layer count=%d, max layerId=%d",
layerId, mPlaybackSpeed, mNumVideoTemporalLayerAllowed,
mCurrentMaxVideoTemporalLayerId);
} else if (layerId > mCurrentMaxVideoTemporalLayerId) {
mCurrentMaxVideoTemporalLayerId = layerId;
} else if (layerId == 0 && mNumVideoTemporalLayerTotal > 1
&& IsIDR(accessUnit->data(), accessUnit->size())) {
mCurrentMaxVideoTemporalLayerId = mNumVideoTemporalLayerTotal - 1;
}
}
if (dropAccessUnit) {
if (layerId <= mCurrentMaxVideoTemporalLayerId && layerId > 0) {
mCurrentMaxVideoTemporalLayerId = layerId - 1;
}
++mNumInputFramesDropped;
}
}
} while (dropAccessUnit); // ......
if (mCCDecoder != NULL) {
mCCDecoder->decode(accessUnit);
} reply->setBuffer("buffer", accessUnit); return OK;
}

fetchInputData做了三件事:

a. 调用Source的dequeueAccessUnit方法获取读取到的数据,判断当前获取数据的标志

如果是格式发送变化则通知decoder eos,如果是时间不连续则同样通知decoder eos,但是会记录下csd信息,用于重新初始化decoder

b. 获取当前AVSync的状态,如果差距大于100ms则丢弃当前数据

c. 将获取到的数据封装到AMessage当中

拿到数据之后接下来就是调用onInputBufferFetched来处理AMessage

bool NuPlayer::Decoder::onInputBufferFetched(const sp<AMessage> &msg) {

    // ......
size_t bufferIx;
CHECK(msg->findSize("buffer-ix", &bufferIx));
CHECK_LT(bufferIx, mInputBuffers.size());
sp<MediaCodecBuffer> codecBuffer = mInputBuffers[bufferIx]; sp<ABuffer> buffer;
bool hasBuffer = msg->findBuffer("buffer", &buffer);
bool needsCopy = true; // 如果获取的buffer为null,则认为是EOS
if (buffer == NULL /* includes !hasBuffer */) {
int32_t streamErr = ERROR_END_OF_STREAM;
CHECK(msg->findInt32("err", &streamErr) || !hasBuffer); CHECK(streamErr != OK); // attempt to queue EOS
status_t err = mCodec->queueInputBuffer(
bufferIx,
0,
0,
0,
MediaCodec::BUFFER_FLAG_EOS);
if (err == OK) {
mInputBufferIsDequeued.editItemAt(bufferIx) = false;
} else if (streamErr == ERROR_END_OF_STREAM) {
streamErr = err;
// err will not be ERROR_END_OF_STREAM
} if (streamErr != ERROR_END_OF_STREAM) {
ALOGE("Stream error for [%s] (err=%d), EOS %s queued",
mComponentName.c_str(),
streamErr,
err == OK ? "successfully" : "unsuccessfully");
handleError(streamErr);
}
} else {
// 否则将buffer copy到mediacodecbuffer当中,送给decoder
sp<AMessage> extra;
if (buffer->meta()->findMessage("extra", &extra) && extra != NULL) {
int64_t resumeAtMediaTimeUs;
if (extra->findInt64(
"resume-at-mediaTimeUs", &resumeAtMediaTimeUs)) {
ALOGV("[%s] suppressing rendering until %lld us",
mComponentName.c_str(), (long long)resumeAtMediaTimeUs);
mSkipRenderingUntilMediaTimeUs = resumeAtMediaTimeUs;
}
} int64_t timeUs = 0;
uint32_t flags = 0;
CHECK(buffer->meta()->findInt64("timeUs", &timeUs)); int32_t eos, csd, cvo;
// we do not expect SYNCFRAME for decoder
if (buffer->meta()->findInt32("eos", &eos) && eos) {
flags |= MediaCodec::BUFFER_FLAG_EOS;
} else if (buffer->meta()->findInt32("csd", &csd) && csd) {
flags |= MediaCodec::BUFFER_FLAG_CODECCONFIG;
} if (buffer->meta()->findInt32("cvo", (int32_t*)&cvo)) {
ALOGV("[%s] cvo(%d) found at %lld us", mComponentName.c_str(), cvo, (long long)timeUs);
switch (cvo) {
case 0:
codecBuffer->meta()->setInt32("cvo", MediaCodec::CVO_DEGREE_0);
break;
case 1:
codecBuffer->meta()->setInt32("cvo", MediaCodec::CVO_DEGREE_90);
break;
case 2:
codecBuffer->meta()->setInt32("cvo", MediaCodec::CVO_DEGREE_180);
break;
case 3:
codecBuffer->meta()->setInt32("cvo", MediaCodec::CVO_DEGREE_270);
break;
}
} // Modular DRM
MediaBufferBase *mediaBuf = NULL;
NuPlayerDrm::CryptoInfo *cryptInfo = NULL; // copy into codec buffer
if (needsCopy) {
if (buffer->size() > codecBuffer->capacity()) {
handleError(ERROR_BUFFER_TOO_SMALL);
mDequeuedInputBuffers.push_back(bufferIx);
return false;
} if (buffer->data() != NULL) {
codecBuffer->setRange(0, buffer->size());
memcpy(codecBuffer->data(), buffer->data(), buffer->size());
} else { // No buffer->data()
// 加密视频的处理
//Modular DRM
sp<RefBase> holder;
if (buffer->meta()->findObject("mediaBufferHolder", &holder)) {
mediaBuf = (holder != nullptr) ?
static_cast<MediaBufferHolder*>(holder.get())->mediaBuffer() : nullptr;
}
if (mediaBuf != NULL) {
if (mediaBuf->size() > codecBuffer->capacity()) {
handleError(ERROR_BUFFER_TOO_SMALL);
mDequeuedInputBuffers.push_back(bufferIx);
return false;
} codecBuffer->setRange(0, mediaBuf->size());
memcpy(codecBuffer->data(), mediaBuf->data(), mediaBuf->size()); MetaDataBase &meta_data = mediaBuf->meta_data();
cryptInfo = NuPlayerDrm::getSampleCryptoInfo(meta_data);
} else { // No mediaBuf
ALOGE("onInputBufferFetched: buffer->data()/mediaBuf are NULL for %p",
buffer.get());
handleError(UNKNOWN_ERROR);
return false;
}
} // buffer->data()
} // needsCopy status_t err;
AString errorDetailMsg;
if (cryptInfo != NULL) {
err = mCodec->queueSecureInputBuffer(
bufferIx,
codecBuffer->offset(),
cryptInfo->subSamples,
cryptInfo->numSubSamples,
cryptInfo->key,
cryptInfo->iv,
cryptInfo->mode,
cryptInfo->pattern,
timeUs,
flags,
&errorDetailMsg);
// synchronous call so done with cryptInfo here
free(cryptInfo);
} else {
err = mCodec->queueInputBuffer(
bufferIx,
codecBuffer->offset(),
codecBuffer->size(),
timeUs,
flags,
&errorDetailMsg);
} // no cryptInfo if (err != OK) {
ALOGE("onInputBufferFetched: queue%sInputBuffer failed for [%s] (err=%d, %s)",
(cryptInfo != NULL ? "Secure" : ""),
mComponentName.c_str(), err, errorDetailMsg.c_str());
handleError(err);
} else {
mInputBufferIsDequeued.editItemAt(bufferIx) = false;
} } // buffer != NULL
return true;
}

这里的代码比较长,主要是分了两种情况:

a. 如果message中的buffer为空,那么说明已经EOS了(并不一定是真的eos,参考上面fetchInputData,可能是seek或者formatchange)

b. 如果buffer不为空,且buffer.data不为空,那么说明是普通视频,直接拷贝到mediacodec的buffer当中;如果buffer.data为空,说明这是个加密视频,要从mediaBufferHolder中获取buffer,并且获取到CryptoInfo,

到这里数据的写入就完成了。

3、mediacodec送回的解码后的数据要如何接收呢,接收完要怎么处理呢

这里比较简单,从mediacodec获取到解码后的数据,送给Renderer做AVsync,Renderer再用消息通知NuPlayerDecoder来渲染

另外有个地方这边没有标注出来,seek之后出了第一帧时(resume),会调用notifyResumeCompleteIfNecessary方法通知上层第一帧解码完成。

4、start,stop,pause,seek,Decoder需要做什么对应的动作

a. start在第一节里面已经讲过了,调用NuPlayer的start接口后创建mediacodec对象,注册callback就OK了

b. NuPlayer并没有stop接口,但是mediaplayer java接口是有这个接口的,从NuPlayerDriver中来看

status_t NuPlayerDriver::stop() {
ALOGD("stop(%p)", this);
Mutex::Autolock autoLock(mLock); switch (mState) {
case STATE_RUNNING:
mPlayer->pause();
FALLTHROUGH_INTENDED; case STATE_PAUSED:
mState = STATE_STOPPED;
notifyListener_l(MEDIA_STOPPED);
break;
// ...... default:
return INVALID_OPERATION;
} return OK;
}

stop接口其实调用的就是pause。

c. pause

pause的代码很简单,调用了Source和Renderer的pause,NuPlayerDecoder不会做任何动作

这时候mediacodec仍然会向上送InputBuffer以及outputBuffer,但是这时候并不去处理这些buffer,fetchInputBuffer不能读取到数据,会在这里一直等待读到数据

void NuPlayer::onPause() {

    updatePlaybackTimer(true /* stopping */, "onPause");

    if (mPaused) {
return;
}
mPaused = true;
if (mSource != NULL) {
mSource->pause();
} else {
ALOGW("pause called when source is gone or not set");
}
if (mRenderer != NULL) {
mRenderer->pause();
} else {
ALOGW("pause called when renderer is gone or not set");
} }

d. seek

调用seek之后Decoder总共有4个动作:

aa. 调用Render的flush

bb. 调用mediacodec的seek

cc. 释放所有保存在Decoder的buffer以及相关的标志

dd. 重新调用mediacodec的start方法,恢复运行

到这里,NuPlayerDecoder部分工作原理学习的差不多就结束了,但是这里还有很多细节没有去研究,但是顺着这个框架看应该就比较简单了。

Android 12(S) MultiMedia Learning(六)NuPlayer Decoder的更多相关文章

  1. Android 12(S) 图形显示系统 - BufferQueue/BLASTBufferQueue之初识(六)

    题外话 你有没有听见,心里有一声咆哮,那一声咆哮,它好像在说:我就是要从后面追上去! 写文章真的好痛苦,特别是自己对这方面的知识也一知半解就更加痛苦了.这已经是这个系列的第六篇了,很多次都想放弃了,但 ...

  2. Android 12(S) 图像显示系统 - SurfaceFlinger之VSync-上篇(十六)

    必读: Android 12(S) 图像显示系统 - 开篇 一.前言 为了提高Android系统的UI交互速度和操作的流畅度,在Android 4.1中,引入了Project Butter,即&quo ...

  3. Android系统--输入系统(六)模拟输入驱动程序

    Android系统--输入系统(六)模拟输入驱动程序 1. 回顾输入子系统 简单字符设备驱动:应用程序通过调用驱动所实现的函数使能硬件. 输入子系统:由于有多个应用程序使用输入子系统,故肯定使用的是早 ...

  4. Android TV开发总结(六)构建一个TV app的直播节目实例

    请尊重分享成果,转载请注明出处:http://blog.csdn.net/hejjunlin/article/details/52966319 近年来,Android TV的迅速发展,传统的有线电视受 ...

  5. Android群英传笔记——第六章:Android绘图机制与处理技巧

    Android群英传笔记--第六章:Android绘图机制与处理技巧 一直在情调,时间都是可以自己调节的,不然世界上哪有这么多牛X的人 今天就开始读第六章了,算日子也刚好一个月了,一个月就读一半,这效 ...

  6. Android 12(S) 图形显示系统 - createSurface的流程(五)

    题外话 刚刚开始着笔写作这篇文章时,正好看电视在采访一位92岁的考古学家,在他的日记中有这样一句话,写在这里与君共勉"不要等待幸运的降临,要去努力的掌握知识".如此朴实的一句话,此 ...

  7. Android 12(S) 图形显示系统 - 初识ANativeWindow/Surface/SurfaceControl(七)

    题外话 "行百里者半九十",是说步行一百里路,走过九十里,只能算是走了一半.因为步行越接近目的地,走起来越困难.借指凡事到了接近成功,往往是最吃力.最艰难的时段.劝人做事贵在坚持, ...

  8. Android 12(S) 图形显示系统 - BufferQueue的工作流程(八)

    题外话 最近总有一个感觉:在不断学习中,越发的感觉自己的无知,自己是不是要从"愚昧之巅"掉到"绝望之谷"了,哈哈哈 邓宁-克鲁格效应 一.前言 前面的文章中已经 ...

  9. Android 12(S) 图形显示系统 - Surface 一点补充知识(十二)

    必读: Android 12(S) 图形显示系统 - 开篇 一.前言 因为个人工作主要是Android多媒体播放的内容,在工作中查看源码或设计程序经常会遇到调用API: static inline i ...

  10. Android 12(S) 图形显示系统 - 简单聊聊 SurfaceView 与 BufferQueue的关联(十三)

    必读: Android 12(S) 图形显示系统 - 开篇 一.前言 前面的文章中,讲解的内容基本都是从我们提供的一个 native demo Android 12(S) 图形显示系统 - 示例应用( ...

随机推荐

  1. js 词法作用域

    前言 什么是作用域? 作用域(scope),程序设计概念,通常来说,一段程序代码中所用到的名字并不总是有效/可用的,而限定这个名字的可用性的代码范围就是这个名字的作用域. 白话文:作用域就是变量在哪块 ...

  2. iOS自动化打包命令xcodebuild大全

    iOS实现自动化打包已经稳定运营几年了,不同的场景用到xcodebuild命令不一样,有的参数可能一直都用不到,列举一些常用的命令,比如编译命令: xcodebuild archive -worksp ...

  3. Kafka 线上性能调优

    Kafka 线上性能调优是一项综合工程,不仅仅是 Kafka 本身,还应该从硬件(存储.网络.CPU)以及操作系统方面来整体考量,首先我们要有一套生产部署方案,基于这套方案再进行调优,这样就有了可靠的 ...

  4. Java实现控制台购书系统

    "感谢您阅读本篇博客!如果您觉得本文对您有所帮助或启发,请不吝点赞和分享给更多的朋友.您的支持是我持续创作的动力,也欢迎留言交流,让我们一起探讨技术,共同成长!谢谢!" 代码 im ...

  5. 如果千百年前有视觉AI算法,世界将会是什么样的光景呢?

    视觉AI算法在近些年取得了一定的突破,被应用在了越来越多的地方,我相信距离真正的AI普及这个大目标也越来越近了.我时常在想假如古代也有视觉AI算法,那是不是很多故事的结局都将被改写?<伯乐相马& ...

  6. 当 TiDB 与 Flink 相结合:高效、易用的实时数仓

    简介: 利用实时数仓,企业可以实现实时 OLAP 分析.实时数据看板.实时业务监控.实时数据接口服务等用途.但想到实时数仓,很多人的第一印象就是架构复杂,难以操作与维护.而得益于新版 Flink 对 ...

  7. 揭秘!阿里实时数仓分布式事务Scale Out设计

    简介: Hybrid Transaction Analytical Processing(HTAP) 是著名信息技术咨询与分析公司Gartner在2014年提出的一个新的数据库系统定义,特指一类兼具O ...

  8. [FAQ] Windows 终端 `git diff` 出现 LF 空格 ^M 符号, 处理方式

      可能是终端内的换行配置和 IDE 当中的不一致. 比如 PHPStorm 的: Git 终端使用 git config core.autocrlf 查看是 true 还是 false. 是 tru ...

  9. [FAQ] Beego2.0.2 bee 生成的 api 项目运行 404, http server Running on http://:8080

    Beego, bee version 2.0.2 https://github.com/beego/beego/issues/4363 Tool:AI 编程助手 Refer:Beego还流行吗 Lin ...

  10. LVGL 显示图片

    一.图片存储 我们可以将图像存储在两个位置 作为内部存储器(RAM或ROM)中的变量 作为文件 图片以文件的形式存储在文件系中(比如SD),需要打开LVGL的文件操作的功能(打开,读取,关闭等).虽然 ...