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;
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user