Entwicklervorschau:
Verfügbar im Rahmen des Google Workspace-Vorschauprogramms für Entwickler , das frühzeitigen Zugriff auf bestimmte Funktionen gewährt.
Wenn Sie mit der Meet Media API auf Echtzeitmedien aus einer Konferenz zugreifen möchten, müssen das Google Cloud-Projekt, das OAuth-Prinzip und alle Teilnehmer der Konferenz für das Entwicklervorschauprogramm registriert sein.
Mit der Google Meet Media API kann Ihre App an einer Google Meet
Konferenz teilnehmen und Echtzeit-
Media-Streams nutzen.
Clients verwenden WebRTC, um mit Meet-Servern zu kommunizieren. Die bereitgestellten
Referenzclients (C++ ,
TypeScript ) zeigen empfohlene Vorgehensweisen.
Wir empfehlen, direkt darauf 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.
Angebot-Antwort-Signalisierung
WebRTC ist ein Peer-to-Peer-Framework (P2P), bei dem Peers miteinander kommunizieren, indem sie sich gegenseitig signalisieren. Um eine Sitzung zu starten, sendet der initiierende Peer ein SDP
Angebot an einen Remote-Peer. Dieses Angebot enthält die folgenden wichtigen Details:
Mediabeschreibungen geben an, was während P2P-Sitzungen kommuniziert wird. Es gibt drei Arten von Beschreibungen: Audio, Video und Daten.
Um n Audiostreams anzugeben, fügt der Anbieter n Audio-Mediabeschreibungen in das Angebot ein. Dasselbe gilt für Video. Es gibt jedoch höchstens eine Daten -Mediabeschreibung.
Verkehrsrichtung
Jede Audio- oder Videobeschreibung beschreibt einzelne Secure Real-time Transport
Protocol (SRTP)-Streams, die durch RFC
3711 geregelt werden. Diese sind bidirektional, sodass zwei Peers Medien über dieselbe Verbindung senden und empfangen können.
Daher enthält jede Mediabeschreibung (sowohl im Angebot als auch in der Antwort) eines von drei Attributen, das beschreibt, wie der Stream verwendet werden soll:
sendonly : Sendet nur Medien vom anbietenden Peer. Der Remote-Peer sendet keine Medien in diesem Stream.
recvonly : Empfängt nur Medien vom Remote-Peer. Der anbietende Peer sendet keine Medien in diesem Stream.
sendrecv : Beide Peers können in diesem Stream senden und empfangen.
Codecs
Jede Mediabeschreibung gibt auch die Codecs an, die ein Peer unterstützt. Bei der
Meet Media API werden Clientangebote abgelehnt, wenn 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-Server-Protokoll. Während des Signalisierungsprozesses stimmt ein Peer zu, als Server zu fungieren, während der andere als Peer fungiert.
Da jeder SRTP-Stream eine eigene DTLS-Verbindung haben kann, gibt jede Mediabeschreibung eines von drei Attributen an, um die Rolle des Peers beim DTLS-Handshake anzugeben:
a=setup:actpass : Der anbietende Peer richtet sich nach der Wahl des Remote-Peers.
a=setup:active : Dieser Peer fungiert als Client.
a=setup:passive : Dieser Peer fungiert als Server.
Datenkanäle (RFC 8831 ) sind
eine Abstraktion des Stream Control Transmission Protocol ("SCTP", RFC
9260 ).
Um während der anfänglichen Signalisierungsphase Datenkanäle zu öffnen, muss das Angebot eine Mediabeschreibung für Anwendungen enthalten. Im Gegensatz zu Audio- und Videobeschreibungen geben Anwendungsbeschreibungen keine Richtung oder Codecs an.
ICE-Kandidaten
Die Interactive Connectivity Establishment -Kandidaten („ICE“, RFC
8445 ) eines Peers sind eine Liste von Routen, die ein Remote-Peer verwenden kann, um eine Verbindung herzustellen.
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.
Signalisierung über die Meet REST API
Verwenden Sie die Meet REST API , um diese
Angebot-Antwort Signalisierung durchzuführen. Ihre App stellt der
connectActiveConference()
Methode ein
SDP-Angebot zur Verfügung und erhält im Gegenzug eine
SDP-Antwort .
Die folgenden Codebeispiele zeigen, wie die Methode aufgerufen wird:
Beispiel für einen Verbindungsablauf
Hier ist ein Angebot mit einer Audio-Mediabeschreibung:
Abbildung 1 Beispielangebot mit einer Audio-Mediabeschreibung.
Der Remote-Peer antwortet mit einer SDP
Antwort , die dieselbe Anzahl
von Mediabeschreibungszeilen enthält. Jede Zeile gibt an, welche Medien der Remote-Peer gegebenenfalls über die SRTP-Streams an den anbietenden Client zurücksendet. Der Remote-Peer kann auch bestimmte Streams vom Anbieter ablehnen, indem er den entsprechenden Eintrag in der Mediabeschreibung 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,
aber Entwickler benutzerdefinierter Clients können WebRTCs PeerConnectionInterface verwenden,
um ein Angebot zu erstellen.
Um eine Verbindung zu Meet herzustellen, muss das Angebot bestimmte
Anforderungen erfüllen:
Der Client muss beim DTLS-Handshake immer als Client fungieren. Daher muss jede Mediabeschreibung im Angebot entweder a=setup:actpass oder a=setup:active angeben.
Jede Mediabeschreibungszeile muss alle erforderlichen
Codecs für diesen Medien
typ unterstützen:
Audio :Opus
Video :VP8, VP9, AV1
Um Audio zu empfangen, muss das Angebot genau drei Audio-Mediabeschreibungen enthalten, die nur zum Empfangen dienen. 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' });
Um Video zu empfangen, muss das Angebot 1 bis 3 Video-Mediabeschreibungen enthalten, die nur zum Empfangen dienen. 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' });
Das Angebot muss immer Datenkanäle enthalten. Mindestens 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 ist ein vollständiges Beispiel für ein gültiges SDP-Angebot und eine passende SDP-Antwort. Dieses Angebot verhandelt eine Meet Media API-Sitzung mit Audio und einem einzelnen Videostream.
Es gibt drei Audio-Mediabeschreibungen, eine Video-Mediabeschreibung und die erforderliche Mediabeschreibung für Anwendungen.
Hinweis: Die Abschnitte sind zur besseren Lesbarkeit in Zeilen unterteilt.
SDP-Angebot des Clients
SDP-Antwort der Meet Media API
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