Home Reference Source

src/demux/adts.ts

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