Microsoft DirectX 8.0

Constructing a Timeline

This article describes how to construct a timeline in Microsoft® DirectShow® Editing Services (DES). It presents an example console application that creates a timeline and renders it. The timeline is minimal, consisting of a single video group with one source clip, but it demonstrates most of the concepts needed to create more complex timelines.

This article contains the following topics.

Creating Timeline Objects

The sample code presented in this article starts with an empty timeline, but the same steps apply if you load an existing project and want to add objects to it.

To create any type of object in the timeline, call the IAMTimeline::CreateEmptyNode method and specify the major type to create, such as composition, group, or track. For example, the following code creates a new group.

IAMTimelineObj *pGroupObj = NULL;
pTL->CreateEmptyNode(&pGroupObj, TIMELINE_MAJOR_TYPE_GROUP);

The CreateEmptyNode method creates the object and returns a pointer to the object's IAMTimelineObj interface. It also increments the reference count on the IAMTimelineObj interface, so you must release the interface when you finish using it. Do not call the CoCreateInstance function. Instead, always use CreateEmptyNode to create a timeline object, because it initializes the new object for use in a timeline.

The IAMTimelineObj interface is a generic interface. It provides methods that are common to all types of timeline object. Each type of object exposes other interfaces as well. For example, groups expose the IAMTimelineGroup interface, among others. You can obtain pointers to the other interfaces by calling QueryInterface.

After you create an object, it is not yet a part of the timeline. The method to add an object to the timeline depends on the object type. The following section describes how to add groups, compositions, and tracks to the timeline.

Creating Groups, Compositions, and Tracks

Groups, compositions, and tracks are the intermediate layers between the timeline and the source clips. They are distinguished by the type of object they can contain.

The following interfaces are exposed by these objects:

InterfaceExposed By
IAMTimelineTrackTracks
IAMTimelineVirtualTrackTracks, Compositions
IAMTimelineCompCompositions, Groups
IAMTimelineGroupGroups

These interfaces contain the methods for adding objects to the timeline.

For example, the following code inserts a new track into a group. As shown in the previous table, a group is considered a kind of composition, and a track is a kind of virtual track. Therefore, to insert the track into the group, you must query the group for its IAMTimelineComp interface and call the IAMTimelineComp::VTrackInsBefore method.

IAMTimelineGroup    *pGroup;
// Create a new group (not shown). 

IAMTimelineComp     *pComp = NULL;
IAMTimelineObj      *pTrackObj = NULL;

pTL->CreateEmptyNode(&pTrackObj, TIMELINE_MAJOR_TYPE_TRACK);
pGroup->QueryInterface(IID_IAMTimelineComp, (void **)&pComp);
pComp->VTrackInsBefore(pTrackObj, 0);

The second parameter to VTrackInsBefore specifies the priority of the virtual track. Priority levels start at zero. If you specify the value –1, the virtual track is inserted at the end of the priority list. If you specify a value where there is already a virtual track, everything from that point on moves down the list by one priority level. Do not insert a virtual track at a priority greater than the number of virtual tracks, because it will cause undefined behavior.

To delete an object permanently from the timeline, call IAMTimelineObj::RemoveAll on the object. This method removes the object and all its children. To remove an object for the purpose of reinserting it elsewhere in the timeline, call IAMTimelineObj::Remove instead. Unlike RemoveAll, this method does not delete the object's children. To remove everything from the timeline, call IAMTimeline::ClearAllGroups.

Setting the Group Media Type

All groups must define an uncompressed media type, either audio or video. The uncompressed media type is the format that viewers see or hear during playback. Typically, the final output will be in a compressed format. For more information, see Rendering a Project.

To set the uncompressed format, create an AM_MEDIA_TYPE structure and fill it with the appropriate major type, subtype, and format header. For video, set the width, height, and bit depth in the format header. For audio, set the sample rate in the format header. If you set only the major type, DES provides reasonable defaults for the other values. In practice, you should set the values explicitly to control the output. The CMediaType base class provides a convenient way to manage the AM_MEDIA_TYPE structure. If you prefer, you can work directly with the AM_MEDIA_TYPE structure.

After initializing the media type structure, call the IAMTimelineGroup::SetMediaType method to set the media type for the group.

The following example specifies 16-bit RGB video, 320 pixels wide by 240 pixels high.

AM_MEDIA_TYPE mtGroup;  
mtGroup.majortype = MEDIATYPE_Video;
mtGroup.subtype = MEDIASUBTYPE_RGB555;

// Set format headers.
mtGroup.pbFormat = (BYTE*)CoTaskMemAlloc(sizeof(VIDEOINFOHEADER));
VIDEOINFOHEADER *pVideoHeader = (VIDEOINFOHEADER*)mtGroup.pbFormat;
ZeroMemory(pVideoHeader, sizeof(VIDEOINFOHEADER));
pVideoHeader->bmiHeader.biBitCount = 16;
pVideoHeader->bmiHeader.biWidth = 320;
pVideoHeader->bmiHeader.biHeight = 240;
pVideoHeader->bmiHeader.biPlanes = 1;
pVideoHeader->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
pVideoHeader->bmiHeader.biSizeImage = DIBSIZE(pVideoHeader->bmiHeader);

mtGroup.formattype = FORMAT_VideoInfo;
mtGroup.bFixedSizeSamples = TRUE;
mtGroup.lSampleSize = DIBSIZE(pVideoHeader->bmiHeader);

// Set the group media type.
pGroup->SetMediaType( &mtGroup );

Adding a Source

Create a source object the same way you create other timeline objects. Before inserting it into the timeline, however, you must specify at least the following properties on the source.

In the following example, the source clip starts four seconds into the file. The media duration is 10 seconds, twice the length of the timeline duration, meaning the source will play at twice normal speed. The constant UNITS is defined as 10000000 (107) and equals one second.

pSourceObj->SetStartStop(0, 50000000)
pSource->SetMediaName(L"C:\\Example.avi");
pSource->SetMediaTimes(40000000, 140000000);

Note  Currently, DES cannot simultaneously render more than 75 sources that were compressed with Video Compression Manager (VCM) codecs. Also, if the project as a whole contains more than 75 such sources, you must use dynamic reconnection or DES cannot preview the project. For more information, see IRenderEngine::SetDynamicReconnectLevel.

For more information about sources, see Working with Sources.

Sample Code

The following is the complete code for the sample application described in this article.

Note  For the sake of brevity, the sample code performs no error checking. In a real application, you should check the return values of method calls to make sure none has failed.

#include <dshow.h>
#include <qedit.h>

// 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);
    pControl->Run();

    long evCode;
    pEvent->WaitForCompletion(INFINITE, &evCode);
    pControl->Stop();

    // Clean up.
    pEvent->Release();
    pControl->Release();
    pGraph->Release();
}

void main( void )
{
    // Start by making an empty timeline.

    IAMTimeline    *pTL = NULL;
    CoInitialize(NULL);
    CoCreateInstance(CLSID_AMTimeline, NULL, CLSCTX_INPROC_SERVER, 
        IID_IAMTimeline, (void**)&pTL);

    // GROUP: Add a video group to the timeline. 

    IAMTimelineGroup    *pGroup = NULL;
    IAMTimelineObj      *pGroupObj = NULL;
    pTL->CreateEmptyNode(&pGroupObj, TIMELINE_MAJOR_TYPE_GROUP);
    pGroupObj->QueryInterface(IID_IAMTimelineGroup, (void **)&pGroup);

    // Set the group media type.
    AM_MEDIA_TYPE mtGroup;  
    ZeroMemory(&mtGroup, sizeof(AM_MEDIA_TYPE));
    mtGroup.majortype = MEDIATYPE_Video;
    pGroup->SetMediaType(&mtGroup);
    pTL->AddGroup(pGroupObj);
    pGroupObj->Release();

    // TRACK: Add a track to the group. 

    IAMTimelineObj      *pTrackObj;
    IAMTimelineTrack    *pTrack;
    IAMTimelineComp     *pComp = NULL;

    pTL->CreateEmptyNode(&pTrackObj, TIMELINE_MAJOR_TYPE_TRACK);
    pGroup->QueryInterface(IID_IAMTimelineComp, (void **)&pComp);
    pComp->VTrackInsBefore(pTrackObj, 0);
    pTrackObj->QueryInterface(IID_IAMTimelineTrack, (void **)&pTrack);

    pTrackObj->Release();
    pComp->Release();    
    pGroup->Release();

    // SOURCE: Add a source to the track.

    IAMTimelineSrc *pSource = NULL;
    IAMTimelineObj *pSourceObj;
    pTL->CreateEmptyNode(&pSourceObj, TIMELINE_MAJOR_TYPE_SOURCE);
    pSourceObj->QueryInterface(IID_IAMTimelineSrc, (void **)&pSource);

    // Set the times and the file name.
    pSourceObj->SetStartStop(0, 50000000);
    pSource->SetMediaName(L"C:\\Example.avi");
    pSource->SetMediaTimes(40000000, 140000000);
    pTrack->SrcAdd(pSourceObj);

    pSourceObj->Release();
    pSource->Release();
    pTrack->Release();

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

    // Clean up.
    pRenderEngine->ScrapIt();
    pTL->ClearAllGroups();
    pRenderEngine->Release();
    pTL->Release();
    CoUninitialize();
}