在多媒体处理场景中,经常需要从视频文件中提取纯净的音频轨道。本文将介绍如何在HarmonyOS应用中实现这一功能,核心代码基于@ohos/mp4parser库的FFmpeg能力。

功能概述

我们实现了一个完整的视频音频提取页面,包含以下功能:

  1. 通过系统选择器选取视频文件
  2. 将视频复制到应用沙箱目录
  3. 使用FFmpeg命令提取音频
  4. 将生成的音频文件保存到公共下载目录

实现详解

1. 视频选择与沙箱准备

视频选择使用PhotoViewPicker组件,限定选择类型为视频文件:

private async selectVideo() {// 创建视频选择器let context = getContext(this) as common.Context;let photoPicker = new picker.PhotoViewPicker(context);let photoSelectOptions = new picker.PhotoSelectOptions();photoSelectOptions.MIMEType = picker.PhotoViewMIMETypes.VIDEO_TYPE;// ...其他设置
}

选择视频后,为防止权限问题,我们将视频复制到应用沙箱目录:

private async copyFileToSandbox(sourcePath: string): Promise<string|undefined> {// 创建沙箱路径const sandboxPath = getContext(this).cacheDir + "/temp_video.mp4";// 读写文件操作...// 具体代码略...
}
2. FFmpeg音频提取

核心提取功能通过MP4Parser模块实现:

MP4Parser.ffmpegCmd(`ffmpeg -y -i "${sandboxVideoPath}" -vn -acodec libmp3lame -q:a 2 "${sandboxAudioPath}"`,callBack
);

关键参数说明:

  • -vn:禁止视频输出
  • -acodec libmp3lame:指定MP3编码器
  • -q:a 2:设置音频质量(2表示较高品质)
3. 结果保存

音频提取完成后,将文件移动到公共目录:

const documentViewPicker = new picker.DocumentViewPicker(context);
const result = await documentViewPicker.save(documentSaveOptions);// 在回调中处理文件写入
const targetPath = new fileUri.FileUri(uri + '/'+ audioName).path;
// ...写入操作
4. 状态管理与用户体验

提取过程中通过状态变量控制UI显示:

@State isExtracting: boolean = false;
@State btnText: string = '选择视频';// 提取开始时更新状态
this.isExtracting = true;
this.btnText = '正在提取...';// 完成时恢复状态
that.isExtracting = false;
that.btnText = '选择视频';

优化点分析

  1. ​临时文件清理​​:无论提取成功与否,都会尝试删除临时文件
  2. ​错误处理​​:每个关键步骤都包含try-catch错误捕获
  3. ​权限隔离​​:通过沙箱机制处理敏感文件操作

注意事项

  1. ​模块依赖​​:需要提前配置好mp4parser的FFmpeg能力
  2. ​存储权限​​:操作公共目录需要申请对应权限
  3. ​大文件处理​​:实际生产环境应考虑分块读写避免内存溢出

效果展示

  • 视频选择界面
  • 完成后的提示弹窗

总结

本文介绍的方案实现了完整的视频音频提取功能,充分利用了HarmonyOS的文件管理和FFmpeg处理能力。核心代码约200行,展示了从视频选择到音频生成的关键流程。开发者可基于此方案扩展更复杂的多媒体处理功能。

具体效果华为应用商店搜索【图影工具箱】查看

完整代码

import { MP4Parser } from "@ohos/mp4parser";
import { ICallBack } from "@ohos/mp4parser";
import { fileIo as fs } from '@kit.CoreFileKit';
import { fileUri, picker } from '@kit.CoreFileKit';
import { common } from '@kit.AbilityKit';
import { TitleBar } from "../components/TitleBar";@Entry
@Component
struct AudioExtractPage {@State btnText: string = '选择视频';@State selectedVideoPath: string = '';@State isExtracting: boolean = false;@State imageWidth: number = 0;@State imageHeight: number = 0;getResourceString(res: Resource) {return getContext().resourceManager.getStringSync(res.id)}build() {Column() {// 顶部栏TitleBar({title: '视频音频提取'})if (this.selectedVideoPath) {Text('已选择视频:' + this.selectedVideoPath).fontSize(16).margin({ bottom: 20 })}Button(this.btnText, { type: ButtonType.Normal, stateEffect: true }).borderRadius(8).backgroundColor(0x317aff).width(250).margin({ top: 15 }).onClick(() => {if (!this.isExtracting) {this.selectVideo();}})if (this.isExtracting) {Image($r('app.media.icon_load')).objectFit(ImageFit.None).width(this.imageWidth).height(this.imageHeight).border({ width: 0 }).borderStyle(BorderStyle.Dashed)}}.width('100%').height('100%').backgroundColor($r('app.color.index_tab_bar'))}private async selectVideo() {try {let context = getContext(this) as common.Context;let photoPicker = new picker.PhotoViewPicker(context);let photoSelectOptions = new picker.PhotoSelectOptions();photoSelectOptions.MIMEType = picker.PhotoViewMIMETypes.VIDEO_TYPE;photoSelectOptions.maxSelectNumber = 1;let result = await photoPicker.select(photoSelectOptions);console.info('PhotoViewPicker.select result: ' + JSON.stringify(result));if (result && result.photoUris && result.photoUris.length > 0) {this.selectedVideoPath = result.photoUris[0];console.info('Selected video path: ' + this.selectedVideoPath);this.extractAudio();}} catch (err) {console.error('选择视频失败:' + JSON.stringify(err));AlertDialog.show({ message: '选择视频失败' });}}private async copyFileToSandbox(sourcePath: string): Promise<string|undefined> {try {// 获取沙箱目录路径const sandboxPath = getContext(this).cacheDir + "/temp_video.mp4";// 读取源文件内容const sourceFd = await fs.open(sourcePath, fs.OpenMode.READ_ONLY);const fileStats = await fs.stat(sourceFd.fd);const buffer = new ArrayBuffer(fileStats.size);await fs.read(sourceFd.fd, buffer);await fs.close(sourceFd);// 写入到沙箱目录const targetFd = await fs.open(sandboxPath, fs.OpenMode.CREATE | fs.OpenMode.READ_WRITE);await fs.write(targetFd.fd, buffer);await fs.close(targetFd);return sandboxPath;} catch (err) {console.error('复制文件到沙箱失败:' + err);return undefined;}}private async moveToPublicDirectory(sourcePath: string): Promise<string|undefined> {try {const documentSaveOptions = new picker.DocumentSaveOptions();documentSaveOptions.pickerMode = picker.DocumentPickerMode.DOWNLOAD;let context = getContext(this) as common.Context;const documentViewPicker = new picker.DocumentViewPicker(context);const result = await documentViewPicker.save(documentSaveOptions);if (result && result.length > 0) {const uri = result[0];console.info('documentViewPicker.save succeed and uri is:' + uri);// 读取源文件内容const sourceFd = await fs.open(sourcePath, fs.OpenMode.READ_ONLY);const fileStats = await fs.stat(sourcePath);const buffer = new ArrayBuffer(fileStats.size);await fs.read(sourceFd.fd, buffer);await fs.close(sourceFd);// 写入到目标文件const audioName = 'extracted_audio_' + new Date().getTime() + '.mp3';const targetPath = new fileUri.FileUri(uri + '/'+ audioName).path;const targetFd = await fs.open(targetPath, fs.OpenMode.CREATE | fs.OpenMode.READ_WRITE);await fs.write(targetFd.fd, buffer);await fs.close(targetFd);return audioName;}return undefined;} catch (err) {console.error('移动到公共目录失败:' + err);return undefined;}}private async extractAudio() {if (!this.selectedVideoPath) {AlertDialog.show({ message: '请先选择视频' });return;}this.isExtracting = true;this.imageWidth = 25;this.imageHeight = 25;this.btnText = '正在提取...';try {// 1. 复制视频到沙箱目录const sandboxVideoPath = await this.copyFileToSandbox(this.selectedVideoPath);// 2. 在沙箱目录中执行ffmpeg命令const sandboxAudioPath = getContext(this).cacheDir + "/temp_audio.mp3";const that = this;let callBack: ICallBack = {async callBackResult(code: number) {that.isExtracting = false;that.imageWidth = 0;that.imageHeight = 0;that.btnText = '选择视频';if (code == 0) {try {// 3. 将音频文件移动到公共目录const publicPath = await that.moveToPublicDirectory(sandboxAudioPath);AlertDialog.show({ message: '音频提取成功,保存路径:我的手机/Download(下载)/图影工具箱/' + publicPath});} catch (err) {console.error('移动文件失败:' + err);AlertDialog.show({ message: '音频提取成功但保存失败' });}} else {AlertDialog.show({ message: '音频提取失败' });}// 清理临时文件try {await fs.unlink(sandboxVideoPath);await fs.unlink(sandboxAudioPath);} catch (err) {console.error('清理临时文件失败:' + err);}}}// 使用ffmpeg命令提取音频MP4Parser.ffmpegCmd(`ffmpeg -y -i "${sandboxVideoPath}" -vn -acodec libmp3lame -q:a 2 "${sandboxAudioPath}"`,callBack);} catch (err) {this.isExtracting = false;this.imageWidth = 0;this.imageHeight = 0;this.btnText = '选择视频';console.error('提取过程出错:' + err);AlertDialog.show({ message: '提取过程出错' });}}aboutToAppear() {MP4Parser.openNativeLog();}
} 

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

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

OpenHands:Manus 最强开源平替——本地部署与实战指南

文章目录⚙️ 一、OpenHands 核心优势&#xff1a;为何是 Manus 最佳平替&#xff1f;&#x1f9e0; 二、核心架构解析&#xff1a;多智能体如何协同工作&#xff1f;&#x1f6e0;️ 三、本地化部署指南&#xff1a;Docke部署Docker 极速部署&#xff08;推荐&#xff09;&…

用 AI 做数据分析:从“数字”里挖“规律”

数据整理干净后&#xff0c;就得分析了——算平均值、看差异、找关系&#xff0c;这些都能靠 AI 搞定。这节以“大学生在线学习满意度”数据为例&#xff0c;教你用 AI 做描述性统计、假设检验、相关性分析&#xff0c;一步步从数据里挖规律&#xff0c;超详细&#xff5e; 1. …

小程序安卓ApK转aab文件详情教程MacM4环境

根据Google Play的政策要求&#xff0c;自 2021 年 8 月起&#xff0c;Google Play 将开始要求新应用使用 Android App Bundle&#xff08;以下简称aab&#xff09; 进行发布。该格式将取代 APK 作为标准发布格式。 想了解更多关于aab的介绍可以直接阅读android官方文档&#x…

率先通过自动制冰性能认证,容声冰箱推动行业品质升级

日前&#xff0c;容声冰箱“电冰箱自动制冰性能认证”由中国家用电器研究院测试并通过&#xff0c;该认证为行业首次。这标志着中国家电行业在冰箱自动制冰功能的技术规范与品质保障领域树立了全新里程碑&#xff0c;也将洁净、高效的制冰体验带入中国家庭日常生活。目前&#…

大模型-batch之continuous batching

一、ORCA1.1 ORCA 概览看下Continuous Batching 技术的开山之作ORCA,这个其实是融合的思路。ORCA&#xff1a;把调度粒度从请求级别调整为迭代级别&#xff0c;并结合选择性批处理&#xff08;selective batching&#xff09;来进行优化。Sarathi[2] &#xff1a;利用Chunked P…

主要分布在背侧海马体(dHPC)CA1区域(dCA1)的时空联合细胞对NLP中的深层语义分析的积极影响和启示

时空联合细胞&#xff08;Spatiotemporal Conjunctive Cells&#xff09;主要分布在背侧海马体CA1区&#xff08;dCA1&#xff09;&#xff0c;其核心功能是​​同步编码空间位置、时间信息和行为意图​​&#xff0c;形成动态的情景记忆表征。这种神经机制为自然语言处理&…

操作系统:系统程序(System Programs)

目录 常见的系统程序类型 1️⃣ 文件管理&#xff08;File Management&#xff09; 2️⃣ 状态信息&#xff08;Status Information&#xff09; 3️⃣ 编译器和程序开发&#xff08;Program Language Support&#xff09; 4️⃣ 程序执行控制类&#xff08;Program Load…

【知识图谱】Neo4j Desktop桌面版中国区被禁(无法打开)问题解决方法

【知识图谱】Neo4j Desktop桌面版进程运行无法打开,UI 界面无法显示问题解决办法 前言 1.问题形式 2.原因分析 3.解决方法 3.1 方法一,断网 3.2 方法二,手动设置代理 4.启动Neo4j Desktop 前言 Neo4j Desktop桌面版安装教程参考链接: https://zskp1012.blog.csdn.net/artic…

安装acunetix软件之后改www.ddosi.org.bat文件

安装环境&#xff1a;windows11 通过网盘分享的文件&#xff1a;Acunetix_15.2.221208162_www.ddosi.org.zip 链接: https://pan.baidu.com/s/1FPFFr583FFFj6hxWB-Ygng?pwdjpim 提取码: jpim 下载后文件是&#xff1a; 解压之后&#xff0c;如下图&#xff1a; 安装步骤如…

pycharm安装教程-PyCharm2023安装详细步骤【MAC版】【安装包自取】

pycharm安装教程-PyCharm2025安装详细步骤【MAC版】安装安装包获取&#xff08;文章末尾&#xff09;今天来给大家分享 Mac 系统安装 PyCharm&#xff0c;附带安装包资源安装&#xff0c; PyCharm 相关就不叙述了&#xff0c;直接开始安装&#xff01; 安装 2024版本、2025年…

Linux(centos7)安装 docker + ollama+ deepseek-r1:7b + Open WebUI(内含一键安装脚本)

windows版本的 ollama &#xff1a;https://blog.csdn.net/YXWik/article/details/143871588 环境&#xff1a;centos7 文中各个脚本 1.docker安装 或者 需要重新安装&#xff1a; install_docker.sh 2.docker已安装只需要安装 ollama deepseek-r1:7b Open WebUI &#xff1…

深度解析:在Odoo 18中基于原生Owl框架为PWA定制功能丰富的底部导航栏

本文旨在提供一个从架构设计、核心功能实现到高级用户体验优化的全面指南&#xff0c;详细阐述如何在Odoo 18中&#xff0c;完全利用其原生的Owl前端框架&#xff0c;为渐进式网络应用&#xff08;PWA&#xff09;从零开始开发一个功能完备、数据驱动且高度可定制的底部导航栏。…

Java泛型初始化ArrayList<String>()和ArrayList<>()的区别

文章目录前言Java 泛型初始化&#xff1a;ArrayList<String>() vs ArrayList<>() 的区别1. 语法差异1.1 显式泛型初始化 (ArrayList<String>())1.2 钻石操作符初始化 (ArrayList<>())2. 编译与运行时的区别3. 使用场景对比3.1 显式泛型初始化的适用情况…

ubuntu25.04+4070+cuda+docker安装

目录 1.4070nvidia驱动安装 2.CUDA安装 3.docker安装 4.docker的GPU支持 1.4070nvidia驱动安装 首先从软件源获取最新的软件包信息&#xff0c;然后升级一下安装好的软件包 #从软件源获取最新的软件包信息 apt update -y #将已安装的软件包升级到最新版本 apt upgrade -y 然…

Mac m系列 VMware Fusion虚拟机安装ARM contos

一、下载虚拟机 VMware Fusion和 CentOS 安装 VMware Fusion下载地址下载好镜像文件CentOS-Stream-9-20230516.0-aarch64-boot.iso下载地址 二、打开VMware Fusion新建虚拟机 选择从光盘或映像中安装点击继 选择刚才下载的镜像&#xff0c;点击继续选择 Linux > 其他Linu…

MYSQL中NOT IN和NOT EXISTS

NOT IN 和 NOT EXISTS 是 MySQL 中用于排除某些数据的两种常见查询方式。它们的功能相似&#xff0c;都用于返回不满足某一条件的结果&#xff0c;但是它们在内部的实现方式以及某些特定场景下的行为有所不同。1. NOT INNOT IN 是用来排除在指定值集合中存在的值。通常用来与子…

数据库关系运算之连接

在数据库理论中&#xff0c;关系连接&#xff08;Join&#xff09; 是将两个或多个关系&#xff08;表&#xff09;中的元组&#xff08;行&#xff09;根据一定条件组合成新关系的操作&#xff0c;是关系型数据库中核心且高频使用的操作。其本质是通过共享的属性&#xff08;列…

npm全局安装后,依然不是内部或外部命令,也不是可运行的程序或批处理文件

虽然通过 npm install -g yarn 安装了 Yarn&#xff0c;但系统无法识别 yarn 命令。这通常是因为 npm 的全局安装目录没有添加到系统的 PATH 环境变量中C:\Users\Administrator>npm install -g yarnadded 1 package in 518msC:\Users\Administrator>yarn yarn 不是内部或…

C++ Proactor 与 Reactor 网络编程模式

&#x1f9e0; C Proactor 与 Reactor 网络编程模式&#x1f4cc; 核心区别概述特性Reactor 模式Proactor 模式事件驱动核心监听 I/O 就绪事件 (可读/可写)监听 I/O 完成事件 (读完成/写完成)I/O 执行者用户线程 主动执行 I/O 操作操作系统 异步执行 I/O 操作控制流同步非阻塞 …

从手动操作到自动化:火语言 RPA 在多系统协作中的实践

在企业日常运营中&#xff0c;很多业务流程需要在多个系统间来回切换&#xff1a;从 A 系统导出数据&#xff0c;到 B 系统校验格式&#xff0c;再到 C 系统录入信息…… 这些跨系统操作步骤繁琐、逻辑固定&#xff0c;却往往依赖人工完成&#xff0c;不仅效率低下&#xff0c;…