7a41ceb5 by David LaPalomento

Segment parser finishes with the example TS segment

The parser can process the example TS but it doesn't appear to be correctly formed. The player does seem to correctly interpret the video duration, but the display area is black and there is no audio.
1 parent c09dfad4
/<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>video.js HLS Plugin Example</title>
<link href="http://vjs.zencdn.net/4.0/video-js.css" rel="stylesheet">
<!-- video.js -->
<script src="libs/video-js/src/js/core.js"></script>
<script src="libs/video-js/src/js/core-object.js"></script>
<script src="libs/video-js/src/js/events.js"></script>
<script src="libs/video-js/src/js/lib.js"></script>
<script src="libs/video-js/src/js/component.js"></script>
<script src="libs/video-js/src/js/button.js"></script>
<script src="libs/video-js/src/js/slider.js"></script>
<script src="libs/video-js/src/js/menu.js"></script>
<script src="libs/video-js/src/js/player.js"></script>
<script src="libs/video-js/src/js/control-bar/control-bar.js"></script>
<script src="libs/video-js/src/js/control-bar/play-toggle.js"></script>
<script src="libs/video-js/src/js/control-bar/time-display.js"></script>
<script src="libs/video-js/src/js/control-bar/fullscreen-toggle.js"></script>
<script src="libs/video-js/src/js/control-bar/progress-control.js"></script>
<script src="libs/video-js/src/js/control-bar/volume-control.js"></script>
<script src="libs/video-js/src/js/control-bar/mute-toggle.js"></script>
<script src="libs/video-js/src/js/control-bar/volume-menu-button.js"></script>
<script src="libs/video-js/src/js/poster.js"></script>
<script src="libs/video-js/src/js/loading-spinner.js"></script>
<script src="libs/video-js/src/js/big-play-button.js"></script>
<script src="libs/video-js/src/js/media/media.js"></script>
<script src="libs/video-js/src/js/media/html5.js"></script>
<script src="libs/video-js/src/js/media/flash.js"></script>
<script src="libs/video-js/src/js/media/loader.js"></script>
<script src="libs/video-js/src/js/tracks.js"></script>
<script src="libs/video-js/src/js/json.js"></script>
<script src="libs/video-js/src/js/setup.js"></script>
<script src="libs/video-js/src/js/plugins.js"></script>
<!-- Media Sources plugin -->
<script src="libs/videojs-media-sources.js"></script>
<!-- HLS plugin -->
<script src="src/video-js-hls.js"></script>
<script src="src/flv-tag.js"></script>
<script src="src/exp-golomb.js"></script>
<script src="src/h264-stream.js"></script>
<script src="src/aac-stream.js"></script>
<script src="src/segment-parser.js"></script>
<!-- an example MPEG2-TS segment -->
<script src="test/tsSegment.js"></script>
</head>
<body>
<video id='video'
class="video-js vjs-default-skin"
height="300"
width="600"
controls>
</video>
<script>
var video, mediaSource;
// initialize the player
videojs.options.flash.swf = "libs/video-js.swf";
video = videojs('video');
// create a media source
mediaSource = new videojs.MediaSource();
mediaSource.addEventListener('sourceopen', function(event){
var tag, bytes, parser;
// feed parsed bytes into the player
var sourceBuffer = mediaSource.addSourceBuffer('video/flv; codecs="vp6,aac"');
parser = new videojs.hls.SegmentParser();
var header = parser.getFlvHeader();
sourceBuffer.appendBuffer(header);
parser.parseSegmentBinaryData(window.testSegment);
while (parser.tagsAvailable()) {
tag = parser.getNextTag();
console.log('sending bytes', tag.length);
sourceBuffer.appendBuffer(tag.bytes.subarray(0, tag.length), video);
}
}, false);
url = videojs.URL.createObjectURL(mediaSource);
video.src({
src: url,
type: "video/flv"
});
</script>
</body>
</html>
......@@ -7,9 +7,216 @@
*/
(function(window) {
var
FlvTag = window.videojs.hls.FlvTag,
adtsSampleingRates = [
96000, 88200,
64000, 48000,
44100, 32000,
24000, 22050,
16000, 12000
];
window.videojs.hls.AacStream = function() {
var
next_pts, // :uint
pts_delta = -1, // :int
state, // :uint
pes_length, // :int
adtsProtectionAbsent, // :Boolean
adtsObjectType, // :int
adtsSampleingIndex, // :int
adtsChanelConfig, // :int
adtsFrameSize, // :int
adtsSampleCount, // :int
adtsDuration, // :int
aacFrame, // :FlvTag = null;
extraData; // :uint;
window.videojs.hls.AacStream = function(){
this.tags = [];
// (pts:uint, pes_size:int, dataAligned:Boolean):void
this.setNextTimeStamp = function(pts, pes_size, dataAligned) {
if (0 > pts_delta) {
// We assume the very firts pts is less than 0x80000000
pts_delta = pts;
}
next_pts = pts - pts_delta;
pes_length = pes_size;
// If data is aligned, flush all internal buffers
if (dataAligned) {
state = 0;
}
};
// (pData:ByteArray, o:int = 0, l:int = 0):void
this.writeBytes = function(pData, o, l) {
var
e, // :int
newExtraData, // :uint
bytesToCopy; // :int
// default arguments
o = o || 0;
l = l || 0;
// Do not allow more that 'pes_length' bytes to be written
l = (pes_length < l ? pes_length : l);
pes_length -= l;
e = o + l;
while(o < e) {
switch (state) {
default:
state = 0;
break;
case 0:
if (o >= e) {
return;
}
if (0xFF !== pData[o]) {
console.log("Error no ATDS header found");
o += 1;
state = 0;
return;
}
o += 1;
state = 1;
break;
case 1:
if (o >= e) {
return;
}
if (0xF0 !== (pData[o] & 0xF0)) {
console.log("Error no ATDS header found");
o +=1;
state = 0;
return;
}
adtsProtectionAbsent = !!(pData[o] & 0x01);
o += 1;
state = 2;
break;
case 2:
if (o >= e) {
return;
}
adtsObjectType = ((pData[o] & 0xC0) >>> 6) + 1;
adtsSampleingIndex = ((pData[o] & 0x3C) >>> 2);
adtsChanelConfig = ((pData[o] & 0x01) << 2);
o += 1;
state = 3;
break;
case 3:
if (o >= e) {
return;
}
adtsChanelConfig |= ((pData[o] & 0xC0) >>> 6);
adtsFrameSize = ((pData[o] & 0x03) << 11);
o += 1;
state = 4;
break;
case 4:
if (o >= e) {
return;
}
adtsFrameSize |= (pData[o] << 3);
o += 1;
state = 5;
break;
case 5:
if(o >= e) {
return;
}
adtsFrameSize |= ((pData[o] & 0xE0) >>> 5);
adtsFrameSize -= (adtsProtectionAbsent ? 7 : 9);
o += 1;
state = 6;
break;
case 6:
if (o >= e) {
return;
}
adtsSampleCount = ((pData[o] & 0x03) + 1) * 1024;
adtsDuration = (adtsSampleCount * 1000) / adtsSampleingRates[adtsSampleingIndex];
newExtraData = (adtsObjectType << 11) |
(adtsSampleingIndex << 7) |
(adtsChanelConfig << 3);
if (newExtraData !== extraData) {
aacFrame = new FlvTag(FlvTag.METADATA_TAG);
aacFrame.pts = next_pts;
aacFrame.dts = next_pts;
// AAC is always 10
aacFrame.writeMetaDataDouble("audiocodecid", 10);
aacFrame.writeMetaDataBoolean("stereo", 2 === adtsChanelConfig);
aacFrame.writeMetaDataDouble ("audiosamplerate", adtsSampleingRates[adtsSampleingIndex]);
// Is AAC always 16 bit?
aacFrame.writeMetaDataDouble ("audiosamplesize", 16);
this.tags.push(aacFrame);
extraData = newExtraData;
aacFrame = new FlvTag(FlvTag.AUDIO_TAG, true);
aacFrame.pts = aacFrame.dts;
// For audio, DTS is always the same as PTS. We want to set the DTS
// however so we can compare with video DTS to determine approximate
// packet order
aacFrame.pts = next_pts;
aacFrame.view.setUint16(aacFrame.position, newExtraData);
aacFrame.position += 2;
this.tags.push(aacFrame);
}
// Skip the checksum if there is one
o += 1;
state = 7;
break;
case 7:
if (!adtsProtectionAbsent) {
if (2 > (e - o)) {
return;
} else {
o += 2;
}
}
aacFrame = new FlvTag(FlvTag.AUDIO_TAG);
aacFrame.pts = next_pts;
aacFrame.dts = next_pts;
state = 8;
break;
case 8:
while (adtsFrameSize) {
if (o >= e) {
return;
}
bytesToCopy = (e - o) < adtsFrameSize ? (e - o) : adtsFrameSize;
aacFrame.writeBytes( pData, o, bytesToCopy );
o += bytesToCopy;
adtsFrameSize -= bytesToCopy;
}
this.tags.push(aacFrame);
// finished with this frame
state = 0;
next_pts += adtsDuration;
}
}
};
};
})(this);
......
(function() {
window.videojs.hls.ExpGolomb = function() {};
/*
public class ExpGolomb
{
private var workingData:ByteArray;
private var workingWord:uint;
private var workingbBitsAvailable:uint;
public function ExpGolomb(pData:ByteArray)
{
workingData = pData;
workingData.position = 0;
loadWord();
}
public function length():uint
{
return ( 8 * workingData.length );
}
public function bitsAvailable():uint
{
return ( 8 * workingData.bytesAvailable ) + workingbBitsAvailable;
}
private function loadWord():void
{
workingWord = 0; workingbBitsAvailable = 0;
switch( workingData.bytesAvailable )
{
case 0: workingbBitsAvailable = 0; break;
default: // not 0, but greater than 4
case 4: workingWord = workingData.readUnsignedByte(); workingbBitsAvailable = 8;
case 3: workingWord = ( workingWord << 8 ) | workingData.readUnsignedByte(); workingbBitsAvailable += 8;
case 2: workingWord = ( workingWord << 8 ) | workingData.readUnsignedByte(); workingbBitsAvailable += 8;
case 1: workingWord = ( workingWord << 8 ) | workingData.readUnsignedByte(); workingbBitsAvailable += 8;
}
workingWord <<= (32 - workingbBitsAvailable);
}
public function skipBits(size:int):void
{
if ( workingbBitsAvailable > size )
{
workingWord <<= size;
workingbBitsAvailable -= size;
}
else
{
size -= workingbBitsAvailable;
var skipBytes:int = size / 8;
size -= ( skipBytes * 8 );
workingData.position += skipBytes;
loadWord();
workingWord <<= size;
workingbBitsAvailable -= size;
}
}
public function readBits(size:int):uint
{
// if ( 32 < size )
// throw new Error("Can not read more than 32 bits at a time");
var bits:uint = ( workingbBitsAvailable < size ? workingbBitsAvailable : size);
var valu:uint = workingWord >>> (32 - bits);
workingbBitsAvailable -= bits;
if ( 0 < workingbBitsAvailable )
workingWord <<= bits;
else
loadWord();
bits = size - bits;
if ( 0 < bits )
return valu << bits | readBits( bits );
else
return valu;
}
private function skipLeadingZeros():uint
{
for( var clz:uint = 0 ; clz < workingbBitsAvailable ; ++clz )
{
if( 0 != ( workingWord & ( 0x80000000 >>> clz ) ) )
{
workingWord <<= clz;
workingbBitsAvailable -= clz;
return clz;
}
}
loadWord(); // we exhausted workingWord and still have not found a 1
return clz + skipLeadingZeros();
}
public function skipUnsignedExpGolomb():void
{
skipBits(1 + skipLeadingZeros() );
}
public function skipExpGolomb():void
{
skipBits(1 + skipLeadingZeros() );
}
public function readUnsignedExpGolomb():uint
{
var clz:uint = skipLeadingZeros();
return readBits(clz+1) - 1;
}
public function readExpGolomb():int
{
var valu:int = readUnsignedExpGolomb();
if ( 0x01 & valu ) // the number is odd if the low order bit is set
return (1 + valu) >>> 1; // add 1 to make it even, and devide by 2
else
return -1 * (valu >>> 1); // devide by two then make it negative
}
// Some convenience functions
public function readBoolean():Boolean
{
return 1 == readBits(1);
}
public function readUnsignedByte():int
{
return readBits(8);
}
(function(window) {
window.videojs.hls.ExpGolomb = function(workingData) {
var
// the number of bytes left to examine in workingData
workingBytesAvailable = workingData.byteLength,
// the current word being examined
workingWord, // :uint
// the number of bits left to examine in the current word
workingBitsAvailable; // :uint;
// ():uint
this.length = function() {
return (8 * workingBytesAvailable);
};
// ():uint
this.bitsAvailable = function() {
return (8 * workingBytesAvailable) + workingBitsAvailable;
};
// ():void
this.loadWord = function() {
var
workingBytes = new Uint8Array(4),
availableBytes = Math.min(4, workingBytesAvailable);
// console.assert(availableBytes > 0);
workingBytes.set(workingData.subarray(0, availableBytes));
workingWord = new DataView(workingBytes.buffer).getUint32(0);
// track the amount of workingData that has been processed
workingBitsAvailable = availableBytes * 8;
workingBytesAvailable -= availableBytes;
};
// (size:int):void
this.skipBits = function(size) {
var skipBytes; // :int
if (workingBitsAvailable > size) {
workingWord <<= size;
workingBitsAvailable -= size;
} else {
size -= workingBitsAvailable;
skipBytes = size / 8;
size -= (skipBytes * 8);
workingData.position += skipBytes;
this.loadWord();
workingWord <<= size;
workingBitsAvailable -= size;
}
*/
})();
};
// (size:int):uint
this.readBits = function(size) {
var
bits = Math.min(workingBitsAvailable, size), // :uint
valu = workingWord >>> (32 - bits); // :uint
console.assert(32 > size, 'Cannot read more than 32 bits at a time');
workingBitsAvailable -= bits;
if (0 < workingBitsAvailable) {
workingWord <<= bits;
} else {
this.loadWord();
}
bits = size - bits;
if (0 < bits) {
return valu << bits | this.readBits(bits);
} else {
return valu;
}
};
// ():uint
this.skipLeadingZeros = function() {
var clz; // :uint
for (clz = 0 ; clz < workingBitsAvailable ; ++clz) {
if (0 !== (workingWord & (0x80000000 >>> clz))) {
workingWord <<= clz;
workingBitsAvailable -= clz;
return clz;
}
}
// we exhausted workingWord and still have not found a 1
this.loadWord();
return clz + this.skipLeadingZeros();
};
// ():void
this.skipUnsignedExpGolomb = function() {
this.skipBits(1 + this.skipLeadingZeros());
};
// ():void
this.skipExpGolomb = function() {
this.skipBits(1 + this.skipLeadingZeros());
};
// ():uint
this.readUnsignedExpGolomb = function() {
var clz = this.skipLeadingZeros(); // :uint
return this.readBits(clz + 1) - 1;
};
// ():int
this.readExpGolomb = function() {
var valu = this.readUnsignedExpGolomb(); // :int
if (0x01 & valu) {
// the number is odd if the low order bit is set
return (1 + valu) >>> 1; // add 1 to make it even, and divide by 2
} else {
return -1 * (valu >>> 1); // divide by two then make it negative
}
};
// Some convenience functions
// :Boolean
this.readBoolean = function() {
return 1 === this.readBits(1);
};
// ():int
this.readUnsignedByte = function() {
return this.readBits(8);
};
this.loadWord();
};
})(this);
......
(function(window) {
window.videojs.hls.FlvTag = function() {};
/*
package com.videojs.providers.hls.utils{
import flash.utils.ByteArray;
import flash.utils.Endian;
public class FlvTag extends ByteArray
{
public static const AUDIO_TAG:uint = 0x08;
public static const VIDEO_TAG:uint = 0x09;
public static const METADATA_TAG:uint = 0x12;
public var keyFrame:Boolean = false;
private var extraData:Boolean = false;
private var adHoc:uint = 0; // Counter if this is a metadata tag, nal start marker if this is a video tag. unused if this is an audio tag
public var pts:uint;
public var dts:uint;
public static function isAudioFrame(tag:ByteArray):Boolean
{
return AUDIO_TAG == tag[0];
}
public static function isVideoFrame(tag:ByteArray):Boolean
{
return VIDEO_TAG == tag[0];
}
public static function isMetaData(tag:ByteArray):Boolean
{
return METADATA_TAG == tag[0];
}
public static function isKeyFrame(tag:ByteArray):Boolean
{
if ( isVideoFrame(tag) )
return tag[11] == 0x17;
if( isAudioFrame(tag) )
return true;
if( isMetaData(tag) )
return true;
return false;
}
public static function frameTime(tag:ByteArray):uint
{
var pts:uint = tag[ 4] << 16;
pts |= tag[ 5] << 8;
pts |= tag[ 6] << 0;
pts |= tag[ 7] << 24;
return pts;
}
public function FlvTag(type:uint, ed:Boolean = false)
{
super();
extraData = ed;
this.endian = Endian.BIG_ENDIAN;
switch(type)
{
case VIDEO_TAG: this.length = 16; break;
case AUDIO_TAG: this.length = 13; keyFrame = true; break;
case METADATA_TAG: this.length = 29; keyFrame = true; break;
default: throw("Error Unknown TagType");
}
this[0] = type
this.position = this.length;
keyFrame = extraData; // Defaults to false
pts = dts = 0;
}
// Negative index into array
public function negIndex(pos:uint):int
{
return this[this.length - pos];
}
// The functions below ONLY work when this[0] == VIDEO_TAG.
// We are not going to check for that because we dont want the overhead
public function nalUnitSize(nal:ByteArray = null):int
{
if( 0 == adHoc )
return 0;
return this.length - ( adHoc + 4 );
}
public function startNalUnit():void
{ // remember position and add 4 bytes
if ( 0 < adHoc )
{
throw new Error("Attempted to create new NAL wihout closing the old one");
}
// reserve 4 bytes for nal unit size
adHoc = this.length;
this.length += 4;
this.position = this.length;
}
public function endNalUnit(nal:ByteArray = null):void
{ // Rewind to the marker and write the size
if ( this.length == adHoc + 4 )
{
this.length -= 4; // we started a nal unit, but didnt write one, so roll back the 4 byte size value
}
else
if ( 0 < adHoc )
{
var nalStart:uint = adHoc + 4;
var nalLength:uint = this.length - nalStart;
this.position = adHoc;
this.writeUnsignedInt( nalLength );
this.position = this.length;
if ( null != nal ) // If the user pass in a ByteArray, copy the NAL to it.
nal.writeBytes( this, nalStart, nalLength );
}
adHoc = 0;
}
public function writeMetaDataDouble(key:String, val:Number):void
{
writeShort ( key.length );
writeUTFBytes ( key );
writeByte ( 0x00 );
writeDouble ( val );
++adHoc;
}
public function writeMetaDataBoolean(key:String, val:Boolean):void
{
writeShort ( key.length );
writeUTFBytes ( key );
writeByte ( 0x01 );
writeByte ( true == val ? 0x01 : 0x00 );
++adHoc;
}
public function finalize():ByteArray
{
switch(this[0])
{ // Video Data
case VIDEO_TAG:
this[11] = ( ( keyFrame || extraData ) ? 0x10 : 0x20 ) | 0x07; // We only support AVC, 1 = key frame (for AVC, a seekable frame), 2 = inter frame (for AVC, a non-seekable frame)
this[12] = extraData ? 0x00 : 0x01;
var dtsDelta:int = pts - dts;
this[13] = ( dtsDelta & 0x00FF0000 ) >>> 16;
this[14] = ( dtsDelta & 0x0000FF00 ) >>> 8;
this[15] = ( dtsDelta & 0x000000FF ) >>> 0;
break;
case AUDIO_TAG:
this[11] = 0xAF;
this[12] = extraData ? 0x00 : 0x01;
break;
case METADATA_TAG:
this.position = 11;
writeByte(0x02); // String type
writeShort(0x0A); // 10 Bytes
writeUTFBytes("onMetaData");
writeByte(0x08); // Array type
writeUnsignedInt( adHoc );
this.position = this.length;
writeUnsignedInt( 0x09 ); // End Data Tag
break;
}
var len:int = this.length - 11;
this[ 1] = ( len & 0x00FF0000 ) >>> 16;
this[ 2] = ( len & 0x0000FF00 ) >>> 8;
this[ 3] = ( len & 0x000000FF ) >>> 0;
this[ 4] = ( pts & 0x00FF0000 ) >>> 16;
this[ 5] = ( pts & 0x0000FF00 ) >>> 8;
this[ 6] = ( pts & 0x000000FF ) >>> 0;
this[ 7] = ( pts & 0xFF000000 ) >>> 24;
this[ 8] = 0;
this[ 9] = 0;
this[10] = 0;
this.writeUnsignedInt( this.length );
return this;
}
var hls = window.videojs.hls;
// (type:uint, extraData:Boolean = false) extends ByteArray
hls.FlvTag = function(type, extraData) {
var
// Counter if this is a metadata tag, nal start marker if this is a video
// tag. unused if this is an audio tag
adHoc = 0; // :uint
this.keyFrame = false; // :Boolean
switch(type) {
case hls.FlvTag.VIDEO_TAG:
this.length = 16;
break;
case hls.FlvTag.AUDIO_TAG:
this.length = 13;
this.keyFrame = true;
break;
case hls.FlvTag.METADATA_TAG:
this.length = 29;
this.keyFrame = true;
break;
default:
throw("Error Unknown TagType");
}
// XXX: I have no idea if 16k is enough to buffer arbitrary FLV tags
this.bytes = new Uint8Array(16384);
this.view = new DataView(this.bytes.buffer);
this.bytes[0] = type;
this.position = this.length;
this.keyFrame = extraData; // Defaults to false
// presentation timestamp
this.pts = 0;
// decoder timestamp
this.dts = 0;
// ByteArray#writeBytes(bytes:ByteArray, offset:uint = 0, length:uint = 0)
this.writeBytes = function(bytes, offset, length) {
offset = offset || 0;
length = length || 0;
try {
this.bytes.set(bytes.subarray(offset, offset + length), this.position);
} catch(e) {
console.log(e);
throw e;
}
}
*/
this.position += length;
this.length = Math.max(this.length, this.position);
};
// ByteArray#writeByte(value:int):void
this.writeByte = function(byte) {
this.bytes[this.position] = byte;
this.position++;
this.length = Math.max(this.length, this.position);
};
// ByteArray#writeShort(value:int):void
this.writeShort = function(short) {
this.view.setUint16(short, this.position);
this.position += 2;
this.length = Math.max(this.length, this.position);
};
// Negative index into array
// (pos:uint):int
this.negIndex = function(pos) {
return this.bytes[this.length - pos];
};
// The functions below ONLY work when this[0] == VIDEO_TAG.
// We are not going to check for that because we dont want the overhead
// (nal:ByteArray = null):int
this.nalUnitSize = function() {
if (adHoc === 0) {
return 0;
}
return this.length - (adHoc + 4);
};
this.startNalUnit = function() {
// remember position and add 4 bytes
if (adHoc > 0) {
throw new Error("Attempted to create new NAL wihout closing the old one");
}
// reserve 4 bytes for nal unit size
adHoc = this.length;
this.length += 4;
this.position = this.length;
};
// (nal:ByteArray = null):void
this.endNalUnit = function(nalContainer) {
var
nalStart, // :uint
nalLength; // :uint
// Rewind to the marker and write the size
if (this.length === adHoc + 4) {
this.length -= 4; // we started a nal unit, but didnt write one, so roll back the 4 byte size value
} else if (adHoc > 0) {
nalStart = adHoc + 4;
nalLength = this.length - nalStart;
this.position = adHoc;
this.view.setUint32(this.position, nalLength);
this.position = this.length;
if (nalContainer) {
// Add the tag to the NAL unit
nalContainer.push(this.bytes.subarray(nalStart, nalStart + nalLength));
}
}
adHoc = 0;
};
// (key:String, val:Number):void
this.writeMetaDataDouble = function(key, val) {
var i;
this.view.setUint16(this.position, key.length);
this.position += 2;
for (i in key) {
console.assert(key.charCodeAt(i) < 255);
this.bytes[this.position] = key.charCodeAt(i);
this.position++;
}
this.view.setUint8(this.position, 0x00);
this.position++;
this.view.setFloat64(this.position, val);
this.position += 2;
++adHoc;
};
// (key:String, val:Boolean):void
this.writeMetaDataBoolean = function(key, val) {
var i;
this.view.setUint16(this.position, key.length);
this.position += 2;
for (i in key) {
console.assert(key.charCodeAt(i) < 255);
this.bytes[this.position] = key.charCodeAt(i);
this.position++;
}
this.view.setUint8(this.position, 0x01);
this.position++;
this.view.setUint8(this.position, val ? 0x01 : 0x00);
this.position++;
++adHoc;
};
// ():ByteArray
this.finalize = function() {
var
dtsDelta, // :int
len; // :int
switch(this.bytes[0]) {
// Video Data
case hls.FlvTag.VIDEO_TAG:
this.bytes[11] = ((this.keyFrame || extraData) ? 0x10 : 0x20 ) | 0x07; // We only support AVC, 1 = key frame (for AVC, a seekable frame), 2 = inter frame (for AVC, a non-seekable frame)
this.bytes[12] = extraData ? 0x00 : 0x01;
dtsDelta = this.pts - this.dts;
this.bytes[13] = (dtsDelta & 0x00FF0000) >>> 16;
this.bytes[14] = (dtsDelta & 0x0000FF00) >>> 8;
this.bytes[15] = (dtsDelta & 0x000000FF) >>> 0;
break;
case hls.FlvTag.AUDIO_TAG:
this.bytes[11] = 0xAF;
this.bytes[12] = extraData ? 0x00 : 0x01;
break;
case hls.FlvTag.METADATA_TAG:
this.position = 11;
this.view.setUint8(this.position, 0x02); // String type
this.position++;
this.view.setUint16(this.position, 0x0A); // 10 Bytes
this.position += 2;
// set "onMetaData"
this.bytes.set([0x6f, 0x6e, 0x4d, 0x65,
0x74, 0x61, 0x44, 0x61,
0x74, 0x61], this.position);
this.position += 10;
this.view.setUint8(this.position,
this.bytes[this.position] | 0x08); // Array type
this.position++;
this.view.setUint32(this.position, adHoc);
this.position = this.length;
this.view.setUint32(this.position, 0x09); // End Data Tag
this.position += 4;
this.length = this.position;
break;
}
len = this.length - 11;
this.bytes[ 1] = ( len & 0x00FF0000 ) >>> 16;
this.bytes[ 2] = ( len & 0x0000FF00 ) >>> 8;
this.bytes[ 3] = ( len & 0x000000FF ) >>> 0;
this.bytes[ 4] = ( this.pts & 0x00FF0000 ) >>> 16;
this.bytes[ 5] = ( this.pts & 0x0000FF00 ) >>> 8;
this.bytes[ 6] = ( this.pts & 0x000000FF ) >>> 0;
this.bytes[ 7] = ( this.pts & 0xFF000000 ) >>> 24;
this.bytes[ 8] = 0;
this.bytes[ 9] = 0;
this.bytes[10] = 0;
this.view.setUint32(this.length, this.length);
this.length += 4;
this.position += 4;
return this;
};
};
hls.FlvTag.AUDIO_TAG = 0x08; // :uint
hls.FlvTag.VIDEO_TAG = 0x09; // :uint
hls.FlvTag.METADATA_TAG = 0x12; // :uint
// (tag:ByteArray):Boolean {
hls.FlvTag.isAudioFrame = function(tag) {
return hls.FlvTag.AUDIO_TAG === tag[0];
};
// (tag:ByteArray):Boolean {
hls.FlvTag.isVideoFrame = function(tag) {
return hls.FlvTag.VIDEO_TAG === tag[0];
};
// (tag:ByteArray):Boolean {
hls.FlvTag.isMetaData = function(tag) {
return hls.FlvTag.METADATA_TAG === tag[0];
};
// (tag:ByteArray):Boolean {
hls.FlvTag.isKeyFrame = function(tag) {
if (hls.FlvTag.isVideoFrame(tag)) {
return tag[11] === 0x17;
}
if (hls.FlvTag.isAudioFrame(tag)) {
return true;
}
if (hls.FlvTag.isMetaData(tag)) {
return true;
}
return false;
};
// (tag:ByteArray):uint {
hls.FlvTag.frameTime = function(tag) {
var pts = tag[ 4] << 16; // :uint
pts |= tag[ 5] << 8;
pts |= tag[ 6] << 0;
pts |= tag[ 7] << 24;
return pts;
};
})(this);
......
......@@ -8,27 +8,31 @@
(function(window) {
var
ExpGolomb = window.videojs.hls.ExpGolomb,
FlvTag = window.videojs.hls.FlvTag,
H264ExtraData = function() {
var
sps = [], // :Array
pps = []; // :Array
this.addSPS = function() { // :ByteArray
var tmp = new Uint8Array(); // :ByteArray
sps.push(tmp);
H264ExtraData = function() {
this.sps = []; // :Array
this.pps = []; // :Array
this.addSPS = function(size) { // :ByteArray
console.log('come on, you fucker');
console.assert(size > 0);
var tmp = new Uint8Array(size); // :ByteArray
this.sps.push(tmp);
return tmp;
};
this.addPPS = function() { // :ByteArray
var tmp = new Uint8Array(); // :ByteArray
pps.push(tmp);
this.addPPS = function(size) { // :ByteArray
console.assert(size);
var tmp = new Uint8Array(size); // :ByteArray
this.pps.push(tmp);
return tmp;
};
this.extraDataExists = function() { // :Boolean
return 0 < sps.length;
return 0 < this.sps.length;
};
// (sizeOfScalingList:int, expGolomb:ExpGolomb):void
......@@ -53,18 +57,20 @@
};
this.getSps0Rbsp = function() { // :ByteArray
// remove emulation bytes. Is this nesessary? is there ever emulation bytes in the SPS?
// remove emulation bytes. Is this nesessary? is there ever emulation
// bytes in the SPS?
var
sps0 = sps[0],// :ByteArray = sps[0];
spsCount = 0,
sps0 = this.sps[0], // :ByteArray
rbspCount = 0,
s, // :uint
e, // :uint
rbsp, // :ByteArray
o; // :uint
sps0.position = 1;
s = sps0.position;
e = sps0.bytesAvailable - 2;
rbsp = new Uint8Array(); // new ByteArray();
s = 1;
e = sps0.byteLength - 2;
rbsp = new Uint8Array(sps0.byteLength); // new ByteArray();
for (o = s ; o < e ;) {
if (3 !== sps0[o + 2]) {
......@@ -73,27 +79,32 @@
o += 2;
} else if (0 !== sps0[o + 0]) {
o += 1;
} else { // found emulation bytess
rbsp.writeShort(0x0000);
} else {
console.log('found emulation bytes');
rbsp.set([0x00, 0x00], rbspCount);
spsCount += 2;
rbspCount += 2;
if ( o > s ) {
if (o > s) {
// If there are bytes to write, write them
sps0.readBytes( rbsp, rbsp.length, o-s );
rbsp.set(sps0.subarray(0, o - s), rbspCount);
spsCount += o - s;
rbspCount += o - s;
}
// skip the emulation bytes
sps0.position += 3;
o = s = sps0.position;
o += 3;
s = o;
}
}
// copy any remaining bytes
sps0.readBytes(rbsp, rbsp.length);
sps0.position = 0;
rbsp.set(sps0.subarray(spsCount), rbspCount); // sps0.readBytes(rbsp, rbsp.length);
return rbsp;
};
//(pts:uint):FlvTag
// (pts:uint):FlvTag
this.metaDataTag = function(pts) {
var
tag = new FlvTag(FlvTag.METADATA_TAG), // :FlvTag
......@@ -144,7 +155,7 @@
expGolomb.skipUnsignedExpGolomb(); // bit_depth_luma_minus8
expGolomb.skipUnsignedExpGolomb(); // bit_depth_chroma_minus8
expGolomb.skipBits(1); // qpprime_y_zero_transform_bypass_flag
if ( expGolomb.readBoolean() ) { // seq_scaling_matrix_present_flag
if (expGolomb.readBoolean()) { // seq_scaling_matrix_present_flag
imax = (chroma_format_idc !== 3) ? 8 : 12;
for (i = 0 ; i < imax ; ++i) {
if (expGolomb.readBoolean()) { // seq_scaling_list_present_flag[ i ]
......@@ -214,22 +225,38 @@
tag.pts = pts;
tag.writeByte(0x01);// version
tag.writeByte(sps[0][1]);// profile
tag.writeByte(sps[0][2]);// compatibility
tag.writeByte(sps[0][3]);// level
tag.writeByte(this.sps[0][1]);// profile
tag.writeByte(this.sps[0][2]);// compatibility
tag.writeByte(this.sps[0][3]);// level
tag.writeByte(0xFC | 0x03); // reserved (6 bits), NULA length size - 1 (2 bits)
tag.writeByte(0xE0 | 0x01 ); // reserved (3 bits), num of SPS (5 bits)
tag.writeShort( sps[0].length ); // data of SPS
tag.writeBytes( sps[0] ); // SPS
tag.writeShort( this.sps[0].length ); // data of SPS
tag.writeBytes( this.sps[0] ); // SPS
tag.writeByte( pps.length ); // num of PPS (will there ever be more that 1 PPS?)
for (i = 0 ; i < pps.length ; ++i) {
tag.writeShort(pps[i].length); // 2 bytes for length of PPS
tag.writeBytes(pps[i]); // data of PPS
tag.writeByte( this.pps.length ); // num of PPS (will there ever be more that 1 PPS?)
for (i = 0 ; i < this.pps.length ; ++i) {
tag.writeShort(this.pps[i].length); // 2 bytes for length of PPS
tag.writeBytes(this.pps[i]); // data of PPS
}
return tag;
};
},
// incomplete, see Table 7.1 of ITU-T H.264 for 12-32
NALUnitType = {
unspecified: 0,
slice_layer_without_partitioning_rbsp_non_idr: 1,
slice_data_partition_a_layer_rbsp: 2,
slice_data_partition_b_layer_rbsp: 3,
slice_data_partition_c_layer_rbsp: 4,
slice_layer_without_partitioning_rbsp_idr: 5,
sei_rbsp: 6,
seq_parameter_set_rbsp: 7,
pic_parameter_set_rbsp: 8,
access_unit_delimiter_rbsp: 9,
end_of_seq_rbsp: 10,
end_of_stream_rbsp: 11
};
window.videojs.hls.H264Stream = function() {
......@@ -270,17 +297,18 @@
};
this.finishFrame = function() {
if (null !== h264Frame) {
console.log('finish frame');
if (h264Frame) {
// Push SPS before EVERY IDR frame fo seeking
if (newExtraData.extraDataExists()) {
oldExtraData = newExtraData;
newExtraData = new H264ExtraData();
}
if(true === h264Frame.keyFrame) {
if (h264Frame.keyFrame) {
// Push extra data on every IDR frame in case we did a stream change + seek
tags.push( oldExtraData.metaDataTag (h264Frame.pts) );
tags.push( oldExtraData.extraDataTag(h264Frame.pts) );
tags.push(oldExtraData.metaDataTag(h264Frame.pts));
tags.push(oldExtraData.extraDataTag(h264Frame.pts));
}
h264Frame.endNalUnit();
......@@ -292,31 +320,35 @@
state = 0;
};
// (pData:ByteArray, o:int, l:int):void
this.writeBytes = function(pData, o, l) {
// (data:ByteArray, o:int, l:int):void
this.writeBytes = function(data, o, l) {
var
nalUnitSize, // :uint
s, // :uint
e, // :uint
t; // :int
if (0 >= l) {
if (l <= 0) {
// data is empty so there's nothing to write
return;
}
// scan through the bytes until we find the start code (0x000001) for a
// NAL unit and then begin writing it out
switch (state) {
default:
state = 1;
break;
/* falls through */
case 0:
state = 1;
break;
/*--------------------------------------------------------------------------------------------------------------------*/
case 1: // We are looking for overlaping start codes
if (1 >= pData[o]) {
nalUnitSize = (null === h264Frame) ? 0 : h264Frame.nalUnitSize();
if (1 <= nalUnitSize && 0 === h264Frame.negIndex(1)) {
/* falls through */
case 1:
// A NAL unit may be split across two TS packets. Look back a bit to
// make sure the prefix of the start code wasn't already written out.
if (data[o] <= 1) {
nalUnitSize = h264Frame ? h264Frame.nalUnitSize() : 0;
if (nalUnitSize >= 1 && h264Frame.negIndex(1) === 0) {
// ?? ?? 00 | O[01] ?? ??
if (1 === pData[o] && 2 <= nalUnitSize && 0 === h264Frame.negIndex(2) ) {
if (1 === data[o] && 2 <= nalUnitSize && 0 === h264Frame.negIndex(2)) {
// ?? 00 00 : 01
if (3 <= nalUnitSize && 0 === h264Frame.negIndex(3)) {
h264Frame.length -= 3; // 00 00 00 : 01
......@@ -325,10 +357,10 @@
}
state = 3;
return this.writeBytes(pData, o + 1, l - 1);
return this.writeBytes(data, o + 1, l - 1);
}
if (1 < l && 0 === pData[o] && 1 === pData[o + 1]) {
if (1 < l && 0 === data[o] && 1 === data[o + 1]) {
// ?? 00 | 00 01
if (2 <= nalUnitSize && 0 === h264Frame.negIndex(2)) {
h264Frame.length -= 2; // 00 00 : 00 01
......@@ -337,71 +369,77 @@
}
state = 3;
return this.writeBytes(pData, o + 2, l - 2);
return this.writeBytes(data, o + 2, l - 2);
}
if (2 < l && 0 === pData[o] && 0 === pData[o + 1] && 1 === pData[o + 2]) {
if (2 < l && 0 === data[o] && 0 === data[o + 1] && 1 === data[o + 2]) {
// 00 | 00 00 01
h264Frame.length -= 1;
state = 3;
return this.writeBytes(pData, o + 3, l - 3);
return this.writeBytes(data, o + 3, l - 3);
}
}
}
// allow fall through if the above fails, we may end up checking a few
// bytes a second time. But that case will be VERY rare
state = 2;
break;
case 2: // Look for start codes in pData
/* falls through */
case 2: // Look for start codes in data
s = o; // s = Start
e = s + l; // e = End
for (t = e - 3 ; o < t ;) {
if (1 < pData[o + 2]) {
o += 3; // if pData[o+2] is greater than 1, there is no way a start code can begin before o+3
} else if (0 !== pData[o + 1]) {
if (1 < data[o + 2]) {
o += 3; // if data[o + 2] is greater than 1, there is no way a start code can begin before o+3
} else if (0 !== data[o + 1]) {
o += 2;
} else if (0 !== pData[o]) {
} else if (0 !== data[o]) {
o += 1;
} else {
// If we get here we have 00 00 00 or 00 00 01
if (1 === pData[o + 2]) {
if (1 === data[o + 2]) {
if (o > s) {
h264Frame.writeBytes(pData, s, o - s);
h264Frame.writeBytes(data, s, o - s);
}
state = 3; o += 3;
return this.writeBytes(pData, o, e - o);
state = 3;
o += 3;
return this.writeBytes(data, o, e - o);
}
if (4 <= e-o && 0 === pData[o + 2] && 1 === pData[o + 3]) {
if (e - o >= 4 && 0 === data[o + 2] && 1 === data[o + 3]) {
if (o > s) {
h264Frame.writeBytes(pData, s, o - s);
h264Frame.writeBytes(data, s, o - s);
}
state = 3;
o += 4;
return this.writeBytes(pData, o, e - o);
return this.writeBytes(data, o, e - o);
}
// We are at the end of the buffer, or we have 3 NULLS followed by something that is not a 1, eaither way we can step forward by at least 3
// We are at the end of the buffer, or we have 3 NULLS followed by
// something that is not a 1, either way we can step forward by at
// least 3
o += 3;
}
}
// We did not find any start codes. Try again next packet
state = 1;
h264Frame.writeBytes( pData, s, l );
h264Frame.writeBytes(data, s, l);
return;
/*--------------------------------------------------------------------------------------------------------------------*/
case 3: // The next byte is the first byte of a NAL Unit
if (null !== h264Frame) {
case 3:
// The next byte is the first byte of a NAL Unit
if (h264Frame) {
// we've come to a new NAL unit so finish up the one we've been
// working on
switch (nalUnitType) {
// We are still operating on the previous NAL Unit
case 7:
h264Frame.endNalUnit(newExtraData.addSPS());
case NALUnitType.seq_parameter_set_rbsp:
h264Frame.endNalUnit(newExtraData.sps);
break;
case 8:
h264Frame.endNalUnit(newExtraData.addPPS());
case NALUnitType.pic_parameter_set_rbsp:
h264Frame.endNalUnit(newExtraData.pps);
break;
case 5:
case NALUnitType.slice_layer_without_partitioning_rbsp_idr:
h264Frame.keyFrame = true;
h264Frame.endNalUnit();
break;
......@@ -411,14 +449,14 @@
}
}
nalUnitType = pData[o] & 0x1F;
if ( null != h264Frame && 9 === nalUnitType ) {
// setup to begin processing the new NAL unit
nalUnitType = data[o] & 0x1F;
if (h264Frame && 9 === nalUnitType) {
this.finishFrame(); // We are starting a new access unit. Flush the previous one
}
// finishFrame may render h264Frame null, so we must test again
if ( null === h264Frame )
{
if (!h264Frame) {
h264Frame = new FlvTag(FlvTag.VIDEO_TAG);
h264Frame.pts = next_pts;
h264Frame.dts = next_dts;
......@@ -426,7 +464,7 @@
h264Frame.startNalUnit();
state = 2; // We know there will not be an overlapping start code, so we can skip that test
return this.writeBytes(pData, o, l);
return this.writeBytes(data, o, l);
/*--------------------------------------------------------------------------------------------------------------------*/
} // switch
};
......
......@@ -187,7 +187,7 @@
}
// attempt to parse a m2ts packet
if (parseTSPacket(data.subarray(dataPosition, m2tsPacketSize))) {
if (parseTSPacket(data.subarray(dataPosition, dataPosition + m2tsPacketSize))) {
dataPosition += m2tsPacketSize;
} else {
// If there was an error parsing a TS packet. it could be
......@@ -203,7 +203,7 @@
// packet!
parseTSPacket = function(data) { // :ByteArray):Boolean {
var
s = data.position, //:uint
s = 0, //:uint
o = s, // :uint
e = o + m2tsPacketSize, // :uint
......@@ -211,10 +211,10 @@
// parseSegmentBinaryData()
// Payload Unit Start Indicator
pusi = !!(data[o+1] & 0x40),
pusi = !!(data[o + 1] & 0x40), // mask: 0100 0000
// PacketId
pid = (data[o + 1] & 0x1F) << 8 | data[o+2],
pid = (data[o + 1] & 0x1F) << 8 | data[o + 2], // mask: 0001 1111
afflag = (data[o + 3] & 0x30 ) >>> 4,
aflen, // :uint
......@@ -259,7 +259,7 @@
console.assert(0x00 === patTableId, 'patTableId should be 0x00');
patCurrentNextIndicator = !!(data[o+5] & 0x01);
patCurrentNextIndicator = !!(data[o + 5] & 0x01);
if (patCurrentNextIndicator) {
patSectionLength = (data[o + 1] & 0x0F) << 8 | data[o + 2];
o += 8; // skip past PSI header
......@@ -280,10 +280,10 @@
} else if (videoPid === pid || audioPid === pid) {
if (pusi) {
// comment out for speed
// if( 0x00 != data[o+0] || 0x00 != data[o+1] || 0x01 != data[o+2] )
// {// look for PES start code
// throw new Error("PES did not begin with start code");
// }
if (0x00 !== data[o + 0] || 0x00 !== data[o + 1] || 0x01 !== data[o + 2]) {
// look for PES start code
throw new Error("PES did not begin with start code");
}
// var sid:int = data[o+3]; // StreamID
pesPacketSize = (data[o + 4] << 8) | data[o + 5];
......@@ -334,14 +334,14 @@
}
if (audioPid === pid) {
aacStream.writeBytes(data,o,e-o);
aacStream.writeBytes(data, o, e - o);
} else if (videoPid === pid) {
h264Stream.writeBytes(data,o,e-o);
h264Stream.writeBytes(data, o, e - o);
}
} else if (pmtPid === pid) {
// TODO sanity check data[o]
// if pusi is set we must skip X bytes (PSI pointer field)
o += ( pusi ? 1 + data[o] : 0 );
o += (pusi ? 1 + data[o] : 0);
pmtTableId = data[o];
console.assert(0x02 === pmtTableId);
......@@ -349,7 +349,7 @@
pmtCurrentNextIndicator = !!(data[o + 5] & 0x01);
if (pmtCurrentNextIndicator) {
audioPid = videoPid = 0;
pmtSectionLength = (data[o + 1] & 0x0F ) << 8 | data[o + 2];
pmtSectionLength = (data[o + 1] & 0x0F) << 8 | data[o + 2];
// skip CRC and PSI data we dont care about
pmtSectionLength -= 13;
......@@ -357,7 +357,7 @@
while (0 < pmtSectionLength) {
streamType = data[o + 0];
elementaryPID = (data[o + 1] & 0x1F) << 8 | data[o + 2];
ESInfolength = (data[o + 3] & 0x0F ) << 8 | data[o + 4];
ESInfolength = (data[o + 3] & 0x0F) << 8 | data[o + 4];
o += 5 + ESInfolength;
pmtSectionLength -= 5 + ESInfolength;
......
No preview for this file type
No preview for this file type
This diff could not be displayed because it is too large.
......@@ -39,9 +39,10 @@
<!-- HLS plugin -->
<script src="../src/video-js-hls.js"></script>
<script src="../src/flv-tag.js"></script>
<script src="../src/exp-golomb.js"></script>
<script src="../src/h264-stream.js"></script>
<script src="../src/aac-stream.js"></script>
<script src="../src/flv-tag.js"></script>
<script src="../src/segment-parser.js"></script>
<!-- an example MPEG2-TS segment -->
<script src="tsSegment.js"></script>
......
......@@ -19,7 +19,13 @@
notStrictEqual(actual, expected, [message])
throws(block, [expected], [message])
*/
var parser;
var
parser,
expectedHeader = [
0x46, 0x4c, 0x56, 0x01, 0x05, 0x00, 0x00, 0x00,
0x09, 0x00, 0x00, 0x00, 0x00
];
module('environment');
......@@ -35,17 +41,20 @@
});
test('creates an flv header', function() {
var header = parser.getFlvHeader();
var header = Array.prototype.slice.call(parser.getFlvHeader());
ok(header, 'the header is truthy');
equal(9 + 4, header.byteLength, 'the header length is correct');
equal(9 + 4, header.length, 'the header length is correct');
equal(header[0], 'F'.charCodeAt(0), 'the signature is correct');
equal(header[1], 'L'.charCodeAt(0), 'the signature is correct');
equal(header[2], 'V'.charCodeAt(0), 'the signature is correct');
deepEqual(expectedHeader, header, 'the rest of the header is correct');
});
test('parses the first bipbop segment', function() {
var tag, bytes;
parser.parseSegmentBinaryData(window.testSegment);
ok(parser.tagsAvailable(), 'tags should be available');
ok(parser.tagsAvailable(), 'tags are available');
});
})(this);
......