• Android 13 - Media框架(9)- NuPlayer::Decoder


    这一节我们将了解 NuPlayer::Decoder,学习如何将 MediaCodec wrap 成一个强大的 Decoder。这一节会提前讲到 MediaCodec 相关的内容,如果看不大懂可以先跳过此篇。原先觉得 Decoder 部分简单,越读越发现自己的无知,Android 源码真是一个巨大的宝库!
    ps:本文中大写的 Decoder 指代的是 NuPlayer::Decoder,小写的 decoder指代 mediacodec 以及底层的真正的解码器

    1、DecoderBase

    首先看 NuPlayer::Decoder 的基类 DecoderBase

    struct NuPlayer::DecoderBase : public AHandler {
        explicit DecoderBase(const sp<AMessage> &notify);
        void configure(const sp<AMessage> &format);
        void init();
        void setParameters(const sp<AMessage> &params);
        // Synchronous call to ensure decoder will not request or send out data.
        void pause();
        void setRenderer(const sp<Renderer> &renderer);
        virtual status_t setVideoSurface(const sp<Surface> &) { return INVALID_OPERATION; }
        void signalFlush();
        void signalResume(bool notifyComplete);
        void initiateShutdown();
        virtual sp<AMessage> getStats() {
            return mStats;
        }
    protected:
    	virtual void onMessageReceived(const sp<AMessage> &msg);
    
        virtual void onConfigure(const sp<AMessage> &format) = 0;
        virtual void onSetParameters(const sp<AMessage> &params) = 0;
        virtual void onSetRenderer(const sp<Renderer> &renderer) = 0;
        virtual void onResume(bool notifyComplete) = 0;
        virtual void onFlush() = 0;
        virtual void onShutdown(bool notifyComplete) = 0;
        void onRequestInputBuffers();
        virtual bool doRequestBuffers() = 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    DecoderBase 定义了 NuPlayer 可以调用 Decoder 的所有接口,可以看到接口数量相当稀少,并没有 start、stop、reset、seek 等等方法,这时候可能就会有人有疑问了,我上层调用的这些接口为什么底层却没有了呢?其实在之前的文章中我们已经对部分接口做了解释,这里就不再赘述。

    来了解下这些接口都是用来干什么、怎么用的:

    • 构造函数:传入一个 AMessage 对象,用于上抛事件于状态;
    • configure:传入 Source 在 prepare 过程中 parse 出的 format 信息,format 信息包括 mime type、surface、secure、width、height、crypto、csd 等等信息;创建 MediaCodec 实例,配置并启动;
    • init:将自身注册到 ALooper 当中;
    • setParameters:给 decoder 设定上层传下的参数;
    • pause:这个方法其实并没有用,后面详细了解它为什么没有用;
    • setRenderer:设定 render,decoder 解出的数据将送到 render 中做 avsync,如果是 audio 数据将直接写入到 AudioTrack;
    • setVideoSurface:重新设定 surface,audio decoder 并不需要这个方法;
    • signalFlush:flush,刷新 decoder 的 input/ouput 缓冲区;
    • signalResume:恢复 decoder 的解码流程;
    • initiateShutdown:停止解码流程并释放相关资源;
    • getStats:获取当前 Decoder 的状态,例如 format 信息,当前解出的帧数,丢弃的帧数等等信息。
    • 其他:onConfigure 等等方法将由具体的 Decoder 来实现,如果 audio 不走 offload, audio / video decoder 会走相同的流程。

    2、Decoder 创建与启动

    从 NuPlayer 源码中我们可以知道,调用 start 方法后会创建 Decoder,这里的 Decoder 是继承于 DecoderBase 的,接着调用 Decoder.init 和 Decoder.configure,这里Decoder 就完成启动了:

    status_t NuPlayer::instantiateDecoder(
            bool audio, sp<DecoderBase> *decoder, bool checkAudioModeChange) {
    	sp<AMessage> format = mSource->getFormat(audio);
        *decoder = new Decoder(
              notify, mSource, mPID, mUID, mRenderer, mSurface, mCCDecoder);
        (*decoder)->init();
        (*decoder)->configure(format);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    init 方法很简单,就做了把 AHandler 注册到 ALooper 中这一件事。在这里我要抛出2个问题, registerHandler 注册的 this 指代的是谁?

    void NuPlayer::DecoderBase::init() {
        mDecoderLooper->registerHandler(this);
    }
    
    • 1
    • 2
    • 3

    后面 onRequestInputBuffers 中这个消息会先经 Decoder::onMessageReceived 处理还是 先经 DecoderBase::onMessageReceived 来处理呢?如果不太确定答案是什么可以搜索多态。

    void NuPlayer::DecoderBase::onRequestInputBuffers() {
    	....
        sp<AMessage> msg = new AMessage(kWhatRequestInputBuffers, this);
        msg->post(10 * 1000LL);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    继续往下看,configure 最终会调用到 onConfigure 方法中:

    void NuPlayer::Decoder::onConfigure(const sp<AMessage> &format) {
        ++mBufferGeneration;
    
        AString mime;
        CHECK(format->findString("mime", &mime));
    
        mIsAudio = !strncasecmp("audio/", mime.c_str(), 6);
        mComponentName = mime;
        mComponentName.append(" decoder");
    
        mCodec = MediaCodec::CreateByType(
                mCodecLooper, mime.c_str(), false /* encoder */, NULL /* err */, mPid, mUid, format);
        int32_t secure = 0;
        if (format->findInt32("secure", &secure) && secure != 0) {
            if (mCodec != NULL) {
                mCodec->getName(&mComponentName);
                mComponentName.append(".secure");
                mCodec->release();
                mCodec = MediaCodec::CreateByComponentName(
                        mCodecLooper, mComponentName.c_str(), NULL /* err */, mPid, mUid);
            }
        }
        err = mCodec->configure(
                format, mSurface, crypto, 0 /* flags */);
       	rememberCodecSpecificData(format);
        sp<AMessage> reply = new AMessage(kWhatCodecNotify, this);
        mCodec->setCallback(reply);
    
        err = mCodec->start();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    1. 查找 format 中的 mime,必须要有这个;
    2. 调用 CreateByType 创建 MediaCodec 实例,如果 format 中带有 secure 字段,那么就调用 CreateByComponentName 创建 Secure Component;
    3. 调用 configure 配置 MediaCodec,需要传入码流的 format,format 中需要有什么我们后面再看,这里传入的 surface 不为 NULL,因为 NuPlayer 中有判断,如果 surface 为 NULL 就不会为 Video 创建 Codec;
    4. 存储 format 中的 codec specific data(csd buffer),这些 buffer 记录了码流的信息,例如 h264,h265码流中的sps pps 等信息,对于有些 decoder 一定需要传入该信息,而有些 decoder 可以自己从码流中 parse 出来这些信息,具体要看各家的 decoder 实现;
    5. 给 MediaCodec 注册 callback,让它以异步的方式工作;
    6. 调用 start 方法启动 decoder,开启整个数据读取、数据解码 以及 数据渲染流程。

    以下内容是个人拙见,讲的比较啰嗦,如不喜欢直接跳过就好。

    除了这些,我们还要看一个成员 mBufferGeneration,这个东西是干什么的呢?其实我们之前已经说过 Media 这边用了跟多 generation 的思想或者说是trick,那这里的 generation 是用来干什么的呢?

    我们搜索代码中的 mBufferGeneration,发现它的值会在 onConfiguredoFlushonShutdownhandleError 中做修改,这四个方法会有一个共同点,它们都会去操作 MediaCodec,改变 MediaCodec 状态,从而影响到 MediaCodec Buffer 的状态。

    我们再看 mBufferGeneration 会在哪里使用:

    bool NuPlayer::Decoder::isStaleReply(const sp<AMessage> &msg) {
        int32_t generation;
        CHECK(msg->findInt32("generation", &generation));
        return generation != mBufferGeneration;
    }
    
    void NuPlayer::Decoder::onMessageReceived(const sp<AMessage> &msg) {
    	switch (msg->what()) {
            case kWhatRenderBuffer:
            {
                if (!isStaleReply(msg)) {
                    onRenderBuffer(msg);
                }
                break;
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    video output buffer 在送入 Renderer 做完 avsync 后送回来做渲染时会判断当前的 mBufferGeneration 是否有发生变化,这里这么做有什么用意呢?

    我的理解是这样:output buffer 送到 Renderer 中处理完成后,Renderer 会调用渲染相关的方法,但是这个时候 buffer 的状态可能已经发生了变化,例如做了 flush、或者是 shutdown,buffer 不需要再被处理,用 mBufferGeneration 来判断就可以跳过处理步骤。

    将一件事情交由其他组件处理时,记录当前的generation ,当事件处理结束并=返回到当前组件时,根据当前的 generation 决定内容是否需要丢弃。Android 在 ACodec、NuPlayer::Source、Renderer 等实现中都用了 generation 技巧来处理状态转换时的事务。

    以上是我看第一遍代码时对 generation 的理解,再次翻阅又有了些新的感悟:

    NuPlayer 中大量使用了 ALooper、AHandler 异步消息机制,这里的异步是相对与调用者而言的,譬如 NuPlayer 调用 Decoder 的 configure 方法,NuPlayer 调用完就结束了,这时候 Decoder 内的 MediaCodec 对象可能还没有创建,这就是异步。但是对于 Decoder 来说,所有的调用(发送来的消息)都是由 Looper 中的线程一条一条处理,所以 Decoder 内部是同步处理的。

    为什么要说这些呢?我们来看看都有谁会给 Decoder 发送消息:NuPlayer、Renderer、MediaCodec 它们都可能同时,或者先后向 Decoder 发送消息,这会引发什么问题呢?以 Renderer 为例子:

        sp<AMessage> reply = new AMessage(kWhatRenderBuffer, this);
        reply->setSize("buffer-ix", index);
        reply->setInt32("generation", mBufferGeneration);
        reply->setSize("size", size);
    
    • 1
    • 2
    • 3
    • 4

    Decoder 收到 MediaCodec 发送的 CB_OUTPUT_AVAILABLE 事件后,会将 mBufferGeneration 存到 msg 中,并且传递给 Renderer,Renderer 做完同步后会将消息重新发送到 Decoder。但是如果同步的过程中,上层调用了 reset,Decoder 也对事件做了处理,那么 Decoder 将不能再去处理 Renderer 发过来的渲染消息(整个流程已经停止,component 已经被释放了)。

    请添加图片描述

    generation 起着状态记录的作用,当状态发生改变后,依赖该状态的消息将会不再处理。它和一些具体的状态,例如 MediaPlayer.cpp 中的状态使用方法类似,但是 generation 的使用更为简单,它不关注具体是什么状态,只关注影响改变 Decoder 状态的方法是否被调用。

    3、Start

    我们引用上一小节中关于 start 的描述: 开启整个数据读取、数据解码 以及 数据渲染流程,start 不仅仅是启动了 MediaCodec,还驱动了所有组件的运行,一起来看看吧。

    先来看关键的 MediaCodec Callback:

    void NuPlayer::Decoder::onMessageReceived(const sp<AMessage> &msg) {
        switch (msg->what()) {
            case kWhatCodecNotify:
            {
                int32_t cbID;
                CHECK(msg->findInt32("callbackID", &cbID));
                switch (cbID) {
                    case MediaCodec::CB_INPUT_AVAILABLE:
                    {
                        int32_t index;
                        CHECK(msg->findInt32("index", &index));
    
                        handleAnInputBuffer(index);
                        break;
                    }
    
                    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;
                    }
                }
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    MediaCodec 通过 kWhatCodecNotify 将消息发送到 Decoder 来处理,用 callbackID 来区分发送过来的内容,常用的有 CB_INPUT_AVAILABLECB_OUTPUT_AVAILABLECB_ERRORCB_OUTPUT_FORMAT_CHANGED 这四个,我们这里只看 input 和 output。

    3.1、CB_INPUT_AVAILABLE

    收到 MediaCodec 上抛的 input 事件后,会调用 handleAnInputBuffer 方法,传入参数为 input buffer id。

    bool NuPlayer::Decoder::handleAnInputBuffer(size_t index) {
    	// 判断是否处在 处理不连续码流 的状态
        if (isDiscontinuityPending()) {
            return false;
        }
    
        sp<MediaCodecBuffer> buffer;
        mCodec->getInputBuffer(index, &buffer);
    
        if (index >= mInputBuffers.size()) {
            for (size_t i = mInputBuffers.size(); i <= index; ++i) {
                mInputBuffers.add();
                mInputBufferIsDequeued.add();
                mMediaBuffers.editItemAt(i) = NULL;
                mInputBufferIsDequeued.editItemAt(i) = false;
            }
        }
        mInputBuffers.editItemAt(index) = buffer;
        mInputBufferIsDequeued.editItemAt(index) = true;
    
    	// 如果有码流不连续的情况,恢复播放后重新发送csd buffer
        if (!mCSDsToSubmit.isEmpty()) {
            sp<AMessage> msg = new AMessage();
            msg->setSize("buffer-ix", index);
    
            sp<ABuffer> buffer = mCSDsToSubmit.itemAt(0);
            msg->setBuffer("buffer", buffer);
            mCSDsToSubmit.removeAt(0);
            if (!onInputBufferFetched(msg)) {
                handleError(UNKNOWN_ERROR);
                return false;
            }
            return true;
        }
    	// 如果有 buffer 没有成功写入 mediacodec 的情况,尝试重新写入
        while (!mPendingInputMessages.empty()) {
            sp<AMessage> msg = *mPendingInputMessages.begin();
            if (!onInputBufferFetched(msg)) {
                break;
            }
            mPendingInputMessages.erase(mPendingInputMessages.begin());
        }
    	// 如果在 尝试重新写入的过程中,把当前 buffer 也顺带处理了,那么就直接返回
        if (!mInputBufferIsDequeued.editItemAt(index)) {
            return true;
        }
    	// 将 buffer 记录到 mDequeuedInputBuffers 中
        mDequeuedInputBuffers.push_back(index);
    	// 尝试从 source 获取数据,填充数据,并送回 decoder
        onRequestInputBuffers();
        return true;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52

    由于这里涉及到 Source 数据的获取 和 input buffer 写入两部分内容,同时考虑了数据获取失败和数据写入失败的问题,所以 input buffer 的处理流程看起来会比较复杂,不过我们不要着急,我们一步步解析。

    首先来看 handleAnInputBuffer 做的事情(省略了部分注释的内容):

    1. 判断当前是否正在处理码流不连续的情况;
    2. 从 MediaCodec 获取对应索引的 MediaCodecBuffer
    3. 将获取的到的 MediaCodecBuffer 按照索引记录到 mInputBuffers 列表当中;
    4. 创建一个列表 mInputBufferIsDequeued,记录索引对应的 input buffer 是否有出队列;
    5. 每次进行了 flush,需要送 csd buffer 给 decoder,记住是每次!其实不是所有的 decoder flush 之后都要 csd buffer 的,之前被坑过!
    6. 先处理被延迟的 input buffer,为什么延迟后面再说;
    7. 处理延迟 buffer 时可能会把当前的 input buffer 处理掉,如果记录出队列的列表中对应位置为 false 说明已经被处理过了;
    8. 如果 input buffer 没有处理,那么再把它加入到一个没有处理的列表当中 mDequeuedInputBuffers
    9. 调用 onRequestInputBuffers 从 Source 读取数据;

    这里涉及四个方法,名字长得比较像,先来介绍下它们是做什么用的:

    • onRequestInputBuffers:从 Source 请求 input data;
    • doRequestBuffers:onRequestInputBuffers 的 内部实现;
    • onInputBufferFetched:成功从Source 获取到数据,填充到 input buffer 并返回给 MediaCodec;
    • fetchInputData:onInputBufferFetched 的内部实现;

    我们从 onRequestInputBuffers 看起,这个方法实现在 DecoderBase 中,权限为 protected:

    void NuPlayer::DecoderBase::onRequestInputBuffers() {
    	// 判断是否处在 处理不连续码流 的状态
        if (mRequestInputBuffersPending) {
            return;
        }
    
        // doRequestBuffers() return true if we should request more data
        // 从 Source 请求数据,如果失败返回 true,发送一条延时消息,retry
        if (doRequestBuffers()) {
        	// retry 时不会继续处理 获取数据的调用
            mRequestInputBuffersPending = true;
    
            sp<AMessage> msg = new AMessage(kWhatRequestInputBuffers, this);
            msg->post(10 * 1000LL);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    可以看到 onRequestInputBuffers 就是封装了 doRequestBuffers,所以它们的作用是相同的,但是一个是基类的方法,一个是子类的方法。这么设计有什么用呢?我的理解是这样:每个 Decoder 都需要从 Source 获取数据,所以把获取数据的方法 onRequestInputBuffers 定义在基类当中,但是每个 Decoder 获取数据的方式或者流程又不一样,所以把 doRequestBuffers 放到子类中实现。子类调用父类的 onRequestInputBuffers 方法使用父类定义的数据读取流程,流程中调用子类的数据读取实现,这样就一举两得,既统一了读取流程又区分了读取方式。

    另外这里还有 mRequestInputBuffersPending 的用法值得学习,如果从 Source 获取数据失败了,那么需要做延时等待,并且重新尝试获取,等待的过程中我们并不希望外部能够再调用到 doRequestBuffers 获取数据,所以将 mRequestInputBuffersPending 置为 true,表示等待的状态,这个状态只有处理 retry 消息时才能够解除。

    bool NuPlayer::Decoder::doRequestBuffers() {
        if (isDiscontinuityPending()) {
            return false;
        }
        status_t err = OK;
        while (err == OK && !mDequeuedInputBuffers.empty()) {
            size_t bufferIx = *mDequeuedInputBuffers.begin();
            sp<AMessage> msg = new AMessage();
            msg->setSize("buffer-ix", bufferIx);
            err = fetchInputData(msg);
            if (err != OK && err != ERROR_END_OF_STREAM) {
                // if EOS, need to queue EOS buffer
                break;
            }
            mDequeuedInputBuffers.erase(mDequeuedInputBuffers.begin());
    
            if (!mPendingInputMessages.empty()
                    || !onInputBufferFetched(msg)) {
                mPendingInputMessages.push_back(msg);
            }
        }
    
        return err == -EWOULDBLOCK
                && mSource->feedMoreTSData() == OK;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    doRequestBuffers 里有个循环,会把当前 mDequeuedInputBuffers 中的所有 input buffer 都处理掉。这里有个问题,什么时候 mDequeuedInputBuffers 中 buffer 的数量会大于 1 呢?从 source 读取数据失败时会直接返回,没能调用 erase 方法,这时候 mDequeuedInputBuffers 的数量会大于1。

    status_t NuPlayer::Decoder::fetchInputData(sp<AMessage> &reply) {
        sp<ABuffer> accessUnit;
        bool dropAccessUnit = true;
        do {
        	// 从 Source 获取数据
            status_t err = mSource->dequeueAccessUnit(mIsAudio, &accessUnit);
    		// 判断返回值,如果是 EWOULDBLOCK 那么说明没读到数据,如果是其他的返回值则说名读到数据
            if (err == -EWOULDBLOCK) {
                return err;
            } else if (err != OK) {
            	// 如果 error 不等于 OK,说明码流出现了一些情况
                if (err == INFO_DISCONTINUITY) {
                    int32_t type;
                    // 获取码流不连续的原因
                    CHECK(accessUnit->meta()->findInt32("discontinuity", &type));
    
                    bool formatChange =
                        (mIsAudio &&
                         (type & ATSParser::DISCONTINUITY_AUDIO_FORMAT))
                        || (!mIsAudio &&
                                (type & ATSParser::DISCONTINUITY_VIDEO_FORMAT));
    
                    bool timeChange = (type & ATSParser::DISCONTINUITY_TIME) != 0;
    
                    ALOGI("%s discontinuity (format=%d, time=%d)",
                            mIsAudio ? "audio" : "video", formatChange, timeChange);
    
                    bool seamlessFormatChange = false;
                    sp<AMessage> newFormat = mSource->getFormat(mIsAudio);
                    // 如果是格式变化
                    if (formatChange) {
                    	// 判断当前播放的码流格式是否支持无缝切换
                        seamlessFormatChange =
                            supportsSeamlessFormatChange(newFormat);
                        // treat seamless format change separately
                        formatChange = !seamlessFormatChange;
                    }
    
                    // For format or time change, return EOS to queue EOS input,
                    // then wait for EOS on output.
                    // 如果不支持无缝切换,那么就要向 decoder 填充 eos
                    if (formatChange /* not seamless */) {
                        mFormatChangePending = true;
                        err = ERROR_END_OF_STREAM;
                    } else if (timeChange) {
                    	// 如果pts不连续,那么就要向 decoder 填充 eos,恢复播放后要 发送 csd buffer
                        rememberCodecSpecificData(newFormat);
                        mTimeChangePending = true;
                        err = ERROR_END_OF_STREAM;
                    } else if (seamlessFormatChange) {
                        // reuse existing decoder and don't flush
                        // 如果是无缝切换,那么仍要发送 csd buffer
                        rememberCodecSpecificData(newFormat);
                        continue;
                    } else {
                        // This stream is unaffected by the discontinuity
                        return -EWOULDBLOCK;
                    }
                }
                // reply should only be returned without a buffer set
                // when there is an error (including EOS)
                CHECK(err != OK);
    
                reply->setInt32("err", err);
                return ERROR_END_OF_STREAM;
            }
    		// 以下是 drop 机制
            dropAccessUnit = false;
            if (!mIsAudio && !mIsEncrypted) {
    			// 如果视频流慢了 100ms,视频为avc,并且不是参考帧,那么就drop掉当前读取的内容
                if (mRenderer->getVideoLateByUs() > 100000LL
                        && mIsVideoAVC
                        && !IsAVCReferenceFrame(accessUnit)) {
                    dropAccessUnit = true;
                } 
                if (dropAccessUnit) {
                    ++mNumInputFramesDropped;
                }
            }
        } while (dropAccessUnit);
    
        reply->setBuffer("buffer", accessUnit);
    
        return OK;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85

    fetchInputData 不仅仅是获取了数据,还对码流的异常情况做了处理。dequeueAccessUnit 有 四种返回值:

    • OK:获取到有效数据;
    • -EWOULDBLOCK:未能读取到数据;
    • INFO_DISCONTINUITY:码流不连续;
    • ERROR_END_OF_STREAM:读到文件末尾;

    返回值为 OK 和 ERROR_END_OF_STREAM 属于正常情况;-EWOULDBLOCK 会直接返回并尝试 retry;INFO_DISCONTINUITY 说明码流出现了不连续的情况,可能是调用了 selectTrack 或者是 seek。

    码流不连续分为两种情况:

    • 码流的格式发生变化,error 为 DISCONTINUITY_VIDEO_FORMAT,格式变化分别宽高变化和mime type变化两种,可能出现在 selectTrack 调用之后;
    • 码流的pts不连续,error 为 DISCONTINUITY_TIME,可能出现在 seek 调用后,或者是码流播放结束 pts 回跳时。

    一是码流的格式发生变化 DISCONTINUITY_VIDEO_FORMAT引发的 flush,可能出现在 selectTrack 时;另外一种是码流 pts 回绕 DISCONTINUITY_TIME,这种情况出现在直播的回播中比较多。

    如果是格式发生变化,那么会判断当前播放的码流是否支持 无缝切换(adaptive-playback),如果支持则不对该事件做处理,如果不支持把返回值设置为 ERROR_END_OF_STREAM。

    如果是 pts不连续 则会直接将返回值设置为 ERROR_END_OF_STREAM。由于设置了 ERROR_END_OF_STREAM,那么重新开始播放之后需要先填充 csd buffer。

    fetchInputData 还为 AVC 格式的码流设计了一套 drop 机制,如果视频流慢于音频100ms,并且当前帧不是参考帧,那么就 drop 掉该帧。

    fetchInputData 调用成功后就该调用 onInputBufferFetched,把获取到的数据填充到 input buffer 中并且送回到 MediaCodec,这里比较简单,就是做了数据拷贝而已,要看的只有 EOS 一点。EOS 有两种情况,一种是 buffer 为空,说明当前已经收到 ERROR_END_OF_STREAM;另一种是 buffer 不为空,返回值为 OK,但是 bufferMeta 中有 eos 信息。

    如果是码流结束,eos 信息送出后 fetchInputData 将不会读到任何数据。

    如果是因为码流不连续发送了 eos,input buffer 处理流程将会被 isDiscontinuityPending 中断,等到前面的数据都解码渲染完成,再处理 Discontinuity 事件,处理完成后才会写入下一个序列的数据,这部分我们放到下一小节来看。

    bool NuPlayer::Decoder::isDiscontinuityPending() const {
        return mFormatChangePending || mTimeChangePending;
    }
    
    • 1
    • 2
    • 3

    3.2、CB_OUTPUT_AVAILABLE

    output buffer 的处理流程相对 input 来说会简单很多,主要是调用了 handleAnOutputBuffer 方法:

    bool NuPlayer::Decoder::handleAnOutputBuffer(
            size_t index,
            size_t offset,
            size_t size,
            int64_t timeUs,
            int32_t flags) {
        sp<MediaCodecBuffer> buffer;
        // 获取 output buffer
        mCodec->getOutputBuffer(index, &buffer);
    
        int64_t frameIndex;
        bool frameIndexFound = buffer->meta()->findInt64("frameIndex", &frameIndex);
    
        buffer->setRange(offset, size);
        // 设置 pts
        buffer->meta()->clear();
        buffer->meta()->setInt64("timeUs", timeUs);
        if (frameIndexFound) {
            buffer->meta()->setInt64("frameIndex", frameIndex);
        }
    	// 判断 output buffer 是否到达 eos
        bool eos = flags & MediaCodec::BUFFER_FLAG_EOS;
        // we do not expect CODECCONFIG or SYNCFRAME for decoder
    
    	// 创建 reply,设置 generation,avsync完成后 renderer 通过该消息 callback 回来
        sp<AMessage> reply = new AMessage(kWhatRenderBuffer, this);
        reply->setSize("buffer-ix", index);
        reply->setInt32("generation", mBufferGeneration);
        reply->setSize("size", size);
    	// 如果出现 eos 则在 reply 中也进行标记
        if (eos) {
            ALOGV("[%s] saw output EOS", mIsAudio ? "audio" : "video");
    
            buffer->meta()->setInt32("eos", true);
            reply->setInt32("eos", true);
        }
    
        mNumFramesTotal += !mIsAudio;
    	// 判断 input buffer 有没有设定起播时间
        if (mSkipRenderingUntilMediaTimeUs >= 0) {
            if (timeUs < mSkipRenderingUntilMediaTimeUs) {
                ALOGV("[%s] dropping buffer at time %lld as requested.",
                         mComponentName.c_str(), (long long)timeUs);
    
                reply->post();
                if (eos) {
                    notifyResumeCompleteIfNecessary();
                    if (mRenderer != NULL && !isDiscontinuityPending()) {
                        mRenderer->queueEOS(mIsAudio, ERROR_END_OF_STREAM);
                    }
                }
                return true;
            }
            mSkipRenderingUntilMediaTimeUs = -1;
        }
    
        // wait until 1st frame comes out to signal resume complete
        // 播放停止后重新恢复播放,等待第一帧到达后上抛消息,在seek时用到
        notifyResumeCompleteIfNecessary();
    
        if (mRenderer != NULL) {
            // send the buffer to renderer.
            // 将 ouput buffer 送到 renderer 做 avsync
            mRenderer->queueBuffer(mIsAudio, buffer, reply);
            // 如果到达 eos,并且不是因为码流中断,调用queueEOS
            if (eos && !isDiscontinuityPending()) {
                mRenderer->queueEOS(mIsAudio, ERROR_END_OF_STREAM);
            }
        }
    
        return true;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    1. 获取 output buffer;
    2. 创建 reply,设置 generation,avsync 完成后 Renderer 通过该消息 callback 到 Decoder;
    3. 判断 output buffer flag 是否是 eos,如果是则在 reply 中进行标记;
    4. queue input buffer 时可能设有开始渲染的 pts,output buffer pts 小于该 pts 时直接 drop;
    5. 将 output buffer 和 reply message 一起送到 Renderer,如果到达 eos,且不是因为码流不连续,还要给 Renderer 送一个 EOS;

    3.3、CB_OUTPUT_FORMAT_CHANGED

    虽然我们使用 decoder 时都会传 input format 下去,但是 decoder 收到数据后仍会自己解析格式,并且上抛 output format change 事件,上层收到事件后需要做对应的处理。

    void NuPlayer::Decoder::handleOutputFormatChange(const sp<AMessage> &format) {
        if (!mIsAudio) {
            int32_t width, height;
            if (format->findInt32("width", &width)
                    && format->findInt32("height", &height)) {
                Mutex::Autolock autolock(mStatsLock);
                mStats->setInt32("width", width);
                mStats->setInt32("height", height);
            }
            sp<AMessage> notify = mNotify->dup();
            notify->setInt32("what", kWhatVideoSizeChanged);
            notify->setMessage("format", format);
            notify->post();
        } else if (mRenderer != NULL) {
            uint32_t flags;
            int64_t durationUs;
            bool hasVideo = (mSource->getFormat(false /* audio */) != NULL);
            if (getAudioDeepBufferSetting() // override regardless of source duration
                    || (mSource->getDuration(&durationUs) == OK
                            && durationUs > AUDIO_SINK_MIN_DEEP_BUFFER_DURATION_US)) {
                flags = AUDIO_OUTPUT_FLAG_DEEP_BUFFER;
            } else {
                flags = AUDIO_OUTPUT_FLAG_NONE;
            }
    
            sp<AMessage> reply = new AMessage(kWhatAudioOutputFormatChanged, this);
            reply->setInt32("generation", mBufferGeneration);
            mRenderer->changeAudioFormat(
                    format, false /* offloadOnly */, hasVideo,
                    flags, mSource->isStreaming(), reply);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    如果是 Video Format 发生改变,继续上抛事件即可。如果是 Audio Format 发生改变,Decoder 需要调用 Renderer.changeAudioFormat 来重新打开 AudioTrack,具体如何处理在 Renderer 篇中会简单介绍。

    3.4、kWhatRenderBuffer

    上一节我们讲到 Renderer 做完 avsync 后会以消息的形式 callback 给 Decoder:

            case kWhatRenderBuffer:
            {
                if (!isStaleReply(msg)) {
                    onRenderBuffer(msg);
                }
                break;
            }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    isStaleReply 我们在上面已经做过解释了,这里不再赘述,主要来看 onRenderBuffer:

    void NuPlayer::Decoder::onRenderBuffer(const sp<AMessage> &msg) {
        status_t err;
        int32_t render;
        size_t bufferIx;
        int32_t eos;
        size_t size;
        // 查找要渲染的output buffer index
        CHECK(msg->findSize("buffer-ix", &bufferIx));
    
        if (mCodec == NULL) {
            err = NO_INIT;
        } else if (msg->findInt32("render", &render) && render) { // 判断是否render
            int64_t timestampNs;
            CHECK(msg->findInt64("timestampNs", &timestampNs));	// 获取render时间
            err = mCodec->renderOutputBufferAndRelease(bufferIx, timestampNs);
        } else {
        	// 如果是 eos 或者 不render 则直接 drop
            if (!msg->findInt32("eos", &eos) || !eos ||
                    !msg->findSize("size", &size) || size) {
                mNumOutputFramesDropped += !mIsAudio;
            }
            err = mCodec->releaseOutputBuffer(bufferIx);
        }
    	// 如果是因为码流不连续造成的eos,则处理不连续事件
        if (msg->findInt32("eos", &eos) && eos
                && isDiscontinuityPending()) {
            finishHandleDiscontinuity(true /* flushOnTimeChange */);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    onRenderBuffer 主要是用来处理 Video 的,Renderer 确定该帧要渲染,那么就调用 renderOutputBufferAndRelease,否则调用 releaseOutputBuffer。

    如果 reply message 中包含有 eos,那么会判断是否因为码流不连续而造成的 eos。我们要注意的是,Renderer 真正执行到 EOS 时,事件并不会发送到 Decoder中,Decoder 只处理 buffer 事件。

    这里我们回过头来看 finishHandleDiscontinuity 是如何处理码流异常的:

    void NuPlayer::Decoder::finishHandleDiscontinuity(bool flushOnTimeChange) {
        ALOGV("finishHandleDiscontinuity: format %d, time %d, flush %d",
                mFormatChangePending, mTimeChangePending, flushOnTimeChange);
    
        // If we have format change, pause and wait to be killed;
        // If we have time change only, flush and restart fetching.
    
        if (mFormatChangePending) {
            mPaused = true;
        } else if (mTimeChangePending) {
            if (flushOnTimeChange) {
                doFlush(false /* notifyComplete */);
                signalResume(false /* notifyComplete */);
            }
        }
    
        // Notify NuPlayer to either shutdown decoder, or rescan sources
        sp<AMessage> msg = mNotify->dup();
        msg->setInt32("what", kWhatInputDiscontinuity);
        msg->setInt32("formatChange", mFormatChangePending);
        msg->post();
    
        mFormatChangePending = false;
        mTimeChangePending = false;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    从注释中我们可以看到针对 format change 和 time change 处理方式是不一样的:

    • format change:暂停 buffer 处理流程,等待重启 decoder;
    • time change:flush 然后调用 resume 恢复;

    format change 中提到一个暂停,将 mPaused 置为 true,onMessageReceived 将不会再处理送来的 buffer,要注意的是,这个 pause 并不是用于播放暂停。format change 的事件要送到 NuPlayer 中:

                if (what == DecoderBase::kWhatInputDiscontinuity) {
                    int32_t formatChange;
                    CHECK(msg->findInt32("formatChange", &formatChange));
    
                    ALOGV("%s discontinuity: formatChange %d",
                            audio ? "audio" : "video", formatChange);
    
                    if (formatChange) {
                        mDeferredActions.push_back(
                                new FlushDecoderAction(
                                    audio ? FLUSH_CMD_SHUTDOWN : FLUSH_CMD_NONE,
                                    audio ? FLUSH_CMD_NONE : FLUSH_CMD_SHUTDOWN));
                    }
    
                    mDeferredActions.push_back(
                            new SimpleAction(
                                    &NuPlayer::performScanSources));
    
                    processDeferredActions();
                } 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    NuPlayer 会执行 FlushDecoderAction,并且进行 shutdown 释放当前 decoder,然后再重新调用 performScanSources 为新的 format 创建 decoder。

    4、signalFlush

    void NuPlayer::Decoder::doFlush(bool notifyComplete) {
        if (mCCDecoder != NULL) {
            mCCDecoder->flush();
        }
    
        if (mRenderer != NULL) {
            mRenderer->flush(mIsAudio, notifyComplete);
            mRenderer->signalTimeDiscontinuity();
        }
    
        status_t err = OK;
        if (mCodec != NULL) {
            err = mCodec->flush();
            mCSDsToSubmit = mCSDsForCurrentFormat; // copy operator
            ++mBufferGeneration;
        }
    
        if (err != OK) {
            ALOGE("failed to flush [%s] (err=%d)", mComponentName.c_str(), err);
            handleError(err);
            // finish with posting kWhatFlushCompleted.
            // we attempt to release the buffers even if flush fails.
        }
        releaseAndResetMediaBuffers();
        mPaused = true;
    }
    
    void NuPlayer::Decoder::onFlush() {
        doFlush(true);
    
        if (isDiscontinuityPending()) {
            // This could happen if the client starts seeking/shutdown
            // after we queued an EOS for discontinuities.
            // We can consider discontinuity handled.
            finishHandleDiscontinuity(false /* flushOnTimeChange */);
        }
    
        sp<AMessage> notify = mNotify->dup();
        notify->setInt32("what", kWhatFlushCompleted);
        notify->post();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41

    flush 比较简单,就不多说废话啦。主要工作是调用 Renderer 的 flush,重置 Renderer 的状态,调用 MediaCodec 的 flush,刷新 input buffer 和 output buffer 缓冲区,注意这个方法调用会修改 mBufferGeneration,最后将 Decoder 存储的 buffer 列表都清空。

    我们在上一节看到 finishHandleDiscontinuity 中调用的是 doFlush,所以是不会有 kWhatFlushCompleted 事件发送到 NuPlayer 的。

    5、initiateShutdown

    void NuPlayer::Decoder::onShutdown(bool notifyComplete) {
        status_t err = OK;
    
        // if there is a pending resume request, notify complete now
        notifyResumeCompleteIfNecessary();
    
        if (mCodec != NULL) {
        	// 释放decoder
            err = mCodec->release();
            // 释放 MediaCodec
            mCodec = NULL;
            // 修改 generation 阻止渲染
            ++mBufferGeneration;
    
            if (mSurface != NULL) {
                // reconnect to surface as MediaCodec disconnected from it
                status_t error = nativeWindowConnect(mSurface.get(), "onShutdown");
                ALOGW_IF(error != NO_ERROR,
                        "[%s] failed to connect to native window, error=%d",
                        mComponentName.c_str(), error);
            }
            mComponentName = "decoder";
        }
    	// 释放 buffer list
        releaseAndResetMediaBuffers();
    
        if (err != OK) {
            ALOGE("failed to release [%s] (err=%d)", mComponentName.c_str(), err);
            handleError(err);
            // finish with posting kWhatShutdownCompleted.
        }
    
        if (notifyComplete) {
            sp<AMessage> notify = mNotify->dup();
            notify->setInt32("what", kWhatShutdownCompleted);
            notify->post();
            // 停止处理 buffer 事件
            mPaused = true;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    shutdown 也很简单:

    1. 调用 MediaCodec 的 release 方法,释放 decoder;
    2. 释放掉 MediaCodec 对象;
    3. 修改 generation,停止处理 render 事件;
    4. 释放 buffer list;
    5. 将 mPaused 置为 true,停止处理 buffer 事件;

    6、signalResume

    void NuPlayer::Decoder::onResume(bool notifyComplete) {
        mPaused = false;
    
        if (notifyComplete) {
            mResumePending = true;
        }
    
        if (mCodec == NULL) {
            ALOGE("[%s] onResume without a valid codec", mComponentName.c_str());
            handleError(NO_INIT);
            return;
        }
        mCodec->start();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    flush 之后要调用 signalResume 才能启动 MediaCodec 恢复解码流程,核心就是调用 MediaCodec 的 start 方法。这里的 mResumePending 是在 decoder 送过来第一帧 ouput buffer 时来判断是否需要发送 kWhatResumeCompleted 给 NuPlayer 的。

    7、总结

    没有其他的,感悟是 Android ALooper 机制领悟的还不够深刻,设计模式也不会,接下来会继续加强这部分的学习!

    以上内容如果有错误请不要吝啬指导。

    如果觉得对您有帮助,还请不要吝啬点赞、收藏与关注哦,您的支持是我更新的最大动力。

    如需阅读其他 Android Media 框架内容,还请移步 https://blog.csdn.net/qq_41828351?spm=1000.2115.3001.5343

  • 相关阅读:
    Springboot自定义starter
    [Qt]QMainWindow
    外包就干了2个月,技术退步明显....
    AndroidStudio模拟器,没有Google Play的就有ROOT权限
    C语言中的动态数组:原理、实现与应用
    hibernate ehcache.xml
    BATJ和字节跳动这些大厂的内部面试解析,面试重难点超出你的想象
    是否可以从一个static方法内部发出对非static方法的调用
    nginx如何安装 以及nginx的配置文件
    懒羊羊闲话2
  • 原文地址:https://blog.csdn.net/qq_41828351/article/details/132589796