1# 媒体数据解析 2 3调用者可以调用本模块的Native API接口,完成媒体数据的解封装相关操作,即从比特流数据中取出音频、视频、字幕等媒体sample,获得DRM相关信息 4 5当前支持的数据输入类型有:远程连接(http协议)和文件描述符(fd)。 6 7当前支持的解封装格式请参考[AVCodec支持的格式](avcodec-support-formats.md#媒体数据解析)。 8 9**适用场景**: 10 11- 播放 12 13 播放媒体文件时,需要先对媒体流进行解封装,然后使用解封装获取的sample进行解码和播放。 14 15- 音视频编辑 16 17 编辑媒体文件时,需要先对媒体流进行解封装,获取到指定sample进行编辑。 18 19- 媒体文件格式转换(转封装) 20 21 媒体文件格式转换时,需要先对媒体流进行解封装,然后按需将媒体流封装至新的格式文件内。 22 23## 开发指导 24 25详细的API说明参考[AVDemuxer](../../reference/apis-avcodec-kit/_a_v_demuxer.md)和[AVSource](../../reference/apis-avcodec-kit/_a_v_source.md) 26 27> **说明** 28> 29> - 调用解封装能力解析网络播放路径,需要[声明权限](../../security/AccessToken/declare-permissions.md):ohos.permission.INTERNET 30> - 调用解封装能力解析本地文件,需要[向用户申请授权](../../security/AccessToken/request-user-authorization.md):ohos.permission.READ_MEDIA 31> - 如果使用ResourceManager.getRawFd打开HAP资源文件描述符,使用方法请参考[ResourceManager API参考](../../reference/apis-localization-kit/js-apis-resource-manager.md#getrawfd9) 32 33### 在 CMake 脚本中链接动态库 34 35``` cmake 36target_link_libraries(sample PUBLIC libnative_media_codecbase.so) 37target_link_libraries(sample PUBLIC libnative_media_avdemuxer.so) 38target_link_libraries(sample PUBLIC libnative_media_avsource.so) 39target_link_libraries(sample PUBLIC libnative_media_core.so) 40``` 41 42> **说明:** 43> 44> 上述'sample'字样仅为示例,此处由调用者根据实际工程目录自定义。 45> 46 47### 开发步骤 48 491. 添加头文件。 50 51 ```c++ 52 #include <multimedia/player_framework/native_avdemuxer.h> 53 #include <multimedia/player_framework/native_avsource.h> 54 #include <multimedia/player_framework/native_avcodec_base.h> 55 #include <multimedia/player_framework/native_avformat.h> 56 #include <multimedia/player_framework/native_avbuffer.h> 57 #include <fcntl.h> 58 #include <sys/stat.h> 59 ``` 60 612. 创建资源管理实例对象。 62 63 调用者HAP中使用open获取fd时,filepath需要转换为[沙箱路径](../../file-management/app-sandbox-directory.md#应用沙箱路径和真实物理路径的对应关系),才能获取沙盒资源。 64 65 ```c++ 66 // 创建文件操作符 fd,打开时对文件句柄必须有读权限(filePath 为待解封装文件路径,需预置文件,保证路径指向的文件存在) 67 std::string filePath = "test.mp4"; 68 int fd = open(filePath.c_str(), O_RDONLY); 69 struct stat fileStatus {}; 70 size_t fileSize = 0; 71 if (stat(filePath.c_str(), &fileStatus) == 0) { 72 fileSize = static_cast<size_t>(fileStatus.st_size); 73 } else { 74 printf("get stat failed"); 75 return; 76 } 77 // 为 fd 资源文件创建 source 资源对象, 传入 offset 不为文件起始位置 或 size 不为文件大小时,可能会因不能获取完整数据导致 source 创建失败、或后续解封装失败等问题 78 OH_AVSource *source = OH_AVSource_CreateWithFD(fd, 0, fileSize); 79 if (source == nullptr) { 80 printf("create source failed"); 81 return; 82 } 83 // 为 uri 资源文件创建 source 资源对象(可选) 84 // OH_AVSource *source = OH_AVSource_CreateWithURI(uri); 85 86 // 为自定义数据源创建 source 资源对象(可选)。使用该方式前,需要先实现AVSourceReadAt接口函数实现。 87 // 当使用OH_AVSource_CreateWithDataSource时需要补充g_filePath 88 // g_filePath = filePath ; 89 // OH_AVDataSource dataSource = {fileSize, AVSourceReadAt}; 90 // OH_AVSource *source = OH_AVSource_CreateWithDataSource(&dataSource); 91 ``` 92 93 AVSourceReadAt接口函数,需要放在创建资源管理实例对象前实现: 94 95 ```c++ 96 // 添加头文件 97 #include <fstream> 98 ``` 99 100 ```c++ 101 static std::string g_filePath; 102 103 enum MediaDataSourceError : int32_t { 104 SOURCE_ERROR_IO = -2, 105 SOURCE_ERROR_EOF = -1 106 }; 107 108 int32_t AVSourceReadAt(OH_AVBuffer *data, int32_t length, int64_t pos) 109 { 110 if (data == nullptr) { 111 printf("AVSourceReadAt : data is nullptr!\n"); 112 return MediaDataSourceError::SOURCE_ERROR_IO; 113 } 114 115 std::ifstream infile(g_filePath, std::ofstream::binary); 116 if (!infile.is_open()) { 117 printf("AVSourceReadAt : open file failed! file:%s\n", g_filePath.c_str()); 118 return MediaDataSourceError::SOURCE_ERROR_IO; // 打开文件失败 119 } 120 121 infile.seekg(0, std::ios::end); 122 int64_t fileSize = infile.tellg(); 123 if (pos >= fileSize) { 124 printf("AVSourceReadAt : pos over or equals file size!\n"); 125 return MediaDataSourceError::SOURCE_ERROR_EOF; // pos已经是文件末尾位置,无法读取 126 } 127 128 if (pos + length > fileSize) { 129 length = fileSize - pos; // pos+length长度超过文件大小时,读取从pos到文件末尾的数据 130 } 131 132 infile.seekg(pos, std::ios::beg); 133 if (length <= 0) { 134 printf("AVSourceReadAt : raed length less than zero!\n"); 135 return MediaDataSourceError::SOURCE_ERROR_IO; 136 } 137 char* buffer = new char[length]; 138 infile.read(buffer, length); 139 infile.close(); 140 141 memcpy(reinterpret_cast<char *>(OH_AVBuffer_GetAddr(data)), 142 buffer, length); 143 delete[] buffer; 144 145 return length; 146 } 147 ``` 1483. 创建解封装器实例对象。 149 ```c++ 150 // 为资源对象创建对应的解封装器 151 OH_AVDemuxer *demuxer = OH_AVDemuxer_CreateWithSource(source); 152 if (demuxer == nullptr) { 153 printf("create demuxer failed"); 154 return; 155 } 156 ``` 1574. 注册[DRM信息监听函数](../../reference/apis-avcodec-kit/_a_v_demuxer.md#demuxer_mediakeysysteminfocallback)(可选,若非DRM码流或已获得[DRM信息](../../reference/apis-drm-kit/_drm.md#drm_mediakeysysteminfo),可跳过此步)。 158 159 设置DRM信息监听的接口,回调函数支持返回解封装器实例,适用于多个解封装器场景。 160 161 ```c++ 162 // DRM信息监听回调OnDrmInfoChangedWithObj实现 163 static void OnDrmInfoChangedWithObj(OH_AVDemuxer *demuxer, DRM_MediaKeySystemInfo *drmInfo) 164 { 165 // 解析DRM信息,包括数量、DRM类型及对应pssh 166 } 167 168 Demuxer_MediaKeySystemInfoCallback callback = &OnDrmInfoChangedWithObj; 169 Drm_ErrCode ret = OH_AVDemuxer_SetDemuxerMediaKeySystemInfoCallback(demuxer, callback); 170 ``` 171 在监听到DRM信息后,也可主动调用获取DRM信息(uuid及对应pssh)接口。 172 173 ```c++ 174 DRM_MediaKeySystemInfo mediaKeySystemInfo; 175 OH_AVDemuxer_GetMediaKeySystemInfo(demuxer, &mediaKeySystemInfo); 176 ``` 177 在获取、解析DRM信息后,需创建对应DRM解决方案的[MediaKeySystem](../drm/native-drm-mediakeysystem-management.md#drm系统管理)、[MediaKeySession](../drm/native-drm-mediakeysession-management.md#drm会话管理),获取DRM许可证等。并根据需要设置音频解密配置(详见[音频解码开发指南开发步骤](./audio-decoding.md#开发步骤)第4步)、设置视频解密配置(详见[视频解码开发指南开发步骤Surface模式](./video-decoding.md#surface模式)第5步或[Buffer模式](./video-decoding.md#buffer模式)第4步),实现DRM内容解密。 178 1795. 获取文件轨道数(可选,若用户已知轨道信息,可跳过此步)。 180 181 ```c++ 182 // 从文件 source 信息获取文件轨道数,用户可通过该接口获取文件级别属性,具体支持信息参考附表 1 183 OH_AVFormat *sourceFormat = OH_AVSource_GetSourceFormat(source); 184 if (sourceFormat == nullptr) { 185 printf("get source format failed"); 186 return; 187 } 188 int32_t trackCount = 0; 189 if (!OH_AVFormat_GetIntValue(sourceFormat, OH_MD_KEY_TRACK_COUNT, &trackCount)) { 190 printf("get track count from source format failed"); 191 return; 192 } 193 OH_AVFormat_Destroy(sourceFormat); 194 ``` 195 1966. 获取轨道index及信息(可选,若用户已知轨道信息,可跳过此步)。 197 198 ```c++ 199 uint32_t audioTrackIndex = 0; 200 uint32_t videoTrackIndex = 0; 201 int32_t w = 0; 202 int32_t h = 0; 203 int32_t trackType; 204 for (uint32_t index = 0; index < (static_cast<uint32_t>(trackCount)); index++) { 205 // 获取轨道信息,用户可通过该接口获取对应轨道级别属性,具体支持信息参考附表 2 206 OH_AVFormat *trackFormat = OH_AVSource_GetTrackFormat(source, index); 207 if (trackFormat == nullptr) { 208 printf("get track format failed"); 209 return; 210 } 211 if (!OH_AVFormat_GetIntValue(trackFormat, OH_MD_KEY_TRACK_TYPE, &trackType)) { 212 printf("get track type from track format failed"); 213 return; 214 } 215 static_cast<OH_MediaType>(trackType) == OH_MediaType::MEDIA_TYPE_AUD ? audioTrackIndex = index : videoTrackIndex = index; 216 // 获取视频轨宽高 217 if (trackType == OH_MediaType::MEDIA_TYPE_VID) { 218 if (!OH_AVFormat_GetIntValue(trackFormat, OH_MD_KEY_WIDTH, &w)) { 219 printf("get track width from track format failed"); 220 return; 221 } 222 if (!OH_AVFormat_GetIntValue(trackFormat, OH_MD_KEY_HEIGHT, &h)) { 223 printf("get track height from track format failed"); 224 return; 225 } 226 } 227 OH_AVFormat_Destroy(trackFormat); 228 } 229 ``` 230 2317. 添加解封装轨道。 232 233 ```c++ 234 if(OH_AVDemuxer_SelectTrackByID(demuxer, audioTrackIndex) != AV_ERR_OK){ 235 printf("select audio track failed: %d", audioTrackIndex); 236 return; 237 } 238 if(OH_AVDemuxer_SelectTrackByID(demuxer, videoTrackIndex) != AV_ERR_OK){ 239 printf("select video track failed: %d", videoTrackIndex); 240 return; 241 } 242 // 取消选择轨道(可选) 243 // OH_AVDemuxer_UnselectTrackByID(demuxer, audioTrackIndex); 244 ``` 245 2468. 调整轨道到指定时间点(可选)。 247 248 ```c++ 249 // 调整轨道到指定时间点,后续从该时间点进行解封装 250 // 注意: 251 // 1. mpegts格式文件使用OH_AVDemuxer_SeekToTime功能时,跳转到的位置可能为非关键帧。可在跳转后调用OH_AVDemuxer_ReadSampleBuffer,通过获取到的OH_AVCodecBufferAttr判断当前帧是否为关键帧。若非关键帧影响应用侧显示等功能,可在跳转后循环读取,获取到后续第一帧关键帧后,再进行解码等处理。 252 // 2. ogg格式文件使用OH_AVDemuxer_SeekToTime功能时,会跳转到传入时间millisecond所在时间间隔(秒)的起始处,可能会导致一定数量的帧误差。 253 OH_AVDemuxer_SeekToTime(demuxer, 0, OH_AVSeekMode::SEEK_MODE_CLOSEST_SYNC); 254 ``` 255 2569. 开始解封装,循环获取sample(以含音频、视频两轨的文件为例)。 257 258 BufferAttr包含的属性: 259 - size:sample尺寸; 260 - offset:数据在AVBuffer中的偏移,一般为0; 261 - pts:文件封装的显示时间戳; 262 - flags:sample属性。 263 264 | flag | 描述 | 265 | -------- | -------- | 266 | AVCODEC_BUFFER_FLAGS_NONE | 默认。 | 267 | AVCODEC_BUFFER_FLAGS_EOS | 结尾sample,数据为空。 | 268 | AVCODEC_BUFFER_FLAGS_SYNC_FRAME | IDR帧或I帧。 | 269 | AVCODEC_BUFFER_FLAGS_INCOMPLETE_FRAME | 非完整的sample,一般由于buffer过小,无法拷贝完整的sample。 | 270 | AVCODEC_BUFFER_FLAGS_CODEC_DATA | 含参数集信息的帧。 | 271 | AVCODEC_BUFFER_FLAGS_DISCARD | 可丢弃的帧。 | 272 273 ```c++ 274 // 按照指定size创建buffer,用于保存用户解封装得到的数据。 275 // buffer大小设置建议大于待获取的码流大小,示例中buffer大小设置为单帧图像的大小。 276 OH_AVBuffer *buffer = OH_AVBuffer_Create(w * h * 3 >> 1); 277 if (buffer == nullptr) { 278 printf("build buffer failed"); 279 return; 280 } 281 OH_AVCodecBufferAttr info; 282 bool videoIsEnd = false; 283 bool audioIsEnd = false; 284 int32_t ret; 285 while (!audioIsEnd || !videoIsEnd) { 286 // 在调用 OH_AVDemuxer_ReadSampleBuffer 接口获取数据前,需要先调用 OH_AVDemuxer_SelectTrackByID 选中需要获取数据的轨道 287 // 获取音频sample 288 if(!audioIsEnd) { 289 ret = OH_AVDemuxer_ReadSampleBuffer(demuxer, audioTrackIndex, buffer); 290 if (ret == AV_ERR_OK) { 291 // 可通过 buffer 获取并处理音频sample 292 OH_AVBuffer_GetBufferAttr(buffer, &info); 293 printf("audio info.size: %d\n", info.size); 294 if (info.flags == OH_AVCodecBufferFlags::AVCODEC_BUFFER_FLAGS_EOS) { 295 audioIsEnd = true; 296 } 297 } 298 } 299 if(!videoIsEnd) { 300 ret = OH_AVDemuxer_ReadSampleBuffer(demuxer, videoTrackIndex, buffer); 301 if (ret == AV_ERR_OK) { 302 // 可通过 buffer 获取并处理视频sample 303 OH_AVBuffer_GetBufferAttr(buffer, &info); 304 printf("video info.size: %d\n", info.size); 305 if (info.flags == OH_AVCodecBufferFlags::AVCODEC_BUFFER_FLAGS_EOS) { 306 videoIsEnd = true; 307 } 308 } 309 } 310 } 311 OH_AVBuffer_Destroy(buffer); 312 ``` 313 31410. 销毁解封装实例。 315 ```c++ 316 // 需要用户调用 OH_AVSource_Destroy 接口成功后,手动将对象置为 NULL,对同一对象重复调用 OH_AVSource_Destroy 会导致程序错误 317 if (OH_AVSource_Destroy(source) != AV_ERR_OK) { 318 printf("destroy source pointer error"); 319 } 320 source = NULL; 321 // 需要用户调用 OH_AVDemuxer_Destroy 接口成功后,手动将对象置为 NULL,对同一对象重复调用 OH_AVDemuxer_Destroy 会导致程序错误 322 if (OH_AVDemuxer_Destroy(demuxer) != AV_ERR_OK) { 323 printf("destroy demuxer pointer error"); 324 } 325 demuxer = NULL; 326 close(fd); 327 ``` 328 329## 附表 330### 文件级别属性支持范围 331 332> **说明:** 333> 正常解析时才可以获取对应属性数据;如果文件信息错误或缺失,将导致解析异常,无法获取数据。 334> 335> 数据类型及详细取值范围参考[媒体数据键值对](../../reference/apis-avcodec-kit/_codec_base.md#媒体数据键值对)。 336 337**表1** 文件级别属性支持范围 338| 名称 | 描述 | 339| -- | -- | 340|OH_MD_KEY_TITLE|文件标题的键| 341|OH_MD_KEY_ARTIST|文件艺术家的键| 342|OH_MD_KEY_ALBUM|文件专辑的键| 343|OH_MD_KEY_ALBUM_ARTIST|文件专辑艺术家的键| 344|OH_MD_KEY_DATE|文件日期的键| 345|OH_MD_KEY_COMMENT|文件注释的键| 346|OH_MD_KEY_GENRE|文件流派的键| 347|OH_MD_KEY_COPYRIGHT|文件版权的键| 348|OH_MD_KEY_LANGUAGE|文件语言的键| 349|OH_MD_KEY_DESCRIPTION|文件描述的键| 350|OH_MD_KEY_LYRICS|文件歌词的键| 351|OH_MD_KEY_TRACK_COUNT|文件轨道数量的键| 352|OH_MD_KEY_DURATION|文件时长的键| 353|OH_MD_KEY_START_TIME|文件起始时间的键| 354 355### 轨道级别属性支持范围 356 357> **说明:** 358> 正常解析时才可以获取对应属性数据;如果文件信息错误或缺失,将导致解析异常,无法获取数据。 359> 360> 数据类型及详细取值范围参考[媒体数据键值对](../../reference/apis-avcodec-kit/_codec_base.md#媒体数据键值对)。 361 362**表2** 轨道级别属性支持范围 363| 名称 | 描述 | 视频轨支持 | 音频轨支持 | 字幕轨支持 | 364| -- | -- | -- | -- | -- | 365|OH_MD_KEY_CODEC_MIME|码流编解码器类型的键|√|√|√| 366|OH_MD_KEY_TRACK_TYPE|码流媒体类型的键|√|√|√| 367|OH_MD_KEY_TRACK_START_TIME|码流起始时间的键|√|√|√| 368|OH_MD_KEY_BITRATE|码流比特率的键|√|√|-| 369|OH_MD_KEY_LANGUAGE|码流语言类型的键|√|√|-| 370|OH_MD_KEY_CODEC_CONFIG|编解码器特定数据的键,视频中表示传递参数集,音频中表示传递解码器的参数配置信息|√|√|-| 371|OH_MD_KEY_WIDTH|视频流宽度的键|√|-|-| 372|OH_MD_KEY_HEIGHT|视频流高度的键|√|-|-| 373|OH_MD_KEY_FRAME_RATE|视频流帧率的键|√|-|-| 374|OH_MD_KEY_ROTATION|视频流旋转角度的键|√|-|-| 375|OH_MD_KEY_VIDEO_SAR|视频流样本长宽比的键|√|-|-| 376|OH_MD_KEY_PROFILE|视频流编码档次,只针对 h265 码流使用|√|-|-| 377|OH_MD_KEY_RANGE_FLAG|视频流视频YUV值域标志的键,只针对 h265 码流使用|√|-|-| 378|OH_MD_KEY_COLOR_PRIMARIES|视频流视频色域的键,只针对 h265 码流使用|√|-|-| 379|OH_MD_KEY_TRANSFER_CHARACTERISTICS|视频流视频传递函数的键,只针对 h265 码流使用|√|-|-| 380|OH_MD_KEY_MATRIX_COEFFICIENTS|视频矩阵系数的键,只针对 h265 码流使用|√|-|-| 381|OH_MD_KEY_VIDEO_IS_HDR_VIVID|视频流标记是否为 HDRVivid 的键,只针对 HDRVivid 码流使用|√|-|-| 382|OH_MD_KEY_AUD_SAMPLE_RATE|音频流采样率的键|-|√|-| 383|OH_MD_KEY_AUD_CHANNEL_COUNT|音频流通道数的键|-|√|-| 384|OH_MD_KEY_CHANNEL_LAYOUT|音频流所需编码通道布局的键|-|√|-| 385|OH_MD_KEY_AUDIO_SAMPLE_FORMAT|音频流样本格式的键|-|√|-| 386|OH_MD_KEY_AAC_IS_ADTS|aac格式的键,只针对 aac 码流使用|-|√|-| 387|OH_MD_KEY_BITS_PER_CODED_SAMPLE|音频流每个编码样本位数的键|-|√|-|