Skip to main content
GameDev.net gamedev.net
🔒 Locked

DirectShow Custom Renderer Question.

Started by mrmrcoleman Oct 12, 2004 at 12:51 PM 22 replies 17.1k views
Original Post
mrmrcoleman
mrmrcoleman
Hello. I have implemented a program which uses DirectShow to draw to a Direct3D surface (as in the Texture3D9 example in the SDK). However I want to be able to uses animations with a format of ARGB32 instead of the RGB24 that is used in the example but i don't really understand the code that copies the DirectShow Sample onto the Direct3D surface. Has anybody implemented this type of thing who would be willing to help me out? I basically just need to know how to copy the data in the DirectShow sample (ARGB32) to the Direct3D surface (ARGB32), taking into account the pitch so that the drawing is performed correctly... Big thanks in advance for any help. Kind regards. Mark Coleman
CodeMunkie
CodeMunkie
Sure. That sample in the SDK, I don't even know how it works. It seems horribly and unneccessarily complex. I found what I think is a much simpler way. Basically, I figured this out by using GraphEdit and observing how graphs were created. As long as your video does not have sound, this should work. First, create your graph manager (open GraphEdit). Then add a Null Renderer filter and Sample Grabber filter to the graph. Next, call RenderFile on the video file (in GraphEdit this is like going to File...Render Media File) and the graph manger will build the graph automatically. Since it will see the sample grabber and null renderer filters, it will use them instead of the default video renderer as it builds the graph. You can confirm this by running GraphEdit and following those exact steps. In code that process translates as follows:

	//create graph manager	HRESULT hr = CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER, IID_IGraphBuilder, (void**)&m_pGraph);        //you can uncomment this line and DirectShow will make a log file for you as the graph is built.//	m_pGraph->SetLogFile((unsigned long)dslogfile);	//create null renderer	hr = CoCreateInstance(CLSID_NullRenderer, NULL, CLSCTX_INPROC_SERVER, IID_IBaseFilter, (void **)&m_pNULLRenderer);        //add null renderer to graph	hr = m_pGraph->AddFilter(m_pNULLRenderer, L"Null Renderer");	//create sample grabber	hr = CoCreateInstance(CLSID_SampleGrabber, NULL, CLSCTX_INPROC_SERVER, IID_IBaseFilter, (void**)&m_pGrabberBase);	hr = m_pGrabberBase->QueryInterface(IID_ISampleGrabber, (void**)&m_pVideoGrabber);	//setup sample grabber	//set media type of sample grabber	ZeroMemory(&mt, sizeof(AM_MEDIA_TYPE));	mt.majortype = MEDIATYPE_Video;	mt.subtype = GUID_NULL;//MEDIASUBTYPE_RGB32;	mt.formattype = FORMAT_VideoInfo;	hr = m_pVideoGrabber->SetMediaType(&mt);	hr = m_pVideoGrabber->SetBufferSamples(TRUE);	hr = m_pVideoGrabber->SetOneShot(FALSE);        //add grabber to graph	hr = m_pGraph->AddFilter(m_pGrabberBase, L"Sample Grabber");	//automatically build graph based on filename	hr = m_pGraph->RenderFile(file_name,L"");	//get media interfaces	hr = m_pGraph->QueryInterface(IID_IMediaControl, (void**)&m_pMediaControl);	hr = m_pGraph->QueryInterface(IID_IMediaEvent, (void**)&m_pMediaEvent);	hr = m_pGraph->QueryInterface(IID_IMediaPosition, (void**)&m_pMediaPosition);


Now you just poll your Sample GRabber for the video frame:
	m_pVideoGrabber->GetCurrentBuffer(&video_buffer_size, (long*)video_buffer);	if(!FAILED(dx8tex->LockRect(0,&d3dlr,0,D3DLOCK_DISCARD)))	{		memcpy((byte*)(d3dlr.pBits),(byte*)video_buffer,video_buffer_size);		dx8tex->UnlockRect(0);	}


If you have sound in your media file, it gets a bit more complicated because the graph manager will add the sample grabber and null renderer to the sound stream instead. So what you have to do is add them after you RenderFile, find the default video renderer, remove it, and manually connect your sample grabber and null renderer. Let me know if you need that code.
GraphEdit is a great tool because if you understand how the graph was constructed in GraphEdit, you can easily convert to code. At first, I did not understand why my Sample Grabber did not seem to be getting added to the graph in my code. Then I repeated my steps in GraphEdit and realized that the Sample Grabber was being added to the sound stream despite my having set the media type to video. I was able to try different ways of building the graph in GraphEdit until I found a process that did what I wanted. Then I just translated that process into code. I highly recommend spending some time with GraphEdit as you will gain a deeper understanding of DirectShow.

[Edited by - CodeMunkie on October 21, 2004 9:18:58 AM]
"When you die, if you get a choice between going to regular heaven or pie heaven, choose pie heaven. It might be a trick, but if it's not, mmmmmmm, boy."
How to Ask Questions the Smart Way.
mrmrcoleman
mrmrcoleman
I never thought of doing that!! That's amazing although unfortunately I do need sound support...;(

Also, will the sample grabber be able to grab me a ARGB32 frame?

Big Thanks.

Mark Coleman
CodeMunkie
CodeMunkie
Setting the media subtype to GUID_NULL will allow it to accept any supported format, including ARGB32. As to the sound, like I said it gets a bit tricky, but if you can follow that last bit of code it is not too bad. First thing to change, move the RenderFile call before the part where you add the Sample Grabber and Null Renderer, otherwise the graph manager will afix them to the sound stream which is not what you want. Ok, so now your code should read, create graph, render file, add sample grabber and null renderer. Here is where it gets a bit complex. Now you must find the default Video Renderer and Color Space Converter that was added automagically by RenderFile and remove them:
	//locate default video renderer	IBaseFilter* pVidRenderer = NULL;	m_pGraph->FindFilterByName(L"Video Renderer", &pVidRenderer);	if(pVidRenderer)	{		//get input pin of video renderer		IPin* ipin = GetPin(pVidRenderer, PINDIR_INPUT);		IPin* opin = NULL;		//find out who the renderer is connected to and disconnect from them		ipin->ConnectedTo(&opin);		ipin->Disconnect();		opin->Disconnect();		SAFE_RELEASE(ipin);		//remove the default renderer from the graph				m_pGraph->RemoveFilter(pVidRenderer);		SAFE_RELEASE(pVidRenderer);		//see if the video renderer was originally connected to 		//a color space converter		IBaseFilter* pColorConverter = NULL;		m_pGraph->FindFilterByName(L"Color Space Converter", &pColorConverter);		if(pColorConverter)		{			SAFE_RELEASE(opin);			//remove the converter from the graph as well			ipin = GetPin(pColorConverter, PINDIR_INPUT);			ipin->ConnectedTo(&opin);			ipin->Disconnect();			opin->Disconnect();			SAFE_RELEASE(ipin);						m_pGraph->RemoveFilter(pColorConverter);			SAFE_RELEASE(pColorConverter);		}		//get the input pin of the sample grabber		ipin = GetPin(m_pGrabberBase, PINDIR_INPUT);		//connect the filter that was originally connected to the default renderer		//to the sample grabber		m_pGraph->Connect(opin, ipin);		SAFE_RELEASE(ipin);		SAFE_RELEASE(opin);		//get output pin of sample grabber		opin = GetPin(m_pGrabberBase, PINDIR_OUTPUT);		//get input pin of null renderer		ipin = GetPin(m_pNULLRenderer, PINDIR_INPUT);		//connect them		m_pGraph->Connect(opin, ipin);		SAFE_RELEASE(ipin);		SAFE_RELEASE(opin);	}


Let me know if that is not straight forward. The GetPin function looks like this:

IPin* GetPin(IBaseFilter *pFilter, PIN_DIRECTION PinDir){    BOOL       bFound = FALSE;    IEnumPins  *pEnum;    IPin       *pPin;    HRESULT hr = pFilter->EnumPins(&pEnum);    if (FAILED(hr))    {        return NULL;    }	int i = 0;    while(pEnum->Next(1, &pPin, 0) == S_OK)    {        PIN_DIRECTION PinDirThis;        pPin->QueryDirection(&PinDirThis);        bFound = (PinDir == PinDirThis);		if(bFound)            break;        pPin->Release();    }    pEnum->Release();    return (bFound ? pPin : NULL);  }
"When you die, if you get a choice between going to regular heaven or pie heaven, choose pie heaven. It might be a trick, but if it's not, mmmmmmm, boy."
How to Ask Questions the Smart Way.
mrmrcoleman
mrmrcoleman
Thanks for your replies, they are extremely useful but I have a problem.

According to the DirectX SDK Documentation the Include for the ISampleGrabber interface is Qedit.h, which apparently is 'not compatible with Direct 3d headers later than version 7'.

So I went to my headers and found the following three which mention Direct3D9.

D3D9.h which can become just D3D.h
D3DX9.h which can become just D3DX.h
D3DX9TEX.h but this one doesn't seem to have a Direct3D7 equivalent.

Does anybody know which headers I have to include to get the same functionality but with Direct3D7 header files? I don't think that I require any of the complex features that may have been added in later versions so can I just go ahead and change the header files?

Thanks in advance.

Mark Coleman
CodeMunkie
CodeMunkie
Oh yes I forgot about that little issue. The simple but not immediately obvious answer is to extract the ISampleGrabber interface from qedit.h and put it in its own header. This is exactly what I have done:
EXTERN_C const IID IID_ISampleGrabberCB;MIDL_INTERFACE("0579154A-2B53-4994-B0D0-E773148EFF85")ISampleGrabberCB : public IUnknown{public:    virtual HRESULT STDMETHODCALLTYPE SampleCB(         double SampleTime,        IMediaSample *pSample) = 0;        virtual HRESULT STDMETHODCALLTYPE BufferCB(         double SampleTime,        BYTE *pBuffer,        long BufferLen) = 0;    };EXTERN_C const CLSID CLSID_NullRenderer;EXTERN_C const CLSID CLSID_SampleGrabber;EXTERN_C const IID IID_ISampleGrabber;MIDL_INTERFACE("6B652FFF-11FE-4fce-92AD-0266B5D7C78F")ISampleGrabber : public IUnknown{public:    virtual HRESULT STDMETHODCALLTYPE SetOneShot(         BOOL OneShot) = 0;        virtual HRESULT STDMETHODCALLTYPE SetMediaType(         const AM_MEDIA_TYPE *pType) = 0;        virtual HRESULT STDMETHODCALLTYPE GetConnectedMediaType(         AM_MEDIA_TYPE *pType) = 0;        virtual HRESULT STDMETHODCALLTYPE SetBufferSamples(         BOOL BufferThem) = 0;        virtual HRESULT STDMETHODCALLTYPE GetCurrentBuffer(         /* [out][in] */ long *pBufferSize,        /* [out] */ long *pBuffer) = 0;        virtual HRESULT STDMETHODCALLTYPE GetCurrentSample(         /* [retval][out] */ IMediaSample **ppSample) = 0;        virtual HRESULT STDMETHODCALLTYPE SetCallback(         ISampleGrabberCB *pCallback,        long WhichMethodToCallback) = 0;    };


Just create ISampleGrabber.h (or some other suitably named header), paste the code in and include it in your project.
"When you die, if you get a choice between going to regular heaven or pie heaven, choose pie heaven. It might be a trick, but if it's not, mmmmmmm, boy."
How to Ask Questions the Smart Way.
mrmrcoleman
mrmrcoleman
Your a genius and a life saver, big thanks.

Mark Coleman
ScipioS
ScipioS
Guys, please, help!!!
I've done all every thing posted here but still have just a black screen :(
Show must go on!
ScipioS
ScipioS
Also I've found out that video_buffer_size & video_buffer are always equal to zero!

Why it can be so?
Show must go on!
mrmrcoleman
mrmrcoleman
Hello again, just implementing the code here and I am having a compilation problem.

If we are using our own header file, won't it be necesary to put something in their about the NullRenderer other than:

EXTERN_C const CLSID CLSID_NullRenderer;?

It doesm't seem to like it for some reason...?

Thanks in advance for any help.

Mark Coleman

[Edited by - mrmrcoleman on October 19, 2004 12:10:41 PM]
CodeMunkie
CodeMunkie
Are you sure it's a compile error or is it a linking error? What is the error exactly? The extern tells the compiler to not worry about it because the linker will find the item in another library or object file. Be sure to link to quartz.lib strmiids.lib and dxguid.lib, because it lives in one of those.

And Scipios, are you checking the HRESULTS returned by those calls? I left out a lot of checking for simplicity sake. Another thing I assumed, don't forget to call CoInitialize() before you make any COM calls (such as CoCreateInstance) and be sure to call CoUninitialize() before your app ends.
"When you die, if you get a choice between going to regular heaven or pie heaven, choose pie heaven. It might be a trick, but if it's not, mmmmmmm, boy."
How to Ask Questions the Smart Way.
mrmrcoleman
mrmrcoleman
I think I may worked it out, and I am all out of computer time for today so I will come back tomorrow. A big thanks for all you help so far.

Kind regards.

Mark Coleman
CodeMunkie
CodeMunkie
No problem. Maybe this would be a good article topic? Or has it been done? I envision an article that walks through how to prototype with GraphEdit and then implement that graph in your application.
"When you die, if you get a choice between going to regular heaven or pie heaven, choose pie heaven. It might be a trick, but if it's not, mmmmmmm, boy."
How to Ask Questions the Smart Way.
ScipioS
ScipioS
Here is what I'm doing:

IDirect3DTexture9 *g_pTexture;IGraphBuilder *pGB;IMediaControl *pMC;IMediaPosition *pMP;IMediaEvent *pME;AM_MEDIA_TYPE mt;IBaseFilter *pBF, *pGrabberBase, *pNullRenderer;ISampleGrabber *pVideoGrabber;void InitDirectShow(void){  CoInitialize(NULL);  CoCreateInstance(CLSID_FilterGraph, NULL, LSCTX_INPROC_SERVER, IID_IGraphBuilder, (void**)&pGB);  CoCreateInstance(CLSID_SampleGrabber, NULL, CLSCTX_INPROC_SERVER, IID_IBaseFilter, (void**)&pGrabberBase);  pGrabberBase->QueryInterface(IID_ISampleGrabber, (void**)&pVideoGrabber);  ZeroMemory(&mt, sizeof(AM_MEDIA_TYPE));  mt.majortype = MEDIATYPE_Video;  mt.subtype = GUID_NULL;  mt.formattype = FORMAT_VideoInfo;  pVideoGrabber->SetMediaType(&mt);  pVideoGrabber->SetBufferSamples(TRUE);  FAILED(pVideoGrabber->SetOneShot(FALSE);  pGB->AddFilter(pGrabberBase, L"Sample Grabber");  CoCreateInstance(CLSID_NullRenderer, NULL, CLSCTX_INPROC_SERVER, IID_IBaseFilter, (void**)&pNullRenderer);  pGB->AddFilter(pNullRenderer, L"Null Renderer");  pGB->RenderFile(L"bg_main.avi", NULL);  pGB->QueryInterface(IID_IMediaControl, (void**)&pMC);  pGB->QueryInterface(IID_IMediaEvent, (void**)&pME);  pGB->QueryInterface(IID_IMediaPosition, (void**)&pMP);  D3DXCreateTexture(pd3dd, screen.width, screen.height, 1, 0, D3DFMT_X8R8G8B8, D3DPOOL_MANAGED, &g_pTexture);  pMC->Run();}void RenderDShow(void){  long video_buffer_size = NULL;  long video_buffer = NULL;  D3DLOCKED_RECT d3dlr;  pVideoGrabber->GetCurrentBuffer(&video_buffer_size, (long*)video_buffer);  if (!FAILED(g_pTexture->LockRect(0, &d3dlr, 0, D3DLOCK_DISCARD)))  {    memcpy ((BYTE*)(d3dlr.pBits), (BYTE*)video_buffer, video_buffer_size);    g_pTexture->UnlockRect(0);  }  else mp_Error("Failed to lock rect", true);}void Render(void){  switch(pd3dd->TestCooperativeLevel())  {    case D3DERR_DEVICELOST:      return;      break;    case D3DERR_DEVICENOTRESET:      if(FAILED(pd3dd->Reset(&d3dpp)))        mp_Error("Failed to reset D3D device", true);      SetRenderState();      break;    default:      break;  }  pd3dd->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(0, 0, 0), 1.0f, 0);  pd3dd->BeginScene();    SetupMatrices();  RenderDShow();  pd3dd->SetTexture(0, g_pTexture);  pd3dd->SetStreamSource( 0, p_vb_bg, 0, sizeof(CUSTOMVERTEX));  pd3dd->SetFVF( D3DFVF_CUSTOMVERTEX );  pd3dd->DrawPrimitive (D3DPT_TRIANGLESTRIP, 0, 2);  ...  pd3dd->EndScene();  pd3dd->Present( NULL, NULL, NULL, NULL );}


Originally I'm checking error in each string!!!

All variables are global!!!

I've linked all libs too.

And the last fact: then I'm loading a bitmap onto this texture every thing goes fine!
Show must go on!
CodeMunkie
CodeMunkie
The only problem that sticks out is video_buffer should be declared as an unsigned char*, not long.
"When you die, if you get a choice between going to regular heaven or pie heaven, choose pie heaven. It might be a trick, but if it's not, mmmmmmm, boy."
How to Ask Questions the Smart Way.
mrmrcoleman
mrmrcoleman
Hey CodeMunkie, the code you gave me has been a great help but I have found one small problem. In the first example if I add the sample grabber first and the null renderer second it does not connect and the sample grabber is left out of the graph, but if I put the Null Renderer in and then the sample grabber then the graph connects correctly! Is this an error in your code or does the connection process change from system to system?

Mark Coleman
CodeMunkie
CodeMunkie
Good eye! Yes I copied the code is incorrectly. Just to make sure, you are referring to the first example that does not work with avi's that contain sound correct? Sorry about that, I have fixed the posted code. I can in fact confirm this with GraphEdit. I'm gald that it was my mistake. It would be unfortunate if the graph was built differently on different machines!
"When you die, if you get a choice between going to regular heaven or pie heaven, choose pie heaven. It might be a trick, but if it's not, mmmmmmm, boy."
How to Ask Questions the Smart Way.
mrmrcoleman
mrmrcoleman
Hey there, in what seems to be an endless one-on-one tutorial I need to pick your brains once again CodeMunkie...!

The program is working great now, it is much better than the texture3D9 method suggested in the SDK but one problem has arisen. All my animations are upside down!! I must have been copying the textures differently in the other method, which leads me to my question.

How can I turn all my animations the right way up? I would be prepared to manually flip all the animation files but there are an awful lot of them (100+..). Is there some way of inserting a transform filter that will flip the stream as the samples come through?

If this is possible then do you have any idea what the CPU hit would be for inserting this extra filter? I am already running almost full out and if the hit was going to be big I would rather manually flip the animations..!

Thanks in advance for any help.

Mark Coleman
CodeMunkie
CodeMunkie
That's odd indeed. My solution would probably be to just rotate the quad until it looks right ;). I know bmp files are stored upside down, but I don't know about avi. It also does not make sense why it should work with one method and not another. If you figure it out, post here. I would be interested to know.
"When you die, if you get a choice between going to regular heaven or pie heaven, choose pie heaven. It might be a trick, but if it's not, mmmmmmm, boy."
How to Ask Questions the Smart Way.
mrmrcoleman
mrmrcoleman
Code Munkie,

Rotate the quad? Do you mean the screen coordinates or am I missing something?

I can see how to rotate the world co-ordinates but not rotate a single texture on the screen? Any clues? I am working in 2d orthogonal if that helps:)

Mark Coleman

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.