鸿蒙5.0版开发:媒体数据解析

 往期鸿蒙全套实战文章必看:


媒体数据解析

调用者可以调用本模块的Native API接口,完成媒体数据的解封装相关操作,即从比特流数据中取出音频、视频、字幕等媒体sample,获得DRM相关信息

当前支持的数据输入类型有:远程连接(http协议)和文件描述符(fd)。

支持的解封装格式如下:

媒体格式封装格式码流格式
音视频mp4

视频码流:AVC(H.264)、HEVC(H.265)

音频码流:AAC、MPEG(MP3)、AudioVivid

字幕流:WEBVTT

音视频fmp4

视频码流:AVC(H.264)、HEVC(H.265)

音频码流:AAC、MPEG(MP3)、AudioVivid

音视频mkv

视频码流:AVC(H.264)、HEVC(H.265)

音频码流:AAC、MPEG(MP3)、OPUS

音视频mpeg-ts

视频码流:AVC(H.264)、HEVC(H.265)

音频码流:AAC、MPEG(MP3)、Audio Vivid

音视频flv

视频码流:AVC(H.264)、HEVC(H.265)

音频码流:AAC

音频m4a音频码流:AAC、AudioVivid
音频aac音频码流:AAC
音频mp3音频码流:MPEG(MP3)
音频ogg音频码流:OGG
音频flac音频码流:FLAC
音频wav音频码流:PCM、PCM-MULAW
音频amr音频码流:AMR(AMR-NB、AMR-WB)
音频ape音频码流:APE
外挂字幕srt字幕流:SRT
外挂字幕webvtt字幕流:WEBVTT

DRM解密能力支持的解封装格式:mp4(H.264,H.265,AAC)、mpeg-ts(H.264,H.265,AAC)。

适用场景

  • 播放

    播放媒体文件时,需要先对媒体流进行解封装,然后使用解封装获取的sample进行解码和播放。

  • 音视频编辑

    编辑媒体文件时,需要先对媒体流进行解封装,获取到指定sample进行编辑。

  • 媒体文件格式转换(转封装)

    媒体文件格式转换时,需要先对媒体流进行解封装,然后按需将媒体流封装至新的格式文件内。

开发指导

 

说明

  • 调用解封装能力解析网络播放路径,需要声明权限:ohos.permission.INTERNET
  • 调用解封装能力解析本地文件,需要向用户申请授权:ohos.permission.READ_MEDIA
  • 如果使用ResourceManager.getRawFd打开HAP资源文件描述符。

在 CMake 脚本中链接动态库

target_link_libraries(sample PUBLIC libnative_media_codecbase.so)
target_link_libraries(sample PUBLIC libnative_media_avdemuxer.so)
target_link_libraries(sample PUBLIC libnative_media_avsource.so)
target_link_libraries(sample PUBLIC libnative_media_core.so)

说明

上述'sample'字样仅为示例,此处由调用者根据实际工程目录自定义。

开发步骤

  1. 添加头文件。

    #include <multimedia/player_framework/native_avdemuxer.h>
    #include <multimedia/player_framework/native_avsource.h>
    #include <multimedia/player_framework/native_avcodec_base.h>
    #include <multimedia/player_framework/native_avformat.h>
    #include <multimedia/player_framework/native_avbuffer.h>
    #include <fcntl.h>
    #include <sys/stat.h>
  2. 创建资源管理实例对象。

    调用者HAP中使用open获取fd时,filepath需要转换为沙箱路径,才能获取沙盒资源。

    // 创建文件操作符 fd,打开时对文件句柄必须有读权限(filePath 为待解封装文件路径,需预置文件,保证路径指向的文件存在)
    std::string filePath = "test.mp4";
    int fd = open(filePath.c_str(), O_RDONLY);
    struct stat fileStatus {};
    size_t fileSize = 0;
    if (stat(filePath.c_str(), &fileStatus) == 0) {
       fileSize = static_cast<size_t>(fileStatus.st_size);
    } else {
       printf("get stat failed");
       return;
    }
    // 为 fd 资源文件创建 source 资源对象, 传入 offset 不为文件起始位置 或 size 不为文件大小时,可能会因不能获取完整数据导致 source 创建失败、或后续解封装失败等问题
    OH_AVSource *source = OH_AVSource_CreateWithFD(fd, 0, fileSize);
    if (source == nullptr) {
       printf("create source failed");
       return;
    }
    // 为 uri 资源文件创建 source 资源对象(可选)
    // OH_AVSource *source = OH_AVSource_CreateWithURI(uri);
    
    // 为自定义数据源创建 source 资源对象(可选)。使用该方式前,需要先实现AVSourceReadAt接口函数实现。
    // 当使用OH_AVSource_CreateWithDataSource时需要补充g_filePath
    // g_filePath = filePath ;
    // OH_AVDataSource dataSource = {fileSize, AVSourceReadAt};
    // OH_AVSource *source = OH_AVSource_CreateWithDataSource(&dataSource);

    AVSourceReadAt接口函数,需要放在创建资源管理实例对象前实现:

    // 添加头文件
    #include <fstream>
    static std::string g_filePath;
    
    enum MediaDataSourceError : int32_t {
       SOURCE_ERROR_IO = -2,
       SOURCE_ERROR_EOF = -1
    };
    
    int32_t AVSourceReadAt(OH_AVBuffer *data, int32_t length, int64_t pos)
    {
       if (data == nullptr) {
          printf("AVSourceReadAt : data is nullptr!\n");
          return MediaDataSourceError::SOURCE_ERROR_IO;
       }
    
       std::ifstream infile(g_filePath, std::ofstream::binary);
       if (!infile.is_open()) {
          printf("AVSourceReadAt : open file failed! file:%s\n", g_filePath.c_str());
          return MediaDataSourceError::SOURCE_ERROR_IO;  // 打开文件失败
       }
    
       infile.seekg(0, std::ios::end);
       int64_t fileSize = infile.tellg();
       if (pos >= fileSize) {
          printf("AVSourceReadAt : pos over or equals file size!\n");
          return MediaDataSourceError::SOURCE_ERROR_EOF;  // pos已经是文件末尾位置,无法读取
       }
    
       if (pos + length > fileSize) {
          length = fileSize - pos;    // pos+length长度超过文件大小时,读取从pos到文件末尾的数据
       }
    
       infile.seekg(pos, std::ios::beg);
       if (length <= 0) {
          printf("AVSourceReadAt : raed length less than zero!\n");
          return MediaDataSourceError::SOURCE_ERROR_IO;
       }
       char* buffer = new char[length];
       infile.read(buffer, length);
       infile.close();
    
       memcpy(reinterpret_cast<char *>(OH_AVBuffer_GetAddr(data)),
          buffer, length);
       delete[] buffer;
    
       return length;
    }
  3. 创建解封装器实例对象。

    // 为资源对象创建对应的解封装器
    OH_AVDemuxer *demuxer = OH_AVDemuxer_CreateWithSource(source);
    if (demuxer == nullptr) {
       printf("create demuxer failed");
       return;
    }
  4. 注册DRM信息监听函数(可选,若非DRM码流或已获得DRM信息,可跳过此步)。

    添加头文件

    #include <multimedia/drm_framework/native_drm_common.h>

    在 CMake 脚本中链接动态库

    target_link_libraries(sample PUBLIC libnative_drm.so)

    设置DRM信息监听的接口有两种,示例一所示的回调函数支持返回解封装器实例,适用于多个解封装器场景,推荐使用。示例二所示的回调函数不支持返回解封装器实例,适用于单个解封装器实例场景。

    使用示例一:

    // DRM信息监听回调OnDrmInfoChangedWithObj实现
    static void OnDrmInfoChangedWithObj(OH_AVDemuxer *demuxer, DRM_MediaKeySystemInfo *drmInfo)
    {
       // 解析DRM信息,包括数量、DRM类型及对应pssh
    }
    
    Demuxer_MediaKeySystemInfoCallback callback = &OnDrmInfoChangedWithObj;
    Drm_ErrCode ret = OH_AVDemuxer_SetDemuxerMediaKeySystemInfoCallback(demuxer, callback);

    使用示例二:

    // DRM信息监听回调OnDrmInfoChanged实现
    static void OnDrmInfoChanged(DRM_MediaKeySystemInfo *drmInfo)
    {
       // 解析DRM信息,包括数量、DRM类型及对应pssh
    }
    
    DRM_MediaKeySystemInfoCallback callback = &OnDrmInfoChanged;
    Drm_ErrCode ret = OH_AVDemuxer_SetMediaKeySystemInfoCallback(demuxer, callback);

    在监听到DRM信息后,也可主动调用获取DRM信息(uuid及对应pssh)接口。

    DRM_MediaKeySystemInfo mediaKeySystemInfo;
    OH_AVDemuxer_GetMediaKeySystemInfo(demuxer, &mediaKeySystemInfo);

    在获取、解析DRM信息后,需创建对应DRM解决方案的MediaKeySystemMediaKeySession,获取DRM许可证等。并根据需要设置音频解密配置、设置视频解密配置,实现DRM内容解密。

  5. 获取文件轨道数(可选,若用户已知轨道信息,可跳过此步)。

    // 从文件 source 信息获取文件轨道数,用户可通过该接口获取文件级别属性,具体支持信息参考附表 1
    OH_AVFormat *sourceFormat = OH_AVSource_GetSourceFormat(source);
    if (sourceFormat == nullptr) {
       printf("get source format failed");
       return;
    }
    int32_t trackCount = 0;
    if (!OH_AVFormat_GetIntValue(sourceFormat, OH_MD_KEY_TRACK_COUNT, &trackCount)) {
       printf("get track count from source format failed");
       return;
    }
    OH_AVFormat_Destroy(sourceFormat);
  6. 获取轨道index及信息(可选,若用户已知轨道信息,可跳过此步)。

    uint32_t audioTrackIndex = 0;
    uint32_t videoTrackIndex = 0;
    int32_t w = 0;
    int32_t h = 0;
    int32_t trackType;
    for (uint32_t index = 0; index < (static_cast<uint32_t>(trackCount)); index++) {
       // 获取轨道信息,用户可通过该接口获取对应轨道级别属性,具体支持信息参考附表 2
       OH_AVFormat *trackFormat = OH_AVSource_GetTrackFormat(source, index);
       if (trackFormat == nullptr) {
          printf("get track format failed");
          return;
       }
       if (!OH_AVFormat_GetIntValue(trackFormat, OH_MD_KEY_TRACK_TYPE, &trackType)) {
          printf("get track type from track format failed");
          return;
       }
       static_cast<OH_MediaType>(trackType) == OH_MediaType::MEDIA_TYPE_AUD ? audioTrackIndex = index : videoTrackIndex = index;
       // 获取视频轨宽高
       if (trackType == OH_MediaType::MEDIA_TYPE_VID) {
          if (!OH_AVFormat_GetIntValue(trackFormat, OH_MD_KEY_WIDTH, &w)) {
             printf("get track width from track format failed");
             return;
          }
          if (!OH_AVFormat_GetIntValue(trackFormat, OH_MD_KEY_HEIGHT, &h)) {
             printf("get track height from track format failed");
             return;
          }
       }
       OH_AVFormat_Destroy(trackFormat);
    }
  7. 添加解封装轨道。

    if(OH_AVDemuxer_SelectTrackByID(demuxer, audioTrackIndex) != AV_ERR_OK){
       printf("select audio track failed: %d", audioTrackIndex);
       return;
    }
    if(OH_AVDemuxer_SelectTrackByID(demuxer, videoTrackIndex) != AV_ERR_OK){
       printf("select video track failed: %d", videoTrackIndex);
       return;
    }
    // 取消选择轨道(可选)
    // OH_AVDemuxer_UnselectTrackByID(demuxer, audioTrackIndex);
  8. 调整轨道到指定时间点(可选)。

    // 调整轨道到指定时间点,后续从该时间点进行解封装
    // 注意:
    // 1. mpegts格式文件使用OH_AVDemuxer_SeekToTime功能时,跳转到的位置可能为非关键帧。可在跳转后调用OH_AVDemuxer_ReadSampleBuffer,通过获取到的OH_AVCodecBufferAttr判断当前帧是否为关键帧。若非关键帧影响应用侧显示等功能,可在跳转后循环读取,获取到后续第一帧关键帧后,再进行解码等处理。
    // 2. ogg格式文件使用OH_AVDemuxer_SeekToTime功能时,会跳转到传入时间millisecond所在时间间隔(秒)的起始处,可能会导致一定数量的帧误差。
    OH_AVDemuxer_SeekToTime(demuxer, 0, OH_AVSeekMode::SEEK_MODE_CLOSEST_SYNC);
  9. 开始解封装,循环获取sample(以含音频、视频两轨的文件为例)。

    BufferAttr包含的属性:

    • size:sample尺寸;
    • offset:数据在AVBuffer中的偏移,一般为0;
    • pts:文件封装的显示时间戳;
    • flags:sample属性。
    flag描述
    AVCODEC_BUFFER_FLAGS_NONE默认。
    AVCODEC_BUFFER_FLAGS_EOS结尾sample,数据为空。
    AVCODEC_BUFFER_FLAGS_SYNC_FRAMEIDR帧或I帧。
    AVCODEC_BUFFER_FLAGS_INCOMPLETE_FRAME非完整的sample,一般由于buffer过小,无法拷贝完整的sample。
    AVCODEC_BUFFER_FLAGS_CODEC_DATA含参数集信息的帧。
    AVCODEC_BUFFER_FLAGS_DISCARD可丢弃的帧。
    // 创建 buffer,用与保存用户解封装得到的数据
    OH_AVBuffer *buffer = OH_AVBuffer_Create(w * h * 3 >> 1);
    if (buffer == nullptr) {
       printf("build buffer failed");
       return;
    }
    OH_AVCodecBufferAttr info;
    bool videoIsEnd = false;
    bool audioIsEnd = false;
    int32_t ret;
    while (!audioIsEnd || !videoIsEnd) {
       // 在调用 OH_AVDemuxer_ReadSampleBuffer 接口获取数据前,需要先调用 OH_AVDemuxer_SelectTrackByID 选中需要获取数据的轨道
       // 获取音频sample
       if(!audioIsEnd) {
          ret = OH_AVDemuxer_ReadSampleBuffer(demuxer, audioTrackIndex, buffer);
          if (ret == AV_ERR_OK) {
             // 可通过 buffer 获取并处理音频sample
             OH_AVBuffer_GetBufferAttr(buffer, &info);
             printf("audio info.size: %d\n", info.size);
             if (info.flags == OH_AVCodecBufferFlags::AVCODEC_BUFFER_FLAGS_EOS) {
                audioIsEnd = true;
             }
          }
       }
       if(!videoIsEnd) {
          ret = OH_AVDemuxer_ReadSampleBuffer(demuxer, videoTrackIndex, buffer);
          if (ret == AV_ERR_OK) {
             // 可通过 buffer 获取并处理视频sample
             OH_AVBuffer_GetBufferAttr(buffer, &info);
             printf("video info.size: %d\n", info.size);
             if (info.flags == OH_AVCodecBufferFlags::AVCODEC_BUFFER_FLAGS_EOS) {
                videoIsEnd = true;
             }
          }
       }
    }
    OH_AVBuffer_Destroy(buffer);
  10. 销毁解封装实例。

    // 需要用户调用 OH_AVSource_Destroy 接口成功后,手动将对象置为 NULL,对同一对象重复调用 OH_AVSource_Destroy 会导致程序错误
    if (OH_AVSource_Destroy(source) != AV_ERR_OK) {
       printf("destroy source pointer error");
    }
    source = NULL;
    // 需要用户调用 OH_AVDemuxer_Destroy 接口成功后,手动将对象置为 NULL,对同一对象重复调用 OH_AVDemuxer_Destroy 会导致程序错误
    if (OH_AVDemuxer_Destroy(demuxer) != AV_ERR_OK) {
       printf("destroy demuxer pointer error");
    }
    demuxer = NULL;
    close(fd);

附表

文件级别属性支持范围

说明

正常解析时才可以获取对应属性数据;如果文件信息错误或缺失,将导致解析异常,无法获取数据。

表1 文件级别属性支持范围

名称描述
OH_MD_KEY_TITLE文件标题的键
OH_MD_KEY_ARTIST文件艺术家的键
OH_MD_KEY_ALBUM文件专辑的键
OH_MD_KEY_ALBUM_ARTIST文件专辑艺术家的键
OH_MD_KEY_DATE文件日期的键
OH_MD_KEY_COMMENT文件注释的键
OH_MD_KEY_GENRE文件流派的键
OH_MD_KEY_COPYRIGHT文件版权的键
OH_MD_KEY_LANGUAGE文件语言的键
OH_MD_KEY_DESCRIPTION文件描述的键
OH_MD_KEY_LYRICS文件歌词的键
OH_MD_KEY_TRACK_COUNT文件轨道数量的键
OH_MD_KEY_DURATION文件时长的键
OH_MD_KEY_START_TIME文件起始时间的键

轨道级别属性支持范围

说明

正常解析时才可以获取对应属性数据;如果文件信息错误或缺失,将导致解析异常,无法获取数据。

表2 轨道级别属性支持范围

名称描述视频轨支持音频轨支持字幕轨支持
OH_MD_KEY_CODEC_MIME码流编解码器类型的键
OH_MD_KEY_TRACK_TYPE码流媒体类型的键
OH_MD_KEY_TRACK_START_TIME码流起始时间的键
OH_MD_KEY_BITRATE码流比特率的键-
OH_MD_KEY_LANGUAGE码流语言类型的键-
OH_MD_KEY_CODEC_CONFIG编解码器特定数据的键,视频中表示传递参数集,音频中表示传递解码器的参数配置信息-
OH_MD_KEY_WIDTH视频流宽度的键--
OH_MD_KEY_HEIGHT视频流高度的键--
OH_MD_KEY_FRAME_RATE视频流帧率的键--
OH_MD_KEY_ROTATION视频流旋转角度的键--
OH_MD_KEY_VIDEO_SAR视频流样本长宽比的键--
OH_MD_KEY_PROFILE视频流编码档次,只针对 h265 码流使用--
OH_MD_KEY_RANGE_FLAG视频流视频YUV值域标志的键,只针对 h265 码流使用--
OH_MD_KEY_COLOR_PRIMARIES视频流视频色域的键,只针对 h265 码流使用--
OH_MD_KEY_TRANSFER_CHARACTERISTICS视频流视频传递函数的键,只针对 h265 码流使用--
OH_MD_KEY_MATRIX_COEFFICIENTS视频矩阵系数的键,只针对 h265 码流使用--
OH_MD_KEY_VIDEO_IS_HDR_VIVID视频流标记是否为 HDRVivid 的键,只针对 HDRVivid 码流使用--
OH_MD_KEY_AUD_SAMPLE_RATE音频流采样率的键--
OH_MD_KEY_AUD_CHANNEL_COUNT音频流通道数的键--
OH_MD_KEY_CHANNEL_LAYOUT音频流所需编码通道布局的键--
OH_MD_KEY_AUDIO_SAMPLE_FORMAT音频流样本格式的键--
OH_MD_KEY_AAC_IS_ADTSaac格式的键,只针对 aac 码流使用--
OH_MD_KEY_BITS_PER_CODED_SAMPLE音频流每个编码样本位数的键--

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值