2014-10-10 18:18:29 +00:00
using System ;
2020-01-04 00:25:46 +00:00
using System.Windows.Forms ;
2014-10-10 18:18:29 +00:00
using BizHawk.Emulation.Common ;
namespace BizHawk.Client.EmuHawk
{
2014-10-10 18:24:11 +00:00
[VideoWriterIgnore]
2014-10-10 18:18:29 +00:00
public class AudioStretcher : AVStretcher
{
public AudioStretcher ( IVideoWriter w )
{
2020-01-04 00:25:46 +00:00
this . W = w ;
2014-10-10 18:18:29 +00:00
}
private long _soundRemainder ; // audio timekeeping for video dumping
2020-01-01 20:06:48 +00:00
/// <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 )
2014-10-10 18:18:29 +00:00
{
2016-12-11 17:14:42 +00:00
// 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" ) ;
2016-12-11 17:14:42 +00:00
}
2020-01-04 00:25:46 +00:00
if ( ! ASet | | ! VSet )
2014-10-10 18:18:29 +00:00
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 ;
2014-10-10 18:18:29 +00:00
// exactly remember fractional parts of an audio sample
2020-01-04 00:25:46 +00:00
_soundRemainder = nSampNum % FpsNum ;
2014-10-10 18:18:29 +00:00
2020-01-04 00:25:46 +00:00
samples = new short [ nsamp * Channels ] ;
2016-12-11 17:14:42 +00:00
asyncSoundProvider . GetSamplesAsync ( samples ) ;
2020-01-04 00:25:46 +00:00
samplesProvided = ( int ) nsamp ;
2014-10-10 18:18:29 +00:00
2020-01-04 00:25:46 +00:00
W . AddFrame ( v ) ;
W . AddSamples ( samples ) ;
2014-10-10 18:18:29 +00:00
}
}
2014-10-10 18:24:11 +00:00
[VideoWriterIgnore]
2014-10-10 18:18:29 +00:00
public class VideoStretcher : AVStretcher
{
public VideoStretcher ( IVideoWriter w )
{
2020-01-04 00:25:46 +00:00
W = w ;
2014-10-10 18:18:29 +00:00
}
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 ;
2014-10-10 18:18:29 +00:00
2020-01-04 00:25:46 +00:00
private bool _pSet ;
private long _threshOne ;
private long _threshMore ;
private long _threshTotal ;
2014-10-10 18:18:29 +00:00
private void VerifyParams ( )
{
2020-01-04 00:25:46 +00:00
if ( ! ASet | | ! VSet )
2017-04-18 17:27:44 +00:00
{
2014-10-10 18:18:29 +00:00
throw new InvalidOperationException ( "Must set params first!" ) ;
2017-04-18 17:27:44 +00:00
}
2014-10-10 18:18:29 +00:00
2020-01-04 00:25:46 +00:00
if ( ! _pSet )
2014-10-10 18:18:29 +00:00
{
2020-01-04 00:25:46 +00:00
_pSet = true ;
2014-10-10 18:18:29 +00:00
2020-01-04 00:25:46 +00:00
// each video frame committed counts as (fpsDen * samplerate / fpsNum) audio samples
_threshTotal = FpsDen * ( long ) Samplerate ;
2014-10-10 18:18:29 +00:00
// blah blah blah
2020-01-04 00:25:46 +00:00
_threshOne = ( long ) ( _threshTotal * 0.4 ) ;
_threshMore = ( long ) ( _threshTotal * 0.9 ) ;
2014-10-10 18:18:29 +00:00
}
}
2020-01-01 20:06:48 +00:00
/// <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 )
2014-10-10 18:18:29 +00:00
{
2016-12-11 17:14:42 +00:00
// 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" ) ;
}
2014-10-10 18:18:29 +00:00
VerifyParams ( ) ;
2020-01-04 00:25:46 +00:00
syncSoundProvider . GetSamplesSync ( out samples , out samplesProvided ) ;
_exAudioNum + = samplesProvided * ( long ) FpsNum ;
2014-10-10 18:18:29 +00:00
2014-10-11 03:33:09 +00:00
// 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 )
2014-10-10 18:18:29 +00:00
{
// add frame once
2020-01-04 00:25:46 +00:00
W . AddFrame ( v ) ;
_exAudioNum - = _threshTotal ;
2014-10-10 18:18:29 +00:00
}
else
{
Console . WriteLine ( "Dropped Frame!" ) ;
}
2020-01-04 00:25:46 +00:00
while ( _exAudioNum > = _threshMore )
2014-10-10 18:18:29 +00:00
{
// add frame again!
2020-01-04 00:25:46 +00:00
W . AddFrame ( v ) ;
_exAudioNum - = _threshTotal ;
2014-10-10 18:18:29 +00:00
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 )
2014-10-10 18:18:29 +00:00
{
2020-01-04 00:25:46 +00:00
W . AddSamples ( samples ) ;
2014-10-10 18:18:29 +00:00
}
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
}
2014-10-10 18:18:29 +00:00
2020-01-04 00:25:46 +00:00
Buffer . BlockCopy ( samples , 0 , _samples , 0 , samplesProvided * Channels * sizeof ( short ) ) ;
W . AddSamples ( _samples ) ;
2014-10-10 18:18:29 +00:00
}
}
}
2020-01-04 00:25:46 +00:00
public abstract class AVStretcher : VwWrap , IVideoWriter
2014-10-10 18:18:29 +00:00
{
2020-01-04 00:25:46 +00:00
protected int FpsNum ;
protected int FpsDen ;
protected bool VSet ;
2014-10-10 18:18:29 +00:00
2020-01-04 00:25:46 +00:00
protected int Samplerate ;
protected int Channels ;
protected int Bits ;
protected bool ASet ;
2014-10-10 18:18:29 +00:00
2020-01-01 20:06:48 +00:00
/// <exception cref="InvalidOperationException">already set</exception>
2020-01-04 00:25:46 +00:00
public new virtual void SetMovieParameters ( int fpsNum , int fpsDen )
2014-10-10 18:18:29 +00:00
{
2020-01-04 00:25:46 +00:00
if ( VSet )
2017-04-18 17:27:44 +00:00
{
2014-10-10 18:18:29 +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 ;
2014-10-10 18:18:29 +00:00
2020-01-04 00:25:46 +00:00
base . SetMovieParameters ( fpsNum , fpsDen ) ;
2014-10-10 18:18:29 +00:00
}
2020-01-01 20:06:48 +00:00
/// <exception cref="InvalidOperationException">already set, or <paramref name="bits"/> is not <c>16</c></exception>
2014-10-10 18:18:29 +00:00
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
{
2014-10-10 18:18:29 +00:00
throw new InvalidOperationException ( ) ;
2017-04-18 17:27:44 +00:00
}
2014-10-10 18:18:29 +00:00
if ( bits ! = 16 )
2017-04-18 17:27:44 +00:00
{
2014-10-10 18:18:29 +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 ;
2014-10-10 18:18:29 +00:00
base . SetAudioParameters ( sampleRate , channels , bits ) ;
}
public new virtual void SetFrame ( int frame )
{
// this writer will never support this capability
}
2020-01-01 20:06:48 +00:00
/// <exception cref="InvalidOperationException">always</exception>
2014-10-10 18:18:29 +00:00
public new virtual void AddFrame ( IVideoProvider source )
{
throw new InvalidOperationException ( "Must call AddAV()!" ) ;
}
2020-01-01 20:06:48 +00:00
/// <exception cref="InvalidOperationException">always</exception>
2014-10-10 18:18:29 +00:00
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
2014-10-10 18:18:29 +00:00
{
2020-01-04 00:25:46 +00:00
protected IVideoWriter W ;
2014-10-10 18:18:29 +00:00
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 ;
2016-03-05 23:19:12 +00:00
2014-10-10 18:18:29 +00:00
public void SetVideoCodecToken ( IDisposable token )
{
2020-01-04 00:25:46 +00:00
W . SetVideoCodecToken ( token ) ;
2014-10-10 18:18:29 +00:00
}
public void SetDefaultVideoCodecToken ( )
{
2020-01-04 00:25:46 +00:00
W . SetDefaultVideoCodecToken ( ) ;
2014-10-10 18:18:29 +00:00
}
public void OpenFile ( string baseName )
{
2020-01-04 00:25:46 +00:00
W . OpenFile ( baseName ) ;
2014-10-10 18:18:29 +00:00
}
public void CloseFile ( )
{
2020-01-04 00:25:46 +00:00
W . CloseFile ( ) ;
2014-10-10 18:18:29 +00:00
}
public void SetFrame ( int frame )
{
2020-01-04 00:25:46 +00:00
W . SetFrame ( frame ) ;
2014-10-10 18:18:29 +00:00
}
public void AddFrame ( IVideoProvider source )
{
2020-01-04 00:25:46 +00:00
W . AddFrame ( source ) ;
2014-10-10 18:18:29 +00:00
}
public void AddSamples ( short [ ] samples )
{
2020-01-04 00:25:46 +00:00
W . AddSamples ( samples ) ;
2014-10-10 18:18:29 +00:00
}
2020-01-04 00:25:46 +00:00
public IDisposable AcquireVideoCodecToken ( IWin32Window hwnd )
2014-10-10 18:18:29 +00:00
{
2020-01-04 00:25:46 +00:00
return W . AcquireVideoCodecToken ( hwnd ) ;
2014-10-10 18:18:29 +00:00
}
2020-01-04 00:25:46 +00:00
public void SetMovieParameters ( int fpsNum , int fpsDen )
2014-10-10 18:18:29 +00:00
{
2020-01-04 00:25:46 +00:00
W . SetMovieParameters ( fpsNum , fpsDen ) ;
2014-10-10 18:18:29 +00:00
}
public void SetVideoParameters ( int width , int height )
{
2020-01-04 00:25:46 +00:00
W . SetVideoParameters ( width , height ) ;
2014-10-10 18:18:29 +00:00
}
public void SetAudioParameters ( int sampleRate , int channels , int bits )
{
2020-01-04 00:25:46 +00:00
W . SetAudioParameters ( sampleRate , channels , bits ) ;
2014-10-10 18:18:29 +00:00
}
2020-01-04 00:25:46 +00:00
public void SetMetaData ( string gameName , string authors , ulong lengthMs , ulong rerecords )
2014-10-10 18:18:29 +00:00
{
2020-01-04 00:25:46 +00:00
W . SetMetaData ( gameName , authors , lengthMs , rerecords ) ;
2014-10-10 18:18:29 +00:00
}
public string DesiredExtension ( )
{
2020-01-04 00:25:46 +00:00
return W . DesiredExtension ( ) ;
2014-10-10 18:18:29 +00:00
}
public void Dispose ( )
{
2020-01-04 00:25:46 +00:00
W . Dispose ( ) ;
2014-10-10 18:18:29 +00:00
}
}
}