Skip to content

Commit

Permalink
Add basic unit tests for h264 decoder
Browse files Browse the repository at this point in the history
  • Loading branch information
any1 committed Aug 12, 2024
1 parent 7eed661 commit b1696f4
Show file tree
Hide file tree
Showing 2 changed files with 109 additions and 2 deletions.
4 changes: 2 additions & 2 deletions core/decoders/h264.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

import * as Log from '../util/logging.js';

class H264Parser {
export class H264Parser {
constructor(data) {
this._data = data;
this._index = 0;
Expand Down Expand Up @@ -109,7 +109,7 @@ class H264Parser {
}
}

class H264Context {
export class H264Context {
constructor(width, height) {
this.lastUsed = 0;
this._width = width;
Expand Down
107 changes: 107 additions & 0 deletions tests/test.h264.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
const expect = chai.expect;

Check failure on line 1 in tests/test.h264.js

View workflow job for this annotation

GitHub Actions / eslint

'chai' is not defined

import { H264Parser, H264Context } from '../core/decoders/h264.js';
import Base64 from '../core/base64.js';

/* This video was generated using the following commands:
* magick -size 16x16 xc:#ff0000ff 1.png
* magick -size 16x16 xc:#00ff00ff 2.png
* magick -size 16x16 xc:#0000ffff 3.png
* ffmpeg -pattern_type glob -i '*.png' -c:v libx264 -pix_fmt yuv420p -profile:v baseline video.h264
*
* It is a 3 frame 16x16 video where the first frame is solid red, the second
* is solid green and the third is solid blue.
*/
const redGreenBlue16x16Video = new Uint8Array(Base64.decode(
'AAAAAWdCwArZHpqAgQEgAAADACAAAAZB4kTJAAAAAWjLg8sgAAABBgX//23cRem95tlIt5Ys2CDZ' +
'I+7veDI2NCAtIGNvcmUgMTY0IHIzMTA4IDMxZTE5ZjkgLSBILjI2NC9NUEVHLTQgQVZDIGNvZGVj' +
'IC0gQ29weWxlZnQgMjAwMy0yMDIzIC0gaHR0cDovL3d3dy52aWRlb2xhbi5vcmcveDI2NC5odG1s' +
'IC0gb3B0aW9uczogY2FiYWM9MCByZWY9MyBkZWJsb2NrPTE6MDowIGFuYWx5c2U9MHgxOjB4MTEx' +
'IG1lPWhleCBzdWJtZT03IHBzeT0xIHBzeV9yZD0xLjAwOjAuMDAgbWl4ZWRfcmVmPTEgbWVfcmFu' +
'Z2U9MTYgY2hyb21hX21lPTEgdHJlbGxpcz0xIDh4OGRjdD0wIGNxbT0wIGRlYWR6b25lPTIxLDEx' +
'IGZhc3RfcHNraXA9MSBjaHJvbWFfcXBfb2Zmc2V0PS0yIHRocmVhZHM9MSBsb29rYWhlYWRfdGhy' +
'ZWFkcz0xIHNsaWNlZF90aHJlYWRzPTAgbnI9MCBkZWNpbWF0ZT0xIGludGVybGFjZWQ9MCBibHVy' +
'YXlfY29tcGF0PTAgY29uc3RyYWluZWRfaW50cmE9MCBiZnJhbWVzPTAgd2VpZ2h0cD0wIGtleWlu' +
'dD0yNTAga2V5aW50X21pbj0yNSBzY2VuZWN1dD00MCBpbnRyYV9yZWZyZXNoPTAgcmNfbG9va2Fo' +
'ZWFkPTQwIHJjPWNyZiBtYnRyZWU9MSBjcmY9MjMuMCBxY29tcD0wLjYwIHFwbWluPTAgcXBtYXg9' +
'NjkgcXBzdGVwPTQgaXBfcmF0aW89MS40MCBhcT0xOjEuMDAAgAAAAWWIhArxGKAAJAMcAAQ644AA' +
'l8YAAAABQYiIK8RigACFJHAAEeuOAAJPOAAAAAFBiJArxGKAAJ6ccAAS+I4AAgu4'));

function createSolidColorFrameBuffer(color, width, height) {
const r = (color >> 24) & 0xff;
const g = (color >> 16) & 0xff;
const b = (color >> 8) & 0xff;
const a = (color >> 0) & 0xff;

const size = width * height * 4;
let array = new Uint8Array(size);

for (let i = 0; i < size / 4; ++i) {
array[i * 4 + 0] = r;
array[i * 4 + 1] = g;
array[i * 4 + 2] = b;
array[i * 4 + 3] = a;
}

return array;
}

function frameBufferFromCanvasContext(ctx) {
let imageData = ctx.getImageData(0, 0, 16, 16);
let buffer = imageData.data.buffer;
return new Uint8Array(buffer);
}

describe('H.264 Parser', function () {
it('should parse constrained baseline video', function () {
let parser = new H264Parser(redGreenBlue16x16Video);

let frame = parser.parse();
expect(frame).to.have.property('key', true);

expect(parser).to.have.property('profileIdc', 66);
expect(parser).to.have.property('constraintSet', 192);
expect(parser).to.have.property('levelIdc', 10);

frame = parser.parse();
expect(frame).to.have.property('key', false);

frame = parser.parse();
expect(frame).to.have.property('key', false);

frame = parser.parse();
expect(frame).to.be.null;
});
});

describe('H.264 Context', function () {
it('should decode constrained baseline video chunk', async function () {
let context = new H264Context(16, 16);
let pendingFrame = context.decode(redGreenBlue16x16Video);

expect(pendingFrame).to.have.property('keep', true);

if (!pendingFrame.ready) {
await pendingFrame.promise;
}
expect(pendingFrame.ready).to.be.true;

let frame = pendingFrame.frame;

expect(frame.visibleRect.width).to.equal(16);
expect(frame.visibleRect.height).to.equal(16);

// Note: VideoFrame.copyTo() doesn't work at all on Firefox and it won't
// do the RGBA conversion on Chrome.

// TODO: Use something more portable
let canvas = new OffscreenCanvas(16, 16);
let ctx = canvas.getContext('2d');

ctx.drawImage(frame, 0, 0);
let framebuffer = frameBufferFromCanvasContext(ctx);

const solidBlue = createSolidColorFrameBuffer(0x0000ffff, 16, 16);
expect(framebuffer).to.eql(solidBlue);
});
});

0 comments on commit b1696f4

Please sign in to comment.