承接上一章节分析:【六】Android MediaPlayer整体架构源码分析 -【start请求播放处理流程】【Part 8】【02】
本系列文章分析的安卓源码版本:【Android 10.0 版本】
推荐涉及到的知识点:
Binder机制实现原理:Android C++底层Binder通信机制原理分析总结【通俗易懂】
ALooper机制实现原理:Android native层媒体通信架构AHandler/ALooper机制实现源码分析
Binder异常关闭监听:Android native层DeathRecipient对关联进程(如相关Service服务进程)异常关闭通知事件的监听实现源码分析
在前面章节流程完毕后,底层编解码器组件在接收到输入输出端口Buffer(输入Buffer递交给组件流程此前已分析)后,将会执行【*mCallbacks->EmptyBufferDone】和【*mCallbacks->FillBufferDone】回调,即将会触发回调OMXNode中回调监听类【OMX_CALLBACKTYPE OMXNodeInstance::kCallbacks】的onEmptyBufferDone消耗输入Buffer完成事件方法被执行和onFillBufferDone填充输出buffer完毕事件方法被执行。
也就是说此前我们终于完成了编解码器获取数据和输出数据的完整过程了!
因此本章节接下来分析这两个底层组件消耗输入buffer或填充输出buffer完成事件回调流程分析:
先分析onEmptyBufferDone,后分析onFillBufferDone
1、onEmptyBufferDone() 消耗输入Buffer完成事件方法实现分析:
// [frameworks/av/media/libstagefright/omx/OMXNodeInstance.cpp]
// static
OMX_ERRORTYPE OMXNodeInstance::OnEmptyBufferDone(
OMX_IN OMX_HANDLETYPE /* hComponent */,
OMX_IN OMX_PTR pAppData,
OMX_IN OMX_BUFFERHEADERTYPE* pBuffer) {
if (pAppData == NULL) {
ALOGE("b/25884056");
return OMX_ErrorBadParameter;
}
// 强转
OMXNodeInstance *instance = static_cast<OMXNodeInstance *>(pAppData);
if (instance->mDying) {
// OMXNode死亡中,但还是返回无错误
return OMX_ErrorNone;
}
// 从输出端口元数据中获取Fence描述符
// 见1.1小节分析
int fenceFd = instance->retrieveFenceFromMeta_l(pBuffer, kPortIndexOutput);
// 创建OMX消息
omx_message msg;
// 标记当前消息类型【EMPTY_BUFFER_DONE】
msg.type = omx_message::EMPTY_BUFFER_DONE;
// 缓存fence描述符
msg.fenceFd = fenceFd;
// 根据Buffer header来查询Buffer ID
// 见1.2小节分析
msg.u.buffer_data.buffer = instance->findBufferID(pBuffer);
// OMX消息分发
// 见1.3小节分析
instance->mDispatcher->post(msg);
// 返回成功
return OMX_ErrorNone;
}
1.1、instance->retrieveFenceFromMeta_l(pBuffer, kPortIndexOutput)实现分析:
从输出端口元数据中获取Fence描述符
// [frameworks/av/media/libstagefright/omx/OMXNodeInstance.cpp]
int OMXNodeInstance::retrieveFenceFromMeta_l(
OMX_BUFFERHEADERTYPE *header, OMX_U32 portIndex) {
// 根据端口类别获取分配元数据大小
OMX_U32 metaSize = portIndex == kPortIndexInput ? header->nAllocLen : header->nFilledLen;
int fenceFd = -1;
// 判断端口元数据Buffer类型
if (mMetadataType[portIndex] == kMetadataBufferTypeANWBuffer
&& header->nAllocLen >= sizeof(VideoNativeMetadata)) {
// 是【VideoNativeMetadata】这种元数据类型时
VideoNativeMetadata &nativeMeta = *(VideoNativeMetadata *)(header->pBuffer);
// 再判断元数据内部Buffer类型
if (nativeMeta.eType == kMetadataBufferTypeANWBuffer) {
// 直接获取该值,并重置-1
fenceFd = nativeMeta.nFenceFd;
nativeMeta.nFenceFd = -1;
}
if (metaSize < sizeof(nativeMeta) && fenceFd >= 0) {
// 这种情况发生时为错误,也就是空数据/无效数据时
CLOG_ERROR(foundFenceInEmptyMeta, BAD_VALUE, FULL_BUFFER(
NULL, header, nativeMeta.nFenceFd));
// 重置-1
fenceFd = -1;
}
}
// 返回
return fenceFd;
}
1.2、instance->findBufferID(pBuffer)实现分析:
根据Buffer header来查询Buffer ID
如下,实际实现很简单就是在此前分配Buffer时缓存的映射map对象中获取对应ID
// [frameworks/av/media/libstagefright/omx/OMXNodeInstance.cpp]
IOMX::buffer_id OMXNodeInstance::findBufferID(OMX_BUFFERHEADERTYPE *bufferHeader) {
if (bufferHeader == NULL) {
return 0;
}
// 加锁访问
Mutex::Autolock autoLock(mBufferIDLock);
ssize_t index = mBufferHeaderToBufferID.indexOfKey(bufferHeader);
if (index < 0) {
CLOGW("findBufferID: bufferHeader %p not found", bufferHeader);
return 0;
}
return mBufferHeaderToBufferID.valueAt(index);
}
1.3、instance->mDispatcher->post(msg)实现分析:
消耗输入Buffer完成事件的OMX消息分发
关于该方法调用执行流程分析,我们已经在此前【六】Android MediaPlayer整体架构源码分析 -【start请求播放处理流程】【Part 4】【02】章节中已有分析了,它将会最终执行到【ACodec::BaseState::onOMXMessage】该方法中,如下【省略其他代码】
// [frameworks/av/media/libstagefright/ACodec.cpp]
bool ACodec::BaseState::onOMXMessage(const sp<AMessage> &msg) {
int32_t type;
CHECK(msg->findInt32("type", &type));
switch (type) {
case omx_message::EVENT:
// 省略其他代码
case omx_message::EMPTY_BUFFER_DONE:
// Codec组件消耗输入Buffer完成事件
{
IOMX::buffer_id bufferID;
int32_t fenceFd;
// 获取参数
CHECK(msg->findInt32("buffer", (int32_t*)&bufferID));
CHECK(msg->findInt32("fence_fd", &fenceFd));
return onOMXEmptyBufferDone(bufferID, fenceFd);
}
// 省略其他代码
case omx_message::FILL_BUFFER_DONE:
case omx_message::FRAME_RENDERED:
default:
ALOGE("Unexpected message type: %d", type);
return false;
}
}
onOMXEmptyBufferDone(bufferID, fenceFd)实现分析:
Codec组件消耗输入Buffer完成事件处理
也就是Codec组件递交输入Buffer返回,交还给上层继续去填充输入Buffer
下面分析可知,最终会再次发起请求Client数据源端填充该输入Buffer,如此Codec编解码器获取输入数据和使用完输入数据的整个流程形成了闭环,也就是源源不断的提供输入数据和消耗它。
// [frameworks/av/media/libstagefright/ACodec.cpp]
bool ACodec::BaseState::onOMXEmptyBufferDone(IOMX::buffer_id bufferID, int fenceFd) {
ALOGV("[%s] onOMXEmptyBufferDone %u",
mCodec->mComponentName.c_str(), bufferID);
// 根据Buffer id查询对应输入Buffer
BufferInfo *info = mCodec->findBufferByID(kPortIndexInput, bufferID);
// 获取当前Buffer的拥有使用权状态
BufferInfo::Status status = BufferInfo::getSafeStatus(info);
// 备注:当前输入Buffer的拥有权必须在Codec组件,否则则错误
if (status != BufferInfo::OWNED_BY_COMPONENT) {
// 若拥有权已不在Codec组件,则错误
ALOGE("Wrong ownership in EBD: %s(%d) buffer #%u", _asString(status), status, bufferID);
// 打印所有输入端口队列Buffer信息
mCodec->dumpBuffers(kPortIndexInput);
if (fenceFd >= 0) {
// 有效时,关闭该文件描述符
::close(fenceFd);
}
// 返回false
return false;
}
// 修改为【OWNED_BY_US】
info->mStatus = BufferInfo::OWNED_BY_US;
// input buffers cannot take fences, so wait for any fence now
// 输入Buffer在Codec组件交还时不能持有fence,所以现在等待任何存在的fence(同步点完成信号)通知
// 见下面分析
(void)mCodec->waitForFence(fenceFd, "onOMXEmptyBufferDone");
// 重置-1
fenceFd = -1;
// 仅仅debug
// still save fence for completeness
info->setWriteFence(fenceFd, "onOMXEmptyBufferDone");
// We're in "store-metadata-in-buffers" mode, the underlying
// OMX component had access to data that's implicitly refcounted
// by this "MediaBuffer" object. Now that the OMX component has
// told us that it's done with the input buffer, we can decrement
// the mediaBuffer's reference count.
// 重置该字段为null,即将会导致AMessage中此前存在的该对象的智能指针计数减一
info->mData->meta()->setObject("mediaBufferHolder", sp<MediaBufferHolder>(nullptr));
// 获取端口类型,此前有讲述过,正常情况下为【RESUBMIT_BUFFERS】
PortMode mode = getPortMode(kPortIndexInput);
switch (mode) {
case KEEP_BUFFERS:
break;
case RESUBMIT_BUFFERS:
// 然后可以再看到发起了请求Client数据源端填充该输入Buffer
// 见此前已有分析
// 备注:如此Codec编解码器获取输入数据和使用完输入数据的整个流程形成了闭环,
// 也就是源源不断的提供输入数据和消耗它。
postFillThisBuffer(info);
break;
case FREE_BUFFERS:
default:
ALOGE("SHOULD NOT REACH HERE: cannot free empty output buffers");
return false;
}
return true;
}
(void)mCodec->waitForFence(fenceFd, “onOMXEmptyBufferDone”)实现分析:
输入Buffer在Codec组件交还时不能持有fence,所以现在等待任何存在的fence(同步点完成信号)通知
// [frameworks/av/media/libstagefright/ACodec.cpp]
status_t ACodec::waitForFence(int fd, const char *dbg ) {
status_t res = OK;
if (fd >= 0) {
sp<Fence> fence = new Fence(fd);
// 也就是需要等待fence通知信号完成,见此前已有分析
res = fence->wait(IOMX::kFenceTimeoutMs);
ALOGW_IF(res != OK, "FENCE TIMEOUT for %d in %s", fd, dbg);
}
return res;
}
2、onFillBufferDone() 填充输出buffer完毕事件方法实现分析:
// [frameworks/av/media/libstagefright/omx/OMXNodeInstance.cpp]
// static
OMX_ERRORTYPE OMXNodeInstance::OnFillBufferDone(
OMX_IN OMX_HANDLETYPE /* hComponent */,
OMX_IN OMX_PTR pAppData,
OMX_IN OMX_BUFFERHEADERTYPE* pBuffer) {
if (pAppData == NULL) {
ALOGE("b/25884056");
return OMX_ErrorBadParameter;
}
OMXNodeInstance *instance = static_cast<OMXNodeInstance *>(pAppData);
if (instance->mDying) {
return OMX_ErrorNone;
}
// 见前面分析
int fenceFd = instance->retrieveFenceFromMeta_l(pBuffer, kPortIndexOutput);
// 和上一个流程处理类似的
// 创建OMX消息
omx_message msg;
// 标记OMX消息类型为【FILL_BUFFER_DONE】
msg.type = omx_message::FILL_BUFFER_DONE;
msg.fenceFd = fenceFd;
// 缓存Buffer ID
msg.u.extended_buffer_data.buffer = instance->findBufferID(pBuffer);
// 缓存Codec组件编解码器输出负载数据的偏移量及其大小(已填充数据量)
msg.u.extended_buffer_data.range_offset = pBuffer->nOffset;
msg.u.extended_buffer_data.range_length = pBuffer->nFilledLen;
// flag标记位
msg.u.extended_buffer_data.flags = pBuffer->nFlags;
// 当前输出(帧)数据的PTS时间戳
msg.u.extended_buffer_data.timestamp = pBuffer->nTimeStamp;
// OMX消息分发
instance->mDispatcher->post(msg);
return OMX_ErrorNone;
}
填充输出buffer完毕事件的OMX消息分发:
同上分析,它将会最终执行到【ACodec::BaseState::onOMXMessage】该方法中,如下【省略其他代码】
// [frameworks/av/media/libstagefright/ACodec.cpp]
bool ACodec::BaseState::onOMXMessage(const sp<AMessage> &msg) {
int32_t type;
CHECK(msg->findInt32("type", &type));
switch (type) {
case omx_message::EVENT:
case omx_message::EMPTY_BUFFER_DONE:
// 省略其他代码
case omx_message::FILL_BUFFER_DONE:
// Codec组件填充输出Buffer完成事件
{
// 获取消息中携带参数
IOMX::buffer_id bufferID;
CHECK(msg->findInt32("buffer", (int32_t*)&bufferID));
int32_t rangeOffset, rangeLength, flags, fenceFd;
int64_t timeUs;
CHECK(msg->findInt32("range_offset", &rangeOffset));
CHECK(msg->findInt32("range_length", &rangeLength));
CHECK(msg->findInt32("flags", &flags));
CHECK(msg->findInt64("timestamp", &timeUs));
CHECK(msg->findInt32("fence_fd", &fenceFd));
return onOMXFillBufferDone(
bufferID,
(size_t)rangeOffset, (size_t)rangeLength,
(OMX_U32)flags,
timeUs,
fenceFd);
}
// 省略其他代码
case omx_message::FRAME_RENDERED:
default:
ALOGE("Unexpected message type: %d", type);
return false;
}
}
onOMXFillBufferDone(bufferID, (size_t)rangeOffset, (size_t)rangeLength, (OMX_U32)flags, timeUs, fenceFd)实现分析:
填充输出buffer完毕事件处理流程
// [frameworks/av/media/libstagefright/ACodec.cpp]
bool ACodec::BaseState::onOMXFillBufferDone(
IOMX::buffer_id bufferID,
size_t rangeOffset, size_t rangeLength,
OMX_U32 flags,
int64_t timeUs,
int fenceFd) {
ALOGV("[%s] onOMXFillBufferDone %u time %" PRId64 " us, flags = 0x%08x",
mCodec->mComponentName.c_str(), bufferID, timeUs, flags);
ssize_t index;
status_t err= OK;
#if TRACK_BUFFER_TIMING
// 如前面分析,TRACK_BUFFER_TIMING只是个debug标记位,
// 因此不关注该流程,主要就是打印编解码器耗时时长
index = mCodec->mBufferStats.indexOfKey(timeUs);
if (index >= 0) {
ACodec::BufferStats *stats = &mCodec->mBufferStats.editValueAt(index);
stats->mFillBufferDoneTimeUs = ALooper::GetNowUs();
ALOGI("frame PTS %lld: %lld",
timeUs,
stats->mFillBufferDoneTimeUs - stats->mEmptyBufferTimeUs);
mCodec->mBufferStats.removeItemsAt(index);
stats = NULL;
}
#endif
// 查询指定输出Buffer ID对应Buffer
// 见此前已有分析
BufferInfo *info =
mCodec->findBufferByID(kPortIndexOutput, bufferID, &index);
// 获取当前Buffer使用权状态
BufferInfo::Status status = BufferInfo::getSafeStatus(info);
// 必须是在Codec组件使用权,否则失败、打印、通知并关闭存在的fenceFd
if (status != BufferInfo::OWNED_BY_COMPONENT) {
ALOGE("Wrong ownership in FBD: %s(%d) buffer #%u", _asString(status), status, bufferID);
mCodec->dumpBuffers(kPortIndexOutput);
mCodec->signalError(OMX_ErrorUndefined, FAILED_TRANSACTION);
if (fenceFd >= 0) {
::close(fenceFd);
}
return true;
}
// 状态正确时
// 增加输出Buffer的出队列计数值【该值递增】
info->mDequeuedAt = ++mCodec->mDequeueCounter;
// 修改Buffer拥有权状态为【OWNED_BY_US】
info->mStatus = BufferInfo::OWNED_BY_US;
// 记录已渲染帧信息是否为空
if (info->mRenderInfo != NULL) {
// The fence for an emptied buffer must have signaled, but there still could be queued
// or out-of-order dequeued buffers in the render queue prior to this buffer. Drop these,
// as we will soon requeue this buffer to the surface. While in theory we could still keep
// track of buffers that are requeued to the surface, it is better to add support to the
// buffer-queue to notify us of released buffers and their fences (in the future).
// 见此前已有分析,此处不再阐述
mCodec->notifyOfRenderedFrames(true /* dropIncomplete */);
}
// 注:当没有Surface时,字节Buffer模式的输出数据不能持有fence,所以需要等待fence完毕
// byte buffers cannot take fences, so wait for any fence now
if (mCodec->mNativeWindow == NULL) {
// 无Surface时,等待fence同步点信号通知完成
(void)mCodec->waitForFence(fenceFd, "onOMXFillBufferDone");
// 重置为-1
fenceFd = -1;
}
// 仅仅debug
info->setReadFence(fenceFd, "onOMXFillBufferDone");
// 获取端口模式,正常情况为【RESUBMIT_BUFFERS】
PortMode mode = getPortMode(kPortIndexOutput);
switch (mode) {
case KEEP_BUFFERS:
break;
case RESUBMIT_BUFFERS:
// 归还输出Buffer(比如归还给Surface)
{
// 检查输出Buffer有效性
if (rangeLength == 0 && !((flags & OMX_BUFFERFLAG_EOS)
|| mCodec->mPortEOS[kPortIndexOutput])) {
// 若输出Buffer数据为0并且非EOS时,或者输出端口EOS状态时
// 将不会归还当前输出Buffer,而是直接请求fillBuffer方法
// 让Codec组件再次使用该Buffer填充输出Buffer。
ALOGV("[%s] calling fillBuffer %u",
mCodec->mComponentName.c_str(), info->mBufferID);
// 见此前已有分析
err = mCodec->fillBuffer(info);
if (err != OK) {
// 发生错误,通知NuPlayer
mCodec->signalError(OMX_ErrorUndefined, makeNoSideEffectStatus(err));
return true;
}
break;
}
// 输出数据有效时
// 实际负载输出数据Buffer
sp<MediaCodecBuffer> buffer = info->mData;
if (mCodec->mOutputFormat != mCodec->mLastOutputFormat && rangeLength > 0) {
// 检查到当前输出Buffer的(要求)配置信息发生变化,并且输出Buffer有效时
// 注:假设输出格式在第一帧时发生了变化(我们过去是这样做的)
// pretend that output format has changed on the first frame (we used to do this)
if (mCodec->mBaseOutputFormat == mCodec->mOutputFormat) {
// 相同时即只处理了第一帧的格式判断改变情况
// 回调输出数据格式变化处理流程
// 见2.1小节分析
mCodec->onOutputFormatChanged(mCodec->mOutputFormat);
}
// 发送格式变化处理流程,其主要就是更新【mLastOutputFormat】为当前改变值
// 该方法在早前流程中分析过,因此不再分析
mCodec->sendFormatChange();
}
// 已更新的输出端口格式配置信息
sp<AMessage> updatedFormat = mCodec->mOutputFormat;
if (mCodec->mIsVideo && (flags & OMX_BUFFERFLAG_EXTRADATA)) {
// 视频编解码器并且该输出Buffer标记为额外数据时
// 执行填充额外数据,并返回给更新输出端口格式配置信息
// 该实现为平台私有实现,比如高通,默认为空实现,直接返回传入的格式
updatedFormat = AVUtils::get()->fillExtradata(
mCodec->mBuffers[kPortIndexOutputExtradata].editItemAt(index).mCodecData,
mCodec->mOutputFormat);
}
// 设置该输出格式配置信息到该输出Buffer中
buffer->setFormat(updatedFormat);
if (mCodec->usingSecureBufferOnEncoderOutput()) {
// 在编码器输出端口上使用加密buffer,不关注加密流程
native_handle_t *handle = NULL;
sp<SecureBuffer> secureBuffer = static_cast<SecureBuffer *>(buffer.get());
if (secureBuffer != NULL) {
#ifdef OMX_ANDROID_COMPILE_AS_32BIT_ON_64BIT_PLATFORMS
// handle is only valid on 32-bit/mediaserver process
handle = NULL;
#else
handle = (native_handle_t *)secureBuffer->getDestinationPointer();
#endif
}
buffer->meta()->setPointer("handle", handle);
buffer->meta()->setInt32("rangeOffset", rangeOffset);
buffer->meta()->setInt32("rangeLength", rangeLength);
} else if (buffer->base() == info->mCodecData->base()) {
// 输出数据负载内存(地址)和【mCodecData】相同时,
// 即不需要拷贝数据,因为可以直接读取到
// 设置数据有效访问范围
buffer->setRange(rangeOffset, rangeLength);
} else {
// 不相同时,也就是不共享内存访问块时,这时候必须执行数据转换器来拷贝转移数据
// 设置数据有效访问范围
info->mCodecData->setRange(rangeOffset, rangeLength);
// in this case we know that mConverter is not null
// 执行数据转换器来拷贝转移数据
status_t err = mCodec->mConverter[kPortIndexOutput]->convert(
info->mCodecData, buffer);
if (err != OK) {
// 转移失败,通知MediaCodec
mCodec->signalError(OMX_ErrorUndefined, makeNoSideEffectStatus(err));
return true;
}
}
#if 0
// 此代码块未打开,只用于debug
if (mCodec->mNativeWindow == NULL) {
// 非Surface输出时,判断是否为IDR帧即关键帧
if (IsIDR(info->mData->data(), info->mData->size())) {
ALOGI("IDR frame");
}
}
#endif
// 该情况暂不关注 TODO,不影响后续渲染流程
if (mCodec->mSkipCutBuffer != NULL) {
mCodec->mSkipCutBuffer->submit(buffer);
}
// 设置PTS
buffer->meta()->setInt64("timeUs", timeUs);
// 清除mData对实际输出数据负载的引用,因为数据已经转移到【mCodecData】去访问了
info->mData.clear();
// 执行Buffer通道消耗该输出Buffer处理流程
// 见2.2小节分析
mCodec->mBufferChannel->drainThisBuffer(info->mBufferID, flags);
// 归还Buffer完成后将其使用权改为【OWNED_BY_DOWNSTREAM】,相当于使用流端
info->mStatus = BufferInfo::OWNED_BY_DOWNSTREAM;
if (flags & OMX_BUFFERFLAG_EOS) {
// 若当前Buffer为EOS状态
ALOGV("[%s] saw output EOS", mCodec->mComponentName.c_str());
// 则回调MediaCodec的监听回调类的eos方法
// 见2.3小节分析
mCodec->mCallback->onEos(mCodec->mInputEOSResult);
// 记录该端口EOS状态为true
mCodec->mPortEOS[kPortIndexOutput] = true;
}
break;
}
case FREE_BUFFERS:
// 释放Buffer
// 见2.4小节分析
err = mCodec->freeBuffer(kPortIndexOutput, index);
if (err != OK) {
// 释放Buffer失败,通知MediaCodec该错误
mCodec->signalError(OMX_ErrorUndefined, makeNoSideEffectStatus(err));
return true;
}
break;
default:
ALOGE("Invalid port mode: %d", mode);
return false;
}
return true;
}
2.1、mCodec->onOutputFormatChanged(mCodec->mOutputFormat)
回调输出数据格式变化处理流程
// [frameworks/av/media/libstagefright/ACodec.cpp]
void ACodec::onOutputFormatChanged(sp<const AMessage> expectedFormat) {
// store new output format, at the same time mark that this is no longer the first frame
// 缓存旧的第一帧数据格式
mOutputFormat = mBaseOutputFormat->dup();
// 获取输出端口格式信息
// 见此前已有分析,其主要就是获取底层组件的该输出配置情况
if (getPortFormat(kPortIndexOutput, mOutputFormat) != OK) {
ALOGE("[%s] Failed to get port format to send format change", mComponentName.c_str());
return;
}
if (expectedFormat != NULL) {
// 新格式不为空时
sp<const AMessage> changes = expectedFormat->changesFrom(mOutputFormat);
sp<const AMessage> to = mOutputFormat->changesFrom(expectedFormat);
if (changes->countEntries() != 0 || to->countEntries() != 0) {
ALOGW("[%s] BAD CODEC: Output format changed unexpectedly from (diff) %s to (diff) %s",
mComponentName.c_str(),
changes->debugString(4).c_str(), to->debugString(4).c_str());
}
}
if (!mIsVideo && !mIsEncoder) {
// 音频解码器时
// 重新获取PCM编码相关新旧配置信息
AudioEncoding pcmEncoding = kAudioEncodingPcm16bit;
(void)mConfigFormat->findInt32("pcm-encoding", (int32_t*)&pcmEncoding);
AudioEncoding codecPcmEncoding = kAudioEncodingPcm16bit;
// 获取的底层组件关于输出端口的配置信息
(void)mOutputFormat->findInt32("pcm-encoding", (int32_t*)&codecPcmEncoding);
// 然后创建输出端口数据转换器
mConverter[kPortIndexOutput] = AudioConverter::Create(codecPcmEncoding, pcmEncoding);
if (mConverter[kPortIndexOutput] != NULL) {
// 再次更改为原来的配置信息,因为有了转换器
mOutputFormat->setInt32("pcm-encoding", pcmEncoding);
}
}
if (mTunneled) {
sendFormatChange();
}
}
2.2、mCodec->mBufferChannel->drainThisBuffer(info->mBufferID, flags)实现分析:
执行Buffer通道消耗该输出Buffer处理流程
由于本章节接下来内容篇幅过长,因此必须放入另一章节分析,请查看:
【六】Android MediaPlayer整体架构源码分析 -【start请求播放处理流程】【Part 10】【02】