接下来将会从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. Python3中pip3命令的用法介绍及安装配置

    第一节:pip3是什么?有啥用? pip3:(Python3 Install Package ),这个英文全称是我为了更好的理解这个命令这么叫的,官方没有这对个命令的全称的解释:) python 支持 ...

  2. 报表如何集成 echarts 官网示例图

    Echarts,江湖人称一个纯 Javascript 的图表库,图形种类星罗棋布且个个颜值爆表,可以轻松驾驭 PC 和移动设备,与绝大部分浏览器都可称兄道弟,而且已然众多拥趸,还有不少报表对它采取了嫁 ...

  3. mysql 必知必会整理—视图[十二]

    前言 简单整理一下视图. 正文 视图: 需要MySQL 5 MySQL 5添加了对视图的支持.因此,本章内容适用于MySQL 5及以后的版本. 视图是虚拟的表.与包含数据的表不一样,视图只包含使用时动 ...

  4. python数据库迁移

    实际操作命令 1,python 文件.py db init 2,python xx.py db migrate -m '版本描述' 3,python xx.py db upgrade 4,python ...

  5. tomcat 服务版本内存设置

    1. 安装服务,如需指定java路径,需要在service.bat 中修改, 如下图 其中 pa代表当前目录 2. 安装服务, service.bat install 服务名,如下图示例 3. 内存设 ...

  6. boltdb 介绍

    介绍 BoltDB 是一个用 Go 语言编写的嵌入式键/值数据库.以下是关于 BoltDB 的一些基本介绍: 键/值存储: BoltDB 为应用程序提供了简单的键/值存储接口. 事务: BoltDB ...

  7. 力扣1107(MySQL)-每日新用户统计(中等)

    题目: Traffic 表: 该表没有主键,它可能有重复的行.activity 列是 ENUM 类型,可能取 ('login', 'logout', 'jobs', 'groups', 'homepa ...

  8. 力扣299(java)-猜数字游戏(中等)

    题目: 你在和朋友一起玩 猜数字(Bulls and Cows)游戏,该游戏规则如下: 写出一个秘密数字,并请朋友猜这个数字是多少.朋友每猜测一次,你就会给他一个包含下述信息的提示: 猜测数字中有多少 ...

  9. 力扣326(java)-3的幂(简单)

    题目: 给定一个整数,写一个函数来判断它是否是 3 的幂次方.如果是,返回 true :否则,返回 false . 整数 n 是 3 的幂次方需满足:存在整数 x 使得 n == 3x 示例 1: 输 ...

  10. 如何做好技术 Team Leader?

    简介: 作为一个技术TL(Team Leader),除了自身技能,还会面临诸多团队管理上的困难和挑战.如何定义和明确团队的目标?怎样建立优秀的工程文化?让团队长期发挥战斗力和创新能力的核心是什么?本文 ...