媒体基础中的音频/视频捕获

Microsoft Media Foundation 支持音频和视频捕获。 视频捕获设备通过 UVC 类驱动程序支持,并且必须与 UVC 1.1 兼容。 音频捕获设备通过 Windows 音频会话 API (WASAPI) 支持。

在 Media Foundation 中,捕获设备由可公开 IMFMediaSource 接口的媒体源对象表示。 在大多数情况下,应用程序不会直接使用此接口,而是使用更高级别的 API(如源读取器)来控制捕获设备。

枚举捕获设备

要枚举系统上的捕获设备,请执行以下步骤:

  1. 调用 MFCreateAttributes 函数以创建属性存储。

  2. MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE 属性设置为下列值之一:

    说明
    MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_AUDCAP_GUID 枚举音频捕获设备。
    MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID 枚举视频捕获设备。

     

  3. 调用 MFEnumDeviceSources 函数。 此函数分配一个 IMFActivate 指针数组。 每个指针代表系统中一个设备的激活对象。

  4. 调用 IMFActivate::ActivateObject 方法,从一个激活对象创建一个媒体源实例。

以下示例为枚举列表中的第一个视频捕获设备创建了一个媒体源:

HRESULT CreateVideoCaptureDevice(IMFMediaSource **ppSource)
{
    *ppSource = NULL;

    UINT32 count = 0;

    IMFAttributes *pConfig = NULL;
    IMFActivate **ppDevices = NULL;

    // Create an attribute store to hold the search criteria.
    HRESULT hr = MFCreateAttributes(&pConfig, 1);

    // Request video capture devices.
    if (SUCCEEDED(hr))
    {
        hr = pConfig->SetGUID(
            MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE, 
            MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID
            );
    }

    // Enumerate the devices,
    if (SUCCEEDED(hr))
    {
        hr = MFEnumDeviceSources(pConfig, &ppDevices, &count);
    }

    // Create a media source for the first device in the list.
    if (SUCCEEDED(hr))
    {
        if (count > 0)
        {
            hr = ppDevices[0]->ActivateObject(IID_PPV_ARGS(ppSource));
        }
        else
        {
            hr = MF_E_NOT_FOUND;
        }
    }

    for (DWORD i = 0; i < count; i++)
    {
        ppDevices[i]->Release();
    }
    CoTaskMemFree(ppDevices);
    return hr;
}

可以查询激活对象的各种属性,具体如下:

以下示例使用 IMFActivate 指针数组,并会将每个设备的显示名称打印到调试窗口:

void DebugShowDeviceNames(IMFActivate **ppDevices, UINT count)
{
    for (DWORD i = 0; i < count; i++)
    {
        HRESULT hr = S_OK;
        WCHAR *szFriendlyName = NULL;
    
        // Try to get the display name.
        UINT32 cchName;
        hr = ppDevices[i]->GetAllocatedString(
            MF_DEVSOURCE_ATTRIBUTE_FRIENDLY_NAME,
            &szFriendlyName, &cchName);

        if (SUCCEEDED(hr))
        {
            OutputDebugString(szFriendlyName);
            OutputDebugString(L"\n");
        }
        CoTaskMemFree(szFriendlyName);
    }
}

如果已经知道视频设备的符号链接,还有另一种方法可以为设备创建媒体源:

  1. 调用 MFCreateAttributes 以创建属性存储。
  2. MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE 属性设置为 MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID
  3. MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK 属性设置为符号链接。
  4. 调用 MFCreateDeviceSourceMFCreateDeviceSourceActivate 函数。 前者会返回一个 IMFMediaSource 指针。 后者会返回一个指向激活对象的 IMFActivate 指针。 可以使用激活对象来创建源。 (激活对象可以被封送到到另一个进程,因此如果要在另一个进程中创建源,它就很有用。有关详细信息,请参阅激活对象。)

以下示例使用视频设备的符号链接创建了一个媒体源。

HRESULT CreateVideoCaptureDevice(PCWSTR *pszSymbolicLink, IMFMediaSource **ppSource)
{
    *ppSource = NULL;
    
    IMFAttributes *pAttributes = NULL;
    IMFMediaSource *pSource = NULL;

    HRESULT hr = MFCreateAttributes(&pAttributes, 2);

    // Set the device type to video.
    if (SUCCEEDED(hr))
    {
        hr = pAttributes->SetGUID(
            MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE,
            MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID
            );
    }


    // Set the symbolic link.
    if (SUCCEEDED(hr))
    {
        hr = pAttributes->SetString(
            MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK,
            (LPCWSTR)pszSymbolicLink
            );            
    }

    if (SUCCEEDED(hr))
    {
        hr = MFCreateDeviceSource(pAttributes, ppSource);
    }

    SafeRelease(&pAttributes);
    return hr;    
}

有一种等效的方法可以通过音频终结点 ID 创建音频设备:

  1. 调用 MFCreateAttributes 以创建属性存储。
  2. MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE 属性设置为 MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_AUDCAP_GUID
  3. MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_AUDCAP_ENDPOINT_ID 属性设置为终结点 ID。
  4. 调用 MFCreateDeviceSourceMFCreateDeviceSourceActivate 函数。

以下示例使用音频终结点 ID 并创建了一个媒体源。

HRESULT CreateAudioCaptureDevice(PCWSTR *pszEndPointID, IMFMediaSource **ppSource)
{
    *ppSource = NULL;
    
    IMFAttributes *pAttributes = NULL;
    IMFMediaSource *pSource = NULL;

    HRESULT hr = MFCreateAttributes(&pAttributes, 2);

    // Set the device type to audio.
    if (SUCCEEDED(hr))
    {
        hr = pAttributes->SetGUID(
            MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE,
            MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_AUDCAP_GUID
            );
    }

    // Set the endpoint ID.
    if (SUCCEEDED(hr))
    {
        hr = pAttributes->SetString(
            MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_AUDCAP_ENDPOINT_ID,
            (LPCWSTR)pszEndPointID
            ); 
    }

    if (SUCCEEDED(hr))
    {
        hr = MFCreateDeviceSource(pAttributes, ppSource);
    }

    SafeRelease(&pAttributes);
    return hr;    
}

使用捕获设备

在为捕获设备创建媒体源之后,使用源读取器从设备获取数据。 源读取器提供包含捕获音频数据或视频帧的媒体示例。 下一步取决于应用场景:

  • 视频预览:使用 Microsoft Direct3D 或 Direct2D 来显示视频。
  • 文件捕获:使用接收器编写器来对文件进行编码。
  • 音频预览:使用 WASAPI

如果要将音频捕获与视频捕获结合起来,请使用聚合媒体源。 聚合媒体源包含媒体源集合,并会将其所有的流合并到一个媒体源对象中。 要创建聚合媒体源实例,请调用 MFCreateAggregateSource 函数。

关闭捕获设备

当不再需要捕获设备时,必须在通过调用 MFCreateDeviceSourceIMFActivate::ActivateObject 获得的 IMFMediaSource 对象上调用 Shutdown 来关闭设备。 如果不调用 Shutdown,可能会导致内存泄漏,因为系统可能会保留对 IMFMediaSource,直到调用 Shutdown

if (g_pSource)
{
    g_pSource->Shutdown();
    g_pSource->Release();
    g_pSource = NULL;
}

如果分配了包含捕获设备符号链接的字符串,则也应释放此对象。

    CoTaskMemFree(g_pwszSymbolicLink);
    g_pwszSymbolicLink = NULL;

    g_cchSymbolicLink = 0;

音频/视频捕获