-
Notifications
You must be signed in to change notification settings - Fork 57
Expand file tree
/
Copy pathMicrophoneSource.cs
More file actions
144 lines (123 loc) · 5.08 KB
/
MicrophoneSource.cs
File metadata and controls
144 lines (123 loc) · 5.08 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
using System;
using System.Collections;
using UnityEngine;
namespace LiveKit
{
/// <summary>
/// An audio source which captures from the device's microphone.
/// </summary>
/// <remarks>
/// Ensure microphone permissions are granted before calling <see cref="Start"/>.
/// </remarks>
sealed public class MicrophoneSource : RtcAudioSource
{
private readonly GameObject _sourceObject;
private readonly string _deviceName;
private readonly uint _sampleRate;
public override event Action<float[], int, int> AudioRead;
private bool _disposed = false;
private bool _started = false;
/// <summary>
/// Creates a new microphone source for the given device.
/// </summary>
/// <param name="deviceName">The name of the device to capture from. Use <see cref="Microphone.devices"/> to
/// get the list of available devices.</param>
/// <param name="sourceObject">The GameObject to attach the AudioSource to. The object must be kept in the scene
/// for the duration of the source's lifetime.</param>
/// <param name="channels">Number of audio channels (default: 2 for stereo). Configurable at runtime.</param>
/// <param name="sampleRate">Sample rate in Hz (default: 48000). Configurable at runtime.</param>
public MicrophoneSource(string deviceName, GameObject sourceObject, int channels = 2, uint sampleRate = 48000)
: base(channels, RtcAudioSourceType.AudioSourceMicrophone, sampleRate)
{
_deviceName = deviceName;
_sourceObject = sourceObject;
_sampleRate = sampleRate;
}
/// <summary>
/// Begins capturing audio from the microphone.
/// </summary>
/// <exception cref="InvalidOperationException">
/// Thrown when the microphone is not available or unauthorized.
/// </exception>
/// <remarks>
/// Ensure microphone permissions are granted before calling this method
/// by calling <see cref="Application.RequestUserAuthorization"/>.
/// </remarks>
public override void Start()
{
base.Start();
if (_started) return;
if (!Application.HasUserAuthorization(mode: UserAuthorization.Microphone))
throw new InvalidOperationException("Microphone access not authorized");
MonoBehaviourContext.OnApplicationPauseEvent += OnApplicationPause;
MonoBehaviourContext.RunCoroutine(StartMicrophone());
_started = true;
}
private IEnumerator StartMicrophone()
{
var clip = Microphone.Start(
_deviceName,
loop: true,
lengthSec: 1,
frequency: (int)_sampleRate // Uses configurable sample rate instead of hardcoded default
);
if (clip == null)
throw new InvalidOperationException("Microphone start failed");
var source = _sourceObject.AddComponent<AudioSource>();
source.clip = clip;
source.loop = true;
var probe = _sourceObject.AddComponent<AudioProbe>();
// Clear the audio data after it is read as to not play it through the speaker locally.
probe.ClearAfterInvocation();
probe.AudioRead += OnAudioRead;
var waitUntilReady = new WaitUntil(() => Microphone.GetPosition(_deviceName) > 0);
yield return waitUntilReady;
source.Play();
}
/// <summary>
/// Stops capturing audio from the microphone.
/// </summary>
public override void Stop()
{
base.Stop();
MonoBehaviourContext.RunCoroutine(StopMicrophone());
MonoBehaviourContext.OnApplicationPauseEvent -= OnApplicationPause;
_started = false;
}
private IEnumerator StopMicrophone()
{
if (Microphone.IsRecording(_deviceName))
Microphone.End(_deviceName);
var probe = _sourceObject.GetComponent<AudioProbe>();
probe.AudioRead -= OnAudioRead;
UnityEngine.Object.Destroy(probe);
var source = _sourceObject.GetComponent<AudioSource>();
UnityEngine.Object.Destroy(source);
yield return null;
}
private void OnAudioRead(float[] data, int channels, int sampleRate)
{
AudioRead?.Invoke(data, channels, sampleRate);
}
private void OnApplicationPause(bool pause)
{
if (!pause && _started)
MonoBehaviourContext.RunCoroutine(RestartMicrophone());
}
private IEnumerator RestartMicrophone()
{
yield return StopMicrophone();
yield return StartMicrophone();
}
protected override void Dispose(bool disposing)
{
if (!_disposed && disposing) Stop();
_disposed = true;
base.Dispose(disposing);
}
~MicrophoneSource()
{
Dispose(false);
}
}
}