1.概述
NuPlayer的解码模块相对比较简单,统一使用了一个基类NuPlayerDecoderBase管理,该类中包含了一个MediaCodec的对象,实际解码工作全靠MediaCodec。下图是MediaCodec在整个框架中的位置:
![](https://img.haomeiwen.com/i18565088/a6e6217700e35e2c.png)
2.解码器创建
解码器创建的入口在NuPlayer的NuPlayer::instantiateDecoder函数调用时。NuPlayer在执行start函数后,会通过一系列调用链,触发该函数。调用流程如下:
![](https://img.haomeiwen.com/i18565088/dba5df4cab586adb.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> ¬ify,
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> ¬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的接口直接相关。
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的工作流程图如下:
![](https://img.haomeiwen.com/i18565088/fe56e2f514ed5ed7.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
![](https://img.haomeiwen.com/i18565088/7cdbfbcedffad4d6.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队列中有数据时,就会通过回调通知播放器,执行对应的回调函数渲染数据。
![](https://img.haomeiwen.com/i18565088/3f219f5a3d53a2fb.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", ×tampNs));
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
}
网友评论