コーデックのメリット

Windows 7 以降では、Media Foundation コーデックに メリット 値を割り当てることができます。 コーデックを列挙する場合は、メリットの低いコーデックよりも高いメリットを持つコーデックが優先されます。 メリット値を持つコーデックは、割り当てられたメリットのないコーデックよりも優先されます。 コーデック列挙の詳細については、「 MFTEnumEx」を参照してください。

メリット値は Microsoft によって割り当てられます。 現時点では、ハードウェア コーデックのみがメリット値を受け取る資格があります。 コーデック ベンダーには、コーデックのメリット値を検証するために使用されるデジタル証明書も発行されます。 証明書を取得するには、 に電子メール要求を送信します wmla@microsoft.com。 証明書を取得するプロセスには、ライセンスに署名し、一連の情報ファイルを Microsoft に提供することが含まれます。

コーデックのメリットは次のように機能します。

  1. コーデック ベンダーは、次のいずれかを実装します。
    • AVStream ミニ ドライバー。 Media Foundation には、AVStream ドライバー用の標準プロキシ MFT が用意されています。 このオプションを選択することをお勧めします。
    • ハードウェアへのプロキシとして機能する Media Foundation 変換 (MFT)。 詳細については、「 ハードウェア MFT」を参照してください。
  2. コーデックのメリット値は、迅速な検索のためにレジストリに格納されます。
  3. MFTEnumEx 関数は、コーデックをメリットの順に並べ替えます。 メリット値を持つコーデックは、ローカルに登録されたコーデック ( 「MFTRegisterLocal」を参照) の背後にある一覧に表示されますが、他のコーデックの前に表示されます。
  4. MFT が作成されると、 Output Protection Manager (OPM) API を使用してコーデックのメリットが検証されます。
  5. プロキシ MFT の場合、コーデックは IOPMVideoOutput インターフェイスを実装します。 AVStream ドライバーの場合、コーデックは KSPROPSETID_OPMVideoOutput プロパティ セットを実装します。

次の図は、どちらの場合もメリットがどのように検証されるかを示しています。

2 つのプロセスを示す図:1 つはメディア基盤プロキシ mft と avstream ドライバーを通じて、もう 1 つはカスタム プロキシ mft を介して

カスタム プロキシ MFT

ハードウェア コーデックのプロキシ MFT を指定する場合は、次のようにコーデックのメリット値を実装します。

  1. MFT に IOPMVideoOutput インターフェイスを実装します。 コード例については、このトピックの次のセクションを参照してください。

  2. 次のように 、MFT_CODEC_MERIT_Attribute 属性をレジストリに追加します。

    1. MFCreateAttributes を呼び出して、新しい属性ストアを作成します。
    2. IMFAttributes::SetUINT32 を呼び出して、MFT_CODEC_MERIT_Attribute属性を設定します。 属性の値は、コーデックに割り当てられたメリットです。
    3. MFTRegister を呼び出して MFT を登録します。 pAttributes パラメーターに属性ストアを渡します。
  3. アプリケーションは MFTEnumEx を呼び出します。 この関数は、列挙条件に一致するコーデックごとに 1 つずつ、 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 を実装する方法を示しています。 OPM API の一般的な説明については、「 Output Protection Manager」を参照してください。

Note

ここに示すコードには、難読化やその他のセキュリティ メカニズムはありません。 これは、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::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 Transforms

カスタム MFT の作成