Codec2简析

Posted by Bill on May 21, 2021

1. 概述

Codec2.0在Android12需要进行对接,本文以AOSP源码分析,简单分析其运行逻辑,具体代码可参考https://cs.android.com/进行查阅。

1.1 MediaCodec流程

分析Codec2.0时,需要重温下MediaCodec的状态机,如下图所示:

mediacodec

  1. 开始处于uninitialized状态,调用configure即进入Configured状态,然后调用start进入Executing状态。此时可以进行Buffer处理。
  2. Executing状态又可细分位三个子状态,Flushed,Running,EndOfStream。当调用start后,MediaCodec已经申请了所有的Buffer,并等待数据的”装载”,此时短暂为Flushed状态。当第一次调用dequeueInputBuffer后获取一个Buffer后,进入Running状态。此后会通过queueInputBuffer将数据装载到dequeueInputBuffer的Buffer中进行处理。输出端也会获取MediaCodec处理后的Buffers用于送显,并将buffer返回给MediaCodec。最后将带有eos标记的inputBuffer给到MediaCodec后,会进入EndOfStream状态,此时MediaCodec不会再处理输入Buffer了,但仍然会输出Output Buffer直到最后一组Input Buffer处理完毕。具体的Buffer流转图可以查看下图。

mediacodec

MediaCodec分为同步和异步的模式,以一段同步的流程为例, 其过程就像是从搬运公司请求搬运的流程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
...
for (int i = 0; i < mExtractor.getTrackCount(); i++) {
    MediaFormat format = mExtractor.getTrackFormat(i);
    String mime = format.getString(MediaFormat.KEY_MIME);
    if (mime.startsWith("video/")) {
        mExtractor.selectTrack(i);
        try {
            mDecoder = MediaCodec.createDecoderByType(mime);
        } catch (IOException e) {
            e.printStackTrace();
        }
        mDecoder.configure(format, mSurfaceHolder.getSurface(), null, 0);
        break;
    }
}

mDecoder.start();
MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
boolean isEOS = false;

while (!Thread.interrupted() && !isStopped) {
    if(isPaused){
        try {
            sleep(timeOutUs);
            continue;
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    if (!isEOS) {
        //dequeueInputBuffer获取对应的下标, 像是从搬运公司要过来搬东西的汽车的车牌号
        int inIndex = mDecoder.dequeueInputBuffer(timeOutUs);
        if (inIndex >= 0) {
            //获取用于放输入数据的buffer。通过车牌号找到对应的汽车, 准备用于装载货物
            ByteBuffer buffer = mDecoder.getInputBuffer(inIndex);
            //将解封装的数据即码流放入buffer中。将货物放在该汽车上
            int sampleSize = mExtractor.readSampleData(buffer, 0);
            if (sampleSize < 0) {
                //搬完了,会设置一个EOS的标志位,告诉搬家公司已经全部搬完了
                mDecoder.queueInputBuffer(inIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
                isEOS = true;
            } else {
                //将带有码流数据的buffer传入解码器中进行解码。让搬运汽车去到目标位置
                mDecoder.queueInputBuffer(inIndex, 0, sampleSize, mExtractor.getSampleTime(), 0);
                mExtractor.advance();
            }
        }
    }
    //获取解码器已经解码完成的数据,获取其下标。这里的搬家汽车卸货完成之后,还获得了报酬。
    int outIndex = mDecoder.dequeueOutputBuffer(info, timeOutUs);
    if(outIndex >= 0){
        //通过下标获取解码后的数据buffer,用于送显。
        ByteBuffer buffer = mDecoder.getOutputBuffer(outIndex);
        mDecoder.releaseOutputBuffer(outIndex, true);
    }

    if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
        break;
    }
...
}

以上为同步的流程,但实际上一般会采用异步的方式实现,而NuPlayer也是使用如下方式去进行的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
MediaCodec codec = MediaCodec.createByCodecName(name);
MediaFormat mOutputFormat;
codec.setCallback(new MediaCodec.Callback() {
    @Override
    void onInputBufferAvailable(MediaCodec mc, int inputBufferId) {
        //当输入Buffer准备好后,就可以异步地将输入Buffer给到解码器
        ByteBuffer inputBuffer = codec.getInputBuffer(inputBufferId);
        ...
        codec.queueInputBuffer(inputBufferId, );
    }

    @Override
    void onOutputBufferAvailable(MediaCodec mc, int outputBufferId, ...)     {
        //当输出Buffer准备好后,即已经解码的数据,可用于送显。
        ByteBuffer outputBuffer = codec.getOutputBuffer(outputBufferId);
        MediaFormat bufferFormat = codec.getOutputFormat(outputBufferId); // option A
        ...
        codec.releaseOutputBuffer(outputBufferId, );
    }
});

了解了MediaCodec的工作方式,也就可以清楚码流是如何传入给解码器,然后再从解码器获取数据进行送显了。

1.2 CCodec

1.3 Codec2的重要类型

1.3.1 C2Work

1.3.2 CCodecBufferChannel

1.3.3 C2Buffer

1.3.4 C2Allocator

1.3.5 C2Block

1.3.6 C2BlockPool

2. Codec2服务

todo: 服务注册,配置文件

3. Buffer流转

3.1 输入Buffer的流转

todo: 从MediaCodec到Codec2的处理

3.2 输出Buffer的流转

todo: 从Codec2回调到MediaCodec的处理

4. Google原生编解码处理

4.1 解码处理

todo: 以avc为例

4.2 编码处理

todo: 以avc为例