From 71652b25dc3f1149ac7d40b7eda152795f16641b Mon Sep 17 00:00:00 2001 From: goyuken Date: Fri, 7 Sep 2012 20:12:47 +0000 Subject: [PATCH] cleanup/simplify SpeexResampler --- .../Consoles/Nintendo/SNES/LibsnesCore.cs | 75 ++---------- BizHawk.Emulation/Sound/Utilities/Metaspu.cs | 4 + .../Sound/Utilities/SpeexResampler.cs | 114 ++++++++++++++++-- 3 files changed, 120 insertions(+), 73 deletions(-) diff --git a/BizHawk.Emulation/Consoles/Nintendo/SNES/LibsnesCore.cs b/BizHawk.Emulation/Consoles/Nintendo/SNES/LibsnesCore.cs index d1449345ef..44f314b7ef 100644 --- a/BizHawk.Emulation/Consoles/Nintendo/SNES/LibsnesCore.cs +++ b/BizHawk.Emulation/Consoles/Nintendo/SNES/LibsnesCore.cs @@ -219,7 +219,7 @@ namespace BizHawk.Emulation.Consoles.Nintendo.SNES BizHawk.Emulation.Consoles.Nintendo.SNES.LibsnesDll.snes_set_audio_sample(soundcb); // start up audio resampler - resampler.StartSession(resamplingfactor); + InitAudio(); //strip header if ((romData.Length & 0x7FFF) == 512) @@ -479,56 +479,20 @@ namespace BizHawk.Emulation.Consoles.Nintendo.SNES #region audio stuff - /// stores input samples as they come from the libsnes core - Queue AudioInBuffer = new Queue(); - /// stores samples that have been converted to 44100hz - Queue AudioOutBuffer = new Queue(); + void InitAudio() + { + metaspu = new Sound.MetaspuSoundProvider(Sound.ESynchMethod.ESynchMethod_V); + resampler = new Sound.Utilities.SpeexResampler(6, 64081, 88200, 32041, 44100, new Action(metaspu.buffer.enqueue_samples)); - GCHandle _gc_snes_audio_sample; + } - /// total number of samples (left and right combined) in the InBuffer before we ask for resampling - const int resamplechunk = 1000; - /// actual sampling factor used - const double resamplingfactor = 44100.0 / 32040.5; + Sound.Utilities.SpeexResampler resampler; - //Sound.Utilities.IStereoResampler resampler = new Sound.Utilities.BizhawkResampler(false); - //Sound.Utilities.IStereoResampler resampler = new Sound.Utilities.SinkResampler(12); - //Sound.Utilities.IStereoResampler resampler = new Sound.Utilities.CubicResampler(); - //Sound.Utilities.IStereoResampler resampler = new Sound.Utilities.LinearResampler(); - Sound.Utilities.SpeexResampler resampler = new Sound.Utilities.SpeexResampler(6); - - Sound.MetaspuSoundProvider metaspu = new Sound.MetaspuSoundProvider(Sound.ESynchMethod.ESynchMethod_V); + Sound.MetaspuSoundProvider metaspu; void snes_audio_sample(ushort left, ushort right) { - - AudioInBuffer.Enqueue((short)left); - AudioInBuffer.Enqueue((short)right); - - /* - try - { - // fixme: i get all sorts of crashes if i do the resampling in here. what? - - - if (AudioInBuffer.Count >= resamplechunk) - { - resampler.ResampleChunk(AudioInBuffer, AudioOutBuffer, false); - // drain into the metaspu immediately - // we could skip this step and drain directly by changing SampleBuffers - while (AudioOutBuffer.Count > 0) - metaspu.buffer.enqueue_sample(AudioOutBuffer.Dequeue(), AudioOutBuffer.Dequeue()); - } - - } - catch (Exception e) - { - System.Windows.Forms.MessageBox.Show(e.ToString()); - AudioOutBuffer.Clear(); - } - */ - - + resampler.EnqueueSample((short)left, (short)right); } @@ -536,26 +500,7 @@ namespace BizHawk.Emulation.Consoles.Nintendo.SNES public void GetSamples(short[] samples) { - // do resampling here, instead of when the samples are generated; see "fixme" comment in snes_audio_sample() - - if (true) - { - //lock (AudioInBuffer) - //{ - resampler.ResampleChunk(AudioInBuffer, AudioOutBuffer, false); - //} - // drain into the metaspu immediately - // we could skip this step and drain directly by changing SampleBuffers implementation - while (AudioOutBuffer.Count > 0) - { - short l = AudioOutBuffer.Dequeue(); - short r = AudioOutBuffer.Dequeue(); - //metaspu.buffer.enqueue_sample(AudioOutBuffer.Dequeue(), AudioOutBuffer.Dequeue()); - metaspu.buffer.enqueue_sample(l, r); - //dbgs.Write(l); - //dbgs.Write(r); - } - } + resampler.Flush(); metaspu.GetSamples(samples); } diff --git a/BizHawk.Emulation/Sound/Utilities/Metaspu.cs b/BizHawk.Emulation/Sound/Utilities/Metaspu.cs index ca776eae6c..48b6789c3c 100644 --- a/BizHawk.Emulation/Sound/Utilities/Metaspu.cs +++ b/BizHawk.Emulation/Sound/Utilities/Metaspu.cs @@ -43,6 +43,10 @@ namespace BizHawk.Emulation.Sound void clear(); //returns the number of samples actually supplied, which may not match the number requested + // ^^ what the hell is that supposed to mean. + // the entire point of an ISynchronzingAudioBuffer + // is to provide exact amounts of output samples, + // even when the input provided varies.... int output_samples(short[] buf, int samples_requested); }; diff --git a/BizHawk.Emulation/Sound/Utilities/SpeexResampler.cs b/BizHawk.Emulation/Sound/Utilities/SpeexResampler.cs index ea95eed21b..e442c43c3d 100644 --- a/BizHawk.Emulation/Sound/Utilities/SpeexResampler.cs +++ b/BizHawk.Emulation/Sound/Utilities/SpeexResampler.cs @@ -9,7 +9,7 @@ namespace BizHawk.Emulation.Sound.Utilities /// /// junk wrapper around LibSpeexDSP. quite inefficient. will be replaced /// - public class SpeexResampler + public class SpeexResampler : IDisposable { static class LibSpeexDSP { @@ -203,6 +203,8 @@ namespace BizHawk.Emulation.Sound.Utilities [DllImport("libspeexdsp.dll", CallingConvention = CallingConvention.Cdecl)] public static extern void speex_resampler_get_output_stride(IntPtr st, ref uint stride); + /*these two functions don't exist in our version of the dll + /// /// Get the latency in input samples introduced by the resampler. /// @@ -219,6 +221,8 @@ namespace BizHawk.Emulation.Sound.Utilities [DllImport("libspeexdsp.dll", CallingConvention = CallingConvention.Cdecl)] public static extern int speex_resampler_get_output_latency(IntPtr st); + */ + /// /// Make sure that the first samples to go out of the resamplers don't have /// leading zeros. This is only useful before starting to use a newly created @@ -253,21 +257,108 @@ namespace BizHawk.Emulation.Sound.Utilities /// /// opaque pointer to state /// - IntPtr st; + IntPtr st = IntPtr.Zero; - public SpeexResampler(int quality) + /// + /// function to call to dispatch output + /// + Action drainer; + + short[] inbuf = new short[512]; + + short[] outbuf; + + /// + /// in buffer position in samples (not sample pairs) + /// + int inbufpos = 0; + + /// + /// throw an exception based on error state + /// + /// + static void CheckError(LibSpeexDSP.RESAMPLER_ERR e) { - LibSpeexDSP.RESAMPLER_ERR err = 0; - - st = LibSpeexDSP.speex_resampler_init_frac(2, 64081, 88200, 32041, 44100, quality, ref err); + switch (e) + { + case LibSpeexDSP.RESAMPLER_ERR.SUCCESS: + return; + case LibSpeexDSP.RESAMPLER_ERR.ALLOC_FAILED: + throw new InsufficientMemoryException("LibSpeexDSP: Alloc failed"); + case LibSpeexDSP.RESAMPLER_ERR.BAD_STATE: + throw new Exception("LibSpeexDSP: Bad state"); + case LibSpeexDSP.RESAMPLER_ERR.INVALID_ARG: + throw new ArgumentException("LibSpeexDSP: Bad Argument"); + case LibSpeexDSP.RESAMPLER_ERR.PTR_OVERLAP: + throw new Exception("LibSpeexDSP: Buffers cannot overlap"); + } } - public void StartSession(double ratio) + /// + /// + /// + /// 0 to 10 + /// numerator of srate change ratio (inrate / outrate) + /// demonenator of srate change ratio (inrate / outrate) + /// sampling rate in, rounded to nearest hz + /// sampling rate out, rounded to nearest hz + /// function which accepts output as produced + public SpeexResampler(int quality, uint rationum, uint ratioden, uint sratein, uint srateout, Action drainer) { - + LibSpeexDSP.RESAMPLER_ERR err = LibSpeexDSP.RESAMPLER_ERR.SUCCESS; + st = LibSpeexDSP.speex_resampler_init_frac(2, rationum, ratioden, sratein, srateout, quality, ref err); + + if (st == IntPtr.Zero) + throw new Exception("LibSpeexDSP returned null!"); + + CheckError(err); + + //System.Windows.Forms.MessageBox.Show(string.Format("inlat: {0} outlat: {1}", LibSpeexDSP.speex_resampler_get_input_latency(st), LibSpeexDSP.speex_resampler_get_output_latency(st))); + this.drainer = drainer; + + outbuf = new short[inbuf.Length * ratioden / rationum / 2 * 2 + 128]; + + //System.Windows.Forms.MessageBox.Show(string.Format("inbuf: {0} outbuf: {1}", inbuf.Length, outbuf.Length)); + + } + + /// + /// add a sample to the queue + /// + /// + /// + public void EnqueueSample(short left, short right) + { + inbuf[inbufpos++] = left; + inbuf[inbufpos++] = right; + + if (inbufpos == inbuf.Length) + Flush(); } + /// + /// flush as many input samples as possible, generating output samples right now + /// + public void Flush() + { + uint inal = (uint)inbufpos / 2; + + uint outal = (uint)outbuf.Length / 2; + + LibSpeexDSP.speex_resampler_process_interleaved_int(st, inbuf, ref inal, outbuf, ref outal); + + // reset inbuf + + Buffer.BlockCopy(inbuf, (int)inal * 2 * sizeof(short), inbuf, 0, inbufpos - (int)inal * 2); + inbufpos -= (int)inal * 2; + + // dispatch outbuf + drainer(outbuf, (int)outal); + } + + + /* public void ResampleChunk(Queue input, Queue output, bool finish) { while (input.Count > 0) @@ -308,6 +399,13 @@ namespace BizHawk.Emulation.Sound.Utilities } } } + */ + + public void Dispose() + { + LibSpeexDSP.speex_resampler_destroy(st); + st = IntPtr.Zero; + } } }