最近手头工作比较轻松了一点就继续研究和完善之前的录屏软件,使用AForge最大的问题在于:最原始的只能够录全屏,而自定义的录屏需要更改非常多的细节:like follows:
1、需要支持区域化录屏;
2、需要支持麦克风录音,并且混音在视频中,同步;
3、需要支持系统声音录取、并且需要混音在视频中,同步;
4、需要支持捕获光标,并且自定义颜色、描边,最重要的是你需要在区域录屏的时候支持坐标位置更新(相对比较难);
前面3个已经在前面的文章介绍了,这里不再赘述。着重列出第4点的内容以及如何解决。如果正在研究录屏这块的朋友们,千万别去copy那什么网上有限制时间录制和收费的录制,特别是有些很恶心的还发表长篇大论写的如何如何实现(的确技术不可否认是实现了),其实最后还是要你付费才能完全使用,就问你恶不恶心!
好了,废话不多说,我们来一一解决;
首先获取系统光标有两种方式,第一种是直接通过系统API进行获取光标,这个是完全记录系统光标在做什么。随着系统光标变化而变化的。这边有用到的是几个类:
第一种方式:【CursorHelper.cs】、【GDIStuff.cs】、【Win32Stuff.cs】相对复杂一些;我就在代码中直接显示就好了,不需要引用任何其他的东西;
///CursorHelper.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;
// }
// }
}
///GDIStuff.cs
/// 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
}
///Win32Stuff.cs
/// 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轴减去你录屏的顶坐标即可获取,这种方式自适应任何区域**
以上是个人在完善时候研究的成果,在此希望把它们分享给更多正在研究的伙伴们,因为研究的时候的确遇到了非常多的问题,我希望这些文章能够给你们一些方向研究,加快你们的开发进度。