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
1 (function(window, videojs, undefined) {
2 'use strict';
3
4 var box, dinf, ftyp, minf, moov, mvex, mvhd, trak, tkhd, mdia, mdhd, hdlr, stbl,
5 stsd, types, MAJOR_BRAND, MINOR_VERSION, VIDEO_HDLR, DREF, TREX, Uint8Array, DataView;
6
7 Uint8Array = window.Uint8Array;
8 DataView = window.DataView;
9
10 // pre-calculate constants
11 (function() {
12 var i;
13 types = {
14 avc1: [], // codingname
15 dinf: [],
16 dref: [],
17 ftyp: [],
18 hdlr: [],
19 mdhd: [],
20 mdia: [],
21 minf: [],
22 moov: [],
23 mvex: [],
24 mvhd: [],
25 stbl: [],
26 stco: [],
27 stsc: [],
28 stsd: [],
29 stts: [],
30 trak: [],
31 trex: [],
32 tkhd: []
33 };
34
35 for (i in types) {
36 if (types.hasOwnProperty(i)) {
37 types[i] = [
38 i.charCodeAt(0),
39 i.charCodeAt(1),
40 i.charCodeAt(2),
41 i.charCodeAt(3)
42 ];
43 }
44 }
45
46 MAJOR_BRAND = new Uint8Array([
47 'i'.charCodeAt(0),
48 's'.charCodeAt(0),
49 'o'.charCodeAt(0),
50 'm'.charCodeAt(0)
51 ]);
52 MINOR_VERSION = new Uint8Array([0, 0, 0, 1]);
53 VIDEO_HDLR = new Uint8Array([
54 0x00, // version 0
55 0x00, 0x00, 0x00, // flags
56 0x00, 0x00, 0x00, 0x00, // pre_defined
57 0x76, 0x69, 0x64, 0x65, // handler_type: 'vide'
58 0x00, 0x00, 0x00, 0x00, // reserved
59 0x00, 0x00, 0x00, 0x00, // reserved
60 0x00, 0x00, 0x00, 0x00, // reserved
61 0x56, 0x69, 0x64, 0x65,
62 0x6f, 0x48, 0x61, 0x6e,
63 0x64, 0x6c, 0x65, 0x72, 0x00 // name: 'VideoHandler'
64 ]);
65 DREF = new Uint8Array([
66 0x00, // version 0
67 0x00, 0x00, 0x00, // flags
68 0x00, 0x00, 0x00, 0x00 // entry_count
69 ]);
70 TREX = new Uint8Array([
71 0x00, // version 0
72 0x00, 0x00, 0x00, // flags
73 0x00, 0x00, 0x00, 0x01, // track_ID
74 0x00, 0x00, 0x00, 0x01, // default_sample_description_index
75 0x00, 0x00, 0x00, 0x00, // default_sample_duration
76 0x00, 0x00, 0x00, 0x00, // default_sample_size
77 0x00, 0x01, 0x00, 0x01 // default_sample_flags
78 ]);
79 })();
80
81 box = function(type) {
82 var
83 payload = Array.prototype.slice.call(arguments, 1),
84 size = 0,
85 i = payload.length,
86 result,
87 view;
88
89 // calculate the total size we need to allocate
90 while (i--) {
91 size += payload[i].byteLength;
92 }
93 result = new Uint8Array(size + 8);
94 view = new DataView(result.buffer, result.byteOffset, result.byteLength);
95 view.setUint32(0, result.byteLength);
96 result.set(type, 4);
97
98 // copy the payload into the result
99 for (i = 0, size = 8; i < payload.length; i++) {
100 result.set(payload[i], size);
101 size += payload[i].byteLength;
102 }
103 return result;
104 };
105
106 dinf = function() {
107 return box(types.dinf, box(types.dref, DREF));
108 };
109
110 ftyp = function() {
111 return box(types.ftyp, MAJOR_BRAND, MINOR_VERSION, MAJOR_BRAND);
112 };
113
114 hdlr = function() {
115 return box(types.hdlr, VIDEO_HDLR);
116 };
117 mdhd = function(duration) {
118 return box(types.mdhd, new Uint8Array([
119 0x00, // version 0
120 0x00, 0x00, 0x00, // flags
121 0x00, 0x00, 0x00, 0x02, // creation_time
122 0x00, 0x00, 0x00, 0x03, // modification_time
123 0x00, 0x00, 0x00, 0x3c, // timescale
124 (duration & 0xFF000000) >> 24,
125 (duration & 0xFF0000) >> 16,
126 (duration & 0xFF00) >> 8,
127 duration & 0xFF, // duration
128 0x55, 0xc4, // 'und' language (undetermined)
129 0x00, 0x00
130 ]));
131 };
132 mdia = function(duration, width, height) {
133 return box(types.mdia, mdhd(duration), hdlr(), minf(width, height));
134 };
135 minf = function(width, height) {
136 return box(types.minf, dinf(), stbl(width, height));
137 };
138 moov = function(duration, width, height) {
139 return box(types.moov, mvhd(duration), trak(duration, width, height), mvex());
140 };
141 mvex = function() {
142 return box(types.mvex, box(types.trex, TREX));
143 };
144 mvhd = function(duration) {
145 var
146 bytes = new Uint8Array([
147 0x00, // version 0
148 0x00, 0x00, 0x00, // flags
149 0x00, 0x00, 0x00, 0x01, // creation_time
150 0x00, 0x00, 0x00, 0x02, // modification_time
151 0x00, 0x00, 0x00, 0x01, // timescale, 1 "tick" per second
152 (duration & 0xFF000000) >> 24,
153 (duration & 0xFF0000) >> 16,
154 (duration & 0xFF00) >> 8,
155 duration & 0xFF, // duration
156 0x00, 0x01, 0x00, 0x00, // 1.0 rate
157 0x01, 0x00, // 1.0 volume
158 0x00, 0x00, // reserved
159 0x00, 0x00, 0x00, 0x00, // reserved
160 0x00, 0x00, 0x00, 0x00, // reserved
161 0x00, 0x01, 0x00, 0x00,
162 0x00, 0x00, 0x00, 0x00,
163 0x00, 0x00, 0x00, 0x00,
164 0x00, 0x00, 0x00, 0x00,
165 0x00, 0x01, 0x00, 0x00,
166 0x00, 0x00, 0x00, 0x00,
167 0x00, 0x00, 0x00, 0x00,
168 0x00, 0x00, 0x00, 0x00,
169 0x40, 0x00, 0x00, 0x00, // transformation: unity matrix
170 0x00, 0x00, 0x00, 0x00,
171 0x00, 0x00, 0x00, 0x00,
172 0x00, 0x00, 0x00, 0x00,
173 0x00, 0x00, 0x00, 0x00,
174 0x00, 0x00, 0x00, 0x00,
175 0x00, 0x00, 0x00, 0x00, // pre_defined
176 0x00, 0x00, 0x00, 0x01 // next_track_ID
177 ]);
178 return box(types.mvhd, bytes);
179 };
180
181 stbl = function(width, height) {
182 return box(types.stbl,
183 stsd(width, height),
184 box(types.stts),
185 box(types.stsc),
186 box(types.stco));
187 };
188
189 stsd = function(width, height) {
190 return box(types.stsd, new Uint8Array([
191 0x00, // version 0
192 0x00, 0x00, 0x00, // flags
193 0x00, 0x00, 0x00, 0x01]),
194 box(types.avc1, new Uint8Array([
195 0x00, 0x00, 0x00,
196 0x00, 0x00, 0x00, // reserved
197 0x00, 0x01, // data_reference_index
198 0x00, 0x00, // pre_defined
199 0x00, 0x00, // reserved
200 0x00, 0x00, 0x00, 0x00,
201 0x00, 0x00, 0x00, 0x00,
202 0x00, 0x00, 0x00, 0x00, // pre_defined
203 (width & 0xff00) >> 8,
204 width & 0xff, // width
205 (height & 0xff00) >> 8,
206 height & 0xff, // height
207 0x00, 0x48, 0x00, 0x00, // horizresolution
208 0x00, 0x48, 0x00, 0x00, // vertresolution
209 0x00, 0x00, 0x00, 0x00, // reserved
210 0x00, 0x01, // frame_count
211 0x13,
212 0x76, 0x69, 0x64, 0x65,
213 0x6f, 0x6a, 0x73, 0x2d,
214 0x63, 0x6f, 0x6e, 0x74,
215 0x72, 0x69, 0x62, 0x2d,
216 0x68, 0x6c, 0x73, 0x00,
217 0x00, 0x00, 0x00, 0x00,
218 0x00, 0x00, 0x00, 0x00,
219 0x00, 0x00, 0x00, // compressorname
220 0x00, 0x18, // depth = 24
221 0x11, 0x11]))); // pre_defined = -1
222 };
223
224 tkhd = function(duration, width, height) {
225 return box(types.tkhd, new Uint8Array([
226 0x00, // version 0
227 0x00, 0x00, 0x00, // flags
228 0x00, 0x00, 0x00, 0x00, // creation_time
229 0x00, 0x00, 0x00, 0x00, // modification_time
230 0x00, 0x00, 0x00, 0x01, // track_ID
231 0x00, 0x00, 0x00, 0x00, // reserved
232 (duration & 0xFF000000) >> 24,
233 (duration & 0xFF0000) >> 16,
234 (duration & 0xFF00) >> 8,
235 duration & 0xFF, // duration
236 0x00, 0x00, 0x00, 0x00,
237 0x00, 0x00, 0x00, 0x00, // reserved
238 0x00, 0x00, // layer
239 0x00, 0x00, // alternate_group
240 0x00, 0x00, // non-audio track volume
241 0x00, 0x00, // reserved
242 0x00, 0x01, 0x00, 0x00,
243 0x00, 0x00, 0x00, 0x00,
244 0x00, 0x00, 0x00, 0x00,
245 0x00, 0x00, 0x00, 0x00,
246 0x00, 0x01, 0x00, 0x00,
247 0x00, 0x00, 0x00, 0x00,
248 0x00, 0x00, 0x00, 0x00,
249 0x00, 0x00, 0x00, 0x00,
250 0x40, 0x00, 0x00, 0x00, // transformation: unity matrix
251 (width & 0xFF000000) >> 24,
252 (width & 0xFF0000) >> 16,
253 (width & 0xFF00) >> 8,
254 width & 0xFF, // width
255 (height & 0xFF000000) >> 24,
256 (height & 0xFF0000) >> 16,
257 (height & 0xFF00) >> 8,
258 height & 0xFF // height
259 ]));
260 };
261
262 trak = function(duration, width, height) {
263 return box(types.trak, tkhd(duration, width, height), mdia(duration, width, height));
264 };
265
266 window.videojs.mp4 = {
267 ftyp: ftyp,
268 moov: moov,
269 initSegment: function() {
270 var
271 fileType = ftyp(),
272 movie = moov(1, 1280, 720),
273 result = new Uint8Array(fileType.byteLength + movie.byteLength);
274
275 result.set(fileType);
276 result.set(movie, fileType.byteLength);
277 return result;
278 }
279 };
280
281 })(window, window.videojs);
1 /** 1 /**
2 * video-js-hls
3 *
4 * Copyright (c) 2014 Brightcove
5 * All rights reserved.
6 */
7
8 /**
2 * A stream-based mp2t to mp4 converter. This utility is used to 9 * A stream-based mp2t to mp4 converter. This utility is used to
3 * deliver mp4s to a SourceBuffer on platforms that support native 10 * deliver mp4s to a SourceBuffer on platforms that support native
4 * Media Source Extensions. The equivalent process for Flash-based 11 * Media Source Extensions. The equivalent process for Flash-based
...@@ -7,7 +14,7 @@ ...@@ -7,7 +14,7 @@
7 (function(window, videojs, undefined) { 14 (function(window, videojs, undefined) {
8 'use strict'; 15 'use strict';
9 16
10 var PacketStream, ParseStream, Transmuxer, MP2T_PACKET_LENGTH; 17 var PacketStream, ParseStream, MP2T_PACKET_LENGTH;
11 18
12 MP2T_PACKET_LENGTH = 188; // bytes 19 MP2T_PACKET_LENGTH = 188; // bytes
13 20
...@@ -232,20 +239,11 @@ ParseStream = function() { ...@@ -232,20 +239,11 @@ ParseStream = function() {
232 }; 239 };
233 ParseStream.prototype = new videojs.Hls.Stream(); 240 ParseStream.prototype = new videojs.Hls.Stream();
234 241
235 Transmuxer = function() {
236 Transmuxer.prototype.init.call(this);
237 this.push = function() {
238 this.mp4 = new Uint8Array();
239 };
240 };
241 Transmuxer.prototype = new videojs.Hls.Stream();
242
243 window.videojs.mp2t = { 242 window.videojs.mp2t = {
244 PAT_PID: 0x0000, 243 PAT_PID: 0x0000,
245 MP2T_PACKET_LENGTH: MP2T_PACKET_LENGTH, 244 MP2T_PACKET_LENGTH: MP2T_PACKET_LENGTH,
246 H264_STREAM_TYPE: 0x1b, 245 H264_STREAM_TYPE: 0x1b,
247 ADTS_STREAM_TYPE: 0x0f, 246 ADTS_STREAM_TYPE: 0x0f,
248 Transmuxer: Transmuxer,
249 PacketStream: PacketStream, 247 PacketStream: PacketStream,
250 ParseStream: ParseStream 248 ParseStream: ParseStream
251 }; 249 };
......
...@@ -90,12 +90,13 @@ module.exports = function(config) { ...@@ -90,12 +90,13 @@ module.exports = function(config) {
90 '../src/playlist-loader.js', 90 '../src/playlist-loader.js',
91 '../src/decrypter.js', 91 '../src/decrypter.js',
92 '../src/transmuxer.js', 92 '../src/transmuxer.js',
93 '../src/mp4-generator.js',
93 '../tmp/manifests.js', 94 '../tmp/manifests.js',
94 '../tmp/expected.js', 95 '../tmp/expected.js',
95 'tsSegment-bc.js', 96 'tsSegment-bc.js',
96 '../src/bin-utils.js', 97 '../src/bin-utils.js',
98 '../test/muxer/js/mp4-inspector.js',
97 '../test/*.js', 99 '../test/*.js',
98 '../test/muxer/js/mp4-inspector.js'
99 ], 100 ],
100 101
101 plugins: [ 102 plugins: [
......
...@@ -55,12 +55,13 @@ module.exports = function(config) { ...@@ -55,12 +55,13 @@ module.exports = function(config) {
55 '../src/playlist-loader.js', 55 '../src/playlist-loader.js',
56 '../src/decrypter.js', 56 '../src/decrypter.js',
57 '../src/transmuxer.js', 57 '../src/transmuxer.js',
58 '../src/mp4-generator.js',
58 '../tmp/manifests.js', 59 '../tmp/manifests.js',
59 '../tmp/expected.js', 60 '../tmp/expected.js',
60 'tsSegment-bc.js', 61 'tsSegment-bc.js',
61 '../src/bin-utils.js', 62 '../src/bin-utils.js',
63 '../test/muxer/js/mp4-inspector.js',
62 '../test/*.js', 64 '../test/*.js',
63 '../test/muxer/js/mp4-inspector.js'
64 ], 65 ],
65 66
66 plugins: [ 67 plugins: [
......
1 (function(window, videojs) {
2 'use strict';
3 /*
4 ======== A Handy Little QUnit Reference ========
5 http://api.qunitjs.com/
6
7 Test methods:
8 module(name, {[setup][ ,teardown]})
9 test(name, callback)
10 expect(numberOfAssertions)
11 stop(increment)
12 start(decrement)
13 Test assertions:
14 ok(value, [message])
15 equal(actual, expected, [message])
16 notEqual(actual, expected, [message])
17 deepEqual(actual, expected, [message])
18 notDeepEqual(actual, expected, [message])
19 strictEqual(actual, expected, [message])
20 notStrictEqual(actual, expected, [message])
21 throws(block, [expected], [message])
22 */
23 var
24 mp4 = videojs.mp4,
25 inspectMp4 = videojs.inspectMp4;
26
27 module('MP4 Generator');
28
29 test('generates a BSMFF ftyp', function() {
30 var data = mp4.ftyp(), boxes;
31
32 ok(data, 'box is not null');
33
34 boxes = inspectMp4(data);
35 equal(1, boxes.length, 'generated a single box');
36 equal(boxes[0].type, 'ftyp', 'generated ftyp type');
37 equal(boxes[0].size, data.byteLength, 'generated size');
38 equal(boxes[0].majorBrand, 'isom', 'major version is "isom"');
39 equal(boxes[0].minorVersion, 1, 'minor version is one');
40 });
41
42 test('generates a moov', function() {
43 var boxes, mvhd, tkhd, mdhd, hdlr, minf, mvex,
44 data = mp4.moov(100, 600, 300);
45
46 ok(data, 'box is not null');
47
48 boxes = inspectMp4(data);
49 equal(boxes.length, 1, 'generated a single box');
50 equal(boxes[0].type, 'moov', 'generated a moov type');
51 equal(boxes[0].size, data.byteLength, 'generated size');
52 equal(boxes[0].boxes.length, 3, 'generated three sub boxes');
53
54 mvhd = boxes[0].boxes[0];
55 equal(mvhd.type, 'mvhd', 'generated a mvhd');
56 equal(mvhd.duration, 100, 'wrote the movie header duration');
57
58 equal(boxes[0].boxes[1].type, 'trak', 'generated a trak');
59 equal(boxes[0].boxes[1].boxes.length, 2, 'generated two track sub boxes');
60 tkhd = boxes[0].boxes[1].boxes[0];
61 equal(tkhd.type, 'tkhd', 'generated a tkhd');
62 equal(tkhd.duration, 100, 'wrote duration into the track header');
63 equal(tkhd.width, 600, 'wrote width into the track header');
64 equal(tkhd.height, 300, 'wrote height into the track header');
65
66 equal(boxes[0].boxes[1].boxes[1].type, 'mdia', 'generated an mdia type');
67 equal(boxes[0].boxes[1].boxes[1].boxes.length, 3, 'generated three track media sub boxes');
68
69 mdhd = boxes[0].boxes[1].boxes[1].boxes[0];
70 equal(mdhd.type, 'mdhd', 'generate an mdhd type');
71 equal(mdhd.language, 'und', 'wrote undetermined language');
72 equal(mdhd.duration, 100, 'wrote duraiton into the media header');
73
74 hdlr = boxes[0].boxes[1].boxes[1].boxes[1];
75 equal(hdlr.type, 'hdlr', 'generate an hdlr type');
76 equal(hdlr.handlerType, 'vide', 'wrote a video handler');
77 equal(hdlr.name, 'VideoHandler', 'wrote the handler name');
78
79 minf = boxes[0].boxes[1].boxes[1].boxes[2];
80 equal(minf.type, 'minf', 'generate an minf type');
81 equal(minf.boxes.length, 2, 'generates two minf sub boxes');
82 deepEqual({
83 type: 'dinf',
84 size: 24,
85 boxes: [{
86 type: 'dref',
87 size: 16,
88 version: 0,
89 flags: new Uint8Array([0, 0, 0]),
90 dataReferences: []
91 }]
92 }, minf.boxes[0], 'generates a dinf');
93
94 equal(minf.boxes[1].type, 'stbl', 'generates an stbl type');
95 deepEqual({
96 type: 'stbl',
97 size: 134,
98 boxes: [{
99 type: 'stsd',
100 size: 102,
101 version: 0,
102 flags: new Uint8Array([0, 0, 0]),
103 sampleDescriptions: [{
104 dataReferenceIndex: 1,
105 width: 600,
106 height: 300,
107 horizresolution: 72,
108 vertresolution: 72,
109 frameCount: 1,
110 depth: 24,
111 size: 86,
112 type: 'avc1'
113 }]
114 }, {
115 type: 'stts',
116 size: 8,
117 timeToSamples: []
118 }, {
119 type: 'stsc',
120 size: 8,
121 sampleToChunks: []
122 }, {
123 type: 'stco',
124 size: 8,
125 chunkOffsets: []
126 }]
127 }, minf.boxes[1], 'generates a stbl');
128
129
130 mvex = boxes[0].boxes[2];
131 equal(mvex.type, 'mvex', 'generates an mvex type');
132 deepEqual({
133 type: 'mvex',
134 size: 40,
135 boxes: [{
136 type: 'trex',
137 size: 32,
138 version: 0,
139 flags: new Uint8Array([0, 0, 0]),
140 trackId: 1,
141 defaultSampleDescriptionIndex: 1,
142 defaultSampleDuration: 0,
143 defaultSampleSize: 0,
144 sampleDependsOn: 0,
145 sampleIsDependedOn: 0,
146 sampleHasRedundancy: 0,
147 samplePaddingValue: 0,
148 sampleIsDifferenceSample: true,
149 sampleDegradationPriority: 1
150 }]
151 }, mvex, 'writes a movie extends box');
152 });
153
154 test('generates an initialization segment', function() {
155 var
156 data = mp4.initSegment(),
157 init;
158
159 init = videojs.inspectMp4(data);
160 equal(init.length, 2, 'generated two boxes');
161 equal(init[0].type, 'ftyp', 'generated a ftyp box');
162 equal(init[1].type, 'moov', 'generated a moov box');
163 });
164
165
166 })(window, window.videojs);
...@@ -108,7 +108,7 @@ var ...@@ -108,7 +108,7 @@ var
108 0x00, 0x00, 0x01, 0x2c, // 300 = 0x12c width 108 0x00, 0x00, 0x01, 0x2c, // 300 = 0x12c width
109 0x00, 0x00, 0x00, 0x96), // 150 = 0x96 height 109 0x00, 0x00, 0x00, 0x96), // 150 = 0x96 height
110 mdhd0 = box('mdhd', 110 mdhd0 = box('mdhd',
111 0x00, // version 1 111 0x00, // version 0
112 0x00, 0x00, 0x00, // flags 112 0x00, 0x00, 0x00, // flags
113 0x00, 0x00, 0x00, 0x02, // creation_time 113 0x00, 0x00, 0x00, 0x02, // creation_time
114 0x00, 0x00, 0x00, 0x03, // modification_time 114 0x00, 0x00, 0x00, 0x03, // modification_time
...@@ -137,14 +137,14 @@ test('can parse a Box', function() { ...@@ -137,14 +137,14 @@ test('can parse a Box', function() {
137 137
138 test('can parse an ftyp', function() { 138 test('can parse an ftyp', function() {
139 deepEqual(videojs.inspectMp4(new Uint8Array(box('ftyp', 139 deepEqual(videojs.inspectMp4(new Uint8Array(box('ftyp',
140 0x00, 0x00, 0x00, 0x01, // major brand 140 0x61, 0x76, 0x63, 0x31, // major brand
141 0x00, 0x00, 0x00, 0x02, // minor version 141 0x00, 0x00, 0x00, 0x02, // minor version
142 0x00, 0x00, 0x00, 0x03, // compatible brands 142 0x00, 0x00, 0x00, 0x03, // compatible brands
143 0x00, 0x00, 0x00, 0x04 // compatible brands 143 0x00, 0x00, 0x00, 0x04 // compatible brands
144 ))), [{ 144 ))), [{
145 type: 'ftyp', 145 type: 'ftyp',
146 size: 4 * 6, 146 size: 4 * 6,
147 majorBrand: 1, 147 majorBrand: 'avc1',
148 minorVersion: 2, 148 minorVersion: 2,
149 compatibleBrands: [3, 4] 149 compatibleBrands: [3, 4]
150 }], 'parsed an ftyp'); 150 }], 'parsed an ftyp');
...@@ -401,15 +401,20 @@ test('can parse a moov', function() { ...@@ -401,15 +401,20 @@ test('can parse a moov', function() {
401 size: 24, 401 size: 24,
402 boxes: [{ 402 boxes: [{
403 type: 'dref', 403 type: 'dref',
404 version: 1,
405 flags: new Uint8Array([0, 0, 0]),
404 dataReferences: [], 406 dataReferences: [],
405 size: 16 407 size: 16
406 }]}, { 408 }]
409 }, {
407 type: 'stbl', 410 type: 'stbl',
408 size: 72, 411 size: 72,
409 boxes: [{ 412 boxes: [{
410 type: 'stsd', 413 type: 'stsd',
414 size: 16,
415 version: 1,
416 flags: new Uint8Array([0, 0, 0]),
411 sampleDescriptions: [], 417 sampleDescriptions: [],
412 size: 16
413 }, { 418 }, {
414 type: 'stts', 419 type: 'stts',
415 timeToSamples: [], 420 timeToSamples: [],
...@@ -430,11 +435,94 @@ test('can parse a moov', function() { ...@@ -430,11 +435,94 @@ test('can parse a moov', function() {
430 }], 'parsed a moov'); 435 }], 'parsed a moov');
431 }); 436 });
432 437
438 test('can parse an mvex', function() {
439 var mvex =
440 box('mvex',
441 box('trex',
442 0x00, // version
443 0x00, 0x00, 0x00, // flags
444 0x00, 0x00, 0x00, 0x01, // track_ID
445 0x00, 0x00, 0x00, 0x01, // default_sample_description_index
446 0x00, 0x00, 0x00, 0x02, // default_sample_duration
447 0x00, 0x00, 0x00, 0x03, // default_sample_size
448 0x00, 0x61, 0x00, 0x01)); // default_sample_flags
449 deepEqual(videojs.inspectMp4(new Uint8Array(mvex)), [{
450 type: 'mvex',
451 size: 40,
452 boxes: [{
453 type: 'trex',
454 size: 32,
455 version: 0,
456 flags: new Uint8Array([0, 0, 0]),
457 trackId: 1,
458 defaultSampleDescriptionIndex: 1,
459 defaultSampleDuration: 2,
460 defaultSampleSize: 3,
461 sampleDependsOn: 0,
462 sampleIsDependedOn: 1,
463 sampleHasRedundancy: 2,
464 samplePaddingValue: 0,
465 sampleIsDifferenceSample: true,
466 sampleDegradationPriority: 1
467 }]
468 }], 'parsed an mvex');
469 });
470
471 test('can parse a video stsd', function() {
472 var data = box('stsd',
473 0x00, // version 0
474 0x00, 0x00, 0x00, // flags
475 0x00, 0x00, 0x00, 0x01,
476 box('avc1',
477 0x00, 0x00, 0x00,
478 0x00, 0x00, 0x00, // reserved
479 0x00, 0x01, // data_reference_index
480 0x00, 0x00, // pre_defined
481 0x00, 0x00, // reserved
482 0x00, 0x00, 0x00, 0x00,
483 0x00, 0x00, 0x00, 0x00,
484 0x00, 0x00, 0x00, 0x00, // pre_defined
485 0x01, 0x2c, // width = 300
486 0x00, 0x96, // height = 150
487 0x00, 0x48, 0x00, 0x00, // horizresolution
488 0x00, 0x48, 0x00, 0x00, // vertresolution
489 0x00, 0x00, 0x00, 0x00, // reserved
490 0x00, 0x01, // frame_count
491 0x04,
492 typeBytes('avc1'),
493 0x00, 0x00, 0x00, 0x00,
494 0x00, 0x00, 0x00, 0x00,
495 0x00, 0x00, 0x00, 0x00,
496 0x00, 0x00, 0x00, 0x00,
497 0x00, 0x00, 0x00, 0x00,
498 0x00, 0x00, 0x00, 0x00,
499 0x00, 0x00, 0x00, // compressorname
500 0x00, 0x18, // depth = 24
501 0x11, 0x11)); // pre_defined
502 deepEqual(videojs.inspectMp4(new Uint8Array(data)), [{
503 type: 'stsd',
504 size: 102,
505 version: 0,
506 flags: new Uint8Array([0, 0, 0]),
507 sampleDescriptions: [{
508 type: 'avc1',
509 size: 86,
510 dataReferenceIndex: 1,
511 width: 300,
512 height: 150,
513 horizresolution: 72,
514 vertresolution: 72,
515 frameCount: 1,
516 depth: 24
517 }]
518 }]);
519 });
520
433 test('can parse a series of boxes', function() { 521 test('can parse a series of boxes', function() {
434 var ftyp = [ 522 var ftyp = [
435 0x00, 0x00, 0x00, 0x18 // size 4 * 6 = 24 523 0x00, 0x00, 0x00, 0x18 // size 4 * 6 = 24
436 ].concat(typeBytes('ftyp')).concat([ 524 ].concat(typeBytes('ftyp')).concat([
437 0x00, 0x00, 0x00, 0x01, // major brand 525 0x69, 0x73, 0x6f, 0x6d, // major brand
438 0x00, 0x00, 0x00, 0x02, // minor version 526 0x00, 0x00, 0x00, 0x02, // minor version
439 0x00, 0x00, 0x00, 0x03, // compatible brands 527 0x00, 0x00, 0x00, 0x03, // compatible brands
440 0x00, 0x00, 0x00, 0x04, // compatible brands 528 0x00, 0x00, 0x00, 0x04, // compatible brands
...@@ -444,13 +532,13 @@ test('can parse a series of boxes', function() { ...@@ -444,13 +532,13 @@ test('can parse a series of boxes', function() {
444 [{ 532 [{
445 type: 'ftyp', 533 type: 'ftyp',
446 size: 4 * 6, 534 size: 4 * 6,
447 majorBrand: 1, 535 majorBrand: 'isom',
448 minorVersion: 2, 536 minorVersion: 2,
449 compatibleBrands: [3, 4] 537 compatibleBrands: [3, 4]
450 },{ 538 },{
451 type: 'ftyp', 539 type: 'ftyp',
452 size: 4 * 6, 540 size: 4 * 6,
453 majorBrand: 1, 541 majorBrand: 'isom',
454 minorVersion: 2, 542 minorVersion: 2,
455 compatibleBrands: [3, 4] 543 compatibleBrands: [3, 4]
456 }], 544 }],
......
...@@ -19,11 +19,26 @@ var ...@@ -19,11 +19,26 @@ var
19 19
20 // registry of handlers for individual mp4 box types 20 // registry of handlers for individual mp4 box types
21 parse = { 21 parse = {
22 // codingname, not a first-class box type. stsd entries share the
23 // same format as real boxes so the parsing infrastructure can be
24 // shared
25 avc1: function(data) {
26 var view = new DataView(data.buffer, data.byteOffset, data.byteLength);
27 return {
28 dataReferenceIndex: view.getUint16(6),
29 width: view.getUint16(24),
30 height: view.getUint16(26),
31 horizresolution: view.getUint16(28) + (view.getUint16(30) / 16),
32 vertresolution: view.getUint16(32) + (view.getUint16(34) / 16),
33 frameCount: view.getUint16(40),
34 depth: view.getUint16(74)
35 };
36 },
22 ftyp: function(data) { 37 ftyp: function(data) {
23 var 38 var
24 view = new DataView(data.buffer, data.byteOffset, data.byteLength), 39 view = new DataView(data.buffer, data.byteOffset, data.byteLength),
25 result = { 40 result = {
26 majorBrand: view.getUint32(0), 41 majorBrand: parseType(data.subarray(0, 4)),
27 minorVersion: view.getUint32(4), 42 minorVersion: view.getUint32(4),
28 compatibleBrands: [] 43 compatibleBrands: []
29 }, 44 },
...@@ -41,6 +56,8 @@ var ...@@ -41,6 +56,8 @@ var
41 }, 56 },
42 dref: function(data) { 57 dref: function(data) {
43 return { 58 return {
59 version: data[0],
60 flags: new Uint8Array(data.subarray(1, 4)),
44 dataReferences: [] 61 dataReferences: []
45 }; 62 };
46 }, 63 },
...@@ -129,6 +146,11 @@ var ...@@ -129,6 +146,11 @@ var
129 boxes: videojs.inspectMp4(data) 146 boxes: videojs.inspectMp4(data)
130 }; 147 };
131 }, 148 },
149 mvex: function(data) {
150 return {
151 boxes: videojs.inspectMp4(data)
152 };
153 },
132 mvhd: function(data) { 154 mvhd: function(data) {
133 var 155 var
134 view = new DataView(data.buffer, data.byteOffset, data.byteLength), 156 view = new DataView(data.buffer, data.byteOffset, data.byteLength),
...@@ -185,6 +207,23 @@ var ...@@ -185,6 +207,23 @@ var
185 boxes: videojs.inspectMp4(data) 207 boxes: videojs.inspectMp4(data)
186 }; 208 };
187 }, 209 },
210 trex: function(data) {
211 var view = new DataView(data.buffer, data.byteOffset, data.byteLength);
212 return {
213 version: data[0],
214 flags: new Uint8Array(data.subarray(1, 4)),
215 trackId: view.getUint32(4),
216 defaultSampleDescriptionIndex: view.getUint32(8),
217 defaultSampleDuration: view.getUint32(12),
218 defaultSampleSize: view.getUint32(16),
219 sampleDependsOn: data[20] & 0x03,
220 sampleIsDependedOn: (data[21] & 0xc0) >> 6,
221 sampleHasRedundancy: (data[21] & 0x30) >> 4,
222 samplePaddingValue: (data[21] & 0x0e) >> 1,
223 sampleIsDifferenceSample: !!(data[21] & 0x01),
224 sampleDegradationPriority: view.getUint16(22)
225 };
226 },
188 stbl: function(data) { 227 stbl: function(data) {
189 return { 228 return {
190 boxes: videojs.inspectMp4(data) 229 boxes: videojs.inspectMp4(data)
...@@ -202,7 +241,9 @@ var ...@@ -202,7 +241,9 @@ var
202 }, 241 },
203 stsd: function(data) { 242 stsd: function(data) {
204 return { 243 return {
205 sampleDescriptions: [] 244 version: data[0],
245 flags: new Uint8Array(data.subarray(1, 4)),
246 sampleDescriptions: videojs.inspectMp4(data.subarray(8))
206 }; 247 };
207 }, 248 },
208 stts: function(data) { 249 stts: function(data) {
......
...@@ -106,6 +106,7 @@ ...@@ -106,6 +106,7 @@
106 </script> 106 </script>
107 <script src="../../src/stream.js"></script> 107 <script src="../../src/stream.js"></script>
108 <script src="../../src/transmuxer.js"></script> 108 <script src="../../src/transmuxer.js"></script>
109 <script src="../../src/mp4-generator.js"></script>
109 <script src="js/mp4-inspector.js"></script> 110 <script src="js/mp4-inspector.js"></script>
110 111
111 <script src="../../src/bin-utils.js"></script> 112 <script src="../../src/bin-utils.js"></script>
...@@ -118,13 +119,9 @@ ...@@ -118,13 +119,9 @@
118 workingBoxes = document.querySelector('.working-boxes'), 119 workingBoxes = document.querySelector('.working-boxes'),
119 120
120 vjsOutput = document.querySelector('.vjs-hls-output'), 121 vjsOutput = document.querySelector('.vjs-hls-output'),
121 workingOutput = document.querySelector('.working-output'), 122 workingOutput = document.querySelector('.working-output');
122 123
123 tagTypes = { 124 // webkit dash-mp4 mime: 'video/mp4;codecs=avc1.4d0020,mp4a.40.2'
124 0x08: 'audio',
125 0x09: 'video',
126 0x12: 'metadata'
127 };
128 125
129 videojs.log = console.log.bind(console); 126 videojs.log = console.log.bind(console);
130 127
...@@ -132,19 +129,43 @@ ...@@ -132,19 +129,43 @@
132 var reader = new FileReader(); 129 var reader = new FileReader();
133 reader.addEventListener('loadend', function() { 130 reader.addEventListener('loadend', function() {
134 var segment = new Uint8Array(reader.result), 131 var segment = new Uint8Array(reader.result),
135 transmuxer = new videojs.Hls.Transmuxer(), 132 packetizer = new videojs.mp2t.PacketStream(),
133 parser = new videojs.mp2t.ParseStream(),
136 hex = ''; 134 hex = '';
137 135
138 transmuxer.push(segment); 136 packetizer.pipe(parser);
137 packetizer.push(segment);
139 138
140 // clear old boxes info 139 // clear old boxes info
141 vjsBoxes.innerHTML = JSON.stringify(videojs.inspectMp4(transmuxer.mp4), null, ' ');
142 140
143 // write out the result 141 // write out the result
144 hex += '<pre>'; 142 hex += '<pre>';
145 hex += videojs.Hls.utils.hexDump(transmuxer.mp4); 143 // hex += videojs.Hls.utils.hexDump(transmuxer.mp4);
146 hex += '</pre>'; 144 hex += '</pre>';
147 vjsOutput.innerHTML = hex; 145 vjsOutput.innerHTML = hex;
146
147 // XXX media source testing
148
149 vjsBoxes.innerHTML = JSON.stringify(videojs.inspectMp4(videojs.mp4.initSegment()), null, ' ');
150
151 var video = document.createElement('video');
152 var mediaSource = new MediaSource();
153 mediaSource.addEventListener('sourceopen', function() {
154 var buffer = mediaSource.addSourceBuffer('video/mp4;codecs=avc1.4d0020,mp4a.40.2');
155 buffer.addEventListener('updatestart', console.log.bind(console));
156 buffer.addEventListener('updateend', console.log.bind(console));
157 buffer.addEventListener('error', console.log.bind(console));
158 buffer.appendBuffer(videojs.mp4.initSegment());
159 console.log('done', mediaSource, buffer, video.error);
160 window.vjsMediaSource = mediaSource;
161 window.vjsSourceBuffer = buffer;
162 window.vjsVideo = video;
163 });
164 mediaSource.addEventListener('error', console.log.bind(console));
165 mediaSource.addEventListener('opened', console.log.bind(console));
166 mediaSource.addEventListener('closed', console.log.bind(console));
167 mediaSource.addEventListener('sourceended', console.log.bind(console));
168 video.src = URL.createObjectURL(mediaSource);
148 }); 169 });
149 reader.readAsArrayBuffer(this.files[0]); 170 reader.readAsArrayBuffer(this.files[0]);
150 }, false); 171 }, false);
......
...@@ -24,9 +24,7 @@ var ...@@ -24,9 +24,7 @@ var
24 PacketStream = videojs.mp2t.PacketStream, 24 PacketStream = videojs.mp2t.PacketStream,
25 packetStream, 25 packetStream,
26 ParseStream = videojs.mp2t.ParseStream, 26 ParseStream = videojs.mp2t.ParseStream,
27 parseStream, 27 parseStream;
28 Transmuxer = videojs.mp2t.Transmuxer,
29 transmuxer;
30 28
31 module('MP2T Packet Stream', { 29 module('MP2T Packet Stream', {
32 setup: function() { 30 setup: function() {
...@@ -394,17 +392,4 @@ test('parses an elementary stream packet without a pts or dts', function() { ...@@ -394,17 +392,4 @@ test('parses an elementary stream packet without a pts or dts', function() {
394 ok(!packet.dts, 'did not parse a dts'); 392 ok(!packet.dts, 'did not parse a dts');
395 }); 393 });
396 394
397 module('MP4 Transmuxer', {
398 setup: function() {
399 transmuxer = new Transmuxer();
400 }
401 });
402
403 test('can mux an empty mp2t', function() {
404 transmuxer.push(new Uint8Array());
405
406 ok(transmuxer.mp4, 'produced a non-null result');
407 strictEqual(transmuxer.mp4.byteLength, 0, 'produced an empty mp4');
408 });
409
410 })(window, window.videojs); 395 })(window, window.videojs);
......
...@@ -44,6 +44,7 @@ ...@@ -44,6 +44,7 @@
44 44
45 <!-- mp4 utilities --> 45 <!-- mp4 utilities -->
46 <script src="../src/transmuxer.js"></script> 46 <script src="../src/transmuxer.js"></script>
47 <script src="../src/mp4-generator.js"></script>
47 <script src="muxer/js/mp4-inspector.js"></script> 48 <script src="muxer/js/mp4-inspector.js"></script>
48 49
49 <!-- Test cases --> 50 <!-- Test cases -->
...@@ -66,6 +67,7 @@ ...@@ -66,6 +67,7 @@
66 <script src="decrypter_test.js"></script> 67 <script src="decrypter_test.js"></script>
67 <script src="transmuxer_test.js"></script> 68 <script src="transmuxer_test.js"></script>
68 <script src="mp4-inspector_test.js"></script> 69 <script src="mp4-inspector_test.js"></script>
70 <script src="mp4-generator_test.js"></script>
69 </head> 71 </head>
70 <body> 72 <body>
71 <div id="qunit"></div> 73 <div id="qunit"></div>
......