views:

644

answers:

1

I am attempting to use DirectShow to play two AVI files consecutively (one after the other) so that there is no interruption in the audio or video when the player transitions from one file to the next.

I have two custom controls on my form. Each one is pre-loaded with an AVI file, and before playback begins I set up all the DirectShow interfaces, set the video windows and resize them, call IMediaControl.Run(), then IMediaControl.Pause(), then IMediaSeeking.SetPositions to reset to frame 0, on both controls. On the form, you can see that both files are paused at their initial frames.

I then call IMediaControl.Run() on the first control, and wait for it to complete before calling Run() on the second control. Initially, I hooked into the first video's EC_COMPLETE notification message, and used this to start the second. Thinking that this event might be slow to arrive (turns out it is, but for a weird reason), I tried two other approaches:

  1. Check the first video's current position inside a timer that goes off every second or so (using IMediaPosition.get_CurrentPosition). When the current position is within a second of the video's stop time (known in advance from IMediaPosition.get_StopTime), I go into a tight while loop and wait for the current position to equal the stop time, and then call Run() on the second video.
  2. Same as the first, except I replace the while loop with a call to timeSetEvent from winmm.dll, with a delay set so that it fires right when the first file is supposed to end. I use the callback to Run() the second file.

Either of these two methods substantially cuts down the delay between the end of the first file and the beginning of the second, indicating that the EC_COMPLETE message doesn't arrive immediately after the file is complete (I also tried hooking the EC_SEGMENT_COMPLETE message, which is supposed to be used for looping within a file, but apparently nobody supports this - it never occurs on my machine, at least).

Doing all of the above has cut the transition delay from as much as a second, down to a barely perceptible glitch; about a third of the time the files transition with no interruption at all, which suggests there's no fundamental reason I can't get this to work all the time.

The slight delay is still unacceptable, unfortunately. I assume (and I could easily be wrong) that the remaining delay is due to a slight variable delay between the call to IMediaControl.Run() and when the video actually starts playing.

Does anybody know anything I can do to eliminate this little lag? It would also help to be told this is fundamentally impossible for whatever reason, which wouldn't surprise me. I've never encountered a video player in Windows that doesn't have this problem, so it may not be doable.

More info: the AVI files I'm playing are completely uncompressed (video and audio are uncompressed), so I don't think the lag is due to DirectShow's having to uncompress the video ahead of play start, although it may still buffer ahead as matter of course (and this may be the source of the problem). I would have though that starting play, pausing and then rewinding to the beginning would fix this.

Also, the way I'm handling the transition is to actually have the second control underneath the first; when the first completes playing, I start the second and then call BringToFront on it, creating the appearance of a single video transitioning between the two originals. I don't think the glitch is due to this, because it works perfectly some of the time, and even if this were problematic, it wouldn't explain the matching audio glitch.

Even more: I just tried starting the second video 30-50 milliseconds "early" and that seemed to eliminate even more of the gap, so I'm guessing that the lag in Run() is about that long. It appears to be variable, though, so this is still not where I need it to be.

Still more: perhaps I could eliminate this delay by loading the AVIs from memory rather than from a file. Unfortunately, I have no idea how to do this. IMediaControl only has a RenderFile() method, not something like a RenderStream or RenderMemory method.

+1  A: 

If you call IMediaControl::Run on a stopped graph, the graph manager will post the call to a worker thread (so there's some variability). On the worker thread, the graph will be paused. Render filters only complete a pause transition once they have received data, so once GetState() returns S_OK, the graph manager knows that the graph is fully cued. At this point, it picks a time roughly 10ms into the future, and calls Run on each filter with that time as the start point. Since it takes time to tell each filter to Run, the dshow Run method has a parameter which is the refclock time at which a sample timestamped zero should be played -- i.e. the time at which the actual transition to run mode should take place.

To synchronise this with another graph, you first have to ensure that both graphs have the same clock. Query the graph (not the filter) for IMediaFilter, and call GetSyncSource on one graph and SetSyncSource on the other. Then you need to pause the second graph, so that it is cued and ready. When you want to start it, call IMediaFilter::Run instead of IMediaControl::Run, and you can pass your own start time. This still has to be a few milliseconds into the future, so the best thing might be to set the start time of the second graph to be the first graph's start time plus its duration (for an indexed container of uncompressed streams, the duration should be accurate).

Another approach is to use multiple graphs. Separating source from rendering would allow you to switch seamlessly between sources since they feed into a common render graph. There is sample source code for this approach at www.gdcl.co.uk/gmfbridge.

G

Geraint Davies
Awesome. I was hoping that someone here would have some idea of what I was talking about. I saw the sync methods earlier and figured that they would be the way to do something like this. Do you think what I'm doing is fundamentally possible? I'm afraid that one of the factors that might kill this is the presence of WAV data in the AVI files. Being off by 1 millisecond on the video transition is not perceptible, but being that far off on the audio definitely would be.
MusiGenesis
In order to do this, I may have to handle the audio separately, unless there's some way to tell DirectShow to prioritize the audio over the video? With the waveOut... API in windows, you can queue up multiple audio buffers and windows will play them one after the other seamlessly; I'm not sure DirectShow can do this, since that wouldn't really be a priority with a primarily video playback mechanism.
MusiGenesis
Thanks again. Switching to IMediaFilter.Run(0) instead of IMediaControl.Run() made my existing mechanism work better. I'm having a weird problem, though: whenever I set the `tStart` parameter to a non-zero value, the audio portion correctly starts at whatever delay I set it to (I know now it's in nanoseconds, which makes me realize how dumb my previous comment was), but the video starts immediately and plays at maximum speed, dumping out all the frames in one burst.
MusiGenesis
In the example code I'm working from, the IMediaFilter interface was never instantiated or used anywhere, so perhaps I'm missing some prior step in its usage? (the `mediaFilter.Run(tStart)` is the only reference to the filter anywhere in the code now)
MusiGenesis
Rereading your original answer, I realize that you were talking about syncing the two graphs, but at this point I'm only playing one file (to learn what effect it has on things). Before, the audio and video always played in sync and at the correct overall speed, but perhaps that was just lucky? and they need to be explicitly synced in some way if I want to start them at a later time.
MusiGenesis
Also, I'm checking out your article code samples, and hopefully I can answer my questions there. I didn't look before, but I obviously should have. :)
MusiGenesis
No I'm talking about syncing the start of one clip to the end of the previous clip. This will not affect the relative timing of the audio and video within individual clips. There's too many points here in the comments to answer in comments, really. Dshow timestamps are in 100 nanosecs, not in nanosecs (so 10000 to a millisecond). You can certainly cue audio for playback in the way you describe. IMediaFilter::Run(0) won't work -- the tStart parameter is an absolute clock time (IReferenceClock::GetTime).In any case, I still think the best way to do seamless joins is a multi-graph approach.
Geraint Davies
If your video runs way too fast, that typically means the video renderer filter's clock has been set to null. It will push frames as fast as it can instead of scheduling samples with the clock.
Alan