frame-utils.js 8.9 KB

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