AR Design
UBC EML collab with UBC SALA - visualizing IoT data in AR
MicrophoneTransmitter.cs
Go to the documentation of this file.
1 // Copyright (c) Microsoft Corporation. All rights reserved.
2 // Licensed under the MIT License. See LICENSE in the project root for license information.
3 
4 using System;
5 using System.Globalization;
6 using System.Threading;
7 using UnityEngine;
8 using HoloToolkit.Unity;
10 
11 namespace HoloToolkit.Sharing.VoiceChat
12 {
16  [RequireComponent(typeof(AudioSource))]
17  public class MicrophoneTransmitter : MonoBehaviour
18  {
22  public MicStream.StreamCategory Streamtype = MicStream.StreamCategory.HIGH_QUALITY_VOICE;
23 
27  public float InputGain = 2;
28 
32  public bool ShouldTransmitAudio = true;
33 
37  public bool Mute;
38 
39  public Transform GlobalAnchorTransform;
40 
41  public bool ShowInterPacketTime;
42 
43  private DateTime timeOfLastPacketSend;
44  private float worstTimeBetweenPackets;
45 
46  private int sequenceNumber;
47 
48  private int sampleRateType = 3; // 48000Hz
49 
50  private AudioSource audioSource;
51 
52  private bool hasServerConnection;
53  private bool micStarted;
54 
55  public const int AudioPacketSize = 960;
56  private CircularBuffer micBuffer = new CircularBuffer(AudioPacketSize * 10 * 2 * 4, true);
57  private byte[] packetSamples = new byte[AudioPacketSize * 4];
58 
59  // bit packers
60  private readonly BitManipulator versionPacker = new BitManipulator(0x7, 0); // 3 bits, 0 shift
61  private readonly BitManipulator audioStreamCountPacker = new BitManipulator(0x38, 3); // 3 bits, 3 shift
62  private readonly BitManipulator channelCountPacker = new BitManipulator(0x1c0, 6); // 3 bits, 6 shift
63  private readonly BitManipulator sampleRatePacker = new BitManipulator(0x600, 9); // 2 bits, 9 shift
64  private readonly BitManipulator sampleTypePacker = new BitManipulator(0x1800, 11); // 2 bits, 11 shift
65  private readonly BitManipulator sampleCountPacker = new BitManipulator(0x7fe000, 13); // 10 bits, 13 shift
66  private readonly BitManipulator codecTypePacker = new BitManipulator(0x1800000, 23); // 2 bits, 23 shift
67  private readonly BitManipulator mutePacker = new BitManipulator(0x2000000, 25); // 1 bits, 25 shift
68  private readonly BitManipulator sequenceNumberPacker = new BitManipulator(0x7C000000, 26); // 6 bits, 26 shift
69 
70  private readonly Mutex audioDataMutex = new Mutex();
71 
72  #region DebugVariables
73  public bool HearSelf;
74 
75  private readonly CircularBuffer testCircularBuffer = new CircularBuffer(48000 * 2 * 4 * 3, true);
76  private AudioSource testSource;
77  public AudioClip TestClip;
78  public bool SaveTestClip;
79  #endregion
80 
81  private NetworkConnection GetActiveConnection()
82  {
83  NetworkConnection connection = null;
84  var stage = SharingStage.Instance;
85  if (stage && stage.Manager != null)
86  {
87  connection = stage.Manager.GetServerConnection();
88  }
89  if (connection == null || !connection.IsConnected())
90  {
91  return null;
92  }
93  return connection;
94  }
95 
96  private void Awake()
97  {
98  audioSource = GetComponent<AudioSource>();
99 
100  int errorCode = MicStream.MicInitializeCustomRate((int)Streamtype, AudioSettings.outputSampleRate);
101  CheckForErrorOnCall(errorCode);
102  if (errorCode == 0 || errorCode == (int)MicStream.ErrorCodes.ALREADY_RUNNING)
103  {
104  if (CheckForErrorOnCall(MicStream.MicSetGain(InputGain)))
105  {
106  audioSource.volume = HearSelf ? 1.0f : 0.0f;
107  micStarted = CheckForErrorOnCall(MicStream.MicStartStream(false, false));
108  }
109  }
110  }
111 
112  private void OnAudioFilterRead(float[] buffer, int numChannels)
113  {
114  try
115  {
116  audioDataMutex.WaitOne();
117 
118  if (micStarted && hasServerConnection)
119  {
120  if (CheckForErrorOnCall(MicStream.MicGetFrame(buffer, buffer.Length, numChannels)))
121  {
122  int dataSize = buffer.Length * 4;
123  if (micBuffer.Write(buffer, 0, dataSize) != dataSize)
124  {
125  Debug.LogError("Send buffer filled up. Some audio will be lost.");
126  }
127  }
128  }
129  }
130  catch (Exception e)
131  {
132  Debug.LogError(e.Message);
133  }
134  finally
135  {
136  audioDataMutex.ReleaseMutex();
137  }
138  }
139 
140  private void Update()
141  {
142  CheckForErrorOnCall(MicStream.MicSetGain(InputGain));
143  audioSource.volume = HearSelf ? 1.0f : 0.0f;
144 
145  try
146  {
147  audioDataMutex.WaitOne();
148 
149  var connection = GetActiveConnection();
150  hasServerConnection = (connection != null);
151  if (hasServerConnection)
152  {
153  while (micBuffer.UsedCapacity >= 4 * AudioPacketSize)
154  {
155  TransmitAudio(connection);
156  }
157  }
158  }
159  catch (Exception e)
160  {
161  Debug.LogError(e.Message);
162  }
163  finally
164  {
165  audioDataMutex.ReleaseMutex();
166  }
167 
168  #region DebugInfo
169  if (SaveTestClip && testCircularBuffer.UsedCapacity == testCircularBuffer.TotalCapacity)
170  {
171  float[] testBuffer = new float[testCircularBuffer.UsedCapacity / 4];
172  testCircularBuffer.Read(testBuffer, 0, testBuffer.Length * 4);
173  testCircularBuffer.Reset();
174  TestClip = AudioClip.Create("testclip", testBuffer.Length / 2, 2, 48000, false);
175  TestClip.SetData(testBuffer, 0);
176  if (!testSource)
177  {
178  GameObject testObj = new GameObject("testclip");
179  testObj.transform.parent = transform;
180  testSource = testObj.AddComponent<AudioSource>();
181  }
182  testSource.PlayClip(TestClip);
183  SaveTestClip = false;
184  }
185  #endregion
186  }
187 
188  private void TransmitAudio(NetworkConnection connection)
189  {
190  micBuffer.Read(packetSamples, 0, 4 * AudioPacketSize);
191  SendFixedSizedChunk(connection, packetSamples, packetSamples.Length);
192 
193  if (SaveTestClip)
194  {
195  testCircularBuffer.Write(packetSamples, 0, packetSamples.Length);
196  }
197  }
198 
199  private void SendFixedSizedChunk(NetworkConnection connection, byte[] data, int dataSize)
200  {
201  DateTime currentTime = DateTime.Now;
202  float seconds = (float)(currentTime - timeOfLastPacketSend).TotalSeconds;
203  timeOfLastPacketSend = currentTime;
204  if (seconds < 10.0)
205  {
206  if (worstTimeBetweenPackets < seconds)
207  {
208  worstTimeBetweenPackets = seconds;
209  }
210 
211  if (ShowInterPacketTime)
212  {
213  Debug.LogFormat("Microphone: Milliseconds since last sent: {0}, Worst: {1}",
214  (seconds * 1000.0).ToString(CultureInfo.InvariantCulture),
215  (worstTimeBetweenPackets * 1000.0).ToString(CultureInfo.InvariantCulture));
216  }
217  }
218 
219  int clientId = SharingStage.Instance.Manager.GetLocalUser().GetID();
220 
221  // pack the header
222  NetworkOutMessage msg = connection.CreateMessage((byte)MessageID.AudioSamples);
223 
224  int dataCountFloats = dataSize / 4;
225 
226  msg.Write((byte)5); // 8 byte header size
227 
228  Int32 pack = 0;
229  versionPacker.SetBits(ref pack, 1); // version
230  audioStreamCountPacker.SetBits(ref pack, 1); // AudioStreamCount
231  channelCountPacker.SetBits(ref pack, 1); // ChannelCount
232  sampleRatePacker.SetBits(ref pack, sampleRateType); // SampleRate: 1 = 16000, 3 = 48000
233  sampleTypePacker.SetBits(ref pack, 0); // SampleType
234  sampleCountPacker.SetBits(ref pack, dataCountFloats); // SampleCount (data count is in bytes and the actual data is in floats, so div by 4)
235  codecTypePacker.SetBits(ref pack, 0); // CodecType
236  mutePacker.SetBits(ref pack, Mute ? 1 : 0);
237  sequenceNumberPacker.SetBits(ref pack, sequenceNumber++);
238  sequenceNumber %= 32;
239 
240  msg.Write(pack); // the packed bits
241 
242  // This is where stream data starts. Write all data for one stream
243 
244  msg.Write(0.0f); // average amplitude. Not needed in direction from client to server.
245  msg.Write(clientId); // non-zero client ID for this client.
246 
247  // HRTF position bits
248 
249  Vector3 cameraPosRelativeToGlobalAnchor = Vector3.zero;
250  Vector3 cameraDirectionRelativeToGlobalAnchor = Vector3.zero;
251 
252  if (GlobalAnchorTransform != null)
253  {
254  cameraPosRelativeToGlobalAnchor = MathUtils.TransformPointFromTo(
255  null,
256  GlobalAnchorTransform,
257  CameraCache.Main.transform.position);
258 
259  cameraDirectionRelativeToGlobalAnchor = MathUtils.TransformDirectionFromTo(
260  null,
261  GlobalAnchorTransform,
262  CameraCache.Main.transform.position);
263  }
264 
265  cameraPosRelativeToGlobalAnchor.Normalize();
266  cameraDirectionRelativeToGlobalAnchor.Normalize();
267 
268  // Camera position
269  msg.Write(cameraPosRelativeToGlobalAnchor.x);
270  msg.Write(cameraPosRelativeToGlobalAnchor.y);
271  msg.Write(cameraPosRelativeToGlobalAnchor.z);
272 
273  // HRTF direction bits
274  msg.Write(cameraDirectionRelativeToGlobalAnchor.x);
275  msg.Write(cameraDirectionRelativeToGlobalAnchor.y);
276  msg.Write(cameraDirectionRelativeToGlobalAnchor.z);
277 
278  msg.WriteArray(data, (uint)dataCountFloats * 4);
279 
280  connection.Send(msg, MessagePriority.Immediate, MessageReliability.ReliableOrdered, MessageChannel.Audio, true);
281  }
282 
283  private void OnDestroy()
284  {
285  CheckForErrorOnCall(MicStream.MicDestroy());
286  }
287 
288  private bool CheckForErrorOnCall(int returnCode)
289  {
290  return MicStream.CheckForErrorOnCall(returnCode);
291  }
292 
293 #if DOTNET_FX
294  // on device, deal with all the ways that we could suspend our program in as few lines as possible
295  private void OnApplicationPause(bool pause)
296  {
297  if (pause)
298  {
299  CheckForErrorOnCall(MicStream.MicPause());
300  }
301  else
302  {
303  CheckForErrorOnCall(MicStream.MicResume());
304  }
305  }
306 
307  private void OnApplicationFocus(bool focused)
308  {
309  OnApplicationPause(!focused);
310  }
311 
312  private void OnDisable()
313  {
314  OnApplicationPause(true);
315  }
316 
317  private void OnEnable()
318  {
319  OnApplicationPause(false);
320  }
321 #endif
322  }
323 }
Helper class for transmitting data over network.
static int MicResume()
Unpauses streaming of microphone data to MicGetFrame (and/or file specified with MicStartRecording) ...
static Vector3 TransformDirectionFromTo(Transform from, Transform to, Vector3 dirInFrom)
Takes a direction in the coordinate space specified by the "from" transform and transforms it to be t...
Definition: MathUtils.cs:59
virtual void Send(NetworkOutMessage msg, MessagePriority priority, MessageReliability reliability, MessageChannel channel, bool releaseMessage)
virtual void WriteArray(byte[] data, uint length)
Transmits data from your microphone to other clients connected to a SessionServer. Requires any receiving client to be running the MicrophoneReceiver script.
static int MicSetGain(float g)
Sets amplification factor for microphone samples returned by MicGetFrame (and/or file specified with ...
static int MicPause()
Pauses streaming of microphone data to MicGetFrame (and/or file specified with MicStartRecording) ...
virtual NetworkOutMessage CreateMessage(byte messageType)
The purpose of this class is to provide a cached reference to the main camera. Calling Camera...
Definition: CameraCache.cs:12
static T Instance
Returns the Singleton instance of the classes type. If no instance is found, then we search for an in...
Definition: Singleton.cs:26
static Camera Main
Returns a cached reference to the main camera and uses Camera.main if it hasn&#39;t been cached yet...
Definition: CameraCache.cs:20
static int MicGetFrame(float[] buffer, int length, int numchannels)
Read from the microphone buffer. Usually called once per frame.
static bool CheckForErrorOnCall(int returnCode)
Prints useful error/warning messages based on error codes returned from the functions in this class ...
Definition: MicStream.cs:161
Math Utilities class.
Definition: MathUtils.cs:13
static int MicStartStream(bool keepData, bool previewOnDevice, LiveMicCallback micsignal)
Call this to start receiving data from a microphone. Then, each frame, call MicGetFrame.
static int MicInitializeCustomRate(int category, int samplerate)
Called before calling MicStartStream or MicstartRecording to initialize microphone ...
int Read(Array dst, int dstWritePosBytes, int byteCount)
The SharingStage is in charge of managing the core networking layer for the application.
Definition: SharingStage.cs:14
bool Mute
Whether other users should be able to hear the transmitted audio
int Write(Array src, int srcReadPosBytes, int byteCount)
static Vector3 TransformPointFromTo(Transform from, Transform to, Vector3 ptInFrom)
Takes a point in the coordinate space specified by the "from" transform and transforms it to be the c...
Definition: MathUtils.cs:44
Helper class for bit manipulation.
static int MicDestroy()
Cleans up data associated with microphone recording. Counterpart to MicInitialize* ...