tor-browser

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

audio-encoder-codec-specific.https.any.js (10305B)


      1 // META: global=window
      2 // META: script=/webcodecs/utils.js
      3 
      4 function make_silent_audio_data(timestamp, channels, sampleRate, frames) {
      5  let data = new Float32Array(frames*channels);
      6 
      7  return new AudioData({
      8    timestamp: timestamp,
      9    data: data,
     10    numberOfChannels: channels,
     11    numberOfFrames: frames,
     12    sampleRate: sampleRate,
     13    format: "f32-planar",
     14  });
     15 }
     16 
     17 // The Opus DTX flag (discontinuous transmission) reduces the encoding bitrate
     18 // for silence. This test ensures the DTX flag is working properly by encoding
     19 // almost 10s of silence and comparing the bitrate with and without the flag.
     20 promise_test(async t => {
     21  let sample_rate = 48000;
     22  let total_duration_s = 10;
     23  let data_count = 100;
     24  let normal_outputs = [];
     25  let dtx_outputs = [];
     26 
     27  let normal_encoder = new AudioEncoder({
     28    error: e => {
     29      assert_unreached('error: ' + e);
     30    },
     31    output: chunk => {
     32      normal_outputs.push(chunk);
     33    }
     34  });
     35 
     36  let dtx_encoder = new AudioEncoder({
     37    error: e => {
     38      assert_unreached('error: ' + e);
     39    },
     40    output: chunk => {
     41      dtx_outputs.push(chunk);
     42    }
     43  });
     44 
     45  let config = {
     46    codec: 'opus',
     47    sampleRate: sample_rate,
     48    numberOfChannels: 2,
     49    bitrate: 256000,  // 256kbit
     50  };
     51 
     52  let normal_config = {...config, opus: {usedtx: false}};
     53  let dtx_config = {...config, opus: {usedtx: true}};
     54 
     55  let normal_config_support = await AudioEncoder.isConfigSupported(normal_config);
     56  assert_implements_optional(normal_config_support.supported, "Opus not supported");
     57 
     58  let dtx_config_support = await AudioEncoder.isConfigSupported(dtx_config);
     59  assert_implements_optional(dtx_config_support.supported, "Opus DTX not supported");
     60 
     61  // Configure one encoder with and one without the DTX flag
     62  normal_encoder.configure(normal_config);
     63  dtx_encoder.configure(dtx_config);
     64 
     65  let timestamp_us = 0;
     66  let data_duration_s = total_duration_s / data_count;
     67  let data_length = data_duration_s * config.sampleRate;
     68  for (let i = 0; i < data_count; i++) {
     69    let data;
     70 
     71    if (i == 0 || i == (data_count - 1)) {
     72      // Send real data for the first and last 100ms.
     73      data = make_audio_data(
     74          timestamp_us, config.numberOfChannels, config.sampleRate,
     75          data_length);
     76 
     77    } else {
     78      // Send silence for the rest of the 10s.
     79      data = make_silent_audio_data(
     80          timestamp_us, config.numberOfChannels, config.sampleRate,
     81          data_length);
     82    }
     83 
     84    normal_encoder.encode(data);
     85    dtx_encoder.encode(data);
     86    data.close();
     87 
     88    timestamp_us += data_duration_s * 1_000_000;
     89  }
     90 
     91  await Promise.all([normal_encoder.flush(), dtx_encoder.flush()])
     92 
     93  normal_encoder.close();
     94  dtx_encoder.close();
     95 
     96  // We expect a significant reduction in the number of packets, over ~10s of silence.
     97  assert_less_than(dtx_outputs.length, (normal_outputs.length / 2));
     98 }, 'Test the Opus DTX flag works.');
     99 
    100 
    101 // The Opus bitrateMode enum chooses whether we use a constant or variable bitrate.
    102 // This test ensures that VBR/CBR is respected properly by encoding almost 10s of
    103 // silence and comparing the size of the encoded variable or constant bitrates.
    104 promise_test(async t => {
    105  let sample_rate = 48000;
    106  let total_duration_s = 10;
    107  let data_count = 100;
    108  let vbr_outputs = [];
    109  let cbr_outputs = [];
    110 
    111  let cbr_encoder = new AudioEncoder({
    112    error: e => {
    113      assert_unreached('error: ' + e);
    114    },
    115    output: chunk => {
    116      cbr_outputs.push(chunk);
    117    }
    118  });
    119 
    120  let vbr_encoder = new AudioEncoder({
    121    error: e => {
    122      assert_unreached('error: ' + e);
    123    },
    124    output: chunk => {
    125      vbr_outputs.push(chunk);
    126    }
    127  });
    128 
    129  let config = {
    130    codec: 'opus',
    131    sampleRate: sample_rate,
    132    numberOfChannels: 2,
    133    bitrate: 256000,  // 256kbit
    134  };
    135 
    136  let cbr_config = { ...config, bitrateMode: "constant" };
    137  let vbr_config = { ...config, bitrateMode: "variable" };
    138 
    139  let cbr_config_support = await AudioEncoder.isConfigSupported(cbr_config);
    140  assert_implements_optional(cbr_config_support.supported, "Opus CBR not supported");
    141 
    142  let vbr_config_support = await AudioEncoder.isConfigSupported(vbr_config);
    143  assert_implements_optional(vbr_config_support.supported, "Opus VBR not supported");
    144 
    145  // Configure one encoder with VBR and one CBR.
    146  cbr_encoder.configure(cbr_config);
    147  vbr_encoder.configure(vbr_config);
    148 
    149  let timestamp_us = 0;
    150  let data_duration_s = total_duration_s / data_count;
    151  let data_length = data_duration_s * config.sampleRate;
    152  for (let i = 0; i < data_count; i++) {
    153    let data;
    154 
    155    if (i == 0 || i == (data_count - 1)) {
    156      // Send real data for the first and last 100ms.
    157      data = make_audio_data(
    158        timestamp_us, config.numberOfChannels, config.sampleRate,
    159        data_length);
    160 
    161    } else {
    162      // Send silence for the rest of the 10s.
    163      data = make_silent_audio_data(
    164        timestamp_us, config.numberOfChannels, config.sampleRate,
    165        data_length);
    166    }
    167 
    168    vbr_encoder.encode(data);
    169    cbr_encoder.encode(data);
    170    data.close();
    171 
    172    timestamp_us += data_duration_s * 1_000_000;
    173  }
    174 
    175  await Promise.all([cbr_encoder.flush(), vbr_encoder.flush()])
    176 
    177  cbr_encoder.close();
    178  vbr_encoder.close();
    179 
    180  let vbr_total_bytes = 0;
    181  vbr_outputs.forEach(chunk => vbr_total_bytes += chunk.byteLength)
    182 
    183  let cbr_total_bytes = 0;
    184  cbr_outputs.forEach(chunk => cbr_total_bytes += chunk.byteLength)
    185 
    186  // We expect a significant reduction in the size of the packets, over ~10s of silence.
    187  assert_less_than(vbr_total_bytes, (cbr_total_bytes / 2));
    188 }, 'Test the Opus bitrateMode flag works.');
    189 
    190 // Opus can handle configs with a frameDuration that is any multiple of 2.5ms,
    191 // up to 120ms. This test ensures that we can handle multiples and encoders
    192 // return encoded packets with the correct frameDuration.
    193 promise_test(async t => {
    194  let sample_rate = 48000;
    195  let total_duration_s = 10;
    196  let data_count = 100;
    197 
    198  let valid_frame_durations = [7500, 30000, 120000];
    199  for(const duration of valid_frame_durations) {
    200    let outputs = [];
    201    // We use an AudioDecoder to verify that the EncodedAudioChunks from
    202    // encoder.encode() can be decoded.
    203    let decoder = new AudioDecoder({
    204      error: e => {
    205        assert_unreached(`Duration ${duration} received this error: ` + e);
    206      },
    207      output: chunk => {
    208        chunk.close();
    209      }
    210    });
    211    decoder.configure({
    212      codec: "opus",
    213      numberOfChannels: 2,
    214      sampleRate: sample_rate
    215    });
    216 
    217    let encoder = new AudioEncoder({
    218      error: e => {
    219        assert_unreached(`Duration ${duration} received this error: ` + e);
    220      },
    221      output: chunk => {
    222        outputs.push(chunk);
    223        decoder.decode(chunk);
    224      }
    225    });
    226    let config = {
    227      codec: 'opus',
    228      sampleRate: sample_rate,
    229      numberOfChannels: 2,
    230      bitrate: 256000,  // 256kbit
    231      bitrateMode: "constant",
    232      opus: {
    233        frameDuration: duration,
    234      },
    235    };
    236 
    237    encoder.configure(config);
    238 
    239    let timestamp_us = 0;
    240    let data_duration_s = total_duration_s / data_count;
    241    let data_length = data_duration_s * config.sampleRate;
    242    for (let i = 0; i < data_count; i++) {
    243      let data = make_audio_data(
    244          timestamp_us, config.numberOfChannels, config.sampleRate,
    245          data_length);
    246      encoder.encode(data);
    247      data.close();
    248 
    249      timestamp_us += data_duration_s * 1_000_000;
    250    }
    251 
    252    await encoder.flush()
    253    await decoder.flush();
    254 
    255    encoder.close();
    256    decoder.close();
    257 
    258    assert_true(outputs.every((chunk) => chunk.duration === duration));
    259 
    260  }
    261 }, 'Test Opus valid frame durations.');
    262 
    263 
    264 
    265 // The AAC bitrateMode enum chooses whether we use a constant or variable bitrate.
    266 // This test exercises the VBR/CBR paths. Some platforms don't support VBR for AAC,
    267 // and still emit a constant bitrate.
    268 promise_test(async t => {
    269  let sample_rate = 48000;
    270  let total_duration_s = 10;
    271  let data_count = 100;
    272  let vbr_outputs = [];
    273  let cbr_outputs = [];
    274 
    275  let cbr_encoder = new AudioEncoder({
    276    error: e => {
    277      assert_unreached('error: ' + e);
    278    },
    279    output: chunk => {
    280      cbr_outputs.push(chunk);
    281    }
    282  });
    283 
    284  let vbr_encoder = new AudioEncoder({
    285    error: e => {
    286      assert_unreached('error: ' + e);
    287    },
    288    output: chunk => {
    289      vbr_outputs.push(chunk);
    290    }
    291  });
    292 
    293  let config = {
    294    codec: 'mp4a.40.2',
    295    sampleRate: sample_rate,
    296    numberOfChannels: 2,
    297    bitrate: 192000,  // 256kbit
    298  };
    299 
    300  let cbr_config = { ...config, bitrateMode: "constant" };
    301  let vbr_config = { ...config, bitrateMode: "variable" };
    302 
    303  let cbr_config_support = await AudioEncoder.isConfigSupported(cbr_config);
    304  assert_implements_optional(cbr_config_support.supported, "AAC CBR not supported");
    305 
    306  let vbr_config_support = await AudioEncoder.isConfigSupported(vbr_config);
    307  assert_implements_optional(vbr_config_support.supported, "AAC VBR not supported");
    308 
    309  // Configure one encoder with VBR and one CBR.
    310  cbr_encoder.configure(cbr_config);
    311  vbr_encoder.configure(vbr_config);
    312 
    313  let timestamp_us = 0;
    314  let data_duration_s = total_duration_s / data_count;
    315  let data_length = data_duration_s * config.sampleRate;
    316  for (let i = 0; i < data_count; i++) {
    317    let data;
    318 
    319    if (i == 0 || i == (data_count - 1)) {
    320      // Send real data for the first and last 100ms.
    321      data = make_audio_data(
    322        timestamp_us, config.numberOfChannels, config.sampleRate,
    323        data_length);
    324 
    325    } else {
    326      // Send silence for the rest of the 10s.
    327      data = make_silent_audio_data(
    328        timestamp_us, config.numberOfChannels, config.sampleRate,
    329        data_length);
    330    }
    331 
    332    vbr_encoder.encode(data);
    333    cbr_encoder.encode(data);
    334    data.close();
    335 
    336    timestamp_us += data_duration_s * 1_000_000;
    337  }
    338 
    339  await Promise.all([cbr_encoder.flush(), vbr_encoder.flush()])
    340 
    341  cbr_encoder.close();
    342  vbr_encoder.close();
    343 
    344  let vbr_total_bytes = 0;
    345  vbr_outputs.forEach(chunk => vbr_total_bytes += chunk.byteLength)
    346 
    347  let cbr_total_bytes = 0;
    348  cbr_outputs.forEach(chunk => cbr_total_bytes += chunk.byteLength)
    349 
    350  // We'd like to confirm that the encoded size using VBR is less than CBR, but
    351  // platforms without VBR support will silently revert to CBR (which is
    352  // technically a subset of VBR).
    353  assert_less_than_equal(vbr_total_bytes, cbr_total_bytes);
    354 }, 'Test the AAC bitrateMode flag works.');