#if UNITY_EDITOR using System; using System.Linq; using System.Collections.Generic; using UnityEditor; using UnityEngine; using System.Reflection; namespace Unity.RenderStreaming.Editor { [CustomPropertyDrawer(typeof(CodecAttribute))] class CodecDrawer : PropertyDrawer { interface Codec { string name { get; } string mimeType { get; } string sdpFmtpLine { get; } int channelCount { get; } int sampleRate { get; } string optionTitle { get; } int order { get; } } class AudioCodec : Codec { public string name { get { return codec_.name; } } public string mimeType { get { return codec_.mimeType; } } public string sdpFmtpLine { get { return codec_.sdpFmtpLine; } } public int channelCount { get { return codec_.channelCount; } } public int sampleRate { get { return codec_.sampleRate; } } public string optionTitle { get { return $"{codec_.channelCount} channel"; } } public int order { get { return codec_.channelCount; } } public AudioCodec(AudioCodecInfo codec) { codec_ = codec; } AudioCodecInfo codec_; } class VideoCodec : Codec { public string name { get { return codec_.name; } } public string mimeType { get { return codec_.mimeType; } } public string sdpFmtpLine { get { return codec_.sdpFmtpLine; } } public string optionTitle { get { switch(codec_) { case H264CodecInfo h264Codec: return $"{h264Codec.profile} Profile, Level {h264Codec.level.ToString().Insert(1, ".")}"; case VP9CodecInfo vp9codec: return $"Profile {(int)vp9codec.profile}"; case AV1CodecInfo av1codec: return $"Profile {(int)av1codec.profile}"; } return null; } } public int channelCount { get { throw new NotSupportedException(); } } public int sampleRate { get { throw new NotSupportedException(); } } public int order { get { switch (codec_) { case H264CodecInfo h264Codec: return (int)h264Codec.profile; case VP9CodecInfo vp9codec: return (int)vp9codec.profile; case AV1CodecInfo av1codec: return (int)av1codec.profile; } return 0; } } public VideoCodec(VideoCodecInfo codec) { codec_ = codec; } VideoCodecInfo codec_; } SerializedProperty propertyMimeType; SerializedProperty propertySdpFmtpLine; SerializedProperty propertyChannelCount; SerializedProperty propertySampleRate; IEnumerable codecs; string[] codecNames = new string[] { "Default" }; string[] codecOptions = new string[] {}; IEnumerable selectedCodecs; GUIContent codecLabel; int selectCodecIndex = 0; int selectCodecOptionIndex = 0; bool hasCodecOptions = false; bool cache = false; bool changed = false; static readonly GUIContent s_audioCodecLabel = EditorGUIUtility.TrTextContent("Audio Codec", "Audio encoding codec."); static readonly GUIContent s_videoCodecLabel = EditorGUIUtility.TrTextContent("Video Codec", "Video encoding codec."); static IEnumerable GetAvailableCodecs(UnityEngine.Object target) { if(target is VideoStreamSender) { return VideoStreamSender.GetAvailableCodecs().Select(codec => new VideoCodec(codec)); } else if (target is VideoStreamReceiver) { return VideoStreamReceiver.GetAvailableCodecs().Select(codec => new VideoCodec(codec)); } else if (target is AudioStreamSender) { return AudioStreamSender.GetAvailableCodecs().Select(codec => new AudioCodec(codec)); } else if (target is AudioStreamReceiver) { return AudioStreamReceiver.GetAvailableCodecs().Select(codec => new AudioCodec(codec)); } throw new ArgumentException(); } static GUIContent GetCodecLabel(UnityEngine.Object target) { if (target is VideoStreamSender || target is VideoStreamReceiver) { return s_videoCodecLabel; } else if (target is AudioStreamSender || target is AudioStreamReceiver) { return s_audioCodecLabel; } throw new ArgumentException(); } int FindOptionIndex(IEnumerable codecs) { return Array.FindIndex(codecs.ToArray(), codec => { if (codec is VideoCodec) return codec.sdpFmtpLine == propertySdpFmtpLine.stringValue; else return codec.sdpFmtpLine == propertySdpFmtpLine.stringValue && codec.channelCount == propertyChannelCount.intValue && codec.sampleRate == propertySampleRate.intValue; }); } public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) { if (!cache) { propertyMimeType = property.FindPropertyInChildren("m_MimeType"); propertySdpFmtpLine = property.FindPropertyInChildren("m_SdpFmtpLine"); propertyChannelCount = property.FindPropertyInChildren("m_ChannelCount"); propertySampleRate = property.FindPropertyInChildren("m_SampleRate"); codecs = GetAvailableCodecs(property.serializedObject.targetObject); codecNames = codecNames.Concat(codecs.Select(codec => codec.name)).Distinct().ToArray(); var mimeType = propertyMimeType.stringValue; var codecName = mimeType.GetCodecName(); selectedCodecs = codecs.Where(codec => codec.name == codecName).OrderBy(codec => codec.order); codecOptions = selectedCodecs.Select(codec => codec.optionTitle).ToArray(); if (!selectedCodecs.Any()) selectCodecIndex = 0; else selectCodecIndex = Array.FindIndex(codecNames, codec => codec == codecName); codecLabel = GetCodecLabel(property.serializedObject.targetObject); hasCodecOptions = codecOptions.Length > 1; if (hasCodecOptions) selectCodecOptionIndex = FindOptionIndex(selectedCodecs); cache = true; } var rect = position; rect.height = EditorGUIUtility.singleLineHeight; EditorGUI.BeginProperty(rect, label, propertyMimeType); rect = EditorGUI.PrefixLabel(rect, codecLabel); EditorGUI.BeginChangeCheck(); selectCodecIndex = EditorGUI.Popup(rect, selectCodecIndex, codecNames); if (EditorGUI.EndChangeCheck()) { if(0 < selectCodecIndex) { string codecName = codecNames[selectCodecIndex]; selectedCodecs = codecs.Where(codec => codec.name == codecName).OrderBy(codec => codec.order); codecOptions = selectedCodecs.Select(codec => codec.optionTitle).ToArray(); hasCodecOptions = codecOptions.Length > 1; var codec = selectedCodecs.First(); propertyMimeType.stringValue = codec.mimeType; propertySdpFmtpLine.stringValue = codec.sdpFmtpLine; if(propertyChannelCount != null) propertyChannelCount.intValue = codec.channelCount; if (propertySampleRate != null) propertySampleRate.intValue = codec.sampleRate; } else { propertyMimeType.stringValue = null; propertySdpFmtpLine.stringValue = null; if (propertyChannelCount != null) propertyChannelCount.intValue = 0; if (propertySampleRate != null) propertySampleRate.intValue = 0; hasCodecOptions = false; } } EditorGUI.EndProperty(); int codecIndex = selectCodecIndex - 1; if (0 < codecIndex) { if (hasCodecOptions && 0 < codecOptions.Length) { // sdp fmtp line rect.y += EditorGUIUtility.singleLineHeight; EditorGUI.BeginProperty(rect, label, propertySdpFmtpLine); EditorGUI.BeginChangeCheck(); selectCodecOptionIndex = EditorGUI.Popup(rect, selectCodecOptionIndex, codecOptions); if (EditorGUI.EndChangeCheck()) { var codec = selectedCodecs.ElementAt(selectCodecOptionIndex); propertySdpFmtpLine.stringValue = codec.sdpFmtpLine; if (propertyChannelCount != null) propertyChannelCount.intValue = codec.channelCount; if (propertySampleRate != null) propertySampleRate.intValue = codec.sampleRate; } EditorGUI.EndProperty(); } } if (changed) { // todo: not supported changing codecs in play mode. //if (Application.isPlaying) //{ // var objectReferenceValue = property.serializedObject.targetObject; // var type = objectReferenceValue.GetType(); // var attribute = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic; // var methodName = "SetCodec"; // var method = type.GetMethod(methodName, attribute); // method.Invoke(objectReferenceValue, new object[] { newValue }); //} changed = false; } } public override float GetPropertyHeight(SerializedProperty property, GUIContent label) { if (property == null) throw new System.ArgumentNullException(nameof(property)); int lineCount = hasCodecOptions ? 2 : 1; return EditorGUIUtility.singleLineHeight * lineCount; } } } #endif