Source: lib/transmuxer/adts.js

  1. /*! @license
  2. * Shaka Player
  3. * Copyright 2016 Google LLC
  4. * SPDX-License-Identifier: Apache-2.0
  5. */
  6. goog.provide('shaka.transmuxer.ADTS');
  7. /**
  8. * ADTS utils
  9. */
  10. shaka.transmuxer.ADTS = class {
  11. /**
  12. * @param {!Uint8Array} data
  13. * @param {!number} offset
  14. * @return {?{headerLength: number, frameLength: number}}
  15. */
  16. static parseHeader(data, offset) {
  17. const ADTS = shaka.transmuxer.ADTS;
  18. // The protection skip bit tells us if we have 2 bytes of CRC data at the
  19. // end of the ADTS header
  20. const headerLength = ADTS.getHeaderLength(data, offset);
  21. if (offset + headerLength <= data.length) {
  22. // retrieve frame size
  23. const frameLength = ADTS.getFullFrameLength(data, offset) - headerLength;
  24. if (frameLength > 0) {
  25. return {
  26. headerLength,
  27. frameLength,
  28. };
  29. }
  30. }
  31. return null;
  32. }
  33. /**
  34. * @param {!Uint8Array} data
  35. * @param {!number} offset
  36. * @return {?{sampleRate: number, channelCount: number, codec: string}}
  37. */
  38. static parseInfo(data, offset) {
  39. const adtsSamplingRates = [
  40. 96000,
  41. 88200,
  42. 64000,
  43. 48000,
  44. 44100,
  45. 32000,
  46. 24000,
  47. 22050,
  48. 16000,
  49. 12000,
  50. 11025,
  51. 8000,
  52. 7350,
  53. ];
  54. const adtsSamplingIndex = (data[offset + 2] & 0x3c) >>> 2;
  55. if (adtsSamplingIndex > adtsSamplingRates.length - 1) {
  56. return null;
  57. }
  58. const adtsObjectType = ((data[offset + 2] & 0xc0) >>> 6) + 1;
  59. let adtsChannelConfig = (data[offset + 2] & 0x01) << 2;
  60. adtsChannelConfig |= (data[offset + 3] & 0xc0) >>> 6;
  61. return {
  62. sampleRate: adtsSamplingRates[adtsSamplingIndex],
  63. channelCount: adtsChannelConfig,
  64. codec: 'mp4a.40.' + adtsObjectType,
  65. };
  66. }
  67. /**
  68. * @param {!Uint8Array} data
  69. * @param {!number} offset
  70. * @return {boolean}
  71. */
  72. static isHeaderPattern(data, offset) {
  73. return data[offset] === 0xff && (data[offset + 1] & 0xf6) === 0xf0;
  74. }
  75. /**
  76. * @param {!Uint8Array} data
  77. * @param {!number} offset
  78. * @return {number}
  79. */
  80. static getHeaderLength(data, offset) {
  81. return data[offset + 1] & 0x01 ? 7 : 9;
  82. }
  83. /**
  84. * @param {!Uint8Array} data
  85. * @param {!number} offset
  86. * @return {number}
  87. */
  88. static getFullFrameLength(data, offset) {
  89. return ((data[offset + 3] & 0x03) << 11) |
  90. (data[offset + 4] << 3) |
  91. ((data[offset + 5] & 0xe0) >>> 5);
  92. }
  93. /**
  94. * @param {!Uint8Array} data
  95. * @param {!number} offset
  96. * @return {boolean}
  97. */
  98. static isHeader(data, offset) {
  99. const ADTS = shaka.transmuxer.ADTS;
  100. // Look for ADTS header | 1111 1111 | 1111 X00X | where X can be
  101. // either 0 or 1
  102. // Layer bits (position 14 and 15) in header should be always 0 for ADTS
  103. // More info https://wiki.multimedia.cx/index.php?title=ADTS
  104. return offset + 1 < data.length && ADTS.isHeaderPattern(data, offset);
  105. }
  106. /**
  107. * @param {!Uint8Array} data
  108. * @param {!number} offset
  109. * @return {boolean}
  110. */
  111. static probe(data, offset) {
  112. const ADTS = shaka.transmuxer.ADTS;
  113. // same as isHeader but we also check that ADTS frame follows last ADTS
  114. // frame or end of data is reached
  115. if (ADTS.isHeader(data, offset)) {
  116. // ADTS header Length
  117. const headerLength = ADTS.getHeaderLength(data, offset);
  118. if (offset + headerLength >= data.length) {
  119. return false;
  120. }
  121. // ADTS frame Length
  122. const frameLength = ADTS.getFullFrameLength(data, offset);
  123. if (frameLength <= headerLength) {
  124. return false;
  125. }
  126. const newOffset = offset + frameLength;
  127. return newOffset === data.length || ADTS.isHeader(data, newOffset);
  128. }
  129. return false;
  130. }
  131. /**
  132. * @param {!number} samplerate
  133. * @return {number}
  134. */
  135. static getFrameDuration(samplerate) {
  136. return (shaka.transmuxer.ADTS.AAC_SAMPLES_PER_FRAME * 90000) / samplerate;
  137. }
  138. /**
  139. * @param {string} codec
  140. * @param {number} channelCount
  141. * @return {?Uint8Array}
  142. */
  143. static getSilentFrame(codec, channelCount) {
  144. switch (codec) {
  145. case 'mp4a.40.2':
  146. if (channelCount === 1) {
  147. return new Uint8Array([0x00, 0xc8, 0x00, 0x80, 0x23, 0x80]);
  148. } else if (channelCount === 2) {
  149. return new Uint8Array([
  150. 0x21, 0x00, 0x49, 0x90, 0x02, 0x19, 0x00, 0x23, 0x80,
  151. ]);
  152. } else if (channelCount === 3) {
  153. return new Uint8Array([
  154. 0x00, 0xc8, 0x00, 0x80, 0x20, 0x84, 0x01, 0x26, 0x40, 0x08, 0x64,
  155. 0x00, 0x8e,
  156. ]);
  157. } else if (channelCount === 4) {
  158. return new Uint8Array([
  159. 0x00, 0xc8, 0x00, 0x80, 0x20, 0x84, 0x01, 0x26, 0x40, 0x08, 0x64,
  160. 0x00, 0x80, 0x2c, 0x80, 0x08, 0x02, 0x38,
  161. ]);
  162. } else if (channelCount === 5) {
  163. return new Uint8Array([
  164. 0x00, 0xc8, 0x00, 0x80, 0x20, 0x84, 0x01, 0x26, 0x40, 0x08, 0x64,
  165. 0x00, 0x82, 0x30, 0x04, 0x99, 0x00, 0x21, 0x90, 0x02, 0x38,
  166. ]);
  167. } else if (channelCount === 6) {
  168. return new Uint8Array([
  169. 0x00, 0xc8, 0x00, 0x80, 0x20, 0x84, 0x01, 0x26, 0x40, 0x08, 0x64,
  170. 0x00, 0x82, 0x30, 0x04, 0x99, 0x00, 0x21, 0x90, 0x02, 0x00, 0xb2,
  171. 0x00, 0x20, 0x08, 0xe0,
  172. ]);
  173. }
  174. break;
  175. // handle HE-AAC below (mp4a.40.5 / mp4a.40.29)
  176. default:
  177. if (channelCount === 1) {
  178. // eslint-disable-next-line max-len
  179. // ffmpeg -y -f lavfi -i "aevalsrc=0:d=0.05" -c:a libfdk_aac -profile:a aac_he -b:a 4k output.aac && hexdump -v -e '16/1 "0x%x," "\n"' -v output.aac
  180. return new Uint8Array([
  181. 0x1, 0x40, 0x22, 0x80, 0xa3, 0x4e, 0xe6, 0x80, 0xba, 0x8, 0x0, 0x0,
  182. 0x0, 0x1c, 0x6, 0xf1, 0xc1, 0xa, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a,
  183. 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a,
  184. 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a,
  185. 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a,
  186. 0x5a, 0x5e,
  187. ]);
  188. } else if (channelCount === 2) {
  189. // eslint-disable-next-line max-len
  190. // ffmpeg -y -f lavfi -i "aevalsrc=0|0:d=0.05" -c:a libfdk_aac -profile:a aac_he_v2 -b:a 4k output.aac && hexdump -v -e '16/1 "0x%x," "\n"' -v output.aac
  191. return new Uint8Array([
  192. 0x1, 0x40, 0x22, 0x80, 0xa3, 0x5e, 0xe6, 0x80, 0xba, 0x8, 0x0, 0x0,
  193. 0x0, 0x0, 0x95, 0x0, 0x6, 0xf1, 0xa1, 0xa, 0x5a, 0x5a, 0x5a, 0x5a,
  194. 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a,
  195. 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a,
  196. 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a,
  197. 0x5a, 0x5e,
  198. ]);
  199. } else if (channelCount === 3) {
  200. // eslint-disable-next-line max-len
  201. // ffmpeg -y -f lavfi -i "aevalsrc=0|0|0:d=0.05" -c:a libfdk_aac -profile:a aac_he_v2 -b:a 4k output.aac && hexdump -v -e '16/1 "0x%x," "\n"' -v output.aac
  202. return new Uint8Array([
  203. 0x1, 0x40, 0x22, 0x80, 0xa3, 0x5e, 0xe6, 0x80, 0xba, 0x8, 0x0, 0x0,
  204. 0x0, 0x0, 0x95, 0x0, 0x6, 0xf1, 0xa1, 0xa, 0x5a, 0x5a, 0x5a, 0x5a,
  205. 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a,
  206. 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a,
  207. 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a,
  208. 0x5a, 0x5e,
  209. ]);
  210. }
  211. break;
  212. }
  213. return null;
  214. }
  215. };
  216. /**
  217. * @const {number}
  218. */
  219. shaka.transmuxer.ADTS.AAC_SAMPLES_PER_FRAME = 1024;