• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# 视频解码
2
3开发者可以调用本模块的Native API接口,完成视频解码,即将媒体数据解码成YUV文件或送显。
4
5<!--RP3--><!--RP3End-->
6
7当前支持的解码能力请参考[AVCodec支持的格式](avcodec-support-formats.md#视频解码)。
8
9<!--RP1--><!--RP1End-->
10
11通过视频解码,应用可以实现以下重点能力,包括:
12
13|          支持的能力                       |             使用简述                                                                     |
14| --------------------------------------- | ---------------------------------------------------------------------------------- |
15| 变分辨率         | 解码器支持输入码流分辨率发生变化,发生变化后会触发OH_VideoDecoder_RegisterCallback接口设置的回调函数OnStreamChanged()。具体可参考下文中:Surface模式步骤-3或Buffer模式步骤-3  |
16| 动态切换surface  | 通过调用OH_VideoDecoder_SetSurface接口配置,仅Surface模式支持。具体可参考下文中:Surface模式步骤-6    |
17| 低时延解码  | 通过调用OH_VideoDecoder_Configure接口配置,具体可参考下文中:Surface模式的步骤-5或Buffer模式步骤-5      |
18
19## 限制约束
20
211. Buffer模式不支持HDRVivid解码。
222. Flush,Reset,Stop之后,重新Start时,需要重新传PPS/SPS。具体示例请参考[Surface模式](#surface模式)“步骤-13:调用OH_VideoDecoder_Flush()”。
233. Flush,Reset,Stop,Destroy在非回调线程中执行时,会等待所有回调执行完成后,将执行结果返回给用户。
244. 由于硬件解码器资源有限,每个解码器在使用完毕后都必须调用OH_VideoDecoder_Destroy接口来销毁实例并释放资源。
255. 视频解码输入码流仅支持AnnexB格式,且支持的AnnexB格式支持多slice,要求同一帧的多个slice一次送入解码器。
266. 在调用Flush,Reset,Stop的过程中,开发者不应对之前回调函数获取到的OH_AVBuffer继续进行操作。
277. DRM解密能力在[Surface模式](#surface模式)下既支持非安全视频通路,也支持安全视频通路,在[Buffer模式](#buffer模式)下仅支持非安全视频通路。
288. Buffer模式和Surface模式使用方式一致的接口,所以只提供了Surface模式的示例。
299. 在Buffer模式下,开发者通过输出回调函数OH_AVCodecOnNewOutputBuffer获取到OH_AVBuffer的指针实例后,必须通过调用OH_VideoDecoder_FreeOutputBuffer接口
30   来通知系统该实例已被使用完毕。这样系统才能够将后续解码的数据写入到相应的位置。如果开发者在调用OH_AVBuffer_GetNativeBuffer接口时获取到OH_NativeBuffer指针实例,并且该实例的生命周期超过了当前的OH_AVBuffer指针实例,那么需要进行一次数据的拷贝操作。在这种情况下,开发者需要自行管理新生成的OH_NativeBuffer实例的生命周期,确保其正确使用和释放。
31<!--RP6--><!--RP6End-->
32
33## surface输出与buffer输出
34
351. 两者数据的输出方式不同。
362. 两者的适用场景不同:
37    - surface输出是指用OHNativeWindow来传递输出数据,可以与其他模块对接,例如XComponent。
38    - buffer输出是指经过解码的数据会以共享内存的方式输出。
39
403. 在接口调用的过程中,两种方式的接口调用方式基本一致,但存在以下差异点:
41    - 在Surface模式下,可选择调用OH_VideoDecoder_FreeOutputBuffer接口丢弃输出帧(不送显);在Buffer模式下,应用必须调用OH_VideoDecoder_FreeOutputBuffer接口释放数据。
42    - Surface模式下,应用在解码器就绪前,必须调用OH_VideoDecoder_SetSurface接口设置OHNativeWindow,启动后,调用OH_VideoDecoder_RenderOutputBuffer接口将解码数据送显。
43    - 输出回调传出的buffer,在Buffer模式下,可以获取共享内存的地址和数据信息;在Surface模式下,只能获取buffer的数据信息。
44
454. Surface模式的数据流转性能优于Buffer模式。
46
47两种模式的开发步骤详细说明请参考:[Surface模式](#surface模式)和[Buffer模式](#buffer模式)。
48
49## 状态机调用关系
50
51如下为状态机调用关系图:
52
53![Invoking relationship of state](figures/state-invocation.png)
54
551. 有两种方式可以使解码器进入Initialized状态:
56   - 初始创建解码器实例时,解码器处于Initialized状态。
57   - 任何状态下,调用OH_VideoDecoder_Reset接口,解码器将会移回Initialized状态。
58
592. Initialized状态下,调用OH_VideoDecoder_Configure接口配置解码器,配置成功后解码器进入Configured状态。
603. Configured状态下,调用OH_VideoDecoder_Prepare接口进入Prepared状态。
614. Prepared状态下,调用OH_VideoDecoder_Start接口使解码器进入Executing状态:
62   - 处于Executing状态时,调用OH_VideoDecoder_Stop接口可以使解码器返回到Prepared状态。
63
645. 在极少数情况下,解码器可能会遇到错误并进入Error状态。解码器的错误传递,可以通过队列操作返回无效值或者抛出异常:
65   - Error状态下,可以调用解码器OH_VideoDecoder_Reset接口将解码器移到Initialized状态;或者调用OH_VideoDecoder_Destroy接口移动到最后的Released状态。
66
676. Executing状态具有三个子状态:Flushed、Running和End-of-Stream:
68    - 在调用了OH_VideoDecoder_Start接口之后,解码器立即进入Running子状态。
69    - 对于处于Executing状态的解码器,可以调用OH_VideoDecoder_Flush接口返回到Flushed子状态。
70    - 当待处理数据全部传递给解码器后,在input buffers队列中为最后一个入队的input buffer中添加[AVCODEC_BUFFER_FLAGS_EOS](../../reference/apis-avcodec-kit/_core.md#oh_avcodecbufferflags-1)标记,遇到这个标记时,解码器会转换为End-of-Stream子状态。在此状态下,解码器不再接受新的输入,但是仍然会继续生成输出,直到输出到达尾帧。
71
727. 使用完解码器后,必须调用OH_VideoDecoder_Destroy接口销毁解码器实例,使解码器进入Released状态。
73
74## 开发指导
75
76详细的API说明请参考[API文档](../../reference/apis-avcodec-kit/_video_decoder.md)。
77如下为视频解码调用关系图:
78
79- 虚线表示可选。
80
81- 实线表示必选。
82
83![Invoking relationship of video decode stream](figures/video-decode.png)
84
85### 在 CMake 脚本中链接动态库
86
87``` cmake
88target_link_libraries(sample PUBLIC libnative_media_codecbase.so)
89target_link_libraries(sample PUBLIC libnative_media_core.so)
90target_link_libraries(sample PUBLIC libnative_media_vdec.so)
91```
92
93> **说明:**
94>
95> 上述'sample'字样仅为示例,此处由开发者根据实际工程目录自定义。
96>
97
98### 定义基础结构
99
100本部分示例代码按照C++17标准编写,仅作参考。开发者可以参考此部分,定义自己的buffer对象。
101
1021. 添加头文件。
103
104    ```c++
105    #include <condition_variable>
106    #include <memory>
107    #include <mutex>
108    #include <queue>
109    #include <shared_mutex>
110    ```
111
1122. 解码器回调buffer的信息。
113
114    ```c++
115    struct CodecBufferInfo {
116        CodecBufferInfo(uint32_t index, OH_AVBuffer *buffer): index(index), buffer(buffer), isValid(true) {}
117        // 回调buffer。
118        OH_AVBuffer *buffer = nullptr;
119        // 回调buffer对应的index。
120        uint32_t index = 0;
121        // 判断当前buffer信息是否有效。
122        bool isValid = true;
123    };
124    ```
125
1263. 解码输入输出队列。
127
128    ```c++
129    class CodecBufferQueue {
130    public:
131        // 将回调buffer的信息传入队列。
132        void Enqueue(const std::shared_ptr<CodecBufferInfo> bufferInfo)
133        {
134            std::unique_lock<std::mutex> lock(mutex_);
135            bufferQueue_.push(bufferInfo);
136            cond_.notify_all();
137        }
138
139        // 获取回调buffer的信息。
140        std::shared_ptr<CodecBufferInfo> Dequeue(int32_t timeoutMs = 1000)
141        {
142            std::unique_lock<std::mutex> lock(mutex_);
143            (void)cond_.wait_for(lock, std::chrono::milliseconds(timeoutMs), [this]() { return !bufferQueue_.empty(); });
144            if (bufferQueue_.empty()) {
145                return nullptr;
146            }
147            std::shared_ptr<CodecBufferInfo> bufferInfo = bufferQueue_.front();
148            bufferQueue_.pop();
149            return bufferInfo;
150        }
151
152        // 清空队列,之前的回调buffer设置为不可用。
153        void Flush()
154        {
155            std::unique_lock<std::mutex> lock(mutex_);
156            while (!bufferQueue_.empty()) {
157                std::shared_ptr<CodecBufferInfo> bufferInfo = bufferQueue_.front();
158                // Flush、Stop、Reset、Destroy操作之后,之前回调的buffer信息设置为无效。
159                bufferInfo->isValid = false;
160                bufferQueue_.pop();
161            }
162        }
163
164    private:
165        std::mutex mutex_;
166        std::condition_variable cond_;
167        std::queue<std::shared_ptr<CodecBufferInfo>> bufferQueue_;
168    };
169    ```
170
1714. 全局变量。
172
173    仅做参考,可以根据实际情况将其封装到对象中。
174
175    ```c++
176    // 视频帧宽度。
177    int32_t width = 320;
178    // 视频帧高度。
179    int32_t height = 240;
180    // 视频像素格式。
181     OH_AVPixelFormat pixelFormat = AV_PIXEL_FORMAT_NV12;
182    // 视频宽跨距。
183    int32_t widthStride = 0;
184    // 视频高跨距。
185    int32_t heightStride = 0;
186    // 解码器实例指针。
187    OH_AVCodec *videoDec = nullptr;
188    // 解码器同步锁。
189    std::shared_mutex codecMutex;
190    // 解码器输入队列。
191    CodecBufferQueue inQueue;
192    // 解码器输出队列。
193    CodecBufferQueue outQueue;
194    ```
195
196### Surface模式
197
198参考以下示例代码,开发者可以完成Surface模式下视频解码的全流程。此处以H.264码流文件输入,解码送显输出为例。
199本模块目前仅支持异步模式的数据轮转。
200
2011. 添加头文件。
202
203    ```c++
204    #include <multimedia/player_framework/native_avcodec_videodecoder.h>
205    #include <multimedia/player_framework/native_avcapability.h>
206    #include <multimedia/player_framework/native_avcodec_base.h>
207    #include <multimedia/player_framework/native_avformat.h>
208    #include <multimedia/player_framework/native_avbuffer.h>
209    #include <fstream>
210    ```
211
2122. 创建解码器实例。
213
214    开发者可以通过名称或媒体类型创建解码器。示例中的变量说明如下:
215
216    - videoDec:视频解码器实例的指针;
217    - capability:解码器能力查询实例的指针;
218    - OH_AVCODEC_MIMETYPE_VIDEO_AVC:AVC格式视频编解码器。
219
220    ```c++
221    // 通过codecname创建解码器,应用有特殊需求,比如选择支持某种分辨率规格的解码器,可先查询capability,再根据codec name创建解码器。
222    OH_AVCapability *capability = OH_AVCodec_GetCapability(OH_AVCODEC_MIMETYPE_VIDEO_AVC, false);
223    // 创建硬件解码器实例。
224    OH_AVCapability *capability= OH_AVCodec_GetCapabilityByCategory(OH_AVCODEC_MIMETYPE_VIDEO_AVC, false, HARDWARE);
225    const char *name = OH_AVCapability_GetName(capability);
226    OH_AVCodec *videoDec = OH_VideoDecoder_CreateByName(name);
227    ```
228
229    ```c++
230    // 通过MIME TYPE创建解码器,只能创建系统推荐的特定编解码器。
231    // 涉及创建多路编解码器时,优先创建硬件解码器实例,硬件资源不够时再创建软件解码器实例。
232    // 软/硬解:创建H.264解码器实例。
233    OH_AVCodec *videoDec = OH_VideoDecoder_CreateByMime(OH_AVCODEC_MIMETYPE_VIDEO_AVC);
234    // 软/硬解:创建H.265解码器实例。
235    OH_AVCodec *videoDec = OH_VideoDecoder_CreateByMime(OH_AVCODEC_MIMETYPE_VIDEO_HEVC);
236    ```
237
2383. 调用OH_VideoDecoder_RegisterCallback()设置回调函数。
239
240    注册回调函数指针集合OH_AVCodecCallback,包括:
241
242    - OH_AVCodecOnError 解码器运行错误,返回的错误码详情请参见:[OH_AVCodecOnError](../../reference/apis-avcodec-kit/_codec_base.md#oh_avcodeconerror);
243    - OH_AVCodecOnStreamChanged 码流信息变化,如码流宽、高变化;
244    - OH_AVCodecOnNeedInputBuffer 运行过程中需要新的输入数据,即解码器已准备好,可以输入数据;
245    - OH_AVCodecOnNewOutputBuffer 运行过程中产生了新的输出数据,即解码完成(注:Surface模式buffer参数为空)。
246
247    开发者可以通过处理该回调报告的信息,确保解码器正常运转。
248
249    <!--RP2--><!--RP2End-->
250
251    ```c++
252    // 解码异常回调OH_AVCodecOnError实现。
253    static void OnError(OH_AVCodec *codec, int32_t errorCode, void *userData)
254    {
255        // 回调的错误码由开发者判断处理。
256        (void)codec;
257        (void)errorCode;
258        (void)userData;
259    }
260
261    // 解码数据流变化回调OH_AVCodecOnStreamChanged实现。
262    static void OnStreamChanged(OH_AVCodec *codec, OH_AVFormat *format, void *userData)
263    {
264        // 可通过format获取到变化后的视频宽、高等。
265        (void)codec;
266        (void)userData;
267        OH_AVFormat_GetIntValue(format, OH_MD_KEY_VIDEO_PIC_WIDTH, &width);
268        OH_AVFormat_GetIntValue(format, OH_MD_KEY_VIDEO_PIC_HEIGHT, &height);
269    }
270
271    // 解码输入回调OH_AVCodecOnNeedInputBuffer实现。
272    static void OnNeedInputBuffer(OH_AVCodec *codec, uint32_t index, OH_AVBuffer *buffer, void *userData)
273    {
274        // 输入帧的数据buffer和对应的index送入inQueue队列。
275        (void)codec;
276        (void)userData;
277        inQueue.Enqueue(std::make_shared<CodecBufferInfo>(index, buffer));
278    }
279
280    // 解码输出回调OH_AVCodecOnNewOutputBuffer实现。
281    static void OnNewOutputBuffer(OH_AVCodec *codec, uint32_t index, OH_AVBuffer *buffer, void *userData)
282    {
283        // 完成帧的数据buffer和对应的index送入outQueue队列。
284        (void)codec;
285        (void)userData;
286        outQueue.Enqueue(std::make_shared<CodecBufferInfo>(index, buffer));
287    }
288    // 配置异步回调,调用 OH_VideoDecoder_RegisterCallback 接口。
289    OH_AVCodecCallback cb = {&OnError, &OnStreamChanged, &OnNeedInputBuffer, &OnNewOutputBuffer};
290    // 配置异步回调。
291    int32_t ret = OH_VideoDecoder_RegisterCallback(videoDec, cb, nullptr); // nullptr:用户特定数据userData为空。
292    if (ret != AV_ERR_OK) {
293        // 异常处理。
294    }
295    ```
296
297    > **说明:**
298    >
299    > 1. 在回调函数中,对数据队列进行操作时,需要注意多线程同步的问题。
300    > 2. 播放视频时,若视频码流的SPS中包含颜色信息,解码器会把这些信息(RangeFlag、ColorPrimary、MatrixCoefficient、TransferCharacteristic)通过OH_AVCodecOnStreamChanged接口中的OH_AVFormat返回。
301    > 3. 视频解码的Surface模式下,内部数据默认是走HEBC(High Efficiency Bandwidth Compression,高效带宽压缩),无法获取到widthStride和heightStride的值。
302    >
303
3044. (可选)OH_VideoDecoder_SetDecryptionConfig设置解密配置。在获取到DRM信息(参考[音视频解封装](audio-video-demuxer.md)开发步骤第4步),完成DRM许可证申请后,通过此接口进行解密配置。此接口需在Prepare前调用。在Surface模式下,DRM解密能力既支持安全视频通路,也支持非安全视频通路。DRM相关接口详见[DRM API文档](../../reference/apis-drm-kit/_drm.md)。
305
306    添加头文件。
307
308    ```c++
309    #include <multimedia/drm_framework/native_mediakeysystem.h>
310    #include <multimedia/drm_framework/native_mediakeysession.h>
311    #include <multimedia/drm_framework/native_drm_err.h>
312    #include <multimedia/drm_framework/native_drm_common.h>
313    ```
314
315    在 CMake 脚本中链接动态库。
316
317    ``` cmake
318    target_link_libraries(sample PUBLIC libnative_drm.so)
319    ```
320
321    <!--RP4-->使用示例:<!--RP4End-->
322
323    ```c++
324    // 根据DRM信息创建指定的DRM系统,以创建"com.clearplay.drm"为例。
325    MediaKeySystem *system = nullptr;
326    int32_t ret = OH_MediaKeySystem_Create("com.clearplay.drm", &system);
327    if (system == nullptr) {
328        printf("create media key system failed");
329        return;
330    }
331
332    // 创建解密会话,如果使用安全视频通路,应创建CONTENT_PROTECTION_LEVEL_HW_CRYPTO及其以上内容保护级别的MediaKeySession;
333    // 如果使用非安全视频通路,应创建CONTENT_PROTECTION_LEVEL_SW_CRYPTO及以上内容保护级别的MediaKeySession。
334    MediaKeySession *session = nullptr;
335    DRM_ContentProtectionLevel contentProtectionLevel = CONTENT_PROTECTION_LEVEL_SW_CRYPTO;
336    ret = OH_MediaKeySystem_CreateMediaKeySession(system, &contentProtectionLevel, &session);
337    if (ret != DRM_OK) {
338        // 如创建失败,请查看DRM接口文档及日志信息。
339        printf("create media key session failed.");
340        return;
341    }
342    if (session == nullptr) {
343        printf("media key session is nullptr.");
344        return;
345    }
346
347    // 获取许可证请求、设置许可证响应等。
348
349    // 设置解密配置,即将解密会话、安全视频通路标志设置到解码器中。
350    // 如果DRM解决方案支持安全视频通路,在使用安全视频通路时,需将secureVideoPath设置为true,并在此之前须创建安全解码器。
351    // 即在步骤2使用OH_VideoDecoder_CreateByName函数、参数为解码器名称后拼接.secure(如“[CodecName].secure”)创建安全解码器。
352    bool secureVideoPath = false;
353    ret = OH_VideoDecoder_SetDecryptionConfig(videoDec, session, secureVideoPath);
354    ```
355
3565. 调用OH_VideoDecoder_Configure()配置解码器。
357
358    详细可配置选项的说明请参考[视频专有键值对](../../reference/apis-avcodec-kit/_codec_base.md#媒体数据键值对)。
359
360    参数校验规则请参考[OH_VideoDecoder_Configure() 参考文档](../../reference/apis-avcodec-kit/_video_decoder.md#oh_videodecoder_configure)。
361
362    参数取值范围可以通过能力查询接口获取,具体示例请参考[获取支持的编解码能力](obtain-supported-codecs.md)。
363
364    目前支持的所有格式都必须配置以下选项:视频帧宽度、视频帧高度、视频像素格式。
365
366    ```c++
367
368    OH_AVFormat *format = OH_AVFormat_Create();
369    // 写入format。
370    OH_AVFormat_SetIntValue(format, OH_MD_KEY_WIDTH, width); // 必须配置。
371    OH_AVFormat_SetIntValue(format, OH_MD_KEY_HEIGHT, height); // 必须配置。
372    OH_AVFormat_SetIntValue(format, OH_MD_KEY_PIXEL_FORMAT, pixelFormat);
373    // 可选,配置低时延解码。
374    // 若平台支持,当使能OH_MD_KEY_VIDEO_ENABLE_LOW_LATENCY接口时,视频解码器将按照解码序输出帧。
375    OH_AVFormat_SetIntValue(format, OH_MD_KEY_VIDEO_ENABLE_LOW_LATENCY, 1);
376    // 配置解码器。
377    int32_t ret = OH_VideoDecoder_Configure(videoDec, format);
378    if (ret != AV_ERR_OK) {
379        // 异常处理。
380    }
381    OH_AVFormat_Destroy(format);
382    ```
383
3846. 设置surface。
385
386    本例中的nativeWindow,有两种方式获取:
387
388    6.1 如果解码后直接显示,则从XComponent组件获取。
389
390    添加头文件。
391
392    ```c++
393    #include <native_window/external_window.h>
394    ```
395
396    在 CMake 脚本中链接动态库。
397
398    ``` cmake
399    target_link_libraries(sample PUBLIC libnative_window.so)
400    ```
401
402    6.1.1 在ArkTS侧,通过xComponentController组件的getXComponentSurfaceId接口获取XComponent对应的Surface的ID。详情请参考[自定义渲染 (XComponent)](../../ui/napi-xcomponent-guidelines.md#arkts-xcomponent场景)。
403
404    6.1.2 在Native侧,调用OH_NativeWindow_CreateNativeWindowFromSurfaceId接口创建出NativeWindow实例。
405
406    ```c++
407    OHNativeWindow* nativeWindow;
408    // 基于步骤1.1中获取的surfaceId创建对应的nativeWindow实例。
409    OH_NativeWindow_CreateNativeWindowFromSurfaceId(surfaceId, nativeWindow);
410    ```
411
412    6.2 如果解码后接OpenGL后处理,则从NativeImage获取,获取方式请参考 [NativeImage](../../graphics/native-image-guidelines.md)。
413
414    Surface模式,开发者可以在解码过程中执行该步骤,即动态切换surface。
415
416    ```c++
417    // 设置surface。
418    // 配置送显窗口参数。
419    int32_t ret = OH_VideoDecoder_SetSurface(videoDec, nativeWindow);  // nativeWindow通过以上两种方式获取。
420    if (ret != AV_ERR_OK) {
421        // 异常处理。
422    }
423    // 配置视频与显示屏匹配模式(缓冲区按原比例缩放,使得缓冲区的较小边与窗口匹配,较长边超出窗口的部分被视为透明)。
424    OH_NativeWindow_NativeWindowSetScalingModeV2(nativeWindow, OH_SCALING_MODE_SCALE_CROP_V2);
425    ```
426
427    > **注意:**
428    > 若应用对1号和2号解码器均通过调用OH_VideoDecoder_SetSurface接口绑定至同一个NativeWindow。在2号解码器处于Running状态时,1号解码器调用OH_VideoDecoder_Destroy接口后,会导致2号解码器的视频播放画面卡住。
429    >
430    > 可以采用以下方案进行更改:
431    > 1. 等1号解码器完全释放后,再调用OH_VideoDecoder_Start接口启动2号解码器;
432    > 2. 1号解码器用surface1,2号解码器先调用OH_ConsumerSurface_Create接口创建临时surface,等1号解码器释放后,再调用OH_VideoDecoder_SetSurface接口将2号解码器绑定至surface1上,详情请参见:[创建视频解码器和NativeWindow初始化并行](../../media/avcodec/parallel-decoding-nativeWindow.md)。
433
4347. (可选)OH_VideoDecoder_SetParameter()动态配置解码器surface参数。
435    详细可配置选项的说明请参考[视频专有键值对](../../reference/apis-avcodec-kit/_codec_base.md#媒体数据键值对)。
436
437    ```c++
438    OH_AVFormat *format = OH_AVFormat_Create();
439    // 配置显示旋转角度。
440    OH_AVFormat_SetIntValue(format, OH_MD_KEY_ROTATION, 90);
441    int32_t ret = OH_VideoDecoder_SetParameter(videoDec, format);
442    OH_AVFormat_Destroy(format);
443    ```
444
4458. 调用OH_VideoDecoder_Prepare()解码器就绪。
446
447    该接口将在解码器运行前进行一些数据的准备工作。
448
449    ```c++
450    ret = OH_VideoDecoder_Prepare(videoDec);
451    if (ret != AV_ERR_OK) {
452        // 异常处理。
453    }
454    ```
455
4569. 调用OH_VideoDecoder_Start()启动解码器。
457
458    ```c++
459    // 启动解码器,开始解码。
460    int32_t ret = OH_VideoDecoder_Start(videoDec);
461    if (ret != AV_ERR_OK) {
462        // 异常处理。
463    }
464    ```
465
46610. (可选)调用OH_AVCencInfo_SetAVBuffer(),设置cencInfo。
467
468    若当前播放的节目是DRM加密节目,应用自行实现媒体解封装功能而非使用系统[解封装](audio-video-demuxer.md)功能时,需调用OH_AVCencInfo_SetAVBuffer()将cencInfo设置到AVBuffer,这样AVBuffer携带待解密的数据以及cencInfo,以实现AVBuffer中媒体数据的解密。当应用使用系统[解封装](audio-video-demuxer.md)功能时,则无需调用此接口。
469
470    添加头文件。
471
472    ```c++
473    #include <multimedia/player_framework/native_cencinfo.h>
474    ```
475
476    在 CMake 脚本中链接动态库。
477
478    ``` cmake
479    target_link_libraries(sample PUBLIC libnative_media_avcencinfo.so)
480    ```
481
482    使用示例:
483    - buffer:回调函数OnNeedInputBuffer传入的参数。
484    ```c++
485    uint32_t keyIdLen = DRM_KEY_ID_SIZE;
486    uint8_t keyId[] = {
487        0xd4, 0xb2, 0x01, 0xe4, 0x61, 0xc8, 0x98, 0x96,
488        0xcf, 0x05, 0x22, 0x39, 0x8d, 0x09, 0xe6, 0x28};
489    uint32_t ivLen = DRM_KEY_IV_SIZE;
490    uint8_t iv[] = {
491        0xbf, 0x77, 0xed, 0x51, 0x81, 0xde, 0x36, 0x3e,
492        0x52, 0xf7, 0x20, 0x4f, 0x72, 0x14, 0xa3, 0x95};
493    uint32_t encryptedBlockCount = 0;
494    uint32_t skippedBlockCount = 0;
495    uint32_t firstEncryptedOffset = 0;
496    uint32_t subsampleCount = 1;
497    DrmSubsample subsamples[1] = { {0x10, 0x16} };
498    // 创建CencInfo实例。
499    OH_AVCencInfo *cencInfo = OH_AVCencInfo_Create();
500    if (cencInfo == nullptr) {
501        // 异常处理。
502    }
503    // 设置解密算法。
504    OH_AVErrCode errNo = OH_AVCencInfo_SetAlgorithm(cencInfo, DRM_ALG_CENC_AES_CTR);
505    if (errNo != AV_ERR_OK) {
506        // 异常处理。
507    }
508    // 设置KeyId和Iv。
509    errNo = OH_AVCencInfo_SetKeyIdAndIv(cencInfo, keyId, keyIdLen, iv, ivLen);
510    if (errNo != AV_ERR_OK) {
511        // 异常处理。
512    }
513    // 设置Sample信息。
514    errNo = OH_AVCencInfo_SetSubsampleInfo(cencInfo, encryptedBlockCount, skippedBlockCount, firstEncryptedOffset,
515        subsampleCount, subsamples);
516    if (errNo != AV_ERR_OK) {
517        // 异常处理。
518    }
519    // 设置模式:KeyId、Iv和SubSamples已被设置。
520    errNo = OH_AVCencInfo_SetMode(cencInfo, DRM_CENC_INFO_KEY_IV_SUBSAMPLES_SET);
521    if (errNo != AV_ERR_OK) {
522        // 异常处理。
523    }
524    // 将CencInfo设置到AVBuffer中。
525    errNo = OH_AVCencInfo_SetAVBuffer(cencInfo, buffer);
526    if (errNo != AV_ERR_OK) {
527        // 异常处理。
528    }
529    // 销毁CencInfo实例。
530    errNo = OH_AVCencInfo_Destroy(cencInfo);
531    if (errNo != AV_ERR_OK) {
532        // 异常处理。
533    }
534    ```
535
53611. 调用OH_VideoDecoder_PushInputBuffer()写入解码码流。
537
538    送入输入队列进行解码,以下示例中:
539
540    - buffer:回调函数OnNeedInputBuffer传入的参数,可以通过[OH_AVBuffer_GetAddr](../../reference/apis-avcodec-kit/_core.md#oh_avbuffer_getaddr)接口获取输入码流虚拟地址;
541    - index:回调函数OnNeedInputBuffer传入的参数,与buffer唯一对应的标识;
542    - size、offset、pts、frameData:输入尺寸、偏移量、时间戳、帧数据等字段信息,获取方式可以参考[音视频解封装](./audio-video-demuxer.md)“步骤-9:开始解封装,循环获取sample”;
543    - flags:缓冲区标记的类别,请参考[OH_AVCodecBufferFlags](../../reference/apis-avcodec-kit/_core.md#oh_avcodecbufferflags)。
544
545    ```c++
546    std::shared_ptr<CodecBufferInfo> bufferInfo = inQueue.Dequeue();
547    std::shared_lock<std::shared_mutex> lock(codecMutex);
548    if (bufferInfo == nullptr || !bufferInfo->isValid) {
549        // 异常处理。
550    }
551    // 写入码流数据。
552    uint8_t *addr = OH_AVBuffer_GetAddr(bufferInfo->buffer);
553    int32_t capcacity = OH_AVBuffer_GetCapacity(bufferInfo->buffer);
554    if (size > capcacity) {
555        // 异常处理。
556    }
557    memcpy(addr, frameData, size);
558    // 配置帧数据的输入尺寸、偏移量、时间戳等字段信息。
559    OH_AVCodecBufferAttr info;
560    info.size = size;
561    info.offset = offset;
562    info.pts = pts;
563    info.flags = flags;
564    // info信息写入buffer。
565    int32_t ret = OH_AVBuffer_SetBufferAttr(bufferInfo->buffer, &info);
566    if (ret != AV_ERR_OK) {
567        // 异常处理。
568    }
569    // 送入解码输入队列进行解码,index为对应buffer队列的下标。
570    ret = OH_VideoDecoder_PushInputBuffer(videoDec, bufferInfo->index);
571    if (ret != AV_ERR_OK) {
572        // 异常处理。
573    }
574    ```
575
57612. 调用OH_VideoDecoder_RenderOutputBuffer()/OH_VideoDecoder_RenderOutputBufferAtTime()显示并释放解码帧,
577    或调用OH_VideoDecoder_FreeOutputBuffer()释放解码帧。
578    以下示例中:
579
580    - index:回调函数OnNewOutputBuffer传入的参数,与buffer唯一对应的标识;
581    - buffer:回调函数OnNewOutputBuffer传入的参数,Surface模式开发者无法通过[OH_AVBuffer_GetAddr](../../reference/apis-avcodec-kit/_core.md#oh_avbuffer_getaddr)接口获取图像虚拟地址。
582
583    ```c++
584    std::shared_ptr<CodecBufferInfo> bufferInfo = outQueue.Dequeue();
585    std::shared_lock<std::shared_mutex> lock(codecMutex);
586    if (bufferInfo == nullptr || !bufferInfo->isValid) {
587        // 异常处理。
588    }
589    // 获取解码后信息。
590    OH_AVCodecBufferAttr info;
591    int32_t ret = OH_AVBuffer_GetBufferAttr(bufferInfo->buffer, &info);
592    if (ret != AV_ERR_OK) {
593        // 异常处理。
594    }
595    // 值由开发者决定。
596    bool isRender;
597    bool isNeedRenderAtTime;
598    if (isRender) {
599        // 显示并释放已完成处理的信息,index为对应buffer队列的下标。
600        if (isNeedRenderAtTime){
601            // 获取系统绝对时间,renderTimestamp由开发者结合业务指定显示时间。
602            int64_t renderTimestamp =
603                std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::high_resolution_clock::now().time_since_epoch()).count();
604            ret = OH_VideoDecoder_RenderOutputBufferAtTime(videoDec, bufferInfo->index, renderTimestamp);
605        } else {
606           ret = OH_VideoDecoder_RenderOutputBuffer(videoDec, bufferInfo->index);
607        }
608
609    } else {
610        // 释放已完成处理的信息。
611        ret = OH_VideoDecoder_FreeOutputBuffer(videoDec, bufferInfo->index);
612    }
613    if (ret != AV_ERR_OK) {
614        // 异常处理。
615    }
616
617    ```
618
619    > **注意:**
620    > 如果要获取buffer的属性,如pixel_format、stride等可通过调用[OH_NativeWindow_NativeWindowHandleOpt](../../reference/apis-arkgraphics2d/_native_window.md#oh_nativewindow_nativewindowhandleopt)接口获取。
621    >
622
62313. (可选)调用OH_VideoDecoder_Flush()刷新解码器。
624
625    调用OH_VideoDecoder_Flush接口后,解码器仍处于运行态,但会清除解码器中缓存的输入和输出数据及参数集如H.264格式的PPS/SPS626    此时需要调用OH_VideoDecoder_Start接口重新开始解码。
627    以下示例中:
628
629    - xpsData、xpsSize:PPS/SPS信息,获取方式可以参考[音视频解封装](./audio-video-demuxer.md)。
630
631    ```c++
632    std::unique_lock<std::shared_mutex> lock(codecMutex);
633    // 刷新解码器videoDec。
634    int32_t ret = OH_VideoDecoder_Flush(videoDec);
635    if (ret != AV_ERR_OK) {
636        // 异常处理。
637    }
638    inQueue.Flush();
639    outQueue.Flush();
640    // 重新开始解码。
641    ret = OH_VideoDecoder_Start(videoDec);
642    if (ret != AV_ERR_OK) {
643        // 异常处理。
644    }
645
646    std::shared_ptr<CodecBufferInfo> bufferInfo = outQueue.Dequeue();
647    if (bufferInfo == nullptr || !bufferInfo->isValid) {
648        // 异常处理。
649    }
650    // 重传PPS/SPS651    // 配置帧数据PPS/SPS信息。
652    uint8_t *addr = OH_AVBuffer_GetAddr(bufferInfo->buffer);
653    int32_t capcacity = OH_AVBuffer_GetCapacity(bufferInfo->buffer);
654    if (xpsSize > capcacity) {
655        // 异常处理。
656    }
657    memcpy(addr, xpsData, xpsSize);
658    OH_AVCodecBufferAttr info;
659    info.flags = AVCODEC_BUFFER_FLAG_CODEC_DATA;
660    // info信息写入buffer。
661    ret = OH_AVBuffer_SetBufferAttr(bufferInfo->buffer, &info);
662    if (ret != AV_ERR_OK) {
663        // 异常处理。
664    }
665    // 将帧数据推送到解码器中,index为对应buffer队列的下标。
666    ret = OH_VideoDecoder_PushInputBuffer(videoDec, bufferInfo->index);
667    if (ret != AV_ERR_OK) {
668        // 异常处理。
669    }
670
671    ```
672
673    > **注意:**
674    > Flush之后,重新调用OH_VideoDecoder_Start接口时,需要重新传PPS/SPS675    >
676
67714. (可选)调用OH_VideoDecoder_Reset()重置解码器。
678
679    调用OH_VideoDecoder_Reset接口后,解码器回到初始化的状态,需要调用OH_VideoDecoder_Configure接口、OH_VideoDecoder_SetSurface接口和OH_VideoDecoder_Prepare接口重新配置。
680
681    ```c++
682    std::unique_lock<std::shared_mutex> lock(codecMutex);
683    // 重置解码器videoDec。
684    int32_t ret = OH_VideoDecoder_Reset(videoDec);
685    if (ret != AV_ERR_OK) {
686        // 异常处理。
687    }
688    inQueue.Flush();
689    outQueue.Flush();
690    // 重新配置解码器参数。
691    ret = OH_VideoDecoder_Configure(videoDec, format);
692    if (ret != AV_ERR_OK) {
693        // 异常处理。
694    }
695    // Surface模式重新配置surface,而Buffer模式不需要配置surface。
696    ret = OH_VideoDecoder_SetSurface(videoDec, nativeWindow);
697    if (ret != AV_ERR_OK) {
698        // 异常处理。
699    }
700    // 解码器重新就绪。
701    ret = OH_VideoDecoder_Prepare(videoDec);
702    if (ret != AV_ERR_OK) {
703        // 异常处理。
704    }
705    ```
706
70715. (可选)调用OH_VideoDecoder_Stop()停止解码器。
708
709    调用OH_VideoDecoder_Stop()后,解码器保留了解码实例,释放输入输出buffer。开发者可以直接调用OH_VideoDecoder_Start接口继续解码,输入的第一个buffer需要携带参数集,从IDR帧开始送入。
710
711    ```c++
712    std::unique_lock<std::shared_mutex> lock(codecMutex);
713    // 终止解码器videoDec。
714    int32_t ret = OH_VideoDecoder_Stop(videoDec);
715    if (ret != AV_ERR_OK) {
716        // 异常处理。
717    }
718    inQueue.Flush();
719    outQueue.Flush();
720    ```
721
72216. 调用OH_VideoDecoder_Destroy()销毁解码器实例,释放资源。
723
724    > **说明:**
725    >
726    > 1. 不能在回调函数中调用;
727    > 2. 执行该步骤之后,需要开发者将videoDec指向nullptr,防止野指针导致程序错误。
728    >
729
730    ```c++
731    std::unique_lock<std::shared_mutex> lock(codecMutex);
732    // 释放nativeWindow实例。
733    if(nativeWindow != nullptr){
734        OH_NativeWindow_DestroyNativeWindow(nativeWindow);
735        nativeWindow = nullptr;
736    }
737    // 调用OH_VideoDecoder_Destroy,注销解码器。
738    int32_t ret = AV_ERR_OK;
739    if (videoDec != nullptr) {
740        ret = OH_VideoDecoder_Destroy(videoDec);
741        videoDec = nullptr;
742    }
743    if (ret != AV_ERR_OK) {
744        // 异常处理。
745    }
746    inQueue.Flush();
747    outQueue.Flush();
748    ```
749
750### Buffer模式
751
752参考以下示例代码,开发者可以完成Buffer模式下视频解码的全流程。此处以H.264文件输入,解码成YUV文件为例。
753本模块目前仅支持异步模式的数据轮转。
754
7551. 添加头文件。
756
757    ```c++
758    #include <multimedia/player_framework/native_avcodec_videodecoder.h>
759    #include <multimedia/player_framework/native_avcapability.h>
760    #include <multimedia/player_framework/native_avcodec_base.h>
761    #include <multimedia/player_framework/native_avformat.h>
762    #include <multimedia/player_framework/native_avbuffer.h>
763    #include <native_buffer/native_buffer.h>
764    #include <fstream>
765    ```
766
7672. 创建解码器实例。
768
769    与Surface模式相同,此处不再赘述。
770
771    ```c++
772    // 通过codecname创建解码器,应用有特殊需求,比如选择支持某种分辨率规格的解码器,可先查询capability,再根据codec name创建解码器。
773    OH_AVCapability *capability = OH_AVCodec_GetCapability(OH_AVCODEC_MIMETYPE_VIDEO_AVC, false);
774    const char *name = OH_AVCapability_GetName(capability);
775    OH_AVCodec *videoDec = OH_VideoDecoder_CreateByName(name);
776    ```
777
778    ```c++
779    // 通过MIME TYPE创建解码器,只能创建系统推荐的特定编解码器。
780    // 涉及创建多路编解码器时,优先创建硬件解码器实例,硬件资源不够时再创建软件解码器实例。
781    // 软/硬解:创建H.264解码器。
782    OH_AVCodec *videoDec = OH_VideoDecoder_CreateByMime(OH_AVCODEC_MIMETYPE_VIDEO_AVC);
783    // 硬解:创建H.265解码器。
784    OH_AVCodec *videoDec = OH_VideoDecoder_CreateByMime(OH_AVCODEC_MIMETYPE_VIDEO_HEVC);
785    ```
786
7873. 调用OH_VideoDecoder_RegisterCallback()设置回调函数。
788
789    注册回调函数指针集合OH_AVCodecCallback,包括:
790
791    - OH_AVCodecOnError 解码器运行错误,返回的错误码详情请参见:[OH_AVCodecOnError](../../reference/apis-avcodec-kit/_codec_base.md#oh_avcodeconerror);
792    - OH_AVCodecOnStreamChanged 码流信息变化,如码流宽、高变化;
793    - OH_AVCodecOnNeedInputBuffer 运行过程中需要新的输入数据,即解码器已准备好,可以输入数据;
794    - OH_AVCodecOnNewOutputBuffer 运行过程中产生了新的输出数据,即解码完成。
795
796    开发者可以通过处理该回调报告的信息,确保解码器正常运转。
797
798    <!--RP2--><!--RP2End-->
799
800    ```c++
801    int32_t cropTop = 0;
802    int32_t cropBottom = 0;
803    int32_t cropLeft = 0;
804    int32_t cropRight = 0;
805    bool isFirstFrame = true;
806    // 解码异常回调OH_AVCodecOnError实现。
807    static void OnError(OH_AVCodec *codec, int32_t errorCode, void *userData)
808    {
809        // 回调的错误码由开发者判断处理。
810        (void)codec;
811        (void)errorCode;
812        (void)userData;
813    }
814
815    // 解码数据流变化回调OH_AVCodecOnStreamChanged实现。
816    static void OnStreamChanged(OH_AVCodec *codec, OH_AVFormat *format, void *userData)
817    {
818        // 可选,开发者需要获取视频宽、高、跨距等时可配置。
819        // 可通过format获取到变化后的视频宽、高、跨距等。
820        (void)codec;
821        (void)userData;
822        OH_AVFormat_GetIntValue(format, OH_MD_KEY_VIDEO_PIC_WIDTH, &width);
823        OH_AVFormat_GetIntValue(format, OH_MD_KEY_VIDEO_PIC_HEIGHT, &height);
824        OH_AVFormat_GetIntValue(format, OH_MD_KEY_VIDEO_STRIDE, &widthStride);
825        OH_AVFormat_GetIntValue(format, OH_MD_KEY_VIDEO_SLICE_HEIGHT, &heightStride);
826        // 获取裁剪矩形信息可选。
827        OH_AVFormat_GetIntValue(format, OH_MD_KEY_VIDEO_CROP_TOP, &cropTop);
828        OH_AVFormat_GetIntValue(format, OH_MD_KEY_VIDEO_CROP_BOTTOM, &cropBottom);
829        OH_AVFormat_GetIntValue(format, OH_MD_KEY_VIDEO_CROP_LEFT, &cropLeft);
830        OH_AVFormat_GetIntValue(format, OH_MD_KEY_VIDEO_CROP_RIGHT, &cropRight);
831    }
832
833    // 解码输入回调OH_AVCodecOnNeedInputBuffer实现。
834    static void OnNeedInputBuffer(OH_AVCodec *codec, uint32_t index, OH_AVBuffer *buffer, void *userData)
835    {
836        // 输入帧的数据buffer和对应的index送入inQueue队列。
837        (void)codec;
838        (void)userData;
839        inQueue.Enqueue(std::make_shared<CodecBufferInfo>(index, buffer));
840    }
841
842    // 解码输出回调OH_AVCodecOnNewOutputBuffer实现。
843    static void OnNewOutputBuffer(OH_AVCodec *codec, uint32_t index, OH_AVBuffer *buffer, void *userData)
844    {
845        // 可选,开发者需要获取视频宽、高、跨距等时可配置。
846        // 获取视频宽、高、跨距。
847        if (isFirstFrame) {
848            OH_AVFormat *format = OH_VideoDecoder_GetOutputDescription(codec);
849            OH_AVFormat_GetIntValue(format, OH_MD_KEY_VIDEO_PIC_WIDTH, &width);
850            OH_AVFormat_GetIntValue(format, OH_MD_KEY_VIDEO_PIC_HEIGHT, &height);
851            OH_AVFormat_GetIntValue(format, OH_MD_KEY_VIDEO_STRIDE, &widthStride);
852            OH_AVFormat_GetIntValue(format, OH_MD_KEY_VIDEO_SLICE_HEIGHT, &heightStride);
853            // 获取裁剪矩形信息可选。
854            OH_AVFormat_GetIntValue(format, OH_MD_KEY_VIDEO_CROP_TOP, &cropTop);
855            OH_AVFormat_GetIntValue(format, OH_MD_KEY_VIDEO_CROP_BOTTOM, &cropBottom);
856            OH_AVFormat_GetIntValue(format, OH_MD_KEY_VIDEO_CROP_LEFT, &cropLeft);
857            OH_AVFormat_GetIntValue(format, OH_MD_KEY_VIDEO_CROP_RIGHT, &cropRight);
858            OH_AVFormat_Destroy(format);
859            isFirstFrame = false;
860        }
861        // 完成帧的数据buffer和对应的index送入outQueue队列。
862        (void)userData;
863        outQueue.Enqueue(std::make_shared<CodecBufferInfo>(index, buffer));
864    }
865    // 配置异步回调,调用OH_VideoDecoder_RegisterCallback接口。
866    OH_AVCodecCallback cb = {&OnError, &OnStreamChanged, &OnNeedInputBuffer, &OnNewOutputBuffer};
867    // 配置异步回调。
868    int32_t ret = OH_VideoDecoder_RegisterCallback(videoDec, cb, nullptr); // nullptr:用户特定数据userData为空。
869    if (ret != AV_ERR_OK) {
870        // 异常处理。
871    }
872    ```
873
874    > **说明:**
875    >
876    > 在回调函数中,对数据队列进行操作时,需要注意多线程同步的问题。
877    >
878
8794. (可选)OH_VideoDecoder_SetDecryptionConfig设置解密配置。在获取到DRM信息(参考[音视频解封装](audio-video-demuxer.md)开发步骤第4步),完成DRM许可证申请后,通过此接口进行解密配置。此接口需在Prepare前调用。在Buffer模式下,DRM解密能力仅支持非安全视频通路。DRM相关接口详见[DRM API文档](../../reference/apis-drm-kit/_drm.md)。
880
881    添加头文件。
882
883    ```c++
884    #include <multimedia/drm_framework/native_mediakeysystem.h>
885    #include <multimedia/drm_framework/native_mediakeysession.h>
886    #include <multimedia/drm_framework/native_drm_err.h>
887    #include <multimedia/drm_framework/native_drm_common.h>
888    ```
889
890    在 CMake 脚本中链接动态库。
891
892    ``` cmake
893    target_link_libraries(sample PUBLIC libnative_drm.so)
894    ```
895
896    使用示例:
897
898    ```c++
899    // 根据DRM信息创建指定的DRM系统,以创建"com.clearplay.drm"为例。
900    MediaKeySystem *system = nullptr;
901    int32_t ret = OH_MediaKeySystem_Create("com.clearplay.drm", &system);
902    if (system == nullptr) {
903        printf("create media key system failed");
904        return;
905    }
906
907    // 创建解密会话。
908    // 使用非安全视频通路,应创建CONTENT_PROTECTION_LEVEL_SW_CRYPTO及以上内容保护级别的MediaKeySession。
909    MediaKeySession *session = nullptr;
910    DRM_ContentProtectionLevel contentProtectionLevel = CONTENT_PROTECTION_LEVEL_SW_CRYPTO;
911    ret = OH_MediaKeySystem_CreateMediaKeySession(system, &contentProtectionLevel, &session);
912    if (ret != DRM_OK) {
913        // 如创建失败,请查看DRM接口文档及日志信息。
914        printf("create media key session failed.");
915        return;
916    }
917    if (session == nullptr) {
918        printf("media key session is nullptr.");
919        return;
920    }
921    // 获取许可证请求、设置许可证响应等。
922    // 设置解密配置,即将解密会话、安全视频通路标志设置到解码器中。
923    bool secureVideoPath = false;
924    ret = OH_VideoDecoder_SetDecryptionConfig(videoDec, session, secureVideoPath);
925    ```
926
9275. 调用OH_VideoDecoder_Configure()配置解码器。
928
929    与Surface模式相同,此处不再赘述。
930
931    ```c++
932    OH_AVFormat *format = OH_AVFormat_Create();
933    // 写入format。
934    OH_AVFormat_SetIntValue(format, OH_MD_KEY_WIDTH, width); // 必须配置。
935    OH_AVFormat_SetIntValue(format, OH_MD_KEY_HEIGHT, height); // 必须配置。
936    OH_AVFormat_SetIntValue(format, OH_MD_KEY_PIXEL_FORMAT, pixelFormat);
937    // 配置解码器。
938    int32_t ret = OH_VideoDecoder_Configure(videoDec, format);
939    if (ret != AV_ERR_OK) {
940        // 异常处理。
941    }
942    OH_AVFormat_Destroy(format);
943    ```
944
9456. 调用OH_VideoDecoder_Prepare()解码器就绪。
946
947    该接口将在解码器运行前进行一些数据的准备工作。
948
949    ```c++
950    int32_t ret = OH_VideoDecoder_Prepare(videoDec);
951    if (ret != AV_ERR_OK) {
952        // 异常处理。
953    }
954    ```
955
9567. 调用OH_VideoDecoder_Start()启动解码器。
957
958    ```c++
959    std::unique_ptr<std::ofstream> outputFile = std::make_unique<std::ofstream>();
960    outputFile->open("/*yourpath*.yuv", std::ios::out | std::ios::binary | std::ios::ate);
961    // 启动解码器,开始解码。
962    int32_t ret = OH_VideoDecoder_Start(videoDec);
963    if (ret != AV_ERR_OK) {
964        // 异常处理。
965    }
966    ```
967
9688. (可选)调用OH_AVCencInfo_SetAVBuffer(),设置cencInfo。
969
970    与Surface模式相同,此处不再赘述。
971
972    使用示例:
973
974    ```c++
975    uint32_t keyIdLen = DRM_KEY_ID_SIZE;
976    uint8_t keyId[] = {
977        0xd4, 0xb2, 0x01, 0xe4, 0x61, 0xc8, 0x98, 0x96,
978        0xcf, 0x05, 0x22, 0x39, 0x8d, 0x09, 0xe6, 0x28};
979    uint32_t ivLen = DRM_KEY_IV_SIZE;
980    uint8_t iv[] = {
981        0xbf, 0x77, 0xed, 0x51, 0x81, 0xde, 0x36, 0x3e,
982        0x52, 0xf7, 0x20, 0x4f, 0x72, 0x14, 0xa3, 0x95};
983    uint32_t encryptedBlockCount = 0;
984    uint32_t skippedBlockCount = 0;
985    uint32_t firstEncryptedOffset = 0;
986    uint32_t subsampleCount = 1;
987    DrmSubsample subsamples[1] = { {0x10, 0x16} };
988    // 创建CencInfo实例。
989    OH_AVCencInfo *cencInfo = OH_AVCencInfo_Create();
990    if (cencInfo == nullptr) {
991        // 异常处理。
992    }
993    // 设置解密算法。
994    OH_AVErrCode errNo = OH_AVCencInfo_SetAlgorithm(cencInfo, DRM_ALG_CENC_AES_CTR);
995    if (errNo != AV_ERR_OK) {
996        // 异常处理。
997    }
998    // 设置KeyId和Iv。
999    errNo = OH_AVCencInfo_SetKeyIdAndIv(cencInfo, keyId, keyIdLen, iv, ivLen);
1000    if (errNo != AV_ERR_OK) {
1001        // 异常处理。
1002    }
1003    // 设置Sample信息。
1004    errNo = OH_AVCencInfo_SetSubsampleInfo(cencInfo, encryptedBlockCount, skippedBlockCount, firstEncryptedOffset,
1005        subsampleCount, subsamples);
1006    if (errNo != AV_ERR_OK) {
1007        // 异常处理。
1008    }
1009    // 设置模式:KeyId、Iv和SubSamples已被设置。
1010    errNo = OH_AVCencInfo_SetMode(cencInfo, DRM_CENC_INFO_KEY_IV_SUBSAMPLES_SET);
1011    if (errNo != AV_ERR_OK) {
1012        // 异常处理。
1013    }
1014    // 将CencInfo设置到AVBuffer中。
1015    errNo = OH_AVCencInfo_SetAVBuffer(cencInfo, buffer);
1016    if (errNo != AV_ERR_OK) {
1017        // 异常处理。
1018    }
1019    // 销毁CencInfo实例。
1020    errNo = OH_AVCencInfo_Destroy(cencInfo);
1021    if (errNo != AV_ERR_OK) {
1022        // 异常处理。
1023    }
1024    ```
1025
10269. 调用OH_VideoDecoder_PushInputBuffer()写入解码码流。
1027
1028    与Surface模式相同,此处不再赘述。
1029
1030    ```c++
1031    std::shared_ptr<CodecBufferInfo> bufferInfo = inQueue.Dequeue();
1032    std::shared_lock<std::shared_mutex> lock(codecMutex);
1033    if (bufferInfo == nullptr || !bufferInfo->isValid) {
1034        // 异常处理。
1035    }
1036    // 写入码流数据。
1037    uint8_t *addr = OH_AVBuffer_GetAddr(bufferInfo->buffer);
1038    int32_t capcacity = OH_AVBuffer_GetCapacity(bufferInfo->buffer);
1039    if (size > capcacity) {
1040        // 异常处理。
1041    }
1042    memcpy(addr, frameData, size);
1043    // 配置帧数据的输入尺寸、偏移量、时间戳等字段信息。
1044    OH_AVCodecBufferAttr info;
1045    info.size = size;
1046    info.offset = offset;
1047    info.pts = pts;
1048    info.flags = flags;
1049    // info信息写入buffer。
1050    ret = OH_AVBuffer_SetBufferAttr(bufferInfo->buffer, &info);
1051    if (ret != AV_ERR_OK) {
1052        // 异常处理。
1053    }
1054    // 送入解码输入队列进行解码,index为对应buffer队列的下标。
1055    int32_t ret = OH_VideoDecoder_PushInputBuffer(videoDec, bufferInfo->index);
1056    if (ret != AV_ERR_OK) {
1057        // 异常处理。
1058    }
1059    ```
1060
106110. 调用OH_VideoDecoder_FreeOutputBuffer()释放解码帧。
1062
1063    以下示例中:
1064
1065    - index:回调函数OnNewOutputBuffer传入的参数,与buffer唯一对应的标识;
1066    - buffer: 回调函数OnNewOutputBuffer传入的参数,可以通过[OH_AVBuffer_GetAddr](../../reference/apis-avcodec-kit/_core.md#oh_avbuffer_getaddr)接口获取图像虚拟地址。
1067
1068    ```c++
1069    std::shared_ptr<CodecBufferInfo> bufferInfo = outQueue.Dequeue();
1070    std::shared_lock<std::shared_mutex> lock(codecMutex);
1071    if (bufferInfo == nullptr || !bufferInfo->isValid) {
1072        // 异常处理。
1073    }
1074    // 获取解码后信息。
1075    OH_AVCodecBufferAttr info;
1076    int32_t ret = OH_AVBuffer_GetBufferAttr(bufferInfo->buffer, &info);
1077    if (ret != AV_ERR_OK) {
1078        // 异常处理。
1079    }
1080    // 将解码完成数据data写入到对应输出文件中。
1081    outputFile->write(reinterpret_cast<char *>(OH_AVBuffer_GetAddr(bufferInfo->buffer)), info.size);
1082    // Buffer模式,释放已完成写入的数据,index为对应buffer队列的下标。
1083    ret = OH_VideoDecoder_FreeOutputBuffer(videoDec, bufferInfo->index);
1084    if (ret != AV_ERR_OK) {
1085        // 异常处理。
1086    }
1087    ```
1088
1089    NV12/NV21图像如果需要依次将Y、U、V三个分量拷贝至另一块buffer中,以NV12图像为例,按行拷贝示例如下:
1090
1091    以NV12图像为例,width、height、wStride、hStride图像排布参考下图:
1092
1093    - OH_MD_KEY_VIDEO_PIC_WIDTH表示width;
1094    - OH_MD_KEY_VIDEO_PIC_HEIGHT表示height;
1095    - OH_MD_KEY_VIDEO_STRIDE表示wStride;
1096    - OH_MD_KEY_VIDEO_SLICE_HEIGHT表示hStride。
1097
1098    ![copy by line](figures/copy-by-line-decoder.png)
1099
1100    添加头文件。
1101
1102    ```c++
1103    #include <string.h>
1104    ```
1105
1106    使用示例:
1107
1108    ```c++
1109    // 源内存区域的宽、高,通过回调函数OnStreamChanged或接口OH_VideoDecoder_GetOutputDescription获取。
1110    struct Rect
1111    {
1112        int32_t width;
1113        int32_t height;
1114    };
1115
1116    struct DstRect // 目标内存区域的宽、高跨距,由开发者自行设置。
1117    {
1118        int32_t wStride;
1119        int32_t hStride;
1120    };
1121    // 源内存区域的宽、高跨距,通过回调函数OnStreamChanged或接口OH_VideoDecoder_GetOutputDescription获取。
1122    struct SrcRect
1123    {
1124        int32_t wStride;
1125        int32_t hStride;
1126    };
1127
1128    Rect rect = {320, 240};
1129    DstRect dstRect = {320, 240};
1130    SrcRect srcRect = {320, 256};
1131    uint8_t* dst = new uint8_t[dstRect.hStride * dstRect.wStride * 3 / 2]; // 目标内存区域的指针。
1132    uint8_t* src = new uint8_t[srcRect.hStride * srcRect.wStride * 3 / 2]; // 源内存区域的指针。
1133    uint8_t* dstTemp = dst;
1134    uint8_t* srcTemp = src;
1135
1136    // Y 将Y区域的源数据复制到另一个区域的目标数据中。
1137    for (int32_t i = 0; i < rect.height; ++i) {
1138        //将源数据的一行数据复制到目标数据的一行中。
1139        memcpy(dstTemp, srcTemp, rect.width);
1140        // 更新源数据和目标数据的指针,进行下一行的复制。每更新一次源数据和目标数据的指针都向下移动一个wStride。
1141        dstTemp += dstRect.wStride;
1142        srcTemp += srcRect.wStride;
1143    }
1144    // padding。
1145    // 更新源数据和目标数据的指针,指针都向下移动一个padding。
1146    dstTemp += (dstRect.hStride - rect.height) * dstRect.wStride;
1147    srcTemp += (srcRect.hStride - rect.height) * srcRect.wStride;
1148    rect.height >>= 1;
1149    // UV 将UV区域的源数据复制到另一个区域的目标数据中。
1150    for (int32_t i = 0; i < rect.height; ++i) {
1151        memcpy(dstTemp, srcTemp, rect.width);
1152        dstTemp += dstRect.wStride;
1153        srcTemp += srcRect.wStride;
1154    }
1155
1156    delete[] dst;
1157    dst = nullptr;
1158    delete[] src;
1159    src = nullptr;
1160    ```
1161
1162    硬件解码在处理buffer数据时(释放数据前),输出回调开发者收到的AVbuffer是宽、高对齐后的图像数据。
1163    一般需要获取数据的宽、高、跨距、像素格式来保证解码输出数据被正确的处理。
1164
1165    具体实现请参考:[Buffer模式](#buffer模式)的步骤3-调用OH_VideoDecoder_RegisterCallback()设置回调函数来获取数据的宽、高、跨距、像素格式。
1166
1167后续流程(包括刷新解码器、重置解码器、停止解码器、销毁解码器)与Surface模式基本一致,请参考[Surface模式](#surface模式)的步骤13-16。
1168
1169<!--RP5-->
1170<!--RP5End-->