天天看點

WebRTC.Net庫開發進階,教你實作螢幕共享和多路複用!

作者:小乖獸技術
WebRTC.Net庫開發進階,教你實作螢幕共享和多路複用!

上一篇文章詳細介紹了WebRTC.Net庫的入門用法。WebRTC.Net庫:讓你的應用更親民友好,實作視訊通話無痛接入! 除了基本用法外,還有一些進階用法可以更好地利用該庫。

WebRTC.Net庫開發進階,教你實作螢幕共享和多路複用!

自定義 STUN/TURN 伺服器配置

WebRTC.Net 預設使用 Google 的 STUN 伺服器和 Coturn 的 TURN 伺服器。如果你需要使用其他 STUN/TURN 伺服器,則可以在初始化 PeerConnectionFactory 和 PeerConnection 時設定自定義配置。

例如,以下代碼設定了使用 coturn 伺服器的 PeerConnectionFactory:

var config = new PeerConnectionConfiguration
{
   IceServers = new List<IceServer>
   {
      new IceServer{ Urls = new[] { "stun:stun.l.google.com:19302" }},
      new IceServer{ Urls = new[] { "turn:my-turn-server.com" }, Username="myusername", Credential="mypassword" }
   }
};
var factory = new PeerConnectionFactory(config);
           

在不同線程中建立和使用 PeerConnectionFactory 和 PeerConnection 對象:

WebRTC.Net 庫本質上是基于線程的,是以它的對象通常在單獨的線程中建立和使用。這樣可以避免在主線程中對 UI 線程造成大量負擔。

以下代碼在背景線程中建立并使用 PeerConnection 對象:

Task.Run(() =>
{
   var config = new PeerConnectionConfiguration { IceServers = new List<IceServer> { new IceServer { Urls = new[] { "stun:stun.l.google.com:19302" } } } };
   var factory = new PeerConnectionFactory(config);
   var pc = factory.CreatePeerConnection(config);   

   // 在這裡使用 PeerConnection 對象,不會阻塞主線程

}).Wait(); 
           

選擇視訊和音頻裝置

在建立 PeerConnectionFactory 對象時,可以設定 defaultAudioDevice 和 defaultVideoDevice 參數以選擇預設的音頻和視訊裝置。

例如,以下如何通過裝置名稱選擇視訊和音頻裝置:

var config = new PeerConnectionConfiguration
{
   IceServers = new List<IceServer> { new IceServer { Urls = new[] { "stun:stun.l.google.com:19302" } } },
   DefaultVideoDevice = VideoCaptureDevice.GetDevices().FirstOrDefault(x => x.Name == "MyCameraName"),
   DefaultAudioDevice = AudioCaptureDevice.GetDevices().FirstOrDefault(x => x.Name == "MyMicrophoneName")
};
var factory = new PeerConnectionFactory(config);
           

實作資料通道

WebRTC.Net 庫不僅支援音視訊傳輸,還支援實作資料通道(DataChannel)。使用資料通道,應用程式可以在用戶端之間傳輸任意類型的資料,例如聊天消息、遊戲狀态等。

以下代碼如何建立資料通道:

// 建立 PeerConnection 對象
var config = new PeerConnectionConfiguration { IceServers = new List<IceServer> { new IceServer { Urls = new[] { "stun:stun.l.google.com:19302" } } } };
var factory = new PeerConnectionFactory(config);
var pc = factory.CreatePeerConnection(config);

// 建立資料通道
var dcConfig = new DataChannelInit { Ordered = true };
var dc = pc.CreateDataChannel("mydatachannel", dcConfig);

// 監聽資料通道事件
dc.MessageReceived += (sender, e) =>
{
   // 處理接收到的資料
};
           

實作螢幕共享

WebRTC.Net庫開發進階,教你實作螢幕共享和多路複用!

除了音視訊傳輸和資料通道,WebRTC.Net 還支援螢幕共享。這意味着應用程式可以捕獲螢幕上的内容并将其共享給其他用戶端。

以下是使用 WinForm 技術棧和 WebRTC.Net 庫實作桌面共享的示例代碼。

using System;
using System.Drawing;
using System.Threading.Tasks;
using System.Windows.Forms;
using Windows.Graphics.Capture;
using Windows.Graphics.DirectX.Direct3D11;
using Microsoft.Toolkit.Win32.UI.Controls.Interop.WinRT;
using Org.WebRtc;

namespace DesktopStreaming
{
    public partial class MainForm : Form
    {
        private PeerConnection _peerConnection;
        private DataChannel _dataChannel;
        private Direct3D11CaptureFramePool _framePool;
        private GraphicsCaptureSession _session;
        private VideoTrack _videoTrack;

        public MainForm()
        {
            InitializeComponent();

            // 初始化 WebRTC
            WebRTC.Initialize(new WebRTCInitializationOptions { EnableAudioBufferLog = false });

            // 建立 PeerConnectionFactory 對象
            var config = new PeerConnectionConfiguration { IceServers = new[] { new IceServer { Urls = new[] { "stun:stun.l.google.com:19302" } } } };
            var factory = new PeerConnectionFactory(config);

            // 建立 PeerConnection 對象
            _peerConnection = factory.CreatePeerConnection();

            // 建立資料通道
            _dataChannel = _peerConnection.CreateDataChannel("mychannel");

            // 訂閱資料通道的消息事件
            _dataChannel.MessageReceived += (sender, args) =>
            {
                // 處理收到的消息
            };

            // 建立 Direct3D11CaptureFramePool 對象
            var device = Direct3D11Helpers.CreateDevice();
            var size = new Size(800, 600);
            _framePool = Direct3D11CaptureFramePool.CreateFreeThreaded(
                device,
                Direct3DPixelFormat.B8G8R8A8UIntNormalized,
                1,
                size);

            // 訂閱 FrameArrived 事件
            _framePool.FrameArrived += (sender, args) =>
            {
                // 擷取最新的桌面幀
                using var frame = sender.TryGetNextFrame();
                if (frame == null) return;

                // 将桌面幀轉換為 RTCVideoFrame 對象
                var videoFrame = new RTCVideoFrame(frame.ContentSize.Width, frame.ContentSize.Height, RTCVideoFrameType.RTCVideoFrameTypeI420);
                videoFrame.ConvertFromArgb32(frame.Surface.Direct3D11Device, frame.Surface);

                // 将 RTCVideoFrame 對象轉換為 VideoTrack 對象并發送
                if (_videoTrack != null)
                    _videoTrack.PushFrame(videoFrame);
            };

            // 建立 GraphicsCaptureItem 對象
            var item = ScreenCapture.GetDefault();

            // 建立 GraphicsCaptureSession 對象
            _session = _framePool.CreateCaptureSession(item);
        }

        private async void btnStart_Click(object sender, EventArgs e)
        {
            // 開始共享桌面
            await _session.StartAsync();

            // 建立視訊軌道
            _videoTrack = await PeerConnectionFactory.GetVideoTrackSourceAsync(_framePool);

            // 添加視訊軌道到 PeerConnection 對象
            await _peerConnection.AddTrack(_videoTrack);

            // 建立 Offer SDP 并設定本地描述符
            var offerSdp = await _peerConnection.CreateOffer();
            await _peerConnection.SetLocalDescription(offerSdp);

            // 發送 Offer SDP 到遠端
            SendSdp(offerSdp);
        }

        private void SendSdp(RTCSessionDescription sdp)
        {
            // 将 SDP 轉換為 JSON 格式并發送到遠端
            var json = Newtonsoft.Json.JsonConvert.SerializeObject(new { type = sdp.Type, sdp = sdp.Sdp });
            _dataChannel.Send(json);
        }

        private async void MainForm_FormClosing(object sender, FormClosingEventArgs e)
        {
            // 關閉 PeerConnection 和 GraphicsCaptureSession 對象
            await _peerConnection.CloseAsync();
            _session.Dispose();
        }
    }
}
           

上述代碼中,我們使用了 ScreenCapture 類來擷取預設的桌面捕獲項目,然後建立了 GraphicsCaptureSession 對象來捕獲桌面幀。我們還使用了 Direct3D11CaptureFramePool 類來建立一個 Direct3D 11 幀池,并訂閱了 FrameArrived 事件以擷取最新的桌面幀。在每次收到桌面幀時,我們将其轉換為 RTCVideoFrame 對象,再将其發送到 WebRTC 連接配接中。通過這種方式,我們就實作了桌面共享的功能。

需要注意的是,由于 WebRTC 是基于 p2p 的實時通信協定,是以本示例代碼中僅示範了如何将桌面共享的資料發送給遠端用戶端,而沒有涉及如何在遠端用戶端上解析和顯示收到的資料。

處理 ICE 連接配接狀态

WebRTC.Net 使用 ICE(Interactive Connectivity Establishment)協定來建立和維護用戶端之間的連接配接。ICE 協定涉及多個狀态和事件,例如 gathering、connected、disconnected 等等。應用程式可以訂閱 PeerConnection 對象上的各種事件來處理這些狀态。

以下代碼如何訂閱 PeerConnection 對象上的連接配接狀态:

// 建立 PeerConnection 對象
var config = new PeerConnectionConfiguration { IceServers = new List<IceServer> { new IceServer { Urls = new[] { "stun:stun.l.google.com:19302" } } } };
var factory = new PeerConnectionFactory(config);
var pc = factory.CreatePeerConnection(config);

// 訂閱 PeerConnection 對象上的連接配接狀态
pc.IceStateChanged += (sender, iceState) =>
{
   if (iceState == IceConnectionState.Connected)
   {
      // 用戶端已成功連接配接
   }
   else if (iceState == IceConnectionState.Disconnected)
   {
      // 用戶端已斷開連接配接
   }
};
           

實作多路複用

WebRTC.Net庫開發進階,教你實作螢幕共享和多路複用!

WebRTC.Net 支援實作多路複用(Multiplexing),這意味着應用程式可以在同一個資料通道上同時傳輸多種類型的資料,例如音頻、視訊、檔案等。

下面是使用 WinForm 技術棧和 WebRTC.Net 庫實作多路複用的示例代碼。

Copy Codeusing System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;
using Microsoft.Toolkit.Win32.UI.Controls.Interop.WinRT;
using Org.WebRtc;

namespace WebRTC_Multiplexing
{
    public partial class Form1 : Form
    {
        private PeerConnection _peerConnection;
        private List<DataChannel> _dataChannels = new List<DataChannel>();

        public Form1()
        {
            InitializeComponent();

            // 初始化 WebRTC
            WebRTC.Initialize(new WebRTCInitializationOptions { EnableAudioBufferLog = false });

            // 建立 PeerConnectionFactory 對象
            var config = new PeerConnectionConfiguration { IceServers = new[] { new IceServer { Urls = new[] { "stun:stun.l.google.com:19302" } } } };
            var factory = new PeerConnectionFactory(config);

            // 建立 PeerConnection 對象
            _peerConnection = factory.CreatePeerConnection();

            // 訂閱 PeerConnection 的連接配接狀态改變事件
            _peerConnection.ConnectionStateChanged += (sender, args) =>
            {
                // 處理連接配接狀态改變事件
                BeginInvoke(new Action(() => txtOutput.AppendText(#34;連接配接狀态:{args.NewState.ToString()}\r\n")));
            };

            // 訂閱 PeerConnection 的資料通道回調事件
            _peerConnection.DataChannelAdded += (sender, args) =>
            {
                // 處理資料通道回調事件
                var dataChannel = args.Channel;
                dataChannel.MessageReceived += DataChannel_MessageReceived;
                _dataChannels.Add(dataChannel);
                BeginInvoke(new Action(() => txtOutput.AppendText(#34;收到資料通道:{dataChannel.Label}\r\n")));
            };
        }

        private async void btnCreateOffer_Click(object sender, EventArgs e)
        {
            // 建立 Offer SDP 并設定本地描述符
            var offerSdp = await _peerConnection.CreateOffer();
            await _peerConnection.SetLocalDescription(offerSdp);

            // 發送 Offer SDP 到對端
            SendSdp(offerSdp);
        }

        private void SendSdp(RTCSessionDescription sdp)
        {
            // 将 SDP 轉換為 JSON 格式并發送到對端
            var json = Newtonsoft.Json.JsonConvert.SerializeObject(new { type = sdp.Type, sdp = sdp.Sdp });
            _dataChannels.ForEach(dc => dc.Send(json));
        }

        private async void DataChannel_MessageReceived(object sender, DataChannelMessageEventArgs e)
        {
            // 收到資料通道消息後将其轉換為 RTCSessionDescription 對象
            if (e.MessageType == DataMessageType.Text)
            {
                var text = e.Data;
                var sdp = Newtonsoft.Json.JsonConvert.DeserializeObject<RTCSessionDescription>(text);

                // 設定遠端描述符并完成連接配接
                await _peerConnection.SetRemoteDescription(sdp);
                if (sdp.Type == RTCSessionDescriptionType.Offer) await _peerConnection.CreateAnswer();
            }
        }
    }
}
           

上述代碼中,我們建立了一個 PeerConnectionFactory 對象和一個 PeerConnection 對象,用于建立 WebRTC 連接配接。我們還建立了一個 _dataChannels 清單來儲存所有的資料通道對象,每當 PeerConnection 對象添加一個新的資料通道時,我們就将其添加到 _dataChannels 清單中。

在 btnCreateOffer_Click 事件處理方法中,我們建立了一個 Offer SDP 并設定本地描述符,然後将其發送到所有的資料通道對象中。當收到對端發送過來的 SDP 消息時,我們将其轉換為 RTCSessionDescription 對象,并調用 SetRemoteDescription 方法設定遠端描述符。如果收到來自對端的 Offer SDP,則執行 CreateAnswer 方法建立 Answer SDP 并将其發送回對端。

通過這種方式,我們就可以使用同一個 PeerConnection 對象來支援多路複用。每當需要發送資料時,隻需要将資料發送到指定的資料通道對象即可。需要注意的是,在使用多路複用時,我們需要為不同的資料通道設定不同的标簽(Label),以便在接收端識别不同的通道。

繼續閱讀