385 lines
16 KiB
JavaScript
385 lines
16 KiB
JavaScript
// requires local modules: ast2100, ast2100idct, ast2100const, ast2100util
|
|
/* jshint expr: true */
|
|
|
|
var assert = chai.assert;
|
|
var expect = chai.expect;
|
|
|
|
|
|
// Convert a hex string, optionally containing spaces, into an array of integers representing bytes.
|
|
var parseHex = function (s) {
|
|
s = s.replace(/\s/g, '');
|
|
if (s.length % 2 != 0)
|
|
throw 'Hex data has uneven length!';
|
|
for (var bytes = [], i = 0; i < s.length; i += 2)
|
|
bytes.push(parseInt(s.substr(i, 2), 16));
|
|
return bytes;
|
|
};
|
|
|
|
|
|
function make_decoder () {
|
|
return new Ast2100Decoder({
|
|
width: 0x400,
|
|
height: 0x300,
|
|
blitCallback: function () {}
|
|
});
|
|
}
|
|
|
|
// Swap u32 byte order. Mutates input!
|
|
function buf_swap32 (data) {
|
|
for (var i = 0; i < data.length; i += 4) {
|
|
var tmp;
|
|
|
|
tmp = data[i+0];
|
|
data[i+0] = data[i+3];
|
|
data[i+3] = tmp;
|
|
|
|
tmp = data[i+1];
|
|
data[i+1] = data[i+2];
|
|
data[i+2] = tmp;
|
|
}
|
|
|
|
return data;
|
|
}
|
|
|
|
|
|
describe('ATEN_AST2100 video encoding', function() {
|
|
"use strict";
|
|
|
|
var dec;
|
|
beforeEach(function () {
|
|
dec = make_decoder();
|
|
dec._mcuLimit = 10;
|
|
});
|
|
|
|
describe('BitStream', function() {
|
|
var stream, stream2, stream3;
|
|
var data = buf_swap32(parseHex('f0f0 cccc cccc cccc cccc cccc cccc cccc'));
|
|
var data2 = parseHex('F0F1F2F3 F4F5F6F7 F8F9FAFB FCFDFEFF');
|
|
var data3 = parseHex('0b0b01bc45fbc5e020040101ffff3f20c0ffffff0000240000000000000000005028140a0000000000000000');
|
|
|
|
beforeEach(function () {
|
|
stream = new BitStream({data: data});
|
|
stream2 = new BitStream({data: data2});
|
|
stream3 = new BitStream({data: data3});
|
|
});
|
|
|
|
it('should pass simple sanity checks', function () {
|
|
expect(stream.read(4)).to.equal(0xF);
|
|
expect(stream.read(4)).to.equal(0x0);
|
|
expect(stream.read(4)).to.equal(0xF);
|
|
expect(stream.read(4)).to.equal(0x0);
|
|
});
|
|
|
|
it('should pass simple sanity checks (part II)', function () {
|
|
var i;
|
|
for (i = 0; i < 4; ++i)
|
|
expect(stream.read(1)).to.equal(1);
|
|
for (i = 0; i < 4; ++i)
|
|
expect(stream.read(1)).to.equal(0);
|
|
});
|
|
|
|
it('should be able to properly refill the buffer', function () {
|
|
expect(stream.read(4)).to.equal(0xF);
|
|
expect(stream.read(4)).to.equal(0x0);
|
|
expect(stream.read(4)).to.equal(0xF);
|
|
expect(stream.read(8)).to.equal(0x0C);
|
|
});
|
|
|
|
it('should properly swap byte order', function () {
|
|
expect(stream2.read(8)).to.equal(0xF3);
|
|
expect(stream2.read(8)).to.equal(0xF2);
|
|
expect(stream2.read(8)).to.equal(0xF1);
|
|
expect(stream2.read(8)).to.equal(0xF0);
|
|
expect(stream2.read(8)).to.equal(0xF7);
|
|
});
|
|
|
|
it('should properly handle skipping the full 32-bit read-buffer (data3)', function () {
|
|
// Must do this in two parts so that bits < 32 each time.
|
|
stream3.skip(16);
|
|
stream3.skip(16);
|
|
expect(stream3.read(4)).to.equal(0xE);
|
|
});
|
|
|
|
// it('issue repro', function () {
|
|
// var data = parseHex('f0f0 cccc cccc cccc cccc cccc cccc cccc');
|
|
// var stream = new BitStream({data: data});
|
|
// expect(stream.read(31)).to.equal(0xF0F0CCCC >>> 1);
|
|
// expect(stream.read(8)).to.equal(0x66);
|
|
// });
|
|
});
|
|
|
|
describe('quant tables', function() {
|
|
it('quant tables are properly loaded and scaled', function () {
|
|
// This is luma quant table #4.
|
|
var expected = Int32Array.from([
|
|
0x00090000, 0x0008527e, 0x00068866, 0x000a9537, 0x000d0000, 0x00114908, 0x000f274b, 0x0009616d,
|
|
0x0008527e, 0x000b8b14, 0x000caf8f, 0x00104f53, 0x00136b26, 0x0022df8f, 0x0018c594, 0x000b7b02,
|
|
0x0009255c, 0x000caf8f, 0x000f5d2c, 0x0013f8fd, 0x001cbe90, 0x0020d994, 0x001adebc, 0x000b2cc4,
|
|
0x00083b2b, 0x000eadca, 0x00126faf, 0x00161f78, 0x0020ecad, 0x002c58a1, 0x001ca316, 0x000b07c7,
|
|
0x000a0000, 0x0010a4fc, 0x001a219a, 0x002473bf, 0x00260000, 0x002fed69, 0x001ed922, 0x000bdd19,
|
|
0x000a36ca, 0x0014b4bd, 0x001ecbfa, 0x00214279, 0x00235b34, 0x0023cdea, 0x001ac9de, 0x000b0e2f,
|
|
0x000e9cbf, 0x001b0616, 0x001e67d4, 0x001e8bd4, 0x001ed922, 0x001cea24, 0x00139fb4, 0x00085c96,
|
|
0x000b0935, 0x00138450, 0x00131afd, 0x0011d7e1, 0x001161b4, 0x000c23a7, 0x000882d0, 0x00042fc6
|
|
]);
|
|
|
|
dec._loadQuantTable(0, ATEN_QT_LUMA[4]);
|
|
var luma_quant_table = dec.quantTables[0];
|
|
expect(luma_quant_table).to.deep.equal(expected);
|
|
});
|
|
});
|
|
|
|
describe('IDCT', function () {
|
|
|
|
var outputBuf;
|
|
|
|
beforeEach(function () {
|
|
outputBuf = new Uint8Array(DCTSIZE2); // equivalent to one of the componentBufs in Ast2100Decoder.
|
|
});
|
|
|
|
// TODO: Fix typing: variables should be typed arrays.
|
|
it('test case 0 - DC value only', function () {
|
|
|
|
// this is the output of the VLC / entropy coding process
|
|
// XXX: @KK: Why is this 16-bit?
|
|
var dataUnit = [ // new Int16Array(
|
|
0xFF9C,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0];
|
|
|
|
var expected_idct_output = Uint8Array.from([ // new Uint8Array(
|
|
0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,
|
|
0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,
|
|
0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF]);
|
|
|
|
dec._loadQuantTable(0, ATEN_QT_LUMA[4]);
|
|
var luma_quant_table = dec.quantTables[0];
|
|
|
|
AST2100IDCT.idct_fixed_aan(luma_quant_table, dataUnit, outputBuf);
|
|
|
|
// console.log(result);
|
|
|
|
console.log('expected:');
|
|
console.log(fmt_u8a(expected_idct_output));
|
|
console.log('result:');
|
|
console.log(fmt_u8a(outputBuf));
|
|
|
|
expect(outputBuf).to.deep.equal(expected_idct_output);
|
|
|
|
});
|
|
|
|
it('test case 1', function () {
|
|
// this is the output of the VLC / entropy coding process
|
|
// XXX: @KK: Why is this 16-bit?
|
|
var dataUnit = Int16Array.from([
|
|
0xFFBD,0,0,0,0,0,0,0,
|
|
0xFFC3,0,0,0,0,0,0,0,
|
|
0x26,0,0,0,0,0,0,0,
|
|
0xFFED,0,0,0,0,0,0,
|
|
0,0,0,0,0,0,0,0,0,
|
|
6,0,0,0,0,0,0,0,
|
|
0xFFFC,0,0,0,0,0,0,0,
|
|
2,0,0,0,0,0,0,0]);
|
|
|
|
var expected = Uint8Array.from([
|
|
0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,
|
|
0x11,0x11,0x11,0x11,0x11,0x11,0x11,0x11,
|
|
0x12,0x12,0x12,0x12,0x12,0x12,0x12,0x12,
|
|
0xE,0xE,0xE,0xE,0xE,0xE,0xE,0xE,
|
|
0x12,0x12,0x12,0x12,0x12,0x12,0x12,0x12,
|
|
0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,
|
|
0x9F,0x9F,0x9F,0x9F,0x9F,0x9F,0x9F,0x9F,
|
|
0xA1,0xA1,0xA1,0xA1,0xA1,0xA1,0xA1,0xA1]);
|
|
|
|
dec._loadQuantTable(0, ATEN_QT_LUMA[4]);
|
|
var luma_quant_table = dec.quantTables[0];
|
|
|
|
AST2100IDCT.idct_fixed_aan(luma_quant_table, dataUnit, outputBuf);
|
|
|
|
/*
|
|
console.log('expected:');
|
|
console.log(fmt_u8a(expected));
|
|
console.log('result:');
|
|
console.log(fmt_u8a(outputBuf));
|
|
*/
|
|
|
|
// XXX: TEMPORARY -- figure out this rounding issue
|
|
// expect(result).to.deep.equal(expected);
|
|
var maxErr = 0;
|
|
for (i = 0; i < 64; ++i)
|
|
maxErr = Math.max(maxErr, Math.abs(outputBuf[i] - expected[i]));
|
|
if (maxErr > 1)
|
|
throw 'Error too high!';
|
|
|
|
});
|
|
|
|
it('test case 2', function () {
|
|
// this is the output of the VLC / entropy coding process
|
|
// XXX: @KK: Why is this 16-bit?
|
|
|
|
var dataUnit = Int16Array.from([
|
|
0xFF9B, 0, 0, 0, 0, 0, 0, 0, 0xFFA4, 0, 0, 0, 0, 0, 0, 0, 0x35, 0, 0, 0, 0, 0, 0, 0,
|
|
0xFFE6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0xFFFA,
|
|
0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0]);
|
|
|
|
var expected = [
|
|
0xE, 0xE, 0xE, 0xE, 0xE, 0xE, 0xE, 0xE, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13,
|
|
0xD, 0xD, 0xD, 0xD, 0xD, 0xD, 0xD, 0xD, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13,
|
|
0xD, 0xD, 0xD, 0xD, 0xD, 0xD, 0xD, 0xD, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14,
|
|
0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1];
|
|
|
|
// Between IDCT passes, the workspace should look like this:
|
|
// [-903, 0, 0, 0, 0, 0, 0, 0, -874, 0, 0, 0, 0, 0, 0, 0, -914, 0, 0, 0, 0, 0, 0, 0, -874, 0, 0, 0, 0, 0, 0, 0,
|
|
// -915, 0, 0, 0, 0, 0, 0, 0, -868, 0, 0, 0, 0, 0, 0, 0, 230, 0, 0, 0, 0, 0, 0, 0, 266, 0, 0, 0, 0, 0, 0, 0]
|
|
|
|
|
|
dec._loadQuantTable(0, ATEN_QT_LUMA[5]);
|
|
var luma_quant_table = dec.quantTables[0];
|
|
|
|
AST2100IDCT.idct_fixed_aan(luma_quant_table, dataUnit, outputBuf);
|
|
|
|
// console.log(result);
|
|
|
|
console.log('expected:');
|
|
console.log(fmt_u8a(expected));
|
|
console.log('result:');
|
|
console.log(fmt_u8a(outputBuf));
|
|
|
|
// XXX: TEMPORARY -- figure out this rounding issue
|
|
// expect(result).to.deep.equal(expected);
|
|
var maxErr = 0;
|
|
for (i = 0; i < 64; ++i)
|
|
maxErr = Math.max(maxErr, Math.abs(outputBuf[i] - expected[i]));
|
|
if (maxErr > 1)
|
|
throw 'Error too high!';
|
|
});
|
|
|
|
});
|
|
|
|
describe('VQ', function () {
|
|
|
|
it('should successfully load a codebook (colors)', function () {
|
|
var data = buf_swap32(parseHex('bc010b0b e0c5fb45 01010420 203fffff ffffffc0 00240000 00000000 00000000 0a142850'));
|
|
data = data.slice(4);
|
|
|
|
var stream = new BitStream({data: data}); // Strip off quant table selectors and subsampling mode.
|
|
var controlFlag = stream.read(4);
|
|
expect(controlFlag).to.equal(0xE);
|
|
|
|
var xMcuPos = stream.read(8), yMcuPos = stream.read(8);
|
|
expect(xMcuPos).to.equal(0xC);
|
|
expect(yMcuPos).to.equal(0x5F);
|
|
|
|
dec.subsamplingMode = 444; // required or an assertion in the VQ code will fail
|
|
dec._stream = stream;
|
|
dec._parseVqBlock(1); // codewordSize=1
|
|
expect(dec._vqCodewordLookup).to.deep.equal([1, 0, 2, 3]);
|
|
expect(dec._vqCodebook).to.deep.equal([
|
|
[0x10, 0x80, 0x80],
|
|
[0xA2, 0x80, 0x80],
|
|
[0x80, 0x80, 0x80],
|
|
[0xC0, 0x80, 0x80]
|
|
]);
|
|
});
|
|
|
|
it('should correctly handle multiple data blocks', function () {
|
|
// This data is from a memory dump. It should contain two VQ (0xE-type) blocks and then an 0x9 (end-frame).
|
|
var data = buf_swap32(parseHex('bc010b0b e0c5fb45 01010420 203fffff ffffffc0 00240000 00000000 00000000 0a142850' + '00000000 00000000'));
|
|
console.log('VQ multiple block data:');
|
|
console.log(fmt_u8a(data));
|
|
dec.decode(data);
|
|
|
|
// TODO: improve asserts/etc. beyond just 'finished without error'
|
|
});
|
|
|
|
});
|
|
|
|
describe('Full JPEG subsampled MCU example 0', function () {
|
|
// Corresponds to whole-mcu-example / main_mcu_test_2()
|
|
var data = parseHex('040701a61bff6280f81fbaa2ff4dbc408dfccf405c15ff1f004800f500000000000000002dd4a462237ced2c9c25dca4cef7e6667d6626f3308063c3c7a1b7335badc24aaf0b5e9daf69590fcb96206b59e6f2ccb2bdd386b17dbb72182080d6288a2a1f2f0608a0fe87ae287f132f1023ff33d057c5ff470012403d0000000000000000ec1fb06cdacd2bdf9d79f3cde7f9f1362b7ce914ba521c79524a5bdc212a5ed47c3cbc6c7e072ae3e3c18384785f6ca6d4e22fb1af0c96449d1847a9c7037e966fa3ac8d4990830339463d277f25fc6f30ffe5f4f5cfec9f4ffff8bf00a0f5d358a2ab6e4e6778d929f686bd58ca9c26b8f7338f15105065dd39c718e219a78e');
|
|
|
|
|
|
it('should decode properly', function () {
|
|
// DOES NOT APPEAR TO correspond to my notes in 'whole-mcu-example.txt'.
|
|
|
|
dec._blitCallback = function (x, y, width, height, buf) {
|
|
console.log({x:x, y:y, width:width, height:height, buf:buf});
|
|
console.log(fmt_rgb_buf(width, buf));
|
|
};
|
|
|
|
dec.decode(data);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
describe('Full JPEG subsampled MCU example 1', function () {
|
|
|
|
/* TODO: this looks VERRRRRY SIMILAR to the above example */
|
|
|
|
var data = parseHex('040701a61bff6280f81fbaa2ff4dbc408dfccf405c15ff1f004800f500000000000000002dd4a462237ced2c9c25dca4cef7e6667d6626f3308063c3c7a1b7335badc24aaf0b5e9daf69590fcb96206b59e6f2ccb2bdd386b17dbb72182080d6288a2a1f2f0608a0fe87ae287f132f1023ff33d057c5ff470012403d0000000000000000ec1fb06cdacd2bdf9d79f3cde7f9f1362b7ce914ba521c79524a5bdc212a5ed47c3cbc6c7e072ae3e3c18384785f6ca6d4e22fb1af0c96449d1847a9c7037e966fa3ac8d4990830339463d277f25fc6f30ffe5f4f5cfec9f4ffff8bf00a0f5d358a2ab6e4e6778d929f686bd58ca9c26b8f7338f15105065dd39c718');
|
|
|
|
/*
|
|
it('should load quant tables and set subsamplingMode', function () {
|
|
dec.decode(data);
|
|
|
|
expect(dec._loadedQuantTables[0]).to.equal(4);
|
|
expect(dec._loadedQuantTables[1]).to.equal(7);
|
|
expect(dec.subsamplingMode).to.equal(422);
|
|
});
|
|
*/
|
|
|
|
it('should decode properly', function () {
|
|
// DOES NOT APPEAR TO correspond to my notes in 'whole-mcu-example.txt'.
|
|
|
|
dec._blitCallback = function (x, y, width, height, buf) {
|
|
console.log({x:x, y:y, width:width, height:height, buf:buf});
|
|
console.log(fmt_rgb_buf(width, buf));
|
|
};
|
|
|
|
dec.decode(data);
|
|
|
|
});
|
|
});
|
|
|
|
// TODO: On the 'full-frame decode' and 'frame udpate decode' tests, we are NOT actually asserting anything about the code's output yet.
|
|
/*
|
|
describe('Full frame decode test', function () {
|
|
|
|
it('should decode properly', function () {
|
|
|
|
throw "Won't work without jQuery (or some other way of loading data).";
|
|
|
|
dec._blitCallback = function (x, y, width, height, buf) {
|
|
if (x == 0 && y == 0) {
|
|
console.log({x:x, y:y, width:width, height:height, buf:buf});
|
|
console.log(fmt_rgb_buf(width, buf));
|
|
}
|
|
};
|
|
|
|
// TODO: This path won't work for other people, and the test case needs to be made to understand that this
|
|
// is async; plus, I had to manually add jQuery to the generated HTML.
|
|
$.get("/novnc-tests/tests/frame4.hex", function (data) {
|
|
data = parseHex(data);
|
|
dec.decode(data);
|
|
});
|
|
});
|
|
});
|
|
*/
|
|
|
|
describe('Frame update decode test', function () {
|
|
|
|
it('should decode properly', function () {
|
|
var data = parseHex('050501a69a3f6080008aa2a8000000900000000000000000');
|
|
console.log(data);
|
|
|
|
// this data should NOT be solid-white!
|
|
|
|
dec._blitCallback = function (x, y, width, height, buf) {
|
|
console.log({x:x, y:y, width:width, height:height, buf:buf});
|
|
console.log(fmt_rgb_buf(width, buf));
|
|
};
|
|
|
|
dec.decode(data);
|
|
});
|
|
});
|
|
|
|
});
|