misc code cleanups in AV code
This commit is contained in:
parent
88348f03fa
commit
912a2d7346
|
@ -1,7 +1,4 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
using BizHawk.Emulation.Common;
|
using BizHawk.Emulation.Common;
|
||||||
|
|
||||||
|
@ -64,7 +61,9 @@ namespace BizHawk.Client.EmuHawk
|
||||||
private void VerifyParams()
|
private void VerifyParams()
|
||||||
{
|
{
|
||||||
if (!aset || !vset)
|
if (!aset || !vset)
|
||||||
|
{
|
||||||
throw new InvalidOperationException("Must set params first!");
|
throw new InvalidOperationException("Must set params first!");
|
||||||
|
}
|
||||||
|
|
||||||
if (!pset)
|
if (!pset)
|
||||||
{
|
{
|
||||||
|
@ -93,7 +92,6 @@ namespace BizHawk.Client.EmuHawk
|
||||||
|
|
||||||
// todo: scan for duplicate frames (ie, video content exactly matches previous frame) and for them, skip the threshone step
|
// todo: scan for duplicate frames (ie, video content exactly matches previous frame) and for them, skip the threshone step
|
||||||
// this is a good idea, but expensive on time. is it worth it?
|
// this is a good idea, but expensive on time. is it worth it?
|
||||||
|
|
||||||
if (exaudio_num >= threshone)
|
if (exaudio_num >= threshone)
|
||||||
{
|
{
|
||||||
// add frame once
|
// add frame once
|
||||||
|
@ -121,7 +119,9 @@ namespace BizHawk.Client.EmuHawk
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (_samples.Length != samplesprovided * channels)
|
if (_samples.Length != samplesprovided * channels)
|
||||||
|
{
|
||||||
_samples = new short[samplesprovided * channels];
|
_samples = new short[samplesprovided * channels];
|
||||||
|
}
|
||||||
|
|
||||||
Buffer.BlockCopy(samples, 0, _samples, 0, samplesprovided * channels * sizeof(short));
|
Buffer.BlockCopy(samples, 0, _samples, 0, samplesprovided * channels * sizeof(short));
|
||||||
w.AddSamples(_samples);
|
w.AddSamples(_samples);
|
||||||
|
@ -144,7 +144,10 @@ namespace BizHawk.Client.EmuHawk
|
||||||
public new virtual void SetMovieParameters(int fpsnum, int fpsden)
|
public new virtual void SetMovieParameters(int fpsnum, int fpsden)
|
||||||
{
|
{
|
||||||
if (vset)
|
if (vset)
|
||||||
|
{
|
||||||
throw new InvalidOperationException();
|
throw new InvalidOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
vset = true;
|
vset = true;
|
||||||
this.fpsnum = fpsnum;
|
this.fpsnum = fpsnum;
|
||||||
this.fpsden = fpsden;
|
this.fpsden = fpsden;
|
||||||
|
@ -155,9 +158,15 @@ namespace BizHawk.Client.EmuHawk
|
||||||
public new virtual void SetAudioParameters(int sampleRate, int channels, int bits)
|
public new virtual void SetAudioParameters(int sampleRate, int channels, int bits)
|
||||||
{
|
{
|
||||||
if (aset)
|
if (aset)
|
||||||
|
{
|
||||||
throw new InvalidOperationException();
|
throw new InvalidOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
if (bits != 16)
|
if (bits != 16)
|
||||||
|
{
|
||||||
throw new InvalidOperationException("Only 16 bit audio is supported!");
|
throw new InvalidOperationException("Only 16 bit audio is supported!");
|
||||||
|
}
|
||||||
|
|
||||||
aset = true;
|
aset = true;
|
||||||
this.samplerate = sampleRate;
|
this.samplerate = sampleRate;
|
||||||
this.channels = channels;
|
this.channels = channels;
|
||||||
|
@ -187,8 +196,9 @@ namespace BizHawk.Client.EmuHawk
|
||||||
{
|
{
|
||||||
protected IVideoWriter w;
|
protected IVideoWriter w;
|
||||||
|
|
||||||
public bool UsesAudio { get { return w.UsesAudio; } }
|
public bool UsesAudio => w.UsesAudio;
|
||||||
public bool UsesVideo { get { return w.UsesVideo; } }
|
|
||||||
|
public bool UsesVideo => w.UsesVideo;
|
||||||
|
|
||||||
public void SetVideoCodecToken(IDisposable token)
|
public void SetVideoCodecToken(IDisposable token)
|
||||||
{
|
{
|
||||||
|
|
|
@ -2,30 +2,31 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
|
||||||
using BizHawk.Client.Common;
|
using BizHawk.Client.Common;
|
||||||
using BizHawk.Emulation.Common;
|
using BizHawk.Emulation.Common;
|
||||||
|
|
||||||
//some helpful p/invoke from http://www.codeproject.com/KB/audio-video/Motion_Detection.aspx?msg=1142967
|
// some helpful p/invoke from http://www.codeproject.com/KB/audio-video/Motion_Detection.aspx?msg=1142967
|
||||||
|
|
||||||
namespace BizHawk.Client.EmuHawk
|
namespace BizHawk.Client.EmuHawk
|
||||||
{
|
{
|
||||||
[VideoWriter("vfwavi", "AVI writer",
|
[VideoWriter("vfwavi", "AVI writer",
|
||||||
"Uses the Microsoft AVIFIL32 system to write .avi files. Audio is uncompressed; Video can be compressed with any installed VCM codec. Splits on 2G and resolution change.")]
|
"Uses the Microsoft AVIFIL32 system to write .avi files. Audio is uncompressed; Video can be compressed with any installed VCM codec. Splits on 2G and resolution change.")]
|
||||||
class AviWriter : IVideoWriter
|
internal class AviWriter : IVideoWriter
|
||||||
{
|
{
|
||||||
CodecToken currVideoCodecToken = null;
|
private CodecToken _currVideoCodecToken = null;
|
||||||
AviWriterSegment currSegment;
|
private AviWriterSegment _currSegment;
|
||||||
IEnumerator<string> nameProvider;
|
private IEnumerator<string> _nameProvider;
|
||||||
|
|
||||||
public void SetFrame(int frame) { }
|
public void SetFrame(int frame)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
bool IsOpen { get { return nameProvider != null; } }
|
private bool IsOpen => _nameProvider != null;
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
if (currSegment != null)
|
_currSegment?.Dispose();
|
||||||
currSegment.Dispose();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -34,9 +35,13 @@ namespace BizHawk.Client.EmuHawk
|
||||||
public void SetVideoCodecToken(IDisposable token)
|
public void SetVideoCodecToken(IDisposable token)
|
||||||
{
|
{
|
||||||
if (token is CodecToken)
|
if (token is CodecToken)
|
||||||
currVideoCodecToken = (CodecToken)token;
|
{
|
||||||
|
_currVideoCodecToken = (CodecToken)token;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
throw new ArgumentException("AviWriter only takes its own Codec Tokens!");
|
throw new ArgumentException("AviWriter only takes its own Codec Tokens!");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IEnumerator<string> CreateBasicNameProvider(string template)
|
public static IEnumerator<string> CreateBasicNameProvider(string template)
|
||||||
|
@ -46,7 +51,7 @@ namespace BizHawk.Client.EmuHawk
|
||||||
string ext = Path.GetExtension(template);
|
string ext = Path.GetExtension(template);
|
||||||
yield return template;
|
yield return template;
|
||||||
int counter = 1;
|
int counter = 1;
|
||||||
for (; ; )
|
for (;;)
|
||||||
{
|
{
|
||||||
yield return Path.Combine(dir, baseName) + "_" + counter + ext;
|
yield return Path.Combine(dir, baseName) + "_" + counter + ext;
|
||||||
counter++;
|
counter++;
|
||||||
|
@ -57,60 +62,69 @@ namespace BizHawk.Client.EmuHawk
|
||||||
/// opens an avi file for recording with names based on the supplied template.
|
/// opens an avi file for recording with names based on the supplied template.
|
||||||
/// set a video codec token first.
|
/// set a video codec token first.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void OpenFile(string baseName) { OpenFile(CreateBasicNameProvider(baseName)); }
|
public void OpenFile(string baseName)
|
||||||
|
{
|
||||||
|
OpenFile(CreateBasicNameProvider(baseName));
|
||||||
|
}
|
||||||
|
|
||||||
// thread communication
|
// thread communication
|
||||||
// synchronized queue with custom messages
|
// synchronized queue with custom messages
|
||||||
// it seems like there are 99999 ways to do everything in C#, so i'm sure this is not the best
|
// it seems like there are 99999 ways to do everything in C#, so i'm sure this is not the best
|
||||||
System.Collections.Concurrent.BlockingCollection<Object> threadQ;
|
System.Collections.Concurrent.BlockingCollection<object> threadQ;
|
||||||
System.Threading.Thread workerT;
|
System.Threading.Thread workerT;
|
||||||
|
|
||||||
void threadproc()
|
private void threadproc()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
Object o = threadQ.Take();
|
object o = threadQ.Take();
|
||||||
if (o is IVideoProvider)
|
if (o is IVideoProvider)
|
||||||
|
{
|
||||||
AddFrameEx((IVideoProvider)o);
|
AddFrameEx((IVideoProvider)o);
|
||||||
|
}
|
||||||
else if (o is short[])
|
else if (o is short[])
|
||||||
|
{
|
||||||
AddSamplesEx((short[])o);
|
AddSamplesEx((short[])o);
|
||||||
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
// anything else is assumed to be quit time
|
// anything else is assumed to be quit time
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
System.Windows.Forms.MessageBox.Show("AVIFIL32 Thread died:\n\n" + e);
|
MessageBox.Show("AVIFIL32 Thread died:\n\n" + e);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// we can't pass the IVideoProvider we get to another thread, because it doesn't actually keep a local copy of its data,
|
// we can't pass the IVideoProvider we get to another thread, because it doesn't actually keep a local copy of its data,
|
||||||
// instead grabbing it from the emu as needed. this causes frame loss/dupping as a race condition
|
// instead grabbing it from the emu as needed. this causes frame loss/dupping as a race condition
|
||||||
// instead we pass this
|
// instead we pass this
|
||||||
class VideoCopy : IVideoProvider
|
private class VideoCopy : IVideoProvider
|
||||||
{
|
{
|
||||||
int[] vb;
|
private readonly int[] _vb;
|
||||||
public int VirtualWidth { get; private set; }
|
public int VirtualWidth { get; }
|
||||||
public int VirtualHeight { get; private set; }
|
public int VirtualHeight { get; }
|
||||||
public int BufferWidth { get; private set; }
|
public int BufferWidth { get; }
|
||||||
public int BufferHeight { get; private set; }
|
public int BufferHeight { get; }
|
||||||
public int BackgroundColor { get; private set; }
|
public int BackgroundColor { get; }
|
||||||
public VideoCopy(IVideoProvider c)
|
public VideoCopy(IVideoProvider c)
|
||||||
{
|
{
|
||||||
vb = (int[])c.GetVideoBuffer().Clone();
|
_vb = (int[])c.GetVideoBuffer().Clone();
|
||||||
BufferWidth = c.BufferWidth;
|
BufferWidth = c.BufferWidth;
|
||||||
BufferHeight= c.BufferHeight;
|
BufferHeight = c.BufferHeight;
|
||||||
BackgroundColor = c.BackgroundColor;
|
BackgroundColor = c.BackgroundColor;
|
||||||
VirtualWidth = c.VirtualWidth;
|
VirtualWidth = c.VirtualWidth;
|
||||||
VirtualHeight = c.VirtualHeight;
|
VirtualHeight = c.VirtualHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int[] GetVideoBuffer()
|
public int[] GetVideoBuffer()
|
||||||
{
|
{
|
||||||
return vb;
|
return _vb;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -122,9 +136,11 @@ namespace BizHawk.Client.EmuHawk
|
||||||
/// <param name="nameProvider"></param>
|
/// <param name="nameProvider"></param>
|
||||||
public void OpenFile(IEnumerator<string> nameProvider)
|
public void OpenFile(IEnumerator<string> nameProvider)
|
||||||
{
|
{
|
||||||
this.nameProvider = nameProvider;
|
_nameProvider = nameProvider;
|
||||||
if (currVideoCodecToken == null)
|
if (_currVideoCodecToken == null)
|
||||||
|
{
|
||||||
throw new InvalidOperationException("Tried to start recording an AVI with no video codec token set");
|
throw new InvalidOperationException("Tried to start recording an AVI with no video codec token set");
|
||||||
|
}
|
||||||
|
|
||||||
threadQ = new System.Collections.Concurrent.BlockingCollection<Object>(30);
|
threadQ = new System.Collections.Concurrent.BlockingCollection<Object>(30);
|
||||||
workerT = new System.Threading.Thread(new System.Threading.ThreadStart(threadproc));
|
workerT = new System.Threading.Thread(new System.Threading.ThreadStart(threadproc));
|
||||||
|
@ -133,11 +149,10 @@ namespace BizHawk.Client.EmuHawk
|
||||||
|
|
||||||
public void CloseFile()
|
public void CloseFile()
|
||||||
{
|
{
|
||||||
threadQ.Add(new Object()); // acts as stop message
|
threadQ.Add(new object()); // acts as stop message
|
||||||
workerT.Join();
|
workerT.Join();
|
||||||
if (currSegment != null)
|
_currSegment?.Dispose();
|
||||||
currSegment.Dispose();
|
_currSegment = null;
|
||||||
currSegment = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AddFrame(IVideoProvider source)
|
public void AddFrame(IVideoProvider source)
|
||||||
|
@ -145,15 +160,22 @@ namespace BizHawk.Client.EmuHawk
|
||||||
while (!threadQ.TryAdd(new VideoCopy(source), 1000))
|
while (!threadQ.TryAdd(new VideoCopy(source), 1000))
|
||||||
{
|
{
|
||||||
if (!workerT.IsAlive)
|
if (!workerT.IsAlive)
|
||||||
|
{
|
||||||
throw new Exception("AVI Worker thread died!");
|
throw new Exception("AVI Worker thread died!");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
void AddFrameEx(IVideoProvider source)
|
|
||||||
|
private void AddFrameEx(IVideoProvider source)
|
||||||
{
|
{
|
||||||
SetVideoParameters(source.BufferWidth, source.BufferHeight);
|
SetVideoParameters(source.BufferWidth, source.BufferHeight);
|
||||||
ConsiderLengthSegment();
|
ConsiderLengthSegment();
|
||||||
if (currSegment == null) Segment();
|
if (_currSegment == null)
|
||||||
currSegment.AddFrame(source);
|
{
|
||||||
|
Segment();
|
||||||
|
}
|
||||||
|
|
||||||
|
_currSegment.AddFrame(source);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AddSamples(short[] samples)
|
public void AddSamples(short[] samples)
|
||||||
|
@ -163,78 +185,102 @@ namespace BizHawk.Client.EmuHawk
|
||||||
while (!threadQ.TryAdd((short[])samples.Clone(), 1000))
|
while (!threadQ.TryAdd((short[])samples.Clone(), 1000))
|
||||||
{
|
{
|
||||||
if (!workerT.IsAlive)
|
if (!workerT.IsAlive)
|
||||||
|
{
|
||||||
throw new Exception("AVI Worker thread died!");
|
throw new Exception("AVI Worker thread died!");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void AddSamplesEx(short[] samples)
|
private void AddSamplesEx(short[] samples)
|
||||||
{
|
{
|
||||||
ConsiderLengthSegment();
|
ConsiderLengthSegment();
|
||||||
if (currSegment == null) Segment();
|
if (_currSegment == null)
|
||||||
currSegment.AddSamples(samples);
|
{
|
||||||
|
Segment();
|
||||||
|
}
|
||||||
|
|
||||||
|
_currSegment.AddSamples(samples);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ConsiderLengthSegment()
|
private void ConsiderLengthSegment()
|
||||||
{
|
{
|
||||||
if (currSegment == null) return;
|
if (_currSegment == null)
|
||||||
long len = currSegment.GetLengthApproximation();
|
{
|
||||||
const long segment_length_limit = 2 * 1000 * 1000 * 1000; //2GB
|
return;
|
||||||
//const long segment_length_limit = 10 * 1000 * 1000; //for testing
|
}
|
||||||
if (len > segment_length_limit) Segment();
|
|
||||||
|
long len = _currSegment.GetLengthApproximation();
|
||||||
|
const long segment_length_limit = 2 * 1000 * 1000 * 1000; // 2GB
|
||||||
|
|
||||||
|
// const long segment_length_limit = 10 * 1000 * 1000; //for testing
|
||||||
|
if (len > segment_length_limit)
|
||||||
|
{
|
||||||
|
Segment();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void StartRecording()
|
private void StartRecording()
|
||||||
{
|
{
|
||||||
//i guess theres nothing to do here
|
// i guess theres nothing to do here
|
||||||
}
|
}
|
||||||
|
|
||||||
void Segment()
|
private void Segment()
|
||||||
{
|
{
|
||||||
if (!IsOpen) return;
|
if (!IsOpen)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (currSegment == null)
|
if (_currSegment == null)
|
||||||
|
{
|
||||||
StartRecording();
|
StartRecording();
|
||||||
|
}
|
||||||
else
|
else
|
||||||
currSegment.Dispose();
|
{
|
||||||
currSegment = new AviWriterSegment();
|
_currSegment.Dispose();
|
||||||
nameProvider.MoveNext();
|
}
|
||||||
currSegment.OpenFile(nameProvider.Current, parameters, currVideoCodecToken);
|
|
||||||
|
_currSegment = new AviWriterSegment();
|
||||||
|
_nameProvider.MoveNext();
|
||||||
|
_currSegment.OpenFile(_nameProvider.Current, parameters, _currVideoCodecToken);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
currSegment.OpenStreams();
|
_currSegment.OpenStreams();
|
||||||
}
|
}
|
||||||
catch // will automatically try again with 32 bit
|
catch // will automatically try again with 32 bit
|
||||||
{
|
{
|
||||||
currSegment.OpenStreams();
|
_currSegment.OpenStreams();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Acquires a video codec configuration from the user. you may save it for future use, but you must dispose of it when youre done with it.
|
/// Acquires a video codec configuration from the user. you may save it for future use, but you must dispose of it when you're done with it.
|
||||||
/// returns null if the user canceled the dialog
|
/// returns null if the user canceled the dialog
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IDisposable AcquireVideoCodecToken(System.Windows.Forms.IWin32Window hwnd) //, CodecToken lastToken)
|
public IDisposable AcquireVideoCodecToken(IWin32Window hwnd)
|
||||||
{
|
{
|
||||||
var temp_params = new Parameters();
|
var tempParams = new Parameters
|
||||||
temp_params.height = 256;
|
{
|
||||||
temp_params.width = 256;
|
height = 256,
|
||||||
temp_params.fps = 60;
|
width = 256,
|
||||||
temp_params.fps_scale = 1;
|
fps = 60,
|
||||||
temp_params.a_bits = 16;
|
fps_scale = 1,
|
||||||
temp_params.a_samplerate = 44100;
|
a_bits = 16,
|
||||||
temp_params.a_channels = 2;
|
a_samplerate = 44100,
|
||||||
|
a_channels = 2
|
||||||
|
};
|
||||||
var temp = new AviWriterSegment();
|
var temp = new AviWriterSegment();
|
||||||
string tempfile = Path.GetTempFileName();
|
string tempfile = Path.GetTempFileName();
|
||||||
File.Delete(tempfile);
|
File.Delete(tempfile);
|
||||||
tempfile = Path.ChangeExtension(tempfile, "avi");
|
tempfile = Path.ChangeExtension(tempfile, "avi");
|
||||||
temp.OpenFile(tempfile, temp_params, null); //lastToken);
|
temp.OpenFile(tempfile, tempParams, null);
|
||||||
CodecToken token = (CodecToken)temp.AcquireVideoCodecToken(hwnd.Handle, currVideoCodecToken);
|
CodecToken token = (CodecToken)temp.AcquireVideoCodecToken(hwnd.Handle, _currVideoCodecToken);
|
||||||
temp.CloseFile();
|
temp.CloseFile();
|
||||||
File.Delete(tempfile);
|
File.Delete(tempfile);
|
||||||
return token;
|
return token;
|
||||||
}
|
}
|
||||||
|
|
||||||
class Parameters
|
private class Parameters
|
||||||
{
|
{
|
||||||
public int width, height;
|
public int width, height;
|
||||||
public int pitch; //in bytes
|
public int pitch; //in bytes
|
||||||
|
@ -245,10 +291,11 @@ namespace BizHawk.Client.EmuHawk
|
||||||
bmih.biPlanes = 1;
|
bmih.biPlanes = 1;
|
||||||
bmih.biBitCount = 24;
|
bmih.biBitCount = 24;
|
||||||
bmih.biHeight = height;
|
bmih.biHeight = height;
|
||||||
//pad up width so that we end up with multiple of 4 bytes
|
|
||||||
|
// pad up width so that we end up with multiple of 4 bytes
|
||||||
pitch = width * 3;
|
pitch = width * 3;
|
||||||
pitch = (pitch + 3) & ~3;
|
pitch = (pitch + 3) & ~3;
|
||||||
pitch_add = pitch - width * 3;
|
pitch_add = pitch - (width * 3);
|
||||||
bmih.biWidth = width;
|
bmih.biWidth = width;
|
||||||
bmih.biSizeImage = (uint)(pitch * height);
|
bmih.biSizeImage = (uint)(pitch * height);
|
||||||
}
|
}
|
||||||
|
@ -287,7 +334,8 @@ namespace BizHawk.Client.EmuHawk
|
||||||
|
|
||||||
public int fps, fps_scale;
|
public int fps, fps_scale;
|
||||||
}
|
}
|
||||||
Parameters parameters = new Parameters();
|
|
||||||
|
private readonly Parameters parameters = new Parameters();
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -303,7 +351,10 @@ namespace BizHawk.Client.EmuHawk
|
||||||
change |= parameters.fps_scale != fps_scale;
|
change |= parameters.fps_scale != fps_scale;
|
||||||
parameters.fps_scale = fps_scale;
|
parameters.fps_scale = fps_scale;
|
||||||
|
|
||||||
if (change) Segment();
|
if (change)
|
||||||
|
{
|
||||||
|
Segment();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -319,7 +370,10 @@ namespace BizHawk.Client.EmuHawk
|
||||||
change |= parameters.height != height;
|
change |= parameters.height != height;
|
||||||
parameters.height = height;
|
parameters.height = height;
|
||||||
|
|
||||||
if (change) Segment();
|
if (change)
|
||||||
|
{
|
||||||
|
Segment();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -341,7 +395,10 @@ namespace BizHawk.Client.EmuHawk
|
||||||
change |= parameters.has_audio != true;
|
change |= parameters.has_audio != true;
|
||||||
parameters.has_audio = true;
|
parameters.has_audio = true;
|
||||||
|
|
||||||
if (change) Segment();
|
if (change)
|
||||||
|
{
|
||||||
|
Segment();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class CodecToken : IDisposable
|
public class CodecToken : IDisposable
|
||||||
|
@ -355,28 +412,41 @@ namespace BizHawk.Client.EmuHawk
|
||||||
|
|
||||||
public static CodecToken CreateFromAVICOMPRESSOPTIONS(ref Win32.AVICOMPRESSOPTIONS opts)
|
public static CodecToken CreateFromAVICOMPRESSOPTIONS(ref Win32.AVICOMPRESSOPTIONS opts)
|
||||||
{
|
{
|
||||||
CodecToken ret = new CodecToken();
|
var ret = new CodecToken
|
||||||
ret.comprOptions = opts;
|
{
|
||||||
ret.codec = Win32.decode_mmioFOURCC(opts.fccHandler);
|
comprOptions = opts,
|
||||||
ret.Format = new byte[opts.cbFormat];
|
codec = Win32.decode_mmioFOURCC(opts.fccHandler),
|
||||||
ret.Parms = new byte[opts.cbParms];
|
Format = new byte[opts.cbFormat],
|
||||||
if (opts.lpFormat != IntPtr.Zero) Marshal.Copy(opts.lpFormat, ret.Format, 0, opts.cbFormat);
|
Parms = new byte[opts.cbParms]
|
||||||
if (opts.lpParms != IntPtr.Zero) Marshal.Copy(opts.lpParms, ret.Parms, 0, opts.cbParms);
|
};
|
||||||
|
|
||||||
|
if (opts.lpFormat != IntPtr.Zero)
|
||||||
|
{
|
||||||
|
Marshal.Copy(opts.lpFormat, ret.Format, 0, opts.cbFormat);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opts.lpParms != IntPtr.Zero)
|
||||||
|
{
|
||||||
|
Marshal.Copy(opts.lpParms, ret.Parms, 0, opts.cbParms);
|
||||||
|
}
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
[DllImport("kernel32.dll", SetLastError = true)]
|
[DllImport("kernel32.dll", SetLastError = true)]
|
||||||
public static extern bool HeapFree(IntPtr hHeap, uint dwFlags, IntPtr lpMem);
|
public static extern bool HeapFree(IntPtr hHeap, uint dwFlags, IntPtr lpMem);
|
||||||
|
|
||||||
[DllImport("kernel32.dll", SetLastError = true)]
|
[DllImport("kernel32.dll", SetLastError = true)]
|
||||||
public static extern IntPtr GetProcessHeap();
|
public static extern IntPtr GetProcessHeap();
|
||||||
|
|
||||||
[DllImport("kernel32.dll", SetLastError = false)]
|
[DllImport("kernel32.dll", SetLastError = false)]
|
||||||
public static extern IntPtr HeapAlloc(IntPtr hHeap, uint dwFlags, int dwBytes);
|
public static extern IntPtr HeapAlloc(IntPtr hHeap, uint dwFlags, int dwBytes);
|
||||||
|
|
||||||
public static void DeallocateAVICOMPRESSOPTIONS(ref Win32.AVICOMPRESSOPTIONS opts)
|
public static void DeallocateAVICOMPRESSOPTIONS(ref Win32.AVICOMPRESSOPTIONS opts)
|
||||||
{
|
{
|
||||||
//test: increase stability by never freeing anything, ever
|
// test: increase stability by never freeing anything, ever
|
||||||
//if (opts.lpParms != IntPtr.Zero) CodecToken.HeapFree(CodecToken.GetProcessHeap(), 0, opts.lpParms);
|
// if (opts.lpParms != IntPtr.Zero) CodecToken.HeapFree(CodecToken.GetProcessHeap(), 0, opts.lpParms);
|
||||||
//if (opts.lpFormat != IntPtr.Zero) CodecToken.HeapFree(CodecToken.GetProcessHeap(), 0, opts.lpFormat);
|
// if (opts.lpFormat != IntPtr.Zero) CodecToken.HeapFree(CodecToken.GetProcessHeap(), 0, opts.lpFormat);
|
||||||
opts.lpParms = IntPtr.Zero;
|
opts.lpParms = IntPtr.Zero;
|
||||||
opts.lpFormat = IntPtr.Zero;
|
opts.lpFormat = IntPtr.Zero;
|
||||||
}
|
}
|
||||||
|
@ -396,7 +466,7 @@ namespace BizHawk.Client.EmuHawk
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
byte[] SerializeToByteArray()
|
private byte[] SerializeToByteArray()
|
||||||
{
|
{
|
||||||
var m = new MemoryStream();
|
var m = new MemoryStream();
|
||||||
var b = new BinaryWriter(m);
|
var b = new BinaryWriter(m);
|
||||||
|
@ -418,7 +488,7 @@ namespace BizHawk.Client.EmuHawk
|
||||||
return m.ToArray();
|
return m.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
static CodecToken DeSerializeFromByteArray(byte[] data)
|
private static CodecToken DeSerializeFromByteArray(byte[] data)
|
||||||
{
|
{
|
||||||
var m = new MemoryStream(data, false);
|
var m = new MemoryStream(data, false);
|
||||||
var b = new BinaryReader(m);
|
var b = new BinaryReader(m);
|
||||||
|
@ -456,11 +526,13 @@ namespace BizHawk.Client.EmuHawk
|
||||||
b.Close();
|
b.Close();
|
||||||
}
|
}
|
||||||
|
|
||||||
CodecToken ret = new CodecToken();
|
var ret = new CodecToken
|
||||||
ret.comprOptions = comprOptions;
|
{
|
||||||
ret.Format = Format;
|
comprOptions = comprOptions,
|
||||||
ret.Parms = Parms;
|
Format = Format,
|
||||||
ret.codec = Win32.decode_mmioFOURCC(comprOptions.fccHandler);
|
Parms = Parms,
|
||||||
|
codec = Win32.decode_mmioFOURCC(comprOptions.fccHandler)
|
||||||
|
};
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -499,31 +571,36 @@ namespace BizHawk.Client.EmuHawk
|
||||||
CloseFile();
|
CloseFile();
|
||||||
}
|
}
|
||||||
|
|
||||||
CodecToken currVideoCodecToken = null;
|
private CodecToken currVideoCodecToken = null;
|
||||||
bool IsOpen;
|
private bool IsOpen;
|
||||||
IntPtr pAviFile, pAviRawVideoStream, pAviRawAudioStream, pAviCompressedVideoStream;
|
private IntPtr pAviFile, pAviRawVideoStream, pAviRawAudioStream, pAviCompressedVideoStream;
|
||||||
IntPtr pGlobalBuf;
|
private IntPtr pGlobalBuf;
|
||||||
int pGlobalBuf_size;
|
private int pGlobalBuf_size;
|
||||||
/// <summary>are we sending 32 bit RGB to avi or 24?</summary>
|
|
||||||
bool bit32 = false;
|
// are we sending 32 bit RGB to avi or 24?
|
||||||
|
private bool bit32 = false;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// there is just ony global buf. this gets it and makes sure its big enough. don't get all re-entrant on it!
|
/// there is just ony global buf. this gets it and makes sure its big enough. don't get all re-entrant on it!
|
||||||
/// </summary>
|
/// </summary>
|
||||||
IntPtr GetStaticGlobalBuf(int amount)
|
private IntPtr GetStaticGlobalBuf(int amount)
|
||||||
{
|
{
|
||||||
if (amount > pGlobalBuf_size)
|
if (amount > pGlobalBuf_size)
|
||||||
{
|
{
|
||||||
if (pGlobalBuf != IntPtr.Zero)
|
if (pGlobalBuf != IntPtr.Zero)
|
||||||
|
{
|
||||||
Marshal.FreeHGlobal(pGlobalBuf);
|
Marshal.FreeHGlobal(pGlobalBuf);
|
||||||
|
}
|
||||||
|
|
||||||
pGlobalBuf_size = amount;
|
pGlobalBuf_size = amount;
|
||||||
pGlobalBuf = Marshal.AllocHGlobal(pGlobalBuf_size);
|
pGlobalBuf = Marshal.AllocHGlobal(pGlobalBuf_size);
|
||||||
}
|
}
|
||||||
|
|
||||||
return pGlobalBuf;
|
return pGlobalBuf;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class OutputStatus
|
private class OutputStatus
|
||||||
{
|
{
|
||||||
public int video_frames;
|
public int video_frames;
|
||||||
public int video_bytes;
|
public int video_bytes;
|
||||||
|
@ -533,9 +610,13 @@ namespace BizHawk.Client.EmuHawk
|
||||||
public const int AUDIO_SEGMENT_SIZE = 44100 * 2;
|
public const int AUDIO_SEGMENT_SIZE = 44100 * 2;
|
||||||
public short[] BufferedShorts = new short[AUDIO_SEGMENT_SIZE];
|
public short[] BufferedShorts = new short[AUDIO_SEGMENT_SIZE];
|
||||||
}
|
}
|
||||||
OutputStatus outStatus;
|
|
||||||
|
|
||||||
public long GetLengthApproximation() { return outStatus.video_bytes + outStatus.audio_bytes; }
|
private OutputStatus outStatus;
|
||||||
|
|
||||||
|
public long GetLengthApproximation()
|
||||||
|
{
|
||||||
|
return outStatus.video_bytes + outStatus.audio_bytes;
|
||||||
|
}
|
||||||
|
|
||||||
static unsafe int AVISaveOptions(IntPtr stream, ref Win32.AVICOMPRESSOPTIONS opts, IntPtr owner)
|
static unsafe int AVISaveOptions(IntPtr stream, ref Win32.AVICOMPRESSOPTIONS opts, IntPtr owner)
|
||||||
{
|
{
|
||||||
|
@ -554,16 +635,20 @@ namespace BizHawk.Client.EmuHawk
|
||||||
this.parameters = parameters;
|
this.parameters = parameters;
|
||||||
this.currVideoCodecToken = videoCodecToken;
|
this.currVideoCodecToken = videoCodecToken;
|
||||||
|
|
||||||
//TODO - try creating the file once first before we let vfw botch it up?
|
// TODO - try creating the file once first before we let vfw botch it up?
|
||||||
|
|
||||||
//open the avi output file handle
|
// open the avi output file handle
|
||||||
if (File.Exists(destPath))
|
if (File.Exists(destPath))
|
||||||
|
{
|
||||||
File.Delete(destPath);
|
File.Delete(destPath);
|
||||||
|
}
|
||||||
|
|
||||||
if (Win32.FAILED(Win32.AVIFileOpenW(ref pAviFile, destPath, Win32.OpenFileStyle.OF_CREATE | Win32.OpenFileStyle.OF_WRITE, 0)))
|
if (Win32.FAILED(Win32.AVIFileOpenW(ref pAviFile, destPath, Win32.OpenFileStyle.OF_CREATE | Win32.OpenFileStyle.OF_WRITE, 0)))
|
||||||
|
{
|
||||||
throw new InvalidOperationException("Couldnt open dest path for avi file: " + destPath);
|
throw new InvalidOperationException("Couldnt open dest path for avi file: " + destPath);
|
||||||
|
}
|
||||||
|
|
||||||
//initialize the video stream
|
// initialize the video stream
|
||||||
Win32.AVISTREAMINFOW vidstream_header = new Win32.AVISTREAMINFOW();
|
Win32.AVISTREAMINFOW vidstream_header = new Win32.AVISTREAMINFOW();
|
||||||
Win32.BITMAPINFOHEADER bmih = new Win32.BITMAPINFOHEADER();
|
Win32.BITMAPINFOHEADER bmih = new Win32.BITMAPINFOHEADER();
|
||||||
parameters.PopulateBITMAPINFOHEADER24(ref bmih);
|
parameters.PopulateBITMAPINFOHEADER24(ref bmih);
|
||||||
|
@ -577,7 +662,7 @@ namespace BizHawk.Client.EmuHawk
|
||||||
throw new InvalidOperationException("Failed opening raw video stream. Not sure how this could happen");
|
throw new InvalidOperationException("Failed opening raw video stream. Not sure how this could happen");
|
||||||
}
|
}
|
||||||
|
|
||||||
//initialize audio stream
|
// initialize audio stream
|
||||||
Win32.AVISTREAMINFOW audstream_header = new Win32.AVISTREAMINFOW();
|
Win32.AVISTREAMINFOW audstream_header = new Win32.AVISTREAMINFOW();
|
||||||
Win32.WAVEFORMATEX wfex = new Win32.WAVEFORMATEX();
|
Win32.WAVEFORMATEX wfex = new Win32.WAVEFORMATEX();
|
||||||
parameters.PopulateWAVEFORMATEX(ref wfex);
|
parameters.PopulateWAVEFORMATEX(ref wfex);
|
||||||
|
@ -603,39 +688,40 @@ namespace BizHawk.Client.EmuHawk
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IDisposable AcquireVideoCodecToken(IntPtr hwnd, CodecToken lastCodecToken)
|
public IDisposable AcquireVideoCodecToken(IntPtr hwnd, CodecToken lastCodecToken)
|
||||||
{
|
{
|
||||||
if (!IsOpen) throw new InvalidOperationException("File must be opened before acquiring a codec token (or else the stream formats wouldnt be known)");
|
if (!IsOpen)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("File must be opened before acquiring a codec token (or else the stream formats wouldnt be known)");
|
||||||
|
}
|
||||||
|
|
||||||
if (lastCodecToken != null)
|
if (lastCodecToken != null)
|
||||||
currVideoCodecToken = lastCodecToken;
|
|
||||||
|
|
||||||
//encoder params
|
|
||||||
Win32.AVICOMPRESSOPTIONS comprOptions = new Win32.AVICOMPRESSOPTIONS();
|
|
||||||
if (currVideoCodecToken != null)
|
|
||||||
{
|
{
|
||||||
currVideoCodecToken.AllocateToAVICOMPRESSOPTIONS(ref comprOptions);
|
currVideoCodecToken = lastCodecToken;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// encoder params
|
||||||
|
Win32.AVICOMPRESSOPTIONS comprOptions = new Win32.AVICOMPRESSOPTIONS();
|
||||||
|
currVideoCodecToken?.AllocateToAVICOMPRESSOPTIONS(ref comprOptions);
|
||||||
|
|
||||||
bool result = AVISaveOptions(pAviRawVideoStream, ref comprOptions, hwnd) != 0;
|
bool result = AVISaveOptions(pAviRawVideoStream, ref comprOptions, hwnd) != 0;
|
||||||
CodecToken ret = CodecToken.CreateFromAVICOMPRESSOPTIONS(ref comprOptions);
|
CodecToken ret = CodecToken.CreateFromAVICOMPRESSOPTIONS(ref comprOptions);
|
||||||
|
|
||||||
//so, AVISaveOptions may have changed some of the pointers
|
// so, AVISaveOptions may have changed some of the pointers
|
||||||
//if it changed the pointers, did it it free the old ones? we don't know
|
// if it changed the pointers, did it it free the old ones? we don't know
|
||||||
//let's assume it frees them. if we're wrong, we leak. if we assume otherwise and we're wrong, we may crash.
|
// let's assume it frees them. if we're wrong, we leak. if we assume otherwise and we're wrong, we may crash.
|
||||||
//so that means any pointers that come in here are either
|
// so that means any pointers that come in here are either
|
||||||
//1. ones we allocated a minute ago
|
// 1. ones we allocated a minute ago
|
||||||
//2. ones VFW allocated
|
// 2. ones VFW allocated
|
||||||
//guess what? doesn't matter. We'll free them all ourselves.
|
// guess what? doesn't matter. We'll free them all ourselves.
|
||||||
CodecToken.DeallocateAVICOMPRESSOPTIONS(ref comprOptions);
|
CodecToken.DeallocateAVICOMPRESSOPTIONS(ref comprOptions);
|
||||||
|
|
||||||
|
if (result)
|
||||||
if(result)
|
|
||||||
{
|
{
|
||||||
// save to config and return it
|
// save to config and return it
|
||||||
Global.Config.AVICodecToken = ret.Serialize();
|
Global.Config.AVICodecToken = ret.Serialize();
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -644,26 +730,33 @@ namespace BizHawk.Client.EmuHawk
|
||||||
public void OpenStreams()
|
public void OpenStreams()
|
||||||
{
|
{
|
||||||
if (currVideoCodecToken == null)
|
if (currVideoCodecToken == null)
|
||||||
|
{
|
||||||
throw new InvalidOperationException("set a video codec token before opening the streams!");
|
throw new InvalidOperationException("set a video codec token before opening the streams!");
|
||||||
|
}
|
||||||
|
|
||||||
//open compressed video stream
|
// open compressed video stream
|
||||||
Win32.AVICOMPRESSOPTIONS opts = new Win32.AVICOMPRESSOPTIONS();
|
Win32.AVICOMPRESSOPTIONS opts = new Win32.AVICOMPRESSOPTIONS();
|
||||||
currVideoCodecToken.AllocateToAVICOMPRESSOPTIONS(ref opts);
|
currVideoCodecToken.AllocateToAVICOMPRESSOPTIONS(ref opts);
|
||||||
bool failed = Win32.FAILED(Win32.AVIMakeCompressedStream(out pAviCompressedVideoStream, pAviRawVideoStream, ref opts, IntPtr.Zero));
|
bool failed = Win32.FAILED(Win32.AVIMakeCompressedStream(out pAviCompressedVideoStream, pAviRawVideoStream, ref opts, IntPtr.Zero));
|
||||||
CodecToken.DeallocateAVICOMPRESSOPTIONS(ref opts);
|
CodecToken.DeallocateAVICOMPRESSOPTIONS(ref opts);
|
||||||
|
|
||||||
if(failed)
|
if (failed)
|
||||||
{
|
{
|
||||||
CloseStreams();
|
CloseStreams();
|
||||||
throw new InvalidOperationException("Failed making compressed video stream");
|
throw new InvalidOperationException("Failed making compressed video stream");
|
||||||
}
|
}
|
||||||
|
|
||||||
//set the compressed video stream input format
|
// set the compressed video stream input format
|
||||||
Win32.BITMAPINFOHEADER bmih = new Win32.BITMAPINFOHEADER();
|
Win32.BITMAPINFOHEADER bmih = new Win32.BITMAPINFOHEADER();
|
||||||
if (bit32)
|
if (bit32)
|
||||||
|
{
|
||||||
parameters.PopulateBITMAPINFOHEADER32(ref bmih);
|
parameters.PopulateBITMAPINFOHEADER32(ref bmih);
|
||||||
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
parameters.PopulateBITMAPINFOHEADER24(ref bmih);
|
parameters.PopulateBITMAPINFOHEADER24(ref bmih);
|
||||||
|
}
|
||||||
|
|
||||||
if (Win32.FAILED(Win32.AVIStreamSetFormat(pAviCompressedVideoStream, 0, ref bmih, Marshal.SizeOf(bmih))))
|
if (Win32.FAILED(Win32.AVIStreamSetFormat(pAviCompressedVideoStream, 0, ref bmih, Marshal.SizeOf(bmih))))
|
||||||
{
|
{
|
||||||
bit32 = true; // we'll try again
|
bit32 = true; // we'll try again
|
||||||
|
@ -671,7 +764,7 @@ namespace BizHawk.Client.EmuHawk
|
||||||
throw new InvalidOperationException("Failed setting compressed video stream input format");
|
throw new InvalidOperationException("Failed setting compressed video stream input format");
|
||||||
}
|
}
|
||||||
|
|
||||||
//set audio stream input format
|
// set audio stream input format
|
||||||
Win32.WAVEFORMATEX wfex = new Win32.WAVEFORMATEX();
|
Win32.WAVEFORMATEX wfex = new Win32.WAVEFORMATEX();
|
||||||
parameters.PopulateWAVEFORMATEX(ref wfex);
|
parameters.PopulateWAVEFORMATEX(ref wfex);
|
||||||
if (Win32.FAILED(Win32.AVIStreamSetFormat(pAviRawAudioStream, 0, ref wfex, Marshal.SizeOf(wfex))))
|
if (Win32.FAILED(Win32.AVIStreamSetFormat(pAviRawAudioStream, 0, ref wfex, Marshal.SizeOf(wfex))))
|
||||||
|
@ -682,7 +775,7 @@ namespace BizHawk.Client.EmuHawk
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// wrap up the avi writing
|
/// wrap up the AVI writing
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void CloseFile()
|
public void CloseFile()
|
||||||
{
|
{
|
||||||
|
@ -692,16 +785,19 @@ namespace BizHawk.Client.EmuHawk
|
||||||
Win32.AVIStreamRelease(pAviRawAudioStream);
|
Win32.AVIStreamRelease(pAviRawAudioStream);
|
||||||
pAviRawAudioStream = IntPtr.Zero;
|
pAviRawAudioStream = IntPtr.Zero;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pAviRawVideoStream != IntPtr.Zero)
|
if (pAviRawVideoStream != IntPtr.Zero)
|
||||||
{
|
{
|
||||||
Win32.AVIStreamRelease(pAviRawVideoStream);
|
Win32.AVIStreamRelease(pAviRawVideoStream);
|
||||||
pAviRawVideoStream = IntPtr.Zero;
|
pAviRawVideoStream = IntPtr.Zero;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pAviFile != IntPtr.Zero)
|
if (pAviFile != IntPtr.Zero)
|
||||||
{
|
{
|
||||||
Win32.AVIFileRelease(pAviFile);
|
Win32.AVIFileRelease(pAviFile);
|
||||||
pAviFile = IntPtr.Zero;
|
pAviFile = IntPtr.Zero;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pGlobalBuf != IntPtr.Zero)
|
if (pGlobalBuf != IntPtr.Zero)
|
||||||
{
|
{
|
||||||
Marshal.FreeHGlobal(pGlobalBuf);
|
Marshal.FreeHGlobal(pGlobalBuf);
|
||||||
|
@ -716,7 +812,10 @@ namespace BizHawk.Client.EmuHawk
|
||||||
public void CloseStreams()
|
public void CloseStreams()
|
||||||
{
|
{
|
||||||
if (pAviRawAudioStream != IntPtr.Zero)
|
if (pAviRawAudioStream != IntPtr.Zero)
|
||||||
|
{
|
||||||
FlushBufferedAudio();
|
FlushBufferedAudio();
|
||||||
|
}
|
||||||
|
|
||||||
if (pAviCompressedVideoStream != IntPtr.Zero)
|
if (pAviCompressedVideoStream != IntPtr.Zero)
|
||||||
{
|
{
|
||||||
Win32.AVIStreamRelease(pAviCompressedVideoStream);
|
Win32.AVIStreamRelease(pAviCompressedVideoStream);
|
||||||
|
@ -724,7 +823,7 @@ namespace BizHawk.Client.EmuHawk
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//todo - why couldnt this take an ISoundProvider? it could do the timekeeping as well.. hmm
|
// todo - why couldnt this take an ISoundProvider? it could do the timekeeping as well.. hmm
|
||||||
public unsafe void AddSamples(short[] samples)
|
public unsafe void AddSamples(short[] samples)
|
||||||
{
|
{
|
||||||
int todo = samples.Length;
|
int todo = samples.Length;
|
||||||
|
@ -734,11 +833,15 @@ namespace BizHawk.Client.EmuHawk
|
||||||
int remain = OutputStatus.AUDIO_SEGMENT_SIZE - outStatus.audio_buffered_shorts;
|
int remain = OutputStatus.AUDIO_SEGMENT_SIZE - outStatus.audio_buffered_shorts;
|
||||||
int chunk = Math.Min(remain, todo);
|
int chunk = Math.Min(remain, todo);
|
||||||
for (int i = 0; i < chunk; i++)
|
for (int i = 0; i < chunk; i++)
|
||||||
|
{
|
||||||
outStatus.BufferedShorts[outStatus.audio_buffered_shorts++] = samples[idx++];
|
outStatus.BufferedShorts[outStatus.audio_buffered_shorts++] = samples[idx++];
|
||||||
|
}
|
||||||
todo -= chunk;
|
todo -= chunk;
|
||||||
|
|
||||||
if (outStatus.audio_buffered_shorts == OutputStatus.AUDIO_SEGMENT_SIZE)
|
if (outStatus.audio_buffered_shorts == OutputStatus.AUDIO_SEGMENT_SIZE)
|
||||||
|
{
|
||||||
FlushBufferedAudio();
|
FlushBufferedAudio();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -753,7 +856,8 @@ namespace BizHawk.Client.EmuHawk
|
||||||
{
|
{
|
||||||
sptr[i] = outStatus.BufferedShorts[i];
|
sptr[i] = outStatus.BufferedShorts[i];
|
||||||
}
|
}
|
||||||
//(TODO - inefficient- build directly in a buffer)
|
|
||||||
|
// (TODO - inefficient- build directly in a buffer)
|
||||||
int bytes_written;
|
int bytes_written;
|
||||||
Win32.AVIStreamWrite(pAviRawAudioStream, outStatus.audio_samples, todo_realsamples, buf, todo_realsamples * 4, 0, IntPtr.Zero, out bytes_written);
|
Win32.AVIStreamWrite(pAviRawAudioStream, outStatus.audio_samples, todo_realsamples, buf, todo_realsamples * 4, 0, IntPtr.Zero, out bytes_written);
|
||||||
outStatus.audio_samples += todo_realsamples;
|
outStatus.audio_samples += todo_realsamples;
|
||||||
|
@ -777,7 +881,7 @@ namespace BizHawk.Client.EmuHawk
|
||||||
{
|
{
|
||||||
IntPtr buf = GetStaticGlobalBuf(todo);
|
IntPtr buf = GetStaticGlobalBuf(todo);
|
||||||
|
|
||||||
//TODO - would using a byte* be faster?
|
// TODO - would using a byte* be faster?
|
||||||
int[] buffer = source.GetVideoBuffer();
|
int[] buffer = source.GetVideoBuffer();
|
||||||
fixed (int* buffer_ptr = &buffer[0])
|
fixed (int* buffer_ptr = &buffer[0])
|
||||||
{
|
{
|
||||||
|
@ -846,8 +950,11 @@ namespace BizHawk.Client.EmuHawk
|
||||||
{
|
{
|
||||||
CodecToken ct = CodecToken.DeSerialize(Global.Config.AVICodecToken);
|
CodecToken ct = CodecToken.DeSerialize(Global.Config.AVICodecToken);
|
||||||
if (ct == null)
|
if (ct == null)
|
||||||
|
{
|
||||||
throw new Exception("No default AVICodecToken in config!");
|
throw new Exception("No default AVICodecToken in config!");
|
||||||
currVideoCodecToken = ct;
|
}
|
||||||
|
|
||||||
|
_currVideoCodecToken = ct;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string DesiredExtension()
|
public string DesiredExtension()
|
||||||
|
@ -855,8 +962,9 @@ namespace BizHawk.Client.EmuHawk
|
||||||
return "avi";
|
return "avi";
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool UsesAudio { get { return parameters.has_audio; } }
|
public bool UsesAudio => parameters.has_audio;
|
||||||
public bool UsesVideo { get { return true; } }
|
|
||||||
|
public bool UsesVideo => true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,4 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
|
|
||||||
using BizHawk.Emulation.Common;
|
using BizHawk.Emulation.Common;
|
||||||
|
@ -9,63 +6,48 @@ using BizHawk.Emulation.Common;
|
||||||
namespace BizHawk.Client.EmuHawk
|
namespace BizHawk.Client.EmuHawk
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// an IVideoProvder wrapping a Bitmap
|
/// an IVideoProvider wrapping a Bitmap
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class BmpVideoProvider : IVideoProvider, IDisposable
|
public class BmpVideoProvider : IVideoProvider, IDisposable
|
||||||
{
|
{
|
||||||
Bitmap bmp;
|
private Bitmap _bmp;
|
||||||
|
|
||||||
public BmpVideoProvider(Bitmap bmp)
|
public BmpVideoProvider(Bitmap bmp)
|
||||||
{
|
{
|
||||||
this.bmp = bmp;
|
_bmp = bmp;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
if (bmp != null)
|
if (_bmp != null)
|
||||||
{
|
{
|
||||||
bmp.Dispose();
|
_bmp.Dispose();
|
||||||
bmp = null;
|
_bmp = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public int[] GetVideoBuffer()
|
public int[] GetVideoBuffer()
|
||||||
{
|
{
|
||||||
// is there a faster way to do this?
|
// is there a faster way to do this?
|
||||||
var data = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), System.Drawing.Imaging.ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
|
var data = _bmp.LockBits(new Rectangle(0, 0, _bmp.Width, _bmp.Height), System.Drawing.Imaging.ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
|
||||||
|
|
||||||
int[] ret = new int[bmp.Width * bmp.Height];
|
int[] ret = new int[_bmp.Width * _bmp.Height];
|
||||||
|
|
||||||
// won't work if stride is messed up
|
// won't work if stride is messed up
|
||||||
System.Runtime.InteropServices.Marshal.Copy(data.Scan0, ret, 0, bmp.Width * bmp.Height);
|
System.Runtime.InteropServices.Marshal.Copy(data.Scan0, ret, 0, _bmp.Width * _bmp.Height);
|
||||||
bmp.UnlockBits(data);
|
_bmp.UnlockBits(data);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int VirtualWidth
|
public int VirtualWidth => _bmp.Width;
|
||||||
{
|
|
||||||
// todo: Bitmap actually has size metric data; use it
|
|
||||||
get { return bmp.Width; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public int VirtualHeight
|
// todo: Bitmap actually has size metric data; use it
|
||||||
{
|
public int VirtualHeight => _bmp.Height;
|
||||||
// todo: Bitmap actually has size metric data; use it
|
|
||||||
get { return bmp.Height; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public int BufferWidth
|
public int BufferWidth => _bmp.Width;
|
||||||
{
|
|
||||||
get { return bmp.Width; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public int BufferHeight
|
public int BufferHeight => _bmp.Height;
|
||||||
{
|
|
||||||
get { return bmp.Height; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public int BackgroundColor
|
public int BackgroundColor => 0;
|
||||||
{
|
|
||||||
get { return 0; }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
|
||||||
using BizHawk.Client.Common;
|
using BizHawk.Client.Common;
|
||||||
using BizHawk.Emulation.Common;
|
using BizHawk.Emulation.Common;
|
||||||
|
@ -12,166 +14,172 @@ namespace BizHawk.Client.EmuHawk
|
||||||
/// uses pipes to launch an external ffmpeg process and encode
|
/// uses pipes to launch an external ffmpeg process and encode
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[VideoWriter("ffmpeg", "FFmpeg writer", "Uses an external FFMPEG process to encode video and audio. Various formats supported. Splits on resolution change.")]
|
[VideoWriter("ffmpeg", "FFmpeg writer", "Uses an external FFMPEG process to encode video and audio. Various formats supported. Splits on resolution change.")]
|
||||||
class FFmpegWriter : IVideoWriter
|
public class FFmpegWriter : IVideoWriter
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// handle to external ffmpeg process
|
/// handle to external ffmpeg process
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Process ffmpeg;
|
private Process _ffmpeg;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// the commandline actually sent to ffmpeg; for informative purposes
|
/// the commandline actually sent to ffmpeg; for informative purposes
|
||||||
/// </summary>
|
/// </summary>
|
||||||
string commandline;
|
private string _commandline;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// current file segment (for multires)
|
/// current file segment (for multires)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
int segment;
|
private int _segment;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// base filename before segment number is attached
|
/// base filename before segment number is attached
|
||||||
/// </summary>
|
/// </summary>
|
||||||
string baseName;
|
private string _baseName;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// recent lines in ffmpeg's stderr, for informative purposes
|
/// recent lines in ffmpeg's stderr, for informative purposes
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Queue<string> stderr;
|
private Queue<string> _stderr;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// number of lines of stderr to buffer
|
/// number of lines of stderr to buffer
|
||||||
/// </summary>
|
/// </summary>
|
||||||
const int consolebuffer = 5;
|
private const int Consolebuffer = 5;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// muxer handle for the current segment
|
/// muxer handle for the current segment
|
||||||
/// </summary>
|
/// </summary>
|
||||||
NutMuxer muxer;
|
private NutMuxer _muxer;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// codec token in use
|
/// codec token in use
|
||||||
/// </summary>
|
/// </summary>
|
||||||
FFmpegWriterForm.FormatPreset token;
|
private FFmpegWriterForm.FormatPreset _token;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// file extension actually used
|
/// file extension actually used
|
||||||
/// </summary>
|
/// </summary>
|
||||||
string ext;
|
private string _ext;
|
||||||
|
|
||||||
public void SetFrame(int frame) { }
|
public void SetFrame(int frame)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
public void OpenFile(string baseName)
|
public void OpenFile(string baseName)
|
||||||
{
|
{
|
||||||
this.baseName = System.IO.Path.Combine(
|
_baseName = Path.Combine(
|
||||||
System.IO.Path.GetDirectoryName(baseName),
|
Path.GetDirectoryName(baseName),
|
||||||
System.IO.Path.GetFileNameWithoutExtension(baseName));
|
Path.GetFileNameWithoutExtension(baseName));
|
||||||
|
|
||||||
ext = System.IO.Path.GetExtension(baseName);
|
_ext = Path.GetExtension(baseName);
|
||||||
|
|
||||||
segment = 0;
|
_segment = 0;
|
||||||
OpenFileSegment();
|
OpenFileSegment();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// starts an ffmpeg process and sets up associated sockets
|
/// starts an ffmpeg process and sets up associated sockets
|
||||||
/// </summary>
|
/// </summary>
|
||||||
void OpenFileSegment()
|
private void OpenFileSegment()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
ffmpeg = new Process();
|
_ffmpeg = new Process();
|
||||||
#if WINDOWS
|
#if WINDOWS
|
||||||
ffmpeg.StartInfo.FileName = System.IO.Path.Combine(PathManager.GetDllDirectory(), "ffmpeg.exe");
|
_ffmpeg.StartInfo.FileName = Path.Combine(PathManager.GetDllDirectory(), "ffmpeg.exe");
|
||||||
#else
|
#else
|
||||||
ffmpeg.StartInfo.FileName = "ffmpeg"; // expecting native version to be in path
|
ffmpeg.StartInfo.FileName = "ffmpeg"; // expecting native version to be in path
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
string filename = String.Format("{0}_{1,4:D4}{2}", baseName, segment, ext);
|
string filename = $"{_baseName}_{_segment,4:D4}{_ext}";
|
||||||
ffmpeg.StartInfo.Arguments = String.Format("-y -f nut -i - {1} \"{0}\"", filename, token.commandline);
|
_ffmpeg.StartInfo.Arguments = string.Format("-y -f nut -i - {1} \"{0}\"", filename, _token.Commandline);
|
||||||
ffmpeg.StartInfo.CreateNoWindow = true;
|
_ffmpeg.StartInfo.CreateNoWindow = true;
|
||||||
|
|
||||||
// ffmpeg sends informative display to stderr, and nothing to stdout
|
// ffmpeg sends informative display to stderr, and nothing to stdout
|
||||||
ffmpeg.StartInfo.RedirectStandardError = true;
|
_ffmpeg.StartInfo.RedirectStandardError = true;
|
||||||
ffmpeg.StartInfo.RedirectStandardInput = true;
|
_ffmpeg.StartInfo.RedirectStandardInput = true;
|
||||||
ffmpeg.StartInfo.UseShellExecute = false;
|
_ffmpeg.StartInfo.UseShellExecute = false;
|
||||||
|
|
||||||
commandline = "ffmpeg " + ffmpeg.StartInfo.Arguments;
|
_commandline = "ffmpeg " + _ffmpeg.StartInfo.Arguments;
|
||||||
|
|
||||||
ffmpeg.ErrorDataReceived += new DataReceivedEventHandler(StderrHandler);
|
_ffmpeg.ErrorDataReceived += new DataReceivedEventHandler(StderrHandler);
|
||||||
|
|
||||||
stderr = new Queue<string>(consolebuffer);
|
_stderr = new Queue<string>(Consolebuffer);
|
||||||
|
|
||||||
ffmpeg.Start();
|
_ffmpeg.Start();
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
ffmpeg.Dispose();
|
_ffmpeg.Dispose();
|
||||||
ffmpeg = null;
|
_ffmpeg = null;
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
ffmpeg.BeginErrorReadLine();
|
|
||||||
|
|
||||||
muxer = new NutMuxer(width, height, fpsnum, fpsden, sampleRate, channels, ffmpeg.StandardInput.BaseStream);
|
_ffmpeg.BeginErrorReadLine();
|
||||||
|
|
||||||
|
_muxer = new NutMuxer(width, height, fpsnum, fpsden, sampleRate, channels, _ffmpeg.StandardInput.BaseStream);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// saves stderr lines from ffmpeg in a short queue
|
/// saves stderr lines from ffmpeg in a short queue
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="p"></param>
|
private void StderrHandler(object p, DataReceivedEventArgs line)
|
||||||
/// <param name="line"></param>
|
|
||||||
void StderrHandler(object p, DataReceivedEventArgs line)
|
|
||||||
{
|
{
|
||||||
if (!String.IsNullOrEmpty(line.Data))
|
if (!string.IsNullOrEmpty(line.Data))
|
||||||
{
|
{
|
||||||
if (stderr.Count == consolebuffer)
|
if (_stderr.Count == Consolebuffer)
|
||||||
stderr.Dequeue();
|
{
|
||||||
stderr.Enqueue(line.Data + "\n");
|
_stderr.Dequeue();
|
||||||
|
}
|
||||||
|
|
||||||
|
_stderr.Enqueue(line.Data + "\n");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// finishes an ffmpeg process
|
/// finishes an ffmpeg process
|
||||||
/// </summary>
|
/// </summary>
|
||||||
void CloseFileSegment()
|
private void CloseFileSegment()
|
||||||
{
|
{
|
||||||
muxer.Finish();
|
_muxer.Finish();
|
||||||
//ffmpeg.StandardInput.Close();
|
//ffmpeg.StandardInput.Close();
|
||||||
|
|
||||||
// how long should we wait here?
|
// how long should we wait here?
|
||||||
ffmpeg.WaitForExit(20000);
|
_ffmpeg.WaitForExit(20000);
|
||||||
ffmpeg.Dispose();
|
_ffmpeg.Dispose();
|
||||||
ffmpeg = null;
|
_ffmpeg = null;
|
||||||
stderr = null;
|
_stderr = null;
|
||||||
commandline = null;
|
_commandline = null;
|
||||||
muxer = null;
|
_muxer = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void CloseFile()
|
public void CloseFile()
|
||||||
{
|
{
|
||||||
CloseFileSegment();
|
CloseFileSegment();
|
||||||
baseName = null;
|
_baseName = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// returns a string containing the commandline sent to ffmpeg and recent console (stderr) output
|
/// returns a string containing the commandline sent to ffmpeg and recent console (stderr) output
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
string ffmpeg_geterror()
|
private string ffmpeg_geterror()
|
||||||
{
|
{
|
||||||
if (ffmpeg.StartInfo.RedirectStandardError)
|
if (_ffmpeg.StartInfo.RedirectStandardError)
|
||||||
{
|
{
|
||||||
ffmpeg.CancelErrorRead();
|
_ffmpeg.CancelErrorRead();
|
||||||
}
|
}
|
||||||
StringBuilder s = new StringBuilder();
|
|
||||||
s.Append(commandline);
|
var s = new StringBuilder();
|
||||||
|
s.Append(_commandline);
|
||||||
s.Append('\n');
|
s.Append('\n');
|
||||||
while (stderr.Count > 0)
|
while (_stderr.Count > 0)
|
||||||
{
|
{
|
||||||
var foo = stderr.Dequeue();
|
var foo = _stderr.Dequeue();
|
||||||
s.Append(foo);
|
s.Append(foo);
|
||||||
}
|
}
|
||||||
|
|
||||||
return s.ToString();
|
return s.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -179,19 +187,23 @@ namespace BizHawk.Client.EmuHawk
|
||||||
public void AddFrame(IVideoProvider source)
|
public void AddFrame(IVideoProvider source)
|
||||||
{
|
{
|
||||||
if (source.BufferWidth != width || source.BufferHeight != height)
|
if (source.BufferWidth != width || source.BufferHeight != height)
|
||||||
|
{
|
||||||
SetVideoParameters(source.BufferWidth, source.BufferHeight);
|
SetVideoParameters(source.BufferWidth, source.BufferHeight);
|
||||||
|
}
|
||||||
|
|
||||||
if (ffmpeg.HasExited)
|
if (_ffmpeg.HasExited)
|
||||||
|
{
|
||||||
throw new Exception("unexpected ffmpeg death:\n" + ffmpeg_geterror());
|
throw new Exception("unexpected ffmpeg death:\n" + ffmpeg_geterror());
|
||||||
|
}
|
||||||
|
|
||||||
var video = source.GetVideoBuffer();
|
var video = source.GetVideoBuffer();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
muxer.WriteVideoFrame(video);
|
_muxer.WriteVideoFrame(video);
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
System.Windows.Forms.MessageBox.Show("Exception! ffmpeg history:\n" + ffmpeg_geterror());
|
MessageBox.Show("Exception! ffmpeg history:\n" + ffmpeg_geterror());
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -199,7 +211,7 @@ namespace BizHawk.Client.EmuHawk
|
||||||
//ffmpeg.StandardInput.BaseStream.Write(b, 0, b.Length);
|
//ffmpeg.StandardInput.BaseStream.Write(b, 0, b.Length);
|
||||||
}
|
}
|
||||||
|
|
||||||
public IDisposable AcquireVideoCodecToken(System.Windows.Forms.IWin32Window hwnd)
|
public IDisposable AcquireVideoCodecToken(IWin32Window hwnd)
|
||||||
{
|
{
|
||||||
return FFmpegWriterForm.DoFFmpegWriterDlg(hwnd);
|
return FFmpegWriterForm.DoFFmpegWriterDlg(hwnd);
|
||||||
}
|
}
|
||||||
|
@ -207,15 +219,19 @@ namespace BizHawk.Client.EmuHawk
|
||||||
public void SetVideoCodecToken(IDisposable token)
|
public void SetVideoCodecToken(IDisposable token)
|
||||||
{
|
{
|
||||||
if (token is FFmpegWriterForm.FormatPreset)
|
if (token is FFmpegWriterForm.FormatPreset)
|
||||||
this.token = (FFmpegWriterForm.FormatPreset)token;
|
{
|
||||||
|
_token = (FFmpegWriterForm.FormatPreset)token;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
throw new ArgumentException("FFmpegWriter can only take its own codec tokens!");
|
throw new ArgumentException("FFmpegWriter can only take its own codec tokens!");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// video params
|
/// video params
|
||||||
/// </summary>
|
/// </summary>
|
||||||
int fpsnum, fpsden, width, height, sampleRate, channels;
|
private int fpsnum, fpsden, width, height, sampleRate, channels;
|
||||||
|
|
||||||
public void SetMovieParameters(int fpsnum, int fpsden)
|
public void SetMovieParameters(int fpsnum, int fpsden)
|
||||||
{
|
{
|
||||||
|
@ -231,10 +247,10 @@ namespace BizHawk.Client.EmuHawk
|
||||||
/* ffmpeg theoretically supports variable resolution videos, but in practice that's not handled very well.
|
/* ffmpeg theoretically supports variable resolution videos, but in practice that's not handled very well.
|
||||||
* so we start a new segment.
|
* so we start a new segment.
|
||||||
*/
|
*/
|
||||||
if (ffmpeg != null)
|
if (_ffmpeg != null)
|
||||||
{
|
{
|
||||||
CloseFileSegment();
|
CloseFileSegment();
|
||||||
segment++;
|
_segment++;
|
||||||
OpenFileSegment();
|
OpenFileSegment();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -248,27 +264,32 @@ namespace BizHawk.Client.EmuHawk
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
if (ffmpeg != null)
|
if (_ffmpeg != null)
|
||||||
|
{
|
||||||
CloseFile();
|
CloseFile();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void AddSamples(short[] samples)
|
public void AddSamples(short[] samples)
|
||||||
{
|
{
|
||||||
if (ffmpeg.HasExited)
|
if (_ffmpeg.HasExited)
|
||||||
|
{
|
||||||
throw new Exception("unexpected ffmpeg death:\n" + ffmpeg_geterror());
|
throw new Exception("unexpected ffmpeg death:\n" + ffmpeg_geterror());
|
||||||
|
}
|
||||||
|
|
||||||
if (samples.Length == 0)
|
if (samples.Length == 0)
|
||||||
{
|
{
|
||||||
// has special meaning for the muxer, so don't pass on
|
// has special meaning for the muxer, so don't pass on
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
muxer.WriteAudioFrame(samples);
|
_muxer.WriteAudioFrame(samples);
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
System.Windows.Forms.MessageBox.Show("Exception! ffmpeg history:\n" + ffmpeg_geterror());
|
MessageBox.Show("Exception! ffmpeg history:\n" + ffmpeg_geterror());
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -276,7 +297,10 @@ namespace BizHawk.Client.EmuHawk
|
||||||
public void SetAudioParameters(int sampleRate, int channels, int bits)
|
public void SetAudioParameters(int sampleRate, int channels, int bits)
|
||||||
{
|
{
|
||||||
if (bits != 16)
|
if (bits != 16)
|
||||||
|
{
|
||||||
throw new ArgumentOutOfRangeException(nameof(bits), "Sampling depth must be 16 bits!");
|
throw new ArgumentOutOfRangeException(nameof(bits), "Sampling depth must be 16 bits!");
|
||||||
|
}
|
||||||
|
|
||||||
this.sampleRate = sampleRate;
|
this.sampleRate = sampleRate;
|
||||||
this.channels = channels;
|
this.channels = channels;
|
||||||
}
|
}
|
||||||
|
@ -284,15 +308,16 @@ namespace BizHawk.Client.EmuHawk
|
||||||
public string DesiredExtension()
|
public string DesiredExtension()
|
||||||
{
|
{
|
||||||
// this needs to interface with the codec token
|
// this needs to interface with the codec token
|
||||||
return token.defaultext;
|
return _token.Defaultext;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetDefaultVideoCodecToken()
|
public void SetDefaultVideoCodecToken()
|
||||||
{
|
{
|
||||||
token = FFmpegWriterForm.FormatPreset.GetDefaultPreset();
|
_token = FFmpegWriterForm.FormatPreset.GetDefaultPreset();
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool UsesAudio { get { return true; } }
|
public bool UsesAudio => true;
|
||||||
public bool UsesVideo { get { return true; } }
|
|
||||||
|
public bool UsesVideo => true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -58,7 +58,7 @@
|
||||||
this.listBox1.Name = "listBox1";
|
this.listBox1.Name = "listBox1";
|
||||||
this.listBox1.Size = new System.Drawing.Size(275, 147);
|
this.listBox1.Size = new System.Drawing.Size(275, 147);
|
||||||
this.listBox1.TabIndex = 1;
|
this.listBox1.TabIndex = 1;
|
||||||
this.listBox1.SelectedIndexChanged += new System.EventHandler(this.listBox1_SelectedIndexChanged);
|
this.listBox1.SelectedIndexChanged += new System.EventHandler(this.ListBox1_SelectedIndexChanged);
|
||||||
//
|
//
|
||||||
// label2
|
// label2
|
||||||
//
|
//
|
||||||
|
|
|
@ -1,10 +1,4 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.ComponentModel;
|
|
||||||
using System.Data;
|
|
||||||
using System.Drawing;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
|
|
||||||
using BizHawk.Client.Common;
|
using BizHawk.Client.Common;
|
||||||
|
@ -22,42 +16,29 @@ namespace BizHawk.Client.EmuHawk
|
||||||
public class FormatPreset : IDisposable
|
public class FormatPreset : IDisposable
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// name for listbox
|
/// Gets the name for the listbox
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string name;
|
public string Name { get; }
|
||||||
/// <summary>
|
|
||||||
/// long human readable description
|
|
||||||
/// </summary>
|
|
||||||
public string desc;
|
|
||||||
/// <summary>
|
|
||||||
/// actual portion of ffmpeg commandline
|
|
||||||
/// </summary>
|
|
||||||
public string commandline;
|
|
||||||
public override string ToString()
|
|
||||||
{
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
/// <summary>
|
|
||||||
/// can be edited
|
|
||||||
/// </summary>
|
|
||||||
public bool custom = false;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// default file extension
|
/// Gets the long human readable description
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string defaultext;
|
public string Desc { get; }
|
||||||
|
|
||||||
FormatPreset(string name, string desc, string commandline, bool custom, string defaultext)
|
/// <summary>
|
||||||
{
|
/// Gets the actual portion of the ffmpeg commandline
|
||||||
this.name = name;
|
/// </summary>
|
||||||
this.desc = desc;
|
public string Commandline { get; set; }
|
||||||
this.custom = custom;
|
|
||||||
if (custom)
|
/// <summary>
|
||||||
this.commandline = Global.Config.FFmpegCustomCommand;
|
/// Gets a value indicating whether or not it can be edited
|
||||||
else
|
/// </summary>
|
||||||
this.commandline = commandline;
|
public bool Custom { get; }
|
||||||
this.defaultext = defaultext;
|
|
||||||
}
|
/// <summary>
|
||||||
|
/// Gets the default file extension
|
||||||
|
/// </summary>
|
||||||
|
public string Defaultext { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// get a list of canned presets
|
/// get a list of canned presets
|
||||||
|
@ -65,7 +46,7 @@ namespace BizHawk.Client.EmuHawk
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public static FormatPreset[] GetPresets()
|
public static FormatPreset[] GetPresets()
|
||||||
{
|
{
|
||||||
return new FormatPreset[]
|
return new[]
|
||||||
{
|
{
|
||||||
new FormatPreset("Uncompressed AVI", "AVI file with uncompressed audio and video. Very large.", "-c:a pcm_s16le -c:v rawvideo -f avi", false, "avi"),
|
new FormatPreset("Uncompressed AVI", "AVI file with uncompressed audio and video. Very large.", "-c:a pcm_s16le -c:v rawvideo -f avi", false, "avi"),
|
||||||
new FormatPreset("Xvid", "AVI file with xvid video and mp3 audio.", "-c:a libmp3lame -c:v libxvid -f avi", false, "avi"),
|
new FormatPreset("Xvid", "AVI file with xvid video and mp3 audio.", "-c:a libmp3lame -c:v libxvid -f avi", false, "avi"),
|
||||||
|
@ -84,7 +65,6 @@ namespace BizHawk.Client.EmuHawk
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// get the default format preset (from config files)
|
/// get the default format preset (from config files)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns></returns>
|
|
||||||
public static FormatPreset GetDefaultPreset()
|
public static FormatPreset GetDefaultPreset()
|
||||||
{
|
{
|
||||||
FormatPreset[] fps = GetPresets();
|
FormatPreset[] fps = GetPresets();
|
||||||
|
@ -93,42 +73,61 @@ namespace BizHawk.Client.EmuHawk
|
||||||
{
|
{
|
||||||
if (fp.ToString() == Global.Config.FFmpegFormat)
|
if (fp.ToString() == Global.Config.FFmpegFormat)
|
||||||
{
|
{
|
||||||
if (fp.custom)
|
if (fp.Custom)
|
||||||
|
{
|
||||||
return fp;
|
return fp;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// default to xvid?
|
// default to xvid?
|
||||||
return fps[1];
|
return fps[1];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return Name;
|
||||||
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private FormatPreset(string name, string desc, string commandline, bool custom, string defaultext)
|
||||||
|
{
|
||||||
|
Name = name;
|
||||||
|
Desc = desc;
|
||||||
|
Custom = custom;
|
||||||
|
|
||||||
|
Commandline = Custom
|
||||||
|
? Global.Config.FFmpegCustomCommand
|
||||||
|
: commandline;
|
||||||
|
|
||||||
|
Defaultext = defaultext;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public FFmpegWriterForm()
|
private FFmpegWriterForm()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void listBox1_SelectedIndexChanged(object sender, EventArgs e)
|
private void ListBox1_SelectedIndexChanged(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
if (listBox1.SelectedIndex != -1)
|
if (listBox1.SelectedIndex != -1)
|
||||||
{
|
{
|
||||||
FormatPreset f = (FormatPreset)listBox1.SelectedItem;
|
var f = (FormatPreset)listBox1.SelectedItem;
|
||||||
|
|
||||||
label5.Text = "Extension: " + f.defaultext;
|
label5.Text = "Extension: " + f.Defaultext;
|
||||||
label3.Text = f.desc;
|
label3.Text = f.Desc;
|
||||||
textBox1.Text = f.commandline;
|
textBox1.Text = f.Commandline;
|
||||||
textBox1.ReadOnly = !f.custom;
|
textBox1.ReadOnly = !f.Custom;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// return a formatpreset corresponding to the user's choice
|
/// return a FormatPreset corresponding to the user's choice
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="owner"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public static FormatPreset DoFFmpegWriterDlg(IWin32Window owner)
|
public static FormatPreset DoFFmpegWriterDlg(IWin32Window owner)
|
||||||
{
|
{
|
||||||
FFmpegWriterForm dlg = new FFmpegWriterForm();
|
FFmpegWriterForm dlg = new FFmpegWriterForm();
|
||||||
|
@ -136,24 +135,28 @@ namespace BizHawk.Client.EmuHawk
|
||||||
|
|
||||||
int i = dlg.listBox1.FindStringExact(Global.Config.FFmpegFormat);
|
int i = dlg.listBox1.FindStringExact(Global.Config.FFmpegFormat);
|
||||||
if (i != ListBox.NoMatches)
|
if (i != ListBox.NoMatches)
|
||||||
|
{
|
||||||
dlg.listBox1.SelectedIndex = i;
|
dlg.listBox1.SelectedIndex = i;
|
||||||
|
}
|
||||||
|
|
||||||
DialogResult result = dlg.ShowDialog(owner);
|
DialogResult result = dlg.ShowDialog(owner);
|
||||||
|
|
||||||
FormatPreset ret;
|
FormatPreset ret;
|
||||||
if (result != DialogResult.OK || dlg.listBox1.SelectedIndex == -1)
|
if (result != DialogResult.OK || dlg.listBox1.SelectedIndex == -1)
|
||||||
|
{
|
||||||
ret = null;
|
ret = null;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
ret = (FormatPreset)dlg.listBox1.SelectedItem;
|
ret = (FormatPreset)dlg.listBox1.SelectedItem;
|
||||||
Global.Config.FFmpegFormat = ret.ToString();
|
Global.Config.FFmpegFormat = ret.ToString();
|
||||||
if (ret.custom)
|
if (ret.Custom)
|
||||||
{
|
{
|
||||||
ret.commandline = dlg.textBox1.Text;
|
ret.Commandline = dlg.textBox1.Text;
|
||||||
Global.Config.FFmpegCustomCommand = dlg.textBox1.Text;
|
Global.Config.FFmpegCustomCommand = dlg.textBox1.Text;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dlg.Dispose();
|
dlg.Dispose();
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,4 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
|
|
||||||
|
@ -18,102 +15,130 @@ namespace BizHawk.Client.EmuHawk
|
||||||
private int _frameskip, _framedelay;
|
private int _frameskip, _framedelay;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// how many frames to skip for each frame deposited
|
/// Gets how many frames to skip for each frame deposited
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int frameskip
|
public int Frameskip
|
||||||
{
|
{
|
||||||
get { return _frameskip; }
|
get
|
||||||
|
{
|
||||||
|
return _frameskip;
|
||||||
|
}
|
||||||
|
|
||||||
private set
|
private set
|
||||||
{
|
{
|
||||||
if (value < 0)
|
if (value < 0)
|
||||||
|
{
|
||||||
_frameskip = 0;
|
_frameskip = 0;
|
||||||
|
}
|
||||||
else if (value > 999)
|
else if (value > 999)
|
||||||
|
{
|
||||||
_frameskip = 999;
|
_frameskip = 999;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
_frameskip = value;
|
_frameskip = value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// how long to delay between each gif frame (units of 10ms, -1 = auto)
|
/// how long to delay between each gif frame (units of 10ms, -1 = auto)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int framedelay
|
public int FrameDelay
|
||||||
{
|
{
|
||||||
get { return _framedelay; }
|
get
|
||||||
|
{
|
||||||
|
return _framedelay;
|
||||||
|
}
|
||||||
|
|
||||||
private set
|
private set
|
||||||
{
|
{
|
||||||
if (value < -1)
|
if (value < -1)
|
||||||
|
{
|
||||||
_framedelay = -1;
|
_framedelay = -1;
|
||||||
|
}
|
||||||
else if (value > 100)
|
else if (value > 100)
|
||||||
|
{
|
||||||
_framedelay = 100;
|
_framedelay = 100;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
_framedelay = value;
|
_framedelay = value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose() { }
|
|
||||||
|
|
||||||
public GifToken(int frameskip, int framedelay)
|
|
||||||
{
|
|
||||||
this.frameskip = frameskip;
|
|
||||||
this.framedelay = framedelay;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static GifToken LoadFromConfig()
|
public static GifToken LoadFromConfig()
|
||||||
{
|
{
|
||||||
GifToken ret = new GifToken(0, 0);
|
return new GifToken(0, 0)
|
||||||
ret.frameskip = Global.Config.GifWriterFrameskip;
|
{
|
||||||
ret.framedelay = Global.Config.GifWriterDelay;
|
Frameskip = Global.Config.GifWriterFrameskip,
|
||||||
return ret;
|
FrameDelay = Global.Config.GifWriterDelay
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private GifToken(int frameskip, int framedelay)
|
||||||
|
{
|
||||||
|
Frameskip = frameskip;
|
||||||
|
FrameDelay = framedelay;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
GifToken token;
|
|
||||||
|
private GifToken _token;
|
||||||
|
|
||||||
public void SetVideoCodecToken(IDisposable token)
|
public void SetVideoCodecToken(IDisposable token)
|
||||||
{
|
{
|
||||||
if (token is GifToken)
|
if (token is GifToken)
|
||||||
{
|
{
|
||||||
this.token = (GifToken)token;
|
_token = (GifToken)token;
|
||||||
CalcDelay();
|
CalcDelay();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
throw new ArgumentException("GifWriter only takes its own tokens!");
|
throw new ArgumentException("GifWriter only takes its own tokens!");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetDefaultVideoCodecToken()
|
public void SetDefaultVideoCodecToken()
|
||||||
{
|
{
|
||||||
token = GifToken.LoadFromConfig();
|
_token = GifToken.LoadFromConfig();
|
||||||
CalcDelay();
|
CalcDelay();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// true if the first frame has been written to the file; false otherwise
|
/// true if the first frame has been written to the file; false otherwise
|
||||||
/// </summary>
|
/// </summary>
|
||||||
bool firstdone = false;
|
private bool firstdone = false;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// the underlying stream we're writing to
|
/// the underlying stream we're writing to
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Stream f;
|
private Stream f;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// a final byte we must write before closing the stream
|
/// a final byte we must write before closing the stream
|
||||||
/// </summary>
|
/// </summary>
|
||||||
byte lastbyte;
|
private byte lastbyte;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// keep track of skippable frames
|
/// keep track of skippable frames
|
||||||
/// </summary>
|
/// </summary>
|
||||||
int skipindex = 0;
|
private int skipindex = 0;
|
||||||
|
|
||||||
int fpsnum = 1, fpsden = 1;
|
private int fpsnum = 1, fpsden = 1;
|
||||||
|
|
||||||
public void SetFrame(int frame) { }
|
public void SetFrame(int frame)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
public void OpenFile(string baseName)
|
public void OpenFile(string baseName)
|
||||||
{
|
{
|
||||||
f = new FileStream(baseName, FileMode.OpenOrCreate, FileAccess.Write);
|
f = new FileStream(baseName, FileMode.OpenOrCreate, FileAccess.Write);
|
||||||
skipindex = token.frameskip;
|
skipindex = _token.Frameskip;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void CloseFile()
|
public void CloseFile()
|
||||||
|
@ -125,23 +150,25 @@ namespace BizHawk.Client.EmuHawk
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// precooked gif header
|
/// precooked gif header
|
||||||
/// </summary>
|
/// </summary>
|
||||||
static byte[] GifAnimation = {33, 255, 11, 78, 69, 84, 83, 67, 65, 80, 69, 50, 46, 48, 3, 1, 0, 0, 0};
|
static byte[] GifAnimation = { 33, 255, 11, 78, 69, 84, 83, 67, 65, 80, 69, 50, 46, 48, 3, 1, 0, 0, 0 };
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// little endian frame length in 10ms units
|
/// little endian frame length in 10ms units
|
||||||
/// </summary>
|
/// </summary>
|
||||||
byte[] Delay = {100, 0};
|
byte[] Delay = {100, 0};
|
||||||
|
|
||||||
public void AddFrame(IVideoProvider source)
|
public void AddFrame(IVideoProvider source)
|
||||||
{
|
{
|
||||||
if (skipindex == token.frameskip)
|
if (skipindex == _token.Frameskip)
|
||||||
|
{
|
||||||
skipindex = 0;
|
skipindex = 0;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
skipindex++;
|
skipindex++;
|
||||||
return; // skip this frame
|
return; // skip this frame
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
using (var bmp = new Bitmap(source.BufferWidth, source.BufferHeight, System.Drawing.Imaging.PixelFormat.Format32bppArgb))
|
using (var bmp = new Bitmap(source.BufferWidth, source.BufferHeight, System.Drawing.Imaging.PixelFormat.Format32bppArgb))
|
||||||
{
|
{
|
||||||
var data = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), System.Drawing.Imaging.ImageLockMode.WriteOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
|
var data = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), System.Drawing.Imaging.ImageLockMode.WriteOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
|
||||||
|
@ -160,6 +187,7 @@ namespace BizHawk.Client.EmuHawk
|
||||||
f.Write(b, 0, 13);
|
f.Write(b, 0, 13);
|
||||||
f.Write(GifAnimation, 0, GifAnimation.Length);
|
f.Write(GifAnimation, 0, GifAnimation.Length);
|
||||||
}
|
}
|
||||||
|
|
||||||
b[785] = Delay[0];
|
b[785] = Delay[0];
|
||||||
b[786] = Delay[1];
|
b[786] = Delay[1];
|
||||||
b[798] = (byte)(b[798] | 0x87);
|
b[798] = (byte)(b[798] | 0x87);
|
||||||
|
@ -182,15 +210,23 @@ namespace BizHawk.Client.EmuHawk
|
||||||
return GifWriterForm.DoTokenForm(hwnd);
|
return GifWriterForm.DoTokenForm(hwnd);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CalcDelay()
|
private void CalcDelay()
|
||||||
{
|
{
|
||||||
if (token == null)
|
if (_token == null)
|
||||||
|
{
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
int delay;
|
int delay;
|
||||||
if (token.framedelay == -1)
|
if (_token.FrameDelay == -1)
|
||||||
delay = (100 * fpsden * (token.frameskip + 1) + (fpsnum / 2)) / fpsnum;
|
{
|
||||||
|
delay = (100 * fpsden * (_token.Frameskip + 1) + (fpsnum / 2)) / fpsnum;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
delay = token.framedelay;
|
{
|
||||||
|
delay = _token.FrameDelay;
|
||||||
|
}
|
||||||
|
|
||||||
Delay[0] = (byte)(delay & 0xff);
|
Delay[0] = (byte)(delay & 0xff);
|
||||||
Delay[1] = (byte)(delay >> 8 & 0xff);
|
Delay[1] = (byte)(delay >> 8 & 0xff);
|
||||||
}
|
}
|
||||||
|
@ -232,7 +268,8 @@ namespace BizHawk.Client.EmuHawk
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool UsesAudio { get { return false; } }
|
public bool UsesAudio => false;
|
||||||
public bool UsesVideo { get { return true; } }
|
|
||||||
|
public bool UsesVideo => true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -102,7 +102,7 @@
|
||||||
this.numericUpDown2.Name = "numericUpDown2";
|
this.numericUpDown2.Name = "numericUpDown2";
|
||||||
this.numericUpDown2.Size = new System.Drawing.Size(96, 20);
|
this.numericUpDown2.Size = new System.Drawing.Size(96, 20);
|
||||||
this.numericUpDown2.TabIndex = 5;
|
this.numericUpDown2.TabIndex = 5;
|
||||||
this.numericUpDown2.ValueChanged += new System.EventHandler(this.numericUpDown2_ValueChanged);
|
this.numericUpDown2.ValueChanged += new System.EventHandler(this.NumericUpDown2_ValueChanged);
|
||||||
//
|
//
|
||||||
// label3
|
// label3
|
||||||
//
|
//
|
||||||
|
|
|
@ -1,10 +1,4 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.ComponentModel;
|
|
||||||
using System.Data;
|
|
||||||
using System.Drawing;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
|
|
||||||
using BizHawk.Client.Common;
|
using BizHawk.Client.Common;
|
||||||
|
@ -24,21 +18,22 @@ namespace BizHawk.Client.EmuHawk
|
||||||
{
|
{
|
||||||
dlg.numericUpDown1.Value = Global.Config.GifWriterFrameskip;
|
dlg.numericUpDown1.Value = Global.Config.GifWriterFrameskip;
|
||||||
dlg.numericUpDown2.Value = Global.Config.GifWriterDelay;
|
dlg.numericUpDown2.Value = Global.Config.GifWriterDelay;
|
||||||
dlg.numericUpDown2_ValueChanged(null, null);
|
dlg.NumericUpDown2_ValueChanged(null, null);
|
||||||
|
|
||||||
var result = dlg.ShowDialog(parent);
|
var result = dlg.ShowDialog(parent);
|
||||||
if (result == DialogResult.OK)
|
if (result == DialogResult.OK)
|
||||||
{
|
{
|
||||||
Global.Config.GifWriterFrameskip = (int)dlg.numericUpDown1.Value;
|
Global.Config.GifWriterFrameskip = (int)dlg.numericUpDown1.Value;
|
||||||
Global.Config.GifWriterDelay = (int)dlg.numericUpDown2.Value;
|
Global.Config.GifWriterDelay = (int)dlg.numericUpDown2.Value;
|
||||||
|
|
||||||
return GifWriter.GifToken.LoadFromConfig();
|
return GifWriter.GifToken.LoadFromConfig();
|
||||||
}
|
}
|
||||||
else
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void numericUpDown2_ValueChanged(object sender, EventArgs e)
|
private void NumericUpDown2_ValueChanged(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
if (numericUpDown2.Value == -1)
|
if (numericUpDown2.Value == -1)
|
||||||
{
|
{
|
||||||
|
@ -50,7 +45,7 @@ namespace BizHawk.Client.EmuHawk
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
label3.Text = string.Format("{0} FPS", (int)((100 + numericUpDown2.Value / 2) / numericUpDown2.Value));
|
label3.Text = $"{(int)((100 + numericUpDown2.Value / 2) / numericUpDown2.Value)} FPS";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
using BizHawk.Emulation.Common;
|
using BizHawk.Emulation.Common;
|
||||||
|
@ -55,7 +54,7 @@ namespace BizHawk.Client.EmuHawk
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// adds audio samples to the stream
|
/// adds audio samples to the stream
|
||||||
/// no attempt is made to sync this to the video
|
/// no attempt is made to sync this to the video
|
||||||
/// reccomendation: try not to have the size or pacing of the audio chunks be too "weird"
|
/// recommendation: try not to have the size or pacing of the audio chunks be too "weird"
|
||||||
/// </summary>
|
/// </summary>
|
||||||
void AddSamples(short[] samples);
|
void AddSamples(short[] samples);
|
||||||
|
|
||||||
|
@ -78,8 +77,6 @@ namespace BizHawk.Client.EmuHawk
|
||||||
/// can be changed in future
|
/// can be changed in future
|
||||||
/// should always match IVideoProvider
|
/// should always match IVideoProvider
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="width"></param>
|
|
||||||
/// <param name="height"></param>
|
|
||||||
void SetVideoParameters(int width, int height);
|
void SetVideoParameters(int width, int height);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -134,15 +131,15 @@ namespace BizHawk.Client.EmuHawk
|
||||||
[AttributeUsage(AttributeTargets.Class)]
|
[AttributeUsage(AttributeTargets.Class)]
|
||||||
public class VideoWriterAttribute : Attribute
|
public class VideoWriterAttribute : Attribute
|
||||||
{
|
{
|
||||||
public string ShortName { get; private set; }
|
public string ShortName { get; }
|
||||||
public string Name { get; private set; }
|
public string Name { get; }
|
||||||
public string Description { get; private set; }
|
public string Description { get; }
|
||||||
|
|
||||||
public VideoWriterAttribute(string ShortName, string Name, string Description)
|
public VideoWriterAttribute(string shortName, string name, string description)
|
||||||
{
|
{
|
||||||
this.ShortName = ShortName;
|
ShortName = shortName;
|
||||||
this.Name = Name;
|
Name = name;
|
||||||
this.Description = Description;
|
Description = description;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -153,18 +150,18 @@ namespace BizHawk.Client.EmuHawk
|
||||||
|
|
||||||
public class VideoWriterInfo
|
public class VideoWriterInfo
|
||||||
{
|
{
|
||||||
public VideoWriterAttribute Attribs { get; private set; }
|
public VideoWriterAttribute Attribs { get; }
|
||||||
private Type type;
|
private Type _type;
|
||||||
|
|
||||||
public VideoWriterInfo(VideoWriterAttribute Attribs, Type type)
|
public VideoWriterInfo(VideoWriterAttribute attribs, Type type)
|
||||||
{
|
{
|
||||||
this.type = type;
|
_type = type;
|
||||||
this.Attribs = Attribs;
|
Attribs = attribs;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IVideoWriter Create()
|
public IVideoWriter Create()
|
||||||
{
|
{
|
||||||
return (IVideoWriter)Activator.CreateInstance(type);
|
return (IVideoWriter)Activator.CreateInstance(_type);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
|
@ -203,15 +200,15 @@ namespace BizHawk.Client.EmuHawk
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// find an IVideoWriter by its short name
|
/// find an IVideoWriter by its short name
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="name"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public static IVideoWriter GetVideoWriter(string name)
|
public static IVideoWriter GetVideoWriter(string name)
|
||||||
{
|
{
|
||||||
VideoWriterInfo ret;
|
VideoWriterInfo ret;
|
||||||
if (vws.TryGetValue(name, out ret))
|
if (vws.TryGetValue(name, out ret))
|
||||||
|
{
|
||||||
return ret.Create();
|
return ret.Create();
|
||||||
else
|
}
|
||||||
return null;
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
|
||||||
using BizHawk.Common.IOExtensions;
|
using BizHawk.Bizware.BizwareGL;
|
||||||
using BizHawk.Emulation.Common;
|
using BizHawk.Emulation.Common;
|
||||||
|
|
||||||
namespace BizHawk.Client.EmuHawk
|
namespace BizHawk.Client.EmuHawk
|
||||||
|
@ -13,8 +13,8 @@ namespace BizHawk.Client.EmuHawk
|
||||||
[VideoWriter("imagesequence", "Image sequence writer", "Writes a sequence of 24bpp PNG or JPG files (default compression quality)")]
|
[VideoWriter("imagesequence", "Image sequence writer", "Writes a sequence of 24bpp PNG or JPG files (default compression quality)")]
|
||||||
public class ImageSequenceWriter : IDisposable, IVideoWriter
|
public class ImageSequenceWriter : IDisposable, IVideoWriter
|
||||||
{
|
{
|
||||||
string BaseName;
|
private string _baseName;
|
||||||
int Frame;
|
private int _frame;
|
||||||
|
|
||||||
public void SetVideoCodecToken(IDisposable token)
|
public void SetVideoCodecToken(IDisposable token)
|
||||||
{
|
{
|
||||||
|
@ -24,12 +24,13 @@ namespace BizHawk.Client.EmuHawk
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool UsesAudio { get { return false; } }
|
public bool UsesAudio => false;
|
||||||
public bool UsesVideo { get { return true; } }
|
|
||||||
|
public bool UsesVideo => true;
|
||||||
|
|
||||||
public void OpenFile(string baseName)
|
public void OpenFile(string baseName)
|
||||||
{
|
{
|
||||||
BaseName = baseName;
|
_baseName = baseName;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void CloseFile()
|
public void CloseFile()
|
||||||
|
@ -38,36 +39,43 @@ namespace BizHawk.Client.EmuHawk
|
||||||
|
|
||||||
public void SetFrame(int frame)
|
public void SetFrame(int frame)
|
||||||
{
|
{
|
||||||
//eh? this gets ditched somehow
|
// eh? this gets ditched somehow
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AddFrame(IVideoProvider source)
|
public void AddFrame(IVideoProvider source)
|
||||||
{
|
{
|
||||||
string ext = Path.GetExtension(BaseName);
|
string ext = Path.GetExtension(_baseName);
|
||||||
string name = Path.GetFileNameWithoutExtension(BaseName) + "_" + Frame.ToString();
|
string name = Path.GetFileNameWithoutExtension(_baseName) + "_" + _frame;
|
||||||
name += ext;
|
name += ext;
|
||||||
name = Path.Combine(Path.GetDirectoryName(BaseName), name);
|
name = Path.Combine(Path.GetDirectoryName(_baseName), name);
|
||||||
BizHawk.Bizware.BizwareGL.BitmapBuffer bb = new Bizware.BizwareGL.BitmapBuffer(source.BufferWidth, source.BufferHeight, source.GetVideoBuffer());
|
BitmapBuffer bb = new BitmapBuffer(source.BufferWidth, source.BufferHeight, source.GetVideoBuffer());
|
||||||
using (var bmp = bb.ToSysdrawingBitmap())
|
using (var bmp = bb.ToSysdrawingBitmap())
|
||||||
{
|
{
|
||||||
if (ext.ToUpper() == ".PNG")
|
if (ext.ToUpper() == ".PNG")
|
||||||
|
{
|
||||||
bmp.Save(name, System.Drawing.Imaging.ImageFormat.Png);
|
bmp.Save(name, System.Drawing.Imaging.ImageFormat.Png);
|
||||||
|
}
|
||||||
else if (ext.ToUpper() == ".JPG")
|
else if (ext.ToUpper() == ".JPG")
|
||||||
|
{
|
||||||
bmp.Save(name, System.Drawing.Imaging.ImageFormat.Jpeg);
|
bmp.Save(name, System.Drawing.Imaging.ImageFormat.Jpeg);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Frame++;
|
|
||||||
|
_frame++;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AddSamples(short[] samples)
|
public void AddSamples(short[] samples)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
class CodecToken : IDisposable
|
private class CodecToken : IDisposable
|
||||||
{
|
{
|
||||||
public void Dispose() { }
|
public void Dispose()
|
||||||
|
{
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public IDisposable AcquireVideoCodecToken(System.Windows.Forms.IWin32Window hwnd)
|
public IDisposable AcquireVideoCodecToken(IWin32Window hwnd)
|
||||||
{
|
{
|
||||||
return new CodecToken();
|
return new CodecToken();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,4 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.ComponentModel;
|
|
||||||
using System.Data;
|
|
||||||
using System.Drawing;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
|
|
||||||
namespace BizHawk.Client.EmuHawk
|
namespace BizHawk.Client.EmuHawk
|
||||||
|
@ -31,15 +25,14 @@ namespace BizHawk.Client.EmuHawk
|
||||||
|
|
||||||
private void threadsBar_Scroll(object sender, EventArgs e)
|
private void threadsBar_Scroll(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
threadTop.Text = String.Format("Number of compression threads: {0}", threadsBar.Value);
|
threadTop.Text = $"Number of compression threads: {threadsBar.Value}";
|
||||||
}
|
}
|
||||||
|
|
||||||
private void compressionBar_Scroll(object sender, EventArgs e)
|
private void compressionBar_Scroll(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
if (compressionBar.Value == compressionBar.Minimum)
|
compressionTop.Text = compressionBar.Value == compressionBar.Minimum
|
||||||
compressionTop.Text = "Compression Level: NONE";
|
? "Compression Level: NONE"
|
||||||
else
|
: $"Compression Level: {compressionBar.Value}";
|
||||||
compressionTop.Text = String.Format("Compression Level: {0}", compressionBar.Value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -64,10 +57,10 @@ namespace BizHawk.Client.EmuHawk
|
||||||
j.compressionBar.Value = complevel;
|
j.compressionBar.Value = complevel;
|
||||||
j.threadsBar_Scroll(null, null);
|
j.threadsBar_Scroll(null, null);
|
||||||
j.compressionBar_Scroll(null, null);
|
j.compressionBar_Scroll(null, null);
|
||||||
j.threadLeft.Text = String.Format("{0}", tmin);
|
j.threadLeft.Text = $"{tmin}";
|
||||||
j.threadRight.Text = String.Format("{0}", tmax);
|
j.threadRight.Text = $"{tmax}";
|
||||||
j.compressionLeft.Text = String.Format("{0}", cmin);
|
j.compressionLeft.Text = $"{cmin}";
|
||||||
j.compressionRight.Text = String.Format("{0}", cmax);
|
j.compressionRight.Text = $"{cmax}";
|
||||||
|
|
||||||
DialogResult d = j.ShowDialog(hwnd);
|
DialogResult d = j.ShowDialog(hwnd);
|
||||||
|
|
||||||
|
@ -76,9 +69,11 @@ namespace BizHawk.Client.EmuHawk
|
||||||
|
|
||||||
j.Dispose();
|
j.Dispose();
|
||||||
if (d == DialogResult.OK)
|
if (d == DialogResult.OK)
|
||||||
|
{
|
||||||
return true;
|
return true;
|
||||||
else
|
}
|
||||||
return false;
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.IO.Compression;
|
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
using ICSharpCode.SharpZipLib.Zip.Compression.Streams;
|
using ICSharpCode.SharpZipLib.Zip.Compression.Streams;
|
||||||
using ICSharpCode.SharpZipLib.Zip.Compression;
|
using ICSharpCode.SharpZipLib.Zip.Compression;
|
||||||
|
|
||||||
|
@ -62,10 +61,12 @@ namespace BizHawk.Client.EmuHawk
|
||||||
/// stores compression parameters
|
/// stores compression parameters
|
||||||
/// </summary>
|
/// </summary>
|
||||||
CodecToken token;
|
CodecToken token;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// fps numerator, constant
|
/// fps numerator, constant
|
||||||
/// </summary>
|
/// </summary>
|
||||||
int fpsnum;
|
int fpsnum;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// fps denominator, constant
|
/// fps denominator, constant
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -75,10 +76,12 @@ namespace BizHawk.Client.EmuHawk
|
||||||
/// audio samplerate, constant
|
/// audio samplerate, constant
|
||||||
/// </summary>
|
/// </summary>
|
||||||
int audiosamplerate;
|
int audiosamplerate;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// audio number of channels, constant; 1 or 2 only
|
/// audio number of channels, constant; 1 or 2 only
|
||||||
/// </summary>
|
/// </summary>
|
||||||
int audiochannels;
|
int audiochannels;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// audio bits per sample, constant; only 16 supported
|
/// audio bits per sample, constant; only 16 supported
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -112,6 +115,7 @@ namespace BizHawk.Client.EmuHawk
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public UInt64 rerecords;
|
public UInt64 rerecords;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// represents the metadata for the active movie (if applicable)
|
/// represents the metadata for the active movie (if applicable)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -138,10 +142,12 @@ namespace BizHawk.Client.EmuHawk
|
||||||
/// current timestamp position
|
/// current timestamp position
|
||||||
/// </summary>
|
/// </summary>
|
||||||
UInt64 timestampoff;
|
UInt64 timestampoff;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// total number of video frames written
|
/// total number of video frames written
|
||||||
/// </summary>
|
/// </summary>
|
||||||
UInt64 totalframes;
|
UInt64 totalframes;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// total number of sample pairs written
|
/// total number of sample pairs written
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -151,14 +157,17 @@ namespace BizHawk.Client.EmuHawk
|
||||||
/// fps of the video stream is fpsnum/fpsden
|
/// fps of the video stream is fpsnum/fpsden
|
||||||
/// </summary>
|
/// </summary>
|
||||||
int fpsnum;
|
int fpsnum;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// fps of the video stream is fpsnum/fpsden
|
/// fps of the video stream is fpsnum/fpsden
|
||||||
/// </summary>
|
/// </summary>
|
||||||
int fpsden;
|
int fpsden;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// audio samplerate in hz
|
/// audio samplerate in hz
|
||||||
/// </summary>
|
/// </summary>
|
||||||
int audiosamplerate;
|
int audiosamplerate;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// true if input will be stereo; mono otherwise
|
/// true if input will be stereo; mono otherwise
|
||||||
/// output stream is always stereo
|
/// output stream is always stereo
|
||||||
|
@ -172,7 +181,9 @@ namespace BizHawk.Client.EmuHawk
|
||||||
public JMDfile(Stream f, int fpsnum, int fpsden, int audiosamplerate, bool stereo)
|
public JMDfile(Stream f, int fpsnum, int fpsden, int audiosamplerate, bool stereo)
|
||||||
{
|
{
|
||||||
if (!f.CanWrite)
|
if (!f.CanWrite)
|
||||||
|
{
|
||||||
throw new ArgumentException("Stream must be writable!");
|
throw new ArgumentException("Stream must be writable!");
|
||||||
|
}
|
||||||
|
|
||||||
this.f = f;
|
this.f = f;
|
||||||
this.fpsnum = fpsnum;
|
this.fpsnum = fpsnum;
|
||||||
|
@ -323,10 +334,13 @@ namespace BizHawk.Client.EmuHawk
|
||||||
/// encoding is similar to MIDI
|
/// encoding is similar to MIDI
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="v"></param>
|
/// <param name="v"></param>
|
||||||
void writeVar(int v)
|
private void writeVar(int v)
|
||||||
{
|
{
|
||||||
if (v < 0)
|
if (v < 0)
|
||||||
|
{
|
||||||
throw new ArgumentException("length cannot be less than 0!");
|
throw new ArgumentException("length cannot be less than 0!");
|
||||||
|
}
|
||||||
|
|
||||||
writeVar((UInt64)v);
|
writeVar((UInt64)v);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -355,7 +369,10 @@ namespace BizHawk.Client.EmuHawk
|
||||||
void writeActual(JMDPacket j)
|
void writeActual(JMDPacket j)
|
||||||
{
|
{
|
||||||
if (j.timestamp < timestampoff)
|
if (j.timestamp < timestampoff)
|
||||||
|
{
|
||||||
throw new ArithmeticException("JMD Timestamp problem?");
|
throw new ArithmeticException("JMD Timestamp problem?");
|
||||||
|
}
|
||||||
|
|
||||||
UInt64 timestampout = j.timestamp - timestampoff;
|
UInt64 timestampout = j.timestamp - timestampoff;
|
||||||
while (timestampout > 0xffffffff)
|
while (timestampout > 0xffffffff)
|
||||||
{
|
{
|
||||||
|
@ -453,6 +470,7 @@ namespace BizHawk.Client.EmuHawk
|
||||||
else
|
else
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
astorage.Enqueue(j);
|
astorage.Enqueue(j);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -56,10 +56,14 @@ namespace BizHawk.Client.EmuHawk
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public int IntValue { get { return Int32.Parse(this.Text); } }
|
public int IntValue => int.Parse(Text);
|
||||||
public decimal DecimalValue { get { return Decimal.Parse(this.Text); } }
|
|
||||||
public bool AllowSpace { set; get; }
|
public decimal DecimalValue => decimal.Parse(Text);
|
||||||
public bool AllowDecimal { set; get; }
|
|
||||||
public bool AllowNegative { set; get; }
|
public bool AllowSpace { get; set; }
|
||||||
|
|
||||||
|
public bool AllowDecimal { get; set; }
|
||||||
|
|
||||||
|
public bool AllowNegative { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -260,20 +260,11 @@ namespace BizHawk.Client.EmuHawk
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public override bool CanRead
|
public override bool CanRead => false;
|
||||||
{
|
|
||||||
get { return false; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public override bool CanSeek
|
public override bool CanSeek => false;
|
||||||
{
|
|
||||||
get { return false; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public override bool CanWrite
|
public override bool CanWrite => true;
|
||||||
{
|
|
||||||
get { return true; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// write data out to underlying stream, including header, footer, checksums
|
/// write data out to underlying stream, including header, footer, checksums
|
||||||
|
@ -286,7 +277,10 @@ namespace BizHawk.Client.EmuHawk
|
||||||
WriteBE64((ulong)startcode, header);
|
WriteBE64((ulong)startcode, header);
|
||||||
WriteVarU(data.Length + 4, header); // +4 for checksum
|
WriteVarU(data.Length + 4, header); // +4 for checksum
|
||||||
if (data.Length > 4092)
|
if (data.Length > 4092)
|
||||||
|
{
|
||||||
WriteBE32(NutCRC32(header.ToArray()), header);
|
WriteBE32(NutCRC32(header.ToArray()), header);
|
||||||
|
}
|
||||||
|
|
||||||
var tmp = header.ToArray();
|
var tmp = header.ToArray();
|
||||||
underlying.Write(tmp, 0, tmp.Length);
|
underlying.Write(tmp, 0, tmp.Length);
|
||||||
|
|
||||||
|
@ -517,7 +511,6 @@ namespace BizHawk.Client.EmuHawk
|
||||||
ReusableBufferPool<byte> _pool;
|
ReusableBufferPool<byte> _pool;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="payload">frame data</param>
|
/// <param name="payload">frame data</param>
|
||||||
/// <param name="payloadlen">actual length of frame data</param>
|
/// <param name="payloadlen">actual length of frame data</param>
|
||||||
|
@ -698,7 +691,6 @@ namespace BizHawk.Client.EmuHawk
|
||||||
WriteAudioFrame(new short[0]);
|
WriteAudioFrame(new short[0]);
|
||||||
|
|
||||||
// flush any remaining queued packets
|
// flush any remaining queued packets
|
||||||
|
|
||||||
while (audioqueue.Count > 0 && videoqueue.Count > 0)
|
while (audioqueue.Count > 0 && videoqueue.Count > 0)
|
||||||
{
|
{
|
||||||
if (audioqueue.Peek() <= videoqueue.Peek())
|
if (audioqueue.Peek() <= videoqueue.Peek())
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.IO;
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
using BizHawk.Emulation.Common;
|
using BizHawk.Emulation.Common;
|
||||||
|
|
||||||
|
@ -38,35 +36,34 @@ namespace BizHawk.Client.EmuHawk
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// avparams
|
/// avparams
|
||||||
/// </summary>
|
/// </summary>
|
||||||
int fpsnum, fpsden, width, height, sampleRate, channels;
|
private int fpsnum, fpsden, width, height, sampleRate, channels;
|
||||||
|
|
||||||
NutMuxer current = null;
|
private NutMuxer _current = null;
|
||||||
string baseName;
|
private string _baseName;
|
||||||
int segment;
|
private int _segment;
|
||||||
|
|
||||||
public void OpenFile(string baseName)
|
public void OpenFile(string baseName)
|
||||||
{
|
{
|
||||||
this.baseName = System.IO.Path.Combine(
|
_baseName = Path.Combine(
|
||||||
System.IO.Path.GetDirectoryName(baseName),
|
Path.GetDirectoryName(baseName),
|
||||||
System.IO.Path.GetFileNameWithoutExtension(baseName));
|
Path.GetFileNameWithoutExtension(baseName));
|
||||||
segment = 0;
|
_segment = 0;
|
||||||
|
|
||||||
startsegment();
|
startsegment();
|
||||||
}
|
}
|
||||||
|
|
||||||
void startsegment()
|
private void startsegment()
|
||||||
{
|
{
|
||||||
var currentfile = System.IO.File.Open(String.Format("{0}_{1,4:D4}.nut", baseName, segment), System.IO.FileMode.Create, System.IO.FileAccess.Write);
|
var currentfile = File.Open($"{_baseName}_{_segment,4:D4}.nut", FileMode.Create, FileAccess.Write);
|
||||||
current = new NutMuxer(width, height, fpsnum, fpsden, sampleRate, channels, currentfile);
|
_current = new NutMuxer(width, height, fpsnum, fpsden, sampleRate, channels, currentfile);
|
||||||
}
|
}
|
||||||
|
|
||||||
void endsegment()
|
private void endsegment()
|
||||||
{
|
{
|
||||||
current.Finish();
|
_current.Finish();
|
||||||
current = null;
|
_current = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void CloseFile()
|
public void CloseFile()
|
||||||
{
|
{
|
||||||
endsegment();
|
endsegment();
|
||||||
|
@ -75,24 +72,26 @@ namespace BizHawk.Client.EmuHawk
|
||||||
public void AddFrame(IVideoProvider source)
|
public void AddFrame(IVideoProvider source)
|
||||||
{
|
{
|
||||||
if (source.BufferHeight != height || source.BufferWidth != width)
|
if (source.BufferHeight != height || source.BufferWidth != width)
|
||||||
|
{
|
||||||
SetVideoParameters(source.BufferWidth, source.BufferHeight);
|
SetVideoParameters(source.BufferWidth, source.BufferHeight);
|
||||||
current.WriteVideoFrame(source.GetVideoBuffer());
|
}
|
||||||
|
|
||||||
|
_current.WriteVideoFrame(source.GetVideoBuffer());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AddSamples(short[] samples)
|
public void AddSamples(short[] samples)
|
||||||
{
|
{
|
||||||
current.WriteAudioFrame(samples);
|
_current.WriteAudioFrame(samples);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void SetMovieParameters(int fpsnum, int fpsden)
|
public void SetMovieParameters(int fpsnum, int fpsden)
|
||||||
{
|
{
|
||||||
this.fpsnum = fpsnum;
|
this.fpsnum = fpsnum;
|
||||||
this.fpsden = fpsden;
|
this.fpsden = fpsden;
|
||||||
if (current != null)
|
if (_current != null)
|
||||||
{
|
{
|
||||||
endsegment();
|
endsegment();
|
||||||
segment++;
|
_segment++;
|
||||||
startsegment();
|
startsegment();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -101,10 +100,10 @@ namespace BizHawk.Client.EmuHawk
|
||||||
{
|
{
|
||||||
this.width = width;
|
this.width = width;
|
||||||
this.height = height;
|
this.height = height;
|
||||||
if (current != null)
|
if (_current != null)
|
||||||
{
|
{
|
||||||
endsegment();
|
endsegment();
|
||||||
segment++;
|
_segment++;
|
||||||
startsegment();
|
startsegment();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -112,7 +111,10 @@ namespace BizHawk.Client.EmuHawk
|
||||||
public void SetAudioParameters(int sampleRate, int channels, int bits)
|
public void SetAudioParameters(int sampleRate, int channels, int bits)
|
||||||
{
|
{
|
||||||
if (bits != 16)
|
if (bits != 16)
|
||||||
|
{
|
||||||
throw new ArgumentOutOfRangeException(nameof(bits), "Audio depth must be 16 bit!");
|
throw new ArgumentOutOfRangeException(nameof(bits), "Audio depth must be 16 bit!");
|
||||||
|
}
|
||||||
|
|
||||||
this.sampleRate = sampleRate;
|
this.sampleRate = sampleRate;
|
||||||
this.channels = channels;
|
this.channels = channels;
|
||||||
}
|
}
|
||||||
|
@ -124,9 +126,12 @@ namespace BizHawk.Client.EmuHawk
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
if (current != null)
|
if (_current != null)
|
||||||
|
{
|
||||||
endsegment();
|
endsegment();
|
||||||
baseName = null;
|
}
|
||||||
|
|
||||||
|
_baseName = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string DesiredExtension()
|
public string DesiredExtension()
|
||||||
|
@ -139,7 +144,8 @@ namespace BizHawk.Client.EmuHawk
|
||||||
// ignored
|
// ignored
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool UsesAudio { get { return true; } }
|
public bool UsesAudio => true;
|
||||||
public bool UsesVideo { get { return true; } }
|
|
||||||
|
public bool UsesVideo => true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,9 @@
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel;
|
|
||||||
using System.Data;
|
|
||||||
using System.Drawing;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
|
|
||||||
using BizHawk.Emulation;
|
|
||||||
using BizHawk.Emulation.Common;
|
using BizHawk.Emulation.Common;
|
||||||
using BizHawk.Bizware.BizwareGL;
|
using BizHawk.Bizware.BizwareGL;
|
||||||
|
|
||||||
|
@ -17,20 +12,27 @@ namespace BizHawk.Client.EmuHawk
|
||||||
[VideoWriter("syncless", "Syncless Recording", "Writes each frame to a directory as a PNG and WAV pair, identified by frame number. The results can be exported into one video file.")]
|
[VideoWriter("syncless", "Syncless Recording", "Writes each frame to a directory as a PNG and WAV pair, identified by frame number. The results can be exported into one video file.")]
|
||||||
public class SynclessRecorder : IVideoWriter
|
public class SynclessRecorder : IVideoWriter
|
||||||
{
|
{
|
||||||
public void Dispose() { }
|
public void Dispose()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
public void SetVideoCodecToken(IDisposable token) { }
|
public void SetVideoCodecToken(IDisposable token)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
public void SetDefaultVideoCodecToken() { }
|
public void SetDefaultVideoCodecToken()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
public void SetFrame(int frame)
|
public void SetFrame(int frame)
|
||||||
{
|
{
|
||||||
mCurrFrame = frame;
|
mCurrFrame = frame;
|
||||||
}
|
}
|
||||||
|
|
||||||
int mCurrFrame;
|
private int mCurrFrame;
|
||||||
string mBaseDirectory, mFramesDirectory;
|
private string mBaseDirectory, mFramesDirectory;
|
||||||
string mProjectFile;
|
private string mProjectFile;
|
||||||
|
|
||||||
public void OpenFile(string projFile)
|
public void OpenFile(string projFile)
|
||||||
{
|
{
|
||||||
mProjectFile = projFile;
|
mProjectFile = projFile;
|
||||||
|
@ -44,7 +46,9 @@ namespace BizHawk.Client.EmuHawk
|
||||||
File.WriteAllText(mProjectFile, sb.ToString());
|
File.WriteAllText(mProjectFile, sb.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void CloseFile() { }
|
public void CloseFile()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
public void AddFrame(IVideoProvider source)
|
public void AddFrame(IVideoProvider source)
|
||||||
{
|
{
|
||||||
|
@ -68,12 +72,21 @@ namespace BizHawk.Client.EmuHawk
|
||||||
wwv.Dispose();
|
wwv.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool UsesAudio { get { return true; } }
|
public bool UsesAudio => true;
|
||||||
public bool UsesVideo { get { return true; } }
|
|
||||||
|
|
||||||
class DummyDisposable : IDisposable { public void Dispose() { } }
|
public bool UsesVideo => true;
|
||||||
|
|
||||||
public IDisposable AcquireVideoCodecToken(IWin32Window hwnd) { return new DummyDisposable(); }
|
private class DummyDisposable : IDisposable
|
||||||
|
{
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public IDisposable AcquireVideoCodecToken(IWin32Window hwnd)
|
||||||
|
{
|
||||||
|
return new DummyDisposable();
|
||||||
|
}
|
||||||
|
|
||||||
public void SetMovieParameters(int fpsnum, int fpsden)
|
public void SetMovieParameters(int fpsnum, int fpsden)
|
||||||
{
|
{
|
||||||
|
@ -82,10 +95,10 @@ namespace BizHawk.Client.EmuHawk
|
||||||
|
|
||||||
public void SetVideoParameters(int width, int height)
|
public void SetVideoParameters(int width, int height)
|
||||||
{
|
{
|
||||||
//may want to todo
|
// may want to todo
|
||||||
}
|
}
|
||||||
|
|
||||||
int paramSampleRate, paramChannels, paramBits;
|
private int paramSampleRate, paramChannels, paramBits;
|
||||||
|
|
||||||
public void SetAudioParameters(int sampleRate, int channels, int bits)
|
public void SetAudioParameters(int sampleRate, int channels, int bits)
|
||||||
{
|
{
|
||||||
|
@ -96,33 +109,42 @@ namespace BizHawk.Client.EmuHawk
|
||||||
|
|
||||||
public void SetMetaData(string gameName, string authors, UInt64 lengthMS, UInt64 rerecords)
|
public void SetMetaData(string gameName, string authors, UInt64 lengthMS, UInt64 rerecords)
|
||||||
{
|
{
|
||||||
//not needed
|
// not needed
|
||||||
}
|
}
|
||||||
|
|
||||||
public string DesiredExtension() { return "syncless.txt"; }
|
public string DesiredExtension()
|
||||||
|
{
|
||||||
|
return "syncless.txt";
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// splits the string into chunks of length s
|
/// splits the string into chunks of length s
|
||||||
/// </summary>
|
/// </summary>
|
||||||
static List<string> StringChunkSplit(string s, int len)
|
private static List<string> StringChunkSplit(string s, int len)
|
||||||
{
|
{
|
||||||
if (len == 0) throw new ArgumentException("Invalid len", nameof(len));
|
if (len == 0)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Invalid len", nameof(len));
|
||||||
|
}
|
||||||
|
|
||||||
int numChunks = (s.Length + len - 1) / len;
|
int numChunks = (s.Length + len - 1) / len;
|
||||||
List<string> output = new List<string>(numChunks);
|
var output = new List<string>(numChunks);
|
||||||
for (int i = 0, j = 0; i < numChunks; i++, j += len)
|
for (int i = 0, j = 0; i < numChunks; i++, j += len)
|
||||||
{
|
{
|
||||||
int todo = len;
|
int todo = len;
|
||||||
int remain = s.Length - j;
|
int remain = s.Length - j;
|
||||||
if (remain < todo) todo = remain;
|
if (remain < todo)
|
||||||
|
{
|
||||||
|
todo = remain;
|
||||||
|
}
|
||||||
|
|
||||||
output.Add(s.Substring(j, todo));
|
output.Add(s.Substring(j, todo));
|
||||||
}
|
}
|
||||||
|
|
||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
string GetAndCreatePathForFrameNum(int index)
|
private string GetAndCreatePathForFrameNum(int index)
|
||||||
{
|
{
|
||||||
string subpath = GetPathFragmentForFrameNum(index);
|
string subpath = GetPathFragmentForFrameNum(index);
|
||||||
string path = mFramesDirectory;
|
string path = mFramesDirectory;
|
||||||
|
@ -139,6 +161,4 @@ namespace BizHawk.Client.EmuHawk
|
||||||
return subpath;
|
return subpath;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,9 @@
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel;
|
|
||||||
using System.Data;
|
|
||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
|
|
||||||
using BizHawk.Bizware;
|
|
||||||
using BizHawk.Bizware.BizwareGL;
|
using BizHawk.Bizware.BizwareGL;
|
||||||
using BizHawk.Client.Common;
|
using BizHawk.Client.Common;
|
||||||
|
|
||||||
|
@ -21,7 +16,7 @@ namespace BizHawk.Client.EmuHawk
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
}
|
}
|
||||||
|
|
||||||
void GetPaths(int index, out string png, out string wav)
|
private void GetPaths(int index, out string png, out string wav)
|
||||||
{
|
{
|
||||||
string subpath = SynclessRecorder.GetPathFragmentForFrameNum(index);
|
string subpath = SynclessRecorder.GetPathFragmentForFrameNum(index);
|
||||||
string path = mFramesDirectory;
|
string path = mFramesDirectory;
|
||||||
|
@ -30,23 +25,28 @@ namespace BizHawk.Client.EmuHawk
|
||||||
wav = path + ".wav";
|
wav = path + ".wav";
|
||||||
}
|
}
|
||||||
|
|
||||||
string mSynclessConfigFile;
|
private string mSynclessConfigFile;
|
||||||
string mFramesDirectory;
|
private string mFramesDirectory;
|
||||||
|
|
||||||
public void Run()
|
public void Run()
|
||||||
{
|
{
|
||||||
var ofd = new OpenFileDialog();
|
var ofd = new OpenFileDialog
|
||||||
ofd.FileName = PathManager.FilesystemSafeName(Global.Game) + ".syncless.txt";
|
{
|
||||||
ofd.InitialDirectory = PathManager.MakeAbsolutePath(Global.Config.PathEntries.AvPathFragment, null);
|
FileName = PathManager.FilesystemSafeName(Global.Game) + ".syncless.txt",
|
||||||
|
InitialDirectory = PathManager.MakeAbsolutePath(Global.Config.PathEntries.AvPathFragment, null)
|
||||||
|
};
|
||||||
|
|
||||||
if (ofd.ShowDialog() == DialogResult.Cancel)
|
if (ofd.ShowDialog() == DialogResult.Cancel)
|
||||||
|
{
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
mSynclessConfigFile = ofd.FileName;
|
mSynclessConfigFile = ofd.FileName;
|
||||||
|
|
||||||
//---- this is pretty crappy:
|
//---- this is pretty crappy:
|
||||||
var lines = File.ReadAllLines(mSynclessConfigFile);
|
var lines = File.ReadAllLines(mSynclessConfigFile);
|
||||||
|
|
||||||
string framesdir = "";
|
string framesdir = string.Empty;
|
||||||
foreach (var line in lines)
|
foreach (var line in lines)
|
||||||
{
|
{
|
||||||
int idx = line.IndexOf('=');
|
int idx = line.IndexOf('=');
|
||||||
|
@ -60,15 +60,18 @@ namespace BizHawk.Client.EmuHawk
|
||||||
|
|
||||||
mFramesDirectory = Path.Combine(Path.GetDirectoryName(Path.GetFullPath(mSynclessConfigFile)), framesdir);
|
mFramesDirectory = Path.Combine(Path.GetDirectoryName(Path.GetFullPath(mSynclessConfigFile)), framesdir);
|
||||||
|
|
||||||
//scan frames directory
|
// scan frames directory
|
||||||
int frame = 1; //hacky! skip frame 0, because we have a problem with dumping that frame somehow
|
int frame = 1; // hacky! skip frame 0, because we have a problem with dumping that frame somehow
|
||||||
for (; ; )
|
for (;;)
|
||||||
{
|
{
|
||||||
string wav, png;
|
string wav, png;
|
||||||
GetPaths(frame, out png, out wav);
|
GetPaths(frame, out png, out wav);
|
||||||
if (!File.Exists(png) || !File.Exists(wav))
|
if (!File.Exists(png) || !File.Exists(wav))
|
||||||
|
{
|
||||||
break;
|
break;
|
||||||
mFrameInfos.Add(new FrameInfo()
|
}
|
||||||
|
|
||||||
|
mFrameInfos.Add(new FrameInfo
|
||||||
{
|
{
|
||||||
pngPath = png,
|
pngPath = png,
|
||||||
wavPath = wav
|
wavPath = wav
|
||||||
|
@ -80,7 +83,8 @@ namespace BizHawk.Client.EmuHawk
|
||||||
ShowDialog();
|
ShowDialog();
|
||||||
}
|
}
|
||||||
|
|
||||||
List<FrameInfo> mFrameInfos = new List<FrameInfo>();
|
private readonly List<FrameInfo> mFrameInfos = new List<FrameInfo>();
|
||||||
|
|
||||||
struct FrameInfo
|
struct FrameInfo
|
||||||
{
|
{
|
||||||
public string wavPath, pngPath;
|
public string wavPath, pngPath;
|
||||||
|
@ -89,7 +93,10 @@ namespace BizHawk.Client.EmuHawk
|
||||||
|
|
||||||
private void btnExport_Click(object sender, EventArgs e)
|
private void btnExport_Click(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
if(mFrameInfos.Count == 0) return;
|
if (mFrameInfos.Count == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
int width, height;
|
int width, height;
|
||||||
using(var bmp = new Bitmap(mFrameInfos[0].pngPath))
|
using(var bmp = new Bitmap(mFrameInfos[0].pngPath))
|
||||||
|
@ -98,16 +105,20 @@ namespace BizHawk.Client.EmuHawk
|
||||||
height = bmp.Height;
|
height = bmp.Height;
|
||||||
}
|
}
|
||||||
|
|
||||||
var sfd = new SaveFileDialog();
|
var sfd = new SaveFileDialog
|
||||||
sfd.FileName = Path.ChangeExtension(mSynclessConfigFile, ".avi");
|
{
|
||||||
|
FileName = Path.ChangeExtension(mSynclessConfigFile, ".avi")
|
||||||
|
};
|
||||||
sfd.InitialDirectory = Path.GetDirectoryName(sfd.FileName);
|
sfd.InitialDirectory = Path.GetDirectoryName(sfd.FileName);
|
||||||
if (sfd.ShowDialog() == DialogResult.Cancel)
|
if (sfd.ShowDialog() == DialogResult.Cancel)
|
||||||
return;
|
|
||||||
|
|
||||||
using (AviWriter avw = new AviWriter())
|
|
||||||
{
|
{
|
||||||
avw.SetAudioParameters(44100, 2, 16); //hacky
|
return;
|
||||||
avw.SetMovieParameters(60, 1); //hacky
|
}
|
||||||
|
|
||||||
|
using (var avw = new AviWriter())
|
||||||
|
{
|
||||||
|
avw.SetAudioParameters(44100, 2, 16); // hacky
|
||||||
|
avw.SetMovieParameters(60, 1); // hacky
|
||||||
avw.SetVideoParameters(width, height);
|
avw.SetVideoParameters(width, height);
|
||||||
var token = avw.AcquireVideoCodecToken(this);
|
var token = avw.AcquireVideoCodecToken(this);
|
||||||
avw.SetVideoCodecToken(token);
|
avw.SetVideoCodecToken(token);
|
||||||
|
@ -119,21 +130,22 @@ namespace BizHawk.Client.EmuHawk
|
||||||
var bbvp = new BitmapBufferVideoProvider(bb);
|
var bbvp = new BitmapBufferVideoProvider(bb);
|
||||||
avw.AddFrame(bbvp);
|
avw.AddFrame(bbvp);
|
||||||
}
|
}
|
||||||
//offset = 44 dec
|
|
||||||
|
// offset = 44 dec
|
||||||
var wavBytes = File.ReadAllBytes(fi.wavPath);
|
var wavBytes = File.ReadAllBytes(fi.wavPath);
|
||||||
var ms = new MemoryStream(wavBytes);
|
var ms = new MemoryStream(wavBytes) { Position = 44 };
|
||||||
ms.Position = 44;
|
|
||||||
var br = new BinaryReader(ms);
|
var br = new BinaryReader(ms);
|
||||||
List<short> sampledata = new List<short>();
|
var sampledata = new List<short>();
|
||||||
while (br.BaseStream.Position != br.BaseStream.Length)
|
while (br.BaseStream.Position != br.BaseStream.Length)
|
||||||
{
|
{
|
||||||
sampledata.Add(br.ReadInt16());
|
sampledata.Add(br.ReadInt16());
|
||||||
}
|
}
|
||||||
|
|
||||||
avw.AddSamples(sampledata.ToArray());
|
avw.AddSamples(sampledata.ToArray());
|
||||||
}
|
}
|
||||||
|
|
||||||
avw.CloseFile();
|
avw.CloseFile();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,5 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel;
|
|
||||||
using System.Data;
|
|
||||||
using System.Drawing;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
|
|
||||||
using BizHawk.Client.Common;
|
using BizHawk.Client.Common;
|
||||||
|
@ -16,7 +11,7 @@ namespace BizHawk.Client.EmuHawk
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public partial class VideoWriterChooserForm : Form
|
public partial class VideoWriterChooserForm : Form
|
||||||
{
|
{
|
||||||
VideoWriterChooserForm()
|
private VideoWriterChooserForm()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
|
||||||
|
@ -32,14 +27,19 @@ namespace BizHawk.Client.EmuHawk
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
lblSize.Text = string.Format("Size:\r\n{0}x{1}", CaptureWidth, CaptureHeight);
|
lblSize.Text = $"Size:\r\n{CaptureWidth}x{CaptureHeight}";
|
||||||
|
|
||||||
if (CaptureWidth % 4 != 0 || CaptureHeight % 4 != 0)
|
if (CaptureWidth % 4 != 0 || CaptureHeight % 4 != 0)
|
||||||
|
{
|
||||||
lblResolutionWarning.Visible = true;
|
lblResolutionWarning.Visible = true;
|
||||||
else lblResolutionWarning.Visible = false;
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
lblResolutionWarning.Visible = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int CaptureWidth, CaptureHeight;
|
private int CaptureWidth, CaptureHeight;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// chose an IVideoWriter
|
/// chose an IVideoWriter
|
||||||
|
@ -49,27 +49,32 @@ namespace BizHawk.Client.EmuHawk
|
||||||
/// <returns>user choice, or null on Cancel\Close\invalid</returns>
|
/// <returns>user choice, or null on Cancel\Close\invalid</returns>
|
||||||
public static IVideoWriter DoVideoWriterChoserDlg(IEnumerable<VideoWriterInfo> list, IWin32Window owner, out int resizew, out int resizeh, out bool pad, out bool audiosync)
|
public static IVideoWriter DoVideoWriterChoserDlg(IEnumerable<VideoWriterInfo> list, IWin32Window owner, out int resizew, out int resizeh, out bool pad, out bool audiosync)
|
||||||
{
|
{
|
||||||
VideoWriterChooserForm dlg = new VideoWriterChooserForm();
|
VideoWriterChooserForm dlg = new VideoWriterChooserForm
|
||||||
|
|
||||||
dlg.labelDescriptionBody.Text = "";
|
|
||||||
|
|
||||||
{
|
{
|
||||||
int idx = 0;
|
labelDescriptionBody = { Text = string.Empty }
|
||||||
int idx_select = -1;
|
};
|
||||||
dlg.listBox1.BeginUpdate();
|
|
||||||
foreach (var vw in list)
|
int idx = 0;
|
||||||
|
int idx_select = -1;
|
||||||
|
dlg.listBox1.BeginUpdate();
|
||||||
|
foreach (var vw in list)
|
||||||
|
{
|
||||||
|
dlg.listBox1.Items.Add(vw);
|
||||||
|
if (vw.Attribs.ShortName == Global.Config.VideoWriter)
|
||||||
{
|
{
|
||||||
dlg.listBox1.Items.Add(vw);
|
idx_select = idx;
|
||||||
if (vw.Attribs.ShortName == Global.Config.VideoWriter)
|
|
||||||
idx_select = idx;
|
|
||||||
idx++;
|
|
||||||
}
|
}
|
||||||
dlg.listBox1.SelectedIndex = idx_select;
|
|
||||||
dlg.listBox1.EndUpdate();
|
idx++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dlg.listBox1.SelectedIndex = idx_select;
|
||||||
|
dlg.listBox1.EndUpdate();
|
||||||
|
|
||||||
foreach (Control c in dlg.panelSizeSelect.Controls)
|
foreach (Control c in dlg.panelSizeSelect.Controls)
|
||||||
|
{
|
||||||
c.Enabled = false;
|
c.Enabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
DialogResult result = dlg.ShowDialog(owner);
|
DialogResult result = dlg.ShowDialog(owner);
|
||||||
|
|
||||||
|
@ -106,16 +111,17 @@ namespace BizHawk.Client.EmuHawk
|
||||||
|
|
||||||
private void listBox1_SelectedIndexChanged(object sender, EventArgs e)
|
private void listBox1_SelectedIndexChanged(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
if (listBox1.SelectedIndex != -1)
|
labelDescriptionBody.Text = listBox1.SelectedIndex != -1
|
||||||
labelDescriptionBody.Text = ((VideoWriterInfo)listBox1.SelectedItem).Attribs.Description;
|
? ((VideoWriterInfo)listBox1.SelectedItem).Attribs.Description
|
||||||
else
|
: string.Empty;
|
||||||
labelDescriptionBody.Text = "";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkBoxResize_CheckedChanged(object sender, EventArgs e)
|
private void checkBoxResize_CheckedChanged(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
foreach (Control c in panelSizeSelect.Controls)
|
foreach (Control c in panelSizeSelect.Controls)
|
||||||
|
{
|
||||||
c.Enabled = checkBoxResize.Checked;
|
c.Enabled = checkBoxResize.Checked;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void buttonAuto_Click(object sender, EventArgs e)
|
private void buttonAuto_Click(object sender, EventArgs e)
|
||||||
|
@ -134,14 +140,12 @@ namespace BizHawk.Client.EmuHawk
|
||||||
{
|
{
|
||||||
MessageBox.Show(this, "Size must be positive!");
|
MessageBox.Show(this, "Size must be positive!");
|
||||||
DialogResult = DialogResult.None;
|
DialogResult = DialogResult.None;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (FormatException)
|
catch (FormatException)
|
||||||
{
|
{
|
||||||
MessageBox.Show(this, "Size must be numeric!");
|
MessageBox.Show(this, "Size must be numeric!");
|
||||||
DialogResult = DialogResult.None;
|
DialogResult = DialogResult.None;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
|
||||||
|
@ -19,6 +18,7 @@ namespace BizHawk.Client.EmuHawk
|
||||||
/// underlying file being written to
|
/// underlying file being written to
|
||||||
/// </summary>
|
/// </summary>
|
||||||
BinaryWriter file;
|
BinaryWriter file;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// sequence of files to write to (split on 32 bit limit)
|
/// sequence of files to write to (split on 32 bit limit)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -28,6 +28,7 @@ namespace BizHawk.Client.EmuHawk
|
||||||
/// samplerate in HZ
|
/// samplerate in HZ
|
||||||
/// </summary>
|
/// </summary>
|
||||||
int samplerate;
|
int samplerate;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// number of audio channels
|
/// number of audio channels
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -46,7 +47,7 @@ namespace BizHawk.Client.EmuHawk
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// write riff headers to current file
|
/// write riff headers to current file
|
||||||
/// </summary>
|
/// </summary>
|
||||||
void writeheaders()
|
private void writeheaders()
|
||||||
{
|
{
|
||||||
file.Write(Encoding.ASCII.GetBytes("RIFF")); // ChunkID
|
file.Write(Encoding.ASCII.GetBytes("RIFF")); // ChunkID
|
||||||
file.Write((uint)0); // ChunkSize
|
file.Write((uint)0); // ChunkSize
|
||||||
|
@ -68,7 +69,7 @@ namespace BizHawk.Client.EmuHawk
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// seek back to beginning of file and fix header sizes (if possible)
|
/// seek back to beginning of file and fix header sizes (if possible)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
void finalizeheaders()
|
private void finalizeheaders()
|
||||||
{
|
{
|
||||||
if (numbytes + 36 >= 0x100000000)
|
if (numbytes + 36 >= 0x100000000)
|
||||||
// passed 4G limit, nothing to be done
|
// passed 4G limit, nothing to be done
|
||||||
|
@ -88,7 +89,7 @@ namespace BizHawk.Client.EmuHawk
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// close current underlying stream
|
/// close current underlying stream
|
||||||
/// </summary>
|
/// </summary>
|
||||||
void closecurrent()
|
private void closecurrent()
|
||||||
{
|
{
|
||||||
if (file != null)
|
if (file != null)
|
||||||
{
|
{
|
||||||
|
@ -96,13 +97,15 @@ namespace BizHawk.Client.EmuHawk
|
||||||
file.Close();
|
file.Close();
|
||||||
file.Dispose();
|
file.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
file = null;
|
file = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// open a new underlying stream
|
/// open a new underlying stream
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="next"></param>
|
/// <param name="next"></param>
|
||||||
void opencurrent(Stream next)
|
private void opencurrent(Stream next)
|
||||||
{
|
{
|
||||||
file = new BinaryWriter(next, Encoding.ASCII);
|
file = new BinaryWriter(next, Encoding.ASCII);
|
||||||
numbytes = 0;
|
numbytes = 0;
|
||||||
|
@ -150,10 +153,12 @@ namespace BizHawk.Client.EmuHawk
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// checks sampling rate, number of channels for validity
|
/// checks sampling rate, number of channels for validity
|
||||||
/// </summary>
|
/// </summary>
|
||||||
void checkargs()
|
private void checkargs()
|
||||||
{
|
{
|
||||||
if (samplerate < 1 || numchannels < 1)
|
if (samplerate < 1 || numchannels < 1)
|
||||||
|
{
|
||||||
throw new ArgumentException("Bad samplerate/numchannels");
|
throw new ArgumentException("Bad samplerate/numchannels");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -186,9 +191,13 @@ namespace BizHawk.Client.EmuHawk
|
||||||
this.numchannels = numchannels;
|
this.numchannels = numchannels;
|
||||||
checkargs();
|
checkargs();
|
||||||
filechain = ss;
|
filechain = ss;
|
||||||
|
|
||||||
// advance to first
|
// advance to first
|
||||||
if (!filechain.MoveNext())
|
if (!filechain.MoveNext())
|
||||||
|
{
|
||||||
throw new ArgumentException("Iterator was empty!");
|
throw new ArgumentException("Iterator was empty!");
|
||||||
|
}
|
||||||
|
|
||||||
opencurrent(ss.Current);
|
opencurrent(ss.Current);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -205,13 +214,15 @@ namespace BizHawk.Client.EmuHawk
|
||||||
public void SetVideoParameters(int width, int height) { }
|
public void SetVideoParameters(int width, int height) { }
|
||||||
public void SetFrame(int frame) { }
|
public void SetFrame(int frame) { }
|
||||||
|
|
||||||
public bool UsesAudio { get { return true; } }
|
public bool UsesAudio => true;
|
||||||
public bool UsesVideo { get { return false; } }
|
|
||||||
|
|
||||||
class WavWriterVToken : IDisposable
|
public bool UsesVideo => false;
|
||||||
|
|
||||||
|
private class WavWriterVToken : IDisposable
|
||||||
{
|
{
|
||||||
public void Dispose() { }
|
public void Dispose() { }
|
||||||
}
|
}
|
||||||
|
|
||||||
public IDisposable AcquireVideoCodecToken(System.Windows.Forms.IWin32Window hwnd)
|
public IDisposable AcquireVideoCodecToken(System.Windows.Forms.IWin32Window hwnd)
|
||||||
{
|
{
|
||||||
// don't care
|
// don't care
|
||||||
|
@ -223,7 +234,9 @@ namespace BizHawk.Client.EmuHawk
|
||||||
this.sampleRate = sampleRate;
|
this.sampleRate = sampleRate;
|
||||||
this.channels = channels;
|
this.channels = channels;
|
||||||
if (bits != 16)
|
if (bits != 16)
|
||||||
|
{
|
||||||
throw new ArgumentException("Only support 16bit audio!");
|
throw new ArgumentException("Only support 16bit audio!");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetMetaData(string gameName, string authors, ulong lengthMS, ulong rerecords)
|
public void SetMetaData(string gameName, string authors, ulong lengthMS, ulong rerecords)
|
||||||
|
@ -233,20 +246,17 @@ namespace BizHawk.Client.EmuHawk
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
if (wavwriter != null)
|
wavwriter?.Dispose();
|
||||||
wavwriter.Dispose();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
WavWriter wavwriter = null;
|
private WavWriter wavwriter = null;
|
||||||
int sampleRate = 0;
|
private int sampleRate = 0;
|
||||||
int channels = 0;
|
private int channels = 0;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// create a simple wav stream iterator
|
/// create a simple wav stream iterator
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="template"></param>
|
private static IEnumerator<Stream> CreateStreamIterator(string template)
|
||||||
/// <returns></returns>
|
|
||||||
static IEnumerator<Stream> CreateStreamIterator(string template)
|
|
||||||
{
|
{
|
||||||
string dir = Path.GetDirectoryName(template);
|
string dir = Path.GetDirectoryName(template);
|
||||||
string baseName = Path.GetFileNameWithoutExtension(template);
|
string baseName = Path.GetFileNameWithoutExtension(template);
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Data;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=StyleCop_002ESA1210/@EntryIndexedValue">DO_NOT_SHOW</s:String>
|
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=StyleCop_002ESA1210/@EntryIndexedValue">DO_NOT_SHOW</s:String>
|
||||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=AF/@EntryIndexedValue">AF</s:String>
|
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=AF/@EntryIndexedValue">AF</s:String>
|
||||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=ARGB/@EntryIndexedValue">ARGB</s:String>
|
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=ARGB/@EntryIndexedValue">ARGB</s:String>
|
||||||
|
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=AV/@EntryIndexedValue">AV</s:String>
|
||||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=BMP/@EntryIndexedValue">BMP</s:String>
|
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=BMP/@EntryIndexedValue">BMP</s:String>
|
||||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=CDL/@EntryIndexedValue">CDL</s:String>
|
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=CDL/@EntryIndexedValue">CDL</s:String>
|
||||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=CGB/@EntryIndexedValue">CGB</s:String>
|
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=CGB/@EntryIndexedValue">CGB</s:String>
|
||||||
|
|
Loading…
Reference in New Issue