EventHandler: Implement InputVolumeMeters

This is probably one of the most requested features for obs-websocket.
This currently works by firing an event to all explicit subscribers
with an array of all active audio sources every **60 milliseconds.**

The `inputLevelsMul` field follows this data format:

Base: [Channel, Channel]
Channel: [magnitude (mul), peak (mul), input_peak (mul)]

           *Not Muted*         *Muted*
Example: [[0.3, 0.5, 0.9], [0.0, 0.0, 0.0]]

(input_peak is the actual peak value, before volume adjustment.)

You may notice that the values are only in mul. This is because we are
trying to cut down on bandwidth. dB values can be calculated using this
formula:

`dB = 20.0 * log10(mul)`
This commit is contained in:
tt2468 2021-11-22 03:37:56 -08:00
parent 1ac6ac6c87
commit d48ddef031
12 changed files with 617 additions and 10 deletions

View File

@ -118,6 +118,7 @@ set(obs-websocket_SOURCES
src/utils/Crypto.cpp
src/utils/Json.cpp
src/utils/Obs.cpp
src/utils/ObsVolumeMeter.cpp
src/utils/Platform.cpp
src/utils/Compat.cpp
deps/qr/cpp/QrCode.cpp)
@ -141,6 +142,8 @@ set(obs-websocket_HEADERS
src/utils/Crypto.h
src/utils/Json.h
src/utils/Obs.h
src/utils/ObsVolumeMeter.h
src/utils/ObsVolumeMeter_Helpers.h
src/utils/Platform.h
src/utils/Compat.h
src/utils/Utils.h

View File

@ -75,8 +75,14 @@ void EventHandler::SetObsLoadedCallback(EventHandler::ObsLoadedCallback cb)
// Function to increment refcounts for high volume event subscriptions
void EventHandler::ProcessSubscription(uint64_t eventSubscriptions)
{
if ((eventSubscriptions & EventSubscription::InputVolumeMeters) != 0)
_inputVolumeMetersRef++;
if ((eventSubscriptions & EventSubscription::InputVolumeMeters) != 0) {
if (_inputVolumeMetersRef.fetch_add(1) == 0) {
if (_inputVolumeMetersHandler)
blog(LOG_WARNING, "[EventHandler::ProcessSubscription] Input volume meter handler already exists!");
else
_inputVolumeMetersHandler = std::make_unique<Utils::Obs::VolumeMeter::Handler>(std::bind(&EventHandler::HandleInputVolumeMeters, this, std::placeholders::_1));
}
}
if ((eventSubscriptions & EventSubscription::InputActiveStateChanged) != 0)
_inputActiveStateChangedRef++;
if ((eventSubscriptions & EventSubscription::InputShowStateChanged) != 0)
@ -88,8 +94,10 @@ void EventHandler::ProcessSubscription(uint64_t eventSubscriptions)
// Function to decrement refcounts for high volume event subscriptions
void EventHandler::ProcessUnsubscription(uint64_t eventSubscriptions)
{
if ((eventSubscriptions & EventSubscription::InputVolumeMeters) != 0)
_inputVolumeMetersRef--;
if ((eventSubscriptions & EventSubscription::InputVolumeMeters) != 0) {
if (_inputVolumeMetersRef.fetch_sub(1) == 1)
_inputVolumeMetersHandler.reset();
}
if ((eventSubscriptions & EventSubscription::InputActiveStateChanged) != 0)
_inputActiveStateChangedRef--;
if ((eventSubscriptions & EventSubscription::InputShowStateChanged) != 0)

View File

@ -27,14 +27,9 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#include "types/EventSubscription.h"
#include "../obs-websocket.h"
#include "../utils/Obs.h"
#include "../utils/ObsVolumeMeter.h"
#include "../plugin-macros.generated.h"
template <typename T> T* GetCalldataPointer(const calldata_t *data, const char* name) {
void *ptr = nullptr;
calldata_get_ptr(data, name, &ptr);
return reinterpret_cast<T*>(ptr);
}
class EventHandler
{
public:
@ -55,6 +50,7 @@ class EventHandler
std::atomic<bool> _obsLoaded;
std::unique_ptr<Utils::Obs::VolumeMeter::Handler> _inputVolumeMetersHandler;
std::atomic<uint64_t> _inputVolumeMetersRef;
std::atomic<uint64_t> _inputActiveStateChangedRef;
std::atomic<uint64_t> _inputShowStateChangedRef;
@ -107,6 +103,7 @@ class EventHandler
void HandleInputCreated(obs_source_t *source);
void HandleInputRemoved(obs_source_t *source);
void HandleInputNameChanged(obs_source_t *source, std::string oldInputName, std::string inputName);
void HandleInputVolumeMeters(std::vector<json> inputs); // AudioMeter::Handler callback
static void HandleInputActiveStateChanged(void *param, calldata_t *data); // Direct callback
static void HandleInputShowStateChanged(void *param, calldata_t *data); // Direct callback
static void HandleInputMuteStateChanged(void *param, calldata_t *data); // Direct callback

View File

@ -0,0 +1,21 @@
/*
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/>
*/
#include "EventHandler.h"

View File

@ -49,6 +49,13 @@ void EventHandler::HandleInputNameChanged(obs_source_t *, std::string oldInputNa
BroadcastEvent(EventSubscription::Inputs, "InputNameChanged", eventData);
}
void EventHandler::HandleInputVolumeMeters(std::vector<json> inputs)
{
json eventData;
eventData["inputs"] = inputs;
BroadcastEvent(EventSubscription::InputVolumeMeters, "InputVolumeMeters", eventData);
}
void EventHandler::HandleInputActiveStateChanged(void *param, calldata_t *data)
{
auto eventHandler = reinterpret_cast<EventHandler*>(param);

View File

@ -142,6 +142,7 @@ void WebSocketApiEventCallback(std::string vendorName, std::string eventType, ob
}
void ___source_dummy_addref(obs_source_t*) {}
void ___weak_source_dummy_addref(obs_weak_source_t*) {}
void ___scene_dummy_addref(obs_scene_t*) {}
void ___sceneitem_dummy_addref(obs_sceneitem_t*) {}
void ___data_dummy_addref(obs_data_t*) {}

View File

@ -33,6 +33,7 @@ with this program. If not, see <https://www.gnu.org/licenses/>
// Autorelease object definitions
void ___source_dummy_addref(obs_source_t*);
void ___weak_source_dummy_addref(obs_weak_source_t*);
void ___scene_dummy_addref(obs_scene_t*);
void ___sceneitem_dummy_addref(obs_sceneitem_t*);
void ___data_dummy_addref(obs_data_t*);
@ -43,6 +44,7 @@ void ___data_item_release(obs_data_item_t*);
void ___properties_dummy_addref(obs_properties_t*);
using OBSSourceAutoRelease = OBSRef<obs_source_t*, ___source_dummy_addref, obs_source_release>;
using OBSWeakSourceAutoRelease = OBSRef<obs_weak_source_t*, ___weak_source_dummy_addref, obs_weak_source_release>;
using OBSSceneAutoRelease = OBSRef<obs_scene_t*, ___scene_dummy_addref, obs_scene_release>;
using OBSSceneItemAutoRelease = OBSRef<obs_sceneitem_t*, ___sceneitem_dummy_addref, obs_sceneitem_release>;
using OBSDataAutoRelease = OBSRef<obs_data_t*, ___data_dummy_addref, obs_data_release>;

View File

@ -24,6 +24,12 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#include "Json.h"
template <typename T> T* GetCalldataPointer(const calldata_t *data, const char* name) {
void *ptr = nullptr;
calldata_get_ptr(data, name, &ptr);
return reinterpret_cast<T*>(ptr);
}
enum ObsOutputState {
OBS_WEBSOCKET_OUTPUT_STARTING,
OBS_WEBSOCKET_OUTPUT_STARTED,

View File

@ -0,0 +1,354 @@
/*
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 "ObsVolumeMeter.h"
#include "ObsVolumeMeter_Helpers.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) {
_mutex.lock();
_running = false;
_mutex.unlock();
_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;
}
}

View File

@ -0,0 +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-websocket.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 = 60);
~Handler();
private:
UpdateCallback _updateCallback;
std::mutex _meterMutex;
std::vector<MeterPtr> _meters;
uint64_t _updatePeriod;
std::mutex _mutex;
std::condition_variable _cond;
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);
};
}
}
}

View File

@ -0,0 +1,108 @@
/*
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/>
*/
// All of this is literally magic
// It should probably be imported as a libobs util someday though.
#define SHIFT_RIGHT_2PS(msb, lsb) \
{ \
__m128 tmp = \
_mm_shuffle_ps(lsb, msb, _MM_SHUFFLE(0, 0, 3, 3)); \
lsb = _mm_shuffle_ps(lsb, tmp, _MM_SHUFFLE(2, 1, 2, 1)); \
msb = _mm_shuffle_ps(msb, msb, _MM_SHUFFLE(3, 3, 2, 1)); \
}
#define abs_ps(v) _mm_andnot_ps(_mm_set1_ps(-0.f), v)
#define VECTOR_MATRIX_CROSS_PS(out, v, m0, m1, m2, m3) \
{ \
out = _mm_mul_ps(v, m0); \
__m128 mul1 = _mm_mul_ps(v, m1); \
__m128 mul2 = _mm_mul_ps(v, m2); \
__m128 mul3 = _mm_mul_ps(v, m3); \
\
_MM_TRANSPOSE4_PS(out, mul1, mul2, mul3); \
\
out = _mm_add_ps(out, mul1); \
out = _mm_add_ps(out, mul2); \
out = _mm_add_ps(out, mul3); \
}
#define hmax_ps(r, x4) \
do { \
float x4_mem[4]; \
_mm_storeu_ps(x4_mem, x4); \
r = x4_mem[0]; \
r = fmaxf(r, x4_mem[1]); \
r = fmaxf(r, x4_mem[2]); \
r = fmaxf(r, x4_mem[3]); \
} while (false)
float GetSamplePeak(__m128 previousSamples, const float *samples, size_t sampleCount)
{
__m128 peak = previousSamples;
for (size_t i = 0; (i + 3) < sampleCount; i += 4) {
__m128 newWork = _mm_load_ps(&samples[i]);
peak = _mm_max_ps(peak, abs_ps(newWork));
}
float ret;
hmax_ps(ret, peak);
return ret;
}
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);
const __m128 p1 = _mm_set_ps(-0.189207f, 0.504551f, 0.756827f, -0.216236f);
const __m128 p3 = _mm_set_ps(-0.103943f, 0.233872f, 0.935489f, -0.155915f);
__m128 work = previousSamples;
__m128 peak = previousSamples;
for (size_t i = 0; (i + 3) < sampleCount; i += 4) {
__m128 newWork = _mm_load_ps(&samples[i]);
__m128 intrpSamples;
__m128 absNewWork = abs_ps(newWork);
peak = _mm_max_ps(peak, absNewWork);
SHIFT_RIGHT_2PS(newWork, work);
VECTOR_MATRIX_CROSS_PS(intrpSamples, work, m3, m1, p1, p3);
peak = _mm_max_ps(peak, abs_ps(intrpSamples));
SHIFT_RIGHT_2PS(newWork, work);
VECTOR_MATRIX_CROSS_PS(intrpSamples, work, m3, m1, p1, p3);
peak = _mm_max_ps(peak, abs_ps(intrpSamples));
SHIFT_RIGHT_2PS(newWork, work);
VECTOR_MATRIX_CROSS_PS(intrpSamples, work, m3, m1, p1, p3);
peak = _mm_max_ps(peak, abs_ps(intrpSamples));
SHIFT_RIGHT_2PS(newWork, work);
VECTOR_MATRIX_CROSS_PS(intrpSamples, work, m3, m1, p1, p3);
peak = _mm_max_ps(peak, abs_ps(intrpSamples));
}
float ret;
hmax_ps(ret, peak);
return ret;
}

View File

@ -22,5 +22,6 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#include "Crypto.h"
#include "Json.h"
#include "Obs.h"
#include "ObsVolumeMeter.h"
#include "Platform.h"
#include "Compat.h"