一、開篇引入
在當今數字化時代,多媒體已經深度融入我們的日常生活。無論是在工作中通過視頻會議進行溝通協作,還是在學習時借助在線課程的音頻講解加深理解,亦或是在休閑時光用手機播放音樂放松身心、觀看視頻打發時間,多媒體功能都無處不在,成為我們生活中不可或缺的一部分。對于應用開發者而言,為 HarmonyOS 應用增添音頻和視頻功能,不僅能夠滿足用戶日益增長的多樣化需求,還能顯著提升應用的競爭力和用戶體驗。接下來,就讓我們一起深入探索如何在 HarmonyOS 應用中實現音頻和視頻的播放、錄制功能,以及處理多媒體文件的格式轉換和編輯。
二、音頻播放功能實現
(一)MediaPlayer 基本介紹
在 HarmonyOS 多媒體開發中,MediaPlayer 是一個極為核心的類,承擔著播放音頻和視頻的重要職責。它就像是一位專業的 “演奏家”,能夠識別多種常見的音頻格式,如 MP3、AAC、WAV 等 ,并將這些音頻文件中的數字信號轉化為美妙的聲音。無論是播放本地存儲的音樂文件,還是從網絡上實時獲取的音頻流,MediaPlayer 都能游刃有余地應對,為用戶帶來豐富的聽覺體驗。通過它,開發者可以輕松實現音頻的播放、暫停、停止、跳轉等操作,為應用增添強大的音頻交互功能。
(二)創建 MediaPlayer 實例
創建 MediaPlayer 實例是實現音頻播放的第一步。在 HarmonyOS 中,我們可以通過以下代碼簡單地創建一個 MediaPlayer 對象:
MediaPlayer mediaPlayer = new MediaPlayer(); |
需要注意的是,創建實例時應確保在合適的生命周期方法中進行,比如在 Ability 的 onStart 方法中。這樣可以保證 MediaPlayer 在應用啟動時被正確初始化,避免出現空指針異常等問題。同時,要記得為 MediaPlayer 對象添加必要的異常處理機制,以應對可能出現的錯誤情況,確保應用的穩定性。
(三)設置音頻源
設置音頻源是告訴 MediaPlayer 要播放的音頻文件來自哪里。MediaPlayer 提供了多種設置音頻源的方式,常見的有以下兩種:
使用本地文件路徑:如果音頻文件存儲在本地設備上,可以直接使用文件路徑來設置音頻源。例如:
try { ????mediaPlayer.setDataSource("/data/local/tmp/your_audio_file.mp3"); } catch (Exception e) { ????e.printStackTrace(); } |
這里的/data/local/tmp/your_audio_file.mp3需要替換為實際的音頻文件路徑。在使用本地文件路徑時,要確保應用具有訪問該文件的權限,否則會導致設置音頻源失敗。
使用 URI:當音頻文件的位置可以通過統一資源標識符(URI)來表示時,我們可以使用setDataSource(Context context, Uri uri)方法來設置音頻源。例如,從應用的資源目錄中獲取音頻文件:
Uri uri = Uri.parse("dataability:///resource/raw/your_audio_file.mp3"); try { ????mediaPlayer.setDataSource(getContext(), uri); } catch (Exception e) { ????e.printStackTrace(); } |
其中dataability:///resource/raw/your_audio_file.mp3是音頻文件在資源目錄下的 URI,開發者需要根據實際的資源路徑進行修改。使用 URI 的方式更加靈活,適用于從不同來源獲取音頻文件的場景。
(四)準備與播放音頻
在設置好音頻源后,需要調用prepareAsync()方法來準備音頻文件。這個方法是異步的,它會在后臺線程中進行音頻文件的準備工作,避免阻塞主線程,從而保證應用的流暢運行。當音頻準備完成后,會觸發OnPreparedListener回調。在這個回調中,我們可以調用start()方法來啟動音頻播放。示例代碼如下:
mediaPlayer.prepareAsync(); mediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() { ????@Override ????public void onPrepared(MediaPlayer mp) { ????????mp.start(); ????} }); |
這種異步準備和回調機制,使得音頻播放的過程更加高效和穩定,尤其是在處理較大的音頻文件或網絡音頻流時,能夠避免因長時間的準備工作而導致應用界面卡頓,為用戶提供更優質的播放體驗。
(五)釋放資源
當音頻播放完成或者不再需要播放音頻時,務必調用release()方法來釋放 MediaPlayer 占用的資源。這是一個非常重要的步驟,如果不及時釋放資源,可能會導致內存泄漏,影響應用的性能,甚至導致應用崩潰。釋放資源的代碼如下:
if (mediaPlayer != null) { ????mediaPlayer.stop(); ????mediaPlayer.release(); ????mediaPlayer = null; } |
通常在 Ability 的 onStop 或 onDestroy 方法中進行資源釋放操作,以確保在應用暫停或銷毀時,MediaPlayer 占用的資源能夠被正確回收,為其他應用或系統進程騰出寶貴的資源空間。
三、音頻錄制功能實現
(一)權限申請
在 HarmonyOS 應用中實現音頻錄制功能,首先必須申請麥克風權限,這是獲取音頻輸入的關鍵前提。因為音頻錄制涉及到對用戶聲音的采集,屬于敏感操作,所以 HarmonyOS 將麥克風權限歸類為用戶授權類型。這意味著應用不僅要在配置文件中聲明該權限,還需要在運行時動態請求用戶授權。
在使用的模塊下的module.json5中添加權限聲明,示例代碼如下:
{ ????"name": "ohos.permission.MICROPHONE", ????"reason": "$string:permission_desc_for_MICROPHONE", ????"usedScene": { ????????"abilities": ["EntryAbility"], ????????"when": "inuse" ????} } |
其中,reason字段用于說明申請權限的原因,會在向用戶請求授權時展示,所以要填寫清晰明了的描述,讓用戶能夠理解應用為什么需要該權限;usedScene字段指定了權限的使用場景,這里表明在EntryAbility中使用,并且是在使用相關功能時才申請。
在代碼中動態申請授權,可以參考以下示例:
import abilityAccessCtrl from '@ohos.abilityAccessCtrl'; import common from '@ohos.app.ability.common'; export function requestMicrophonePermission(context: common.UIAbilityContext, permissionResult: (allow: boolean) => void): void { ????let atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager(); ????let permissions: Array<string> = ['ohos.permission.MICROPHONE']; ????atManager.requestPermissionsFromUser(context, permissions).then((data) => { ????????let grantStatus: Array<number> = data.authResults; ????????let length: number = grantStatus.length; ????????for (let i = 0; i < length; i++) { ????????????if (grantStatus[i] === 0) { ????????????????permissionResult(true); ????????????????console.debug("麥克風授權成功:用戶授權"); ????????????} else { ????????????????permissionResult(false); ????????????????console.debug("麥克風授權失敗:用戶拒絕"); ????????????????return; ????????????} ????????} ????}).catch((err: BusinessError) => { ????????permissionResult(false); ????????console.error(`Failed to request permissions from user. Code is ${err.code}, message is ${err.message}`); ????}); } |
在上述代碼中,首先創建了AtManager實例,然后調用requestPermissionsFromUser方法向用戶請求麥克風權限。該方法會返回一個 Promise,通過then方法處理授權成功的情況,通過catch方法處理請求權限過程中可能出現的錯誤。當用戶授權成功時,grantStatus[i]的值為 0,此時調用permissionResult(true)通知調用者權限已被授予;如果用戶拒絕授權,grantStatus[i]的值不為 0,調用permissionResult(false)并提示用戶拒絕授權。
(二)AVRecorder 配置
AVRecorder 是 HarmonyOS 中用于音頻和視頻錄制的重要類,在使用它進行音頻錄制前,需要進行一系列關鍵配置。以下是一些主要的配置參數:
音頻源類型(audioSourceType):用于指定音頻輸入源,通常設置為media.AudioSourceType.AUDIO_SOURCE_TYPE_MIC,表示使用麥克風作為音頻輸入源。這是最常見的音頻錄制場景,例如錄制語音備忘錄、會議記錄等。
采樣率(audioSampleRate):它決定了每秒采集的音頻樣本數量,單位是赫茲(Hz)。常見的采樣率有 44100Hz、48000Hz 等 。較高的采樣率可以更準確地還原聲音,但會占用更多的存儲空間和處理資源。在選擇采樣率時,需要根據實際需求和應用場景進行權衡。例如,對于一般的語音錄制,44100Hz 已經足夠;而對于高質量的音樂錄制,可能需要選擇 48000Hz 甚至更高的采樣率。
編碼格式(audioCodec):指定音頻的編碼方式,當前 HarmonyOS 中 AVRecorder 支持的音頻編碼格式如media.CodecMimeType.AUDIO_AAC?。不同的編碼格式在壓縮比、音質和兼容性等方面存在差異。AAC 是一種廣泛應用的音頻編碼格式,它在保持較高音質的同時,能夠實現較好的壓縮效果,文件體積相對較小,因此在很多場景中都被優先選用。
封裝格式(fileFormat):確定錄制生成的音頻文件的封裝格式,例如media.ContainerFormatType.CFT_MPEG_4A表示生成的音頻文件為 M4A 格式 。封裝格式決定了音頻數據如何存儲在文件中,以及文件的結構和元數據信息。M4A 格式是一種常見的音頻封裝格式,它與 AAC 編碼格式配合良好,能夠有效地存儲和傳輸音頻數據。
以下是一個 AVRecorder 配置的示例代碼:
import media from '@ohos.multimedia.media'; let avProfile = { ????audioBitrate: 100000, // 音頻比特率 ????audioChannels: 2, // 音頻聲道數 ????audioCodec: media.CodecMimeType.AUDIO_AAC, // 音頻編碼格式,當前只支持aac ????audioSampleRate: 48000, // 音頻采樣率 ????fileFormat: media.ContainerFormatType.CFT_MPEG_4A // 封裝格式,當前只支持m4a }; let avConfig = { ????audioSourceType: media.AudioSourceType.AUDIO_SOURCE_TYPE_MIC, // 音頻輸入源,這里設置為麥克風 ????profile: avProfile, ????url: 'fd://35' // 參考應用文件訪問與管理中的開發示例獲取創建的音頻文件fd填入此處 }; |
在這個示例中,首先定義了一個avProfile對象,設置了音頻錄制的各項參數,包括音頻比特率、聲道數、編碼格式、采樣率和封裝格式。然后創建了avConfig對象,將音頻源類型設置為麥克風,并關聯了前面定義的avProfile對象。同時,還需要指定錄制文件的存儲位置,這里使用fd://35的形式,其中35需要替換為實際通過文件操作獲取到的文件描述符(fd)。在實際應用中,獲取文件描述符的過程需要參考 HarmonyOS 的文件訪問與管理相關文檔和示例代碼,確保能夠正確創建和訪問用于存儲錄制音頻的文件。
(三)錄音操作控制
完成權限申請和 AVRecorder 配置后,就可以進行錄音操作控制了。這包括開始、暫停、恢復和停止錄音等操作,同時要妥善處理錄音過程中可能出現的異常情況,以保證錄音功能的穩定性和可靠性。
開始錄音:調用 AVRecorder 的start()方法開始錄音。在調用該方法前,需要確保 AVRecorder 已經完成配置并處于prepared狀態。示例代碼如下:
avRecorder.start().then(() => { ????console.log('錄音開始成功'); }).catch((err) => { ????console.error(`錄音開始失敗,錯誤信息:${err.message}`); }); |
在這個代碼片段中,start()方法返回一個 Promise,通過then方法處理錄音開始成功的情況,在控制臺打印 “錄音開始成功”;通過catch方法捕獲可能出現的錯誤,并在控制臺輸出錯誤信息,以便開發者進行調試和問題排查。
暫停錄音:當需要暫停錄音時,調用pause()方法。但要注意,只有在 AVRecorder 處于started狀態時,調用pause()方法才是合理的狀態切換。示例代碼如下:
if (avRecorder.state ==='started') { ????avRecorder.pause().then(() => { ????????console.log('錄音暫停成功'); ????}).catch((err) => { ????????console.error(`錄音暫停失敗,錯誤信息:${err.message}`); ????}); } |
這里首先檢查 AVRecorder 的當前狀態是否為started,如果是,則調用pause()方法。同樣,通過then和catch方法分別處理暫停成功和失敗的情況。
恢復錄音:如果之前暫停了錄音,可以調用resume()方法恢復錄音。只有在 AVRecorder 處于paused狀態時,才能成功調用該方法。示例代碼如下:
if (avRecorder.state === 'paused') { ????avRecorder.resume().then(() => { ????????console.log('錄音恢復成功'); ????}).catch((err) => { ????????console.error(`錄音恢復失敗,錯誤信息:${err.message}`); ????}); } |
此代碼邏輯與暫停錄音類似,先檢查狀態,再進行恢復操作,并處理相應的結果。
停止錄音:當錄音完成或需要停止錄音時,調用stop()方法。停止錄音后,通常還需要調用reset()方法重置 AVRecorder 的狀態,使其回到idle狀態,以便進行下一次錄音配置和操作;最后調用release()方法釋放 AVRecorder 占用的資源,避免內存泄漏。示例代碼如下:
if (avRecorder.state ==='started' || avRecorder.state === 'paused') { ????avRecorder.stop().then(() => { ????????console.log('錄音停止成功'); ????????avRecorder.reset().then(() => { ????????????console.log('AVRecorder已重置'); ????????????avRecorder.release().then(() => { ????????????????console.log('AVRecorder資源已釋放'); ????????????}).catch((err) => { ????????????????console.error(`釋放AVRecorder資源失敗,錯誤信息:${err.message}`); ????????????}); ????????}).catch((err) => { ????????????console.error(`重置AVRecorder失敗,錯誤信息:${err.message}`); ????????}); ????}).catch((err) => { ????????console.error(`錄音停止失敗,錯誤信息:${err.message}`); ????}); } |
這段代碼展示了停止錄音的完整流程,先判斷 AVRecorder 的狀態是否為started或paused,如果是,則依次調用stop()、reset()和release()方法,并在每個操作成功或失敗時進行相應的日志記錄和錯誤處理。
在錄音過程中,還可能出現各種異常情況,例如設備故障、權限被收回等。為了及時處理這些異常,可以為 AVRecorder 添加錯誤監聽事件。示例代碼如下:
avRecorder.on('error', (err) => { ????console.error(`錄音過程中出現錯誤,錯誤代碼:${err.code},錯誤信息:${err.message}`); ????// 這里可以添加一些錯誤處理邏輯,比如提示用戶、嘗試重新錄制等 }); |
通過on('error', callback)方法,當 AVRecorder 在錄音過程中發生錯誤時,會調用傳入的回調函數callback,在回調函數中可以獲取到錯誤信息,包括錯誤代碼和詳細的錯誤描述,開發者可以根據這些信息進行針對性的處理,例如向用戶顯示友好的錯誤提示,或者嘗試重新初始化 AVRecorder 并進行錄音操作,以提高應用的穩定性和用戶體驗。
四、視頻播放功能實現
(一)播放框架概述
HarmonyOS 的視頻播放框架旨在為開發者提供高效、便捷且強大的視頻播放能力。其設計目標圍繞著低消耗、簡單易用和靈活擴展展開。低消耗意味著在播放視頻時,框架能夠優化資源的使用,降低設備的功耗,確保即使在長時間播放視頻的情況下,設備也能保持良好的性能表現,不會出現過熱、卡頓等問題。簡單易用體現在框架提供了簡潔明了的 API,無論是對于經驗豐富的開發者還是初學者,都能快速上手并實現基本的視頻播放功能。同時,它還提供了多種接口形式,如 JS 接口和結合 ArkUI 提供的 UI 控件接口,滿足不同開發場景和習慣的需求。靈活擴展則使得框架能夠適應不斷發展的視頻技術和多樣化的應用需求,開發者可以根據實際情況對播放引擎進行增強、擴展或替換,以實現更豐富的功能和更好的播放效果。
從架構上看,播放框架主要由中間件和硬件適配層組成。中間件的核心是基于引擎提供各種各樣的服務能力,目前 HarmonyOS 提供了 GStreamer 引擎和 HiStreamer 引擎 ,這兩套引擎功能都較為齊全,能夠支持多種常見的視頻格式和編碼方式。例如,GStreamer 引擎是一個功能強大的多媒體框架,它具有豐富的插件生態系統,能夠處理各種復雜的多媒體任務;HiStreamer 引擎則以其輕量化和高效性著稱,基于傳統的 pipeline,通過插件化機制增強了音視頻的編解碼和解析能力,在處理一些對性能要求較高的視頻播放場景時表現出色。在硬件適配層,即 HDF(Hardware Driver Foundation)層,提供了兼容設計,能夠適配不同的硬件設備,確保視頻播放功能在各種終端設備上都能穩定運行。這種分層架構的設計,使得播放框架具有良好的可維護性和可擴展性,開發者可以專注于應用層的開發,而無需過多關注底層硬件的差異和復雜的多媒體處理細節。
(二)使用 MediaPlayer 播放視頻
使用 MediaPlayer 播放視頻的步驟與播放音頻有相似之處,但也存在一些關鍵的區別。在創建 MediaPlayer 實例方面,與音頻播放一致,通過MediaPlayer mediaPlayer = new MediaPlayer();即可創建。然而,在設置視頻源時,同樣可以使用本地文件路徑或 URI 的方式。例如,使用本地文件路徑設置視頻源:
try { ????mediaPlayer.setDataSource("/data/local/tmp/your_video_file.mp4"); } catch (Exception e) { ????e.printStackTrace(); } |
這里的/data/local/tmp/your_video_file.mp4需替換為實際的視頻文件路徑,并且要確保應用具有訪問該文件的權限。使用 URI 設置視頻源時,示例如下:
Uri uri = Uri.parse("dataability:///resource/raw/your_video_file.mp4"); try { ????mediaPlayer.setDataSource(getContext(), uri); } catch (Exception e) { ????e.printStackTrace(); } |
其中dataability:///resource/raw/your_video_file.mp4是視頻文件在資源目錄下的 URI,開發者需根據實際資源路徑修改。
與音頻播放最大的不同在于,視頻播放需要設置一個 Surface 用于顯示視頻畫面。Surface 是一個可以在其上繪制圖形的對象,在視頻播放中,它充當了視頻畫面的載體。我們可以通過創建 SurfaceView 來獲取 Surface,然后將其設置給 MediaPlayer。首先,在 XML 布局文件中添加 SurfaceView:
<ohos.agp.components.SurfaceView ????ohos:id="$+id:surface_view" ????ohos:height="match_parent" ????ohos:width="match_parent" /> |
然后在代碼中獲取 SurfaceView 并設置給 MediaPlayer:
SurfaceView surfaceView = (SurfaceView) findComponentById(ResourceTable.Id_surface_view); Surface surface = surfaceView.getSurface(); mediaPlayer.setVideoSurface(surface); |
在準備和播放視頻階段,與音頻播放類似,調用prepareAsync()方法異步準備視頻,當準備完成后,通過OnPreparedListener回調中的start()方法啟動播放:
mediaPlayer.prepareAsync(); mediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() { ????@Override ????public void onPrepared(MediaPlayer mp) { ????????mp.start(); ????} }); |
通過以上步驟,就可以實現基本的視頻播放功能,將視頻文件的內容在設備屏幕上展示出來。
(三)處理視頻播放的交互
為了提升用戶體驗,視頻播放應用通常需要實現一系列交互功能,如播放進度控制、音量調節、暫停與播放切換等。
播放進度控制可以通過 SeekBar 組件和 MediaPlayer 的seekTo(int msec)方法來實現。SeekBar 是一個拖動條組件,用戶可以通過拖動它來改變視頻的播放位置。首先,在 XML 布局文件中添加 SeekBar:
<ohos.agp.components.SeekBar ????ohos:id="$+id:seek_bar" ????ohos:height="wrap_content" ????ohos:width="match_parent" /> |
然后在代碼中獲取 SeekBar 并為其添加進度改變監聽器。當用戶拖動 SeekBar 時,監聽器會獲取當前的進度值,并調用seekTo(int msec)方法將視頻播放位置調整到對應的時間點。示例代碼如下:
SeekBar seekBar = (SeekBar) findComponentById(ResourceTable.Id_seek_bar); seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { ????@Override ????public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { ????????if (fromUser) { ????????????mediaPlayer.seekTo(progress); ????????} ????} ????@Override ????public void onStartTrackingTouch(SeekBar seekBar) { ????????// 開始拖動時的操作 ????} ????@Override ????public void onStopTrackingTouch(SeekBar seekBar) { ????????// 停止拖動時的操作 ????} }); |
同時,為了實時更新 SeekBar 的進度,使其與視頻的實際播放進度保持一致,可以通過一個定時任務來獲取視頻的當前播放位置,并更新 SeekBar 的進度。例如,使用 Handler 實現定時更新:
Handler handler = new Handler(); Runnable runnable = new Runnable() { ????@Override ????public void run() { ????????if (mediaPlayer!= null) { ????????????int currentPosition = mediaPlayer.getCurrentPosition(); ????????????seekBar.setProgress(currentPosition); ????????} ????????handler.postDelayed(this, 1000); ????} }; handler.post(runnable); |
這樣,每隔 1 秒就會獲取一次視頻的當前播放位置,并更新 SeekBar 的進度,讓用戶能夠直觀地了解視頻的播放進度。
音量調節可以通過 AudioManager 類來實現。AudioManager 是 HarmonyOS 中用于管理音頻相關設置的類,我們可以通過它來獲取當前的音量,并實現音量的增加和減少操作。首先獲取 AudioManager 實例:
AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE); |
然后通過getStreamVolume(int streamType)方法獲取當前的音量,streamType參數指定音頻流類型,對于視頻播放,通常使用AudioManager.STREAM_MUSIC。例如:
int currentVolume = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC); |
實現音量增加和減少的操作可以通過adjustVolume(int direction, int flags)方法,direction參數指定調節方向,AudioManager.ADJUST_RAISE表示增加音量,AudioManager.ADJUST_LOWER表示減少音量。示例代碼如下:
// 增加音量 audioManager.adjustVolume(AudioManager.ADJUST_RAISE, AudioManager.FLAG_SHOW_UI); // 減少音量 audioManager.adjustVolume(AudioManager.ADJUST_LOWER, AudioManager.FLAG_SHOW_UI); |
這里的AudioManager.FLAG_SHOW_UI標志表示在調節音量時顯示系統的音量調節 UI,讓用戶能夠直觀地看到音量的變化。
暫停與播放切換是視頻播放中最基本的交互功能之一,通過 MediaPlayer 的pause()和start()方法即可實現。可以在界面上添加一個按鈕,當用戶點擊按鈕時,根據當前視頻的播放狀態來決定執行暫停還是播放操作。示例代碼如下:
Button playPauseButton = (Button) findComponentById(ResourceTable.Id.play_pause_button); playPauseButton.setOnClickListener(new Component.OnClickListener() { ????@Override ????public void onClick(Component component) { ????????if (mediaPlayer.isPlaying()) { ????????????mediaPlayer.pause(); ????????????playPauseButton.setText("播放"); ????????} else { ????????????mediaPlayer.start(); ????????????playPauseButton.setText("暫停"); ????????} ????} }); |
通過以上對播放進度控制、音量調節、暫停與播放切換等交互功能的實現,能夠為用戶提供更加豐富和便捷的視頻播放體驗,滿足用戶在觀看視頻過程中的各種操作需求。
五、視頻錄制功能實現
(一)相機權限與配置
在 HarmonyOS 應用中實現視頻錄制功能,首先需要申請相機權限。相機權限屬于敏感權限,HarmonyOS 對其管理較為嚴格,應用不僅要在module.json5文件中聲明權限,還需要在運行時動態請求用戶授權。在module.json5文件中添加相機權限聲明的示例代碼如下:
{ ????"name": "ohos.permission.CAMERA", ????"reason": "$string:permission_desc_for_CAMERA", ????"usedScene": { ????????"abilities": ["EntryAbility"], ????????"when": "inuse" ????} } |
這里的reason字段用于向用戶解釋申請權限的原因,usedScene字段指定了權限的使用場景,表明在EntryAbility中使用該權限,并且是在使用相關功能時才申請。
在運行時動態請求相機權限,可以使用abilityAccessCtrl模塊中的requestPermissionsFromUser方法。示例代碼如下:
import abilityAccessCtrl from '@ohos.abilityAccessCtrl'; import common from '@ohos.app.ability.common'; export function requestCameraPermission(context: common.UIAbilityContext, permissionResult: (allow: boolean) => void): void { ????let atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager(); ????let permissions: Array<string> = ['ohos.permission.CAMERA']; ????atManager.requestPermissionsFromUser(context, permissions).then((data) => { ????????let grantStatus: Array<number> = data.authResults; ????????let length: number = grantStatus.length; ????????for (let i = 0; i < length; i++) { ????????????if (grantStatus[i] === 0) { ????????????????permissionResult(true); ????????????????console.debug("相機授權成功:用戶授權"); ????????????} else { ????????????????permissionResult(false); ????????????????console.debug("相機授權失敗:用戶拒絕"); ????????????????return; ????????????} ????????} ????}).catch((err: BusinessError) => { ????????permissionResult(false); ????????console.error(`Failed to request permissions from user. Code is ${err.code}, message is ${err.message}`); ????}); } |
在上述代碼中,首先創建了AtManager實例,然后調用requestPermissionsFromUser方法向用戶請求相機權限。該方法返回一個 Promise,通過then方法處理授權成功的情況,通過catch方法處理請求權限過程中可能出現的錯誤。當用戶授權成功時,grantStatus[i]的值為 0,此時調用permissionResult(true)通知調用者權限已被授予;如果用戶拒絕授權,grantStatus[i]的值不為 0,調用permissionResult(false)并提示用戶拒絕授權。
配置相機參數是實現高質量視頻錄制的關鍵步驟。相機參數包括分辨率、幀率、對焦模式、曝光模式等 。不同的參數設置會對視頻的質量和性能產生顯著影響。例如,較高的分辨率可以提供更清晰的圖像細節,但會占用更多的存儲空間和網絡帶寬;較高的幀率可以使視頻播放更加流暢,但也會增加設備的處理負擔。因此,在配置相機參數時,需要根據實際需求和設備性能進行權衡和選擇。
以設置分辨率和幀率為例,在 HarmonyOS 中可以使用CameraConfiguration類來配置相機參數。示例代碼如下:
import ohos.media.camera.CameraConfiguration; import ohos.media.camera.CameraDevice; CameraDevice cameraDevice = getCameraDevice(); // 獲取相機設備實例 CameraConfiguration configuration = new CameraConfiguration(cameraDevice); configuration.setPreviewSize(1920, 1080); // 設置預覽分辨率為1920x1080 configuration.setPreviewFpsRange(new int[]{30, 30}); // 設置預覽幀率為30fps cameraDevice.applyConfiguration(configuration); // 應用配置 |
在上述代碼中,首先獲取相機設備實例,然后創建CameraConfiguration對象,并通過setPreviewSize和setPreviewFpsRange方法分別設置預覽分辨率和幀率。最后,調用applyConfiguration方法將配置應用到相機設備上。除了分辨率和幀率,還可以通過CameraConfiguration類設置其他相機參數,如對焦模式、曝光模式等 。例如,設置自動對焦模式的代碼如下:
configuration.setFocusMode(CameraConfiguration.FOCUS_MODE_CONTINUOUS_PICTURE); |
設置自動曝光模式的代碼如下:
configuration.setExposureMode(CameraConfiguration.EXPOSURE_MODE_AUTO); |
通過合理配置這些相機參數,可以滿足不同場景下的視頻錄制需求,為用戶提供高質量的視頻錄制體驗。
(二)使用 Camera 進行視頻錄制
在 HarmonyOS 中,使用 Camera 進行視頻錄制的核心步驟是獲取視頻流并結合 AVRecorder 進行錄制。首先,需要創建相機輸入和輸出流。相機輸入流用于獲取相機采集的數據,相機輸出流則用于將視頻數據輸出到 AVRecorder 進行錄制。
創建相機輸入流的示例代碼如下:
import camera from '@ohos.multimedia.camera'; // 獲取相機管理器 let cameraManager: camera.CameraManager = camera.getCameraManager(context); // 獲取相機列表 let cameraArray: Array<camera.CameraDevice> = cameraManager.getSupportedCameras(); // 創建相機輸入 let cameraInput: camera.CameraInput = cameraManager.createCameraInput(cameraArray[0]); |
在上述代碼中,首先通過camera.getCameraManager(context)獲取相機管理器,然后使用getSupportedCameras方法獲取設備支持的相機列表。最后,選擇第一個相機設備并通過createCameraInput方法創建相機輸入流。
創建相機預覽輸出流和視頻輸出流的示例代碼如下:
// 創建預覽輸出流 let previewOutput: camera.PreviewOutput = cameraManager.createPreviewOutput(previewProfilesArray[0], surfaceId); // 創建視頻輸出流 let videoProfile: undefined | camera.VideoProfile = videoProfilesArray.find((profile: camera.VideoProfile) => { ????return profile.size.width === aVRecorderProfile.videoFrameWidth && profile.size.height === aVRecorderProfile.videoFrameHeight; }); let videoOutput: camera.VideoOutput | undefined = undefined; if (videoProfile) { ????videoOutput = cameraManager.createVideoOutput(videoProfile, videoSurfaceId); } |
在這段代碼中,首先創建預覽輸出流,其中previewProfilesArray是相機支持的預覽配置文件數組,surfaceId是用于顯示預覽畫面的 Surface 的 ID。然后,從相機支持的視頻配置文件數組videoProfilesArray中找到與 AVRecorder 配置的視頻分辨率相匹配的視頻配置文件videoProfile,并使用createVideoOutput方法創建視頻輸出流,videoSurfaceId是 AVRecorder 用于接收視頻數據的 Surface 的 ID。
創建好相機輸入和輸出流后,需要配置相機會話,將輸入流和輸出流添加到會話中,并啟動會話。示例代碼如下:
// 創建會話 let videoSession: camera.CaptureSession | undefined = undefined; videoSession = cameraManager.createCaptureSession(); videoSession.beginConfig(); // 添加相機輸入流 videoSession.addInput(cameraInput); // 添加預覽輸出流 videoSession.addOutput(previewOutput); // 添加視頻輸出流 if (videoOutput) { ????videoSession.addOutput(videoOutput); } // 提交會話配置 videoSession.commitConfig(); // 啟動會話 videoSession.start(); |
在上述代碼中,首先創建相機會話videoSession,然后使用beginConfig方法開始配置會話。接著,將相機輸入流、預覽輸出流和視頻輸出流添加到會話中。完成配置后,調用commitConfig方法提交配置,并使用start方法啟動會話。此時,相機開始采集視頻數據,并將數據輸出到預覽輸出流和視頻輸出流中。
AVRecorder 是 HarmonyOS 中用于音視頻錄制的重要類,它負責將相機輸出的視頻流進行編碼、封裝,并保存為視頻文件。在使用 AVRecorder 進行視頻錄制前,需要進行一系列配置,包括設置視頻源類型、編碼格式、封裝格式、視頻分辨率、幀率等參數。配置 AVRecorder 的示例代碼如下:
import media from '@ohos.multimedia.media'; // 創建AVRecorder實例 let avRecorder: media.AVRecorder = await media.createAVRecorder(); // 配置AVRecorder參數 let aVRecorderProfile: media.AVRecorderProfile = { ????fileFormat: media.ContainerFormatType.CFT_MPEG_4, // 視頻文件封裝格式,只支持MP4 ????videoBitrate: 2000000, // 視頻比特率 ????videoCodec: media.CodecMimeType.VIDEO_AVC, // 視頻文件編碼格式,支持avc格式 ????videoFrameWidth: 640, // 視頻分辨率的寬 ????videoFrameHeight: 480, // 視頻分辨率的高 ????videoFrameRate: 30 // 視頻幀率 }; let aVRecorderConfig: media.AVRecorderConfig = { ????videoSourceType: media.VideoSourceType.VIDEO_SOURCE_TYPE_SURFACE_YUV, // 視頻源類型,支持YUV和ES兩種格式 ????profile: aVRecorderProfile, ????url: 'fd://' + file.fd, // 參考應用文件訪問與管理中的開發示例獲取創建的音頻文件fd填入此處 ????rotation: 0 // 視頻旋轉角度,默認為0不旋轉,支持的值為0、90、180、270 }; // 準備AVRecorder await avRecorder.prepare(aVRecorderConfig); |
在上述代碼中,首先創建 AVRecorder 實例,然后配置錄制參數。aVRecorderProfile對象設置了視頻的封裝格式、比特率、編碼格式、分辨率和幀率等參數;aVRecorderConfig對象設置了視頻源類型、配置文件、錄制文件的存儲路徑(url)和視頻旋轉角度。最后,調用prepare方法準備 AVRecorder,使其處于可錄制狀態。
完成上述步驟后,相機采集的視頻流就會通過視頻輸出流傳遞給 AVRecorder,AVRecorder 根據配置的參數對視頻流進行編碼、封裝,并將錄制的視頻保存到指定的文件路徑中。
(三)錄制控制與文件保存
實現錄制的開始、暫停、停止操作是視頻錄制功能的基本需求。在 HarmonyOS 中,通過 AVRecorder 和相機輸出流的相關方法可以輕松實現這些控制操作。
開始錄制時,需要同時啟動相機的視頻輸出流和 AVRecorder 的錄制功能。示例代碼如下:
// 啟動相機視頻輸出流 if (videoOutput) { ????videoOutput.start(); } // 啟動AVRecorder錄制 avRecorder.start().then(() => { ????console.log('視頻錄制開始成功'); }).catch((err) => { ????console.error(`視頻錄制開始失敗,錯誤信息:${err.message}`); }); |
在上述代碼中,首先判斷videoOutput是否存在,如果存在則調用其start方法啟動相機視頻輸出流。然后,調用 AVRecorder 的start方法開始錄制視頻,start方法返回一個 Promise,通過then方法處理錄制開始成功的情況,通過catch方法捕獲并處理可能出現的錯誤。
暫停錄制時,需要同時暫停相機的視頻輸出流和 AVRecorder 的錄制功能。示例代碼如下:
// 暫停相機視頻輸出流 if (videoOutput) { ????videoOutput.stop(); } // 暫停AVRecorder錄制 if (avRecorder.state ==='started') { ????avRecorder.pause().then(() => { ????????console.log('視頻錄制暫停成功'); ????}).catch((err) => { ????????console.error(`視頻錄制暫停失敗,錯誤信息:${err.message}`); ????}); } |
在這段代碼中,首先判斷videoOutput是否存在,如果存在則調用其stop方法暫停相機視頻輸出流。然后,檢查 AVRecorder 的當前狀態是否為started,如果是則調用pause方法暫停錄制,并通過then和catch方法處理暫停操作的結果。
停止錄制時,同樣需要停止相機的視頻輸出流和 AVRecorder 的錄制功能,并釋放相關資源。示例代碼如下:
// 停止相機視頻輸出流 if (videoOutput) { ????videoOutput.stop(); } // 停止AVRecorder錄制 if (avRecorder.state ==='started' || avRecorder.state === 'paused') { ????avRecorder.stop().then(() => { ????????console.log('視頻錄制停止成功'); ????????avRecorder.reset().then(() => { ????????????console.log('AVRecorder已重置'); ????????????avRecorder.release().then(() => { ????????????????console.log('AVRecorder資源已釋放'); ????????????}).catch((err) => { ????????????????console.error(`釋放AVRecorder資源失敗,錯誤信息:${err.message}`); ????????????}); ????????}).catch((err) => { ????????????console.error(`重置AVRecorder失敗,錯誤信息:${err.message}`); ????????}); ????}).catch((err) => { ????????console.error(`視頻錄制停止失敗,錯誤信息:${err.message}`); ????}); } |
在上述代碼中,首先停止相機視頻輸出流。然后,檢查 AVRecorder 的狀態,如果處于started或paused狀態,則調用stop方法停止錄制。停止錄制后,依次調用reset方法重置 AVRecorder 的狀態,使其回到初始狀態,以便進行下一次錄制配置和操作;最后調用release方法釋放 AVRecorder 占用的資源,避免內存泄漏,并通過then和catch方法處理每個操作的結果。
將錄制的視頻保存到指定路徑是視頻錄制功能的最終目標。在配置 AVRecorder 時,通過aVRecorderConfig對象的url參數指定了錄制文件的存儲路徑,例如url: 'fd://' + file.fd?,其中file.fd是通過文件操作獲取到的文件描述符(fd)。在實際應用中,需要根據具體的文件訪問與管理方式獲取文件描述符,并確保應用具有對該文件的讀寫權限。
在錄制完成后,視頻文件就會保存到指定的路徑中。為了方便用戶管理和使用錄制的視頻,還可以在應用中提供一些文件管理功能,例如顯示錄制視頻的列表、提供視頻的播放和分享功能等。通過這些功能,用戶可以更加便捷地查看和使用自己錄制的視頻,提升應用的實用性和用戶體驗。
六、多媒體文件格式轉換和編輯
(一)格式轉換的必要性
在多媒體應用開發中,不同設備和平臺對多媒體格式的支持存在顯著差異。例如,某些老舊設備可能僅支持常見的 MP3 音頻格式和 MP4 視頻格式,而對于一些新興的、高壓縮比或特殊用途的格式,如 FLAC 音頻格式、AV1 視頻格式等則無法識別和播放。在不同的平臺上,像 Windows 系統原生支持的多媒體格式與 HarmonyOS、iOS 系統所支持的格式也不盡相同。這就導致當我們開發的應用需要在多種設備和平臺上運行時,如果多媒體文件格式不兼容,就會出現無法播放或顯示異常的問題。因此,為了確保多媒體內容能夠在各種設備和平臺上穩定、流暢地運行,實現多媒體文件格式轉換是非常必要的。它能夠使我們的應用適應不同的環境,滿足更廣泛用戶的需求,提升應用的通用性和用戶體驗。
(二)HarmonyOS 的格式轉換工具與 API
在 HarmonyOS 中,AVCodec Kit 是實現多媒體文件格式轉換的重要工具。它提供了豐富的 API,能夠實現音頻、視頻格式的轉換。以視頻格式轉換為例,我們可以使用AVTranscoder類來完成轉換操作。
首先,需要創建AVTranscoder實例,代碼如下:
import { media } from '@ohos.multimedia.media'; let avTranscoder: media.AVTranscoder; media.createAVTranscoder().then((transcoder: media.AVTranscoder) => { ????avTranscoder = transcoder; }, (error) => { ????console.error(`createAVTranscoder failed`); }); |
在創建實例后,需要設置轉碼的源文件和目標文件。這里的源文件和目標文件可以通過文件描述符(fd)來指定。例如,設置源文件屬性fdSrc和目標文件屬性fdDst:
// 設置轉碼的源文件屬性fdSrc avTranscoder.fdSrc = sourceFileFd; // 設置轉碼的目標文件屬性fdDst avTranscoder.fdDst = targetFileFd; |
其中sourceFileFd和targetFileFd需要根據實際情況獲取,比如使用本地資源轉碼時,要確認資源文件可用,并使用應用沙箱路徑訪問對應資源;如果使用ResourceManager.getRawFd打開 HAP 資源文件描述符,可參考ResourceManager?API 參考。
接下來,配置視頻轉碼參數,調用prepare()接口。配置參數時要注意,prepare接口的入參avConfig中僅設置轉碼相關的配置參數,示例代碼如下:
let avConfig: media.AVTranscoderConfig = { ????audioBitrate: 100000, // 音頻比特率 ????audioCodec: media.CodecMimeType.AUDIO_AAC, ????fileFormat: media.ContainerFormatType.CFT_MPEG_4A, ????videoBitrate: 2000000, // 視頻比特率 ????videoCodec: media.CodecMimeType.VIDEO_AVC, ????videoFrameWidth: 640, ?// 視頻分辨率的寬 ????videoFrameHeight: 480 ?// 視頻分辨率的高 }; avTranscoder.prepare(avConfig).then(() => { ????console.log('Invoke prepare succeeded.'); }, (err) => { ????console.error(`Invoke prepare failed, code is ${err.code}, message is ${err.message}`); }); |
完成上述配置后,就可以調用start()方法開始轉碼:
avTranscoder.start(); |
通過以上步驟,利用 AVCodec Kit 提供的AVTranscoder類及其相關 API,就能夠在 HarmonyOS 應用中實現視頻格式的轉換。對于音頻格式轉換,原理和步驟類似,只需根據音頻的特點和需求,合理配置相關參數即可。
(三)多媒體文件編輯功能
HarmonyOS 提供了一系列 API 來實現多媒體文件的剪輯、合并等編輯操作。以視頻剪輯為例,我們可以利用AVTranscoder類結合時間軸的概念來實現。通過設置起始時間和結束時間,AVTranscoder可以從原始視頻中提取出指定時間段的內容,從而實現剪輯功能。例如,假設我們要剪輯一個視頻,從第 5 秒開始,到第 10 秒結束,代碼實現如下:
// 設置轉碼參數時,添加剪輯相關參數 let avConfig: media.AVTranscoderConfig = { ????// 其他轉碼參數... ????startTime: 5000, // 起始時間,單位毫秒 ????endTime: 10000 ?// 結束時間,單位毫秒 }; // 配置轉碼參數 avTranscoder.prepare(avConfig).then(() => { ????// 開始轉碼,此時輸出的視頻即為剪輯后的視頻 ????avTranscoder.start(); }, (err) => { ????console.error(`Invoke prepare failed, code is ${err.code}, message is ${err.message}`); }); |
對于視頻合并操作,可以通過將多個視頻的軌道數據進行合并來實現。首先,需要使用解封裝器(如AVDemuxer)將每個視頻文件解封裝,獲取其視頻流數據和音頻流數據。然后,將這些流數據按照一定的順序合并到一個新的視頻軌道和音頻軌道中。最后,使用封裝器(如AVMuxer)將合并后的軌道數據封裝成一個新的視頻文件。在 HarmonyOS 中,雖然沒有直接提供一個簡單的合并 API,但通過組合使用這些音視頻處理類,可以實現視頻合并的功能。具體實現過程涉及到對音視頻數據的讀取、處理和重新封裝,需要開發者具備一定的音視頻處理知識和編程技巧。音頻文件的剪輯和合并操作原理與視頻類似,也是通過對音頻數據的讀取、處理和重新封裝來實現。例如,音頻剪輯可以通過解封裝音頻文件,定位到指定的時間點,讀取該時間范圍內的音頻數據,然后重新封裝成新的音頻文件;音頻合并則是將多個音頻文件的音頻數據依次合并到一個新的音頻文件中。通過這些 API 和操作,開發者可以在 HarmonyOS 應用中實現豐富的多媒體文件編輯功能,滿足用戶對多媒體內容個性化處理的需求。
七、常見問題與解決方案
在 HarmonyOS 應用開發中,實現音頻和視頻功能時可能會遇到各種問題,下面為大家列舉一些常見問題及解決方案:
格式不支持:當使用 MediaPlayer 播放音頻或視頻時,可能會遇到不支持的文件格式,導致無法播放。解決方案是在播放前對文件格式進行檢查和轉換。可以通過查詢 HarmonyOS 支持的多媒體格式列表,確認文件格式是否在支持范圍內。對于不支持的格式,利用 AVCodec Kit 提供的轉碼工具和 API,將其轉換為支持的格式。例如,將 FLAC 音頻格式轉換為 MP3 格式,將 AVI 視頻格式轉換為 MP4 格式等。
權限不足:在進行音頻錄制、視頻錄制或訪問多媒體文件時,如果權限不足,會導致操作失敗。對于權限問題,首先要在module.json5文件中正確聲明所需權限,如麥克風權限(ohos.permission.MICROPHONE)、相機權限(ohos.permission.CAMERA)、讀取音頻文件權限(ohos.permission.READ_AUDIO)等 。然后在運行時,使用abilityAccessCtrl模塊中的requestPermissionsFromUser方法動態請求用戶授權。在請求授權時,要向用戶清晰地解釋申請權限的原因,提高用戶授權的成功率。同時,在權限申請成功或失敗時,要進行相應的邏輯處理,比如權限申請失敗時,提示用戶開啟權限的方法,引導用戶進行操作。
資源沖突:當多個組件同時使用多媒體資源時,可能會出現資源沖突的情況。例如,同時有兩個音頻播放任務在競爭音頻輸出設備,或者視頻錄制和相機預覽同時占用相機資源等。為了避免資源沖突,在開發過程中,要合理規劃多媒體資源的使用。可以使用單例模式來管理多媒體資源,確保同一時間只有一個組件能夠訪問和使用特定的資源。例如,創建一個音頻播放管理器類,使用單例模式來控制音頻播放的實例,當有新的音頻播放請求時,先檢查當前是否有正在播放的音頻,如果有,則暫停或停止當前播放,再進行新的播放操作。在進行視頻錄制和相機預覽等操作時,也要進行資源的協調和管理,避免沖突的發生。
八、總結與展望
在 HarmonyOS 應用開發中,實現音頻和視頻功能為應用帶來了豐富的交互體驗和更廣闊的應用場景。通過 MediaPlayer、AVRecorder、Camera 等關鍵 API,我們能夠輕松實現音頻和視頻的播放、錄制功能,并且利用 AVCodec Kit 等工具完成多媒體文件的格式轉換和編輯操作。在這個過程中,我們需要關注權限申請、資源管理以及各種異常情況的處理,以確保應用的穩定性和可靠性。
隨著技術的不斷發展,未來多媒體開發的趨勢將更加注重用戶體驗的提升、跨平臺兼容性以及與新興技術的融合。例如,隨著 5G 技術的普及,高清、流暢的視頻流傳輸將成為常態,這對視頻播放和錄制功能提出了更高的要求;人工智能技術在多媒體內容分析、智能編輯等方面的應用也將日益廣泛;虛擬現實(VR)和增強現實(AR)技術與多媒體的結合,將為用戶帶來沉浸式的體驗,開辟新的應用領域。
希望本文能夠為大家在 HarmonyOS 多媒體開發的道路上提供有價值的參考和幫助。相信大家在實踐過程中,能夠不斷探索和創新,開發出更多功能強大、體驗優秀的多媒體應用。