音频audioContext记录

1,167

先记录一下查过的有用的文章

更改AudioContext的采样率
Web AudioContext 录音如何降低采样率呢?
浅谈H5音频处理(更多谈谈录音方向的内容) 音视频录制浅解 字符串arraybuffer转换及拼接 JS ArrayBuffer 类型转换及拼接

1.调用AudioContext的接口操作音频PCM数据

缓冲区采样率的关系:缓冲区应该大于数据读取间隔*采样率。

navigator.mediaDevices.getUserMedia({audio: true}, initRecorder)
 
function initRecorder(stream) {
    const AudioContext = window.AudioContext
    const audioContext = new AudioContext()
    // 创建MediaStreamAudioSourceNode对象,给定媒体流(例如,来自navigator.getUserMedia实例),然后可以播放和操作音频。
    const audioInput = audioContext.createMediaStreamSource(stream)
    // 缓冲区大小为4096,控制着多长时间需触发一次audioprocess事件
    const bufferSize = 4096
    // 创建一个javascriptNode,用于使用js直接操作音频数据
    // 第一个参数表示每一帧缓存的数据大小,可以是256, 512, 1024, 2048, 4096, 8192, 16384,值越小一帧的数据就越小,声音就越短,onaudioprocess 触发就越频繁。4096的数据大小大概是0.085s,就是说每过0.085s就触发一次onaudioprocess,第二,三个参数表示输入帧,和输出帧的通道数。这里表示2通道的输入和输出,当然我也可以采集1,4,5等通道
    const recorder = audioContext.createScriptProcessor(bufferSize, 1, 1)
    // 每个满足一个分片的buffer大小就会触发这个回调函数
    recorder.onaudioprocess = recorderProcess
    // const monitorGainNode = audioContext.createGain()
    // 延迟0.01秒输出到扬声器
    // monitorGainNode.gain.setTargetAtTime(音量, audioContext.currentTime, 0.01)
    // monitorGainNode.connect(audioContext.destination)
    // audioInput.connect(monitorGainNode)
    // const recordingGainNode = audioContext.createGain()
    // recordingGainNode.gain.setTargetAtTime(音量, audioContext.currentTime, 0.01)
    // recordingGainNode.connect(audioContext.scriptProcessorNode)
    // 将音频的数据流输出到这个jsNode对象中
    audioInput.connect(recorder)
    // 最后先音频流输出到扬声器。(将录音流原本的输出位置再定回原来的目标地)
    recorder.connect(audioContext.destination)
}

// 可以取到对应声道的数据,进行分别处理
function recorderProcess(e) {
  // 左声道
  const left = e.inputBuffer.getChannelData(0);
}

2.数据处理与转换(涉及到采样率sampleRate和位深度bitDepth

获取麦克风采样率

const AudioContext = window.AudioContext
const audioContext = new AudioContext()
// 可读属性
console.log(audioContext.sampleRate)
// 44100

修改采样率

function interleave(e){
  var t = e.length;
  sampleRate += 0.0;
  outputSampleRate += 0.0;
  var s = 0,
  o = sampleRate / outputSampleRate,
  u = Math.ceil(t * outputSampleRate / sampleRate),
  a = new Float32Array(u);
  for (i = 0; i < u; i++) {
    a[i] = e[Math.floor(s)];
    s += o;
  }
  return a;
}

剩下还有两个问题。将音频流转为16位深,这里就比较简单了。只要确保你生成的位数足够就行。比如,8位深的音频只需要生成一个Uint8Array,16位深就要生成2个的长度。new Unit8Array(bitDepth / 8)

3.位深相关知识点

WAV格式的音频文件,如果是没有经过压缩的,那里面的PCM数据,就是最原始的采样数据。根据采样位数的不同,常见的有8bit PCM数据, 16bit PCM数据, 由于种种原因,有时可能会遇到8bit 与16bit PCM编码之间相互转换的情况,

首先明确一点的时,8b PCM的采样数据是无符号数据, 而16b PCM的采样数据是有符号数据,因此8bit PCM转换为 16bit PCM时,必须先把无符号数据转换为有符号数。

4.获取当前电脑可使用的音视频设备

// 获取录音设备列表
if (navigator.mediaDevices && navigator.mediaDevices.enumerateDevices) {
  navigator.mediaDevices.enumerateDevices()
  .then((devices) => {
    console.log('录音设备列表',devices)
    // audioinput
    let arr = []
    devices.forEach((item) => {
      if (item.kind === 'audioinput') {
        arr.push({
          label: item.label,
          id: item.deviceId
        })
      }
    })
    console.log(arr)
  })
  .catch(() => {
    alert('列表获取失败')
  })
}

5.字符串和音频流拼接

const pcm_prefix = Buffer.from('----SPEECH_AUDIO', 'utf-8').toString('latin1')

const pcmString = String.fromCharCode
    .apply(null, new Unit8Array(event.data))
    .aplit(pcm_prefix)[2]
if (pcmString) {
    const arraybuffer = Buffer.from(pcmString, 'latin1')
    const dataAudio =. new Unit8Array(arraybuffer)  // 最终音频
}