Web Audio API:有效地播放PCM流

弗拉德里亚

这是一个问题:

  • 我的JS应用程序(通过WebRTC数据通道)接收原始PCM数据,
  • 采样率为88200(我可以很容易地将其更改为另一端的44100),
  • 数据已经正确地以4字节浮点[-1,1]小尾数采样编码,
  • 数据以512个样本的块(512 * 4字节)到达,
  • 数据可以随时开始到达,可以持续任何时间,可以停止,也可以恢复。
  • 目的是渲染声音。

我所做的是:

var samples = []; // each element of this array stores a chunk of 512 samples
var audioCtx = new AudioContext();
var source = audioCtx.createBufferSource();

source.buffer = audioCtx.createBuffer(1, 512, 88200);

// bufferSize is 512 because it is the size of chunks
var scriptNode = audioCtx.createScriptProcessor(512, 1, 1);

scriptNode.onaudioprocess = function(audioProcessingEvent) {
  // play a chunk if there is at least one.
  if (samples.length > 0) {
    audioProcessingEvent.outputBuffer.copyToChannel(samples.shift(), 0, 0);
  }
};

source.connect(scriptNode);
scriptNode.connect(audioCtx.destination);
source.start();

peerConnection.addEventListener("datachannel", function(e) {
  e.channel.onmessage = function(m) {
    var values = new Float32Array(m.data);
    samples.push(values);
  };
);

有几个问题:

  • audioProcessingEvent.outputBuffer.sampleRate一直都是48000显然,它是不依赖于的比特率的source,我找不到将其设置为的方法8820044100找不到任何其他值。声音呈现的延迟会不断增长。
  • ScriptProcessorNode 不推荐使用。
  • 就处理器而言,这是一种非常昂贵的方法。

预先感谢您的任何建议!

海道

您需要一个AudioBuffer

您可以直接从TypedArray将原始PCM数据复制到其通道中。
您可以指定其sampleRate,AudioContext将负责重新采样以匹配声卡的设置。

但是请注意,每个块2048个字节意味着@ 88Khz,每个块将仅表示5毫秒的音频数据:我们传递了一个Float32Array,因此byteSize为4,并且2048/4/88200 =±0.0058s。
您可能会希望增加它,并实施一些缓冲策略。

这是一个小演示,作为概念证明,将大块的数据存储到缓冲区Float32Array中。

const min_sample_duration = 2; // sec
const sample_rate = 88200; // Hz
// how much data is needed to play for at least min_sample_duration
const min_sample_size = min_sample_duration * sample_rate;

const fetching_interval = 100; // ms

// you'll probably want this much bigger
let chunk_size = 2048; // bytes

const log = document.getElementById( 'log' );
const btn = document.getElementById( 'btn' );

btn.onclick = e => {

  let stopped = false;
  let is_reading = false;
  
  const ctx = new AudioContext();
  // to control output volume
  const gain = ctx.createGain();
  gain.gain.value = 0.01;
  gain.connect( ctx.destination );
  // this will get updated at every new fetch
  let fetched_data  = new Float32Array( 0 );
  // keep it accessible so we can stop() it
  let active_node;

  // let's begin
  periodicFetch();

  // UI update
  btn.textContent = "stop";
  btn.onclick = e => {
    stopped = true;
    if( active_node ) { active_node.stop(0); }
  };
  oninput = handleUIEvents;

  // our fake fetcher, calls itself every 50ms
  function periodicFetch() {

    // data from server (here just some noise)
    const noise = Float32Array.from( { length: chunk_size / 4 }, _ => (Math.random() * 1) - 0.5 );
    // we concatenate the data just fetched with what we have already buffered
    fetched_data = concatFloat32Arrays( fetched_data, noise );
    // for demo only
    log.textContent = "buffering: " +  fetched_data.length + '/ ' + min_sample_size;

    if( !stopped ) {
      // do it again
      setTimeout( periodicFetch , fetching_interval );
    }
    // if we are not actively reading and have fetched enough
    if( !is_reading && fetched_data.length > min_sample_size ) {
      readingLoop(); // start reading
    }
  
  }
  function readingLoop() {
  
    if( stopped  || fetched_data.length < min_sample_size ) {
      is_reading = false;
      return;
    }
    // let the world know we are actively reading
    is_reading = true;
    // create a new AudioBuffer
    const aud_buf = ctx.createBuffer( 1, fetched_data.length, sample_rate );
    // copy our fetched data to its first channel
    aud_buf.copyToChannel( fetched_data, 0 );

    // clear the buffered data
    fetched_data = new Float32Array( 0 );
    
    // the actual player
    active_node = ctx.createBufferSource();
    active_node.buffer = aud_buf;
    active_node.onended = readingLoop; // in case we buffered enough while playing
    active_node.connect( gain );
    active_node.start( 0 );

  }

  function handleUIEvents( evt ) {

    const type = evt.target.name;
    const value = evt.target.value;
    switch( type ) {
      case "chunk-size":
        chunk_size = +value;
        break;
      case "volume":
        gain.gain.value = +value;
        break;
    }

  }

};

// helpers
function concatFloat32Arrays( arr1, arr2 ) {
  if( !arr1 || !arr1.length ) {
    return arr2 && arr2.slice();
  }
  if( !arr2 || !arr2.length ) {
    return arr1 && arr1.slice();
  }
  const out = new Float32Array( arr1.length + arr2.length );
  out.set( arr1 );
  out.set( arr2, arr1.length );
  return out;
}
label { display: block }
<button id="btn">start</button>
<pre id="log"></pre>

<div>
<label>Output volume:<input type="range" name="volume" min="0" max="0.5" step="0.01" value="0.01"></label>
</div>
<div>
Size of each chunk fetched:
  <label><input type="radio" name="chunk-size" value="2048" checked>2048 bytes (OP's current)</label>
  <label><input type="radio" name="chunk-size" value="35280">35280 bytes (barely enough for 0.1s interval)</label>
  <label><input type="radio" name="chunk-size" value="44100">44100 bytes (enough for 0.1s interval)</label>
</div>

本文收集自互联网,转载请注明来源。

如有侵权,请联系[email protected] 删除。

编辑于
0

我来说两句

0条评论
登录后参与评论

相关文章

来自分类Dev

单击使用Web Audio Api播放的流中的声音

来自分类Dev

Web Audio API和<audio>标签

来自分类Dev

Web Audio API和<audio>标签

来自分类Dev

Web Audio API互相播放说明

来自分类Dev

Web Audio API 延迟缓冲播放

来自分类Dev

Web Audio Api,设置增益

来自分类Dev

与Web Audio API完美同步

来自分类Dev

在Web Audio中播放原始音频PCM样本

来自分类Dev

如何使用流有效地读取带有嵌套对象的soap web服务响应

来自分类Dev

基于Web的广播和音频流服务是否使用Web Audio API进行播放?

来自分类Dev

寻找有关如何更有效地清理Web API返回的Python NoneType值的建议

来自分类Dev

Web Audio API,从声卡获取输出

来自分类Dev

Web Audio API中音符的频率

来自分类Dev

Web Audio API的位深度是多少?

来自分类Dev

Web Audio API中的反馈循环

来自分类Dev

Web Audio API,设置高音和低音

来自分类Dev

Create a waveform of the full track with Web Audio API

来自分类Dev

Shoutcast + Web Audio API CORS问题

来自分类Dev

使用Web Audio API缓冲音频

来自分类Dev

Web Audio API如何命名声音

来自分类Dev

Web Audio API-计划问题

来自分类Dev

Web Audio API中AudioWorkerNode的状态

来自分类Dev

Web Audio API故障/变形问题

来自分类Dev

Web Audio API通道未缩混

来自分类Dev

Web Audio API延迟后停止

来自分类Dev

使用Web Audio API缓冲音频

来自分类Dev

Web Audio API故障/变形问题

来自分类Dev

Web Audio API流:为什么dataArray不更改?

来自分类Dev

通过Web Audio API使用多个audioContext是否有问题?