Programming DES

Programming DES

The following application, DESClip, displays a File dialog box asking the user to point to an AVI file. DESClip uses this clip as the foundation for a new movie, adding a five-second clip to the front of it, transitioning to the clip, which plays in its entirety, and then following with a five-second closing clip. The whole movie is scored with a soundtrack that replaces any native sounds that might be part of the source clips.

Creating a Timeline and Track Group

We ll take a look at the entire application, beginning with main.

int main(int argc, char* argv[]) { // Let's get a media file to render. if (!GetMediaFileName()) { return 0; } // Start by making an empty timeline. Add a media detector as well. IAMTimeline *pTL = NULL; IMediaDet *pMD = NULL; CoInitialize(NULL); HRESULT hr = CoCreateInstance(CLSID_AMTimeline, NULL, CLSCTX_INPROC_SERVER, IID_IAMTimeline, (void**)&pTL); hr = CoCreateInstance(CLSID_MediaDet, NULL, CLSCTX_INPROC_SERVER, IID_IMediaDet, (void**)&pMD); // GROUP: Add a video group to the timeline. IAMTimelineGroup *pGroup = NULL; IAMTimelineObj *pGroupObj = NULL; hr = pTL->CreateEmptyNode(&pGroupObj, TIMELINE_MAJOR_TYPE_GROUP); hr = pGroupObj->QueryInterface(IID_IAMTimelineGroup, (void **)&pGroup); // Set the group media type. This example sets the type to "video" and // lets DES pick the default settings. For a more detailed example, // see "Setting the Group Media Type." AM_MEDIA_TYPE mtGroup; ZeroMemory(&mtGroup, sizeof(AM_MEDIA_TYPE)); mtGroup.majortype = MEDIATYPE_Video; hr = pGroup->SetMediaType(&mtGroup); hr = pTL->AddGroup(pGroupObj); pGroupObj->Release();

First we make a call to get a valid AVI file name. COM is initialized, and instead of the normal CoCreateInstance call to create a Filter Graph Manager, we pass the GUID CLSID_AMTimeline, which returns an IAMTimeline object, the DES object that gives you overall control of the timeline. This call is immediately followed by another call to CoCreate Instance with the GUID CLSID_MediaDet, which returns an IMediaDet object. This object will be very useful later on when we re poking at our source clips, trying to learn their durations and what media types they contain.

Now that we ve got an IAMTimeline object, we can start to create objects that drop into the timeline. The basic method for creating an object is CreateEmptyNode; the second parameter defines the object being created. In this case, TIMELINE_MAJOR_TYPE_GROUP is used to create a group, that is, the object that contains either the entire video timeline or the entire audio timeline. The next calls establish the media type for the timeline group. First a structure of type AM_MEDIA_TYPE is created, and its majortype field is initialized with the value MEDIATYPE_Video. In this case, DES picks the default output format, including the image size and the bit depth of the image. (Later we ll discuss how to establish our own media parameters using the AM_MEDIA_TYPE structure.) Once that s done, a call is made to the IAMTimelineGroup interface on the IAMTimelineObj object, the SetMediaType method. This call establishes that this timeline group is exclusively for video (we ll do the same for audio later), and then the timeline group is added to the master timeline with a call to the IAMTimeline method AddGroup.

Adding a Track to a Timeline

It takes only a few lines of code to add a track to the timeline, as shown here:

 // TRACK: Add a track to the group. IAMTimelineObj *pTrackObj; IAMTimelineTrack *pTrack; IAMTimelineComp *pComp = NULL; hr = pTL->CreateEmptyNode(&pTrackObj, TIMELINE_MAJOR_TYPE_TRACK); hr = pGroup->QueryInterface(IID_IAMTimelineComp, (void **)&pComp); hr = pComp->VTrackInsBefore(pTrackObj, 0); hr = pTrackObj->QueryInterface(IID_IAMTimelineTrack, (void **)&pTrack); pTrackObj->Release();

Once again, a call is made to the IAMTimeline method CreateEmptyNode, this time with TIMELINE_MAJOR_TYPE_TRACK as the passed parameter. This call returns an IAMTimelineObj object. Another query returns the IAMTimeline Comp interface from the group object. (A group in DES is also considered a composition object because it contains one or more tracks.) The IAMTimeline Comp interface is needed so that a call can be made to its VTrackInsBefore method. This method places the tracks together, front to back. There can be many tracks in a composition, so this call allows DES to assign an order to the tracks. A value of zero places the track in front of all other tracks, while higher values send the track behind tracks with lower priority values. After the track has been prioritized, we get the IAMTimelineTrack interface for it. This interface will be used as source clips are added to the track.

Adding a Source Clip

Adding a source clip in this case, a video clip because we re working within the video track group is a bit more complex than adding a track.

 // SOURCE: Add a source clip to the track. IAMTimelineSrc *pSource = NULL; IAMTimelineObj *pSourceObj; hr = pTL->CreateEmptyNode(&pSourceObj, TIMELINE_MAJOR_TYPE_SOURCE); hr = pSourceObj->QueryInterface(IID_IAMTimelineSrc, (void **)&pSource); // Set the times and the file name. BSTR bstrFile = SysAllocString(OLESTR("C:\\DShow.avi")); hr = pSource->SetMediaName(bstrFile); SysFreeString(bstrFile); hr = pSource->SetMediaTimes(0, 50000000); hr = pSourceObj->SetStartStop(0, 50000000); hr = pTrack->SrcAdd(pSourceObj); pSourceObj->Release(); pSource->Release();

Once again, we begin with a call to the IAMTimeline method CreateEmptyNode; this time the passed parameter is TIMELINE_MAJOR_TYPE_SOURCE, which returns an IAMTimelineObj that we immediately query to get its IAMTimelineSrc interface. With this interface, we can set the parameters for this clip. First a call to the IAMTimelineSrc method SetMediaName sets the file name of the clip. (The file name can include full path information. In this case, we ve hardcoded the path and required both DShow.avi and MSPress.avi to be in C:\. You wouldn t do this in a production program, of course!) The start and stop positions within the clip are set with a call to the IAMTimelineSrc method SetMediaTimes. These values are given in reference time that is, units of 100 nanoseconds so five seconds (the duration of this clip) has the outlandish reference time value of 50 million. This call ensures that the full five seconds of the clip are placed into the track. (We know how long the clip is because we created it earlier.) This call is followed by a call to the IAMTimeline Obj method SetStartStop, which positions the clip within the track. These values, 0 and 50 million, set the clip so that it begins to play at the start of the track and continues for five seconds just the length of the clip. If we had passed a range in SetStartStop of less than 50 million, the source clip would have been sped up so that the full length of it, set by the call to SetMediaTimes, would have been played in the allotted period. Finally the source clip s duration has been set, and its start and stop positions on the track have been established. The source clip is added to the track with a call to the IAMTimelineTrack method SrcAdd.

Adding a Transition

Because this is an opening clip, we want to add a nice video transition to the track, which will take us into the clip the user selected at application launch.

 // TRANSITION: Add a transition object to the track. // Note that the GUID for the transition is hard-coded, // but we'd probably want the user to select it. IAMTimelineObj *pTransObj = NULL; pTL->CreateEmptyNode(&pTransObj, TIMELINE_MAJOR_TYPE_TRANSITION); hr = pTransObj->SetSubObjectGUID(CLSID_DxtJpeg); // SMPTE Wipe hr = pTransObj->SetStartStop(40000000, 50000000); // The last second IAMTimelineTransable *pTransable = NULL; hr = pTrack->QueryInterface(IID_IAMTimelineTransable, (void **)&pTransable); hr = pTransable->TransAdd(pTransObj); IAMTimelineTrans *pTrans = NULL; hr = pTransObj->QueryInterface(IID_IAMTimelineTrans, (void**)&pTrans); hr = pTrans->SetSwapInputs(true);

Yet again, we begin with a call to CreateEmptyNode, passing TIMELINE_MAJOR_TYPE_TRANSITION, which returns an IAMTimelineObj that contains our transition element. The transition is selected with a call to the IAMTimelineObj method SetSubObjectGUID. This GUID is predefined as CLSID_DxtJpeg, which wipes the screen from this video source to a subsequent one (which we haven t yet put into a track). We want this transition to take one second, and we want it to be the last second of the clip; given this information, we can make a call to SetStartStop with the start value of 40 million, and a stop value of 50 million, which means that the transition will run between seconds 4 and 5 of the track. Next, to add the transition to the track, we get the IAMTimelineTransable interface from the track object and then call the IAMTimelineTransable method TransAdd. The transition has now been added to the track.

Finally we need to modify one of the parameters of the transition; we want to wipe from this track to another track, which is the inverse of the way this transition operates in its default state. To do this, we get the IAMTimelineTrans interface from the IAMTimelineObj and then make a call to SetSwap Inputs, which tells the transition to transition from this source clip to another, rather than from another source clip to this one.

Setting Transition Properties

The SMPTE Wipe transition is common, but it comes in many different flavors; you can wipe from the bottom of the screen to the top, right to left, and so on. DES provides about 100 different versions of the transition, and we re going to use one that features a triangle, point upward, coming out of the center of the screen. To do that, we re going to have to manipulate the transition s properties, as shown here:

 // PROPERTY: Set a property on this transition. IPropertySetter *pProp; // Property setter hr = CoCreateInstance(CLSID_PropertySetter, NULL, CLSCTX_INPROC_SERVER, IID_IPropertySetter, (void**) &pProp); DEXTER_PARAM param; DEXTER_VALUE *pValue = (DEXTER_VALUE*)CoTaskMemAlloc(sizeof(DEXTER_VALUE)); // Initialize the parameter. param.Name = SysAllocString(L"MaskNum"); param.dispID = 0; param.nValues = 1; // Initialize the value. pValue->v.vt = VT_BSTR; pValue->v.bstrVal = SysAllocString(L"103"); // Triangle, up pValue->rt = 0; pValue->dwInterp = DEXTERF_JUMP; hr = pProp->AddProp(param, pValue); // Free allocated resources. SysFreeString(param.Name); VariantClear(&(pValue->v)); CoTaskMemFree(pValue); // Set the property on the transition. hr = pTransObj->SetPropertySetter(pProp); pProp->Release(); pTrans->Release(); pTransObj->Release(); pTransable->Release(); pTrack->Release();

To set the properties on our wipe transition, we need to call CoCreate Instance to create an IPropertySetter object. Now we allocate and populate two structures, DEXTER_PARAM and DEXTER_VALUE. (Dexter was the internal name for DES at Microsoft, and it stuck around in these structure names.) The DEXTER_PARAM field Name contains a string that identifies the parameter we want to modify. (The content of this field varies enormously, depending on the transition you re using, and it can be found in the DirectX SDK documentation for the DirectShow transitions. Other transitions are installed with Microsoft Internet Explorer; information on those transitions can be found online). Because we re passing only one parameter, nValues is set to 1. The DEXTER_VALUE structure is now loaded with the specific values to be passed along with the parameter. In this case, the wipe we re using is number 103, which is placed into the structure. Once both data structures have been initialized, they re applied to the IPropertySetter object with a call to the AddProp method. After that, the IAMTimelineObj method SetPropertySetter is invoked and the property is applied to the transition. Now, when the movie is rendered, the transition will use the appropriate mask.

Determining the Media Type of a Source Clip

We re done with the first track, which contains only the opening clip and a transition. Now we need to create another track because a transition operates on source images contained in two separate tracks. The second track will contain the source clip selected by the user and our closing clip.

 // TRACK: Add another video track to the Timeline. hr = pTL->CreateEmptyNode(&pTrackObj, TIMELINE_MAJOR_TYPE_TRACK); hr = pComp->VTrackInsBefore(pTrackObj, 0); hr = pTrackObj->QueryInterface(IID_IAMTimelineTrack, (void **)&pTrack); pTrackObj->Release(); // SOURCE: Add a source to the track. hr = pTL->CreateEmptyNode(&pSourceObj, TIMELINE_MAJOR_TYPE_SOURCE); hr = pSourceObj->QueryInterface(IID_IAMTimelineSrc, (void **)&pSource); // Set file name. #ifndef UNICODE WCHAR wFileName[MAX_PATH]; MultiByteToWideChar(CP_ACP, 0, g_PathFileName, -1, wFileName, MAX_PATH); BSTR bstrFile = SysAllocString((const OLECHAR*) wFileName); // This is all that's required to create a filter graph // that will render a media file! #else BSTR bstrFile = SysAllocString((const OLECHAR*) g_PathFileName); #endif hr = pSource->SetMediaName(bstrFile); // Figure out how big the track is, and add it in at that length. // We'll use the IMediaDet interface to do this work. hr = pMD->put_Filename(bstrFile); double psl = 0; hr = pMD->get_StreamLength(&psl); REFERENCE_TIME pSourceLength = psl * 10000000; // Convert units hr = pSource->SetMediaTimes(0, pSourceLength); hr = pSourceObj->SetStartStop(40000000, 40000000+pSourceLength); hr = pTrack->SrcAdd(pSourceObj); SysFreeString(bstrFile); pSourceObj->Release(); pSource->Release(); // SOURCE: Add another source to the track, after that sample. hr = pTL->CreateEmptyNode(&pSourceObj, TIMELINE_MAJOR_TYPE_SOURCE); hr = pSourceObj->QueryInterface(IID_IAMTimelineSrc, (void **)&pSource); // Set the times and the file name. hr = pSourceObj->SetStartStop(40000000+pSourceLength, 40000000+pSourceLength+50000000); bstrFile = SysAllocString(OLESTR("C:\\MSPress.avi")); hr = pSource->SetMediaName(bstrFile); SysFreeString(bstrFile); hr = pSource->SetMediaTimes(00000000, 50000000); hr = pTrack->SrcAdd(pSourceObj); pSourceObj->Release(); pSource->Release(); pComp->Release(); pTrack->Release(); pGroup->Release();

We add a track using a clone of the code that we used to add the original video track, and then we add a source clip to the track. This point is when we use the IMediaDet object that instantiated during program initialization. We call its put_Filename method, which now allows the object to query the media file for many of its properties. Of concern to us is the duration of the clip; we re going to need to know how long the clip is before we can know where to drop a follow-on clip on the timeline. We get this value (in seconds) returned with a call to get_StreamLength. This number has to be converted into reference time, so it s multiplied by 10 million. This value is then used in subsequent calls to Set MediaTime and SetStartStop, positioning the clip accurately on the track and starting the clip 4 seconds into the timeline, allowing the opening clip to roll on until it begins its transition to this clip. The clip is added with a call to SrcAdd.

The follow-on clip is added with a few more lines of code, and the values passed in SetStartStop reflect the length of the source clip which precedes it, which keeps the timeline clean and consistent. Multiple clips on the same track can t occupy the same time on the timeline.

That finishes up the video group of tracks; we now have two tracks. The first track has an opening clip and a transition, while the second track has the user-selected clip with a follow-on clip.

Adding Audio Tracks

To add audio tracks to this movie, we need to create a new track group, like this:

 // GROUP: Add an audio group to the timeline. IAMTimelineGroup *pAudioGroup = NULL; IAMTimelineObj *pAudioGroupObj = NULL; hr = pTL->CreateEmptyNode(&pAudioGroupObj, TIMELINE_MAJOR_TYPE_GROUP); hr = pAudioGroupObj->QueryInterface(IID_IAMTimelineGroup, (void **)&pAudioGroup); // Set the group media type. // We'll use the IMediaDet object to make this painless. AM_MEDIA_TYPE amtGroup; // Soundtrack file to use. bstrFile = SysAllocString(OLESTR("C:\\FoggyDay.wav")); hr = pMD->put_Filename(bstrFile); hr = pMD->get_StreamMediaType(&amtGroup); hr = pAudioGroup->SetMediaType(&amtGroup); hr = pTL->AddGroup(pAudioGroupObj); pAudioGroupObj->Release(); // TRACK: Add an audio track to the group. IAMTimelineObj *pAudioTrackObj; IAMTimelineTrack *pAudioTrack; IAMTimelineComp *pAudioComp = NULL; hr = pTL->CreateEmptyNode(&pAudioTrackObj, TIMELINE_MAJOR_TYPE_TRACK); hr = pAudioGroup->QueryInterface(IID_IAMTimelineComp, (void **)&pAudioComp); hr = pAudioComp->VTrackInsBefore(pAudioTrackObj, 0); hr = pAudioTrackObj->QueryInterface(IID_IAMTimelineTrack, (void **)&pAudioTrack); pAudioTrackObj->Release(); pAudioComp->Release(); pAudioGroup->Release(); // SOURCE: Add another source to the track. hr = pTL->CreateEmptyNode(&pSourceObj, TIMELINE_MAJOR_TYPE_SOURCE); hr = pSourceObj->QueryInterface(IID_IAMTimelineSrc, (void **)&pSource); // Set the times and the file name. hr = pSourceObj->SetStartStop(0, 50000000+pSourceLength+50000000); hr = pSource->SetMediaName(bstrFile); SysFreeString(bstrFile); hr = pSource->SetMediaTimes(00000000, 50000000+pSourceLength+50000000); hr = pAudioTrack->SrcAdd(pSourceObj); pSourceObj->Release(); pSource->Release(); pAudioTrack->Release();

We create a new track group using the CreateEmptyNode call with the parameter TIMELINE_MAJOR_TYPE_GROUP, just as we did when we created the video track group. The difference comes when we set the media type for the group, and here again we use the IMediaDet object. First we call put_Filename with the name of the soundtrack file. (Doing so is a bit of a cheat because we don t add this source clip until a bit further along, but it s perfectly legal DirectShow programming.) Now we call the IMediaDet method get_StreamMediaType, which returns an AM_MEDIA_TYPE data structure packed with data that describes the particulars of the media in the audio source clip. The group becomes the audio group with a call to the IAMTimelineGroup method SetMediaType, which gets passed the same AM_MEDIA_TYPE structure that was returned by the call to get_StreamMediaType. Note that this will work only if the audio is uncompressed (in other words, WAV files will work, but MP3s will not) and DES supports the audio type.

The track group type determines the output type of the movie created by DirectShow. If you provide only a major type, such as MEDIATYPE_Video (as we did in our video track group), DirectShow will make some assumptions about the kind of formats it will output assumptions you might or might not be happy with. You ll get better results if you set the output media type by building it from scratch. DES won t generate every video type or audio type. Source samples can be in any supported audio or video type, and the filter graph will automatically convert them when the movie is rendered.

Once the audio track group has been added to the timeline, a track is created and added to the timeline, and the source clip is created, has its start and stop points set so that they match the duration of the overall video clip, and is added to the track. At this point, we re done with the audio source clips, audio tracks, and audio group. We re ready to render this movie.

Rendering a Preview Movie

In DirectShow, the timeline is rendered with an object known as the IRender Engine. It s created with a COM call, as shown here:

 // Preview the timeline. IRenderEngine *pRenderEngine = NULL; CoCreateInstance(CLSID_RenderEngine, NULL, CLSCTX_INPROC_SERVER, IID_IRenderEngine, (void**) &pRenderEngine); PreviewTL(pTL, pRenderEngine);

Once the IRenderEngine object has been instantiated, we can send a preview of the movie (so that you can inspect your work) to the display with a call to the local function PreviewTL.

// Preview a timeline. void PreviewTL(IAMTimeline *pTL, IRenderEngine *pRender) { IGraphBuilder *pGraph = NULL; IMediaControl *pControl = NULL; IMediaEvent *pEvent = NULL; // Build the graph. pRender->SetTimelineObject(pTL); pRender->ConnectFrontEnd( ); pRender->RenderOutputPins( ); // Run the graph. pRender->GetFilterGraph(&pGraph); pGraph->QueryInterface(IID_IMediaControl, (void **)&pControl); pGraph->QueryInterface(IID_IMediaEvent, (void **)&pEvent); SaveGraphFile(pGraph, L"C:\\MyGraph_preview.GRF"); // Save the graph // file to disk pControl->Run(); long evCode; pEvent->WaitForCompletion(INFINITE, &evCode); pControl->Stop(); // Clean up. pEvent->Release(); pControl->Release(); pGraph->Release(); }

The IRenderEngine object has three methods that speed the rendering of a movie preview, beginning with a call to SetTimelineObject, which binds the movie s timeline to the IRenderEngine object. This call is followed by a call to ConnectFrontEnd, which builds the front end of the filter graph, that is, everything except for the filters used to render the movie. That call is followed immediately by a call to RenderOutputPins, which completes the filter graph construction, building a filter graph that will preview the movie.

Next we use some code that should look very familiar: we acquire a pointer to the Filter Graph Manager, use it to query for interfaces to IMedia Control and IMediaEvent, and then execute the IMediaControl method Run. At this point, the Active Movie window will open on the display, and the movie will begin to render, as shown in Figure 8-2.

figure 8-2 the timeline rendering to a preview window

Figure 8-2. The timeline rendering to a preview window

When the filter graph finishes its execution, it receives a Stop message and the function exits. As you can see, most of the work required to render a timeline to the display is done in three method calls made to the IRenderEngine object.

Keep in mind that preview versions of movies can look choppy and their audio can get out of sync with the video portion of the movie. Neither will happen when the movie is written to a disk file, but they can possibly happen during preview particularly on underpowered computers.

Rendering a Timeline to an AVI File

It s a little more complicated to render a timeline to a disk-based AVI file. Instead of rendering to a preview window, we must build a filter graph that creates an output file and sends the rendered movie to that file.

 pRenderEngine->ScrapIt(); WriteTL(pTL, pRenderEngine, L"C:\\MyMovie.avi"); // Clean up. pRenderEngine->ScrapIt(); pRenderEngine->Release(); pMD->Release(); pTL->Release(); CoUninitialize(); return 0; }

First we need to destroy the filter graph we created on the call to PreviewTL by calling the IRenderEngine method ScrapIt. If you don t call ScrapIt, subsequent calls to the IRender Engine object won t work correctly. With our cleanup complete, we can create the AVI file with a call to the local function WriteTL.

// Write a timeline to a disk file. void WriteTL(IAMTimeline *pTL, IRenderEngine *pRender, WCHAR *fileName) { IGraphBuilder *pGraph = NULL; ICaptureGraphBuilder2 *pBuilder = NULL; IMediaControl *pControl = NULL; IMediaEvent *pEvent = NULL; // Build the graph. HRESULT hr = pRender->SetTimelineObject(pTL); hr = pRender->ConnectFrontEnd( ); hr = CoCreateInstance(CLSID_CaptureGraphBuilder2, NULL, CLSCTX_INPROC, IID_ICaptureGraphBuilder2, (void **)&pBuilder); // Get a pointer to the graph front end. hr = pRender->GetFilterGraph(&pGraph); hr = pBuilder->SetFiltergraph(pGraph); // Create the file-writing section. IBaseFilter *pMux; hr = pBuilder->SetOutputFileName(&MEDIASUBTYPE_Avi, fileName, &pMux, NULL); long NumGroups; hr = pTL->GetGroupCount(&NumGroups); // Loop through the groups and get the output pins. for (int i = 0; i < NumGroups; i++) { IPin *pPin; if (pRender->GetGroupOutputPin(i, &pPin) == S_OK) { // Connect the pin. hr = pBuilder->RenderStream(NULL, NULL, pPin, NULL, pMux); pPin->Release(); } } // Run the graph. hr = pGraph->QueryInterface(IID_IMediaControl, (void **)&pControl); hr = pGraph->QueryInterface(IID_IMediaEvent, (void **)&pEvent); SaveGraphFile(pGraph, L"C:\\MyGraph_write.GRF"); // Save the graph // file to disk hr = pControl->Run(); long evCode; hr = pEvent->WaitForCompletion(INFINITE, &evCode); hr = pControl->Stop(); // Clean up. if (pMux) { pMux->Release(); } pEvent->Release(); pControl->Release(); pGraph->Release(); pBuilder->Release(); }

As in PreviewTL, we begin with calls to the IRenderEngine methods SetTimelineObject and ConnectFrontEnd. However, because we want to render this file to disk, we need to take it step-by-step hereafter. We do so by creating an ICaptureGraphBuilder2 object, which we ll use as the basis for a filter graph that will take the capture source (in this case, the portion of the filter graph created by the render engine) and send it out to a capture file. We need to give the ICaptureGraphBuilder2 object a pointer to the Filter Graph Manager, so we retrieve it with a call to the IRenderEngine method GetFilterGraph. Next we call the ICaptureGraphBuilder2 method SetOutputFileName, passing it our requested output media type (MEDIATYPE_Avi, which will produce an AVI file). That call will instantiate an AVI Mux filter and a file writer, and it will return a pointer to the AVI Mux filter in pMux.

Now we have to make an inquiry of the timeline, calling the IAMTimeline method GetGroupCount, which returns the number of track groups within the movie. We should have two track groups one for video and one for audio and each of these will need to be rendered separately. Inside of a loop, we call the IRenderEngine method GetGroup OutputPin, which returns an IPin corresponding to an output pin on a filter created by the call to ConnectFrontEnd. As we find each output pin, we call RenderStream on each pin, connecting the IRenderEngine-created portion of the filter graph to the AVI file writer through the AVI Mux. Once that s done, we run the filter graph, which renders the movie to a disk file. And that s it the movie is now on disk, ready to be viewed. When we return from the function, we do a little cleanup, close COM, and then exit the application.

The code in this example creates an uncompressed video file. You can encode the video to a compressed format (such as Windows Media) by inserting an encoder filter after the output pin. Specify the encoder filter in the fourth parameter to RenderStream.

IBaseFilter *pEncoder; // Pointer to the encoder filter // Create the encoder filter (not shown). hr = pBuilder->RenderStream(NULL, NULL, pPin, pEncoder, pMux);

Alternatively, you can use the Smart Render Engine instead of the Render Engine. The Smart Render Engine automatically compresses the video stream to the desired format. The section Rendering a Project in the DirectShow SDK documentation has more information on how this engine works.

Improving Video Output Quality

The example we ve given here creates an output file that has only half the image width and height of the source AVI file because we used the default video parameters when we established the media type for the video track. Setting these parameters explicitly would have created a high-quality video output. To ensure high-quality output, which you might want if you re creating an AVI file that you ll want to write to a digital camcorder, the media subtype for the video group should be either RGB24 or RGB32. Set the dimensions of the video group equal to the size of the largest video clip (in the case of DV, 720480 pixels), and set the stretch mode on each clip to crop by calling IAMTimelineSrc::GetStretchMode with the RESIZEF_CROP flag. This flag prevents DES from stretching the source video to cover the entire area defined for the video group. Alternatively, you can write your own custom video resizer. (This feature is new in DirectX 9.) The default resizer in DES uses StretchBlt to resize the video, which is fast but does not produce the best quality. A number of better (but slower) algorithms exist for resizing video.

One final note: although we created an output file in AVI format, you could rewrite this application to create Windows Media files, which would lead to smaller file sizes. To do so, you d pass MEDIASUBTYPE_Asf instead of MEDIASUBTYPE_Avi to SetOutputFileName in WriteTL. You d also need to set the profile parameters for the output file, and you ll learn how to do that in Chapter 15.



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