cleanups in AV code
This commit is contained in:
parent
b875f46a95
commit
101a403420
|
@ -1,4 +1,5 @@
|
|||
using System;
|
||||
using System.Windows.Forms;
|
||||
|
||||
using BizHawk.Emulation.Common;
|
||||
|
||||
|
@ -9,7 +10,7 @@ namespace BizHawk.Client.EmuHawk
|
|||
{
|
||||
public AudioStretcher(IVideoWriter w)
|
||||
{
|
||||
this.w = w;
|
||||
this.W = w;
|
||||
}
|
||||
|
||||
private long _soundRemainder; // audio timekeeping for video dumping
|
||||
|
@ -18,7 +19,7 @@ namespace BizHawk.Client.EmuHawk
|
|||
/// <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)
|
||||
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)
|
||||
|
@ -26,21 +27,21 @@ namespace BizHawk.Client.EmuHawk
|
|||
throw new InvalidOperationException("Only async mode is supported, set async mode before passing in the sound provider");
|
||||
}
|
||||
|
||||
if (!aset || !vset)
|
||||
if (!ASet || !VSet)
|
||||
throw new InvalidOperationException("Must set params first!");
|
||||
|
||||
long nsampnum = samplerate * (long)fpsden + _soundRemainder;
|
||||
long nsamp = nsampnum / fpsnum;
|
||||
long nSampNum = Samplerate * (long)FpsDen + _soundRemainder;
|
||||
long nsamp = nSampNum / FpsNum;
|
||||
|
||||
// exactly remember fractional parts of an audio sample
|
||||
_soundRemainder = nsampnum % fpsnum;
|
||||
_soundRemainder = nSampNum % FpsNum;
|
||||
|
||||
samples = new short[nsamp * channels];
|
||||
samples = new short[nsamp * Channels];
|
||||
asyncSoundProvider.GetSamplesAsync(samples);
|
||||
samplesprovided = (int)nsamp;
|
||||
samplesProvided = (int)nsamp;
|
||||
|
||||
w.AddFrame(v);
|
||||
w.AddSamples(samples);
|
||||
W.AddFrame(v);
|
||||
W.AddSamples(samples);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -49,41 +50,41 @@ namespace BizHawk.Client.EmuHawk
|
|||
{
|
||||
public VideoStretcher(IVideoWriter w)
|
||||
{
|
||||
this.w = w;
|
||||
W = w;
|
||||
}
|
||||
|
||||
private short[] _samples = new short[0];
|
||||
|
||||
// how many extra audio samples there are (* fpsnum)
|
||||
private long exaudio_num;
|
||||
// how many extra audio samples there are (* fpsNum)
|
||||
private long _exAudioNum;
|
||||
|
||||
private bool pset = false;
|
||||
private long threshone;
|
||||
private long threshmore;
|
||||
private long threshtotal;
|
||||
private bool _pSet;
|
||||
private long _threshOne;
|
||||
private long _threshMore;
|
||||
private long _threshTotal;
|
||||
|
||||
private void VerifyParams()
|
||||
{
|
||||
if (!aset || !vset)
|
||||
if (!ASet || !VSet)
|
||||
{
|
||||
throw new InvalidOperationException("Must set params first!");
|
||||
}
|
||||
|
||||
if (!pset)
|
||||
if (!_pSet)
|
||||
{
|
||||
pset = true;
|
||||
_pSet = true;
|
||||
|
||||
// each video frame committed counts as (fpsden * samplerate / fpsnum) audio samples
|
||||
threshtotal = fpsden * (long)samplerate;
|
||||
// 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);
|
||||
_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)
|
||||
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)
|
||||
|
@ -92,78 +93,78 @@ namespace BizHawk.Client.EmuHawk
|
|||
}
|
||||
|
||||
VerifyParams();
|
||||
syncSoundProvider.GetSamplesSync(out samples, out samplesprovided);
|
||||
exaudio_num += samplesprovided * (long)fpsnum;
|
||||
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 (exaudio_num >= threshone)
|
||||
if (_exAudioNum >= _threshOne)
|
||||
{
|
||||
// add frame once
|
||||
w.AddFrame(v);
|
||||
exaudio_num -= threshtotal;
|
||||
W.AddFrame(v);
|
||||
_exAudioNum -= _threshTotal;
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine("Dropped Frame!");
|
||||
}
|
||||
while (exaudio_num >= threshmore)
|
||||
while (_exAudioNum >= _threshMore)
|
||||
{
|
||||
// add frame again!
|
||||
w.AddFrame(v);
|
||||
exaudio_num -= threshtotal;
|
||||
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)
|
||||
if (samples.Length == samplesProvided * Channels)
|
||||
{
|
||||
w.AddSamples(samples);
|
||||
W.AddSamples(samples);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_samples.Length != samplesprovided * channels)
|
||||
if (_samples.Length != samplesProvided * Channels)
|
||||
{
|
||||
_samples = new short[samplesprovided * channels];
|
||||
_samples = new short[samplesProvided * Channels];
|
||||
}
|
||||
|
||||
Buffer.BlockCopy(samples, 0, _samples, 0, samplesprovided * channels * sizeof(short));
|
||||
w.AddSamples(_samples);
|
||||
Buffer.BlockCopy(samples, 0, _samples, 0, samplesProvided * Channels * sizeof(short));
|
||||
W.AddSamples(_samples);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class AVStretcher : VWWrap, IVideoWriter
|
||||
public abstract class AVStretcher : VwWrap, IVideoWriter
|
||||
{
|
||||
protected int fpsnum;
|
||||
protected int fpsden;
|
||||
protected bool vset = false;
|
||||
protected int FpsNum;
|
||||
protected int FpsDen;
|
||||
protected bool VSet;
|
||||
|
||||
protected int samplerate;
|
||||
protected int channels;
|
||||
protected int bits;
|
||||
protected bool aset = false;
|
||||
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)
|
||||
public new virtual void SetMovieParameters(int fpsNum, int fpsDen)
|
||||
{
|
||||
if (vset)
|
||||
if (VSet)
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
vset = true;
|
||||
this.fpsnum = fpsnum;
|
||||
this.fpsden = fpsden;
|
||||
VSet = true;
|
||||
FpsNum = fpsNum;
|
||||
FpsDen = fpsDen;
|
||||
|
||||
base.SetMovieParameters(fpsnum, 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)
|
||||
if (ASet)
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
@ -173,10 +174,10 @@ namespace BizHawk.Client.EmuHawk
|
|||
throw new InvalidOperationException("Only 16 bit audio is supported!");
|
||||
}
|
||||
|
||||
aset = true;
|
||||
this.samplerate = sampleRate;
|
||||
this.channels = channels;
|
||||
this.bits = bits;
|
||||
ASet = true;
|
||||
Samplerate = sampleRate;
|
||||
Channels = channels;
|
||||
Bits = bits;
|
||||
|
||||
base.SetAudioParameters(sampleRate, channels, bits);
|
||||
}
|
||||
|
@ -197,85 +198,84 @@ namespace BizHawk.Client.EmuHawk
|
|||
{
|
||||
throw new InvalidOperationException("Must call AddAV()!");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public abstract class VWWrap : IVideoWriter
|
||||
public abstract class VwWrap : IVideoWriter
|
||||
{
|
||||
protected IVideoWriter w;
|
||||
protected IVideoWriter W;
|
||||
|
||||
public bool UsesAudio => w.UsesAudio;
|
||||
public bool UsesAudio => W.UsesAudio;
|
||||
|
||||
public bool UsesVideo => w.UsesVideo;
|
||||
public bool UsesVideo => W.UsesVideo;
|
||||
|
||||
public void SetVideoCodecToken(IDisposable token)
|
||||
{
|
||||
w.SetVideoCodecToken(token);
|
||||
W.SetVideoCodecToken(token);
|
||||
}
|
||||
|
||||
public void SetDefaultVideoCodecToken()
|
||||
{
|
||||
w.SetDefaultVideoCodecToken();
|
||||
W.SetDefaultVideoCodecToken();
|
||||
}
|
||||
|
||||
public void OpenFile(string baseName)
|
||||
{
|
||||
w.OpenFile(baseName);
|
||||
W.OpenFile(baseName);
|
||||
}
|
||||
|
||||
public void CloseFile()
|
||||
{
|
||||
w.CloseFile();
|
||||
W.CloseFile();
|
||||
}
|
||||
|
||||
public void SetFrame(int frame)
|
||||
{
|
||||
w.SetFrame(frame);
|
||||
W.SetFrame(frame);
|
||||
}
|
||||
|
||||
public void AddFrame(IVideoProvider source)
|
||||
{
|
||||
w.AddFrame(source);
|
||||
W.AddFrame(source);
|
||||
}
|
||||
|
||||
public void AddSamples(short[] samples)
|
||||
{
|
||||
w.AddSamples(samples);
|
||||
W.AddSamples(samples);
|
||||
}
|
||||
|
||||
public IDisposable AcquireVideoCodecToken(System.Windows.Forms.IWin32Window hwnd)
|
||||
public IDisposable AcquireVideoCodecToken(IWin32Window hwnd)
|
||||
{
|
||||
return w.AcquireVideoCodecToken(hwnd);
|
||||
return W.AcquireVideoCodecToken(hwnd);
|
||||
}
|
||||
|
||||
public void SetMovieParameters(int fpsnum, int fpsden)
|
||||
public void SetMovieParameters(int fpsNum, int fpsDen)
|
||||
{
|
||||
w.SetMovieParameters(fpsnum, fpsden);
|
||||
W.SetMovieParameters(fpsNum, fpsDen);
|
||||
}
|
||||
|
||||
public void SetVideoParameters(int width, int height)
|
||||
{
|
||||
w.SetVideoParameters(width, height);
|
||||
W.SetVideoParameters(width, height);
|
||||
}
|
||||
|
||||
public void SetAudioParameters(int sampleRate, int channels, int bits)
|
||||
{
|
||||
w.SetAudioParameters(sampleRate, channels, bits);
|
||||
W.SetAudioParameters(sampleRate, channels, bits);
|
||||
}
|
||||
|
||||
public void SetMetaData(string gameName, string authors, ulong lengthMS, ulong rerecords)
|
||||
public void SetMetaData(string gameName, string authors, ulong lengthMs, ulong rerecords)
|
||||
{
|
||||
w.SetMetaData(gameName, authors, lengthMS, rerecords);
|
||||
W.SetMetaData(gameName, authors, lengthMs, rerecords);
|
||||
}
|
||||
|
||||
public string DesiredExtension()
|
||||
{
|
||||
return w.DesiredExtension();
|
||||
return W.DesiredExtension();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
w.Dispose();
|
||||
W.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -345,15 +345,15 @@ namespace BizHawk.Client.EmuHawk
|
|||
/// <summary>
|
||||
/// set basic movie timing parameters for the avi file. must be set before the file is opened.
|
||||
/// </summary>
|
||||
public void SetMovieParameters(int fps, int fps_scale)
|
||||
public void SetMovieParameters(int fpsNum, int fpsDen)
|
||||
{
|
||||
bool change = false;
|
||||
|
||||
change |= fps != parameters.fps;
|
||||
parameters.fps = fps;
|
||||
change |= fpsNum != parameters.fps;
|
||||
parameters.fps = fpsNum;
|
||||
|
||||
change |= parameters.fps_scale != fps_scale;
|
||||
parameters.fps_scale = fps_scale;
|
||||
change |= parameters.fps_scale != fpsDen;
|
||||
parameters.fps_scale = fpsDen;
|
||||
|
||||
if (change)
|
||||
{
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
using System;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Imaging;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
using BizHawk.Emulation.Common;
|
||||
|
||||
|
@ -12,11 +14,11 @@ namespace BizHawk.Client.EmuHawk
|
|||
{
|
||||
private Bitmap _bmp;
|
||||
|
||||
public BmpVideoProvider(Bitmap bmp, int vsyncnum, int vsyncden)
|
||||
public BmpVideoProvider(Bitmap bmp, int vsyncNum, int vsyncDen)
|
||||
{
|
||||
_bmp = bmp;
|
||||
VsyncNumerator = vsyncnum;
|
||||
VsyncDenominator = vsyncden;
|
||||
VsyncNumerator = vsyncNum;
|
||||
VsyncDenominator = vsyncDen;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
|
@ -31,12 +33,12 @@ namespace BizHawk.Client.EmuHawk
|
|||
public int[] GetVideoBuffer()
|
||||
{
|
||||
// is there a faster way to do this?
|
||||
var data = _bmp.LockBits(new Rectangle(0, 0, _bmp.Width, _bmp.Height), System.Drawing.Imaging.ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
|
||||
var data = _bmp.LockBits(new Rectangle(0, 0, _bmp.Width, _bmp.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
|
||||
|
||||
int[] ret = new int[_bmp.Width * _bmp.Height];
|
||||
|
||||
// won't work if stride is messed up
|
||||
System.Runtime.InteropServices.Marshal.Copy(data.Scan0, ret, 0, _bmp.Width * _bmp.Height);
|
||||
Marshal.Copy(data.Scan0, ret, 0, _bmp.Width * _bmp.Height);
|
||||
_bmp.UnlockBits(data);
|
||||
return ret;
|
||||
}
|
||||
|
@ -52,8 +54,8 @@ namespace BizHawk.Client.EmuHawk
|
|||
|
||||
public int BackgroundColor => 0;
|
||||
|
||||
public int VsyncNumerator { get; private set; }
|
||||
public int VsyncNumerator { get; }
|
||||
|
||||
public int VsyncDenominator { get; private set; }
|
||||
public int VsyncDenominator { get; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -225,10 +225,10 @@ namespace BizHawk.Client.EmuHawk
|
|||
/// </summary>
|
||||
private int fpsnum, fpsden, width, height, sampleRate, channels;
|
||||
|
||||
public void SetMovieParameters(int fpsnum, int fpsden)
|
||||
public void SetMovieParameters(int fpsNum, int fpsDen)
|
||||
{
|
||||
this.fpsnum = fpsnum;
|
||||
this.fpsden = fpsden;
|
||||
this.fpsnum = fpsNum;
|
||||
this.fpsden = fpsDen;
|
||||
}
|
||||
|
||||
public void SetVideoParameters(int width, int height)
|
||||
|
|
|
@ -220,10 +220,10 @@ namespace BizHawk.Client.EmuHawk
|
|||
Delay[1] = (byte)(delay >> 8 & 0xff);
|
||||
}
|
||||
|
||||
public void SetMovieParameters(int fpsnum, int fpsden)
|
||||
public void SetMovieParameters(int fpsNum, int fpsDen)
|
||||
{
|
||||
this.fpsnum = fpsnum;
|
||||
this.fpsden = fpsden;
|
||||
this.fpsnum = fpsNum;
|
||||
this.fpsden = fpsDen;
|
||||
CalcDelay();
|
||||
}
|
||||
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using BizHawk.Emulation.Common;
|
||||
using BizHawk.Common.ReflectionExtensions;
|
||||
|
||||
namespace BizHawk.Client.EmuHawk
|
||||
{
|
||||
|
@ -19,12 +17,12 @@ namespace BizHawk.Client.EmuHawk
|
|||
void SetDefaultVideoCodecToken();
|
||||
|
||||
/// <summary>
|
||||
/// Returns whether this videowriter dumps audio
|
||||
/// Returns whether this VideoWriter dumps audio
|
||||
/// </summary>
|
||||
bool UsesAudio { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns whether this videowriter dumps video
|
||||
/// Returns whether this VideoWriter dumps video
|
||||
/// </summary>
|
||||
bool UsesVideo { get; }
|
||||
|
||||
|
@ -67,9 +65,9 @@ namespace BizHawk.Client.EmuHawk
|
|||
IDisposable AcquireVideoCodecToken(System.Windows.Forms.IWin32Window hwnd);
|
||||
|
||||
/// <summary>
|
||||
/// set framerate to fpsnum/fpsden (assumed to be unchanging over the life of the stream)
|
||||
/// set framerate to fpsNum/fpsDen (assumed to be unchanging over the life of the stream)
|
||||
/// </summary>
|
||||
void SetMovieParameters(int fpsnum, int fpsden);
|
||||
void SetMovieParameters(int fpsNum, int fpsDen);
|
||||
|
||||
/// <summary>
|
||||
/// set resolution parameters (width x height)
|
||||
|
@ -90,43 +88,16 @@ namespace BizHawk.Client.EmuHawk
|
|||
/// </summary>
|
||||
/// <param name="gameName">The name of the game loaded</param>
|
||||
/// <param name="authors">Authors on movie file</param>
|
||||
/// <param name="lengthMS">Length of movie file in milliseconds</param>
|
||||
/// <param name="lengthMs">Length of movie file in milliseconds</param>
|
||||
/// <param name="rerecords">Number of rerecords on movie file</param>
|
||||
void SetMetaData(string gameName, string authors, UInt64 lengthMS, UInt64 rerecords);
|
||||
void SetMetaData(string gameName, string authors, ulong lengthMs, ulong rerecords);
|
||||
|
||||
// /// <summary>
|
||||
// /// short description of this IVideoWriter
|
||||
// /// </summary>
|
||||
// string WriterDescription();
|
||||
/// <summary>
|
||||
/// what default extension this writer would like to put on its output
|
||||
/// </summary>
|
||||
string DesiredExtension();
|
||||
// /// <summary>
|
||||
// /// name that command line parameters can refer to
|
||||
// /// </summary>
|
||||
// string ShortName();
|
||||
}
|
||||
|
||||
public static class VideoWriterExtensions
|
||||
{
|
||||
public static string WriterDescription(this IVideoWriter w)
|
||||
{
|
||||
return w.GetAttribute<VideoWriterAttribute>().Description;
|
||||
}
|
||||
|
||||
public static string ShortName(this IVideoWriter w)
|
||||
{
|
||||
return w.GetAttribute<VideoWriterAttribute>().ShortName;
|
||||
}
|
||||
|
||||
public static string LongName(this IVideoWriter w)
|
||||
{
|
||||
return w.GetAttribute<VideoWriterAttribute>().Name;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
public class VideoWriterAttribute : Attribute
|
||||
{
|
||||
|
@ -150,7 +121,7 @@ namespace BizHawk.Client.EmuHawk
|
|||
public class VideoWriterInfo
|
||||
{
|
||||
public VideoWriterAttribute Attribs { get; }
|
||||
private Type _type;
|
||||
private readonly Type _type;
|
||||
|
||||
public VideoWriterInfo(VideoWriterAttribute attribs, Type type)
|
||||
{
|
||||
|
@ -158,15 +129,9 @@ namespace BizHawk.Client.EmuHawk
|
|||
Attribs = attribs;
|
||||
}
|
||||
|
||||
public IVideoWriter Create()
|
||||
{
|
||||
return (IVideoWriter)Activator.CreateInstance(_type);
|
||||
}
|
||||
public IVideoWriter Create() => (IVideoWriter)Activator.CreateInstance(_type);
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Attribs.Name;
|
||||
}
|
||||
public override string ToString() => Attribs.Name;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -174,7 +139,7 @@ namespace BizHawk.Client.EmuHawk
|
|||
/// </summary>
|
||||
public static class VideoWriterInventory
|
||||
{
|
||||
private static Dictionary<string, VideoWriterInfo> vws = new Dictionary<string, VideoWriterInfo>();
|
||||
private static readonly Dictionary<string, VideoWriterInfo> VideoWriters = new Dictionary<string, VideoWriterInfo>();
|
||||
|
||||
static VideoWriterInventory()
|
||||
{
|
||||
|
@ -186,28 +151,21 @@ namespace BizHawk.Client.EmuHawk
|
|||
&& t.GetCustomAttributes(typeof(VideoWriterIgnoreAttribute), false).Length == 0)
|
||||
{
|
||||
var a = (VideoWriterAttribute)t.GetCustomAttributes(typeof(VideoWriterAttribute), false)[0];
|
||||
vws.Add(a.ShortName, new VideoWriterInfo(a, t));
|
||||
VideoWriters.Add(a.ShortName, new VideoWriterInfo(a, t));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<VideoWriterInfo> GetAllWriters()
|
||||
{
|
||||
return vws.Values;
|
||||
}
|
||||
public static IEnumerable<VideoWriterInfo> GetAllWriters() => VideoWriters.Values;
|
||||
|
||||
/// <summary>
|
||||
/// find an IVideoWriter by its short name
|
||||
/// </summary>
|
||||
public static IVideoWriter GetVideoWriter(string name)
|
||||
{
|
||||
VideoWriterInfo ret;
|
||||
if (vws.TryGetValue(name, out ret))
|
||||
{
|
||||
return ret.Create();
|
||||
}
|
||||
|
||||
return null;
|
||||
return VideoWriters.TryGetValue(name, out var ret)
|
||||
? ret.Create()
|
||||
: null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Drawing.Imaging;
|
||||
using System.Windows.Forms;
|
||||
|
||||
using BizHawk.Bizware.BizwareGL;
|
||||
|
@ -44,19 +45,17 @@ namespace BizHawk.Client.EmuHawk
|
|||
|
||||
public void AddFrame(IVideoProvider source)
|
||||
{
|
||||
string ext = Path.GetExtension(_baseName);
|
||||
var name = Path.Combine(Path.GetDirectoryName(_baseName), $"{Path.GetFileNameWithoutExtension(_baseName)}_{_frame}{ext}");
|
||||
string ext = Path.GetExtension(_baseName) ?? "";
|
||||
var name = Path.Combine(Path.GetDirectoryName(_baseName) ?? "", $"{Path.GetFileNameWithoutExtension(_baseName)}_{_frame}{ext}");
|
||||
BitmapBuffer bb = new BitmapBuffer(source.BufferWidth, source.BufferHeight, source.GetVideoBuffer());
|
||||
using (var bmp = bb.ToSysdrawingBitmap())
|
||||
using var bmp = bb.ToSysdrawingBitmap();
|
||||
if (ext.ToUpper() == ".PNG")
|
||||
{
|
||||
if (ext.ToUpper() == ".PNG")
|
||||
{
|
||||
bmp.Save(name, System.Drawing.Imaging.ImageFormat.Png);
|
||||
}
|
||||
else if (ext.ToUpper() == ".JPG")
|
||||
{
|
||||
bmp.Save(name, System.Drawing.Imaging.ImageFormat.Jpeg);
|
||||
}
|
||||
bmp.Save(name, ImageFormat.Png);
|
||||
}
|
||||
else if (ext.ToUpper() == ".JPG")
|
||||
{
|
||||
bmp.Save(name, ImageFormat.Jpeg);
|
||||
}
|
||||
|
||||
_frame++;
|
||||
|
@ -78,7 +77,7 @@ namespace BizHawk.Client.EmuHawk
|
|||
return new CodecToken();
|
||||
}
|
||||
|
||||
public void SetMovieParameters(int fpsnum, int fpsden)
|
||||
public void SetMovieParameters(int fpsNum, int fpsDen)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -90,14 +89,11 @@ namespace BizHawk.Client.EmuHawk
|
|||
{
|
||||
}
|
||||
|
||||
public void SetMetaData(string gameName, string authors, ulong lengthMS, ulong rerecords)
|
||||
public void SetMetaData(string gameName, string authors, ulong lengthMs, ulong rerecords)
|
||||
{
|
||||
}
|
||||
|
||||
public string DesiredExtension()
|
||||
{
|
||||
return "png";
|
||||
}
|
||||
public string DesiredExtension() => "png";
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
namespace BizHawk.Client.EmuHawk
|
||||
{
|
||||
partial class JMDForm
|
||||
partial class JmdForm
|
||||
{
|
||||
/// <summary>
|
||||
/// Required designer variable.
|
||||
|
@ -52,7 +52,6 @@
|
|||
this.okButton.TabIndex = 0;
|
||||
this.okButton.Text = "OK";
|
||||
this.okButton.UseVisualStyleBackColor = true;
|
||||
this.okButton.Click += new System.EventHandler(this.okButton_Click);
|
||||
//
|
||||
// threadsBar
|
||||
//
|
||||
|
@ -60,7 +59,7 @@
|
|||
this.threadsBar.Name = "threadsBar";
|
||||
this.threadsBar.Size = new System.Drawing.Size(104, 45);
|
||||
this.threadsBar.TabIndex = 5;
|
||||
this.threadsBar.Scroll += new System.EventHandler(this.threadsBar_Scroll);
|
||||
this.threadsBar.Scroll += new System.EventHandler(this.ThreadsBar_Scroll);
|
||||
//
|
||||
// compressionBar
|
||||
//
|
||||
|
@ -68,7 +67,7 @@
|
|||
this.compressionBar.Name = "compressionBar";
|
||||
this.compressionBar.Size = new System.Drawing.Size(104, 45);
|
||||
this.compressionBar.TabIndex = 9;
|
||||
this.compressionBar.Scroll += new System.EventHandler(this.compressionBar_Scroll);
|
||||
this.compressionBar.Scroll += new System.EventHandler(this.CompressionBar_Scroll);
|
||||
//
|
||||
// threadLeft
|
||||
//
|
||||
|
@ -136,7 +135,6 @@
|
|||
this.cancelButton.TabIndex = 1;
|
||||
this.cancelButton.Text = "Cancel";
|
||||
this.cancelButton.UseVisualStyleBackColor = true;
|
||||
this.cancelButton.Click += new System.EventHandler(this.cancelButton_Click);
|
||||
//
|
||||
// JMDForm
|
||||
//
|
||||
|
@ -155,7 +153,7 @@
|
|||
this.Controls.Add(this.compressionBar);
|
||||
this.Controls.Add(this.threadsBar);
|
||||
this.Controls.Add(this.okButton);
|
||||
this.Name = "JMDForm";
|
||||
this.Name = "JmdForm";
|
||||
this.ShowIcon = false;
|
||||
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
|
||||
this.Text = "JMD Compression Options";
|
||||
|
|
|
@ -1,34 +1,25 @@
|
|||
using System;
|
||||
using System.Windows.Forms;
|
||||
using BizHawk.Client.EmuHawk.WinFormExtensions;
|
||||
|
||||
namespace BizHawk.Client.EmuHawk
|
||||
{
|
||||
/// <summary>
|
||||
/// implements a minimal dialog for configuring JMDWriter
|
||||
/// </summary>
|
||||
public partial class JMDForm : Form
|
||||
public partial class JmdForm : Form
|
||||
{
|
||||
public JMDForm()
|
||||
public JmdForm()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void okButton_Click(object sender, EventArgs e)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
private void cancelButton_Click(object sender, EventArgs e)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
private void threadsBar_Scroll(object sender, EventArgs e)
|
||||
private void ThreadsBar_Scroll(object sender, EventArgs e)
|
||||
{
|
||||
threadTop.Text = $"Number of compression threads: {threadsBar.Value}";
|
||||
}
|
||||
|
||||
private void compressionBar_Scroll(object sender, EventArgs e)
|
||||
private void CompressionBar_Scroll(object sender, EventArgs e)
|
||||
{
|
||||
compressionTop.Text = compressionBar.Value == compressionBar.Minimum
|
||||
? "Compression Level: NONE"
|
||||
|
@ -39,43 +30,37 @@ namespace BizHawk.Client.EmuHawk
|
|||
/// Show a configuration dialog (modal) for JMDWriter
|
||||
/// </summary>
|
||||
/// <param name="threads">number of threads</param>
|
||||
/// <param name="complevel">compression level</param>
|
||||
/// <param name="tmin">minimum possible number of threads</param>
|
||||
/// <param name="tmax">maximum possible number of threads</param>
|
||||
/// <param name="cmin">minimum compression level, assumed to be "no compression"</param>
|
||||
/// <param name="cmax">maximum compression level</param>
|
||||
/// <param name="compLevel">compression level</param>
|
||||
/// <param name="tMin">minimum possible number of threads</param>
|
||||
/// <param name="tMax">maximum possible number of threads</param>
|
||||
/// <param name="cMin">minimum compression level, assumed to be "no compression"</param>
|
||||
/// <param name="cMax">maximum compression level</param>
|
||||
/// <param name="hwnd">hwnd of parent</param>
|
||||
/// <returns>false if user canceled; true if user consented</returns>
|
||||
public static bool DoCompressionDlg(ref int threads, ref int complevel, int tmin, int tmax, int cmin, int cmax, IWin32Window hwnd)
|
||||
public static bool DoCompressionDlg(ref int threads, ref int compLevel, int tMin, int tMax, int cMin, int cMax, IWin32Window hwnd)
|
||||
{
|
||||
JMDForm j = new JMDForm();
|
||||
j.threadsBar.Minimum = tmin;
|
||||
j.threadsBar.Maximum = tmax;
|
||||
j.compressionBar.Minimum = cmin;
|
||||
j.compressionBar.Maximum = cmax;
|
||||
var j = new JmdForm
|
||||
{
|
||||
threadsBar = { Minimum = tMin, Maximum = tMax },
|
||||
compressionBar = { Minimum = cMin, Maximum = cMax }
|
||||
};
|
||||
|
||||
j.threadsBar.Value = threads;
|
||||
j.compressionBar.Value = complevel;
|
||||
j.threadsBar_Scroll(null, null);
|
||||
j.compressionBar_Scroll(null, null);
|
||||
j.threadLeft.Text = $"{tmin}";
|
||||
j.threadRight.Text = $"{tmax}";
|
||||
j.compressionLeft.Text = $"{cmin}";
|
||||
j.compressionRight.Text = $"{cmax}";
|
||||
j.compressionBar.Value = compLevel;
|
||||
j.ThreadsBar_Scroll(null, null);
|
||||
j.CompressionBar_Scroll(null, null);
|
||||
j.threadLeft.Text = $"{tMin}";
|
||||
j.threadRight.Text = $"{tMax}";
|
||||
j.compressionLeft.Text = $"{cMin}";
|
||||
j.compressionRight.Text = $"{cMax}";
|
||||
|
||||
DialogResult d = j.ShowDialog(hwnd);
|
||||
|
||||
threads = j.threadsBar.Value;
|
||||
complevel = j.compressionBar.Value;
|
||||
compLevel = j.compressionBar.Value;
|
||||
|
||||
j.Dispose();
|
||||
if (d == DialogResult.OK)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
return d.IsOk();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Windows.Forms;
|
||||
|
||||
using ICSharpCode.SharpZipLib.Zip.Compression.Streams;
|
||||
using ICSharpCode.SharpZipLib.Zip.Compression;
|
||||
|
@ -18,12 +21,12 @@ namespace BizHawk.Client.EmuHawk
|
|||
/// they can be processed with JPC-rr streamtools or JMDSource (avisynth)
|
||||
/// </summary>
|
||||
[VideoWriter("jmd", "JMD writer", "Writes a JPC-rr multidump file (JMD). These can be read and further processed with jpc-streamtools. One JMD file contains all audio (uncompressed) and video (compressed).")]
|
||||
class JMDWriter : IVideoWriter
|
||||
public class JmdWriter : IVideoWriter
|
||||
{
|
||||
/// <summary>
|
||||
/// carries private compression information data
|
||||
/// </summary>
|
||||
class CodecToken : IDisposable
|
||||
private class CodecToken : IDisposable
|
||||
{
|
||||
public void Dispose()
|
||||
{
|
||||
|
@ -32,202 +35,162 @@ namespace BizHawk.Client.EmuHawk
|
|||
/// <summary>
|
||||
/// how hard the zlib compressor works
|
||||
/// </summary>
|
||||
public int compressionlevel
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
public int CompressionLevel { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// number of threads to be used for video compression (sort of)
|
||||
/// </summary>
|
||||
public int numthreads
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
public int NumThreads { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// instantiates a CodecToken with default parameters
|
||||
/// </summary>
|
||||
public CodecToken()
|
||||
{
|
||||
compressionlevel = Deflater.DEFAULT_COMPRESSION;
|
||||
numthreads = 3;
|
||||
CompressionLevel = Deflater.DEFAULT_COMPRESSION;
|
||||
NumThreads = 3;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// stores compression parameters
|
||||
/// </summary>
|
||||
CodecToken token;
|
||||
// stores compression parameters
|
||||
private CodecToken _token;
|
||||
|
||||
/// <summary>
|
||||
/// fps numerator, constant
|
||||
/// </summary>
|
||||
int fpsnum;
|
||||
// fps numerator, constant
|
||||
private int _fpsNum;
|
||||
|
||||
/// <summary>
|
||||
/// fps denominator, constant
|
||||
/// </summary>
|
||||
int fpsden;
|
||||
// fps denominator, constant
|
||||
private int _fpsDen;
|
||||
|
||||
/// <summary>
|
||||
/// audio samplerate, constant
|
||||
/// </summary>
|
||||
int audiosamplerate;
|
||||
// audio samplerate, constant
|
||||
private int _audioSampleRate;
|
||||
|
||||
/// <summary>
|
||||
/// audio number of channels, constant; 1 or 2 only
|
||||
/// </summary>
|
||||
int audiochannels;
|
||||
private int _audioChannels;
|
||||
|
||||
/// <summary>
|
||||
/// audio bits per sample, constant; only 16 supported
|
||||
/// </summary>
|
||||
int audiobits;
|
||||
|
||||
/// <summary>
|
||||
/// actual disk file being written
|
||||
/// </summary>
|
||||
JMDfile jmdfile;
|
||||
// actual disk file being written
|
||||
private JmdFile _jmdFile;
|
||||
|
||||
/// <summary>
|
||||
/// metadata for a movie
|
||||
/// not needed if we aren't dumping something that's not a movie
|
||||
/// </summary>
|
||||
class MovieMetaData
|
||||
private class MovieMetaData
|
||||
{
|
||||
/// <summary>
|
||||
/// name of the game (rom)
|
||||
/// </summary>
|
||||
public string gamename;
|
||||
public string GameName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// author(s) names
|
||||
/// </summary>
|
||||
public string authors;
|
||||
public string Authors { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// total length of the movie: ms
|
||||
/// </summary>
|
||||
public UInt64 lengthms;
|
||||
public ulong LengthMs { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// number of rerecords
|
||||
/// </summary>
|
||||
public UInt64 rerecords;
|
||||
public ulong Rerecords { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// represents the metadata for the active movie (if applicable)
|
||||
/// </summary>
|
||||
MovieMetaData moviemetadata;
|
||||
// represents the metadata for the active movie (if applicable)
|
||||
private MovieMetaData _movieMetadata;
|
||||
|
||||
/// <summary>
|
||||
/// represents a JMD file packet ready to be written except for sorting and timestamp offset
|
||||
/// </summary>
|
||||
class JMDPacket
|
||||
private class JmdPacket
|
||||
{
|
||||
public UInt16 stream;
|
||||
public UInt64 timestamp; // final muxed timestamp will be relative to previous
|
||||
public byte subtype;
|
||||
public byte[] data;
|
||||
public ushort Stream { get; set; }
|
||||
public ulong Timestamp { get; set; } // final muxed timestamp will be relative to previous
|
||||
public byte Subtype { get; set; }
|
||||
public byte[] Data { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// writes JMDfile packets to an underlying bytestream
|
||||
/// writes JMD file packets to an underlying bytestream
|
||||
/// handles one video, one pcm audio, and one metadata track
|
||||
/// </summary>
|
||||
class JMDfile
|
||||
private class JmdFile
|
||||
{
|
||||
/// <summary>
|
||||
/// current timestamp position
|
||||
/// </summary>
|
||||
UInt64 timestampoff;
|
||||
// current timestamp position
|
||||
private ulong _timestampOff;
|
||||
|
||||
/// <summary>
|
||||
/// total number of video frames written
|
||||
/// </summary>
|
||||
UInt64 totalframes;
|
||||
// total number of video frames written
|
||||
private ulong _totalFrames;
|
||||
|
||||
/// <summary>
|
||||
/// total number of sample pairs written
|
||||
/// </summary>
|
||||
UInt64 totalsamples;
|
||||
// total number of sample pairs written
|
||||
private ulong _totalSamples;
|
||||
|
||||
/// <summary>
|
||||
/// fps of the video stream is fpsnum/fpsden
|
||||
/// </summary>
|
||||
int fpsnum;
|
||||
// fps of the video stream is fpsNum/fpsDen
|
||||
private readonly int _fpsNum;
|
||||
|
||||
/// <summary>
|
||||
/// fps of the video stream is fpsnum/fpsden
|
||||
/// </summary>
|
||||
int fpsden;
|
||||
// fps of the video stream is fpsNum/fpsDen
|
||||
private readonly int _fpsDen;
|
||||
|
||||
/// <summary>
|
||||
/// audio samplerate in hz
|
||||
/// </summary>
|
||||
int audiosamplerate;
|
||||
// audio samplerate in hz
|
||||
private readonly int _audioSamplerate;
|
||||
|
||||
/// <summary>
|
||||
/// true if input will be stereo; mono otherwise
|
||||
/// output stream is always stereo
|
||||
/// </summary>
|
||||
bool stereo;
|
||||
// true if input will be stereo; mono otherwise
|
||||
// output stream is always stereo
|
||||
private readonly bool _stereo;
|
||||
|
||||
/// <summary>
|
||||
/// underlying bytestream that is being written to
|
||||
/// </summary>
|
||||
Stream f;
|
||||
private readonly Stream _f;
|
||||
|
||||
/// <exception cref="ArgumentException"><paramref name="f"/> cannot be written to</exception>
|
||||
public JMDfile(Stream f, int fpsnum, int fpsden, int audiosamplerate, bool stereo)
|
||||
public JmdFile(Stream f, int fpsNum, int fpsDen, int audioSamplerate, bool stereo)
|
||||
{
|
||||
if (!f.CanWrite)
|
||||
{
|
||||
throw new ArgumentException($"{nameof(Stream)} must be writable!");
|
||||
}
|
||||
|
||||
this.f = f;
|
||||
this.fpsnum = fpsnum;
|
||||
this.fpsden = fpsden;
|
||||
this.audiosamplerate = audiosamplerate;
|
||||
this.stereo = stereo;
|
||||
_f = f;
|
||||
_fpsNum = fpsNum;
|
||||
_fpsDen = fpsDen;
|
||||
_audioSamplerate = audioSamplerate;
|
||||
_stereo = stereo;
|
||||
|
||||
timestampoff = 0;
|
||||
totalframes = 0;
|
||||
totalsamples = 0;
|
||||
_timestampOff = 0;
|
||||
_totalFrames = 0;
|
||||
_totalSamples = 0;
|
||||
|
||||
astorage = new Queue<JMDPacket>();
|
||||
vstorage = new Queue<JMDPacket>();
|
||||
_audioStorage = new Queue<JmdPacket>();
|
||||
_videoStorage = new Queue<JmdPacket>();
|
||||
|
||||
writeheader();
|
||||
WriteHeader();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// write header to the JPC file
|
||||
/// assumes one video, one audio, and one metadata stream, with hardcoded IDs
|
||||
/// </summary>
|
||||
void writeheader()
|
||||
// write header to the JPC file
|
||||
// assumes one video, one audio, and one metadata stream, with hardcoded IDs
|
||||
private void WriteHeader()
|
||||
{
|
||||
// write JPC MAGIC
|
||||
writeBE16(0xffff);
|
||||
f.Write(Encoding.ASCII.GetBytes("JPCRRMULTIDUMP"), 0, 14);
|
||||
WriteBe16(0xffff);
|
||||
_f.Write(Encoding.ASCII.GetBytes("JPCRRMULTIDUMP"), 0, 14);
|
||||
|
||||
// write channel table
|
||||
writeBE16(3); // number of streams
|
||||
WriteBe16(3); // number of streams
|
||||
|
||||
// for each stream
|
||||
writeBE16(0); // channel 0
|
||||
writeBE16(0); // video
|
||||
writeBE16(0); // no name
|
||||
WriteBe16(0); // channel 0
|
||||
WriteBe16(0); // video
|
||||
WriteBe16(0); // no name
|
||||
|
||||
writeBE16(1); // channel 1
|
||||
writeBE16(1); // pcm audio
|
||||
writeBE16(0); // no name
|
||||
WriteBe16(1); // channel 1
|
||||
WriteBe16(1); // pcm audio
|
||||
WriteBe16(0); // no name
|
||||
|
||||
writeBE16(2); // channel 2
|
||||
writeBE16(5); // metadata
|
||||
writeBE16(0); // no name
|
||||
WriteBe16(2); // channel 2
|
||||
WriteBe16(5); // metadata
|
||||
WriteBe16(0); // no name
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -235,46 +198,46 @@ namespace BizHawk.Client.EmuHawk
|
|||
/// can be called at any time
|
||||
/// </summary>
|
||||
/// <param name="mmd">metadata to write</param>
|
||||
public void writemetadata(MovieMetaData mmd)
|
||||
public void WriteMetadata(MovieMetaData mmd)
|
||||
{
|
||||
byte[] temp;
|
||||
// write metadatas
|
||||
writeBE16(2); // data channel
|
||||
WriteBe16(2); // data channel
|
||||
writeBE32(0); // timestamp (same time as previous packet)
|
||||
f.WriteByte(71); // gamename
|
||||
temp = Encoding.UTF8.GetBytes(mmd.gamename);
|
||||
_f.WriteByte(71); // GameName
|
||||
|
||||
var temp = Encoding.UTF8.GetBytes(mmd.GameName);
|
||||
writeVar(temp.Length);
|
||||
f.Write(temp, 0, temp.Length);
|
||||
_f.Write(temp, 0, temp.Length);
|
||||
|
||||
writeBE16(2);
|
||||
WriteBe16(2);
|
||||
writeBE32(0);
|
||||
f.WriteByte(65); // authors
|
||||
temp = Encoding.UTF8.GetBytes(mmd.authors);
|
||||
_f.WriteByte(65); // authors
|
||||
temp = Encoding.UTF8.GetBytes(mmd.Authors);
|
||||
writeVar(temp.Length);
|
||||
f.Write(temp, 0, temp.Length);
|
||||
_f.Write(temp, 0, temp.Length);
|
||||
|
||||
writeBE16(2);
|
||||
WriteBe16(2);
|
||||
writeBE32(0);
|
||||
f.WriteByte(76); // length
|
||||
_f.WriteByte(76); // length
|
||||
writeVar(8);
|
||||
writeBE64(mmd.lengthms * 1000000);
|
||||
writeBE64(mmd.LengthMs * 1000000);
|
||||
|
||||
writeBE16(2);
|
||||
WriteBe16(2);
|
||||
writeBE32(0);
|
||||
f.WriteByte(82); // rerecords
|
||||
_f.WriteByte(82); // rerecords
|
||||
writeVar(8);
|
||||
writeBE64(mmd.rerecords);
|
||||
writeBE64(mmd.Rerecords);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// write big endian 16 bit unsigned
|
||||
/// </summary>
|
||||
void writeBE16(UInt16 v)
|
||||
private void WriteBe16(ushort v)
|
||||
{
|
||||
byte[] b = new byte[2];
|
||||
b[0] = (byte)(v >> 8);
|
||||
b[1] = (byte)(v & 255);
|
||||
f.Write(b, 0, 2);
|
||||
_f.Write(b, 0, 2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -287,7 +250,7 @@ namespace BizHawk.Client.EmuHawk
|
|||
b[1] = (byte)(v >> 16);
|
||||
b[2] = (byte)(v >> 8);
|
||||
b[3] = (byte)(v & 255);
|
||||
f.Write(b, 0, 4);
|
||||
_f.Write(b, 0, 4);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -301,7 +264,7 @@ namespace BizHawk.Client.EmuHawk
|
|||
b[i] = (byte)(v & 255);
|
||||
v >>= 8;
|
||||
}
|
||||
f.Write(b, 0, 8);
|
||||
_f.Write(b, 0, 8);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -321,10 +284,10 @@ namespace BizHawk.Client.EmuHawk
|
|||
v /= 128;
|
||||
}
|
||||
if (i == 0)
|
||||
f.WriteByte(0);
|
||||
_f.WriteByte(0);
|
||||
else
|
||||
for (; i > 0; i--)
|
||||
f.WriteByte(b[i - 1]);
|
||||
_f.WriteByte(b[i - 1]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -362,27 +325,27 @@ namespace BizHawk.Client.EmuHawk
|
|||
/// actually write a packet to file
|
||||
/// timestamp sequence must be nondecreasing
|
||||
/// </summary>
|
||||
void writeActual(JMDPacket j)
|
||||
void writeActual(JmdPacket j)
|
||||
{
|
||||
if (j.timestamp < timestampoff)
|
||||
if (j.Timestamp < _timestampOff)
|
||||
{
|
||||
throw new ArithmeticException("JMD Timestamp problem?");
|
||||
}
|
||||
|
||||
UInt64 timestampout = j.timestamp - timestampoff;
|
||||
UInt64 timestampout = j.Timestamp - _timestampOff;
|
||||
while (timestampout > 0xffffffff)
|
||||
{
|
||||
timestampout -= 0xffffffff;
|
||||
// write timestamp skipper
|
||||
for (int i = 0; i < 6; i++)
|
||||
f.WriteByte(0xff);
|
||||
_f.WriteByte(0xff);
|
||||
}
|
||||
timestampoff = j.timestamp;
|
||||
writeBE16(j.stream);
|
||||
_timestampOff = j.Timestamp;
|
||||
WriteBe16(j.Stream);
|
||||
writeBE32((UInt32)timestampout);
|
||||
f.WriteByte(j.subtype);
|
||||
writeVar((UInt64)j.data.LongLength);
|
||||
f.Write(j.data, 0, j.data.Length);
|
||||
_f.WriteByte(j.Subtype);
|
||||
writeVar((UInt64)j.Data.LongLength);
|
||||
_f.Write(j.Data, 0, j.Data.Length);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -391,13 +354,13 @@ namespace BizHawk.Client.EmuHawk
|
|||
/// <param name="source">zlibed frame with width and height prepended</param>
|
||||
public void AddVideo(byte[] source)
|
||||
{
|
||||
var j = new JMDPacket();
|
||||
j.stream = 0;
|
||||
j.subtype = 1; // zlib compressed, other possibility is 0 = uncompressed
|
||||
j.data = source;
|
||||
j.timestamp = timestampcalc(fpsnum, fpsden, (UInt64)totalframes);
|
||||
totalframes++;
|
||||
writevideo(j);
|
||||
var j = new JmdPacket();
|
||||
j.Stream = 0;
|
||||
j.Subtype = 1; // zlib compressed, other possibility is 0 = uncompressed
|
||||
j.Data = source;
|
||||
j.Timestamp = timestampcalc(_fpsNum, _fpsDen, (UInt64)_totalFrames);
|
||||
_totalFrames++;
|
||||
WriteVideo(j);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -406,7 +369,7 @@ namespace BizHawk.Client.EmuHawk
|
|||
/// </summary>
|
||||
public void AddSamples(short[] samples)
|
||||
{
|
||||
if (!stereo)
|
||||
if (!_stereo)
|
||||
for (int i = 0; i < samples.Length; i++)
|
||||
doaudiopacket(samples[i], samples[i]);
|
||||
else
|
||||
|
@ -421,90 +384,80 @@ namespace BizHawk.Client.EmuHawk
|
|||
/// <param name="r">right sample</param>
|
||||
void doaudiopacket(short l, short r)
|
||||
{
|
||||
var j = new JMDPacket();
|
||||
j.stream = 1;
|
||||
j.subtype = 1; // raw PCM audio
|
||||
j.data = new byte[4];
|
||||
j.data[0] = (byte)(l >> 8);
|
||||
j.data[1] = (byte)(l & 255);
|
||||
j.data[2] = (byte)(r >> 8);
|
||||
j.data[3] = (byte)(r & 255);
|
||||
var j = new JmdPacket();
|
||||
j.Stream = 1;
|
||||
j.Subtype = 1; // raw PCM audio
|
||||
j.Data = new byte[4];
|
||||
j.Data[0] = (byte)(l >> 8);
|
||||
j.Data[1] = (byte)(l & 255);
|
||||
j.Data[2] = (byte)(r >> 8);
|
||||
j.Data[3] = (byte)(r & 255);
|
||||
|
||||
j.timestamp = timestampcalc(audiosamplerate, 1, totalsamples);
|
||||
totalsamples++;
|
||||
writesound(j);
|
||||
j.Timestamp = timestampcalc(_audioSamplerate, 1, _totalSamples);
|
||||
_totalSamples++;
|
||||
WriteSound(j);
|
||||
}
|
||||
|
||||
// ensure outputs are in order
|
||||
// JMD packets must be in nondecreasing timestamp order, but there's no obligation
|
||||
// for us to get handed that. this code is a bit overcomplex to handle edge cases
|
||||
// JMD packets must be in non-decreasing timestamp order, but there's no obligation
|
||||
// for us to get handed that. This code is a bit overly complex to handle edge cases
|
||||
// that may not be a problem with the current system?
|
||||
|
||||
/// <summary>
|
||||
/// collection of JMDpackets yet to be written (audio)
|
||||
/// </summary>
|
||||
Queue<JMDPacket> astorage;
|
||||
/// <summary>
|
||||
/// collection of JMDpackets yet to be written (video)
|
||||
/// </summary>
|
||||
Queue<JMDPacket> vstorage;
|
||||
// collection of JMD packets yet to be written (audio)
|
||||
private readonly Queue<JmdPacket> _audioStorage;
|
||||
|
||||
/// <summary>
|
||||
/// add a sound packet to the file write queue
|
||||
/// will be written when order-appropriate wrt video
|
||||
/// the sound packets added must be internally ordered (but need not match video order)
|
||||
/// </summary>
|
||||
void writesound(JMDPacket j)
|
||||
// collection of JMD packets yet to be written (video)
|
||||
private readonly Queue<JmdPacket> _videoStorage;
|
||||
|
||||
// add a sound packet to the file write queue
|
||||
// will be written when order-appropriate wrt video
|
||||
// the sound packets added must be internally ordered (but need not match video order)
|
||||
private void WriteSound(JmdPacket j)
|
||||
{
|
||||
while (vstorage.Count > 0)
|
||||
while (_videoStorage.Count > 0)
|
||||
{
|
||||
var p = vstorage.Peek();
|
||||
if (p.timestamp <= j.timestamp)
|
||||
writeActual(vstorage.Dequeue());
|
||||
var p = _videoStorage.Peek();
|
||||
if (p.Timestamp <= j.Timestamp)
|
||||
writeActual(_videoStorage.Dequeue());
|
||||
else
|
||||
break;
|
||||
}
|
||||
|
||||
astorage.Enqueue(j);
|
||||
_audioStorage.Enqueue(j);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// add a video packet to the file write queue
|
||||
/// will be written when order-appropriate wrt audio
|
||||
/// the video packets added must be internally ordered (but need not match audio order)
|
||||
/// </summary>
|
||||
void writevideo(JMDPacket j)
|
||||
// add a video packet to the file write queue
|
||||
// will be written when order-appropriate wrt audio
|
||||
// the video packets added must be internally ordered (but need not match audio order)
|
||||
private void WriteVideo(JmdPacket j)
|
||||
{
|
||||
while (astorage.Count > 0)
|
||||
while (_audioStorage.Count > 0)
|
||||
{
|
||||
var p = astorage.Peek();
|
||||
if (p.timestamp <= j.timestamp)
|
||||
writeActual(astorage.Dequeue());
|
||||
var p = _audioStorage.Peek();
|
||||
if (p.Timestamp <= j.Timestamp)
|
||||
writeActual(_audioStorage.Dequeue());
|
||||
else
|
||||
break;
|
||||
}
|
||||
vstorage.Enqueue(j);
|
||||
_videoStorage.Enqueue(j);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// flush all remaining JMDPackets to file
|
||||
/// call before closing the file
|
||||
/// </summary>
|
||||
void flushpackets()
|
||||
// flush all remaining JMDPackets to file
|
||||
// call before closing the file
|
||||
private void FlushPackets()
|
||||
{
|
||||
while (astorage.Count > 0 && vstorage.Count > 0)
|
||||
while (_audioStorage.Count > 0 && _videoStorage.Count > 0)
|
||||
{
|
||||
var ap = astorage.Peek();
|
||||
var av = vstorage.Peek();
|
||||
if (ap.timestamp <= av.timestamp)
|
||||
writeActual(astorage.Dequeue());
|
||||
else
|
||||
writeActual(vstorage.Dequeue());
|
||||
var ap = _audioStorage.Peek();
|
||||
var av = _videoStorage.Peek();
|
||||
writeActual(ap.Timestamp <= av.Timestamp
|
||||
? _audioStorage.Dequeue()
|
||||
: _videoStorage.Dequeue());
|
||||
}
|
||||
while (astorage.Count > 0)
|
||||
writeActual(astorage.Dequeue());
|
||||
while (vstorage.Count > 0)
|
||||
writeActual(vstorage.Dequeue());
|
||||
while (_audioStorage.Count > 0)
|
||||
writeActual(_audioStorage.Dequeue());
|
||||
while (_videoStorage.Count > 0)
|
||||
writeActual(_videoStorage.Dequeue());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -512,24 +465,23 @@ namespace BizHawk.Client.EmuHawk
|
|||
/// </summary>
|
||||
public void Close()
|
||||
{
|
||||
flushpackets();
|
||||
f.Close();
|
||||
FlushPackets();
|
||||
_f.Close();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// sets default (probably wrong) parameters
|
||||
/// </summary>
|
||||
public JMDWriter()
|
||||
public JmdWriter()
|
||||
{
|
||||
fpsnum = 25;
|
||||
fpsden = 1;
|
||||
audiosamplerate = 22050;
|
||||
audiochannels = 1;
|
||||
audiobits = 8;
|
||||
token = null;
|
||||
_fpsNum = 25;
|
||||
_fpsDen = 1;
|
||||
_audioSampleRate = 22050;
|
||||
_audioChannels = 1;
|
||||
_token = null;
|
||||
|
||||
moviemetadata = null;
|
||||
_movieMetadata = null;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
|
@ -538,11 +490,11 @@ namespace BizHawk.Client.EmuHawk
|
|||
}
|
||||
|
||||
/// <summary>sets the codec token to be used for video compression</summary>
|
||||
/// <exception cref="ArgumentException"><paramref name="token"/> does not inherit <see cref="JMDWriter.CodecToken"/></exception>
|
||||
/// <exception cref="ArgumentException"><paramref name="token"/> does not inherit <see cref="JmdWriter.CodecToken"/></exception>
|
||||
public void SetVideoCodecToken(IDisposable token)
|
||||
{
|
||||
if (token is CodecToken)
|
||||
this.token = (CodecToken)token;
|
||||
if (token is CodecToken codecToken)
|
||||
this._token = codecToken;
|
||||
else
|
||||
throw new ArgumentException("codec token must be of right type");
|
||||
}
|
||||
|
@ -561,22 +513,22 @@ namespace BizHawk.Client.EmuHawk
|
|||
|
||||
int c = Math.Min(Math.Max(Global.Config.JMDCompression, Deflater.NO_COMPRESSION), Deflater.BEST_COMPRESSION);
|
||||
|
||||
if (!JMDForm.DoCompressionDlg(ref t, ref c, 1, 6, Deflater.NO_COMPRESSION, Deflater.BEST_COMPRESSION, hwnd))
|
||||
if (!JmdForm.DoCompressionDlg(ref t, ref c, 1, 6, Deflater.NO_COMPRESSION, Deflater.BEST_COMPRESSION, hwnd))
|
||||
return null;
|
||||
|
||||
Global.Config.JMDThreads = ret.numthreads = t;
|
||||
Global.Config.JMDCompression = ret.compressionlevel = c;
|
||||
Global.Config.JMDThreads = ret.NumThreads = t;
|
||||
Global.Config.JMDCompression = ret.CompressionLevel = c;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// set framerate to fpsnum/fpsden (assumed to be unchanging over the life of the stream)
|
||||
/// set framerate to fpsNum/fpsDen (assumed to be unchanging over the life of the stream)
|
||||
/// </summary>
|
||||
public void SetMovieParameters(int fpsnum, int fpsden)
|
||||
public void SetMovieParameters(int fpsNum, int fpsDen)
|
||||
{
|
||||
this.fpsnum = fpsnum;
|
||||
this.fpsden = fpsden;
|
||||
_fpsNum = fpsNum;
|
||||
_fpsDen = fpsDen;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -597,10 +549,12 @@ namespace BizHawk.Client.EmuHawk
|
|||
// the sampleRate limits are arbitrary, just to catch things which are probably silly-wrong
|
||||
// if a larger range of sampling rates is needed, it should be supported
|
||||
if (sampleRate < 8000 || sampleRate > 96000 || channels < 1 || channels > 2 || bits != 16)
|
||||
{
|
||||
throw new ArgumentException("Audio parameters out of range!");
|
||||
audiosamplerate = sampleRate;
|
||||
audiochannels = channels;
|
||||
audiobits = bits;
|
||||
}
|
||||
|
||||
_audioSampleRate = sampleRate;
|
||||
_audioChannels = channels;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -611,57 +565,66 @@ namespace BizHawk.Client.EmuHawk
|
|||
{
|
||||
string ext = Path.GetExtension(baseName);
|
||||
if (ext == null || ext.ToLower() != ".jmd")
|
||||
{
|
||||
baseName = baseName + ".jmd";
|
||||
}
|
||||
|
||||
jmdfile = new JMDfile(File.Open(baseName, FileMode.Create), fpsnum, fpsden, audiosamplerate, audiochannels == 2);
|
||||
_jmdFile = new JmdFile(File.Open(baseName, FileMode.Create), _fpsNum, _fpsDen, _audioSampleRate, _audioChannels == 2);
|
||||
|
||||
|
||||
if (moviemetadata != null)
|
||||
jmdfile.writemetadata(moviemetadata);
|
||||
if (_movieMetadata != null)
|
||||
{
|
||||
_jmdFile.WriteMetadata(_movieMetadata);
|
||||
}
|
||||
|
||||
// start up thread
|
||||
// problem: since audio chunks and video frames both go through here, exactly how many zlib workers
|
||||
// gives is not known without knowing how the emulator will chunk audio packets
|
||||
// this shouldn't affect results though, just performance
|
||||
threadQ = new System.Collections.Concurrent.BlockingCollection<Object>(token.numthreads * 2);
|
||||
workerT = new System.Threading.Thread(new System.Threading.ThreadStart(threadproc));
|
||||
workerT.Start();
|
||||
GzipFrameDelegate = new GzipFrameD(GzipFrame);
|
||||
_threadQ = new BlockingCollection<object>(_token.NumThreads * 2);
|
||||
_workerT = new Thread(ThreadProc);
|
||||
_workerT.Start();
|
||||
_gzipFrameDelegate = GzipFrame;
|
||||
}
|
||||
|
||||
// some of this code is copied from AviWriter... not sure how if at all it should be abstracted
|
||||
/// <summary>
|
||||
/// blocking threadsafe queue, used for communication between main program and file writing thread
|
||||
/// blocking thread safe queue, used for communication between main program and file writing thread
|
||||
/// </summary>
|
||||
System.Collections.Concurrent.BlockingCollection<Object> threadQ;
|
||||
private BlockingCollection<object> _threadQ;
|
||||
|
||||
/// <summary>
|
||||
/// file writing thread; most of the work happens here
|
||||
/// </summary>
|
||||
System.Threading.Thread workerT;
|
||||
private Thread _workerT;
|
||||
|
||||
/// <summary>
|
||||
/// filewriting thread's loop
|
||||
/// file writing thread's loop
|
||||
/// </summary>
|
||||
void threadproc()
|
||||
private void ThreadProc()
|
||||
{
|
||||
try
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
Object o = threadQ.Take();
|
||||
if (o is IAsyncResult)
|
||||
jmdfile.AddVideo(GzipFrameDelegate.EndInvoke((IAsyncResult)o));
|
||||
else if (o is short[])
|
||||
jmdfile.AddSamples((short[])o);
|
||||
object o = _threadQ.Take();
|
||||
if (o is IAsyncResult result)
|
||||
{
|
||||
_jmdFile.AddVideo(_gzipFrameDelegate.EndInvoke(result));
|
||||
}
|
||||
else if (o is short[] shorts)
|
||||
{
|
||||
_jmdFile.AddSamples(shorts);
|
||||
}
|
||||
else
|
||||
{
|
||||
// anything else is assumed to be quit time
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
System.Windows.Forms.MessageBox.Show($"JMD Worker Thread died:\n\n{e}");
|
||||
return;
|
||||
MessageBox.Show($"JMD Worker Thread died:\n\n{e}");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -670,26 +633,26 @@ namespace BizHawk.Client.EmuHawk
|
|||
/// </summary>
|
||||
public void CloseFile()
|
||||
{
|
||||
threadQ.Add(new Object()); // acts as stop message
|
||||
workerT.Join();
|
||||
|
||||
jmdfile.Close();
|
||||
_threadQ.Add(new object()); // acts as stop message
|
||||
_workerT.Join();
|
||||
_jmdFile.Close();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// makes a copy of an IVideoProvider
|
||||
/// handles conversion to a byte array suitable for compression by zlib
|
||||
/// </summary>
|
||||
class VideoCopy
|
||||
public class VideoCopy
|
||||
{
|
||||
public byte[] VideoBuffer;
|
||||
public byte[] VideoBuffer { get; set; }
|
||||
|
||||
public int BufferWidth;
|
||||
public int BufferHeight;
|
||||
public int BufferWidth { get; set; }
|
||||
public int BufferHeight { get; set; }
|
||||
public VideoCopy(IVideoProvider c)
|
||||
{
|
||||
int[] vb = c.GetVideoBuffer();
|
||||
VideoBuffer = new byte[vb.Length * sizeof(int)];
|
||||
|
||||
// we have to switch RGB ordering here
|
||||
for (int i = 0; i < vb.Length; i++)
|
||||
{
|
||||
|
@ -698,7 +661,7 @@ namespace BizHawk.Client.EmuHawk
|
|||
VideoBuffer[i * 4 + 2] = (byte)(vb[i] & 255);
|
||||
VideoBuffer[i * 4 + 3] = 0;
|
||||
}
|
||||
//Buffer.BlockCopy(vb, 0, VideoBuffer, 0, VideoBuffer.Length);
|
||||
|
||||
BufferWidth = c.BufferWidth;
|
||||
BufferHeight = c.BufferHeight;
|
||||
}
|
||||
|
@ -711,19 +674,24 @@ namespace BizHawk.Client.EmuHawk
|
|||
/// </summary>
|
||||
/// <param name="v">video frame to compress</param>
|
||||
/// <returns>zlib compressed frame, with width and height prepended</returns>
|
||||
byte[] GzipFrame(VideoCopy v)
|
||||
private byte[] GzipFrame(VideoCopy v)
|
||||
{
|
||||
MemoryStream m = new MemoryStream();
|
||||
var m = new MemoryStream();
|
||||
|
||||
// write frame height and width first
|
||||
m.WriteByte((byte)(v.BufferWidth >> 8));
|
||||
m.WriteByte((byte)(v.BufferWidth & 255));
|
||||
m.WriteByte((byte)(v.BufferHeight >> 8));
|
||||
m.WriteByte((byte)(v.BufferHeight & 255));
|
||||
var g = new DeflaterOutputStream(m, new Deflater(token.compressionlevel));
|
||||
g.IsStreamOwner = false; // leave memory stream open so we can pick its contents
|
||||
var g = new DeflaterOutputStream(m, new Deflater(_token.CompressionLevel))
|
||||
{
|
||||
IsStreamOwner = false // leave memory stream open so we can pick its contents
|
||||
};
|
||||
|
||||
g.Write(v.VideoBuffer, 0, v.VideoBuffer.Length);
|
||||
g.Flush();
|
||||
g.Close();
|
||||
|
||||
byte[] ret = m.GetBuffer();
|
||||
Array.Resize(ref ret, (int)m.Length);
|
||||
m.Close();
|
||||
|
@ -735,21 +703,23 @@ namespace BizHawk.Client.EmuHawk
|
|||
/// </summary>
|
||||
/// <param name="v">VideoCopy to compress</param>
|
||||
/// <returns>gzipped stream with width and height prepended</returns>
|
||||
delegate byte[] GzipFrameD(VideoCopy v);
|
||||
/// <summary>
|
||||
/// delegate for GzipFrame
|
||||
/// </summary>
|
||||
GzipFrameD GzipFrameDelegate;
|
||||
private delegate byte[] GzipFrameD(VideoCopy v);
|
||||
|
||||
// delegate for GzipFrame
|
||||
private GzipFrameD _gzipFrameDelegate;
|
||||
|
||||
/// <summary>
|
||||
/// adds a frame to the stream
|
||||
/// </summary>
|
||||
public void AddFrame(IVideoProvider source)
|
||||
{
|
||||
if (!workerT.IsAlive)
|
||||
if (!_workerT.IsAlive)
|
||||
{
|
||||
// signal some sort of error?
|
||||
return;
|
||||
threadQ.Add(GzipFrameDelegate.BeginInvoke(new VideoCopy(source), null, null));
|
||||
}
|
||||
|
||||
_threadQ.Add(_gzipFrameDelegate.BeginInvoke(new VideoCopy(source), null, null));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -758,10 +728,13 @@ namespace BizHawk.Client.EmuHawk
|
|||
/// </summary>
|
||||
public void AddSamples(short[] samples)
|
||||
{
|
||||
if (!workerT.IsAlive)
|
||||
if (!_workerT.IsAlive)
|
||||
{
|
||||
// signal some sort of error?
|
||||
return;
|
||||
threadQ.Add((short[])samples.Clone());
|
||||
}
|
||||
|
||||
_threadQ.Add((short[])samples.Clone());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -769,11 +742,11 @@ namespace BizHawk.Client.EmuHawk
|
|||
/// </summary>
|
||||
public void SetMetaData(string gameName, string authors, UInt64 lengthMS, UInt64 rerecords)
|
||||
{
|
||||
moviemetadata = new MovieMetaData();
|
||||
moviemetadata.gamename = gameName;
|
||||
moviemetadata.authors = authors;
|
||||
moviemetadata.lengthms = lengthMS;
|
||||
moviemetadata.rerecords = rerecords;
|
||||
_movieMetadata = new MovieMetaData();
|
||||
_movieMetadata.GameName = gameName;
|
||||
_movieMetadata.Authors = authors;
|
||||
_movieMetadata.LengthMs = lengthMS;
|
||||
_movieMetadata.Rerecords = rerecords;
|
||||
}
|
||||
|
||||
public string DesiredExtension()
|
||||
|
@ -790,10 +763,10 @@ namespace BizHawk.Client.EmuHawk
|
|||
|
||||
int c = Math.Min(Math.Max(Global.Config.JMDCompression, Deflater.NO_COMPRESSION), Deflater.BEST_COMPRESSION);
|
||||
|
||||
ct.compressionlevel = c;
|
||||
ct.numthreads = t;
|
||||
ct.CompressionLevel = c;
|
||||
ct.NumThreads = t;
|
||||
|
||||
token = ct;
|
||||
_token = ct;
|
||||
}
|
||||
|
||||
public void SetFrame(int frame) { }
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
using System;
|
||||
using System.Windows.Forms;
|
||||
using System.Windows.Forms;
|
||||
using System.Globalization;
|
||||
|
||||
namespace BizHawk.Client.EmuHawk
|
||||
|
@ -20,7 +19,7 @@ namespace BizHawk.Client.EmuHawk
|
|||
|
||||
string keyInput = e.KeyChar.ToString();
|
||||
|
||||
if (Char.IsDigit(e.KeyChar))
|
||||
if (char.IsDigit(e.KeyChar))
|
||||
{
|
||||
// Digits are OK
|
||||
}
|
||||
|
@ -34,16 +33,12 @@ namespace BizHawk.Client.EmuHawk
|
|||
}
|
||||
else if (keyInput.Equals(groupSeparator))
|
||||
{
|
||||
// group seperator is ok
|
||||
// group separator is ok
|
||||
}
|
||||
else if (e.KeyChar == '\b')
|
||||
{
|
||||
// Backspace key is OK
|
||||
}
|
||||
// else if ((ModifierKeys & (Keys.Control | Keys.Alt)) != 0)
|
||||
// {
|
||||
// // Let the edit control handle control and alt key combinations
|
||||
// }
|
||||
else if (AllowSpace && e.KeyChar == ' ')
|
||||
{
|
||||
|
||||
|
@ -58,8 +53,6 @@ namespace BizHawk.Client.EmuHawk
|
|||
|
||||
public int IntValue => int.Parse(Text);
|
||||
|
||||
public decimal DecimalValue => decimal.Parse(Text);
|
||||
|
||||
public bool AllowSpace { get; set; }
|
||||
|
||||
public bool AllowDecimal { get; set; }
|
||||
|
|
|
@ -11,7 +11,7 @@ namespace BizHawk.Client.EmuHawk
|
|||
/// implements a simple muxer for the NUT media format
|
||||
/// http://ffmpeg.org/~michael/nut.txt
|
||||
/// </summary>
|
||||
class NutMuxer
|
||||
public class NutMuxer
|
||||
{
|
||||
// this code isn't really any good for general purpose nut creation
|
||||
|
||||
|
@ -19,8 +19,8 @@ namespace BizHawk.Client.EmuHawk
|
|||
|
||||
public class ReusableBufferPool<T>
|
||||
{
|
||||
private List<T[]> _available = new List<T[]>();
|
||||
private ICollection<T[]> _inuse = new HashSet<T[]>();
|
||||
private readonly List<T[]> _available = new List<T[]>();
|
||||
private readonly ICollection<T[]> _inUse = new HashSet<T[]>();
|
||||
|
||||
private readonly int _capacity;
|
||||
|
||||
|
@ -32,13 +32,15 @@ namespace BizHawk.Client.EmuHawk
|
|||
|
||||
private T[] GetBufferInternal(int length, bool zerofill, Func<T[], bool> criteria)
|
||||
{
|
||||
if (_inuse.Count == _capacity)
|
||||
if (_inUse.Count == _capacity)
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
T[] candidate = _available.FirstOrDefault(criteria);
|
||||
if (candidate == null)
|
||||
{
|
||||
if (_available.Count + _inuse.Count == _capacity)
|
||||
if (_available.Count + _inUse.Count == _capacity)
|
||||
{
|
||||
// out of space! should not happen often
|
||||
Console.WriteLine("Purging");
|
||||
|
@ -49,10 +51,14 @@ namespace BizHawk.Client.EmuHawk
|
|||
else
|
||||
{
|
||||
if (zerofill)
|
||||
{
|
||||
Array.Clear(candidate, 0, candidate.Length);
|
||||
}
|
||||
|
||||
_available.Remove(candidate);
|
||||
}
|
||||
_inuse.Add(candidate);
|
||||
|
||||
_inUse.Add(candidate);
|
||||
return candidate;
|
||||
}
|
||||
|
||||
|
@ -69,8 +75,11 @@ namespace BizHawk.Client.EmuHawk
|
|||
/// <exception cref="ArgumentException"><paramref name="buffer"/> is not in use</exception>
|
||||
public void ReleaseBuffer(T[] buffer)
|
||||
{
|
||||
if (!_inuse.Remove(buffer))
|
||||
if (!_inUse.Remove(buffer))
|
||||
{
|
||||
throw new ArgumentException();
|
||||
}
|
||||
|
||||
_available.Add(buffer);
|
||||
}
|
||||
}
|
||||
|
@ -82,7 +91,7 @@ namespace BizHawk.Client.EmuHawk
|
|||
/// <summary>
|
||||
/// variable length value, unsigned
|
||||
/// </summary>
|
||||
static void WriteVarU(ulong v, Stream stream)
|
||||
private static void WriteVarU(ulong v, Stream stream)
|
||||
{
|
||||
byte[] b = new byte[10];
|
||||
int i = 0;
|
||||
|
@ -94,8 +103,11 @@ namespace BizHawk.Client.EmuHawk
|
|||
b[i++] = (byte)(v & 127);
|
||||
v /= 128;
|
||||
} while (v > 0);
|
||||
|
||||
for (; i > 0; i--)
|
||||
{
|
||||
stream.WriteByte(b[i - 1]);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -104,37 +116,30 @@ namespace BizHawk.Client.EmuHawk
|
|||
static void WriteVarU(int v, Stream stream)
|
||||
{
|
||||
if (v < 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(v), "unsigned must be non-negative");
|
||||
}
|
||||
|
||||
WriteVarU((ulong)v, stream);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// variable length value, unsigned
|
||||
/// </summary>
|
||||
static void WriteVarU(long v, Stream stream)
|
||||
private static void WriteVarU(long v, Stream stream)
|
||||
{
|
||||
if (v < 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(v), "unsigned must be non-negative");
|
||||
WriteVarU((ulong)v, stream);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// variable length value, signed
|
||||
/// </summary>
|
||||
static void WriteVarS(long v, Stream stream)
|
||||
{
|
||||
ulong temp;
|
||||
if (v < 0)
|
||||
temp = 1 + 2 * (ulong)(-v);
|
||||
else
|
||||
temp = 2 * (ulong)(v);
|
||||
WriteVarU(temp - 1, stream);
|
||||
WriteVarU((ulong)v, stream);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// utf-8 string with length prepended
|
||||
/// </summary>
|
||||
static void WriteString(string s, Stream stream)
|
||||
private static void WriteString(string s, Stream stream)
|
||||
{
|
||||
WriteBytes(Encoding.UTF8.GetBytes(s), stream);
|
||||
}
|
||||
|
@ -151,7 +156,7 @@ namespace BizHawk.Client.EmuHawk
|
|||
/// <summary>
|
||||
/// big endian 64 bit unsigned
|
||||
/// </summary>
|
||||
static void WriteBE64(ulong v, Stream stream)
|
||||
private static void WriteBe64(ulong v, Stream stream)
|
||||
{
|
||||
byte[] b = new byte[8];
|
||||
for (int i = 7; i >= 0; i--)
|
||||
|
@ -159,13 +164,14 @@ namespace BizHawk.Client.EmuHawk
|
|||
b[i] = (byte)(v & 255);
|
||||
v >>= 8;
|
||||
}
|
||||
|
||||
stream.Write(b, 0, 8);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// big endian 32 bit unsigned
|
||||
/// </summary>
|
||||
static void WriteBE32(uint v, Stream stream)
|
||||
private static void WriteBe32(uint v, Stream stream)
|
||||
{
|
||||
byte[] b = new byte[4];
|
||||
for (int i = 3; i >= 0; i--)
|
||||
|
@ -173,20 +179,7 @@ namespace BizHawk.Client.EmuHawk
|
|||
b[i] = (byte)(v & 255);
|
||||
v >>= 8;
|
||||
}
|
||||
stream.Write(b, 0, 4);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// big endian 32 bit unsigned
|
||||
/// </summary>
|
||||
static void WriteBE32(int v, Stream stream)
|
||||
{
|
||||
byte[] b = new byte[4];
|
||||
for (int i = 3; i >= 0; i--)
|
||||
{
|
||||
b[i] = (byte)(v & 255);
|
||||
v >>= 8;
|
||||
}
|
||||
stream.Write(b, 0, 4);
|
||||
}
|
||||
|
||||
|
@ -194,7 +187,7 @@ namespace BizHawk.Client.EmuHawk
|
|||
|
||||
#region CRC calculator
|
||||
|
||||
static readonly uint[] CRCtable = new uint[]
|
||||
private static readonly uint[] CrcTable =
|
||||
{
|
||||
0x00000000, 0x04C11DB7, 0x09823B6E, 0x0D4326D9,
|
||||
0x130476DC, 0x17C56B6B, 0x1A864DB2, 0x1E475005,
|
||||
|
@ -206,14 +199,14 @@ namespace BizHawk.Client.EmuHawk
|
|||
/// seems to be different than standard CRC32?????
|
||||
/// </summary>
|
||||
/// <returns>crc32, nut variant</returns>
|
||||
static uint NutCRC32(byte[] buf)
|
||||
private static uint NutCRC32(byte[] buf)
|
||||
{
|
||||
uint crc = 0;
|
||||
for (int i = 0; i < buf.Length; i++)
|
||||
foreach (var b in buf)
|
||||
{
|
||||
crc ^= (uint)buf[i] << 24;
|
||||
crc = (crc << 4) ^ CRCtable[crc >> 28];
|
||||
crc = (crc << 4) ^ CRCtable[crc >> 28];
|
||||
crc ^= (uint)b << 24;
|
||||
crc = (crc << 4) ^ CrcTable[crc >> 28];
|
||||
crc = (crc << 4) ^ CrcTable[crc >> 28];
|
||||
}
|
||||
return crc;
|
||||
}
|
||||
|
@ -221,9 +214,9 @@ namespace BizHawk.Client.EmuHawk
|
|||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// writes a single packet out, including checksums
|
||||
/// writes a single packet out, including CheckSums
|
||||
/// </summary>
|
||||
class NutPacket : Stream
|
||||
private class NutPacket : Stream
|
||||
{
|
||||
public enum StartCode : ulong
|
||||
{
|
||||
|
@ -234,23 +227,22 @@ namespace BizHawk.Client.EmuHawk
|
|||
Info = 0x4e49ab68b596ba78
|
||||
};
|
||||
|
||||
MemoryStream data;
|
||||
StartCode startcode;
|
||||
Stream underlying;
|
||||
private MemoryStream _data;
|
||||
private readonly StartCode _startCode;
|
||||
private readonly Stream _underlying;
|
||||
|
||||
/// <summary>
|
||||
/// create a new NutPacket
|
||||
/// </summary>
|
||||
/// <param name="startcode">startcode for this packet</param>
|
||||
/// <param name="startCode">startCode for this packet</param>
|
||||
/// <param name="underlying">stream to write to</param>
|
||||
public NutPacket(StartCode startcode, Stream underlying)
|
||||
public NutPacket(StartCode startCode, Stream underlying)
|
||||
{
|
||||
data = new MemoryStream();
|
||||
this.startcode = startcode;
|
||||
this.underlying = underlying;
|
||||
_data = new MemoryStream();
|
||||
_startCode = startCode;
|
||||
_underlying = underlying;
|
||||
}
|
||||
|
||||
|
||||
public override bool CanRead => false;
|
||||
|
||||
public override bool CanSeek => false;
|
||||
|
@ -265,38 +257,29 @@ namespace BizHawk.Client.EmuHawk
|
|||
{
|
||||
// first, prep header
|
||||
var header = new MemoryStream();
|
||||
WriteBE64((ulong)startcode, header);
|
||||
WriteVarU(data.Length + 4, header); // +4 for checksum
|
||||
if (data.Length > 4092)
|
||||
WriteBe64((ulong)_startCode, header);
|
||||
WriteVarU(_data.Length + 4, header); // +4 for checksum
|
||||
if (_data.Length > 4092)
|
||||
{
|
||||
WriteBE32(NutCRC32(header.ToArray()), header);
|
||||
WriteBe32(NutCRC32(header.ToArray()), header);
|
||||
}
|
||||
|
||||
var tmp = header.ToArray();
|
||||
underlying.Write(tmp, 0, tmp.Length);
|
||||
_underlying.Write(tmp, 0, tmp.Length);
|
||||
|
||||
tmp = data.ToArray();
|
||||
underlying.Write(tmp, 0, tmp.Length);
|
||||
WriteBE32(NutCRC32(tmp), underlying);
|
||||
tmp = _data.ToArray();
|
||||
_underlying.Write(tmp, 0, tmp.Length);
|
||||
WriteBe32(NutCRC32(tmp), _underlying);
|
||||
|
||||
data = null;
|
||||
_data = null;
|
||||
}
|
||||
|
||||
public override long Length
|
||||
{
|
||||
get { throw new NotImplementedException(); }
|
||||
}
|
||||
public override long Length => throw new NotImplementedException();
|
||||
|
||||
public override long Position
|
||||
{
|
||||
get
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
set
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
get => throw new NotImplementedException();
|
||||
set => throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
|
@ -316,7 +299,7 @@ namespace BizHawk.Client.EmuHawk
|
|||
|
||||
public override void Write(byte[] buffer, int offset, int count)
|
||||
{
|
||||
data.Write(buffer, offset, count);
|
||||
_data.Write(buffer, offset, count);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -325,59 +308,51 @@ namespace BizHawk.Client.EmuHawk
|
|||
/// <summary>
|
||||
/// stores basic AV parameters
|
||||
/// </summary>
|
||||
class AVParams
|
||||
private class AVParams
|
||||
{
|
||||
public int width, height, samplerate, fpsnum, fpsden, channels;
|
||||
public int Width { get; set; }
|
||||
public int Height { get; set; }
|
||||
public int Samplerate { get; set; }
|
||||
public int FpsNum { get; set; }
|
||||
public int FpsDen { get; set; }
|
||||
public int Channels { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// puts fpsnum, fpsden in lowest terms
|
||||
/// puts fpsNum, fpsDen in lowest terms
|
||||
/// </summary>
|
||||
public void Reduce()
|
||||
{
|
||||
int gcd = (int) BigInteger.GreatestCommonDivisor(new BigInteger(fpsnum), new BigInteger(fpsden));
|
||||
fpsnum /= gcd;
|
||||
fpsden /= gcd;
|
||||
int gcd = (int)BigInteger.GreatestCommonDivisor(new BigInteger(FpsNum), new BigInteger(FpsDen));
|
||||
FpsNum /= gcd;
|
||||
FpsDen /= gcd;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// stores basic AV parameters
|
||||
/// </summary>
|
||||
AVParams avparams;
|
||||
// stores basic AV parameters
|
||||
private readonly AVParams _avParams;
|
||||
|
||||
/// <summary>
|
||||
/// target output for nut stream
|
||||
/// </summary>
|
||||
Stream output;
|
||||
// target output for nut stream
|
||||
private Stream _output;
|
||||
|
||||
/// <summary>
|
||||
/// PTS of video stream. timebase is 1/framerate, so this is equal to number of frames
|
||||
/// </summary>
|
||||
ulong videopts;
|
||||
// PTS of video stream. timebase is 1/framerate, so this is equal to number of frames
|
||||
private ulong _videoOpts;
|
||||
|
||||
/// <summary>
|
||||
/// PTS of audio stream. timebase is 1/samplerate, so this is equal to number of samples
|
||||
/// </summary>
|
||||
ulong audiopts;
|
||||
// PTS of audio stream. timebase is 1/samplerate, so this is equal to number of samples
|
||||
private ulong _audioPts;
|
||||
|
||||
/// <summary>
|
||||
/// has EOR been writen on this stream?
|
||||
/// </summary>
|
||||
bool videodone;
|
||||
/// <summary>
|
||||
/// has EOR been written on this stream?
|
||||
/// </summary>
|
||||
bool audiodone;
|
||||
// has EOR been written on this stream?
|
||||
private bool _videoDone;
|
||||
|
||||
// has EOR been written on this stream?
|
||||
private bool _audioDone;
|
||||
|
||||
/// <summary>
|
||||
/// video packets waiting to be written
|
||||
/// </summary>
|
||||
Queue<NutFrame> videoqueue;
|
||||
/// <summary>
|
||||
/// audio packets waiting to be written
|
||||
/// </summary>
|
||||
Queue<NutFrame> audioqueue;
|
||||
// video packets waiting to be written
|
||||
private readonly Queue<NutFrame> _videoQueue;
|
||||
|
||||
// audio packets waiting to be written
|
||||
private readonly Queue<NutFrame> _audioQueue;
|
||||
|
||||
ReusableBufferPool<byte> _bufferpool = new ReusableBufferPool<byte>(12);
|
||||
readonly ReusableBufferPool<byte> _bufferPool = new ReusableBufferPool<byte>(12);
|
||||
|
||||
#endregion
|
||||
|
||||
|
@ -386,13 +361,13 @@ namespace BizHawk.Client.EmuHawk
|
|||
/// <summary>
|
||||
/// write out the main header
|
||||
/// </summary>
|
||||
void writemainheader()
|
||||
private void WriteMainHeader()
|
||||
{
|
||||
// note: this file starttag not actually part of main headers
|
||||
var tmp = Encoding.ASCII.GetBytes("nut/multimedia container\0");
|
||||
output.Write(tmp, 0, tmp.Length);
|
||||
_output.Write(tmp, 0, tmp.Length);
|
||||
|
||||
var header = new NutPacket(NutPacket.StartCode.Main, output);
|
||||
var header = new NutPacket(NutPacket.StartCode.Main, _output);
|
||||
|
||||
WriteVarU(3, header); // version
|
||||
WriteVarU(2, header); // stream_count
|
||||
|
@ -400,15 +375,15 @@ namespace BizHawk.Client.EmuHawk
|
|||
|
||||
WriteVarU(2, header); // time_base_count
|
||||
// timebase is length of single frame, so reversed num+den is intentional
|
||||
WriteVarU(avparams.fpsden, header); // time_base_num[0]
|
||||
WriteVarU(avparams.fpsnum, header); // time_base_den[0]
|
||||
WriteVarU(_avParams.FpsDen, header); // time_base_num[0]
|
||||
WriteVarU(_avParams.FpsNum, header); // time_base_den[0]
|
||||
WriteVarU(1, header); // time_base_num[1]
|
||||
WriteVarU(avparams.samplerate, header); // time_base_den[1]
|
||||
WriteVarU(_avParams.Samplerate, header); // time_base_den[1]
|
||||
|
||||
// frame flag compression is ignored for simplicity
|
||||
for (int i = 0; i < 255; i++) // not 256 because entry 0x4e is skipped (as it would indicate a startcode)
|
||||
{
|
||||
WriteVarU((1 << 12), header); // tmp_flag = FLAG_CODED
|
||||
WriteVarU(1 << 12, header); // tmp_flag = FLAG_CODED
|
||||
WriteVarU(0, header); // tmp_fields
|
||||
}
|
||||
|
||||
|
@ -421,12 +396,10 @@ namespace BizHawk.Client.EmuHawk
|
|||
header.Flush();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// write out the 0th stream header (video)
|
||||
/// </summary>
|
||||
void writevideoheader()
|
||||
// write out the 0th stream header (video)
|
||||
private void WriteVideoHeader()
|
||||
{
|
||||
var header = new NutPacket(NutPacket.StartCode.Stream, output);
|
||||
var header = new NutPacket(NutPacket.StartCode.Stream, _output);
|
||||
WriteVarU(0, header); // stream_id
|
||||
WriteVarU(0, header); // stream_class = video
|
||||
WriteString("BGRA", header); // fourcc = "BGRA"
|
||||
|
@ -438,8 +411,8 @@ namespace BizHawk.Client.EmuHawk
|
|||
WriteBytes(new byte[0], header); // codec_specific_data
|
||||
|
||||
// stream_class = video
|
||||
WriteVarU(avparams.width, header); // width
|
||||
WriteVarU(avparams.height, header); // height
|
||||
WriteVarU(_avParams.Width, header); // width
|
||||
WriteVarU(_avParams.Height, header); // height
|
||||
WriteVarU(1, header); // sample_width
|
||||
WriteVarU(1, header); // sample_height
|
||||
WriteVarU(18, header); // colorspace_type = full range rec709 (avisynth's "PC.709")
|
||||
|
@ -447,26 +420,24 @@ namespace BizHawk.Client.EmuHawk
|
|||
header.Flush();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// write out the 1st stream header (audio)
|
||||
/// </summary>
|
||||
void writeaudioheader()
|
||||
// write out the 1st stream header (audio)
|
||||
private void WriteAudioHeader()
|
||||
{
|
||||
var header = new NutPacket(NutPacket.StartCode.Stream, output);
|
||||
var header = new NutPacket(NutPacket.StartCode.Stream, _output);
|
||||
WriteVarU(1, header); // stream_id
|
||||
WriteVarU(1, header); // stream_class = audio
|
||||
WriteString("\x01\x00\x00\x00", header); // fourcc = 01 00 00 00
|
||||
WriteVarU(1, header); // time_base_id = 1
|
||||
WriteVarU(8, header); // msb_pts_shift
|
||||
WriteVarU(avparams.samplerate, header); // max_pts_distance
|
||||
WriteVarU(_avParams.Samplerate, header); // max_pts_distance
|
||||
WriteVarU(0, header); // decode_delay
|
||||
WriteVarU(0, header); // stream_flags = none; no FIXED_FPS because we aren't guaranteeing same-size audio chunks
|
||||
WriteBytes(new byte[0], header); // codec_specific_data
|
||||
|
||||
// stream_class = audio
|
||||
WriteVarU(avparams.samplerate, header); // samplerate_num
|
||||
WriteVarU(_avParams.Samplerate, header); // samplerate_num
|
||||
WriteVarU(1, header); // samplerate_den
|
||||
WriteVarU(avparams.channels, header); // channel_count
|
||||
WriteVarU(_avParams.Channels, header); // channel_count
|
||||
|
||||
header.Flush();
|
||||
}
|
||||
|
@ -477,75 +448,84 @@ namespace BizHawk.Client.EmuHawk
|
|||
/// stores a single frame with syncpoint, in mux-ready form
|
||||
/// used because reordering of audio and video can be needed for proper interleave
|
||||
/// </summary>
|
||||
class NutFrame
|
||||
private class NutFrame
|
||||
{
|
||||
/// <summary>
|
||||
/// data ready to be written to stream/disk
|
||||
/// </summary>
|
||||
byte[] data;
|
||||
private readonly byte[] _data;
|
||||
|
||||
/// <summary>
|
||||
/// valid length of the data
|
||||
/// </summary>
|
||||
int actual_length;
|
||||
private readonly int _actualLength;
|
||||
|
||||
/// <summary>
|
||||
/// presentation timestamp
|
||||
/// </summary>
|
||||
ulong pts;
|
||||
private readonly ulong _pts;
|
||||
|
||||
/// <summary>
|
||||
/// fraction of the specified timebase
|
||||
/// </summary>
|
||||
ulong ptsnum, ptsden;
|
||||
private readonly ulong _ptsNum;
|
||||
|
||||
ReusableBufferPool<byte> _pool;
|
||||
/// <summary>
|
||||
/// fraction of the specified timebase
|
||||
/// </summary>
|
||||
private readonly ulong _ptsDen;
|
||||
|
||||
private readonly ReusableBufferPool<byte> _pool;
|
||||
|
||||
/// <param name="payload">frame data</param>
|
||||
/// <param name="payloadlen">actual length of frame data</param>
|
||||
/// <param name="payLoadLen">actual length of frame data</param>
|
||||
/// <param name="pts">presentation timestamp</param>
|
||||
/// <param name="ptsnum">numerator of timebase</param>
|
||||
/// <param name="ptsden">denominator of timebase</param>
|
||||
/// <param name="ptsindex">which timestamp base is used, assumed to be also stream number</param>
|
||||
public NutFrame(byte[] payload, int payloadlen, ulong pts, ulong ptsnum, ulong ptsden, int ptsindex, ReusableBufferPool<byte> pool)
|
||||
/// <param name="ptsNum">numerator of timebase</param>
|
||||
/// <param name="ptsDen">denominator of timebase</param>
|
||||
/// <param name="ptsIndex">which timestamp base is used, assumed to be also stream number</param>
|
||||
public NutFrame(byte[] payload, int payLoadLen, ulong pts, ulong ptsNum, ulong ptsDen, int ptsIndex, ReusableBufferPool<byte> pool)
|
||||
{
|
||||
this.pts = pts;
|
||||
this.ptsnum = ptsnum;
|
||||
this.ptsden = ptsden;
|
||||
_pts = pts;
|
||||
_ptsNum = ptsNum;
|
||||
_ptsDen = ptsDen;
|
||||
|
||||
this._pool = pool;
|
||||
data = pool.GetBufferAtLeast(payloadlen + 2048);
|
||||
var frame = new MemoryStream(data);
|
||||
_pool = pool;
|
||||
_data = pool.GetBufferAtLeast(payLoadLen + 2048);
|
||||
var frame = new MemoryStream(_data);
|
||||
|
||||
// create syncpoint
|
||||
var sync = new NutPacket(NutPacket.StartCode.Syncpoint, frame);
|
||||
WriteVarU(pts * 2 + (ulong)ptsindex, sync); // global_key_pts
|
||||
WriteVarU(pts * 2 + (ulong)ptsIndex, sync); // global_key_pts
|
||||
WriteVarU(1, sync); // back_ptr_div_16, this is wrong
|
||||
sync.Flush();
|
||||
|
||||
|
||||
var frameheader = new MemoryStream();
|
||||
frameheader.WriteByte(0); // frame_code
|
||||
var frameHeader = new MemoryStream();
|
||||
frameHeader.WriteByte(0); // frame_code
|
||||
|
||||
// frame_flags = FLAG_CODED, so:
|
||||
int flags = 0;
|
||||
flags |= 1 << 0; // FLAG_KEY
|
||||
if (payloadlen == 0)
|
||||
if (payLoadLen == 0)
|
||||
{
|
||||
flags |= 1 << 1; // FLAG_EOR
|
||||
}
|
||||
|
||||
flags |= 1 << 3; // FLAG_CODED_PTS
|
||||
flags |= 1 << 4; // FLAG_STREAM_ID
|
||||
flags |= 1 << 5; // FLAG_SIZE_MSB
|
||||
flags |= 1 << 6; // FLAG_CHECKSUM
|
||||
WriteVarU(flags, frameheader);
|
||||
WriteVarU(ptsindex, frameheader); // stream_id
|
||||
WriteVarU(pts + 256, frameheader); // coded_pts = pts + 1 << msb_pts_shift
|
||||
WriteVarU(payloadlen, frameheader); // data_size_msb
|
||||
WriteVarU(flags, frameHeader);
|
||||
WriteVarU(ptsIndex, frameHeader); // stream_id
|
||||
WriteVarU(pts + 256, frameHeader); // coded_pts = pts + 1 << msb_pts_shift
|
||||
WriteVarU(payLoadLen, frameHeader); // data_size_msb
|
||||
|
||||
var frameheaderarr = frameheader.ToArray();
|
||||
frame.Write(frameheaderarr, 0, frameheaderarr.Length);
|
||||
WriteBE32(NutCRC32(frameheaderarr), frame); // checksum
|
||||
frame.Write(payload, 0, payloadlen);
|
||||
var frameHeaderArr = frameHeader.ToArray();
|
||||
frame.Write(frameHeaderArr, 0, frameHeaderArr.Length);
|
||||
WriteBe32(NutCRC32(frameHeaderArr), frame); // checksum
|
||||
frame.Write(payload, 0, payLoadLen);
|
||||
|
||||
actual_length = (int)frame.Position;
|
||||
_actualLength = (int)frame.Position;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -553,19 +533,19 @@ namespace BizHawk.Client.EmuHawk
|
|||
/// </summary>
|
||||
public static bool operator <=(NutFrame lhs, NutFrame rhs)
|
||||
{
|
||||
BigInteger left = new BigInteger(lhs.pts);
|
||||
left = left * lhs.ptsnum * rhs.ptsden;
|
||||
BigInteger right = new BigInteger(rhs.pts);
|
||||
right = right * rhs.ptsnum * lhs.ptsden;
|
||||
BigInteger left = new BigInteger(lhs._pts);
|
||||
left = left * lhs._ptsNum * rhs._ptsDen;
|
||||
BigInteger right = new BigInteger(rhs._pts);
|
||||
right = right * rhs._ptsNum * lhs._ptsDen;
|
||||
|
||||
return left <= right;
|
||||
}
|
||||
public static bool operator >=(NutFrame lhs, NutFrame rhs)
|
||||
{
|
||||
BigInteger left = new BigInteger(lhs.pts);
|
||||
left = left * lhs.ptsnum * rhs.ptsden;
|
||||
BigInteger right = new BigInteger(rhs.pts);
|
||||
right = right * rhs.ptsnum * lhs.ptsden;
|
||||
BigInteger left = new BigInteger(lhs._pts);
|
||||
left = left * lhs._ptsNum * rhs._ptsDen;
|
||||
BigInteger right = new BigInteger(rhs._pts);
|
||||
right = right * rhs._ptsNum * lhs._ptsDen;
|
||||
|
||||
return left >= right;
|
||||
}
|
||||
|
@ -575,9 +555,8 @@ namespace BizHawk.Client.EmuHawk
|
|||
/// </summary>
|
||||
public void WriteData(Stream dest)
|
||||
{
|
||||
dest.Write(data, 0, actual_length);
|
||||
_pool.ReleaseBuffer(data);
|
||||
//dbg.WriteLine($"{pts},{ptsnum},{ptsden}");
|
||||
dest.Write(_data, 0, _actualLength);
|
||||
_pool.ReleaseBuffer(_data);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -587,21 +566,26 @@ namespace BizHawk.Client.EmuHawk
|
|||
/// <exception cref="InvalidOperationException">already written EOR</exception>
|
||||
public void WriteVideoFrame(int[] video)
|
||||
{
|
||||
if (videodone)
|
||||
if (_videoDone)
|
||||
throw new InvalidOperationException("Can't write data after end of relevance!");
|
||||
if (audioqueue.Count > 5)
|
||||
if (_audioQueue.Count > 5)
|
||||
throw new Exception("A/V Desync?");
|
||||
int datalen = video.Length * sizeof(int);
|
||||
byte[] data = _bufferpool.GetBufferAtLeast(datalen);
|
||||
Buffer.BlockCopy(video, 0, data, 0, datalen);
|
||||
if (datalen == 0)
|
||||
videodone = true;
|
||||
var f = new NutFrame(data, datalen, videopts, (ulong) avparams.fpsden, (ulong) avparams.fpsnum, 0, _bufferpool);
|
||||
_bufferpool.ReleaseBuffer(data);
|
||||
videopts++;
|
||||
videoqueue.Enqueue(f);
|
||||
while (audioqueue.Count > 0 && f >= audioqueue.Peek())
|
||||
audioqueue.Dequeue().WriteData(output);
|
||||
int dataLen = video.Length * sizeof(int);
|
||||
byte[] data = _bufferPool.GetBufferAtLeast(dataLen);
|
||||
Buffer.BlockCopy(video, 0, data, 0, dataLen);
|
||||
if (dataLen == 0)
|
||||
{
|
||||
_videoDone = true;
|
||||
}
|
||||
|
||||
var f = new NutFrame(data, dataLen, _videoOpts, (ulong) _avParams.FpsDen, (ulong) _avParams.FpsNum, 0, _bufferPool);
|
||||
_bufferPool.ReleaseBuffer(data);
|
||||
_videoOpts++;
|
||||
_videoQueue.Enqueue(f);
|
||||
while (_audioQueue.Count > 0 && f >= _audioQueue.Peek())
|
||||
{
|
||||
_audioQueue.Dequeue().WriteData(_output);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>write an audio frame to the stream</summary>
|
||||
|
@ -610,22 +594,32 @@ namespace BizHawk.Client.EmuHawk
|
|||
/// <exception cref="InvalidOperationException">already written EOR</exception>
|
||||
public void WriteAudioFrame(short[] samples)
|
||||
{
|
||||
if (audiodone)
|
||||
if (_audioDone)
|
||||
{
|
||||
throw new Exception("Can't write audio after end of relevance!");
|
||||
if (videoqueue.Count > 5)
|
||||
throw new Exception("A/V Desync?");
|
||||
int datalen = samples.Length * sizeof(short);
|
||||
byte[] data = _bufferpool.GetBufferAtLeast(datalen);
|
||||
Buffer.BlockCopy(samples, 0, data, 0, datalen);
|
||||
if (datalen == 0)
|
||||
audiodone = true;
|
||||
}
|
||||
|
||||
var f = new NutFrame(data, datalen, audiopts, 1, (ulong)avparams.samplerate, 1, _bufferpool);
|
||||
_bufferpool.ReleaseBuffer(data);
|
||||
audiopts += (ulong)samples.Length / (ulong)avparams.channels;
|
||||
audioqueue.Enqueue(f);
|
||||
while (videoqueue.Count > 0 && f >= videoqueue.Peek())
|
||||
videoqueue.Dequeue().WriteData(output);
|
||||
if (_videoQueue.Count > 5)
|
||||
{
|
||||
throw new Exception("A/V Desync?");
|
||||
}
|
||||
|
||||
int dataLen = samples.Length * sizeof(short);
|
||||
byte[] data = _bufferPool.GetBufferAtLeast(dataLen);
|
||||
Buffer.BlockCopy(samples, 0, data, 0, dataLen);
|
||||
if (dataLen == 0)
|
||||
{
|
||||
_audioDone = true;
|
||||
}
|
||||
|
||||
var f = new NutFrame(data, dataLen, _audioPts, 1, (ulong)_avParams.Samplerate, 1, _bufferPool);
|
||||
_bufferPool.ReleaseBuffer(data);
|
||||
_audioPts += (ulong)samples.Length / (ulong)_avParams.Channels;
|
||||
_audioQueue.Enqueue(f);
|
||||
while (_videoQueue.Count > 0 && f >= _videoQueue.Peek())
|
||||
{
|
||||
_videoQueue.Dequeue().WriteData(_output);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -633,35 +627,38 @@ namespace BizHawk.Client.EmuHawk
|
|||
/// </summary>
|
||||
/// <param name="width">video width</param>
|
||||
/// <param name="height">video height</param>
|
||||
/// <param name="fpsnum">fps numerator</param>
|
||||
/// <param name="fpsden">fps denominator</param>
|
||||
/// <param name="fpsNum">fps numerator</param>
|
||||
/// <param name="fpsDen">fps denominator</param>
|
||||
/// <param name="samplerate">audio samplerate</param>
|
||||
/// <param name="channels">audio number of channels</param>
|
||||
/// <param name="underlying">Stream to write to</param>
|
||||
public NutMuxer(int width, int height, int fpsnum, int fpsden, int samplerate, int channels, Stream underlying)
|
||||
public NutMuxer(int width, int height, int fpsNum, int fpsDen, int samplerate, int channels, Stream underlying)
|
||||
{
|
||||
avparams = new AVParams();
|
||||
avparams.width = width;
|
||||
avparams.height = height;
|
||||
avparams.fpsnum = fpsnum;
|
||||
avparams.fpsden = fpsden;
|
||||
avparams.Reduce(); // timebases in nut MUST be relatively prime
|
||||
avparams.samplerate = samplerate;
|
||||
avparams.channels = channels;
|
||||
output = underlying;
|
||||
_avParams = new AVParams
|
||||
{
|
||||
Width = width,
|
||||
Height = height,
|
||||
FpsNum = fpsNum,
|
||||
FpsDen = fpsDen
|
||||
};
|
||||
|
||||
audiopts = 0;
|
||||
videopts = 0;
|
||||
_avParams.Reduce(); // TimeBases in nut MUST be relatively prime
|
||||
_avParams.Samplerate = samplerate;
|
||||
_avParams.Channels = channels;
|
||||
_output = underlying;
|
||||
|
||||
audioqueue = new Queue<NutFrame>();
|
||||
videoqueue = new Queue<NutFrame>();
|
||||
_audioPts = 0;
|
||||
_videoOpts = 0;
|
||||
|
||||
writemainheader();
|
||||
writevideoheader();
|
||||
writeaudioheader();
|
||||
_audioQueue = new Queue<NutFrame>();
|
||||
_videoQueue = new Queue<NutFrame>();
|
||||
|
||||
videodone = false;
|
||||
audiodone = false;
|
||||
WriteMainHeader();
|
||||
WriteVideoHeader();
|
||||
WriteAudioHeader();
|
||||
|
||||
_videoDone = false;
|
||||
_audioDone = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -670,26 +667,41 @@ namespace BizHawk.Client.EmuHawk
|
|||
/// </summary>
|
||||
public void Finish()
|
||||
{
|
||||
if (!videodone)
|
||||
if (!_videoDone)
|
||||
{
|
||||
WriteVideoFrame(new int[0]);
|
||||
if (!audiodone)
|
||||
}
|
||||
|
||||
if (!_audioDone)
|
||||
{
|
||||
WriteAudioFrame(new short[0]);
|
||||
}
|
||||
|
||||
// flush any remaining queued packets
|
||||
while (audioqueue.Count > 0 && videoqueue.Count > 0)
|
||||
while (_audioQueue.Count > 0 && _videoQueue.Count > 0)
|
||||
{
|
||||
if (audioqueue.Peek() <= videoqueue.Peek())
|
||||
audioqueue.Dequeue().WriteData(output);
|
||||
if (_audioQueue.Peek() <= _videoQueue.Peek())
|
||||
{
|
||||
_audioQueue.Dequeue().WriteData(_output);
|
||||
}
|
||||
else
|
||||
videoqueue.Dequeue().WriteData(output);
|
||||
{
|
||||
_videoQueue.Dequeue().WriteData(_output);
|
||||
}
|
||||
}
|
||||
while (audioqueue.Count > 0)
|
||||
audioqueue.Dequeue().WriteData(output);
|
||||
while (videoqueue.Count > 0)
|
||||
videoqueue.Dequeue().WriteData(output);
|
||||
|
||||
output.Close();
|
||||
output = null;
|
||||
while (_audioQueue.Count > 0)
|
||||
{
|
||||
_audioQueue.Dequeue().WriteData(_output);
|
||||
}
|
||||
|
||||
while (_videoQueue.Count > 0)
|
||||
{
|
||||
_videoQueue.Dequeue().WriteData(_output);
|
||||
}
|
||||
|
||||
_output.Close();
|
||||
_output = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Windows.Forms;
|
||||
|
||||
using BizHawk.Emulation.Common;
|
||||
|
||||
|
@ -10,12 +11,12 @@ namespace BizHawk.Client.EmuHawk
|
|||
/// uncompressed video and audio
|
||||
/// </summary>
|
||||
[VideoWriter("nut", "NUT writer", "Writes a series of .nut files to disk, a container format which can be opened by ffmpeg. All data is uncompressed. Splits occur on resolution changes. NOT RECCOMENDED FOR USE.")]
|
||||
class NutWriter : IVideoWriter
|
||||
public class NutWriter : IVideoWriter
|
||||
{
|
||||
/// <summary>
|
||||
/// dummy codec token class
|
||||
/// </summary>
|
||||
class NutWriterToken : IDisposable
|
||||
private class NutWriterToken : IDisposable
|
||||
{
|
||||
public void Dispose()
|
||||
{
|
||||
|
@ -28,37 +29,36 @@ namespace BizHawk.Client.EmuHawk
|
|||
{
|
||||
// ignored
|
||||
}
|
||||
public IDisposable AcquireVideoCodecToken(System.Windows.Forms.IWin32Window hwnd)
|
||||
public IDisposable AcquireVideoCodecToken(IWin32Window hwnd)
|
||||
{
|
||||
return new NutWriterToken();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// avparams
|
||||
/// </summary>
|
||||
private int fpsnum, fpsden, width, height, sampleRate, channels;
|
||||
|
||||
private NutMuxer _current = null;
|
||||
// avParams
|
||||
private int _fpsNum, _fpsDen, _width, _height, _sampleRate, _channels;
|
||||
|
||||
private NutMuxer _current;
|
||||
private string _baseName;
|
||||
private int _segment;
|
||||
|
||||
public void OpenFile(string baseName)
|
||||
{
|
||||
_baseName = Path.Combine(
|
||||
Path.GetDirectoryName(baseName),
|
||||
Path.GetFileNameWithoutExtension(baseName));
|
||||
Path.GetDirectoryName(baseName) ?? "",
|
||||
Path.GetFileNameWithoutExtension(baseName) ?? "");
|
||||
_segment = 0;
|
||||
|
||||
startsegment();
|
||||
StartSegment();
|
||||
}
|
||||
|
||||
private void startsegment()
|
||||
private void StartSegment()
|
||||
{
|
||||
var currentfile = File.Open($"{_baseName}_{_segment,4:D4}.nut", FileMode.Create, FileAccess.Write);
|
||||
_current = new NutMuxer(width, height, fpsnum, fpsden, sampleRate, channels, currentfile);
|
||||
var currentFile = File.Open($"{_baseName}_{_segment,4:D4}.nut", FileMode.Create, FileAccess.Write);
|
||||
_current = new NutMuxer(_width, _height, _fpsNum, _fpsDen, _sampleRate, _channels, currentFile);
|
||||
}
|
||||
|
||||
private void endsegment()
|
||||
private void EndSegment()
|
||||
{
|
||||
_current.Finish();
|
||||
_current = null;
|
||||
|
@ -66,12 +66,12 @@ namespace BizHawk.Client.EmuHawk
|
|||
|
||||
public void CloseFile()
|
||||
{
|
||||
endsegment();
|
||||
EndSegment();
|
||||
}
|
||||
|
||||
public void AddFrame(IVideoProvider source)
|
||||
{
|
||||
if (source.BufferHeight != height || source.BufferWidth != width)
|
||||
if (source.BufferHeight != _height || source.BufferWidth != _width)
|
||||
{
|
||||
SetVideoParameters(source.BufferWidth, source.BufferHeight);
|
||||
}
|
||||
|
@ -84,27 +84,27 @@ namespace BizHawk.Client.EmuHawk
|
|||
_current.WriteAudioFrame(samples);
|
||||
}
|
||||
|
||||
public void SetMovieParameters(int fpsnum, int fpsden)
|
||||
public void SetMovieParameters(int fpsNum, int fpsDen)
|
||||
{
|
||||
this.fpsnum = fpsnum;
|
||||
this.fpsden = fpsden;
|
||||
_fpsNum = fpsNum;
|
||||
_fpsDen = fpsDen;
|
||||
if (_current != null)
|
||||
{
|
||||
endsegment();
|
||||
EndSegment();
|
||||
_segment++;
|
||||
startsegment();
|
||||
StartSegment();
|
||||
}
|
||||
}
|
||||
|
||||
public void SetVideoParameters(int width, int height)
|
||||
{
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
_width = width;
|
||||
_height = height;
|
||||
if (_current != null)
|
||||
{
|
||||
endsegment();
|
||||
EndSegment();
|
||||
_segment++;
|
||||
startsegment();
|
||||
StartSegment();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -116,11 +116,11 @@ namespace BizHawk.Client.EmuHawk
|
|||
throw new ArgumentOutOfRangeException(nameof(bits), "Audio depth must be 16 bit!");
|
||||
}
|
||||
|
||||
this.sampleRate = sampleRate;
|
||||
this.channels = channels;
|
||||
_sampleRate = sampleRate;
|
||||
_channels = channels;
|
||||
}
|
||||
|
||||
public void SetMetaData(string gameName, string authors, ulong lengthMS, ulong rerecords)
|
||||
public void SetMetaData(string gameName, string authors, ulong lengthMs, ulong rerecords)
|
||||
{
|
||||
// could be implemented?
|
||||
}
|
||||
|
@ -129,16 +129,13 @@ namespace BizHawk.Client.EmuHawk
|
|||
{
|
||||
if (_current != null)
|
||||
{
|
||||
endsegment();
|
||||
EndSegment();
|
||||
}
|
||||
|
||||
_baseName = null;
|
||||
}
|
||||
|
||||
public string DesiredExtension()
|
||||
{
|
||||
return "nut";
|
||||
}
|
||||
public string DesiredExtension() => "nut";
|
||||
|
||||
public void SetDefaultVideoCodecToken()
|
||||
{
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing.Imaging;
|
||||
using System.Text;
|
||||
using System.Windows.Forms;
|
||||
|
||||
|
@ -26,24 +27,24 @@ namespace BizHawk.Client.EmuHawk
|
|||
|
||||
public void SetFrame(int frame)
|
||||
{
|
||||
mCurrFrame = frame;
|
||||
_mCurrFrame = frame;
|
||||
}
|
||||
|
||||
private int mCurrFrame;
|
||||
private string mBaseDirectory, mFramesDirectory;
|
||||
private string mProjectFile;
|
||||
private int _mCurrFrame;
|
||||
private string _mBaseDirectory, _mFramesDirectory;
|
||||
private string _mProjectFile;
|
||||
|
||||
public void OpenFile(string projFile)
|
||||
{
|
||||
mProjectFile = projFile;
|
||||
mBaseDirectory = Path.GetDirectoryName(mProjectFile);
|
||||
_mProjectFile = projFile;
|
||||
_mBaseDirectory = Path.GetDirectoryName(_mProjectFile) ?? "";
|
||||
string basename = Path.GetFileNameWithoutExtension(projFile);
|
||||
string framesDirFragment = $"{basename}_frames";
|
||||
mFramesDirectory = Path.Combine(mBaseDirectory, framesDirFragment);
|
||||
StringBuilder sb = new StringBuilder();
|
||||
_mFramesDirectory = Path.Combine(_mBaseDirectory, framesDirFragment);
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendLine("version=1");
|
||||
sb.AppendLine($"framesdir={framesDirFragment}");
|
||||
File.WriteAllText(mProjectFile, sb.ToString());
|
||||
File.WriteAllText(_mProjectFile, sb.ToString());
|
||||
}
|
||||
|
||||
public void CloseFile()
|
||||
|
@ -53,17 +54,17 @@ namespace BizHawk.Client.EmuHawk
|
|||
public void AddFrame(IVideoProvider source)
|
||||
{
|
||||
using var bb = new BitmapBuffer(source.BufferWidth, source.BufferHeight, source.GetVideoBuffer());
|
||||
string subPath = GetAndCreatePathForFrameNum(mCurrFrame);
|
||||
string subPath = GetAndCreatePathForFrameNum(_mCurrFrame);
|
||||
string path = $"{subPath}.png";
|
||||
bb.ToSysdrawingBitmap().Save(path, System.Drawing.Imaging.ImageFormat.Png);
|
||||
bb.ToSysdrawingBitmap().Save(path, ImageFormat.Png);
|
||||
}
|
||||
|
||||
public void AddSamples(short[] samples)
|
||||
{
|
||||
string subPath = GetAndCreatePathForFrameNum(mCurrFrame);
|
||||
string subPath = GetAndCreatePathForFrameNum(_mCurrFrame);
|
||||
string path = $"{subPath}.wav";
|
||||
WavWriterV wwv = new WavWriterV();
|
||||
wwv.SetAudioParameters(paramSampleRate, paramChannels, paramBits);
|
||||
var wwv = new WavWriterV();
|
||||
wwv.SetAudioParameters(_paramSampleRate, _paramChannels, _paramBits);
|
||||
wwv.OpenFile(path);
|
||||
wwv.AddSamples(samples);
|
||||
wwv.CloseFile();
|
||||
|
@ -86,7 +87,7 @@ namespace BizHawk.Client.EmuHawk
|
|||
return new DummyDisposable();
|
||||
}
|
||||
|
||||
public void SetMovieParameters(int fpsnum, int fpsden)
|
||||
public void SetMovieParameters(int fpsNum, int fpsDen)
|
||||
{
|
||||
//should probably todo in here
|
||||
}
|
||||
|
@ -96,24 +97,21 @@ namespace BizHawk.Client.EmuHawk
|
|||
// may want to todo
|
||||
}
|
||||
|
||||
private int paramSampleRate, paramChannels, paramBits;
|
||||
private int _paramSampleRate, _paramChannels, _paramBits;
|
||||
|
||||
public void SetAudioParameters(int sampleRate, int channels, int bits)
|
||||
{
|
||||
paramSampleRate = sampleRate;
|
||||
paramChannels = channels;
|
||||
paramBits = bits;
|
||||
_paramSampleRate = sampleRate;
|
||||
_paramChannels = channels;
|
||||
_paramBits = bits;
|
||||
}
|
||||
|
||||
public void SetMetaData(string gameName, string authors, ulong lengthMS, ulong rerecords)
|
||||
public void SetMetaData(string gameName, string authors, ulong lengthMs, ulong rerecords)
|
||||
{
|
||||
// not needed
|
||||
}
|
||||
|
||||
public string DesiredExtension()
|
||||
{
|
||||
return "syncless.txt";
|
||||
}
|
||||
public string DesiredExtension() => "syncless.txt";
|
||||
|
||||
/// <summary>
|
||||
/// splits the string into chunks of length s
|
||||
|
@ -145,18 +143,18 @@ namespace BizHawk.Client.EmuHawk
|
|||
private string GetAndCreatePathForFrameNum(int index)
|
||||
{
|
||||
string subPath = GetPathFragmentForFrameNum(index);
|
||||
string path = mFramesDirectory;
|
||||
string path = _mFramesDirectory;
|
||||
path = Path.Combine(path, subPath);
|
||||
string fpath = $"{path}.nothing";
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(fpath));
|
||||
string fPath = $"{path}.nothing";
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(fPath) ?? "");
|
||||
return path;
|
||||
}
|
||||
|
||||
public static string GetPathFragmentForFrameNum(int index)
|
||||
{
|
||||
var chunks = StringChunkSplit(index.ToString(), 2);
|
||||
string subpath = string.Join("/", chunks);
|
||||
return subpath;
|
||||
string subPath = string.Join("/", chunks);
|
||||
return subPath;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,14 +19,14 @@ namespace BizHawk.Client.EmuHawk
|
|||
private void GetPaths(int index, out string png, out string wav)
|
||||
{
|
||||
string subPath = SynclessRecorder.GetPathFragmentForFrameNum(index);
|
||||
string path = mFramesDirectory;
|
||||
string path = _mFramesDirectory;
|
||||
path = Path.Combine(path, subPath);
|
||||
png = $"{path}.png";
|
||||
wav = $"{path}.wav";
|
||||
}
|
||||
|
||||
private string mSynclessConfigFile;
|
||||
private string mFramesDirectory;
|
||||
private string _mSynclessConfigFile;
|
||||
private string _mFramesDirectory;
|
||||
|
||||
public void Run()
|
||||
{
|
||||
|
@ -41,12 +41,12 @@ namespace BizHawk.Client.EmuHawk
|
|||
return;
|
||||
}
|
||||
|
||||
mSynclessConfigFile = ofd.FileName;
|
||||
_mSynclessConfigFile = ofd.FileName;
|
||||
|
||||
//---- this is pretty crappy:
|
||||
var lines = File.ReadAllLines(mSynclessConfigFile);
|
||||
var lines = File.ReadAllLines(_mSynclessConfigFile);
|
||||
|
||||
string framesdir = "";
|
||||
string framesDir = "";
|
||||
foreach (var line in lines)
|
||||
{
|
||||
int idx = line.IndexOf('=');
|
||||
|
@ -54,11 +54,11 @@ namespace BizHawk.Client.EmuHawk
|
|||
string value = line.Substring(idx + 1, line.Length - (idx + 1));
|
||||
if (key == "framesdir")
|
||||
{
|
||||
framesdir = value;
|
||||
framesDir = value;
|
||||
}
|
||||
}
|
||||
|
||||
mFramesDirectory = Path.Combine(Path.GetDirectoryName(Path.GetFullPath(mSynclessConfigFile)), framesdir);
|
||||
_mFramesDirectory = Path.Combine(Path.GetDirectoryName(Path.GetFullPath(_mSynclessConfigFile)), framesDir);
|
||||
|
||||
// scan frames directory
|
||||
int frame = 1; // hacky! skip frame 0, because we have a problem with dumping that frame somehow
|
||||
|
@ -70,7 +70,7 @@ namespace BizHawk.Client.EmuHawk
|
|||
break;
|
||||
}
|
||||
|
||||
mFrameInfos.Add(new FrameInfo
|
||||
_mFrameInfos.Add(new FrameInfo
|
||||
{
|
||||
pngPath = png,
|
||||
wavPath = wav
|
||||
|
@ -82,7 +82,7 @@ namespace BizHawk.Client.EmuHawk
|
|||
ShowDialog();
|
||||
}
|
||||
|
||||
private readonly List<FrameInfo> mFrameInfos = new List<FrameInfo>();
|
||||
private readonly List<FrameInfo> _mFrameInfos = new List<FrameInfo>();
|
||||
|
||||
struct FrameInfo
|
||||
{
|
||||
|
@ -92,13 +92,13 @@ namespace BizHawk.Client.EmuHawk
|
|||
|
||||
private void btnExport_Click(object sender, EventArgs e)
|
||||
{
|
||||
if (mFrameInfos.Count == 0)
|
||||
if (_mFrameInfos.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int width, height;
|
||||
using(var bmp = new Bitmap(mFrameInfos[0].pngPath))
|
||||
using(var bmp = new Bitmap(_mFrameInfos[0].pngPath))
|
||||
{
|
||||
width = bmp.Width;
|
||||
height = bmp.Height;
|
||||
|
@ -106,7 +106,7 @@ namespace BizHawk.Client.EmuHawk
|
|||
|
||||
var sfd = new SaveFileDialog
|
||||
{
|
||||
FileName = Path.ChangeExtension(mSynclessConfigFile, ".avi")
|
||||
FileName = Path.ChangeExtension(_mSynclessConfigFile, ".avi")
|
||||
};
|
||||
sfd.InitialDirectory = Path.GetDirectoryName(sfd.FileName);
|
||||
if (sfd.ShowDialog() == DialogResult.Cancel)
|
||||
|
@ -121,7 +121,7 @@ namespace BizHawk.Client.EmuHawk
|
|||
var token = avw.AcquireVideoCodecToken(this);
|
||||
avw.SetVideoCodecToken(token);
|
||||
avw.OpenFile(sfd.FileName);
|
||||
foreach (var fi in mFrameInfos)
|
||||
foreach (var fi in _mFrameInfos)
|
||||
{
|
||||
using (var bb = new BitmapBuffer(fi.pngPath, new BitmapLoadOptions()))
|
||||
{
|
||||
|
@ -133,13 +133,13 @@ namespace BizHawk.Client.EmuHawk
|
|||
var wavBytes = File.ReadAllBytes(fi.wavPath);
|
||||
var ms = new MemoryStream(wavBytes) { Position = 44 };
|
||||
var br = new BinaryReader(ms);
|
||||
var sampledata = new List<short>();
|
||||
var sampleData = new List<short>();
|
||||
while (br.BaseStream.Position != br.BaseStream.Length)
|
||||
{
|
||||
sampledata.Add(br.ReadInt16());
|
||||
sampleData.Add(br.ReadInt16());
|
||||
}
|
||||
|
||||
avw.AddSamples(sampledata.ToArray());
|
||||
avw.AddSamples(sampleData.ToArray());
|
||||
}
|
||||
|
||||
avw.CloseFile();
|
||||
|
|
|
@ -62,7 +62,7 @@
|
|||
this.checkBoxResize.TabIndex = 9;
|
||||
this.checkBoxResize.Text = "Resize Video";
|
||||
this.checkBoxResize.UseVisualStyleBackColor = true;
|
||||
this.checkBoxResize.CheckedChanged += new System.EventHandler(this.checkBoxResize_CheckedChanged);
|
||||
this.checkBoxResize.CheckedChanged += new System.EventHandler(this.CheckBoxResize_CheckedChanged);
|
||||
//
|
||||
// listBox1
|
||||
//
|
||||
|
@ -73,7 +73,7 @@
|
|||
this.listBox1.Name = "listBox1";
|
||||
this.listBox1.Size = new System.Drawing.Size(329, 202);
|
||||
this.listBox1.TabIndex = 0;
|
||||
this.listBox1.SelectedIndexChanged += new System.EventHandler(this.listBox1_SelectedIndexChanged);
|
||||
this.listBox1.SelectedIndexChanged += new System.EventHandler(this.ListBox1_SelectedIndexChanged);
|
||||
//
|
||||
// buttonOK
|
||||
//
|
||||
|
@ -85,7 +85,7 @@
|
|||
this.buttonOK.TabIndex = 1;
|
||||
this.buttonOK.Text = "OK";
|
||||
this.buttonOK.UseVisualStyleBackColor = true;
|
||||
this.buttonOK.Click += new System.EventHandler(this.buttonOK_Click);
|
||||
this.buttonOK.Click += new System.EventHandler(this.ButtonOK_Click);
|
||||
//
|
||||
// buttonCancel
|
||||
//
|
||||
|
@ -161,7 +161,7 @@
|
|||
this.buttonAuto.TabIndex = 14;
|
||||
this.buttonAuto.Text = "/\\ Set As Resize";
|
||||
this.buttonAuto.UseVisualStyleBackColor = true;
|
||||
this.buttonAuto.Click += new System.EventHandler(this.buttonAuto_Click);
|
||||
this.buttonAuto.Click += new System.EventHandler(this.ButtonAuto_Click);
|
||||
//
|
||||
// panelSizeSelect
|
||||
//
|
||||
|
|
|
@ -118,14 +118,14 @@ namespace BizHawk.Client.EmuHawk
|
|||
return ret;
|
||||
}
|
||||
|
||||
private void listBox1_SelectedIndexChanged(object sender, EventArgs e)
|
||||
private void ListBox1_SelectedIndexChanged(object sender, EventArgs e)
|
||||
{
|
||||
labelDescriptionBody.Text = listBox1.SelectedIndex != -1
|
||||
? ((VideoWriterInfo)listBox1.SelectedItem).Attribs.Description
|
||||
: "";
|
||||
}
|
||||
|
||||
private void checkBoxResize_CheckedChanged(object sender, EventArgs e)
|
||||
private void CheckBoxResize_CheckedChanged(object sender, EventArgs e)
|
||||
{
|
||||
foreach (Control c in panelSizeSelect.Controls)
|
||||
{
|
||||
|
@ -133,13 +133,13 @@ namespace BizHawk.Client.EmuHawk
|
|||
}
|
||||
}
|
||||
|
||||
private void buttonAuto_Click(object sender, EventArgs e)
|
||||
private void ButtonAuto_Click(object sender, EventArgs e)
|
||||
{
|
||||
numericTextBoxW.Text = _captureWidth.ToString();
|
||||
numericTextBoxH.Text = _captureHeight.ToString();
|
||||
}
|
||||
|
||||
private void buttonOK_Click(object sender, EventArgs e)
|
||||
private void ButtonOK_Click(object sender, EventArgs e)
|
||||
{
|
||||
if (checkBoxResize.Checked)
|
||||
{
|
||||
|
|
|
@ -17,121 +17,125 @@ namespace BizHawk.Client.EmuHawk
|
|||
/// <summary>
|
||||
/// underlying file being written to
|
||||
/// </summary>
|
||||
BinaryWriter file;
|
||||
private BinaryWriter _file;
|
||||
|
||||
/// <summary>
|
||||
/// sequence of files to write to (split on 32 bit limit)
|
||||
/// </summary>
|
||||
IEnumerator<Stream> filechain;
|
||||
private IEnumerator<Stream> _fileChain;
|
||||
|
||||
/// <summary>
|
||||
/// samplerate in HZ
|
||||
/// </summary>
|
||||
int samplerate;
|
||||
private int _sampleRate;
|
||||
|
||||
/// <summary>
|
||||
/// number of audio channels
|
||||
/// </summary>
|
||||
int numchannels;
|
||||
private int _numChannels;
|
||||
|
||||
/// <summary>
|
||||
/// number of bytes of PCM data written to current file
|
||||
/// </summary>
|
||||
UInt64 numbytes;
|
||||
private ulong _numBytes;
|
||||
|
||||
/// <summary>
|
||||
/// number of bytes after which a file split should be made
|
||||
/// </summary>
|
||||
const UInt64 splitpoint = 2 * 1000 * 1000 * 1000;
|
||||
private const ulong SplitPoint = 2 * 1000 * 1000 * 1000;
|
||||
|
||||
/// <summary>
|
||||
/// write riff headers to current file
|
||||
/// </summary>
|
||||
private void writeheaders()
|
||||
private void WriteHeaders()
|
||||
{
|
||||
file.Write(Encoding.ASCII.GetBytes("RIFF")); // ChunkID
|
||||
file.Write((uint)0); // ChunkSize
|
||||
file.Write(Encoding.ASCII.GetBytes("WAVE")); // Format
|
||||
_file.Write(Encoding.ASCII.GetBytes("RIFF")); // ChunkID
|
||||
_file.Write((uint)0); // ChunkSize
|
||||
_file.Write(Encoding.ASCII.GetBytes("WAVE")); // Format
|
||||
|
||||
file.Write(Encoding.ASCII.GetBytes("fmt ")); // SubchunkID
|
||||
file.Write((uint)16); // SubchunkSize
|
||||
file.Write((ushort)1); // AudioFormat (PCM)
|
||||
file.Write((ushort)numchannels); // NumChannels
|
||||
file.Write((uint)samplerate); // SampleRate
|
||||
file.Write((uint)(samplerate * numchannels * 2)); // ByteRate
|
||||
file.Write((ushort)(numchannels * 2)); // BlockAlign
|
||||
file.Write((ushort)16); // BitsPerSample
|
||||
_file.Write(Encoding.ASCII.GetBytes("fmt ")); // SubchunkID
|
||||
_file.Write((uint)16); // SubchunkSize
|
||||
_file.Write((ushort)1); // AudioFormat (PCM)
|
||||
_file.Write((ushort)_numChannels); // NumChannels
|
||||
_file.Write((uint)_sampleRate); // SampleRate
|
||||
_file.Write((uint)(_sampleRate * _numChannels * 2)); // ByteRate
|
||||
_file.Write((ushort)(_numChannels * 2)); // BlockAlign
|
||||
_file.Write((ushort)16); // BitsPerSample
|
||||
|
||||
file.Write(Encoding.ASCII.GetBytes("data")); // SubchunkID
|
||||
file.Write((uint)0); // SubchunkSize
|
||||
_file.Write(Encoding.ASCII.GetBytes("data")); // SubchunkID
|
||||
_file.Write((uint)0); // SubchunkSize
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// seek back to beginning of file and fix header sizes (if possible)
|
||||
/// </summary>
|
||||
private void finalizeheaders()
|
||||
private void FinalizeHeaders()
|
||||
{
|
||||
if (numbytes + 36 >= 0x100000000)
|
||||
if (_numBytes + 36 >= 0x100000000)
|
||||
{
|
||||
// passed 4G limit, nothing to be done
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
file.Seek(4, SeekOrigin.Begin);
|
||||
file.Write((uint)(36 + numbytes));
|
||||
file.Seek(40, SeekOrigin.Begin);
|
||||
file.Write((uint)(numbytes));
|
||||
_file.Seek(4, SeekOrigin.Begin);
|
||||
_file.Write((uint)(36 + _numBytes));
|
||||
_file.Seek(40, SeekOrigin.Begin);
|
||||
_file.Write((uint)(_numBytes));
|
||||
}
|
||||
catch (NotSupportedException)
|
||||
{ // unseekable; oh well
|
||||
{
|
||||
// unseekable; oh well
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// close current underlying stream
|
||||
/// </summary>
|
||||
private void closecurrent()
|
||||
private void CloseCurrent()
|
||||
{
|
||||
if (file != null)
|
||||
if (_file != null)
|
||||
{
|
||||
finalizeheaders();
|
||||
file.Close();
|
||||
file.Dispose();
|
||||
FinalizeHeaders();
|
||||
_file.Close();
|
||||
_file.Dispose();
|
||||
}
|
||||
|
||||
file = null;
|
||||
_file = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// open a new underlying stream
|
||||
/// </summary>
|
||||
private void opencurrent(Stream next)
|
||||
private void OpenCurrent(Stream next)
|
||||
{
|
||||
file = new BinaryWriter(next, Encoding.ASCII);
|
||||
numbytes = 0;
|
||||
writeheaders();
|
||||
_file = new BinaryWriter(next, Encoding.ASCII);
|
||||
_numBytes = 0;
|
||||
WriteHeaders();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// write samples to file
|
||||
/// </summary>
|
||||
/// <param name="samples">samples to write; should contain one for each channel</param>
|
||||
public void writesamples(short[] samples)
|
||||
public void WriteSamples(short[] samples)
|
||||
{
|
||||
file.Write(samples);
|
||||
numbytes += (ulong)(samples.Length * sizeof(short));
|
||||
_file.Write(samples);
|
||||
_numBytes += (ulong)(samples.Length * sizeof(short));
|
||||
|
||||
// try splitting if we can
|
||||
if (numbytes >= splitpoint && filechain != null)
|
||||
if (_numBytes >= SplitPoint && _fileChain != null)
|
||||
{
|
||||
if (!filechain.MoveNext())
|
||||
if (!_fileChain.MoveNext())
|
||||
{ // out of files, just keep on writing to this one
|
||||
filechain = null;
|
||||
_fileChain = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
Stream next = filechain.Current;
|
||||
closecurrent();
|
||||
opencurrent(next);
|
||||
Stream next = _fileChain.Current;
|
||||
CloseCurrent();
|
||||
OpenCurrent(next);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -146,15 +150,15 @@ namespace BizHawk.Client.EmuHawk
|
|||
/// </summary>
|
||||
public void Close()
|
||||
{
|
||||
closecurrent();
|
||||
CloseCurrent();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// checks sampling rate, number of channels for validity
|
||||
/// </summary>
|
||||
private void checkargs()
|
||||
private void CheckArgs()
|
||||
{
|
||||
if (samplerate < 1 || numchannels < 1)
|
||||
if (_sampleRate < 1 || _numChannels < 1)
|
||||
{
|
||||
throw new ArgumentException("Bad samplerate/numchannels");
|
||||
}
|
||||
|
@ -165,15 +169,15 @@ namespace BizHawk.Client.EmuHawk
|
|||
/// no attempt is made to split
|
||||
/// </summary>
|
||||
/// <param name="s">WavWriter now owns this stream</param>
|
||||
/// <param name="samplerate">sampling rate in HZ</param>
|
||||
/// <param name="numchannels">number of audio channels</param>
|
||||
public WavWriter(Stream s, int samplerate, int numchannels)
|
||||
/// <param name="sampleRate">sampling rate in HZ</param>
|
||||
/// <param name="numChannels">number of audio channels</param>
|
||||
public WavWriter(Stream s, int sampleRate, int numChannels)
|
||||
{
|
||||
this.samplerate = samplerate;
|
||||
this.numchannels = numchannels;
|
||||
filechain = null;
|
||||
checkargs();
|
||||
opencurrent(s);
|
||||
_sampleRate = sampleRate;
|
||||
_numChannels = numChannels;
|
||||
_fileChain = null;
|
||||
CheckArgs();
|
||||
OpenCurrent(s);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -182,23 +186,23 @@ namespace BizHawk.Client.EmuHawk
|
|||
/// if the enumerator runs out before the audio stream does, the last file could be >2G
|
||||
/// </summary>
|
||||
/// <param name="ss">WavWriter now owns any of these streams that it enumerates</param>
|
||||
/// <param name="samplerate">sampling rate in HZ</param>
|
||||
/// <param name="numchannels">number of audio channels</param>
|
||||
/// <param name="sampleRate">sampling rate in HZ</param>
|
||||
/// <param name="numChannels">number of audio channels</param>
|
||||
/// <exception cref="ArgumentException"><paramref name="ss"/> cannot be progressed</exception>
|
||||
public WavWriter(IEnumerator<Stream> ss, int samplerate, int numchannels)
|
||||
public WavWriter(IEnumerator<Stream> ss, int sampleRate, int numChannels)
|
||||
{
|
||||
this.samplerate = samplerate;
|
||||
this.numchannels = numchannels;
|
||||
checkargs();
|
||||
filechain = ss;
|
||||
_sampleRate = sampleRate;
|
||||
_numChannels = numChannels;
|
||||
CheckArgs();
|
||||
_fileChain = ss;
|
||||
|
||||
// advance to first
|
||||
if (!filechain.MoveNext())
|
||||
if (!_fileChain.MoveNext())
|
||||
{
|
||||
throw new ArgumentException("Iterator was empty!");
|
||||
}
|
||||
|
||||
opencurrent(ss.Current);
|
||||
OpenCurrent(ss.Current);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -210,7 +214,7 @@ namespace BizHawk.Client.EmuHawk
|
|||
{
|
||||
public void SetVideoCodecToken(IDisposable token) { }
|
||||
public void AddFrame(IVideoProvider source) { }
|
||||
public void SetMovieParameters(int fpsnum, int fpsden) { }
|
||||
public void SetMovieParameters(int fpsNum, int fpsDen) { }
|
||||
public void SetVideoParameters(int width, int height) { }
|
||||
public void SetFrame(int frame) { }
|
||||
|
||||
|
@ -232,35 +236,35 @@ namespace BizHawk.Client.EmuHawk
|
|||
/// <exception cref="ArgumentOutOfRangeException"><paramref name="bits"/> is not <c>16</c></exception>
|
||||
public void SetAudioParameters(int sampleRate, int channels, int bits)
|
||||
{
|
||||
this.sampleRate = sampleRate;
|
||||
this.channels = channels;
|
||||
this._sampleRate = sampleRate;
|
||||
this._channels = channels;
|
||||
if (bits != 16)
|
||||
{
|
||||
throw new ArgumentException("Only support 16bit audio!");
|
||||
}
|
||||
}
|
||||
|
||||
public void SetMetaData(string gameName, string authors, ulong lengthMS, ulong rerecords)
|
||||
public void SetMetaData(string gameName, string authors, ulong lengthMs, ulong rerecords)
|
||||
{
|
||||
// not implemented
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
wavwriter?.Dispose();
|
||||
_wavWriter?.Dispose();
|
||||
}
|
||||
|
||||
private WavWriter wavwriter = null;
|
||||
private int sampleRate = 0;
|
||||
private int channels = 0;
|
||||
private WavWriter _wavWriter;
|
||||
private int _sampleRate;
|
||||
private int _channels;
|
||||
|
||||
/// <summary>
|
||||
/// create a simple wav stream iterator
|
||||
/// </summary>
|
||||
private static IEnumerator<Stream> CreateStreamIterator(string template)
|
||||
{
|
||||
string dir = Path.GetDirectoryName(template);
|
||||
string baseName = Path.GetFileNameWithoutExtension(template);
|
||||
string dir = Path.GetDirectoryName(template) ?? "";
|
||||
string baseName = Path.GetFileNameWithoutExtension(template) ?? "";
|
||||
string ext = Path.GetExtension(template);
|
||||
yield return new FileStream(template, FileMode.Create);
|
||||
int counter = 1;
|
||||
|
@ -273,25 +277,22 @@ namespace BizHawk.Client.EmuHawk
|
|||
|
||||
public void OpenFile(string baseName)
|
||||
{
|
||||
wavwriter = new WavWriter(CreateStreamIterator(baseName), sampleRate, channels);
|
||||
_wavWriter = new WavWriter(CreateStreamIterator(baseName), _sampleRate, _channels);
|
||||
}
|
||||
|
||||
public void CloseFile()
|
||||
{
|
||||
wavwriter.Close();
|
||||
wavwriter.Dispose();
|
||||
wavwriter = null;
|
||||
_wavWriter.Close();
|
||||
_wavWriter.Dispose();
|
||||
_wavWriter = null;
|
||||
}
|
||||
|
||||
public void AddSamples(short[] samples)
|
||||
{
|
||||
wavwriter.writesamples(samples);
|
||||
_wavWriter.WriteSamples(samples);
|
||||
}
|
||||
|
||||
public string DesiredExtension()
|
||||
{
|
||||
return "wav";
|
||||
}
|
||||
public string DesiredExtension() => "wav";
|
||||
|
||||
public void SetDefaultVideoCodecToken()
|
||||
{
|
||||
|
|
|
@ -184,6 +184,7 @@
|
|||
<s:Boolean x:Key="/Default/UserDictionary/Words/=amstrad/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Arkanoid/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=atten/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Attribs/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Autofire/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=autoflushing/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=autohold/@EntryIndexedValue">True</s:Boolean>
|
||||
|
@ -191,6 +192,7 @@
|
|||
<s:Boolean x:Key="/Default/UserDictionary/Words/=automagically/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=autorestore/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Autosave/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=avisynth/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=backbuffer/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=backcolor/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Bezier/@EntryIndexedValue">True</s:Boolean>
|
||||
|
@ -204,7 +206,9 @@
|
|||
<s:Boolean x:Key="/Default/UserDictionary/Words/=bsnes/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=btns/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Bundler/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=bytestream/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Byteswap/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=checksums/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=chromeless/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Clicky/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Coalescer/@EntryIndexedValue">True</s:Boolean>
|
||||
|
@ -230,6 +234,7 @@
|
|||
<s:Boolean x:Key="/Default/UserDictionary/Words/=disassembly/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=disp/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Dontfire/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Dupped/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Ejin/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Endian/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=endianess/@EntryIndexedValue">True</s:Boolean>
|
||||
|
@ -239,6 +244,7 @@
|
|||
<s:Boolean x:Key="/Default/UserDictionary/Words/=FCEU/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=ffmpeg/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=filenames/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=filesize/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Finetuned/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=fname/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=frameadvance/@EntryIndexedValue">True</s:Boolean>
|
||||
|
@ -297,11 +303,13 @@
|
|||
<s:Boolean x:Key="/Default/UserDictionary/Words/=mmsys/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=MOTW/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Multidisk/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=multidump/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=multilines/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=multiplayer/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Multitap/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Multitrack/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Mupen/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=muxed/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Nametable/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Nametables/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=neshawk/@EntryIndexedValue">True</s:Boolean>
|
||||
|
@ -338,6 +346,7 @@
|
|||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Rewinder/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Roms/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=samp/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=samplerate/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Saturnus/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=saveram/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=savestate/@EntryIndexedValue">True</s:Boolean>
|
||||
|
@ -352,14 +361,18 @@
|
|||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Spriteback/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Sram/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=sSeeki/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=starttag/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Statable/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Stateable/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=streamtools/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=subdirectory/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Subfile/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=subfilename/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=subframe/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Subshell/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=sums_000D_000A_0009_0009_0009/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Syncless/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=syncpoint/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=SYSCOMMAND/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=taseditor/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=tasproj/@EntryIndexedValue">True</s:Boolean>
|
||||
|
@ -382,6 +395,7 @@
|
|||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Unpausing/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=unpress/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Unregister/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=unseekable/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Unthrottle/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Unthrottled/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Untransform/@EntryIndexedValue">True</s:Boolean>
|
||||
|
|
Loading…
Reference in New Issue