承接上一章节分析:【六】Android MediaPlayer整体架构源码分析 -【start请求播放处理流程】【Part 10】【01】
本系列文章分析的安卓源码版本:【Android 10.0 版本】
推荐涉及到的知识点:
Binder机制实现原理:Android C++底层Binder通信机制原理分析总结【通俗易懂】
ALooper机制实现原理:Android native层媒体通信架构AHandler/ALooper机制实现源码分析
Binder异常关闭监听:Android native层DeathRecipient对关联进程(如相关Service服务进程)异常关闭通知事件的监听实现源码分析
【此章节小节编号就接着上一章节排列】
2.2、mCodec->mBufferChannel->drainThisBuffer(info->mBufferID, flags)实现分析:
执行Buffer通道消耗该输出Buffer处理流程
// [frameworks/av/media/libstagefright/ACodecBufferChannel.cpp]
void ACodecBufferChannel::drainThisBuffer(
IOMX::buffer_id bufferId,
OMX_U32 omxFlags) {
ALOGV("drainThisBuffer #%d", bufferId);
// 输出端口队列
std::shared_ptr<const std::vector<const BufferInfo>> array(
std::atomic_load(&mOutputBuffers));
// 查找该输出buffer id对应的buffer访问迭代器
BufferInfoIterator it = findBufferId(array, bufferId);
if (it == array->end()) {
// 未查询到
ALOGE("drainThisBuffer: unrecognized buffer #%d", bufferId);
return;
}
if (it->mClientBuffer != it->mCodecBuffer) {
// 两个Buffer数据未共享内存时,设置解码器Buffer的输出格式到客户端输出Buffer中
it->mClientBuffer->setFormat(it->mCodecBuffer->format());
}
// 以下为转换OMX flags类型为普通数据类型记录
uint32_t flags = 0;
if (omxFlags & OMX_BUFFERFLAG_SYNCFRAME) {
// 同步帧
flags |= MediaCodec::BUFFER_FLAG_SYNCFRAME;
}
if (omxFlags & OMX_BUFFERFLAG_CODECCONFIG) {
// CSD配置数据【编解码器配置信息】
flags |= MediaCodec::BUFFER_FLAG_CODECCONFIG;
}
if (omxFlags & OMX_BUFFERFLAG_EOS) {
// EOS
flags |= MediaCodec::BUFFER_FLAG_EOS;
}
if (omxFlags & OMX_BUFFERFLAG_EXTRADATA) {
flags |= MediaCodec::BUFFER_FLAG_EXTRADATA;
}
if (omxFlags & OMX_BUFFERFLAG_DATACORRUPT) {
// 数据损坏标记位
flags |= MediaCodec::BUFFER_FLAG_DATACORRUPT;
}
// 设置该标记位
it->mClientBuffer->meta()->setInt32("flags", flags);
// 执行输出Buffer数据可取处理流程
// 见下面分析
mCallback->onOutputBufferAvailable(
std::distance(array->begin(), it),
it->mClientBuffer);
}
mCallback->onOutputBufferAvailable(std::distance(array->begin(), it), it->mClientBuffer)实现分析:
执行输出Buffer数据可取处理流程
由此前流程中有过的分析可知,该回调监听类是MediaCodec中实现设置的,如下
// [frameworks/av/media/libstagefright/ACodecBufferChannel.cpp]
void BufferCallback::onOutputBufferAvailable(
size_t index, const sp<MediaCodecBuffer> &buffer) {
// 由此前流程分析可知,mNotify为【kWhatCodecNotify】事件,MediaCodec接收处理
sp<AMessage> notify(mNotify->dup());
// 设置子事件类型【kWhatDrainThisBuffer】
notify->setInt32("what", kWhatDrainThisBuffer);
// 设置当前已填充数据的输出Buffer及其index
notify->setSize("index", index);
notify->setObject("buffer", buffer);
notify->post();
}
MediaCodec接收处理【kWhatCodecNotify】事件消息的子事件【kWhatDrainThisBuffer】:
// [frameworks/av/media/libstagefright/MediaCodec.cpp]
void MediaCodec::onMessageReceived(const sp<AMessage> &msg) {
switch (msg->what()) {
case kWhatCodecNotify:
{
int32_t what;
CHECK(msg->findInt32("what", &what));
switch (what) {
case kWhatDrainThisBuffer:
{
// 更新输出端口Buffer,见此前已有流程分析
// 主要将当前实际负载输出Buffer更新到输出端口mPortBuffers队列中
/* size_t index = */updateBuffers(kPortIndexOutput, msg);
if (mState == FLUSHING
|| mState == STOPPING
|| mState == RELEASING) {
// 状态不对时,归还输出Buffer给Codec组,见此前已有流程分析
returnBuffersToCodecOnPort(kPortIndexOutput);
break;
}
sp<RefBase> obj;
// 获取实际负载数据Buffer,并强转
CHECK(msg->findObject("buffer", &obj));
sp<MediaCodecBuffer> buffer = static_cast<MediaCodecBuffer *>(obj.get());
if (mOutputFormat != buffer->format()) {
// 该Buffer输出格式发生变化时
// 更新它
mOutputFormat = buffer->format();
ALOGV("[%s] output format changed to: %s",
mComponentName.c_str(), mOutputFormat->debugString(4).c_str());
if (mSoftRenderer == NULL &&
mSurface != NULL &&
(mFlags & kFlagUsesSoftwareRenderer)) {
// 软渲染器为空,并且Surface不为空,并且flag为使用软渲染器时
// 备注:由我们早前章节的分析可知,这种情况一般是软解码器时发生
AString mime;
// 获取当前输出mime格式
CHECK(mOutputFormat->findString("mime", &mime));
// 注:传递色彩空间模式给软件渲染器,以允许更好的色彩空间转换到RGB。
// 而现在,只是将数据(色彩)空间标记为YUV渲染即可。
// TODO: propagate color aspects to software renderer to allow better
// color conversion to RGB. For now, just mark dataspace for YUV
// rendering.
int32_t dataSpace;
// 获取数据(色彩)空间类型
if (mOutputFormat->findInt32("android._dataspace", &dataSpace)) {
// 查询成功
ALOGD("[%s] setting dataspace on output surface to #%x",
mComponentName.c_str(), dataSpace);
// 设置该类型给Surface
// 备注:关于Surface的分析此处不展开,后续有时间再分析Surface问题
int err = native_window_set_buffers_data_space(
mSurface.get(), (android_dataspace)dataSpace);
ALOGW_IF(err != 0, "failed to set dataspace on surface (%d)", err);
}
if (mOutputFormat->contains("hdr-static-info")) {
HDRStaticInfo info;
// 获取HDR配置信息,见此前已有分析
if (ColorUtils::getHDRStaticInfoFromFormat(mOutputFormat, &info)) {
// 获取成功时再次设置HDR配置信息给Surface
// 见2.2.1小节分析
setNativeWindowHdrMetadata(mSurface.get(), &info);
}
}
sp<ABuffer> hdr10PlusInfo;
// 获取HDR增强版本信息
if (mOutputFormat->findBuffer("hdr10-plus-info", &hdr10PlusInfo)
&& hdr10PlusInfo != nullptr && hdr10PlusInfo->size() > 0) {
// 若存在时
// 直接设置给Surface
// 关于Surface暂不分析
native_window_set_buffers_hdr10_plus_metadata(mSurface.get(),
hdr10PlusInfo->size(), hdr10PlusInfo->data());
}
if (mime.startsWithIgnoreCase("video/")) {
// 视频编解码时
// 重置Surface该值
mSurface->setDequeueTimeout(-1);
// 创建一个软渲染器
// 备注:关于该软渲染器不展开分析,只阐述下它的功能原理作用,
// 就是在软解码器时进行render渲染请求时,
// 根据色彩空间类型对YUV等数据的进行重新处理转换为Surface接收的数据,然后显示,
// 内部主要调用【mNativeWindow->dequeueBuffer】和【mNativeWindow->queueBuffer】来完成的。
mSoftRenderer = new SoftwareRenderer(mSurface, mRotationDegrees);
}
}
// 如果需要,请求提高CPU
// 见2.2.2小节分析
requestCpuBoostIfNeeded();
if (mFlags & kFlagIsEncoder) {
// 编码器时
// 注:在通知格式信息改变之前,我们应该修正编解码器特定数据即CSD数据,
// 并根据需要修改输出格式。
// Before we announce the format change we should
// collect codec specific data and amend the output
// format as necessary.
int32_t flags = 0;
// 获取当前Buffer的标记位
(void) buffer->meta()->findInt32("flags", &flags);
if (flags & BUFFER_FLAG_CODECCONFIG) {
// 标记位包含CODECCONFIG即当前Buffer数据为CSD数据时
// 修正编码器特定数据即CSD数据的输出格式
// 见2.2.3小节分析
status_t err =
amendOutputFormatWithCodecSpecificData(buffer);
if (err != OK) {
// 失败时只是打印
ALOGE("Codec spit out malformed codec "
"specific data!");
}
}
}
// 有前面相关章节可知,该编解码器flag标记位为【kFlagIsAsync】模式,即通过异步回调方式的编解码器工作流程
if (mFlags & kFlagIsAsync) {
// 编解码器异步工作模式时
// 回调输出数据格式改变通知处理流程
// 见2.2.4小节分析
onOutputFormatChanged();
} else {
// 不关注
mFlags |= kFlagOutputFormatChanged;
postActivityNotificationIfPossible();
}
// 不关注加密源
// Notify mCrypto of video resolution changes
if (mCrypto != NULL) {
int32_t left, top, right, bottom, width, height;
if (mOutputFormat->findRect("crop", &left, &top, &right, &bottom)) {
mCrypto->notifyResolution(right - left + 1, bottom - top + 1);
} else if (mOutputFormat->findInt32("width", &width)
&& mOutputFormat->findInt32("height", &height)) {
mCrypto->notifyResolution(width, height);
}
}
}
if (mFlags & kFlagIsAsync) {
// 编解码器异步工作模式时
// 输出Buffer可取回调处理
// 见2.2.5小节分析
onOutputBufferAvailable();
} else if (mFlags & kFlagDequeueOutputPending) {
// 不关注
CHECK(handleDequeueOutputBuffer(mDequeueOutputReplyID));
++mDequeueOutputTimeoutGeneration;
mFlags &= ~kFlagDequeueOutputPending;
mDequeueOutputReplyID = 0;
} else {
// 不关注
postActivityNotificationIfPossible();
}
break;
}
}
}
}
}
2.2.1、setNativeWindowHdrMetadata(mSurface.get(), &info)实现分析:
设置HDR配置信息给Surface
// [frameworks/av/media/libstagefright/SurfaceUtils.cpp]
void setNativeWindowHdrMetadata(ANativeWindow *nativeWindow, HDRStaticInfo *info) {
// 根据HDR配置信息来配置SMPTE2086标准的元数据信息,即计算色彩空间显示基元色RGB、YUV的相关值
struct android_smpte2086_metadata smpte2086_meta = {
.displayPrimaryRed = {
info->sType1.mR.x * 0.00002f,
info->sType1.mR.y * 0.00002f
},
.displayPrimaryGreen = {
info->sType1.mG.x * 0.00002f,
info->sType1.mG.y * 0.00002f
},
.displayPrimaryBlue = {
info->sType1.mB.x * 0.00002f,
info->sType1.mB.y * 0.00002f
},
.whitePoint = {
info->sType1.mW.x * 0.00002f,
info->sType1.mW.y * 0.00002f
},
// 最大亮度
.maxLuminance = (float) info->sType1.mMaxDisplayLuminance,
// 最小亮度
.minLuminance = info->sType1.mMinDisplayLuminance * 0.0001f
};
// 设置给Surface
// 关于Surface暂不分析
int err = native_window_set_buffers_smpte2086_metadata(nativeWindow, &smpte2086_meta);
ALOGW_IF(err != 0, "failed to set smpte2086 metadata on surface (%d)", err);
// 创建CTA861标准信息
struct android_cta861_3_metadata cta861_meta = {
.maxContentLightLevel = (float) info->sType1.mMaxContentLightLevel,
.maxFrameAverageLightLevel = (float) info->sType1.mMaxFrameAverageLightLevel
};
// 设置给Surface
// 关于Surface暂不分析
err = native_window_set_buffers_cta861_3_metadata(nativeWindow, &cta861_meta);
ALOGW_IF(err != 0, "failed to set cta861_3 metadata on surface (%d)", err);
}
2.2.2、requestCpuBoostIfNeeded()实现分析:
如果需要,请求提高CPU
// [frameworks/av/media/libstagefright/MediaCodec.cpp]
void MediaCodec::requestCpuBoostIfNeeded() {
if (mCpuBoostRequested) {
return;
}
int32_t colorFormat;
if (mOutputFormat->contains("hdr-static-info")
&& mOutputFormat->findInt32("color-format", &colorFormat)
// check format for OMX only, for C2 the format is always opaque since the
// software rendering doesn't go through client
&& ((mSoftRenderer != NULL && colorFormat == OMX_COLOR_FormatYUV420Planar16)
|| mOwnerName.equalsIgnoreCase("codec2::software"))) {
// 只有当前输出格式配置信息有HDR静态信息时,且有色彩格式信息时,并且还有,
// 当软渲染器存在且色彩格式为YUV420平面格式16位深 或 编解码器组件名拥有者名称为"codec2::software"时,进入
// 备注:"codec2::software"组件拥有者名称早前分析过。
int32_t left, top, right, bottom, width, height;
int64_t totalPixel = 0;
if (mOutputFormat->findRect("crop", &left, &top, &right, &bottom)) {
// 获取到帧图像剪切尺寸:左上右下四个值时
// 计算当前剪切后的帧图像总像素大小:长高像素相乘
totalPixel = (right - left + 1) * (bottom - top + 1);
} else if (mOutputFormat->findInt32("width", &width)
&& mOutputFormat->findInt32("height", &height)) {
// 不需要剪切图像帧时,获取宽高成功时
// 计算当前帧图像总像素大小:长高像素相乘
totalPixel = width * height;
}
if (totalPixel >= 1920 * 1080) {
// 若大于1920*1080的乘积时
// 添加需要提高CPU的特殊资源到资源管理服务中心
addResource(MediaResource::kCpuBoost,
MediaResource::kUnspecifiedSubType, 1);
// 标记已请求CPU提升
mCpuBoostRequested = true;
}
}
}
2.2.3、amendOutputFormatWithCodecSpecificData(buffer)实现分析:
编码器时,修正编码器特定数据即CSD数据的输出格式
// [frameworks/av/media/libstagefright/MediaCodec.cpp]
status_t MediaCodec::amendOutputFormatWithCodecSpecificData(
const sp<MediaCodecBuffer> &buffer) {
AString mime;
// 获取输出Buffer的mime格式
CHECK(mOutputFormat->findString("mime", &mime));
// 获取码流特性NAL长度
int32_t nalLengthBistream = 0;
mOutputFormat->findInt32("feature-nal-length-bitstream", &nalLengthBistream);
if (!strcasecmp(mime.c_str(), MEDIA_MIMETYPE_VIDEO_AVC)) {
// AVC时
// 注:编码器特定数据CSD应该是单个Buffer的SPS和PPS,
// 每个都以一个(NALU单元)起始码(0x00 0x00 0x00 0x01)为前缀。
// 我们将两者分开,并将它们设置在key值为“csd-0”和“csd-1”中的输出格式中。
// Codec specific data should be SPS and PPS in a single buffer,
// each prefixed by a startcode (0x00 0x00 0x00 0x01).
// We separate the two and put them into the output format
// under the keys "csd-0" and "csd-1".
unsigned csdIndex = 0;
// 当前输出Buffer的数据负载访问指针及其数据大小
const uint8_t *data = buffer->data();
size_t size = buffer->size();
if (!memcmp(data, "\x00\x00\x00\x01", 4)) {
// NALU起始码字节内存比较前4个字节相等时
// NAL长度重置该值为0
nalLengthBistream = 0;
}
if (!nalLengthBistream) {
// 不为0时
const uint8_t *nalStart;
size_t nalSize;
// 循环读取当前buffer中的NALU单元系列数据
// 见前面章节已有分析
while (getNextNALUnit(&data, &size, &nalStart, &nalSize, true) == OK) {
// 获取成功时
// 创建新Buffer,长度为读取到的NALU单元数据大小加上4的大小(字节)
// 备注:为什么要加上4字节,原因是读取到的NALU单元数据大小不包括四个字节的起始码
sp<ABuffer> csd = new ABuffer(nalSize + 4);
// 首先写入NALU起始码四字节
memcpy(csd->data(), "\x00\x00\x00\x01", 4);
// 然后在4字节后面写入当前NALU单元负载数据
memcpy(csd->data() + 4, nalStart, nalSize);
// 然后设置CSD数据Buffer缓存在csd-0或csd-1字段中
mOutputFormat->setBuffer(
AStringPrintf("csd-%u", csdIndex).c_str(), csd);
// csd索引递增
++csdIndex;
}
} else {
// 当前buffer已经是以NALU起始码开始的数据buffer时
int32_t bytesLeft = size;
const uint8_t *tmp = data;
// while计算剩余字节数是否大于4字节起始码
while (bytesLeft > 4) {
// 大于起始码时
int32_t nalSize = 0;
// 覆盖拷贝赋值,拷贝到nalSize的开始内存地址中
std::copy(tmp, tmp+4, reinterpret_cast<uint8_t *>(&nalSize));
// 将该指针转换为int
// 该方法实现早前章节已有分析,即将network网络字节(表示)转换为host本机字节(表示)
// 将一个无符号长整形数从网络字节顺序转换为主机字节顺序, 返回一个以主机字节顺序表达的数
nalSize = ntohl(nalSize);
// 创建Buffer对象
sp<ABuffer> csd = new ABuffer(nalSize + 4);
// 存储
memcpy(csd->data(), tmp, nalSize + 4);
// 缓存它
mOutputFormat->setBuffer(
AStringPrintf("csd-%u", csdIndex).c_str(), csd);
// 移动访问指针到下一个NALU单元
tmp += nalSize + 4;
// 减去已读取数据大小
bytesLeft -= (nalSize + 4);
// csd索引递增
++csdIndex;
}
}
if (csdIndex != 2) {
// 注意此处,csd索引必须等于2,否则认为是错误数据
return ERROR_MALFORMED;
}
} else {
// 其他音视频编码格式时
// 备注:对于其他格式,我们只是将编解码器特定数据作为“csd-0”下的单个csd块保存到输出格式中
// For everything else we just stash the codec specific data into
// the output format as a single piece of csd under "csd-0".
sp<ABuffer> csd = new ABuffer(buffer->size());
memcpy(csd->data(), buffer->data(), buffer->size());
csd->setRange(0, buffer->size());
mOutputFormat->setBuffer("csd-0", csd);
}
return OK;
}
2.2.4、onOutputFormatChanged()实现分析:
回调输出数据格式改变通知处理流程
由于该部分分析较长,因此单独拎出后续章节分析,请查看:
【六】Android MediaPlayer整体架构源码分析 -【start请求播放处理流程】【Part 11】
2.2.5、onOutputBufferAvailable()实现分析:
已填充的输出Buffer可取回调处理
// [frameworks/av/media/libstagefright/MediaCodec.cpp]
void MediaCodec::onOutputBufferAvailable() {
int32_t index;
// while循环取出输出端口Buffer
// 备注:dequeuePortBuffer该方法实现请见此前关于章节输入buffer【Part 7】部分中已有分析
// 也就是取出输出端口队列中第一个可用Buffer,并返回其索引
while ((index = dequeuePortBuffer(kPortIndexOutput)) >= 0) {
// 获取输出Buffer成功时
// 获取该Buffer
const sp<MediaCodecBuffer> &buffer =
mPortBuffers[kPortIndexOutput][index].mData;
// 回调监听事件消息对象,即前面配置过程中NuPlayerDecoder创建接收处理的【kWhatCodecNotify】事件
sp<AMessage> msg = mCallback->dup();
// 子事件类型回调ID为输出buffer可用时,通知客户端可消耗使用该输出Buffer数据
msg->setInt32("callbackID", CB_OUTPUT_AVAILABLE);
// 输入端口队列索引
msg->setInt32("index", index);
// 数据读取位置偏移量及其数据大小
msg->setSize("offset", buffer->offset());
msg->setSize("size", buffer->size());
int64_t timeUs;
// 获取当前Buffer帧的PTS时间戳
CHECK(buffer->meta()->findInt64("timeUs", &timeUs));
// 设置当前Buffer帧的PTS时间戳
msg->setInt64("timeUs", timeUs);
// 统计接收到的Buffer相关信息
// 见2.2.5.1小节分析
statsBufferReceived(timeUs);
int32_t flags;
// 获取该输出Buffer标记位
CHECK(buffer->meta()->findInt32("flags", &flags));
// 设置该输出Buffer标记位
msg->setInt32("flags", flags);
// 发送【kWhatCodecNotify】事件消息给NuPlayerDecoder
// 见2.2.5.2小节分析
msg->post();
}
}
2.2.5.1、statsBufferReceived(timeUs)实现分析:
统计从Codec编解码器中接收到的Buffer相关PTS及系统时间戳信息
// when we get a buffer back from the codec
void MediaCodec::statsBufferReceived(int64_t presentationUs) {
CHECK_NE(mState, UNINITIALIZED);
// 加锁访问
// mutex access to mBuffersInFlight and other stats
Mutex::Autolock al(mLatencyLock);
// 注:该Buffer花了多长时间通过编解码器
// NB:管道传输可以/将使这些加倍变大。
// 例如,如果每个包总是2毫秒,而我们在任何给定的时间有3个在"飞行"中,我们将看到“6毫秒”作为一个答案。
// how long this buffer took for the round trip through the codec
// NB: pipelining can/will make these times larger. e.g., if each packet
// is always 2 msec and we have 3 in flight at any given time, we're going to
// see "6 msec" as an answer.
// 忽略没有PTS的数据
// ignore stuff with no presentation time
if (presentationUs <= 0) {
// 小于0时
ALOGV("-- returned buffer timestamp %" PRId64 " <= 0, ignore it", presentationUs);
// 记录未知延迟时长个数增加
mLatencyUnknown++;
return;
}
// 见前面已有分析
// 备注:先执行【addResource】,然后再执行【removeResource】
if (mBatteryChecker != nullptr) {
mBatteryChecker->onCodecActivity([this] () {
addResource(MediaResource::kBattery, MediaResource::kVideoCodec, 1);
});
}
// Buffer"飞行"时间,也就是编解码(从输入到输出)耗时时长
BufferFlightTiming_t startdata;
bool valid = false;
// 该PTS时间戳队列mBuffersInFlight见此前已有分析,输入Buffer递交Buffer给Codec时会添加item
while (mBuffersInFlight.size() > 0) {
// 队列有数据时
// 取出第一个item,可以描述为预期Buffer的PTS
startdata = *mBuffersInFlight.begin();
// startedNs为输入Buffer递交Buffer给Codec时的当时的系统开机时长
// presentationUs为输入Buffer递交Buffer给Codec时该Buffer帧PTS时间戳
ALOGV("-- Looking at startdata. presentation %" PRId64 ", start %" PRId64,
startdata.presentationUs, startdata.startedNs);
if (startdata.presentationUs == presentationUs) {
// a match
// 帧匹配时
ALOGV("-- match entry for %" PRId64 ", hits our frame of %" PRId64,
startdata.presentationUs, presentationUs);
// 弹出第一个item数据
mBuffersInFlight.pop_front();
// 记录有效
valid = true;
break;
} else if (startdata.presentationUs < presentationUs) {
// 小于时表示当前输出Buffer帧PTS比预期的前面的输入Buffer编解码的快,即当前输出Buffer帧PTS比预期的PTS早(快)了
// 也就是不符合预期,一定错过了该预期Buffer,因此丢弃预期PTS,继续下一个循环匹配
// we must have missed the match for this, drop it and keep looking
ALOGV("-- drop entry for %" PRId64 ", before our frame of %" PRId64,
startdata.presentationUs, presentationUs);
mBuffersInFlight.pop_front();
continue;
} else {
// 大于时表示当前输出Buffer帧PTS比预期的后面的输入Buffer编解码的慢,
// 当前输出Buffer帧PTS比预期的PTS慢(晚)了,也表明错过了但此时不需要drop预期PTS
// head is after, so we don't have a frame for ourselves
ALOGV("-- found entry for %" PRId64 ", AFTER our frame of %" PRId64
" we have nothing to pair with",
startdata.presentationUs, presentationUs);
// 记录未知延迟时长个数增加
mLatencyUnknown++;
// 直接返回
return;
}
}
if (!valid) {
// 无效时即表示当前【mBuffersInFlight】PTS队列没有查询到需要匹配的数据,其实际就相当于当前队列已经空了
ALOGV("-- empty queue, so ignore that.");
// 记录未知延迟时长个数增加
mLatencyUnknown++;
return;
}
// 匹配上时
// nowNs start our calculations
// 开始计算
// 当前系统开机已时长
const int64_t nowNs = systemTime(SYSTEM_TIME_MONOTONIC);
// 计算延迟时长:加了500纳秒,转换为微妙
int64_t latencyUs = (nowNs - startdata.startedNs + 500) / 1000;
// 它实际是个Histogram类变量,早前章节有分析过它。主要就是柱状图统计延迟时长队列
mLatencyHist.insert(latencyUs);
// push into the recent samples
// 加锁代码块,添加到最近样本队列中
{
Mutex::Autolock al(mRecentLock);
// 在“最近延迟帧”直方图中有多少个样本是300帧(即5秒@ 60fps或~12秒@ 24fps)
// 也就是最近延迟帧队列最多允许记录300帧,否则若大于等于300则从0开始再次记录
if (mRecentHead >= kRecentLatencyFrames) {
mRecentHead = 0;
}
// 记录当前延迟时长在延迟时长队列中
// 备注:上面分析可知,再超过300帧时它将会从头覆盖前面帧的延迟时长
mRecentSamples[mRecentHead++] = latencyUs;
}
}
2.2.5.2、发送【kWhatCodecNotify】事件消息给NuPlayerDecoder处理:
NuPlayerDecoder创建接收处理【kWhatCodecNotify】事件消息的子事件类型【CB_OUTPUT_AVAILABLE】回调ID:
即请求客户端NuPlayerDecoder处理编解码器输出Buffer可使用事件,消耗输出Buffer
// [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 kWhatCodecNotify:
{
int32_t cbID;
CHECK(msg->findInt32("callbackID", &cbID));
ALOGV("[%s] kWhatCodecNotify: cbID = %d, paused = %d",
mIsAudio ? "audio" : "video", cbID, mPaused);
if (mPaused) {
// 已暂停时则忽略该事件
break;
}
switch (cbID) {
case MediaCodec::CB_OUTPUT_AVAILABLE:
{
int32_t index;
size_t offset;
size_t size;
int64_t timeUs;
int32_t flags;
// 取出传递参数
CHECK(msg->findInt32("index", &index));
CHECK(msg->findSize("offset", &offset));
CHECK(msg->findSize("size", &size));
CHECK(msg->findInt64("timeUs", &timeUs));
CHECK(msg->findInt32("flags", &flags));
handleAnOutputBuffer(index, offset, size, timeUs, flags);
break;
}
}
}
}
}
handleAnOutputBuffer(index, offset, size, timeUs, flags)实现分析:
处理编解码器输出Buffer可使用事件,消耗输出Buffer
由于该部分分析较长,因此单独拎出后续章节分析,请查看:
【六】Android MediaPlayer整体架构源码分析 -【start请求播放处理流程】【Part 12】
2.3、mCodec->mCallback->onEos(mCodec->mInputEOSResult)实现分析:
回调MediaCodec的监听回调类的eos方法处理流程
TODO 关于编解码器EOS回调处理流程,暂不展开分析,可自行分析
它将会通知NuPlayerRender该事件【kWhatEOS】消息去处理EOS流程,并最终通知NuPlayer【kWhatEOS】事件去处理。当音视频同时EOS时将会通知回调上层APP的播放完成事件,否则若只有其中一个数据流EOS,那么将继续另一个数据流的单独播放。
注意:该流程原生处理会导致一个严重bug问题,即音频EOS而视频未EOS时,恢复播放时将会造成视频无法继续播放问题,需要自行修复,后续有时间再来分析下该流程和问题产生原因。
2.4、mCodec->freeBuffer(kPortIndexOutput, index)实现分析:
释放Buffer
关于编解码器完成工作后释放端口处理流程,此处就不再展开分析了,见最后一个章节【Part 13】部分的分析
本章结束