我有可以传输音频数据的网络摄像机。该音频不是 MP3 或 WAV 等普通音频格式,并且无法使用 VLC 等播放器播放。设备流 ADPCM 块,每个块长度为 544 字节。每个音频块都有 32 字节的标头,应将其删除。因此,我正在研究浏览器端 Javascript 代码(我计划制作浏览器扩展),该代码消耗此流,删除标头,然后将其转换为可播放格式(如 PCM)并在浏览器中播放。
这是我在 Firefox 中成功完成一些工作的代码:
<script>
let form = document.forms.audioStreamSettings;
let inputAudioStreamURL = form.elements.audioStreamURL;
let inputAudioChunksLimit = form.elements.audioChunksLimit;
let btnStartStream = form.elements.startStream;
//Start to play audio when click to the button "Play audio stream:"
btnStartStream.addEventListener('click', () => {
let audioStreamURL = inputAudioStreamURL.value;
let audioChunksLimit = inputAudioChunksLimit.value;
(async () => {
let response = await fetch(audioStreamURL);
const reader = response.body.getReader();
const audioCtx = new (window.AudioContext || window.webkitAudioContext)();
let receivedLength = 0; // received that many bytes at the moment
let pcmStream = new Float32Array();
let audioDataChunks = new Uint8Array(reader.result); // array of received binary chunks (comprises the body)
for (let step = 0; step < audioChunksLimit; step++) {
const {done, value} = await reader.read();
if (done) {
break;
}
let audioChunk = new Uint8Array(value);
console.log('Audio chunk:');
console.log(audioChunk);
//Removing header from every piece of audio data
adpcmRawChunk = audioChunk.slice(32,544);
console.log('Adpcm raw chunk:');
console.log(adpcmRawChunk);
//Device streams ADPCM audio, convert every piece to PCM
var pcmChunk = decodeAdpcm(adpcmRawChunk);
//Merge converted pieces to PCM stream
pcmStream = Float32Array.from([...pcmStream,...pcmChunk]);
receivedLength += audioChunk.length;
console.log(`Received ${receivedLength}`);
}
console.log("pcmStream:");
console.log(pcmStream);
// Create an empty buffer at the sample rate of the AudioContext
const audioBuffer = audioCtx.createBuffer(
1,
pcmStream.length,
8000
);
// Fill the buffer with PCM stream data
for (let channel = 0; channel < audioBuffer.numberOfChannels; channel++) {
// This gives us the actual array that contains the data
const nowBuffering = audioBuffer.getChannelData(channel);
for (let i = 0; i < audioBuffer.length; i++) {
nowBuffering[i] = pcmStream[i];
}
}
// Get an AudioBufferSourceNode.
// This is the AudioNode to use when we want to play an AudioBuffer
const source = audioCtx.createBufferSource();
// set the buffer in the AudioBufferSourceNode
source.buffer = audioBuffer;
// connect the AudioBufferSourceNode to the
// destination so we can hear the sound
source.connect(audioCtx.destination);
// start the source playing
source.start();
})()
})
</script>
这段代码的问题是它捕获指定数量的块,将其保存到内存(数组)然后播放。 但我需要实时连续播放音频流。我还想要一个停止按钮来停止播放音频流。
请给我一个正确的方向,以进一步改进此代码并实现我想要的。
节省他人时间:
async function playAudioPCMStream(url: string){
const res = await fetch(url)
const reader = res.body!.getReader();
const audioCtx = new AudioContext() // might need to create at user interaction on iOS
await streamRawAudio(reader, audioCtx);
}
async function streamRawAudio(
reader: ReadableStreamDefaultReader<Uint8Array>,
audioCtx: AudioContext,
) {
while (true) {
const { done, value } = await reader.read();
if (done) {
break;
}
await playAudioData(value, audioCtx);
}
}
async function playAudioData(
audioData: Uint8Array,
audioContext: AudioContext,
) {
// https://stackoverflow.com/questions/60921018/web-audio-api-efficiently-play-a-pcm-stream
const pcmAudio: Float32Array = new Float32Array(audioData.buffer);
const audioBuffer = audioContext.createBuffer(1, pcmAudio.length, 24000);
audioBuffer.copyToChannel(pcmAudio, 0);
const source = audioContext.createBufferSource();
source.buffer = audioBuffer;
source.connect(audioContext.destination);
source.start();
await new Promise((resolve) => (source.onended = resolve));
}