/// <summary> /// Retrieves the moved and dirty regions of the currently duplicated frame, populating the specified desktop frame. /// </summary> /// <param name="frame"></param> /// <param name="frameInfo"></param> public void RetrieveFrameMetadata(DesktopFrame frame, OutduplFrameInfo frameInfo) { if (frameInfo.TotalMetadataBufferSize > 0) { var movedRectangles = new OutduplMoveRect[frameInfo.TotalMetadataBufferSize]; // Get moved regions _outputDuplication.GetFrameMoveRects(movedRectangles.Length, movedRectangles, out int movedRegionsLength); var movedRegions = new MovedRegion[movedRegionsLength / Marshal.SizeOf(typeof(OutduplMoveRect))]; for (int i = 0; i < movedRegions.Length; i++) { movedRegions[i] = new MovedRegion() { Source = new Point(movedRectangles[i].SourcePoint.X, movedRectangles[i].SourcePoint.Y), Destination = new Rectangle(movedRectangles[i].DestinationRect.Left, movedRectangles[i].DestinationRect.Top, movedRectangles[i].DestinationRect.Right, movedRectangles[i].DestinationRect.Bottom) }; } frame.MovedRegions = movedRegions; // Get dirty regions var dirtyRectangles = new RawRect[frameInfo.TotalMetadataBufferSize]; _outputDuplication.GetFrameDirtyRects(dirtyRectangles.Length, dirtyRectangles, out int dirtyRegionsLength); var updatedRegions = new Rectangle[dirtyRegionsLength / Marshal.SizeOf(typeof(Rectangle))]; for (int i = 0; i < updatedRegions.Length; i++) { updatedRegions[i] = new Rectangle(dirtyRectangles[i].Left, dirtyRectangles[i].Top, dirtyRectangles[i].Right, dirtyRectangles[i].Bottom); } frame.UpdatedRegions = updatedRegions; } else { frame.MovedRegions = Array.Empty <MovedRegion>(); frame.UpdatedRegions = Array.Empty <Rectangle>(); } }
/// <summary> /// Updates the specified frame with the next retrieved frame. /// </summary> private void UpdateFrame(DesktopDuplicatorInternal ddupe, DesktopFrame frame) { // Try to get the latest frame; if (ddupe.TryRetrieveFrame(out var frameInfo)) { try { ddupe.RetrieveFrameMetadata(frame, frameInfo); ddupe.RetrieveCursorMetadata(frame, frameInfo); ddupe.ProcessFrame(frame); } finally { ddupe.ReleaseFrame(_logger); } } }
/// <summary> /// Using the staging texture /// </summary> /// <param name="frame"></param> public void ProcessFrame(DesktopFrame frame) { frame.DesktopWidth = Math.Abs(_desktopRect.Right - _desktopRect.Left); frame.DesktopHeight = Math.Abs(_desktopRect.Bottom - _desktopRect.Top); // Get the desktop capture texture var mapSource = _d3dDevice.ImmediateContext.Map(_desktopImageTexture, 0, MapMode.Read, MapFlags.None); var buffer = new byte[frame.DesktopHeight * mapSource.RowPitch]; // Copy pixels from screen capture Texture to the image buffer. var bufferSpan = new Span <byte>(buffer); mapSource.AsSpan <byte>(buffer.Length).CopyTo(bufferSpan); // Release source and dest locks _d3dDevice.ImmediateContext.Unmap(_desktopImageTexture, 0); frame.DesktopFrameBuffer = buffer; frame.IsDesktopImageBufferEmpty = bufferSpan.Trim((byte)0x00).IsEmpty; }
/// <summary> /// Duplicates the desktop yielding Desktop Frames until cancelled. /// </summary> /// <param name="token"></param> /// <returns></returns> public IEnumerable <DesktopFrame> DuplicateDesktop(CancellationToken token) { _logger.LogInformation($"Beginning Desktop Duplication"); using var duplicator = DesktopDuplicatorInternal.CreateDesktopDuplicator(_logger, _adapterIndex, _outputDeviceIndex); while (token.IsCancellationRequested == false) { var desktopFrame = new DesktopFrame(); UpdateFrame(duplicator, desktopFrame); if (token.IsCancellationRequested == false) { if (desktopFrame.IsDesktopImageBufferEmpty) { continue; } yield return(desktopFrame); } } }
public static DesktopFrame FromFile(string path) { if (string.IsNullOrWhiteSpace(path)) { throw new ArgumentNullException(nameof(path)); } var result = new DesktopFrame(); using var image = SixLabors.ImageSharp.Image.Load(path); { using var imageClone = image.CloneAs <Bgra32>(); { var bpp = imageClone.PixelType.BitsPerPixel / 8; int bytes = imageClone.Height * imageClone.Width * bpp; var frameBuffer = new byte[bytes]; var bufferSpan = new Span <byte>(frameBuffer); if (imageClone.TryGetSinglePixelSpan(out Span <Bgra32> imageSpan)) { for (int i = 0; i < imageSpan.Length; i++) { bufferSpan[i * bpp] = imageSpan[i].B; bufferSpan[i * bpp + 1] = imageSpan[i].G; bufferSpan[i * bpp + 2] = imageSpan[i].R; bufferSpan[i * bpp + 3] = imageSpan[i].A; } result.DesktopFrameBuffer = frameBuffer; result.DesktopWidth = imageClone.Width; result.DesktopHeight = imageClone.Height; result.IsDesktopImageBufferEmpty = bufferSpan.Trim((byte)0x00).IsEmpty; } } } return(result); }
/// <summary> /// Retrieves the cursor information contained in the currently duplicated frame, populating the specified desktop frame. /// </summary> /// <param name="frame"></param> /// <param name="frameInfo"></param> public void RetrieveCursorMetadata(DesktopFrame frame, OutduplFrameInfo frameInfo) { // A non-zero mouse update timestamp indicates that there is a mouse position update and optionally a shape change if (frameInfo.LastMouseUpdateTime == 0) { frame.PointerPosition.Visible = _pointerInfo.Visible; frame.PointerPosition.X = _pointerInfo.Position.X; frame.PointerPosition.Y = _pointerInfo.Position.Y; 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 != _outputDeviceIndex)) { 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 != _outputDeviceIndex) && (_pointerInfo.LastTimeStamp > frameInfo.LastMouseUpdateTime)) { updatePosition = false; } // Update position if (updatePosition) { _pointerInfo.Position = new Point(frameInfo.PointerPosition.Position.X, frameInfo.PointerPosition.Position.Y); _pointerInfo.WhoUpdatedPositionLast = _outputDeviceIndex; _pointerInfo.LastTimeStamp = frameInfo.LastMouseUpdateTime; _pointerInfo.Visible = frameInfo.PointerPosition.Visible; } frame.PointerPosition.Visible = _pointerInfo.Visible; frame.PointerPosition.X = _pointerInfo.Position.X; frame.PointerPosition.Y = _pointerInfo.Position.Y; // No new shape if (frameInfo.PointerShapeBufferSize == 0) { return; } if (_pointerInfo.ShapeBuffer == null || frameInfo.PointerShapeBufferSize != _pointerInfo.ShapeBuffer.Length) { _pointerInfo.ShapeBuffer = new byte[frameInfo.PointerShapeBufferSize]; } try { // Create a new pinned region of memory, copy the contents of the PtrShapeBuffer to it, use it to retrieve the pointer shape and then free the pinned region. unsafe { fixed(byte *ptrShapeBufferPtr = _pointerInfo.ShapeBuffer) { _outputDuplication.GetFramePointerShape(frameInfo.PointerShapeBufferSize, (IntPtr)ptrShapeBufferPtr, out int bufferSize, out _pointerInfo.ShapeInfo); } } } catch (SharpGenException ex) { if (ex.ResultCode.Failure) { throw new DesktopDuplicationException("Failed to get frame pointer shape."); } } var pointerShapeSpan = new ReadOnlySpan <byte>(_pointerInfo.ShapeBuffer); if (!pointerShapeSpan.Trim((byte)0x00).IsEmpty) { frame.PointerShape.Width = _pointerInfo.ShapeInfo.Width; frame.PointerShape.Height = _pointerInfo.ShapeInfo.Height; frame.PointerShape.Pitch = _pointerInfo.ShapeInfo.Pitch; frame.PointerShape.Type = _pointerInfo.ShapeInfo.Type; frame.PointerShape.HotSpotX = _pointerInfo.ShapeInfo.HotSpot.X; frame.PointerShape.HotSpotY = _pointerInfo.ShapeInfo.HotSpot.Y; frame.PointerShapeBuffer = _pointerInfo.ShapeBuffer; } }