1
    2
    3
    4
    5
    6
    7
    8
    9
   10
   11
   12
   13
   14
   15
   16
   17
   18
   19
   20
   21
   22
   23
   24
   25
   26
   27
   28
   29
   30
   31
   32
   33
   34
   35
   36
   37
   38
   39
   40
   41
   42
   43
   44
   45
   46
   47
   48
   49
   50
   51
   52
   53
   54
   55
   56
   57
   58
   59
   60
   61
   62
   63
   64
   65
   66
   67
   68
   69
   70
   71
   72
   73
   74
   75
   76
   77
   78
   79
   80
   81
   82
   83
   84
   85
   86
   87
   88
   89
   90
   91
   92
   93
   94
   95
   96
   97
   98
   99
  100
  101
  102
  103
  104
  105
  106
  107
  108
  109
  110
  111
  112
  113
  114
  115
  116
  117
  118
  119
  120
  121
  122
  123
  124
  125
  126
  127
  128
  129
  130
  131
  132
  133
  134
  135
  136
  137
  138
  139
  140
  141
  142
  143
  144
  145
  146
  147
  148
  149
  150
  151
  152
  153
  154
  155
  156
  157
  158
  159
  160
  161
  162
  163
  164
  165
  166
  167
  168
  169
  170
  171
  172
  173
  174
  175
  176
  177
  178
  179
  180
  181
  182
  183
  184
  185

content / test / data / gpu / webcodecs / audio-encode-decode.html [blame]

<!DOCTYPE html>
<!--
Check that AudioDecoder can decode output of AudioEncoder
-->
<title>Encode test</title>
<script src="webcodecs_common.js"></script>
<script type="text/javascript">
'use strict';

function make_audio_data(timestamp, channels, sampleRate, frames) {
  let data = new Float32Array(frames*channels);

  // This generates samples in a planar format.
  for (var channel = 0; channel < channels; channel++) {
    let hz = 100 + channel * 50; // sound frequency
    let base_index = channel * frames;
    for (var i = 0; i < frames; i++) {
      let t = (i / sampleRate) * hz * (Math.PI * 2);
      data[base_index + i] = Math.sin(t);
    }
  }

  return new AudioData({
    timestamp: timestamp,
    data: data,
    numberOfChannels: channels,
    numberOfFrames: frames,
    sampleRate: sampleRate,
    format: "f32-planar",
  });
}

async function main(args) {
  let errors = 0;
  let output_count = 0;

  let config = {
    codec: args.codec,
    sampleRate: args.sample_rate,
    numberOfChannels: args.channels,
    bitrate: 96000
  };

  if (args.aac_format) {
    config.aac = {
      format : args.aac_format
    };
  }
  let decoder_config = null;

  let supported = false;
  try {
    supported = (await AudioEncoder.isConfigSupported(config)).supported;
  } catch (e) {}
  if (!supported) {
    TEST.skip('Unsupported codec: ' + args.codec);
    return;
  }

  let total_duration_s = 1.0;
  let data_count = 20; // each chunk is 500 ms
  let outputs = [];
  let init = {
    error: e => {
      errors++;
      TEST.log(e);
    },
    output: (chunk, md) => {
      if (md.decoderConfig) {
        decoder_config = md.decoderConfig;
      }
      outputs.push(chunk);
    }
  };

  // Make some sin() waves and encode them
  let encoder = new AudioEncoder(init);
  encoder.configure(config);

  let timestamp_us = 0;
  let data_duration_s = total_duration_s / data_count;
  let data_length = data_duration_s * config.sampleRate;
  for (let i = 0; i < data_count; i++) {
    let data = make_audio_data(timestamp_us, config.numberOfChannels,
      config.sampleRate, data_length);
    encoder.encode(data);
    data.close();
    timestamp_us += data_duration_s * 1_000_000;
    if (i % 10 == 0) {
      // Inserting flushes in the middle, to see how the encoder handles
      // more inputs coming after flush()
      await encoder.flush();
    }

  }
  await encoder.flush();
  encoder.close();

  // Maximum amount of padding from supported encoders. Applied twice, since the
  // flush above can also introduce padding.
  timestamp_us += 2 * (2112 / config.sampleRate) * 1_000_000;

  TEST.assert(decoder_config != null, "No decoder config");
  if (args.aac_format == "adts") {
    TEST.assert(decoder_config.description == null, "ADTS should carry desc");
  } else if (args.aac_format == "aac") {
    TEST.assert(decoder_config.description != null, "AAC should carry desc");
    TEST.assert(decoder_config.description.byteLength > 1,
                "AAC desc is too short");
  }
  TEST.assert(outputs.length > 0, "no outputs");
  TEST.assert(outputs[0].timestamp == 0, "first chunk timestamp non zero");

  let total_encoded_duration = 0
  for (let chunk of outputs) {
    TEST.assert(chunk.byteLength > 0,  "chunk is empty");
    TEST.assert(timestamp_us >= chunk.timestamp,
        `chunk timestamp is too small. ${timestamp_us} vs ${chunk.timestamp}`);
    TEST.assert(chunk.duration >= 0, "chunk duration is zero");
    total_encoded_duration += chunk.duration;
    let buf = new ArrayBuffer(chunk.byteLength);
    chunk.copyTo(buf);
    if (args.aac_format == "adts") {
      let adts_header = new DataView(buf).getUint8(0);
      TEST.assert(adts_header == 0xff,  "Incorrect ADTS header");
    }
  }

  // The total duration might be padded with silence.
  TEST.assert(
      total_encoded_duration >= total_duration_s * 1_000_000,
      `Unexpected encoded duration: ${total_encoded_duration} us` );


  // Decode output and check that the output still makes sense
  let audio_buffers = [];
  let decoder_init = {
    error: e => {
      errors++;
      TEST.log(e);
    },
    output: (audio_data) => {
      let buffer = new AudioBuffer({
            length: audio_data.numberOfFrames,
            numberOfChannels: audio_data.numberOfChannels,
            sampleRate: audio_data.sampleRate
      });
      for (let i = 0; i < audio_data.numberOfChannels; i++) {
        audio_data.copyTo(buffer.getChannelData(i), {
          planeIndex : i,
          frameOffset : 0,
          frameCount : audio_data.numberOfFrames,
          format : "f32-planar"
        });
      }
      audio_buffers.push(buffer);
      audio_data.close();
    }
  };

  let decoder = new AudioDecoder(decoder_init)
  decoder.configure(decoder_config);
  for (let chunk of outputs) {
    decoder.decode(chunk);
  }
  await decoder.flush();

  let total_decoded_duration_s = 0.0;
  for (let buffer of audio_buffers) {
    total_decoded_duration_s += buffer.duration;
    TEST.assert(buffer.numberOfChannels == config.numberOfChannels,
                "unexpected number of channels");
  }

  TEST.assert(errors == 0, "errors: " + errors);

  // The total duration might be padded with silence, but no too much.
  TEST.assert(
    total_decoded_duration_s >= total_duration_s,
      `Decoded duration is too short: ${total_decoded_duration_s} s`);
      TEST.assert(
    total_decoded_duration_s < 2 * total_duration_s,
      `Decoded duration is too long: ${total_decoded_duration_s} s`);
}
</script>