#if !UNITY_WEBGL // Microphone API is not supported in WebGL
using System;
using System.Buffers;
using System.Collections;
using System.Linq;
using UnityEngine;
using Object = UnityEngine.Object;
namespace TensorFlowLite
{
///
/// An utility for getting latest N samples from microphone.
///
public class MicrophoneBuffer : IDisposable
{
public enum Frequency
{
Hz8000 = 8000,
Hz16000 = 16000,
Hz32000 = 32000,
Hz44100 = 44100,
Hz48000 = 48000,
Hz96000 = 96000,
}
[Serializable]
public class Options
{
[Tooltip("Microphone device name; null to use default device")]
[MicrophoneName]
public string deviceName = null;
[Tooltip("Mic Frequency in Hz")]
public Frequency frequency = Frequency.Hz16000;
[Tooltip("Max duration in seconds")]
[Min(1)]
public int maxDurationSec = 5;
}
[Tooltip("Default options")]
[SerializeField]
private Options defaultOptions = new();
private string deviceName;
private AudioClip clip;
private ArrayPool pool;
public bool IsRecording => clip != null;
public void Dispose()
{
StopRecording();
}
public IEnumerator StartRecording(Options options = null)
{
// Check permission
yield return Application.RequestUserAuthorization(UserAuthorization.Microphone);
if (!Application.HasUserAuthorization(UserAuthorization.Microphone))
{
Debug.LogError("Microphone permission is required");
yield break;
}
options ??= defaultOptions;
// Find device name
string[] availableDevices = Microphone.devices;
deviceName = options.deviceName;
if (string.IsNullOrEmpty(deviceName) || !availableDevices.Contains(deviceName))
{
deviceName = availableDevices[0];
}
// Clamp frequency
int frequency = (int)options.frequency;
Microphone.GetDeviceCaps(deviceName, out int minFreq, out int maxFreq);
frequency = Math.Clamp(frequency, minFreq, maxFreq);
int maxDurationSec = Math.Max(1, options.maxDurationSec);
var clip = Microphone.Start(deviceName, true, maxDurationSec, frequency);
pool ??= ArrayPool.Create(frequency * maxDurationSec, 2);
// Wait until recording starts
yield return new WaitUntil(() => Microphone.GetPosition(deviceName) > 0);
this.clip = clip;
Debug.Log($"Started Microphone: Device={deviceName}, Hz={frequency}, MaxSec={maxDurationSec}");
}
public void StopRecording()
{
{
Microphone.End(deviceName);
}
if (clip != null)
{
Object.Destroy(clip);
clip = null;
}
}
public void GetLatestSamples(float[] samples)
{
if (!IsRecording)
{
throw new InvalidOperationException("Recording is not started");
}
if (samples.Length > clip.samples)
{
throw new ArgumentException("samples.Length must be less than clip total samples");
}
int position = Microphone.GetPosition(deviceName);
if (position < samples.Length)
{
GetLoopedSamples(position, samples);
}
else
{
int offset = position - samples.Length;
clip.GetData(samples, offset);
}
}
#if UNITY_2023_2_OR_NEWER
// GetData(Span) is added since Unity 2023.2
private void GetLoopedSamples(int position, float[] samples)
{
int firstLength = samples.Length - position;
Span firstBuffer = samples.AsSpan(0, firstLength);
clip.GetData(firstBuffer, clip.samples - firstLength);
if (position <= 0)
{
return;
}
int secondLength = position;
Span secondBuffer = samples.AsSpan(firstLength, secondLength);
clip.GetData(secondBuffer, 0);
}
#else
private void GetLoopedSamples(int position, float[] samples)
{
int firstLength = samples.Length - position;
float[] firstBuffer = pool.Rent(firstLength);
clip.GetData(firstBuffer, clip.samples - firstLength);
Array.Copy(firstBuffer, 0, samples, 0, firstLength);
pool.Return(firstBuffer);
if (position <= 0)
{
return;
}
int secondLength = position;
float[] secondBuffer = pool.Rent(secondLength);
clip.GetData(secondBuffer, 0);
Array.Copy(secondBuffer, 0, samples, firstLength, secondLength);
pool.Return(secondBuffer);
}
#endif
}
}
#endif // !UNITY_WEBGL