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

View File

@ -2,30 +2,31 @@
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using BizHawk.Client.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
{
[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.")]
class AviWriter : IVideoWriter
internal class AviWriter : IVideoWriter
{
CodecToken currVideoCodecToken = null;
AviWriterSegment currSegment;
IEnumerator<string> nameProvider;
private CodecToken _currVideoCodecToken = null;
private AviWriterSegment _currSegment;
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()
{
if (currSegment != null)
currSegment.Dispose();
_currSegment?.Dispose();
}
/// <summary>
@ -34,9 +35,13 @@ namespace BizHawk.Client.EmuHawk
public void SetVideoCodecToken(IDisposable token)
{
if (token is CodecToken)
currVideoCodecToken = (CodecToken)token;
{
_currVideoCodecToken = (CodecToken)token;
}
else
{
throw new ArgumentException("AviWriter only takes its own Codec Tokens!");
}
}
public static IEnumerator<string> CreateBasicNameProvider(string template)
@ -46,7 +51,7 @@ namespace BizHawk.Client.EmuHawk
string ext = Path.GetExtension(template);
yield return template;
int counter = 1;
for (; ; )
for (;;)
{
yield return Path.Combine(dir, baseName) + "_" + counter + ext;
counter++;
@ -57,60 +62,69 @@ namespace BizHawk.Client.EmuHawk
/// opens an avi file for recording with names based on the supplied template.
/// set a video codec token first.
/// </summary>
public void OpenFile(string baseName) { OpenFile(CreateBasicNameProvider(baseName)); }
public void OpenFile(string baseName)
{
OpenFile(CreateBasicNameProvider(baseName));
}
// thread communication
// 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
System.Collections.Concurrent.BlockingCollection<Object> threadQ;
System.Collections.Concurrent.BlockingCollection<object> threadQ;
System.Threading.Thread workerT;
void threadproc()
private void threadproc()
{
try
{
while (true)
{
Object o = threadQ.Take();
object o = threadQ.Take();
if (o is IVideoProvider)
{
AddFrameEx((IVideoProvider)o);
}
else if (o is short[])
{
AddSamplesEx((short[])o);
}
else
{
// anything else is assumed to be quit time
return;
}
}
}
catch (Exception e)
{
System.Windows.Forms.MessageBox.Show("AVIFIL32 Thread died:\n\n" + e);
return;
MessageBox.Show("AVIFIL32 Thread died:\n\n" + e);
}
}
// 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 we pass this
class VideoCopy : IVideoProvider
private class VideoCopy : IVideoProvider
{
int[] vb;
public int VirtualWidth { get; private set; }
public int VirtualHeight { get; private set; }
public int BufferWidth { get; private set; }
public int BufferHeight { get; private set; }
public int BackgroundColor { get; private set; }
private readonly int[] _vb;
public int VirtualWidth { get; }
public int VirtualHeight { get; }
public int BufferWidth { get; }
public int BufferHeight { get; }
public int BackgroundColor { get; }
public VideoCopy(IVideoProvider c)
{
vb = (int[])c.GetVideoBuffer().Clone();
_vb = (int[])c.GetVideoBuffer().Clone();
BufferWidth = c.BufferWidth;
BufferHeight= c.BufferHeight;
BufferHeight = c.BufferHeight;
BackgroundColor = c.BackgroundColor;
VirtualWidth = c.VirtualWidth;
VirtualHeight = c.VirtualHeight;
}
public int[] GetVideoBuffer()
{
return vb;
return _vb;
}
}
@ -122,9 +136,11 @@ namespace BizHawk.Client.EmuHawk
/// <param name="nameProvider"></param>
public void OpenFile(IEnumerator<string> nameProvider)
{
this.nameProvider = nameProvider;
if (currVideoCodecToken == null)
_nameProvider = nameProvider;
if (_currVideoCodecToken == null)
{
throw new InvalidOperationException("Tried to start recording an AVI with no video codec token set");
}
threadQ = new System.Collections.Concurrent.BlockingCollection<Object>(30);
workerT = new System.Threading.Thread(new System.Threading.ThreadStart(threadproc));
@ -133,11 +149,10 @@ namespace BizHawk.Client.EmuHawk
public void CloseFile()
{
threadQ.Add(new Object()); // acts as stop message
threadQ.Add(new object()); // acts as stop message
workerT.Join();
if (currSegment != null)
currSegment.Dispose();
currSegment = null;
_currSegment?.Dispose();
_currSegment = null;
}
public void AddFrame(IVideoProvider source)
@ -145,15 +160,22 @@ namespace BizHawk.Client.EmuHawk
while (!threadQ.TryAdd(new VideoCopy(source), 1000))
{
if (!workerT.IsAlive)
{
throw new Exception("AVI Worker thread died!");
}
}
}
void AddFrameEx(IVideoProvider source)
private void AddFrameEx(IVideoProvider source)
{
SetVideoParameters(source.BufferWidth, source.BufferHeight);
ConsiderLengthSegment();
if (currSegment == null) Segment();
currSegment.AddFrame(source);
if (_currSegment == null)
{
Segment();
}
_currSegment.AddFrame(source);
}
public void AddSamples(short[] samples)
@ -163,78 +185,102 @@ namespace BizHawk.Client.EmuHawk
while (!threadQ.TryAdd((short[])samples.Clone(), 1000))
{
if (!workerT.IsAlive)
{
throw new Exception("AVI Worker thread died!");
}
}
}
void AddSamplesEx(short[] samples)
private void AddSamplesEx(short[] samples)
{
ConsiderLengthSegment();
if (currSegment == null) Segment();
currSegment.AddSamples(samples);
if (_currSegment == null)
{
Segment();
}
_currSegment.AddSamples(samples);
}
void ConsiderLengthSegment()
private void ConsiderLengthSegment()
{
if (currSegment == null) return;
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();
if (_currSegment == null)
{
return;
}
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();
}
else
currSegment.Dispose();
currSegment = new AviWriterSegment();
nameProvider.MoveNext();
currSegment.OpenFile(nameProvider.Current, parameters, currVideoCodecToken);
{
_currSegment.Dispose();
}
_currSegment = new AviWriterSegment();
_nameProvider.MoveNext();
_currSegment.OpenFile(_nameProvider.Current, parameters, _currVideoCodecToken);
try
{
currSegment.OpenStreams();
_currSegment.OpenStreams();
}
catch // will automatically try again with 32 bit
{
currSegment.OpenStreams();
_currSegment.OpenStreams();
}
}
/// <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
/// </summary>
public IDisposable AcquireVideoCodecToken(System.Windows.Forms.IWin32Window hwnd) //, CodecToken lastToken)
public IDisposable AcquireVideoCodecToken(IWin32Window hwnd)
{
var temp_params = new Parameters();
temp_params.height = 256;
temp_params.width = 256;
temp_params.fps = 60;
temp_params.fps_scale = 1;
temp_params.a_bits = 16;
temp_params.a_samplerate = 44100;
temp_params.a_channels = 2;
var tempParams = new Parameters
{
height = 256,
width = 256,
fps = 60,
fps_scale = 1,
a_bits = 16,
a_samplerate = 44100,
a_channels = 2
};
var temp = new AviWriterSegment();
string tempfile = Path.GetTempFileName();
File.Delete(tempfile);
tempfile = Path.ChangeExtension(tempfile, "avi");
temp.OpenFile(tempfile, temp_params, null); //lastToken);
CodecToken token = (CodecToken)temp.AcquireVideoCodecToken(hwnd.Handle, currVideoCodecToken);
temp.OpenFile(tempfile, tempParams, null);
CodecToken token = (CodecToken)temp.AcquireVideoCodecToken(hwnd.Handle, _currVideoCodecToken);
temp.CloseFile();
File.Delete(tempfile);
return token;
}
class Parameters
private class Parameters
{
public int width, height;
public int pitch; //in bytes
@ -245,10 +291,11 @@ namespace BizHawk.Client.EmuHawk
bmih.biPlanes = 1;
bmih.biBitCount = 24;
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 = (pitch + 3) & ~3;
pitch_add = pitch - width * 3;
pitch_add = pitch - (width * 3);
bmih.biWidth = width;
bmih.biSizeImage = (uint)(pitch * height);
}
@ -287,7 +334,8 @@ namespace BizHawk.Client.EmuHawk
public int fps, fps_scale;
}
Parameters parameters = new Parameters();
private readonly Parameters parameters = new Parameters();
/// <summary>
@ -303,7 +351,10 @@ namespace BizHawk.Client.EmuHawk
change |= parameters.fps_scale != fps_scale;
parameters.fps_scale = fps_scale;
if (change) Segment();
if (change)
{
Segment();
}
}
/// <summary>
@ -319,7 +370,10 @@ namespace BizHawk.Client.EmuHawk
change |= parameters.height != height;
parameters.height = height;
if (change) Segment();
if (change)
{
Segment();
}
}
/// <summary>
@ -341,7 +395,10 @@ namespace BizHawk.Client.EmuHawk
change |= parameters.has_audio != true;
parameters.has_audio = true;
if (change) Segment();
if (change)
{
Segment();
}
}
public class CodecToken : IDisposable
@ -355,28 +412,41 @@ namespace BizHawk.Client.EmuHawk
public static CodecToken CreateFromAVICOMPRESSOPTIONS(ref Win32.AVICOMPRESSOPTIONS opts)
{
CodecToken ret = new CodecToken();
ret.comprOptions = opts;
ret.codec = Win32.decode_mmioFOURCC(opts.fccHandler);
ret.Format = new byte[opts.cbFormat];
ret.Parms = new byte[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);
var ret = new CodecToken
{
comprOptions = opts,
codec = Win32.decode_mmioFOURCC(opts.fccHandler),
Format = new byte[opts.cbFormat],
Parms = new byte[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;
}
[DllImport("kernel32.dll", SetLastError = true)]
public static extern bool HeapFree(IntPtr hHeap, uint dwFlags, IntPtr lpMem);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern IntPtr GetProcessHeap();
[DllImport("kernel32.dll", SetLastError = false)]
public static extern IntPtr HeapAlloc(IntPtr hHeap, uint dwFlags, int dwBytes);
public static void DeallocateAVICOMPRESSOPTIONS(ref Win32.AVICOMPRESSOPTIONS opts)
{
//test: increase stability by never freeing anything, ever
//if (opts.lpParms != IntPtr.Zero) CodecToken.HeapFree(CodecToken.GetProcessHeap(), 0, opts.lpParms);
//if (opts.lpFormat != IntPtr.Zero) CodecToken.HeapFree(CodecToken.GetProcessHeap(), 0, opts.lpFormat);
// test: increase stability by never freeing anything, ever
// if (opts.lpParms != IntPtr.Zero) CodecToken.HeapFree(CodecToken.GetProcessHeap(), 0, opts.lpParms);
// if (opts.lpFormat != IntPtr.Zero) CodecToken.HeapFree(CodecToken.GetProcessHeap(), 0, opts.lpFormat);
opts.lpParms = IntPtr.Zero;
opts.lpFormat = IntPtr.Zero;
}
@ -396,7 +466,7 @@ namespace BizHawk.Client.EmuHawk
}
}
byte[] SerializeToByteArray()
private byte[] SerializeToByteArray()
{
var m = new MemoryStream();
var b = new BinaryWriter(m);
@ -418,7 +488,7 @@ namespace BizHawk.Client.EmuHawk
return m.ToArray();
}
static CodecToken DeSerializeFromByteArray(byte[] data)
private static CodecToken DeSerializeFromByteArray(byte[] data)
{
var m = new MemoryStream(data, false);
var b = new BinaryReader(m);
@ -456,11 +526,13 @@ namespace BizHawk.Client.EmuHawk
b.Close();
}
CodecToken ret = new CodecToken();
ret.comprOptions = comprOptions;
ret.Format = Format;
ret.Parms = Parms;
ret.codec = Win32.decode_mmioFOURCC(comprOptions.fccHandler);
var ret = new CodecToken
{
comprOptions = comprOptions,
Format = Format,
Parms = Parms,
codec = Win32.decode_mmioFOURCC(comprOptions.fccHandler)
};
return ret;
}
@ -499,31 +571,36 @@ namespace BizHawk.Client.EmuHawk
CloseFile();
}
CodecToken currVideoCodecToken = null;
bool IsOpen;
IntPtr pAviFile, pAviRawVideoStream, pAviRawAudioStream, pAviCompressedVideoStream;
IntPtr pGlobalBuf;
int pGlobalBuf_size;
/// <summary>are we sending 32 bit RGB to avi or 24?</summary>
bool bit32 = false;
private CodecToken currVideoCodecToken = null;
private bool IsOpen;
private IntPtr pAviFile, pAviRawVideoStream, pAviRawAudioStream, pAviCompressedVideoStream;
private IntPtr pGlobalBuf;
private int pGlobalBuf_size;
// are we sending 32 bit RGB to avi or 24?
private bool bit32 = false;
/// <summary>
/// there is just ony global buf. this gets it and makes sure its big enough. don't get all re-entrant on it!
/// </summary>
IntPtr GetStaticGlobalBuf(int amount)
private IntPtr GetStaticGlobalBuf(int amount)
{
if (amount > pGlobalBuf_size)
{
if (pGlobalBuf != IntPtr.Zero)
{
Marshal.FreeHGlobal(pGlobalBuf);
}
pGlobalBuf_size = amount;
pGlobalBuf = Marshal.AllocHGlobal(pGlobalBuf_size);
}
return pGlobalBuf;
}
class OutputStatus
private class OutputStatus
{
public int video_frames;
public int video_bytes;
@ -533,9 +610,13 @@ namespace BizHawk.Client.EmuHawk
public const int AUDIO_SEGMENT_SIZE = 44100 * 2;
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)
{
@ -554,16 +635,20 @@ namespace BizHawk.Client.EmuHawk
this.parameters = parameters;
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))
{
File.Delete(destPath);
}
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);
}
//initialize the video stream
// initialize the video stream
Win32.AVISTREAMINFOW vidstream_header = new Win32.AVISTREAMINFOW();
Win32.BITMAPINFOHEADER bmih = new Win32.BITMAPINFOHEADER();
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");
}
//initialize audio stream
// initialize audio stream
Win32.AVISTREAMINFOW audstream_header = new Win32.AVISTREAMINFOW();
Win32.WAVEFORMATEX wfex = new Win32.WAVEFORMATEX();
parameters.PopulateWAVEFORMATEX(ref wfex);
@ -603,39 +688,40 @@ namespace BizHawk.Client.EmuHawk
/// </summary>
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)
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;
CodecToken ret = CodecToken.CreateFromAVICOMPRESSOPTIONS(ref comprOptions);
//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
//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
//1. ones we allocated a minute ago
//2. ones VFW allocated
//guess what? doesn't matter. We'll free them all ourselves.
// 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
// 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
// 1. ones we allocated a minute ago
// 2. ones VFW allocated
// guess what? doesn't matter. We'll free them all ourselves.
CodecToken.DeallocateAVICOMPRESSOPTIONS(ref comprOptions);
if(result)
if (result)
{
// save to config and return it
Global.Config.AVICodecToken = ret.Serialize();
return ret;
}
else
return null;
return null;
}
/// <summary>
@ -644,26 +730,33 @@ namespace BizHawk.Client.EmuHawk
public void OpenStreams()
{
if (currVideoCodecToken == null)
{
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();
currVideoCodecToken.AllocateToAVICOMPRESSOPTIONS(ref opts);
bool failed = Win32.FAILED(Win32.AVIMakeCompressedStream(out pAviCompressedVideoStream, pAviRawVideoStream, ref opts, IntPtr.Zero));
CodecToken.DeallocateAVICOMPRESSOPTIONS(ref opts);
if(failed)
if (failed)
{
CloseStreams();
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();
if (bit32)
{
parameters.PopulateBITMAPINFOHEADER32(ref bmih);
}
else
{
parameters.PopulateBITMAPINFOHEADER24(ref bmih);
}
if (Win32.FAILED(Win32.AVIStreamSetFormat(pAviCompressedVideoStream, 0, ref bmih, Marshal.SizeOf(bmih))))
{
bit32 = true; // we'll try again
@ -671,7 +764,7 @@ namespace BizHawk.Client.EmuHawk
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();
parameters.PopulateWAVEFORMATEX(ref wfex);
if (Win32.FAILED(Win32.AVIStreamSetFormat(pAviRawAudioStream, 0, ref wfex, Marshal.SizeOf(wfex))))
@ -682,7 +775,7 @@ namespace BizHawk.Client.EmuHawk
}
/// <summary>
/// wrap up the avi writing
/// wrap up the AVI writing
/// </summary>
public void CloseFile()
{
@ -692,16 +785,19 @@ namespace BizHawk.Client.EmuHawk
Win32.AVIStreamRelease(pAviRawAudioStream);
pAviRawAudioStream = IntPtr.Zero;
}
if (pAviRawVideoStream != IntPtr.Zero)
{
Win32.AVIStreamRelease(pAviRawVideoStream);
pAviRawVideoStream = IntPtr.Zero;
}
if (pAviFile != IntPtr.Zero)
{
Win32.AVIFileRelease(pAviFile);
pAviFile = IntPtr.Zero;
}
if (pGlobalBuf != IntPtr.Zero)
{
Marshal.FreeHGlobal(pGlobalBuf);
@ -716,7 +812,10 @@ namespace BizHawk.Client.EmuHawk
public void CloseStreams()
{
if (pAviRawAudioStream != IntPtr.Zero)
{
FlushBufferedAudio();
}
if (pAviCompressedVideoStream != IntPtr.Zero)
{
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)
{
int todo = samples.Length;
@ -734,11 +833,15 @@ namespace BizHawk.Client.EmuHawk
int remain = OutputStatus.AUDIO_SEGMENT_SIZE - outStatus.audio_buffered_shorts;
int chunk = Math.Min(remain, todo);
for (int i = 0; i < chunk; i++)
{
outStatus.BufferedShorts[outStatus.audio_buffered_shorts++] = samples[idx++];
}
todo -= chunk;
if (outStatus.audio_buffered_shorts == OutputStatus.AUDIO_SEGMENT_SIZE)
{
FlushBufferedAudio();
}
}
}
@ -753,7 +856,8 @@ namespace BizHawk.Client.EmuHawk
{
sptr[i] = outStatus.BufferedShorts[i];
}
//(TODO - inefficient- build directly in a buffer)
// (TODO - inefficient- build directly in a buffer)
int 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;
@ -777,7 +881,7 @@ namespace BizHawk.Client.EmuHawk
{
IntPtr buf = GetStaticGlobalBuf(todo);
//TODO - would using a byte* be faster?
// TODO - would using a byte* be faster?
int[] buffer = source.GetVideoBuffer();
fixed (int* buffer_ptr = &buffer[0])
{
@ -846,8 +950,11 @@ namespace BizHawk.Client.EmuHawk
{
CodecToken ct = CodecToken.DeSerialize(Global.Config.AVICodecToken);
if (ct == null)
{
throw new Exception("No default AVICodecToken in config!");
currVideoCodecToken = ct;
}
_currVideoCodecToken = ct;
}
public string DesiredExtension()
@ -855,8 +962,9 @@ namespace BizHawk.Client.EmuHawk
return "avi";
}
public bool UsesAudio { get { return parameters.has_audio; } }
public bool UsesVideo { get { return true; } }
public bool UsesAudio => parameters.has_audio;
public bool UsesVideo => true;
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,10 +1,4 @@
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 BizHawk.Client.Common;
@ -24,21 +18,22 @@ namespace BizHawk.Client.EmuHawk
{
dlg.numericUpDown1.Value = Global.Config.GifWriterFrameskip;
dlg.numericUpDown2.Value = Global.Config.GifWriterDelay;
dlg.numericUpDown2_ValueChanged(null, null);
dlg.NumericUpDown2_ValueChanged(null, null);
var result = dlg.ShowDialog(parent);
if (result == DialogResult.OK)
{
Global.Config.GifWriterFrameskip = (int)dlg.numericUpDown1.Value;
Global.Config.GifWriterDelay = (int)dlg.numericUpDown2.Value;
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)
{
@ -50,7 +45,7 @@ namespace BizHawk.Client.EmuHawk
}
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.Collections;
using System.Collections.Generic;
using BizHawk.Emulation.Common;
@ -55,7 +54,7 @@ namespace BizHawk.Client.EmuHawk
/// <summary>
/// adds audio samples to the stream
/// 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>
void AddSamples(short[] samples);
@ -78,8 +77,6 @@ namespace BizHawk.Client.EmuHawk
/// can be changed in future
/// should always match IVideoProvider
/// </summary>
/// <param name="width"></param>
/// <param name="height"></param>
void SetVideoParameters(int width, int height);
/// <summary>
@ -134,15 +131,15 @@ namespace BizHawk.Client.EmuHawk
[AttributeUsage(AttributeTargets.Class)]
public class VideoWriterAttribute : Attribute
{
public string ShortName { get; private set; }
public string Name { get; private set; }
public string Description { get; private set; }
public string ShortName { get; }
public string Name { get; }
public string Description { get; }
public VideoWriterAttribute(string ShortName, string Name, string Description)
public VideoWriterAttribute(string shortName, string name, string description)
{
this.ShortName = ShortName;
this.Name = Name;
this.Description = Description;
ShortName = shortName;
Name = name;
Description = description;
}
}
@ -153,18 +150,18 @@ namespace BizHawk.Client.EmuHawk
public class VideoWriterInfo
{
public VideoWriterAttribute Attribs { get; private set; }
private Type type;
public VideoWriterAttribute Attribs { get; }
private Type _type;
public VideoWriterInfo(VideoWriterAttribute Attribs, Type type)
public VideoWriterInfo(VideoWriterAttribute attribs, Type type)
{
this.type = type;
this.Attribs = Attribs;
_type = type;
Attribs = attribs;
}
public IVideoWriter Create()
{
return (IVideoWriter)Activator.CreateInstance(type);
return (IVideoWriter)Activator.CreateInstance(_type);
}
public override string ToString()
@ -203,15 +200,15 @@ namespace BizHawk.Client.EmuHawk
/// <summary>
/// find an IVideoWriter by its short name
/// </summary>
/// <param name="name"></param>
/// <returns></returns>
public static IVideoWriter GetVideoWriter(string name)
{
VideoWriterInfo ret;
if (vws.TryGetValue(name, out ret))
{
return ret.Create();
else
return null;
}
return null;
}
}
}

View File

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

View File

@ -1,10 +1,4 @@
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;
namespace BizHawk.Client.EmuHawk
@ -31,15 +25,14 @@ namespace BizHawk.Client.EmuHawk
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)
{
if (compressionBar.Value == compressionBar.Minimum)
compressionTop.Text = "Compression Level: NONE";
else
compressionTop.Text = String.Format("Compression Level: {0}", compressionBar.Value);
compressionTop.Text = compressionBar.Value == compressionBar.Minimum
? "Compression Level: NONE"
: $"Compression Level: {compressionBar.Value}";
}
/// <summary>
@ -64,10 +57,10 @@ namespace BizHawk.Client.EmuHawk
j.compressionBar.Value = complevel;
j.threadsBar_Scroll(null, null);
j.compressionBar_Scroll(null, null);
j.threadLeft.Text = String.Format("{0}", tmin);
j.threadRight.Text = String.Format("{0}", tmax);
j.compressionLeft.Text = String.Format("{0}", cmin);
j.compressionRight.Text = String.Format("{0}", cmax);
j.threadLeft.Text = $"{tmin}";
j.threadRight.Text = $"{tmax}";
j.compressionLeft.Text = $"{cmin}";
j.compressionRight.Text = $"{cmax}";
DialogResult d = j.ShowDialog(hwnd);
@ -76,9 +69,11 @@ namespace BizHawk.Client.EmuHawk
j.Dispose();
if (d == DialogResult.OK)
{
return true;
else
return false;
}
return false;
}

View File

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

View File

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

View File

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

View File

@ -1,14 +1,9 @@
using System;
using System.IO;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using BizHawk.Emulation;
using BizHawk.Emulation.Common;
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.")]
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)
{
mCurrFrame = frame;
}
int mCurrFrame;
string mBaseDirectory, mFramesDirectory;
string mProjectFile;
private int mCurrFrame;
private string mBaseDirectory, mFramesDirectory;
private string mProjectFile;
public void OpenFile(string projFile)
{
mProjectFile = projFile;
@ -44,7 +46,9 @@ namespace BizHawk.Client.EmuHawk
File.WriteAllText(mProjectFile, sb.ToString());
}
public void CloseFile() { }
public void CloseFile()
{
}
public void AddFrame(IVideoProvider source)
{
@ -68,12 +72,21 @@ namespace BizHawk.Client.EmuHawk
wwv.Dispose();
}
public bool UsesAudio { get { return true; } }
public bool UsesVideo { get { return true; } }
public bool UsesAudio => 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)
{
@ -82,10 +95,10 @@ namespace BizHawk.Client.EmuHawk
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)
{
@ -96,33 +109,42 @@ namespace BizHawk.Client.EmuHawk
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>
/// splits the string into chunks of length s
/// </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;
List<string> output = new List<string>(numChunks);
var output = new List<string>(numChunks);
for (int i = 0, j = 0; i < numChunks; i++, j += len)
{
int todo = len;
int remain = s.Length - j;
if (remain < todo) todo = remain;
if (remain < todo)
{
todo = remain;
}
output.Add(s.Substring(j, todo));
}
return output;
}
string GetAndCreatePathForFrameNum(int index)
private string GetAndCreatePathForFrameNum(int index)
{
string subpath = GetPathFragmentForFrameNum(index);
string path = mFramesDirectory;
@ -139,6 +161,4 @@ namespace BizHawk.Client.EmuHawk
return subpath;
}
}
}

View File

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

View File

@ -1,10 +1,5 @@
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 BizHawk.Client.Common;
@ -16,7 +11,7 @@ namespace BizHawk.Client.EmuHawk
/// </summary>
public partial class VideoWriterChooserForm : Form
{
VideoWriterChooserForm()
private VideoWriterChooserForm()
{
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)
{
lblResolutionWarning.Visible = true;
else lblResolutionWarning.Visible = false;
}
else
{
lblResolutionWarning.Visible = false;
}
}
int CaptureWidth, CaptureHeight;
private int CaptureWidth, CaptureHeight;
/// <summary>
/// chose an IVideoWriter
@ -49,27 +49,32 @@ namespace BizHawk.Client.EmuHawk
/// <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)
{
VideoWriterChooserForm dlg = new VideoWriterChooserForm();
dlg.labelDescriptionBody.Text = "";
VideoWriterChooserForm dlg = new VideoWriterChooserForm
{
int idx = 0;
int idx_select = -1;
dlg.listBox1.BeginUpdate();
foreach (var vw in list)
labelDescriptionBody = { Text = string.Empty }
};
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);
if (vw.Attribs.ShortName == Global.Config.VideoWriter)
idx_select = idx;
idx++;
idx_select = 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)
{
c.Enabled = false;
}
DialogResult result = dlg.ShowDialog(owner);
@ -106,16 +111,17 @@ namespace BizHawk.Client.EmuHawk
private void listBox1_SelectedIndexChanged(object sender, EventArgs e)
{
if (listBox1.SelectedIndex != -1)
labelDescriptionBody.Text = ((VideoWriterInfo)listBox1.SelectedItem).Attribs.Description;
else
labelDescriptionBody.Text = "";
labelDescriptionBody.Text = listBox1.SelectedIndex != -1
? ((VideoWriterInfo)listBox1.SelectedItem).Attribs.Description
: string.Empty;
}
private void checkBoxResize_CheckedChanged(object sender, EventArgs e)
{
foreach (Control c in panelSizeSelect.Controls)
{
c.Enabled = checkBoxResize.Checked;
}
}
private void buttonAuto_Click(object sender, EventArgs e)
@ -134,14 +140,12 @@ namespace BizHawk.Client.EmuHawk
{
MessageBox.Show(this, "Size must be positive!");
DialogResult = DialogResult.None;
return;
}
}
catch (FormatException)
{
MessageBox.Show(this, "Size must be numeric!");
DialogResult = DialogResult.None;
return;
}
}
}

View File

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

View File

@ -1,5 +1,4 @@
using System;
using System.Data;
using System.Linq;
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/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/=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/=CDL/@EntryIndexedValue">CDL</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=CGB/@EntryIndexedValue">CGB</s:String>