【六】Android MediaPlayer整体架构源码分析 -【start请求播放处理流程】【Part 10】【02】

承接上一章节分析:【六】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】部分的分析

本章结束

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值