I've been attempting to implement IAMGraphBuilderCallback interface
I would imagine that the callback gets called everytime a candidate filter is found through intelligent-connect, but the call back in my impelmentation only gets called once during graph construction regardless of if I accept or reject the filter, which I'm not sure why.
I've used grapheditNext.exe to view the default setup when constructing the graph, which uses both the Microsoft DTV Video and Audio decoder.
(Graph without callBack)
I've implemented the Select Filter Method to reject both of the filter.
(Graph with callBack)
Lav Video filter was inserted when viewed using grapheditNext remote graph and the select filter callback never gets called after. This behaviour brings me to conclude two problems with my current implementation.
even though multiple filters are inserted by intelligent connect (DTV Video and DTV Audio), the callback is only called once.
even when you reject the filter in a callback, the callback is not called for other filters that are proposed/inserted.
Update:
I've implemented manual connection method to Connect two filter. I've inserted a source filter, mp4 demux filter, video renderer and audio renderer. The connection sequence and its behavior is as follows:
Source Filter -> mp4 Demux (Direct Connect)
mp4 Demux -> Video Renderer
Callback happened once. Microsoft DTV video decoder was rejected with the callback. Lav filter was inserted after without triggering the callback.
mp4 Demux -> Audio Renderer
Microsoft DTV audio decoder was inserted without the callback.
The little test app I've wrote is as follows:
#include <iostream>
#include <dshow.h>
#include <Streams.h>
#include <Combase.h>
#include <vector>
#include <algorithm>
void insertCallBackHandle(IGraphBuilder* pGraph, IAMGraphBuilderCallback* pGraphBuilderCallBackWithExcluList);
class GraphBuilderCallBackWithExcluList : public IAMGraphBuilderCallback, public CUnknown
{
public:
DECLARE_IUNKNOWN
GraphBuilderCallBackWithExcluList()
: CUnknown(NAME("GraphBuilderCallback"), NULL)
{
}
virtual HRESULT STDMETHODCALLTYPE SelectedFilter(
/* [in] */ IMoniker *pMon);
{
GUID DTVVideo = { 0x212690FB, 0x83E5, 0x4526,{ 0x8F, 0xD7, 0x74, 0x47, 0x8B, 0x79, 0x39, 0xCD } };
GUID DTVAudio = { 0xE1F1A0B8, 0xBEEE, 0x490D,{ 0xBA, 0x7C, 0x06, 0x6C, 0x40, 0xB5, 0xE2, 0xB9 } };
std::vector<GUID> m_vFilterGuid;
m_vFilterGuid.push_back(DTVVideo);
m_vFilterGuid.push_back(DTVAudio);
IPropertyBag *pPropBag;
HRESULT hr = pMon->BindToStorage(0, 0, IID_IPropertyBag,
(void **)&pPropBag);
if (SUCCEEDED(hr))
{
VARIANT varName;
VariantInit(&varName);
hr = pPropBag->Read(L"CLSID", &varName, 0);
if (SUCCEEDED(hr))
{
LPCWSTR name = varName.bstrVal ? varName.bstrVal : L"";
GUID classIdOfFilter;;
HRESULT hr = CLSIDFromString(name,(LPCLSID)&classIdOfFilter);
if (SUCCEEDED(hr))
{
auto it = std::find_if(m_vFilterGuid.begin(), m_vFilterGuid.end(), [&classIdOfFilter](const GUID excluFilter) {return excluFilter == classIdOfFilter; });
if (it == m_vFilterGuid.end())
{
VariantClear(&varName);
pPropBag->Release();
return S_OK;
}
else
{
VariantClear(&varName);
pPropBag->Release();
return E_FAIL;
}
}
else
{
VariantClear(&varName);
pPropBag->Release();
return E_FAIL;
}
}
else
{
VariantClear(&varName);
pPropBag->Release();
return E_FAIL;
}
}
else
{
pPropBag->Release();
return E_FAIL;
}
}
virtual HRESULT STDMETHODCALLTYPE CreatedFilter(
/* [in] */ IBaseFilter *pFil)
{
return S_OK;
}
STDMETHOD(NonDelegatingQueryInterface)(REFIID iid, void** ppv)
{
if (iid == __uuidof(IAMGraphBuilderCallback))
return GetInterface((IAMGraphBuilderCallback*)this, ppv);
return CUnknown::NonDelegatingQueryInterface(iid, ppv);
}
};
int main()
{
HRESULT hr = CoInitialize(NULL);
if (FAILED(hr))
{
std::cout << "Failed to initialise directshow";
}
GraphBuilderCallBackWithExcluList* pGraphBuilderCallBackWithExcluList = new GraphBuilderCallBackWithExcluList;
IGraphBuilder *pGraph;
hr = CoCreateInstance(CLSID_FilterGraph, NULL,
CLSCTX_INPROC_SERVER, IID_IGraphBuilder, (void **)&pGraph);
if (FAILED(hr))
{
std::cout << "Failed to create filter graph instance";
}
IMediaControl *pControl;
IMediaEvent *pEvent;
hr = pGraph->QueryInterface(IID_IMediaControl, (void **)&pControl);
if (FAILED(hr))
{
std::cout << "Failed to query interface media control";
}
hr = pGraph->QueryInterface(IID_IMediaEvent, (void **)&pEvent);
if (FAILED(hr))
{
std::cout << "Failed to query interface media event";
}
insertCallBackHandle(pGraph, pGraphBuilderCallBackWithExcluList);
IBaseFilter * pFilter0 = NULL;
GUID fileSource = { 0xE436EBB5 ,0x524F ,0x11CE, { 0x9F,0x53, 0x00,0x20,0xAF, 0x0B, 0xA7, 0x70} };
hr = CoCreateInstance(fileSource, NULL, CLSCTX_INPROC_SERVER,
IID_PPV_ARGS(&pFilter0));
if (FAILED(hr))
{
std::cout << "Failed to create mp4Demux filter";
}
IFileSourceFilter * pFilterSourceFitler;
hr = pFilter0->QueryInterface(&pFilterSourceFitler);
if (FAILED(hr))
{
std::cout << "Failed to query interface IFileSourceFilter";
}
LPCOLESTR FileSourceUri = L"D:\\Test.mp4";
hr = pFilterSourceFitler->Load(FileSourceUri, NULL);
if (FAILED(hr))
{
std::cout << "Failed to load fileSource uri";
}
IBaseFilter * pFilter1 = NULL;
GUID mp4Demux = { 0x025BE2E4 , 0x1787 , 0x4DA4,{ 0xA5, 0x85, 0xC5,0xB2,0xB9,0xEE,0xB5,0x7C } };
hr = CoCreateInstance(mp4Demux, NULL, CLSCTX_INPROC_SERVER,
IID_PPV_ARGS(&pFilter1));
if (FAILED(hr))
{
std::cout << "Failed to create mp4Demux filter";
}
IBaseFilter * pFilter2 = NULL;
GUID videoRenderer = { 0xB87BEB7B , 0x8D29 , 0x423F, { 0xAE, 0x4D, 0x65,0x82,0xC1,0x01,0x75,0xAC }};
hr = CoCreateInstance(videoRenderer, NULL, CLSCTX_INPROC_SERVER,
IID_PPV_ARGS(&pFilter2));
if (FAILED(hr))
{
std::cout << "Failed to create videoRenderer filter";
}
IBaseFilter * pFilter3 = NULL;
GUID audioRenderer = { 0x79376820 , 0x07D0 ,0x11CF, {0xA2,0x4D,0x00,0x20,0xAF,0xD7,0x97,0x67} };
hr = CoCreateInstance(audioRenderer, NULL, CLSCTX_INPROC_SERVER,
IID_PPV_ARGS(&pFilter3));
if (FAILED(hr))
{
std::cout << "Failed to create audioRenderer filter";
}
hr = pGraph->AddFilter(pFilter0, NULL);
if (FAILED(hr))
{
std::cout << "Failed to add mp4Demux filter";
}
hr = pGraph->AddFilter(pFilter1, NULL);
if (FAILED(hr))
{
std::cout << "Failed to add mp4Demux filter";
}
hr = pGraph->AddFilter(pFilter2, NULL);
if (FAILED(hr))
{
std::cout << "Failed to add videoRenderer filter";
}
hr = pGraph->AddFilter(pFilter3, NULL);
if (FAILED(hr))
{
std::cout << "Failed to add audioRenderer filter";
}
// Auto connect
// Connect FileSource to mp4Demux
hr = ConnectFilters(pGraph, pFilter0, pFilter1);
if (FAILED(hr))
{
std::cout << "Failed to connect FilterSource to mp4Demux" << hr;
}
// Connect mp4Demux to Video Renderer
hr = ConnectFilters(pGraph, pFilter1, pFilter2);
if (FAILED(hr))
{
std::cout << "Failed to connect mp4Demux to videoRenderer";
}
// Connect mp4Demux to Audio Renderer
hr = ConnectFilters(pGraph, pFilter1, pFilter3);
if (FAILED(hr))
{
std::cout << "Failed to connect mp4Demux to audioRenderer";
}
hr = pControl->Run();
if (FAILED(hr))
{
std::cout << "Failed to run graph";
}
long evCode = 0;
pEvent->WaitForCompletion(INFINITE, &evCode);
pControl->Release();
pEvent->Release();
pGraph->Release();
CoUninitialize();
return 0;
}
void insertCallBackHandle(IGraphBuilder* pGraph,IAMGraphBuilderCallback* pGraphBuilderCallBackWithExcluList)
{
IObjectWithSite* iObjectWithSite;
HRESULT hr = pGraph->QueryInterface(&iObjectWithSite);
if (FAILED(hr))
{
std::cout << "Failed to query interface iObjectWithSite";
}
hr = iObjectWithSite->SetSite(pGraphBuilderCallBackWithExcluList);
if (FAILED(hr))
{
std::cout << "Failed to set site";
}
if (iObjectWithSite)
{
iObjectWithSite->Release();
}
}
So i missed the crucial point in COM implementation on reference count. The problem with the above implementation is that the reference count of the IAMGraphBuilderCallback Object is always zero, so the callback was called once and possibly destroyed after by directshow because of 0 reference count and never gets called again. With the above implementation, the work around I came up with is to manually increment the reference count when creating the object
GraphBuilderCallBackWithExcluList* pGraphBuilderCallBackWithExcluList = new GraphBuilderCallBackWithExcluList;
pGraphBuilderCallBackWithExcluList->NonDelegatingAddRef()
and calling
pGraphBuilderCallBackWithExcluList->NonDelegatingRelease()
Alternatively I found the code to fake the reference count with the following implementation:
class GraphBuilderCallBackWithExcluList : public IAMGraphBuilderCallback
{
public:
GraphBuilderCallBackWithExcluList()
{
}
virtual HRESULT STDMETHODCALLTYPE SelectedFilter(
/* [in] */ IMoniker *pMon)
{
GUID DTVVideo = { 0x212690FB, 0x83E5, 0x4526,{ 0x8F, 0xD7, 0x74, 0x47, 0x8B, 0x79, 0x39, 0xCD } };
GUID DTVAudio = { 0xE1F1A0B8, 0xBEEE, 0x490D,{ 0xBA, 0x7C, 0x06, 0x6C, 0x40, 0xB5, 0xE2, 0xB9 } };
std::vector<GUID> m_vFilterGuid;
m_vFilterGuid.push_back(DTVVideo);
m_vFilterGuid.push_back(DTVAudio);
IPropertyBag *pPropBag;
HRESULT hr = pMon->BindToStorage(0, 0, IID_IPropertyBag,
(void **)&pPropBag);
if (SUCCEEDED(hr))
{
VARIANT varName;
VariantInit(&varName);
hr = pPropBag->Read(L"CLSID", &varName, 0);
if (SUCCEEDED(hr))
{
LPCWSTR name = varName.bstrVal ? varName.bstrVal : L"";
GUID classIdOfFilter;;
HRESULT hr = CLSIDFromString(name, (LPCLSID)&classIdOfFilter);
if (SUCCEEDED(hr))
{
auto it = std::find_if(m_vFilterGuid.begin(), m_vFilterGuid.end(), [&classIdOfFilter](const GUID excluFilter) {return excluFilter == classIdOfFilter; });
if (it == m_vFilterGuid.end())
{
VariantClear(&varName);
pPropBag->Release();
return S_OK;
}
else
{
VariantClear(&varName);
pPropBag->Release();
return E_FAIL;
}
}
else
{
VariantClear(&varName);
pPropBag->Release();
return E_FAIL;
}
}
else
{
VariantClear(&varName);
pPropBag->Release();
return E_FAIL;
}
}
else
{
pPropBag->Release();
return E_FAIL;
}
}
virtual HRESULT STDMETHODCALLTYPE CreatedFilter(
/* [in] */ IBaseFilter *pFil)
{
return S_OK;
}
/**
* #brief Fake out any COM ref counting
*/
STDMETHODIMP_(ULONG) AddRef() { return 2; }
/**
* #brief Fake out any COM ref counting
*/
STDMETHODIMP_(ULONG) Release() { return 1; }
/**
* #brief Fake out any COM ref QI'ing
*/
STDMETHODIMP QueryInterface(REFIID riid, void ** ppv)
{
CheckPointer(ppv, E_POINTER);
if (riid == IID_IAMGraphBuilderCallback || riid == IID_IUnknown)
{
*ppv = (void *) static_cast<IAMGraphBuilderCallback*> (this);
return NOERROR;
}
return E_NOINTERFACE;
}
};
Related
I am having a big issue getting raw audio data from the SampleGrabber GraphFilter and writing it to an audio file in the proper format.
What i'm trying to achieve is:
grabbing the data from: Microphone->SampleGrabber->Null Rendrer
Write the raw data captured to a proper audio file (MP3\AVI\WAVE)
Some questions:
Am I setting the proper MediaType for the SampleGrabber?
How can you compress the audio data to a proper audio file?
My current code below:
struct sRecortdingData {
std::vector<BYTE> recordingData;
double totalRecordingTime;
};
class CFakeCallback : public ISampleGrabberCB
{
public:
sRecortdingData sRecording;
STDMETHODIMP_(ULONG) AddRef() { return 2; }
STDMETHODIMP_(ULONG) Release() { return 1; }
STDMETHODIMP_(HRESULT __stdcall) QueryInterface(REFIID riid, void** ppv)
{
if (riid == IID_ISampleGrabberCB || riid == IID_IUnknown)
{
*ppv = (void*) static_cast<ISampleGrabberCB*>(this);
return NOERROR;
}
return E_NOINTERFACE;
}
STDMETHODIMP_(HRESULT __stdcall) SampleCB(double SampleTime, IMediaSample* pSample)
{
return S_OK;
}
STDMETHODIMP_(HRESULT __stdcall) BufferCB(double SampleTime, BYTE* pBuffer, long BufferLen)
{
sRecording.recordingData.push_back(*pBuffer);
sRecording.totalRecordingTime = SampleTime;
return S_OK;
}
};
void Microphone::RecordAudio()
{
HRESULT hr;
AM_MEDIA_TYPE mt;
long recordingEventCode;
IMediaControl* pMediaControl = NULL;
ISampleGrabber* pISampleGrabber = NULL;
IBaseFilter* pSampleGrabberFilter;
IGraphBuilder* pGraphBuilder = NULL;
IMediaEventEx* pEvent = NULL;
IBaseFilter* pMicrophoneFilter = NULL;
ISampleGrabber* pSampleGrabber = NULL;
ICreateDevEnum* pDeviceEnum = NULL;
IBaseFilter* pNullRender = NULL;
hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
if (FAILED(hr))
{
HR_Failed(hr);
return;
}
// Filter graph
hr = CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pGraphBuilder));
if (FAILED(hr))
{
HR_Failed(hr);
return;
}
// Device enum
hr = CoCreateInstance(CLSID_SystemDeviceEnum, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pDeviceEnum));
if (FAILED(hr))
{
HR_Failed(hr);
return;
}
// Null renderer
hr = CoCreateInstance(CLSID_NullRenderer, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pNullRender));
if (FAILED(hr))
{
HR_Failed(hr);
return;
}
// Get the even control
hr = pGraphBuilder->QueryInterface(IID_PPV_ARGS(&pEvent));
if (FAILED(hr))
{
HR_Failed(hr);
return;
}
// Sample Grabber
hr = CoCreateInstance(CLSID_SampleGrabber, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pSampleGrabberFilter));
if (FAILED(hr))
{
HR_Failed(hr);
return;
}
// Get Media Control
hr = pGraphBuilder->QueryInterface(IID_PPV_ARGS(&pMediaControl));
if (FAILED(hr))
{
HR_Failed(hr);
return;
}
// Setup input device filter
pMicrophoneFilter = Microphone::SetupDeviceFilter(CLSID_AudioInputDeviceCategory, pGraphBuilder, pDeviceEnum,
Utils::s2ws("My Microphone");
if (pMicrophoneFilter == NULL) {
HR_Failed(hr);
return;
}
// Setup sample grabber filter
pSampleGrabberFilter = Microphone::SetupDeviceFilter(CLSID_LegacyAmFilterCategory,
pGraphBuilder, pDeviceEnum, L"SampleGrabber");
if (pSampleGrabberFilter == NULL) {
HR_Failed(hr);
return;
}
// Connect both pins together
Device_Connect(pMicrophoneFilter, pSampleGrabberFilter);
// Setup null renderer filter
pNullRender = Microphone::SetupDeviceFilter(CLSID_LegacyAmFilterCategory,
pGraphBuilder, pDeviceEnum, L"Null Renderer");
if (pNullRender == NULL) {
HR_Failed(hr);
return;
}
// Connect both pins together
Device_Connect(pSampleGrabberFilter, pNullRender);
// Get the ISampleGranner interface
hr = pSampleGrabberFilter->QueryInterface(IID_ISampleGrabber, (LPVOID*)&pISampleGrabber);
if (FAILED(hr))
{
HR_Failed(hr);
return;
}
hr = pISampleGrabber->SetCallback(&CB, 1);
ZeroMemory(&mt, sizeof(mt));
mt.majortype = MEDIATYPE_Audio;
mt.subtype = MEDIASUBTYPE_PCM;
// Set the media type
hr = pISampleGrabber->SetMediaType(&mt);
if (FAILED(hr))
{
HR_Failed(hr);
return;
}
pISampleGrabber->SetBufferSamples(FALSE);
if (FAILED(hr))
{
HR_Failed(hr);
return;
}
hr = pMediaControl->Run();
if (FAILED(hr))
{
HR_Failed(hr);
return;
}
DWORD recordingTime = 20000;
hr = pEvent->WaitForCompletion(recordingTime, &recordingEventCode);
std::string str(CB.sRecording.recordingData.begin(), CB.sRecording.recordingData.end());
// Do something with the data from callback and write it to audio file
}
I have created a sample based on this MS link:
https://learn.microsoft.com/en-us/windows/win32/directshow/using-the-sample-grabber
Code appears below.
But the code only grabs one frame. I think the very first frame in the video But I want to grab every frame available in the video file. How do I do that?
/*
for this sample to work you have to have
"C:\\temp\\FlickAnimation.avi"
and it will output the first frame to grab1.bmp
work out how to do all frames.
qedit.h from here:
https://social.msdn.microsoft.com/Forums/windowsdesktop/en-US/2ab5c212-5824-419d-b5d9-7f5db82f57cd/qedith-missing-in-current-windows-sdk-v70?forum=windowsdirectshowdevelopment
dshowutil.h from here:
https://raw.githubusercontent.com/microsoft/Windows-classic-samples/master/Samples/Win7Samples/multimedia/directshow/common/dshowutil.h
*/
#include <windows.h>
#include <dshow.h> // DirectShow main header
#include "qedit.h" // from Microsoft
#include <strmif.h> // for IMediaSample
#include <combaseapi.h> // IID_PPV_ARGS
#include "dshowutil.h" // from Microsoft
#pragma comment(lib, "strmiids.lib")
#pragma comment(lib, "dxguid.lib")
template <class T> void SafeRelease(T **ppT)
{
if (*ppT)
{
(*ppT)->Release();
*ppT = NULL;
}
}
HRESULT WriteBitmap(PCWSTR, BITMAPINFOHEADER*, size_t, BYTE*, size_t);
HRESULT GrabVideoBitmap(PCWSTR pszVideoFile, PCWSTR pszBitmapFile)
{
IGraphBuilder *pGraph = NULL;
IMediaControl *pControl = NULL;
IMediaEventEx *pEvent = NULL;
IBaseFilter *pGrabberF = NULL;
ISampleGrabber *pGrabber = NULL;
IBaseFilter *pSourceF = NULL;
IEnumPins *pEnum = NULL;
IPin *pPin = NULL;
IBaseFilter *pNullF = NULL;
BYTE *pBuffer = NULL;
HRESULT hr = CoCreateInstance(CLSID_FilterGraph, NULL,
CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pGraph));
if (FAILED(hr))
{
goto done;
}
hr = pGraph->QueryInterface(IID_PPV_ARGS(&pControl));
if (FAILED(hr))
{
goto done;
}
hr = pGraph->QueryInterface(IID_PPV_ARGS(&pEvent));
if (FAILED(hr))
{
goto done;
}
// Create the Sample Grabber filter.
hr = CoCreateInstance(CLSID_SampleGrabber, NULL, CLSCTX_INPROC_SERVER,
IID_PPV_ARGS(&pGrabberF));
if (FAILED(hr))
{
goto done;
}
hr = pGraph->AddFilter(pGrabberF, L"Sample Grabber");
if (FAILED(hr))
{
goto done;
}
hr = pGrabberF->QueryInterface(IID_PPV_ARGS(&pGrabber));
if (FAILED(hr))
{
goto done;
}
AM_MEDIA_TYPE mt;
ZeroMemory(&mt, sizeof(mt));
mt.majortype = MEDIATYPE_Video;
mt.subtype = MEDIASUBTYPE_RGB24;
hr = pGrabber->SetMediaType(&mt);
if (FAILED(hr))
{
goto done;
}
hr = pGraph->AddSourceFilter(pszVideoFile, L"Source", &pSourceF);
if (FAILED(hr))
{
goto done;
}
hr = pSourceF->EnumPins(&pEnum);
if (FAILED(hr))
{
goto done;
}
while (S_OK == pEnum->Next(1, &pPin, NULL))
{
hr = ConnectFilters(pGraph, pPin, pGrabberF);
SafeRelease(&pPin);
if (SUCCEEDED(hr))
{
break;
}
}
if (FAILED(hr))
{
goto done;
}
hr = CoCreateInstance(CLSID_NullRenderer, NULL, CLSCTX_INPROC_SERVER,
IID_PPV_ARGS(&pNullF));
if (FAILED(hr))
{
goto done;
}
hr = pGraph->AddFilter(pNullF, L"Null Filter");
if (FAILED(hr))
{
goto done;
}
hr = ConnectFilters(pGraph, pGrabberF, pNullF);
if (FAILED(hr))
{
goto done;
}
hr = pGrabber->SetOneShot(FALSE); // TRUE);
if (FAILED(hr))
{
goto done;
}
hr = pGrabber->SetBufferSamples(TRUE);
if (FAILED(hr))
{
goto done;
}
hr = pControl->Run();
if (FAILED(hr))
{
goto done;
}
long evCode;
hr = pEvent->WaitForCompletion(INFINITE, &evCode);
// Find the required buffer size.
long cbBuffer;
hr = pGrabber->GetCurrentBuffer(&cbBuffer, NULL);
if (FAILED(hr))
{
goto done;
}
pBuffer = (BYTE*)CoTaskMemAlloc(cbBuffer);
if (!pBuffer)
{
hr = E_OUTOFMEMORY;
goto done;
}
hr = pGrabber->GetCurrentBuffer(&cbBuffer, (long*)pBuffer);
if (FAILED(hr))
{
goto done;
}
hr = pGrabber->GetConnectedMediaType(&mt);
if (FAILED(hr))
{
goto done;
}
// Examine the format block.
if ((mt.formattype == FORMAT_VideoInfo) &&
(mt.cbFormat >= sizeof(VIDEOINFOHEADER)) &&
(mt.pbFormat != NULL))
{
VIDEOINFOHEADER *pVih = (VIDEOINFOHEADER*)mt.pbFormat;
hr = WriteBitmap(pszBitmapFile, &pVih->bmiHeader,
mt.cbFormat - SIZE_PREHEADER, pBuffer, cbBuffer);
}
else
{
// Invalid format.
hr = VFW_E_INVALIDMEDIATYPE;
}
FreeMediaType(mt);
done:
CoTaskMemFree(pBuffer);
SafeRelease(&pPin);
SafeRelease(&pEnum);
SafeRelease(&pNullF);
SafeRelease(&pSourceF);
SafeRelease(&pGrabber);
SafeRelease(&pGrabberF);
SafeRelease(&pControl);
SafeRelease(&pEvent);
SafeRelease(&pGraph);
return hr;
};
// Writes a bitmap file
// pszFileName: Output file name.
// pBMI: Bitmap format information (including pallete).
// cbBMI: Size of the BITMAPINFOHEADER, including palette, if present.
// pData: Pointer to the bitmap bits.
// cbData Size of the bitmap, in bytes.
HRESULT WriteBitmap(PCWSTR pszFileName, BITMAPINFOHEADER *pBMI, size_t cbBMI,
BYTE *pData, size_t cbData)
{
HANDLE hFile = CreateFile(pszFileName, GENERIC_WRITE, 0, NULL,
CREATE_ALWAYS, 0, NULL);
if (hFile == NULL)
{
return HRESULT_FROM_WIN32(GetLastError());
}
BITMAPFILEHEADER bmf = {};
bmf.bfType = 0x4d42; // 0x42 = "B" 0x4d = "M"
bmf.bfSize = cbBMI + cbData + sizeof(bmf);
bmf.bfOffBits = sizeof(bmf) + cbBMI;
DWORD cbWritten = 0;
BOOL result = WriteFile(hFile, &bmf, sizeof(bmf), &cbWritten, NULL);
if (result)
{
result = WriteFile(hFile, pBMI, cbBMI, &cbWritten, NULL);
}
if (result)
{
result = WriteFile(hFile, pData, cbData, &cbWritten, NULL);
}
HRESULT hr = result ? S_OK : HRESULT_FROM_WIN32(GetLastError());
CloseHandle(hFile);
return hr;
}
int main() {
// Initialize the COM library.
HRESULT hr = CoInitialize(NULL);
if (FAILED(hr))
{
printf("ERROR - Could not initialize COM library");
return 1;
}
GrabVideoBitmap(L"C:\\temp\\FlickAnimation.avi", L"grab1.bmp");
CoUninitialize();
}
Idea behind solution:
Try opening the video with OpenCV, then use it's helper functions to read it frame-by-frame, saving the frame to images.
Code sample:
#include "opencv2/opencv.hpp"
#include <iostream>
using namespace std;
using namespace cv;
int main(){
// Create a VideoCapture object and open the input file
// If the input is the web camera, pass 0 instead of the video file name
VideoCapture cap("chaplin.mp4");
// Check if camera opened successfully
if(!cap.isOpened()){
cout << "Error opening video stream or file" << endl;
return -1;
}
int counter = 0;
while(1){
Mat frame;
// Capture frame-by-frame
cap >> frame;
// If the frame is empty, break immediately
if (frame.empty())
break;
// Display the resulting frame
//imshow( "Frame", frame );
//Save the resulting frame
imwrite( "FilePathAndName" + std::to_string( counter ), frame );
counter++;
// Press ESC on keyboard to exit
char c=(char)waitKey(25);
if(c==27)
break;
}
// When everything done, release the video capture object
cap.release();
// Closes all the frames
destroyAllWindows();
return 0;
}
Hope this helps! ;)
I'm using Window's Core Audio API (https://learn.microsoft.com/en-us/windows/desktop/CoreAudio/core-audio-apis-in-windows-vista) for controlling audio settings for my audio processing/analysis application. Below is my modified version of WalkTreeBackwardsFromPart. I can control the sidetone setting from the API, but I don't know what it actually does. If it does what I think it does, it could be useful to me. It does not seem to do the exact same thing as "listen to this device" setting. See line 204 for where I turn off the setting. Thanks.
#include "stdafx.h"
#include <windows.h>
#include <stdio.h>
#include <mmdeviceapi.h>
#include <devicetopology.h>
#include <vector>
using namespace std;
struct AudioNode
{
LPWSTR name;
AudioNode *parent;
vector<AudioNode *>children;
};
AudioNode *newAudioNode(LPWSTR name) {
AudioNode *temp = new AudioNode;
temp->name = name;
temp->parent = NULL;
return temp;
}
AudioNode *audioTreeHead;
//AudioNode *audioTreeCurrNode;
HRESULT WalkTreeBackwardsFromPart(IPart *pPart, EDataFlow deviceType, AudioNode *audioTreeCurrNode, int iTabLevel = 0);
//HRESULT DisplayVolume(IAudioVolumeLevel *pVolume, int iTabLevel);
HRESULT DisplayMute(IAudioMute *pMute, int iTabLevel);
int SetDeviceVolume(EDataFlow deviceType, float vol);
void Tab(int iTabLevel);
int __cdecl main(void) {
//float volumes[] = {0, 5, 10, 15}
printf("capture\n");
int result = SetDeviceVolume(eCapture, -20);
printf("render\n");
result = SetDeviceVolume(eRender, -20);
system("pause");
return 0;
}
//HRESULT DisplayVolume(IAudioVolumeLevel *pVolume, int iTabLevel) {
//
//}
int SetDeviceVolume(EDataFlow deviceType, float vol) {
audioTreeHead = newAudioNode(L"");
AudioNode *audioTreeCurrNode = audioTreeHead;
HRESULT hr = CoInitialize(NULL);
if (FAILED(hr)) {
printf("Failed CoInitializeEx: hr = 0x%08x\n", hr);
return __LINE__;
}
// get default render endpoint
IMMDeviceEnumerator *pEnum = NULL;
hr = CoCreateInstance(
__uuidof(MMDeviceEnumerator), NULL, CLSCTX_ALL, __uuidof(IMMDeviceEnumerator),
(void**)&pEnum
);
if (FAILED(hr)) {
printf("Couldn't get device enumerator: hr = 0x%08x\n", hr);
CoUninitialize();
return __LINE__;
}
IMMDevice *pDevice = NULL;
hr = pEnum->GetDefaultAudioEndpoint(deviceType, eConsole, &pDevice);
if (FAILED(hr)) {
printf("Couldn't get default capture device: hr = 0x%08x\n", hr);
pEnum->Release();
CoUninitialize();
return __LINE__;
}
pEnum->Release();
// get device topology object for that endpoint
IDeviceTopology *pDT = NULL;
hr = pDevice->Activate(__uuidof(IDeviceTopology), CLSCTX_ALL, NULL, (void**)&pDT);
if (FAILED(hr)) {
printf("Couldn't get device topology object: hr = 0x%08x\n", hr);
pDevice->Release();
CoUninitialize();
return __LINE__;
}
pDevice->Release();
// get the single connector for that endpoint
IConnector *pConnEndpoint = NULL;
hr = pDT->GetConnector(0, &pConnEndpoint);
if (FAILED(hr)) {
printf("Couldn't get the connector on the endpoint: hr = 0x%08x\n", hr);
pDT->Release();
CoUninitialize();
return __LINE__;
}
pDT->Release();
// get the connector on the device that is
// connected to
// the connector on the endpoint
IConnector *pConnDevice = NULL;
hr = pConnEndpoint->GetConnectedTo(&pConnDevice);
if (FAILED(hr)) {
printf("Couldn't get the connector on the device: hr = 0x%08x\n", hr);
pConnEndpoint->Release();
CoUninitialize();
return __LINE__;
}
pConnEndpoint->Release();
// QI on the device's connector for IPart
IPart *pPart = NULL;
hr = pConnDevice->QueryInterface(__uuidof(IPart), (void**)&pPart);
if (FAILED(hr)) {
printf("Couldn't get the part: hr = 0x%08x\n", hr);
pConnDevice->Release();
CoUninitialize();
return __LINE__;
}
pConnDevice->Release();
// all the real work is done in this function
hr = WalkTreeBackwardsFromPart(pPart, deviceType, audioTreeCurrNode);
if (FAILED(hr)) {
printf("Couldn't walk the tree: hr = 0x%08x\n", hr);
pPart->Release();
CoUninitialize();
return __LINE__;
}
pPart->Release();
CoUninitialize();
return 0;
}
HRESULT WalkTreeBackwardsFromPart(IPart *pPart, EDataFlow deviceType, AudioNode *audioTreeCurrNode, int iTabLevel /* = 0 */) {
HRESULT hr = S_OK;
LPWSTR pwszPartName = NULL;
hr = pPart->GetName(&pwszPartName);
if (FAILED(hr)) {
Tab(iTabLevel);
printf("Could not get part name: hr = 0x%08x\n", hr);
return hr;
}
Tab(iTabLevel);
printf("Part name: %ws\n", *pwszPartName ? pwszPartName : L"(Unnamed)");
AudioNode * tempNode = newAudioNode(pwszPartName);
//CoTaskMemFree(pwszPartName);
tempNode->parent = audioTreeCurrNode;
audioTreeCurrNode->children.push_back(tempNode);
audioTreeCurrNode = tempNode;
audioTreeCurrNode->name = pwszPartName;
if (audioTreeCurrNode->parent != NULL) {
Tab(iTabLevel);
printf("my parent: %ws\n", audioTreeCurrNode->parent->name);
if (audioTreeCurrNode->parent->parent != NULL) {
Tab(iTabLevel);
printf("my parent of parent: %ws\n", audioTreeCurrNode->parent->parent->name);
}
}
else {
Tab(iTabLevel);
printf("parent is NULL!\n");
}
if (wcscmp(audioTreeCurrNode->name, L"Mute") == 0 &&
audioTreeCurrNode->parent != NULL && audioTreeCurrNode->parent->parent != NULL &&
wcscmp(audioTreeCurrNode->parent->name, L"Volume") == 0 &&
wcscmp(audioTreeCurrNode->parent->parent->name, L"SuperMix") == 0) {
Tab(iTabLevel);
printf("found the mute I want!\n");
Tab(iTabLevel);
printf("parent: %ws\n", audioTreeCurrNode->parent->name);
Tab(iTabLevel);
printf("parent of parent: %ws\n", audioTreeCurrNode->parent->parent->name);
Tab(iTabLevel);
printf("muting passthrough...\n");
const IID IID_IAudioMute = __uuidof(IAudioMute);
IAudioMute *muteControl = NULL;
hr = pPart->Activate(CLSCTX_ALL, IID_IAudioMute, (void**)&muteControl);
if (E_NOINTERFACE == hr) {
Tab(iTabLevel);
printf("NO MUTE CONTROL\n");
}
else if (FAILED(hr)) {
Tab(iTabLevel);
printf("Unexpected failure trying to activate IAudioMute : hr = 0x%08x\n", hr);
return hr;
}
else {
Tab(iTabLevel);
printf("HAS MUTE CONTROL\n");
//LPCGUID pguidEventContext;
BOOL muted;
muteControl->SetMute(TRUE, NULL);
muteControl->GetMute(&muted);
Tab(iTabLevel);
printf("%s\n", muted ? "MUTED" : "NOT MUTED");
}
}
// Check AGC settings
const IID IID_IAudioAutoGainControl = __uuidof(IAudioAutoGainControl);
IAudioAutoGainControl *aGCcontrol = NULL;
hr = pPart->Activate(CLSCTX_ALL, IID_IAudioAutoGainControl, (void**)&aGCcontrol);
if (E_NOINTERFACE == hr) {
Tab(iTabLevel);
printf("NO AGC CONTROL\n");
// not a Microphone node
}
else if (FAILED(hr)) {
Tab(iTabLevel);
printf("Unexpected failure trying to activate IAudioAutoGainControl : hr = 0x%08x\n", hr);
return hr;
}
else {
// it's an AGC node...
Tab(iTabLevel);
printf("HAS AGC CONTROL\n");
aGCcontrol->SetEnabled(0, NULL); //Disable it
if (FAILED(hr)) {
Tab(iTabLevel);
printf("AGC Failed: hr = 0x%08x", hr);
aGCcontrol->Release();
return hr;
}
aGCcontrol->Release();
}
// Check Volume Settings
const IID IID_IAudioVolumeLevel = __uuidof(IAudioVolumeLevel);
IAudioVolumeLevel *volControl = NULL;
hr = pPart->Activate(CLSCTX_ALL, IID_IAudioVolumeLevel, (void**)&volControl);
if (E_NOINTERFACE == hr) {
Tab(iTabLevel);
printf("NO VOLUME CONTROL\n");
// not a volume control
}
else if (FAILED(hr)) {
Tab(iTabLevel);
printf("Unexpected failure trying to activate IAudioVolumeLevel : hr = 0x%08x\n", hr);
return hr;
}
else {
// it's a volume control node
Tab(iTabLevel);
printf("HAS VOLUME CONTROL\n");
UINT numChannels;
float pfLevelDB;
float minLevel;
float maxLevel;
float stepLevel;
volControl->GetChannelCount(&numChannels);
for (int i = 0; i < numChannels; i++) {
volControl->GetLevel(i + 1, &pfLevelDB);
volControl->GetLevelRange(i, &minLevel, &maxLevel, &stepLevel);
Tab(iTabLevel);
printf("Volume Level on %d: %f\n", i, pfLevelDB);
Tab(iTabLevel);
printf("Volume range: %f to %f steps of %f\n", minLevel, maxLevel, stepLevel);
}
}
// get the list of incoming parts
IPartsList *pOutgoingParts = NULL;
if (deviceType == eCapture) {
hr = pPart->EnumPartsOutgoing(&pOutgoingParts);
}
else {
hr = pPart->EnumPartsIncoming(&pOutgoingParts);
}
if (E_NOTFOUND == hr) {
return hr;
// not an error... we've just reached the end of the path
//MessageBox("No incoming parts at this part\n", MB_OK);
}
if (FAILED(hr)) {
return hr;
//MessageBox("Couldn't enum outgoing parts", MB_OK);
}
UINT nParts = 0;
hr = pOutgoingParts->GetCount(&nParts);
if (FAILED(hr)) {
//MessageBox("Couldn't get count of outgoing parts", MB_OK);
pOutgoingParts->Release();
return hr;
}
// walk the tree on each incoming part recursively
for (UINT n = 0; n < nParts; n++) {
IPart *pOutgoingPart = NULL;
hr = pOutgoingParts->GetPart(n, &pOutgoingPart);
if (FAILED(hr)) {
//MessageBox("Couldn't get part ", MB_OK);
pOutgoingParts->Release();
return hr;
}
hr = WalkTreeBackwardsFromPart(pOutgoingPart, deviceType, audioTreeCurrNode, iTabLevel + 1);
if (FAILED(hr)) {
//MessageBox("Couldn't walk tree on part", MB_OK);
pOutgoingPart->Release();
pOutgoingParts->Release();
return hr;
}
pOutgoingPart->Release();
}
pOutgoingParts->Release();
return S_OK;
}
void Tab(int iTabLevel) {
if (0 >= iTabLevel) { return; }
printf("\t");
Tab(iTabLevel - 1);
}
Here is my original post: (What Is This Sidetone Setting on My Playback Device?)
It was flagged for not being on topic because it's not programming related.
When executing the following code, extracted from MSDN, there is some serious memory leaks. I am using mingw 5.3 (same leaks with mingw 4.8) and Windows 7.
Did I forget to call a Release() or another method to prevent the leak ?
#include <dshow.h>
#include <iostream>
#include <mmdeviceapi.h>
#include <uuids.h>
void print_device() {
// Create the System Device Enumerator.
HRESULT hr;
ICreateDevEnum *pSysDevEnum = NULL;
hr = CoCreateInstance(CLSID_SystemDeviceEnum, NULL, CLSCTX_INPROC_SERVER,
IID_ICreateDevEnum, (void **)&pSysDevEnum);
if (FAILED(hr))
{
return;
}
// Obtain a class enumerator for the video compressor category.
IEnumMoniker *pEnumCat = NULL;
hr = pSysDevEnum->CreateClassEnumerator(CLSID_VideoCompressorCategory, &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))
{
std::wcout << varName.bstrVal << '\n';
}
VariantClear(&varName);
}
pMoniker->Release();
}
pEnumCat->Release();
}
pSysDevEnum->Release();
}
int main() {
// Initializes
CoInitialize( NULL );
// loop to test leak
while( true ) {
print_device();
}
}
I'm trying to get notifications whenever the master volume changes on Windows Vista/7. This is the code I'm using:
#include <audiopolicy.h>
#include <audioclient.h>
#include <mmdeviceapi.h>
#include <endpointvolume.h>
#include <windows.h>
#include <shlwapi.h>
#include <iostream>
#include <Tchar.h>
static const GUID AudioSessionVolumeCtx = { 0x2715279f, 0x4139, 0x4ba0, { 0x9c, 0xb1, 0xb3, 0x51, 0xf1, 0xb5, 0x8a, 0x4a } };
template <class T> void SafeRelease(T **ppT)
{
if (*ppT)
{
(*ppT)->Release();
*ppT = NULL;
}
}
class CAudioSessionVolume : public IAudioSessionEvents
{
public:
static HRESULT CreateInstance( UINT uNotificationMessage, HWND hwndNotification, CAudioSessionVolume **ppAudioSessionVolume )
{
CAudioSessionVolume *pAudioSessionVolume = new (std::nothrow)
CAudioSessionVolume(uNotificationMessage, hwndNotification);
if (pAudioSessionVolume == NULL)
{
return E_OUTOFMEMORY;
}
HRESULT hr = pAudioSessionVolume->Initialize();
if (SUCCEEDED(hr))
{
*ppAudioSessionVolume = pAudioSessionVolume;
}
else
{
pAudioSessionVolume->Release();
}
return hr;
}
// IUnknown methods.
STDMETHODIMP QueryInterface(REFIID riid, void **ppv)
{
static const QITAB qit[] =
{
QITABENT(CAudioSessionVolume, IAudioSessionEvents),
{ 0 },
};
return QISearch(this, qit, riid, ppv);
}
STDMETHODIMP_(ULONG) AddRef()
{
return InterlockedIncrement(&m_cRef);
}
STDMETHODIMP_(ULONG) Release()
{
LONG cRef = InterlockedDecrement( &m_cRef );
if (cRef == 0)
{
delete this;
}
return cRef;
}
STDMETHODIMP OnSimpleVolumeChanged( float NewVolume, BOOL NewMute, LPCGUID EventContext )
{
MessageBox(NULL, _T("vol changed"), _T("test"), MB_OK);
return S_OK;
}
// The remaining audio session events do not require any action.
STDMETHODIMP OnDisplayNameChanged(LPCWSTR,LPCGUID)
{
return S_OK;
}
STDMETHODIMP OnIconPathChanged(LPCWSTR,LPCGUID)
{
return S_OK;
}
STDMETHODIMP OnChannelVolumeChanged(DWORD,float[],DWORD,LPCGUID)
{
return S_OK;
}
STDMETHODIMP OnGroupingParamChanged(LPCGUID,LPCGUID)
{
return S_OK;
}
STDMETHODIMP OnStateChanged(AudioSessionState)
{
return S_OK;
}
STDMETHODIMP OnSessionDisconnected(AudioSessionDisconnectReason)
{
return S_OK;
}
// Other methods
HRESULT EnableNotifications(BOOL bEnable)
{
HRESULT hr = S_OK;
if (bEnable)
{
hr = m_pAudioSession->RegisterAudioSessionNotification(this);
}
else
{
hr = m_pAudioSession->UnregisterAudioSessionNotification(this);
}
return hr;
}
HRESULT SetDisplayName(const WCHAR *wszName)
{
if (m_pAudioSession == NULL)
{
return E_FAIL;
}
else
{
return m_pAudioSession->SetDisplayName(wszName, NULL);
}
}
protected:
CAudioSessionVolume( UINT uNotificationMessage, HWND hwndNotification ) :
m_cRef(1), m_pAudioSession(NULL), m_pSimpleAudioVolume(NULL)
{
}
~CAudioSessionVolume()
{
EnableNotifications(FALSE);
SafeRelease(&m_pAudioSession);
SafeRelease(&m_pSimpleAudioVolume);
}
HRESULT Initialize()
{
HRESULT hr = S_OK;
IMMDeviceEnumerator *pDeviceEnumerator = NULL;
IMMDevice *pDevice = NULL;
IAudioSessionManager *pAudioSessionManager = NULL;
// Get the enumerator for the audio endpoint devices.
hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL,
CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pDeviceEnumerator));
if (FAILED(hr))
{
goto done;
}
// Get the default audio endpoint that the SAR will use.
hr = pDeviceEnumerator->GetDefaultAudioEndpoint(eRender, eConsole,
&pDevice);
if (FAILED(hr))
{
goto done;
}
// Get the session manager for this device.
hr = pDevice->Activate(__uuidof(IAudioSessionManager),
CLSCTX_INPROC_SERVER, NULL, (void**) &pAudioSessionManager);
if (FAILED(hr))
{
goto done;
}
// Get the audio session.
hr = pAudioSessionManager->GetAudioSessionControl(
&GUID_NULL, // Get the default audio session.
FALSE, // The session is not cross-process.
&m_pAudioSession);
if (FAILED(hr))
{
goto done;
}
hr = pAudioSessionManager->GetSimpleAudioVolume(&GUID_NULL, 0,
&m_pSimpleAudioVolume);
done:
SafeRelease(&pDeviceEnumerator);
SafeRelease(&pDevice);
SafeRelease(&pAudioSessionManager);
return hr;
}
private:
LONG m_cRef;
IAudioSessionControl *m_pAudioSession;
ISimpleAudioVolume *m_pSimpleAudioVolume;
};
int main()
{
CoInitialize(NULL);
CAudioSessionVolume *asv;
CAudioSessionVolume::CreateInstance(0, NULL, &asv);
asv->EnableNotifications(true);
char s;
gets(&s);
}
I was expecting to get a message box saying "vol changed" when the system volume changes, but I never get a message box.
I got most of the code from http://msdn.microsoft.com/en-us/library/dd374921(VS.85).aspx
What am I doing wrong?
EDIT: I do get notifications when changing the volume for my application (thanks #nobugz). But I want notification when changing the master volume. (Listed as "Device" in Win7's Volume Mixers dialog)
EDIT 2: Larry Osterman has a series of blog posts about the volume in Vista/7. This one in particular is interesting: http://blogs.msdn.com/larryosterman/archive/2007/03/22/fun-with-the-endpoint-volume-interfaces-closing-the-loop.aspx I'll try the code out tomorrow to see if I can get what I want. Now it's time for bed.
EDIT 3: This is the complete code from Larry's blog posts up to and including the link posted above. It does what I need and then some. I'll try to trim the features I don't need.
#include <windows.h>
#include <mmdeviceapi.h>
#include <endpointvolume.h>
#include <Tchar.h>
#include <strsafe.h>
class CVolumeNotification : public IAudioEndpointVolumeCallback
{
LONG m_RefCount;
~CVolumeNotification(void) {};
public:
CVolumeNotification(void) : m_RefCount(1)
{
}
STDMETHODIMP_(ULONG)AddRef() { return InterlockedIncrement(&m_RefCount); }
STDMETHODIMP_(ULONG)Release()
{
LONG ref = InterlockedDecrement(&m_RefCount);
if (ref == 0)
delete this;
return ref;
}
STDMETHODIMP QueryInterface(REFIID IID, void **ReturnValue)
{
if (IID == IID_IUnknown || IID== __uuidof(IAudioEndpointVolumeCallback))
{
*ReturnValue = static_cast<IUnknown*>(this);
AddRef();
return S_OK;
}
*ReturnValue = NULL;
return E_NOINTERFACE;
}
STDMETHODIMP OnNotify(PAUDIO_VOLUME_NOTIFICATION_DATA NotificationData)
{
wchar_t outputString[256];
DWORD written;
COORD writeCoord;
StringCbPrintf(outputString, sizeof(outputString), L"Volume Changed: %f", NotificationData->fMasterVolume);
writeCoord.X = 0;
writeCoord.Y = 3;
WriteConsoleOutputCharacter(GetStdHandle(STD_OUTPUT_HANDLE), outputString, (DWORD)wcslen(outputString), writeCoord, &written);
return S_OK;
}
};
struct TimerContext
{
IAudioMeterInformation *_Meter;
};
const int TimerPeriodicityMS = 100;
void CALLBACK TimerMeterCallback(PTP_CALLBACK_INSTANCE CallbackInstance, PVOID Context, PTP_TIMER Timer)
{
TimerContext *timerContext = (TimerContext *)Context;
wchar_t outputString[256];
float peakValue;
DWORD written;
COORD writeCoord;
StringCbCopy(outputString, sizeof(outputString), L"Meter: ");
timerContext->_Meter->GetPeakValue(&peakValue);
for (size_t i = 0 ; i < peakValue*100; i += 1)
{
StringCbCat(outputString, sizeof(outputString), L"*");
}
for (size_t i = (size_t)(peakValue*100) ; i < 100; i += 1)
{
StringCbCat(outputString, sizeof(outputString), L".");
}
writeCoord.X = 0;
writeCoord.Y = 1;
WriteConsoleOutputCharacter(GetStdHandle(STD_OUTPUT_HANDLE), outputString, (DWORD)wcslen(outputString), writeCoord, &written);
}
int _tmain(int argc, _TCHAR* argv[])
{
CoInitialize(NULL);
HRESULT hr;
IMMDeviceEnumerator *deviceEnumerator = NULL;;
HANDLE handle;
TP_CALLBACK_ENVIRON callbackEnvironment;
PTP_CLEANUP_GROUP cleanupGroup;
PTP_TIMER timer;
TimerContext context;
InitializeThreadpoolEnvironment(&callbackEnvironment);
cleanupGroup = CreateThreadpoolCleanupGroup();
SetThreadpoolCallbackCleanupGroup(&callbackEnvironment, cleanupGroup, NULL);
timer = CreateThreadpoolTimer(TimerMeterCallback, &context, &callbackEnvironment);
//
// Clear the screen. Code stolen from: http://support.microsoft.com/kb/319257.
//
handle = GetStdHandle(STD_OUTPUT_HANDLE);
DWORD writtenChars = 0;
CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
COORD home;
home.X = home.Y = 0;
GetConsoleScreenBufferInfo(handle, &consoleInfo);
FillConsoleOutputCharacter(handle, L' ', consoleInfo.dwSize.X * consoleInfo.dwSize.Y, home, &writtenChars);
SetConsoleCursorPosition(handle, home);
//
// Set the console to raw mode.
//
DWORD oldMode;
GetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), &oldMode);
SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), oldMode & ~(ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT));
//
// Instantiate an endpoint volume object.
//
hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_INPROC_SERVER, __uuidof(IMMDeviceEnumerator), (LPVOID *)&deviceEnumerator);
IMMDevice *defaultDevice = NULL;
hr = deviceEnumerator->GetDefaultAudioEndpoint(eRender, eConsole, &defaultDevice);
deviceEnumerator->Release();
deviceEnumerator = NULL;
IAudioEndpointVolume *endpointVolume = NULL;
hr = defaultDevice->Activate(__uuidof(IAudioEndpointVolume), CLSCTX_INPROC_SERVER, NULL, (LPVOID *)&endpointVolume);
CVolumeNotification *volumeNotification = new CVolumeNotification();
hr = endpointVolume->RegisterControlChangeNotify(volumeNotification);
hr = defaultDevice->Activate(__uuidof(IAudioMeterInformation), CLSCTX_INPROC_SERVER, NULL, (LPVOID *)&context._Meter);
defaultDevice->Release();
defaultDevice = NULL;
// Set a 100 millisecond timer.
LARGE_INTEGER timerPeriodicityLI;
FILETIME timerPeriodicity;
timerPeriodicityLI.QuadPart = -1*(TimerPeriodicityMS* 10000 );
timerPeriodicity.dwLowDateTime = timerPeriodicityLI.LowPart;
timerPeriodicity.dwHighDateTime = timerPeriodicityLI.HighPart;
SetThreadpoolTimer(timer, &timerPeriodicity, TimerPeriodicityMS, 10);
wchar_t inputChar = '\0';
while (inputChar != '\r')
{
UINT currentStep, stepCount;
DWORD read, written;
COORD writeCoord;
wchar_t outputString[256];
StringCbCopy(outputString, sizeof(outputString), L"Volume: ");
//
// Calculate the cheesy OSD.
//
endpointVolume->GetVolumeStepInfo(¤tStep, &stepCount);
for (size_t i = 0 ; i < stepCount ; i += 1)
{
if (i <= currentStep)
{
StringCbCat(outputString, sizeof(outputString), L"=");
}
else
{
StringCbCat(outputString, sizeof(outputString), L"-");
}
}
writeCoord.X = 0;
writeCoord.Y = 0;
WriteConsoleOutputCharacter(handle, outputString, (DWORD)wcslen(outputString), writeCoord, &written);
ReadConsole(GetStdHandle(STD_INPUT_HANDLE), &inputChar, 1, &read, NULL);
if (inputChar == '+')
{
endpointVolume->VolumeStepUp(NULL);
}
else if (inputChar == '-')
{
endpointVolume->VolumeStepDown(NULL);
}
}
//
// Remove our notification.
//
endpointVolume->UnregisterControlChangeNotify(volumeNotification);
endpointVolume->Release();
volumeNotification->Release();
SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), oldMode);
CloseThreadpoolCleanupGroupMembers(cleanupGroup, FALSE, NULL);
CloseThreadpoolCleanupGroup(cleanupGroup);
cleanupGroup = NULL;
DestroyThreadpoolEnvironment(&callbackEnvironment);
context._Meter->Release();
CoUninitialize();
return 0;
}
EDIT 4: This is a stripped down version of Larry's code.
#include <windows.h>
#include <mmdeviceapi.h>
#include <endpointvolume.h>
#include <iostream>
#include <Tchar.h>
class CVolumeNotification : public IAudioEndpointVolumeCallback
{
LONG m_RefCount;
~CVolumeNotification(void) {};
public:
CVolumeNotification(void) : m_RefCount(1)
{
}
STDMETHODIMP_(ULONG)AddRef() { return InterlockedIncrement(&m_RefCount); }
STDMETHODIMP_(ULONG)Release()
{
LONG ref = InterlockedDecrement(&m_RefCount);
if (ref == 0)
delete this;
return ref;
}
STDMETHODIMP QueryInterface(REFIID IID, void **ReturnValue)
{
if (IID == IID_IUnknown || IID== __uuidof(IAudioEndpointVolumeCallback))
{
*ReturnValue = static_cast<IUnknown*>(this);
AddRef();
return S_OK;
}
*ReturnValue = NULL;
return E_NOINTERFACE;
}
STDMETHODIMP OnNotify(PAUDIO_VOLUME_NOTIFICATION_DATA NotificationData)
{
std::cout << NotificationData->fMasterVolume << _T(" ");
return S_OK;
}
};
int _tmain(int argc, _TCHAR* argv[])
{
CoInitialize(NULL);
HRESULT hr;
IMMDeviceEnumerator *deviceEnumerator = NULL;;
//
// Instantiate an endpoint volume object.
//
hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_INPROC_SERVER, __uuidof(IMMDeviceEnumerator), (LPVOID *)&deviceEnumerator);
IMMDevice *defaultDevice = NULL;
hr = deviceEnumerator->GetDefaultAudioEndpoint(eRender, eConsole, &defaultDevice);
deviceEnumerator->Release();
deviceEnumerator = NULL;
IAudioEndpointVolume *endpointVolume = NULL;
hr = defaultDevice->Activate(__uuidof(IAudioEndpointVolume), CLSCTX_INPROC_SERVER, NULL, (LPVOID *)&endpointVolume);
CVolumeNotification *volumeNotification = new CVolumeNotification();
hr = endpointVolume->RegisterControlChangeNotify(volumeNotification);
defaultDevice->Release();
defaultDevice = NULL;
wchar_t inputChar = '\0';
while (inputChar != '\r')
{
DWORD read;
ReadConsole(GetStdHandle(STD_INPUT_HANDLE), &inputChar, 1, &read, NULL);
}
//
// Remove our notification.
//
endpointVolume->UnregisterControlChangeNotify(volumeNotification);
endpointVolume->Release();
volumeNotification->Release();
CoUninitialize();
return 0;
}
I still haven't looked at the SDK example. I now know how this stuff works well enough to add what I need to my application. Thanks for everyones help!
It works fine when I try it. Be sure to click on the volume control tray icon, click Mixer and modify the volume for your app.
The Windows SDK "OSD" sample is a much simpler example of monitoring the hardware volume than the one I posted on the blog (you don't need all the metering junk in that post).