开发者可以调用本模块的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)
开发步骤
-
添加头文件。
#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> -
创建资源管理实例对象。
// 创建文件操作符 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;
}
-
创建解封装器实例对象。
// 为资源对象创建对应的解封装器
OH_AVDemuxer *demuxer = OH_AVDemuxer_CreateWithSource(source);
if (demuxer == nullptr) {
printf("create demuxer failed");
return;
} -
注册[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);
-
获取文件轨道数(可选,若用户已知轨道信息,可跳过此步)。
// 从文件 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); -
获取轨道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);
} -
添加解封装轨道。
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); -
调整轨道到指定时间点(可选)。
// 调整轨道到指定时间点,后续从该时间点进行解封装
// 注意:
// 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); -
开始解封装,循环获取帧数据(以含音频、视频两轨的文件为例)。
// 创建 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); -
销毁解封装实例。
// 需要用户调用 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 万款的应用。那么这么多的应用需要开发,也就意味着需要有更多的鸿蒙人才。鸿蒙开发工程师也将会迎来爆发式的增长,学习鸿蒙势在必行! 自↓↓↓拿