Source: lib/media/segment_utils.js

  1. /*! @license
  2. * Shaka Player
  3. * Copyright 2016 Google LLC
  4. * SPDX-License-Identifier: Apache-2.0
  5. */
  6. goog.provide('shaka.media.SegmentUtils');
  7. goog.require('goog.asserts');
  8. goog.require('shaka.log');
  9. goog.require('shaka.media.ClosedCaptionParser');
  10. goog.require('shaka.util.BufferUtils');
  11. goog.require('shaka.util.MimeUtils');
  12. goog.require('shaka.util.Mp4BoxParsers');
  13. goog.require('shaka.util.Mp4Parser');
  14. goog.require('shaka.util.TsParser');
  15. /**
  16. * @summary Utility functions for segment parsing.
  17. */
  18. shaka.media.SegmentUtils = class {
  19. /**
  20. * @param {string} mimeType
  21. * @return {shaka.media.SegmentUtils.BasicInfo}
  22. */
  23. static getBasicInfoFromMimeType(mimeType) {
  24. const baseMimeType = shaka.util.MimeUtils.getBasicType(mimeType);
  25. const type = baseMimeType.split('/')[0];
  26. const codecs = shaka.util.MimeUtils.getCodecs(mimeType);
  27. return {
  28. type: type,
  29. mimeType: baseMimeType,
  30. codecs: codecs,
  31. language: null,
  32. height: null,
  33. width: null,
  34. channelCount: null,
  35. sampleRate: null,
  36. closedCaptions: new Map(),
  37. };
  38. }
  39. /**
  40. * @param {!BufferSource} data
  41. * @return {?shaka.media.SegmentUtils.BasicInfo}
  42. */
  43. static getBasicInfoFromTs(data) {
  44. const uint8ArrayData = shaka.util.BufferUtils.toUint8(data);
  45. const tsParser = new shaka.util.TsParser().parse(uint8ArrayData);
  46. const tsCodecs = tsParser.getCodecs();
  47. const videoInfo = tsParser.getVideoInfo();
  48. const codecs = [];
  49. let hasAudio = false;
  50. let hasVideo = false;
  51. switch (tsCodecs.audio) {
  52. case 'aac':
  53. case 'aac-loas':
  54. codecs.push('mp4a.40.2');
  55. hasAudio = true;
  56. break;
  57. case 'mp3':
  58. codecs.push('mp4a.40.34');
  59. hasAudio = true;
  60. break;
  61. case 'ac3':
  62. codecs.push('ac-3');
  63. hasAudio = true;
  64. break;
  65. case 'ec3':
  66. codecs.push('ec-3');
  67. hasAudio = true;
  68. break;
  69. case 'opus':
  70. codecs.push('opus');
  71. hasAudio = true;
  72. break;
  73. }
  74. switch (tsCodecs.video) {
  75. case 'avc':
  76. if (videoInfo.codec) {
  77. codecs.push(videoInfo.codec);
  78. } else {
  79. codecs.push('avc1.42E01E');
  80. }
  81. hasVideo = true;
  82. break;
  83. case 'hvc':
  84. if (videoInfo.codec) {
  85. codecs.push(videoInfo.codec);
  86. } else {
  87. codecs.push('hvc1.1.6.L93.90');
  88. }
  89. hasVideo = true;
  90. break;
  91. case 'av1':
  92. codecs.push('av01.0.01M.08');
  93. hasVideo = true;
  94. break;
  95. }
  96. if (!codecs.length) {
  97. return null;
  98. }
  99. const onlyAudio = hasAudio && !hasVideo;
  100. const closedCaptions = new Map();
  101. if (hasVideo) {
  102. const captionParser = new shaka.media.ClosedCaptionParser('video/mp2t');
  103. captionParser.parseFrom(data);
  104. for (const stream of captionParser.getStreams()) {
  105. closedCaptions.set(stream, stream);
  106. }
  107. captionParser.reset();
  108. }
  109. return {
  110. type: onlyAudio ? 'audio' : 'video',
  111. mimeType: 'video/mp2t',
  112. codecs: codecs.join(', '),
  113. language: null,
  114. height: videoInfo.height,
  115. width: videoInfo.width,
  116. channelCount: null,
  117. sampleRate: null,
  118. closedCaptions: closedCaptions,
  119. };
  120. }
  121. /**
  122. * @param {?BufferSource} initData
  123. * @param {!BufferSource} data
  124. * @return {?shaka.media.SegmentUtils.BasicInfo}
  125. */
  126. static getBasicInfoFromMp4(initData, data) {
  127. const Mp4Parser = shaka.util.Mp4Parser;
  128. const SegmentUtils = shaka.media.SegmentUtils;
  129. const codecs = [];
  130. let hasAudio = false;
  131. let hasVideo = false;
  132. const addCodec = (codec) => {
  133. const codecLC = codec.toLowerCase();
  134. switch (codecLC) {
  135. case 'avc1':
  136. case 'avc3':
  137. codecs.push(codecLC + '.42E01E');
  138. hasVideo = true;
  139. break;
  140. case 'hev1':
  141. case 'hvc1':
  142. codecs.push(codecLC + '.1.6.L93.90');
  143. hasVideo = true;
  144. break;
  145. case 'dvh1':
  146. case 'dvhe':
  147. codecs.push(codecLC + '.05.04');
  148. hasVideo = true;
  149. break;
  150. case 'vp09':
  151. codecs.push(codecLC + '.00.10.08');
  152. hasVideo = true;
  153. break;
  154. case 'av01':
  155. codecs.push(codecLC + '.0.01M.08');
  156. hasVideo = true;
  157. break;
  158. case 'mp4a':
  159. // We assume AAC, but this can be wrong since mp4a supports
  160. // others codecs
  161. codecs.push('mp4a.40.2');
  162. hasAudio = true;
  163. break;
  164. case 'ac-3':
  165. case 'ec-3':
  166. case 'opus':
  167. case 'flac':
  168. codecs.push(codecLC);
  169. hasAudio = true;
  170. break;
  171. }
  172. };
  173. const codecBoxParser = (box) => addCodec(box.name);
  174. /** @type {?string} */
  175. let language = null;
  176. /** @type {?string} */
  177. let height = null;
  178. /** @type {?string} */
  179. let width = null;
  180. /** @type {?number} */
  181. let channelCount = null;
  182. /** @type {?number} */
  183. let sampleRate = null;
  184. new Mp4Parser()
  185. .box('moov', Mp4Parser.children)
  186. .box('trak', Mp4Parser.children)
  187. .fullBox('tkhd', (box) => {
  188. goog.asserts.assert(
  189. box.version != null,
  190. 'TKHD is a full box and should have a valid version.');
  191. const parsedTKHDBox = shaka.util.Mp4BoxParsers.parseTKHD(
  192. box.reader, box.version);
  193. height = String(parsedTKHDBox.height);
  194. width = String(parsedTKHDBox.width);
  195. })
  196. .box('mdia', Mp4Parser.children)
  197. .fullBox('mdhd', (box) => {
  198. goog.asserts.assert(
  199. box.version != null,
  200. 'MDHD is a full box and should have a valid version.');
  201. const parsedMDHDBox = shaka.util.Mp4BoxParsers.parseMDHD(
  202. box.reader, box.version);
  203. language = parsedMDHDBox.language;
  204. })
  205. .box('minf', Mp4Parser.children)
  206. .box('stbl', Mp4Parser.children)
  207. .fullBox('stsd', Mp4Parser.sampleDescription)
  208. // AUDIO
  209. // These are the various boxes that signal a codec.
  210. .box('mp4a', (box) => {
  211. const parsedMP4ABox = shaka.util.Mp4BoxParsers.parseMP4A(box.reader);
  212. channelCount = parsedMP4ABox.channelCount;
  213. sampleRate = parsedMP4ABox.sampleRate;
  214. if (box.reader.hasMoreData()) {
  215. Mp4Parser.children(box);
  216. } else {
  217. codecBoxParser(box);
  218. }
  219. })
  220. .box('esds', (box) => {
  221. const parsedESDSBox = shaka.util.Mp4BoxParsers.parseESDS(box.reader);
  222. codecs.push(parsedESDSBox.codec);
  223. hasAudio = true;
  224. })
  225. .box('ac-3', codecBoxParser)
  226. .box('ec-3', codecBoxParser)
  227. .box('opus', codecBoxParser)
  228. .box('Opus', codecBoxParser)
  229. .box('fLaC', codecBoxParser)
  230. // VIDEO
  231. // These are the various boxes that signal a codec.
  232. .box('avc1', (box) => {
  233. const parsedAVCBox =
  234. shaka.util.Mp4BoxParsers.parseAVC(box.reader, box.name);
  235. codecs.push(parsedAVCBox.codec);
  236. hasVideo = true;
  237. })
  238. .box('avc3', (box) => {
  239. const parsedAVCBox =
  240. shaka.util.Mp4BoxParsers.parseAVC(box.reader, box.name);
  241. codecs.push(parsedAVCBox.codec);
  242. hasVideo = true;
  243. })
  244. .box('hev1', codecBoxParser)
  245. .box('hvc1', codecBoxParser)
  246. .box('dvh1', codecBoxParser)
  247. .box('dvhe', codecBoxParser)
  248. .box('vp09', codecBoxParser)
  249. .box('av01', codecBoxParser)
  250. // This signals an encrypted sample, which we can go inside of to
  251. // find the codec used.
  252. // Note: If encrypted, you can only have audio or video, not both.
  253. .box('enca', Mp4Parser.audioSampleEntry)
  254. .box('encv', Mp4Parser.visualSampleEntry)
  255. .box('sinf', Mp4Parser.children)
  256. .box('frma', (box) => {
  257. const {codec} = shaka.util.Mp4BoxParsers.parseFRMA(box.reader);
  258. addCodec(codec);
  259. })
  260. .parse(initData || data, /* partialOkay= */ true);
  261. if (!codecs.length) {
  262. return null;
  263. }
  264. const onlyAudio = hasAudio && !hasVideo;
  265. const closedCaptions = new Map();
  266. if (hasVideo) {
  267. const captionParser = new shaka.media.ClosedCaptionParser('video/mp4');
  268. if (initData) {
  269. captionParser.init(initData);
  270. }
  271. captionParser.parseFrom(data);
  272. for (const stream of captionParser.getStreams()) {
  273. closedCaptions.set(stream, stream);
  274. }
  275. captionParser.reset();
  276. }
  277. return {
  278. type: onlyAudio ? 'audio' : 'video',
  279. mimeType: onlyAudio ? 'audio/mp4' : 'video/mp4',
  280. codecs: SegmentUtils.filterDuplicateCodecs_(codecs).join(', '),
  281. language: language,
  282. height: height,
  283. width: width,
  284. channelCount: channelCount,
  285. sampleRate: sampleRate,
  286. closedCaptions: closedCaptions,
  287. };
  288. }
  289. /**
  290. * @param {!Array.<string>} codecs
  291. * @return {!Array.<string>} codecs
  292. * @private
  293. */
  294. static filterDuplicateCodecs_(codecs) {
  295. // Filter out duplicate codecs.
  296. const seen = new Set();
  297. const ret = [];
  298. for (const codec of codecs) {
  299. const shortCodec = shaka.util.MimeUtils.getCodecBase(codec);
  300. if (!seen.has(shortCodec)) {
  301. ret.push(codec);
  302. seen.add(shortCodec);
  303. } else {
  304. shaka.log.debug('Ignoring duplicate codec');
  305. }
  306. }
  307. return ret;
  308. }
  309. };
  310. /**
  311. * @typedef {{
  312. * type: string,
  313. * mimeType: string,
  314. * codecs: string,
  315. * language: ?string,
  316. * height: ?string,
  317. * width: ?string,
  318. * channelCount: ?number,
  319. * sampleRate: ?number,
  320. * closedCaptions: Map.<string, string>
  321. * }}
  322. *
  323. * @property {string} type
  324. * @property {string} mimeType
  325. * @property {string} codecs
  326. * @property {?string} language
  327. * @property {?string} height
  328. * @property {?string} width
  329. * @property {?number} channelCount
  330. * @property {?number} sampleRate
  331. * @property {Map.<string, string>} closedCaptions
  332. */
  333. shaka.media.SegmentUtils.BasicInfo;