Codec Mérito
A partir do Windows 7, um codec Media Foundation pode receber um mérito valor. Quando codecs são enumerados, codecs com maior mérito são preferidos sobre codecs com menor mérito. Codecs com qualquer valor de mérito são preferidos sobre codecs sem um mérito atribuído. Para obter detalhes sobre a enumeração de codec, consulte MFTEnumEx.
Os valores de mérito são atribuídos pela Microsoft. Atualmente, apenas codecs de hardware são elegíveis 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 por e-mail 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.
Codec mérito funciona da seguinte forma:
- O fornecedor do codec implementa um dos seguintes:
- Um mini-driver AVStream. Media Foundation fornece um MFT proxy padrão para drivers AVStream. Esta é a opção recomendada.
- Uma transformação do Media Foundation (MFT) que atua como um proxy para o hardware. Para obter mais informações, consulte MFTs de hardware.
- O valor de mérito do codec é armazenado no registro para pesquisa rápida.
- A função MFTEnumEx classifica codecs em ordem de mérito. Codecs com valores de mérito aparecem na lista atrás de codecs registrados localmente (consulte MFTRegisterLocal), mas à frente de outros codecs.
- Quando o MFT é criado, o mérito do codec é verificado usando a API do Output Protection Manager (OPM).
- Para um proxy MFT, o codec implementa o IOPMVideoOutput interface. 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:
Proxy MFT personalizado
Se você fornecer um proxy MFT para o codec de hardware, implemente o valor de mérito do codec da seguinte maneira:
Implemente o IOPMVideoOutput interface no MFT. O código de exemplo é mostrado na próxima seção deste tópico.
Adicione o atributo MFT_CODEC_MERIT_Attribute ao registo, da seguinte forma:
- Chame MFCreateAttributes para criar um novo repositório de atributos.
- Chame IMFAttributes::SetUINT32 para definir o atributo MFT_CODEC_MERIT_Attribute. O valor do atributo é o mérito atribuído ao codec.
- Ligue MFTRegister para registrar o MFT. Passe o repositório de atributos no parâmetro pAttributes.
O aplicativo chama MFTEnumEx. Esta função retorna uma matriz de ponteiros IMFActivate, um para cada codec que corresponde aos critérios de enumeração.
O aplicativo chama IMFActivate::ActivateObject para criar o MFT.
O métodoActivateObject chama o função MFGetMFTMerit para verificar o certificado e o valor de mérito.
A função MFGetMFTMerit chama IOPMVideoOutput::GetInformation e envia uma solicitação de status OPM_GET_CODEC_INFO. Essa solicitação de status retorna o valor de mérito atribuído ao codec. Se esse valor não corresponder ao valor do Registro, ActivateObject pode 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 de codec. Para obter uma discussão mais geral sobre a API do OPM, consulte Output Protection Manager.
Observação
O código mostrado aqui não tem ofuscação ou outros mecanismos de segurança. Destina-se a mostrar a implementação básica do handshake OPM e 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. Devolva este 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 completa o handshake 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 de IOPMVideoOutput::GetInformation, implemente a solicitação de status OPM_GET_CODEC_INFO. Os dados de entrada são uma estrutura OPM_GET_CODEC_INFO_PARAMETERS que contém o CLSID do seu MFT. Os dados de saída são uma estrutura OPM_GET_CODEC_INFO_INFORMATION que contém o mérito do 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 código de autenticação de mensagem (MAC) usando o algoritmo OMAC-1; consulte Calculando o valor OMAC-1.
Não é necessário suportar quaisquer outros pedidos de estado OPM.
O IOPMVideoOutput::COPPCompatibleGetInformation e IOPMVideoOutput::Configure métodos não são necessários para o mérito do 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;
}
Tópicos relacionados
-
Escrevendo um MFT personalizado