967f4f7e by David LaPalomento

ADTS to AAC conversion

Create a stream that unpacks individual AAC frames into events when piped ADTS data from an Elementary Stream.
1 parent 5a79bc48
...@@ -323,12 +323,18 @@ ElementaryStream = function() { ...@@ -323,12 +323,18 @@ ElementaryStream = function() {
323 pes: function() { 323 pes: function() {
324 var stream, streamType; 324 var stream, streamType;
325 325
326 if (data.streamType === H264_STREAM_TYPE) { 326 switch (data.streamType) {
327 case H264_STREAM_TYPE:
327 stream = video; 328 stream = video;
328 streamType = 'video'; 329 streamType = 'video';
329 } else { 330 break;
331 case ADTS_STREAM_TYPE:
330 stream = audio; 332 stream = audio;
331 streamType = 'audio'; 333 streamType = 'audio';
334 break;
335 default:
336 // ignore unknown stream types
337 return;
332 } 338 }
333 339
334 // if a new packet is starting, we can flush the completed 340 // if a new packet is starting, we can flush the completed
...@@ -390,16 +396,61 @@ ElementaryStream.prototype = new videojs.Hls.Stream(); ...@@ -390,16 +396,61 @@ ElementaryStream.prototype = new videojs.Hls.Stream();
390 396
391 /* 397 /*
392 * Accepts a ElementaryStream and emits data events with parsed 398 * Accepts a ElementaryStream and emits data events with parsed
393 * AAC Audio Frames of the individual packets. 399 * AAC Audio Frames of the individual packets. Input audio in ADTS
400 * format is unpacked and re-emitted as AAC frames.
401 *
402 * @see http://wiki.multimedia.cx/index.php?title=ADTS
403 * @see http://wiki.multimedia.cx/?title=Understanding_AAC
394 */ 404 */
395 AacStream = function() { 405 AacStream = function() {
396 var self; 406 var i = 1, self, buffer;
397 AacStream.prototype.init.call(this); 407 AacStream.prototype.init.call(this);
398 self = this; 408 self = this;
399 409
400 this.push = function(packet) { 410 this.push = function(packet) {
401 if (packet.type === 'audio') { 411 var frameLength;
402 this.trigger('data', packet); 412
413 if (packet.type !== 'audio') {
414 // ignore non-audio data
415 return;
416 }
417
418 buffer = packet.data;
419
420 // find ADTS frame sync sequences
421 // ADTS frames start with 12 byte-aligned ones
422 while (i < buffer.length) {
423 switch (buffer[i] & 0xf0) {
424 case 0xf0:
425 // skip past non-sync sequences
426 if (buffer[i - 1] !== 0xff) {
427 i++;
428 break;
429 }
430
431 // parse the ADTS header
432
433 // frame length is a 13 bit integer starting 16 bits from the
434 // end of the sync sequence
435 frameLength = ((buffer[i + 2] & 0x03) << 11) |
436 (buffer[i + 3] << 3) |
437 ((buffer[i + 4] & 0xe0) >> 5);
438
439 // deliver the AAC frame
440 this.trigger('data', {
441 data: buffer.subarray(i + 6, i + frameLength - 1)
442 });
443
444 // move to one byte beyond the frame length to continue
445 // searching for sync sequences
446 i += frameLength;
447 break;
448 default:
449 // the top four bytes are not all ones so the end of the
450 // closest possible start sequence is at least two bytes ahead
451 i += 2;
452 break;
453 }
403 } 454 }
404 }; 455 };
405 }; 456 };
...@@ -437,6 +488,8 @@ NalByteStream = function() { ...@@ -437,6 +488,8 @@ NalByteStream = function() {
437 // 0 0 1 .. NAL .. 0 0 0 488 // 0 0 1 .. NAL .. 0 0 0
438 // ^ sync point ^ i 489 // ^ sync point ^ i
439 while (i < buffer.byteLength) { 490 while (i < buffer.byteLength) {
491 // look at the current byte to determine if we've hit the end of
492 // a NAL unit boundary
440 switch (buffer[i]) { 493 switch (buffer[i]) {
441 case 0: 494 case 0:
442 // skip past non-sync sequences 495 // skip past non-sync sequences
...@@ -472,6 +525,8 @@ NalByteStream = function() { ...@@ -472,6 +525,8 @@ NalByteStream = function() {
472 i += 3; 525 i += 3;
473 break; 526 break;
474 default: 527 default:
528 // the current byte isn't a one or zero, so it cannot be part
529 // of a sync sequence
475 i += 3; 530 i += 3;
476 break; 531 break;
477 } 532 }
......
...@@ -31,6 +31,8 @@ var ...@@ -31,6 +31,8 @@ var
31 h264Stream, 31 h264Stream,
32 VideoSegmentStream = videojs.mp2t.VideoSegmentStream, 32 VideoSegmentStream = videojs.mp2t.VideoSegmentStream,
33 videoSegmentStream, 33 videoSegmentStream,
34 AacStream = videojs.mp2t.AacStream,
35 aacStream,
34 Transmuxer = videojs.mp2t.Transmuxer, 36 Transmuxer = videojs.mp2t.Transmuxer,
35 transmuxer, 37 transmuxer,
36 38
...@@ -524,6 +526,10 @@ test('parses standalone program stream packets', function() { ...@@ -524,6 +526,10 @@ test('parses standalone program stream packets', function() {
524 }); 526 });
525 elementaryStream.push({ 527 elementaryStream.push({
526 type: 'pes', 528 type: 'pes',
529 streamType: ADTS_STREAM_TYPE,
530 payloadUnitStartIndicator: true,
531 pts: 7,
532 dts: 8,
527 data: new Uint8Array(19) 533 data: new Uint8Array(19)
528 }); 534 });
529 elementaryStream.end(); 535 elementaryStream.end();
...@@ -640,6 +646,25 @@ test('flushes the buffered packets when a new one of that type is started', func ...@@ -640,6 +646,25 @@ test('flushes the buffered packets when a new one of that type is started', func
640 equal(7, packets[2].data.byteLength, 'parsed the audio payload'); 646 equal(7, packets[2].data.byteLength, 'parsed the audio payload');
641 }); 647 });
642 648
649 test('drops packets with unknown stream types', function() {
650 var packets = [];
651 elementaryStream.on('data', function(packet) {
652 packets.push(packet);
653 });
654 elementaryStream.push({
655 type: 'pes',
656 payloadUnitStartIndicator: true,
657 data: new Uint8Array(1)
658 });
659 elementaryStream.push({
660 type: 'pes',
661 payloadUnitStartIndicator: true,
662 data: new Uint8Array(1)
663 });
664
665 equal(packets.length, 0, 'ignored unknown packets');
666 });
667
643 module('H264 Stream', { 668 module('H264 Stream', {
644 setup: function() { 669 setup: function() {
645 h264Stream = new H264Stream(); 670 h264Stream = new H264Stream();
...@@ -911,6 +936,36 @@ test('scales DTS values from milliseconds to 90kHz', function() { ...@@ -911,6 +936,36 @@ test('scales DTS values from milliseconds to 90kHz', function() {
911 equal(samples[2].duration, 2 * 90, 'inferred the final sample duration'); 936 equal(samples[2].duration, 2 * 90, 'inferred the final sample duration');
912 }); 937 });
913 938
939 module('AAC Stream', {
940 setup: function() {
941 aacStream = new AacStream();
942 }
943 });
944
945 test('generates AAC frame events from ADTS bytes', function() {
946 var frames = [];
947 aacStream.on('data', function(frame) {
948 frames.push(frame);
949 });
950 aacStream.push({
951 type: 'audio',
952 data: new Uint8Array([
953 0xff, 0xf1, // no CRC
954 0x00, // AAC Main, 44.1KHz
955 0xfc, 0x01, 0x20, // frame length 9 bytes
956 0x00, // one AAC per ADTS frame
957 0x12, 0x34, // AAC payload
958 0x56, 0x78 // extra junk that should be ignored
959 ])
960 });
961
962 equal(frames.length, 1, 'generated one frame');
963 deepEqual(frames[0].data, new Uint8Array([0x12, 0x34]), 'extracted AAC frame');
964 });
965
966 // not handled: ADTS with CRC
967 // ADTS with payload broken across push events
968
914 module('Transmuxer', { 969 module('Transmuxer', {
915 setup: function() { 970 setup: function() {
916 transmuxer = new Transmuxer(); 971 transmuxer = new Transmuxer();
......