123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489 |
- using System;
- using System.Linq;
- using System.Collections;
- using System.Collections.Generic;
- using Unity.Collections;
- using Unity.WebRTC;
- using UnityEngine;
- namespace Unity.RenderStreaming
- {
- /// <summary>
- ///
- /// </summary>
- public enum AudioStreamSource
- {
- /// <summary>
- ///
- /// </summary>
- AudioListener = 0,
- /// <summary>
- ///
- /// </summary>
- AudioSource = 1,
- /// <summary>
- ///
- /// </summary>
- Microphone = 2,
- /// <summary>
- ///
- /// </summary>
- APIOnly = 3
- }
- /// <summary>
- /// Attach AudioListerner or AudioSource
- /// </summary>
- [AddComponentMenu("Render Streaming/Audio Stream Sender")]
- public class AudioStreamSender : StreamSenderBase
- {
- static readonly uint s_defaultMinBitrate = 0;
- static readonly uint s_defaultMaxBitrate = 200;
- [SerializeField]
- private AudioStreamSource m_Source;
- [SerializeField]
- private AudioListener m_AudioListener;
- [SerializeField]
- private AudioSource m_AudioSource;
- [SerializeField]
- private int m_MicrophoneDeviceIndex;
- [SerializeField]
- private bool m_AutoRequestUserAuthorization = true;
- [SerializeField, Codec]
- private AudioCodecInfo m_Codec;
- [SerializeField, Bitrate(0, 1000)]
- private Range m_Bitrate = new Range(s_defaultMinBitrate, s_defaultMaxBitrate);
- private int m_sampleRate = 0;
- private AudioStreamSourceImpl m_sourceImpl = null;
- private int m_frequency = 48000;
- /// <summary>
- ///
- /// </summary>
- public AudioStreamSource source
- {
- get { return m_Source; }
- set
- {
- if (m_Source == value)
- return;
- m_Source = value;
- if (!isPlaying)
- return;
- var op = CreateTrack();
- StartCoroutineWithCallback(op, _ => ReplaceTrack(_.Track));
- }
- }
- /// <summary>
- ///
- /// </summary>
- public AudioCodecInfo codec
- {
- get { return m_Codec; }
- }
- /// <summary>
- ///
- /// </summary>
- public uint minBitrate
- {
- get { return m_Bitrate.min; }
- }
- /// <summary>
- ///
- /// </summary>
- public uint maxBitrate
- {
- get { return m_Bitrate.max; }
- }
- /// <summary>
- /// The index of WebCamTexture.devices.
- /// </summary>
- public int sourceDeviceIndex
- {
- get { return m_MicrophoneDeviceIndex; }
- set
- {
- if (m_MicrophoneDeviceIndex == value)
- return;
- m_MicrophoneDeviceIndex = value;
- if (!isPlaying || m_Source != AudioStreamSource.Microphone)
- return;
- var op = CreateTrack();
- StartCoroutineWithCallback(op, _ => ReplaceTrack(_.Track));
- }
- }
- /// <summary>
- ///
- /// </summary>
- public AudioSource audioSource
- {
- get { return m_AudioSource; }
- set
- {
- if (m_AudioSource == value)
- return;
- m_AudioSource = value;
- if (!isPlaying || m_Source != AudioStreamSource.AudioSource)
- return;
- var op = CreateTrack();
- StartCoroutineWithCallback(op, _ => ReplaceTrack(_.Track));
- }
- }
- /// <summary>
- ///
- /// </summary>
- public AudioListener audioListener
- {
- get { return m_AudioListener; }
- set
- {
- if (m_AudioListener == value)
- return;
- m_AudioListener = value;
- if (!isPlaying || m_Source != AudioStreamSource.AudioListener)
- return;
- var op = CreateTrack();
- StartCoroutineWithCallback(op, _ => ReplaceTrack(_.Track));
- }
- }
- /// <summary>
- ///
- /// </summary>
- /// <returns></returns>
- static public IEnumerable<AudioCodecInfo> GetAvailableCodecs()
- {
- var excludeCodecMimeType = new[] { "audio/CN", "audio/telephone-event" };
- var capabilities = RTCRtpSender.GetCapabilities(TrackKind.Audio);
- return capabilities.codecs.Where(codec => !excludeCodecMimeType.Contains(codec.mimeType)).Select(codec => AudioCodecInfo.Create(codec));
- }
- /// <summary>
- ///
- /// </summary>
- /// <param name="minBitrate"></param>
- /// <param name="maxBitrate"></param>
- public void SetBitrate(uint minBitrate, uint maxBitrate)
- {
- if (minBitrate > maxBitrate)
- throw new ArgumentException("The maxBitrate must be greater than minBitrate.", "maxBitrate");
- m_Bitrate.min = minBitrate;
- m_Bitrate.max = maxBitrate;
- foreach (var transceiver in Transceivers.Values)
- {
- RTCError error = transceiver.Sender.SetBitrate(m_Bitrate.min, m_Bitrate.max);
- if (error.errorType != RTCErrorType.None)
- Debug.LogError(error.message);
- }
- }
- /// <summary>
- ///
- /// </summary>
- /// <param name="codec"></param>
- public void SetCodec(AudioCodecInfo codec)
- {
- m_Codec = codec;
- foreach (var transceiver in Transceivers.Values)
- {
- if (!string.IsNullOrEmpty(transceiver.Mid))
- continue;
- if (transceiver.Sender.Track.ReadyState == TrackState.Ended)
- continue;
- var codecs = new AudioCodecInfo[] { m_Codec };
- RTCErrorType error = transceiver.SetCodecPreferences(SelectCodecCapabilities(codecs).ToArray());
- if (error != RTCErrorType.None)
- throw new InvalidOperationException($"Set codec is failed. errorCode={error}");
- }
- }
- internal IEnumerable<RTCRtpCodecCapability> SelectCodecCapabilities(IEnumerable<AudioCodecInfo> codecs)
- {
- return RTCRtpSender.GetCapabilities(TrackKind.Audio).SelectCodecCapabilities(codecs);
- }
- private protected virtual void Awake()
- {
- OnStartedStream += _OnStartedStream;
- OnStoppedStream += _OnStoppedStream;
- }
- private protected override void OnDestroy()
- {
- base.OnDestroy();
- m_sourceImpl?.Dispose();
- m_sourceImpl = null;
- }
- void OnAudioConfigurationChanged(bool deviceWasChanged)
- {
- m_sampleRate = AudioSettings.outputSampleRate;
- }
- void _OnStartedStream(string connectionId)
- {
- }
- void _OnStoppedStream(string connectionId)
- {
- m_sourceImpl?.Dispose();
- m_sourceImpl = null;
- }
- internal override WaitForCreateTrack CreateTrack()
- {
- m_sourceImpl?.Dispose();
- m_sourceImpl = CreateAudioStreamSource();
- return m_sourceImpl.CreateTrack();
- }
- AudioStreamSourceImpl CreateAudioStreamSource()
- {
- switch (m_Source)
- {
- case AudioStreamSource.AudioListener:
- return new AudioStreamSourceAudioListener(this);
- case AudioStreamSource.AudioSource:
- return new AudioStreamSourceAudioSource(this);
- case AudioStreamSource.Microphone:
- return new AudioStreamSourceMicrophone(this);
- case AudioStreamSource.APIOnly:
- return new AudioStreamSourceAPIOnly(this);
- }
- throw new InvalidOperationException("");
- }
- private protected override void OnEnable()
- {
- OnAudioConfigurationChanged(false);
- AudioSettings.OnAudioConfigurationChanged += OnAudioConfigurationChanged;
- base.OnEnable();
- }
- private protected override void OnDisable()
- {
- AudioSettings.OnAudioConfigurationChanged -= OnAudioConfigurationChanged;
- base.OnDisable();
- }
- public void SetData(ref NativeArray<float> nativeArray, int channels)
- {
- if (m_Source != AudioStreamSource.APIOnly)
- throw new InvalidOperationException("To use this method, please set AudioStreamSource.APIOnly to source property");
- if (!isPlaying)
- return;
- (m_sourceImpl as AudioStreamSourceAPIOnly)?.SetData(ref nativeArray, channels, m_sampleRate);
- }
- abstract class AudioStreamSourceImpl : IDisposable
- {
- protected AudioStreamSourceImpl(AudioStreamSender parent)
- {
- }
- public abstract WaitForCreateTrack CreateTrack();
- public abstract void Dispose();
- }
- class AudioStreamSourceAudioListener : AudioStreamSourceImpl
- {
- private AudioListener m_audioListener;
- public AudioStreamSourceAudioListener(AudioStreamSender parent) : base(parent)
- {
- m_audioListener = parent.m_AudioListener;
- if (m_audioListener == null)
- throw new InvalidOperationException("The audioListener is not assigned.");
- }
- public override WaitForCreateTrack CreateTrack()
- {
- var instruction = new WaitForCreateTrack();
- instruction.Done(new AudioStreamTrack(m_audioListener));
- return instruction;
- }
- public override void Dispose()
- {
- GC.SuppressFinalize(this);
- }
- ~AudioStreamSourceAudioListener()
- {
- Dispose();
- }
- }
- class AudioStreamSourceAudioSource : AudioStreamSourceImpl
- {
- private AudioSource m_audioSource;
- public AudioStreamSourceAudioSource(AudioStreamSender parent) : base(parent)
- {
- m_audioSource = parent.m_AudioSource;
- if (m_audioSource == null)
- throw new InvalidOperationException("The audioSource is not assigned.");
- }
- public override WaitForCreateTrack CreateTrack()
- {
- var instruction = new WaitForCreateTrack();
- instruction.Done(new AudioStreamTrack(m_audioSource));
- return instruction;
- }
- public override void Dispose()
- {
- GC.SuppressFinalize(this);
- }
- ~AudioStreamSourceAudioSource()
- {
- Dispose();
- }
- }
- class AudioStreamSourceMicrophone : AudioStreamSourceImpl
- {
- int m_deviceIndex;
- bool m_autoRequestUserAuthorization;
- int m_frequency;
- string m_deviceName;
- AudioSource m_audioSource;
- GameObject m_audioSourceObj;
- AudioStreamSender m_parent;
- public AudioStreamSourceMicrophone(AudioStreamSender parent) : base(parent)
- {
- int deviceIndex = parent.m_MicrophoneDeviceIndex;
- if (deviceIndex < 0 || Microphone.devices.Length <= deviceIndex)
- throw new ArgumentOutOfRangeException("deviceIndex", deviceIndex, "The deviceIndex is out of range");
- m_parent = parent;
- m_deviceIndex = deviceIndex;
- m_frequency = parent.m_frequency;
- m_autoRequestUserAuthorization = parent.m_AutoRequestUserAuthorization;
- }
- public override WaitForCreateTrack CreateTrack()
- {
- var instruction = new WaitForCreateTrack();
- m_parent.StartCoroutine(CreateTrackCoroutine(instruction));
- return instruction;
- }
- IEnumerator CreateTrackCoroutine(WaitForCreateTrack instruction)
- {
- if (m_autoRequestUserAuthorization)
- {
- AsyncOperation op = Application.RequestUserAuthorization(UserAuthorization.Microphone);
- yield return op;
- }
- if (!Application.HasUserAuthorization(UserAuthorization.Microphone))
- throw new InvalidOperationException("Call Application.RequestUserAuthorization before creating track with Microphone.");
- m_deviceName = Microphone.devices[m_deviceIndex];
- Microphone.GetDeviceCaps(m_deviceName, out int minFreq, out int maxFreq);
- var micClip = Microphone.Start(m_deviceName, true, 1, m_frequency);
- // set the latency to “0” samples before the audio starts to play.
- yield return new WaitUntil(() => Microphone.GetPosition(m_deviceName) > 0);
- m_audioSourceObj = new GameObject("Audio");
- m_audioSourceObj.hideFlags = HideFlags.HideInHierarchy;
- DontDestroyOnLoad(m_audioSourceObj);
- m_audioSource = m_audioSourceObj.AddComponent<AudioSource>();
- m_audioSource.clip = micClip;
- m_audioSource.loop = true;
- m_audioSource.Play();
- instruction.Done(new AudioStreamTrack(m_audioSource));
- }
- public override void Dispose()
- {
- if (m_audioSourceObj != null)
- {
- m_audioSource.Stop();
- var clip = m_audioSource.clip;
- if (clip != null)
- {
- Destroy(clip);
- }
- m_audioSource.clip = null;
- Destroy(m_audioSourceObj);
- m_audioSourceObj = null;
- m_audioSource = null;
- }
- if (Microphone.IsRecording(m_deviceName))
- Microphone.End(m_deviceName);
- GC.SuppressFinalize(this);
- }
- ~AudioStreamSourceMicrophone()
- {
- Dispose();
- }
- }
- class AudioStreamSourceAPIOnly : AudioStreamSourceImpl
- {
- AudioStreamTrack m_audioTrack;
- public AudioStreamSourceAPIOnly(AudioStreamSender parent) : base(parent)
- {
- }
- public override WaitForCreateTrack CreateTrack()
- {
- var instruction = new WaitForCreateTrack();
- m_audioTrack = new AudioStreamTrack();
- instruction.Done(new AudioStreamTrack());
- return instruction;
- }
- public void SetData(ref NativeArray<float> nativeArray, int channels, int sampleRate)
- {
- m_audioTrack?.SetData(ref nativeArray, channels, sampleRate);
- }
- public override void Dispose()
- {
- GC.SuppressFinalize(this);
- }
- ~AudioStreamSourceAPIOnly()
- {
- Dispose();
- }
- }
- }
- }
|