fune/dom/media/webaudio/test/test_mediaDecoding.html
Stanca Serban 3274be9ff2 Backed out 13 changesets (bug 1860492) for causing multiple failures.
Backed out changeset 1b6bef229ce4 (bug 1860492)
Backed out changeset 22a3235fe2dc (bug 1860492)
Backed out changeset a5b2e4d12e44 (bug 1860492)
Backed out changeset cbcb811ca382 (bug 1860492)
Backed out changeset d6a999866f19 (bug 1860492)
Backed out changeset 40cee82fa090 (bug 1860492)
Backed out changeset eeec1917ab49 (bug 1860492)
Backed out changeset cf1230802d5c (bug 1860492)
Backed out changeset b34aacf0e966 (bug 1860492)
Backed out changeset 45d137c795f1 (bug 1860492)
Backed out changeset fb667be5932d (bug 1860492)
Backed out changeset a6d22d73c8a5 (bug 1860492)
Backed out changeset c8b6c53698f5 (bug 1860492)
2024-03-22 18:18:12 +02:00

436 lines
14 KiB
HTML

<!DOCTYPE html>
<html>
<head>
<title>Test the decodeAudioData API and Resampling</title>
<script src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body>
<pre id="test">
<script src="webaudio.js" type="text/javascript"></script>
<script type="text/javascript">
// These routines have been copied verbatim from WebKit, and are used in order
// to convert a memory buffer into a wave buffer.
function writeString(s, a, offset) {
for (var i = 0; i < s.length; ++i) {
a[offset + i] = s.charCodeAt(i);
}
}
function writeInt16(n, a, offset) {
n = Math.floor(n);
var b1 = n & 255;
var b2 = (n >> 8) & 255;
a[offset + 0] = b1;
a[offset + 1] = b2;
}
function writeInt32(n, a, offset) {
n = Math.floor(n);
var b1 = n & 255;
var b2 = (n >> 8) & 255;
var b3 = (n >> 16) & 255;
var b4 = (n >> 24) & 255;
a[offset + 0] = b1;
a[offset + 1] = b2;
a[offset + 2] = b3;
a[offset + 3] = b4;
}
function writeAudioBuffer(audioBuffer, a, offset) {
var n = audioBuffer.length;
var channels = audioBuffer.numberOfChannels;
for (var i = 0; i < n; ++i) {
for (var k = 0; k < channels; ++k) {
var buffer = audioBuffer.getChannelData(k);
var sample = buffer[i] * 32768.0;
// Clip samples to the limitations of 16-bit.
// If we don't do this then we'll get nasty wrap-around distortion.
if (sample < -32768)
sample = -32768;
if (sample > 32767)
sample = 32767;
writeInt16(sample, a, offset);
offset += 2;
}
}
}
function createWaveFileData(audioBuffer) {
var frameLength = audioBuffer.length;
var numberOfChannels = audioBuffer.numberOfChannels;
var sampleRate = audioBuffer.sampleRate;
var bitsPerSample = 16;
var byteRate = sampleRate * numberOfChannels * bitsPerSample / 8;
var blockAlign = numberOfChannels * bitsPerSample / 8;
var wavDataByteLength = frameLength * numberOfChannels * 2; // 16-bit audio
var headerByteLength = 44;
var totalLength = headerByteLength + wavDataByteLength;
var waveFileData = new Uint8Array(totalLength);
var subChunk1Size = 16; // for linear PCM
var subChunk2Size = wavDataByteLength;
var chunkSize = 4 + (8 + subChunk1Size) + (8 + subChunk2Size);
writeString("RIFF", waveFileData, 0);
writeInt32(chunkSize, waveFileData, 4);
writeString("WAVE", waveFileData, 8);
writeString("fmt ", waveFileData, 12);
writeInt32(subChunk1Size, waveFileData, 16); // SubChunk1Size (4)
writeInt16(1, waveFileData, 20); // AudioFormat (2)
writeInt16(numberOfChannels, waveFileData, 22); // NumChannels (2)
writeInt32(sampleRate, waveFileData, 24); // SampleRate (4)
writeInt32(byteRate, waveFileData, 28); // ByteRate (4)
writeInt16(blockAlign, waveFileData, 32); // BlockAlign (2)
writeInt32(bitsPerSample, waveFileData, 34); // BitsPerSample (4)
writeString("data", waveFileData, 36);
writeInt32(subChunk2Size, waveFileData, 40); // SubChunk2Size (4)
// Write actual audio data starting at offset 44.
writeAudioBuffer(audioBuffer, waveFileData, 44);
return waveFileData;
}
</script>
<script class="testbody" type="text/javascript">
SimpleTest.waitForExplicitFinish();
// fuzzTolerance and fuzzToleranceMobile are used to determine fuzziness
// thresholds. They're needed to make sure that we can deal with neglibible
// differences in the binary buffer caused as a result of resampling the
// audio. fuzzToleranceMobile is typically larger on mobile platforms since
// we do fixed-point resampling as opposed to floating-point resampling on
// those platforms.
// If fuzzMagnitude, is present, is the maximum magnitude difference, per
// sample, to consider two samples are identical. It is multiplied by the
// maximum value a sample, in our case INT16_MAX. This allows checking files
// that should be identical except one has e.g. a higher quantization noise.
var files = [
// An ogg file, 44.1khz, mono
{
url: "ting-44.1k-1ch.ogg",
valid: true,
expectedUrl: "ting-44.1k-1ch.wav",
numberOfChannels: 1,
frames: 30592,
sampleRate: 44100,
duration: 0.693,
fuzzTolerance: 5,
fuzzToleranceMobile: 1284
},
// An ogg file, 44.1khz, stereo
{
url: "ting-44.1k-2ch.ogg",
valid: true,
expectedUrl: "ting-44.1k-2ch.wav",
numberOfChannels: 2,
frames: 30592,
sampleRate: 44100,
duration: 0.693,
fuzzTolerance: 6,
fuzzToleranceMobile: 2544
},
// An ogg file, 48khz, mono
{
url: "ting-48k-1ch.ogg",
valid: true,
expectedUrl: "ting-48k-1ch.wav",
numberOfChannels: 1,
frames: 33297,
sampleRate: 48000,
duration: 0.693,
fuzzTolerance: 5,
fuzzToleranceMobile: 1388
},
// An ogg file, 48khz, stereo
{
url: "ting-48k-2ch.ogg",
valid: true,
expectedUrl: "ting-48k-2ch.wav",
numberOfChannels: 2,
frames: 33297,
sampleRate: 48000,
duration: 0.693,
fuzzTolerance: 14,
fuzzToleranceMobile: 2752
},
// Make sure decoding a wave file results in the same buffer (for both the
// resampling and non-resampling cases)
{
url: "ting-44.1k-1ch.wav",
valid: true,
expectedUrl: "ting-44.1k-1ch.wav",
numberOfChannels: 1,
frames: 30592,
sampleRate: 44100,
duration: 0.693,
fuzzTolerance: 0,
fuzzToleranceMobile: 0
},
{
url: "ting-48k-1ch.wav",
valid: true,
expectedUrl: "ting-48k-1ch.wav",
numberOfChannels: 1,
frames: 33297,
sampleRate: 48000,
duration: 0.693,
fuzzTolerance: 0,
fuzzToleranceMobile: 0
},
// // A wave file
// //{ url: "24bit-44khz.wav", valid: true, expectedUrl: "24bit-44khz-expected.wav" },
// A non-audio file
{ url: "invalid.txt", valid: false, sampleRate: 44100 },
// A webm file with no audio
{ url: "noaudio.webm", valid: false, sampleRate: 48000 },
// A video ogg file with audio
{
url: "audio.ogv",
valid: true,
expectedUrl: "audio-expected.wav",
numberOfChannels: 2,
sampleRate: 44100,
frames: 47680,
duration: 1.0807,
fuzzTolerance: 106,
fuzzToleranceMobile: 3482
},
{
url: "nil-packet.ogg",
expectedUrl: null,
valid: true,
numberOfChannels: 2,
sampleRate: 48000,
frames: 18600,
duration: 0.3874,
},
{
url: "half-a-second-1ch-44100-mulaw.wav",
// It is expected that mulaw and linear are similar enough at 16-bits
expectedUrl: "half-a-second-1ch-44100.wav",
valid: true,
numberOfChannels: 1,
sampleRate: 44100,
frames: 22050,
duration: 0.5,
fuzzMagnitude: 0.04,
},
{
url: "half-a-second-1ch-44100-alaw.wav",
// It is expected that alaw and linear are similar enough at 16-bits
expectedUrl: "half-a-second-1ch-44100.wav",
valid: true,
numberOfChannels: 1,
sampleRate: 44100,
frames: 22050,
duration: 0.5,
fuzzMagnitude: 0.04,
},
{
url: "waveformatextensible.wav",
valid: true,
numberOfChannels: 1,
sampleRate: 44100,
frames: 472,
duration: 0.01
},
{
// A wav file that has 8 channel, but has a channel mask that doesn't
// match the channel count.
url: "waveformatextensiblebadmask.wav",
valid: true,
numberOfChannels: 8,
sampleRate: 8000,
frames: 80,
duration: 0.01
}
];
// Returns true if the memory buffers are less different that |fuzz| bytes
function fuzzyMemcmp(buf1, buf2, fuzz) {
var difference = 0;
is(buf1.length, buf2.length, "same length");
for (var i = 0; i < buf1.length; ++i) {
if (Math.abs(buf1[i] - buf2[i]) > fuzz.magnitude * (2 << 15)) {
++difference;
}
}
if (difference > fuzz.count) {
ok(false, "Expected at most " + fuzz + " bytes difference, found " + difference + " bytes");
}
console.log(difference, fuzz.count);
return difference <= fuzz.count;
}
function getFuzzTolerance(test) {
var kIsMobile =
navigator.userAgent.includes("Mobile") || // b2g
navigator.userAgent.includes("Android"); // android
return {
magnitude: test.fuzzMagnitude ?? 0,
count: kIsMobile ? test.fuzzToleranceMobile ?? 0 : test.fuzzTolerance ?? 0
};
}
function bufferIsSilent(buffer) {
for (var i = 0; i < buffer.length; ++i) {
if (buffer.getChannelData(0)[i] != 0) {
return false;
}
}
return true;
}
function checkAudioBuffer(buffer, test) {
if (buffer.numberOfChannels != test.numberOfChannels) {
is(buffer.numberOfChannels, test.numberOfChannels, "Correct number of channels");
return;
}
ok(Math.abs(buffer.duration - test.duration) < 1e-3, `Correct duration expected ${test.duration} got ${buffer.duration}`);
if (Math.abs(buffer.duration - test.duration) >= 1e-3) {
ok(false, "got: " + buffer.duration + ", expected: " + test.duration);
}
is(buffer.sampleRate, test.sampleRate, "Correct sample rate");
is(buffer.length, test.frames, "Correct length");
var wave = createWaveFileData(buffer);
if (test.expectedWaveData) {
ok(fuzzyMemcmp(wave, test.expectedWaveData, getFuzzTolerance(test)), "Received expected decoded data for " + test.url);
}
}
function checkResampledBuffer(buffer, test, callback) {
if (buffer.numberOfChannels != test.numberOfChannels) {
is(buffer.numberOfChannels, test.numberOfChannels, "Correct number of channels");
return;
}
ok(Math.abs(buffer.duration - test.duration) < 1e-3, "Correct duration");
if (Math.abs(buffer.duration - test.duration) >= 1e-3) {
ok(false, "got: " + buffer.duration + ", expected: " + test.duration);
}
// Take into account the resampling when checking the size
var expectedLength = test.frames * buffer.sampleRate / test.sampleRate;
SimpleTest.ok(
Math.abs(buffer.length - expectedLength) < 1.0,
"Correct length - got " + buffer.length +
", expected about " + expectedLength
);
// Playback the buffer in the original context, to resample back to the
// original rate and compare with the decoded buffer without resampling.
let cx = test.nativeContext;
var expected = cx.createBufferSource();
expected.buffer = test.expectedBuffer;
expected.start();
var inverse = cx.createGain();
inverse.gain.value = -1;
expected.connect(inverse);
inverse.connect(cx.destination);
var resampled = cx.createBufferSource();
resampled.buffer = buffer;
resampled.start();
// This stop should do nothing, but it tests for bug 937475
resampled.stop(test.frames / cx.sampleRate);
resampled.connect(cx.destination);
cx.oncomplete = function (e) {
ok(!bufferIsSilent(e.renderedBuffer), "Expect buffer not silent");
// Resampling will lose the highest frequency components, so we should
// pass the difference through a low pass filter. However, either the
// input files don't have significant high frequency components or the
// tolerance in compareBuffers() is too high to detect them.
compareBuffers(e.renderedBuffer,
cx.createBuffer(test.numberOfChannels,
test.frames, test.sampleRate));
callback();
}
cx.startRendering();
}
function runResampling(test, response, callback) {
var sampleRate = test.sampleRate == 44100 ? 48000 : 44100;
var cx = new OfflineAudioContext(1, 1, sampleRate);
cx.decodeAudioData(response, function onSuccess(asyncResult) {
is(asyncResult.sampleRate, sampleRate, "Correct sample rate");
checkResampledBuffer(asyncResult, test, callback);
}, function onFailure() {
ok(false, "Expected successful decode with resample");
callback();
});
}
function runTest(test, response, callback) {
// We need to copy the array here, because decodeAudioData will detach the
// array's buffer.
var compressedAudio = response.slice(0);
var expectCallback = false;
var cx = new OfflineAudioContext(test.numberOfChannels || 1,
test.frames || 1, test.sampleRate);
cx.decodeAudioData(response, function onSuccess(asyncResult) {
ok(expectCallback, "Success callback should fire asynchronously");
ok(test.valid, "Did expect success for test " + test.url);
checkAudioBuffer(asyncResult, test);
test.expectedBuffer = asyncResult;
test.nativeContext = cx;
runResampling(test, compressedAudio, callback);
}, function onFailure(e) {
ok(e instanceof DOMException, "We want to see an exception here");
is(e.name, "EncodingError", "Exception name matches");
ok(expectCallback, "Failure callback should fire asynchronously");
ok(!test.valid, "Did expect failure for test " + test.url);
callback();
});
expectCallback = true;
}
function loadTest(test, callback) {
var xhr = new XMLHttpRequest();
xhr.open("GET", test.url, true);
xhr.responseType = "arraybuffer";
xhr.onload = function () {
if (!test.expectedUrl) {
runTest(test, xhr.response, callback);
return;
}
var getExpected = new XMLHttpRequest();
getExpected.open("GET", test.expectedUrl, true);
getExpected.responseType = "arraybuffer";
getExpected.onload = function () {
test.expectedWaveData = new Uint8Array(getExpected.response);
runTest(test, xhr.response, callback);
};
getExpected.send();
};
xhr.send();
}
function loadNextTest() {
if (files.length) {
loadTest(files.shift(), loadNextTest);
} else {
SimpleTest.finish();
}
}
loadNextTest();
</script>
</pre>
</body>
</html>