Source: lib/transmuxer/adts.js

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