mirror of
https://github.com/immense/Remotely.git
synced 2025-10-26 11:27:15 +00:00
Update to screen resource locking. Changed capture throttling.
This commit is contained in:
parent
f3e368543b
commit
17ae89fd17
@ -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)
|
||||
{
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -19,7 +19,7 @@ namespace Remotely.ScreenCast.Core.Interfaces
|
||||
int GetScreenCount();
|
||||
Rectangle GetVirtualScreenBounds();
|
||||
|
||||
void Capture();
|
||||
void GetNextFrame();
|
||||
void Init();
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@ -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
|
||||
{
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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
@ -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"
|
||||
Loading…
Reference in New Issue
Block a user