Skip to content

Instantly share code, notes, and snippets.

@gro-ove
Last active December 29, 2020 19:56
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save gro-ove/55f7de37942b9b7f042ef7e274eae90a to your computer and use it in GitHub Desktop.
Save gro-ove/55f7de37942b9b7f042ef7e274eae90a to your computer and use it in GitHub Desktop.
Easy way to get peak level of currently playing audio.
#include <memory>
#include <string>
#include <iostream>
#include <Windows.h>
#include <mmdeviceapi.h>
#include <endpointvolume.h>
#pragma comment(lib, "Ole32.lib")
#define CRUCIAL(cmd) \
{ \
if (FAILED(cmd)) \
{ \
throw std::exception("Failed: " #cmd); \
} \
}
#define DEBUG_NAME
#ifdef DEBUG_NAME
#include <Functiondiscoverykeys_devpkey.h>
std::wstring get_name(IMMDevice *device)
{
IPropertyStore *ps = nullptr;
PROPVARIANT pv;
PropVariantInit(&pv);
CRUCIAL(device->OpenPropertyStore(STGM_READ, &ps));
CRUCIAL(ps->GetValue(PKEY_Device_FriendlyName, &pv));
if (ps)
ps->Release();
const auto ret = std::wstring(pv.pwszVal);
PropVariantClear(&pv);
return ret;
}
std::string utf16_to_utf8(const wchar_t *s, size_t len)
{
std::string result;
if (len > 0)
{
result.resize(len * 2);
auto r = WideCharToMultiByte(CP_UTF8, 0, s, int(len), &result[0], int(result.size()), nullptr, nullptr);
if (r == 0)
{
result.resize(WideCharToMultiByte(CP_UTF8, 0, s, int(len), nullptr, 0, nullptr, nullptr));
r = WideCharToMultiByte(CP_UTF8, 0, s, int(len), &result[0], int(result.size()), nullptr, nullptr);
}
result.resize(r);
}
return result;
}
std::string utf16_to_utf8(const std::wstring &s)
{
return utf16_to_utf8(s.c_str(), s.size());
}
#endif
struct audio_meter_monitor_impl : IMMNotificationClient
{
struct device_holder
{
device_holder(IMMDeviceCollection *device_collection, uint32_t index)
{
device_collection->Item(index, &device);
device->Activate(__uuidof(IAudioMeterInformation), CLSCTX_INPROC_SERVER, nullptr, (void **)&audio_meter);
}
~device_holder()
{
SafeRelease(audio_meter);
SafeRelease(device);
}
void peak(float &v) { audio_meter->GetPeakValue(&v); }
IMMDevice *device{};
IAudioMeterInformation *audio_meter{};
};
audio_meter_monitor_impl()
{
CRUCIAL(CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED));
com_initialized_ = true;
}
~audio_meter_monitor_impl()
{
invalidate();
if (com_initialized_)
{
CoUninitialize();
}
}
ULONG STDMETHODCALLTYPE AddRef(void) { return InterlockedIncrement(&ref_counter_); }
ULONG STDMETHODCALLTYPE Release(void) { return InterlockedDecrement(&ref_counter_); }
HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, VOID **ppvInterface)
{
if (IID_IUnknown == riid)
{
*ppvInterface = (IUnknown *)this;
}
else if (__uuidof(IMMNotificationClient) == riid)
{
*ppvInterface = (IMMNotificationClient *)this;
}
else
{
*ppvInterface = nullptr;
return E_NOINTERFACE;
}
AddRef();
return S_OK;
}
HRESULT STDMETHODCALLTYPE OnDeviceAdded(LPCWSTR pwstrDeviceId)
{
invalidate();
return S_OK;
}
HRESULT STDMETHODCALLTYPE OnDeviceRemoved(LPCWSTR pwstrDeviceId)
{
invalidate();
return S_OK;
}
HRESULT STDMETHODCALLTYPE OnDefaultDeviceChanged(EDataFlow flow, ERole role, LPCWSTR pwstrDeviceId) { return S_OK; }
HRESULT STDMETHODCALLTYPE OnDeviceStateChanged(LPCWSTR pwstrDeviceId, DWORD dwNewState) { return S_OK; }
HRESULT STDMETHODCALLTYPE OnPropertyValueChanged(LPCWSTR pwstrDeviceId, const PROPERTYKEY key) { return S_OK; };
bool peak(float &v)
{
if (rescan_delay_ > 0)
{
--rescan_delay_;
}
if (!active_device_ || rescan_counter_ > 40 && rescan_delay_ == 0U)
{
float max_peak = -1.f;
if (rescan_delay_ == 0U)
{
rescan_delay_ = 20U;
rescan_counter_ = 0U;
if (!device_collection_)
{
SafeRelease(device_enumerator_);
CRUCIAL(CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&device_enumerator_)));
device_enumerator_->RegisterEndpointNotificationCallback(this);
CRUCIAL(device_enumerator_->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE, &device_collection_));
CRUCIAL(device_collection_->GetCount(&devices_num_));
}
for (auto i = 0U; i < devices_num_; ++i)
{
auto dev = device_active_ == int32_t(i) ? std::move(active_device_) : std::make_unique<device_holder>(device_collection_, i);
float cur_peak;
dev->peak(cur_peak);
if (cur_peak > max_peak)
{
max_peak = cur_peak;
active_device_ = std::move(dev);
device_active_ = int32_t(i);
}
}
}
if (v < 0.f || active_device_ == nullptr)
{
return false;
}
#ifdef DEBUG_NAME
std::cout << "currently listening to: " << utf16_to_utf8(get_name(active_device_->device)) << std::endl;
#endif
v = max_peak;
}
else
{
active_device_->peak(v);
rescan_counter_ = v > 0.01f ? 0 : rescan_counter_ + 1;
}
return true;
}
private:
IMMDeviceEnumerator *device_enumerator_{};
IMMDeviceCollection *device_collection_{};
std::unique_ptr<device_holder> active_device_{};
uint32_t devices_num_{};
uint32_t rescan_counter_{};
uint32_t rescan_delay_{};
int32_t device_active_ = -1;
bool com_initialized_{};
long ref_counter_{};
void invalidate()
{
SafeRelease(device_enumerator_);
SafeRelease(device_collection_);
active_device_ = nullptr;
device_active_ = -1;
rescan_delay_ = 20U;
}
template <class T>
static void SafeRelease(T *&p)
{
if (p)
{
p->Release();
p = nullptr;
}
}
};
struct audio_meter_monitor
{
float peak()
{
if (!impl_)
{
impl_ = std::make_unique<audio_meter_monitor_impl>();
}
impl_->peak(prev_value);
return prev_value;
}
private:
std::unique_ptr<audio_meter_monitor_impl> impl_{};
float prev_value;
};
void main()
{
const auto monitor = std::make_unique<audio_meter_monitor>();
for (auto k = 0; k < 400; ++k)
{
std::cout << monitor->peak() << std::endl;
Sleep(20);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment