2014-10-10 18:18:29 +00:00
using System ;
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 )
{
this . w = w ;
}
private long _soundRemainder ; // audio timekeeping for video dumping
2016-12-11 17:14:42 +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
}
2014-10-10 18:18:29 +00:00
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 ;
2014-11-14 17:48:18 +00:00
samples = new short [ nsamp * channels ] ;
2016-12-11 17:14:42 +00:00
asyncSoundProvider . GetSamplesAsync ( samples ) ;
2014-10-10 18:18:29 +00:00
samplesprovided = ( int ) nsamp ;
w . AddFrame ( v ) ;
w . AddSamples ( samples ) ;
}
}
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 )
{
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 )
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
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 ) ;
}
}
2016-12-11 17:14:42 +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 ( ) ;
2016-12-11 17:14:42 +00:00
syncSoundProvider . GetSamplesSync ( out samples , out samplesprovided ) ;
2014-10-10 18:18:29 +00:00
exaudio_num + = samplesprovided * ( long ) fpsnum ;
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?
2014-10-10 18:18:29 +00:00
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 )
2017-04-18 17:27:44 +00:00
{
2014-10-10 18:18:29 +00:00
_samples = new short [ samplesprovided * channels ] ;
2017-04-18 17:27:44 +00:00
}
2014-10-10 18:18:29 +00:00
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 )
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
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 )
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
}
2014-10-10 18:18:29 +00:00
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 ;
2017-04-18 17:27:44 +00:00
public bool UsesAudio = > w . UsesAudio ;
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 )
{
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 ( ) ;
}
}
}