using System; using System.Drawing.Imaging; using System.IO; using SharpDX; using SharpDX.Direct3D11; using SharpDX.DXGI; using Device = SharpDX.Direct3D11.Device; using MapFlags = SharpDX.Direct3D11.MapFlags; using Rectangle = SharpDX.Rectangle; using System.Drawing; using System.Diagnostics; using System.Runtime.InteropServices; namespace RichCreator.Utility.Captures.DesktopDuplication { /// /// Provides access to frame-by-frame updates of a particular desktop (i.e. one monitor), with image and cursor information. /// public class DesktopDuplicator { private Device mDevice; private Texture2DDescription mTextureDesc; private OutputDescription mOutputDesc; private OutputDuplication mDeskDupl; private Texture2D desktopImageTexture = null; private OutputDuplicateFrameInformation frameInfo = new OutputDuplicateFrameInformation(); private int mWhichOutputDevice = -1; private Bitmap finalImage1, finalImage2; private bool isFinalImage1 = false; private Bitmap FinalImage { get { return isFinalImage1 ? finalImage1 : finalImage2; } set { if (isFinalImage1) { finalImage2 = value; if (finalImage1 != null) finalImage1.Dispose(); } else { finalImage1 = value; if (finalImage2 != null) finalImage2.Dispose(); } isFinalImage1 = !isFinalImage1; } } /// /// Duplicates the output of the specified monitor. /// /// The output device to duplicate (i.e. monitor). Begins with zero, which seems to correspond to the primary monitor. public DesktopDuplicator(int whichMonitor) : this(0, whichMonitor) { } /// /// Duplicates the output of the specified monitor on the specified graphics adapter. /// /// The adapter which contains the desired outputs. /// The output device to duplicate (i.e. monitor). Begins with zero, which seems to correspond to the primary monitor. public DesktopDuplicator(int whichGraphicsCardAdapter, int whichOutputDevice) { this.mWhichOutputDevice = whichOutputDevice; Adapter1 adapter = null; try { adapter = new Factory1().GetAdapter1(whichGraphicsCardAdapter); } catch (SharpDXException) { throw new DesktopDuplicationException("Could not find the specified graphics card adapter."); } this.mDevice = new Device(adapter); Output output = null; try { output = adapter.GetOutput(whichOutputDevice); } catch (SharpDXException) { throw new DesktopDuplicationException("Could not find the specified output device."); } var output1 = output.QueryInterface(); this.mOutputDesc = output.Description; this.mTextureDesc = new Texture2DDescription() { CpuAccessFlags = CpuAccessFlags.Read, BindFlags = BindFlags.None, Format = Format.B8G8R8A8_UNorm, Width = this.mOutputDesc.DesktopBounds.Width, Height = this.mOutputDesc.DesktopBounds.Height, OptionFlags = ResourceOptionFlags.None, MipLevels = 1, ArraySize = 1, SampleDescription = { Count = 1, Quality = 0 }, Usage = ResourceUsage.Staging }; try { this.mDeskDupl = output1.DuplicateOutput(mDevice); } catch (SharpDXException ex) { if (ex.ResultCode.Code == SharpDX.DXGI.ResultCode.NotCurrentlyAvailable.Result.Code) { throw new DesktopDuplicationException("There is already the maximum number of applications using the Desktop Duplication API running, please close one of the applications and try again."); } } } /// /// Retrieves the latest desktop image and associated metadata. /// public DesktopFrame GetLatestFrame() { var frame = new DesktopFrame(); // Try to get the latest frame; this may timeout bool retrievalTimedOut = RetrieveFrame(); if (retrievalTimedOut) { return null; } try { RetrieveFrameMetadata(frame); RetrieveCursorMetadata(frame); ProcessFrame(frame); } catch { ReleaseFrame(); } try { ReleaseFrame(); } catch { // throw new DesktopDuplicationException("Couldn't release frame."); } return frame; } /// /// 获取帧超时时间 /// private const Int32 AcquireTimeoutMillSecond = 500; private bool RetrieveFrame() { if (desktopImageTexture == null) { desktopImageTexture = new Texture2D(mDevice, mTextureDesc); } SharpDX.DXGI.Resource desktopResource = null; frameInfo = new OutputDuplicateFrameInformation(); try { mDeskDupl.AcquireNextFrame(AcquireTimeoutMillSecond, out frameInfo, out desktopResource); } catch (SharpDXException ex) { if (ex.ResultCode.Code == SharpDX.DXGI.ResultCode.WaitTimeout.Result.Code) { return true; } if (ex.ResultCode.Failure) { throw new DesktopDuplicationException("Failed to acquire next frame."); } } using (var tempTexture = desktopResource.QueryInterface()) { mDevice.ImmediateContext.CopyResource(tempTexture, desktopImageTexture); } desktopResource.Dispose(); return false; } private void RetrieveFrameMetadata(DesktopFrame frame) { if (frameInfo.TotalMetadataBufferSize > 0) { // Get moved regions int movedRegionsLength = 0; OutputDuplicateMoveRectangle[] movedRectangles = new OutputDuplicateMoveRectangle[frameInfo.TotalMetadataBufferSize]; mDeskDupl.GetFrameMoveRects(movedRectangles.Length, movedRectangles, out movedRegionsLength); frame.MovedRegions = new MovedRegion[movedRegionsLength / Marshal.SizeOf(typeof(OutputDuplicateMoveRectangle))]; for (int i = 0; i < frame.MovedRegions.Length; i++) { frame.MovedRegions[i] = new MovedRegion() { Source = new System.Drawing.Point(movedRectangles[i].SourcePoint.X, movedRectangles[i].SourcePoint.Y), Destination = new System.Drawing.Rectangle(movedRectangles[i].DestinationRect.X, movedRectangles[i].DestinationRect.Y, movedRectangles[i].DestinationRect.Width, movedRectangles[i].DestinationRect.Height) }; } // Get dirty regions int dirtyRegionsLength = 0; Rectangle[] dirtyRectangles = new Rectangle[frameInfo.TotalMetadataBufferSize]; mDeskDupl.GetFrameDirtyRects(dirtyRectangles.Length, dirtyRectangles, out dirtyRegionsLength); frame.UpdatedRegions = new System.Drawing.Rectangle[dirtyRegionsLength / Marshal.SizeOf(typeof(Rectangle))]; for (int i = 0; i < frame.UpdatedRegions.Length; i++) { frame.UpdatedRegions[i] = new System.Drawing.Rectangle(dirtyRectangles[i].X, dirtyRectangles[i].Y, dirtyRectangles[i].Width, dirtyRectangles[i].Height); } } else { frame.MovedRegions = new MovedRegion[0]; frame.UpdatedRegions = new System.Drawing.Rectangle[0]; } } private void RetrieveCursorMetadata(DesktopFrame frame) { var pointerInfo = new PointerInfo(); // A non-zero mouse update timestamp indicates that there is a mouse position update and optionally a shape change if (frameInfo.LastMouseUpdateTime == 0) return; bool updatePosition = true; // Make sure we don't update pointer position wrongly // If pointer is invisible, make sure we did not get an update from another output that the last time that said pointer // was visible, if so, don't set it to invisible or update. if (!frameInfo.PointerPosition.Visible && (pointerInfo.WhoUpdatedPositionLast != this.mWhichOutputDevice)) updatePosition = false; // If two outputs both say they have a visible, only update if new update has newer timestamp if (frameInfo.PointerPosition.Visible && pointerInfo.Visible && (pointerInfo.WhoUpdatedPositionLast != this.mWhichOutputDevice) && (pointerInfo.LastTimeStamp > frameInfo.LastMouseUpdateTime)) updatePosition = false; // Update position if (updatePosition) { pointerInfo.Position = new SharpDX.Point(frameInfo.PointerPosition.Position.X, frameInfo.PointerPosition.Position.Y); pointerInfo.WhoUpdatedPositionLast = mWhichOutputDevice; pointerInfo.LastTimeStamp = frameInfo.LastMouseUpdateTime; pointerInfo.Visible = frameInfo.PointerPosition.Visible; } // No new shape if (frameInfo.PointerShapeBufferSize == 0) return; if (frameInfo.PointerShapeBufferSize > pointerInfo.BufferSize) { pointerInfo.PtrShapeBuffer = new byte[frameInfo.PointerShapeBufferSize]; pointerInfo.BufferSize = frameInfo.PointerShapeBufferSize; } try { unsafe { fixed (byte* ptrShapeBufferPtr = pointerInfo.PtrShapeBuffer) { mDeskDupl.GetFramePointerShape(frameInfo.PointerShapeBufferSize, (IntPtr)ptrShapeBufferPtr, out pointerInfo.BufferSize, out pointerInfo.ShapeInfo); } } } catch (SharpDXException ex) { if (ex.ResultCode.Failure) { throw new DesktopDuplicationException("Failed to get frame pointer shape."); } } //frame.CursorVisible = pointerInfo.Visible; frame.CursorLocation = new System.Drawing.Point(pointerInfo.Position.X, pointerInfo.Position.Y); } private void ProcessFrame(DesktopFrame frame) { // Get the desktop capture texture var mapSource = mDevice.ImmediateContext.MapSubresource(desktopImageTexture, 0, MapMode.Read, MapFlags.None); FinalImage = new System.Drawing.Bitmap(mOutputDesc.DesktopBounds.Width, mOutputDesc.DesktopBounds.Height, PixelFormat.Format32bppRgb); var boundsRect = new System.Drawing.Rectangle(0, 0, mOutputDesc.DesktopBounds.Width, mOutputDesc.DesktopBounds.Height); // Copy pixels from screen capture Texture to GDI bitmap var mapDest = FinalImage.LockBits(boundsRect, ImageLockMode.WriteOnly, FinalImage.PixelFormat); var sourcePtr = mapSource.DataPointer; var destPtr = mapDest.Scan0; for (int y = 0; y < mOutputDesc.DesktopBounds.Height; y++) { // Copy a single line Utilities.CopyMemory(destPtr, sourcePtr, mOutputDesc.DesktopBounds.Width * 4); // Advance pointers sourcePtr = IntPtr.Add(sourcePtr, mapSource.RowPitch); destPtr = IntPtr.Add(destPtr, mapDest.Stride); } // Release source and dest locks FinalImage.UnlockBits(mapDest); mDevice.ImmediateContext.UnmapSubresource(desktopImageTexture, 0); frame.DesktopImage = FinalImage; } private void ReleaseFrame() { try { mDeskDupl.ReleaseFrame(); } catch (SharpDXException ex) { if (ex.ResultCode.Failure) { throw new DesktopDuplicationException("Failed to release frame."); } } } } }