microsoft / Windows.UI.Composition-Win32-Samples

Windows.UI.Composition Win32 Samples
MIT License
459 stars 186 forks source link

ScreenCapture Sample - write capture stream to disk mp4? #118

Closed unboundin closed 1 year ago

unboundin commented 1 year ago

Is it possible to write the captured video stream to a local mp4 file?

I'm still new to WindowsGraphcsCapture and not sure how I could save these recordings...

Thank you

robmikh commented 1 year ago

Yes it is, but it will require using another library to do the encoding. I would recommnd using the Windows.Media.Transcoding APIs. The idea would be to create a MediaTranscoder that's configured to take in raw BGRA8 input and output H264 samples. To feed the transcoder, you'll need to create a MediaStreamSource and respond to the SampleRequested event by providing frames in the form of MediaStreamSample objects which you can create using MediaStreamSample::CreateFromDirect3D11Surface.

I have a project here that demonstrates all that: https://github.com/robmikh/CaptureVideoSample. In particular, I would look at VideoRecordingSession.cpp.

It's worth noting that the media-foundation branch uses the Media Foundation APIs instead of Windows.Media.Transcoding. If you're saving the recordings to disk, I would use Windows.Media.Trancoding. The Media Foundation version exists to demonstrate that you can get the raw H264 samples. CaptureVideoSample just writes them to disk, but you could send them across the wire and decode them somewhere else. Unfortunately I haven't had luck getting the Media Foundation version to work on my ARM64 devices. I haven't had that issue with the Windows.Media.Transcoding version.

I'm going to close the issue, but feel free to ask questions!

robmikh commented 1 year ago

In case others stumble across this issue, I'll list some other examples of video encoding:

gary-89 commented 8 months ago

Hi @robmikh, thanks for the contributions and nice repositories you shared. I would like to better understand the different approaches I can use to record a screen using C# nowadays. I have been working with WPF for many years, but, as Microsoft suggests, I am moving towards WinUI apps development. All the source code I can find is either for Win32 or UWP (WinRT-based). Do you have any recommendations? The goal is to record the monitor and save the output to a .avi or .mp4 file. Thanks!

gary-89 commented 8 months ago

I don't understand how I can plug in your suggestion and use MediaTranscoder (PrepareFileTranscodeAsync()), MediaStreamSource etc...

Could you clarify how can I start encoding and write to an "output.mp4" file using the Texture2D bitmap frame generated by Direct3D11CaptureFramePool?

Here code I am referring to (in the WPF project):

public BasicCapture(IDirect3DDevice d, GraphicsCaptureItem i)
{
    item = i;
    device = d;
    d3dDevice = Direct3D11Helper.CreateSharpDXDevice(device);
    var dxgiFactory = new Factory2();
    var description = new SwapChainDescription1()
    {
        Width = item.Size.Width,
        Height = item.Size.Height,
        Format = Format.B8G8R8A8_UNorm,
        Stereo = false,
        SampleDescription = new SampleDescription()
        {
            Count = 1,
            Quality = 0
        },
        Usage = Usage.RenderTargetOutput,
        BufferCount = 2,
        Scaling = Scaling.Stretch,
        SwapEffect = SwapEffect.FlipSequential,
        AlphaMode = AlphaMode.Premultiplied,
        Flags = SwapChainFlags.None
    };
    swapChain = new SwapChain1(dxgiFactory, d3dDevice, ref description);

    framePool = Direct3D11CaptureFramePool.Create(
        device,
        DirectXPixelFormat.B8G8R8A8UIntNormalized,
        2,
        i.Size);
    session = framePool.CreateCaptureSession(i);
    lastSize = i.Size;

    framePool.FrameArrived += OnFrameArrived;
}

private void OnFrameArrived(Direct3D11CaptureFramePool sender, object args)
{
  var newSize = false;

  using (var frame = sender.TryGetNextFrame())
  {
      if (frame.ContentSize.Width != lastSize.Width ||
          frame.ContentSize.Height != lastSize.Height)
      {
          // The thing we have been capturing has changed size.
          // We need to resize the swap chain first, then blit the pixels.
          // After we do that, retire the frame and then recreate the frame pool.
          newSize = true;
          lastSize = frame.ContentSize;
          swapChain.ResizeBuffers(
              2,
              lastSize.Width,
              lastSize.Height,
              Format.B8G8R8A8_UNorm,
              SwapChainFlags.None);
      }

      using (var backBuffer = swapChain.GetBackBuffer<Texture2D>(0))
      using (var bitmap = Direct3D11Helper.CreateSharpDXTexture2D(frame.Surface))
      {
          d3dDevice.ImmediateContext.CopyResource(bitmap, backBuffer);
          _videoEncoder.AddFrame(bitmap);
      }

  } // Retire the frame.

  swapChain.Present(0, PresentFlags.None);

  if (newSize)
  {
      framePool.Recreate(
          device,
          DirectXPixelFormat.B8G8R8A8UIntNormalized,
          2,
          lastSize);
  }
}
robmikh commented 8 months ago

I would take a look at the SimpleRecorder repo. The approach should be the same between WPF, UWP, and WinAppSDK. The only difference would be how the preview is hooked up into the UI.