承接上一章节分析:【六】Android MediaPlayer整体架构源码分析 -【start请求播放处理流程】【Part 12】
本系列文章分析的安卓源码版本:【Android 10.0 版本】
推荐涉及到的知识点:
Binder机制实现原理:Android C++底层Binder通信机制原理分析总结【通俗易懂】
ALooper机制实现原理:Android native层媒体通信架构AHandler/ALooper机制实现源码分析
Binder异常关闭监听:Android native层DeathRecipient对关联进程(如相关Service服务进程)异常关闭通知事件的监听实现源码分析
【此章节小节编号将重新排序】
已消耗该Buffer完成通知事件消息接收处理:
NuPlayerDecoder接收的【kWhatRenderBuffer】已渲染Buffer完成事件应答消息
备注:此时此处的已渲染Buffer指的是音频Buffer,而当该流程处理视频Buffer时视频帧却还未真正渲染,因此此流程就是处理视频帧渲染流程。
因此看此处处理逻辑时,建议先看完上面音频处理流程和后面【4.2】小节的视频消耗渲染处理流程后,再来统一看此处处理。
// [frameworks/av/media/libmediaplayerservice/nuplayer/NuPlayerDecoder.cpp]
void NuPlayer::Decoder::onMessageReceived(const sp<AMessage> &msg) {
ALOGV("[%s] onMessage: %s", mComponentName.c_str(), msg->debugString().c_str());
switch (msg->what()) {
case kWhatRenderBuffer:
{
if (!isStaleReply(msg)) {
// 非Buffer队列旧消息应答时
// 回调渲染Buffer处理流程
// 见下面分析
onRenderBuffer(msg);
}
break;
}
}
}
isStaleReply(msg)实现:
判断是否为旧消息应答,true时为旧消息应答
其实现很简单,就是返回当前Buffer输出队列对应的代数值
备注:也就是每一次flush或者configure流程执行,都将更新该代数值,即该代数值标记着当前正在使用的输出队列
// [frameworks/av/media/libmediaplayerservice/nuplayer/NuPlayerDecoder.cpp]
bool NuPlayer::Decoder::isStaleReply(const sp<AMessage> &msg) {
int32_t generation;
CHECK(msg->findInt32("generation", &generation));
// 代数值不同表示为旧消息应答
return generation != mBufferGeneration;
}
onRenderBuffer(msg)实现分析:
回调渲染Buffer处理流程
// [frameworks/av/media/libmediaplayerservice/nuplayer/NuPlayerDecoder.cpp]
void NuPlayer::Decoder::onRenderBuffer(const sp<AMessage> &msg) {
status_t err;
int32_t render;
size_t bufferIx;
int32_t eos;
size_t size;
// 获取当前输出buffer index索引
CHECK(msg->findSize("buffer-ix", &bufferIx));
if (!mIsAudio) {
// 非音频解码器即为视频解码器时
int64_t timeUs;
// 获取输出队列中对应buffer 和 其PTS帧显示时间戳
sp<MediaCodecBuffer> buffer = mOutputBuffers[bufferIx];
buffer->meta()->findInt64("timeUs", &timeUs);
// 字幕流解码器处理,判断显示合适字幕
// 字幕解码器暂不分析
if (mCCDecoder != NULL && mCCDecoder->isSelected()) {
mCCDecoder->display(timeUs);
}
}
if (mCodec == NULL) {
// Codec未初始化错误
err = NO_INIT;
} else if (msg->findInt32("render", &render) && render) {
// 获取该视频帧是否渲染标志位,当为1即true时需要渲染视频帧
int64_t timestampNs;
// 获取【该视频帧使用(系统)真实时间戳表示的PTS】,单位纳秒
CHECK(msg->findInt64("timestampNs", ×tampNs));
// 请求渲染该帧视频输出Buffer并释放它
// 见第1小节分析
err = mCodec->renderOutputBufferAndRelease(bufferIx, timestampNs);
} else {
// 其它情况即音频或视频帧Buffer时
// 视频走此处表示该视频帧将不会被渲染
if (!msg->findInt32("eos", &eos) || !eos ||
!msg->findSize("size", &size) || size) {
// 非eos 或 无"eos"字段 或 无"size"字段 或 buffer有负载大小时 进入
// 被丢弃的视频帧总数目递增【只记录视频丢弃帧数】
// 备注:若为音频软解码器那么该值将不会增加,默认为0
mNumOutputFramesDropped += !mIsAudio;
}
// 释放音频或视频帧输出Buffer
// 见第2小节分析
err = mCodec->releaseOutputBuffer(bufferIx);
}
if (err != OK) {
// 执行失败
ALOGE("failed to release output buffer for [%s] (err=%d)",
mComponentName.c_str(), err);
// 通知该失败事件,见此前已有分析
handleError(err);
}
if (msg->findInt32("eos", &eos) && eos
&& isDiscontinuityPending()) {
// eos状态的buffer,并且当音频或视频输出格式发生改变时,进入
// 完成处理非连续帧
// 备注:目前暂不分析该流程 TODO
// 简单说下,它可能将会flush并且通知NuPlayer【kWhatInputDiscontinuity】该事件去关闭解码器或重新扫描源
finishHandleDiscontinuity(true /* flushOnTimeChange */);
}
}
1、mCodec->renderOutputBufferAndRelease(bufferIx, timestampNs)实现分析:
请求渲染该帧视频输出Buffer并释放它
// [frameworks/av/media/libstagefright/MediaCodec.cpp]
status_t MediaCodec::renderOutputBufferAndRelease(size_t index, int64_t timestampNs) {
// 发送【kWhatReleaseOutputBuffer】释放输出buffer事件消息,并携带三个参数
sp<AMessage> msg = new AMessage(kWhatReleaseOutputBuffer, this);
// 输出buffer索引
msg->setSize("index", index);
// 要求渲染该帧buffer
msg->setInt32("render", true);
// 渲染该输出buffer的系统真实时间戳
msg->setInt64("timestampNs", timestampNs);
sp<AMessage> response;
// 同步发送并等待响应
return PostAndAwaitResponse(msg, &response);
}
MediaCodec接收【kWhatReleaseOutputBuffer】释放输出buffer事件消息处理:
备注:由后面第2小节分析可知,该流程处理实际上是音视频共同处理流程,只是携带不同参数去区别处理。
// [frameworks/av/media/libstagefright/MediaCodec.cpp]
void MediaCodec::onMessageReceived(const sp<AMessage> &msg) {
switch (msg->what()) {
case kWhatReleaseOutputBuffer:
{
// 应答消息
sp<AReplyToken> replyID;
CHECK(msg->senderAwaitsResponse(&replyID));
if (!isExecuting()) {
// MediaClock未执行状态中时,应答无效错误码
PostReplyWithError(replyID, INVALID_OPERATION);
break;
} else if (mFlags & kFlagStickyError) {
// 粘性错误时 应答该错误
PostReplyWithError(replyID, getStickyError());
break;
}
// 回调执行释放输出buffer处理流程
status_t err = onReleaseOutputBuffer(msg);
// 应答请求端并返回错误码
PostReplyWithError(replyID, err);
break;
}
}
}
onReleaseOutputBuffer(msg)实现分析:
回调执行释放(音频或视频)输出buffer处理流程
// [frameworks/av/media/libstagefright/MediaCodec.cpp]
status_t MediaCodec::onReleaseOutputBuffer(const sp<AMessage> &msg) {
size_t index;
// 获取输出buffer索引
CHECK(msg->findSize("index", &index));
int32_t render;
// 渲染标志位,未设置时为0即false,不渲染处理
if (!msg->findInt32("render", &render)) {
render = 0;
}
if (!isExecuting()) {
// MediaClock未执行状态中时,返回无效错误码
return -EINVAL;
}
if (index >= mPortBuffers[kPortIndexOutput].size()) {
// 索引无效 越界错误码
return -ERANGE;
}
// index对应的输出Buffer
BufferInfo *info = &mPortBuffers[kPortIndexOutput][index];
if (info->mData == nullptr || !info->mOwnedByClient) {
// 实际负载buffer为空 或 当前输出buffer拥有权不在Client端 时
// 返回访问权限错误码
return -EACCES;
}
// 输出端口Buffer锁加锁代码块,同步边界
// synchronization boundary for getBufferAndFormat
sp<MediaCodecBuffer> buffer;
{
Mutex::Autolock al(mBufferLock);
// 更改为false,即Client递交该Buffer给MediaCodec访问处理
info->mOwnedByClient = false;
// 实际负载(音频或视频)数据
buffer = info->mData;
// 清除原来队列中的Buffer携带的实际负载数据对象智能指针,并指针计数减1
info->mData.clear();
}
if (render && buffer->size() != 0) {
// 需要渲染(视频帧) 并且 (视频帧)实际负载数据不为0时
int64_t mediaTimeUs = -1;
// 获取当前视频帧PTS媒体显示时间
buffer->meta()->findInt64("timeUs", &mediaTimeUs);
int64_t renderTimeNs = 0;
// 获取【该视频帧使用(系统)真实时间戳表示的PTS】,单位纳秒
if (!msg->findInt64("timestampNs", &renderTimeNs)) {
// 无该字段时
// 如果客户端没有请求特定的显示时间戳,则使用PTS媒体时间戳
// use media timestamp if client did not request a specific render timestamp
ALOGV("using buffer PTS of %lld", (long long)mediaTimeUs);
renderTimeNs = mediaTimeUs * 1000;
}
if (mSoftRenderer != NULL) {
// 软渲染器存在时,进行视频帧渲染处理
// 由此前章节可知通常软视频解码器时将会创建它
// 备注:关于该软渲染器不展开分析,只阐述下它的功能原理作用,
// 就是在软解码器时进行render渲染请求时,
// 根据色彩空间类型对YUV等数据的进行重新处理转换为Surface接收的数据,然后显示,
// 内部主要调用【mNativeWindow->dequeueBuffer】和【mNativeWindow->queueBuffer】来完成的。
std::list<FrameRenderTracker::Info> doneFrames = mSoftRenderer->render(
buffer->data(), buffer->size(), mediaTimeUs, renderTimeNs,
mPortBuffers[kPortIndexOutput].size(), buffer->format());
// 正在运行中时,通知已渲染帧,该通知消息默认未使用的,不关注
// if we are running, notify rendered frames
if (!doneFrames.empty() && mState == STARTED && mOnFrameRenderedNotification != NULL) {
sp<AMessage> notify = mOnFrameRenderedNotification->dup();
sp<AMessage> data = new AMessage;
if (CreateFramesRenderedMessage(doneFrames, data)) {
notify->setMessage("data", data);
notify->post();
}
}
}
// Buffer通道对象执行渲染输出Buffer处理
// 见1.1小节分析
mBufferChannel->renderOutputBuffer(buffer, renderTimeNs);
} else {
// 丢弃不需要渲染的(音频或视频)输出Buffer
// 见1.2小节分析
mBufferChannel->discardBuffer(buffer);
}
return OK;
}
1.1、mBufferChannel->renderOutputBuffer(buffer, renderTimeNs)实现分析:
Buffer通道对象执行(已)渲染输出Buffer处理
备注:由上面的软渲染器实现,我们必须需要知道的是,软渲染器实际上已经渲染了该帧数据,也就是说此处1.1小节中的处理也会包括两种流程处理,即若未使用软渲染器渲染过,则会在1.1中进行判断符合条件后渲染视频帧,否则将只会release它。
该条件则为实际负载数据大小是否为0,若已渲染则其数据可读大小应该为0了才对。
在说明一下Surface的渲染流程简单的说即为:
从【mNativeWindow->dequeueBuffer】中获取一个生产者输入图形buffer,将此处视频输出帧数据放进去,然后通过【mNativeWindow->queueBuffer】将其图形buffer放入Surface中进行渲染。
// [frameworks/av/media/libstagefright/ACodecBufferChannel.cpp]
status_t ACodecBufferChannel::renderOutputBuffer(
const sp<MediaCodecBuffer> &buffer, int64_t timestampNs) {
// 输出Buffer队列
std::shared_ptr<const std::vector<const BufferInfo>> array(
std::atomic_load(&mOutputBuffers));
// 获取该buffer的item访问迭代器,见此前已有分析
BufferInfoIterator it = findClientBuffer(array, buffer);
if (it == array->end()) {
// 未查询到
return -ENOENT;
}
ALOGV("renderOutputBuffer #%d", it->mBufferId);
// 由前面分析可知,mOutputBufferDrained对应ACodec处理的【kWhatOutputBufferDrained】已渲染输出帧事件消息
sp<AMessage> msg = mOutputBufferDrained->dup();
// 设置这些参数
msg->setObject("buffer", buffer);
msg->setInt32("buffer-id", it->mBufferId);
msg->setInt32("render", true);
msg->setInt64("timestampNs", timestampNs);
msg->post();
return OK;
}
ACodec接收处理【kWhatOutputBufferDrained】事件消息:
// [frameworks/av/media/libstagefright/ACodec.cpp]
bool ACodec::BaseState::onMessageReceived(const sp<AMessage> &msg) {
switch (msg->what()) {
case kWhatOutputBufferDrained:
{
onOutputBufferDrained(msg);
break;
}
}
}
onOutputBufferDrained(msg)实现分析:
// [frameworks/av/media/libstagefright/ACodec.cpp]
void ACodec::BaseState::onOutputBufferDrained(const sp<AMessage> &msg) {
IOMX::buffer_id bufferID;
// 获取输出Buffer id
CHECK(msg->findInt32("buffer-id", (int32_t*)&bufferID));
sp<RefBase> obj;
// 获取实际负载数据Buffer 并强转
CHECK(msg->findObject("buffer", &obj));
sp<MediaCodecBuffer> buffer = static_cast<MediaCodecBuffer *>(obj.get());
// 是否丢弃该帧 标志位
int32_t discarded = 0;
msg->findInt32("discarded", &discarded);
ssize_t index;
// 根据Buffer ID来查询对应Buffer,见此前已有分析
BufferInfo *info = mCodec->findBufferByID(kPortIndexOutput, bufferID, &index);
// 获取该输出Buffer的(拥有权)状态类型,见此前已有分析
BufferInfo::Status status = BufferInfo::getSafeStatus(info);
// 该状态表示输出流端拥有权。否则失败处理
if (status != BufferInfo::OWNED_BY_DOWNSTREAM) {
// 拥有权状态错误时
ALOGE("Wrong ownership in OBD: %s(%d) buffer #%u", _asString(status), status, bufferID);
// 该方法其实际只是dump打印当前输入端口队列所有Buffer信息
mCodec->dumpBuffers(kPortIndexOutput);
// 通知MediaCodec该转移Buffer失败错误码,见此前已有分析
mCodec->signalError(OMX_ErrorUndefined, FAILED_TRANSACTION);
return;
}
int64_t timeUs = -1;
// 获取该帧PTS
buffer->meta()->findInt64("timeUs", &timeUs);
// 判断是否跳过该帧,该方法在ACodec中是空实现,始终返回false。
bool skip = mCodec->getDSModeHint(msg, timeUs);
// 缓存实际负载数据
info->mData = buffer;
int32_t render;
if (!skip && mCodec->mNativeWindow != NULL
&& msg->findInt32("render", &render) && render != 0
&& !discarded && buffer->size() != 0) {
// 存在Surface 并且 渲染标识位为true 并且 非丢弃标识 并且 有效负载数据大小不为0 时
// 执行渲染处理流程
ATRACE_NAME("render");
// 客户端希望该Buffer被渲染显示
// The client wants this buffer to be rendered.
// 记录剪切尺寸:左上右下
android_native_rect_t crop;
if (buffer->format()->findRect("crop", &crop.left, &crop.top, &crop.right, &crop.bottom)) {
// 获取剪切尺寸:左上右下成功时
// NOTE: Surface使用扩展的右下坐标,因此将其都加1
// NOTE: native window uses extended right-bottom coordinate
++crop.right;
++crop.bottom;
// 比较当前全局缓存的最后一次Surface的图像剪切尺寸的字节内存是否一致
if (memcmp(&crop, &mCodec->mLastNativeWindowCrop, sizeof(crop)) != 0) {
// 不一样时,更新它
mCodec->mLastNativeWindowCrop = crop;
// 并设置给Surface更新
status_t err = native_window_set_crop(mCodec->mNativeWindow.get(), &crop);
ALOGW_IF(err != NO_ERROR, "failed to set crop: %d", err);
}
}
int32_t dataSpace;
// 色彩空间模式类型
if (buffer->format()->findInt32("android._dataspace", &dataSpace)
&& dataSpace != mCodec->mLastNativeWindowDataSpace) {
// 不相同时,设置给Surface更新
status_t err = native_window_set_buffers_data_space(
mCodec->mNativeWindow.get(), (android_dataspace)dataSpace);
// 缓存更新
mCodec->mLastNativeWindowDataSpace = dataSpace;
ALOGW_IF(err != NO_ERROR, "failed to set dataspace: %d", err);
}
if (buffer->format()->contains("hdr-static-info")) {
// 包含HDR静态信息字段时
HDRStaticInfo info;
// 获取HDR静态信息,见此前已有分析
if (ColorUtils::getHDRStaticInfoFromFormat(buffer->format(), &info)
&& memcmp(&mCodec->mLastHDRStaticInfo, &info, sizeof(info))) {
// 获取成功并不一样时,更新它
// 设置给Surface,见此前已有分析
setNativeWindowHdrMetadata(mCodec->mNativeWindow.get(), &info);
mCodec->mLastHDRStaticInfo = info;
}
}
sp<ABuffer> hdr10PlusInfo;
if (buffer->format()->findBuffer("hdr10-plus-info", &hdr10PlusInfo)
&& hdr10PlusInfo != nullptr && hdr10PlusInfo->size() > 0
&& hdr10PlusInfo != mCodec->mLastHdr10PlusBuffer) {
// 获取HDR增强版本信息成功、有效且不一样时
// 设置给Surface,见此前已有分析
native_window_set_buffers_hdr10_plus_metadata(mCodec->mNativeWindow.get(),
hdr10PlusInfo->size(), hdr10PlusInfo->data());
mCodec->mLastHdr10PlusBuffer = hdr10PlusInfo;
}
// 保存发送给Surface的Buffer,以便在返回时获得渲染时间
// save buffers sent to the surface so we can get render time when they return
int64_t mediaTimeUs = -1;
// 视频帧PTS
buffer->meta()->findInt64("timeUs", &mediaTimeUs);
if (mediaTimeUs >= 0) {
// PTS有效时
// 缓存记录该帧信息数据到视频帧渲染追踪器对象缓冲队列中,暂不展开,自行分析
mCodec->mRenderTracker.onFrameQueued(
mediaTimeUs, info->mGraphicBuffer, new Fence(::dup(info->mFenceFd)));
}
int64_t timestampNs = 0;
// 获取【该视频帧使用(系统)真实时间戳表示的PTS】,单位纳秒
if (!msg->findInt64("timestampNs", ×tampNs)) {
// 获取失败时
// 如果客户端没有请求特定的显示时间戳,则使用PTS媒体时间戳
// use media timestamp if client did not request a specific render timestamp
if (buffer->meta()->findInt64("timeUs", ×tampNs)) {
// 获取PTS媒体时间戳成功时
ALOGV("using buffer PTS of %lld", (long long)timestampNs);
timestampNs *= 1000;
}
}
status_t err;
// 设置当前Buffer真实待播放时间
err = native_window_set_buffers_timestamp(mCodec->mNativeWindow.get(), timestampNs);
ALOGW_IF(err != NO_ERROR, "failed to set buffer timestamp: %d", err);
// debug 方法不关注
info->checkReadFence("onOutputBufferDrained before queueBuffer");
// 将该事件负责数据图形Buffer放回Surface的数据生产者队列中,
// 也就是说【mGraphicBuffer】该buffer是此前流程输出缓冲区分配时从Surface的该数据生产者队列中获取来的,
// 因此填充完真正的视频帧实际负载数据后,将其交还给Surface去渲染该帧处理。
err = mCodec->mNativeWindow->queueBuffer(
mCodec->mNativeWindow.get(), info->mGraphicBuffer.get(), info->mFenceFd);
// 重置为-1
info->mFenceFd = -1;
if (err == OK) {
// 成功递交输出Buffer给Surface时
// 设置该输出Buffer使用拥有权在Surface模块
info->mStatus = BufferInfo::OWNED_BY_NATIVE_WINDOW;
} else {
// 失败时
ALOGE("queueBuffer failed in onOutputBufferDrained: %d", err);
// 通知该错误给MediaCodec,并标记其Buffer状态为US
mCodec->signalError(OMX_ErrorUndefined, makeNoSideEffectStatus(err));
info->mStatus = BufferInfo::OWNED_BY_US;
// keeping read fence as write fence to avoid clobbering
// false,保持读fence为写fence,以避免冲突
info->mIsReadFence = false;
}
} else {
// 其他情况,不需要渲染时,此时可能为音频或视频Buffer
if (mCodec->mNativeWindow != NULL && (discarded || buffer->size() != 0)) {
// Surface存在 并且 【标记为丢弃帧或buffer实际负载大小不为0】 时
// 将读fence移到写fence,以避免冲突
// move read fence into write fence to avoid clobbering
info->mIsReadFence = false;
ATRACE_NAME("frame-drop");
}
// 标记其Buffer状态为US
info->mStatus = BufferInfo::OWNED_BY_US;
}
// 获取队列端口模式,见此前分析可知,在正常工作状态ExecutingState中,值为【RESUBMIT_BUFFERS】
PortMode mode = getPortMode(kPortIndexOutput);
switch (mode) {
case KEEP_BUFFERS:
{// 根据注释可知,这种情况不合理,因此做了容错处理
// XXX fishy, revisit!!! What about the FREE_BUFFERS case below?
if (info->mStatus == BufferInfo::OWNED_BY_NATIVE_WINDOW) {
// 若该Buffer已递交给Surface时
// 注:不能重新递交刚刚已渲染的Buffer,而是将空闲的Buffer出队列。
// We cannot resubmit the buffer we just rendered, dequeue
// the spare instead.
// 重新从Surface的数据生产者队列中获取新的空闲Buffer,见此前已有分析
info = mCodec->dequeueBufferFromNativeWindow();
}
break;
}
// 重递交Buffer模式
case RESUBMIT_BUFFERS:
{
if (!mCodec->mPortEOS[kPortIndexOutput]) {
// 输出端口队列未EOS时
if (info->mStatus == BufferInfo::OWNED_BY_NATIVE_WINDOW) {
// 若该Buffer已递交给Surface时
// 注:不能重新递交刚刚已渲染的Buffer,而是将空闲的Buffer出队列。
// We cannot resubmit the buffer we just rendered, dequeue
// the spare instead.
// 重新从Surface的数据生产者队列中获取新的空闲Buffer,见此前已有分析
info = mCodec->dequeueBufferFromNativeWindow();
}
if (info != NULL) {
// info不为空时
ALOGV("[%s] calling fillBuffer %u",
mCodec->mComponentName.c_str(), info->mBufferID);
info->checkWriteFence("onOutputBufferDrained::RESUBMIT_BUFFERS");
// 再次递交新的待填充的输出Buffer给底层组件去填充编解码数据,见此前已有分析
// 备注:如此输出Buffer队列也完成了一个循环处理。
status_t err = mCodec->fillBuffer(info);
if (err != OK) {
// 递交失败时,通知MediaCodec该事件
mCodec->signalError(OMX_ErrorUndefined, makeNoSideEffectStatus(err));
}
}
}
break;
}
case FREE_BUFFERS:
{
// 释放该Buffer
// 见下面分析
status_t err = mCodec->freeBuffer(kPortIndexOutput, index);
if (err != OK) {
// 失败时,通知MediaCodec该事件
mCodec->signalError(OMX_ErrorUndefined, makeNoSideEffectStatus(err));
}
break;
}
default:
ALOGE("Invalid port mode: %d", mode);
return;
}
}
mCodec->freeBuffer(kPortIndexOutput, index)实现分析:
释放该Buffer
// [frameworks/av/media/libstagefright/ACodec.cpp]
status_t ACodec::freeBuffer(OMX_U32 portIndex, size_t i) {
// 获取指定端口队列中指针索引的Buffer
BufferInfo *info = &mBuffers[portIndex].editItemAt(i);
status_t err = OK;
// there should not be any fences in the metadata
// 元数据中不应该有任何Fence信息
if (mPortMode[portIndex] == IOMX::kPortModeDynamicANWBuffer && info->mCodecData != NULL
&& info->mCodecData->size() >= sizeof(VideoNativeMetadata)) {
// 该元数据的fence有效时
int fenceFd = ((VideoNativeMetadata *)info->mCodecData->base())->nFenceFd;
if (fenceFd >= 0) {
ALOGW("unreleased fence (%d) in %s metadata buffer %zu",
fenceFd, portIndex == kPortIndexInput ? "input" : "output", i);
}
}
// 判断当前Buffer使用拥有权状态
switch (info->mStatus) {
case BufferInfo::OWNED_BY_US:
// ACodec自身持有时
if (portIndex == kPortIndexOutput && mNativeWindow != NULL) {
// 输出端口并且Surface不为空时
// 请求取消该Buffer,递还给Surface,见此前章节已有分析
(void)cancelBufferToNativeWindow(info);
}
FALLTHROUGH_INTENDED;
case BufferInfo::OWNED_BY_NATIVE_WINDOW:
// 若为Surface使用权时
// 备注:也就是说这时候的输出Buffer其实际已经交还给Surface了,
// 因此只需要请求底层组件对应端口队列释放其队列中该ID的buffer即可
// 请求底层组件释放在Buffer
// 见下面分析
err = mOMXNode->freeBuffer(portIndex, info->mBufferID);
break;
default:
// 其它状态则为错误
ALOGE("trying to free buffer not owned by us or ANW (%d)", info->mStatus);
err = FAILED_TRANSACTION;
break;
}
if (info->mFenceFd >= 0) {
// fence大于0时直接关闭该文件描述符
::close(info->mFenceFd);
}
if (portIndex == kPortIndexOutput) {
// 输出端口时
// 视频帧渲染追踪器将取消追踪该buffer帧信息
mRenderTracker.untrackFrame(info->mRenderInfo, i);
info->mRenderInfo = NULL;
}
// remove buffer even if mOMXNode->freeBuffer fails
// 从对应端口队列中移除它,即使OMXNode释放buffer失败。
mBuffers[portIndex].removeAt(i);
return err;
}
mOMXNode->freeBuffer(portIndex, info->mBufferID)实现分析:
请求底层组件释放在Buffer
// [frameworks/av/media/libstagefright/omx/OMXNodeInstance.cpp]
status_t OMXNodeInstance::freeBuffer(
OMX_U32 portIndex, IOMX::buffer_id buffer) {
// 加锁访问
Mutex::Autolock autoLock(mLock);
if (mHandle == NULL) {
return DEAD_OBJECT;
}
CLOG_BUFFER(freeBuffer, "%s:%u %#x", portString(portIndex), portIndex, buffer);
// 从正在使用buffer队列中移除该buffer
// 见下面分析
removeActiveBuffer(portIndex, buffer);
// 从对应端口队列中,查询该buffer id对应的buffer,见此前亦有分析
OMX_BUFFERHEADERTYPE *header = findBufferHeader(buffer, portIndex);
if (header == NULL) {
ALOGE("b/25884056");
return BAD_VALUE;
}
// 私有数据对象强制转换
BufferMeta *buffer_meta = static_cast<BufferMeta *>(header->pAppPrivate);
// 注:在调用OMX_FreeBuffer之前,先让客户端Buffer失效。
// 如果不是,客户端中挂起的事件可能会在释放后访问缓冲区队列。
// Invalidate buffers in the client side first before calling OMX_FreeBuffer.
// If not, pending events in the client side might access the buffers after free.
// 使该buffer id无效
// 见下面分析
invalidateBufferID(buffer);
// 宏定义调用,最终执行了实际组件的FreeBuffer对应方法去释放该header对应的Buffer
// 暂不展开分析了
OMX_ERRORTYPE err = OMX_FreeBuffer(mHandle, portIndex, header);
CLOG_IF_ERROR(freeBuffer, err, "%s:%u %#x", portString(portIndex), portIndex, buffer);
// 然后释放该buffer
delete buffer_meta;
buffer_meta = NULL;
return StatusFromOMXError(err);
}
removeActiveBuffer(portIndex, buffer)实现分析:
从正在使用buffer队列中移除该buffer
// [frameworks/av/media/libstagefright/omx/OMXNodeInstance.cpp]
void OMXNodeInstance::removeActiveBuffer(
OMX_U32 portIndex, IOMX::buffer_id id) {
// 循环查询到对应buffer id的该buffer,然后从mActiveBuffers即正在使用buffer队列中移除该buffer
for (size_t i = 0; i < mActiveBuffers.size(); ++i) {
if (mActiveBuffers[i].mPortIndex == portIndex
&& mActiveBuffers[i].mID == id) {
mActiveBuffers.removeItemsAt(i);
if (portIndex == kPortIndexInputExtradata || portIndex == kPortIndexOutputExtradata) {
// Allow extradata ports
} else if (portIndex < NELEM(mNumPortBuffers)) {
// 该端口buffer个数减少
--mNumPortBuffers[portIndex];
}
return;
}
}
CLOGW("Attempt to remove an active buffer [%#x] we know nothing about...", id);
}
invalidateBufferID(buffer)实现分析:
使该buffer id无效
// [frameworks/av/media/libstagefright/omx/OMXNodeInstance.cpp]
void OMXNodeInstance::invalidateBufferID(IOMX::buffer_id buffer) {
if (buffer == 0) {
return;
}
// 加锁访问
Mutex::Autolock autoLock(mBufferIDLock);
// 获取该Buffer ID在其队列中的索引
ssize_t index = mBufferIDToBufferHeader.indexOfKey(buffer);
if (index < 0) {
// 已无效则不处理
CLOGW("invalidateBufferID: buffer %u not found", buffer);
return;
}
// 从这两个队列中移除该索引对应的item映射数据,也就完成了该任务
mBufferHeaderToBufferID.removeItem(mBufferIDToBufferHeader.valueAt(index));
mBufferIDToBufferHeader.removeItemsAt(index);
}
1.2、mBufferChannel->discardBuffer(buffer)实现分析:
丢弃不需要渲染的(音频或视频)输出Buffer
备注:此方法实现分析见在此前【Part 7】【03】部分中已有分析,此处直接定位其输出Buffer的【mOutputBufferDrained】事件消息处理分析。而该事件也和上面1.1小节中最终也是相同的事件消息处理流程,通过携带的数据参数不同来区别,因此见1.1小节中分析。
2、mCodec->releaseOutputBuffer(bufferIx)实现分析:
释放音频或视频帧输出Buffer
可以看到它其实和第1小节中的处理是同一个事件处理的,只是携带不同参数去区别处理。
// [frameworks/av/media/libstagefright/MediaCodec.cpp]
status_t MediaCodec::releaseOutputBuffer(size_t index) {
sp<AMessage> msg = new AMessage(kWhatReleaseOutputBuffer, this);
msg->setSize("index", index);
sp<AMessage> response;
return PostAndAwaitResponse(msg, &response);
}
本章结束
整个系列文章结束语:
【Android MediaPlayer整体架构源码分析】到此处基本完成了它的整体架构源码实现分析,在此过程中,这是一次完成系统性的工作任务总结和技术积累,也学习到了更多关于系统层Binder新技术HIDL、功能架构、模块架构、C及C++语言特性多种使用方式、媒体框架架构及其工作原理和细节处理,以及还有多各种数据算法处理等非常棒的知识技术、架构设计、设计模式,另外也增强了对于实际音视频数据的理论知识点进行的处理技术,扩展增加了技术能力范围及其程序设计思维和解决问题能力。