Fix ADTS parsing over multiple events
Simplify ADTS parsing algorith to assume it is invoked at the start of a sync sequence. Track progress through the ADTS data accrately so that subsequent events carrying frames after the first are parsed correclty.
Showing
2 changed files
with
57 additions
and
41 deletions
... | @@ -38,7 +38,7 @@ ADTS_SAMPLING_FREQUENCIES = [ | ... | @@ -38,7 +38,7 @@ ADTS_SAMPLING_FREQUENCIES = [ |
38 | 8000, | 38 | 8000, |
39 | 7350 | 39 | 7350 |
40 | ]; | 40 | ]; |
41 | 41 | ||
42 | mp4 = videojs.mp4; | 42 | mp4 = videojs.mp4; |
43 | 43 | ||
44 | /** | 44 | /** |
... | @@ -434,45 +434,27 @@ AacStream = function() { | ... | @@ -434,45 +434,27 @@ AacStream = function() { |
434 | 434 | ||
435 | buffer = packet.data; | 435 | buffer = packet.data; |
436 | 436 | ||
437 | // find ADTS frame sync sequences | 437 | // unpack any ADTS frames which have been fully received |
438 | // ADTS frames start with 12 byte-aligned ones | 438 | while (i + 4 < buffer.length) { |
439 | while (i < buffer.length) { | 439 | // frame length is a 13 bit integer starting 16 bits from the |
440 | switch (buffer[i] & 0xf0) { | 440 | // end of the sync sequence |
441 | case 0xf0: | 441 | frameLength = ((buffer[i + 2] & 0x03) << 11) | |
442 | // skip past non-sync sequences | 442 | (buffer[i + 3] << 3) | |
443 | if (buffer[i - 1] !== 0xff) { | 443 | ((buffer[i + 4] & 0xe0) >> 5); |
444 | i++; | 444 | |
445 | break; | 445 | // deliver the AAC frame |
446 | } | 446 | this.trigger('data', { |
447 | 447 | channelcount: ((buffer[i + 1] & 1) << 3) | | |
448 | // parse the ADTS header | 448 | ((buffer[i + 2] & 0xc0) >> 6), |
449 | 449 | samplerate: ADTS_SAMPLING_FREQUENCIES[(buffer[i + 1] & 0x3c) >> 2], | |
450 | // frame length is a 13 bit integer starting 16 bits from the | 450 | // assume ISO/IEC 14496-12 AudioSampleEntry default of 16 |
451 | // end of the sync sequence | 451 | samplesize: 16, |
452 | frameLength = ((buffer[i + 2] & 0x03) << 11) | | 452 | data: buffer.subarray(i + 6, i + frameLength - 1) |
453 | (buffer[i + 3] << 3) | | 453 | }); |
454 | ((buffer[i + 4] & 0xe0) >> 5); | ||
455 | |||
456 | // deliver the AAC frame | ||
457 | this.trigger('data', { | ||
458 | channelcount: ((buffer[i + 1] & 1) << 3) | | ||
459 | ((buffer[i + 2] & 0xc0) >> 6), | ||
460 | samplerate: ADTS_SAMPLING_FREQUENCIES[(buffer[i + 1] & 0x3c) >> 2], | ||
461 | // assume ISO/IEC 14496-12 AudioSampleEntry default of 16 | ||
462 | samplesize: 16, | ||
463 | data: buffer.subarray(i + 6, i + frameLength - 1) | ||
464 | }); | ||
465 | 454 | ||
466 | // move to one byte beyond the frame length to continue | 455 | // flush the finished frame and try again |
467 | // searching for sync sequences | 456 | buffer = buffer.subarray(i + frameLength - 1); |
468 | i += frameLength; | 457 | i = 1; |
469 | break; | ||
470 | default: | ||
471 | // the top four bytes are not all ones so the end of the | ||
472 | // closest possible start sequence is at least two bytes ahead | ||
473 | i += 2; | ||
474 | break; | ||
475 | } | ||
476 | } | 458 | } |
477 | }; | 459 | }; |
478 | }; | 460 | }; |
... | @@ -514,7 +496,7 @@ AudioSegmentStream = function(track) { | ... | @@ -514,7 +496,7 @@ AudioSegmentStream = function(track) { |
514 | 496 | ||
515 | data.set(currentFrame.data, i); | 497 | data.set(currentFrame.data, i); |
516 | i += currentFrame.data.byteLength; | 498 | i += currentFrame.data.byteLength; |
517 | 499 | ||
518 | aacFrames.shift(); | 500 | aacFrames.shift(); |
519 | } | 501 | } |
520 | aacFramesLength = 0; | 502 | aacFramesLength = 0; |
... | @@ -944,7 +926,7 @@ Transmuxer = function() { | ... | @@ -944,7 +926,7 @@ Transmuxer = function() { |
944 | pps, | 926 | pps, |
945 | 927 | ||
946 | packetStream, parseStream, elementaryStream, | 928 | packetStream, parseStream, elementaryStream, |
947 | aacStream, h264Stream, | 929 | aacStream, h264Stream, |
948 | videoSegmentStream, audioSegmentStream; | 930 | videoSegmentStream, audioSegmentStream; |
949 | 931 | ||
950 | Transmuxer.prototype.init.call(this); | 932 | Transmuxer.prototype.init.call(this); | ... | ... |
... | @@ -1007,6 +1007,40 @@ test('generates AAC frame events from ADTS bytes', function() { | ... | @@ -1007,6 +1007,40 @@ test('generates AAC frame events from ADTS bytes', function() { |
1007 | equal(frames[0].samplesize, 16, 'parsed samplesize'); | 1007 | equal(frames[0].samplesize, 16, 'parsed samplesize'); |
1008 | }); | 1008 | }); |
1009 | 1009 | ||
1010 | test('parses across packets', function() { | ||
1011 | |||
1012 | var frames = []; | ||
1013 | aacStream.on('data', function(frame) { | ||
1014 | frames.push(frame); | ||
1015 | }); | ||
1016 | aacStream.push({ | ||
1017 | type: 'audio', | ||
1018 | data: new Uint8Array([ | ||
1019 | 0xff, 0xf1, // no CRC | ||
1020 | 0x10, // AAC Main, 44.1KHz | ||
1021 | 0xbc, 0x01, 0x20, // 2 channels, frame length 9 bytes | ||
1022 | 0x00, // one AAC per ADTS frame | ||
1023 | 0x12, 0x34 // AAC payload 1 | ||
1024 | ]) | ||
1025 | }); | ||
1026 | aacStream.push({ | ||
1027 | type: 'audio', | ||
1028 | data: new Uint8Array([ | ||
1029 | 0xff, 0xf1, // no CRC | ||
1030 | 0x10, // AAC Main, 44.1KHz | ||
1031 | 0xbc, 0x01, 0x20, // 2 channels, frame length 9 bytes | ||
1032 | 0x00, // one AAC per ADTS frame | ||
1033 | 0x9a, 0xbc, // AAC payload 2 | ||
1034 | 0xde, 0xf0 // extra junk that should be ignored | ||
1035 | ]) | ||
1036 | }); | ||
1037 | |||
1038 | equal(frames.length, 2, 'parsed two frames'); | ||
1039 | deepEqual(frames[1].data, | ||
1040 | new Uint8Array([0x9a, 0xbc]), | ||
1041 | 'extracted the second AAC frame'); | ||
1042 | }); | ||
1043 | |||
1010 | // not handled: ADTS with CRC | 1044 | // not handled: ADTS with CRC |
1011 | // ADTS with payload broken across push events | 1045 | // ADTS with payload broken across push events |
1012 | 1046 | ... | ... |
-
Please register or sign in to post a comment