欧美阿v视频在线大全_亚洲欧美中文日韩V在线观看_www性欧美日韩欧美91_亚洲欧美日韩久久精品

主頁 > 知識庫 > HTML5錄音實踐總結(Preact)

HTML5錄音實踐總結(Preact)

熱門標簽:如何獲取地圖標注客戶 機器人外呼系統存在哪些能力 平涼地圖標注位置怎么弄 電話機器人黑斑馬免費 電話機器人電銷系統掙話費 南昌仁和怎么申請開通400電話 只辦理400電話 高德地圖標注地點糾錯 拓展地圖標注

獲取 PCM 數據

處理 PCM 數據

Float32Int16

ArrayBufferBase64

PCM 文件播放

重采樣

PCMMP3

PCMWAV

短時能量計算

Web Worker優化性能

音頻存儲(IndexedDB)

WebView 開啟 WebRTC

獲取 PCM 數據

查看 DEMO

https://github.com/deepkolos/pc-pcm-wave

樣例代碼:

const mediaStream = await window.navigator.mediaDevices.getUserMedia({
    audio: {
		// sampleRate: 44100, // 采樣率 不生效需要手動重采樣
        channelCount: 1, // 聲道
        // echoCancellation: true,
        // noiseSuppression: true, // 降噪 實測效果不錯
    },
})
const audioContext = new window.AudioContext()
const inputSampleRate = audioContext.sampleRate
const mediaNode = audioContext.createMediaStreamSource(mediaStream)

if (!audioContext.createScriptProcessor) {
	audioContext.createScriptProcessor = audioContext.createJavaScriptNode
}
// 創建一個jsNode
const jsNode = audioContext.createScriptProcessor(4096, 1, 1)
jsNode.connect(audioContext.destination)
jsNode.onaudioprocess = (e) => {
    // e.inputBuffer.getChannelData(0) (left)
    // 雙通道通過e.inputBuffer.getChannelData(1)獲取 (right)
}
mediaNode.connect(jsNode)

簡要流程如下:

start=>start: 開始
getUserMedia=>operation: 獲取MediaStream
audioContext=>operation: 創建AudioContext
scriptNode=>operation: 創建scriptNode并關聯AudioContext
onaudioprocess=>operation: 設置onaudioprocess并處理數據
end=>end: 結束

start->getUserMedia->audioContext->scriptNode->onaudioprocess->end

停止錄制只需要把 audioContext 掛在的 node 卸載即可,然后把存儲的每一幀數據合并即可產出 PCM 數據

jsNode.disconnect()
mediaNode.disconnect()
jsNode.onaudioprocess = null

PCM 數據處理

通過 WebRTC 獲取的 PCM 數據格式是 Float32 的, 如果是雙通道錄音的話, 還需要增加合并通道

const leftDataList = [];
const rightDataList = [];
function onAudioProcess(event) {
  // 一幀的音頻PCM數據
  let audioBuffer = event.inputBuffer;
  leftDataList.push(audioBuffer.getChannelData(0).slice(0));
  rightDataList.push(audioBuffer.getChannelData(1).slice(0));
}

// 交叉合并左右聲道的數據
function interleaveLeftAndRight(left, right) {
  let totalLength = left.length + right.length;
  let data = new Float32Array(totalLength);
  for (let i = 0; i < left.length; i++) {
    let k = i * 2;
    data[k] = left[i];
    data[k + 1] = right[i];
  }
  return data;
}

Float32 轉 Int16

const float32 = new Float32Array(1)
const int16 = Int16Array.from(
	float32.map(x => (x > 0 ? x * 0x7fff : x * 0x8000)),
)

arrayBuffer 轉 Base64

注意: 在瀏覽器上有個 btoa() 函數也是可以轉換為 Base64 但是輸入參數必須為字符串, 如果傳遞 buffer 參數會先被 toString() 然后再 Base64 , 使用 ffplay 播放反序列化的 Base64 , 會比較刺耳

使用 base64-arraybuffer 即可完成

import { encode } from 'base64-arraybuffer'

const float32 = new Float32Array(1)
const int16 = Int16Array.from(
	float32.map(x => (x > 0 ? x * 0x7fff : x * 0x8000)),
)
console.log(encode(int16.buffer))

驗證 Base64 是否正確, 可以在 node 下把產出的 Base64 轉換為 Int16 的 PCM 文件, 然后使用 FFPlay 播放, 看看音頻是否正常播放

PCM 文件播放

# 單通道 采樣率:16000 Int16
ffplay -f s16le -ar 16k -ac 1 test.pcm

# 雙通道 采樣率:48000 Float32
ffplay -f f32le -ar 48000 -ac 2 test.pcm

重采樣/調整采樣率

雖然 getUserMedia 參數可設置采樣率, 但是在最新Chrome也不生效, 所以需要手動做個重采樣

const mediaStream = await window.navigator.mediaDevices.getUserMedia({
    audio: {
    	// sampleRate: 44100, // 采樣率 設置不生效
        channelCount: 1, // 聲道
        // echoCancellation: true, // 減低回音
        // noiseSuppression: true, // 降噪, 實測效果不錯
    },
})

使用 wave-resampler 即可完成

import { resample } from 'wave-resampler'

const inputSampleRate =  44100
const outputSampleRate = 16000
const resampledBuffers = resample(
    // 需要onAudioProcess每一幀的buffer合并后的數組
	mergeArray(audioBuffers),
	inputSampleRate,
	outputSampleRate,
)

PCM 轉 MP3

import { Mp3Encoder } from 'lamejs'

let mp3buf
const mp3Data = []
const sampleBlockSize = 576 * 10 // 工作緩存區, 576的倍數
const mp3Encoder = new Mp3Encoder(1, outputSampleRate, kbps)
const samples = float32ToInt16(
  audioBuffers,
  inputSampleRate,
  outputSampleRate,
)

let remaining = samples.length
for (let i = 0; remaining >= 0; i += sampleBlockSize) {
  const left = samples.subarray(i, i + sampleBlockSize)
  mp3buf = mp3Encoder.encodeBuffer(left)
  mp3Data.push(new Int8Array(mp3buf))
  remaining -= sampleBlockSize
}

mp3Data.push(new Int8Array(mp3Encoder.flush()))
console.log(mp3Data)

// 工具函數
function float32ToInt16(audioBuffers, inputSampleRate, outputSampleRate) {
  const float32 = resample(
    // 需要onAudioProcess每一幀的buffer合并后的數組
    mergeArray(audioBuffers),
    inputSampleRate,
    outputSampleRate,
  )
  const int16 = Int16Array.from(
    float32.map(x => (x > 0 ? x * 0x7fff : x * 0x8000)),
  )
  return int16
}

使用 lamejs 即可, 但是體積較大(160+KB), 如果沒有存儲需求可使用 WAV 格式

> ls -alh
-rwxrwxrwx 1 root root  95K  4月 22 12:45 12s.mp3*
-rwxrwxrwx 1 root root 1.1M  4月 22 12:44 12s.wav*
-rwxrwxrwx 1 root root 235K  4月 22 12:41 30s.mp3*
-rwxrwxrwx 1 root root 2.6M  4月 22 12:40 30s.wav*
-rwxrwxrwx 1 root root  63K  4月 22 12:49 8s.mp3*
-rwxrwxrwx 1 root root 689K  4月 22 12:48 8s.wav*

PCM 轉 WAV

function mergeArray(list) {
  const length = list.length * list[0].length
  const data = new Float32Array(length)
  let offset = 0
  for (let i = 0; i < list.length; i++) {
    data.set(list[i], offset)
    offset += list[i].length
  }
  return data
}

function writeUTFBytes(view, offset, string) {
  var lng = string.length
  for (let i = 0; i < lng; i++) {
    view.setUint8(offset + i, string.charCodeAt(i))
  }
}

function createWavBuffer(audioData, sampleRate = 44100, channels = 1) {
  const WAV_HEAD_SIZE = 44
  const buffer = new ArrayBuffer(audioData.length * 2 + WAV_HEAD_SIZE)
  // 需要用一個view來操控buffer
  const view = new DataView(buffer)
  // 寫入wav頭部信息
  // RIFF chunk descriptor/identifier
  writeUTFBytes(view, 0, 'RIFF')
  // RIFF chunk length
  view.setUint32(4, 44 + audioData.length * 2, true)
  // RIFF type
  writeUTFBytes(view, 8, 'WAVE')
  // format chunk identifier
  // FMT sub-chunk
  writeUTFBytes(view, 12, 'fmt')
  // format chunk length
  view.setUint32(16, 16, true)
  // sample format (raw)
  view.setUint16(20, 1, true)
  // stereo (2 channels)
  view.setUint16(22, channels, true)
  // sample rate
  view.setUint32(24, sampleRate, true)
  // byte rate (sample rate * block align)
  view.setUint32(28, sampleRate * 2, true)
  // block align (channel count * bytes per sample)
  view.setUint16(32, channels * 2, true)
  // bits per sample
  view.setUint16(34, 16, true)
  // data sub-chunk
  // data chunk identifier
  writeUTFBytes(view, 36, 'data')
  // data chunk length
  view.setUint32(40, audioData.length * 2, true)

  // 寫入PCM數據
  let index = 44
  const volume = 1
  const { length } = audioData
  for (let i = 0; i < length; i++) {
    view.setInt16(index, audioData[i] * (0x7fff * volume), true)
    index += 2
  }
  return buffer
}

// 需要onAudioProcess每一幀的buffer合并后的數組
createWavBuffer(mergeArray(audioBuffers))

WAV 基本上是 PCM 加上一些音頻信息

簡單的短時能量計算

function shortTimeEnergy(audioData) {
  let sum = 0
  const energy = []
  const { length } = audioData
  for (let i = 0; i < length; i++) {
    sum += audioData[i] ** 2

    if ((i + 1) % 256 === 0) {
      energy.push(sum)
      sum = 0
    } else if (i === length - 1) {
      energy.push(sum)
    }
  }
  return energy
}

由于計算結果有會因設備的錄音增益差異較大, 計算出數據也較大, 所以使用比值簡單區分人聲和噪音

查看 DEMO

const NoiseVoiceWatershedWave = 2.3
const energy = shortTimeEnergy(e.inputBuffer.getChannelData(0).slice(0))
const avg = energy.reduce((a, b) => a + b) / energy.length

const nextState = Math.max(...energy) / avg > NoiseVoiceWatershedWave ? 'voice' : 'noise'

Web Worker 優化性能

音頻數據數據量較大, 所以可以使用 Web Worker 進行優化, 不卡 UI 線程

在 Webpack 項目里 Web Worker 比較簡單, 安裝 worker-loader 即可

preact.config.js

export default (config, env, helpers) => {
    config.module.rules.push({
        test: /\.worker\.js$/,
        use: { loader: 'worker-loader', options: { inline: true } },
      })
}

recorder.worker.js

self.addEventListener('message', event => {
  console.log(event.data)
  // 轉MP3/轉Base64/轉WAV等等
  const output = ''
  self.postMessage(output)
}

使用 Worker

async function toMP3(audioBuffers, inputSampleRate, outputSampleRate = 16000) {
  const { default: Worker } = await import('./recorder.worker')
  const worker = new Worker()
  // 簡單使用, 項目可以在recorder實例化的時候創建worker實例, 有并法需求可多個實例

  return new Promise(resolve => {
    worker.postMessage({
      audioBuffers: audioBuffers,
      inputSampleRate: inputSampleRate,
      outputSampleRate: outputSampleRate,
      type: 'mp3',
    })
    worker.onmessage = event => resolve(event.data)
  })
}

音頻的存儲

瀏覽器持久化儲存的地方有 LocalStorage 和 IndexedDB , 其中 LocalStorage 較為常用, 但是只能儲存字符串, 而 IndexedDB 可直接儲存 Blob , 所以優先選擇 IndexedDB ,使用 LocalStorage 則需要轉 Base64 體積將會更大

所以為了避免占用用戶太多空間, 所以選擇MP3格式進行存儲

> ls -alh
-rwxrwxrwx 1 root root  95K  4月 22 12:45 12s.mp3*
-rwxrwxrwx 1 root root 1.1M  4月 22 12:44 12s.wav*
-rwxrwxrwx 1 root root 235K  4月 22 12:41 30s.mp3*
-rwxrwxrwx 1 root root 2.6M  4月 22 12:40 30s.wav*
-rwxrwxrwx 1 root root  63K  4月 22 12:49 8s.mp3*
-rwxrwxrwx 1 root root 689K  4月 22 12:48 8s.wav*

IndexedDB 簡單封裝如下, 熟悉后臺的同學可以找個 ORM 庫方便數據讀寫

const indexedDB =
  window.indexedDB ||
  window.webkitIndexedDB ||
  window.mozIndexedDB ||
  window.OIndexedDB ||
  window.msIndexedDB

const IDBTransaction =
  window.IDBTransaction ||
  window.webkitIDBTransaction ||
  window.OIDBTransaction ||
  window.msIDBTransaction

const readWriteMode =
  typeof IDBTransaction.READ_WRITE === 'undefined'
    ? 'readwrite'
    : IDBTransaction.READ_WRITE

const dbVersion = 1
const storeDefault = 'mp3'

let dbLink

function initDB(store) {
  return new Promise((resolve, reject) => {
    if (dbLink) resolve(dbLink)

    // Create/open database
    const request = indexedDB.open('audio', dbVersion)

    request.onsuccess = event => {
      const db = request.result

      db.onerror = event => {
        reject(event)
      }

      if (db.version === dbVersion) resolve(db)
    }

    request.onerror = event => {
      reject(event)
    }

    // For future use. Currently only in latest Firefox versions
    request.onupgradeneeded = event => {
      dbLink = event.target.result
      const { transaction } = event.target

      if (!dbLink.objectStoreNames.contains(store)) {
        dbLink.createObjectStore(store)
      }

      transaction.oncomplete = event => {
        // Now store is available to be populated
        resolve(dbLink)
      }
    }
  })
}

export const writeIDB = async (name, blob, store = storeDefault) => {
  const db = await initDB(store)

  const transaction = db.transaction([store], readWriteMode)
  const objStore = transaction.objectStore(store)

  return new Promise((resolve, reject) => {
    const request = objStore.put(blob, name)
    request.onsuccess = event => resolve(event)
    request.onerror = event => reject(event)
    transaction.commit && transaction.commit()
  })
}

export const readIDB = async (name, store = storeDefault) => {
  const db = await initDB(store)

  const transaction = db.transaction([store], readWriteMode)
  const objStore = transaction.objectStore(store)

  return new Promise((resolve, reject) => {
    const request = objStore.get(name)
    request.onsuccess = event => resolve(event.target.result)
    request.onerror = event => reject(event)
    transaction.commit && transaction.commit()
  })
}

export const clearIDB = async (store = storeDefault) => {
  const db = await initDB(store)

  const transaction = db.transaction([store], readWriteMode)
  const objStore = transaction.objectStore(store)
  return new Promise((resolve, reject) => {
    const request = objStore.clear()
    request.onsuccess = event => resolve(event)
    request.onerror = event => reject(event)
    transaction.commit && transaction.commit()
  })
}

WebView 開啟 WebRTC

見 WebView WebRTC not working

webView.setWebChromeClient(new WebChromeClient(){
	@TargetApi(Build.VERSION_CODES.LOLLIPOP)
	@Override
	public void onPermissionRequest(final PermissionRequest request) {
		request.grant(request.getResources());
	}
});

到此這篇關于HTML5錄音實踐總結(Preact)的文章就介紹到這了,更多相關html5錄音內容請搜索腳本之家以前的文章或繼續瀏覽下面的相關文章,希望大家以后多多支持腳本之家!

標簽:遼源 漯河 青島 西藏 棗莊 新疆 永州 池州

巨人網絡通訊聲明:本文標題《HTML5錄音實踐總結(Preact)》,本文關鍵詞  HTML5,錄音,實踐,總結,Preact,;如發現本文內容存在版權問題,煩請提供相關信息告之我們,我們將及時溝通與處理。本站內容系統采集于網絡,涉及言論、版權與本站無關。
  • 相關文章
  • 下面列出與本文章《HTML5錄音實踐總結(Preact)》相關的同類信息!
  • 本頁收集關于HTML5錄音實踐總結(Preact)的相關信息資訊供網民參考!
  • 推薦文章
    欧美阿v视频在线大全_亚洲欧美中文日韩V在线观看_www性欧美日韩欧美91_亚洲欧美日韩久久精品
  • <rt id="w000q"><acronym id="w000q"></acronym></rt>
  • <abbr id="w000q"></abbr>
    <rt id="w000q"></rt>
    久久嫩草捆绑紧缚| 中日韩av电影| 亚洲高清中文字幕| 日韩理论片中文av| 国产ts人妖一区二区| 国产在线综合视频| 久久众筹精品私拍模特| 久久av资源网| 美国黄色特级片| 国产日韩欧美精品电影三级在线 | 中文幕一区二区三区久久蜜桃| 韩国一区二区视频| 一级片久久久久| 久久这里只有精品视频网| 久久国产麻豆精品| 亚洲ⅴ国产v天堂a无码二区| 国产亚洲欧美日韩日本| 国产福利91精品一区| www.97视频| 黄色精品视频在线观看| 成人免费视频在线观看| 99久久精品一区| 欧美卡一卡二卡三| 亚洲三级在线免费观看| aaaaa黄色片| 欧美一级爆毛片| 久久综合综合久久综合| 久久精品三级视频| 国产精品欧美极品| 99精品欧美一区二区蜜桃免费 | 国产精品99久久久久久久vr| 美女三级黄色片| 亚洲免费资源在线播放| 激情小说欧美色图| 日韩精品中文字幕在线一区| 精品一区二区三区在线播放| 小向美奈子av| 一区二区三区精密机械公司| 国产精品嫩草av| 久久久久久久久久美女| 成人免费的视频| 欧美日韩精品久久久| 日韩精品欧美精品| 日本人亚洲人jjzzjjz| 最新国产精品久久精品| 91传媒理伦片在线观看| 久久亚洲影视婷婷| 99精品欧美一区| 欧美变态口味重另类| 国v精品久久久网| 欧美日韩免费电影| 韩国v欧美v日本v亚洲v| 91成人免费在线| 日本中文一区二区三区| 又嫩又硬又黄又爽的视频| 亚洲三级免费电影| 毛茸茸free性熟hd| 久久婷婷久久一区二区三区| 99精品视频一区二区| 日韩视频123| 成人ar影院免费观看视频| 国产尤物在线播放| 亚洲国产成人91porn| 大胸美女被爆操| 亚洲精品成人天堂一二三| 黄色aaa视频| 亚洲美女淫视频| 麻豆精品免费视频| 亚洲激情网站免费观看| 神马久久久久久久久久久| 一级做a爱片久久| 男人天堂资源网| 日韩主播视频在线| 色综合久久天天| 九九精品视频在线看| 欧美日韩久久久久久| 国产精品456| 日韩美女在线视频| 99精品桃花视频在线观看| 欧美精品一区二区三区蜜臀| 97精品视频在线观看自产线路二| 精品理论电影在线观看| 台湾佬美性中文| 国产精品久线观看视频| 久久久久久九九九九九| 亚洲国产另类av| 日本中文在线视频| 久久国内精品视频| 538prom精品视频线放| 99久久婷婷国产综合精品电影| 久久品道一品道久久精品| 日韩Av无码精品| 亚洲女人****多毛耸耸8| 国产又粗又硬视频| 青青草成人在线观看| 欧美午夜影院一区| 成人av网站大全| 国产女人18毛片水真多成人如厕| 在线免费观看成年人视频| 亚洲国产欧美在线| 在线观看一区不卡| 成人av资源在线观看| 国产午夜亚洲精品理论片色戒 | 国产高清成人久久| 一区二区三区中文字幕精品精品| 欧美成人777| 国产成人在线观看免费网站| 精品免费国产二区三区| 亚洲精品中文字幕在线播放| 亚洲高清久久久| 欧美三级电影网站| 女王人厕视频2ⅴk| 一区二区三区在线视频播放| 天天看片中文字幕| 成人午夜激情视频| 欧美高清一级片在线观看| 一级二级黄色片| 国产一区二区三区不卡在线观看 | 日本女人性视频| 亚洲欧美国产三级| 色哟哟一区二区| 99精品黄色片免费大全| 18涩涩午夜精品.www| 色综合一个色综合| 99久久精品国产网站| 亚洲日本va在线观看| 在线观看一区二区视频| 国产精品一级无码| 亚洲成人第一页| 欧美一卡2卡3卡4卡| 欧美丰满少妇人妻精品| 奇米在线7777在线精品| 精品国产乱码久久久久久蜜臀| 精品国产无码在线观看| 精品一区二区成人精品| 久久精品夜色噜噜亚洲aⅴ| 日韩一区二区三区四区视频| 国产成人在线视频网站| 中文字幕中文字幕中文字幕亚洲无线 | 亚洲一区二区三区激情| 欧美日产在线观看| 一出一进一爽一粗一大视频| 免费成人在线视频观看| 精品成人一区二区三区四区| 美国美女黄色片| 暴力调教一区二区三区| 亚洲综合视频网| 日韩一区二区三区高清免费看看| 亚洲最大成人网站| 国产精品亚洲а∨天堂免在线| 国产精品理论片| 欧美网站一区二区| 欧美在线一级片| 久久国产福利国产秒拍| 国产精品理论片| 欧美日韩国产首页在线观看| jizz日本免费| 国内精品伊人久久久久影院对白| 国产精品精品国产色婷婷| 欧美午夜精品电影| 男人操女人动态图| 成人国产精品免费| 亚洲不卡av一区二区三区| 久久综合久久综合久久综合| 国产三级国产精品国产国在线观看 | 国产视频不卡在线| 99re这里只有精品首页| 天堂影院一区二区| 国产欧美日韩综合| 欧美日韩一二区| 一区二区伦理片| 91网站最新地址| 久久精品国产99| 亚洲女同ⅹxx女同tv| 日韩欧美激情一区| 少妇久久久久久被弄高潮| 一区二区视频观看| 成人av网站在线观看| 日韩av在线播放中文字幕| 国产精品久久久久影院| 7777精品伊人久久久大香线蕉| 激情五月深爱五月| 国产艳妇疯狂做爰视频| 国产一区二区三区美女| 亚洲国产精品天堂| 久久久久久久综合色一本| 欧美日韩午夜精品| 国产精品夜夜夜爽阿娇| 极品白嫩的小少妇| 高清成人在线观看| 青娱乐精品视频| 一区二区三区日本| 国产日产欧美一区二区三区| 欧美精品乱码久久久久久| 欧美三级黄色大片| 国产交换配乱淫视频免费| 91丨porny丨在线| 国产美女在线精品| 日韩电影免费一区| 亚洲黄色小视频|