frame-utils.js 9.1 KB

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