utils.js 4.9 KB

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