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 });