ChangeCodecsSample.cs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437
  1. using System;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using System.Linq;
  5. using UnityEngine;
  6. using Unity.WebRTC;
  7. using Unity.WebRTC.Samples;
  8. using UnityEngine.UI;
  9. using Button = UnityEngine.UI.Button;
  10. class ChangeCodecsSample : MonoBehaviour
  11. {
  12. #pragma warning disable 0649
  13. [SerializeField] private Dropdown codecSelector;
  14. [SerializeField] private Button startButton;
  15. [SerializeField] private Button callButton;
  16. [SerializeField] private Button hangUpButton;
  17. [SerializeField] private Text actualCodecText;
  18. [SerializeField] private Camera cam;
  19. [SerializeField] private RawImage sourceImage;
  20. [SerializeField] private RawImage receiveImage;
  21. [SerializeField] private Transform rotateObject;
  22. #pragma warning restore 0649
  23. private RTCPeerConnection _pc1, _pc2;
  24. private List<RTCRtpSender> pc1Senders;
  25. private MediaStream videoStream, receiveStream;
  26. private DelegateOnIceConnectionChange pc1OnIceConnectionChange;
  27. private DelegateOnIceConnectionChange pc2OnIceConnectionChange;
  28. private DelegateOnIceCandidate pc1OnIceCandidate;
  29. private DelegateOnIceCandidate pc2OnIceCandidate;
  30. private DelegateOnTrack pc2Ontrack;
  31. private DelegateOnNegotiationNeeded pc1OnNegotiationNeeded;
  32. private bool videoUpdateStarted;
  33. private readonly string[] excludeCodecMimeType = { "video/red", "video/ulpfec", "video/rtx" };
  34. private RTCRtpCodecCapability[] availableCodecs;
  35. private void Awake()
  36. {
  37. WebRTC.Initialize(WebRTCSettings.LimitTextureSize);
  38. startButton.onClick.AddListener(OnStart);
  39. callButton.onClick.AddListener(Call);
  40. hangUpButton.onClick.AddListener(HangUp);
  41. receiveStream = new MediaStream();
  42. }
  43. private void OnDestroy()
  44. {
  45. WebRTC.Dispose();
  46. }
  47. private void Start()
  48. {
  49. pc1Senders = new List<RTCRtpSender>();
  50. callButton.interactable = true;
  51. hangUpButton.interactable = false;
  52. codecSelector.interactable = false;
  53. pc1OnIceConnectionChange = state => { OnIceConnectionChange(_pc1, state); };
  54. pc2OnIceConnectionChange = state => { OnIceConnectionChange(_pc2, state); };
  55. pc1OnIceCandidate = candidate => { OnIceCandidate(_pc1, candidate); };
  56. pc2OnIceCandidate = candidate => { OnIceCandidate(_pc2, candidate); };
  57. pc2Ontrack = e =>
  58. {
  59. receiveStream.AddTrack(e.Track);
  60. };
  61. pc1OnNegotiationNeeded = () => { StartCoroutine(PeerNegotiationNeeded(_pc1)); };
  62. receiveStream.OnAddTrack = e =>
  63. {
  64. if (e.Track is VideoStreamTrack track)
  65. {
  66. track.OnVideoReceived += tex =>
  67. {
  68. receiveImage.texture = tex;
  69. receiveImage.color = Color.white;
  70. };
  71. }
  72. };
  73. }
  74. private void OnStart()
  75. {
  76. var capabilities = RTCRtpSender.GetCapabilities(TrackKind.Video);
  77. availableCodecs =
  78. capabilities.codecs.Where(codec => !excludeCodecMimeType.Contains(codec.mimeType)).ToArray();
  79. var list = availableCodecs
  80. .Select(codec => new Dropdown.OptionData { text = codec.mimeType + " " + codec.sdpFmtpLine })
  81. .ToList();
  82. codecSelector.options.AddRange(list);
  83. codecSelector.interactable = true;
  84. startButton.interactable = false;
  85. }
  86. private void Update()
  87. {
  88. if (rotateObject != null)
  89. {
  90. float t = Time.deltaTime;
  91. rotateObject.Rotate(100 * t, 200 * t, 300 * t);
  92. }
  93. }
  94. private static RTCConfiguration GetSelectedSdpSemantics()
  95. {
  96. RTCConfiguration config = default;
  97. config.iceServers = new[] { new RTCIceServer { urls = new[] { "stun:stun.l.google.com:19302" } } };
  98. return config;
  99. }
  100. private void OnIceConnectionChange(RTCPeerConnection pc, RTCIceConnectionState state)
  101. {
  102. switch (state)
  103. {
  104. case RTCIceConnectionState.New:
  105. Debug.Log($"{GetName(pc)} IceConnectionState: New");
  106. break;
  107. case RTCIceConnectionState.Checking:
  108. Debug.Log($"{GetName(pc)} IceConnectionState: Checking");
  109. break;
  110. case RTCIceConnectionState.Closed:
  111. Debug.Log($"{GetName(pc)} IceConnectionState: Closed");
  112. break;
  113. case RTCIceConnectionState.Completed:
  114. Debug.Log($"{GetName(pc)} IceConnectionState: Completed");
  115. break;
  116. case RTCIceConnectionState.Connected:
  117. Debug.Log($"{GetName(pc)} IceConnectionState: Connected");
  118. break;
  119. case RTCIceConnectionState.Disconnected:
  120. Debug.Log($"{GetName(pc)} IceConnectionState: Disconnected");
  121. break;
  122. case RTCIceConnectionState.Failed:
  123. Debug.Log($"{GetName(pc)} IceConnectionState: Failed");
  124. break;
  125. case RTCIceConnectionState.Max:
  126. Debug.Log($"{GetName(pc)} IceConnectionState: Max");
  127. break;
  128. default:
  129. throw new ArgumentOutOfRangeException(nameof(state), state, null);
  130. }
  131. }
  132. IEnumerator PeerNegotiationNeeded(RTCPeerConnection pc)
  133. {
  134. var op = pc.CreateOffer();
  135. yield return op;
  136. if (!op.IsError)
  137. {
  138. if (pc.SignalingState != RTCSignalingState.Stable)
  139. {
  140. Debug.LogError($"{GetName(pc)} signaling state is not stable.");
  141. yield break;
  142. }
  143. yield return StartCoroutine(OnCreateOfferSuccess(pc, op.Desc));
  144. }
  145. else
  146. {
  147. OnCreateSessionDescriptionError(op.Error);
  148. }
  149. }
  150. private void AddTracks()
  151. {
  152. foreach (var track in videoStream.GetTracks())
  153. {
  154. pc1Senders.Add(_pc1.AddTrack(track, videoStream));
  155. }
  156. if (!videoUpdateStarted)
  157. {
  158. StartCoroutine(WebRTC.Update());
  159. videoUpdateStarted = true;
  160. }
  161. RTCRtpCodecCapability[] codecs = null;
  162. if (codecSelector.value == 0)
  163. {
  164. codecs = RTCRtpSender.GetCapabilities(TrackKind.Video).codecs;
  165. }
  166. else
  167. {
  168. RTCRtpCodecCapability preferredCodec = availableCodecs[codecSelector.value-1];
  169. codecs = new[] { preferredCodec };
  170. }
  171. RTCRtpTransceiver transceiver = _pc1.GetTransceivers().First();
  172. RTCErrorType error = transceiver.SetCodecPreferences(codecs);
  173. if (error != RTCErrorType.None)
  174. {
  175. Debug.LogErrorFormat("RTCRtpTransceiver.SetCodecPreferences failed. {0}", error);
  176. }
  177. }
  178. private void RemoveTracks()
  179. {
  180. foreach (var sender in pc1Senders)
  181. {
  182. _pc1.RemoveTrack(sender);
  183. }
  184. pc1Senders.Clear();
  185. MediaStreamTrack[] tracks = receiveStream.GetTracks().ToArray();
  186. foreach (var track in tracks)
  187. {
  188. receiveStream.RemoveTrack(track);
  189. track.Dispose();
  190. }
  191. }
  192. private void Call()
  193. {
  194. callButton.interactable = false;
  195. hangUpButton.interactable = true;
  196. codecSelector.interactable = false;
  197. var configuration = GetSelectedSdpSemantics();
  198. _pc1 = new RTCPeerConnection(ref configuration);
  199. _pc1.OnIceCandidate = pc1OnIceCandidate;
  200. _pc1.OnIceConnectionChange = pc1OnIceConnectionChange;
  201. _pc1.OnNegotiationNeeded = pc1OnNegotiationNeeded;
  202. _pc2 = new RTCPeerConnection(ref configuration);
  203. _pc2.OnIceCandidate = pc2OnIceCandidate;
  204. _pc2.OnIceConnectionChange = pc2OnIceConnectionChange;
  205. _pc2.OnTrack = pc2Ontrack;
  206. if (videoStream == null)
  207. {
  208. videoStream = cam.CaptureStream(WebRTCSettings.StreamSize.x, WebRTCSettings.StreamSize.y);
  209. }
  210. sourceImage.texture = cam.targetTexture;
  211. sourceImage.color = Color.white;
  212. AddTracks();
  213. }
  214. private void HangUp()
  215. {
  216. RemoveTracks();
  217. _pc1.Close();
  218. _pc2.Close();
  219. _pc1.Dispose();
  220. _pc2.Dispose();
  221. _pc1 = null;
  222. _pc2 = null;
  223. callButton.interactable = true;
  224. hangUpButton.interactable = false;
  225. codecSelector.interactable = true;
  226. codecSelector.value = 0;
  227. actualCodecText.text = string.Empty;
  228. sourceImage.color = Color.black;
  229. receiveImage.color = Color.black;
  230. }
  231. private void OnIceCandidate(RTCPeerConnection pc, RTCIceCandidate candidate)
  232. {
  233. GetOtherPc(pc).AddIceCandidate(candidate);
  234. Debug.Log($"{GetName(pc)} ICE candidate:\n {candidate.Candidate}");
  235. }
  236. private string GetName(RTCPeerConnection pc)
  237. {
  238. return (pc == _pc1) ? "pc1" : "pc2";
  239. }
  240. private RTCPeerConnection GetOtherPc(RTCPeerConnection pc)
  241. {
  242. return (pc == _pc1) ? _pc2 : _pc1;
  243. }
  244. private IEnumerator OnCreateOfferSuccess(RTCPeerConnection pc, RTCSessionDescription desc)
  245. {
  246. Debug.Log($"Offer from {GetName(pc)}\n{desc.sdp}");
  247. Debug.Log($"{GetName(pc)} setLocalDescription start");
  248. var op = pc.SetLocalDescription(ref desc);
  249. yield return op;
  250. if (!op.IsError)
  251. {
  252. OnSetLocalSuccess(pc);
  253. }
  254. else
  255. {
  256. var error = op.Error;
  257. OnSetSessionDescriptionError(ref error);
  258. }
  259. var otherPc = GetOtherPc(pc);
  260. Debug.Log($"{GetName(otherPc)} setRemoteDescription start");
  261. var op2 = otherPc.SetRemoteDescription(ref desc);
  262. yield return op2;
  263. if (!op2.IsError)
  264. {
  265. OnSetRemoteSuccess(otherPc);
  266. }
  267. else
  268. {
  269. var error = op2.Error;
  270. OnSetSessionDescriptionError(ref error);
  271. }
  272. Debug.Log($"{GetName(otherPc)} createAnswer start");
  273. // Since the 'remote' side has no media stream we need
  274. // to pass in the right constraints in order for it to
  275. // accept the incoming offer of audio and video.
  276. var op3 = otherPc.CreateAnswer();
  277. yield return op3;
  278. if (!op3.IsError)
  279. {
  280. yield return OnCreateAnswerSuccess(otherPc, op3.Desc);
  281. }
  282. else
  283. {
  284. OnCreateSessionDescriptionError(op3.Error);
  285. }
  286. }
  287. private void OnSetLocalSuccess(RTCPeerConnection pc)
  288. {
  289. Debug.Log($"{GetName(pc)} SetLocalDescription complete");
  290. }
  291. static void OnSetSessionDescriptionError(ref RTCError error)
  292. {
  293. Debug.LogError($"Error Detail Type: {error.message}");
  294. }
  295. private void OnSetRemoteSuccess(RTCPeerConnection pc)
  296. {
  297. Debug.Log($"{GetName(pc)} SetRemoteDescription complete");
  298. }
  299. IEnumerator OnCreateAnswerSuccess(RTCPeerConnection pc, RTCSessionDescription desc)
  300. {
  301. Debug.Log($"Answer from {GetName(pc)}:\n{desc.sdp}");
  302. Debug.Log($"{GetName(pc)} setLocalDescription start");
  303. var op = pc.SetLocalDescription(ref desc);
  304. yield return op;
  305. if (!op.IsError)
  306. {
  307. OnSetLocalSuccess(pc);
  308. }
  309. else
  310. {
  311. var error = op.Error;
  312. OnSetSessionDescriptionError(ref error);
  313. }
  314. var otherPc = GetOtherPc(pc);
  315. Debug.Log($"{GetName(otherPc)} setRemoteDescription start");
  316. var op2 = otherPc.SetRemoteDescription(ref desc);
  317. yield return op2;
  318. if (!op2.IsError)
  319. {
  320. OnSetRemoteSuccess(otherPc);
  321. StartCoroutine(CheckActualCodec());
  322. }
  323. else
  324. {
  325. var error = op2.Error;
  326. OnSetSessionDescriptionError(ref error);
  327. }
  328. }
  329. // Display the video codec that is actually used.
  330. IEnumerator CheckActualCodec()
  331. {
  332. yield return new WaitForSeconds(1f);
  333. if (_pc1 == null)
  334. yield break;
  335. var op = _pc1.GetStats();
  336. yield return op;
  337. if (op.IsError)
  338. {
  339. Debug.LogErrorFormat("RTCPeerConnection.GetStats failed: {0}", op.Error);
  340. yield break;
  341. }
  342. RTCStatsReport report = op.Value;
  343. List<RTCOutboundRTPStreamStats> outBoundStatsList = report.Stats.Values.Where(
  344. stats => stats.Type == RTCStatsType.OutboundRtp).Cast<RTCOutboundRTPStreamStats>().ToList();
  345. if (!outBoundStatsList.Any())
  346. {
  347. Debug.LogWarning($"{nameof(RTCStatsReport)} don't contain any {nameof(RTCOutboundRTPStreamStats)}");
  348. yield break;
  349. }
  350. RTCOutboundRTPStreamStats outBoundStats = outBoundStatsList.FirstOrDefault(stats => stats.kind == "video");
  351. if (outBoundStats == null)
  352. {
  353. Debug.LogWarning($"{nameof(RTCOutboundRTPStreamStats)} with kind video is not included in {nameof(outBoundStatsList)}.");
  354. yield break;
  355. }
  356. string codecId = outBoundStats.codecId;
  357. List<RTCCodecStats> codecStatsList = report.Stats.Values.Where(
  358. stats => stats.Type == RTCStatsType.Codec).Cast<RTCCodecStats>().ToList();
  359. if (!codecStatsList.Any())
  360. {
  361. Debug.LogWarning($"{nameof(RTCOutboundRTPStreamStats)} don't contain any {nameof(RTCCodecStats)}");
  362. yield break;
  363. }
  364. RTCCodecStats codecStats =
  365. codecStatsList.FirstOrDefault(stats => stats.Id == codecId);
  366. if (codecStats == null)
  367. {
  368. Debug.LogWarning($"{nameof(RTCCodecStats)} with codecId {codecId} is not included in {nameof(codecStatsList)}.");
  369. yield break;
  370. }
  371. actualCodecText.text = string.Format("Using {0} {1}, payloadType={2}.",
  372. codecStats.mimeType,
  373. codecStats.sdpFmtpLine,
  374. codecStats.payloadType
  375. );
  376. }
  377. private static void OnCreateSessionDescriptionError(RTCError error)
  378. {
  379. Debug.LogError($"Error Detail Type: {error.message}");
  380. }
  381. }