Windows Media Encoder 9 SDK
Sample Code Bug Fix
According to Microsoft the Windows Media
Encode 9 SDK was retired in mid-2010, and replaced by the Expression LIbrary which
is intended to be used with dotNET languages such as C#.
The WME SDK makes heavy use of
the COM automation libraries, and is notoriously difficult to learn as the
various method and property names required are hidden behind obscure COM calls.
That is, just looking at application source code it's not immediately possible
for a programmer new to the SDK to understand what's really happening.
The downloaded SDK also contains
some sample code that is supposed to convert (i.e. encode) one video file format
to another, amongst other things. This sample code has been aggravating
programmers for years, as it does not work as written, and it's unclear why
there is a problem.
If the Windows Media Encoder
application (required to be installed to get the SDK) is run, it will quite
happily convert an input video file, but getting the same function to work using
the SDK is impossible using the sample code provided.
One of the most common
frustrating errors in the sample code is the call to
hr = pEncoder->PrepareToEncode(VARIANT_TRUE);
that crashes for no apparent
reason. Depending on the testing process, the fatal error may appear as an
invalid handle.
This page describes why
there is a problem, and what can be done about it to get working code.
In summary , PrepareToEncode
initializes the encoder object and makes it ready for the encoding process to
run. If the setup of the encoder object is not correct and complete, the call
will crash. The most common reason, located after some days of poking around, is
that the encoder does not have a valid video profile present in the
ProfileGroup. The relevent method in the automation object (wmenc.exe) is poorly
written and does not include any kind of graceful error handling. This makes
debugging next to impossible, and did I mention very frustrating...
Hence, when the
ProfileGroup causes the crash, it's because the code is looking for audio /
video input object handles and is not finding what it wants.
It also turns out that the
Windows Media Encoder application looks to different profiles (described below)
while the SDK sample code looks to default profiles in the SYSTEM32 directory.
The sample code also wants to
create a console window, which is unnecessary and undesirable.
So how is this to be fixed?
Here's the updated sample code.
-
BOOL SaveAVItoWMV
(WCHAR *pAviFileName)
-
{
-
// Declare
variables.
-
HRESULT
hr;
IWMEncoder* pEncoder=
NULL;
-
IWMEncSourceGroupCollection* pSrcGrpColl =
NULL;
-
IWMEncSourceGroup* pSrcGrp =
NULL;
-
IWMEncSource* pSrc =
NULL;
-
IWMEncSource* pSrcAud =
NULL;
-
IWMEncVideoSource* pSrcVid =
NULL;
-
IWMEncProfileCollection* pProColl =
NULL;
-
IWMEncProfile* pPro =
NULL;
-
IWMEncProfile2* pPro2 =
NULL;
-
IWMEncFile* pFile =
NULL;
-
IWMEncAttributes* pAttr =
NULL;
-
IWMEncDisplayInfo* pDispInfo =
NULL;
-
CComBSTR bstrName =
NULL;
-
long lCount =
0;
-
int
i;
-
// Initialize the
COM library and retrieve a pointer to an IWMEncoder
interface.
-
// Don't need this
if COM is already initialized.
-
hr =
CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
-
if ( SUCCEEDED( hr
) )
-
{
-
hr =
CoCreateInstance(CLSID_WMEncoder,
-
NULL,
-
CLSCTX_INPROC_SERVER,
-
IID_IWMEncoder,
-
(void**)
&pEncoder);
-
if (REGDB_E_CLASSNOTREG ==
hr)
-
{
-
MessageBox(ghwndApp,L".WMV video file CLSID_WMEncoder
class not registered.",L"Unexpected Error",MB_OK);
-
return
FALSE;
-
}
-
}
-
-
// Retrieve the
source group collection.
-
if ( SUCCEEDED( hr
) )
-
{
-
hr =
pEncoder->get_SourceGroupCollection(&pSrcGrpColl);
-
}
-
-
long srcCount =
-1;
-
pSrcGrpColl->get_Count(&srcCount);
-
// Unnecessary,
srcCount is usually 0
-
if (0 <
srcCount)
-
{
-
for (i = 0;
i < srcCount; i++)
-
{
-
pSrcGrpColl->Remove(CComVariant(i));
-
}
-
}
-
-
// Add a source
group to the collection.
-
if ( SUCCEEDED( hr
) )
-
{ // Arbitrary name, so we'll use
LancerProject.
-
hr =
pSrcGrpColl->Add(CComBSTR(L"LancerProject"),
&pSrcGrp);
-
}
-
-
hr =
CoCreateInstance(CLSID_WMEncProfile2,NULL,CLSCTX_INPROC_SERVER,
-
IID_IWMEncProfile2,(void**)&pPro2);
-
// Get the Program
Files directory, make the path.
-
hr =
pPro2->LoadFromFile(CComBSTR("C:\\Program Files\\Windows Media
Components\\Encoder\\Settings\\d0_cbr_high.prx"));
-
hr =
pPro2->get_Name(&bstrName);
-
-
//
-
// If we make the
next two AddSource calls without the
-
//
AutoSetFileSource as well, put_Profile below fails.
-
// Here, we are
simply adding one audio and one video source object,
-
// but they
are not initialized with input devices.
-
if ( SUCCEEDED( hr
) )
-
{
-
hr =
pSrcGrp->AddSource(WMENC_AUDIO, &pSrcAud);
-
}
-
if ( SUCCEEDED( hr
) )
-
{
-
hr =
pSrcGrp->AddSource(WMENC_VIDEO, &pSrc);
-
}
-
-
// Instead, try AutoSetFileSource. IN
DEBUG mode this displays
// 5 invalid handle messages in debug,
which can be ignored.
// In Release mode COM handles the errors and
eventually it returns S_OK.
-
hr =
pSrcGrp->AutoSetFileSource(CComBSTR(pAviFileName));
-
-
// Retrieve an IWMEncVideoSource
pointer. Ignore any errors in DEBUG mode,
// it
eventually returns S_OK.
// Errors are handled internally by COM in Release
mode?
-
if ( SUCCEEDED( hr
) )
-
{
-
hr =
pSrc->QueryInterface(IID_IWMEncVideoSource,
(void**)&pSrcVid);
-
}
-
-
// Add a video and audio source to the
source group.
// This is the
input .AVI file in both cases.
-
if (
((SUCCEEDED(hr)) && (NULL != pSrcAud)) )
-
{
-
hr =
pSrcAud->SetInput(CComBSTR(pAviFileName));
-
}
-
if ( SUCCEEDED( hr
) )
-
{
-
hr =
pSrcVid->SetInput(CComBSTR(pAviFileName));
-
}
-
-
// There has to be both sources and
inputs to those
// sources or
this call fails as shown.
-
hr =
pSrcGrp->put_Profile(CComVariant(pPro2));
-
if (0xc00d1b5f ==
hr)
-
MessageBox(ghwndApp,L"The media content defined in the
profile does not match the media content defined in the source
group.",L"Debug",MB_OK);
-
-
// Specify the
cropping margins. This is optional.
-
/*
-
if ( SUCCEEDED( hr
) )
-
{
-
hr =
pSrcVid->put_CroppingBottomMargin(5);
-
}
-
if ( SUCCEEDED( hr
) )
-
{
-
hr =
pSrcVid->put_CroppingTopMargin(5);
-
}
-
if ( SUCCEEDED( hr
) )
-
{
-
hr =
pSrcVid->put_CroppingLeftMargin(3);
-
}
-
if ( SUCCEEDED( hr
) )
-
{
-
hr =
pSrcVid->put_CroppingRightMargin(3);
-
}
-
*/
-
// TODO: Fill in
the description object members.
-
// May add some
optional setup dialog
-
-
if ( SUCCEEDED( hr
) )
-
{
-
hr =
pEncoder->get_DisplayInfo(&pDispInfo);
-
}
-
if ( SUCCEEDED( hr
) )
-
{
-
hr =
pDispInfo->put_Author(CComBSTR(L"LancerProject.ca"));
-
}
-
if ( SUCCEEDED( hr
) )
-
{
-
hr =
pDispInfo->put_Copyright(CComBSTR(L" Copyright 2011"));
-
}
-
if ( SUCCEEDED( hr
) )
-
{
-
hr =
pDispInfo->put_Description(CComBSTR(L"Endoscope video
sequence"));
-
}
-
if ( SUCCEEDED( hr
) )
-
{
-
hr =
pDispInfo->put_Rating(CComBSTR(L"Rating"));
-
}
-
if ( SUCCEEDED( hr
) )
-
{
-
hr =
pDispInfo->put_Title(CComBSTR(L"Endoscope Video"));
-
}
-
-
// Add some relevent name-value pair
attributes to the collection.
// These are abitrary, and here are websitelurl
and app name/video file
-
if ( SUCCEEDED( hr
) )
-
{
-
hr =
pEncoder->get_Attributes(&pAttr);
-
}
-
if ( SUCCEEDED( hr
) )
-
{
-
hr = pAttr->Add(CComBSTR(L"WebSite:"),
CComVariant(L"www.lancerproject.ca"));
-
hr = pAttr->Add(CComBSTR(L"Endoscope"),
CComVariant(L"Saved WMV
File"));
-
}
-
-
// Specify (ie
create) a file object in which to save encoded content.
-
if ( SUCCEEDED( hr
) )
-
{
-
hr =
pEncoder->get_File(&pFile);
-
}
-
// All we do is
change the file extension at present
-
if ( SUCCEEDED( hr
) )
-
{
-
// Copy the name
and change the extension
-
int length =
wcslen(pAviFileName);
-
WCHAR *pWmvFileName =
new WCHAR[length + 1];
-
wcsncpy_s(pWmvFileName,length + 1,pAviFileName,length -
4);
-
wcscat_s(pWmvFileName,length +
1,L".wmv");
-
hr =
pFile->put_LocalFileName(CComBSTR(pWmvFileName));
-
}
-
// Choose a profile from the
collection. This is required to
//
succeed when PrepareToEncode is called, below.
-
// Here is also the problem.
get_ProfileCollection goes to the
// system directory and accesses WMSysPr9.prx (seems to be
the
// WME default, and out of date codec
file)
// while we REALLY want C:\Program
Files\Windows Media
// Components\Encoder\Profiles
-
// which is where WMENC.EXE gets the
codecs that do work. Calling
//
set_ProfileDirectory and Refresh does NOT load the correct profile set.
// Calling SetCurrentDirectory does not help
either.
-
// So we need a
pPro2
-
-
-
/*
-
BSTR
profileDirectory;
-
if ( SUCCEEDED( hr
) )
-
{
-
hr =
pEncoder->get_ProfileCollection(&pProColl);
-
}
-
hr =
pProColl->get_ProfileDirectory(&profileDirectory);
-
hr =
pProColl->Refresh();
-
-
if ( SUCCEEDED( hr
) )
-
{
-
hr =
pProColl->get_Count(&lCount);
-
}
-
for (i = 0; i
< lCount; i++)
-
{
-
if ( SUCCEEDED( hr
) )
-
{
-
hr =
pProColl->Item(i, &pPro);
-
}
-
if ( SUCCEEDED( hr
) )
-
{
-
hr =
pPro->get_Name(&bstrName);
-
}
-
if (0 ==
_wcsicmp(bstrName,CComBSTR("DVD quality video
(VBR)")))
-
{
-
// Set the profile
in the source group.
-
if ( SUCCEEDED( hr
) )
-
{
-
hr =
pSrcGrp->put_Profile(CComVariant(pPro));
-
}
-
break;
-
}
-
hr =
S_FALSE;
-
}
-
*/
-
-
-
// Start the
encoding process.
-
if ( SUCCEEDED( hr
) )
-
{
-
BSTR
name;
-
pEncoder->get_Name(&name);
-
pEncoder->put_Name(CComBSTR("LancerProject"));
-
VARIANT_BOOL
autoArchive;
-
pEncoder->get_EnableAutoArchive(&autoArchive);
-
VARIANT_BOOL
remoteAdmin;
-
pEncoder->get_RemoteAdmin(&remoteAdmin);
-
long
errorState;
-
pEncoder->get_ErrorState(&errorState);
-
-
hr =
pEncoder->PrepareToEncode(VARIANT_TRUE);
-
-
if (0xC00D1B67
== hr)< /FONT > <
/FONT >
-
MessageBox(ghwndApp,L"No profile found in the source
group",L"Debug",MB_OK);
-
-
}
-
if ( SUCCEEDED( hr
) )
-
{
-
// Have encoding
stop automatically.
-
pEncoder->put_AutoStop(VARIANT_TRUE);
-
hr =
pEncoder->Start();
-
// see
pEncoder->RunState
-
// Stop the
encoding process.
-
-
if ( SUCCEEDED(
hr ) )
-
{
-
// Wait till
encoding is complete
-
MSG
msg;
-
WMENC_ENCODER_STATE runState;
-
pEncoder->get_RunState(&runState);
-
while(WMENC_ENCODER_RUNNING ==
runState)
-
{
-
// In order
for the events to be triggered correctly,
-
// the
windows message queue needs to be processed.
-
while(PeekMessage(&msg,NULL,NULL,NULL,PM_REMOVE))
-
{
-
TranslateMessage(&msg);
-
DispatchMessage(&msg);
-
}
-
// Test the
run state.
-
pEncoder->get_RunState(&runState);
-
}
-
hr = pEncoder->Stop();
-
}
-
}
-
// Release
pointers.
-
if ( NULL !=
pSrcGrpColl )
-
{
-
pSrcGrpColl->Release();
-
pSrcGrpColl =
NULL;
-
}
-
if ( NULL !=
pSrcGrp )
-
{
-
pSrcGrp->Release();
-
pSrcGrp =
NULL;
-
}
-
if ( NULL !=
pProColl )
-
{
-
pProColl->Release();
-
pProColl =
NULL;
-
}
-
if ( NULL !=
pPro )
-
{
-
pPro->Release();
-
pPro =
NULL;
-
}
-
if ( NULL !=
pFile )
-
{
-
pFile->Release();
-
pFile =
NULL;
-
}
-
if ( NULL !=
pSrcAud )
-
{
-
pSrcAud->Release();
-
pSrcAud =
NULL;
-
}
-
if ( NULL !=
pSrcVid )
-
{
-
pSrcVid->Release();
-
pSrcVid =
NULL;
-
}
-
if ( NULL !=
pSrc )
-
{
-
pSrc->Release();
-
pSrc =
NULL;
-
}
-
if ( NULL !=
pAttr )
-
{
-
pAttr->Release();
-
pAttr =
NULL;
-
}
-
if ( NULL !=
pDispInfo )
-
{
-
pDispInfo->Release();
-
pDispInfo =
NULL;
-
}
-
if ( NULL !=
pEncoder )
-
{
-
pEncoder->Release();
-
pEncoder =
NULL;
-
}
-
if ( SUCCEEDED( hr
) )
-
return
TRUE;
-
return
FALSE;
-
}
I hope you found this
bug fix helpful. Please have a look around rest of the website.
Home
USB Touch Devices
USB Video Endoscope
Copyright © 2005-2012 PiXCL Automation Technologies, Canada. All Rights Reserved.