NodeJS:将立体声 PCM 波形流捕获到单声道 AudioBuffer
NodeJS: Capturing a stereo PCM wave stream into mono AudioBuffer
我正在使用 node-microphone (which is just a javascript interface for arecord), and want to store the stream chunks in an AudioBuffer
using web-audio-api(这是 Web 音频 API 的 nodejs 实现)从 nodejs 录制音频。
我的音频源有两个通道,而我的 AudioBuffer
只有一个(故意)。
这是我通过 USB 声卡使用 arecord 录制音频的工作配置(我在 Raspbian buster 上使用 Raspberry pi 3 运行):
arecord -D hw:1,0 -c 2 -f S16_LE -r 44100
运行 此命令带有输出路径并使用 aplay 播放生成的 wav 文件效果很好。所以节点麦克风能够使用这些参数录制音频,最后我得到一个 nodejs 可读流流动的波形数据。
但是
我正在努力搭建从流块(Buffer
个实例)到 AudioBuffer
的桥梁。更确切地说;我不确定传入数据的格式,不确定目标格式,也不确定我将如何进行转换:
流块是 Buffer
,所以它们也是 Uint8Array
。关于我的配置,我猜他们是16位有符号整数的二进制表示(little endian,我不知道它是什么意思)。
AudioBuffer
包含多个缓冲区(每个通道一个,所以在我的例子中只有一个),我可以通过调用 AudioBuffer.prototype.getChannelData()
作为 Float32Array
访问这些缓冲区。 MDN 还说:
The buffer contains data in the following format: non-interleaved IEEE754 32-bit linear PCM with a nominal range between -1 and +1, that is, 32bits floating point buffer, with each samples between -1.0 and 1.0.
重点是找到我必须从传入的 Buffer
s 中提取的内容以及我应该如何转换它以便它适合 Float32Array
目的地(并保持有效的 wave 数据),知道音频源是立体声而 AudioBuffer
不是。
到目前为止,我最好的竞争者是 Buffer.prototype.readFloatLE()
方法,其名称看起来可以解决我的问题,但这并不成功(只是噪音)。
我的第一次尝试(在进行研究之前)只是天真地将缓冲区数据复制到 Float32Array
并交错索引以处理 stereo/mono 转换。显然它主要产生噪音,但我能听到我录制的一些声音(难以置信的失真但肯定存在)所以我想我应该提到这一点。
这是我天真的尝试的简化版本(我知道这并不意味着效果很好,我只是将它作为讨论的基础包含在我的问题中):
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)
})
如果您能提供帮助,我将不胜感激:)
因此输入立体声信号以 16 位有符号整数的形式出现,左右声道交错,这意味着相应的缓冲区(8 位无符号整数)具有单个立体声样本的这种格式:
[LEFT ] 8 bits (LSB)
[LEFT ] 8 bits (MSB)
[RIGHT] 8 bits (LSB)
[RIGHT] 8 bits (MSB)
由于 arecord 配置为 little endian 格式,Least Significant Byte (LSB) 在前,最高有效字节 (MSB) 接下来是。
AudioBuffer
单通道缓冲区,由 Float32Array
表示,期望值介于 -1
和 1
之间(每个样本一个值)。
因此,为了将输入 Buffer
的值映射到目标 Float32Array
,我必须使用 Buffer.prototype.readInt16LE(offset)
方法,每个样本将字节 offset
参数递增 4 (2 个左字节 + 2 个右字节 = 4 个字节),并将输入值从范围 [-32768;+32768]
(16 位带符号整数范围)内插到范围 [-1;+1]
:
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)
})
我正在使用 node-microphone (which is just a javascript interface for arecord), and want to store the stream chunks in an AudioBuffer
using web-audio-api(这是 Web 音频 API 的 nodejs 实现)从 nodejs 录制音频。
我的音频源有两个通道,而我的 AudioBuffer
只有一个(故意)。
这是我通过 USB 声卡使用 arecord 录制音频的工作配置(我在 Raspbian buster 上使用 Raspberry pi 3 运行):
arecord -D hw:1,0 -c 2 -f S16_LE -r 44100
运行 此命令带有输出路径并使用 aplay 播放生成的 wav 文件效果很好。所以节点麦克风能够使用这些参数录制音频,最后我得到一个 nodejs 可读流流动的波形数据。
但是
我正在努力搭建从流块(Buffer
个实例)到 AudioBuffer
的桥梁。更确切地说;我不确定传入数据的格式,不确定目标格式,也不确定我将如何进行转换:
流块是 Buffer
,所以它们也是 Uint8Array
。关于我的配置,我猜他们是16位有符号整数的二进制表示(little endian,我不知道它是什么意思)。
AudioBuffer
包含多个缓冲区(每个通道一个,所以在我的例子中只有一个),我可以通过调用 AudioBuffer.prototype.getChannelData()
作为 Float32Array
访问这些缓冲区。 MDN 还说:
The buffer contains data in the following format: non-interleaved IEEE754 32-bit linear PCM with a nominal range between -1 and +1, that is, 32bits floating point buffer, with each samples between -1.0 and 1.0.
重点是找到我必须从传入的 Buffer
s 中提取的内容以及我应该如何转换它以便它适合 Float32Array
目的地(并保持有效的 wave 数据),知道音频源是立体声而 AudioBuffer
不是。
到目前为止,我最好的竞争者是 Buffer.prototype.readFloatLE()
方法,其名称看起来可以解决我的问题,但这并不成功(只是噪音)。
我的第一次尝试(在进行研究之前)只是天真地将缓冲区数据复制到 Float32Array
并交错索引以处理 stereo/mono 转换。显然它主要产生噪音,但我能听到我录制的一些声音(难以置信的失真但肯定存在)所以我想我应该提到这一点。
这是我天真的尝试的简化版本(我知道这并不意味着效果很好,我只是将它作为讨论的基础包含在我的问题中):
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)
})
如果您能提供帮助,我将不胜感激:)
因此输入立体声信号以 16 位有符号整数的形式出现,左右声道交错,这意味着相应的缓冲区(8 位无符号整数)具有单个立体声样本的这种格式:
[LEFT ] 8 bits (LSB)
[LEFT ] 8 bits (MSB)
[RIGHT] 8 bits (LSB)
[RIGHT] 8 bits (MSB)
由于 arecord 配置为 little endian 格式,Least Significant Byte (LSB) 在前,最高有效字节 (MSB) 接下来是。
AudioBuffer
单通道缓冲区,由 Float32Array
表示,期望值介于 -1
和 1
之间(每个样本一个值)。
因此,为了将输入 Buffer
的值映射到目标 Float32Array
,我必须使用 Buffer.prototype.readInt16LE(offset)
方法,每个样本将字节 offset
参数递增 4 (2 个左字节 + 2 个右字节 = 4 个字节),并将输入值从范围 [-32768;+32768]
(16 位带符号整数范围)内插到范围 [-1;+1]
:
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)
})