Sign in to follow this  
AyaKoshigaya

Playing AVI Files using DirectShow

Recommended Posts

Hi, I need to Playblack AVI Videos in my Application and need support to step trough the Movie Frame by Frame. Currently I'm using the VFW-API for this, works very well but doesn't support all Codecs (i.E. xVid videos doesn't work).. so I thought I could switch over to DirectX for this. But, I now searched the whole web I think.. how can I step trough an Video like I can in VFW??! I have no problems Playing the AVI Video and grav the Frames via SampleGrabber, but FrameStepping.. - no chance :( I tried using the IVideoFrameStep Interface, but it seems like it doesn't work. The best thing would be, if I could call "getFrame(int frame)" and would get the data for this Frame, like in VFW. (This can be done using this Poster-Frame Interface, but that's way to slow) So, if anyone have any Idea how to do this.. please help me :) Au'revoir, Aya~

Share this post


Link to post
Share on other sites
IVideoFrameStep seems to be exactly what you want to use, so I would search out any documentation/samples/forum posts related to that. Alternatively, you could try IMediaSeeking. Here is an MSDN post that kind of sounds like your problem.

Share this post


Link to post
Share on other sites
Hi,

I don't know what I am doing wrong, but when I try to seek the Video with the IMediaSeeking Interface it allways jumps back to Frame 0.

This is my Initialisation:

  CoCreateInstance(CLSID_FilterGraph, nil, CLSCTX_INPROC, IID_IGraphBuilder, pGraph);
CoCreateInstance(CLSID_SampleGrabber, nil, CLSCTX_INPROC_SERVER, IID_IBaseFilter, pGrabberF);
CoCreateInstance(CLSID_NullRenderer, nil, CLSCTX_INPROC_SERVER, IID_IBaseFilter, pNull);
pGraph.QueryInterface(IID_IMediaControl, pControl);
pGraph.QueryInterface(IID_IMediaEvent, pEvent);
pGraph.QueryInterface(IID_IMediaSeeking, pSeeking);
pGraph.QueryInterface(IID_IMediaFilter, pFilter);

pSeeking.SetTimeFormat(TIME_FORMAT_FRAME);

pFilter.SetSyncSource(nil);

pGraph.AddFilter(pGrabberF, 'Sample Grabber');
pGraph.AddFilter(pNull, 'Null Render');
pGrabberF.QueryInterface(IID_ISampleGrabber, pGrabber);

ZeroMemory(@mType, SizeOf(AM_MEDIA_TYPE));
mType.majortype := MEDIATYPE_Video;
mType.subtype := MEDIASUBTYPE_RGB24;
pGrabber.SetMediaType(mType);

pGraph.AddSourceFilter(Filename, 'Source', pSrc);

ConnectFilters(pGraph, pSrc, pGrabberF);
ConnectFilters(pGraph, pGrabberF, pNull);

pGrabber.SetOneShot(false);
pGrabber.SetBufferSamples(true);

GrabberCB:=TSampleGrabberCB.Create;
GrabberCB.Parent:=Self;
pGrabber.SetCallback(GrabberCB, 1);



When I now use:
  pControl.Run;

the Video is Played back.

But when I call
  pSeeking.SetPositions(XXX, AM_SEEKING_AbsolutePositioning, 0, AM_SEEKING_NoPositioning);

where XXX is any number you want, it just jumps back to Frame 0.

Does anyone know, why?

Aya~

Share this post


Link to post
Share on other sites
As Circlesoft says, you could use IVideoFrameStep for this. However, I don't recommend that for two reasons:

1. It only supports forward stepping

2. It isn't necessarily consistent in stepping. For example, one step may jump 1001 reference time units. The next step would jump around 300,000 time units (which, in my case, was actually close to the number of reference time units representing one frame).

Regarding your code, there are two problems I see:

1. You don't query the seeking interface to see whether it supports TIME_FORMAT_FRAME. Many filters don't--for example, WMV filters on my PC don't.

2. Even if TIME_FORMAT_FRAME is supported, some filters always return 0 as the current position, as well as silently fail to seek to any position as you have encountered (that is, returning S_OK even though they didn't seek).

In conclusion, use manual seeking instead:

void MediaPlayer::StepFrames(int numFrames)
{
if(m_state != MediaPlayerState::Paused)
Pause();

LONGLONG timePerFrame = ReferenceTimePerSecond / FrameRate();

LONGLONG currentPos;
DSE(m_mediaSeeking->GetCurrentPosition(&currentPos));

LONGLONG timeDelta = timePerFrame * abs(numFrames);

if(numFrames > 0)
{
currentPos += timeDelta;

if(currentPos > Duration())
currentPos = Duration();
}
else
{
currentPos -= timeDelta;

if(currentPos < 0)
currentPos = 0;
}

DSE(m_mediaSeeking->SetPositions(&currentPos, AM_SEEKING_AbsolutePositioning,
0, AM_SEEKING_NoPositioning));
}




In the above code, DSE is simply a macro that checks for failure and throws an exception.

Share this post


Link to post
Share on other sites
I know this is not an answer to your question, but I have been trying very very hard to playback videos in my game with DirectShow.
It never really worked as it should have. I mean: SLOW SLOW SLOW!
Even when you give the video stream enough time to work, it still skips frames, because of its internal low priority threads which you can 't change in a normal way.

I've tried using VFW (Video For Windows), of course this only works on windows, but it works much better. Only problem there is codecs, it doesn't play everything and if it plays, it sometimes doesn't really work as it should.

The final and really successful try was: FFMpeg, and this is REALLY good! I don't say it's flawless, but after including the libraries and compiling the example you see on the website, I had enough info to play back video's of lots of supported video formats, even without their codecs. Plus: it's fast!

Don't know if you needed this info, but if you're trying to implement videos in your game and want them to be smooth... don't go with DirectShow.

Share this post


Link to post
Share on other sites
Hi,

thanks a lot for your answers! :)

Muhammad Haggag: What is "ReferenceTimePerSecond" in your code example?? ^^

scippie: Sound's cool, I'll take a look at it :)

Aya~

Share this post


Link to post
Share on other sites
Quote:
Muhammad Haggag: What is "ReferenceTimePerSecond" in your code example?? ^^


// Constant: ReferenceTimePerSecond
// The number of reference time units per second. DirectShow uses a time unit where each
// corresponds to 100 nanoseconds. The media player uses the same units.
//
static const LONGLONG ReferenceTimePerSecond = 1e7;


Quote:
I know this is not an answer to your question, but I have been trying very very hard to playback videos in my game with DirectShow.
It never really worked as it should have. I mean: SLOW SLOW SLOW!

I'm curious--how do you use DirectShow in your game? Do you use it to play cutscenes, or are you using it to render to textures mid-game?

If it's for cutscenes, it should be fast enough--there should be nothing else working in the background. As far as rendering-to-texture using VMR, I recall that there was a D3D sample that did this, and was fast as well.

Share this post


Link to post
Share on other sites
Quote:
Original post by AyaKoshigaya
Hi,

thanks, Seeking works now. But.. it's not Frame by Frame.. more like 20 Frame Jumps or something :(

The time delta jumped per-frame depends on your frame rate. Are you sure the frame rate value you use is correct?

Share this post


Link to post
Share on other sites
Hi,

I got the FrameRate from the IMediaSeeking-Interface:

  pSeeking.GetRate(framesPerSecond);


framesPerSecond is declared as 'Double'

var
currentFrame: Int64;
ReferenceTimePerSecond, timeDelta, timePerFrame: Double;
begin
ReferenceTimePerSecond:=1e7;
timePerFrame:=(ReferenceTimePerSecond / framesPerSecond);
pSeeking.GetCurrentPosition(currentFrame);
timeDelta:=timePerFrame * FramesToStep;
currentFrame:=currentFrame + Round(timeDelta);

pSeeking.SetPositions(currentFrame, AM_SEEKING_AbsolutePositioning, 0, AM_SEEKING_NoPositioning);


That is what I'm doing, anything wrong?
(Except the things to check if currentFrame > Duration etc, it's the same as your code I think)

Aya~

Share this post


Link to post
Share on other sites
Quote:
Original post by AyaKoshigaya
Hi,
I got the FrameRate from the IMediaSeeking-Interface:
pSeeking.GetRate(framesPerSecond);

This is what messed things up--you're getting the playback rate here, not the frame rate. The playback rate is, by default, 1.0, which means the video is running at a speed of 1X (i.e. normal speed). If your video has a frame-rate of 30 frames per second, then each of your steps would jump 30 frames (because of the above call, you divided by 1 instead of 30, so you jump 30 times the delta you should jump).

Retrieve the frame rate as follows, through the IBasicVideo interface:

double MediaPlayer::FrameRate() const
{
AssertPlayerValidity();

double timePerFrameInSeconds;
DSE(m_basicVideo->get_AvgTimePerFrame(&timePerFrameInSeconds));
return 1.0 / timePerFrameInSeconds;
}



Quote:
That is what I'm doing, anything wrong?
(Except the things to check if currentFrame > Duration etc, it's the same as your code I think)

Don't store your variables as double's--only FrameRate. Everything else (time-related) should be int64's. In large videos, you get very large numbers very fast (since we're operating at a granluarity of 100 nanoseconds), which are stored with much less precision as doubles (i.e. you'll be actually losing precision and performing inaccurate calculations that lead to more loss with doubles).

Share this post


Link to post
Share on other sites
Hi,

mhh.. it's weird.. I'm now Query'ing the IBaseVideo, but it seems like it's not working with the SampleGrabber..

  pGraph.QueryInterface(IID_IBasicVideo, pVideo);


If I try to get the VideoWith for example, I allways get 0 (it doesn't change the passed Variable).

If I now remove these 3 Lines:
  GrabberCB:=TSampleGrabberCB.Create;
GrabberCB.Parent:=Self;
pGrabber.SetCallback(GrabberCB, 1);


and load the File via
pControl.RenderFile(Filename);


it's working.
Why doesn't it work with the SampleGrabber and the AddSourceFilter?

Aya~

Share this post


Link to post
Share on other sites
I don't know. I tried setting a callback in one of the DirectShow.NET samples (DxPlay), and I can still retrieve an IBasicVideo through the graph builder. What is the HRESULT returned by QueryInterface? Perhaps it contains some useful information.

Also, if possible, post your full graph creation code--perhaps there's something wrong with it.

Share this post


Link to post
Share on other sites
Hi,

this is the complete Creation Block:

var
timePerFrame: Double;
begin
CoCreateInstance(CLSID_FilterGraph, nil, CLSCTX_INPROC, IID_IGraphBuilder, pGraph);
CoCreateInstance(CLSID_SampleGrabber, nil, CLSCTX_INPROC_SERVER, IID_IBaseFilter, pGrabberF);
CoCreateInstance(CLSID_NullRenderer, nil, CLSCTX_INPROC_SERVER, IID_IBaseFilter, pNull);
pGraph.QueryInterface(IID_IMediaControl, pControl);
pGraph.QueryInterface(IID_IMediaEvent, pEvent);
pGraph.QueryInterface(IID_IMediaSeeking, pSeeking);
pGraph.QueryInterface(IID_IMediaFilter, pFilter);
pGraph.QueryInterface(IID_IBasicVideo, pVideo);
pGrabberF.QueryInterface(IID_ISampleGrabber, pGrabber);

pFilter.SetSyncSource(nil);

pGraph.AddFilter(pGrabberF, 'Sample Grabber');
pGraph.AddFilter(pNull, 'Null Render');

ZeroMemory(@mType, SizeOf(AM_MEDIA_TYPE));
mType.majortype := MEDIATYPE_Video;
mType.subtype := MEDIASUBTYPE_RGB24;
pGrabber.SetMediaType(mType);

pGraph.AddSourceFilter(Filename, 'Source', pSrc);
ConnectFilters(pGraph, pSrc, pGrabberF);
ConnectFilters(pGraph, pGrabberF, pNull);

pGrabber.SetOneShot(false);
pGrabber.SetBufferSamples(true);

//If I Remove these 3 Lines...
GrabberCB:=TSampleGrabberCB.Create;
GrabberCB.Parent:=Self;
pGrabber.SetCallback(GrabberCB, 1);

//And add this one, it works
//pControl.RenderFile(Filename);

pSeeking.GetDuration(videoDuration);
pControl.Pause;

//Width is still -1 after get_VideoWidth..
Width:=-1;
pVideo.get_VideoWidth(Width);
ShowMessage(IntToStr(Width));


Anything wrong with it?

Aya~


PS: Here are the Functions like ConnectFilters I use in the above code:

function ConnectFilters(var pGraph: IGraphBuilder; var pSrc,
pDest: IBaseFilter): HResult;
var
pOut: IPin;
hr: HResult;
begin
if (pGraph = nil) or (pSrc = nil) or (pDest = nil) then begin
Result:=E_POINTER;
Exit;
end;
hr:=GetUnconnectedPin(pSrc, PINDIR_OUTPUT, pOut);
if Failed(hr) then begin
Result:=hr;
Exit;
end;
hr:=ConnectFilters(pGraph, pOut, pDest);
Result:=hr;
end;

function ConnectFilters(var pGraph: IGraphBuilder;
var pOut: IPin; pDest: IBaseFilter): HResult;
var
pIn: IPin;
hr: HResult;
begin
if (pGraph = nil) or (pOut = nil) or (pDest = nil) then begin
Result:=E_POINTER;
Exit;
end;
hr:=GetUnconnectedPin(pDest, PINDIR_INPUT, pIn);
if Failed(hr) then begin
Result:=hr;
Exit;
end;
hr:=pGraph.Connect(pOut, pIn);
Result:=hr;
end;

function GetUnconnectedPin(var pFilter: IBaseFilter;
pinDir: PIN_DIRECTION; var ppPin: IPin): HResult;
var
hr: HResult;
pTmp, pPin: IPin;
pEnum: IEnumPins;
thisPinDir: PIN_DIRECTION;
begin
ppPin:=nil;
pEnum:=nil;
pPin:=nil;
hr:=pFilter.EnumPins(pEnum);
if Failed(hr) then begin
Result:=hr;
Exit;
end;
while pEnum.Next(1, pPin, nil) = S_OK do begin
pPin.QueryDirection(thisPinDir);
if thisPinDir = pinDir then begin
hr:=pPin.ConnectedTo(pTmp);
if Failed(hr) then begin
ppPin:=pPin;
Result:=S_OK;
Exit;
end;
end;
end;
Result:=E_FAIL;
end;

Share this post


Link to post
Share on other sites
I don't see anything wrong, but I'm no DirectShow expert. I'll try your code myself later today after work (read: at least 8 hours later). For the time being, you can use a workaround: Use IMediaDet to get the average frame rate. Look it up in MSDN--it's a totally stand-alone interface (i.e. not related to the graph builder) that's intended as a helper utility for retrieving media information, as well as grabbing media frames.

Share this post


Link to post
Share on other sites
Got it! [smile]

First of all, it has nothing to do with the sample grabber callback--even if you comment out the code that sets the callback, it still fails.

The problem is that, according to the Null renderer documentation page, that renderer doesn't support the IBasicVideo interface. That's why when you used RenderFile (which uses a video renderer filter or directdraw filter or whatever's available other than the null one), it worked.

I verified this using the following code (pardon me, C++):

// ...
// DSE(ConnectFilters(m_graphBuilder, sampleGrabberF, nullRenderer));
CComPtr<IPin> pin;
DSE(GetUnconnectedPin(sampleGrabberF, PINDIR_OUTPUT, &pin));
DSE(m_graphBuilder->Render(pin));



If you try the above, it'll work.

Share this post


Link to post
Share on other sites
Quote:
Original post by AyaKoshigaya
But, I'm a bit confuesed.. what do I have to change in my code to make it work?
Do I only have to add these 3 lines? o.O

You have to use a renderer other than the null renderer (i.e. remove the null renderer creation and connection, and use the code I posted instead).

Share this post


Link to post
Share on other sites
Quote:
Original post by AyaKoshigaya
But, if I don't use the Null-Renderer a Window Pops-Up displaying the Video (the Grabber still works anyway).. but I don't want that window o.O

Look up the functions in IVideoWindow. There's something called put_AutoShow IIRC with which you can disable this behavior (as well as other video window control functions).

If that doesn't work for you (though I'm sure it should), create the graph twice--once with a renderer to get the required information (frame-rate and all), and then another time with a null renderer to do the grabbing. This is what IMediaDet does, by the way, judging from the way it works (once you start grabbing, you can no longer query for video information)

Share this post


Link to post
Share on other sites
Hi,

oook, it's working now, thank's a lot!! *hugs* :)

But I still have one more question, is there a way to disable the FrameDropping? If I grab Frames very fast some Frmaes are Skipped.. :(

I need something like "Play as fast as possible" :)

Aya~

Share this post


Link to post
Share on other sites
Quote:
Original post by AyaKoshigaya
But I still have one more question, is there a way to disable the FrameDropping? If I grab Frames very fast some Frmaes are Skipped.. :(

Hmm..that's weird. Can you post your frame-grabbing code?

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this