【鴻蒙HarmonyOS Next App實戰開發】視頻提取音頻

在多媒體處理場景中,經常需要從視頻文件中提取純凈的音頻軌道。本文將介紹如何在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;…