Поделиться через


Заслуги кодека

Начиная с Windows 7, кодеку Media Foundation можно присвоить значение заслуг . При перечислении кодеков предпочтительнее кодеков с более высокими достоинствами, чем кодеки с меньшими достоинствами. Кодеки с любым значением заслуг предпочтительнее кодеков без назначенных заслуг. Дополнительные сведения о перечислении кодека см. в разделе MFTEnumEx.

Значения заслуг назначаются корпорацией Майкрософт. В настоящее время только аппаратные кодеки могут получить значение заслуги. Поставщику кодека также выдается цифровой сертификат, который используется для проверки ценности кодека. Чтобы получить сертификат, отправьте запрос по электронной почте по адресу [email protected]. Процесс получения сертификата включает в себя подписывание лицензии и предоставление набора информационных файлов корпорации Майкрософт.

Преимущества кодека работают следующим образом:

  1. Поставщик кодека реализует одно из следующих компонентов:
    • Мини-драйвер AVStream. Media Foundation предоставляет стандартный прокси-сервер MFT для драйверов AVStream. Это желательно сделать.
    • Преобразование Media Foundation (MFT), которое выступает в качестве прокси-сервера для оборудования. Дополнительные сведения см. в разделе Аппаратные MFT.
  2. Значение заслуг кодека хранится в реестре для быстрого поиска.
  3. Функция MFTEnumEx сортирует кодеки в порядке заслуг. Кодеки со значениями заслуг отображаются в списке локально зарегистрированных кодеков (см. раздел MFTRegisterLocal), но опережают другие кодеки.
  4. При создании MFT заслуги кодека проверяются с помощью API диспетчера защиты вывода (OPM).
  5. Для прокси-сервера MFT кодек реализует интерфейс IOPMVideoOutput . Для драйвера AVStream кодек реализует набор свойств KSPROPSETID_OPMVideoOutput.

На следующей схеме показано, как проверяется заслуга в обоих случаях:

Схема, показывающая два процесса: один ведет через media foundation proxy mft и avstream driver, другой через настраиваемый прокси-сервер mft

Настраиваемый прокси-сервер MFT

Если вы предоставляете прокси-сервер MFT для аппаратного кодека, реализуйте значение преимущества кодека следующим образом:

  1. Реализуйте интерфейс IOPMVideoOutput в MFT. Пример кода показан в следующем разделе этого раздела.

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

Реализация IOPMVideoOutput для заслуг кодека

В следующем коде показано, как реализовать IOPMVideoOutput для обеспечения качества кодека. Более общие сведения об API OPM см. в разделе 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 . Входные данные — это структура OPM_GET_CODEC_INFO_PARAMETERS , содержащая ИДЕНТИФИКАТОР CLSID MFT. Выходные данные — это структура 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 должен вычислить код проверки подлинности сообщения (MAC) с помощью алгоритма OMAC-1; См . раздел Вычисление значения OMAC-1.

Поддержка других запросов состояния OPM не требуется.

Методы IOPMVideoOutput::COPPCompatibleGetInformation и IOPMVideoOutput::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;
}

Преобразования Media Foundation

Написание пользовательского MFT