Add OpenAL sound output.

XAudio2: Some stuff I forgot to dispose.
This commit is contained in:
jdpurcell 2015-02-19 02:30:55 +00:00
parent 822d42048e
commit c56edd6e93
7 changed files with 204 additions and 7 deletions

View File

@ -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 };

View File

@ -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" />

View File

@ -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
{

View File

@ -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();
}
}
}
}
}

View File

@ -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);

View File

@ -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()

View File

@ -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;