using System; using System.Drawing; using System.Windows.Forms; namespace ScreenConnect; public class VfwEncoder : CriticalFinalizerDisposableObject, IVideoFileEncoder, IBlittable, IDisposable { private IntPtr pfile; private IntPtr pstream; private IntPtr pcompstream; private ICursor cursor; private CorePoint cursorPosition; private NativeBitmap screenBitmap; private NativeBitmap cursorBackBuffer; public string ContentType => "video/avi"; public string FileExtension => ".avi"; public int FramesPerSecond => 4; public void StartEncoding(string filePath, int width, int height) { if (screenBitmap != null) { throw new InvalidOperationException("Already started"); } WindowsNative.AVISTREAMINFO ptr_streaminfo = new WindowsNative.AVISTREAMINFO { fccType = (uint)WindowsNative.mmioStringToFOURCC("vids", 0), dwScale = 1u, dwRate = (uint)FramesPerSecond, rcFrame = { bottom = height, right = width }, szName = new ushort[64] }; WindowsNative.AVICOMPRESSOPTIONS lpOptions = new WindowsNative.AVICOMPRESSOPTIONS { fccType = (uint)WindowsNative.mmioStringToFOURCC("vids", 0), fccHandler = (uint)WindowsNative.mmioStringToFOURCC("msvc", 0), dwFlags = 12u, dwKeyFrameEvery = (uint)(FramesPerSecond * 10), dwQuality = 8000u }; WindowsNative.BITMAPINFO lpFormat = NativeBitmap.GetBitmapInfo(width, height, 32, topDownOrBottomUp: false); try { WindowsNative.AVIFileInit(); screenBitmap = NativeBitmap.CreateBitmap(lpFormat); WindowsNative.AVIFileOpen(out pfile, filePath, 4097u, 0).AssertWin32ResultZero(); WindowsNative.AVIFileCreateStream(pfile, out pstream, ref ptr_streaminfo).AssertWin32ResultZero(); WindowsNative.AVIMakeCompressedStream(out pcompstream, pstream, ref lpOptions, IntPtr.Zero).AssertWin32ResultZero(); WindowsNative.AVIStreamSetFormat(pcompstream, 0, ref lpFormat, lpFormat.bmiHeader.biSize).AssertWin32ResultZero(); } catch { Dispose(); throw; } } public void StopEncoding() { Dispose(); } public void WriteFrame(int frameIndex) { if (screenBitmap == null) { throw new InvalidOperationException("Cannot write frame until recording started."); } if (cursor != null) { Rectangle rectangle = new Rectangle(cursorPosition.X - cursor.HotSpot.X, cursorPosition.Y - cursor.HotSpot.Y, cursor.Size.Width, cursor.Size.Height); if (cursorBackBuffer == null || cursorBackBuffer.Width < cursor.Size.Width || cursorBackBuffer.Height < cursor.Size.Height) { cursorBackBuffer.DisposeQuietly(); cursorBackBuffer = NativeBitmap.CreateBitmap(cursor.Size.Width, cursor.Size.Height, 32, topDownOrBottomUp: false); } WindowsNative.BitBlt(cursorBackBuffer.DeviceContextHandle, 0, 0, cursor.Size.Width, cursor.Size.Height, screenBitmap.DeviceContextHandle, rectangle.X, rectangle.Y, WindowsNative.TernaryRasterOperations.SRCCOPY); Graphics val = Graphics.FromHdc(screenBitmap.DeviceContextHandle); try { cursor.RawObject.To().Draw(val, rectangle); } finally { ((IDisposable)val)?.Dispose(); } } WindowsNative.AVIStreamWrite(pcompstream, frameIndex, 1, screenBitmap.DibBits, Math.Abs(screenBitmap.Stride) * screenBitmap.Height, 0, (IntPtr)0, (IntPtr)0).AssertWin32ResultZero(); if (cursor != null) { Rectangle rectangle2 = new Rectangle(cursorPosition.X - cursor.HotSpot.X, cursorPosition.Y - cursor.HotSpot.Y, cursor.Size.Width, cursor.Size.Height); WindowsNative.BitBlt(screenBitmap.DeviceContextHandle, rectangle2.X, rectangle2.Y, cursor.Size.Width, cursor.Size.Height, cursorBackBuffer.DeviceContextHandle, 0, 0, WindowsNative.TernaryRasterOperations.SRCCOPY); } } protected override void Dispose(bool disposing) { Extensions.ReleaseIntPtrIfNotNull(ref pcompstream, WindowsNative.AVIStreamRelease); Extensions.ReleaseIntPtrIfNotNull(ref pstream, WindowsNative.AVIStreamRelease); Extensions.ReleaseIntPtrIfNotNull(ref pfile, WindowsNative.AVIFileRelease); if (screenBitmap != null) { Extensions.DisposeQuietly(ref screenBitmap); WindowsNative.AVIFileExit(); } } public void SetCursor(ICursor cursor, CorePoint position) { this.cursor = cursor; cursorPosition = position; } public IBitmapData AcquireBits(CoreRect bounds) { return screenBitmap.AssertNonNull().AcquireBits(bounds); } public void ReleaseBits(IBitmapData bitmapData) { screenBitmap.AssertNonNull().ReleaseBits(bitmapData); } }