美文网首页
Android多媒体框架--11:Decoder解码器流程分析

Android多媒体框架--11:Decoder解码器流程分析

作者: DarcyZhou | 来源:发表于2023-04-25 08:17 被阅读0次

1.概述

  NuPlayer的解码模块相对比较简单,统一使用了一个基类NuPlayerDecoderBase管理,该类中包含了一个MediaCodec的对象,实际解码工作全靠MediaCodec。下图是MediaCodec在整个框架中的位置:

01.png

2.解码器创建

  解码器创建的入口在NuPlayer的NuPlayer::instantiateDecoder函数调用时。NuPlayer在执行start函数后,会通过一系列调用链,触发该函数。调用流程如下:

02.png

  (1)先看到NuPlayer::postScanSources():

void NuPlayer::postScanSources() {
    if (mScanSourcesPending) {
        return;
    }

    sp<AMessage> msg = new AMessage(kWhatScanSources, this);
    msg->setInt32("generation", mScanSourcesGeneration);
    msg->post();

    mScanSourcesPending = true;
}

又是简单的一行,但是里面做了很多工作,包括初始化Decoder,并且启动Decoder:

void NuPlayer::onMessageReceived(const sp<AMessage> &msg) {
case kWhatScanSources:
        {
            int32_t generation;
            CHECK(msg->findInt32("generation", &generation));
            if (generation != mScanSourcesGeneration) {
                // Drop obsolete msg.
                break;
            }

            mScanSourcesPending = false;

            ALOGV("scanning sources haveAudio=%d, haveVideo=%d",
                 mAudioDecoder != NULL, mVideoDecoder != NULL);

            bool mHadAnySourcesBefore =
                (mAudioDecoder != NULL) || (mVideoDecoder != NULL);

            // initialize video before audio because successful initialization of
            // video may change deep buffer mode of audio.
            if (mSurface != NULL) {
                instantiateDecoder(false, &mVideoDecoder);// 视频解码器
            }

            // Don't try to re-open audio sink if there's an existing decoder.
            if (mAudioSink != NULL && mAudioDecoder == NULL) {
                instantiateDecoder(true, &mAudioDecoder); // 音频解码器
            }

            if (!mHadAnySourcesBefore
                    && (mAudioDecoder != NULL || mVideoDecoder != NULL)) {
                // This is the first time we've found anything playable.

                if (mSourceFlags & Source::FLAG_DYNAMIC_DURATION) {
                    schedulePollDuration();
                }
            }

            status_t err;
            if ((err = mSource->feedMoreTSData()) != OK) {
                if (mAudioDecoder == NULL && mVideoDecoder == NULL) {
                    // We're not currently decoding anything (no audio or
                    // video tracks found) and we just ran out of input data.

                    if (err == ERROR_END_OF_STREAM) {
                        notifyListener(MEDIA_PLAYBACK_COMPLETE, 0, 0);
                    } else {
                        notifyListener(MEDIA_ERROR, MEDIA_ERROR_UNKNOWN, err);
                    }
                }
                break;
            }

            if ((mAudioDecoder == NULL && mAudioSink != NULL)
                    || (mVideoDecoder == NULL && mSurface != NULL)) {
                msg->post(100000ll);
                mScanSourcesPending = true;
            }
            break;
        }

这里会根据是否设置了Surface来决定要不要创建VideoDecoder,同时根据mAudioSink是否存在来决定要不要创建AudioDecoder,都是通过instantiateDecoder函数来完成的,所以下面看这个函数:

// 参数部分:audio true调用者想要创建音频解码器, false 想要创建视频解码器
// 参数部分:*decoder 该函数最终会创建指定解码器,使用该函数将解码器对象地址提供给调用者
status_t NuPlayer::instantiateDecoder(
        bool audio, sp<DecoderBase> *decoder, bool checkAudioModeChange) {
    ...
    // 其实就是GenericSource中的MetaData
    sp<AMessage> format = mSource->getFormat(audio);
    ...
    if (!audio) {// 视频
        ...
        if (mCCDecoder == NULL) {
            mCCDecoder = new CCDecoder(ccNotify);// 创建字幕解码器
        }
        ...
    }
// 创建音/视频解码器
    if (audio) {// 音频解码器
            ...
            *decoder = AVNuFactory::get()->createDecoder(notify, mSource, mPID, mUID, mRenderer);//最终调用NuPlayer::Decoder
            ...
    } else {// 视频解码器
        ...
        *decoder = new Decoder(
                notify, mSource, mPID, mUID, mRenderer, mSurface, mCCDecoder);
        ...
    }
    (*decoder)->init();
    (*decoder)->configure(format);

    if (!audio) {// 视频
        sp<AMessage> params = new AMessage();
        float rate = getFrameRate();
        if (rate > 0) {
            params->setFloat("frame-rate-total", rate);
        }
        ...
        if (params->countEntries() > 0) {
            (*decoder)->setParameters(params);
        }
    }
    return OK;
}

CCDecoder是字幕解码器,在new Video Decoder的时候,还会把这个CCDecoder作为一个参数传进去。解码器创建成功后会init()和configure()方法进行初始化和设置。

//new视频解码器,这里还会把字幕解码器作为一个参数传进来
*decoder = new Decoder( notify, mSource, mPID, mRenderer, mSurface, mCCDecoder);

(*decoder)->init(); //解码器初始化

(*decoder)->configure(format); //解码器格式化

(*decoder)->init();的实现在NuPlayerDecoderBase.cpp中,里面只是注册了Looper的Handler。

3.Decoder

  音/视频解码器都是通过Decoder进行创建,它继承于DecoderBase。Decoder定义如下:

struct NuPlayer::Decoder : public DecoderBase {
    Decoder(const sp<AMessage> &notify,
            const sp<Source> &source,
            pid_t pid,
            uid_t uid,
            const sp<Renderer> &renderer = NULL,//渲染器
            const sp<Surface> &surface = NULL,//视频播放的surface实体
            const sp<CCDecoder> &ccDecoder = NULL);//字幕解码器
    ...
protected:
    virtual ~Decoder();

DecoderBase定义如下:

struct ABuffer;
struct MediaCodec;
class MediaBuffer;
class MediaCodecBuffer;
class Surface;
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);
protected:
    virtual ~DecoderBase();
    void stopLooper();
    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;
};

可以从DecoderBase的实现看到,它包含了所有解码相关的接口,这些接口往往都和MediaCodec的接口直接相关。

3.1 (*decoder)->init()

  解码器创建完成后会对其进行初始化操作,直接在Decoder类中查找init()并不能找到,因为Decoder继承与DecoderBase,所以这里执行的应该是DecoderBase的init函数:

void NuPlayer::DecoderBase::init() {
    mDecoderLooper->registerHandler(this);
}

DecoderBase的构造函数中,已经创建了一套NativeHandler体系,并将Looper启动,只是没有将AHandler的子类对象和ALooper绑定,直到init()函数执行后,这种绑定关系才算结束。

3.2 (*decoder)->configure(format)

  configure函数实际上是在DecoderBase中实现,最终调用了DecoderBase的纯虚构函数:onConfigure,让它的子类去实现具体的配置方法:

void NuPlayer::Decoder::onConfigure(const sp<AMessage> &format) {
    CHECK(mCodec == NULL);
    ...
    AString mime;
    CHECK(format->findString("mime", &mime));
    ...
    if (mCodec == NULL) {
    // 根据需要创建的解码器类型创建解码器
    mCodec = MediaCodec::CreateByType(
            mCodecLooper, mime.c_str(), false /* encoder */, NULL /* err */, mPid, mUid);
    }
    ...
    err = mCodec->configure(
            format, mSurface, crypto, 0 /* flags */);//配置解码器
    ...
    sp<AMessage> reply = new AMessage(kWhatCodecNotify, this);
    mCodec->setCallback(reply);//配置解码器回调

    err = mCodec->start();//启动解码器
    ...
}
------------------------
sp<MediaCodec> MediaCodec::CreateByType(
        const sp<ALooper> &looper, const char *mime, bool encoder, status_t *err, pid_t pid) {
    sp<MediaCodec> codec = new MediaCodec(looper, pid);

    const status_t ret = codec->init(mime, true /* nameIsType */, encoder);
    if (err != NULL) {
        *err = ret;
    }
    return ret == OK ? codec : NULL; // NULL deallocates codec.
}

  这里只分析到MediaCodec.cpp这一层,不继续向下分析ACodec和OMX。onConfigure()函数中new了一个MediaCodec类,这个类就可以理解为Decoder的wrapper,它的下一层是ACodec,每个ACodec对应一个解码器,在codec->init中会为MediaCodec中的mCodec赋值。

  这里还有一点需要注意,在NuPlayer::Decoder中有个mCodec是sp<MediaCodec> 类型的,在MediaCodec中也有一个mCodec是sp<CodecBase> 类型的,即ACodec的父类,注意区分这两个,如果在NuPlayerDecoder.cpp中使用mCodec就是跳到MediaCodec.cpp中了,如果在MediaCodec.cpp中使用mCodec,就是对应ACodec.cpp中。

  (1)接下来,创建MediaCodec后回去调用它的configure函数:

err = mCodec->configure(format, mSurface, NULL /* crypto */, 0 /* flags */);

  这里的mCodec是sp<MediaCodec> 类型的,所以这个函数是MediaCodec::configure()函数,在这个函数中设置了Vector<MediaResource>,然后发送kWhatConfigure这个AMessage,在MediaCodec::onMessageReceived函数的kWhatConfigure case中,通过 handleSetSurface()来设置Surface,然后通过setState(CONFIGURING);来设置状态为CONFIGURING,这个状态在OMX中很重要,整个OMX就是通过状态来驱动的,最后是一个mCodec->initiateConfigureComponent(format);函数,这里的mCodec是ACodec了,所以跳到ACodec::initiateConfigureComponent()中执行,Async执行,最后跳到ACodec::LoadedState::onConfigureComponent中,然后这个函数通过mCodec->configureCodec函数来设置Decoder,这个函数很重要,在里面设置了Audio和Video。

  最后配置完毕后,通过kWhatComponentConfigured这个notify通知外层的MediaCodec,在case CodecBase::kWhatComponentConfigured:这个case中,设置状态为CONFIGURED。

  (2)设置callback函数

mCodec->setCallback(reply);

打印出: MediaCodec will operate in async mode

  (3)启动解码器

mCodec->start();

对应到 MediaCodec::start()函数,在这个函数中设置了Vector<MediaResource>,然后发送kWhatStart AMessage,在处理函数中,这时候的状态为CONFIGURED,所以不会执行onInputBufferAvailable函数,而是继续向下执行,首先通过setState设置状态为STARTING,然后执行mCodec->initiateStart();这个mCodec是ACodec了,继续跳到ACodec::initiateStart()中去执行,最后执行到ACodec::LoadedState::onStart()函数中,在这个函数中通过mCodec->mOMX->sendCommand设置状态为OMX_StateIdle,然后设置:mCodec->changeState(mCodec->mLoadedToIdleState)。

void ACodec::LoadedState::onStart() {
    ALOGV("onStart");

    status_t err = mCodec->mOMXNode->sendCommand(OMX_CommandStateSet, OMX_StateIdle);
    if (err != OK) {
        mCodec->signalError(OMX_ErrorUndefined, makeNoSideEffectStatus(err));
    } else {
        mCodec->changeState(mCodec->mLoadedToIdleState);
    }
}

最后到达ACodec::LoadedToIdleState::stateEntered()里面,在这里面通过allocateBuffers函数来分配内存,然后就开始通过OMX来驱动了。

4.填充数据到解码队列

  从上层来看,MediaCodec就是一个黑盒,只需要是如何驱动它的,而不需要关心它内部是如何实现解码的,对于这个黑盒,它有一个input port,一个output port,buffer是如何运转就会非常重要,所以在这里关注的就是NuPlayerDecoder和MediaCodec的交互关系。MediaCodec的工作流程图如下:

03.png

  (1)在3.2节中分析到OMX会分配buffer,然后,在input port就有buffer了,这时候就会调用MediaCodec::onInputBufferAvailable,来告诉NuPlayerDecoder在MediaCodec的输入端口有个可以使用的buffer,然后NuPlayerDecoder就调用handleAnInputBuffer来向里面填充数据。

  (2)填充完数据后,MediaCodec就可以通过OMX来解码了,解码后的数据就会到达output port,这时候,MediaCodec就会调用onOutputBufferAvailable来通知NuPlayerDecoder,它的output port有个可以使用的buffer,NuPlayerDecoder可以把它发送到下一阶段了,所以NuPlayerDecoder就调用handleAnOutputBuffer来处理这个buffer,在这个函数中通过mRenderer->queueBuffer(mIsAudio, buffer, reply),把解码后的数据发送给Renderer。

  (3)下面先从MediaCodec有buffer时,通过异步消息通知NuPlayerDecoder有可以使用的buffer

04.png
 // 获取可用的输入缓冲区的索引(MediaCodec)
 public int dequeueInputBuffer (long timeoutUs)
 // ==》NuPlayerDecoder: 1.onMessageReceived()

 // 获取输入缓冲区
 public ByteBuffer getInputBuffer(int index)
 // 将填满数据的inputBuffer提交到编码队列
 public final void queueInputBuffer(int index,int offset, int size, long presentationTimeUs, int flags)

  (4)onInputBufferFetched()函数

bool NuPlayer::Decoder::onInputBufferFetched(const sp<AMessage> &msg) {
    ...
    size_t bufferIx;
    CHECK(msg->findSize("buffer-ix", &bufferIx));
    CHECK_LT(bufferIx, mInputBuffers.size());
    sp<MediaCodecBuffer> codecBuffer = mInputBuffers[bufferIx];

    sp<ABuffer> buffer;
    bool hasBuffer = msg->findBuffer("buffer", &buffer);//填充解封装模块获取的数据
    bool needsCopy = true;//是否需要将数据拷贝给MediaCodec

    if (buffer == NULL /* includes !hasBuffer */) {//如果已经没有buffer可以提供了
        // attempt to queue EOS: 插入结束标志
        status_t err = mCodec->queueInputBuffer(
                bufferIx,
                0,
                0,
                0,
                MediaCodec::BUFFER_FLAG_EOS);
        ...
    } else {// 还有buffer
        ...
        // copy into codec buffer
        if (needsCopy) {
            ...
            if (buffer->data() != NULL) {// 拷贝到MediaCodec的buffer中
                codecBuffer->setRange(0, buffer->size());
                memcpy(codecBuffer->data(), buffer->data(), buffer->size());
            } 
            ...
        } // needsCopy

        status_t err;
        AString errorDetailMsg;
        ...
            // 将buffer加入到MediaCodec的待解码队列中
            err = mCodec->queueInputBuffer(
                    bufferIx,
                    codecBuffer->offset(),
                    codecBuffer->size(),
                    timeUs,
                    flags,
                    &errorDetailMsg);
        } // no cryptInfo
        ...
    }   // buffer != NULL
    return true;
}

这个函数的核心,就是调用MediaCodec的queueInputBuffer函数,将填充好的MediaCodecBuffer添加到MediaCodec的输入队列中,等待解码。

5.渲染解码后的数据

  在获取到解码数据后会通过onRenderBuffer去对数据进行渲染,它的执行时机和onInputBufferFetched几乎是同时的,当MediaCodec的解码outputBuffer队列中有数据时,就会通过回调通知播放器,执行对应的回调函数渲染数据。

05.png
// 获取已成功编解码的输出缓冲区的索引(MediaCodec)
public final int dequeueOutputBuffer(BufferInfo info, long timeoutUs)
// ==》 NuPlayerDecoder: 1.onMessageReceived()

// 获取输出缓冲区
public ByteBuffer getOutputBuffer(int index)
// 释放输出缓冲区
public final void releaseOutputBuffer(int index, boolean render) 

最终执行取出解码数据并渲染的函数便是onRenderBuffer:

void NuPlayer::Decoder::onRenderBuffer(const sp<AMessage> &msg) {
    status_t err;
    int32_t render;
    size_t bufferIx;
    int32_t eos;
    CHECK(msg->findSize("buffer-ix", &bufferIx));
    ...
    if (mCodec == NULL) {
        err = NO_INIT;
    } else if (msg->findInt32("render", &render) && render) {
        //如果render为true,就去渲染
        int64_t timestampNs;
        CHECK(msg->findInt64("timestampNs", &timestampNs));
        err = mCodec->renderOutputBufferAndRelease(bufferIx, timestampNs);
    } else { //如果render为false,就丢帧不去渲染
        mNumOutputFramesDropped += !mIsAudio;
        err = mCodec->releaseOutputBuffer(bufferIx);
    }
    ...
}

6.释放解码器

NuPlayer::Decoder::~Decoder() {
    // Need to stop looper first since mCodec could be accessed on the mDecoderLooper.
    stopLooper(); // 停止looper
    if (mCodec != NULL) {
        mCodec->release(); // release掉MediaCodec
    }
    releaseAndResetMediaBuffers(); // 清理buffer
}

相关文章

网友评论

      本文标题:Android多媒体框架--11:Decoder解码器流程分析

      本文链接:https://www.haomeiwen.com/subject/efqiddtx.html