#define MS_CLASS "RTC::AudioLevelObserver"
#include "RTC/AudioLevelObserver.hpp"
#include "Logger.hpp"
#include "MediaSoupErrors.hpp"
#include "Utils.hpp"
#include "RTC/RtpDictionaries.hpp"
#include <cmath>
#include <map>
namespace RTC
{
AudioLevelObserver::AudioLevelObserver(
RTC::Shared* shared, const std::string& id, RTC::RtpObserver::Listener* listener, json& data)
: RTC::RtpObserver(shared, id, listener)
{
MS_TRACE();
auto jsonMaxEntriesIt = data.find("maxEntries");
if (
jsonMaxEntriesIt == data.end() ||
!Utils::Json::IsPositiveInteger(*jsonMaxEntriesIt)
)
{
MS_THROW_TYPE_ERROR("missing maxEntries");
}
this->maxEntries = jsonMaxEntriesIt->get<uint16_t>();
if (this->maxEntries < 1)
MS_THROW_TYPE_ERROR("invalid maxEntries value %" PRIu16, this->maxEntries);
auto jsonThresholdIt = data.find("threshold");
if (jsonThresholdIt == data.end() || !jsonThresholdIt->is_number())
MS_THROW_TYPE_ERROR("missing threshold");
this->threshold = jsonThresholdIt->get<int8_t>();
if (this->threshold < -127 || this->threshold > 0)
MS_THROW_TYPE_ERROR("invalid threshold value %" PRIi8, this->threshold);
auto jsonIntervalIt = data.find("interval");
if (jsonIntervalIt == data.end() || !jsonIntervalIt->is_number())
MS_THROW_TYPE_ERROR("missing interval");
this->interval = jsonIntervalIt->get<uint16_t>();
if (this->interval < 250)
this->interval = 250;
else if (this->interval > 5000)
this->interval = 5000;
this->periodicTimer = new Timer(this);
this->periodicTimer->Start(this->interval, this->interval);
this->shared->channelMessageRegistrator->RegisterHandler(
this->id,
this,
nullptr,
nullptr);
}
AudioLevelObserver::~AudioLevelObserver()
{
MS_TRACE();
this->shared->channelMessageRegistrator->UnregisterHandler(this->id);
delete this->periodicTimer;
}
void AudioLevelObserver::AddProducer(RTC::Producer* producer)
{
MS_TRACE();
if (producer->GetKind() != RTC::Media::Kind::AUDIO)
MS_THROW_TYPE_ERROR("not an audio Producer");
this->mapProducerDBovs[producer];
}
void AudioLevelObserver::RemoveProducer(RTC::Producer* producer)
{
MS_TRACE();
this->mapProducerDBovs.erase(producer);
}
void AudioLevelObserver::ReceiveRtpPacket(RTC::Producer* producer, RTC::RtpPacket* packet)
{
MS_TRACE();
if (IsPaused())
return;
uint8_t volume;
bool voice;
if (!packet->ReadSsrcAudioLevel(volume, voice))
return;
auto& dBovs = this->mapProducerDBovs.at(producer);
dBovs.totalSum += volume;
dBovs.count++;
}
void AudioLevelObserver::ProducerPaused(RTC::Producer* producer)
{
this->mapProducerDBovs.erase(producer);
}
void AudioLevelObserver::ProducerResumed(RTC::Producer* producer)
{
this->mapProducerDBovs[producer];
}
void AudioLevelObserver::Paused()
{
MS_TRACE();
this->periodicTimer->Stop();
ResetMapProducerDBovs();
if (!this->silence)
{
this->silence = true;
this->shared->channelNotifier->Emit(this->id, "silence");
}
}
void AudioLevelObserver::Resumed()
{
MS_TRACE();
this->periodicTimer->Restart();
}
void AudioLevelObserver::Update()
{
MS_TRACE();
absl::btree_map<int8_t, RTC::Producer*> mapDBovsProducer;
for (auto& kv : this->mapProducerDBovs)
{
auto* producer = kv.first;
auto& dBovs = kv.second;
if (dBovs.count < 10)
continue;
auto avgDBov = -1 * static_cast<int8_t>(std::lround(dBovs.totalSum / dBovs.count));
if (avgDBov >= this->threshold)
mapDBovsProducer[avgDBov] = producer;
}
ResetMapProducerDBovs();
if (!mapDBovsProducer.empty())
{
this->silence = false;
uint16_t idx{ 0 };
auto rit = mapDBovsProducer.crbegin();
json data = json::array();
for (; idx < this->maxEntries && rit != mapDBovsProducer.crend(); ++idx, ++rit)
{
data.emplace_back(json::value_t::object);
auto& jsonEntry = data[idx];
jsonEntry["producerId"] = rit->second->id;
jsonEntry["volume"] = rit->first;
}
this->shared->channelNotifier->Emit(this->id, "volumes", data);
}
else if (!this->silence)
{
this->silence = true;
this->shared->channelNotifier->Emit(this->id, "silence");
}
}
void AudioLevelObserver::ResetMapProducerDBovs()
{
MS_TRACE();
for (auto& kv : this->mapProducerDBovs)
{
auto& dBovs = kv.second;
dBovs.totalSum = 0;
dBovs.count = 0;
}
}
inline void AudioLevelObserver::OnTimer(Timer* )
{
MS_TRACE();
Update();
}
}