共用方式為


編解碼器優點

從 Windows 7 開始,媒體基礎編解碼器可以指派 優點 值。 列舉編解碼器時,優先使用具有較高優點的編解碼器,而編解碼器的優點較低。 具有任何優點值的編解碼器優先於沒有指派優點的編解碼器。 如需編解碼器列舉的詳細資訊,請參閱 MFTEnumEx

優點值是由Microsoft指派。 目前,只有硬體編解碼器才有資格獲得優點值。 編解碼器廠商也會頒發數位證書,用來驗證編解碼器的優點值。 若要取得憑證,請傳送電子郵件要求給 wmla@microsoft.com。 取得憑證的程式包括簽署授權,並提供一組資訊檔案來Microsoft。

編解碼器優點的運作方式如下:

  1. 編解碼器廠商會實作下列其中一項:
    • AVStream 迷你驅動程式。 媒體基礎為AVStream驅動程式提供標準 Proxy MFT。 這是建議的選項。
    • 作為硬體 Proxy 的媒體基礎轉換 (MFT)。 如需詳細資訊,請參閱 硬體 MFT
  2. 編解碼器的優點值會儲存在登錄中,以便快速查閱。
  3. MFTEnumEx 函式會依優點排序編解碼器。 具有優點值的編解碼器會出現在本機註冊編解碼器的清單中(請參閱 MFTRegisterLocal),但領先於其他編解碼器。
  4. 建立 MFT 時,編解碼器的優點會使用 Output Protection Manager (OPM) API 進行驗證。
  5. 針對 Proxy MFT,編解碼器會實作 IOPMVideoOutput 介面。 針對AVStream驅動程式,編解碼器會實作KSPROPSETID_OPMVideoOutput屬性集。

下圖顯示在這兩種情況下如何驗證優點:

顯示兩個程式的 圖表:一個透過媒體基礎 Proxy mft 和 avstream 驅動程序領先,另一個則透過自定義 Proxy mft

自訂 Proxy MFT

如果您為硬體編解碼器提供 Proxy MFT,請實作編解碼器優點值,如下所示:

  1. 在 MFT 中實作 IOPMVideoOutput 介面。 本主題的下一節會顯示範例程序代碼。

  2. MFT_CODEC_MERIT_Attribute 屬性新增至登錄,如下所示:

    1. 呼叫 MFCreateAttributes,以建立新的屬性存放區。
    2. 呼叫 IMFAttributes::SetUINT32 來設定 MFT_CODEC_MERIT_Attribute 屬性。 屬性的值是編解碼器指派的優點。
    3. 呼叫 MFTRegister 註冊 MFT。 在 pAttributes 參數中傳遞屬性存放區。
  3. 應用程式會呼叫 MFTEnumEx 。 此函式會傳回 IMFActivate 指標的陣列,每個編解碼器各一個符合列舉準則。

  4. 應用程式會呼叫 IMFActivate::ActivateObject 來建立 MFT。

  5. ActivateObject 方法會呼叫 MFGetMFTMerit 函式來驗證憑證和優點值。

  6. MFGetMFTMerit 函式會 呼叫 IOPMVideoOutput::GetInformation,並傳送 OPM_GET_CODEC_INFO 狀態要求。 此狀態要求會傳回編解碼器的指派優點值。 如果此值不符合登錄值,ActivateObject 可能會失敗。

下列程式代碼示範如何在註冊 MFT 時,將優點值新增至登錄:

// Shows how to register an MFT with a merit value.

HRESULT RegisterMFTWithMerit()
{
    // The following media types would apply to an H.264 decoder, 
    // and are used here as an example.

    MFT_REGISTER_TYPE_INFO aDecoderInputTypes[] = 
    {
        { MFMediaType_Video, MFVideoFormat_H264 },
    };

    MFT_REGISTER_TYPE_INFO aDecoderOutputTypes[] = 
    {
        { MFMediaType_Video, MFVideoFormat_RGB32 }
    };
    
    // Create an attribute store to hold the merit attribute.
    HRESULT hr = S_OK;
    IMFAttributes *pAttributes = NULL;

    hr = MFCreateAttributes(&pAttributes, 1);

    if (SUCCEEDED(hr))
    {
        hr = pAttributes->SetUINT32(
            MFT_CODEC_MERIT_Attribute, 
            DECODER_MERIT   // Use the codec's assigned merit value.
            );
    }

    // Register the decoder for MFTEnum(Ex).
    if (SUCCEEDED(hr))
    {
        hr = MFTRegister(
            CLSID_MPEG1SampleDecoder,                   // CLSID
            MFT_CATEGORY_VIDEO_DECODER,                 // Category
            const_cast<LPWSTR>(SZ_DECODER_NAME),        // Friendly name
            0,                                          // Flags
            ARRAYSIZE(aDecoderInputTypes),              // Number of input types
            aDecoderInputTypes,                         // Input types
            ARRAYSIZE(aDecoderOutputTypes),             // Number of output types
            aDecoderOutputTypes,                        // Output types
            pAttributes                                 // Attributes 
            );
    }

    SafeRelease(&pAttributes);
    return hr;
}

實作 Codec Merit 的 IOPMVideoOutput

下列程式代碼示範如何實作 IOPMVideoOutput,以取得編解碼器的優點。 如需 OPM API 的更一般討論,請參閱 Output Protection Manager

注意

此處顯示的程式代碼沒有混淆或其他安全性機制。 其旨在顯示 OPM 交握和狀態要求的基本實作。

 

此範例假設 g_TestCert 是包含編解碼器憑證鏈結的位元組陣列,而 g_PrivateKey 是包含憑證私鑰的位元組數組:

// Byte array that contains the codec's certificate.

const BYTE g_TestCert[] =
{
    // ... (certificate not shown)
// Byte array that contains the private key from the certificate.

const BYTE g_PrivateKey[] = 
{
    // .... (key not shown)

IOPMVideoOutput::StartInitialization 方法中,為交握產生隨機數。 將這個號碼和憑證傳回給呼叫端:

STDMETHODIMP CodecMerit::StartInitialization(
    OPM_RANDOM_NUMBER *prnRandomNumber,
    BYTE **ppbCertificate,
    ULONG *pulCertificateLength
    )
{
    HRESULT hr = S_OK;

    DWORD cbCertificate = sizeof(g_TestCert);
    const BYTE *pCertificate = g_TestCert;

    // Generate the random number for the OPM handshake.
    hr = BCryptGenRandom(
        NULL,  
        (PUCHAR)&m_RandomNumber, 
        sizeof(m_RandomNumber),
        BCRYPT_USE_SYSTEM_PREFERRED_RNG
        );

    // Allocate the buffer to copy the certificate.
    if (SUCCEEDED(hr))
    {
        *ppbCertificate = (PBYTE)CoTaskMemAlloc(cbCertificate);

        if (*ppbCertificate == NULL) 
        {
            hr = E_OUTOFMEMORY;
        }
    }

    // Copy the certificate and the random number.
    if (SUCCEEDED(hr))
    {
        *pulCertificateLength = cbCertificate;
        CopyMemory(*ppbCertificate, pCertificate, cbCertificate);
        *prnRandomNumber = m_RandomNumber;
    }
    return hr;
}

IOPMVideoOutput::FinishInitialization 方法會完成 OPM 交握:

STDMETHODIMP CodecMerit::FinishInitialization(
    const OPM_ENCRYPTED_INITIALIZATION_PARAMETERS *pParameters
    )
{
    HRESULT hr = S_OK;
    BCRYPT_ALG_HANDLE hAlg = NULL;
    BCRYPT_KEY_HANDLE hKey = NULL;
    BCRYPT_OAEP_PADDING_INFO paddingInfo = {0};
    DWORD DecryptedLength = 0;
    PBYTE pbDecryptedParams = NULL;

    // The caller should pass the following structure in
    // pParameters:

    typedef struct {
        GUID  guidCOPPRandom;   // Our random number.
        GUID  guidKDI;          // AES signing key.
        DWORD StatusSeqStart;   // Status sequence number.
        DWORD CommandSeqStart;  // Command sequence number.
    } InitParams;

    paddingInfo.pszAlgId = BCRYPT_SHA512_ALGORITHM;

    //  Decrypt the input using the decoder's private key.

    hr = BCryptOpenAlgorithmProvider(
        &hAlg,
        BCRYPT_RSA_ALGORITHM,
        MS_PRIMITIVE_PROVIDER,
        0
        );

    //  Import the private key.
    if (SUCCEEDED(hr))
    {
        hr = BCryptImportKeyPair(
            hAlg,
            NULL,
            BCRYPT_RSAPRIVATE_BLOB,
            &hKey,
            (PUCHAR)g_PrivateKey, //pbData,
            sizeof(g_PrivateKey), //cbData,
            0
            );
    }

    //  Decrypt the input data.

    if (SUCCEEDED(hr))
    {
        hr = BCryptDecrypt(
            hKey,
            (PBYTE)pParameters,
            OPM_ENCRYPTED_INITIALIZATION_PARAMETERS_SIZE,
            &paddingInfo,
            NULL,
            0,
            NULL,
            0,
            &DecryptedLength,
            BCRYPT_PAD_OAEP
            );
    }

    if (SUCCEEDED(hr))
    {
        pbDecryptedParams = new (std::nothrow) BYTE[DecryptedLength];

        if (pbDecryptedParams == NULL) 
        {
            hr = E_OUTOFMEMORY;
        }
    }

    if (SUCCEEDED(hr))
    {
         hr = BCryptDecrypt(
             hKey,
             (PBYTE)pParameters,
             OPM_ENCRYPTED_INITIALIZATION_PARAMETERS_SIZE,
             &paddingInfo,
             NULL,
             0,
             pbDecryptedParams,
             DecryptedLength,
             &DecryptedLength,
             BCRYPT_PAD_OAEP
             );
    }

    if (SUCCEEDED(hr))
    {
        InitParams *Params = (InitParams *)pbDecryptedParams;
        
        //  Check the random number.
        if (0 != memcmp(&m_RandomNumber, &Params->guidCOPPRandom, sizeof(m_RandomNumber)))
        {
            hr = E_ACCESSDENIED;
        } 
        else 
        {
            //  Save the key and the sequence numbers.

            CopyMemory(m_AESKey.abRandomNumber, &Params->guidKDI, sizeof(m_AESKey));
            m_StatusSequenceNumber = Params->StatusSeqStart;
            m_CommandSequenceNumber = Params->CommandSeqStart;
        }
    }

    //  Clean up.

    if (hKey)
    {
        BCryptDestroyKey(hKey);
    }
    if (hAlg)
    {
        BCryptCloseAlgorithmProvider(hAlg, 0);
    }
    delete [] pbDecryptedParams;

    return hr;
}

IOPMVideoOutput::GetInformation 方法中,實作 OPM_GET_CODEC_INFO 狀態要求。 輸入數據是包含 MFT CLSID 的 OPM_GET_CODEC_INFO_PARAMETERS 結構。 輸出數據是包含編解碼器優點的 OPM_GET_CODEC_INFO_INFORMATION 結構。

STDMETHODIMP CodecMerit::GetInformation( 
    const OPM_GET_INFO_PARAMETERS *Parameters,
    OPM_REQUESTED_INFORMATION *pRequest
    )
{

    HRESULT hr = S_OK;
    OPM_GET_CODEC_INFO_PARAMETERS *CodecInfoParameters;

    //  Check the MAC.
    OPM_OMAC Tag = { 0 };

    hr = ComputeOMAC(
        m_AESKey, 
        (PBYTE)Parameters + OPM_OMAC_SIZE, 
        sizeof(OPM_GET_INFO_PARAMETERS) - OPM_OMAC_SIZE, 
        &Tag
        );

    if (SUCCEEDED(hr))
    {
        if (0 != memcmp(Tag.abOMAC, &Parameters->omac, OPM_OMAC_SIZE))
        {
            hr = E_ACCESSDENIED;
        }
    }

    // Validate the status sequence number. This must be consecutive
    // from the previous sequence number.

    if (SUCCEEDED(hr))
    {
        if (Parameters->ulSequenceNumber != m_StatusSequenceNumber++)
        {
            hr = E_ACCESSDENIED;
        }
    }

    //  Check the status request.

    if (SUCCEEDED(hr))
    {
        if (Parameters->guidInformation != OPM_GET_CODEC_INFO) 
        {
            hr = E_NOTIMPL;
        }
    }

    //  Check parameters.

    if (SUCCEEDED(hr))
    {
        CodecInfoParameters = (OPM_GET_CODEC_INFO_PARAMETERS *)Parameters->abParameters;

        if (Parameters->cbParametersSize > OPM_GET_INFORMATION_PARAMETERS_SIZE ||
            Parameters->cbParametersSize < sizeof(ULONG) ||
            Parameters->cbParametersSize - sizeof(ULONG) != CodecInfoParameters->cbVerifier
            ) 
        {
            hr = E_INVALIDARG;
        }
    }

    //  The input data should consist of the CLSID of the decoder.

    if (SUCCEEDED(hr))
    {
        CodecInfoParameters = (OPM_GET_CODEC_INFO_PARAMETERS *)Parameters->abParameters;
    
        if (CodecInfoParameters->cbVerifier != sizeof(CLSID) ||
            0 != memcmp(&CLSID_MPEG1SampleDecoder,
                        CodecInfoParameters->Verifier,
                        CodecInfoParameters->cbVerifier)) 
        {
            hr = E_ACCESSDENIED;
        }
    }

    if (SUCCEEDED(hr))
    {
        // Now return the decoder merit to the caller.

        ZeroMemory(pRequest, sizeof(OPM_REQUESTED_INFORMATION));

        OPM_GET_CODEC_INFO_INFORMATION *pInfo = 
            (OPM_GET_CODEC_INFO_INFORMATION *)pRequest->abRequestedInformation;

        pInfo->Merit = DECODER_MERIT;
        pInfo->rnRandomNumber = Parameters->rnRandomNumber;

        pRequest->cbRequestedInformationSize = sizeof(OPM_GET_CODEC_INFO_INFORMATION);

        //  Sign it with the key.

        hr = ComputeOMAC(
            m_AESKey, 
            (PBYTE)pRequest + OPM_OMAC_SIZE, 
            sizeof(OPM_REQUESTED_INFORMATION) - OPM_OMAC_SIZE, 
            &pRequest->omac
            );
    }

    return hr;
}

GetInformation 方法必須使用 OMAC-1 演算法來計算訊息驗證碼 (MAC):請參閱 計算 OMAC-1 值

不需要支援任何其他 OPM 狀態要求。

IOPMVideoOutput::COPPCompatibleGetInformationIOPMVideoOutput::Configure 方法,因此這些方法可以傳回 E_NOTIMPL

STDMETHODIMP CodecMerit::COPPCompatibleGetInformation( 
    const OPM_COPP_COMPATIBLE_GET_INFO_PARAMETERS *pParameters,
    OPM_REQUESTED_INFORMATION *pRequestedInformation)
{
    return E_NOTIMPL;
}

STDMETHODIMP CodecMerit::Configure( 
    const OPM_CONFIGURE_PARAMETERS *pParameters,
    ULONG ulAdditionalParametersSize,
    const BYTE *pbAdditionalParameters)
{
    return E_NOTIMPL;
}

媒體基礎轉換

撰寫自定義 MFT