make SDL2WavStream consume a generic Stream rather than passing in a file path
this now should make it functionally the same as a System.Media.SoundPlayer, letting us only doing disk access one time per wav file (and allows some embbed wav to be played)
This commit is contained in:
parent
a38e854a6d
commit
51925e7cc9
|
@ -2,6 +2,7 @@
|
|||
using System.Buffers;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
|
||||
|
@ -326,9 +327,9 @@ namespace BizHawk.Bizware.Audio
|
|||
_wavDeviceBuffer = null;
|
||||
}
|
||||
|
||||
public void PlayWavFile(string path, double volume)
|
||||
public void PlayWavFile(Stream wavFile, double volume)
|
||||
{
|
||||
using var wavStream = new SDL2WavStream(path);
|
||||
using var wavStream = new SDL2WavStream(wavFile);
|
||||
var format = wavStream.Format == SDL2WavStream.AudioFormat.F32LSB
|
||||
? WaveFormat.CreateIeeeFloatWaveFormat(wavStream.Frequency, wavStream.Channels)
|
||||
: new(wavStream.Frequency, wavStream.BitsPerSample, wavStream.Channels);
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
using BizHawk.Client.Common;
|
||||
|
@ -186,9 +187,9 @@ namespace BizHawk.Bizware.Audio
|
|||
}
|
||||
}
|
||||
|
||||
public void PlayWavFile(string path, double volume)
|
||||
public void PlayWavFile(Stream wavFile, double volume)
|
||||
{
|
||||
using var wavStream = new SDL2WavStream(path);
|
||||
using var wavStream = new SDL2WavStream(wavFile);
|
||||
if (wavStream.Channels > 2)
|
||||
{
|
||||
throw new NotSupportedException("OpenAL does not support more than 2 channels");
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
using System;
|
||||
using System.Buffers;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
using BizHawk.Common;
|
||||
|
||||
|
@ -16,11 +18,11 @@ namespace BizHawk.Bizware.Audio
|
|||
// These are the only formats SDL2's wav loadder will output
|
||||
public enum AudioFormat : ushort
|
||||
{
|
||||
U8 = 0x0008,
|
||||
S16LSB = 0x8010,
|
||||
S32LSB = 0x8020,
|
||||
F32LSB = 0x8120,
|
||||
S16MSB = 0x9010,
|
||||
U8 = AUDIO_U8,
|
||||
S16LSB = AUDIO_S16LSB,
|
||||
S32LSB = AUDIO_S32LSB,
|
||||
F32LSB = AUDIO_F32LSB,
|
||||
S16MSB = AUDIO_S16MSB,
|
||||
}
|
||||
|
||||
public int Frequency { get; }
|
||||
|
@ -34,12 +36,19 @@ namespace BizHawk.Bizware.Audio
|
|||
AudioFormat.S32LSB or AudioFormat.F32LSB => 32,
|
||||
_ => throw new InvalidOperationException(),
|
||||
};
|
||||
|
||||
[DllImport("SDL2", CallingConvention = CallingConvention.Cdecl)]
|
||||
private static extern IntPtr SDL_LoadWAV_RW(
|
||||
IntPtr src,
|
||||
int freesrc,
|
||||
out SDL_AudioSpec spec,
|
||||
out IntPtr audio_buf,
|
||||
out uint audio_len);
|
||||
|
||||
public SDL2WavStream(string path)
|
||||
public SDL2WavStream(Stream wavFile)
|
||||
{
|
||||
// TODO: Perhaps this should just take a Stream?
|
||||
// need to update SDL2-CS since the version we're on doesn't expose SDL_LoadWAV_RW :(
|
||||
if (SDL_LoadWAV(path, out var spec, out var wav, out var len) == IntPtr.Zero)
|
||||
using var rwOpWrapper = new SDLRwOpsStreamWrapper(wavFile);
|
||||
if (SDL_LoadWAV_RW(rwOpWrapper.Rw, 0, out var spec, out var wav, out var len) == IntPtr.Zero)
|
||||
{
|
||||
throw new($"Could not load WAV file! SDL error: {SDL_GetError()}");
|
||||
}
|
||||
|
@ -121,5 +130,94 @@ namespace BizHawk.Bizware.Audio
|
|||
|
||||
public override void Write(byte[] buffer, int offset, int count)
|
||||
=> throw new NotSupportedException();
|
||||
|
||||
private unsafe class SDLRwOpsStreamWrapper : IDisposable
|
||||
{
|
||||
public IntPtr Rw { get; private set; }
|
||||
private readonly Stream _s;
|
||||
|
||||
private readonly SDLRWopsSizeCallback _sizeCallback;
|
||||
private readonly SDLRWopsSeekCallback _seekCallback;
|
||||
private readonly SDLRWopsReadCallback _readCallback;
|
||||
private readonly SDLRWopsWriteCallback _writeCallback;
|
||||
private readonly SDLRWopsCloseCallback _closeCallback;
|
||||
|
||||
public SDLRwOpsStreamWrapper(Stream s)
|
||||
{
|
||||
Rw = SDL_AllocRW();
|
||||
if (Rw == IntPtr.Zero)
|
||||
{
|
||||
throw new($"Could not allocate SDL_RWops! SDL error: {SDL_GetError()}");
|
||||
}
|
||||
|
||||
_s = s;
|
||||
_sizeCallback = SizeCallback;
|
||||
_seekCallback = SeekCallback;
|
||||
_readCallback = ReadCallback;
|
||||
_writeCallback = WriteCallback;
|
||||
_closeCallback = CloseCallback;
|
||||
|
||||
var rw = (SDL_RWops*)Rw;
|
||||
rw->size = Marshal.GetFunctionPointerForDelegate(_sizeCallback);
|
||||
rw->seek = Marshal.GetFunctionPointerForDelegate(_seekCallback);
|
||||
rw->read = Marshal.GetFunctionPointerForDelegate(_readCallback);
|
||||
rw->write = Marshal.GetFunctionPointerForDelegate(_writeCallback);
|
||||
rw->close = Marshal.GetFunctionPointerForDelegate(_closeCallback);
|
||||
rw->type = SDL_RWOPS_UNKNOWN;
|
||||
}
|
||||
|
||||
private long SizeCallback(IntPtr ctx)
|
||||
=> _s.Length;
|
||||
|
||||
private long SeekCallback(IntPtr ctx, long offset, int whence)
|
||||
=> _s.Seek(offset, (SeekOrigin)whence);
|
||||
|
||||
private nint ReadCallback(IntPtr ctx, IntPtr ptr, nint size, nint num)
|
||||
{
|
||||
const int TEMP_BUFFER_LENGTH = 65536;
|
||||
var tempBuffer = ArrayPool<byte>.Shared.Rent(TEMP_BUFFER_LENGTH);
|
||||
try
|
||||
{
|
||||
var numBytes = (nuint)size * (nuint)num;
|
||||
var remainingBytes = numBytes;
|
||||
while (remainingBytes != 0)
|
||||
{
|
||||
var numRead = _s.Read(tempBuffer, 0, (int)Math.Min(remainingBytes, TEMP_BUFFER_LENGTH));
|
||||
if (numRead == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
Marshal.Copy(tempBuffer, 0, ptr, numRead);
|
||||
ptr += numRead;
|
||||
remainingBytes -= (uint)numRead;
|
||||
}
|
||||
|
||||
return (nint)((numBytes - remainingBytes) / (nuint)size);
|
||||
}
|
||||
finally
|
||||
{
|
||||
ArrayPool<byte>.Shared.Return(tempBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
private static nint WriteCallback(IntPtr ctx, IntPtr ptr, nint size, nint num)
|
||||
=> 0;
|
||||
|
||||
private int CloseCallback(IntPtr ctx)
|
||||
{
|
||||
Dispose();
|
||||
return 0;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (Rw != IntPtr.Zero)
|
||||
{
|
||||
SDL_FreeRW(Rw);
|
||||
Rw = IntPtr.Zero;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
using BizHawk.Client.Common;
|
||||
|
@ -169,9 +170,9 @@ namespace BizHawk.Bizware.Audio
|
|||
_wavBuffer = null;
|
||||
}
|
||||
|
||||
public void PlayWavFile(string path, double volume)
|
||||
public void PlayWavFile(Stream wavFile, double volume)
|
||||
{
|
||||
using var wavStream = new SDL2WavStream(path);
|
||||
using var wavStream = new SDL2WavStream(wavFile);
|
||||
var format = wavStream.Format == SDL2WavStream.AudioFormat.F32LSB
|
||||
? WaveFormat.CreateIeeeFloatWaveFormat(wavStream.Frequency, wavStream.Channels)
|
||||
: new(wavStream.Frequency, wavStream.BitsPerSample, wavStream.Channels);
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace BizHawk.Client.Common
|
||||
{
|
||||
|
@ -10,6 +11,6 @@ namespace BizHawk.Client.Common
|
|||
int MaxSamplesDeficit { get; }
|
||||
int CalculateSamplesNeeded();
|
||||
void WriteSamples(short[] samples, int sampleOffset, int sampleCount);
|
||||
void PlayWavFile(string path, double volume);
|
||||
void PlayWavFile(Stream wavFile, double volume);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
|
||||
namespace BizHawk.Client.Common
|
||||
{
|
||||
|
@ -77,7 +78,7 @@ namespace BizHawk.Client.Common
|
|||
_remainingSamples += sampleCount;
|
||||
}
|
||||
|
||||
public void PlayWavFile(string path, double volume)
|
||||
public void PlayWavFile(Stream wavFile, double volume)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4900,7 +4900,7 @@ namespace BizHawk.Client.EmuHawk
|
|||
InputManager,
|
||||
Tools,
|
||||
() => Config,
|
||||
path => Sound.PlayWavFile(path, 1), // TODO: Make this configurable
|
||||
wavFile => Sound.PlayWavFile(wavFile, 1), // TODO: Make this configurable
|
||||
RetroAchievementsMenuItem.DropDownItems,
|
||||
() =>
|
||||
{
|
||||
|
|
|
@ -7,21 +7,34 @@ namespace BizHawk.Client.EmuHawk
|
|||
{
|
||||
public partial class RCheevos
|
||||
{
|
||||
private readonly Action<string> _playWavFileCallback;
|
||||
private static MemoryStream ReadWavFile(string path)
|
||||
{
|
||||
try
|
||||
{
|
||||
return new(File.ReadAllBytes(Path.Combine(PathUtils.ExeDirectoryPath, path)), false);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly string _loginSound = Path.Combine(PathUtils.ExeDirectoryPath, "overlay/login.wav");
|
||||
private static readonly string _unlockSound = Path.Combine(PathUtils.ExeDirectoryPath, "overlay/unlock.wav");
|
||||
private static readonly string _lboardStartSound = Path.Combine(PathUtils.ExeDirectoryPath, "overlay/lb.wav");
|
||||
private static readonly string _lboardFailedSound = Path.Combine(PathUtils.ExeDirectoryPath, "overlay/lbcancel.wav");
|
||||
private static readonly string _infoSound = Path.Combine(PathUtils.ExeDirectoryPath, "overlay/info.wav");
|
||||
private static readonly MemoryStream _loginSound = ReadWavFile("overlay/login.wav");
|
||||
private static readonly MemoryStream _unlockSound = ReadWavFile( "overlay/unlock.wav");
|
||||
private static readonly MemoryStream _lboardStartSound = ReadWavFile("overlay/lb.wav");
|
||||
private static readonly MemoryStream _lboardFailedSound = ReadWavFile("overlay/lbcancel.wav");
|
||||
private static readonly MemoryStream _infoSound = ReadWavFile("overlay/info.wav");
|
||||
|
||||
private readonly Action<Stream> _playWavFileCallback;
|
||||
|
||||
private bool EnableSoundEffects { get; set; }
|
||||
|
||||
private void PlaySound(string path)
|
||||
private void PlaySound(Stream wavFile)
|
||||
{
|
||||
if (EnableSoundEffects)
|
||||
{
|
||||
_playWavFileCallback(path);
|
||||
wavFile.Position = 0;
|
||||
_playWavFileCallback(wavFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Windows.Forms;
|
||||
|
||||
using BizHawk.Client.Common;
|
||||
|
@ -47,7 +48,7 @@ namespace BizHawk.Client.EmuHawk
|
|||
InputManager inputManager,
|
||||
ToolManager tools,
|
||||
Func<Config> getConfig,
|
||||
Action<string> playWavFile,
|
||||
Action<Stream> playWavFile,
|
||||
ToolStripItemCollection raDropDownItems,
|
||||
Action shutdownRACallback)
|
||||
{
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
|
||||
using BizHawk.Bizware.Audio;
|
||||
|
@ -236,12 +237,12 @@ namespace BizHawk.Client.EmuHawk
|
|||
_outputDevice.WriteSamples(samples, sampleOffset, sampleCount);
|
||||
}
|
||||
|
||||
public void PlayWavFile(string path, float atten)
|
||||
public void PlayWavFile(Stream wavFile, float atten)
|
||||
{
|
||||
if (atten <= 0) return;
|
||||
try
|
||||
{
|
||||
_outputDevice.PlayWavFile(path, Math.Min(atten, 1));
|
||||
_outputDevice.PlayWavFile(wavFile, Math.Min(atten, 1));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
|
Loading…
Reference in New Issue