63c045e6 by David LaPalomento

@gkatsev ensure segments without an initial IDR are not displayed in 4:3 initially.

Closes #272.

Squashed commit of the following:

commit de597c3ee10075cece7036aeaacedb74f33d9ef5
Author: David LaPalomento <dlapalomento@gmail.com>
Date:   Fri May 22 14:02:27 2015 -0400

    Remove vim swap file
    Ignore them in version control in the future.

commit fb189ba16330311371bb6b9cf1bb6248005e10b7
Author: Gary Katsevman <git@gkatsev.com>
Date:   Wed May 20 18:30:07 2015 -0400

    Default 'nextFrameKeyFrame' to false.

commit 3788ad0607fabb2cde1a1d5eea4163ceafb5fab5
Author: Gary Katsevman <git@gkatsev.com>
Date:   Wed May 20 18:24:16 2015 -0400

    setNextFrameKeyFrame

commit 6944234afd99285a448a9cf3adbcaf1eb26cfe33
Author: Gary Katsevman <git@gkatsev.com>
Date:   Wed May 20 18:06:26 2015 -0400

    Another camelcase usage

commit da6e32a4d9024d859ca58bc1d4bc3fcbe1de7a33
Author: Gary Katsevman <git@gkatsev.com>
Date:   Wed May 20 18:05:30 2015 -0400

    Dont parse unused fields. Use camel case names

commit fb7990b6606501a549d7448de6eec38784695896
Author: Gary Katsevman <git@gkatsev.com>
Date:   Wed May 20 16:28:28 2015 -0400

    ifs should have curly braces

commit 92e40c642270e4ce4652a874073a66876312072f
Author: Gary Katsevman <git@gkatsev.com>
Date:   Wed May 20 15:58:18 2015 -0400

    Adaptation Field vars. Use Random Access Indicator
    The Random Access Indicator tells us whether something is a keyframe.
    Set the stream's frame's keyFrame property to true if the Random Access
    Indicator is set.

commit 047a6d7771cd2d9c324b6da9bd76d4b6aeeadaf0
Author: Gary Katsevman <git@gkatsev.com>
Date:   Mon May 18 16:56:23 2015 -0400

    Set up the test with exact conditions

commit e2f8b18656d4cdb830cd23013fe7e5fcfa599ca4
Author: Gary Katsevman <git@gkatsev.com>
Date:   Mon May 18 16:56:01 2015 -0400

    Restore stubbed out methods

commit ff5f3b5fdb9dbe5e34447f8a4680c294163e0c46
Author: Gary Katsevman <git@gkatsev.com>
Date:   Mon May 18 16:50:22 2015 -0400

    prototypeify h264-stream

commit 7ee359d582550474a33730547742ce3d656dad52
Author: Gary Katsevman <git@gkatsev.com>
Date:   Mon May 18 16:28:08 2015 -0400

    Initial test for metadata

commit 0e3a961c7594d67280958cf21330fb347e1dda0a
Author: Gary Katsevman <git@gkatsev.com>
Date:   Mon May 18 16:21:55 2015 -0400

    Fix test properly

commit 7e0de308227a4373c6abd71769ed474e59c7edeb
Author: Gary Katsevman <git@gkatsev.com>
Date:   Mon May 18 16:14:57 2015 -0400

    Move h264-extradata into separate file

commit 66a676c234e6e087694611d710cad6414cbc984c
Author: Gary Katsevman <git@gkatsev.com>
Date:   Mon May 18 15:52:08 2015 -0400

    prototypeify H264ExtraData
1 parent 004e13d8
......@@ -6,4 +6,4 @@ dist/*
*.ipr
*.iws
*.swp
tmp/**
\ No newline at end of file
tmp/**.*.swo
......
......@@ -5,6 +5,7 @@ CHANGELOG
* @dmlap use contribflow to manage contributions ([view](https://github.com/videojs/videojs-contrib-hls/pull/275))
* @dmlap add a contribflow configuration ([view](https://github.com/videojs/videojs-contrib-hls/pull/276))
* @ntadej Do not unnecessarily reset to the live point when refreshing playlists. Clean up playlist loader timeouts. ([view](https://github.com/videojs/videojs-contrib-hls/pull/274))
* @gkatsev ensure segments without an initial IDR are not displayed in 4
--------------------
......
......@@ -27,6 +27,7 @@ module.exports = function(grunt) {
'src/stream.js',
'src/flv-tag.js',
'src/exp-golomb.js',
'src/h264-extradata.js',
'src/h264-stream.js',
'src/aac-stream.js',
'src/metadata-stream.js',
......
......@@ -20,6 +20,7 @@
<script src="src/flv-tag.js"></script>
<script src="src/stream.js"></script>
<script src="src/exp-golomb.js"></script>
<script src="src/h264-extradata.js"></script>
<script src="src/h264-stream.js"></script>
<script src="src/aac-stream.js"></script>
<script src="src/metadata-stream.js"></script>
......
(function() {
var
H264ExtraData,
ExpGolomb = window.videojs.Hls.ExpGolomb,
FlvTag = window.videojs.Hls.FlvTag;
window.videojs.Hls.H264ExtraData = H264ExtraData = function() {
this.sps = []; // :Array
this.pps = []; // :Array
};
H264ExtraData.prototype.extraDataExists = function() { // :Boolean
return this.sps.length > 0;
};
// (sizeOfScalingList:int, expGolomb:ExpGolomb):void
H264ExtraData.prototype.scaling_list = function(sizeOfScalingList, expGolomb) {
var
lastScale = 8, // :int
nextScale = 8, // :int
j,
delta_scale; // :int
for (j = 0; j < sizeOfScalingList; ++j) {
if (0 !== nextScale) {
delta_scale = expGolomb.readExpGolomb();
nextScale = (lastScale + delta_scale + 256) % 256;
//useDefaultScalingMatrixFlag = ( j = = 0 && nextScale = = 0 )
}
lastScale = (nextScale === 0) ? lastScale : nextScale;
// scalingList[ j ] = ( nextScale == 0 ) ? lastScale : nextScale;
// lastScale = scalingList[ j ]
}
};
/**
* RBSP: raw bit-stream payload. The actual encoded video data.
*
* SPS: sequence parameter set. Part of the RBSP. Metadata to be applied
* to a complete video sequence, like width and height.
*/
H264ExtraData.prototype.getSps0Rbsp = function() { // :ByteArray
var
sps = this.sps[0],
offset = 1,
start = 1,
written = 0,
end = sps.byteLength - 2,
result = new Uint8Array(sps.byteLength);
// In order to prevent 0x0000 01 from being interpreted as a
// NAL start code, occurences of that byte sequence in the
// RBSP are escaped with an "emulation byte". That turns
// sequences of 0x0000 01 into 0x0000 0301. When interpreting
// a NAL payload, they must be filtered back out.
while (offset < end) {
if (sps[offset] === 0x00 &&
sps[offset + 1] === 0x00 &&
sps[offset + 2] === 0x03) {
result.set(sps.subarray(start, offset + 1), written);
written += offset + 1 - start;
start = offset + 3;
}
offset++;
}
result.set(sps.subarray(start), written);
return result.subarray(0, written + (sps.byteLength - start));
};
// (pts:uint):FlvTag
H264ExtraData.prototype.metaDataTag = function(pts) {
var
tag = new FlvTag(FlvTag.METADATA_TAG), // :FlvTag
expGolomb, // :ExpGolomb
profile_idc, // :int
chroma_format_idc, // :int
imax, // :int
i, // :int
pic_order_cnt_type, // :int
num_ref_frames_in_pic_order_cnt_cycle, // :uint
pic_width_in_mbs_minus1, // :int
pic_height_in_map_units_minus1, // :int
frame_mbs_only_flag, // :int
frame_cropping_flag, // :Boolean
frame_crop_left_offset = 0, // :int
frame_crop_right_offset = 0, // :int
frame_crop_top_offset = 0, // :int
frame_crop_bottom_offset = 0, // :int
width,
height;
tag.dts = pts;
tag.pts = pts;
expGolomb = new ExpGolomb(this.getSps0Rbsp());
// :int = expGolomb.readUnsignedByte(); // profile_idc u(8)
profile_idc = expGolomb.readUnsignedByte();
// constraint_set[0-5]_flag, u(1), reserved_zero_2bits u(2), level_idc u(8)
expGolomb.skipBits(16);
// seq_parameter_set_id
expGolomb.skipUnsignedExpGolomb();
if (profile_idc === 100 ||
profile_idc === 110 ||
profile_idc === 122 ||
profile_idc === 244 ||
profile_idc === 44 ||
profile_idc === 83 ||
profile_idc === 86 ||
profile_idc === 118 ||
profile_idc === 128) {
chroma_format_idc = expGolomb.readUnsignedExpGolomb();
if (3 === chroma_format_idc) {
expGolomb.skipBits(1); // separate_colour_plane_flag
}
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
imax = (chroma_format_idc !== 3) ? 8 : 12;
for (i = 0 ; i < imax ; ++i) {
if (expGolomb.readBoolean()) { // seq_scaling_list_present_flag[ i ]
if (i < 6) {
this.scaling_list(16, expGolomb);
} else {
this.scaling_list(64, expGolomb);
}
}
}
}
}
expGolomb.skipUnsignedExpGolomb(); // log2_max_frame_num_minus4
pic_order_cnt_type = expGolomb.readUnsignedExpGolomb();
if ( 0 === pic_order_cnt_type ) {
expGolomb.readUnsignedExpGolomb(); //log2_max_pic_order_cnt_lsb_minus4
} else if ( 1 === pic_order_cnt_type ) {
expGolomb.skipBits(1); // delta_pic_order_always_zero_flag
expGolomb.skipExpGolomb(); // offset_for_non_ref_pic
expGolomb.skipExpGolomb(); // offset_for_top_to_bottom_field
num_ref_frames_in_pic_order_cnt_cycle = expGolomb.readUnsignedExpGolomb();
for(i = 0 ; i < num_ref_frames_in_pic_order_cnt_cycle ; ++i) {
expGolomb.skipExpGolomb(); // offset_for_ref_frame[ i ]
}
}
expGolomb.skipUnsignedExpGolomb(); // max_num_ref_frames
expGolomb.skipBits(1); // gaps_in_frame_num_value_allowed_flag
pic_width_in_mbs_minus1 = expGolomb.readUnsignedExpGolomb();
pic_height_in_map_units_minus1 = expGolomb.readUnsignedExpGolomb();
frame_mbs_only_flag = expGolomb.readBits(1);
if (0 === frame_mbs_only_flag) {
expGolomb.skipBits(1); // mb_adaptive_frame_field_flag
}
expGolomb.skipBits(1); // direct_8x8_inference_flag
frame_cropping_flag = expGolomb.readBoolean();
if (frame_cropping_flag) {
frame_crop_left_offset = expGolomb.readUnsignedExpGolomb();
frame_crop_right_offset = expGolomb.readUnsignedExpGolomb();
frame_crop_top_offset = expGolomb.readUnsignedExpGolomb();
frame_crop_bottom_offset = expGolomb.readUnsignedExpGolomb();
}
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);
tag.writeMetaDataDouble("videocodecid", 7);
tag.writeMetaDataDouble("width", width);
tag.writeMetaDataDouble("height", height);
// tag.writeMetaDataDouble("videodatarate", 0 );
// tag.writeMetaDataDouble("framerate", 0);
return tag;
};
// (pts:uint):FlvTag
H264ExtraData.prototype.extraDataTag = function(pts) {
var
i,
tag = new FlvTag(FlvTag.VIDEO_TAG, true);
tag.dts = pts;
tag.pts = pts;
tag.writeByte(0x01);// version
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( this.sps[0].length ); // data of SPS
tag.writeBytes( this.sps[0] ); // SPS
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;
};
})();
(function(window) {
var
ExpGolomb = window.videojs.Hls.ExpGolomb,
FlvTag = window.videojs.Hls.FlvTag,
H264ExtraData = function() {
this.sps = []; // :Array
this.pps = []; // :Array
this.extraDataExists = function() { // :Boolean
return this.sps.length > 0;
};
// (sizeOfScalingList:int, expGolomb:ExpGolomb):void
this.scaling_list = function(sizeOfScalingList, expGolomb) {
var
lastScale = 8, // :int
nextScale = 8, // :int
j,
delta_scale; // :int
for (j = 0; j < sizeOfScalingList; ++j) {
if (0 !== nextScale) {
delta_scale = expGolomb.readExpGolomb();
nextScale = (lastScale + delta_scale + 256) % 256;
//useDefaultScalingMatrixFlag = ( j = = 0 && nextScale = = 0 )
}
lastScale = (nextScale === 0) ? lastScale : nextScale;
// scalingList[ j ] = ( nextScale == 0 ) ? lastScale : nextScale;
// lastScale = scalingList[ j ]
}
};
/**
* RBSP: raw bit-stream payload. The actual encoded video data.
*
* SPS: sequence parameter set. Part of the RBSP. Metadata to be applied
* to a complete video sequence, like width and height.
*/
this.getSps0Rbsp = function() { // :ByteArray
var
sps = this.sps[0],
offset = 1,
start = 1,
written = 0,
end = sps.byteLength - 2,
result = new Uint8Array(sps.byteLength);
// In order to prevent 0x0000 01 from being interpreted as a
// NAL start code, occurences of that byte sequence in the
// RBSP are escaped with an "emulation byte". That turns
// sequences of 0x0000 01 into 0x0000 0301. When interpreting
// a NAL payload, they must be filtered back out.
while (offset < end) {
if (sps[offset] === 0x00 &&
sps[offset + 1] === 0x00 &&
sps[offset + 2] === 0x03) {
result.set(sps.subarray(start, offset + 1), written);
written += offset + 1 - start;
start = offset + 3;
}
offset++;
}
result.set(sps.subarray(start), written);
return result.subarray(0, written + (sps.byteLength - start));
};
// (pts:uint):FlvTag
this.metaDataTag = function(pts) {
var
tag = new FlvTag(FlvTag.METADATA_TAG), // :FlvTag
expGolomb, // :ExpGolomb
profile_idc, // :int
chroma_format_idc, // :int
imax, // :int
i, // :int
pic_order_cnt_type, // :int
num_ref_frames_in_pic_order_cnt_cycle, // :uint
pic_width_in_mbs_minus1, // :int
pic_height_in_map_units_minus1, // :int
frame_mbs_only_flag, // :int
frame_cropping_flag, // :Boolean
frame_crop_left_offset = 0, // :int
frame_crop_right_offset = 0, // :int
frame_crop_top_offset = 0, // :int
frame_crop_bottom_offset = 0, // :int
width,
height;
tag.dts = pts;
tag.pts = pts;
expGolomb = new ExpGolomb(this.getSps0Rbsp());
// :int = expGolomb.readUnsignedByte(); // profile_idc u(8)
profile_idc = expGolomb.readUnsignedByte();
// constraint_set[0-5]_flag, u(1), reserved_zero_2bits u(2), level_idc u(8)
expGolomb.skipBits(16);
// seq_parameter_set_id
expGolomb.skipUnsignedExpGolomb();
if (profile_idc === 100 ||
profile_idc === 110 ||
profile_idc === 122 ||
profile_idc === 244 ||
profile_idc === 44 ||
profile_idc === 83 ||
profile_idc === 86 ||
profile_idc === 118 ||
profile_idc === 128) {
chroma_format_idc = expGolomb.readUnsignedExpGolomb();
if (3 === chroma_format_idc) {
expGolomb.skipBits(1); // separate_colour_plane_flag
}
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
imax = (chroma_format_idc !== 3) ? 8 : 12;
for (i = 0 ; i < imax ; ++i) {
if (expGolomb.readBoolean()) { // seq_scaling_list_present_flag[ i ]
if (i < 6) {
this.scaling_list(16, expGolomb);
} else {
this.scaling_list(64, expGolomb);
}
}
}
}
}
expGolomb.skipUnsignedExpGolomb(); // log2_max_frame_num_minus4
pic_order_cnt_type = expGolomb.readUnsignedExpGolomb();
if ( 0 === pic_order_cnt_type ) {
expGolomb.readUnsignedExpGolomb(); //log2_max_pic_order_cnt_lsb_minus4
} else if ( 1 === pic_order_cnt_type ) {
expGolomb.skipBits(1); // delta_pic_order_always_zero_flag
expGolomb.skipExpGolomb(); // offset_for_non_ref_pic
expGolomb.skipExpGolomb(); // offset_for_top_to_bottom_field
num_ref_frames_in_pic_order_cnt_cycle = expGolomb.readUnsignedExpGolomb();
for(i = 0 ; i < num_ref_frames_in_pic_order_cnt_cycle ; ++i) {
expGolomb.skipExpGolomb(); // offset_for_ref_frame[ i ]
}
}
expGolomb.skipUnsignedExpGolomb(); // max_num_ref_frames
expGolomb.skipBits(1); // gaps_in_frame_num_value_allowed_flag
pic_width_in_mbs_minus1 = expGolomb.readUnsignedExpGolomb();
pic_height_in_map_units_minus1 = expGolomb.readUnsignedExpGolomb();
frame_mbs_only_flag = expGolomb.readBits(1);
if (0 === frame_mbs_only_flag) {
expGolomb.skipBits(1); // mb_adaptive_frame_field_flag
}
expGolomb.skipBits(1); // direct_8x8_inference_flag
frame_cropping_flag = expGolomb.readBoolean();
if (frame_cropping_flag) {
frame_crop_left_offset = expGolomb.readUnsignedExpGolomb();
frame_crop_right_offset = expGolomb.readUnsignedExpGolomb();
frame_crop_top_offset = expGolomb.readUnsignedExpGolomb();
frame_crop_bottom_offset = expGolomb.readUnsignedExpGolomb();
}
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);
tag.writeMetaDataDouble("videocodecid", 7);
tag.writeMetaDataDouble("width", width);
tag.writeMetaDataDouble("height", height);
// tag.writeMetaDataDouble("videodatarate", 0 );
// tag.writeMetaDataDouble("framerate", 0);
return tag;
};
// (pts:uint):FlvTag
this.extraDataTag = function(pts) {
var
i,
tag = new FlvTag(FlvTag.VIDEO_TAG, true);
tag.dts = pts;
tag.pts = pts;
tag.writeByte(0x01);// version
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( this.sps[0].length ); // data of SPS
tag.writeBytes( this.sps[0] ); // SPS
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;
};
},
H264ExtraData = window.videojs.Hls.H264ExtraData,
H264Stream,
NALUnitType;
/**
......@@ -241,235 +34,246 @@
end_of_stream_rbsp: 11
};
window.videojs.Hls.H264Stream = function() {
var
next_pts, // :uint;
next_dts, // :uint;
pts_offset, // :int
window.videojs.Hls.H264Stream = H264Stream = function() {
this._next_pts = 0; // :uint;
this._next_dts = 0; // :uint;
this._pts_offset = 0; // :int
this._h264Frame = null; // :FlvTag
h264Frame, // :FlvTag
this._oldExtraData = new H264ExtraData(); // :H264ExtraData
this._newExtraData = new H264ExtraData(); // :H264ExtraData
oldExtraData = new H264ExtraData(), // :H264ExtraData
newExtraData = new H264ExtraData(), // :H264ExtraData
this._nalUnitType = -1; // :int
nalUnitType = -1, // :int
this._state = 0; // :uint;
state; // :uint;
this._nextFrameKeyFrame = false;
this.tags = [];
//(pts:uint):void
this.setTimeStampOffset = function(pts) {
pts_offset = pts;
};
};
//(pts:uint):void
H264Stream.prototype.setTimeStampOffset = function(pts) {
this._pts_offset = pts;
};
//(pts:uint, dts:uint, dataAligned:Boolean):void
this.setNextTimeStamp = function(pts, dts, dataAligned) {
// We could end up with a DTS less than 0 here. We need to deal with that!
next_pts = pts - pts_offset;
next_dts = dts - pts_offset;
//(pts:uint, dts:uint, dataAligned:Boolean):void
H264Stream.prototype.setNextTimeStamp = function(pts, dts, dataAligned) {
// We could end up with a DTS less than 0 here. We need to deal with that!
this._next_pts = pts - this._pts_offset;
this._next_dts = dts - this._pts_offset;
// If data is aligned, flush all internal buffers
if (dataAligned) {
this.finishFrame();
// If data is aligned, flush all internal buffers
if (dataAligned) {
this.finishFrame();
}
};
H264Stream.prototype.finishFrame = function() {
if (this._h264Frame) {
// Push SPS before EVERY IDR frame for seeking
if (this._newExtraData.extraDataExists()) {
this._oldExtraData = this._newExtraData;
this._newExtraData = new H264ExtraData();
}
};
this.finishFrame = function() {
if (h264Frame) {
// Push SPS before EVERY IDR frame for seeking
if (newExtraData.extraDataExists()) {
oldExtraData = newExtraData;
newExtraData = new H264ExtraData();
}
// Check if keyframe and the length of tags.
// This makes sure we write metadata on the first frame of a segment.
if (h264Frame.keyFrame || this.tags.length === 0) {
// Push extra data on every IDR frame in case we did a stream change + seek
this.tags.push(oldExtraData.metaDataTag(h264Frame.pts));
this.tags.push(oldExtraData.extraDataTag(h264Frame.pts));
}
// Check if keyframe and the length of tags.
// This makes sure we write metadata on the first frame of a segment.
if (this._h264Frame.keyFrame || this.tags.length === 0) {
// Push extra data on every IDR frame in case we did a stream change + seek
this.tags.push(this._oldExtraData.metaDataTag(this._h264Frame.pts));
this.tags.push(this._oldExtraData.extraDataTag(this._h264Frame.pts));
}
h264Frame.endNalUnit();
this.tags.push(h264Frame);
this._h264Frame.endNalUnit();
this.tags.push(this._h264Frame);
}
}
h264Frame = null;
nalUnitType = -1;
state = 0;
};
// (data:ByteArray, o:int, l:int):void
this.writeBytes = function(data, offset, length) {
var
nalUnitSize, // :uint
start, // :uint
end, // :uint
t; // :int
// default argument values
offset = offset || 0;
length = length || 0;
if (length <= 0) {
// data is empty so there's nothing to write
return;
}
this._h264Frame = null;
this._nalUnitType = -1;
this._state = 0;
};
// scan through the bytes until we find the start code (0x000001) for a
// NAL unit and then begin writing it out
// strip NAL start codes as we go
switch (state) {
default:
/* falls through */
case 0:
state = 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[offset] <= 1) {
nalUnitSize = h264Frame ? h264Frame.nalUnitSize() : 0;
if (nalUnitSize >= 1 && h264Frame.negIndex(1) === 0) {
// ?? ?? 00 | O[01] ?? ??
if (data[offset] === 1 &&
nalUnitSize >= 2 &&
h264Frame.negIndex(2) === 0) {
// ?? 00 00 : 01
if (3 <= nalUnitSize && 0 === h264Frame.negIndex(3)) {
h264Frame.length -= 3; // 00 00 00 : 01
} else {
h264Frame.length -= 2; // 00 00 : 01
}
state = 3;
return this.writeBytes(data, offset + 1, length - 1);
}
if (length > 1 && data[offset] === 0 && data[offset + 1] === 1) {
// ?? 00 | 00 01
if (nalUnitSize >= 2 && h264Frame.negIndex(2) === 0) {
h264Frame.length -= 2; // 00 00 : 00 01
} else {
h264Frame.length -= 1; // 00 : 00 01
}
H264Stream.prototype.setNextFrameKeyFrame = function() {
this._nextFrameKeyFrame = true;
};
state = 3;
return this.writeBytes(data, offset + 2, length - 2);
// (data:ByteArray, o:int, l:int):void
H264Stream.prototype.writeBytes = function(data, offset, length) {
var
nalUnitSize, // :uint
start, // :uint
end, // :uint
t; // :int
// default argument values
offset = offset || 0;
length = length || 0;
if (length <= 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
// strip NAL start codes as we go
switch (this._state) {
default:
/* falls through */
case 0:
this._state = 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[offset] <= 1) {
nalUnitSize = this._h264Frame ? this._h264Frame.nalUnitSize() : 0;
if (nalUnitSize >= 1 && this._h264Frame.negIndex(1) === 0) {
// ?? ?? 00 | O[01] ?? ??
if (data[offset] === 1 &&
nalUnitSize >= 2 &&
this._h264Frame.negIndex(2) === 0) {
// ?? 00 00 : 01
if (3 <= nalUnitSize && 0 === this._h264Frame.negIndex(3)) {
this._h264Frame.length -= 3; // 00 00 00 : 01
} else {
this._h264Frame.length -= 2; // 00 00 : 01
}
if (length > 2 &&
data[offset] === 0 &&
data[offset + 1] === 0 &&
data[offset + 2] === 1) {
// 00 : 00 00 01
// h264Frame.length -= 1;
state = 3;
return this.writeBytes(data, offset + 3, length - 3);
this._state = 3;
return this.writeBytes(data, offset + 1, length - 1);
}
if (length > 1 && data[offset] === 0 && data[offset + 1] === 1) {
// ?? 00 | 00 01
if (nalUnitSize >= 2 && this._h264Frame.negIndex(2) === 0) {
this._h264Frame.length -= 2; // 00 00 : 00 01
} else {
this._h264Frame.length -= 1; // 00 : 00 01
}
this._state = 3;
return this.writeBytes(data, offset + 2, length - 2);
}
if (length > 2 &&
data[offset] === 0 &&
data[offset + 1] === 0 &&
data[offset + 2] === 1) {
// 00 : 00 00 01
// this._h264Frame.length -= 1;
this._state = 3;
return this.writeBytes(data, offset + 3, length - 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;
/* falls through */
case 2:
// Look for start codes in the data from the current offset forward
start = offset;
end = start + length;
for (t = end - 3; offset < t;) {
if (data[offset + 2] > 1) {
// if data[offset + 2] is greater than 1, there is no way a start
// code can begin before offset + 3
offset += 3;
} else if (data[offset + 1] !== 0) {
offset += 2;
} else if (data[offset] !== 0) {
offset += 1;
} else {
// If we get here we have 00 00 00 or 00 00 01
if (data[offset + 2] === 1) {
if (offset > start) {
h264Frame.writeBytes(data, start, offset - start);
}
state = 3;
offset += 3;
return this.writeBytes(data, offset, end - offset);
}
// 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
this._state = 2;
/* falls through */
case 2:
// Look for start codes in the data from the current offset forward
start = offset;
end = start + length;
for (t = end - 3; offset < t;) {
if (data[offset + 2] > 1) {
// if data[offset + 2] is greater than 1, there is no way a start
// code can begin before offset + 3
offset += 3;
} else if (data[offset + 1] !== 0) {
offset += 2;
} else if (data[offset] !== 0) {
offset += 1;
} else {
// If we get here we have 00 00 00 or 00 00 01
if (data[offset + 2] === 1) {
if (offset > start) {
this._h264Frame.writeBytes(data, start, offset - start);
}
this._state = 3;
offset += 3;
return this.writeBytes(data, offset, end - offset);
}
if (end - offset >= 4 &&
data[offset + 2] === 0 &&
data[offset + 3] === 1) {
if (offset > start) {
h264Frame.writeBytes(data, start, offset - start);
}
state = 3;
offset += 4;
return this.writeBytes(data, offset, end - offset);
if (end - offset >= 4 &&
data[offset + 2] === 0 &&
data[offset + 3] === 1) {
if (offset > start) {
this._h264Frame.writeBytes(data, start, offset - start);
}
// 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
offset += 3;
this._state = 3;
offset += 4;
return this.writeBytes(data, offset, end - offset);
}
// 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
offset += 3;
}
}
// We did not find any start codes. Try again next packet
state = 1;
if (h264Frame) {
h264Frame.writeBytes(data, start, length);
// We did not find any start codes. Try again next packet
this._state = 1;
if (this._h264Frame) {
this._h264Frame.writeBytes(data, start, length);
}
return;
case 3:
// The next byte is the first byte of a NAL Unit
if (this._h264Frame) {
// we've come to a new NAL unit so finish up the one we've been
// working on
switch (this._nalUnitType) {
case NALUnitType.seq_parameter_set_rbsp:
this._h264Frame.endNalUnit(this._newExtraData.sps);
break;
case NALUnitType.pic_parameter_set_rbsp:
this._h264Frame.endNalUnit(this._newExtraData.pps);
break;
case NALUnitType.slice_layer_without_partitioning_rbsp_idr:
this._h264Frame.endNalUnit();
break;
default:
this._h264Frame.endNalUnit();
break;
}
return;
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) {
case NALUnitType.seq_parameter_set_rbsp:
h264Frame.endNalUnit(newExtraData.sps);
break;
case NALUnitType.pic_parameter_set_rbsp:
h264Frame.endNalUnit(newExtraData.pps);
break;
case NALUnitType.slice_layer_without_partitioning_rbsp_idr:
h264Frame.endNalUnit();
break;
default:
h264Frame.endNalUnit();
break;
}
// setup to begin processing the new NAL unit
this._nalUnitType = data[offset] & 0x1F;
if (this._h264Frame) {
if (this._nalUnitType === NALUnitType.access_unit_delimiter_rbsp) {
// starting a new access unit, flush the previous one
this.finishFrame();
} else if (this._nalUnitType === NALUnitType.slice_layer_without_partitioning_rbsp_idr) {
this._h264Frame.keyFrame = true;
}
}
}
// setup to begin processing the new NAL unit
nalUnitType = data[offset] & 0x1F;
if (h264Frame) {
if (nalUnitType === NALUnitType.access_unit_delimiter_rbsp) {
// starting a new access unit, flush the previous one
this.finishFrame();
} else if (nalUnitType === NALUnitType.slice_layer_without_partitioning_rbsp_idr) {
h264Frame.keyFrame = true;
}
}
// finishFrame may render this._h264Frame null, so we must test again
if (!this._h264Frame) {
this._h264Frame = new FlvTag(FlvTag.VIDEO_TAG);
this._h264Frame.pts = this._next_pts;
this._h264Frame.dts = this._next_dts;
// finishFrame may render h264Frame null, so we must test again
if (!h264Frame) {
h264Frame = new FlvTag(FlvTag.VIDEO_TAG);
h264Frame.pts = next_pts;
h264Frame.dts = next_dts;
if (this._nextFrameKeyFrame) {
this._h264Frame.keyFrame = true;
this._nextFrameKeyFrame = false;
}
}
h264Frame.startNalUnit();
// We know there will not be an overlapping start code, so we can skip
// that test
state = 2;
return this.writeBytes(data, offset, length);
} // switch
};
this._h264Frame.startNalUnit();
// We know there will not be an overlapping start code, so we can skip
// that test
this._state = 2;
return this.writeBytes(data, offset, length);
} // switch
};
})(this);
......
......@@ -214,6 +214,9 @@
// adaptation_field_control, whether this header is followed by an
// adaptation field, a payload, or both
afflag = (data[offset + 3] & 0x30 ) >>> 4,
adaptationFieldLength,
afftemp,
randomAccessIndicator,
patTableId, // :int
patCurrentNextIndicator, // Boolean
......@@ -247,7 +250,19 @@
// used to specify some forms of timing and control data that we
// do not currently use.
if (afflag > 0x01) {
offset += data[offset] + 1;
adaptationFieldLength = data[offset];
if (adaptationFieldLength > 0) {
afftemp = data[offset + 1];
randomAccessIndicator = (afftemp & 0x40) >>> 6;
if (randomAccessIndicator === 1) {
h264Stream.setNextFrameKeyFrame();
}
}
offset += adaptationFieldLength + 1;
}
// Handle a Program Association Table (PAT). PATs map PIDs to
......
......@@ -62,7 +62,10 @@ test('metadata is generated for IDRs after a full NAL unit is written', function
test('starting PTS values can be negative', function() {
var
h264Stream = new videojs.Hls.H264Stream(),
H264ExtraData = videojs.Hls.H264ExtraData,
oldExtraData = H264ExtraData.prototype.extraDataTag,
oldMetadata = H264ExtraData.prototype.metaDataTag,
h264Stream,
accessUnitDelimiter = new Uint8Array([
0x00,
0x00,
......@@ -70,8 +73,14 @@ test('starting PTS values can be negative', function() {
nalUnitTypes.access_unit_delimiter_rbsp
]);
// add a "tag" to the stream so that it doesn't try and parse metadata
h264Stream.tags.push('spacer tag');
H264ExtraData.prototype.extraDataTag = function() {
return 'extraDataTag';
};
H264ExtraData.prototype.metaDataTag = function() {
return 'metaDataTag';
};
h264Stream = new videojs.Hls.H264Stream();
h264Stream.setTimeStampOffset(-100);
h264Stream.setNextTimeStamp(-100, -100, true);
......@@ -83,6 +92,8 @@ test('starting PTS values can be negative', function() {
// flush out the last tag
h264Stream.writeBytes(accessUnitDelimiter, 0, accessUnitDelimiter.byteLength);
// shift the metadata and extradata tags out, since we don't care about them here
h264Stream.tags.shift();
h264Stream.tags.shift();
strictEqual(h264Stream.tags.length, 3, 'three tags are ready');
......@@ -93,6 +104,57 @@ test('starting PTS values can be negative', function() {
strictEqual(h264Stream.tags[2].pts, 100, 'the third PTS is 100');
strictEqual(h264Stream.tags[2].dts, 100, 'the third DTS is 100');
H264ExtraData.prototype.extraDataTag = oldExtraData;
H264ExtraData.prototype.metaDataTag = oldMetadata;
});
test('make sure we add metadata and extra data at the beginning of a stream', function() {
var
H264ExtraData = videojs.Hls.H264ExtraData,
oldExtraData = H264ExtraData.prototype.extraDataTag,
oldMetadata = H264ExtraData.prototype.metaDataTag,
h264Stream,
accessUnitDelimiter = new Uint8Array([
0x00,
0x00,
0x01,
nalUnitTypes.access_unit_delimiter_rbsp
]);
H264ExtraData.prototype.extraDataTag = function() {
return 'extraDataTag';
};
H264ExtraData.prototype.metaDataTag = function() {
return 'metaDataTag';
};
h264Stream = new videojs.Hls.H264Stream();
h264Stream.setTimeStampOffset(0);
h264Stream.setNextTimeStamp(0, 0, true);
h264Stream.writeBytes(accessUnitDelimiter, 0, accessUnitDelimiter.byteLength);
// make sure that keyFrame is set to false but that we don't have any tags currently written out
h264Stream._h264Frame.keyFrame = false;
h264Stream.tags = [];
h264Stream.setNextTimeStamp(5, 5, true);
h264Stream.writeBytes(accessUnitDelimiter, 0, accessUnitDelimiter.byteLength);
// flush out the last tag
h264Stream.writeBytes(accessUnitDelimiter, 0, accessUnitDelimiter.byteLength);
strictEqual(h264Stream.tags.length, 4, 'three tags are ready');
strictEqual(h264Stream.tags[0], 'metaDataTag', 'the first tag is the metaDataTag');
strictEqual(h264Stream.tags[1], 'extraDataTag', 'the second tag is the extraDataTag');
strictEqual(h264Stream.tags[2].pts, 0, 'the first PTS is 0');
strictEqual(h264Stream.tags[2].dts, 0, 'the first DTS is 0');
strictEqual(h264Stream.tags[3].pts, 5, 'the second PTS is 5');
strictEqual(h264Stream.tags[3].dts, 5, 'the second DTS is 5');
H264ExtraData.prototype.extraDataTag = oldExtraData;
H264ExtraData.prototype.metaDataTag = oldMetadata;
});
})(window.videojs);
......
......@@ -83,6 +83,7 @@ module.exports = function(config) {
'../src/stream.js',
'../src/flv-tag.js',
'../src/exp-golomb.js',
'../src/h264-extradata.js',
'../src/h264-stream.js',
'../src/aac-stream.js',
'../src/metadata-stream.js',
......
......@@ -47,6 +47,7 @@ module.exports = function(config) {
'../src/stream.js',
'../src/flv-tag.js',
'../src/exp-golomb.js',
'../src/h264-extradata.js',
'../src/h264-stream.js',
'../src/aac-stream.js',
'../src/metadata-stream.js',
......
......@@ -24,6 +24,7 @@
<script src="../src/stream.js"></script>
<script src="../src/flv-tag.js"></script>
<script src="../src/exp-golomb.js"></script>
<script src="../src/h264-extradata.js"></script>
<script src="../src/h264-stream.js"></script>
<script src="../src/aac-stream.js"></script>
<script src="../src/metadata-stream.js"></script>
......