Add OpenAL sound output.
XAudio2: Some stuff I forgot to dispose.
This commit is contained in:
parent
822d42048e
commit
c56edd6e93
BizHawk.Client.Common/config
BizHawk.Client.EmuHawk
|
@ -111,7 +111,7 @@ namespace BizHawk.Client.Common
|
|||
|
||||
public enum EDispMethod { OpenGL, GdiPlus, SlimDX9 };
|
||||
|
||||
public enum ESoundOutputMethod { DirectSound, XAudio2, Dummy };
|
||||
public enum ESoundOutputMethod { DirectSound, XAudio2, OpenAL, Dummy };
|
||||
|
||||
public enum EDispManagerAR { None, System, Custom };
|
||||
|
||||
|
|
|
@ -625,6 +625,7 @@
|
|||
</Compile>
|
||||
<Compile Include="Sound\Output\DirectSoundSoundOutput.cs" />
|
||||
<Compile Include="Sound\Output\DummySoundOutput.cs" />
|
||||
<Compile Include="Sound\Output\OpenALSoundOutput.cs" />
|
||||
<Compile Include="Sound\Output\XAudio2SoundOutput.cs" />
|
||||
<Compile Include="Sound\Sound.cs" />
|
||||
<Compile Include="Sound\Utilities\SoundOutputProvider.cs" />
|
||||
|
|
|
@ -276,6 +276,9 @@ namespace BizHawk.Client.EmuHawk
|
|||
Global.ActiveController = Global.NullControls;
|
||||
Global.AutoFireController = Global.AutofireNullControls;
|
||||
Global.AutofireStickyXORAdapter.SetOnOffPatternFromConfig();
|
||||
#if !WINDOWS
|
||||
Global.Config.SoundOutputMethod = Config.ESoundOutputMethod.OpenAL;
|
||||
#endif
|
||||
try { GlobalWin.Sound = new Sound(Handle); }
|
||||
catch
|
||||
{
|
||||
|
|
|
@ -0,0 +1,177 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
using OpenTK.Audio;
|
||||
using OpenTK.Audio.OpenAL;
|
||||
|
||||
using BizHawk.Client.Common;
|
||||
|
||||
namespace BizHawk.Client.EmuHawk
|
||||
{
|
||||
public class OpenALSoundOutput : ISoundOutput
|
||||
{
|
||||
private bool _disposed;
|
||||
private Sound _sound;
|
||||
private AudioContext _context;
|
||||
private int _sourceID;
|
||||
private BufferPool _bufferPool;
|
||||
private long _runningSamplesPlayed;
|
||||
private long _runningSamplesQueued;
|
||||
|
||||
public OpenALSoundOutput(Sound sound)
|
||||
{
|
||||
_sound = sound;
|
||||
_context = new AudioContext();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed) return;
|
||||
|
||||
_context.Dispose();
|
||||
_context = null;
|
||||
|
||||
_disposed = true;
|
||||
}
|
||||
|
||||
public static IEnumerable<string> GetDeviceNames()
|
||||
{
|
||||
return Enumerable.Empty<string>();
|
||||
}
|
||||
|
||||
private int BufferSizeSamples { get; set; }
|
||||
|
||||
public int MaxSamplesDeficit { get; private set; }
|
||||
|
||||
public void ApplyVolumeSettings(double volume)
|
||||
{
|
||||
AL.Source(_sourceID, ALSourcef.Gain, (float)volume);
|
||||
}
|
||||
|
||||
public void StartSound()
|
||||
{
|
||||
BufferSizeSamples = Sound.MillisecondsToSamples(Global.Config.SoundBufferSizeMs);
|
||||
MaxSamplesDeficit = BufferSizeSamples;
|
||||
|
||||
_sourceID = AL.GenSource();
|
||||
|
||||
_bufferPool = new BufferPool();
|
||||
_runningSamplesQueued = 0;
|
||||
}
|
||||
|
||||
public void StopSound()
|
||||
{
|
||||
AL.SourceStop(_sourceID);
|
||||
|
||||
AL.DeleteSource(_sourceID);
|
||||
|
||||
_bufferPool.Dispose();
|
||||
_bufferPool = null;
|
||||
|
||||
BufferSizeSamples = 0;
|
||||
}
|
||||
|
||||
public int CalculateSamplesNeeded()
|
||||
{
|
||||
bool isInitializing = _runningSamplesQueued == 0;
|
||||
bool detectedUnderrun = !isInitializing && GetSource(ALGetSourcei.BuffersProcessed) == GetSource(ALGetSourcei.BuffersQueued);
|
||||
if (detectedUnderrun)
|
||||
{
|
||||
_sound.OnUnderrun();
|
||||
}
|
||||
UnqueueProcessedBuffers();
|
||||
long samplesAwaitingPlayback = _runningSamplesQueued - (_runningSamplesPlayed + GetSource(ALGetSourcei.SampleOffset));
|
||||
int samplesNeeded = (int)Math.Max(BufferSizeSamples - samplesAwaitingPlayback, 0);
|
||||
if (isInitializing || detectedUnderrun)
|
||||
{
|
||||
_sound.HandleInitializationOrUnderrun(detectedUnderrun, ref samplesNeeded);
|
||||
}
|
||||
return samplesNeeded;
|
||||
}
|
||||
|
||||
public void WriteSamples(short[] samples, int sampleCount)
|
||||
{
|
||||
if (sampleCount == 0) return;
|
||||
UnqueueProcessedBuffers();
|
||||
int byteCount = sampleCount * Sound.BlockAlign;
|
||||
var buffer = _bufferPool.Obtain(byteCount);
|
||||
AL.BufferData(buffer.BufferID, ALFormat.Stereo16, samples, byteCount, Sound.SampleRate);
|
||||
AL.SourceQueueBuffer(_sourceID, buffer.BufferID);
|
||||
_runningSamplesQueued += sampleCount;
|
||||
if (AL.GetSourceState(_sourceID) != ALSourceState.Playing)
|
||||
{
|
||||
AL.SourcePlay(_sourceID);
|
||||
}
|
||||
}
|
||||
|
||||
private void UnqueueProcessedBuffers()
|
||||
{
|
||||
int releaseCount = GetSource(ALGetSourcei.BuffersProcessed);
|
||||
while (releaseCount > 0)
|
||||
{
|
||||
AL.SourceUnqueueBuffer(_sourceID);
|
||||
var releasedBuffer = _bufferPool.ReleaseOne();
|
||||
_runningSamplesPlayed += releasedBuffer.Length / Sound.BlockAlign;
|
||||
releaseCount--;
|
||||
}
|
||||
}
|
||||
|
||||
private int GetSource(ALGetSourcei param)
|
||||
{
|
||||
int value;
|
||||
AL.GetSource(_sourceID, param, out value);
|
||||
return value;
|
||||
}
|
||||
|
||||
private class BufferPool : IDisposable
|
||||
{
|
||||
private List<BufferPoolItem> _availableItems = new List<BufferPoolItem>();
|
||||
private Queue<BufferPoolItem> _obtainedItems = new Queue<BufferPoolItem>();
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
foreach (BufferPoolItem item in _availableItems.Concat(_obtainedItems))
|
||||
{
|
||||
AL.DeleteBuffer(item.BufferID);
|
||||
}
|
||||
_availableItems.Clear();
|
||||
_obtainedItems.Clear();
|
||||
}
|
||||
|
||||
public BufferPoolItem Obtain(int length)
|
||||
{
|
||||
BufferPoolItem item = GetAvailableItem() ?? new BufferPoolItem();
|
||||
item.Length = length;
|
||||
_obtainedItems.Enqueue(item);
|
||||
return item;
|
||||
}
|
||||
|
||||
private BufferPoolItem GetAvailableItem()
|
||||
{
|
||||
if (_availableItems.Count == 0) return null;
|
||||
BufferPoolItem item = _availableItems[0];
|
||||
_availableItems.RemoveAt(0);
|
||||
return item;
|
||||
}
|
||||
|
||||
public BufferPoolItem ReleaseOne()
|
||||
{
|
||||
BufferPoolItem item = _obtainedItems.Dequeue();
|
||||
_availableItems.Add(item);
|
||||
return item;
|
||||
}
|
||||
|
||||
public class BufferPoolItem
|
||||
{
|
||||
public int BufferID { get; private set; }
|
||||
public int Length { get; set; }
|
||||
|
||||
public BufferPoolItem()
|
||||
{
|
||||
BufferID = AL.GenBuffer();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -92,6 +92,7 @@ namespace BizHawk.Client.EmuHawk
|
|||
_sourceVoice.Dispose();
|
||||
_sourceVoice = null;
|
||||
|
||||
_bufferPool.Dispose();
|
||||
_bufferPool = null;
|
||||
|
||||
BufferSizeSamples = 0;
|
||||
|
@ -129,11 +130,21 @@ namespace BizHawk.Client.EmuHawk
|
|||
_runningSamplesQueued += sampleCount;
|
||||
}
|
||||
|
||||
private class BufferPool
|
||||
private class BufferPool : IDisposable
|
||||
{
|
||||
private List<BufferPoolItem> _availableItems = new List<BufferPoolItem>();
|
||||
private Queue<BufferPoolItem> _obtainedItems = new Queue<BufferPoolItem>();
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
foreach (BufferPoolItem item in _availableItems.Concat(_obtainedItems))
|
||||
{
|
||||
item.DataStream.Dispose();
|
||||
}
|
||||
_availableItems.Clear();
|
||||
_obtainedItems.Clear();
|
||||
}
|
||||
|
||||
public BufferPoolItem Obtain(int length)
|
||||
{
|
||||
BufferPoolItem item = GetAvailableItem(length) ?? new BufferPoolItem(length);
|
||||
|
|
|
@ -25,13 +25,16 @@ namespace BizHawk.Client.EmuHawk
|
|||
#if WINDOWS
|
||||
if (Global.Config.SoundOutputMethod == Config.ESoundOutputMethod.DirectSound)
|
||||
_soundOutput = new DirectSoundSoundOutput(this, mainWindowHandle);
|
||||
else if (Global.Config.SoundOutputMethod == Config.ESoundOutputMethod.XAudio2)
|
||||
|
||||
if (Global.Config.SoundOutputMethod == Config.ESoundOutputMethod.XAudio2)
|
||||
_soundOutput = new XAudio2SoundOutput(this);
|
||||
else
|
||||
_soundOutput = new DummySoundOutput(this);
|
||||
#else
|
||||
_soundOutput = new DummySoundOutput(this);
|
||||
#endif
|
||||
|
||||
if (Global.Config.SoundOutputMethod == Config.ESoundOutputMethod.OpenAL)
|
||||
_soundOutput = new OpenALSoundOutput(this);
|
||||
|
||||
if (_soundOutput == null)
|
||||
_soundOutput = new DummySoundOutput(this);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
|
|
|
@ -67,8 +67,10 @@ namespace BizHawk.Client.EmuHawk
|
|||
private void PopulateDeviceList()
|
||||
{
|
||||
IEnumerable<string> deviceNames = Enumerable.Empty<string>();
|
||||
#if WINDOWS
|
||||
if (rbOutputMethodDirectSound.Checked) deviceNames = DirectSoundSoundOutput.GetDeviceNames();
|
||||
if (rbOutputMethodXAudio2.Checked) deviceNames = XAudio2SoundOutput.GetDeviceNames();
|
||||
#endif
|
||||
listBoxSoundDevices.Items.Clear();
|
||||
listBoxSoundDevices.Items.Add("<default>");
|
||||
listBoxSoundDevices.SelectedIndex = 0;
|
||||
|
|
Loading…
Reference in New Issue