成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

如何在瀏覽器中播放pcm音頻

zxhaaa / 1765人閱讀

摘要:格式文件中不包含頭部信息,播放器無(wú)法知道采樣率,聲道數(shù),采樣位數(shù),音頻數(shù)據(jù)大小等信息,導(dǎo)致無(wú)法播放。

本文記錄一點(diǎn)工作經(jīng)歷,探討音頻文件的格式
更多訪問(wèn)我的博客
前言

最近在整理音視頻編程的知識(shí),回憶起半年多,有一次需求是在后臺(tái)播放某來(lái)源的 pcm 文件,當(dāng)時(shí)處理方法用了點(diǎn)技巧,記錄下來(lái)

背景:業(yè)務(wù)需求,在web后臺(tái)里播放 pcm 文件,文件不大(約300KB,已知 pcm 的參數(shù)采樣率16000,采樣位數(shù)16,聲道數(shù)1

如何播放

瀏覽器是無(wú)法直接播放 pcm 音頻的,因?yàn)?pcm 是比較原始的音頻格式:

PCM(Puls Code Modulation)全稱脈碼調(diào)制錄音,PCM錄音就是將聲音的模擬信號(hào)表示成0,1標(biāo)識(shí)的數(shù)字信號(hào),未經(jīng)任何編碼和壓縮處理,所以可以認(rèn)為PCM是未經(jīng)壓縮的音頻原始格式。PCM格式文件中不包含頭部信息,播放器無(wú)法知道采樣率,聲道數(shù),采樣位數(shù),音頻數(shù)據(jù)大小等信息,導(dǎo)致無(wú)法播放。

如何讓瀏覽器識(shí)別 pcm

瀏覽器可以播放另一種音頻格式:WAV格式全稱為WAVE,前面提到只需要在PCM文件的前面添加WAV文件頭,就可以生成WAV格式文件

所以我的解決方法是給 pcm 添加 wav header,接下來(lái)就是 browser javascript 的實(shí)踐編碼了

javascript 如何處理文件流

js 在處理文件流、網(wǎng)絡(luò)數(shù)據(jù),常用到 ArrayBuffer 類型,關(guān)于 ArrayBuffer 類型的API調(diào)用方法,需要事先多了解。

第一步,ajax異步獲取網(wǎng)絡(luò) pcm 文件的 ArrayBuffer

const getWebFileArrayBuffer = async (url) => {
  return await fetch(url).then(response => response.arrayBuffer())
}

第二步,對(duì)獲取的 pcm 文件流 ArrayBuffer 添加 wav header,我們先弄明白 header 的構(gòu)造:

看以上圖片,我們需要將獲取到的 pcm data 添加 44 bytes 的 header,根據(jù) header 的結(jié)構(gòu),對(duì)齊、緊湊地填充信息,在 javascript 中,需要使用 DataView 類型幫助我們進(jìn)行字節(jié)填充的操作,注意DataView提供的 API 默認(rèn)使用 little end 的數(shù)據(jù)格式,需要額外定義 big end 格式填充字節(jié)的方法。

以下以代碼來(lái)說(shuō)明如何一步一步填充字節(jié)信息:

const getWebPcm2WavArrayBuffer = async (url) => {
  const bytes = await getWebFileArrayBuffer(url)
  return addWavHeader(bytes, 16000, 16, 1) // 這里是當(dāng)前業(yè)務(wù)需求,特定的參數(shù),采樣率16000,采樣位數(shù)16,聲道數(shù)1
}

const addWavHeader = function (samples, sampleRateTmp, sampleBits, channelCount) {
  let dataLength = samples.byteLength
  /* 新的buffer類,預(yù)留 44 bytes 的 heaer 空間 */
  let buffer = new ArrayBuffer(44 + dataLength)
  /* 轉(zhuǎn)為 Dataview, 利用 API 來(lái)填充字節(jié) */
  let view = new DataView(buffer)
  /* 定義一個(gè)內(nèi)部函數(shù),以 big end 數(shù)據(jù)格式填充字符串至 DataView */
  function writeString (view, offset, string) {
    for (let i = 0; i < string.length; i++) {
      view.setUint8(offset + i, string.charCodeAt(i))
    }
  }

  let offset = 0
  /* ChunkID, 4 bytes,  資源交換文件標(biāo)識(shí)符 */
  writeString(view, offset, "RIFF"); offset += 4
  /* ChunkSize, 4 bytes, 下個(gè)地址開(kāi)始到文件尾總字節(jié)數(shù),即文件大小-8 */
  view.setUint32(offset, /* 32 */ 36 + dataLength, true); offset += 4
  /* Format, 4 bytes, WAV文件標(biāo)志 */
  writeString(view, offset, "WAVE"); offset += 4
  /* Subchunk1 ID, 4 bytes, 波形格式標(biāo)志 */
  writeString(view, offset, "fmt "); offset += 4
  /* Subchunk1 Size, 4 bytes, 過(guò)濾字節(jié),一般為 0x10 = 16 */
  view.setUint32(offset, 16, true); offset += 4
  /* Audio Format, 2 bytes, 格式類別 (PCM形式采樣數(shù)據(jù)) */
  view.setUint16(offset, 1, true); offset += 2
  /* Num Channels, 2 bytes,  通道數(shù) */
  view.setUint16(offset, channelCount, true); offset += 2
  /* SampleRate, 4 bytes, 采樣率,每秒樣本數(shù),表示每個(gè)通道的播放速度 */
  view.setUint32(offset, sampleRateTmp, true); offset += 4
  /* ByteRate, 4 bytes, 波形數(shù)據(jù)傳輸率 (每秒平均字節(jié)數(shù)) 通道數(shù)×每秒數(shù)據(jù)位數(shù)×每樣本數(shù)據(jù)位/8 */
  view.setUint32(offset, sampleRateTmp * channelCount * (sampleBits / 8), true); offset += 4
  /* BlockAlign, 2 bytes, 快數(shù)據(jù)調(diào)整數(shù) 采樣一次占用字節(jié)數(shù) 通道數(shù)×每樣本的數(shù)據(jù)位數(shù)/8 */
  view.setUint16(offset, channelCount * (sampleBits / 8), true); offset += 2
  /* BitsPerSample, 2 bytes, 每樣本數(shù)據(jù)位數(shù) */
  view.setUint16(offset, sampleBits, true); offset += 2
  /* Subchunk2 ID, 4 bytes, 數(shù)據(jù)標(biāo)識(shí)符 */
  writeString(view, offset, "data"); offset += 4
  /* Subchunk2 Size, 4 bytes, 采樣數(shù)據(jù)總數(shù),即數(shù)據(jù)總大小-44 */
  view.setUint32(offset, dataLength, true); offset += 4

  /* 數(shù)據(jù)流需要以大端的方式存儲(chǔ),定義不同采樣比特的 API */
  function floatTo32BitPCM (output, offset, input) {
    input = new Int32Array(input)
    for (let i = 0; i < input.length; i++, offset += 4) {
      output.setInt32(offset, input[i], true)
    }
  }
  function floatTo16BitPCM (output, offset, input) {
    input = new Int16Array(input)
    for (let i = 0; i < input.length; i++, offset += 2) {
      output.setInt16(offset, input[i], true)
    }
  }
  function floatTo8BitPCM (output, offset, input) {
    input = new Int8Array(input)
    for (let i = 0; i < input.length; i++, offset++) {
      output.setInt8(offset, input[i], true)
    }
  }
  if (sampleBits == 16) {
    floatTo16BitPCM(view, 44, samples)
  } else if (sampleBits == 8) {
    floatTo8BitPCM(view, 44, samples)
  } else {
    floatTo32BitPCM(view, 44, samples)
  }
  return view.buffer
}

第三步,在瀏覽器播放 pcm 轉(zhuǎn)出的 wav 的文件流 ArrayBuffer

先轉(zhuǎn)成 base64 格式

const getWebPcm2WavBase64 = async (url) => {
  let bytes = await getWebPcm2WavArrayBuffer(url)
  return `data:audio/wav;base64,${btoa(new Uint8Array(bytes).reduce((data, byte) => {
    return data + String.fromCharCode(byte)
  }, ""))}`
}

將 base64 字符串放入組件中,這里以 react/ant design 的組件為例,封裝一個(gè)方法

const playWebPcm = async (url) => {
    try {
    let pcmBase64 = await fileServer.getWebPcm2WavBase64(url)
    Modal.info({
        title: "播放音頻",
        content: (
        
Reference

音頻格式簡(jiǎn)介和PCM轉(zhuǎn)換成WAV,Java版本

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/101326.html

相關(guān)文章

  • BREW 應(yīng)用音頻播放

    摘要:以下就是用這種方法時(shí)應(yīng)用所作的處理判斷要播放的音樂(lè)播放當(dāng)前音樂(lè)在音樂(lè)播放完后繼續(xù)播放在應(yīng)用程序中斷時(shí)作的操作是恢復(fù)時(shí)的操作是判斷要播放的音樂(lè)循環(huán)播放另一種辦法就是調(diào)用回調(diào)函數(shù)如或。 BREW中支持的音頻格式主要分為兩類:基于MIDI的文件格式和特殊編碼的音頻文件格式?;贛IDI的文...

    lsxiao 評(píng)論0 收藏0
  • 純js實(shí)現(xiàn)web端錄音與播放功能

    摘要:純實(shí)現(xiàn)端錄音功能,功能并不是特別多,逐步增加中,詳細(xì)地址。使用轉(zhuǎn)碼到并播放小文件,大文件使用。其他資源基于阿里云實(shí)現(xiàn)簡(jiǎn)單的語(yǔ)音識(shí)別功能學(xué)習(xí)與音頻播放實(shí)現(xiàn)音頻數(shù)據(jù)收集實(shí)現(xiàn)數(shù)據(jù)編碼轉(zhuǎn)化到格式與播放 純js實(shí)現(xiàn)web端錄音功能,功能并不是特別多,逐步增加中,詳細(xì)地址:github。 getUserMedia在非localhost和127的情況下,需要開(kāi)啟https,由于騰訊云的沒(méi)備案,dem...

    lanffy 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<