diff --git a/BizHawk.MultiClient/BizHawk.MultiClient.csproj b/BizHawk.MultiClient/BizHawk.MultiClient.csproj index e967543f74..fe940cacfc 100644 --- a/BizHawk.MultiClient/BizHawk.MultiClient.csproj +++ b/BizHawk.MultiClient/BizHawk.MultiClient.csproj @@ -164,6 +164,7 @@ + Form diff --git a/BizHawk.MultiClient/FFmpegWriter.cs b/BizHawk.MultiClient/FFmpegWriter.cs new file mode 100644 index 0000000000..4464f670ef --- /dev/null +++ b/BizHawk.MultiClient/FFmpegWriter.cs @@ -0,0 +1,229 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Net.Sockets; +using System.Net; +using System.Diagnostics; + +namespace BizHawk.MultiClient +{ + /// + /// uses tcp sockets to launch an external ffmpeg process and encode + /// + class FFmpegWriter : WavWriterV, IVideoWriter + { + /// + /// handle to external ffmpeg process + /// + Process ffmpeg; + + /// + /// the commandline actually sent to ffmpeg; for informative purposes + /// + string commandline; + + /// + /// current file segment (for multires) + /// + int segment; + + /// + /// base filename before segment number is attached + /// + string baseName; + + /// + /// recent lines in ffmpeg's stderr, for informative purposes + /// + Queue stderr; + + /// + /// number of lines of stderr to buffer + /// + const int consolebuffer = 5; + + public new void OpenFile(string baseName) + { + string s = System.IO.Path.GetFileNameWithoutExtension(baseName); + + base.OpenFile(s + ".wav"); + + this.baseName = s; + + segment = 0; + OpenFileSegment(); + } + + /// + /// starts an ffmpeg process and sets up associated sockets + /// + void OpenFileSegment() + { + ffmpeg = new Process(); + ffmpeg.StartInfo.FileName = "ffmpeg"; + + string filename = String.Format("{0}_{1,4:D4}", baseName, segment); + + ffmpeg.StartInfo.Arguments = String.Format + ( + "-y -f rawvideo -pix_fmt bgra -s {0}x{1} -r {2}/{3} -i - -vcodec libx264rgb -crf 0 \"{4}.mkv\"", + width, + height, + fpsnum, + fpsden, + filename + ); + + ffmpeg.StartInfo.CreateNoWindow = true; + + // ffmpeg sends informative display to stderr, and nothing to stdout + ffmpeg.StartInfo.RedirectStandardError = true; + ffmpeg.StartInfo.RedirectStandardInput = true; + ffmpeg.StartInfo.UseShellExecute = false; + + commandline = "ffmpeg " + ffmpeg.StartInfo.Arguments; + + ffmpeg.ErrorDataReceived += new DataReceivedEventHandler(StderrHandler); + + stderr = new Queue(consolebuffer); + + ffmpeg.Start(); + ffmpeg.BeginErrorReadLine(); + } + + /// + /// saves stderr lines from ffmpeg in a short queue + /// + /// + /// + void StderrHandler(object p, DataReceivedEventArgs line) + { + if (!String.IsNullOrEmpty(line.Data)) + { + if (stderr.Count == consolebuffer) + stderr.Dequeue(); + stderr.Enqueue(line.Data + "\n"); + } + } + + /// + /// finishes an ffmpeg process + /// + void CloseFileSegment() + { + ffmpeg.StandardInput.Close(); + + // how long should we wait here? + ffmpeg.WaitForExit(20000); + ffmpeg = null; + stderr = null; + commandline = null; + } + + + public new void CloseFile() + { + CloseFileSegment(); + baseName = null; + base.CloseFile(); + } + + /// + /// returns a string containing the commandline sent to ffmpeg and recent console (stderr) output + /// + /// + string ffmpeg_geterror() + { + if (ffmpeg.StartInfo.RedirectStandardError) + { + ffmpeg.CancelErrorRead(); + } + StringBuilder s = new StringBuilder(); + s.Append(commandline); + s.Append('\n'); + while (stderr.Count > 0) + { + var foo = stderr.Dequeue(); + System.Windows.Forms.MessageBox.Show(foo); + s.Append(foo); + } + return s.ToString(); + } + + + public new void AddFrame(IVideoProvider source) + { + if (source.BufferWidth != width || source.BufferHeight != height) + SetVideoParameters(source.BufferWidth, source.BufferHeight); + + if (ffmpeg.HasExited) + throw new Exception("unexpected ffmpeg death:\n" + ffmpeg_geterror()); + var a = source.GetVideoBuffer(); + var b = new byte[a.Length * sizeof (int)]; + Buffer.BlockCopy(a, 0, b, 0, b.Length); + // have to do binary write! + ffmpeg.StandardInput.BaseStream.Write(b, 0, b.Length); + } + + + /// + /// codec token for FFmpegWriter + /// + class FFmpegWriterToken : IDisposable + { + public void Dispose() + { + } + } + + public new IDisposable AcquireVideoCodecToken(IntPtr hwnd) + { + return new FFmpegWriterToken(); + } + + public new void SetVideoCodecToken(IDisposable token) + { + // nyi + } + + /// + /// video params + /// + int fpsnum, fpsden, width, height; + + public new void SetMovieParameters(int fpsnum, int fpsden) + { + this.fpsnum = fpsnum; + this.fpsden = fpsden; + } + + public new void SetVideoParameters(int width, int height) + { + this.width = width; + this.height = height; + + /* ffmpeg theoretically supports variable resolution videos, but there's no way to + * signal that metadata in a raw pipe. so if we're currently in a segment, + * start a new one */ + if (ffmpeg != null) + { + CloseFileSegment(); + segment++; + OpenFileSegment(); + } + } + + + public new void SetMetaData(string gameName, string authors, ulong lengthMS, ulong rerecords) + { + // can be implemented with ffmpeg "-metadata" parameter??? + // nyi + } + + public new void Dispose() + { + base.Dispose(); + } + } +} diff --git a/BizHawk.MultiClient/MainForm.cs b/BizHawk.MultiClient/MainForm.cs index 1ab04f3388..92f20ec0a8 100644 --- a/BizHawk.MultiClient/MainForm.cs +++ b/BizHawk.MultiClient/MainForm.cs @@ -2748,7 +2748,7 @@ namespace BizHawk.MultiClient sfd.FileName = "NULL"; sfd.InitialDirectory = PathManager.MakeAbsolutePath(Global.Config.AVIPath, ""); } - sfd.Filter = "AVI (*.avi)|*.avi|JMD (*.jmd)|*.jmd|WAV (*.wav)|*.wav|All Files|*.*"; + sfd.Filter = "AVI (*.avi)|*.avi|JMD (*.jmd)|*.jmd|WAV (*.wav)|*.wav|Matroska (*.mkv)|*.mkv|All Files|*.*"; Global.Sound.StopSound(); var result = sfd.ShowDialog(); Global.Sound.StartSound(); @@ -2768,6 +2768,8 @@ namespace BizHawk.MultiClient aw = new AviWriter(); else if (ext == ".wav") aw = new WavWriterV(); + else if (ext == ".mkv") + aw = new FFmpegWriter(); else // hmm? aw = new AviWriter(); try diff --git a/BizHawk.MultiClient/WavWriter.cs b/BizHawk.MultiClient/WavWriter.cs index 22ff286ec2..f915bd7ed5 100644 --- a/BizHawk.MultiClient/WavWriter.cs +++ b/BizHawk.MultiClient/WavWriter.cs @@ -254,7 +254,7 @@ namespace BizHawk.MultiClient public void OpenFile(string baseName) { - wavwriter = new WavWriter(CreateStreamIterator (baseName), sampleRate, channels); + wavwriter = new WavWriter(CreateStreamIterator(baseName), sampleRate, channels); } public void CloseFile()