Redesign a/v writer selection. A simple dialog is shown with a list of all IVideoWriter possibilities

This commit is contained in:
goyuken 2012-06-13 19:50:50 +00:00
parent e1a3f687b5
commit f69fc08012
11 changed files with 439 additions and 39 deletions

View File

@ -652,6 +652,22 @@ namespace BizHawk.MultiClient
}
}
public override string ToString()
{
return "avi writer";
}
public string WriterDescription()
{
return "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.";
}
public string DesiredExtension()
{
return "avi";
}
}
}

View File

@ -314,6 +314,12 @@
<DependentUpon>HexColor.cs</DependentUpon>
</Compile>
<Compile Include="tools\WatchCommon.cs" />
<Compile Include="VideoWriterChooserForm.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="VideoWriterChooserForm.Designer.cs">
<DependentUpon>VideoWriterChooserForm.cs</DependentUpon>
</Compile>
<Compile Include="WavWriter.cs" />
<EmbeddedResource Include="config\GifAnimator.resx">
<DependentUpon>GifAnimator.cs</DependentUpon>
@ -366,6 +372,9 @@
<DependentUpon>Resources.resx</DependentUpon>
<DesignTime>True</DesignTime>
</Compile>
<EmbeddedResource Include="VideoWriterChooserForm.resx">
<DependentUpon>VideoWriterChooserForm.cs</DependentUpon>
</EmbeddedResource>
<None Include="Properties\Settings.settings">
<Generator>SettingsSingleFileGenerator</Generator>
<LastGenOutput>Settings.Designer.cs</LastGenOutput>

View File

@ -241,5 +241,22 @@ namespace BizHawk.MultiClient
this.sampleRate = sampleRate;
this.channels = channels;
}
public override string ToString()
{
return "ffmpeg writer";
}
public string WriterDescription()
{
return "Uses an external FFMPEG process to encode video and audio. Various formats supported. Splits on resolution change.";
}
public string DesiredExtension()
{
// this needs to interface with the codec token
return "mkv";
}
}
}

View File

@ -75,5 +75,13 @@ namespace BizHawk
/// <param name="rerecords">Number of rerecords on movie file</param>
void SetMetaData(string gameName, string authors, UInt64 lengthMS, UInt64 rerecords);
/// <summary>
/// short description of this IVideoWriter
/// </summary>
string WriterDescription();
/// <summary>
/// what default extension this writer would like to put on its output
/// </summary>
string DesiredExtension();
}
}

View File

@ -764,5 +764,21 @@ namespace BizHawk.MultiClient
moviemetadata.lengthms = lengthMS;
moviemetadata.rerecords = rerecords;
}
public override string ToString()
{
return "JMD writer";
}
public string WriterDescription()
{
return "Writes a JPC-rr multidump file (JMD). These can be read and further processed with jpc-streamtools. One JMD file contains all audio (uncompressed) and video (compressed).";
}
public string DesiredExtension()
{
return "jmd";
}
}
}

View File

@ -2737,71 +2737,83 @@ namespace BizHawk.MultiClient
public void RecordAVI()
{
if (CurrAviWriter != null) return;
var sfd = new SaveFileDialog();
if (!(Global.Emulator is NullEmulator))
{
sfd.FileName = PathManager.FilesystemSafeName(Global.Game);
sfd.InitialDirectory = PathManager.MakeAbsolutePath(Global.Config.AVIPath, "");
}
else
{
sfd.FileName = "NULL";
sfd.InitialDirectory = PathManager.MakeAbsolutePath(Global.Config.AVIPath, "");
}
sfd.Filter = "AVI (*.avi)|*.avi|JMD (*.jmd)|*.jmd|WAV (*.wav)|*.wav|Matroska (*.mkv)|*.mkv|NUT (*.nut)|*.nut|All Files|*.*";
Global.Sound.StopSound();
var result = sfd.ShowDialog();
Global.Sound.StartSound();
if (result == DialogResult.Cancel)
// select IVideoWriter to use
var writers = new IVideoWriter[]{new AviWriter(), new JMDWriter(), new WavWriterV(), new FFmpegWriter(), new NutWriter()};
IVideoWriter aw = VideoWriterChooserForm.DoVideoWriterChoserDlg(writers, Global.MainForm);
foreach (var w in writers)
{
if (w != aw)
w.Dispose();
}
if (aw == null)
{
Global.OSD.AddMessage("A/V capture canceled.");
return;
}
//TODO - cores should be able to specify exact values for these instead of relying on this to calculate them
int fps = (int)(Global.Emulator.CoreOutputComm.VsyncRate * 0x01000000);
IVideoWriter aw;
string ext = Path.GetExtension(sfd.FileName).ToLower();
if (ext == ".jmd")
aw = new JMDWriter();
else if (ext == ".avi")
aw = new AviWriter();
else if (ext == ".wav")
aw = new WavWriterV();
else if (ext == ".mkv")
aw = new FFmpegWriter();
else if (ext == ".nut")
aw = new NutWriter();
else // hmm?
aw = new AviWriter();
try
{
//TODO - cores should be able to specify exact values for these instead of relying on this to calculate them
int fps = (int)(Global.Emulator.CoreOutputComm.VsyncRate * 0x01000000);
aw.SetMovieParameters(fps, 0x01000000);
aw.SetVideoParameters(Global.Emulator.VideoProvider.BufferWidth, Global.Emulator.VideoProvider.BufferHeight);
aw.SetAudioParameters(44100, 2, 16);
// 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);
if (token == null)
{
Global.OSD.AddMessage("AVI capture canceled.");
Global.OSD.AddMessage("A/V capture canceled.");
aw.Dispose();
return;
}
aw.SetVideoCodecToken(token);
// select file to save to
var sfd = new SaveFileDialog();
if (!(Global.Emulator is NullEmulator))
{
sfd.FileName = PathManager.FilesystemSafeName(Global.Game);
sfd.InitialDirectory = PathManager.MakeAbsolutePath(Global.Config.AVIPath, "");
}
else
{
sfd.FileName = "NULL";
sfd.InitialDirectory = PathManager.MakeAbsolutePath(Global.Config.AVIPath, "");
}
sfd.Filter = String.Format("{0} (*.{0})|*.{0}|All Files|*.*", aw.DesiredExtension());
Global.Sound.StopSound();
var result = sfd.ShowDialog();
Global.Sound.StartSound();
if (result == DialogResult.Cancel)
{
aw.Dispose();
return;
}
aw.SetVideoCodecToken(token);
aw.OpenFile(sfd.FileName);
//commit the avi writing last, in case there were any errors earlier
CurrAviWriter = aw;
Global.OSD.AddMessage("AVI capture started");
Global.OSD.AddMessage("A/V capture started");
AVIStatusLabel.Image = BizHawk.MultiClient.Properties.Resources.AVI;
AVIStatusLabel.ToolTipText = "AVI capture in progress";
AVIStatusLabel.ToolTipText = "A/V capture in progress";
}
catch
{
Global.OSD.AddMessage("AVI capture failed!");
Global.OSD.AddMessage("A/V capture failed!");
aw.Dispose();
throw;
}
// buffersize here is entirely guess
DumpProxy = new Emulation.Sound.Utilities.DualSound(Global.Emulator.SoundProvider, 8192);
}

View File

@ -123,5 +123,21 @@ namespace BizHawk.MultiClient
public void Dispose()
{
}
public override string ToString()
{
return ".nut writer";
}
public string WriterDescription()
{
return "Writes a series of .nut files to disk, a container format which can be opened by ffmpeg. All data is uncompressed. Splits occur on resolution changes. NOT RECCOMENDED FOR USE.";
}
public string DesiredExtension()
{
return "nut";
}
}
}

View File

@ -0,0 +1,112 @@
namespace BizHawk.MultiClient
{
partial class VideoWriterChooserForm
{
/// <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.listBox1 = new System.Windows.Forms.ListBox();
this.button1 = new System.Windows.Forms.Button();
this.button2 = new System.Windows.Forms.Button();
this.label1 = new System.Windows.Forms.Label();
this.label2 = new System.Windows.Forms.Label();
this.SuspendLayout();
//
// listBox1
//
this.listBox1.FormattingEnabled = true;
this.listBox1.Location = new System.Drawing.Point(12, 21);
this.listBox1.Name = "listBox1";
this.listBox1.Size = new System.Drawing.Size(279, 186);
this.listBox1.TabIndex = 0;
this.listBox1.SelectedIndexChanged += new System.EventHandler(this.listBox1_SelectedIndexChanged);
//
// button1
//
this.button1.DialogResult = System.Windows.Forms.DialogResult.OK;
this.button1.Location = new System.Drawing.Point(297, 21);
this.button1.Name = "button1";
this.button1.Size = new System.Drawing.Size(96, 32);
this.button1.TabIndex = 1;
this.button1.Text = "OK";
this.button1.UseVisualStyleBackColor = true;
//
// button2
//
this.button2.Location = new System.Drawing.Point(297, 59);
this.button2.Name = "button2";
this.button2.Size = new System.Drawing.Size(96, 37);
this.button2.TabIndex = 2;
this.button2.Text = "Cancel";
this.button2.UseVisualStyleBackColor = true;
//
// label1
//
this.label1.AutoSize = true;
this.label1.Location = new System.Drawing.Point(9, 224);
this.label1.Name = "label1";
this.label1.Size = new System.Drawing.Size(35, 13);
this.label1.TabIndex = 3;
this.label1.Text = "label1";
//
// label2
//
this.label2.AutoSize = true;
this.label2.Location = new System.Drawing.Point(10, 252);
this.label2.MaximumSize = new System.Drawing.Size(370, 0);
this.label2.Name = "label2";
this.label2.Size = new System.Drawing.Size(35, 13);
this.label2.TabIndex = 4;
this.label2.Text = "label2";
//
// VideoWriterChooserForm
//
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(405, 313);
this.Controls.Add(this.label2);
this.Controls.Add(this.label1);
this.Controls.Add(this.button2);
this.Controls.Add(this.button1);
this.Controls.Add(this.listBox1);
this.Name = "VideoWriterChooserForm";
this.Text = "Choose A\\V Writer";
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private System.Windows.Forms.ListBox listBox1;
private System.Windows.Forms.Button button1;
private System.Windows.Forms.Button button2;
private System.Windows.Forms.Label label1;
private System.Windows.Forms.Label label2;
}
}

View File

@ -0,0 +1,59 @@
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>
/// implements a simple dialog which chooses an IVideoWriter to record with
/// </summary>
public partial class VideoWriterChooserForm : Form
{
public VideoWriterChooserForm()
{
InitializeComponent();
}
/// <summary>
/// chose an IVideoWriter
/// </summary>
/// <param name="list">list of IVideoWriters to choose from</param>
/// <param name="owner">parent window</param>
/// <returns>user choice, or null on Cancel\Close\invalid</returns>
public static IVideoWriter DoVideoWriterChoserDlg(IVideoWriter[] list, IWin32Window owner)
{
VideoWriterChooserForm dlg = new VideoWriterChooserForm();
dlg.label1.Text = "Description:";
dlg.label2.Text = "";
dlg.listBox1.Items.AddRange(list);
DialogResult result = dlg.ShowDialog(owner);
IVideoWriter ret;
if (result == DialogResult.OK && dlg.listBox1.SelectedIndex != -1)
ret = (IVideoWriter)dlg.listBox1.SelectedItem;
else
ret = null;
dlg.Dispose();
return ret;
}
private void listBox1_SelectedIndexChanged(object sender, EventArgs e)
{
if (listBox1.SelectedIndex != -1)
label2.Text = ((IVideoWriter)listBox1.SelectedItem).WriterDescription();
else
label2.Text = "";
}
}
}

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

@ -268,5 +268,20 @@ namespace BizHawk.MultiClient
{
wavwriter.writesamples(samples);
}
public override string ToString()
{
return ".wav writer";
}
public string WriterDescription()
{
return "Writes a series of standard RIFF wav files containing uncompressed audio. Does not write video. Splits every 2G.";
}
public string DesiredExtension()
{
return "wav";
}
}
}