├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── browser-test └── index.html ├── lib └── mpegtssections.js ├── package.json └── test └── tests.js /.gitignore: -------------------------------------------------------------------------------- 1 | /browser-test/app.js 2 | /node_modules 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014, CableLabs, Inc. 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 5 | 6 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 7 | 8 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 9 | 10 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 11 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: hint prepare-test-browser test test-chrome test-firefox test-node 2 | 3 | test: hint test-node 4 | 5 | hint: node_modules/.bin/jshint 6 | node_modules/.bin/jshint --show-non-errors lib test 7 | 8 | test-node: node_modules/.bin/nodeunit 9 | node_modules/.bin/nodeunit test 10 | 11 | test-chrome: prepare-test-browser 12 | google-chrome browser-test/index.html 13 | 14 | test-firefox: prepare-test-browser 15 | firefox browser-test/index.html 16 | 17 | prepare-test-browser: browser-test/app.js node_modules/nodeunit/dist/browser/nodeunit.js 18 | 19 | node_modules/.bin/gluejs: node_modules 20 | node_modules/.bin/jshint: node_modules 21 | node_modules/.bin/nodeunit: node_modules 22 | node_modules/nodeunit: node_modules 23 | node_modules: package.json 24 | npm install 25 | 26 | browser-test/app.js: node_modules/.bin/gluejs lib/* test/* 27 | ./node_modules/.bin/gluejs --global-require --include ./lib/ --include ./test/ --include ./node_modules/nodeunit/lib/ --main test/tests.js --out ./browser-test/app.js 28 | 29 | node_modules/nodeunit/dist/browser/nodeunit.js: node_modules/nodeunit 30 | cd node_modules/nodeunit && npm install 31 | cd node_modules/nodeunit && make browser 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MPEG-TS Sections in JavaScript 2 | 3 | Author: Brendan Long 4 | 5 | CRC-32 code adapted from the output of [Thomas Pircher's (MIT licensed) pycrc][pycrc]. 6 | 7 | This library decodes [MPEG-TS program-specific information][mpegts-psi] into JSON. It's intended to be used with [HTML5 DataCues][datacue], but will work in any case where you have an `ArrayBuffer`. 8 | 9 | For additional information, you may want to consult the [ISO 13818-1 spec][iso-13818-1]. 10 | 11 | ## Using this library 12 | 13 | To use, copy mpegtssections.js into your application's script directory, then add something like this to the `` of your HTML page(s): 14 | 15 | 16 | 17 | You can then use `MpegTs` methods in your JavaScript code. 18 | 19 | ## Testing 20 | 21 | You can run the tests using Node.js: 22 | 23 | cd ~/mpegtssection-js 24 | npm install 25 | npm test # runs unit tests 26 | npm run-script hint # runs JSHint 27 | 28 | See `test/tests.js` for the [Nodeunit][nodeunit] tests. 29 | 30 | [JSHint][jshint] follows directives in the `/*jslint ... */` block at the top of each JavaScript file. 31 | 32 | ## API Documentation 33 | 34 | ### Data Structures 35 | 36 | The following uses [Web IDL][webidl] syntax to describe the resulting data structures. The attribute names are taken directly from the MPEG-TS spec (except changed to CamelCase to match JavaScript conventions). Data which isn't only needed for parsing or validation is validated, but not exposed. 37 | 38 | #### Generic MPEG-TS Section 39 | 40 | interface MpegTsSyntaxSection { 41 | attribute unsigned short tableIdExtension; 42 | attribute octet versionNumber; 43 | attribute boolean currentNextIndicator; 44 | attribute octet sectionNumber; 45 | attribute octet lastSectionNumber; 46 | } 47 | 48 | interface MpegTsSection { 49 | attribute octet tableId; 50 | attribute MpegTsSyntaxSection? syntaxSection; 51 | } 52 | 53 | interface MpegTsDescriptor { 54 | attribute octet tag; 55 | attribute ArrayBuffer data; 56 | } 57 | 58 | #### Program Association Section 59 | 60 | See Table 2-25 - Program association section 61 | 62 | interface MpegTsPatProgramInfo { 63 | attribute unsigned short programNumber; 64 | 65 | // this is the network_PID if programNumber == 0, 66 | // or the program_map_PID if programNumber != 0 67 | attribute unsigned short pid; 68 | } 69 | 70 | interface MpegTsPat implements MpegTsSection { 71 | attribute unsigned short transportStreamId; 72 | attribute MpegTsPatProgramInfo[] programInfo; 73 | } 74 | 75 | #### Conditional Access Table 76 | 77 | See Table 2-27 - Conditional access section. 78 | 79 | interface MpegTsCat implements MpegTsSection { 80 | attribute MpegTsDescriptor[] descriptors; 81 | } 82 | 83 | #### Program Map Table 84 | 85 | See Table 2-28 - Transport Stream program map section. 86 | 87 | interface MpegTsElementaryStream { 88 | attribute octet streamType; 89 | attribute unsigned short elementaryPID; 90 | attribute MpegTsDescriptor[] descriptors; 91 | } 92 | 93 | interface MpegTsPmt implements MpegTsSection { 94 | attribute unsigned short programNumber; 95 | attribute unsigned short? pcrPID; // 8191 maps to null 96 | attribute MpegTsDescriptor[] descriptors; 97 | attribute MpegTsElementaryStreamData[] streams; 98 | } 99 | 100 | #### Private Section 101 | 102 | See Table 2-30 - Private Section 103 | 104 | interface MpegTsPrivateSection implements MpegTsSection { 105 | attribute boolean privateIndicator; 106 | attribute MpegTsSyntaxSection? syntaxSection; 107 | ArrayBuffer privateData; 108 | } 109 | 110 | #### Transport Stream Description 111 | 112 | See Table 2-30-1 - The Transport Stream Description Table 113 | 114 | interface MpegTsDescriptionSection implements MpegTsSection { 115 | attribute MpegTsDescriptor[] descriptors; 116 | } 117 | 118 | #### Functions 119 | 120 | `String MpegTs.decodeSection(ArrayBuffer buf)` 121 | 122 | If `buf` is a PSI table (starting with the `table_id`), it will be decoded into the most appropriate type, using the following algorithm: 123 | 124 | 1. If there are any serious problems with the data (lengths are wrong), throw a `BadSizeError`. 125 | 2. If `table_id` is 0, 1, 2, or 3 (PAT, CAT, PMT, Description), throw a `MissingSyntaxSectionError` if `syntax_section_indicator` is not 1. 126 | 3. If `syntax_section_indicator` is 1, throw an `InvalidCrcError` if the CRC-32 of the buffer is not 0. 127 | 4. If the `table_id` is 0, return an `MpegTsPat`. 128 | 5. If the `table_id` is 1, return an `MpegTsCat`. 129 | 6. If the `table_id` is 2, return an `MpegTsPmt`. 130 | 7. If the `table_id` is 3, return an `MpegTsDescriptionSection`. 131 | 8. If the `table_id` is >= 128, return an `MpegTsPrivateSection`. 132 | 9. Return an `MpegTsSection`. 133 | 134 | `unsigned short MpegTs.calculateCrc32(ArrayBuffer buf)` 135 | 136 | Calculates the CRC32/MPEG-2 of the given data. If you want to calculate the CRC of just part of the buffer, use `ArrayBuffer.slice()`. For a valid MPEG-TS section with a syntax section, the CRC-32 of all of the data from the `table_id` to the `CRC_32` should be 0. This check will be run automatically by `decodeSection()`. 137 | 138 | [datacue]: http://www.w3.org/html/wg/drafts/html/CR/embedded-content-0.html#datacue 139 | [iso-13818-1]: http://www.iso.org/iso/home/store/catalogue_ics/catalogue_detail_ics.htm?csnumber=62074 140 | [jshint]: http://www.jshint.com/about/ 141 | [mpegts-psi]: http://en.wikipedia.org/wiki/Program-specific_information 142 | [nodeunit]: https://github.com/caolan/nodeunit 143 | [pycrc]: https://github.com/tpircher/pycrc 144 | [webidl]: http://www.w3.org/TR/WebIDL/ 145 | -------------------------------------------------------------------------------- /browser-test/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | MPEG-TS Sections JS 6 | 7 | 8 | 9 | 10 | 11 |

MPEG-TS Sections JS Test Suite

12 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /lib/mpegtssections.js: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2014, CableLabs, Inc. 2 | * All rights reserved. 3 | * 4 | * Redistribution and use in source and binary forms, with or without 5 | * modification, are permitted provided that the following conditions are met: 6 | * 7 | * 1. Redistributions of source code must retain the above copyright notice, 8 | * this list of conditions and the following disclaimer. 9 | * 10 | * 2. Redistributions in binary form must reproduce the above copyright notice, 11 | * this list of conditions and the following disclaimer in the documentation 12 | * and/or other materials provided with the distribution. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 18 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 19 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 20 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 21 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 22 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 23 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 24 | * POSSIBILITY OF SUCH DAMAGE. 25 | */ 26 | /*jslint browser: true, node: true, bitwise: true, plusplus: true, vars: true, 27 | white: true, indent: 4, maxlen: 80 */ 28 | /*jshint strict: true, shadow: true, camelcase: true, curly: true, 29 | quotmark: double, latedef: true, undef: true, unused: true, trailing: true */ 30 | /*global Uint8Array */ 31 | (function (exports) { 32 | "use strict"; 33 | 34 | /* Constants */ 35 | var TableIds = { 36 | PROGRAM_ASSOCIATION_SECTION: 0, 37 | CONDITIONAL_ACCESS_SECTION: 1, 38 | TS_PROGRAM_MAP_SECTION: 2, 39 | TS_DESCRIPTION_SECTION: 3, 40 | ISO_IEC_14496_SCENE_DESCRIPTION_SECTION: 4, 41 | ISO_IEC_14496_OBJECT_DESCRIPTION_SECTION: 5, 42 | getName: function(id) { 43 | switch (id) { 44 | case TableIds.PROGRAM_ASSOCIATION_SECTION: 45 | return "PROGRAM_ASSOCIATION_SECTION"; 46 | case TableIds.CONDITIONAL_ACCESS_SECTION: 47 | return "CONDITIONAL_ACCESS_SECTION"; 48 | case TableIds.TS_PROGRAM_MAP_SECTION: 49 | return "TS_PROGRAM_MAP_SECTION"; 50 | case TableIds.TS_DESCRIPTION_SECTION: 51 | return "TS_DESCRIPTION_SECTION"; 52 | case TableIds.ISO_IEC_14496_SCENE_DESCRIPTION_SECTION: 53 | return "ISO_IEC_14496_SCENE_DESCRIPTION_SECTION"; 54 | case TableIds.ISO_IEC_14496_OBJECT_DESCRIPTION_SECTION: 55 | return "ISO_IEC_14496_OBJECT_DESCRIPTION_SECTION"; 56 | } 57 | } 58 | }; 59 | 60 | var ReservedPids = { 61 | PROGRAM_ASSOCIATION: 0, 62 | CONDITIONAL_ACCESS: 1, 63 | TRANSPORT_STREAM_DESCRIPTION: 2 64 | }; 65 | 66 | /* Errors */ 67 | function defineError(name) { 68 | function NewError(message) { 69 | this.name = name; 70 | this.message = message; 71 | 72 | this.stack = (new Error(message)).stack; 73 | /* Cleanup the stack */ 74 | if (typeof this.stack === "string") { 75 | var stack = this.stack.split("\n"); 76 | if (stack.length > 0) { 77 | if (stack[0].lastIndexOf("Error") === 0) { 78 | /* Chrome-like browsers */ 79 | stack[0] = name; 80 | if (message) { 81 | stack[0] += ": " + message; 82 | } 83 | stack.splice(1, 1); 84 | } else if (stack[0].lastIndexOf("NewError@") === 0) { 85 | /* Firefox-like browsers */ 86 | stack.splice(0, 1); 87 | } 88 | this.stack = stack.join("\n"); 89 | } 90 | } 91 | } 92 | NewError.prototype = new Error(); 93 | NewError.prototype.constructor = NewError; 94 | return NewError; 95 | } 96 | var BadSizeError = defineError("BadSizeError"); 97 | var InvalidCrcError = defineError("InvalidCrcError"); 98 | var MissingSyntaxSectionError = defineError("MissingSyntaxSectionError"); 99 | 100 | /* Functions */ 101 | /* CRC functions are adapted from the output of pycrc by Thomas Pircher 102 | * (MIT License) 103 | * https://github.com/tpircher/pycrc */ 104 | var crcTable = [0x00000000, 0x04c11db7, 0x09823b6e, 0x0d4326d9, 0x130476dc, 105 | 0x17c56b6b, 0x1a864db2, 0x1e475005, 0x2608edb8, 0x22c9f00f, 0x2f8ad6d6, 106 | 0x2b4bcb61, 0x350c9b64, 0x31cd86d3, 0x3c8ea00a, 0x384fbdbd, 0x4c11db70, 107 | 0x48d0c6c7, 0x4593e01e, 0x4152fda9, 0x5f15adac, 0x5bd4b01b, 0x569796c2, 108 | 0x52568b75, 0x6a1936c8, 0x6ed82b7f, 0x639b0da6, 0x675a1011, 0x791d4014, 109 | 0x7ddc5da3, 0x709f7b7a, 0x745e66cd, 0x9823b6e0, 0x9ce2ab57, 0x91a18d8e, 110 | 0x95609039, 0x8b27c03c, 0x8fe6dd8b, 0x82a5fb52, 0x8664e6e5, 0xbe2b5b58, 111 | 0xbaea46ef, 0xb7a96036, 0xb3687d81, 0xad2f2d84, 0xa9ee3033, 0xa4ad16ea, 112 | 0xa06c0b5d, 0xd4326d90, 0xd0f37027, 0xddb056fe, 0xd9714b49, 0xc7361b4c, 113 | 0xc3f706fb, 0xceb42022, 0xca753d95, 0xf23a8028, 0xf6fb9d9f, 0xfbb8bb46, 114 | 0xff79a6f1, 0xe13ef6f4, 0xe5ffeb43, 0xe8bccd9a, 0xec7dd02d, 0x34867077, 115 | 0x30476dc0, 0x3d044b19, 0x39c556ae, 0x278206ab, 0x23431b1c, 0x2e003dc5, 116 | 0x2ac12072, 0x128e9dcf, 0x164f8078, 0x1b0ca6a1, 0x1fcdbb16, 0x018aeb13, 117 | 0x054bf6a4, 0x0808d07d, 0x0cc9cdca, 0x7897ab07, 0x7c56b6b0, 0x71159069, 118 | 0x75d48dde, 0x6b93dddb, 0x6f52c06c, 0x6211e6b5, 0x66d0fb02, 0x5e9f46bf, 119 | 0x5a5e5b08, 0x571d7dd1, 0x53dc6066, 0x4d9b3063, 0x495a2dd4, 0x44190b0d, 120 | 0x40d816ba, 0xaca5c697, 0xa864db20, 0xa527fdf9, 0xa1e6e04e, 0xbfa1b04b, 121 | 0xbb60adfc, 0xb6238b25, 0xb2e29692, 0x8aad2b2f, 0x8e6c3698, 0x832f1041, 122 | 0x87ee0df6, 0x99a95df3, 0x9d684044, 0x902b669d, 0x94ea7b2a, 0xe0b41de7, 123 | 0xe4750050, 0xe9362689, 0xedf73b3e, 0xf3b06b3b, 0xf771768c, 0xfa325055, 124 | 0xfef34de2, 0xc6bcf05f, 0xc27dede8, 0xcf3ecb31, 0xcbffd686, 0xd5b88683, 125 | 0xd1799b34, 0xdc3abded, 0xd8fba05a, 0x690ce0ee, 0x6dcdfd59, 0x608edb80, 126 | 0x644fc637, 0x7a089632, 0x7ec98b85, 0x738aad5c, 0x774bb0eb, 0x4f040d56, 127 | 0x4bc510e1, 0x46863638, 0x42472b8f, 0x5c007b8a, 0x58c1663d, 0x558240e4, 128 | 0x51435d53, 0x251d3b9e, 0x21dc2629, 0x2c9f00f0, 0x285e1d47, 0x36194d42, 129 | 0x32d850f5, 0x3f9b762c, 0x3b5a6b9b, 0x0315d626, 0x07d4cb91, 0x0a97ed48, 130 | 0x0e56f0ff, 0x1011a0fa, 0x14d0bd4d, 0x19939b94, 0x1d528623, 0xf12f560e, 131 | 0xf5ee4bb9, 0xf8ad6d60, 0xfc6c70d7, 0xe22b20d2, 0xe6ea3d65, 0xeba91bbc, 132 | 0xef68060b, 0xd727bbb6, 0xd3e6a601, 0xdea580d8, 0xda649d6f, 0xc423cd6a, 133 | 0xc0e2d0dd, 0xcda1f604, 0xc960ebb3, 0xbd3e8d7e, 0xb9ff90c9, 0xb4bcb610, 134 | 0xb07daba7, 0xae3afba2, 0xaafbe615, 0xa7b8c0cc, 0xa379dd7b, 0x9b3660c6, 135 | 0x9ff77d71, 0x92b45ba8, 0x9675461f, 0x8832161a, 0x8cf30bad, 0x81b02d74, 136 | 0x857130c3, 0x5d8a9099, 0x594b8d2e, 0x5408abf7, 0x50c9b640, 0x4e8ee645, 137 | 0x4a4ffbf2, 0x470cdd2b, 0x43cdc09c, 0x7b827d21, 0x7f436096, 0x7200464f, 138 | 0x76c15bf8, 0x68860bfd, 0x6c47164a, 0x61043093, 0x65c52d24, 0x119b4be9, 139 | 0x155a565e, 0x18197087, 0x1cd86d30, 0x029f3d35, 0x065e2082, 0x0b1d065b, 140 | 0x0fdc1bec, 0x3793a651, 0x3352bbe6, 0x3e119d3f, 0x3ad08088, 0x2497d08d, 141 | 0x2056cd3a, 0x2d15ebe3, 0x29d4f654, 0xc5a92679, 0xc1683bce, 0xcc2b1d17, 142 | 0xc8ea00a0, 0xd6ad50a5, 0xd26c4d12, 0xdf2f6bcb, 0xdbee767c, 0xe3a1cbc1, 143 | 0xe760d676, 0xea23f0af, 0xeee2ed18, 0xf0a5bd1d, 0xf464a0aa, 0xf9278673, 144 | 0xfde69bc4, 0x89b8fd09, 0x8d79e0be, 0x803ac667, 0x84fbdbd0, 0x9abc8bd5, 145 | 0x9e7d9662, 0x933eb0bb, 0x97ffad0c, 0xafb010b1, 0xab710d06, 0xa6322bdf, 146 | 0xa2f33668, 0xbcb4666d, 0xb8757bda, 0xb5365d03, 0xb1f740b4]; 147 | 148 | function calculateCrc32(buf) { 149 | var data = new Uint8Array(buf); 150 | var crc = 0xffffffff; 151 | for (var i = 0; i < data.length; ++i) { 152 | var tableIndex = ((crc >>> 24) ^ data[i]) & 0xff; 153 | crc = (crcTable[tableIndex] ^ (crc << 8)) & 0xffffffff; 154 | } 155 | return crc & 0xffffffff; 156 | } 157 | 158 | function decodeDescriptors(buf) { 159 | var descriptors = []; 160 | if (buf.byteLength === 0) { 161 | return descriptors; 162 | } 163 | var view = new DataView(buf); 164 | for (var i = 0; i < buf.byteLength; ) { 165 | var descriptor = { 166 | tag: view.getUint8(i) 167 | }; 168 | var length = view.getUint8(i + 1); 169 | i += 2; 170 | if (i + length > buf.byteLength) { 171 | throw new BadSizeError("descriptor length is " + length + 172 | ", but we only have " + (buf.byteLength - i) + " bytes " + 173 | "left."); 174 | } 175 | descriptor.data = buf.slice(i, i + length); 176 | descriptors.push(descriptor); 177 | i += length; 178 | } 179 | return descriptors; 180 | } 181 | 182 | function decodeSection(buf) { 183 | if (!buf || !(buf instanceof ArrayBuffer)) { 184 | throw new TypeError("Expected an ArrayBuffer as the first " + 185 | "argument but got " + typeof buf + ": " + buf); 186 | } 187 | 188 | if (buf.byteLength < 3) { 189 | throw new BadSizeError("MPEG-TS sections must be at least 3 " + 190 | "bytes long, but got buffer with length " + buf.byteLength); 191 | } 192 | 193 | var view = new DataView(buf); 194 | 195 | var sectionLength = view.getUint16(1) & 0xFFF; 196 | if (sectionLength + 3 != buf.byteLength) { 197 | throw new BadSizeError("Got section_length " + sectionLength + 198 | ", but remaining length of buffer is " + (buf.byteLength - 3)); 199 | } 200 | 201 | var section = { 202 | tableId: view.getUint8(0) 203 | }; 204 | 205 | /* if section_syntax_indicator is set, parse the syntax section */ 206 | if ((view.getUint8(1) & 0x80) >>> 7) { 207 | if (buf.byteLength < 7) { 208 | throw new BadSizeError("section_syntax_indicator is 1, but " + 209 | "the buffer is not long enough to contain a valid " + 210 | "syntax section"); 211 | } 212 | 213 | section.syntaxSection = { 214 | tableIdExtension: view.getUint16(3), 215 | versionNumber: (view.getUint8(5) & 0x3E) >> 1, 216 | currentNextIndicator: (view.getUint8(5) & 1) === 1, 217 | sectionNumber: view.getUint8(6), 218 | lastSectionNumber: view.getUint8(7) 219 | }; 220 | 221 | var crc32 = calculateCrc32(buf); 222 | if (crc32 !== 0) { 223 | throw new InvalidCrcError("Got CRC-32 " + crc32 + ", but " + 224 | "should be 0"); 225 | } 226 | } else { 227 | section.syntaxSection = null; 228 | } 229 | 230 | if (section.tableId <= 3 && !section.syntaxSection) { 231 | var tableName = TableIds.getName(section.tableId); 232 | throw new MissingSyntaxSectionError(tableName + " requires " + 233 | "a syntax section, but section_syntax_indicator is 0"); 234 | } 235 | 236 | switch (section.tableId) { 237 | case TableIds.PROGRAM_ASSOCIATION_SECTION: 238 | section.transportStreamId = view.getUint16(3); 239 | section.programInfo = []; 240 | for (var i = 8, end = buf.byteLength - 4; i < end; i += 4) { 241 | var program = { 242 | programNumber: view.getUint16(i), 243 | pid: view.getUint16(i) & 0x1FFF 244 | }; 245 | section.programInfo.push(program); 246 | } 247 | break; 248 | case TableIds.CONDITIONAL_ACCESS_SECTION: 249 | case TableIds.TS_DESCRIPTION_SECTION: 250 | section.descriptors = decodeDescriptors(buf.slice(8, 251 | buf.byteLength - 4)); 252 | break; 253 | case TableIds.TS_PROGRAM_MAP_SECTION: 254 | section.programNumber = view.getUint16(3); 255 | section.pcrPid = view.getUint16(8) & 0x1FFF; 256 | if (section.pcrPid === 0x1FFF) { 257 | section.pcrPid = null; 258 | } 259 | var programInfoLength = view.getUint16(10) & 0xFFF; 260 | 261 | section.descriptors = decodeDescriptors(buf.slice(12, 262 | 12 + programInfoLength)); 263 | 264 | section.streams = []; 265 | 266 | var streamStart = 12 + programInfoLength; 267 | var streamsEnd = buf.byteLength - 4 - 5; 268 | var i = 0; 269 | while (streamStart <= streamsEnd) { 270 | var esLength = view.getUint16(streamStart + 3) & 0xFFF; 271 | var esBegin = streamStart + 5; 272 | var esEnd = esBegin + esLength; 273 | section.streams[i] = { 274 | streamType: view.getUint8(streamStart), 275 | elementaryPid: view.getUint16(streamStart + 1) & 0x1FFF, 276 | descriptors: decodeDescriptors(buf.slice(esBegin, esEnd)) 277 | }; 278 | streamStart += 5 + esLength; 279 | ++i; 280 | } 281 | break; 282 | default: 283 | if (section.tableId >= 128) { 284 | section.privateIndicator = (view.getUint8(1) & 0x80) === 1; 285 | var start = section.syntaxSection ? 8 : 3; 286 | var end = start + sectionLength; 287 | if (section.syntaxSection) { 288 | /* Leave 4 bytes for the CRC */ 289 | end -= 4; 290 | } 291 | section.privateData = buf.slice(start, end); 292 | } 293 | break; 294 | } 295 | return section; 296 | } 297 | 298 | exports.TableIds = TableIds; 299 | exports.ReservedPids = ReservedPids; 300 | 301 | exports.BadSizeError = BadSizeError; 302 | exports.InvalidCrcError = InvalidCrcError; 303 | exports.MissingSyntaxSectionError = MissingSyntaxSectionError; 304 | 305 | exports.calculateCrc32 = calculateCrc32; 306 | exports.decodeSection = decodeSection; 307 | }(typeof exports === "undefined" ? this.MpegTs = {} : exports)); 308 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": { 3 | "name": "Brendan Long", 4 | "email": "b.long@cablelabs.com" 5 | }, 6 | "bugs": "https://github.com/cablelabs/mpegtssections-js/issues", 7 | "devDependencies": { 8 | "gluejs": "2.4.x", 9 | "jshint": "2.9.x", 10 | "nodeunit": "0.9.x" 11 | }, 12 | "homepage": "https://github.com/cablelabs/mpegtssections-js", 13 | "license": "BSD-2-Clause", 14 | "main": "./lib/mpegtssections", 15 | "name": "mpegtssections", 16 | "repository": { 17 | "type": "git", 18 | "url": "https://github.com/cablelabs/mpegtssections-js.git" 19 | }, 20 | "scripts": { 21 | "test": "make test" 22 | }, 23 | "version": "1.0.0" 24 | } 25 | -------------------------------------------------------------------------------- /test/tests.js: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2014, CableLabs, Inc. 2 | * All rights reserved. 3 | * 4 | * Redistribution and use in source and binary forms, with or without 5 | * modification, are permitted provided that the following conditions are met: 6 | * 7 | * 1. Redistributions of source code must retain the above copyright notice, 8 | * this list of conditions and the following disclaimer. 9 | * 10 | * 2. Redistributions in binary form must reproduce the above copyright notice, 11 | * this list of conditions and the following disclaimer in the documentation 12 | * and/or other materials provided with the distribution. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 18 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 19 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 20 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 21 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 22 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 23 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 24 | * POSSIBILITY OF SUCH DAMAGE. 25 | */ 26 | /*jslint browser: true, node: true, bitwise: true, plusplus: true, vars: true, 27 | white: true, indent: 4 */ 28 | /*jshint strict: true, shadow: true, camelcase: true, curly: true, 29 | quotmark: double, latedef: true, undef: true, unused: true, trailing: true */ 30 | /*global Uint8Array */ 31 | "use strict"; 32 | var MpegTs = require("../lib/mpegtssections"); 33 | 34 | function arrayBufferToArray(buf) { 35 | var result = []; 36 | var asArray = new Uint8Array(buf); 37 | for (var i = 0; i < asArray.length; ++i) { 38 | result[i] = asArray[i]; 39 | } 40 | return result; 41 | } 42 | 43 | function arrayToBufferWithValidCrc32(array) { 44 | array.push(0, 0, 0, 0); 45 | var buffer = new Uint8Array(array).buffer; 46 | var crc32 = MpegTs.calculateCrc32(buffer.slice(0, buffer.byteLength - 4)); 47 | var view = new DataView(buffer); 48 | view.setUint32(buffer.byteLength - 4, crc32); 49 | return buffer; 50 | } 51 | 52 | exports.TestBufArgumentNull = function(test) { 53 | test.throws(function() { 54 | MpegTs.decodeSection(null); 55 | }, TypeError); 56 | test.done(); 57 | }; 58 | 59 | exports.TestBufArgumentUndefined = function(test) { 60 | test.throws(function() { 61 | MpegTs.decodeSection(); 62 | }, TypeError); 63 | test.done(); 64 | }; 65 | 66 | exports.TestBufArgumentWrongType = function(test) { 67 | test.throws(function() { 68 | MpegTs.decodeSection([5]); 69 | }, TypeError); 70 | test.done(); 71 | }; 72 | 73 | exports.TestBufArgumentTooSmall = function(test) { 74 | test.throws(function() { 75 | var data = new Uint8Array([1, 2]).buffer; 76 | MpegTs.decodeSection(data); 77 | }, MpegTs.BadSizeError); 78 | test.done(); 79 | }; 80 | 81 | exports.TestBufLessThanSectionLength = function(test) { 82 | test.throws(function() { 83 | var data = new Uint8Array([0, 176, 13, 0, 1, 193, 0, 0, 0, 2, 224, 32, 160, 170, 220]).buffer; 84 | MpegTs.decodeSection(data); 85 | }, MpegTs.BadSizeError); 86 | test.done(); 87 | }; 88 | 89 | exports.TestBufGreaterThanSectionLength = function(test) { 90 | test.throws(function() { 91 | var data = new Uint8Array([0, 176, 13, 0, 1, 193, 0, 0, 0, 2, 224, 32, 160, 170, 220, 0, 0]).buffer; 92 | MpegTs.decodeSection(data); 93 | }, MpegTs.BadSizeError); 94 | test.done(); 95 | }; 96 | 97 | exports.TestBufferTooShortForSyntaxSection = function(test) { 98 | test.throws(function() { 99 | try { 100 | var data = new Uint8Array([0, 176, 0]).buffer; 101 | MpegTs.decodeSection(data); 102 | } catch (e) { 103 | test.equal(e.message, "section_syntax_indicator is 1, but the buffer is not long enough to contain a valid syntax section"); 104 | throw e; 105 | } 106 | }, MpegTs.BadSizeError); 107 | test.done(); 108 | }; 109 | 110 | exports.TestBadCrc = function(test) { 111 | test.throws(function() { 112 | var data = new Uint8Array([0, 176, 13, 0, 5, 193, 0, 0, 0, 2, 224, 32, 160, 170, 220, 200]).buffer; 113 | MpegTs.decodeSection(data); 114 | }, MpegTs.InvalidCrcError); 115 | test.done(); 116 | }; 117 | 118 | exports.TestPat = function(test) { 119 | var data = new Uint8Array([0, 176, 13, 0, 1, 193, 0, 0, 0, 2, 224, 32, 160, 170, 220, 200]).buffer; 120 | var section = MpegTs.decodeSection(data); 121 | 122 | test.equal(section.tableId, 0, "tableId"); 123 | 124 | test.equal(section.syntaxSection.tableIdExtension, 1, "tableIdExtension"); 125 | test.equal(section.syntaxSection.versionNumber, 0, "versionNumber"); 126 | test.equal(section.syntaxSection.currentNextIndicator, 1, "currentNextIndicator"); 127 | test.equal(section.syntaxSection.sectionNumber, 0, "sectionNumber"); 128 | test.equal(section.syntaxSection.lastSectionNumber, 0, "lastSectionNumber"); 129 | 130 | test.equal(section.transportStreamId, 1, "transportStreamId"); 131 | test.deepEqual(section.programInfo, [{ 132 | programNumber: 2, 133 | pid: 2 134 | }], "programInfo"); 135 | 136 | test.done(); 137 | }; 138 | 139 | exports.TestPmt = function(test) { 140 | var data = new Uint8Array([2, 176, 61, 0, 2, 193, 0, 0, 224, 33, 240, 6, 5, 4, 67, 85, 69, 73, 2, 224, 33, 240, 0, 129, 224, 36, 240, 0, 134, 224, 45, 240, 0, 192, 230, 232, 240, 9, 5, 4, 69, 84, 86, 49, 162, 1, 0, 192, 230, 234, 240, 8, 5, 4, 69, 84, 86, 49, 161, 0, 112, 252, 191, 31]).buffer; 141 | var section = MpegTs.decodeSection(data); 142 | 143 | test.equal(section.tableId, 2, "tableId"); 144 | 145 | test.equal(section.syntaxSection.tableIdExtension, 2, "tableIdExtension"); 146 | test.equal(section.syntaxSection.versionNumber, 0, "versionNumber"); 147 | test.equal(section.syntaxSection.currentNextIndicator, 1, "currentNextIndicator"); 148 | test.equal(section.syntaxSection.sectionNumber, 0, "sectionNumber"); 149 | test.equal(section.syntaxSection.lastSectionNumber, 0, "lastSectionNumber"); 150 | 151 | test.equal(section.programNumber, 2, "programNumber"); 152 | test.equal(section.pcrPid, 33, "pcrPid"); 153 | 154 | test.deepEqual(section.descriptors, [{ 155 | tag: 5, 156 | data: new Uint8Array([67, 85, 69, 73]).buffer 157 | }], "descriptors"); 158 | 159 | test.deepEqual(section.streams, [{ 160 | streamType: 2, 161 | elementaryPid: 33, 162 | descriptors: [] 163 | }, { 164 | streamType: 129, 165 | elementaryPid: 36, 166 | descriptors: [] 167 | }, { 168 | streamType: 134, 169 | elementaryPid: 45, 170 | descriptors: [] 171 | }, { 172 | streamType: 192, 173 | elementaryPid: 1768, 174 | descriptors: [{ 175 | tag: 5, 176 | data: new Uint8Array([69, 84, 86, 49]).buffer 177 | }, { 178 | tag: 162, 179 | data: new Uint8Array([0]).buffer 180 | }] 181 | }, { 182 | streamType: 192, 183 | elementaryPid: 1770, 184 | descriptors: [{ 185 | tag: 5, 186 | data: new Uint8Array([69, 84, 86, 49]).buffer 187 | }, { 188 | tag: 161, 189 | data: new Uint8Array([]).buffer 190 | }] 191 | }], "streams"); 192 | 193 | test.done(); 194 | }; 195 | 196 | exports.TestPmtWithNullPcrPid = function(test) { 197 | var data = new Uint8Array(arrayToBufferWithValidCrc32([2, 176, 61, 0, 2, 193, 0, 0, 255, 255, 240, 6, 5, 4, 67, 85, 69, 73, 2, 224, 33, 240, 0, 129, 224, 36, 240, 0, 134, 224, 45, 240, 0, 192, 230, 232, 240, 9, 5, 4, 69, 84, 86, 49, 162, 1, 0, 192, 230, 234, 240, 8, 5, 4, 69, 84, 86, 49, 161, 0])).buffer; 198 | var section = MpegTs.decodeSection(data); 199 | test.strictEqual(section.pcrPid, null, "pcrPid"); 200 | test.done(); 201 | }; 202 | 203 | exports.TestUserPrivateData = function(test) { 204 | var data = new Uint8Array([224, 0, 114, 0, 0, 0, 3, 0, 0, 8, 0, 255, 255, 255, 0, 1, 0, 224, 94, 1, 1, 0, 0, 0, 0, 0, 0, 0, 100, 16, 82, 0, 80, 108, 105, 100, 58, 47, 47, 105, 98, 46, 116, 118, 119, 111, 114, 107, 115, 46, 99, 111, 109, 47, 67, 97, 98, 108, 101, 108, 97, 98, 115, 95, 78, 97, 116, 105, 111, 110, 97, 108, 95, 101, 116, 118, 95, 115, 116, 114, 101, 97, 109, 95, 99, 111, 110, 102, 105, 103, 47, 109, 97, 105, 110, 97, 112, 112, 47, 49, 46, 48, 47, 109, 97, 105, 110, 95, 112, 114, 46, 112, 114, 90, 3, 153, 38]).buffer; 205 | var section = MpegTs.decodeSection(data); 206 | test.equal(section.tableId, 224, "tableId"); 207 | test.equal(section.privateIndicator, 0, "privateIndicator"); 208 | 209 | test.equal(section.syntaxSection, null, "syntaxSection"); 210 | 211 | test.deepEqual(arrayBufferToArray(section.privateData), [0, 0, 0, 3, 0, 0, 8, 0, 255, 255, 255, 0, 1, 0, 224, 94, 1, 1, 0, 0, 0, 0, 0, 0, 0, 100, 16, 82, 0, 80, 108, 105, 100, 58, 47, 47, 105, 98, 46, 116, 118, 119, 111, 114, 107, 115, 46, 99, 111, 109, 47, 67, 97, 98, 108, 101, 108, 97, 98, 115, 95, 78, 97, 116, 105, 111, 110, 97, 108, 95, 101, 116, 118, 95, 115, 116, 114, 101, 97, 109, 95, 99, 111, 110, 102, 105, 103, 47, 109, 97, 105, 110, 97, 112, 112, 47, 49, 46, 48, 47, 109, 97, 105, 110, 95, 112, 114, 46, 112, 114, 90, 3, 153, 38], "privateData"); 212 | test.done(); 213 | }; 214 | --------------------------------------------------------------------------------