Интересно, когда эти аудиофайлы воспроизводятся, ощутимой разницы в ощущениях от прослушивания нет. Однако двоичные данные захваченного аудио различаются в зависимости от запуска, и это вызывает проблемы в моем случае использования, где согласованность необработанных данных имеет решающее значение.
Что я делаю:
Я использую AudioContext, AudioWorkletNode и MediaStreamDestination для захвата звука.
Среда
Браузер: Chrome
Аудио API: веб-аудио API
Формат PCM: Float32, моноканал
Я разработал расширение для браузера с простыми кнопками запуска и остановки записи. Кроме того, запись автоматически останавливается после завершения воспроизведения видео.
content.js
Код: Выделить всё
let audioContext;
let audioWorkletNode;
let audioChunks = [];
let isRecording = false;
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (message.type === 'start-recording') {
startRecording();
}
if (message.type === 'stop-recording') {
stopRecording();
console.log('Recording stopped.');
}
});
async function startRecording() {
try {
// Select the video element
const videoElement = document.querySelector('video');
audioChunks = [];
monitorVideo(videoElement);
if (!videoElement) {
throw new Error('No video element found!');
}
// Capture the audio stream
const stream = videoElement.captureStream();
if (!stream) {
throw new Error('Failed to capture audio stream!');
}
// Initialize AudioContext
audioContext = new AudioContext();
// Add an AudioWorkletProcessor for audio processing
await audioContext.audioWorklet.addModule(chrome.runtime.getURL('processor.js'));
// Create an AudioWorkletNode
audioWorkletNode = new AudioWorkletNode(audioContext, 'audio-processor');
// Connect the audio stream to the AudioContext
const source = audioContext.createMediaStreamSource(stream);
source.connect(audioWorkletNode).connect(audioContext.destination);
// Collect audio chunks from AudioWorkletProcessor
audioWorkletNode.port.onmessage = (event) => {
if (event.data.type === 'chunk') {
audioChunks.push(event.data.chunk);
}
};
// Start the AudioContext
await audioContext.resume();
isRecording = true;
console.log('Recording started...');
} catch (error) {
console.error('Failed to start recording:', error);
}
function stopRecording() {
if (!isRecording) {
console.warn('No recording in progress.');
return;
}
isRecording = false;
// Stop the audio context
if (audioWorkletNode) {
audioWorkletNode.disconnect();
audioContext.close();
}
if (audioChunks.length > 0) {
savePCMFile(audioChunks);
console.log('Recording stopped and saved');
}
console.log('Recording stopped and file saved.');
}
// Function to save PCM data as a binary file
function savePCMFile(pcmBuffer) {
// Flatten the chunks into one Float32Array
const totalLength = pcmBuffer.reduce((sum, chunk) => sum + chunk.length, 0);
const combinedArray = new Float32Array(totalLength);
let offset = 0;
for (const chunk of pcmBuffer) {
combinedArray.set(chunk, offset);
offset += chunk.length;
}
// Create a Blob from the combined Float32Array
const blob = new Blob([combinedArray.buffer], { type: 'application/octet-stream' });
const url = URL.createObjectURL(blob);
// Trigger download
const a = document.createElement('a');
a.href = url;
a.download = 'audio.pcm';
a.click();
}``
Код: Выделить всё
class AudioProcessor extends AudioWorkletProcessor {
constructor() {
super();
this.audioChunks = \[\];
}
process(inputs, outputs, parameters) {
const input = inputs[0]; // Get the first channel's input
if (input && input.length > 0) {
const channelData = input[0]; // First channel's audio data
this.port.postMessage({
type: 'chunk',
chunk: channelData.slice(0), // Copy the audio data
});
}
// Return true to keep processing
return true;
}
}
registerProcessor('audio-processor', AudioProcessor);
Подробнее здесь: https://stackoverflow.com/questions/793 ... orkletnode