再 nuget 中 搜索 shapAvi 並添加引用git
github 地址:https://github.com/baSSiLL/SharpAvigithub
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 } }