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:
CasualPokePlayer 2023-12-30 16:03:21 -08:00
parent a38e854a6d
commit 51925e7cc9
10 changed files with 147 additions and 29 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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,
() =>
{

View File

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

View File

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

View File

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