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

282 lines
7.4 KiB
C#
Raw Normal View History

using System;
2020-01-04 00:25:46 +00:00
using System.Windows.Forms;
using BizHawk.Emulation.Common;
namespace BizHawk.Client.EmuHawk
{
[VideoWriterIgnore]
public class AudioStretcher : AVStretcher
{
public AudioStretcher(IVideoWriter w)
{
2020-01-04 00:25:46 +00:00
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>
2020-01-04 00:25:46 +00:00
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)
{
2016-12-11 19:07:12 +00:00
throw new InvalidOperationException("Only async mode is supported, set async mode before passing in the sound provider");
}
2020-01-04 00:25:46 +00:00
if (!ASet || !VSet)
throw new InvalidOperationException("Must set params first!");
2020-01-04 00:25:46 +00:00
long nSampNum = Samplerate * (long)FpsDen + _soundRemainder;
long nsamp = nSampNum / FpsNum;
// exactly remember fractional parts of an audio sample
2020-01-04 00:25:46 +00:00
_soundRemainder = nSampNum % FpsNum;
2020-01-04 00:25:46 +00:00
samples = new short[nsamp * Channels];
asyncSoundProvider.GetSamplesAsync(samples);
2020-01-04 00:25:46 +00:00
samplesProvided = (int)nsamp;
2020-01-04 00:25:46 +00:00
W.AddFrame(v);
W.AddSamples(samples);
}
}
[VideoWriterIgnore]
public class VideoStretcher : AVStretcher
{
public VideoStretcher(IVideoWriter w)
{
2020-01-04 00:25:46 +00:00
W = w;
}
private short[] _samples = new short[0];
2020-01-04 00:25:46 +00:00
// how many extra audio samples there are (* fpsNum)
private long _exAudioNum;
2020-01-04 00:25:46 +00:00
private bool _pSet;
private long _threshOne;
private long _threshMore;
private long _threshTotal;
private void VerifyParams()
{
2020-01-04 00:25:46 +00:00
if (!ASet || !VSet)
2017-04-18 17:27:44 +00:00
{
throw new InvalidOperationException("Must set params first!");
2017-04-18 17:27:44 +00:00
}
2020-01-04 00:25:46 +00:00
if (!_pSet)
{
2020-01-04 00:25:46 +00:00
_pSet = true;
2020-01-04 00:25:46 +00:00
// each video frame committed counts as (fpsDen * samplerate / fpsNum) audio samples
_threshTotal = FpsDen * (long)Samplerate;
// blah blah blah
2020-01-04 00:25:46 +00:00
_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>
2020-01-04 00:25:46 +00:00
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();
2020-01-04 00:25:46 +00:00
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?
2020-01-04 00:25:46 +00:00
if (_exAudioNum >= _threshOne)
{
// add frame once
2020-01-04 00:25:46 +00:00
W.AddFrame(v);
_exAudioNum -= _threshTotal;
}
else
{
Console.WriteLine("Dropped Frame!");
}
2020-01-04 00:25:46 +00:00
while (_exAudioNum >= _threshMore)
{
// add frame again!
2020-01-04 00:25:46 +00:00
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
2020-01-04 00:25:46 +00:00
if (samples.Length == samplesProvided * Channels)
{
2020-01-04 00:25:46 +00:00
W.AddSamples(samples);
}
else
{
2020-01-04 00:25:46 +00:00
if (_samples.Length != samplesProvided * Channels)
2017-04-18 17:27:44 +00:00
{
2020-01-04 00:25:46 +00:00
_samples = new short[samplesProvided * Channels];
2017-04-18 17:27:44 +00:00
}
2020-01-04 00:25:46 +00:00
Buffer.BlockCopy(samples, 0, _samples, 0, samplesProvided * Channels * sizeof(short));
W.AddSamples(_samples);
}
}
}
2020-01-04 00:25:46 +00:00
public abstract class AVStretcher : VwWrap, IVideoWriter
{
2020-01-04 00:25:46 +00:00
protected int FpsNum;
protected int FpsDen;
protected bool VSet;
2020-01-04 00:25:46 +00:00
protected int Samplerate;
protected int Channels;
protected int Bits;
protected bool ASet;
/// <exception cref="InvalidOperationException">already set</exception>
2020-01-04 00:25:46 +00:00
public new virtual void SetMovieParameters(int fpsNum, int fpsDen)
{
2020-01-04 00:25:46 +00:00
if (VSet)
2017-04-18 17:27:44 +00:00
{
throw new InvalidOperationException();
2017-04-18 17:27:44 +00:00
}
2020-01-04 00:25:46 +00:00
VSet = true;
FpsNum = fpsNum;
FpsDen = fpsDen;
2020-01-04 00:25:46 +00:00
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)
{
2020-01-04 00:25:46 +00:00
if (ASet)
2017-04-18 17:27:44 +00:00
{
throw new InvalidOperationException();
2017-04-18 17:27:44 +00:00
}
if (bits != 16)
2017-04-18 17:27:44 +00:00
{
throw new InvalidOperationException("Only 16 bit audio is supported!");
2017-04-18 17:27:44 +00:00
}
2020-01-04 00:25:46 +00:00
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()!");
}
}
2020-01-04 00:25:46 +00:00
public abstract class VwWrap : IVideoWriter
{
2020-01-04 00:25:46 +00:00
protected IVideoWriter W;
2020-01-04 00:25:46 +00:00
public bool UsesAudio => W.UsesAudio;
2017-04-18 17:27:44 +00:00
2020-01-04 00:25:46 +00:00
public bool UsesVideo => W.UsesVideo;
public void SetVideoCodecToken(IDisposable token)
{
2020-01-04 00:25:46 +00:00
W.SetVideoCodecToken(token);
}
public void SetDefaultVideoCodecToken()
{
2020-01-04 00:25:46 +00:00
W.SetDefaultVideoCodecToken();
}
public void OpenFile(string baseName)
{
2020-01-04 00:25:46 +00:00
W.OpenFile(baseName);
}
public void CloseFile()
{
2020-01-04 00:25:46 +00:00
W.CloseFile();
}
public void SetFrame(int frame)
{
2020-01-04 00:25:46 +00:00
W.SetFrame(frame);
}
public void AddFrame(IVideoProvider source)
{
2020-01-04 00:25:46 +00:00
W.AddFrame(source);
}
public void AddSamples(short[] samples)
{
2020-01-04 00:25:46 +00:00
W.AddSamples(samples);
}
2020-01-04 00:25:46 +00:00
public IDisposable AcquireVideoCodecToken(IWin32Window hwnd)
{
2020-01-04 00:25:46 +00:00
return W.AcquireVideoCodecToken(hwnd);
}
2020-01-04 00:25:46 +00:00
public void SetMovieParameters(int fpsNum, int fpsDen)
{
2020-01-04 00:25:46 +00:00
W.SetMovieParameters(fpsNum, fpsDen);
}
public void SetVideoParameters(int width, int height)
{
2020-01-04 00:25:46 +00:00
W.SetVideoParameters(width, height);
}
public void SetAudioParameters(int sampleRate, int channels, int bits)
{
2020-01-04 00:25:46 +00:00
W.SetAudioParameters(sampleRate, channels, bits);
}
2020-01-04 00:25:46 +00:00
public void SetMetaData(string gameName, string authors, ulong lengthMs, ulong rerecords)
{
2020-01-04 00:25:46 +00:00
W.SetMetaData(gameName, authors, lengthMs, rerecords);
}
public string DesiredExtension()
{
2020-01-04 00:25:46 +00:00
return W.DesiredExtension();
}
public void Dispose()
{
2020-01-04 00:25:46 +00:00
W.Dispose();
}
}
}