Paradox Game Engine  v1.0.0 beta06
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Events Macros Pages
AudioVoice.iOS.cs
Go to the documentation of this file.
1 // Copyright (c) 2014 Silicon Studio Corp. (http://siliconstudio.co.jp)
2 // This file is distributed under GPL v3. See LICENSE.md for details.
3 
4 #if SILICONSTUDIO_PLATFORM_IOS
5 using System;
6 using System.Collections.Generic;
7 using System.Diagnostics;
8 using System.Runtime.InteropServices;
9 using MonoTouch.AudioToolbox;
10 using MonoTouch.AudioUnit;
11 using SiliconStudio.Core;
12 using SiliconStudio.Paradox.Audio.Wave;
13 using SiliconStudio.Core.Diagnostics;
14 
15 namespace SiliconStudio.Paradox.Audio
16 {
17  internal unsafe class AudioVoice : ComponentBase
18  {
19  [DllImport(NativeLibrary.LibraryName, CallingConvention = NativeLibrary.CallConvention)]
20  private static extern int SetInputRenderCallbackToChannelMixerDefault(IntPtr inUnit, uint element, IntPtr userData);
21 
22  [DllImport(NativeLibrary.LibraryName, CallingConvention = NativeLibrary.CallConvention)]
23  private static extern int SetInputRenderCallbackTo3DMixerDefault(IntPtr inUnit, uint element, IntPtr userData);
24 
25  [DllImport(NativeLibrary.LibraryName, CallingConvention = NativeLibrary.CallConvention)]
26  private static extern int SetInputRenderCallbackToNull(IntPtr inUnit, uint element);
27 
28  /// <summary>
29  /// The frequency of the output of the audio unit graph.
30  /// </summary>
31  public const int AudioUnitOutputSampleRate = 44100;
32 
33  private readonly AudioEngine audioEngine;
34  private readonly SoundEffectInstance soundEffectInstance;
35  private readonly WaveFormat waveFormat;
36  private const int MaxNumberOfTracks = 16;
37 
38  private static readonly Logger Log = GlobalLogger.GetLogger("AudioVoice");
39 
40  private static int nbOfInstances;
41  private static readonly object StaticMembersLock = new object();
42 
43  private static AUGraph audioGraph;
44  private static AudioUnit unitChannelMixer;
45  private static AudioUnit unit3DMixer;
46 
47  private readonly AudioDataRendererInfo* pAudioDataRendererInfo;
48 
49  /// <summary>
50  /// The list of UnitElement index that are available for use in the 3D and channel mixer.
51  /// </summary>
52  private static Queue<uint> availableMixerBusIndices;
53 
54  /// <summary>
55  /// Boolean indicating if the sound is a 3D sound or not (input to 3DMixer or ChannelMixer)
56  /// </summary>
57  private bool is3D;
58  private bool Is3D
59  {
60  get { return is3D; }
61  set
62  {
63  if (value != is3D)
64  {
65  is3D = value;
66  EnableMixerCurrentInput(pAudioDataRendererInfo->IsEnabled2D || pAudioDataRendererInfo->IsEnabled3D);
67  }
68  }
69  }
70 
71  /// <summary>
72  /// The index of the mixer bus used by this instance.
73  /// </summary>
74  internal uint BusIndexMixer { get; private set; }
75 
76  /// <summary>
77  /// This enumeration is missing from MonoTouch
78  /// </summary>
79  enum _3DMixerParametersIds
80  {
81  Azimuth = 0,
82  Elevation = 1,
83  Distance = 2,
84  Gain = 3,
85  PlaybackRate = 4,
86  Enable = 5,
87  }
88 
89  public bool DidVoicePlaybackEnd()
90  {
91  return pAudioDataRendererInfo->PlaybackEnded;
92  }
93 
94  private static void CheckUnitStatus(AudioUnitStatus status, string msg)
95  {
96  if (status != AudioUnitStatus.OK)
97  {
98  Log.Error("Audio Error [{0} / {1}]. Voices: {2}", msg, status, nbOfInstances);
99  throw new AudioSystemInternalException(msg + " [Error=" + status + "].");
100  }
101  }
102 
103  private void EnableMixerCurrentInput(bool shouldBeEnabled)
104  {
105  if(BusIndexMixer == uint.MaxValue)
106  return;
107 
108  CheckUnitStatus(unitChannelMixer.SetParameter(AudioUnitParameterType.MultiChannelMixerEnable,
109  !Is3D && shouldBeEnabled ? 1f : 0f, AudioUnitScopeType.Input, BusIndexMixer), "Failed to enable/disable the ChannelMixerInput.");
110 
111  if(waveFormat.Channels == 1) // no 3D mixer for stereo sounds
112  CheckUnitStatus(unit3DMixer.SetParameter((AudioUnitParameterType)_3DMixerParametersIds.Enable,
113  Is3D && shouldBeEnabled ? 1f : 0f, AudioUnitScopeType.Input, BusIndexMixer), "Failed to enable/disable the 3DMixerInput.");
114 
115  pAudioDataRendererInfo->IsEnabled2D = shouldBeEnabled && !Is3D;
116  pAudioDataRendererInfo->IsEnabled3D = shouldBeEnabled && Is3D;
117  }
118 
119  private static void CheckGraphError(AUGraphError error, string msg)
120  {
121  if (error != AUGraphError.OK)
122  throw new AudioSystemInternalException(msg + " [Error=" + error + "].");
123  }
124 
125  /// <summary>
126  /// Create the audio stream format for 16bits PCM data.
127  /// </summary>
128  /// <param name="numberOfChannels"></param>
129  /// <param name="frameRate"></param>
130  /// <param name="isInterleaved"></param>
131  /// <returns></returns>
132  private static AudioStreamBasicDescription CreateLinear16BitsPcm(int numberOfChannels, double frameRate, bool isInterleaved = true)
133  {
134  AudioStreamBasicDescription retFormat;
135  const int wordSize = 2;
136 
137  retFormat.FramesPerPacket = 1;
138  retFormat.Format = AudioFormatType.LinearPCM;
139  retFormat.FormatFlags = AudioFormatFlags.IsPacked | AudioFormatFlags.IsSignedInteger;
140  retFormat.SampleRate = frameRate;
141  retFormat.BitsPerChannel = 8 * wordSize;
142  retFormat.ChannelsPerFrame = numberOfChannels;
143  retFormat.BytesPerFrame = isInterleaved ? numberOfChannels * wordSize : wordSize;
144  retFormat.BytesPerPacket = retFormat.FramesPerPacket * retFormat.BytesPerFrame;
145  retFormat.Reserved = 0;
146 
147  if(!isInterleaved)
148  retFormat.FormatFlags |= AudioFormatFlags.IsNonInterleaved;
149 
150  return retFormat;
151  }
152 
153  public AudioVoice(AudioEngine engine, SoundEffectInstance effectInstance, WaveFormat desiredFormat)
154  {
155  if (engine == null) throw new ArgumentNullException("engine");
156  if (desiredFormat == null) throw new ArgumentNullException("desiredFormat");
157 
158  audioEngine = engine;
159  soundEffectInstance = effectInstance;
160  waveFormat = desiredFormat;
161  BusIndexMixer = uint.MaxValue;
162 
163  if (desiredFormat.BitsPerSample != 16)
164  throw new AudioSystemInternalException("Invalid Audio Format. " + desiredFormat.BitsPerSample + " bits by sample is not supported.");
165 
166  lock (StaticMembersLock)
167  {
168  if (nbOfInstances == 0)
169  {
170  // Create the Audio Graph
171  audioGraph = new AUGraph();
172 
173  // Open the graph (does not initialize it yet)
174  audioGraph.Open();
175 
176  // Create the AudioComponentDescrition corresponding to the IO Remote output and MultiChannelMixer
177  var remoteInOutComponentDesc = AudioComponentDescription.CreateOutput(AudioTypeOutput.Remote);
178  var mixerMultiChannelComponentDesc = AudioComponentDescription.CreateMixer(AudioTypeMixer.MultiChannel);
179  var mixer3DComponentDesc = AudioComponentDescription.CreateMixer(AudioTypeMixer.Spacial);
180 
181  // Add the Audio Unit nodes to the AudioGraph
182  var outputUnitNodeId = audioGraph.AddNode(remoteInOutComponentDesc);
183  var idChannelMixerNode = audioGraph.AddNode(mixerMultiChannelComponentDesc);
184  var id3DMixerNode = audioGraph.AddNode(mixer3DComponentDesc);
185 
186  // Connect the nodes together
187  CheckGraphError(audioGraph.ConnnectNodeInput(idChannelMixerNode, 0, outputUnitNodeId, 0), "Connection of the graph node failed.");
188  CheckGraphError(audioGraph.ConnnectNodeInput(id3DMixerNode, 0, idChannelMixerNode, MaxNumberOfTracks), "Connection of the graph node failed.");
189 
190  // Get the MixerUnit objects
191  unitChannelMixer = audioGraph.GetNodeInfo(idChannelMixerNode);
192  unit3DMixer = audioGraph.GetNodeInfo(id3DMixerNode);
193 
194  // Set the mixers' output formats (the stream format is propagated along the linked input during the graph initialization)
195  var desiredSampleRate = (engine.AudioSampleRate != 0) ? engine.AudioSampleRate : AudioUnitOutputSampleRate;
196  unit3DMixer.SetAudioFormat(CreateLinear16BitsPcm(2, desiredSampleRate), AudioUnitScopeType.Output);
197  unitChannelMixer.SetAudioFormat(CreateLinear16BitsPcm(2, desiredSampleRate), AudioUnitScopeType.Output);
198 
199  // set the element count to the max number of possible tracks before initializing the audio graph
200  CheckUnitStatus(unitChannelMixer.SetElementCount(AudioUnitScopeType.Input, MaxNumberOfTracks+1), string.Format("Failed to set element count on ChannelMixer [{0}]", MaxNumberOfTracks+1)); // +1 for the 3DMixer output
201  CheckUnitStatus(unit3DMixer.SetElementCount(AudioUnitScopeType.Input, MaxNumberOfTracks), string.Format("Failed to set element count on 3DMixer [{0}]", MaxNumberOfTracks));
202 
203  // set a null renderer callback to the channel and 3d mixer input bus
204  for (uint i = 0; i < MaxNumberOfTracks; i++)
205  {
206  CheckUnitStatus((AudioUnitStatus)SetInputRenderCallbackToNull(unit3DMixer.Handle, i), "Failed to set the render callback");
207  CheckUnitStatus((AudioUnitStatus)SetInputRenderCallbackToNull(unitChannelMixer.Handle, i), "Failed to set the render callback");
208  }
209 
210  // Initialize the graph (validation of the topology)
211  CheckGraphError(audioGraph.Initialize(), "The audio graph initialization failed.");
212 
213  // Start audio rendering
214  CheckGraphError(audioGraph.Start(), "Audio Graph could not start.");
215 
216  // disable all the input bus at the beginning
217  for (uint i = 0; i < MaxNumberOfTracks; i++)
218  {
219  CheckUnitStatus(unitChannelMixer.SetParameter(AudioUnitParameterType.MultiChannelMixerEnable, 0f, AudioUnitScopeType.Input, i), "Failed to enable/disable the ChannelMixerInput.");
220  CheckUnitStatus(unit3DMixer.SetParameter((AudioUnitParameterType)_3DMixerParametersIds.Enable, 0f, AudioUnitScopeType.Input, i), "Failed to enable/disable the 3DMixerInput.");
221  }
222 
223  // At initialization all UnitElement are available.
224  availableMixerBusIndices = new Queue<uint>();
225  for (uint i = 0; i < MaxNumberOfTracks; i++)
226  availableMixerBusIndices.Enqueue(i);
227  }
228  ++nbOfInstances;
229 
230  // Create a AudioDataRendererInfo for the sounds.
231  pAudioDataRendererInfo = (AudioDataRendererInfo*)Utilities.AllocateClearedMemory(sizeof(AudioDataRendererInfo));
232  pAudioDataRendererInfo->HandleChannelMixer = unitChannelMixer.Handle;
233  pAudioDataRendererInfo->Handle3DMixer = unit3DMixer.Handle;
234  }
235  }
236 
237  /// <summary>
238  /// Set the loop points of the AudioVoice.
239  /// </summary>
240  /// <param name="startPoint">The beginning of the loop in frame number.</param>
241  /// <param name="endPoint">The end of the loop in frame number.</param>
242  /// <param name="loopsNumber">The number of times to loop.</param>
243  /// <param name="loopInfinite">Should loop infinitely or not</param>
244  public void SetLoopingPoints(int startPoint, int endPoint, int loopsNumber, bool loopInfinite)
245  {
246  pAudioDataRendererInfo->LoopStartPoint = Math.Max(0, startPoint);
247  pAudioDataRendererInfo->LoopEndPoint = Math.Min(pAudioDataRendererInfo->TotalNumberOfFrames, endPoint);
248 
249  pAudioDataRendererInfo->IsInfiniteLoop = loopInfinite;
250  pAudioDataRendererInfo->NumberOfLoops = Math.Max(0, loopsNumber);
251  }
252 
253  protected override void Destroy()
254  {
255  base.Destroy();
256 
257  Utilities.FreeMemory((IntPtr)pAudioDataRendererInfo);
258 
259  lock (StaticMembersLock)
260  {
261  --nbOfInstances;
262  if (nbOfInstances == 0)
263  {
264  CheckGraphError(audioGraph.Stop(), "The audio Graph failed to stop.");
265  audioGraph.Dispose();
266  }
267  }
268  }
269 
270  public void Play()
271  {
272  pAudioDataRendererInfo->PlaybackEnded = false;
273 
274  EnableMixerCurrentInput(true);
275  }
276 
277  public void Pause()
278  {
279  EnableMixerCurrentInput(false);
280  }
281 
282  public void Stop()
283  {
284  EnableMixerCurrentInput(false);
285  pAudioDataRendererInfo->CurrentFrame = 0;
286 
287  // free the input bus for other sound effects.
288  if (BusIndexMixer != uint.MaxValue)
289  {
290  // reset the mixer callbacks to null renderers
291  CheckUnitStatus((AudioUnitStatus)SetInputRenderCallbackToNull(unit3DMixer.Handle, BusIndexMixer), "Failed to set the render callback");
292  CheckUnitStatus((AudioUnitStatus)SetInputRenderCallbackToNull(unitChannelMixer.Handle, BusIndexMixer), "Failed to set the render callback");
293 
294  availableMixerBusIndices.Enqueue(BusIndexMixer);
295  }
296  }
297 
298  public void SetAudioData(SoundEffect soundEffect)
299  {
300  BusIndexMixer = uint.MaxValue; // invalid index value (when no bus are free)
301 
302  // find a available bus indices for this instance.
303  if (availableMixerBusIndices.Count > 0)
304  BusIndexMixer = availableMixerBusIndices.Dequeue();
305  else
306  {
307  // force the update of all sound effect instance to free bus indices
308  audioEngine.ForceSoundEffectInstanceUpdate();
309 
310  // retry to get an free bus index
311  if (availableMixerBusIndices.Count > 0)
312  {
313  BusIndexMixer = availableMixerBusIndices.Dequeue();
314  }
315  else // try to stop another instance
316  {
317  // try to get a sound effect to stop
318  var soundEffectToStop = audioEngine.GetLeastSignificativeSoundEffect();
319  if (soundEffectToStop == null) // no available sound effect to stop -> give up the creation of the track
320  return;
321 
322  // stop the sound effect instances and retry to get an available track
323  soundEffectToStop.StopAllInstances();
324 
325  // retry to get an free bus index
326  if (availableMixerBusIndices.Count > 0)
327  BusIndexMixer = availableMixerBusIndices.Dequeue();
328  else
329  return;
330  }
331  }
332 
333  // Set the audio stream format of the current mixer input bus.
334  unitChannelMixer.SetAudioFormat(CreateLinear16BitsPcm(waveFormat.Channels, waveFormat.SampleRate), AudioUnitScopeType.Input, BusIndexMixer);
335 
336  // set the channel input bus callback
337  CheckUnitStatus((AudioUnitStatus)SetInputRenderCallbackToChannelMixerDefault(unitChannelMixer.Handle, BusIndexMixer, (IntPtr)pAudioDataRendererInfo), "Failed to set the render callback");
338 
339  ResetChannelMixerParameter();
340 
341  // initialize the 3D mixer input bus, if the sound can be used as 3D sound.
342  if (waveFormat.Channels == 1)
343  {
344  // Set the audio stream format of the current mixer input bus.
345  unit3DMixer.SetAudioFormat(CreateLinear16BitsPcm(waveFormat.Channels, waveFormat.SampleRate), AudioUnitScopeType.Input, BusIndexMixer);
346 
347  // set the 3D mixer input bus callback
348  CheckUnitStatus((AudioUnitStatus)SetInputRenderCallbackTo3DMixerDefault(unit3DMixer.Handle, BusIndexMixer, (IntPtr)pAudioDataRendererInfo), "Failed to set the render callback");
349 
350  Reset3DMixerParameter();
351  }
352 
353  // Disable the input by default so that it started in Stopped mode.
354  EnableMixerCurrentInput(false);
355 
356  // set render info data
357  pAudioDataRendererInfo->AudioDataBuffer = soundEffect.WaveDataPtr;
358  pAudioDataRendererInfo->TotalNumberOfFrames = (soundEffect.WaveDataSize / waveFormat.BlockAlign);
359  pAudioDataRendererInfo->NumberOfChannels = waveFormat.Channels;
360 
361  // reset playback to the beginning of the track and set the looping status
362  pAudioDataRendererInfo->CurrentFrame = 0;
363  SetLoopingPoints(0, int.MaxValue, 0, pAudioDataRendererInfo->IsInfiniteLoop);
364  SetVolume(soundEffectInstance.Volume);
365  SetPan(soundEffectInstance.Pan);
366  }
367 
368  public void SetVolume(float volume)
369  {
370  if (BusIndexMixer == uint.MaxValue)
371  return;
372 
373  if (Is3D)
374  {
375  var gain = Math.Max(-120f, (float) (20*Math.Log10(volume)));
376  CheckUnitStatus(unit3DMixer.SetParameter((AudioUnitParameterType)_3DMixerParametersIds.Gain, gain, AudioUnitScopeType.Input, BusIndexMixer), "Failed to set the Gain parameter of the 3D mixer");
377  }
378  else
379  {
380  CheckUnitStatus(unitChannelMixer.SetParameter(AudioUnitParameterType.MultiChannelMixerVolume, volume, AudioUnitScopeType.Input, BusIndexMixer), "Failed to set the mixer bus Volume parameter.");
381  }
382  }
383 
384  public void SetPan(float pan)
385  {
386  if (BusIndexMixer == uint.MaxValue)
387  return;
388 
389  Is3D = false;
390  CheckUnitStatus(unitChannelMixer.SetParameter(AudioUnitParameterType.MultiChannelMixerPan, pan, AudioUnitScopeType.Input, BusIndexMixer), "Failed to set the mixer bus Pan parameter.");
391  }
392 
393  private void Set3DParameters(float azimuth, float elevation, float distance, float playRate)
394  {
395  if (BusIndexMixer == uint.MaxValue)
396  return;
397 
398  CheckUnitStatus(unit3DMixer.SetParameter((AudioUnitParameterType)_3DMixerParametersIds.Azimuth, azimuth, AudioUnitScopeType.Input, BusIndexMixer), "Failed to set the Azimuth parameter of the 3D mixer");
399  CheckUnitStatus(unit3DMixer.SetParameter((AudioUnitParameterType)_3DMixerParametersIds.Elevation, elevation, AudioUnitScopeType.Input, BusIndexMixer), "Failed to set the Elevation parameter of the 3D mixer");
400  CheckUnitStatus(unit3DMixer.SetParameter((AudioUnitParameterType)_3DMixerParametersIds.Distance, distance, AudioUnitScopeType.Input, BusIndexMixer), "Failed to set the Distance parameter of the 3D mixer");
401  CheckUnitStatus(unit3DMixer.SetParameter((AudioUnitParameterType)_3DMixerParametersIds.PlaybackRate, playRate, AudioUnitScopeType.Input, BusIndexMixer), "Failed to set the PlayRate parameter of the 3D mixer");
402  }
403 
404  public void Apply3D(float azimut, float elevation, float distance, float playRate)
405  {
406  Set3DParameters(azimut, elevation, distance, playRate);
407  Is3D = true;
408  }
409 
410  public void Reset3D()
411  {
412  if (Is3D)
413  {
414  Set3DParameters(0, 0, 0, 1);
415  Is3D = false;
416  }
417  }
418 
419  private void Reset3DMixerParameter()
420  {
421  if (BusIndexMixer == uint.MaxValue)
422  return;
423 
424  Set3DParameters(0, 0, 0, 1);
425  CheckUnitStatus(unit3DMixer.SetParameter((AudioUnitParameterType)_3DMixerParametersIds.Gain, 0, AudioUnitScopeType.Input, BusIndexMixer), "Failed to set the Gain parameter of the 3D mixer");
426  }
427 
428  private void ResetChannelMixerParameter()
429  {
430  if (BusIndexMixer == uint.MaxValue)
431  return;
432 
433  CheckUnitStatus(unitChannelMixer.SetParameter(AudioUnitParameterType.MultiChannelMixerVolume, 1, AudioUnitScopeType.Input, BusIndexMixer), "Failed to set the mixer bus Volume parameter.");
434  CheckUnitStatus(unitChannelMixer.SetParameter(AudioUnitParameterType.MultiChannelMixerPan, 0, AudioUnitScopeType.Input, BusIndexMixer), "Failed to set the mixer bus Pan parameter.");
435  }
436 
437  [DebuggerDisplay("AudioDataMixer for input bus {parent.BusIndexChannelMixer}-{parent.BusIndex3DMixer}")]
438  [StructLayout(LayoutKind.Sequential, Pack = 4)]
439  struct AudioDataRendererInfo
440  {
441  public int LoopStartPoint;
442  public int LoopEndPoint;
443  public int NumberOfLoops;
444  public bool IsInfiniteLoop;
445 
446  public int CurrentFrame;
447  public int TotalNumberOfFrames;
448 
449  public int NumberOfChannels;
450  public IntPtr AudioDataBuffer;
451 
452  public bool IsEnabled2D;
453  public bool IsEnabled3D;
454 
455  public bool PlaybackEnded;
456 
457  public IntPtr HandleChannelMixer;
458  public IntPtr Handle3DMixer;
459  }
460  }
461 }
462 
463 #endif
ComponentBase.Destroy() event.
SiliconStudio.Core.Utilities Utilities
Definition: Texture.cs:29
Output message to log right away.