//------------------------------------------------------------------------------ // File: XMLTlTst.cpp // // Desc: DirectShow sample code - test utility for building timelines from // .XTL files. // // Copyright (c) 1999-2001 Microsoft Corporation. All rights reserved. //------------------------------------------------------------------------------ // // This sample demonstrates the following: // // - Using IXml2Dex to load timelines from and save to .XTL files // - Adding Windows Media (ASF) support to Editing applications // - Using IAMErrorLog to display error messages // - Preview of DES timelines (IAMTimeLine, IRenderEngine) // - Traversing the timeline nodes // #include #include #include #include #include extern CComModule _Module; #include // // NOTE: // // In order to write ASF files using this program, you need to obtain // a Windows Media Format SDK Certificate (WMStub.lib), link to it, // and define USE_WMF_CERT below. // // See the Windows Media Format SDK documentation for more information. // // The SDK download page is located at // http://msdn.microsoft.com/workshop/imedia/windowsmedia/sdk/wmsdk.asp, // with links to the SDK itself and information for obtaining a certificate. // // #define USE_WMF_CERT // #ifdef USE_WMF_CERT #include #include // Note: this object is a SEMI-COM object, and can only be created statically. class CKeyProvider : public IServiceProvider { public: STDMETHODIMP QueryInterface(REFIID riid, void ** ppv); STDMETHODIMP_(ULONG) AddRef() { return 2; } STDMETHODIMP_(ULONG) Release() { return 1; } // IServiceProvider STDMETHODIMP QueryService(REFIID siid, REFIID riid, void **ppv); }; CKeyProvider g_prov; // note, must stay in scope while graph is alive #endif // USE_WMF_CERT class CErrorReporter : public IAMErrorLog { // IAMErrorLog STDMETHODIMP LogError( long Severity, BSTR ErrorString, LONG ErrorCode, HRESULT hresult, VARIANT * pExtraInfo ); public: // SEMI COM STDMETHODIMP QueryInterface(REFIID riid, void ** ppv) { if (riid == IID_IAMErrorLog || riid == IID_IUnknown) { *ppv = (void *) static_cast(this); return NOERROR; } return E_NOINTERFACE; } STDMETHODIMP_(ULONG) AddRef() { return 2; } STDMETHODIMP_(ULONG) Release() { return 1; } }; // // Declarations for helper functions defined below // IPin * GetPin( IBaseFilter * pFilter, int PinNum, PIN_DIRECTION pd ); void TurnOffPreviewMode( IAMTimeline * pTimeline ); HRESULT GetFirstSourceOnTimeline( IAMTimeline *pTimeline, GUID MajorType, IAMTimelineGroup ** ppGroup, IAMTimelineSrc ** ppSource ); HRESULT GetSourceVideoType(WCHAR *wszFilename, AM_MEDIA_TYPE *pmt); HRESULT GetDestinationASFFormat( AM_MEDIA_TYPE **ppmt, int iProfile ); #ifdef USE_WMF_CERT HRESULT MapProfileIdToProfile( int iProfile, IWMProfile **ppProfile); #endif void ListWMSDKProfiles(); BOOL IsAsfExtension( WCHAR * Filename ); HRESULT ConnectOutputFile( IRenderEngine * pEngine, WCHAR * Filename #ifdef USE_WMF_CERT , int iProfile #endif ); // // Main program code // int __cdecl main(int argc, char *argv[]) { CErrorReporter pLog; // note, must stay in scope while graph is alive HRESULT hr = 0; ++argv; // skip the app name --argc; int Ret = 0; char **&ppArg = argv; BOOL fNoRender = FALSE; BOOL fDynamic = FALSE; BOOL fRecomp = FALSE; BOOL fWriteIt = FALSE; int nWriteArg = -1; BOOL fWriteGrf = FALSE; int nWriteGrfArg = -1; int nInputFileArg = -1; BOOL fWriteXtl = FALSE; int nWriteXtlArg = -1; double RenderStart = -1.0; double RenderStop = -1.0; BOOL fNoClock = FALSE; #ifdef USE_WMF_CERT int iASFProfile = -1; #endif // find which switches we've set. Every time we process a switch, // we pull it off the stack // for( int arg = 0 ; arg < argc ; arg++ ) { if( !ppArg[arg] ) continue; // found the input .xtl file specified // if( ppArg[arg][0] != '/' ) { nInputFileArg = arg; continue; } if (ppArg[arg][1] == 'N' || ppArg[arg][1] == 'n') { fNoRender = TRUE; continue; } if (ppArg[arg][1] == 'D' || ppArg[arg][1] == 'd') { fDynamic = TRUE; continue; } if (ppArg[arg][1] == 'C' || ppArg[arg][1] == 'c') { fNoClock = TRUE; continue; } if (ppArg[arg][1] == 'R' || ppArg[arg][1] == 'r') { fRecomp = TRUE; continue; } if (ppArg[arg][1] == 'W' || ppArg[arg][1] == 'w') { fWriteIt = TRUE; nWriteArg = arg + 1; arg++; // advance beyond the extra arg continue; } if (ppArg[arg][1] == 'G' || ppArg[arg][1] == 'g') { fWriteGrf = TRUE; nWriteGrfArg = arg + 1; arg++; // advance beyond the extra arg continue; } if (ppArg[arg][1] == 'X' || ppArg[arg][1] == 'x') { fWriteXtl = TRUE; nWriteXtlArg = arg + 1; arg++; // advance beyond the extra arg continue; } #ifdef USE_WMF_CERT if (ppArg[arg][1] == 'P' || ppArg[arg][1] == 'p') { arg++; if (arg >= argc || (ppArg[arg][0] < '0' || ppArg[arg][0] > '9')) { ListWMSDKProfiles(); return -1; } iASFProfile = atoiA(ppArg[arg]); printf("Using WMSDK profile #%d\r\n", iASFProfile); continue; } #endif // USE_WMF_CERT if( ppArg[arg][1] == '[' ) { // need to pull doubles out of the string // char * p = &ppArg[arg][2]; for( unsigned long k = 0 ; k < strlen( p ) ; k++ ) { if( p[k] == '-' ) { break; } } if( p[k] != '-' ) { printf( "forgot the '-' between doubles\r\n" ); return -1; } for( unsigned long j = 0 ; j < strlen( p ) ; j++ ) { if( p[j] == ']' ) { break; } } if( p[j] != ']' ) { printf( "forgot the ']' \r\n" ); return -1; } // set it to zero so atof will work // p[k] = 0; p[j] = 0; // get the float // RenderStart = atof( p ); // get the next float // p = &p[k+1]; RenderStop = atof( p ); continue; } printf("unrecognized switch: %s\n\n", ppArg[arg]); nInputFileArg = -1; break; } if( argc < 1 || nInputFileArg == -1 ) { printf("Usage: [various switches] input.xtl\r\n"); printf(" /N - No preview, just connect it up\r\n"); printf(" /G output.grf - Output a GRF file\r\n"); printf(" /X output.xtl - Output an XTL file\r\n"); printf(" /R - Connect the graph with smart recompression turned on\r\n"); printf(" /W - Render the clip to file. (It may take a while)\r\n" ); printf(" /D - Dynamic connections on\r\n"); printf(" /[double-double] - set the render range start/stop times\r\n" ); printf(" /C - No clock. Run as fast as you can\r\n" ); #ifdef USE_WMF_CERT printf(" /P [number] - Choose an ASF compression profile\r\n" ); printf(" /P without a number - list available profiles\r\n" ); #endif // USE_WMF_CERT return -1; } USES_CONVERSION; WCHAR *wszFileInput = A2W(ppArg[nInputFileArg]); CComPtr< IRenderEngine > pRenderEngine; CComPtr< IAMTimeline > pTimeline; CComPtr< IXml2Dex > pXml; do { hr = CoInitialize( NULL ); hr = CoCreateInstance( __uuidof(AMTimeline), NULL, CLSCTX_INPROC_SERVER, __uuidof(IAMTimeline), (void**) &pTimeline ); if (FAILED(hr)) { printf("Failed to create timeline, error = %x\r\n", hr); Ret = -1; break; } CComQIPtr< IAMSetErrorLog, &IID_IAMSetErrorLog > pTimelineLog( pTimeline ); if( pTimelineLog ) { pTimelineLog->put_ErrorLog( &pLog ); } hr = CoCreateInstance( __uuidof(Xml2Dex), NULL, CLSCTX_INPROC_SERVER, __uuidof(IXml2Dex), (void**) &pXml ); if (FAILED(hr)) { printf("QEDIT not registered properly\r\n"); Ret = -1; break; } hr = pXml->ReadXMLFile(pTimeline, wszFileInput); printf("ReadXMLFile('%ls') returned %x\r\n", wszFileInput, hr); if (FAILED(hr)) { Ret = -1; break; } // validate sources // HANDLE WaitEvent = CreateEvent( NULL, FALSE, FALSE, NULL ); long Flags = SFN_VALIDATEF_CHECK | SFN_VALIDATEF_POPUP | // SFN_VALIDATEF_TELLME | SFN_VALIDATEF_REPLACE | SFN_VALIDATEF_USELOCAL | 0; hr = pTimeline->ValidateSourceNames( Flags, NULL, (LONG_PTR) WaitEvent ); if( hr == NOERROR ) { WaitForSingleObject( WaitEvent, INFINITE ); } CloseHandle( WaitEvent ); // create a render engine // if( fRecomp ) { hr = CoCreateInstance( __uuidof(SmartRenderEngine), NULL, CLSCTX_INPROC_SERVER, __uuidof(IRenderEngine), (void**) &pRenderEngine ); TurnOffPreviewMode( pTimeline ); #ifdef USE_WMF_CERT // if we're doing smart recompression then the AMTimeline object will // need a way to get our key, so we SetSite on the AMTimeline object here. // Note that the pRenderEngine gets the key a little later than this. if( SUCCEEDED( hr ) ) { CComQIPtr< IObjectWithSite, &IID_IObjectWithSite > pOWS( pTimeline ); ASSERT( pOWS ); if( pOWS ) { pOWS->SetSite((IUnknown *) (IServiceProvider *) &g_prov); } } #endif // USE_WMF_CERT SCompFmt0 scompFmt; ZeroMemory(&scompFmt, sizeof(scompFmt)); // scompFmt.nFormatId initialized to 0; // use the compression type of the first input video in // the project (note this can change the height/width of // the group) CComPtr pGroup; CComPtr pSource; hr = GetFirstSourceOnTimeline( pTimeline, MEDIATYPE_Video, &pGroup, &pSource); #ifdef USE_WMF_CERT // smart recompression works differently for ASF than it // does for AVI. ASF compression needs to be configured by // profile id rather than compressor and format block. We // cannot recover the profile used to author an ASF file, // so the caller is required to provide us the right // profile id. We ask the WMSDK for the media type // corresponding to the profile. WCHAR *wszFileTmp = A2W(ppArg[nWriteArg]); if( IsAsfExtension( wszFileTmp ) ) { AM_MEDIA_TYPE *pmt; hr = GetDestinationASFFormat( &pmt, iASFProfile); if(SUCCEEDED(hr)) { CopyMediaType(&scompFmt.MediaType, pmt); DeleteMediaType(pmt); } } else #endif // USE_WMF_CERT { CComBSTR Filename; if(SUCCEEDED(hr)) { hr = pSource->GetMediaName(&Filename); } if(SUCCEEDED(hr)) { hr = GetSourceVideoType(Filename, &scompFmt.MediaType); } } if(SUCCEEDED(hr)) { hr = pGroup->SetSmartRecompressFormat( (long*) &scompFmt ); FreeMediaType(scompFmt.MediaType); } } else { hr = CoCreateInstance( __uuidof(RenderEngine), NULL, CLSCTX_INPROC_SERVER, __uuidof(IRenderEngine), (void**) &pRenderEngine ); } if (pRenderEngine == NULL) { printf("Failed to create render engine, error = %x\r\n", hr); Ret = -1; break; } #ifdef USE_WMF_CERT // // give the RenderEngine a key to support ASF // { CComQIPtr< IObjectWithSite, &IID_IObjectWithSite > pOWS( pRenderEngine ); ASSERT( pOWS ); if( pOWS ) { pOWS->SetSite((IUnknown *) (IServiceProvider *) &g_prov); } } #endif // USE_WMF_CERT long gRenderMedLocFlags = 0 | // SFN_VALIDATEF_POPUP | // SFN_VALIDATEF_TELLME | // SFN_VALIDATEF_VERBOSE | // SFN_VALIDATEF_USELOCAL | 0; pRenderEngine->SetSourceNameValidation( NULL, NULL, gRenderMedLocFlags ); // set it's timeline // hr = pRenderEngine->SetTimelineObject( pTimeline ); if (FAILED(hr)) { printf("Failed to set timeline object, error = %x\r\n", hr); Ret = -1; break; } // set dynamicness // if (fDynamic) { pRenderEngine->SetDynamicReconnectLevel(CONNECTF_DYNAMIC_SOURCES); printf("DYNAMIC ON\r\n"); } else { pRenderEngine->SetDynamicReconnectLevel(CONNECTF_DYNAMIC_NONE); printf("DYNAMIC OFF\r\n"); } // set the renderrange, if there is one // if( RenderStart != -1.0 && RenderStop != -1.0 ) { pRenderEngine->SetRenderRange2( RenderStart, RenderStop ); } // connect up first half // hr = pRenderEngine->ConnectFrontEnd( ); if( FAILED( hr ) ) { printf("ConnectFrontEnd returned bomb code of %x\r\n", hr); Ret = -1; break; } // get this now, because other calls will need it // CComPtr< IGraphBuilder > pGraph; hr = pRenderEngine->GetFilterGraph( &pGraph ); if (FAILED(hr)) { printf("Couldn't get graph! hr = %x", hr); break; } // if we didn't want to write the file to disk, may as well render it // if( !fWriteIt ) { hr = pRenderEngine->RenderOutputPins( ); if (FAILED(hr)) { printf("RenderOutputPins returned bomb code of %x\r\n", hr); Ret = -1; break; } if( fNoClock ) { CComQIPtr< IMediaFilter, &IID_IMediaFilter > pMFGraph( pGraph ); if( pMFGraph ) { pMFGraph->SetSyncSource( NULL ); } } } else { TurnOffPreviewMode( pTimeline ); WCHAR *wszFileTmp = A2W(ppArg[nWriteArg]); hr = ConnectOutputFile(pRenderEngine, wszFileTmp #ifdef USE_WMF_CERT , iASFProfile #endif ); printf( "ConnectOutputFile returned %x\r\n", hr ); if( FAILED( hr ) ) { fNoRender = TRUE; } } // write out GRF files // if( fWriteGrf ) { WCHAR *wszFileTmp = A2W(ppArg[nWriteGrfArg]); hr = pXml->WriteGrfFile(pGraph, wszFileTmp); printf("WriteGrfFile('%ls') returned %x\r\n", wszFileTmp, hr); } if( fWriteXtl ) { WCHAR *wszFileTmp = A2W(ppArg[nWriteXtlArg]); hr = pXml->WriteXMLFile(pTimeline, wszFileTmp); printf("WriteXtlFile('%ls') returned %x\r\n", wszFileTmp, hr); } if (!fNoRender) { CComQIPtr< IMediaEvent, &IID_IMediaEvent > pEvent( pGraph ); CComQIPtr< IMediaControl, &IID_IMediaControl > pControl( pGraph ); hr = pControl->Run( ); if (FAILED(hr)) { printf("Failed to run the graph, error = %x\r\n", hr); Ret = -1; break; } HANDLE hEvent; if( !pEvent ) { // unexpected error Ret = -1; } hr = pEvent->GetEventHandle((OAEVENT *)&hEvent); if(FAILED(hr)) { // unexpected error Ret = -1; } // wait for completion and dispatch messages for any windows // created on our thread. for(;;) { while(MsgWaitForMultipleObjects( 1, &hEvent, FALSE, INFINITE, QS_ALLINPUT) != WAIT_OBJECT_0) { MSG Message; while (PeekMessage(&Message, NULL, 0, 0, TRUE)) { TranslateMessage(&Message); DispatchMessage(&Message); } } // event signaled. see if we're done. LONG levCode; LONG_PTR lp1, lp2; if(pEvent->GetEvent(&levCode, &lp1, &lp2, 0) == S_OK) { EXECUTE_ASSERT(SUCCEEDED( pEvent->FreeEventParams(levCode, lp1, lp2))); if(levCode == EC_COMPLETE || levCode == EC_ERRORABORT || levCode == EC_USERABORT) { break; } } } hr = pControl->Stop( ); } } while(0); pRenderEngine.Release( ); pXml.Release( ); if (pTimeline) { hr = pTimeline->ClearAllGroups(); pTimeline.Release( ); } CoUninitialize( ); return Ret; } // // helper function to traverse a timline and return the first source // HRESULT GetFirstSourceOnTimeline( IAMTimeline *pTimeline, GUID MajorType, IAMTimelineGroup ** ppGroup, IAMTimelineSrc ** ppSource ) { *ppGroup = 0; *ppSource = 0; for( long g = 0 ;; g++ ) { // locate group of right media type (audio / video) CComPtr< IAMTimelineObj > pGroupObj; HRESULT hr = pTimeline->GetGroup( &pGroupObj, g ); if(FAILED(hr)) { break; } CComQIPtr< IAMTimelineGroup, &IID_IAMTimelineGroup > pGroup( pGroupObj ); CMediaType GroupType; pGroup->GetMediaType( &GroupType ); if( GroupType.majortype != MajorType ) { continue; } // found the right group, go through each track in it long TrackCount = 0; long Layers = 0; pTimeline->GetCountOfType( g, &TrackCount, &Layers, TIMELINE_MAJOR_TYPE_TRACK ); if( TrackCount < 1 ) { continue; } CComQIPtr< IAMTimelineComp, &IID_IAMTimelineComp > pGroupComp( pGroupObj ); for( int CurrentLayer = 0 ; CurrentLayer < Layers ; CurrentLayer++ ) { // get the layer itself // CComPtr< IAMTimelineObj > pLayer; hr = pGroupComp->GetRecursiveLayerOfType( &pLayer, CurrentLayer, TIMELINE_MAJOR_TYPE_TRACK ); // ask if the layer is muted // BOOL LayerMuted = FALSE; pLayer->GetMuted( &LayerMuted ); if( LayerMuted ) { // don't look at this layer // continue; // skip this layer, don't worry about grid } CComQIPtr< IAMTimelineTrack, &IID_IAMTimelineTrack > pTrack( pLayer ); if( !pTrack ) { continue; } REFERENCE_TIME InOut = 0; while( 1 ) { // get the next source on this layer, given a time. // CComPtr< IAMTimelineObj > pSourceObj; hr = pTrack->GetNextSrc( &pSourceObj, &InOut ); ASSERT( !FAILED( hr ) ); if( hr != NOERROR ) { // all done with sources // break; } CComQIPtr< IAMTimelineSrc, &IID_IAMTimelineSrc > pSource( pSourceObj ); ASSERT( pSource ); if( !pSource ) { // this one failed, so look at the next // continue; // sources } // ask if the source is muted // BOOL SourceMuted = FALSE; pSourceObj->GetMuted( &SourceMuted ); if( SourceMuted ) { // don't look at this source // continue; // sources } *ppSource = pSource; pSource.p->AddRef(); *ppGroup = pGroup; pGroup.p->AddRef(); return S_OK; } // while sources } // while currentlayer } // for all groups return VFW_E_NOT_FOUND; } void TurnOffPreviewMode( IAMTimeline * pTimeline ) { long Groups = 0; pTimeline->GetGroupCount( &Groups ); for( int i = 0 ; i < Groups ; i++ ) { CComPtr< IAMTimelineObj > pGroupObj; pTimeline->GetGroup( &pGroupObj, i ); CComQIPtr< IAMTimelineGroup, &IID_IAMTimelineGroup > pGroup( pGroupObj ); pGroup->SetPreviewMode( FALSE ); } } // // uses the media detector to retrieve mediatype of a clip // HRESULT GetSourceVideoType(WCHAR *wszFilename, AM_MEDIA_TYPE *pmt) { CComPtr< IMediaDet > pDet; HRESULT hr = CoCreateInstance( CLSID_MediaDet, NULL, CLSCTX_INPROC_SERVER, IID_IMediaDet, (void**) &pDet ); if( FAILED( hr ) ) { return hr; } #ifdef USE_WMF_CERT // // set the site provider on the MediaDet object to allowed keyed apps // to use ASF decoder // CComQIPtr< IObjectWithSite, &IID_IObjectWithSite > pOWS( pDet ); if( pOWS ) { pOWS->SetSite( (IServiceProvider *) &g_prov ); } #endif // USE_WMF_CERT hr = pDet->put_Filename( wszFilename ); if( FAILED( hr ) ) { return hr; } // go through and find the video stream type // long Streams = 0; long VideoStream = -1; hr = pDet->get_OutputStreams( &Streams ); for( int i = 0 ; i < Streams ; i++ ) { pDet->put_CurrentStream( i ); GUID Major = GUID_NULL; pDet->get_StreamType( &Major ); if( Major == MEDIATYPE_Video ) { VideoStream = i; break; } } if( VideoStream == -1 ) { return VFW_E_INVALIDMEDIATYPE; } hr = pDet->get_StreamMediaType( pmt ); return hr; } #ifdef USE_WMF_CERT // ------------------------------------------------------------------------ // CKeyProvider methods // HRESULT CKeyProvider::QueryInterface(REFIID riid, void ** ppv) { if (riid == IID_IServiceProvider || riid == IID_IUnknown) { *ppv = (void *) static_cast(this); return NOERROR; } return E_NOINTERFACE; } STDMETHODIMP CKeyProvider::QueryService(REFIID siid, REFIID riid, void **ppv) { if (siid == __uuidof(IWMReader) && riid == IID_IUnknown) { IUnknown *punkCert; HRESULT hr = WMCreateCertificate( &punkCert ); if (SUCCEEDED(hr)) { *ppv = (void *) punkCert; } return hr; } return E_NOINTERFACE; } #endif // USE_WMF_CERT // ------------------------------------------------------------------------ // CErrorReporter methods // STDMETHODIMP CErrorReporter::LogError( long Severity, BSTR ErrorString, LONG ErrorCode, HRESULT hresult, VARIANT * pExtraInfo ) { USES_CONVERSION; printf( "Error %d (HRESULT %x)\r\n", ErrorCode, hresult); char * t = W2A( ErrorString ); printf( t ); printf( "\r\n" ); #ifdef DEBUG OutputDebugStringA( t ); OutputDebugString( TEXT("\r\n") ); #endif // DEBUG // look at variant // if( pExtraInfo ) { if( pExtraInfo->vt == VT_BSTR ) { printf( "Extra Info:%ls\r\n", pExtraInfo->bstrVal); } else if( pExtraInfo->vt == VT_I4 ) { printf( "Extra Info: %d\r\n", (int)pExtraInfo->lVal ); } else if( pExtraInfo->vt == VT_R8 ) { printf( "Extra Info: %d/100\r\n", (int)(V_R8(pExtraInfo) * 100)); } } return hresult; } #ifdef USE_WMF_CERT // list formats Windows Media Format SDK supports. // void ListWMSDKProfiles() { USES_CONVERSION; int wextent = 0 ; int Loop = 0 ; SIZE extent ; DWORD cProfiles = 0 ; printf("Standard system profiles:\n"); CComPtr pIWMProfileManager; HRESULT hr = WMCreateProfileManager( &pIWMProfileManager ); if( FAILED( hr ) ) { return; // error } IWMProfileManager2* pIPM2; hr = pIWMProfileManager->QueryInterface( IID_IWMProfileManager2, ( void ** )&pIPM2 ); if(FAILED(hr)) { return; } // we only use 7_0 profiles hr = pIPM2->SetSystemProfileVersion( WMT_VER_7_0 ); pIPM2->Release(); if(FAILED(hr)) { return; } hr = pIWMProfileManager->GetSystemProfileCount( &cProfiles ); if( FAILED( hr ) ) { return; } // // now load the profile strings // LRESULT ix; DWORD cchName, cchDescription; for (int i = 0; i < (int)cProfiles; ++i) { CComPtr pIWMProfile; hr = pIWMProfileManager->LoadSystemProfile( i, &pIWMProfile ); if( FAILED( hr ) ) return; hr = pIWMProfile->GetName( NULL, &cchName ); if( FAILED( hr ) ) return; WCHAR *wszProfile = new WCHAR[ cchName ]; if( NULL == wszProfile ) return; hr = pIWMProfile->GetName( wszProfile, &cchName ); if( FAILED( hr ) ) return; hr = pIWMProfile->GetDescription( NULL, &cchDescription ); if( FAILED( hr ) ) return; WCHAR *wszDescription = new WCHAR[ cchDescription ]; if( NULL == wszDescription ) return; hr = pIWMProfile->GetDescription( wszDescription, &cchDescription ); if( FAILED( hr ) ) return; printf(" %3d: %ls\n", i, wszProfile); delete[] wszProfile; delete[] wszDescription; } printf("\r\n Use /p [#] to choose the profile you want\r\n"); } #endif // USE_WMF_CERT // // helper function to return Nth input pin or output pin on a // filter. pin is addref'd // IPin * GetPin( IBaseFilter * pFilter, int PinNum, PIN_DIRECTION pd ) { CComPtr pEnum; HRESULT hr = pFilter->EnumPins( &pEnum ); if(SUCCEEDED(hr)) { ULONG cFetched; IPin * pPin; while(pEnum->Next( 1, &pPin, &cFetched) == S_OK) { PIN_DIRECTION pd2; pPin->QueryDirection( &pd2 ); if( pd2 == pd ) { if( PinNum == 0 ) { // return addref'd pin return pPin; } PinNum--; } pPin->Release(); } } return NULL; } // // Configure the graph to write to an AVI file (or ASF or .WAV) // HRESULT ConnectOutputFile( IRenderEngine * pEngine, WCHAR * Filename #ifdef USE_WMF_CERT , int iProfile #endif ) { CComPtr< ICaptureGraphBuilder2 > pBuilder; HRESULT hr = CoCreateInstance( CLSID_CaptureGraphBuilder2, NULL, CLSCTX_INPROC_SERVER, IID_ICaptureGraphBuilder2, (void**) &pBuilder ); if( FAILED( hr ) ) { return hr; } CComPtr< IAMTimeline > pTimeline; hr = pEngine->GetTimelineObject( &pTimeline ); if( FAILED( hr ) ) { return hr; } CComPtr< IGraphBuilder > pGraph; hr = pEngine->GetFilterGraph( &pGraph ); if( FAILED( hr ) ) { return hr; } long Groups = 0; pTimeline->GetGroupCount( &Groups ); if( !Groups ) { return E_INVALIDARG; } CComPtr< IBaseFilter > pMux; CComPtr< IFileSinkFilter > pWriter; hr = pBuilder->SetFiltergraph( pGraph ); if( FAILED( hr ) ) { return hr; } bool fConnectManually = false; GUID guid = MEDIASUBTYPE_Avi; // determine which writer to use based on file extension if (lstrlenW(Filename) > 4) { WCHAR *pExt = Filename + lstrlenW(Filename) - 3; #ifdef USE_WMF_CERT if(IsAsfExtension(pExt)) { guid = CLSID_WMAsfWriter; } #endif // USE_WMF_CERT if (lstrcmpiW(pExt, L"wav") == 0) { fConnectManually = true; // creation of the wav writer filter SDK sample // // .wav files need to be special-cased here because the // capture graph builder doesn't know about the wav writer // filter. // // {3C78B8E2-6C4D-11d1-ADE2-0000F8754B99} const CLSID CLSID_WavDest = { 0x3C78B8E2,0x6C4D,0x11D1,{0xAD,0xE2,0x00,0x00,0xF8,0x75,0x4B,0x99} }; hr = CoCreateInstance( CLSID_WavDest, NULL, CLSCTX_INPROC_SERVER, IID_IBaseFilter, (void**) &pMux ); if( SUCCEEDED( hr ) ) { hr = pGraph->AddFilter( pMux, L"Wave Mux" ); } if( SUCCEEDED( hr ) ) { hr = CoCreateInstance( CLSID_FileWriter, NULL, CLSCTX_INPROC_SERVER, IID_IFileSinkFilter, (void**) &pWriter ); } if( SUCCEEDED( hr ) ) { hr = pWriter->SetFileName( Filename, NULL ); } if( SUCCEEDED( hr ) ) { CComQIPtr< IBaseFilter, &IID_IBaseFilter > pWriterBase( pWriter ); hr = pGraph->AddFilter( pWriterBase, L"Writer" ); } } } if(FAILED(hr)) { return hr; } if(!fConnectManually) { hr = pBuilder->SetOutputFileName( &guid, Filename, &pMux, &pWriter ); } if( FAILED( hr ) ) { return hr; } CComQIPtr pConfigInterleaving(pMux); if(pConfigInterleaving) { pConfigInterleaving->put_Mode(INTERLEAVE_FULL); } CComQIPtr pCfgFw(pWriter); if(pCfgFw) { pCfgFw->SetMode(AM_FILE_OVERWRITE); } #ifdef USE_WMF_CERT CComQIPtr pConfigAsfWriter(pMux); if(pConfigAsfWriter && iProfile >= 0) { CComPtr pProfile; hr = MapProfileIdToProfile(iProfile, &pProfile); if(FAILED(hr)) { return hr; } // note that the ASF writer will not run if the number of streams // does not match the profile. hr = pConfigAsfWriter->ConfigureFilterUsingProfile(pProfile); if(FAILED(hr)) { return hr; } } #endif for( int g = 0 ; g < Groups ; g++ ) { CComPtr< IPin > pPin; hr = pEngine->GetGroupOutputPin( g, &pPin ); if( FAILED( hr ) ) { return hr; } // connect pin to the mux // hr = pBuilder->RenderStream( NULL, NULL, pPin, NULL, pMux ); if( FAILED( hr ) ) { return hr; } } if( fConnectManually ) { // connect the mux to the writer. the wav mux refuses to // connect its output pin until its input pin is // connected. Otherwise this could be done earlier. // CComQIPtr< IBaseFilter, &IID_IBaseFilter > pWriterBase( pWriter ); CComPtr pMuxOut, pWriterIn; pMuxOut.p = GetPin( pMux, 0, PINDIR_OUTPUT ); pWriterIn.p = GetPin( pWriterBase, 0, PINDIR_INPUT ); if(!pMuxOut || !pWriterIn) { return E_UNEXPECTED; } hr = pGraph->Connect( pMuxOut, pWriterIn ); } return hr; } BOOL IsAsfExtension( WCHAR *Filename ) { if (lstrlenW(Filename) >= 3) { WCHAR *pExt = Filename + lstrlenW(Filename) - 3; if(lstrcmpiW(pExt, L"asf") == 0 || lstrcmpiW(pExt, L"wma") == 0 || lstrcmpiW(pExt, L"wmv") == 0) { return TRUE; } } return FALSE; } // // Get the mediatype for a particular ASF profile. it does this using // the DirectShow components that use the WMSDK rather than through // the WMSDK directly. // #ifdef USE_WMF_CERT HRESULT GetDestinationASFFormat( AM_MEDIA_TYPE **ppmt, int iProfile ) { *ppmt = 0; CComPtr< IConfigAsfWriter > pWriter; HRESULT hr = CoCreateInstance( CLSID_WMAsfWriter, NULL, CLSCTX_INPROC_SERVER, IID_IConfigAsfWriter, (void **)&pWriter); if(FAILED(hr)) { return hr; } CComQIPtr pwf(pWriter); if(!pWriter) { return E_UNEXPECTED; } // we have to add the ASF writer to a dummy graph in order for the // calls to work. CComPtr< IGraphBuilder > pBuilder; hr = CoCreateInstance( CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER, IID_IGraphBuilder, (void**) &pBuilder ); if( FAILED( hr ) ) { return hr; } // // give the graph a key to support ASF // { CComQIPtr< IObjectWithSite, &IID_IObjectWithSite > pOWS( pBuilder ); ASSERT( pOWS ); if( pOWS ) { pOWS->SetSite((IUnknown *) (IServiceProvider *) &g_prov); } } hr = pBuilder->AddFilter(pwf, 0); if(FAILED(hr)) { return hr; } // we use profile id -1 to indicate no profile was set. if (iProfile >= 0) { CComPtr pProfile; hr = MapProfileIdToProfile(iProfile, &pProfile); if(FAILED(hr)) { return hr; } hr = pWriter->ConfigureFilterUsingProfile(pProfile); if(FAILED(hr)) { return hr; } } CComPtr pEnumPins; hr = pwf->EnumPins( &pEnumPins ); if(FAILED(hr)) { return hr; } // get destination video format from ASF writer using IAMStreamConfig::GetFormat on // its input pins int iPins = 0; ULONG ul = 0; CComPtr < IPin > pWriterInputPin; // we only expect to find input pins while( S_OK == pEnumPins->Next( 1, &pWriterInputPin, &ul ) ) { #ifdef DEBUG { PIN_DIRECTION pd; pWriterInputPin->QueryDirection( &pd ); ASSERT( PINDIR_OUTPUT != pd ); } #endif CComQIPtr pStreamConfig( pWriterInputPin); if( !pStreamConfig ) { return E_UNEXPECTED; } pWriterInputPin.Release(); AM_MEDIA_TYPE *pmt2; hr = pStreamConfig->GetFormat( &pmt2 ); if( SUCCEEDED( hr ) ) { if(pmt2->majortype != MEDIATYPE_Video) { DeleteMediaType(pmt2); continue; } *ppmt = pmt2; // caller needs to delete return S_OK; } } return E_FAIL; } HRESULT MapProfileIdToProfile( int iProfile, IWMProfile **ppProfile) { *ppProfile = 0; CComPtr pIWMProfileManager; HRESULT hr = WMCreateProfileManager( &pIWMProfileManager ); if(FAILED(hr)) { return hr; } // we only use 7_0 profiles CComQIPtr pIPM2(pIWMProfileManager); if(!pIWMProfileManager) { return E_UNEXPECTED; } hr = pIPM2->SetSystemProfileVersion( WMT_VER_7_0 ); if(FAILED(hr)) { return hr; } DWORD cProfiles; hr = pIWMProfileManager->GetSystemProfileCount( &cProfiles ); if(FAILED(hr)) { return hr; } if( (DWORD)iProfile >= cProfiles ) { printf("Invalid profile: %d\n", iProfile); return E_INVALIDARG; } return pIWMProfileManager->LoadSystemProfile( iProfile, ppProfile ); } #endif // USE_WMF_CERT