diff --git a/BizHawk.Client.EmuHawk/AVOut/AviWriter.cs b/BizHawk.Client.EmuHawk/AVOut/AviWriter.cs index e1ebca51de..84e02d7bec 100644 --- a/BizHawk.Client.EmuHawk/AVOut/AviWriter.cs +++ b/BizHawk.Client.EmuHawk/AVOut/AviWriter.cs @@ -228,7 +228,7 @@ namespace BizHawk.Client.EmuHawk File.Delete(tempfile); tempfile = Path.ChangeExtension(tempfile, "avi"); temp.OpenFile(tempfile, temp_params, null); //lastToken); - CodecToken token = (CodecToken)temp.AcquireVideoCodecToken(hwnd.Handle); + CodecToken token = (CodecToken)temp.AcquireVideoCodecToken(hwnd.Handle, currVideoCodecToken); temp.CloseFile(); File.Delete(tempfile); return token; @@ -346,66 +346,59 @@ namespace BizHawk.Client.EmuHawk public class CodecToken : IDisposable { - ~CodecToken() - { - Dispose(); - } - public static CodecToken TakePossession(Win32.AVICOMPRESSOPTIONS comprOptions) + public void Dispose() { } + private CodecToken() { } + private Win32.AVICOMPRESSOPTIONS comprOptions; + public string codec; + public byte[] Format = new byte[0]; + public byte[] Parms = new byte[0]; + + public static CodecToken CreateFromAVICOMPRESSOPTIONS(ref Win32.AVICOMPRESSOPTIONS opts) { CodecToken ret = new CodecToken(); - ret.allocated = true; - ret.comprOptions = comprOptions; - ret.codec = Win32.decode_mmioFOURCC(comprOptions.fccHandler); + ret.comprOptions = opts; + ret.codec = Win32.decode_mmioFOURCC(opts.fccHandler); + ret.Format = new byte[opts.cbFormat]; + ret.Parms = new byte[opts.cbParms]; + if(opts.lpFormat != 0) Marshal.Copy(new IntPtr(opts.lpFormat), ret.Format, 0, opts.cbFormat); + if (opts.lpParms != 0) Marshal.Copy(new IntPtr(opts.lpParms), ret.Parms, 0, opts.cbParms); + return ret; } - private CodecToken() { } - public Win32.AVICOMPRESSOPTIONS comprOptions; - public string codec; - /// - /// true if data was allocated by AviSaveOptions and should be freed by AVISaveOptionsFree - /// - bool allocated = false; - /// - /// true if data was allocated by AllocHGlobal and should be freed by FreeHGlobal - /// - bool marshaled = false; - public void Dispose() + + + [DllImport("kernel32.dll", SetLastError = true)] + public static extern bool HeapFree(IntPtr hHeap, uint dwFlags, int lpMem); + [DllImport("kernel32.dll", SetLastError = true)] + public static extern IntPtr GetProcessHeap(); + [DllImport("kernel32.dll", SetLastError = false)] + public static extern IntPtr HeapAlloc(IntPtr hHeap, uint dwFlags, int dwBytes); + + public static void DeallocateAVICOMPRESSOPTIONS(ref Win32.AVICOMPRESSOPTIONS opts) { - if (allocated) - { - IntPtr[] infPtrs = new IntPtr[1]; - IntPtr mem; - - // alloc unmanaged memory - mem = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(Win32.AVICOMPRESSOPTIONS))); - infPtrs[0] = mem; - - // copy from managed structure to unmanaged memory - Marshal.StructureToPtr(comprOptions, mem, false); - - Win32.AVISaveOptionsFree(1, infPtrs); - Marshal.FreeHGlobal(mem); - - codec = null; - comprOptions = new Win32.AVICOMPRESSOPTIONS(); - allocated = false; - } - if (marshaled) - { - IntPtr p; - p = (IntPtr)comprOptions.lpFormat; - if (p != IntPtr.Zero) - Marshal.FreeHGlobal(p); - p = (IntPtr)comprOptions.lpParms; - if (p != IntPtr.Zero) - Marshal.FreeHGlobal(p); - - codec = null; - comprOptions = new Win32.AVICOMPRESSOPTIONS(); - marshaled = false; - } + //test: increase stability by never freeing anything, ever + //if (opts.lpParms != 0) CodecToken.HeapFree(CodecToken.GetProcessHeap(), 0, opts.lpParms); + //if (opts.lpFormat != 0) CodecToken.HeapFree(CodecToken.GetProcessHeap(), 0, opts.lpFormat); + opts.lpParms = 0; + opts.lpFormat = 0; } + + public void AllocateToAVICOMPRESSOPTIONS(ref Win32.AVICOMPRESSOPTIONS opts) + { + opts = comprOptions; + if (opts.cbParms != 0) + { + opts.lpParms = HeapAlloc(GetProcessHeap(), 0, opts.cbParms).ToInt32(); + Marshal.Copy(Parms, 0, new IntPtr(opts.lpParms), opts.cbParms); + } + if (opts.cbFormat != 0) + { + opts.lpFormat = HeapAlloc(GetProcessHeap(), 0, opts.cbFormat).ToInt32(); + Marshal.Copy(Format, 0, new IntPtr(opts.lpFormat), opts.cbFormat); + } + } + byte[] SerializeToByteArray() { var m = new MemoryStream(); @@ -422,17 +415,8 @@ namespace BizHawk.Client.EmuHawk //b.Write(comprOptions.lpParms); b.Write(comprOptions.cbParms); b.Write(comprOptions.dwInterleaveEvery); - - // make opaque copies of the unmanaged structs pointed to - byte[] Format = new byte[comprOptions.cbFormat]; - byte[] Params = new byte[comprOptions.cbParms]; - if (comprOptions.lpFormat != 0) - Marshal.Copy(new IntPtr(comprOptions.lpFormat), Format, 0, Format.Length); - if (comprOptions.lpParms != 0) - Marshal.Copy(new IntPtr(comprOptions.lpParms), Params, 0, Params.Length); - b.Write(Format); - b.Write(Params); + b.Write(Parms); b.Close(); return m.ToArray(); } @@ -445,7 +429,7 @@ namespace BizHawk.Client.EmuHawk Win32.AVICOMPRESSOPTIONS comprOptions = new Win32.AVICOMPRESSOPTIONS(); byte[] Format; - byte[] Params; + byte[] Parms; try { @@ -463,7 +447,7 @@ namespace BizHawk.Client.EmuHawk comprOptions.dwInterleaveEvery = b.ReadInt32(); Format = b.ReadBytes(comprOptions.cbFormat); - Params = b.ReadBytes(comprOptions.cbParms); + Parms = b.ReadBytes(comprOptions.cbParms); } catch (IOException) { @@ -475,27 +459,10 @@ namespace BizHawk.Client.EmuHawk b.Close(); } - // create unmanaged copies of Format, Params - if (comprOptions.cbFormat != 0) - { - IntPtr lpFormat = Marshal.AllocHGlobal(comprOptions.cbFormat); - Marshal.Copy(Format, 0, lpFormat, comprOptions.cbFormat); - comprOptions.lpFormat = (int)lpFormat; - } - else - comprOptions.lpFormat = (int)IntPtr.Zero; - if (comprOptions.cbParms != 0) - { - IntPtr lpParms = Marshal.AllocHGlobal(comprOptions.cbParms); - Marshal.Copy(Params, 0, lpParms, comprOptions.cbParms); - comprOptions.lpParms = (int)lpParms; - } - else - comprOptions.lpParms = (int)IntPtr.Zero; - CodecToken ret = new CodecToken(); - ret.marshaled = true; ret.comprOptions = comprOptions; + ret.Format = Format; + ret.Parms = Parms; ret.codec = Win32.decode_mmioFOURCC(comprOptions.fccHandler); return ret; } @@ -573,23 +540,15 @@ namespace BizHawk.Client.EmuHawk public long GetLengthApproximation() { return outStatus.video_bytes + outStatus.audio_bytes; } - static int AVISaveOptions(IntPtr stream, ref Win32.AVICOMPRESSOPTIONS opts, IntPtr owner) + static unsafe int AVISaveOptions(IntPtr stream, ref Win32.AVICOMPRESSOPTIONS opts, IntPtr owner) { - IntPtr mem = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(Win32.AVICOMPRESSOPTIONS))); - - Marshal.StructureToPtr(opts, mem, false); - - IntPtr[] streams = new[] { stream }; - IntPtr[] infPtrs = new[] { mem }; - - int ret = Win32.AVISaveOptions(owner, 0, 1, streams, infPtrs); - - if (ret == 1) - opts = (Win32.AVICOMPRESSOPTIONS)Marshal.PtrToStructure(mem, typeof(Win32.AVICOMPRESSOPTIONS)); - - Marshal.FreeHGlobal(mem); - - return ret; + fixed (Win32.AVICOMPRESSOPTIONS* _popts = &opts) + { + IntPtr* pStream = &stream; + Win32.AVICOMPRESSOPTIONS* popts = _popts; + Win32.AVICOMPRESSOPTIONS** ppopts = &popts; + return Win32.AVISaveOptions(owner, 0, 1, (void*)pStream, (void*)ppopts); + } } Parameters parameters; @@ -641,28 +600,40 @@ namespace BizHawk.Client.EmuHawk IsOpen = true; } + /// /// Acquires a video codec configuration from the user /// - public IDisposable AcquireVideoCodecToken(IntPtr hwnd) + 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 (lastCodecToken != null) + currVideoCodecToken = lastCodecToken; + //encoder params Win32.AVICOMPRESSOPTIONS comprOptions = new Win32.AVICOMPRESSOPTIONS(); if (currVideoCodecToken != null) { - comprOptions = currVideoCodecToken.comprOptions; - } - else if (!string.IsNullOrEmpty(Global.Config.AVICodecToken)) - { - comprOptions = CodecToken.DeSerialize(Global.Config.AVICodecToken).comprOptions; + currVideoCodecToken.AllocateToAVICOMPRESSOPTIONS(ref comprOptions); } - if (AVISaveOptions(pAviRawVideoStream, ref comprOptions, hwnd) != 0) + bool result = AVISaveOptions(pAviRawVideoStream, ref comprOptions, hwnd) != 0; + CodecToken ret = CodecToken.CreateFromAVICOMPRESSOPTIONS(ref comprOptions); + + //so, AVISaveOptions may have changed some of the pointers + //if it changed the pointers, did it it free the old ones? we don't know + //let's assume it frees them. if we're wrong, we leak. if we assume otherwise and we're wrong, we may crash. + //so that means any pointers that come in here are either + //1. ones we allocated a minute ago + //2. ones VFW allocated + //guess what? doesn't matter. We'll free them all ourselves. + CodecToken.DeallocateAVICOMPRESSOPTIONS(ref comprOptions); + + + if(result) { - CodecToken ret = CodecToken.TakePossession(comprOptions); - // save to config as well + // save to config and return it Global.Config.AVICodecToken = ret.Serialize(); return ret; } @@ -679,7 +650,12 @@ namespace BizHawk.Client.EmuHawk throw new InvalidOperationException("set a video codec token before opening the streams!"); //open compressed video stream - if (Win32.FAILED(Win32.AVIMakeCompressedStream(out pAviCompressedVideoStream, pAviRawVideoStream, ref currVideoCodecToken.comprOptions, IntPtr.Zero))) + Win32.AVICOMPRESSOPTIONS opts = new Win32.AVICOMPRESSOPTIONS(); + currVideoCodecToken.AllocateToAVICOMPRESSOPTIONS(ref opts); + bool failed = Win32.FAILED(Win32.AVIMakeCompressedStream(out pAviCompressedVideoStream, pAviRawVideoStream, ref opts, IntPtr.Zero)); + CodecToken.DeallocateAVICOMPRESSOPTIONS(ref opts); + + if(failed) { CloseStreams(); throw new InvalidOperationException("Failed making compressed video stream"); diff --git a/BizHawk.Client.EmuHawk/CustomControls/Win32.cs b/BizHawk.Client.EmuHawk/CustomControls/Win32.cs index d1ebb963b5..9e734d75eb 100644 --- a/BizHawk.Client.EmuHawk/CustomControls/Win32.cs +++ b/BizHawk.Client.EmuHawk/CustomControls/Win32.cs @@ -7,7 +7,7 @@ using Microsoft.Win32.SafeHandles; namespace BizHawk.Client.EmuHawk { - public static class Win32 + public unsafe static class Win32 { public static bool Is64BitProcess { get { return (IntPtr.Size == 8); } } public static bool Is64BitOperatingSystem { get { return Is64BitProcess || InternalCheckIsWow64(); } } @@ -386,12 +386,7 @@ namespace BizHawk.Client.EmuHawk // Retrieve the save options for a file and returns them in a buffer [DllImport("avifil32.dll")] - public static extern int AVISaveOptions( - IntPtr hwnd, - int flags, - int streams, - [In, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 2)] IntPtr[] ppavi, - [In, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 2)] IntPtr[] plpOptions); + public static extern int AVISaveOptions(IntPtr hwnd, int flags, int streams, void* ppAvi, void* plpOptions); // Free the resources allocated by the AVISaveOptions function [DllImport("avifil32.dll")] diff --git a/BizHawk.Client.EmuHawk/MainForm.cs b/BizHawk.Client.EmuHawk/MainForm.cs index 88a8460200..ba7be1d47a 100644 --- a/BizHawk.Client.EmuHawk/MainForm.cs +++ b/BizHawk.Client.EmuHawk/MainForm.cs @@ -2925,6 +2925,8 @@ namespace BizHawk.Client.EmuHawk try { + bool usingAvi = aw is AviWriter; //SO GROSS! + if (_dumpaudiosync) { aw = new VideoStretcher(aw); @@ -2954,6 +2956,11 @@ namespace BizHawk.Client.EmuHawk } else { + //THIS IS REALLY SLOPPY! + //PLEASE REDO ME TO NOT CARE WHICH AVWRITER IS USED! + if(usingAvi && !string.IsNullOrEmpty(Global.Config.AVICodecToken)) + aw.SetDefaultVideoCodecToken(); + var token = aw.AcquireVideoCodecToken(this); if (token == null) {