Add FFmpegWriterForm, dialog choosing what ffmpeg format to write to

Fix some error handling and cleanup
This commit is contained in:
goyuken 2012-06-16 16:51:47 +00:00
parent 540be07cf2
commit 42c9a78047
13 changed files with 467 additions and 58 deletions

View File

@ -196,7 +196,7 @@ namespace BizHawk.MultiClient
/// 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.
/// returns null if the user canceled the dialog
/// </summary>
public IDisposable AcquireVideoCodecToken(IntPtr hwnd) //, CodecToken lastToken)
public IDisposable AcquireVideoCodecToken(System.Windows.Forms.IWin32Window hwnd) //, CodecToken lastToken)
{
var temp_params = new Parameters();
temp_params.height = 256;
@ -211,7 +211,7 @@ namespace BizHawk.MultiClient
File.Delete(tempfile);
tempfile = Path.ChangeExtension(tempfile, "avi");
temp.OpenFile(tempfile, temp_params, null); //lastToken);
CodecToken token = (CodecToken) temp.AcquireVideoCodecToken(hwnd);
CodecToken token = (CodecToken) temp.AcquireVideoCodecToken(hwnd.Handle);
temp.CloseFile();
File.Delete(tempfile);
return token;

View File

@ -165,6 +165,12 @@
<Compile Include="DisplayManager\DisplayManager.cs" />
<Compile Include="DisplayManager\Filters\Hq2x.cs" />
<Compile Include="FFmpegWriter.cs" />
<Compile Include="FFmpegWriterForm.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="FFmpegWriterForm.Designer.cs">
<DependentUpon>FFmpegWriterForm.cs</DependentUpon>
</Compile>
<Compile Include="Gameboy\Debugger.cs">
<SubType>Form</SubType>
</Compile>
@ -324,6 +330,9 @@
<EmbeddedResource Include="config\GifAnimator.resx">
<DependentUpon>GifAnimator.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="FFmpegWriterForm.resx">
<DependentUpon>FFmpegWriterForm.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="Gameboy\Debugger.resx">
<DependentUpon>Debugger.cs</DependentUpon>
<SubType>Designer</SubType>

View File

@ -46,9 +46,20 @@ namespace BizHawk.MultiClient
/// </summary>
NutMuxer muxer;
/// <summary>
/// codec token in use
/// </summary>
FFmpegWriterForm.FormatPreset token;
/// <summary>
/// file extension actually used
/// </summary>
string ext;
public void OpenFile(string baseName)
{
string s = System.IO.Path.GetFileNameWithoutExtension(baseName);
ext = System.IO.Path.GetExtension(baseName);
this.baseName = s;
@ -64,13 +75,9 @@ namespace BizHawk.MultiClient
ffmpeg = new Process();
ffmpeg.StartInfo.FileName = "ffmpeg";
string filename = String.Format("{0}_{1,4:D4}", baseName, segment);
string filename = String.Format("{0}_{1,4:D4}{2}", baseName, segment, ext);
ffmpeg.StartInfo.Arguments = String.Format
(
"-y -f nut -i - -vcodec libx264rgb -acodec pcm_s16le -crf 0 \"{0}.mkv\"",
filename
);
ffmpeg.StartInfo.Arguments = String.Format("-y -f nut -i - {1} \"{0}\"", filename, token.commandline);
ffmpeg.StartInfo.CreateNoWindow = true;
@ -145,7 +152,6 @@ namespace BizHawk.MultiClient
while (stderr.Count > 0)
{
var foo = stderr.Dequeue();
//System.Windows.Forms.MessageBox.Show(foo);
s.Append(foo);
}
return s.ToString();
@ -163,31 +169,31 @@ namespace BizHawk.MultiClient
var b = new byte[a.Length * sizeof (int)];
Buffer.BlockCopy(a, 0, b, 0, b.Length);
muxer.writevideoframe(b);
try
{
muxer.writevideoframe(b);
}
catch
{
System.Windows.Forms.MessageBox.Show("Exception! ffmpeg history:\n" + ffmpeg_geterror());
throw;
}
// have to do binary write!
//ffmpeg.StandardInput.BaseStream.Write(b, 0, b.Length);
}
/// <summary>
/// codec token for FFmpegWriter
/// </summary>
class FFmpegWriterToken : IDisposable
public IDisposable AcquireVideoCodecToken(System.Windows.Forms.IWin32Window hwnd)
{
public void Dispose()
{
}
}
public IDisposable AcquireVideoCodecToken(IntPtr hwnd)
{
return new FFmpegWriterToken();
return FFmpegWriterForm.DoFFmpegWriterDlg(hwnd);
}
public void SetVideoCodecToken(IDisposable token)
{
// nyi
if (token is FFmpegWriterForm.FormatPreset)
this.token = (FFmpegWriterForm.FormatPreset)token;
else
throw new ArgumentException("FFmpegWriter can only take its own codec tokens!");
}
/// <summary>
@ -231,7 +237,17 @@ namespace BizHawk.MultiClient
public void AddSamples(short[] samples)
{
muxer.writeaudioframe(samples);
if (ffmpeg.HasExited)
throw new Exception("unexpected ffmpeg death:\n" + ffmpeg_geterror());
try
{
muxer.writeaudioframe(samples);
}
catch
{
System.Windows.Forms.MessageBox.Show("Exception! ffmpeg history:\n" + ffmpeg_geterror());
throw;
}
}
public void SetAudioParameters(int sampleRate, int channels, int bits)
@ -256,7 +272,7 @@ namespace BizHawk.MultiClient
public string DesiredExtension()
{
// this needs to interface with the codec token
return "mkv";
return token.defaultext;
}
}
}

View File

@ -0,0 +1,159 @@
namespace BizHawk.MultiClient
{
partial class FFmpegWriterForm
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.label1 = new System.Windows.Forms.Label();
this.listBox1 = new System.Windows.Forms.ListBox();
this.label2 = new System.Windows.Forms.Label();
this.label3 = new System.Windows.Forms.Label();
this.label4 = new System.Windows.Forms.Label();
this.textBox1 = new System.Windows.Forms.TextBox();
this.button1 = new System.Windows.Forms.Button();
this.button2 = new System.Windows.Forms.Button();
this.label5 = new System.Windows.Forms.Label();
this.SuspendLayout();
//
// label1
//
this.label1.AutoSize = true;
this.label1.Location = new System.Drawing.Point(5, 5);
this.label1.Name = "label1";
this.label1.Size = new System.Drawing.Size(47, 13);
this.label1.TabIndex = 0;
this.label1.Text = "Formats:";
//
// listBox1
//
this.listBox1.FormattingEnabled = true;
this.listBox1.Location = new System.Drawing.Point(5, 23);
this.listBox1.Name = "listBox1";
this.listBox1.Size = new System.Drawing.Size(275, 147);
this.listBox1.TabIndex = 1;
this.listBox1.SelectedIndexChanged += new System.EventHandler(this.listBox1_SelectedIndexChanged);
//
// label2
//
this.label2.AutoSize = true;
this.label2.Location = new System.Drawing.Point(7, 176);
this.label2.Name = "label2";
this.label2.Size = new System.Drawing.Size(63, 13);
this.label2.TabIndex = 2;
this.label2.Text = "Description:";
//
// label3
//
this.label3.AutoSize = true;
this.label3.Location = new System.Drawing.Point(6, 193);
this.label3.MaximumSize = new System.Drawing.Size(260, 0);
this.label3.Name = "label3";
this.label3.Size = new System.Drawing.Size(0, 13);
this.label3.TabIndex = 3;
//
// label4
//
this.label4.AutoSize = true;
this.label4.Location = new System.Drawing.Point(10, 255);
this.label4.Name = "label4";
this.label4.Size = new System.Drawing.Size(57, 13);
this.label4.TabIndex = 4;
this.label4.Text = "Command:";
//
// textBox1
//
this.textBox1.Location = new System.Drawing.Point(8, 273);
this.textBox1.Name = "textBox1";
this.textBox1.ReadOnly = true;
this.textBox1.Size = new System.Drawing.Size(272, 20);
this.textBox1.TabIndex = 5;
//
// button1
//
this.button1.DialogResult = System.Windows.Forms.DialogResult.OK;
this.button1.Location = new System.Drawing.Point(65, 314);
this.button1.Name = "button1";
this.button1.Size = new System.Drawing.Size(75, 23);
this.button1.TabIndex = 6;
this.button1.Text = "OK";
this.button1.UseVisualStyleBackColor = true;
//
// button2
//
this.button2.DialogResult = System.Windows.Forms.DialogResult.Cancel;
this.button2.Location = new System.Drawing.Point(146, 314);
this.button2.Name = "button2";
this.button2.Size = new System.Drawing.Size(75, 23);
this.button2.TabIndex = 7;
this.button2.Text = "Cancel";
this.button2.UseVisualStyleBackColor = true;
//
// label5
//
this.label5.AutoSize = true;
this.label5.Location = new System.Drawing.Point(159, 176);
this.label5.Name = "label5";
this.label5.Size = new System.Drawing.Size(56, 13);
this.label5.TabIndex = 8;
this.label5.Text = "Extension:";
//
// FFmpegWriterForm
//
this.AcceptButton = this.button1;
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.CancelButton = this.button2;
this.ClientSize = new System.Drawing.Size(292, 349);
this.Controls.Add(this.label5);
this.Controls.Add(this.button2);
this.Controls.Add(this.button1);
this.Controls.Add(this.textBox1);
this.Controls.Add(this.label4);
this.Controls.Add(this.label3);
this.Controls.Add(this.label2);
this.Controls.Add(this.listBox1);
this.Controls.Add(this.label1);
this.Name = "FFmpegWriterForm";
this.Text = "Choose Video Format";
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private System.Windows.Forms.Label label1;
private System.Windows.Forms.ListBox listBox1;
private System.Windows.Forms.Label label2;
private System.Windows.Forms.Label label3;
private System.Windows.Forms.Label label4;
private System.Windows.Forms.TextBox textBox1;
private System.Windows.Forms.Button button1;
private System.Windows.Forms.Button button2;
private System.Windows.Forms.Label label5;
}
}

View File

@ -0,0 +1,127 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace BizHawk.MultiClient
{
/// <summary>
/// configures the FFmpegWriter
/// </summary>
public partial class FFmpegWriterForm : Form
{
/// <summary>
/// stores a single format preset
/// </summary>
public class FormatPreset : IDisposable
{
/// <summary>
/// name for listbox
/// </summary>
public string name;
/// <summary>
/// long human readable description
/// </summary>
public string desc;
/// <summary>
/// actual portion of ffmpeg commandline
/// </summary>
public string commandline;
public override string ToString()
{
return name;
}
/// <summary>
/// can be edited
/// </summary>
public bool custom = false;
/// <summary>
/// default file extension
/// </summary>
public string defaultext;
FormatPreset(string name, string desc, string commandline, bool custom, string defaultext)
{
this.name = name;
this.desc = desc;
this.commandline = commandline;
this.custom = custom;
this.defaultext = defaultext;
}
/// <summary>
/// get a list of canned presets
/// </summary>
/// <returns></returns>
public static FormatPreset[] GetPresets()
{
return new FormatPreset[]
{
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("Lossless Compressed AVI", "AVI file with zlib video and uncompressed audio.", "-c:a pcm_s16le -c:v zlib -f avi", false, "avi"),
new FormatPreset("FLV", "avc+aac in flash container.", "-c:a libvo_aacenc -c:v libx264 -f flv", false, "flv"),
new FormatPreset("Matroska Lossless", "MKV file with lossless video and audio", "-c:a pcm_s16le -c:v libx264rgb -crf 0 -f matroska", false, "mkv"),
new FormatPreset("Matroska", "MKV file with h264 + vorbis", "-c:a libvorbis -c:v libx264 -f matroska", false, "mkv"),
new FormatPreset("QuickTime", "MOV file with avc+aac", "-c:a libvo_aacenc -c:v libx264 -f mov", false, "mov"),
new FormatPreset("Ogg", "Theora + Vorbis in OGG", "-c:a libvorbis -c:v libtheora -f ogg", false, "ogg"),
new FormatPreset("WebM", "Vp8 + Vorbis in WebM", "-c:a libvorbis -c:v libvpx -f webm", false, "webm"),
new FormatPreset("mp4", "ISO mp4 with AVC+AAC", "-c:a libvo_aacenc -c:v libx264 -f mp4", false, "mp4"),
new FormatPreset("[Custom]", "Write your own ffmpeg command. For advanced users only", "-c:a foo -c:v bar -f baz", true, "foobar"),
};
}
public void Dispose()
{
}
}
public FFmpegWriterForm()
{
InitializeComponent();
}
private void listBox1_SelectedIndexChanged(object sender, EventArgs e)
{
if (listBox1.SelectedIndex != -1)
{
FormatPreset f = (FormatPreset)listBox1.SelectedItem;
label5.Text = "Extension: " + f.defaultext;
label3.Text = f.desc;
textBox1.Text = f.commandline;
textBox1.ReadOnly = !f.custom;
}
}
/// <summary>
/// return a formatpreset corresponding to the user's choice
/// </summary>
/// <param name="owner"></param>
/// <returns></returns>
public static FormatPreset DoFFmpegWriterDlg(IWin32Window owner)
{
FFmpegWriterForm dlg = new FFmpegWriterForm();
dlg.listBox1.Items.AddRange(FormatPreset.GetPresets());
DialogResult result = dlg.ShowDialog(owner);
FormatPreset ret;
if (result != DialogResult.OK || dlg.listBox1.SelectedIndex == -1)
ret = null;
else
{
ret = (FormatPreset)dlg.listBox1.SelectedItem;
if (ret.custom)
ret.commandline = dlg.textBox1.Text;
}
dlg.Dispose();
return ret;
}
}
}

View File

@ -0,0 +1,120 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

View File

@ -43,7 +43,7 @@ namespace BizHawk
/// </summary>
/// <param name="hwnd">hwnd to attach to if the user is shown config dialog</param>
/// <returns>codec token, dispose of it when you're done with it</returns>
IDisposable AcquireVideoCodecToken(IntPtr hwnd);
IDisposable AcquireVideoCodecToken(System.Windows.Forms.IWin32Window hwnd);
/// <summary>
/// set framerate to fpsnum/fpsden (assumed to be unchanging over the life of the stream)

View File

@ -42,26 +42,6 @@ namespace BizHawk.MultiClient
compressionTop.Text = String.Format("Compression Level: {0}", compressionBar.Value);
}
/// <summary>
/// minimal IWin32Window wrapper around an IntPtr hwnd
/// </summary>
class WindowWrapper : IWin32Window
{
/// <summary>
/// create an instance of WindowWrapper
/// </summary>
/// <param name="handle">hwnd to store</param>
public WindowWrapper (IntPtr handle)
{
hwnd = handle;
}
public IntPtr Handle
{
get { return hwnd; }
}
IntPtr hwnd;
}
/// <summary>
/// Show a configuration dialog (modal) for JMDWriter
/// </summary>
@ -73,7 +53,7 @@ namespace BizHawk.MultiClient
/// <param name="cmax">maximum compression level</param>
/// <param name="hwnd">hwnd of parent</param>
/// <returns>false if user canceled; true if user consented</returns>
public static bool DoCompressionDlg(ref int threads, ref int complevel, int tmin, int tmax, int cmin, int cmax, IntPtr hwnd)
public static bool DoCompressionDlg(ref int threads, ref int complevel, int tmin, int tmax, int cmin, int cmax, System.Windows.Forms.IWin32Window hwnd)
{
JMDForm j = new JMDForm();
j.threadsBar.Minimum = tmin;
@ -89,7 +69,7 @@ namespace BizHawk.MultiClient
j.compressionLeft.Text = String.Format("{0}", cmin);
j.compressionRight.Text = String.Format("{0}", cmax);
DialogResult d = j.ShowDialog(new WindowWrapper(hwnd));
DialogResult d = j.ShowDialog(hwnd);
threads = j.threadsBar.Value;
complevel = j.compressionBar.Value;

View File

@ -538,7 +538,7 @@ namespace BizHawk.MultiClient
/// </summary>
/// <param name="hwnd">hwnd to attach to if the user is shown config dialog</param>
/// <returns>codec token, dispose of it when you're done with it</returns>
public IDisposable AcquireVideoCodecToken(IntPtr hwnd)
public IDisposable AcquireVideoCodecToken(System.Windows.Forms.IWin32Window hwnd)
{
CodecToken ret = new CodecToken();

View File

@ -2765,7 +2765,7 @@ namespace BizHawk.MultiClient
// select codec token
// do this before save dialog because ffmpeg won't know what extension it wants until it's been configured
var token = aw.AcquireVideoCodecToken(Global.MainForm.Handle);
var token = aw.AcquireVideoCodecToken(Global.MainForm);
if (token == null)
{
Global.OSD.AddMessage("A/V capture canceled.");

View File

@ -505,10 +505,10 @@ namespace BizHawk.MultiClient
static NutFrame()
{
dbg = new StreamWriter(".\\nutframe.txt", false);
//dbg = new StreamWriter(".\\nutframe.txt", false);
}
static StreamWriter dbg;
//static StreamWriter dbg;
/// <summary>
/// write out frame, with syncpoint and all headers
@ -516,10 +516,8 @@ namespace BizHawk.MultiClient
/// <param name="dest"></param>
public void WriteData(Stream dest)
{
dest.Write(data, 0, data.Length);
dbg.WriteLine(string.Format("{0},{1},{2}", pts, ptsnum, ptsden));
//dbg.WriteLine(string.Format("{0},{1},{2}", pts, ptsnum, ptsden));
}
}

View File

@ -26,7 +26,7 @@ namespace BizHawk.MultiClient
{
// ignored
}
public IDisposable AcquireVideoCodecToken(IntPtr hwnd)
public IDisposable AcquireVideoCodecToken(System.Windows.Forms.IWin32Window hwnd)
{
return new NutWriterToken();
}

View File

@ -204,7 +204,7 @@ namespace BizHawk.MultiClient
{
public void Dispose() { }
}
public IDisposable AcquireVideoCodecToken(IntPtr hwnd)
public IDisposable AcquireVideoCodecToken(System.Windows.Forms.IWin32Window hwnd)
{
// don't care
return new WavWriterVToken();