Skip to content

Commit

Permalink
Merge pull request videojs#42 from imbcmdth/caption-id3-parsing
Browse files Browse the repository at this point in the history
Caption ID3 Parsing Improvements
  • Loading branch information
dmlap committed Dec 7, 2015
2 parents 492f060 + bab6509 commit cb86179
Show file tree
Hide file tree
Showing 5 changed files with 432 additions and 221 deletions.
1 change: 1 addition & 0 deletions lib/codecs/h264.js
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ H264Stream = function() {
break;
case 0x06:
event.nalUnitType = 'sei_rbsp';
event.escapedRBSP = discardEmulationPreventionBytes(data.subarray(1));
break;
case 0x07:
event.nalUnitType = 'seq_parameter_set_rbsp';
Expand Down
142 changes: 103 additions & 39 deletions lib/m2ts/caption-stream.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,38 +20,62 @@
// payload type field to indicate how they are to be
// interpreted. CEAS-708 caption content is always transmitted with
// payload type 0x04.
var USER_DATA_REGISTERED_ITU_T_T35 = 4;
var USER_DATA_REGISTERED_ITU_T_T35 = 4,
RBSP_TRAILING_BITS = 128;

/**
* Parse a supplemental enhancement information (SEI) NAL unit.
* Stops parsing once a message of type ITU T T35 has been found.
*
* @param bytes {Uint8Array} the bytes of a SEI NAL unit
* @return {object} the parsed SEI payload
* @see Rec. ITU-T H.264, 7.3.2.3.1
*/
var parseSei = function(bytes) {
var result = {
payloadType: -1,
payloadSize: 0,
}, i;

// parse the payload type
// if the payload type is not user_data_registered_itu_t_t35,
// don't bother parsing any further
if (bytes[1] !== USER_DATA_REGISTERED_ITU_T_T35) {
return result;
}
result.payloadType = USER_DATA_REGISTERED_ITU_T_T35;
var
i = 0,
result = {
payloadType: -1,
payloadSize: 0,
},
payloadType = 0,
payloadSize = 0;

// go through the sei_rbsp parsing each each individual sei_message
while (i < bytes.byteLength) {
// stop once we have hit the end of the sei_rbsp
if (bytes[i] === RBSP_TRAILING_BITS) {
break;
}

// parse the payload size
for (i = 2; i < bytes.length && bytes[i] === 0xff; i++) {
result.payloadSize += 255;
}
result.payloadSize <<= 8;
result.payloadSize |= bytes[i];
i++;
// Parse payload type
while (bytes[i] === 0xFF) {
payloadType += 255;
i++;
}
payloadType += bytes[i++];

result.payload = bytes.subarray(i, i + result.payloadSize);
// Parse payload size
while (bytes[i] === 0xFF) {
payloadSize += 255;
i++;
}
payloadSize += bytes[i++];

// this sei_message is a 608/708 caption so save it and break
// there can only ever be one caption message in a frame's sei
if (!result.payload && payloadType === USER_DATA_REGISTERED_ITU_T_T35) {
result.payloadType = payloadType;
result.payloadSize = payloadSize;
result.payload = bytes.subarray(i, i + payloadSize);
break;
}

// skip the payload and parse the next message
i += payloadSize;
payloadType = 0;
payloadSize = 0;
}

return result;
};
Expand Down Expand Up @@ -115,21 +139,28 @@
};

var CaptionStream = function() {
var self = this;
CaptionStream.prototype.init.call(this);

this.captionPackets_ = [];

this.field1_ = new Cea608Stream();

// forward data and done events from field1_ to this CaptionStream
this.field1_.on('data', this.trigger.bind(this, 'data'));
this.field1_.on('done', this.trigger.bind(this, 'done'));
};
CaptionStream.prototype = new muxjs.utils.Stream();
CaptionStream.prototype.push = function(event) {
var sei, userData, captionPackets, i;
var sei, userData, captionPackets;

// only examine SEI NALs
if (event.nalUnitType !== 'sei_rbsp') {
return;
}

// parse the sei
sei = parseSei(event.data);
sei = parseSei(event.escapedRBSP);

// ignore everything but user_data_registered_itu_t_t35
if (sei.payloadType !== USER_DATA_REGISTERED_ITU_T_T35) {
Expand All @@ -144,22 +175,35 @@
return;
}

// parse out CC data packets
captionPackets = parseCaptionPackets(event.pts, userData);
// parse out CC data packets and save them for later
this.captionPackets_ = this.captionPackets_.concat(parseCaptionPackets(event.pts, userData));
};

// send the data to the appropriate field
for (i = 0; i < captionPackets.length; i++) {
if (captionPackets[i].type === 0) {
this.field1_.push(captionPackets[i]);
}
CaptionStream.prototype.flush = function () {
// make sure we actually parsed captions before proceeding
if (!this.captionPackets_.length) {
this.field1_.flush();
return;
}
};

// sort caption byte-pairs based on their PTS values
this.captionPackets_.sort(function(a, b) {
return a.pts - b.pts;
});

// Push each caption into Cea608Stream
this.captionPackets_.forEach(this.field1_.push, this.field1_);

this.captionPackets_.length = 0;
this.field1_.flush();
return;
};
// ----------------------
// Session to Application
// ----------------------

var BASIC_CHARACTER_TRANSLATION = {
0x2a: 0xe1,
0x5c: 0xe9,
0x5e: 0xed,
0x5f: 0xf3,
Expand All @@ -168,7 +212,6 @@
0x7c: 0xf7,
0x7d: 0xd1,
0x7e: 0xf1,
0x2a: 0xe1,
0x7f: 0x2588
};

Expand All @@ -186,8 +229,8 @@
ROLL_UP_2_ROWS = 0x1425,
ROLL_UP_3_ROWS = 0x1426,
ROLL_UP_4_ROWS = 0x1427,
RESUME_DIRECT_CAPTIONING = 0x1429,
CARRIAGE_RETURN = 0x142d,

// Erasure
BACKSPACE = 0x1421,
ERASE_DISPLAYED_MEMORY = 0x142c,
Expand Down Expand Up @@ -217,16 +260,29 @@
this.startPts_ = 0;
this.displayed_ = createDisplayBuffer();
this.nonDisplayed_ = createDisplayBuffer();
this.lastControlCode_ = null;

this.push = function(packet) {
var data, swap, charCode;
var data, swap, char0, char1;
// remove the parity bits
data = packet.ccData & 0x7f7f;

// ignore duplicate control codes
if (data === this.lastControlCode_) {
this.lastControlCode_ = null;
return;
}

// Store control codes
if ((data & 0xf000) === 0x1000) {
this.lastControlCode_ = data;
} else {
this.lastControlCode_ = null;
}

switch (data) {
case PADDING:
break;

case RESUME_CAPTION_LOADING:
this.mode_ = 'popOn';
break;
Expand Down Expand Up @@ -275,17 +331,25 @@
case ERASE_NON_DISPLAYED_MEMORY:
this.nonDisplayed_ = createDisplayBuffer();
break;

default:
charCode = data >>> 8;
char0 = data >>> 8;
char1 = data & 0xff;

// Look for a Channel 1 Preamble Address Code
if (char0 >= 0x10 && char0 <= 0x17 &&
char1 >= 0x40 && char1 <= 0x7F &&
(char0 !== 0x10 || char1 < 0x60)) {
// Follow Safari's lead and replace the PAC with a space
char0 = char1 = 0x20;
}

// ignore unsupported control codes
if ((charCode & 0xf0) === 0x10) {
if ((char0 & 0xf0) === 0x10) {
return;
}

// character handling is dependent on the current mode
this[this.mode_](packet.pts, charCode, data & 0xff);
this[this.mode_](packet.pts, char0, char1);
break;
}
};
Expand Down
26 changes: 10 additions & 16 deletions lib/m2ts/metadata-stream.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@
parseIso88591 = function(bytes, start, end) {
return window.unescape(percentEncode(bytes, start, end));
},
parseSyncSafeInteger = function (data) {
return (data[0] << 21) |
(data[1] << 14) |
(data[2] << 7) |
(data[3]);
},
tagParsers = {
'TXXX': function(tag) {
var i;
Expand Down Expand Up @@ -142,10 +148,7 @@
// last four bytes of the ID3 header.
// The most significant bit of each byte is dropped and the
// results concatenated to recover the actual value.
tagSize = (chunk.data[6] << 21) |
(chunk.data[7] << 14) |
(chunk.data[8] << 7) |
(chunk.data[9]);
tagSize = parseSyncSafeInteger(chunk.data.subarray(6, 10));

// ID3 reports the tag size excluding the header but it's more
// convenient for our comparisons to include it
Expand Down Expand Up @@ -176,26 +179,17 @@
if (tag.data[5] & 0x40) {
// advance the frame start past the extended header
frameStart += 4; // header size field
frameStart += (tag.data[10] << 24) |
(tag.data[11] << 16) |
(tag.data[12] << 8) |
(tag.data[13]);
frameStart += parseSyncSafeInteger(tag.data.subarray(10, 14));

// clip any padding off the end
tagSize -= (tag.data[16] << 24) |
(tag.data[17] << 16) |
(tag.data[18] << 8) |
(tag.data[19]);
tagSize -= parseSyncSafeInteger(tag.data.subarray(16, 20));
}

// parse one or more ID3 frames
// http://id3.org/id3v2.3.0#ID3v2_frame_overview
do {
// determine the number of bytes in this frame
frameSize = (tag.data[frameStart + 4] << 24) |
(tag.data[frameStart + 5] << 16) |
(tag.data[frameStart + 6] << 8) |
(tag.data[frameStart + 7]);
frameSize = parseSyncSafeInteger(tag.data.subarray(frameStart + 4, frameStart + 8));
if (frameSize < 1) {
return console.log('Malformed ID3 frame encountered. Skipping metadata parsing.');
}
Expand Down
Loading

0 comments on commit cb86179

Please sign in to comment.