You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

SdpSimulcast.ts 7.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  1. import { MediaDirection } from '../../service/RTC/MediaDirection';
  2. import { MediaType } from '../../service/RTC/MediaType';
  3. import { SIM_LAYERS } from '../../service/RTC/StandardVideoSettings';
  4. import * as transform from 'sdp-transform';
  5. interface Description {
  6. type: RTCSdpType;
  7. sdp: string;
  8. }
  9. /**
  10. * This class handles SDP munging for enabling simulcast for local video streams in Unified plan. A set of random SSRCs
  11. * are generated for the higher layer streams and they are cached for a given mid. The cached SSRCs are then reused on
  12. * the subsequent iterations while munging the local description. This class also handles imploding of the simulcast
  13. * SSRCs for remote endpoints into the primary FID group in remote description since Jicofo signals all SSRCs relevant
  14. * to a given endpoint.
  15. */
  16. export default class SdpSimulcast {
  17. private _numOfLayers: number;
  18. private _ssrcCache: Map<string, Array<number>>;
  19. /**
  20. * Creates a new instance.
  21. *
  22. * @param options
  23. */
  24. constructor() {
  25. this._ssrcCache = new Map();
  26. this._numOfLayers = SIM_LAYERS.length;
  27. }
  28. /**
  29. * Updates the given media description using the SSRCs that were cached for the mid associated
  30. * with the media description and returns the modified media description.
  31. *
  32. * @param mLine
  33. * @returns
  34. */
  35. _fillSsrcsFromCache(mLine: transform.MediaDescription) : any {
  36. const mid = mLine.mid;
  37. const cachedSsrcs = this._ssrcCache.get(mid);
  38. const newSsrcs = this._parseSimLayers(mLine);
  39. const newMsid = this._getSsrcAttribute(mLine, newSsrcs[0], 'msid');
  40. const newCname = this._getSsrcAttribute(mLine, newSsrcs[0], 'cname');
  41. mLine.ssrcs = [];
  42. mLine.ssrcGroups = [];
  43. for (const ssrc of cachedSsrcs) {
  44. mLine.ssrcs.push({
  45. id: ssrc,
  46. attribute: 'msid',
  47. value: newMsid
  48. });
  49. mLine.ssrcs.push({
  50. id: ssrc,
  51. attribute: 'cname',
  52. value: newCname
  53. });
  54. }
  55. mLine.ssrcGroups.push({
  56. semantics: 'SIM',
  57. ssrcs: cachedSsrcs.join(' ')
  58. });
  59. return mLine;
  60. }
  61. /**
  62. * Generates a new set of SSRCs for the higher simulcast layers/streams and adds the attributes and SIM group to
  63. * the given media description and returns the modified media description.
  64. *
  65. * @param mLine
  66. * @param primarySsrc
  67. * @returns
  68. */
  69. _generateNewSsrcsForSimulcast(mLine: transform.MediaDescription, primarySsrc: number) : any {
  70. const cname = this._getSsrcAttribute(mLine, primarySsrc, 'cname');
  71. let msid = this._getSsrcAttribute(mLine, primarySsrc, 'msid');
  72. const addAssociatedAttributes = (mLine: transform.MediaDescription, ssrc: number) => {
  73. mLine.ssrcs.push({
  74. id: ssrc,
  75. attribute: 'cname',
  76. value: cname
  77. });
  78. mLine.ssrcs.push({
  79. id: ssrc,
  80. attribute: 'msid',
  81. value: msid
  82. });
  83. }
  84. // In Unified-plan mode, the a=ssrc lines with the msid attribute are not present (only cname attributes are
  85. // present) in the answers that Chrome and Safari generate for an offer received from Jicofo. Generate these
  86. // a=ssrc lines using the msid values from the a=msid line.
  87. if (!msid) {
  88. msid = mLine.msid;
  89. const primarySsrcs = mLine.ssrcs;
  90. primarySsrcs.forEach(ssrc => {
  91. mLine.ssrcs.push({
  92. id: ssrc.id,
  93. attribute: 'msid',
  94. value: msid
  95. });
  96. })
  97. }
  98. // Generate SIM layers.
  99. const simSsrcs = [];
  100. for (let i = 0; i < this._numOfLayers - 1; ++i) {
  101. const simSsrc = this._generateSsrc();
  102. addAssociatedAttributes(mLine, simSsrc);
  103. simSsrcs.push(simSsrc);
  104. }
  105. mLine.ssrcGroups = mLine.ssrcGroups || [];
  106. mLine.ssrcGroups.push({
  107. semantics: 'SIM',
  108. ssrcs: primarySsrc + ' ' + simSsrcs.join(' ')
  109. });
  110. return mLine;
  111. }
  112. /**
  113. * Returns a random number to be used for the SSRC.
  114. *
  115. * @returns
  116. */
  117. _generateSsrc() : number {
  118. const max = 0xffffffff;
  119. return Math.floor(Math.random() * max);
  120. }
  121. /**
  122. * Returns the requested attribute value for a SSRC from a given media description.
  123. *
  124. * @param mLine
  125. * @param ssrc
  126. * @param attributeName
  127. * @returns
  128. */
  129. _getSsrcAttribute(mLine: transform.MediaDescription, ssrc: number, attributeName: string) : string | undefined {
  130. return mLine.ssrcs?.find(
  131. ssrcInfo => Number(ssrcInfo.id) === ssrc
  132. && ssrcInfo.attribute === attributeName)?.value;
  133. }
  134. /**
  135. * Returns an array of all the primary SSRCs in the SIM group for a given media description.
  136. *
  137. * @param mLine
  138. * @returns
  139. */
  140. _parseSimLayers(mLine: transform.MediaDescription) : Array<number> | null {
  141. const simGroup = mLine.ssrcGroups?.find(group => group.semantics === 'SIM');
  142. if (simGroup) {
  143. return simGroup.ssrcs.split(' ').map(ssrc => Number(ssrc));
  144. }
  145. if (mLine.ssrcs?.length) {
  146. return [ Number(mLine.ssrcs[0].id) ];
  147. }
  148. return null;
  149. }
  150. /**
  151. * Munges the given media description to enable simulcast for the video media sections that are in either have
  152. * SENDRECV or SENDONLY as the media direction thereby ignoring all the RECVONLY transceivers created for remote
  153. * endpoints.
  154. * NOTE: This needs to be called only when simulcast is enabled.
  155. *
  156. * @param description
  157. * @returns
  158. */
  159. mungeLocalDescription(description: Description) : Description {
  160. if (!description || !description.sdp) {
  161. return description;
  162. }
  163. const session = transform.parse(description.sdp);
  164. for (let media of session.media) {
  165. // Ignore recvonly and inactive transceivers created for remote sources.
  166. if (media.direction === MediaDirection.RECVONLY || media.direction === MediaDirection.INACTIVE) {
  167. continue;
  168. }
  169. // Ignore audio m-lines.
  170. if (media.type !== MediaType.VIDEO) {
  171. continue;
  172. }
  173. const mid = media.mid;
  174. const numSsrcs = new Set(media.ssrcs?.map(ssrcInfo => ssrcInfo.id));
  175. const numGroups = media.ssrcGroups?.length ?? 0;
  176. let primarySsrc: number;
  177. // Do not munge if the description has no ssrcs or if simulcast is already enabled.
  178. if (numSsrcs.size === 0 || numSsrcs.size > 2 || (numSsrcs.size === 2 && numGroups === 0)) {
  179. continue;
  180. }
  181. if (numSsrcs.size === 1) {
  182. primarySsrc = Number(media.ssrcs[0]?.id);
  183. } else {
  184. const fidGroup = media.ssrcGroups.find(group => group.semantics === 'FID');
  185. if (fidGroup) {
  186. primarySsrc = Number(fidGroup.ssrcs.split(' ')[0]);
  187. }
  188. }
  189. if (this._ssrcCache.has(mid)) {
  190. media = this._fillSsrcsFromCache(media);
  191. } else {
  192. media = this._generateNewSsrcsForSimulcast(media, primarySsrc);
  193. const simulcastSsrcs = this._parseSimLayers(media);
  194. // Update the SSRCs in the cache so that they can re-used for the same mid again.
  195. this._ssrcCache.set(mid, simulcastSsrcs);
  196. }
  197. }
  198. return new RTCSessionDescription({
  199. type: description.type,
  200. sdp: transform.write(session)
  201. });
  202. }
  203. }