Search code examples
c++videodelaydirectshowinfinite-loop

DirectShow loop video infinitely with C++


I have this small C++ code playing a video clip with DirectShow. What I want is the video to never stop so once it reaches the end I set the position to 0 again. The problem is that between the end and the start of the new loop there's a small delay that I would like to remove. My code looks like this:

#define WM_GRAPHNOTIFY  WM_USER

// Global vars
IGraphBuilder*      g_pGraphBuilder;
IMediaControl*      g_pMediaCtrl;
IMediaPosition*     g_pMediaPos;
IMediaEventEx*      g_pMediaEvent;

HWND                h_MainWindow;

// PlayVideo() - I removed the errors checking lines (irrelevant right now)
RECT grc;
IVideoWindow *pVidWin = NULL;
HRESULT hr = CoInitialize(NULL);

hr = CoCreateInstance(
    CLSID_FilterGraph, 
    NULL, 
    CLSCTX_INPROC_SERVER,
    IID_IGraphBuilder,
    (void**)&g_pGraphBuilder
);

hr = g_pGraphBuilder->RenderFile(L"Clip.mpeg", NULL);
hr = g_pGraphBuilder->QueryInterface(IID_IVideoWindow, (void **)&pVidWin);
hr = pVidWin->put_Owner((OAHWND)h_MainWindow);
hr = pVidWin->put_WindowStyle(WS_CHILD | WS_CLIPSIBLINGS);

GetClientRect(h_MainWindow, &grc);
pVidWin->SetWindowPosition(0, 0, grc.right, grc.bottom);

hr = g_pGraphBuilder->QueryInterface(IID_IMediaEventEx, (void **)&g_pMediaEvent);
hr = g_pGraphBuilder->QueryInterface(IID_IMediaPosition, (void**)&g_pMediaPos);
hr = g_pMediaEvent->SetNotifyWindow((OAHWND)h_MainWindow, WM_GRAPHNOTIFY, 0);
g_pMediaEvent->SetNotifyFlags(0);

hr = g_pGraphBuilder->QueryInterface(IID_IMediaControl, (void **)&g_pMediaCtrl);
g_pMediaCtrl->Run();

// WndPrc of the main window, the WM_GRAPHNOTIFY message
long EventCode, Param1, Param2;
// ...
case WM_GRAPHNOTIFY:
    while (g_pMediaEvent->GetEvent(&EventCode, &Param1, &Param2, 0)!=E_ABORT) {
        switch (EventCode) {
            case EC_COMPLETE:
                // Going back to the start of the clip
                g_pMediaPos->put_CurrentPosition(0);
            break;
            default:
            break;
        }   
        g_pMediaEvent->FreeEventParams(EventCode, Param1, Param2);
    }
break;
// ...

The problem here, as I said, is that there's a noticeable lag when the video stops and starts again. It looks like EC_COMPLETE is not sent exactly when the video stops or maybe it is but put_CurrentPosition() takes some time to set the position back to 0. Anyway, the problem is that and I wonder if there's a solution to it.


Solution

  • There is no standard solution for looped playback, yet the task is not impossible and is doable with reasonable effort.

    The wrong way is to achieve the desired behavior with standard graph/pipeline expecting some magic to perform a seamless seek operation on the upstream part of the graph. The graph streams data in a very efficient way and preloads the pipeline to play data accurately. However once you reached the end of the file, there is inevitable seek and state transition which involves a delay

    You need either of the two (both approaches require development effort):

    1. a smart custom filter which intercepts completion (end of stream) message and seeks the upstream part, or in case of small stream, buffers data entirely and continues seamlessly from internal buffer (good for small streams)
    2. a two-graph design where one part is reading from file and sending data to another graph for playback, and the other graph handling endless playback from injected data; you can seek first graph normally without delay propagating to the other graph

    Update. Since the media file is short (sort of animation perhaps), the item #1 above looks applicable. The goal is that buffering filter accepts everything up to completion, keeps this on internal buffer, and streams further from internal buffer.

    • Source -> ... (demultiplexer etc) -> Buffer -> Decompressor -> Video Renderer
    • Source -> ... (demultiplexer etc) -> Decompressor -> Buffer -> Video Renderer

    Both options achieve the goal and you choose between the two depending on memory/speed requirements and preferences.

    The best base for the Buffer filter is CBaseFilter.