Android 版 AudioWorklet 中的麦克风录音故障

时间:2021-03-23 18:17:50

标签: javascript android web-audio-api audio-worklet

我想对麦克风输入进行一些实时处理(包括下采样),最重要的是我想保存输入样本。

在将代码部署到生产环境后,我注意到我收到的一些录音有问题,有些比其他问题严重得多。通过故障,我的意思是录音包含零值样本的随机周期。该问题出现在 Android 设备上。

幸运的是,我得到了一个能够始终如一地重现该问题的设备 (OnePlus 6)。在将 AudioWorklet 剥离到最低限度后,我仍然可以观察到故障。这是我的测试 AudioWorklet 类的样子,去掉了通信部分。

class TestWorklet extends AudioWorkletProcessor {
    constructor(options) {
        super(options);

        // Allocate the buffer once in the beginning
        this.recordingBuffer = new Float32Array(sampleRate * 20);  // store max 20 sec audio
        this.recordingBufferOffset = 0;
    }

    process(inputs, outputs) {
        const input = inputs[0];
        const output = outputs[0];

        // Copy samples to the recording buffer
        if (this.recordingBufferOffset < this.recordingBuffer.length - input[0].length) {
            this.recordingBuffer.set(input[0], this.recordingBufferOffset);
            this.recordingBufferOffset += input[0].length;
        }

        // Copy input to the output
        for (let channel = 0; channel < input.length; ++channel) {
            output[channel].set(input[channel]);
        }

        return true;
    }
}

registerProcessor('TestWorklet', TestWorklet);

为了完整起见,这就是我连接 AudioWorklet 的方式

import TestWorklet from './testWorklet';

start = async () => {
    let stream;
    let audioCtx;
    if (navigator.mediaDevices) {
        try {
            stream = await navigator.mediaDevices.getUserMedia({
                audio: true,
                video: false
            });
        } catch (e) { }
    }

    if (!stream) {
        return false;
    }

    try {
        audioCtx = new AudioContext();
    } catch (e) {
        return false;
    }

    await audioCtx.audioWorklet.addModule(TestWorklet);
    const audioSource = audioCtx.createMediaStreamSource(stream);

    const worklet = new AudioWorkletNode(audioCtx, "TestWorklet", {
        channelCount: 1,
        channelCountMode: "explicit",
        channelInterpretation: "discrete"
    });

    audioSource.connect(worklet);

    await audioCtx.resume();
    return true;
}

有趣的是,在功能较弱的 Android 设备上,我没有遇到这些零故障。至少我没注意到。

有人遇到过这个问题吗?也许我正在使用次优参数初始化 Worklet?

与此同时,我正在尝试使用已弃用的 ScriptProcessorNode 进行替代实现,以查看其性能是否更好。

2 个答案:

答案 0 :(得分:0)

我认为您在 Chrome 中遇到了一个错误,该错误已报告 here。如果我没记错的话,它是在 AudioContext 不产生任何可听声音时触发的。在这种情况下,Chrome 会应用优化,这意味着它从内部时钟运行上下文,而不是使用实际的物理音频设备。

如果上述所有情况都成立,您可能会通过向音频输出发送一个微小信号以保持上下文从该设备运行而逃脱。

const constantSourceNode = new ConstantSourceNode(audioContext);

// 0.0001 is an arbitrary value
// maybe it needs to be bigger
// maybe a smaller value works as well
constantSourceNode.offset.value = 0.0001;

constantSourceNode.connect(audioContext.destination);
constantSourceNode.start();

答案 1 :(得分:0)

AudioWorkletProcessor 运行在由声卡计时的音频线程上,process() 方法回调也是如此。它意味着每次准备好处理 128 个样本时都会调用它。根据我的直接观察,您希望尽可能地减慢速度。

您可能会尝试将 AudioworletProcessor 仅用作这 128 个样本块的导出器,使用 AudioworkletNode.port() 方法与主脚本对话并“导出”来自输入(麦克风)的数组缓冲区:< /p>

class CustomMicProcessor extends AudioWorkletProcessor {
    process (inputs, outputs, parameters) {
    const input = inputs[0];
    const output = outputs[0];

    if (input[0]) 
      this.port.postMessage(input[0]);
      
    return true;
    }
}

另一方面,您将有一个消息侦听器:

customMicProcessor.port.onmessage = (samples) => { FillMicBuffer (samples.data)};
                      

然后你可以对数据做任何你需要做的事情,但在主线程而不是音频线程上。

我在 voip 网络应用程序中这样做(下采样录音 + 编码然后网络传送),并且可以从我的 Android 设备完美地记录和发送数据。

相关问题