frame-utils.js 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302
  1. /**
  2. * mux.js
  3. *
  4. * Copyright (c) Brightcove
  5. * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
  6. */
  7. // Convert an array of nal units into an array of frames with each frame being
  8. // composed of the nal units that make up that frame
  9. // Also keep track of cummulative data about the frame from the nal units such
  10. // as the frame duration, starting pts, etc.
  11. var groupNalsIntoFrames = function groupNalsIntoFrames(nalUnits) {
  12. var i,
  13. currentNal,
  14. currentFrame = [],
  15. frames = []; // TODO added for LHLS, make sure this is OK
  16. frames.byteLength = 0;
  17. frames.nalCount = 0;
  18. frames.duration = 0;
  19. currentFrame.byteLength = 0;
  20. for (i = 0; i < nalUnits.length; i++) {
  21. currentNal = nalUnits[i]; // Split on 'aud'-type nal units
  22. if (currentNal.nalUnitType === 'access_unit_delimiter_rbsp') {
  23. // Since the very first nal unit is expected to be an AUD
  24. // only push to the frames array when currentFrame is not empty
  25. if (currentFrame.length) {
  26. currentFrame.duration = currentNal.dts - currentFrame.dts; // TODO added for LHLS, make sure this is OK
  27. frames.byteLength += currentFrame.byteLength;
  28. frames.nalCount += currentFrame.length;
  29. frames.duration += currentFrame.duration;
  30. frames.push(currentFrame);
  31. }
  32. currentFrame = [currentNal];
  33. currentFrame.byteLength = currentNal.data.byteLength;
  34. currentFrame.pts = currentNal.pts;
  35. currentFrame.dts = currentNal.dts;
  36. } else {
  37. // Specifically flag key frames for ease of use later
  38. if (currentNal.nalUnitType === 'slice_layer_without_partitioning_rbsp_idr') {
  39. currentFrame.keyFrame = true;
  40. }
  41. currentFrame.duration = currentNal.dts - currentFrame.dts;
  42. currentFrame.byteLength += currentNal.data.byteLength;
  43. currentFrame.push(currentNal);
  44. }
  45. } // For the last frame, use the duration of the previous frame if we
  46. // have nothing better to go on
  47. if (frames.length && (!currentFrame.duration || currentFrame.duration <= 0)) {
  48. currentFrame.duration = frames[frames.length - 1].duration;
  49. } // Push the final frame
  50. // TODO added for LHLS, make sure this is OK
  51. frames.byteLength += currentFrame.byteLength;
  52. frames.nalCount += currentFrame.length;
  53. frames.duration += currentFrame.duration;
  54. frames.push(currentFrame);
  55. return frames;
  56. }; // Convert an array of frames into an array of Gop with each Gop being composed
  57. // of the frames that make up that Gop
  58. // Also keep track of cummulative data about the Gop from the frames such as the
  59. // Gop duration, starting pts, etc.
  60. var groupFramesIntoGops = function groupFramesIntoGops(frames) {
  61. var i,
  62. currentFrame,
  63. currentGop = [],
  64. gops = []; // We must pre-set some of the values on the Gop since we
  65. // keep running totals of these values
  66. currentGop.byteLength = 0;
  67. currentGop.nalCount = 0;
  68. currentGop.duration = 0;
  69. currentGop.pts = frames[0].pts;
  70. currentGop.dts = frames[0].dts; // store some metadata about all the Gops
  71. gops.byteLength = 0;
  72. gops.nalCount = 0;
  73. gops.duration = 0;
  74. gops.pts = frames[0].pts;
  75. gops.dts = frames[0].dts;
  76. for (i = 0; i < frames.length; i++) {
  77. currentFrame = frames[i];
  78. if (currentFrame.keyFrame) {
  79. // Since the very first frame is expected to be an keyframe
  80. // only push to the gops array when currentGop is not empty
  81. if (currentGop.length) {
  82. gops.push(currentGop);
  83. gops.byteLength += currentGop.byteLength;
  84. gops.nalCount += currentGop.nalCount;
  85. gops.duration += currentGop.duration;
  86. }
  87. currentGop = [currentFrame];
  88. currentGop.nalCount = currentFrame.length;
  89. currentGop.byteLength = currentFrame.byteLength;
  90. currentGop.pts = currentFrame.pts;
  91. currentGop.dts = currentFrame.dts;
  92. currentGop.duration = currentFrame.duration;
  93. } else {
  94. currentGop.duration += currentFrame.duration;
  95. currentGop.nalCount += currentFrame.length;
  96. currentGop.byteLength += currentFrame.byteLength;
  97. currentGop.push(currentFrame);
  98. }
  99. }
  100. if (gops.length && currentGop.duration <= 0) {
  101. currentGop.duration = gops[gops.length - 1].duration;
  102. }
  103. gops.byteLength += currentGop.byteLength;
  104. gops.nalCount += currentGop.nalCount;
  105. gops.duration += currentGop.duration; // push the final Gop
  106. gops.push(currentGop);
  107. return gops;
  108. };
  109. /*
  110. * Search for the first keyframe in the GOPs and throw away all frames
  111. * until that keyframe. Then extend the duration of the pulled keyframe
  112. * and pull the PTS and DTS of the keyframe so that it covers the time
  113. * range of the frames that were disposed.
  114. *
  115. * @param {Array} gops video GOPs
  116. * @returns {Array} modified video GOPs
  117. */
  118. var extendFirstKeyFrame = function extendFirstKeyFrame(gops) {
  119. var currentGop;
  120. if (!gops[0][0].keyFrame && gops.length > 1) {
  121. // Remove the first GOP
  122. currentGop = gops.shift();
  123. gops.byteLength -= currentGop.byteLength;
  124. gops.nalCount -= currentGop.nalCount; // Extend the first frame of what is now the
  125. // first gop to cover the time period of the
  126. // frames we just removed
  127. gops[0][0].dts = currentGop.dts;
  128. gops[0][0].pts = currentGop.pts;
  129. gops[0][0].duration += currentGop.duration;
  130. }
  131. return gops;
  132. };
  133. /**
  134. * Default sample object
  135. * see ISO/IEC 14496-12:2012, section 8.6.4.3
  136. */
  137. var createDefaultSample = function createDefaultSample() {
  138. return {
  139. size: 0,
  140. flags: {
  141. isLeading: 0,
  142. dependsOn: 1,
  143. isDependedOn: 0,
  144. hasRedundancy: 0,
  145. degradationPriority: 0,
  146. isNonSyncSample: 1
  147. }
  148. };
  149. };
  150. /*
  151. * Collates information from a video frame into an object for eventual
  152. * entry into an MP4 sample table.
  153. *
  154. * @param {Object} frame the video frame
  155. * @param {Number} dataOffset the byte offset to position the sample
  156. * @return {Object} object containing sample table info for a frame
  157. */
  158. var sampleForFrame = function sampleForFrame(frame, dataOffset) {
  159. var sample = createDefaultSample();
  160. sample.dataOffset = dataOffset;
  161. sample.compositionTimeOffset = frame.pts - frame.dts;
  162. sample.duration = frame.duration;
  163. sample.size = 4 * frame.length; // Space for nal unit size
  164. sample.size += frame.byteLength;
  165. if (frame.keyFrame) {
  166. sample.flags.dependsOn = 2;
  167. sample.flags.isNonSyncSample = 0;
  168. }
  169. return sample;
  170. }; // generate the track's sample table from an array of gops
  171. var generateSampleTable = function generateSampleTable(gops, baseDataOffset) {
  172. var h,
  173. i,
  174. sample,
  175. currentGop,
  176. currentFrame,
  177. dataOffset = baseDataOffset || 0,
  178. samples = [];
  179. for (h = 0; h < gops.length; h++) {
  180. currentGop = gops[h];
  181. for (i = 0; i < currentGop.length; i++) {
  182. currentFrame = currentGop[i];
  183. sample = sampleForFrame(currentFrame, dataOffset);
  184. dataOffset += sample.size;
  185. samples.push(sample);
  186. }
  187. }
  188. return samples;
  189. }; // generate the track's raw mdat data from an array of gops
  190. var concatenateNalData = function concatenateNalData(gops) {
  191. var h,
  192. i,
  193. j,
  194. currentGop,
  195. currentFrame,
  196. currentNal,
  197. dataOffset = 0,
  198. nalsByteLength = gops.byteLength,
  199. numberOfNals = gops.nalCount,
  200. totalByteLength = nalsByteLength + 4 * numberOfNals,
  201. data = new Uint8Array(totalByteLength),
  202. view = new DataView(data.buffer); // For each Gop..
  203. for (h = 0; h < gops.length; h++) {
  204. currentGop = gops[h]; // For each Frame..
  205. for (i = 0; i < currentGop.length; i++) {
  206. currentFrame = currentGop[i]; // For each NAL..
  207. for (j = 0; j < currentFrame.length; j++) {
  208. currentNal = currentFrame[j];
  209. view.setUint32(dataOffset, currentNal.data.byteLength);
  210. dataOffset += 4;
  211. data.set(currentNal.data, dataOffset);
  212. dataOffset += currentNal.data.byteLength;
  213. }
  214. }
  215. }
  216. return data;
  217. }; // generate the track's sample table from a frame
  218. var generateSampleTableForFrame = function generateSampleTableForFrame(frame, baseDataOffset) {
  219. var sample,
  220. dataOffset = baseDataOffset || 0,
  221. samples = [];
  222. sample = sampleForFrame(frame, dataOffset);
  223. samples.push(sample);
  224. return samples;
  225. }; // generate the track's raw mdat data from a frame
  226. var concatenateNalDataForFrame = function concatenateNalDataForFrame(frame) {
  227. var i,
  228. currentNal,
  229. dataOffset = 0,
  230. nalsByteLength = frame.byteLength,
  231. numberOfNals = frame.length,
  232. totalByteLength = nalsByteLength + 4 * numberOfNals,
  233. data = new Uint8Array(totalByteLength),
  234. view = new DataView(data.buffer); // For each NAL..
  235. for (i = 0; i < frame.length; i++) {
  236. currentNal = frame[i];
  237. view.setUint32(dataOffset, currentNal.data.byteLength);
  238. dataOffset += 4;
  239. data.set(currentNal.data, dataOffset);
  240. dataOffset += currentNal.data.byteLength;
  241. }
  242. return data;
  243. };
  244. module.exports = {
  245. groupNalsIntoFrames: groupNalsIntoFrames,
  246. groupFramesIntoGops: groupFramesIntoGops,
  247. extendFirstKeyFrame: extendFirstKeyFrame,
  248. generateSampleTable: generateSampleTable,
  249. concatenateNalData: concatenateNalData,
  250. generateSampleTableForFrame: generateSampleTableForFrame,
  251. concatenateNalDataForFrame: concatenateNalDataForFrame
  252. };