Last active
December 29, 2020 19:56
-
-
Save gro-ove/55f7de37942b9b7f042ef7e274eae90a to your computer and use it in GitHub Desktop.
Easy way to get peak level of currently playing audio.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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