tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

audio-encoder.https.any.js (18192B)


      1 // META: global=window
      2 // META: script=/webcodecs/utils.js
      3 
      4 // Merge all audio buffers into a new big one with all the data.
      5 function join_audio_data(audio_data_array) {
      6  assert_greater_than_equal(audio_data_array.length, 0);
      7  let total_frames = 0;
      8  let base_buffer = audio_data_array[0];
      9  for (const data of audio_data_array) {
     10    assert_not_equals(data, null);
     11    assert_equals(data.sampleRate, base_buffer.sampleRate);
     12    assert_equals(data.numberOfChannels, base_buffer.numberOfChannels);
     13    assert_equals(data.format, base_buffer.format);
     14    total_frames += data.numberOfFrames;
     15  }
     16 
     17  assert_true(base_buffer.format == 'f32' || base_buffer.format == 'f32-planar');
     18 
     19  if (base_buffer.format == 'f32')
     20    return join_interleaved_data(audio_data_array, total_frames);
     21 
     22  // The format is 'FLTP'.
     23  return join_planar_data(audio_data_array, total_frames);
     24 }
     25 
     26 function join_interleaved_data(audio_data_array, total_frames) {
     27  let base_data =  audio_data_array[0];
     28  let channels = base_data.numberOfChannels;
     29  let total_samples = total_frames * channels;
     30 
     31  let result = new Float32Array(total_samples);
     32 
     33  let copy_dest = new Float32Array(base_data.numberOfFrames * channels);
     34 
     35  // Copy all the interleaved data.
     36  let position = 0;
     37  for (const data of audio_data_array) {
     38    let samples = data.numberOfFrames * channels;
     39    if (copy_dest.length < samples)
     40      copy_dest = new Float32Array(samples);
     41 
     42    data.copyTo(copy_dest, {planeIndex: 0});
     43    result.set(copy_dest, position);
     44    position += samples;
     45  }
     46 
     47  assert_equals(position, total_samples);
     48 
     49  return result;
     50 }
     51 
     52 function join_planar_data(audio_data_array, total_frames) {
     53  let base_frames = audio_data_array[0].numberOfFrames;
     54  let channels = audio_data_array[0].numberOfChannels;
     55  let result = new Float32Array(total_frames*channels);
     56  let copyDest = new Float32Array(base_frames);
     57 
     58  // Merge all samples and lay them out according to the FLTP memory layout.
     59  let position = 0;
     60  for (let ch = 0; ch < channels; ch++) {
     61    for (const data of audio_data_array) {
     62      data.copyTo(copyDest, { planeIndex: ch});
     63      result.set(copyDest, position);
     64      position += data.numberOfFrames;
     65    }
     66  }
     67  assert_equals(position, total_frames * channels);
     68 
     69  return result;
     70 }
     71 
     72 promise_test(async t => {
     73  let sample_rate = 48000;
     74  let total_duration_s = 1;
     75  let data_count = 10;
     76  let outputs = [];
     77  let init = {
     78    error: e => {
     79      assert_unreached("error: " + e);
     80    },
     81    output: chunk => {
     82      outputs.push(chunk);
     83    }
     84  };
     85 
     86  let encoder = new AudioEncoder(init);
     87 
     88  assert_equals(encoder.state, "unconfigured");
     89  let config = {
     90    codec: 'opus',
     91    sampleRate: sample_rate,
     92    numberOfChannels: 2,
     93    bitrate: 256000 //256kbit
     94  };
     95 
     96  encoder.configure(config);
     97 
     98  let timestamp_us = 0;
     99  let data_duration_s = total_duration_s / data_count;
    100  let data_length = data_duration_s * config.sampleRate;
    101  for (let i = 0; i < data_count; i++) {
    102    let data = make_audio_data(timestamp_us, config.numberOfChannels,
    103      config.sampleRate, data_length);
    104    encoder.encode(data);
    105    data.close();
    106    timestamp_us += data_duration_s * 1_000_000;
    107  }
    108  await encoder.flush();
    109  encoder.close();
    110  assert_greater_than_equal(outputs.length, data_count);
    111  assert_equals(outputs[0].timestamp, 0, "first chunk timestamp");
    112  let total_encoded_duration = 0
    113  for (chunk of outputs) {
    114    assert_greater_than(chunk.byteLength, 0);
    115    assert_greater_than_equal(timestamp_us, chunk.timestamp);
    116    assert_greater_than(chunk.duration, 0);
    117    total_encoded_duration += chunk.duration;
    118  }
    119 
    120  // The total duration might be padded with silence.
    121  assert_greater_than_equal(
    122      total_encoded_duration, total_duration_s * 1_000_000);
    123 }, 'Simple audio encoding');
    124 
    125 promise_test(async t => {
    126  let outputs = 0;
    127  let init = getDefaultCodecInit(t);
    128  let firstOutput = new Promise(resolve => {
    129    init.output = (chunk, metadata) => {
    130      outputs++;
    131      assert_equals(outputs, 1, 'outputs');
    132      encoder.reset();
    133      resolve();
    134    };
    135  });
    136 
    137  let encoder = new AudioEncoder(init);
    138  let config = {
    139    codec: 'opus',
    140    sampleRate: 48000,
    141    numberOfChannels: 2,
    142    bitrate: 256000  // 256kbit
    143  };
    144  encoder.configure(config);
    145 
    146  let frame_count = 1024;
    147  let frame1 = make_audio_data(
    148      0, config.numberOfChannels, config.sampleRate, frame_count);
    149  let frame2 = make_audio_data(
    150      frame_count / config.sampleRate, config.numberOfChannels,
    151      config.sampleRate, frame_count);
    152  t.add_cleanup(() => {
    153    frame1.close();
    154    frame2.close();
    155  });
    156 
    157  encoder.encode(frame1);
    158  encoder.encode(frame2);
    159  const flushDone = encoder.flush();
    160 
    161  // Wait for the first output, then reset.
    162  await firstOutput;
    163 
    164  // Flush should have been synchronously rejected.
    165  await promise_rejects_dom(t, 'AbortError', flushDone);
    166 
    167  assert_equals(outputs, 1, 'outputs');
    168 }, 'Test reset during flush');
    169 
    170 promise_test(async t => {
    171  let sample_rate = 48000;
    172  let total_duration_s = 1;
    173  let data_count = 10;
    174  let outputs = [];
    175  let init = {
    176    error: e => {
    177      assert_unreached('error: ' + e);
    178    },
    179    output: chunk => {
    180      outputs.push(chunk);
    181    }
    182  };
    183 
    184  let encoder = new AudioEncoder(init);
    185 
    186  assert_equals(encoder.state, 'unconfigured');
    187  let config = {
    188    codec: 'opus',
    189    sampleRate: sample_rate,
    190    numberOfChannels: 2,
    191    bitrate: 256000  // 256kbit
    192  };
    193 
    194  encoder.configure(config);
    195 
    196  let timestamp_us = -10000;
    197  let data = make_audio_data(
    198      timestamp_us, config.numberOfChannels, config.sampleRate, 10000);
    199  encoder.encode(data);
    200  data.close();
    201  await encoder.flush();
    202  encoder.close();
    203  assert_greater_than_equal(outputs.length, 1);
    204  assert_equals(outputs[0].timestamp, -10000, 'first chunk timestamp');
    205  for (chunk of outputs) {
    206    assert_greater_than(chunk.byteLength, 0);
    207    assert_greater_than_equal(chunk.timestamp, timestamp_us);
    208  }
    209 }, 'Encode audio with negative timestamp');
    210 
    211 async function checkEncodingError(t, config, good_data, bad_data) {
    212  let support = await AudioEncoder.isConfigSupported(config);
    213  assert_true(support.supported)
    214  config = support.config;
    215 
    216  const callbacks = {};
    217  let errors = 0;
    218  let gotError = new Promise(resolve => callbacks.error = e => {
    219    errors++;
    220    resolve(e);
    221  });
    222 
    223  let outputs = 0;
    224  callbacks.output = chunk => {
    225    outputs++;
    226  };
    227 
    228  let encoder = new AudioEncoder(callbacks);
    229  encoder.configure(config);
    230  for (let data of good_data) {
    231    encoder.encode(data);
    232    data.close();
    233  }
    234  await encoder.flush();
    235 
    236  let txt_config = "sampleRate: " + config.sampleRate
    237                 + " numberOfChannels: " + config.numberOfChannels;
    238  assert_equals(errors, 0, txt_config);
    239  assert_greater_than(outputs, 0);
    240  outputs = 0;
    241 
    242  encoder.encode(bad_data);
    243  await promise_rejects_dom(t, 'EncodingError', encoder.flush().catch((e) => {
    244    assert_equals(errors, 1);
    245    throw e;
    246  }));
    247 
    248  assert_equals(outputs, 0);
    249  let e = await gotError;
    250  assert_true(e instanceof DOMException);
    251  assert_equals(e.name, 'EncodingError');
    252  assert_equals(encoder.state, 'closed', 'state');
    253 }
    254 
    255 function channelNumberVariationTests() {
    256  let sample_rate = 48000;
    257  for (let channels = 1; channels <= 2; channels++) {
    258    let config = {
    259      codec: 'opus',
    260      sampleRate: sample_rate,
    261      numberOfChannels: channels,
    262      bitrate: 128000
    263    };
    264 
    265    let ts = 0;
    266    let length = sample_rate / 10;
    267    let data1 = make_audio_data(ts, channels, sample_rate, length);
    268 
    269    ts += Math.floor(data1.duration / 1000000);
    270    let data2 = make_audio_data(ts, channels, sample_rate, length);
    271    ts += Math.floor(data2.duration / 1000000);
    272 
    273    let bad_data = make_audio_data(ts, channels + 1, sample_rate, length);
    274    promise_test(
    275        async t => checkEncodingError(t, config, [data1, data2], bad_data),
    276        'Channel number variation: ' + channels);
    277  }
    278 }
    279 channelNumberVariationTests();
    280 
    281 function sampleRateVariationTests() {
    282  let channels = 1
    283  for (let sample_rate = 3000; sample_rate < 96000; sample_rate += 10000) {
    284    let config = {
    285      codec: 'opus',
    286      sampleRate: sample_rate,
    287      numberOfChannels: channels,
    288      bitrate: 128000
    289    };
    290 
    291    let ts = 0;
    292    let length = sample_rate / 10;
    293    let data1 = make_audio_data(ts, channels, sample_rate, length);
    294 
    295    ts += Math.floor(data1.duration / 1000000);
    296    let data2 = make_audio_data(ts, channels, sample_rate, length);
    297    ts += Math.floor(data2.duration / 1000000);
    298 
    299    let bad_data = make_audio_data(ts, channels, sample_rate + 333, length);
    300    promise_test(
    301        async t => checkEncodingError(t, config, [data1, data2], bad_data),
    302        'Sample rate variation: ' + sample_rate);
    303  }
    304 }
    305 sampleRateVariationTests();
    306 
    307 promise_test(async t => {
    308  let sample_rate = 48000;
    309  let total_duration_s = 1;
    310  let data_count = 10;
    311  let input_data = [];
    312  let output_data = [];
    313 
    314  let decoder_init = {
    315    error: t.unreached_func("Decode error"),
    316    output: data => {
    317      output_data.push(data);
    318    }
    319  };
    320  let decoder = new AudioDecoder(decoder_init);
    321 
    322  let encoder_init = {
    323    error: t.unreached_func("Encoder error"),
    324    output: (chunk, metadata) => {
    325      let config = metadata.decoderConfig;
    326      if (config)
    327        decoder.configure(config);
    328      decoder.decode(chunk);
    329    }
    330  };
    331  let encoder = new AudioEncoder(encoder_init);
    332 
    333  let config = {
    334    codec: 'opus',
    335    sampleRate: sample_rate,
    336    numberOfChannels: 2,
    337    bitrate: 256000, //256kbit
    338  };
    339  encoder.configure(config);
    340 
    341  let timestamp_us = 0;
    342  const data_duration_s = total_duration_s / data_count;
    343  const data_length = data_duration_s * config.sampleRate;
    344  for (let i = 0; i < data_count; i++) {
    345    let data = make_audio_data(timestamp_us, config.numberOfChannels,
    346      config.sampleRate, data_length);
    347    input_data.push(data);
    348    encoder.encode(data);
    349    timestamp_us += data_duration_s * 1_000_000;
    350  }
    351  await encoder.flush();
    352  encoder.close();
    353  await decoder.flush();
    354  decoder.close();
    355 
    356 
    357  let total_input = join_audio_data(input_data);
    358  let frames_per_plane = total_input.length / config.numberOfChannels;
    359 
    360  let total_output = join_audio_data(output_data);
    361 
    362  let base_input = input_data[0];
    363  let base_output = output_data[0];
    364 
    365  // TODO: Convert formats to simplify conversions, once
    366  // https://github.com/w3c/webcodecs/issues/232 is resolved.
    367  assert_equals(base_input.format, "f32-planar");
    368  assert_equals(base_output.format, "f32");
    369 
    370  assert_equals(base_output.numberOfChannels, config.numberOfChannels);
    371  assert_equals(base_output.sampleRate, sample_rate);
    372 
    373  // Output can be slightly longer that the input due to padding
    374  assert_greater_than_equal(total_output.length, total_input.length);
    375 
    376  // Compare waveform before and after encoding
    377  for (let channel = 0; channel < base_input.numberOfChannels; channel++) {
    378 
    379    let plane_start = channel * frames_per_plane;
    380    let input_plane = total_input.slice(
    381        plane_start, plane_start + frames_per_plane);
    382 
    383    for (let i = 0; i < base_input.numberOfFrames; i += 10) {
    384      // Instead of de-interleaving the data, directly look into |total_output|
    385      // for the sample we are interested in.
    386      let ouput_index = i * base_input.numberOfChannels + channel;
    387 
    388      // Checking only every 10th sample to save test time in slow
    389      // configurations like MSAN etc.
    390      assert_approx_equals(
    391          input_plane[i], total_output[ouput_index], 0.5,
    392          'Difference between input and output is too large.' +
    393              ' index: ' + i + ' channel: ' + channel +
    394              ' input: ' + input_plane[i] +
    395              ' output: ' + total_output[ouput_index]);
    396    }
    397  }
    398 
    399 }, 'Encoding and decoding');
    400 
    401 promise_test(async t => {
    402  let output_count = 0;
    403  let encoder_config = {
    404    codec: 'opus',
    405    sampleRate: 24000,
    406    numberOfChannels: 1,
    407    bitrate: 96000
    408  };
    409  let decoder_config = null;
    410 
    411  let init = {
    412    error: t.unreached_func("Encoder error"),
    413    output: (chunk, metadata) => {
    414      let config = metadata.decoderConfig;
    415      // Only the first invocation of the output callback is supposed to have
    416      // a |config| in it.
    417      output_count++;
    418      if (output_count == 1) {
    419        assert_equals(typeof config, "object");
    420        decoder_config = config;
    421      } else {
    422        assert_equals(config, undefined);
    423      }
    424    }
    425  };
    426 
    427  let encoder = new AudioEncoder(init);
    428  encoder.configure(encoder_config);
    429 
    430  let large_data = make_audio_data(0, encoder_config.numberOfChannels,
    431    encoder_config.sampleRate, encoder_config.sampleRate);
    432  encoder.encode(large_data);
    433  await encoder.flush();
    434 
    435  // Large data produced more than one output, and we've got decoder_config
    436  assert_greater_than(output_count, 1);
    437  assert_not_equals(decoder_config, null);
    438  assert_equals(decoder_config.codec, encoder_config.codec);
    439  assert_equals(decoder_config.sampleRate, encoder_config.sampleRate);
    440  assert_equals(decoder_config.numberOfChannels, encoder_config.numberOfChannels);
    441 
    442  // Check that description start with 'Opus'
    443  let extra_data = new Uint8Array(decoder_config.description);
    444  assert_equals(extra_data[0], 0x4f);
    445  assert_equals(extra_data[1], 0x70);
    446  assert_equals(extra_data[2], 0x75);
    447  assert_equals(extra_data[3], 0x73);
    448 
    449  decoder_config = null;
    450  output_count = 0;
    451  encoder_config.bitrate = 256000;
    452  encoder.configure(encoder_config);
    453  encoder.encode(large_data);
    454  await encoder.flush();
    455 
    456  // After reconfiguring encoder should produce decoder config again
    457  assert_greater_than(output_count, 1);
    458  assert_not_equals(decoder_config, null);
    459  assert_not_equals(decoder_config.description, null);
    460  encoder.close();
    461 }, "Emit decoder config and extra data.");
    462 
    463 promise_test(async t => {
    464  let sample_rate = 48000;
    465  let total_duration_s = 1;
    466  let data_count = 100;
    467  let init = getDefaultCodecInit(t);
    468  init.output = (chunk, metadata) => {}
    469 
    470  let encoder = new AudioEncoder(init);
    471 
    472  // No encodes yet.
    473  assert_equals(encoder.encodeQueueSize, 0);
    474 
    475  let config = {
    476    codec: 'opus',
    477    sampleRate: sample_rate,
    478    numberOfChannels: 2,
    479    bitrate: 256000 //256kbit
    480  };
    481  encoder.configure(config);
    482 
    483  // Still no encodes.
    484  assert_equals(encoder.encodeQueueSize, 0);
    485 
    486  let datas = [];
    487  let timestamp_us = 0;
    488  let data_duration_s = total_duration_s / data_count;
    489  let data_length = data_duration_s * config.sampleRate;
    490  for (let i = 0; i < data_count; i++) {
    491    let data = make_audio_data(timestamp_us, config.numberOfChannels,
    492      config.sampleRate, data_length);
    493    datas.push(data);
    494    timestamp_us += data_duration_s * 1_000_000;
    495  }
    496 
    497  let lastDequeueSize = Infinity;
    498  encoder.ondequeue = () => {
    499    assert_greater_than(lastDequeueSize, 0, "Dequeue event after queue empty");
    500    assert_greater_than(lastDequeueSize, encoder.encodeQueueSize,
    501                        "Dequeue event without decreased queue size");
    502    lastDequeueSize = encoder.encodeQueueSize;
    503  };
    504 
    505  for (let data of datas)
    506    encoder.encode(data);
    507 
    508  assert_greater_than_equal(encoder.encodeQueueSize, 0);
    509  assert_less_than_equal(encoder.encodeQueueSize, data_count);
    510 
    511  await encoder.flush();
    512  // We can guarantee that all encodes are processed after a flush.
    513  assert_equals(encoder.encodeQueueSize, 0);
    514  // Last dequeue event should fire when the queue is empty.
    515  assert_equals(lastDequeueSize, 0);
    516 
    517  // Reset this to Infinity to track the decline of queue size for this next
    518  // batch of encodes.
    519  lastDequeueSize = Infinity;
    520 
    521  for (let data of datas) {
    522    encoder.encode(data);
    523    data.close();
    524  }
    525 
    526  assert_greater_than_equal(encoder.encodeQueueSize, 0);
    527  encoder.reset();
    528  assert_equals(encoder.encodeQueueSize, 0);
    529 }, 'encodeQueueSize test');
    530 
    531 const testOpusEncoderConfigs = [
    532  {
    533    comment: 'Empty Opus config',
    534    opus: {},
    535  },
    536  {
    537    comment: 'Opus with frameDuration',
    538    opus: {frameDuration: 2500},
    539  },
    540  {
    541    comment: 'Opus with complexity',
    542    opus: {complexity: 10},
    543  },
    544  {
    545    comment: 'Opus with useinbandfec',
    546    opus: {
    547      packetlossperc: 15,
    548      useinbandfec: true,
    549    },
    550  },
    551  {
    552    comment: 'Opus with usedtx',
    553    opus: {usedtx: true},
    554  },
    555  {
    556    comment: 'Opus mixed parameters',
    557    opus: {
    558      frameDuration: 40000,
    559      complexity: 0,
    560      packetlossperc: 10,
    561      useinbandfec: true,
    562      usedtx: true,
    563    },
    564  }
    565 ];
    566 
    567 testOpusEncoderConfigs.forEach(entry => {
    568  promise_test(async t => {
    569    let sample_rate = 48000;
    570    let total_duration_s = 0.5;
    571    let data_count = 10;
    572    let outputs = [];
    573    let init = {
    574      error: e => {
    575        assert_unreached('error: ' + e);
    576      },
    577      output: chunk => {
    578        outputs.push(chunk);
    579      }
    580    };
    581 
    582    let encoder = new AudioEncoder(init);
    583 
    584    assert_equals(encoder.state, 'unconfigured');
    585    let config = {
    586      codec: 'opus',
    587      sampleRate: sample_rate,
    588      numberOfChannels: 2,
    589      bitrate: 256000,  // 256kbit
    590      opus: entry.opus,
    591    };
    592 
    593    encoder.configure(config);
    594 
    595    let timestamp_us = 0;
    596    let data_duration_s = total_duration_s / data_count;
    597    let data_length = data_duration_s * config.sampleRate;
    598    for (let i = 0; i < data_count; i++) {
    599      let data = make_audio_data(
    600          timestamp_us, config.numberOfChannels, config.sampleRate,
    601          data_length);
    602      encoder.encode(data);
    603      data.close();
    604      timestamp_us += data_duration_s * 1_000_000;
    605    }
    606 
    607    // Encoders might output an extra buffer of silent padding.
    608    let padding_us = data_duration_s * 1_000_000;
    609 
    610    await encoder.flush();
    611    encoder.close();
    612    assert_greater_than_equal(outputs.length, data_count);
    613    assert_equals(outputs[0].timestamp, 0, 'first chunk timestamp');
    614    let total_encoded_duration = 0
    615    for (chunk of outputs) {
    616      assert_greater_than(chunk.byteLength, 0, 'chunk byteLength');
    617      assert_greater_than_equal(
    618          timestamp_us + padding_us, chunk.timestamp, 'chunk timestamp');
    619      assert_greater_than(chunk.duration, 0, 'chunk duration');
    620      total_encoded_duration += chunk.duration;
    621    }
    622 
    623    // The total duration might be padded with silence.
    624    assert_greater_than_equal(
    625        total_encoded_duration, total_duration_s * 1_000_000);
    626  }, 'Test encoding Opus with additional parameters: ' + entry.comment);
    627 });