7cf3d82d by David LaPalomento

Create mp4 generator

Build helper classes to construct boxes for mp4s. Use the mp4 inspector to validate the generated boxes are sensical. Remove the stubbed Transmuxer because it's not implemented yet and it may not make sense once things come together a bit more. Left some MediaSource test code in mp4.html for debugging purposes. The tests all pass but we're still not quite generating a valid init segment.
1 parent 49f0fe0f
(function(window, videojs, undefined) {
'use strict';
var box, dinf, ftyp, minf, moov, mvex, mvhd, trak, tkhd, mdia, mdhd, hdlr, stbl,
stsd, types, MAJOR_BRAND, MINOR_VERSION, VIDEO_HDLR, DREF, TREX, Uint8Array, DataView;
Uint8Array = window.Uint8Array;
DataView = window.DataView;
// pre-calculate constants
(function() {
var i;
types = {
avc1: [], // codingname
dinf: [],
dref: [],
ftyp: [],
hdlr: [],
mdhd: [],
mdia: [],
minf: [],
moov: [],
mvex: [],
mvhd: [],
stbl: [],
stco: [],
stsc: [],
stsd: [],
stts: [],
trak: [],
trex: [],
tkhd: []
};
for (i in types) {
if (types.hasOwnProperty(i)) {
types[i] = [
i.charCodeAt(0),
i.charCodeAt(1),
i.charCodeAt(2),
i.charCodeAt(3)
];
}
}
MAJOR_BRAND = new Uint8Array([
'i'.charCodeAt(0),
's'.charCodeAt(0),
'o'.charCodeAt(0),
'm'.charCodeAt(0)
]);
MINOR_VERSION = new Uint8Array([0, 0, 0, 1]);
VIDEO_HDLR = new Uint8Array([
0x00, // version 0
0x00, 0x00, 0x00, // flags
0x00, 0x00, 0x00, 0x00, // pre_defined
0x76, 0x69, 0x64, 0x65, // handler_type: 'vide'
0x00, 0x00, 0x00, 0x00, // reserved
0x00, 0x00, 0x00, 0x00, // reserved
0x00, 0x00, 0x00, 0x00, // reserved
0x56, 0x69, 0x64, 0x65,
0x6f, 0x48, 0x61, 0x6e,
0x64, 0x6c, 0x65, 0x72, 0x00 // name: 'VideoHandler'
]);
DREF = new Uint8Array([
0x00, // version 0
0x00, 0x00, 0x00, // flags
0x00, 0x00, 0x00, 0x00 // entry_count
]);
TREX = new Uint8Array([
0x00, // version 0
0x00, 0x00, 0x00, // flags
0x00, 0x00, 0x00, 0x01, // track_ID
0x00, 0x00, 0x00, 0x01, // default_sample_description_index
0x00, 0x00, 0x00, 0x00, // default_sample_duration
0x00, 0x00, 0x00, 0x00, // default_sample_size
0x00, 0x01, 0x00, 0x01 // default_sample_flags
]);
})();
box = function(type) {
var
payload = Array.prototype.slice.call(arguments, 1),
size = 0,
i = payload.length,
result,
view;
// calculate the total size we need to allocate
while (i--) {
size += payload[i].byteLength;
}
result = new Uint8Array(size + 8);
view = new DataView(result.buffer, result.byteOffset, result.byteLength);
view.setUint32(0, result.byteLength);
result.set(type, 4);
// copy the payload into the result
for (i = 0, size = 8; i < payload.length; i++) {
result.set(payload[i], size);
size += payload[i].byteLength;
}
return result;
};
dinf = function() {
return box(types.dinf, box(types.dref, DREF));
};
ftyp = function() {
return box(types.ftyp, MAJOR_BRAND, MINOR_VERSION, MAJOR_BRAND);
};
hdlr = function() {
return box(types.hdlr, VIDEO_HDLR);
};
mdhd = function(duration) {
return box(types.mdhd, new Uint8Array([
0x00, // version 0
0x00, 0x00, 0x00, // flags
0x00, 0x00, 0x00, 0x02, // creation_time
0x00, 0x00, 0x00, 0x03, // modification_time
0x00, 0x00, 0x00, 0x3c, // timescale
(duration & 0xFF000000) >> 24,
(duration & 0xFF0000) >> 16,
(duration & 0xFF00) >> 8,
duration & 0xFF, // duration
0x55, 0xc4, // 'und' language (undetermined)
0x00, 0x00
]));
};
mdia = function(duration, width, height) {
return box(types.mdia, mdhd(duration), hdlr(), minf(width, height));
};
minf = function(width, height) {
return box(types.minf, dinf(), stbl(width, height));
};
moov = function(duration, width, height) {
return box(types.moov, mvhd(duration), trak(duration, width, height), mvex());
};
mvex = function() {
return box(types.mvex, box(types.trex, TREX));
};
mvhd = function(duration) {
var
bytes = new Uint8Array([
0x00, // version 0
0x00, 0x00, 0x00, // flags
0x00, 0x00, 0x00, 0x01, // creation_time
0x00, 0x00, 0x00, 0x02, // modification_time
0x00, 0x00, 0x00, 0x01, // timescale, 1 "tick" per second
(duration & 0xFF000000) >> 24,
(duration & 0xFF0000) >> 16,
(duration & 0xFF00) >> 8,
duration & 0xFF, // 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
0x00, 0x01, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x01, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x40, 0x00, 0x00, 0x00, // transformation: unity matrix
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, 0x01 // next_track_ID
]);
return box(types.mvhd, bytes);
};
stbl = function(width, height) {
return box(types.stbl,
stsd(width, height),
box(types.stts),
box(types.stsc),
box(types.stco));
};
stsd = function(width, height) {
return box(types.stsd, new Uint8Array([
0x00, // version 0
0x00, 0x00, 0x00, // flags
0x00, 0x00, 0x00, 0x01]),
box(types.avc1, new Uint8Array([
0x00, 0x00, 0x00,
0x00, 0x00, 0x00, // reserved
0x00, 0x01, // data_reference_index
0x00, 0x00, // pre_defined
0x00, 0x00, // reserved
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, // pre_defined
(width & 0xff00) >> 8,
width & 0xff, // width
(height & 0xff00) >> 8,
height & 0xff, // height
0x00, 0x48, 0x00, 0x00, // horizresolution
0x00, 0x48, 0x00, 0x00, // vertresolution
0x00, 0x00, 0x00, 0x00, // reserved
0x00, 0x01, // frame_count
0x13,
0x76, 0x69, 0x64, 0x65,
0x6f, 0x6a, 0x73, 0x2d,
0x63, 0x6f, 0x6e, 0x74,
0x72, 0x69, 0x62, 0x2d,
0x68, 0x6c, 0x73, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, // compressorname
0x00, 0x18, // depth = 24
0x11, 0x11]))); // pre_defined = -1
};
tkhd = function(duration, width, height) {
return box(types.tkhd, new Uint8Array([
0x00, // version 0
0x00, 0x00, 0x00, // flags
0x00, 0x00, 0x00, 0x00, // creation_time
0x00, 0x00, 0x00, 0x00, // modification_time
0x00, 0x00, 0x00, 0x01, // track_ID
0x00, 0x00, 0x00, 0x00, // reserved
(duration & 0xFF000000) >> 24,
(duration & 0xFF0000) >> 16,
(duration & 0xFF00) >> 8,
duration & 0xFF, // 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
0x00, 0x01, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x01, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x40, 0x00, 0x00, 0x00, // transformation: unity matrix
(width & 0xFF000000) >> 24,
(width & 0xFF0000) >> 16,
(width & 0xFF00) >> 8,
width & 0xFF, // width
(height & 0xFF000000) >> 24,
(height & 0xFF0000) >> 16,
(height & 0xFF00) >> 8,
height & 0xFF // height
]));
};
trak = function(duration, width, height) {
return box(types.trak, tkhd(duration, width, height), mdia(duration, width, height));
};
window.videojs.mp4 = {
ftyp: ftyp,
moov: moov,
initSegment: function() {
var
fileType = ftyp(),
movie = moov(1, 1280, 720),
result = new Uint8Array(fileType.byteLength + movie.byteLength);
result.set(fileType);
result.set(movie, fileType.byteLength);
return result;
}
};
})(window, window.videojs);
/**
* video-js-hls
*
* Copyright (c) 2014 Brightcove
* All rights reserved.
*/
/**
* A stream-based mp2t to mp4 converter. This utility is used to
* deliver mp4s to a SourceBuffer on platforms that support native
* Media Source Extensions. The equivalent process for Flash-based
......@@ -7,7 +14,7 @@
(function(window, videojs, undefined) {
'use strict';
var PacketStream, ParseStream, Transmuxer, MP2T_PACKET_LENGTH;
var PacketStream, ParseStream, MP2T_PACKET_LENGTH;
MP2T_PACKET_LENGTH = 188; // bytes
......@@ -232,20 +239,11 @@ ParseStream = function() {
};
ParseStream.prototype = new videojs.Hls.Stream();
Transmuxer = function() {
Transmuxer.prototype.init.call(this);
this.push = function() {
this.mp4 = new Uint8Array();
};
};
Transmuxer.prototype = new videojs.Hls.Stream();
window.videojs.mp2t = {
PAT_PID: 0x0000,
MP2T_PACKET_LENGTH: MP2T_PACKET_LENGTH,
H264_STREAM_TYPE: 0x1b,
ADTS_STREAM_TYPE: 0x0f,
Transmuxer: Transmuxer,
PacketStream: PacketStream,
ParseStream: ParseStream
};
......
......@@ -90,12 +90,13 @@ module.exports = function(config) {
'../src/playlist-loader.js',
'../src/decrypter.js',
'../src/transmuxer.js',
'../src/mp4-generator.js',
'../tmp/manifests.js',
'../tmp/expected.js',
'tsSegment-bc.js',
'../src/bin-utils.js',
'../test/muxer/js/mp4-inspector.js',
'../test/*.js',
'../test/muxer/js/mp4-inspector.js'
],
plugins: [
......
......@@ -55,12 +55,13 @@ module.exports = function(config) {
'../src/playlist-loader.js',
'../src/decrypter.js',
'../src/transmuxer.js',
'../src/mp4-generator.js',
'../tmp/manifests.js',
'../tmp/expected.js',
'tsSegment-bc.js',
'../src/bin-utils.js',
'../test/muxer/js/mp4-inspector.js',
'../test/*.js',
'../test/muxer/js/mp4-inspector.js'
],
plugins: [
......
(function(window, videojs) {
'use strict';
/*
======== 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:
ok(value, [message])
equal(actual, expected, [message])
notEqual(actual, expected, [message])
deepEqual(actual, expected, [message])
notDeepEqual(actual, expected, [message])
strictEqual(actual, expected, [message])
notStrictEqual(actual, expected, [message])
throws(block, [expected], [message])
*/
var
mp4 = videojs.mp4,
inspectMp4 = videojs.inspectMp4;
module('MP4 Generator');
test('generates a BSMFF ftyp', function() {
var data = mp4.ftyp(), boxes;
ok(data, 'box is not null');
boxes = inspectMp4(data);
equal(1, boxes.length, 'generated a single box');
equal(boxes[0].type, 'ftyp', 'generated ftyp type');
equal(boxes[0].size, data.byteLength, 'generated size');
equal(boxes[0].majorBrand, 'isom', 'major version is "isom"');
equal(boxes[0].minorVersion, 1, 'minor version is one');
});
test('generates a moov', function() {
var boxes, mvhd, tkhd, mdhd, hdlr, minf, mvex,
data = mp4.moov(100, 600, 300);
ok(data, 'box is not null');
boxes = inspectMp4(data);
equal(boxes.length, 1, 'generated a single box');
equal(boxes[0].type, 'moov', 'generated a moov type');
equal(boxes[0].size, data.byteLength, 'generated size');
equal(boxes[0].boxes.length, 3, 'generated three sub boxes');
mvhd = boxes[0].boxes[0];
equal(mvhd.type, 'mvhd', 'generated a mvhd');
equal(mvhd.duration, 100, 'wrote the movie header duration');
equal(boxes[0].boxes[1].type, 'trak', 'generated a trak');
equal(boxes[0].boxes[1].boxes.length, 2, 'generated two track sub boxes');
tkhd = boxes[0].boxes[1].boxes[0];
equal(tkhd.type, 'tkhd', 'generated a tkhd');
equal(tkhd.duration, 100, 'wrote duration into the track header');
equal(tkhd.width, 600, 'wrote width into the track header');
equal(tkhd.height, 300, 'wrote height into the track header');
equal(boxes[0].boxes[1].boxes[1].type, 'mdia', 'generated an mdia type');
equal(boxes[0].boxes[1].boxes[1].boxes.length, 3, 'generated three track media sub boxes');
mdhd = boxes[0].boxes[1].boxes[1].boxes[0];
equal(mdhd.type, 'mdhd', 'generate an mdhd type');
equal(mdhd.language, 'und', 'wrote undetermined language');
equal(mdhd.duration, 100, 'wrote duraiton into the media header');
hdlr = boxes[0].boxes[1].boxes[1].boxes[1];
equal(hdlr.type, 'hdlr', 'generate an hdlr type');
equal(hdlr.handlerType, 'vide', 'wrote a video handler');
equal(hdlr.name, 'VideoHandler', 'wrote the handler name');
minf = boxes[0].boxes[1].boxes[1].boxes[2];
equal(minf.type, 'minf', 'generate an minf type');
equal(minf.boxes.length, 2, 'generates two minf sub boxes');
deepEqual({
type: 'dinf',
size: 24,
boxes: [{
type: 'dref',
size: 16,
version: 0,
flags: new Uint8Array([0, 0, 0]),
dataReferences: []
}]
}, minf.boxes[0], 'generates a dinf');
equal(minf.boxes[1].type, 'stbl', 'generates an stbl type');
deepEqual({
type: 'stbl',
size: 134,
boxes: [{
type: 'stsd',
size: 102,
version: 0,
flags: new Uint8Array([0, 0, 0]),
sampleDescriptions: [{
dataReferenceIndex: 1,
width: 600,
height: 300,
horizresolution: 72,
vertresolution: 72,
frameCount: 1,
depth: 24,
size: 86,
type: 'avc1'
}]
}, {
type: 'stts',
size: 8,
timeToSamples: []
}, {
type: 'stsc',
size: 8,
sampleToChunks: []
}, {
type: 'stco',
size: 8,
chunkOffsets: []
}]
}, minf.boxes[1], 'generates a stbl');
mvex = boxes[0].boxes[2];
equal(mvex.type, 'mvex', 'generates an mvex type');
deepEqual({
type: 'mvex',
size: 40,
boxes: [{
type: 'trex',
size: 32,
version: 0,
flags: new Uint8Array([0, 0, 0]),
trackId: 1,
defaultSampleDescriptionIndex: 1,
defaultSampleDuration: 0,
defaultSampleSize: 0,
sampleDependsOn: 0,
sampleIsDependedOn: 0,
sampleHasRedundancy: 0,
samplePaddingValue: 0,
sampleIsDifferenceSample: true,
sampleDegradationPriority: 1
}]
}, mvex, 'writes a movie extends box');
});
test('generates an initialization segment', function() {
var
data = mp4.initSegment(),
init;
init = videojs.inspectMp4(data);
equal(init.length, 2, 'generated two boxes');
equal(init[0].type, 'ftyp', 'generated a ftyp box');
equal(init[1].type, 'moov', 'generated a moov box');
});
})(window, window.videojs);
......@@ -108,7 +108,7 @@ var
0x00, 0x00, 0x01, 0x2c, // 300 = 0x12c width
0x00, 0x00, 0x00, 0x96), // 150 = 0x96 height
mdhd0 = box('mdhd',
0x00, // version 1
0x00, // version 0
0x00, 0x00, 0x00, // flags
0x00, 0x00, 0x00, 0x02, // creation_time
0x00, 0x00, 0x00, 0x03, // modification_time
......@@ -137,14 +137,14 @@ test('can parse a Box', function() {
test('can parse an ftyp', function() {
deepEqual(videojs.inspectMp4(new Uint8Array(box('ftyp',
0x00, 0x00, 0x00, 0x01, // major brand
0x61, 0x76, 0x63, 0x31, // major brand
0x00, 0x00, 0x00, 0x02, // minor version
0x00, 0x00, 0x00, 0x03, // compatible brands
0x00, 0x00, 0x00, 0x04 // compatible brands
))), [{
type: 'ftyp',
size: 4 * 6,
majorBrand: 1,
majorBrand: 'avc1',
minorVersion: 2,
compatibleBrands: [3, 4]
}], 'parsed an ftyp');
......@@ -401,40 +401,128 @@ test('can parse a moov', function() {
size: 24,
boxes: [{
type: 'dref',
version: 1,
flags: new Uint8Array([0, 0, 0]),
dataReferences: [],
size: 16
}]}, {
type: 'stbl',
size: 72,
boxes: [{
type: 'stsd',
sampleDescriptions: [],
size: 16
}, {
type: 'stts',
timeToSamples: [],
size: 16
}, {
type: 'stsc',
sampleToChunks: [],
size: 16
}, {
type: 'stco',
chunkOffsets: [],
size: 16
}]
}]
}, {
type: 'stbl',
size: 72,
boxes: [{
type: 'stsd',
size: 16,
version: 1,
flags: new Uint8Array([0, 0, 0]),
sampleDescriptions: [],
}, {
type: 'stts',
timeToSamples: [],
size: 16
}, {
type: 'stsc',
sampleToChunks: [],
size: 16
}, {
type: 'stco',
chunkOffsets: [],
size: 16
}]
}]
}]
}]
}], 'parsed a moov');
}]
}], 'parsed a moov');
});
test('can parse an mvex', function() {
var mvex =
box('mvex',
box('trex',
0x00, // version
0x00, 0x00, 0x00, // flags
0x00, 0x00, 0x00, 0x01, // track_ID
0x00, 0x00, 0x00, 0x01, // default_sample_description_index
0x00, 0x00, 0x00, 0x02, // default_sample_duration
0x00, 0x00, 0x00, 0x03, // default_sample_size
0x00, 0x61, 0x00, 0x01)); // default_sample_flags
deepEqual(videojs.inspectMp4(new Uint8Array(mvex)), [{
type: 'mvex',
size: 40,
boxes: [{
type: 'trex',
size: 32,
version: 0,
flags: new Uint8Array([0, 0, 0]),
trackId: 1,
defaultSampleDescriptionIndex: 1,
defaultSampleDuration: 2,
defaultSampleSize: 3,
sampleDependsOn: 0,
sampleIsDependedOn: 1,
sampleHasRedundancy: 2,
samplePaddingValue: 0,
sampleIsDifferenceSample: true,
sampleDegradationPriority: 1
}]
}], 'parsed an mvex');
});
test('can parse a video stsd', function() {
var data = box('stsd',
0x00, // version 0
0x00, 0x00, 0x00, // flags
0x00, 0x00, 0x00, 0x01,
box('avc1',
0x00, 0x00, 0x00,
0x00, 0x00, 0x00, // reserved
0x00, 0x01, // data_reference_index
0x00, 0x00, // pre_defined
0x00, 0x00, // reserved
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, // pre_defined
0x01, 0x2c, // width = 300
0x00, 0x96, // height = 150
0x00, 0x48, 0x00, 0x00, // horizresolution
0x00, 0x48, 0x00, 0x00, // vertresolution
0x00, 0x00, 0x00, 0x00, // reserved
0x00, 0x01, // frame_count
0x04,
typeBytes('avc1'),
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, // compressorname
0x00, 0x18, // depth = 24
0x11, 0x11)); // pre_defined
deepEqual(videojs.inspectMp4(new Uint8Array(data)), [{
type: 'stsd',
size: 102,
version: 0,
flags: new Uint8Array([0, 0, 0]),
sampleDescriptions: [{
type: 'avc1',
size: 86,
dataReferenceIndex: 1,
width: 300,
height: 150,
horizresolution: 72,
vertresolution: 72,
frameCount: 1,
depth: 24
}]
}]);
});
test('can parse a series of boxes', function() {
var ftyp = [
0x00, 0x00, 0x00, 0x18 // size 4 * 6 = 24
].concat(typeBytes('ftyp')).concat([
0x00, 0x00, 0x00, 0x01, // major brand
0x69, 0x73, 0x6f, 0x6d, // major brand
0x00, 0x00, 0x00, 0x02, // minor version
0x00, 0x00, 0x00, 0x03, // compatible brands
0x00, 0x00, 0x00, 0x04, // compatible brands
......@@ -444,13 +532,13 @@ test('can parse a series of boxes', function() {
[{
type: 'ftyp',
size: 4 * 6,
majorBrand: 1,
majorBrand: 'isom',
minorVersion: 2,
compatibleBrands: [3, 4]
},{
type: 'ftyp',
size: 4 * 6,
majorBrand: 1,
majorBrand: 'isom',
minorVersion: 2,
compatibleBrands: [3, 4]
}],
......
......@@ -19,11 +19,26 @@ var
// registry of handlers for individual mp4 box types
parse = {
// codingname, not a first-class box type. stsd entries share the
// same format as real boxes so the parsing infrastructure can be
// shared
avc1: function(data) {
var view = new DataView(data.buffer, data.byteOffset, data.byteLength);
return {
dataReferenceIndex: view.getUint16(6),
width: view.getUint16(24),
height: view.getUint16(26),
horizresolution: view.getUint16(28) + (view.getUint16(30) / 16),
vertresolution: view.getUint16(32) + (view.getUint16(34) / 16),
frameCount: view.getUint16(40),
depth: view.getUint16(74)
};
},
ftyp: function(data) {
var
view = new DataView(data.buffer, data.byteOffset, data.byteLength),
result = {
majorBrand: view.getUint32(0),
majorBrand: parseType(data.subarray(0, 4)),
minorVersion: view.getUint32(4),
compatibleBrands: []
},
......@@ -41,6 +56,8 @@ var
},
dref: function(data) {
return {
version: data[0],
flags: new Uint8Array(data.subarray(1, 4)),
dataReferences: []
};
},
......@@ -129,6 +146,11 @@ var
boxes: videojs.inspectMp4(data)
};
},
mvex: function(data) {
return {
boxes: videojs.inspectMp4(data)
};
},
mvhd: function(data) {
var
view = new DataView(data.buffer, data.byteOffset, data.byteLength),
......@@ -185,6 +207,23 @@ var
boxes: videojs.inspectMp4(data)
};
},
trex: function(data) {
var view = new DataView(data.buffer, data.byteOffset, data.byteLength);
return {
version: data[0],
flags: new Uint8Array(data.subarray(1, 4)),
trackId: view.getUint32(4),
defaultSampleDescriptionIndex: view.getUint32(8),
defaultSampleDuration: view.getUint32(12),
defaultSampleSize: view.getUint32(16),
sampleDependsOn: data[20] & 0x03,
sampleIsDependedOn: (data[21] & 0xc0) >> 6,
sampleHasRedundancy: (data[21] & 0x30) >> 4,
samplePaddingValue: (data[21] & 0x0e) >> 1,
sampleIsDifferenceSample: !!(data[21] & 0x01),
sampleDegradationPriority: view.getUint16(22)
};
},
stbl: function(data) {
return {
boxes: videojs.inspectMp4(data)
......@@ -202,7 +241,9 @@ var
},
stsd: function(data) {
return {
sampleDescriptions: []
version: data[0],
flags: new Uint8Array(data.subarray(1, 4)),
sampleDescriptions: videojs.inspectMp4(data.subarray(8))
};
},
stts: function(data) {
......
......@@ -106,6 +106,7 @@
</script>
<script src="../../src/stream.js"></script>
<script src="../../src/transmuxer.js"></script>
<script src="../../src/mp4-generator.js"></script>
<script src="js/mp4-inspector.js"></script>
<script src="../../src/bin-utils.js"></script>
......@@ -118,13 +119,9 @@
workingBoxes = document.querySelector('.working-boxes'),
vjsOutput = document.querySelector('.vjs-hls-output'),
workingOutput = document.querySelector('.working-output'),
workingOutput = document.querySelector('.working-output');
tagTypes = {
0x08: 'audio',
0x09: 'video',
0x12: 'metadata'
};
// webkit dash-mp4 mime: 'video/mp4;codecs=avc1.4d0020,mp4a.40.2'
videojs.log = console.log.bind(console);
......@@ -132,19 +129,43 @@
var reader = new FileReader();
reader.addEventListener('loadend', function() {
var segment = new Uint8Array(reader.result),
transmuxer = new videojs.Hls.Transmuxer(),
packetizer = new videojs.mp2t.PacketStream(),
parser = new videojs.mp2t.ParseStream(),
hex = '';
transmuxer.push(segment);
packetizer.pipe(parser);
packetizer.push(segment);
// clear old boxes info
vjsBoxes.innerHTML = JSON.stringify(videojs.inspectMp4(transmuxer.mp4), null, ' ');
// write out the result
hex += '<pre>';
hex += videojs.Hls.utils.hexDump(transmuxer.mp4);
// hex += videojs.Hls.utils.hexDump(transmuxer.mp4);
hex += '</pre>';
vjsOutput.innerHTML = hex;
// XXX media source testing
vjsBoxes.innerHTML = JSON.stringify(videojs.inspectMp4(videojs.mp4.initSegment()), null, ' ');
var video = document.createElement('video');
var mediaSource = new MediaSource();
mediaSource.addEventListener('sourceopen', function() {
var buffer = mediaSource.addSourceBuffer('video/mp4;codecs=avc1.4d0020,mp4a.40.2');
buffer.addEventListener('updatestart', console.log.bind(console));
buffer.addEventListener('updateend', console.log.bind(console));
buffer.addEventListener('error', console.log.bind(console));
buffer.appendBuffer(videojs.mp4.initSegment());
console.log('done', mediaSource, buffer, video.error);
window.vjsMediaSource = mediaSource;
window.vjsSourceBuffer = buffer;
window.vjsVideo = video;
});
mediaSource.addEventListener('error', console.log.bind(console));
mediaSource.addEventListener('opened', console.log.bind(console));
mediaSource.addEventListener('closed', console.log.bind(console));
mediaSource.addEventListener('sourceended', console.log.bind(console));
video.src = URL.createObjectURL(mediaSource);
});
reader.readAsArrayBuffer(this.files[0]);
}, false);
......
......@@ -24,9 +24,7 @@ var
PacketStream = videojs.mp2t.PacketStream,
packetStream,
ParseStream = videojs.mp2t.ParseStream,
parseStream,
Transmuxer = videojs.mp2t.Transmuxer,
transmuxer;
parseStream;
module('MP2T Packet Stream', {
setup: function() {
......@@ -394,17 +392,4 @@ test('parses an elementary stream packet without a pts or dts', function() {
ok(!packet.dts, 'did not parse a dts');
});
module('MP4 Transmuxer', {
setup: function() {
transmuxer = new Transmuxer();
}
});
test('can mux an empty mp2t', function() {
transmuxer.push(new Uint8Array());
ok(transmuxer.mp4, 'produced a non-null result');
strictEqual(transmuxer.mp4.byteLength, 0, 'produced an empty mp4');
});
})(window, window.videojs);
......
......@@ -44,6 +44,7 @@
<!-- mp4 utilities -->
<script src="../src/transmuxer.js"></script>
<script src="../src/mp4-generator.js"></script>
<script src="muxer/js/mp4-inspector.js"></script>
<!-- Test cases -->
......@@ -66,6 +67,7 @@
<script src="decrypter_test.js"></script>
<script src="transmuxer_test.js"></script>
<script src="mp4-inspector_test.js"></script>
<script src="mp4-generator_test.js"></script>
</head>
<body>
<div id="qunit"></div>
......