ADTS to AAC conversion
Create a stream that unpacks individual AAC frames into events when piped ADTS data from an Elementary Stream.
Showing
2 changed files
with
116 additions
and
6 deletions
... | @@ -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(); | ... | ... |
-
Please register or sign in to post a comment