robmikh / SimpleRecorder

A simple screen recorder using both the Windows.Graphics.Capture and Windows.Media.Transcoding APIs.
MIT License
219 stars 43 forks source link

Help on Screen Recording #48

Closed SilentEngineer1999 closed 1 year ago

SilentEngineer1999 commented 1 year ago

i am new to windows apps i am working on a screen recorder and i need some help. im using winui3.

  1. Whats the difference between mainwindow and mainpage should i use window or Page in my mainwinodw.xaml file.
  2. So i figured out how to get frames from this project `public sealed partial class MainWindow : Window {

    private SizeInt32 _lastSize;
    private GraphicsCaptureItem _item;
    private Direct3D11CaptureFramePool _framePool;
    private GraphicsCaptureSession _session;
    
    // Non-API related members.
    private CanvasDevice _canvasDevice;
    private CompositionGraphicsDevice _compositionGraphicsDevice;
    private Compositor _compositor;
    private CompositionDrawingSurface _surface;
    private CanvasBitmap _currentFrame;
    private string _screenshotFilename = "test.png";
    private AppWindow m_AppWindow;
    
    public MainWindow()
    {
    
        this.InitializeComponent();
    
        m_AppWindow = GetAppWindowForCurrentWindow();
        m_AppWindow.Title = "Test";
        m_AppWindow.SetIcon("C:\\Users\\name\\OneDrive\\Pictures\\zup.0849b3176cec0e8f7fe3.ico");
    
        var titleBar = m_AppWindow.TitleBar;
        titleBar.BackgroundColor = Colors.Purple;
        titleBar.ForegroundColor = Colors.Purple;
        titleBar.InactiveBackgroundColor = Colors.Purple;
        titleBar.InactiveForegroundColor = Colors.Purple;
        titleBar.ButtonForegroundColor = Colors.Orange;
        titleBar.ButtonBackgroundColor = Colors.Purple;
        titleBar.ButtonInactiveForegroundColor = Colors.Orange;
        titleBar.ButtonInactiveBackgroundColor = Colors.Purple;
    }
    
    private AppWindow GetAppWindowForCurrentWindow()
    {
        IntPtr hWnd = WindowNative.GetWindowHandle(this);
        WindowId wndId = Win32Interop.GetWindowIdFromWindow(hWnd);
        return AppWindow.GetFromWindowId(wndId);
    }
    
    private async void myButton_Click(object sender, RoutedEventArgs e)
    {
        await StartCaptureAsync();
    
    }
    
    public async Task StartCaptureAsync()
    {
        // The GraphicsCapturePicker follows the same pattern the
        // file pickers do.
        var hWnd = await Task.Run(() => WinRT.Interop.WindowNative.GetWindowHandle(this));
        var picker = new GraphicsCapturePicker();
        InitializeWithWindow.Initialize(picker, hWnd);
        GraphicsCaptureItem item = await picker.PickSingleItemAsync();
    
        // The item may be null if the user dismissed the
        // control without making a selection or hit Cancel.
        if (item != null)
        {
            Debug.WriteLine(item.DisplayName);
            StartCaptureInternal(item);
        }
    
    }
    
    private void Stop(object sender, RoutedEventArgs e)
    {
        StopCapture();
    }
    
        public void StartCaptureInternal(GraphicsCaptureItem item)
    {
        _canvasDevice = new CanvasDevice();
        _item = item;
        _lastSize = _item.Size;
        int i = 1;
        _framePool = Direct3D11CaptureFramePool.Create(
            _canvasDevice, // D3D device
            Windows.Graphics.DirectX.DirectXPixelFormat.B8G8R8A8UIntNormalized, // Pixel format
            60, // Number of frames
            _item.Size); // Size of the buffers
        Debug.WriteLine(i);
    
        _framePool.FrameArrived += (s, a) =>
        {
            // The FrameArrived event is raised for every frame on the thread
            // that created the Direct3D11CaptureFramePool. This means we
            // don't have to do a null-check here, as we know we're the only
            // one dequeueing frames in our application.  
    
            // NOTE: Disposing the frame retires it and returns  
            // the buffer to the pool.
    
            using (var frame = _framePool.TryGetNextFrame())
            {
                ProcessFrame(frame);
                i++;
                Debug.WriteLine(i);
                Debug.WriteLine(frame.GetType().Name);
    
            }
        };
    
        _item.Closed += (s, a) =>
        {
            StopCapture();
        };
    
        _session = _framePool.CreateCaptureSession(_item);
        _session.StartCapture();
    }
    
    public void StopCapture()
    {
        _session?.Dispose();
        _framePool?.Dispose();
        _item = null;
        _session = null;
        _framePool = null;
    }
    
    private void ProcessFrame(Direct3D11CaptureFrame frame)
    {
        // Resize and device-lost leverage the same function on the
        // Direct3D11CaptureFramePool. Refactoring it this way avoids
        // throwing in the catch block below (device creation could always
        // fail) along with ensuring that resize completes successfully and
        // isn’t vulnerable to device-lost.
        bool needsReset = false;
        bool recreateDevice = false;
    
        if ((frame.ContentSize.Width != _lastSize.Width) ||
            (frame.ContentSize.Height != _lastSize.Height))
        {
    
            needsReset = true;
            _lastSize = frame.ContentSize;
        }
    
        try
        {
            // Take the D3D11 surface and draw it into a  
            // Composition surface.
    
            // Convert our D3D11 surface into a Win2D object.
            CanvasBitmap canvasBitmap = CanvasBitmap.CreateFromDirect3D11Surface(
                _canvasDevice,
                frame.Surface);
    
            _currentFrame = canvasBitmap;
    
            // Helper that handles the drawing for us.
            //FillSurfaceWithBitmap(canvasBitmap);
        }
    
        // This is the device-lost convention for Win2D.
        catch (Exception e) when (_canvasDevice.IsDeviceLost(e.HResult))
        {
            // We lost our graphics device. Recreate it and reset
            // our Direct3D11CaptureFramePool.  
            needsReset = true;
            recreateDevice = true;
        }
    
        if (needsReset)
        {
            ResetFramePool(frame.ContentSize, recreateDevice);
        }
    }
    
    private void ResetFramePool(SizeInt32 size, bool recreateDevice)
    {
        do
        {
            try
            {
                if (recreateDevice)
                {
                    _canvasDevice = new CanvasDevice();
                }
    
                _framePool.Recreate(
                    _canvasDevice,
                    Windows.Graphics.DirectX.DirectXPixelFormat.B8G8R8A8UIntNormalized,
                    2,
                    size);
            }
            // This is the device-lost convention for Win2D.
            catch (Exception e) when (_canvasDevice.IsDeviceLost(e.HResult))
            {
                _canvasDevice = null;
                recreateDevice = true;
            }
        } while (_canvasDevice == null);
    }`

    Up till here everything is working how do i display the frames on my ui page? This is my UI `

        <TextBlock Margin="300" x:Name="StopTextBlock" Text="Click here to Stop" Style="{StaticResource SubtitleTextBlockStyle}" HorizontalAlignment="Stretch" HorizontalTextAlignment="Right" VerticalAlignment="Top" />
    
        <Button x:Name="CaptureButton" Grid.Row="2" Margin="300" HorizontalAlignment="Left" VerticalAlignment="Center" Click="myButton_Click" Background="Black">
                <!--<FontIcon Glyph="&#x25B6;" FontFamily="Segoe UI Emoji" FontSize="24"  />-->
                Capture
        </Button>
    
        <Button x:Name="StopButton" Grid.Row="2" Margin="300" HorizontalAlignment="Right" VerticalAlignment="Center" Click="Stop" Background="Black">
            <!--<FontIcon Glyph="&#x1F6D1;" FontFamily="Segoe UI Emoji" FontSize="24"  />-->
            STOP
        </Button>
        <CheckBox Margin="300 100 0 0" >Check this to display mouse</CheckBox>

    ` Please help me im barely understanding what im doing.

robmikh commented 1 year ago

You may want to familiarize yourself with WinUI3 before proceeding if you're new to Windows application development. In general I would ask questions on Stack Overflow or reach out on the WinAppSDK repo.

I've put together a little sample that shows you how to use Win2D's CanvasSwapChainPanel to display the capture into a UI built with WinUI3. You can find that here: https://github.com/robmikh/WinUI3Win2DSwapChain

robmikh commented 1 year ago

Sorry, forgot to answer your question about using pages vs the window. If your application only has a single view, you can put your UI into the window directly. But for a more complex app, you may choose to place just a Frame control in your window and navigate to different pages. This is similar to the default configuration of a UWP XAML app.

SilentEngineer1999 commented 1 year ago

Thanks this helps.

SilentEngineer1999 commented 1 year ago

One last question is there any place where i can learn this any online course or books or anything that covers XAML, Winui3, DirectX, Windows APIs?

robmikh commented 1 year ago

Unfortunately, I don't know of a great resource that covers the intersection of all of those topics. My recommendation would be to look at individual sources. Here are some to get you started:

For the Windows.Graphics.Capture API questions, feel free to ask questions here: Windows.UI.Composition-Win32-Samples. You should also feel free to ask questions in one of my capture-related repos.