【问题标题】:How to catch UWP MediaCapture exception when capture device is used by another app?当捕获设备被另一个应用程序使用时如何捕获 UWP MediaCapture 异常?
【发布时间】:2020-02-21 11:42:56
【问题描述】:

我正在为 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() 的任何文本,但我确实在调试输出窗口中收到以下消息(两次):

在 0x00007FFC7114A839 (KernelBase.dll) 中引发异常 DBRacing.exe:WinRT 发起错误 - 0xC00D3704:'硬件 MFT 由于缺乏硬件资源,无法开始流式传输。'。

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

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

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

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

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

【问题讨论】:

    标签: uwp c++-winrt mediacapture


    【解决方案1】:

    根据我的测试,当我在捕获异常之前第一次注册 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();
    }
    

    【讨论】:

    • 这行得通。出于某种原因,通过将 CaptureDeviceExclusiveControlStatusChanged 的​​注册移到流程的前面,它不仅将抛出的异常更改为正确的异常,而且还允许 catch 块实际捕获它。我也尝试了 mediaCapture.Failed 事件,它也可以工作,但是对于同一问题它会得到不同的错误代码。
    猜你喜欢
    • 2013-09-05
    • 2016-06-13
    • 2012-11-04
    • 2010-09-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多