281 lines
9.2 KiB
C#
281 lines
9.2 KiB
C#
using System;
|
|
using System.Drawing;
|
|
using System.Drawing.Imaging;
|
|
using System.IO;
|
|
using System.Runtime.InteropServices;
|
|
using SharpDX;
|
|
using SharpDX.Direct3D11;
|
|
using SharpDX.DXGI;
|
|
using TurboJpegWrapper;
|
|
using Device = SharpDX.Direct3D11.Device;
|
|
using MapFlags = SharpDX.Direct3D11.MapFlags;
|
|
using Resource = SharpDX.DXGI.Resource;
|
|
|
|
namespace Crysome.Client.Handlers
|
|
{
|
|
internal sealed class DxgiCapture : IDisposable
|
|
{
|
|
private Device _device;
|
|
private OutputDuplication _duplication;
|
|
private Texture2D _staging;
|
|
private int _width;
|
|
private int _height;
|
|
private bool _disposed;
|
|
|
|
private byte[] _bgrBuffer;
|
|
private TJCompressor _turboJpeg;
|
|
private bool _turboAvailable;
|
|
|
|
public int Width => _width;
|
|
public int Height => _height;
|
|
public bool IsInitialized => _duplication != null;
|
|
|
|
public bool Init(int screenIndex)
|
|
{
|
|
Cleanup();
|
|
try
|
|
{
|
|
using (var factory = new Factory1())
|
|
{
|
|
Adapter1 adapter = factory.GetAdapter1(0);
|
|
_device = new Device(adapter, DeviceCreationFlags.BgraSupport);
|
|
|
|
int outputIdx = Math.Max(0, screenIndex);
|
|
Output output = adapter.GetOutput(outputIdx);
|
|
using (var output1 = output.QueryInterface<Output1>())
|
|
{
|
|
_width = output.Description.DesktopBounds.Right - output.Description.DesktopBounds.Left;
|
|
_height = output.Description.DesktopBounds.Bottom - output.Description.DesktopBounds.Top;
|
|
|
|
_staging = new Texture2D(_device, new Texture2DDescription
|
|
{
|
|
CpuAccessFlags = CpuAccessFlags.Read,
|
|
BindFlags = BindFlags.None,
|
|
Format = Format.B8G8R8A8_UNorm,
|
|
Width = _width,
|
|
Height = _height,
|
|
MipLevels = 1,
|
|
ArraySize = 1,
|
|
SampleDescription = new SampleDescription(1, 0),
|
|
Usage = ResourceUsage.Staging,
|
|
OptionFlags = ResourceOptionFlags.None
|
|
});
|
|
|
|
_duplication = output1.DuplicateOutput(_device);
|
|
}
|
|
output.Dispose();
|
|
adapter.Dispose();
|
|
}
|
|
|
|
_bgrBuffer = new byte[_width * _height * 3];
|
|
|
|
try
|
|
{
|
|
_turboJpeg = new TJCompressor();
|
|
_turboAvailable = true;
|
|
}
|
|
catch
|
|
{
|
|
_turboAvailable = false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
catch
|
|
{
|
|
Cleanup();
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public byte[] CaptureFrameToJpeg(int quality, int timeoutMs = 8)
|
|
{
|
|
if (_duplication == null)
|
|
return null;
|
|
|
|
Resource desktopResource = null;
|
|
try
|
|
{
|
|
var result = _duplication.TryAcquireNextFrame(timeoutMs, out _, out desktopResource);
|
|
if (result.Failure || desktopResource == null)
|
|
return null;
|
|
|
|
using (var texture = desktopResource.QueryInterface<Texture2D>())
|
|
{
|
|
_device.ImmediateContext.CopyResource(texture, _staging);
|
|
}
|
|
|
|
var mapSource = _device.ImmediateContext.MapSubresource(_staging, 0, MapMode.Read, MapFlags.None);
|
|
try
|
|
{
|
|
ConvertBgraToRgb(mapSource.DataPointer, mapSource.RowPitch);
|
|
}
|
|
finally
|
|
{
|
|
_device.ImmediateContext.UnmapSubresource(_staging, 0);
|
|
}
|
|
|
|
if (_turboAvailable)
|
|
return TurboEncode(quality);
|
|
else
|
|
return GdiEncode(quality);
|
|
}
|
|
catch (SharpDXException ex) when (ex.ResultCode.Code == SharpDX.DXGI.ResultCode.AccessLost.Result.Code)
|
|
{
|
|
Cleanup();
|
|
return null;
|
|
}
|
|
catch
|
|
{
|
|
return null;
|
|
}
|
|
finally
|
|
{
|
|
try { desktopResource?.Dispose(); } catch { }
|
|
try { _duplication?.ReleaseFrame(); } catch { }
|
|
}
|
|
}
|
|
|
|
private unsafe void ConvertBgraToRgb(IntPtr srcPtr, int srcStride)
|
|
{
|
|
fixed (byte* dstBase = _bgrBuffer)
|
|
{
|
|
byte* src = (byte*)srcPtr;
|
|
byte* dst = dstBase;
|
|
int dstStride = _width * 3;
|
|
|
|
for (int y = 0; y < _height; y++)
|
|
{
|
|
byte* srcRow = src + y * srcStride;
|
|
byte* dstRow = dst + y * dstStride;
|
|
int x = 0;
|
|
|
|
for (; x < _width; x++)
|
|
{
|
|
dstRow[0] = srcRow[2]; // R (turbo wants RGB)
|
|
dstRow[1] = srcRow[1]; // G
|
|
dstRow[2] = srcRow[0]; // B
|
|
srcRow += 4;
|
|
dstRow += 3;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private byte[] TurboEncode(int quality)
|
|
{
|
|
try
|
|
{
|
|
return _turboJpeg.Compress(
|
|
_bgrBuffer,
|
|
_width * 3,
|
|
_width,
|
|
_height,
|
|
TJPixelFormats.TJPF_RGB,
|
|
TJSubsamplingOptions.TJSAMP_420,
|
|
quality,
|
|
TJFlags.FASTDCT);
|
|
}
|
|
catch
|
|
{
|
|
_turboAvailable = false;
|
|
return GdiEncode(quality);
|
|
}
|
|
}
|
|
|
|
private byte[] GdiEncode(int quality)
|
|
{
|
|
using (var bmp = new Bitmap(_width, _height, PixelFormat.Format24bppRgb))
|
|
{
|
|
var bmpData = bmp.LockBits(
|
|
new Rectangle(0, 0, _width, _height),
|
|
ImageLockMode.WriteOnly,
|
|
PixelFormat.Format24bppRgb);
|
|
|
|
int bgrStride = _width * 3;
|
|
int dstStride = bmpData.Stride;
|
|
|
|
unsafe
|
|
{
|
|
fixed (byte* srcBase = _bgrBuffer)
|
|
{
|
|
byte* dst = (byte*)bmpData.Scan0;
|
|
for (int y = 0; y < _height; y++)
|
|
{
|
|
// BGR buffer has RGB order, but Bitmap Format24bppRgb expects BGR
|
|
byte* srcRow = srcBase + y * bgrStride;
|
|
byte* dstRow = dst + y * dstStride;
|
|
for (int x = 0; x < _width; x++)
|
|
{
|
|
dstRow[0] = srcRow[2]; // B
|
|
dstRow[1] = srcRow[1]; // G
|
|
dstRow[2] = srcRow[0]; // R
|
|
srcRow += 3;
|
|
dstRow += 3;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bmp.UnlockBits(bmpData);
|
|
|
|
var codec = GetJpegCodec();
|
|
using (var ms = new MemoryStream(131072))
|
|
{
|
|
if (codec != null)
|
|
{
|
|
using (var ep = new EncoderParameters(1))
|
|
{
|
|
ep.Param[0] = new EncoderParameter(Encoder.Quality, quality);
|
|
bmp.Save(ms, codec, ep);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
bmp.Save(ms, ImageFormat.Jpeg);
|
|
}
|
|
return ms.ToArray();
|
|
}
|
|
}
|
|
}
|
|
|
|
private static ImageCodecInfo _jpegCodec;
|
|
private static readonly object _codecLock = new object();
|
|
|
|
private static ImageCodecInfo GetJpegCodec()
|
|
{
|
|
if (_jpegCodec != null) return _jpegCodec;
|
|
lock (_codecLock)
|
|
{
|
|
if (_jpegCodec != null) return _jpegCodec;
|
|
foreach (var info in ImageCodecInfo.GetImageEncoders())
|
|
if (info.FormatID == ImageFormat.Jpeg.Guid)
|
|
{ _jpegCodec = info; break; }
|
|
}
|
|
return _jpegCodec;
|
|
}
|
|
|
|
private void Cleanup()
|
|
{
|
|
try { _turboJpeg?.Dispose(); } catch { }
|
|
try { _duplication?.Dispose(); } catch { }
|
|
try { _staging?.Dispose(); } catch { }
|
|
try { _device?.Dispose(); } catch { }
|
|
_turboJpeg = null;
|
|
_duplication = null;
|
|
_staging = null;
|
|
_device = null;
|
|
_bgrBuffer = null;
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
if (!_disposed)
|
|
{
|
|
_disposed = true;
|
|
Cleanup();
|
|
}
|
|
}
|
|
}
|
|
}
|