Microsoft DirectX 8.0

Seeking Within a Stream

This section describes how an application can seek to different positions within a media stream. It contains the following topics:

Overview

Microsoft® DirectShow® supports seeking through the IMediaSeeking interface. The filter graph manager exposes this interface, but the seeking functionality is always implemented by filters.

The application queries the filter graph manager for IMediaSeeking and uses it to issue seek commands. The filter graph manager distributes the seek commands to all of the renderer filters in the graph, again using the IMediaSeeking interface. Each renderer passes the commands upstream, through the output pins of the upstream filters, until they reach a filter that can execute them. Typically a source filter or a parser filter, such as the AVI Splitter, carries out the seek operation.

When a filter performs a seek operation, it flushes any pending data. The downstream filters also flush their data. For more information, see Data Flow for Filter Developers. The result is to minimize the latency of seek commands, because existing data is flushed from the graph. The following diagram illustrates the sequence of events.

If a parser filter has more than one output pin, it typically designates one of them to accept seek commands. The other pins reject or ignore any seek commands they receive. In this way, the parser keeps all of its streams synchronized.

Seeking Capabilities

Some data cannot be seeked. For example, you cannot seek a live video stream from a camera. If a stream is seekable, however, there are various types of seeking it might support. These include:

The IMediaSeeking interface defines a set of flags, AM_SEEKING_SEEKING_CAPABILITIES, that describe the possible seeking capabilities. To retrieve the stream's capabilities, call the IMediaSeeking::GetCapabilities method. The method returns a bitwise combination of flags. The application can test them using the & (bitwise AND) operator. For example, the following code checks whether the graph can seek to an arbitrary position:

DWORD dwCap = 0;
HRESULT hr = pSeek->GetCapabilities(&dwCap);
if (AM_SEEKING_CanSeekAbsolute & dwCap)
{
    // Graph can seek to absolute positions.
}

An alternate method is IMediaSeeking::CheckCapabilities, which tests a subset of the seeking capabilities. The caller passes a bitwise combination of flags, as follows:

// Set flags for the capabilities you want to check.
DWORD dwCaps = AM_SEEKING_CanSeekAbsolute | 
               AM_SEEKING_CanSeekForwards |
               AM_SEEKING_CanSeekBackwards;

HRESULT hr = pMediaSeeking->CheckCapabilities(&dwCaps);

If the stream supports all of the indicated capabilities, the return value is S_OK. If it supports none of them, the method returns an error code. If the stream supports a subset of them, the return value is S_FALSE. When the method returns, the input parameter contains a bitwise combination of the supported capabilities, from the original set that was passed in. You can test the result as follows:

if (hr == S_FALSE) // Some requested capabilities are not supported.
{
    if (dwCaps & AM_SEEKING_CanSeekAbsolute)
        // This capability is supported.
        ...
} 

Time Formats

The IMediaSeeking interface supports several time formats, including media time (in 100-nanosecond units), video frames, and byte offsets within the stream. Each time format is defined by a globally unique identifier (GUID). For a list of the time formats that DirectShow defines, see Time Format GUIDs. Third parties can also define GUIDs for custom time formats.

The filter graph manager defaults to media time. To use another time format, call the IMediaSeeking::SetTimeFormat method. If the method succeeds, all subsequent seek commands use the new format. To determine whether the media stream supports a given time format, call the IMediaSeeking::IsFormatSupported method. The following example sets the time format to frames and stores the result in a Boolean flag, for later use:

hr = pSeek->SetTimeFormat(&TIME_FORMAT_FRAME);
BOOL bTimeInFrames = SUCCEEDED(hr);

Setting and Retrieving the Stream Position

The filter graph maintains two position values: the current position and the stop position:

To retrieve the current position, call the IMediaSeeking::GetCurrentPosition method. To retrieve the stop position, call the IMediaSeeking::GetStopPosition method. The returned values are in units of the current time format (see preceding section). They are always relative to the original media source.

To seek to a new position, or to set a new stop position, call the IMediaSeeking::SetPositions method. The following code shows an example:

#define ONE_SECOND 10000000
REFERENCE_TIME rtNow = 2 * ONE_SECOND, rtStop = 5 * ONE_SECOND;

hr = pSeek->SetPositions(
    &rtNow,  AM_SEEKING_AbsolutePositioning, 
    &rtStop, AM_SEEKING_AbsolutePositioning
    );

This example assumes that the current time format is media time, which has units of 100 nanoseconds. One second is 10,000,000 units. For convenience, the example defines this value as ONE_SECOND.

Note  If you are using the DirectShow base classes, the constant UNITS is defined, which has the same value. For more information, see Base Classes.

The rtNow parameter specifies the new position. The second parameter is a flag that defines how to interpret rtNow. The flag in this example indicates that rtNow is defined as an absolute position in the source. When the call returns, the filter graph seeks to a position that is two seconds from the start of the stream. The rtStop parameter gives the stop time. The last parameter is another flag, indicating that the stop time is also an absolute position.

To specify a position relative to the previous position value, use the AM_SEEKING_RelativePositioning flag. To leave one of the position values unchanged, use the AM_SEEKING_NoPositioning flag. The corresponding time parameter can be NULL in that case. The following example seeks forward by 10 seconds, leaving the stop position unchanged:

REFERENCE_TIME rtNow = 10 * ONE_SECOND;
hr = pSeek->SetPositions(
    &rtNow, AM_SEEKING_RelativePositioning, 
    NULL, AM_SEEKING_NoPositioning
    );

When the filter graph is stopped, the video renderer does not update its image after a seek operation. To the user, it will appear as if the seek did not happen. To update the image, pause the graph after the seek operation. Pausing the graph cues a new video frame for the video renderer. The IMediaControl::StopWhenReady method pauses the graph and then returns it to a stopped state.

Setting the Playback Rate

To change the playback rate, call the IMediaSeeking::SetRate method. Specify the new rate as a fraction of the original rate. For example, to play at twice-normal speed, use the following:

pSeek->SetRate(2.0)

The rate must be greater than zero. Rates greater than one are faster than normal. Rates between zero and one are slower than normal.

Regardless of the playback rate, the current position and the stop position are always expressed relative to the original source. For example, if a source file is 20 seconds long (at normal playback rate), setting the current position to 10 seconds seeks to the middle of the file. If the stop position is 20 seconds (the end of the file) and the playback rate is 2.0, the file will play for another 5 seconds of real time: 10 seconds of source file, at twice the normal playback speed.

Case Study: Implementing a Seek Bar

This section describes how to implement a seek bar for a media-player application. The seek bar is implemented as a trackbar control.

When the application starts, initialize the trackbar:

HWND hTrack = GetDlgItem(hwnd, IDC_SLIDER1);
EnableWindow(hTrack, FALSE);
SendMessage(hTrack, TBM_SETRANGE, TRUE, MAKELONG(0, 100));

The trackbar is disabled until the user opens a media file. The trackbar range is from 0 to 100. The application converts from media time to a percentage of the file duration. For example, trackbar position "50" always corresponds to the middle of the file. (An alternate approach would be setting the trackbar's maximum range to the duration of the file. However, media time is a 64-bit integer, and trackbar ranges are limited to 32 bits.)

When the user opens a file, the application builds a file-playback graph. It queries the filter graph manager for the IMediaSeeking interface (not shown). The application then checks whether the graph can seek and whether it can retrieve the file duration. If the graph supports both of these capabilities, the application retrieves the duration and enables the trackbar:

REFERENCE_TIME g_rtTotalTime;
DWORD caps = AM_SEEKING_CanSeekAbsolute | AM_SEEKING_CanGetDuration; 

if (pSeek && S_OK == pSeek->CheckCapabilities(&caps)) 
{
    pSeek->GetDuration(&g_rtTotalTime);
    EnableWindow(hTrack, TRUE);
}

When the user issues a "Play" command, the application runs the graph. It also starts a timer event that periodically updates the trackbar position. As each timer event is activated, the timer callback retrieves the current playback position and sets the trackbar accordingly:

#define TICKLEN 100   // Tick length (frequency of trackbar update).
MMRESULT wTimerID;

void CALLBACK MediaTimer(UINT wTimerID, UINT msg, 
    DWORD dwUser, DWORD dw1, DWORD dw2) 
{ 
    REFERENCE_TIME rtNow;
    pSeek->GetCurrentPosition(&rtNow);
    long sliderTick = (long)((rtNow * 100) / g_rtTotalTime);
    SendMessage( hScroll, TBM_SETPOS, TRUE, sliderTick );
} 

void OnUserPlay()
{
    pGraph->Run();
    if(wTimerID)        // Is timer event pending?
    {                
        timeKillEvent(wTimerID);  // Cancel the timer.
        wTimerID = 0;
    }
    wTimerID = timeSetEvent(TICKLEN, 50, MediaTimer, NULL, TIME_PERIODIC);
}

When the user stops or pauses the graph, the application cancels the timer event.

When the user drags or clicks the trackbar control, the application receives a WM_HSCROLL event. The low word of the wParam parameter is the trackbar notification message. The relevant messages are TB_ENDTRACK, which is sent at the end of the trackbar action, and TB_THUMBTRACK, which is sent continuously while the user drags the trackbar.

The following function handles both messages:

void HandleTrackbar(HWND hTrackbar, short int userReq)
{
// If the file is not seekable, the trackbar is disabled. 
// Therefore, seeking is valid here.

    static OAFilterState state;
    static BOOL bStartOfScroll = TRUE;

    if (userReq == TB_ENDTRACK || userReq == TB_THUMBTRACK)
    {
        DWORD Position  = SendMessage(hTrackbar, TBM_GETPOS, 0, 0);

        // Pause when the scroll action begins.
        if (bStartOfScroll) 
        {       
            pControl->GetState(10, &state);
            bStartOfScroll = FALSE;
            pControl->Pause();
        }
        
        // Update the position continuously.
        REFERENCE_TIME newTime = (g_rtTotalTime * Position) / 100;
        pSeek->SetPositions(&newTime, AM_SEEKING_AbsolutePositioning,
            NULL, AM_SEEKING_NoPositioning);

        // Restore the state at the end.
        if (userReq == TB_ENDTRACK)
        {
            if (state == State_Stopped)
                pControl->Stop();
            else if (state == State_Running) 
                pControl->Run();
            bStartOfScroll = TRUE;
        }
    }
}

In order to seek smoothly while the user drags the trackbar, the function pauses the graph until the user releases the trackbar. If the graph was running, paused mode prevents the graph from continuing to play while the user is dragging the control. If the graph was stopped, paused mode ensures that the video window is updated. On receiving the TB_ENDTRACK message, the function restores the graph to its original state.