no commit message

This commit is contained in:
niandeng
2020-09-17 18:40:01 +08:00
parent 4382915d09
commit d957abceaa
283 changed files with 86935 additions and 63 deletions
+246
View File
@@ -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;
};
}
}
+54
View File
@@ -0,0 +1,54 @@
/* 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 flash.utils.ByteArray;
CONFIG::LOGGING {
import org.mangui.hls.utils.Log;
import org.mangui.hls.HLSSettings;
}
public class AVCC {
/** H264 profiles. **/
private static const PROFILES : Object = {'66':'H264 Baseline', '77':'H264 Main', '100':'H264 High'};
/** Get Avcc header from AVC stream
See ISO 14496-15, 5.2.4.1 for the description of AVCDecoderConfigurationRecord
**/
public static function getAVCC(sps : ByteArray, ppsVect : Vector.<ByteArray>) : ByteArray {
// Write startbyte
var avcc : ByteArray = new ByteArray();
avcc.writeByte(0x01);
// Write profile, compatibility and level.
avcc.writeBytes(sps, 1, 3);
// reserved (6 bits), NALU length size - 1 (2 bits)
avcc.writeByte(0xFC | 3);
// reserved (3 bits), num of SPS (5 bits)
avcc.writeByte(0xE0 | 1);
// 2 bytes for length of SPS
avcc.writeShort(sps.length);
// data of SPS
avcc.writeBytes(sps, 0, sps.length);
// Number of PPS
avcc.writeByte(ppsVect.length);
for each (var pps : ByteArray in ppsVect) {
// 2 bytes for length of PPS
avcc.writeShort(pps.length);
// data of PPS
avcc.writeBytes(pps, 0, pps.length);
}
CONFIG::LOGGING {
if (HLSSettings.logDebug2) {
// Grab profile/level
sps.position = 1;
var prf : int = sps.readByte();
sps.position = 3;
var lvl : int = sps.readByte();
Log.debug("AVC: " + PROFILES[prf] + ' level ' + lvl);
}
}
avcc.position = 0;
return avcc;
}
;
}
}
+19
View File
@@ -0,0 +1,19 @@
/* 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 {
/** Audio Frame **/
public class AudioFrame {
public var start : int;
public var length : int;
public var expected_length : int;
public var rate : int;
public function AudioFrame(start : int, length : int, expected_length : int, rate : int) {
this.start = start;
this.length = length;
this.expected_length = expected_length;
this.rate = rate;
}
}
}
@@ -0,0 +1,66 @@
/* 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 flash.utils.ByteArray;
import org.mangui.hls.model.Level;
CONFIG::LOGGING {
import org.mangui.hls.utils.Log;
}
public class DemuxHelper {
public static function probe(data : ByteArray,
level : Level,
audioselect : Function,
progress : Function,
complete : Function,
error : Function,
videometadata : Function,
id3tagfound : Function,
audioOnly : Boolean) : Demuxer {
data.position = 0;
CONFIG::LOGGING {
Log.debug("probe fragment type");
}
var aac_match : Boolean = AACDemuxer.probe(data);
var mp3_match : Boolean = MP3Demuxer.probe(data);
var ts_match : Boolean = TSDemuxer.probe(data);
CONFIG::LOGGING {
Log.debug("AAC/MP3/TS match:" + aac_match + "/" + mp3_match + "/" + ts_match);
}
/* prioritize level info :
* if ts_match && codec_avc => TS demuxer
* if aac_match && codec_aac => AAC demuxer
* if mp3_match && codec_mp3 => MP3 demuxer
* if no codec info in Manifest, use fallback order : AAC/MP3/TS
*/
if (ts_match && level.codec_h264) {
CONFIG::LOGGING {
Log.debug("TS match + H264 signaled in Manifest, use TS demuxer");
}
return new TSDemuxer(audioselect, progress, complete, error, videometadata, audioOnly);
} else if (aac_match && level.codec_aac) {
CONFIG::LOGGING {
Log.debug("AAC match + AAC signaled in Manifest, use AAC demuxer");
}
return new AACDemuxer(audioselect, progress, complete, error, id3tagfound);
} else if (mp3_match && level.codec_mp3) {
CONFIG::LOGGING {
Log.debug("MP3 match + MP3 signaled in Manifest, use MP3 demuxer");
}
return new MP3Demuxer(audioselect, progress, complete, error, id3tagfound);
} else if (aac_match) {
return new AACDemuxer(audioselect, progress, complete, error, id3tagfound);
} else if (mp3_match) {
return new MP3Demuxer(audioselect, progress, complete, error, id3tagfound);
} else if (ts_match) {
return new TSDemuxer(audioselect, progress, complete, error, videometadata, audioOnly);
} else {
CONFIG::LOGGING {
Log.debug("probe fails");
}
return null;
}
}
}
}
+18
View File
@@ -0,0 +1,18 @@
/* 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 flash.utils.ByteArray;
public interface Demuxer {
function append(data : ByteArray) : void;
function notifycomplete() : void;
function cancel() : void;
function get audioExpected() : Boolean;
function get videoExpected() : Boolean;
}
}
+59
View File
@@ -0,0 +1,59 @@
/* 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 flash.utils.ByteArray;
public class ExpGolomb {
private var _data : ByteArray;
private var _bit : int;
private var _curByte : uint;
public function ExpGolomb(data : ByteArray) {
_data = data;
_bit = -1;
}
private function _readBit() : uint {
var res : uint;
if (_bit == -1) {
// read next
_curByte = _data.readByte();
_bit = 7;
}
res = _curByte & (1 << _bit) ? 1 : 0;
_bit--;
return res;
}
public function readBoolean() : Boolean {
return (_readBit() == 1);
}
public function readBits(nbBits : uint) : int {
var val : int = 0;
for (var i : uint = 0; i < nbBits; ++i)
val = (val << 1) + _readBit();
return val;
}
public function readUE() : uint {
var nbZero : uint = 0;
while (_readBit() == 0)
++nbZero;
var x : uint = readBits(nbZero);
return x + (1 << nbZero) - 1;
}
public function readSE() : uint {
var value : int = readUE();
// the number is odd if the low order bit is set
if (0x01 & value) {
// add 1 to make it even, and divide by 2
return (1 + value) >> 1;
} else {
// divide by two then make it negative
return -1 * (value >> 1);
}
}
}
}
+144
View File
@@ -0,0 +1,144 @@
/* 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 by.blooddy.crypto.Base64;
import com.hurlant.util.Base64;
import flash.utils.ByteArray;
CONFIG::LOGGING {
import org.mangui.hls.utils.Log;
}
public class ID3 {
public var len : int;
public var hasTimestamp : Boolean = false;
public var timestamp : Number;
public var tags : Vector.<ID3Tag> = new Vector.<ID3Tag>();
/* create ID3 object by parsing ByteArray, looking for ID3 tag length, and timestamp */
public function ID3(data : ByteArray) {
var tagSize : uint = 0;
try {
var pos : uint = data.position;
var header : String;
do {
header = data.readUTFBytes(3);
if (header == 'ID3') {
// skip 24 bits
data.position += 3;
// retrieve tag(s) length
var byte1 : uint = data.readUnsignedByte() & 0x7f;
var byte2 : uint = data.readUnsignedByte() & 0x7f;
var byte3 : uint = data.readUnsignedByte() & 0x7f;
var byte4 : uint = data.readUnsignedByte() & 0x7f;
tagSize = (byte1 << 21) + (byte2 << 14) + (byte3 << 7) + byte4;
var endPos : uint = data.position + tagSize;
CONFIG::LOGGING {
Log.debug2("ID3 tag found, size/end pos:" + tagSize + "/" + endPos);
}
// read ID3 tags
_parseID3Frames(data, endPos);
data.position = endPos;
} else if (header == '3DI') {
// http://id3.org/id3v2.4.0-structure chapter 3.4. ID3v2 footer
data.position += 7;
CONFIG::LOGGING {
Log.debug2("3DI footer found, end pos:" + data.position);
}
} else {
data.position -= 3;
len = data.position - pos;
CONFIG::LOGGING {
if (len) {
Log.debug2("ID3 len:" + len);
if (!hasTimestamp) {
Log.warn("ID3 tag found, but no timestamp");
}
}
}
return;
}
} while (true);
} catch(e : Error) {
}
len = 0;
return;
};
/* Each Elementary Audio Stream segment MUST signal the timestamp of
its first sample with an ID3 PRIV tag [ID3] at the beginning of
the segment. The ID3 PRIV owner identifier MUST be
"com.apple.streaming.transportStreamTimestamp". The ID3 payload
MUST be a 33-bit MPEG-2 Program Elementary Stream timestamp
expressed as a big-endian eight-octet number, with the upper 31
bits set to zero.
*/
private function _parseID3Frames(data : ByteArray, endPos : uint) : void {
while(data.position + 8 <= endPos) {
var tag_id : String = data.readUTFBytes(4);
var tag_len : int = data.readUnsignedInt();
var tag_flags : int = data.readUnsignedShort();
CONFIG::LOGGING {
Log.debug("ID3 tag id:" + tag_id);
}
switch(tag_id) {
case "PRIV":
// owner should be "com.apple.streaming.transportStreamTimestamp"
if (data.readUTFBytes(44) == 'com.apple.streaming.transportStreamTimestamp') {
// smelling even better ! we found the right descriptor
// skip null character (string end) + 3 first bytes
data.position += 4;
// timestamp is 33 bit expressed as a big-endian eight-octet number, with the upper 31 bits set to zero.
var pts_33_bit : int = data.readUnsignedByte() & 0x1;
hasTimestamp = true;
timestamp = (data.readUnsignedInt() / 90);
if (pts_33_bit) {
timestamp += 47721858.84; // 2^32 / 90
}
timestamp = Math.round(timestamp);
CONFIG::LOGGING {
Log.debug("ID3 timestamp found:" + timestamp);
}
}
break;
default:
var tag_data : * = null;
var base64 : Boolean;
var firstChar : String = tag_id.charAt(0);
if(firstChar == 'T' || firstChar == 'W') {
base64 = false;
var cur_tag_pos : int;
var end_tag_pos : int = data.position + tag_len;
// skip character encoding byte
data.position++;
while(data.position < end_tag_pos - 1) {
cur_tag_pos = data.position;
var text : String = data.readUTFBytes(end_tag_pos-cur_tag_pos);
if(tag_data) {
tag_data += ',' + text;
} else {
tag_data = text;
}
data.position = cur_tag_pos + text.length + 1;
}
data.position = end_tag_pos;
CONFIG::LOGGING {
Log.debug2("id/data:" + tag_id + "/" + tag_data);
}
} else {
base64 = true;
tag_data = new ByteArray();
data.readBytes(tag_data, 0, tag_len);
tag_data=Base64.encode(tag_data);
}
var id3tag : ID3Tag = new ID3Tag(tag_id,tag_flags,base64,tag_data);
tags.push(id3tag);
break;
}
}
}
}
}
+23
View File
@@ -0,0 +1,23 @@
/* 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 {
/** Video Frame **/
public class ID3Tag {
public var id : String;
public var flag : int;
public var base64 : Boolean;
public var data : String;
public function ID3Tag(id : String, flag : int, base64: Boolean, data : String) {
this.id = id;
this.flag = flag;
this.base64 = base64;
this.data = data;
}
public function toString(): String {
return "id/flag/base64/data:" + id + '/' + flag + '/' + base64 + '/' + data;
}
}
}
+159
View File
@@ -0,0 +1,159 @@
/* 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 flash.utils.ByteArray;
import org.mangui.hls.model.AudioTrack;
import org.mangui.hls.flv.FLVTag;
CONFIG::LOGGING {
import org.mangui.hls.utils.Log;
}
public class MP3Demuxer implements Demuxer {
/* MPEG1-Layer3 syncword */
private static const SYNCWORD : uint = 0xFFFB;
private static const RATES : Array = [44100, 48000, 32000];
private static const BIT_RATES : Array = [0, 32000, 40000, 48000, 56000, 64000, 80000, 96000, 112000, 128000, 160000, 192000, 224000, 256000, 320000, 0];
private static const SAMPLES_PER_FRAME : uint = 1152;
/** 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("MP3: extracting MP3 tags");
}
var audioTags : Vector.<FLVTag> = new Vector.<FLVTag>();
/* parse MP3, convert Elementary Streams to TAG */
_data.position = 0;
var id3 : ID3 = new ID3(_data);
// MP3 should contain ID3 tag filled with a timestamp
var frames : Vector.<AudioFrame> = getFrames(_data, _data.position);
var audioTag : FLVTag;
var stamp : int;
var i : int = 0;
while (i < frames.length) {
stamp = Math.round(id3.timestamp + i * 1024 * 1000 / frames[i].rate);
audioTag = new FLVTag(FLVTag.MP3_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('MP3 ES', AudioTrack.FROM_DEMUX, 0, true,false));
// report unique audio track. dont check return value as obviously the track will be selected
_callback_audioselect(audiotracks);
CONFIG::LOGGING {
Log.debug("MP3: all tags extracted, callback demux");
}
_data = null;
if(id3.tags.length) {
_callback_id3tag(id3.tags);
}
_callback_progress(audioTags);
_callback_complete();
}
public function MP3Demuxer(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);
// MP3 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 MP3 header
var short : uint = data.readUnsignedShort();
if (short == SYNCWORD) {
CONFIG::LOGGING {
Log.debug2("MP3: found header " + short + "@ " + (data.position-2));
}
data.position = pos;
return true;
} else {
data.position--;
}
}
data.position = pos;
}
return false;
}
private static function getFrames(data : 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(data);
position += id3.len;
// Get raw MP3 frames from audio stream.
data.position = position;
// we need at least 3 bytes, 2 for sync word, 1 for flags
while (data.bytesAvailable > 3) {
frame_start = data.position;
// frame header described here : http://mpgedit.org/mpgedit/mpeg_format/MP3Format.html
var short : uint = data.readUnsignedShort();
if (short == SYNCWORD) {
var flag : uint = data.readByte();
// (15,12)=(&0xf0 >>4) Bitrate index
var bitrate : uint = BIT_RATES[(flag & 0xf0) >> 4];
// (11,10)=(&0xc >> 2) Sampling rate frequency index (values are in Hz)
var samplerate : uint = RATES[(flag & 0xc) >> 2];
// (9)=(&2 >>1) Padding bit
var padbit : uint = (flag & 2) >> 1;
frame_length = (SAMPLES_PER_FRAME / 8) * bitrate / samplerate + padbit;
frame_length = Math.round(frame_length);
data.position = data.position + (frame_length - 3);
frames.push(new AudioFrame(frame_start, frame_length, frame_length, samplerate));
} else {
data.position = data.position - 1;
}
}
data.position = position;
return frames;
}
}
}
+172
View File
@@ -0,0 +1,172 @@
/* 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 flash.utils.ByteArray;
CONFIG::LOGGING {
import org.mangui.hls.HLSSettings;
import org.mangui.hls.utils.Log;
}
/** Constants and utilities for the H264 video format. **/
public class Nalu {
private static var _audNalu : ByteArray;
// static initializer
{
_audNalu = new ByteArray();
_audNalu.length = 2;
_audNalu.writeByte(0x09);
_audNalu.writeByte(0xF0);
};
/** Return an array with NAL delimiter indexes. **/
public static function getNALU(nalu : ByteArray, position : uint) : Vector.<VideoFrame> {
var len : uint = nalu.length,i : uint = position;
var unitHeader : int,lastUnitHeader : int = 0;
var unitStart : int,lastUnitStart : int = 0;
var unitType : int,lastUnitType : int = 0;
var audFound : Boolean = false;
var value : uint,state : uint = 0;
var units : Vector.<VideoFrame> = new Vector.<VideoFrame>();
// Loop through data to find NAL startcodes.
while (i < len) {
// finding 3 or 4-byte start codes (00 00 01 OR 00 00 00 01)
value = nalu[i++];
switch(state)
{
case 0:
if(!value) {
state = 1;
// unitHeader is NAL header offset
unitHeader=i-1;
}
break;
case 1:
if(value) {
state = 0;
} else {
state = 2;
}
break;
case 2:
case 3:
if(value) {
if(value === 1 && i < len) {
unitType = nalu[i] & 0x1f;
if(unitType == 9) {
audFound = true;
}
if(lastUnitStart) {
// use Math.min(4,...) as max header size is 4.
// in case there are any leading zeros
// such as 00 00 00 00 00 00 01
// ^^
// we need to ignore them as they are part of previous NAL unit
units.push(new VideoFrame(Math.min(4,lastUnitStart-lastUnitHeader), i-state-1-lastUnitStart, lastUnitStart, lastUnitType));
}
lastUnitStart = i;
lastUnitType = unitType;
lastUnitHeader = unitHeader;
if(audFound == true && (unitType === 1 || unitType === 5)) {
// OPTI !!! if AUD unit already parsed and if IDR/NDR unit, consider it is last NALu
i = len;
}
}
state = 0;
} else {
state = 3;
}
break;
default:
break;
}
}
//push last unit
if(lastUnitStart) {
units.push(new VideoFrame(Math.min(4,lastUnitStart-lastUnitHeader), len-lastUnitStart, lastUnitStart, lastUnitType));
}
// Reset position and return results.
CONFIG::LOGGING {
if (HLSSettings.logDebug2) {
/** H264 NAL unit names. **/
const NAMES : Array = ['Unspecified',// 0
'NDR', // 1
'Partition A', // 2
'Partition B', // 3
'Partition C', // 4
'IDR', // 5
'SEI', // 6
'SPS', // 7
'PPS', // 8
'AUD', // 9
'End of Sequence', // 10
'End of Stream', // 11
'Filler Data'// 12
];
if (units.length) {
var txt : String = "AVC: ";
for (i = 0; i < units.length; i++) {
txt += NAMES[units[i].type] + ","; //+ ":" + units[i].length
}
Log.debug2(txt.substr(0,txt.length-1) + " slices");
} else {
Log.debug2('AVC: no NALU slices found');
}
}
}
nalu.position = position;
return units;
};
public static function get AUD():ByteArray {
return _audNalu;
}
/**
* remove Emulation Prevention bytes from a RBSP
*/
public static function unescapeStream(data : ByteArray):ByteArray {
var length : uint = data.length;
var EPBPositions : Vector.<uint> = new Vector.<uint>();
var i : uint = 1;
var newLength : uint;
var newData : ByteArray;
// Find all `Emulation Prevention Bytes`
while (i < length - 2) {
if (data[i] === 0 &&
data[i + 1] === 0 &&
data[i + 2] === 0x03) {
EPBPositions.push(i + 2);
i += 2;
} else {
i++;
}
}
// If no Emulation Prevention Bytes were found just return the original
// array
if (EPBPositions.length === 0) {
return data;
}
// Create a new array to hold the NAL unit data
newLength = length - EPBPositions.length;
newData = new ByteArray();
newData.length = newLength;
var sourceIndex : uint = 0;
for (i = 0; i < newLength; sourceIndex++, i++) {
if (EPBPositions.length && sourceIndex === EPBPositions[0]) {
// Skip this byte
sourceIndex++;
// Remove this position index
EPBPositions.shift();
}
newData[i] = data[sourceIndex];
}
return newData;
}
}
}
+82
View File
@@ -0,0 +1,82 @@
/* 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 flash.utils.ByteArray;
/** Representation of a Packetized Elementary Stream. **/
public class PES {
/** The PES data (including headers). **/
public var data : ByteArray;
/** PES prefix **/
public var prefix : uint;
/** Start of the payload. **/
public var payload : uint;
/** Timestamp from the PTS header. **/
public var pts : Number;
/** Timestamp from the DTS header. **/
public var dts : Number;
/** PES packet len **/
public var len : int;
/** PES packet len **/
public var payload_len : int;
/** Save the first chunk of PES data. **/
public function PES(dat : ByteArray) {
data = dat;
parse();
};
/** When all data is appended, parse the PES headers. **/
private function parse() : void {
data.position = 0;
// Start code prefix and packet ID.
prefix = data.readUnsignedInt();
// read len
len = data.readUnsignedShort();
// Ignore marker bits.
data.position += 1;
// Check for PTS
var flags : uint = (data.readUnsignedByte() & 192) >> 6;
// Check PES header length
var length : uint = data.readUnsignedByte();
if (flags == 2 || flags == 3) {
// Grab the timestamp from PTS data (spread out over 5 bytes):
// XXXX---X -------- -------X -------- -------X
var _pts : Number = Number((data.readUnsignedByte() & 0x0e)) * Number(1 << 29) + Number((data.readUnsignedShort() >> 1) << 15) + Number((data.readUnsignedShort() >> 1));
// check if greater than 2^32 -1
if (_pts > 4294967295) {
// decrement 2^33
_pts -= 8589934592;
}
length -= 5;
var _dts : Number = _pts;
if (flags == 3) {
// Grab the DTS (like PTS)
_dts = Number((data.readUnsignedByte() & 0x0e)) * Number(1 << 29) + Number((data.readUnsignedShort() >> 1) << 15) + Number((data.readUnsignedShort() >> 1));
// check if greater than 2^32 -1
if (_dts > 4294967295) {
// decrement 2^33
_dts -= 8589934592;
}
length -= 5;
}
pts = Math.round(_pts / 90);
dts = Math.round(_dts / 90);
// CONFIG::LOGGING {
// Log.info("pts/dts: " + pts + "/"+ dts);
// }
}
// Skip other header data and parse payload.
data.position += length;
payload = data.position;
if (len) {
payload_len = len - data.position + 6;
} else {
payload_len = 0;
}
};
}
}
+107
View File
@@ -0,0 +1,107 @@
/* 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 flash.utils.ByteArray;
/* inspired from https://github.com/aizvorski/h264bitstream/blob/master/h264_stream.c#L241-L342 */
public class SPSInfo {
public var width : int;
public var height : int;
public function SPSInfo(sps : ByteArray) {
var profile_idc : int;
sps.position++;
profile_idc = sps.readUnsignedByte();
var eg : ExpGolomb = new ExpGolomb(sps);
// constraint_set[0-5]_flag, u(1), reserved_zero_2bits u(2), level_idc u(8)
eg.readBits(16);
// skip seq_parameter_set_id
eg.readUE();
if (profile_idc == 100 || profile_idc == 110 || profile_idc == 122 || profile_idc == 144) {
var chroma_format_idc : int = eg.readUE();
if (3 === chroma_format_idc) {
// separate_colour_plane_flag
eg.readBits(1);
}
// bit_depth_luma_minus8
eg.readUE();
// bit_depth_chroma_minus8
eg.readUE();
// qpprime_y_zero_transform_bypass_flag
eg.readBits(1);
// seq_scaling_matrix_present_flag
var seq_scaling_matrix_present_flag : Boolean = eg.readBoolean();
if (seq_scaling_matrix_present_flag) {
var imax : int = (chroma_format_idc != 3) ? 8 : 12;
for (var i : int = 0; i < imax; ++i) {
// seq_scaling_list_present_flag[ i ]
if (eg.readBoolean()) {
if (i < 6) {
scaling_list(16, eg);
} else {
scaling_list(64, eg);
}
}
}
}
}
// log2_max_frame_num_minus4
eg.readUE();
var pic_order_cnt_type : int = eg.readUE();
if ( 0 === pic_order_cnt_type ) {
// log2_max_pic_order_cnt_lsb_minus4
eg.readUE();
} else if ( 1 === pic_order_cnt_type ) {
// delta_pic_order_always_zero_flag
eg.readBits(1);
// offset_for_non_ref_pic
eg.readUE();
// offset_for_top_to_bottom_field
eg.readUE();
var num_ref_frames_in_pic_order_cnt_cycle : int = eg.readUE();
for (i = 0; i < num_ref_frames_in_pic_order_cnt_cycle; ++i) {
// offset_for_ref_frame[ i ]
eg.readUE();
}
}
// max_num_ref_frames
eg.readUE();
// gaps_in_frame_num_value_allowed_flag
eg.readBits(1);
var pic_width_in_mbs_minus1 : int = eg.readUE();
var pic_height_in_map_units_minus1 : int = eg.readUE();
var frame_mbs_only_flag : int = eg.readBits(1);
if (0 === frame_mbs_only_flag) {
// mb_adaptive_frame_field_flag
eg.readBits(1);
}
// direct_8x8_inference_flag
eg.readBits(1);
var frame_cropping_flag : int = eg.readBits(1);
if (frame_cropping_flag) {
var frame_crop_left_offset : int = eg.readUE();
var frame_crop_right_offset : int = eg.readUE();
var frame_crop_top_offset : int = eg.readUE();
var frame_crop_bottom_offset : int = eg.readUE();
}
width = ((pic_width_in_mbs_minus1 + 1) * 16) - frame_crop_left_offset * 2 - frame_crop_right_offset * 2;
height = ((2 - frame_mbs_only_flag) * (pic_height_in_map_units_minus1 + 1) * 16) - (frame_crop_top_offset * 2) - (frame_crop_bottom_offset * 2);
}
private static function scaling_list(sizeOfScalingList : int, eg : ExpGolomb) : void {
var lastScale : int = 8;
var nextScale : int = 8;
var delta_scale : int;
for (var j : int = 0; j < sizeOfScalingList; ++j) {
if (nextScale != 0) {
delta_scale = eg.readSE();
nextScale = (lastScale + delta_scale + 256) % 256;
}
lastScale = (nextScale == 0) ? lastScale : nextScale;
}
}
}
}
File diff suppressed because it is too large Load Diff
+19
View File
@@ -0,0 +1,19 @@
/* 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 {
/** Video Frame **/
public class VideoFrame {
public var header : int;
public var start : int;
public var length : int;
public var type : int;
public function VideoFrame(header : int, length : int, start : int, type : int) {
this.header = header;
this.start = start;
this.length = length;
this.type = type;
}
}
}