編解碼器優點
從 Windows 7 開始,媒體基礎編解碼器可以指派 優點 值。 列舉編解碼器時,優先使用具有較高優點的編解碼器,而非具有較低優點的編解碼器。 具有任何優點值的編解碼器會優先于不具指派優點的編解碼器。 如需編解碼器列舉的詳細資訊,請參閱 MFTEnumEx。
價值是由 Microsoft 指派。 目前,只有硬體編解碼器有資格獲得價值。 編解碼器廠商也會發出數位憑證,用來驗證編解碼器的價值。 若要取得憑證,請將電子郵件要求傳送至 wmla@microsoft.com 。 取得憑證的套裝程式括簽署授權,以及提供一組資訊檔案給 Microsoft。
編解碼器優點的運作方式如下:
- 編解碼器廠商會實作下列其中一項:
- AVStream 迷你驅動程式。 媒體基礎提供 AVStream 驅動程式的標準 Proxy MFT。 這是建議選項。
- 媒體基礎轉換 (做為硬體 Proxy 的 MFT) 。 如需詳細資訊,請參閱 硬體 MFT。
- 編解碼器的優點值會儲存在登錄中以供快速查閱。
- MFTEnumEx函式會依優點順序排序編解碼器。 具有優點值的編解碼器會出現在本機註冊編解碼器後方的清單中, (請參閱 MFTRegisterLocal) ,但在其他編解碼器之前。
- 建立 MFT 時,會使用 Output Protection Manager (OPM) API 來驗證編解碼器的優點。
- 針對 Proxy MFT,編解碼器會實作 IOPMVideoOutput 介面。 針對 AVStream 驅動程式,編解碼器會實作 KSPROPSETID_OPMVideoOutput 屬性集。
下圖顯示這兩種情況下如何驗證優點:
自訂 Proxy MFT
如果您為硬體編解碼器提供 Proxy MFT,請實作編解碼器價值,如下所示:
在 MFT 中實作 IOPMVideoOutput 介面。 本主題的下一節會顯示範常式序代碼。
將 MFT_CODEC_MERIT_Attribute 屬性新增至登錄,如下所示:
- 呼叫 MFCreateAttributes 以建立新的屬性存放區。
- 呼叫 IMFAttributes::SetUINT32 來設定 MFT_CODEC_MERIT_Attribute 屬性。 屬性的值是編解碼器的指派優點。
- 呼叫 MFTRegister 以註冊 MFT。 在 pAttributes 參數中傳遞屬性存放區。
應用程式會呼叫 MFTEnumEx。 此函式會傳回 IMFActivate 指標的陣列,其中一個用於符合列舉準則的每個編解碼器。
應用程式會呼叫 IMFActivate::ActivateObject 來建立 MFT。
ActivateObject方法會呼叫MFGetMFTMerit函式來驗證憑證和優點值。
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::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;
}
相關主題