Utils: Reenable check for valid input in volumemeter

This commit is contained in:
tt2468 2021-12-29 20:45:20 -08:00
parent 0c5d4ba3fb
commit 444685c89d
3 changed files with 454 additions and 455 deletions

View File

@ -1,354 +1,353 @@
/* /*
obs-websocket obs-websocket
Copyright (C) 2014 by Leonhard Oelke <leonhard@in-verted.de> Copyright (C) 2014 by Leonhard Oelke <leonhard@in-verted.de>
Copyright (C) 2016-2021 Stephane Lepin <stephane.lepin@gmail.com> Copyright (C) 2016-2021 Stephane Lepin <stephane.lepin@gmail.com>
Copyright (C) 2020-2021 Kyle Manning <tt2468@gmail.com> Copyright (C) 2020-2021 Kyle Manning <tt2468@gmail.com>
This program is free software; you can redistribute it and/or modify This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or the Free Software Foundation; either version 2 of the License, or
(at your option) any later version. (at your option) any later version.
This program is distributed in the hope that it will be useful, This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details. GNU General Public License for more details.
You should have received a copy of the GNU General Public License along You should have received a copy of the GNU General Public License along
with this program. If not, see <https://www.gnu.org/licenses/> with this program. If not, see <https://www.gnu.org/licenses/>
*/ */
#include <cmath> #include <cmath>
#include <algorithm> #include <algorithm>
#include "Obs.h" #include "Obs.h"
#include "Obs_VolumeMeter.h" #include "Obs_VolumeMeter.h"
#include "Obs_VolumeMeter_Helpers.h" #include "Obs_VolumeMeter_Helpers.h"
#include "../obs-websocket.h" #include "../obs-websocket.h"
Utils::Obs::VolumeMeter::Meter::Meter(obs_source_t *input) : Utils::Obs::VolumeMeter::Meter::Meter(obs_source_t *input) :
PeakMeterType(SAMPLE_PEAK_METER), PeakMeterType(SAMPLE_PEAK_METER),
_input(obs_source_get_weak_source(input)), _input(obs_source_get_weak_source(input)),
_channels(0), _channels(0),
_lastUpdate(0), _lastUpdate(0),
_volume(obs_source_get_volume(input)) _volume(obs_source_get_volume(input))
{ {
signal_handler_t *sh = obs_source_get_signal_handler(input); signal_handler_t *sh = obs_source_get_signal_handler(input);
signal_handler_connect(sh, "volume", Meter::InputVolumeCallback, this); signal_handler_connect(sh, "volume", Meter::InputVolumeCallback, this);
obs_source_add_audio_capture_callback(input, Meter::InputAudioCaptureCallback, this); obs_source_add_audio_capture_callback(input, Meter::InputAudioCaptureCallback, this);
blog_debug("[Utils::Obs::VolumeMeter::Meter::Meter] Meter created for input: %s", obs_source_get_name(input)); blog_debug("[Utils::Obs::VolumeMeter::Meter::Meter] Meter created for input: %s", obs_source_get_name(input));
} }
Utils::Obs::VolumeMeter::Meter::~Meter() Utils::Obs::VolumeMeter::Meter::~Meter()
{ {
OBSSourceAutoRelease input = obs_weak_source_get_source(_input); OBSSourceAutoRelease input = obs_weak_source_get_source(_input);
if (!input) { if (!input) {
blog(LOG_WARNING, "[Utils::Obs::VolumeMeter::Meter::~Meter] Failed to get strong reference to input. Has it been destroyed?"); blog(LOG_WARNING, "[Utils::Obs::VolumeMeter::Meter::~Meter] Failed to get strong reference to input. Has it been destroyed?");
return; return;
} }
signal_handler_t *sh = obs_source_get_signal_handler(input); signal_handler_t *sh = obs_source_get_signal_handler(input);
signal_handler_disconnect(sh, "volume", Meter::InputVolumeCallback, this); signal_handler_disconnect(sh, "volume", Meter::InputVolumeCallback, this);
obs_source_remove_audio_capture_callback(input, Meter::InputAudioCaptureCallback, this); obs_source_remove_audio_capture_callback(input, Meter::InputAudioCaptureCallback, this);
blog_debug("[Utils::Obs::VolumeMeter::Meter::~Meter] Meter destroyed for input: %s", obs_source_get_name(input)); blog_debug("[Utils::Obs::VolumeMeter::Meter::~Meter] Meter destroyed for input: %s", obs_source_get_name(input));
} }
bool Utils::Obs::VolumeMeter::Meter::InputValid() bool Utils::Obs::VolumeMeter::Meter::InputValid()
{ {
// return !obs_weak_source_expired(_input); return !obs_weak_source_expired(_input);
return true; }
}
json Utils::Obs::VolumeMeter::Meter::GetMeterData()
json Utils::Obs::VolumeMeter::Meter::GetMeterData() {
{ json ret;
json ret;
OBSSourceAutoRelease input = obs_weak_source_get_source(_input);
OBSSourceAutoRelease input = obs_weak_source_get_source(_input); if (!input) {
if (!input) { blog(LOG_WARNING, "[Utils::Obs::VolumeMeter::Meter::GetMeterData] Failed to get strong reference to input. Has it been destroyed?");
blog(LOG_WARNING, "[Utils::Obs::VolumeMeter::Meter::GetMeterData] Failed to get strong reference to input. Has it been destroyed?"); return ret;
return ret; }
}
std::vector<std::vector<float>> levels;
std::vector<std::vector<float>> levels; const float volume = _muted ? 0.0f : _volume.load();
const float volume = _muted ? 0.0f : _volume.load();
std::unique_lock<std::mutex> l(_mutex);
std::unique_lock<std::mutex> l(_mutex);
if (_lastUpdate != 0 && (os_gettime_ns() - _lastUpdate) * 0.000000001 > 0.3)
if (_lastUpdate != 0 && (os_gettime_ns() - _lastUpdate) * 0.000000001 > 0.3) ResetAudioLevels();
ResetAudioLevels();
for (int channel = 0; channel < _channels; channel++) {
for (int channel = 0; channel < _channels; channel++) { std::vector<float> level;
std::vector<float> level; level.push_back(_magnitude[channel] * volume);
level.push_back(_magnitude[channel] * volume); level.push_back(_peak[channel] * volume);
level.push_back(_peak[channel] * volume); level.push_back(_peak[channel]);
level.push_back(_peak[channel]);
levels.push_back(level);
levels.push_back(level); }
} l.unlock();
l.unlock();
ret["inputName"] = obs_source_get_name(input);
ret["inputName"] = obs_source_get_name(input); ret["inputLevelsMul"] = levels;
ret["inputLevelsMul"] = levels;
return ret;
return ret; }
}
// MUST HOLD LOCK
// MUST HOLD LOCK void Utils::Obs::VolumeMeter::Meter::ResetAudioLevels()
void Utils::Obs::VolumeMeter::Meter::ResetAudioLevels() {
{ _lastUpdate = 0;
_lastUpdate = 0; for (int channelNumber = 0; channelNumber < MAX_AUDIO_CHANNELS; channelNumber++) {
for (int channelNumber = 0; channelNumber < MAX_AUDIO_CHANNELS; channelNumber++) { _magnitude[channelNumber] = 0;
_magnitude[channelNumber] = 0; _peak[channelNumber] = 0;
_peak[channelNumber] = 0; }
} }
}
// MUST HOLD LOCK
// MUST HOLD LOCK void Utils::Obs::VolumeMeter::Meter::ProcessAudioChannels(const struct audio_data *data)
void Utils::Obs::VolumeMeter::Meter::ProcessAudioChannels(const struct audio_data *data) {
{ int channels = 0;
int channels = 0; for (int i = 0; i < MAX_AV_PLANES; i++) {
for (int i = 0; i < MAX_AV_PLANES; i++) { if (data->data[i])
if (data->data[i]) channels++;
channels++; }
}
bool channelsChanged = _channels != channels;
bool channelsChanged = _channels != channels; _channels = std::clamp(channels, 0, MAX_AUDIO_CHANNELS);
_channels = std::clamp(channels, 0, MAX_AUDIO_CHANNELS);
if (channelsChanged)
if (channelsChanged) ResetAudioLevels();
ResetAudioLevels(); }
}
// MUST HOLD LOCK
// MUST HOLD LOCK void Utils::Obs::VolumeMeter::Meter::ProcessPeak(const struct audio_data *data)
void Utils::Obs::VolumeMeter::Meter::ProcessPeak(const struct audio_data *data) {
{ size_t sampleCount = data->frames;
size_t sampleCount = data->frames; int channelNumber = 0;
int channelNumber = 0;
for (int planeNumber = 0; channelNumber < _channels; planeNumber++) {
for (int planeNumber = 0; channelNumber < _channels; planeNumber++) { float *samples = (float*)data->data[planeNumber];
float *samples = (float*)data->data[planeNumber]; if (!samples)
if (!samples) continue;
continue;
if (((uintptr_t)samples & 0xf) > 0) {
if (((uintptr_t)samples & 0xf) > 0) { _peak[channelNumber] = 1.0f;
_peak[channelNumber] = 1.0f; channelNumber++;
channelNumber++; continue;
continue; }
}
__m128 previousSamples = _mm_loadu_ps(_previousSamples[channelNumber]);
__m128 previousSamples = _mm_loadu_ps(_previousSamples[channelNumber]);
float peak;
float peak; switch (PeakMeterType) {
switch (PeakMeterType) { default:
default: case SAMPLE_PEAK_METER:
case SAMPLE_PEAK_METER: peak = GetSamplePeak(previousSamples, samples, sampleCount);
peak = GetSamplePeak(previousSamples, samples, sampleCount); break;
break; case TRUE_PEAK_METER:
case TRUE_PEAK_METER: peak = GetTruePeak(previousSamples, samples, sampleCount);
peak = GetTruePeak(previousSamples, samples, sampleCount); break;
break; }
}
switch (sampleCount) {
switch (sampleCount) { case 0:
case 0: break;
break; case 1:
case 1: _previousSamples[channelNumber][0] = _previousSamples[channelNumber][1];
_previousSamples[channelNumber][0] = _previousSamples[channelNumber][1]; _previousSamples[channelNumber][1] = _previousSamples[channelNumber][2];
_previousSamples[channelNumber][1] = _previousSamples[channelNumber][2]; _previousSamples[channelNumber][2] = _previousSamples[channelNumber][3];
_previousSamples[channelNumber][2] = _previousSamples[channelNumber][3]; _previousSamples[channelNumber][3] = samples[sampleCount - 1];
_previousSamples[channelNumber][3] = samples[sampleCount - 1]; break;
break; case 2:
case 2: _previousSamples[channelNumber][0] = _previousSamples[channelNumber][2];
_previousSamples[channelNumber][0] = _previousSamples[channelNumber][2]; _previousSamples[channelNumber][1] = _previousSamples[channelNumber][3];
_previousSamples[channelNumber][1] = _previousSamples[channelNumber][3]; _previousSamples[channelNumber][2] = samples[sampleCount - 2];
_previousSamples[channelNumber][2] = samples[sampleCount - 2]; _previousSamples[channelNumber][3] = samples[sampleCount - 1];
_previousSamples[channelNumber][3] = samples[sampleCount - 1]; break;
break; case 3:
case 3: _previousSamples[channelNumber][0] = _previousSamples[channelNumber][3];
_previousSamples[channelNumber][0] = _previousSamples[channelNumber][3]; _previousSamples[channelNumber][1] = samples[sampleCount - 3];
_previousSamples[channelNumber][1] = samples[sampleCount - 3]; _previousSamples[channelNumber][2] = samples[sampleCount - 2];
_previousSamples[channelNumber][2] = samples[sampleCount - 2]; _previousSamples[channelNumber][3] = samples[sampleCount - 1];
_previousSamples[channelNumber][3] = samples[sampleCount - 1]; break;
break; default:
default: _previousSamples[channelNumber][0] = samples[sampleCount - 4];
_previousSamples[channelNumber][0] = samples[sampleCount - 4]; _previousSamples[channelNumber][1] = samples[sampleCount - 3];
_previousSamples[channelNumber][1] = samples[sampleCount - 3]; _previousSamples[channelNumber][2] = samples[sampleCount - 2];
_previousSamples[channelNumber][2] = samples[sampleCount - 2]; _previousSamples[channelNumber][3] = samples[sampleCount - 1];
_previousSamples[channelNumber][3] = samples[sampleCount - 1]; }
}
_peak[channelNumber] = peak;
_peak[channelNumber] = peak;
channelNumber++;
channelNumber++; }
}
for (; channelNumber < MAX_AUDIO_CHANNELS; channelNumber++)
for (; channelNumber < MAX_AUDIO_CHANNELS; channelNumber++) _peak[channelNumber] = 0.0;
_peak[channelNumber] = 0.0; }
}
// MUST HOLD LOCK
// MUST HOLD LOCK void Utils::Obs::VolumeMeter::Meter::ProcessMagnitude(const struct audio_data *data)
void Utils::Obs::VolumeMeter::Meter::ProcessMagnitude(const struct audio_data *data) {
{ size_t sampleCount = data->frames;
size_t sampleCount = data->frames;
int channelNumber = 0;
int channelNumber = 0; for (int planeNumber = 0; channelNumber < _channels; planeNumber++) {
for (int planeNumber = 0; channelNumber < _channels; planeNumber++) { float *samples = (float*)data->data[planeNumber];
float *samples = (float*)data->data[planeNumber]; if (!samples)
if (!samples) continue;
continue;
float sum = 0.0;
float sum = 0.0; for (size_t i = 0; i < sampleCount; i++) {
for (size_t i = 0; i < sampleCount; i++) { float sample = samples[i];
float sample = samples[i]; sum += sample * sample;
sum += sample * sample; }
}
_magnitude[channelNumber] = std::sqrt(sum / sampleCount);
_magnitude[channelNumber] = std::sqrt(sum / sampleCount);
channelNumber++;
channelNumber++; }
} }
}
void Utils::Obs::VolumeMeter::Meter::InputAudioCaptureCallback(void *priv_data, obs_source_t *, const struct audio_data *data, bool muted)
void Utils::Obs::VolumeMeter::Meter::InputAudioCaptureCallback(void *priv_data, obs_source_t *, const struct audio_data *data, bool muted) {
{ auto c = static_cast<Meter*>(priv_data);
auto c = static_cast<Meter*>(priv_data);
std::unique_lock<std::mutex> l(c->_mutex);
std::unique_lock<std::mutex> l(c->_mutex);
c->_muted = muted;
c->_muted = muted; c->ProcessAudioChannels(data);
c->ProcessAudioChannels(data); c->ProcessPeak(data);
c->ProcessPeak(data); c->ProcessMagnitude(data);
c->ProcessMagnitude(data);
c->_lastUpdate = os_gettime_ns();
c->_lastUpdate = os_gettime_ns(); }
}
void Utils::Obs::VolumeMeter::Meter::InputVolumeCallback(void *priv_data, calldata_t *cd)
void Utils::Obs::VolumeMeter::Meter::InputVolumeCallback(void *priv_data, calldata_t *cd) {
{ auto c = static_cast<Meter*>(priv_data);
auto c = static_cast<Meter*>(priv_data);
c->_volume = (float)calldata_float(cd, "volume");
c->_volume = (float)calldata_float(cd, "volume"); }
}
Utils::Obs::VolumeMeter::Handler::Handler(UpdateCallback cb, uint64_t updatePeriod) :
Utils::Obs::VolumeMeter::Handler::Handler(UpdateCallback cb, uint64_t updatePeriod) : _updateCallback(cb),
_updateCallback(cb), _updatePeriod(updatePeriod),
_updatePeriod(updatePeriod), _running(false)
_running(false) {
{ signal_handler_t *sh = obs_get_signal_handler();
signal_handler_t *sh = obs_get_signal_handler(); if (!sh)
if (!sh) return;
return;
auto enumProc = [](void *priv_data, obs_source_t *input) {
auto enumProc = [](void *priv_data, obs_source_t *input) { auto c = static_cast<Handler*>(priv_data);
auto c = static_cast<Handler*>(priv_data);
if (!obs_source_active(input))
if (!obs_source_active(input)) return true;
return true;
uint32_t flags = obs_source_get_output_flags(input);
uint32_t flags = obs_source_get_output_flags(input); if ((flags & OBS_SOURCE_AUDIO) == 0)
if ((flags & OBS_SOURCE_AUDIO) == 0) return true;
return true;
c->_meters.emplace_back(std::move(new Meter(input)));
c->_meters.emplace_back(std::move(new Meter(input)));
return true;
return true; };
}; obs_enum_sources(enumProc, this);
obs_enum_sources(enumProc, this);
signal_handler_connect(sh, "source_activate", Handler::InputActivateCallback, this);
signal_handler_connect(sh, "source_activate", Handler::InputActivateCallback, this); signal_handler_connect(sh, "source_deactivate", Handler::InputDeactivateCallback, this);
signal_handler_connect(sh, "source_deactivate", Handler::InputDeactivateCallback, this);
_running = true;
_running = true; _updateThread = std::thread(&Handler::UpdateThread, this);
_updateThread = std::thread(&Handler::UpdateThread, this);
blog_debug("[Utils::Obs::VolumeMeter::Handler::Handler] Handler created.");
blog_debug("[Utils::Obs::VolumeMeter::Handler::Handler] Handler created."); }
}
Utils::Obs::VolumeMeter::Handler::~Handler()
Utils::Obs::VolumeMeter::Handler::~Handler() {
{ signal_handler_t *sh = obs_get_signal_handler();
signal_handler_t *sh = obs_get_signal_handler(); if (!sh)
if (!sh) return;
return;
signal_handler_disconnect(sh, "source_activate", Handler::InputActivateCallback, this);
signal_handler_disconnect(sh, "source_activate", Handler::InputActivateCallback, this); signal_handler_disconnect(sh, "source_deactivate", Handler::InputDeactivateCallback, this);
signal_handler_disconnect(sh, "source_deactivate", Handler::InputDeactivateCallback, this);
if (_running) {
if (_running) { _running = false;
_running = false; _cond.notify_all();
_cond.notify_all(); }
}
if (_updateThread.joinable())
if (_updateThread.joinable()) _updateThread.join();
_updateThread.join();
blog_debug("[Utils::Obs::VolumeMeter::Handler::~Handler] Handler destroyed.");
blog_debug("[Utils::Obs::VolumeMeter::Handler::~Handler] Handler destroyed."); }
}
void Utils::Obs::VolumeMeter::Handler::UpdateThread()
void Utils::Obs::VolumeMeter::Handler::UpdateThread() {
{ blog_debug("[Utils::Obs::VolumeMeter::Handler::UpdateThread] Thread started.");
blog_debug("[Utils::Obs::VolumeMeter::Handler::UpdateThread] Thread started."); while (_running) {
while (_running) { {
{ std::unique_lock<std::mutex> l(_mutex);
std::unique_lock<std::mutex> l(_mutex); if (_cond.wait_for(l, std::chrono::milliseconds(_updatePeriod), [this]{ return !_running; }))
if (_cond.wait_for(l, std::chrono::milliseconds(_updatePeriod), [this]{ return !_running; })) break;
break; }
}
std::vector<json> inputs;
std::vector<json> inputs; std::unique_lock<std::mutex> l(_meterMutex);
std::unique_lock<std::mutex> l(_meterMutex); for (auto &meter : _meters) {
for (auto &meter : _meters) { if (meter->InputValid())
if (meter->InputValid()) inputs.push_back(meter->GetMeterData());
inputs.push_back(meter->GetMeterData()); }
} l.unlock();
l.unlock();
if (_updateCallback)
if (_updateCallback) _updateCallback(inputs);
_updateCallback(inputs); }
} blog_debug("[Utils::Obs::VolumeMeter::Handler::UpdateThread] Thread stopped.");
blog_debug("[Utils::Obs::VolumeMeter::Handler::UpdateThread] Thread stopped."); }
}
void Utils::Obs::VolumeMeter::Handler::InputActivateCallback(void *priv_data, calldata_t *cd)
void Utils::Obs::VolumeMeter::Handler::InputActivateCallback(void *priv_data, calldata_t *cd) {
{ auto c = static_cast<Handler*>(priv_data);
auto c = static_cast<Handler*>(priv_data);
obs_source_t *input = GetCalldataPointer<obs_source_t>(cd, "source");
obs_source_t *input = GetCalldataPointer<obs_source_t>(cd, "source"); if (!input)
if (!input) return;
return;
if (obs_source_get_type(input) != OBS_SOURCE_TYPE_INPUT)
if (obs_source_get_type(input) != OBS_SOURCE_TYPE_INPUT) return;
return;
uint32_t flags = obs_source_get_output_flags(input);
uint32_t flags = obs_source_get_output_flags(input); if ((flags & OBS_SOURCE_AUDIO) == 0)
if ((flags & OBS_SOURCE_AUDIO) == 0) return;
return;
std::unique_lock<std::mutex> l(c->_meterMutex);
std::unique_lock<std::mutex> l(c->_meterMutex); c->_meters.emplace_back(std::move(new Meter(input)));
c->_meters.emplace_back(std::move(new Meter(input))); }
}
void Utils::Obs::VolumeMeter::Handler::InputDeactivateCallback(void *priv_data, calldata_t *cd)
void Utils::Obs::VolumeMeter::Handler::InputDeactivateCallback(void *priv_data, calldata_t *cd) {
{ auto c = static_cast<Handler*>(priv_data);
auto c = static_cast<Handler*>(priv_data);
obs_source_t *input = GetCalldataPointer<obs_source_t>(cd, "source");
obs_source_t *input = GetCalldataPointer<obs_source_t>(cd, "source"); if (!input)
if (!input) return;
return;
if (obs_source_get_type(input) != OBS_SOURCE_TYPE_INPUT)
if (obs_source_get_type(input) != OBS_SOURCE_TYPE_INPUT) return;
return;
// Don't ask me why, but using std::remove_if segfaults trying this.
// Don't ask me why, but using std::remove_if segfaults trying this. std::unique_lock<std::mutex> l(c->_meterMutex);
std::unique_lock<std::mutex> l(c->_meterMutex); std::vector<MeterPtr>::iterator iter;
std::vector<MeterPtr>::iterator iter; for (iter = c->_meters.begin(); iter != c->_meters.end();) {
for (iter = c->_meters.begin(); iter != c->_meters.end();) { if (obs_weak_source_references_source(iter->get()->GetWeakInput(), input))
if (obs_weak_source_references_source(iter->get()->GetWeakInput(), input)) iter = c->_meters.erase(iter);
iter = c->_meters.erase(iter); else
else ++iter;
++iter; }
} }
}

View File

@ -1,99 +1,99 @@
/* /*
obs-websocket obs-websocket
Copyright (C) 2016-2021 Stephane Lepin <stephane.lepin@gmail.com> Copyright (C) 2016-2021 Stephane Lepin <stephane.lepin@gmail.com>
Copyright (C) 2020-2021 Kyle Manning <tt2468@gmail.com> Copyright (C) 2020-2021 Kyle Manning <tt2468@gmail.com>
This program is free software; you can redistribute it and/or modify This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or the Free Software Foundation; either version 2 of the License, or
(at your option) any later version. (at your option) any later version.
This program is distributed in the hope that it will be useful, This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details. GNU General Public License for more details.
You should have received a copy of the GNU General Public License along You should have received a copy of the GNU General Public License along
with this program. If not, see <https://www.gnu.org/licenses/> with this program. If not, see <https://www.gnu.org/licenses/>
*/ */
#pragma once #pragma once
#include <string> #include <string>
#include <atomic> #include <atomic>
#include <mutex> #include <mutex>
#include <condition_variable> #include <condition_variable>
#include <memory> #include <memory>
#include <thread> #include <thread>
#include <obs.hpp> #include <obs.hpp>
#include "Obs.h" #include "Obs.h"
#include "Json.h" #include "Json.h"
namespace Utils { namespace Utils {
namespace Obs { namespace Obs {
namespace VolumeMeter { namespace VolumeMeter {
// Some code copied from https://github.com/obsproject/obs-studio/blob/master/libobs/obs-audio-controls.c // Some code copied from https://github.com/obsproject/obs-studio/blob/master/libobs/obs-audio-controls.c
// Keeps a running tally of the current audio levels, for a specific input // Keeps a running tally of the current audio levels, for a specific input
class Meter { class Meter {
public: public:
Meter(obs_source_t *input); Meter(obs_source_t *input);
~Meter(); ~Meter();
bool InputValid(); bool InputValid();
obs_weak_source_t *GetWeakInput() { return _input; } obs_weak_source_t *GetWeakInput() { return _input; }
json GetMeterData(); json GetMeterData();
std::atomic<enum obs_peak_meter_type> PeakMeterType; std::atomic<enum obs_peak_meter_type> PeakMeterType;
private: private:
OBSWeakSourceAutoRelease _input; OBSWeakSourceAutoRelease _input;
// All values in mul // All values in mul
std::mutex _mutex; std::mutex _mutex;
bool _muted; bool _muted;
int _channels; int _channels;
float _magnitude[MAX_AUDIO_CHANNELS]; float _magnitude[MAX_AUDIO_CHANNELS];
float _peak[MAX_AUDIO_CHANNELS]; float _peak[MAX_AUDIO_CHANNELS];
float _previousSamples[MAX_AUDIO_CHANNELS][4]; float _previousSamples[MAX_AUDIO_CHANNELS][4];
std::atomic<uint64_t> _lastUpdate; std::atomic<uint64_t> _lastUpdate;
std::atomic<float> _volume; std::atomic<float> _volume;
void ResetAudioLevels(); void ResetAudioLevels();
void ProcessAudioChannels(const struct audio_data *data); void ProcessAudioChannels(const struct audio_data *data);
void ProcessPeak(const struct audio_data *data); void ProcessPeak(const struct audio_data *data);
void ProcessMagnitude(const struct audio_data *data); void ProcessMagnitude(const struct audio_data *data);
static void InputAudioCaptureCallback(void *priv_data, obs_source_t *source, const struct audio_data *data, bool muted); static void InputAudioCaptureCallback(void *priv_data, obs_source_t *source, const struct audio_data *data, bool muted);
static void InputVolumeCallback(void *priv_data, calldata_t *cd); static void InputVolumeCallback(void *priv_data, calldata_t *cd);
}; };
// Maintains an array of active inputs // Maintains an array of active inputs
class Handler { class Handler {
typedef std::function<void(std::vector<json>)> UpdateCallback; typedef std::function<void(std::vector<json>)> UpdateCallback;
typedef std::unique_ptr<Meter> MeterPtr; typedef std::unique_ptr<Meter> MeterPtr;
public: public:
Handler(UpdateCallback cb, uint64_t updatePeriod = 50); Handler(UpdateCallback cb, uint64_t updatePeriod = 50);
~Handler(); ~Handler();
private: private:
UpdateCallback _updateCallback; UpdateCallback _updateCallback;
std::mutex _meterMutex; std::mutex _meterMutex;
std::vector<MeterPtr> _meters; std::vector<MeterPtr> _meters;
uint64_t _updatePeriod; uint64_t _updatePeriod;
std::mutex _mutex; std::mutex _mutex;
std::condition_variable _cond; std::condition_variable _cond;
std::atomic<bool> _running; std::atomic<bool> _running;
std::thread _updateThread; std::thread _updateThread;
void UpdateThread(); void UpdateThread();
static void InputActivateCallback(void *priv_data, calldata_t *cd); static void InputActivateCallback(void *priv_data, calldata_t *cd);
static void InputDeactivateCallback(void *priv_data, calldata_t *cd); static void InputDeactivateCallback(void *priv_data, calldata_t *cd);
}; };
} }
} }
} }

View File

@ -56,7 +56,7 @@ with this program. If not, see <https://www.gnu.org/licenses/>
r = fmaxf(r, x4_mem[3]); \ r = fmaxf(r, x4_mem[3]); \
} while (false) } while (false)
float GetSamplePeak(__m128 previousSamples, const float *samples, size_t sampleCount) static float GetSamplePeak(__m128 previousSamples, const float *samples, size_t sampleCount)
{ {
__m128 peak = previousSamples; __m128 peak = previousSamples;
for (size_t i = 0; (i + 3) < sampleCount; i += 4) { for (size_t i = 0; (i + 3) < sampleCount; i += 4) {
@ -69,7 +69,7 @@ float GetSamplePeak(__m128 previousSamples, const float *samples, size_t sampleC
return ret; return ret;
} }
float GetTruePeak(__m128 previousSamples, const float *samples, size_t sampleCount) static float GetTruePeak(__m128 previousSamples, const float *samples, size_t sampleCount)
{ {
const __m128 m3 = _mm_set_ps(-0.155915f, 0.935489f, 0.233872f, -0.103943f); const __m128 m3 = _mm_set_ps(-0.155915f, 0.935489f, 0.233872f, -0.103943f);
const __m128 m1 = _mm_set_ps(-0.216236f, 0.756827f, 0.504551f, -0.189207f); const __m128 m1 = _mm_set_ps(-0.216236f, 0.756827f, 0.504551f, -0.189207f);