23a2bf27 by David LaPalomento

Inspect and generate avcC, btrt, stsz, tkhd, and vmhd boxes

Add support for a number of new boxes in the generator and inspector. Bring some existing generated boxes more closely into line with a working init segment.
1 parent 7cf3d82d
...@@ -2,7 +2,8 @@ ...@@ -2,7 +2,8 @@
2 'use strict'; 2 'use strict';
3 3
4 var box, dinf, ftyp, minf, moov, mvex, mvhd, trak, tkhd, mdia, mdhd, hdlr, stbl, 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; 5 stsd, types, MAJOR_BRAND, MINOR_VERSION, VIDEO_HDLR, VMHD, DREF, STCO, STSC, STSZ, STTS, TREX,
6 Uint8Array, DataView;
6 7
7 Uint8Array = window.Uint8Array; 8 Uint8Array = window.Uint8Array;
8 DataView = window.DataView; 9 DataView = window.DataView;
...@@ -12,6 +13,8 @@ DataView = window.DataView; ...@@ -12,6 +13,8 @@ DataView = window.DataView;
12 var i; 13 var i;
13 types = { 14 types = {
14 avc1: [], // codingname 15 avc1: [], // codingname
16 avcC: [],
17 btrt: [],
15 dinf: [], 18 dinf: [],
16 dref: [], 19 dref: [],
17 ftyp: [], 20 ftyp: [],
...@@ -26,10 +29,12 @@ DataView = window.DataView; ...@@ -26,10 +29,12 @@ DataView = window.DataView;
26 stco: [], 29 stco: [],
27 stsc: [], 30 stsc: [],
28 stsd: [], 31 stsd: [],
32 stsz: [],
29 stts: [], 33 stts: [],
30 trak: [], 34 trak: [],
31 trex: [], 35 trex: [],
32 tkhd: [] 36 tkhd: [],
37 vmhd: []
33 }; 38 };
34 39
35 for (i in types) { 40 for (i in types) {
...@@ -65,7 +70,11 @@ DataView = window.DataView; ...@@ -65,7 +70,11 @@ DataView = window.DataView;
65 DREF = new Uint8Array([ 70 DREF = new Uint8Array([
66 0x00, // version 0 71 0x00, // version 0
67 0x00, 0x00, 0x00, // flags 72 0x00, 0x00, 0x00, // flags
68 0x00, 0x00, 0x00, 0x00 // entry_count 73 0x00, 0x00, 0x00, 0x01, // entry_count
74 0x00, 0x00, 0x00, 0x0c, // entry_size
75 0x75, 0x72, 0x6c, 0x20, // 'url' type
76 0x00, // version 0
77 0x00, 0x00, 0x01 // entry_flags
69 ]); 78 ]);
70 TREX = new Uint8Array([ 79 TREX = new Uint8Array([
71 0x00, // version 0 80 0x00, // version 0
...@@ -76,6 +85,27 @@ DataView = window.DataView; ...@@ -76,6 +85,27 @@ DataView = window.DataView;
76 0x00, 0x00, 0x00, 0x00, // default_sample_size 85 0x00, 0x00, 0x00, 0x00, // default_sample_size
77 0x00, 0x01, 0x00, 0x01 // default_sample_flags 86 0x00, 0x01, 0x00, 0x01 // default_sample_flags
78 ]); 87 ]);
88 STCO = new Uint8Array([
89 0x00, // version
90 0x00, 0x00, 0x00, // flags
91 0x00, 0x00, 0x00, 0x00 // entry_count
92 ]);
93 STSC = STCO;
94 STSZ = new Uint8Array([
95 0x00, // version
96 0x00, 0x00, 0x00, // flags
97 0x00, 0x00, 0x00, 0x00, // sample_size
98 0x00, 0x00, 0x00, 0x00, // sample_count
99 ]);
100 STTS = STCO;
101 VMHD = new Uint8Array([
102 0x00, // version
103 0x00, 0x00, 0x00, // flags
104 0x00, 0x00, // graphicsmode
105 0x00, 0x00,
106 0x00, 0x00,
107 0x00, 0x00 // opcolor
108 ]);
79 })(); 109 })();
80 110
81 box = function(type) { 111 box = function(type) {
...@@ -120,7 +150,8 @@ mdhd = function(duration) { ...@@ -120,7 +150,8 @@ mdhd = function(duration) {
120 0x00, 0x00, 0x00, // flags 150 0x00, 0x00, 0x00, // flags
121 0x00, 0x00, 0x00, 0x02, // creation_time 151 0x00, 0x00, 0x00, 0x02, // creation_time
122 0x00, 0x00, 0x00, 0x03, // modification_time 152 0x00, 0x00, 0x00, 0x03, // modification_time
123 0x00, 0x00, 0x00, 0x3c, // timescale 153 0x00, 0x01, 0x5f, 0x90, // timescale, 90,000 "ticks" per second
154
124 (duration & 0xFF000000) >> 24, 155 (duration & 0xFF000000) >> 24,
125 (duration & 0xFF0000) >> 16, 156 (duration & 0xFF0000) >> 16,
126 (duration & 0xFF00) >> 8, 157 (duration & 0xFF00) >> 8,
...@@ -133,7 +164,7 @@ mdia = function(duration, width, height) { ...@@ -133,7 +164,7 @@ mdia = function(duration, width, height) {
133 return box(types.mdia, mdhd(duration), hdlr(), minf(width, height)); 164 return box(types.mdia, mdhd(duration), hdlr(), minf(width, height));
134 }; 165 };
135 minf = function(width, height) { 166 minf = function(width, height) {
136 return box(types.minf, dinf(), stbl(width, height)); 167 return box(types.minf, box(types.vmhd, VMHD), dinf(), stbl(width, height));
137 }; 168 };
138 moov = function(duration, width, height) { 169 moov = function(duration, width, height) {
139 return box(types.moov, mvhd(duration), trak(duration, width, height), mvex()); 170 return box(types.moov, mvhd(duration), trak(duration, width, height), mvex());
...@@ -148,7 +179,7 @@ mvhd = function(duration) { ...@@ -148,7 +179,7 @@ mvhd = function(duration) {
148 0x00, 0x00, 0x00, // flags 179 0x00, 0x00, 0x00, // flags
149 0x00, 0x00, 0x00, 0x01, // creation_time 180 0x00, 0x00, 0x00, 0x01, // creation_time
150 0x00, 0x00, 0x00, 0x02, // modification_time 181 0x00, 0x00, 0x00, 0x02, // modification_time
151 0x00, 0x00, 0x00, 0x01, // timescale, 1 "tick" per second 182 0x00, 0x01, 0x5f, 0x90, // timescale, 90,000 "ticks" per second
152 (duration & 0xFF000000) >> 24, 183 (duration & 0xFF000000) >> 24,
153 (duration & 0xFF0000) >> 16, 184 (duration & 0xFF0000) >> 16,
154 (duration & 0xFF00) >> 8, 185 (duration & 0xFF00) >> 8,
...@@ -181,9 +212,10 @@ mvhd = function(duration) { ...@@ -181,9 +212,10 @@ mvhd = function(duration) {
181 stbl = function(width, height) { 212 stbl = function(width, height) {
182 return box(types.stbl, 213 return box(types.stbl,
183 stsd(width, height), 214 stsd(width, height),
184 box(types.stts), 215 box(types.stts, STTS),
185 box(types.stsc), 216 box(types.stsc, STSC),
186 box(types.stco)); 217 box(types.stsz, STSZ),
218 box(types.stco, STCO));
187 }; 219 };
188 220
189 stsd = function(width, height) { 221 stsd = function(width, height) {
...@@ -218,7 +250,26 @@ stsd = function(width, height) { ...@@ -218,7 +250,26 @@ stsd = function(width, height) {
218 0x00, 0x00, 0x00, 0x00, 250 0x00, 0x00, 0x00, 0x00,
219 0x00, 0x00, 0x00, // compressorname 251 0x00, 0x00, 0x00, // compressorname
220 0x00, 0x18, // depth = 24 252 0x00, 0x18, // depth = 24
221 0x11, 0x11]))); // pre_defined = -1 253 0x11, 0x11]), // pre_defined = -1
254 box(types.avcC, new Uint8Array([
255 0x01, // configurationVersion
256 0x4d, // AVCProfileIndication??
257 0x40, // profile_compatibility
258 0x20, // AVCLevelIndication
259 0xff, // lengthSizeMinusOne
260 0xe1, // numOfSequenceParameterSets
261 0x00, 0x0c, // sequenceParameterSetLength
262 0x67, 0x4d, 0x40, 0x20,
263 0x96, 0x52, 0x80, 0xa0,
264 0x0b, 0x76, 0x02, 0x05, // SPS
265 0x01, // numOfPictureParameterSets
266 0x00, 0x04, // pictureParameterSetLength
267 0x68, 0xef, 0x38, 0x80])), // "PPS"
268 box(types.btrt, new Uint8Array([
269 0x00, 0x1c, 0x9c, 0x80, // bufferSizeDB
270 0x00, 0x2d, 0xc6, 0xc0, // maxBitrate
271 0x00, 0x2d, 0xc6, 0xc0])) // avgBitrate
272 ));
222 }; 273 };
223 274
224 tkhd = function(duration, width, height) { 275 tkhd = function(duration, width, height) {
...@@ -248,14 +299,12 @@ tkhd = function(duration, width, height) { ...@@ -248,14 +299,12 @@ tkhd = function(duration, width, height) {
248 0x00, 0x00, 0x00, 0x00, 299 0x00, 0x00, 0x00, 0x00,
249 0x00, 0x00, 0x00, 0x00, 300 0x00, 0x00, 0x00, 0x00,
250 0x40, 0x00, 0x00, 0x00, // transformation: unity matrix 301 0x40, 0x00, 0x00, 0x00, // transformation: unity matrix
251 (width & 0xFF000000) >> 24,
252 (width & 0xFF0000) >> 16,
253 (width & 0xFF00) >> 8, 302 (width & 0xFF00) >> 8,
254 width & 0xFF, // width 303 width & 0xFF,
255 (height & 0xFF000000) >> 24, 304 0x00, 0x00, // width
256 (height & 0xFF0000) >> 16,
257 (height & 0xFF00) >> 8, 305 (height & 0xFF00) >> 8,
258 height & 0xFF // height 306 height & 0xFF,
307 0x00, 0x00 // height
259 ])); 308 ]));
260 }; 309 };
261 310
...@@ -269,7 +318,7 @@ window.videojs.mp4 = { ...@@ -269,7 +318,7 @@ window.videojs.mp4 = {
269 initSegment: function() { 318 initSegment: function() {
270 var 319 var
271 fileType = ftyp(), 320 fileType = ftyp(),
272 movie = moov(1, 1280, 720), 321 movie = moov(0xffffffff, 1280, 720),
273 result = new Uint8Array(fileType.byteLength + movie.byteLength); 322 result = new Uint8Array(fileType.byteLength + movie.byteLength);
274 323
275 result.set(fileType); 324 result.set(fileType);
......
...@@ -78,29 +78,48 @@ test('generates a moov', function() { ...@@ -78,29 +78,48 @@ test('generates a moov', function() {
78 78
79 minf = boxes[0].boxes[1].boxes[1].boxes[2]; 79 minf = boxes[0].boxes[1].boxes[1].boxes[2];
80 equal(minf.type, 'minf', 'generate an minf type'); 80 equal(minf.type, 'minf', 'generate an minf type');
81 equal(minf.boxes.length, 2, 'generates two minf sub boxes'); 81 equal(minf.boxes.length, 3, 'generates three minf sub boxes');
82
83 equal(minf.boxes[0].type, 'vmhd', 'generates a vmhd type');
84 deepEqual({
85 type: 'vmhd',
86 size: 20,
87 version: 0,
88 flags: new Uint8Array([0, 0, 0]),
89 graphicsmode: 0,
90 opcolor: new Uint16Array([0, 0, 0])
91 }, minf.boxes[0], 'generates a vhmd');
92
93 equal(minf.boxes[1].type, 'dinf', 'generates a dinf type');
82 deepEqual({ 94 deepEqual({
83 type: 'dinf', 95 type: 'dinf',
84 size: 24, 96 size: 36,
85 boxes: [{ 97 boxes: [{
86 type: 'dref', 98 type: 'dref',
87 size: 16, 99 size: 28,
88 version: 0, 100 version: 0,
89 flags: new Uint8Array([0, 0, 0]), 101 flags: new Uint8Array([0, 0, 0]),
90 dataReferences: [] 102 dataReferences: [{
103 type: 'url ',
104 size: 12,
105 version: 0,
106 flags: new Uint8Array([0, 0, 1])
107 }]
91 }] 108 }]
92 }, minf.boxes[0], 'generates a dinf'); 109 }, minf.boxes[1], 'generates a dinf');
93 110
94 equal(minf.boxes[1].type, 'stbl', 'generates an stbl type'); 111 equal(minf.boxes[2].type, 'stbl', 'generates an stbl type');
95 deepEqual({ 112 deepEqual({
96 type: 'stbl', 113 type: 'stbl',
97 size: 134, 114 size: 233,
98 boxes: [{ 115 boxes: [{
99 type: 'stsd', 116 type: 'stsd',
100 size: 102, 117 size: 157,
101 version: 0, 118 version: 0,
102 flags: new Uint8Array([0, 0, 0]), 119 flags: new Uint8Array([0, 0, 0]),
103 sampleDescriptions: [{ 120 sampleDescriptions: [{
121 type: 'avc1',
122 size: 141,
104 dataReferenceIndex: 1, 123 dataReferenceIndex: 1,
105 width: 600, 124 width: 600,
106 height: 300, 125 height: 300,
...@@ -108,23 +127,57 @@ test('generates a moov', function() { ...@@ -108,23 +127,57 @@ test('generates a moov', function() {
108 vertresolution: 72, 127 vertresolution: 72,
109 frameCount: 1, 128 frameCount: 1,
110 depth: 24, 129 depth: 24,
111 size: 86, 130 config: [{
112 type: 'avc1' 131 type: 'avcC',
132 size: 35,
133 configurationVersion: 1,
134 avcProfileIndication: 0x4d,
135 profileCompatibility: 0x40,
136 avcLevelIndication: 0x20,
137 lengthSizeMinusOne: 3,
138 sps: [new Uint8Array([
139 0x67, 0x4d, 0x40, 0x20,
140 0x96, 0x52, 0x80, 0xa0,
141 0x0b, 0x76, 0x02, 0x05
142 ])],
143 pps: [new Uint8Array([
144 0x68, 0xef, 0x38, 0x80
145 ])]
146 }, {
147 type: 'btrt',
148 size: 20,
149 bufferSizeDB: 1875072,
150 maxBitrate: 3000000,
151 avgBitrate: 3000000
152 }]
113 }] 153 }]
114 }, { 154 }, {
115 type: 'stts', 155 type: 'stts',
116 size: 8, 156 size: 16,
157 version: 0,
158 flags: new Uint8Array([0, 0, 0]),
117 timeToSamples: [] 159 timeToSamples: []
118 }, { 160 }, {
119 type: 'stsc', 161 type: 'stsc',
120 size: 8, 162 size: 16,
163 version: 0,
164 flags: new Uint8Array([0, 0, 0]),
121 sampleToChunks: [] 165 sampleToChunks: []
122 }, { 166 }, {
167 type: 'stsz',
168 version: 0,
169 size: 20,
170 flags: new Uint8Array([0, 0, 0]),
171 sampleSize: 0,
172 entries: []
173 }, {
123 type: 'stco', 174 type: 'stco',
124 size: 8, 175 size: 16,
176 version: 0,
177 flags: new Uint8Array([0, 0, 0]),
125 chunkOffsets: [] 178 chunkOffsets: []
126 }] 179 }]
127 }, minf.boxes[1], 'generates a stbl'); 180 }, minf.boxes[2], 'generates a stbl');
128 181
129 182
130 mvex = boxes[0].boxes[2]; 183 mvex = boxes[0].boxes[2];
...@@ -160,6 +213,7 @@ test('generates an initialization segment', function() { ...@@ -160,6 +213,7 @@ test('generates an initialization segment', function() {
160 equal(init.length, 2, 'generated two boxes'); 213 equal(init.length, 2, 'generated two boxes');
161 equal(init[0].type, 'ftyp', 'generated a ftyp box'); 214 equal(init[0].type, 'ftyp', 'generated a ftyp box');
162 equal(init[1].type, 'moov', 'generated a moov box'); 215 equal(init[1].type, 'moov', 'generated a moov box');
216 equal(init[1].boxes[0].duration, 0xffffffff, 'wrote a maximum duration');
163 }); 217 });
164 218
165 219
......
...@@ -105,8 +105,8 @@ var ...@@ -105,8 +105,8 @@ var
105 0x00, 0x00, // non-audio track volume 105 0x00, 0x00, // non-audio track volume
106 0x00, 0x00, // reserved 106 0x00, 0x00, // reserved
107 unityMatrix, 107 unityMatrix,
108 0x00, 0x00, 0x01, 0x2c, // 300 = 0x12c width 108 0x01, 0x2c, 0x00, 0x00, // 300 in 16.16 fixed point
109 0x00, 0x00, 0x00, 0x96), // 150 = 0x96 height 109 0x00, 0x96, 0x00, 0x00), // 150 in 16.16 fixed point
110 mdhd0 = box('mdhd', 110 mdhd0 = box('mdhd',
111 0x00, // version 0 111 0x00, // version 0
112 0x00, 0x00, 0x00, // flags 112 0x00, 0x00, 0x00, // flags
...@@ -201,8 +201,8 @@ test('can parse a version 0 mvhd', function() { ...@@ -201,8 +201,8 @@ test('can parse a version 0 mvhd', function() {
201 type: 'mvhd', 201 type: 'mvhd',
202 version: 0, 202 version: 0,
203 flags: new Uint8Array([0, 0, 0]), 203 flags: new Uint8Array([0, 0, 0]),
204 creationTime: 1, 204 creationTime: new Date(1000 - 2082844800000),
205 modificationTime: 2, 205 modificationTime: new Date(2000 - 2082844800000),
206 timescale: 60, 206 timescale: 60,
207 duration: 600, 207 duration: 600,
208 rate: 1, 208 rate: 1,
...@@ -218,8 +218,8 @@ test('can parse a version 0 tkhd', function() { ...@@ -218,8 +218,8 @@ test('can parse a version 0 tkhd', function() {
218 type: 'tkhd', 218 type: 'tkhd',
219 version: 0, 219 version: 0,
220 flags: new Uint8Array([0, 0, 0]), 220 flags: new Uint8Array([0, 0, 0]),
221 creationTime: 2, 221 creationTime: new Date(2000 - 2082844800000),
222 modificationTime: 3, 222 modificationTime: new Date(3000 - 2082844800000),
223 size: 92, 223 size: 92,
224 trackId: 1, 224 trackId: 1,
225 duration: 600, 225 duration: 600,
...@@ -237,8 +237,8 @@ test('can parse a version 0 mdhd', function() { ...@@ -237,8 +237,8 @@ test('can parse a version 0 mdhd', function() {
237 type: 'mdhd', 237 type: 'mdhd',
238 version: 0, 238 version: 0,
239 flags: new Uint8Array([0, 0, 0]), 239 flags: new Uint8Array([0, 0, 0]),
240 creationTime: 2, 240 creationTime: new Date(2000 - 2082844800000),
241 modificationTime: 3, 241 modificationTime: new Date(3000 - 2082844800000),
242 size: 32, 242 size: 32,
243 timescale: 60, 243 timescale: 60,
244 duration: 600, 244 duration: 600,
...@@ -291,8 +291,8 @@ test('can parse a moov', function() { ...@@ -291,8 +291,8 @@ test('can parse a moov', function() {
291 0x00, 0x00, // non-audio track volume 291 0x00, 0x00, // non-audio track volume
292 0x00, 0x00, // reserved 292 0x00, 0x00, // reserved
293 unityMatrix, 293 unityMatrix,
294 0x00, 0x00, 0x01, 0x2c, // 300 = 0x12c width 294 0x01, 0x2c, 0x00, 0x00, // 300 in 16.16 fixed-point
295 0x00, 0x00, 0x00, 0x96), // 150 = 0x96 height 295 0x00, 0x96, 0x00, 0x00), // 150 in 16.16 fixed-point
296 box('mdia', 296 box('mdia',
297 box('mdhd', 297 box('mdhd',
298 0x01, // version 1 298 0x01, // version 1
...@@ -320,7 +320,10 @@ test('can parse a moov', function() { ...@@ -320,7 +320,10 @@ test('can parse a moov', function() {
320 box('dref', 320 box('dref',
321 0x01, // version 1 321 0x01, // version 1
322 0x00, 0x00, 0x00, // flags 322 0x00, 0x00, 0x00, // flags
323 0x00, 0x00, 0x00, 0x00)), // entry_count 323 0x00, 0x00, 0x00, 0x01, // entry_count
324 box('url ',
325 0x00, // version
326 0x00, 0x00, 0x01))), // flags
324 box('stbl', 327 box('stbl',
325 box('stsd', 328 box('stsd',
326 0x01, // version 1 329 0x01, // version 1
...@@ -329,25 +332,32 @@ test('can parse a moov', function() { ...@@ -329,25 +332,32 @@ test('can parse a moov', function() {
329 box('stts', 332 box('stts',
330 0x01, // version 1 333 0x01, // version 1
331 0x00, 0x00, 0x00, // flags 334 0x00, 0x00, 0x00, // flags
332 0x00, 0x00, 0x00, 0x00), // entry_count 335 0x00, 0x00, 0x00, 0x01, // entry_count
336 0x00, 0x00, 0x00, 0x01, // sample_count
337 0x00, 0x00, 0x00, 0x01), // sample_delta
333 box('stsc', 338 box('stsc',
334 0x01, // version 1 339 0x01, // version 1
335 0x00, 0x00, 0x00, // flags 340 0x00, 0x00, 0x00, // flags
336 0x00, 0x00, 0x00, 0x00), // entry_count 341 0x00, 0x00, 0x00, 0x01, // entry_count
342 0x00, 0x00, 0x00, 0x02, // first_chunk
343 0x00, 0x00, 0x00, 0x03, // samples_per_chunk
344 0x00, 0x00, 0x00, 0x01), // sample_description_index
337 box('stco', 345 box('stco',
338 0x01, // version 1 346 0x01, // version 1
339 0x00, 0x00, 0x00, // flags 347 0x00, 0x00, 0x00, // flags
340 0x00, 0x00, 0x00, 0x00)))))); // entry_count; 348 0x00, 0x00, 0x00, 0x01, // entry_count
349 0x00, 0x00, 0x00, 0x01)))))); // chunk_offset
350
341 351
342 deepEqual(videojs.inspectMp4(new Uint8Array(data)), [{ 352 deepEqual(videojs.inspectMp4(new Uint8Array(data)), [{
343 size: 433,
344 type: 'moov', 353 type: 'moov',
354 size: 469,
345 boxes: [{ 355 boxes: [{
346 type: 'mvhd', 356 type: 'mvhd',
347 version: 1, 357 version: 1,
348 flags: new Uint8Array([0, 0, 0]), 358 flags: new Uint8Array([0, 0, 0]),
349 creationTime: 1, 359 creationTime: new Date(1000 - 2082844800000),
350 modificationTime: 2, 360 modificationTime: new Date(2000 - 2082844800000),
351 timescale: 60, 361 timescale: 60,
352 duration: 600, 362 duration: 600,
353 rate: 1, 363 rate: 1,
...@@ -357,13 +367,13 @@ test('can parse a moov', function() { ...@@ -357,13 +367,13 @@ test('can parse a moov', function() {
357 nextTrackId: 2 367 nextTrackId: 2
358 }, { 368 }, {
359 type: 'trak', 369 type: 'trak',
360 size: 305, 370 size: 341,
361 boxes: [{ 371 boxes: [{
362 type: 'tkhd', 372 type: 'tkhd',
363 flags: new Uint8Array([0, 0, 0]), 373 flags: new Uint8Array([0, 0, 0]),
364 version: 1, 374 version: 1,
365 creationTime: 2, 375 creationTime: new Date(2000 - 2082844800000),
366 modificationTime: 3, 376 modificationTime: new Date(3000 - 2082844800000),
367 size: 104, 377 size: 104,
368 trackId: 1, 378 trackId: 1,
369 duration: 600, 379 duration: 600,
...@@ -375,13 +385,13 @@ test('can parse a moov', function() { ...@@ -375,13 +385,13 @@ test('can parse a moov', function() {
375 height: 150 385 height: 150
376 }, { 386 }, {
377 type: 'mdia', 387 type: 'mdia',
378 size: 193, 388 size: 229,
379 boxes: [{ 389 boxes: [{
380 type: 'mdhd', 390 type: 'mdhd',
381 version: 1, 391 version: 1,
382 flags: new Uint8Array([0, 0, 0]), 392 flags: new Uint8Array([0, 0, 0]),
383 creationTime: 2, 393 creationTime: new Date(2000 - 2082844800000),
384 modificationTime: 3, 394 modificationTime: new Date(3000 - 2082844800000),
385 timescale: 60, 395 timescale: 60,
386 duration: 600, 396 duration: 600,
387 language: 'eng', 397 language: 'eng',
...@@ -395,20 +405,25 @@ test('can parse a moov', function() { ...@@ -395,20 +405,25 @@ test('can parse a moov', function() {
395 size: 37 405 size: 37
396 }, { 406 }, {
397 type: 'minf', 407 type: 'minf',
398 size: 104, 408 size: 140,
399 boxes: [{ 409 boxes: [{
400 type: 'dinf', 410 type: 'dinf',
401 size: 24, 411 size: 36,
402 boxes: [{ 412 boxes: [{
403 type: 'dref', 413 type: 'dref',
414 size: 28,
404 version: 1, 415 version: 1,
405 flags: new Uint8Array([0, 0, 0]), 416 flags: new Uint8Array([0, 0, 0]),
406 dataReferences: [], 417 dataReferences: [{
407 size: 16 418 type: 'url ',
419 size: 12,
420 version: 0,
421 flags: new Uint8Array([0, 0, 1])
422 }],
408 }] 423 }]
409 }, { 424 }, {
410 type: 'stbl', 425 type: 'stbl',
411 size: 72, 426 size: 96,
412 boxes: [{ 427 boxes: [{
413 type: 'stsd', 428 type: 'stsd',
414 size: 16, 429 size: 16,
...@@ -417,16 +432,29 @@ test('can parse a moov', function() { ...@@ -417,16 +432,29 @@ test('can parse a moov', function() {
417 sampleDescriptions: [], 432 sampleDescriptions: [],
418 }, { 433 }, {
419 type: 'stts', 434 type: 'stts',
420 timeToSamples: [], 435 size: 24,
421 size: 16 436 version: 1,
437 flags: new Uint8Array([0, 0, 0]),
438 timeToSamples: [{
439 sampleCount: 1,
440 sampleDelta: 1
441 }]
422 }, { 442 }, {
423 type: 'stsc', 443 type: 'stsc',
424 sampleToChunks: [], 444 version: 1,
425 size: 16 445 flags: new Uint8Array([0, 0, 0]),
446 sampleToChunks: [{
447 firstChunk: 2,
448 samplesPerChunk: 3,
449 sampleDescriptionIndex: 1
450 }],
451 size: 28
426 }, { 452 }, {
427 type: 'stco', 453 type: 'stco',
428 chunkOffsets: [], 454 size: 20,
429 size: 16 455 version: 1,
456 flags: new Uint8Array([0, 0, 0]),
457 chunkOffsets: [1]
430 }] 458 }]
431 }] 459 }]
432 }] 460 }]
...@@ -498,23 +526,99 @@ test('can parse a video stsd', function() { ...@@ -498,23 +526,99 @@ test('can parse a video stsd', function() {
498 0x00, 0x00, 0x00, 0x00, 526 0x00, 0x00, 0x00, 0x00,
499 0x00, 0x00, 0x00, // compressorname 527 0x00, 0x00, 0x00, // compressorname
500 0x00, 0x18, // depth = 24 528 0x00, 0x18, // depth = 24
501 0x11, 0x11)); // pre_defined 529 0x11, 0x11, // pre_defined
530 box('avcC',
531 0x01, // configurationVersion
532 0x00, // AVCProfileIndication??
533 0x00, // profile_compatibility
534 0x00, // AVCLevelIndication
535 0x1c, // lengthSizeMinusOne
536 0xe1, // numOfSequenceParameterSets
537 0x00, 0x01, // sequenceParameterSetLength
538 0x00, // "SPS"
539 0x02, // numOfPictureParameterSets
540 0x00, 0x02, // pictureParameterSetLength
541 0x01, 0x02, // "PPS"
542 0x00, 0x01, // pictureParameterSetLength
543 0xff), // "PPS"
544 box('btrt',
545 0x00, 0x00, 0x00, 0x00, // bufferSizeDB
546 0x00, 0x00, 0x00, 0x01, // maxBitrate
547 0x00, 0x00, 0x00, 0x01))); // avgBitrate
502 deepEqual(videojs.inspectMp4(new Uint8Array(data)), [{ 548 deepEqual(videojs.inspectMp4(new Uint8Array(data)), [{
503 type: 'stsd', 549 type: 'stsd',
504 size: 102, 550 size: 147,
505 version: 0, 551 version: 0,
506 flags: new Uint8Array([0, 0, 0]), 552 flags: new Uint8Array([0, 0, 0]),
507 sampleDescriptions: [{ 553 sampleDescriptions: [{
508 type: 'avc1', 554 type: 'avc1',
509 size: 86, 555 size: 131,
510 dataReferenceIndex: 1, 556 dataReferenceIndex: 1,
511 width: 300, 557 width: 300,
512 height: 150, 558 height: 150,
513 horizresolution: 72, 559 horizresolution: 72,
514 vertresolution: 72, 560 vertresolution: 72,
515 frameCount: 1, 561 frameCount: 1,
516 depth: 24 562 depth: 24,
563 config: [{
564 type: 'avcC',
565 size: 25,
566 configurationVersion: 1,
567 avcProfileIndication: 0,
568 profileCompatibility: 0,
569 avcLevelIndication: 0,
570 lengthSizeMinusOne: 0,
571 sps: [new Uint8Array(1)],
572 pps: [new Uint8Array([1, 2]),
573 new Uint8Array([0xff])]
574 }, {
575 type: 'btrt',
576 size: 20,
577 bufferSizeDB: 0,
578 maxBitrate: 1,
579 avgBitrate: 1
517 }] 580 }]
581 }]
582 }]);
583 });
584
585 test('can parse a vmhd', function() {
586 var data = box('vmhd',
587 0x00, // version
588 0x00, 0x00, 0x00, // flags
589 0x00, 0x00, // graphicsmode
590 0x00, 0x00,
591 0x00, 0x00,
592 0x00, 0x00); // opcolor
593
594 deepEqual(videojs.inspectMp4(new Uint8Array(data)),
595 [{
596 type: 'vmhd',
597 size: 20,
598 version: 0,
599 flags: new Uint8Array([0, 0, 0]),
600 graphicsmode: 0,
601 opcolor: new Uint16Array([0, 0, 0])
602 }]);
603 });
604
605 test('can parse an stsz', function() {
606 var data = box('stsz',
607 0x00, // version
608 0x00, 0x00, 0x00, // flags
609 0x00, 0x00, 0x00, 0x00, // sample_size
610 0x00, 0x00, 0x00, 0x03, // sample_count
611 0x00, 0x00, 0x00, 0x01, // entry_size
612 0x00, 0x00, 0x00, 0x02, // entry_size
613 0x00, 0x00, 0x00, 0x03); // entry_size
614 deepEqual(videojs.inspectMp4(new Uint8Array(data)),
615 [{
616 type: 'stsz',
617 size: 32,
618 version: 0,
619 flags: new Uint8Array([0, 0, 0]),
620 sampleSize: 0,
621 entries: [1, 2, 3]
518 }]); 622 }]);
519 }); 623 });
520 624
......
...@@ -16,6 +16,9 @@ var ...@@ -16,6 +16,9 @@ var
16 result += String.fromCharCode(buffer[3]); 16 result += String.fromCharCode(buffer[3]);
17 return result; 17 return result;
18 }, 18 },
19 parseMp4Date = function(seconds) {
20 return new Date(seconds * 1000 - 2082844800000);
21 },
19 22
20 // registry of handlers for individual mp4 box types 23 // registry of handlers for individual mp4 box types
21 parse = { 24 parse = {
...@@ -31,7 +34,53 @@ var ...@@ -31,7 +34,53 @@ var
31 horizresolution: view.getUint16(28) + (view.getUint16(30) / 16), 34 horizresolution: view.getUint16(28) + (view.getUint16(30) / 16),
32 vertresolution: view.getUint16(32) + (view.getUint16(34) / 16), 35 vertresolution: view.getUint16(32) + (view.getUint16(34) / 16),
33 frameCount: view.getUint16(40), 36 frameCount: view.getUint16(40),
34 depth: view.getUint16(74) 37 depth: view.getUint16(74),
38 config: videojs.inspectMp4(data.subarray(78, data.byteLength))
39 };
40 },
41 avcC: function(data) {
42 var
43 view = new DataView(data.buffer, data.byteOffset, data.byteLength),
44 result = {
45 configurationVersion: data[0],
46 avcProfileIndication: data[1],
47 profileCompatibility: data[2],
48 avcLevelIndication: data[3],
49 lengthSizeMinusOne: data[4] & 0x03,
50 sps: [],
51 pps: []
52 },
53 numOfSequenceParameterSets = data[5] & 0x1f,
54 numOfPictureParameterSets,
55 nalSize,
56 offset,
57 i;
58
59 // iterate past any SPSs
60 offset = 6;
61 for (i = 0; i < numOfSequenceParameterSets; i++) {
62 nalSize = view.getUint16(offset);
63 offset += 2;
64 result.sps.push(data.subarray(offset, offset + nalSize));
65 offset += nalSize;
66 }
67 // iterate past any PPSs
68 numOfPictureParameterSets = data[offset];
69 offset++;
70 for (i = 0; i < numOfPictureParameterSets; i++) {
71 nalSize = view.getUint16(offset);
72 offset += 2;
73 result.pps.push(data.subarray(offset, offset + nalSize));
74 offset += nalSize;
75 }
76 return result;
77 },
78 btrt: function(data) {
79 var view = new DataView(data.buffer, data.byteOffset, data.byteLength);
80 return {
81 bufferSizeDB: view.getUint32(0),
82 maxBitrate: view.getUint32(4),
83 avgBitrate: view.getUint32(8)
35 }; 84 };
36 }, 85 },
37 ftyp: function(data) { 86 ftyp: function(data) {
...@@ -58,7 +107,7 @@ var ...@@ -58,7 +107,7 @@ var
58 return { 107 return {
59 version: data[0], 108 version: data[0],
60 flags: new Uint8Array(data.subarray(1, 4)), 109 flags: new Uint8Array(data.subarray(1, 4)),
61 dataReferences: [] 110 dataReferences: videojs.inspectMp4(data.subarray(8))
62 }; 111 };
63 }, 112 },
64 hdlr: function(data) { 113 hdlr: function(data) {
...@@ -105,17 +154,17 @@ var ...@@ -105,17 +154,17 @@ var
105 }; 154 };
106 if (result.version === 1) { 155 if (result.version === 1) {
107 i += 4; 156 i += 4;
108 result.creationTime = view.getUint32(i); // truncating top 4 bytes 157 result.creationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes
109 i += 8; 158 i += 8;
110 result.modificationTime = view.getUint32(i); // truncating top 4 bytes 159 result.modificationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes
111 i += 4; 160 i += 4;
112 result.timescale = view.getUint32(i); 161 result.timescale = view.getUint32(i);
113 i += 8; 162 i += 8;
114 result.duration = view.getUint32(i); // truncating top 4 bytes 163 result.duration = view.getUint32(i); // truncating top 4 bytes
115 } else { 164 } else {
116 result.creationTime = view.getUint32(i); 165 result.creationTime = parseMp4Date(view.getUint32(i));
117 i += 4; 166 i += 4;
118 result.modificationTime = view.getUint32(i); 167 result.modificationTime = parseMp4Date(view.getUint32(i));
119 i += 4; 168 i += 4;
120 result.timescale = view.getUint32(i); 169 result.timescale = view.getUint32(i);
121 i += 4; 170 i += 4;
...@@ -162,17 +211,17 @@ var ...@@ -162,17 +211,17 @@ var
162 211
163 if (result.version === 1) { 212 if (result.version === 1) {
164 i += 4; 213 i += 4;
165 result.creationTime = view.getUint32(i); // truncating top 4 bytes 214 result.creationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes
166 i += 8; 215 i += 8;
167 result.modificationTime = view.getUint32(i); // truncating top 4 bytes 216 result.modificationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes
168 i += 4; 217 i += 4;
169 result.timescale = view.getUint32(i); 218 result.timescale = view.getUint32(i);
170 i += 8 219 i += 8
171 result.duration = view.getUint32(i); // truncating top 4 bytes 220 result.duration = view.getUint32(i); // truncating top 4 bytes
172 } else { 221 } else {
173 result.creationTime = view.getUint32(i); 222 result.creationTime = parseMp4Date(view.getUint32(i));
174 i += 4; 223 i += 4;
175 result.modificationTime = view.getUint32(i); 224 result.modificationTime = parseMp4Date(view.getUint32(i));
176 i += 4; 225 i += 4;
177 result.timescale = view.getUint32(i); 226 result.timescale = view.getUint32(i);
178 i += 4; 227 i += 4;
...@@ -230,14 +279,38 @@ var ...@@ -230,14 +279,38 @@ var
230 }; 279 };
231 }, 280 },
232 stco: function(data) { 281 stco: function(data) {
233 return { 282 var
283 view = new DataView(data.buffer, data.byteOffset, data.byteLength),
284 result = {
285 version: data[0],
286 flags: new Uint8Array(data.subarray(1, 4)),
234 chunkOffsets: [] 287 chunkOffsets: []
235 }; 288 },
289 entryCount = view.getUint32(4),
290 i;
291 for (i = 8; entryCount; i += 4, entryCount--) {
292 result.chunkOffsets.push(view.getUint32(i));
293 }
294 return result;
236 }, 295 },
237 stsc: function(data) { 296 stsc: function(data) {
238 return { 297 var
298 view = new DataView(data.buffer, data.byteOffset, data.byteLength),
299 entryCount = view.getUint32(4),
300 result = {
301 version: data[0],
302 flags: new Uint8Array(data.subarray(1, 4)),
239 sampleToChunks: [] 303 sampleToChunks: []
240 }; 304 },
305 i;
306 for (i = 8; entryCount; i += 12, entryCount--) {
307 result.sampleToChunks.push({
308 firstChunk: view.getUint32(i),
309 samplesPerChunk: view.getUint32(i + 4),
310 sampleDescriptionIndex: view.getUint32(i + 8)
311 });
312 }
313 return result;
241 }, 314 },
242 stsd: function(data) { 315 stsd: function(data) {
243 return { 316 return {
...@@ -246,10 +319,39 @@ var ...@@ -246,10 +319,39 @@ var
246 sampleDescriptions: videojs.inspectMp4(data.subarray(8)) 319 sampleDescriptions: videojs.inspectMp4(data.subarray(8))
247 }; 320 };
248 }, 321 },
322 stsz: function(data) {
323 var
324 view = new DataView(data.buffer, data.byteOffset, data.byteLength),
325 result = {
326 version: data[0],
327 flags: new Uint8Array(data.subarray(1, 4)),
328 sampleSize: view.getUint32(4),
329 entries: []
330 },
331 i;
332 for (i = 12; i < data.byteLength; i += 4) {
333 result.entries.push(view.getUint32(i));
334 }
335 return result;
336 },
249 stts: function(data) { 337 stts: function(data) {
250 return { 338 var
339 view = new DataView(data.buffer, data.byteOffset, data.byteLength),
340 result = {
341 version: data[0],
342 flags: new Uint8Array(data.subarray(1, 4)),
251 timeToSamples: [] 343 timeToSamples: []
252 }; 344 },
345 entryCount = view.getUint32(4),
346 i;
347
348 for (i = 8; entryCount; i += 8, entryCount--) {
349 result.timeToSamples.push({
350 sampleCount: view.getUint32(i),
351 sampleDelta: view.getUint32(i + 4)
352 });
353 }
354 return result;
253 }, 355 },
254 tkhd: function(data) { 356 tkhd: function(data) {
255 var 357 var
...@@ -261,18 +363,18 @@ var ...@@ -261,18 +363,18 @@ var
261 }; 363 };
262 if (result.version === 1) { 364 if (result.version === 1) {
263 i += 4; 365 i += 4;
264 result.creationTime = view.getUint32(i); // truncating top 4 bytes 366 result.creationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes
265 i += 8; 367 i += 8;
266 result.modificationTime = view.getUint32(i); // truncating top 4 bytes 368 result.modificationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes
267 i += 4; 369 i += 4;
268 result.trackId = view.getUint32(i); 370 result.trackId = view.getUint32(i);
269 i += 4; 371 i += 4;
270 i += 8; 372 i += 8;
271 result.duration = view.getUint32(i); // truncating top 4 bytes 373 result.duration = view.getUint32(i); // truncating top 4 bytes
272 } else { 374 } else {
273 result.creationTime = view.getUint32(i); 375 result.creationTime = parseMp4Date(view.getUint32(i));
274 i += 4; 376 i += 4;
275 result.modificationTime = view.getUint32(i); 377 result.modificationTime = parseMp4Date(view.getUint32(i));
276 i += 4; 378 i += 4;
277 result.trackId = view.getUint32(i); 379 result.trackId = view.getUint32(i);
278 i += 4; 380 i += 4;
...@@ -291,10 +393,27 @@ var ...@@ -291,10 +393,27 @@ var
291 i += 2; 393 i += 2;
292 result.matrix = new Uint32Array(data.subarray(i, i + (9 * 4))); 394 result.matrix = new Uint32Array(data.subarray(i, i + (9 * 4)));
293 i += 9 * 4; 395 i += 9 * 4;
294 result.width = view.getUint32(i); 396 result.width = view.getUint16(i) + (view.getUint16(i + 2) / 16);
295 i += 4; 397 i += 4;
296 result.height = view.getUint32(i); 398 result.height = view.getUint16(i) + (view.getUint16(i + 2) / 16);
297 return result; 399 return result;
400 },
401 'url ': function(data) {
402 return {
403 version: data[0],
404 flags: new Uint8Array(data.subarray(1, 4))
405 };
406 },
407 vmhd: function(data) {
408 var view = new DataView(data.buffer, data.byteOffset, data.byteLength);
409 return {
410 version: data[0],
411 flags: new Uint8Array(data.subarray(1, 4)),
412 graphicsmode: view.getUint16(4),
413 opcolor: new Uint16Array([view.getUint16(6),
414 view.getUint16(8),
415 view.getUint16(10)])
416 };
298 } 417 }
299 }; 418 };
300 419
......
...@@ -140,32 +140,13 @@ ...@@ -140,32 +140,13 @@
140 140
141 // write out the result 141 // write out the result
142 hex += '<pre>'; 142 hex += '<pre>';
143 // hex += videojs.Hls.utils.hexDump(transmuxer.mp4); 143 hex += videojs.Hls.utils.hexDump(videojs.mp4.initSegment());
144 hex += '</pre>'; 144 hex += '</pre>';
145 vjsOutput.innerHTML = hex; 145 vjsOutput.innerHTML = hex;
146 146
147 // XXX media source testing 147 // XXX media source testing
148 148
149 vjsBoxes.innerHTML = JSON.stringify(videojs.inspectMp4(videojs.mp4.initSegment()), null, ' '); 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);
169 }); 150 });
170 reader.readAsArrayBuffer(this.files[0]); 151 reader.readAsArrayBuffer(this.files[0]);
171 }, false); 152 }, false);
...@@ -184,6 +165,28 @@ ...@@ -184,6 +165,28 @@
184 hex += videojs.Hls.utils.hexDump(bytes); 165 hex += videojs.Hls.utils.hexDump(bytes);
185 hex += '</pre>'; 166 hex += '</pre>';
186 workingOutput.innerHTML = hex; 167 workingOutput.innerHTML = hex;
168
169 // XXX Media Sources Testing
170
171 var video = document.createElement('video');
172 var mediaSource = new MediaSource();
173 mediaSource.addEventListener('sourceopen', function() {
174 var buffer = mediaSource.addSourceBuffer('video/mp4;codecs=avc1.4d0020,mp4a.40.2');
175 buffer.addEventListener('updatestart', console.log.bind(console));
176 buffer.addEventListener('updateend', console.log.bind(console));
177 buffer.addEventListener('error', console.log.bind(console));
178 buffer.appendBuffer(bytes);
179 console.log('done');
180 window.vjsMediaSource = mediaSource;
181 window.vjsSourceBuffer = buffer;
182 window.vjsVideo = video;
183 });
184 mediaSource.addEventListener('error', console.log.bind(console));
185 mediaSource.addEventListener('opened', console.log.bind(console));
186 mediaSource.addEventListener('closed', console.log.bind(console));
187 mediaSource.addEventListener('sourceended', console.log.bind(console));
188 video.src = URL.createObjectURL(mediaSource);
189 video.addEventListener('error', console.log.bind(console));
187 }); 190 });
188 reader.readAsArrayBuffer(this.files[0]); 191 reader.readAsArrayBuffer(this.files[0]);
189 }, false); 192 }, false);
......