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);
......
......@@ -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);
......