DirectShow and Windows Media

DirectShow and Windows Media

To integrate most efficiently with the Windows Media Format SDK, the DirectShow development team chose to implement the WM ASF Reader and Writer filters as thin wrappers of the Windows Media Format SDK. The filters act as a bridge between the reader and writer objects in that SDK and the rest of the DirectShow filter graph. They make it very easy, relatively speaking, to stream data into and out of the Windows Media reader and writer objects, using all the power of DirectShow to handle format conversion, intermediate transforms, and stream synchronization. For non-streaming functionality, such as profile creation, metadata insertion, and certain other configuration tasks, the application can talk directly to the Windows Media Format SDK.

You can also build graphs automatically using Intelligent Connect to create ASF file transcoding graphs with the WM ASF Writer object as the renderer; this filter creates ASF files from any number of input streams. The IFilterGraph2 method RenderEx renders only to renderer filters that already exist in the graph when the call is made if you specify the flag AM_RENDEREX_RENDERTO EXISTINGRENDERERS. You build this filter graph by first adding the WM ASF Writer filter to the filter graph. This filter has a default configuration for encoding streams passed to it by the filter graph. We used that default configuration when we used the filter in previous chapters, but as I explained earlier in this chapter, the filter s default profile uses the Windows Media 8 codecs. Therefore, to use the Windows Media 9 Series codecs, you must configure the filter with a custom Windows Media profile, as part of the filter initialization process, before file encoding can commence.

To configure the WM ASF Writer filter, you must query it for its IConfigAsfWriter2 interface. (Because this interface exists only on the latest version of the filter, which ships with the Windows Media Format SDK, it is documented there rather than in DirectX.) IConfigAsfWriter2 inherits much of its functionality from the IConfigAsfWriter interface (which exists on earlier versions of the filter and is documented in the DirectX SDK documentation). IConfig AsfWriter has several methods that allow a profile to be applied to the filter, including ConfigureFilterUsingProfile, which takes a pointer to an IWMProfile interface, using that profile to configure the filter. To instantiate the profile object, you can either create it from scratch or load it from a .PRX file using the Windows Media Format SDK method LoadProfile. We re using the latter method for the sake of convenience, which is why we downloaded the Windows Media Profile Editor. LoadProfile returns a IWMProfile interface on the object; through this and related interfaces, your application works with the profile.

The IWMProfile interface has a rich set of methods that allow you to manually adjust each of the parameters of the profile just as we did with a GUI in the Windows Media Profile Editor. However, using the IWMProfile interface means you can build a profile that might be rejected by the Windows Media Format SDK, so you have to guard against errors your profile might generate when you put it to work. The upside of this power is that the IWMProfile methods offer greater functionality than the Profile Editor. (A caveat: for audio streams, you can t arbitrarily change the stream encoding; you must retrieve the list of formats using IWMCodecInfo3 and select an entry from that list.)

As mentioned earlier, the DirectShow filters for reading and writing Windows Media files are implemented as a thin wrapper. These filters allow DirectShow applications to program directly to the Windows Media Format SDK if necessary. When you use the Windows Media Format SDK from within a DirectShow application, your operations will likely be confined to one of these interfaces:

  • IWMReaderAdvanced, which handles the parsing of ASF files and is provided as an interface on the WM ASF Reader filter.

  • IWMWriterAdvanced2, which manages the creation and composition of ASF files and is available through the IServiceProvider interface on the WM ASF Writer filter.

  • IWMHeaderInfo3, which adds or modifies metadata information. You can obtain this interface only in a roundabout way, by calling Query Interface using a IWMWriterAdvanced2 pointer that you obtain using a QueryService call.

  • IWMMetadataEditor, which allows you to open Windows Media file and manipulate the header information within it.

  • IWMProfileManager, which allows you to create, load, and save profiles.

  • IWMCodecInfo3, which provides the configuration of supported formats for the Windows Media codecs. If you want to change the parameters of an audio stream, you must obtain the stream configuration information using the methods of this interface.

  • IWMProfile3, which provides access to the information in a profile object.

  • IWMStreamConfig3, which enables configuration of individual media streams.

  • IWMMutualExclusion2, which enables you to configure mutually exclusive streams (such as those created for multiple bit-rate profiles).

  • IWMMediaProps, which provides access to basic media properties for streams.

Full documentation on these interfaces and their methods can be found in the Windows Media Format SDK documentation.

With these COM-based interfaces to Windows Media, you can have very fine-grained control over the creation, encoding, and parsing of ASF files. Each interface works in conjunction with one of the two DirectShow filters, WM ASF Reader or WM ASF Writer, to expose low-level controls not provided in the standard filter interfaces.

The most difficult aspects of streaming media file creation, such as dealing with timing and synchronization issues among multiple streams with different encoding rules, are handled invisibly by DirectShow s WM ASF Reader and WM ASF Writer filters. Yet again, DirectShow hides the complexity of the Windows Media API behind a few very simple components.

MakeASF: Using DirectShow and Windows Media in an Application

The two most common tasks DirectShow applications perform on Windows Media files are decoding (playback) and encoding. Decoding can be handled with the RenderFile method on the IFilterGraph interface, but encoding is a more sophisticated procedure, involving Windows Media profile creation and manipulation. MakeASF is a GUI-based application that transcodes (decodes and then encodes) a media file from any format understood by DirectShow (and that s a lot of formats) into a Windows Media format ASF file. In this sense, MakeASF is a universal translator that you can use to produce Windows Media files from almost any media file you encounter. The interface elements in MakeASF are simple and straightforward, as shown in Figure 15-5.

figure 15-5 the makeasf application, which accepts nearly any media file as input and outputs a windows media asf file

Figure 15-5. The MakeASF application, which accepts nearly any media file as input and outputs a Windows Media ASF file

The application s window allows you to browse for the source media file. It automatically creates an output file name based on the source file name, but the user can edit this field. Next you select a profile file (with the .PRX extension), which specifies the encoding parameters for the output file. Finally, you can add output file metadata by typing in the Title and Author fields. Three encoding options are available. The Multipass option allows the Windows Media codec to review and preprocess the entire media sample before beginning the encoding process. This requires more processing time but generally improves stream quality at a given bit rate. Frame Indexing adds index numbers to the frames, which allows the created file to respond to frame-addressable seek commands. SMPTE Time Codes adds timecode information (as described in Chapter 6) to data unit extensions for the file. (Don t confuse these optional timecodes with the timestamps that will be applied to each media sample in the file.)

After you specify all the parameters, click Start Encoding to begin the transcoding process. This process takes some time, and you won t see any progress dialog box or thermometer-type visual feedback, but an output field at the bottom of the window gives up-to-date progress information as the encoding process proceeds. The transcoding process isn t speedy a lot of work is going on even though it all looks simple enough but eventually the field will report Encoding complete! Next we ll take a detailed look at how MakeASF works.

Constructing the Filter Graph

The filter graph is constructed and executed in one monolithic function, Make AsfFile. MakeAsfFile is a big function, but it breaks into the following discrete steps:

  1. Load a profile from disk. (This step is required unless you choose to create a profile from scratch.)

  2. If the profile specifies a display window of zero dimensions for video, adjust the video dimension in the profile to a temporary valid value until you can determine the native dimensions of the input stream. (This step, together with step 9, is optional.)

  3. Create the filter graph.

  4. Add the WM ASF Writer filter to the graph.

  5. Apply the loaded profile to the WM ASF Writer filter.

  6. Set configuration parameters on the WM ASF Writer based on UI selections.

  7. Add the File Source filter to the graph.

  8. Connect all output pins of the File Source filter to corresponding input pins on the WM ASF Writer filter.

  9. Readjust the dimensions of the video rectangle in the profile, if necessary. (Optional, together with step 2.)

  10. Add data unit extensions for SMPTE timecode, if selected in the UI, and install a callback routine that will add the SMPTE timecode data to every sample as it passes through the WM ASF Writer. (Optional.)

  11. Add the title and author metadata. (Optional.)

  12. Run the filter graph, and stop it when it completes.

  13. If multipass encoding has been selected in the UI, rewind the source to the beginning and run the filter graph again, stopping it when complete. (Optional.)

  14. Run the frame indexing functions, if selected in the UI. (Optional.)

  15. Release everything and exit.

This is a lot of work for just one function to perform, so MakeAsfFile makes calls to other local functions to perform some of the tasks. Here s the function s first portion, where it loads the profile:

HRESULT MakeAsfFile(_TCHAR* szSource, _TCHAR* szTarget, _TCHAR* szProfile) { HRESULT hr; WCHAR wszTargetFile[_MAX_PATH] = {'\0'}; WCHAR wszSourceFile[_MAX_PATH] = {'\0'}; WCHAR wszAuthor[_MAX_PATH] = {'\0'}; WCHAR wszTitle[_MAX_PATH] = {'\0'}; CComPtr <IGraphBuilder> pGraph; CComPtr <IBaseFilter> pSourceFilter; CComPtr <IBaseFilter> pASFWriter; CASFCallback* pASFCallback = NULL; CComPtr <IAMWMBufferPassCallback> pSMPTECallback; // Convert target filename, // title and author metadata fields to a wide character string. wcsncpy(wszTargetFile, szTarget, NUMELMS(wszTargetFile)); wcsncpy(wszAuthor, szAuthor, NUMELMS(wszAuthor)); wcsncpy(wszTitle, szTitle, NUMELMS(wszTitle)); // Load the prx file into memory // and tell us whether it is a special case profile with a zero-sized // video rectangle. If it is, we'll have to adjust the profile later, // to specify the native video size before running the graph. // This is how the Windows Media Encoder works with profiles created // by the Profile Editor. CComPtr<IWMProfile> pProfile1; BOOL bZeroSizedVidRect = FALSE; // Load the data in the prx file into a WMProfile object. hr = LoadCustomProfile((LPCSTR)szProfile, &pProfile1, bZeroSizedVidRect); if(FAILED(hr)) { DbgLog((LOG_TRACE, 3, _T("Failed to load profile! hr=0x%x\n"), hr)); return hr; } if(bZeroSizedVidRect) { OutputMsg(_T("The profile has a zero-sized rectangle. This will be interpreted as an instruction to use the native video size.\r\n")); DbgLog((LOG_TRACE, 3, _T("Zero-sized rectangle!\n"))); // Now we need to insert some dummy values // for the output rectangle in order to avoid an // unhandled exception when we first configure the filter // with this profile. // Later, after we connect the filter, // we will be able to determine the upstream rectangle size, and // then adjust profile's rectangle values to match it. hr = SetNativeVideoSize(pASFWriter, pProfile1, TRUE); if(FAILED(hr)) { DbgLog((LOG_TRACE, 3, _T("Failed to SetNativeVideoSize with dummy values! hr=0x%x\n"), hr)); return hr; } }

The MakeASF application uses ATL smart pointers to handle the instantiation and management of COM objects. A smart pointer to an IWMProfile interface is created to manage the profile for the transcoding. It is then loaded from disk, in the function LoadCustomProfile (taken from the Windows Media Format SDK samples):

HRESULT LoadCustomProfile( LPCTSTR ptszProfileFileName, IWMProfile ** ppIWMProfile, BOOL& bEmptyVidRect ) { HRESULT hr = S_OK; DWORD dwLength = 0; DWORD dwBytesRead = 0; IWMProfileManager * pProfileManager = NULL; HANDLE hFile = INVALID_HANDLE_VALUE; LPWSTR pwszProfile = NULL; if( NULL == ptszProfileFileName NULL == ptszProfileFileName ) { return( E_POINTER ); } do { // // Create profile manager. // hr = CreateProfileManager( &pProfileManager ); if( FAILED( hr ) ) { break; } // // Open the profile file. // hFile = CreateFile( ptszProfileFileName, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL ); if( INVALID_HANDLE_VALUE == hFile ) { hr = HRESULT_FROM_WIN32( GetLastError() ); break; } if( FILE_TYPE_DISK != GetFileType( hFile ) ) { hr = NS_E_INVALID_NAME; break; } dwLength = GetFileSize( hFile, NULL ); if( -1 == dwLength ) { hr = HRESULT_FROM_WIN32( GetLastError() ); break; } // // Allocate memory to hold profile XML file. // pwszProfile = (WCHAR *)new BYTE[ dwLength + sizeof(WCHAR) ]; if( NULL == pwszProfile ) { hr = E_OUTOFMEMORY; break; } // The buffer must be null-terminated. ZeroMemory(pwszProfile, dwLength + sizeof(WCHAR) ) ; // // Read the profile to a buffer. // if( !ReadFile( hFile, pwszProfile, dwLength, &dwBytesRead, NULL ) ) { hr = HRESULT_FROM_WIN32( GetLastError() ); break; } // // Load the profile from the buffer // hr = pProfileManager->LoadProfileByData( pwszProfile, ppIWMProfile ); if( FAILED(hr) ) { break; } } while( FALSE ); // The WM Profile Editor uses empty rectangles // as a signal to the Windows Media Encoder // to use the native video size. // Our application will do the same thing. // Here we cheat for the sake of efficiency. In general, do not // get into the habit of manipulating the XML profile string directly. // Here we are just peeking to see // if we have an empty video rectangle. // If we do have one, // we won't attempt to modify the XML string directly, but // instead will just set a flag now // and modify the profile object later using the SDK methods. if(SUCCEEDED(hr)) { if( wcsstr( pwszProfile , L"biwidth=\"0\"" ) != NULL wcsstr( pwszProfile , L"biheight=\"0\"" ) != NULL ) { bEmptyVidRect = TRUE; } } // // Release all resources. // SAFE_ARRAYDELETE( pwszProfile ); SAFE_CLOSEHANDLE( hFile ); SAFE_RELEASE( pProfileManager ); return( hr ); }

LoadCustomProfile is our first example of how a DirectShow application programs directly to the Windows Media Format SDK. It instantiates a Windows Media Profile Manager object in the call to CreateProfileManager, which is just a wrapper around a call to WMCreateProfileManager. The specified file containing the XML schema for the profile is opened and read into memory. The XML is parsed and returned in a pointer to an IWMProfile interface in the Profile Manager method LoadProfileByData. Although the profile has been loaded successfully, the function takes the extra (optional) step of examining the XML of the profile for the biwidth and biheight values contained in the schema. The Windows Media Encoder application knows what this means, but the Windows Media Format SDK does not. To be as smart as the Encoder, we check the rectangle, and if it is empty, the global flag bEmptyVidRect is set. If, on return, the flag is set, the function SetNativeVideoSize is called:

HRESULT SetNativeVideoSize(IBaseFilter* pASFWriter, IWMProfile* pProfile, BOOL bUseDummyValues) { HRESULT hr = S_OK; // Get the native rectangle size from the video pin. SIZE nativeRect; if( TRUE == bUseDummyValues) { nativeRect.cx = 640; nativeRect.cy = 480; } else { nativeRect = GetVideoSizeFromPin(pASFWriter); } // For the profile, // get the IWMStreamConfig interface for the video stream. DWORD dwStreams = 0; DWORD dwMediaTypeSize = 0; hr = pProfile->GetStreamCount(&dwStreams); if ( FAILED( hr ) ) { DbgLog((LOG_TRACE, 3, _T("Failed GetStreamCount (hr=0x%08x)!\n"), hr)); return hr; } for(WORD j = 1; j <= dwStreams ; j++) { CComPtr<IWMStreamConfig> pWMStreamConfig; hr = pProfile->GetStreamByNumber(j, &pWMStreamConfig); if ( FAILED( hr ) ) { DbgLog((LOG_TRACE, 3, _T("Failed GetStreamByNumber (hr=0x%08x)!\n"), hr )); return hr; } // Get the stream's major type. // Note that we assume only one video stream in the file. GUID guidStreamType; hr = pWMStreamConfig->GetStreamType(&guidStreamType); if ( FAILED( hr ) ) { DbgLog((LOG_TRACE, 3, _T("Failed GetStreamType (hr=0x%08x)!\n"), hr )); return hr; } if(IsEqualGUID(WMMEDIATYPE_Video, guidStreamType)) { CComQIPtr<IWMMediaProps, &IID_IWMMediaProps> pMediaProps (pWMStreamConfig); if(!pMediaProps) { DbgLog((LOG_TRACE, 3, _T("Failed to QI for IWMMediaProps (hr=0x%08x)!\n"), hr )); return hr; } //Retrieve the amount of memory required // to hold the returned structure. hr = pMediaProps->GetMediaType(NULL, &dwMediaTypeSize); if(FAILED( hr ) ) { DbgLog((LOG_TRACE, 3, _T("Failed GetMediaType first call(hr=0x%08x)!\n"), hr )); return hr; } //Allocate the memory. BYTE *pData = 0; do { pData = new BYTE[ dwMediaTypeSize ]; if ( NULL == pData ) { hr = E_OUTOFMEMORY; DbgLog((LOG_TRACE, 3, _T( " Out of memory: (hr=0x%08x)\n" ), hr )); break; } ZeroMemory( pData, dwMediaTypeSize ); // Retrieve the actual WM_MEDIA_TYPE structure // and format block. hr = pMediaProps->GetMediaType( ( WM_MEDIA_TYPE *) pData, &dwMediaTypeSize ); if ( FAILED( hr ) ) { DbgLog((LOG_TRACE, 3, _T( " GetMediatype second call failed: (hr=0x%08x)\n" ), hr )); break; } WM_MEDIA_TYPE* pMT = ( WM_MEDIA_TYPE *) pData; // pMT is easier to read // Set the native video rectangle size // on the BITMAPINFOHEADER. if(IsEqualGUID(pMT->formattype, WMFORMAT_VideoInfo)) { WMVIDEOINFOHEADER* pWMVih = (WMVIDEOINFOHEADER*) pMT->pbFormat; pWMVih->bmiHeader.biHeight = nativeRect.cy; pWMVih->bmiHeader.biWidth = nativeRect.cx; } else { // We only handle WMFORMAT_VideoInfo. DbgLog((LOG_TRACE, 3, _T( "Video Media Type is not WMFORMAT_VideoInfo\n" ) )); break; } hr = pMediaProps->SetMediaType(pMT); if ( FAILED( hr ) ) { DbgLog((LOG_TRACE, 3, _T( " SetMediaType failed: (hr=0x%08x)\n" ), hr )); break; } hr = pProfile->ReconfigStream(pWMStreamConfig); if ( FAILED( hr ) ) { DbgLog((LOG_TRACE, 3, _T( " ReconfigStream failed: (hr=0x%08x)\n" ), hr )); break; } }while (FALSE); SAFE_ARRAYDELETE(pData); }//end ifIsEqualGUID } return hr; }

SetNativeVideoSize examines the IWMProfile pointer passed to it, and it determines the number of streams in the profile. The function assumes that there is only one video stream in the file and iterates through the streams, calling the IWMProfile interface method GetStreamByNumber, which returns a pointer to an IWMStreamConfig interface. The stream s media major type is returned in a call to the IWMStreamConfig method GetStreamType. This returns a GUID value, which is then compared to WMMEDIATYPE_Video, the GUID for Windows Media video streams.

Now that we know we re working with a video stream, we can query for the IWMMediaProps interface on the IWMStreamConfig interface. This interface allows us to examine the media type specific properties of the stream with a call to its method GetMediaType. The first call passes a null pointer, so it returns the size of the buffer needed to hold the media type. When allocated, a second call to GetMediaType returns a pointer to the WM_MEDIA_TYPE structure. The definition of this structure is identical to the definition of the AM_MEDIA_TYPE structure we ve used throughout the last several chapters. (The format block is allocated as part of the memory for the media type.) Once we have that structure in memory, we modify it with the new temporary video size (a default of 640 x 480, because it s a standard video size) and then commit these changes to the stream with a call to the IWMMediaProps method SetMediaType.

Finally, the profile must be reconfigured to accommodate the media type change to one of its streams. We do this with a call to the IWMProfile method ReconfigStream, which is passed a pointer to the modified IWMStreamConfig object. We must do this to accommodate a scenario where the user wants to keep the native video size but doesn t know that size in advance. When you have more control over the encoding settings and you know in advance the input size or the desired output size, or if you simply disallow zero-sized video rectangles, you can delete SetNativeVideoSize and its related code from your application.

Now that the profile has been loaded and adjusted, we can return to Make AsfFile, where we now build the filter graph:

 // Create an empty DirectShow filter graph. hr = CreateFilterGraph(&pGraph); if(FAILED(hr)) { DbgLog((LOG_TRACE, 3, _T("Couldn't create filter graph! hr=0x%x"), hr)); return hr; } // Create the WS ASF Writer Filter. hr = CreateFilter(CLSID_WMAsfWriter, &pASFWriter); if(FAILED(hr)) { DbgLog((LOG_TRACE, 3, _T("Failed to create WMAsfWriter filter! hr=0x%x\n"), hr)); return hr; } // Get a file sink filter interface from the ASF Writer filter // and set the output file name. CComQIPtr<IFileSinkFilter, &IID_IFileSinkFilter> pFileSink (pASFWriter); if(!pFileSink) { DbgLog((LOG_TRACE, 3, _T("Failed to create QI IFileSinkFilter! hr=0x%x\n"), hr)); return hr; } hr = pFileSink->SetFileName(wszTargetFile, NULL); if(FAILED(hr)) { DbgLog((LOG_TRACE, 3, _T("Failed to set target filename! hr=0x%x\n"), hr)); return hr; } // Add the WM ASF writer to the graph. hr = pGraph->AddFilter(pASFWriter, L"ASF Writer"); if(FAILED(hr)) { DbgLog((LOG_TRACE, 3, _T("Failed to add ASF Writer filter to graph! hr=0x%x\n"), hr)); return hr; } // Obtain the interface we will use to configure the WM ASF Writer. CComQIPtr<IConfigAsfWriter2, &IID_IConfigAsfWriter2> pConfigAsfWriter2(pASFWriter); if(!pConfigAsfWriter2) { DbgLog((LOG_TRACE, 3, _T("Failed to QI for IConfigAsfWriter2! hr=0x%x\n"), hr)); return hr; } // Configure the filter with the profile. hr = pConfigAsfWriter2->ConfigureFilterUsingProfile(pProfile1); if(FAILED(hr)) { DbgLog((LOG_TRACE, 3, _T("Failed to configure filter to use profile1! hr=0x%08x\n"), hr)); return hr; } // If frame-based indexing was requested, disable the default // time-based (temporal) indexing. if (g_fFrameIndexing) { hr = pConfigAsfWriter2->SetIndexMode(FALSE); if(FAILED(hr)) { DbgLog((LOG_TRACE, 3, _T("Failed to disable time-based indexing! hr=0x%08x\n"), hr)); return hr; } OutputMsg(_T("Frame-based indexing has been requested.\r\n")); } // Enable multipass encoding if requested. if (g_fMultipassEncode) { hr = pConfigAsfWriter2->SetParam(AM_CONFIGASFWRITER_PARAM_MULTIPASS, TRUE, 0); if (FAILED(hr)) { DbgLog((LOG_TRACE, 3, _T("Failed to enable multipass encoding param! hr=0x%x\n"), hr)); return hr; } } // Set sync source to NULL to encode as fast as possible. SetNoClock(pGraph); // Convert the source file into WCHARs for DirectShow. wcsncpy(wszSourceFile, szSource, NUMELMS(wszSourceFile)); DbgLog((LOG_TRACE, 3, _T("\nCopying [%ls] to [%ls]\n"), wszSourceFile, wszTargetFile)); // Let DirectShow render the source file. // We use "AddSourceFilter" and then // render its output pins using IFilterGraph2::RenderEx // in order to force the // Filter Graph Manager to always use the // WM ASF Writer as the renderer. hr = pGraph->AddSourceFilter(wszSourceFile, L"Source Filter", &pSourceFilter); if(FAILED(hr)) { DbgLog((LOG_TRACE, 3, _T("Failed to add source filter! hr=0x%x\n"), hr)); return hr; } // Render all output pins on source filter using RenderEx. hr = RenderOutputPins(pSourceFilter, pGraph); if(FAILED(hr)) { DbgLog((LOG_TRACE, 3, _T("Failed RenderOutputPins! hr=0x%x\n"), hr)); return hr; }

The filter graph building is a straightforward affair: a filter graph is created, and the WM ASF Writer and File Source filters are instantiated and added to the filter graph. The IFileSink interface on the WM ASF Writer is used to call SetFileName on the output file. The WM ASF Writer also presents an IConfigAsfWriter2 interface (which is documented only in the Windows Media Format SDK, although it inherits most of its methods from IConfigAsfWriter, which is documented in the DirectX SDK), and the Windows Media profile is applied to the filter, using the IConfigAsfWriter interface method ConfigureFilterUsingProfile.

Two of the user settings, for multipass encoding and frame indexing, are implemented in calls to IConfigAsfWriter methods. These are user options, so they are obviously not required steps. First, a call to SetIndexMode turns off automatic indexing of the ASF file. We want frame-based indexing, not temporal indexing (based against the REFERENCE_TIME of the samples), so we have to turn off the latter to prevent automatic index generation. (We ll turn on frame indexing just as we finish the entire transcoding process.) Next, we call the SetParam method, with a parameter value of AM_CONFIGASFWRITER_MULTPASS to tell the filter that multipass encoding will be used. As you ll see later, when multipass encoding is enabled, the filter graph is run twice, sequentially, so the encoder can process the streams twice.

The source filter is added with a call to the IGraphBuilder method AddSourceFilter, and then control passes to the local function RenderOutputPins, which attempts to detect all the streams presented by the source filter and connects them to the WM ASF Writer filter:

HRESULT RenderOutputPins(IBaseFilter *pFilter, IGraphBuilder* pGB) { CComPtr <IEnumPins> pEnum; CComPtr <IPin> pPin; CComQIPtr<IFilterGraph2, &IID_IFilterGraph2> pFilterGraph2(pGB); HRESULT hr = pFilter->EnumPins(&pEnum); if (FAILED(hr)) { return hr; } while(pEnum->Next(1, &pPin, 0) == S_OK) { PIN_DIRECTION PinDirThis; pPin->QueryDirection(&PinDirThis); if (PINDIR_OUTPUT == PinDirThis) { pFilterGraph2->RenderEx(pPin, AM_RENDEREX_RENDERTOEXISTINGRENDERERS, NULL); if (FAILED(hr)) { DbgLog((LOG_TRACE, 3, _T("Failed to render source filter pin (hr=0x%08x)!\n"), hr)); return hr; } } pPin.Release(); } return hr; }

RenderOutputPins enumerates the pins on the source filter; for each output pin, the IFilterGraph2 method RenderEx is invoked. RenderEx forces rendering of a media stream through to renderer filters already present in the filter graph. In this case, this can only mean the WM ASF Writer filter because it s the only renderer filter in the filter graph. Every stream presented by the source filter is connected perhaps through some intermediate conversion filters (using Intelligent Connect) to the WM ASF Writer filter. With the connections made, control passes back to MakeAsfFile:

 // Verify that all of our input pins are connected. // If the profile specifies more streams // than are actually contained in the source file, // the filter will not run. So here we check // for the condition ourselves and fail gracefully if necessary. hr = VerifyInputsConnected(pASFWriter); if(FAILED(hr)) { OutputMsg(_T("Cannot encode this file because not all input pins were connected. The profile specifies more input streams than the file contains. Aborting copy operation. \r\n")); DbgLog((LOG_TRACE, 3, _T("Not all inputs connected! hr=0x%x\n"), hr)); return hr; } // To support profiles that were created // in the Windows Media Profile Editor, we need to // handle the case where the user selected // the "Video Size Same as Input" option. This causes // the video rectangle in the profile to be set to zero. // The WM Encoder understands the "zero" value // and obtains the source video size before encoding. // If we don't do the same thing, we will create a // valid ASF file but it will have no video frames. // We have waited until now to check the input rectangle // size because it is most efficient to do this // after the filter graph has been built. if(bZeroSizedVidRect) { hr = SetNativeVideoSize(pASFWriter, pProfile1, FALSE); if(FAILED(hr)) { DbgLog((LOG_TRACE, 3, _T("Failed to SetNativeVideoSize! hr=0x%x\n"), hr)); return hr; } hr = pConfigAsfWriter2->ConfigureFilterUsingProfile(pProfile1); if(FAILED(hr)) { DbgLog((LOG_TRACE, 3, _T("Failed ConfigureFilterUsingProfile-2-! hr=0x%x\n"), hr)); return hr; } OutputMsg(_T("Filter was successfully reconfigured for native video size.\r\n")); }

VerifyInputsConnected walks through the list of input pins on the WM ASF Writer filter, verifying that all the inputs are connected. If any input pins are left unconnected, a mismatch of some sort has occurred between the media streams presented by the File Source filter on its output pins and the number of streams expected by the WM ASF Writer filter based on the profile we gave it. The output profile must match the input media streams exactly, or the filter graph won t execute. VerifyInputsConnected lets us fail gracefully if a problem is found. (Strictly speaking, this step is optional, but the application could crash without this check!)

Now that we have a filter graph, we can discover what our input stream looks like, so once again we call SetNativeVideoSize this time modifying the profile not with dummy values but with the dimnsions of the actual video stream. We pass FALSE as the final parameter, which forces SetNativeVideoSize to issue a call to the local function GetVideoSizeFromPin. GetVideoSizeFromPin retrieves the AM_MEDIA_TYPE associated with all pins on a filter, looks for a FORMAT_VideoInfo or FORMAT_VideoInfo2 format type, and extracts the video size from the fields associated with those formats.



Programming Microsoft DirectShow for Digital Video and Television
Programming Microsoft DirectShow for Digital Video and Television (Pro-Developer)
ISBN: 0735618216
EAN: 2147483647
Year: 2002
Pages: 108
Authors: Mark D. Pesce

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net