解码模块
文章目录
系列文章分为如下几个模块:
NuPlayer的解码模块相对比较简单,统一使用了一个基类NuPlayerDecoderBase管理,该类中包含了一个MediaCodec的对象,实际解码工作全靠MediaCodec。
如果你不会知道MediaCodec是什么,推荐去官网看看:MediaCodec
尽管解码工作都被MediaCodec
接管,我还是会按照播放器的一般步骤,来分析一下NuPlayerDecoderBase
。步骤如下:
一个Android播放器典型的播放步骤一般是:
- 播放器创建。
- 设置媒体源文件(本地文件路径、或者Uri)。
- 准备媒体数据。
- 播放视频。
- 停止播放。
对应于解码模块,会稍微简单一些:
NuPlayerDecoderBase
:解码器创建onInputBufferFetched
:填充数据到解码队列onRenderBuffer
:渲染解码后的数据~Decoder
:释放解码器
解码器创建:NuPlayerDecoderBase
当前位置:
NuPlayerDecoderBase
:解码器创建onInputBufferFetched
:填充数据到解码队列onRenderBuffer
:渲染解码后的数据~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> ¬ify,
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> ¬ify);
void configure(const sp<AMessage> &format);
void init();
void setParameters(const sp<AMessage> ¶ms);
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> ¶ms) = 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> ¬ify,
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
,所以这里执行的应该是DecoderBase
的init
函数:
void NuPlayer::DecoderBase::init() {
mDecoderLooper->registerHandler(this);
}
DecoderBase
的构造函数中,已经创建了一套NativeHandler
体系,并将Looper
启动,只是没有将AHandler
的子类对象和ALooper
绑定,知道init()
函数执行后,这种绑定关系才算结束。也只有这样,DecoderBase
中的NativeHandle
r体系才能够正常工作。有关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
继承于DecoderBase
,DecoderBas
e基本上接管了解码工作所有的操作,通过纯虚构(抽象)函数来让子类,也就是Decoder
来实现一些具体的操作。DecoderBase
解码体系,都是通过MediaCodec
来实现解码流程。有鉴于此,它的基本操作函数都是为了MediaCodec
的服务。
填充数据到解码队列:onInputBufferFetched
当前位置:
NuPlayerDecoderBase
:解码器创建onInputBufferFetched
:填充数据到解码队列onRenderBuffer
:渲染解码后的数据~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;
}
这个函数的核心,就是调用MediaCodec
的queueInputBuffer
函数,将填充好的MediaCodecBuffer
添加到MediaCodec
的输入队列中,等待解码。解释都放在注释里了。来看一下如何取数据的。
渲染解码后的数据:onRenderBuffer
当前位置:
NuPlayerDecoderBase
:解码器创建onInputBufferFetched
:填充数据到解码队列onRenderBuffer
:渲染解码后的数据~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", ×tampNs));
// 触发播放音频数据
err = mCodec->renderOutputBufferAndRelease(bufferIx, timestampNs);
} else { // 播放视频数据
mNumOutputFramesDropped += !mIsAudio;
err = mCodec->releaseOutputBuffer(bufferIx);
}
// ...
}
释放解码器:~Decoder
当前位置:
NuPlayerDecoderBase
:解码器创建onInputBufferFetched
:填充数据到解码队列onRenderBuffer
:渲染解码后的数据~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
}