123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495 |
- 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()
- {
- WebRTC.Initialize(WebRTCSettings.LimitTextureSize);
- 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 OnDestroy()
- {
- WebRTC.Dispose();
- }
- 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;
- 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;
- }
- }
- }
|