using System.Runtime.InteropServices;
using DeviceAgent.Models;
using SharpDX;
using SharpDX.Direct3D11;
using SharpDX.DXGI;
using SharpDX.MediaFoundation;
using Device = SharpDX.Direct3D11.Device;
using Resource = SharpDX.DXGI.Resource;
namespace DeviceAgent.Services;
///
/// H.264 屏幕捕获服务 - 使用 DXGI Desktop Duplication + Media Foundation H.264 编码
///
public class H264ScreenCaptureService : IDisposable
{
private readonly ILogger _logger;
private Device? _device;
private OutputDuplication? _duplicatedOutput;
private Texture2D? _stagingTexture;
private SinkWriter? _sinkWriter;
private int _videoStreamIndex;
private int _frameWidth;
private int _frameHeight;
private int _fps;
private int _bitrate;
private long _frameIndex;
private bool _isInitialized;
private readonly object _lock = new();
private MemoryStream? _outputStream;
private byte[]? _lastEncodedFrame;
private StreamQualityProfile _currentProfile = StreamQualityProfile.Low;
public H264ScreenCaptureService(ILogger logger)
{
_logger = logger;
}
public bool Initialize(int targetWidth = 1280, int targetHeight = 720, int fps = 15, int bitrate = 2000000)
{
lock (_lock)
{
try
{
if (_isInitialized) return true;
// 初始化 Media Foundation
MediaManager.Startup();
// 创建 D3D11 设备
_device = new Device(SharpDX.Direct3D.DriverType.Hardware,
DeviceCreationFlags.BgraSupport | DeviceCreationFlags.VideoSupport);
// 获取 DXGI 输出
using var dxgiDevice = _device.QueryInterface();
using var adapter = dxgiDevice.Adapter;
using var output = adapter.GetOutput(0);
using var output1 = output.QueryInterface();
// 获取屏幕尺寸
var outputDesc = output.Description;
_frameWidth = Math.Min(targetWidth, outputDesc.DesktopBounds.Right - outputDesc.DesktopBounds.Left);
_frameHeight = Math.Min(targetHeight, outputDesc.DesktopBounds.Bottom - outputDesc.DesktopBounds.Top);
// 创建桌面复制
_duplicatedOutput = output1.DuplicateOutput(_device);
// 创建暂存纹理用于 CPU 读取
var textureDesc = new Texture2DDescription
{
Width = _frameWidth,
Height = _frameHeight,
MipLevels = 1,
ArraySize = 1,
Format = Format.B8G8R8A8_UNorm,
SampleDescription = new SampleDescription(1, 0),
Usage = ResourceUsage.Staging,
CpuAccessFlags = CpuAccessFlags.Read,
BindFlags = BindFlags.None
};
_stagingTexture = new Texture2D(_device, textureDesc);
// 初始化 H.264 编码器
InitializeEncoder(fps, bitrate);
_isInitialized = true;
_logger.LogInformation("H.264 屏幕捕获服务初始化成功: {Width}x{Height}, {Fps}fps, {Bitrate}bps",
_frameWidth, _frameHeight, fps, bitrate);
return true;
}
catch (Exception ex)
{
_logger.LogError(ex, "初始化 H.264 屏幕捕获服务失败");
Cleanup();
return false;
}
}
}
private void InitializeEncoder(int fps, int bitrate)
{
_outputStream = new MemoryStream();
// 创建字节流
var byteStream = new ByteStream(_outputStream);
// 创建 Sink Writer 属性
using var attributes = new MediaAttributes();
attributes.Set(SinkWriterAttributeKeys.ReadwriteEnableHardwareTransforms, 1);
// 创建 Sink Writer
_sinkWriter = MediaFactory.CreateSinkWriterFromURL(null, byteStream, attributes);
// 设置输出媒体类型 (H.264)
using var outputType = new MediaType();
outputType.Set(MediaTypeAttributeKeys.MajorType, MediaTypeGuids.Video);
outputType.Set(MediaTypeAttributeKeys.Subtype, VideoFormatGuids.H264);
outputType.Set(MediaTypeAttributeKeys.AvgBitrate, bitrate);
outputType.Set(MediaTypeAttributeKeys.InterlaceMode, (int)VideoInterlaceMode.Progressive);
outputType.Set(MediaTypeAttributeKeys.FrameSize, PackSize(_frameWidth, _frameHeight));
outputType.Set(MediaTypeAttributeKeys.FrameRate, PackSize(fps, 1));
outputType.Set(MediaTypeAttributeKeys.PixelAspectRatio, PackSize(1, 1));
_sinkWriter.AddStream(outputType, out _videoStreamIndex);
// 设置输入媒体类型 (BGRA)
using var inputType = new MediaType();
inputType.Set(MediaTypeAttributeKeys.MajorType, MediaTypeGuids.Video);
inputType.Set(MediaTypeAttributeKeys.Subtype, VideoFormatGuids.Argb32);
inputType.Set(MediaTypeAttributeKeys.InterlaceMode, (int)VideoInterlaceMode.Progressive);
inputType.Set(MediaTypeAttributeKeys.FrameSize, PackSize(_frameWidth, _frameHeight));
inputType.Set(MediaTypeAttributeKeys.FrameRate, PackSize(fps, 1));
inputType.Set(MediaTypeAttributeKeys.PixelAspectRatio, PackSize(1, 1));
_sinkWriter.SetInputMediaType(_videoStreamIndex, inputType, null);
_sinkWriter.BeginWriting();
}
private static long PackSize(int width, int height)
{
return ((long)width << 32) | (uint)height;
}
///
/// 捕获并编码一帧
///
public byte[]? CaptureFrame()
{
lock (_lock)
{
if (!_isInitialized || _duplicatedOutput == null || _device == null)
return null;
try
{
// 尝试获取下一帧
var result = _duplicatedOutput.TryAcquireNextFrame(100,
out var frameInfo, out var desktopResource);
if (result.Failure)
{
return _lastEncodedFrame; // 返回上一帧
}
try
{
using var desktopTexture = desktopResource.QueryInterface();
// 复制到暂存纹理
_device.ImmediateContext.CopyResource(desktopTexture, _stagingTexture);
// 读取像素数据
var dataBox = _device.ImmediateContext.MapSubresource(
_stagingTexture, 0, MapMode.Read, SharpDX.Direct3D11.MapFlags.None);
try
{
// 编码帧
var encodedFrame = EncodeFrame(dataBox.DataPointer, dataBox.RowPitch);
if (encodedFrame != null && encodedFrame.Length > 0)
{
_lastEncodedFrame = encodedFrame;
}
}
finally
{
_device.ImmediateContext.UnmapSubresource(_stagingTexture, 0);
}
}
finally
{
desktopResource?.Dispose();
_duplicatedOutput.ReleaseFrame();
}
return _lastEncodedFrame;
}
catch (SharpDXException ex) when (ex.ResultCode == SharpDX.DXGI.ResultCode.AccessLost)
{
_logger.LogWarning("桌面访问丢失,需要重新初始化");
_isInitialized = false;
return null;
}
catch (Exception ex)
{
_logger.LogError(ex, "捕获帧失败");
return _lastEncodedFrame;
}
}
}
private unsafe byte[]? EncodeFrame(IntPtr dataPointer, int rowPitch)
{
if (_sinkWriter == null || _outputStream == null)
return null;
try
{
var frameSize = _frameWidth * _frameHeight * 4;
// 创建媒体缓冲区
var buffer = MediaFactory.CreateMemoryBuffer(frameSize);
try
{
// 锁定缓冲区并复制数据
var bufferPtr = buffer.Lock(out var maxLength, out var currentLength);
try
{
// 复制像素数据
for (int y = 0; y < _frameHeight; y++)
{
var srcRow = IntPtr.Add(dataPointer, y * rowPitch);
var dstRow = IntPtr.Add(bufferPtr, y * _frameWidth * 4);
System.Buffer.MemoryCopy(srcRow.ToPointer(), dstRow.ToPointer(),
_frameWidth * 4, _frameWidth * 4);
}
}
finally
{
buffer.Unlock();
}
buffer.CurrentLength = frameSize;
// 创建样本
using var sample = MediaFactory.CreateSample();
sample.AddBuffer(buffer);
// 设置时间戳
var duration = 10_000_000L / 15; // 假设 15fps
sample.SampleTime = _frameIndex * duration;
sample.SampleDuration = duration;
// 重置输出流
_outputStream.SetLength(0);
_outputStream.Position = 0;
// 写入样本
_sinkWriter.WriteSample(_videoStreamIndex, sample);
_frameIndex++;
// 返回编码后的数据
if (_outputStream.Length > 0)
{
return _outputStream.ToArray();
}
}
finally
{
buffer?.Dispose();
}
}
catch (Exception ex)
{
_logger.LogError(ex, "编码帧失败");
}
return null;
}
private void Cleanup()
{
_isInitialized = false;
try { _sinkWriter?.Dispose(); } catch { }
try { _stagingTexture?.Dispose(); } catch { }
try { _duplicatedOutput?.Dispose(); } catch { }
try { _device?.Dispose(); } catch { }
try { _outputStream?.Dispose(); } catch { }
_sinkWriter = null;
_stagingTexture = null;
_duplicatedOutput = null;
_device = null;
_outputStream = null;
}
public void Dispose()
{
lock (_lock)
{
Cleanup();
MediaManager.Shutdown();
}
}
}