misc code cleanups in AV code

This commit is contained in:
adelikat 2017-04-18 12:27:44 -05:00
parent 88348f03fa
commit 912a2d7346
22 changed files with 794 additions and 568 deletions

View File

@ -1,7 +1,4 @@
using System; using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using BizHawk.Emulation.Common; using BizHawk.Emulation.Common;
@ -64,7 +61,9 @@ namespace BizHawk.Client.EmuHawk
private void VerifyParams() private void VerifyParams()
{ {
if (!aset || !vset) if (!aset || !vset)
{
throw new InvalidOperationException("Must set params first!"); throw new InvalidOperationException("Must set params first!");
}
if (!pset) if (!pset)
{ {
@ -93,7 +92,6 @@ namespace BizHawk.Client.EmuHawk
// todo: scan for duplicate frames (ie, video content exactly matches previous frame) and for them, skip the threshone step // 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? // this is a good idea, but expensive on time. is it worth it?
if (exaudio_num >= threshone) if (exaudio_num >= threshone)
{ {
// add frame once // add frame once
@ -121,7 +119,9 @@ namespace BizHawk.Client.EmuHawk
else 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)); Buffer.BlockCopy(samples, 0, _samples, 0, samplesprovided * channels * sizeof(short));
w.AddSamples(_samples); w.AddSamples(_samples);
@ -144,7 +144,10 @@ namespace BizHawk.Client.EmuHawk
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(); throw new InvalidOperationException();
}
vset = true; vset = true;
this.fpsnum = fpsnum; this.fpsnum = fpsnum;
this.fpsden = fpsden; this.fpsden = fpsden;
@ -155,9 +158,15 @@ namespace BizHawk.Client.EmuHawk
public new virtual void SetAudioParameters(int sampleRate, int channels, int bits) public new virtual void SetAudioParameters(int sampleRate, int channels, int bits)
{ {
if (aset) if (aset)
{
throw new InvalidOperationException(); throw new InvalidOperationException();
}
if (bits != 16) if (bits != 16)
{
throw new InvalidOperationException("Only 16 bit audio is supported!"); throw new InvalidOperationException("Only 16 bit audio is supported!");
}
aset = true; aset = true;
this.samplerate = sampleRate; this.samplerate = sampleRate;
this.channels = channels; this.channels = channels;
@ -187,8 +196,9 @@ namespace BizHawk.Client.EmuHawk
{ {
protected IVideoWriter w; protected IVideoWriter w;
public bool UsesAudio { get { return w.UsesAudio; } } public bool UsesAudio => w.UsesAudio;
public bool UsesVideo { get { return w.UsesVideo; } }
public bool UsesVideo => w.UsesVideo;
public void SetVideoCodecToken(IDisposable token) public void SetVideoCodecToken(IDisposable token)
{ {

View File

@ -2,30 +2,31 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Windows.Forms;
using BizHawk.Client.Common; using BizHawk.Client.Common;
using BizHawk.Emulation.Common; using BizHawk.Emulation.Common;
//some helpful p/invoke from http://www.codeproject.com/KB/audio-video/Motion_Detection.aspx?msg=1142967 // some helpful p/invoke from http://www.codeproject.com/KB/audio-video/Motion_Detection.aspx?msg=1142967
namespace BizHawk.Client.EmuHawk namespace BizHawk.Client.EmuHawk
{ {
[VideoWriter("vfwavi", "AVI writer", [VideoWriter("vfwavi", "AVI writer",
"Uses the Microsoft AVIFIL32 system to write .avi files. Audio is uncompressed; Video can be compressed with any installed VCM codec. Splits on 2G and resolution change.")] "Uses the Microsoft AVIFIL32 system to write .avi files. Audio is uncompressed; Video can be compressed with any installed VCM codec. Splits on 2G and resolution change.")]
class AviWriter : IVideoWriter internal class AviWriter : IVideoWriter
{ {
CodecToken currVideoCodecToken = null; private CodecToken _currVideoCodecToken = null;
AviWriterSegment currSegment; private AviWriterSegment _currSegment;
IEnumerator<string> nameProvider; private IEnumerator<string> _nameProvider;
public void SetFrame(int frame) { } public void SetFrame(int frame)
{
}
bool IsOpen { get { return nameProvider != null; } } private bool IsOpen => _nameProvider != null;
public void Dispose() public void Dispose()
{ {
if (currSegment != null) _currSegment?.Dispose();
currSegment.Dispose();
} }
/// <summary> /// <summary>
@ -34,9 +35,13 @@ namespace BizHawk.Client.EmuHawk
public void SetVideoCodecToken(IDisposable token) public void SetVideoCodecToken(IDisposable token)
{ {
if (token is CodecToken) if (token is CodecToken)
currVideoCodecToken = (CodecToken)token; {
_currVideoCodecToken = (CodecToken)token;
}
else else
{
throw new ArgumentException("AviWriter only takes its own Codec Tokens!"); throw new ArgumentException("AviWriter only takes its own Codec Tokens!");
}
} }
public static IEnumerator<string> CreateBasicNameProvider(string template) public static IEnumerator<string> CreateBasicNameProvider(string template)
@ -46,7 +51,7 @@ namespace BizHawk.Client.EmuHawk
string ext = Path.GetExtension(template); string ext = Path.GetExtension(template);
yield return template; yield return template;
int counter = 1; int counter = 1;
for (; ; ) for (;;)
{ {
yield return Path.Combine(dir, baseName) + "_" + counter + ext; yield return Path.Combine(dir, baseName) + "_" + counter + ext;
counter++; counter++;
@ -57,60 +62,69 @@ namespace BizHawk.Client.EmuHawk
/// opens an avi file for recording with names based on the supplied template. /// opens an avi file for recording with names based on the supplied template.
/// set a video codec token first. /// set a video codec token first.
/// </summary> /// </summary>
public void OpenFile(string baseName) { OpenFile(CreateBasicNameProvider(baseName)); } public void OpenFile(string baseName)
{
OpenFile(CreateBasicNameProvider(baseName));
}
// thread communication // thread communication
// synchronized queue with custom messages // synchronized queue with custom messages
// it seems like there are 99999 ways to do everything in C#, so i'm sure this is not the best // it seems like there are 99999 ways to do everything in C#, so i'm sure this is not the best
System.Collections.Concurrent.BlockingCollection<Object> threadQ; System.Collections.Concurrent.BlockingCollection<object> threadQ;
System.Threading.Thread workerT; System.Threading.Thread workerT;
void threadproc() private void threadproc()
{ {
try try
{ {
while (true) while (true)
{ {
Object o = threadQ.Take(); object o = threadQ.Take();
if (o is IVideoProvider) if (o is IVideoProvider)
{
AddFrameEx((IVideoProvider)o); AddFrameEx((IVideoProvider)o);
}
else if (o is short[]) else if (o is short[])
{
AddSamplesEx((short[])o); AddSamplesEx((short[])o);
}
else else
{
// anything else is assumed to be quit time // anything else is assumed to be quit time
return; return;
}
} }
} }
catch (Exception e) catch (Exception e)
{ {
System.Windows.Forms.MessageBox.Show("AVIFIL32 Thread died:\n\n" + e); MessageBox.Show("AVIFIL32 Thread died:\n\n" + e);
return;
} }
} }
// we can't pass the IVideoProvider we get to another thread, because it doesn't actually keep a local copy of its data, // we can't pass the IVideoProvider we get to another thread, because it doesn't actually keep a local copy of its data,
// instead grabbing it from the emu as needed. this causes frame loss/dupping as a race condition // instead grabbing it from the emu as needed. this causes frame loss/dupping as a race condition
// instead we pass this // instead we pass this
class VideoCopy : IVideoProvider private class VideoCopy : IVideoProvider
{ {
int[] vb; private readonly int[] _vb;
public int VirtualWidth { get; private set; } public int VirtualWidth { get; }
public int VirtualHeight { get; private set; } public int VirtualHeight { get; }
public int BufferWidth { get; private set; } public int BufferWidth { get; }
public int BufferHeight { get; private set; } public int BufferHeight { get; }
public int BackgroundColor { get; private set; } public int BackgroundColor { get; }
public VideoCopy(IVideoProvider c) public VideoCopy(IVideoProvider c)
{ {
vb = (int[])c.GetVideoBuffer().Clone(); _vb = (int[])c.GetVideoBuffer().Clone();
BufferWidth = c.BufferWidth; BufferWidth = c.BufferWidth;
BufferHeight= c.BufferHeight; BufferHeight = c.BufferHeight;
BackgroundColor = c.BackgroundColor; BackgroundColor = c.BackgroundColor;
VirtualWidth = c.VirtualWidth; VirtualWidth = c.VirtualWidth;
VirtualHeight = c.VirtualHeight; VirtualHeight = c.VirtualHeight;
} }
public int[] GetVideoBuffer() public int[] GetVideoBuffer()
{ {
return vb; return _vb;
} }
} }
@ -122,9 +136,11 @@ namespace BizHawk.Client.EmuHawk
/// <param name="nameProvider"></param> /// <param name="nameProvider"></param>
public void OpenFile(IEnumerator<string> nameProvider) public void OpenFile(IEnumerator<string> nameProvider)
{ {
this.nameProvider = nameProvider; _nameProvider = nameProvider;
if (currVideoCodecToken == null) if (_currVideoCodecToken == null)
{
throw new InvalidOperationException("Tried to start recording an AVI with no video codec token set"); throw new InvalidOperationException("Tried to start recording an AVI with no video codec token set");
}
threadQ = new System.Collections.Concurrent.BlockingCollection<Object>(30); threadQ = new System.Collections.Concurrent.BlockingCollection<Object>(30);
workerT = new System.Threading.Thread(new System.Threading.ThreadStart(threadproc)); workerT = new System.Threading.Thread(new System.Threading.ThreadStart(threadproc));
@ -133,11 +149,10 @@ namespace BizHawk.Client.EmuHawk
public void CloseFile() public void CloseFile()
{ {
threadQ.Add(new Object()); // acts as stop message threadQ.Add(new object()); // acts as stop message
workerT.Join(); workerT.Join();
if (currSegment != null) _currSegment?.Dispose();
currSegment.Dispose(); _currSegment = null;
currSegment = null;
} }
public void AddFrame(IVideoProvider source) public void AddFrame(IVideoProvider source)
@ -145,15 +160,22 @@ namespace BizHawk.Client.EmuHawk
while (!threadQ.TryAdd(new VideoCopy(source), 1000)) while (!threadQ.TryAdd(new VideoCopy(source), 1000))
{ {
if (!workerT.IsAlive) if (!workerT.IsAlive)
{
throw new Exception("AVI Worker thread died!"); throw new Exception("AVI Worker thread died!");
}
} }
} }
void AddFrameEx(IVideoProvider source)
private void AddFrameEx(IVideoProvider source)
{ {
SetVideoParameters(source.BufferWidth, source.BufferHeight); SetVideoParameters(source.BufferWidth, source.BufferHeight);
ConsiderLengthSegment(); ConsiderLengthSegment();
if (currSegment == null) Segment(); if (_currSegment == null)
currSegment.AddFrame(source); {
Segment();
}
_currSegment.AddFrame(source);
} }
public void AddSamples(short[] samples) public void AddSamples(short[] samples)
@ -163,78 +185,102 @@ namespace BizHawk.Client.EmuHawk
while (!threadQ.TryAdd((short[])samples.Clone(), 1000)) while (!threadQ.TryAdd((short[])samples.Clone(), 1000))
{ {
if (!workerT.IsAlive) if (!workerT.IsAlive)
{
throw new Exception("AVI Worker thread died!"); throw new Exception("AVI Worker thread died!");
}
} }
} }
void AddSamplesEx(short[] samples) private void AddSamplesEx(short[] samples)
{ {
ConsiderLengthSegment(); ConsiderLengthSegment();
if (currSegment == null) Segment(); if (_currSegment == null)
currSegment.AddSamples(samples); {
Segment();
}
_currSegment.AddSamples(samples);
} }
void ConsiderLengthSegment() private void ConsiderLengthSegment()
{ {
if (currSegment == null) return; if (_currSegment == null)
long len = currSegment.GetLengthApproximation(); {
const long segment_length_limit = 2 * 1000 * 1000 * 1000; //2GB return;
//const long segment_length_limit = 10 * 1000 * 1000; //for testing }
if (len > segment_length_limit) Segment();
long len = _currSegment.GetLengthApproximation();
const long segment_length_limit = 2 * 1000 * 1000 * 1000; // 2GB
// const long segment_length_limit = 10 * 1000 * 1000; //for testing
if (len > segment_length_limit)
{
Segment();
}
} }
void StartRecording() private void StartRecording()
{ {
//i guess theres nothing to do here // i guess theres nothing to do here
} }
void Segment() private void Segment()
{ {
if (!IsOpen) return; if (!IsOpen)
{
return;
}
if (currSegment == null) if (_currSegment == null)
{
StartRecording(); StartRecording();
}
else else
currSegment.Dispose(); {
currSegment = new AviWriterSegment(); _currSegment.Dispose();
nameProvider.MoveNext(); }
currSegment.OpenFile(nameProvider.Current, parameters, currVideoCodecToken);
_currSegment = new AviWriterSegment();
_nameProvider.MoveNext();
_currSegment.OpenFile(_nameProvider.Current, parameters, _currVideoCodecToken);
try try
{ {
currSegment.OpenStreams(); _currSegment.OpenStreams();
} }
catch // will automatically try again with 32 bit catch // will automatically try again with 32 bit
{ {
currSegment.OpenStreams(); _currSegment.OpenStreams();
} }
} }
/// <summary> /// <summary>
/// Acquires a video codec configuration from the user. you may save it for future use, but you must dispose of it when youre done with it. /// Acquires a video codec configuration from the user. you may save it for future use, but you must dispose of it when you're done with it.
/// returns null if the user canceled the dialog /// returns null if the user canceled the dialog
/// </summary> /// </summary>
public IDisposable AcquireVideoCodecToken(System.Windows.Forms.IWin32Window hwnd) //, CodecToken lastToken) public IDisposable AcquireVideoCodecToken(IWin32Window hwnd)
{ {
var temp_params = new Parameters(); var tempParams = new Parameters
temp_params.height = 256; {
temp_params.width = 256; height = 256,
temp_params.fps = 60; width = 256,
temp_params.fps_scale = 1; fps = 60,
temp_params.a_bits = 16; fps_scale = 1,
temp_params.a_samplerate = 44100; a_bits = 16,
temp_params.a_channels = 2; a_samplerate = 44100,
a_channels = 2
};
var temp = new AviWriterSegment(); var temp = new AviWriterSegment();
string tempfile = Path.GetTempFileName(); string tempfile = Path.GetTempFileName();
File.Delete(tempfile); File.Delete(tempfile);
tempfile = Path.ChangeExtension(tempfile, "avi"); tempfile = Path.ChangeExtension(tempfile, "avi");
temp.OpenFile(tempfile, temp_params, null); //lastToken); temp.OpenFile(tempfile, tempParams, null);
CodecToken token = (CodecToken)temp.AcquireVideoCodecToken(hwnd.Handle, currVideoCodecToken); CodecToken token = (CodecToken)temp.AcquireVideoCodecToken(hwnd.Handle, _currVideoCodecToken);
temp.CloseFile(); temp.CloseFile();
File.Delete(tempfile); File.Delete(tempfile);
return token; return token;
} }
class Parameters private class Parameters
{ {
public int width, height; public int width, height;
public int pitch; //in bytes public int pitch; //in bytes
@ -245,10 +291,11 @@ namespace BizHawk.Client.EmuHawk
bmih.biPlanes = 1; bmih.biPlanes = 1;
bmih.biBitCount = 24; bmih.biBitCount = 24;
bmih.biHeight = height; bmih.biHeight = height;
//pad up width so that we end up with multiple of 4 bytes
// pad up width so that we end up with multiple of 4 bytes
pitch = width * 3; pitch = width * 3;
pitch = (pitch + 3) & ~3; pitch = (pitch + 3) & ~3;
pitch_add = pitch - width * 3; pitch_add = pitch - (width * 3);
bmih.biWidth = width; bmih.biWidth = width;
bmih.biSizeImage = (uint)(pitch * height); bmih.biSizeImage = (uint)(pitch * height);
} }
@ -287,7 +334,8 @@ namespace BizHawk.Client.EmuHawk
public int fps, fps_scale; public int fps, fps_scale;
} }
Parameters parameters = new Parameters();
private readonly Parameters parameters = new Parameters();
/// <summary> /// <summary>
@ -303,7 +351,10 @@ namespace BizHawk.Client.EmuHawk
change |= parameters.fps_scale != fps_scale; change |= parameters.fps_scale != fps_scale;
parameters.fps_scale = fps_scale; parameters.fps_scale = fps_scale;
if (change) Segment(); if (change)
{
Segment();
}
} }
/// <summary> /// <summary>
@ -319,7 +370,10 @@ namespace BizHawk.Client.EmuHawk
change |= parameters.height != height; change |= parameters.height != height;
parameters.height = height; parameters.height = height;
if (change) Segment(); if (change)
{
Segment();
}
} }
/// <summary> /// <summary>
@ -341,7 +395,10 @@ namespace BizHawk.Client.EmuHawk
change |= parameters.has_audio != true; change |= parameters.has_audio != true;
parameters.has_audio = true; parameters.has_audio = true;
if (change) Segment(); if (change)
{
Segment();
}
} }
public class CodecToken : IDisposable public class CodecToken : IDisposable
@ -355,28 +412,41 @@ namespace BizHawk.Client.EmuHawk
public static CodecToken CreateFromAVICOMPRESSOPTIONS(ref Win32.AVICOMPRESSOPTIONS opts) public static CodecToken CreateFromAVICOMPRESSOPTIONS(ref Win32.AVICOMPRESSOPTIONS opts)
{ {
CodecToken ret = new CodecToken(); var ret = new CodecToken
ret.comprOptions = opts; {
ret.codec = Win32.decode_mmioFOURCC(opts.fccHandler); comprOptions = opts,
ret.Format = new byte[opts.cbFormat]; codec = Win32.decode_mmioFOURCC(opts.fccHandler),
ret.Parms = new byte[opts.cbParms]; Format = new byte[opts.cbFormat],
if (opts.lpFormat != IntPtr.Zero) Marshal.Copy(opts.lpFormat, ret.Format, 0, opts.cbFormat); Parms = new byte[opts.cbParms]
if (opts.lpParms != IntPtr.Zero) Marshal.Copy(opts.lpParms, ret.Parms, 0, opts.cbParms); };
if (opts.lpFormat != IntPtr.Zero)
{
Marshal.Copy(opts.lpFormat, ret.Format, 0, opts.cbFormat);
}
if (opts.lpParms != IntPtr.Zero)
{
Marshal.Copy(opts.lpParms, ret.Parms, 0, opts.cbParms);
}
return ret; return ret;
} }
[DllImport("kernel32.dll", SetLastError = true)] [DllImport("kernel32.dll", SetLastError = true)]
public static extern bool HeapFree(IntPtr hHeap, uint dwFlags, IntPtr lpMem); public static extern bool HeapFree(IntPtr hHeap, uint dwFlags, IntPtr lpMem);
[DllImport("kernel32.dll", SetLastError = true)] [DllImport("kernel32.dll", SetLastError = true)]
public static extern IntPtr GetProcessHeap(); public static extern IntPtr GetProcessHeap();
[DllImport("kernel32.dll", SetLastError = false)] [DllImport("kernel32.dll", SetLastError = false)]
public static extern IntPtr HeapAlloc(IntPtr hHeap, uint dwFlags, int dwBytes); public static extern IntPtr HeapAlloc(IntPtr hHeap, uint dwFlags, int dwBytes);
public static void DeallocateAVICOMPRESSOPTIONS(ref Win32.AVICOMPRESSOPTIONS opts) public static void DeallocateAVICOMPRESSOPTIONS(ref Win32.AVICOMPRESSOPTIONS opts)
{ {
//test: increase stability by never freeing anything, ever // test: increase stability by never freeing anything, ever
//if (opts.lpParms != IntPtr.Zero) CodecToken.HeapFree(CodecToken.GetProcessHeap(), 0, opts.lpParms); // if (opts.lpParms != IntPtr.Zero) CodecToken.HeapFree(CodecToken.GetProcessHeap(), 0, opts.lpParms);
//if (opts.lpFormat != IntPtr.Zero) CodecToken.HeapFree(CodecToken.GetProcessHeap(), 0, opts.lpFormat); // if (opts.lpFormat != IntPtr.Zero) CodecToken.HeapFree(CodecToken.GetProcessHeap(), 0, opts.lpFormat);
opts.lpParms = IntPtr.Zero; opts.lpParms = IntPtr.Zero;
opts.lpFormat = IntPtr.Zero; opts.lpFormat = IntPtr.Zero;
} }
@ -396,7 +466,7 @@ namespace BizHawk.Client.EmuHawk
} }
} }
byte[] SerializeToByteArray() private byte[] SerializeToByteArray()
{ {
var m = new MemoryStream(); var m = new MemoryStream();
var b = new BinaryWriter(m); var b = new BinaryWriter(m);
@ -418,7 +488,7 @@ namespace BizHawk.Client.EmuHawk
return m.ToArray(); return m.ToArray();
} }
static CodecToken DeSerializeFromByteArray(byte[] data) private static CodecToken DeSerializeFromByteArray(byte[] data)
{ {
var m = new MemoryStream(data, false); var m = new MemoryStream(data, false);
var b = new BinaryReader(m); var b = new BinaryReader(m);
@ -456,11 +526,13 @@ namespace BizHawk.Client.EmuHawk
b.Close(); b.Close();
} }
CodecToken ret = new CodecToken(); var ret = new CodecToken
ret.comprOptions = comprOptions; {
ret.Format = Format; comprOptions = comprOptions,
ret.Parms = Parms; Format = Format,
ret.codec = Win32.decode_mmioFOURCC(comprOptions.fccHandler); Parms = Parms,
codec = Win32.decode_mmioFOURCC(comprOptions.fccHandler)
};
return ret; return ret;
} }
@ -499,31 +571,36 @@ namespace BizHawk.Client.EmuHawk
CloseFile(); CloseFile();
} }
CodecToken currVideoCodecToken = null; private CodecToken currVideoCodecToken = null;
bool IsOpen; private bool IsOpen;
IntPtr pAviFile, pAviRawVideoStream, pAviRawAudioStream, pAviCompressedVideoStream; private IntPtr pAviFile, pAviRawVideoStream, pAviRawAudioStream, pAviCompressedVideoStream;
IntPtr pGlobalBuf; private IntPtr pGlobalBuf;
int pGlobalBuf_size; private int pGlobalBuf_size;
/// <summary>are we sending 32 bit RGB to avi or 24?</summary>
bool bit32 = false; // are we sending 32 bit RGB to avi or 24?
private bool bit32 = false;
/// <summary> /// <summary>
/// there is just ony global buf. this gets it and makes sure its big enough. don't get all re-entrant on it! /// there is just ony global buf. this gets it and makes sure its big enough. don't get all re-entrant on it!
/// </summary> /// </summary>
IntPtr GetStaticGlobalBuf(int amount) private IntPtr GetStaticGlobalBuf(int amount)
{ {
if (amount > pGlobalBuf_size) if (amount > pGlobalBuf_size)
{ {
if (pGlobalBuf != IntPtr.Zero) if (pGlobalBuf != IntPtr.Zero)
{
Marshal.FreeHGlobal(pGlobalBuf); Marshal.FreeHGlobal(pGlobalBuf);
}
pGlobalBuf_size = amount; pGlobalBuf_size = amount;
pGlobalBuf = Marshal.AllocHGlobal(pGlobalBuf_size); pGlobalBuf = Marshal.AllocHGlobal(pGlobalBuf_size);
} }
return pGlobalBuf; return pGlobalBuf;
} }
class OutputStatus private class OutputStatus
{ {
public int video_frames; public int video_frames;
public int video_bytes; public int video_bytes;
@ -533,9 +610,13 @@ namespace BizHawk.Client.EmuHawk
public const int AUDIO_SEGMENT_SIZE = 44100 * 2; public const int AUDIO_SEGMENT_SIZE = 44100 * 2;
public short[] BufferedShorts = new short[AUDIO_SEGMENT_SIZE]; public short[] BufferedShorts = new short[AUDIO_SEGMENT_SIZE];
} }
OutputStatus outStatus;
public long GetLengthApproximation() { return outStatus.video_bytes + outStatus.audio_bytes; } private OutputStatus outStatus;
public long GetLengthApproximation()
{
return outStatus.video_bytes + outStatus.audio_bytes;
}
static unsafe int AVISaveOptions(IntPtr stream, ref Win32.AVICOMPRESSOPTIONS opts, IntPtr owner) static unsafe int AVISaveOptions(IntPtr stream, ref Win32.AVICOMPRESSOPTIONS opts, IntPtr owner)
{ {
@ -554,16 +635,20 @@ namespace BizHawk.Client.EmuHawk
this.parameters = parameters; this.parameters = parameters;
this.currVideoCodecToken = videoCodecToken; this.currVideoCodecToken = videoCodecToken;
//TODO - try creating the file once first before we let vfw botch it up? // TODO - try creating the file once first before we let vfw botch it up?
//open the avi output file handle // open the avi output file handle
if (File.Exists(destPath)) if (File.Exists(destPath))
{
File.Delete(destPath); File.Delete(destPath);
}
if (Win32.FAILED(Win32.AVIFileOpenW(ref pAviFile, destPath, Win32.OpenFileStyle.OF_CREATE | Win32.OpenFileStyle.OF_WRITE, 0))) if (Win32.FAILED(Win32.AVIFileOpenW(ref pAviFile, destPath, Win32.OpenFileStyle.OF_CREATE | Win32.OpenFileStyle.OF_WRITE, 0)))
{
throw new InvalidOperationException("Couldnt open dest path for avi file: " + destPath); throw new InvalidOperationException("Couldnt open dest path for avi file: " + destPath);
}
//initialize the video stream // initialize the video stream
Win32.AVISTREAMINFOW vidstream_header = new Win32.AVISTREAMINFOW(); Win32.AVISTREAMINFOW vidstream_header = new Win32.AVISTREAMINFOW();
Win32.BITMAPINFOHEADER bmih = new Win32.BITMAPINFOHEADER(); Win32.BITMAPINFOHEADER bmih = new Win32.BITMAPINFOHEADER();
parameters.PopulateBITMAPINFOHEADER24(ref bmih); parameters.PopulateBITMAPINFOHEADER24(ref bmih);
@ -577,7 +662,7 @@ namespace BizHawk.Client.EmuHawk
throw new InvalidOperationException("Failed opening raw video stream. Not sure how this could happen"); throw new InvalidOperationException("Failed opening raw video stream. Not sure how this could happen");
} }
//initialize audio stream // initialize audio stream
Win32.AVISTREAMINFOW audstream_header = new Win32.AVISTREAMINFOW(); Win32.AVISTREAMINFOW audstream_header = new Win32.AVISTREAMINFOW();
Win32.WAVEFORMATEX wfex = new Win32.WAVEFORMATEX(); Win32.WAVEFORMATEX wfex = new Win32.WAVEFORMATEX();
parameters.PopulateWAVEFORMATEX(ref wfex); parameters.PopulateWAVEFORMATEX(ref wfex);
@ -603,39 +688,40 @@ namespace BizHawk.Client.EmuHawk
/// </summary> /// </summary>
public IDisposable AcquireVideoCodecToken(IntPtr hwnd, CodecToken lastCodecToken) public IDisposable AcquireVideoCodecToken(IntPtr hwnd, CodecToken lastCodecToken)
{ {
if (!IsOpen) throw new InvalidOperationException("File must be opened before acquiring a codec token (or else the stream formats wouldnt be known)"); if (!IsOpen)
{
throw new InvalidOperationException("File must be opened before acquiring a codec token (or else the stream formats wouldnt be known)");
}
if (lastCodecToken != null) if (lastCodecToken != null)
currVideoCodecToken = lastCodecToken;
//encoder params
Win32.AVICOMPRESSOPTIONS comprOptions = new Win32.AVICOMPRESSOPTIONS();
if (currVideoCodecToken != null)
{ {
currVideoCodecToken.AllocateToAVICOMPRESSOPTIONS(ref comprOptions); currVideoCodecToken = lastCodecToken;
} }
// encoder params
Win32.AVICOMPRESSOPTIONS comprOptions = new Win32.AVICOMPRESSOPTIONS();
currVideoCodecToken?.AllocateToAVICOMPRESSOPTIONS(ref comprOptions);
bool result = AVISaveOptions(pAviRawVideoStream, ref comprOptions, hwnd) != 0; bool result = AVISaveOptions(pAviRawVideoStream, ref comprOptions, hwnd) != 0;
CodecToken ret = CodecToken.CreateFromAVICOMPRESSOPTIONS(ref comprOptions); CodecToken ret = CodecToken.CreateFromAVICOMPRESSOPTIONS(ref comprOptions);
//so, AVISaveOptions may have changed some of the pointers // so, AVISaveOptions may have changed some of the pointers
//if it changed the pointers, did it it free the old ones? we don't know // if it changed the pointers, did it it free the old ones? we don't know
//let's assume it frees them. if we're wrong, we leak. if we assume otherwise and we're wrong, we may crash. // let's assume it frees them. if we're wrong, we leak. if we assume otherwise and we're wrong, we may crash.
//so that means any pointers that come in here are either // so that means any pointers that come in here are either
//1. ones we allocated a minute ago // 1. ones we allocated a minute ago
//2. ones VFW allocated // 2. ones VFW allocated
//guess what? doesn't matter. We'll free them all ourselves. // guess what? doesn't matter. We'll free them all ourselves.
CodecToken.DeallocateAVICOMPRESSOPTIONS(ref comprOptions); CodecToken.DeallocateAVICOMPRESSOPTIONS(ref comprOptions);
if (result)
if(result)
{ {
// save to config and return it // save to config and return it
Global.Config.AVICodecToken = ret.Serialize(); Global.Config.AVICodecToken = ret.Serialize();
return ret; return ret;
} }
else
return null; return null;
} }
/// <summary> /// <summary>
@ -644,26 +730,33 @@ namespace BizHawk.Client.EmuHawk
public void OpenStreams() public void OpenStreams()
{ {
if (currVideoCodecToken == null) if (currVideoCodecToken == null)
{
throw new InvalidOperationException("set a video codec token before opening the streams!"); throw new InvalidOperationException("set a video codec token before opening the streams!");
}
//open compressed video stream // open compressed video stream
Win32.AVICOMPRESSOPTIONS opts = new Win32.AVICOMPRESSOPTIONS(); Win32.AVICOMPRESSOPTIONS opts = new Win32.AVICOMPRESSOPTIONS();
currVideoCodecToken.AllocateToAVICOMPRESSOPTIONS(ref opts); currVideoCodecToken.AllocateToAVICOMPRESSOPTIONS(ref opts);
bool failed = Win32.FAILED(Win32.AVIMakeCompressedStream(out pAviCompressedVideoStream, pAviRawVideoStream, ref opts, IntPtr.Zero)); bool failed = Win32.FAILED(Win32.AVIMakeCompressedStream(out pAviCompressedVideoStream, pAviRawVideoStream, ref opts, IntPtr.Zero));
CodecToken.DeallocateAVICOMPRESSOPTIONS(ref opts); CodecToken.DeallocateAVICOMPRESSOPTIONS(ref opts);
if(failed) if (failed)
{ {
CloseStreams(); CloseStreams();
throw new InvalidOperationException("Failed making compressed video stream"); throw new InvalidOperationException("Failed making compressed video stream");
} }
//set the compressed video stream input format // set the compressed video stream input format
Win32.BITMAPINFOHEADER bmih = new Win32.BITMAPINFOHEADER(); Win32.BITMAPINFOHEADER bmih = new Win32.BITMAPINFOHEADER();
if (bit32) if (bit32)
{
parameters.PopulateBITMAPINFOHEADER32(ref bmih); parameters.PopulateBITMAPINFOHEADER32(ref bmih);
}
else else
{
parameters.PopulateBITMAPINFOHEADER24(ref bmih); parameters.PopulateBITMAPINFOHEADER24(ref bmih);
}
if (Win32.FAILED(Win32.AVIStreamSetFormat(pAviCompressedVideoStream, 0, ref bmih, Marshal.SizeOf(bmih)))) if (Win32.FAILED(Win32.AVIStreamSetFormat(pAviCompressedVideoStream, 0, ref bmih, Marshal.SizeOf(bmih))))
{ {
bit32 = true; // we'll try again bit32 = true; // we'll try again
@ -671,7 +764,7 @@ namespace BizHawk.Client.EmuHawk
throw new InvalidOperationException("Failed setting compressed video stream input format"); throw new InvalidOperationException("Failed setting compressed video stream input format");
} }
//set audio stream input format // set audio stream input format
Win32.WAVEFORMATEX wfex = new Win32.WAVEFORMATEX(); Win32.WAVEFORMATEX wfex = new Win32.WAVEFORMATEX();
parameters.PopulateWAVEFORMATEX(ref wfex); parameters.PopulateWAVEFORMATEX(ref wfex);
if (Win32.FAILED(Win32.AVIStreamSetFormat(pAviRawAudioStream, 0, ref wfex, Marshal.SizeOf(wfex)))) if (Win32.FAILED(Win32.AVIStreamSetFormat(pAviRawAudioStream, 0, ref wfex, Marshal.SizeOf(wfex))))
@ -682,7 +775,7 @@ namespace BizHawk.Client.EmuHawk
} }
/// <summary> /// <summary>
/// wrap up the avi writing /// wrap up the AVI writing
/// </summary> /// </summary>
public void CloseFile() public void CloseFile()
{ {
@ -692,16 +785,19 @@ namespace BizHawk.Client.EmuHawk
Win32.AVIStreamRelease(pAviRawAudioStream); Win32.AVIStreamRelease(pAviRawAudioStream);
pAviRawAudioStream = IntPtr.Zero; pAviRawAudioStream = IntPtr.Zero;
} }
if (pAviRawVideoStream != IntPtr.Zero) if (pAviRawVideoStream != IntPtr.Zero)
{ {
Win32.AVIStreamRelease(pAviRawVideoStream); Win32.AVIStreamRelease(pAviRawVideoStream);
pAviRawVideoStream = IntPtr.Zero; pAviRawVideoStream = IntPtr.Zero;
} }
if (pAviFile != IntPtr.Zero) if (pAviFile != IntPtr.Zero)
{ {
Win32.AVIFileRelease(pAviFile); Win32.AVIFileRelease(pAviFile);
pAviFile = IntPtr.Zero; pAviFile = IntPtr.Zero;
} }
if (pGlobalBuf != IntPtr.Zero) if (pGlobalBuf != IntPtr.Zero)
{ {
Marshal.FreeHGlobal(pGlobalBuf); Marshal.FreeHGlobal(pGlobalBuf);
@ -716,7 +812,10 @@ namespace BizHawk.Client.EmuHawk
public void CloseStreams() public void CloseStreams()
{ {
if (pAviRawAudioStream != IntPtr.Zero) if (pAviRawAudioStream != IntPtr.Zero)
{
FlushBufferedAudio(); FlushBufferedAudio();
}
if (pAviCompressedVideoStream != IntPtr.Zero) if (pAviCompressedVideoStream != IntPtr.Zero)
{ {
Win32.AVIStreamRelease(pAviCompressedVideoStream); Win32.AVIStreamRelease(pAviCompressedVideoStream);
@ -724,7 +823,7 @@ namespace BizHawk.Client.EmuHawk
} }
} }
//todo - why couldnt this take an ISoundProvider? it could do the timekeeping as well.. hmm // todo - why couldnt this take an ISoundProvider? it could do the timekeeping as well.. hmm
public unsafe void AddSamples(short[] samples) public unsafe void AddSamples(short[] samples)
{ {
int todo = samples.Length; int todo = samples.Length;
@ -734,11 +833,15 @@ namespace BizHawk.Client.EmuHawk
int remain = OutputStatus.AUDIO_SEGMENT_SIZE - outStatus.audio_buffered_shorts; int remain = OutputStatus.AUDIO_SEGMENT_SIZE - outStatus.audio_buffered_shorts;
int chunk = Math.Min(remain, todo); int chunk = Math.Min(remain, todo);
for (int i = 0; i < chunk; i++) for (int i = 0; i < chunk; i++)
{
outStatus.BufferedShorts[outStatus.audio_buffered_shorts++] = samples[idx++]; outStatus.BufferedShorts[outStatus.audio_buffered_shorts++] = samples[idx++];
}
todo -= chunk; todo -= chunk;
if (outStatus.audio_buffered_shorts == OutputStatus.AUDIO_SEGMENT_SIZE) if (outStatus.audio_buffered_shorts == OutputStatus.AUDIO_SEGMENT_SIZE)
{
FlushBufferedAudio(); FlushBufferedAudio();
}
} }
} }
@ -753,7 +856,8 @@ namespace BizHawk.Client.EmuHawk
{ {
sptr[i] = outStatus.BufferedShorts[i]; sptr[i] = outStatus.BufferedShorts[i];
} }
//(TODO - inefficient- build directly in a buffer)
// (TODO - inefficient- build directly in a buffer)
int bytes_written; int bytes_written;
Win32.AVIStreamWrite(pAviRawAudioStream, outStatus.audio_samples, todo_realsamples, buf, todo_realsamples * 4, 0, IntPtr.Zero, out bytes_written); Win32.AVIStreamWrite(pAviRawAudioStream, outStatus.audio_samples, todo_realsamples, buf, todo_realsamples * 4, 0, IntPtr.Zero, out bytes_written);
outStatus.audio_samples += todo_realsamples; outStatus.audio_samples += todo_realsamples;
@ -777,7 +881,7 @@ namespace BizHawk.Client.EmuHawk
{ {
IntPtr buf = GetStaticGlobalBuf(todo); IntPtr buf = GetStaticGlobalBuf(todo);
//TODO - would using a byte* be faster? // TODO - would using a byte* be faster?
int[] buffer = source.GetVideoBuffer(); int[] buffer = source.GetVideoBuffer();
fixed (int* buffer_ptr = &buffer[0]) fixed (int* buffer_ptr = &buffer[0])
{ {
@ -846,8 +950,11 @@ namespace BizHawk.Client.EmuHawk
{ {
CodecToken ct = CodecToken.DeSerialize(Global.Config.AVICodecToken); CodecToken ct = CodecToken.DeSerialize(Global.Config.AVICodecToken);
if (ct == null) if (ct == null)
{
throw new Exception("No default AVICodecToken in config!"); throw new Exception("No default AVICodecToken in config!");
currVideoCodecToken = ct; }
_currVideoCodecToken = ct;
} }
public string DesiredExtension() public string DesiredExtension()
@ -855,8 +962,9 @@ namespace BizHawk.Client.EmuHawk
return "avi"; return "avi";
} }
public bool UsesAudio { get { return parameters.has_audio; } } public bool UsesAudio => parameters.has_audio;
public bool UsesVideo { get { return true; } }
public bool UsesVideo => true;
} }
} }

View File

@ -1,7 +1,4 @@
using System; using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing; using System.Drawing;
using BizHawk.Emulation.Common; using BizHawk.Emulation.Common;
@ -9,63 +6,48 @@ using BizHawk.Emulation.Common;
namespace BizHawk.Client.EmuHawk namespace BizHawk.Client.EmuHawk
{ {
/// <summary> /// <summary>
/// an IVideoProvder wrapping a Bitmap /// an IVideoProvider wrapping a Bitmap
/// </summary> /// </summary>
public class BmpVideoProvider : IVideoProvider, IDisposable public class BmpVideoProvider : IVideoProvider, IDisposable
{ {
Bitmap bmp; private Bitmap _bmp;
public BmpVideoProvider(Bitmap bmp) public BmpVideoProvider(Bitmap bmp)
{ {
this.bmp = bmp; _bmp = bmp;
} }
public void Dispose() public void Dispose()
{ {
if (bmp != null) if (_bmp != null)
{ {
bmp.Dispose(); _bmp.Dispose();
bmp = null; _bmp = null;
} }
} }
public int[] GetVideoBuffer() public int[] GetVideoBuffer()
{ {
// is there a faster way to do this? // 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), System.Drawing.Imaging.ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
int[] ret = new int[bmp.Width * bmp.Height]; int[] ret = new int[_bmp.Width * _bmp.Height];
// won't work if stride is messed up // won't work if stride is messed up
System.Runtime.InteropServices.Marshal.Copy(data.Scan0, ret, 0, bmp.Width * bmp.Height); System.Runtime.InteropServices.Marshal.Copy(data.Scan0, ret, 0, _bmp.Width * _bmp.Height);
bmp.UnlockBits(data); _bmp.UnlockBits(data);
return ret; return ret;
} }
public int VirtualWidth public int VirtualWidth => _bmp.Width;
{
// todo: Bitmap actually has size metric data; use it
get { return bmp.Width; }
}
public int VirtualHeight // todo: Bitmap actually has size metric data; use it
{ public int VirtualHeight => _bmp.Height;
// todo: Bitmap actually has size metric data; use it
get { return bmp.Height; }
}
public int BufferWidth public int BufferWidth => _bmp.Width;
{
get { return bmp.Width; }
}
public int BufferHeight public int BufferHeight => _bmp.Height;
{
get { return bmp.Height; }
}
public int BackgroundColor public int BackgroundColor => 0;
{
get { return 0; }
}
} }
} }

View File

@ -1,7 +1,9 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using System.Text; using System.Text;
using System.Diagnostics; using System.Diagnostics;
using System.Windows.Forms;
using BizHawk.Client.Common; using BizHawk.Client.Common;
using BizHawk.Emulation.Common; using BizHawk.Emulation.Common;
@ -12,166 +14,172 @@ namespace BizHawk.Client.EmuHawk
/// uses pipes to launch an external ffmpeg process and encode /// uses pipes to launch an external ffmpeg process and encode
/// </summary> /// </summary>
[VideoWriter("ffmpeg", "FFmpeg writer", "Uses an external FFMPEG process to encode video and audio. Various formats supported. Splits on resolution change.")] [VideoWriter("ffmpeg", "FFmpeg writer", "Uses an external FFMPEG process to encode video and audio. Various formats supported. Splits on resolution change.")]
class FFmpegWriter : IVideoWriter public class FFmpegWriter : IVideoWriter
{ {
/// <summary> /// <summary>
/// handle to external ffmpeg process /// handle to external ffmpeg process
/// </summary> /// </summary>
Process ffmpeg; private Process _ffmpeg;
/// <summary> /// <summary>
/// the commandline actually sent to ffmpeg; for informative purposes /// the commandline actually sent to ffmpeg; for informative purposes
/// </summary> /// </summary>
string commandline; private string _commandline;
/// <summary> /// <summary>
/// current file segment (for multires) /// current file segment (for multires)
/// </summary> /// </summary>
int segment; private int _segment;
/// <summary> /// <summary>
/// base filename before segment number is attached /// base filename before segment number is attached
/// </summary> /// </summary>
string baseName; private string _baseName;
/// <summary> /// <summary>
/// recent lines in ffmpeg's stderr, for informative purposes /// recent lines in ffmpeg's stderr, for informative purposes
/// </summary> /// </summary>
Queue<string> stderr; private Queue<string> _stderr;
/// <summary> /// <summary>
/// number of lines of stderr to buffer /// number of lines of stderr to buffer
/// </summary> /// </summary>
const int consolebuffer = 5; private const int Consolebuffer = 5;
/// <summary> /// <summary>
/// muxer handle for the current segment /// muxer handle for the current segment
/// </summary> /// </summary>
NutMuxer muxer; private NutMuxer _muxer;
/// <summary> /// <summary>
/// codec token in use /// codec token in use
/// </summary> /// </summary>
FFmpegWriterForm.FormatPreset token; private FFmpegWriterForm.FormatPreset _token;
/// <summary> /// <summary>
/// file extension actually used /// file extension actually used
/// </summary> /// </summary>
string ext; private string _ext;
public void SetFrame(int frame) { } public void SetFrame(int frame)
{
}
public void OpenFile(string baseName) public void OpenFile(string baseName)
{ {
this.baseName = System.IO.Path.Combine( _baseName = Path.Combine(
System.IO.Path.GetDirectoryName(baseName), Path.GetDirectoryName(baseName),
System.IO.Path.GetFileNameWithoutExtension(baseName)); Path.GetFileNameWithoutExtension(baseName));
ext = System.IO.Path.GetExtension(baseName); _ext = Path.GetExtension(baseName);
segment = 0; _segment = 0;
OpenFileSegment(); OpenFileSegment();
} }
/// <summary> /// <summary>
/// starts an ffmpeg process and sets up associated sockets /// starts an ffmpeg process and sets up associated sockets
/// </summary> /// </summary>
void OpenFileSegment() private void OpenFileSegment()
{ {
try try
{ {
ffmpeg = new Process(); _ffmpeg = new Process();
#if WINDOWS #if WINDOWS
ffmpeg.StartInfo.FileName = System.IO.Path.Combine(PathManager.GetDllDirectory(), "ffmpeg.exe"); _ffmpeg.StartInfo.FileName = Path.Combine(PathManager.GetDllDirectory(), "ffmpeg.exe");
#else #else
ffmpeg.StartInfo.FileName = "ffmpeg"; // expecting native version to be in path ffmpeg.StartInfo.FileName = "ffmpeg"; // expecting native version to be in path
#endif #endif
string filename = String.Format("{0}_{1,4:D4}{2}", baseName, segment, ext); string filename = $"{_baseName}_{_segment,4:D4}{_ext}";
ffmpeg.StartInfo.Arguments = String.Format("-y -f nut -i - {1} \"{0}\"", filename, token.commandline); _ffmpeg.StartInfo.Arguments = string.Format("-y -f nut -i - {1} \"{0}\"", filename, _token.Commandline);
ffmpeg.StartInfo.CreateNoWindow = true; _ffmpeg.StartInfo.CreateNoWindow = true;
// ffmpeg sends informative display to stderr, and nothing to stdout // ffmpeg sends informative display to stderr, and nothing to stdout
ffmpeg.StartInfo.RedirectStandardError = true; _ffmpeg.StartInfo.RedirectStandardError = true;
ffmpeg.StartInfo.RedirectStandardInput = true; _ffmpeg.StartInfo.RedirectStandardInput = true;
ffmpeg.StartInfo.UseShellExecute = false; _ffmpeg.StartInfo.UseShellExecute = false;
commandline = "ffmpeg " + ffmpeg.StartInfo.Arguments; _commandline = "ffmpeg " + _ffmpeg.StartInfo.Arguments;
ffmpeg.ErrorDataReceived += new DataReceivedEventHandler(StderrHandler); _ffmpeg.ErrorDataReceived += new DataReceivedEventHandler(StderrHandler);
stderr = new Queue<string>(consolebuffer); _stderr = new Queue<string>(Consolebuffer);
ffmpeg.Start(); _ffmpeg.Start();
} }
catch catch
{ {
ffmpeg.Dispose(); _ffmpeg.Dispose();
ffmpeg = null; _ffmpeg = null;
throw; throw;
} }
ffmpeg.BeginErrorReadLine();
muxer = new NutMuxer(width, height, fpsnum, fpsden, sampleRate, channels, ffmpeg.StandardInput.BaseStream); _ffmpeg.BeginErrorReadLine();
_muxer = new NutMuxer(width, height, fpsnum, fpsden, sampleRate, channels, _ffmpeg.StandardInput.BaseStream);
} }
/// <summary> /// <summary>
/// saves stderr lines from ffmpeg in a short queue /// saves stderr lines from ffmpeg in a short queue
/// </summary> /// </summary>
/// <param name="p"></param> private void StderrHandler(object p, DataReceivedEventArgs line)
/// <param name="line"></param>
void StderrHandler(object p, DataReceivedEventArgs line)
{ {
if (!String.IsNullOrEmpty(line.Data)) if (!string.IsNullOrEmpty(line.Data))
{ {
if (stderr.Count == consolebuffer) if (_stderr.Count == Consolebuffer)
stderr.Dequeue(); {
stderr.Enqueue(line.Data + "\n"); _stderr.Dequeue();
}
_stderr.Enqueue(line.Data + "\n");
} }
} }
/// <summary> /// <summary>
/// finishes an ffmpeg process /// finishes an ffmpeg process
/// </summary> /// </summary>
void CloseFileSegment() private void CloseFileSegment()
{ {
muxer.Finish(); _muxer.Finish();
//ffmpeg.StandardInput.Close(); //ffmpeg.StandardInput.Close();
// how long should we wait here? // how long should we wait here?
ffmpeg.WaitForExit(20000); _ffmpeg.WaitForExit(20000);
ffmpeg.Dispose(); _ffmpeg.Dispose();
ffmpeg = null; _ffmpeg = null;
stderr = null; _stderr = null;
commandline = null; _commandline = null;
muxer = null; _muxer = null;
} }
public void CloseFile() public void CloseFile()
{ {
CloseFileSegment(); CloseFileSegment();
baseName = null; _baseName = null;
} }
/// <summary> /// <summary>
/// returns a string containing the commandline sent to ffmpeg and recent console (stderr) output /// returns a string containing the commandline sent to ffmpeg and recent console (stderr) output
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
string ffmpeg_geterror() private string ffmpeg_geterror()
{ {
if (ffmpeg.StartInfo.RedirectStandardError) if (_ffmpeg.StartInfo.RedirectStandardError)
{ {
ffmpeg.CancelErrorRead(); _ffmpeg.CancelErrorRead();
} }
StringBuilder s = new StringBuilder();
s.Append(commandline); var s = new StringBuilder();
s.Append(_commandline);
s.Append('\n'); s.Append('\n');
while (stderr.Count > 0) while (_stderr.Count > 0)
{ {
var foo = stderr.Dequeue(); var foo = _stderr.Dequeue();
s.Append(foo); s.Append(foo);
} }
return s.ToString(); return s.ToString();
} }
@ -179,19 +187,23 @@ namespace BizHawk.Client.EmuHawk
public void AddFrame(IVideoProvider source) public void AddFrame(IVideoProvider source)
{ {
if (source.BufferWidth != width || source.BufferHeight != height) if (source.BufferWidth != width || source.BufferHeight != height)
{
SetVideoParameters(source.BufferWidth, source.BufferHeight); SetVideoParameters(source.BufferWidth, source.BufferHeight);
}
if (ffmpeg.HasExited) if (_ffmpeg.HasExited)
{
throw new Exception("unexpected ffmpeg death:\n" + ffmpeg_geterror()); throw new Exception("unexpected ffmpeg death:\n" + ffmpeg_geterror());
}
var video = source.GetVideoBuffer(); var video = source.GetVideoBuffer();
try try
{ {
muxer.WriteVideoFrame(video); _muxer.WriteVideoFrame(video);
} }
catch catch
{ {
System.Windows.Forms.MessageBox.Show("Exception! ffmpeg history:\n" + ffmpeg_geterror()); MessageBox.Show("Exception! ffmpeg history:\n" + ffmpeg_geterror());
throw; throw;
} }
@ -199,7 +211,7 @@ namespace BizHawk.Client.EmuHawk
//ffmpeg.StandardInput.BaseStream.Write(b, 0, b.Length); //ffmpeg.StandardInput.BaseStream.Write(b, 0, b.Length);
} }
public IDisposable AcquireVideoCodecToken(System.Windows.Forms.IWin32Window hwnd) public IDisposable AcquireVideoCodecToken(IWin32Window hwnd)
{ {
return FFmpegWriterForm.DoFFmpegWriterDlg(hwnd); return FFmpegWriterForm.DoFFmpegWriterDlg(hwnd);
} }
@ -207,15 +219,19 @@ namespace BizHawk.Client.EmuHawk
public void SetVideoCodecToken(IDisposable token) public void SetVideoCodecToken(IDisposable token)
{ {
if (token is FFmpegWriterForm.FormatPreset) if (token is FFmpegWriterForm.FormatPreset)
this.token = (FFmpegWriterForm.FormatPreset)token; {
_token = (FFmpegWriterForm.FormatPreset)token;
}
else else
{
throw new ArgumentException("FFmpegWriter can only take its own codec tokens!"); throw new ArgumentException("FFmpegWriter can only take its own codec tokens!");
}
} }
/// <summary> /// <summary>
/// video params /// video params
/// </summary> /// </summary>
int fpsnum, fpsden, width, height, sampleRate, channels; private int fpsnum, fpsden, width, height, sampleRate, channels;
public void SetMovieParameters(int fpsnum, int fpsden) public void SetMovieParameters(int fpsnum, int fpsden)
{ {
@ -231,10 +247,10 @@ namespace BizHawk.Client.EmuHawk
/* ffmpeg theoretically supports variable resolution videos, but in practice that's not handled very well. /* ffmpeg theoretically supports variable resolution videos, but in practice that's not handled very well.
* so we start a new segment. * so we start a new segment.
*/ */
if (ffmpeg != null) if (_ffmpeg != null)
{ {
CloseFileSegment(); CloseFileSegment();
segment++; _segment++;
OpenFileSegment(); OpenFileSegment();
} }
} }
@ -248,27 +264,32 @@ namespace BizHawk.Client.EmuHawk
public void Dispose() public void Dispose()
{ {
if (ffmpeg != null) if (_ffmpeg != null)
{
CloseFile(); CloseFile();
}
} }
public void AddSamples(short[] samples) public void AddSamples(short[] samples)
{ {
if (ffmpeg.HasExited) if (_ffmpeg.HasExited)
{
throw new Exception("unexpected ffmpeg death:\n" + ffmpeg_geterror()); throw new Exception("unexpected ffmpeg death:\n" + ffmpeg_geterror());
}
if (samples.Length == 0) if (samples.Length == 0)
{ {
// has special meaning for the muxer, so don't pass on // has special meaning for the muxer, so don't pass on
return; return;
} }
try try
{ {
muxer.WriteAudioFrame(samples); _muxer.WriteAudioFrame(samples);
} }
catch catch
{ {
System.Windows.Forms.MessageBox.Show("Exception! ffmpeg history:\n" + ffmpeg_geterror()); MessageBox.Show("Exception! ffmpeg history:\n" + ffmpeg_geterror());
throw; throw;
} }
} }
@ -276,7 +297,10 @@ namespace BizHawk.Client.EmuHawk
public void SetAudioParameters(int sampleRate, int channels, int bits) public void SetAudioParameters(int sampleRate, int channels, int bits)
{ {
if (bits != 16) if (bits != 16)
{
throw new ArgumentOutOfRangeException(nameof(bits), "Sampling depth must be 16 bits!"); throw new ArgumentOutOfRangeException(nameof(bits), "Sampling depth must be 16 bits!");
}
this.sampleRate = sampleRate; this.sampleRate = sampleRate;
this.channels = channels; this.channels = channels;
} }
@ -284,15 +308,16 @@ namespace BizHawk.Client.EmuHawk
public string DesiredExtension() public string DesiredExtension()
{ {
// this needs to interface with the codec token // this needs to interface with the codec token
return token.defaultext; return _token.Defaultext;
} }
public void SetDefaultVideoCodecToken() public void SetDefaultVideoCodecToken()
{ {
token = FFmpegWriterForm.FormatPreset.GetDefaultPreset(); _token = FFmpegWriterForm.FormatPreset.GetDefaultPreset();
} }
public bool UsesAudio { get { return true; } } public bool UsesAudio => true;
public bool UsesVideo { get { return true; } }
public bool UsesVideo => true;
} }
} }

View File

@ -58,7 +58,7 @@
this.listBox1.Name = "listBox1"; this.listBox1.Name = "listBox1";
this.listBox1.Size = new System.Drawing.Size(275, 147); this.listBox1.Size = new System.Drawing.Size(275, 147);
this.listBox1.TabIndex = 1; this.listBox1.TabIndex = 1;
this.listBox1.SelectedIndexChanged += new System.EventHandler(this.listBox1_SelectedIndexChanged); this.listBox1.SelectedIndexChanged += new System.EventHandler(this.ListBox1_SelectedIndexChanged);
// //
// label2 // label2
// //

View File

@ -1,10 +1,4 @@
using System; using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms; using System.Windows.Forms;
using BizHawk.Client.Common; using BizHawk.Client.Common;
@ -22,42 +16,29 @@ namespace BizHawk.Client.EmuHawk
public class FormatPreset : IDisposable public class FormatPreset : IDisposable
{ {
/// <summary> /// <summary>
/// name for listbox /// Gets the name for the listbox
/// </summary> /// </summary>
public string name; public string Name { get; }
/// <summary>
/// long human readable description
/// </summary>
public string desc;
/// <summary>
/// actual portion of ffmpeg commandline
/// </summary>
public string commandline;
public override string ToString()
{
return name;
}
/// <summary>
/// can be edited
/// </summary>
public bool custom = false;
/// <summary> /// <summary>
/// default file extension /// Gets the long human readable description
/// </summary> /// </summary>
public string defaultext; public string Desc { get; }
FormatPreset(string name, string desc, string commandline, bool custom, string defaultext) /// <summary>
{ /// Gets the actual portion of the ffmpeg commandline
this.name = name; /// </summary>
this.desc = desc; public string Commandline { get; set; }
this.custom = custom;
if (custom) /// <summary>
this.commandline = Global.Config.FFmpegCustomCommand; /// Gets a value indicating whether or not it can be edited
else /// </summary>
this.commandline = commandline; public bool Custom { get; }
this.defaultext = defaultext;
} /// <summary>
/// Gets the default file extension
/// </summary>
public string Defaultext { get; }
/// <summary> /// <summary>
/// get a list of canned presets /// get a list of canned presets
@ -65,7 +46,7 @@ namespace BizHawk.Client.EmuHawk
/// <returns></returns> /// <returns></returns>
public static FormatPreset[] GetPresets() public static FormatPreset[] GetPresets()
{ {
return new FormatPreset[] return new[]
{ {
new FormatPreset("Uncompressed AVI", "AVI file with uncompressed audio and video. Very large.", "-c:a pcm_s16le -c:v rawvideo -f avi", false, "avi"), new FormatPreset("Uncompressed AVI", "AVI file with uncompressed audio and video. Very large.", "-c:a pcm_s16le -c:v rawvideo -f avi", false, "avi"),
new FormatPreset("Xvid", "AVI file with xvid video and mp3 audio.", "-c:a libmp3lame -c:v libxvid -f avi", false, "avi"), new FormatPreset("Xvid", "AVI file with xvid video and mp3 audio.", "-c:a libmp3lame -c:v libxvid -f avi", false, "avi"),
@ -84,7 +65,6 @@ namespace BizHawk.Client.EmuHawk
/// <summary> /// <summary>
/// get the default format preset (from config files) /// get the default format preset (from config files)
/// </summary> /// </summary>
/// <returns></returns>
public static FormatPreset GetDefaultPreset() public static FormatPreset GetDefaultPreset()
{ {
FormatPreset[] fps = GetPresets(); FormatPreset[] fps = GetPresets();
@ -93,42 +73,61 @@ namespace BizHawk.Client.EmuHawk
{ {
if (fp.ToString() == Global.Config.FFmpegFormat) if (fp.ToString() == Global.Config.FFmpegFormat)
{ {
if (fp.custom) if (fp.Custom)
{
return fp; return fp;
}
} }
} }
// default to xvid? // default to xvid?
return fps[1]; return fps[1];
} }
public override string ToString()
{
return Name;
}
public void Dispose() public void Dispose()
{ {
} }
private FormatPreset(string name, string desc, string commandline, bool custom, string defaultext)
{
Name = name;
Desc = desc;
Custom = custom;
Commandline = Custom
? Global.Config.FFmpegCustomCommand
: commandline;
Defaultext = defaultext;
}
} }
public FFmpegWriterForm() private FFmpegWriterForm()
{ {
InitializeComponent(); InitializeComponent();
} }
private void listBox1_SelectedIndexChanged(object sender, EventArgs e) private void ListBox1_SelectedIndexChanged(object sender, EventArgs e)
{ {
if (listBox1.SelectedIndex != -1) if (listBox1.SelectedIndex != -1)
{ {
FormatPreset f = (FormatPreset)listBox1.SelectedItem; var f = (FormatPreset)listBox1.SelectedItem;
label5.Text = "Extension: " + f.defaultext; label5.Text = "Extension: " + f.Defaultext;
label3.Text = f.desc; label3.Text = f.Desc;
textBox1.Text = f.commandline; textBox1.Text = f.Commandline;
textBox1.ReadOnly = !f.custom; textBox1.ReadOnly = !f.Custom;
} }
} }
/// <summary> /// <summary>
/// return a formatpreset corresponding to the user's choice /// return a FormatPreset corresponding to the user's choice
/// </summary> /// </summary>
/// <param name="owner"></param>
/// <returns></returns>
public static FormatPreset DoFFmpegWriterDlg(IWin32Window owner) public static FormatPreset DoFFmpegWriterDlg(IWin32Window owner)
{ {
FFmpegWriterForm dlg = new FFmpegWriterForm(); FFmpegWriterForm dlg = new FFmpegWriterForm();
@ -136,24 +135,28 @@ namespace BizHawk.Client.EmuHawk
int i = dlg.listBox1.FindStringExact(Global.Config.FFmpegFormat); int i = dlg.listBox1.FindStringExact(Global.Config.FFmpegFormat);
if (i != ListBox.NoMatches) if (i != ListBox.NoMatches)
{
dlg.listBox1.SelectedIndex = i; dlg.listBox1.SelectedIndex = i;
}
DialogResult result = dlg.ShowDialog(owner); DialogResult result = dlg.ShowDialog(owner);
FormatPreset ret; FormatPreset ret;
if (result != DialogResult.OK || dlg.listBox1.SelectedIndex == -1) if (result != DialogResult.OK || dlg.listBox1.SelectedIndex == -1)
{
ret = null; ret = null;
}
else else
{ {
ret = (FormatPreset)dlg.listBox1.SelectedItem; ret = (FormatPreset)dlg.listBox1.SelectedItem;
Global.Config.FFmpegFormat = ret.ToString(); Global.Config.FFmpegFormat = ret.ToString();
if (ret.custom) if (ret.Custom)
{ {
ret.commandline = dlg.textBox1.Text; ret.Commandline = dlg.textBox1.Text;
Global.Config.FFmpegCustomCommand = dlg.textBox1.Text; Global.Config.FFmpegCustomCommand = dlg.textBox1.Text;
} }
} }
dlg.Dispose(); dlg.Dispose();
return ret; return ret;
} }

View File

@ -1,7 +1,4 @@
using System; using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO; using System.IO;
using System.Drawing; using System.Drawing;
@ -18,102 +15,130 @@ namespace BizHawk.Client.EmuHawk
private int _frameskip, _framedelay; private int _frameskip, _framedelay;
/// <summary> /// <summary>
/// how many frames to skip for each frame deposited /// Gets how many frames to skip for each frame deposited
/// </summary> /// </summary>
public int frameskip public int Frameskip
{ {
get { return _frameskip; } get
{
return _frameskip;
}
private set private set
{ {
if (value < 0) if (value < 0)
{
_frameskip = 0; _frameskip = 0;
}
else if (value > 999) else if (value > 999)
{
_frameskip = 999; _frameskip = 999;
}
else else
{
_frameskip = value; _frameskip = value;
}
} }
} }
/// <summary> /// <summary>
/// how long to delay between each gif frame (units of 10ms, -1 = auto) /// how long to delay between each gif frame (units of 10ms, -1 = auto)
/// </summary> /// </summary>
public int framedelay public int FrameDelay
{ {
get { return _framedelay; } get
{
return _framedelay;
}
private set private set
{ {
if (value < -1) if (value < -1)
{
_framedelay = -1; _framedelay = -1;
}
else if (value > 100) else if (value > 100)
{
_framedelay = 100; _framedelay = 100;
}
else else
{
_framedelay = value; _framedelay = value;
}
} }
} }
public void Dispose() { }
public GifToken(int frameskip, int framedelay)
{
this.frameskip = frameskip;
this.framedelay = framedelay;
}
public static GifToken LoadFromConfig() public static GifToken LoadFromConfig()
{ {
GifToken ret = new GifToken(0, 0); return new GifToken(0, 0)
ret.frameskip = Global.Config.GifWriterFrameskip; {
ret.framedelay = Global.Config.GifWriterDelay; Frameskip = Global.Config.GifWriterFrameskip,
return ret; FrameDelay = Global.Config.GifWriterDelay
};
}
public void Dispose()
{
}
private GifToken(int frameskip, int framedelay)
{
Frameskip = frameskip;
FrameDelay = framedelay;
} }
} }
GifToken token;
private GifToken _token;
public void SetVideoCodecToken(IDisposable token) public void SetVideoCodecToken(IDisposable token)
{ {
if (token is GifToken) if (token is GifToken)
{ {
this.token = (GifToken)token; _token = (GifToken)token;
CalcDelay(); CalcDelay();
} }
else else
{
throw new ArgumentException("GifWriter only takes its own tokens!"); throw new ArgumentException("GifWriter only takes its own tokens!");
}
} }
public void SetDefaultVideoCodecToken() public void SetDefaultVideoCodecToken()
{ {
token = GifToken.LoadFromConfig(); _token = GifToken.LoadFromConfig();
CalcDelay(); CalcDelay();
} }
/// <summary> /// <summary>
/// true if the first frame has been written to the file; false otherwise /// true if the first frame has been written to the file; false otherwise
/// </summary> /// </summary>
bool firstdone = false; private bool firstdone = false;
/// <summary> /// <summary>
/// the underlying stream we're writing to /// the underlying stream we're writing to
/// </summary> /// </summary>
Stream f; private Stream f;
/// <summary> /// <summary>
/// a final byte we must write before closing the stream /// a final byte we must write before closing the stream
/// </summary> /// </summary>
byte lastbyte; private byte lastbyte;
/// <summary> /// <summary>
/// keep track of skippable frames /// keep track of skippable frames
/// </summary> /// </summary>
int skipindex = 0; private int skipindex = 0;
int fpsnum = 1, fpsden = 1; private int fpsnum = 1, fpsden = 1;
public void SetFrame(int frame) { } public void SetFrame(int frame)
{
}
public void OpenFile(string baseName) public void OpenFile(string baseName)
{ {
f = new FileStream(baseName, FileMode.OpenOrCreate, FileAccess.Write); f = new FileStream(baseName, FileMode.OpenOrCreate, FileAccess.Write);
skipindex = token.frameskip; skipindex = _token.Frameskip;
} }
public void CloseFile() public void CloseFile()
@ -125,23 +150,25 @@ namespace BizHawk.Client.EmuHawk
/// <summary> /// <summary>
/// precooked gif header /// precooked gif header
/// </summary> /// </summary>
static byte[] GifAnimation = {33, 255, 11, 78, 69, 84, 83, 67, 65, 80, 69, 50, 46, 48, 3, 1, 0, 0, 0}; static byte[] GifAnimation = { 33, 255, 11, 78, 69, 84, 83, 67, 65, 80, 69, 50, 46, 48, 3, 1, 0, 0, 0 };
/// <summary> /// <summary>
/// little endian frame length in 10ms units /// little endian frame length in 10ms units
/// </summary> /// </summary>
byte[] Delay = {100, 0}; byte[] Delay = {100, 0};
public void AddFrame(IVideoProvider source) public void AddFrame(IVideoProvider source)
{ {
if (skipindex == token.frameskip) if (skipindex == _token.Frameskip)
{
skipindex = 0; skipindex = 0;
}
else else
{ {
skipindex++; skipindex++;
return; // skip this frame return; // skip this frame
} }
using (var bmp = new Bitmap(source.BufferWidth, source.BufferHeight, System.Drawing.Imaging.PixelFormat.Format32bppArgb)) using (var bmp = new Bitmap(source.BufferWidth, source.BufferHeight, System.Drawing.Imaging.PixelFormat.Format32bppArgb))
{ {
var data = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), System.Drawing.Imaging.ImageLockMode.WriteOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb); var data = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), System.Drawing.Imaging.ImageLockMode.WriteOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
@ -160,6 +187,7 @@ namespace BizHawk.Client.EmuHawk
f.Write(b, 0, 13); f.Write(b, 0, 13);
f.Write(GifAnimation, 0, GifAnimation.Length); f.Write(GifAnimation, 0, GifAnimation.Length);
} }
b[785] = Delay[0]; b[785] = Delay[0];
b[786] = Delay[1]; b[786] = Delay[1];
b[798] = (byte)(b[798] | 0x87); b[798] = (byte)(b[798] | 0x87);
@ -182,15 +210,23 @@ namespace BizHawk.Client.EmuHawk
return GifWriterForm.DoTokenForm(hwnd); return GifWriterForm.DoTokenForm(hwnd);
} }
void CalcDelay() private void CalcDelay()
{ {
if (token == null) if (_token == null)
{
return; return;
}
int delay; int delay;
if (token.framedelay == -1) if (_token.FrameDelay == -1)
delay = (100 * fpsden * (token.frameskip + 1) + (fpsnum / 2)) / fpsnum; {
delay = (100 * fpsden * (_token.Frameskip + 1) + (fpsnum / 2)) / fpsnum;
}
else else
delay = token.framedelay; {
delay = _token.FrameDelay;
}
Delay[0] = (byte)(delay & 0xff); Delay[0] = (byte)(delay & 0xff);
Delay[1] = (byte)(delay >> 8 & 0xff); Delay[1] = (byte)(delay >> 8 & 0xff);
} }
@ -232,7 +268,8 @@ namespace BizHawk.Client.EmuHawk
} }
} }
public bool UsesAudio { get { return false; } } public bool UsesAudio => false;
public bool UsesVideo { get { return true; } }
public bool UsesVideo => true;
} }
} }

View File

@ -102,7 +102,7 @@
this.numericUpDown2.Name = "numericUpDown2"; this.numericUpDown2.Name = "numericUpDown2";
this.numericUpDown2.Size = new System.Drawing.Size(96, 20); this.numericUpDown2.Size = new System.Drawing.Size(96, 20);
this.numericUpDown2.TabIndex = 5; this.numericUpDown2.TabIndex = 5;
this.numericUpDown2.ValueChanged += new System.EventHandler(this.numericUpDown2_ValueChanged); this.numericUpDown2.ValueChanged += new System.EventHandler(this.NumericUpDown2_ValueChanged);
// //
// label3 // label3
// //

View File

@ -1,10 +1,4 @@
using System; using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms; using System.Windows.Forms;
using BizHawk.Client.Common; using BizHawk.Client.Common;
@ -24,21 +18,22 @@ namespace BizHawk.Client.EmuHawk
{ {
dlg.numericUpDown1.Value = Global.Config.GifWriterFrameskip; dlg.numericUpDown1.Value = Global.Config.GifWriterFrameskip;
dlg.numericUpDown2.Value = Global.Config.GifWriterDelay; dlg.numericUpDown2.Value = Global.Config.GifWriterDelay;
dlg.numericUpDown2_ValueChanged(null, null); dlg.NumericUpDown2_ValueChanged(null, null);
var result = dlg.ShowDialog(parent); var result = dlg.ShowDialog(parent);
if (result == DialogResult.OK) if (result == DialogResult.OK)
{ {
Global.Config.GifWriterFrameskip = (int)dlg.numericUpDown1.Value; Global.Config.GifWriterFrameskip = (int)dlg.numericUpDown1.Value;
Global.Config.GifWriterDelay = (int)dlg.numericUpDown2.Value; Global.Config.GifWriterDelay = (int)dlg.numericUpDown2.Value;
return GifWriter.GifToken.LoadFromConfig(); return GifWriter.GifToken.LoadFromConfig();
} }
else
return null; return null;
} }
} }
private void numericUpDown2_ValueChanged(object sender, EventArgs e) private void NumericUpDown2_ValueChanged(object sender, EventArgs e)
{ {
if (numericUpDown2.Value == -1) if (numericUpDown2.Value == -1)
{ {
@ -50,7 +45,7 @@ namespace BizHawk.Client.EmuHawk
} }
else else
{ {
label3.Text = string.Format("{0} FPS", (int)((100 + numericUpDown2.Value / 2) / numericUpDown2.Value)); label3.Text = $"{(int)((100 + numericUpDown2.Value / 2) / numericUpDown2.Value)} FPS";
} }
} }
} }

View File

@ -1,5 +1,4 @@
using System; using System;
using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using BizHawk.Emulation.Common; using BizHawk.Emulation.Common;
@ -55,7 +54,7 @@ namespace BizHawk.Client.EmuHawk
/// <summary> /// <summary>
/// adds audio samples to the stream /// adds audio samples to the stream
/// no attempt is made to sync this to the video /// no attempt is made to sync this to the video
/// reccomendation: try not to have the size or pacing of the audio chunks be too "weird" /// recommendation: try not to have the size or pacing of the audio chunks be too "weird"
/// </summary> /// </summary>
void AddSamples(short[] samples); void AddSamples(short[] samples);
@ -78,8 +77,6 @@ namespace BizHawk.Client.EmuHawk
/// can be changed in future /// can be changed in future
/// should always match IVideoProvider /// should always match IVideoProvider
/// </summary> /// </summary>
/// <param name="width"></param>
/// <param name="height"></param>
void SetVideoParameters(int width, int height); void SetVideoParameters(int width, int height);
/// <summary> /// <summary>
@ -134,15 +131,15 @@ namespace BizHawk.Client.EmuHawk
[AttributeUsage(AttributeTargets.Class)] [AttributeUsage(AttributeTargets.Class)]
public class VideoWriterAttribute : Attribute public class VideoWriterAttribute : Attribute
{ {
public string ShortName { get; private set; } public string ShortName { get; }
public string Name { get; private set; } public string Name { get; }
public string Description { get; private set; } public string Description { get; }
public VideoWriterAttribute(string ShortName, string Name, string Description) public VideoWriterAttribute(string shortName, string name, string description)
{ {
this.ShortName = ShortName; ShortName = shortName;
this.Name = Name; Name = name;
this.Description = Description; Description = description;
} }
} }
@ -153,18 +150,18 @@ namespace BizHawk.Client.EmuHawk
public class VideoWriterInfo public class VideoWriterInfo
{ {
public VideoWriterAttribute Attribs { get; private set; } public VideoWriterAttribute Attribs { get; }
private Type type; private Type _type;
public VideoWriterInfo(VideoWriterAttribute Attribs, Type type) public VideoWriterInfo(VideoWriterAttribute attribs, Type type)
{ {
this.type = type; _type = type;
this.Attribs = Attribs; Attribs = attribs;
} }
public IVideoWriter Create() public IVideoWriter Create()
{ {
return (IVideoWriter)Activator.CreateInstance(type); return (IVideoWriter)Activator.CreateInstance(_type);
} }
public override string ToString() public override string ToString()
@ -203,15 +200,15 @@ namespace BizHawk.Client.EmuHawk
/// <summary> /// <summary>
/// find an IVideoWriter by its short name /// find an IVideoWriter by its short name
/// </summary> /// </summary>
/// <param name="name"></param>
/// <returns></returns>
public static IVideoWriter GetVideoWriter(string name) public static IVideoWriter GetVideoWriter(string name)
{ {
VideoWriterInfo ret; VideoWriterInfo ret;
if (vws.TryGetValue(name, out ret)) if (vws.TryGetValue(name, out ret))
{
return ret.Create(); return ret.Create();
else }
return null;
return null;
} }
} }
} }

View File

@ -1,8 +1,8 @@
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Windows.Forms;
using BizHawk.Common.IOExtensions; using BizHawk.Bizware.BizwareGL;
using BizHawk.Emulation.Common; using BizHawk.Emulation.Common;
namespace BizHawk.Client.EmuHawk namespace BizHawk.Client.EmuHawk
@ -13,8 +13,8 @@ namespace BizHawk.Client.EmuHawk
[VideoWriter("imagesequence", "Image sequence writer", "Writes a sequence of 24bpp PNG or JPG files (default compression quality)")] [VideoWriter("imagesequence", "Image sequence writer", "Writes a sequence of 24bpp PNG or JPG files (default compression quality)")]
public class ImageSequenceWriter : IDisposable, IVideoWriter public class ImageSequenceWriter : IDisposable, IVideoWriter
{ {
string BaseName; private string _baseName;
int Frame; private int _frame;
public void SetVideoCodecToken(IDisposable token) public void SetVideoCodecToken(IDisposable token)
{ {
@ -24,12 +24,13 @@ namespace BizHawk.Client.EmuHawk
{ {
} }
public bool UsesAudio { get { return false; } } public bool UsesAudio => false;
public bool UsesVideo { get { return true; } }
public bool UsesVideo => true;
public void OpenFile(string baseName) public void OpenFile(string baseName)
{ {
BaseName = baseName; _baseName = baseName;
} }
public void CloseFile() public void CloseFile()
@ -38,36 +39,43 @@ namespace BizHawk.Client.EmuHawk
public void SetFrame(int frame) public void SetFrame(int frame)
{ {
//eh? this gets ditched somehow // eh? this gets ditched somehow
} }
public void AddFrame(IVideoProvider source) public void AddFrame(IVideoProvider source)
{ {
string ext = Path.GetExtension(BaseName); string ext = Path.GetExtension(_baseName);
string name = Path.GetFileNameWithoutExtension(BaseName) + "_" + Frame.ToString(); string name = Path.GetFileNameWithoutExtension(_baseName) + "_" + _frame;
name += ext; name += ext;
name = Path.Combine(Path.GetDirectoryName(BaseName), name); name = Path.Combine(Path.GetDirectoryName(_baseName), name);
BizHawk.Bizware.BizwareGL.BitmapBuffer bb = new Bizware.BizwareGL.BitmapBuffer(source.BufferWidth, source.BufferHeight, source.GetVideoBuffer()); 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); bmp.Save(name, System.Drawing.Imaging.ImageFormat.Png);
}
else if (ext.ToUpper() == ".JPG") else if (ext.ToUpper() == ".JPG")
{
bmp.Save(name, System.Drawing.Imaging.ImageFormat.Jpeg); bmp.Save(name, System.Drawing.Imaging.ImageFormat.Jpeg);
}
} }
Frame++;
_frame++;
} }
public void AddSamples(short[] samples) public void AddSamples(short[] samples)
{ {
} }
class CodecToken : IDisposable private class CodecToken : IDisposable
{ {
public void Dispose() { } public void Dispose()
{
}
} }
public IDisposable AcquireVideoCodecToken(System.Windows.Forms.IWin32Window hwnd) public IDisposable AcquireVideoCodecToken(IWin32Window hwnd)
{ {
return new CodecToken(); return new CodecToken();
} }

View File

@ -1,10 +1,4 @@
using System; using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms; using System.Windows.Forms;
namespace BizHawk.Client.EmuHawk namespace BizHawk.Client.EmuHawk
@ -31,15 +25,14 @@ namespace BizHawk.Client.EmuHawk
private void threadsBar_Scroll(object sender, EventArgs e) private void threadsBar_Scroll(object sender, EventArgs e)
{ {
threadTop.Text = String.Format("Number of compression threads: {0}", threadsBar.Value); threadTop.Text = $"Number of compression threads: {threadsBar.Value}";
} }
private void compressionBar_Scroll(object sender, EventArgs e) private void compressionBar_Scroll(object sender, EventArgs e)
{ {
if (compressionBar.Value == compressionBar.Minimum) compressionTop.Text = compressionBar.Value == compressionBar.Minimum
compressionTop.Text = "Compression Level: NONE"; ? "Compression Level: NONE"
else : $"Compression Level: {compressionBar.Value}";
compressionTop.Text = String.Format("Compression Level: {0}", compressionBar.Value);
} }
/// <summary> /// <summary>
@ -64,10 +57,10 @@ namespace BizHawk.Client.EmuHawk
j.compressionBar.Value = complevel; j.compressionBar.Value = complevel;
j.threadsBar_Scroll(null, null); j.threadsBar_Scroll(null, null);
j.compressionBar_Scroll(null, null); j.compressionBar_Scroll(null, null);
j.threadLeft.Text = String.Format("{0}", tmin); j.threadLeft.Text = $"{tmin}";
j.threadRight.Text = String.Format("{0}", tmax); j.threadRight.Text = $"{tmax}";
j.compressionLeft.Text = String.Format("{0}", cmin); j.compressionLeft.Text = $"{cmin}";
j.compressionRight.Text = String.Format("{0}", cmax); j.compressionRight.Text = $"{cmax}";
DialogResult d = j.ShowDialog(hwnd); DialogResult d = j.ShowDialog(hwnd);
@ -76,9 +69,11 @@ namespace BizHawk.Client.EmuHawk
j.Dispose(); j.Dispose();
if (d == DialogResult.OK) if (d == DialogResult.OK)
{
return true; return true;
else }
return false;
return false;
} }

View File

@ -1,9 +1,8 @@
using System; using System;
using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.IO.Compression;
using System.Text; using System.Text;
using ICSharpCode.SharpZipLib.Zip.Compression.Streams; using ICSharpCode.SharpZipLib.Zip.Compression.Streams;
using ICSharpCode.SharpZipLib.Zip.Compression; using ICSharpCode.SharpZipLib.Zip.Compression;
@ -62,10 +61,12 @@ namespace BizHawk.Client.EmuHawk
/// stores compression parameters /// stores compression parameters
/// </summary> /// </summary>
CodecToken token; CodecToken token;
/// <summary> /// <summary>
/// fps numerator, constant /// fps numerator, constant
/// </summary> /// </summary>
int fpsnum; int fpsnum;
/// <summary> /// <summary>
/// fps denominator, constant /// fps denominator, constant
/// </summary> /// </summary>
@ -75,10 +76,12 @@ namespace BizHawk.Client.EmuHawk
/// audio samplerate, constant /// audio samplerate, constant
/// </summary> /// </summary>
int audiosamplerate; int audiosamplerate;
/// <summary> /// <summary>
/// audio number of channels, constant; 1 or 2 only /// audio number of channels, constant; 1 or 2 only
/// </summary> /// </summary>
int audiochannels; int audiochannels;
/// <summary> /// <summary>
/// audio bits per sample, constant; only 16 supported /// audio bits per sample, constant; only 16 supported
/// </summary> /// </summary>
@ -112,6 +115,7 @@ namespace BizHawk.Client.EmuHawk
/// </summary> /// </summary>
public UInt64 rerecords; public UInt64 rerecords;
} }
/// <summary> /// <summary>
/// represents the metadata for the active movie (if applicable) /// represents the metadata for the active movie (if applicable)
/// </summary> /// </summary>
@ -138,10 +142,12 @@ namespace BizHawk.Client.EmuHawk
/// current timestamp position /// current timestamp position
/// </summary> /// </summary>
UInt64 timestampoff; UInt64 timestampoff;
/// <summary> /// <summary>
/// total number of video frames written /// total number of video frames written
/// </summary> /// </summary>
UInt64 totalframes; UInt64 totalframes;
/// <summary> /// <summary>
/// total number of sample pairs written /// total number of sample pairs written
/// </summary> /// </summary>
@ -151,14 +157,17 @@ namespace BizHawk.Client.EmuHawk
/// fps of the video stream is fpsnum/fpsden /// fps of the video stream is fpsnum/fpsden
/// </summary> /// </summary>
int fpsnum; int fpsnum;
/// <summary> /// <summary>
/// fps of the video stream is fpsnum/fpsden /// fps of the video stream is fpsnum/fpsden
/// </summary> /// </summary>
int fpsden; int fpsden;
/// <summary> /// <summary>
/// audio samplerate in hz /// audio samplerate in hz
/// </summary> /// </summary>
int audiosamplerate; int audiosamplerate;
/// <summary> /// <summary>
/// true if input will be stereo; mono otherwise /// true if input will be stereo; mono otherwise
/// output stream is always stereo /// output stream is always stereo
@ -172,7 +181,9 @@ namespace BizHawk.Client.EmuHawk
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) if (!f.CanWrite)
{
throw new ArgumentException("Stream must be writable!"); throw new ArgumentException("Stream must be writable!");
}
this.f = f; this.f = f;
this.fpsnum = fpsnum; this.fpsnum = fpsnum;
@ -323,10 +334,13 @@ namespace BizHawk.Client.EmuHawk
/// encoding is similar to MIDI /// encoding is similar to MIDI
/// </summary> /// </summary>
/// <param name="v"></param> /// <param name="v"></param>
void writeVar(int v) private void writeVar(int v)
{ {
if (v < 0) if (v < 0)
{
throw new ArgumentException("length cannot be less than 0!"); throw new ArgumentException("length cannot be less than 0!");
}
writeVar((UInt64)v); writeVar((UInt64)v);
} }
@ -355,7 +369,10 @@ namespace BizHawk.Client.EmuHawk
void writeActual(JMDPacket j) void writeActual(JMDPacket j)
{ {
if (j.timestamp < timestampoff) if (j.timestamp < timestampoff)
{
throw new ArithmeticException("JMD Timestamp problem?"); throw new ArithmeticException("JMD Timestamp problem?");
}
UInt64 timestampout = j.timestamp - timestampoff; UInt64 timestampout = j.timestamp - timestampoff;
while (timestampout > 0xffffffff) while (timestampout > 0xffffffff)
{ {
@ -453,6 +470,7 @@ namespace BizHawk.Client.EmuHawk
else else
break; break;
} }
astorage.Enqueue(j); astorage.Enqueue(j);
} }

View File

@ -56,10 +56,14 @@ namespace BizHawk.Client.EmuHawk
} }
} }
public int IntValue { get { return Int32.Parse(this.Text); } } public int IntValue => int.Parse(Text);
public decimal DecimalValue { get { return Decimal.Parse(this.Text); } }
public bool AllowSpace { set; get; } public decimal DecimalValue => decimal.Parse(Text);
public bool AllowDecimal { set; get; }
public bool AllowNegative { set; get; } public bool AllowSpace { get; set; }
public bool AllowDecimal { get; set; }
public bool AllowNegative { get; set; }
} }
} }

View File

@ -260,20 +260,11 @@ namespace BizHawk.Client.EmuHawk
} }
public override bool CanRead public override bool CanRead => false;
{
get { return false; }
}
public override bool CanSeek public override bool CanSeek => false;
{
get { return false; }
}
public override bool CanWrite public override bool CanWrite => true;
{
get { return true; }
}
/// <summary> /// <summary>
/// write data out to underlying stream, including header, footer, checksums /// write data out to underlying stream, including header, footer, checksums
@ -286,7 +277,10 @@ namespace BizHawk.Client.EmuHawk
WriteBE64((ulong)startcode, header); WriteBE64((ulong)startcode, header);
WriteVarU(data.Length + 4, header); // +4 for checksum WriteVarU(data.Length + 4, header); // +4 for checksum
if (data.Length > 4092) if (data.Length > 4092)
{
WriteBE32(NutCRC32(header.ToArray()), header); WriteBE32(NutCRC32(header.ToArray()), header);
}
var tmp = header.ToArray(); var tmp = header.ToArray();
underlying.Write(tmp, 0, tmp.Length); underlying.Write(tmp, 0, tmp.Length);
@ -517,7 +511,6 @@ namespace BizHawk.Client.EmuHawk
ReusableBufferPool<byte> _pool; ReusableBufferPool<byte> _pool;
/// <summary> /// <summary>
///
/// </summary> /// </summary>
/// <param name="payload">frame data</param> /// <param name="payload">frame data</param>
/// <param name="payloadlen">actual length of frame data</param> /// <param name="payloadlen">actual length of frame data</param>
@ -698,7 +691,6 @@ namespace BizHawk.Client.EmuHawk
WriteAudioFrame(new short[0]); WriteAudioFrame(new short[0]);
// flush any remaining queued packets // flush any remaining queued packets
while (audioqueue.Count > 0 && videoqueue.Count > 0) while (audioqueue.Count > 0 && videoqueue.Count > 0)
{ {
if (audioqueue.Peek() <= videoqueue.Peek()) if (audioqueue.Peek() <= videoqueue.Peek())

View File

@ -1,7 +1,5 @@
using System; using System;
using System.Collections.Generic; using System.IO;
using System.Linq;
using System.Text;
using BizHawk.Emulation.Common; using BizHawk.Emulation.Common;
@ -38,35 +36,34 @@ namespace BizHawk.Client.EmuHawk
/// <summary> /// <summary>
/// avparams /// avparams
/// </summary> /// </summary>
int fpsnum, fpsden, width, height, sampleRate, channels; private int fpsnum, fpsden, width, height, sampleRate, channels;
NutMuxer current = null; private NutMuxer _current = null;
string baseName; private string _baseName;
int segment; private int _segment;
public void OpenFile(string baseName) public void OpenFile(string baseName)
{ {
this.baseName = System.IO.Path.Combine( _baseName = Path.Combine(
System.IO.Path.GetDirectoryName(baseName), Path.GetDirectoryName(baseName),
System.IO.Path.GetFileNameWithoutExtension(baseName)); Path.GetFileNameWithoutExtension(baseName));
segment = 0; _segment = 0;
startsegment(); startsegment();
} }
void startsegment() private void startsegment()
{ {
var currentfile = System.IO.File.Open(String.Format("{0}_{1,4:D4}.nut", baseName, segment), System.IO.FileMode.Create, System.IO.FileAccess.Write); var currentfile = File.Open($"{_baseName}_{_segment,4:D4}.nut", FileMode.Create, FileAccess.Write);
current = new NutMuxer(width, height, fpsnum, fpsden, sampleRate, channels, currentfile); _current = new NutMuxer(width, height, fpsnum, fpsden, sampleRate, channels, currentfile);
} }
void endsegment() private void endsegment()
{ {
current.Finish(); _current.Finish();
current = null; _current = null;
} }
public void CloseFile() public void CloseFile()
{ {
endsegment(); endsegment();
@ -75,24 +72,26 @@ namespace BizHawk.Client.EmuHawk
public void AddFrame(IVideoProvider source) public void AddFrame(IVideoProvider source)
{ {
if (source.BufferHeight != height || source.BufferWidth != width) if (source.BufferHeight != height || source.BufferWidth != width)
{
SetVideoParameters(source.BufferWidth, source.BufferHeight); SetVideoParameters(source.BufferWidth, source.BufferHeight);
current.WriteVideoFrame(source.GetVideoBuffer()); }
_current.WriteVideoFrame(source.GetVideoBuffer());
} }
public void AddSamples(short[] samples) public void AddSamples(short[] samples)
{ {
current.WriteAudioFrame(samples); _current.WriteAudioFrame(samples);
} }
public void SetMovieParameters(int fpsnum, int fpsden) public void SetMovieParameters(int fpsnum, int fpsden)
{ {
this.fpsnum = fpsnum; this.fpsnum = fpsnum;
this.fpsden = fpsden; this.fpsden = fpsden;
if (current != null) if (_current != null)
{ {
endsegment(); endsegment();
segment++; _segment++;
startsegment(); startsegment();
} }
} }
@ -101,10 +100,10 @@ namespace BizHawk.Client.EmuHawk
{ {
this.width = width; this.width = width;
this.height = height; this.height = height;
if (current != null) if (_current != null)
{ {
endsegment(); endsegment();
segment++; _segment++;
startsegment(); startsegment();
} }
} }
@ -112,7 +111,10 @@ namespace BizHawk.Client.EmuHawk
public void SetAudioParameters(int sampleRate, int channels, int bits) public void SetAudioParameters(int sampleRate, int channels, int bits)
{ {
if (bits != 16) if (bits != 16)
{
throw new ArgumentOutOfRangeException(nameof(bits), "Audio depth must be 16 bit!"); throw new ArgumentOutOfRangeException(nameof(bits), "Audio depth must be 16 bit!");
}
this.sampleRate = sampleRate; this.sampleRate = sampleRate;
this.channels = channels; this.channels = channels;
} }
@ -124,9 +126,12 @@ namespace BizHawk.Client.EmuHawk
public void Dispose() public void Dispose()
{ {
if (current != null) if (_current != null)
{
endsegment(); endsegment();
baseName = null; }
_baseName = null;
} }
public string DesiredExtension() public string DesiredExtension()
@ -139,7 +144,8 @@ namespace BizHawk.Client.EmuHawk
// ignored // ignored
} }
public bool UsesAudio { get { return true; } } public bool UsesAudio => true;
public bool UsesVideo { get { return true; } }
public bool UsesVideo => true;
} }
} }

View File

@ -1,14 +1,9 @@
using System; using System;
using System.IO; using System.IO;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text; using System.Text;
using System.Windows.Forms; using System.Windows.Forms;
using BizHawk.Emulation;
using BizHawk.Emulation.Common; using BizHawk.Emulation.Common;
using BizHawk.Bizware.BizwareGL; using BizHawk.Bizware.BizwareGL;
@ -17,20 +12,27 @@ namespace BizHawk.Client.EmuHawk
[VideoWriter("syncless", "Syncless Recording", "Writes each frame to a directory as a PNG and WAV pair, identified by frame number. The results can be exported into one video file.")] [VideoWriter("syncless", "Syncless Recording", "Writes each frame to a directory as a PNG and WAV pair, identified by frame number. The results can be exported into one video file.")]
public class SynclessRecorder : IVideoWriter public class SynclessRecorder : IVideoWriter
{ {
public void Dispose() { } public void Dispose()
{
}
public void SetVideoCodecToken(IDisposable token) { } public void SetVideoCodecToken(IDisposable token)
{
}
public void SetDefaultVideoCodecToken() { } public void SetDefaultVideoCodecToken()
{
}
public void SetFrame(int frame) public void SetFrame(int frame)
{ {
mCurrFrame = frame; mCurrFrame = frame;
} }
int mCurrFrame; private int mCurrFrame;
string mBaseDirectory, mFramesDirectory; private string mBaseDirectory, mFramesDirectory;
string mProjectFile; private string mProjectFile;
public void OpenFile(string projFile) public void OpenFile(string projFile)
{ {
mProjectFile = projFile; mProjectFile = projFile;
@ -44,7 +46,9 @@ namespace BizHawk.Client.EmuHawk
File.WriteAllText(mProjectFile, sb.ToString()); File.WriteAllText(mProjectFile, sb.ToString());
} }
public void CloseFile() { } public void CloseFile()
{
}
public void AddFrame(IVideoProvider source) public void AddFrame(IVideoProvider source)
{ {
@ -68,12 +72,21 @@ namespace BizHawk.Client.EmuHawk
wwv.Dispose(); wwv.Dispose();
} }
public bool UsesAudio { get { return true; } } public bool UsesAudio => true;
public bool UsesVideo { get { return true; } }
class DummyDisposable : IDisposable { public void Dispose() { } } public bool UsesVideo => true;
public IDisposable AcquireVideoCodecToken(IWin32Window hwnd) { return new DummyDisposable(); } private class DummyDisposable : IDisposable
{
public void Dispose()
{
}
}
public IDisposable AcquireVideoCodecToken(IWin32Window hwnd)
{
return new DummyDisposable();
}
public void SetMovieParameters(int fpsnum, int fpsden) public void SetMovieParameters(int fpsnum, int fpsden)
{ {
@ -82,10 +95,10 @@ namespace BizHawk.Client.EmuHawk
public void SetVideoParameters(int width, int height) public void SetVideoParameters(int width, int height)
{ {
//may want to todo // may want to todo
} }
int paramSampleRate, paramChannels, paramBits; private int paramSampleRate, paramChannels, paramBits;
public void SetAudioParameters(int sampleRate, int channels, int bits) public void SetAudioParameters(int sampleRate, int channels, int bits)
{ {
@ -96,33 +109,42 @@ namespace BizHawk.Client.EmuHawk
public void SetMetaData(string gameName, string authors, UInt64 lengthMS, UInt64 rerecords) public void SetMetaData(string gameName, string authors, UInt64 lengthMS, UInt64 rerecords)
{ {
//not needed // not needed
} }
public string DesiredExtension() { return "syncless.txt"; } public string DesiredExtension()
{
return "syncless.txt";
}
/// <summary> /// <summary>
/// splits the string into chunks of length s /// splits the string into chunks of length s
/// </summary> /// </summary>
static List<string> StringChunkSplit(string s, int len) private static List<string> StringChunkSplit(string s, int len)
{ {
if (len == 0) throw new ArgumentException("Invalid len", nameof(len)); if (len == 0)
{
throw new ArgumentException("Invalid len", nameof(len));
}
int numChunks = (s.Length + len - 1) / len; int numChunks = (s.Length + len - 1) / len;
List<string> output = new List<string>(numChunks); var output = new List<string>(numChunks);
for (int i = 0, j = 0; i < numChunks; i++, j += len) for (int i = 0, j = 0; i < numChunks; i++, j += len)
{ {
int todo = len; int todo = len;
int remain = s.Length - j; int remain = s.Length - j;
if (remain < todo) todo = remain; if (remain < todo)
{
todo = remain;
}
output.Add(s.Substring(j, todo)); output.Add(s.Substring(j, todo));
} }
return output; return output;
} }
string GetAndCreatePathForFrameNum(int index) private string GetAndCreatePathForFrameNum(int index)
{ {
string subpath = GetPathFragmentForFrameNum(index); string subpath = GetPathFragmentForFrameNum(index);
string path = mFramesDirectory; string path = mFramesDirectory;
@ -139,6 +161,4 @@ namespace BizHawk.Client.EmuHawk
return subpath; return subpath;
} }
} }
} }

View File

@ -1,14 +1,9 @@
using System; using System;
using System.IO; using System.IO;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing; using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms; using System.Windows.Forms;
using BizHawk.Bizware;
using BizHawk.Bizware.BizwareGL; using BizHawk.Bizware.BizwareGL;
using BizHawk.Client.Common; using BizHawk.Client.Common;
@ -21,7 +16,7 @@ namespace BizHawk.Client.EmuHawk
InitializeComponent(); InitializeComponent();
} }
void GetPaths(int index, out string png, out string wav) private void GetPaths(int index, out string png, out string wav)
{ {
string subpath = SynclessRecorder.GetPathFragmentForFrameNum(index); string subpath = SynclessRecorder.GetPathFragmentForFrameNum(index);
string path = mFramesDirectory; string path = mFramesDirectory;
@ -30,23 +25,28 @@ namespace BizHawk.Client.EmuHawk
wav = path + ".wav"; wav = path + ".wav";
} }
string mSynclessConfigFile; private string mSynclessConfigFile;
string mFramesDirectory; private string mFramesDirectory;
public void Run() public void Run()
{ {
var ofd = new OpenFileDialog(); var ofd = new OpenFileDialog
ofd.FileName = PathManager.FilesystemSafeName(Global.Game) + ".syncless.txt"; {
ofd.InitialDirectory = PathManager.MakeAbsolutePath(Global.Config.PathEntries.AvPathFragment, null); FileName = PathManager.FilesystemSafeName(Global.Game) + ".syncless.txt",
InitialDirectory = PathManager.MakeAbsolutePath(Global.Config.PathEntries.AvPathFragment, null)
};
if (ofd.ShowDialog() == DialogResult.Cancel) if (ofd.ShowDialog() == DialogResult.Cancel)
{
return; return;
}
mSynclessConfigFile = ofd.FileName; mSynclessConfigFile = ofd.FileName;
//---- this is pretty crappy: //---- this is pretty crappy:
var lines = File.ReadAllLines(mSynclessConfigFile); var lines = File.ReadAllLines(mSynclessConfigFile);
string framesdir = ""; string framesdir = string.Empty;
foreach (var line in lines) foreach (var line in lines)
{ {
int idx = line.IndexOf('='); int idx = line.IndexOf('=');
@ -60,15 +60,18 @@ namespace BizHawk.Client.EmuHawk
mFramesDirectory = Path.Combine(Path.GetDirectoryName(Path.GetFullPath(mSynclessConfigFile)), framesdir); mFramesDirectory = Path.Combine(Path.GetDirectoryName(Path.GetFullPath(mSynclessConfigFile)), framesdir);
//scan frames directory // scan frames directory
int frame = 1; //hacky! skip frame 0, because we have a problem with dumping that frame somehow int frame = 1; // hacky! skip frame 0, because we have a problem with dumping that frame somehow
for (; ; ) for (;;)
{ {
string wav, png; string wav, png;
GetPaths(frame, out png, out wav); GetPaths(frame, out png, out wav);
if (!File.Exists(png) || !File.Exists(wav)) if (!File.Exists(png) || !File.Exists(wav))
{
break; break;
mFrameInfos.Add(new FrameInfo() }
mFrameInfos.Add(new FrameInfo
{ {
pngPath = png, pngPath = png,
wavPath = wav wavPath = wav
@ -80,7 +83,8 @@ namespace BizHawk.Client.EmuHawk
ShowDialog(); ShowDialog();
} }
List<FrameInfo> mFrameInfos = new List<FrameInfo>(); private readonly List<FrameInfo> mFrameInfos = new List<FrameInfo>();
struct FrameInfo struct FrameInfo
{ {
public string wavPath, pngPath; public string wavPath, pngPath;
@ -89,7 +93,10 @@ namespace BizHawk.Client.EmuHawk
private void btnExport_Click(object sender, EventArgs e) private void btnExport_Click(object sender, EventArgs e)
{ {
if(mFrameInfos.Count == 0) return; if (mFrameInfos.Count == 0)
{
return;
}
int width, height; int width, height;
using(var bmp = new Bitmap(mFrameInfos[0].pngPath)) using(var bmp = new Bitmap(mFrameInfos[0].pngPath))
@ -98,16 +105,20 @@ namespace BizHawk.Client.EmuHawk
height = bmp.Height; height = bmp.Height;
} }
var sfd = new SaveFileDialog(); var sfd = new SaveFileDialog
sfd.FileName = Path.ChangeExtension(mSynclessConfigFile, ".avi"); {
FileName = Path.ChangeExtension(mSynclessConfigFile, ".avi")
};
sfd.InitialDirectory = Path.GetDirectoryName(sfd.FileName); sfd.InitialDirectory = Path.GetDirectoryName(sfd.FileName);
if (sfd.ShowDialog() == DialogResult.Cancel) if (sfd.ShowDialog() == DialogResult.Cancel)
return;
using (AviWriter avw = new AviWriter())
{ {
avw.SetAudioParameters(44100, 2, 16); //hacky return;
avw.SetMovieParameters(60, 1); //hacky }
using (var avw = new AviWriter())
{
avw.SetAudioParameters(44100, 2, 16); // hacky
avw.SetMovieParameters(60, 1); // hacky
avw.SetVideoParameters(width, height); avw.SetVideoParameters(width, height);
var token = avw.AcquireVideoCodecToken(this); var token = avw.AcquireVideoCodecToken(this);
avw.SetVideoCodecToken(token); avw.SetVideoCodecToken(token);
@ -119,21 +130,22 @@ namespace BizHawk.Client.EmuHawk
var bbvp = new BitmapBufferVideoProvider(bb); var bbvp = new BitmapBufferVideoProvider(bb);
avw.AddFrame(bbvp); avw.AddFrame(bbvp);
} }
//offset = 44 dec
// offset = 44 dec
var wavBytes = File.ReadAllBytes(fi.wavPath); var wavBytes = File.ReadAllBytes(fi.wavPath);
var ms = new MemoryStream(wavBytes); var ms = new MemoryStream(wavBytes) { Position = 44 };
ms.Position = 44;
var br = new BinaryReader(ms); var br = new BinaryReader(ms);
List<short> sampledata = new List<short>(); var sampledata = new List<short>();
while (br.BaseStream.Position != br.BaseStream.Length) 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(); avw.CloseFile();
} }
} }
} }
} }

View File

@ -1,10 +1,5 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms; using System.Windows.Forms;
using BizHawk.Client.Common; using BizHawk.Client.Common;
@ -16,7 +11,7 @@ namespace BizHawk.Client.EmuHawk
/// </summary> /// </summary>
public partial class VideoWriterChooserForm : Form public partial class VideoWriterChooserForm : Form
{ {
VideoWriterChooserForm() private VideoWriterChooserForm()
{ {
InitializeComponent(); InitializeComponent();
@ -32,14 +27,19 @@ namespace BizHawk.Client.EmuHawk
} }
} }
lblSize.Text = string.Format("Size:\r\n{0}x{1}", CaptureWidth, CaptureHeight); lblSize.Text = $"Size:\r\n{CaptureWidth}x{CaptureHeight}";
if (CaptureWidth % 4 != 0 || CaptureHeight % 4 != 0) if (CaptureWidth % 4 != 0 || CaptureHeight % 4 != 0)
{
lblResolutionWarning.Visible = true; lblResolutionWarning.Visible = true;
else lblResolutionWarning.Visible = false; }
else
{
lblResolutionWarning.Visible = false;
}
} }
int CaptureWidth, CaptureHeight; private int CaptureWidth, CaptureHeight;
/// <summary> /// <summary>
/// chose an IVideoWriter /// chose an IVideoWriter
@ -49,27 +49,32 @@ namespace BizHawk.Client.EmuHawk
/// <returns>user choice, or null on Cancel\Close\invalid</returns> /// <returns>user choice, or null on Cancel\Close\invalid</returns>
public static IVideoWriter DoVideoWriterChoserDlg(IEnumerable<VideoWriterInfo> list, IWin32Window owner, out int resizew, out int resizeh, out bool pad, out bool audiosync) public static IVideoWriter DoVideoWriterChoserDlg(IEnumerable<VideoWriterInfo> list, IWin32Window owner, out int resizew, out int resizeh, out bool pad, out bool audiosync)
{ {
VideoWriterChooserForm dlg = new VideoWriterChooserForm(); VideoWriterChooserForm dlg = new VideoWriterChooserForm
dlg.labelDescriptionBody.Text = "";
{ {
int idx = 0; labelDescriptionBody = { Text = string.Empty }
int idx_select = -1; };
dlg.listBox1.BeginUpdate();
foreach (var vw in list) int idx = 0;
int idx_select = -1;
dlg.listBox1.BeginUpdate();
foreach (var vw in list)
{
dlg.listBox1.Items.Add(vw);
if (vw.Attribs.ShortName == Global.Config.VideoWriter)
{ {
dlg.listBox1.Items.Add(vw); idx_select = idx;
if (vw.Attribs.ShortName == Global.Config.VideoWriter)
idx_select = idx;
idx++;
} }
dlg.listBox1.SelectedIndex = idx_select;
dlg.listBox1.EndUpdate(); idx++;
} }
dlg.listBox1.SelectedIndex = idx_select;
dlg.listBox1.EndUpdate();
foreach (Control c in dlg.panelSizeSelect.Controls) foreach (Control c in dlg.panelSizeSelect.Controls)
{
c.Enabled = false; c.Enabled = false;
}
DialogResult result = dlg.ShowDialog(owner); DialogResult result = dlg.ShowDialog(owner);
@ -106,16 +111,17 @@ namespace BizHawk.Client.EmuHawk
private void listBox1_SelectedIndexChanged(object sender, EventArgs e) private void listBox1_SelectedIndexChanged(object sender, EventArgs e)
{ {
if (listBox1.SelectedIndex != -1) labelDescriptionBody.Text = listBox1.SelectedIndex != -1
labelDescriptionBody.Text = ((VideoWriterInfo)listBox1.SelectedItem).Attribs.Description; ? ((VideoWriterInfo)listBox1.SelectedItem).Attribs.Description
else : string.Empty;
labelDescriptionBody.Text = "";
} }
private void checkBoxResize_CheckedChanged(object sender, EventArgs e) private void checkBoxResize_CheckedChanged(object sender, EventArgs e)
{ {
foreach (Control c in panelSizeSelect.Controls) foreach (Control c in panelSizeSelect.Controls)
{
c.Enabled = checkBoxResize.Checked; c.Enabled = checkBoxResize.Checked;
}
} }
private void buttonAuto_Click(object sender, EventArgs e) private void buttonAuto_Click(object sender, EventArgs e)
@ -134,14 +140,12 @@ namespace BizHawk.Client.EmuHawk
{ {
MessageBox.Show(this, "Size must be positive!"); MessageBox.Show(this, "Size must be positive!");
DialogResult = DialogResult.None; DialogResult = DialogResult.None;
return;
} }
} }
catch (FormatException) catch (FormatException)
{ {
MessageBox.Show(this, "Size must be numeric!"); MessageBox.Show(this, "Size must be numeric!");
DialogResult = DialogResult.None; DialogResult = DialogResult.None;
return;
} }
} }
} }

View File

@ -1,6 +1,5 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Text; using System.Text;
using System.IO; using System.IO;
@ -19,6 +18,7 @@ namespace BizHawk.Client.EmuHawk
/// underlying file being written to /// underlying file being written to
/// </summary> /// </summary>
BinaryWriter file; BinaryWriter file;
/// <summary> /// <summary>
/// sequence of files to write to (split on 32 bit limit) /// sequence of files to write to (split on 32 bit limit)
/// </summary> /// </summary>
@ -28,6 +28,7 @@ namespace BizHawk.Client.EmuHawk
/// samplerate in HZ /// samplerate in HZ
/// </summary> /// </summary>
int samplerate; int samplerate;
/// <summary> /// <summary>
/// number of audio channels /// number of audio channels
/// </summary> /// </summary>
@ -46,7 +47,7 @@ namespace BizHawk.Client.EmuHawk
/// <summary> /// <summary>
/// write riff headers to current file /// write riff headers to current file
/// </summary> /// </summary>
void writeheaders() private void writeheaders()
{ {
file.Write(Encoding.ASCII.GetBytes("RIFF")); // ChunkID file.Write(Encoding.ASCII.GetBytes("RIFF")); // ChunkID
file.Write((uint)0); // ChunkSize file.Write((uint)0); // ChunkSize
@ -68,7 +69,7 @@ namespace BizHawk.Client.EmuHawk
/// <summary> /// <summary>
/// seek back to beginning of file and fix header sizes (if possible) /// seek back to beginning of file and fix header sizes (if possible)
/// </summary> /// </summary>
void finalizeheaders() private void finalizeheaders()
{ {
if (numbytes + 36 >= 0x100000000) if (numbytes + 36 >= 0x100000000)
// passed 4G limit, nothing to be done // passed 4G limit, nothing to be done
@ -88,7 +89,7 @@ namespace BizHawk.Client.EmuHawk
/// <summary> /// <summary>
/// close current underlying stream /// close current underlying stream
/// </summary> /// </summary>
void closecurrent() private void closecurrent()
{ {
if (file != null) if (file != null)
{ {
@ -96,13 +97,15 @@ namespace BizHawk.Client.EmuHawk
file.Close(); file.Close();
file.Dispose(); file.Dispose();
} }
file = null; file = null;
} }
/// <summary> /// <summary>
/// open a new underlying stream /// open a new underlying stream
/// </summary> /// </summary>
/// <param name="next"></param> /// <param name="next"></param>
void opencurrent(Stream next) private void opencurrent(Stream next)
{ {
file = new BinaryWriter(next, Encoding.ASCII); file = new BinaryWriter(next, Encoding.ASCII);
numbytes = 0; numbytes = 0;
@ -150,10 +153,12 @@ namespace BizHawk.Client.EmuHawk
/// <summary> /// <summary>
/// checks sampling rate, number of channels for validity /// checks sampling rate, number of channels for validity
/// </summary> /// </summary>
void checkargs() private void checkargs()
{ {
if (samplerate < 1 || numchannels < 1) if (samplerate < 1 || numchannels < 1)
{
throw new ArgumentException("Bad samplerate/numchannels"); throw new ArgumentException("Bad samplerate/numchannels");
}
} }
/// <summary> /// <summary>
@ -186,9 +191,13 @@ namespace BizHawk.Client.EmuHawk
this.numchannels = numchannels; this.numchannels = numchannels;
checkargs(); checkargs();
filechain = ss; filechain = ss;
// advance to first // advance to first
if (!filechain.MoveNext()) if (!filechain.MoveNext())
{
throw new ArgumentException("Iterator was empty!"); throw new ArgumentException("Iterator was empty!");
}
opencurrent(ss.Current); opencurrent(ss.Current);
} }
} }
@ -205,13 +214,15 @@ namespace BizHawk.Client.EmuHawk
public void SetVideoParameters(int width, int height) { } public void SetVideoParameters(int width, int height) { }
public void SetFrame(int frame) { } public void SetFrame(int frame) { }
public bool UsesAudio { get { return true; } } public bool UsesAudio => true;
public bool UsesVideo { get { return false; } }
class WavWriterVToken : IDisposable public bool UsesVideo => false;
private class WavWriterVToken : IDisposable
{ {
public void Dispose() { } public void Dispose() { }
} }
public IDisposable AcquireVideoCodecToken(System.Windows.Forms.IWin32Window hwnd) public IDisposable AcquireVideoCodecToken(System.Windows.Forms.IWin32Window hwnd)
{ {
// don't care // don't care
@ -223,7 +234,9 @@ namespace BizHawk.Client.EmuHawk
this.sampleRate = sampleRate; this.sampleRate = sampleRate;
this.channels = channels; this.channels = channels;
if (bits != 16) if (bits != 16)
{
throw new ArgumentException("Only support 16bit audio!"); 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)
@ -233,20 +246,17 @@ namespace BizHawk.Client.EmuHawk
public void Dispose() public void Dispose()
{ {
if (wavwriter != null) wavwriter?.Dispose();
wavwriter.Dispose();
} }
WavWriter wavwriter = null; private WavWriter wavwriter = null;
int sampleRate = 0; private int sampleRate = 0;
int channels = 0; private int channels = 0;
/// <summary> /// <summary>
/// create a simple wav stream iterator /// create a simple wav stream iterator
/// </summary> /// </summary>
/// <param name="template"></param> private static IEnumerator<Stream> CreateStreamIterator(string template)
/// <returns></returns>
static IEnumerator<Stream> CreateStreamIterator(string template)
{ {
string dir = Path.GetDirectoryName(template); string dir = Path.GetDirectoryName(template);
string baseName = Path.GetFileNameWithoutExtension(template); string baseName = Path.GetFileNameWithoutExtension(template);

View File

@ -1,5 +1,4 @@
using System; using System;
using System.Data;
using System.Linq; using System.Linq;
using System.Windows.Forms; using System.Windows.Forms;

View File

@ -18,6 +18,7 @@
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=StyleCop_002ESA1210/@EntryIndexedValue">DO_NOT_SHOW</s:String> <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=StyleCop_002ESA1210/@EntryIndexedValue">DO_NOT_SHOW</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=AF/@EntryIndexedValue">AF</s:String> <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=AF/@EntryIndexedValue">AF</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=ARGB/@EntryIndexedValue">ARGB</s:String> <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=ARGB/@EntryIndexedValue">ARGB</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=AV/@EntryIndexedValue">AV</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=BMP/@EntryIndexedValue">BMP</s:String> <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=BMP/@EntryIndexedValue">BMP</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=CDL/@EntryIndexedValue">CDL</s:String> <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=CDL/@EntryIndexedValue">CDL</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=CGB/@EntryIndexedValue">CGB</s:String> <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=CGB/@EntryIndexedValue">CGB</s:String>