鸿蒙开发5.0【基于AVCodecKit】音视频解码及二次处理播放

news2024/11/30 4:53:13

1:场景描述

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

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

使用的核心API:

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

2:方案描述

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

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

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

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

效果图如下:

1

具体步骤如下:

步骤一:导入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;
}

解封装支持的文件格式:

2

步骤四:使用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 decoder
  int32_t ret = ConfigureVideoDecoder(sampleInfo);
  // SetSurface from video decoder
  if (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 decoder
  ret = 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;
}

以上就是本篇文章所带来的鸿蒙开发中一小部分技术讲解;想要学习完整的鸿蒙全栈技术。可以在结尾找我可全部拿到!
下面是鸿蒙的完整学习路线,展示如下:
1

除此之外,根据这个学习鸿蒙全栈学习路线,也附带一整套完整的学习【文档+视频】,内容包含如下

内容包含了:(ArkTS、ArkUI、Stage模型、多端部署、分布式应用开发、音频、视频、WebGL、OpenHarmony多媒体技术、Napi组件、OpenHarmony内核、鸿蒙南向开发、鸿蒙项目实战)等技术知识点。帮助大家在学习鸿蒙路上快速成长!

鸿蒙【北向应用开发+南向系统层开发】文档

鸿蒙【基础+实战项目】视频

鸿蒙面经

2

为了避免大家在学习过程中产生更多的时间成本,对比我把以上内容全部放在了↓↓↓想要的可以自拿喔!谢谢大家观看!
3

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2059737.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Yolov8:模型部署到安卓端

1. 项目准备 1.1 先安装JDK和Android studio &#xff08;1&#xff09;JDK下载&#xff1a; 官网站&#xff1a;https://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html 同意协议后&#xff0c;下载相应版本的JDK 我这里没有下载JDK21版的&a…

作品集图片美化处理网站推荐

今天要做作品集的时候发现单一的截屏很单调&#xff0c;想着看能不能添加一些边框之类的元素使图片更加精美有活力&#xff0c;于是就找到了一个个人认为非常好用的网站镜头 - 创建惊人的模型 (shots.so)&#xff0c;它利用的是ai算法&#xff0c;使用起来非常便捷&#xff0c;…

亚马逊店铺自养号测评:安全可控的引流新策略全解析

亚马逊作为全球最大的电商平台之一&#xff0c;吸引了无数卖家加入其平台&#xff0c;争夺市场份额。然而&#xff0c;新店如何在众多竞争对手中脱颖而出&#xff0c;实现引流和销售的快速增长&#xff0c;成为了卖家们亟待解决的问题。本文珑哥将和大家探讨亚马逊新店引流的关…

浅析Thermo-Calc软件在合金热处理工艺中的应用

Thermo-Calc软件是世界公认的最好的最全面的多元体系热力学与相图计算软件&#xff0c;具备通用、计算灵活的特点&#xff0c;被广泛应用于钢铁、有色金属、核燃料、高熵合金等多领域的材料成分设计优化、工艺过程的设计优化、缺陷和问题的分析等过程中&#xff0c;是目前国内外…

焊工安全操作规范

1、焊工作业人员必须经专业安全技术培训考试合格&#xff0c;发给许可证后&#xff0c;持证上岗操作。明火作业必须履行审批手续。 2、工作前应认真检查工具、设备是否完好&#xff0c;焊机的外壳是否可靠地接地。焊机的修理应由电气保养人员进行&#xff0c;其他人员不得拆修…

Maven-07.依赖管理-依赖范围

一.依赖范围 一个项目依赖的jar包默认情况下可以在任何地方使用。这里的任何地方是指在main文件夹范围内作用&#xff0c;test文件夹范围内作用和package打包进去都可以。 依赖的范围可以通过scope标签进行调整&#xff0c; scope的值和其作用范围以及代表性jar包范例如上表所…

渗透必备:BurpSuite

一、介绍 BurpSuite是渗透测试、漏洞挖掘以及Web应用程序测试的最佳工具之一&#xff0c;是一款用于攻击web 应用程序的集成攻击测试平台&#xff0c;可以进行抓包、重放、爆破&#xff0c;包含许多工具&#xff0c;能处理对应的HTTP消息、持久性、认证、代理、日志、警报。 …

电力领域知识图谱:赋能电力智能发展的关键技术

电力领域知识图谱&#xff1a;赋能电力智能发展的关键技术 前言电力领域知识图谱 前言 电力领域知识图谱作为一项新兴技术&#xff0c;在电力系统中具有重要的应用价值。它能够以结构化的方式刻画电力系统中的各种概念、实体、事件及其间的关系&#xff0c;为电力人工智能的发…

淮北农商银行年报与审计报告数据不合,盈利能力与资产质量承压

撰稿|芋圆 来源|贝多财经 7月24日&#xff0c;国家金融监督管理总局发布《国家金融监督管理总局安徽监管局关于淮北农村商业银行股份有限公司&#xff08;以下简称“淮北农商行”或“淮北农商银行”&#xff09;吸收合并安徽濉溪农村商业银行股份有限公司的批复》。 内容显示…

SM4前后端加密和解密

一&#xff1a;前端vue 二&#xff1a;后端Java 三&#xff1a;详细 3.1maven添加 <dependency><groupId>org.bouncycastle</groupId><artifactId>bcprov-jdk15to18</artifactId><version>1.66</version> </dependency> <…

合宙Air700ECQ硬件设计手册——主要性能

Air700ECQ是一款基于移芯EC716E平台设计的LTE Cat 1无线通信模组。支持移动双模FDD-LTE/TDD-LTE的4G远距离无线传输技术。以极小封装&#xff0c;极高性价比&#xff0c;满足IoT行业的数传应用需求。例如共享应用场景&#xff0c;定位器场景&#xff0c;DTU数传场景等。 无论是…

IP in IP 协议

IP in IP 是一种多重IP协议&#xff0c;即&#xff1a;客户机可以发送一个IP协议内部在嵌套一个IP协议到某个特定的主机上&#xff0c;在由具体的主机作为路由进行转发的协议。 例如&#xff1a; IP in IP帧协议结构为&#xff0c;第一层为发送到IP in IP 路由主机的报文&…

引领未来教育的新工具,NUC 14 Pro 赋能智慧课堂

在追求高效、互动、个性化的教育新时代&#xff0c;传统的教学工具已难以满足现代教育的需求。华硕NUC 14 Pro应运而生&#xff0c;它以小巧的机身、强大的性能以及丰富的商用功能&#xff0c;正逐步成为重塑教育生态的重要力量。它不仅是一款教学工具&#xff0c;更是推动教育…

从bbl和overleaf版本解决Arxiv提交后缺失参考文献Citation on page undefined on input line

debug 食用指南&#xff1a;框架/语言&#xff1a;问题描述&#xff1a;解决方案&#xff1a;问题原因&#xff1a;版本解决方案&#xff1a; 安利时间&#xff1a; 食用指南&#xff1a; 框架使用过程中的问题首先要注意版本发布时间造成方法弃用 当你在CSDN等网站查找不到最…

家电触摸感应芯片/4键触控检测IC-VK36Q4 DFN10L超小封装触摸芯片

产品品牌&#xff1a;永嘉微电/VINKA 产品型号&#xff1a;VK36Q4 封装形式&#xff1a;DFN10L 概述 VK36Q4具有4个触摸按键&#xff0c;可用来检测外部触摸按键上人手的触摸动作。该芯片具有较 高的集成度&#xff0c;仅需极少的外部组件便可实现触摸按键的检测。 提供了4路…

jmeter中的json提取器

将响应结果提取作为变量 线程组->添加->后置处理器->json提取器 可以通过debug 调试器查看有没有提取出来 线程组->添加->取样器->debug sampler&#xff08;debug调试器&#xff09; 获取出来的响应数据。作为下一条接口的是否发送的判断内容 线程组…

一款多平台开源索尼 PlayStation 3 模拟器和调试器

大家好&#xff0c;今天给大家分享的是一款开源的PlayStation 3模拟器RPCS3。它允许用户在PC上运行PlayStation 3的游戏&#xff0c;无需PlayStation 3主机。 项目介绍 RPCS3 是一款多平台开源索尼 PlayStation 3 模拟器和调试器&#xff0c;使用 C 编写&#xff0c;适用于 Wi…

【秋招笔试】8.17米哈游秋招(第二场)-三语言题解

🍭 大家好这里是 春秋招笔试突围,一起备战大厂笔试 💻 ACM金牌团队🏅️ | 多次AK大厂笔试 | 编程一对一辅导 ✨ 本系列打算持续跟新 春秋招笔试题 👏 感谢大家的订阅➕ 和 喜欢💗 和 手里的小花花🌸 ✨ 笔试合集传送们 -> 🧷春秋招笔试合集 🍒 本专栏已收…

OS-Copilot:自学习的通用计算机智能体

人工智能咨询培训老师叶梓 转载标明出处 现有的计算机智能体大多局限于特定应用或领域&#xff0c;难以应对多样化和不断变化的计算机操作需求。这种局限性不仅限制了智能体的实用性&#xff0c;也阻碍了其在更广泛场景中的应用潜力。为了突破这一瓶颈&#xff0c;研究者们一直…

接口自动化面试题超全,助你离Offer又近一步!

一 、请问你是如何做接口测试的&#xff1f; 大体来说&#xff0c;经历以下过程&#xff1a;接口需求调研、接口测试工具选择、接口测试用例编写、接口测试执行、接口测试回归、接口测试自动化持续集成。具体来说&#xff0c;接口测试流程分成以下九步&#xff1a; 第一步&am…