往期推文全新看点(文中附带全新鸿蒙5.0全栈学习笔录)
✏️ 鸿蒙应用开发与鸿蒙系统开发哪个更有前景?
✏️ 嵌入式开发适不适合做鸿蒙南向开发?看完这篇你就了解了~
✏️ 对于大前端开发来说,转鸿蒙开发究竟是福还是祸?
✏️ 鸿蒙岗位需求突增!移动端、PC端、IoT到底该怎么选?
✏️ 记录一场鸿蒙开发岗位面试经历~
✏️ 持续更新中……
概述
随着5G、光纤等网络技术的普及,终端显示能力进一步提升,“真实”、“沉浸”的超高清音视频体验时代已到来。高动态范围(High-Dynamic Range,简称HDR)作为超高清音视频产业的关键技术之一,比传统的标准动态范围(Standard Dynamic Range,简称SDR)拥有更广的色域和更高的动态范围(动态范围指的是亮度最大值和最小值的比值),为图像保留更多细节。超高清HDR加上宽色域能让视频亮度层次和色彩的呈现更真实、更自然。
HDRVivid是由华为主导开发的高动态范围视频技术标准,中文名为“菁彩影像”。它受亮度、对比度、色深、色域等因素影响,是一种提高画面亮度及对比度的画面处理技术。 其技术特点与优势为:
- 高动态范围:HDRVivid通过更广的色域和更高的动态范围,使得画面能够容纳更多的细节和色彩层次。与传统的SDR相比,HDRVivid的高光亮度是SDR的40倍,它能够同时呈现更深的黑色和更亮的白色,让画面的亮部和暗部细节更加清晰。
- 色彩丰富:HDRVivid支持10bit/12bit的色深,使得色彩过渡更加平滑,色彩表现更加细腻。色域面积相对BT.709标准增加了70%**,**色域越大证明能显示的颜色越多,HDRVivid能够呈现更加真实的色彩,让用户感受到更加丰富的视觉体验。
- 智能优化:作为一种动态HDR标准,HDRVivid能够根据显示硬件和视频场景,逐帧动态优化画面的亮度、对比度和色彩。HDRVivid使用动态元数据和智能映射引擎,将母版颜色容积映射到显示设备上,这种映射使得创作者的意图在不同显示设备上都得以保留,并且呈现最优画质效果,解决了传统制作和显示过程中色彩信息和亮度细节丢失的问题。
目前涉及到的HDR场景包括:HDR视频录制、HDR视频播放、视频转码(HDR2SDR)、HDR图片拍照等。本文旨在向开发者介绍,如何使用HarmonyOS的系统能力进行常用场景的开发:
- HDRVivid视频录制:主要使用场景为有HDRVivid视频录制需求的应用。
- HDRVivid视频播放:主要使用场景为有播放HDRVivid视频片源需求的应用。
- HDRVivid视频转码成SDR视频:主要使用场景为不支持HDRVivid视频播放的场景或其他特殊情况。
HDRVivid视频录制
HDRVivid视频录制是一种先进的视频录制技术,它基于动态元数据对亮度、对比度和颜色进行逐个场景优化,为显示终端提供更加准确的动态映射方式。
HDRVivid视频录制的开发和 主流格式视频录制 的开发基本一致,差异点体现在部分参数的配置上。
HarmonyOS上开发录制视频的功能一般有两个方案:
- 使用Camera配合系统级音视频录制类AVRecorder进行录制。
- 使用Camera配合系统编码器进行录制。
使用Camera+AVRecorder录制
实现原理
应用通过调用 AVRecorder 实现视频录制时,先通过Camera接口调用相机服务,通过视频HDI捕获图像数据送显至应用,同时送至AVRecorder的录制服务,录制服务将图像数据编码后封装至文件中,实现视频录制功能。流程图如下:
使用 Camera + AVRecorder 录制HDRVivid视频,与录制普通视频的区别主要在于:
-
AVRecorder
- AVRecoder需要配置isHdr参数为true,对应的编码格式必须为video/hevc。
-
Camera
- 相机创建video output实例时,选择yuv 10bit profile(CAMERA_FORMAT_YCRCB_P010)。
- HDR录像需要相机支持视频防抖功能。
- 相机会话配置颜色空间为BT2020_HLG_LIMIT。
开发步骤
针对以上四点不同,开发视频录制功能时,可参考以下步骤(详细开发步骤可参考 HDR Vivid相机录像(ArkTS) ):
- 调用 createAVRecorder() 创建AVRecorder实例。
// 创建AVRecorder实例
try {
this.avRecorder = await media.createAVRecorder();
} catch (error) {
let err = error as BusinessError;
Logger.error(TAG, `createAVRecorder call failed. error code: ${err.code}`);
}
- 配置预览流与录像输出流的分辨率为16:9, AVRecorderProfile 参数中的变量isHdr为True,videoCodec为VIDEO_HEVC格式,表示要录制的是HDRVivid视频。
let videoSize: camera.Size = {
width: 1280,
height: 720
}
// 设置配置参数AVRecorderProfile中的变量isHdr和videoCodec
let avRecorderProfile: media.AVRecorderProfile = {
// ...
videoCodec: media.CodecMimeType.VIDEO_HEVC,
// ...
isHdr: true
};
- 调用 createVideoOutput() 创建video output实例,选择yuv 10bit profile。
let videoProfile: undefined | camera.VideoProfile = videoProfilesArray.find((profile: camera.VideoProfile) => {
return profile.size.width === videoSize.width && profile.size.height === videoSize.height &&
profile.format === camera.CameraFormat.CAMERA_FORMAT_YCRCB_P010;
});
let previewProfile: undefined | camera.Profile = previewProfilesArray.find((profile: camera.Profile) => {
return profile.format === camera.CameraFormat.CAMERA_FORMAT_YCRCB_P010 &&
profile.size.width === videoSize.width && profile.size.height == videoSize.height
});
// ...
let avRecorderConfig: media.AVRecorderConfig = {
audioSourceType: media.AudioSourceType.AUDIO_SOURCE_TYPE_MIC,
videoSourceType: media.VideoSourceType.VIDEO_SOURCE_TYPE_SURFACE_YUV,
profile: avRecorderProfile,
url: this.url,
rotation: 0,
location: {
latitude: 30,
longitude: 130
}
};
// ...
try {
await this.avRecorder.prepare(avRecorderConfig);
} catch (error) {
let err = error as BusinessError;
Logger.error(TAG, `prepare call failed. error code: ${err.code}`);
}
let videoSurfaceId: string | undefined = undefined;
try {
videoSurfaceId = await this.avRecorder.getInputSurface();
} catch (error) {
let err = error as BusinessError;
Logger.error(TAG, `getInputSurface call failed. error code: ${err.code}`);
}
if (videoSurfaceId === undefined) {
return;
}
try {
this.videoOutput = this.cameraManager.createVideoOutput(videoProfile, videoSurfaceId);
} catch (error) {
let err = error as BusinessError;
Logger.error(TAG, `Failed to create the videoOutput instance. error: ${JSON.stringify(err)}`);
}
- 创建并配置普通录像模式( VideoSession )的相机会话,同时设置视频防抖及色彩空间属性。
// 创建并配置相机会话
try {
this.captureSession = this.cameraManager.createSession(camera.SceneMode.NORMAL_VIDEO) as camera.VideoSession;
} catch (error) {
// ...
}
// ...
try {
this.captureSession.beginConfig();
} catch (error) {
// ...
}
// ...
try {
await this.captureSession.commitConfig();
} catch (error) {
// ...
}
let mode: camera.VideoStabilizationMode = camera.VideoStabilizationMode.AUTO;
// 查询是否支持视频防抖
let isSupported: boolean = false;
try {
isSupported = this.captureSession.isVideoStabilizationModeSupported(mode);
Logger.info(TAG, `isVideoStabilizationModeSupported: ${JSON.stringify(isSupported)}`);
} catch (error) {
// 失败返回错误码error.code并处理
let err = error as BusinessError;
Logger.error(`The isVideoStabilizationModeSupported call failed. error code: ${err.code}`);
}
if (isSupported) {
// 设置视频防抖
this.captureSession.setVideoStabilizationMode(mode);
let activeVideoStabilizationMode = this.captureSession.getActiveVideoStabilizationMode();
Logger.info(`activeVideoStabilizationMode: ${activeVideoStabilizationMode}`);
} else {
Logger.error(`videoStabilizationMode: ${mode} is not support`);
}
// Camera Session配置颜色空间为BT2020_HLG_LIMIT
if (isSupported) {
let colorSpace: colorSpaceManager.ColorSpace = colorSpaceManager.ColorSpace.BT2020_HLG_LIMIT;
let colorSpaces: Array<colorSpaceManager.ColorSpace> = [];
try {
colorSpaces = this.captureSession.getSupportedColorSpaces();
} catch (error) {
let err = error as BusinessError;
Logger.error(`The getSupportedColorSpaces call failed. error code: ${err.code}`);
}
let isSupportedColorSpaces = colorSpaces.indexOf(colorSpace) >= 0;
if (isSupportedColorSpaces) {
Logger.info(`setColorSpace: ${colorSpace}`);
try {
this.captureSession.setColorSpace(colorSpace);
} catch (error) {
let err = error as BusinessError;
Logger.error(`The setColorSpace call failed, error code: ${err.code}`);
}
let activeColorSpace: colorSpaceManager.ColorSpace = this.captureSession.getActiveColorSpace();
Logger.info(`activeColorSpace: ${activeColorSpace}`);
} else {
Logger.error(`colorSpace: ${colorSpace} is not support`);
}
}
try {
await this.captureSession.start();
} catch (error) {
let err = error as BusinessError;
Logger.error(TAG, `captureSession start error: ${JSON.stringify(err)}`);
}
- 启动video流。
this.videoOutput.start((err: BusinessError) => {
if (err) {
Logger.error(TAG, `Failed to start the video output. error: ${JSON.stringify(err)}`);
return;
}
Logger.info(TAG, 'Callback invoked to indicate the video output start success.');
});
- 启动recorder进行录制。
async startRecord() {
if (this.avRecorder) {
try {
await this.avRecorder.start();
} catch (error) {
let err = error as BusinessError;
Logger.error(TAG, `avRecorder start error: ${JSON.stringify(err)}`);
}
}
}
使用Camera+AVCodec录制
实现原理
应用通过调用 AVCodec 实现视频录制时,先通过Camera接口调用相机服务,通过视频HDI捕获图像数据送显至应用,同时送至AVCodec的编码模块将图像数据编码后封装至文件中,实现视频录制功能。流程图如下:
使用Camera+ AVCodec 录制HDRVivid视频,与录制普通视频的区别主要在于:
- AVCodec
- 视频编码器AVCodec需要选择HEVC格式,并配置profile为HEVC_PROFILE_MAIN_10的相机底层。
- 编码器AVCodec配置颜色相关参数为COLOR_PRIMARY_BT2020。
- Camera
- 相机在创建video output实例时,选择yuv 10bit profile。
- HDR录像需要相机支持视频防抖功能,并配置颜色空间为BT2020_HLG_LIMIT。
开发步骤
针对以上四点不同,使用Camera+AVCodec开发HDRVivid视频录制功能时,可参考以下步骤(详细开发步骤可参考 HDRVivid视频录制 ):
- 调用 OH_VideoEncoder_CreateByMime() 根据MIME类型创建创建HEVC格式视频编码器并初始化。
// 创建视频编码器并初始化
int32_t VideoEncoder::Create(const std::string &videoCodecMime)
{
encoder_ = OH_VideoEncoder_CreateByMime(OH_AVCODEC_MIMETYPE_VIDEO_HEVC);
CHECK_AND_RETURN_RET_LOG(encoder_ != nullptr, AVCODEC_SAMPLE_ERR_ERROR, "Create failed");
return AVCODEC_SAMPLE_ERR_OK;
}
- 配置HDRVivid相关参数,包括可选配置视频帧宽度、频帧高度、视频颜色格式,以及必须配置为HEVC_PROFILE_MAIN_10的 OH_HEVCProfile ,表示HEVC编码档次为10bit主档次。
int32_t VideoEncoder::Configure(const SampleInfo &sampleInfo)
{
// ...
// 配置HDRVivid相关参数
if (sampleInfo.isHDRVivid) {
OH_AVFormat_SetIntValue(format, OH_MD_KEY_PROFILE, sampleInfo.hevcProfile);
OH_AVFormat_SetIntValue(format, OH_MD_KEY_I_FRAME_INTERVAL, sampleInfo.iFrameInterval);
OH_AVFormat_SetIntValue(format, OH_MD_KEY_RANGE_FLAG, sampleInfo.rangFlag);
OH_AVFormat_SetIntValue(format, OH_MD_KEY_COLOR_PRIMARIES, sampleInfo.primary);
OH_AVFormat_SetIntValue(format, OH_MD_KEY_TRANSFER_CHARACTERISTICS, sampleInfo.transfer);
OH_AVFormat_SetIntValue(format, OH_MD_KEY_MATRIX_COEFFICIENTS, sampleInfo.matrix);
}
// ...
}
- 使用 OH_VideoEncoder_Configure() 配置编码器。
// 配置编码器
int ret = OH_VideoEncoder_Configure(encoder_, format);
- ArkTs侧调用createVideoOutput创建video output实例,选择yuv 10bit profile。
export function videoProfileCheck(cameraManager: camera.CameraManager,
cameraData: CameraDataModel): undefined | camera.VideoProfile {
let cameraDevices = cameraManager.getSupportedCameras();
// ...
let profiles: camera.CameraOutputCapability =
cameraManager.getSupportedOutputCapability(cameraDevices[0], camera.SceneMode.NORMAL_VIDEO);
// ...
let videoProfiles: Array<camera.VideoProfile> = profiles.videoProfiles;
// ...
let videoProfile: undefined | camera.VideoProfile = videoProfiles.find((profile: camera.VideoProfile) => {
if (cameraData.isHDRVivid) {
// ...
return profile.size.width === cameraData.cameraWidth &&
profile.size.height === cameraData.cameraHeight &&
profile.format === camera.CameraFormat.CAMERA_FORMAT_YCRCB_P010 &&
profile.frameRateRange.min === 1 &&
profile.frameRateRange.max === 30;
// ...
} else {
// ...
}
});
return videoProfile;
}
let videoProfile: undefined | camera.VideoProfile = videoProfileCheck(cameraManager, params);
if (!videoProfile) {
Logger.error(TAG, 'videoProfile is not found!');
return;
}
// ...
// 创建video output实例
encoderVideoOutput = cameraManager.createVideoOutput(videoProfile, params.surfaceId);
if (encoderVideoOutput === undefined) {
Logger.error(TAG, 'encoderVideoOutput is undefined');
return;
}
Logger.info(TAG, 'encoderVideoOutput success');
- 设置视频防抖及色彩空间。
function isVideoStabilizationModeSupported(session: camera.VideoSession, mode: camera.VideoStabilizationMode): boolean {
let isSupported: boolean = false;
try {
isSupported = session.isVideoStabilizationModeSupported(mode);
} catch (error) {
// 失败返回错误码error.code并处理
let err = error as BusinessError;
console.error(`The isVideoStabilizationModeSupported call failed. error code: ${err.code}`);
}
return isSupported;
}
function setVideoStabilizationMode(session: camera.VideoSession): boolean {
let mode: camera.VideoStabilizationMode = camera.VideoStabilizationMode.AUTO;
// 查询是否支持视频防抖
let isSupported: boolean = isVideoStabilizationModeSupported(session, mode);
if (isSupported) {
console.info(`setVideoStabilizationMode: ${mode}`);
// 设置视频防抖
session.setVideoStabilizationMode(mode);
let activeVideoStabilizationMode = session.getActiveVideoStabilizationMode();
console.info(`activeVideoStabilizationMode: ${activeVideoStabilizationMode}`);
} else {
console.info(`videoStabilizationMode: ${mode} is not support`);
}
return isSupported;
}
function getSupportedColorSpaces(session: camera.VideoSession): Array<colorSpaceManager.ColorSpace> {
let colorSpaces: Array<colorSpaceManager.ColorSpace> = [];
try {
colorSpaces = session.getSupportedColorSpaces();
} catch (error) {
let err = error as BusinessError;
console.error(`The getSupportedColorSpaces call failed. error code: ${err.code}`);
}
return colorSpaces;
}
// 设置色彩空间
function setColorSpaceBeforeCommitConfig(session: camera.VideoSession, isHdr: boolean): void {
let colorSpace: colorSpaceManager.ColorSpace = isHdr? colorSpaceManager.ColorSpace.BT2020_HLG_LIMIT : colorSpaceManager.ColorSpace.BT709_LIMIT;
let colorSpaces: Array<colorSpaceManager.ColorSpace> = getSupportedColorSpaces(session);
let isSupportedColorSpaces = colorSpaces.indexOf(colorSpace) >= 0;
if (isSupportedColorSpaces) {
console.info(`setColorSpace: ${colorSpace}`);
session.setColorSpace(colorSpace);
let activeColorSpace:colorSpaceManager.ColorSpace = session.getActiveColorSpace();
console.info(`activeColorSpace: ${activeColorSpace}`);
} else {
console.info(`colorSpace: ${colorSpace} is not support`);
}
}
- 创建并配置相机会话。
let XComponentPreviewProfile: camera.Profile | undefined = previewProfileCameraCheck(cameraManager, params)
if (XComponentPreviewProfile === undefined) {
Logger.error(TAG, 'XComponentPreviewProfile is not found');
return;
}
// ...
// 创建会话流
try {
videoSession = cameraManager.createSession(camera.SceneMode.NORMAL_VIDEO) as camera.VideoSession;
} catch (error) {
let err = error as BusinessError;
Logger.error(TAG, `Failed to create the session instance. error: ${JSON.stringify(err)}`);
}
// ...
// 开始配置会话
try {
videoSession.beginConfig();
} catch (error) {
// ...
}
try {
videoSession.addInput(cameraInput);
} catch (error) {
// ...
}
// 将XComponent预览流添加到会话中
try {
videoSession.addOutput(XComponentPreviewOutput);
} catch (error) {
// ...
}
try {
videoSession.addOutput(encoderVideoOutput);
} catch (error) {
// ...
}
try {
await videoSession.commitConfig();
} catch (error) {
// ...
}
// 设置视频防抖
if (setVideoStabilizationMode(videoSession)) {
// 设置色彩空间
setColorSpaceBeforeCommitConfig(videoSession, true);
}
try {
await videoSession.start();
} catch (error) {
// ...
}
// 启动视频输出流
encoderVideoOutput.start((err: BusinessError) => {
// ...
});
- 调用OH_AVMuxer_Create()创建AVMuxer封装器实例对象,设置封装格式及封装路径,配置HDRVivid相关参数。
// 创建封装器实例对象,设置封装格式为mp4
int32_t Muxer::Create(int32_t fd)
{
muxer_ = OH_AVMuxer_Create(fd, AV_OUTPUT_FORMAT_MPEG_4);
CHECK_AND_RETURN_RET_LOG(muxer_ != nullptr, AVCODEC_SAMPLE_ERR_ERROR, "Muxer create failed, fd: %{public}d", fd);
return AVCODEC_SAMPLE_ERR_OK;
}
int32_t Muxer::Config(SampleInfo &sampleInfo)
{
CHECK_AND_RETURN_RET_LOG(muxer_ != nullptr, AVCODEC_SAMPLE_ERR_ERROR, "Muxer is null");
OH_AVFormat *formatVideo = OH_AVFormat_CreateVideoFormat(sampleInfo.videoCodecMime.data(),
sampleInfo.videoWidth, sampleInfo.videoHeight);
CHECK_AND_RETURN_RET_LOG(formatVideo != nullptr, AVCODEC_SAMPLE_ERR_ERROR, "Create video format failed");
OH_AVFormat_SetDoubleValue(formatVideo, OH_MD_KEY_FRAME_RATE, sampleInfo.frameRate);
OH_AVFormat_SetIntValue(formatVideo, OH_MD_KEY_WIDTH, sampleInfo.videoWidth);
OH_AVFormat_SetIntValue(formatVideo, OH_MD_KEY_HEIGHT, sampleInfo.videoHeight);
OH_AVFormat_SetStringValue(formatVideo, OH_MD_KEY_CODEC_MIME, sampleInfo.videoCodecMime.data());
if (sampleInfo.isHDRVivid) {
OH_AVFormat_SetIntValue(formatVideo, OH_MD_KEY_VIDEO_IS_HDR_VIVID, 1);
OH_AVFormat_SetIntValue(formatVideo, OH_MD_KEY_RANGE_FLAG, sampleInfo.rangFlag);
OH_AVFormat_SetIntValue(formatVideo, OH_MD_KEY_COLOR_PRIMARIES, sampleInfo.primary);
OH_AVFormat_SetIntValue(formatVideo, OH_MD_KEY_TRANSFER_CHARACTERISTICS, sampleInfo.transfer);
OH_AVFormat_SetIntValue(formatVideo, OH_MD_KEY_MATRIX_COEFFICIENTS, sampleInfo.matrix);
}
int32_t ret = OH_AVMuxer_AddTrack(muxer_, &videoTrackId_, formatVideo);
CHECK_AND_RETURN_RET_LOG(ret == AV_ERR_OK, AVCODEC_SAMPLE_ERR_ERROR, "AddTrack failed");
OH_AVFormat_Destroy(formatVideo);
OH_AVMuxer_SetRotation(muxer_, sampleInfo.videoHeight > sampleInfo.videoWidth ? VERTICAL_ANGLE : HORIZONTAL_ANGLE);
return AVCODEC_SAMPLE_ERR_OK;
}
- 调用 OH_VideoEncoder_Start() 启动编码器。
// 启动编码器
int32_t VideoEncoder::Start()
{
CHECK_AND_RETURN_RET_LOG(encoder_ != nullptr, AVCODEC_SAMPLE_ERR_ERROR, "Encoder is null");
int ret = OH_VideoEncoder_Start(encoder_);
CHECK_AND_RETURN_RET_LOG(ret == AV_ERR_OK, AVCODEC_SAMPLE_ERR_ERROR, "Start failed, ret: %{public}d", ret);
return AVCODEC_SAMPLE_ERR_OK;
}
HDRVivid视频播放
要实现HDRVivid视频播放,通常需要在视频播放流程中进行特定的处理,这包括:视频文件解析、解码器配置和渲染处理。
相对于主流格式视频播放的开发,HDRVivid视频播放的开发与其流程基本一致,只在解码格式上有所区别。下面将从解码格式上来讲解在HarmonyOS上进行视频开发的主要两种方案:
- 使用系统播放器(已集成系统解码和渲染能力)进行开发。
- 基于系统解码和渲染能力开发应用级播放器。
使用系统播放器AVPlayer开发
实现原理
AVPlayer提供功能完善一体化播放能力,应用只需要提供流媒体来源,不负责数据解析和解码就可达成播放效果。
开发步骤
使用系统播放器 AVPlayer 开发视频播放功能,主要开发步骤为(详细开发步骤可参考使用AVPlayer播放视频(ArkTS)):
- 调用createAVPlayer() 创建AVPlayer实例,初始化进入idle状态并设置资源属性url。
// 创建AVPlayer实例
public initAVPlayer() {
media.createAVPlayer().then((player: media.AVPlayer) => {
if (player !== null) {
this.avPlayer = player;
// this.curSource.video为本地视频路径,this.curSource.url为网络视频路径
if (this.curSource.video) {
let fileDescriptor = this.context?.resourceManager.getRawFdSync(this.curSource.video);
if (fileDescriptor) {
let avFileDescriptor: media.AVFileDescriptor =
{ fd: fileDescriptor.fd, offset: fileDescriptor.offset, length: fileDescriptor.length };
this.avPlayer.fdSrc = avFileDescriptor;
}
} else {
this.avPlayer.url = this.curSource.url;
}
// ...
} else {
Logger.error(TAG, 'createAVPlayer fail');
}
}).catch((error: BusinessError) => {
Logger.error(TAG, `AVPlayer catchCallback, error message:${error.message}`);
});
}
- 设置业务需要的监听事件,从XComponent组件获取surfaceID并设置该属性,接着调用 prepare() ,AVPlayer进入prepared状态,可设置缩放模式、音量等。
private setStateChangeCallback(avPlayer: media.AVPlayer) {
avPlayer.on('stateChange', async (state: string) => {
switch (state) {
// ...
case 'initialized':
Logger.info(TAG, 'AVPlayer state initialized called.' + ` this.curIndex:${this.curIndex}`);
avPlayer.surfaceId = this.surfaceID;
avPlayer.prepare();
break;
case 'prepared':
// ...
break;
// ...
}
})
}
- 设置视频播控功能,包括播放 play() ,暂停 pause() ,跳转 seek() ,停止 stop() 、重置 reset() 等操作。
public playVideo() {
// ...
}
public pauseVideo() {
// ...
}
public stopVideo() {
// ...
}
public seek(seekTime: number) {
// ...
}
- 最后调用 release() 销毁实例,退出播放。
public releaseVideo(index: number) {
if (this.avPlayer) {
Logger.info(TAG,
`releaseVideo: state:${this.avPlayer.state} this.curIndex:${this.curIndex} this.index:${index}`);
this.avPlayer.off('timeUpdate');
this.avPlayer.off('seekDone');
this.avPlayer.off('speedDone');
this.avPlayer.off('error');
this.avPlayer.off('stateChange');
this.avPlayer.release();
// ...
}
}
使用系统解码器AVCodec开发
实现原理
AVCodec模块 的Native API接口,可以完成视频解码功能,即将媒体数据在系统侧解码成YUV文件或送显至应用上。
开发步骤
使用系统解码器AVCodec开发HDRVivid视频播放功能,主要开发步骤为(详细开发步骤可参考HDR Vivid视频播放):
- 调用OH_VideoDecoder_CreateByMime()通过HEVC格式创建解码器实例对象。如果需要对HDRVivid视频进行解码,需要配置MimeType为H265 (即OH_AVCODEC_MIMETYPE_VIDEO_HEVC)。
// 创建解码器实例对象
int32_t VideoDecoder::Create(const std::string &videoCodecMime) {
decoder_ = OH_VideoDecoder_CreateByMime(OH_AVCODEC_MIMETYPE_VIDEO_HEVC);
CHECK_AND_RETURN_RET_LOG(decoder_ != nullptr, AVCODEC_SAMPLE_ERR_ERROR, "Create failed");
return AVCODEC_SAMPLE_ERR_OK;
}
- 调用 OH_VideoDecoder_RegisterCallback() 设置回调函数。
// 设置回调函数
int32_t VideoDecoder::SetCallback(CodecUserData *codecUserData) {
int32_t ret = AV_ERR_OK;
ret = OH_VideoDecoder_RegisterCallback(decoder_,
{SampleCallback::OnCodecError, SampleCallback::OnCodecFormatChange,
SampleCallback::OnNeedInputBuffer, SampleCallback::OnNewOutputBuffer},
codecUserData);
CHECK_AND_RETURN_RET_LOG(ret == AV_ERR_OK, AVCODEC_SAMPLE_ERR_ERROR, "Set callback failed, ret: %{public}d", ret);
return AVCODEC_SAMPLE_ERR_OK;
}
- 调用 OH_VideoDecoder_Configure() 配置解码器。
// 配置解码器
int32_t VideoDecoder::Configure(const SampleInfo &sampleInfo) {
// ...
int ret = OH_VideoDecoder_Configure(decoder_, format);
// ...
}
- 从XComponent组件获取window参数,设置Surface,并调用 OH_VideoDecoder_Prepare() 使解码器就绪。
int32_t VideoDecoder::Config(const SampleInfo &sampleInfo, CodecUserData *codecUserData) {
// ...
// 设置Surface。本例中的nativeWindow,需要从XComponent组件获取
if (sampleInfo.window != nullptr) {
int ret = OH_VideoDecoder_SetSurface(decoder_, sampleInfo.window);
CHECK_AND_RETURN_RET_LOG(ret == AV_ERR_OK && sampleInfo.window, AVCODEC_SAMPLE_ERR_ERROR,
"Set surface failed, ret: %{public}d", ret);
}
// ...
// 解码器就绪
{
int ret = OH_VideoDecoder_Prepare(decoder_);
CHECK_AND_RETURN_RET_LOG(ret == AV_ERR_OK, AVCODEC_SAMPLE_ERR_ERROR, "Prepare failed, ret: %{public}d", ret);
}
return AVCODEC_SAMPLE_ERR_OK;
}
- 调用 OH_VideoDecoder_Start() 启动解码器。
// 启动解码器
int32_t VideoDecoder::Start() {
CHECK_AND_RETURN_RET_LOG(decoder_ != nullptr, AVCODEC_SAMPLE_ERR_ERROR, "Decoder is null");
int ret = OH_VideoDecoder_Start(decoder_);
CHECK_AND_RETURN_RET_LOG(ret == AV_ERR_OK, AVCODEC_SAMPLE_ERR_ERROR, "Start failed, ret: %{public}d", ret);
return AVCODEC_SAMPLE_ERR_OK;
}
- 调用 OH_VideoDecoder_PushInputBuffer() 写入解码码流。
// 写入解码码流
int32_t VideoDecoder::PushInputBuffer(CodecBufferInfo &info) {
CHECK_AND_RETURN_RET_LOG(decoder_ != nullptr, AVCODEC_SAMPLE_ERR_ERROR, "Decoder is null");
int32_t ret = OH_VideoDecoder_PushInputBuffer(decoder_, info.bufferIndex);
CHECK_AND_RETURN_RET_LOG(ret == AV_ERR_OK, AVCODEC_SAMPLE_ERR_ERROR, "Push input data failed");
return AVCODEC_SAMPLE_ERR_OK;
}
- 调用 OH_VideoDecoder_RenderOutputBuffer() 渲染并释放解码帧。
// 渲染并释放解码帧
int32_t VideoDecoder::FreeOutputBuffer(uint32_t bufferIndex, bool render) {
CHECK_AND_RETURN_RET_LOG(decoder_ != nullptr, AVCODEC_SAMPLE_ERR_ERROR, "Decoder is null");
int32_t ret = AVCODEC_SAMPLE_ERR_OK;
if (render) {
ret = OH_VideoDecoder_RenderOutputBuffer(decoder_, bufferIndex);
} else {
ret = OH_VideoDecoder_FreeOutputBuffer(decoder_, bufferIndex);
}
CHECK_AND_RETURN_RET_LOG(ret == AV_ERR_OK, AVCODEC_SAMPLE_ERR_ERROR, "Free output data failed");
return AVCODEC_SAMPLE_ERR_OK;
}
- 最后调用 OH_VideoDecoder_Destroy() 销毁解码器实例,释放资源。
// 销毁解码器实例,释放资源
int32_t VideoDecoder::Release() {
if (decoder_ != nullptr) {
OH_VideoDecoder_Flush(decoder_);
OH_VideoDecoder_Stop(decoder_);
OH_VideoDecoder_Destroy(decoder_);
decoder_ = nullptr;
}
return AVCODEC_SAMPLE_ERR_OK;
}
HDRVivid视频转码成SDR视频
实现原理
将HDRVivid视频转码成SDR视频是一个涉及多个技术要点的复杂过程。通过合理的转码处理,可以确保视频内容在不同设备上都能呈现出更好的效果,不仅优化了视频的播放体验,还可以满足更广泛受众的需求,提高市场影响力。
开发步骤
HarmonyOS提供了Native侧的 VideoProcessing 函数,可以将HDRVivid视频转码成SDR视频,主要的开发步骤为(详细开发步骤可参考 视频色彩空间转换 ):
- 调用 OH_VideoProcessing_Create() 创建色彩空间转换模块。
void PluginManager::PrepareSurface(){
VideoProcessing_ErrorCode ret = OH_VideoProcessing_InitializeEnvironment();
if (ret != VIDEO_PROCESSING_SUCCESS) {
OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "PluginManager","OH_VideoProcessing_InitializeEnvironment %d",ret);
return;
}
// 创建色彩空间转换模块
ret = OH_VideoProcessing_Create(&processor, VIDEO_PROCESSING_TYPE_COLOR_SPACE_CONVERSION);
if (ret != VIDEO_PROCESSING_SUCCESS) {
OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "PluginManager","OH_VideoProcessing_Create %d",ret);
return;
}
// ...
}
- 从XComponent获取OHNativeWindow实例,调用 OH_VideoProcessing_SetSurface() 设置surface。
void PluginManager::InitProcessing(SampleInfo &sampleInfo){
int32_t err = 0;
OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "PluginManager","target colorspace %{public}d",sampleInfo.colorSpace);
// 为OHNativeWindow设置元数据属性值、宽高、内容格式等
if (sampleInfo.colorSpace == OH_COLORSPACE_BT709_LIMIT) {
err = OH_NativeWindow_NativeWindowHandleOpt(PluginManager::GetInstance()->windowOut, SET_FORMAT, NATIVEBUFFER_PIXEL_FMT_YCBCR_420_SP);
// ...
} else if (sampleInfo.colorSpace == OH_COLORSPACE_BT2020_HLG_LIMIT) {
err = OH_NativeWindow_SetMetadataValue(PluginManager::GetInstance()->windowOut,
OH_HDR_METADATA_TYPE, sizeof(uint8_t), (uint8_t *)&sampleInfo.metaData);
// ...
err = OH_NativeWindow_NativeWindowHandleOpt(PluginManager::GetInstance()->windowOut, SET_FORMAT, NATIVEBUFFER_PIXEL_FMT_RGBA_1010102);
// ...
} else if (sampleInfo.colorSpace == OH_COLORSPACE_BT2020_PQ_LIMIT) {
err = OH_NativeWindow_SetMetadataValue(PluginManager::GetInstance()->windowOut,
OH_HDR_METADATA_TYPE, sizeof(uint8_t), (uint8_t *)&sampleInfo.metaData);
// ...
err = OH_NativeWindow_NativeWindowHandleOpt(PluginManager::GetInstance()->windowOut, SET_FORMAT, NATIVEBUFFER_PIXEL_FMT_RGBA_1010102);
// ...
}
// 为OHNativeWindow设置颜色空间属性
err = OH_NativeWindow_SetColorSpace(PluginManager::GetInstance()->windowOut, sampleInfo.colorSpace);
// ...
// 设置输出surface
VideoProcessing_ErrorCode ret = OH_VideoProcessing_SetSurface(processor, PluginManager::GetInstance()->windowOut);
// ...
}
- 调用 OH_VideoProcessing_RegisterCallback() 等函数创建并绑定回调函数。
// 创建并绑定回调函数
ret = OH_VideoProcessingCallback_Create(&callback);
if (ret != VIDEO_PROCESSING_SUCCESS) {
OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "PluginManager","OH_VideoProcessingCallback_Create %d",ret);
return;
}
ret = OH_VideoProcessingCallback_BindOnError(callback, OnError);
if (ret != VIDEO_PROCESSING_SUCCESS) {
OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "PluginManager","OH_VideoProcessingCallback_BindOnError %d",ret);
return;
}
ret = OH_VideoProcessingCallback_BindOnState(callback, OnState);
if (ret != VIDEO_PROCESSING_SUCCESS) {
OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "PluginManager","OH_VideoProcessingCallback_BindOnState %d",ret);
return;
}
ret = OH_VideoProcessingCallback_BindOnNewOutputBuffer(callback, OnNewOutputBuffer);
if (ret != VIDEO_PROCESSING_SUCCESS) {
OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "PluginManager","OH_VideoProcessingCallback_BindOnNewOutputBuffer %d",ret);
return;
}
OH_VideoProcessing_RegisterCallback(processor, callback, nullptr);
OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, "PluginManager","InitProcessing end");
- 调用 OH_VideoProcessing_Start() 启动色彩空间转换处理。
// 启动色彩空间转换处理
void PluginManager::StartProcessing(){
OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, "PluginManager","StartProcessing ");
VideoProcessing_ErrorCode ret = OH_VideoProcessing_Start(processor);
if (ret != VIDEO_PROCESSING_SUCCESS) {
OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "PluginManager","StartProcessing %d",ret);
return;
}
}
- 调用 OH_VideoProcessing_Stop() 停止色彩空间转换处理。
// 停止色彩空间转换处理
void PluginManager::StopProcessing(){
OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, "PluginManager","StopProcessing ");
VideoProcessing_ErrorCode ret = OH_VideoProcessing_Stop(processor);
if (ret != VIDEO_PROCESSING_SUCCESS) {
OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "PluginManager","OH_VideoProcessing_Stop %d",ret);
return;
}
}
- 调用 OH_VideoProcessing_Destroy() 及 OH_VideoProcessing_DeinitializeEnvironment() 释放处理实例和资源。
// 释放处理实例和资源
void PluginManager::DestroyProcessing(){
OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, "PluginManager","DestroyProcessing ");
VideoProcessing_ErrorCode ret = OH_VideoProcessing_Destroy(processor);
if (ret != VIDEO_PROCESSING_SUCCESS) {
OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "PluginManager","OH_VideoProcessing_Destroy %d",ret);
return;
}
ret = OH_VideoProcessingCallback_Destroy(callback);
if (ret != VIDEO_PROCESSING_SUCCESS) {
OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "PluginManager","OH_VideoProcessingCallback_Destroy %d",ret);
return;
}
ret = OH_VideoProcessing_DeinitializeEnvironment();
if (ret != VIDEO_PROCESSING_SUCCESS) {
OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "PluginManager","OH_VideoProcessing_DeinitializeEnvironment %d",ret);
return;
}
}