1107 lines
35 KiB
C++
1107 lines
35 KiB
C++
//*****************************************************************************
|
|
//
|
|
// Microsoft Windows Media
|
|
// Copyright (C) Microsoft Corporation. All rights reserved.
|
|
//
|
|
// FileName: DSCopy.cpp
|
|
//
|
|
// Abstract: Windows Media / DirectShow sample code
|
|
//
|
|
//*****************************************************************************
|
|
|
|
#include <windows.h>
|
|
#include <strmif.h>
|
|
#include <vfwmsgs.h>
|
|
#include <uuids.h>
|
|
#include "control.h"
|
|
#include "evcode.h"
|
|
|
|
#include <atlbase.h>
|
|
#include <stdio.h>
|
|
|
|
// Disable warning C4268, which is generated within <wmsdk.h>
|
|
#pragma warning(disable:4268)
|
|
#include <wmsdk.h>
|
|
#include <dshowasf.h>
|
|
#pragma warning(default:4268)
|
|
|
|
#pragma warning(disable: 4100)
|
|
|
|
//
|
|
// Constants
|
|
//
|
|
#define DEFAULT_PROFILE_VERSION WMT_VER_8_0
|
|
#define PROFILE_VERSION_NUM (g_WMTVersion >> 16)
|
|
|
|
// Unique string name for CreateEvent() (simply a GUID)
|
|
#define WMVCOPY_INDEX_EVENT TEXT("{78268A45-34B2-489a-838B-38833C949CBF}")
|
|
|
|
#define USAGE_STRING \
|
|
_T("Usage: DSCopy [/v] [/l] [/f] [/m] [/n ProfileVersion]\n") \
|
|
_T("/p ProfileNumber Source1 [Source2 ...] Target\n\n") \
|
|
_T("The following command-line switches are supported:\n") \
|
|
_T(" /v Verbose mode\n") \
|
|
_T(" /l Lists all available system profiles (versions 4,7,8)\n") \
|
|
_T(" /f Selects frame-based indexing (instead of temporal)\n") \
|
|
_T(" /m Enables multipass encoding\n") \
|
|
_T(" /n Selects a system profile version (4, 7, or 8)\n") \
|
|
_T(" /p Specifies the profile number\n\n") \
|
|
_T("Specify an ASF profile using the /p switch. If you omit this switch, ASFCopy \n") \
|
|
_T("displays a list of the standard system profiles and exits.\n\n") \
|
|
_T("Specify the name of one or more source files and the name of the target file. \n") \
|
|
_T("If you specify more than one source file, the application multiplexes all of \n") \
|
|
_T("the source files. You must specify a profile that matches the streams \n") \
|
|
_T("contained in the source files, or else the application will not work correctly. \n") \
|
|
_T("For example, if you specify Video for Web Servers (56 Kbps), the combined \n") \
|
|
_T("source files must have exactly one video stream and one audio stream.\n") \
|
|
|
|
//
|
|
// Macros
|
|
//
|
|
#ifndef NUMELMS
|
|
#define NUMELMS(aa) (sizeof(aa)/sizeof((aa)[0]))
|
|
#endif
|
|
|
|
//
|
|
// Global data
|
|
//
|
|
WMT_VERSION g_WMTVersion = DEFAULT_PROFILE_VERSION;
|
|
|
|
BOOL fVerbose = FALSE, fListProfiles = TRUE;
|
|
BOOL fListAllProfiles = FALSE, fFrameIndexing = FALSE, fMultipassEncode = FALSE;
|
|
|
|
//
|
|
// Function prototypes
|
|
//
|
|
void ListAllProfiles(void);
|
|
void ListProfiles(WMT_VERSION ProfileVersion);
|
|
LONG WaitForCompletion( IGraphBuilder *pGraph );
|
|
|
|
HRESULT CreateFilterGraph(IGraphBuilder **pGraph);
|
|
HRESULT CreateFilter(REFCLSID clsid, IBaseFilter **ppFilter);
|
|
HRESULT SetNoClock(IFilterGraph *pGraph);
|
|
HRESULT MapProfileIdToProfile(int iProfile, IWMProfile **ppProfile);
|
|
HRESULT IndexFileByFrames(__in LPWSTR wszTargetFile);
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////////////
|
|
// This class implements the methods of the IWMStatusCallback interface //
|
|
//////////////////////////////////////////////////////////////////////////////////
|
|
|
|
class CIndexCallback : public IWMStatusCallback
|
|
{
|
|
public:
|
|
CIndexCallback()
|
|
{
|
|
phr = NULL ;
|
|
hEvent = NULL;
|
|
}
|
|
|
|
~CIndexCallback(){}
|
|
|
|
virtual HRESULT STDMETHODCALLTYPE OnStatus(
|
|
/* [in] */ WMT_STATUS Status,
|
|
/* [in] */ HRESULT hr,
|
|
/* [in] */ WMT_ATTR_DATATYPE dwType,
|
|
/* [in] */ BYTE __RPC_FAR *pValue,
|
|
/* [in] */ void __RPC_FAR *pvContext)
|
|
{
|
|
switch ( Status )
|
|
{
|
|
case WMT_INDEX_PROGRESS:
|
|
// Display the indexing progress as a percentage.
|
|
// Use "carriage return" (\r) to reuse the status line.
|
|
_tprintf(_T("Indexing in progress (%d%%)\r"), *pValue);
|
|
break ;
|
|
|
|
case WMT_CLOSED:
|
|
*phr = hr;
|
|
SetEvent(hEvent) ;
|
|
_tprintf(_T("\n")); // Move to new line (past progress line)
|
|
break;
|
|
|
|
case WMT_ERROR:
|
|
*phr = hr;
|
|
SetEvent(hEvent) ;
|
|
_tprintf(_T("\nError during indexing operation! hr=0x%x\n"), hr);
|
|
break;
|
|
|
|
// Ignore these messages
|
|
case WMT_OPENED:
|
|
case WMT_STARTED:
|
|
case WMT_STOPPED:
|
|
break;
|
|
}
|
|
return S_OK;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Implementation of IUnknown methods
|
|
//------------------------------------------------------------------------------
|
|
ULONG STDMETHODCALLTYPE AddRef( void )
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
ULONG STDMETHODCALLTYPE Release( void )
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
HRESULT STDMETHODCALLTYPE QueryInterface(
|
|
/* [in] */ REFIID riid,
|
|
/* [iid_is][out] */ void __RPC_FAR *__RPC_FAR *ppvObject)
|
|
{
|
|
if ( riid == IID_IWMStatusCallback )
|
|
{
|
|
*ppvObject = ( IWMStatusCallback * )this;
|
|
}
|
|
else
|
|
{
|
|
*ppvObject = NULL;
|
|
return E_NOINTERFACE;
|
|
}
|
|
return S_OK;
|
|
}
|
|
|
|
public:
|
|
HANDLE hEvent ;
|
|
HRESULT *phr ;
|
|
};
|
|
|
|
//////////////////////////////////////////////////////////////////////////////////
|
|
// End callback class
|
|
//////////////////////////////////////////////////////////////////////////////////
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Name: ListAllProfiles()
|
|
// Desc: Displays profiles for all versions.
|
|
//------------------------------------------------------------------------------
|
|
void ListAllProfiles(void)
|
|
{
|
|
WMT_VERSION ver[3] = { WMT_VER_4_0, WMT_VER_7_0, WMT_VER_8_0};
|
|
|
|
// List all system profiles supported for each Windows Media version
|
|
for (int i=0; i < 3; i++)
|
|
{
|
|
ListProfiles(ver[i]);
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Name: ListProfiles()
|
|
// Desc: Displays profiles for a specified version.
|
|
//------------------------------------------------------------------------------
|
|
void ListProfiles(WMT_VERSION ProfileVersion)
|
|
{
|
|
USES_CONVERSION;
|
|
|
|
DWORD cProfiles = 0;
|
|
DWORD cchName, cchDescription;
|
|
CComPtr <IWMProfileManager> pIWMProfileManager;
|
|
|
|
// Create a profile manager object
|
|
HRESULT hr = WMCreateProfileManager(&pIWMProfileManager);
|
|
if(FAILED(hr))
|
|
{
|
|
_tprintf(_T("ListProfiles: Failed to create profile manager! hr=0x%x\n"), hr);
|
|
return;
|
|
}
|
|
|
|
CComQIPtr<IWMProfileManager2, &IID_IWMProfileManager2> pIPM2(pIWMProfileManager);
|
|
if(!pIPM2)
|
|
{
|
|
_tprintf(_T("ListProfiles: Failed to QI IWMProfileManager2! hr=0x%x\n"), hr);
|
|
return;
|
|
}
|
|
|
|
// Set to the requested system profile version
|
|
hr = pIPM2->SetSystemProfileVersion( ProfileVersion );
|
|
if(FAILED(hr))
|
|
{
|
|
_tprintf(_T("ListProfiles: Failed to set system profile version! hr=0x%x\n"), hr);
|
|
return;
|
|
}
|
|
|
|
// Read back the current version to verify and save it to global variable
|
|
hr = pIPM2->GetSystemProfileVersion( &g_WMTVersion );
|
|
if(FAILED(hr))
|
|
{
|
|
_tprintf(_T("ListProfiles: Failed to set system profile version! hr=0x%x\n"), hr);
|
|
return;
|
|
}
|
|
|
|
// How many system profiles exist for this version?
|
|
hr = pIWMProfileManager->GetSystemProfileCount(&cProfiles);
|
|
if(FAILED(hr))
|
|
{
|
|
_tprintf(_T("ListProfiles: Failed to read system profile count! hr=0x%x\n"), hr);
|
|
return;
|
|
}
|
|
else
|
|
_tprintf(_T("There are %d system profiles available for version %d.\n"),
|
|
cProfiles, g_WMTVersion >> 16);
|
|
|
|
// Load the profile strings
|
|
for(int i = 0; i < (int) cProfiles; ++i)
|
|
{
|
|
CComPtr <IWMProfile> pIWMProfile;
|
|
|
|
hr = pIWMProfileManager->LoadSystemProfile(i, &pIWMProfile);
|
|
if(FAILED(hr))
|
|
{
|
|
_tprintf(_T("ListProfiles: Failed to load system profile! hr=0x%x\n"), hr);
|
|
return;
|
|
}
|
|
|
|
// How large is the profile name?
|
|
hr = pIWMProfile->GetName(NULL, &cchName);
|
|
if(FAILED(hr))
|
|
{
|
|
_tprintf(_T("ListProfiles: Failed to read profile name size! hr=0x%x\n"), hr);
|
|
return;
|
|
}
|
|
|
|
// Allocate a string to hold the profile name
|
|
WCHAR *wszProfile = new WCHAR[ cchName ];
|
|
if(NULL == wszProfile)
|
|
return;
|
|
|
|
// Read the profile name into the newly allocated string
|
|
hr = pIWMProfile->GetName(wszProfile, &cchName);
|
|
if(FAILED(hr))
|
|
{
|
|
_tprintf(_T("ListProfiles: Failed to read profile name! hr=0x%x\n"), hr);
|
|
delete [] wszProfile;
|
|
return;
|
|
}
|
|
|
|
if (fVerbose)
|
|
{
|
|
// How large is the description?
|
|
hr = pIWMProfile->GetDescription(NULL, &cchDescription);
|
|
if(FAILED(hr))
|
|
{
|
|
_tprintf(_T("ListProfiles: Failed to read profile description size! hr=0x%x\n"), hr);
|
|
delete [] wszProfile;
|
|
return;
|
|
}
|
|
|
|
// Allocate a string to hold the profile description
|
|
WCHAR *wszDescription = new WCHAR[ cchDescription ];
|
|
if(NULL == wszDescription)
|
|
{
|
|
delete [] wszProfile;
|
|
return;
|
|
}
|
|
|
|
// Read the description into the newly allocated string
|
|
hr = pIWMProfile->GetDescription(wszDescription, &cchDescription);
|
|
if(FAILED(hr))
|
|
{
|
|
_tprintf(_T("ListProfiles: Failed to read profile description! hr=0x%x\n"), hr);
|
|
delete [] wszProfile;
|
|
delete [] wszDescription;
|
|
return;
|
|
}
|
|
|
|
// Display the profile name and description
|
|
_tprintf(_T(" %3d: %ls \n[%ls]\n\n"), i, wszProfile, wszDescription);
|
|
delete[] wszDescription;
|
|
}
|
|
|
|
// Not verbose mode, so display only the profile's name
|
|
else
|
|
_tprintf(_T(" %3d: %ls\n"), i, wszProfile);
|
|
|
|
delete[] wszProfile;
|
|
}
|
|
|
|
_tprintf(_T("\n"));
|
|
}
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Name: CreateFilterGraph()
|
|
// Desc: Create a DirectShow filter graph.
|
|
//------------------------------------------------------------------------------
|
|
HRESULT CreateFilterGraph(IGraphBuilder **pGraph)
|
|
{
|
|
HRESULT hr;
|
|
|
|
if (!pGraph)
|
|
return E_POINTER;
|
|
|
|
hr = CoCreateInstance(CLSID_FilterGraph, // get the graph object
|
|
NULL,
|
|
CLSCTX_INPROC_SERVER,
|
|
IID_IGraphBuilder,
|
|
(void **) pGraph);
|
|
|
|
if(FAILED(hr))
|
|
{
|
|
_tprintf(_T("CreateFilterGraph: Failed to create graph! hr=0x%x\n"), hr);
|
|
*pGraph = NULL;
|
|
return hr;
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Name: CreateFilter()
|
|
// Desc: Create a DirectShow filter.
|
|
//------------------------------------------------------------------------------
|
|
HRESULT CreateFilter(REFCLSID clsid, IBaseFilter **ppFilter)
|
|
{
|
|
HRESULT hr;
|
|
|
|
if (!ppFilter)
|
|
return E_POINTER;
|
|
|
|
hr = CoCreateInstance(clsid,
|
|
NULL,
|
|
CLSCTX_INPROC_SERVER,
|
|
IID_IBaseFilter,
|
|
(void **) ppFilter);
|
|
|
|
if(FAILED(hr))
|
|
{
|
|
_tprintf(_T("CreateFilter: Failed to create filter! hr=0x%x\n"), hr);
|
|
*ppFilter = NULL;
|
|
return hr;
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Name: SetNoClock()
|
|
// Desc: Prevents an unnecessary clock from being created.
|
|
// This speeds up the copying process, since the renderer won't wait
|
|
// for the proper time to render a sample; instead, the data will
|
|
// be processed as fast as possible.
|
|
//------------------------------------------------------------------------------
|
|
HRESULT SetNoClock(IFilterGraph *pGraph)
|
|
{
|
|
if (!pGraph)
|
|
return E_POINTER;
|
|
|
|
IMediaFilter *pFilter=NULL;
|
|
HRESULT hr = pGraph->QueryInterface(IID_IMediaFilter, (void **) &pFilter);
|
|
|
|
if(SUCCEEDED(hr))
|
|
{
|
|
// Set to "no clock"
|
|
hr = pFilter->SetSyncSource(NULL);
|
|
if (FAILED(hr))
|
|
_tprintf(_T("SetNoClock: Failed to set sync source! hr=0x%x\n"), hr);
|
|
|
|
pFilter->Release();
|
|
}
|
|
else
|
|
{
|
|
_tprintf(_T("SetNoClock: Failed to QI for media filter! hr=0x%x\n"), hr);
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Name: MapProfileIdToProfile()
|
|
// Desc: Retrieves a specified profile.
|
|
//------------------------------------------------------------------------------
|
|
HRESULT MapProfileIdToProfile(int iProfile, IWMProfile **ppProfile)
|
|
{
|
|
DWORD cProfiles;
|
|
|
|
if (!ppProfile)
|
|
return E_POINTER;
|
|
|
|
*ppProfile = 0;
|
|
|
|
CComPtr <IWMProfileManager> pIWMProfileManager;
|
|
HRESULT hr = WMCreateProfileManager( &pIWMProfileManager );
|
|
if(FAILED(hr))
|
|
{
|
|
_tprintf(_T("MapProfile: Failed to create profile manager! hr=0x%x\n"), hr);
|
|
return hr;
|
|
}
|
|
|
|
CComQIPtr<IWMProfileManager2, &IID_IWMProfileManager2> pIPM2(pIWMProfileManager);
|
|
if(!pIPM2)
|
|
{
|
|
_tprintf(_T("MapProfile: Failed to QI IWMProfileManager2!\n"));
|
|
return E_UNEXPECTED;
|
|
}
|
|
|
|
// Set the system profile version (4, 7, 8)
|
|
hr = pIPM2->SetSystemProfileVersion( g_WMTVersion );
|
|
if(FAILED(hr))
|
|
{
|
|
_tprintf(_T("MapProfile: Failed to set system profile version! hr=0x%x\n"), hr);
|
|
return hr;
|
|
}
|
|
|
|
// Read back the current version to verify
|
|
hr = pIPM2->GetSystemProfileVersion( &g_WMTVersion );
|
|
if(FAILED(hr))
|
|
{
|
|
_tprintf(_T("ListProfiles: Failed to read system profile version! hr=0x%x\n"), hr);
|
|
return hr;
|
|
}
|
|
|
|
// How many profiles exist for this version?
|
|
hr = pIWMProfileManager->GetSystemProfileCount( &cProfiles );
|
|
if(FAILED(hr))
|
|
{
|
|
_tprintf(_T("MapProfile: Failed to get system profile count! hr=0x%x\n"), hr);
|
|
return hr;
|
|
}
|
|
|
|
// Invalid profile requested?
|
|
if( (DWORD)iProfile >= cProfiles )
|
|
{
|
|
_tprintf(_T("Invalid profile: %d\n"), iProfile);
|
|
return E_INVALIDARG;
|
|
}
|
|
|
|
// Load the requested systme profile and return its interface
|
|
// in the ppProfile parameter
|
|
return (pIWMProfileManager->LoadSystemProfile( iProfile, ppProfile ));
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Name: FindPinOnFilter()
|
|
// Desc: Retrieves a specified pin.
|
|
//------------------------------------------------------------------------------
|
|
HRESULT FindPinOnFilter( IBaseFilter * pFilter, PIN_DIRECTION PinDir,
|
|
DWORD dwPin, BOOL fConnected, IPin ** ppPin )
|
|
{
|
|
HRESULT hr = S_OK;
|
|
IEnumPins * pEnumPin = NULL;
|
|
IPin * pConnectedPin = NULL;
|
|
PIN_DIRECTION PinDirection;
|
|
ULONG ulFetched;
|
|
DWORD nFound = 0;
|
|
|
|
if (!pFilter || !ppPin)
|
|
return E_POINTER;
|
|
|
|
*ppPin = NULL;
|
|
|
|
// Get a pin enumerator for the filter's pins
|
|
hr = pFilter->EnumPins( &pEnumPin );
|
|
if(SUCCEEDED(hr))
|
|
{
|
|
// Explicitly check for S_OK instead of SUCCEEDED, since IEnumPins::Next()
|
|
// will return S_FALSE if it does not retrieve as many pins as requested.
|
|
while ( S_OK == ( hr = pEnumPin->Next( 1L, ppPin, &ulFetched ) ) )
|
|
{
|
|
hr = (*ppPin)->ConnectedTo( &pConnectedPin );
|
|
if (pConnectedPin)
|
|
{
|
|
pConnectedPin->Release();
|
|
pConnectedPin = NULL;
|
|
}
|
|
|
|
if ( ( ( VFW_E_NOT_CONNECTED == hr ) && !fConnected ) ||
|
|
( ( SUCCEEDED(hr) ) && fConnected ) )
|
|
{
|
|
hr = (*ppPin)->QueryDirection( &PinDirection );
|
|
if ( ( SUCCEEDED(hr) ) && ( PinDirection == PinDir ) )
|
|
{
|
|
if ( nFound == dwPin )
|
|
break;
|
|
|
|
nFound++;
|
|
}
|
|
}
|
|
|
|
(*ppPin)->Release();
|
|
}
|
|
|
|
// Release enumerator
|
|
pEnumPin->Release();
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Name: WaitForCompletion()
|
|
// Desc: Waits for a media event that signifies completion or cancellation
|
|
// of a task.
|
|
//------------------------------------------------------------------------------
|
|
LONG WaitForCompletion( IGraphBuilder *pGraph )
|
|
{
|
|
HRESULT hr;
|
|
LONG levCode = 0;
|
|
CComPtr <IMediaEvent> pME;
|
|
|
|
if (!pGraph)
|
|
return -1;
|
|
|
|
hr = pGraph->QueryInterface(IID_IMediaEvent, (void **) &pME);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
_tprintf(_T("Waiting for completion...\n This could take several minutes, ")
|
|
_T("depending on file size and selected profile.\n"));
|
|
HANDLE hEvent;
|
|
|
|
hr = pME->GetEventHandle((OAEVENT *)&hEvent);
|
|
if(SUCCEEDED(hr))
|
|
{
|
|
// 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_PTR lp1, lp2;
|
|
|
|
if(pME->GetEvent(&levCode, &lp1, &lp2, 0) == S_OK)
|
|
{
|
|
pME->FreeEventParams(levCode, lp1, lp2);
|
|
|
|
if(EC_COMPLETE == levCode)
|
|
{
|
|
// Display received event information
|
|
if (fVerbose)
|
|
{
|
|
_tprintf(_T("WaitForCompletion: Received EC_COMPLETE.\n"));
|
|
}
|
|
break;
|
|
}
|
|
else if(EC_ERRORABORT == levCode)
|
|
{
|
|
if (fVerbose)
|
|
{
|
|
_tprintf(_T("WaitForCompletion: Received EC_ERRORABORT.\n"));
|
|
}
|
|
break;
|
|
}
|
|
else if(EC_USERABORT == levCode)
|
|
{
|
|
if (fVerbose)
|
|
{
|
|
_tprintf(_T("WaitForCompletion: Received EC_USERABORT.\n"));
|
|
}
|
|
break;
|
|
}
|
|
else if( EC_PREPROCESS_COMPLETE == levCode)
|
|
{
|
|
if (fVerbose)
|
|
{
|
|
_tprintf(_T("WaitForCompletion: Received EC_PREPROCESS_COMPLETE.\n"));
|
|
}
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
if (fVerbose)
|
|
{
|
|
_tprintf(_T("WaitForCompletion: Received event %d.\n"), levCode);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
_tprintf(_T("Unexpected failure (GetEventHandle failed)...\n"));
|
|
}
|
|
}
|
|
else
|
|
_tprintf(_T("QI failed for IMediaEvent interface!\n"));
|
|
|
|
return levCode;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Name: IndexFileByFrames()
|
|
// Desc: Index the file.
|
|
//------------------------------------------------------------------------------
|
|
HRESULT IndexFileByFrames(__in LPWSTR wszTargetFile)
|
|
{
|
|
HRESULT hr;
|
|
IWMIndexer *pIndexer;
|
|
|
|
hr = WMCreateIndexer(&pIndexer);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
// Get an IWMIndexer2 interface to configure for frame indexing
|
|
CComQIPtr<IWMIndexer2, &IID_IWMIndexer2> pIndexer2(pIndexer);
|
|
|
|
if(!pIndexer2)
|
|
{
|
|
_tprintf(_T("CopyASF: Failed to QI for IWMIndexer2! hr=0x%x\n"), hr);
|
|
return hr;
|
|
}
|
|
|
|
// Configure for frame-based indexing
|
|
WORD wIndexType = WMT_IT_NEAREST_CLEAN_POINT;
|
|
|
|
hr = pIndexer2->Configure(0, WMT_IT_FRAME_NUMBERS, NULL,
|
|
&wIndexType);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
HANDLE hIndexEvent = CreateEvent( NULL, FALSE, FALSE, WMVCOPY_INDEX_EVENT );
|
|
if ( NULL == hIndexEvent )
|
|
{
|
|
_tprintf(_T("Failed to create index event!\n"));
|
|
return E_FAIL;
|
|
}
|
|
|
|
// Create and configure a callback object
|
|
HRESULT hrIndex = S_OK;
|
|
|
|
CIndexCallback callbackIndex;
|
|
callbackIndex.hEvent = hIndexEvent;
|
|
callbackIndex.phr = &hrIndex;
|
|
|
|
if (fVerbose)
|
|
_tprintf(_T("\nStarting the frame indexing process.\n"));
|
|
|
|
hr = pIndexer->StartIndexing(wszTargetFile, &callbackIndex, NULL);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
// Wait for indexing operation to complete
|
|
WaitForSingleObject( hIndexEvent, INFINITE );
|
|
if ( FAILED( hrIndex ) )
|
|
{
|
|
_tprintf(_T("Indexing Failed (hr=0x%08x)!\n"), hrIndex );
|
|
return hr;
|
|
}
|
|
else
|
|
_tprintf(_T("Frame indexing completed.\n"));
|
|
}
|
|
else
|
|
{
|
|
_tprintf(_T("StartIndexing failed (hr=0x%08x)!\n"), hr);
|
|
return hr;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
_tprintf(_T("Failed to configure frame indexer! hr=0x%x\n"), hr);
|
|
return hr;
|
|
}
|
|
}
|
|
|
|
if (pIndexer)
|
|
pIndexer->Release();
|
|
|
|
return hr;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Name: CopyASF()
|
|
// Desc: Play the file through a DirectShow filter graph.
|
|
//------------------------------------------------------------------------------
|
|
HRESULT CopyASF(int argc, __in_ecount(argc) LPTSTR argv[])
|
|
{
|
|
HRESULT hr;
|
|
WCHAR wszSourceFile[MAX_PATH];
|
|
WCHAR wszTargetFile[MAX_PATH];
|
|
DWORD dwRequestedProfile=0;
|
|
DWORD dwRequestedVersion;
|
|
int i = 1;
|
|
|
|
//
|
|
// Parse command line options
|
|
//
|
|
while(i < argc && (argv[i][0] == _T('-') || argv[i][0] == _T('/')))
|
|
{
|
|
// Verbose mode (to display profile descriptions and detailed output)
|
|
if(lstrcmpi(argv[i] + 1, _T("v")) == 0)
|
|
{
|
|
fVerbose = TRUE;
|
|
_tprintf(_T("Verbose mode enabled.\n"));
|
|
}
|
|
|
|
// List all profiles (all profile versions) and exit the loop
|
|
else if(lstrcmpi(argv[i] + 1, _T("l")) == 0)
|
|
{
|
|
fListAllProfiles = TRUE;
|
|
break;
|
|
}
|
|
|
|
// Select frame-based indexing (instead of the default
|
|
// temporal [time-based] indexing)
|
|
else if(lstrcmpi(argv[i] + 1, _T("f")) == 0)
|
|
{
|
|
fFrameIndexing = TRUE;
|
|
_tprintf(_T("Requesting frame-based indexing.\n"));
|
|
}
|
|
|
|
else if(lstrcmpi(argv[i] + 1, _T("m")) == 0)
|
|
{
|
|
fMultipassEncode = TRUE;
|
|
_tprintf(_T("Requesting multipass encoding.\n"));
|
|
}
|
|
|
|
// Select a system profile
|
|
else if((i+1 < argc) && lstrcmpi(argv[i] + 1, _T("p")) == 0)
|
|
{
|
|
fListProfiles = FALSE;
|
|
dwRequestedProfile = _ttoi(argv[i+1]);
|
|
_tprintf(_T("Requesting profile #%d.\n"), dwRequestedProfile);
|
|
i++; // skip two args here
|
|
}
|
|
|
|
// Select a system profile version
|
|
else if((i+1 < argc) && lstrcmpi(argv[i] + 1, _T("n")) == 0)
|
|
{
|
|
dwRequestedVersion = _ttoi(argv[i+1]);
|
|
|
|
// If this is a valid request, save the version number
|
|
if (dwRequestedVersion == 4 || dwRequestedVersion == 7 ||
|
|
dwRequestedVersion == 8)
|
|
{
|
|
_tprintf(_T("Requesting Windows Media System Profile version %d.\n"),
|
|
dwRequestedVersion);
|
|
g_WMTVersion = (WMT_VERSION) (dwRequestedVersion << 16);
|
|
}
|
|
else
|
|
{
|
|
_tprintf(_T("Invalid profile version. Only 4, 7, and 8 are valid.\n"));
|
|
_tprintf(_T("%s"), USAGE_STRING);
|
|
return -1;
|
|
}
|
|
|
|
i++; // skip two args here
|
|
}
|
|
|
|
i++;
|
|
}
|
|
|
|
// List all system profiles (all supported versions)?
|
|
if(fListAllProfiles)
|
|
{
|
|
_tprintf(_T("%s"), USAGE_STRING);
|
|
ListAllProfiles();
|
|
return -1;
|
|
}
|
|
|
|
// List system profiles only (then exit)?
|
|
if(fListProfiles)
|
|
{
|
|
_tprintf(_T("%s"), USAGE_STRING);
|
|
ListProfiles(g_WMTVersion);
|
|
return -1;
|
|
}
|
|
|
|
// Fail with usage information if improper number of arguments
|
|
if(argc < i+2)
|
|
{
|
|
_tprintf(_T("%s"), USAGE_STRING);
|
|
return -1;
|
|
}
|
|
|
|
|
|
//
|
|
// Command-line processing is complete.
|
|
// Create the interfaces needed to copy and configure the ASF file.
|
|
//
|
|
|
|
CComPtr <IGraphBuilder> pGraph;
|
|
CComPtr <IFileSinkFilter> pFileSink;
|
|
CComPtr <IBaseFilter> pASFWriter;
|
|
CComPtr <IConfigAsfWriter> pConfigAsfWriter;
|
|
CComPtr <IMediaControl> pMC;
|
|
|
|
// Convert target filename to a wide character string
|
|
#ifndef UNICODE
|
|
MultiByteToWideChar(CP_ACP, 0, argv[argc - 1], -1,
|
|
wszTargetFile, NUMELMS(wszTargetFile));
|
|
#else
|
|
wcsncpy_s(wszTargetFile, MAX_PATH, argv[argc-1], NUMELMS(wszTargetFile));
|
|
#endif
|
|
|
|
// Create an empty DirectShow filter graph
|
|
hr = CreateFilterGraph(&pGraph);
|
|
if(FAILED(hr))
|
|
{
|
|
_tprintf(_T("Couldn't create filter graph! hr=0x%x"), hr);
|
|
return hr;
|
|
}
|
|
|
|
// Add the ASF Writer filter to the graph
|
|
hr = CreateFilter(CLSID_WMAsfWriter, &pASFWriter);
|
|
if(FAILED(hr))
|
|
{
|
|
_tprintf(_T("Failed to create WMAsfWriter filter! hr=0x%x\n"), hr);
|
|
return hr;
|
|
}
|
|
|
|
// Get a file sink filter interface from the ASF Writer filter
|
|
hr = pASFWriter->QueryInterface(IID_IFileSinkFilter, (void **) &pFileSink);
|
|
if(FAILED(hr))
|
|
{
|
|
_tprintf(_T("Failed to create QI IFileSinkFilter! hr=0x%x\n"), hr);
|
|
return hr;
|
|
}
|
|
|
|
// Set the target file name (from command line user input)
|
|
hr = pFileSink->SetFileName(wszTargetFile, NULL);
|
|
if(FAILED(hr))
|
|
{
|
|
_tprintf(_T("Failed to set target filename! hr=0x%x\n"), hr);
|
|
return hr;
|
|
}
|
|
|
|
// Add the MUX filter (ASF writer) to the graph
|
|
hr = pGraph->AddFilter(pASFWriter, L"Mux");
|
|
if(FAILED(hr))
|
|
{
|
|
_tprintf(_T("Failed to add Mux filter to graph! hr=0x%x\n"), hr);
|
|
return hr;
|
|
}
|
|
|
|
//
|
|
// Load and configure a Windows Media Profile
|
|
//
|
|
// We should only require a profile if we're using a filter which needs it
|
|
hr = pASFWriter->QueryInterface(IID_IConfigAsfWriter, (void **) &pConfigAsfWriter);
|
|
if(SUCCEEDED(hr))
|
|
{
|
|
if (fVerbose)
|
|
_tprintf(_T("Setting profile to %d\r\n"), dwRequestedProfile);
|
|
|
|
CComPtr<IWMProfile> pProfile;
|
|
|
|
// Convert the numerical profile index into a IWMProfile interface pointer
|
|
hr = MapProfileIdToProfile(dwRequestedProfile, &pProfile);
|
|
if(FAILED(hr)) {
|
|
_tprintf(_T("Failed to map profile ID! hr=0x%x\n"), 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)) {
|
|
_tprintf(_T("Failed to configure filter to use profile! hr=0x%x\n"), hr);
|
|
return hr;
|
|
}
|
|
|
|
// If frame-based indexing was requested, disable the default
|
|
// time-based (temporal) indexing
|
|
if (fFrameIndexing)
|
|
{
|
|
hr = pConfigAsfWriter->SetIndexMode(FALSE);
|
|
if(FAILED(hr)) {
|
|
_tprintf(_T("Failed to disable time-based indexing! hr=0x%x\n"), hr);
|
|
return hr;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
_tprintf(_T("Failed to QI for IConfigAsfWriter! hr=0x%x\n"), hr);
|
|
return hr;
|
|
}
|
|
|
|
// Set sync source to NULL to speed processing
|
|
SetNoClock(pGraph);
|
|
|
|
_tprintf(_T("Using version %d system profiles.\n"), PROFILE_VERSION_NUM);
|
|
|
|
// Enable multipass encoding if requested
|
|
CComQIPtr<IConfigAsfWriter2, &IID_IConfigAsfWriter2> pConfigAsfWriter2(pASFWriter);
|
|
CComQIPtr<IWMWriterPreprocess, &IID_IWMWriterPreprocess> pPreprocess(pASFWriter);
|
|
|
|
if (fMultipassEncode)
|
|
{
|
|
// Verify that QIs were successful
|
|
if(!pConfigAsfWriter2)
|
|
{
|
|
_tprintf(_T("ListProfiles: Failed to QI IConfigAsfWriter2! hr=0x%x\n"), hr);
|
|
return E_FAIL;
|
|
}
|
|
if(!pPreprocess)
|
|
{
|
|
_tprintf(_T("ListProfiles: Failed to QI IWMWriterPreprocess! hr=0x%x\n"), hr);
|
|
return E_FAIL;
|
|
}
|
|
|
|
// Enable multipass encoding
|
|
hr = pConfigAsfWriter2->SetParam(AM_CONFIGASFWRITER_PARAM_MULTIPASS, TRUE, 0);
|
|
if (FAILED(hr))
|
|
{
|
|
_tprintf(_T("Failed to enable multipass encoding param! hr=0x%x\n"), hr);
|
|
return hr;
|
|
}
|
|
|
|
// Read back for debugging purposes
|
|
DWORD dwSetting;
|
|
hr = pConfigAsfWriter2->GetParam(AM_CONFIGASFWRITER_PARAM_MULTIPASS, &dwSetting, 0);
|
|
if (FAILED(hr))
|
|
{
|
|
_tprintf(_T("Failed to read multipass encoding param! hr=0x%x\n"), hr);
|
|
return hr;
|
|
}
|
|
}
|
|
|
|
// Render all source files listed on the command line
|
|
while(i < argc - 1)
|
|
{
|
|
#ifndef UNICODE
|
|
MultiByteToWideChar(CP_ACP, 0, argv[i], -1, wszSourceFile, NUMELMS(wszSourceFile));
|
|
#else
|
|
wcsncpy_s(wszSourceFile, MAX_PATH, argv[i], NUMELMS(wszSourceFile));
|
|
#endif
|
|
|
|
_tprintf(_T("\nCopying [%ls] to [%ls] with profile #%d\n"),
|
|
wszSourceFile, wszTargetFile, dwRequestedProfile);
|
|
|
|
// Let DirectShow render the source file
|
|
hr = pGraph->RenderFile(wszSourceFile, NULL);
|
|
|
|
if(FAILED(hr))
|
|
{
|
|
_tprintf(_T("Failed to render source file %s! hr=0x%x\n"), argv[i], hr);
|
|
return hr;
|
|
}
|
|
else if (fVerbose)
|
|
_tprintf(_T("RenderFile('%ls') returned hr=0x%x\n"), wszSourceFile, hr);
|
|
|
|
++i;
|
|
}
|
|
|
|
// Get the media control interface to control the graph
|
|
hr = pGraph->QueryInterface(IID_IMediaControl, (void **) &pMC);
|
|
if(FAILED(hr))
|
|
{
|
|
_tprintf(_T("Failed to QI for IMediaControl! hr=0x%x\n"), hr);
|
|
return hr;
|
|
}
|
|
|
|
// Run the graph
|
|
hr = pMC->Run();
|
|
|
|
if(SUCCEEDED(hr))
|
|
{
|
|
// How many passes can we make?
|
|
DWORD dwMaxPasses=1;
|
|
|
|
hr = pPreprocess->GetMaxPreprocessingPasses(0, 0, &dwMaxPasses);
|
|
if (SUCCEEDED(hr))
|
|
_tprintf(_T("Max supported preprocessing passes: %d\n"), dwMaxPasses);
|
|
|
|
|
|
// Wait for the copy operation to complete
|
|
int nEvent = WaitForCompletion(pGraph);
|
|
|
|
// Stop the graph
|
|
hr = pMC->Stop();
|
|
if (FAILED(hr))
|
|
_tprintf(_T("Failed to stop filter graph! hr=0x%x\n"), hr);
|
|
|
|
if (fMultipassEncode && (nEvent != EC_PREPROCESS_COMPLETE))
|
|
{
|
|
_tprintf(_T("ERROR: Failed to recieve expected EC_PREPROCESSCOMPLETE.\n"));
|
|
return E_FAIL;
|
|
}
|
|
|
|
// If we're using multipass encode, run again
|
|
if (fMultipassEncode)
|
|
{
|
|
_tprintf(_T("Preprocessing complete.\n"));
|
|
|
|
// Seek to beginning of file for actual encoding pass
|
|
CComQIPtr<IMediaSeeking, &IID_IMediaSeeking> pMS(pMC);
|
|
if (pMS)
|
|
{
|
|
LONGLONG pos=0;
|
|
|
|
hr = pMS->SetPositions(&pos, AM_SEEKING_AbsolutePositioning ,
|
|
NULL, AM_SEEKING_NoPositioning);
|
|
|
|
// Now that preprocessing is done, write the file normally
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = pMC->Run();
|
|
}
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
WaitForCompletion(pGraph);
|
|
|
|
hr = pMC->Stop();
|
|
if (FAILED(hr))
|
|
_tprintf(_T("Failed to stop filter graph after completion! hr=0x%x\n"), hr);
|
|
|
|
_tprintf(_T("Copy complete.\n"));
|
|
}
|
|
else
|
|
_tprintf(_T("Failed to run the graph! hr=0x%x\n"), hr);
|
|
}
|
|
else
|
|
{
|
|
_tprintf(_T("Failed to QI for IMediaSeeking!\n"));
|
|
}
|
|
|
|
// Turn off multipass encoding
|
|
hr = pConfigAsfWriter2->SetParam(AM_CONFIGASFWRITER_PARAM_MULTIPASS, FALSE, 0);
|
|
if (FAILED(hr))
|
|
{
|
|
_tprintf(_T("Failed to disable multipass encoding! hr=0x%x\n"), hr);
|
|
return hr;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
_tprintf(_T("Failed to run the graph! hr=0x%x\nCopy aborted.\n\n"), hr);
|
|
_tprintf(_T("Please check that you have selected the correct profile for copying.\n")
|
|
_T("Note that if your source ASF file is audio-only, then selecting a\n")
|
|
_T("video profile will cause a failure when running the graph.\n\n"));
|
|
}
|
|
|
|
// If frame-based indexing was requested, this must be performed
|
|
// manually after the file is created
|
|
if (fFrameIndexing)
|
|
{
|
|
IndexFileByFrames(wszTargetFile);
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
|
|
int __cdecl _tmain(int argc, __in_ecount(argc) LPTSTR argv[])
|
|
{
|
|
USES_CONVERSION;
|
|
|
|
// Initialize COM
|
|
HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
|
|
if (FAILED(hr))
|
|
return hr;
|
|
|
|
// Since COM smart pointers are used, the main functionality is wrapped
|
|
// in CopyASF(). When the function returns, the smart pointers will clean
|
|
// up properly, and then we'll uninitialize COM.
|
|
hr = CopyASF(argc, argv);
|
|
|
|
CoUninitialize();
|
|
return hr;
|
|
}
|
|
|