選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

RtxModifier.js 10.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264
  1. import { getLogger } from "jitsi-meet-logger";
  2. const logger = getLogger(__filename);
  3. import * as transform from 'sdp-transform';
  4. import * as SDPUtil from "./SDPUtil";
  5. /**
  6. * Begin helper functions
  7. */
  8. /**
  9. * Given a videoMLine, returns a list of the video
  10. * ssrcs (those used to actually send video, not
  11. * any associated secondary streams)
  12. * @param {object} videoMLine media line object from transform.parse
  13. * @returns {list<string>} list of primary video ssrcs
  14. */
  15. function getPrimaryVideoSsrcs (videoMLine) {
  16. let videoSsrcs = videoMLine.ssrcs
  17. .map(ssrcInfo => ssrcInfo.id)
  18. .filter((ssrc, index, array) => array.indexOf(ssrc) === index);
  19. if (videoMLine.ssrcGroups) {
  20. videoMLine.ssrcGroups.forEach((ssrcGroupInfo) => {
  21. // Right now, FID groups are the only ones we parse to
  22. // disqualify streams. If/when others arise we'll
  23. // need to add support for them here
  24. if (ssrcGroupInfo.semantics === "FID") {
  25. // secondary FID streams should be filtered out
  26. let secondarySsrc = ssrcGroupInfo.ssrcs.split(" ")[1];
  27. videoSsrcs.splice(
  28. videoSsrcs.indexOf(parseInt(secondarySsrc)), 1);
  29. }
  30. });
  31. }
  32. return videoSsrcs;
  33. }
  34. /**
  35. * Given a video mline (as parsed from transform.parse),
  36. * and a primary ssrc, return the corresponding rtx ssrc
  37. * (if there is one) for that video ssrc
  38. * @param {object} videoMLine the video MLine from which to extract the
  39. * rtx video ssrc
  40. * @param {number} primarySsrc the video ssrc for which to find the
  41. * corresponding rtx ssrc
  42. * @returns {number} the rtx ssrc (or undefined if there isn't one)
  43. */
  44. function getRtxSsrc (videoMLine, primarySsrc) {
  45. if (videoMLine.ssrcGroups) {
  46. let fidGroup = videoMLine.ssrcGroups.find(group => {
  47. if (group.semantics === "FID") {
  48. let groupPrimarySsrc = SDPUtil.parseGroupSsrcs(group)[0];
  49. return groupPrimarySsrc === primarySsrc;
  50. }
  51. });
  52. if (fidGroup) {
  53. return SDPUtil.parseGroupSsrcs(fidGroup)[1];
  54. }
  55. }
  56. }
  57. /**
  58. * Updates or inserts the appropriate rtx information for primarySsrc with
  59. * the given rtxSsrc. If no rtx ssrc for primarySsrc currently exists, it will
  60. * add the appropriate ssrc and ssrc group lines. If primarySsrc already has
  61. * an rtx ssrc, the appropriate ssrc and group lines will be updated
  62. * @param {object} videoMLine video mline object that will be updated (in place)
  63. * @param {object} primarySsrcInfo the info (ssrc, msid & cname) for the
  64. * primary ssrc
  65. * @param {number} rtxSsrc the rtx ssrc to associate with the primary ssrc
  66. */
  67. function updateAssociatedRtxStream (videoMLine, primarySsrcInfo, rtxSsrc) {
  68. logger.debug("Updating mline to associate " + rtxSsrc +
  69. " rtx ssrc with primary stream ", primarySsrcInfo.id);
  70. let primarySsrc = primarySsrcInfo.id;
  71. let primarySsrcMsid = primarySsrcInfo.msid;
  72. let primarySsrcCname = primarySsrcInfo.cname;
  73. let previousAssociatedRtxStream =
  74. getRtxSsrc (videoMLine, primarySsrc);
  75. if (previousAssociatedRtxStream === rtxSsrc) {
  76. logger.debug(rtxSsrc + " was already associated with " +
  77. primarySsrc);
  78. return;
  79. }
  80. if (previousAssociatedRtxStream) {
  81. logger.debug(primarySsrc + " was previously assocaited with rtx " +
  82. previousAssociatedRtxStream + ", removing all references to it");
  83. // Stream already had an rtx ssrc that is different than the one given,
  84. // remove all trace of the old one
  85. videoMLine.ssrcs = videoMLine.ssrcs
  86. .filter(ssrcInfo => ssrcInfo.id !== previousAssociatedRtxStream);
  87. logger.debug("groups before filtering for " +
  88. previousAssociatedRtxStream);
  89. logger.debug(JSON.stringify(videoMLine.ssrcGroups));
  90. videoMLine.ssrcGroups = videoMLine.ssrcGroups
  91. .filter(groupInfo => {
  92. return groupInfo
  93. .ssrcs
  94. .indexOf(previousAssociatedRtxStream + "") === -1;
  95. });
  96. }
  97. videoMLine.ssrcs.push({
  98. id: rtxSsrc,
  99. attribute: "cname",
  100. value: primarySsrcCname
  101. });
  102. videoMLine.ssrcs.push({
  103. id: rtxSsrc,
  104. attribute: "msid",
  105. value: primarySsrcMsid
  106. });
  107. videoMLine.ssrcGroups = videoMLine.ssrcGroups || [];
  108. videoMLine.ssrcGroups.push({
  109. semantics: "FID",
  110. ssrcs: primarySsrc + " " + rtxSsrc
  111. });
  112. }
  113. /**
  114. * End helper functions
  115. */
  116. /**
  117. * Adds any missing RTX streams for video streams
  118. * and makes sure that they remain consistent
  119. */
  120. export default class RtxModifier {
  121. /**
  122. * Constructor
  123. */
  124. constructor () {
  125. /**
  126. * Map of video ssrc to corresponding RTX
  127. * ssrc
  128. */
  129. this.correspondingRtxSsrcs = new Map();
  130. }
  131. /**
  132. * Clear the cached map of primary video ssrcs to
  133. * their corresponding rtx ssrcs so that they will
  134. * not be used for the next call to modifyRtxSsrcs
  135. */
  136. clearSsrcCache () {
  137. this.correspondingRtxSsrcs.clear();
  138. }
  139. /**
  140. * Explicitly set the primary video ssrc -> rtx ssrc
  141. * mapping to be used in modifyRtxSsrcs
  142. * @param {Map} ssrcMapping a mapping of primary video
  143. * ssrcs to their corresponding rtx ssrcs
  144. */
  145. setSsrcCache (ssrcMapping) {
  146. logger.debug("Setting ssrc cache to ", ssrcMapping);
  147. this.correspondingRtxSsrcs = ssrcMapping;
  148. }
  149. /**
  150. * Adds RTX ssrcs for any video ssrcs that don't
  151. * already have them. If the video ssrc has been
  152. * seen before, and already had an RTX ssrc generated,
  153. * the same RTX ssrc will be used again.
  154. * @param {string} sdpStr sdp in raw string format
  155. */
  156. modifyRtxSsrcs (sdpStr) {
  157. let parsedSdp = transform.parse(sdpStr);
  158. let videoMLine =
  159. parsedSdp.media.find(mLine => mLine.type === "video");
  160. if (videoMLine.direction === "inactive" ||
  161. videoMLine.direction === "recvonly") {
  162. logger.debug("RtxModifier doing nothing, video " +
  163. "m line is inactive or recvonly");
  164. return sdpStr;
  165. }
  166. if (!videoMLine.ssrcs) {
  167. logger.debug("RtxModifier doing nothing, no video ssrcs present");
  168. return sdpStr;
  169. }
  170. logger.debug("Current ssrc mapping: ", this.correspondingRtxSsrcs);
  171. let primaryVideoSsrcs = getPrimaryVideoSsrcs(videoMLine);
  172. logger.debug("Parsed primary video ssrcs ", primaryVideoSsrcs, " " +
  173. "making sure all have rtx streams");
  174. primaryVideoSsrcs.forEach(ssrc => {
  175. let msid = SDPUtil.getSsrcAttribute(videoMLine, ssrc, "msid");
  176. let cname = SDPUtil.getSsrcAttribute(videoMLine, ssrc, "cname");
  177. let correspondingRtxSsrc = this.correspondingRtxSsrcs.get(ssrc);
  178. if (correspondingRtxSsrc) {
  179. logger.debug("Already have an associated rtx ssrc for " +
  180. " video ssrc " + ssrc + ": " +
  181. correspondingRtxSsrc);
  182. } else {
  183. logger.debug("No previously associated rtx ssrc for " +
  184. " video ssrc " + ssrc);
  185. // If there's one in the sdp already for it, we'll just set
  186. // that as the corresponding one
  187. let previousAssociatedRtxStream =
  188. getRtxSsrc (videoMLine, ssrc);
  189. if (previousAssociatedRtxStream) {
  190. logger.debug("Rtx stream " + previousAssociatedRtxStream +
  191. " already existed in the sdp as an rtx stream for " +
  192. ssrc);
  193. correspondingRtxSsrc = previousAssociatedRtxStream;
  194. } else {
  195. correspondingRtxSsrc = SDPUtil.generateSsrc();
  196. logger.debug("Generated rtx ssrc " + correspondingRtxSsrc +
  197. " for ssrc " + ssrc);
  198. }
  199. logger.debug("Caching rtx ssrc " + correspondingRtxSsrc +
  200. " for video ssrc " + ssrc);
  201. this.correspondingRtxSsrcs.set(ssrc, correspondingRtxSsrc);
  202. }
  203. updateAssociatedRtxStream(
  204. videoMLine,
  205. {
  206. id: ssrc,
  207. cname: cname,
  208. msid: msid
  209. },
  210. correspondingRtxSsrc);
  211. });
  212. return transform.write(parsedSdp);
  213. }
  214. /**
  215. * Strip all rtx streams from the given sdp
  216. * @param {string} sdpStr sdp in raw string format
  217. * @returns {string} sdp string with all rtx streams stripped
  218. */
  219. stripRtx (sdpStr) {
  220. const parsedSdp = transform.parse(sdpStr);
  221. const videoMLine =
  222. parsedSdp.media.find(mLine => mLine.type === "video");
  223. if (videoMLine.direction === "inactive" ||
  224. videoMLine.direction === "recvonly") {
  225. logger.debug("RtxModifier doing nothing, video " +
  226. "m line is inactive or recvonly");
  227. return sdpStr;
  228. }
  229. if (!videoMLine.ssrcs) {
  230. logger.debug("RtxModifier doing nothing, no video ssrcs present");
  231. return sdpStr;
  232. }
  233. if (!videoMLine.ssrcGroups) {
  234. logger.debug("RtxModifier doing nothing, no video ssrcGroups present");
  235. return sdpStr;
  236. }
  237. const fidGroups = videoMLine.ssrcGroups
  238. .filter(group => group.semantics === "FID");
  239. // Remove the fid groups from the mline
  240. videoMLine.ssrcGroups = videoMLine.ssrcGroups
  241. .filter(group => group.semantics !== "FID");
  242. // Get the rtx ssrcs and remove them from the mline
  243. const ssrcsToRemove = [];
  244. fidGroups.forEach(fidGroup => {
  245. const groupSsrcs = SDPUtil.parseGroupSsrcs(fidGroup);
  246. const rtxSsrc = groupSsrcs[1];
  247. ssrcsToRemove.push(rtxSsrc);
  248. });
  249. videoMLine.ssrcs = videoMLine.ssrcs
  250. .filter(line => ssrcsToRemove.indexOf(line.id) === -1);
  251. return transform.write(parsedSdp);
  252. }
  253. }