目录
1 概述
AVCodec Kit(Audio & Video Codec Kit,音视频编解码,封装解析)是媒体系统中的音视频的编解码、媒体文件的解析、封装、媒体数据输入等原子能力。
基于性能考虑,AVCodec Kit仅提供C接口。
1.1 能力范围
- 媒体数据输入:媒体应用可以传入文件fd、或者流媒体url,进行后续的媒体信息解析等处理。
- 媒体基础能力(Media Foundation):提供媒体数据处理的公共基础类型,包括AVBuffer、AVFormat等。
- 音频编码:音频类应用(比如音频通话、音频录制等)可以将未压缩的音频数据送到音频编码器进行编码,应用可以设置编码要用到的编码格式、码率、采样率等参数,控制编码的输出,达到压缩音频文件的目的。
- 视频编码:视频类应用(比如视频通话、视频录制等)可以将未压缩的视频数据送到视频编码器进行编码,应用可以设置编码要用到的编码格式、码率、帧率等参数,控制编码的输出,达到压缩视频文件的目的。
- 音频解码:音频类应用(比如音频通话、音频播放器等)将音频码流通过音频解码器解码,解码后的数据可以送到音频设备播放。
- 视频解码:视频类应用(比如视频通话、视频播放器等)将视频码流通过视频解码器解码,解码后的图像数据可以送到视频显示设备显示。
- 媒体文件解析:在媒体应用(音视频播放器等),将本地或者网络接收到的媒体文件解析,获得音视频的码流、音视频的呈现时间、编码格式、文件的一些基本属性信息等。
- 媒体文件封装:在媒体应用(音视频录制等),将音视频编码器编码后的码流数据封装成媒体文件(mp4、m4a),将音视频的码流、音视频的呈现时间、编码格式、文件的一些基本属性信息等按照文件格式写入应用指定的文件中。
1.2 亮点/特征
系统内部数据零拷贝:在视频解码过程,AVCodec通过回调函数提供AVBuffer给应用,由应用将要解码的sample数据写入AVBuffer,在AVCodec中数据不再需要从内存拷入硬件解码器,而是直接送入解码器解码,实现系统内数据零拷贝。
视频编码、解码支持硬件加速:支持H.264、H.265、H.265 10bit的硬件编解码。
1.3 基础概念
媒体文件:携带有音视频、字幕等媒体数据的文件,如.mp4、.m4a。
流媒体:可以边下载,边播放的媒体传输形式,下载协议如HTTP/HTTPS、HLS协议。
音视频编码:将未压缩原序列音视频数据转换为另一种格式数据,如H.264、AAC。
音视频解码:将一种数据格式转换为未压缩状态的原序列音视频数据,如YUV、PCM。
媒体文件封装:将音频、视频、字幕等数据以及描述信息,按照某种格式要求,写入到同一个文件中,如.mp4。
媒体文件解封装:将文件中的音频、视频、字幕等媒体数据读出,解析出媒体的描述信息。
sample:有相同时间属性的一组数据。
对于音视频,通常是有相同解码时间戳的压缩数据。
对于字幕,通常包含对应时间点的字幕内容。
所有的轨道结尾数据都是为空。
1.4 方式
-
视频编解码
视频编码的输入和视频解码的输出支持Surface模式。
在编码和解码过程中,通过回调函数通知应用数据处理的情况;如编码过程通过回调通知应用,完成一帧编码,输出编码结果AVBuffer;在解码过程通过回调通知应用输入一帧码流到解码器解码,当解码完成也会通过回调通知应用解码完成,应用可以对数据做后续处理。
视频编解码的逻辑如图所示。
具体开发指导请参考视频解码Surface模式、视频编码Surface模式。
-
音频编解码
音频编码的输入和音频解码的输出为PCM格式。
在编码和解码过程中,通过回调函数通知应用数据处理的情况;如编码过程通过回调通知应用,完成一帧编码,输出编码结果AVBuffer;在解码过程通过回调通知应用输入一帧码流到解码器解码,当解码完成也会通过回调通知应用解码完成,应用可以对数据做后续处理。
音频编解码逻辑如图所示。
-
文件解析封装
在文件封装环节,应用将AVBuffer送入Codec对应的接口,执行数据封装,AVBuffer可以是由上述编码输出的AVBuffer,也可以是应用创建的AVBuffer,AVBuffer中要携带有效的码流数据和相关的时间描述等信息;
在文件解析环节,应用从Codec对应的接口获得携带有码流数据的AVBuffer,该AVBuffer可以送入上述视频和音频编解码对应接口。
文件封装解封装逻辑如图所示。
2 AVCodec支持的格式
2.1 媒体编解码
1 视频解码
当前支持的解码能力如下:
视频硬解类型 | 视频软解类型 |
---|---|
AVC(H.264)、HEVC(H.265)、VVC(H.266) | AVC(H.264)、HEVC(H.265) |
- 视频解码软/硬件解码存在差异,基于MimeType创建解码器时,软解支持H.264(OH_AVCODEC_MIMETYPE_VIDEO_AVC)、H.265(OH_AVCODEC_MIMETYPE_VIDEO_HEVC),
- 如果硬件平台支持,则可以使用H.264(OH_AVCODEC_MIMETYPE_VIDEO_AVC)、H.265(OH_AVCODEC_MIMETYPE_VIDEO_HEVC)、H.266(OH_AVCODEC_MIMETYPE_VIDEO_VVC)硬件解码能力。
每一种解码的能力范围,可以通过获取支持的编解码能力获取。
2 视频编码
当前支持的编码能力如下:
视频编码类型 |
---|
HEVC(H.265)、 AVC(H.264) |
目前仅支持硬件编码,基于MimeType创建编码器时,支持配置为H.264(OH_AVCODEC_MIMETYPE_VIDEO_AVC)和H.265(OH_AVCODEC_MIMETYPE_VIDEO_HEVC)。
每一种编码的能力范围,可以通过获取支持的编解码能力获取。
3 音频解码
当前支持的解码能力如下:
容器规格 | 音频解码类型 |
---|---|
mp4 | AAC、MPEG(MP3)、Flac、Vorbis、AudioViVid11+ |
m4a | AAC |
flac | Flac |
ogg | Vorbis、opus |
aac | AAC |
mp3 | MPEG(MP3) |
amr | AMR(amrnb、amrwb) |
raw | G711mu |
ape | APE |
4 音频编码
当前支持的编码能力如下:
容器规格 | 音频编码类型 |
---|---|
mp4 | AAC、Flac |
m4a | AAC |
flac | Flac |
aac | AAC |
mp3 | MP3 |
raw | G711mu |
amr | AMR |
ogg | opus |
2.2 媒体数据封装与解析
1 媒体数据解析
支持的解封装格式如下:
媒体格式 | 封装格式 | 码流格式 |
---|---|---|
音视频 | 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)。
2 媒体数据封装
当前支持的封装能力如下:
封装格式 | 视频编解码类型 | 音频编解码类型 | 封面类型 |
---|---|---|---|
mp4 | AVC(H.264)、HEVC(H.265) | AAC、MPEG(MP3) | jpeg、png、bmp |
m4a | - | AAC | jpeg、png、bmp |
mp3 | - | MPEG(MP3) | - |
amr | - | AMR(amrnb、amrwb) | - |
wav | - | G711mu(pcm-mulaw) | - |
说明
- 封装格式为mp4,音频编解码类型为MPEG(MP3)时采样率需大于等于16000Hz。
- 封装格式为mp4/m4a,音频编解码类型为AAC时声道数范围为1~7。
配置选项key值说明:
mp4封装格式:
key | 描述 | aac | mp3 | H.264 | H.265 | jpg | png | bmp |
---|---|---|---|---|---|---|---|---|
OH_MD_KEY_AUD_SAMPLE_RATE | 采样率 | 必须 | 必须 | - | - | - | - | - |
OH_MD_KEY_AUD_CHANNEL_COUNT | 声道数 | 必须 | 必须 | - | - | - | - | - |
OH_MD_KEY_AUDIO_SAMPLE_FORMAT | 输出音频流格式 | 可选 | 可选 | - | - | - | - | - |
OH_MD_KEY_CHANNEL_LAYOUT | 通道布局 | 可选 | 可选 | - | - | - | - | - |
OH_MD_KEY_PROFILE | 编码档次 | 可选 | - | - | - | - | - | - |
OH_MD_KEY_BITRATE | 码率 | 可选 | 可选 | 可选 | 可选 | - | - | - |
OH_MD_KEY_CODEC_CONFIG | 编解码器特定数据 | 可选 | - | 可选 | 可选 | - | - | - |
OH_MD_KEY_WIDTH | 宽度 | - | - | 必须 | 必须 | 必须 | 必须 | 必须 |
OH_MD_KEY_HEIGHT | 高度 | - | - | 必须 | 必须 | 必须 | 必须 | 必须 |
OH_MD_KEY_FRAME_RATE | 视频流帧率 | - | - | 可选 | 可选 | - | - | - |
OH_MD_KEY_COLOR_PRIMARIES | 视频色域 | - | - | 可选 | 可选 | - | - | - |
OH_MD_KEY_TRANSFER_CHARACTERISTICS | 视频传递函数 | - | - | 可选 | 可选 | - | - | - |
OH_MD_KEY_MATRIX_COEFFICIENTS | 视频矩阵系数 | - | - | 可选 | 可选 | - | - | - |
OH_MD_KEY_RANGE_FLAG | 值域标志 | - | - | 可选 | 可选 | - | - | - |
OH_MD_KEY_VIDEO_IS_HDR_VIVID | 视频轨是否为HDR VIVID | - | - | - | 可选 | - | - | - |
m4a封装格式:
key | 描述 | aac | jpg | png | bmp |
---|---|---|---|---|---|
OH_MD_KEY_AUD_SAMPLE_RATE | 采样率 | 必须 | - | - | - |
OH_MD_KEY_AUD_CHANNEL_COUNT | 声道数 | 必须 | - | - | - |
OH_MD_KEY_AUDIO_SAMPLE_FORMAT | 输出音频流格式 | 可选 | - | - | - |
OH_MD_KEY_CHANNEL_LAYOUT | 通道布局 | 可选 | - | - | - |
OH_MD_KEY_PROFILE | 编码档次 | 可选 | - | - | - |
OH_MD_KEY_BITRATE | 码率 | 可选 | - | - | - |
OH_MD_KEY_CODEC_CONFIG | 编解码器特定数据 | 可选 | - | - | - |
OH_MD_KEY_WIDTH | 宽度 | - | 必须 | 必须 | 必须 |
OH_MD_KEY_HEIGHT | 高度 | - | 必须 | 必须 | 必须 |
amr封装格式:
key | 描述 | amr_nb | amr_wb |
---|---|---|---|
OH_MD_KEY_AUD_SAMPLE_RATE | 采样率 | 必须 | 必须 |
OH_MD_KEY_AUD_CHANNEL_COUNT | 声道数 | 必须 | 必须 |
OH_MD_KEY_AUDIO_SAMPLE_FORMAT | 输出音频流格式 | 可选 | 可选 |
OH_MD_KEY_CHANNEL_LAYOUT | 通道布局 | 可选 | 可选 |
OH_MD_KEY_BITRATE | 码率 | 可选 | 可选 |
mp3封装格式:
key | 描述 | mp3 | jpg |
---|---|---|---|
OH_MD_KEY_AUD_SAMPLE_RATE | 采样率 | 必须 | - |
OH_MD_KEY_AUD_CHANNEL_COUNT | 声道数 | 必须 | - |
OH_MD_KEY_AUDIO_SAMPLE_FORMAT | 输出音频流格式 | 可选 | - |
OH_MD_KEY_CHANNEL_LAYOUT | 通道布局 | 可选 | - |
OH_MD_KEY_BITRATE | 码率 | 可选 | - |
OH_MD_KEY_WIDTH | 宽度 | - | 必须 |
OH_MD_KEY_HEIGHT | 高度 | - | 必须 |
wav封装格式:
key | 描述 | g711mu |
---|---|---|
OH_MD_KEY_AUD_SAMPLE_RATE | 采样率 | 必须 |
OH_MD_KEY_AUD_CHANNEL_COUNT | 声道数 | 必须 |
OH_MD_KEY_AUDIO_SAMPLE_FORMAT | 输出音频流格式 | 可选 |
OH_MD_KEY_CHANNEL_LAYOUT | 通道布局 | 可选 |
OH_MD_KEY_BITRATE | 码率 | 必须 |
3 音视频编解码
3.1 获取支持的编解码能力
3.1.1 通用开发指导
1. 在CMake脚本中链接动态库。
target_link_libraries(sample PUBLIC libnative_media_codecbase.so)
target_link_libraries(sample PUBLIC libnative_media_core.so)
target_link_libraries(sample PUBLIC libnative_media_venc.so)
target_link_libraries(sample PUBLIC libnative_media_vdec.so)
说明
上述'sample'字样仅为示例,此处由开发者根据实际工程目录自定义。
2. 添加头文件。
#include <multimedia/player_framework/native_avcapability.h>
#include <multimedia/player_framework/native_avcodec_base.h>
#include <multimedia/player_framework/native_avformat.h>
#include <multimedia/player_framework/native_avcodec_videoencoder.h>
#include <multimedia/player_framework/native_avcodec_videodecoder.h>
3. 获得音视频编解码能力实例。
支持两种获取音视频编解码能力实例的方式:
方式一:通过OH_AVCodec_GetCapability获取框架推荐的音视频编解码器能力实例。与OH_XXX_CreateByMime系列接口框架推荐策略一致。
// 获取系统推荐的音频AAC解码器能力实例。
OH_AVCapability *capability = OH_AVCodec_GetCapability(OH_AVCODEC_MIMETYPE_AUDIO_AAC, false);
方式二:通过OH_AVCodec_GetCapabilityByCategory获取指定软件或硬件的编解码能力实例。
// 获取指定硬件的视频AVC编码器能力实例。
OH_AVCapability *capability = OH_AVCodec_GetCapabilityByCategory(OH_AVCODEC_MIMETYPE_VIDEO_AVC, true, HARDWARE);
若获取能力实例成功,则可继续向下执行
4. 按需调用相应查询接口
3.1.2 场景化开发指导
1 创建指定名称的编解码器
如系统内存在相同MIME类型的多个编码器或多个解码器。使用OH_XXX_CreateByMime系列接口只能创建系统推荐的特定编解码器。若需创建其他编解码器,开发者可先获取编解码器名称,再通过OH_XXX_CreateByName系列接口创建指定名称的编解码器。
接口 | 功能描述 |
---|---|
OH_AVCapability_GetName | 获取能力实例对应编解码器的名称 |
H.264软件解码器和H.264硬件解码器共存时,创建H.264软件解码器示例:
// 1. 获取H.264软件解码器能力实例。
OH_AVCapability *capability = OH_AVCodec_GetCapabilityByCategory(OH_AVCODEC_MIMETYPE_VIDEO_AVC, false, SOFTWARE);
if (capability != nullptr) {
// 2. 获取H.264软件解码器名称。
const char *codecName = OH_AVCapability_GetName(capability);
// 3. 创建H.264软件解码器实例。
OH_AVCodec *videoDec = OH_VideoDecoder_CreateByName(codecName);
}
2 针对软硬件类别差异化配置编解码器参数
软件编解码器和硬件编解码器定义如下:
-
软件编解码器: 指在CPU上进行编解码工作的编解码器,能力可灵活迭代,相比硬件编解码器具有更好的兼容性,更好的协议和规格扩展能力。
-
硬件编解码器: 指在专有硬件上进行编解码工作的编解码器,其特点是已在硬件平台硬化,能力随硬件平台迭代。相比软件编解码器具有更好的功耗、耗时和吞吐表现,同时能降低CPU负载。
基于上述软件编解码器和硬件编解码器的特点,在硬件编解码器满足要求的时候,优先使用硬件编解码器,否则使用软件编解码器。开发者可基于软件还是硬件类别差异化配置编解码参数。
接口 | 功能描述 |
---|---|
OH_AVCapability_IsHardware | 确认能力实例对应的编解码器是否是硬件的 |
视频编码,软硬件差异化配置帧率示例:
// 1. 确认推荐的H.264编码器的软硬件类别。
OH_AVCapability *capability = OH_AVCodec_GetCapability(OH_AVCODEC_MIMETYPE_VIDEO_AVC, true);
bool isHardward = OH_AVCapability_IsHardware(capability);
// 2. 基于软硬件类别差异化配置。
OH_AVCodec *videoEnc = OH_VideoEncoder_CreateByMime(OH_AVCODEC_MIMETYPE_VIDEO_AVC);
OH_AVFormat *format = OH_AVFormat_CreateVideoFormat(OH_AVCODEC_MIMETYPE_VIDEO_AVC, 1920, 1080);
double frameRate = isHardward ? 60.0 : 30.0;
if (!OH_AVFormat_SetDoubleValue(format, OH_MD_KEY_FRAME_RATE, frameRate)) {
// 异常处理。
}
if (OH_VideoEncoder_Configure(videoEnc, format) != AV_ERR_OK) {
// 异常处理。
}
OH_AVFormat_Destroy(format);
3 创建多路编解码器
部分业务场景涉及创建多路编解码器,基于各类资源的限制,某一编解码器的实例数是有限的,不能无限制创建。
接口 | 功能描述 |
---|---|
OH_AVCapability_GetMaxSupportedInstances | 获取能力实例对应编解码器可同时运行的最大实例数,实际能成功创建的数目还受系统其他资源的约束 |
优先创建硬件解码器实例,不够时再创建软件解码器实例,示例如下:
constexpr int32_t NEEDED_VDEC_NUM = 8;
// 1. 创建硬件解码器实例。
OH_AVCapability *capHW = OH_AVCodec_GetCapabilityByCategory(OH_AVCODEC_MIMETYPE_VIDEO_AVC, false, HARDWARE);
int32_t vDecNumHW = min(OH_AVCapability_GetMaxSupportedInstances(capHW), NEEDED_VDEC_NUM);
int32_t createdVDecNum = 0;
for (int i = 0; i < vDecNumHW; i++) {
OH_AVCodec *videoDec = OH_VideoDecoder_CreateByName(OH_AVCapability_GetName(capHW));
if (videoDec != nullptr) {
// 维护在videoDecVector中。
createdVDecNum++;
}
}
if (createdVDecNum < NEEDED_VDEC_NUM) {
// 2. 不够时,创建软件解码器实例。
OH_AVCapability *capSW = OH_AVCodec_GetCapabilityByCategory(OH_AVCODEC_MIMETYPE_VIDEO_AVC, false, SOFTWARE);
int32_t vDecNumSW = min(OH_AVCapability_GetMaxSupportedInstances(capSW), NEEDED_VDEC_NUM - createdVDecNum);
for (int i = 0; i < vDecNumSW; i++) {
OH_AVCodec *videoDec = OH_VideoDecoder_CreateByName(OH_AVCapability_GetName(capSW));
if (videoDec != nullptr) {
// 维护在videoDecVector中。
createdVDecNum++;
}
}
}
4 控制编码质量
当前提供三种码控模式供开发者选用,分别是恒定码率(CBR)码控模式、动态码率(VBR)码控模式,以及恒定质量(CQ)码控模式。对于CBR和VBR码控模式,编码质量由码率参数决定。对于CQ码控模式,编码质量由质量参数决定。
接口 | 功能描述 |
---|---|
OH_AVCapability_IsEncoderBitrateModeSupported | 确认当前编解码器是否支持给定的码控模式 |
OH_AVCapability_GetEncoderBitrateRange | 获取当前编解码器支持的码率范围,在CBR和VBR码控模式下使用 |
OH_AVCapability_GetEncoderQualityRange | 获取当前编解码器支持的质量范围,在CQ码控模式下使用 |
CBR和VBR码控模式示例如下:
OH_BitrateMode bitrateMode = BITRATE_MODE_CBR;
int32_t bitrate = 3000000;
OH_AVCapability *capability = OH_AVCodec_GetCapability(OH_AVCODEC_MIMETYPE_VIDEO_AVC, true);
if (capability == nullptr) {
// 异常处理。
}
// 1. 确认待配置码控模式是否支持。
bool isSupported = OH_AVCapability_IsEncoderBitrateModeSupported(capability, bitrateMode);
if (!isSupported) {
// 异常处理。
}
// 2. 获取码率范围,判断待配置码率参数是否在范围内。
OH_AVRange bitrateRange = {-1, -1};
int32_t ret = OH_AVCapability_GetEncoderBitrateRange(capability, &bitrateRange);
if (ret != AV_ERR_OK || bitrateRange.maxVal <= 0) {
// 异常处理。
}
if (bitrate > bitrateRange.maxVal || bitrate < bitrateRange.minVal) {
// 3.(可选)调整待配置码率参数。
}
// 4. 配置编码参数。
OH_AVCodec *videoEnc = OH_VideoEncoder_CreateByMime(OH_AVCODEC_MIMETYPE_VIDEO_AVC);
OH_AVFormat *format = OH_AVFormat_CreateVideoFormat(OH_AVCODEC_MIMETYPE_VIDEO_AVC, 1920, 1080);
if (OH_AVFormat_SetIntValue(format, OH_MD_KEY_VIDEO_ENCODE_BITRATE_MODE, bitrateMode) &&
OH_AVFormat_SetLongValue(format, OH_MD_KEY_BITRATE, static_cast<int64_t>(bitrate)) == false) {
// 异常处理。
}
if (OH_VideoEncoder_Configure(videoEnc, format) != AV_ERR_OK) {
// 异常处理。
}
OH_AVFormat_Destroy(format);
CQ码控模式示例如下:
OH_BitrateMode bitrateMode = BITRATE_MODE_CQ;
int32_t quality = 0;
OH_AVCapability *capability = OH_AVCodec_GetCapability(OH_AVCODEC_MIMETYPE_VIDEO_AVC, true);
if (capability == nullptr) {
// 异常处理。
}
// 1. 确认待配置码控模式是否支持。
bool isSupported = OH_AVCapability_IsEncoderBitrateModeSupported(capability, bitrateMode);
if (!isSupported) {
// 异常处理。
}
// 2. 获取质量范围,判断待配置质量参数是否在范围内。
OH_AVRange qualityRange = {-1, -1};
int32_t ret = OH_AVCapability_GetEncoderQualityRange(capability, &qualityRange);
if (ret != AV_ERR_OK || qualityRange.maxVal < 0) {
// 异常处理。
}
if (quality > qualityRange.maxVal || quality < qualityRange.minVal) {
// 3.(可选)调整待配置质量参数。
}
// 5. 配置编码参数。
OH_AVCodec *videoEnc = OH_VideoEncoder_CreateByMime(OH_AVCODEC_MIMETYPE_VIDEO_AVC);
OH_AVFormat *format = OH_AVFormat_CreateVideoFormat(OH_AVCODEC_MIMETYPE_VIDEO_AVC, 1920, 1080);
if (OH_AVFormat_SetIntValue(format, OH_MD_KEY_VIDEO_ENCODE_BITRATE_MODE, bitrateMode) &&
OH_AVFormat_SetIntValue(format, OH_MD_KEY_QUALITY, quality) == false) {
// 异常处理。
}
if (OH_VideoEncoder_Configure(videoEnc, format) != AV_ERR_OK) {
// 异常处理。
}
OH_AVFormat_Destroy(format);
5 查询编解码器支持复杂度范围
复杂度等级决定了编解码器使用的工具的数目,仅部分编解码器支持。
接口 | 功能描述 |
---|---|
OH_AVCapability_GetEncoderComplexityRange | 获取当前编解码器支持的复杂度范围 |
OH_AVCapability *capability = OH_AVCodec_GetCapability(OH_AVCODEC_MIMETYPE_AUDIO_AAC, true);
if (capability == nullptr) {
// 异常处理。
}
// 确认支持的编码复杂度范围。
OH_AVRange complexityRange = {-1, -1};
int32_t ret = OH_AVCapability_GetEncoderComplexityRange(capability, &complexityRange);
6 设置正确的音频编解码参数
在音频编解码场景中,有采样率、通道数以及码率(仅音频编码)等参数需要查询后设置。
接口 | 功能描述 |
---|---|
OH_AVCapability_GetAudioSupportedSampleRates | 获取当前音频编解码器支持的采样率范围 |
OH_AVCapability_GetAudioChannelCountRange | 获取当前音频编解码器支持的通道数范围 |
OH_AVCapability_GetEncoderBitrateRange | 获取当前编码器支持的码率范围 |
音频编码场景,确认并设置正确的编码的参数,示例如下:
int32_t sampleRate = 44100;
int32_t channelCount = 2;
int32_t bitrate = 261000;
OH_AVCapability *capability = OH_AVCodec_GetCapability(OH_AVCODEC_MIMETYPE_AUDIO_AAC, true);
if (capability == nullptr) {
// 异常处理。
}
// 1. 确认待配置采样率是否支持。
const int32_t *sampleRates = nullptr;
uint32_t sampleRateNum = 0;
int32_t ret = OH_AVCapability_GetAudioSupportedSampleRates(capability, &sampleRates, &sampleRateNum);
if (ret != AV_ERR_OK || sampleRates == nullptr || sampleRateNum == 0) {
// 异常处理。
}
bool isMatched = false;
for (int i = 0; i < sampleRateNum; i++) {
if (sampleRates[i] == sampleRate) {
isMatched = true;
}
}
if (!isMatched) {
// 2.(可选)调整待配置采样率。
}
// 3. 获取通道数范围,判断待配置通道数参数是否在范围内。
OH_AVRange channelRange = {-1, -1};
ret = OH_AVCapability_GetAudioChannelCountRange(capability, &channelRange);
if (ret != AV_ERR_OK || channelRange.maxVal <= 0) {
// 异常处理。
}
if (channelCount > channelRange.maxVal || channelCount < channelRange.minVal ) {
// 4.(可选)调整待配置通道数。
}
// 5. 获取码率范围,判断待配置码率参数是否在范围内。
OH_AVRange bitrateRange = {-1, -1};
ret = OH_AVCapability_GetEncoderBitrateRange(capability, &bitrateRange);
if (ret != AV_ERR_OK || bitrateRange.maxVal <= 0) {
// 异常处理。
}
if (bitrate > bitrateRange.maxVal || bitrate < bitrateRange.minVal ) {
// 7.(可选)调整待配置码率值。
}
// 8. 配置编码参数。
OH_AVCodec *audioEnc = OH_AudioEncoder_CreateByMime(OH_AVCODEC_MIMETYPE_AUDIO_AAC);
OH_AVFormat *format = OH_AVFormat_Create();
if (OH_AVFormat_SetIntValue(format, OH_MD_KEY_AUD_SAMPLE_RATE, sampleRate) &&
OH_AVFormat_SetIntValue(format, OH_MD_KEY_AUD_CHANNEL_COUNT, channelCount) &&
OH_AVFormat_SetLongValue(format, OH_MD_KEY_BITRATE, static_cast<int64_t>(bitrate)) == false) {
// 异常处理。
}
if (OH_AudioEncoder_Configure(audioEnc, format) != AV_ERR_OK) {
// 异常处理。
}
OH_AVFormat_Destroy(format);
7 查询编解码档次和级别支持情况
编解码标准由很多编码工具构成,能应对多种编码场景。对于特定应用场景,并非需要所有的工具,编解码标准按档次确定多种编码工具的开启与关闭情况。以H.264为例,存在基本档次、主档次和高档次,参考OH_AVCProfile。
级别是对编解码器所需的处理能力和储存空间的划分。以H.264为例,存在1到6.2的20个级别,参考OH_AVCLevel。
接口 | 功能描述 |
---|---|
OH_AVCapability_GetSupportedProfiles | 获取当前编解码器支持的档次 |
OH_AVCapability_GetSupportedLevelsForProfile | 获取当前编解码器在给定档次的情况下支持的等级信息 |
OH_AVCapability_AreProfileAndLevelSupported | 确认当前编解码器是否支持特定的档次和等级组合 |
确认待配置档次是否支持,并查询能支持的级别,示例如下:
OH_AVCProfile profile = AVC_PROFILE_MAIN;
OH_AVCapability *capability = OH_AVCodec_GetCapability(OH_AVCODEC_MIMETYPE_VIDEO_AVC, true);
if (capability == nullptr) {
// 异常处理。
}
// 1. 确认待配置档次是否支持。
const int32_t *profiles = nullptr;
uint32_t profileNum = 0;
int32_t ret = OH_AVCapability_GetSupportedProfiles(capability, &profiles, &profileNum);
if (ret != AV_ERR_OK || profiles == nullptr || profileNum == 0) {
// 异常处理。
}
bool isMatched = false;
for (int i = 0; i < profileNum; i++) {
if (profiles[i] == profile) {
isMatched = true;
}
}
// 2. 查询待配置档次能支持的级别范围。
const int32_t *levels = nullptr;
uint32_t levelNum = 0;
ret = OH_AVCapability_GetSupportedLevelsForProfile(capability, profile, &levels, &levelNum);
if (ret != AV_ERR_OK || levels == nullptr || levelNum == 0) {
// 异常处理。
}
OH_AVCLevel maxLevel = static_cast<OH_AVCLevel>(levels[levelNum -1]);
// 3.(可选)基于支持的最大级别做业务逻辑区分。
switch (maxLevel) {
case AVC_LEVEL_31:
// level 3.1-3.2,宽、高最大可配1280x720。
break;
case AVC_LEVEL_51:
// level 4.0以上,宽、高最大可配1920x1080。
break;
default:
// 报错,不做编码。
}
// 4. 配置档次参数。
OH_AVCodec *videoEnc = OH_VideoEncoder_CreateByMime(OH_AVCODEC_MIMETYPE_VIDEO_AVC);
OH_AVFormat *format = OH_AVFormat_CreateVideoFormat(OH_AVCODEC_MIMETYPE_VIDEO_AVC, 1920, 1080);
if (!OH_AVFormat_SetIntValue(format, OH_MD_KEY_PROFILE, profile)) {
// 异常处理。
}
if (OH_VideoEncoder_Configure(videoEnc, format) != AV_ERR_OK) {
// 异常处理。
}
OH_AVFormat_Destroy(format);
已知需要的编码档次和级别组合,直接查询支持情况示例如下:
// 1. 获取H.264编码器能力实例。
OH_AVCapability *capability = OH_AVCodec_GetCapability(OH_AVCODEC_MIMETYPE_VIDEO_AVC, true);
if (capability == nullptr) {
// 异常处理。
}
// 2. 查询编码档次和级别是否支持。
bool isSupported = OH_AVCapability_AreProfileAndLevelSupported(capability, AVC_PROFILE_MAIN, AVC_LEVEL_51);
8 设置正确的视频宽高
视频编解码器对宽高存在对齐约束,如主流编解码器默认编解码像素格式为YUV420系列中的,会对UV分量下采样,在此情况下视频编解码的宽高至少要按2对齐。此外还有其他因素可能导致更加严格的对齐约束。
视频编解码的宽高不仅会受帧级编解码能力限制,同时也会受协议中级别对帧级能力的限制。以H.264为例,AVC_LEVEL_51限定最大每帧宏块数目为36864。
给定图像宽和高,求最大帧率的公式如下, 其中MaxMBsPerFrameLevelLimits是编解码器能支持的最大级别在协议中限定的最大每帧宏块数, MaxMBsPerFrameSubmit是编解码器上报能支持的最大每帧宏块数,实际能力取两者交集。
接口 | 功能描述 |
---|---|
OH_AVCapability_GetVideoWidthAlignment | 获取当前视频编解码器的宽对齐 |
OH_AVCapability_GetVideoHeightAlignment | 确认当前视频编解码器的高对齐 |
OH_AVCapability_GetVideoWidthRange | 获取当前视频编解码器支持的宽的范围 |
OH_AVCapability_GetVideoHeightRange | 获取当前视频编解码器支持的高的范围 |
OH_AVCapability_GetVideoWidthRangeForHeight | 获取当前视频编解码器在给定高情况下的宽的范围 |
OH_AVCapability_GetVideoHeightRangeForWidth | 获取当前视频编解码器在给定宽情况下的高的范围 |
OH_AVCapability_IsVideoSizeSupported | 确认当前视频编解码器是否支持给定的宽高组合 |
已知视频高和视频宽,校验是否支持,示例如下:
int32_t width = 1920;
int32_t height = 1080;
OH_AVCapability *capability = OH_AVCodec_GetCapability(OH_AVCODEC_MIMETYPE_VIDEO_AVC, true);
// 1. 确认视频宽高是否支持。
bool isSupported = OH_AVCapability_IsVideoSizeSupported(capability, width, height);
if (!isSupported) {
// 2. (可选) 按已知视频高或已知视频宽查询详细限制,并调整。
}
若遇到基于已知视频高和视频宽校验不支持或配置失败,可用下列示意的多种方式尝试找到正确的视频宽高范围。
已知视频宽,找到正确的尺寸配置,示例如下:
int32_t width = 1920;
OH_AVCapability *capability = OH_AVCodec_GetCapability(OH_AVCODEC_MIMETYPE_VIDEO_AVC, true);
// 1. 确认视频宽符合宽对齐要求。
int32_t widthAlignment = 0;
int32_t ret = OH_AVCapability_GetVideoWidthAlignment(capability, &widthAlignment);
if (ret != AV_ERR_OK || widthAlignment <= 0) {
// 异常处理。
} else if (width % widthAlignment != 0) {
// 2. (可选) 对齐视频宽。
width = (width + widthAlignment - 1) / widthAlignment * widthAlignment;
}
// 3. 确认视频宽处在可支持宽范围内。
OH_AVRange widthRange = {-1, -1};
ret = OH_AVCapability_GetVideoWidthRange(capability, &widthRange);
if (ret != AV_ERR_OK || widthRange.maxVal <= 0) {
// 异常处理。
} else if (width < widthRange.minVal || width > widthRange.maxVal) {
// 4. (可选) 调整视频宽。
width = min(max(width, widthRange.minVal), widthRange.maxVal);
}
// 5. 基于视频宽,获取可选视频高的范围。
OH_AVRange heightRange = {-1, -1};
ret = OH_AVCapability_GetVideoHeightRangeForWidth(capability, width, &heightRange);
if (ret != AV_ERR_OK || heightRange.maxVal <= 0) {
// 异常处理。
}
// 6. 从可选高度范围中挑选合适的高度配置。
已知视频高,找到正确的尺寸配置,示例如下:
int32_t height = 1080;
OH_AVCapability *capability = OH_AVCodec_GetCapability(OH_AVCODEC_MIMETYPE_VIDEO_AVC, true);
// 1. 确认视频高符合高对齐要求。
int32_t heightAlignment = 0;
int32_t ret = OH_AVCapability_GetVideoHeightAlignment(capability, &heightAlignment);
if (ret != AV_ERR_OK || heightAlignment <= 0) {
// 异常处理。
} else if (height % heightAlignment != 0) {
// 2. (可选) 对齐视频高。
height = (height + heightAlignment - 1) / heightAlignment * heightAlignment;
}
// 3. 确认视频高处在可支持高范围内。
OH_AVRange heightRange = {-1, -1};
ret = OH_AVCapability_GetVideoHeightRange(capability, &heightRange);
if (ret != AV_ERR_OK || heightRange.maxVal <= 0) {
// 异常处理。
} else if (height < heightRange.minVal || height > heightRange.maxVal) {
// 4. (可选) 调整视频高。
height = min(max(height, heightRange.minVal), heightRange.maxVal);
}
// 5. 基于视频高,获取可选视频宽的范围。
OH_AVRange widthRange = {-1, -1};
ret = OH_AVCapability_GetVideoWidthRangeForHeight(capability, height, &widthRange);
if (ret != AV_ERR_OK || widthRange.maxVal <= 0) {
// 异常处理。
}
// 6. 从可选宽度范围中挑选合适的宽度配置。
9 设置正确的视频帧率
视频编解码的帧率不仅会受编解码器秒级编解码能力限制,同时也会受协议中级别对秒级能力的限制。以H.264为例,AVC_LEVEL_51限定最大每秒宏块数目为983040。
给定图像宽和高,求最大帧率的公式如下, 其中MaxMBsPerSecondLevelLimits是编解码器能支持的最大级别在协议中限定的最大每秒宏块数, MaxMBsPerSecondSubmit是编解码器上报能支持的最大每秒宏块数,实际能力取两者交集。
接口 | 功能描述 |
---|---|
OH_AVCapability_GetVideoFrameRateRange | 获取当前视频编解码器支持的帧率的范围 |
OH_AVCapability_GetVideoFrameRateRangeForSize | 获取当前视频编解码器在给定图像尺寸情况下的帧率的范围 |
OH_AVCapability_AreVideoSizeAndFrameRateSupported | 检查视频编解码器是否支持视频大小和帧率的特定组合 |
有需求的帧率目标,确认帧率是否在可选范围内,示例如下:
int32_t frameRate = 120;
OH_AVCapability *capability = OH_AVCodec_GetCapability(OH_AVCODEC_MIMETYPE_VIDEO_AVC, true);
// 1. 获取支持的帧率范围。
OH_AVRange frameRateRange = {-1, -1};
int32_t ret = OH_AVCapability_GetVideoFrameRateRange(capability, &frameRateRange);
if (ret != AV_ERR_OK || frameRateRange.maxVal <= 0) {
// 异常处理。
}
// 2. 判断是否在可选帧率范围内。
bool isSupported = frameRate >= frameRateRange.minVal && frameRate <= frameRateRange.maxVal;
基于待配置的尺寸找到合适的帧率配置,示例代码如下:
constexpr int32_t width = 1920;
constexpr int32_t height = 1080;
int32_t frameRate = 120;
OH_AVCapability *capability = OH_AVCodec_GetCapability(OH_AVCODEC_MIMETYPE_VIDEO_AVC, true);
// 1. 确认待配置尺寸是否能达到理想帧率。
bool isSupported = OH_AVCapability_AreVideoSizeAndFrameRateSupported(capability, width, height, frameRate);
if (!isSupported) {
// 2. 基于待配置视频尺寸,查询支持的帧率范围,并基于查询到的帧率调整待配置帧率。
OH_AVRange frameRateRange = {-1, -1};
int32_t ret = OH_AVCapability_GetVideoFrameRateRangeForSize(capability, width, height, &frameRateRange);
if (ret != AV_ERR_OK || frameRateRange.maxVal <= 0) {
// 异常处理。
}
frameRate = min(max(frameRate, frameRateRange.minVal), frameRateRange.maxVal);
}
// 3. 配置尺寸和帧率参数。
OH_AVCodec *videoEnc = OH_VideoEncoder_CreateByMime(OH_AVCODEC_MIMETYPE_VIDEO_AVC);
OH_AVFormat *format = OH_AVFormat_CreateVideoFormat(OH_AVCODEC_MIMETYPE_VIDEO_AVC, width, height);
if (!OH_AVFormat_SetIntValue(format, OH_MD_KEY_FRAME_RATE, frameRate)) {
// 异常处理。
}
if (OH_VideoEncoder_Configure(videoEnc, format) != AV_ERR_OK) {
// 异常处理。
}
OH_AVFormat_Destroy(format);
10 设置正确的视频像素格式信息
视频像素格式指示的编码输入图像或解码输出图像的像素排布方式,参考OH_AVPixelFormat。
接口 | 功能描述 |
---|---|
OH_AVCapability_GetVideoSupportedPixelFormats | 获取当前视频编解码器支持的像素格式 |
constexpr OH_AVPixelFormat DEFAULT_PIXELFORMAT = AV_PIXEL_FORMAT_NV12;
OH_AVCapability *capability = OH_AVCodec_GetCapability(OH_AVCODEC_MIMETYPE_VIDEO_AVC, true);
if (capability == nullptr) {
// 异常处理。
}
// 1. 获取当前视频编解码器支持的像素格式。
const int32_t *pixFormats = nullptr;
uint32_t pixFormatNum = 0;
int32_t ret = OH_AVCapability_GetVideoSupportedPixelFormats(capability, &pixFormats, &pixFormatNum);
if (ret != AV_ERR_OK || pixFormats == nullptr || pixFormatNum == 0) {
// 异常处理。
}
// 2. 校验是否支持对应像素格式。
bool isMatched = false;
for (int i = 0; i < pixFormatNum; i++) {
if (pixFormats[i] == DEFAULT_PIXELFORMAT) {
isMatched = true;
}
}
if (!isMatched) {
// 3. 替换其他像素格式输入或选择其他编解码器。
}
11 查询编解码特性支持情况并获取特性属性信息
编解码特性是指仅在特定编解码场景中使用的可选特性,参考OH_AVCapabilityFeature。
接口 | 功能描述 |
---|---|
OH_AVCapability_IsFeatureSupported | 确认当前编解码器是否支持给定的特性 |
OH_AVCapability_GetFeatureProperties | 获取当前编码器支持的指定特性的属性,仅部分特性存在属性信息 |
查询H.264编码器是否支持长期参考帧特性示例如下:
constexpr int32_t NEEDED_LTR_NUM = 2;
OH_AVFormat *format = OH_AVFormat_CreateVideoFormat(OH_AVCODEC_MIMETYPE_VIDEO_AVC, 1920, 1080);
OH_AVCapability *capability = OH_AVCodec_GetCapability(OH_AVCODEC_MIMETYPE_VIDEO_AVC, true);
if (capability == nullptr) {
// 异常处理。
}
// 1. 查询是否支持长期参考帧特性。
bool isSupported = OH_AVCapability_IsFeatureSupported(capability,VIDEO_ENCODER_LONG_TERM_REFERENCE);
if (isSupported) {
// 2. 查询支持的长期参考帧个数。
OH_AVFormat *properties = OH_AVCapability_GetFeatureProperties(capability, VIDEO_ENCODER_LONG_TERM_REFERENCE);
int32_t maxLTRCount = -1;
bool ret = OH_AVFormat_GetIntValue(properties, OH_FEATURE_PROPERTY_KEY_VIDEO_ENCODER_MAX_LTR_FRAME_COUNT, &maxLTRCount);
if (ret && maxLTRCount >= NEEDED_LTR_NUM) {
if (!OH_AVFormat_SetIntValue(format, OH_MD_KEY_VIDEO_ENCODER_LTR_FRAME_COUNT, NEEDED_LTR_NUM)) {
// 异常处理。
}
}
}
// 3. 编码器创建和配置。
OH_AVCodec *videoEnc = OH_VideoEncoder_CreateByMime(OH_AVCODEC_MIMETYPE_VIDEO_AVC);
if (OH_VideoEncoder_Configure(videoEnc, format) != AV_ERR_OK) {
// 异常处理。
}
3.2 音频编码
可以调用本模块的Native API接口,完成音频编码,即将音频PCM编码压缩成不同的格式。
适用场景
说明
AAC编码器默认采用的VBR可变码率模式,与配置的预期参数可能存在偏差。
3.2.1 开发指导
参考以下示例代码,完成音频编码的全流程,包括:创建编码器、设置编码参数(采样率/码率/声道数等)、开始、刷新、重置、销毁资源。
在应用开发过程中,开发者应按一定顺序调用方法,执行对应操作,否则系统可能会抛出异常或生成其他未定义的行为。具体顺序可参考下列开发步骤及对应说明。
如下为音频编码调用关系图:
虚线表示可选。
实线表示必选。
3.2.2 在 CMake 脚本中链接动态库
target_link_libraries(sample PUBLIC libnative_media_codecbase.so)
target_link_libraries(sample PUBLIC libnative_media_core.so)
target_link_libraries(sample PUBLIC libnative_media_acodec.so)
3.2.3 开发步骤
1. 添加头文件。
#include <multimedia/player_framework/native_avcodec_audiocodec.h>
#include <multimedia/native_audio_channel_layout.h>
#include <multimedia/player_framework/native_avcapability.h>
#include <multimedia/player_framework/native_avcodec_base.h>
#include <multimedia/player_framework/native_avformat.h>
#include <multimedia/player_framework/native_avbuffer.h>
2. 创建编码器实例对象,OH_AVCodec *为编码器实例指针。
应用可以通过名称或媒体类型创建编码器。
// c++标准库命名空间。
using namespace std;
// 通过 codecname 创建编码器。
OH_AVCapability *capability = OH_AVCodec_GetCapability(OH_AVCODEC_MIMETYPE_AUDIO_AAC, true);
const char *name = OH_AVCapability_GetName(capability);
OH_AVCodec *audioEnc_ = OH_AudioCodec_CreateByName(name);
// 设置判定是否为编码;设置true表示当前是编码。
bool isEncoder = true;
// 通过媒体类型创建编码器。
OH_AVCodec *audioEnc_ = OH_AudioCodec_CreateByMime(OH_AVCODEC_MIMETYPE_AUDIO_AAC, isEncoder);
// 初始化队列。
class AEncBufferSignal {
public:
std::mutex inMutex_;
std::mutex outMutex_;
std::mutex startMutex_;
std::condition_variable inCond_;
std::condition_variable outCond_;
std::condition_variable startCond_;
std::queue<uint32_t> inQueue_;
std::queue<uint32_t> outQueue_;
std::queue<OH_AVBuffer *> inBufferQueue_;
std::queue<OH_AVBuffer *> outBufferQueue_;
};
AEncBufferSignal *signal_;
3. 调用OH_AudioCodec_RegisterCallback()注册回调函数。
注册回调函数指针集合OH_AVCodecCallback,包括:
- OH_AVCodecOnError:编码器运行错误。
- OH_AVCodecOnStreamChanged:音频编码器暂未支持此回调。
- OH_AVCodecOnNeedInputBuffer:运行过程中需要新的输入数据,即编码器已准备好,可以输入PCM数据。
- OH_AVCodecOnNewOutputBuffer:运行过程中产生了新的输出数据,即编码完成。
// OH_AVCodecOnError回调函数的实现。
static void OnError(OH_AVCodec *codec, int32_t errorCode, void *userData)
{
(void)codec;
(void)errorCode;
(void)userData;
}
// OH_AVCodecOnStreamChanged回调函数的实现。
static void OnOutputFormatChanged(OH_AVCodec *codec, OH_AVFormat *format, void *userData)
{
(void)codec;
(void)format;
(void)userData;
}
// OH_AVCodecOnNeedInputBuffer回调函数的实现。
static void OnInputBufferAvailable(OH_AVCodec *codec, uint32_t index, OH_AVBuffer *data, void *userData)
{
(void)codec;
// 编码输入码流送入InputBuffer队列
AEncBufferSignal *signal = static_cast<AEncBufferSignal *>(userData);
unique_lock<mutex> lock(signal->inMutex_);
signal->inQueue_.push(index);
signal->inBufferQueue_.push(data);
signal->inCond_.notify_all();
}
// OH_AVCodecOnNewOutputBuffer回调函数的实现。
static void OnOutputBufferAvailable(OH_AVCodec *codec, uint32_t index, OH_AVBuffer *data, void *userData)
{
(void)codec;
// 将对应输出buffer的index送入OutputQueue_队列。
// 将对应编码完成的数据data送入outBuffer队列。
AEncBufferSignal *signal = static_cast<AEncBufferSignal *>(userData);
unique_lock<mutex> lock(signal->outMutex_);
signal->outQueue_.push(index);
signal->outBufferQueue_.push(data);
}
signal_ = new AEncBufferSignal();
OH_AVCodecCallback cb_ = {&OnError, &OnOutputFormatChanged, &OnInputBufferAvailable, &OnOutputBufferAvailable};
// 配置异步回调。
int32_t ret = OH_AudioCodec_RegisterCallback(audioEnc_, cb_, signal_);
if (ret != AV_ERR_OK) {
// 异常处理。
}
4. 调用OH_AudioCodec_Configure设置编码器。
设置必选项:采样率,码率,以及声道数,声道类型、位深。
可选项:最大输入长度。
flac编码: 需要额外标识兼容性级别(Compliance Level)和采样精度。
各音频编码类型参数范围说明:
音频编码类型 | 采样率(Hz) | 声道数 |
---|---|---|
Flac | 8000、11025、12000、16000、22050、24000、32000、44100、48000、64000、88200、96000 | 1~8 |
MP3 | 8000、11025、12000、16000、22050、24000、32000、44100、48000 | 1~2 |
G711mu | 8000 | 1 |
AAC-LC | 8000、11025、12000、16000、22050、24000、32000、44100、48000、64000、88200、96000 | 1、2、3、4、5、6、8 |
HE-AAC、HE-AAC v2 | 16000、22050、24000、32000、44100、48000、64000、88200、96000 | 1、2、3、4、5、6、8 |
opus | 8000、12000、16000、24000、48000 | 1~2 |
AMR(amrnb) | 8000 | 1 |
AMR(amrwb) | 16000 | 1 |
例如对一个44100Hz采样率、2声道立体声、SAMPLE_S16LE采样格式的PCM音频,以32000bps的码率进行AAC编码的调用流程如下:
int32_t ret;
// 配置音频采样率(必须)
constexpr uint32_t DEFAULT_SAMPLERATE = 44100;
// 配置音频码率(必须)
constexpr uint64_t DEFAULT_BITRATE = 32000;
// 配置音频声道数(必须)
constexpr uint32_t DEFAULT_CHANNEL_COUNT = 2;
// 配置音频声道类型(必须)
constexpr OH_AudioChannelLayout CHANNEL_LAYOUT = OH_AudioChannelLayout::CH_LAYOUT_STEREO;
// 配置音频位深(必须)
constexpr OH_BitsPerSample SAMPLE_FORMAT = OH_BitsPerSample::SAMPLE_S16LE;
// 配置AAC profile(可选,默认值:AAC_PROFILE_LC,其他可选值:AAC_PROFILE_HE、AAC_PROFILE_HE_V2)
constexpr int32_t AAC_PROFILE = OH_AACProfile::AAC_PROFILE_LC;
// 每20ms一帧音频数据
constexpr float TIME_PER_FRAME = 0.02;
// 配置最大输入长度, 每帧音频数据的大小(可选)
constexpr uint32_t DEFAULT_MAX_INPUT_SIZE = DEFAULT_SAMPLERATE * TIME_PER_FRAME * DEFAULT_CHANNEL_COUNT * sizeof(short); // aac
OH_AVFormat *format = OH_AVFormat_Create();
// 写入format
OH_AVFormat_SetIntValue(format,OH_MD_KEY_AUD_CHANNEL_COUNT, DEFAULT_CHANNEL_COUNT);
OH_AVFormat_SetIntValue(format,OH_MD_KEY_AUD_SAMPLE_RATE, DEFAULT_SAMPLERATE);
OH_AVFormat_SetLongValue(format,OH_MD_KEY_BITRATE, DEFAULT_BITRATE);
OH_AVFormat_SetIntValue(format, OH_MD_KEY_AUDIO_SAMPLE_FORMAT, SAMPLE_FORMAT);
OH_AVFormat_SetLongValue(format,OH_MD_KEY_CHANNEL_LAYOUT, CHANNEL_LAYOUT);
OH_AVFormat_SetIntValue(format, OH_MD_KEY_PROFILE, AAC_PROFILE);
OH_AVFormat_SetIntValue(format,OH_MD_KEY_MAX_INPUT_SIZE, DEFAULT_MAX_INPUT_SIZE);
// 配置编码器
ret = OH_AudioCodec_Configure(audioEnc_, format);
if (ret != AV_ERR_OK) {
// 异常处理
}
例FLAC调用流程:
int32_t ret;
// 配置音频采样率(必须)。
constexpr uint32_t DEFAULT_SAMPLERATE = 44100;
// 配置音频码率(必须)。
constexpr uint64_t DEFAULT_BITRATE = 261000;
// 配置音频声道数(必须)。
constexpr uint32_t DEFAULT_CHANNEL_COUNT = 2;
// 配置音频声道类型(必须)。
constexpr OH_AudioChannelLayout CHANNEL_LAYOUT = OH_AudioChannelLayout::CH_LAYOUT_STEREO;
// 配置音频位深(必须) flac只有SAMPLE_S16LE和SAMPLE_S32LE。
constexpr OH_BitsPerSample SAMPLE_FORMAT = OH_BitsPerSample::SAMPLE_S32LE;
// 配置音频compliance level (默认值0,取值范围-2~2)。
constexpr int32_t COMPLIANCE_LEVEL = 0;
// 配置音频精度(必须) SAMPLE_S16LE和SAMPLE_S24LE和SAMPLE_S32LE。
constexpr OH_BitsPerSample BITS_PER_CODED_SAMPLE = OH_BitsPerSample::SAMPLE_S24LE;
OH_AVFormat *format = OH_AVFormat_Create();
// 写入format。
OH_AVFormat_SetIntValue(format, OH_MD_KEY_AUD_CHANNEL_COUNT, DEFAULT_CHANNEL_COUNT);
OH_AVFormat_SetIntValue(format, OH_MD_KEY_AUD_SAMPLE_RATE, DEFAULT_SAMPLERATE);
OH_AVFormat_SetLongValue(format, OH_MD_KEY_BITRATE, DEFAULT_BITRATE);
OH_AVFormat_SetIntValue(format, OH_MD_KEY_BITS_PER_CODED_SAMPLE, BITS_PER_CODED_SAMPLE);
OH_AVFormat_SetIntValue(format, OH_MD_KEY_AUDIO_SAMPLE_FORMAT, SAMPLE_FORMAT);
OH_AVFormat_SetLongValue(format, OH_MD_KEY_CHANNEL_LAYOUT, CHANNEL_LAYOUT);
OH_AVFormat_SetLongValue(format, OH_MD_KEY_COMPLIANCE_LEVEL, COMPLIANCE_LEVEL);
// 配置编码器。
ret = OH_AudioCodec_Configure(audioEnc_, format);
if (ret != AV_ERR_OK) {
// 异常处理。
}
例AMR编码调用流程:
int32_t ret;
// 配置音频采样率(必须),amr-nb输入采样率为8000hz的PCM,amr-wb输入采样率为16000hz的PCM
constexpr uint32_t DEFAULT_SAMPLERATE = 8000;
// 配置音频码率(必须)
// amr-nb支持码率4750、5150、5900、6700、7400、7950、10200、12200
// amr-wb支持码率6600、8850、12650、14250、15850、18250、19850、23050、23850
constexpr uint64_t DEFAULT_BITRATE = 10200;
// 配置音频声道数(必须)
constexpr uint32_t DEFAULT_CHANNEL_COUNT = 1;
// 配置音频声道类型(必须)
constexpr OH_AudioChannelLayout CHANNEL_LAYOUT = OH_AudioChannelLayout::CH_LAYOUT_MONO;
// 配置音频位深(必须)
constexpr OH_BitsPerSample SAMPLE_FORMAT = OH_BitsPerSample::SAMPLE_S16LE;
OH_AVFormat *format = OH_AVFormat_Create();
// 写入format
OH_AVFormat_SetIntValue(format, OH_MD_KEY_AUD_CHANNEL_COUNT, DEFAULT_CHANNEL_COUNT);
OH_AVFormat_SetIntValue(format, OH_MD_KEY_AUD_SAMPLE_RATE, DEFAULT_SAMPLERATE);
OH_AVFormat_SetLongValue(format, OH_MD_KEY_BITRATE, DEFAULT_BITRATE);
OH_AVFormat_SetIntValue(format, OH_MD_KEY_AUDIO_SAMPLE_FORMAT, SAMPLE_FORMAT);
OH_AVFormat_SetLongValue(format, OH_MD_KEY_CHANNEL_LAYOUT, CHANNEL_LAYOUT);
// 配置编码器
ret = OH_AudioCodec_Configure(audioEnc_, format);
if (ret != AV_ERR_OK) {
// 异常处理
}
例opus编码调用流程:
int32_t ret;
// 配置音频采样率(必须)
// opus编码支持采样率:8000、12000、16000、24000、48000
constexpr uint32_t DEFAULT_SAMPLERATE = 8000;
// 配置音频码率(必须)
// opus编码码率范围:[6000, 510000]
constexpr uint64_t DEFAULT_BITRATE = 6000;
// 配置音频声道数(必须)
// opus编码支持声道数:1、2
constexpr uint32_t DEFAULT_CHANNEL_COUNT = 1;
// 配置音频位深(必须)
// opus编码支持位深:SAMPLE_S16LE
constexpr OH_BitsPerSample SAMPLE_FORMAT = OH_BitsPerSample::SAMPLE_S16LE;
OH_AVFormat *format = OH_AVFormat_Create();
// 写入format
OH_AVFormat_SetIntValue(format, OH_MD_KEY_AUD_CHANNEL_COUNT, DEFAULT_CHANNEL_COUNT);
OH_AVFormat_SetIntValue(format, OH_MD_KEY_AUD_SAMPLE_RATE, DEFAULT_SAMPLERATE);
OH_AVFormat_SetLongValue(format, OH_MD_KEY_BITRATE, DEFAULT_BITRATE);
OH_AVFormat_SetIntValue(format, OH_MD_KEY_AUDIO_SAMPLE_FORMAT, SAMPLE_FORMAT);
// 配置编码器
ret = OH_AudioCodec_Configure(audioEnc_, format);
if (ret != AV_ERR_OK) {
// 异常处理
}
5. 调用OH_AudioCodec_Prepare(),编码器就绪。
ret = OH_AudioCodec_Prepare(audioEnc_);
if (ret != AV_ERR_OK) {
// 异常处理。
}
6. 调用OH_AudioCodec_Start()启动编码器,进入运行态。
unique_ptr<ifstream> inputFile_ = make_unique<ifstream>();
unique_ptr<ofstream> outFile_ = make_unique<ofstream>();
// 打开待编码二进制文件路径(此处以输入为PCM文件为例)。
inputFile_->open(inputFilePath.data(), ios::in | ios::binary);
// 配置编码文件输出路径(此处以输出为编码码流文件为例)。
outFile_->open(outputFilePath.data(), ios::out | ios::binary);
// 开始编码。
ret = OH_AudioCodec_Start(audioEnc_);
if (ret != AV_ERR_OK) {
// 异常处理。
}
7. 调用OH_AudioCodec_PushInputBuffer(),写入待编码器的数据。需开发者填充完整的输入数据后调用。
每帧样点数(SAMPLES_PER_FRAME)取值:
aac建议使用20ms的PCM样点数,即采样率*0.02。
flac比较特殊,需要根据如下表格进行设置。
采样率 | 样点数 |
---|---|
8000 | 576 |
16000 | 1152 |
22050 | 2304 |
24000 | 2304 |
32000 | 2304 |
44100 | 4608 |
48000 | 4608 |
88200 | 8192 |
96000 | 8192 |
说明
aac编码的每帧样点数建议使用20ms的PCM样点数,即采样率*0.02。flac编码的样点数建议根据采样率按照表格传入,大于这个值也会返回错误码,如果小于有可能出现编码文件损坏问题。
// 每帧样点数。
constexpr int32_t SAMPLES_PER_FRAME = DEFAULT_SAMPLERATE * TIME_PER_FRAME;
// 声道数,对于amr编码声道数只支持单声道的音频输入。
constexpr int32_t DEFAULT_CHANNEL_COUNT = 2;
// 每帧输入数据的长度,声道数 * 每帧样点数 * 每个样点的字节数(以采样格式SAMPLE_S16LE为例)。
// 如果最后一帧数据不满足长度,建议进行丢弃或填充处理。
constexpr int32_t INPUT_FRAME_BYTES = DEFAULT_CHANNEL_COUNT * SAMPLES_PER_FRAME * sizeof(short);
uint32_t index = signal_->inQueue_.front();
auto buffer = signal_->inBufferQueue_.front();
OH_AVCodecBufferAttr attr = {0};
if (!inputFile_->eof()) {
inputFile_->read((char *)OH_AVBuffer_GetAddr(buffer), INPUT_FRAME_BYTES);
attr.size = INPUT_FRAME_BYTES;
attr.flags = AVCODEC_BUFFER_FLAGS_NONE;
} else {
attr.size = 0;
attr.flags = AVCODEC_BUFFER_FLAGS_EOS;
}
OH_AVBuffer_SetBufferAttr(buffer, &attr);
// 送入编码输入队列进行编码, index为对应队列下标。
ret = OH_AudioCodec_PushInputBuffer(audioEnc_, index);
if (ret != AV_ERR_OK) {
// 异常处理。
}
在上方案例中,attr.flags代表缓冲区标记的类别。
如果是结束,需要将flags标识成AVCODEC_BUFFER_FLAGS_EOS。
枚举值 | 描述 |
---|---|
AVCODEC_BUFFER_FLAGS_NONE | 表示为普通帧。 |
AVCODEC_BUFFER_FLAGS_EOS | 表示缓冲区是流结束帧。 |
AVCODEC_BUFFER_FLAGS_CODEC_DATA | 表示缓冲区包含编解码特定数据。 |
8. 调用OH_AudioCodec_FreeOutputBuffer(),释放编码后的数据。
在取走编码码流后,就应及时调用OH_AudioCodec_FreeOutputBuffer()进行释放。
uint32_t index = signal_->outQueue_.front();
OH_AVBuffer *avBuffer = signal_->outBufferQueue_.front();
// 获取buffer attributes。
OH_AVCodecBufferAttr attr = {0};
ret = OH_AVBuffer_GetBufferAttr(avBuffer, &attr);
if (ret != AV_ERR_OK) {
// 异常处理。
}
// 将编码完成数据data写入到对应输出文件中。
outputFile_->write(reinterpret_cast<char *>(OH_AVBuffer_GetAddr(avBuffer)), attr.size);
// 释放已完成写入的数据。
ret = OH_AudioCodec_FreeOutputBuffer(audioEnc_, index);
if (ret != AV_ERR_OK) {
// 异常处理。
}
if (attr.flags == AVCODEC_BUFFER_FLAGS_EOS) {
// 结束。
}
9. (可选)调用OH_AudioCodec_Flush()刷新编码器。
调用OH_AudioCodec_Flush()后,编码器处于Flush状态,会将当前编码队列清空。
此时需要调用OH_AudioCodec_Start()重新开始编码。
使用情况:
- 在文件EOS之后,需要调用刷新。
- 在执行过程中遇到可继续执行的错误时(即OH_AudioCodec_IsValid 为true)可以调用,然后重新调用OH_AudioCodec_Start。
// 刷新编码器 audioEnc_。
ret = OH_AudioCodec_Flush(audioEnc_);
if (ret != AV_ERR_OK) {
// 异常处理。
}
// 重新开始编码。
ret = OH_AudioCodec_Start(audioEnc_);
if (ret != AV_ERR_OK) {
// 异常处理。
}
10.(可选)调用OH_AudioCodec_Reset()重置编码器。
调用OH_AudioCodec_Reset()后,编码器回到初始化的状态,需要调用OH_AudioCodec_Configure()重新配置,然后调用OH_AudioCodec_Start()重新开始编码。
// 重置编码器 audioEnc_。
ret = OH_AudioCodec_Reset(audioEnc_);
if (ret != AV_ERR_OK) {
// 异常处理。
}
// 重新配置编码器参数。
ret = OH_AudioCodec_Configure(audioEnc_, format);
if (ret != AV_ERR_OK) {
// 异常处理。
}
11. 调用OH_AudioCodec_Stop()停止编码器。
停止后,可以通过Start重新进入已启动状态(started),但需要注意的是,如果编码器之前已输入数据,则需要重新输入编码器数据。
// 终止编码器 audioEnc_。
ret = OH_AudioCodec_Stop(audioEnc_);
if (ret != AV_ERR_OK) {
// 异常处理。
}
12. 调用OH_AudioCodec_Destroy()销毁编码器实例,释放资源。
// 调用OH_AudioCodec_Destroy, 注销编码器。
ret = OH_AudioCodec_Destroy(audioEnc_);
if (ret != AV_ERR_OK) {
// 异常处理。
} else {
audioEnc_ = NULL; // 不可重复destroy。
}
说明
资源不能重复销毁
3.3 音频解码
可以调用本模块的Native API接口,完成音频解码,即将媒体数据解码为PCM码流
开发步骤详见官方文档:音频解码-音视频编解码-AVCodec Kit(音视频编解码服务)-媒体 - 华为HarmonyOS开发者
3.4 视频编码
以调用本模块的Native API接口,完成视频编码,即将未压缩的视频数据压缩成视频码流。
当前支持的编码能力请参考AVCodec支持的格式。
如果需要对HDRVivid视频进行编码,需要配置MimeType为H265 (OH_AVCODEC_MIMETYPE_VIDEO_HEVC),本功能从API version 11开始支持。
视频编码支持以下能力:
支持的能力 | 使用简述 |
---|---|
运行时配置编码器参数,包括帧率、码率、QPMin/QPMax | 通过调用OH_VideoEncoder_SetParameter()配置, 具体可参考下文中:Surface模式的步骤-9 |
随帧设置编码QPMin/QPMax | 通过调用OH_VideoEncoder_RegisterParameterCallback()注册随帧参数回调时配置,具体可参考下文中:Surface模式的步骤-4 |
分层编码,LTR设置 | 具体可参考:时域可分层视频编码 |
获取编码每帧平均量化参数(QPAverage)、平方误差(mseValue) | 在配置回调函数OnNewOutputBuffer()时获取,具体可参考下文中:Surface模式的步骤-3 |
变分辨率 | 编码器支持输入图像分辨率发生变化。目前仅Surface模式支持且图像的宽、高不能超过OH_VideoEncoder_Configure接口配置的宽、高,具体可参考下文中:Surface模式的步骤-5 |
3.4.1 限制约束
- Buffer模式不支持10bit的图像数据。
- 由于硬件编码器资源有限,每个编码器在使用完毕后都必须调用OH_VideoEncoder_Destroy接口来销毁实例并释放资源。
- Flush,Reset,Stop,Destroy在非回调线程中执行时,会等待所有回调执行完成后,将执行结果返回给用户。
- 一旦调用Flush,Reset,Stop接口,会触发系统回收OH_AVBuffer,开发者不应对之前回调函数获取到的OH_AVBuffer继续进行操作。
- Buffer模式和Surface模式使用方式一致的接口,所以只提供了Surface模式的示例。
- 在Buffer模式下,开发者通过输入回调函数OH_AVCodecOnNeedInputBuffer获取到OH_AVBuffer的指针实例后,必须通过调用OH_VideoEncoder_PushInputBuffer接口来通知系统该实例已被使用完毕。这样系统才能够将该实例里面的数据进行编码。如果开发者在调用OH_AVBuffer_GetNativeBuffer接口时获取到OH_NativeBuffer指针实例,并且该实例的生命周期超过了当前的OH_AVBuffer指针实例,那么需要进行一次数据的拷贝操作。在这种情况下,开发者需要自行管理新生成的OH_NativeBuffer实例的生命周期,确保其正确使用和释放。
- 为确保系统服务的持续可用性,当检测到应用存在异常实例占用行为时,系统将自动介入。开发者应注意:持续的实例管理不当可能导致进程被终止。
3.4.2 surface输入与buffer输入
-
两者的数据来源不同。
-
两者的适用场景不同:
- surface输入是指用OHNativeWindow来传递输入数据,可以与其他模块对接,例如相机模块。
- buffer输入是指有一块预先分配好的内存区域,需要将原始数据拷贝进这块内存区域中。更适用于从文件中读取视频数据等场景。
-
在接口调用的过程中,两种方式的接口调用方式基本一致,但存在以下差异点:
- Buffer模式下,通过OH_VideoEncoder_PushInputBuffer接口输入数据;Surface模式下,开发者应在编码器就绪前调用OH_VideoEncoder_GetSurface接口,获取OHNativeWindow用于传递视频数据。
- Buffer模式下,通过OH_AVBuffer中的attr传入结束flag,编码器读取到尾帧后,停止编码;Surface模式下,需要调用OH_VideoEncoder_NotifyEndOfStream接口通知编码器输入流结束。
两种模式的开发步骤详细说明请参考:Surface模式和Buffer模式。
3.4.3 状态机调用关系
如下为状态机调用关系图:
-
有两种方式可以使编码器进入Initialized状态:
- 初始创建编码器实例时,编码器处于Initialized状态。
- 任何状态下,调用OH_VideoEncoder_Reset接口,编码器将会移回Initialized状态。
-
Initialized状态下,调用OH_VideoEncoder_Configure接口配置编码器,配置成功后编码器进入Configured状态。
-
Configured状态下,调用OH_VideoEncoder_Prepare()进入Prepared状态。
-
Prepared状态下,调用OH_VideoEncoder_Start接口使编码器进入Executing状态:
- 处于Executing状态时,调用OH_VideoEncoder_Stop接口可以使编码器返回到Prepared状态。
-
在极少数情况下,编码器可能会遇到错误并进入Error状态。编码器的错误传递,可以通过队列操作返回无效值或者抛出异常:
- Error状态下,可以调用OH_VideoEncoder_Reset接口将编码器移到Initialized状态;或者调用OH_VideoEncoder_Destroy接口移动到最后的Released状态。
-
Executing 状态具有三个子状态:Flushed、Running和End-of-Stream:
- 在调用了OH_VideoEncoder_Start接口之后,编码器立即进入Running子状态。
- 对于处于Executing状态的编码器,可以调用OH_VideoEncoder_Flush接口返回到Flushed子状态。
- 当待处理数据全部传递给编码器后,可以在input buffers队列中为最后一个入队的input buffer中添加AVCODEC_BUFFER_FLAGS_EOS标记,遇到这个标记时,编码器会转换为End-of-Stream子状态。在此状态下,编码器不再接受新的输入,但是仍然会继续生成输出,直到输出到达尾帧。
-
使用完编码器后,必须调用OH_VideoEncoder_Destroy接口销毁编码器实例,使编码器进入Released状态。
3.4.4 开发指导
如下为视频编码调用关系图:
虚线表示可选。
实线表示必选。
1、在 CMake 脚本中链接动态库
target_link_libraries(sample PUBLIC libnative_media_codecbase.so)
target_link_libraries(sample PUBLIC libnative_media_core.so)
target_link_libraries(sample PUBLIC libnative_media_venc.so)
2、定义基础结构
本部分示例代码按照C++17标准编写,仅作参考。开发者可以参考此部分,定义自己的buffer对象。
1. 添加头文件。
#include <condition_variable>
#include <memory>
#include <mutex>
#include <queue>
#include <shared_mutex>
2. 编码器回调buffer的信息。
struct CodecBufferInfo {
CodecBufferInfo(uint32_t index, OH_AVBuffer *buffer): index(index), buffer(buffer), isValid(true) {}
CodecBufferInfo(uint32_t index, OH_AVFormat *parameter): index(index), parameter(parameter), isValid(true) {}
// 回调buffer。
OH_AVBuffer *buffer = nullptr;
// Surface模式下,输入回调的随帧参数,需要注册随帧通路后使用。
OH_AVFormat *parameter = nullptr;
// 回调buffer对应的index。
uint32_t index = 0;
// 判断当前buffer信息是否有效。
bool isValid = true;
};
3. 编码输入输出队列。
class CodecBufferQueue {
public:
// 将回调buffer的信息传入队列。
void Enqueue(const std::shared_ptr<CodecBufferInfo> bufferInfo)
{
std::unique_lock<std::mutex> lock(mutex_);
bufferQueue_.push(bufferInfo);
cond_.notify_all();
}
// 获取回调buffer的信息。
std::shared_ptr<CodecBufferInfo> Dequeue(int32_t timeoutMs = 1000)
{
std::unique_lock<std::mutex> lock(mutex_);
(void)cond_.wait_for(lock, std::chrono::milliseconds(timeoutMs), [this]() { return !bufferQueue_.empty(); });
if (bufferQueue_.empty()) {
return nullptr;
}
std::shared_ptr<CodecBufferInfo> bufferInfo = bufferQueue_.front();
bufferQueue_.pop();
return bufferInfo;
}
// 清空队列,之前的回调buffer设置为不可用。
void Flush()
{
std::unique_lock<std::mutex> lock(mutex_);
while (!bufferQueue_.empty()) {
std::shared_ptr<CodecBufferInfo> bufferInfo = bufferQueue_.front();
// Flush、Stop、Reset、Destroy操作之后,之前回调的buffer信息设置为无效。
bufferInfo->isValid = false;
bufferQueue_.pop();
}
}
private:
std::mutex mutex_;
std::condition_variable cond_;
std::queue<std::shared_ptr<CodecBufferInfo>> bufferQueue_;
};
4. 全局变量
仅做参考,可以根据实际情况将其封装到对象中。
// 视频帧宽度。
int32_t width = 320;
// 视频帧高度。
int32_t height = 240;
// 视频像素格式。
OH_AVPixelFormat pixelFormat = AV_PIXEL_FORMAT_NV12;
// 视频宽跨距。
int32_t widthStride = 0;
// 视频高跨距。
int32_t heightStride = 0;
// 编码器实例指针。
OH_AVCodec *videoEnc = nullptr;
// 编码器同步锁。
std::shared_mutex codecMutex;
// 编码器输入队列。
CodecBufferQueue inQueue;
// 编码器输出队列。
CodecBufferQueue outQueue;
3.4.5 Surface模式
参考以下示例代码,可以完成Surface模式下视频编码的全流程。此处以surface数据输入,编码成H.264格式为例。
本模块目前仅支持异步模式的数据轮转。
1. 添加头文件。
#include <multimedia/player_framework/native_avcodec_videoencoder.h>
#include <multimedia/player_framework/native_avcapability.h>
#include <multimedia/player_framework/native_avcodec_base.h>
#include <multimedia/player_framework/native_avformat.h>
#include <multimedia/player_framework/native_avbuffer.h>
#include <fstream>
2. 创建编码器实例。
可以通过名称或媒体类型创建编码器。示例中的变量说明如下:
- videoEnc:视频编码器实例的指针;
- capability:编解码器能力查询实例的指针;
- OH_AVCODEC_MIMETYPE_VIDEO_AVC:AVC格式视频编解码器。
创建方式示例如下:
// 通过codec name创建编码器,应用有特殊需求,比如选择支持某种分辨率规格的编码器,可先查询capability,再根据codec name创建编码器。
OH_AVCapability *capability = OH_AVCodec_GetCapability(OH_AVCODEC_MIMETYPE_VIDEO_AVC, true);
// 创建硬件编码器实例。
OH_AVCapability *capability= OH_AVCodec_GetCapabilityByCategory(OH_AVCODEC_MIMETYPE_VIDEO_AVC, true, HARDWARE);
const char *codecName = OH_AVCapability_GetName(capability);
OH_AVCodec *videoEnc = OH_VideoEncoder_CreateByName(codecName);
// 通过MIME TYPE创建编码器,只能创建系统推荐的特定编解码器。
// 只能创建硬件编码器。
OH_AVCodec *videoEnc = OH_VideoEncoder_CreateByMime(OH_AVCODEC_MIMETYPE_VIDEO_AVC);
3.
调用OH_VideoEncoder_RegisterCallback()设置回调函数。
注册回调函数指针集合OH_AVCodecCallback,包括:
- OH_AVCodecOnError 编码器运行错误,返回的错误码详情请参见OH_AVCodecOnError;
- OH_AVCodecOnStreamChanged 码流信息变化,如格式变化等;
- OH_AVCodecOnNeedInputBuffer 输入回调无作用,开发者通过获取的surface输入数据;
- OH_AVCodecOnNewOutputBuffer 运行过程中产生了新的输出数据,即编码完成。
回调函数的具体实现可参考示例工程。
示例如下所示:
int32_t qpAverage = 20;
double mseValue = 0.0;
// 设置OH_AVCodecOnError 回调函数,编码异常
static void OnError(OH_AVCodec *codec, int32_t errorCode, void *userData)
{
// 回调的错误码由用户判断处理
(void)codec;
(void)errorCode;
(void)userData;
}
// 设置OH_AVCodecOnStreamChanged 回调函数,编码数据流变化
static void OnStreamChanged(OH_AVCodec *codec, OH_AVFormat *format, void *userData)
{
(void)codec;
(void)format;
(void)userData;
// 可通过format获取到分辨率变化后的视频宽、高
OH_AVFormat_GetIntValue(format, OH_MD_KEY_VIDEO_PIC_WIDTH, &width);
OH_AVFormat_GetIntValue(format, OH_MD_KEY_VIDEO_PIC_HEIGHT, &height);
}
// 设置 OH_AVCodecOnNeedInputBuffer 回调函数,编码输入帧送入数据队列。
static void OnNeedInputBuffer(OH_AVCodec *codec, uint32_t index, OH_AVBuffer *buffer, void *userData)
{
// Surface模式下,该回调函数无作用,开发者通过获取的surface输入数据。
(void)userData;
(void)index;
(void)buffer;
}
// 设置 OH_AVCodecOnNewOutputBuffer 回调函数,编码完成帧送入输出队列
static void OnNewOutputBuffer(OH_AVCodec *codec, uint32_t index, OH_AVBuffer *buffer, void *userData)
{
// 获取视频帧的平均量化参数,平方误差
OH_AVFormat *format = OH_AVBuffer_GetParameter(buffer);
OH_AVFormat_GetIntValue(format, OH_MD_KEY_VIDEO_ENCODER_QP_AVERAGE, &qpAverage);
OH_AVFormat_GetDoubleValue(format, OH_MD_KEY_VIDEO_ENCODER_MSE, &mseValue);
OH_AVFormat_Destroy(format);
// 完成帧的数据buffer和对应的index送入outQueue队列
(void)codec;
(void)userData;
outQueue.Enqueue(std::make_shared<CodecBufferInfo>(index, buffer));
}
// 配置异步回调,调用 OH_VideoEncoder_RegisterCallback()接口。
OH_AVCodecCallback cb = {&OnError, &OnStreamChanged, &OnNeedInputBuffer, &OnNewOutputBuffer};
int32_t ret = OH_VideoEncoder_RegisterCallback(videoEnc, cb, nullptr); // nullptr:用户特定数据userData为空。
if (ret != AV_ERR_OK) {
// 异常处理。
}
说明
在回调函数中,对数据队列进行操作时,需要注意多线程同步的问题。
4.(可选)调用OH_VideoEncoder_RegisterParameterCallback()在Configure接口之前注册随帧通路回调。
详情请参考时域可分层视频编码。
// 5.1 编码输入参数回调OH_VideoEncoder_OnNeedInputParameter实现
static void OnNeedInputParameter(OH_AVCodec *codec, uint32_t index, OH_AVFormat *parameter, void *userData)
{
// 输入帧parameter对应的index,送入InParameterIndexQueue队列
// 输入帧的数据parameter送入InParameterQueue队列
// 数据处理
// 随帧参数写入
// 配置OH_MD_KEY_VIDEO_ENCODER_QP_MAX 的值应大于等于OH_MD_KEY_VIDEO_ENCODER_QP_MIN
OH_AVFormat_SetIntValue(parameter, OH_MD_KEY_VIDEO_ENCODER_QP_MAX, 30);
OH_AVFormat_SetIntValue(parameter, OH_MD_KEY_VIDEO_ENCODER_QP_MIN, 20);
}
// 5.2 注册随帧参数回调
OH_VideoEncoder_OnNeedInputParameter inParaCb = OnNeedInputParameter;
OH_VideoEncoder_RegisterParameterCallback(videoEnc, inParaCb, NULL); // NULL:用户特定数据userData为空
5. 调用OH_VideoEncoder_Configure()配置编码器。
详细可配置选项的说明请参考视频专有键值对。
参数校验规则请参考OH_VideoEncoder_Configure()参考文档。
参数取值范围可以通过能力查询接口获取,具体示例请参考获取支持的编解码能力文档。
目前支持的所有格式都必须配置以下选项:视频帧宽度、视频帧高度、视频像素格式。示例中的变量如下:
- DEFAULT_WIDTH:320像素宽度;
- DEFAULT_HEIGHT:240像素高度;
- DEFAULT_PIXELFORMAT: 像素格式,因为示例使用YUV的文件保存的像素格式是NV12,所以设置为 AV_PIXEL_FORMAT_NV12。
// 配置视频帧速率。
double frameRate = 30.0;
// 配置视频YUV值范围标志。
bool rangeFlag = false;
// 配置视频原色。
int32_t primary = static_cast<int32_t>(OH_ColorPrimary::COLOR_PRIMARY_BT709);
// 配置传输特性。
int32_t transfer = static_cast<int32_t>(OH_TransferCharacteristic::TRANSFER_CHARACTERISTIC_BT709);
// 配置最大矩阵系数。
int32_t matrix = static_cast<int32_t>(OH_MatrixCoefficient::MATRIX_COEFFICIENT_IDENTITY);
// 配置编码Profile。
int32_t profile = static_cast<int32_t>(OH_AVCProfile::AVC_PROFILE_HIGH);
// 配置编码比特率模式。
int32_t rateMode = static_cast<int32_t>(OH_BitrateMode::BITRATE_MODE_VBR);
// 配置关键帧的间隔,单位为毫秒。
int32_t iFrameInterval = 1000;
// 配置比特率,单位为bps。
int64_t bitRate = 5000000;
// 配置编码质量。
int64_t quality = 90;
OH_AVFormat *format = OH_AVFormat_Create();
OH_AVFormat_SetIntValue(format, OH_MD_KEY_WIDTH, width); // 必须配置。
OH_AVFormat_SetIntValue(format, OH_MD_KEY_HEIGHT, height); // 必须配置。
OH_AVFormat_SetIntValue(format, OH_MD_KEY_PIXEL_FORMAT, pixelFormat); // 必须配置。
OH_AVFormat_SetDoubleValue(format, OH_MD_KEY_FRAME_RATE, frameRate);
OH_AVFormat_SetIntValue(format, OH_MD_KEY_RANGE_FLAG, rangeFlag);
OH_AVFormat_SetIntValue(format, OH_MD_KEY_COLOR_PRIMARIES, primary);
OH_AVFormat_SetIntValue(format, OH_MD_KEY_TRANSFER_CHARACTERISTICS, transfer);
OH_AVFormat_SetIntValue(format, OH_MD_KEY_MATRIX_COEFFICIENTS, matrix);
OH_AVFormat_SetIntValue(format, OH_MD_KEY_I_FRAME_INTERVAL, iFrameInterval);
OH_AVFormat_SetIntValue(format, OH_MD_KEY_PROFILE, profile);
//只有当OH_BitrateMode = BITRATE_MODE_CQ 时,才需要配置OH_MD_KEY_QUALITY。
if (rateMode == static_cast<int32_t>(OH_BitrateMode::BITRATE_MODE_CQ)) {
OH_AVFormat_SetIntValue(format, OH_MD_KEY_QUALITY, quality);
} else if (rateMode == static_cast<int32_t>(OH_BitrateMode::BITRATE_MODE_CBR) ||
rateMode == static_cast<int32_t>(OH_BitrateMode::BITRATE_MODE_VBR)){
OH_AVFormat_SetLongValue(format, OH_MD_KEY_BITRATE, bitRate);
}
OH_AVFormat_SetIntValue(format, OH_MD_KEY_VIDEO_ENCODE_BITRATE_MODE, rateMode);
int32_t ret = OH_VideoEncoder_Configure(videoEnc, format);
if (ret != AV_ERR_OK) {
// 异常处理。
}
OH_AVFormat_Destroy(format);
6. 获取surface。
获取编码器Surface模式的OHNativeWindow输入,获取surface需要在调用OH_VideoEncoder_Prepare接口之前完成。
// 获取需要输入的surface,以进行编码。
OHNativeWindow *nativeWindow;
int32_t ret = OH_VideoEncoder_GetSurface(videoEnc, &nativeWindow);
if (ret != AV_ERR_OK) {
// 异常处理。
}
// 通过OHNativeWindow*变量类型,可通过生产者接口获取待填充数据地址。
OHNativeWindow*变量类型的使用方法请参考图形子系统 OHNativeWindow。
7. 调用OH_VideoEncoder_Prepare()编码器就绪。
该接口将在编码器运行前进行一些数据的准备工作。
int32_t ret = OH_VideoEncoder_Prepare(videoEnc);
if (ret != AV_ERR_OK) {
// 异常处理。
}
8. 调用OH_VideoEncoder_Start()启动编码器。
// 配置待编码文件路径。
std::string_view outputFilePath = "/*yourpath*.h264";
std::unique_ptr<std::ofstream> outputFile = std::make_unique<std::ofstream>();
outputFile->open(outputFilePath.data(), std::ios::out | std::ios::binary | std::ios::ate);
// 启动编码器,开始编码。
int32_t ret = OH_VideoEncoder_Start(videoEnc);
if (ret != AV_ERR_OK) {
// 异常处理。
}
9. (可选)OH_VideoEncoder_SetParameter()在运行过程中动态配置编码器参数。
OH_AVFormat *format = OH_AVFormat_Create();
// 支持动态请求IDR帧
OH_AVFormat_SetIntValue(format, OH_MD_KEY_REQUEST_I_FRAME, true);
// 支持动态重置比特率
int64_t bitRate = 2000000;
OH_AVFormat_SetLongValue(format, OH_MD_KEY_BITRATE, bitRate);
// 支持动态重置视频帧速率
double frameRate = 60.0;
OH_AVFormat_SetDoubleValue(format, OH_MD_KEY_FRAME_RATE, frameRate);
// 支持动态设置QP值
// 配置OH_MD_KEY_VIDEO_ENCODER_QP_MAX 的值应大于等于OH_MD_KEY_VIDEO_ENCODER_QP_MIN
OH_AVFormat_SetIntValue(format, OH_MD_KEY_VIDEO_ENCODER_QP_MAX, 30);
OH_AVFormat_SetIntValue(format, OH_MD_KEY_VIDEO_ENCODER_QP_MIN, 20);
int32_t ret = OH_VideoEncoder_SetParameter(videoEnc, format);
if (ret != AV_ERR_OK) {
// 异常处理
}
OH_AVFormat_Destroy(format);
10. 写入编码图像。
在之前的第6步中,开发者已经对OH_VideoEncoder_GetSurface接口返回的OHNativeWindow*类型变量进行配置。因为编码所需的数据,由配置的surface进行持续地输入,所以开发者无需对OnNeedInputBuffer回调函数进行处理,也无需使用OH_VideoEncoder_PushInputBuffer接口输入数据。
在变分辨率场景中,此规则也同样适用。
11. (可选)调用OH_VideoEncoder_PushInputParameter()通知编码器随帧参数配置输入完成。
在之前的第4步中,开发者已经注册随帧通路回调。
以下示例中:
- index:回调函数OnNeedInputParameter传入的参数,与buffer唯一对应的标识。
std::shared_ptr<CodecBufferInfo> bufferInfo = inQueue.Dequeue();
std::shared_lock<std::shared_mutex> lock(codecMutex);
if (bufferInfo == nullptr || !bufferInfo->isValid) {
// 异常处理。
}
// 值由开发者决定。
int32_t isIFrame;
OH_AVFormat_SetIntValue(bufferInfo->parameter, OH_MD_KEY_REQUEST_I_FRAME, isIFrame);
int32_t ret = OH_VideoEncoder_PushInputParameter(videoEnc, bufferInfo->index);
if (ret != AV_ERR_OK) {
// 异常处理。
}
12. 调用OH_VideoEncoder_NotifyEndOfStream()通知编码器结束。
// Surface模式:通知视频编码器输入流已结束,只能使用此接口进行通知。
// 不能像Buffer模式中将flag设为AVCODEC_BUFFER_FLAGS_EOS,再调用OH_VideoEncoder_PushInputBuffer接口通知编码器输入结束。
int32_t ret = OH_VideoEncoder_NotifyEndOfStream(videoEnc);
if (ret != AV_ERR_OK) {
// 异常处理。
}
13. 调用OH_VideoEncoder_FreeOutputBuffer()释放编码帧。
以下示例中:
- index:回调函数OnNewOutputBuffer传入的参数,与buffer唯一对应的标识;
- buffer:回调函数OnNewOutputBuffer传入的参数,可以通过OH_AVBuffer_GetAddr接口得到共享内存地址的指针。
std::shared_ptr<CodecBufferInfo> bufferInfo = outQueue.Dequeue();
std::shared_lock<std::shared_mutex> lock(codecMutex);
if (bufferInfo == nullptr || !bufferInfo->isValid) {
// 异常处理。
}
// 获取编码后信息。
OH_AVCodecBufferAttr info;
int32_t ret = OH_AVBuffer_GetBufferAttr(bufferInfo->buffer, &info);
if (ret != AV_ERR_OK) {
// 异常处理。
}
// 将编码完成帧数据buffer写入到对应输出文件中。
outputFile->write(reinterpret_cast<char *>(OH_AVBuffer_GetAddr(bufferInfo->buffer)), info.size);
// 释放已完成写入的数据,index为对应输出队列下标。
ret = OH_VideoEncoder_FreeOutputBuffer(videoEnc, bufferInfo->index);
if (ret != AV_ERR_OK) {
// 异常处理。
}
14. (可选)调用OH_VideoEncoder_Flush()刷新编码器。
调用OH_VideoEncoder_Flush接口后,编码器仍处于运行态,但会清除编码器中缓存的输入和输出数据及参数集如H.264格式的PPS/SPS。
此时需要调用OH_VideoEncoder_Start接口重新开始编码。
std::unique_lock<std::shared_mutex> lock(codecMutex);
// 刷新编码器videoEnc。
int32_t ret = OH_VideoEncoder_Flush(videoEnc);
if (ret != AV_ERR_OK) {
// 异常处理。
}
inQueue.Flush();
outQueue.Flush();
// 重新开始编码。
ret = OH_VideoEncoder_Start(videoEnc);
if (ret != AV_ERR_OK) {
// 异常处理。
}
15. (可选)调用OH_VideoEncoder_Reset()重置编码器。
调用OH_VideoEncoder_Reset接口后,编码器将回到初始化的状态,需要调用OH_VideoEncoder_Configure接口和OH_VideoEncoder_Prepare接口重新配置。
std::unique_lock<std::shared_mutex> lock(codecMutex);
// 重置编码器videoEnc。
int32_t ret = OH_VideoEncoder_Reset(videoEnc);
if (ret != AV_ERR_OK) {
// 异常处理。
}
inQueue.Flush();
outQueue.Flush();
// 重新配置编码器参数。
OH_AVFormat *format = OH_AVFormat_Create();
ret = OH_VideoEncoder_Configure(videoEnc, format);
if (ret != AV_ERR_OK) {
// 异常处理。
}
OH_AVFormat_Destroy(format);
// 编码器重新就绪。
ret = OH_VideoEncoder_Prepare(videoEnc);
if (ret != AV_ERR_OK) {
// 异常处理。
}
17. 调用OH_VideoEncoder_Destroy()销毁编码器实例,释放资源。
说明
- 不能在回调函数中调用;
- 执行该步骤之后,需要开发者将videoEnc指向nullptr,防止野指针导致程序错误。
std::unique_lock<std::shared_mutex> lock(codecMutex);
// 释放nativeWindow实例。
if(nativeWindow != nullptr){
OH_NativeWindow_DestroyNativeWindow(nativeWindow);
nativeWindow = nullptr;
}
// 调用OH_VideoEncoder_Destroy,注销编码器。
int32_t ret = AV_ERR_OK;
if (videoEnc != nullptr) {
ret = OH_VideoEncoder_Destroy(videoEnc);
videoEnc = nullptr;
}
if (ret != AV_ERR_OK) {
// 异常处理。
}
inQueue.Flush();
outQueue.Flush();
3.4.5 Buffer模式
参考以下示例代码,开发者可以完成Buffer模式下视频编码的全流程。此处以YUV文件输入,编码成H.264格式为例。
本模块目前仅支持异步模式的数据轮转。
1. 添加头文件。
#include <multimedia/player_framework/native_avcodec_videoencoder.h>
#include <multimedia/player_framework/native_avcapability.h>
#include <multimedia/player_framework/native_avcodec_base.h>
#include <multimedia/player_framework/native_avformat.h>
#include <multimedia/player_framework/native_avbuffer.h>
#include <fstream>
2. 创建编码器实例。
// 通过codec name创建编码器,应用有特殊需求,比如选择支持某种分辨率规格的编码器,可先查询capability,再根据codec name创建编码器。
OH_AVCapability *capability = OH_AVCodec_GetCapability(OH_AVCODEC_MIMETYPE_VIDEO_AVC, true);
const char *codecName = OH_AVCapability_GetName(capability);
OH_AVCodec *videoEnc = OH_VideoEncoder_CreateByName(codecName);
// 通过MIME TYPE创建编码器,只能创建系统推荐的特定编解码器。
// 涉及创建多路编解码器时,优先创建硬件编码器实例,硬件资源不够时再创建软件编码器实例。
OH_AVCodec *videoEnc = OH_VideoEncoder_CreateByMime(OH_AVCODEC_MIMETYPE_VIDEO_AVC);
3. 调用OH_VideoEncoder_RegisterCallback()设置回调函数。
注册回调函数指针集合OH_AVCodecCallback,包括:
- OH_AVCodecOnError 编码器运行错误,返回的错误码详情请参见OH_AVCodecOnError;
- OH_AVCodecOnStreamChanged 码流信息变化,如格式变化等;
- OH_AVCodecOnNeedInputBuffer 运行过程中需要新的输入数据,即编码器已准备好,可以输入YUV/RGB数据;
- OH_AVCodecOnNewOutputBuffer 运行过程中产生了新的输出数据,即编码完成。
开发者可以通过处理该回调报告的信息,确保编码器正常运转。
回调函数的具体实现可参考示例工程。
bool isFirstFrame = true;
int32_t qpAverage = 20;
double mseValue = 0.0;
// 编码异常回调OH_AVCodecOnError实现。
static void OnError(OH_AVCodec *codec, int32_t errorCode, void *userData)
{
// 回调的错误码由开发者判断处理。
(void)codec;
(void)errorCode;
(void)userData;
}
// 编码数据流变化回调OH_AVCodecOnStreamChanged实现。
static void OnStreamChanged(OH_AVCodec *codec, OH_AVFormat *format, void *userData)
{
// Buffer模式下,该回调函数无作用。
(void)codec;
(void)format;
(void)userData;
}
// 编码输入回调OH_AVCodecOnNeedInputBuffer实现。
static void OnNeedInputBuffer(OH_AVCodec *codec, uint32_t index, OH_AVBuffer *buffer, void *userData)
{
// 获取视频宽、高跨距。
if (isFirstFrame) {
OH_AVFormat *format = OH_VideoEncoder_GetInputDescription(codec);
OH_AVFormat_GetIntValue(format, OH_MD_KEY_VIDEO_STRIDE, &widthStride);
OH_AVFormat_GetIntValue(format, OH_MD_KEY_VIDEO_SLICE_HEIGHT, &heightStride);
OH_AVFormat_Destroy(format);
isFirstFrame = false;
}
// 输入帧的数据buffer和对应的index送入inQueue队列。
(void)codec;
(void)userData;
inQueue.Enqueue(std::make_shared<CodecBufferInfo>(index, buffer));
}
// 编码输出回调OH_AVCodecOnNewOutputBuffer实现
static void OnNewOutputBuffer(OH_AVCodec *codec, uint32_t index, OH_AVBuffer *buffer, void *userData)
{
// 获取视频帧的平均量化参数,平方误差
OH_AVFormat *format = OH_AVBuffer_GetParameter(buffer);
OH_AVFormat_GetIntValue(format, OH_MD_KEY_VIDEO_ENCODER_QP_AVERAGE, &qpAverage);
OH_AVFormat_GetDoubleValue(format, OH_MD_KEY_VIDEO_ENCODER_MSE, &mseValue);
OH_AVFormat_Destroy(format);
// 完成帧的数据buffer和对应的index送入outQueue队列
(void)userData;
outQueue.Enqueue(std::make_shared<CodecBufferInfo>(index, buffer));
}
// 配置异步回调,调用OH_VideoEncoder_RegisterCallback接口。
OH_AVCodecCallback cb = {&OnError, &OnStreamChanged, &OnNeedInputBuffer, &OnNewOutputBuffer};
int32_t ret = OH_VideoEncoder_RegisterCallback(videoEnc, cb, nullptr);
if (ret != AV_ERR_OK) {
// 异常处理。
}
说明
在回调函数中,对数据队列进行操作时,需要注意多线程同步的问题。
4. 调用OH_VideoEncoder_Configure()配置编码器。
OH_AVFormat *format = OH_AVFormat_Create();
// 写入format。
OH_AVFormat_SetIntValue(format, OH_MD_KEY_WIDTH, width); // 必须配置。
OH_AVFormat_SetIntValue(format, OH_MD_KEY_HEIGHT, height); // 必须配置。
OH_AVFormat_SetIntValue(format, OH_MD_KEY_PIXEL_FORMAT, pixelFormat); // 必须配置。
// 配置编码器。
int32_t ret = OH_VideoEncoder_Configure(videoEnc, format);
if (ret != AV_ERR_OK) {
// 异常处理。
}
OH_AVFormat_Destroy(format);
5. 调用OH_VideoEncoder_Prepare()编码器就绪。
该接口将在编码器运行前进行一些数据的准备工作。
ret = OH_VideoEncoder_Prepare(videoEnc);
if (ret != AV_ERR_OK) {
// 异常处理。
}
6. 调用OH_VideoEncoder_Start()启动编码器,进入运行态。
启动编码器后,回调函数将开始响应事件。所以,需要先配置输入文件、输出文件。
// 配置待编码文件路径。
std::string_view inputFilePath = "/*yourpath*.yuv";
std::string_view outputFilePath = "/*yourpath*.h264";
std::unique_ptr<std::ifstream> inputFile = std::make_unique<std::ifstream>();
std::unique_ptr<std::ofstream> outputFile = std::make_unique<std::ofstream>();
inputFile->open(inputFilePath.data(), std::ios::in | std::ios::binary);
outputFile->open(outputFilePath.data(), std::ios::out | std::ios::binary | std::ios::ate);
// 启动编码器,开始编码。
int32_t ret = OH_VideoEncoder_Start(videoEnc);
if (ret != AV_ERR_OK) {
// 异常处理。
}
7. (可选)在运行过程中动态配置编码器参数。
OH_AVFormat *format = OH_AVFormat_Create();
// 支持动态请求IDR帧
OH_AVFormat_SetIntValue(format, OH_MD_KEY_REQUEST_I_FRAME, true);
// 支持动态重置比特率
int64_t bitRate = 2000000;
OH_AVFormat_SetLongValue(format, OH_MD_KEY_BITRATE, bitRate);
// 支持动态重置视频帧速率
double frameRate = 60.0;
OH_AVFormat_SetDoubleValue(format, OH_MD_KEY_FRAME_RATE, frameRate);
int32_t ret = OH_VideoEncoder_SetParameter(videoEnc, format);
if (ret != AV_ERR_OK) {
// 异常处理
}
OH_AVFormat_Destroy(format);
8. 调用OH_VideoEncoder_PushInputBuffer()写入编码图像。
送入输入队列进行编码,以下示例中:
- buffer:回调函数OnNeedInputBuffer传入的参数,可以通过OH_AVBuffer_GetAddr接口得到共享内存地址的指针;
- index:回调函数OnNeedInputBuffer传入的参数,与buffer唯一对应的标识;
- flags:缓冲区标记的类别,请参考OH_AVCodecBufferFlags;
- widthStride: 获取到的buffer数据的跨距。
std::shared_ptr<CodecBufferInfo> bufferInfo = inQueue.Dequeue();
std::shared_lock<std::shared_mutex> lock(codecMutex);
if (bufferInfo == nullptr || !bufferInfo->isValid) {
// 异常处理。
}
// 写入图像数据。
if (widthStride == width) {
// 处理文件流得到帧的长度,再将需要编码的数据写入到对应index的buffer中。
int32_t frameSize = width * height * 3 / 2; // NV12像素格式下,每帧数据大小的计算公式。
inputFile->read(reinterpret_cast<char *>(OH_AVBuffer_GetAddr(bufferInfo->buffer)), frameSize);
} else {
// 如果跨距不等于宽,需要开发者按照跨距进行偏移,具体可参考以下示例。
}
// 配置buffer info信息。
OH_AVCodecBufferAttr info;
info.size = frameSize;
info.offset = 0;
info.pts = 0;
info.flags = flags;
int32_t ret = OH_AVBuffer_SetBufferAttr(bufferInfo->buffer, &info);
if (ret != AV_ERR_OK) {
// 异常处理。
}
// 配置buffer 随帧信息。
// 值由开发者决定。
int32_t isIFrame;
OH_AVFormat *parameter = OH_AVBuffer_GetParameter(bufferInfo->buffer);
OH_AVFormat_SetIntValue(parameter, OH_MD_KEY_REQUEST_I_FRAME, isIFrame);
ret = OH_AVBuffer_SetParameter(bufferInfo->buffer, parameter);
if (ret != AV_ERR_OK) {
// 异常处理。
}
OH_AVFormat_Destroy(parameter);
// 送入编码输入队列进行编码,index为对应输入队列的下标。
ret = OH_VideoEncoder_PushInputBuffer(videoEnc, bufferInfo->index);
if (ret != AV_ERR_OK) {
// 异常处理。
}
跨距进行偏移,以NV12图像为例,示例如下:
以NV12图像为例,width、height、wStride、hStride图像排布参考下图:
- OH_MD_KEY_VIDEO_PIC_WIDTH表示width;
- OH_MD_KEY_VIDEO_PIC_HEIGHT表示height;
- OH_MD_KEY_VIDEO_STRIDE表示wStride;
- OH_MD_KEY_VIDEO_SLICE_HEIGHT表示hStride。
添加头文件。
#include <string.h>
使用示例:
struct Rect // 源内存区域的宽、高,由开发者自行设置。
{
int32_t width;
int32_t height;
};
struct DstRect // 目标内存区域的宽、高跨距,通过接口OH_VideoEncoder_GetInputDescription获取。
{
int32_t wStride;
int32_t hStride;
};
struct SrcRect // 源内存区域的宽、高跨距,由开发者自行设置。
{
int32_t wStride;
int32_t hStride;
};
Rect rect = {320, 240};
DstRect dstRect = {320, 256};
SrcRect srcRect = {320, 250};
uint8_t* dst = new uint8_t[dstRect.hStride * dstRect.wStride * 3 / 2]; // 目标内存区域的指针。
uint8_t* src = new uint8_t[srcRect.hStride * srcRect.wStride * 3 / 2]; // 源内存区域的指针。
uint8_t* dstTemp = dst;
uint8_t* srcTemp = src;
// Y 将Y区域的源数据复制到另一个区域的目标数据中。
for (int32_t i = 0; i < rect.height; ++i) {
//将源数据的一行数据复制到目标数据的一行中。
memcpy(dstTemp, srcTemp, rect.width);
// 更新源数据和目标数据的指针,进行下一行的复制。每更新一次源数据和目标数据的指针都向下移动一个wStride。
dstTemp += dstRect.wStride;
srcTemp += srcRect.wStride;
}
// padding。
// 更新源数据和目标数据的指针,指针都向下移动一个padding。
dstTemp += (dstRect.hStride - rect.height) * dstRect.wStride;
srcTemp += (srcRect.hStride - rect.height) * srcRect.wStride;
rect.height >>= 1;
// UV 将UV区域的源数据复制到另一个区域的目标数据中。
for (int32_t i = 0; i < rect.height; ++i) {
memcpy(dstTemp, srcTemp, rect.width);
dstTemp += dstRect.wStride;
srcTemp += srcRect.wStride;
}
delete[] dst;
dst = nullptr;
delete[] src;
src = nullptr;
硬件编码在处理buffer数据时(推送数据前),需要开发者拷贝宽、高对齐后的图像数据到输入回调的AVbuffer中。
一般需要获取数据的宽、高、跨距、像素格式来保证编码输入数据被正确的处理。
具体实现请参考:Buffer模式的步骤3-调用OH_VideoEncoder_RegisterCallback接口设置回调函数来获取数据的宽、高、跨距、像素格式。
9. 通知编码器结束。
以下示例中:
- index:回调函数OnNeedInputBuffer传入的参数,与buffer唯一对应的标识;
- buffer:回调函数OnNeedInputBuffer传入的参数,可以通过OH_AVBuffer_GetAddr接口得到共享内存地址的指针。
与“步骤-8. 写入编码图像”一样,使用同一个接口OH_VideoEncoder_PushInputBuffer,通知编码器输入结束,需要将flag标识成AVCODEC_BUFFER_FLAGS_EOS。
std::shared_ptr<CodecBufferInfo> bufferInfo = inQueue.Dequeue();
std::shared_lock<std::shared_mutex> lock(codecMutex);
if (bufferInfo == nullptr || !bufferInfo->isValid) {
// 异常处理。
}
OH_AVCodecBufferAttr info;
info.size = 0;
info.offset = 0;
info.pts = 0;
info.flags = AVCODEC_BUFFER_FLAGS_EOS;
int32_t ret = OH_AVBuffer_SetBufferAttr(bufferInfo->buffer, &info);
if (ret != AV_ERR_OK) {
// 异常处理。
}
ret = OH_VideoEncoder_PushInputBuffer(videoEnc, bufferInfo->index);
if (ret != AV_ERR_OK) {
// 异常处理。
}
10. 调用OH_VideoEncoder_FreeOutputBuffer()释放编码帧。
std::shared_ptr<CodecBufferInfo> bufferInfo = outQueue.Dequeue();
std::shared_lock<std::shared_mutex> lock(codecMutex);
if (bufferInfo == nullptr || !bufferInfo->isValid) {
// 异常处理。
}
// 获取编码后信息。
OH_AVCodecBufferAttr info;
int32_t ret = OH_AVBuffer_GetBufferAttr(bufferInfo->buffer, &info);
if (ret != AV_ERR_OK) {
// 异常处理。
}
// 将编码完成帧数据buffer写入到对应输出文件中。
outputFile->write(reinterpret_cast<char *>(OH_AVBuffer_GetAddr(bufferInfo->buffer)), info.size);
// 释放已完成写入的数据,index为对应输出队列的下标。
ret = OH_VideoEncoder_FreeOutputBuffer(videoEnc, bufferInfo->index);
if (ret != AV_ERR_OK) {
// 异常处理。
}
后续流程(包括刷新编码器、重置编码器、停止编码器、销毁编码器)与Surface模式一致,请参考Surface模式的步骤14-17。
3.5 视频解码
开发步骤详见官方文档:
视频解码-音视频编解码-AVCodec Kit(音视频编解码服务)-媒体 - 华为HarmonyOS开发者
4 媒体数据封装与解析
4.1 媒体数据封装
可以调用本模块的Native API接口,完成音视频封装,即将音频、视频等编码后的媒体数据,按一定的格式存储到文件里。
如果需要对HDRVivid视频码流进行封装,需要配置MimeType为H265 (OH_AVCODEC_MIMETYPE_VIDEO_HEVC),本功能从API version 11开始支持。
适用场景
录像、录音
保存录像、录音文件时,需要先对音视频流进行编码,再封装为文件。
音视频编辑
完成编辑后的音视频,需要封装为文件。
音视频转码
转码后,保存文件时需要封装。
4.1.1 开发指导
说明
如果调用封装模块写本地文件,需要向用户申请授权:ohos.permission.READ_MEDIA, ohos.permission.WRITE_MEDIA。
4.1.2 在 CMake 脚本中链接动态库
target_link_libraries(sample PUBLIC libnative_media_avmuxer.so)
target_link_libraries(sample PUBLIC libnative_media_core.so)
4.1.3 开发步骤
参考以下示例代码,完成音视频封装的全流程。以封装mp4格式的音视频文件为例。
不同的封装格式需要配置的key请参考AVCodec支持的格式。
1. 添加头文件。
#include <multimedia/player_framework/native_avmuxer.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>
2. 调用OH_AVMuxer_Create()创建封装器实例对象。
// 设置封装格式为mp4。
OH_AVOutputFormat format = AV_OUTPUT_FORMAT_MPEG_4;
// 以读写方式创建fd。
int32_t fd = open("test.mp4", O_CREAT | O_RDWR | O_TRUNC, S_IRUSR | S_IWUSR);
OH_AVMuxer *muxer = OH_AVMuxer_Create(fd, format);
3. (可选)调用OH_AVMuxer_SetRotation()设置旋转角度。
// 旋转角度,视频画面需要旋转的时候设置。
OH_AVMuxer_SetRotation(muxer, 0);
4. 添加文件级数据。
OH_AVFormat *format = OH_AVFormat_Create(); // 用OH_AVFormat_Create创建format。
OH_AVFormat_SetStringValue(format, OH_MD_KEY_CREATION_TIME, "2024-12-28T00:00:00:000000Z"); // 设置创建时间(使用ISO 8601标准的时间格式且为UTC时间)。
int ret = OH_AVMuxer_SetFormat(muxer, format); // 设置封装的format。
if (ret != AV_ERR_OK) {
// 设置format失败,未找到有效待写入的key数据。
}
OH_AVFormat_Destroy(format); // 销毁。
5. 添加音频轨。
方法一:用OH_AVFormat_Create创建format
int audioTrackId = -1;
uint8_t *buffer = ...; // 编码config data,如果没有可以不传。
size_t size = ...; // 编码config data的长度,根据实际情况配置。
OH_AVFormat *formatAudio = OH_AVFormat_Create(); // 用OH_AVFormat_Create创建format,这里以封装44100Hz采样率、2声道的AAC-LC音频为例。
OH_AVFormat_SetStringValue(formatAudio, OH_MD_KEY_CODEC_MIME, OH_AVCODEC_MIMETYPE_AUDIO_AAC); // 必填。
OH_AVFormat_SetIntValue(formatAudio, OH_MD_KEY_AUD_SAMPLE_RATE, 44100); // 必填。
OH_AVFormat_SetIntValue(formatAudio, OH_MD_KEY_AUD_CHANNEL_COUNT, 2); // 必填。
OH_AVFormat_SetIntValue(formatAudio, OH_MD_KEY_PROFILE, AAC_PROFILE_LC); // 选填。
OH_AVFormat_SetBuffer(formatAudio, OH_MD_KEY_CODEC_CONFIG, buffer, size); // 选填。
int ret = OH_AVMuxer_AddTrack(muxer, &audioTrackId, formatAudio);
if (ret != AV_ERR_OK || audioTrackId < 0) {
// 音频轨添加失败。
}
OH_AVFormat_Destroy(formatAudio); // 销毁。
方法二:用OH_AVFormat_CreateAudioFormat创建format
int audioTrackId = -1;
uint8_t *buffer = ...; // 编码config data,如果没有可以不传。
size_t size = ...; // 编码config data的长度,根据实际情况配置。
OH_AVFormat *formatAudio = OH_AVFormat_CreateAudioFormat(OH_AVCODEC_MIMETYPE_AUDIO_AAC, 44100, 2);
OH_AVFormat_SetIntValue(formatAudio, OH_MD_KEY_PROFILE, AAC_PROFILE_LC); // 选填。
OH_AVFormat_SetBuffer(formatAudio, OH_MD_KEY_CODEC_CONFIG, buffer, size); // 选填。
int ret = OH_AVMuxer_AddTrack(muxer, &audioTrackId, formatAudio);
if (ret != AV_ERR_OK || audioTrackId < 0) {
// 音频轨添加失败。
}
OH_AVFormat_Destroy(formatAudio); // 销毁。
6. 添加视频轨。
方法一:用OH_AVFormat_Create创建format
int videoTrackId = -1;
uint8_t *buffer = ...; // 编码config data,如果没有可以不传。
size_t size = ...; // 编码config data的长度,根据实际情况配置。
OH_AVFormat *formatVideo = OH_AVFormat_Create();
OH_AVFormat_SetStringValue(formatVideo, OH_MD_KEY_CODEC_MIME, OH_AVCODEC_MIMETYPE_VIDEO_AVC); // 必填。
OH_AVFormat_SetIntValue(formatVideo, OH_MD_KEY_WIDTH, 1280); // 必填。
OH_AVFormat_SetIntValue(formatVideo, OH_MD_KEY_HEIGHT, 720); // 必填。
OH_AVFormat_SetBuffer(formatVideo, OH_MD_KEY_CODEC_CONFIG, buffer, size); // 非必须。
int ret = OH_AVMuxer_AddTrack(muxer, &videoTrackId, formatVideo);
if (ret != AV_ERR_OK || videoTrackId < 0) {
// 视频轨添加失败。
}
OH_AVFormat_Destroy(formatVideo); // 销毁。
方法二:用OH_AVFormat_CreateVideoFormat创建format
int videoTrackId = -1;
uint8_t *buffer = ...; // 编码config data,如果没有可以不传。
size_t size = ...; // 编码config data的长度,根据实际情况配置。
OH_AVFormat *formatVideo = OH_AVFormat_CreateVideoFormat(OH_AVCODEC_MIMETYPE_VIDEO_AVC, 1280, 720);
OH_AVFormat_SetBuffer(formatVideo, OH_MD_KEY_CODEC_CONFIG, buffer, size); // 非必须。
int ret = OH_AVMuxer_AddTrack(muxer, &videoTrackId, formatVideo);
if (ret != AV_ERR_OK || videoTrackId < 0) {
// 视频轨添加失败。
}
OH_AVFormat_Destroy(formatVideo); // 销毁。
7. 添加封面轨。
方法一:用OH_AVFormat_Create创建format
int coverTrackId = -1;
OH_AVFormat *formatCover = OH_AVFormat_Create();
OH_AVFormat_SetStringValue(formatCover, OH_MD_KEY_CODEC_MIME, OH_AVCODEC_MIMETYPE_IMAGE_JPG);
OH_AVFormat_SetIntValue(formatCover, OH_MD_KEY_WIDTH, 1280);
OH_AVFormat_SetIntValue(formatCover, OH_MD_KEY_HEIGHT, 720);
int ret = OH_AVMuxer_AddTrack(muxer, &coverTrackId, formatCover);
if (ret != AV_ERR_OK || coverTrackId < 0) {
// 添加封面失败。
}
OH_AVFormat_Destroy(formatCover); // 销毁。
方法二:用OH_AVFormat_CreateVideoFormat创建format
int coverTrackId = -1;
OH_AVFormat *formatCover = OH_AVFormat_CreateVideoFormat(OH_AVCODEC_MIMETYPE_IMAGE_JPG, 1280, 720);
int ret = OH_AVMuxer_AddTrack(muxer, &coverTrackId, formatCover);
if (ret != AV_ERR_OK || coverTrackId < 0) {
// 添加封面失败。
}
OH_AVFormat_Destroy(formatCover); // 销毁。
8. 调用OH_AVMuxer_Start()开始封装。
// 调用start,写封装文件头。start后,不能设置媒体参数、不能添加音视频轨。
if (OH_AVMuxer_Start(muxer) != AV_ERR_OK) {
// 异常处理。
}
9. 调用OH_AVMuxer_WriteSampleBuffer(),写入封装数据。
封装数据包括视频、音频、封面等数据。
// start后,才能开始写入数据。
int size = ...;
OH_AVBuffer *sample = OH_AVBuffer_Create(size); // 创建AVBuffer。
// 通过OH_AVBuffer_GetAddr(sample)往sampleBuffer里写入数据参考OH_AVBuffer的使用方法。
// 封装封面,必须一次写完一张图片。
// 创建buffer info。
OH_AVCodecBufferAttr info = {0};
info.pts = ...; // 当前数据的开始播放的时间,单位微秒,相对时间。
info.size = size; // 当前数据的长度。
info.offset = 0; // 偏移,一般为0。
info.flags |= AVCODEC_BUFFER_FLAGS_SYNC_FRAME; // 当前数据的标志。具体参考OH_AVCodecBufferFlags。
info.flags |= AVCODEC_BUFFER_FLAGS_CODEC_DATA; // 当annex-b格式的avc包含codec config的标志。
OH_AVBuffer_SetBufferAttr(sample, &info); // 设置buffer的属性。
int trackId = audioTrackId; // 选择写的音视频轨。
int ret = OH_AVMuxer_WriteSampleBuffer(muxer, trackId, sample);
if (ret != AV_ERR_OK) {
// 异常处理。
}
10. 调用OH_AVMuxer_Stop(),停止封装。
// 调用stop,写封装文件尾。stop后不能写入媒体数据。
if (OH_AVMuxer_Stop(muxer) != AV_ERR_OK) {
// 异常处理。
}
- 调用OH_AVMuxer_Destroy()销毁实例,释放资源。
注意不能重复销毁,否则将会导致程序崩溃。
if (OH_AVMuxer_Destroy(muxer) != AV_ERR_OK) {
// 异常处理。
}
muxer = NULL;
close(fd); // 关闭文件描述符。
4.2 媒体数据解析
以调用本模块的Native API接口,完成媒体数据的解封装相关操作,即从比特流数据中取出音频、视频、字幕等媒体sample,获得DRM相关信息。
适用场景:
-
播放
播放媒体文件时,需要先对媒体流进行解封装,然后使用解封装获取的sample进行解码和播放。
-
音视频编辑
编辑媒体文件时,需要先对媒体流进行解封装,获取到指定sample进行编辑。
-
媒体文件格式转换(转封装)
媒体文件格式转换时,需要先对媒体流进行解封装,然后按需将媒体流封装至新的格式文件内。
4.2.1 开发指导
说明
- 调用解封装能力解析网络播放路径,需要声明权限:ohos.permission.INTERNET
- 调用解封装能力解析本地文件,需要向用户申请授权:ohos.permission.READ_MEDIA
- 如果使用ResourceManager.getRawFd打开HAP资源文件描述符,使用方法请参考ResourceManager API参考
4.2.2 在 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)
4.2.3 开发步骤
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 资源实例(可选)。使用该方式前,需要先实现AVSourceReadAt接口函数实现。
// 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信息,可跳过此步)。
设置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信息后,也可主动调用获取DRM信息(uuid及对应pssh)接口。
DRM_MediaKeySystemInfo mediaKeySystemInfo;
OH_AVDemuxer_GetMediaKeySystemInfo(demuxer, &mediaKeySystemInfo);
在获取、解析DRM信息后,需创建对应DRM解决方案的MediaKeySystem、MediaKeySession,获取DRM许可证等。并根据需要设置音频解密配置(详见音频解码开发指南开发步骤第4步)、设置视频解密配置(详见视频解码开发指南开发步骤Surface模式第5步或Buffer模式第4步),实现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_FRAME | IDR帧或I帧。 |
AVCODEC_BUFFER_FLAGS_INCOMPLETE_FRAME | 非完整的sample,一般由于buffer过小,无法拷贝完整的sample。 |
AVCODEC_BUFFER_FLAGS_CODEC_DATA | 含参数集信息的帧。 |
AVCODEC_BUFFER_FLAGS_DISCARD | 可丢弃的帧。 |
// 按照指定size创建buffer,用于保存用户解封装得到的数据。
// buffer大小设置建议大于待获取的码流大小,示例中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 接口成功后,手动将实例置为nullptr,对同一实例重复调用 OH_AVSource_Destroy 会导致程序错误。
if (OH_AVSource_Destroy(source) != AV_ERR_OK) {
printf("destroy source pointer error");
}
source = nullptr;
// 需要用户调用 OH_AVDemuxer_Destroy 接口成功后,手动将实例置为nullptr,对同一实例重复调用 OH_AVDemuxer_Destroy 会导致程序错误。
if (OH_AVDemuxer_Destroy(demuxer) != AV_ERR_OK) {
printf("destroy demuxer pointer error");
}
demuxer = nullptr;
close(fd);
4.2.4 附表
1、文件级别属性支持范围
说明
正常解析时才可以获取对应属性数据;如果文件信息错误或缺失,将导致解析异常,无法获取数据。
数据类型及详细取值范围参考媒体数据键值对。
表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、轨道级别属性支持范围
说明
正常解析时才可以获取对应属性数据;如果文件信息错误或缺失,将导致解析异常,无法获取数据。
数据类型及详细取值范围参考媒体数据键值对。
表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_ADTS | aac格式的键,只针对 aac 码流使用 | - | √ | - |
OH_MD_KEY_BITS_PER_CODED_SAMPLE | 音频流每个编码样本位数的键 | - | √ | - |