using System; using System.Windows.Forms; using BizHawk.Emulation.Common; namespace BizHawk.Client.EmuHawk { [VideoWriterIgnore] public class AudioStretcher : AVStretcher { public AudioStretcher(IVideoWriter w) { this.W = w; } private long _soundRemainder; // audio timekeeping for video dumping /// /// 's mode is not , or /// A/V parameters haven't been set (need to call and ) /// public void DumpAV(IVideoProvider v, ISoundProvider asyncSoundProvider, out short[] samples, out int samplesProvided) { // Sound refactor TODO: we could try set it here, but we want the client to be responsible for mode switching? There may be non-trivial complications with when to switch modes that we don't want this object worrying about if (asyncSoundProvider.SyncMode != SyncSoundMode.Async) { throw new InvalidOperationException("Only async mode is supported, set async mode before passing in the sound provider"); } if (!ASet || !VSet) throw new InvalidOperationException("Must set params first!"); long nSampNum = Samplerate * (long)FpsDen + _soundRemainder; long nsamp = nSampNum / FpsNum; // exactly remember fractional parts of an audio sample _soundRemainder = nSampNum % FpsNum; samples = new short[nsamp * Channels]; asyncSoundProvider.GetSamplesAsync(samples); samplesProvided = (int)nsamp; W.AddFrame(v); W.AddSamples(samples); } } [VideoWriterIgnore] public class VideoStretcher : AVStretcher { public VideoStretcher(IVideoWriter w) { W = w; } private short[] _samples = new short[0]; // how many extra audio samples there are (* fpsNum) private long _exAudioNum; private bool _pSet; private long _threshOne; private long _threshMore; private long _threshTotal; private void VerifyParams() { if (!ASet || !VSet) { throw new InvalidOperationException("Must set params first!"); } if (!_pSet) { _pSet = true; // each video frame committed counts as (fpsDen * samplerate / fpsNum) audio samples _threshTotal = FpsDen * (long)Samplerate; // blah blah blah _threshOne = (long)(_threshTotal * 0.4); _threshMore = (long)(_threshTotal * 0.9); } } /// 's mode is not public void DumpAV(IVideoProvider v, ISoundProvider syncSoundProvider, out short[] samples, out int samplesProvided) { // Sound refactor TODO: we could just set it here, but we want the client to be responsible for mode switching? There may be non-trivial complications with when to switch modes that we don't want this object worrying about if (syncSoundProvider.SyncMode != SyncSoundMode.Sync) { throw new InvalidOperationException("Only sync mode is supported, set sync mode before passing in the sound provider"); } VerifyParams(); syncSoundProvider.GetSamplesSync(out samples, out samplesProvided); _exAudioNum += samplesProvided * (long)FpsNum; // todo: scan for duplicate frames (ie, video content exactly matches previous frame) and for them, skip the threshone step // this is a good idea, but expensive on time. is it worth it? if (_exAudioNum >= _threshOne) { // add frame once W.AddFrame(v); _exAudioNum -= _threshTotal; } else { Console.WriteLine("Dropped Frame!"); } while (_exAudioNum >= _threshMore) { // add frame again! W.AddFrame(v); _exAudioNum -= _threshTotal; Console.WriteLine("Dupped Frame!"); } // a bit of hackey due to the fact that this api can't read a // usable buffer length separately from the actual length of the buffer if (samples.Length == samplesProvided * Channels) { W.AddSamples(samples); } else { if (_samples.Length != samplesProvided * Channels) { _samples = new short[samplesProvided * Channels]; } Buffer.BlockCopy(samples, 0, _samples, 0, samplesProvided * Channels * sizeof(short)); W.AddSamples(_samples); } } } public abstract class AVStretcher : VwWrap, IVideoWriter { protected int FpsNum; protected int FpsDen; protected bool VSet; protected int Samplerate; protected int Channels; protected int Bits; protected bool ASet; /// already set public new virtual void SetMovieParameters(int fpsNum, int fpsDen) { if (VSet) { throw new InvalidOperationException(); } VSet = true; FpsNum = fpsNum; FpsDen = fpsDen; base.SetMovieParameters(fpsNum, fpsDen); } /// already set, or is not 16 public new virtual void SetAudioParameters(int sampleRate, int channels, int bits) { if (ASet) { throw new InvalidOperationException(); } if (bits != 16) { throw new InvalidOperationException("Only 16 bit audio is supported!"); } ASet = true; Samplerate = sampleRate; Channels = channels; Bits = bits; base.SetAudioParameters(sampleRate, channels, bits); } public new virtual void SetFrame(int frame) { // this writer will never support this capability } /// always public new virtual void AddFrame(IVideoProvider source) { throw new InvalidOperationException("Must call AddAV()!"); } /// always public new virtual void AddSamples(short[] samples) { throw new InvalidOperationException("Must call AddAV()!"); } } public abstract class VwWrap : IVideoWriter { protected IVideoWriter W; public bool UsesAudio => W.UsesAudio; public bool UsesVideo => W.UsesVideo; public void SetVideoCodecToken(IDisposable token) { W.SetVideoCodecToken(token); } public void SetDefaultVideoCodecToken() { W.SetDefaultVideoCodecToken(); } public void OpenFile(string baseName) { W.OpenFile(baseName); } public void CloseFile() { W.CloseFile(); } public void SetFrame(int frame) { W.SetFrame(frame); } public void AddFrame(IVideoProvider source) { W.AddFrame(source); } public void AddSamples(short[] samples) { W.AddSamples(samples); } public IDisposable AcquireVideoCodecToken(IWin32Window hwnd) { return W.AcquireVideoCodecToken(hwnd); } public void SetMovieParameters(int fpsNum, int fpsDen) { W.SetMovieParameters(fpsNum, fpsDen); } public void SetVideoParameters(int width, int height) { W.SetVideoParameters(width, height); } public void SetAudioParameters(int sampleRate, int channels, int bits) { W.SetAudioParameters(sampleRate, channels, bits); } public void SetMetaData(string gameName, string authors, ulong lengthMs, ulong rerecords) { W.SetMetaData(gameName, authors, lengthMs, rerecords); } public string DesiredExtension() { return W.DesiredExtension(); } public void Dispose() { W.Dispose(); } } }