utils.js 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  1. /**
  2. * mux.js
  3. *
  4. * Copyright (c) Brightcove
  5. * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
  6. *
  7. * Utilities to detect basic properties and metadata about Aac data.
  8. */
  9. 'use strict';
  10. var ADTS_SAMPLING_FREQUENCIES = [
  11. 96000,
  12. 88200,
  13. 64000,
  14. 48000,
  15. 44100,
  16. 32000,
  17. 24000,
  18. 22050,
  19. 16000,
  20. 12000,
  21. 11025,
  22. 8000,
  23. 7350
  24. ];
  25. var parseId3TagSize = function(header, byteIndex) {
  26. var
  27. returnSize = (header[byteIndex + 6] << 21) |
  28. (header[byteIndex + 7] << 14) |
  29. (header[byteIndex + 8] << 7) |
  30. (header[byteIndex + 9]),
  31. flags = header[byteIndex + 5],
  32. footerPresent = (flags & 16) >> 4;
  33. // if we get a negative returnSize clamp it to 0
  34. returnSize = returnSize >= 0 ? returnSize : 0;
  35. if (footerPresent) {
  36. return returnSize + 20;
  37. }
  38. return returnSize + 10;
  39. };
  40. var getId3Offset = function(data, offset) {
  41. if (data.length - offset < 10 ||
  42. data[offset] !== 'I'.charCodeAt(0) ||
  43. data[offset + 1] !== 'D'.charCodeAt(0) ||
  44. data[offset + 2] !== '3'.charCodeAt(0)) {
  45. return offset;
  46. }
  47. offset += parseId3TagSize(data, offset);
  48. return getId3Offset(data, offset);
  49. };
  50. // TODO: use vhs-utils
  51. var isLikelyAacData = function(data) {
  52. var offset = getId3Offset(data, 0);
  53. return data.length >= offset + 2 &&
  54. (data[offset] & 0xFF) === 0xFF &&
  55. (data[offset + 1] & 0xF0) === 0xF0 &&
  56. // verify that the 2 layer bits are 0, aka this
  57. // is not mp3 data but aac data.
  58. (data[offset + 1] & 0x16) === 0x10;
  59. };
  60. var parseSyncSafeInteger = function(data) {
  61. return (data[0] << 21) |
  62. (data[1] << 14) |
  63. (data[2] << 7) |
  64. (data[3]);
  65. };
  66. // return a percent-encoded representation of the specified byte range
  67. // @see http://en.wikipedia.org/wiki/Percent-encoding
  68. var percentEncode = function(bytes, start, end) {
  69. var i, result = '';
  70. for (i = start; i < end; i++) {
  71. result += '%' + ('00' + bytes[i].toString(16)).slice(-2);
  72. }
  73. return result;
  74. };
  75. // return the string representation of the specified byte range,
  76. // interpreted as ISO-8859-1.
  77. var parseIso88591 = function(bytes, start, end) {
  78. return unescape(percentEncode(bytes, start, end)); // jshint ignore:line
  79. };
  80. var parseAdtsSize = function(header, byteIndex) {
  81. var
  82. lowThree = (header[byteIndex + 5] & 0xE0) >> 5,
  83. middle = header[byteIndex + 4] << 3,
  84. highTwo = header[byteIndex + 3] & 0x3 << 11;
  85. return (highTwo | middle) | lowThree;
  86. };
  87. var parseType = function(header, byteIndex) {
  88. if ((header[byteIndex] === 'I'.charCodeAt(0)) &&
  89. (header[byteIndex + 1] === 'D'.charCodeAt(0)) &&
  90. (header[byteIndex + 2] === '3'.charCodeAt(0))) {
  91. return 'timed-metadata';
  92. } else if ((header[byteIndex] & 0xff === 0xff) &&
  93. ((header[byteIndex + 1] & 0xf0) === 0xf0)) {
  94. return 'audio';
  95. }
  96. return null;
  97. };
  98. var parseSampleRate = function(packet) {
  99. var i = 0;
  100. while (i + 5 < packet.length) {
  101. if (packet[i] !== 0xFF || (packet[i + 1] & 0xF6) !== 0xF0) {
  102. // If a valid header was not found, jump one forward and attempt to
  103. // find a valid ADTS header starting at the next byte
  104. i++;
  105. continue;
  106. }
  107. return ADTS_SAMPLING_FREQUENCIES[(packet[i + 2] & 0x3c) >>> 2];
  108. }
  109. return null;
  110. };
  111. var parseAacTimestamp = function(packet) {
  112. var frameStart, frameSize, frame, frameHeader;
  113. // find the start of the first frame and the end of the tag
  114. frameStart = 10;
  115. if (packet[5] & 0x40) {
  116. // advance the frame start past the extended header
  117. frameStart += 4; // header size field
  118. frameStart += parseSyncSafeInteger(packet.subarray(10, 14));
  119. }
  120. // parse one or more ID3 frames
  121. // http://id3.org/id3v2.3.0#ID3v2_frame_overview
  122. do {
  123. // determine the number of bytes in this frame
  124. frameSize = parseSyncSafeInteger(packet.subarray(frameStart + 4, frameStart + 8));
  125. if (frameSize < 1) {
  126. return null;
  127. }
  128. frameHeader = String.fromCharCode(packet[frameStart],
  129. packet[frameStart + 1],
  130. packet[frameStart + 2],
  131. packet[frameStart + 3]);
  132. if (frameHeader === 'PRIV') {
  133. frame = packet.subarray(frameStart + 10, frameStart + frameSize + 10);
  134. for (var i = 0; i < frame.byteLength; i++) {
  135. if (frame[i] === 0) {
  136. var owner = parseIso88591(frame, 0, i);
  137. if (owner === 'com.apple.streaming.transportStreamTimestamp') {
  138. var d = frame.subarray(i + 1);
  139. var size = ((d[3] & 0x01) << 30) |
  140. (d[4] << 22) |
  141. (d[5] << 14) |
  142. (d[6] << 6) |
  143. (d[7] >>> 2);
  144. size *= 4;
  145. size += d[7] & 0x03;
  146. return size;
  147. }
  148. break;
  149. }
  150. }
  151. }
  152. frameStart += 10; // advance past the frame header
  153. frameStart += frameSize; // advance past the frame body
  154. } while (frameStart < packet.byteLength);
  155. return null;
  156. };
  157. module.exports = {
  158. isLikelyAacData: isLikelyAacData,
  159. parseId3TagSize: parseId3TagSize,
  160. parseAdtsSize: parseAdtsSize,
  161. parseType: parseType,
  162. parseSampleRate: parseSampleRate,
  163. parseAacTimestamp: parseAacTimestamp
  164. };