no commit message
This commit is contained in:
@@ -0,0 +1,235 @@
|
||||
/* 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.flv {
|
||||
import flash.utils.ByteArray;
|
||||
|
||||
/** Metadata needed to build an FLV tag. **/
|
||||
public class FLVTag {
|
||||
/** AAC Header Type ID. **/
|
||||
public static const AAC_HEADER : int = 0;
|
||||
/** AAC Data Type ID. **/
|
||||
public static const AAC_RAW : int = 1;
|
||||
/** AVC Header Type ID. **/
|
||||
public static const AVC_HEADER : int = 2;
|
||||
/** AVC Data Type ID. **/
|
||||
public static const AVC_NALU : int = 3;
|
||||
/** MP3 Data Type ID. **/
|
||||
public static const MP3_RAW : int = 4;
|
||||
/** Discontinuity Data Type ID. **/
|
||||
public static const DISCONTINUITY : int = 5;
|
||||
/** metadata Type ID. **/
|
||||
public static const METADATA : int = 6;
|
||||
/* FLV TAG TYPE */
|
||||
private static const TAG_TYPE_AUDIO : int = 8;
|
||||
private static const TAG_TYPE_VIDEO : int = 9;
|
||||
private static const TAG_TYPE_SCRIPT : int = 18;
|
||||
/** Is this a keyframe. **/
|
||||
public var keyframe : Boolean;
|
||||
/** Array with data pointers. **/
|
||||
protected var pointers : Vector.<TagData> = new Vector.<TagData>();
|
||||
/** PTS of this frame. **/
|
||||
public var pts : Number;
|
||||
/** DTS of this frame. **/
|
||||
public var dts : Number;
|
||||
/** Type of FLV tag.**/
|
||||
public var type : int;
|
||||
/* built data */
|
||||
protected var builtData : ByteArray;
|
||||
/* payload length */
|
||||
protected var length : int;
|
||||
|
||||
/** Get the FLV file header. **/
|
||||
public static function getHeader() : ByteArray {
|
||||
var flv : ByteArray = new ByteArray();
|
||||
flv.length = 13;
|
||||
// "F" + "L" + "V".
|
||||
flv.writeByte(0x46);
|
||||
flv.writeByte(0x4C);
|
||||
flv.writeByte(0x56);
|
||||
// File version (1)
|
||||
flv.writeByte(1);
|
||||
/*
|
||||
Signal that both Audio and Video tags are present. this is needed as getHeader() is used when injecting discontinuity
|
||||
if we don't signal both, there will be issues while switching between AV stream to Video Only or vice versa
|
||||
*/
|
||||
flv.writeByte(5);
|
||||
// Length of the header.
|
||||
flv.writeUnsignedInt(9);
|
||||
// PreviousTagSize0
|
||||
flv.writeUnsignedInt(0);
|
||||
return flv;
|
||||
}
|
||||
|
||||
/** Get an FLV Tag header (11 bytes). **/
|
||||
private static function updateTagHeader(tag: ByteArray, type : int, length : int, stamp : int) : void {
|
||||
if(tag.length == 0) {
|
||||
// set exact length only if not specified already
|
||||
tag.length = 11+length+4;
|
||||
}
|
||||
tag.writeByte(type);
|
||||
|
||||
// Size of the tag in bytes after StreamID.
|
||||
tag.writeByte(length >> 16);
|
||||
tag.writeByte(length >> 8);
|
||||
tag.writeByte(length);
|
||||
// Timestamp (lower 24 plus upper 8)
|
||||
tag.writeByte(stamp >> 16);
|
||||
tag.writeByte(stamp >> 8);
|
||||
tag.writeByte(stamp);
|
||||
tag.writeByte(stamp >> 24);
|
||||
// StreamID (3 empty bytes)
|
||||
tag.writeByte(0);
|
||||
tag.writeByte(0);
|
||||
tag.writeByte(0);
|
||||
// All done
|
||||
return;
|
||||
}
|
||||
|
||||
/** Save the frame data and parameters. **/
|
||||
public function FLVTag(typ : int, stp_p : Number, stp_d : Number, key : Boolean) {
|
||||
type = typ;
|
||||
pts = stp_p;
|
||||
dts = stp_d;
|
||||
keyframe = key;
|
||||
length = 0;
|
||||
}
|
||||
|
||||
/** Returns the tag data. **/
|
||||
public function get data() : ByteArray {
|
||||
if(builtData) {
|
||||
// update header (PTS/DTS may have changed because of keyframe/accurate seeking)
|
||||
buildHeader(builtData);
|
||||
} else {
|
||||
build();
|
||||
}
|
||||
return builtData;
|
||||
}
|
||||
|
||||
/** Build tag data (including header) **/
|
||||
public function build() : void {
|
||||
if(!builtData) {
|
||||
var array : ByteArray = new ByteArray();
|
||||
buildHeader(array);
|
||||
if(pointers) {
|
||||
for each(var pointer : TagData in pointers) {
|
||||
if (type == AVC_NALU) {
|
||||
array.writeUnsignedInt(pointer.length);
|
||||
}
|
||||
array.writeBytes(pointer.array, pointer.start, pointer.length);
|
||||
}
|
||||
// save memory, free pointers (PES payload)
|
||||
pointers = null;
|
||||
}
|
||||
// Write previousTagSize and return data.
|
||||
array.writeUnsignedInt(array.length);
|
||||
builtData = array;
|
||||
}
|
||||
}
|
||||
|
||||
/** build/update FLV tag header **/
|
||||
private function buildHeader(array : ByteArray) : void {
|
||||
/* following specification http://download.macromedia.com/f4v/video_file_format_spec_v10_1.pdf */
|
||||
// Render header data
|
||||
// ensure that we are at the beginning , for update case
|
||||
array.position = 0;
|
||||
if (type == FLVTag.MP3_RAW) {
|
||||
updateTagHeader(array,TAG_TYPE_AUDIO, length + 1, pts);
|
||||
// Presume MP3 is 44.1 stereo.
|
||||
array.writeByte(0x2F);
|
||||
} else if (type == AVC_HEADER || type == AVC_NALU) {
|
||||
updateTagHeader(array,TAG_TYPE_VIDEO, length + 5, dts);
|
||||
// keyframe/interframe switch (0x10 / 0x20) + AVC (0x07)
|
||||
keyframe ? array.writeByte(0x17) : array.writeByte(0x27);
|
||||
/* AVC Packet Type :
|
||||
0 = AVC sequence header
|
||||
1 = AVC NALU
|
||||
2 = AVC end of sequence (lower level NALU sequence ender is
|
||||
not required or supported) */
|
||||
type == AVC_HEADER ? array.writeByte(0x00) : array.writeByte(0x01);
|
||||
// CompositionTime (in ms)
|
||||
// CONFIG::LOGGING {
|
||||
// Log.info("pts:"+pts+",dts:"+dts+",delta:"+compositionTime);
|
||||
// }
|
||||
var compositionTime : Number = (pts - dts);
|
||||
array.writeByte(compositionTime >> 16);
|
||||
array.writeByte(compositionTime >> 8);
|
||||
array.writeByte(compositionTime);
|
||||
} else if (type == DISCONTINUITY || type == METADATA) {
|
||||
updateTagHeader(array,FLVTag.TAG_TYPE_SCRIPT, length, pts);
|
||||
} else {
|
||||
updateTagHeader(array,TAG_TYPE_AUDIO, length + 2, pts);
|
||||
// SoundFormat, -Rate, -Size, Type and Header/Raw switch.
|
||||
array.writeByte(0xAF);
|
||||
type == AAC_HEADER ? array.writeByte(0x00) : array.writeByte(0x01);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
CONFIG::LOGGING {
|
||||
public function get typeString() : String {
|
||||
switch(type) {
|
||||
case AAC_HEADER:
|
||||
return "AAC_HEADER";
|
||||
case AAC_RAW :
|
||||
return "AAC_RAW";
|
||||
case AVC_HEADER:
|
||||
return "AVC_HEADER";
|
||||
case AVC_NALU:
|
||||
if(keyframe) {
|
||||
return "AVC_NALU_K";
|
||||
} else {
|
||||
return "AVC_NALU";
|
||||
}
|
||||
case MP3_RAW:
|
||||
return "MP3_RAW";
|
||||
case DISCONTINUITY:
|
||||
return "DISCONTINUITY";
|
||||
case METADATA:
|
||||
return "METADATA";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** push a data pointer into the frame. **/
|
||||
public function push(array : ByteArray, start : int, len : int) : void {
|
||||
if(len) {
|
||||
pointers.push(new TagData(array, start, len));
|
||||
length += len;
|
||||
if (type == AVC_NALU) {
|
||||
length += 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Trace the contents of this tag. **/
|
||||
public function toString() : String {
|
||||
return "TAG (type: " + type + ", pts:" + pts + ", dts:" + dts + ", length:" + length + ")";
|
||||
}
|
||||
|
||||
public function clone() : FLVTag {
|
||||
var cloned : FLVTag = new FLVTag(this.type, this.pts, this.dts, this.keyframe);
|
||||
cloned.builtData = this.builtData;
|
||||
cloned.pointers = this.pointers;
|
||||
cloned.length = this.length;
|
||||
return cloned;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Tag Content **/
|
||||
class TagData {
|
||||
import flash.utils.ByteArray;
|
||||
|
||||
public var array : ByteArray;
|
||||
public var start : int;
|
||||
public var length : int;
|
||||
|
||||
public function TagData(array : ByteArray, start : int, length : int) {
|
||||
this.array = array;
|
||||
this.start = start;
|
||||
this.length = length;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user