使用 Windows 图形捕获 API 暂停/恢复屏幕录制

Riz*_*Riz 5 c# screen-capture direct3d11 screen-recording windows-graphics-capture

我正在使用Windows Graphics Capture API用 C# 构建屏幕录制应用程序。我正在使用这个脚本。我可以选择监视器并将其录制到 mp4 文件。我正在尝试添加暂停/恢复功能。

这是启动录制的主窗口的代码

try
{
    newFile = GetTempFile();
    using (var stream = new FileStream(newFile, FileMode.CreateNew).AsRandomAccessStream())
    using (_encoder = new Encoder(_device, item))
    {
        await _encoder.EncodeAsync(
            stream,
            width, height, bitrate,
            frameRate);
    }
}
catch (Exception ex)
{
  //
}
Run Code Online (Sandbox Code Playgroud)

这是上面使用的Encoder 类的主要函数

private async Task EncodeInternalAsync(IRandomAccessStream stream, uint width, uint height, uint bitrateInBps, uint frameRate)
{
    if (!_isRecording)
    {
        _isRecording = true;

        _frameGenerator = new CaptureFrameWait(
            _device,
            _captureItem,
            _captureItem.Size);

        using (_frameGenerator)
        {
            var encodingProfile = new MediaEncodingProfile();
            encodingProfile.Container.Subtype = "MPEG4";
            encodingProfile.Video.Subtype = "H264";
            encodingProfile.Video.Width = width;
            encodingProfile.Video.Height = height;
            encodingProfile.Video.Bitrate = bitrateInBps;
            encodingProfile.Video.FrameRate.Numerator = frameRate;
            encodingProfile.Video.FrameRate.Denominator = 1;
            encodingProfile.Video.PixelAspectRatio.Numerator = 1;
            encodingProfile.Video.PixelAspectRatio.Denominator = 1;
            var transcode = await _transcoder.PrepareMediaStreamSourceTranscodeAsync(_mediaStreamSource, stream, encodingProfile);

            await transcode.TranscodeAsync();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

最后这是CaptureFrameWait类中的初始化函数

private void InitializeCapture(SizeInt32 size)
{
    _framePool = Direct3D11CaptureFramePool.CreateFreeThreaded(
        _device,
        DirectXPixelFormat.B8G8R8A8UIntNormalized,
        1,
        size);
    _framePool.FrameArrived += OnFrameArrived;
    _session = _framePool.CreateCaptureSession(_item);
    _session.IsBorderRequired = false;
    _session.StartCapture();
}
Run Code Online (Sandbox Code Playgroud)

我们如何修改它来暂停录制?我尝试在暂停时处理_framepool_session对象,并在CaptureFrameWait类中的恢复时再次初始化它们,如下所示。它工作正常,但有时 TranscodeAsync 函数会在暂停期间终止并结束录制。我们怎样才能避免这种情况呢?

bool _paused = false;
public void PauseSession(bool status)
{
    if (status) {
        _paused = true;
        _framePool?.Dispose();
        _session?.Dispose();
    }
    else {
        InitializeCapture(_size);
        _paused = false;
    }
}
Run Code Online (Sandbox Code Playgroud)

Sim*_*ier 2

一种解决方案是延期。医生说:

然后,MediaStreamSource 将等待您提供 MediaStreamSample,直到您将延迟标记为完成。

例如,向类添加两个私有成员Encoder和两个方法:

private MediaStreamSourceSampleRequestedEventArgs _args;
private MediaStreamSourceSampleRequestDeferral _def;

public bool IsPaused { get; private set; }

public void Pause()
{
    IsPaused = true;
}

public void Resume()
{
    IsPaused = false;

    // complete the request we saved earlier
    OnMediaStreamSourceSampleRequested(_mediaStreamSource, _args);
}
Run Code Online (Sandbox Code Playgroud)

并修改OnMediaStreamSourceSampleRequested这样的方法(我在其中添加了注释):

private void OnMediaStreamSourceSampleRequested(MediaStreamSource sender, MediaStreamSourceSampleRequestedEventArgs args)
{
    if (_isRecording && !_closed)
    {
        // if paused get a deferral and save the current arguments.
        // OnMediaStreamSourceSampleRequested will not be called again until we complete the deferral
        if (IsPaused)
        {
            _def = args.Request.GetDeferral();
            _args = args;
            return;
        }

        try
        {
            using (var frame = _frameGenerator.WaitForNewFrame())
            {
                if (frame == null)
                {
                    args.Request.Sample = null;
                    DisposeInternal();
                    return;
                }

                var timeStamp = frame.SystemRelativeTime;

                var sample = MediaStreamSample.CreateFromDirect3D11Surface(frame.Surface, timeStamp);
                args.Request.Sample = sample;

                // when called again (manually by us) complete the work
                // and reset members
                if (_def != null)
                {
                    _def.Complete();
                    _def = null;
                    _args = null;
                }
            }
        }
        catch (Exception e)
        {
          ...
        }
    }
    else
    {
      ...
    }
}
Run Code Online (Sandbox Code Playgroud)

另一个解决方案是简单地冻结帧时间戳,因此添加这些成员:

private TimeSpan _pausedTimestamp;
public bool IsPaused { get; private set; }

public void Pause()
{
    IsPaused = true;
}

public void Resume()
{
    IsPaused = false;
}
Run Code Online (Sandbox Code Playgroud)

并修改OnMediaStreamSourceSampleRequested这样的方法(我在其中添加了注释):

private void OnMediaStreamSourceSampleRequested(MediaStreamSource sender, MediaStreamSourceSampleRequestedEventArgs args)
{
    if (_isRecording && !_closed)
    {
        try
        {
            using (var frame = _frameGenerator.WaitForNewFrame())
            {
                if (frame == null)
                {
                    args.Request.Sample = null;
                    DisposeInternal();
                    return;
                }

                // if paused, "freeze" the timestamp
                TimeSpan timeStamp;
                if (IsPaused)
                {
                    timeStamp = _pausedTimestamp;
                }
                else
                {
                    timeStamp = frame.SystemRelativeTime;
                    _pausedTimestamp = timeStamp;
                }

                var sample = MediaStreamSample.CreateFromDirect3D11Surface(frame.Surface, timeStamp);
                args.Request.Sample = sample;
            }
        }
        catch (Exception e)
        {
          ...
        }
    }
    else
    {
      ...
    }
}
Run Code Online (Sandbox Code Playgroud)