Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.

SdpSimulcast.ts 7.6KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  1. import * as transform from 'sdp-transform';
  2. import { MediaDirection } from '../../service/RTC/MediaDirection';
  3. import { MediaType } from '../../service/RTC/MediaType';
  4. import { SIM_LAYERS, SSRC_GROUP_SEMANTICS } from '../../service/RTC/StandardVideoQualitySettings';
  5. interface IDescription {
  6. sdp: string;
  7. type: RTCSdpType;
  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: SSRC_GROUP_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. // In Unified-plan mode, the a=ssrc lines with the msid attribute are not present (only cname attributes are
  73. // present) in the answers that Chrome and Safari generate for an offer received from Jicofo. Generate these
  74. // a=ssrc lines using the msid values from the a=msid line.
  75. if (!msid) {
  76. msid = mLine.msid;
  77. const primarySsrcs = mLine.ssrcs;
  78. primarySsrcs.forEach(ssrc => {
  79. mLine.ssrcs.push({
  80. id: ssrc.id,
  81. attribute: 'msid',
  82. value: msid
  83. });
  84. });
  85. }
  86. // Generate SIM layers.
  87. const simSsrcs = [];
  88. for (let i = 0; i < this._numOfLayers - 1; ++i) {
  89. const simSsrc = this._generateSsrc();
  90. mLine.ssrcs.push({
  91. id: simSsrc,
  92. attribute: 'cname',
  93. value: cname
  94. });
  95. mLine.ssrcs.push({
  96. id: simSsrc,
  97. attribute: 'msid',
  98. value: msid
  99. });
  100. simSsrcs.push(simSsrc);
  101. }
  102. mLine.ssrcGroups = mLine.ssrcGroups || [];
  103. mLine.ssrcGroups.push({
  104. semantics: SSRC_GROUP_SEMANTICS.SIM,
  105. ssrcs: `${primarySsrc} ${simSsrcs.join(' ')}`
  106. });
  107. return mLine;
  108. }
  109. /**
  110. * Returns a random number to be used for the SSRC.
  111. *
  112. * @returns
  113. */
  114. _generateSsrc(): number {
  115. const max = 0xffffffff;
  116. return Math.floor(Math.random() * max);
  117. }
  118. /**
  119. * Returns the requested attribute value for a SSRC from a given media description.
  120. *
  121. * @param mLine
  122. * @param ssrc
  123. * @param attributeName
  124. * @returns
  125. */
  126. _getSsrcAttribute(mLine: transform.MediaDescription, ssrc: number, attributeName: string): string | undefined {
  127. return mLine.ssrcs?.find(
  128. ssrcInfo => Number(ssrcInfo.id) === ssrc
  129. && ssrcInfo.attribute === attributeName)?.value;
  130. }
  131. /**
  132. * Returns an array of all the primary SSRCs in the SIM group for a given media description.
  133. *
  134. * @param mLine
  135. * @returns
  136. */
  137. _parseSimLayers(mLine: transform.MediaDescription): Array<number> | null {
  138. const simGroup = mLine.ssrcGroups?.find(group => group.semantics === SSRC_GROUP_SEMANTICS.SIM);
  139. if (simGroup) {
  140. return simGroup.ssrcs.split(' ').map(ssrc => Number(ssrc));
  141. }
  142. if (mLine.ssrcs?.length) {
  143. return [ Number(mLine.ssrcs[0].id) ];
  144. }
  145. return null;
  146. }
  147. /**
  148. * Munges the given media description to enable simulcast for the video media sections that are in either have
  149. * SENDRECV or SENDONLY as the media direction thereby ignoring all the RECVONLY transceivers created for remote
  150. * endpoints.
  151. * NOTE: This needs to be called only when simulcast is enabled.
  152. *
  153. * @param description
  154. * @returns
  155. */
  156. mungeLocalDescription(description: IDescription): IDescription {
  157. if (!description?.sdp) {
  158. return description;
  159. }
  160. const session = transform.parse(description.sdp);
  161. for (let media of session.media) {
  162. // Ignore recvonly and inactive transceivers created for remote sources.
  163. if (media.direction === MediaDirection.RECVONLY || media.direction === MediaDirection.INACTIVE) {
  164. continue;
  165. }
  166. // Ignore audio m-lines.
  167. if (media.type !== MediaType.VIDEO) {
  168. continue;
  169. }
  170. const mid = media.mid;
  171. const numSsrcs = new Set(media.ssrcs?.map(ssrcInfo => ssrcInfo.id));
  172. const numGroups = media.ssrcGroups?.length ?? 0;
  173. let primarySsrc: number;
  174. // Do not munge if the description has no ssrcs or if simulcast is already enabled.
  175. if (numSsrcs.size === 0 || numSsrcs.size > 2 || (numSsrcs.size === 2 && numGroups === 0)) {
  176. continue;
  177. }
  178. if (numSsrcs.size === 1) {
  179. primarySsrc = Number(media.ssrcs[0]?.id);
  180. } else {
  181. const fidGroup = media.ssrcGroups.find(group => group.semantics === SSRC_GROUP_SEMANTICS.FID);
  182. if (fidGroup) {
  183. primarySsrc = Number(fidGroup.ssrcs.split(' ')[0]);
  184. }
  185. }
  186. if (this._ssrcCache.has(mid)) {
  187. media = this._fillSsrcsFromCache(media);
  188. } else {
  189. media = this._generateNewSsrcsForSimulcast(media, primarySsrc);
  190. const simulcastSsrcs = this._parseSimLayers(media);
  191. // Update the SSRCs in the cache so that they can re-used for the same mid again.
  192. this._ssrcCache.set(mid, simulcastSsrcs);
  193. }
  194. }
  195. return {
  196. type: description.type,
  197. sdp: transform.write(session)
  198. };
  199. }
  200. }