using UnityEngine; using Adrenak.UniMic; using Adrenak.UniVoice.Networks; using Adrenak.UniVoice.Outputs; using Adrenak.UniVoice.Inputs; using Adrenak.UniVoice.Filters; namespace Adrenak.UniVoice.Samples { /// /// To get this setup sample to work, ensure that you have done the following: /// - Import Mirror and add the UNIVOICE_NETWORK_MIRROR compilation symbol to your project /// - If you want to use RNNoise filter, import RNNoise4Unity into your project and add UNIVOICE_FILTER_RNNOISE4UNITY /// - Add this component to the first scene of your Unity project /// /// *** More info on adding and activating non packaged dependencies is here: https://github.com/adrenak/univoice?tab=readme-ov-file#activating-non-packaged-dependencies *** /// /// This is a basic integration script that uses the following to setup UniVoice: /// - , an implementation of /// - , an implementation of /// - , an implementation of that captures audio from a mic /// - , an implementation of that is basically /// an idle audio input used when there is no input device /// - , an implementation of that removes noise from /// captured audio. /// - , an implementation of that encodes captured audio /// using Concentus (C# Opus) to reduce the size of audio frames /// - , an implementation of that decodes incoming audio /// using Concentus to decode and make the audio frame playable. /// public class UniVoiceMirrorSetupSample : MonoBehaviour { const string TAG = "[BasicUniVoiceSetupSample]"; /// /// Whether UniVoice has been setup successfully. This field will return true if the setup was successful. /// It runs on both server and client. /// public static bool HasSetUp { get; private set; } /// /// The server object. /// public static IAudioServer AudioServer { get; private set; } /// /// The client session. /// public static ClientSession ClientSession { get; private set; } #pragma warning disable CS0414 [SerializeField] bool useRNNoise4UnityIfAvailable = true; [SerializeField] bool useConcentusEncodeAndDecode = true; [SerializeField] bool useVad = true; #pragma warning restore void Start() { if (HasSetUp) { Debug.unityLogger.Log(LogType.Log, TAG, "UniVoice is already set up. Ignoring..."); return; } HasSetUp = Setup(); } bool Setup() { Debug.unityLogger.Log(LogType.Log, TAG, "Trying to setup UniVoice"); bool failed = false; // Set setup the AudioServer and ClientSession on ALL builds. This means that you'd // have a ClientSession on a dedicated server, even though there's not much you can do with it. // Similarly, a client would also have an AudioServer object. But it would just be inactive. // This sample is for ease of use and to get something working quickly, so we don't bother // with these minor details. Note that doing so does not have any performance implications // so you can do this, so you could keep this approach without any tradeoffs. var createdAudioServer = SetupAudioServer(); if (!createdAudioServer) { Debug.unityLogger.Log(LogType.Error, TAG, "Could not setup UniVoice server."); failed = true; } var setupAudioClient = SetupClientSession(); if (!setupAudioClient) { Debug.unityLogger.Log(LogType.Error, TAG, "Could not setup UniVoice client."); failed = true; } if (!failed) Debug.unityLogger.Log(LogType.Log, TAG, "UniVoice successfully setup!"); else Debug.unityLogger.Log(LogType.Error, TAG, $"Refer to the notes on top of {typeof(UniVoiceMirrorSetupSample).Name}.cs for setup instructions."); return !failed; } bool SetupAudioServer() { #if MIRROR // ---- CREATE AUDIO SERVER AND SUBSCRIBE TO EVENTS TO PRINT LOGS ---- // We create a server. If this code runs in server mode, MirrorServer will take care // or automatically handling all incoming messages. On a device connecting as a client, // this code doesn't do anything. AudioServer = new MirrorServer(); Debug.unityLogger.Log(LogType.Log, TAG, "Created MirrorServer object"); AudioServer.OnServerStart += () => { Debug.unityLogger.Log(LogType.Log, TAG, "Server started"); }; AudioServer.OnServerStop += () => { Debug.unityLogger.Log(LogType.Log, TAG, "Server stopped"); }; return true; #else Debug.unityLogger.Log(LogType.Error, TAG, "MirrorServer implementation not found!"); return false; #endif } bool SetupClientSession() { #if MIRROR // ---- CREATE AUDIO CLIENT AND SUBSCRIBE TO EVENTS ---- IAudioClient client = new MirrorClient(); client.OnJoined += (id, peerIds) => { Debug.unityLogger.Log(LogType.Log, TAG, $"You are Peer ID {id}"); }; client.OnLeft += () => { Debug.unityLogger.Log(LogType.Log, TAG, "You left the chatroom"); }; // When a peer joins, we instantiate a new peer view client.OnPeerJoined += id => { Debug.unityLogger.Log(LogType.Log, TAG, $"Peer {id} joined"); }; // When a peer leaves, destroy the UI representing them client.OnPeerLeft += id => { Debug.unityLogger.Log(LogType.Log, TAG, $"Peer {id} left"); }; Debug.unityLogger.Log(LogType.Log, TAG, "Created MirrorClient object"); // ---- CREATE AUDIO INPUT ---- IAudioInput input; // Since in this sample we use microphone input via UniMic, we first check if there // are any mic devices available. Mic.Init(); // Must do this to use the Mic class if (Mic.AvailableDevices.Count == 0) { Debug.unityLogger.Log(LogType.Log, TAG, "Device has no microphones." + "Will only be able to hear other clients, cannot send any audio."); input = new EmptyAudioInput(); Debug.unityLogger.Log(LogType.Log, TAG, "Created EmptyAudioInput"); } else { // Get the first recording device that we have available and start it. // Then we create a UniMicInput instance that requires the mic object // For more info on UniMic refer to https://www.github.com/adrenak/unimic var mic = Mic.AvailableDevices[0]; mic.StartRecording(60); Debug.unityLogger.Log(LogType.Log, TAG, "Started recording with Mic device named." + mic.Name + $" at frequency {mic.SamplingFrequency} with frame duration {mic.FrameDurationMS} ms."); input = new UniMicInput(mic); Debug.unityLogger.Log(LogType.Log, TAG, "Created UniMicInput"); } // ---- CREATE AUDIO OUTPUT FACTORY ---- IAudioOutputFactory outputFactory; // We want the incoming audio from peers to be played via the StreamedAudioSourceOutput // implementation of IAudioSource interface. So we get the factory for it. outputFactory = new StreamedAudioSourceOutput.Factory(); Debug.unityLogger.Log(LogType.Log, TAG, "Using StreamedAudioSourceOutput.Factory as output factory"); // ---- CREATE CLIENT SESSION AND ADD FILTERS TO IT ---- // With the client, input and output factory ready, we create create the client session ClientSession = new ClientSession(client, input, outputFactory); Debug.unityLogger.Log(LogType.Log, TAG, "Created session"); #if UNIVOICE_FILTER_RNNOISE4UNITY if(useRNNoise4UnityIfAvailable) { // RNNoiseFilter to remove noise from captured audio session.InputFilters.Add(new RNNoiseFilter()); Debug.unityLogger.Log(LogType.Log, TAG, "Registered RNNoiseFilter as an input filter"); } #endif if (useVad) { // We add the VAD filter after RNNoise. // This way lot of the background noise has been removed, VAD is truly trying to detect voice ClientSession.InputFilters.Add(new SimpleVadFilter(new SimpleVad())); } if (useConcentusEncodeAndDecode) { // ConcentureEncoder filter to encode captured audio that reduces the audio frame size ClientSession.InputFilters.Add(new ConcentusEncodeFilter()); Debug.unityLogger.Log(LogType.Log, TAG, "Registered ConcentusEncodeFilter as an input filter"); // For incoming audio register the ConcentusDecodeFilter to decode the encoded audio received from other clients ClientSession.AddOutputFilter(() => new ConcentusDecodeFilter()); Debug.unityLogger.Log(LogType.Log, TAG, "Registered ConcentusDecodeFilter as an output filter"); } return true; #else Debug.unityLogger.Log(LogType.Error, TAG, "MirrorClient implementation not found!"); return false; #endif } } }