Meet Media API-Konzepte

Mit der Google Meet Media API kann Ihre App an einer Google Meet-Konferenz teilnehmen und Echtzeit-Medienstreams nutzen.

Clients verwenden WebRTC, um mit Meet-Servern zu kommunizieren. Die bereitgestellten Referenzclients (C++, TypeScript) veranschaulichen empfohlene Praktiken. Wir empfehlen Ihnen, direkt auf diesen aufzubauen.

Sie können aber auch vollständig benutzerdefinierte WebRTC-Clients erstellen, die den technischen Anforderungen der Meet Media API entsprechen.

Auf dieser Seite werden die wichtigsten WebRTC-Konzepte beschrieben, die für eine erfolgreiche Meet Media API-Sitzung erforderlich sind.

Signale für Angebotsantworten

WebRTC ist ein Peer-to-Peer-Framework (P2P), bei dem Peers durch Signalisierung miteinander kommunizieren. Um eine Sitzung zu starten, sendet der initiierende Peer ein SDP-Angebot an einen Remote-Peer. Dieses Angebot enthält die folgenden wichtigen Details:

Medienbeschreibungen für Audio- und Videoinhalte

Medienbeschreibungen geben an, was während P2P-Sitzungen übertragen wird. Es gibt drei Arten von Beschreibungen: Audio, Video und Daten.

Um n Audiostreams anzugeben, fügt der Anbieter n Audiomedienbeschreibungen in das Angebot ein. Dasselbe gilt für Videos. Es gibt jedoch maximal eine Datenmedienbeschreibung.

Verkehrsrichtung

Jede Audio- oder Videobeschreibung beschreibt einzelne Secure Real-time Transport Protocol (SRTP)-Streams, die von RFC 3711 verwaltet werden. Sie sind bidirektional, sodass zwei Peers Medien über dieselbe Verbindung senden und empfangen können.

Daher enthält jede Medienbeschreibung (sowohl im Angebot als auch in der Antwort) eines von drei Attributen, die beschreiben, wie der Stream verwendet werden soll:

  • sendonly: Es werden nur Medien vom anbietenden Peer gesendet. Der Remote-Peer sendet keine Medien über diesen Stream.

  • recvonly: Empfängt nur Medien vom Remote-Peer. Der Offering-Peer sendet keine Medien über diesen Stream.

  • sendrecv: Beide Peer können über diesen Stream senden und empfangen.

Codecs

Jede Medienbeschreibung gibt auch die Codecs an, die ein Peer unterstützt. Bei der Meet Media API werden Clientangebote abgelehnt, sofern sie nicht mindestens die in den technischen Anforderungen angegebenen Codecs unterstützen.

DTLS-Handshake

SRTP-Streams werden durch einen anfänglichen Datagram Transport Layer Security-Handshake („DTLS“, RFC 9147) zwischen den Peers gesichert. DTLS ist traditionell ein Client-zu-Server-Protokoll. Während des Signalisierungsprozesses erklärt sich ein Peer bereit, als Server zu fungieren, während der andere als Peer agiert.

Da jeder SRTP-Stream eine eigene DTLS-Verbindung haben kann, gibt jede Medienbeschreibung eines der drei Attribute an, um die Rolle des Peers im DTLS-Handshake anzugeben:

  • a=setup:actpass: Der Angebots-Peer überlässt die Auswahl dem Remote-Peer.

  • a=setup:active: Dieser Peer fungiert als Client.

  • a=setup:passive: Dieser Peer fungiert als Server.

Beschreibungen von App-Medien

Datenkanäle (RFC 8831) sind eine Abstraktion des Stream Control Transmission Protocol („SCTP“, RFC 9260).

Damit Datenkanäle während der anfänglichen Signalphase geöffnet werden können, muss das Angebot eine Anwendungsmedienbeschreibung enthalten. Im Gegensatz zu Audio- und Videobeschreibungen werden in App-Beschreibungen keine Richtung oder Codecs angegeben.

ICE-Kandidaten

Die Interactive Connectivity Establishment-Kandidaten („ICE“, RFC 8445) eines Peers sind eine Liste von Routen, die ein Remote-Peer zum Herstellen einer Verbindung verwenden kann.

Das kartesische Produkt der Listen der beiden Peers, die sogenannten Kandidatenpaare, stellt die potenziellen Routen zwischen zwei Peers dar. Diese Paare werden getestet, um die optimale Route zu ermitteln.

Signale über die Meet REST API senden

Verwenden Sie die Meet REST API, um dieses Angebot-Antwort-Signal zu senden. Ihre App stellt der Methode connectActiveConference() ein SDP-Angebot zur Verfügung und erhält im Gegenzug eine SDP-Antwort.

Beispiel für einen Verbindungsablauf

Hier ist ein Angebot mit einer Beschreibung von Audiomedien:

Beispiel für ein Angebot mit einer Beschreibung von Audiomedien.
Abbildung 1: Beispiel für ein Angebot mit einer Beschreibung von Audiomedien.

Der Remote-Peer antwortet mit einer SDP-Antwort, die dieselbe Anzahl von Zeilen mit Medienbeschreibungen enthält. Jede Zeile gibt an, welche Medien der Remote-Peer gegebenenfalls über die SRTP-Streams an den Angebotsclient zurücksendet. Der Remote-Peer kann auch bestimmte Streams vom Anbieter ablehnen, indem er den Eintrag in der Medienbeschreibung auf recvonly setzt.

Bei der Meet Media API senden Clients immer das SDP-Angebot, um eine Verbindung herzustellen. Meet ist nie der Initiator.

Dieses Verhalten wird intern von den Referenzclients (C++, TypeScript) verwaltet. Entwickler benutzerdefinierter Clients können jedoch die PeerConnectionInterface von WebRTC verwenden, um ein Angebot zu generieren.

Damit eine Verbindung zu Google Meet hergestellt werden kann, muss das Angebot bestimmte Anforderungen erfüllen:

  1. Der Client muss immer als Client im DTLS-Handshake agieren. Daher muss in jeder Medienbeschreibung im Angebot entweder a=setup:actpass oder a=setup:active angegeben werden.

  2. Jede Zeile der Medienbeschreibung muss alle erforderlichen Codecs für diesen Medientyp unterstützen:

    • Audio:Opus
    • Video:VP8, VP9, AV1
  3. Damit Audioinhalte empfangen werden können, muss das Angebot genau drei Beschreibungen von Audiomedien zum Empfangen enthalten. Dazu können Sie Transceiver für das Peer-Verbindungsobjekt festlegen.

    C++

    // ...
    rtc::scoped_refptr<webrtc::PeerConnectionInterface> peer_connection;
    
    for (int i = 0; i < 3; ++i) {
      webrtc::RtpTransceiverInit audio_init;
      audio_init.direction = webrtc::RtpTransceiverDirection::kRecvOnly;
      audio_init.stream_ids = {absl::StrCat("audio_stream_", i)};
    
      webrtc::RTCErrorOr<rtc::scoped_refptr<webrtc::RtpTransceiverInterface>>
        audio_result = peer_connection->AddTransceiver(
          cricket::MediaType::MEDIA_TYPE_AUDIO, audio_init);
    
      if (!audio_result.ok()) {
        return absl::InternalError(absl::StrCat("Failed to add audio transceiver: ",
                                                audio_result.error().message()));
      }
    }
    

    JavaScript

    pc = new RTCPeerConnection();
    
    // Configure client to receive audio from Meet servers.
    pc.addTransceiver('audio', {'direction':'recvonly'});
    pc.addTransceiver('audio', {'direction':'recvonly'});
    pc.addTransceiver('audio', {'direction':'recvonly'});
    
  4. Damit Videos empfangen werden können, muss das Angebot 1–3 Beschreibungen für Videomedien zum Empfangen enthalten. Dazu können Sie Transceiver für das Peer-Verbindungsobjekt festlegen.

    C++

    // ...
    rtc::scoped_refptr<webrtc::PeerConnectionInterface> peer_connection;
    
    for (uint32_t i = 0; i < configurations.receiving_video_stream_count; ++i) {
      webrtc::RtpTransceiverInit video_init;
      video_init.direction = webrtc::RtpTransceiverDirection::kRecvOnly;
      video_init.stream_ids = {absl::StrCat("video_stream_", i)};
    
      webrtc::RTCErrorOr<rtc::scoped_refptr<webrtc::RtpTransceiverInterface>>
          video_result = peer_connection->AddTransceiver(
            cricket::MediaType::MEDIA_TYPE_VIDEO, video_init);
    
      if (!video_result.ok()) {
        return absl::InternalError(absl::StrCat("Failed to add video transceiver: ",
                                                video_result.error().message()));
      }
    }
    

    JavaScript

    pc = new RTCPeerConnection();
    
    // Configure client to receive video from Meet servers.
    pc.addTransceiver('video', {'direction':'recvonly'});
    pc.addTransceiver('video', {'direction':'recvonly'});
    pc.addTransceiver('video', {'direction':'recvonly'});
    
  5. Das Angebot muss immer Datenkanäle enthalten. Die Kanäle session-control und media-stats sollten immer geöffnet sein. Alle Datenkanäle müssen ordered sein.

    C++

    // ...
    // All data channels must be ordered.
    constexpr webrtc::DataChannelInit kDataChannelConfig = {.ordered = true};
    
    rtc::scoped_refptr<webrtc::PeerConnectionInterface> peer_connection;
    
    // Signal session-control data channel.
    webrtc::RTCErrorOr<rtc::scoped_refptr<webrtc::DataChannelInterface>>
      session_create_result = peer_connection->CreateDataChannelOrError(
        "session-control", &kDataChannelConfig);
    
    if (!session_create_result.ok()) {
      return absl::InternalError(absl::StrCat("Failed to create data channel ",
                                              data_channel_label, ": ",
                                              session_create_result.error().message()));
    }
    
    // Signal media-stats data channel.
    webrtc::RTCErrorOr<rtc::scoped_refptr<webrtc::DataChannelInterface>>
      stats_create_result = peer_connection->CreateDataChannelOrError(
        "media-stats", &kDataChannelConfig);
    
    if (!stats_create_result.ok()) {
      return absl::InternalError(absl::StrCat("Failed to create data channel ",
                                              data_channel_label, ": ",
                                              stats_create_result.error().message()));
    }
    

    JavaScript

    // ...
    pc = new RTCPeerConnection();
    
    // All data channels must be ordered.
    const dataChannelConfig = {
      ordered: true,
    };
    
    // Signal session-control data channel.
    sessionControlChannel = pc.createDataChannel('session-control', dataChannelConfig);
    sessionControlChannel.onopen = () => console.log("data channel is now open");
    sessionControlChannel.onclose = () => console.log("data channel is now closed");
    sessionControlChannel.onmessage = async (e) => {
      console.log("data channel message", e.data);
    };
    
    // Signal media-stats data channel.
    mediaStatsChannel = pc.createDataChannel('media-stats', dataChannelConfig);
    mediaStatsChannel.onopen = () => console.log("data channel is now open");
    mediaStatsChannel.onclose = () => console.log("data channel is now closed");
    mediaStatsChannel.onmessage = async (e) => {
      console.log("data channel message", e.data);
    };
    

Beispiel für ein SDP-Angebot und eine SDP-Antwort

Hier ein vollständiges Beispiel für ein gültiges SDP-Angebot und eine zugehörige SDP-Antwort. Mit diesem Angebot wird eine Meet Media API-Sitzung mit Audio und einem einzelnen Videostream ausgehandelt.

Beachten Sie, dass es drei Audiomedienbeschreibungen, eine Videomedienbeschreibung und die erforderliche Anwendungsmedienbeschreibung gibt.

Beispiel ansehen

SDP-Angebot des Clients Meet Media API-SDP-Antwort
v=0
o=- 1479484780199836840 3 IN IP4 127.0.0.1
s=-
t=0 0
a=group:BUNDLE 0 1 2 3 4
a=extmap-allow-mixed
a=msid-semantic: WMS
v=0
o=- 0 2 IN IP4 127.0.0.1
s=-
t=0 0
a=group:BUNDLE 0 1 2 3 4
a=msid-semantic: WMS virtual-6666 virtual-video-7777/7777
a=ice-lite
m=audio 59905 UDP/TLS/RTP/SAVPF 111 63 9 0 8 13 110 126
c=IN IP4 136.55.18.35
a=rtcp:9 IN IP4 0.0.0.0
a=candidate:3490152339 1 udp 2113937151 46ae665f-23fd-49df-a002-6d12bc897a54.local 59905 typ host generation 0 network-cost 999
a=candidate:1937170525 1 udp 2113939711 aa575ae6-68fc-4155-83c1-007a1f5e8e55.local 58304 typ host generation 0 network-cost 999
a=candidate:2999458021 1 udp 1677732095 2605:a601:55ab:b000:615a:2317:bf6b:7a30 58304 typ srflx raddr :: rport 0 generation 0 network-cost 999
a=candidate:2517543359 1 udp 1677729535 136.55.18.35 59905 typ srflx raddr 0.0.0.0 rport 0 generation 0 network-cost 999
a=ice-ufrag:0HPF
a=ice-pwd:GcBv48eO/q64iPxb7MHKS87y
a=ice-options:trickle
a=fingerprint:sha-256 71:0A:DD:DF:D1:63:8E:D5:CB:E6:2B:6D:41:1D:D4:EE:79:B2:95:97:8A:F0:64:FF:10:37:8D:41:ED:DB:EC:C4
a=setup:actpass
a=mid:0
a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level
a=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time
a=extmap:3 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01
a=extmap:4 urn:ietf:params:rtp-hdrext:sdes:mid
a=recvonly
a=rtcp-mux
a=rtcp-rsize
a=rtpmap:111 opus/48000/2
a=rtcp-fb:111 transport-cc
a=fmtp:111 minptime=10;useinbandfec=1
a=rtpmap:63 red/48000/2
a=fmtp:63 111/111
a=rtpmap:9 G722/8000
a=rtpmap:0 PCMU/8000
a=rtpmap:8 PCMA/8000
a=rtpmap:13 CN/8000
a=rtpmap:110 telephone-event/48000
a=rtpmap:126 telephone-event/8000
m=audio 19306 UDP/TLS/RTP/SAVPF 111
c=IN IP4 142.250.82.213
a=rtcp:9 IN IP4 0.0.0.0
a=candidate: 1 udp 2113932031 142.250.82.213 19306 typ host generation 0
a=candidate: 1 tcp 2113932030 142.250.82.253 19306 typ host tcptype passive generation 0
a=candidate: 1 ssltcp 2113932029 142.250.82.253 19313 typ host generation 0
a=candidate: 1 udp 2113939711 2001:4860:4864:6:4000::19 19306 typ host generation 0
a=candidate: 1 tcp 2113939710 2001:4860:4864:6:8000::5 19306 typ host tcptype passive generation 0
a=candidate: 1 ssltcp 2113939709 2001:4860:4864:6:8000::5 19313 typ host generation 0
a=ice-ufrag:K8mRD3UolM6pjwoKAhgCCIoBnCgCIAEQ
a=ice-pwd:+7DfqMEDEFB6dLAKfGjT41l7ygg=
a=fingerprint:sha-256 32:C0:9D:17:AD:99:E2:B8:2D:FD:5D:87:D4:36:44:4A:5B:3E:EE:EA:F2:BE:BE:72:3B:66:4C:F2:57:3C:0D:FF
a=setup:passive
a=mid:0
a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level
a=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time
a=extmap:3 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01
a=sendonly
a=msid:virtual-6666 virtual-6666
a=rtcp-mux
a=rtpmap:111 opus/48000/2
a=fmtp:111 minptime=10;useinbandfec=1
a=ssrc:6666 cname:6666
m=audio 9 UDP/TLS/RTP/SAVPF 111 63 9 0 8 13 110 126
c=IN IP4 0.0.0.0
a=rtcp:9 IN IP4 0.0.0.0
a=ice-ufrag:0HPF
a=ice-pwd:GcBv48eO/q64iPxb7MHKS87y
a=ice-options:trickle
a=fingerprint:sha-256 71:0A:DD:DF:D1:63:8E:D5:CB:E6:2B:6D:41:1D:D4:EE:79:B2:95:97:8A:F0:64:FF:10:37:8D:41:ED:DB:EC:C4
a=setup:actpass
a=mid:1
a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level
a=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time
a=extmap:3 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01
a=extmap:4 urn:ietf:params:rtp-hdrext:sdes:mid
a=recvonly
a=rtcp-mux
a=rtcp-rsize
a=rtpmap:111 opus/48000/2
a=rtcp-fb:111 transport-cc
a=fmtp:111 minptime=10;useinbandfec=1
a=rtpmap:63 red/48000/2
a=fmtp:63 111/111
a=rtpmap:9 G722/8000
a=rtpmap:0 PCMU/8000
a=rtpmap:8 PCMA/8000
a=rtpmap:13 CN/8000
a=rtpmap:110 telephone-event/48000
a=rtpmap:126 telephone-event/8000
m=audio 9 UDP/TLS/RTP/SAVPF 111
c=IN IP4 0.0.0.0
a=rtcp:9 IN IP4 0.0.0.0
a=ice-ufrag:K8mRD3UolM6pjwoKAhgCCIoBnCgCIAEQ
a=ice-pwd:+7DfqMEDEFB6dLAKfGjT41l7ygg=
a=fingerprint:sha-256 32:C0:9D:17:AD:99:E2:B8:2D:FD:5D:87:D4:36:44:4A:5B:3E:EE:EA:F2:BE:BE:72:3B:66:4C:F2:57:3C:0D:FF
a=setup:passive
a=mid:1
a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level
a=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time
a=extmap:3 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01
a=sendonly
a=msid:virtual-6667 virtual-6667
a=rtcp-mux
a=rtpmap:111 opus/48000/2
a=fmtp:111 minptime=10;useinbandfec=1
a=ssrc:6667 cname:6667
m=audio 9 UDP/TLS/RTP/SAVPF 111 63 9 0 8 13 110 126
c=IN IP4 0.0.0.0
a=rtcp:9 IN IP4 0.0.0.0
a=ice-ufrag:0HPF
a=ice-pwd:GcBv48eO/q64iPxb7MHKS87y
a=ice-options:trickle
a=fingerprint:sha-256 71:0A:DD:DF:D1:63:8E:D5:CB:E6:2B:6D:41:1D:D4:EE:79:B2:95:97:8A:F0:64:FF:10:37:8D:41:ED:DB:EC:C4
a=setup:actpass
a=mid:2
a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level
a=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time
a=extmap:3 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01
a=extmap:4 urn:ietf:params:rtp-hdrext:sdes:mid
a=recvonly
a=rtcp-mux
a=rtcp-rsize
a=rtpmap:111 opus/48000/2
a=rtcp-fb:111 transport-cc
a=fmtp:111 minptime=10;useinbandfec=1
a=rtpmap:63 red/48000/2
a=fmtp:63 111/111
a=rtpmap:9 G722/8000
a=rtpmap:0 PCMU/8000
a=rtpmap:8 PCMA/8000
a=rtpmap:13 CN/8000
a=rtpmap:110 telephone-event/48000
a=rtpmap:126 telephone-event/8000
m=audio 9 UDP/TLS/RTP/SAVPF 111
c=IN IP4 0.0.0.0
a=rtcp:9 IN IP4 0.0.0.0
a=ice-ufrag:K8mRD3UolM6pjwoKAhgCCIoBnCgCIAEQ
a=ice-pwd:+7DfqMEDEFB6dLAKfGjT41l7ygg=
a=fingerprint:sha-256 32:C0:9D:17:AD:99:E2:B8:2D:FD:5D:87:D4:36:44:4A:5B:3E:EE:EA:F2:BE:BE:72:3B:66:4C:F2:57:3C:0D:FF
a=setup:passive
a=mid:2
a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level
a=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time
a=extmap:3 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01
a=sendonly
a=msid:virtual-6668 virtual-6668
a=rtcp-mux
a=rtpmap:111 opus/48000/2
a=fmtp:111 minptime=10;useinbandfec=1
a=ssrc:6668 cname:6668
m=application 9 UDP/DTLS/SCTP webrtc-datachannel
c=IN IP4 0.0.0.0
a=ice-ufrag:0HPF
a=ice-pwd:GcBv48eO/q64iPxb7MHKS87y
a=ice-options:trickle
a=fingerprint:sha-256 71:0A:DD:DF:D1:63:8E:D5:CB:E6:2B:6D:41:1D:D4:EE:79:B2:95:97:8A:F0:64:FF:10:37:8D:41:ED:DB:EC:C4
a=setup:actpass
a=mid:3
a=sctp-port:5000
a=max-message-size:262144
m=application 9 DTLS/SCTP 5000
c=IN IP4 0.0.0.0
a=ice-ufrag:K8mRD3UolM6pjwoKAhgCCIoBnCgCIAEQ
a=ice-pwd:+7DfqMEDEFB6dLAKfGjT41l7ygg=
a=fingerprint:sha-256 32:C0:9D:17:AD:99:E2:B8:2D:FD:5D:87:D4:36:44:4A:5B:3E:EE:EA:F2:BE:BE:72:3B:66:4C:F2:57:3C:0D:FF
a=setup:passive
a=mid:3
a=sctpmap:5000 webrtc-datachannel 1024
m=video 9 UDP/TLS/RTP/SAVPF 96 97 98 99 100 101 35 36 37 38 102 103 104 105 106 107 108 109 127 125 39 40 41 42 43 44 45 46 47 48 112 113 114 115 116 117 118 49
c=IN IP4 0.0.0.0
a=rtcp:9 IN IP4 0.0.0.0
a=ice-ufrag:0HPF
a=ice-pwd:GcBv48eO/q64iPxb7MHKS87y
a=ice-options:trickle
a=fingerprint:sha-256 71:0A:DD:DF:D1:63:8E:D5:CB:E6:2B:6D:41:1D:D4:EE:79:B2:95:97:8A:F0:64:FF:10:37:8D:41:ED:DB:EC:C4
a=setup:actpass
a=mid:4
a=extmap:14 urn:ietf:params:rtp-hdrext:toffset
a=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time
a=extmap:13 urn:3gpp:video-orientation
a=extmap:3 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01
a=extmap:5 http://www.webrtc.org/experiments/rtp-hdrext/playout-delay
a=extmap:6 http://www.webrtc.org/experiments/rtp-hdrext/video-content-type
a=extmap:7 http://www.webrtc.org/experiments/rtp-hdrext/video-timing
a=extmap:8 http://www.webrtc.org/experiments/rtp-hdrext/color-space
a=extmap:4 urn:ietf:params:rtp-hdrext:sdes:mid
a=extmap:10 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id
a=extmap:11 urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id
a=recvonly
a=rtcp-mux
a=rtcp-rsize
a=rtpmap:96 VP8/90000
a=rtcp-fb:96 goog-remb
a=rtcp-fb:96 transport-cc
a=rtcp-fb:96 ccm fir
a=rtcp-fb:96 nack
a=rtcp-fb:96 nack pli
a=rtpmap:97 rtx/90000
a=fmtp:97 apt=96
a=rtpmap:98 VP9/90000
a=rtcp-fb:98 goog-remb
a=rtcp-fb:98 transport-cc
a=rtcp-fb:98 ccm fir
a=rtcp-fb:98 nack
a=rtcp-fb:98 nack pli
a=fmtp:98 profile-id=0
a=rtpmap:99 rtx/90000
a=fmtp:99 apt=98
a=rtpmap:100 VP9/90000
a=rtcp-fb:100 goog-remb
a=rtcp-fb:100 transport-cc
a=rtcp-fb:100 ccm fir
a=rtcp-fb:100 nack
a=rtcp-fb:100 nack pli
a=fmtp:100 profile-id=2
a=rtpmap:101 rtx/90000
a=fmtp:101 apt=100
a=rtpmap:35 VP9/90000
a=rtcp-fb:35 goog-remb
a=rtcp-fb:35 transport-cc
a=rtcp-fb:35 ccm fir
a=rtcp-fb:35 nack
a=rtcp-fb:35 nack pli
a=fmtp:35 profile-id=1
a=rtpmap:36 rtx/90000
a=fmtp:36 apt=35
a=rtpmap:37 VP9/90000
a=rtcp-fb:37 goog-remb
a=rtcp-fb:37 transport-cc
a=rtcp-fb:37 ccm fir
a=rtcp-fb:37 nack
a=rtcp-fb:37 nack pli
a=fmtp:37 profile-id=3
a=rtpmap:38 rtx/90000
a=fmtp:38 apt=37
a=rtpmap:102 H264/90000
a=rtcp-fb:102 goog-remb
a=rtcp-fb:102 transport-cc
a=rtcp-fb:102 ccm fir
a=rtcp-fb:102 nack
a=rtcp-fb:102 nack pli
a=fmtp:102 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42001f
a=rtpmap:103 rtx/90000
a=fmtp:103 apt=102
a=rtpmap:104 H264/90000
a=rtcp-fb:104 goog-remb
a=rtcp-fb:104 transport-cc
a=rtcp-fb:104 ccm fir
a=rtcp-fb:104 nack
a=rtcp-fb:104 nack pli
a=fmtp:104 level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42001f
a=rtpmap:105 rtx/90000
a=fmtp:105 apt=104
a=rtpmap:106 H264/90000
a=rtcp-fb:106 goog-remb
a=rtcp-fb:106 transport-cc
a=rtcp-fb:106 ccm fir
a=rtcp-fb:106 nack
a=rtcp-fb:106 nack pli
a=fmtp:106 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f
a=rtpmap:107 rtx/90000
a=fmtp:107 apt=106
a=rtpmap:108 H264/90000
a=rtcp-fb:108 goog-remb
a=rtcp-fb:108 transport-cc
a=rtcp-fb:108 ccm fir
a=rtcp-fb:108 nack
a=rtcp-fb:108 nack pli
a=fmtp:108 level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42e01f
a=rtpmap:109 rtx/90000
a=fmtp:109 apt=108
a=rtpmap:127 H264/90000
a=rtcp-fb:127 goog-remb
a=rtcp-fb:127 transport-cc
a=rtcp-fb:127 ccm fir
a=rtcp-fb:127 nack
a=rtcp-fb:127 nack pli
a=fmtp:127 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=4d001f
a=rtpmap:125 rtx/90000
a=fmtp:125 apt=127
a=rtpmap:39 H264/90000
a=rtcp-fb:39 goog-remb
a=rtcp-fb:39 transport-cc
a=rtcp-fb:39 ccm fir
a=rtcp-fb:39 nack
a=rtcp-fb:39 nack pli
a=fmtp:39 level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=4d001f
a=rtpmap:40 rtx/90000
a=fmtp:40 apt=39
a=rtpmap:41 H264/90000
a=rtcp-fb:41 goog-remb
a=rtcp-fb:41 transport-cc
a=rtcp-fb:41 ccm fir
a=rtcp-fb:41 nack
a=rtcp-fb:41 nack pli
a=fmtp:41 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=f4001f
a=rtpmap:42 rtx/90000
a=fmtp:42 apt=41
a=rtpmap:43 H264/90000
a=rtcp-fb:43 goog-remb
a=rtcp-fb:43 transport-cc
a=rtcp-fb:43 ccm fir
a=rtcp-fb:43 nack
a=rtcp-fb:43 nack pli
a=fmtp:43 level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=f4001f
a=rtpmap:44 rtx/90000
a=fmtp:44 apt=43
a=rtpmap:45 AV1/90000
a=rtcp-fb:45 goog-remb
a=rtcp-fb:45 transport-cc
a=rtcp-fb:45 ccm fir
a=rtcp-fb:45 nack
a=rtcp-fb:45 nack pli
a=fmtp:45 level-idx=5;profile=0;tier=0
a=rtpmap:46 rtx/90000
a=fmtp:46 apt=45
a=rtpmap:47 AV1/90000
a=rtcp-fb:47 goog-remb
a=rtcp-fb:47 transport-cc
a=rtcp-fb:47 ccm fir
a=rtcp-fb:47 nack
a=rtcp-fb:47 nack pli
a=fmtp:47 level-idx=5;profile=1;tier=0
a=rtpmap:48 rtx/90000
a=fmtp:48 apt=47
a=rtpmap:112 H264/90000
a=rtcp-fb:112 goog-remb
a=rtcp-fb:112 transport-cc
a=rtcp-fb:112 ccm fir
a=rtcp-fb:112 nack
a=rtcp-fb:112 nack pli
a=fmtp:112 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=64001f
a=rtpmap:113 rtx/90000
a=fmtp:113 apt=112
a=rtpmap:114 H264/90000
a=rtcp-fb:114 goog-remb
a=rtcp-fb:114 transport-cc
a=rtcp-fb:114 ccm fir
a=rtcp-fb:114 nack
a=rtcp-fb:114 nack pli
a=fmtp:114 level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=64001f
a=rtpmap:115 rtx/90000
a=fmtp:115 apt=114
a=rtpmap:116 red/90000
a=rtpmap:117 rtx/90000
a=fmtp:117 apt=116
a=rtpmap:118 ulpfec/90000
a=rtpmap:49 flexfec-03/90000
a=rtcp-fb:49 goog-remb
a=rtcp-fb:49 transport-cc
a=fmtp:49 repair-window=10000000
m=video 9 UDP/TLS/RTP/SAVPF 96 97 98 99
c=IN IP4 0.0.0.0
a=rtcp:9 IN IP4 0.0.0.0
a=ice-ufrag:K8mRD3UolM6pjwoKAhgCCIoBnCgCIAEQ
a=ice-pwd:+7DfqMEDEFB6dLAKfGjT41l7ygg=
a=fingerprint:sha-256 32:C0:9D:17:AD:99:E2:B8:2D:FD:5D:87:D4:36:44:4A:5B:3E:EE:EA:F2:BE:BE:72:3B:66:4C:F2:57:3C:0D:FF
a=setup:passive
a=mid:4
a=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time
a=extmap:13 urn:3gpp:video-orientation
a=extmap:3 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01
a=sendonly
a=msid:virtual-video-7777/7777 virtual-video-7777/7777
a=rtcp-mux
a=rtpmap:96 VP8/90000
a=rtcp-fb:96 ccm fir
a=rtcp-fb:96 nack
a=rtcp-fb:96 nack pli
a=rtcp-fb:96 goog-remb
a=rtpmap:97 rtx/90000
a=fmtp:97 apt=96
a=rtpmap:98 VP9/90000
a=rtcp-fb:98 ccm fir
a=rtcp-fb:98 nack
a=rtcp-fb:98 nack pli
a=rtcp-fb:98 goog-remb
a=fmtp:98 profile-id=0
a=rtpmap:99 rtx/90000
a=fmtp:99 apt=98
a=ssrc-group:FID 7777 7778
a=ssrc:7777 cname:7777
a=ssrc:7778 cname:7777