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.
Related
I need to write a program that can play .ogg Vorbis file with the help of IGraphBuilder or any other windows API directly (in C++/win32 API)?
I tried with IGraphBuilder but that is not working for me.
Sample code:
IMediaControl *pControl = NULL;
IGraphBuilder *pGraph= NULL;
IMediaEventEx *pEvent= NULL;
IMediaPosition *pMediaPosition= NULL;
hr = ::CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER,
IID_IGraphBuilder, (void **)&pGraph);
if (FAILED(hr)) {
return false;
}
hr = pGraph->AddFilter(pFilter, L"Out");
hr = pGraph->QueryInterface(IID_IMediaControl, (void **)&pControl);
hr = pGraph->QueryInterface(IID_IMediaEvent, (void **)&pEvent);
hr = pGraph->QueryInterface(IID_IMediaPosition, (void**)&pMediaPosition);
// Build the graph.
hr = pGraph->RenderFile(mFilePath.c_str()/*"C:\\sample.ogg file"*/, NULL);
/* here hr = 0x80040265 so SUCCEEDED(hr) didnt allow it to enter in if condition*/
if (SUCCEEDED(hr)) {
// Run the graph.
hr = pControl->Run();
if (SUCCEEDED(hr)) {
// Wait for completion.
long evCode;
pEvent->WaitForCompletion(INFINITE, &evCode);
}
}
// Clean up in reverse order.
SAFE_RELEASE(pEvent);
SAFE_RELEASE(pControl);
SAFE_RELEASE(pGraph);
pGraph = NULL;
::CoUninitialize();
NOTE:
Above statement hr = pGraph->RenderFile() returned the hr = 0x80040265 and condition if (SUCCEEDED(hr)) doesnt allow to play it.
If I dont use this condition then pControl->Run() gets executed with return ID_OK. But nothing played with speaker.
Please suggest the solution/method.
Pay attention to HRESULT error codes; they mean something. MSDN is often helpful with function-specific error codes like the one you got. (With enough COM programming you'll be able to recognize common ones like E_INVALIDARG by sight.) If not, you can use your header files to pinpoint potential error codes. HRESULTs have a specific format; learn it!
In the case of IGraphBuilder::RenderFile(), that HRESULT maps to VFW_E_UNSUPPORTED_STREAM, which basically means your setup does not support playing Ogg Vorbis files. You will need to install a filter that allows DirectShow to play Ogg Vorbis files, such as the official one from Xiph.Org.
I was referring to the example program provided here to run a video file, in my case mp4 format using DShow.
Refer to the complete code:
#include <dshow.h>
#pragma comment (lib, "strmiids.lib")
void main(void)
{
IGraphBuilder *pGraph = NULL;
IMediaControl *pControl = NULL;
IMediaEvent *pEvent = NULL;
// Initialize the COM library.
HRESULT hr = CoInitialize(NULL);
if (FAILED(hr))
{
printf("ERROR - Could not initialize COM library");
return;
}
// Create the filter graph manager and query for interfaces.
hr = CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER,
IID_IGraphBuilder, (void **)&pGraph);
if (FAILED(hr))
{
printf("ERROR - Could not create the Filter Graph Manager.");
return;
}
hr = pGraph->QueryInterface(IID_IMediaControl, (void **)&pControl);
hr = pGraph->QueryInterface(IID_IMediaEvent, (void **)&pEvent);
// Build the graph. IMPORTANT: Change this string to a file on your system.
hr = pGraph->RenderFile(L"C:\\Example.avi", NULL);
if (SUCCEEDED(hr))
{
// 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.
}
}
pControl->Release();
pEvent->Release();
pGraph->Release();
CoUninitialize();
}
However when I build the program, the build is sucessful but when I run it, the console window pops up and disappears within a second.
I referred to the comments on the same page and many others faced the same issue. However a few were able to successfully run the program.
What is it that I am doing wrong?
Is it the type of project I am selecting? I am selecting win32 console application. Should I select something else? Or is there something else that I am doing wrong?
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
THe filter graphs renderer method takes a reference of a output pin. But the FileWriter Filter has no output pin. So how do i make the filter graph render itself?
HRESULT hr = pGraph->Render(FilefilterOutPin); //File Writer does not offer an outputpin
This is my code that builds the graph with its filters:
void static SaveFile()
{
CoInitialize(NULL);
IGraphBuilder *pGraph = NULL;
IMediaControl *pControl = NULL;
IMediaEvent *pEvent = NULL;
HRESULT myhr = CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER,
IID_IFilterGraph, (void **) &pGraph);
pGraph->QueryInterface(IID_IMediaControl, (void **)&pControl);
pGraph->QueryInterface(IID_IMediaEvent, (void **) &pEvent);
IBaseFilter *pFileWritter;
IFileSinkFilter* fileSInk = NULL;
CoCreateInstance(CLSID_FileWriter, NULL, CLSCTX_INPROC_SERVER, IID_IBaseFilter, (void **) &pFileWritter);
pFileWritter->QueryInterface(IID_IFileSinkFilter, (void **) &fileSInk);
fileSInk->SetFileName(L"C:\\TEMP\\yc1.avi", NULL);
//get source out pin
IBaseFilter *pSource;
IPin* SourcePin;
pGraph->AddSourceFilter(L"C:\\TEMP\\sample.avi", L"sample", &pSource);
SourcePin = GetPin(pSource, PINDIR_OUTPUT);
//AVI SPLITTER
IBaseFilter *aviSplitter;
CoCreateInstance(CLSID_AviSplitter, NULL, CLSCTX_INPROC_SERVER, IID_IBaseFilter, (void **) &aviSplitter);
pGraph->AddFilter(aviSplitter, L"AVI-Splitter");
IPin* avisplitIn;
IPin* avisplitOut;
avisplitIn = GetPin(aviSplitter, PINDIR_INPUT);
avisplitOut = GetPin(aviSplitter, PINDIR_OUTPUT);
pGraph->Connect(SourcePin, avisplitIn);
//END
//MyFilter//
IBaseFilter *myFilter;
CoCreateInstance(CLSID_MyFilter, NULL, CLSCTX_INPROC_SERVER, IID_IBaseFilter, (void **) &myFilter);
IMyFilter *myfilterinterface;
myFilter->QueryInterface(IID_IMyFilter, (void **) &myfilterinterface);
myfilterinterface->StartRecording();
pGraph->AddFilter(myFilter, L"MyFilter");
IPin* myInputPin;
IPin* myOutPutPin;
myInputPin = myfilterinterface->GetMyPin(0);
myOutPutPin = myfilterinterface->GetMyPin(3);
pGraph->Connect(avisplitOut, myInputPin);
//END//
///AVMUX
IBaseFilter *avmux;
CoCreateInstance(CLSID_AVIDec, NULL, CLSCTX_INPROC_SERVER, IID_IBaseFilter, (void **) &avmux);
IPin* avmuxIn;
IPin* avmuxOut;
avmuxIn = GetPin(avmux, PINDIR_INPUT);
avmuxOut = GetPin(avmux, PINDIR_OUTPUT);
pGraph->AddFilter(avmux, L"AVmux");
pGraph->Connect(myOutPutPin, avmuxIn);
//END
//get writer inpin
IPin *writerPin;
writerPin = GetPin(pFileWritter, PINDIR_INPUT);
//connect pins
pGraph->Connect(SourcePin, writerPin);
//render
HRESULT hr S_OK;
if(SUCCEEDED(hr))
{
hr = pControl->Run();
if(SUCCEEDED(hr))
{
long evCode;
pEvent->WaitForCompletion(INFINITE, &evCode);
myfilterinterface->StopRecording();
myFilter->Release();
CoUninitialize();
}
}
}
This compiles and runs fine but doesn't produce any file.
Pins connect filter to enable them pass data one to another. File writer does not have anything to stream data to - instead it just writers to file. Hence, no output pin.
To specify the file name you use IFileSinkFilter::SetFileName interface (see When changing a file name, Recording Start is overdue for 3 seconds. OnTimer code snippet using it).
So if Im understanding correctly you want to render to screen AND render to file.
To do this you need to place an infinite pin tee in between your AVI splitter and your AVI mux. 1 output of the tee would continue to go to the AVI Mux and you can use another to stream to a video renderer (e.g a VMR9).
Please read in MSDN what Render method does, it's not what you think. The closest thing to your idea is RenderStream (from a bit different interface, look it up) with the FileWriter in its last parameter. But please learn some DirectShow basics first.
First, FileWriter Filter only has input pin because it receives data and writes to a file.
Second, if you want to play a file you should use File Source (Async.) filter. So, create a instance of File Source (Async.), get it's output pin and pass to
pGraph->Render(FileSourceOutputPin);
Third, alternatively you can render file like this:
pGraph->RenderFile("C:\\test.avi", "");
I am using direct show to play media files within my program, and i have been able to play a media file a single time using the following code.
void main()
{
IGraphBuilder *pGraph = NULL;
IMediaControl *pControl = NULL;
IMediaEvent *pEvent = NULL;
HRESULT hr = CoInitialize(NULL);
if (FAILED(hr))
{
printf("ERROR - Could not initialize COM library");
}
hr = CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER, IID_IGraphBuilder, (void **)&pGraph);
if (FAILED(hr))
{
printf("ERROR - Could not create the Filter Graph Manager.");
}
hr = pGraph->QueryInterface(IID_IMediaControl, (void **)&pControl);
hr = pGraph->QueryInterface(IID_IMediaEvent, (void **)&pEvent);
hr = pGraph->RenderFile(L"C:\\Example.mp3", NULL);
if (SUCCEEDED(hr))
{
hr = pControl->Run();
if (SUCCEEDED(hr))
{
long evCode;
pEvent->WaitForCompletion(INFINITE, &evCode);
}
}
pControl->Release();
pEvent->Release();
pGraph->Release();
CoUninitialize();
return;
}
My question then becomes how would i make the file continue to play and repeat itself a given number of times, or infinitely if i so desired. IMediaControl has no member named loop.
There is no built in way to do this. Once way of implementing this is that instead of exiting you function when you get the completion event, you seek to the beginning of the file an start playback again.
The following link on event handling may help: Learning When an Event Occurs. EC_COMPLETE event/notification is issued when streaming is complete.