文章目录
- 应用流程
- 外部主动获取输入和输出buffer
- 外部设置回调
- 内部流程
应用流程
外部主动获取输入和输出buffer
解码的调用流程,以android原生的一个bin来说明
android 原生代码位置: frameworks/av/cmds/stagefright/codec.cpp
frameworks/av/cmds/stagefright/SimplePlayer.cpp
编译出来的是codec的bin,使用bin播放mp4, 可以使用如下的命令进行调试。
screenrecord /sdcard/test.mp4
codec -pSR /sdcard/test.mp4
-
prepare: 跟编码同样的流程
createByType: 根据MIME创建解码器。
configure:配置视频宽高、fromat信息到解码器。
start:启动解码器进行解码。
获取输入和输出的buffer 队列:先dequeue有效的输入buffer,然后将extract到的csd数据放入到这块buffer 中, 将这块buffer在queue到codec中(作用是 将解码所需要的额外的数据 给到解码器,类似于ffmpeg中的extradata)。 -
start: dequeue所有可用的input 和output buffer。然后解析封装读取数据 将数据拷贝到dequeue出来的input buffer 中, 拷贝完成后 queue到解码器, 同时从解码器中尽可能多的取输出的buffer, 对每个输出buffer 获取显示的时间戳。跟现有的时间比较,到显示时间的范围内则调用renderOutputBufferAndRelease将buffer 显示出去。
state->mCodec = MediaCodec::CreateByType(
looper, mime.c_str(), false /* encoder */);
CHECK(state->mCodec != NULL);
err = state->mCodec->configure(
format, isVideo ? surface : NULL,
NULL /* crypto */,
0 /* flags */);
CHECK_EQ((status_t)OK, codec->start());
CHECK_EQ((status_t)OK, codec->getInputBuffers(&state->mInBuffers));
CHECK_EQ((status_t)OK, codec->getOutputBuffers(&state->mOutBuffers))
err = state->mCodec->queueInputBuffer(
index,
0 /* offset */,
buffer->size(),
timeUs,
bufferFlags);
外部设置回调
大部分应用都不会主动去获取解码输入和输出buffer。 类似NuPlayer是通过外部设置回调。MediaCodec内部有输出或者输入buffer的时候 回调到应用层。
NuPlayerDecoder.cpp
void NuPlayer::Decoder::onConfigure(const sp<AMessage> &format) {
sp<AMessage> reply = new AMessage(kWhatCodecNotify, this);
mCodec->setCallback(reply);
}
void NuPlayer::Decoder::onMessageReceived(const sp<AMessage> &msg) {
switch (msg->what()) {
case kWhatCodecNotify:
{
switch (cbID) {
case MediaCodec::CB_INPUT_AVAILABLE:
{
int32_t index;
CHECK(msg->findInt32("index", &index));
handleAnInputBuffer(index);
break;
}
case MediaCodec::CB_OUTPUT_AVAILABLE:
{
CHECK(msg->findInt32("index", &index));
CHECK(msg->findSize("offset", &offset));
handleAnOutputBuffer(index, offset, size, timeUs, flags);
break;
}
}
内部流程
- create 和configure
create:mediacodeclist中遍历所有的解码器 然后将所有match的解码器返回。 这个里面放的是component名字如c2.android.vorbis.decoder等等。 找到之后 就用这个名字来init mediacodec。
configure: 将format 和 surface 配置到解码器当中,。
- format的configure
其中format可以存储很多信息 比如基本的图像宽高,解码器的类型等等。
configure将编解码的一些关键信息配置到编解码器 比如宽高、MIME、bitrate、frameRate 等等。
- surface 的configure surface
- 在设置surface的流程中操作为nativeWindowConnect、设置一个surface的generaration,然后又重新connect、disconnect。
- 调用codec的setSurface, codec直接调用的channel 的setSurface。
- channel的setSurface不会调用mComponent->setOutputSurface, 因为初始化的时候对应的outputPoolId为0.
- mediacodec 的start()
- 调用component 的start, 调用对应的SimpleC2Component的start(), 对应的每个解码器实现的onInit(),。
- mChannel->start, 调用channel的start
这里有一个问题,如何决定要申请的buffer 是graphic 还是liner类型的。
这在simpleC2interface中已经定义,会按照音视频的类型进行决定,视频 grahic,音频liner
addParameter(
DefineParam(mInputFormat, C2_PARAMKEY_INPUT_STREAM_BUFFER_TYPE)
.withConstValue(new C2StreamBufferTypeSetting::input(
0u, isEncoder ? rawBufferType : codedBufferType))
.build());
2.1 在Channel的start 中会根据是编码输入的surface 还是解码输出的surface 配置申请buffer的个数, 以及调用Component->createBlockPool,创建graphic的pool。会返回pool的allocatorID 和outputPoolId
对应于在底层的实现是在C2AllocatedGraphic。 同时为生成的pools->outputPoolId生成poolIdsTuning并将这个Tuning 设置到componet中。
2.2 调用mComponent->setOutputSurface: 设置surface到解码模块,使得后续可以从对应的nativewindow中申请buffer。