I've written a custom sample grabber (in C++) to handle the VideoInfoHeader2
format and have registered it as a COM object. I want to use it in a C#
application in a similar way that one would use the standard DirectShow
SampleGrabber in order to grab frame data of video from a live capture source.
I can successfully add my custom filter to the graph, however when I try
casting it as an ISampleGrabber filter in order to set the callback and
intercept video data I get the following exception:
Unable to cast COM object of type 'System.__ComObject' to interface type
'DirectShowLib.ISampleGrabber'. This operation failed because the
QueryInterface call on the COM component for the interface with IID
'{6B652FFF-11FE-4FCE-92AD-0266B5D7C78F}' failed due to the following error: No
such interface supported (Exception from HRESULT: 0x80004002 (E_NOINTERFACE)).
If anyone can help me out or provide any pointers as to why I'm getting this
exception, or how to acquire the set callback method, I would be greatly
appreciative.
Yours faithfully,
Voc.**
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
Does the filter you wrote actually support ISampleGrabber? Have you tried
putting a breakpoint on your NonDelegatingQueryInterface to see what is
happening?
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
Solved it. I made a stupid mistake in thinking I had to have a different GUID
for the sample grabber IID. I can cast my custom fitler as a sample grabber. I
have a new problem however. When I call SetCallback on my filter I get the
following exception:
Attempted to read or write protected memory. This is often an indication that other memory is corrupt.
Is this behaviour a typical error?
Kind regards,
Voc.
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
Thanks for the responses. I put a breakpoint in the SetCallback method of the
C++ code, however I'm not sure how to step into it whilst debugging my C#
code. Can you help me with this?
Thanks in advance,
Voc.
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
I know it's been a while, I was working on other projects and had to leave
this one, but now I'm back on it. The signature was correct as provided in the
sample code and the post above. The problem with memory to my understanding
was how I was building my graph with another custom transform filter which I
have rectified.
I have a new problem however, the setcallback method in the unmanaged code is
never called. I will be posting this in the msdn directshow forum for obvious
reasons, but felt it was necessary to bring closure to this thread.
Kind regards and thanks,
Voc.
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
I posted on the msdn forum and the experts there suggest that it is how I
configure my callback in my C# code.
I'm not quite sure where my C++ filter would be calling C# code, I thought it
worked the other way around. I think I'm misunderstanding something.
To ensure it wasn't my custom filter that was problematic I built the example
sample grabber in the 2004 Summer SDK and registered it as a COM object. I
then used GraphEditPlus to construct a graph and generate the code in C#. The
source filter was configured to deliver VideoInfo samples which was connected
to an AVI Decompressor and Color Space Converter and then to the example
sample grabber followed by a VMR9 Renderer.
I then added code to configure the sample grabber callback. It is this which I
am lead to believe I have done incorrectly. I have configured sample grabbers
before in a similar fashion, so I'm really confused as to what I'm doing wrong
here. The code is posted below:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices.ComTypes;
using System.Runtime.InteropServices;
using DirectShowLib;
//add AVI Decompressor
IBaseFilter pAVIDecompressor = (IBaseFilter)new AVIDec();
hr = pGraph.AddFilter(pAVIDecompressor, "AVI Decompressor");
checkHR(hr, "Can't add AVI Decompressor to graph");
//add Color Space Converter
IBaseFilter pColorSpaceConverter = (IBaseFilter)new Colour();
hr = pGraph.AddFilter(pColorSpaceConverter, "Color Space Converter");
checkHR(hr, "Can't add Color Space Converter to graph");
//connect Pinnacle 510-USB and AVI Decompressor
hr = pGraph.ConnectDirect(GetPin(pPinnacle510USB, "Video out"),
GetPin(pAVIDecompressor, "XForm In"), null);
checkHR(hr, "Can't connect Pinnacle 510-USB and AVI Decompressor");
//connect AVI Decompressor and Color Space Converter
hr = pGraph.ConnectDirect(GetPin(pAVIDecompressor, "XForm Out"),
GetPin(pColorSpaceConverter, "Input"), null);
checkHR(hr, "Can't connect AVI Decompressor and Color Space Converter");
//connect Color Space Converter and SampleGrabber Example
hr = pGraph.ConnectDirect(GetPin(pColorSpaceConverter, "XForm Out"),
GetPin(pSampleGrabberExample, "Input"), null);
checkHR(hr, "Can't connect Color Space Converter and SampleGrabber Example");
//connect SampleGrabber Example and Video Mixing Renderer 9
hr = pGraph.ConnectDirect(GetPin(pSampleGrabberExample, "Output"),
GetPin(pVideoMixingRenderer9, "VMR Input0"), null);
checkHR(hr, "Can't connect SampleGrabber Example and Video Mixing Renderer
9");
int ISampleGrabberCB.BufferCB(double SampleTime, IntPtr pBuffer, int
BufferLen)
{
throw new NotImplementedException();
}
int ISampleGrabberCB.SampleCB(double SampleTime, IMediaSample pSample)
{
Marshal.ReleaseComObject(pSample);
return 1;
}
endregion
}
}
I am well and truly puzzled. If you have any ideas on how I can spot my error
or any advice at all I'd really appreciate it. My post in the msdn forum can
be found here:
I forgot to mention that I ran the graph and it displays video fine, however
the code in the SampleCB interface which was implemented explicitly is never
hit.
Voc.
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
It appears to me that this code will pass null to
ISampleGrabber.SetCallback(). Have you actually checked the value? Take a look
at some of our samples for how to pass the correct value here.
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
The sampleGrabber was not null but it also was not correct based on the sample
code on this site. I edited my code accordingly but now have new (and strange)
problems. I can only call my ConfigureSampleGrabber() method after I connect
the baseGrabberFilter in the graph. I have a feeling this is due to how the
DirectX SDK Sample Grabber filter is written because it seems that the
standard SG filter used in the code on this site can be configured prior to
connection .
If I call method prior to connecting the filter I get the following error:
hr = -2147220983
"The operation cannot be performed because the pins are not connected."
If I configure the sample grabber after I connect the pins the SetCallback
method causes an exception:
"First-chance exception at 0x03b98ed4 (grabber.ax) in
ExampleSampleGrabber.exe: 0xC0000005: Access violation writing location
0x00000000.
A first chance exception of type 'System.AccessViolationException' occurred in
ExampleSampleGrabber.exe"
I can't step into the unmanaged code at the line where SetCallback is called
either which is perplexing. After some searching, people have mentioned that
memory access violations when Setcallback is used is due to qedit.h, although
my call stack mentions nothing of the like.
This is the edited BuildGraph() method:
private void BuildGraph(IGraphBuilder pGraph)
{
int hr = 0;
//add AVI Decompressor
IBaseFilter pAVIDecompressor = (IBaseFilter)new AVIDec();
hr = pGraph.AddFilter(pAVIDecompressor, "AVI Decompressor");
checkHR(hr, "Can't add AVI Decompressor to graph");
//add Color Space Converter
IBaseFilter pColorSpaceConverter = (IBaseFilter)new Colour();
hr = pGraph.AddFilter(pColorSpaceConverter, "Color Space Converter");
checkHR(hr, "Can't add Color Space Converter to graph");
//connect Pinnacle 510-USB and AVI Decompressor
hr = pGraph.ConnectDirect(GetPin(pPinnacle510USB, "Video out"),
GetPin(pAVIDecompressor, "XForm In"), null);
checkHR(hr, "Can't connect Pinnacle 510-USB and AVI Decompressor");
//connect AVI Decompressor and Color Space Converter
hr = pGraph.ConnectDirect(GetPin(pAVIDecompressor, "XForm Out"),
GetPin(pColorSpaceConverter, "Input"), null);
checkHR(hr, "Can't connect AVI Decompressor and Color Space Converter");
//connect Color Space Converter and SampleGrabber Example
hr = pGraph.ConnectDirect(GetPin(pColorSpaceConverter, "XForm Out"),
GetPin(baseGrabberFilter, "Input"), null);
checkHR(hr, "Can't connect Color Space Converter and SampleGrabber Example");
//connect SampleGrabber Example and Video Mixing Renderer 9
hr = pGraph.ConnectDirect(GetPin(baseGrabberFilter, "Output"),
GetPin(pVideoMixingRenderer9, "VMR Input0"), null);
checkHR(hr, "Can't connect SampleGrabber Example and Video Mixing Renderer
9");
ConfigureSampleGrabber(sampleGrabber);
}
This is the edited ConfigureSampleGrabber() method:
private void ConfigureSampleGrabber(ISampleGrabber sampleGrabber)
{
// Initialise error code.
AMMediaType media;
int hr = 0;
media = new AMMediaType();
media.majorType = MediaType.Video;
media.subType = MediaSubType.YUY2;
media.formatType = FormatType.VideoInfo;
hr = sampleGrabber.SetMediaType(media);
DsError.ThrowExceptionForHR(hr);
DsUtils.FreeAMMediaType(media);
media = null;
Yes. I can step into other parts of the unmanaged code or place breakpoints in
the unmanaged code with _asm int 3. This doesn't work for the line in the
managed code where I use SetCallback.
Thanks for the prompt reply!
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
I can place breakpoints the normal way (using F9) as you suggest, forgive my
convolution. However I still cannot step into the StepCallback() method in C#.
Is this line meant to execute the SetCallback method of the C++ code? Below is
the code snippet from the DirectX SDK code sample:
Does the C# SetCallback method wrap the SetCallback method of the C++ filter?
Also I'm not quite sure about what you mean by the symbols for my provider not
loading properly.
Thanks for the constant help. I feel that when this gets nutted out I'm going
to do something out of sheer joy like propose or ask permission to bear your
children.
Yours faithfully,
Voc.
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
Not having that source code in front of me, what is SAMPLECALLBACK defined
as? In the DS SampleGrabber filter it's an interface pointer. This isn't a
class name, is it?
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
// File: Grabber.h
//
// Desc: DirectShow sample code - Header file for the SampleGrabber
// example filter
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//----------------------------------------------------------------------------
--
// Define new GUID and IID for the sample grabber example so that they do NOT
// conflict with the official DirectX SampleGrabber filter
//----------------------------------------------------------------------------
--
// {2FA4F053-6D60-4cb0-9503-8E89234F3F73}
DEFINE_GUID(CLSID_GrabberSample,
0x2fa4f053, 0x6d60, 0x4cb0, 0x95, 0x3, 0x8e, 0x89, 0x23, 0x4f, 0x3f, 0x73);
// We define a callback typedef for this example.
// Normally, you would make the SampleGrabber support a COM interface,
// and in one of its methods you would pass in a pointer to a COM interface
// used for calling back. See the DirectX documentation for the SampleGrabber
// for more information.
// We define the interface the app can use to program us
MIDL_INTERFACE("6B652FFF-11FE-4FCE-92AD-0266B5D7C78F")
IGrabberSample : public IUnknown
{
public:
//----------------------------------------------------------------------------
// This is a special allocator that KNOWS that the person who is creating it
// will only create one of them. It allocates CMediaSamples that only
// reference the buffer location that is set in the pin's renderer's
// data variable
//----------------------------------------------------------------------------
class CSampleGrabberAllocator : public CMemAllocator
{
friend class CSampleGrabberInPin;
friend class CSampleGrabber;
protected:
// our pin who created us
//
CSampleGrabberInPin * m_pPin;
~CSampleGrabberAllocator( )
{
// wipe out m_pBuffer before we try to delete it. It's not an allocated
// buffer, and the default destructor will try to free it!
m_pBuffer = NULL;
}
HRESULT Alloc( );
void ReallyFree();
// Override this to reject anything that does not match the actual buffer
// that was created by the application
STDMETHODIMP SetProperties(ALLOCATOR_PROPERTIES pRequest,
ALLOCATOR_PROPERTIES pActual);
};
//----------------------------------------------------------------------------
// we override the input pin class so we can provide a media type
// to speed up connection times. When you try to connect a filesourceasync
// to a transform filter, DirectShow will insert a splitter and then
// start trying codecs, both audio and video, video codecs first. If
// your sample grabber's set to connect to audio, unless we do this, it
// will try all the video codecs first. Connection times are sped up x10
// for audio with just this minor modification!
//----------------------------------------------------------------------------
class CSampleGrabberInPin : public CTransInPlaceInputPin
{
friend class CSampleGrabberAllocator;
friend class CSampleGrabber;
// we override this to tell whoever's upstream of us what kind of
// properties we're going to demand to have
//
STDMETHODIMP GetAllocatorRequirements( ALLOCATOR_PROPERTIES *pProps );
class CSampleGrabber : public CTransInPlaceFilter,
public IGrabberSample
{
friend class CSampleGrabberInPin;
friend class CSampleGrabberAllocator;
protected:
CMediaType m_mtAccept;
SAMPLECALLBACK m_callback;
CCritSec m_Lock; // serialize access to our data
BOOL IsReadOnly( ) { return !m_bModifiesData; }
// PURE, override this to ensure we get
// connected with the right media type
HRESULT CheckInputType( const CMediaType * pmt );
// PURE, override this to callback
// the user when a sample is received
HRESULT Transform( IMediaSample * pms );
// override this so we can return S_FALSE directly.
// The base class CTransInPlace
// Transform( ) method is called by it's
// Receive( ) method. There is no way
// to get Transform( ) to return an S_FALSE value
// (which means "stop giving me data"),
// to Receive( ) and get Receive( ) to return S_FALSE as well.
The DS SampleGrabber's SetCallback method takes an interface pointer
(ISampleGrabberCB). So, if you have a COM object that implements the COM
interface ISampleGrabberCB in any language, you can pass a pointer to that
interface to ISampleGrabber.SetCallback.
However, that's not what this code does. As the comments above this typedef
warn, Normally, you would.... However, that's not what they did. Instead
they just declared a function pointer. While .Net performs all the necessary
magic to call and be called by COM objects, having unmanaged code make calls
to a c# method... I don't even know if you can.
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
Ah OK, thanks for the clarification. I think I'll have to find some other
means of writing my custom sample grabber. I tried searching for the source
code of the SampleGrabber that DirectShow provides (C1F400A0-3F08-11D3-9F0B-
006008039E37), but I could not find it. Is it available somewhere?
I also read in an MSDN forum that the December 2002 DirectX SDK has a sample
grabber sample, but I couldn't find this on the DirectX SDK downloads site.
Thanks again!
Voc
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
I know it's been a while. Are you saying that if I were to write a DMO, I
would be able to register it as a COM object and be able to implement the
ISampleCallback in managed code as I describe in my OP?
Kind regards,
Voc
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
Hi forum,
I've written a custom sample grabber (in C++) to handle the VideoInfoHeader2
format and have registered it as a COM object. I want to use it in a C#
application in a similar way that one would use the standard DirectShow
SampleGrabber in order to grab frame data of video from a live capture source.
I can successfully add my custom filter to the graph, however when I try
casting it as an ISampleGrabber filter in order to set the callback and
intercept video data I get the following exception:
Guid CLSID_VI2SampleGrabber = new Guid("{574AA56A-62D7-4EDE-
9E90-CCAA9C811D0D}");
object grabberObject =
Activator.CreateInstance(Type.GetTypeFromCLSID(CLSID_VI2SampleGrabber));
ISampleGrabber sampleGrabber = (ISampleGrabber)grabberObject; **{<-------
EXCEPTION THROWN HERE}
The exception is:
Unable to cast COM object of type 'System.__ComObject' to interface type
'DirectShowLib.ISampleGrabber'. This operation failed because the
QueryInterface call on the COM component for the interface with IID
'{6B652FFF-11FE-4FCE-92AD-0266B5D7C78F}' failed due to the following error: No
such interface supported (Exception from HRESULT: 0x80004002 (E_NOINTERFACE)).
If anyone can help me out or provide any pointers as to why I'm getting this
exception, or how to acquire the set callback method, I would be greatly
appreciative.
Yours faithfully,
Voc.**
Does the filter you wrote actually support ISampleGrabber? Have you tried
putting a breakpoint on your NonDelegatingQueryInterface to see what is
happening?
HI,
Solved it. I made a stupid mistake in thinking I had to have a different GUID
for the sample grabber IID. I can cast my custom fitler as a sample grabber. I
have a new problem however. When I call SetCallback on my filter I get the
following exception:
Attempted to read or write protected memory. This is often an indication that other memory is corrupt.
Is this behaviour a typical error?
Kind regards,
Voc.
Have you set a breakpoint on the SetCallback method? That will tell you if the
problem is coming in managed or unmanaged code.
Hi Snarfle,
Thanks for the responses. I put a breakpoint in the SetCallback method of the
C++ code, however I'm not sure how to step into it whilst debugging my C#
code. Can you help me with this?
Thanks in advance,
Voc.
We're wandering a little far afield from the forum topic. However, I'll point
you to Project Properties/Debug/Enable unmanaged code debugging.
Snarfle: OP multi-posted here:
http://social.msdn.microsoft.com/Forums/en-US/windowsdirectshowdevelopment/th
read/387eb9f1-bcb5-4ed7-a2f7-d8c4235671a7
My guess is that he hasn't got the signature correct for the custom interface
in the grabber sample:
Hi guys,
I know it's been a while, I was working on other projects and had to leave
this one, but now I'm back on it. The signature was correct as provided in the
sample code and the post above. The problem with memory to my understanding
was how I was building my graph with another custom transform filter which I
have rectified.
I have a new problem however, the setcallback method in the unmanaged code is
never called. I will be posting this in the msdn directshow forum for obvious
reasons, but felt it was necessary to bring closure to this thread.
Kind regards and thanks,
Voc.
Have you tried setting a breakpoint in your filter at the point where it
should be calling your c# code?
Hi Snarfle,
I posted on the msdn forum and the experts there suggest that it is how I
configure my callback in my C# code.
I'm not quite sure where my C++ filter would be calling C# code, I thought it
worked the other way around. I think I'm misunderstanding something.
To ensure it wasn't my custom filter that was problematic I built the example
sample grabber in the 2004 Summer SDK and registered it as a COM object. I
then used GraphEditPlus to construct a graph and generate the code in C#. The
source filter was configured to deliver VideoInfo samples which was connected
to an AVI Decompressor and Color Space Converter and then to the example
sample grabber followed by a VMR9 Renderer.
I then added code to configure the sample grabber callback. It is this which I
am lead to believe I have done incorrectly. I have configured sample grabbers
before in a similar fashion, so I'm really confused as to what I'm doing wrong
here. The code is posted below:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices.ComTypes;
using System.Runtime.InteropServices;
using DirectShowLib;
namespace ExampleSampleGrabber
{
public partial class Form1 : Form, ISampleGrabberCB
{
// Sample grabber.
ISampleGrabber sampleGrabber = null;
IGraphBuilder graph = null;
private void checkHR(int hr, string msg)
{
if (hr < 0)
{
Console.WriteLine(msg);
DsError.ThrowExceptionForHR(hr);
}
}
private IPin GetPin(IBaseFilter filter, string pinname)
{
IEnumPins epins;
int hr = filter.EnumPins(out epins);
checkHR(hr, "Can't enumerate pins");
IntPtr fetched = Marshal.AllocCoTaskMem(4);
IPin pins = new IPin;
while (epins.Next(1, pins, fetched) == 0)
{
PinInfo pinfo;
pins.QueryPinInfo(out pinfo);
bool found = (pinfo.name == pinname);
DsUtils.FreePinInfo(pinfo);
if (found)
return pins;
}
checkHR(-1, "Pin not found");
return null;
}
private void BuildGraph(IGraphBuilder pGraph)
{
int hr = 0;
//graph builder
ICaptureGraphBuilder2 pBuilder = (ICaptureGraphBuilder2)new
CaptureGraphBuilder2();
hr = pBuilder.SetFiltergraph(pGraph);
checkHR(hr, "Can't SetFiltergraph");
Guid CLSID_VideoMixingRenderer9 = new Guid("{51B4ABF3-748F-
4E3B-A276-C828330E926A}"); //quartz.dll
Guid CLSID_SampleGrabberExample = new
Guid("{2FA4F053-6D60-4CB0-9503-8E89234F3F73}"); //grabber.ax
//add Video Mixing Renderer 9
IBaseFilter pVideoMixingRenderer9 = (IBaseFilter)Activator.CreateInstance(Type
.GetTypeFromCLSID(CLSID_VideoMixingRenderer9));
hr = pGraph.AddFilter(pVideoMixingRenderer9, "Video Mixing Renderer 9");
checkHR(hr, "Can't add Video Mixing Renderer 9 to graph");
//add SampleGrabber Example
IBaseFilter pSampleGrabberExample = (IBaseFilter)Activator.CreateInstance(Type
.GetTypeFromCLSID(CLSID_SampleGrabberExample));
hr = pGraph.AddFilter(pSampleGrabberExample, "SampleGrabber Example");
checkHR(hr, "Can't add SampleGrabber Example to graph");
sampleGrabber = (ISampleGrabber)new SampleGrabber();
ConfigureSampleGrabber();
//add Pinnacle 510-USB
IBaseFilter pPinnacle510USB =
CreateFilter(@"@device:pnp:\?\usb#vid_2304&pid_0223#6&2f8c4b4&0&7#{65e8773d-
8f56-11d0-a3b9-00a0c9223196}{fb112213-ff34-40a9-bb84-f2c76b349ebb}");
hr = pGraph.AddFilter(pPinnacle510USB, "Pinnacle 510-USB");
checkHR(hr, "Can't add Pinnacle 510-USB to graph");
//add AVI Decompressor
IBaseFilter pAVIDecompressor = (IBaseFilter)new AVIDec();
hr = pGraph.AddFilter(pAVIDecompressor, "AVI Decompressor");
checkHR(hr, "Can't add AVI Decompressor to graph");
//add Color Space Converter
IBaseFilter pColorSpaceConverter = (IBaseFilter)new Colour();
hr = pGraph.AddFilter(pColorSpaceConverter, "Color Space Converter");
checkHR(hr, "Can't add Color Space Converter to graph");
AMMediaType pmt = new AMMediaType();
pmt.majorType = MediaType.Video;
pmt.subType = MediaSubType.YUY2;
pmt.formatType = FormatType.VideoInfo;
pmt.fixedSizeSamples = true;
pmt.formatSize = 88;
pmt.sampleSize = 829440;
pmt.temporalCompression = false;
VideoInfoHeader format = new VideoInfoHeader();
format.SrcRect = new DsRect();
format.TargetRect = new DsRect();
format.BitRate = 165888000;
format.AvgTimePerFrame = 400000;
format.BmiHeader = new BitmapInfoHeader();
format.BmiHeader.Size = 40;
format.BmiHeader.Width = 720;
format.BmiHeader.Height = 576;
format.BmiHeader.Planes = 1;
format.BmiHeader.BitCount = 16;
format.BmiHeader.Compression = 844715353;
format.BmiHeader.ImageSize = 829440;
pmt.formatPtr = Marshal.AllocCoTaskMem(Marshal.SizeOf(format));
Marshal.StructureToPtr(format, pmt.formatPtr, false);
hr = ((IAMStreamConfig)GetPin(pPinnacle510USB, "Video out")).SetFormat(pmt);
DsUtils.FreeAMMediaType(pmt);
checkHR(hr, "Can't set format");
//connect Pinnacle 510-USB and AVI Decompressor
hr = pGraph.ConnectDirect(GetPin(pPinnacle510USB, "Video out"),
GetPin(pAVIDecompressor, "XForm In"), null);
checkHR(hr, "Can't connect Pinnacle 510-USB and AVI Decompressor");
//connect AVI Decompressor and Color Space Converter
hr = pGraph.ConnectDirect(GetPin(pAVIDecompressor, "XForm Out"),
GetPin(pColorSpaceConverter, "Input"), null);
checkHR(hr, "Can't connect AVI Decompressor and Color Space Converter");
//connect Color Space Converter and SampleGrabber Example
hr = pGraph.ConnectDirect(GetPin(pColorSpaceConverter, "XForm Out"),
GetPin(pSampleGrabberExample, "Input"), null);
checkHR(hr, "Can't connect Color Space Converter and SampleGrabber Example");
//connect SampleGrabber Example and Video Mixing Renderer 9
hr = pGraph.ConnectDirect(GetPin(pSampleGrabberExample, "Output"),
GetPin(pVideoMixingRenderer9, "VMR Input0"), null);
checkHR(hr, "Can't connect SampleGrabber Example and Video Mixing Renderer
9");
}
public Form1()
{
InitializeComponent();
}
public IBaseFilter CreateFilter(string displayName)
{
int hr = 0;
IBaseFilter filter = null;
IBindCtx bindCtx = null;
IMoniker moniker = null;
try
{
hr = CreateBindCtx(0, out bindCtx);
Marshal.ThrowExceptionForHR(hr);
int eaten;
hr = MkParseDisplayName(bindCtx, displayName, out eaten, out moniker);
Marshal.ThrowExceptionForHR(hr);
Guid guid = typeof(IBaseFilter).GUID;
object obj;
moniker.BindToObject(bindCtx, null, ref guid, out obj);
filter = (IBaseFilter)obj;
}
finally
{
if (bindCtx != null) Marshal.ReleaseComObject(bindCtx);
if (moniker != null) Marshal.ReleaseComObject(moniker);
}
return filter;
}
public static extern int CreateBindCtx(int reserved, out IBindCtx ppbc);
public static extern int MkParseDisplayName(IBindCtx pcb, string szUserName,
out int pchEaten, out IMoniker ppmk);
private void Form1_Load(object sender, EventArgs e)
{
try
{
graph = (IGraphBuilder)new FilterGraph();
Console.WriteLine("Building graph...");
BuildGraph(graph);
Console.WriteLine("Running...");
IMediaControl mediaControl = (IMediaControl)graph;
IMediaEvent mediaEvent = (IMediaEvent)graph;
int hr = mediaControl.Run();
checkHR(hr, "Can't run the graph");
bool stop = false;
int n = 0;
while (!stop)
{
System.Threading.Thread.Sleep(500);
Console.Write(".");
EventCode ev;
IntPtr p1, p2;
if (mediaEvent.GetEvent(out ev, out p1, out p2, 0) == 0)
{
if (ev == EventCode.Complete || ev == EventCode.UserAbort)
{
Console.WriteLine("Done!");
stop = true;
}
else
if (ev == EventCode.ErrorAbort)
{
Console.WriteLine("An error occured: HRESULT={0:X}", p1);
mediaControl.Stop();
stop = true;
}
mediaEvent.FreeEventParams(ev, p1, p2);
}
// stop after 10 seconds
n++;
if (n > 20)
{
Console.WriteLine("stopping..");
mediaControl.Stop();
stop = true;
}
}
}
catch (COMException ex)
{
Console.WriteLine("COM error: " + ex.ToString());
}
catch (Exception ex)
{
Console.WriteLine("Error: " + ex.ToString());
}
}
private void ConfigureSampleGrabber()
{
// Initialise error code.
AMMediaType media;
int hr = 0;
media = new AMMediaType();
media.majorType = MediaType.Video;
media.subType = MediaSubType.YUY2;
media.formatType = FormatType.VideoInfo;
hr = sampleGrabber.SetMediaType(media);
DsError.ThrowExceptionForHR(hr);
DsUtils.FreeAMMediaType(media);
media = null;
hr = sampleGrabber.SetBufferSamples(true);
DsError.ThrowExceptionForHR(hr);
{
int a = 1;
}
// Set sample grabber call back.
hr = sampleGrabber.SetOneShot(false);
DsError.ThrowExceptionForHR(hr);
ISampleGrabberCB sampleGrabberCB = graph as ISampleGrabberCB;
hr = sampleGrabber.SetCallback(sampleGrabberCB, 0);
DsError.ThrowExceptionForHR(hr);
}
region ISampleGrabberCB Members
int ISampleGrabberCB.BufferCB(double SampleTime, IntPtr pBuffer, int
BufferLen)
{
throw new NotImplementedException();
}
int ISampleGrabberCB.SampleCB(double SampleTime, IMediaSample pSample)
{
Marshal.ReleaseComObject(pSample);
return 1;
}
endregion
}
}
I am well and truly puzzled. If you have any ideas on how I can spot my error
or any advice at all I'd really appreciate it. My post in the msdn forum can
be found here:
http://social.msdn.microsoft.com/Forums/en-
AU/windowsdirectshowdevelopment/thread/a4c92ec9-a3c2-49fd-
a7e0-478a99e87c43?prof=required
Again many thanks for your input thus far Snarfle.
Sincerely,
Voc.
I forgot to mention that I ran the graph and it displays video fine, however
the code in the SampleCB interface which was implemented explicitly is never
hit.
Voc.
It appears to me that this code will pass null to
ISampleGrabber.SetCallback(). Have you actually checked the value? Take a look
at some of our samples for how to pass the correct value here.
Hi Snarfle,
The sampleGrabber was not null but it also was not correct based on the sample
code on this site. I edited my code accordingly but now have new (and strange)
problems. I can only call my ConfigureSampleGrabber() method after I connect
the baseGrabberFilter in the graph. I have a feeling this is due to how the
DirectX SDK Sample Grabber filter is written because it seems that the
standard SG filter used in the code on this site can be configured prior to
connection .
If I call method prior to connecting the filter I get the following error:
hr = -2147220983
"The operation cannot be performed because the pins are not connected."
If I configure the sample grabber after I connect the pins the SetCallback
method causes an exception:
"First-chance exception at 0x03b98ed4 (grabber.ax) in
ExampleSampleGrabber.exe: 0xC0000005: Access violation writing location
0x00000000.
A first chance exception of type 'System.AccessViolationException' occurred in
ExampleSampleGrabber.exe"
I can't step into the unmanaged code at the line where SetCallback is called
either which is perplexing. After some searching, people have mentioned that
memory access violations when Setcallback is used is due to qedit.h, although
my call stack mentions nothing of the like.
This is the edited BuildGraph() method:
private void BuildGraph(IGraphBuilder pGraph)
{
int hr = 0;
//graph builder
ICaptureGraphBuilder2 pBuilder = (ICaptureGraphBuilder2)new
CaptureGraphBuilder2();
hr = pBuilder.SetFiltergraph(pGraph);
checkHR(hr, "Can't SetFiltergraph");
Guid CLSID_VideoMixingRenderer9 = new Guid("{51B4ABF3-748F-
4E3B-A276-C828330E926A}"); //quartz.dll
Guid CLSID_SampleGrabberExample = new
Guid("{2FA4F053-6D60-4CB0-9503-8E89234F3F73}"); //grabber.ax
//add Video Mixing Renderer 9
IBaseFilter pVideoMixingRenderer9 = (IBaseFilter)Activator.CreateInstance(Type
.GetTypeFromCLSID(CLSID_VideoMixingRenderer9));
hr = pGraph.AddFilter(pVideoMixingRenderer9, "Video Mixing Renderer 9");
checkHR(hr, "Can't add Video Mixing Renderer 9 to graph");
//add SampleGrabber Example
IBaseFilter pSampleGrabberExample = (IBaseFilter)Activator.CreateInstance(Type
.GetTypeFromCLSID(CLSID_SampleGrabberExample));
sampleGrabber = (ISampleGrabber)pSampleGrabberExample;
baseGrabberFilter = sampleGrabber as IBaseFilter;
hr = pGraph.AddFilter(baseGrabberFilter, "Grabber");
checkHR(hr, "Can't add Grabber to graph");
//add Pinnacle 510-USB
IBaseFilter pPinnacle510USB =
CreateFilter(@"@device:pnp:\?\usb#vid_2304&pid_0223#6&2f8c4b4&0&7#{65e8773d-
8f56-11d0-a3b9-00a0c9223196}{fb112213-ff34-40a9-bb84-f2c76b349ebb}");
hr = pGraph.AddFilter(pPinnacle510USB, "Pinnacle 510-USB");
checkHR(hr, "Can't add Pinnacle 510-USB to graph");
//add AVI Decompressor
IBaseFilter pAVIDecompressor = (IBaseFilter)new AVIDec();
hr = pGraph.AddFilter(pAVIDecompressor, "AVI Decompressor");
checkHR(hr, "Can't add AVI Decompressor to graph");
//add Color Space Converter
IBaseFilter pColorSpaceConverter = (IBaseFilter)new Colour();
hr = pGraph.AddFilter(pColorSpaceConverter, "Color Space Converter");
checkHR(hr, "Can't add Color Space Converter to graph");
AMMediaType pmt = new AMMediaType();
pmt.majorType = MediaType.Video;
pmt.subType = MediaSubType.YUY2;
pmt.formatType = FormatType.VideoInfo;
pmt.fixedSizeSamples = true;
pmt.formatSize = 88;
pmt.sampleSize = 829440;
pmt.temporalCompression = false;
VideoInfoHeader format = new VideoInfoHeader();
format.SrcRect = new DsRect();
format.TargetRect = new DsRect();
format.BitRate = 165888000;
format.AvgTimePerFrame = 400000;
format.BmiHeader = new BitmapInfoHeader();
format.BmiHeader.Size = 40;
format.BmiHeader.Width = 720;
format.BmiHeader.Height = 576;
format.BmiHeader.Planes = 1;
format.BmiHeader.BitCount = 16;
format.BmiHeader.Compression = 844715353;
format.BmiHeader.ImageSize = 829440;
pmt.formatPtr = Marshal.AllocCoTaskMem(Marshal.SizeOf(format));
Marshal.StructureToPtr(format, pmt.formatPtr, false);
hr = ((IAMStreamConfig)GetPin(pPinnacle510USB, "Video out")).SetFormat(pmt);
DsUtils.FreeAMMediaType(pmt);
checkHR(hr, "Can't set format");
//connect Pinnacle 510-USB and AVI Decompressor
hr = pGraph.ConnectDirect(GetPin(pPinnacle510USB, "Video out"),
GetPin(pAVIDecompressor, "XForm In"), null);
checkHR(hr, "Can't connect Pinnacle 510-USB and AVI Decompressor");
//connect AVI Decompressor and Color Space Converter
hr = pGraph.ConnectDirect(GetPin(pAVIDecompressor, "XForm Out"),
GetPin(pColorSpaceConverter, "Input"), null);
checkHR(hr, "Can't connect AVI Decompressor and Color Space Converter");
//connect Color Space Converter and SampleGrabber Example
hr = pGraph.ConnectDirect(GetPin(pColorSpaceConverter, "XForm Out"),
GetPin(baseGrabberFilter, "Input"), null);
checkHR(hr, "Can't connect Color Space Converter and SampleGrabber Example");
//connect SampleGrabber Example and Video Mixing Renderer 9
hr = pGraph.ConnectDirect(GetPin(baseGrabberFilter, "Output"),
GetPin(pVideoMixingRenderer9, "VMR Input0"), null);
checkHR(hr, "Can't connect SampleGrabber Example and Video Mixing Renderer
9");
ConfigureSampleGrabber(sampleGrabber);
}
This is the edited ConfigureSampleGrabber() method:
private void ConfigureSampleGrabber(ISampleGrabber sampleGrabber)
{
// Initialise error code.
AMMediaType media;
int hr = 0;
media = new AMMediaType();
media.majorType = MediaType.Video;
media.subType = MediaSubType.YUY2;
media.formatType = FormatType.VideoInfo;
hr = sampleGrabber.SetMediaType(media);
DsError.ThrowExceptionForHR(hr);
DsUtils.FreeAMMediaType(media);
media = null;
hr = sampleGrabber.SetCallback(this, 0);
DsError.ThrowExceptionForHR(hr);
}
Can anyone help?
Thanks for your patience and time,
Voc.
Have you Enabled unmanaged code debugging?
Yes. I can step into other parts of the unmanaged code or place breakpoints in
the unmanaged code with _asm int 3. This doesn't work for the line in the
managed code where I use SetCallback.
Thanks for the prompt reply!
int 3? You should be able just use F9 to set breakpoints. Sounds like the
symbols for your provider aren't being loaded correctly.
Hi Snarfle,
I can place breakpoints the normal way (using F9) as you suggest, forgive my
convolution. However I still cannot step into the StepCallback() method in C#.
Is this line meant to execute the SetCallback method of the C++ code? Below is
the code snippet from the DirectX SDK code sample:
STDMETHODIMP CSampleGrabber::SetCallback( SAMPLECALLBACK Callback )
{
CAutoLock lock( &m_Lock );
m_callback = Callback;
return NOERROR;
}
Does the C# SetCallback method wrap the SetCallback method of the C++ filter?
Also I'm not quite sure about what you mean by the symbols for my provider not
loading properly.
Thanks for the constant help. I feel that when this gets nutted out I'm going
to do something out of sheer joy like propose or ask permission to bear your
children.
Yours faithfully,
Voc.
Not having that source code in front of me, what is SAMPLECALLBACK defined
as? In the DS SampleGrabber filter it's an interface pointer. This isn't a
class name, is it?
It's a typedef. Here is the header file of the DirectX sample code:
//----------------------------------------------------------------------------
// File: Grabber.h
//
// Desc: DirectShow sample code - Header file for the SampleGrabber
// example filter
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//----------------------------------------------------------------------------
--
//----------------------------------------------------------------------------
// Define new GUID and IID for the sample grabber example so that they do NOT
// conflict with the official DirectX SampleGrabber filter
//----------------------------------------------------------------------------
--
// {2FA4F053-6D60-4cb0-9503-8E89234F3F73}
DEFINE_GUID(CLSID_GrabberSample,
0x2fa4f053, 0x6d60, 0x4cb0, 0x95, 0x3, 0x8e, 0x89, 0x23, 0x4f, 0x3f, 0x73);
DEFINE_GUID(IID_IGrabberSample,
0x6b652fff, 0x11fe, 0x4fce, 0x92, 0xad, 0x02, 0x66, 0xb5, 0xd7, 0xc7, 0x8f);
// We define a callback typedef for this example.
// Normally, you would make the SampleGrabber support a COM interface,
// and in one of its methods you would pass in a pointer to a COM interface
// used for calling back. See the DirectX documentation for the SampleGrabber
// for more information.
*typedef HRESULT (SAMPLECALLBACK) (
IMediaSample * pSample,
REFERENCE_TIME * StartTime,
REFERENCE_TIME * StopTime,
BOOL TypeChanged );
// We define the interface the app can use to program us
MIDL_INTERFACE("6B652FFF-11FE-4FCE-92AD-0266B5D7C78F")
IGrabberSample : public IUnknown
{
public:
virtual HRESULT STDMETHODCALLTYPE SetAcceptedMediaType(
const CMediaType *pType) = 0;
virtual HRESULT STDMETHODCALLTYPE GetConnectedMediaType(
CMediaType *pType) = 0;
virtual HRESULT STDMETHODCALLTYPE SetCallback(
SAMPLECALLBACK Callback) = 0;
virtual HRESULT STDMETHODCALLTYPE SetDeliveryBuffer(
ALLOCATOR_PROPERTIES props,
BYTE *pBuffer) = 0;
};
class CSampleGrabberInPin;
class CSampleGrabber;
//----------------------------------------------------------------------------
// This is a special allocator that KNOWS that the person who is creating it
// will only create one of them. It allocates CMediaSamples that only
// reference the buffer location that is set in the pin's renderer's
// data variable
//----------------------------------------------------------------------------
class CSampleGrabberAllocator : public CMemAllocator
{
friend class CSampleGrabberInPin;
friend class CSampleGrabber;
protected:
// our pin who created us
//
CSampleGrabberInPin * m_pPin;
public:
CSampleGrabberAllocator( CSampleGrabberInPin * pParent, HRESULT *phr )
: CMemAllocator( TEXT("SampleGrabberAllocator\0"), NULL, phr )
, m_pPin( pParent )
{
};
~CSampleGrabberAllocator( )
{
// wipe out m_pBuffer before we try to delete it. It's not an allocated
// buffer, and the default destructor will try to free it!
m_pBuffer = NULL;
}
HRESULT Alloc( );
void ReallyFree();
// Override this to reject anything that does not match the actual buffer
// that was created by the application
STDMETHODIMP SetProperties(ALLOCATOR_PROPERTIES pRequest,
ALLOCATOR_PROPERTIES pActual);
};
//----------------------------------------------------------------------------
// we override the input pin class so we can provide a media type
// to speed up connection times. When you try to connect a filesourceasync
// to a transform filter, DirectShow will insert a splitter and then
// start trying codecs, both audio and video, video codecs first. If
// your sample grabber's set to connect to audio, unless we do this, it
// will try all the video codecs first. Connection times are sped up x10
// for audio with just this minor modification!
//----------------------------------------------------------------------------
class CSampleGrabberInPin : public CTransInPlaceInputPin
{
friend class CSampleGrabberAllocator;
friend class CSampleGrabber;
CSampleGrabberAllocator * m_pPrivateAllocator;
ALLOCATOR_PROPERTIES m_allocprops;
BYTE * m_pBuffer;
BOOL m_bMediaTypeChanged;
protected:
CSampleGrabber * SampleGrabber( ) { return (CSampleGrabber*) m_pFilter; }
HRESULT SetDeliveryBuffer( ALLOCATOR_PROPERTIES props, BYTE * m_pBuffer );
public:
CSampleGrabberInPin( CTransInPlaceFilter * pFilter, HRESULT * pHr )
: CTransInPlaceInputPin( TEXT("SampleGrabberInputPin\0"), pFilter, pHr,
L"Input\0" )
, m_pPrivateAllocator( NULL )
, m_pBuffer( NULL )
, m_bMediaTypeChanged( FALSE )
{
memset( &m_allocprops, 0, sizeof( m_allocprops ) );
}
~CSampleGrabberInPin( )
{
if( m_pPrivateAllocator ) delete m_pPrivateAllocator;
}
// override to provide major media type for fast connects
HRESULT GetMediaType( int iPosition, CMediaType *pMediaType );
// override this or GetMediaType is never called
STDMETHODIMP EnumMediaTypes( IEnumMediaTypes **ppEnum );
// override this to refuse any allocators besides
// the one the user wants, if this is set
STDMETHODIMP NotifyAllocator( IMemAllocator *pAllocator, BOOL bReadOnly );
// override this so we always return the special allocator, if necessary
STDMETHODIMP GetAllocator( IMemAllocator **ppAllocator );
HRESULT SetMediaType( const CMediaType *pmt );
// we override this to tell whoever's upstream of us what kind of
// properties we're going to demand to have
//
STDMETHODIMP GetAllocatorRequirements( ALLOCATOR_PROPERTIES *pProps );
};
//----------------------------------------------------------------------------
//
//----------------------------------------------------------------------------
class CSampleGrabber : public CTransInPlaceFilter,
public IGrabberSample
{
friend class CSampleGrabberInPin;
friend class CSampleGrabberAllocator;
protected:
CMediaType m_mtAccept;
SAMPLECALLBACK m_callback;
CCritSec m_Lock; // serialize access to our data
BOOL IsReadOnly( ) { return !m_bModifiesData; }
// PURE, override this to ensure we get
// connected with the right media type
HRESULT CheckInputType( const CMediaType * pmt );
// PURE, override this to callback
// the user when a sample is received
HRESULT Transform( IMediaSample * pms );
// override this so we can return S_FALSE directly.
// The base class CTransInPlace
// Transform( ) method is called by it's
// Receive( ) method. There is no way
// to get Transform( ) to return an S_FALSE value
// (which means "stop giving me data"),
// to Receive( ) and get Receive( ) to return S_FALSE as well.
HRESULT Receive( IMediaSample * pms );
public:
static CUnknown WINAPI CreateInstance(LPUNKNOWN punk, HRESULT phr);
// Expose ISampleGrabber
STDMETHODIMP NonDelegatingQueryInterface(REFIID riid, void ** ppv);
DECLARE_IUNKNOWN;
CSampleGrabber( IUnknown * pOuter, HRESULT * pHr, BOOL ModifiesData );
// IGrabberSample
STDMETHODIMP SetAcceptedMediaType( const CMediaType * pmt );
STDMETHODIMP GetConnectedMediaType( CMediaType * pmt );
STDMETHODIMP SetCallback( SAMPLECALLBACK Callback );
STDMETHODIMP SetDeliveryBuffer( ALLOCATOR_PROPERTIES props, BYTE * m_pBuffer
);
};
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------**
Well, that's the source of your problem.
The DS SampleGrabber's SetCallback method takes an interface pointer
(ISampleGrabberCB). So, if you have a COM object that implements the COM
interface ISampleGrabberCB in any language, you can pass a pointer to that
interface to ISampleGrabber.SetCallback.
However, that's not what this code does. As the comments above this typedef
warn, Normally, you would.... However, that's not what they did. Instead
they just declared a function pointer. While .Net performs all the necessary
magic to call and be called by COM objects, having unmanaged code make calls
to a c# method... I don't even know if you can.
Ah OK, thanks for the clarification. I think I'll have to find some other
means of writing my custom sample grabber. I tried searching for the source
code of the SampleGrabber that DirectShow provides (C1F400A0-3F08-11D3-9F0B-
006008039E37), but I could not find it. Is it available somewhere?
I also read in an MSDN forum that the December 2002 DirectX SDK has a sample
grabber sample, but I couldn't find this on the DirectX SDK downloads site.
Thanks again!
Voc
Have you considered writing a DMO? There are a couple in the samples you can
look at.
Hi Snarfle,
I know it's been a while. Are you saying that if I were to write a DMO, I
would be able to register it as a COM object and be able to implement the
ISampleCallback in managed code as I describe in my OP?
Kind regards,
Voc
A DMO might be described as a light-weight filter, except that unlike DS
filters, DMOs can readily be written in c#..
Read the docs for the DMO Wrapper filter (http://msdn.microsoft.com/en-
us/library/dd375519%28v=VS.85%29.aspx).