BizHawk/BizHawk.Client.EmuHawk/AVOut/AVSync.cs

282 lines
7.4 KiB
C#

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
/// <exception cref="InvalidOperationException">
/// <paramref name="asyncSoundProvider"/>'s mode is not <see cref="SyncSoundMode.Async"/>, or
/// A/V parameters haven't been set (need to call <see cref="SetAudioParameters"/> and <see cref="SetMovieParameters"/>)
/// </exception>
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);
}
}
/// <exception cref="InvalidOperationException"><paramref name="syncSoundProvider"/>'s mode is not <see cref="SyncSoundMode.Sync"/></exception>
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;
/// <exception cref="InvalidOperationException">already set</exception>
public new virtual void SetMovieParameters(int fpsNum, int fpsDen)
{
if (VSet)
{
throw new InvalidOperationException();
}
VSet = true;
FpsNum = fpsNum;
FpsDen = fpsDen;
base.SetMovieParameters(fpsNum, fpsDen);
}
/// <exception cref="InvalidOperationException">already set, or <paramref name="bits"/> is not <c>16</c></exception>
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
}
/// <exception cref="InvalidOperationException">always</exception>
public new virtual void AddFrame(IVideoProvider source)
{
throw new InvalidOperationException("Must call AddAV()!");
}
/// <exception cref="InvalidOperationException">always</exception>
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();
}
}
}