mirror of
https://github.com/shaka-project/shaka-player.git
synced 2026-06-14 15:56:38 +03:00
38ce45dce5
Cleanup imported from an internal Google migration process, courtesy of Laura Harker. Change-Id: I11de518eafe6008938589e5250bdcaf8151267e9
582 lines
20 KiB
JavaScript
582 lines
20 KiB
JavaScript
/*! @license
|
|
* Shaka Player
|
|
* Copyright 2016 Google LLC
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
goog.require('shaka.cea.CeaDecoder');
|
|
goog.require('shaka.cea.CeaUtils');
|
|
goog.require('shaka.log');
|
|
goog.require('shaka.test.CeaUtils');
|
|
goog.require('shaka.text.Cue');
|
|
|
|
describe('CeaDecoder', () => {
|
|
const CeaUtils = shaka.test.CeaUtils;
|
|
|
|
/** @type {string} */
|
|
const DEFAULT_BG_COLOR = shaka.cea.CeaUtils.DEFAULT_BG_COLOR;
|
|
|
|
/**
|
|
* Initialization bytes for CC packet.
|
|
* Includes padding bytes, USA country code, and ATSC provider code.
|
|
* @type {!Uint8Array}
|
|
*/
|
|
const atscCaptionInitBytes = new Uint8Array([
|
|
0xb5, // USA country code.
|
|
0x00, 0x31, // ATSC provider code.
|
|
0x47, 0x41, 0x39, 0x34, // ATSC user identifier.
|
|
0x03, // User data type for cc_data.
|
|
]);
|
|
|
|
/** @type {!shaka.cea.CeaDecoder} */
|
|
const decoder = new shaka.cea.CeaDecoder();
|
|
|
|
describe('decodes CEA-608', () => {
|
|
const edmCodeByte2 = 0x2c; // Erase displayed memory byte 2.
|
|
|
|
// Blank padding control code between two control codes that are the same.
|
|
const blankPaddingControlCode = new Uint8Array([0x97, 0x23]);
|
|
|
|
// Erases displayed memory on every captioning mode.
|
|
const eraseDisplayedMemory = new Uint8Array([
|
|
...atscCaptionInitBytes, 0xc4, /* padding= */ 0xff,
|
|
0xfc, 0x94, edmCodeByte2, // EDM on CC1
|
|
0xfc, 0x1c, edmCodeByte2, // EDM on CC2
|
|
0xfd, 0x15, edmCodeByte2, // EDM on CC3
|
|
0xfd, 0x9d, edmCodeByte2, // EDM on CC4
|
|
]);
|
|
|
|
beforeEach(() => {
|
|
decoder.clear();
|
|
});
|
|
|
|
it('green and underlined popon caption data on CC3', () => {
|
|
const controlCount = 0x08;
|
|
const captionData = 0xc0 | controlCount;
|
|
const greenTextCC3Packet = new Uint8Array([
|
|
...atscCaptionInitBytes, captionData, /* padding= */ 0xff,
|
|
0xfd, 0x15, 0x20, // Pop-on mode (RCL control code)
|
|
0xfd, 0x13, 0xe3, // PAC to underline and color text green on last row.
|
|
0xfd, 0x67, 0xf2, // g, r
|
|
0xfd, 0xe5, 0xe5, // e, e
|
|
0xfd, 0x6e, 0x20, // n, space
|
|
0xfd, 0xf4, 0xe5, // t, e
|
|
0xfd, 0xf8, 0xf4, // x, t
|
|
0xfd, 0x15, 0x2f, // EOC
|
|
]);
|
|
|
|
const startTimeCaption1 = 1;
|
|
const startTimeCaption2 = 2;
|
|
const expectedText = 'green text';
|
|
|
|
const topLevelCue = new shaka.text.Cue(
|
|
startTimeCaption1, startTimeCaption2, '');
|
|
topLevelCue.nestedCues = [
|
|
CeaUtils.createStyledCue(
|
|
startTimeCaption1, startTimeCaption2, expectedText,
|
|
/* underline= */ true, /* italics= */ false,
|
|
/* textColor= */ 'green', /* backgroundColor= */ 'black'),
|
|
];
|
|
|
|
const expectedCaptions = [
|
|
{
|
|
stream: 'CC3',
|
|
cue: topLevelCue,
|
|
},
|
|
];
|
|
|
|
decoder.extract(greenTextCC3Packet, startTimeCaption1);
|
|
decoder.extract(eraseDisplayedMemory, startTimeCaption2);
|
|
const captions = decoder.decode();
|
|
|
|
expect(captions).toEqual(expectedCaptions);
|
|
});
|
|
|
|
it('popon captions that change color and underline midrow on CC2', () => {
|
|
const controlCount = 0x08;
|
|
const captionData = 0xc0 | controlCount;
|
|
const midrowStyleChangeCC2Packet = new Uint8Array([
|
|
...atscCaptionInitBytes, captionData, /* padding= */ 0xff,
|
|
0xfc, 0x1c, 0x20, // Pop-on mode (RCL control code).
|
|
0xfc, 0xad, 0xad, // -, -
|
|
0xfc, 0x19, 0x29, // Red + underline midrow style control code.
|
|
0xfc, 0xf2, 0xe5, // r, e
|
|
0xfc, 0x64, 0x80, // d, invalid
|
|
0xfc, 0x19, 0x20, // Midrow style control code to clear styles.
|
|
0xfc, 0xad, 0xad, // -, -
|
|
0xfc, 0x1c, 0x2f, // EOC
|
|
]);
|
|
|
|
const startTimeCaption1 = 1;
|
|
const startTimeCaption2 = 2;
|
|
const expectedText1 = '-- ';
|
|
const expectedText2 = 'red';
|
|
const expectedText3 = ' --';
|
|
|
|
// Since there are three style changes, there should be three nested cues.
|
|
const topLevelCue = new shaka.text.Cue(
|
|
startTimeCaption1, startTimeCaption2, '');
|
|
|
|
topLevelCue.nestedCues = [
|
|
CeaUtils.createDefaultCue(
|
|
startTimeCaption1, startTimeCaption2, expectedText1),
|
|
|
|
CeaUtils.createStyledCue(
|
|
startTimeCaption1, startTimeCaption2, expectedText2,
|
|
/* underline= */ true, /* italics= */ false,
|
|
/* textColor= */ 'red', /* backgroundColor= */ DEFAULT_BG_COLOR),
|
|
|
|
CeaUtils.createDefaultCue(
|
|
startTimeCaption1, startTimeCaption2, expectedText3),
|
|
];
|
|
|
|
const expectedCaptions = [
|
|
{
|
|
stream: 'CC2',
|
|
cue: topLevelCue,
|
|
},
|
|
];
|
|
|
|
decoder.extract(midrowStyleChangeCC2Packet, startTimeCaption1);
|
|
decoder.extract(eraseDisplayedMemory, startTimeCaption2);
|
|
const captions = decoder.decode();
|
|
|
|
expect(captions).toEqual(expectedCaptions);
|
|
});
|
|
|
|
it('italicized popon captions on a yellow background on CC2', () => {
|
|
const controlCount = 0x08;
|
|
const captionData = 0xc0 | controlCount;
|
|
const midrowStyleChangeCC2Packet = new Uint8Array([
|
|
...atscCaptionInitBytes, captionData, /* padding= */ 0xff,
|
|
0xfc, 0x1c, 0x20, // Pop-on mode (RCL control code).
|
|
0xfc, 0x19, 0x6e, // White Italics PAC.
|
|
0xfc, 0x98, 0x2a, // Background attribute yellow.
|
|
0xfc, 0xf4, 0xe5, // t, e
|
|
0xfc, 0x73, 0xf4, // s, t
|
|
0xfc, 0x19, 0x20, // Midrow style control code to clear styles.
|
|
0xfc, 0x98, 0x20, // Background attribute to clear background.
|
|
0xfc, 0x1c, 0x2f, // EOC
|
|
]);
|
|
|
|
const startTimeCaption1 = 1;
|
|
const startTimeCaption2 = 2;
|
|
const expectedText = 'test';
|
|
|
|
// A single nested cue containing yellow, italicized text.
|
|
const topLevelCue = new shaka.text.Cue(startTimeCaption1,
|
|
startTimeCaption2, '');
|
|
topLevelCue.nestedCues = [
|
|
CeaUtils.createStyledCue(
|
|
startTimeCaption1, startTimeCaption2, expectedText,
|
|
/* underline= */ false, /* italics= */ true,
|
|
/* textColor= */ 'white', /* backgroundColor= */ 'yellow'),
|
|
];
|
|
|
|
const expectedCaptions = [{
|
|
stream: 'CC2',
|
|
cue: topLevelCue,
|
|
}];
|
|
|
|
decoder.extract(midrowStyleChangeCC2Packet, startTimeCaption1);
|
|
decoder.extract(eraseDisplayedMemory, startTimeCaption2);
|
|
const captions = decoder.decode();
|
|
|
|
expect(captions).toEqual(expectedCaptions);
|
|
});
|
|
|
|
it('popon captions with special characters on CC2', () => {
|
|
const controlCount = 0x07;
|
|
const captionData = 0xc0 | controlCount;
|
|
const midrowStyleChangeCC2Packet = new Uint8Array([
|
|
...atscCaptionInitBytes, captionData, /* padding= */ 0xff,
|
|
0xfc, 0x1c, 0x20, // Pop-on mode (RCL control code).
|
|
0xfc, 0x19, 0x37, // Special North American character (♪)
|
|
0xfc, 0x20, 0x80, // SP, invalid. SP will be replaced by extended char.
|
|
0xfc, 0x1a, 0x25, // Extended Spanish/Misc character (ü)
|
|
0xfc, 0x20, 0x80, // SP, invalid.
|
|
0xfc, 0x9b, 0xb9, // Extended German/Danish character (å)
|
|
0xfc, 0x1c, 0x2f, // EOC
|
|
]);
|
|
|
|
const startTimeCaption1 = 1;
|
|
const startTimeCaption2 = 2;
|
|
const expectedText = '♪üå';
|
|
|
|
const topLevelCue = new shaka.text.Cue(startTimeCaption1,
|
|
startTimeCaption2, '');
|
|
topLevelCue.nestedCues = [
|
|
CeaUtils.createDefaultCue(
|
|
startTimeCaption1, startTimeCaption2, expectedText),
|
|
];
|
|
|
|
const expectedCaptions = [{
|
|
stream: 'CC2',
|
|
cue: topLevelCue,
|
|
}];
|
|
|
|
decoder.extract(midrowStyleChangeCC2Packet, startTimeCaption1);
|
|
decoder.extract(eraseDisplayedMemory, startTimeCaption2);
|
|
const captions = decoder.decode();
|
|
expect(captions).toEqual(expectedCaptions);
|
|
});
|
|
|
|
it('painton captions on CC1', () => {
|
|
const controlCount = 0x03;
|
|
const captionData = 0xc0 | controlCount;
|
|
const paintonCaptionCC1Packet = new Uint8Array([
|
|
...atscCaptionInitBytes, captionData, /* padding= */ 0xff,
|
|
0xfc, 0x94, 0x29, // Paint-on mode (RDC control code).
|
|
0xfc, 0xf4, 0xe5, // t, e
|
|
0xfc, 0x73, 0xf4, // s, t
|
|
]);
|
|
|
|
const startTimeCaption1 = 1;
|
|
const startTimeCaption2 = 2;
|
|
const expectedText = 'test';
|
|
|
|
const topLevelCue = new shaka.text.Cue(startTimeCaption1,
|
|
startTimeCaption2, '');
|
|
topLevelCue.nestedCues = [
|
|
CeaUtils.createDefaultCue(
|
|
startTimeCaption1, startTimeCaption2, expectedText),
|
|
];
|
|
|
|
const expectedCaptions = [{
|
|
stream: 'CC1',
|
|
cue: topLevelCue,
|
|
}];
|
|
|
|
decoder.extract(paintonCaptionCC1Packet, startTimeCaption1);
|
|
decoder.extract(eraseDisplayedMemory, startTimeCaption2);
|
|
const captions = decoder.decode();
|
|
|
|
expect(captions).toEqual(expectedCaptions);
|
|
});
|
|
|
|
it('rollup captions (2 lines) on CC1', () => {
|
|
const controlCount1 = 0x03;
|
|
const controlCount2 = 0x02;
|
|
const stream = 'CC1';
|
|
const time1 = 1;
|
|
const time2 = 2;
|
|
const time3 = 3;
|
|
const time4 = 4;
|
|
const time5 = 5;
|
|
|
|
// Carriage return on CC1
|
|
const carriageReturnControlCode = new Uint8Array([0x94, 0xad]);
|
|
const packets = [
|
|
new Uint8Array([
|
|
...atscCaptionInitBytes, 0xc0 | controlCount1, /* padding= */ 0xff,
|
|
0xfc, 0x94, 0x25, // Roll-up 2 rows control code.
|
|
0xfc, ...carriageReturnControlCode,
|
|
0xfc, ...blankPaddingControlCode,
|
|
]),
|
|
new Uint8Array([
|
|
...atscCaptionInitBytes, 0xc0 | controlCount1, /* padding= */ 0xff,
|
|
0xfc, 0x31, 0xae, // 1, .
|
|
0xfc, ...carriageReturnControlCode,
|
|
0xfc, ...blankPaddingControlCode,
|
|
]),
|
|
new Uint8Array([
|
|
...atscCaptionInitBytes, 0xc0 | controlCount1, /* padding= */ 0xff,
|
|
0xfc, 0x32, 0xae, // 2, .
|
|
0xfc, ...carriageReturnControlCode,
|
|
0xfc, ...blankPaddingControlCode,
|
|
]),
|
|
new Uint8Array([
|
|
...atscCaptionInitBytes, 0xc0 | controlCount1, /* padding= */ 0xff,
|
|
0xfc, 0xb3, 0xae, // 3, .
|
|
0xfc, ...carriageReturnControlCode,
|
|
0xfc, ...blankPaddingControlCode,
|
|
]),
|
|
new Uint8Array([
|
|
...atscCaptionInitBytes, 0xc0 | controlCount2, /* padding= */ 0xff,
|
|
0xfc, 0x34, 0xae, // 4, .
|
|
0xfc, 0x94, 0x2f, // EOC
|
|
]),
|
|
];
|
|
|
|
for (let i = 0; i < packets.length; i++) {
|
|
decoder.extract(packets[i], i+1);
|
|
}
|
|
decoder.extract(eraseDisplayedMemory, 6);
|
|
|
|
// Top level cue corresponding to the first closed caption.
|
|
const topLevelCue1 = new shaka.text.Cue(
|
|
/* startTime= */ time1, /* endTime= */ time2, '');
|
|
topLevelCue1.nestedCues = [
|
|
CeaUtils.createDefaultCue(
|
|
/* startTime= */ time1, /* endTime= */ time2, /* payload= */ '1.'),
|
|
];
|
|
|
|
// Top level cue corresponding to the second closed caption.
|
|
const topLevelCue2 = new shaka.text.Cue(
|
|
/* startTime= */ time2, /* endTime= */ time3, '');
|
|
topLevelCue2.nestedCues = [
|
|
CeaUtils.createDefaultCue(
|
|
/* startTime= */ time2, /* endTime= */ time3, /* payload= */ '1.'),
|
|
|
|
CeaUtils.createLineBreakCue(
|
|
/* startTime= */ time2, /* endTime= */ time3),
|
|
|
|
CeaUtils.createDefaultCue(
|
|
/* startTime= */ time2, /* endTime= */ time3, /* payload= */ '2.'),
|
|
];
|
|
|
|
// Top level cue corresponding to the third closed caption.
|
|
const topLevelCue3 = new shaka.text.Cue(
|
|
/* startTime= */ time3, /* endTime= */ time4, '');
|
|
topLevelCue3.nestedCues = [
|
|
CeaUtils.createDefaultCue(
|
|
/* startTime= */ time3, /* endTime= */ time4, /* payload= */ '2.'),
|
|
|
|
CeaUtils.createLineBreakCue(
|
|
/* startTime= */ time3, /* endTime= */ time4),
|
|
|
|
CeaUtils.createDefaultCue(
|
|
/* startTime= */ time3, /* endTime= */ time4, /* payload= */ '3.'),
|
|
];
|
|
|
|
// Top level cue corresponding to the fourth closed caption.
|
|
const topLevelCue4 = new shaka.text.Cue(
|
|
/* startTime= */ time4, /* endTime= */ time5, '');
|
|
topLevelCue4.nestedCues = [
|
|
CeaUtils.createDefaultCue(
|
|
/* startTime= */ time4, /* endTime= */ time5, /* payload= */ '3.'),
|
|
|
|
CeaUtils.createLineBreakCue(
|
|
/* startTime= */ time4, /* endTime= */ time5),
|
|
|
|
CeaUtils.createDefaultCue(
|
|
/* startTime= */ time4, /* endTime= */ time5, /* payload= */ '4.'),
|
|
];
|
|
|
|
const expectedCaptions = [
|
|
{
|
|
stream,
|
|
cue: topLevelCue1,
|
|
},
|
|
{
|
|
stream,
|
|
cue: topLevelCue2,
|
|
},
|
|
{
|
|
stream,
|
|
cue: topLevelCue3,
|
|
},
|
|
{
|
|
stream,
|
|
cue: topLevelCue4,
|
|
},
|
|
];
|
|
|
|
const captions = decoder.decode();
|
|
|
|
expect(captions).toEqual(expectedCaptions);
|
|
});
|
|
|
|
it('PAC shifts entire 2-line rollup window to a new row on CC1', () => {
|
|
const controlCount1 = 0x03;
|
|
const controlCount2 = 0x02;
|
|
const stream = 'CC1';
|
|
|
|
// Carriage return on CC1
|
|
const carriageReturnControlCode = new Uint8Array([0x94, 0xad]);
|
|
const packets = [
|
|
new Uint8Array([
|
|
...atscCaptionInitBytes, 0xc0 | controlCount1, /* padding= */ 0xff,
|
|
0xfc, 0x94, 0x25, // Roll-up 2 rows control code.
|
|
0xfc, ...carriageReturnControlCode,
|
|
0xfc, 0x97, 0x23, // Blank padding control code
|
|
]),
|
|
new Uint8Array([
|
|
...atscCaptionInitBytes, 0xc0 | controlCount1, /* padding= */ 0xff,
|
|
0xfc, 0x31, 0xae, // 1, .
|
|
0xfc, ...carriageReturnControlCode,
|
|
0xfc, 0x97, 0x23, // Blank padding control code
|
|
]),
|
|
new Uint8Array([
|
|
...atscCaptionInitBytes, 0xc0 | controlCount2, /* padding= */ 0xff,
|
|
0xfc, 0x32, 0xae, // 2, .
|
|
0xfc, 0x92, 0xe0, // PAC control code to move to row 4.
|
|
]),
|
|
];
|
|
|
|
for (let i = 0; i < packets.length; i++) {
|
|
decoder.extract(packets[i], i+1);
|
|
}
|
|
decoder.extract(eraseDisplayedMemory, 3);
|
|
|
|
// Top level cue corresponding to the first closed caption.
|
|
const topLevelCue1 = new shaka.text.Cue(/* startTime= */ 1,
|
|
/* endTime= */ 2, '');
|
|
topLevelCue1.nestedCues = [
|
|
CeaUtils.createDefaultCue(
|
|
/* startTime= */ 1, /* endTime= */ 2, /* payload= */ '1.'),
|
|
];
|
|
|
|
// Top level cue corresponding to the second closed caption.
|
|
const topLevelCue2 = new shaka.text.Cue(/* startTime= */ 2,
|
|
/* endTime= */ 3, '');
|
|
topLevelCue2.nestedCues = [
|
|
CeaUtils.createDefaultCue(
|
|
/* startTime= */ 2, /* endTime= */ 3, /* payload= */ '1.'),
|
|
|
|
CeaUtils.createLineBreakCue(/* startTime= */ 2, /* endTime= */ 3),
|
|
|
|
CeaUtils.createDefaultCue(
|
|
/* startTime= */ 2, /* endTime= */ 3, /* payload= */ '2.'),
|
|
];
|
|
|
|
const expectedCaptions = [
|
|
{
|
|
stream,
|
|
cue: topLevelCue1,
|
|
},
|
|
{
|
|
stream,
|
|
cue: topLevelCue2,
|
|
},
|
|
];
|
|
|
|
const captions = decoder.decode();
|
|
|
|
expect(captions).toEqual(expectedCaptions);
|
|
});
|
|
|
|
it('does not emit text sent while in CEA-608 Text Mode', () => {
|
|
const controlCount = 0x03;
|
|
const captionData = 0xc0 | controlCount;
|
|
const textModePacket = new Uint8Array([
|
|
...atscCaptionInitBytes, captionData, /* padding= */ 0xff,
|
|
0xfc, 0x94, 0x2a, // Text mode (Text restart control code).
|
|
0xfc, 0xf4, 0xe5, // t, e
|
|
0xfc, 0x73, 0xf4, // s, t
|
|
]);
|
|
|
|
const startTimeCaption1 = 1;
|
|
const startTimeCaption2 = 2;
|
|
|
|
decoder.extract(textModePacket, startTimeCaption1);
|
|
decoder.extract(eraseDisplayedMemory, startTimeCaption2);
|
|
|
|
const captions = decoder.decode();
|
|
expect(captions).toEqual([]);
|
|
});
|
|
|
|
it('resets the decoder on >=45 consecutive bad frames', () => {
|
|
// CEA-608-B C.21 says to reset the decoder after 45 invalid frames.
|
|
const controlCount = 0x0f;
|
|
const captionData = 0xc0 | controlCount;
|
|
const badFrames = [];
|
|
const badFrameCount = 15;
|
|
for (let i = 0; i<badFrameCount; i++) {
|
|
// Without loss of generality, the bad frames will be sent on CC1.
|
|
badFrames.push(0xfc, 0x0, 0x0);
|
|
}
|
|
|
|
const badFramesBuffer = new Uint8Array([
|
|
...atscCaptionInitBytes, captionData, /* padding= */ 0xff,
|
|
...new Uint8Array(badFrames),
|
|
]);
|
|
|
|
// 3*15 = 45 total bad frames extracted.
|
|
for (let i = 0; i < 3; i++) {
|
|
decoder.extract(badFramesBuffer, i+1);
|
|
}
|
|
|
|
spyOn(decoder, 'reset').and.callThrough();
|
|
decoder.decode();
|
|
|
|
expect(decoder.reset).toHaveBeenCalledTimes(1);
|
|
});
|
|
});
|
|
|
|
describe('decodes CEA-708', () => {
|
|
// Hide window (2 bytes), with a bitmap provided to indicate all windows.
|
|
const hideWindow = new Uint8Array([
|
|
...atscCaptionInitBytes, 0xc2, /* padding= */ 0xff,
|
|
0xff, 0x02, 0x22, // Service #1, and 2 bytes will follow.
|
|
0xfe, 0x8a, 0xff,
|
|
]);
|
|
|
|
it('well-formed caption packet that contains valid control codes', () => {
|
|
const startTime = 1;
|
|
const endTime = 2;
|
|
const bytePairCount = 0x07;
|
|
const captionData = 0xc0 | bytePairCount;
|
|
const serviceNumber = 1;
|
|
const cea708Packet = new Uint8Array([
|
|
...atscCaptionInitBytes, captionData, /* padding= */ 0xff,
|
|
// Byte 1 (0x07) is a DTVCC_PACKET_START that states 7 * 2 - 1 bytes
|
|
// will follow. Byte 2 is a service block header that selects service #
|
|
// and states that there are 12 bytes that will follow in the block.
|
|
0xff, 0x07, (serviceNumber << 5) | 12,
|
|
|
|
// Define window (7 bytes). Visible window #0 with 10 rows, 10 columns.
|
|
0xfe, 0x98, 0x38,
|
|
0xfe, 0x00, 0x00,
|
|
0xfe, 0x0a, 0x0a,
|
|
0xfe, 0x00,
|
|
|
|
// Series of G0 control codes that add text
|
|
0x74, // t
|
|
0xfe, 0x65, 0x73, // e, s
|
|
0xfe, 0x74, 0x00, // t, padding
|
|
]);
|
|
|
|
decoder.extract(cea708Packet, startTime);
|
|
decoder.extract(hideWindow, endTime);
|
|
|
|
const text = 'test';
|
|
const topLevelCue = new shaka.text.Cue(startTime, endTime, '');
|
|
topLevelCue.nestedCues = [
|
|
CeaUtils.createDefaultCue(startTime, endTime, /* payload= */ text),
|
|
];
|
|
|
|
const expectedCaptions = [
|
|
{
|
|
stream: 'svc1',
|
|
cue: topLevelCue,
|
|
},
|
|
];
|
|
|
|
const captions = decoder.decode();
|
|
expect(captions).toEqual(expectedCaptions);
|
|
});
|
|
|
|
it('service block contains a corrupted header', () => {
|
|
const startTime = 1;
|
|
const endTime = 2;
|
|
const bytePairCount = 0x02;
|
|
const captionData = 0xc0 | bytePairCount;
|
|
const serviceNumber = 1;
|
|
const cea708Packet = new Uint8Array([
|
|
...atscCaptionInitBytes, captionData, /* padding= */ 0xff,
|
|
// Byte 1 (0x01) is DTVCC_PACKET_START that states 1 * 2 - 1 bytes
|
|
// will follow. Byte 2 is a service block header that selects service #
|
|
// and states that there are 12 bytes that will follow in the block.
|
|
0xff, 0x01, (serviceNumber << 5) | 12,
|
|
0xfe, 0x00, 0x00,
|
|
]);
|
|
|
|
// The data corrupted, since the service block header claimed 12 bytes
|
|
// would follow, but only two bytes followed.
|
|
decoder.extract(cea708Packet, startTime);
|
|
decoder.extract(hideWindow, endTime);
|
|
|
|
// Then we should have warned of the invalid data and stopped processing
|
|
// the block without interrupting playback.
|
|
spyOn(shaka.log, 'warnOnce').and.callThrough();
|
|
|
|
const captions = decoder.decode();
|
|
expect(shaka.log.warnOnce).toHaveBeenCalledWith('CEA708_INVALID_DATA',
|
|
'Buffer read out of bounds / invalid CEA-708 Data.');
|
|
expect(shaka.log.warnOnce).toHaveBeenCalledTimes(1);
|
|
expect(captions).toEqual([]);
|
|
});
|
|
});
|
|
});
|