編解碼器優點

從 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