tor-browser

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

audio-data.any.js (14327B)


      1 // META: global=window,dedicatedworker
      2 // META: script=/common/media.js
      3 // META: script=/webcodecs/utils.js
      4 
      5 var defaultInit = {
      6  timestamp: 1234,
      7  channels: 2,
      8  sampleRate: 8000,
      9  frames: 100,
     10 };
     11 
     12 function createDefaultAudioData() {
     13  return make_audio_data(
     14    defaultInit.timestamp,
     15    defaultInit.channels,
     16    defaultInit.sampleRate,
     17    defaultInit.frames
     18  );
     19 }
     20 
     21 test(t => {
     22  let local_data = new Float32Array(defaultInit.channels * defaultInit.frames);
     23 
     24  let audio_data_init = {
     25    timestamp: defaultInit.timestamp,
     26    data: local_data,
     27    numberOfFrames: defaultInit.frames,
     28    numberOfChannels: defaultInit.channels,
     29    sampleRate: defaultInit.sampleRate,
     30    format: 'f32-planar',
     31  }
     32 
     33  let data = new AudioData(audio_data_init);
     34 
     35  assert_equals(data.timestamp, defaultInit.timestamp, 'timestamp');
     36  assert_equals(data.numberOfFrames, defaultInit.frames, 'frames');
     37  assert_equals(data.numberOfChannels, defaultInit.channels, 'channels');
     38  assert_equals(data.sampleRate, defaultInit.sampleRate, 'sampleRate');
     39  assert_equals(
     40      data.duration, defaultInit.frames / defaultInit.sampleRate * 1_000_000,
     41      'duration');
     42  assert_equals(data.format, 'f32-planar', 'format');
     43 
     44  // Create an Int16 array of the right length.
     45  let small_data = new Int16Array(defaultInit.channels * defaultInit.frames);
     46 
     47  let wrong_format_init = {...audio_data_init};
     48  wrong_format_init.data = small_data;
     49 
     50  // Creating `f32-planar` AudioData from Int16 from should throw.
     51  assert_throws_js(TypeError, () => {
     52    let data = new AudioData(wrong_format_init);
     53  }, `AudioDataInit.data needs to be big enough`);
     54 
     55  var members = [
     56    'timestamp',
     57    'data',
     58    'numberOfFrames',
     59    'numberOfChannels',
     60    'sampleRate',
     61    'format',
     62  ];
     63 
     64  for (const member of members) {
     65    let incomplete_init = {...audio_data_init};
     66    delete incomplete_init[member];
     67 
     68    assert_throws_js(
     69        TypeError, () => {let data = new AudioData(incomplete_init)},
     70        'AudioData requires \'' + member + '\'');
     71  }
     72 
     73  let invalid_init = {...audio_data_init};
     74  invalid_init.numberOfFrames = 0
     75 
     76  assert_throws_js(
     77      TypeError, () => {let data = new AudioData(invalid_init)},
     78      'AudioData requires numberOfFrames > 0');
     79 
     80  invalid_init = {...audio_data_init};
     81  invalid_init.numberOfChannels = 0
     82 
     83  assert_throws_js(
     84      TypeError, () => {let data = new AudioData(invalid_init)},
     85      'AudioData requires numberOfChannels > 0');
     86 
     87 }, 'Verify AudioData constructors');
     88 
     89 test(t => {
     90  let data = createDefaultAudioData();
     91  data.close();
     92  assert_equals(data.sampleRate, 0);
     93  assert_equals(data.numberOfFrames, 0);
     94  assert_equals(data.numberOfChannels, 0);
     95  assert_equals(data.format, null);
     96 }, 'AudioData close');
     97 
     98 test(t => {
     99  let data = createDefaultAudioData();
    100 
    101  let clone = data.clone();
    102 
    103  // Verify the parameters match.
    104  assert_equals(data.timestamp, clone.timestamp, 'timestamp');
    105  assert_equals(data.numberOfFrames, clone.numberOfFrames, 'frames');
    106  assert_equals(data.numberOfChannels, clone.numberOfChannels, 'channels');
    107  assert_equals(data.sampleRate, clone.sampleRate, 'sampleRate');
    108  assert_equals(data.format, clone.format, 'format');
    109 
    110  const data_copyDest = new Float32Array(defaultInit.frames);
    111  const clone_copyDest = new Float32Array(defaultInit.frames);
    112 
    113  // Verify the data matches.
    114  for (var channel = 0; channel < defaultInit.channels; channel++) {
    115    data.copyTo(data_copyDest, {planeIndex: channel});
    116    clone.copyTo(clone_copyDest, {planeIndex: channel});
    117 
    118    assert_array_equals(
    119        data_copyDest, clone_copyDest, 'Cloned data ch=' + channel);
    120  }
    121 
    122  // Verify closing the original data doesn't close the clone.
    123  data.close();
    124  assert_equals(data.numberOfFrames, 0, 'data.buffer (closed)');
    125  assert_not_equals(clone.numberOfFrames, 0, 'clone.buffer (not closed)');
    126 
    127  clone.close();
    128  assert_equals(clone.numberOfFrames, 0, 'clone.buffer (closed)');
    129 
    130  // Verify closing a closed AudioData does not throw.
    131  data.close();
    132 }, 'Verify closing and cloning AudioData');
    133 
    134 test(t => {
    135  let data = make_audio_data(
    136      -10, defaultInit.channels, defaultInit.sampleRate, defaultInit.frames);
    137  assert_equals(data.timestamp, -10, 'timestamp');
    138  data.close();
    139 }, 'Test we can construct AudioData with a negative timestamp.');
    140 
    141 test(t => {
    142  var data = new Float32Array([0]);
    143  let audio_data_init = {
    144    timestamp: 0,
    145    data: data,
    146    numberOfFrames: 1,
    147    numberOfChannels: 1,
    148    sampleRate: 44100,
    149    format: 'f32',
    150  };
    151  let audioData = new AudioData(audio_data_init);
    152  assert_not_equals(data.length, 0, "Input data is copied when constructing an AudioData");
    153 }, 'Test input array is copied on construction');
    154 
    155 test(t => {
    156  let audio_data_init = {
    157    timestamp: 0,
    158    data: new Float32Array([1,2,3,4,5,6,7,8]),
    159    numberOfFrames: 4,
    160    numberOfChannels: 2,
    161    sampleRate: 44100,
    162    format: 'f32',
    163  };
    164  let audioData = new AudioData(audio_data_init);
    165  let dest = new Float32Array(8);
    166  assert_throws_js(
    167      RangeError, () => audioData.copyTo(dest, {planeIndex: 1}),
    168      'copyTo from interleaved data with non-zero planeIndex throws');
    169  audioData.close();
    170 }, 'Test that copyTo throws if copying from interleaved with a non-zero planeIndex');
    171 
    172 // Indices to pick a particular specific value in a specific sample-format
    173 const MIN = 0; // Minimum sample value, max amplitude
    174 const MAX = 1; // Maximum sample value, max amplitude
    175 const HALF = 2; // Half the maximum sample value, positive
    176 const NEGATIVE_HALF = 3; // Half the maximum sample value, negative
    177 const BIAS = 4; // Center of the range, silence
    178 const DISCRETE_STEPS = 5; // Number of different value for a type.
    179 
    180 function pow2(p) {
    181  return 2 ** p;
    182 }
    183 // Rounding operations for conversion, currently always floor (round towards
    184 // zero).
    185 let r = Math.floor.bind(this);
    186 
    187 const TEST_VALUES = {
    188  u8: [0, 255, 191, 64, 128, 256],
    189  s16: [
    190    -pow2(15),
    191    pow2(15) - 1,
    192    r((pow2(15) - 1) / 2),
    193    r(-pow2(15) / 2),
    194    0,
    195    pow2(16),
    196  ],
    197  s32: [
    198    -pow2(31),
    199    pow2(31) - 1,
    200    r((pow2(31) - 1) / 2),
    201    r(-pow2(31) / 2),
    202    0,
    203    pow2(32),
    204  ],
    205  f32: [-1.0, 1.0, 0.5, -0.5, 0, pow2(24)],
    206 };
    207 
    208 const TEST_TEMPLATE = {
    209  channels: 2,
    210  frames: 5,
    211  // Each test is run with an element of the cartesian product of a pair of
    212  // elements of the set of type in [u8, s16, s32, f32]
    213  // For each test, this template is copied and the values replaced with the
    214  // appropriate values for this particular type.
    215  // For each test, copy this template and replace the number by the appropriate
    216  // number for this type
    217  testInput: [MIN, BIAS, MAX, MIN, HALF, NEGATIVE_HALF, BIAS, MAX, BIAS, BIAS],
    218  testInterleavedResult: [MIN, NEGATIVE_HALF, BIAS, BIAS, MAX, MAX, MIN, BIAS, HALF, BIAS],
    219  testVectorInterleavedResult: [
    220    [MIN, MAX, HALF, BIAS, BIAS],
    221    [BIAS, MIN, NEGATIVE_HALF, MAX, BIAS],
    222  ],
    223  testVectorPlanarResult: [
    224    [MIN, BIAS, MAX, MIN, HALF],
    225    [NEGATIVE_HALF, BIAS, MAX, BIAS, BIAS],
    226  ],
    227 };
    228 
    229 function isInteger(type) {
    230  switch (type) {
    231    case "u8":
    232    case "s16":
    233    case "s32":
    234      return true;
    235    case "f32":
    236      return false;
    237    default:
    238      throw "invalid type";
    239  }
    240 }
    241 
    242 // This is the complex part: carefully select an acceptable error value
    243 // depending on various factors: expected destination value, source type,
    244 // destination type. This is designed to be strict but reachable with simple
    245 // sample format transformation (no dithering or complex transformation).
    246 function epsilon(expectedDestValue, sourceType, destType) {
    247  // Strict comparison if not converting
    248  if (sourceType == destType) {
    249    return 0.0;
    250  }
    251  // There are three cases in which the maximum value cannot be reached, when
    252  // converting from a smaller integer sample type to a wider integer sample
    253  // type:
    254  // - u8 to s16
    255  // - u8 to s32
    256  // - s16 to u32
    257  if (expectedDestValue == TEST_VALUES[destType][MAX]) {
    258    if (sourceType == "u8" && destType == "s16") {
    259      return expectedDestValue - 32511; // INT16_MAX - 2 << 7 + 1
    260    } else if (sourceType == "u8" && destType == "s32") {
    261      return expectedDestValue - 2130706432; // INT32_MAX - (2 << 23) + 1
    262    } else if (sourceType == "s16" && destType == "s32") {
    263      return expectedDestValue - 2147418112; // INT32_MAX - UINT16_MAX
    264    }
    265  }
    266  // Min and bias value are correctly mapped for all integer sample-types
    267  if (isInteger(sourceType) && isInteger(destType)) {
    268    if (expectedDestValue == TEST_VALUES[destType][MIN] ||
    269        expectedDestValue == TEST_VALUES[destType][BIAS]) {
    270      return 0.0;
    271    }
    272  }
    273  // If converting from float32 to u8 or s16, allow choosing the rounding
    274  // direction. s32 has higher resolution than f32 in [-1.0,1.0] (24 bits of
    275  // mantissa)
    276  if (!isInteger(sourceType) && isInteger(destType) && destType != "s32") {
    277    return 1.0;
    278  }
    279  // In all other cases, expect an accuracy that depends on the source type and
    280  // the destination type.
    281  // The resolution of the source type.
    282  var sourceResolution = TEST_VALUES[sourceType][DISCRETE_STEPS];
    283  // The resolution of the destination type.
    284  var destResolution = TEST_VALUES[destType][DISCRETE_STEPS];
    285  // Computations should be exact if going from high resolution to low resolution.
    286  if (sourceResolution > destResolution) {
    287    return 0.0;
    288  } else {
    289    // Something that approaches the precision imbalance
    290    return destResolution / sourceResolution;
    291  }
    292 }
    293 
    294 // Fill the template above with the values for a particular type
    295 function get_type_values(type) {
    296  let cloned = structuredClone(TEST_TEMPLATE);
    297  cloned.testInput = Array.from(
    298    cloned.testInput,
    299    idx => TEST_VALUES[type][idx]
    300  );
    301  cloned.testInterleavedResult = Array.from(
    302    cloned.testInterleavedResult,
    303    idx => TEST_VALUES[type][idx]
    304  );
    305  cloned.testVectorInterleavedResult = Array.from(
    306    cloned.testVectorInterleavedResult,
    307    c => {
    308      return Array.from(c, idx => {
    309        return TEST_VALUES[type][idx];
    310      });
    311    }
    312  );
    313  cloned.testVectorPlanarResult = Array.from(
    314    cloned.testVectorPlanarResult,
    315    c => {
    316      return Array.from(c, idx => {
    317        return TEST_VALUES[type][idx];
    318      });
    319    }
    320  );
    321  return cloned;
    322 }
    323 
    324 function typeToArrayType(type) {
    325  switch (type) {
    326    case "u8":
    327      return Uint8Array;
    328    case "s16":
    329      return Int16Array;
    330    case "s32":
    331      return Int32Array;
    332    case "f32":
    333      return Float32Array;
    334    default:
    335      throw "Unexpected";
    336  }
    337 }
    338 
    339 function arrayTypeToType(array) {
    340  switch (array.constructor) {
    341    case Uint8Array:
    342      return "u8";
    343    case Int16Array:
    344      return "s16";
    345    case Int32Array:
    346      return "s32";
    347    case Float32Array:
    348      return "f32";
    349    default:
    350      throw "Unexpected";
    351  }
    352 }
    353 
    354 function check_array_equality(values, expected, sourceType, message, assert_func) {
    355  if (values.length != expected.length) {
    356    throw "Array not of the same length";
    357  }
    358  for (var i = 0; i < values.length; i++) {
    359    var eps = epsilon(expected[i], sourceType, arrayTypeToType(values));
    360    assert_func(
    361      Math.abs(expected[i] - values[i]) <= eps,
    362      `Got ${values[i]} but expected result ${
    363        expected[i]
    364      } at index ${i} when converting from ${sourceType} to ${arrayTypeToType(
    365        values
    366      )}, epsilon ${eps}`
    367    );
    368  }
    369  assert_func(
    370    true,
    371    `${values} is equal to ${expected} when converting from ${sourceType} to ${arrayTypeToType(
    372      values
    373    )}`
    374  );
    375 }
    376 
    377 function conversionTest(sourceType, destinationType) {
    378  test(function (t) {
    379    var test = get_type_values(sourceType);
    380    var result = get_type_values(destinationType);
    381 
    382    var sourceArrayCtor = typeToArrayType(sourceType);
    383    var destArrayCtor = typeToArrayType(destinationType);
    384 
    385    let data = new AudioData({
    386      timestamp: defaultInit.timestamp,
    387      data: new sourceArrayCtor(test.testInput),
    388      numberOfFrames: test.frames,
    389      numberOfChannels: test.channels,
    390      sampleRate: defaultInit.sampleRate,
    391      format: sourceType,
    392    });
    393 
    394    // All conversions can be supported, but conversion of any type to f32-planar
    395    // MUST be supported.
    396    var assert_func = destinationType == "f32" ? assert_true : assert_implements_optional;
    397    let dest = new destArrayCtor(data.numberOfFrames);
    398    data.copyTo(dest, { planeIndex: 0, format: destinationType + "-planar" });
    399    check_array_equality(
    400      dest,
    401      result.testVectorInterleavedResult[0],
    402      sourceType,
    403      "interleaved channel 0",
    404      assert_func
    405    );
    406    data.copyTo(dest, { planeIndex: 1, format: destinationType + "-planar" });
    407    check_array_equality(
    408      dest,
    409      result.testVectorInterleavedResult[1],
    410      sourceType,
    411      "interleaved channel 0",
    412      assert_func
    413    );
    414    let destInterleaved = new destArrayCtor(data.numberOfFrames * data.numberOfChannels);
    415    data.copyTo(destInterleaved, { planeIndex: 0, format: destinationType });
    416    check_array_equality(
    417      destInterleaved,
    418      result.testInput,
    419      sourceType,
    420      "copyTo from interleaved to interleaved (conversion only)",
    421      assert_implements_optional
    422    );
    423 
    424    data = new AudioData({
    425      timestamp: defaultInit.timestamp,
    426      data: new sourceArrayCtor(test.testInput),
    427      numberOfFrames: test.frames,
    428      numberOfChannels: test.channels,
    429      sampleRate: defaultInit.sampleRate,
    430      format: sourceType + "-planar",
    431    });
    432 
    433    data.copyTo(dest, { planeIndex: 0, format: destinationType + "-planar" });
    434    check_array_equality(
    435      dest,
    436      result.testVectorPlanarResult[0],
    437      sourceType,
    438      "planar channel 0",
    439      assert_func,
    440    );
    441    data.copyTo(dest, { planeIndex: 1, format: destinationType + "-planar" });
    442    check_array_equality(
    443      dest,
    444      result.testVectorPlanarResult[1],
    445      sourceType,
    446      "planar channel 1",
    447      assert_func
    448    );
    449    // Copy to interleaved from planar: all channels are copied
    450    data.copyTo(destInterleaved, {planeIndex: 0, format: destinationType});
    451    check_array_equality(
    452      destInterleaved,
    453      result.testInterleavedResult,
    454      sourceType,
    455      "planar to interleaved",
    456      assert_func
    457    );
    458  }, `Test conversion of ${sourceType} to ${destinationType}`);
    459 }
    460 
    461 const TYPES = ["u8", "s16", "s32", "f32"];
    462 TYPES.forEach(sourceType => {
    463   TYPES.forEach(destinationType => {
    464    conversionTest(sourceType, destinationType);
    465  });
    466 });