Paradox Game Engine  v1.0.0 beta06
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Events Macros Pages
AudioEngine.Windows.Desktop.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 #if SILICONSTUDIO_PLATFORM_WINDOWS_DESKTOP
4 using System;
5 using System.Linq;
6 using System.Runtime.InteropServices;
7 using System.Threading;
8 
9 using SharpDX;
10 using SharpDX.MediaFoundation;
11 using SharpDX.Win32;
12 
13 using SiliconStudio.Core;
14 
15 namespace SiliconStudio.Paradox.Audio
16 {
17 
18  // We use MediaFoundation.MediaSession on windows desktop to play SoundMusics.
19  // The class has an internal thread to process MediaSessionEvents.
20  public partial class AudioEngine
21  {
22  /// <summary>
23  /// This method is called during engine construction to initialize Windows.Desktop specific components.
24  /// </summary>
25  /// <remarks>Variables do not need to be locked here since there are no concurrent calls before the end of the construction.</remarks>
26  private void PlatformSpecificInit()
27  {
28  // get the GUID of the AudioStreamVolume interface from Class Attributes.
29  if (streamAudioVolumeGuid == Guid.Empty)
30  streamAudioVolumeGuid = Guid.Parse(Attribute.GetCustomAttributes(typeof(AudioStreamVolume)).OfType<GuidAttribute>().First().Value);
31  }
32 
33  private void PlatformSpecificDispose()
34  {
35  if (mediaSession != null)
36  {
37  mediaSession.Close();
38 
39  var timeOut = 2000;
40  while (mediaSession != null && timeOut > 0)
41  {
42  ProccessQueuedMediaSessionEvents();
43 
44  const int sleepTime = 5;
45  Thread.Sleep(sleepTime);
46  timeOut -= sleepTime;
47  }
48 
49  if (timeOut < 0)
50  throw new AudioSystemInternalException("Audio Engine failed to dispose (Event SessionClose did not came after 2 seconds).");
51  }
52  }
53 
54  /// <summary>
55  /// Guid to the StreamAudioVolume interface.
56  /// </summary>
57  private Guid streamAudioVolumeGuid = Guid.Empty;
58 
59  /// <summary>
60  /// The Media session used to play the SoundMusic.
61  /// </summary>
62  private MediaSession mediaSession;
63 
64  /// <summary>
65  /// AudioStreamVolume interface object used to control the SoundMusic Volume.
66  /// </summary>
67  /// <remarks>
68  /// <para>The SimpleAudioVolume interface can not be used here because it modifies the volume of the whole application (XAudio2 included).</para>
69  /// <para>streamVolume is valid only when the media session topology is ready. streamVolume is set to null when not valid.</para>
70  /// </remarks>
71  private AudioStreamVolume streamVolume;
72 
73  /// <summary>
74  /// The MediaSource used in the current topology. We need to keep a pointer on it to call shutdown when destroying to MediaSession.
75  /// </summary>
76  private MediaSource mediaSource;
77 
78  /// <summary>
79  /// The topology of the media session. Need to be disposed after the Session Closed.
80  /// </summary>
81  private Topology topology;
82 
83  /// <summary>
84  /// The media session callback that process the event. Need to be disposed every time the Session is destroyed.
85  /// </summary>
86  private MediaSessionCallback mediaSessionCallback;
87 
88  /// <summary>
89  /// The ByteStream associated with .
90  /// </summary>
91  private ByteStream mediaInputByteStream;
92 
93  /// <summary>
94  /// Create a topology to be played with a MediaSession from a filepath.
95  /// </summary>
96  internal static Topology CreateTopology(ByteStream mediaInputStream, out MediaSource mediaSource)
97  {
98  // collector to dispose all the created Media Foundation native objects.
99  var collector = new ObjectCollector();
100 
101  // Get the MediaSource object.
102  var sourceResolver = new SourceResolver();
103  collector.Add(sourceResolver);
104  ComObject mediaSourceObject;
105 
106  // Try to load music
107  try
108  {
109  mediaSourceObject = sourceResolver.CreateObjectFromStream(mediaInputStream, null, SourceResolverFlags.MediaSource | SourceResolverFlags.ContentDoesNotHaveToMatchExtensionOrMimeType);
110  }
111  catch (SharpDXException)
112  {
113  collector.Dispose();
114  throw new InvalidOperationException("Music stream format not supported");
115  }
116 
117  Topology retTopo;
118 
119  try
120  {
121  mediaSource = mediaSourceObject.QueryInterface<MediaSource>();
122  collector.Add(mediaSourceObject);
123 
124  // Get the PresentationDescriptor
125  PresentationDescriptor presDesc;
126  mediaSource.CreatePresentationDescriptor(out presDesc);
127  collector.Add(presDesc);
128 
129  // Create the topology
130  MediaFactory.CreateTopology(out retTopo);
131  for (var i = 0; i < presDesc.StreamDescriptorCount; i++)
132  {
133  Bool selected;
134  StreamDescriptor desc;
135  presDesc.GetStreamDescriptorByIndex(i, out selected, out desc);
136  collector.Add(desc);
137 
138  if (selected)
139  {
140  // Test that the audio file data is valid and supported.
141  var typeHandler = desc.MediaTypeHandler;
142  collector.Add(typeHandler);
143 
144  var majorType = typeHandler.MajorType;
145 
146  if (majorType != MediaTypeGuids.Audio)
147  throw new InvalidOperationException("The music stream is not a valid audio stream.");
148 
149  for (int mType = 0; mType < typeHandler.MediaTypeCount; mType++)
150  {
151  MediaType type;
152  typeHandler.GetMediaTypeByIndex(mType, out type);
153  collector.Add(type);
154 
155  var nbChannels = type.Get(MediaTypeAttributeKeys.AudioNumChannels);
156  if (nbChannels > 2)
157  throw new InvalidOperationException("The provided audio stream has more than 2 channels.");
158  }
159 
160  // create the topology (source,...)
161  TopologyNode sourceNode;
162  MediaFactory.CreateTopologyNode(TopologyType.SourceStreamNode, out sourceNode);
163  collector.Add(sourceNode);
164 
165  sourceNode.Set(TopologyNodeAttributeKeys.Source, mediaSource);
166  sourceNode.Set(TopologyNodeAttributeKeys.PresentationDescriptor, presDesc);
167  sourceNode.Set(TopologyNodeAttributeKeys.StreamDescriptor, desc);
168 
169  TopologyNode outputNode;
170  MediaFactory.CreateTopologyNode(TopologyType.OutputNode, out outputNode);
171  collector.Add(outputNode);
172 
173  Activate activate;
174  MediaFactory.CreateAudioRendererActivate(out activate);
175  collector.Add(activate);
176  outputNode.Object = activate;
177 
178  retTopo.AddNode(sourceNode);
179  retTopo.AddNode(outputNode);
180  sourceNode.ConnectOutput(0, outputNode, 0);
181  }
182  }
183  }
184  finally
185  {
186  collector.Dispose();
187  }
188 
189  return retTopo;
190  }
191 
192  /// <summary>
193  /// Load a new music into the media session. That is create a new session and a new topology and set the topology of the session.
194  /// </summary>
195  /// <param name="music"></param>
196  private void LoadNewMusic(SoundMusic music)
197  {
198  if (currentMusic != null || mediaSession != null)
199  throw new AudioSystemInternalException("State of the audio engine invalid at the entry of LoadNewMusic.");
200 
201  music.Stream.Position = 0;
202  mediaInputByteStream = new ByteStream(music.Stream);
203  topology = CreateTopology(mediaInputByteStream, out mediaSource);
204  MediaFactory.CreateMediaSession(null, out mediaSession);
205  mediaSessionCallback = new MediaSessionCallback(mediaSession, OnMediaSessionEvent);
206  mediaSession.SetTopology(SessionSetTopologyFlags.None, topology);
207 
208  currentMusic = music;
209  }
210 
211  private void OnMediaSessionEvent(MediaEvent mEvent)
212  {
213  var type = mEvent.TypeInfo;
214  //Console.WriteLine("MediaEvent {0}", type);
215  //Console.Out.Flush();
216 
217  switch (type)
218  {
219  case MediaEventTypes.SessionClosed:
220  musicMediaEvents.Enqueue(new SoundMusicEventNotification(SoundMusicEvent.MusicPlayerClosed, mEvent));
221  break;
222  case MediaEventTypes.SessionEnded:
223  musicMediaEvents.Enqueue(new SoundMusicEventNotification(SoundMusicEvent.EndOfTrackReached, mEvent));
224  break;
225  case MediaEventTypes.SessionTopologyStatus:
226  if (mEvent.Get(EventAttributeKeys.TopologyStatus) == TopologyStatus.Ready)
227  musicMediaEvents.Enqueue(new SoundMusicEventNotification(SoundMusicEvent.ReadyToBePlayed, mEvent));
228  break;
229  case MediaEventTypes.Error:
230  break;
231  case MediaEventTypes.SourceMetadataChanged:
232  break;
233  }
234  }
235 
236  private void UpdateMusicVolume()
237  {
238  // volume factor used in order to adjust Sound Music and Sound Effect Maximum volumes
239  const float volumeAdjustFactor = 0.5f;
240 
241  if (streamVolume != null)
242  {
243  var vol = volumeAdjustFactor * currentMusic.Volume;
244  streamVolume.SetAllVolumes(2, new[] { vol, vol });
245  }
246  }
247 
248  private void PauseCurrentMusic()
249  {
250  mediaSession.Pause();
251  }
252 
253  private void StopCurrentMusic()
254  {
255  mediaSession.Stop();
256  }
257 
258  private void StartCurrentMusic()
259  {
260  mediaSession.Start(null, new Variant ());
261  }
262 
263  private void ResetMusicPlayer()
264  {
265  mediaSession.Close();
266  }
267 
268  private void RestartCurrentMusic()
269  {
270  mediaSession.Start(null, new Variant { ElementType = VariantElementType.Long, Type = VariantType.Default, Value = (long)0 });
271  }
272 
273  private void PlatformSpecificProcessMusicReady()
274  {
275  // The topology of the MediaSession is ready.
276 
277  if (!currentMusic.IsDisposed) // disposal of the music can happen between the call to Play and its ready-to-play state notification
278  {
279  // Query the Volume interface associated to the new topology
280  IntPtr volumeObj;
281  MediaFactory.GetService(mediaSession, MediaServiceKeys.StreamVolume, streamAudioVolumeGuid, out volumeObj);
282  streamVolume = new AudioStreamVolume(volumeObj);
283  }
284  }
285 
286  private void ProcessMusicError(SoundMusicEventNotification eventNotification)
287  {
288  var mediaEvent = (MediaEvent)eventNotification.EventData;
289 
290  var status = mediaEvent.Status;
291  if (status.Failure)
292  {
293  mediaEvent.Dispose();
294  status.CheckError();
295  }
296  }
297 
298  private void ProcessMusicMetaData()
299  {
300  throw new NotImplementedException();
301  }
302 
303  private void ProcessPlayerClosed()
304  {
305  // The session finished to close, we have to dispose all related object.
306  currentMusic = null;
307 
308  mediaSessionCallback.Dispose();
309 
310  if (mediaSource != null) mediaSource.Shutdown();
311  if (mediaSession != null) mediaSession.Shutdown();
312 
313  if (streamVolume != null) streamVolume.Dispose();
314  if (mediaSource != null) mediaSource.Dispose();
315  if (topology != null) topology.Dispose();
316  if (mediaSession != null) mediaSession.Dispose();
317  if (mediaInputByteStream != null) mediaInputByteStream.Dispose();
318 
319  topology = null;
320  streamVolume = null;
321  mediaSession = null;
322  mediaSource = null;
323  mediaInputByteStream = null;
324  mediaSessionCallback = null;
325  isMusicPlayerReady = false;
326  }
327  }
328 }
329 
330 #endif
The type of the serialized type will be passed as a generic arguments of the serializer. Example: serializer of A becomes instantiated as Serializer{A}.