cleanups in AV code

This commit is contained in:
adelikat 2020-01-03 18:25:46 -06:00
parent b875f46a95
commit 101a403420
19 changed files with 856 additions and 929 deletions

View File

@ -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();
}
}
}

View File

@ -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)
{

View File

@ -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; }
}
}

View File

@ -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)

View File

@ -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();
}

View File

@ -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;
}
}
}

View File

@ -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()
{

View File

@ -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";

View File

@ -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();
}
}
}

View File

@ -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) { }

View File

@ -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; }

View File

@ -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;
}
}
}

View File

@ -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()
{

View File

@ -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;
}
}
}

View File

@ -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();

View File

@ -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
//

View File

@ -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)
{

View File

@ -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()
{

View File

@ -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>