• 本篇记录鸿蒙用于音视频编解码的原生AVCodec Kit学习过程
  • AVCodec编解码模块官网:AVCodec Kit简介
  • 示例代码仓:AVCodecVideo

编码过程分析

TS层:Index页面在用户点击录制->同意保存按钮后调用initNative获取SurfaceId,并带SurfaceId路由到Recorder页面

Init

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
graph TD
A["initNative"] --- B["RecorderNative::Init"]
B --- C["napi_create_async_work"]
C ---|napi_async_execute_callback| C1["NativeInit"]
C ---|napi_async_complete_callback| C2["DealCallBack"]
C1 --- D1["Recorder.Init"]
C1 --- D2["OH_NativeWindow_GetSurfaceId"]
D1 --- E1["Muxer"]
E1 --- E11["Muxer.Create"]
E1 --- |配置AVFormat| E12["Muxer.Config"]
D1 --- E2["Audio"]
E2 --- E21["CreateAudioEncoder"]
E2 --- E22["AudioCapturerInit"]
D1 --- E3["Video"]
E3 --- E31["CreateVideoEncoder"]
E11 ---|传入output file描述和格式| E111["OH_AVMuxer_Create"]
E21 --- |最终调用| E211["OH_AudioCodec_CreateByMime"]
E21 --- |新增| E212[CreateAudioBufferQueue]
E31 ---|最终调用| E311[OH_VideoEncoder_CreateByMime]
E31 --- |新增| E312[CreateVideoBufferQueue]

classDef libfunc fill:#4CAF50,stroke:#388E3C,stroke-width:2px;
classDef newfunc fill:#E3F2FD,stroke:#2196F3,stroke-width:2px;


class D2,E111,E211,E311 libfunc
class E212,E312 newfunc

Start

在record页面onload调用recorder.startNative

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
graph TD
A["startNative"] --- B["RecorderNative::Start"]
B --- C["Recorder.Start"]
C --- D1["muxer_->Start"]
C --- D2["videoEncoder_->Start"]
C --- D3["EncOutputThread"]
C --- D4["Audio"]
D1 --- D11[OH_AVMuxer_Start]
D2 --- D21[OH_VideoEncoder_Start]
D4 --- D41["AudioCapturerStart"]
D4 --- D42["audioEncoder->Start"]
D4 --- D43["AudioEncInputThread"]
D4 --- D44["AudioEncOutputThread"]

classDef libfunc fill:#4CAF50,stroke:#388E3C,stroke-width:2px;
class D11,D21 libfunc

1
2
3
4
5
6
7
8
9
10
11
12
13
graph TD 
A["EncOutputThread"] --- Z["循环体"]
A --- D[StartRelease]
subgraph 循环
Z --- B["muxer_->WriteSample"]
Z ---|传入pop出的bufferInfo的bufferIndex| C["videoEncoder_->FreeOutputBuffer"]
C --- |将处理后的index对应的OH_AVBuffer退回给编码器|C1["OH_VideoEncoder_FreeOutputBuffer"]
B --- B1["OH_AVBuffer_SetBufferAttr"]
B --- |将sample写入muxer封装器对应视频轨道|B2["OH_AVMuxer_WriteSampleBuffer"]
end
classDef libfunc fill:#4CAF50,stroke:#388E3C,stroke-width:2px;

class C1,B1,B2 libfunc

1
2
3
4
5
6
7
8
9
10
11
graph TD 
A["AudioEncInputThread"] --- Z["循环体"]
subgraph 循环
Z --- B[audioEncContext_->ReadCache]
Z --- C[audioEncoder_->PushInputData]
C --- C1[OH_AVBuffer_SetBufferAttr]
C --- C2[OH_AudioCodec_PushInputBuffer]
end
classDef libfunc fill:#4CAF50,stroke:#388E3C,stroke-width:2px;

class C1,C2 libfunc

1
2
3
4
5
6
7
8
9
10
11
12
13
graph TD 
A["AudioEncOutputThread"] --- Z["循环体"]
A --- D[StartRelease]
subgraph 循环
Z --- B["muxer_->WriteSample"]
Z ---|传入pop出的bufferInfo的bufferIndex| C["audioEncoder_->FreeOutputData"]
C --- |将处理后的index对应的OH_AVBuffer退回给编码器|C1["OH_AudioCodec_FreeOutputBuffer"]
B --- B1["OH_AVBuffer_SetBufferAttr"]
B --- |将sample写入muxer封装器对应视频轨道|B2["OH_AVMuxer_WriteSampleBuffer"]
end
classDef libfunc fill:#4CAF50,stroke:#388E3C,stroke-width:2px;

class C1,B1,B2 libfunc

Stop

用户在录制页面点击结束录制按钮时release函数调用recorder.stopNative

1
2
3
4
5
6
7
8
9
10
11
graph TD
A["stopNative"] --- B["NativeRecorder::Stop"]
B --- C["NativeStop"]
C --- D["Recorder.Stop"]
D --- D1["videoEncoder_->NotifyEndOfStream"]
D --- D2["WaitForDone"]
D1 --- E1["OH_VideoEncoder_NotifyEndOfStream"]

classDef libfunc fill:#4CAF50,stroke:#388E3C,stroke-width:2px;

class E1 libfunc

运行逻辑

AudioInputThread

  • AudioCapturerOnReadData通过WriteCache将数据写入userData的cache中
  • AudioEncInputThread中将cache中数据读取到bufferInfo的buffer,bufferInfo是从inputBufferInfoQueue中pop出来的
    以上读写的cache如何保证是同一个cache?
    • AudioCapturerInit会传入 Recorder 中的 audioEncContext_ (CodecUserData*类型) ,注册回调时会指定它为回调函数所依赖的数据
    • AudioEncInputThread中读取的cache就是Recorder::audioEncContext_

Audio注册回调

  1. OnNeedInputBuffer, OnNewOutputBuffer分别会向 inputBufferInfoQueue 和 outputBufferQueue 中添加对应 index 的 buffer,供 InputThread 和 OutputThread pop。
  2. 当 OH_AVCodec 在运行过程中需要新的输入数据时,会回调 OnNeedInputBuffer 给出一个可用的 buffer,InputThread 会将
    将 cache 中数据(audioCapturer生成)写入 buffer,之后调用 OH_AudioCodec_PushInputBuffer 告诉 codec 对应 Index 的 buffer 已经写有数据可以进行编码了。
  3. 编码结束后回调 OnNewOutputBuffer,给出存储编码数据的buffer,OutputThread 将buffer中数据写入muxer_