Compartilhar via


Mérito do Codec

A partir do Windows 7, um codec do Media Foundation pode receber um valor de mérito . Quando codecs são enumerados, codecs com maior mérito são preferenciais em vez de codecs com menor mérito. Codecs com qualquer valor de mérito são preferenciais em vez de codecs sem um mérito atribuído. Para obter detalhes sobre a enumeração codec, consulte MFTEnumEx.

Os valores de mérito são atribuídos pela Microsoft. Atualmente, somente codecs de hardware são qualificados para receber um valor de mérito. O fornecedor do codec também recebe um certificado digital, que é usado para verificar o valor de mérito do codec. Para obter um certificado, envie uma solicitação de email para wmla@microsoft.com. O processo para obter um certificado inclui assinar uma licença e fornecer um conjunto de arquivos de informações para a Microsoft.

O mérito codec funciona da seguinte maneira:

  1. O fornecedor de codec implementa um dos seguintes:
    • Um mini-driver AVStream. O Media Foundation fornece um MFT de proxy padrão para drivers AVStream. Essa é a opção indicada.
    • Uma MFT (transformação do Media Foundation) que atua como um proxy para o hardware. Para obter mais informações, consulte MFTs de hardware.
  2. O valor de mérito do codec é armazenado no registro para pesquisa rápida.
  3. A função MFTEnumEx classifica codecs em ordem de mérito. Codecs com valores de mérito aparecem na lista por trás de codecs registrados localmente (consulte MFTRegisterLocal), mas à frente de outros codecs.
  4. Quando o MFT é criado, o mérito do codec é verificado usando a API do OPM ( Output Protection Manager ).
  5. Para um MFT proxy, o codec implementa a interface IOPMVideoOutput . Para um driver AVStream, o codec implementa o conjunto de propriedades KSPROPSETID_OPMVideoOutput.

O diagrama a seguir mostra como o mérito é verificado em ambos os casos:

diagrama mostrando dois processos: um leva por meio de mft de proxy de base de mídia e driver avstream, o outro por meio de mft de proxy personalizado

MFT de proxy personalizado

Se você fornecer um MFT de proxy para o codec de hardware, implemente o valor de mérito codec da seguinte maneira:

  1. Implemente a interface IOPMVideoOutput no MFT. O código de exemplo é mostrado na próxima seção deste tópico.

  2. Adicione o atributo MFT_CODEC_MERIT_Attribute ao registro, da seguinte maneira:

    1. Chame MFCreateAttributes para criar um novo repositório de atributos.
    2. Chame IMFAttributes::SetUINT32 para definir o atributo MFT_CODEC_MERIT_Attribute . O valor do atributo é o mérito atribuído do codec.
    3. Chame MFTRegister para registrar o MFT. Passe o repositório de atributos no parâmetro pAttributes .
  3. O aplicativo chama MFTEnumEx. Essa função retorna uma matriz de ponteiros IMFActivate , um para cada codec que corresponde aos critérios de enumeração.

  4. O aplicativo chama IMFActivate::ActivateObject para criar o MFT.

  5. O método ActivateObject chama a função MFGetMFTMerit para verificar o certificado e o valor de mérito.

  6. A função MFGetMFTMerit chama IOPMVideoOutput::GetInformation e envia uma solicitação status OPM_GET_CODEC_INFO. Essa solicitação status retorna o valor de mérito atribuído do codec. Se esse valor não corresponder ao valor do Registro, ActivateObject poderá falhar.

O código a seguir mostra como adicionar o valor de mérito ao registro quando você registra o 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;
}

Implementando IOPMVideoOutput para Codec Merit

O código a seguir mostra como implementar IOPMVideoOutput para mérito codec. Para obter uma discussão mais geral sobre a API do OPM, consulte Gerenciador de Proteção de Saída.

Observação

O código mostrado aqui não tem ofuscação ou outros mecanismos de segurança. Ele destina-se a mostrar a implementação básica do handshake do OPM e da solicitação de status.

 

Este exemplo pressupõe que g_TestCert é uma matriz de bytes que contém a cadeia de certificados do codec e g_PrivateKey é uma matriz de bytes que contém a chave privada do certificado:

// 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)

No método IOPMVideoOutput::StartInitialization , gere um número aleatório para o handshake. Retorne esse número e o certificado ao chamador:

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

O método IOPMVideoOutput::FinishInitialization conclui o handshake do 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;
}

No método IOPMVideoOutput::GetInformation, implemente a solicitação status OPM_GET_CODEC_INFO. Os dados de entrada são uma estrutura OPM_GET_CODEC_INFO_PARAMETERS que contém o CLSID do MFT. Os dados de saída são uma estrutura OPM_GET_CODEC_INFO_INFORMATION que contém o mérito codec.

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

O método GetInformation deve calcular um MAC (Código de Autenticação de Mensagem) usando o algoritmo OMAC-1; consulte Computando o valor OMAC-1.

Não é necessário dar suporte a nenhuma outra solicitação de status OPM.

Os métodos IOPMVideoOutput::COPPCompatibleGetInformation e IOPMVideoOutput::Configure não são necessários para o mérito codec, portanto, esses métodos podem retornar 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;
}

Transformações do Media Foundation

Escrevendo um MFT personalizado