b2716ec2 by David LaPalomento

Merge pull request #27 from videojs/feature/mux-helper

Add a page to help debug the transmuxing process
2 parents 7fbc62b9 1c5dcf30
......@@ -46,7 +46,7 @@ module.exports = function(grunt) {
}
},
qunit: {
files: ['test/**/*.html', '!test/perf.html']
files: ['test/**/*.html', '!test/perf.html', '!test/muxer/**']
},
jshint: {
gruntfile: {
......@@ -68,7 +68,8 @@ module.exports = function(grunt) {
src: ['test/**/*.js',
'!test/tsSegment.js',
'!test/fixtures/*.js',
'!test/manifest/**']
'!test/manifest/**',
'!test/muxer/**']
}
},
connect: {
......
......@@ -20,7 +20,7 @@ var
window.videojs.hls.AacStream = function() {
var
next_pts, // :uint
pts_delta = -1, // :int
pts_offset, // :int
state, // :uint
pes_length, // :int
......@@ -39,12 +39,13 @@ window.videojs.hls.AacStream = function() {
// (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;
// on the first invocation, capture the starting PTS value
pts_offset = pts;
// on subsequent invocations, calculate the PTS based on the starting offset
this.setNextTimeStamp = function(pts, pes_size, dataAligned) {
next_pts = pts - pts_offset;
pes_length = pes_size;
// If data is aligned, flush all internal buffers
......@@ -53,101 +54,104 @@ window.videojs.hls.AacStream = function() {
}
};
this.setNextTimeStamp(pts, pes_size, dataAligned);
};
// (data:ByteArray, o:int = 0, l:int = 0):void
this.writeBytes = function(data, o, l) {
this.writeBytes = function(data, offset, length) {
var
e, // :int
end, // :int
newExtraData, // :uint
bytesToCopy; // :int
// default arguments
o = o || 0;
l = l || 0;
offset = offset || 0;
length = length || 0;
// Do not allow more than 'pes_length' bytes to be written
l = (pes_length < l ? pes_length : l);
pes_length -= l;
e = o + l;
while (o < e) {
length = (pes_length < length ? pes_length : length);
pes_length -= length;
end = offset + length;
while (offset < end) {
switch (state) {
default:
state = 0;
break;
case 0:
if (o >= e) {
if (offset >= end) {
return;
}
if (0xFF !== data[o]) {
if (0xFF !== data[offset]) {
console.assert(false, 'Error no ATDS header found');
o += 1;
offset += 1;
state = 0;
return;
}
o += 1;
offset += 1;
state = 1;
break;
case 1:
if (o >= e) {
if (offset >= end) {
return;
}
if (0xF0 !== (data[o] & 0xF0)) {
if (0xF0 !== (data[offset] & 0xF0)) {
console.assert(false, 'Error no ATDS header found');
o +=1;
offset +=1;
state = 0;
return;
}
adtsProtectionAbsent = !!(data[o] & 0x01);
adtsProtectionAbsent = !!(data[offset] & 0x01);
o += 1;
offset += 1;
state = 2;
break;
case 2:
if (o >= e) {
if (offset >= end) {
return;
}
adtsObjectType = ((data[o] & 0xC0) >>> 6) + 1;
adtsSampleingIndex = ((data[o] & 0x3C) >>> 2);
adtsChanelConfig = ((data[o] & 0x01) << 2);
adtsObjectType = ((data[offset] & 0xC0) >>> 6) + 1;
adtsSampleingIndex = ((data[offset] & 0x3C) >>> 2);
adtsChanelConfig = ((data[offset] & 0x01) << 2);
o += 1;
offset += 1;
state = 3;
break;
case 3:
if (o >= e) {
if (offset >= end) {
return;
}
adtsChanelConfig |= ((data[o] & 0xC0) >>> 6);
adtsFrameSize = ((data[o] & 0x03) << 11);
adtsChanelConfig |= ((data[offset] & 0xC0) >>> 6);
adtsFrameSize = ((data[offset] & 0x03) << 11);
o += 1;
offset += 1;
state = 4;
break;
case 4:
if (o >= e) {
if (offset >= end) {
return;
}
adtsFrameSize |= (data[o] << 3);
adtsFrameSize |= (data[offset] << 3);
o += 1;
offset += 1;
state = 5;
break;
case 5:
if(o >= e) {
if(offset >= end) {
return;
}
adtsFrameSize |= ((data[o] & 0xE0) >>> 5);
adtsFrameSize |= ((data[offset] & 0xE0) >>> 5);
adtsFrameSize -= (adtsProtectionAbsent ? 7 : 9);
o += 1;
offset += 1;
state = 6;
break;
case 6:
if (o >= e) {
if (offset >= end) {
return;
}
adtsSampleCount = ((data[o] & 0x03) + 1) * 1024;
adtsSampleCount = ((data[offset] & 0x03) + 1) * 1024;
adtsDuration = (adtsSampleCount * 1000) / adtsSampleingRates[adtsSampleingIndex];
newExtraData = (adtsObjectType << 11) |
......@@ -182,15 +186,15 @@ window.videojs.hls.AacStream = function() {
}
// Skip the checksum if there is one
o += 1;
offset += 1;
state = 7;
break;
case 7:
if (!adtsProtectionAbsent) {
if (2 > (e - o)) {
if (2 > (end - offset)) {
return;
} else {
o += 2;
offset += 2;
}
}
......@@ -201,12 +205,12 @@ window.videojs.hls.AacStream = function() {
break;
case 8:
while (adtsFrameSize) {
if (o >= e) {
if (offset >= end) {
return;
}
bytesToCopy = (e - o) < adtsFrameSize ? (e - o) : adtsFrameSize;
aacFrame.writeBytes(data, o, bytesToCopy);
o += bytesToCopy;
bytesToCopy = (end - offset) < adtsFrameSize ? (end - offset) : adtsFrameSize;
aacFrame.writeBytes(data, offset, bytesToCopy);
offset += bytesToCopy;
adtsFrameSize -= bytesToCopy;
}
......
......@@ -14,13 +14,15 @@
}
return '.';
},
result = '',
hex,
ascii;
for (var j = 0; j < bytes.length / step; j++) {
hex = bytes.slice(j * step, j * step + step).map(formatHexString).join(' ');
ascii = bytes.slice(j * step, j * step + step).map(formatAsciiString).join('');
return hex + ' ' + ascii;
result += hex + ' ' + ascii + '\n';
}
return result;
},
tagDump: function(tag) {
return module.hexDump(tag.bytes);
......
......@@ -32,7 +32,6 @@ window.videojs.hls.ExpGolomb = function(workingData) {
workingBytes = new Uint8Array(4),
availableBytes = Math.min(4, workingBytesAvailable);
// console.assert(availableBytes > 0);
if (availableBytes === 0) {
throw new Error('no bytes available');
}
......
(function(window) {
window.videojs = window.videojs || {};
window.videojs.hls = window.videojs.hls || {};
var
hls = window.videojs.hls,
......
......@@ -8,7 +8,6 @@
(function(window) {
var
ExpGolomb = window.videojs.hls.ExpGolomb,
FlvTag = window.videojs.hls.FlvTag,
......@@ -16,22 +15,8 @@
this.sps = []; // :Array
this.pps = []; // :Array
this.addSPS = function(size) { // :ByteArray
console.assert(size > 0);
var tmp = new Uint8Array(size); // :ByteArray
this.sps.push(tmp);
return 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 < this.sps.length;
return this.sps.length > 0;
};
// (sizeOfScalingList:int, expGolomb:ExpGolomb):void
......@@ -56,61 +41,37 @@
};
/**
* NAL unit
* |- NAL header -|------ RBSP ------|
*
* NAL unit: Network abstraction layer unit. The combination of a NAL
* header and an RBSP.
* NAL header: the encapsulation unit for transport-specific metadata in
* an h264 stream. Exactly one byte.
* 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
// remove emulation bytes. Is this nesessary? is there ever emulation
// bytes in the SPS?
var
spsCount = 0,
sps0 = this.sps[0], // :ByteArray
rbspCount = 0,
start = 1, // :uint
end = sps0.byteLength - 2, // :uint
rbsp = new Uint8Array(sps0.byteLength), // :ByteArray
offset = 0; // :uint
// H264 requires emulation bytes (0x03) be dropped to interpret NAL
// units. For instance, 0x8a03b4 should be read as 0x8ab4.
for (offset = start ; offset < end ;) {
if (3 !== sps0[offset + 2]) {
offset += 3;
} else if (0 !== sps0[offset + 1]) {
offset += 2;
} else if (0 !== sps0[offset + 0]) {
offset += 1;
} else {
rbsp.set([0x00, 0x00], rbspCount);
spsCount += 2;
rbspCount += 2;
if (offset > start) {
// If there are bytes to write, write them
rbsp.set(sps0.subarray(start, offset - start), rbspCount);
spsCount += offset - start;
rbspCount += offset - start;
}
// skip the emulation bytes
offset += 3;
start = offset;
}
}
// copy any remaining bytes
rbsp.set(sps0.subarray(spsCount), rbspCount); // sps0.readBytes(rbsp, rbsp.length);
return rbsp;
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
......@@ -257,8 +218,23 @@
};
},
NALUnitType;
/**
* Network Abstraction Layer (NAL) units are the packets of an H264
* stream. NAL units are divided into types based on their payload
* data. Each type has a unique numeric identifier.
*
* NAL unit
* |- NAL header -|------ RBSP ------|
*
* NAL unit: Network abstraction layer unit. The combination of a NAL
* header and an RBSP.
* NAL header: the encapsulation unit for transport-specific metadata in
* an h264 stream. Exactly one byte.
*/
// incomplete, see Table 7.1 of ITU-T H.264 for 12-32
NALUnitType = {
window.videojs.hls.NALUnitType = NALUnitType = {
unspecified: 0,
slice_layer_without_partitioning_rbsp_non_idr: 1,
slice_data_partition_a_layer_rbsp: 2,
......@@ -277,7 +253,7 @@
var
next_pts, // :uint;
next_dts, // :uint;
pts_delta = -1, // :int
pts_offset, // :int
h264Frame, // :FlvTag
......@@ -292,15 +268,14 @@
//(pts:uint, dts:uint, dataAligned:Boolean):void
this.setNextTimeStamp = function(pts, dts, dataAligned) {
if (pts_delta < 0) {
// We assume the very first pts is less than 0x8FFFFFFF (max signed
// int32)
pts_delta = pts;
}
// on the first invocation, capture the starting PTS value
pts_offset = pts;
// on subsequent invocations, calculate the PTS based on the starting offset
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_delta;
next_dts = dts - pts_delta;
next_pts = pts - pts_offset;
next_dts = dts - pts_offset;
// If data is aligned, flush all internal buffers
if (dataAligned) {
......@@ -308,6 +283,9 @@
}
};
this.setNextTimeStamp(pts, dts, dataAligned);
};
this.finishFrame = function() {
if (h264Frame) {
// Push SPS before EVERY IDR frame for seeking
......@@ -395,7 +373,7 @@
data[offset + 1] === 0 &&
data[offset + 2] === 1) {
// 00 : 00 00 01
h264Frame.length -= 1;
// h264Frame.length -= 1;
state = 3;
return this.writeBytes(data, offset + 3, length - 3);
}
......@@ -466,7 +444,6 @@
h264Frame.endNalUnit(newExtraData.pps);
break;
case NALUnitType.slice_layer_without_partitioning_rbsp_idr:
h264Frame.keyFrame = true;
h264Frame.endNalUnit();
break;
default:
......@@ -477,8 +454,13 @@
// setup to begin processing the new NAL unit
nalUnitType = data[offset] & 0x1F;
if (h264Frame && 9 === nalUnitType) {
this.finishFrame(); // We are starting a new access unit. Flush the previous one
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 h264Frame null, so we must test again
......
......@@ -18,8 +18,7 @@
streamBuffer = new Uint8Array(MP2T_PACKET_LENGTH),
streamBufferByteCount = 0,
h264Stream = new H264Stream(),
aacStream = new AacStream(),
seekToKeyFrame = false;
aacStream = new AacStream();
// expose the stream metadata
self.stream = {
......@@ -84,60 +83,35 @@
self.flushTags = function() {
h264Stream.finishFrame();
};
self.doSeek = function() {
self.flushTags();
aacStream.tags.length = 0;
h264Stream.tags.length = 0;
seekToKeyFrame = true;
};
/**
* Returns whether a call to `getNextTag()` will be successful.
* @return {boolean} whether there is at least one transmuxed FLV
* tag ready
*/
self.tagsAvailable = function() { // :int {
var i, pts; // :uint
if (seekToKeyFrame) {
for (i = 0 ; i < h264Stream.tags.length && seekToKeyFrame; ++i) {
if (h264Stream.tags[i].keyFrame) {
seekToKeyFrame = false; // We found, a keyframe, stop seeking
}
}
if (seekToKeyFrame) {
// we didnt find a keyframe. yet
h264Stream.tags.length = 0;
return 0;
}
// TODO we MAY need to use dts, not pts
h264Stream.tags = h264Stream.tags.slice(i);
pts = h264Stream.tags[0].pts;
// Remove any audio before the found keyframe
while( 0 < aacStream.tags.length && pts > aacStream.tags[0].pts ) {
aacStream.tags.shift();
}
}
return h264Stream.tags.length + aacStream.tags.length;
};
self.getNextTag = function() { // :ByteArray {
var tag; // :FlvTag; // return tags in approximate dts order
if (0 === self.tagsAvailable()) {
throw new Error("getNextTag() called when 0 == tagsAvailable()");
}
/**
* Returns the next tag in decoder-timestamp (DTS) order.
* @returns {object} the next tag to decoded.
*/
self.getNextTag = function() {
var tag;
if (0 < h264Stream.tags.length) {
if (0 < aacStream.tags.length && aacStream.tags[0].dts < h264Stream.tags[0].dts) {
if (!h264Stream.tags.length) {
// only audio tags remain
tag = aacStream.tags.shift();
} else {
} else if (!aacStream.tags.length) {
// only video tags remain
tag = h264Stream.tags.shift();
}
} else if ( 0 < aacStream.tags.length ) {
} else if (aacStream.tags[0].dts < h264Stream.tags[0].dts) {
// audio should be decoded next
tag = aacStream.tags.shift();
} else {
// We dont have any tags available to return
return new Uint8Array();
// video should be decoded next
tag = h264Stream.tags.shift();
}
return tag.finalize();
......
......@@ -21,6 +21,7 @@
*/
var
buffer,
ExpGolomb = window.videojs.hls.ExpGolomb,
expGolomb;
module('Exponential Golomb coding');
......@@ -44,32 +45,62 @@ test('small numbers are coded correctly', function() {
while (i--) {
buffer = new Uint8Array([expected[i][0]]);
expGolomb = new window.videojs.hls.ExpGolomb(buffer);
expGolomb = new ExpGolomb(buffer);
result = expGolomb.readUnsignedExpGolomb();
equal(expected[i][1], result, expected[i][0] + ' is decoded to ' + expected[i][1]);
}
});
test('drops working data as it is parsed', function() {
var expGolomb = new window.videojs.hls.ExpGolomb(new Uint8Array([0x00, 0xFF]));
var expGolomb = new ExpGolomb(new Uint8Array([0x00, 0xFF]));
expGolomb.skipBits(8);
equal(8, expGolomb.bitsAvailable(), '8 bits remain');
equal(0xFF, expGolomb.readBits(8), 'the second byte is read');
});
test('drops working data when skipping leading zeros', function() {
var expGolomb = new window.videojs.hls.ExpGolomb(new Uint8Array([0x00, 0x00, 0x00, 0x00, 0xFF]));
var expGolomb = new ExpGolomb(new Uint8Array([0x00, 0x00, 0x00, 0x00, 0xFF]));
equal(32, expGolomb.skipLeadingZeros(), '32 leading zeros are dropped');
equal(8, expGolomb.bitsAvailable(), '8 bits remain');
equal(0xFF, expGolomb.readBits(8), 'the second byte is read');
});
test('drops working data when skipping leading zeros', function() {
var expGolomb = new window.videojs.hls.ExpGolomb(new Uint8Array([0x15, 0xab, 0x40, 0xc8, 0xFF]));
var expGolomb = new ExpGolomb(new Uint8Array([0x15, 0xab, 0x40, 0xc8, 0xFF]));
equal(3, expGolomb.skipLeadingZeros(), '3 leading zeros are dropped');
equal((8 * 4) + 5, expGolomb.bitsAvailable(), '37 bits remain');
expGolomb.skipBits(1);
equal(0x5a, expGolomb.readBits(8), 'the next bits are read');
});
test('parses a sequence parameter set', function() {
var
sps = new Uint8Array([
0x27, 0x42, 0xe0, 0x0b,
0xa9, 0x18, 0x60, 0x9d,
0x80, 0x35, 0x06, 0x01,
0x06, 0xb6, 0xc2, 0xb5,
0xef, 0x7c, 0x04
]),
expGolomb = new ExpGolomb(sps);
strictEqual(expGolomb.readBits(8), 0x27, 'the NAL type specifies an SPS');
strictEqual(expGolomb.readBits(8), 66, 'profile_idc is 66');
strictEqual(expGolomb.readBits(4), 0x0E, 'constraints 0-3 are correct');
expGolomb.skipBits(4);
strictEqual(expGolomb.readBits(8), 11, 'level_idc is 11');
strictEqual(expGolomb.readUnsignedExpGolomb(), 0, 'seq_parameter_set_id is 0');
strictEqual(expGolomb.readUnsignedExpGolomb(), 1, 'log2_max_frame_num_minus4 is 1');
strictEqual(expGolomb.readUnsignedExpGolomb(), 0, 'pic_order_cnt_type is 0');
strictEqual(expGolomb.readUnsignedExpGolomb(), 3, 'log2_max_pic_order_cnt_lsb_minus4 is 3');
strictEqual(expGolomb.readUnsignedExpGolomb(), 2, 'max_num_ref_frames is 2');
strictEqual(expGolomb.readBits(1), 0, 'gaps_in_frame_num_value_allowed_flag is false');
strictEqual(expGolomb.readUnsignedExpGolomb(), 11, 'pic_width_in_mbs_minus1 is 11');
strictEqual(expGolomb.readUnsignedExpGolomb(), 8, 'pic_height_in_map_units_minus1 is 8');
strictEqual(expGolomb.readBits(1), 1, 'frame_mbs_only_flag is true');
strictEqual(expGolomb.readBits(1), 1, 'direct_8x8_inference_flag is true');
strictEqual(expGolomb.readBits(1), 0, 'frame_cropping_flag is false');
});
})(this);
......
(function(videojs) {
module('H264 Stream');
var
nalUnitTypes = window.videojs.hls.NALUnitType,
FlvTag = window.videojs.hls.FlvTag;
test('metadata is generated for IDRs after a full NAL unit is written', function() {
var
h264Stream = new videojs.hls.H264Stream(),
accessUnitDelimiter = new Uint8Array([
0x00,
0x00,
0x01,
nalUnitTypes.access_unit_delimiter_rbsp
]),
seqParamSet = new Uint8Array([
0x00,
0x00,
0x01,
0x60 | nalUnitTypes.seq_parameter_set_rbsp,
0x00, // profile_idc
0x00, // constraint_set flags
0x00, // level_idc
// seq_parameter_set_id ue(v) 0 => 1
// log2_max_frame_num_minus4 ue(v) 1 => 010
// pic_order_cnt_type ue(v) 0 => 1
// log2_max_pic_order_cnt_lsb_minus4 ue(v) 1 => 010
// max_num_ref_frames ue(v) 1 => 010
// gaps_in_frame_num_value_allowed u(1) 0
// pic_width_in_mbs_minus1 ue(v) 0 => 1
// pic_height_in_map_units_minus1 ue(v) 0 => 1
// frame_mbs_only_flag u(1) 1
// direct_8x8_inference_flag u(1) 0
// frame_cropping_flag u(1) 0
// vui_parameters_present_flag u(1) 0
// 1010 1010 0100 1110 00(00 0000)
0xAA,
0x4E,
0x00
]),
idr = new Uint8Array([
0x00,
0x00,
0x01,
nalUnitTypes.slice_layer_without_partitioning_rbsp_idr
]);
h264Stream.setNextTimeStamp(0, 0, true);
h264Stream.writeBytes(accessUnitDelimiter, 0, accessUnitDelimiter.byteLength);
h264Stream.writeBytes(seqParamSet, 0, seqParamSet.byteLength);
h264Stream.writeBytes(idr, 0, idr.byteLength);
h264Stream.setNextTimeStamp(1, 1, true);
strictEqual(h264Stream.tags.length, 3, 'three tags are written');
ok(FlvTag.isMetaData(h264Stream.tags[0].bytes),
'metadata is written');
ok(FlvTag.isVideoFrame(h264Stream.tags[1].bytes),
'picture parameter set is written');
ok(h264Stream.tags[2].keyFrame, 'key frame is written');
});
test('starting PTS values can be negative', function() {
var
h264Stream = new videojs.hls.H264Stream(),
accessUnitDelimiter = new Uint8Array([
0x00,
0x00,
0x01,
nalUnitTypes.access_unit_delimiter_rbsp
]);
h264Stream.setNextTimeStamp(-100, -100, true);
h264Stream.writeBytes(accessUnitDelimiter, 0, accessUnitDelimiter.byteLength);
h264Stream.setNextTimeStamp(-99, -99, true);
h264Stream.writeBytes(accessUnitDelimiter, 0, accessUnitDelimiter.byteLength);
h264Stream.setNextTimeStamp(0, 0, true);
h264Stream.writeBytes(accessUnitDelimiter, 0, accessUnitDelimiter.byteLength);
// flush out the last tag
h264Stream.writeBytes(accessUnitDelimiter, 0, accessUnitDelimiter.byteLength);
strictEqual(h264Stream.tags.length, 3, 'three tags are ready');
strictEqual(h264Stream.tags[0].pts, 0, 'the first PTS is zero');
strictEqual(h264Stream.tags[0].dts, 0, 'the first DTS is zero');
strictEqual(h264Stream.tags[1].pts, 1, 'the second PTS is one');
strictEqual(h264Stream.tags[1].dts, 1, 'the second DTS is one');
strictEqual(h264Stream.tags[2].pts, 100, 'the third PTS is 100');
strictEqual(h264Stream.tags[2].dts, 100, 'the third DTS is 100');
});
})(window.videojs);
/* ==========================================================================
HTML5 Boilerplate styles - h5bp.com (generated via initializr.com)
========================================================================== */
html,
button,
input,
select,
textarea {
color: #222;
}
body {
font-size: 1em;
line-height: 1.4;
}
::-moz-selection {
background: #b3d4fc;
text-shadow: none;
}
::selection {
background: #b3d4fc;
text-shadow: none;
}
hr {
display: block;
height: 1px;
border: 0;
border-top: 1px solid #ccc;
margin: 1em 0;
padding: 0;
}
img {
vertical-align: middle;
}
fieldset {
border: 0;
margin: 0;
padding: 0;
}
textarea {
resize: vertical;
}
.chromeframe {
margin: 0.2em 0;
background: #ccc;
color: #000;
padding: 0.2em 0;
}
/* ===== Initializr Styles ==================================================
Author: Jonathan Verrecchia - verekia.com/initializr/responsive-template
========================================================================== */
body {
font: 16px/26px Helvetica, Helvetica Neue, Arial;
}
.wrapper {
width: 90%;
margin: 0 5%;
}
/* ===================
ALL: Orange Theme
=================== */
.header-container {
border-bottom: 20px solid #e44d26;
}
.footer-container,
.main aside {
border-top: 20px solid #e44d26;
}
.header-container,
.footer-container,
.main aside {
background: #f16529;
}
.title {
color: white;
}
/* ==============
MOBILE: Menu
============== */
nav ul {
margin: 0;
padding: 0;
}
nav a {
display: block;
margin-bottom: 10px;
padding: 15px 0;
text-align: center;
text-decoration: none;
font-weight: bold;
color: white;
background: #e44d26;
}
nav a:hover,
nav a:visited {
color: white;
}
nav a:hover {
text-decoration: underline;
}
/* ==============
MOBILE: Main
============== */
.main {
padding: 30px 0;
}
.main article h1 {
font-size: 2em;
}
.main aside {
color: white;
padding: 0px 5% 10px;
}
.footer-container footer {
color: white;
padding: 20px 0;
}
/* ===============
ALL: IE Fixes
=============== */
.ie7 .title {
padding-top: 20px;
}
/* ==========================================================================
Author's custom styles
========================================================================== */
section {
clear: both;
}
form label {
display: block;
}
.result-wrapper {
float: left;
margin: 10px 0;
min-width: 422px;
width: 50%;
}
.result {
border: thin solid #aaa;
border-radius: 5px;
font-size: 10px;
line-height: 15px;
margin: 0 5px;
padding: 0 10px;
}
/* ==========================================================================
Media Queries
========================================================================== */
@media only screen and (min-width: 480px) {
/* ====================
INTERMEDIATE: Menu
==================== */
nav a {
float: left;
width: 27%;
margin: 0 1.7%;
padding: 25px 2%;
margin-bottom: 0;
}
nav li:first-child a {
margin-left: 0;
}
nav li:last-child a {
margin-right: 0;
}
/* ========================
INTERMEDIATE: IE Fixes
======================== */
nav ul li {
display: inline;
}
.oldie nav a {
margin: 0 0.7%;
}
}
@media only screen and (min-width: 768px) {
/* ====================
WIDE: CSS3 Effects
==================== */
.header-container,
.main aside {
-webkit-box-shadow: 0 5px 10px #aaa;
-moz-box-shadow: 0 5px 10px #aaa;
box-shadow: 0 5px 10px #aaa;
}
/* ============
WIDE: Menu
============ */
.title {
float: left;
}
nav {
float: right;
width: 38%;
}
/* ============
WIDE: Main
============ */
.main article {
float: left;
width: 100%;
}
}
@media only screen and (min-width: 1140px) {
/* ===============
Maximal Width
=============== */
.wrapper {
width: 1026px; /* 1140px - 10% for margins */
margin: 0 auto;
}
}
/* ==========================================================================
Helper classes
========================================================================== */
.ir {
background-color: transparent;
border: 0;
overflow: hidden;
*text-indent: -9999px;
}
.ir:before {
content: "";
display: block;
width: 0;
height: 150%;
}
.hidden {
display: none !important;
visibility: hidden;
}
.visuallyhidden {
border: 0;
clip: rect(0 0 0 0);
height: 1px;
margin: -1px;
overflow: hidden;
padding: 0;
position: absolute;
width: 1px;
}
.visuallyhidden.focusable:active,
.visuallyhidden.focusable:focus {
clip: auto;
height: auto;
margin: 0;
overflow: visible;
position: static;
width: auto;
}
.invisible {
visibility: hidden;
}
.clearfix:before,
.clearfix:after {
content: " ";
display: table;
}
.clearfix:after {
clear: both;
}
.clearfix {
*zoom: 1;
}
/* ==========================================================================
Print styles
========================================================================== */
@media print {
* {
background: transparent !important;
color: #000 !important; /* Black prints faster: h5bp.com/s */
box-shadow: none !important;
text-shadow: none !important;
}
a,
a:visited {
text-decoration: underline;
}
a[href]:after {
content: " (" attr(href) ")";
}
abbr[title]:after {
content: " (" attr(title) ")";
}
/*
* Don't show links for images, or javascript/internal links
*/
.ir a:after,
a[href^="javascript:"]:after,
a[href^="#"]:after {
content: "";
}
pre,
blockquote {
border: 1px solid #999;
page-break-inside: avoid;
}
thead {
display: table-header-group; /* h5bp.com/t */
}
tr,
img {
page-break-inside: avoid;
}
img {
max-width: 100% !important;
}
@page {
margin: 0.5cm;
}
p,
h2,
h3 {
orphans: 3;
widows: 3;
}
h2,
h3 {
page-break-after: avoid;
}
}
\ No newline at end of file
/*! normalize.css v1.1.2 | MIT License | git.io/normalize */
/* ==========================================================================
HTML5 display definitions
========================================================================== */
/**
* Correct `block` display not defined in IE 6/7/8/9 and Firefox 3.
*/
article,
aside,
details,
figcaption,
figure,
footer,
header,
hgroup,
main,
nav,
section,
summary {
display: block;
}
/**
* Correct `inline-block` display not defined in IE 6/7/8/9 and Firefox 3.
*/
audio,
canvas,
video {
display: inline-block;
*display: inline;
*zoom: 1;
}
/**
* Prevent modern browsers from displaying `audio` without controls.
* Remove excess height in iOS 5 devices.
*/
audio:not([controls]) {
display: none;
height: 0;
}
/**
* Address styling not present in IE 7/8/9, Firefox 3, and Safari 4.
* Known issue: no IE 6 support.
*/
[hidden] {
display: none;
}
/* ==========================================================================
Base
========================================================================== */
/**
* 1. Correct text resizing oddly in IE 6/7 when body `font-size` is set using
* `em` units.
* 2. Prevent iOS text size adjust after orientation change, without disabling
* user zoom.
*/
html {
font-size: 100%; /* 1 */
-ms-text-size-adjust: 100%; /* 2 */
-webkit-text-size-adjust: 100%; /* 2 */
}
/**
* Address `font-family` inconsistency between `textarea` and other form
* elements.
*/
html,
button,
input,
select,
textarea {
font-family: sans-serif;
}
/**
* Address margins handled incorrectly in IE 6/7.
*/
body {
margin: 0;
}
/* ==========================================================================
Links
========================================================================== */
/**
* Address `outline` inconsistency between Chrome and other browsers.
*/
a:focus {
outline: thin dotted;
}
/**
* Improve readability when focused and also mouse hovered in all browsers.
*/
a:active,
a:hover {
outline: 0;
}
/* ==========================================================================
Typography
========================================================================== */
/**
* Address font sizes and margins set differently in IE 6/7.
* Address font sizes within `section` and `article` in Firefox 4+, Safari 5,
* and Chrome.
*/
h1 {
font-size: 2em;
margin: 0.67em 0;
}
h2 {
font-size: 1.5em;
margin: 0.83em 0;
}
h3 {
font-size: 1.17em;
margin: 1em 0;
}
h4 {
font-size: 1em;
margin: 1.33em 0;
}
h5 {
font-size: 0.83em;
margin: 1.67em 0;
}
h6 {
font-size: 0.67em;
margin: 2.33em 0;
}
/**
* Address styling not present in IE 7/8/9, Safari 5, and Chrome.
*/
abbr[title] {
border-bottom: 1px dotted;
}
/**
* Address style set to `bolder` in Firefox 3+, Safari 4/5, and Chrome.
*/
b,
strong {
font-weight: bold;
}
blockquote {
margin: 1em 40px;
}
/**
* Address styling not present in Safari 5 and Chrome.
*/
dfn {
font-style: italic;
}
/**
* Address differences between Firefox and other browsers.
* Known issue: no IE 6/7 normalization.
*/
hr {
-moz-box-sizing: content-box;
box-sizing: content-box;
height: 0;
}
/**
* Address styling not present in IE 6/7/8/9.
*/
mark {
background: #ff0;
color: #000;
}
/**
* Address margins set differently in IE 6/7.
*/
p,
pre {
margin: 1em 0;
}
/**
* Correct font family set oddly in IE 6, Safari 4/5, and Chrome.
*/
code,
kbd,
pre,
samp {
font-family: monospace, serif;
_font-family: 'courier new', monospace;
font-size: 1em;
}
/**
* Improve readability of pre-formatted text in all browsers.
*/
pre {
white-space: pre;
white-space: pre-wrap;
word-wrap: break-word;
}
/**
* Address CSS quotes not supported in IE 6/7.
*/
q {
quotes: none;
}
/**
* Address `quotes` property not supported in Safari 4.
*/
q:before,
q:after {
content: '';
content: none;
}
/**
* Address inconsistent and variable font size in all browsers.
*/
small {
font-size: 80%;
}
/**
* Prevent `sub` and `sup` affecting `line-height` in all browsers.
*/
sub,
sup {
font-size: 75%;
line-height: 0;
position: relative;
vertical-align: baseline;
}
sup {
top: -0.5em;
}
sub {
bottom: -0.25em;
}
/* ==========================================================================
Lists
========================================================================== */
/**
* Address margins set differently in IE 6/7.
*/
dl,
menu,
ol,
ul {
margin: 1em 0;
}
dd {
margin: 0 0 0 40px;
}
/**
* Address paddings set differently in IE 6/7.
*/
menu,
ol,
ul {
padding: 0 0 0 40px;
}
/**
* Correct list images handled incorrectly in IE 7.
*/
nav ul,
nav ol {
list-style: none;
list-style-image: none;
}
/* ==========================================================================
Embedded content
========================================================================== */
/**
* 1. Remove border when inside `a` element in IE 6/7/8/9 and Firefox 3.
* 2. Improve image quality when scaled in IE 7.
*/
img {
border: 0; /* 1 */
-ms-interpolation-mode: bicubic; /* 2 */
}
/**
* Correct overflow displayed oddly in IE 9.
*/
svg:not(:root) {
overflow: hidden;
}
/* ==========================================================================
Figures
========================================================================== */
/**
* Address margin not present in IE 6/7/8/9, Safari 5, and Opera 11.
*/
figure {
margin: 0;
}
/* ==========================================================================
Forms
========================================================================== */
/**
* Correct margin displayed oddly in IE 6/7.
*/
form {
margin: 0;
}
/**
* Define consistent border, margin, and padding.
*/
fieldset {
border: 1px solid #c0c0c0;
margin: 0 2px;
padding: 0.35em 0.625em 0.75em;
}
/**
* 1. Correct color not being inherited in IE 6/7/8/9.
* 2. Correct text not wrapping in Firefox 3.
* 3. Correct alignment displayed oddly in IE 6/7.
*/
legend {
border: 0; /* 1 */
padding: 0;
white-space: normal; /* 2 */
*margin-left: -7px; /* 3 */
}
/**
* 1. Correct font size not being inherited in all browsers.
* 2. Address margins set differently in IE 6/7, Firefox 3+, Safari 5,
* and Chrome.
* 3. Improve appearance and consistency in all browsers.
*/
button,
input,
select,
textarea {
font-size: 100%; /* 1 */
margin: 0; /* 2 */
vertical-align: baseline; /* 3 */
*vertical-align: middle; /* 3 */
}
/**
* Address Firefox 3+ setting `line-height` on `input` using `!important` in
* the UA stylesheet.
*/
button,
input {
line-height: normal;
}
/**
* Address inconsistent `text-transform` inheritance for `button` and `select`.
* All other form control elements do not inherit `text-transform` values.
* Correct `button` style inheritance in Chrome, Safari 5+, and IE 6+.
* Correct `select` style inheritance in Firefox 4+ and Opera.
*/
button,
select {
text-transform: none;
}
/**
* 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`
* and `video` controls.
* 2. Correct inability to style clickable `input` types in iOS.
* 3. Improve usability and consistency of cursor style between image-type
* `input` and others.
* 4. Remove inner spacing in IE 7 without affecting normal text inputs.
* Known issue: inner spacing remains in IE 6.
*/
button,
html input[type="button"], /* 1 */
input[type="reset"],
input[type="submit"] {
-webkit-appearance: button; /* 2 */
cursor: pointer; /* 3 */
*overflow: visible; /* 4 */
}
/**
* Re-set default cursor for disabled elements.
*/
button[disabled],
html input[disabled] {
cursor: default;
}
/**
* 1. Address box sizing set to content-box in IE 8/9.
* 2. Remove excess padding in IE 8/9.
* 3. Remove excess padding in IE 7.
* Known issue: excess padding remains in IE 6.
*/
input[type="checkbox"],
input[type="radio"] {
box-sizing: border-box; /* 1 */
padding: 0; /* 2 */
*height: 13px; /* 3 */
*width: 13px; /* 3 */
}
/**
* 1. Address `appearance` set to `searchfield` in Safari 5 and Chrome.
* 2. Address `box-sizing` set to `border-box` in Safari 5 and Chrome
* (include `-moz` to future-proof).
*/
input[type="search"] {
-webkit-appearance: textfield; /* 1 */
-moz-box-sizing: content-box;
-webkit-box-sizing: content-box; /* 2 */
box-sizing: content-box;
}
/**
* Remove inner padding and search cancel button in Safari 5 and Chrome
* on OS X.
*/
input[type="search"]::-webkit-search-cancel-button,
input[type="search"]::-webkit-search-decoration {
-webkit-appearance: none;
}
/**
* Remove inner padding and border in Firefox 3+.
*/
button::-moz-focus-inner,
input::-moz-focus-inner {
border: 0;
padding: 0;
}
/**
* 1. Remove default vertical scrollbar in IE 6/7/8/9.
* 2. Improve readability and alignment in all browsers.
*/
textarea {
overflow: auto; /* 1 */
vertical-align: top; /* 2 */
}
/* ==========================================================================
Tables
========================================================================== */
/**
* Remove most spacing between table cells.
*/
table {
border-collapse: collapse;
border-spacing: 0;
}
/*! normalize.css v1.1.2 | MIT License | git.io/normalize */article,aside,details,figcaption,figure,footer,header,hgroup,main,nav,section,summary{display:block}audio,canvas,video{display:inline-block;*display:inline;*zoom:1}audio:not([controls]){display:none;height:0}[hidden]{display:none}html{font-size:100%;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}html,button,input,select,textarea{font-family:sans-serif}body{margin:0}a:focus{outline:thin dotted}a:active,a:hover{outline:0}h1{font-size:2em;margin:.67em 0}h2{font-size:1.5em;margin:.83em 0}h3{font-size:1.17em;margin:1em 0}h4{font-size:1em;margin:1.33em 0}h5{font-size:.83em;margin:1.67em 0}h6{font-size:.67em;margin:2.33em 0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:bold}blockquote{margin:1em 40px}dfn{font-style:italic}hr{-moz-box-sizing:content-box;box-sizing:content-box;height:0}mark{background:#ff0;color:#000}p,pre{margin:1em 0}code,kbd,pre,samp{font-family:monospace,serif;_font-family:'courier new',monospace;font-size:1em}pre{white-space:pre;white-space:pre-wrap;word-wrap:break-word}q{quotes:none}q:before,q:after{content:'';content:none}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-0.5em}sub{bottom:-0.25em}dl,menu,ol,ul{margin:1em 0}dd{margin:0 0 0 40px}menu,ol,ul{padding:0 0 0 40px}nav ul,nav ol{list-style:none;list-style-image:none}img{border:0;-ms-interpolation-mode:bicubic}svg:not(:root){overflow:hidden}figure{margin:0}form{margin:0}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}legend{border:0;padding:0;white-space:normal;*margin-left:-7px}button,input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}button,input{line-height:normal}button,select{text-transform:none}button,html input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer;*overflow:visible}button[disabled],html input[disabled]{cursor:default}input[type="checkbox"],input[type="radio"]{box-sizing:border-box;padding:0;*height:13px;*width:13px}input[type="search"]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}textarea{overflow:auto;vertical-align:top}table{border-collapse:collapse;border-spacing:0}
\ No newline at end of file
<!DOCTYPE html>
<!--[if lt IE 7]> <html class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]-->
<!--[if IE 7]> <html class="no-js lt-ie9 lt-ie8"> <![endif]-->
<!--[if IE 8]> <html class="no-js lt-ie9"> <![endif]-->
<!--[if gt IE 8]><!--> <html class="no-js"> <!--<![endif]-->
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title></title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width">
<link rel="stylesheet" href="css/normalize.min.css">
<link rel="stylesheet" href="css/main.css">
<script src="js/vendor/modernizr-2.6.2.min.js"></script>
</head>
<body>
<!--[if lt IE 7]>
<p class="chromeframe">You are using an <strong>outdated</strong> browser. Please <a href="http://browsehappy.com/">upgrade your browser</a> or <a href="http://www.google.com/chromeframe/?redirect=true">activate Google Chrome Frame</a> to improve your experience.</p>
<![endif]-->
<div class="header-container">
<header class="wrapper clearfix">
<h1 class="title">Transmux Analyzer</h1>
</header>
</div>
<div class="main-container">
<div class="main wrapper clearfix">
<article>
<header>
<p>
This page can help you compare the results of the
transmuxing performed by videojs-contrib-hls with a known,
working file produced by another tool. You could use
ffmpeg to transform an MPEG-2 transport stream into an FLV
with a command like this:
<pre>ffmpeg -i input.ts -acodec copy -vcodec copy output.flv</pre>
</p>
</header>
<section>
<h2>Inputs</h2>
<form id="inputs">
<label>
Your original MP2T segment:
<input type="file" id="original">
</label>
<label>
A working, FLV version of the underlying stream
produced by another tool:
<input type="file" id="working">
</label>
</form>
</section>
<section>
<h2>Tag Comparison</h2>
<div class="result-wrapper">
<h3>videojs-contrib-hls</h3>
<ol class="vjs-tags">
</ol>
</div>
<div class="result-wrapper">
<h3>Working</h3>
<ol class="working-tags">
</ol>
</div>
</section>
<section>
<h2>Results</h2>
<div class="result-wrapper">
<h3>videojs-contrib-hls</h3>
<div class="vjs-hls-output result">
<p>
The results of transmuxing your input file with
videojs-contrib-hls will show up here.
</p>
</div>
</div>
<div class="result-wrapper">
<h3>Working</h3>
<div class="working-output result">
<p>
The "good" version of the file will show up here.
</p>
</div>
</div>
</section>
</article>
</div> <!-- #main -->
</div> <!-- #main-container -->
<div class="footer-container">
<footer class="wrapper">
<h3>footer</h3>
</footer>
</div>
<!-- transmuxing -->
<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>
<script src="../../src/bin-utils.js"></script>
<script>
var inputs = document.getElementById('inputs'),
original = document.getElementById('original'),
working = document.getElementById('working'),
vjsTags = document.querySelector('.vjs-tags'),
workingTags = document.querySelector('.working-tags'),
vjsOutput = document.querySelector('.vjs-hls-output'),
workingOutput = document.querySelector('.working-output'),
tagTypes = {
0x08: 'audio',
0x09: 'video',
0x12: 'metadata'
};
original.addEventListener('change', function() {
var reader = new FileReader();
reader.addEventListener('loadend', function() {
var parser = new videojs.hls.SegmentParser(),
tags = [parser.getFlvHeader()],
tag,
hex,
li,
byteLength = 0,
data,
i,
pos;
// clear old tag info
vjsTags.innerHTML = '';
parser.parseSegmentBinaryData(new Uint8Array(reader.result));
// collect all the tags
while (parser.tagsAvailable()) {
tag = parser.getNextTag();
tags.push(tag.bytes);
li = document.createElement('li');
li.innerHTML = tagTypes[tag.bytes[0]] + ': ' + tag.bytes.byteLength + 'B';
vjsTags.appendChild(li);
}
// create a uint8array for the entire segment and copy everything over
i = tags.length;
while (i--) {
byteLength += tags[i].byteLength;
}
data = new Uint8Array(byteLength);
i = tags.length;
pos = byteLength;
while (i--) {
pos -= tags[i].byteLength;
data.set(tags[i], pos);
}
hex = '<pre>'
hex += videojs.hls.utils.hexDump(data);
hex += '</pre>'
vjsOutput.innerHTML = hex;
});
reader.readAsArrayBuffer(this.files[0]);
}, false);
working.addEventListener('change', function() {
var reader = new FileReader();
reader.addEventListener('loadend', function() {
var hex = '<pre>',
bytes = new Uint8Array(reader.result),
i = 9, // header
dataSize,
li;
// clear old tag info
workingTags.innerHTML = '';
// traverse the tags
i += 4; // previous tag size
while (i < bytes.byteLength) {
dataSize = bytes[i + 1] << 16;
dataSize |= bytes[i + 2] << 8;
dataSize |= bytes[i + 3];
dataSize += 11;
li = document.createElement('li');
li.innerHTML = tagTypes[bytes[i]] + ': ' + dataSize + 'B';
workingTags.appendChild(li);
i += dataSize; // tag size
i += 4; // previous tag size
}
// output the hex dump
hex += videojs.hls.utils.hexDump(bytes);
hex += '</pre>';
workingOutput.innerHTML = hex;
});
reader.readAsArrayBuffer(this.files[0]);
}, false);
</script>
<script type="text/plain">
// map nal_unit_types to friendly names
console.log([
'unspecified',
'slice_layer_without_partitioning',
'slice_data_partition_a_layer',
'slice_data_partition_b_layer',
'slice_data_partition_c_layer',
'slice_layer_without_partitioning_idr',
'sei',
'seq_parameter_set',
'pic_parameter_set',
'access_unit_delimiter',
'end_of_seq',
'end_of_stream',
'filler',
'seq_parameter_set_ext',
'prefix_nal_unit',
'subset_seq_parameter_set',
'reserved',
'reserved',
'reserved'
][nalUnitType]);
</script>
</body>
</html>
......@@ -44,6 +44,7 @@
</script>
<script src="videojs-hls_test.js"></script>
<script src="segment-parser.js"></script>
<script src="h264-stream_test.js"></script>
<script src="exp-golomb_test.js"></script>
<script src="flv-tag_test.js"></script>
<script src="m3u8_test.js"></script>
......