简体   繁体   English

Chromium Audio Worklet 丢失。 一些最初,然后偶尔以 30 秒为间隔

[英]Chromium Audio Worklet dropouts. Some initially, then occasionally in 30s intervals

I'm noticing frequent and repeatable dropouts when using the Chromium Audio worklet with a MediaDevices.getUserMedia media stream (microphone).当使用带有MediaDevices.getUserMedia媒体 stream(麦克风)的 Chromium Audio 工作集时,我注意到频繁且可重复的丢失。 This is not 100% reproducible, but they do tend to follow pattern when they do occur:这不是 100% 可重现的,但是当它们确实发生时它们确实倾向于遵循模式:

(Time ranges vary slightly per attempt) (每次尝试的时间范围略有不同)

  1. 0:00 -> 0:00.2: No samples are received. 0:00 -> 0:00.2:没有收到样本。 (Able to reproduce 100% of the time, but this feels like a separate issue that I wasn't necessarily tracking down at the moment) (能够 100% 重现,但这感觉像是一个单独的问题,我目前不一定要追踪)
  2. 0:00.2 -> 0:00.5: Samples are received 0:00.2 -> 0:00.5:收到样本
  3. 0:00.5 -> 0:00.6: Dropout occurs, no samples received (Able to reproduce ~20% of the time). 0:00.5 -> 0:00.6:发生丢失,没有收到样本(能够重现约 20% 的时间)。
  4. 0:00.6 -> 0:30.0: Samples received 0:00.6 -> 0:30.0:收到样本
  5. Every 30s from here on out, occasionally dropouts will occur.从现在开始每隔 30 秒,偶尔会发生辍学。 Tends to happen the most often at the first 30s mark.往往最常发生在第一个 30 岁大关。 (First 30s mark I can reproduce around 20% of the time as well). (前 30 秒标记我也可以重现大约 20% 的时间)。

Here is a codepen that illustrates the behavior: https://codepen.io/GJStevenson/pen/GRErPbm这是一个说明行为的codepen:https://codepen.io/GJStevenson/pen/GRErPbm

const startRecordingButton = document.getElementById('startRecordingButton');
let mediaStreamSourceNode;
let isRecording = false;
let timer;

const workletString = `
const formatTimeString = s => {
   const m = (s / 60).toFixed(2);
   const h = (m / 60).toFixed(2);
   const ms = Math.trunc(s * 1000) % 1000;
   const ss = Math.trunc(s) % 60;
   const mm = Math.trunc(m) % 60;
   const hh = Math.trunc(h);
   return hh + ":" + mm + ":" + ss + "." + ms;
};

class RecorderWorklet extends AudioWorkletProcessor {
    constructor(options) {
        super(options);
        this.sampleRate = 0;
        this.sampleCount = 0;

        this.port.onmessage = event => {
            if (event.data.message === 'init') {
                this.sampleRate = event.data.sampleRate;
            }
        }
    }

    process(inputs) {
        if (inputs.length > 0 && inputs[0].length > 0) {
            this.sampleCount += inputs[0][0].length; 
            //console.debug(formatTimeString(this.sampleCount/this.sampleRate), ' : ', inputs[0][0]);

            if (inputs[0][0].includes(0)) {
                console.log('Dropped Samples at: ', formatTimeString(this.sampleCount/this.sampleRate), ' : ', ...inputs[0][0])
            }
        }
        return true;
    }
}

registerProcessor('recorder-worklet', RecorderWorklet);
`;

async function listAudioInputs() {
    const devices = await navigator.mediaDevices.enumerateDevices();
    return devices.filter((device) => device.kind === 'audioinput');
}

async function getDefaultInput(fallbackToFirstInput = true) {
    const audioInputs = await listAudioInputs();
    const defaultDevice = audioInputs.find((device) => device.deviceId === 'default');
    if (defaultDevice) {
        return defaultDevice;
    }
    return fallbackToFirstInput && audioInputs.length > 0 ? audioInputs[0] : undefined;
}

async function getAudioStream(device) {
    const constraints = {
        audio: {
            deviceId: device.deviceId,
        },
    };
    return navigator.mediaDevices.getUserMedia(constraints);
}

async function createRecordingPipeline(device) {
    const stream = await getAudioStream(device);
    const audioTracks = stream.getAudioTracks();

    const sampleRate = audioTracks[0].getSettings().sampleRate;
    console.log('Sample Rate: ', sampleRate);
    const context = new AudioContext({ sampleRate, latencyHint: 'interactive' });

    const blob = new Blob([workletString], { type: 'text/javascript' });
    const workletUrl = URL.createObjectURL(blob);

    await context.audioWorklet.addModule(workletUrl);
    const workletNode = new AudioWorkletNode(context, 'recorder-worklet');

    workletNode.port.postMessage({
        message: 'init',
        sampleRate: sampleRate
    });

    mediaStreamSourceNode = context.createMediaStreamSource(stream);
    mediaStreamSourceNode.connect(workletNode)
                         .connect(context.destination);
}

function formatTimeString(s) {
   const m = (s / 60).toFixed(2);
   const h = (m / 60).toFixed(2);
   const ms = Math.trunc(s * 1000) % 1000;
   const ss = Math.trunc(s) % 60;
   const mm = Math.trunc(m) % 60;
   const hh = Math.trunc(h);
   return hh + ":" + mm + ":" + ss + "." + ms;
};

async function startRecording() {
    const device = await getDefaultInput();
    await createRecordingPipeline(device);

    let timeElapsed = 0;
    timer = setInterval(() => {
        timeElapsed++;
        console.log('Time: ', formatTimeString(timeElapsed));
    }, 1000);
  
    startRecordingButton.innerText = "Stop Recording";
}

async function stopRecording() {
    if (mediaStreamSourceNode) {
        mediaStreamSourceNode.mediaStream.getAudioTracks().forEach(track => {
           track.stop();
        });
        mediaStreamSourceNode.disconnect();
    }
    mediaStreamSourceNode = null;
    clearInterval(timer);
    
    startRecordingButton.innerText = "Start Recording";
}

async function toggleRecording() {
    if (!isRecording) {
        await startRecording();
    } else {
        await stopRecording();
    }
    isRecording = !isRecording;
}
<button onclick="toggleRecording()" id="startRecordingButton">Start Recording</button>

The dropped samples will look something like this in the console:丢弃的样本在控制台中看起来像这样:

在控制台中丢弃样本

Any thoughts as to what the problem may be?关于问题可能是什么的任何想法?

Edit: Ran chrome://tracing to capture trace of the dropped sample.编辑:运行 chrome://tracing 以捕获掉落样本的踪迹。 https://www.dropbox.com/s/veg1vgsg9nn03ty/trace_dropped-sample-trace.json.gz?dl=0 . https://www.dropbox.com/s/veg1vgsg9nn03ty/trace_dropped-sample-trace.json.gz?dl=0 Dropped samples happened from ~.53s ->.61s掉落的样本发生在 ~.53s ->.61s

从 .53s 到 .61s 的掉线图

Got some answers after opening an issue with Chromium.在打开 Chromium 问题后得到了一些答案。 Summarizing some of the responses from https://bugs.chromium.org/p/chromium/issues/detail?id=1248169 :总结https://bugs.chromium.org/p/chromium/issues/detail?id=1248169的一些回复:

0:00 -> 0:00.2: No samples are received. 0:00 -> 0:00.2:没有收到样本。 (Able to reproduce 100% of the time, but this feels like a separate issue that I wasn't necessarily tracking down at the moment) (能够 100% 重现,但这感觉像是一个单独的问题,我目前不一定要追踪)

These first few zero samples are the initial primed values from the underlying buffers and are expected.这些前几个零样本是来自底层缓冲区的初始值,并且是预期的。

0:00.5 -> 0:00.6: Dropout occurs, no samples received (Able to reproduce ~20% of the time). 0:00.5 -> 0:00.6:发生丢失,没有收到样本(能够重现约 20% 的时间)。

This should not happen if the AudioWorklet thread runs on a RT priority.如果 AudioWorklet 线程以 RT 优先级运行,则不应发生这种情况。 This was difficult to reproduce, so shelving it for now.这很难重现,所以暂时搁置。

Every 30s from here on out, occasionally dropouts will occur.从现在开始每隔 30 秒,偶尔会发生辍学。 Tends to happen the most often at the first 30s mark.往往最常发生在第一个 30 岁大关。 (First 30s mark I can reproduce around 20% of the time as well). (前 30 秒标记我也可以重现大约 20% 的时间)。

This dropout was a result of having configured a destination source, but not utilizing it.此丢失是配置目标源但未使用它的结果。 After 30 seconds of silence, the AudioWorklet thread switches to a low priority one, causing the dropout.静默 30 秒后,AudioWorklet 线程切换到低优先级线程,导致 dropout。

So changing如此变化

mediaStreamSourceNode.connect(workletNode)
                     .connect(context.destination);

to

mediaStreamSourceNode.connect(workletNode);

fixed the problem.解决了问题。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM