C#使用 SharpAVI進行 屏幕錄制


再 nuget 中 搜索 shapAvi 並添加引用

github 地址:https://github.com/baSSiLL/SharpAvi

 

using SharpAvi;
using SharpAvi.Codecs;
using SharpAvi.Output;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace BankAutoTransfer.Common
{
    public class Recorder
    {

        private readonly int zoomWidth;
        private readonly int zoomHeight;
        private readonly AviWriter writer;
        private readonly IAviVideoStream videoStream;
        private readonly Thread screenThread;

        /// <summary>
        /// 上一次圖片
        /// </summary>
        Bitmap lastBitmap = new Bitmap(10, 10);

        bool stop = false;

        /// <summary>
        /// 縮放
        /// </summary>
        float zoom = 1;
        /// <summary>
        /// 鼠標是否點擊
        /// </summary>
        bool mouseclick = false;
        /// <summary>
        /// 鈎子句柄
        /// </summary>
        int hook = 0;
        /// <summary>
        /// 錄制屏幕
        /// </summary>
        /// <param name="fileName">要保存的文件名</param>
        /// <param name="codec">編碼</param>
        /// <param name="quality">錄制質量</param>
        /// <param name="zoom">縮放</param>
        public Recorder(string fileName, FourCC codec, int quality = 70, float zoom = 1.0F)
        {
            //設置縮放 寬高
            zoomHeight = (int)Math.Floor(System.Windows.Forms.Screen.PrimaryScreen.Bounds.Height * zoom);
            zoomWidth = (int)Math.Floor(System.Windows.Forms.Screen.PrimaryScreen.Bounds.Width * zoom);

            this.zoom = zoom;
            //創建視頻
            writer = new AviWriter(fileName)
            {
                FramesPerSecond = 10,
                EmitIndex1 = true,
            };

            //創建視頻流
            videoStream = CreateVideoStream(codec, quality);
            videoStream.Name = "Screencast";
            //開啟一個線程錄制屏幕
            screenThread = new Thread(RecordScreen)
            {
                Name = typeof(Recorder).Name + ".RecordScreen",
                IsBackground = true
            };
            //鈎子函數用於監控是否點擊了鼠標
            hook = WinHook.SetWindowsHookEx(HookType.WH_MOUSE_LL, WinHook.hookProc += MouseHook, Win32Api.GetModuleHandle(Process.GetCurrentProcess().MainModule.ModuleName), 0);

            screenThread.Start();
        }

        private IAviVideoStream CreateVideoStream(FourCC codec, int quality)
        {
            // Select encoder type based on FOURCC of codec
            if (codec == KnownFourCCs.Codecs.Uncompressed)
            {
                return writer.AddUncompressedVideoStream(zoomWidth, zoomHeight);
            }
            else if (codec == KnownFourCCs.Codecs.MotionJpeg)
            {
                return writer.AddMotionJpegVideoStream(zoomWidth, zoomHeight, quality);
            }
            else
            {
                return writer.AddMpeg4VideoStream(zoomWidth, zoomHeight, (double)writer.FramesPerSecond,
                    // It seems that all tested MPEG-4 VfW codecs ignore the quality affecting parameters passed through VfW API
                    // They only respect the settings from their own configuration dialogs, and Mpeg4VideoEncoder currently has no support for this
                    quality: quality,
                    codec: codec,
                    // Most of VfW codecs expect single-threaded use, so we wrap this encoder to special wrapper
                    // Thus all calls to the encoder (including its instantiation) will be invoked on a single thread although encoding (and writing) is performed asynchronously
                    forceSingleThreadedAccess: true);
            }
        }
        /// <summary>
        /// 停止錄制
        /// </summary>
        public void Stop()
        {
            WinHook.UnhookWindowsHookEx(hook);
            lastBitmap.Dispose();
            stop = true;
        }

        private void RecordScreen()
        {
            while (!stop)
            {
                var buffer = GetScreenshot();
                // 把圖片寫入視頻流
                videoStream.WriteFrameAsync(true, buffer, 0, buffer.Length).Wait();
            }
            writer.Close();
        }

        private byte[] GetScreenshot()
        {
            using (Bitmap avibitmap = GetScreen())
            {
                Point mouseXY = new Point();
                Win32Api.GetCursorPos(ref mouseXY);
                using (Graphics mouseGraphics = Graphics.FromImage(avibitmap))
                {
                    //繪制鼠標位置
                    if (mouseclick)
                        mouseGraphics.DrawEllipse(new Pen(new SolidBrush(Color.Red), 10), new Rectangle(mouseXY.X - 10, mouseXY.Y - 10, 20, 20));
                    else
                        mouseGraphics.DrawEllipse(new Pen(new SolidBrush(Color.Black), 5), new Rectangle(mouseXY.X - 10, mouseXY.Y - 10, 20, 20));
                    if (zoom != 1)
                    {
                        //縮放
                        using (var copy = new Bitmap(this.zoomWidth, this.zoomHeight))
                        {
                            var buffer = new byte[copy.Width * copy.Height * 4];
                            var gcopy = Graphics.FromImage(copy);
                            gcopy.DrawImage(avibitmap, new Rectangle(new Point(0, 0), copy.Size), 0, 0, avibitmap.Width, avibitmap.Height, GraphicsUnit.Pixel);
                            gcopy.Dispose();
                            var bits = copy.LockBits(new Rectangle(0, 0, copy.Width, copy.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
                            Marshal.Copy(bits.Scan0, buffer, 0, buffer.Length);
                            copy.UnlockBits(bits);
                            return buffer;
                        }
                    }
                    else
                    {
                        var buffer = new byte[avibitmap.Width * avibitmap.Height * 4];
                        var bits = avibitmap.LockBits(new Rectangle(0, 0, avibitmap.Width, avibitmap.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
                        Marshal.Copy(bits.Scan0, buffer, 0, buffer.Length);
                        avibitmap.UnlockBits(bits);
                        return buffer;
                    }
                }
            }

        }

        private Bitmap GetScreen()
        {
            try
            {
                var bitmap = new Bitmap(System.Windows.Forms.Screen.PrimaryScreen.Bounds.Width, System.Windows.Forms.Screen.PrimaryScreen.Bounds.Height);
                var graphics = Graphics.FromImage(bitmap);
                graphics.CopyFromScreen(0, 0, 0, 0, System.Windows.Forms.Screen.PrimaryScreen.Bounds.Size);
                graphics.Dispose();
                lastBitmap.Dispose();
                lastBitmap = bitmap;
            }
            catch
            {
            }
            return (Bitmap)lastBitmap.Clone();
        }

        private int MouseHook(int nCode, int wParam, IntPtr lParam)
        {
            if (wParam == CommonConst.WM_LBUTTONDOWN)
                mouseclick = true;
            else if (wParam == CommonConst.WM_LBUTTONUP)
                mouseclick = false;
            return WinHook.CallNextHookEx(hook, nCode, wParam, lParam);
        }
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;

namespace BankAutoTransfer.Common
{
    public class WinHook
    {
        [DllImport("user32.dll")]
        public static extern int SetWindowsHookEx(
           HookType idHook,
           HookProc lpfn,
           IntPtr hInstance,
           int threadId
           );

        public delegate int HookProc(int nCode, int wParam, IntPtr lParam);

        public static HookProc hookProc;

        [DllImport("user32.dll")]
        public static extern int CallNextHookEx(
            int hhk,      //handle to current hook  
            int nCode,      //hook code passed to hook procedure  
            int wParam,      //value passed to hook procedure  
            IntPtr lParam       //value passed to hook procedure  
            );

        [DllImport("user32.dll")]
        public static extern bool UnhookWindowsHookEx(int hook);
    }

    /// <summary>
    /// 設置的鈎子類型
    /// </summary>
    public enum HookType : int
    {
        /// <summary>
        /// WH_MSGFILTER 和 WH_SYSMSGFILTER Hooks使我們可以監視菜單,滾動 
        ///條,消息框,對話框消息並且發現用戶使用ALT+TAB or ALT+ESC 組合鍵切換窗口。 
        ///WH_MSGFILTER Hook只能監視傳遞到菜單,滾動條,消息框的消息,以及傳遞到通 
        ///過安裝了Hook子過程的應用程序建立的對話框的消息。WH_SYSMSGFILTER Hook 
        ///監視所有應用程序消息。 
        /// 
        ///WH_MSGFILTER 和 WH_SYSMSGFILTER Hooks使我們可以在模式循環期間 
        ///過濾消息,這等價於在主消息循環中過濾消息。 
        ///    
        ///通過調用CallMsgFilter function可以直接的調用WH_MSGFILTER Hook。通過使用這 
        ///個函數,應用程序能夠在模式循環期間使用相同的代碼去過濾消息,如同在主消息循 
        ///環里一樣
        /// </summary>
        WH_MSGFILTER = -1,
        /// <summary>
        /// WH_JOURNALRECORD Hook用來監視和記錄輸入事件。典型的,可以使用這 
        ///個Hook記錄連續的鼠標和鍵盤事件,然后通過使用WH_JOURNALPLAYBACK Hook 
        ///來回放。WH_JOURNALRECORD Hook是全局Hook,它不能象線程特定Hook一樣 
        ///使用。WH_JOURNALRECORD是system-wide local hooks,它們不會被注射到任何行 
        ///程地址空間
        /// </summary>
        WH_JOURNALRECORD = 0,
        /// <summary>
        /// WH_JOURNALPLAYBACK Hook使應用程序可以插入消息到系統消息隊列。可 
        ///以使用這個Hook回放通過使用WH_JOURNALRECORD Hook記錄下來的連續的鼠 
        ///標和鍵盤事件。只要WH_JOURNALPLAYBACK Hook已經安裝,正常的鼠標和鍵盤 
        ///事件就是無效的。WH_JOURNALPLAYBACK Hook是全局Hook,它不能象線程特定 
        ///Hook一樣使用。WH_JOURNALPLAYBACK Hook返回超時值,這個值告訴系統在處 
        ///理來自回放Hook當前消息之前需要等待多長時間(毫秒)。這就使Hook可以控制實 
        ///時事件的回放。WH_JOURNALPLAYBACK是system-wide local hooks,它們不會被 
        ///注射到任何行程地址空間
        /// </summary>
        WH_JOURNALPLAYBACK = 1,
        /// <summary>
        /// 在應用程序中,WH_KEYBOARD Hook用來監視WM_KEYDOWN and  
        ///WM_KEYUP消息,這些消息通過GetMessage or PeekMessage function返回。可以使 
        ///用這個Hook來監視輸入到消息隊列中的鍵盤消息
        /// </summary>
        WH_KEYBOARD = 2,
        /// <summary>
        /// 應用程序使用WH_GETMESSAGE Hook來監視從GetMessage or PeekMessage函 
        ///數返回的消息。你可以使用WH_GETMESSAGE Hook去監視鼠標和鍵盤輸入,以及 
        ///其它發送到消息隊列中的消息
        /// </summary>
        WH_GETMESSAGE = 3,
        /// <summary>
        /// 監視發送到窗口過程的消息,系統在消息發送到接收窗口過程之前調用
        /// </summary>
        WH_CALLWNDPROC = 4,
        /// <summary>
        /// 在以下事件之前,系統都會調用WH_CBT Hook子過程,這些事件包括: 
        ///1. 激活,建立,銷毀,最小化,最大化,移動,改變尺寸等窗口事件; 
        ///2. 完成系統指令; 
        ///3. 來自系統消息隊列中的移動鼠標,鍵盤事件; 
        ///4. 設置輸入焦點事件; 
        ///5. 同步系統消息隊列事件。
        ///Hook子過程的返回值確定系統是否允許或者防止這些操作中的一個
        /// </summary>
        WH_CBT = 5,
        /// <summary>
        /// WH_MSGFILTER 和 WH_SYSMSGFILTER Hooks使我們可以監視菜單,滾動 
        ///條,消息框,對話框消息並且發現用戶使用ALT+TAB or ALT+ESC 組合鍵切換窗口。 
        ///WH_MSGFILTER Hook只能監視傳遞到菜單,滾動條,消息框的消息,以及傳遞到通 
        ///過安裝了Hook子過程的應用程序建立的對話框的消息。WH_SYSMSGFILTER Hook 
        ///監視所有應用程序消息。 
        /// 
        ///WH_MSGFILTER 和 WH_SYSMSGFILTER Hooks使我們可以在模式循環期間 
        ///過濾消息,這等價於在主消息循環中過濾消息。 
        ///    
        ///通過調用CallMsgFilter function可以直接的調用WH_MSGFILTER Hook。通過使用這 
        ///個函數,應用程序能夠在模式循環期間使用相同的代碼去過濾消息,如同在主消息循 
        ///環里一樣
        /// </summary>
        WH_SYSMSGFILTER = 6,
        /// <summary>
        /// WH_MOUSE Hook監視從GetMessage 或者 PeekMessage 函數返回的鼠標消息。 
        ///使用這個Hook監視輸入到消息隊列中的鼠標消息
        /// </summary>
        WH_MOUSE = 7,
        /// <summary>
        /// 當調用GetMessage 或 PeekMessage 來從消息隊列種查詢非鼠標、鍵盤消息時
        /// </summary>
        WH_HARDWARE = 8,
        /// <summary>
        /// 在系統調用系統中與其它Hook關聯的Hook子過程之前,系統會調用 
        ///WH_DEBUG Hook子過程。你可以使用這個Hook來決定是否允許系統調用與其它 
        ///Hook關聯的Hook子過程
        /// </summary>
        WH_DEBUG = 9,
        /// <summary>
        /// 外殼應用程序可以使用WH_SHELL Hook去接收重要的通知。當外殼應用程序是 
        ///激活的並且當頂層窗口建立或者銷毀時,系統調用WH_SHELL Hook子過程。 
        ///WH_SHELL 共有5鍾情況: 
        ///1. 只要有個top-level、unowned 窗口被產生、起作用、或是被摧毀; 
        ///2. 當Taskbar需要重畫某個按鈕; 
        ///3. 當系統需要顯示關於Taskbar的一個程序的最小化形式; 
        ///4. 當目前的鍵盤布局狀態改變; 
        ///5. 當使用者按Ctrl+Esc去執行Task Manager(或相同級別的程序)。 
        ///
        ///按照慣例,外殼應用程序都不接收WH_SHELL消息。所以,在應用程序能夠接 
        ///收WH_SHELL消息之前,應用程序必須調用SystemParametersInfo function注冊它自 
        ////// </summary>
        WH_SHELL = 10,
        /// <summary>
        /// 當應用程序的前台線程處於空閑狀態時,可以使用WH_FOREGROUNDIDLE  
        ///Hook執行低優先級的任務。當應用程序的前台線程大概要變成空閑狀態時,系統就 
        ///會調用WH_FOREGROUNDIDLE Hook子過程
        /// </summary>
        WH_FOREGROUNDIDLE = 11,
        /// <summary>
        /// 監視發送到窗口過程的消息,系統在消息發送到接收窗口過程之后調用
        /// </summary>
        WH_CALLWNDPROCRET = 12,
        /// <summary>
        /// 監視輸入到線程消息隊列中的鍵盤消息
        /// </summary>
        WH_KEYBOARD_LL = 13,
        /// <summary>
        /// 監視輸入到線程消息隊列中的鼠標消息
        /// </summary>
        WH_MOUSE_LL = 14
    }

}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM