throw out the speex resampler, place in SDL's resampler to replace it

no more 2010 vc++ runtime requirement
This commit is contained in:
CasualPokePlayer 2024-03-10 15:01:51 -07:00
parent 05abb3adfa
commit d9a93c474a
8 changed files with 222 additions and 506 deletions

Binary file not shown.

View File

@ -126,24 +126,17 @@ namespace BizHawk.Client.EmuHawk
if (!OSTailoredCode.IsUnixHost)
{
// TODO: We optimally should only require the new VS2015-2022 all in one redist be installed
// None of this old 2010 runtime garbage
// (just removing speex completely, perhaps can just use SDL2 as a replacement?)
foreach (var (dllToLoad, desc) in new[]
{
("vcruntime140_1.dll", "Microsoft Visual C++ Redistributable for Visual Studio 2015, 2017, 2019, and 2022 (x64)"),
// for libspeexdsp.dll
("msvcr100.dll", "Microsoft Visual C++ 2010 SP1 Runtime (x64)"),
})
// Check if we have the C++ VS2015-2022 redist all in one redist be installed
var p = OSTailoredCode.LinkedLibManager.LoadOrZero("vcruntime140_1.dll");
if (p != IntPtr.Zero)
{
OSTailoredCode.LinkedLibManager.FreeByPtr(p);
}
else
{
var p = OSTailoredCode.LinkedLibManager.LoadOrZero(dllToLoad);
if (p != IntPtr.Zero)
{
OSTailoredCode.LinkedLibManager.FreeByPtr(p);
continue;
}
// else it's missing or corrupted
const string desc =
"Microsoft Visual C++ Redistributable for Visual Studio 2015, 2017, 2019, and 2022 (x64)";
MessageBox.Show($"EmuHawk needs {desc} in order to run! See the readme on GitHub for more info. (EmuHawk will now close.) " +
$"Internal error message: {OSTailoredCode.LinkedLibManager.GetErrorMessage()}");
return -1;

View File

@ -9,6 +9,7 @@
<ItemGroup>
<PackageReference Include="System.Collections.Immutable" />
<PackageReference Include="Newtonsoft.Json" />
<PackageReference Include="ppy.SDL2-CS" ExcludeAssets="native;contentFiles" />
<ProjectReference Include="$(ProjectDir)../BizHawk.BizInvoke/BizHawk.BizInvoke.csproj" />
<ProjectReference Include="$(ProjectDir)../BizHawk.Common/BizHawk.Common.csproj" />
</ItemGroup>

View File

@ -1,234 +0,0 @@
#nullable disable
using System;
using System.Runtime.InteropServices;
using BizHawk.BizInvoke;
using static BizHawk.Emulation.Common.SpeexResampler;
namespace BizHawk.Emulation.Common
{
public abstract class LibSpeexDSP
{
public enum RESAMPLER_ERR
{
SUCCESS = 0,
ALLOC_FAILED = 1,
BAD_STATE = 2,
INVALID_ARG = 3,
PTR_OVERLAP = 4,
MAX_ERROR
}
/// <summary>
/// Create a new resampler with integer input and output rates.
/// </summary>
/// <param name="nb_channels">Number of channels to be processed</param>
/// <param name="in_rate">Input sampling rate (integer number of Hz).</param>
/// <param name="out_rate">Output sampling rate (integer number of Hz).</param>
/// <param name="quality">Resampling quality between 0 and 10, where 0 has poor quality and 10 has very high quality.</param>
/// <param name="err">The error state</param>
/// <returns>Newly created resampler state</returns>
[BizImport(CallingConvention.Cdecl)]
public abstract IntPtr speex_resampler_init(uint nb_channels, uint in_rate, uint out_rate, Quality quality, ref RESAMPLER_ERR err);
/// <summary>
/// Create a new resampler with fractional input/output rates. The sampling
/// rate ratio is an arbitrary rational number with both the numerator and
/// denominator being 32-bit integers.
/// </summary>
/// <param name="nb_channels">Number of channels to be processed</param>
/// <param name="ratio_num">Numerator of the sampling rate ratio</param>
/// <param name="ratio_den">Denominator of the sampling rate ratio</param>
/// <param name="in_rate">Input sampling rate rounded to the nearest integer (in Hz).</param>
/// <param name="out_rate">Output sampling rate rounded to the nearest integer (in Hz).</param>
/// <param name="quality">Resampling quality between 0 and 10, where 0 has poor quality and 10 has very high quality.</param>
/// <param name="err">The error state</param>
/// <returns>Newly created resampler state</returns>
[BizImport(CallingConvention.Cdecl)]
public abstract IntPtr speex_resampler_init_frac(uint nb_channels, uint ratio_num, uint ratio_den, uint in_rate, uint out_rate, Quality quality, ref RESAMPLER_ERR err);
/// <summary>
/// Destroy a resampler state.
/// </summary>
/// <param name="st">Resampler state</param>
[BizImport(CallingConvention.Cdecl)]
public abstract void speex_resampler_destroy(IntPtr st);
/// <summary>
/// Resample a float array. The input and output buffers must *not* overlap.
/// </summary>
/// <param name="st">Resampler state</param>
/// <param name="channel_index">Index of the channel to process for the multi-channel base (0 otherwise)</param>
/// <param name="inp">Input buffer</param>
/// <param name="in_len">Number of input samples in the input buffer. Returns the number of samples processed</param>
/// <param name="outp">Output buffer</param>
/// <param name="out_len">Size of the output buffer. Returns the number of samples written</param>
[BizImport(CallingConvention.Cdecl)]
public abstract RESAMPLER_ERR speex_resampler_process_float(IntPtr st, uint channel_index, float[] inp, ref uint in_len, float[] outp, ref uint out_len);
/// <summary>
/// Resample an int array. The input and output buffers must *not* overlap.
/// </summary>
/// <param name="st">Resampler state</param>
/// <param name="channel_index">Index of the channel to process for the multi-channel base (0 otherwise)</param>
/// <param name="inp">Input buffer</param>
/// <param name="in_len">Number of input samples in the input buffer. Returns the number of samples processed</param>
/// <param name="outp">Output buffer</param>
/// <param name="out_len">Size of the output buffer. Returns the number of samples written</param>
[BizImport(CallingConvention.Cdecl)]
public abstract RESAMPLER_ERR speex_resampler_process_int(IntPtr st, uint channel_index, short[] inp, ref uint in_len, short[] outp, ref uint out_len);
/// <summary>
/// Resample an interleaved float array. The input and output buffers must *not* overlap.
/// </summary>
/// <param name="st">Resampler state</param>
/// <param name="inp">Input buffer</param>
/// <param name="in_len">Number of input samples in the input buffer. Returns the number of samples processed. This is all per-channel.</param>
/// <param name="outp">Output buffer</param>
/// <param name="out_len">Size of the output buffer. Returns the number of samples written. This is all per-channel.</param>
[BizImport(CallingConvention.Cdecl)]
public abstract RESAMPLER_ERR speex_resampler_process_interleaved_float(IntPtr st, float[] inp, ref uint in_len, float[] outp, ref uint out_len);
/// <summary>
/// Resample an interleaved int array. The input and output buffers must *not* overlap.
/// </summary>
/// <param name="st">Resampler state</param>
/// <param name="inp">Input buffer</param>
/// <param name="in_len">Number of input samples in the input buffer. Returns the number of samples processed. This is all per-channel.</param>
/// <param name="outp">Output buffer</param>
/// <param name="out_len">Size of the output buffer. Returns the number of samples written. This is all per-channel.</param>
[BizImport(CallingConvention.Cdecl)]
public abstract RESAMPLER_ERR speex_resampler_process_interleaved_int(IntPtr st, short[] inp, ref uint in_len, short[] outp, ref uint out_len);
/// <summary>
/// Set (change) the input/output sampling rates (integer value).
/// </summary>
/// <param name="st">Resampler state</param>
/// <param name="in_rate">Input sampling rate (integer number of Hz).</param>
/// <param name="out_rate">Output sampling rate (integer number of Hz).</param>
[BizImport(CallingConvention.Cdecl)]
public abstract RESAMPLER_ERR speex_resampler_set_rate(IntPtr st, uint in_rate, uint out_rate);
/// <summary>
/// Get the current input/output sampling rates (integer value).
/// </summary>
/// <param name="st">Resampler state</param>
/// <param name="in_rate">Input sampling rate (integer number of Hz) copied.</param>
/// <param name="out_rate">Output sampling rate (integer number of Hz) copied.</param>
[BizImport(CallingConvention.Cdecl)]
public abstract void speex_resampler_get_rate(IntPtr st, ref uint in_rate, ref uint out_rate);
/// <summary>
/// Set (change) the input/output sampling rates and resampling ratio (fractional values in Hz supported).
/// </summary>
/// <param name="st">resampler state</param>
/// <param name="ratio_num">Numerator of the sampling rate ratio</param>
/// <param name="ratio_den">Denominator of the sampling rate ratio</param>
/// <param name="in_rate">Input sampling rate rounded to the nearest integer (in Hz).</param>
/// <param name="out_rate">Output sampling rate rounded to the nearest integer (in Hz).</param>
[BizImport(CallingConvention.Cdecl)]
public abstract RESAMPLER_ERR speex_resampler_set_rate_frac(IntPtr st, uint ratio_num, uint ratio_den, uint in_rate, uint out_rate);
/// <summary>
/// Get the current resampling ratio. This will be reduced to the least common denominator.
/// </summary>
/// <param name="st">Resampler state</param>
/// <param name="ratio_num">Numerator of the sampling rate ratio copied</param>
/// <param name="ratio_den">Denominator of the sampling rate ratio copied</param>
[BizImport(CallingConvention.Cdecl)]
public abstract void speex_resampler_get_ratio(IntPtr st, ref uint ratio_num, ref uint ratio_den);
/// <summary>
/// Set (change) the conversion quality.
/// </summary>
/// <param name="st">Resampler state</param>
/// <param name="quality">Resampling quality between 0 and 10, where 0 has poor quality and 10 has very high quality.</param>
[BizImport(CallingConvention.Cdecl)]
public abstract RESAMPLER_ERR speex_resampler_set_quality(IntPtr st, Quality quality);
/// <summary>
/// Get the conversion quality.
/// </summary>
/// <param name="st">Resampler state</param>
/// <param name="quality">Resampling quality between 0 and 10, where 0 has poor quality and 10 has very high quality.</param>
[BizImport(CallingConvention.Cdecl)]
public abstract void speex_resampler_get_quality(IntPtr st, ref Quality quality);
/// <summary>
/// Set (change) the input stride.
/// </summary>
/// <param name="st">Resampler state</param>
/// <param name="stride">Input stride</param>
[BizImport(CallingConvention.Cdecl)]
public abstract void speex_resampler_set_input_stride(IntPtr st, uint stride);
/// <summary>
/// Get the input stride.
/// </summary>
/// <param name="st">Resampler state</param>
/// <param name="stride">Input stride copied</param>
[BizImport(CallingConvention.Cdecl)]
public abstract void speex_resampler_get_input_stride(IntPtr st, ref uint stride);
/// <summary>
/// Set (change) the output stride.
/// </summary>
/// <param name="st">Resampler state</param>
/// <param name="stride">Output stride</param>
[BizImport(CallingConvention.Cdecl)]
public abstract void speex_resampler_set_output_stride(IntPtr st, uint stride);
/// <summary>
/// Get the output stride.
/// </summary>
/// <param name="st">Resampler state</param>
/// <param name="stride">Output stride copied</param>
[BizImport(CallingConvention.Cdecl)]
public abstract void speex_resampler_get_output_stride(IntPtr st, ref uint stride);
/*these two functions don't exist in our version of the dll
/// <summary>
/// Get the latency in input samples introduced by the resampler.
/// </summary>
/// <param name="st">Resampler state</param>
[BizImport(CallingConvention.Cdecl)]
public abstract int speex_resampler_get_input_latency(IntPtr st);
/// <summary>
/// Get the latency in output samples introduced by the resampler.
/// </summary>
/// <param name="st">Resampler state</param>
[BizImport(CallingConvention.Cdecl)]
public abstract int speex_resampler_get_output_latency(IntPtr st);
*/
/// <summary>
/// Make sure that the first samples to go out of the resampler don't have
/// leading zeros. This is only useful before starting to use a newly created
/// resampler. It is recommended to use that when resampling an audio file, as
/// it will generate a file with the same length. For real-time processing,
/// it is probably easier not to use this call (so that the output duration
/// is the same for the first frame).
/// </summary>
/// <param name="st">Resampler state</param>
[BizImport(CallingConvention.Cdecl)]
public abstract RESAMPLER_ERR speex_resampler_skip_zeros(IntPtr st);
/// <summary>
/// Reset a resampler so a new (unrelated) stream can be processed.
/// </summary>
/// <param name="st">Resampler state</param>
[BizImport(CallingConvention.Cdecl)]
public abstract RESAMPLER_ERR speex_resampler_reset_mem(IntPtr st);
/// <summary>
/// Returns the English meaning for an error code
/// </summary>
/// <param name="err">Error code</param>
/// <returns>English string</returns>
[BizImport(CallingConvention.Cdecl, Compatibility = true)]
public abstract string speex_resampler_strerror(RESAMPLER_ERR err);
}
}

View File

@ -0,0 +1,191 @@
#nullable disable
using System;
using System.Runtime.InteropServices;
using static SDL2.SDL;
namespace BizHawk.Emulation.Common
{
/// <summary>
/// Wrapper against SDL's resampler
/// </summary>
public class SDLResampler : IDisposable, ISoundProvider
{
// to accept an ISyncSoundProvider input
private readonly ISoundProvider _input;
// function to call to dispatch output
private readonly Action<short[], int> _drainer;
private short[] _outBuf = Array.Empty<short>();
// for ISoundProvider output use
private short[] _outSamples = Array.Empty<short>();
private int _outNumSamps;
// opaque pointer to SDL_AudioStream
private IntPtr _stream;
/// <param name="src_rate">sampling rate in by hz</param>
/// <param name="dst_rate">sampling rate out by hz</param>
/// <param name="drainer">function which accepts output as produced. if null, act as an <see cref="ISoundProvider"/></param>
/// <param name="input">source to take input from when output is requested. if null, no auto-fetching</param>
/// <exception cref="ArgumentException"><paramref name="drainer"/> and <paramref name="input"/> are both non-null</exception>
/// <exception cref="Exception">unmanaged call failed</exception>
public SDLResampler(int src_rate, int dst_rate, Action<short[], int> drainer = null, ISoundProvider input = null)
{
if (drainer is not null && input is not null) throw new ArgumentException(message: $"Can't autofetch without being an {nameof(ISoundProvider)}?", paramName: nameof(input));
_stream = SDL_NewAudioStream(AUDIO_S16SYS, 2, src_rate, AUDIO_S16SYS, 2, dst_rate);
if (_stream == IntPtr.Zero)
{
throw new($"{nameof(SDL_NewAudioStream)} returned null! SDL error: {SDL_GetError()}");
}
_drainer = drainer ?? InternalDrain;
_input = input;
}
// SDL bindings don't have this for some reason :(
[DllImport("SDL2", CallingConvention = CallingConvention.Cdecl)]
private static extern int SDL_AudioStreamFlush(IntPtr stream);
/// <summary>change sampling rate on the fly</summary>
/// <param name="src_rate">sampling rate in by hz</param>
/// <param name="dst_rate">sampling rate out by hz</param>
public void ChangeRate(int src_rate, int dst_rate)
{
// force flush the stream, as we'll be destroying it to change the sample rate...
if (SDL_AudioStreamFlush(_stream) != 0)
{
throw new($"{nameof(SDL_AudioStreamFlush)} failed! SDL error: {SDL_GetError()}");
}
Flush();
SDL_FreeAudioStream(_stream);
_stream = SDL_NewAudioStream(AUDIO_S16SYS, 2, src_rate, AUDIO_S16SYS, 2, dst_rate);
if (_stream == IntPtr.Zero)
{
throw new($"{nameof(SDL_NewAudioStream)} returned null! SDL error: {SDL_GetError()}");
}
}
/// <summary>
/// add multiple samples to the queue
/// </summary>
/// <param name="userbuf">interleaved stereo samples</param>
/// <param name="nsamp">number of sample pairs</param>
public unsafe void EnqueueSamples(short[] userbuf, int nsamp)
{
fixed (short* ub = userbuf)
{
if (SDL_AudioStreamPut(_stream, (IntPtr)ub, nsamp * 4) != 0)
{
throw new($"{nameof(SDL_AudioStreamPut)} failed! SDL error: {SDL_GetError()}");
}
}
}
/// <summary>flush as many input samples as possible, generating output samples right now</summary>
/// <exception cref="Exception">unmanaged call failed</exception>
public unsafe void Flush()
{
var streamAvail = SDL_AudioStreamAvailable(_stream);
if (streamAvail == 0)
{
return;
}
// stereo s16 audio always has 4 bytes per interleaved sample
if (streamAvail % 4 != 0)
{
throw new("SDL audio stream contained partial sample frames?");
}
if (streamAvail / 2 > _outBuf.Length)
{
_outBuf = new short[streamAvail / 2];
}
fixed (short* outBuf = _outBuf)
{
var numRead = SDL_AudioStreamGet(_stream, (IntPtr)outBuf, streamAvail);
if (numRead == -1)
{
throw new($"{nameof(SDL_AudioStreamGet)} failed! SDL error: {SDL_GetError()}");
}
if (numRead != streamAvail)
{
throw new($"{nameof(SDL_AudioStreamGet)} didn't eat the whole array?");
}
}
_drainer(_outBuf, streamAvail / 4);
}
public void Dispose()
{
if (_stream != IntPtr.Zero)
{
SDL_FreeAudioStream(_stream);
_stream = IntPtr.Zero;
}
}
private void InternalDrain(short[] buf, int nsamp)
{
if (_outNumSamps + nsamp * 2 > _outBuf.Length)
{
var newbuf = new short[_outNumSamps + nsamp * 2];
Buffer.BlockCopy(_outSamples, 0, newbuf, 0, _outNumSamps * sizeof(short));
_outSamples = newbuf;
}
Buffer.BlockCopy(buf, 0, _outSamples, _outNumSamps * sizeof(short), nsamp * 2 * sizeof(short));
_outNumSamps += nsamp * 2;
}
public void GetSamplesSync(out short[] samples, out int nsamp)
{
if (_input != null)
{
_input.GetSamplesSync(out var sampin, out var nsampin);
EnqueueSamples(sampin, nsampin);
}
Flush();
nsamp = _outNumSamps / 2;
samples = _outSamples;
_outNumSamps = 0;
}
public void DiscardSamples()
{
_outNumSamps = 0;
}
public bool CanProvideAsync => false;
public SyncSoundMode SyncMode => SyncSoundMode.Sync;
/// <exception cref="InvalidOperationException">always</exception>
public void GetSamplesAsync(short[] samples)
{
throw new InvalidOperationException("Async mode is not supported.");
}
/// <exception cref="NotSupportedException"><paramref name="mode"/> is <see cref="SyncSoundMode.Async"/></exception>
public void SetSyncMode(SyncSoundMode mode)
{
if (mode == SyncSoundMode.Async)
{
throw new NotSupportedException("Async mode is not supported.");
}
}
}
}

View File

@ -1,245 +0,0 @@
#nullable disable
using System;
using BizHawk.BizInvoke;
using BizHawk.Common;
// ReSharper disable UnusedMember.Local
// ReSharper disable UnusedMember.Global
// ReSharper disable IdentifierTypo
// ReSharper disable StyleCop.SA1300
// ReSharper disable InconsistentNaming
namespace BizHawk.Emulation.Common
{
/// <summary>
/// junk wrapper around LibSpeexDSP. quite inefficient. will be replaced
/// </summary>
public class SpeexResampler : IDisposable, ISoundProvider
{
private static readonly LibSpeexDSP NativeDSP;
static SpeexResampler()
{
var resolver = new DynamicLibraryImportResolver(
OSTailoredCode.IsUnixHost ? "libspeexdsp.so.1" : "libspeexdsp.dll", hasLimitedLifetime: false);
NativeDSP = BizInvoker.GetInvoker<LibSpeexDSP>(resolver, CallingConventionAdapters.Native);
}
// to accept an ISyncSoundProvider input
private readonly ISoundProvider _input;
// function to call to dispatch output
private readonly Action<short[], int> _drainer;
// TODO: this size is roughly based on how big you can make the buffer before the snes resampling (32040.5 -> 44100) gets screwed up
private readonly short[] _inbuf = new short[512]; // [8192]; // [512];
/// <summary>
/// quality of the resampler. values other than those listed are valid, provided they are between MIN and MAX
/// </summary>
public enum Quality
{
QUALITY_MAX = 10,
QUALITY_MIN = 0,
QUALITY_DEFAULT = 4,
QUALITY_VOIP = 3,
QUALITY_DESKTOP = 5
}
// opaque pointer to state
private IntPtr _st = IntPtr.Zero;
private short[] _outbuf;
// for sync
private short[] _outbuf2 = new short[16];
private int _outbuf2pos;
// in buffer position in samples (not sample pairs)
private int _inbufpos;
/// <summary>
/// throw an exception based on error state
/// </summary>
private static void CheckError(LibSpeexDSP.RESAMPLER_ERR e)
{
switch (e)
{
case LibSpeexDSP.RESAMPLER_ERR.SUCCESS:
return;
#pragma warning disable MA0012 // this will crash the .NET host, as it should
case LibSpeexDSP.RESAMPLER_ERR.ALLOC_FAILED:
throw new InsufficientMemoryException($"{nameof(LibSpeexDSP)}: Alloc failed");
#pragma warning restore MA0012
case LibSpeexDSP.RESAMPLER_ERR.BAD_STATE:
throw new Exception($"{nameof(LibSpeexDSP)}: Bad state");
case LibSpeexDSP.RESAMPLER_ERR.INVALID_ARG:
throw new Exception($"{nameof(LibSpeexDSP)}: Bad Argument");
case LibSpeexDSP.RESAMPLER_ERR.PTR_OVERLAP:
throw new Exception($"{nameof(LibSpeexDSP)}: Buffers cannot overlap");
}
}
/// <param name="quality">0 to 10</param>
/// <param name="rationum">numerator of sample rate change ratio (inrate / outrate)</param>
/// <param name="ratioden">denominator of sample rate change ratio (inrate / outrate)</param>
/// <param name="sratein">sampling rate in, rounded to nearest hz</param>
/// <param name="srateout">sampling rate out, rounded to nearest hz</param>
/// <param name="drainer">function which accepts output as produced. if null, act as an <see cref="ISoundProvider"/></param>
/// <param name="input">source to take input from when output is requested. if null, no auto-fetching</param>
/// <exception cref="ArgumentException"><paramref name="drainer"/> and <paramref name="input"/> are both non-null</exception>
/// <exception cref="Exception">unmanaged call failed</exception>
public SpeexResampler(Quality quality, uint rationum, uint ratioden, uint sratein, uint srateout, Action<short[], int> drainer = null, ISoundProvider input = null)
{
if (drainer is not null && input is not null) throw new ArgumentException(message: $"Can't autofetch without being an {nameof(ISoundProvider)}?", paramName: nameof(input));
var err = LibSpeexDSP.RESAMPLER_ERR.SUCCESS;
_st = NativeDSP.speex_resampler_init_frac(2, rationum, ratioden, sratein, srateout, quality, ref err);
if (_st == IntPtr.Zero)
{
throw new Exception($"{nameof(LibSpeexDSP)} returned null!");
}
CheckError(err);
_drainer = drainer ?? InternalDrain;
_input = input;
_outbuf = new short[(_inbuf.Length * ratioden / rationum / 2 * 2) + 128];
}
/// <summary>change sampling rate on the fly</summary>
/// <param name="rationum">numerator of sample rate change ratio (inrate / outrate)</param>
/// <param name="ratioden">denominator of sample rate change ratio (inrate / outrate)</param>
/// <param name="sratein">sampling rate in, rounded to nearest hz</param>
/// <param name="srateout">sampling rate out, rounded to nearest hz</param>
public void ChangeRate(uint rationum, uint ratioden, uint sratein, uint srateout)
{
CheckError(NativeDSP.speex_resampler_set_rate_frac(_st, rationum, ratioden, sratein, srateout));
_outbuf = new short[(_inbuf.Length * ratioden / rationum / 2 * 2) + 128];
}
/// <summary>
/// add a sample to the queue
/// </summary>
public void EnqueueSample(short left, short right)
{
_inbuf[_inbufpos++] = left;
_inbuf[_inbufpos++] = right;
if (_inbufpos == _inbuf.Length)
{
Flush();
}
}
/// <summary>
/// add multiple samples to the queue
/// </summary>
/// <param name="userbuf">interleaved stereo samples</param>
/// <param name="nsamp">number of sample pairs</param>
public void EnqueueSamples(short[] userbuf, int nsamp)
{
int numused = 0;
while (numused < nsamp)
{
int shortstocopy = Math.Min(_inbuf.Length - _inbufpos, (nsamp - numused) * 2);
Buffer.BlockCopy(userbuf, numused * 2 * sizeof(short), _inbuf, _inbufpos * sizeof(short), shortstocopy * sizeof(short));
_inbufpos += shortstocopy;
numused += shortstocopy / 2;
if (_inbufpos == _inbuf.Length)
{
Flush();
}
}
}
/// <summary>flush as many input samples as possible, generating output samples right now</summary>
/// <exception cref="Exception">unmanaged call failed</exception>
public void Flush()
{
uint inal = (uint)_inbufpos / 2;
uint outal = (uint)_outbuf.Length / 2;
NativeDSP.speex_resampler_process_interleaved_int(_st, _inbuf, ref inal, _outbuf, ref outal);
// reset inbuf
if (inal != _inbufpos / 2)
{
throw new Exception("Speexresampler didn't eat the whole array?");
}
_inbufpos = 0;
_drainer(_outbuf, (int)outal);
}
public void Dispose()
{
if (_st != IntPtr.Zero)
{
NativeDSP.speex_resampler_destroy(_st);
_st = IntPtr.Zero;
GC.SuppressFinalize(this);
}
}
~SpeexResampler()
{
Dispose();
}
private void InternalDrain(short[] buf, int nsamp)
{
if (_outbuf2pos + (nsamp * 2) > _outbuf2.Length)
{
short[] newbuf = new short[_outbuf2pos + (nsamp * 2)];
Buffer.BlockCopy(_outbuf2, 0, newbuf, 0, _outbuf2pos * sizeof(short));
_outbuf2 = newbuf;
}
Buffer.BlockCopy(buf, 0, _outbuf2, _outbuf2pos * sizeof(short), nsamp * 2 * sizeof(short));
_outbuf2pos += nsamp * 2;
}
public void GetSamplesSync(out short[] samples, out int nsamp)
{
if (_input != null)
{
_input.GetSamplesSync(out var sampin, out int nsampin);
EnqueueSamples(sampin, nsampin);
}
Flush();
nsamp = _outbuf2pos / 2;
samples = _outbuf2;
_outbuf2pos = 0;
}
public void DiscardSamples()
{
_outbuf2pos = 0;
}
public bool CanProvideAsync => false;
public SyncSoundMode SyncMode => SyncSoundMode.Sync;
/// <exception cref="InvalidOperationException">always</exception>
public void GetSamplesAsync(short[] samples)
{
throw new InvalidOperationException("Async mode is not supported.");
}
/// <exception cref="NotSupportedException"><paramref name="mode"/> is <see cref="SyncSoundMode.Async"/></exception>
public void SetSyncMode(SyncSoundMode mode)
{
if (mode == SyncSoundMode.Async)
{
throw new NotSupportedException("Async mode is not supported.");
}
}
}
}

View File

@ -16,24 +16,28 @@ namespace BizHawk.Emulation.Cores.Nintendo.N64
/// <summary>
/// Buffer for audio data
/// </summary>
private short[] audioBuffer = new short[0];
private uint _samplingRate = 0;
private short[] audioBuffer = Array.Empty<short>();
private int _samplingRate;
/// <summary>
/// Currently active sampling rate
/// </summary>
public uint SamplingRate
public int SamplingRate
{
get => _samplingRate;
private set
{
_samplingRate = value;
Resampler.ChangeRate(_samplingRate, 44100, _samplingRate, 44100);
Resampler.ChangeRate(_samplingRate, 44100);
}
}
/// <summary>
/// Resampler for audio output
/// </summary>
public SpeexResampler Resampler { get; private set; }
public SDLResampler Resampler { get; private set; }
public bool RenderSound { get; set; }
/// <summary>
@ -44,8 +48,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.N64
this.api = new mupen64plusAudioApi(core);
_samplingRate = api.GetSamplingRate();
Resampler = new SpeexResampler((SpeexResampler.Quality)6, SamplingRate, 44100,
SamplingRate, 44100);
Resampler = new(SamplingRate, 44100);
coreAPI = core;
coreAPI.VInterrupt += DoAudioFrame;
@ -57,19 +60,26 @@ namespace BizHawk.Emulation.Cores.Nintendo.N64
/// </summary>
public void DoAudioFrame()
{
uint m64pSamplingRate = api.GetSamplingRate();
var m64pSamplingRate = api.GetSamplingRate();
if (m64pSamplingRate != SamplingRate)
{
SamplingRate = m64pSamplingRate;
}
int audioBufferSize = api.GetAudioBufferSize();
var audioBufferSize = api.GetAudioBufferSize();
if (audioBuffer.Length < audioBufferSize)
{
audioBuffer = new short[audioBufferSize];
}
if (audioBufferSize > 0)
{
api.GetAudioBuffer(audioBuffer);
if (RenderSound)
{
Resampler.EnqueueSamples(audioBuffer, audioBufferSize / 2);
Resampler.Flush();
}
}
}

View File

@ -57,9 +57,9 @@ namespace BizHawk.Emulation.Cores.Nintendo.N64.NativeApi
/// <summary>
/// Returns currently used sampling rate
/// </summary>
public uint GetSamplingRate()
public int GetSamplingRate()
{
return (uint)dllGetAudioRate();
return dllGetAudioRate();
}
/// <summary>