1
0
Fork 0
mirror of https://github.com/DanielnetoDotCom/YouPHPTube synced 2025-10-05 19:42:38 +02:00
Also check the lang in case insensitive
This commit is contained in:
Daniel 2022-03-14 14:28:38 -03:00
parent 33e7f7384e
commit 2a9630258f
22658 changed files with 3562773 additions and 3562767 deletions

View file

@ -1,5 +1,5 @@
{
"env": {
"qunit": true
}
}
{
"env": {
"qunit": true
}
}

View file

@ -1,289 +1,289 @@
'use strict';
var
aacStream,
AacStream = require('../lib/aac'),
QUnit = require('qunit'),
utils = require('./utils'),
createId3Header,
createId3FrameHeader,
createAdtsHeader;
createId3Header = function(tagSize) {
var header = [];
header[0] = 'I'.charCodeAt(0);
header[1] = 'D'.charCodeAt(0);
header[2] = '3'.charCodeAt(0);
// 2 version bytes, ID3v2.4.0 (major 4, revision 0)
header[3] = 4;
header[4] = 0;
// unsynchronization, extended header, experimental indicator, footer present flags
header[5] = 0;
// "The ID3v2 tag size is the sum of the byte length of the extended
// header, the padding and the frames after unsynchronisation. If a
// footer is present this equals to ('total size' - 20) bytes, otherwise
// ('total size' - 10) bytes."
// http://id3.org/id3v2.4.0-structure
header[6] = 0;
header[7] = 0;
header[8] = 0;
header[9] = tagSize;
return header;
};
createId3FrameHeader = function() {
var header = [];
// four byte frame ID, XYZ are experimental
header[0] = 'X'.charCodeAt(0);
header[1] = 'Y'.charCodeAt(0);
header[2] = 'Z'.charCodeAt(0);
header[3] = '0'.charCodeAt(0);
// four byte sync safe integer size (excluding frame header)
header[4] = 0;
header[5] = 0;
header[6] = 0;
header[7] = 10;
// two bytes for flags
header[8] = 0;
header[9] = 0;
return header;
};
createAdtsHeader = function(frameLength) {
// Header consists of 7 or 9 bytes (without or with CRC).
// see: https://wiki.multimedia.cx/index.php/ADTS
return utils.binaryStringToArrayOfBytes(''.concat(
// 12 bits for syncword (0xFFF)
'111111111111',
// 1 bit MPEG version
'0',
// 2 bit layer (always 0)
'00',
// 1 bit protection absent (1 for no CRC)
'1',
// 2 bit profile
'10',
// 4 bit sampling frequency index
'0110',
// 1 bit private bit
'0',
// 3 bit channel config
'100',
// 2 bit (ignore)
'00',
// 2 bit (copright bits)
'00',
// 13 bit frame length (includes header length)
utils.leftPad(frameLength.toString(2), 13),
// 11 bit buffer fullness
'11111111111',
// 2 bit number of AAC frames minus 1
'00'
// 16 bit CRC (if present)
));
};
QUnit.module('AAC Stream', {
beforeEach: function() {
aacStream = new AacStream();
}
});
QUnit.test('parses ID3 tag', function(assert) {
var
id3Count = 0,
adtsCount = 0,
frameHeader = createId3FrameHeader(),
id3Tag = createId3Header(frameHeader.length).concat(frameHeader);
aacStream.on('data', function(frame) {
if (frame.type === 'timed-metadata') {
id3Count += 1;
} else if (frame.type === 'audio') {
adtsCount += 1;
}
});
aacStream.push(new Uint8Array(id3Tag));
assert.equal(adtsCount, 0, 'no adts frames');
assert.equal(id3Count, 1, 'one id3 chunk');
});
QUnit.test('parses two ID3 tags in sequence', function(assert) {
var
id3Count = 0,
adtsCount = 0,
frameHeader = createId3FrameHeader(),
id3Tag = createId3Header(frameHeader.length).concat(frameHeader);
aacStream.on('data', function(frame) {
if (frame.type === 'timed-metadata') {
id3Count += 1;
} else if (frame.type === 'audio') {
adtsCount += 1;
}
});
aacStream.push(new Uint8Array(id3Tag.concat(id3Tag)));
assert.equal(adtsCount, 0, 'no adts frames');
assert.equal(id3Count, 2, 'two id3 chunks');
});
QUnit.test('does not parse second ID3 tag if it\'s incomplete', function(assert) {
var
id3Count = 0,
adtsCount = 0,
frameHeader = createId3FrameHeader(),
id3Tag = createId3Header(frameHeader.length).concat(frameHeader);
aacStream.on('data', function(frame) {
if (frame.type === 'timed-metadata') {
id3Count += 1;
} else if (frame.type === 'audio') {
adtsCount += 1;
}
});
aacStream.push(new Uint8Array(id3Tag.concat(id3Tag.slice(0, id3Tag.length - 1))));
assert.equal(adtsCount, 0, 'no adts frames');
assert.equal(id3Count, 1, 'one id3 chunk');
});
QUnit.test('handles misaligned adts header', function(assert) {
var
id3Count = 0,
adtsCount = 0,
// fake adts frame
adtsFrame = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
packetStream = createAdtsHeader(adtsFrame.length).concat(adtsFrame);
aacStream.on('data', function(frame) {
if (frame.type === 'timed-metadata') {
id3Count += 1;
} else if (frame.type === 'audio') {
adtsCount += 1;
}
});
// misalign by two bytes specific to a bug related to detecting sync bytes
// (where we were only properly checking the second byte)
aacStream.push(new Uint8Array([0x01, 0xf0].concat(packetStream)));
assert.equal(adtsCount, 1, 'one adts frames');
assert.equal(id3Count, 0, 'no id3 chunk');
});
QUnit.test('handles incomplete adts frame after id3 frame', function(assert) {
var
id3Count = 0,
adtsCount = 0,
id3FrameHeader = createId3FrameHeader(),
id3Tag = createId3Header(id3FrameHeader.length).concat(id3FrameHeader),
// in this case:
// id3 tag = 20 bytes
// adts header = 7 bytes
// total = 27 bytes
// report the ADTS frame size as 20 bytes
adtsHeader = createAdtsHeader(20),
// no adts frame, stream was cut off
packetStream = id3Tag.concat(adtsHeader);
aacStream.on('data', function(frame) {
if (frame.type === 'timed-metadata') {
id3Count += 1;
} else if (frame.type === 'audio') {
adtsCount += 1;
}
});
aacStream.push(new Uint8Array(packetStream));
assert.equal(adtsCount, 0, 'no adts frame');
assert.equal(id3Count, 1, 'one id3 chunk');
});
QUnit.test('emits data after receiving push', function(assert) {
var
array = new Uint8Array(109),
count = 0;
array[0] = 255;
array[1] = 241;
array[2] = 92;
array[3] = 128;
array[4] = 13;
array[5] = 191;
array[6] = 252;
array[7] = 33;
array[8] = 32;
array[9] = 3;
array[10] = 64;
array[11] = 104;
array[12] = 27;
array[13] = 212;
aacStream.setTimestamp(90);
aacStream.on('data', function(frame) {
if (frame.pts === 90 && frame.dts === 90) {
count += 1;
}
});
aacStream.push(array);
assert.equal(count, 1);
});
QUnit.test('continues parsing after corrupted stream', function(assert) {
var
array = new Uint8Array(10000),
adtsCount = 0,
id3Count = 0;
// an ID3 frame
array[0] = 73;
array[1] = 68;
array[2] = 51;
array[3] = 4;
array[4] = 0;
array[5] = 0;
array[6] = 0;
array[7] = 0;
array[8] = 0;
array[9] = 63;
array[10] = 80;
array[11] = 82;
array[12] = 73;
array[13] = 86;
// an atds frame
array[1020] = 255;
array[1021] = 241;
array[1022] = 92;
array[1023] = 128;
array[1024] = 13;
array[1025] = 191;
array[1026] = 252;
array[1027] = 33;
array[1028] = 32;
array[1029] = 3;
array[1030] = 64;
array[1031] = 104;
array[1032] = 27;
array[1033] = 212;
aacStream.on('data', function(frame) {
if (frame.type === 'timed-metadata') {
id3Count += 1;
} else if (frame.type === 'audio') {
adtsCount += 1;
}
});
aacStream.push(array);
assert.equal(adtsCount, 1);
assert.equal(id3Count, 1);
});
'use strict';
var
aacStream,
AacStream = require('../lib/aac'),
QUnit = require('qunit'),
utils = require('./utils'),
createId3Header,
createId3FrameHeader,
createAdtsHeader;
createId3Header = function(tagSize) {
var header = [];
header[0] = 'I'.charCodeAt(0);
header[1] = 'D'.charCodeAt(0);
header[2] = '3'.charCodeAt(0);
// 2 version bytes, ID3v2.4.0 (major 4, revision 0)
header[3] = 4;
header[4] = 0;
// unsynchronization, extended header, experimental indicator, footer present flags
header[5] = 0;
// "The ID3v2 tag size is the sum of the byte length of the extended
// header, the padding and the frames after unsynchronisation. If a
// footer is present this equals to ('total size' - 20) bytes, otherwise
// ('total size' - 10) bytes."
// http://id3.org/id3v2.4.0-structure
header[6] = 0;
header[7] = 0;
header[8] = 0;
header[9] = tagSize;
return header;
};
createId3FrameHeader = function() {
var header = [];
// four byte frame ID, XYZ are experimental
header[0] = 'X'.charCodeAt(0);
header[1] = 'Y'.charCodeAt(0);
header[2] = 'Z'.charCodeAt(0);
header[3] = '0'.charCodeAt(0);
// four byte sync safe integer size (excluding frame header)
header[4] = 0;
header[5] = 0;
header[6] = 0;
header[7] = 10;
// two bytes for flags
header[8] = 0;
header[9] = 0;
return header;
};
createAdtsHeader = function(frameLength) {
// Header consists of 7 or 9 bytes (without or with CRC).
// see: https://wiki.multimedia.cx/index.php/ADTS
return utils.binaryStringToArrayOfBytes(''.concat(
// 12 bits for syncword (0xFFF)
'111111111111',
// 1 bit MPEG version
'0',
// 2 bit layer (always 0)
'00',
// 1 bit protection absent (1 for no CRC)
'1',
// 2 bit profile
'10',
// 4 bit sampling frequency index
'0110',
// 1 bit private bit
'0',
// 3 bit channel config
'100',
// 2 bit (ignore)
'00',
// 2 bit (copright bits)
'00',
// 13 bit frame length (includes header length)
utils.leftPad(frameLength.toString(2), 13),
// 11 bit buffer fullness
'11111111111',
// 2 bit number of AAC frames minus 1
'00'
// 16 bit CRC (if present)
));
};
QUnit.module('AAC Stream', {
beforeEach: function() {
aacStream = new AacStream();
}
});
QUnit.test('parses ID3 tag', function(assert) {
var
id3Count = 0,
adtsCount = 0,
frameHeader = createId3FrameHeader(),
id3Tag = createId3Header(frameHeader.length).concat(frameHeader);
aacStream.on('data', function(frame) {
if (frame.type === 'timed-metadata') {
id3Count += 1;
} else if (frame.type === 'audio') {
adtsCount += 1;
}
});
aacStream.push(new Uint8Array(id3Tag));
assert.equal(adtsCount, 0, 'no adts frames');
assert.equal(id3Count, 1, 'one id3 chunk');
});
QUnit.test('parses two ID3 tags in sequence', function(assert) {
var
id3Count = 0,
adtsCount = 0,
frameHeader = createId3FrameHeader(),
id3Tag = createId3Header(frameHeader.length).concat(frameHeader);
aacStream.on('data', function(frame) {
if (frame.type === 'timed-metadata') {
id3Count += 1;
} else if (frame.type === 'audio') {
adtsCount += 1;
}
});
aacStream.push(new Uint8Array(id3Tag.concat(id3Tag)));
assert.equal(adtsCount, 0, 'no adts frames');
assert.equal(id3Count, 2, 'two id3 chunks');
});
QUnit.test('does not parse second ID3 tag if it\'s incomplete', function(assert) {
var
id3Count = 0,
adtsCount = 0,
frameHeader = createId3FrameHeader(),
id3Tag = createId3Header(frameHeader.length).concat(frameHeader);
aacStream.on('data', function(frame) {
if (frame.type === 'timed-metadata') {
id3Count += 1;
} else if (frame.type === 'audio') {
adtsCount += 1;
}
});
aacStream.push(new Uint8Array(id3Tag.concat(id3Tag.slice(0, id3Tag.length - 1))));
assert.equal(adtsCount, 0, 'no adts frames');
assert.equal(id3Count, 1, 'one id3 chunk');
});
QUnit.test('handles misaligned adts header', function(assert) {
var
id3Count = 0,
adtsCount = 0,
// fake adts frame
adtsFrame = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
packetStream = createAdtsHeader(adtsFrame.length).concat(adtsFrame);
aacStream.on('data', function(frame) {
if (frame.type === 'timed-metadata') {
id3Count += 1;
} else if (frame.type === 'audio') {
adtsCount += 1;
}
});
// misalign by two bytes specific to a bug related to detecting sync bytes
// (where we were only properly checking the second byte)
aacStream.push(new Uint8Array([0x01, 0xf0].concat(packetStream)));
assert.equal(adtsCount, 1, 'one adts frames');
assert.equal(id3Count, 0, 'no id3 chunk');
});
QUnit.test('handles incomplete adts frame after id3 frame', function(assert) {
var
id3Count = 0,
adtsCount = 0,
id3FrameHeader = createId3FrameHeader(),
id3Tag = createId3Header(id3FrameHeader.length).concat(id3FrameHeader),
// in this case:
// id3 tag = 20 bytes
// adts header = 7 bytes
// total = 27 bytes
// report the ADTS frame size as 20 bytes
adtsHeader = createAdtsHeader(20),
// no adts frame, stream was cut off
packetStream = id3Tag.concat(adtsHeader);
aacStream.on('data', function(frame) {
if (frame.type === 'timed-metadata') {
id3Count += 1;
} else if (frame.type === 'audio') {
adtsCount += 1;
}
});
aacStream.push(new Uint8Array(packetStream));
assert.equal(adtsCount, 0, 'no adts frame');
assert.equal(id3Count, 1, 'one id3 chunk');
});
QUnit.test('emits data after receiving push', function(assert) {
var
array = new Uint8Array(109),
count = 0;
array[0] = 255;
array[1] = 241;
array[2] = 92;
array[3] = 128;
array[4] = 13;
array[5] = 191;
array[6] = 252;
array[7] = 33;
array[8] = 32;
array[9] = 3;
array[10] = 64;
array[11] = 104;
array[12] = 27;
array[13] = 212;
aacStream.setTimestamp(90);
aacStream.on('data', function(frame) {
if (frame.pts === 90 && frame.dts === 90) {
count += 1;
}
});
aacStream.push(array);
assert.equal(count, 1);
});
QUnit.test('continues parsing after corrupted stream', function(assert) {
var
array = new Uint8Array(10000),
adtsCount = 0,
id3Count = 0;
// an ID3 frame
array[0] = 73;
array[1] = 68;
array[2] = 51;
array[3] = 4;
array[4] = 0;
array[5] = 0;
array[6] = 0;
array[7] = 0;
array[8] = 0;
array[9] = 63;
array[10] = 80;
array[11] = 82;
array[12] = 73;
array[13] = 86;
// an atds frame
array[1020] = 255;
array[1021] = 241;
array[1022] = 92;
array[1023] = 128;
array[1024] = 13;
array[1025] = 191;
array[1026] = 252;
array[1027] = 33;
array[1028] = 32;
array[1029] = 3;
array[1030] = 64;
array[1031] = 104;
array[1032] = 27;
array[1033] = 212;
aacStream.on('data', function(frame) {
if (frame.type === 'timed-metadata') {
id3Count += 1;
} else if (frame.type === 'audio') {
adtsCount += 1;
}
});
aacStream.push(array);
assert.equal(adtsCount, 1);
assert.equal(id3Count, 1);
});

View file

@ -1,89 +1,89 @@
'use strict';
var segments = require('data-files!segments');
var
QUnit = require('qunit'),
utils = require('../lib/aac/utils.js'),
testSegment = segments['test-aac-segment.aac']();
var id3TagOffset = 0;
var audioFrameOffset = 73;
QUnit.module('AAC Utils');
QUnit.test('correctly determines aac data', function(assert) {
assert.ok(utils.isLikelyAacData(testSegment), 'test segment is aac');
var id3Offset = utils.parseId3TagSize(testSegment, 0);
var id3 = Array.prototype.slice.call(testSegment, 0, id3Offset);
var segmentOnly = testSegment.subarray(id3Offset);
var multipleId3 = new Uint8Array([]
.concat(id3)
.concat(id3)
.concat(id3)
.concat(id3)
.concat(Array.prototype.slice.call(segmentOnly))
);
assert.ok(utils.isLikelyAacData(segmentOnly), 'test segment is aac without id3');
assert.notOk(utils.isLikelyAacData(testSegment.subarray(id3Offset + 25)), 'non aac data not recognized');
assert.notOk(utils.isLikelyAacData(testSegment.subarray(0, 5)), 'not enough aac data is not recognized');
assert.ok(utils.isLikelyAacData(multipleId3), 'test segment with multilpe id3');
});
QUnit.test('correctly parses aac packet type', function(assert) {
assert.equal(utils.parseType(testSegment, id3TagOffset), 'timed-metadata',
'parsed timed-metadata type');
assert.equal(utils.parseType(testSegment, 1), null,
'parsed unknown type');
assert.equal(utils.parseType(testSegment, audioFrameOffset), 'audio',
'parsed audio type');
});
QUnit.test('correctly parses ID3 tag size', function(assert) {
assert.equal(utils.parseId3TagSize(testSegment, id3TagOffset), 73,
'correct id3 tag size');
});
QUnit.test('correctly parses timestamp from ID3 metadata', function(assert) {
var frameSize = utils.parseId3TagSize(testSegment, id3TagOffset);
var frame = testSegment.subarray(id3TagOffset, id3TagOffset + frameSize);
assert.equal(utils.parseAacTimestamp(frame), 895690, 'correct aac timestamp');
});
QUnit.test('correctly parses adts frame size', function(assert) {
assert.equal(utils.parseAdtsSize(testSegment, audioFrameOffset), 13,
'correct adts frame size');
});
QUnit.test('correctly parses packet sample rate', function(assert) {
var frameSize = utils.parseAdtsSize(testSegment, audioFrameOffset);
var frame = testSegment.subarray(audioFrameOffset, audioFrameOffset + frameSize);
assert.equal(utils.parseSampleRate(frame), 44100, 'correct sample rate');
});
QUnit.test('parses correct ID3 tag size', function(assert) {
var packetStream = new Uint8Array(10);
packetStream[9] = 63;
assert.equal(utils.parseId3TagSize(packetStream, 0),
73,
'correctly parsed a header without a footer');
});
QUnit.test('parses correct ADTS Frame size', function(assert) {
var packetStream = new Uint8Array(6);
packetStream[3] = 128;
packetStream[4] = 29;
packetStream[5] = 255;
assert.equal(utils.parseAdtsSize(packetStream, 0), 239, 'correctly parsed framesize');
});
'use strict';
var segments = require('data-files!segments');
var
QUnit = require('qunit'),
utils = require('../lib/aac/utils.js'),
testSegment = segments['test-aac-segment.aac']();
var id3TagOffset = 0;
var audioFrameOffset = 73;
QUnit.module('AAC Utils');
QUnit.test('correctly determines aac data', function(assert) {
assert.ok(utils.isLikelyAacData(testSegment), 'test segment is aac');
var id3Offset = utils.parseId3TagSize(testSegment, 0);
var id3 = Array.prototype.slice.call(testSegment, 0, id3Offset);
var segmentOnly = testSegment.subarray(id3Offset);
var multipleId3 = new Uint8Array([]
.concat(id3)
.concat(id3)
.concat(id3)
.concat(id3)
.concat(Array.prototype.slice.call(segmentOnly))
);
assert.ok(utils.isLikelyAacData(segmentOnly), 'test segment is aac without id3');
assert.notOk(utils.isLikelyAacData(testSegment.subarray(id3Offset + 25)), 'non aac data not recognized');
assert.notOk(utils.isLikelyAacData(testSegment.subarray(0, 5)), 'not enough aac data is not recognized');
assert.ok(utils.isLikelyAacData(multipleId3), 'test segment with multilpe id3');
});
QUnit.test('correctly parses aac packet type', function(assert) {
assert.equal(utils.parseType(testSegment, id3TagOffset), 'timed-metadata',
'parsed timed-metadata type');
assert.equal(utils.parseType(testSegment, 1), null,
'parsed unknown type');
assert.equal(utils.parseType(testSegment, audioFrameOffset), 'audio',
'parsed audio type');
});
QUnit.test('correctly parses ID3 tag size', function(assert) {
assert.equal(utils.parseId3TagSize(testSegment, id3TagOffset), 73,
'correct id3 tag size');
});
QUnit.test('correctly parses timestamp from ID3 metadata', function(assert) {
var frameSize = utils.parseId3TagSize(testSegment, id3TagOffset);
var frame = testSegment.subarray(id3TagOffset, id3TagOffset + frameSize);
assert.equal(utils.parseAacTimestamp(frame), 895690, 'correct aac timestamp');
});
QUnit.test('correctly parses adts frame size', function(assert) {
assert.equal(utils.parseAdtsSize(testSegment, audioFrameOffset), 13,
'correct adts frame size');
});
QUnit.test('correctly parses packet sample rate', function(assert) {
var frameSize = utils.parseAdtsSize(testSegment, audioFrameOffset);
var frame = testSegment.subarray(audioFrameOffset, audioFrameOffset + frameSize);
assert.equal(utils.parseSampleRate(frame), 44100, 'correct sample rate');
});
QUnit.test('parses correct ID3 tag size', function(assert) {
var packetStream = new Uint8Array(10);
packetStream[9] = 63;
assert.equal(utils.parseId3TagSize(packetStream, 0),
73,
'correctly parsed a header without a footer');
});
QUnit.test('parses correct ADTS Frame size', function(assert) {
var packetStream = new Uint8Array(6);
packetStream[3] = 128;
packetStream[4] = 29;
packetStream[5] = 255;
assert.equal(utils.parseAdtsSize(packetStream, 0), 239, 'correctly parsed framesize');
});

View file

@ -1,17 +1,17 @@
var window = require('global/window');
// TODO: use vhs-utils here
var atob = (s) => window.atob ? window.atob(s) : Buffer.from(s, 'base64').toString('binary');
var base64ToUint8Array = function(base64) {
var decoded = atob(base64);
var uint8Array = new Uint8Array(new ArrayBuffer(decoded.length));
for (var i = 0; i < decoded.length; i++) {
uint8Array[i] = decoded.charCodeAt(i);
}
return uint8Array;
};
module.exports = base64ToUint8Array;
var window = require('global/window');
// TODO: use vhs-utils here
var atob = (s) => window.atob ? window.atob(s) : Buffer.from(s, 'base64').toString('binary');
var base64ToUint8Array = function(base64) {
var decoded = atob(base64);
var uint8Array = new Uint8Array(new ArrayBuffer(decoded.length));
for (var i = 0; i < decoded.length; i++) {
uint8Array[i] = decoded.charCodeAt(i);
}
return uint8Array;
};
module.exports = base64ToUint8Array;

View file

@ -1,271 +1,271 @@
'use strict';
var segments = require('data-files!segments');
var probe = require('../lib/mp4/probe');
var CaptionParser = require('../lib/mp4').CaptionParser;
var captionParser;
var dashInit = segments['dash-608-captions-init.mp4']();
// This file includes 2 segments data to force a flush
// of the first caption. The second caption is at 200s
var dashSegment = segments['dash-608-captions-seg.m4s']();
var malformedSei = segments['malformed-sei.m4s']();
var malformedSeiInit = segments['malformed-sei-init.mp4']();
var mp4Helpers = require('./utils/mp4-helpers');
var box = mp4Helpers.box;
var seiNalUnitGenerator = require('./utils/sei-nal-unit-generator');
var makeMdatFromCaptionPackets = seiNalUnitGenerator.makeMdatFromCaptionPackets;
var characters = seiNalUnitGenerator.characters;
var packets0;
var version0Moof;
var version0Segment;
var packets1;
var version1Moof;
var version1Segment;
QUnit.module('MP4 Caption Parser', {
beforeEach: function() {
captionParser = new CaptionParser();
captionParser.init();
},
afterEach: function() {
captionParser.reset();
}
});
QUnit.test('parse captions from real segment', function(assert) {
var trackIds;
var timescales;
var cc;
trackIds = probe.videoTrackIds(dashInit);
timescales = probe.timescale(dashInit);
cc = captionParser.parse(dashSegment, trackIds, timescales);
assert.equal(cc.captions.length, 1);
assert.equal(cc.captions[0].text, '00:00:00',
'real segment caption has correct text');
assert.equal(cc.captions[0].stream, 'CC1',
'real segment caption has correct stream');
assert.equal(cc.captions[0].startTime, 0,
'real segment caption has correct startTime');
assert.equal(cc.captions[0].endTime, 119,
'real segment caption has correct endTime');
assert.equal(cc.captionStreams.CC1, true,
'real segment caption streams have correct settings');
});
QUnit.test('parse captions when init segment received late', function(assert) {
var trackIds;
var timescales;
var cc;
trackIds = probe.videoTrackIds(dashInit);
timescales = probe.timescale(dashInit);
cc = captionParser.parse(dashSegment, [], {});
assert.ok(!cc, 'there should not be any parsed captions yet');
cc = captionParser.parse(dashSegment, trackIds, timescales);
assert.equal(cc.captions.length, 1);
});
QUnit.test('parseTrackId for version 0 and version 1 boxes', function(assert) {
var v0Captions;
var v1Captions;
v0Captions = captionParser.parse(
new Uint8Array(version0Segment), // segment
[1], // trackIds
{ 1: 90000 }); // timescales);
assert.equal(v0Captions.captions.length, 1, 'got 1 version0 caption');
assert.equal(v0Captions.captions[0].text, 'test string #1',
'got the expected version0 caption text');
assert.equal(v0Captions.captions[0].stream, 'CC1',
'returned the correct caption stream CC1');
assert.equal(v0Captions.captions[0].startTime, 10 / 90000,
'the start time for version0 caption is correct');
assert.equal(v0Captions.captions[0].endTime, 10 / 90000,
'the end time for version0 caption is correct');
assert.equal(v0Captions.captionStreams.CC1, true,
'stream is CC1');
assert.ok(!v0Captions.captionStreams.CC4,
'stream is not CC4');
// Clear parsed captions
captionParser.clearParsedCaptions();
v1Captions = captionParser.parse(
new Uint8Array(version1Segment),
[2], // trackIds
{ 2: 90000 }); // timescales
assert.equal(v1Captions.captions.length, 1, 'got version1 caption');
assert.equal(v1Captions.captions[0].text, 'test string #2',
'got the expected version1 caption text');
assert.equal(v1Captions.captions[0].stream, 'CC4',
'returned the correct caption stream CC4');
assert.equal(v1Captions.captions[0].startTime, 30 / 90000,
'the start time for version1 caption is correct');
assert.equal(v1Captions.captions[0].endTime, 30 / 90000,
'the end time for version1 caption is correct');
assert.equal(v1Captions.captionStreams.CC4, true,
'stream is CC4');
assert.ok(!v1Captions.captionStreams.CC1,
'stream is not CC1');
});
QUnit.test('returns log on invalid sei nal parse', function(assert) {
var trackIds;
var timescales;
var result;
var logs = [];
trackIds = probe.videoTrackIds(malformedSeiInit);
timescales = probe.timescale(malformedSeiInit);
result = captionParser.parse(malformedSei, trackIds, timescales);
assert.deepEqual(result.logs, [
{level: 'warn', message: 'We\'ve encountered a nal unit without data at 189975 for trackId 1. See mux.js#223.'}
], 'logged invalid sei nal');
});
// ---------
// Test Data
// ---------
// "test string #1", channel 1, field 1
packets0 = [
// Send another command so that the second EOC isn't ignored
{ ccData: 0x1420, type: 0 },
// RCL, resume caption loading
{ ccData: 0x1420, type: 0 },
// 'test string #1'
{ ccData: characters('te'), type: 0 },
{ ccData: characters('st'), type: 0 },
{ ccData: characters(' s'), type: 0 },
// 'test string #1' continued
{ ccData: characters('tr'), type: 0 },
{ ccData: characters('in'), type: 0 },
{ ccData: characters('g '), type: 0 },
{ ccData: characters('#1'), type: 0 },
// EOC, End of Caption. End display
{ ccData: 0x142f, type: 0 },
// EOC, End of Caption. Finished transmitting, begin display
{ ccData: 0x142f, type: 0 },
// Send another command so that the second EOC isn't ignored
{ ccData: 0x1420, type: 0 },
// EOC, End of Caption. End display
{ ccData: 0x142f, type: 0 }
];
// "test string #2", channel 2, field 2
packets1 = [
// Send another command so that the second EOC isn't ignored
{ ccData: 0x1d20, type: 1 },
// RCL, resume caption loading
{ ccData: 0x1d20, type: 1 },
// 'test string #2'
{ ccData: characters('te'), type: 1 },
{ ccData: characters('st'), type: 1 },
{ ccData: characters(' s'), type: 1 },
// 'test string #2' continued
{ ccData: characters('tr'), type: 1 },
{ ccData: characters('in'), type: 1 },
{ ccData: characters('g '), type: 1 },
{ ccData: characters('#2'), type: 1 },
// EOC, End of Caption. End display
{ ccData: 0x1d2f, type: 1 },
// EOC, End of Caption. Finished transmitting, begin display
{ ccData: 0x1d2f, type: 1 },
// Send another command so that the second EOC isn't ignored
{ ccData: 0x1d20, type: 1 },
// EOC, End of Caption. End display
{ ccData: 0x1d2f, type: 1 }
];
/**
* version 0:
* Uses version 0 boxes, no first sample flags
* sample size, flags, duration, composition time offset included.
**/
version0Moof =
box('moof',
box('traf',
box('tfhd',
0x00, // version
0x00, 0x00, 0x00, // flags
0x00, 0x00, 0x00, 0x01, // track_ID
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, // base_data_offset
0x00, 0x00, 0x00, 0x00, // sample_description_index
0x00, 0x00, 0x00, 0x00, // default_sample_duration
0x00, 0x00, 0x00, 0x00, // default_sample_size
0x00, 0x00, 0x00, 0x00), // default_sample_flags
box('tfdt',
0x00, // version
0x00, 0x00, 0x00, // flags
0x00, 0x00, 0x00, 0x00), // baseMediaDecodeTime,
box('trun',
0x00, // version
0x00, 0x0f, 0x01, // flags: dataOffsetPresent, sampleDurationPresent,
// sampleSizePresent, sampleFlagsPresent,
// sampleCompositionTimeOffsetsPresent
0x00, 0x00, 0x00, 0x02, // sample_count
0x00, 0x00, 0x00, 0x00, // data_offset, no first_sample_flags
// sample 1
0x00, 0x00, 0x00, 0x0a, // sample_duration = 10
0x00, 0x00, 0x00, 0x0a, // sample_size = 10
0x00, 0x00, 0x00, 0x00, // sample_flags
0x00, 0x00, 0x00, 0x0a, // signed sample_composition_time_offset = 10
// sample 2
0x00, 0x00, 0x00, 0x0a, // sample_duration = 10
0x00, 0x00, 0x00, 0x0a, // sample_size = 10
0x00, 0x00, 0x00, 0x00, // sample_flags
0x00, 0x00, 0x00, 0x14))); // signed sample_composition_time_offset = 20
version0Segment = version0Moof.concat(makeMdatFromCaptionPackets(packets0));
/**
* version 1:
* Uses version 1 boxes, has first sample flags,
* other samples include flags and composition time offset only.
**/
version1Moof =
box('moof',
box('traf',
box('tfhd',
0x01, // version
0x00, 0x00, 0x18, // flags
0x00, 0x00, 0x00, 0x02, // track_ID
// no base_data_offset, sample_description_index
0x00, 0x00, 0x00, 0x0a, // default_sample_duration = 10
0x00, 0x00, 0x00, 0x0a), // default_sample_size = 10
box('tfdt',
0x01, // version
0x00, 0x00, 0x00, // flags
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x14), // baseMediaDecodeTime = 20,
box('trun',
0x01, // version
0x00, 0x0c, 0x05, // flags: dataOffsetPresent, sampleFlagsPresent,
// firstSampleFlagsPresent,
// sampleCompositionTimeOffsetsPresent
0x00, 0x00, 0x00, 0x02, // sample_count
0x00, 0x00, 0x00, 0x00, // data_offset, has first_sample_flags
// sample 1
0x00, 0x00, 0x00, 0x00, // sample_flags
0x00, 0x00, 0x00, 0x0a, // signed sample_composition_time_offset = 10
// sample 2
0x00, 0x00, 0x00, 0x00, // sample_flags
0x00, 0x00, 0x00, 0x14))); // signed sample_composition_time_offset = 20
version1Segment = version1Moof.concat(makeMdatFromCaptionPackets(packets1));
'use strict';
var segments = require('data-files!segments');
var probe = require('../lib/mp4/probe');
var CaptionParser = require('../lib/mp4').CaptionParser;
var captionParser;
var dashInit = segments['dash-608-captions-init.mp4']();
// This file includes 2 segments data to force a flush
// of the first caption. The second caption is at 200s
var dashSegment = segments['dash-608-captions-seg.m4s']();
var malformedSei = segments['malformed-sei.m4s']();
var malformedSeiInit = segments['malformed-sei-init.mp4']();
var mp4Helpers = require('./utils/mp4-helpers');
var box = mp4Helpers.box;
var seiNalUnitGenerator = require('./utils/sei-nal-unit-generator');
var makeMdatFromCaptionPackets = seiNalUnitGenerator.makeMdatFromCaptionPackets;
var characters = seiNalUnitGenerator.characters;
var packets0;
var version0Moof;
var version0Segment;
var packets1;
var version1Moof;
var version1Segment;
QUnit.module('MP4 Caption Parser', {
beforeEach: function() {
captionParser = new CaptionParser();
captionParser.init();
},
afterEach: function() {
captionParser.reset();
}
});
QUnit.test('parse captions from real segment', function(assert) {
var trackIds;
var timescales;
var cc;
trackIds = probe.videoTrackIds(dashInit);
timescales = probe.timescale(dashInit);
cc = captionParser.parse(dashSegment, trackIds, timescales);
assert.equal(cc.captions.length, 1);
assert.equal(cc.captions[0].text, '00:00:00',
'real segment caption has correct text');
assert.equal(cc.captions[0].stream, 'CC1',
'real segment caption has correct stream');
assert.equal(cc.captions[0].startTime, 0,
'real segment caption has correct startTime');
assert.equal(cc.captions[0].endTime, 119,
'real segment caption has correct endTime');
assert.equal(cc.captionStreams.CC1, true,
'real segment caption streams have correct settings');
});
QUnit.test('parse captions when init segment received late', function(assert) {
var trackIds;
var timescales;
var cc;
trackIds = probe.videoTrackIds(dashInit);
timescales = probe.timescale(dashInit);
cc = captionParser.parse(dashSegment, [], {});
assert.ok(!cc, 'there should not be any parsed captions yet');
cc = captionParser.parse(dashSegment, trackIds, timescales);
assert.equal(cc.captions.length, 1);
});
QUnit.test('parseTrackId for version 0 and version 1 boxes', function(assert) {
var v0Captions;
var v1Captions;
v0Captions = captionParser.parse(
new Uint8Array(version0Segment), // segment
[1], // trackIds
{ 1: 90000 }); // timescales);
assert.equal(v0Captions.captions.length, 1, 'got 1 version0 caption');
assert.equal(v0Captions.captions[0].text, 'test string #1',
'got the expected version0 caption text');
assert.equal(v0Captions.captions[0].stream, 'CC1',
'returned the correct caption stream CC1');
assert.equal(v0Captions.captions[0].startTime, 10 / 90000,
'the start time for version0 caption is correct');
assert.equal(v0Captions.captions[0].endTime, 10 / 90000,
'the end time for version0 caption is correct');
assert.equal(v0Captions.captionStreams.CC1, true,
'stream is CC1');
assert.ok(!v0Captions.captionStreams.CC4,
'stream is not CC4');
// Clear parsed captions
captionParser.clearParsedCaptions();
v1Captions = captionParser.parse(
new Uint8Array(version1Segment),
[2], // trackIds
{ 2: 90000 }); // timescales
assert.equal(v1Captions.captions.length, 1, 'got version1 caption');
assert.equal(v1Captions.captions[0].text, 'test string #2',
'got the expected version1 caption text');
assert.equal(v1Captions.captions[0].stream, 'CC4',
'returned the correct caption stream CC4');
assert.equal(v1Captions.captions[0].startTime, 30 / 90000,
'the start time for version1 caption is correct');
assert.equal(v1Captions.captions[0].endTime, 30 / 90000,
'the end time for version1 caption is correct');
assert.equal(v1Captions.captionStreams.CC4, true,
'stream is CC4');
assert.ok(!v1Captions.captionStreams.CC1,
'stream is not CC1');
});
QUnit.test('returns log on invalid sei nal parse', function(assert) {
var trackIds;
var timescales;
var result;
var logs = [];
trackIds = probe.videoTrackIds(malformedSeiInit);
timescales = probe.timescale(malformedSeiInit);
result = captionParser.parse(malformedSei, trackIds, timescales);
assert.deepEqual(result.logs, [
{level: 'warn', message: 'We\'ve encountered a nal unit without data at 189975 for trackId 1. See mux.js#223.'}
], 'logged invalid sei nal');
});
// ---------
// Test Data
// ---------
// "test string #1", channel 1, field 1
packets0 = [
// Send another command so that the second EOC isn't ignored
{ ccData: 0x1420, type: 0 },
// RCL, resume caption loading
{ ccData: 0x1420, type: 0 },
// 'test string #1'
{ ccData: characters('te'), type: 0 },
{ ccData: characters('st'), type: 0 },
{ ccData: characters(' s'), type: 0 },
// 'test string #1' continued
{ ccData: characters('tr'), type: 0 },
{ ccData: characters('in'), type: 0 },
{ ccData: characters('g '), type: 0 },
{ ccData: characters('#1'), type: 0 },
// EOC, End of Caption. End display
{ ccData: 0x142f, type: 0 },
// EOC, End of Caption. Finished transmitting, begin display
{ ccData: 0x142f, type: 0 },
// Send another command so that the second EOC isn't ignored
{ ccData: 0x1420, type: 0 },
// EOC, End of Caption. End display
{ ccData: 0x142f, type: 0 }
];
// "test string #2", channel 2, field 2
packets1 = [
// Send another command so that the second EOC isn't ignored
{ ccData: 0x1d20, type: 1 },
// RCL, resume caption loading
{ ccData: 0x1d20, type: 1 },
// 'test string #2'
{ ccData: characters('te'), type: 1 },
{ ccData: characters('st'), type: 1 },
{ ccData: characters(' s'), type: 1 },
// 'test string #2' continued
{ ccData: characters('tr'), type: 1 },
{ ccData: characters('in'), type: 1 },
{ ccData: characters('g '), type: 1 },
{ ccData: characters('#2'), type: 1 },
// EOC, End of Caption. End display
{ ccData: 0x1d2f, type: 1 },
// EOC, End of Caption. Finished transmitting, begin display
{ ccData: 0x1d2f, type: 1 },
// Send another command so that the second EOC isn't ignored
{ ccData: 0x1d20, type: 1 },
// EOC, End of Caption. End display
{ ccData: 0x1d2f, type: 1 }
];
/**
* version 0:
* Uses version 0 boxes, no first sample flags
* sample size, flags, duration, composition time offset included.
**/
version0Moof =
box('moof',
box('traf',
box('tfhd',
0x00, // version
0x00, 0x00, 0x00, // flags
0x00, 0x00, 0x00, 0x01, // track_ID
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, // base_data_offset
0x00, 0x00, 0x00, 0x00, // sample_description_index
0x00, 0x00, 0x00, 0x00, // default_sample_duration
0x00, 0x00, 0x00, 0x00, // default_sample_size
0x00, 0x00, 0x00, 0x00), // default_sample_flags
box('tfdt',
0x00, // version
0x00, 0x00, 0x00, // flags
0x00, 0x00, 0x00, 0x00), // baseMediaDecodeTime,
box('trun',
0x00, // version
0x00, 0x0f, 0x01, // flags: dataOffsetPresent, sampleDurationPresent,
// sampleSizePresent, sampleFlagsPresent,
// sampleCompositionTimeOffsetsPresent
0x00, 0x00, 0x00, 0x02, // sample_count
0x00, 0x00, 0x00, 0x00, // data_offset, no first_sample_flags
// sample 1
0x00, 0x00, 0x00, 0x0a, // sample_duration = 10
0x00, 0x00, 0x00, 0x0a, // sample_size = 10
0x00, 0x00, 0x00, 0x00, // sample_flags
0x00, 0x00, 0x00, 0x0a, // signed sample_composition_time_offset = 10
// sample 2
0x00, 0x00, 0x00, 0x0a, // sample_duration = 10
0x00, 0x00, 0x00, 0x0a, // sample_size = 10
0x00, 0x00, 0x00, 0x00, // sample_flags
0x00, 0x00, 0x00, 0x14))); // signed sample_composition_time_offset = 20
version0Segment = version0Moof.concat(makeMdatFromCaptionPackets(packets0));
/**
* version 1:
* Uses version 1 boxes, has first sample flags,
* other samples include flags and composition time offset only.
**/
version1Moof =
box('moof',
box('traf',
box('tfhd',
0x01, // version
0x00, 0x00, 0x18, // flags
0x00, 0x00, 0x00, 0x02, // track_ID
// no base_data_offset, sample_description_index
0x00, 0x00, 0x00, 0x0a, // default_sample_duration = 10
0x00, 0x00, 0x00, 0x0a), // default_sample_size = 10
box('tfdt',
0x01, // version
0x00, 0x00, 0x00, // flags
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x14), // baseMediaDecodeTime = 20,
box('trun',
0x01, // version
0x00, 0x0c, 0x05, // flags: dataOffsetPresent, sampleFlagsPresent,
// firstSampleFlagsPresent,
// sampleCompositionTimeOffsetsPresent
0x00, 0x00, 0x00, 0x02, // sample_count
0x00, 0x00, 0x00, 0x00, // data_offset, has first_sample_flags
// sample 1
0x00, 0x00, 0x00, 0x00, // sample_flags
0x00, 0x00, 0x00, 0x0a, // signed sample_composition_time_offset = 10
// sample 2
0x00, 0x00, 0x00, 0x00, // sample_flags
0x00, 0x00, 0x00, 0x14))); // signed sample_composition_time_offset = 20
version1Segment = version1Moof.concat(makeMdatFromCaptionPackets(packets1));

File diff suppressed because it is too large Load diff

2228
node_modules/mux.js/test/captions.dfxp generated vendored

File diff suppressed because it is too large Load diff

View file

@ -1,113 +1,113 @@
/*
======== A Handy Little QUnit Reference ========
http://api.qunitjs.com/
Test methods:
module(name, {[setup][ ,teardown]})
test(name, callback)
expect(numberOfAssertions)
stop(increment)
start(decrement)
Test assertions:
assert.ok(value, [message])
assert.equal(actual, expected, [message])
assert.notEqual(actual, expected, [message])
assert.deepEqual(actual, expected, [message])
assert.notDeepEqual(actual, expected, [message])
assert.strictEqual(actual, expected, [message])
assert.notStrictEqual(actual, expected, [message])
assert.throws(block, [expected], [message])
*/
var
buffer,
ExpGolomb = require('../lib/utils/exp-golomb'),
expGolomb;
QUnit.module('Exponential Golomb coding');
QUnit.test('small numbers are coded correctly', function(assert) {
var
expected = [
[0xF8, 0],
[0x5F, 1],
[0x7F, 2],
[0x27, 3],
[0x2F, 4],
[0x37, 5],
[0x3F, 6],
[0x11, 7],
[0x13, 8],
[0x15, 9]
],
i = expected.length,
result;
while (i--) {
buffer = new Uint8Array([expected[i][0]]);
expGolomb = new ExpGolomb(buffer);
result = expGolomb.readUnsignedExpGolomb();
assert.equal(expected[i][1], result, expected[i][0] + ' is decoded to ' + expected[i][1]);
}
});
QUnit.test('drops working data as it is parsed', function(assert) {
var expGolomb = new ExpGolomb(new Uint8Array([0x00, 0xFF]));
expGolomb.skipBits(8);
assert.equal(8, expGolomb.bitsAvailable(), '8 bits remain');
assert.equal(0xFF, expGolomb.readBits(8), 'the second byte is read');
});
QUnit.test('drops working data when skipping leading zeros', function(assert) {
var expGolomb = new ExpGolomb(new Uint8Array([0x00, 0x00, 0x00, 0x00, 0xFF]));
assert.equal(32, expGolomb.skipLeadingZeros(), '32 leading zeros are dropped');
assert.equal(8, expGolomb.bitsAvailable(), '8 bits remain');
assert.equal(0xFF, expGolomb.readBits(8), 'the second byte is read');
});
QUnit.test('drops working data when skipping leading zeros', function(assert) {
var expGolomb = new ExpGolomb(new Uint8Array([0x15, 0xab, 0x40, 0xc8, 0xFF]));
assert.equal(3, expGolomb.skipLeadingZeros(), '3 leading zeros are dropped');
assert.equal((8 * 4) + 5, expGolomb.bitsAvailable(), '37 bits remain');
expGolomb.skipBits(1);
assert.equal(0x5a, expGolomb.readBits(8), 'the next bits are read');
});
QUnit.test('skipBits correctly across word-boundaries', function(assert) {
var expGolomb = new ExpGolomb(new Uint8Array([0x15, 0x00, 0x00, 0x28, 0x00, 0x0a, 0x00, 0x00]));
assert.equal(expGolomb.readUnsignedExpGolomb(), 9, 'the first number is read');
expGolomb.skipBits(17);
assert.equal(expGolomb.readUnsignedExpGolomb(), 4, 'the second number is read');
expGolomb.skipBits(13); // Crosses word boundary
assert.equal(expGolomb.readUnsignedExpGolomb(), 4, 'the third number is read');
});
QUnit.test('parses a sequence parameter set', function(assert) {
var
sps = new Uint8Array([
0x27, 0x42, 0xe0, 0x0b,
0xa9, 0x18, 0x60, 0x9d,
0x80, 0x35, 0x06, 0x01,
0x06, 0xb6, 0xc2, 0xb5,
0xef, 0x7c, 0x04
]),
expGolomb = new ExpGolomb(sps);
assert.strictEqual(expGolomb.readBits(8), 0x27, 'the NAL type specifies an SPS');
assert.strictEqual(expGolomb.readBits(8), 66, 'profile_idc is 66');
assert.strictEqual(expGolomb.readBits(4), 0x0E, 'constraints 0-3 are correct');
expGolomb.skipBits(4);
assert.strictEqual(expGolomb.readBits(8), 11, 'level_idc is 11');
assert.strictEqual(expGolomb.readUnsignedExpGolomb(), 0, 'seq_parameter_set_id is 0');
assert.strictEqual(expGolomb.readUnsignedExpGolomb(), 1, 'log2_max_frame_num_minus4 is 1');
assert.strictEqual(expGolomb.readUnsignedExpGolomb(), 0, 'pic_order_cnt_type is 0');
assert.strictEqual(expGolomb.readUnsignedExpGolomb(), 3, 'log2_max_pic_order_cnt_lsb_minus4 is 3');
assert.strictEqual(expGolomb.readUnsignedExpGolomb(), 2, 'max_num_ref_frames is 2');
assert.strictEqual(expGolomb.readBits(1), 0, 'gaps_in_frame_num_value_allowed_flag is false');
assert.strictEqual(expGolomb.readUnsignedExpGolomb(), 11, 'pic_width_in_mbs_minus1 is 11');
assert.strictEqual(expGolomb.readUnsignedExpGolomb(), 8, 'pic_height_in_map_units_minus1 is 8');
assert.strictEqual(expGolomb.readBits(1), 1, 'frame_mbs_only_flag is true');
assert.strictEqual(expGolomb.readBits(1), 1, 'direct_8x8_inference_flag is true');
assert.strictEqual(expGolomb.readBits(1), 0, 'frame_cropping_flag is false');
});
/*
======== A Handy Little QUnit Reference ========
http://api.qunitjs.com/
Test methods:
module(name, {[setup][ ,teardown]})
test(name, callback)
expect(numberOfAssertions)
stop(increment)
start(decrement)
Test assertions:
assert.ok(value, [message])
assert.equal(actual, expected, [message])
assert.notEqual(actual, expected, [message])
assert.deepEqual(actual, expected, [message])
assert.notDeepEqual(actual, expected, [message])
assert.strictEqual(actual, expected, [message])
assert.notStrictEqual(actual, expected, [message])
assert.throws(block, [expected], [message])
*/
var
buffer,
ExpGolomb = require('../lib/utils/exp-golomb'),
expGolomb;
QUnit.module('Exponential Golomb coding');
QUnit.test('small numbers are coded correctly', function(assert) {
var
expected = [
[0xF8, 0],
[0x5F, 1],
[0x7F, 2],
[0x27, 3],
[0x2F, 4],
[0x37, 5],
[0x3F, 6],
[0x11, 7],
[0x13, 8],
[0x15, 9]
],
i = expected.length,
result;
while (i--) {
buffer = new Uint8Array([expected[i][0]]);
expGolomb = new ExpGolomb(buffer);
result = expGolomb.readUnsignedExpGolomb();
assert.equal(expected[i][1], result, expected[i][0] + ' is decoded to ' + expected[i][1]);
}
});
QUnit.test('drops working data as it is parsed', function(assert) {
var expGolomb = new ExpGolomb(new Uint8Array([0x00, 0xFF]));
expGolomb.skipBits(8);
assert.equal(8, expGolomb.bitsAvailable(), '8 bits remain');
assert.equal(0xFF, expGolomb.readBits(8), 'the second byte is read');
});
QUnit.test('drops working data when skipping leading zeros', function(assert) {
var expGolomb = new ExpGolomb(new Uint8Array([0x00, 0x00, 0x00, 0x00, 0xFF]));
assert.equal(32, expGolomb.skipLeadingZeros(), '32 leading zeros are dropped');
assert.equal(8, expGolomb.bitsAvailable(), '8 bits remain');
assert.equal(0xFF, expGolomb.readBits(8), 'the second byte is read');
});
QUnit.test('drops working data when skipping leading zeros', function(assert) {
var expGolomb = new ExpGolomb(new Uint8Array([0x15, 0xab, 0x40, 0xc8, 0xFF]));
assert.equal(3, expGolomb.skipLeadingZeros(), '3 leading zeros are dropped');
assert.equal((8 * 4) + 5, expGolomb.bitsAvailable(), '37 bits remain');
expGolomb.skipBits(1);
assert.equal(0x5a, expGolomb.readBits(8), 'the next bits are read');
});
QUnit.test('skipBits correctly across word-boundaries', function(assert) {
var expGolomb = new ExpGolomb(new Uint8Array([0x15, 0x00, 0x00, 0x28, 0x00, 0x0a, 0x00, 0x00]));
assert.equal(expGolomb.readUnsignedExpGolomb(), 9, 'the first number is read');
expGolomb.skipBits(17);
assert.equal(expGolomb.readUnsignedExpGolomb(), 4, 'the second number is read');
expGolomb.skipBits(13); // Crosses word boundary
assert.equal(expGolomb.readUnsignedExpGolomb(), 4, 'the third number is read');
});
QUnit.test('parses a sequence parameter set', function(assert) {
var
sps = new Uint8Array([
0x27, 0x42, 0xe0, 0x0b,
0xa9, 0x18, 0x60, 0x9d,
0x80, 0x35, 0x06, 0x01,
0x06, 0xb6, 0xc2, 0xb5,
0xef, 0x7c, 0x04
]),
expGolomb = new ExpGolomb(sps);
assert.strictEqual(expGolomb.readBits(8), 0x27, 'the NAL type specifies an SPS');
assert.strictEqual(expGolomb.readBits(8), 66, 'profile_idc is 66');
assert.strictEqual(expGolomb.readBits(4), 0x0E, 'constraints 0-3 are correct');
expGolomb.skipBits(4);
assert.strictEqual(expGolomb.readBits(8), 11, 'level_idc is 11');
assert.strictEqual(expGolomb.readUnsignedExpGolomb(), 0, 'seq_parameter_set_id is 0');
assert.strictEqual(expGolomb.readUnsignedExpGolomb(), 1, 'log2_max_frame_num_minus4 is 1');
assert.strictEqual(expGolomb.readUnsignedExpGolomb(), 0, 'pic_order_cnt_type is 0');
assert.strictEqual(expGolomb.readUnsignedExpGolomb(), 3, 'log2_max_pic_order_cnt_lsb_minus4 is 3');
assert.strictEqual(expGolomb.readUnsignedExpGolomb(), 2, 'max_num_ref_frames is 2');
assert.strictEqual(expGolomb.readBits(1), 0, 'gaps_in_frame_num_value_allowed_flag is false');
assert.strictEqual(expGolomb.readUnsignedExpGolomb(), 11, 'pic_width_in_mbs_minus1 is 11');
assert.strictEqual(expGolomb.readUnsignedExpGolomb(), 8, 'pic_height_in_map_units_minus1 is 8');
assert.strictEqual(expGolomb.readBits(1), 1, 'frame_mbs_only_flag is true');
assert.strictEqual(expGolomb.readBits(1), 1, 'direct_8x8_inference_flag is true');
assert.strictEqual(expGolomb.readBits(1), 0, 'frame_cropping_flag is false');
});

View file

@ -1,75 +1,75 @@
'use strict';
var segments = require('data-files!segments');
var
QUnit = require('qunit'),
probe = require('../lib/m2ts/probe.js'),
testSegment = segments['test-segment.ts'](),
stuffedPesPacket = segments['test-stuffed-pes.ts']();
/**
* All subarray indices verified with the use of thumbcoil.
*/
var patPacket = testSegment.subarray(188, 376);
var pmtPid = 4095;
var programMapTable = {
256: 0x1B,
257: 0x0F
};
var pmtPacket = testSegment.subarray(376, 564);
var pesPacket = testSegment.subarray(564, 752);
var videoPacket = testSegment.subarray(564, 1692);
var videoNoKeyFramePacket = testSegment.subarray(1880, 2820);
var audioPacket = testSegment.subarray(6956, 7144);
var notPusiPacket = testSegment.subarray(1316, 1504);
QUnit.module('M2TS Probe');
QUnit.test('correctly parses packet type', function(assert) {
assert.equal(probe.parseType(patPacket), 'pat', 'parses pat type');
assert.equal(probe.parseType(pmtPacket), null,
'cannot determine type of pmt packet when pmt pid has not been parsed yet');
assert.equal(probe.parseType(pmtPacket, pmtPid), 'pmt', 'parses pmt type');
assert.equal(probe.parseType(pesPacket), null,
'cannot determine type of pes packet when pmt pid has not been parsed yet');
assert.equal(probe.parseType(pesPacket, pmtPid), 'pes', 'parses pes type');
});
QUnit.test('correctly parses pmt pid from pat packet', function(assert) {
assert.equal(probe.parsePat(patPacket), pmtPid, 'parses pmt pid from pat');
});
QUnit.test('correctly parses program map table from pmt packet', function(assert) {
assert.deepEqual(probe.parsePmt(pmtPacket), programMapTable, 'generates correct pmt');
});
QUnit.test('correctly parses payload unit start indicator', function(assert) {
assert.ok(probe.parsePayloadUnitStartIndicator(pesPacket),
'detects payload unit start indicator');
assert.ok(!probe.parsePayloadUnitStartIndicator(notPusiPacket),
'detects no payload unit start indicator');
});
QUnit.test('correctly parses type of pes packet', function(assert) {
assert.equal(probe.parsePesType(videoPacket, programMapTable), 'video',
'parses video pes type');
assert.equal(probe.parsePesType(audioPacket, programMapTable), 'audio',
'parses audio pes type');
});
QUnit.test('correctly parses dts and pts values of pes packet', function(assert) {
var videoPes = probe.parsePesTime(videoPacket);
assert.equal(videoPes.dts, 126000, 'correct dts value');
assert.equal(videoPes.pts, 126000, 'correct pts value');
videoPes = probe.parsePesTime(stuffedPesPacket);
assert.equal(videoPes, null,
'correctly returned null when there is no packet data, only stuffing');
});
QUnit.test('correctly determines if video pes packet contains a key frame', function(assert) {
assert.ok(probe.videoPacketContainsKeyFrame(videoPacket), 'detects key frame in packet');
assert.ok(!probe.videoPacketContainsKeyFrame(videoNoKeyFramePacket),
'detects no key frame in packet');
});
'use strict';
var segments = require('data-files!segments');
var
QUnit = require('qunit'),
probe = require('../lib/m2ts/probe.js'),
testSegment = segments['test-segment.ts'](),
stuffedPesPacket = segments['test-stuffed-pes.ts']();
/**
* All subarray indices verified with the use of thumbcoil.
*/
var patPacket = testSegment.subarray(188, 376);
var pmtPid = 4095;
var programMapTable = {
256: 0x1B,
257: 0x0F
};
var pmtPacket = testSegment.subarray(376, 564);
var pesPacket = testSegment.subarray(564, 752);
var videoPacket = testSegment.subarray(564, 1692);
var videoNoKeyFramePacket = testSegment.subarray(1880, 2820);
var audioPacket = testSegment.subarray(6956, 7144);
var notPusiPacket = testSegment.subarray(1316, 1504);
QUnit.module('M2TS Probe');
QUnit.test('correctly parses packet type', function(assert) {
assert.equal(probe.parseType(patPacket), 'pat', 'parses pat type');
assert.equal(probe.parseType(pmtPacket), null,
'cannot determine type of pmt packet when pmt pid has not been parsed yet');
assert.equal(probe.parseType(pmtPacket, pmtPid), 'pmt', 'parses pmt type');
assert.equal(probe.parseType(pesPacket), null,
'cannot determine type of pes packet when pmt pid has not been parsed yet');
assert.equal(probe.parseType(pesPacket, pmtPid), 'pes', 'parses pes type');
});
QUnit.test('correctly parses pmt pid from pat packet', function(assert) {
assert.equal(probe.parsePat(patPacket), pmtPid, 'parses pmt pid from pat');
});
QUnit.test('correctly parses program map table from pmt packet', function(assert) {
assert.deepEqual(probe.parsePmt(pmtPacket), programMapTable, 'generates correct pmt');
});
QUnit.test('correctly parses payload unit start indicator', function(assert) {
assert.ok(probe.parsePayloadUnitStartIndicator(pesPacket),
'detects payload unit start indicator');
assert.ok(!probe.parsePayloadUnitStartIndicator(notPusiPacket),
'detects no payload unit start indicator');
});
QUnit.test('correctly parses type of pes packet', function(assert) {
assert.equal(probe.parsePesType(videoPacket, programMapTable), 'video',
'parses video pes type');
assert.equal(probe.parsePesType(audioPacket, programMapTable), 'audio',
'parses audio pes type');
});
QUnit.test('correctly parses dts and pts values of pes packet', function(assert) {
var videoPes = probe.parsePesTime(videoPacket);
assert.equal(videoPes.dts, 126000, 'correct dts value');
assert.equal(videoPes.pts, 126000, 'correct pts value');
videoPes = probe.parsePesTime(stuffedPesPacket);
assert.equal(videoPes, null,
'correctly returned null when there is no packet data, only stuffing');
});
QUnit.test('correctly determines if video pes packet contains a key frame', function(assert) {
assert.ok(probe.videoPacketContainsKeyFrame(videoPacket), 'detects key frame in packet');
assert.ok(!probe.videoPacketContainsKeyFrame(videoNoKeyFramePacket),
'detects no key frame in packet');
});

View file

@ -1,11 +1,11 @@
var mp2t, metadataStream;
mp2t = require('../lib/m2ts');
metadataStream = new mp2t.MetadataStream();
self.addEventListener('message', function(e) {
metadataStream.on('data', function(data) {
self.postMessage(data);
});
metadataStream.push(e.data);
});
var mp2t, metadataStream;
mp2t = require('../lib/m2ts');
metadataStream = new mp2t.MetadataStream();
self.addEventListener('message', function(e) {
metadataStream.on('data', function(data) {
self.postMessage(data);
});
metadataStream.push(e.data);
});

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,407 +1,407 @@
'use strict';
var
QUnit = require('qunit'),
probe = require('../lib/mp4/probe'),
mp4Helpers = require('./utils/mp4-helpers'),
box = mp4Helpers.box,
// defined below
moovWithoutMdhd,
moovWithoutTkhd,
moofWithTfdt,
multiMoof,
multiTraf,
noTrunSamples,
v1boxes;
QUnit.module('MP4 Probe');
QUnit.test('reads the timescale from an mdhd', function(assert) {
// sampleMoov has a base timescale of 1000 with an override to 90kHz
// in the mdhd
assert.deepEqual(probe.timescale(new Uint8Array(mp4Helpers.sampleMoov)), {
1: 90e3,
2: 90e3
}, 'found the timescale');
});
QUnit.test('reads tracks', function(assert) {
var tracks = probe.tracks(new Uint8Array(mp4Helpers.sampleMoov));
assert.equal(tracks.length, 2, 'two tracks');
assert.equal(tracks[0].codec, 'avc1.4d400d', 'codec is correct');
assert.equal(tracks[0].id, 1, 'id is correct');
assert.equal(tracks[0].type, 'video', 'type is correct');
assert.equal(tracks[0].timescale, 90e3, 'timescale is correct');
assert.equal(tracks[1].codec, 'mp4a.40.2', 'codec is correct');
assert.equal(tracks[1].id, 2, 'id is correct');
assert.equal(tracks[1].type, 'audio', 'type is correct');
assert.equal(tracks[1].timescale, 90e3, 'timescale is correct');
});
QUnit.test('returns null if the tkhd is missing', function(assert) {
assert.equal(probe.timescale(new Uint8Array(moovWithoutTkhd)), null, 'indicated missing info');
});
QUnit.test('returns null if the mdhd is missing', function(assert) {
assert.equal(probe.timescale(new Uint8Array(moovWithoutMdhd)), null, 'indicated missing info');
});
QUnit.test('startTime reads the base decode time from a tfdt', function(assert) {
assert.equal(probe.startTime({
4: 2
}, new Uint8Array(moofWithTfdt)),
0x01020304 / 2,
'calculated base decode time');
});
QUnit.test('startTime returns the earliest base decode time', function(assert) {
assert.equal(probe.startTime({
4: 2,
6: 1
}, new Uint8Array(multiMoof)),
0x01020304 / 2,
'returned the earlier time');
});
QUnit.test('startTime parses 64-bit base decode times', function(assert) {
assert.equal(probe.startTime({
4: 3
}, new Uint8Array(v1boxes)),
0x0101020304 / 3,
'parsed a long value');
});
QUnit.test('compositionStartTime calculates composition time using composition time' +
'offset from first trun sample', function(assert) {
assert.equal(probe.compositionStartTime({
1: 6,
4: 3
}, new Uint8Array(moofWithTfdt)),
(0x01020304 + 10) / 3,
'calculated correct composition start time');
});
QUnit.test('compositionStartTime looks at only the first traf', function(assert) {
assert.equal(probe.compositionStartTime({
2: 6,
4: 3
}, new Uint8Array(multiTraf)),
(0x01020304 + 10) / 3,
'calculated composition start time from first traf');
});
QUnit.test('compositionStartTime uses default composition time offset of 0' +
'if no trun samples present', function(assert) {
assert.equal(probe.compositionStartTime({
2: 6,
4: 3
}, new Uint8Array(noTrunSamples)),
(0x01020304 + 0) / 3,
'calculated correct composition start time using default offset');
});
QUnit.test('getTimescaleFromMediaHeader gets timescale for version 0 mdhd', function(assert) {
var mdhd = new Uint8Array([
0x00, // version 0
0x00, 0x00, 0x00, // flags
// version 0 has 32 bit creation_time, modification_time, and duration
0x00, 0x00, 0x00, 0x02, // creation_time
0x00, 0x00, 0x00, 0x03, // modification_time
0x00, 0x00, 0x03, 0xe8, // timescale = 1000
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x02, 0x58, // 600 = 0x258 duration
0x15, 0xc7 // 'eng' language
]);
assert.equal(
probe.getTimescaleFromMediaHeader(mdhd),
1000,
'got timescale from version 0 mdhd'
);
});
QUnit.test('getTimescaleFromMediaHeader gets timescale for version 0 mdhd', function(assert) {
var mdhd = new Uint8Array([
0x01, // version 1
0x00, 0x00, 0x00, // flags
// version 1 has 64 bit creation_time, modification_time, and duration
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, // creation_time
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, // modification_time
0x00, 0x00, 0x03, 0xe8, // timescale = 1000
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x58, // 600 = 0x258 duration
0x15, 0xc7 // 'eng' language
]);
assert.equal(
probe.getTimescaleFromMediaHeader(mdhd),
1000,
'got timescale from version 1 mdhd'
);
});
// ---------
// Test Data
// ---------
moovWithoutTkhd =
box('moov',
box('trak',
box('mdia',
box('mdhd',
0x00, // version 0
0x00, 0x00, 0x00, // flags
0x00, 0x00, 0x00, 0x02, // creation_time
0x00, 0x00, 0x00, 0x03, // modification_time
0x00, 0x00, 0x03, 0xe8, // timescale = 1000
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x02, 0x58, // 600 = 0x258 duration
0x15, 0xc7, // 'eng' language
0x00, 0x00),
box('hdlr',
0x00, // version 1
0x00, 0x00, 0x00, // flags
0x00, 0x00, 0x00, 0x00, // pre_defined
mp4Helpers.typeBytes('vide'), // handler_type
0x00, 0x00, 0x00, 0x00, // reserved
0x00, 0x00, 0x00, 0x00, // reserved
0x00, 0x00, 0x00, 0x00, // reserved
mp4Helpers.typeBytes('one'), 0x00)))); // name
moovWithoutMdhd =
box('moov',
box('trak',
box('tkhd',
0x01, // version 1
0x00, 0x00, 0x00, // flags
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x02, // creation_time
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x03, // modification_time
0x00, 0x00, 0x00, 0x01, // track_ID
0x00, 0x00, 0x00, 0x00, // reserved
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x02, 0x58, // 600 = 0x258 duration
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, // reserved
0x00, 0x00, // layer
0x00, 0x00, // alternate_group
0x00, 0x00, // non-audio track volume
0x00, 0x00, // reserved
mp4Helpers.unityMatrix,
0x01, 0x2c, 0x00, 0x00, // 300 in 16.16 fixed-point
0x00, 0x96, 0x00, 0x00), // 150 in 16.16 fixed-point
box('mdia',
box('hdlr',
0x01, // version 1
0x00, 0x00, 0x00, // flags
0x00, 0x00, 0x00, 0x00, // pre_defined
mp4Helpers.typeBytes('vide'), // handler_type
0x00, 0x00, 0x00, 0x00, // reserved
0x00, 0x00, 0x00, 0x00, // reserved
0x00, 0x00, 0x00, 0x00, // reserved
mp4Helpers.typeBytes('one'), 0x00)))); // name
moofWithTfdt =
box('moof',
box('mfhd',
0x00, // version
0x00, 0x00, 0x00, // flags
0x00, 0x00, 0x00, 0x04), // sequence_number
box('traf',
box('tfhd',
0x00, // version
0x00, 0x00, 0x3b, // flags
0x00, 0x00, 0x00, 0x04, // track_ID = 4
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x01, // base_data_offset
0x00, 0x00, 0x00, 0x02, // sample_description_index
0x00, 0x00, 0x00, 0x03, // default_sample_duration,
0x00, 0x00, 0x00, 0x04, // default_sample_size
0x00, 0x00, 0x00, 0x05),
box('tfdt',
0x00, // version
0x00, 0x00, 0x00, // flags
0x01, 0x02, 0x03, 0x04), // baseMediaDecodeTime
box('trun',
0x00, // version
0x00, 0x0f, 0x01, // flags: dataOffsetPresent, sampleDurationPresent,
// sampleSizePresent, sampleFlagsPresent,
// sampleCompositionTimeOffsetsPresent
0x00, 0x00, 0x00, 0x02, // sample_count
0x00, 0x00, 0x00, 0x00, // data_offset, no first_sample_flags
// sample 1
0x00, 0x00, 0x00, 0x0a, // sample_duration = 10
0x00, 0x00, 0x00, 0x0a, // sample_size = 10
0x00, 0x00, 0x00, 0x00, // sample_flags
0x00, 0x00, 0x00, 0x0a, // signed sample_composition_time_offset = 10
// sample 2
0x00, 0x00, 0x00, 0x0a, // sample_duration = 10
0x00, 0x00, 0x00, 0x0a, // sample_size = 10
0x00, 0x00, 0x00, 0x00, // sample_flags
0x00, 0x00, 0x00, 0x14))); // signed sample_composition_time_offset = 20
noTrunSamples =
box('moof',
box('mfhd',
0x00, // version
0x00, 0x00, 0x00, // flags
0x00, 0x00, 0x00, 0x04), // sequence_number
box('traf',
box('tfhd',
0x00, // version
0x00, 0x00, 0x3b, // flags
0x00, 0x00, 0x00, 0x04, // track_ID = 4
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x01, // base_data_offset
0x00, 0x00, 0x00, 0x02, // sample_description_index
0x00, 0x00, 0x00, 0x03, // default_sample_duration,
0x00, 0x00, 0x00, 0x04, // default_sample_size
0x00, 0x00, 0x00, 0x05),
box('tfdt',
0x00, // version
0x00, 0x00, 0x00, // flags
0x01, 0x02, 0x03, 0x04), // baseMediaDecodeTime
box('trun',
0x00, // version
0x00, 0x0f, 0x01, // flags: dataOffsetPresent, sampleDurationPresent,
// sampleSizePresent, sampleFlagsPresent,
// sampleCompositionTimeOffsetsPresent
0x00, 0x00, 0x00, 0x00, // sample_count
0x00, 0x00, 0x00, 0x00))); // data_offset, no first_sample_flags
multiTraf =
box('moof',
box('mfhd',
0x00, // version
0x00, 0x00, 0x00, // flags
0x00, 0x00, 0x00, 0x04), // sequence_number
box('traf',
box('tfhd',
0x00, // version
0x00, 0x00, 0x3b, // flags
0x00, 0x00, 0x00, 0x04, // track_ID = 4
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x01, // base_data_offset
0x00, 0x00, 0x00, 0x02, // sample_description_index
0x00, 0x00, 0x00, 0x03, // default_sample_duration,
0x00, 0x00, 0x00, 0x04, // default_sample_size
0x00, 0x00, 0x00, 0x05),
box('tfdt',
0x00, // version
0x00, 0x00, 0x00, // flags
0x01, 0x02, 0x03, 0x04), // baseMediaDecodeTime
box('trun',
0x00, // version
0x00, 0x0f, 0x01, // flags: dataOffsetPresent, sampleDurationPresent,
// sampleSizePresent, sampleFlagsPresent,
// sampleCompositionTimeOffsetsPresent
0x00, 0x00, 0x00, 0x02, // sample_count
0x00, 0x00, 0x00, 0x00, // data_offset, no first_sample_flags
// sample 1
0x00, 0x00, 0x00, 0x0a, // sample_duration = 10
0x00, 0x00, 0x00, 0x0a, // sample_size = 10
0x00, 0x00, 0x00, 0x00, // sample_flags
0x00, 0x00, 0x00, 0x0a, // signed sample_composition_time_offset = 10
// sample 2
0x00, 0x00, 0x00, 0x0a, // sample_duration = 10
0x00, 0x00, 0x00, 0x0a, // sample_size = 10
0x00, 0x00, 0x00, 0x00, // sample_flags
0x00, 0x00, 0x00, 0x14)), // signed sample_composition_time_offset = 20
box('traf',
box('tfhd',
0x00, // version
0x00, 0x00, 0x3b, // flags
0x00, 0x00, 0x00, 0x02, // track_ID = 2
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x01, // base_data_offset
0x00, 0x00, 0x00, 0x02, // sample_description_index
0x00, 0x00, 0x00, 0x03, // default_sample_duration,
0x00, 0x00, 0x00, 0x04, // default_sample_size
0x00, 0x00, 0x00, 0x05),
box('tfdt',
0x00, // version
0x00, 0x00, 0x00, // flags
0x01, 0x02, 0x01, 0x02), // baseMediaDecodeTime
box('trun',
0x00, // version
0x00, 0x0f, 0x01, // flags: dataOffsetPresent, sampleDurationPresent,
// sampleSizePresent, sampleFlagsPresent,
// sampleCompositionTimeOffsetsPresent
0x00, 0x00, 0x00, 0x02, // sample_count
0x00, 0x00, 0x00, 0x00, // data_offset, no first_sample_flags
// sample 1
0x00, 0x00, 0x00, 0x0a, // sample_duration = 10
0x00, 0x00, 0x00, 0x0a, // sample_size = 10
0x00, 0x00, 0x00, 0x00, // sample_flags
0x00, 0x00, 0x00, 0x0b, // signed sample_composition_time_offset = 11
// sample 2
0x00, 0x00, 0x00, 0x0a, // sample_duration = 10
0x00, 0x00, 0x00, 0x0a, // sample_size = 10
0x00, 0x00, 0x00, 0x00, // sample_flags
0x00, 0x00, 0x00, 0x05))); // signed sample_composition_time_offset = 5
multiMoof = moofWithTfdt
.concat(box('moof',
box('mfhd',
0x00, // version
0x00, 0x00, 0x00, // flags
0x00, 0x00, 0x00, 0x04), // sequence_number
box('traf',
box('tfhd',
0x00, // version
0x00, 0x00, 0x3b, // flags
0x00, 0x00, 0x00, 0x06, // track_ID = 6
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x01, // base_data_offset
0x00, 0x00, 0x00, 0x02, // sample_description_index
0x00, 0x00, 0x00, 0x03, // default_sample_duration,
0x00, 0x00, 0x00, 0x04, // default_sample_size
0x00, 0x00, 0x00, 0x05),
box('tfdt',
0x00, // version
0x00, 0x00, 0x00, // flags
0x01, 0x02, 0x03, 0x04), // baseMediaDecodeTime
box('trun',
0x00, // version
0x00, 0x0f, 0x01, // flags: dataOffsetPresent, sampleDurationPresent,
// sampleSizePresent, sampleFlagsPresent,
// sampleCompositionTimeOffsetsPresent
0x00, 0x00, 0x00, 0x02, // sample_count
0x00, 0x00, 0x00, 0x00, // data_offset, no first_sample_flags
// sample 1
0x00, 0x00, 0x00, 0x0a, // sample_duration = 10
0x00, 0x00, 0x00, 0x0a, // sample_size = 10
0x00, 0x00, 0x00, 0x00, // sample_flags
0x00, 0x00, 0x00, 0x14, // signed sample_composition_time_offset = 20
// sample 2
0x00, 0x00, 0x00, 0x0a, // sample_duration = 10
0x00, 0x00, 0x00, 0x0a, // sample_size = 10
0x00, 0x00, 0x00, 0x00, // sample_flags
0x00, 0x00, 0x00, 0x0a)))); // signed sample_composition_time_offset = 10
v1boxes =
box('moof',
box('mfhd',
0x01, // version
0x00, 0x00, 0x00, // flags
0x00, 0x00, 0x00, 0x04), // sequence_number
box('traf',
box('tfhd',
0x01, // version
0x00, 0x00, 0x3b, // flags
0x00, 0x00, 0x00, 0x04, // track_ID = 4
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x01, // base_data_offset
0x00, 0x00, 0x00, 0x02, // sample_description_index
0x00, 0x00, 0x00, 0x03, // default_sample_duration,
0x00, 0x00, 0x00, 0x04, // default_sample_size
0x00, 0x00, 0x00, 0x05),
box('tfdt',
0x01, // version
0x00, 0x00, 0x00, // flags
0x00, 0x00, 0x00, 0x01,
0x01, 0x02, 0x03, 0x04))); // baseMediaDecodeTime
'use strict';
var
QUnit = require('qunit'),
probe = require('../lib/mp4/probe'),
mp4Helpers = require('./utils/mp4-helpers'),
box = mp4Helpers.box,
// defined below
moovWithoutMdhd,
moovWithoutTkhd,
moofWithTfdt,
multiMoof,
multiTraf,
noTrunSamples,
v1boxes;
QUnit.module('MP4 Probe');
QUnit.test('reads the timescale from an mdhd', function(assert) {
// sampleMoov has a base timescale of 1000 with an override to 90kHz
// in the mdhd
assert.deepEqual(probe.timescale(new Uint8Array(mp4Helpers.sampleMoov)), {
1: 90e3,
2: 90e3
}, 'found the timescale');
});
QUnit.test('reads tracks', function(assert) {
var tracks = probe.tracks(new Uint8Array(mp4Helpers.sampleMoov));
assert.equal(tracks.length, 2, 'two tracks');
assert.equal(tracks[0].codec, 'avc1.4d400d', 'codec is correct');
assert.equal(tracks[0].id, 1, 'id is correct');
assert.equal(tracks[0].type, 'video', 'type is correct');
assert.equal(tracks[0].timescale, 90e3, 'timescale is correct');
assert.equal(tracks[1].codec, 'mp4a.40.2', 'codec is correct');
assert.equal(tracks[1].id, 2, 'id is correct');
assert.equal(tracks[1].type, 'audio', 'type is correct');
assert.equal(tracks[1].timescale, 90e3, 'timescale is correct');
});
QUnit.test('returns null if the tkhd is missing', function(assert) {
assert.equal(probe.timescale(new Uint8Array(moovWithoutTkhd)), null, 'indicated missing info');
});
QUnit.test('returns null if the mdhd is missing', function(assert) {
assert.equal(probe.timescale(new Uint8Array(moovWithoutMdhd)), null, 'indicated missing info');
});
QUnit.test('startTime reads the base decode time from a tfdt', function(assert) {
assert.equal(probe.startTime({
4: 2
}, new Uint8Array(moofWithTfdt)),
0x01020304 / 2,
'calculated base decode time');
});
QUnit.test('startTime returns the earliest base decode time', function(assert) {
assert.equal(probe.startTime({
4: 2,
6: 1
}, new Uint8Array(multiMoof)),
0x01020304 / 2,
'returned the earlier time');
});
QUnit.test('startTime parses 64-bit base decode times', function(assert) {
assert.equal(probe.startTime({
4: 3
}, new Uint8Array(v1boxes)),
0x0101020304 / 3,
'parsed a long value');
});
QUnit.test('compositionStartTime calculates composition time using composition time' +
'offset from first trun sample', function(assert) {
assert.equal(probe.compositionStartTime({
1: 6,
4: 3
}, new Uint8Array(moofWithTfdt)),
(0x01020304 + 10) / 3,
'calculated correct composition start time');
});
QUnit.test('compositionStartTime looks at only the first traf', function(assert) {
assert.equal(probe.compositionStartTime({
2: 6,
4: 3
}, new Uint8Array(multiTraf)),
(0x01020304 + 10) / 3,
'calculated composition start time from first traf');
});
QUnit.test('compositionStartTime uses default composition time offset of 0' +
'if no trun samples present', function(assert) {
assert.equal(probe.compositionStartTime({
2: 6,
4: 3
}, new Uint8Array(noTrunSamples)),
(0x01020304 + 0) / 3,
'calculated correct composition start time using default offset');
});
QUnit.test('getTimescaleFromMediaHeader gets timescale for version 0 mdhd', function(assert) {
var mdhd = new Uint8Array([
0x00, // version 0
0x00, 0x00, 0x00, // flags
// version 0 has 32 bit creation_time, modification_time, and duration
0x00, 0x00, 0x00, 0x02, // creation_time
0x00, 0x00, 0x00, 0x03, // modification_time
0x00, 0x00, 0x03, 0xe8, // timescale = 1000
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x02, 0x58, // 600 = 0x258 duration
0x15, 0xc7 // 'eng' language
]);
assert.equal(
probe.getTimescaleFromMediaHeader(mdhd),
1000,
'got timescale from version 0 mdhd'
);
});
QUnit.test('getTimescaleFromMediaHeader gets timescale for version 0 mdhd', function(assert) {
var mdhd = new Uint8Array([
0x01, // version 1
0x00, 0x00, 0x00, // flags
// version 1 has 64 bit creation_time, modification_time, and duration
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, // creation_time
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, // modification_time
0x00, 0x00, 0x03, 0xe8, // timescale = 1000
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x58, // 600 = 0x258 duration
0x15, 0xc7 // 'eng' language
]);
assert.equal(
probe.getTimescaleFromMediaHeader(mdhd),
1000,
'got timescale from version 1 mdhd'
);
});
// ---------
// Test Data
// ---------
moovWithoutTkhd =
box('moov',
box('trak',
box('mdia',
box('mdhd',
0x00, // version 0
0x00, 0x00, 0x00, // flags
0x00, 0x00, 0x00, 0x02, // creation_time
0x00, 0x00, 0x00, 0x03, // modification_time
0x00, 0x00, 0x03, 0xe8, // timescale = 1000
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x02, 0x58, // 600 = 0x258 duration
0x15, 0xc7, // 'eng' language
0x00, 0x00),
box('hdlr',
0x00, // version 1
0x00, 0x00, 0x00, // flags
0x00, 0x00, 0x00, 0x00, // pre_defined
mp4Helpers.typeBytes('vide'), // handler_type
0x00, 0x00, 0x00, 0x00, // reserved
0x00, 0x00, 0x00, 0x00, // reserved
0x00, 0x00, 0x00, 0x00, // reserved
mp4Helpers.typeBytes('one'), 0x00)))); // name
moovWithoutMdhd =
box('moov',
box('trak',
box('tkhd',
0x01, // version 1
0x00, 0x00, 0x00, // flags
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x02, // creation_time
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x03, // modification_time
0x00, 0x00, 0x00, 0x01, // track_ID
0x00, 0x00, 0x00, 0x00, // reserved
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x02, 0x58, // 600 = 0x258 duration
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, // reserved
0x00, 0x00, // layer
0x00, 0x00, // alternate_group
0x00, 0x00, // non-audio track volume
0x00, 0x00, // reserved
mp4Helpers.unityMatrix,
0x01, 0x2c, 0x00, 0x00, // 300 in 16.16 fixed-point
0x00, 0x96, 0x00, 0x00), // 150 in 16.16 fixed-point
box('mdia',
box('hdlr',
0x01, // version 1
0x00, 0x00, 0x00, // flags
0x00, 0x00, 0x00, 0x00, // pre_defined
mp4Helpers.typeBytes('vide'), // handler_type
0x00, 0x00, 0x00, 0x00, // reserved
0x00, 0x00, 0x00, 0x00, // reserved
0x00, 0x00, 0x00, 0x00, // reserved
mp4Helpers.typeBytes('one'), 0x00)))); // name
moofWithTfdt =
box('moof',
box('mfhd',
0x00, // version
0x00, 0x00, 0x00, // flags
0x00, 0x00, 0x00, 0x04), // sequence_number
box('traf',
box('tfhd',
0x00, // version
0x00, 0x00, 0x3b, // flags
0x00, 0x00, 0x00, 0x04, // track_ID = 4
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x01, // base_data_offset
0x00, 0x00, 0x00, 0x02, // sample_description_index
0x00, 0x00, 0x00, 0x03, // default_sample_duration,
0x00, 0x00, 0x00, 0x04, // default_sample_size
0x00, 0x00, 0x00, 0x05),
box('tfdt',
0x00, // version
0x00, 0x00, 0x00, // flags
0x01, 0x02, 0x03, 0x04), // baseMediaDecodeTime
box('trun',
0x00, // version
0x00, 0x0f, 0x01, // flags: dataOffsetPresent, sampleDurationPresent,
// sampleSizePresent, sampleFlagsPresent,
// sampleCompositionTimeOffsetsPresent
0x00, 0x00, 0x00, 0x02, // sample_count
0x00, 0x00, 0x00, 0x00, // data_offset, no first_sample_flags
// sample 1
0x00, 0x00, 0x00, 0x0a, // sample_duration = 10
0x00, 0x00, 0x00, 0x0a, // sample_size = 10
0x00, 0x00, 0x00, 0x00, // sample_flags
0x00, 0x00, 0x00, 0x0a, // signed sample_composition_time_offset = 10
// sample 2
0x00, 0x00, 0x00, 0x0a, // sample_duration = 10
0x00, 0x00, 0x00, 0x0a, // sample_size = 10
0x00, 0x00, 0x00, 0x00, // sample_flags
0x00, 0x00, 0x00, 0x14))); // signed sample_composition_time_offset = 20
noTrunSamples =
box('moof',
box('mfhd',
0x00, // version
0x00, 0x00, 0x00, // flags
0x00, 0x00, 0x00, 0x04), // sequence_number
box('traf',
box('tfhd',
0x00, // version
0x00, 0x00, 0x3b, // flags
0x00, 0x00, 0x00, 0x04, // track_ID = 4
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x01, // base_data_offset
0x00, 0x00, 0x00, 0x02, // sample_description_index
0x00, 0x00, 0x00, 0x03, // default_sample_duration,
0x00, 0x00, 0x00, 0x04, // default_sample_size
0x00, 0x00, 0x00, 0x05),
box('tfdt',
0x00, // version
0x00, 0x00, 0x00, // flags
0x01, 0x02, 0x03, 0x04), // baseMediaDecodeTime
box('trun',
0x00, // version
0x00, 0x0f, 0x01, // flags: dataOffsetPresent, sampleDurationPresent,
// sampleSizePresent, sampleFlagsPresent,
// sampleCompositionTimeOffsetsPresent
0x00, 0x00, 0x00, 0x00, // sample_count
0x00, 0x00, 0x00, 0x00))); // data_offset, no first_sample_flags
multiTraf =
box('moof',
box('mfhd',
0x00, // version
0x00, 0x00, 0x00, // flags
0x00, 0x00, 0x00, 0x04), // sequence_number
box('traf',
box('tfhd',
0x00, // version
0x00, 0x00, 0x3b, // flags
0x00, 0x00, 0x00, 0x04, // track_ID = 4
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x01, // base_data_offset
0x00, 0x00, 0x00, 0x02, // sample_description_index
0x00, 0x00, 0x00, 0x03, // default_sample_duration,
0x00, 0x00, 0x00, 0x04, // default_sample_size
0x00, 0x00, 0x00, 0x05),
box('tfdt',
0x00, // version
0x00, 0x00, 0x00, // flags
0x01, 0x02, 0x03, 0x04), // baseMediaDecodeTime
box('trun',
0x00, // version
0x00, 0x0f, 0x01, // flags: dataOffsetPresent, sampleDurationPresent,
// sampleSizePresent, sampleFlagsPresent,
// sampleCompositionTimeOffsetsPresent
0x00, 0x00, 0x00, 0x02, // sample_count
0x00, 0x00, 0x00, 0x00, // data_offset, no first_sample_flags
// sample 1
0x00, 0x00, 0x00, 0x0a, // sample_duration = 10
0x00, 0x00, 0x00, 0x0a, // sample_size = 10
0x00, 0x00, 0x00, 0x00, // sample_flags
0x00, 0x00, 0x00, 0x0a, // signed sample_composition_time_offset = 10
// sample 2
0x00, 0x00, 0x00, 0x0a, // sample_duration = 10
0x00, 0x00, 0x00, 0x0a, // sample_size = 10
0x00, 0x00, 0x00, 0x00, // sample_flags
0x00, 0x00, 0x00, 0x14)), // signed sample_composition_time_offset = 20
box('traf',
box('tfhd',
0x00, // version
0x00, 0x00, 0x3b, // flags
0x00, 0x00, 0x00, 0x02, // track_ID = 2
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x01, // base_data_offset
0x00, 0x00, 0x00, 0x02, // sample_description_index
0x00, 0x00, 0x00, 0x03, // default_sample_duration,
0x00, 0x00, 0x00, 0x04, // default_sample_size
0x00, 0x00, 0x00, 0x05),
box('tfdt',
0x00, // version
0x00, 0x00, 0x00, // flags
0x01, 0x02, 0x01, 0x02), // baseMediaDecodeTime
box('trun',
0x00, // version
0x00, 0x0f, 0x01, // flags: dataOffsetPresent, sampleDurationPresent,
// sampleSizePresent, sampleFlagsPresent,
// sampleCompositionTimeOffsetsPresent
0x00, 0x00, 0x00, 0x02, // sample_count
0x00, 0x00, 0x00, 0x00, // data_offset, no first_sample_flags
// sample 1
0x00, 0x00, 0x00, 0x0a, // sample_duration = 10
0x00, 0x00, 0x00, 0x0a, // sample_size = 10
0x00, 0x00, 0x00, 0x00, // sample_flags
0x00, 0x00, 0x00, 0x0b, // signed sample_composition_time_offset = 11
// sample 2
0x00, 0x00, 0x00, 0x0a, // sample_duration = 10
0x00, 0x00, 0x00, 0x0a, // sample_size = 10
0x00, 0x00, 0x00, 0x00, // sample_flags
0x00, 0x00, 0x00, 0x05))); // signed sample_composition_time_offset = 5
multiMoof = moofWithTfdt
.concat(box('moof',
box('mfhd',
0x00, // version
0x00, 0x00, 0x00, // flags
0x00, 0x00, 0x00, 0x04), // sequence_number
box('traf',
box('tfhd',
0x00, // version
0x00, 0x00, 0x3b, // flags
0x00, 0x00, 0x00, 0x06, // track_ID = 6
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x01, // base_data_offset
0x00, 0x00, 0x00, 0x02, // sample_description_index
0x00, 0x00, 0x00, 0x03, // default_sample_duration,
0x00, 0x00, 0x00, 0x04, // default_sample_size
0x00, 0x00, 0x00, 0x05),
box('tfdt',
0x00, // version
0x00, 0x00, 0x00, // flags
0x01, 0x02, 0x03, 0x04), // baseMediaDecodeTime
box('trun',
0x00, // version
0x00, 0x0f, 0x01, // flags: dataOffsetPresent, sampleDurationPresent,
// sampleSizePresent, sampleFlagsPresent,
// sampleCompositionTimeOffsetsPresent
0x00, 0x00, 0x00, 0x02, // sample_count
0x00, 0x00, 0x00, 0x00, // data_offset, no first_sample_flags
// sample 1
0x00, 0x00, 0x00, 0x0a, // sample_duration = 10
0x00, 0x00, 0x00, 0x0a, // sample_size = 10
0x00, 0x00, 0x00, 0x00, // sample_flags
0x00, 0x00, 0x00, 0x14, // signed sample_composition_time_offset = 20
// sample 2
0x00, 0x00, 0x00, 0x0a, // sample_duration = 10
0x00, 0x00, 0x00, 0x0a, // sample_size = 10
0x00, 0x00, 0x00, 0x00, // sample_flags
0x00, 0x00, 0x00, 0x0a)))); // signed sample_composition_time_offset = 10
v1boxes =
box('moof',
box('mfhd',
0x01, // version
0x00, 0x00, 0x00, // flags
0x00, 0x00, 0x00, 0x04), // sequence_number
box('traf',
box('tfhd',
0x01, // version
0x00, 0x00, 0x3b, // flags
0x00, 0x00, 0x00, 0x04, // track_ID = 4
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x01, // base_data_offset
0x00, 0x00, 0x00, 0x02, // sample_description_index
0x00, 0x00, 0x00, 0x03, // default_sample_duration,
0x00, 0x00, 0x00, 0x04, // default_sample_size
0x00, 0x00, 0x00, 0x05),
box('tfdt',
0x01, // version
0x00, 0x00, 0x00, // flags
0x00, 0x00, 0x00, 0x01,
0x01, 0x02, 0x03, 0x04))); // baseMediaDecodeTime

View file

@ -1,142 +1,142 @@
var Transmuxer = require('../lib/partial/transmuxer.js');
var utils = require('./utils');
var generatePMT = utils.generatePMT;
var videoPes = utils.videoPes;
var audioPes = utils.audioPes;
var packetize = utils.packetize;
var PAT = utils.PAT;
QUnit.module('Partial Transmuxer - Options');
[
{options: {keepOriginalTimestamps: false}},
{options: {keepOriginalTimestamps: true}},
{options: {keepOriginalTimestamps: false, baseMediaDecodeTime: 15000}},
{options: {keepOriginalTimestamps: true, baseMediaDecodeTime: 15000}},
{options: {keepOriginalTimestamps: false}, baseMediaSetter: 15000},
{options: {keepOriginalTimestamps: true}, baseMediaSetter: 15000}
].forEach(function(test) {
var createTransmuxer = function() {
var transmuxer = new Transmuxer(test.options);
if (test.baseMediaSetter) {
transmuxer.setBaseMediaDecodeTime(test.baseMediaSetter);
}
return transmuxer;
};
var name = '';
Object.keys(test.options).forEach(function(optionName) {
name += '' + optionName + ' ' + test.options[optionName] + ' ';
});
if (test.baseMediaSetter) {
name += 'baseMediaDecodeTime setter ' + test.baseMediaSetter;
}
QUnit.test('Audio frames after video not trimmed, ' + name, function(assert) {
var
segments = [],
earliestDts = 15000,
transmuxer = createTransmuxer();
transmuxer.on('data', function(segment) {
segments.push(segment);
});
// the following transmuxer pushes add tiny video and
// audio data to the transmuxer. When we add the data
// we also set the pts/dts time so that audio should
// not be trimmed.
transmuxer.push(packetize(PAT));
transmuxer.push(packetize(generatePMT({
hasVideo: true,
hasAudio: true
})));
transmuxer.push(packetize(audioPes([
0x19, 0x47
], true, earliestDts + 1)));
transmuxer.push(packetize(videoPes([
0x09, 0x01 // access_unit_delimiter_rbsp
], true, earliestDts)));
transmuxer.push(packetize(videoPes([
0x08, 0x01 // pic_parameter_set_rbsp
], true, earliestDts)));
transmuxer.push(packetize(videoPes([
0x07, // seq_parameter_set_rbsp
0x27, 0x42, 0xe0, 0x0b,
0xa9, 0x18, 0x60, 0x9d,
0x80, 0x53, 0x06, 0x01,
0x06, 0xb6, 0xc2, 0xb5,
0xef, 0x7c, 0x04
], false, earliestDts)));
transmuxer.push(packetize(videoPes([
0x05, 0x01 // slice_layer_without_partitioning_rbsp_idr
], true, earliestDts)));
transmuxer.flush();
// the partial transmuxer only generates a video segment
// when all audio frames are trimmed. So we should have an audio and video
// segment
assert.equal(segments.length, 2, 'generated a video and an audio segment');
assert.equal(segments[0].type, 'video', 'video segment exists');
assert.equal(segments[1].type, 'audio', 'audio segment exists');
});
QUnit.test('Audio frames trimmed before video, ' + name, function(assert) {
var
segments = [],
earliestDts = 15000,
baseTime = test.options.baseMediaDecodeTime || test.baseMediaSetter || 0,
transmuxer = createTransmuxer();
transmuxer.on('data', function(segment) {
segments.push(segment);
});
// the following transmuxer pushes add tiny video and
// audio data to the transmuxer. When we add the data
// we also set the pts/dts time so that audio should
// be trimmed.
transmuxer.push(packetize(PAT));
transmuxer.push(packetize(generatePMT({
hasVideo: true,
hasAudio: true
})));
transmuxer.push(packetize(audioPes([
0x19, 0x47
], true, earliestDts - baseTime - 1)));
transmuxer.push(packetize(videoPes([
0x09, 0x01 // access_unit_delimiter_rbsp
], true, earliestDts)));
transmuxer.push(packetize(videoPes([
0x08, 0x01 // pic_parameter_set_rbsp
], true, earliestDts)));
transmuxer.push(packetize(videoPes([
0x07, // seq_parameter_set_rbsp
0x27, 0x42, 0xe0, 0x0b,
0xa9, 0x18, 0x60, 0x9d,
0x80, 0x53, 0x06, 0x01,
0x06, 0xb6, 0xc2, 0xb5,
0xef, 0x7c, 0x04
], false, earliestDts)));
transmuxer.push(packetize(videoPes([
0x05, 0x01 // slice_layer_without_partitioning_rbsp_idr
], true, earliestDts)));
transmuxer.flush();
// the partial transmuxer only generates a video segment
// when all audio frames are trimmed.
if (test.options.keepOriginalTimestamps && !baseTime) {
assert.equal(segments.length, 2, 'generated both a video/audio segment');
assert.equal(segments[0].type, 'video', 'segment is video');
assert.equal(segments[1].type, 'audio', 'segment is audio');
} else {
assert.equal(segments.length, 1, 'generated only a video segment');
assert.equal(segments[0].type, 'video', 'segment is video');
}
});
});
var Transmuxer = require('../lib/partial/transmuxer.js');
var utils = require('./utils');
var generatePMT = utils.generatePMT;
var videoPes = utils.videoPes;
var audioPes = utils.audioPes;
var packetize = utils.packetize;
var PAT = utils.PAT;
QUnit.module('Partial Transmuxer - Options');
[
{options: {keepOriginalTimestamps: false}},
{options: {keepOriginalTimestamps: true}},
{options: {keepOriginalTimestamps: false, baseMediaDecodeTime: 15000}},
{options: {keepOriginalTimestamps: true, baseMediaDecodeTime: 15000}},
{options: {keepOriginalTimestamps: false}, baseMediaSetter: 15000},
{options: {keepOriginalTimestamps: true}, baseMediaSetter: 15000}
].forEach(function(test) {
var createTransmuxer = function() {
var transmuxer = new Transmuxer(test.options);
if (test.baseMediaSetter) {
transmuxer.setBaseMediaDecodeTime(test.baseMediaSetter);
}
return transmuxer;
};
var name = '';
Object.keys(test.options).forEach(function(optionName) {
name += '' + optionName + ' ' + test.options[optionName] + ' ';
});
if (test.baseMediaSetter) {
name += 'baseMediaDecodeTime setter ' + test.baseMediaSetter;
}
QUnit.test('Audio frames after video not trimmed, ' + name, function(assert) {
var
segments = [],
earliestDts = 15000,
transmuxer = createTransmuxer();
transmuxer.on('data', function(segment) {
segments.push(segment);
});
// the following transmuxer pushes add tiny video and
// audio data to the transmuxer. When we add the data
// we also set the pts/dts time so that audio should
// not be trimmed.
transmuxer.push(packetize(PAT));
transmuxer.push(packetize(generatePMT({
hasVideo: true,
hasAudio: true
})));
transmuxer.push(packetize(audioPes([
0x19, 0x47
], true, earliestDts + 1)));
transmuxer.push(packetize(videoPes([
0x09, 0x01 // access_unit_delimiter_rbsp
], true, earliestDts)));
transmuxer.push(packetize(videoPes([
0x08, 0x01 // pic_parameter_set_rbsp
], true, earliestDts)));
transmuxer.push(packetize(videoPes([
0x07, // seq_parameter_set_rbsp
0x27, 0x42, 0xe0, 0x0b,
0xa9, 0x18, 0x60, 0x9d,
0x80, 0x53, 0x06, 0x01,
0x06, 0xb6, 0xc2, 0xb5,
0xef, 0x7c, 0x04
], false, earliestDts)));
transmuxer.push(packetize(videoPes([
0x05, 0x01 // slice_layer_without_partitioning_rbsp_idr
], true, earliestDts)));
transmuxer.flush();
// the partial transmuxer only generates a video segment
// when all audio frames are trimmed. So we should have an audio and video
// segment
assert.equal(segments.length, 2, 'generated a video and an audio segment');
assert.equal(segments[0].type, 'video', 'video segment exists');
assert.equal(segments[1].type, 'audio', 'audio segment exists');
});
QUnit.test('Audio frames trimmed before video, ' + name, function(assert) {
var
segments = [],
earliestDts = 15000,
baseTime = test.options.baseMediaDecodeTime || test.baseMediaSetter || 0,
transmuxer = createTransmuxer();
transmuxer.on('data', function(segment) {
segments.push(segment);
});
// the following transmuxer pushes add tiny video and
// audio data to the transmuxer. When we add the data
// we also set the pts/dts time so that audio should
// be trimmed.
transmuxer.push(packetize(PAT));
transmuxer.push(packetize(generatePMT({
hasVideo: true,
hasAudio: true
})));
transmuxer.push(packetize(audioPes([
0x19, 0x47
], true, earliestDts - baseTime - 1)));
transmuxer.push(packetize(videoPes([
0x09, 0x01 // access_unit_delimiter_rbsp
], true, earliestDts)));
transmuxer.push(packetize(videoPes([
0x08, 0x01 // pic_parameter_set_rbsp
], true, earliestDts)));
transmuxer.push(packetize(videoPes([
0x07, // seq_parameter_set_rbsp
0x27, 0x42, 0xe0, 0x0b,
0xa9, 0x18, 0x60, 0x9d,
0x80, 0x53, 0x06, 0x01,
0x06, 0xb6, 0xc2, 0xb5,
0xef, 0x7c, 0x04
], false, earliestDts)));
transmuxer.push(packetize(videoPes([
0x05, 0x01 // slice_layer_without_partitioning_rbsp_idr
], true, earliestDts)));
transmuxer.flush();
// the partial transmuxer only generates a video segment
// when all audio frames are trimmed.
if (test.options.keepOriginalTimestamps && !baseTime) {
assert.equal(segments.length, 2, 'generated both a video/audio segment');
assert.equal(segments[0].type, 'video', 'segment is video');
assert.equal(segments[1].type, 'audio', 'segment is audio');
} else {
assert.equal(segments.length, 1, 'generated only a video segment');
assert.equal(segments[0].type, 'video', 'segment is video');
}
});
});

View file

@ -1,48 +1,48 @@
'use strict';
var
stream,
Stream = require('../lib/utils/stream'),
QUnit = require('qunit');
QUnit.module('Stream', {
beforeEach: function() {
stream = new Stream();
stream.init();
}
});
QUnit.test('trigger calls listeners', function(assert) {
var args = [];
stream.on('test', function(data) {
args.push(data);
});
stream.trigger('test', 1);
stream.trigger('test', 2);
assert.deepEqual(args, [1, 2]);
});
QUnit.test('callbacks can remove themselves', function(assert) {
var args1 = [], args2 = [], args3 = [];
stream.on('test', function(event) {
args1.push(event);
});
stream.on('test', function t(event) {
args2.push(event);
stream.off('test', t);
});
stream.on('test', function(event) {
args3.push(event);
});
stream.trigger('test', 1);
stream.trigger('test', 2);
assert.deepEqual(args1, [1, 2], 'first callback ran all times');
assert.deepEqual(args2, [1], 'second callback removed after first run');
assert.deepEqual(args3, [1, 2], 'third callback ran all times');
});
'use strict';
var
stream,
Stream = require('../lib/utils/stream'),
QUnit = require('qunit');
QUnit.module('Stream', {
beforeEach: function() {
stream = new Stream();
stream.init();
}
});
QUnit.test('trigger calls listeners', function(assert) {
var args = [];
stream.on('test', function(data) {
args.push(data);
});
stream.trigger('test', 1);
stream.trigger('test', 2);
assert.deepEqual(args, [1, 2]);
});
QUnit.test('callbacks can remove themselves', function(assert) {
var args1 = [], args2 = [], args3 = [];
stream.on('test', function(event) {
args1.push(event);
});
stream.on('test', function t(event) {
args2.push(event);
stream.off('test', t);
});
stream.on('test', function(event) {
args3.push(event);
});
stream.trigger('test', 1);
stream.trigger('test', 2);
assert.deepEqual(args1, [1, 2], 'first callback ran all times');
assert.deepEqual(args2, [1], 'second callback removed after first run');
assert.deepEqual(args3, [1, 2], 'third callback ran all times');
});

File diff suppressed because it is too large Load diff

View file

@ -1,205 +1,205 @@
'use strict';
var segments = require('data-files!segments');
var
QUnit = require('qunit'),
tsInspector = require('../lib/tools/ts-inspector.js'),
StreamTypes = require('../lib/m2ts/stream-types.js'),
tsSegment = segments['test-segment.ts'](),
tsNoAudioSegment = segments['test-no-audio-segment.ts'](),
aacSegment = segments['test-aac-segment.aac'](),
utils = require('./utils'),
inspect = tsInspector.inspect,
parseAudioPes_ = tsInspector.parseAudioPes_,
packetize = utils.packetize,
audioPes = utils.audioPes,
PES_TIMESCALE = 90000;
QUnit.module('TS Inspector');
QUnit.test('returns null for empty segment input', function(assert) {
assert.equal(inspect(new Uint8Array([])), null, 'returned null');
});
QUnit.test('can parse a ts segment', function(assert) {
var expected = {
video: [
{
type: 'video',
pts: 126000,
dts: 126000,
ptsTime: 126000 / PES_TIMESCALE,
dtsTime: 126000 / PES_TIMESCALE
},
{
type: 'video',
pts: 924000,
dts: 924000,
ptsTime: 924000 / PES_TIMESCALE,
dtsTime: 924000 / PES_TIMESCALE
}
],
firstKeyFrame: {
type: 'video',
pts: 126000,
dts: 126000,
ptsTime: 126000 / PES_TIMESCALE,
dtsTime: 126000 / PES_TIMESCALE
},
audio: [
{
type: 'audio',
pts: 126000,
dts: 126000,
ptsTime: 126000 / PES_TIMESCALE,
dtsTime: 126000 / PES_TIMESCALE
},
{
type: 'audio',
pts: 859518,
dts: 859518,
ptsTime: 859518 / PES_TIMESCALE,
dtsTime: 859518 / PES_TIMESCALE
}
]
};
assert.deepEqual(inspect(tsSegment), expected, 'parses ts segment timing data');
});
QUnit.test('adjusts timestamp values based on provided reference', function(assert) {
var rollover = Math.pow(2, 33);
var expected = {
video: [
{
type: 'video',
pts: (126000 + rollover),
dts: (126000 + rollover),
ptsTime: (126000 + rollover) / PES_TIMESCALE,
dtsTime: (126000 + rollover) / PES_TIMESCALE
},
{
type: 'video',
pts: (924000 + rollover),
dts: (924000 + rollover),
ptsTime: (924000 + rollover) / PES_TIMESCALE,
dtsTime: (924000 + rollover) / PES_TIMESCALE
}
],
firstKeyFrame: {
type: 'video',
pts: (126000 + rollover),
dts: (126000 + rollover),
ptsTime: (126000 + rollover) / PES_TIMESCALE,
dtsTime: (126000 + rollover) / PES_TIMESCALE
},
audio: [
{
type: 'audio',
pts: (126000 + rollover),
dts: (126000 + rollover),
ptsTime: (126000 + rollover) / PES_TIMESCALE,
dtsTime: (126000 + rollover) / PES_TIMESCALE
},
{
type: 'audio',
pts: (859518 + rollover),
dts: (859518 + rollover),
ptsTime: (859518 + rollover) / PES_TIMESCALE,
dtsTime: (859518 + rollover) / PES_TIMESCALE
}
]
};
assert.deepEqual(inspect(tsSegment, rollover - 1), expected,
'adjusts inspected time data to account for pts rollover');
});
QUnit.test('can parse an aac segment', function(assert) {
var expected = {
audio: [
{
type: 'audio',
pts: 895690,
dts: 895690,
ptsTime: 895690 / PES_TIMESCALE,
dtsTime: 895690 / PES_TIMESCALE
},
{
type: 'audio',
pts: (895690 + (430 * 1024 * PES_TIMESCALE / 44100)),
dts: (895690 + (430 * 1024 * PES_TIMESCALE / 44100)),
ptsTime: (895690 + (430 * 1024 * PES_TIMESCALE / 44100)) / PES_TIMESCALE,
dtsTime: (895690 + (430 * 1024 * PES_TIMESCALE / 44100)) / PES_TIMESCALE
}
]
};
assert.deepEqual(inspect(aacSegment), expected, 'parses aac segment timing data');
});
QUnit.test('can parse ts segment with no audio muxed in', function(assert) {
var expected = {
video: [
{
type: 'video',
pts: 126000,
dts: 126000,
ptsTime: 126000 / PES_TIMESCALE,
dtsTime: 126000 / PES_TIMESCALE
},
{
type: 'video',
pts: 924000,
dts: 924000,
ptsTime: 924000 / PES_TIMESCALE,
dtsTime: 924000 / PES_TIMESCALE
}
],
firstKeyFrame: {
type: 'video',
pts: 126000,
dts: 126000,
ptsTime: 126000 / PES_TIMESCALE,
dtsTime: 126000 / PES_TIMESCALE
}
};
var actual = inspect(tsNoAudioSegment);
assert.equal(typeof actual.audio, 'undefined', 'results do not contain audio info');
assert.deepEqual(actual, expected,
'parses ts segment without audio timing data');
});
QUnit.test('can parse audio PES when it\'s the only packet in a stream', function(assert) {
var
pts = 90000,
pmt = {
// fake pmt pid that doesn't clash with the audio pid
pid: 0x10,
table: {
// pid copied over from default of audioPes function
0x12: StreamTypes.ADTS_STREAM_TYPE
}
},
result = { audio: [] };
parseAudioPes_(packetize(audioPes([0x00], true, pts)), pmt, result);
// note that both the first and last packet timings are the same, as there's only one
// packet to parse
assert.deepEqual(
result.audio,
[{
dts: pts,
pts: pts,
type: 'audio'
}, {
dts: pts,
pts: pts,
type: 'audio'
}],
'parses audio pes for timing info');
});
'use strict';
var segments = require('data-files!segments');
var
QUnit = require('qunit'),
tsInspector = require('../lib/tools/ts-inspector.js'),
StreamTypes = require('../lib/m2ts/stream-types.js'),
tsSegment = segments['test-segment.ts'](),
tsNoAudioSegment = segments['test-no-audio-segment.ts'](),
aacSegment = segments['test-aac-segment.aac'](),
utils = require('./utils'),
inspect = tsInspector.inspect,
parseAudioPes_ = tsInspector.parseAudioPes_,
packetize = utils.packetize,
audioPes = utils.audioPes,
PES_TIMESCALE = 90000;
QUnit.module('TS Inspector');
QUnit.test('returns null for empty segment input', function(assert) {
assert.equal(inspect(new Uint8Array([])), null, 'returned null');
});
QUnit.test('can parse a ts segment', function(assert) {
var expected = {
video: [
{
type: 'video',
pts: 126000,
dts: 126000,
ptsTime: 126000 / PES_TIMESCALE,
dtsTime: 126000 / PES_TIMESCALE
},
{
type: 'video',
pts: 924000,
dts: 924000,
ptsTime: 924000 / PES_TIMESCALE,
dtsTime: 924000 / PES_TIMESCALE
}
],
firstKeyFrame: {
type: 'video',
pts: 126000,
dts: 126000,
ptsTime: 126000 / PES_TIMESCALE,
dtsTime: 126000 / PES_TIMESCALE
},
audio: [
{
type: 'audio',
pts: 126000,
dts: 126000,
ptsTime: 126000 / PES_TIMESCALE,
dtsTime: 126000 / PES_TIMESCALE
},
{
type: 'audio',
pts: 859518,
dts: 859518,
ptsTime: 859518 / PES_TIMESCALE,
dtsTime: 859518 / PES_TIMESCALE
}
]
};
assert.deepEqual(inspect(tsSegment), expected, 'parses ts segment timing data');
});
QUnit.test('adjusts timestamp values based on provided reference', function(assert) {
var rollover = Math.pow(2, 33);
var expected = {
video: [
{
type: 'video',
pts: (126000 + rollover),
dts: (126000 + rollover),
ptsTime: (126000 + rollover) / PES_TIMESCALE,
dtsTime: (126000 + rollover) / PES_TIMESCALE
},
{
type: 'video',
pts: (924000 + rollover),
dts: (924000 + rollover),
ptsTime: (924000 + rollover) / PES_TIMESCALE,
dtsTime: (924000 + rollover) / PES_TIMESCALE
}
],
firstKeyFrame: {
type: 'video',
pts: (126000 + rollover),
dts: (126000 + rollover),
ptsTime: (126000 + rollover) / PES_TIMESCALE,
dtsTime: (126000 + rollover) / PES_TIMESCALE
},
audio: [
{
type: 'audio',
pts: (126000 + rollover),
dts: (126000 + rollover),
ptsTime: (126000 + rollover) / PES_TIMESCALE,
dtsTime: (126000 + rollover) / PES_TIMESCALE
},
{
type: 'audio',
pts: (859518 + rollover),
dts: (859518 + rollover),
ptsTime: (859518 + rollover) / PES_TIMESCALE,
dtsTime: (859518 + rollover) / PES_TIMESCALE
}
]
};
assert.deepEqual(inspect(tsSegment, rollover - 1), expected,
'adjusts inspected time data to account for pts rollover');
});
QUnit.test('can parse an aac segment', function(assert) {
var expected = {
audio: [
{
type: 'audio',
pts: 895690,
dts: 895690,
ptsTime: 895690 / PES_TIMESCALE,
dtsTime: 895690 / PES_TIMESCALE
},
{
type: 'audio',
pts: (895690 + (430 * 1024 * PES_TIMESCALE / 44100)),
dts: (895690 + (430 * 1024 * PES_TIMESCALE / 44100)),
ptsTime: (895690 + (430 * 1024 * PES_TIMESCALE / 44100)) / PES_TIMESCALE,
dtsTime: (895690 + (430 * 1024 * PES_TIMESCALE / 44100)) / PES_TIMESCALE
}
]
};
assert.deepEqual(inspect(aacSegment), expected, 'parses aac segment timing data');
});
QUnit.test('can parse ts segment with no audio muxed in', function(assert) {
var expected = {
video: [
{
type: 'video',
pts: 126000,
dts: 126000,
ptsTime: 126000 / PES_TIMESCALE,
dtsTime: 126000 / PES_TIMESCALE
},
{
type: 'video',
pts: 924000,
dts: 924000,
ptsTime: 924000 / PES_TIMESCALE,
dtsTime: 924000 / PES_TIMESCALE
}
],
firstKeyFrame: {
type: 'video',
pts: 126000,
dts: 126000,
ptsTime: 126000 / PES_TIMESCALE,
dtsTime: 126000 / PES_TIMESCALE
}
};
var actual = inspect(tsNoAudioSegment);
assert.equal(typeof actual.audio, 'undefined', 'results do not contain audio info');
assert.deepEqual(actual, expected,
'parses ts segment without audio timing data');
});
QUnit.test('can parse audio PES when it\'s the only packet in a stream', function(assert) {
var
pts = 90000,
pmt = {
// fake pmt pid that doesn't clash with the audio pid
pid: 0x10,
table: {
// pid copied over from default of audioPes function
0x12: StreamTypes.ADTS_STREAM_TYPE
}
},
result = { audio: [] };
parseAudioPes_(packetize(audioPes([0x00], true, pts)), pmt, result);
// note that both the first and last packet timings are the same, as there's only one
// packet to parse
assert.deepEqual(
result.audio,
[{
dts: pts,
pts: pts,
type: 'audio'
}, {
dts: pts,
pts: pts,
type: 'audio'
}],
'parses audio pes for timing info');
});

View file

@ -1,32 +1,32 @@
var
QUnit = require('qunit'),
toUnsigned = require('../lib/utils/bin').toUnsigned;
QUnit.module('Binary Utils');
QUnit.test('converts values to unsigned integers after bitwise operations', function(assert) {
var bytes;
bytes = [0, 0, 124, 129];
assert.equal(toUnsigned(bytes[0] << 24 |
bytes[1] << 16 |
bytes[2] << 8 |
bytes[3]),
31873, 'positive signed result stays positive');
bytes = [150, 234, 221, 192];
// sanity check
assert.equal(bytes[0] << 24 |
bytes[1] << 16 |
bytes[2] << 8 |
bytes[3],
-1762992704, 'bitwise operation produces negative signed result');
assert.equal(toUnsigned(bytes[0] << 24 |
bytes[1] << 16 |
bytes[2] << 8 |
bytes[3]),
2531974592, 'negative signed result becomes unsigned positive');
});
var
QUnit = require('qunit'),
toUnsigned = require('../lib/utils/bin').toUnsigned;
QUnit.module('Binary Utils');
QUnit.test('converts values to unsigned integers after bitwise operations', function(assert) {
var bytes;
bytes = [0, 0, 124, 129];
assert.equal(toUnsigned(bytes[0] << 24 |
bytes[1] << 16 |
bytes[2] << 8 |
bytes[3]),
31873, 'positive signed result stays positive');
bytes = [150, 234, 221, 192];
// sanity check
assert.equal(bytes[0] << 24 |
bytes[1] << 16 |
bytes[2] << 8 |
bytes[3],
-1762992704, 'bitwise operation produces negative signed result');
assert.equal(toUnsigned(bytes[0] << 24 |
bytes[1] << 16 |
bytes[2] << 8 |
bytes[3]),
2531974592, 'negative signed result becomes unsigned positive');
});

View file

@ -1,181 +1,181 @@
'use strict';
var
QUnit = require('qunit'),
clock = require('../lib/utils/clock');
QUnit.module('Clock Utils');
QUnit.test('converts from seconds to video timestamps', function(assert) {
assert.equal(clock.secondsToVideoTs(0), 0, 'converts seconds to video timestamp');
assert.equal(clock.secondsToVideoTs(1), 90000, 'converts seconds to video timestamp');
assert.equal(clock.secondsToVideoTs(10), 900000, 'converts seconds to video timestamp');
assert.equal(clock.secondsToVideoTs(-1), -90000, 'converts seconds to video timestamp');
assert.equal(clock.secondsToVideoTs(3), 270000, 'converts seconds to video timestamp');
assert.equal(clock.secondsToVideoTs(0.1), 9000, 'converts seconds to video timestamp');
});
QUnit.test('converts from seconds to audio timestamps', function(assert) {
assert.equal(clock.secondsToAudioTs(0, 90000),
0,
'converts seconds to audio timestamp');
assert.equal(clock.secondsToAudioTs(1, 90000),
90000,
'converts seconds to audio timestamp');
assert.equal(clock.secondsToAudioTs(-1, 90000),
-90000,
'converts seconds to audio timestamp');
assert.equal(clock.secondsToAudioTs(3, 90000),
270000,
'converts seconds to audio timestamp');
assert.equal(clock.secondsToAudioTs(0, 44100),
0,
'converts seconds to audio timestamp');
assert.equal(clock.secondsToAudioTs(1, 44100),
44100,
'converts seconds to audio timestamp');
assert.equal(clock.secondsToAudioTs(3, 44100),
132300,
'converts seconds to audio timestamp');
assert.equal(clock.secondsToAudioTs(-1, 44100),
-44100,
'converts seconds to audio timestamp');
assert.equal(clock.secondsToAudioTs(0.1, 44100),
4410,
'converts seconds to audio timestamp');
});
QUnit.test('converts from video timestamp to seconds', function(assert) {
assert.equal(clock.videoTsToSeconds(0), 0, 'converts video timestamp to seconds');
assert.equal(clock.videoTsToSeconds(90000), 1, 'converts video timestamp to seconds');
assert.equal(clock.videoTsToSeconds(900000), 10, 'converts video timestamp to seconds');
assert.equal(clock.videoTsToSeconds(-90000), -1, 'converts video timestamp to seconds');
assert.equal(clock.videoTsToSeconds(270000), 3, 'converts video timestamp to seconds');
assert.equal(clock.videoTsToSeconds(9000), 0.1, 'converts video timestamp to seconds');
});
QUnit.test('converts from audio timestamp to seconds', function(assert) {
assert.equal(clock.audioTsToSeconds(0, 90000),
0,
'converts seconds to audio timestamp');
assert.equal(clock.audioTsToSeconds(90000, 90000),
1,
'converts seconds to audio timestamp');
assert.equal(clock.audioTsToSeconds(-90000, 90000),
-1,
'converts seconds to audio timestamp');
assert.equal(clock.audioTsToSeconds(270000, 90000),
3,
'converts seconds to audio timestamp');
assert.equal(clock.audioTsToSeconds(0, 44100),
0,
'converts seconds to audio timestamp');
assert.equal(clock.audioTsToSeconds(44100, 44100),
1,
'converts seconds to audio timestamp');
assert.equal(clock.audioTsToSeconds(132300, 44100),
3,
'converts seconds to audio timestamp');
assert.equal(clock.audioTsToSeconds(-44100, 44100),
-1,
'converts seconds to audio timestamp');
assert.equal(clock.audioTsToSeconds(4410, 44100),
0.1,
'converts seconds to audio timestamp');
});
QUnit.test('converts from audio timestamp to video timestamp', function(assert) {
assert.equal(clock.audioTsToVideoTs(0, 90000),
0,
'converts audio timestamp to video timestamp');
assert.equal(clock.audioTsToVideoTs(90000, 90000),
90000,
'converts audio timestamp to video timestamp');
assert.equal(clock.audioTsToVideoTs(900000, 90000),
900000,
'converts audio timestamp to video timestamp');
assert.equal(clock.audioTsToVideoTs(-90000, 90000),
-90000,
'converts audio timestamp to video timestamp');
assert.equal(clock.audioTsToVideoTs(270000, 90000),
270000,
'converts audio timestamp to video timestamp');
assert.equal(clock.audioTsToVideoTs(9000, 90000),
9000,
'converts audio timestamp to video timestamp');
assert.equal(clock.audioTsToVideoTs(0, 44100),
0,
'converts audio timestamp to video timestamp');
assert.equal(clock.audioTsToVideoTs(44100, 44100),
90000,
'converts audio timestamp to video timestamp');
assert.equal(clock.audioTsToVideoTs(441000, 44100),
900000,
'converts audio timestamp to video timestamp');
assert.equal(clock.audioTsToVideoTs(-44100, 44100),
-90000,
'converts audio timestamp to video timestamp');
assert.equal(clock.audioTsToVideoTs(132300, 44100),
270000,
'converts audio timestamp to video timestamp');
assert.equal(clock.audioTsToVideoTs(4410, 44100),
9000,
'converts audio timestamp to video timestamp');
});
QUnit.test('converts from video timestamp to audio timestamp', function(assert) {
assert.equal(clock.videoTsToAudioTs(0, 90000),
0,
'converts video timestamp to audio timestamp');
assert.equal(clock.videoTsToAudioTs(90000, 90000),
90000,
'converts video timestamp to audio timestamp');
assert.equal(clock.videoTsToAudioTs(900000, 90000),
900000,
'converts video timestamp to audio timestamp');
assert.equal(clock.videoTsToAudioTs(-90000, 90000),
-90000,
'converts video timestamp to audio timestamp');
assert.equal(clock.videoTsToAudioTs(270000, 90000),
270000,
'converts video timestamp to audio timestamp');
assert.equal(clock.videoTsToAudioTs(9000, 90000),
9000,
'converts video timestamp to audio timestamp');
assert.equal(clock.videoTsToAudioTs(0, 44100),
0,
'converts video timestamp to audio timestamp');
assert.equal(clock.videoTsToAudioTs(90000, 44100),
44100,
'converts video timestamp to audio timestamp');
assert.equal(clock.videoTsToAudioTs(900000, 44100),
441000,
'converts video timestamp to audio timestamp');
assert.equal(clock.videoTsToAudioTs(-90000, 44100),
-44100,
'converts video timestamp to audio timestamp');
assert.equal(clock.videoTsToAudioTs(270000, 44100),
132300,
'converts video timestamp to audio timestamp');
assert.equal(clock.videoTsToAudioTs(9000, 44100),
4410,
'converts video timestamp to audio timestamp');
});
QUnit.test('converts from metadata timestamp to seconds', function(assert) {
assert.equal(clock.metadataTsToSeconds(90000, 90000, false),
0,
'converts metadata timestamp to seconds and adjusts by timelineStartPts');
assert.equal(clock.metadataTsToSeconds(270000, 90000, false),
2,
'converts metadata timestamp to seconds and adjusts by timelineStartPts');
assert.equal(clock.metadataTsToSeconds(90000, 90000, true),
1,
'converts metadata timestamp to seconds while keeping original timestamps');
assert.equal(clock.metadataTsToSeconds(180000, 0, true),
2,
'converts metadata timestamp to seconds while keeping original timestamps');
});
'use strict';
var
QUnit = require('qunit'),
clock = require('../lib/utils/clock');
QUnit.module('Clock Utils');
QUnit.test('converts from seconds to video timestamps', function(assert) {
assert.equal(clock.secondsToVideoTs(0), 0, 'converts seconds to video timestamp');
assert.equal(clock.secondsToVideoTs(1), 90000, 'converts seconds to video timestamp');
assert.equal(clock.secondsToVideoTs(10), 900000, 'converts seconds to video timestamp');
assert.equal(clock.secondsToVideoTs(-1), -90000, 'converts seconds to video timestamp');
assert.equal(clock.secondsToVideoTs(3), 270000, 'converts seconds to video timestamp');
assert.equal(clock.secondsToVideoTs(0.1), 9000, 'converts seconds to video timestamp');
});
QUnit.test('converts from seconds to audio timestamps', function(assert) {
assert.equal(clock.secondsToAudioTs(0, 90000),
0,
'converts seconds to audio timestamp');
assert.equal(clock.secondsToAudioTs(1, 90000),
90000,
'converts seconds to audio timestamp');
assert.equal(clock.secondsToAudioTs(-1, 90000),
-90000,
'converts seconds to audio timestamp');
assert.equal(clock.secondsToAudioTs(3, 90000),
270000,
'converts seconds to audio timestamp');
assert.equal(clock.secondsToAudioTs(0, 44100),
0,
'converts seconds to audio timestamp');
assert.equal(clock.secondsToAudioTs(1, 44100),
44100,
'converts seconds to audio timestamp');
assert.equal(clock.secondsToAudioTs(3, 44100),
132300,
'converts seconds to audio timestamp');
assert.equal(clock.secondsToAudioTs(-1, 44100),
-44100,
'converts seconds to audio timestamp');
assert.equal(clock.secondsToAudioTs(0.1, 44100),
4410,
'converts seconds to audio timestamp');
});
QUnit.test('converts from video timestamp to seconds', function(assert) {
assert.equal(clock.videoTsToSeconds(0), 0, 'converts video timestamp to seconds');
assert.equal(clock.videoTsToSeconds(90000), 1, 'converts video timestamp to seconds');
assert.equal(clock.videoTsToSeconds(900000), 10, 'converts video timestamp to seconds');
assert.equal(clock.videoTsToSeconds(-90000), -1, 'converts video timestamp to seconds');
assert.equal(clock.videoTsToSeconds(270000), 3, 'converts video timestamp to seconds');
assert.equal(clock.videoTsToSeconds(9000), 0.1, 'converts video timestamp to seconds');
});
QUnit.test('converts from audio timestamp to seconds', function(assert) {
assert.equal(clock.audioTsToSeconds(0, 90000),
0,
'converts seconds to audio timestamp');
assert.equal(clock.audioTsToSeconds(90000, 90000),
1,
'converts seconds to audio timestamp');
assert.equal(clock.audioTsToSeconds(-90000, 90000),
-1,
'converts seconds to audio timestamp');
assert.equal(clock.audioTsToSeconds(270000, 90000),
3,
'converts seconds to audio timestamp');
assert.equal(clock.audioTsToSeconds(0, 44100),
0,
'converts seconds to audio timestamp');
assert.equal(clock.audioTsToSeconds(44100, 44100),
1,
'converts seconds to audio timestamp');
assert.equal(clock.audioTsToSeconds(132300, 44100),
3,
'converts seconds to audio timestamp');
assert.equal(clock.audioTsToSeconds(-44100, 44100),
-1,
'converts seconds to audio timestamp');
assert.equal(clock.audioTsToSeconds(4410, 44100),
0.1,
'converts seconds to audio timestamp');
});
QUnit.test('converts from audio timestamp to video timestamp', function(assert) {
assert.equal(clock.audioTsToVideoTs(0, 90000),
0,
'converts audio timestamp to video timestamp');
assert.equal(clock.audioTsToVideoTs(90000, 90000),
90000,
'converts audio timestamp to video timestamp');
assert.equal(clock.audioTsToVideoTs(900000, 90000),
900000,
'converts audio timestamp to video timestamp');
assert.equal(clock.audioTsToVideoTs(-90000, 90000),
-90000,
'converts audio timestamp to video timestamp');
assert.equal(clock.audioTsToVideoTs(270000, 90000),
270000,
'converts audio timestamp to video timestamp');
assert.equal(clock.audioTsToVideoTs(9000, 90000),
9000,
'converts audio timestamp to video timestamp');
assert.equal(clock.audioTsToVideoTs(0, 44100),
0,
'converts audio timestamp to video timestamp');
assert.equal(clock.audioTsToVideoTs(44100, 44100),
90000,
'converts audio timestamp to video timestamp');
assert.equal(clock.audioTsToVideoTs(441000, 44100),
900000,
'converts audio timestamp to video timestamp');
assert.equal(clock.audioTsToVideoTs(-44100, 44100),
-90000,
'converts audio timestamp to video timestamp');
assert.equal(clock.audioTsToVideoTs(132300, 44100),
270000,
'converts audio timestamp to video timestamp');
assert.equal(clock.audioTsToVideoTs(4410, 44100),
9000,
'converts audio timestamp to video timestamp');
});
QUnit.test('converts from video timestamp to audio timestamp', function(assert) {
assert.equal(clock.videoTsToAudioTs(0, 90000),
0,
'converts video timestamp to audio timestamp');
assert.equal(clock.videoTsToAudioTs(90000, 90000),
90000,
'converts video timestamp to audio timestamp');
assert.equal(clock.videoTsToAudioTs(900000, 90000),
900000,
'converts video timestamp to audio timestamp');
assert.equal(clock.videoTsToAudioTs(-90000, 90000),
-90000,
'converts video timestamp to audio timestamp');
assert.equal(clock.videoTsToAudioTs(270000, 90000),
270000,
'converts video timestamp to audio timestamp');
assert.equal(clock.videoTsToAudioTs(9000, 90000),
9000,
'converts video timestamp to audio timestamp');
assert.equal(clock.videoTsToAudioTs(0, 44100),
0,
'converts video timestamp to audio timestamp');
assert.equal(clock.videoTsToAudioTs(90000, 44100),
44100,
'converts video timestamp to audio timestamp');
assert.equal(clock.videoTsToAudioTs(900000, 44100),
441000,
'converts video timestamp to audio timestamp');
assert.equal(clock.videoTsToAudioTs(-90000, 44100),
-44100,
'converts video timestamp to audio timestamp');
assert.equal(clock.videoTsToAudioTs(270000, 44100),
132300,
'converts video timestamp to audio timestamp');
assert.equal(clock.videoTsToAudioTs(9000, 44100),
4410,
'converts video timestamp to audio timestamp');
});
QUnit.test('converts from metadata timestamp to seconds', function(assert) {
assert.equal(clock.metadataTsToSeconds(90000, 90000, false),
0,
'converts metadata timestamp to seconds and adjusts by timelineStartPts');
assert.equal(clock.metadataTsToSeconds(270000, 90000, false),
2,
'converts metadata timestamp to seconds and adjusts by timelineStartPts');
assert.equal(clock.metadataTsToSeconds(90000, 90000, true),
1,
'converts metadata timestamp to seconds while keeping original timestamps');
assert.equal(clock.metadataTsToSeconds(180000, 0, true),
2,
'converts metadata timestamp to seconds while keeping original timestamps');
});

654
node_modules/mux.js/test/utils.js generated vendored
View file

@ -1,327 +1,327 @@
var
mp2t = require('../lib/m2ts'),
id3Generator = require('./utils/id3-generator'),
MP2T_PACKET_LENGTH = mp2t.MP2T_PACKET_LENGTH,
PMT,
PAT,
generatePMT,
pesHeader,
packetize,
transportPacket,
videoPes,
adtsFrame,
audioPes,
timedMetadataPes,
binaryStringToArrayOfBytes,
leftPad;
PMT = [
0x47, // sync byte
// tei:0 pusi:1 tp:0 pid:0 0000 0010 0000
0x40, 0x10,
// tsc:01 afc:01 cc:0000 pointer_field:0000 0000
0x50, 0x00,
// tid:0000 0010 ssi:0 0:0 r:00 sl:0000 0001 1100
0x02, 0x00, 0x1c,
// pn:0000 0000 0000 0001
0x00, 0x01,
// r:00 vn:00 000 cni:1 sn:0000 0000 lsn:0000 0000
0x01, 0x00, 0x00,
// r:000 ppid:0 0011 1111 1111
0x03, 0xff,
// r:0000 pil:0000 0000 0000
0x00, 0x00,
// h264
// st:0001 1010 r:000 epid:0 0000 0001 0001
0x1b, 0x00, 0x11,
// r:0000 esil:0000 0000 0000
0x00, 0x00,
// adts
// st:0000 1111 r:000 epid:0 0000 0001 0010
0x0f, 0x00, 0x12,
// r:0000 esil:0000 0000 0000
0x00, 0x00,
// timed metadata
// st:0001 0111 r:000 epid:0 0000 0001 0011
0x15, 0x00, 0x13,
// r:0000 esil:0000 0000 0000
0x00, 0x00,
// crc
0x00, 0x00, 0x00, 0x00
];
/*
Packet Header:
| sb | tei pusi tp pid:5 | pid | tsc afc cc |
with af:
| afl | ... | <data> |
without af:
| <data> |
PAT:
| pf? | ... |
| tid | ssi '0' r sl:4 | sl | tsi:8 |
| tsi | r vn cni | sn | lsn |
with program_number == '0':
| pn | pn | r np:5 | np |
otherwise:
| pn | pn | r pmp:5 | pmp |
*/
PAT = [
0x47, // sync byte
// tei:0 pusi:1 tp:0 pid:0 0000 0000 0000
0x40, 0x00,
// tsc:01 afc:01 cc:0000 pointer_field:0000 0000
0x50, 0x00,
// tid:0000 0000 ssi:0 0:0 r:00 sl:0000 0000 0000
0x00, 0x00, 0x00,
// tsi:0000 0000 0000 0000
0x00, 0x00,
// r:00 vn:00 000 cni:1 sn:0000 0000 lsn:0000 0000
0x01, 0x00, 0x00,
// pn:0000 0000 0000 0001
0x00, 0x01,
// r:000 pmp:0 0000 0010 0000
0x00, 0x10,
// crc32:0000 0000 0000 0000 0000 0000 0000 0000
0x00, 0x00, 0x00, 0x00
];
generatePMT = function(options) {
var PMT = [
0x47, // sync byte
// tei:0 pusi:1 tp:0 pid:0 0000 0010 0000
0x40, 0x10,
// tsc:01 afc:01 cc:0000 pointer_field:0000 0000
0x50, 0x00,
// tid:0000 0010 ssi:0 0:0 r:00 sl:0000 0001 1100
0x02, 0x00, 0x1c,
// pn:0000 0000 0000 0001
0x00, 0x01,
// r:00 vn:00 000 cni:1 sn:0000 0000 lsn:0000 0000
0x01, 0x00, 0x00,
// r:000 ppid:0 0011 1111 1111
0x03, 0xff,
// r:0000 pil:0000 0000 0000
0x00, 0x00];
if (options.hasVideo) {
// h264
PMT = PMT.concat([
// st:0001 1010 r:000 epid:0 0000 0001 0001
0x1b, 0x00, 0x11,
// r:0000 esil:0000 0000 0000
0x00, 0x00
]);
}
if (options.hasAudio) {
// adts
PMT = PMT.concat([
// st:0000 1111 r:000 epid:0 0000 0001 0010
0x0f, 0x00, 0x12,
// r:0000 esil:0000 0000 0000
0x00, 0x00
]);
}
if (options.hasMetadata) {
// timed metadata
PMT = PMT.concat([
// st:0001 0111 r:000 epid:0 0000 0001 0011
0x15, 0x00, 0x13,
// r:0000 esil:0000 0000 0000
0x00, 0x00
]);
}
// crc
return PMT.concat([0x00, 0x00, 0x00, 0x00]);
};
pesHeader = function(first, pts, dataLength) {
if (!dataLength) {
dataLength = 0;
} else {
// Add the pes header length (only the portion after the
// pes_packet_length field)
dataLength += 3;
}
// PES_packet(), Rec. ITU-T H.222.0, Table 2-21
var result = [
// pscp:0000 0000 0000 0000 0000 0001
0x00, 0x00, 0x01,
// sid:0000 0000 ppl:0000 0000 0000 0000
0x00, 0x00, 0x00,
// 10 psc:00 pp:0 dai:1 c:0 ooc:0
0x84,
// pdf:?0 ef:1 erf:0 dtmf:0 acif:0 pcf:0 pef:0
0x20 | (pts ? 0x80 : 0x00),
// phdl:0000 0000
(first ? 0x01 : 0x00) + (pts ? 0x05 : 0x00)
];
// Only store 15 bits of the PTS for QUnit.testing purposes
if (pts) {
var
pts32 = Math.floor(pts / 2), // right shift by 1
leftMostBit = ((pts32 & 0x80000000) >>> 31) & 0x01,
firstThree;
pts = pts & 0xffffffff; // remove left most bit
firstThree = (leftMostBit << 3) | (((pts & 0xc0000000) >>> 29) & 0x06) | 0x01;
result.push((0x2 << 4) | firstThree);
result.push((pts >>> 22) & 0xff);
result.push(((pts >>> 14) | 0x01) & 0xff);
result.push((pts >>> 7) & 0xff);
result.push(((pts << 1) | 0x01) & 0xff);
// Add the bytes spent on the pts info
dataLength += 5;
}
if (first) {
result.push(0x00);
dataLength += 1;
}
// Finally set the pes_packet_length field
result[4] = (dataLength & 0x0000FF00) >> 8;
result[5] = dataLength & 0x000000FF;
return result;
};
packetize = function(data) {
var packet = new Uint8Array(MP2T_PACKET_LENGTH);
packet.set(data);
return packet;
};
/**
* Helper function to create transport stream PES packets
* @param pid {uint8} - the program identifier (PID)
* @param data {arraylike} - the payload bytes
* @payload first {boolean} - true if this PES should be a payload
* unit start
*/
transportPacket = function(pid, data, first, pts, isVideoData) {
var
adaptationFieldLength = 188 - data.length - 14 - (first ? 1 : 0) - (pts ? 5 : 0),
// transport_packet(), Rec. ITU-T H.222.0, Table 2-2
result = [
// sync byte
0x47,
// tei:0 pusi:1 tp:0 pid:0 0000 0001 0001
0x40, pid,
// tsc:01 afc:11 cc:0000
0x70
].concat([
// afl
adaptationFieldLength & 0xff,
// di:0 rai:0 espi:0 pf:0 of:0 spf:0 tpdf:0 afef:0
0x00
]),
i;
i = adaptationFieldLength - 1;
while (i--) {
// stuffing_bytes
result.push(0xff);
}
// PES_packet(), Rec. ITU-T H.222.0, Table 2-21
result = result.concat(pesHeader(first, pts, isVideoData ? 0 : data.length));
return result.concat(data);
};
/**
* Helper function to create video PES packets
* @param data {arraylike} - the payload bytes
* @payload first {boolean} - true if this PES should be a payload
* unit start
*/
videoPes = function(data, first, pts) {
return transportPacket(0x11, [
// NAL unit start code
0x00, 0x00, 0x01
].concat(data), first, pts, true);
};
/**
* Helper function to create audio ADTS frame header
* @param dataLength {number} - the payload byte count
*/
adtsFrame = function(dataLength) {
var frameLength = dataLength + 7;
return [
0xff, 0xf1, // no CRC
0x10, // AAC Main, 44.1KHz
0xb0 | ((frameLength & 0x1800) >> 11), // 2 channels
(frameLength & 0x7f8) >> 3,
((frameLength & 0x07) << 5) + 7, // frame length in bytes
0x00 // one AAC per ADTS frame
];
};
/**
* Helper function to create audio PES packets
* @param data {arraylike} - the payload bytes
* @payload first {boolean} - true if this PES should be a payload
* unit start
*/
audioPes = function(data, first, pts) {
return transportPacket(0x12,
adtsFrame(data.length).concat(data),
first, pts);
};
timedMetadataPes = function(data) {
var id3 = id3Generator;
return transportPacket(0x13, id3.id3Tag(id3.id3Frame('PRIV', 0x00, 0x01)));
};
binaryStringToArrayOfBytes = function(string) {
var
array = [],
arrayIndex = 0,
stringIndex = 0;
while (stringIndex < string.length) {
array[arrayIndex] = parseInt(string.slice(stringIndex, stringIndex + 8), 2);
arrayIndex++;
// next byte
stringIndex += 8;
}
return array;
};
leftPad = function(string, targetLength) {
if (string.length >= targetLength) {
return string;
}
return new Array(targetLength - string.length + 1).join('0') + string;
};
module.exports = {
PMT: PMT,
PAT: PAT,
generatePMT: generatePMT,
pesHeader: pesHeader,
packetize: packetize,
transportPacket: transportPacket,
videoPes: videoPes,
adtsFrame: adtsFrame,
audioPes: audioPes,
timedMetadataPes: timedMetadataPes,
binaryStringToArrayOfBytes: binaryStringToArrayOfBytes,
leftPad: leftPad
};
var
mp2t = require('../lib/m2ts'),
id3Generator = require('./utils/id3-generator'),
MP2T_PACKET_LENGTH = mp2t.MP2T_PACKET_LENGTH,
PMT,
PAT,
generatePMT,
pesHeader,
packetize,
transportPacket,
videoPes,
adtsFrame,
audioPes,
timedMetadataPes,
binaryStringToArrayOfBytes,
leftPad;
PMT = [
0x47, // sync byte
// tei:0 pusi:1 tp:0 pid:0 0000 0010 0000
0x40, 0x10,
// tsc:01 afc:01 cc:0000 pointer_field:0000 0000
0x50, 0x00,
// tid:0000 0010 ssi:0 0:0 r:00 sl:0000 0001 1100
0x02, 0x00, 0x1c,
// pn:0000 0000 0000 0001
0x00, 0x01,
// r:00 vn:00 000 cni:1 sn:0000 0000 lsn:0000 0000
0x01, 0x00, 0x00,
// r:000 ppid:0 0011 1111 1111
0x03, 0xff,
// r:0000 pil:0000 0000 0000
0x00, 0x00,
// h264
// st:0001 1010 r:000 epid:0 0000 0001 0001
0x1b, 0x00, 0x11,
// r:0000 esil:0000 0000 0000
0x00, 0x00,
// adts
// st:0000 1111 r:000 epid:0 0000 0001 0010
0x0f, 0x00, 0x12,
// r:0000 esil:0000 0000 0000
0x00, 0x00,
// timed metadata
// st:0001 0111 r:000 epid:0 0000 0001 0011
0x15, 0x00, 0x13,
// r:0000 esil:0000 0000 0000
0x00, 0x00,
// crc
0x00, 0x00, 0x00, 0x00
];
/*
Packet Header:
| sb | tei pusi tp pid:5 | pid | tsc afc cc |
with af:
| afl | ... | <data> |
without af:
| <data> |
PAT:
| pf? | ... |
| tid | ssi '0' r sl:4 | sl | tsi:8 |
| tsi | r vn cni | sn | lsn |
with program_number == '0':
| pn | pn | r np:5 | np |
otherwise:
| pn | pn | r pmp:5 | pmp |
*/
PAT = [
0x47, // sync byte
// tei:0 pusi:1 tp:0 pid:0 0000 0000 0000
0x40, 0x00,
// tsc:01 afc:01 cc:0000 pointer_field:0000 0000
0x50, 0x00,
// tid:0000 0000 ssi:0 0:0 r:00 sl:0000 0000 0000
0x00, 0x00, 0x00,
// tsi:0000 0000 0000 0000
0x00, 0x00,
// r:00 vn:00 000 cni:1 sn:0000 0000 lsn:0000 0000
0x01, 0x00, 0x00,
// pn:0000 0000 0000 0001
0x00, 0x01,
// r:000 pmp:0 0000 0010 0000
0x00, 0x10,
// crc32:0000 0000 0000 0000 0000 0000 0000 0000
0x00, 0x00, 0x00, 0x00
];
generatePMT = function(options) {
var PMT = [
0x47, // sync byte
// tei:0 pusi:1 tp:0 pid:0 0000 0010 0000
0x40, 0x10,
// tsc:01 afc:01 cc:0000 pointer_field:0000 0000
0x50, 0x00,
// tid:0000 0010 ssi:0 0:0 r:00 sl:0000 0001 1100
0x02, 0x00, 0x1c,
// pn:0000 0000 0000 0001
0x00, 0x01,
// r:00 vn:00 000 cni:1 sn:0000 0000 lsn:0000 0000
0x01, 0x00, 0x00,
// r:000 ppid:0 0011 1111 1111
0x03, 0xff,
// r:0000 pil:0000 0000 0000
0x00, 0x00];
if (options.hasVideo) {
// h264
PMT = PMT.concat([
// st:0001 1010 r:000 epid:0 0000 0001 0001
0x1b, 0x00, 0x11,
// r:0000 esil:0000 0000 0000
0x00, 0x00
]);
}
if (options.hasAudio) {
// adts
PMT = PMT.concat([
// st:0000 1111 r:000 epid:0 0000 0001 0010
0x0f, 0x00, 0x12,
// r:0000 esil:0000 0000 0000
0x00, 0x00
]);
}
if (options.hasMetadata) {
// timed metadata
PMT = PMT.concat([
// st:0001 0111 r:000 epid:0 0000 0001 0011
0x15, 0x00, 0x13,
// r:0000 esil:0000 0000 0000
0x00, 0x00
]);
}
// crc
return PMT.concat([0x00, 0x00, 0x00, 0x00]);
};
pesHeader = function(first, pts, dataLength) {
if (!dataLength) {
dataLength = 0;
} else {
// Add the pes header length (only the portion after the
// pes_packet_length field)
dataLength += 3;
}
// PES_packet(), Rec. ITU-T H.222.0, Table 2-21
var result = [
// pscp:0000 0000 0000 0000 0000 0001
0x00, 0x00, 0x01,
// sid:0000 0000 ppl:0000 0000 0000 0000
0x00, 0x00, 0x00,
// 10 psc:00 pp:0 dai:1 c:0 ooc:0
0x84,
// pdf:?0 ef:1 erf:0 dtmf:0 acif:0 pcf:0 pef:0
0x20 | (pts ? 0x80 : 0x00),
// phdl:0000 0000
(first ? 0x01 : 0x00) + (pts ? 0x05 : 0x00)
];
// Only store 15 bits of the PTS for QUnit.testing purposes
if (pts) {
var
pts32 = Math.floor(pts / 2), // right shift by 1
leftMostBit = ((pts32 & 0x80000000) >>> 31) & 0x01,
firstThree;
pts = pts & 0xffffffff; // remove left most bit
firstThree = (leftMostBit << 3) | (((pts & 0xc0000000) >>> 29) & 0x06) | 0x01;
result.push((0x2 << 4) | firstThree);
result.push((pts >>> 22) & 0xff);
result.push(((pts >>> 14) | 0x01) & 0xff);
result.push((pts >>> 7) & 0xff);
result.push(((pts << 1) | 0x01) & 0xff);
// Add the bytes spent on the pts info
dataLength += 5;
}
if (first) {
result.push(0x00);
dataLength += 1;
}
// Finally set the pes_packet_length field
result[4] = (dataLength & 0x0000FF00) >> 8;
result[5] = dataLength & 0x000000FF;
return result;
};
packetize = function(data) {
var packet = new Uint8Array(MP2T_PACKET_LENGTH);
packet.set(data);
return packet;
};
/**
* Helper function to create transport stream PES packets
* @param pid {uint8} - the program identifier (PID)
* @param data {arraylike} - the payload bytes
* @payload first {boolean} - true if this PES should be a payload
* unit start
*/
transportPacket = function(pid, data, first, pts, isVideoData) {
var
adaptationFieldLength = 188 - data.length - 14 - (first ? 1 : 0) - (pts ? 5 : 0),
// transport_packet(), Rec. ITU-T H.222.0, Table 2-2
result = [
// sync byte
0x47,
// tei:0 pusi:1 tp:0 pid:0 0000 0001 0001
0x40, pid,
// tsc:01 afc:11 cc:0000
0x70
].concat([
// afl
adaptationFieldLength & 0xff,
// di:0 rai:0 espi:0 pf:0 of:0 spf:0 tpdf:0 afef:0
0x00
]),
i;
i = adaptationFieldLength - 1;
while (i--) {
// stuffing_bytes
result.push(0xff);
}
// PES_packet(), Rec. ITU-T H.222.0, Table 2-21
result = result.concat(pesHeader(first, pts, isVideoData ? 0 : data.length));
return result.concat(data);
};
/**
* Helper function to create video PES packets
* @param data {arraylike} - the payload bytes
* @payload first {boolean} - true if this PES should be a payload
* unit start
*/
videoPes = function(data, first, pts) {
return transportPacket(0x11, [
// NAL unit start code
0x00, 0x00, 0x01
].concat(data), first, pts, true);
};
/**
* Helper function to create audio ADTS frame header
* @param dataLength {number} - the payload byte count
*/
adtsFrame = function(dataLength) {
var frameLength = dataLength + 7;
return [
0xff, 0xf1, // no CRC
0x10, // AAC Main, 44.1KHz
0xb0 | ((frameLength & 0x1800) >> 11), // 2 channels
(frameLength & 0x7f8) >> 3,
((frameLength & 0x07) << 5) + 7, // frame length in bytes
0x00 // one AAC per ADTS frame
];
};
/**
* Helper function to create audio PES packets
* @param data {arraylike} - the payload bytes
* @payload first {boolean} - true if this PES should be a payload
* unit start
*/
audioPes = function(data, first, pts) {
return transportPacket(0x12,
adtsFrame(data.length).concat(data),
first, pts);
};
timedMetadataPes = function(data) {
var id3 = id3Generator;
return transportPacket(0x13, id3.id3Tag(id3.id3Frame('PRIV', 0x00, 0x01)));
};
binaryStringToArrayOfBytes = function(string) {
var
array = [],
arrayIndex = 0,
stringIndex = 0;
while (stringIndex < string.length) {
array[arrayIndex] = parseInt(string.slice(stringIndex, stringIndex + 8), 2);
arrayIndex++;
// next byte
stringIndex += 8;
}
return array;
};
leftPad = function(string, targetLength) {
if (string.length >= targetLength) {
return string;
}
return new Array(targetLength - string.length + 1).join('0') + string;
};
module.exports = {
PMT: PMT,
PAT: PAT,
generatePMT: generatePMT,
pesHeader: pesHeader,
packetize: packetize,
transportPacket: transportPacket,
videoPes: videoPes,
adtsFrame: adtsFrame,
audioPes: audioPes,
timedMetadataPes: timedMetadataPes,
binaryStringToArrayOfBytes: binaryStringToArrayOfBytes,
leftPad: leftPad
};

File diff suppressed because it is too large Load diff

View file

@ -1,74 +1,74 @@
/**
* Helper functions for creating ID3 metadata.
*/
'use strict';
var stringToInts, stringToCString, id3Tag, id3Frame;
stringToInts = function(string) {
var result = [], i;
for (i = 0; i < string.length; i++) {
result[i] = string.charCodeAt(i);
}
return result;
};
stringToCString = function(string) {
return stringToInts(string).concat([0x00]);
};
id3Tag = function() {
var
frames = Array.prototype.concat.apply([], Array.prototype.slice.call(arguments)),
result = stringToInts('ID3').concat([
0x03, 0x00, // version 3.0 of ID3v2 (aka ID3v.2.3.0)
0x40, // flags. include an extended header
0x00, 0x00, 0x00, 0x00, // size. set later
// extended header
0x00, 0x00, 0x00, 0x06, // extended header size. no CRC
0x00, 0x00, // extended flags
0x00, 0x00, 0x00, 0x02 // size of padding
], frames),
size;
// size is stored as a sequence of four 7-bit integers with the
// high bit of each byte set to zero
size = result.length - 10;
result[6] = (size >>> 21) & 0x7f;
result[7] = (size >>> 14) & 0x7f;
result[8] = (size >>> 7) & 0x7f;
result[9] = size & 0x7f;
return result;
};
id3Frame = function(type) {
var result = stringToInts(type).concat([
0x00, 0x00, 0x00, 0x00, // size
0xe0, 0x00 // flags. tag/file alter preservation, read-only
]),
size = result.length - 10;
// append the fields of the ID3 frame
result = result.concat.apply(result, Array.prototype.slice.call(arguments, 1));
// set the size
size = result.length - 10;
result[4] = (size >>> 21) & 0x7f;
result[5] = (size >>> 14) & 0x7f;
result[6] = (size >>> 7) & 0x7f;
result[7] = size & 0x7f;
return result;
};
module.exports = {
stringToInts: stringToInts,
stringToCString: stringToCString,
id3Tag: id3Tag,
id3Frame: id3Frame
};
/**
* Helper functions for creating ID3 metadata.
*/
'use strict';
var stringToInts, stringToCString, id3Tag, id3Frame;
stringToInts = function(string) {
var result = [], i;
for (i = 0; i < string.length; i++) {
result[i] = string.charCodeAt(i);
}
return result;
};
stringToCString = function(string) {
return stringToInts(string).concat([0x00]);
};
id3Tag = function() {
var
frames = Array.prototype.concat.apply([], Array.prototype.slice.call(arguments)),
result = stringToInts('ID3').concat([
0x03, 0x00, // version 3.0 of ID3v2 (aka ID3v.2.3.0)
0x40, // flags. include an extended header
0x00, 0x00, 0x00, 0x00, // size. set later
// extended header
0x00, 0x00, 0x00, 0x06, // extended header size. no CRC
0x00, 0x00, // extended flags
0x00, 0x00, 0x00, 0x02 // size of padding
], frames),
size;
// size is stored as a sequence of four 7-bit integers with the
// high bit of each byte set to zero
size = result.length - 10;
result[6] = (size >>> 21) & 0x7f;
result[7] = (size >>> 14) & 0x7f;
result[8] = (size >>> 7) & 0x7f;
result[9] = size & 0x7f;
return result;
};
id3Frame = function(type) {
var result = stringToInts(type).concat([
0x00, 0x00, 0x00, 0x00, // size
0xe0, 0x00 // flags. tag/file alter preservation, read-only
]),
size = result.length - 10;
// append the fields of the ID3 frame
result = result.concat.apply(result, Array.prototype.slice.call(arguments, 1));
// set the size
size = result.length - 10;
result[4] = (size >>> 21) & 0x7f;
result[5] = (size >>> 14) & 0x7f;
result[6] = (size >>> 7) & 0x7f;
result[7] = size & 0x7f;
return result;
};
module.exports = {
stringToInts: stringToInts,
stringToCString: stringToCString,
id3Tag: id3Tag,
id3Frame: id3Frame
};

File diff suppressed because it is too large Load diff

View file

@ -1,315 +1,315 @@
/**
* Helper functions for creating test MP4 data.
*/
'use strict';
var box, typeBytes, unityMatrix;
module.exports = {};
// ----------------------
// Box Generation Helpers
// ----------------------
module.exports.typeBytes = typeBytes = function(type) {
return [
type.charCodeAt(0),
type.charCodeAt(1),
type.charCodeAt(2),
type.charCodeAt(3)
];
};
module.exports.box = box = function(type) {
var
array = Array.prototype.slice.call(arguments, 1),
result = [],
size,
i;
// "unwrap" any arrays that were passed as arguments
// e.g. box('etc', 1, [2, 3], 4) -> box('etc', 1, 2, 3, 4)
for (i = 0; i < array.length; i++) {
if (array[i] instanceof Array) {
array.splice.apply(array, [i, 1].concat(array[i]));
}
}
size = 8 + array.length;
result[0] = (size & 0xFF000000) >> 24;
result[1] = (size & 0x00FF0000) >> 16;
result[2] = (size & 0x0000FF00) >> 8;
result[3] = size & 0xFF;
result = result.concat(typeBytes(type));
result = result.concat(array);
return result;
};
module.exports.unityMatrix = unityMatrix = [
0, 0, 0x10, 0,
0, 0, 0, 0,
0, 0, 0, 0,
0, 0, 0, 0,
0, 0, 0x10, 0,
0, 0, 0, 0,
0, 0, 0, 0,
0, 0, 0, 0,
0x40, 0, 0, 0
];
// ------------
// Example Data
// ------------
module.exports.sampleMoov =
box('moov',
box('mvhd',
0x01, // version 1
0x00, 0x00, 0x00, // flags
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x01, // creation_time
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x02, // modification_time
0x00, 0x00, 0x03, 0xe8, // timescale = 1000
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x02, 0x58, // 600 = 0x258 duration
0x00, 0x01, 0x00, 0x00, // 1.0 rate
0x01, 0x00, // 1.0 volume
0x00, 0x00, // reserved
0x00, 0x00, 0x00, 0x00, // reserved
0x00, 0x00, 0x00, 0x00, // reserved
unityMatrix,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, // pre_defined
0x00, 0x00, 0x00, 0x02), // next_track_ID
box('trak',
box('tkhd',
0x01, // version 1
0x00, 0x00, 0x00, // flags
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x02, // creation_time
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x03, // modification_time
0x00, 0x00, 0x00, 0x01, // track_ID
0x00, 0x00, 0x00, 0x00, // reserved
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x02, 0x58, // 600 = 0x258 duration
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, // reserved
0x00, 0x00, // layer
0x00, 0x00, // alternate_group
0x00, 0x00, // non-audio track volume
0x00, 0x00, // reserved
unityMatrix,
0x01, 0x2c, 0x00, 0x00, // 300 in 16.16 fixed-point
0x00, 0x96, 0x00, 0x00), // 150 in 16.16 fixed-point
box('edts',
box('elst',
0x00, // version
0x00, 0x00, 0x00, // flags
0x00, 0x00, 0x00, 0x01, // entry_count
0x00, 0x00, 0x00, 0x00, // segment_duration
0x00, 0x00, 0x04, 0x00, // media_time
0x00, 0x01, 0x80, 0x00)), // media_rate
box('mdia',
box('mdhd',
0x01, // version 1
0x00, 0x00, 0x00, // flags
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x02, // creation_time
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x03, // modification_time
0x00, 0x01, 0x5f, 0x90, // timescale = 90000
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x02, 0x58, // 600 = 0x258 duration
0x15, 0xc7, // 'eng' language
0x00, 0x00),
box('hdlr',
0x01, // version 1
0x00, 0x00, 0x00, // flags
0x00, 0x00, 0x00, 0x00, // pre_defined
typeBytes('vide'), // handler_type
0x00, 0x00, 0x00, 0x00, // reserved
0x00, 0x00, 0x00, 0x00, // reserved
0x00, 0x00, 0x00, 0x00, // reserved
typeBytes('one'), 0x00), // name
box('minf',
box('dinf',
box('dref',
0x01, // version 1
0x00, 0x00, 0x00, // flags
0x00, 0x00, 0x00, 0x01, // entry_count
box('url ',
0x00, // version
0x00, 0x00, 0x01))), // flags
box('stbl',
box('stsd',
0x01, // version 1
0x00, 0x00, 0x00, // flags
0x00, 0x00, 0x00, 0x00, // entry_count
box('avc1',
0x00, 0x00, 0x00, 0x00, // box content
0x00, 0x00, 0x00, 0x00, // box content
0x00, 0x00, 0x00, 0x00, // box content
0x00, 0x00, 0x00, 0x00, // box content
0x00, 0x00, 0x00, 0x00, // box content
0x00, 0x00, 0x00, 0x00, // box content
0x00, 0x00, 0x00, 0x00, // box content
0x00, 0x00, 0x00, 0x00, // box content
0x00, 0x00, 0x00, 0x00, // box content
0x00, 0x00, 0x00, 0x00, // box content
0x00, 0x00, 0x00, 0x00, // box content
0x00, 0x00, 0x00, 0x00, // box content
0x00, 0x00, 0x00, 0x00, // box content
0x00, 0x00, 0x00, 0x00, // box content
0x00, 0x00, 0x00, 0x00, // box content
0x00, 0x00, 0x00, 0x00, // box content
0x00, 0x00, 0x00, 0x00, // box content
0x00, 0x00, 0x00, 0x00, // box content
0x00, 0x00, 0x00, 0x00, // box content
0x00, 0x00, 0x00, 0x00, // box content
0x00, 0x00, // box content
typeBytes('avcC'), // codec profile type
0x00, 0x4d, 0x40, 0x0d)), // codec parameters
box('stts',
0x01, // version 1
0x00, 0x00, 0x00, // flags
0x00, 0x00, 0x00, 0x01, // entry_count
0x00, 0x00, 0x00, 0x01, // sample_count
0x00, 0x00, 0x00, 0x01), // sample_delta
box('stsc',
0x01, // version 1
0x00, 0x00, 0x00, // flags
0x00, 0x00, 0x00, 0x01, // entry_count
0x00, 0x00, 0x00, 0x02, // first_chunk
0x00, 0x00, 0x00, 0x03, // samples_per_chunk
0x00, 0x00, 0x00, 0x01), // sample_description_index
box('stco',
0x01, // version 1
0x00, 0x00, 0x00, // flags
0x00, 0x00, 0x00, 0x01, // entry_count
0x00, 0x00, 0x00, 0x01), // chunk_offset
box('stss',
0x00, // version 0
0x00, 0x00, 0x00, // flags
0x00, 0x00, 0x00, 0x01, // entry_count
0x00, 0x00, 0x00, 0x01), // sync_sample
box('ctts',
0x00, // version 0
0x00, 0x00, 0x00, // flags
0x00, 0x00, 0x00, 0x01, // entry_count
0x00, 0x00, 0x00, 0x01, // sample_count
0x00, 0x00, 0x00, 0x01))))), // sample_offset
box('trak',
box('tkhd',
0x01, // version 1
0x00, 0x00, 0x00, // flags
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x02, // creation_time
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x03, // modification_time
0x00, 0x00, 0x00, 0x02, // track_ID
0x00, 0x00, 0x00, 0x00, // reserved
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x02, 0x58, // 600 = 0x258 duration
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, // reserved
0x00, 0x00, // layer
0x00, 0x00, // alternate_group
0x00, 0x00, // non-audio track volume
0x00, 0x00, // reserved
unityMatrix,
0x01, 0x2c, 0x00, 0x00, // 300 in 16.16 fixed-point
0x00, 0x96, 0x00, 0x00), // 150 in 16.16 fixed-point
box('edts',
box('elst',
0x01, // version
0x00, 0x00, 0x00, // flags
0x00, 0x00, 0x00, 0x01, // entry_count
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // segment_duration
0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // media_time
0x00, 0x01, 0x80, 0x00)), // media_rate
box('mdia',
box('mdhd',
0x01, // version 1
0x00, 0x00, 0x00, // flags
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x02, // creation_time
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x03, // modification_time
0x00, 0x01, 0x5f, 0x90, // timescale = 90000
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x02, 0x58, // 600 = 0x258 duration
0x15, 0xc7, // 'eng' language
0x00, 0x00),
box('hdlr',
0x01, // version 1
0x00, 0x00, 0x00, // flags
0x00, 0x00, 0x00, 0x00, // pre_defined
typeBytes('soun'), // handler_type
0x00, 0x00, 0x00, 0x00, // reserved
0x00, 0x00, 0x00, 0x00, // reserved
0x00, 0x00, 0x00, 0x00, // reserved
typeBytes('one'), 0x00), // name
box('minf',
box('dinf',
box('dref',
0x01, // version 1
0x00, 0x00, 0x00, // flags
0x00, 0x00, 0x00, 0x01, // entry_count
box('url ',
0x00, // version
0x00, 0x00, 0x01))), // flags
box('stbl',
box('stsd',
0x01, // version 1
0x00, 0x00, 0x00, // flags
0x00, 0x00, 0x00, 0x00, // entry_count
box('mp4a',
0x00, 0x00, 0x00, 0x00, // box content
0x00, 0x00, 0x00, 0x00, // box content
0x00, 0x00, 0x00, 0x00, // box content
0x00, 0x00, 0x00, 0x00, // box content
0x00, 0x00, 0x00, 0x00, // box content
0x00, 0x00, 0x00, 0x00, // box content
0x00, 0x00, 0x00, 0x00, // box content
0x00, 0x00, 0x00, 0x00, // box content
typeBytes('esds'), // codec profile type
0x00, 0x00, 0x00, 0x00, // box content
0x00, 0x00, 0x00, 0x00, // box content
0x00, 0x00, 0x00, // box content
0x40, 0x0a, // codec params
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00)), // codec params
box('stts',
0x01, // version 1
0x00, 0x00, 0x00, // flags
0x00, 0x00, 0x00, 0x01, // entry_count
0x00, 0x00, 0x00, 0x01, // sample_count
0x00, 0x00, 0x00, 0x01), // sample_delta
box('stsc',
0x01, // version 1
0x00, 0x00, 0x00, // flags
0x00, 0x00, 0x00, 0x01, // entry_count
0x00, 0x00, 0x00, 0x02, // first_chunk
0x00, 0x00, 0x00, 0x03, // samples_per_chunk
0x00, 0x00, 0x00, 0x01), // sample_description_index
box('ctts',
0x01, // version 1
0x00, 0x00, 0x00, // flags
0x00, 0x00, 0x00, 0x01, // entry_count
0x00, 0x00, 0x00, 0x01, // sample_count
0xff, 0xff, 0xff, 0xff), // sample_offset
box('stco',
0x01, // version 1
0x00, 0x00, 0x00, // flags
0x00, 0x00, 0x00, 0x01, // entry_count
0x00, 0x00, 0x00, 0x01)))))); // chunk_offset
/**
* Helper functions for creating test MP4 data.
*/
'use strict';
var box, typeBytes, unityMatrix;
module.exports = {};
// ----------------------
// Box Generation Helpers
// ----------------------
module.exports.typeBytes = typeBytes = function(type) {
return [
type.charCodeAt(0),
type.charCodeAt(1),
type.charCodeAt(2),
type.charCodeAt(3)
];
};
module.exports.box = box = function(type) {
var
array = Array.prototype.slice.call(arguments, 1),
result = [],
size,
i;
// "unwrap" any arrays that were passed as arguments
// e.g. box('etc', 1, [2, 3], 4) -> box('etc', 1, 2, 3, 4)
for (i = 0; i < array.length; i++) {
if (array[i] instanceof Array) {
array.splice.apply(array, [i, 1].concat(array[i]));
}
}
size = 8 + array.length;
result[0] = (size & 0xFF000000) >> 24;
result[1] = (size & 0x00FF0000) >> 16;
result[2] = (size & 0x0000FF00) >> 8;
result[3] = size & 0xFF;
result = result.concat(typeBytes(type));
result = result.concat(array);
return result;
};
module.exports.unityMatrix = unityMatrix = [
0, 0, 0x10, 0,
0, 0, 0, 0,
0, 0, 0, 0,
0, 0, 0, 0,
0, 0, 0x10, 0,
0, 0, 0, 0,
0, 0, 0, 0,
0, 0, 0, 0,
0x40, 0, 0, 0
];
// ------------
// Example Data
// ------------
module.exports.sampleMoov =
box('moov',
box('mvhd',
0x01, // version 1
0x00, 0x00, 0x00, // flags
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x01, // creation_time
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x02, // modification_time
0x00, 0x00, 0x03, 0xe8, // timescale = 1000
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x02, 0x58, // 600 = 0x258 duration
0x00, 0x01, 0x00, 0x00, // 1.0 rate
0x01, 0x00, // 1.0 volume
0x00, 0x00, // reserved
0x00, 0x00, 0x00, 0x00, // reserved
0x00, 0x00, 0x00, 0x00, // reserved
unityMatrix,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, // pre_defined
0x00, 0x00, 0x00, 0x02), // next_track_ID
box('trak',
box('tkhd',
0x01, // version 1
0x00, 0x00, 0x00, // flags
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x02, // creation_time
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x03, // modification_time
0x00, 0x00, 0x00, 0x01, // track_ID
0x00, 0x00, 0x00, 0x00, // reserved
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x02, 0x58, // 600 = 0x258 duration
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, // reserved
0x00, 0x00, // layer
0x00, 0x00, // alternate_group
0x00, 0x00, // non-audio track volume
0x00, 0x00, // reserved
unityMatrix,
0x01, 0x2c, 0x00, 0x00, // 300 in 16.16 fixed-point
0x00, 0x96, 0x00, 0x00), // 150 in 16.16 fixed-point
box('edts',
box('elst',
0x00, // version
0x00, 0x00, 0x00, // flags
0x00, 0x00, 0x00, 0x01, // entry_count
0x00, 0x00, 0x00, 0x00, // segment_duration
0x00, 0x00, 0x04, 0x00, // media_time
0x00, 0x01, 0x80, 0x00)), // media_rate
box('mdia',
box('mdhd',
0x01, // version 1
0x00, 0x00, 0x00, // flags
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x02, // creation_time
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x03, // modification_time
0x00, 0x01, 0x5f, 0x90, // timescale = 90000
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x02, 0x58, // 600 = 0x258 duration
0x15, 0xc7, // 'eng' language
0x00, 0x00),
box('hdlr',
0x01, // version 1
0x00, 0x00, 0x00, // flags
0x00, 0x00, 0x00, 0x00, // pre_defined
typeBytes('vide'), // handler_type
0x00, 0x00, 0x00, 0x00, // reserved
0x00, 0x00, 0x00, 0x00, // reserved
0x00, 0x00, 0x00, 0x00, // reserved
typeBytes('one'), 0x00), // name
box('minf',
box('dinf',
box('dref',
0x01, // version 1
0x00, 0x00, 0x00, // flags
0x00, 0x00, 0x00, 0x01, // entry_count
box('url ',
0x00, // version
0x00, 0x00, 0x01))), // flags
box('stbl',
box('stsd',
0x01, // version 1
0x00, 0x00, 0x00, // flags
0x00, 0x00, 0x00, 0x00, // entry_count
box('avc1',
0x00, 0x00, 0x00, 0x00, // box content
0x00, 0x00, 0x00, 0x00, // box content
0x00, 0x00, 0x00, 0x00, // box content
0x00, 0x00, 0x00, 0x00, // box content
0x00, 0x00, 0x00, 0x00, // box content
0x00, 0x00, 0x00, 0x00, // box content
0x00, 0x00, 0x00, 0x00, // box content
0x00, 0x00, 0x00, 0x00, // box content
0x00, 0x00, 0x00, 0x00, // box content
0x00, 0x00, 0x00, 0x00, // box content
0x00, 0x00, 0x00, 0x00, // box content
0x00, 0x00, 0x00, 0x00, // box content
0x00, 0x00, 0x00, 0x00, // box content
0x00, 0x00, 0x00, 0x00, // box content
0x00, 0x00, 0x00, 0x00, // box content
0x00, 0x00, 0x00, 0x00, // box content
0x00, 0x00, 0x00, 0x00, // box content
0x00, 0x00, 0x00, 0x00, // box content
0x00, 0x00, 0x00, 0x00, // box content
0x00, 0x00, 0x00, 0x00, // box content
0x00, 0x00, // box content
typeBytes('avcC'), // codec profile type
0x00, 0x4d, 0x40, 0x0d)), // codec parameters
box('stts',
0x01, // version 1
0x00, 0x00, 0x00, // flags
0x00, 0x00, 0x00, 0x01, // entry_count
0x00, 0x00, 0x00, 0x01, // sample_count
0x00, 0x00, 0x00, 0x01), // sample_delta
box('stsc',
0x01, // version 1
0x00, 0x00, 0x00, // flags
0x00, 0x00, 0x00, 0x01, // entry_count
0x00, 0x00, 0x00, 0x02, // first_chunk
0x00, 0x00, 0x00, 0x03, // samples_per_chunk
0x00, 0x00, 0x00, 0x01), // sample_description_index
box('stco',
0x01, // version 1
0x00, 0x00, 0x00, // flags
0x00, 0x00, 0x00, 0x01, // entry_count
0x00, 0x00, 0x00, 0x01), // chunk_offset
box('stss',
0x00, // version 0
0x00, 0x00, 0x00, // flags
0x00, 0x00, 0x00, 0x01, // entry_count
0x00, 0x00, 0x00, 0x01), // sync_sample
box('ctts',
0x00, // version 0
0x00, 0x00, 0x00, // flags
0x00, 0x00, 0x00, 0x01, // entry_count
0x00, 0x00, 0x00, 0x01, // sample_count
0x00, 0x00, 0x00, 0x01))))), // sample_offset
box('trak',
box('tkhd',
0x01, // version 1
0x00, 0x00, 0x00, // flags
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x02, // creation_time
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x03, // modification_time
0x00, 0x00, 0x00, 0x02, // track_ID
0x00, 0x00, 0x00, 0x00, // reserved
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x02, 0x58, // 600 = 0x258 duration
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, // reserved
0x00, 0x00, // layer
0x00, 0x00, // alternate_group
0x00, 0x00, // non-audio track volume
0x00, 0x00, // reserved
unityMatrix,
0x01, 0x2c, 0x00, 0x00, // 300 in 16.16 fixed-point
0x00, 0x96, 0x00, 0x00), // 150 in 16.16 fixed-point
box('edts',
box('elst',
0x01, // version
0x00, 0x00, 0x00, // flags
0x00, 0x00, 0x00, 0x01, // entry_count
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // segment_duration
0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // media_time
0x00, 0x01, 0x80, 0x00)), // media_rate
box('mdia',
box('mdhd',
0x01, // version 1
0x00, 0x00, 0x00, // flags
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x02, // creation_time
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x03, // modification_time
0x00, 0x01, 0x5f, 0x90, // timescale = 90000
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x02, 0x58, // 600 = 0x258 duration
0x15, 0xc7, // 'eng' language
0x00, 0x00),
box('hdlr',
0x01, // version 1
0x00, 0x00, 0x00, // flags
0x00, 0x00, 0x00, 0x00, // pre_defined
typeBytes('soun'), // handler_type
0x00, 0x00, 0x00, 0x00, // reserved
0x00, 0x00, 0x00, 0x00, // reserved
0x00, 0x00, 0x00, 0x00, // reserved
typeBytes('one'), 0x00), // name
box('minf',
box('dinf',
box('dref',
0x01, // version 1
0x00, 0x00, 0x00, // flags
0x00, 0x00, 0x00, 0x01, // entry_count
box('url ',
0x00, // version
0x00, 0x00, 0x01))), // flags
box('stbl',
box('stsd',
0x01, // version 1
0x00, 0x00, 0x00, // flags
0x00, 0x00, 0x00, 0x00, // entry_count
box('mp4a',
0x00, 0x00, 0x00, 0x00, // box content
0x00, 0x00, 0x00, 0x00, // box content
0x00, 0x00, 0x00, 0x00, // box content
0x00, 0x00, 0x00, 0x00, // box content
0x00, 0x00, 0x00, 0x00, // box content
0x00, 0x00, 0x00, 0x00, // box content
0x00, 0x00, 0x00, 0x00, // box content
0x00, 0x00, 0x00, 0x00, // box content
typeBytes('esds'), // codec profile type
0x00, 0x00, 0x00, 0x00, // box content
0x00, 0x00, 0x00, 0x00, // box content
0x00, 0x00, 0x00, // box content
0x40, 0x0a, // codec params
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00)), // codec params
box('stts',
0x01, // version 1
0x00, 0x00, 0x00, // flags
0x00, 0x00, 0x00, 0x01, // entry_count
0x00, 0x00, 0x00, 0x01, // sample_count
0x00, 0x00, 0x00, 0x01), // sample_delta
box('stsc',
0x01, // version 1
0x00, 0x00, 0x00, // flags
0x00, 0x00, 0x00, 0x01, // entry_count
0x00, 0x00, 0x00, 0x02, // first_chunk
0x00, 0x00, 0x00, 0x03, // samples_per_chunk
0x00, 0x00, 0x00, 0x01), // sample_description_index
box('ctts',
0x01, // version 1
0x00, 0x00, 0x00, // flags
0x00, 0x00, 0x00, 0x01, // entry_count
0x00, 0x00, 0x00, 0x01, // sample_count
0xff, 0xff, 0xff, 0xff), // sample_offset
box('stco',
0x01, // version 1
0x00, 0x00, 0x00, // flags
0x00, 0x00, 0x00, 0x01, // entry_count
0x00, 0x00, 0x00, 0x01)))))); // chunk_offset

View file

@ -1,137 +1,137 @@
/**
* Helper functions for creating 608/708 SEI NAL units
*/
'use strict';
var box = require('./mp4-helpers').box;
// Create SEI nal-units from Caption packets
var makeSeiFromCaptionPacket = function(caption) {
return {
pts: caption.pts,
dts: caption.dts,
nalUnitType: 'sei_rbsp',
escapedRBSP: new Uint8Array([
0x04, // payload_type === user_data_registered_itu_t_t35
0x0e, // payload_size
181, // itu_t_t35_country_code
0x00, 0x31, // itu_t_t35_provider_code
0x47, 0x41, 0x39, 0x34, // user_identifier, "GA94"
0x03, // user_data_type_code, 0x03 is cc_data
// 110 00001
0xc1, // process_cc_data, cc_count
0xff, // reserved
// 1111 1100
(0xfc | caption.type), // cc_valid, cc_type (608, field 1)
(caption.ccData & 0xff00) >> 8, // cc_data_1
caption.ccData & 0xff, // cc_data_2 without parity bit set
0xff // marker_bits
])
};
};
// Create SEI nal-units from Caption packets
var makeSeiFromMultipleCaptionPackets = function(captionHash) {
var pts = captionHash.pts,
dts = captionHash.dts,
captions = captionHash.captions;
var data = [];
captions.forEach(function(caption) {
data.push(0xfc | caption.type);
data.push((caption.ccData & 0xff00) >> 8);
data.push(caption.ccData & 0xff);
});
return {
pts: pts,
dts: dts,
nalUnitType: 'sei_rbsp',
escapedRBSP: new Uint8Array([
0x04, // payload_type === user_data_registered_itu_t_t35
(0x0b + (captions.length * 3)), // payload_size
181, // itu_t_t35_country_code
0x00, 0x31, // itu_t_t35_provider_code
0x47, 0x41, 0x39, 0x34, // user_identifier, "GA94"
0x03, // user_data_type_code, 0x03 is cc_data
// 110 00001
(0x6 << 5) | captions.length, // process_cc_data, cc_count
0xff // reserved
].concat(data).concat([0xff /* marker bits */])
)
};
};
var makeMdatFromCaptionPackets = function(packets) {
var mdat = ['mdat'];
var seis = packets.map(makeSeiFromCaptionPacket);
seis.forEach(function(sei) {
mdat.push(0x00);
mdat.push(0x00);
mdat.push(0x00);
mdat.push(sei.escapedRBSP.length + 1); // nal length
mdat.push(0x06); // declare nal type as SEI
// SEI message
for (var i = 0; i < sei.escapedRBSP.length; i++) {
var byte = sei.escapedRBSP[i];
mdat.push(byte);
}
});
return box.apply(null, mdat);
};
// Returns a ccData byte-pair for a two character string. That is,
// it converts a string like 'hi' into the two-byte number that
// would be parsed back as 'hi' when provided as ccData.
var characters = function(text) {
if (text.length !== 2) {
throw new Error('ccdata must be specified two characters at a time');
}
return (text.charCodeAt(0) << 8) | text.charCodeAt(1);
};
// Returns a ccData byte-pair including
// Header for 708 packet
// Header for the first service block
// seq should increment by 1 for each byte pair mod 3 (0,1,2,0,1,2,...)
// sizeCode is the number of byte pairs in the packet (including header)
// serviceNum is the service number of the first service block
// blockSize is the size of the first service block in bytes (no header)
// If there's only one service block, the blockSize should be (sizeCode-1)*2
var packetHeader708 = function(seq, sizeCode, serviceNum, blockSize) {
var b1 = (seq << 6) | sizeCode;
var b2 = (serviceNum << 5) | blockSize;
return (b1 << 8) | b2;
};
// Returns a ccData byte-pair to execute a 708 DSW command
// Takes an array of window indicies to display
var displayWindows708 = function(windows) {
var cmd = 0x8900;
windows.forEach(function(winIdx) {
cmd |= (0x01 << winIdx);
});
return cmd;
};
module.exports = {
makeSeiFromCaptionPacket: makeSeiFromCaptionPacket,
makeSeiFromMultipleCaptionPackets: makeSeiFromMultipleCaptionPackets,
makeMdatFromCaptionPackets: makeMdatFromCaptionPackets,
characters: characters,
packetHeader708: packetHeader708,
displayWindows708: displayWindows708
};
/**
* Helper functions for creating 608/708 SEI NAL units
*/
'use strict';
var box = require('./mp4-helpers').box;
// Create SEI nal-units from Caption packets
var makeSeiFromCaptionPacket = function(caption) {
return {
pts: caption.pts,
dts: caption.dts,
nalUnitType: 'sei_rbsp',
escapedRBSP: new Uint8Array([
0x04, // payload_type === user_data_registered_itu_t_t35
0x0e, // payload_size
181, // itu_t_t35_country_code
0x00, 0x31, // itu_t_t35_provider_code
0x47, 0x41, 0x39, 0x34, // user_identifier, "GA94"
0x03, // user_data_type_code, 0x03 is cc_data
// 110 00001
0xc1, // process_cc_data, cc_count
0xff, // reserved
// 1111 1100
(0xfc | caption.type), // cc_valid, cc_type (608, field 1)
(caption.ccData & 0xff00) >> 8, // cc_data_1
caption.ccData & 0xff, // cc_data_2 without parity bit set
0xff // marker_bits
])
};
};
// Create SEI nal-units from Caption packets
var makeSeiFromMultipleCaptionPackets = function(captionHash) {
var pts = captionHash.pts,
dts = captionHash.dts,
captions = captionHash.captions;
var data = [];
captions.forEach(function(caption) {
data.push(0xfc | caption.type);
data.push((caption.ccData & 0xff00) >> 8);
data.push(caption.ccData & 0xff);
});
return {
pts: pts,
dts: dts,
nalUnitType: 'sei_rbsp',
escapedRBSP: new Uint8Array([
0x04, // payload_type === user_data_registered_itu_t_t35
(0x0b + (captions.length * 3)), // payload_size
181, // itu_t_t35_country_code
0x00, 0x31, // itu_t_t35_provider_code
0x47, 0x41, 0x39, 0x34, // user_identifier, "GA94"
0x03, // user_data_type_code, 0x03 is cc_data
// 110 00001
(0x6 << 5) | captions.length, // process_cc_data, cc_count
0xff // reserved
].concat(data).concat([0xff /* marker bits */])
)
};
};
var makeMdatFromCaptionPackets = function(packets) {
var mdat = ['mdat'];
var seis = packets.map(makeSeiFromCaptionPacket);
seis.forEach(function(sei) {
mdat.push(0x00);
mdat.push(0x00);
mdat.push(0x00);
mdat.push(sei.escapedRBSP.length + 1); // nal length
mdat.push(0x06); // declare nal type as SEI
// SEI message
for (var i = 0; i < sei.escapedRBSP.length; i++) {
var byte = sei.escapedRBSP[i];
mdat.push(byte);
}
});
return box.apply(null, mdat);
};
// Returns a ccData byte-pair for a two character string. That is,
// it converts a string like 'hi' into the two-byte number that
// would be parsed back as 'hi' when provided as ccData.
var characters = function(text) {
if (text.length !== 2) {
throw new Error('ccdata must be specified two characters at a time');
}
return (text.charCodeAt(0) << 8) | text.charCodeAt(1);
};
// Returns a ccData byte-pair including
// Header for 708 packet
// Header for the first service block
// seq should increment by 1 for each byte pair mod 3 (0,1,2,0,1,2,...)
// sizeCode is the number of byte pairs in the packet (including header)
// serviceNum is the service number of the first service block
// blockSize is the size of the first service block in bytes (no header)
// If there's only one service block, the blockSize should be (sizeCode-1)*2
var packetHeader708 = function(seq, sizeCode, serviceNum, blockSize) {
var b1 = (seq << 6) | sizeCode;
var b2 = (serviceNum << 5) | blockSize;
return (b1 << 8) | b2;
};
// Returns a ccData byte-pair to execute a 708 DSW command
// Takes an array of window indicies to display
var displayWindows708 = function(windows) {
var cmd = 0x8900;
windows.forEach(function(winIdx) {
cmd |= (0x01 << winIdx);
});
return cmd;
};
module.exports = {
makeSeiFromCaptionPacket: makeSeiFromCaptionPacket,
makeSeiFromMultipleCaptionPackets: makeSeiFromMultipleCaptionPackets,
makeMdatFromCaptionPackets: makeMdatFromCaptionPackets,
characters: characters,
packetHeader708: packetHeader708,
displayWindows708: displayWindows708
};