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.');