AudioSample.cs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495
  1. using System;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using System.Linq;
  5. using Unity.WebRTC.Samples;
  6. using UnityEngine;
  7. using UnityEngine.UI;
  8. namespace Unity.WebRTC
  9. {
  10. class AudioSample : MonoBehaviour
  11. {
  12. [SerializeField] private AudioSource inputAudioSource;
  13. [SerializeField] private AudioSource outputAudioSource;
  14. [SerializeField] private Toggle toggleEnableMicrophone;
  15. [SerializeField] private Toggle toggleLoopback;
  16. [SerializeField] private Dropdown dropdownAudioClips;
  17. [SerializeField] private Dropdown dropdownMicrophoneDevices;
  18. [SerializeField] private Dropdown dropdownAudioCodecs;
  19. [SerializeField] private Dropdown dropdownSpeakerMode;
  20. [SerializeField] private Dropdown dropdownDSPBufferSize;
  21. [SerializeField] private Dropdown dropdownBandwidth;
  22. [SerializeField] private Button buttonStart;
  23. [SerializeField] private Button buttonCall;
  24. [SerializeField] private Button buttonPause;
  25. [SerializeField] private Button buttonResume;
  26. [SerializeField] private Button buttonHangup;
  27. [SerializeField] private AudioClip[] audioclipList;
  28. [SerializeField] private Text textBandwidth;
  29. private RTCPeerConnection _pc1, _pc2;
  30. private MediaStream _sendStream;
  31. private MediaStream _receiveStream;
  32. private AudioClip m_clipInput;
  33. private AudioStreamTrack m_audioTrack;
  34. private List<RTCRtpCodecCapability> availableCodecs = new List<RTCRtpCodecCapability>();
  35. int m_samplingFrequency = 48000;
  36. int m_lengthSeconds = 1;
  37. private string m_deviceName = null;
  38. private Dictionary<string, ulong?> bandwidthOptions = new Dictionary<string, ulong?>()
  39. {
  40. { "undefined", null },
  41. { "320", 320 },
  42. { "160", 160 },
  43. { "80", 80 },
  44. { "40", 40 },
  45. { "20", 20 },
  46. };
  47. private Dictionary<string, int> dspBufferSizeOptions = new Dictionary<string, int>()
  48. {
  49. { "Best Latency", 256 },
  50. { "Good Latency", 512 },
  51. { "Best Performance", 1024 },
  52. };
  53. void Start()
  54. {
  55. WebRTC.Initialize(WebRTCSettings.LimitTextureSize);
  56. StartCoroutine(WebRTC.Update());
  57. StartCoroutine(LoopStatsCoroutine());
  58. toggleEnableMicrophone.isOn = false;
  59. toggleEnableMicrophone.onValueChanged.AddListener(OnEnableMicrophone);
  60. toggleEnableMicrophone.isOn = false;
  61. toggleLoopback.onValueChanged.AddListener(OnChangeLoopback);
  62. dropdownAudioClips.interactable = true;
  63. dropdownAudioClips.options =
  64. audioclipList.Select(clip => new Dropdown.OptionData(clip.name)).ToList();
  65. dropdownMicrophoneDevices.interactable = false;
  66. dropdownMicrophoneDevices.options =
  67. Microphone.devices.Select(name => new Dropdown.OptionData(name)).ToList();
  68. dropdownMicrophoneDevices.onValueChanged.AddListener(OnDeviceChanged);
  69. var audioConf = AudioSettings.GetConfiguration();
  70. dropdownSpeakerMode.options =
  71. Enum.GetNames(typeof(AudioSpeakerMode)).Select(mode => new Dropdown.OptionData(mode)).ToList();
  72. dropdownSpeakerMode.value = (int)audioConf.speakerMode;
  73. dropdownSpeakerMode.onValueChanged.AddListener(OnSpeakerModeChanged);
  74. dropdownDSPBufferSize.options =
  75. dspBufferSizeOptions.Select(clip => new Dropdown.OptionData(clip.Key)).ToList();
  76. dropdownDSPBufferSize.onValueChanged.AddListener(OnDSPBufferSizeChanged);
  77. // best latency is default
  78. OnDSPBufferSizeChanged(dropdownDSPBufferSize.value);
  79. dropdownAudioCodecs.AddOptions(new List<string>{"Default"});
  80. var codecs = RTCRtpSender.GetCapabilities(TrackKind.Audio).codecs;
  81. var excludeCodecTypes = new[] { "audio/CN", "audio/telephone-event" };
  82. foreach (var codec in codecs)
  83. {
  84. if (excludeCodecTypes.Count(type => codec.mimeType.Contains(type)) > 0)
  85. continue;
  86. availableCodecs.Add(codec);
  87. }
  88. dropdownAudioCodecs.AddOptions(availableCodecs.Select(codec =>
  89. new Dropdown.OptionData(CodecToOptionName(codec))).ToList());
  90. dropdownBandwidth.options = bandwidthOptions
  91. .Select(pair => new Dropdown.OptionData { text = pair.Key })
  92. .ToList();
  93. dropdownBandwidth.onValueChanged.AddListener(OnBandwidthChanged);
  94. dropdownBandwidth.interactable = false;
  95. // Update UI
  96. OnDeviceChanged(dropdownMicrophoneDevices.value);
  97. buttonStart.onClick.AddListener(OnStart);
  98. buttonCall.onClick.AddListener(OnCall);
  99. buttonPause.onClick.AddListener(OnPause);
  100. buttonResume.onClick.AddListener(OnResume);
  101. buttonHangup.onClick.AddListener(OnHangUp);
  102. }
  103. static string CodecToOptionName(RTCRtpCodecCapability cap)
  104. {
  105. return string.Format($"{cap.mimeType} " +
  106. $"{cap.clockRate} " +
  107. $"channel={cap.channels}");
  108. }
  109. void OnDestroy()
  110. {
  111. WebRTC.Dispose();
  112. }
  113. void OnStart()
  114. {
  115. if (toggleEnableMicrophone.isOn)
  116. {
  117. m_deviceName = dropdownMicrophoneDevices.captionText.text;
  118. m_clipInput = Microphone.Start(m_deviceName, true, m_lengthSeconds, m_samplingFrequency);
  119. // set the latency to “0” samples before the audio starts to play.
  120. while (!(Microphone.GetPosition(m_deviceName) > 0)) {}
  121. }
  122. else
  123. {
  124. var clipIndex = dropdownAudioClips.value;
  125. m_clipInput = audioclipList[clipIndex];
  126. }
  127. inputAudioSource.loop = true;
  128. inputAudioSource.clip = m_clipInput;
  129. inputAudioSource.Play();
  130. buttonStart.interactable = false;
  131. buttonCall.interactable = true;
  132. buttonHangup.interactable = true;
  133. dropdownSpeakerMode.interactable = false;
  134. dropdownDSPBufferSize.interactable = false;
  135. dropdownAudioCodecs.interactable = false;
  136. }
  137. void OnEnableMicrophone(bool enable)
  138. {
  139. dropdownMicrophoneDevices.interactable = enable;
  140. dropdownAudioClips.interactable = !enable;
  141. }
  142. void OnChangeLoopback(bool loopback)
  143. {
  144. if (m_audioTrack != null)
  145. {
  146. m_audioTrack.Loopback = loopback;
  147. }
  148. }
  149. void OnCall()
  150. {
  151. buttonCall.interactable = false;
  152. buttonPause.interactable = true;
  153. dropdownBandwidth.interactable = true;
  154. _receiveStream = new MediaStream();
  155. _receiveStream.OnAddTrack += OnAddTrack;
  156. _sendStream = new MediaStream();
  157. var configuration = GetSelectedSdpSemantics();
  158. _pc1 = new RTCPeerConnection(ref configuration)
  159. {
  160. OnIceCandidate = candidate => _pc2.AddIceCandidate(candidate),
  161. OnNegotiationNeeded = () => StartCoroutine(PeerNegotiationNeeded(_pc1))
  162. };
  163. _pc2 = new RTCPeerConnection(ref configuration)
  164. {
  165. OnIceCandidate = candidate => _pc1.AddIceCandidate(candidate),
  166. OnTrack = e => _receiveStream.AddTrack(e.Track),
  167. };
  168. var transceiver2 = _pc2.AddTransceiver(TrackKind.Audio);
  169. transceiver2.Direction = RTCRtpTransceiverDirection.RecvOnly;
  170. m_audioTrack = new AudioStreamTrack(inputAudioSource);
  171. m_audioTrack.Loopback = toggleLoopback.isOn;
  172. _pc1.AddTrack(m_audioTrack, _sendStream);
  173. var transceiver1 = _pc1.GetTransceivers().First();
  174. if (dropdownAudioCodecs.value == 0)
  175. {
  176. var error = transceiver1.SetCodecPreferences(this.availableCodecs.ToArray());
  177. if(error != RTCErrorType.None)
  178. Debug.LogError(error);
  179. }
  180. else
  181. {
  182. var codec = availableCodecs[dropdownAudioCodecs.value - 1];
  183. var error = transceiver1.SetCodecPreferences(new[] { codec });
  184. if (error != RTCErrorType.None)
  185. Debug.LogError(error);
  186. }
  187. }
  188. void OnPause()
  189. {
  190. var transceiver1 = _pc1.GetTransceivers().First();
  191. var track = transceiver1.Sender.Track;
  192. track.Enabled = false;
  193. buttonResume.gameObject.SetActive(true);
  194. buttonPause.gameObject.SetActive(false);
  195. }
  196. void OnResume()
  197. {
  198. var transceiver1 = _pc1.GetTransceivers().First();
  199. var track = transceiver1.Sender.Track;
  200. track.Enabled = true;
  201. buttonResume.gameObject.SetActive(false);
  202. buttonPause.gameObject.SetActive(true);
  203. }
  204. void OnAddTrack(MediaStreamTrackEvent e)
  205. {
  206. var track = e.Track as AudioStreamTrack;
  207. outputAudioSource.SetTrack(track);
  208. outputAudioSource.loop = true;
  209. outputAudioSource.Play();
  210. }
  211. void OnHangUp()
  212. {
  213. Microphone.End(m_deviceName);
  214. m_clipInput = null;
  215. m_audioTrack?.Dispose();
  216. _receiveStream?.Dispose();
  217. _sendStream?.Dispose();
  218. _pc1?.Dispose();
  219. _pc2?.Dispose();
  220. _pc1 = null;
  221. _pc2 = null;
  222. inputAudioSource.Stop();
  223. outputAudioSource.Stop();
  224. buttonStart.interactable = true;
  225. buttonCall.interactable = false;
  226. buttonHangup.interactable = false;
  227. buttonPause.interactable = false;
  228. buttonResume.gameObject.SetActive(false);
  229. buttonPause.gameObject.SetActive(true);
  230. dropdownSpeakerMode.interactable = true;
  231. dropdownDSPBufferSize.interactable = true;
  232. dropdownAudioCodecs.interactable = true;
  233. dropdownBandwidth.interactable = false;
  234. }
  235. void OnDeviceChanged(int value)
  236. {
  237. if (dropdownMicrophoneDevices.options.Count == 0)
  238. return;
  239. m_deviceName = dropdownMicrophoneDevices.options[value].text;
  240. Microphone.GetDeviceCaps(m_deviceName, out int minFreq, out int maxFreq);
  241. }
  242. private void OnBandwidthChanged(int index)
  243. {
  244. if (_pc1 == null || _pc2 == null)
  245. return;
  246. ulong? bandwidth = bandwidthOptions.Values.ElementAt(index);
  247. RTCRtpSender sender = _pc1.GetSenders().First();
  248. RTCRtpSendParameters parameters = sender.GetParameters();
  249. if (bandwidth == null)
  250. {
  251. parameters.encodings[0].maxBitrate = null;
  252. parameters.encodings[0].minBitrate = null;
  253. }
  254. else
  255. {
  256. parameters.encodings[0].maxBitrate = bandwidth * 1000;
  257. parameters.encodings[0].minBitrate = bandwidth * 1000;
  258. }
  259. RTCError error = sender.SetParameters(parameters);
  260. if (error.errorType != RTCErrorType.None)
  261. {
  262. Debug.LogErrorFormat("RTCRtpSender.SetParameters failed {0}", error.errorType);
  263. }
  264. Debug.Log("SetParameters:" + bandwidth);
  265. }
  266. void OnSpeakerModeChanged(int value)
  267. {
  268. var audioConf = AudioSettings.GetConfiguration();
  269. audioConf.speakerMode = (AudioSpeakerMode)value;
  270. Debug.Log(audioConf.speakerMode);
  271. if (!AudioSettings.Reset(audioConf))
  272. {
  273. Debug.LogError("Failed changing Audio Settings");
  274. }
  275. }
  276. void OnDSPBufferSizeChanged(int value)
  277. {
  278. var audioConf = AudioSettings.GetConfiguration();
  279. audioConf.dspBufferSize = dspBufferSizeOptions.Values.ToArray()[value];
  280. if (!AudioSettings.Reset(audioConf))
  281. {
  282. Debug.LogError("Failed changing Audio Settings");
  283. }
  284. }
  285. private static RTCConfiguration GetSelectedSdpSemantics()
  286. {
  287. RTCConfiguration config = default;
  288. config.iceServers = new[] { new RTCIceServer { urls = new[] { "stun:stun.l.google.com:19302" } } };
  289. return config;
  290. }
  291. IEnumerator PeerNegotiationNeeded(RTCPeerConnection pc)
  292. {
  293. var op = pc.CreateOffer();
  294. yield return op;
  295. if (!op.IsError)
  296. {
  297. if (pc.SignalingState != RTCSignalingState.Stable)
  298. {
  299. yield break;
  300. }
  301. yield return StartCoroutine(OnCreateOfferSuccess(pc, op.Desc));
  302. }
  303. else
  304. {
  305. var error = op.Error;
  306. OnSetSessionDescriptionError(ref error);
  307. }
  308. }
  309. private RTCPeerConnection GetOtherPc(RTCPeerConnection pc)
  310. {
  311. return (pc == _pc1) ? _pc2 : _pc1;
  312. }
  313. private IEnumerator OnCreateOfferSuccess(RTCPeerConnection pc, RTCSessionDescription desc)
  314. {
  315. var op = pc.SetLocalDescription(ref desc);
  316. yield return op;
  317. if (!op.IsError)
  318. {
  319. OnSetLocalSuccess(pc);
  320. }
  321. else
  322. {
  323. var error = op.Error;
  324. OnSetSessionDescriptionError(ref error);
  325. }
  326. var otherPc = GetOtherPc(pc);
  327. var op2 = otherPc.SetRemoteDescription(ref desc);
  328. yield return op2;
  329. if (op2.IsError)
  330. {
  331. var error = op2.Error;
  332. OnSetSessionDescriptionError(ref error);
  333. }
  334. var op3 = otherPc.CreateAnswer();
  335. yield return op3;
  336. if (!op3.IsError)
  337. {
  338. yield return OnCreateAnswerSuccess(otherPc, op3.Desc);
  339. }
  340. }
  341. IEnumerator OnCreateAnswerSuccess(RTCPeerConnection pc, RTCSessionDescription desc)
  342. {
  343. var op = pc.SetLocalDescription(ref desc);
  344. yield return op;
  345. if (!op.IsError)
  346. {
  347. OnSetLocalSuccess(pc);
  348. }
  349. else
  350. {
  351. var error = op.Error;
  352. OnSetSessionDescriptionError(ref error);
  353. }
  354. var otherPc = GetOtherPc(pc);
  355. var op2 = otherPc.SetRemoteDescription(ref desc);
  356. yield return op2;
  357. if (op2.IsError)
  358. {
  359. var error = op2.Error;
  360. OnSetSessionDescriptionError(ref error);
  361. }
  362. }
  363. private void OnSetLocalSuccess(RTCPeerConnection pc)
  364. {
  365. Debug.Log("SetLocalDescription complete");
  366. }
  367. static void OnSetSessionDescriptionError(ref RTCError error)
  368. {
  369. Debug.LogError($"Error Detail Type: {error.message}");
  370. }
  371. private IEnumerator LoopStatsCoroutine()
  372. {
  373. while (true)
  374. {
  375. yield return StartCoroutine(UpdateStatsCoroutine());
  376. yield return new WaitForSeconds(1f);
  377. }
  378. }
  379. private IEnumerator UpdateStatsCoroutine()
  380. {
  381. RTCRtpSender sender = _pc1?.GetSenders().First();
  382. if (sender == null)
  383. yield break;
  384. RTCStatsReportAsyncOperation op = sender.GetStats();
  385. yield return op;
  386. if (op.IsError)
  387. {
  388. Debug.LogErrorFormat("RTCRtpSender.GetStats() is failed {0}", op.Error.errorType);
  389. }
  390. else
  391. {
  392. UpdateStatsPacketSize(op.Value);
  393. }
  394. }
  395. private RTCStatsReport lastResult = null;
  396. private void UpdateStatsPacketSize(RTCStatsReport res)
  397. {
  398. foreach (RTCStats stats in res.Stats.Values)
  399. {
  400. if (!(stats is RTCOutboundRTPStreamStats report))
  401. {
  402. continue;
  403. }
  404. long now = report.Timestamp;
  405. ulong bytes = report.bytesSent;
  406. if (lastResult != null)
  407. {
  408. if (!lastResult.TryGetValue(report.Id, out RTCStats last))
  409. continue;
  410. var lastStats = last as RTCOutboundRTPStreamStats;
  411. var duration = (double)(now - lastStats.Timestamp) / 1000000;
  412. ulong bitrate = (ulong)(8 * (bytes - lastStats.bytesSent) / duration);
  413. textBandwidth.text = (bitrate / 1000.0f).ToString("f2");
  414. //if (autoScroll.isOn)
  415. //{
  416. // statsField.MoveTextEnd(false);
  417. //}
  418. }
  419. }
  420. lastResult = res;
  421. }
  422. }
  423. }