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