misc code cleanups in AV code
This commit is contained in:
parent
88348f03fa
commit
912a2d7346
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
//
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
//
|
||||
|
|
|
@ -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";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
using System;
|
||||
using System.Data;
|
||||
using System.Linq;
|
||||
using System.Windows.Forms;
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Reference in New Issue