如安在浏览器中播放pcm音频

本文纪录一点工作经历,讨论音频文件的花样

更多接见
我的博客

媒介

最近在整顿音视频编程的学问,回忆起半年多,有一次需求是在背景播放某泉源的 pcm 文件,当时处置惩罚要领用了点技能,纪录下来

背景:营业需求,在web背景里播放 pcm 文件,文件不大(约300KB,已知 pcm 的参数采样率16000,采样位数16,声道数1

怎样播放

浏览器是没法直接播放 pcm 音频的,由于 pcm 是比较原始的音频花样:

PCM(Puls Code Modulation)全称脉码调制灌音,PCM灌音就是将声响的模拟信号示意成0,1标识的数字信号,未经任何编码和紧缩处置惩罚,所以能够以为PCM是未经紧缩的音频原始花样。PCM花样文件中不包括头部信息,播放器没法晓得采样率,声道数,采样位数,音频数据大小等信息,致使没法播放。

怎样让浏览器辨认 pcm

浏览器能够播放另一种音频花样:WAV花样全称为WAVE,前面提到只须要在PCM文件的前面增加WAV文件头,就能够天生WAV花样文件

所以我的解决要领是给 pcm 增加 wav header,接下来就是 browser javascript 的实践编码了

javascript 怎样处置惩罚文件流

js 在处置惩罚文件流、收集数据,常用到 ArrayBuffer 范例,关于 ArrayBuffer 范例的API挪用要领,须要事前多相识。

  • 第一步,ajax异步猎取收集 pcm 文件的 ArrayBuffer
const getWebFileArrayBuffer = async (url) => {
  return await fetch(url).then(response => response.arrayBuffer())
}
  • 第二步,对猎取的 pcm 文件流 ArrayBuffer 增加 wav header,我们先弄邃晓 header 的组织:

《如安在浏览器中播放pcm音频》

看以上图片,我们须要将猎取到的 pcm data 增加 44 bytes 的 header,依据 header 的构造,对齐、紧凑地添补信息,在 javascript 中,须要运用 DataView 范例协助我们举行字节添补的操纵,注重DataView供应的 API 默许运用 little end 的数据花样,须要分外定义 big end 花样添补字节的要领。

以下以代码来申明怎样一步一步添补字节信息:

const getWebPcm2WavArrayBuffer = async (url) => {
  const bytes = await getWebFileArrayBuffer(url)
  return addWavHeader(bytes, 16000, 16, 1) // 这里是当前营业需求,特定的参数,采样率16000,采样位数16,声道数1
}

const addWavHeader = function (samples, sampleRateTmp, sampleBits, channelCount) {
  let dataLength = samples.byteLength
  /* 新的buffer类,预留 44 bytes 的 heaer 空间 */
  let buffer = new ArrayBuffer(44 + dataLength)
  /* 转为 Dataview, 应用 API 来添补字节 */
  let view = new DataView(buffer)
  /* 定义一个内部函数,以 big end 数据花样添补字符串至 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,  资本交流文件标识符 */
  writeString(view, offset, 'RIFF'); offset += 4
  /* ChunkSize, 4 bytes, 下个地点最先到文件尾总字节数,即文件大小-8 */
  view.setUint32(offset, /* 32 */ 36 + dataLength, true); offset += 4
  /* Format, 4 bytes, WAV文件标志 */
  writeString(view, offset, 'WAVE'); offset += 4
  /* Subchunk1 ID, 4 bytes, 波形花样标志 */
  writeString(view, offset, 'fmt '); offset += 4
  /* Subchunk1 Size, 4 bytes, 过滤字节,平常为 0x10 = 16 */
  view.setUint32(offset, 16, true); offset += 4
  /* Audio Format, 2 bytes, 花样种别 (PCM情势采样数据) */
  view.setUint16(offset, 1, true); offset += 2
  /* Num Channels, 2 bytes,  通道数 */
  view.setUint16(offset, channelCount, true); offset += 2
  /* SampleRate, 4 bytes, 采样率,每秒样本数,示意每一个通道的播放速率 */
  view.setUint32(offset, sampleRateTmp, true); offset += 4
  /* ByteRate, 4 bytes, 波形数据传输率 (每秒均匀字节数) 通道数×每秒数据位数×每样本数据位/8 */
  view.setUint32(offset, sampleRateTmp * channelCount * (sampleBits / 8), true); offset += 4
  /* BlockAlign, 2 bytes, 快数据调整数 采样一次占用字节数 通道数×每样本的数据位数/8 */
  view.setUint16(offset, channelCount * (sampleBits / 8), true); offset += 2
  /* BitsPerSample, 2 bytes, 每样本数据位数 */
  view.setUint16(offset, sampleBits, true); offset += 2
  /* Subchunk2 ID, 4 bytes, 数据标识符 */
  writeString(view, offset, 'data'); offset += 4
  /* Subchunk2 Size, 4 bytes, 采样数据总数,即数据总大小-44 */
  view.setUint32(offset, dataLength, true); offset += 4

  /* 数据流须要以大端的体式格局存储,定义差别采样比特的 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 转出的 wav 的文件流 ArrayBuffer
  • 先转成 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 字符串放入<audio>组件中,这里以 react/ant design 的组件为例,封装一个要领
const playWebPcm = async (url) => {
    try {
    let pcmBase64 = await fileServer.getWebPcm2WavBase64(url)
    Modal.info({
        title: '播放音频',
        content: (
        <audio controls src={pcmBase64} type="audio/wav" autoPlay />
        ),
        onOk () {},
        okText: '封闭',
    })
    } catch (err) {
    console.error(err)
    message.error('预载音频文件失利')
    }
}

Reference

音频花样简介和PCM转换成WAV,Java版本

    原文作者:光鱼
    原文地址: https://segmentfault.com/a/1190000017982073
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞