AudioBuffer.cs 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230
  1. /*
  2. * Copyright (c) Meta Platforms, Inc. and affiliates.
  3. * All rights reserved.
  4. *
  5. * This source code is licensed under the license found in the
  6. * LICENSE file in the root directory of this source tree.
  7. */
  8. using System.Collections;
  9. using System.Collections.Generic;
  10. using Meta.WitAi.Events;
  11. using Meta.WitAi.Interfaces;
  12. using Meta.WitAi.Lib;
  13. using UnityEngine;
  14. namespace Meta.WitAi.Data
  15. {
  16. public class AudioBuffer : MonoBehaviour
  17. {
  18. #region Singleton
  19. private static AudioBuffer _instance;
  20. public static AudioBuffer Instance
  21. {
  22. get
  23. {
  24. if (!_instance && Application.isPlaying)
  25. {
  26. _instance = FindObjectOfType<AudioBuffer>();
  27. if (!_instance)
  28. {
  29. var audioBufferObject = new GameObject("AudioBuffer");
  30. _instance = audioBufferObject.AddComponent<AudioBuffer>();
  31. }
  32. }
  33. return _instance;
  34. }
  35. }
  36. #endregion
  37. [SerializeField] private bool alwaysRecording;
  38. [SerializeField] private AudioBufferConfiguration audioBufferConfiguration = new AudioBufferConfiguration();
  39. [SerializeField] private AudioBufferEvents events = new AudioBufferEvents();
  40. public AudioBufferEvents Events => events;
  41. public IAudioInputSource MicInput
  42. {
  43. get
  44. {
  45. if (_micInput == null && Application.isPlaying)
  46. {
  47. // Check this gameobject & it's children for audio input
  48. _micInput = gameObject.GetComponentInChildren<IAudioInputSource>();
  49. // Check all roots for Mic Input JIC
  50. if (_micInput == null)
  51. {
  52. foreach (var root in gameObject.scene.GetRootGameObjects())
  53. {
  54. _micInput = root.GetComponentInChildren<IAudioInputSource>();
  55. if (_micInput != null)
  56. {
  57. break;
  58. }
  59. }
  60. }
  61. // Use default mic script
  62. if (_micInput == null)
  63. {
  64. _micInput = gameObject.AddComponent<Mic>();
  65. }
  66. }
  67. return _micInput;
  68. }
  69. }
  70. private IAudioInputSource _micInput;
  71. private RingBuffer<byte> _micDataBuffer;
  72. private byte[] _byteDataBuffer;
  73. private HashSet<Component> _waitingRecorders = new HashSet<Component>();
  74. private HashSet<Component> _activeRecorders = new HashSet<Component>();
  75. public bool IsRecording(Component component) => _waitingRecorders.Contains(component) || _activeRecorders.Contains(component);
  76. public bool IsInputAvailable => MicInput != null && MicInput.IsInputAvailable;
  77. public void CheckForInput() => MicInput.CheckForInput();
  78. public AudioEncoding AudioEncoding => MicInput.AudioEncoding;
  79. private void Awake()
  80. {
  81. _instance = this;
  82. InitializeMicDataBuffer();
  83. }
  84. private void OnEnable()
  85. {
  86. MicInput.OnSampleReady += OnMicSampleReady;
  87. if (alwaysRecording) StartRecording(this);
  88. }
  89. // Remove mic delegates
  90. private void OnDisable()
  91. {
  92. MicInput.OnSampleReady -= OnMicSampleReady;
  93. if (alwaysRecording) StopRecording(this);
  94. }
  95. // Callback for mic sample ready
  96. private void OnMicSampleReady(int sampleCount, float[] sample, float levelMax)
  97. {
  98. events.OnMicLevelChanged.Invoke(levelMax);
  99. var marker = CreateMarker();
  100. Convert(sample);
  101. if (null != events.OnByteDataReady)
  102. {
  103. marker.Clone().ReadIntoWriters(events.OnByteDataReady.Invoke);
  104. }
  105. events.OnSampleReady?.Invoke(marker, levelMax);
  106. }
  107. // Generate mic data buffer if needed
  108. private void InitializeMicDataBuffer()
  109. {
  110. if (null == _micDataBuffer && audioBufferConfiguration.micBufferLengthInSeconds > 0)
  111. {
  112. var bufferSize = (int) Mathf.Ceil(2 *
  113. audioBufferConfiguration
  114. .micBufferLengthInSeconds * 1000 *
  115. audioBufferConfiguration.sampleLengthInMs);
  116. if (bufferSize <= 0)
  117. {
  118. bufferSize = 1024;
  119. }
  120. _micDataBuffer = new RingBuffer<byte>(bufferSize);
  121. }
  122. }
  123. // Convert
  124. private void Convert(float[] samples)
  125. {
  126. var sampleCount = samples.Length;
  127. const int rescaleFactor = 32767; //to convert float to Int16
  128. for (int i = 0; i < sampleCount; i++)
  129. {
  130. short data = (short) (samples[i] * rescaleFactor);
  131. _micDataBuffer.Push((byte) data);
  132. _micDataBuffer.Push((byte) (data >> 8));
  133. }
  134. }
  135. public RingBuffer<byte>.Marker CreateMarker()
  136. {
  137. return _micDataBuffer.CreateMarker();
  138. }
  139. /// <summary>
  140. /// Creates a marker with an offset
  141. /// </summary>
  142. /// <param name="offset">Number of seconds to offset the marker by</param>
  143. /// <returns></returns>
  144. public RingBuffer<byte>.Marker CreateMarker(float offset)
  145. {
  146. var samples = (int) (AudioEncoding.samplerate * offset);
  147. return _micDataBuffer.CreateMarker(samples);
  148. }
  149. public void StartRecording(Component component)
  150. {
  151. StartCoroutine(WaitForMicToStart(component));
  152. }
  153. private IEnumerator WaitForMicToStart(Component component)
  154. {
  155. // Wait for mic
  156. _waitingRecorders.Add(component);
  157. yield return new WaitUntil(() => null != MicInput && MicInput.IsInputAvailable);
  158. if (!_waitingRecorders.Contains(component))
  159. {
  160. yield break;
  161. }
  162. _waitingRecorders.Remove(component);
  163. // Add component
  164. _activeRecorders.Add(component);
  165. // Start mic
  166. if (!MicInput.IsRecording)
  167. {
  168. MicInput.StartRecording(audioBufferConfiguration.sampleLengthInMs);
  169. }
  170. // On Start Listening
  171. if (component is IVoiceEventProvider v)
  172. {
  173. v.VoiceEvents.OnStartListening?.Invoke();
  174. }
  175. }
  176. public void StopRecording(Component component)
  177. {
  178. // Remove waiting recorder
  179. if (_waitingRecorders.Contains(component))
  180. {
  181. _waitingRecorders.Remove(component);
  182. return;
  183. }
  184. // Ignore unless active
  185. if (!_activeRecorders.Contains(component))
  186. {
  187. return;
  188. }
  189. // Remove active recorder
  190. _activeRecorders.Remove(component);
  191. // Stop recording if last active recorder
  192. if (_activeRecorders.Count == 0)
  193. {
  194. MicInput.StopRecording();
  195. }
  196. // On Stop Listening
  197. if (component is IVoiceEventProvider v)
  198. {
  199. v.VoiceEvents.OnStoppedListening?.Invoke();
  200. }
  201. }
  202. }
  203. }