Android 12(S) MultiMedia Learning(六)NuPlayer Decoder
接下来将会从4个角度来记录NuPlayerDecoder部分
相关代码路径:
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的更多相关文章
- Android 12(S) 图形显示系统 - BufferQueue/BLASTBufferQueue之初识(六)
题外话 你有没有听见,心里有一声咆哮,那一声咆哮,它好像在说:我就是要从后面追上去! 写文章真的好痛苦,特别是自己对这方面的知识也一知半解就更加痛苦了.这已经是这个系列的第六篇了,很多次都想放弃了,但 ...
- Android 12(S) 图像显示系统 - SurfaceFlinger之VSync-上篇(十六)
必读: Android 12(S) 图像显示系统 - 开篇 一.前言 为了提高Android系统的UI交互速度和操作的流畅度,在Android 4.1中,引入了Project Butter,即&quo ...
- Android系统--输入系统(六)模拟输入驱动程序
Android系统--输入系统(六)模拟输入驱动程序 1. 回顾输入子系统 简单字符设备驱动:应用程序通过调用驱动所实现的函数使能硬件. 输入子系统:由于有多个应用程序使用输入子系统,故肯定使用的是早 ...
- Android TV开发总结(六)构建一个TV app的直播节目实例
请尊重分享成果,转载请注明出处:http://blog.csdn.net/hejjunlin/article/details/52966319 近年来,Android TV的迅速发展,传统的有线电视受 ...
- Android群英传笔记——第六章:Android绘图机制与处理技巧
Android群英传笔记--第六章:Android绘图机制与处理技巧 一直在情调,时间都是可以自己调节的,不然世界上哪有这么多牛X的人 今天就开始读第六章了,算日子也刚好一个月了,一个月就读一半,这效 ...
- Android 12(S) 图形显示系统 - createSurface的流程(五)
题外话 刚刚开始着笔写作这篇文章时,正好看电视在采访一位92岁的考古学家,在他的日记中有这样一句话,写在这里与君共勉"不要等待幸运的降临,要去努力的掌握知识".如此朴实的一句话,此 ...
- Android 12(S) 图形显示系统 - 初识ANativeWindow/Surface/SurfaceControl(七)
题外话 "行百里者半九十",是说步行一百里路,走过九十里,只能算是走了一半.因为步行越接近目的地,走起来越困难.借指凡事到了接近成功,往往是最吃力.最艰难的时段.劝人做事贵在坚持, ...
- Android 12(S) 图形显示系统 - BufferQueue的工作流程(八)
题外话 最近总有一个感觉:在不断学习中,越发的感觉自己的无知,自己是不是要从"愚昧之巅"掉到"绝望之谷"了,哈哈哈 邓宁-克鲁格效应 一.前言 前面的文章中已经 ...
- Android 12(S) 图形显示系统 - Surface 一点补充知识(十二)
必读: Android 12(S) 图形显示系统 - 开篇 一.前言 因为个人工作主要是Android多媒体播放的内容,在工作中查看源码或设计程序经常会遇到调用API: static inline i ...
- Android 12(S) 图形显示系统 - 简单聊聊 SurfaceView 与 BufferQueue的关联(十三)
必读: Android 12(S) 图形显示系统 - 开篇 一.前言 前面的文章中,讲解的内容基本都是从我们提供的一个 native demo Android 12(S) 图形显示系统 - 示例应用( ...
随机推荐
- docker 应用篇————docker基本命令[四]
前言 介绍一下一些docker的基本命令. 正文 帮助命令: 首先要学的肯定是docker --help 命令了,因为这样我们就不用经常去查官网. docker version docker info ...
- 初探Mysql架构和InnoDB存储引擎
前言 mysql相信大家都不陌生了,分享之前我们先思考几个面试题: 1.undo log和redo log了解过吗?它们的作⽤分别是什么? 2.redo log是如何保证事务不丢失的? 3.mysql ...
- 如何快速实现Prometheus监控Kubernetes集群
Prometheus K8S集群中常见的监控工具有哪些: Kubernetes Dashboard Kube-monkey K8s-testsuite Kubespray Minikube Prome ...
- 图像验证码识别,字母数字汉子均可cnn+lstm+ctc
图形验证码如下: 训练两轮时的准确率:上边显示的是未识别的 config_demo.yaml System: GpuMemoryFraction: 0.7 TrainSetPath: 'train/ ...
- 力扣744(java&python)- 寻找比目标字母大的最小字母(简单)
题目: 给你一个排序后的字符列表 letters ,列表中只包含小写英文字母.另给出一个目标字母 target,请你寻找在这一有序列表里比目标字母大的最小字母. 在比较时,字母是依序循环出现的.举个例 ...
- 力扣445(java&python)-两数相加Ⅱ(中等)
题目: 给你两个 非空 链表来代表两个非负整数.数字最高位位于链表开始位置.它们的每个节点只存储一位数字.将这两数相加会返回一个新的链表. 你可以假设除了数字 0 之外,这两个数字都不会以零开头. 示 ...
- 可观测|时序数据降采样在Prometheus实践复盘
简介: 基于 Prometheus 的监控实践中,尤其是在规模较大时,时序数据的存储与查询是其中非常关键,而且问题点较多的一环.如何应对大数据量下的长周期查询,原生的 Prometheus 体系并未能 ...
- 记一次 JMeter 压测 HTTPS 性能问题
简介:在使用 JMeter 压测时,发现同一后端服务,在单机 500 并发下,HTTP 和 HTTPS 协议压测 RT 差距非常大.同时观测后端服务各监控指标水位都很低,因此怀疑性能瓶颈在 JMet ...
- ClickHouse Keeper 源码解析
简介:ClickHouse 社区在21.8版本中引入了 ClickHouse Keeper.ClickHouse Keeper 是完全兼容 Zookeeper 协议的分布式协调服务.本文对开源版本 C ...
- 学术顶会再突破!计算平台MaxCompute论文入选国际顶会VLDB 2021
简介: VLDB 2021上,阿里云计算平台MaxCompute参与的论文入选,核心分布式调度执行引擎Fangorn.基于TVR Cost模型的通用增量计算优化器框架Tempura等分别被Indu ...