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>