probe.js 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  1. /**
  2. * mux.js
  3. *
  4. * Copyright (c) 2015 Brightcove
  5. * All rights reserved.
  6. *
  7. * Utilities to detect basic properties and metadata about MP4s.
  8. */
  9. 'use strict';
  10. var findBox, parseType, timescale, startTime;
  11. // Find the data for a box specified by its path
  12. findBox = function(data, path) {
  13. var results = [],
  14. i, size, type, end, subresults;
  15. if (!path.length) {
  16. // short-circuit the search for empty paths
  17. return null;
  18. }
  19. for (i = 0; i < data.byteLength;) {
  20. size = data[i] << 24;
  21. size |= data[i + 1] << 16;
  22. size |= data[i + 2] << 8;
  23. size |= data[i + 3];
  24. type = parseType(data.subarray(i + 4, i + 8));
  25. end = size > 1 ? i + size : data.byteLength;
  26. if (type === path[0]) {
  27. if (path.length === 1) {
  28. // this is the end of the path and we've found the box we were
  29. // looking for
  30. results.push(data.subarray(i + 8, end));
  31. } else {
  32. // recursively search for the next box along the path
  33. subresults = findBox(data.subarray(i + 8, end), path.slice(1));
  34. if (subresults.length) {
  35. results = results.concat(subresults);
  36. }
  37. }
  38. }
  39. i = end;
  40. }
  41. // we've finished searching all of data
  42. return results;
  43. };
  44. /**
  45. * Returns the string representation of an ASCII encoded four byte buffer.
  46. * @param buffer {Uint8Array} a four-byte buffer to translate
  47. * @return {string} the corresponding string
  48. */
  49. parseType = function(buffer) {
  50. var result = '';
  51. result += String.fromCharCode(buffer[0]);
  52. result += String.fromCharCode(buffer[1]);
  53. result += String.fromCharCode(buffer[2]);
  54. result += String.fromCharCode(buffer[3]);
  55. return result;
  56. };
  57. /**
  58. * Parses an MP4 initialization segment and extracts the timescale
  59. * values for any declared tracks. Timescale values indicate the
  60. * number of clock ticks per second to assume for time-based values
  61. * elsewhere in the MP4.
  62. *
  63. * To determine the start time of an MP4, you need two pieces of
  64. * information: the timescale unit and the earliest base media decode
  65. * time. Multiple timescales can be specified within an MP4 but the
  66. * base media decode time is always expressed in the timescale from
  67. * the media header box for the track:
  68. * ```
  69. * moov > trak > mdia > mdhd.timescale
  70. * ```
  71. * @param init {Uint8Array} the bytes of the init segment
  72. * @return {object} a hash of track ids to timescale values or null if
  73. * the init segment is malformed.
  74. */
  75. timescale = function(init) {
  76. var
  77. result = {},
  78. traks = findBox(init, ['moov', 'trak']);
  79. // mdhd timescale
  80. return traks.reduce(function(result, trak) {
  81. var tkhd, version, index, id, mdhd;
  82. tkhd = findBox(trak, ['tkhd'])[0];
  83. if (!tkhd) {
  84. return null;
  85. }
  86. version = tkhd[0];
  87. index = version === 0 ? 12 : 20;
  88. id = tkhd[index] << 24 |
  89. tkhd[index + 1] << 16 |
  90. tkhd[index + 2] << 8 |
  91. tkhd[index + 3];
  92. mdhd = findBox(trak, ['mdia', 'mdhd'])[0];
  93. if (!mdhd) {
  94. return null;
  95. }
  96. version = mdhd[0];
  97. index = version === 0 ? 12 : 20;
  98. result[id] = mdhd[index] << 24 |
  99. mdhd[index + 1] << 16 |
  100. mdhd[index + 2] << 8 |
  101. mdhd[index + 3];
  102. return result;
  103. }, result);
  104. };
  105. /**
  106. * Determine the base media decode start time, in seconds, for an MP4
  107. * fragment. If multiple fragments are specified, the earliest time is
  108. * returned.
  109. *
  110. * The base media decode time can be parsed from track fragment
  111. * metadata:
  112. * ```
  113. * moof > traf > tfdt.baseMediaDecodeTime
  114. * ```
  115. * It requires the timescale value from the mdhd to interpret.
  116. *
  117. * @param timescale {object} a hash of track ids to timescale values.
  118. * @return {number} the earliest base media decode start time for the
  119. * fragment, in seconds
  120. */
  121. startTime = function(timescale, fragment) {
  122. var trafs, baseTimes, result;
  123. // we need info from two childrend of each track fragment box
  124. trafs = findBox(fragment, ['moof', 'traf']);
  125. // determine the start times for each track
  126. baseTimes = [].concat.apply([], trafs.map(function(traf) {
  127. return findBox(traf, ['tfhd']).map(function(tfhd) {
  128. var id, scale, baseTime;
  129. // get the track id from the tfhd
  130. id = tfhd[4] << 24 |
  131. tfhd[5] << 16 |
  132. tfhd[6] << 8 |
  133. tfhd[7];
  134. // assume a 90kHz clock if no timescale was specified
  135. scale = timescale[id] || 90e3;
  136. // get the base media decode time from the tfdt
  137. baseTime = findBox(traf, ['tfdt']).map(function(tfdt) {
  138. var version, result;
  139. version = tfdt[0];
  140. result = tfdt[4] << 24 |
  141. tfdt[5] << 16 |
  142. tfdt[6] << 8 |
  143. tfdt[7];
  144. if (version === 1) {
  145. result *= Math.pow(2, 32);
  146. result += tfdt[8] << 24 |
  147. tfdt[9] << 16 |
  148. tfdt[10] << 8 |
  149. tfdt[11];
  150. }
  151. return result;
  152. })[0];
  153. baseTime = baseTime || Infinity;
  154. // convert base time to seconds
  155. return baseTime / scale;
  156. });
  157. }));
  158. // return the minimum
  159. result = Math.min.apply(null, baseTimes);
  160. return isFinite(result) ? result : 0;
  161. };
  162. module.exports = {
  163. parseType: parseType,
  164. timescale: timescale,
  165. startTime: startTime
  166. };