NuPlayer源码分析三:解码模块

解码模块

NuPlayer的解码模块相对比较简单,统一使用了一个基类NuPlayerDecoderBase管理,该类中包含了一个MediaCodec的对象,实际解码工作全靠MediaCodec。

如果你不会知道MediaCodec是什么,推荐去官网看看:MediaCodec

尽管解码工作都被MediaCodec接管,我还是会按照播放器的一般步骤,来分析一下NuPlayerDecoderBase。步骤如下:

一个Android播放器典型的播放步骤一般是:

  1. 播放器创建。
  2. 设置媒体源文件(本地文件路径、或者Uri)。
  3. 准备媒体数据。
  4. 播放视频。
  5. 停止播放。

对应于解码模块,会稍微简单一些:

  1. NuPlayerDecoderBase:解码器创建
  2. onInputBufferFetched:填充数据到解码队列
  3. onRenderBuffer:渲染解码后的数据
  4. ~Decoder:释放解码器

解码器创建:NuPlayerDecoderBase

当前位置:

  1. NuPlayerDecoderBase:解码器创建
  2. onInputBufferFetched:填充数据到解码队列
  3. onRenderBuffer:渲染解码后的数据
  4. ~Decoder:释放解码器

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

// 参数部分:audio true调用者想要创建音频解码器, false 想要创建视频解码器
// 参数部分:*decoder 该函数最终会创建指定解码器,使用该函数将解码器对象地址提供给调用者
status_t NuPlayer::instantiateDecoder(
        bool audio, sp<DecoderBase> *decoder, bool checkAudioModeChange) {
    sp<AMessage> format = mSource->getFormat(audio); // 其实就是GenericSource中的MetaData
    format->setInt32("priority", 0 /* realtime */);

    if (!audio) { // 视频
        // 总要丢掉一些代码的
            mCCDecoder = new CCDecoder(ccNotify); // 创建字幕解码器
    }
	// 创建音/视频解码器
    if (audio) { // 音频
        // ...
            *decoder = new Decoder(notify, mSource, mPID, mUID, mRenderer);
        // ...
    } 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;
}

删删减减去掉了大量代码,留下来我感兴趣的。

先来说说,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,
            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的接口直接相关。可见,它是处在解码的前沿阵地上的。

instantiateDecoder函数中,创建音频和视频的解码器,参数略有不同,创建视频解码器是会多出一个mSurface,提供给MediaCodec以显示视频,mCCDecoder则是字幕相关解码器。来看一下解码器构建函数:

NuPlayer::Decoder::Decoder(
        const sp<AMessage> &notify,
        const sp<Source> &source,
        pid_t pid,
        uid_t uid,
        const sp<Renderer> &renderer,
        const sp<Surface> &surface,
        const sp<CCDecoder> &ccDecoder)
    : DecoderBase(notify),
      mSurface(surface), // 视频播放的surface实体
      mSource(source),
      mRenderer(renderer), // 渲染器
      mCCDecoder(ccDecoder), // 字幕解码器
      mIsAudio(true) { // 是否为音频
    mCodecLooper = new ALooper;
    mCodecLooper->setName("NPDecoder-CL");
    mCodecLooper->start(false, false, ANDROID_PRIORITY_AUDIO);
    mVideoTemporalLayerAggregateFps[0] = mFrameRateTotal;
}

构造函数基本上就是将传递进来的参数,直接保存到自己的各类变量中,功能后续使用。继续看一下接下来对解码器来说比较重要的调用。

再来看看关于解码器的第二各操作:(*decoder)->init();

直接在Decoder类中查找init()并不能找到,因为Decoder继承与DecoderBase,所以这里执行的应该是DecoderBaseinit函数:

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

DecoderBase的构造函数中,已经创建了一套NativeHandler体系,并将Looper启动,只是没有将AHandler的子类对象和ALooper绑定,知道init()函数执行后,这种绑定关系才算结束。也只有这样,DecoderBase中的NativeHandler体系才能够正常工作。有关NativeHandler的详细信息,请参考:NativeHandler系列(一)

接下来看看和解码相关的最重要的一步操作:(*decoder)->configure(format);

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

void NuPlayer::Decoder::onConfigure(const sp<AMessage> &format) {
    AString mime;
    CHECK(format->findString("mime", &mime));
	// 根据需要创建的解码器类型创建解码器
    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(); // 启动解码器
}

从简化后的代码可以看到, 在onConfigure函数中,有关MediaCodec的调用都是比较经典的调用方式。分别有,MediaCodec的创建、配置、设置回调通知、启动解码器。

关于MediaCodec还有buffer的入队和出队以及释放函数,相信不久后在其它地方可以见到。

解码器创建部分暂时就这么多,小结一下:

小结解码器创建

  • Decoder继承于DecoderBaseDecoderBase基本上接管了解码工作所有的操作,通过纯虚构(抽象)函数来让子类,也就是Decoder来实现一些具体的操作。
  • DecoderBase解码体系,都是通过MediaCodec来实现解码流程。有鉴于此,它的基本操作函数都是为了MediaCodec的服务。

填充数据到解码队列:onInputBufferFetched

当前位置:

  1. NuPlayerDecoderBase:解码器创建
  2. onInputBufferFetched:填充数据到解码队列
  3. onRenderBuffer:渲染解码后的数据
  4. ~Decoder:释放解码器

MediaCode创建并执行了start函数后,就已经在通过mCodec->setCallback(reply)提供的回调,不断地调用填充数据有关的逻辑,最后实现数据填充的地方是在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可以提供了。
       status_t err = mCodec->queueInputBuffer(bufferIx, 0, 0, 0, MediaCodec::BUFFER_FLAG_EOS);
		// ...
   } else { // 还有buffer
        if (needsCopy) { // 拷贝给MediaCodec
            // ...
            if (buffer->data() != NULL) {
                codecBuffer->setRange(0, buffer->size());
                // 拷贝到MediaCodec的buffer中
                memcpy(codecBuffer->data(), buffer->data(), buffer->size()); 
            }
        } // needsCopy

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

这个函数的核心,就是调用MediaCodecqueueInputBuffer函数,将填充好的MediaCodecBuffer添加到MediaCodec的输入队列中,等待解码。解释都放在注释里了。来看一下如何取数据的。

渲染解码后的数据:onRenderBuffer

当前位置:

  1. NuPlayerDecoderBase:解码器创建
  2. onInputBufferFetched:填充数据到解码队列
  3. onRenderBuffer:渲染解码后的数据
  4. ~Decoder:释放解码器

onRenderBuffer的执行时机,和onInputBufferFetched几乎是同时的,当MediaCodec的解码outputBuffer队列中有数据时,就会通过回调通知播放器,执行对应的回调函数渲染数据。在NuPlayer这样的回调函数执行链条为:NuPlayer::Decoder::onMessageReceived ==> handleAnOutputBuffer ==> NuPlayer::Decoder::onRenderBuffer

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

void NuPlayer::Decoder::onRenderBuffer(const sp<AMessage> &msg) {
    int32_t render;
    size_t bufferIx;
    CHECK(msg->findSize("buffer-ix", &bufferIx));
	ALOGE("onRenderBuffer");

    if (msg->findInt32("render", &render) && render) {
        int64_t timestampNs;
        CHECK(msg->findInt64("timestampNs", &timestampNs));
        // 触发播放音频数据
        err = mCodec->renderOutputBufferAndRelease(bufferIx, timestampNs);
    } else { // 播放视频数据
        mNumOutputFramesDropped += !mIsAudio;
        err = mCodec->releaseOutputBuffer(bufferIx);
    }
	// ...
}

释放解码器:~Decoder

当前位置:

  1. NuPlayerDecoderBase:解码器创建
  2. onInputBufferFetched:填充数据到解码队列
  3. onRenderBuffer:渲染解码后的数据
  4. ~Decoder:释放解码器
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
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章