IID_IMediaControl is stuck when stopping it - c++

I'm using DirectShow in order to capture video. I'm attaching the camera to sample grabber and saving the images that I get.
In the following code I have IID_ICaptureGraphBuilder2 connected to the capture device (CLSID_VideoCaptureSources) that is connected to the sample grabber (ISampleGrabber).
The graph is connected to IID_IMediaControl.
I use IID_IMediaControl Run and Stop. Most of the times the stop gets stuck.
There's some kind of deadlock. I tried to add IID_IMediaEvent and m_pEvent->WaitForCompletion(INFINITE, &evCode); but it still doesn't work.
Pausing works with no problesm, however upon trying to stop the software is stuck
Building the graph
ICaptureGraphBuilder2 *pBuilder;
IBaseFilter *pCamera;
IPin *pOutPin;
IPin *pInPin;
IBaseFilter *pSampleGrabberFilter;
IBaseFilter *pNullRendererFilter;
ISampleGrabber *pSampleGrabber;
HRESULT hr = CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER, IID_IGraphBuilder, (void **)&m_pGraph);
hr = CoCreateInstance(CLSID_CaptureGraphBuilder2, NULL, CLSCTX_INPROC_SERVER, IID_ICaptureGraphBuilder2, (LPVOID*)&pBuilder);
CHECK_HR(hr, L"Can't create Capture Graph Builder");
hr = pBuilder->SetFiltergraph(m_pGraph);
CHECK_HR(hr, L"Can't SetFiltergraph");
pCamera = CreateFilterByName(pCaptureDeviceName, CLSID_VideoCaptureSources);
WCHAR err[256];
wsprintf(err, L"Can't add Camera '%s' to graph", pCaptureDeviceName);
hr = m_pGraph->AddFilter(pCamera , pCaptureDeviceName);
CHECK_HR(hr, err);
WCHAR filterName[256];
pOutPin = GetPinCapture(pCamera, L"Capture", i);
if (!pOutPin)
continue;
IAMStreamConfig *pConfig = NULL;
hr = pOutPin->QueryInterface(IID_IAMStreamConfig, (void**)&pConfig);
CHECK_HR(hr, L"Can't get configuration");
AM_MEDIA_TYPE *pmt = NULL;
pConfig->GetFormat(&pmt);
VIDEOINFOHEADER *pFrmt = (VIDEOINFOHEADER *)pmt->pbFormat;
pFrmt->bmiHeader.biWidth = 1920;
pFrmt->bmiHeader.biHeight = 1080;
pConfig->SetFormat(pmt);
SAFE_RELEASE(pConfig);
SAFE_RELEASE(pOutPin);
// Create a sample grabber
wsprintf(filterName, L"Sample Grabber %d", i);
// Create a sample grabber
hr = CoCreateInstance(CLSID_SampleGrabber, NULL, CLSCTX_INPROC_SERVER, IID_IBaseFilter, (void **)&pSampleGrabberFilter);
CHECK_HR(hr, L"Unable to create sample grabber filter");
// Initialize sample grabber
hr = pSampleGrabberFilter->QueryInterface(IID_ISampleGrabber, (void **)&pSampleGrabber);
CHECK_HR(hr, L"Unable to get sample grabber");
hr = pSampleGrabber->SetMediaType(pmt);
CHECK_HR(hr, L"Unable to set media type");
hr = pSampleGrabber->SetBufferSamples(false);
CHECK_HR(hr, L"Unable to set buffer samples!");
hr = pSampleGrabber->SetOneShot(false);
CHECK_HR(hr, L"Unable to set one shot!");
// Add the sample grabber to the graph
hr = m_pGraph->AddFilter(pSampleGrabberFilter, filterName);
CHECK_HR(hr, L"Unable to add sample grabber to graph");
pOutPin = GetPinCapture(pCamera, L"Capture", i);
pInPin = GetPin(pSampleGrabberFilter, PINDIR_INPUT);
hr = m_pGraph->ConnectDirect(pOutPin, pInPin, 0);
CHECK_HR(hr, L"Unable to connect Camera to pSampleGrabberFilter");
SAFE_RELEASE(pOutPin);
SAFE_RELEASE(pInPin);
wsprintf(filterName, L"Null Renderer %d", i);
// Create a null renderer
hr = CoCreateInstance(CLSID_NullRenderer, NULL, CLSCTX_INPROC_SERVER, IID_IBaseFilter,(void **)&pNullRendererFilter);
CHECK_HR(hr, L"Unable to create null renderer filter");
hr = m_pGraph->AddFilter(pNullRendererFilter, filterName);
CHECK_HR(hr, L"Unable to add null renderer to graph");
pOutPin = GetPin(pSampleGrabberFilter, PINDIR_OUTPUT);
pInPin = GetPin(pNullRendererFilter, PINDIR_INPUT);
hr = m_pGraph->ConnectDirect(pOutPin, pInPin, 0);
SAFE_RELEASE(pOutPin);
SAFE_RELEASE(pInPin);
pFrmt = ((VIDEOINFOHEADER *)pmt->pbFormat);
// Initialize the capture grabber
m_pCapGrabber[i] = new CapGrabber(i);
m_pCapGrabber[i]->SetVideoInfoHeader(pFrmt);
m_pCapGrabber[i]->SetAttachGrabberCB(m_funcAttachGrabber);
m_pCapGrabber[i]->SetWidth((int)pFrmt->bmiHeader.biWidth);
m_pCapGrabber[i]->SetHeight((int)pFrmt->bmiHeader.biHeight);
// Set the capture callback
hr = pSampleGrabber->SetCallback(m_pCapGrabber[i], 1);
SAFE_RELEASE(pSampleGrabberFilter);
SAFE_RELEASE(pSampleGrabber);
SAFE_RELEASE(pNullRendererFilter);

The problem is typical, but it is a guesswork without additional details you need to find out. It is easy to start multithreaded operation, but when it comes to stopping it, one needs to synchronize threads and inaccurate doing this is a typical cause of a deadlock - what you are seeing.
To address this problem you typically attach debugger to the process in question and inspect its call stacks. You will see one thread calling stop and sleeping somewhere deep within the call waiting for something else to happen. It is likely that there is another thread is doing something suspicious as well.
The threads, call stacks and modules on them are suggesting what's wrong and isolating the problem to specific filter or library. More to that, you might want to start reducing your graph temporarily removing filters until you see that the freeze is gone, and you identify the suspicious filter.

Sometime it is better to pause graph and wait for state change before stopping the graph.
Also note that as per msdn stop will not reset the graph position to the beginning.
http://msdn.microsoft.com/en-us/library/windows/desktop/dd390178(v=vs.85).aspx
So to make sure that graph goes to the beginning after stop. I would recommend use SetPosition interface. Below is the small snippet of code I've used for stopping the graph. I assumed that you have valid media control and media seeking interface.
IMediaControl *m_pControl = NULL;
IMediaSeeking *m_pMediaSeek = NULL;
//Assuming that you have valid media control interface and media seeking interface using QueryInterface
long long m_pStart = 0;
m_pControl->Pause();
m_pControl->GetState(1000, NULL);
m_pMediaSeek->SetPositions(&m_pStart, AM_SEEKING_AbsolutePositioning, NULL, AM_SEEKING_NoPositioning);
m_pControl->Run();
m_pControl->GetState(1000,NULL);
m_pControl->Stop();
m_pControl->GetState(1000,NULL);

Related

Directshow Application crash after exiting of main()

I am writing directshow application. Below is the code that works fine but crashes with the error message "App.exe has stopped working". The entire code is written below. Please note that I am using Windows SDK 7.0 which does not have atlbase.h and hence I cannot use CComPtr<IBaseFilter> myFilter; type of pointer declaration which is supposed to clear memory at exit.
EDIT: The application crashes only if I connect all filters explicitly. In this case, the destructor of my filter is not called. If I just connect source filter to renderer (which will internally connect my filter and a demux filter), the destructor of my filter is called and there is no crash. I have put the macro MANUAL_CONNECT over the code which causes the crash. I have removed RemoveFilter call and replaced it with Release call.
I am writing the application code here:
void main(WORD32 argc, CHAR *argv[])
{
// Initialize the COM library.
HRESULT hr = CoInitialize(NULL);
if (FAILED(hr))
{
printf("ERROR - Could not initialize COM library");
return;
}
{
IGraphBuilder *pGraph = NULL;
IFileSourceFilter *pFileSourceFilter = NULL;
IMediaControl *pControl = NULL;
IMediaEvent *pEvent = NULL;
IBaseFilter *pSource = NULL;
IBaseFilter *pVideoDecode = NULL;
IBaseFilter *pVideoRenderer = NULL;
IEnumPins *pEnumPins = NULL;
IPin *pPinIn = NULL;
IPin *pPinOut = NULL;
ULONG fetched;
PIN_INFO PinInfo;
IEnumFilters *pEnum = NULL;
BOOL stop = FALSE;
int i;
// Create the filter graph manager and query for interfaces.
hr = CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER, IID_IGraphBuilder, (void **)&pGraph);
// Create the filter graph manager and query for interfaces.
hr = CoCreateInstance(CLSID_AsyncReader, NULL, CLSCTX_INPROC_SERVER, IID_IBaseFilter, (void **)&pSource);
hr = pGraph->AddFilter(pSource, NULL);
hr = pSource->QueryInterface(IID_IFileSourceFilter, (void**)&pFileSourceFilter);
hr = pFileSourceFilter->Load(L"input.mp4", NULL);
// Create Ittiam HEVC Decoder instance
hr = CoCreateInstance(CLSID_ivdec, NULL, CLSCTX_INPROC_SERVER, IID_IBaseFilter, (void **)&pVideoDecode);
// Create Video Renderer instance. We have used default video renderer
hr = CoCreateInstance(CLSID_VideoRendererDefault, NULL, CLSCTX_INPROC_SERVER, IID_IBaseFilter, (void **)&pVideoRenderer);
// Add decoder filter to the filter graph
hr = pGraph->AddFilter(pVideoDecode, NULL);
// Add renderer filter to the filter graph
hr = pGraph->AddFilter(pVideoRenderer, NULL);
/**************************************************************/
/* -- Connecting source filter to demux filter starts here -- */
/**************************************************************/
// Enumerate pins of the source filter
hr = pSource->EnumPins(&pEnumPins);
hr = pEnumPins->Reset();
// Get pin of source filter. Source filter has only output pin, so no check required
hr = pEnumPins->Next(1, &pPinOut, &fetched);
hr = pEnumPins->Release();
#if MANUAL_CONNECT
// Enumerate pins of the decoder filter
hr = pVideoDecode->EnumPins(&pEnumPins);
hr = pEnumPins->Reset();
// Get pin of decoder filter. Decoder filter has 2 pins, so ensure the selected pin is input pin.
// If not, get another pin
hr = pEnumPins->Next(1, &pPinIn, &fetched);
hr = pPinIn->QueryPinInfo(&PinInfo);
if(PINDIR_OUTPUT == PinInfo.dir)
{
hr = pPinIn->Release();
hr = pEnumPins->Next(1, &pPinIn, &fetched);
}
// Connect output pin of demux filter to input pin of decoder filter
hr = pGraph->Connect(pPinOut, pPinIn);
/*************************************************************/
/* -- Connecting demux filter to decoder filter ends here -- */
/*************************************************************/
/******************************************************************/
/* -- Connecting decoder filter to renderer filter starts here -- */
/******************************************************************/
// Enumerate pins of the decoder filter
hr = pVideoDecode->EnumPins(&pEnumPins);
hr = pEnumPins->Reset();
// Get pin of decoder filter. Decoder filter has 2 pins, so ensure the selected pin is output pin.
// If not, get another pin
hr = pEnumPins->Next(1, &pPinOut, &fetched);
hr = pPinOut->QueryPinInfo(&PinInfo);
if(PINDIR_INPUT == PinInfo.dir)
{
hr = pPinOut->Release();
hr = pEnumPins->Next(1, &pPinOut, &fetched);
}
hr = pEnumPins->Release();
#endif
// Enumerate pins of the renderer filter
hr = pVideoRenderer->EnumPins(&pEnumPins);
hr = pEnumPins->Reset();
// Get pin of renderer filter. Renderer filter has only input pin, so no check required
hr = pEnumPins->Next(1, &pPinIn, &fetched);
hr = pPinIn->QueryPinInfo(&PinInfo);
// Connect output pin of decoder filter to input pin of renderer filter
hr = pGraph->Connect(pPinOut, pPinIn);
/****************************************************************/
/* -- Connecting decoder filter to renderer filter ends here -- */
/****************************************************************/
hr = pGraph->QueryInterface(IID_IMediaControl, (void **)&pControl);
hr = pGraph->QueryInterface(IID_IMediaEvent, (void **)&pEvent);
// Run the graph.
hr = pControl->Run();
if (SUCCEEDED(hr))
{
// Wait for completion.
long evCode;
pEvent->WaitForCompletion(INFINITE, &evCode);
// Note: Do not use INFINITE in a real application, because it
// can block indefinitely.
}
hr = pControl->Stop();
hr = pSource->Release();
hr = pVideoDecode->Release();
hr = pControl->Release();
hr = pEvent->Release();
hr = pGraph->Release();
}
CoUninitialize();
printf("Exiting main!!\n");
}
I have removed error checks from the post but I have all error checks in my code. I can see the Exiting main!! print but than the application crashes. Any suggestions on how to debug this? Please let me know if any information is missing. I am using Microsoft Visual C++ 2010 Express for my development.
You must terminate all COM activity (specifically: release all COM interface pointers) on the thread before calling CoUninitialize, you don't do it.
See, for example, this code and its Release calls at the _tmain bottom.
It makes sense to use more recent version of Visual Studio (2013, 2015), where free community edition already includes ATL and you can enjoy automatic COM interface reference management using CComPtr and friends. This is the first advise to those who uses raw COM interface pointers and experience issues managing them incorrectly.
See also:
Not releasing filter com object causing a crash
Unreleased DirectShow CSource filter makes program crash at process shutdown
Why does CoUninitialize cause an error on exit?
UPDATE: Using raw pointers inaccurately, you keep having leaks:
hr = pGraph->Connect(pPinOut, pPinIn);
// ...
hr = pEnumPins->Next(1, &pPinOut, &fetched);
IEnumPin::Next call overwrites pPinOut pointer and leaks a reference.

How to play a sound (mp3 / wav) on windows using native API on selected output device

I simply want to be able to create options for my program, so that user can pick which output device will be used to play sounds, like this one in MS Lync:
I originally created my program in Qt and I asked similar (but not identical) question here Qt5+ How to set default audio device for QMediaPlayer
I figured out that Qt is too much bugged for this and this is impossible, so I lowered my requirements and I will use native windows API as these are probably only solution here. This unfortunately requires rewrite of some parts of my program, and now I am following this guide on msdn: https://msdn.microsoft.com/en-us/library/windows/desktop/dd371455%28v=vs.85%29.aspx
I basically want to be able to do following:
List all available output devices and display them on preferences form - I already have a working code for that using IMMDeviceEnumerator
Let user pick a device they want to use for output of my program - I already have that part
Create a function, let's call it PlaySound(string path) that if called with path of .wav or .mp3 file would use the preferred IMMDevice and play a file through it - this is what I need help with
Because I was using Qt so far and I have pretty much no idea of MS windows internals, I have no idea how could one take a file stored somewhere on disk and play it using windows API's especially using that selected IMMDevice which user set in their preferences. I was googling and searching through documentation, but I could only work extremely complex and weird solutions, such as https://msdn.microsoft.com/en-us/library/windows/desktop/dd316756%28v=vs.85%29.aspx
I could even find some examples where you can play mp3 file using MCI device, but that didn't really explain how to alter preferred output device, so it isn't very useful for my use.
I understand that low-level API is probably not going to offer some simple "playmyfile" function, but it would be nice to have at least some example of super-simple solution or some tutorial that would play media files using selected output device on windows so that I could use that as a starting reference. I have a working active IMMDevice, now I just need to make it possible to play mp3 / wav files through it.
NOTE: This is not some generic "how to play a sound on windows" question. I need to be able to play that sound on selected audio output device. For my program only (just like MS Lync, VLC media player or any other advanced audio program can). I don't want to change system global preferences (default device etc).
I managed to do that but surprisingly using windows native libraries called "DirectShow" which are primarily designed for video rendering, but can handle audio as well.
How to:
Enumerate output devices
This functions iterates over all audio devices detected by OS and store them in a list.
void Options::Initialize()
{
#ifdef WIN
HRESULT hr;
ICreateDevEnum *pSysDevEnum = NULL;
hr = CoCreateInstance(CLSID_SystemDeviceEnum, NULL, CLSCTX_INPROC_SERVER, IID_ICreateDevEnum, (void **)&pSysDevEnum);
if (FAILED(hr))
return;
IEnumMoniker *pEnumCat = NULL;
hr = pSysDevEnum->CreateClassEnumerator(CLSID_AudioRendererCategory, &pEnumCat, 0);
if (hr == S_OK)
{
// Enumerate the monikers.
IMoniker *pMoniker = NULL;
ULONG cFetched;
while (pEnumCat->Next(1, &pMoniker, &cFetched) == S_OK)
{
IPropertyBag *pPropBag;
hr = pMoniker->BindToStorage(0, 0, IID_IPropertyBag, (void **)&pPropBag);
if (SUCCEEDED(hr))
{
// To retrieve the filter's friendly name, do the following:
VARIANT varName;
VariantInit(&varName);
hr = pPropBag->Read(L"FriendlyName", &varName, 0);
if (SUCCEEDED(hr))
{
OutputDevice device;
device.Name = QString((QChar*)varName.bstrVal, wcslen(varName.bstrVal));
Options::devices.append(device);
}
VariantClear(&varName);
pPropBag->Release();
}
pMoniker->Release();
}
pEnumCat->Release();
}
pSysDevEnum->Release();
#endif
}
Create a filter for device that user selected
Iterate over all devices once more and make a filter for that which was selected by user
HRESULT hr;
ICreateDevEnum *pSysDevEnum = NULL;
hr = CoCreateInstance(CLSID_SystemDeviceEnum, NULL, CLSCTX_INPROC_SERVER, IID_ICreateDevEnum, (void **)&pSysDevEnum);
if (FAILED(hr))
{
Error("Failed SystemDeviceEnum");
return;
}
IEnumMoniker *pEnumCat = NULL;
QSettings s;
hr = pSysDevEnum->CreateClassEnumerator(CLSID_AudioRendererCategory, &pEnumCat, 0);
IBaseFilter *pFilter = NULL;
if (hr == S_OK)
{
// Enumerate the monikers.
IMoniker *pMoniker = NULL;
ULONG cFetched;
int i = 0;
while (pEnumCat->Next(1, &pMoniker, &cFetched) == S_OK)
{
IPropertyBag *pPropBag;
hr = pMoniker->BindToStorage(0, 0, IID_IPropertyBag, (void **)&pPropBag);
if (SUCCEEDED(hr))
{
// retrieve the filter's friendly name now
VARIANT varName;
VariantInit(&varName);
hr = pPropBag->Read(L"FriendlyName", &varName, 0);
if (SUCCEEDED(hr))
{
QString name = QString((QChar*)varName.bstrVal, wcslen(varName.bstrVal));
if (s.value("d:" + name).toBool())
{
hr = pMoniker->BindToObject(NULL, NULL, IID_IBaseFilter, (void**)&pFilter);
// now we got the filter in pFilter so we can play sound using that filter
PlayWin(pFilter, path);
}
}
VariantClear(&varName);
pPropBag->Release();
}
pMoniker->Release();
}
pEnumCat->Release();
}
pSysDevEnum->Release();
Play the sound using the filter for our device
In this function device is pFilter from previous function
HRESULT hr = CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER, __uuidof(IGraphBuilder), (void **)&x->pGraph);
if (FAILED(hr))
{
Error("ERROR - Could not create the Filter Graph Manager.");
return;
}
hr = x->pGraph->QueryInterface(IID_IBasicAudio, (void**)&x->pOutput);
if (FAILED(hr))
{
Error("ERROR - Could not create the IBasicAudio.");
return;
}
x->pFlx = device;
if (device)
x->pGraph->AddFilter(device, L"fd");
hr = x->pGraph->QueryInterface(__uuidof(IMediaControl), (void **)&x->pControl);
hr = x->pGraph->QueryInterface(__uuidof(IMediaEvent), (void **)&x->pEvent);
// Build the graph.
hr = x->pGraph->RenderFile(path, NULL);
if (SUCCEEDED(hr))
{
// Run the graph.
hr = x->pControl->Run();
}
else
{
Error("Unable to play: " + QString::fromWCharArray(path));
}
This code on itself is of course not going to compile out of box, but it gives you a clue how to do this, in nutshell:
Retrieve list of all devices and store it somewhere, so that we can create dialog for user
Before we play a sound, we check which device user selected and create a filter for it
We apply the filter to DirectShow BasicAudio which is itself able to play any media file supported by system codecs.
Documentation on msdn: https://msdn.microsoft.com/en-us/library/windows/desktop/dd407292%28v=vs.85%29.aspx

DirectShow dilemma - Not able to record

That's the actual main code, preview works fine:
int main()
{
HRESULT hr = CoInitialize(NULL);
ICaptureGraphBuilder2 *pBuild;
IGraphBuilder *pGraph;
IMoniker *pMoniker;
IMediaControl *pControl;
IMediaEvent *pEvent;
InitCaptureGraphBuilder(&pGraph, &pBuild);
hr = pGraph->QueryInterface(IID_IMediaControl, (void **)&pControl);
hr = pGraph->QueryInterface(IID_IMediaEvent, (void **)&pEvent);
IBaseFilter *pCap; // Video capture filter
IEnumMoniker *pEnum;
hr = EnumerateDevices(CLSID_VideoInputDeviceCategory, &pEnum);
DisplayDeviceInformation(pEnum, &pMoniker);
hr = pMoniker->BindToObject(0, 0, IID_IBaseFilter, (void**)&pCap);
if (SUCCEEDED(hr))
{
hr = pGraph->AddFilter(pCap, L"Capture Filter");
}
hr = pBuild->RenderStream(&PIN_CATEGORY_PREVIEW, &MEDIATYPE_Video, pCap, NULL, NULL);
hr = pControl->Run();
_getch();
pControl->Release();
pCap->Release();
pGraph->Release();
pBuild->Release();
CoUninitialize();
return 0;
}
Now, I know that for recording I need this piece of code:
IBaseFilter *pMux;
hr = pBuild->SetOutputFileName(&MEDIASUBTYPE_Avi, L"D:\\test.avi", &pMux, NULL);
hr = pBuild->RenderStream(&PIN_CATEGORY_CAPTURE, &MEDIATYPE_Video, pCap, NULL, pMux);
If I replace this to the preview code, it actually create the AVI file (a very big one), but it's empty, no video.
I mean I'm replacing the:
hr = pBuild->RenderStream(&PIN_CATEGORY_PREVIEW, &MEDIATYPE_Video, pCap, NULL, NULL);
code, with the one above.
What I'm doing wrong, or better, what I'm missing?
RenderStream is high level method that internally embeds other calls through public documented APIs, typically for the ease of use. While it looks simple, it's not so easy to troubleshoot in case something does not work well and as expected. Even harder to tell inspecting just code visually. It also is not the most efficient because there is something you can do yourself to get closer to the solution, which is:
Your further steps are along either of the two:
You take working sample code and compare to yours looking at differences and locating the source of the problem.
You inspect the resulting filter graph topology putting your graph onto ROT, and checking using GraphEdit or a similar tool to ensure the topology is matching your expectations.
You also certainly need to check HRESULT codes, what you already seem to be doing.

Capturing Video to an AVI File with DirectShow

I'm trying to write a C++ application with directshow that saves video capture to file.
The steps in the code are:
1. Create the Capture Graph Builder
2. Create the System Device Enumerator
3. Create the System Device Enumerator - in order to get capture filter
4. Create an enumerator for the video capture category
5. Create query to capture the video
Attaching the code
// gets the device filter
HRESULT getDeviceFilter(REFCLSID clsid, int order, IBaseFilter **pCap)
{
ICreateDevEnum *pDevEnum = NULL;
IEnumMoniker *pEnum = NULL;
// Create the System Device Enumerator.
HRESULT hr = CoCreateInstance(CLSID_SystemDeviceEnum, NULL,
CLSCTX_INPROC_SERVER, IID_ICreateDevEnum,
reinterpret_cast<void**>(&pDevEnum));
if (SUCCEEDED(hr))
{
// Create an enumerator for the video capture category.
hr = pDevEnum->CreateClassEnumerator( clsid, &pEnum, 0);
}
IMoniker *pMoniker = NULL;
if (pEnum->Next(1, &pMoniker, NULL) == S_OK)
hr = pMoniker->BindToObject(0, 0, IID_IBaseFilter, (void**)pCap);
return hr;
}
int main()
{
IGraphBuilder *pGraph = 0;
ICaptureGraphBuilder2 *pBuild = 0;
IBaseFilter *pCap = 0;
HRESULT hr = CoInitialize(NULL);
// Create the Capture Graph Builder.
hr = CoCreateInstance(CLSID_CaptureGraphBuilder2,
NULL,
CLSCTX_INPROC_SERVER,
IID_ICaptureGraphBuilder2,
(void**)&pBuild );
ICreateDevEnum *pDevEnum = NULL;
IEnumMoniker *pEnum = NULL;
// Create the System Device Enumerator.
hr = CoCreateInstance(CLSID_SystemDeviceEnum,
NULL,
CLSCTX_INPROC_SERVER,
IID_ICreateDevEnum,
reinterpret_cast<void**>(&pDevEnum));
IBaseFilter *pMux = 0;
hr = pBuild->SetOutputFileName(&MEDIASUBTYPE_Avi, // Specifies AVI for the target file.
L"C:\\Example.avi", // File name.
&pMux, // Receives a pointer to the mux.
NULL); // (Optional) Receives a pointer to the file sink.
// gets the first device, VDM tv card
hr = getDeviceFilter(CLSID_VideoInputDeviceCategory, 0, &pCap);
hr = pBuild->RenderStream(&PIN_CATEGORY_CAPTURE, // Pin category.
&MEDIATYPE_Video, // Media type.
pCap, // Capture filter.
NULL, // Intermediate filter (optional).
pMux); // Mux or file sink filter.
// Release the mux filter.
pMux->Release();
IConfigAviMux *pConfigMux = NULL;
hr = pMux->QueryInterface(IID_IConfigAviMux, (void**)&pConfigMux);
if (SUCCEEDED(hr))
{
pConfigMux->SetMasterStream(1);
pConfigMux->Release();
}
return 0;
}
However, upon calling RenderStream I get an E_INVALIDARG error
Any suggestions?
Thanks
Take a look at this topic. It seems you have missed some steps.
First of all, you are not using pGraph anywhere. You should create a graph manager, and then initialize the graph builder by providing it with a pointer to the graph manager using SetFilterGraph.
// Create the Filter Graph Manager.
hr = CoCreateInstance(CLSID_FilterGraph, 0, CLSCTX_INPROC_SERVER,
IID_IGraphBuilder, (void**)&pGraph);
if (SUCCEEDED(hr))
{
// Initialize the Capture Graph Builder.
pBuild->SetFiltergraph(pGraph);
// ...
}
Second, you are using filters that are not managed by a graph manager. Quoting from here:
All of the filters specified by pSource, pIntermediate, and pSink must be added to the graph prior to calling the method.
You will have to add the filters pCap and pMux to the graph manager you created earlier, using AddFilter. You should do this before calling RenderStream. This is so because RenderStream eventually calls connection methods on the manager.
If the above steps do not solve your problem, there are several other things you can try.
Device Filter. You are using the first device of CLSID_VideoInputDeviceCategory, but are you sure this is the correct device? Webcams and such are also included in this category. Make sure that there are no other devices of the same category connected, and try again.
Connection. Every device is different. It could be that your device can't be directly connected to the mux. In this case, we will have to figure out why, and determine whether you need to connect additional filters (like decoders). GraphEdit is a very fast way to find these filters.
Pin Category/Media Type. In my experience, E_INVALIDARG is 90% of the time caused by the first 2 parameters of RenderStream. Try setting the pin category or the media type to NULL.
System Device Enumerator: As you described yourself, you are creating a system device enumerator twice. This seems odd to me, why not use one, for both purposes?
If your code still does not work, you should provide me with more information. Have you achieved your goals when using GraphEdit? How does your VDM TV card filter look like (pins, media types)?

How to render and save captured Video/Audio into a custom file/filter format in DirectShow?

Basiclly, I want to capture audio/video. Run it through a mp4 muxer and save it to a file on disk. Before I used ICaptureGraphBuilder2, but it seems unusuable when saving to custom formats.
My code so far,
I enumerate video/audio devices. In this sample I only try to capture audio. I get the correct device and use GetPin to enumerate the filters pins to get its output pin.
hr = pMoniker2->BindToObject(0, 0, IID_IBaseFilter, (void**)&pSrc2);
hr = pGraph->AddFilter(pSrc2, L"AudioCap");
hr = GetPin(pSrc2, PINDIR_OUTPUT, &outPin);
This is the custom filter, a MP4 muxer. It properly loads and I can get the input pin and connect it with my output pin. So far so good.
HRESULT hr = CreateObjectFromPath(TEXT("c:\\filters\\mp4mux.dll"), clsid, &pUnk);
if (SUCCEEDED(hr))
{
IBaseFilterPtr pFilter = pUnk;
HRESULT hr = pGraph->AddFilter(pFilter, L"Private Filter");
hr = GetPin(pFilter, PINDIR_INPUT, &inPin);
}
hr = pGraph->Connect(outPin, inPin);
This is where I get lost, I can't find out how to take the next steps to Render and save the output to a file on disk. So any help with the next steps would be greatfully appreciated, thanks in advance!
EDIT: Filesink code
AM_MEDIA_TYPE mType;
mType.majortype = MEDIATYPE_Video;
mType.subtype = MEDIASUBTYPE_H264;
mType.bFixedSizeSamples = FALSE;
mType.bTemporalCompression = TRUE;
mType.lSampleSize = 0;
mType.formattype = FORMAT_None;
mType.pUnk = NULL;
mType.cbFormat = 0;
mType.pbFormat = NULL;
//Not 100% sure about the setup of the media format.
IBaseFilter * iFiltera = NULL;
IFileSinkFilter* iFilter = NULL;
IGraphBuilder *pGraph;
hr = pMoniker2->BindToObject(0, 0, IID_IBaseFilter, (void**)&pSrc2); //audio capture
hr = CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER, IID_IGraphBuilder, (void**)&pGraph);
hr = CoCreateInstance(CLSID_FileWriter, NULL, CLSCTX_INPROC_SERVER, IID_IBaseFilter, (void**)&iFiltera);
hr = pBuild->SetFiltergraph(pGraph);
hr = pGraph->AddFilter(pSrc2, L"AudioCap");
hr = GetPin(pSrc2, PINDIR_OUTPUT, &outPin); //ADDED
hr = pGraph->AddFilter(iFiltera, L"FileWriter");
hr = iFiltera->QueryInterface(IID_IFileSinkFilter, (void**)&iFilter);
iFilter->SetFileName((LPCOLESTR)"c:\\wav\\tester.mp4", NULL); //UPDATED mType set to NULL
HRESULT hr = CreateObjectFromPath(TEXT("c:\\filters\\mp4mux.dll"), clsid, &pUnk);
IBaseFilterPtr pFilter = pUnk;
if (SUCCEEDED(hr))
{
HRESULT hr = pGraph->AddFilter(pFilter, L"Private Filter");
hr = GetPin(pFilter, PINDIR_INPUT, &inPin); //mux in
hr = GetPin(pFilter, PINDIR_OUTPUT, &mOutPin); //mux out
hr = GetPin(iFiltera, PINDIR_INPUT, &filePin); // filewriter in
}
hr = pGraph->Connect(outPin, inPin); //connect audio out and mux in
hr = pGraph->Connect(mOutPin, filePin); //connect mux out and file in; Error 0x80040217(VFW_E_CANNOT_CONNECT?) //works now
//ADDED code
IMediaControl *pMC = NULL;
hr = pGraph->QueryInterface(IID_IMediaControl, (void **)&pMC);
hr = pMC->Run();
Sleep(4000);
hr = pMC->Stop();
You need to have an idea what filter graph topology you need for specific task. You are doing capture, here - fine. So you have audio capture filter you provided code snippet for. Then you either compress audio (where your preferred choice should be AAC AKA MPEG-4 Part 3, provided that you are to produce MP4 file), or keep audio uncompressed PCM. Then you connect MPEG-4 multiplexer as you do. The multiplexer produces output stream and you are expected to complete the pipeline with File Writer Filter.
You can manually build the chain in GraphEdit SDK application (or there are alternate richer tools). Your filter graph looks like this:
Note that you can expose filter graph in your application, and connect to it remotely and inspect the topology. This makes debugging much easier. Starting/stopping the filter graph (IMediaControl::Run, ::Stop from code) creates you the file.
My understanding is that you get lost immediately after adding multiplexer. Now you need to find its output pin, add File Writer, query its IFileSinkFilter, set the destination file name using it, find its input pin, connect the two unconnected pins (mux output, writer input). Your pipeline is ready to run.