簡(jiǎn)介
OpenAtom OpenHarmony(以下簡(jiǎn)稱“OpenHarmony”)是由開放原子開源基金會(huì)孵化及運(yùn)營(yíng)的開源項(xiàng)目,是面向全場(chǎng)景、全連接、全智能時(shí)代的智能物聯(lián)網(wǎng)操作系統(tǒng)。
多媒體子系統(tǒng)是OpenHarmony系統(tǒng)中的核心子系統(tǒng),為系統(tǒng)提供了相機(jī)、音頻和視頻等多媒體功能。多媒體子系統(tǒng)的音頻模塊、音頻錄音功能可以提供兩套接口,一是由ohos.multimedia.media提供的AudioRecorder接口,能夠直接設(shè)置錄音保存的文件路徑,在錄制結(jié)束以后自動(dòng)生成對(duì)應(yīng)的錄音文件,代碼編寫比較簡(jiǎn)單;二是由ohos.multimedia.audio提供的AudioCapturer接口,能夠獲得錄音過(guò)程中的PCM數(shù)據(jù),并對(duì)數(shù)據(jù)進(jìn)行處理。由于Capturer接口對(duì)于原始數(shù)據(jù)的處理更加靈活,今天就和大家介紹通過(guò)Capturer接口實(shí)現(xiàn)錄音變速的功能的方法。
效果展示
通過(guò)Capturer接口實(shí)現(xiàn)音頻的錄制,在錄制過(guò)程中對(duì)PCM數(shù)據(jù)進(jìn)行重采樣實(shí)現(xiàn)聲音的快放和慢放。
詳細(xì)效果請(qǐng)看下方視頻:
首先設(shè)置錄音加速或者錄音減速,設(shè)置完成以后點(diǎn)擊“錄音開始”按鈕進(jìn)行錄音,點(diǎn)擊“錄音結(jié)束”按鈕停止錄音,再通過(guò)點(diǎn)擊“播放開始”對(duì)錄音的音頻進(jìn)行播放,播放的音頻是設(shè)置后的加速或者減速效果。
代碼已經(jīng)上傳至SIG倉(cāng)庫(kù),鏈接如下:
https://gitee.com/openharmony-sig/knowledge_demo_entainment/tree/master/FA/AudioChangeDemo
目錄結(jié)構(gòu)

調(diào)用流程
1.Start的框架層調(diào)用流程

2. Read的框架層調(diào)用流程

源碼分析
1.首先看一下頁(yè)面的布局,主要分為四個(gè)模塊:(1)設(shè)置錄音加速
設(shè)置錄音加速:1.25倍速1.5倍速1.75倍速2倍速
(2)設(shè)置錄音減速
設(shè)置錄音減速:0.75倍速0.5倍速
(3)錄音
錄音:錄音開始錄音結(jié)束
(4)播放
播放:播放開始播放結(jié)束class="video" src="{{url}}" autoplay="{{ autoplay }}" controls="{{ controlShow }}" muted="false" onseeked="seeked" onprepared="prepared" >
2.邏輯代碼在JS中:
(1)首先通過(guò)AudioCapturer接口獲取到PCM數(shù)據(jù),再通過(guò)調(diào)用AudioCapturer的start接口來(lái)啟動(dòng)錄音流程。
globalThis.capturer.start().then(function () {console.log("gyf start");globalThis.capturer.getBufferSize((err, bufferSize) => {if (err) {console.error('gyf getBufferSize error');} else {console.log("gyf bufferSize = " + bufferSize);globalThis.getBuf(bufferSize);}});});
(2)啟動(dòng)成功以后,getBuf會(huì)調(diào)用到getData函數(shù),getData函數(shù)通過(guò)AudioCapturer的read方法來(lái)讀取數(shù)據(jù),成功讀取到數(shù)據(jù)以后,通過(guò)handleBuffer函數(shù)對(duì)數(shù)據(jù)進(jìn)行處理。handleBuffer函數(shù)的參數(shù)arrayBuffer就是通過(guò)read方法讀取出來(lái)的pcm數(shù)據(jù),在handleBuffer中對(duì)數(shù)據(jù)進(jìn)行了快速播放或者慢速播放的處理。
//循環(huán)調(diào)用read,進(jìn)行數(shù)據(jù)的讀取handleBuffer(arrayBuffer) {console.log("gyf handleBuffer");let result = new Uint8Array(arrayBuffer);console.log("gyf handleBuffer ================== " + result);let outData = this.test(result, up, down);fileio.writeSync(globalThis.fd, outData.buffer);globalThis.capturer.read(globalThis.bufSize, true).then(this.handleBuffer);},getData(bufSize) {console.log("gyf getData");globalThis.capturer.read(bufSize, true).then(this.handleBuffer);},getBuf(bufSize) {console.log("gyf getBuf");this.getData(bufSize);},
(3)快速播放或者慢速播放是通過(guò)up和down兩個(gè)方法的組合來(lái)實(shí)現(xiàn)的,down方法的原理是對(duì)PCM數(shù)據(jù)進(jìn)行插值處理,在相鄰兩點(diǎn)間插入down個(gè)采樣點(diǎn),up方法的原理是間隔抽取,間隔up個(gè)點(diǎn)進(jìn)行抽取采樣。
up(data, up) {if (1 == up) {return data;}let length = data.byteLength;let upLength = Math.round(length / up);var upData = new Uint8Array(upLength);for (var i = 0, j = 0; i < length; ) {if (j >= upLength) {break;}upData[j] = data[i];i += up;j++;}return upData;},down(data, down) {if (1 == down) {return data;}let length = data.byteLength;let downLength = Math.round(length * down);var downData = new Uint8Array(downLength);for (var i = 0, j = 0; i < length - 1; ) {for (var k = 0; k < down; k++) {downData[j] = data[i];j++;}i++;}return downData;},
(4)將down和up的方法組合調(diào)用,來(lái)實(shí)現(xiàn)1.25倍、1.5倍、1.75倍、2倍、0.75倍、0.5倍的速度播放。
test(data, up, down) {let downData = this.down(data, down);let upData = this.up(downData, up);return upData;},
(5)播放wav格式的音頻文件,采集獲取PCM數(shù)據(jù),需要我們根據(jù)設(shè)置的參數(shù)對(duì)pcm數(shù)據(jù)進(jìn)行添加wav的頭部信息,通過(guò)創(chuàng)建AudioCapturer實(shí)例的時(shí)候設(shè)置采集音頻的參數(shù),如采樣率、通道數(shù)、采樣格式等。
//音頻采集初始化var audioStreamInfo = {samplingRate: audio.AudioSamplingRate.SAMPLE_RATE_8000,channels: audio.AudioChannel.CHANNEL_1,sampleFormat: audio.AudioSampleFormat.SAMPLE_FORMAT_U8,encodingType: audio.AudioEncodingType.ENCODING_TYPE_RAW}var audioCapturerInfo = {source: audio.SourceType.SOURCE_TYPE_MIC,capturerFlags: 1}var audioCapturerOptions = {streamInfo: audioStreamInfo,capturerInfo: audioCapturerInfo}let that = this;audio.createAudioCapturer(audioCapturerOptions,(err, data) => {if (err) {console.error(`gyf AudioCapturer Created : Error: ${err.message}`);}else {console.info('gyf AudioCapturer Created : Success : SUCCESS');that.capturer = data;}});
(6)根據(jù)這些參數(shù)設(shè)置的信息需要對(duì)wav文件寫入文件頭,頭信息一般包含44個(gè)字節(jié),里面需要設(shè)置三個(gè)chunk的信息(RIFF chunk、fmt chunk、data chunk),具體的信息可以查看官網(wǎng)的介紹WAV文件格式介紹(http://www-mmsp.ece.mcgill.ca/Documents/AudioFormats/WAVE/WAVE.html)。
//假設(shè)數(shù)據(jù)為1000秒鐘的時(shí)間(8000 * 1000)encodeWAV() {var dataLen = 8000000;var sampleRate = 8000;var sampleBits = 8;var buffer = new ArrayBuffer(44);var data = new DataView(buffer);var channelCount = 1; // 單聲道var offset = 0;// 資源交換文件標(biāo)識(shí)符this.writeString(data, offset, 'RIFF'); offset += 4;// 下個(gè)地址開始到文件尾總字節(jié)數(shù),即文件大小-8data.setUint32(offset, 36 + dataLen, true); offset += 4;// WAV文件標(biāo)志this.writeString(data, offset, 'WAVE'); offset += 4;// 波形格式標(biāo)志this.writeString(data, offset, 'fmt '); offset += 4;// 過(guò)濾字節(jié),一般為 0x10 = 16data.setUint32(offset, 16, true); offset += 4;// 格式類別 (PCM形式采樣數(shù)據(jù))data.setUint16(offset, 1, true); offset += 2;// 通道數(shù)data.setUint16(offset, channelCount, true); offset += 2;// 采樣率,每秒樣本數(shù),表示每個(gè)通道的播放速度data.setUint32(offset, sampleRate, true); offset += 4;// 波形數(shù)據(jù)傳輸率 (每秒平均字節(jié)數(shù)) 單聲道×每秒數(shù)據(jù)位數(shù)×每樣本數(shù)據(jù)位/8data.setUint32(offset, channelCount * sampleRate * (sampleBits / 8), true); offset += 4;// 快數(shù)據(jù)調(diào)整數(shù) 采樣一次占用字節(jié)數(shù) 單聲道×每樣本的數(shù)據(jù)位數(shù)/8data.setUint16(offset, channelCount * (sampleBits / 8), true); offset += 2;// 每樣本數(shù)據(jù)位數(shù)data.setUint16(offset, sampleBits, true); offset += 2;// 數(shù)據(jù)標(biāo)識(shí)符this.writeString(data, offset, 'data'); offset += 4;// 采樣數(shù)據(jù)總數(shù),即數(shù)據(jù)總大小-44data.setUint32(offset, dataLen, true); offset += 4;return data;},
總結(jié)
本文介紹了通過(guò)使用OpenHarmony音頻模塊的AudioCapturer接口實(shí)現(xiàn)錄音功能。AudioCapturer接口對(duì)于原始數(shù)據(jù)的處理非常靈活,能夠?qū)Σ杉臄?shù)據(jù)進(jìn)行插值/抽值的重采樣處理,并將處理后的音頻處理保存至本地文件。由于本地文件使用的是WAV格式,故在寫數(shù)據(jù)前需要對(duì)WAV文件進(jìn)行頭部信息的添加,這些信息可以根據(jù)創(chuàng)建AudioCapturer時(shí)設(shè)置的參數(shù)來(lái)進(jìn)行設(shè)置,以此保證頭部信息的準(zhǔn)確性,最后再通過(guò)應(yīng)用層的video組件對(duì)音頻數(shù)據(jù)進(jìn)行播放。
希望這篇文章能為開發(fā)者提供一些新的思路,從而進(jìn)行其他場(chǎng)景的拓展,例如將獲取到采集的數(shù)據(jù)通過(guò)這種方式實(shí)現(xiàn)語(yǔ)音識(shí)別、語(yǔ)音轉(zhuǎn)寫等功能,在實(shí)踐開發(fā)的過(guò)程中為OpenHarmony生態(tài)的發(fā)展貢獻(xiàn)一份力量。
審核編輯:湯梓紅
-
接口
+關(guān)注
關(guān)注
33文章
9307瀏覽量
155713 -
音頻模塊
+關(guān)注
關(guān)注
1文章
151瀏覽量
12807 -
OpenHarmony
+關(guān)注
關(guān)注
31文章
3897瀏覽量
20496
原文標(biāo)題:如何通過(guò)OpenHarmony的音頻模塊實(shí)現(xiàn)錄音變速功能?
文章出處:【微信號(hào):gh_e4f28cfa3159,微信公眾號(hào):OpenAtom OpenHarmony】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
請(qǐng)問(wèn)如何通過(guò)hdmi接口實(shí)現(xiàn)控制單反拍照及錄像
怎樣使用IWeb接口實(shí)現(xiàn)HTTPS的功能
STM32通過(guò)串口實(shí)現(xiàn)雙機(jī)通信的方法是什么
esp32能否通過(guò)AT接口實(shí)現(xiàn)modbusTCP slaver的功能?
如何通過(guò)OpenHarmony的音頻模塊實(shí)現(xiàn)錄音變速功能?
小波變換在語(yǔ)音變速上的應(yīng)用
如何通過(guò)STM32的串口實(shí)現(xiàn)簡(jiǎn)易脫機(jī)編程器
SPI總線接口實(shí)現(xiàn)數(shù)據(jù)傳輸?shù)?b class='flag-5'>實(shí)現(xiàn)方法
語(yǔ)音變換的語(yǔ)音篡改檢測(cè)方法
ARM與FPGA的接口實(shí)現(xiàn)的解析
C#-Interface接口實(shí)現(xiàn)
通過(guò)標(biāo)準(zhǔn)的CAN接口實(shí)現(xiàn)的測(cè)試臺(tái)自動(dòng)化解決方案
基于XML語(yǔ)言描述的接口實(shí)現(xiàn)方法
RK3399 Ubuntu通過(guò)Python實(shí)現(xiàn)錄音和播放功能

通過(guò)Capturer接口實(shí)現(xiàn)錄音變速功能的方法
評(píng)論