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.
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! ;)
Here's my code:
// Defines an event handler for general UI Automation events. It listens for
// tooltip and window creation and destruction events.
#include <windows.h>
#include <stdio.h>
#include <UIAutomation.h>
class EventHandler :
public IUIAutomationEventHandler
{
private:
LONG _refCount;
public:
int _eventCount;
// Constructor.
EventHandler() : _refCount(1), _eventCount(0)
{
}
// IUnknown methods.
ULONG STDMETHODCALLTYPE AddRef()
{
ULONG ret = InterlockedIncrement(&_refCount);
return ret;
}
ULONG STDMETHODCALLTYPE Release()
{
ULONG ret = InterlockedDecrement(&_refCount);
if (ret == 0)
{
delete this;
return 0;
}
return ret;
}
HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void** ppInterface)
{
if (riid == __uuidof(IUnknown))
*ppInterface = static_cast<IUIAutomationEventHandler*>(this);
else if (riid == __uuidof(IUIAutomationEventHandler))
*ppInterface = static_cast<IUIAutomationEventHandler*>(this);
else
{
*ppInterface = NULL;
return E_NOINTERFACE;
}
this->AddRef();
return S_OK;
}
// IUIAutomationEventHandler methods
HRESULT STDMETHODCALLTYPE HandleAutomationEvent(IUIAutomationElement * pSender, EVENTID eventID)
{
_eventCount++;
switch (eventID)
{
case UIA_AutomationFocusChangedEventId:
wprintf(L">> Event FocusChanged Received! (count: %d)\n", _eventCount);
break;
case UIA_ToolTipOpenedEventId:
wprintf(L">> Event ToolTipOpened Received! (count: %d)\n", _eventCount);
break;
case UIA_ToolTipClosedEventId:
wprintf(L">> Event ToolTipClosed Received! (count: %d)\n", _eventCount);
break;
case UIA_Window_WindowOpenedEventId:
wprintf(L">> Event WindowOpened Received! (count: %d)\n", _eventCount);
break;
case UIA_Window_WindowClosedEventId:
wprintf(L">> Event WindowClosed Received! (count: %d)\n", _eventCount);
break;
default:
wprintf(L">> Event (%d) Received! (count: %d)\n", eventID, _eventCount);
break;
}
return S_OK;
}
};
int main(int argc, char* argv[])
{
HRESULT hr;
int ret = 0;
IUIAutomationElement* pTargetElement = NULL;
EventHandler* pEHTemp = NULL;
CoInitializeEx(NULL, COINIT_MULTITHREADED);
IUIAutomation* pAutomation = NULL;
hr = CoCreateInstance(__uuidof(CUIAutomation), NULL, CLSCTX_INPROC_SERVER, __uuidof(IUIAutomation), (void**)&pAutomation);
if (FAILED(hr) || pAutomation == NULL)
{
ret = 1;
goto cleanup;
}
// Use root element for listening to window and tooltip creation and destruction.
hr = pAutomation->GetRootElement(&pTargetElement);
if (FAILED(hr) || pTargetElement == NULL)
{
ret = 1;
goto cleanup;
}
pEHTemp = new EventHandler();
if (pEHTemp == NULL)
{
ret = 1;
goto cleanup;
}
wprintf(L"-Adding Event Handlers.\n");
hr = pAutomation->AddAutomationEventHandler(UIA_ToolTipOpenedEventId, pTargetElement, TreeScope_Subtree, NULL, (IUIAutomationEventHandler*)pEHTemp);
if (FAILED(hr))
{
ret = 1;
goto cleanup;
}
hr = pAutomation->AddAutomationEventHandler(UIA_ToolTipClosedEventId, pTargetElement, TreeScope_Subtree, NULL, (IUIAutomationEventHandler*)pEHTemp);
if (FAILED(hr))
{
ret = 1;
goto cleanup;
}
hr = pAutomation->AddAutomationEventHandler(UIA_Window_WindowOpenedEventId, pTargetElement, TreeScope_Subtree, NULL, (IUIAutomationEventHandler*)pEHTemp);
if (FAILED(hr))
{
ret = 1;
goto cleanup;
}
hr = pAutomation->AddAutomationEventHandler(UIA_Window_WindowClosedEventId, pTargetElement, TreeScope_Subtree, NULL, (IUIAutomationEventHandler*)pEHTemp);
if (FAILED(hr))
{
ret = 1;
goto cleanup;
}
// Error is here. hr returns E_INVALIDARG.
hr = pAutomation->AddAutomationEventHandler(UIA_AutomationFocusChangedEventId, pTargetElement, TreeScope_Subtree, NULL, (IUIAutomationEventHandler*)pEHTemp);
if (FAILED(hr))
{
ret = 1;
goto cleanup;
}
wprintf(L"-Press any key to remove event handlers and exit\n");
getchar();
wprintf(L"-Removing Event Handlers.\n");
cleanup:
// Remove event handlers, release resources, and terminate
if (pAutomation != NULL)
{
hr = pAutomation->RemoveAllEventHandlers();
if (FAILED(hr))
ret = 1;
pAutomation->Release();
}
if (pEHTemp != NULL)
pEHTemp->Release();
if (pTargetElement != NULL)
pTargetElement->Release();
CoUninitialize();
return ret;
}
This code comes from the examples from Microsoft about implementing Event classes for UIAutomation.
I edited the code a little bit so that it can support Focus events but I failed attempting to initialize one line of code.
I don't understand why hr = pAutomation->AddAutomationEventHandler(UIA_AutomationFocusChangedEventId, pTargetElement, TreeScope_Subtree, NULL, (IUIAutomationEventHandler*)pEHTemp) returns E_INVALIDARG.
Tried reading the docs and I can't find a reason why.
Please help.
There is a separate handler for handling FocusChanged, AddFocusChangedEventHandler which should be used for monitor focus changes. Since you are trying to send an EventHandler function pointer to handle FocusChanged, runtime error is coming.
To create that handler, need an instance of class that inherits from IUIAutomationFocusChangedEventHandler.
class FocusChangedEventHandler :
public IUIAutomationFocusChangedEventHandler
{
private:
LONG _refCount;
public:
int _eventCount;
//Constructor.
FocusChangedEventHandler() : _refCount(1), _eventCount(0)
{
}
//IUnknown methods.
ULONG STDMETHODCALLTYPE AddRef()
{
ULONG ret = InterlockedIncrement(&_refCount);
return ret;
}
ULONG STDMETHODCALLTYPE Release()
{
ULONG ret = InterlockedDecrement(&_refCount);
if (ret == 0)
{
delete this;
return 0;
}
return ret;
}
HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void** ppInterface)
{
if (riid == __uuidof(IUnknown))
*ppInterface = static_cast<IUIAutomationFocusChangedEventHandler*>(this);
else if (riid == __uuidof(IUIAutomationFocusChangedEventHandler))
*ppInterface = static_cast<IUIAutomationFocusChangedEventHandler*>(this);
else
{
*ppInterface = NULL;
return E_NOINTERFACE;
}
this->AddRef();
return S_OK;
}
// IUIAutomationFocusChangedEventHandler methods.
HRESULT STDMETHODCALLTYPE HandleFocusChangedEvent(IUIAutomationElement * pSender)
{
_eventCount++;
wprintf(L">> FocusChangedEvent Received! (count: %d)\n", _eventCount);
return S_OK;
}
};
In main()
pFHTemp = new FocusChangedEventHandler();
hr = pAutomation->AddFocusChangedEventHandler(NULL, (IUIAutomationFocusChangedEventHandler*)pFHTemp);
if (FAILED(hr))
{
ret = 1;
goto cleanup;
}
Code snippets taken from MSDN link
I need to change sample rate for microphone and speakers.
I know how to change volume levels for speakers and microphone using IMMDevice, but how can I set sample rate?
bool SoundDeviceControler::setVolume(EDataFlow dataFlow,float volume )
{
HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
if (FAILED(hr))
return false
IMMDeviceEnumerator *deviceEnumerator = NULL;
hr = CoCreateInstance( __uuidof(MMDeviceEnumerator), NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&deviceEnumerator) );
if (FAILED(hr))
return false;
IMMDeviceCollection *deviceCollection = NULL;
hr = deviceEnumerator->EnumAudioEndpoints( dataFlow, DEVICE_STATE_ACTIVE | DEVICE_STATE_UNPLUGGED, &deviceCollection );
if (FAILED(hr))
return false;
UINT deviceCount;
hr = deviceCollection->GetCount(&deviceCount);
if (FAILED(hr))
return false;
IMMDevice *device = NULL;
for (UINT i = 0 ; i < deviceCount ; i += 1)
{
device = NULL;
hr = deviceCollection->Item(i, &device);
if (FAILED(hr))
return false;
IAudioEndpointVolume *endpointVolume = NULL;
hr = device->Activate( __uuidof(IAudioEndpointVolume), CLSCTX_INPROC_SERVER, NULL, reinterpret_cast<void **>(&endpointVolume) );
if (FAILED(hr))
retur false;
hr=endpointVolume->SetMasterVolumeLevelScalar(volume,NULL);
if (FAILED(hr))
return false;
hr=endpointVolume->SetMute(false,NULL);
if (FAILED(hr))
return false;
}
return true;
}
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).