鸿蒙(API 12 Beta3版)【音视频解封装】 文件解析封装

开发者可以调用本模块的Native API接口,完成音视频解封装,即从比特流数据中取出音频、视频等媒体帧数据。

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

支持的解封装格式如下:

媒体格式 封装格式 码流格式
音视频 mp4 视频码流:AVC(H.264)、HEVC(H.265)音频码流:AAC、MPEG(MP3)、AudioVivid
音视频 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
音频 amr 音频码流:AMR(AMR-NB、AMR-WB)
音频 ape 音频码流:APE
外挂字幕 srt 字幕流:SRT

适用场景

  • 播放

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

  • 音视频编辑

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

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

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

开发指导

说明

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

在 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)

开发步骤

  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. 创建资源管理实例对象。

    // 创建文件操作符 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;
}
  1. 创建解封装器实例对象。

    // 为资源对象创建对应的解封装器
    OH_AVDemuxer *demuxer = OH_AVDemuxer_CreateWithSource(source);
    if (demuxer == nullptr) {
    printf("create demuxer failed");
    return;
    }

  2. 注册[DRM信息监听函数](可选,若非DRM码流或已获得[DRM信息],可跳过此步)。

添加头文件

复制代码
#include <multimedia/drm_framework/native_drm_common.h>

在 CMake 脚本中链接动态库

复制代码
target_link_libraries(sample PUBLIC libnative_drm.so)

设置DRM信息监听的接口有两种,可根据需要选择。

使用示例一:

复制代码
// DRM信息监听回调OnDrmInfoChanged实现
static void OnDrmInfoChanged(DRM_MediaKeySystemInfo *drmInfo)
{
   // 解析DRM信息,包括数量、DRM类型及对应pssh
}

DRM_MediaKeySystemInfoCallback callback = &OnDrmInfoChanged;
Drm_ErrCode ret = OH_AVDemuxer_SetMediaKeySystemInfoCallback(demuxer, callback);

使用示例二:

复制代码
// 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信息后,也可主动调用获取DRM信息(uuid及对应pssh)接口。

复制代码
DRM_MediaKeySystemInfo mediaKeySystemInfo;
OH_AVDemuxer_GetMediaKeySystemInfo(demuxer, &mediaKeySystemInfo);
  1. 获取文件轨道数(可选,若用户已知轨道信息,可跳过此步)。

    // 从文件 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);

  2. 获取轨道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);
    }

  3. 添加解封装轨道。

    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);

  4. 调整轨道到指定时间点(可选)。

    // 调整轨道到指定时间点,后续从该时间点进行解封装
    // 注意:
    // 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);

  5. 开始解封装,循环获取帧数据(以含音频、视频两轨的文件为例)。

    // 创建 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 选中需要获取数据的轨道
    // 获取音频帧数据
    if(!audioIsEnd) {
    ret = OH_AVDemuxer_ReadSampleBuffer(demuxer, audioTrackIndex, buffer);
    if (ret == AV_ERR_OK) {
    // 可通过 buffer 获取并处理音频帧数据
    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 获取并处理视频帧数据
    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);

  6. 销毁解封装实例。

    // 需要用户调用 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_BITRATE 码流比特率的键 -
OH_MD_KEY_LANGUAGE 码流语言类型的键 -
OH_MD_KEY_CODEC_CONFIG 编解码器特定数据的键,视频中表示传递xps,音频中表示传递extraData -
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_ADTS aac格式的键,只针对 aac 码流使用 - -
OH_MD_KEY_BITS_PER_CODED_SAMPLE 音频流每个编码样本位数的键 - -

最后呢

很多开发朋友不知道需要学习那些鸿蒙技术?鸿蒙开发岗位需要掌握那些核心技术点?为此鸿蒙的开发学习必须要系统性的进行。

而网上有关鸿蒙的开发资料非常的少,假如你想学好鸿蒙的应用开发与系统底层开发。你可以参考这份资料,少走很多弯路,节省没必要的麻烦。由两位前阿里高级研发工程师联合打造的《鸿蒙NEXT星河版OpenHarmony开发文档 》里面内容包含了(ArkTS、ArkUI开发组件、Stage模型、多端部署、分布式应用开发、音频、视频、WebGL、OpenHarmony多媒体技术、Napi组件、OpenHarmony内核、Harmony南向开发、鸿蒙项目实战等等)鸿蒙(Harmony NEXT)技术知识点

如果你是一名Android、Java、前端等等开发人员,想要转入鸿蒙方向发展。可以直接领取这份资料辅助你的学习。下面是鸿蒙开发的学习路线图。

针对鸿蒙成长路线打造的鸿蒙学习文档。话不多说,我们直接看详细鸿蒙(OpenHarmony )手册(共计1236页)与鸿蒙(OpenHarmony )开发入门视频,帮助大家在技术的道路上更进一步。

  • 《鸿蒙 (OpenHarmony)开发学习视频》
  • 《鸿蒙生态应用开发V2.0白皮书》
  • 《鸿蒙 (OpenHarmony)开发基础到实战手册》
  • OpenHarmony北向、南向开发环境搭建
  • 《鸿蒙开发基础》
  • 《鸿蒙开发进阶》
  • 《鸿蒙开发实战》

总结

鸿蒙---作为国家主力推送的国产操作系统。部分的高校已经取消了安卓课程,从而开设鸿蒙课程;企业纷纷跟进启动了鸿蒙研发。

并且鸿蒙是完全具备无与伦比的机遇和潜力的;预计到年底将有 5,000 款的应用完成原生鸿蒙开发,未来将会支持 50 万款的应用。那么这么多的应用需要开发,也就意味着需要有更多的鸿蒙人才。鸿蒙开发工程师也将会迎来爆发式的增长,学习鸿蒙势在必行! 自↓↓↓拿

相关推荐
泡泡大魔王1 小时前
鸿蒙ArkTS开发:微信/系统来电通话监听功能实现
华为·harmonyos
黑臂麒麟1 小时前
harmonyOS基础- 快速弄懂HarmonyOS ArkTs基础组件、布局容器(前端视角篇)
harmonyos·arkui
The旺2 小时前
《HarmonyOS Next开发进阶:打造功能完备的Todo应用华章》
harmonyos
yunteng5216 小时前
音视频(二)ffmpeg编译及推流
ffmpeg·音视频·h264·媒体推流
用户545748341776 小时前
Harmonyos5应用开发实战——地图组件集成与定位功能实现(part1)
harmonyos
用户545748341776 小时前
Harmonyos5应用开发实战——订单页面开发(part2)
harmonyos
用户545748341776 小时前
Harmonyos5应用开发实战——地图组件集成与定位功能实现(part2)
harmonyos
用户545748341776 小时前
HarmonyOS Next应用开发实战——登录页面实现(part1)
harmonyos
用户545748341776 小时前
HarmonyOS Next应用开发实战——底部弹框组件的实现(part1)
harmonyos
用户545748341776 小时前
HarmonyOS Next应用开发实战——底部弹框组件的实现(part2)
harmonyos