274 lines
6.7 KiB
C#
274 lines
6.7 KiB
C#
using System;
|
|
|
|
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
|
|
|
|
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)
|
|
{
|
|
this.w = w;
|
|
}
|
|
|
|
private short[] _samples = new short[0];
|
|
|
|
// how many extra audio samples there are (* fpsnum)
|
|
private long exaudio_num;
|
|
|
|
private bool pset = false;
|
|
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);
|
|
}
|
|
}
|
|
|
|
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);
|
|
exaudio_num += 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 (exaudio_num >= threshone)
|
|
{
|
|
// add frame once
|
|
w.AddFrame(v);
|
|
exaudio_num -= threshtotal;
|
|
}
|
|
else
|
|
{
|
|
Console.WriteLine("Dropped Frame!");
|
|
}
|
|
while (exaudio_num >= threshmore)
|
|
{
|
|
// add frame again!
|
|
w.AddFrame(v);
|
|
exaudio_num -= 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 = false;
|
|
|
|
protected int samplerate;
|
|
protected int channels;
|
|
protected int bits;
|
|
protected bool aset = false;
|
|
|
|
|
|
public new virtual void SetMovieParameters(int fpsnum, int fpsden)
|
|
{
|
|
if (vset)
|
|
{
|
|
throw new InvalidOperationException();
|
|
}
|
|
|
|
vset = true;
|
|
this.fpsnum = fpsnum;
|
|
this.fpsden = fpsden;
|
|
|
|
base.SetMovieParameters(fpsnum, fpsden);
|
|
}
|
|
|
|
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;
|
|
this.samplerate = sampleRate;
|
|
this.channels = channels;
|
|
this.bits = bits;
|
|
|
|
base.SetAudioParameters(sampleRate, channels, bits);
|
|
}
|
|
|
|
public new virtual void SetFrame(int frame)
|
|
{
|
|
// this writer will never support this capability
|
|
}
|
|
|
|
public new virtual void AddFrame(IVideoProvider source)
|
|
{
|
|
throw new InvalidOperationException("Must call AddAV()!");
|
|
}
|
|
|
|
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(System.Windows.Forms.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();
|
|
}
|
|
}
|
|
}
|