当前位置: 首页 > news >正文

鸿蒙( Beta5版)开发实战:基于AVCodecKit【音视频解码】

1:场景描述

场景:基于VideoCoder的音视频解码及二次处理播放。

首先导入选择器picker模块,使用PhotoViewPicker方法拉起图库选择视频文件,将视频文件传递到native侧使用Demuxer解封装器进行解封装,再使用OH_VideoDecoder进行解码(surface模式)送显播放

使用的核心API:

  • picker:提供拉起图库选择视频的功能接口。
  • AVDemuxer:音视频解封装,用于获取视频等媒体帧数据。
  • VideoDecoder:视频解码,将视频数据解码后送显播放。

2:方案描述

Step1:导入picker模块(仅代表选择一个视频路径,还有其它获取媒体文件的方式), 拉起图库选择视频文件保存到自定义路径。

Step2:将文件传递到native侧进行交互。

Step3:使用AVDemuxer接口对文件进行解封装获取视频流数据。

Step4:使用VideoDecoder接口将视频数据解码,结合Xcomponent送显播放。

效果图如下:

zh-cn_image_0000001903006730.gif

具体步骤如下:

步骤一:导入picker模块, 拉起图库选择视频文件自定义保存。

import { picker } from '@kit.CoreFileKit';
let photoSelectOptions = new picker.PhotoSelectOptions();
photoSelectOptions.MIMEType = picker.PhotoViewMIMETypes.VIDEO_TYPE;
photoSelectOptions.maxSelectNumber = 1;
let photoPicker = new picker.PhotoViewPicker();
photoPicker.select(photoSelectOptions).then((PhotoSelectResult: picker.PhotoSelectResult) => {hilog.info(0x0000, TAG, 'PhotoViewPicker.select successfully, PhotoSelectResult uri: ' + JSON.stringify(PhotoSelectResult));this.selectFilePath = PhotoSelectResult.photoUris[0];hilog.info(0x0000, TAG, 'Get selectFilePath successfully: ' + JSON.stringify(this.selectFilePath));
}).catch((err: BusinessError) => {hilog.error(0x0000, TAG, 'PhotoViewPicker.select failed with err: ' + JSON.stringify(err));
})
}

步骤二:将文件传递到native侧进行交互。

import player from 'libplayer.so';
export const playNative: (inputFileFd: number,inputFileOffset: number,inputFileSize: number,cbFn: () => void
) => void;
static napi_value Init(napi_env env, napi_value exports) {napi_property_descriptor classProp[] = {{"playNative", nullptr, Play, nullptr, nullptr, nullptr, napi_default, nullptr},
};
napi_value PlayerNative = nullptr;
const char *classBindName = "playerNative";
napi_define_class(env, classBindName, strlen(classBindName), nullptr, nullptr, 1, classProp, &PlayerNative);
PluginManager::GetInstance()->Export(env, exports);
napi_define_properties(env, exports, sizeof(classProp) / sizeof(classProp[0]), classProp);
return exports;
}

步骤三:使用Demuxer接口对文件进行解封装获取视频流数据。

Step1:创建解封装器,传入媒体文件格式信息。

int32_t Demuxer::CreateDemuxer(SampleInfo &info) {source = OH_AVSource_CreateWithFD(info.inputFd, info.inputFileOffset, info.inputFileSize);demuxer = OH_AVDemuxer_CreateWithSource(source);auto sourceFormat = std::shared_ptr<OH_AVFormat>(OH_AVSource_GetSourceFormat(source), OH_AVFormat_Destroy);int32_t ret = GetTrackInfo(sourceFormat, info);return AV_ERR_OK;
}

Step2:添加解封装轨道,获取文件轨道信息。

int32_t Demuxer::GetTrackInfo(std::shared_ptr<OH_AVFormat> sourceFormat, SampleInfo &info) {int32_t trackCount = 0;OH_AVFormat_GetIntValue(sourceFormat.get(), OH_MD_KEY_TRACK_COUNT, &trackCount);for (int32_t index = 0; index < trackCount; index++) {int trackType = -1;auto trackFormat = std::shared_ptr<OH_AVFormat>(OH_AVSource_GetTrackFormat(source, index), OH_AVFormat_Destroy);OH_AVFormat_GetIntValue(trackFormat.get(), OH_MD_KEY_TRACK_TYPE, &trackType);if (trackType == MEDIA_TYPE_VID) {OH_AVDemuxer_SelectTrackByID(demuxer, index);OH_AVFormat_GetIntValue(trackFormat.get(), OH_MD_KEY_WIDTH, &info.videoWidth);OH_AVFormat_GetIntValue(trackFormat.get(), OH_MD_KEY_HEIGHT, &info.videoHeight);OH_AVFormat_GetDoubleValue(trackFormat.get(), OH_MD_KEY_FRAME_RATE, &info.frameRate);OH_AVFormat_GetLongValue(trackFormat.get(), OH_MD_KEY_BITRATE, &info.bitrate);OH_AVFormat_GetIntValue(trackFormat.get(), "video_is_hdr_vivid", &info.isHDRVivid);OH_AVFormat_GetIntValue(trackFormat.get(), OH_MD_KEY_ROTATION, &info.rotation);char *codecMime;OH_AVFormat_GetStringValue(trackFormat.get(), OH_MD_KEY_CODEC_MIME, const_cast<char const **>(&codecMime));info.codecMime = codecMime;OH_AVFormat_GetIntValue(trackFormat.get(), OH_MD_KEY_PROFILE, &info.hevcProfile);videoTrackId_ = index;OH_LOG_ERROR(LOG_APP, "Demuxer config: %{public}d*%{public}d, %{public}.1ffps, %{public}ld" "kbps",info.videoWidth, info.videoHeight, info.frameRate, info.bitrate / 1024);}}return AV_ERR_OK;
}

Step3:开始解封装,循环获取视频帧数据。

int32_t Demuxer::ReadSample(OH_AVBuffer *buffer, OH_AVCodecBufferAttr &attr) {int32_t ret = OH_AVDemuxer_ReadSampleBuffer(demuxer, videoTrackId_, buffer);ret = OH_AVBuffer_GetBufferAttr(buffer, &attr);return AV_ERR_OK;
}

解封装支持的文件格式:

image.png

步骤四:使用VideoDecoder接口将视频数据解码,结合Xcomponent送显播放。

Step1:将解封装后的数据送去解码器进行解码

void Player::DecInputThread() {while (true) {std::unique_lock<std::mutex> lock(signal->inputMutex_);bool condRet = signal->inputCond_.wait_for(lock, 5s, [this]() { return !isStarted_ || !signal->inputBufferInfoQueue_.empty(); });if (!isStarted_) {OH_LOG_ERROR(LOG_APP, "Work done, thread out");break;}if (signal->inputBufferInfoQueue_.empty()) {OH_LOG_ERROR(LOG_APP, "Buffer queue is empty, continue, cond ret: %{public}d", condRet);}CodecBufferInfo bufferInfo = signal->inputBufferInfoQueue_.front();signal->inputBufferInfoQueue_.pop();signal->inputFrameCount_++;lock.unlock();demuxer_->ReadSample(reinterpret_cast<OH_AVBuffer *>(bufferInfo.buffer), bufferInfo.attr);int32_t ret = videoDecoder_->PushInputData(bufferInfo);
}
StartRelease();
}

Step2:获取解码后的数据

void Player::DecOutputThread() {sampleInfo_.frameInterval = MICROSECOND / sampleInfo_.frameRate;while (true) {thread_local auto lastPushTime = std::chrono::system_clock::now();if (!isStarted_) {OH_LOG_ERROR(LOG_APP, "Decoder output thread out");break;}std::unique_lock<std::mutex> lock(signal->outputMutex_);bool condRet = signal->outputCond_.wait_for(lock, 5s, [this]() { return !isStarted_ || !signal->outputBufferInfoQueue_.empty(); });if (!isStarted_) {OH_LOG_ERROR(LOG_APP, "Decoder output thread out");break;}if (signal->outputBufferInfoQueue_.empty()) {OH_LOG_ERROR(LOG_APP, "Buffer queue is empty, continue, cond ret: %{public}d", condRet);}CodecBufferInfo bufferInfo = signal->outputBufferInfoQueue_.front();signal->outputBufferInfoQueue_.pop();if (bufferInfo.attr.flags & AVCODEC_BUFFER_FLAGS_EOS) {OH_LOG_ERROR(LOG_APP, "Catch EOS, thread out");break;}signal->outputFrameCount_++;OH_LOG_ERROR(LOG_APP, "Out buffer count: %{public}u, size: %{public}d, flag: %{public}u, pts: %{public}ld",signal->outputFrameCount_, bufferInfo.attr.size, bufferInfo.attr.flags, bufferInfo.attr.pts);lock.unlock();int32_t ret = videoDecoder_->FreeOutputData(bufferInfo.bufferIndex, true);if (ret != AV_ERR_OK) {OH_LOG_ERROR(LOG_APP, "Decoder output thread out");break;}std::this_thread::sleep_until(lastPushTime + std::chrono::microseconds(sampleInfo_.frameInterval));lastPushTime = std::chrono::system_clock::now();
}
OH_LOG_ERROR(LOG_APP, "Exit, frame count: %{public}u", signal->outputFrameCount_);
StartRelease();
}

Step3:使用OH_VideoDecoder_SetSurface设置surface数据和window绑定

int32_t VideoDecoder::Config(const SampleInfo &sampleInfo, VDecSignal *signal) {// Configure video decoderint32_t ret = ConfigureVideoDecoder(sampleInfo);// SetSurface from video decoderif (sampleInfo.window != nullptr) {int ret = OH_VideoDecoder_SetSurface(decoder, sampleInfo.window);if (ret != AV_ERR_OK || sampleInfo.window == nullptr) {OH_LOG_ERROR(LOG_APP, "Set surface failed, ret: %{public}d", ret);return AV_ERR_UNKNOWN;}}// SetCallback for video decoderret = SetCallback(signal);if (ret != AV_ERR_OK) {OH_LOG_ERROR(LOG_APP, "Set callback failed, ret: %{public}d", ret);return AV_ERR_UNKNOWN;}// Prepare video decoder{int ret = OH_VideoDecoder_Prepare(decoder);if (ret != AV_ERR_OK) {OH_LOG_ERROR(LOG_APP, "Prepare failed, ret: %{public}d", ret);return AV_ERR_UNKNOWN;}}return AV_ERR_OK;
}

Step4: native层获取 NativeXComponent

void PluginManager::Export(napi_env env, napi_value exports) {napi_value exportInstance = nullptr;if (napi_get_named_property(env, exports, OH_NATIVE_XCOMPONENT_OBJ, &exportInstance) != napi_ok) {OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_DOMAIN, "PluginManager", "Export: napi_get_named_property fail");return;}OH_NativeXComponent *nativeXComponent = nullptr;if (napi_unwrap(env, exportInstance, reinterpret_cast<void **>(&nativeXComponent)) != napi_ok) {OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_DOMAIN, "PluginManager", "Export: napi_unwrap fail");return;}char idStr[OH_XCOMPONENT_ID_LEN_MAX + 1] = {'\0'};uint64_t idSize = OH_XCOMPONENT_ID_LEN_MAX + 1;if (OH_NativeXComponent_GetXComponentId(nativeXComponent, idStr, &idSize) != OH_NATIVEXCOMPONENT_RESULT_SUCCESS) {OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_DOMAIN, "PluginManager", "Export: OH_NativeXComponent_GetXComponentId fail");return;}std::string id(idStr);auto context = PluginManager::GetInstance();if ((context != nullptr) && (nativeXComponent != nullptr)) {context->SetNativeXComponent(id, nativeXComponent);auto render = context->GetRender(id);OH_NativeXComponent_RegisterCallback(nativeXComponent, &PluginRender::m_callback);}
}

step5:通过回调将window渲染播放

void OnSurfaceCreatedCB(OH_NativeXComponent *component, void *window) {OH_LOG_Print(LOG_APP, LOG_INFO, LOG_DOMAIN, "Callback", "OnSurfaceCreatedCB");auto context = PluginManager::GetInstance();context->m_window = (OHNativeWindow *)window;
}

最后

小编在之前的鸿蒙系统扫盲中,有很多朋友给我留言,不同的角度的问了一些问题,我明显感觉到一点,那就是许多人参与鸿蒙开发,但是又不知道从哪里下手,因为资料太多,太杂,教授的人也多,无从选择。有很多小伙伴不知道学习哪些鸿蒙开发技术?不知道需要重点掌握哪些鸿蒙应用开发知识点?而且学习时频繁踩坑,最终浪费大量时间。所以有一份实用的鸿蒙(HarmonyOS NEXT)文档用来跟着学习是非常有必要的。 

为了确保高效学习,建议规划清晰的学习路线,涵盖以下关键阶段:

希望这一份鸿蒙学习文档能够给大家带来帮助~


 鸿蒙(HarmonyOS NEXT)最新学习路线

该路线图包含基础技能、就业必备技能、多媒体技术、六大电商APP、进阶高级技能、实战就业级设备开发,不仅补充了华为官网未涉及的解决方案

路线图适合人群:

IT开发人员:想要拓展职业边界
零基础小白:鸿蒙爱好者,希望从0到1学习,增加一项技能。
技术提升/进阶跳槽:发展瓶颈期,提升职场竞争力,快速掌握鸿蒙技术

2.视频教程+学习PDF文档

(鸿蒙语法ArkTS、TypeScript、ArkUI教程……)

 纯血版鸿蒙全套学习文档(面试、文档、全套视频等)

                   

鸿蒙APP开发必备

​​

总结

参与鸿蒙开发,你要先认清适合你的方向,如果是想从事鸿蒙应用开发方向的话,可以参考本文的学习路径,简单来说就是:为了确保高效学习,建议规划清晰的学习路线


http://www.mrgr.cn/news/12997.html

相关文章:

  • 【已解决】Vue Duplicate keys detected: ‘[object Object]’
  • 【STM32】FMC
  • 操作符详解
  • go slices.Clone官方文档
  • 力扣(单调递增的数字)
  • AtCoder Beginner Contest 368 题ABCD详细题解(C++,Python)
  • 无法验证 Anaconda 仓库证书
  • rk3568 Android12 增加 USB HOST 模式开关
  • WPF 手撸插件 七 日志记录(二)
  • 协同过滤推荐算法:个性化推荐的基石
  • 速盾:服务器接入cdn后上传图片失败怎么解决?
  • 【python】懂车帝字体反爬逐层解密案例(附完整代码)
  • JS学习大纲
  • react面试题四
  • android selinux报avc denied权限和编译报neverallow解决方案
  • 论文阅读笔记:RepViT: Revisiting Mobile CNN From Vit Perspective
  • Linux C创建进程及父子进程虚拟地址空间(附源码)
  • 通过python解决原神解密
  • Stable Diffusion的微调方法原理总结
  • cordova手动更新