当捕获设备被另一个应用程序使用时,如何捕获 UWP MediaCapture 异常?

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

我正在为 UWP 应用程序添加一个简单的网络摄像头预览。启动预览并且 运行 工作正常,所以我正在努力正确处理 InitializeAsync()StartPreviewAsync() 抛出的异常,但无法正确捕获第二个异常。

我的代码基于 Display the camera preview,它说如果应用程序未被授予访问捕获设备的权限,它将在调用 InitializeAsync() 时抛出 UnauthorizedAccessException .对于 C++/WinRT,似乎可以捕获 E_ACCESSDENIEDhresult_error 代码,如下所示。我已经通过在应用程序选项中关闭对网络摄像头的访问来对此进行了测试,并且 try/catch 块的工作方式与您预期的一样,弹出内容对话框并向用户解释问题。

该参考文献还指出,如果另一个应用程序对捕获设备具有独占控制权,StartPreviewAsync() 应该抛出一个 FileLoadException。首先,我无法弄清楚与该异常对应的 C++/WinRT 是什么。其次,我似乎根本无法捕捉到任何异常。我尝试使用与 InitializeAsync() 相同类型的 catch 块,因为那是 Catching exceptions 中描述的内容,但当它不起作用时,我求助于尝试使用下面的块捕获任何内容。文档说您可以在捕获异常时注册 CaptureDeviceExclusiveControlStatusChanged 事件,但由于我无法捕获异常,所以我不确定在何处进行该操作或者如果我的应用程序会触发该事件在另一个应用程序已经控制了捕获设备后启动。我从未在 catch 块中看到来自 OutputDebugString() 的任何文本,但我确实在调试输出 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.'.

似乎正在生成异常,但出于某种原因我似乎无法捕获它。

在下面的代码中,与我的 ViewModel() 一起使用的方法只是提供对本地设置的访问权限,我在其中存储上次使用的设备 ID,当我的应用程序对网络摄像头具有独占控制权时,一切正常。

所以,我的问题是:我如何正确识别另一个应用程序何时拥有捕获设备的独占控制权?

我有一个用于 MediaCapture 对象的私有页面 class 变量:

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

导航到页面时相机预览开始:

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

    StartPreviewAsync();
}

StartPreviewAsync() 定义如下:

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;
    }
}

根据我的测试,当我第一次注册 CaptureDeviceExclusiveControlStatusChanged 事件之前捕获异常,并且其中一个应用程序使用了相机。之后,我 运行 另一个也使用相同相机的应用程序,它可以捕获异常。您可以尝试先添加事件进行如下测试,mediaCapture.Failed事件具有相同的效果。

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();
}