最近手头工作比较轻松了一点就继续研究和完善之前的录屏软件,使用AForge最大的问题在于:最原始的只能够录全屏,而自定义的录屏需要更改非常多的细节:like follows:
1、需要支持区域化录屏;
2、需要支持麦克风录音,并且混音在视频中,同步;
3、需要支持系统声音录取、并且需要混音在视频中,同步;
4、需要支持捕获光标,并且自定义颜色、描边,最重要的是你需要在区域录屏的时候支持坐标位置更新(相对比较难);
前面3个已经在前面的文章介绍了,这里不再赘述。着重列出第4点的内容以及如何解决。如果正在研究录屏这块的朋友们,千万别去copy那什么网上有限制时间录制和收费的录制,特别是有些很恶心的还发表长篇大论写的如何如何实现(的确技术不可否认是实现了),其实最后还是要你付费才能完全使用,就问你恶不恶心!
好了,废话不多说,我们来一一解决;
首先获取系统光标有两种方式,第一种是直接通过系统API进行获取光标,这个是完全记录系统光标在做什么。随着系统光标变化而变化的。这边有用到的是几个类:
第一种方式:【CursorHelper.cs】、【GDIStuff.cs】、【Win32Stuff.cs】相对复杂一些;我就在代码中直接显示就好了,不需要引用任何其他的东西;
////// The rt global cursor. /// public class CursorHelper { #region Constants ////// The curso r_ showing. /// private const int CURSOR_SHOWING = 1; #endregion #region Public Methods and Operators ////// The capture cursor. /// /// /// The x. /// /// /// The y. /// ////// The public static Bitmap CaptureCursor(ref int x, ref int y) { Win32Stuff.CURSORINFO cursorInfo = new Win32Stuff.CURSORINFO(); cursorInfo.cbSize = Marshal.SizeOf(cursorInfo); if (!Win32Stuff.GetCursorInfo(out cursorInfo)) { return null; } if (cursorInfo.flags != Win32Stuff.CURSOR_SHOWING) { return null; } IntPtr hicon = Win32Stuff.CopyIcon(cursorInfo.hCursor); if (hicon == IntPtr.Zero) { return null; } Win32Stuff.ICONINFO iconInfo; if (!Win32Stuff.GetIconInfo(hicon, out iconInfo)) { return null; } x = cursorInfo.ptScreenPos.x - ((int)iconInfo.xHotspot); y = cursorInfo.ptScreenPos.y - ((int)iconInfo.yHotspot); using (Bitmap maskBitmap = Bitmap.FromHbitmap(iconInfo.hbmMask)) { // Is this a monochrome cursor? if (maskBitmap.Height == maskBitmap.Width * 2) { Bitmap resultBitmap = new Bitmap(maskBitmap.Width, maskBitmap.Width); using (Graphics desktopGraphics = Graphics.FromHwnd(Win32Stuff.GetDesktopWindow())) { IntPtr desktopHdc = desktopGraphics.GetHdc(); IntPtr maskHdc = GDIStuff.CreateCompatibleDC(desktopHdc); IntPtr oldPtr = GDIStuff.SelectObject(maskHdc, maskBitmap.GetHbitmap()); using (Graphics resultGraphics = Graphics.FromImage(resultBitmap)) { IntPtr resultHdc = resultGraphics.GetHdc(); // These two operation will result in a black cursor over a white background. // Later in the code, a call to MakeTransparent() will get rid of the white background. GDIStuff.BitBlt( resultHdc, 0, 0, 32, 32, maskHdc, 0, 32, (int)GDIStuff.TernaryRasterOperations.SRCCOPY); GDIStuff.BitBlt( resultHdc, 0, 0, 32, 32, maskHdc, 0, 0, (int)GDIStuff.TernaryRasterOperations.SRCINVERT); resultGraphics.ReleaseHdc(resultHdc); GDIStuff.DeleteDC(resultHdc); GDIStuff.DeleteObject(resultHdc); } IntPtr newPtr = GDIStuff.SelectObject(maskHdc, oldPtr); GDIStuff.DeleteObject(oldPtr); GDIStuff.DeleteObject(newPtr); GDIStuff.DeleteDC(maskHdc); desktopGraphics.ReleaseHdc(desktopHdc); GDIStuff.DeleteDC(desktopHdc); } // Remove the white background from the BitBlt calls, // resulting in a black cursor over a transparent background. resultBitmap.MakeTransparent(Color.White); return resultBitmap; } } //// Delete the mask, if present. // if (iconInfo.hbmMask != IntPtr.Zero) // { // DeleteObject(iconInfo.hbmMask); // } //// Delete the color bitmap, if present. // if (iconInfo.hbmColor != IntPtr.Zero) // { // DeleteObject(iconInfo.hbmColor); // } using (Icon icon = Icon.FromHandle(hicon)) { return icon.ToBitmap(); } } #endregion #region Methods ///. /// /// The copy icon. /// /// /// The h icon. /// ////// The [DllImport("user32.dll")] private static extern IntPtr CopyIcon(IntPtr hIcon); ///. /// /// The delete object. /// /// /// The h dc. /// ////// The [DllImport("gdi32.dll")] private static extern IntPtr DeleteObject(IntPtr hDc); ///. /// /// The destroy icon. /// /// /// The h icon. /// ////// The [DllImport("user32.dll")] private static extern bool DestroyIcon(IntPtr hIcon); ///. /// /// The get cursor info. /// /// /// The pci. /// ////// The [DllImport("user32.dll")] private static extern bool GetCursorInfo(out CURSORINFO pci); ///. /// /// The get gdi handle count. /// ////// The private static int GetGDIHandleCount() { return GetGuiResources(Process.GetCurrentProcess().Handle, 0); } ///. /// /// The get gui resources. /// /// /// The h process. /// /// /// The ui flags. /// ////// The [DllImport("user32.dll")] private static extern int GetGuiResources(IntPtr hProcess, int uiFlags); ///. /// /// The get icon info. /// /// /// The h icon. /// /// /// The piconinfo. /// ////// The [DllImport("user32.dll")] private static extern bool GetIconInfo(IntPtr hIcon, out ICONINFO piconinfo); ///. /// /// The get user handle count. /// ////// The private static int GetUserHandleCount() { return GetGuiResources(Process.GetCurrentProcess().Handle, 1); } ///. /// /// The handle message. /// /// /// The message. /// private static void HandleMessage(string message) { Debug.WriteLine("HC: " + message + ": GDI: " + GetGDIHandleCount() + ": User: " + GetUserHandleCount()); } #endregion ////// The cursorinfo. /// [StructLayout(LayoutKind.Sequential)] private struct CURSORINFO { // Fields ////// The cb size. /// [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation", Justification = "Reviewed. Suppression is OK here."), SuppressMessage("StyleCop.CSharp.NamingRules", "SA1307:AccessibleFieldsMustBeginWithUpperCaseLetter", Justification = "Reviewed. Suppression is OK here.")] public int cbSize; ////// The flags. /// [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation", Justification = "Reviewed. Suppression is OK here."), SuppressMessage("StyleCop.CSharp.NamingRules", "SA1307:AccessibleFieldsMustBeginWithUpperCaseLetter", Justification = "Reviewed. Suppression is OK here.")] public int flags; ////// The h cursor. /// [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation", Justification = "Reviewed. Suppression is OK here."), SuppressMessage("StyleCop.CSharp.NamingRules", "SA1307:AccessibleFieldsMustBeginWithUpperCaseLetter", Justification = "Reviewed. Suppression is OK here.")] public IntPtr hCursor; ////// The pt screen pos. /// [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation", Justification = "Reviewed. Suppression is OK here."), SuppressMessage("StyleCop.CSharp.NamingRules", "SA1307:AccessibleFieldsMustBeginWithUpperCaseLetter", Justification = "Reviewed. Suppression is OK here.")] public POINT ptScreenPos; } ////// The iconinfo. /// [StructLayout(LayoutKind.Sequential)] private struct ICONINFO { // Fields ////// The f icon. /// public bool fIcon; ////// The x hotspot. /// public int xHotspot; ////// The y hotspot. /// public int yHotspot; // Handle of the icon’s bitmask bitmap. ////// The hbm mask. /// public IntPtr hbmMask; // Handle of the icon’s color bitmap. Optional for monochrome icons. ////// The hbm color. /// public IntPtr hbmColor; } ////// The point. /// [StructLayout(LayoutKind.Sequential)] private struct POINT { // Fields ////// The x. /// public int x; ////// The y. /// public int y; } ////////// The capture cursor. ///// ///// ///// The x. ///// ///// ///// The y. ///// ////////// The // public static Bitmap CaptureCursor(ref int x, ref int y) // { // try // { // // Return value initially nothing // Bitmap bmp = null; // CURSORINFO curInfo = new CURSORINFO(); // curInfo.cbSize = Marshal.SizeOf(curInfo); // // HandleMessage("Start") // if (GetCursorInfo(ref curInfo)) // { // if (curInfo.flags == CURSOR_SHOWING) // { // IntPtr hicon = CopyIcon(curInfo.hCursor); // if (hicon != IntPtr.Zero) // { // ICONINFO icoInfo = default(ICONINFO); // if (GetIconInfo(hicon, ref icoInfo)) // { // // Delete the mask, if present. // if (icoInfo.hbmMask != IntPtr.Zero) // { // DeleteObject(icoInfo.hbmMask); // } // // Delete the color bitmap, if present. // if (icoInfo.hbmColor != IntPtr.Zero) // { // DeleteObject(icoInfo.hbmColor); // } // x = curInfo.ptScreenPos.x - icoInfo.xHotspot; // y = curInfo.ptScreenPos.y - icoInfo.yHotspot; // } // Icon ic = Icon.FromHandle(hicon); // bmp = ic.ToBitmap(); // // Must destroy the icon object we got from CopyIcon // DestroyIcon(hicon); // } // } // } // // HandleMessage("End") // return bmp; // } // catch // { // return null; // } // } }. /////
////// The gdi stuff. /// internal class GDIStuff { #region Constants ////// The srccopy. /// public const int SRCCOPY = 13369376; #endregion #region Enums ////// Specifies a raster-operation code. These codes define how the color data for the /// source rectangle is to be combined with the color data for the destination /// rectangle to achieve the final color. /// public enum TernaryRasterOperations { ///dest = source SRCCOPY = 0x00CC0020, ///dest = source OR dest SRCPAINT = 0x00EE0086, ///dest = source AND dest SRCAND = 0x008800C6, ///dest = source XOR dest SRCINVERT = 0x00660046, ///dest = source AND (NOT dest) SRCERASE = 0x00440328, ///dest = (NOT source) NOTSRCCOPY = 0x00330008, ///dest = (NOT src) AND (NOT dest) NOTSRCERASE = 0x001100A6, ///dest = (source AND pattern) MERGECOPY = 0x00C000CA, ///dest = (NOT source) OR dest MERGEPAINT = 0x00BB0226, ///dest = pattern PATCOPY = 0x00F00021, ///dest = DPSnoo PATPAINT = 0x00FB0A09, ///dest = pattern XOR dest PATINVERT = 0x005A0049, ///dest = (NOT dest) DSTINVERT = 0x00550009, ///dest = BLACK BLACKNESS = 0x00000042, ///dest = WHITE WHITENESS = 0x00FF0062, ////// Capture window as seen on screen. This includes layered windows /// such as WPF windows with AllowsTransparency="true" /// CAPTUREBLT = 0x40000000 } #endregion #region Public Methods and Operators ////// The bit blt. /// /// /// The hdc dest. /// /// /// The x dest. /// /// /// The y dest. /// /// /// The w dest. /// /// /// The h dest. /// /// /// The hdc source. /// /// /// The x src. /// /// /// The y src. /// /// /// The raster op. /// ////// The [DllImport("gdi32.dll", EntryPoint = "BitBlt")] [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation", Justification = "Reviewed. Suppression is OK here.")] [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1306:FieldNamesMustBeginWithLowerCaseLetter", Justification = "Reviewed. Suppression is OK here.")] public static extern bool BitBlt( IntPtr hdcDest, int xDest, int yDest, int wDest, int hDest, IntPtr hdcSource, int xSrc, int ySrc, int RasterOp); ///. /// /// The create compatible bitmap. /// /// /// The hdc. /// /// /// The n width. /// /// /// The n height. /// ////// The [DllImport("gdi32.dll", EntryPoint = "CreateCompatibleBitmap")] [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation", Justification = "Reviewed. Suppression is OK here.")] public static extern IntPtr CreateCompatibleBitmap(IntPtr hdc, int nWidth, int nHeight); ///. /// /// The create compatible dc. /// /// /// The hdc. /// ////// The [DllImport("gdi32.dll", EntryPoint = "CreateCompatibleDC")] public static extern IntPtr CreateCompatibleDC(IntPtr hdc); ///. /// /// The create dc. /// /// /// The lpsz driver. /// /// /// The lpsz device. /// /// /// The lpsz output. /// /// /// The lp init data. /// ////// The [DllImport("gdi32.dll", EntryPoint = "CreateDC")] public static extern IntPtr CreateDC(IntPtr lpszDriver, string lpszDevice, IntPtr lpszOutput, IntPtr lpInitData); ///. /// /// The delete dc. /// /// /// The h dc. /// ////// The [DllImport("gdi32.dll", EntryPoint = "DeleteDC")] public static extern IntPtr DeleteDC(IntPtr hDc); ///. /// /// The delete object. /// /// /// The h dc. /// ////// The [DllImport("gdi32.dll", EntryPoint = "DeleteObject")] public static extern IntPtr DeleteObject(IntPtr hDc); ///. /// /// The select object. /// /// /// The hdc. /// /// /// The bmp. /// ////// The [DllImport("gdi32.dll", EntryPoint = "SelectObject")] public static extern IntPtr SelectObject(IntPtr hdc, IntPtr bmp); #endregion }. ///
////// The win 32 stuff. /// internal class Win32Stuff { #region Constants ////// The curso r_ showing. /// public const int CURSOR_SHOWING = 0x00000001; ////// The s m_ cxscreen. /// public const int SM_CXSCREEN = 0; ////// The s m_ cyscreen. /// public const int SM_CYSCREEN = 1; #endregion #region Public Methods and Operators ////// The copy icon. /// /// /// The h icon. /// ////// The [DllImport("user32.dll", EntryPoint = "CopyIcon")] public static extern IntPtr CopyIcon(IntPtr hIcon); ///. /// /// The get cursor info. /// /// /// The pci. /// ////// The [DllImport("user32.dll", EntryPoint = "GetCursorInfo")] public static extern bool GetCursorInfo(out CURSORINFO pci); ///. /// /// The get dc. /// /// /// The ptr. /// ////// The [DllImport("user32.dll", EntryPoint = "GetDC")] public static extern IntPtr GetDC(IntPtr ptr); ///. /// /// The get desktop window. /// ////// The [DllImport("user32.dll", EntryPoint = "GetDesktopWindow")] public static extern IntPtr GetDesktopWindow(); ///. /// /// The get icon info. /// /// /// The h icon. /// /// /// The piconinfo. /// ////// The [DllImport("user32.dll", EntryPoint = "GetIconInfo")] public static extern bool GetIconInfo(IntPtr hIcon, out ICONINFO piconinfo); ///. /// /// The get system metrics. /// /// /// The abc. /// ////// The [DllImport("user32.dll", EntryPoint = "GetSystemMetrics")] public static extern int GetSystemMetrics(int abc); ///. /// /// The get window dc. /// /// /// The ptr. /// ////// The [DllImport("user32.dll", EntryPoint = "GetWindowDC")] public static extern IntPtr GetWindowDC(int ptr); ///. /// /// The release dc. /// /// /// The h wnd. /// /// /// The h dc. /// ////// The [DllImport("user32.dll", EntryPoint = "ReleaseDC")] public static extern IntPtr ReleaseDC(IntPtr hWnd, IntPtr hDc); #endregion ///. /// /// The cursorinfo. /// [StructLayout(LayoutKind.Sequential)] public struct CURSORINFO { ////// The cb size. /// public int cbSize; // Specifies the size, in bytes, of the structure. ////// The flags. /// public int flags; // Specifies the cursor state. This parameter can be one of the following values: ////// The h cursor. /// public IntPtr hCursor; // Handle to the cursor. ////// The pt screen pos. /// public POINT ptScreenPos; // A POINT structure that receives the screen coordinates of the cursor. } ////// The iconinfo. /// [StructLayout(LayoutKind.Sequential)] public struct ICONINFO { ////// The f icon. /// public bool fIcon; // Specifies whether this structure defines an icon or a cursor. A value of TRUE specifies ////// The x hotspot. /// public int xHotspot; // Specifies the x-coordinate of a cursor's hot spot. If this structure defines an icon, the hot ////// The y hotspot. /// public int yHotspot; // Specifies the y-coordinate of the cursor's hot spot. If this structure defines an icon, the hot ////// The hbm mask. /// public IntPtr hbmMask; // (HBITMAP) Specifies the icon bitmask bitmap. If this structure defines a black and white icon, ////// The hbm color. /// public IntPtr hbmColor; // (HBITMAP) Handle to the icon color bitmap. This member can be optional if this } ////// The point. /// [StructLayout(LayoutKind.Sequential)] public struct POINT { ////// The x. /// public int x; ////// The y. /// public int y; } }
OK,类已经铺垫好了,接下来就在你的视频捕获方法中放入:关键方法--CursorHelper.CaptureCursor(ref x,ref y);
1 Graphics g = Graphics.FromImage(bitmap);//编辑原始视频帧 2 g.SmoothingMode = SmoothingMode.AntiAlias;//设置鼠标质量 3 g.InterpolationMode = InterpolationMode.HighQualityBicubic; 4 g.PixelOffsetMode = PixelOffsetMode.HighQuality; 5 var x = _currentPoint.X; 6 var y = _currentPoint.Y; 7 var cursorBmp = CursorHelper.CaptureCursor(ref x, ref y); 8 if (cursorBmp != null) 9 {10 g.DrawImage(cursorBmp, _currentPoint);11 }12 cursorBmp.Dispose();
**注释说明:其中_currentPoint 是相对于屏幕的坐标Point** 获取方法是--
_currentPoint = System.Windows.Forms.Cursor.Position;//(大屏坐标)
**注释说明:其中bitmap是当前获取的最原始的视频帧(不包含任何的例如光标-声音-什么锤子之类的哈哈哈)**,此类方法就是把原始视频帧重新编辑!
第二种方式:相对简单一点,获取光标_currentPoint还是使用上面的方法,但是不同的地方是我要自定义光标icon,这个又有一点难点就是如何画怎么画;---项目中采用的是外圈描边,内边填充方式;
1 SolidBrush myBrush = new SolidBrush(System.Drawing.Color.FromArgb(50, ColorTranslator.FromHtml("#你的填充颜色")));//设置透明度跟填充颜色 2 System.Drawing.Pen p = new System.Drawing.Pen(ColorTranslator.FromHtml("#你的描边颜色"));//设置透明度跟描边颜色 3 Graphics g = Graphics.FromImage(bitmap);//编辑原始视频帧 4 g.SmoothingMode = SmoothingMode.AntiAlias;//设置鼠标质量 5 g.InterpolationMode = InterpolationMode.HighQualityBicubic; 6 g.PixelOffsetMode = PixelOffsetMode.HighQuality; 7 g.DrawEllipse(p, new Rectangle(_currentPoint.X - this.screenArea.Left, _currentPoint.Y - this.screenArea.Top, 25, 25));//描边 8 g.FillEllipse(myBrush, new Rectangle(_currentPoint.X - this.screenArea.Left, _currentPoint.Y - this.screenArea.Top, 25, 25));//填充圆形区域 9 myBrush.Dispose();10 p.Dispose();11 g.Flush();
**注释:在上述这种方式中特别注意,原始的方法比如你是全屏录制则采用以下方式即可,还有自定义笔刷的画法,我想做完给大家分享。**
1 g.DrawEllipse(p, new Rectangle(_currentPoint.X , _currentPoint.Y, 25, 25));//描边2 g.FillEllipse(myBrush, new Rectangle(_currentPoint.X , _currentPoint.Y, 25, 25));//填充圆形区域
**注释:如果你的录屏方式也存在区域模式,那么就采用 当前光标位置X轴减去你录屏区域的左坐标,当前光标位置Y轴减去你录屏的顶坐标即可获取,这种方式自适应任何区域**
以上是个人在完善时候研究的成果,在此希望把它们分享给更多正在研究的伙伴们,因为研究的时候的确遇到了非常多的问题,我希望这些文章能够给你们一些方向研究,加快你们的开发进度。