Home Reference Source

src/demux/adts.js

  1. /**
  2. * ADTS parser helper
  3. * @link https://wiki.multimedia.cx/index.php?title=ADTS
  4. */
  5. import { logger } from '../utils/logger';
  6. import { ErrorTypes, ErrorDetails } from '../errors';
  7.  
  8. import Event from '../events';
  9.  
  10. import { getSelfScope } from '../utils/get-self-scope';
  11.  
  12. export function getAudioConfig (observer, data, offset, audioCodec) {
  13. let adtsObjectType, // :int
  14. adtsSampleingIndex, // :int
  15. adtsExtensionSampleingIndex, // :int
  16. adtsChanelConfig, // :int
  17. config,
  18. userAgent = navigator.userAgent.toLowerCase(),
  19. manifestCodec = audioCodec,
  20. adtsSampleingRates = [
  21. 96000, 88200,
  22. 64000, 48000,
  23. 44100, 32000,
  24. 24000, 22050,
  25. 16000, 12000,
  26. 11025, 8000,
  27. 7350];
  28. // byte 2
  29. adtsObjectType = ((data[offset + 2] & 0xC0) >>> 6) + 1;
  30. adtsSampleingIndex = ((data[offset + 2] & 0x3C) >>> 2);
  31. if (adtsSampleingIndex > adtsSampleingRates.length - 1) {
  32. observer.trigger(Event.ERROR, { type: ErrorTypes.MEDIA_ERROR, details: ErrorDetails.FRAG_PARSING_ERROR, fatal: true, reason: `invalid ADTS sampling index:${adtsSampleingIndex}` });
  33. return;
  34. }
  35. adtsChanelConfig = ((data[offset + 2] & 0x01) << 2);
  36. // byte 3
  37. adtsChanelConfig |= ((data[offset + 3] & 0xC0) >>> 6);
  38. logger.log(`manifest codec:${audioCodec},ADTS data:type:${adtsObjectType},sampleingIndex:${adtsSampleingIndex}[${adtsSampleingRates[adtsSampleingIndex]}Hz],channelConfig:${adtsChanelConfig}`);
  39. // firefox: freq less than 24kHz = AAC SBR (HE-AAC)
  40. if (/firefox/i.test(userAgent)) {
  41. if (adtsSampleingIndex >= 6) {
  42. adtsObjectType = 5;
  43. config = new Array(4);
  44. // HE-AAC uses SBR (Spectral Band Replication) , high frequencies are constructed from low frequencies
  45. // there is a factor 2 between frame sample rate and output sample rate
  46. // multiply frequency by 2 (see table below, equivalent to substract 3)
  47. adtsExtensionSampleingIndex = adtsSampleingIndex - 3;
  48. } else {
  49. adtsObjectType = 2;
  50. config = new Array(2);
  51. adtsExtensionSampleingIndex = adtsSampleingIndex;
  52. }
  53. // Android : always use AAC
  54. } else if (userAgent.indexOf('android') !== -1) {
  55. adtsObjectType = 2;
  56. config = new Array(2);
  57. adtsExtensionSampleingIndex = adtsSampleingIndex;
  58. } else {
  59. /* for other browsers (Chrome/Vivaldi/Opera ...)
  60. always force audio type to be HE-AAC SBR, as some browsers do not support audio codec switch properly (like Chrome ...)
  61. */
  62. adtsObjectType = 5;
  63. config = new Array(4);
  64. // if (manifest codec is HE-AAC or HE-AACv2) OR (manifest codec not specified AND frequency less than 24kHz)
  65. if ((audioCodec && ((audioCodec.indexOf('mp4a.40.29') !== -1) ||
  66. (audioCodec.indexOf('mp4a.40.5') !== -1))) ||
  67. (!audioCodec && adtsSampleingIndex >= 6)) {
  68. // HE-AAC uses SBR (Spectral Band Replication) , high frequencies are constructed from low frequencies
  69. // there is a factor 2 between frame sample rate and output sample rate
  70. // multiply frequency by 2 (see table below, equivalent to substract 3)
  71. adtsExtensionSampleingIndex = adtsSampleingIndex - 3;
  72. } else {
  73. // if (manifest codec is AAC) AND (frequency less than 24kHz AND nb channel is 1) OR (manifest codec not specified and mono audio)
  74. // Chrome fails to play back with low frequency AAC LC mono when initialized with HE-AAC. This is not a problem with stereo.
  75. if (audioCodec && audioCodec.indexOf('mp4a.40.2') !== -1 && ((adtsSampleingIndex >= 6 && adtsChanelConfig === 1) ||
  76. /vivaldi/i.test(userAgent)) ||
  77. (!audioCodec && adtsChanelConfig === 1)) {
  78. adtsObjectType = 2;
  79. config = new Array(2);
  80. }
  81. adtsExtensionSampleingIndex = adtsSampleingIndex;
  82. }
  83. }
  84. /* refer to http://wiki.multimedia.cx/index.php?title=MPEG-4_Audio#Audio_Specific_Config
  85. ISO 14496-3 (AAC).pdf - Table 1.13 — Syntax of AudioSpecificConfig()
  86. Audio Profile / Audio Object Type
  87. 0: Null
  88. 1: AAC Main
  89. 2: AAC LC (Low Complexity)
  90. 3: AAC SSR (Scalable Sample Rate)
  91. 4: AAC LTP (Long Term Prediction)
  92. 5: SBR (Spectral Band Replication)
  93. 6: AAC Scalable
  94. sampling freq
  95. 0: 96000 Hz
  96. 1: 88200 Hz
  97. 2: 64000 Hz
  98. 3: 48000 Hz
  99. 4: 44100 Hz
  100. 5: 32000 Hz
  101. 6: 24000 Hz
  102. 7: 22050 Hz
  103. 8: 16000 Hz
  104. 9: 12000 Hz
  105. 10: 11025 Hz
  106. 11: 8000 Hz
  107. 12: 7350 Hz
  108. 13: Reserved
  109. 14: Reserved
  110. 15: frequency is written explictly
  111. Channel Configurations
  112. These are the channel configurations:
  113. 0: Defined in AOT Specifc Config
  114. 1: 1 channel: front-center
  115. 2: 2 channels: front-left, front-right
  116. */
  117. // audioObjectType = profile => profile, the MPEG-4 Audio Object Type minus 1
  118. config[0] = adtsObjectType << 3;
  119. // samplingFrequencyIndex
  120. config[0] |= (adtsSampleingIndex & 0x0E) >> 1;
  121. config[1] |= (adtsSampleingIndex & 0x01) << 7;
  122. // channelConfiguration
  123. config[1] |= adtsChanelConfig << 3;
  124. if (adtsObjectType === 5) {
  125. // adtsExtensionSampleingIndex
  126. config[1] |= (adtsExtensionSampleingIndex & 0x0E) >> 1;
  127. config[2] = (adtsExtensionSampleingIndex & 0x01) << 7;
  128. // adtsObjectType (force to 2, chrome is checking that object type is less than 5 ???
  129. // https://chromium.googlesource.com/chromium/src.git/+/master/media/formats/mp4/aac.cc
  130. config[2] |= 2 << 2;
  131. config[3] = 0;
  132. }
  133. return { config: config, samplerate: adtsSampleingRates[adtsSampleingIndex], channelCount: adtsChanelConfig, codec: ('mp4a.40.' + adtsObjectType), manifestCodec: manifestCodec };
  134. }
  135.  
  136. export function isHeaderPattern (data, offset) {
  137. return data[offset] === 0xff && (data[offset + 1] & 0xf6) === 0xf0;
  138. }
  139.  
  140. export function getHeaderLength (data, offset) {
  141. return (data[offset + 1] & 0x01 ? 7 : 9);
  142. }
  143.  
  144. export function getFullFrameLength (data, offset) {
  145. return ((data[offset + 3] & 0x03) << 11) |
  146. (data[offset + 4] << 3) |
  147. ((data[offset + 5] & 0xE0) >>> 5);
  148. }
  149.  
  150. export function isHeader (data, offset) {
  151. // Look for ADTS header | 1111 1111 | 1111 X00X | where X can be either 0 or 1
  152. // Layer bits (position 14 and 15) in header should be always 0 for ADTS
  153. // More info https://wiki.multimedia.cx/index.php?title=ADTS
  154. if (offset + 1 < data.length && isHeaderPattern(data, offset)) {
  155. return true;
  156. }
  157.  
  158. return false;
  159. }
  160.  
  161. export function probe (data, offset) {
  162. // same as isHeader but we also check that ADTS frame follows last ADTS frame
  163. // or end of data is reached
  164. if (isHeader(data, offset)) {
  165. // ADTS header Length
  166. let headerLength = getHeaderLength(data, offset);
  167. // ADTS frame Length
  168. let frameLength = headerLength;
  169. if (offset + 5 < data.length) {
  170. frameLength = getFullFrameLength(data, offset);
  171. }
  172.  
  173. let newOffset = offset + frameLength;
  174. if (newOffset === data.length || (newOffset + 1 < data.length && isHeaderPattern(data, newOffset))) {
  175. return true;
  176. }
  177. }
  178. return false;
  179. }
  180.  
  181. export function initTrackConfig (track, observer, data, offset, audioCodec) {
  182. if (!track.samplerate) {
  183. let config = getAudioConfig(observer, data, offset, audioCodec);
  184. track.config = config.config;
  185. track.samplerate = config.samplerate;
  186. track.channelCount = config.channelCount;
  187. track.codec = config.codec;
  188. track.manifestCodec = config.manifestCodec;
  189. logger.log(`parsed codec:${track.codec},rate:${config.samplerate},nb channel:${config.channelCount}`);
  190. }
  191. }
  192.  
  193. export function getFrameDuration (samplerate) {
  194. return 1024 * 90000 / samplerate;
  195. }
  196.  
  197. export function parseFrameHeader (data, offset, pts, frameIndex, frameDuration) {
  198. let headerLength, frameLength, stamp;
  199. let length = data.length;
  200.  
  201. // The protection skip bit tells us if we have 2 bytes of CRC data at the end of the ADTS header
  202. headerLength = getHeaderLength(data, offset);
  203. // retrieve frame size
  204. frameLength = getFullFrameLength(data, offset);
  205. frameLength -= headerLength;
  206.  
  207. if ((frameLength > 0) && ((offset + headerLength + frameLength) <= length)) {
  208. stamp = pts + frameIndex * frameDuration;
  209. // logger.log(`AAC frame, offset/length/total/pts:${offset+headerLength}/${frameLength}/${data.byteLength}/${(stamp/90).toFixed(0)}`);
  210. return { headerLength, frameLength, stamp };
  211. }
  212.  
  213. return undefined;
  214. }
  215.  
  216. export function appendFrame (track, data, offset, pts, frameIndex) {
  217. let frameDuration = getFrameDuration(track.samplerate);
  218. let header = parseFrameHeader(data, offset, pts, frameIndex, frameDuration);
  219. if (header) {
  220. let stamp = header.stamp;
  221. let headerLength = header.headerLength;
  222. let frameLength = header.frameLength;
  223.  
  224. // logger.log(`AAC frame, offset/length/total/pts:${offset+headerLength}/${frameLength}/${data.byteLength}/${(stamp/90).toFixed(0)}`);
  225. let aacSample = {
  226. unit: data.subarray(offset + headerLength, offset + headerLength + frameLength),
  227. pts: stamp,
  228. dts: stamp
  229. };
  230.  
  231. track.samples.push(aacSample);
  232. return { sample: aacSample, length: frameLength + headerLength };
  233. }
  234.  
  235. return undefined;
  236. }