How to play ogg Vorbis files using IGraphBuilder - c++

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.

Related

DShow Sample Code for playing video does not play the video MSDN C++

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?

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

Make media loop many times with direct show c++

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.

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.

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.