首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >NodeJS:将立体声PCM波流捕获为单声道AudioBuffer

NodeJS:将立体声PCM波流捕获为单声道AudioBuffer
EN

Stack Overflow用户
提问于 2020-05-13 14:40:55
回答 1查看 1.7K关注 0票数 0

我正在使用节点传声器录制来自nodejs的音频(这只是一个记录的javascript接口),并且希望使用网络音频api (这是Web的一个nodejs实现)将流块存储在一个AudioBuffer中。

我的音频源有两个频道,而我的AudioBuffer只有一个(目的)。

这是我通过USB声卡记录音频的工作配置(我使用的是运行在Raspbian上的Raspberry pi 3):

代码语言:javascript
复制
arecord -D hw:1,0 -c 2 -f S16_LE -r 44100

使用输出路径运行此命令,并使用aplay播放生成的wav文件,效果很好。因此,节点-麦克风能够用这些参数记录音频,最后我得到一个nodejs可读的流流波数据。

我正在努力完成从流块(Buffer实例)到AudioBuffer的桥。更确切地说,我不确定传入数据的格式,也不确定目标格式,也不确定如何进行转换:

流块是Buffer的,所以它们也是Uint8Array的。关于我的配置,我猜它们是16位有符号整数的二进制表示(我不知道它是什么意思)。

AudioBuffer拥有多个缓冲区(每个通道一个,所以在我的情况下只有一个),可以通过调用AudioBuffer.prototype.getChannelData()作为Float32Array进行访问。MDN还说:

缓冲区包含以下格式的数据:非交错的IEEE754 32位线性PCM,标称范围在-1和+1之间,即32位浮点缓冲区,每个样本在-1.0和1.0之间。

重点是找到我必须从传入的Buffer中提取什么,以及应该如何转换它,以便它适合于Float32Array目的地(并且仍然是有效的波形数据),因为它知道音频源是立体声的,而AudioBuffer不是。

到目前为止,我最好的竞争者是Buffer.prototype.readFloatLE()方法,它的名字看起来可以解决我的问题,但这并不是一个成功(只是噪音)。

我的第一次尝试(在进行研究之前)只是天真地将缓冲区数据复制到Float32Array和交错索引来处理立体声/单点转换。显然,它主要产生噪音,但我可以听到一些我录制的声音(难以置信的扭曲,但肯定存在),所以我想我应该提一下。

这是我天真尝试的简化版本(我知道这不是很好,我只是把它作为讨论的基础):

代码语言:javascript
复制
import { AudioBuffer } from 'web-audio-api'
import Microphone from 'node-microphone'

const rate = 44100
const channels = 2 // Number of source channels

const microphone = new Microphone({ // These parameters result to the arecord command above
  channels,
  rate,
  device: 'hw:1,0',
  bitwidth: 16,
  endian: 'little',
  encoding: 'signed-integer'
})

const audioBuffer = new AudioBuffer(
  1, // 1 channel
  30 * rate, // 30 seconds buffer
  rate
})

const chunks = []
const data = audioBuffer.getChannelData(0) // This is the Float32Array
const stream = microphone.startRecording()

setTimeout(() => microphone.stopRecording(), 5000) // Recording for 5 seconds

stream.on('data', chunk => chunks.push(chunk))

stream.on('close', () => {
  chunks.reduce((offset, chunk) => {
    for (var index = 0; index < chunk.length; index += channels) {
      let value = 0

      for (var channel = 0; channel < channels; channel++) {
        value += chunk[index + channel]
      }

      data[(offset + index) / channels] = value / channels // Average value from the two channels
    }

    return offset + chunk.length // Since data comes as chunks, this offsets AudioBuffer's index
  }, 0)
})

如果你能帮忙,我会非常感激的:)

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2020-05-15 13:33:32

因此,输入的立体声信号是16位有符号整数,交叉左、右通道,这意味着对应的缓冲区(8位无符号整数)对于单个立体声示例具有这种格式:

代码语言:javascript
复制
[LEFT ] 8 bits (LSB)
[LEFT ] 8 bits (MSB)
[RIGHT] 8 bits (LSB)
[RIGHT] 8 bits (MSB)

因为记录是用小endian格式配置的,所以最不重要的Byte (LSB)排在第一位,而最重要的Byte (MSB)次之。

AudioBuffer单通道缓冲区(由Float32Array表示)期望-11之间的值(每个样本一个值)。

因此,要将值从输入Buffer映射到目标Float32Array,我必须使用Buffer.prototype.readInt16LE(offset)方法,将字节offset参数递增4个每个示例(2个左字节+2个右字节=4个字节),并将从range [-32768;+32768] (16位符号整数范围)到range [-1;+1]的输入值进行内插。

代码语言:javascript
复制
import { AudioBuffer } from 'web-audio-api'
import Microphone from 'node-microphone'

const rate = 44100
const channels = 2 // 2 input channels

const microphone = new Microphone({
  channels,
  rate,
  device: 'hw:1,0',
  bitwidth: 16,
  endian: 'little',
  encoding: 'signed-integer'
})

const audioBuffer = new AudioBuffer(
  1, // 1 channel
  30 * rate, // 30 seconds buffer
  rate
})

const chunks = []
const data = audioBuffer.getChannelData(0)
const stream = microphone.startRecording()

setTimeout(() => microphone.stopRecording(), 5000) // Recording for 5 seconds

stream.on('data', chunk => chunks.push(chunk))

stream.on('close', () => {
  chunks.reduce((offset, chunk) => {
    for (var index = 0; index < chunk.length; index += channels + 2) {
      let value = 0

      for (var channel = 0; channel < channels; channel++) {
        // Iterates through input channels and adds the values
        // of all the channel so we can compute the
        // average value later to reduce them into a mono signal

        // Multiplies the channel index by 2 because
        // there are 2 bytes per channel sample

        value += chunk.readInt16LE(index + channel * 2)
      }

      // Interpolates index according to the number of input channels
      // (also divides it by 2 because there are 2 bytes per channel sample)
      // and computes average value as well as the interpolation
      // from range [-32768;+32768] to range [-1;+1]
      data[(offset + index) / channels / 2] = value / channels / 32768
    }

    return offset + chunk.length
  }, 0)
})
票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/61777531

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档