• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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、MediaKeySession](../drm/drm-c-dev-guide.md),获取DRM许可证等。并根据需要设置音频解密配置(详见[音频解码开发指南开发步骤](audio-decoding.md#开发步骤)第4步)、设置视频解密配置(详见[视频解码开发指南开发步骤Surface模式](video-decoding.md#surface模式)第5步或[Buffer模式](video-decoding.md#buffer模式)第4步),实现DRM内容解密。
178
1795. 获取文件信息。
180
181   ```c++
182   // 获取文件用户自定义属性(可选,若用户无需获取自定义属性,可跳过此步)。
183   // 从文件 source 获取用户自定义属性信息。
184   OH_AVFormat *customMetadataFormat = OH_AVSource_GetCustomMetadataFormat(source);
185   if (customMetadataFormat == nullptr) {
186      printf("get custom metadata format failed");
187      return;
188   }
189   // 注意事项:
190   // 1. customKey需与封装时写入的key完全一致(含完整命名层级),
191   //    示例key仅为演示,实际应替换为用户自定义的字符串。
192   //    例:封装时写入key为"com.openharmony.custom.meta.abc.efg",
193   //       获取时必须使用完整key,截断使用"com.openharmony.custom.meta.abc"会失败。
194   // 2. value类型需与封装时数据类型匹配(示例为string类型,int/float类型需调用对应接口)。
195   const char *customKey = "com.openharmony.custom.meta.string"; // 替换为实际封装时使用的完整key。
196   const char *customValue;
197   if (!OH_AVFormat_GetStringValue(customMetadataFormat, customKey, &customValue)) {
198      printf("get custom metadata from custom metadata format failed");
199      return;
200   }
201   OH_AVFormat_Destroy(customMetadataFormat);
202
203   // 获取文件轨道数(可选,若用户已知轨道信息,可跳过此步)。
204   // 从文件 source 信息获取文件轨道数,用户可通过该接口获取文件级别属性,具体支持信息参考附表 1。
205   OH_AVFormat *sourceFormat = OH_AVSource_GetSourceFormat(source);
206   if (sourceFormat == nullptr) {
207      printf("get source format failed");
208      return;
209   }
210   int32_t trackCount = 0;
211   if (!OH_AVFormat_GetIntValue(sourceFormat, OH_MD_KEY_TRACK_COUNT, &trackCount)) {
212      printf("get track count from source format failed");
213      return;
214   }
215   OH_AVFormat_Destroy(sourceFormat);
216   ```
217
2186. 获取轨道index及信息(可选,若用户已知轨道信息,可跳过此步)。
219
220   ```c++
221   uint32_t audioTrackIndex = 0;
222   uint32_t videoTrackIndex = 0;
223   int32_t w = 0;
224   int32_t h = 0;
225   int32_t trackType;
226   for (uint32_t index = 0; index < (static_cast<uint32_t>(trackCount)); index++) {
227      // 获取轨道信息,用户可通过该接口获取对应轨道级别属性,具体支持信息参考附表 2。
228      OH_AVFormat *trackFormat = OH_AVSource_GetTrackFormat(source, index);
229      if (trackFormat == nullptr) {
230         printf("get track format failed");
231         return;
232      }
233      if (!OH_AVFormat_GetIntValue(trackFormat, OH_MD_KEY_TRACK_TYPE, &trackType)) {
234         printf("get track type from track format failed");
235         return;
236      }
237      static_cast<OH_MediaType>(trackType) == OH_MediaType::MEDIA_TYPE_AUD ? audioTrackIndex = index : videoTrackIndex = index;
238      // 获取视频轨宽高。
239      if (trackType == OH_MediaType::MEDIA_TYPE_VID) {
240         if (!OH_AVFormat_GetIntValue(trackFormat, OH_MD_KEY_WIDTH, &w)) {
241            printf("get track width from track format failed");
242            return;
243         }
244         if (!OH_AVFormat_GetIntValue(trackFormat, OH_MD_KEY_HEIGHT, &h)) {
245            printf("get track height from track format failed");
246            return;
247         }
248      }
249      OH_AVFormat_Destroy(trackFormat);
250   }
251   ```
252
2537. 添加解封装轨道。
254
255   ```c++
256   if(OH_AVDemuxer_SelectTrackByID(demuxer, audioTrackIndex) != AV_ERR_OK){
257      printf("select audio track failed: %d", audioTrackIndex);
258      return;
259   }
260   if(OH_AVDemuxer_SelectTrackByID(demuxer, videoTrackIndex) != AV_ERR_OK){
261      printf("select video track failed: %d", videoTrackIndex);
262      return;
263   }
264   // 取消选择轨道(可选)。
265   // OH_AVDemuxer_UnselectTrackByID(demuxer, audioTrackIndex);
266   ```
267
2688. 调整轨道到指定时间点(可选)。
269
270   ```c++
271   // 调整轨道到指定时间点,后续从该时间点进行解封装。
272   // 注意:
273   // 1. mpegts、mpg 格式文件使用OH_AVDemuxer_SeekToTime功能时,跳转到的位置可能为非关键帧。可在跳转后调用OH_AVDemuxer_ReadSampleBuffer,通过获取到的OH_AVCodecBufferAttr判断当前帧是否为关键帧。若非关键帧影响应用侧显示等功能,可在跳转后循环读取,获取到后续第一帧关键帧后,再进行解码等处理。
274   // 2. ogg格式文件使用OH_AVDemuxer_SeekToTime功能时,会跳转到传入时间millisecond所在时间间隔(秒)的起始处,可能会导致一定数量的帧误差。
275   // 3. demuxer的seek处理只针对解码行为一致的码流进行处理,如果seek后需要解码器重新配置参数,或者需要重新送入参数集的数据才可以正确解码的码流,seek后可能会出现花屏、解码卡死等问题。
276   OH_AVDemuxer_SeekToTime(demuxer, 0, OH_AVSeekMode::SEEK_MODE_CLOSEST_SYNC);
277   ```
278
2799. 开始解封装,循环获取sample(以含音频、视频两轨的文件为例)。
280
281   BufferAttr包含的属性:
282   - size:sample尺寸;
283   - offset:数据在AVBuffer中的偏移,一般为0;
284   - pts:文件封装的显示时间戳;
285   - flags:sample属性。
286
287   | flag | 描述 |
288   | -------- | -------- |
289   | AVCODEC_BUFFER_FLAGS_NONE | 默认。 |
290   | AVCODEC_BUFFER_FLAGS_EOS | 结尾sample,数据为空。 |
291   | AVCODEC_BUFFER_FLAGS_SYNC_FRAME | IDR帧或I帧。 |
292   | AVCODEC_BUFFER_FLAGS_INCOMPLETE_FRAME | 非完整的sample,一般由于buffer过小,无法拷贝完整的sample。 |
293   | AVCODEC_BUFFER_FLAGS_CODEC_DATA | 含参数集信息的帧。 |
294   | AVCODEC_BUFFER_FLAGS_DISCARD  | 可丢弃的帧。 |
295
296   OH_AVDemuxer_ReadSampleBuffer接口本身可能存在耗时久,取决于文件IO,建议以异步方式进行调用。
297   ```c++
298   // 为每个线程定义处理函数。
299   void ReadTrackSamples(OH_AVFormatDemuxer *demuxer, int trackIndex, int buffer_size,
300                         std::atomic<bool>& isEnd, std::atomic<bool>& threadFinished)
301   {
302      // 创建缓冲区。
303      OH_AVBuffer *buffer = OH_AVBuffer_Create(buffer_size);
304      if (buffer == nullptr) {
305         printf("Create buffer failed for track %d\n", trackIndex);
306         threadFinished.store(true);
307         return;
308      }
309      OH_AVCodecBufferAttr info;
310      int32_t ret;
311
312      while (!isEnd.load()) {
313         ret = OH_AVDemuxer_ReadSampleBuffer(demuxer, trackIndex, buffer);
314         if (ret == AV_ERR_OK) {
315               OH_AVBuffer_GetBufferAttr(buffer, &info);
316               printf("Track %d sample size: %d\n", trackIndex, info.size);
317               // 检查EOS标志。
318               if (info.flags == OH_AVCodecBufferFlags::AVCODEC_BUFFER_FLAGS_EOS) {
319                  isEnd.store(true);
320               }
321               // 处理缓冲区数据(这里可以根据需要实现解码逻辑)。
322         } else {
323               printf("Read sample failed for track %d\n", trackIndex);
324         }
325         // 销毁缓冲区。
326         OH_AVBuffer_Destroy(buffer);
327         buffer = nullptr;
328      }
329      threadFinished.store(true);
330   }
331
332   // 根据需求计算合适的缓冲区大小。
333   int audioBufferSize = 4096;  // 典型音频缓冲区大小。
334   int videoBufferSize = w * h * 3 >> 1;  // 原始视频缓冲区大小。
335
336   // 创建原子变量用于线程通信。
337   std::atomic<bool> audioIsEnd{false}, videoIsEnd{false}; // 表示流是否结束。
338   std::atomic<bool> audioThreadFinished{false}, videoThreadFinished{false}; // 表示线程是否暂停。
339
340   // 创建线程。
341   std::thread audioThread(ReadTrackSamples, demuxer, audioTrackIndex, audioBufferSize,
342                           std::ref(audioIsEnd), std::ref(audioThreadFinished));
343   std::thread videoThread(ReadTrackSamples, demuxer, videoTrackIndex, videoBufferSize,
344                           std::ref(videoIsEnd), std::ref(videoThreadFinished));
345   audioThread.join();
346   videoThread.join();
347   ```
348
34910. 销毁解封装实例。
350      ```c++
351      // 需要用户调用 OH_AVSource_Destroy 接口成功后,手动将实例置为nullptr,对同一实例重复调用 OH_AVSource_Destroy 会导致程序错误。
352      if (OH_AVSource_Destroy(source) != AV_ERR_OK) {
353         printf("destroy source pointer error");
354      }
355      source = nullptr;
356      // 需要用户调用 OH_AVDemuxer_Destroy 接口成功后,手动将实例置为nullptr,对同一实例重复调用 OH_AVDemuxer_Destroy 会导致程序错误。
357      if (OH_AVDemuxer_Destroy(demuxer) != AV_ERR_OK) {
358         printf("destroy demuxer pointer error");
359      }
360      demuxer = nullptr;
361      close(fd);
362      ```
363
364## 附表
365### 文件级别属性支持范围
366
367> **说明:**
368> 正常解析时才可以获取对应属性数据,如果文件信息错误或缺失,将导致解析异常,无法获取数据。
369> 当前GBK格式字符集数据会转换为UTF8提供,其他类型字符集如果需要转换为UTF8格式使用,需要调用方自行转换,参考[icu4c](../../reference/native-lib/icu4c.md)。
370>
371> 数据类型及详细取值范围参考[媒体数据键值对](../../reference/apis-avcodec-kit/_codec_base.md#媒体数据键值对)。
372
373**表1** 文件级别属性支持范围
374| 名称 | 描述 |
375| -- | -- |
376|OH_MD_KEY_TITLE|文件标题的键|
377|OH_MD_KEY_ARTIST|文件艺术家的键|
378|OH_MD_KEY_ALBUM|文件专辑的键|
379|OH_MD_KEY_ALBUM_ARTIST|文件专辑艺术家的键|
380|OH_MD_KEY_DATE|文件日期的键|
381|OH_MD_KEY_COMMENT|文件注释的键|
382|OH_MD_KEY_GENRE|文件流派的键|
383|OH_MD_KEY_COPYRIGHT|文件版权的键|
384|OH_MD_KEY_LANGUAGE|文件语言的键|
385|OH_MD_KEY_DESCRIPTION|文件描述的键|
386|OH_MD_KEY_LYRICS|文件歌词的键|
387|OH_MD_KEY_TRACK_COUNT|文件轨道数量的键|
388|OH_MD_KEY_DURATION|文件时长的键|
389|OH_MD_KEY_START_TIME|文件起始时间的键|
390
391### 轨道级别属性支持范围
392
393> **说明:**
394> 正常解析时才可以获取对应属性数据;如果文件信息错误或缺失,将导致解析异常,无法获取数据。
395>
396> 数据类型及详细取值范围参考[媒体数据键值对](../../reference/apis-avcodec-kit/_codec_base.md#媒体数据键值对)。
397
398**表2** 轨道级别属性支持范围
399| 名称 | 描述 | 视频轨支持 | 音频轨支持 | 字幕轨支持 |
400| -- | -- | -- | -- | -- |
401|OH_MD_KEY_CODEC_MIME|码流编解码器类型的键|√|√|√|
402|OH_MD_KEY_TRACK_TYPE|码流媒体类型的键|√|√|√|
403|OH_MD_KEY_TRACK_START_TIME|码流起始时间的键|√|√|√|
404|OH_MD_KEY_BITRATE|码流比特率的键|√|√|-|
405|OH_MD_KEY_LANGUAGE|码流语言类型的键|√|√|-|
406|OH_MD_KEY_CODEC_CONFIG|编解码器特定数据的键,视频中表示传递参数集,音频中表示传递解码器的参数配置信息|√|√|-|
407|OH_MD_KEY_WIDTH|视频流宽度的键|√|-|-|
408|OH_MD_KEY_HEIGHT|视频流高度的键|√|-|-|
409|OH_MD_KEY_FRAME_RATE|视频流帧率的键|√|-|-|
410|OH_MD_KEY_ROTATION|视频流旋转角度的键|√|-|-|
411|OH_MD_KEY_VIDEO_SAR|视频流样本长宽比的键|√|-|-|
412|OH_MD_KEY_PROFILE|视频流编码档次,只针对 h265 码流使用|√|-|-|
413|OH_MD_KEY_RANGE_FLAG|视频流视频YUV值域标志的键,只针对 h265 码流使用|√|-|-|
414|OH_MD_KEY_COLOR_PRIMARIES|视频流视频色域的键,只针对 h265 码流使用|√|-|-|
415|OH_MD_KEY_TRANSFER_CHARACTERISTICS|视频流视频传递函数的键,只针对 h265 码流使用|√|-|-|
416|OH_MD_KEY_MATRIX_COEFFICIENTS|视频矩阵系数的键,只针对 h265 码流使用|√|-|-|
417|OH_MD_KEY_VIDEO_IS_HDR_VIVID|视频流标记是否为 HDRVivid 的键,只针对 HDRVivid 码流使用|√|-|-|
418|OH_MD_KEY_AUD_SAMPLE_RATE|音频流采样率的键|-|√|-|
419|OH_MD_KEY_AUD_CHANNEL_COUNT|音频流通道数的键|-|√|-|
420|OH_MD_KEY_CHANNEL_LAYOUT|音频流所需编码通道布局的键|-|√|-|
421|OH_MD_KEY_AUDIO_SAMPLE_FORMAT|音频流样本格式的键|-|√|-|
422|OH_MD_KEY_AAC_IS_ADTS|aac格式的键,只针对 aac 码流使用|-|√|-|
423|OH_MD_KEY_BITS_PER_CODED_SAMPLE|音频流每个编码样本位数的键|-|√|-|