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