承接上一章节分析:【六】Android MediaPlayer整体架构源码分析 -【start请求播放处理流程】【Part 7】【03】
本系列文章分析的安卓源码版本:【Android 10.0 版本】
推荐涉及到的知识点:
Binder机制实现原理:Android C++底层Binder通信机制原理分析总结【通俗易懂】
ALooper机制实现原理:Android native层媒体通信架构AHandler/ALooper机制实现源码分析
Binder异常关闭监听:Android native层DeathRecipient对关联进程(如相关Service服务进程)异常关闭通知事件的监听实现源码分析
【此章节小节编号将重新排序】
onInputBufferFetched(msg)实现分析:
输入Buffer已获取完成处理流程,参数为获取到的携带输入Buffer的消息对象
// [frameworks/av/media/libmediaplayerservice/nuplayer/NuPlayerDecoder.cpp]
bool NuPlayer::Decoder::onInputBufferFetched(const sp<AMessage> &msg) {
if (mCodec == NULL) {
// 未初始化错误码
ALOGE("[%s] onInputBufferFetched without a valid codec", mComponentName.c_str());
handleError(NO_INIT);
return false;
}
size_t bufferIx;
// 已获取到的输入Buffer索引
CHECK(msg->findSize("buffer-ix", &bufferIx));
// 该索引必须小于当前输入缓冲区个数
CHECK_LT(bufferIx, mInputBuffers.size());
// 获取该索引在输入端口队列中对应的Buffer
sp<MediaCodecBuffer> codecBuffer = mInputBuffers[bufferIx];
sp<ABuffer> buffer;
// 提取已获取到的实际数据Buffer对象指针,若获取成功则表示有Buffer
bool hasBuffer = msg->findBuffer("buffer", &buffer);
// 判断是否需要拷贝实际数据Buffer,默认为true
bool needsCopy = true;
if (buffer == NULL /* includes !hasBuffer */) {
// 没有实际数据buffer时
// 流错误码,默认为EOS
int32_t streamErr = ERROR_END_OF_STREAM;
// 获取错误码
CHECK(msg->findInt32("err", &streamErr) || !hasBuffer);
// 错误码必须不能为OK
CHECK(streamErr != OK);
// attempt to queue EOS
// 尝试入队列该Buffer到输入端口队列缓冲区,
// 也就是递交当前已填充输入Buffer给Codec的输入端口队列
// 见第1小节分析
// 备注:这里递交的输入Buffer是无效数据,下面流程中才会递交有效已填充输入Buffer
status_t err = mCodec->queueInputBuffer(
bufferIx,
0,
0,
0,
MediaCodec::BUFFER_FLAG_EOS);
if (err == OK) {
// 成功之后就将当前buffer index对应的该列表中item设置为false,
// 即标志当前index的输入Buffer未出队列状态了,也就是说该buffer已填充完成时修改该状态
mInputBufferIsDequeued.editItemAt(bufferIx) = false;
} else if (streamErr == ERROR_END_OF_STREAM) {
// 流错误码为EOS时,转换为递交Buffer返回错误码
streamErr = err;
// err will not be ERROR_END_OF_STREAM
}
if (streamErr != ERROR_END_OF_STREAM) {
// 非EOS时
ALOGE("Stream error for [%s] (err=%d), EOS %s queued",
mComponentName.c_str(),
streamErr,
err == OK ? "successfully" : "unsuccessfully");
// 通知NuPlayer【kWhatError】事件消息错误
// 见早前章节中已有分析
handleError(streamErr);
}
} else {
// 有输入Buffer数据时
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);
// 全局记录该值
// 备注:此处处理也非常重要,跳过渲染直到帧数据到达此恢复播放媒体时间点时才会递交Buffer给解码进行解码播放
// 重要处理的是在上层APP指定seek模式为seek到指定媒体时间点(可能为非关键帧)时才能播放的情况处理。
mSkipRenderingUntilMediaTimeUs = resumeAtMediaTimeUs;
}
}
sp<ABuffer> hdr10PlusInfo;
if (buffer->meta()->findBuffer("hdr10-plus-info", &hdr10PlusInfo) &&
hdr10PlusInfo != NULL) {
// 获取HDR增强信息成功时
sp<AMessage> hdr10PlusMsg = new AMessage;
hdr10PlusMsg->setBuffer("hdr10-plus-info", hdr10PlusInfo);
// 重新设置该配置信息参数给底层组件
// 见此前已有流程分析
mCodec->setParameters(hdr10PlusMsg);
}
int64_t timeUs = 0;
uint32_t flags = 0;
// 获取该帧Buffer显示时间戳PTS
CHECK(buffer->meta()->findInt64("timeUs", &timeUs));
int32_t eos, csd;
// we do not expect SYNCFRAME for decoder
if (buffer->meta()->findInt32("eos", &eos) && eos) {
// eos状态Buffer时,添加Buffer EOS flag
flags |= MediaCodec::BUFFER_FLAG_EOS;
} else if (buffer->meta()->findInt32("csd", &csd) && csd) {
// 若为CSD负责数据Buffer时,标记当前Buffer flag为BUFFER_FLAG_CODECCONFIG
flags |= MediaCodec::BUFFER_FLAG_CODECCONFIG;
}
// Modular DRM
MediaBufferBase *mediaBuf = NULL;
NuPlayerDrm::CryptoInfo *cryptInfo = NULL;
// 从上面needsCopy标识默认为true可知,
// ACodec递交Buffer时都默认需要拷贝负载数据给Codec支持的Buffer
// copy into codec buffer
if (needsCopy) {
// 实际负载数据不能大于codecBuffer的容量值
if (buffer->size() > codecBuffer->capacity()) {
// 否则通知Buffer太小错误码
handleError(ERROR_BUFFER_TOO_SMALL);
// 然后将当前Buffer index放入到已出队列输入Buffer中
// 备注:也就是说让Decoder重新去填充输入Buffer
mDequeuedInputBuffers.push_back(bufferIx);
return false;
}
if (buffer->data() != NULL) {
// 实际负载数据Buffer不为空时
// 设置负载数据有效范围
codecBuffer->setRange(0, buffer->size());
// 拷贝数据
memcpy(codecBuffer->data(), buffer->data(), buffer->size());
} else { // No buffer->data()
// buffer负载数据为空时
// 此处处理的是DRM加密数据源情况,不关注它
//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 {
// 非加密普通数据源时
// 递交当前已填充输入Buffer给Codec的输入端口队列
// 见前面第1小节分析
err = mCodec->queueInputBuffer(
bufferIx,
codecBuffer->offset(),
codecBuffer->size(),
timeUs,
flags,
&errorDetailMsg);
} // no cryptInfo
if (err != OK) {
// 递交输入Buffer失败时,通知错误给NuPlayer
ALOGE("onInputBufferFetched: queue%sInputBuffer failed for [%s] (err=%d, %s)",
(cryptInfo != NULL ? "Secure" : ""),
mComponentName.c_str(), err, errorDetailMsg.c_str());
handleError(err);
} else {
// 递交Buffer成功时
// 重置该输入Buffer的该状态
mInputBufferIsDequeued.editItemAt(bufferIx) = false;
}
} // buffer != NULL
// 返回true
return true;
}
1、mCodec->queueInputBuffer(bufferIx, 0, 0, 0, MediaCodec::BUFFER_FLAG_EOS)实现分析:
尝试入队列该Buffer到输入端口队列缓冲区,即递交当前已输入Buffer给Codec的输入端口队列
最后一个参数errorDetailMsg默认为null
备注:注意该方法是同步调用执行的。
// [frameworks/av/media/libstagefright/MediaCodec.cpp]
status_t MediaCodec::queueInputBuffer(
size_t index,
size_t offset,
size_t size,
int64_t presentationTimeUs,
uint32_t flags,
AString *errorDetailMsg) {
if (errorDetailMsg != NULL) {
errorDetailMsg->clear();
}
// 创建【kWhatQueueInputBuffer】事件消息,MediaCodec接收处理
sp<AMessage> msg = new AMessage(kWhatQueueInputBuffer, this);
// 设置这些参数
// buffer index索引
msg->setSize("index", index);
// 实际负载Buffer的数据偏移量
msg->setSize("offset", offset);
// 实际负载Buffer的数据大小
msg->setSize("size", size);
// PTS帧显示时间戳
msg->setInt64("timeUs", presentationTimeUs);
// 标志位
msg->setInt32("flags", flags);
// 存储详细错误信息
msg->setPointer("errorDetailMsg", errorDetailMsg);
// 发送并等待响应消息
sp<AMessage> response;
// 该方法见早前已有分析
return PostAndAwaitResponse(msg, &response);
}
MediaCodec接收处理【kWhatQueueInputBuffer】事件消息:
// [frameworks/av/media/libstagefright/MediaCodec.cpp]
void MediaCodec::onMessageReceived(const sp<AMessage> &msg) {
switch (msg->what()) {
// 入队列输入Buffer到输入端口队列缓冲区事件处理
case kWhatQueueInputBuffer:
{
// 获取应答消息对象
sp<AReplyToken> replyID;
CHECK(msg->senderAwaitsResponse(&replyID));
// 判断当前MediaCodec是否正在执行状态中,即判断 mState == STARTED || mState == FLUSHED
if (!isExecuting()) {
// 未执行状态时,应答无效操作错误码
PostReplyWithError(replyID, INVALID_OPERATION);
break;
} else if (mFlags & kFlagStickyError) {
// 有前面可知,若发生该错误码,也应答返回此前发生的错误码
PostReplyWithError(replyID, getStickyError());
break;
}
// 入队列已填充数据的输入Buffer到输入端口队列缓冲区
// 见下面分析
status_t err = onQueueInputBuffer(msg);
// 应答返回执行状态码
PostReplyWithError(replyID, err);
break;
}
}
}
onQueueInputBuffer(msg)实现分析:
入队列已填充数据的输入Buffer到输入端口队列缓冲区
// [frameworks/av/media/libstagefright/MediaCodec.cpp]
status_t MediaCodec::onQueueInputBuffer(const sp<AMessage> &msg) {
size_t index;
size_t offset;
size_t size;
int64_t timeUs;
uint32_t flags;
// 获取传入的参数
CHECK(msg->findSize("index", &index));
CHECK(msg->findSize("offset", &offset));
CHECK(msg->findInt64("timeUs", &timeUs));
CHECK(msg->findInt32("flags", (int32_t *)&flags));
// 加密插件子样本数,不关注它,此为加密数据源时才有
const CryptoPlugin::SubSample *subSamples;
// 子样本数据数量
size_t numSubSamples;
const uint8_t *key;
const uint8_t *iv;
// 数据源加密模式,默认为非加密源
CryptoPlugin::Mode mode = CryptoPlugin::kMode_Unencrypted;
// 注:允许在加密模式下使用更简单的queueInputBuffer API,方法是制造一个未加密的subSample
// We allow the simpler queueInputBuffer API to be used even in
// secure mode, by fabricating a single unencrypted subSample.
CryptoPlugin::SubSample ss;
// 加密模式
CryptoPlugin::Pattern pattern;
if (msg->findSize("size", &size)) {
// 查询到size字段存在时,即使size输入数据大小为0,即如前面的分析
if (hasCryptoOrDescrambler()) {
// 有加密或解密器时,不关注
ss.mNumBytesOfClearData = size;
ss.mNumBytesOfEncryptedData = 0;
subSamples = &ss;
numSubSamples = 1;
key = NULL;
iv = NULL;
pattern.mEncryptBlocks = 0;
pattern.mSkipBlocks = 0;
}
} else {
// 不存在“size”字段时
// 备注:暂不关注该流程,通常执行流程为上面if中流程
// 必须为非加密数据源
if (!hasCryptoOrDescrambler()) {
ALOGE("[%s] queuing secure buffer without mCrypto or mDescrambler!",
mComponentName.c_str());
return -EINVAL;
}
// 获取传递的参数
CHECK(msg->findPointer("subSamples", (void **)&subSamples));
CHECK(msg->findSize("numSubSamples", &numSubSamples));
CHECK(msg->findPointer("key", (void **)&key));
CHECK(msg->findPointer("iv", (void **)&iv));
CHECK(msg->findInt32("encryptBlocks", (int32_t *)&pattern.mEncryptBlocks));
CHECK(msg->findInt32("skipBlocks", (int32_t *)&pattern.mSkipBlocks));
int32_t tmp;
CHECK(msg->findInt32("mode", &tmp));
mode = (CryptoPlugin::Mode)tmp;
size = 0;
for (size_t i = 0; i < numSubSamples; ++i) {
size += subSamples[i].mNumBytesOfClearData;
size += subSamples[i].mNumBytesOfEncryptedData;
}
}
// 检查输入Buffer index索引有效性,无效则返回[RANGE]范围越界错误码
if (index >= mPortBuffers[kPortIndexInput].size()) {
return -ERANGE;
}
// 获取index对应输入Buffer
BufferInfo *info = &mPortBuffers[kPortIndexInput][index];
if (info->mData == nullptr || !info->mOwnedByClient) {
// 实际负载数据不存在或该Buffer不属于Client端时,将返回[ACCES]访问(权限)错误码
return -EACCES;
}
// 有实际负载数据时
// 检查偏移量加Buffer大小是否大于实际负载Buffer当前负载数据容量
if (offset + size > info->mData->capacity()) {
// 大于时,即表示数据有误
if ( ((int)size < 0) && !(flags & BUFFER_FLAG_EOS)) {
// size小于0并且该buffer的标志位flags不包含EOS时
// 也就是说虽然该size小于0,但该Buffer并没有发生EOS的情况时,
// 其实际也要处理为EOS事件,将重置size为0
size = 0;
ALOGD("EOS, reset size to zero");
} else {
// 否则将返回无效操作错误码
return -EINVAL;
}
}
// 设置当前输入Buffer的实际负载数据访问
info->mData->setRange(offset, size);
// 设置输入Buffer的PTS时间戳
info->mData->meta()->setInt64("timeUs", timeUs);
if (flags & BUFFER_FLAG_EOS) {
// 若当前输入Buffer为EOS数据时,设置eos字段为true
info->mData->meta()->setInt32("eos", true);
}
if (flags & BUFFER_FLAG_CODECCONFIG) {
// 当前输入Buffer为CSD数据时,设置编解码器特殊配置数据标志位为true
info->mData->meta()->setInt32("csd", true);
}
// 实际负载数据Buffer
sp<MediaCodecBuffer> buffer = info->mData;
status_t err = OK;
if (hasCryptoOrDescrambler()) {
// 加密数据源时,不关注
AString *errorDetailMsg;
CHECK(msg->findPointer("errorDetailMsg", (void **)&errorDetailMsg));
err = mBufferChannel->queueSecureInputBuffer(
buffer,
(mFlags & kFlagIsSecure),
key,
iv,
mode,
pattern,
subSamples,
numSubSamples,
errorDetailMsg);
} else {
// 非加密数据源时
// ACodec Buffer通道对象执行入队列已填充数据的输入Buffer
// 见1.1小节分析
err = mBufferChannel->queueInputBuffer(buffer);
}
if (err == OK) {
// 加锁和getBufferAndFormat执行流程同步
// synchronization boundary for getBufferAndFormat
Mutex::Autolock al(mBufferLock);
// 设置为false,即当前Buffer使用权将不属于Client端了,而属于Codec端
info->mOwnedByClient = false;
// 清除实际负载数据Buffer
info->mData.clear();
// 统计已发送给Codec的Buffer
// 见1.2小节分析
statsBufferSent(timeUs);
}
return err;
}
1.1、mBufferChannel->queueInputBuffer(buffer)实现分析:
ACodec Buffer通道对象执行入队列已填充数据的输入Buffer
// [frameworks/av/media/libstagefright/MediaCodec.cpp]
status_t ACodecBufferChannel::queueInputBuffer(const sp<MediaCodecBuffer> &buffer) {
// mDealer对象只会在加密数据源时创建的,因此非加密源时为null,否则失败
if (mDealer != nullptr) {
return -ENOSYS;
}
// 获取输入端口队列
std::shared_ptr<const std::vector<const BufferInfo>> array(
std::atomic_load(&mInputBuffers));
// 获取当前Buffer在输入端口队列中的item访问迭代器
// 见前面流程中已有分析
BufferInfoIterator it = findClientBuffer(array, buffer);
if (it == array->end()) {
// 匹配失败时
return -ENOENT;
}
ALOGV("queueInputBuffer #%d", it->mBufferId);
// 关于【mInputBufferFilled】该事件消息就是早前configure过程中
// 或在前面流程中已有提到过的,对应ACodec处理的【kWhatInputBufferFilled】事件消息。
// 见下面分析【注意该事件消息处理和当前方法返回后是异步同时执行的】
sp<AMessage> msg = mInputBufferFilled->dup();
// 设置当前输入Buffer及其Buffer id
msg->setObject("buffer", it->mCodecBuffer);
msg->setInt32("buffer-id", it->mBufferId);
msg->post();
// 返回成功
return OK;
}
ACodec接收【kWhatInputBufferFilled】已填充输入Buffer事件消息处理流程:
// [frameworks/av/media/libstagefright/ACodec.cpp]
bool ACodec::BaseState::onMessageReceived(const sp<AMessage> &msg) {
switch (msg->what()) {
case kWhatInputBufferFilled:
{
// 已填充输入Buffer事件处理流程
// 见下面分析
onInputBufferFilled(msg);
break;
}
}
}
onInputBufferFilled(msg)实现分析:
已填充输入Buffer事件处理流程
// [frameworks/av/media/libstagefright/ACodec.cpp]
void ACodec::BaseState::onInputBufferFilled(const sp<AMessage> &msg) {
IOMX::buffer_id bufferID;
// 获取已填充输入Buffer id
CHECK(msg->findInt32("buffer-id", (int32_t*)&bufferID));
sp<MediaCodecBuffer> buffer;
int32_t err = OK;
bool eos = false;
// 获取(输入Buffer)端口模式类型
// 备注:它的实现很简单因此直接给出结果,该方法是有不同状态机实现者可覆写的,
// 因此由于当前状态机状态为ExecutingState,因此调用正在执行状态实现者的该方法,
// 其方法为空实现,始终返回【RESUBMIT_BUFFERS】即重递交Buffer,也就是递交已填充输入Buffer的意思。
PortMode mode = getPortMode(kPortIndexInput);
int32_t discarded = 0;
// 是否标记丢弃该Buffer
// 备注:此处可知【kWhatInputBufferFilled】该事件处理流程其实也会在前面分析流程中
// MediaCodec状态不对时直接丢弃Codec对输入Buffer的请求。
if (msg->findInt32("discarded", &discarded) && discarded) {
// these are unfilled buffers returned by client
// buffers are returned on MediaCodec.flush
// 更改上面的该值为【KEEP_BUFFERS】即保持Buffer,也就是Codec继续保持该输入Buffer
mode = KEEP_BUFFERS;
}
sp<RefBase> obj;
// 获取实际负载数据Buffer
CHECK(msg->findObject("buffer", &obj));
// 并强转
buffer = static_cast<MediaCodecBuffer *>(obj.get());
int32_t tmp;
// Buffer负载不为空时,获取该Buffer是否为eos状态
if (buffer != NULL && buffer->meta()->findInt32("eos", &tmp) && tmp) {
// 为EOS状态Buffer时
// 标记eos和EOS状态错误码
eos = true;
err = ERROR_END_OF_STREAM;
}
// 根据Buffer ID来查询对应Buffer
// 见1.1.1小节分析
BufferInfo *info = mCodec->findBufferByID(kPortIndexInput, bufferID);
// 获取BufferInfo的状态类型
// 其实际就是此前分析过的,获取的是Buffer当前拥有权在哪个模块,如OWNED_BY_US、OWNED_BY_COMPONENT、OWNED_BY_UPSTREAM
BufferInfo::Status status = BufferInfo::getSafeStatus(info);
// 有前面流程分析可知,在递交该Buffer给Codec时,此前该Buffer使用权在Client端即必须为【OWNED_BY_UPSTREAM】状态,
// 该状态表示输入流端拥有权。否则失败处理
if (status != BufferInfo::OWNED_BY_UPSTREAM) {
// 状态错误时
ALOGE("Wrong ownership in IBF: %s(%d) buffer #%u", _asString(status), status, bufferID);
// 该方法其实际只是dump打印当前输入端口队列所有Buffer信息
mCodec->dumpBuffers(kPortIndexInput);
// 通知MediaCodec该转移Buffer失败错误码,见此前已有分析
mCodec->signalError(OMX_ErrorUndefined, FAILED_TRANSACTION);
return;
}
// 递交Buffer给ACodec时,将会把当前Buffer ownership更改为【OWNED_BY_US】
// 备注:由此可见【OWNED_BY_US】使用权状态指的是ACodec模块使用的
info->mStatus = BufferInfo::OWNED_BY_US;
// 转移实际负载数据Buffer到当前输入Buffer中缓存
info->mData = buffer;
// 输入Buffer)端口模式类型
switch (mode) {
// 保持Buffer模式类型处理流程
case KEEP_BUFFERS:
{
if (eos) {
// eos状态Buffer时
if (!mCodec->mPortEOS[kPortIndexInput]) {
// 此前未标记输入端口EOS状态时
// 则标记它为true
mCodec->mPortEOS[kPortIndexInput] = true;
// 并标记输入EOS结果状态为当前错误码类型
mCodec->mInputEOSResult = err;
}
}
break;
}
// 重递交Buffer,即递交已填充输入Buffer
case RESUBMIT_BUFFERS:
{
if (buffer != NULL && !mCodec->mPortEOS[kPortIndexInput]) {
// 实际负载Buffer不为空并且当前端口未EOS状态时
// 备注:此处为什么需要判断当前端口是否EOS状态,原因在于最后EOS状态的Buffer可能不止一个,
// 应该可能在前一个有错误的EOS状态Buffer改变了该状态信息,导致后续没错误的EOS状态Buffer得不到处理。
// Do not send empty input buffer w/o EOS to the component.
// 检查实际负载数据大小及其Buffer EOS状态
if (buffer->size() == 0 && !eos) {
// 大小为0并且非EOS状态时,再次重新执行【请求客户端去填充
// (待解码或待编码的原始音频或视频)数据】的处理流程
// 见前面已有分析
postFillThisBuffer(info);
// 退出
break;
}
int64_t timeUs;
// 获取当前帧Buffer的PTS显示时间戳
CHECK(buffer->meta()->findInt64("timeUs", &timeUs));
// Buffer标记,默认为帧尾标记
OMX_U32 flags = OMX_BUFFERFLAG_ENDOFFRAME;
int32_t isCSD = 0;
if (buffer->meta()->findInt32("csd", &isCSD) && isCSD != 0) {
// isCSD不为0,当前Buffer为CSD负载数据时【有前面流程可知,CSD数据也会单独提交给编解码器的】
if (mCodec->mIsLegacyVP9Decoder) {
// 若当前为遗留VP9视频编码格式解码器时,直接丢弃CSD数据,重
// 新请求下一个Buffer,也就是VP9不支持CSD数据
ALOGV("[%s] is legacy VP9 decoder. Ignore %u codec specific data",
mCodec->mComponentName.c_str(), bufferID);
postFillThisBuffer(info);
break;
}
// 其实视频编码格式时
// 添加Codec config标记位,即编解码器特殊数据配置信息的意思
flags |= OMX_BUFFERFLAG_CODECCONFIG;
}
if (eos) {
// eos时,添加EOS标记位
flags |= OMX_BUFFERFLAG_EOS;
}
// 获取当前实际负载数据Buffer的大小和数据有效可读偏移量
size_t size = buffer->size();
size_t offset = buffer->offset();
if (buffer->base() != info->mCodecData->base()) {
// 实际负载数据访问指针(内存地址)和当前通过ID匹配到的输入Buffer的数据访问内存地址不同时
// 即也就是说他们两存储数据的内存空间当前不是同一个分配的内存空间时
// 需要拷贝实际负载输入数据
ALOGV("[%s] Needs to copy input data for buffer %u. (%p != %p)",
mCodec->mComponentName.c_str(),
bufferID,
buffer->base(), info->mCodecData->base());
// 获取输入端口是否有数据转换器
// 备注:关于数据转换器此前流程中阐述过很多次了,并且对它的处理就是将不同单位数据进行转换而已,
// 可自行查看其实际实现。一种比较理想的状态是输入数据单位相同则不需要转换器【当然此处的CSD数据还是需要转换器】。
sp<DataConverter> converter = mCodec->mConverter[kPortIndexInput];
if (converter == NULL || isCSD) {
// 无转换器或当前为CSD数据时,获取一个拷贝数据转换器
converter = getCopyConverter();
}
// 将目标【buffer】对象中的实际负载数据转换到【info->mCodecData】数据承载对象中 TODO
status_t err = converter->convert(buffer, info->mCodecData);
if (err != OK) {
// 转换失败则通知MediaCodec该错误
mCodec->signalError(OMX_ErrorUndefined, err);
return;
}
// 转换成功时,获取转换后数据单位的数据大小
size = info->mCodecData->size();
} else {
// 数据单位相同时的理想状态即不需要数据转换器时
// 直接设置数据的有效访问范围,而不需要再次设置负载数据Buffer给它,
// 原理就是他们两本来就是同一个数据访问内存地址呀,干嘛还重新分配。。。
info->mCodecData->setRange(offset, size);
}
if (flags & OMX_BUFFERFLAG_CODECCONFIG) {
// 若是CSD数据时
ALOGV("[%s] calling emptyBuffer %u w/ codec specific data",
mCodec->mComponentName.c_str(), bufferID);
} else if (flags & OMX_BUFFERFLAG_EOS) {
// 若是EOS数据时
ALOGV("[%s] calling emptyBuffer %u w/ EOS",
mCodec->mComponentName.c_str(), bufferID);
} else {
#if TRACK_BUFFER_TIMING
// 默认该追踪Buffer时间标记宏定义为0,也就是说这里的处理不同在于log级别,
// 若定义为true则会默认打印log否则不打印。也就是你可以追踪Buffer帧PTS时间的意思。
// 即TRACK_BUFFER_TIMING只是个debug标记位而已
ALOGI("[%s] calling emptyBuffer %u w/ time %lld us",
mCodec->mComponentName.c_str(), bufferID, (long long)timeUs);
#else
ALOGV("[%s] calling emptyBuffer %u w/ time %lld us",
mCodec->mComponentName.c_str(), bufferID, (long long)timeUs);
#endif
}
#if TRACK_BUFFER_TIMING
// 此处也只是记录debug数据,不关注
ACodec::BufferStats stats;
stats.mEmptyBufferTimeUs = ALooper::GetNowUs();
stats.mFillBufferDoneTimeUs = -1ll;
mCodec->mBufferStats.add(timeUs, stats);
#endif
// 该方法前面分析过
if (mCodec->storingMetadataInDecodedBuffers()) {
// 支持存储元数据在解码缓冲区时
// 尝试为每一个输入Buffer递交一个输出Buffer
// try to submit an output buffer for each input buffer
// 获取输入端口模式类型
// 上面已分析过,ExecutingState状态时该方法为空实现,始终返回【RESUBMIT_BUFFERS】即重递交Buffer
PortMode outputMode = getPortMode(kPortIndexOutput);
ALOGV("MetadataBuffersToSubmit=%u portMode=%s",
mCodec->mMetadataBuffersToSubmit,
(outputMode == FREE_BUFFERS ? "FREE" :
outputMode == KEEP_BUFFERS ? "KEEP" : "RESUBMIT"));
if (outputMode == RESUBMIT_BUFFERS) {
// 重递交Buffer模式时
// 递交输出元数据Buffer给底层Codec去填充其输出数据
// 【主要就是从Surface中获取其(生产者端)缓冲区Buffer,让Codec组件放入数据后,Surface可以显示】
// 备注:该方法见此前已有分析,最终会执行【mOMXNode->fillBuffer】
// 并且在递交Buffer给Codec组件后,将该Buffer状态使用权修改为【OWNED_BY_COMPONENT】。
// 其处理流程请见在【Part 7】部分的fillBuffer分析底层组件工作原理阐述
mCodec->submitOutputMetadataBuffer();
}
}
// 只是个debug方法
info->checkReadFence("onInputBufferFilled");
status_t err2 = OK;
// 输入端口队列Buffer模式类型,也就是和底层组件交互使用的Buffer负载数据类型
switch (mCodec->mPortMode[kPortIndexInput]) {
case IOMX::kPortModePresetByteBuffer:
case IOMX::kPortModePresetANWBuffer:
case IOMX::kPortModePresetSecureBuffer:
// 这三种时
{
// 执行OMXNode请求清空(消耗)当前输入Buffer数据
// 见1.1.2小节分析
err2 = mCodec->mOMXNode->emptyBuffer(
bufferID, info->mCodecData, flags, timeUs, info->mFenceFd);
}
break;
#ifndef OMX_ANDROID_COMPILE_AS_32BIT_ON_64BIT_PLATFORMS
// 该宏定义此前有分析到过,根据名称就可以知道它的作用为:安卓程序编译为32位运行在64位平台上。
// 因此在我们现在的机器上通常不再使用它,因为32位速度慢呀。
case IOMX::kPortModeDynamicNativeHandle:
if (info->mCodecData->size() >= sizeof(VideoNativeHandleMetadata)) {
// 数据量大于等于【VideoNativeHandleMetadata】结构数据size时
// 强转负载数据的对象
VideoNativeHandleMetadata *vnhmd =
(VideoNativeHandleMetadata*)info->mCodecData->base();
// 然后利用它的数据访问句柄指针来创建native访问句柄指针,暂不关注它
sp<NativeHandle> handle = NativeHandle::create(
vnhmd->pHandle, false /* ownsHandle */);
// 然后执行OMXNode请求清空(消耗)当前输入Buffer数据
// 备注:其实际这个处理也相当于是将负载数据对象类型转换为另一种类型后才给到组件使用
err2 = mCodec->mOMXNode->emptyBuffer(
bufferID, handle, flags, timeUs, info->mFenceFd);
}
break;
case IOMX::kPortModeDynamicANWBuffer:
// 和上一个处理类似的,不关注
if (info->mCodecData->size() >= sizeof(VideoNativeMetadata)) {
VideoNativeMetadata *vnmd = (VideoNativeMetadata*)info->mCodecData->base();
// 将数据负载类型转换给GraphicBuffer类型
sp<GraphicBuffer> graphicBuffer = GraphicBuffer::from(vnmd->pBuffer);
err2 = mCodec->mOMXNode->emptyBuffer(
bufferID, graphicBuffer, flags, timeUs, info->mFenceFd);
}
break;
#endif
default:
// 其他Buffer模式类型错误,返回无法支持错误码
ALOGW("Can't marshall %s data in %zu sized buffers in %zu-bit mode",
asString(mCodec->mPortMode[kPortIndexInput]),
info->mCodecData->size(),
sizeof(buffer_handle_t) * 8);
err2 = ERROR_UNSUPPORTED;
break;
}
// 递交Buffer完成后
// 将当前fence描述符置为-1
info->mFenceFd = -1;
if (err2 != OK) {
// 递交Buffer调用过程失败,通知上层MediaCodec该错误
mCodec->signalError(OMX_ErrorUndefined, makeNoSideEffectStatus(err2));
return;
}
// 递交Buffer成功后
// 修改当前输入Buffer使用权在底层组件【OWNED_BY_COMPONENT】
info->mStatus = BufferInfo::OWNED_BY_COMPONENT;
// 注:在组件使用该Buffer时保持该引用
// Hold the reference while component is using the buffer.
// 缓存实际Buffer负载数据指针
info->mData = buffer;
if (!eos && err == OK) {
// 当前Buffer非EOS状态并且无错误时
// 可能的话尝试获取更多输入Buffer
// 见1.1.3小节分析
getMoreInputDataIfPossible();
} else {
// 有错误发生时,通知输入端口EOS状态
ALOGV("[%s] Signalled EOS (%d) on the input port",
mCodec->mComponentName.c_str(), err);
mCodec->mPortEOS[kPortIndexInput] = true;
mCodec->mInputEOSResult = err;
}
} else if (!mCodec->mPortEOS[kPortIndexInput]) {
// 没有输入Buffer时,若没有发送EOS时,进入此处
if (err != OK && err != ERROR_END_OF_STREAM) {
// 有错误发生,但不是EOS时,也会通知输入端口EOS
ALOGV("[%s] Signalling EOS on the input port due to error %d",
mCodec->mComponentName.c_str(), err);
} else {
// EOS
ALOGV("[%s] Signalling EOS on the input port",
mCodec->mComponentName.c_str());
}
// 还是需要调用【emptyBuffer 】来通知EOS事件,
// 只是此时传递的Buffer数据为一个预设空数据而已,只是起到EOS通知作用
ALOGV("[%s] calling emptyBuffer %u signalling EOS",
mCodec->mComponentName.c_str(), bufferID);
info->checkReadFence("onInputBufferFilled");
// 通知底层组件EOS
status_t err2 = mCodec->mOMXNode->emptyBuffer(
bufferID, OMXBuffer::sPreset, OMX_BUFFERFLAG_EOS, 0, info->mFenceFd);
// 重置-1
info->mFenceFd = -1;
if (err2 != OK) {
// 通知底层组件EOS也失败时,通知MediaCodec该错误
mCodec->signalError(OMX_ErrorUndefined, makeNoSideEffectStatus(err2));
return;
}
// 通知底层组件EOS成功时
// 修改当前输入Buffer状态为OWNED_BY_COMPONENT
info->mStatus = BufferInfo::OWNED_BY_COMPONENT;
// 标记EOS状态
mCodec->mPortEOS[kPortIndexInput] = true;
mCodec->mInputEOSResult = err;
}
break;
}
// 释放Buffer空实现
case FREE_BUFFERS:
break;
default:
// 其他端口模式类型,无效
ALOGE("invalid port mode: %d", mode);
break;
}
}
1.1.1、mCodec->findBufferByID(kPortIndexInput, bufferID)实现分析:
根据Buffer ID来查询对应Buffer,返回其对象指针,第三个参数index默认为NULL
由于本章节接下来内容篇幅过长,因此必须放入另一章节分析,请查看:
【六】Android MediaPlayer整体架构源码分析 -【start请求播放处理流程】【Part 8】【02】