fix AviWriter

This commit is contained in:
Morilli 2025-05-23 22:34:01 +02:00
parent b5ef422ab6
commit 93562b86e7
4 changed files with 63 additions and 145 deletions

View File

@ -6,11 +6,11 @@ namespace Windows.Win32
public static partial class Win32Imports public static partial class Win32Imports
{ {
/// <seealso cref="AVISaveOptions(HWND, uint, int, IAVIStream[], AVICOMPRESSOPTIONS**)"/> /// <seealso cref="AVISaveOptions(HWND, uint, int, IAVIStream[], AVICOMPRESSOPTIONS**)"/>
public static unsafe nint AVISaveOptions(IAVIStream[] ppavi, ref AVICOMPRESSOPTIONS opts, HWND owner) public static unsafe nint AVISaveOptions(IAVIStream ppavi, ref AVICOMPRESSOPTIONS opts, HWND owner)
{ {
fixed (AVICOMPRESSOPTIONS* popts = &opts) fixed (AVICOMPRESSOPTIONS* popts = &opts)
{ {
return AVISaveOptions(owner, uiFlags: 0, nStreams: 1, ppavi, &popts); return AVISaveOptions(owner, uiFlags: 0, nStreams: 1, [ ppavi ], &popts);
} }
} }
} }

View File

@ -17,8 +17,8 @@ namespace Windows.Win32
} }
/// <seealso cref="HeapAlloc(SafeHandle, HEAP_FLAGS, nuint)"/> /// <seealso cref="HeapAlloc(SafeHandle, HEAP_FLAGS, nuint)"/>
public static unsafe IntPtr HeapAlloc(int dwBytes, HEAP_FLAGS dwFlags = HEAP_FLAGS.HEAP_NONE) public static unsafe void* HeapAlloc(int dwBytes, HEAP_FLAGS dwFlags = HEAP_FLAGS.HEAP_NONE)
=> unchecked((IntPtr) HeapAlloc(GetProcessHeap_SafeHandle(), dwFlags, dwBytes: (UIntPtr) dwBytes)); => HeapAlloc(GetProcessHeap_SafeHandle(), dwFlags, dwBytes: (UIntPtr) dwBytes);
/// <inheritdoc cref="IsWow64Process(HANDLE, BOOL*)"/> /// <inheritdoc cref="IsWow64Process(HANDLE, BOOL*)"/>
public static unsafe BOOL IsWow64Process(HANDLE hProcess, out BOOL Wow64Process) public static unsafe BOOL IsWow64Process(HANDLE hProcess, out BOOL Wow64Process)

View File

@ -7,11 +7,9 @@ using System.Runtime.InteropServices;
using System.Threading; using System.Threading;
using BizHawk.Client.Common; using BizHawk.Client.Common;
using BizHawk.Common;
using BizHawk.Common.PathExtensions; using BizHawk.Common.PathExtensions;
using BizHawk.Emulation.Common; using BizHawk.Emulation.Common;
using Windows.Win32;
using Windows.Win32.Graphics.Gdi; using Windows.Win32.Graphics.Gdi;
using Windows.Win32.Media.Audio; using Windows.Win32.Media.Audio;
using Windows.Win32.Media.Multimedia; using Windows.Win32.Media.Multimedia;
@ -351,7 +349,8 @@ namespace BizHawk.Client.EmuHawk
wfex.nAvgBytesPerSec = (uint)(wfex.nBlockAlign * a_samplerate); wfex.nAvgBytesPerSec = (uint)(wfex.nBlockAlign * a_samplerate);
} }
public int fps, fps_scale; public uint fps;
public uint fps_scale;
} }
private readonly Parameters _parameters = new Parameters(); private readonly Parameters _parameters = new Parameters();
@ -365,10 +364,10 @@ namespace BizHawk.Client.EmuHawk
bool change = false; bool change = false;
change |= fpsNum != _parameters.fps; change |= fpsNum != _parameters.fps;
_parameters.fps = fpsNum; _parameters.fps = (uint)fpsNum;
change |= _parameters.fps_scale != fpsDen; change |= _parameters.fps_scale != fpsDen;
_parameters.fps_scale = fpsDen; _parameters.fps_scale = (uint)fpsDen;
if (change) if (change)
{ {
@ -418,7 +417,7 @@ namespace BizHawk.Client.EmuHawk
public byte[] Format = Array.Empty<byte>(); public byte[] Format = Array.Empty<byte>();
public byte[] Parms = Array.Empty<byte>(); public byte[] Parms = Array.Empty<byte>();
private static unsafe string Decode_mmioFOURCC(int code) private static string Decode_mmioFOURCC(uint code)
{ {
var chs = stackalloc char[4]; var chs = stackalloc char[4];
@ -437,19 +436,19 @@ namespace BizHawk.Client.EmuHawk
var ret = new CodecToken var ret = new CodecToken
{ {
_comprOptions = opts, _comprOptions = opts,
codec = unchecked((int) Decode_mmioFOURCC(opts.fccHandler)), codec = Decode_mmioFOURCC(opts.fccHandler),
Format = new byte[opts.cbFormat], Format = new byte[opts.cbFormat],
Parms = new byte[opts.cbParms], Parms = new byte[opts.cbParms],
}; };
if (opts.lpFormat is not null) if (opts.lpFormat is not null)
{ {
Marshal.Copy(opts.lpFormat, ret.Format, 0, opts.cbFormat); Marshal.Copy((IntPtr)opts.lpFormat, ret.Format, 0, unchecked((int)opts.cbFormat));
} }
if (opts.lpParms is not null) if (opts.lpParms is not null)
{ {
Marshal.Copy(opts.lpParms, ret.Parms, 0, opts.cbParms); Marshal.Copy((IntPtr)opts.lpParms, ret.Parms, 0, unchecked((int)opts.cbParms));
} }
return ret; return ret;
@ -478,14 +477,14 @@ namespace BizHawk.Client.EmuHawk
{ {
if (_comprOptions.cbParms != 0) if (_comprOptions.cbParms != 0)
{ {
_comprOptions.lpParms = Win32Imports.HeapAlloc(_comprOptions.cbParms); _comprOptions.lpParms = AVIWriterImports.HeapAlloc(unchecked((int)_comprOptions.cbParms));
Marshal.Copy(Parms, 0, _comprOptions.lpParms, _comprOptions.cbParms); Marshal.Copy(Parms, 0, (IntPtr)_comprOptions.lpParms, unchecked((int)_comprOptions.cbParms));
} }
if (_comprOptions.cbFormat != 0) if (_comprOptions.cbFormat != 0)
{ {
_comprOptions.lpFormat = Win32Imports.HeapAlloc(_comprOptions.cbFormat); _comprOptions.lpFormat = AVIWriterImports.HeapAlloc(unchecked((int)_comprOptions.cbFormat));
Marshal.Copy(Format, 0, _comprOptions.lpFormat, _comprOptions.cbFormat); Marshal.Copy(Format, 0, (IntPtr)_comprOptions.lpFormat, unchecked((int)_comprOptions.cbFormat));
} }
opts = _comprOptions; opts = _comprOptions;
@ -538,8 +537,8 @@ namespace BizHawk.Client.EmuHawk
comprOptions.cbParms = b.ReadUInt32(); comprOptions.cbParms = b.ReadUInt32();
comprOptions.dwInterleaveEvery = b.ReadUInt32(); comprOptions.dwInterleaveEvery = b.ReadUInt32();
format = b.ReadBytes(comprOptions.cbFormat); format = b.ReadBytes(unchecked((int)comprOptions.cbFormat));
parms = b.ReadBytes(comprOptions.cbParms); parms = b.ReadBytes(unchecked((int)comprOptions.cbParms));
} }
catch (IOException) catch (IOException)
{ {
@ -579,7 +578,7 @@ namespace BizHawk.Client.EmuHawk
{ {
static AviWriterSegment() static AviWriterSegment()
{ {
AVIWriterImports.AVIFileInit(); MemoryApiImports.AVIFileInit();
} }
public void Dispose() public void Dispose()
@ -587,7 +586,10 @@ namespace BizHawk.Client.EmuHawk
private CodecToken _currVideoCodecToken; private CodecToken _currVideoCodecToken;
private bool _isOpen; private bool _isOpen;
private IntPtr _pAviFile, _pAviRawVideoStream, _pAviRawAudioStream, _pAviCompressedVideoStream; private IAVIFile _pAviFile;
private IAVIStream _pAviRawVideoStream;
private IAVIStream _pAviRawAudioStream;
private IAVIStream _pAviCompressedVideoStream;
private IntPtr _pGlobalBuf; private IntPtr _pGlobalBuf;
private int _pGlobalBuffSize; private int _pGlobalBuffSize;
@ -629,15 +631,15 @@ namespace BizHawk.Client.EmuHawk
public long GetLengthApproximation() public long GetLengthApproximation()
=> _outStatus.video_bytes + _outStatus.audio_bytes; => _outStatus.video_bytes + _outStatus.audio_bytes;
public static unsafe int AVISaveOptions(IntPtr stream, ref AVICOMPRESSOPTIONS opts, IntPtr owner) public static int AVISaveOptions(IAVIStream stream, ref AVICOMPRESSOPTIONS opts, IntPtr owner)
=> unchecked((int) AVIWriterImports.AVISaveOptions((IAVIStream[]) (void*) stream, ref opts, new(owner))); => unchecked((int) AVIWriterImports.AVISaveOptions(stream, ref opts, new(owner)));
private Parameters _parameters; private Parameters _parameters;
/// <exception cref="InvalidOperationException">unmanaged call failed</exception> /// <exception cref="InvalidOperationException">unmanaged call failed</exception>
public void OpenFile(string destPath, Parameters parameters, CodecToken videoCodecToken) public void OpenFile(string destPath, Parameters parameters, CodecToken videoCodecToken)
{ {
static int mmioFOURCC(string str) => ( static uint mmioFOURCC(string str) => (uint)(
(byte)str[0] | (byte)str[0] |
((byte)str[1] << 8) | ((byte)str[1] << 8) |
((byte)str[2] << 16) | ((byte)str[2] << 16) |
@ -656,10 +658,10 @@ namespace BizHawk.Client.EmuHawk
} }
var hr = AVIWriterImports.AVIFileOpenW( var hr = AVIWriterImports.AVIFileOpenW(
ref _pAviFile, out _pAviFile,
destPath, destPath,
AVIWriterImports.OpenFileStyle.OF_CREATE | AVIWriterImports.OpenFileStyle.OF_WRITE, (uint)(BizHawk.Common.AVIWriterImports.OpenFileStyle.OF_CREATE | BizHawk.Common.AVIWriterImports.OpenFileStyle.OF_WRITE),
0); null);
var hrEx = Marshal.GetExceptionForHR(hr); var hrEx = Marshal.GetExceptionForHR(hr);
if (hrEx != null) if (hrEx != null)
{ {
@ -673,12 +675,12 @@ namespace BizHawk.Client.EmuHawk
vidstream_header.fccType = mmioFOURCC("vids"); vidstream_header.fccType = mmioFOURCC("vids");
vidstream_header.dwRate = parameters.fps; vidstream_header.dwRate = parameters.fps;
vidstream_header.dwScale = parameters.fps_scale; vidstream_header.dwScale = parameters.fps_scale;
vidstream_header.dwSuggestedBufferSize = (int)bmih.biSizeImage; vidstream_header.dwSuggestedBufferSize = bmih.biSizeImage;
hr = AVIWriterImports.AVIFileCreateStreamW( hr = AVIWriterImports.AVIFileCreateStreamW(
_pAviFile, _pAviFile,
out _pAviRawVideoStream, out _pAviRawVideoStream,
ref vidstream_header); in vidstream_header);
hrEx = Marshal.GetExceptionForHR(hr); hrEx = Marshal.GetExceptionForHR(hr);
if (hrEx != null) if (hrEx != null)
{ {
@ -691,9 +693,9 @@ namespace BizHawk.Client.EmuHawk
WAVEFORMATEX wfex = default; WAVEFORMATEX wfex = default;
parameters.PopulateWAVEFORMATEX(ref wfex); parameters.PopulateWAVEFORMATEX(ref wfex);
audstream_header.fccType = mmioFOURCC("auds"); audstream_header.fccType = mmioFOURCC("auds");
audstream_header.dwQuality = -1; audstream_header.dwQuality = unchecked((uint)-1);
audstream_header.dwScale = wfex.nBlockAlign; audstream_header.dwScale = wfex.nBlockAlign;
audstream_header.dwRate = (int)wfex.nAvgBytesPerSec; audstream_header.dwRate = wfex.nAvgBytesPerSec;
audstream_header.dwSampleSize = wfex.nBlockAlign; audstream_header.dwSampleSize = wfex.nBlockAlign;
audstream_header.dwInitialFrames = 1; // ??? optimal value? audstream_header.dwInitialFrames = 1; // ??? optimal value?
@ -761,8 +763,8 @@ namespace BizHawk.Client.EmuHawk
var hr = AVIWriterImports.AVIMakeCompressedStream( var hr = AVIWriterImports.AVIMakeCompressedStream(
out _pAviCompressedVideoStream, out _pAviCompressedVideoStream,
_pAviRawVideoStream, _pAviRawVideoStream,
ref opts, in opts,
IntPtr.Zero); null);
var hrEx = Marshal.GetExceptionForHR(hr); var hrEx = Marshal.GetExceptionForHR(hr);
CodecToken.DeallocateAVICOMPRESSOPTIONS(ref opts); CodecToken.DeallocateAVICOMPRESSOPTIONS(ref opts);
if (hrEx != null) if (hrEx != null)
@ -785,7 +787,7 @@ namespace BizHawk.Client.EmuHawk
hr = AVIWriterImports.AVIStreamSetFormat( hr = AVIWriterImports.AVIStreamSetFormat(
_pAviCompressedVideoStream, _pAviCompressedVideoStream,
0, 0,
ref bmih, &bmih,
Marshal.SizeOf(bmih)); Marshal.SizeOf(bmih));
hrEx = Marshal.GetExceptionForHR(hr); hrEx = Marshal.GetExceptionForHR(hr);
if (hrEx != null) if (hrEx != null)
@ -802,7 +804,7 @@ namespace BizHawk.Client.EmuHawk
hr = AVIWriterImports.AVIStreamSetFormat( hr = AVIWriterImports.AVIStreamSetFormat(
_pAviRawAudioStream, _pAviRawAudioStream,
0, 0,
ref wfex, &wfex,
Marshal.SizeOf(wfex)); Marshal.SizeOf(wfex));
hrEx = Marshal.GetExceptionForHR(hr); hrEx = Marshal.GetExceptionForHR(hr);
if (hrEx != null) if (hrEx != null)
@ -818,22 +820,22 @@ namespace BizHawk.Client.EmuHawk
public void CloseFile() public void CloseFile()
{ {
CloseStreams(); CloseStreams();
if (_pAviRawAudioStream != IntPtr.Zero) if (_pAviRawAudioStream is not null)
{ {
_ = AVIWriterImports.AVIStreamRelease(_pAviRawAudioStream); _ = AVIWriterImports.AVIStreamRelease(_pAviRawAudioStream);
_pAviRawAudioStream = IntPtr.Zero; _pAviRawAudioStream = null;
} }
if (_pAviRawVideoStream != IntPtr.Zero) if (_pAviRawVideoStream is not null)
{ {
_ = AVIWriterImports.AVIStreamRelease(_pAviRawVideoStream); _ = AVIWriterImports.AVIStreamRelease(_pAviRawVideoStream);
_pAviRawVideoStream = IntPtr.Zero; _pAviRawVideoStream = null;
} }
if (_pAviFile != IntPtr.Zero) if (_pAviFile is not null)
{ {
_ = AVIWriterImports.AVIFileRelease(_pAviFile); _ = AVIWriterImports.AVIFileRelease(_pAviFile);
_pAviFile = IntPtr.Zero; _pAviFile = null;
} }
if (_pGlobalBuf != IntPtr.Zero) if (_pGlobalBuf != IntPtr.Zero)
@ -849,15 +851,15 @@ namespace BizHawk.Client.EmuHawk
/// </summary> /// </summary>
private void CloseStreams() private void CloseStreams()
{ {
if (_pAviRawAudioStream != IntPtr.Zero) if (_pAviRawAudioStream is not null)
{ {
FlushBufferedAudio(); FlushBufferedAudio();
} }
if (_pAviCompressedVideoStream != IntPtr.Zero) if (_pAviCompressedVideoStream is not null)
{ {
_ = AVIWriterImports.AVIStreamRelease(_pAviCompressedVideoStream); _ = AVIWriterImports.AVIStreamRelease(_pAviCompressedVideoStream);
_pAviCompressedVideoStream = IntPtr.Zero; _pAviCompressedVideoStream = null;
} }
} }
@ -885,7 +887,7 @@ namespace BizHawk.Client.EmuHawk
} }
} }
private unsafe void FlushBufferedAudio() private void FlushBufferedAudio()
{ {
var todo = _outStatus.audio_buffered_shorts; var todo = _outStatus.audio_buffered_shorts;
var todo_realsamples = todo / 2; var todo_realsamples = todo / 2;
@ -898,22 +900,23 @@ namespace BizHawk.Client.EmuHawk
} }
// (TODO - inefficient- build directly in a buffer) // (TODO - inefficient- build directly in a buffer)
int bytesWritten;
_ = AVIWriterImports.AVIStreamWrite( _ = AVIWriterImports.AVIStreamWrite(
_pAviRawAudioStream, _pAviRawAudioStream,
_outStatus.audio_samples, _outStatus.audio_samples,
todo_realsamples, todo_realsamples,
buf, buf.ToPointer(),
todo_realsamples * 4, todo_realsamples * 4,
0, 0,
IntPtr.Zero, null,
out var bytes_written); &bytesWritten);
_outStatus.audio_samples += todo_realsamples; _outStatus.audio_samples += todo_realsamples;
_outStatus.audio_bytes += bytes_written; _outStatus.audio_bytes += bytesWritten;
_outStatus.audio_buffered_shorts = 0; _outStatus.audio_buffered_shorts = 0;
} }
/// <exception cref="InvalidOperationException">attempted frame resize during encoding</exception> /// <exception cref="InvalidOperationException">attempted frame resize during encoding</exception>
public unsafe void AddFrame(IVideoProvider source) public void AddFrame(IVideoProvider source)
{ {
if (_parameters.width != source.BufferWidth if (_parameters.width != source.BufferWidth
|| _parameters.height != source.BufferHeight) || _parameters.height != source.BufferHeight)
@ -953,17 +956,17 @@ namespace BizHawk.Client.EmuHawk
bp += pitch_add; bp += pitch_add;
} }
int bytes_written = default; int bytesWritten;
_ = AVIWriterImports.AVIStreamWrite( _ = AVIWriterImports.AVIStreamWrite(
_pAviCompressedVideoStream, _pAviCompressedVideoStream,
lStart: _outStatus.video_frames, lStart: _outStatus.video_frames,
samples: 1, lSamples: 1,
lpBuffer: bytes_ptr, lpBuffer: bytes_ptr,
cbBuffer: todo, cbBuffer: todo,
dwFlags: AVIWriterImports.AVIIF_KEYFRAME, dwFlags: AVIWriterImports.AVIIF_KEYFRAME,
plSampWritten: default, plSampWritten: null,
plBytesWritten: &bytes_written); plBytesWritten: &bytesWritten);
_outStatus.video_bytes += bytes_written; _outStatus.video_bytes += bytesWritten;
_outStatus.video_frames++; _outStatus.video_frames++;
} }
} }
@ -994,16 +997,17 @@ namespace BizHawk.Client.EmuHawk
idx -= w * 2; idx -= w * 2;
} }
int bytesWritten;
_ = AVIWriterImports.AVIStreamWrite( _ = AVIWriterImports.AVIStreamWrite(
_pAviCompressedVideoStream, _pAviCompressedVideoStream,
lStart: _outStatus.video_frames, lStart: _outStatus.video_frames,
samples: 1, lSamples: 1,
lpBuffer: bytes_ptr, lpBuffer: bytes_ptr,
cbBuffer: todo * 3, cbBuffer: todo * 3,
dwFlags: AVIWriterImports.AVIIF_KEYFRAME, dwFlags: AVIWriterImports.AVIIF_KEYFRAME,
plSampWritten: default, plSampWritten: null,
plBytesWritten: &bytes_written); plBytesWritten: &bytesWritten);
_outStatus.video_bytes += bytes_written; _outStatus.video_bytes += bytesWritten;
_outStatus.video_frames++; _outStatus.video_frames++;
} }
} }

View File

@ -1,9 +1,6 @@
#nullable disable #nullable disable
#if AVI_SUPPORT #if AVI_SUPPORT
using System.Runtime.InteropServices;
// ReSharper disable FieldCanBeMadeReadOnly.Global
namespace BizHawk.Common namespace BizHawk.Common
{ {
@ -15,89 +12,6 @@ namespace BizHawk.Common
OF_WRITE = 0x00000001, OF_WRITE = 0x00000001,
OF_CREATE = 0x00001000, OF_CREATE = 0x00001000,
} }
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct AVISTREAMINFOW
{
public int fccType;
public int fccHandler;
public int dwFlags;
public int dwCaps;
public short wPriority;
public short wLanguage;
public int dwScale;
public int dwRate;
public int dwStart;
public int dwLength;
public int dwInitialFrames;
public int dwSuggestedBufferSize;
public int dwQuality;
public int dwSampleSize;
public RECT rcFrame;
public int dwEditCount;
public int dwFormatChangeCount;
[MarshalAs(UnmanagedType.LPWStr, SizeConst = 64)]
public string szName;
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct RECT
{
public int Left;
public int Top;
public int Right;
public int Bottom;
}
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct BITMAPINFOHEADER
{
public uint biSize;
public int biWidth;
public int biHeight;
public ushort biPlanes;
public ushort biBitCount;
public uint biCompression;
public uint biSizeImage;
public int biXPelsPerMeter;
public int biYPelsPerMeter;
public uint biClrUsed;
public uint biClrImportant;
public void Init()
=> biSize = (uint)Marshal.SizeOf(this);
}
[StructLayout(LayoutKind.Sequential)]
public struct AVICOMPRESSOPTIONS
{
public int fccType;
public int fccHandler;
public int dwKeyFrameEvery;
public int dwQuality;
public int dwBytesPerSecond;
public int dwFlags;
public IntPtr lpFormat;
public int cbFormat;
public IntPtr lpParms;
public int cbParms;
public int dwInterleaveEvery;
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct WAVEFORMATEX
{
public ushort wFormatTag;
public ushort nChannels;
public uint nSamplesPerSec;
public uint nAvgBytesPerSec;
public ushort nBlockAlign;
public ushort wBitsPerSample;
public ushort cbSize;
public void Init()
=> cbSize = (ushort)Marshal.SizeOf(this);
}
} }
} }
#endif #endif