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