通过createMediaElementSource从<audio/>元素将音频数据加载到AudioBufferSourceNode?

21
是否可以通过使用createMediaElementSource<audio/>元素中加载音频文件,然后将音频数据加载到AudioBufferSourceNode中?使用音频元素作为源(MediaElementSource)似乎不是一个选项,因为我想使用缓冲区方法,如noteOnnoteGrain。直接通过XHR将音频文件加载到缓冲区也不是一种选择(见Open stream_url of a Soundcloud Track via Client-Side XHR?)。不过,从音频元素中加载缓冲区内容似乎是可能的:http://www.w3.org/2011/audio/wiki/Spec_Differences#Reading_Data_from_a_Media_Element。或者甚至可以直接使用<audio/>元素的缓冲区作为源节点吗?
4个回答

9

今天是:请查看我的答案 https://dev59.com/0mgu5IYBdhLWcg3we3Ct#70753627 - frank-dspeed

6
这是可以实现的。请查看我在http://updates.html5rocks.com/2012/02/HTML5-audio-and-the-Web-Audio-API-are-BFFs中的帖子。那里还有代码片段和示例。虽然还存在一些问题,但将<audio>加载到Web Audio API中应该可以按照您的要求工作。
// Create an <audio> element dynamically.
var audio = new Audio();
audio.src = 'myfile.mp3';
audio.controls = true;
audio.autoplay = true;
document.body.appendChild(audio);

var context = new webkitAudioContext();
var analyser = context.createAnalyser();

// Wait for window.onload to fire. See crbug.com/112368
window.addEventListener('load', function(e) {
  // Our <audio> element will be the audio source.
  var source = context.createMediaElementSource(audio);
  source.connect(analyser);
  analyser.connect(context.destination);

  // ...call requestAnimationFrame() and render the analyser's output to canvas.
}, false);

6
是的,但我想知道是否有可能从MediaElementSource获取缓冲区并将其用作/在bufferSourceNode中(具有noteOn、noteOff、noteGrain等)的缓冲源节点? - gherkins
为什么让100多的东西浪费呢...所以就这样吧 :) - gherkins

3
今天2020年以后,可以通过audioWorklet节点实现。

https://developer.mozilla.org/en-US/docs/Web/API/AudioWorkletProcessor/AudioWorkletProcessor

在AudioWorkletContext中运行,因此只有通过消息传递二进制原始数据的方式。
// test-processor.js
class RecorderWorkletProcessor extends AudioWorkletProcessor {
  constructor (options) {
    super()
    console.log(options.numberOfInputs)
    console.log(options.processorOptions.someUsefulVariable)
  }
  // @ts-ignore 
  process(inputs, output, parameters) {
      /**
      * @type {Float32Array} length 128 Float32Array(128)
      * non-interleaved IEEE754 32-bit linear PCM 
      * with a nominal range between -1 and +1, 
      * with each sample between -1.0 and 1.0.
      * the sample rate depends on the audioContext and is variable
      */
      const inputChannel = inputs[0][0];  //inputChannel Float32Array(128)
      const { postMessage } = this.port;
      postMessage(inputChannel)  // float32Array sent as byte[512] 
      return true; // always do this!
  }
}

主代码
const audioContext = new AudioContext()

const audioMediaElement = audioContext.createMediaElementSource(
  /** @type {HTMLAudioElement} */ audio
);

await audioContext.audioWorklet.addModule('test-processor.js')
const recorder = new AudioWorkletNode(audioContext, 'test-processor', 
  {
     processorOptions: {
     someUsefulVariable: new Map([[1, 'one'], [2, 'two']])
  }
});

/**
 *   
 * Objects of these types are designed to hold small audio snippets, 
 * typically less than 45 s. For longer sounds, objects implementing 
 * the MediaElementAudioSourceNode are more suitable. 
 * The buffer contains data in the following format: 
 * non-interleaved IEEE754 32-bit linear PCM (LPCM)
 * with a nominal range between -1 and +1, that is, a 32-bit floating point buffer, 
 * with each sample between -1.0 and 1.0.  
 * @param {ArrayBufferLike|Float32Array} data 
 */
 const convertFloatToAudioBuffer = (data) => {
    const sampleRate = 8000 | audioContext.sampleRate
    const channels = 1;
    const sampleLength = 128 | data.length; // 1sec = sampleRate * 1
    const audioBuffer = audioContext.createBuffer(channels, sampleLength, sampleRate); // Empty Audio
    audioBuffer.copyToChannel(new Float32Array(data), 0); // depending on your processing this could be already a float32array
    return audioBuffer;
}
let startAt = 0
const streamDestination = audioContext.createMediaStreamDestination();
/**
 * Note this is a minimum example it plays only the first sound 
 * it uses the main audio context if it would use a
 * streamDestination = context.createMediaStreamDestination();
 * 
 * @param {ArrayBufferLike|Float32Array} data 
 */
const play = (data) => {
    const audioBufferSourceNoce = audioContext.createBufferSource();  
    audioBufferSourceNoce.buffer = convertFloatToAudioBuffer(data);

    const context = audioContext; // streamDestination; // creates a MediaStream on streamDestination.stream property
    audioBufferSourceNoce.connect(context);    

    // here you will need a hugh enqueue algo that is out of scope for this answer
    startAt = Math.max(context.currentTime, startAt);
    source.start(startAt);
    startAt += buffer.duration;
    audioBufferSourceNoce.start(startAt);
}

// Here is your raw arrayBuffer ev.data
recorder.port.onmessage = (ev) => play(ev.data);

// connect the processor with the source
audioMediaElement.connect(recorder);




注意

这只是最基本的方法,用于在控制台中记录来自录音处理器的数据。当您真正想要处理这些数据时,您应该考虑在工作线程中执行操作,再注册一个处理程序,将数据直接发送到该工作线程,否则如果您进行大量处理,主进程可能会变得无响应。


很好,我真的没有时间去研究它! - gherkins
我不知道为什么,在testprocessor.js中解构不起作用;必须使用以下方法:this.port.postMessage(inputChannel); //将float32Array作为byte[512]发送。 - patrick
@patrick,我也不知道为什么它对你不起作用,但正如所说,即使只是从我的一个更大的项目中复制了最基本的部分,也可能是我的构建管道修复了它。我之所以发布这个帖子,是因为我看到了错误的答案。 - frank-dspeed
同时,你需要在 test-processor.js 的末尾添加以下代码:registerProcessor("testprocessorr", RecorderWorkletProcessor); 否则它将无法在主代码中使用。 - patrick

0

我不确定你是否已经找到了更好的解决方案,我也查看了你发布的W3C链接: http://www.w3.org/2011/audio/wiki/Spec_Differences#Reading_Data_from_a_Media_Element

但是为了让它真正起作用,您必须使用AudioContext.createScriptProcessor()。我还没有尝试过这个,但基本上您需要将源节点(音频元素)连接到脚本处理器,但如果您不需要输出音频,则甚至不要输出音频。在onaudioprocess回调中,您可以直接访问音频缓冲区数据(当然是以指定大小的块为单位)。上面的链接中有示例。

此外,我认为您可以以某种方式调整播放速度,以便更快地获取更多的缓冲区数组。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接