Skip to content

Commit

Permalink
Rename ScreenCapturer.
Browse files Browse the repository at this point in the history
  • Loading branch information
bitbound committed Jan 4, 2025
1 parent 1409856 commit 90ea87a
Show file tree
Hide file tree
Showing 5 changed files with 111 additions and 109 deletions.
186 changes: 94 additions & 92 deletions ControlR.Streamer/Services/DesktopCapturer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ internal class DesktopCapturer : IDesktopCapturer
private readonly IMemoryProvider _memoryProvider;
private readonly IMessenger _messenger;
private readonly Stopwatch _metricsBroadcastTimer = Stopwatch.StartNew();
private readonly IScreenCapturer _screenCapturer;
private readonly IScreenGrabber _screenGrabber;
private readonly ConcurrentQueue<SentFrame> _sentRegions = new();
private readonly IOptions<StartupOptions> _startupOptions;
private readonly TimeProvider _timeProvider;
Expand All @@ -63,7 +63,7 @@ internal class DesktopCapturer : IDesktopCapturer
public DesktopCapturer(
TimeProvider timeProvider,
IMessenger messenger,
IScreenCapturer screenCapturer,
IScreenGrabber screenGrabber,
IBitmapUtility bitmapUtility,
IMemoryProvider memoryProvider,
IWin32Interop win32Interop,
Expand All @@ -73,7 +73,7 @@ public DesktopCapturer(
ILogger<DesktopCapturer> logger)
{
_messenger = messenger;
_screenCapturer = screenCapturer;
_screenGrabber = screenGrabber;
_bitmapUtility = bitmapUtility;
_memoryProvider = memoryProvider;
_win32Interop = win32Interop;
Expand All @@ -82,7 +82,7 @@ public DesktopCapturer(
_startupOptions = startupOptions;
_appLifetime = appLifetime;
_logger = logger;
_displays = _screenCapturer.GetDisplays().ToArray();
_displays = _screenGrabber.GetDisplays().ToArray();
_selectedDisplay =
_displays.FirstOrDefault(x => x.IsPrimary) ??
_displays.FirstOrDefault();
Expand Down Expand Up @@ -148,7 +148,7 @@ public IEnumerable<DisplayDto> GetDisplays()

public void ResetDisplays()
{
_displays = _screenCapturer.GetDisplays().ToArray();
_displays = _screenGrabber.GetDisplays().ToArray();
_selectedDisplay =
_displays.FirstOrDefault(x => x.IsPrimary) ??
_displays.FirstOrDefault();
Expand All @@ -159,7 +159,7 @@ public void ResetDisplays()

public Task StartCapturingChanges()
{
EncodeScreenCaptures(_appLifetime.ApplicationStopping).Forget();
StartCapturingChangesImpl(_appLifetime.ApplicationStopping).Forget();
return Task.CompletedTask;
}

Expand Down Expand Up @@ -266,92 +266,6 @@ private void EncodeRegion(Bitmap bitmap, Rectangle region, bool isKeyFrame = fal
}
}

private async Task EncodeScreenCaptures(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
try
{
await _frameRequestedSignal.Wait(stoppingToken);

Interlocked.Increment(ref _iterations);

if (_selectedDisplay is not { } selectedDisplay)
{
_logger.LogWarning("Selected display is null. Unable to capture latest frame.");
await _delayer.Delay(_afterFailureDelay, stoppingToken);
continue;
}

_win32Interop.SwitchToInputDesktop();

using var captureResult = _screenCapturer.Capture(selectedDisplay);

if (captureResult.HadNoChanges)
{
await _delayer.Delay(_afterFailureDelay, stoppingToken);
continue;
}

if (captureResult.DxTimedOut)
{
_logger.LogDebug("DirectX capture timed out. BitBlt fallback used.");
}

if (!captureResult.IsSuccess)
{
_logger.LogWarning(captureResult.Exception, "Failed to capture latest frame. Reason: {ResultReason}",
captureResult.FailureReason);
await _delayer.Delay(_afterFailureDelay, stoppingToken);
continue;
}

if (ShouldSendKeyFrame())
{
EncodeRegion(captureResult.Bitmap, captureResult.Bitmap.ToRectangle(), true);
_forceKeyFrame = false;
_needsKeyFrame = false;
_lastCpuBitmap?.Dispose();
_lastCpuBitmap = null;
_lastDisplayId = selectedDisplay.DeviceName;
_lastMonitorArea = selectedDisplay.MonitorArea;
continue;
}

if (captureResult.IsUsingGpu)
{
await EncodeGpuCaptureResult(captureResult);
}
else
{
await EncodeCpuCaptureResult(captureResult, stoppingToken);
}

await ReportMetrics();
}
catch (OperationCanceledException)
{
_logger.LogInformation("Screen streaming cancelled.");
break;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error encoding screen captures.");
}
finally
{
if (!_changedRegions.IsEmpty)
{
_frameReadySignal.Set();
}
else
{
_frameRequestedSignal.Set();
}
}
}
}

private int GetTargetImageQuality()
{
if (_currentMbps < TargetMbps)
Expand Down Expand Up @@ -458,6 +372,94 @@ private bool ShouldSendKeyFrame()
return _needsKeyFrame && _currentMbps < TargetMbps * .5;
}

private async Task StartCapturingChangesImpl(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
try
{
await _frameRequestedSignal.Wait(stoppingToken);

Interlocked.Increment(ref _iterations);

if (_selectedDisplay is not { } selectedDisplay)
{
_logger.LogWarning("Selected display is null. Unable to capture latest frame.");
await _delayer.Delay(_afterFailureDelay, stoppingToken);
continue;
}

_win32Interop.SwitchToInputDesktop();

using var captureResult = _screenGrabber.Capture(selectedDisplay);

if (captureResult.HadNoChanges)
{
await _delayer.Delay(_afterFailureDelay, stoppingToken);
continue;
}

if (captureResult.DxTimedOut)
{
_logger.LogDebug("DirectX capture timed out. BitBlt fallback used.");
}

if (!captureResult.IsSuccess)
{
_logger.LogWarning(captureResult.Exception, "Failed to capture latest frame. Reason: {ResultReason}",
captureResult.FailureReason);
_lastCpuBitmap = null;
_forceKeyFrame = true;
await _delayer.Delay(_afterFailureDelay, stoppingToken);
continue;
}

if (ShouldSendKeyFrame())
{
EncodeRegion(captureResult.Bitmap, captureResult.Bitmap.ToRectangle(), true);
_forceKeyFrame = false;
_needsKeyFrame = false;
_lastCpuBitmap?.Dispose();
_lastCpuBitmap = null;
_lastDisplayId = selectedDisplay.DeviceName;
_lastMonitorArea = selectedDisplay.MonitorArea;
continue;
}

if (captureResult.IsUsingGpu)
{
await EncodeGpuCaptureResult(captureResult);
}
else
{
await EncodeCpuCaptureResult(captureResult, stoppingToken);
}

await ReportMetrics();
}
catch (OperationCanceledException)
{
_logger.LogInformation("Screen streaming cancelled.");
break;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error encoding screen captures.");
}
finally
{
if (!_changedRegions.IsEmpty)
{
_frameReadySignal.Set();
}
else
{
_frameRequestedSignal.Set();
}
}
}
}

private async Task ThrottleStream()
{
if (_currentMbps > TargetMbps * 2)
Expand Down
2 changes: 1 addition & 1 deletion ControlR.Web.Server/wwwroot/downloads/AgentVersion.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.11.92.0
0.11.93.0
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ public static class ServiceCollectionExtensions
/// Adds the following services with the specified lifetimes:
/// <list type="bullet">
/// <item>
/// <see cref="IScreenCapturer" /> as Singleton
/// <see cref="IScreenGrabber" /> as Singleton
/// </item>
/// <item>
/// <see cref="IBitmapUtility" /> as Singleton
Expand All @@ -22,7 +22,7 @@ public static IServiceCollection AddScreenCapturer(this IServiceCollection servi
{
return services
.AddSingleton<IBitmapUtility, BitmapUtility>()
.AddSingleton<IScreenCapturer, ScreenCapturer>()
.AddSingleton<IScreenGrabber, ScreenGrabber>()
.AddSingleton<IDxOutputGenerator, DxOutputGenerator>();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

namespace ControlR.Libraries.ScreenCapture;

public interface IScreenCapturer
public interface IScreenGrabber
{
/// <summary>
/// Gets a capture of a specific display.
Expand Down Expand Up @@ -65,16 +65,16 @@ CaptureResult Capture(
Rectangle GetVirtualScreenBounds();
}

internal sealed class ScreenCapturer(
internal sealed class ScreenGrabber(
TimeProvider timeProvider,
IBitmapUtility bitmapUtility,
IDxOutputGenerator dxOutputGenerator,
ILogger<ScreenCapturer> logger) : IScreenCapturer
ILogger<ScreenGrabber> logger) : IScreenGrabber
{
private readonly TimeProvider _timeProvider = timeProvider;
private readonly IBitmapUtility _bitmapUtility = bitmapUtility;
private readonly IDxOutputGenerator _dxOutputGenerator = dxOutputGenerator;
private readonly ILogger<ScreenCapturer> _logger = logger;
private readonly ILogger<ScreenGrabber> _logger = logger;

public CaptureResult Capture(
DisplayInfo targetDisplay,
Expand Down
20 changes: 10 additions & 10 deletions Tests/ControlR.Libraries.ScreenCapture.Benchmarks/CaptureTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,16 @@ public sealed class CaptureTests
private readonly LoggerFactory _loggerFactory;
private readonly BitmapUtility _bitmapUtility;
private readonly DxOutputGenerator _dxGenerator;
private readonly ScreenCapturer _capturer;
private readonly ScreenGrabber _grabber;
private readonly IEnumerable<DisplayInfo> _displays;

public CaptureTests()
{
_loggerFactory = new LoggerFactory();
_bitmapUtility = new BitmapUtility();
_dxGenerator = new DxOutputGenerator(_loggerFactory.CreateLogger<DxOutputGenerator>());
_capturer = new ScreenCapturer(TimeProvider.System, _bitmapUtility, _dxGenerator, _loggerFactory.CreateLogger<ScreenCapturer>());
_displays = _capturer.GetDisplays();
_grabber = new ScreenGrabber(TimeProvider.System, _bitmapUtility, _dxGenerator, _loggerFactory.CreateLogger<ScreenGrabber>());
_displays = _grabber.GetDisplays();
}


Expand All @@ -36,7 +36,7 @@ public void DoCaptureEncodeAndDiff()

for (var i = 0; i < count; i++)
{
var result = _capturer.Capture(display1, true, tryUseDirectX: true, allowFallbackToBitBlt: true);
var result = _grabber.Capture(display1, true, tryUseDirectX: true, allowFallbackToBitBlt: true);
if (!result.IsSuccess)
{
continue;
Expand Down Expand Up @@ -90,7 +90,7 @@ public void DoCaptures()

for (var i = 0; i < count; i++)
{
using var result = _capturer.Capture(display1, true, tryUseDirectX: true, allowFallbackToBitBlt: false);
using var result = _grabber.Capture(display1, true, tryUseDirectX: true, allowFallbackToBitBlt: false);
}

var fps = Math.Round(count / sw.Elapsed.TotalSeconds);
Expand All @@ -112,7 +112,7 @@ public void DoDiffSizeComparison()

for (var i = 0; i < count; i++)
{
var result = _capturer.Capture(display1, true, tryUseDirectX: true, allowFallbackToBitBlt: false);
var result = _grabber.Capture(display1, true, tryUseDirectX: true, allowFallbackToBitBlt: false);
if (!result.IsSuccess)
{
continue;
Expand All @@ -137,7 +137,7 @@ public void DoDiffSizeComparison()

for (var i = 0; i < count; i++)
{
var result = _capturer.Capture(display1, true, tryUseDirectX: true, allowFallbackToBitBlt: false);
var result = _grabber.Capture(display1, true, tryUseDirectX: true, allowFallbackToBitBlt: false);
if (!result.IsSuccess)
{
continue;
Expand Down Expand Up @@ -179,7 +179,7 @@ public void DoEncoding()
{
var count = 300;
var display1 = _displays.First(x => x.DeviceName == "\\\\.\\DISPLAY1");
using var result = _capturer.Capture(display1, true, tryUseDirectX: true, allowFallbackToBitBlt: true);
using var result = _grabber.Capture(display1, true, tryUseDirectX: true, allowFallbackToBitBlt: true);
if (!result.IsSuccess)
{
throw new Exception("Capture failed.");
Expand All @@ -197,7 +197,7 @@ public void DoEncoding()
public void DoSaveToDesktop()
{
var display1 = _displays.First(x => x.DeviceName == "\\\\.\\DISPLAY1");
using var result = _capturer.Capture(display1);
using var result = _grabber.Capture(display1);
if (!result.IsSuccess)
{
throw new Exception("Capture failed.");
Expand All @@ -215,7 +215,7 @@ public async Task DoWinRtComparison()
{
var iterations = 300;
var display1 = _displays.First(x => x.DeviceName == "\\\\.\\DISPLAY1");
using var result = _capturer.Capture(display1);
using var result = _grabber.Capture(display1);
if (!result.IsSuccess)
{
throw new Exception("Capture failed.");
Expand Down

0 comments on commit 90ea87a

Please sign in to comment.