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() {
pes: function() {
var stream, streamType;
if (data.streamType === H264_STREAM_TYPE) {
switch (data.streamType) {
case H264_STREAM_TYPE:
stream = video;
streamType = 'video';
} else {
break;
case ADTS_STREAM_TYPE:
stream = audio;
streamType = 'audio';
break;
default:
// ignore unknown stream types
return;
}
// if a new packet is starting, we can flush the completed
......@@ -390,16 +396,61 @@ ElementaryStream.prototype = new videojs.Hls.Stream();
/*
* Accepts a ElementaryStream and emits data events with parsed
* AAC Audio Frames of the individual packets.
* AAC Audio Frames of the individual packets. Input audio in ADTS
* format is unpacked and re-emitted as AAC frames.
*
* @see http://wiki.multimedia.cx/index.php?title=ADTS
* @see http://wiki.multimedia.cx/?title=Understanding_AAC
*/
AacStream = function() {
var self;
var i = 1, self, buffer;
AacStream.prototype.init.call(this);
self = this;
this.push = function(packet) {
if (packet.type === 'audio') {
this.trigger('data', packet);
var frameLength;
if (packet.type !== 'audio') {
// ignore non-audio data
return;
}
buffer = packet.data;
// find ADTS frame sync sequences
// ADTS frames start with 12 byte-aligned ones
while (i < buffer.length) {
switch (buffer[i] & 0xf0) {
case 0xf0:
// skip past non-sync sequences
if (buffer[i - 1] !== 0xff) {
i++;
break;
}
// parse the ADTS header
// frame length is a 13 bit integer starting 16 bits from the
// end of the sync sequence
frameLength = ((buffer[i + 2] & 0x03) << 11) |
(buffer[i + 3] << 3) |
((buffer[i + 4] & 0xe0) >> 5);
// deliver the AAC frame
this.trigger('data', {
data: buffer.subarray(i + 6, i + frameLength - 1)
});
// move to one byte beyond the frame length to continue
// searching for sync sequences
i += frameLength;
break;
default:
// the top four bytes are not all ones so the end of the
// closest possible start sequence is at least two bytes ahead
i += 2;
break;
}
}
};
};
......@@ -437,6 +488,8 @@ NalByteStream = function() {
// 0 0 1 .. NAL .. 0 0 0
// ^ sync point ^ i
while (i < buffer.byteLength) {
// look at the current byte to determine if we've hit the end of
// a NAL unit boundary
switch (buffer[i]) {
case 0:
// skip past non-sync sequences
......@@ -472,6 +525,8 @@ NalByteStream = function() {
i += 3;
break;
default:
// the current byte isn't a one or zero, so it cannot be part
// of a sync sequence
i += 3;
break;
}
......
......@@ -31,6 +31,8 @@ var
h264Stream,
VideoSegmentStream = videojs.mp2t.VideoSegmentStream,
videoSegmentStream,
AacStream = videojs.mp2t.AacStream,
aacStream,
Transmuxer = videojs.mp2t.Transmuxer,
transmuxer,
......@@ -524,6 +526,10 @@ test('parses standalone program stream packets', function() {
});
elementaryStream.push({
type: 'pes',
streamType: ADTS_STREAM_TYPE,
payloadUnitStartIndicator: true,
pts: 7,
dts: 8,
data: new Uint8Array(19)
});
elementaryStream.end();
......@@ -640,6 +646,25 @@ test('flushes the buffered packets when a new one of that type is started', func
equal(7, packets[2].data.byteLength, 'parsed the audio payload');
});
test('drops packets with unknown stream types', function() {
var packets = [];
elementaryStream.on('data', function(packet) {
packets.push(packet);
});
elementaryStream.push({
type: 'pes',
payloadUnitStartIndicator: true,
data: new Uint8Array(1)
});
elementaryStream.push({
type: 'pes',
payloadUnitStartIndicator: true,
data: new Uint8Array(1)
});
equal(packets.length, 0, 'ignored unknown packets');
});
module('H264 Stream', {
setup: function() {
h264Stream = new H264Stream();
......@@ -911,6 +936,36 @@ test('scales DTS values from milliseconds to 90kHz', function() {
equal(samples[2].duration, 2 * 90, 'inferred the final sample duration');
});
module('AAC Stream', {
setup: function() {
aacStream = new AacStream();
}
});
test('generates AAC frame events from ADTS bytes', function() {
var frames = [];
aacStream.on('data', function(frame) {
frames.push(frame);
});
aacStream.push({
type: 'audio',
data: new Uint8Array([
0xff, 0xf1, // no CRC
0x00, // AAC Main, 44.1KHz
0xfc, 0x01, 0x20, // frame length 9 bytes
0x00, // one AAC per ADTS frame
0x12, 0x34, // AAC payload
0x56, 0x78 // extra junk that should be ignored
])
});
equal(frames.length, 1, 'generated one frame');
deepEqual(frames[0].data, new Uint8Array([0x12, 0x34]), 'extracted AAC frame');
});
// not handled: ADTS with CRC
// ADTS with payload broken across push events
module('Transmuxer', {
setup: function() {
transmuxer = new Transmuxer();
......