no commit message
This commit is contained in:
@@ -0,0 +1,246 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
package org.mangui.hls.demux {
|
||||
import org.mangui.hls.model.AudioTrack;
|
||||
import org.mangui.hls.flv.FLVTag;
|
||||
|
||||
import flash.utils.ByteArray;
|
||||
|
||||
CONFIG::LOGGING {
|
||||
import org.mangui.hls.utils.Log;
|
||||
}
|
||||
/** Constants and utilities for the AAC audio format, refer to
|
||||
* http://wiki.multimedia.cx/index.php?title=ADTS
|
||||
**/
|
||||
public class AACDemuxer implements Demuxer {
|
||||
/** ADTS Syncword (0xFFF), ID:0 (MPEG4), layer (00) and protection_absent (1:no CRC).**/
|
||||
private static const SYNCWORD : uint = 0xFFF1;
|
||||
/** ADTS Syncword (0xFFF), ID:1 (MPEG2), layer (00) and protection_absent (1: no CRC).**/
|
||||
private static const SYNCWORD_2 : uint = 0xFFF9;
|
||||
/** ADTS Syncword (0xFFF), ID:1 (MPEG2), layer (00) and protection_absent (0: CRC).**/
|
||||
private static const SYNCWORD_3 : uint = 0xFFF8;
|
||||
/** ADTS/ADIF sample rates index. **/
|
||||
private static const RATES : Array = [96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350];
|
||||
/** ADIF profile index (ADTS doesn't have Null). **/
|
||||
private static const PROFILES : Array = ['Null', 'Main', 'LC', 'SSR', 'LTP', 'SBR'];
|
||||
/** ADTS not found error **/
|
||||
private static const ADTS_NOT_FOUND : String = "AAC:stream did not start with ADTS header";
|
||||
/** Byte data to be read **/
|
||||
private var _data : ByteArray;
|
||||
/* callback functions for audio selection, and parsing progress/complete */
|
||||
private var _callback_audioselect : Function;
|
||||
private var _callback_progress : Function;
|
||||
private var _callback_complete : Function;
|
||||
private var _callback_error : Function;
|
||||
private var _callback_id3tag : Function;
|
||||
|
||||
/** append new data */
|
||||
public function append(data : ByteArray) : void {
|
||||
if (_data == null) {
|
||||
_data = new ByteArray();
|
||||
}
|
||||
_data.writeBytes(data);
|
||||
}
|
||||
|
||||
/** cancel demux operation */
|
||||
public function cancel() : void {
|
||||
_data = null;
|
||||
}
|
||||
|
||||
public function get audioExpected() : Boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
public function get videoExpected() : Boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
public function notifycomplete() : void {
|
||||
CONFIG::LOGGING {
|
||||
Log.debug("AAC: extracting AAC tags");
|
||||
}
|
||||
var audioTags : Vector.<FLVTag> = new Vector.<FLVTag>();
|
||||
/* parse AAC, convert Elementary Streams to TAG */
|
||||
_data.position = 0;
|
||||
var id3 : ID3 = new ID3(_data);
|
||||
// AAC should contain ID3 tag filled with a timestamp
|
||||
var frames : Vector.<AudioFrame> = AACDemuxer.getFrames(_data, _data.position);
|
||||
var adif : ByteArray = getADIF(_data, id3.len);
|
||||
if(adif == null && _callback_error != null) {
|
||||
_callback_error(ADTS_NOT_FOUND);
|
||||
return;
|
||||
}
|
||||
var adifTag : FLVTag = new FLVTag(FLVTag.AAC_HEADER, id3.timestamp, id3.timestamp, true);
|
||||
adifTag.push(adif, 0, adif.length);
|
||||
adifTag.build();
|
||||
audioTags.push(adifTag);
|
||||
|
||||
var audioTag : FLVTag;
|
||||
var stamp : uint;
|
||||
var i : int = 0;
|
||||
|
||||
while (i < frames.length) {
|
||||
stamp = Math.round(id3.timestamp + i * 1024 * 1000 / frames[i].rate);
|
||||
audioTag = new FLVTag(FLVTag.AAC_RAW, stamp, stamp, false);
|
||||
if (i != frames.length - 1) {
|
||||
audioTag.push(_data, frames[i].start, frames[i].length);
|
||||
} else {
|
||||
audioTag.push(_data, frames[i].start, _data.length - frames[i].start);
|
||||
}
|
||||
audioTag.build();
|
||||
audioTags.push(audioTag);
|
||||
i++;
|
||||
}
|
||||
var audiotracks : Vector.<AudioTrack> = new Vector.<AudioTrack>();
|
||||
audiotracks.push(new AudioTrack('AAC ES', AudioTrack.FROM_DEMUX, 0, true, true));
|
||||
// report unique audio track. dont check return value as obviously the track will be selected
|
||||
_callback_audioselect(audiotracks);
|
||||
CONFIG::LOGGING {
|
||||
Log.debug("AAC: all tags extracted, callback demux");
|
||||
}
|
||||
_data = null;
|
||||
if(id3.tags.length) {
|
||||
_callback_id3tag(id3.tags);
|
||||
}
|
||||
_callback_progress(audioTags);
|
||||
_callback_complete();
|
||||
}
|
||||
|
||||
public function AACDemuxer(callback_audioselect : Function,
|
||||
callback_progress : Function,
|
||||
callback_complete : Function,
|
||||
callback_error : Function,
|
||||
callback_id3tag : Function) : void {
|
||||
_callback_audioselect = callback_audioselect;
|
||||
_callback_progress = callback_progress;
|
||||
_callback_complete = callback_complete;
|
||||
_callback_error = callback_error;
|
||||
_callback_id3tag = callback_id3tag;
|
||||
};
|
||||
|
||||
public static function probe(data : ByteArray) : Boolean {
|
||||
var pos : uint = data.position;
|
||||
var id3 : ID3 = new ID3(data);
|
||||
// AAC should contain ID3 tag filled with a timestamp
|
||||
if (id3.hasTimestamp) {
|
||||
var afterID3 : uint = data.position;
|
||||
while (data.bytesAvailable > 1 && (data.position - afterID3) < 100) {
|
||||
// Check for ADTS header
|
||||
var short : uint = data.readUnsignedShort();
|
||||
if (short == SYNCWORD || short == SYNCWORD_2 || short == SYNCWORD_3) {
|
||||
CONFIG::LOGGING {
|
||||
Log.debug2("AAC: found header " + short + "@ " + (data.position-2));
|
||||
}
|
||||
data.position = pos;
|
||||
return true;
|
||||
} else {
|
||||
data.position--;
|
||||
}
|
||||
}
|
||||
data.position = pos;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Get ADIF header from ADTS stream. **/
|
||||
public static function getADIF(adts : ByteArray, position : uint) : ByteArray {
|
||||
adts.position = position;
|
||||
var short : uint;
|
||||
// we need at least 6 bytes, 2 for sync word, 4 for frame length
|
||||
while ((adts.bytesAvailable > 5) && (short != SYNCWORD) && (short != SYNCWORD_2) && (short != SYNCWORD_3)) {
|
||||
short = adts.readUnsignedShort();
|
||||
adts.position--;
|
||||
}
|
||||
adts.position++;
|
||||
if (short == SYNCWORD || short == SYNCWORD_2 || short == SYNCWORD_3) {
|
||||
var profile : uint = (adts.readByte() & 0xF0) >> 6;
|
||||
// Correcting zero-index of ADIF and Flash playing only LC/HE.
|
||||
if (profile > 3) {
|
||||
profile = 5;
|
||||
} else {
|
||||
profile = 2;
|
||||
}
|
||||
adts.position--;
|
||||
var srate : uint = (adts.readByte() & 0x3C) >> 2;
|
||||
adts.position--;
|
||||
var channels : uint = (adts.readShort() & 0x01C0) >> 6;
|
||||
// 5 bits profile + 4 bits samplerate + 4 bits channels.
|
||||
var adif : ByteArray = new ByteArray();
|
||||
adif.writeByte((profile << 3) + (srate >> 1));
|
||||
adif.writeByte((srate << 7) + (channels << 3));
|
||||
CONFIG::LOGGING {
|
||||
Log.debug('AAC: ' + PROFILES[profile] + ', ' + RATES[srate] + ' Hz ' + channels + ' channel(s)');
|
||||
}
|
||||
// Reset position and return adif.
|
||||
adts.position -= 4;
|
||||
adif.position = 0;
|
||||
return adif;
|
||||
} else {
|
||||
CONFIG::LOGGING {
|
||||
Log.error(ADTS_NOT_FOUND);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
/** Get a list with AAC frames from ADTS stream. **/
|
||||
public static function getFrames(adts : ByteArray, position : uint) : Vector.<AudioFrame> {
|
||||
var frames : Vector.<AudioFrame> = new Vector.<AudioFrame>();
|
||||
var frame_start : uint;
|
||||
var frame_length : uint;
|
||||
var id3 : ID3 = new ID3(adts);
|
||||
position += id3.len;
|
||||
// Get raw AAC frames from audio stream.
|
||||
adts.position = position;
|
||||
var samplerate : uint;
|
||||
// we need at least 6 bytes, 2 for sync word, 4 for frame length
|
||||
while (adts.bytesAvailable > 5) {
|
||||
// Check for ADTS header
|
||||
var short : uint = adts.readUnsignedShort();
|
||||
if (short == SYNCWORD || short == SYNCWORD_2 || short == SYNCWORD_3) {
|
||||
// Store samplerate for offsetting timestamps.
|
||||
if (!samplerate) {
|
||||
samplerate = RATES[(adts.readByte() & 0x3C) >> 2];
|
||||
adts.position--;
|
||||
}
|
||||
// Store raw AAC preceding this header.
|
||||
if (frame_start) {
|
||||
frames.push(new AudioFrame(frame_start, frame_length, frame_length, samplerate));
|
||||
}
|
||||
// protection_absent=1, crc_len = 0,protection_absent=0,crc_len=2
|
||||
var crc_len : int = (1 - (short & 0x1)) << 1;
|
||||
// ADTS header is 7+crc_len bytes.
|
||||
frame_length = ((adts.readUnsignedInt() & 0x0003FFE0) >> 5) - 7 - crc_len;
|
||||
frame_start = adts.position + 1 + crc_len;
|
||||
adts.position += frame_length + 1 + crc_len;
|
||||
} else {
|
||||
CONFIG::LOGGING {
|
||||
Log.debug2("no ADTS header found, probing...");
|
||||
}
|
||||
adts.position--;
|
||||
}
|
||||
}
|
||||
if (frame_start) {
|
||||
// check if we have a complete frame available at the end, i.e. last found frame is fitting in this PES packet
|
||||
var overflow : int = frame_start + frame_length - adts.length;
|
||||
if (overflow <= 0 ) {
|
||||
// no overflow, Write raw AAC after last header.
|
||||
frames.push(new AudioFrame(frame_start, frame_length, frame_length, samplerate));
|
||||
}
|
||||
CONFIG::LOGGING {
|
||||
if (overflow > 0) {
|
||||
Log.debug2("ADTS overflow at the end of PES packet, missing " + overflow + " bytes to complete the ADTS frame");
|
||||
}
|
||||
}
|
||||
}
|
||||
CONFIG::LOGGING {
|
||||
if (!frame_start && frames.length == 0) {
|
||||
Log.warn("No ADTS headers found in this stream.");
|
||||
}
|
||||
}
|
||||
adts.position = position;
|
||||
return frames;
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user