Update to screen resource locking. Changed capture throttling.

This commit is contained in:
Jared Goodwin 2019-12-28 15:30:16 -08:00
parent f3e368543b
commit 17ae89fd17
17 changed files with 16417 additions and 29374 deletions

View File

@ -176,6 +176,7 @@ namespace Remotely.Desktop.Win.ViewModels
{
App.Current.Dispatcher.Invoke(() =>
{
App.Current.MainWindow.Activate();
var result = MessageBox.Show(Application.Current.MainWindow, $"You've received a connection request from {screenCastRequest.RequesterName}. Accept?", "Connection Request", MessageBoxButton.YesNo, MessageBoxImage.Question);
if (result == MessageBoxResult.Yes)
{

View File

@ -13,20 +13,53 @@ using System.Threading.Tasks;
using Remotely.Shared.Services;
using Remotely.Shared.Win32;
using Remotely.ScreenCast.Core.Interfaces;
using System.Diagnostics;
using System.Threading;
namespace Remotely.ScreenCast.Core.Capture
{
public class ScreenCasterBase
{
Conductor conductor;
string desktopName = string.Empty;
byte[] encodedImageBytes;
Queue<DateTime> fpsQueue = new Queue<DateTime>();
Viewer viewer;
private static SemaphoreSlim ScreenLock { get; } = new SemaphoreSlim(1);
public async Task BeginScreenCasting(string viewerID,
string requesterName,
ICapturer capturer)
{
var conductor = Conductor.Current;
Viewer viewer;
byte[] encodedImageBytes;
conductor = Conductor.Current;
await ScreenLock.WaitAsync();
await InitScreenCaster(viewerID, requesterName, capturer);
ScreenLock.Release();
while (!viewer.DisconnectRequested)
{
await ScreenLock.WaitAsync();
await ProcessCurrentFrame(capturer, viewerID);
ScreenLock.Release();
}
Logger.Write($"Ended screen cast. Requester: {requesterName}. Viewer ID: {viewerID}.");
conductor.Viewers.TryRemove(viewerID, out _);
capturer.Dispose();
// Close if no one is viewing.
if (conductor.Viewers.Count == 0 && conductor.Mode == Enums.AppMode.Unattended)
{
await conductor.CasterSocket.Disconnect();
Environment.Exit(0);
}
}
private async Task InitScreenCaster(string viewerID, string requesterName, ICapturer capturer)
{
Logger.Write($"Starting screen cast. Requester: {requesterName}. Viewer ID: {viewerID}. Capturer: {capturer.GetType().ToString()}. App Mode: {conductor.Mode} Desktop: {conductor.CurrentDesktopName}");
viewer = new Viewer()
@ -59,81 +92,86 @@ namespace Remotely.ScreenCast.Core.Capture
await conductor.CasterSocket.SendScreenSize(bounds.Width, bounds.Height, viewerID);
};
var desktopName = string.Empty;
if (OSUtils.IsWindows)
{
desktopName = Win32Interop.GetCurrentDesktop();
}
}
while (!viewer.DisconnectRequested)
private async Task ProcessCurrentFrame(ICapturer capturer, string viewerID)
{
try
{
try
WriteFps();
if (OSUtils.IsWindows)
{
if (OSUtils.IsWindows)
var currentDesktopName = Win32Interop.GetCurrentDesktop();
if (desktopName.ToLower() != currentDesktopName.ToLower())
{
var currentDesktopName = Win32Interop.GetCurrentDesktop();
if (desktopName.ToLower() != currentDesktopName.ToLower())
{
desktopName = currentDesktopName;
Logger.Write($"Switching to desktop {desktopName} in ScreenCaster.");
Win32Interop.SwitchToInputDesktop();
continue;
}
}
while (viewer.PendingFrames > 10000 / viewer.Latency)
{
await Task.Delay(10);
}
capturer.Capture();
var diffArea = ImageUtils.GetDiffArea(capturer.CurrentFrame, capturer.PreviousFrame, capturer.CaptureFullscreen);
if (diffArea.IsEmpty)
{
continue;
}
using (var newImage = capturer.CurrentFrame.Clone(diffArea, System.Drawing.Imaging.PixelFormat.Format32bppArgb))
{
if (capturer.CaptureFullscreen)
{
capturer.CaptureFullscreen = false;
}
encodedImageBytes = ImageUtils.EncodeBitmap(newImage, viewer.EncoderParams);
if (encodedImageBytes?.Length > 0)
{
await conductor.CasterSocket.SendScreenCapture(encodedImageBytes, viewerID, diffArea.Left, diffArea.Top, diffArea.Width, diffArea.Height, DateTime.UtcNow);
viewer.PendingFrames++;
}
desktopName = currentDesktopName;
Logger.Write($"Switching to desktop {desktopName} in ScreenCaster.");
Win32Interop.SwitchToInputDesktop();
return;
}
}
catch (Exception ex)
if (viewer.PendingFrames > 10)
{
Logger.Write(ex);
Logger.Write($"Throttling screen capture. Latency: {viewer.Latency}. Pending Frames: {viewer.PendingFrames}");
// This is to prevent dead-lock in case updates are missed from the browser.
viewer.PendingFrames = Math.Max(0, viewer.PendingFrames - 1);
await Task.Delay(100);
return;
}
finally
capturer.GetNextFrame();
var diffArea = ImageUtils.GetDiffArea(capturer.CurrentFrame, capturer.PreviousFrame, capturer.CaptureFullscreen);
if (diffArea.IsEmpty)
{
// TODO: Even after disposing of the bitmap, GC doesn't collect in time. Memory usage soars quickly.
// Need to revisit this later.
GC.Collect();
return;
}
using (var newImage = capturer.CurrentFrame.Clone(diffArea, System.Drawing.Imaging.PixelFormat.Format32bppArgb))
{
if (capturer.CaptureFullscreen)
{
capturer.CaptureFullscreen = false;
}
encodedImageBytes = ImageUtils.EncodeBitmap(newImage, viewer.EncoderParams);
if (encodedImageBytes?.Length > 0)
{
await conductor.CasterSocket.SendScreenCapture(encodedImageBytes, viewerID, diffArea.Left, diffArea.Top, diffArea.Width, diffArea.Height, DateTime.UtcNow);
viewer.PendingFrames++;
}
}
}
Logger.Write($"Ended screen cast. Requester: {requesterName}. Viewer ID: {viewerID}.");
conductor.Viewers.TryRemove(viewerID, out _);
capturer.Dispose();
// Close if no one is viewing.
if (conductor.Viewers.Count == 0 && conductor.Mode == Enums.AppMode.Unattended)
catch (Exception ex)
{
await conductor.CasterSocket.Disconnect();
Environment.Exit(0);
Logger.Write(ex);
}
finally
{
// TODO: Even after disposing of the bitmap, GC doesn't collect in time. Memory usage soars quickly.
// Need to revisit this later.
GC.Collect();
}
}
private void WriteFps()
{
if (Conductor.Current.IsDebug)
{
while (fpsQueue.Any() && DateTime.Now - fpsQueue.Peek() > TimeSpan.FromSeconds(1))
{
fpsQueue.Dequeue();
}
fpsQueue.Enqueue(DateTime.Now);
Debug.WriteLine("Capture FPS: " + fpsQueue.Count);
}
}
}

View File

@ -20,6 +20,7 @@ namespace Remotely.ScreenCast.Core
{
public static Conductor Current { get; private set; }
public IScreenCaster ScreenCaster { get; }
public bool IsDebug { get; }
public Conductor(CasterSocket casterSocket,
IScreenCaster screenCaster)
@ -27,6 +28,9 @@ namespace Remotely.ScreenCast.Core
Current = this;
ScreenCaster = screenCaster;
CasterSocket = casterSocket;
#if DEBUG
IsDebug = true;
#endif
}
public event EventHandler<ScreenCastRequest> ScreenCastRequested;

View File

@ -19,7 +19,7 @@ namespace Remotely.ScreenCast.Core.Interfaces
int GetScreenCount();
Rectangle GetVirtualScreenBounds();
void Capture();
void GetNextFrame();
void Init();
}
}

View File

@ -275,9 +275,9 @@ namespace Remotely.ScreenCast.Core.Sockets
{
if (conductor.Viewers.TryGetValue(viewerID, out var viewer))
{
viewer.PendingFrames--;
var latency = DateTime.UtcNow - sentTime;
viewer.Latency = latency.TotalMilliseconds;
viewer.PendingFrames = Math.Max(0, viewer.PendingFrames - 1);
}
});

View File

@ -26,7 +26,7 @@ namespace Remotely.ScreenCast.Linux.Capture
public Bitmap PreviousFrame { get; set; }
public event EventHandler<Rectangle> ScreenChanged;
public int SelectedScreen { get; private set; } = -1;
public void Capture()
public void GetNextFrame()
{
try
{

View File

@ -21,33 +21,14 @@ namespace Remotely.ScreenCast.Win.Capture
Init();
}
public event EventHandler<Rectangle> ScreenChanged;
public bool CaptureFullscreen { get; set; } = true;
public Bitmap CurrentFrame { get; set; }
public Rectangle CurrentScreenBounds { get; set; } = Screen.PrimaryScreen.Bounds;
public Bitmap PreviousFrame { get; set; }
public event EventHandler<Rectangle> ScreenChanged;
public int SelectedScreen { get; private set; } = Screen.AllScreens.ToList().IndexOf(Screen.PrimaryScreen);
private Graphics Graphic { get; set; }
private object ScreenLock { get; } = new object();
public void Capture()
{
try
{
lock (ScreenLock)
{
PreviousFrame = (Bitmap)CurrentFrame.Clone();
Graphic.CopyFromScreen(CurrentScreenBounds.Left, CurrentScreenBounds.Top, 0, 0, new Size(CurrentScreenBounds.Width, CurrentScreenBounds.Height));
}
}
catch (Exception ex)
{
Logger.Write(ex);
Init();
}
}
public void Dispose()
{
Graphic.Dispose();
@ -55,6 +36,19 @@ namespace Remotely.ScreenCast.Win.Capture
PreviousFrame.Dispose();
}
public void GetNextFrame()
{
try
{
PreviousFrame = (Bitmap)CurrentFrame.Clone();
Graphic.CopyFromScreen(CurrentScreenBounds.Left, CurrentScreenBounds.Top, 0, 0, new Size(CurrentScreenBounds.Width, CurrentScreenBounds.Height));
}
catch (Exception ex)
{
Logger.Write(ex);
Init();
}
}
public int GetScreenCount()
{
return Screen.AllScreens.Length;
@ -78,21 +72,19 @@ namespace Remotely.ScreenCast.Win.Capture
{
return;
}
lock (ScreenLock)
if (GetScreenCount() >= screenNumber + 1)
{
if (GetScreenCount() >= screenNumber + 1)
{
SelectedScreen = screenNumber;
}
else
{
SelectedScreen = 0;
}
CurrentScreenBounds = Screen.AllScreens[SelectedScreen].Bounds;
CaptureFullscreen = true;
Init();
ScreenChanged?.Invoke(this, CurrentScreenBounds);
SelectedScreen = screenNumber;
}
else
{
SelectedScreen = 0;
}
CurrentScreenBounds = Screen.AllScreens[SelectedScreen].Bounds;
CaptureFullscreen = true;
Init();
ScreenChanged?.Invoke(this, CurrentScreenBounds);
}
}
}

View File

@ -3,6 +3,7 @@ using Remotely.ScreenCast.Core.Services;
using SharpDX;
using SharpDX.Direct3D11;
using SharpDX.DXGI;
using SharpDX.Mathematics.Interop;
using System;
using System.Diagnostics;
using System.Drawing;
@ -38,7 +39,7 @@ namespace Remotely.ScreenCast.Win.Capture
Init();
}
public void Capture()
public void GetNextFrame()
{
try
{
@ -53,27 +54,29 @@ namespace Remotely.ScreenCast.Win.Capture
OutputDuplicateFrameInformation duplicateFrameInformation;
// Try to get duplicated frame within given time is ms
duplicatedOutput.TryAcquireNextFrame(50, out duplicateFrameInformation, out screenResource);
duplicatedOutput.TryAcquireNextFrame(100, out duplicateFrameInformation, out screenResource);
while (duplicateFrameInformation.AccumulatedFrames < 1)
if (duplicateFrameInformation.AccumulatedFrames == 0)
{
var result = duplicatedOutput.TryAcquireNextFrame(50, out duplicateFrameInformation, out screenResource);
if (result.Failure)
try
{
try
{
duplicatedOutput.ReleaseFrame();
}
catch { }
duplicatedOutput.ReleaseFrame();
}
catch { }
return;
}
// TODO: Get dirty rects.
//RawRectangle[] dirtyRectsBuffer = new RawRectangle[duplicateFrameInformation.TotalMetadataBufferSize];
//duplicatedOutput.GetFrameDirtyRects(duplicateFrameInformation.TotalMetadataBufferSize, dirtyRectsBuffer, out var dirtyRectsSizeRequired);
// copy resource into memory that can be accessed by the CPU
using (var screenTexture2D = screenResource.QueryInterface<Texture2D>())
{
device.ImmediateContext.CopyResource(screenTexture2D, screenTexture);
}
// Get the desktop capture texture
var mapSource = device.ImmediateContext.MapSubresource(screenTexture, 0, MapMode.Read, SharpDX.Direct3D11.MapFlags.None);

View File

@ -251,6 +251,9 @@ namespace Remotely.ScreenCast.Win.Services
case "F12":
keyCode = VirtualKey.F12;
break;
case "Meta":
keyCode = VirtualKey.LWIN;
break;
default:
keyCode = (VirtualKey)VkKeyScan(Convert.ToChar(key));
break;

View File

@ -34,14 +34,7 @@ namespace Remotely.ScreenCast.Win.Services
ICapturer capturer;
try
{
if (Conductor.Current.Viewers.Count == 0)
{
capturer = new DXCapture();
}
else
{
capturer = new BitBltCapture();
}
capturer = new DXCapture();
}
catch (Exception ex)
{

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@ -50,4 +50,6 @@ $Cmdlets | ForEach-Object {
$Error.Clear()
}
$Global:Commands = $Global:Commands -replace "([^\\])\\([\w\d])", '$1\\$2'
$Global:Commands | Out-File -FilePath "$env:USERPROFILE\Downloads\PS.txt"