I have created a custom allocator/presenter that works fine for playback of normal media files. However, when I use the following code to try to playback a DVD, it fails with a stack overflow exception.
vmr9_ap = new vmr9ap();
HMONITOR monitor = MonitorFromWindow(hwnd, NULL);
IGraphBuilder *graph;
IBaseFilter *filter;
IDvdGraphBuilder *builder;
CoCreateInstance(CLSID_DvdGraphBuilder, NULL, CLSCTX_INPROC_SERVER, IID_IDvdGraphBuilder, reinterpret_cast<void**>(&builder));
CoCreateInstance(::CLSID_VideoMixingRenderer9, NULL, CLSCTX_INPROC_SERVER, IID_IBaseFilter, reinterpret_cast<void**>(&filter));
builder->GetDvdInterface(IID_IVMRFilterConfig9, (void**)&vmr9_config);
vmr9_ap->Initialize(g_pd3dDevice, monitor, vmr9_config);
HRESULT hr = builder->RenderDvdVideoVolume(L"G:\\VIDEO_TS", AM_DVD_SWDEC_PREFER | AM_DVD_VMR9_ONLY, &status);
builder->GetFiltergraph(&graph);
IDvdControl2 *dvdControl;
builder->GetDvdInterface(::IID_IDvdControl2, (void**)&dvdControl);
graph->QueryInterface(::IID_IMediaControl, (void**)&control);
HRESULT h = control->Run();
The stack overflow happens immediately after the call to control->Run(). It's driving me nuts, as I'm sure I'm just forgetting something really really simple.
Thanks.
Your graph should look something like this. Make sure there aren't any buggy filters in your graph.
Because you are using a custom allocator, I would look there for the issue and set some breakpoints there. You code you pasted might be incomplete as I do not see you configure the VMR9 with the custom allocator, nor do I see it being added to the graph. I avoid using the DVDGraphBuilder as I had too difficult of a time getting it RenderVolume correctly with my VMR9+Allocator. I would build the graph a little more manually.
I have a custom allocator in my open source project, along w/ a DVD player. You can check that out for reference, though there is a lot of code noise in there due to me needing to hack a few things in there for WPF compatiblity. http://wpfmediakit.codeplex.com
What you are seeing should NOT be a DRM issue.
alt text http://img29.imageshack.us/img29/7798/capturelu.jpg
Could it be a form of DRM protection? Decoders in DVD graphs will typically try to prevent you building graphs that get access to the uncompressed data as you do here. Normally they do that by a cleaner method, such as refusing to connect to unauthorised renderers, but it's possible that this might be caused by something like that -- certainly there are mpeg-2 decoders which use deliberate crashes to prevent reverse engineering.
G
Thanks to the code Jeremiah Morrill pointed me to, I managed to get playback mostly working.
It works fine, as long as you don't try to resize the D3DImage that it's played in. Devil's in the details, I suppose.
Thanks to all the answers. DVD playback doesn't work with a debugger attached, which according to Google, is not DRM, but is an anti-reverse engineering measure. Could be particular to the DVD codec I'm using too.
extern "C" __declspec(dllexport) LPDIRECT3DSURFACE9 InitializeDvd(HWND hWnd)
{
CoInitialize(NULL);
IPin *dvdVideoOut;
IPin *vmr9VideoIn;
HRESULT hr = S_OK;
hr = CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER,
IID_IGraphBuilder, (void **)&graph);
if(graph)
{
hr = CoCreateInstance(CLSID_DVDNavigator, NULL, CLSCTX_INPROC_SERVER,
IID_IBaseFilter, (void **)&dvdNavigator);
if(dvdNavigator)
{
hr = graph->AddFilter(dvdNavigator, L"DVD Navigator");
if(SUCCEEDED(hr))
{
hr = CoCreateInstance(CLSID_VideoMixingRenderer9, NULL, CLSCTX_INPROC_SERVER,
IID_IBaseFilter, (void **)&vmr9);
if(vmr9)
{
hr = vmr9->QueryInterface(IID_IVMRFilterConfig9, reinterpret_cast<void**>(&p_fConfig));
p_Ap = new VMR9AllocatorPresenter();
p_Dh = new DeviceHandler();
p_device = p_Dh->Initialize(hWnd);
p_fConfig->SetRenderingMode(VMR9Mode_Renderless);
p_fConfig->SetNumberOfStreams(1);
p_Ap->Initialize(hWnd, p_device, p_fConfig);
if(SUCCEEDED(hr))
{
hr = graph->AddFilter(vmr9, L"Video Mixing Renderer 9");
if(p_fConfig)
{
dvdNavigator->FindPin(L"Video", &dvdVideoOut);
if(dvdVideoOut)
{
hr = graph->Render(dvdVideoOut);
}
hr = graph->QueryInterface(IID_IMediaControl, reinterpret_cast<void**>(&control));
if(control)
{
control->Run();
}
}
}
}
}
}
}
return p_Dh->g_surface9;
}
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 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
I am trying to basically render a preview from a capture card (720p) from a PS3 to the enhanced video render.
Ideally, I would like something like this:
I used to do this:
hr = m_pCapture->RenderStream (&PIN_CATEGORY_CAPTURE, &MEDIATYPE_Video, m_pSrcFilter, NULL, NULL);
But I find that it only renders to an old default renderer, which is not adequate enough to stretch the image to 1080p (image becomes pixelated). [http://msdn.microsoft.com/en-us/library/aa930715.aspx ]
I want to use the enhanced video render as the sink but I have no idea how to. I viewed the tutorials here: http://msdn.microsoft.com/en-us/library/windows/desktop/ff625867%28v=vs.85%29.aspx
And tried to put my code in but it would not render.
Here is a snippet of the code that sets the source. Assume that setResolution will set the AM_MEDIA_TYPE format and that getVideoSourceByKeyword will get the AVermedia capture card device.
HRESULT DShowPlayer::SetPreviewDevice(PCWSTR keyname)
{
IBaseFilter *pSource = NULL;
// Create a new filter graph. (This also closes the old one, if any.)
HRESULT hr = CoCreateInstance(CLSID_CaptureGraphBuilder2, NULL,
CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&m_pCapture));
if (FAILED(hr))
{
goto done;
}
hr = InitializeGraph();
if (FAILED(hr))
{
goto done;
}
// Add the source filter to the graph.
hr = getVideoSourceByKeyword(keyname, &pSource);
if (FAILED(hr))
{
goto done;
}
hr = m_pGraph->AddFilter(pSource, L"Source filter");
if (FAILED(hr))
{
goto done;
}
setResolution(pSource, 1280, 720);
// Try to render the streams.
hr = RenderStreams(pSource);
if (FAILED(hr))
{
goto done;
}
hr = m_pControl->Run();
done:
if (FAILED(hr))
{
TearDownGraph();
}
SafeRelease(&pSource);
return hr;
}
When the code runs RenderStreams, this is the code (from http://msdn.microsoft.com/en-us/library/windows/desktop/ff625878%28v=vs.85%29.aspx):
// Enumerate the pins on the source filter.
hr = pSource->EnumPins(&pEnum);
if (FAILED(hr))
{
goto done;
}
// Loop through all the pins
IPin *pPin;
while (S_OK == pEnum->Next(1, &pPin, NULL))
{
PIN_INFO pInfo;
pPin->QueryPinInfo(&pInfo);
// Try to render this pin.
// It's OK if we fail some pins, if at least one pin renders.
HRESULT hr2 = pGraph2->RenderEx(pPin, AM_RENDEREX_RENDERTOEXISTINGRENDERERS, NULL);
pPin->Release();
if (SUCCEEDED(hr2))
{
bRenderedAnyPin = TRUE;
}
}
In visual studio I debugged at the pin to get the source name ("Capture" pin name of the AVermedia capture card). It said it was successful to attach to the render at RenderEx however at
hr = m_pControl->Run();
It fails and there error is device is not connected.
I also tried to get the EVR renderer directly and tried to render the stream:
IBaseFilter* render;
m_pVideo->getRender(&render);
m_pGraph->AddFilter(render, L"EVR Filter");
hr = m_pCapture->RenderStream(&PIN_CATEGORY_CAPTURE, &MEDIATYPE_Video, pSource, NULL, render);
if (FAILED(hr))
{
goto done;
}
But it fails and says that VFW_E_NOT_IN_GRAPH.
What I am asking: I am still pretty new at learning Directshow and I would like to be able to preview the capture card with EVR. I found no comprehensive tutorials or source code to do this. If you need anymore information, I can add more.
Thanks in advance.
EVR can be used programmatically very much the same way as VMR-7/9. The only difference is that EVR needs "windowless" mode, while earlier renderers supported also "windowed" mode where you need minimal initialization of the renderer.
I suppose you can see video on EVR in GraphEdit? You should be able to do so, just use Preview pin, not Capture. Or, connect Capture through Smart Tee filter and its preview output.
The error codes suggest that you don't build graph correctly. In particular, VFW_E_NOT_IN_GRAPH says your filter is not in graph and hence invalid argument. You don't need to use getRender, just CoCreateInstance the EVR the usual and straightforward way. At the first moment you get an error you are interested in putting everything on hold and reviewing the filter graph topology you have at the moment.
Windows SDK samples contain \Samples\multimedia\directshow\vmr9\windowless which shows VMR-9 in windowless mode, this is supposedly the closest starting point to just switch from VMR-9 to EVR.
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 do I from C or C++ use the MP3 decoder supposedly built in with Windows since Windows Media Player 6.1?
I want to play an mp3 file without having to depend on any other third party library such as for instance LAME.DLL.
I updated the question to better fit the answers I got, since I liked them a lot. Related question.
Sure. Like lots of other things in the Windows API, there's more than one way to go about playing .mp3 files. The "easiest" way to do this programmatically is using DirectShow. The MSDN docs even include a minimal code example on a page aptly called "How To Play a File" to get you started:
// Visual C++ example
#include <dshow.h>
#include <cstdio>
// For IID_IGraphBuilder, IID_IMediaControl, IID_IMediaEvent
#pragma comment(lib, "strmiids.lib")
// Obviously change this to point to a valid mp3 file.
const wchar_t* filePath = L"C:/example.mp3";
int main()
{
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 0;
}
// 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 0;
}
hr = pGraph->QueryInterface(IID_IMediaControl, (void **)&pControl);
hr = pGraph->QueryInterface(IID_IMediaEvent, (void **)&pEvent);
// Build the graph.
hr = pGraph->RenderFile(filePath, 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.
}
}
// Clean up in reverse order.
pEvent->Release();
pControl->Release();
pGraph->Release();
::CoUninitialize();
}
Make sure you read through the DirectShow documentation to get an idea of what's supposed to happen in a proper DirectShow application.
To "feed" media data into a graph, you need to implement a IAsyncReader. Fortunately, the Windows SDK includes a sample that implements an IAsyncReader called CAsyncReader. The sample reads a media file into a memory buffer then uses CAsyncReader to stream the data into the graph. This may be what you want. On my machine the sample is located in the folder C:\Program Files\Microsoft SDKs\Windows\v7.0\Samples\multimedia\directshow\filters\async.
You can control an audio channel (in order to make it play anything, including MP3) with mciSendString http://msdn.microsoft.com/en-us/library/ms709492%28VS.85%29.aspx
Here's an example (it's in C#, but it's basically the same principle):
http://social.msdn.microsoft.com/forums/en-US/Vsexpressvcs/thread/152f0149-a62a-446d-a205-91256da7845d
Here is the same principle in C:
http://www.daniweb.com/software-development/c/code/268167