Developer Preview:
พร้อมใช้งานเป็นส่วนหนึ่งของโปรแกรม Developer Preview ของ Google Workspace ซึ่งให้สิทธิ์เข้าถึงฟีเจอร์บางอย่างก่อนใคร
หากต้องการใช้ Meet Media API เพื่อเข้าถึงสื่อแบบเรียลไทม์จากการประชุม โปรเจ็กต์ Google Cloud, หลักการ OAuth และผู้เข้าร่วมทุกคนในการประชุมต้องลงทะเบียนในโปรแกรม Developer Preview
Google Meet Media API ช่วยให้แอปของคุณเข้าร่วมการประชุม Google Meet
และใช้สตรีมสื่อแบบเรียลไทม์
ได้
ไคลเอ็นต์ใช้ WebRTC เพื่อสื่อสารกับเซิร์ฟเวอร์ Meet ไคลเอ็นต์อ้างอิง (C++ ,
TypeScript ) ที่ให้มาแสดงแนวทางปฏิบัติแนะนำ และ
เราขอแนะนำให้คุณสร้างไคลเอ็นต์โดยอิงตามไคลเอ็นต์อ้างอิงเหล่านี้โดยตรง
อย่างไรก็ตาม คุณยังสร้างไคลเอ็นต์ WebRTC ที่กำหนดเองได้อย่างเต็มที่ซึ่งเป็นไปตาม
ข้อกำหนดทางเทคนิค
ของ Meet Media API
หน้านี้สรุปแนวคิดหลักของ WebRTC ที่จำเป็นสำหรับเซสชัน Meet Media API ที่ประสบความสำเร็จ
การส่งสัญญาณข้อเสนอ-คำตอบ
WebRTC เป็นเฟรมเวิร์กแบบเพียร์ทูเพียร์ (P2P) ซึ่งเพียร์จะสื่อสารกันโดยการส่งสัญญาณถึงกัน หากต้องการเริ่มเซสชัน เพียร์ที่เริ่มต้นจะส่งข้อเสนอ
SDP ไปยังเพียร์ระยะไกล ข้อเสนอนี้มีรายละเอียดที่สำคัญดังนี้
คำอธิบายสื่อระบุสิ่งที่สื่อสารระหว่างเซสชัน P2P คำอธิบายมี 3 ประเภท ได้แก่ เสียง วิดีโอ และข้อมูล
หากต้องการระบุสตรีมเสียง n รายการ ผู้เสนอจะใส่คำอธิบายสื่อเสียง n รายการในข้อเสนอ เช่นเดียวกันกับวิดีโอ อย่างไรก็ตาม จะมีคำอธิบายสื่อ ข้อมูล เพียงรายการเดียวเท่านั้น
ทิศทาง
คำอธิบายเสียงหรือวิดีโอแต่ละรายการจะอธิบายสตรีม โปรโตคอลรับส่งข้อมูลเรียลไทม์แบบปลอดภัย (SRTP) แต่ละรายการ ซึ่งอยู่ในบังคับของ RFC
3711 สตรีมเหล่านี้เป็นแบบ 2 ทิศทาง ซึ่งช่วยให้เพียร์ 2 รายการส่งและรับสื่อผ่านการเชื่อมต่อเดียวกันได้
ด้วยเหตุนี้ คำอธิบายสื่อแต่ละรายการ (ทั้งในข้อเสนอและคำตอบ) จึงมีแอตทริบิวต์ 1 ใน 3 รายการที่อธิบายวิธีใช้สตรีม ดังนี้
sendonly : ส่งสื่อจากเพียร์ที่เสนอเท่านั้น เพียร์ระยะไกลจะไม่ส่งสื่อในสตรีมนี้
recvonly : รับสื่อจากเพียร์ระยะไกลเท่านั้น เพียร์ที่เสนอจะไม่ส่งสื่อในสตรีมนี้
sendrecv : เพียร์ทั้ง 2 รายการอาจส่งและรับในสตรีมนี้
ตัวแปลงรหัส
คำอธิบายสื่อแต่ละรายการยังระบุตัวแปลงรหัสที่เพียร์รองรับด้วย ในกรณีของ
Meet Media API ระบบจะปฏิเสธข้อเสนอของไคลเอ็นต์หากไม่รองรับ
(อย่างน้อย) ตัวแปลงรหัสที่ระบุไว้ในข้อกำหนด
ทางเทคนิค
แฮนด์เชค DTLS
สตรีม SRTP ได้รับการรักษาความปลอดภัยโดยการแฮนด์เชคเริ่มต้น
Datagram Transport Layer Security ("DTLS", RFC
9147 ) ระหว่างเพียร์
โดยปกติแล้ว DTLS เป็นโปรโตคอลแบบไคลเอ็นต์-เซิร์ฟเวอร์ ในระหว่างกระบวนการส่งสัญญาณ เพียร์รายหนึ่งจะตกลงที่จะทำหน้าที่เป็นเซิร์ฟเวอร์ ส่วนอีกรายจะทำหน้าที่เป็นเพียร์
เนื่องจากสตรีม SRTP แต่ละรายการอาจมีการเชื่อมต่อ DTLS เฉพาะของตัวเอง คำอธิบายสื่อแต่ละรายการจึงระบุแอตทริบิวต์ 1 ใน 3 รายการเพื่อระบุบทบาทของเพียร์ในการแฮนด์เชค DTLS ดังนี้
a=setup:actpass : เพียร์ ที่เสนอ จะรอการเลือกของเพียร์ระยะไกล
a=setup:active : เพียร์นี้ทำหน้าที่เป็นไคลเอ็นต์
a=setup:passive : เพียร์นี้ทำหน้าที่เป็นเซิร์ฟเวอร์
ช่องข้อมูล (RFC 8831 ) เป็น
การแยกรายละเอียดของ Stream Control Transmission Protocol ("SCTP", RFC
9260 )
หากต้องการเปิดช่องข้อมูลในระหว่างระยะการส่งสัญญาณเริ่มต้น ข้อเสนอต้องมี คำอธิบายสื่อของแอปพลิเคชัน คำอธิบายแอปพลิเคชันจะไม่ระบุทิศทางหรือตัวแปลงรหัส ซึ่งแตกต่างจากคำอธิบายเสียงและวิดีโอ
ICE Candidate
Candidate Interactive Connectivity Establishment ("ICE", RFC
8445 ) ของเพียร์คือรายการ
เส้นทางที่เพียร์ระยะไกลอาจใช้เพื่อสร้างการเชื่อมต่อ
ผลคูณคาร์ทีเซียนของรายการของเพียร์ 2 รายการ ซึ่งเรียกว่า คู่ Candidate
แสดงเส้นทางที่เป็นไปได้ระหว่างเพียร์ 2 รายการ ระบบจะทดสอบคู่เหล่านี้เพื่อกำหนดเส้นทางที่เหมาะสมที่สุด
ส่งสัญญาณผ่าน Meet REST API
ใช้ Meet REST API เพื่อดำเนินการส่งสัญญาณข้อเสนอ-คำตอบนี้
แอปของคุณจะส่งข้อเสนอ
SDP ไปยังเมธอด
connectActiveConference()
และรับคำตอบ
SDP กลับมา
ตัวอย่างโค้ดต่อไปนี้แสดงวิธีเรียกเมธอด
ตัวอย่างขั้นตอนการเชื่อมต่อ
นี่คือข้อเสนอที่มีคำอธิบายสื่อเสียง
รูปที่ 1 ตัวอย่างข้อเสนอที่มีคำอธิบายสื่อเสียง
เพียร์ระยะไกลจะตอบกลับด้วยคำตอบ SDP ที่มีบรรทัดคำอธิบายสื่อจำนวนเท่ากัน
แต่ละบรรทัดจะระบุสื่อที่เพียร์ระยะไกลส่งกลับไปยังไคลเอ็นต์ที่เสนอผ่านสตรีม SRTP (หากมี) เพียร์ระยะไกลอาจปฏิเสธสตรีมบางรายการจากผู้เสนอโดยตั้งค่ารายการคำอธิบายสื่อนั้นเป็น recvonly
สำหรับ Meet Media API ไคลเอ็นต์จะส่งข้อเสนอ SDP เสมอเพื่อเริ่มการเชื่อมต่อ Meet จะไม่เป็นผู้เริ่ม
ไคลเอ็นต์อ้างอิง
(C++ , TypeScript ) จะจัดการลักษณะการทำงานนี้ภายใน
แต่ผู้พัฒนาไคลเอ็นต์ที่กำหนดเองสามารถใช้ PeerConnectionInterface ของ WebRTC เพื่อ
สร้างข้อเสนอได้
หากต้องการเชื่อมต่อกับ Meet ข้อเสนอต้องเป็นไปตามข้อกำหนดต่อไปนี้
:
ไคลเอ็นต์ต้องทำหน้าที่เป็นไคลเอ็นต์ในการแฮนด์เชค DTLS เสมอ ดังนั้นคำอธิบายสื่อทุกรายการในข้อเสนอต้องระบุ a=setup:actpass หรือ a=setup:active
บรรทัดคำอธิบายสื่อแต่ละบรรทัดต้องรองรับตัวแปลงรหัสที่จำเป็นทั้งหมดสำหรับสื่อประเภทนั้นๆ ดังนี้
เสียง: Opus
วิดีโอ: VP8, VP9, AV1
หากต้องการรับเสียง ข้อเสนอต้องมีคำอธิบายสื่อเสียงแบบรับอย่างเดียว 3 รายการ คุณทำได้โดยการตั้งค่าตัวรับส่งสัญญาณในออบเจ็กต์การเชื่อมต่อเพียร์
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' });
หากต้องการรับวิดีโอ ข้อเสนอต้องมีคำอธิบายสื่อวิดีโอแบบรับอย่างเดียว 1-3 รายการ คุณทำได้โดยการตั้งค่าตัวรับส่งสัญญาณในออบเจ็กต์การเชื่อมต่อเพียร์
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' });
ข้อเสนอต้องมีช่องข้อมูลเสมอ ช่อง session-control และ media-stats ควรเปิดอยู่เสมอ ช่องข้อมูลทั้งหมดต้องเป็น ordered
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 );
};
ตัวอย่างข้อเสนอและคำตอบ SDP
นี่คือตัวอย่างข้อเสนอ SDP ที่ถูกต้องและคำตอบ SDP ที่ตรงกันแบบเต็ม ข้อเสนอนี้จะเจรจาเซสชัน Meet Media API ที่มีเสียงและสตรีมวิดีโอเดียว
สังเกตว่ามีคำอธิบายสื่อเสียง 3 รายการ คำอธิบายสื่อวิดีโอ 1 รายการ และคำอธิบายสื่อของแอปพลิเคชันที่จำเป็น
หมายเหตุ: ระบบจะแยกส่วนต่างๆ ออกเป็นแถวเพื่อให้ดูได้ง่ายขึ้น
ข้อเสนอ SDP ของไคลเอ็นต์
คำตอบ SDP ของ 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