Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Made sound system optional #6629

Merged
merged 6 commits into from Feb 10, 2019
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 4 additions & 0 deletions MonoGame.Framework/Audio/DynamicSoundEffectInstance.cs
Expand Up @@ -76,6 +76,10 @@ public int PendingBufferCount
/// <param name="channels">Number of channels (mono or stereo).</param>
public DynamicSoundEffectInstance(int sampleRate, AudioChannels channels)
{
SoundEffect.Initialize();
if (SoundEffect._systemState != SoundEffect.SoundSystemState.Initialized)
throw new NoAudioHardwareException("Audio has failed to initialize. Call SoundEffect.Initialize() before sound operation to get more specific errors.");

if ((sampleRate < 8000) || (sampleRate > 48000))
throw new ArgumentOutOfRangeException("sampleRate");
if ((channels != AudioChannels.Mono) && (channels != AudioChannels.Stereo))
Expand Down
3 changes: 2 additions & 1 deletion MonoGame.Framework/Audio/Microphone.cs
Expand Up @@ -115,6 +115,7 @@ public static ReadOnlyCollection<Microphone> All
{
get
{
SoundEffect.Initialize();
if (_allMicrophones == null)
_allMicrophones = new List<Microphone>();
return new ReadOnlyCollection<Microphone>(_allMicrophones);
Expand All @@ -128,7 +129,7 @@ public static ReadOnlyCollection<Microphone> All
/// </summary>
public static Microphone Default
{
get { return _default; }
get { SoundEffect.Initialize(); return _default; }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Write code like this in the static initializer instead of static property.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just so there is no confusion, ie:

static Microphone()
{
    SoundEffect.Initialize();
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could also use static constructors in few other places.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's better to check if initialization was done properly on every call, rather than do it only once. It makes behavior more predictable. Static initializers complicate control flow because you can't be sure when they'll be called.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They get called during first access to their respective class.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The thing is, Default and ```All`` are the microphone entry points, but you have to initialize the sound API to populate the microphones, and the Microphone class is static by design...
Unless we change the design of the Microphone class, I don't think we can do anything else than having this in a static field. I'm not a fan of static classes because this kind of design problems.

Alternative proposition: not initializing the sound API at all in the Microphone class, and throw if All/Default are accessed while not initialized. Forcing users to either call SoundEffect.Initialize() or to use the auto initialization when loading content.

I've seen static classes initializing without obvious calls to them, so I'd feel more comfortable with not having any library loading happening inside static ctor/fields.

For the same reason, I don't quite like OpenAL.NativeLibrary being initialized statically. I think I'm going to change that too.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should not change the API by forcing explicit initialization. If we could have a fresh API, I'd definitely want to make it explicit though.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about just checking if Audio systems can be initialized on game run?

For the same reason, I don't quite like OpenAL.NativeLibrary being initialized statically. I think I'm going to change that too.

Please don't, those are just wrappers. Think of them as DllImport statements :P

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, but they do load the dll if the static ctor kicks in, which we want to avoid to not mess up with third party sound engine.

But if we can't get the sound api to initialize in a non static way for Microphone, there's no point in removing the static initialization from OpenAL anyway because it'll still be around within Microphone.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am more suspicious of Microphone than I am of OpenAL. I'd prefer to not add a static constructor to Microphone.

If we'd like to be 100% safe, I'd suggest to move static initialization out of OpenAL func loading and manually load them (to have a perfect control on the "when" and "how").

}

#endregion
Expand Down
4 changes: 2 additions & 2 deletions MonoGame.Framework/Audio/OALSoundBuffer.cs
Expand Up @@ -41,9 +41,9 @@ public double Duration

public void BindDataBuffer(byte[] dataBuffer, ALFormat format, int size, int sampleRate, int sampleAlignment = 0)
{
if ((format == ALFormat.MonoMSAdpcm || format == ALFormat.StereoMSAdpcm) && !OpenALSoundController.GetInstance.SupportsAdpcm)
if ((format == ALFormat.MonoMSAdpcm || format == ALFormat.StereoMSAdpcm) && !OpenALSoundController.Instance.SupportsAdpcm)
throw new InvalidOperationException("MS-ADPCM is not supported by this OpenAL driver");
if ((format == ALFormat.MonoIma4 || format == ALFormat.StereoIma4) && !OpenALSoundController.GetInstance.SupportsIma4)
if ((format == ALFormat.MonoIma4 || format == ALFormat.StereoIma4) && !OpenALSoundController.Instance.SupportsIma4)
throw new InvalidOperationException("IMA/ADPCM is not supported by this OpenAL driver");

openALFormat = format;
Expand Down
4 changes: 2 additions & 2 deletions MonoGame.Framework/Audio/OggStream.cs
Expand Up @@ -45,7 +45,7 @@ public OggStream(string filename, Action finishedAction = null, int bufferCount

alBufferIds = AL.GenBuffers(bufferCount);
ALHelper.CheckError("Failed to generate buffers.");
alSourceId = OpenALSoundController.GetInstance.ReserveSource();
alSourceId = OpenALSoundController.Instance.ReserveSource();

if (OggStreamer.Instance.XRam.IsInitialized)
{
Expand Down Expand Up @@ -238,7 +238,7 @@ public void Dispose()

AL.Source(alSourceId, ALSourcei.Buffer, 0);
ALHelper.CheckError("Failed to free source from buffers.");
OpenALSoundController.GetInstance.RecycleSource(alSourceId);
OpenALSoundController.Instance.RecycleSource(alSourceId);
AL.DeleteBuffers(alBufferIds);
ALHelper.CheckError("Failed to delete buffer.");
if (OggStreamer.Instance.Efx.IsInitialized)
Expand Down
37 changes: 30 additions & 7 deletions MonoGame.Framework/Audio/OpenALSoundController.cs
Expand Up @@ -108,6 +108,9 @@ internal sealed class OpenALSoundController : IDisposable
/// </summary>
private OpenALSoundController()
{
if (AL.NativeLibrary == IntPtr.Zero)
throw new DllNotFoundException("Couldn't initialize OpenAL because the native binaries couldn't be found.");

if (!OpenSoundController())
{
throw new NoAudioHardwareException("OpenAL device could not be initialized, see console output for details.");
Expand Down Expand Up @@ -149,10 +152,6 @@ private bool OpenSoundController()
_device = Alc.OpenDevice(string.Empty);
EffectsExtension.device = _device;
}
catch (DllNotFoundException ex)
{
throw ex;
}
catch (Exception ex)
{
throw new NoAudioHardwareException("OpenAL device could not be initialized.", ex);
Expand Down Expand Up @@ -287,12 +286,36 @@ private bool OpenSoundController()
return false;
}

public static OpenALSoundController GetInstance
public static void EnsureInitialized()
{
if (_instance == null)
{
try
{
_instance = new OpenALSoundController();
}
catch (DllNotFoundException)
{
throw;
}
catch (NoAudioHardwareException)
{
throw;
}
catch (Exception ex)
{
throw (new NoAudioHardwareException("Failed to init OpenALSoundController", ex));
}
}
}


public static OpenALSoundController Instance
{
get
{
if (_instance == null)
_instance = new OpenALSoundController();
if (_instance == null)
throw new NoAudioHardwareException("OpenAL context has failed to initialize. Call SoundEffect.Initialize() before sound operation to get more specific errors.");
return _instance;
}
}
Expand Down
18 changes: 5 additions & 13 deletions MonoGame.Framework/Audio/SoundEffect.OpenAL.cs
Expand Up @@ -63,7 +63,7 @@ private void PlatformInitializePcm(byte[] buffer, int offset, int count, int sam

private void PlatformInitializeIeeeFloat(byte[] buffer, int offset, int count, int sampleRate, AudioChannels channels, int loopStart, int loopLength)
{
if (!OpenALSoundController.GetInstance.SupportsIeee)
if (!OpenALSoundController.Instance.SupportsIeee)
{
// If 32-bit IEEE float is not supported, convert to 16-bit signed PCM
buffer = AudioLoader.ConvertFloatTo16(buffer, offset, count);
Expand All @@ -80,7 +80,7 @@ private void PlatformInitializeIeeeFloat(byte[] buffer, int offset, int count, i

private void PlatformInitializeAdpcm(byte[] buffer, int offset, int count, int sampleRate, AudioChannels channels, int blockAlignment, int loopStart, int loopLength)
{
if (!OpenALSoundController.GetInstance.SupportsAdpcm)
if (!OpenALSoundController.Instance.SupportsAdpcm)
{
// If MS-ADPCM is not supported, convert to 16-bit signed PCM
buffer = AudioLoader.ConvertMsAdpcmToPcm(buffer, offset, count, (int)channels, blockAlignment);
Expand All @@ -100,7 +100,7 @@ private void PlatformInitializeAdpcm(byte[] buffer, int offset, int count, int s

private void PlatformInitializeIma4(byte[] buffer, int offset, int count, int sampleRate, AudioChannels channels, int blockAlignment, int loopStart, int loopLength)
{
if (!OpenALSoundController.GetInstance.SupportsIma4)
if (!OpenALSoundController.Instance.SupportsIma4)
{
// If IMA/ADPCM is not supported, convert to 16-bit signed PCM
buffer = AudioLoader.ConvertIma4ToPcm(buffer, offset, count, (int)channels, blockAlignment);
Expand Down Expand Up @@ -235,17 +235,9 @@ private void PlatformDispose(bool disposing)

#endregion

internal static void InitializeSoundEffect()
internal static void PlatformInitialize()
{
try
{
// Getting the instance for the first time initializes OpenAL
var oal = OpenALSoundController.GetInstance;
}
catch (DllNotFoundException ex)
{
throw new NoAudioHardwareException("Failed to init OpenALSoundController", ex);
}
OpenALSoundController.EnsureInitialized();
Jjagg marked this conversation as resolved.
Show resolved Hide resolved
}

internal static void PlatformShutdown()
Expand Down
2 changes: 1 addition & 1 deletion MonoGame.Framework/Audio/SoundEffect.Web.cs
Expand Up @@ -47,7 +47,7 @@ internal static void PlatformSetReverbSettings(ReverbSettings reverbSettings)
{
}

internal static void InitializeSoundEffect()
internal static void PlatformInitialize()
{
}

Expand Down
2 changes: 1 addition & 1 deletion MonoGame.Framework/Audio/SoundEffect.XAudio.cs
Expand Up @@ -91,7 +91,7 @@ internal static SubmixVoice ReverbVoice
/// <summary>
/// Initializes XAudio.
/// </summary>
internal static void InitializeSoundEffect()
internal static void PlatformInitialize()
{
try
{
Expand Down
69 changes: 60 additions & 9 deletions MonoGame.Framework/Audio/SoundEffect.cs
Expand Up @@ -6,7 +6,7 @@
using System.IO;

namespace Microsoft.Xna.Framework.Audio
{
{
/// <summary>Represents a loaded sound resource.</summary>
/// <remarks>
/// <para>A SoundEffect represents the buffer used to hold audio data and metadata. SoundEffectInstances are used to play from SoundEffects. Multiple SoundEffectInstance objects can be created and played from the same SoundEffect object.</para>
Expand All @@ -28,7 +28,11 @@ public sealed partial class SoundEffect : IDisposable

// Only used from SoundEffect.FromStream.
private SoundEffect(Stream stream)
{
{
Initialize();
if (_systemState != SoundSystemState.Initialized)
throw new NoAudioHardwareException("Audio has failed to initialize. Call SoundEffect.Initialize() before sound operation to get more specific errors.");

/*
The Stream object must point to the head of a valid PCM wave file. Also, this wave file must be in the RIFF bitstream format.
The audio format has the following restrictions:
Expand All @@ -43,7 +47,11 @@ private SoundEffect(Stream stream)

// Only used from SoundEffectReader.
internal SoundEffect(byte[] header, byte[] buffer, int bufferSize, int durationMs, int loopStart, int loopLength)
{
{
Initialize();
if (_systemState != SoundSystemState.Initialized)
throw new NoAudioHardwareException("Audio has failed to initialize. Call SoundEffect.Initialize() before sound operation to get more specific errors.");

_duration = TimeSpan.FromMilliseconds(durationMs);

// Peek at the format... handle regular PCM data.
Expand All @@ -63,7 +71,11 @@ internal SoundEffect(byte[] header, byte[] buffer, int bufferSize, int durationM

// Only used from XACT WaveBank.
internal SoundEffect(MiniFormatTag codec, byte[] buffer, int channels, int sampleRate, int blockAlignment, int loopStart, int loopLength)
{
{
Initialize();
if (_systemState != SoundSystemState.Initialized)
mrhelmut marked this conversation as resolved.
Show resolved Hide resolved
throw new NoAudioHardwareException("Audio has failed to initialize. Call SoundEffect.Initialize() before sound operation to get more specific errors.");

// Handle the common case... the rest is platform specific.
if (codec == MiniFormatTag.Pcm)
{
Expand All @@ -73,12 +85,47 @@ internal SoundEffect(MiniFormatTag codec, byte[] buffer, int channels, int sampl
}

PlatformInitializeXact(codec, buffer, channels, sampleRate, blockAlignment, loopStart, loopLength, out _duration);
}

}

#endregion

#region Audio System Initialization

internal enum SoundSystemState
{
NotInitialized,
Initialized,
FailedToInitialized
}

internal static SoundSystemState _systemState = SoundSystemState.NotInitialized;

/// <summary>
/// Initializes the sound system for SoundEffect support.
/// This method is automatically called when a SoundEffect is loaded, a DynamicSoundEffectInstance is created, or Microphone.All is queried.
/// You can however call this method manually (preferably in, or before the Game constructor) to catch any Exception that may occur during the sound system initialization (and act accordingly).
/// </summary>
public static void Initialize()
{
if (_systemState != SoundSystemState.NotInitialized)
return;

try
{
PlatformInitialize();
_systemState = SoundSystemState.Initialized;
}
catch (Exception)
{
_systemState = SoundSystemState.FailedToInitialized;
throw;
}
}

#endregion

#region Public Constructors

/// <summary>
/// Create a sound effect.
/// </summary>
Expand All @@ -103,7 +150,11 @@ public SoundEffect(byte[] buffer, int sampleRate, AudioChannels channels)
/// <param name="loopLength">The duration of the sound data loop in samples.</param>
/// <remarks>This only supports uncompressed 16bit PCM wav data.</remarks>
public SoundEffect(byte[] buffer, int offset, int count, int sampleRate, AudioChannels channels, int loopStart, int loopLength)
{
{
Initialize();
if (_systemState != SoundSystemState.Initialized)
throw new NoAudioHardwareException("Audio has failed to initialize. Call SoundEffect.Initialize() before sound operation to get more specific errors.");

if (sampleRate < 8000 || sampleRate > 48000)
throw new ArgumentOutOfRangeException("sampleRate");
if ((int)channels != 1 && (int)channels != 2)
Expand Down
4 changes: 2 additions & 2 deletions MonoGame.Framework/Audio/SoundEffectInstance.OpenAL.cs
Expand Up @@ -41,7 +41,7 @@ internal void PlatformInitialize(byte[] buffer, int sampleRate, int channels)
/// </summary>
internal void InitializeSound()
{
controller = OpenALSoundController.GetInstance;
controller = OpenALSoundController.Instance;
}

#endregion // Initialization
Expand Down Expand Up @@ -174,7 +174,7 @@ private void PlatformStop(bool immediate)

// Reset the SendFilter to 0 if we are NOT using reverb since
// sources are recycled
if (OpenALSoundController.GetInstance.SupportsEfx)
if (OpenALSoundController.Instance.SupportsEfx)
{
OpenALSoundController.Efx.BindSourceToAuxiliarySlot(SourceId, 0, 0, 0);
ALHelper.CheckError("Failed to unset reverb.");
Expand Down
3 changes: 0 additions & 3 deletions MonoGame.Framework/FrameworkDispatcher.cs
Expand Up @@ -38,9 +38,6 @@ private static void DoUpdate()

private static void Initialize()
{
// Initialize sound system
SoundEffect.InitializeSoundEffect();

_initialized = true;
}
}
Expand Down
5 changes: 3 additions & 2 deletions MonoGame.Framework/Game.cs
Expand Up @@ -139,8 +139,9 @@ protected virtual void Dispose(bool disposing)
}

ContentTypeReaderManager.ClearTypeCreators();

SoundEffect.PlatformShutdown();

if (SoundEffect._systemState == SoundEffect.SoundSystemState.Initialized)
SoundEffect.PlatformShutdown();
}
#if ANDROID
Activity = null;
Expand Down
3 changes: 3 additions & 0 deletions MonoGame.Framework/Media/Song.NVorbis.cs
Expand Up @@ -16,6 +16,9 @@ public sealed partial class Song : IEquatable<Song>, IDisposable

private void PlatformInitialize(string fileName)
{
// init OpenAL if need be
OpenALSoundController.EnsureInitialized();

stream = new OggStream(fileName, OnFinishedPlaying);
stream.Prepare();

Expand Down
11 changes: 0 additions & 11 deletions MonoGame.Framework/SDL/SDLGamePlatform.cs
Expand Up @@ -7,7 +7,6 @@
using System.Diagnostics;
using System.Threading;
using System.Runtime.InteropServices;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using MonoGame.Utilities;
Expand All @@ -22,7 +21,6 @@ public override GameRunBehavior DefaultRunBehavior
}

private readonly Game _game;
private readonly OpenALSoundController _soundControllerInstance;
private readonly List<Keys> _keys;

private int _isExiting;
Expand Down Expand Up @@ -62,15 +60,6 @@ public SdlGamePlatform(Game game)

GamePad.InitDatabase();
Window = _view = new SdlGameWindow(_game);

try
{
_soundControllerInstance = OpenALSoundController.GetInstance;
}
catch (DllNotFoundException ex)
{
throw (new NoAudioHardwareException("Failed to init OpenALSoundController", ex));
}
}

public override void BeforeInitialize ()
Expand Down