uwpc++-winrtmediacapture

How to catch UWP MediaCapture exception when capture device is used by another app?


I am working on adding a simple webcam preview to a UWP app. Getting the preview up and running works fine so I am working on properly handling exceptions thrown by InitializeAsync() and StartPreviewAsync() and am not able to properly catch exceptions for the second one.

I'm basing my code off of Display the camera preview and it says that if an app is not given access to the capture device that it will throw a UnauthorizedAccessException when calling InitializeAsync(). For C++/WinRT that seems to work out to catching an hresult_error code of E_ACCESSDENIED as shown below. I've tested this by turning off access to the webcam in the app options and the try/catch block works just like you would expect with the content dialog popping up and explaining the problem to the user.

The reference also says that if another app has exclusive control of the capture device that StartPreviewAsync() should throw a FileLoadException. To start with, I can't figure out what the C++/WinRT equivalent of that exception is. Secondly, I can't seem to catch any exceptions at all. I tried using the same type of catch block I used for InitializeAsync() since that is what is described in Catching exceptions but when that didn't work I resorted to trying to catch anything with the block below. The docs say you can register for a CaptureDeviceExclusiveControlStatusChanged event when you catch the exception but since I can't catch the exception i'm not sure where an appropriate place to do that would be or if the event fires if my app is launched after another app already has control of the capture device. I never see any text from OutputDebugString() within the catch block but I do get the following message (twice) in the debug output window:

Exception thrown at 0x00007FFC7114A839 (KernelBase.dll) in DBRacing.exe: WinRT originate error - 0xC00D3704 : 'Hardware MFT failed to start streaming due to lack of hardware resources.'.

It seems like the exception is being generated, I just can't seem to catch it for some reason.

In the code below, the methods used with my ViewModel() are just to provide access to local settings where I store the last used device ID and everything works fine when my app has exclusive control of the webcam.

So, my question is: how do I properly identify when another app has exclusive control of the capture device?

I have a private page class variable for the MediaCapture object:

private:
    Windows::Media::Capture::MediaCapture m_mediaCapture;

The camera preview starts when navigating to the page:

void VideoPage::OnNavigatedTo(NavigationEventArgs /*e*/) {

    StartPreviewAsync();
}

StartPreviewAsync() is defined like this:

Windows::Foundation::IAsyncAction VideoPage::StartPreviewAsync() {

    // if we have a previously used device, then we should check if it's valid and use it
    if (!ViewModel().hasDeviceID()) {

        // device picker to choose camera
        DevicePicker picker;

        // create a filter that only looks for video capture devices
        picker.Filter().SupportedDeviceClasses().Append(DeviceClass::VideoCapture);

        // show the picker below the button that opens it and get the chosen device
        DeviceInformation device = co_await picker.PickSingleDeviceAsync({ 0,0,100,100 });

        // the user can cancel the dialog before picking something
        if (!device)
            return;

        // store the device ID
        ViewModel().deviceID(device.Id());

        // store the device name
        ViewModel().deviceName(device.Name());
    }

    // settings for the media capture object (such as which camera to use)
    MediaCaptureInitializationSettings settings;

    // add the chosen device to the settings so we initialize on that camera
    settings.VideoDeviceId(ViewModel().deviceID());

    try {

        // initialize the mediacapture object using the chosen camera
        co_await m_mediaCapture.InitializeAsync(settings);

        // dont let the screen go to sleep while the preview is active
        m_displayRequest.RequestActive();
    }

    // an exception is thrown if the user does not allow access to the camera
    catch (winrt::hresult_error const& ex) {

        winrt::hresult hr = ex.to_abi();

        if (hr == E_ACCESSDENIED) {

            ContentDialog msg;

            // set all the options for the dialog
            msg.Title(box_value(L"Access Denied"));
            msg.Content(box_value(L"This App has not been given permission to use the Camera and/or Microphone.\nPlease go to the settings in Windows for this App to allow access."));
            msg.CloseButtonText(L"OK");

            // Show the message dialog.
            msg.ShowAsync();
        }

        return;
    }

    try {

        // assign the source to the Capture Element on the XAML page
        capturePreview().Source(m_mediaCapture);

        co_await m_mediaCapture.StartPreviewAsync();
    }

    // This method should throw a FileLoadException (0x80070020) if another app has exclusive control of the capture device
    catch(...) {

        OutputDebugString(L"Exception Message\n");

        return;
    }
}

Solution

  • By my testing, when I first registered for a CaptureDeviceExclusiveControlStatusChanged event before catching the exception, and one of the app have used the camera. After that, I run another app which will also use the same camera, it can catch the exception. You can try to add the event first to test like below and the mediaCapture.Failed event has the same effect.

    try 
    {
        DisplayRequest displayRequest = DisplayRequest();
        m_mediaCapture = MediaCapture();
        // initialize the mediacapture object using the chosen camera
        co_await m_mediaCapture.InitializeAsync();
        //Register
        m_mediaCapture.CaptureDeviceExclusiveControlStatusChanged({ this, &MainPage::MediaCapture_CaptureDeviceExclusiveControlStatusChanged });
    
        displayRequest.RequestActive();
    }
    catch (winrt::hresult_error const& ex) 
    {
        winrt::hresult hr = ex.to_abi();
        if (hr == E_ACCESSDENIED) {
        }
    
        return;
    }
    
    try 
    {
        PreviewControl().Source(m_mediaCapture);
        co_await m_mediaCapture.StartPreviewAsync();
    }
    catch (winrt::hresult_error const& ex)
    {
        winrt::hresult hr = ex.to_abi(); // HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND).
        winrt::hstring message = ex.message(); // The system cannot find the file specified.
    }
    
    //event
    void MainPage::MediaCapture_CaptureDeviceExclusiveControlStatusChanged(MediaCapture const&, MediaCaptureDeviceExclusiveControlStatusChangedEventArgs const&)
    {
        throw hresult_not_implemented();
    }