//***************************************************************************** // // Microsoft Windows Media // Copyright (C) Microsoft Corporation. All rights reserved. // // FileName: DSSeekFm.cpp // // Abstract: Windows Media / DirectShow sample code // This sample shows how to seek by frame number when using // DirectShow to play ASF files. // //***************************************************************************** #include #include #include #include "control.h" #include "evcode.h" #include // Disable warning C4268, which is generated within #pragma warning(push) #pragma warning(disable:4268) #include #pragma warning(pop) #include #include #include #include // Function prototypes HRESULT RenderOutputPins(IGraphBuilder *pGB, IBaseFilter *pFilter); HRESULT FrameNumberToTime( IWMSyncReader * pSyncReader, WORD wFrameSeekStream, QWORD qwFrame, REFERENCE_TIME * prtFrameTime ); BOOL IsFrameSeekable( LPCWSTR wszFile, IWMSyncReader ** ppSyncReader, WORD *pwFrameSeekStream, QWORD * pqwTotalFrames ); void Msg(__in LPTSTR szFormat, ...); //------------------------------------------------------------------------------ // Name: CreateFilterGraph() // Desc: Create the DirectShow filter graph. // // pGraph: Receives a pointer to the IGraphBuilder interface. //------------------------------------------------------------------------------ HRESULT CreateFilterGraph(IGraphBuilder **pGraph) { HRESULT hr; // Create the Filter Graph Manager object. hr = CoCreateInstance(CLSID_FilterGraph, 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 filter with the specified CLSID. // // clsid: CLSID of the filter. // pFilter: Receives a pointer to the filter's IBaseFilter interface. //------------------------------------------------------------------------------ HRESULT CreateFilter(REFCLSID clsid, IBaseFilter **ppFilter) { HRESULT hr; 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: WaitForCompletion() // Desc: Desc: Waits for a media event that signifies completion or cancellation // of a task. // // pGraph: Pointer to the Filter Graph Manager's IGraphBuilder interface. //------------------------------------------------------------------------------ LONG WaitForCompletion( IGraphBuilder *pGraph ) { HRESULT hr; LONG lEvCode = 0; IMediaEvent *pEvent; pGraph->QueryInterface(IID_IMediaEvent, (void **) &pEvent); do { MSG Message; // Pump queued Windows messages while(PeekMessage(&Message, NULL, 0, 0, TRUE)) { TranslateMessage(&Message); DispatchMessage(&Message); } // Wait 10ms for an event hr = pEvent->WaitForCompletion(10, &lEvCode); } while(lEvCode == 0); pEvent->Release(); return lEvCode; } //------------------------------------------------------------------------------ // Name: IsFrameSeekable() // Desc: Ascertains whether the file contains a stream that is seekable by frame. // If so, the method returns the stream number and the number of frames. // // wszFile: Specifies the file name. // ppSyncReader: Receives a pointer to the synchronous reader's IWMSyncReader // interface. // pwFrameSeekStream: Receives the stream number of the seekable stream. // pqqTotalFrames: Receives the number of frames in the stream. //------------------------------------------------------------------------------ BOOL IsFrameSeekable( LPCWSTR wszFile, IWMSyncReader ** ppSyncReader, WORD *pwFrameSeekStream, QWORD *pqwTotalFrames ) { if( !ppSyncReader || !pwFrameSeekStream ) return FALSE; HRESULT hr = E_FAIL; // Create a synchronous reader and load the specified file. hr = WMCreateSyncReader( 0, 0, ppSyncReader ); if( FAILED( hr ) ) { _tprintf(_T("Couldn't create the synchronous reader for ASF frame seeking! hr=0x%x\nPlayback aborted.\n\n"), hr); } if( SUCCEEDED( hr ) ) { // Open the source file. hr = (*ppSyncReader)->Open( wszFile ); if( FAILED( hr ) ) { _tprintf(_T("WMSDK Sync reader failed to open file! hr=0x%x\nPlayback aborted.\n\n"), hr); } } if( SUCCEEDED( hr ) ) { CComPtr pProfile; hr = (*ppSyncReader)->QueryInterface( IID_IWMProfile, (void **) &pProfile ); if( SUCCEEDED( hr ) ) { // Loop through each stream and check the "NumberOfFrames" attribute in the // ASF header for that stream number. This attribute is present if the stream // has been indexed by frame. DWORD cStreams; WORD wStreamNum; // Get the number of streams. hr = pProfile->GetStreamCount(&cStreams); if( SUCCEEDED( hr ) ) { for( DWORD dw = 0; dw < cStreams; dw++ ) { CComPtr pConfig; hr = pProfile->GetStream( dw, &pConfig ); if( SUCCEEDED( hr ) ) { // Get the stream number for this stream. hr = pConfig->GetStreamNumber( &wStreamNum ); if( SUCCEEDED( hr ) ) { CComPtr pWMHI; hr = (*ppSyncReader)->QueryInterface( IID_IWMHeaderInfo, (void **) &pWMHI ); if( SUCCEEDED( hr ) ) { // Check whether the header contains a "NumberOfFrames" attribute // (g_wszWMNumberOfFrames) for this stream. If so, the value of the // attribute is the number of frames in the stream. WMT_ATTR_DATATYPE Type; QWORD qwFrames; WORD cbLength = sizeof(qwFrames); hr = pWMHI->GetAttributeByName(&wStreamNum, g_wszWMNumberOfFrames, &Type, (BYTE *) &qwFrames, &cbLength); if( SUCCEEDED( hr ) ) { // This stream is seekable by frame. Return the stream number // and the number of frames. *pwFrameSeekStream = wStreamNum; *pqwTotalFrames = qwFrames; } } } } } } } } return ( S_OK == hr ); } //------------------------------------------------------------------------------ // Name: FrameNumberToTime() // Desc: Converts a frame number to a reference time (100-nanosecond units). // // pSyncReader: Pointer to the synchronous reader's IWMSyncReader interface. // wFrameSeekStream: Specifies the stream number. // qwFrame: Specifies the frame number. // prtFrameTime: Receives the reference time. //------------------------------------------------------------------------------ HRESULT FrameNumberToTime( IWMSyncReader * pSyncReader, WORD wFrameSeekStream, QWORD qwFrame, REFERENCE_TIME * prtFrameTime ) { if( !pSyncReader || !prtFrameTime ) return E_POINTER; // Use the Windows Media Format SDK synchronous reader object to seek to // the specified frame. The reader object returns the presentation time // for the next sample. // Seek to the specified frame number. HRESULT hr = pSyncReader->SetRangeByFrame( wFrameSeekStream, qwFrame, 0 ); if( FAILED( hr ) ) { _tprintf(_T("SetRangeByFrameFailed... hr=0x%x\nPlayback aborted.\n\n"), hr); } else { // Get the next sample and return the presentation time to the caller. INSSBuffer * pINSSBuffer; QWORD qwSampleTime, qwSampleDuration; DWORD dwFlags; WORD idStream; hr = pSyncReader->GetNextSample( wFrameSeekStream, &pINSSBuffer, &qwSampleTime, &qwSampleDuration, &dwFlags, NULL, &idStream ); if( SUCCEEDED( hr ) ) { pINSSBuffer->Release(); *prtFrameTime = qwSampleTime; } } return hr; } //------------------------------------------------------------------------------ // Name: RenderOutputPins() // Desc: Renders every output pin on a specified filter. // // pGB: Pointer to the Filter Graph Manager. // pFilter: Pointer to the filter. //------------------------------------------------------------------------------ HRESULT RenderOutputPins(IGraphBuilder *pGB, IBaseFilter *pFilter) { HRESULT hr = S_OK; IEnumPins * pEnumPin = NULL; IPin * pConnectedPin = NULL, * pPin; PIN_DIRECTION PinDirection; ULONG ulFetched; // Enumerate all the pins on the filter. hr = pFilter->EnumPins( &pEnumPin ); if(SUCCEEDED(hr)) { // Step through every pin, looking for the output pins. while (S_OK == (hr = pEnumPin->Next( 1L, &pPin, &ulFetched))) { // Check whether this pin is already connected. If so, ignore it. hr = pPin->ConnectedTo(&pConnectedPin); if (pConnectedPin) { // Release the IPin interface on the connected pin. pConnectedPin->Release(); pConnectedPin = NULL; } if (VFW_E_NOT_CONNECTED == hr) { // This pin is not connected to another filter yet. Check // whether it is an output pin. If so, use the Filter Graph // Manager's Render() method to render the pin. hr = pPin->QueryDirection( &PinDirection ); if ( ( S_OK == hr ) && ( PinDirection == PINDIR_OUTPUT ) ) { hr = pGB->Render(pPin); } } pPin->Release(); // If there was an error, stop enumerating. if (FAILED(hr)) break; } } // Release the pin enumerator object. pEnumPin->Release(); return hr; } //------------------------------------------------------------------------------ // Name: SeekASF() // Desc: Parse the command line and play the specified file, // with optional seeking. //------------------------------------------------------------------------------ HRESULT SeekASF(int argc, __in_ecount(argc) LPSTR argv[]) { HRESULT hr; WCHAR wszFile[256]; int i = 1; int iFrameStart = 0; int iFramesToPlay = -1; // Default to an invalid value, to force user setting. int iFrameIncForLoopSeek = 0; BOOL fSeekByFrame = FALSE; BOOL fLoopSeekToEnd = FALSE; BOOL bAbortWithUsage = FALSE; QWORD qwTotalFrames = 0; // Parse the command line options while(i < argc && (argv[i][0] == '-' || argv[i][0] == '/')) { // Options if((i+3 <= argc) && lstrcmpiA(argv[i] + 1, "f") == 0) { fSeekByFrame = TRUE; iFrameStart = atoi(argv[i+1]); if (isalnum(*argv[i+2])) iFramesToPlay = atoi(argv[i+2]); if( iFrameStart < 1 ) { _tprintf(_T("Error: Start frame must be >= 1! \n")); bAbortWithUsage = TRUE; break; } if( iFramesToPlay < 0 ) { _tprintf(_T("Error: Number of frames to play must be >= 0! \n")); bAbortWithUsage = TRUE; break; } // Skip 3 arguments here i += 2; } else if((i+1 < argc) && lstrcmpiA(argv[i] + 1, "l") == 0) { fLoopSeekToEnd = TRUE; iFrameIncForLoopSeek = atoi(argv[i+1]); // Skip 2 arguments here i++; } i++; } // Fail with usage information if improper number of arguments if(argc < i+1 || ( fLoopSeekToEnd && !fSeekByFrame ) || bAbortWithUsage || (fSeekByFrame && iFramesToPlay < 0)) { if( fLoopSeekToEnd && !fSeekByFrame ) { _tprintf(_T("Error: /l option requires /f option\n\n")); } _tprintf(_T("Usage: DSSeekFm [/f FrameStart NumFramesToPlay] [/l LoopModeIncrement] Filename\n\n")); _tprintf(_T(" FrameStart: Frame on which to begin playback\n")); _tprintf(_T(" LoopModeIncrement: Adjust start point by this many frames when looping\n")); _tprintf(_T(" If NumFramesToPlay is 0, file will play to completion.\n\n")); _tprintf(_T("Example: DSSeekFm /f 100 50 frameseek.wmv\n")); _tprintf(_T(" This will seek to frame 100 and play 50 frames.\n\n")); _tprintf(_T("Definition of 'Loop Mode':\n")); _tprintf(_T(" Plays segments of length , \n")); _tprintf(_T(" incrementing by until EOF.\n\n")); _tprintf(_T("Example: DSSeekFm /f 100 10 /l 50 frameseek.wmv\n")); _tprintf(_T(" This will seek to frame 100, play 10 frames, advance 50 frames,\n")); _tprintf(_T(" play 10 more frames, advance 50 frames, etc. until completion.\n")); return -1; } CComPtr pGraph; // Filter Graph Manager CComPtr pReader; // ASF reader filter CComPtr pFS; // Sets the file name on the ASF readewr. CComPtr pMC; // Controls filter graph playback. CComPtr pMS; // Seeks the filter graph. // Convert the file name to Unicode. if(0 == MultiByteToWideChar(CP_ACP, 0, argv[argc - 1], -1, wszFile, 256)) { _tprintf(_T("Couldn't convert file name to Unicode!\n")); return E_FAIL; } // Create the DirectShow Filter Graph Manager. hr = CreateFilterGraph(&pGraph); if(FAILED(hr)) { _tprintf(_T("Couldn't create filter graph! hr=0x%x\n"), hr); return hr; } // Create the ASF reader filter and add it to the Filter Graph. hr = CreateFilter(CLSID_WMAsfReader, &pReader); if(FAILED(hr)) { _tprintf(_T("Failed to create WMAsfReader filter! hr=0x%x\n"), hr); return hr; } hr = pGraph->AddFilter(pReader, L"ASF Reader"); if(FAILED(hr)) { _tprintf(_T("Failed to add ASF Reader filter to graph! hr=0x%x\n"), hr); return hr; } // Get the IFileSourceFilter interface, which is used to set the source file name. hr = pReader->QueryInterface(IID_IFileSourceFilter, (void **) &pFS); if(FAILED(hr)) { _tprintf(_T("QI for IFileSourceFilter failed! hr=0x%x\n"), hr); return hr; } // Load the source file. hr = pFS->Load( wszFile, NULL ); if(FAILED(hr)) { _tprintf(_T("Failed to load file! hr=0x%x\n"), hr); _tprintf(_T("Make sure this is an ASF file!!\n")); return hr; } else { // Render the output pins of the ASF reader to build the remainder of the // filter graph automatically. This step connects the ASF reader filter to // the video and audio renderer filters. hr = RenderOutputPins(pGraph, pReader); if( FAILED( hr ) ) { _tprintf(_T("Failed to render output pins! hr=0x%x\n"), hr); return hr; } } IWMSyncReader * pSyncReader = NULL; WORD wFrameSeekStream = 0; // Check whether the ASF file can be seeked by frame number. If so, this method // call returns the stream number and the total number of frames in the stream. if( !IsFrameSeekable( wszFile, &pSyncReader, &wFrameSeekStream, &qwTotalFrames ) && fSeekByFrame) { _tprintf(_T("The selected ASF file isn't frame seekable.\n")); return E_FAIL; } if( qwTotalFrames > 0 ) { _tprintf(_T("Total Frames in ASF file = %d\n"), (LONG) qwTotalFrames ); } // Prepare to run the graph. Query for the necessary interfaces. hr = pGraph->QueryInterface(IID_IMediaControl, (void **) &pMC); if(FAILED(hr)) { _tprintf(_T("Failed to QI for IMediaControl! hr=0x%x\n"), hr); return hr; } hr = pGraph->QueryInterface(IID_IMediaSeeking, (void **) &pMS); if(FAILED(hr)) { _tprintf(_T("Failed to QI for IMediaSeeking! hr=0x%x\n"), hr); return hr; } REFERENCE_TIME rtStart = 0; REFERENCE_TIME rtStop = 0; LONG lCompletionEvent; if( fSeekByFrame && iFrameStart > 0 ) { // The WM ASF Reader filter does not directly support frame seeking. Therefore, we use // the Windows Media Format SDK to create a separate instance of the synchronous // reader object. We seek the reader object to the desired frame number and retrieve the // presentation time. Then we use that time vaue to seek the DirectShow filter graph. do { if( fSeekByFrame && iFrameStart > 0 ) { // Calculate the frame at which to stop. int iFrameStop = iFrameStart + iFramesToPlay; // Check if we're trying to seek past the end of the file. if( ( iFrameStart > (int)qwTotalFrames ) || ( iFrameStop > (int)qwTotalFrames ) ) { _tprintf(_T("Playback finished: Attempting to seek past last frame number!\n") _T(" (requested: start %d, stop %d, total frames: %d \n"), iFrameStart, iFrameStop, (LONG)qwTotalFrames); hr = S_OK; break; } // Convert the requested frame number to a reference time (100-nanosecond units). hr = FrameNumberToTime( pSyncReader, wFrameSeekStream, (QWORD) iFrameStart, &rtStart ); if(FAILED(hr)) { _tprintf(_T("Failed to convert start frame number to frame time! hr=0x%x\n"), hr); } else if( 0 < iFramesToPlay ) // If iFramesToPlay == 0, it means to play to the end of the file. { // Convert the stop frame number to a reference time. hr = FrameNumberToTime( pSyncReader, wFrameSeekStream, (WORD) iFrameStop, &rtStop ); if(FAILED(hr)) { _tprintf(_T("Failed to convert stop frame number to frame time! hr=0x%x\n"), hr); } } // Perform the seek. if( SUCCEEDED( hr ) ) { if (iFramesToPlay > 0) _tprintf(_T("Playing from frame #%d to frame #%d.\n"), iFrameStart, iFrameStop); else _tprintf(_T("Playing from frame #%d to completion.\n"), iFrameStart); // Seek the graph to the calculated reference time. hr = pMS->SetPositions( &rtStart, AM_SEEKING_AbsolutePositioning, &rtStop, (iFramesToPlay > 0 ) ? AM_SEEKING_AbsolutePositioning : 0 ); if(FAILED(hr)) { _tprintf(_T("Failed seek (rtStart = %dms, rtStop = %dms)! hr=0x%x\n"), (LONG) (rtStart/10000), (LONG)(rtStop/10000), hr); } } // Begin playback of the filter graph. if( SUCCEEDED( hr ) ) { hr = pMC->Run(); if(FAILED(hr)) { _tprintf(_T("Failed to run the graph! hr=0x%x\nPlayback aborted.\n\n"), hr); } else { // Wait for the graph to finish playing (or the user to abort). if (!fLoopSeekToEnd) _tprintf(_T("Waiting for completion...\n")); lCompletionEvent = WaitForCompletion(pGraph); if( EC_USERABORT == lCompletionEvent || EC_ERRORABORT == lCompletionEvent ) { _tprintf(_T("Playback aborted.\n")); break; } if (!fLoopSeekToEnd) _tprintf(_T("Playback complete.\n")); } } } // If we are looping, calculate the next starting frame. iFrameStart += iFrameIncForLoopSeek; } while( fLoopSeekToEnd && SUCCEEDED( hr ) ); // Stop the graph. hr = pMC->Stop(); } // Seeking was not requested, so just play the graph normally. else { _tprintf(_T("Seeking was not requested. Playing graph to completion.\n")); hr = pMC->Run(); if(FAILED(hr)) { _tprintf(_T("Failed to run the graph! hr=0x%x\nPlayback aborted.\n\n"), hr); } else { lCompletionEvent = WaitForCompletion(pGraph); if( EC_USERABORT == lCompletionEvent || EC_ERRORABORT == lCompletionEvent ) { _tprintf(_T("Playback aborted.\n")); } else _tprintf(_T("Playback complete.\n")); // Stop the graph hr = pMC->Stop(); } } return hr; } //------------------------------------------------------------------------------ // Name: main() // Desc: Entry point for the application. //------------------------------------------------------------------------------ int __cdecl main( int argc, __in_ecount(argc) LPSTR argv[] ) { // Initialize COM. HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED); // 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 = SeekASF(argc, argv); CoUninitialize(); return hr; }