在 DirectShow 中读取DRM-Protected ASF 文件

[与此页面关联的功能 DirectShow 是旧版功能。 它已被 MediaPlayerIMFMediaEngineMedia Foundation 中的音频/视频捕获所取代。 这些功能已针对Windows 10和Windows 11进行了优化。 Microsoft 强烈建议新代码尽可能在 Media Foundation 中使用 MediaPlayerIMFMediaEngine音频/视频捕获 ,而不是 DirectShow。 如果可能,Microsoft 建议重写使用旧 API 的现有代码以使用新 API。]

本主题介绍如何使用 DirectShow 播放受 Windows Media Digital Rights Management (DRM) 保护的媒体文件。

DRM 概念

使用 Windows Media DRM 保护媒体文件涉及两个不同的步骤:

  • 内容提供程序打包文件,即加密文件并将许可信息附加到 ASF 文件头。 许可信息包括客户端可在其中获取许可证的 URL。
  • 客户端应用程序获取内容的许可证。

在分发文件之前进行打包。 当用户尝试播放或复制文件时,将获取许可证。 许可证获取可以是 无提示 的,也可以 是非无提示的。 无提示获取不需要与用户进行任何交互。 非无提示获取要求应用程序打开浏览器窗口并向用户显示网页。 此时,用户可能需要向内容提供商提供某种信息。 但是,这两种类型的许可证获取都需要客户端将 HTTP 请求提交到许可证服务器。

DRM 版本

存在多个版本的 Windows Media DRM。 从客户端应用程序的角度来看,它们可以分为两类:DRM 版本 1 和 DRM 版本 7 或更高版本。 (第二类包括 DRM 版本 9 和 10 以及版本 7.) 以这种方式对 DRM 版本进行分类的原因是版本 1 许可证的处理方式与版本 7 或更高版本许可证略有不同。 在本文档中,术语 版本 7 许可证 是指版本 7 或更高版本。

将 DRM 打包与 DRM 许可证区分开来也很重要。 如果使用 Windows Media Rights Manager 版本 7 或更高版本打包文件,则 DRM 标头除了版本 7 许可证 URL 外,还可以包含版本 1 许可证 URL。 版本 1 许可证 URL 使不支持版本 7 的较旧玩家可以获得内容许可证。 但是,反之亦然,因此具有版本 1 打包的文件不能具有版本 7 许可证 URL。

应用程序安全级别

若要播放受 DRM 保护的文件,客户端应用程序必须链接到 Microsoft 以二进制形式提供的静态库。 此库唯一标识应用程序,有时称为存根库。 存根库具有分配的安全级别,其值由获取存根库时签署的许可协议确定。

内容提供程序设置获取许可证所需的最低安全级别。 如果存根库的安全级别低于许可证服务器所需的最低安全级别,则应用程序无法获取许可证。

个性化

为了提高安全性,应用程序可以更新客户端计算机上的 DRM 组件。 此更新称为个性化,可将用户的应用程序副本与同一应用程序的其他所有副本区分开来。 受保护文件的 DRM 标头可以指定可以指定最小个性化级别。 (有关详细信息,请参阅 Windows Media Rights Manager SDK 中的 WMRMHeader.IndividualizedVersion 文档)

Microsoft 个性化服务处理来自用户的信息。 因此,在应用程序个性化之前,必须显示 Microsoft 隐私声明,或提供指向它的链接, (请参阅 Microsoft 隐私声明) 。

提供软件证书

若要使应用程序能够使用 DRM 许可证,应用程序必须向 Filter Graph 管理器提供软件证书或 密钥 。 此密钥包含在针对应用程序进行个性化设置的静态库中。 有关获取个性化库的信息,请参阅 Windows 媒体格式 SDK 文档中 的获取所需的 DRM 库

若要提供软件密钥,请执行以下步骤:

  1. 指向静态库的链接。
  2. 实现 IServiceProvider 接口。
  3. 查询 IObjectWithSite 接口的 Filter Graph 管理器。
  4. 使用指向 IServiceProvider 实现的指针调用 IObjectWithSite::SetSite
  5. Filter Graph 管理器将调用 IServiceProvider::QueryService,为服务标识符指定 IID_IWMReader
  6. QueryService 的实现中,调用 WMCreateCertificate 来创建软件密钥。

以下代码演示如何实现 QueryService 方法:

STDMETHODIMP Player::QueryService(REFIID siid, REFIID riid, void **ppv)
{
    if (ppv == NULL ) 
    { 
        return E_POINTER; 
    }

    if (siid == __uuidof(IWMReader) && riid == __uuidof(IUnknown))
    {
        IUnknown *punkCert;

        HRESULT hr = WMCreateCertificate(&punkCert);

        if (SUCCEEDED(hr))
        {
            *ppv = (void *) punkCert;
        }

        return hr;
    }
    return E_NOINTERFACE;
}

以下代码演示如何在 Filter Graph Manager 上调用 SetSite

HRESULT Player::CreateFilterGraph()
{
    CComPtr<IObjectWithSite> pSite;

    HRESULT hr = pGraph.CoCreateInstance(CLSID_FilterGraph);

    if (FAILED(hr))
    {
        goto done;
    }

    // Register the application as a site (service).
    hr = pGraph->QueryInterface(&pSite);
    if (FAILED(hr))
    {
        goto done;
    }

    hr = pSite->SetSite(this);


done:
    return hr;
}

生成播放图

若要播放受 DRM 保护的 ASF 文件,请执行以下步骤:

  1. 创建 Filter Graph 管理器 并使用 IMediaEventEx 接口注册图形事件。
  2. 调用 CoCreateInstance 以创建 WM ASF 读取器 筛选器的新实例。
  3. 调用 IFilterGraph::AddFilter 将筛选器添加到筛选器图。
  4. 查询 IFileSourceFilter 接口的筛选器。
  5. 使用文件的 URL 调用 IFileSourceFilter::Load
  6. 处理 EC_WMT_EVENT 事件。
  7. 在第一 个EC_WMT_EVENT 事件中,查询 WM ASF 读取器 筛选器以获取 IServiceProvider 接口。
  8. 调用 IServiceProvider::QueryService 以获取指向 IWMDRMReader 接口的 指针。
  9. 调用 IGraphBuilder::Render 以呈现 WM ASF 读取器 筛选器的输出引脚。

备注

打开受 DRM 保护的文件时,请勿调用 IGraphBuilder::RenderFile 来创建筛选器图。 在获取 DRM 许可证之前,WM ASF 读取器筛选器无法连接到任何其他筛选器。 此步骤要求应用程序使用 IWMDRMReader 接口,该接口必须从筛选器获取,如步骤 7-8 中所述。 因此,必须创建筛选器并将其添加到图形

 

备注

在将 WM ASF 读取器 筛选器添加到步骤 3) (图之前,请务必 (步骤 1) 注册图形事件,因为应用程序必须处理 EC_WMT_EVENT 事件。 在步骤 5) (调用 Load 时发送事件。

 

以下代码演示如何生成图形:

HRESULT Player::LoadMediaFile(PCWSTR pwszFile)
{


    BOOL bIsWindowsMediaFile = IsWindowsMediaFile(pwszFile);

    HRESULT hr = S_OK;

    // If this is the first time opening the file, create the
    // filter graph and add the WM ASF Reader filter.

    if (m_DRM.State() == DRM_INITIAL)
    {
        hr = CreateFilterGraph();
        if (FAILED(hr))
        {
            goto done;
        }

        // Use special handling for Windows Media files.
        if (bIsWindowsMediaFile)
        {
            // Add the ASF Reader filter to the graph.
            hr = m_pReader.CoCreateInstance(CLSID_WMAsfReader);
            if (FAILED(hr))
            {
                goto done;
            }

            hr = pGraph->AddFilter(m_pReader, NULL);
            if (FAILED(hr))
            {
                goto done;
            }

            hr = m_pReader->QueryInterface(&m_pFileSource);
            if (FAILED(hr))
            {
                goto done;
            }
        }
    }

    if (bIsWindowsMediaFile)
    {
            hr = m_pFileSource->Load(pwszFile, NULL);
C++
            if (FAILED(hr))            {                goto done;            }            hr = RenderOutputPins(pGraph, m_pReader);    }    else    {        // Not a Windows Media file, so just render the standard way.        hr = pGraph->RenderFile(pwszFile, NULL);    }done:    return hr;}

在前面的代码中,函数 RenderOutputPins 枚举 WM ASF 读取器 筛选器上的输出引脚,并为每个引脚调用 IGraphBuilder::Render

HRESULT RenderOutputPins(IGraphBuilder *pGraph, IBaseFilter *pFilter)
{
    CComPtr<IEnumPins>  pEnumPin = NULL;
    CComPtr<IPin>       pConnectedPin;
    CComPtr<IPin>       pPin;

    // Enumerate all pins on the filter
    HRESULT hr = pFilter->EnumPins(&pEnumPin);
    if (FAILED(hr))
    {
        goto done;
    }

    // Step through every pin, looking for the output pins.
    while (S_OK == (hr = pEnumPin->Next(1, &pPin, NULL)))
    {
        // Skip connected pins.
        hr = pPin->ConnectedTo(&pConnectedPin);
        if (hr == VFW_E_NOT_CONNECTED)
        {
            PIN_DIRECTION PinDirection;
            hr = pPin->QueryDirection(&PinDirection);

            if ((S_OK == hr) && (PinDirection == PINDIR_OUTPUT))
            {
                hr = pGraph->Render(pPin);
            }
        }

        pConnectedPin.Release();
        pPin.Release();

        // If there was an error, stop enumerating.
        if (FAILED(hr))
        {
            break;
        }
    }

done:
    return hr;
}

以下代码演示如何从 WM ASF 读取器获取指向 IWMDRMReader 接口的指针:

HRESULT DrmManager::Initialize(IBaseFilter *pFilter)
{


    CComPtr<IServiceProvider> pService;
    CComPtr<IWMDRMReader> pDrmReader;

    HRESULT hr = pFilter->QueryInterface(&pService);
    if (SUCCEEDED(hr))
    {
        hr = pService->QueryService(
            __uuidof(IWMDRMReader), IID_PPV_ARGS(&m_pDrmReader));
    }
    return hr;
}

在 DirectShow 中读取 ASF 文件