mirror of
https://github.com/Palakis/obs-websocket.git
synced 2024-08-30 18:12:16 +00:00
Utils: Reenable check for valid input in volumemeter
This commit is contained in:
parent
0c5d4ba3fb
commit
444685c89d
@ -1,354 +1,353 @@
|
||||
/*
|
||||
obs-websocket
|
||||
Copyright (C) 2014 by Leonhard Oelke <leonhard@in-verted.de>
|
||||
Copyright (C) 2016-2021 Stephane Lepin <stephane.lepin@gmail.com>
|
||||
Copyright (C) 2020-2021 Kyle Manning <tt2468@gmail.com>
|
||||
|
||||
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
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
#include <cmath>
|
||||
#include <algorithm>
|
||||
|
||||
#include "Obs.h"
|
||||
#include "Obs_VolumeMeter.h"
|
||||
#include "Obs_VolumeMeter_Helpers.h"
|
||||
#include "../obs-websocket.h"
|
||||
|
||||
Utils::Obs::VolumeMeter::Meter::Meter(obs_source_t *input) :
|
||||
PeakMeterType(SAMPLE_PEAK_METER),
|
||||
_input(obs_source_get_weak_source(input)),
|
||||
_channels(0),
|
||||
_lastUpdate(0),
|
||||
_volume(obs_source_get_volume(input))
|
||||
{
|
||||
signal_handler_t *sh = obs_source_get_signal_handler(input);
|
||||
signal_handler_connect(sh, "volume", Meter::InputVolumeCallback, 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));
|
||||
}
|
||||
|
||||
Utils::Obs::VolumeMeter::Meter::~Meter()
|
||||
{
|
||||
OBSSourceAutoRelease input = obs_weak_source_get_source(_input);
|
||||
if (!input) {
|
||||
blog(LOG_WARNING, "[Utils::Obs::VolumeMeter::Meter::~Meter] Failed to get strong reference to input. Has it been destroyed?");
|
||||
return;
|
||||
}
|
||||
|
||||
signal_handler_t *sh = obs_source_get_signal_handler(input);
|
||||
signal_handler_disconnect(sh, "volume", Meter::InputVolumeCallback, 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));
|
||||
}
|
||||
|
||||
bool Utils::Obs::VolumeMeter::Meter::InputValid()
|
||||
{
|
||||
// return !obs_weak_source_expired(_input);
|
||||
return true;
|
||||
}
|
||||
|
||||
json Utils::Obs::VolumeMeter::Meter::GetMeterData()
|
||||
{
|
||||
json ret;
|
||||
|
||||
OBSSourceAutoRelease input = obs_weak_source_get_source(_input);
|
||||
if (!input) {
|
||||
blog(LOG_WARNING, "[Utils::Obs::VolumeMeter::Meter::GetMeterData] Failed to get strong reference to input. Has it been destroyed?");
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::vector<std::vector<float>> levels;
|
||||
const float volume = _muted ? 0.0f : _volume.load();
|
||||
|
||||
std::unique_lock<std::mutex> l(_mutex);
|
||||
|
||||
if (_lastUpdate != 0 && (os_gettime_ns() - _lastUpdate) * 0.000000001 > 0.3)
|
||||
ResetAudioLevels();
|
||||
|
||||
for (int channel = 0; channel < _channels; channel++) {
|
||||
std::vector<float> level;
|
||||
level.push_back(_magnitude[channel] * volume);
|
||||
level.push_back(_peak[channel] * volume);
|
||||
level.push_back(_peak[channel]);
|
||||
|
||||
levels.push_back(level);
|
||||
}
|
||||
l.unlock();
|
||||
|
||||
ret["inputName"] = obs_source_get_name(input);
|
||||
ret["inputLevelsMul"] = levels;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
// MUST HOLD LOCK
|
||||
void Utils::Obs::VolumeMeter::Meter::ResetAudioLevels()
|
||||
{
|
||||
_lastUpdate = 0;
|
||||
for (int channelNumber = 0; channelNumber < MAX_AUDIO_CHANNELS; channelNumber++) {
|
||||
_magnitude[channelNumber] = 0;
|
||||
_peak[channelNumber] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// MUST HOLD LOCK
|
||||
void Utils::Obs::VolumeMeter::Meter::ProcessAudioChannels(const struct audio_data *data)
|
||||
{
|
||||
int channels = 0;
|
||||
for (int i = 0; i < MAX_AV_PLANES; i++) {
|
||||
if (data->data[i])
|
||||
channels++;
|
||||
}
|
||||
|
||||
bool channelsChanged = _channels != channels;
|
||||
_channels = std::clamp(channels, 0, MAX_AUDIO_CHANNELS);
|
||||
|
||||
if (channelsChanged)
|
||||
ResetAudioLevels();
|
||||
}
|
||||
|
||||
// MUST HOLD LOCK
|
||||
void Utils::Obs::VolumeMeter::Meter::ProcessPeak(const struct audio_data *data)
|
||||
{
|
||||
size_t sampleCount = data->frames;
|
||||
int channelNumber = 0;
|
||||
|
||||
for (int planeNumber = 0; channelNumber < _channels; planeNumber++) {
|
||||
float *samples = (float*)data->data[planeNumber];
|
||||
if (!samples)
|
||||
continue;
|
||||
|
||||
if (((uintptr_t)samples & 0xf) > 0) {
|
||||
_peak[channelNumber] = 1.0f;
|
||||
channelNumber++;
|
||||
continue;
|
||||
}
|
||||
|
||||
__m128 previousSamples = _mm_loadu_ps(_previousSamples[channelNumber]);
|
||||
|
||||
float peak;
|
||||
switch (PeakMeterType) {
|
||||
default:
|
||||
case SAMPLE_PEAK_METER:
|
||||
peak = GetSamplePeak(previousSamples, samples, sampleCount);
|
||||
break;
|
||||
case TRUE_PEAK_METER:
|
||||
peak = GetTruePeak(previousSamples, samples, sampleCount);
|
||||
break;
|
||||
}
|
||||
|
||||
switch (sampleCount) {
|
||||
case 0:
|
||||
break;
|
||||
case 1:
|
||||
_previousSamples[channelNumber][0] = _previousSamples[channelNumber][1];
|
||||
_previousSamples[channelNumber][1] = _previousSamples[channelNumber][2];
|
||||
_previousSamples[channelNumber][2] = _previousSamples[channelNumber][3];
|
||||
_previousSamples[channelNumber][3] = samples[sampleCount - 1];
|
||||
break;
|
||||
case 2:
|
||||
_previousSamples[channelNumber][0] = _previousSamples[channelNumber][2];
|
||||
_previousSamples[channelNumber][1] = _previousSamples[channelNumber][3];
|
||||
_previousSamples[channelNumber][2] = samples[sampleCount - 2];
|
||||
_previousSamples[channelNumber][3] = samples[sampleCount - 1];
|
||||
break;
|
||||
case 3:
|
||||
_previousSamples[channelNumber][0] = _previousSamples[channelNumber][3];
|
||||
_previousSamples[channelNumber][1] = samples[sampleCount - 3];
|
||||
_previousSamples[channelNumber][2] = samples[sampleCount - 2];
|
||||
_previousSamples[channelNumber][3] = samples[sampleCount - 1];
|
||||
break;
|
||||
default:
|
||||
_previousSamples[channelNumber][0] = samples[sampleCount - 4];
|
||||
_previousSamples[channelNumber][1] = samples[sampleCount - 3];
|
||||
_previousSamples[channelNumber][2] = samples[sampleCount - 2];
|
||||
_previousSamples[channelNumber][3] = samples[sampleCount - 1];
|
||||
}
|
||||
|
||||
_peak[channelNumber] = peak;
|
||||
|
||||
channelNumber++;
|
||||
}
|
||||
|
||||
for (; channelNumber < MAX_AUDIO_CHANNELS; channelNumber++)
|
||||
_peak[channelNumber] = 0.0;
|
||||
}
|
||||
|
||||
// MUST HOLD LOCK
|
||||
void Utils::Obs::VolumeMeter::Meter::ProcessMagnitude(const struct audio_data *data)
|
||||
{
|
||||
size_t sampleCount = data->frames;
|
||||
|
||||
int channelNumber = 0;
|
||||
for (int planeNumber = 0; channelNumber < _channels; planeNumber++) {
|
||||
float *samples = (float*)data->data[planeNumber];
|
||||
if (!samples)
|
||||
continue;
|
||||
|
||||
float sum = 0.0;
|
||||
for (size_t i = 0; i < sampleCount; i++) {
|
||||
float sample = samples[i];
|
||||
sum += sample * sample;
|
||||
}
|
||||
|
||||
_magnitude[channelNumber] = std::sqrt(sum / sampleCount);
|
||||
|
||||
channelNumber++;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
std::unique_lock<std::mutex> l(c->_mutex);
|
||||
|
||||
c->_muted = muted;
|
||||
c->ProcessAudioChannels(data);
|
||||
c->ProcessPeak(data);
|
||||
c->ProcessMagnitude(data);
|
||||
|
||||
c->_lastUpdate = os_gettime_ns();
|
||||
}
|
||||
|
||||
void Utils::Obs::VolumeMeter::Meter::InputVolumeCallback(void *priv_data, calldata_t *cd)
|
||||
{
|
||||
auto c = static_cast<Meter*>(priv_data);
|
||||
|
||||
c->_volume = (float)calldata_float(cd, "volume");
|
||||
}
|
||||
|
||||
Utils::Obs::VolumeMeter::Handler::Handler(UpdateCallback cb, uint64_t updatePeriod) :
|
||||
_updateCallback(cb),
|
||||
_updatePeriod(updatePeriod),
|
||||
_running(false)
|
||||
{
|
||||
signal_handler_t *sh = obs_get_signal_handler();
|
||||
if (!sh)
|
||||
return;
|
||||
|
||||
auto enumProc = [](void *priv_data, obs_source_t *input) {
|
||||
auto c = static_cast<Handler*>(priv_data);
|
||||
|
||||
if (!obs_source_active(input))
|
||||
return true;
|
||||
|
||||
uint32_t flags = obs_source_get_output_flags(input);
|
||||
if ((flags & OBS_SOURCE_AUDIO) == 0)
|
||||
return true;
|
||||
|
||||
c->_meters.emplace_back(std::move(new Meter(input)));
|
||||
|
||||
return true;
|
||||
};
|
||||
obs_enum_sources(enumProc, this);
|
||||
|
||||
signal_handler_connect(sh, "source_activate", Handler::InputActivateCallback, this);
|
||||
signal_handler_connect(sh, "source_deactivate", Handler::InputDeactivateCallback, this);
|
||||
|
||||
_running = true;
|
||||
_updateThread = std::thread(&Handler::UpdateThread, this);
|
||||
|
||||
blog_debug("[Utils::Obs::VolumeMeter::Handler::Handler] Handler created.");
|
||||
}
|
||||
|
||||
Utils::Obs::VolumeMeter::Handler::~Handler()
|
||||
{
|
||||
signal_handler_t *sh = obs_get_signal_handler();
|
||||
if (!sh)
|
||||
return;
|
||||
|
||||
signal_handler_disconnect(sh, "source_activate", Handler::InputActivateCallback, this);
|
||||
signal_handler_disconnect(sh, "source_deactivate", Handler::InputDeactivateCallback, this);
|
||||
|
||||
if (_running) {
|
||||
_running = false;
|
||||
_cond.notify_all();
|
||||
}
|
||||
|
||||
if (_updateThread.joinable())
|
||||
_updateThread.join();
|
||||
|
||||
blog_debug("[Utils::Obs::VolumeMeter::Handler::~Handler] Handler destroyed.");
|
||||
}
|
||||
|
||||
void Utils::Obs::VolumeMeter::Handler::UpdateThread()
|
||||
{
|
||||
blog_debug("[Utils::Obs::VolumeMeter::Handler::UpdateThread] Thread started.");
|
||||
while (_running) {
|
||||
{
|
||||
std::unique_lock<std::mutex> l(_mutex);
|
||||
if (_cond.wait_for(l, std::chrono::milliseconds(_updatePeriod), [this]{ return !_running; }))
|
||||
break;
|
||||
}
|
||||
|
||||
std::vector<json> inputs;
|
||||
std::unique_lock<std::mutex> l(_meterMutex);
|
||||
for (auto &meter : _meters) {
|
||||
if (meter->InputValid())
|
||||
inputs.push_back(meter->GetMeterData());
|
||||
}
|
||||
l.unlock();
|
||||
|
||||
if (_updateCallback)
|
||||
_updateCallback(inputs);
|
||||
}
|
||||
blog_debug("[Utils::Obs::VolumeMeter::Handler::UpdateThread] Thread stopped.");
|
||||
}
|
||||
|
||||
void Utils::Obs::VolumeMeter::Handler::InputActivateCallback(void *priv_data, calldata_t *cd)
|
||||
{
|
||||
auto c = static_cast<Handler*>(priv_data);
|
||||
|
||||
obs_source_t *input = GetCalldataPointer<obs_source_t>(cd, "source");
|
||||
if (!input)
|
||||
return;
|
||||
|
||||
if (obs_source_get_type(input) != OBS_SOURCE_TYPE_INPUT)
|
||||
return;
|
||||
|
||||
uint32_t flags = obs_source_get_output_flags(input);
|
||||
if ((flags & OBS_SOURCE_AUDIO) == 0)
|
||||
return;
|
||||
|
||||
std::unique_lock<std::mutex> l(c->_meterMutex);
|
||||
c->_meters.emplace_back(std::move(new Meter(input)));
|
||||
}
|
||||
|
||||
void Utils::Obs::VolumeMeter::Handler::InputDeactivateCallback(void *priv_data, calldata_t *cd)
|
||||
{
|
||||
auto c = static_cast<Handler*>(priv_data);
|
||||
|
||||
obs_source_t *input = GetCalldataPointer<obs_source_t>(cd, "source");
|
||||
if (!input)
|
||||
return;
|
||||
|
||||
if (obs_source_get_type(input) != OBS_SOURCE_TYPE_INPUT)
|
||||
return;
|
||||
|
||||
// Don't ask me why, but using std::remove_if segfaults trying this.
|
||||
std::unique_lock<std::mutex> l(c->_meterMutex);
|
||||
std::vector<MeterPtr>::iterator iter;
|
||||
for (iter = c->_meters.begin(); iter != c->_meters.end();) {
|
||||
if (obs_weak_source_references_source(iter->get()->GetWeakInput(), input))
|
||||
iter = c->_meters.erase(iter);
|
||||
else
|
||||
++iter;
|
||||
}
|
||||
}
|
||||
/*
|
||||
obs-websocket
|
||||
Copyright (C) 2014 by Leonhard Oelke <leonhard@in-verted.de>
|
||||
Copyright (C) 2016-2021 Stephane Lepin <stephane.lepin@gmail.com>
|
||||
Copyright (C) 2020-2021 Kyle Manning <tt2468@gmail.com>
|
||||
|
||||
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
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
#include <cmath>
|
||||
#include <algorithm>
|
||||
|
||||
#include "Obs.h"
|
||||
#include "Obs_VolumeMeter.h"
|
||||
#include "Obs_VolumeMeter_Helpers.h"
|
||||
#include "../obs-websocket.h"
|
||||
|
||||
Utils::Obs::VolumeMeter::Meter::Meter(obs_source_t *input) :
|
||||
PeakMeterType(SAMPLE_PEAK_METER),
|
||||
_input(obs_source_get_weak_source(input)),
|
||||
_channels(0),
|
||||
_lastUpdate(0),
|
||||
_volume(obs_source_get_volume(input))
|
||||
{
|
||||
signal_handler_t *sh = obs_source_get_signal_handler(input);
|
||||
signal_handler_connect(sh, "volume", Meter::InputVolumeCallback, 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));
|
||||
}
|
||||
|
||||
Utils::Obs::VolumeMeter::Meter::~Meter()
|
||||
{
|
||||
OBSSourceAutoRelease input = obs_weak_source_get_source(_input);
|
||||
if (!input) {
|
||||
blog(LOG_WARNING, "[Utils::Obs::VolumeMeter::Meter::~Meter] Failed to get strong reference to input. Has it been destroyed?");
|
||||
return;
|
||||
}
|
||||
|
||||
signal_handler_t *sh = obs_source_get_signal_handler(input);
|
||||
signal_handler_disconnect(sh, "volume", Meter::InputVolumeCallback, 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));
|
||||
}
|
||||
|
||||
bool Utils::Obs::VolumeMeter::Meter::InputValid()
|
||||
{
|
||||
return !obs_weak_source_expired(_input);
|
||||
}
|
||||
|
||||
json Utils::Obs::VolumeMeter::Meter::GetMeterData()
|
||||
{
|
||||
json ret;
|
||||
|
||||
OBSSourceAutoRelease input = obs_weak_source_get_source(_input);
|
||||
if (!input) {
|
||||
blog(LOG_WARNING, "[Utils::Obs::VolumeMeter::Meter::GetMeterData] Failed to get strong reference to input. Has it been destroyed?");
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::vector<std::vector<float>> levels;
|
||||
const float volume = _muted ? 0.0f : _volume.load();
|
||||
|
||||
std::unique_lock<std::mutex> l(_mutex);
|
||||
|
||||
if (_lastUpdate != 0 && (os_gettime_ns() - _lastUpdate) * 0.000000001 > 0.3)
|
||||
ResetAudioLevels();
|
||||
|
||||
for (int channel = 0; channel < _channels; channel++) {
|
||||
std::vector<float> level;
|
||||
level.push_back(_magnitude[channel] * volume);
|
||||
level.push_back(_peak[channel] * volume);
|
||||
level.push_back(_peak[channel]);
|
||||
|
||||
levels.push_back(level);
|
||||
}
|
||||
l.unlock();
|
||||
|
||||
ret["inputName"] = obs_source_get_name(input);
|
||||
ret["inputLevelsMul"] = levels;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
// MUST HOLD LOCK
|
||||
void Utils::Obs::VolumeMeter::Meter::ResetAudioLevels()
|
||||
{
|
||||
_lastUpdate = 0;
|
||||
for (int channelNumber = 0; channelNumber < MAX_AUDIO_CHANNELS; channelNumber++) {
|
||||
_magnitude[channelNumber] = 0;
|
||||
_peak[channelNumber] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// MUST HOLD LOCK
|
||||
void Utils::Obs::VolumeMeter::Meter::ProcessAudioChannels(const struct audio_data *data)
|
||||
{
|
||||
int channels = 0;
|
||||
for (int i = 0; i < MAX_AV_PLANES; i++) {
|
||||
if (data->data[i])
|
||||
channels++;
|
||||
}
|
||||
|
||||
bool channelsChanged = _channels != channels;
|
||||
_channels = std::clamp(channels, 0, MAX_AUDIO_CHANNELS);
|
||||
|
||||
if (channelsChanged)
|
||||
ResetAudioLevels();
|
||||
}
|
||||
|
||||
// MUST HOLD LOCK
|
||||
void Utils::Obs::VolumeMeter::Meter::ProcessPeak(const struct audio_data *data)
|
||||
{
|
||||
size_t sampleCount = data->frames;
|
||||
int channelNumber = 0;
|
||||
|
||||
for (int planeNumber = 0; channelNumber < _channels; planeNumber++) {
|
||||
float *samples = (float*)data->data[planeNumber];
|
||||
if (!samples)
|
||||
continue;
|
||||
|
||||
if (((uintptr_t)samples & 0xf) > 0) {
|
||||
_peak[channelNumber] = 1.0f;
|
||||
channelNumber++;
|
||||
continue;
|
||||
}
|
||||
|
||||
__m128 previousSamples = _mm_loadu_ps(_previousSamples[channelNumber]);
|
||||
|
||||
float peak;
|
||||
switch (PeakMeterType) {
|
||||
default:
|
||||
case SAMPLE_PEAK_METER:
|
||||
peak = GetSamplePeak(previousSamples, samples, sampleCount);
|
||||
break;
|
||||
case TRUE_PEAK_METER:
|
||||
peak = GetTruePeak(previousSamples, samples, sampleCount);
|
||||
break;
|
||||
}
|
||||
|
||||
switch (sampleCount) {
|
||||
case 0:
|
||||
break;
|
||||
case 1:
|
||||
_previousSamples[channelNumber][0] = _previousSamples[channelNumber][1];
|
||||
_previousSamples[channelNumber][1] = _previousSamples[channelNumber][2];
|
||||
_previousSamples[channelNumber][2] = _previousSamples[channelNumber][3];
|
||||
_previousSamples[channelNumber][3] = samples[sampleCount - 1];
|
||||
break;
|
||||
case 2:
|
||||
_previousSamples[channelNumber][0] = _previousSamples[channelNumber][2];
|
||||
_previousSamples[channelNumber][1] = _previousSamples[channelNumber][3];
|
||||
_previousSamples[channelNumber][2] = samples[sampleCount - 2];
|
||||
_previousSamples[channelNumber][3] = samples[sampleCount - 1];
|
||||
break;
|
||||
case 3:
|
||||
_previousSamples[channelNumber][0] = _previousSamples[channelNumber][3];
|
||||
_previousSamples[channelNumber][1] = samples[sampleCount - 3];
|
||||
_previousSamples[channelNumber][2] = samples[sampleCount - 2];
|
||||
_previousSamples[channelNumber][3] = samples[sampleCount - 1];
|
||||
break;
|
||||
default:
|
||||
_previousSamples[channelNumber][0] = samples[sampleCount - 4];
|
||||
_previousSamples[channelNumber][1] = samples[sampleCount - 3];
|
||||
_previousSamples[channelNumber][2] = samples[sampleCount - 2];
|
||||
_previousSamples[channelNumber][3] = samples[sampleCount - 1];
|
||||
}
|
||||
|
||||
_peak[channelNumber] = peak;
|
||||
|
||||
channelNumber++;
|
||||
}
|
||||
|
||||
for (; channelNumber < MAX_AUDIO_CHANNELS; channelNumber++)
|
||||
_peak[channelNumber] = 0.0;
|
||||
}
|
||||
|
||||
// MUST HOLD LOCK
|
||||
void Utils::Obs::VolumeMeter::Meter::ProcessMagnitude(const struct audio_data *data)
|
||||
{
|
||||
size_t sampleCount = data->frames;
|
||||
|
||||
int channelNumber = 0;
|
||||
for (int planeNumber = 0; channelNumber < _channels; planeNumber++) {
|
||||
float *samples = (float*)data->data[planeNumber];
|
||||
if (!samples)
|
||||
continue;
|
||||
|
||||
float sum = 0.0;
|
||||
for (size_t i = 0; i < sampleCount; i++) {
|
||||
float sample = samples[i];
|
||||
sum += sample * sample;
|
||||
}
|
||||
|
||||
_magnitude[channelNumber] = std::sqrt(sum / sampleCount);
|
||||
|
||||
channelNumber++;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
std::unique_lock<std::mutex> l(c->_mutex);
|
||||
|
||||
c->_muted = muted;
|
||||
c->ProcessAudioChannels(data);
|
||||
c->ProcessPeak(data);
|
||||
c->ProcessMagnitude(data);
|
||||
|
||||
c->_lastUpdate = os_gettime_ns();
|
||||
}
|
||||
|
||||
void Utils::Obs::VolumeMeter::Meter::InputVolumeCallback(void *priv_data, calldata_t *cd)
|
||||
{
|
||||
auto c = static_cast<Meter*>(priv_data);
|
||||
|
||||
c->_volume = (float)calldata_float(cd, "volume");
|
||||
}
|
||||
|
||||
Utils::Obs::VolumeMeter::Handler::Handler(UpdateCallback cb, uint64_t updatePeriod) :
|
||||
_updateCallback(cb),
|
||||
_updatePeriod(updatePeriod),
|
||||
_running(false)
|
||||
{
|
||||
signal_handler_t *sh = obs_get_signal_handler();
|
||||
if (!sh)
|
||||
return;
|
||||
|
||||
auto enumProc = [](void *priv_data, obs_source_t *input) {
|
||||
auto c = static_cast<Handler*>(priv_data);
|
||||
|
||||
if (!obs_source_active(input))
|
||||
return true;
|
||||
|
||||
uint32_t flags = obs_source_get_output_flags(input);
|
||||
if ((flags & OBS_SOURCE_AUDIO) == 0)
|
||||
return true;
|
||||
|
||||
c->_meters.emplace_back(std::move(new Meter(input)));
|
||||
|
||||
return true;
|
||||
};
|
||||
obs_enum_sources(enumProc, this);
|
||||
|
||||
signal_handler_connect(sh, "source_activate", Handler::InputActivateCallback, this);
|
||||
signal_handler_connect(sh, "source_deactivate", Handler::InputDeactivateCallback, this);
|
||||
|
||||
_running = true;
|
||||
_updateThread = std::thread(&Handler::UpdateThread, this);
|
||||
|
||||
blog_debug("[Utils::Obs::VolumeMeter::Handler::Handler] Handler created.");
|
||||
}
|
||||
|
||||
Utils::Obs::VolumeMeter::Handler::~Handler()
|
||||
{
|
||||
signal_handler_t *sh = obs_get_signal_handler();
|
||||
if (!sh)
|
||||
return;
|
||||
|
||||
signal_handler_disconnect(sh, "source_activate", Handler::InputActivateCallback, this);
|
||||
signal_handler_disconnect(sh, "source_deactivate", Handler::InputDeactivateCallback, this);
|
||||
|
||||
if (_running) {
|
||||
_running = false;
|
||||
_cond.notify_all();
|
||||
}
|
||||
|
||||
if (_updateThread.joinable())
|
||||
_updateThread.join();
|
||||
|
||||
blog_debug("[Utils::Obs::VolumeMeter::Handler::~Handler] Handler destroyed.");
|
||||
}
|
||||
|
||||
void Utils::Obs::VolumeMeter::Handler::UpdateThread()
|
||||
{
|
||||
blog_debug("[Utils::Obs::VolumeMeter::Handler::UpdateThread] Thread started.");
|
||||
while (_running) {
|
||||
{
|
||||
std::unique_lock<std::mutex> l(_mutex);
|
||||
if (_cond.wait_for(l, std::chrono::milliseconds(_updatePeriod), [this]{ return !_running; }))
|
||||
break;
|
||||
}
|
||||
|
||||
std::vector<json> inputs;
|
||||
std::unique_lock<std::mutex> l(_meterMutex);
|
||||
for (auto &meter : _meters) {
|
||||
if (meter->InputValid())
|
||||
inputs.push_back(meter->GetMeterData());
|
||||
}
|
||||
l.unlock();
|
||||
|
||||
if (_updateCallback)
|
||||
_updateCallback(inputs);
|
||||
}
|
||||
blog_debug("[Utils::Obs::VolumeMeter::Handler::UpdateThread] Thread stopped.");
|
||||
}
|
||||
|
||||
void Utils::Obs::VolumeMeter::Handler::InputActivateCallback(void *priv_data, calldata_t *cd)
|
||||
{
|
||||
auto c = static_cast<Handler*>(priv_data);
|
||||
|
||||
obs_source_t *input = GetCalldataPointer<obs_source_t>(cd, "source");
|
||||
if (!input)
|
||||
return;
|
||||
|
||||
if (obs_source_get_type(input) != OBS_SOURCE_TYPE_INPUT)
|
||||
return;
|
||||
|
||||
uint32_t flags = obs_source_get_output_flags(input);
|
||||
if ((flags & OBS_SOURCE_AUDIO) == 0)
|
||||
return;
|
||||
|
||||
std::unique_lock<std::mutex> l(c->_meterMutex);
|
||||
c->_meters.emplace_back(std::move(new Meter(input)));
|
||||
}
|
||||
|
||||
void Utils::Obs::VolumeMeter::Handler::InputDeactivateCallback(void *priv_data, calldata_t *cd)
|
||||
{
|
||||
auto c = static_cast<Handler*>(priv_data);
|
||||
|
||||
obs_source_t *input = GetCalldataPointer<obs_source_t>(cd, "source");
|
||||
if (!input)
|
||||
return;
|
||||
|
||||
if (obs_source_get_type(input) != OBS_SOURCE_TYPE_INPUT)
|
||||
return;
|
||||
|
||||
// Don't ask me why, but using std::remove_if segfaults trying this.
|
||||
std::unique_lock<std::mutex> l(c->_meterMutex);
|
||||
std::vector<MeterPtr>::iterator iter;
|
||||
for (iter = c->_meters.begin(); iter != c->_meters.end();) {
|
||||
if (obs_weak_source_references_source(iter->get()->GetWeakInput(), input))
|
||||
iter = c->_meters.erase(iter);
|
||||
else
|
||||
++iter;
|
||||
}
|
||||
}
|
||||
|
@ -1,99 +1,99 @@
|
||||
/*
|
||||
obs-websocket
|
||||
Copyright (C) 2016-2021 Stephane Lepin <stephane.lepin@gmail.com>
|
||||
Copyright (C) 2020-2021 Kyle Manning <tt2468@gmail.com>
|
||||
|
||||
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
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <atomic>
|
||||
#include <mutex>
|
||||
#include <condition_variable>
|
||||
#include <memory>
|
||||
#include <thread>
|
||||
#include <obs.hpp>
|
||||
|
||||
#include "Obs.h"
|
||||
#include "Json.h"
|
||||
|
||||
namespace Utils {
|
||||
namespace Obs {
|
||||
namespace VolumeMeter {
|
||||
// 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
|
||||
class Meter {
|
||||
public:
|
||||
Meter(obs_source_t *input);
|
||||
~Meter();
|
||||
|
||||
bool InputValid();
|
||||
obs_weak_source_t *GetWeakInput() { return _input; }
|
||||
json GetMeterData();
|
||||
|
||||
std::atomic<enum obs_peak_meter_type> PeakMeterType;
|
||||
|
||||
private:
|
||||
OBSWeakSourceAutoRelease _input;
|
||||
|
||||
// All values in mul
|
||||
std::mutex _mutex;
|
||||
bool _muted;
|
||||
int _channels;
|
||||
float _magnitude[MAX_AUDIO_CHANNELS];
|
||||
float _peak[MAX_AUDIO_CHANNELS];
|
||||
float _previousSamples[MAX_AUDIO_CHANNELS][4];
|
||||
|
||||
std::atomic<uint64_t> _lastUpdate;
|
||||
std::atomic<float> _volume;
|
||||
|
||||
void ResetAudioLevels();
|
||||
void ProcessAudioChannels(const struct audio_data *data);
|
||||
void ProcessPeak(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 InputVolumeCallback(void *priv_data, calldata_t *cd);
|
||||
};
|
||||
|
||||
// Maintains an array of active inputs
|
||||
class Handler {
|
||||
typedef std::function<void(std::vector<json>)> UpdateCallback;
|
||||
typedef std::unique_ptr<Meter> MeterPtr;
|
||||
|
||||
public:
|
||||
Handler(UpdateCallback cb, uint64_t updatePeriod = 50);
|
||||
~Handler();
|
||||
|
||||
private:
|
||||
UpdateCallback _updateCallback;
|
||||
|
||||
std::mutex _meterMutex;
|
||||
std::vector<MeterPtr> _meters;
|
||||
uint64_t _updatePeriod;
|
||||
|
||||
std::mutex _mutex;
|
||||
std::condition_variable _cond;
|
||||
std::atomic<bool> _running;
|
||||
std::thread _updateThread;
|
||||
|
||||
void UpdateThread();
|
||||
static void InputActivateCallback(void *priv_data, calldata_t *cd);
|
||||
static void InputDeactivateCallback(void *priv_data, calldata_t *cd);
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
/*
|
||||
obs-websocket
|
||||
Copyright (C) 2016-2021 Stephane Lepin <stephane.lepin@gmail.com>
|
||||
Copyright (C) 2020-2021 Kyle Manning <tt2468@gmail.com>
|
||||
|
||||
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
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <atomic>
|
||||
#include <mutex>
|
||||
#include <condition_variable>
|
||||
#include <memory>
|
||||
#include <thread>
|
||||
#include <obs.hpp>
|
||||
|
||||
#include "Obs.h"
|
||||
#include "Json.h"
|
||||
|
||||
namespace Utils {
|
||||
namespace Obs {
|
||||
namespace VolumeMeter {
|
||||
// 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
|
||||
class Meter {
|
||||
public:
|
||||
Meter(obs_source_t *input);
|
||||
~Meter();
|
||||
|
||||
bool InputValid();
|
||||
obs_weak_source_t *GetWeakInput() { return _input; }
|
||||
json GetMeterData();
|
||||
|
||||
std::atomic<enum obs_peak_meter_type> PeakMeterType;
|
||||
|
||||
private:
|
||||
OBSWeakSourceAutoRelease _input;
|
||||
|
||||
// All values in mul
|
||||
std::mutex _mutex;
|
||||
bool _muted;
|
||||
int _channels;
|
||||
float _magnitude[MAX_AUDIO_CHANNELS];
|
||||
float _peak[MAX_AUDIO_CHANNELS];
|
||||
float _previousSamples[MAX_AUDIO_CHANNELS][4];
|
||||
|
||||
std::atomic<uint64_t> _lastUpdate;
|
||||
std::atomic<float> _volume;
|
||||
|
||||
void ResetAudioLevels();
|
||||
void ProcessAudioChannels(const struct audio_data *data);
|
||||
void ProcessPeak(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 InputVolumeCallback(void *priv_data, calldata_t *cd);
|
||||
};
|
||||
|
||||
// Maintains an array of active inputs
|
||||
class Handler {
|
||||
typedef std::function<void(std::vector<json>)> UpdateCallback;
|
||||
typedef std::unique_ptr<Meter> MeterPtr;
|
||||
|
||||
public:
|
||||
Handler(UpdateCallback cb, uint64_t updatePeriod = 50);
|
||||
~Handler();
|
||||
|
||||
private:
|
||||
UpdateCallback _updateCallback;
|
||||
|
||||
std::mutex _meterMutex;
|
||||
std::vector<MeterPtr> _meters;
|
||||
uint64_t _updatePeriod;
|
||||
|
||||
std::mutex _mutex;
|
||||
std::condition_variable _cond;
|
||||
std::atomic<bool> _running;
|
||||
std::thread _updateThread;
|
||||
|
||||
void UpdateThread();
|
||||
static void InputActivateCallback(void *priv_data, calldata_t *cd);
|
||||
static void InputDeactivateCallback(void *priv_data, calldata_t *cd);
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -56,7 +56,7 @@ with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
r = fmaxf(r, x4_mem[3]); \
|
||||
} 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;
|
||||
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;
|
||||
}
|
||||
|
||||
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 m1 = _mm_set_ps(-0.216236f, 0.756827f, 0.504551f, -0.189207f);
|
||||
|
Loading…
Reference in New Issue
Block a user