Search Unity

  1. Unity Asset Manager is now available in public beta. Try it out now and join the conversation here in the forums.
    Dismiss Notice

WebRTC Internet Multiplayer ??

Discussion in 'Unity Render Streaming' started by CloudyVR, Jun 11, 2022.

  1. CloudyVR

    CloudyVR

    Joined:
    Mar 26, 2017
    Posts:
    715
    I tried using some of the Unity examples for multiplayer with webrtc and was able to play two games on the same PC.

    But how are the examples supposed to work over the internet? Do I specify a remote IP somewhere..??

    My main question is how to connect two PCs over the internet? All of the basic examples work great! But they only work over local network so they are not really useful.

    Also Webserver.exe only runs on Windows but I need a Linux solution so I can run it on a AWS server!!!! Is this possible? Or do i not need webserver.exe at all???

    Are there any Webservers that run on Linux and allow connecting WebRTC examples over the internet?
     
    Last edited: Jun 12, 2022
  2. kannan-xiao4

    kannan-xiao4

    Unity Technologies

    Joined:
    Nov 5, 2020
    Posts:
    76
  3. Deleted User

    Deleted User

    Guest

    I have established a webserver. Could u help provide more details, such as how to modify the c# source code of the Unity WebRTC samples. The audio sample is as follows:

    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Linq;
    using Unity.WebRTC.Samples;
    using UnityEngine;
    using UnityEngine.UI;
    namespace Unity.WebRTC
    {
    class AudioSample : MonoBehaviour
    {
    [SerializeField] private AudioSource inputAudioSource;
    [SerializeField] private AudioSource outputAudioSource;
    [SerializeField] private Toggle toggleEnableMicrophone;
    [SerializeField] private Toggle toggleLoopback;
    [SerializeField] private Dropdown dropdownAudioClips;
    [SerializeField] private Dropdown dropdownMicrophoneDevices;
    [SerializeField] private Dropdown dropdownAudioCodecs;
    [SerializeField] private Dropdown dropdownSpeakerMode;
    [SerializeField] private Dropdown dropdownDSPBufferSize;
    [SerializeField] private Dropdown dropdownBandwidth;
    [SerializeField] private Button buttonStart;
    [SerializeField] private Button buttonCall;
    [SerializeField] private Button buttonPause;
    [SerializeField] private Button buttonResume;
    [SerializeField] private Button buttonHangup;
    [SerializeField] private AudioClip[] audioclipList;
    [SerializeField] private Text textBandwidth;
    private RTCPeerConnection _pc1, _pc2;
    private MediaStream _sendStream;
    private MediaStream _receiveStream;
    private AudioClip m_clipInput;
    private AudioStreamTrack m_audioTrack;
    private List<RTCRtpCodecCapability> availableCodecs = new List<RTCRtpCodecCapability>();
    int m_samplingFrequency = 48000;
    int m_lengthSeconds = 1;
    private string m_deviceName = null;
    private Dictionary<string, ulong?> bandwidthOptions = new Dictionary<string, ulong?>()
    {
    { "undefined", null },
    { "320", 320 },
    { "160", 160 },
    { "80", 80 },
    { "40", 40 },
    { "20", 20 },
    };
    private Dictionary<string, int> dspBufferSizeOptions = new Dictionary<string, int>()
    {
    { "Best Latency", 256 },
    { "Good Latency", 512 },
    { "Best Performance", 1024 },
    };
    void Start()
    {
    StartCoroutine(WebRTC.Update());
    StartCoroutine(LoopStatsCoroutine());
    toggleEnableMicrophone.isOn = false;
    toggleEnableMicrophone.onValueChanged.AddListener(OnEnableMicrophone);
    toggleEnableMicrophone.isOn = false;
    toggleLoopback.onValueChanged.AddListener(OnChangeLoopback);
    dropdownAudioClips.interactable = true;
    dropdownAudioClips.options =
    audioclipList.Select(clip => new Dropdown.OptionData(clip.name)).ToList();
    dropdownMicrophoneDevices.interactable = false;
    dropdownMicrophoneDevices.options =
    Microphone.devices.Select(name => new Dropdown.OptionData(name)).ToList();
    dropdownMicrophoneDevices.onValueChanged.AddListener(OnDeviceChanged);
    var audioConf = AudioSettings.GetConfiguration();
    dropdownSpeakerMode.options =
    Enum.GetNames(typeof(AudioSpeakerMode)).Select(mode => new Dropdown.OptionData(mode)).ToList();
    dropdownSpeakerMode.value = (int)audioConf.speakerMode;
    dropdownSpeakerMode.onValueChanged.AddListener(OnSpeakerModeChanged);
    dropdownDSPBufferSize.options =
    dspBufferSizeOptions.Select(clip => new Dropdown.OptionData(clip.Key)).ToList();
    dropdownDSPBufferSize.onValueChanged.AddListener(OnDSPBufferSizeChanged);
    // best latency is default
    OnDSPBufferSizeChanged(dropdownDSPBufferSize.value);
    dropdownAudioCodecs.AddOptions(new List<string>{"Default"});
    var codecs = RTCRtpSender.GetCapabilities(TrackKind.Audio).codecs;
    var excludeCodecTypes = new[] { "audio/CN", "audio/telephone-event" };
    foreach (var codec in codecs)
    {
    if (excludeCodecTypes.Count(type => codec.mimeType.Contains(type)) > 0)
    continue;
    availableCodecs.Add(codec);
    }
    dropdownAudioCodecs.AddOptions(availableCodecs.Select(codec =>
    new Dropdown.OptionData(CodecToOptionName(codec))).ToList());
    dropdownBandwidth.options = bandwidthOptions
    .Select(pair => new Dropdown.OptionData { text = pair.Key })
    .ToList();
    dropdownBandwidth.onValueChanged.AddListener(OnBandwidthChanged);
    dropdownBandwidth.interactable = false;
    // Update UI
    OnDeviceChanged(dropdownMicrophoneDevices.value);
    buttonStart.onClick.AddListener(OnStart);
    buttonCall.onClick.AddListener(OnCall);
    buttonPause.onClick.AddListener(OnPause);
    buttonResume.onClick.AddListener(OnResume);
    buttonHangup.onClick.AddListener(OnHangUp);
    }
    static string CodecToOptionName(RTCRtpCodecCapability cap)
    {
    return string.Format($"{cap.mimeType} " +
    $"{cap.clockRate} " +
    $"channel={cap.channels}");
    }
    void OnStart()
    {
    if (toggleEnableMicrophone.isOn)
    {
    m_deviceName = dropdownMicrophoneDevices.captionText.text;
    m_clipInput = Microphone.Start(m_deviceName, true, m_lengthSeconds, m_samplingFrequency);
    // set the latency to “0” samples before the audio starts to play.
    while (!(Microphone.GetPosition(m_deviceName) > 0)) {}
    }
    else
    {
    var clipIndex = dropdownAudioClips.value;
    m_clipInput = audioclipList[clipIndex];
    }
    inputAudioSource.loop = true;
    inputAudioSource.clip = m_clipInput;
    inputAudioSource.Play();
    buttonStart.interactable = false;
    buttonCall.interactable = true;
    buttonHangup.interactable = true;
    dropdownSpeakerMode.interactable = false;
    dropdownDSPBufferSize.interactable = false;
    dropdownAudioCodecs.interactable = false;
    }
    void OnEnableMicrophone(bool enable)
    {
    dropdownMicrophoneDevices.interactable = enable;
    dropdownAudioClips.interactable = !enable;
    }
    void OnChangeLoopback(bool loopback)
    {
    if (m_audioTrack != null)
    {
    m_audioTrack.Loopback = loopback;
    }
    }
    void OnCall()
    {
    buttonCall.interactable = false;
    buttonPause.interactable = true;
    dropdownBandwidth.interactable = true;
    _receiveStream = new MediaStream();
    _receiveStream.OnAddTrack += OnAddTrack;
    _sendStream = new MediaStream();
    var configuration = GetSelectedSdpSemantics();
    _pc1 = new RTCPeerConnection(ref configuration)
    {
    OnIceCandidate = candidate => _pc2.AddIceCandidate(candidate),
    OnNegotiationNeeded = () => StartCoroutine(PeerNegotiationNeeded(_pc1))
    };
    _pc2 = new RTCPeerConnection(ref configuration)
    {
    OnIceCandidate = candidate => _pc1.AddIceCandidate(candidate),
    OnTrack = e => _receiveStream.AddTrack(e.Track),
    };
    var transceiver2 = _pc2.AddTransceiver(TrackKind.Audio);
    transceiver2.Direction = RTCRtpTransceiverDirection.RecvOnly; // _pc2只接收voice
    m_audioTrack = new AudioStreamTrack(inputAudioSource);
    m_audioTrack.Loopback = toggleLoopback.isOn;
    _pc1.AddTrack(m_audioTrack, _sendStream);
    var transceiver1 = _pc1.GetTransceivers().First();
    if (dropdownAudioCodecs.value == 0)
    {
    var error = transceiver1.SetCodecPreferences(this.availableCodecs.ToArray());
    if(error != RTCErrorType.None)
    Debug.LogError(error);
    }
    else
    {
    var codec = availableCodecs[dropdownAudioCodecs.value - 1];
    var error = transceiver1.SetCodecPreferences(new[] { codec });
    if (error != RTCErrorType.None)
    Debug.LogError(error);
    }
    }
    void OnPause()
    {
    var transceiver1 = _pc1.GetTransceivers().First();
    var track = transceiver1.Sender.Track;
    track.Enabled = false;
    buttonResume.gameObject.SetActive(true);
    buttonPause.gameObject.SetActive(false);
    }
    void OnResume()
    {
    var transceiver1 = _pc1.GetTransceivers().First();
    var track = transceiver1.Sender.Track;
    track.Enabled = true;
    buttonResume.gameObject.SetActive(false);
    buttonPause.gameObject.SetActive(true);
    }
    void OnAddTrack(MediaStreamTrackEvent e)
    {
    var track = e.Track as AudioStreamTrack;
    outputAudioSource.SetTrack(track);
    outputAudioSource.loop = true;
    outputAudioSource.Play();
    }
    void OnHangUp()
    {
    Microphone.End(m_deviceName);
    m_clipInput = null;
    m_audioTrack?.Dispose();
    _receiveStream?.Dispose();
    _sendStream?.Dispose();
    _pc1?.Dispose();
    _pc2?.Dispose();
    _pc1 = null;
    _pc2 = null;
    inputAudioSource.Stop();
    outputAudioSource.Stop();
    buttonStart.interactable = true;
    buttonCall.interactable = false;
    buttonHangup.interactable = false;
    buttonPause.interactable = false;
    buttonResume.gameObject.SetActive(false);
    buttonPause.gameObject.SetActive(true);
    dropdownSpeakerMode.interactable = true;
    dropdownDSPBufferSize.interactable = true;
    dropdownAudioCodecs.interactable = true;
    dropdownBandwidth.interactable = false;
    }
    void OnDeviceChanged(int value)
    {
    if (dropdownMicrophoneDevices.options.Count == 0)
    return;
    m_deviceName = dropdownMicrophoneDevices.options[value].text;
    Microphone.GetDeviceCaps(m_deviceName, out int minFreq, out int maxFreq);
    }
    private void OnBandwidthChanged(int index)
    {
    if (_pc1 == null || _pc2 == null)
    return;
    ulong? bandwidth = bandwidthOptions.Values.ElementAt(index);
    RTCRtpSender sender = _pc1.GetSenders().First();
    RTCRtpSendParameters parameters = sender.GetParameters();
    if (bandwidth == null)
    {
    parameters.encodings[0].maxBitrate = null;
    parameters.encodings[0].minBitrate = null;
    }
    else
    {
    parameters.encodings[0].maxBitrate = bandwidth * 1000;
    parameters.encodings[0].minBitrate = bandwidth * 1000;
    }
    RTCError error = sender.SetParameters(parameters);
    if (error.errorType != RTCErrorType.None)
    {
    Debug.LogErrorFormat("RTCRtpSender.SetParameters failed {0}", error.errorType);
    }
    Debug.Log("SetParameters:" + bandwidth);
    }
    void OnSpeakerModeChanged(int value)
    {
    var audioConf = AudioSettings.GetConfiguration();
    audioConf.speakerMode = (AudioSpeakerMode)value;
    Debug.Log(audioConf.speakerMode);
    if (!AudioSettings.Reset(audioConf))
    {
    Debug.LogError("Failed changing Audio Settings");
    }
    }
    void OnDSPBufferSizeChanged(int value)
    {
    var audioConf = AudioSettings.GetConfiguration();
    audioConf.dspBufferSize = dspBufferSizeOptions.Values.ToArray()[value];
    if (!AudioSettings.Reset(audioConf))
    {
    Debug.LogError("Failed changing Audio Settings");
    }
    }
    private static RTCConfiguration GetSelectedSdpSemantics()
    {
    RTCConfiguration config = default;
    config.iceServers = new[] { new RTCIceServer { urls = new[] { "stun:stun.l.google.com:19302" } } };
    return config;
    }
    IEnumerator PeerNegotiationNeeded(RTCPeerConnection pc)
    {
    var op = pc.CreateOffer();
    yield return op;
    if (!op.IsError)
    {
    if (pc.SignalingState != RTCSignalingState.Stable)
    {
    yield break;
    }
    yield return StartCoroutine(OnCreateOfferSuccess(pc, op.Desc));
    }
    else
    {
    var error = op.Error;
    OnSetSessionDescriptionError(ref error);
    }
    }
    private RTCPeerConnection GetOtherPc(RTCPeerConnection pc)
    {
    return (pc == _pc1) ? _pc2 : _pc1;
    }
    private IEnumerator OnCreateOfferSuccess(RTCPeerConnection pc, RTCSessionDescription desc)
    {
    var op = pc.SetLocalDescription(ref desc);
    yield return op;
    if (!op.IsError)
    {
    OnSetLocalSuccess(pc);
    }
    else
    {
    var error = op.Error;
    OnSetSessionDescriptionError(ref error);
    }
    var otherPc = GetOtherPc(pc);
    var op2 = otherPc.SetRemoteDescription(ref desc);
    yield return op2;
    if (op2.IsError)
    {
    var error = op2.Error;
    OnSetSessionDescriptionError(ref error);
    }
    var op3 = otherPc.CreateAnswer();
    yield return op3;
    if (!op3.IsError)
    {
    yield return OnCreateAnswerSuccess(otherPc, op3.Desc);
    }
    }
    IEnumerator OnCreateAnswerSuccess(RTCPeerConnection pc, RTCSessionDescription desc)
    {
    var op = pc.SetLocalDescription(ref desc);
    yield return op;
    if (!op.IsError)
    {
    OnSetLocalSuccess(pc);
    }
    else
    {
    var error = op.Error;
    OnSetSessionDescriptionError(ref error);
    }
    var otherPc = GetOtherPc(pc);
    var op2 = otherPc.SetRemoteDescription(ref desc);
    yield return op2;
    if (op2.IsError)
    {
    var error = op2.Error;
    OnSetSessionDescriptionError(ref error);
    }
    }
    private void OnSetLocalSuccess(RTCPeerConnection pc)
    {
    Debug.Log("SetLocalDescription complete");
    }
    static void OnSetSessionDescriptionError(ref RTCError error)
    {
    Debug.LogError($"Error Detail Type: {error.message}");
    }
    private IEnumerator LoopStatsCoroutine()
    {
    while (true)
    {
    yield return StartCoroutine(UpdateStatsCoroutine());
    yield return new WaitForSeconds(1f);
    }
    }
    private IEnumerator UpdateStatsCoroutine()
    {
    RTCRtpSender sender = _pc1?.GetSenders().First();
    if (sender == null)
    yield break;
    RTCStatsReportAsyncOperation op = sender.GetStats();
    yield return op;
    if (op.IsError)
    {
    Debug.LogErrorFormat("RTCRtpSender.GetStats() is failed {0}", op.Error.errorType);
    }
    else
    {
    UpdateStatsPacketSize(op.Value);
    }
    }
    private RTCStatsReport lastResult = null;
    private void UpdateStatsPacketSize(RTCStatsReport res)
    {
    foreach (RTCStats stats in res.Stats.Values)
    {
    if (!(stats is RTCOutboundRTPStreamStats report))
    {
    continue;
    }
    long now = report.Timestamp;
    ulong bytes = report.bytesSent;
    if (lastResult != null)
    {
    if (!lastResult.TryGetValue(report.Id, out RTCStats last))
    continue;
    var lastStats = last as RTCOutboundRTPStreamStats;
    var duration = (double)(now - lastStats.Timestamp) / 1000000;
    ulong bitrate = (ulong)(8 * (bytes - lastStats.bytesSent) / duration);
    textBandwidth.text = (bitrate / 1000.0f).ToString("f2");
    //if (autoScroll.isOn)
    //{
    // statsField.MoveTextEnd(false);
    //}
    }
    }
    lastResult = res;
    }
    }
    }
     
  4. kannan-xiao4

    kannan-xiao4

    Unity Technologies

    Joined:
    Nov 5, 2020
    Posts:
    76
    Can you tell me what has changed?
     
  5. winlin_srs

    winlin_srs

    Joined:
    Jan 10, 2023
    Posts:
    4