Commit c52e332b authored by Philippe Gorley's avatar Philippe Gorley Committed by Philippe Gorley

audio: add audio meter

Adds a signal that sends the linear RMS level for a given ring buffer.
The signal must be turned on via the API and can be turned off when
needed.

Adds an audio preview so the mic can be read. Call startAudioDevice and
stopAudioDevice to initialize and stop the audio layer.

Change-Id: I6a71ef87ee805a6d4bfa824fa901dd638e8cbd65
parent 53da9361
......@@ -867,6 +867,37 @@
</arg>
</method>
<!-- Audio meter -->
<method name="isAudioMeterActive" tp:name-for-bindings="isAudioMeterActive">
<tp:docstring>Gets whether or not an audio meter is active for id. If id is empty, returns whether or not there is at least one audio meter active.</tp:docstring>
<arg type="s" name="id" direction="in">
<tp:docstring>Ring buffer id.</tp:docstring>
</arg>
<arg type="b" name="isActive" direction="out" tp:type="Boolean">
<tp:docstring>If the audio meter is active for the passed in id.</tp:docstring>
</arg>
</method>
<method name="setAudioMeterState" tp:name-for-bindings="setAudioMeterState">
<tp:docstring>Sets whether or not the audio meter should be active for id. If id is empty, applies to all ring buffers.</tp:docstring>
<arg type="s" name="id" direction="in">
<tp:docstring>Ring buffer id.</tp:docstring>
</arg>
<arg type="b" name="state" direction="in" tp:type="Boolean">
<tp:docstring>True to enabled the audio meter, false to disable.</tp:docstring>
</arg>
</method>
<signal name="audioMeter" tp:name-for-bindings="audioMeter">
<tp:docstring>Signal containing volume level.</tp:docstring>
<arg type="s" name="id">
<tp:docstring>Ring buffer id.</tp:docstring>
</arg>
<arg type="d" name="level">
<tp:docstring>RMS value for the volume. Conversion to dB can be done with dB=20*log10(level). Level is between 0 and 1.</tp:docstring>
</arg>
</signal>
<!-- General Settings Panel -->
<method name="getNoiseSuppressState" tp:name-for-bindings="getNoiseSuppressState">
......
......@@ -72,6 +72,13 @@
<method name="stopCamera" tp:name-for-bindings="stopCamera">
</method>
<method name="startAudioDevice" tp:name-for-bindings="startAudioDevice">
<tp:docstring> Starts the audio layer stream, so the audio device can be read.</tp:docstring>
</method>
<method name="stopAudioDevice" tp:name-for-bindings="stopAudioDevice">
</method>
<method name="switchInput" tp:name-for-bindings="switchInput">
<arg type="s" name="resource" direction="in">
<tp:docstring>
......
......@@ -198,8 +198,10 @@ DBusClient::initLibrary(int flags)
exportable_callback<PresenceSignal::SubscriptionStateChanged>(bind(&DBusPresenceManager::subscriptionStateChanged, presM, _1, _2, _3)),
};
// Audio event handlers
const std::map<std::string, SharedCallback> audioEvHandlers = {
exportable_callback<AudioSignal::DeviceEvent>(bind(&DBusConfigurationManager::audioDeviceEvent, confM)),
exportable_callback<AudioSignal::AudioMeter>(bind(&DBusConfigurationManager::audioMeter, confM, _1, _2)),
};
const std::map<std::string, SharedCallback> dataXferEvHandlers = {
......
......@@ -711,3 +711,15 @@ DBusConfigurationManager::cancelDataTransfer(const uint64_t& id)
{
return uint32_t(DRing::cancelDataTransfer(id));
}
bool
DBusConfigurationManager::isAudioMeterActive(const std::string& id)
{
return DRing::isAudioMeterActive(id);
}
void
DBusConfigurationManager::setAudioMeterState(const std::string& id, const bool& state)
{
return DRing::setAudioMeterState(id, state);
}
......@@ -168,6 +168,9 @@ class DRING_PUBLIC DBusConfigurationManager :
void dataTransferBytesProgress(const uint64_t& id, uint32_t& error, int64_t& total, int64_t& progress);
uint32_t acceptFileTransfer(const uint64_t& id, const std::string& file_path, const int64_t& offset);
uint32_t cancelDataTransfer(const uint64_t& id);
bool isAudioMeterActive(const std::string& id);
void setAudioMeterState(const std::string& id, const bool& state);
};
#endif // __RING_DBUSCONFIGURATIONMANAGER_H__
......@@ -73,6 +73,18 @@ DBusVideoManager::stopCamera()
DRing::stopCamera();
}
void
DBusVideoManager::startAudioDevice()
{
DRing::startAudioDevice();
}
void
DBusVideoManager::stopAudioDevice()
{
DRing::stopAudioDevice();
}
auto
DBusVideoManager::switchInput(const std::string& resource) -> decltype(DRing::switchInput(resource))
{
......
......@@ -59,6 +59,8 @@ class DRING_PUBLIC DBusVideoManager :
std::string getDefaultDevice();
void startCamera();
void stopCamera();
void startAudioDevice();
void stopAudioDevice();
bool switchInput(const std::string& resource);
bool hasCameraStarted();
bool getDecodingAccelerated();
......
......@@ -59,6 +59,8 @@ public:
virtual void hardwareDecodingChanged(bool /*state*/){}
virtual void hardwareEncodingChanged(bool /*state*/){}
virtual void audioMeter(const std::string& /*id*/, float /*level*/){}
};
%}
......@@ -215,6 +217,8 @@ void enableProxyClient(const std::string& accountID, bool enable);
void setPushNotificationToken(const std::string& pushDeviceToken);
void pushNotificationReceived(const std::string& from, const std::map<std::string, std::string>& data);
bool isAudioMeterActive(const std::string& id);
void setAudioMeterState(const std::string& id, bool state);
}
class ConfigurationCallback {
......@@ -253,4 +257,6 @@ public:
virtual void hardwareDecodingChanged(bool /*state*/){}
virtual void hardwareEncodingChanged(bool /*state*/){}
virtual void audioMeter(const std::string& /*id*/, float /*level*/){}
};
......@@ -381,6 +381,8 @@ std::string getDefaultDevice();
void startCamera();
void stopCamera();
bool hasCameraStarted();
void startAudioDevice();
void stopAudioDevice();
bool switchInput(const std::string& resource);
bool switchToCamera();
std::map<std::string, std::string> getSettings(const std::string& name);
......
......@@ -57,6 +57,8 @@ public:
virtual void hardwareDecodingChanged(bool /*state*/){}
virtual void hardwareEncodingChanged(bool /*state*/){}
};
virtual void audioMeter(const std::string& /*id*/, float /*level*/){}
%}
%feature("director") ConfigurationCallback;
......@@ -206,6 +208,9 @@ int exportAccounts(std::vector<std::string> accountIDs, std::string toDir, std::
int importAccounts(std::string archivePath, std::string password);
void connectivityChanged();
bool isAudioMeterActive(const std::string& id);
void setAudioMeterState(const std::string& id, bool state);
}
class ConfigurationCallback {
......@@ -242,4 +247,5 @@ public:
virtual void hardwareDecodingChanged(bool /*state*/){}
virtual void hardwareEncodingChanged(bool /*state*/){}
virtual void audioMeter(const std::string& /*id*/, float /*level*/){}
};
......@@ -51,6 +51,8 @@ std::string getDefaultDevice();
void startCamera();
void stopCamera();
bool hasCameraStarted();
void startAudioDevice();
void stopAudioDevice();
bool switchInput(const std::string& resource);
bool switchToCamera();
std::map<std::string, std::string> getSettings(const std::string& name);
......
......@@ -2,7 +2,7 @@ dnl Jami - configure.ac for automake 1.9 and autoconf 2.59
dnl Process this file with autoconf to produce a configure script.
AC_PREREQ([2.65])
AC_INIT([Ring Daemon],[7.3.0],[ring@gnu.org],[ring])
AC_INIT([Ring Daemon],[7.4.0],[ring@gnu.org],[ring])
AC_COPYRIGHT([[Copyright (c) Savoir-faire Linux 2004-2018]])
AC_REVISION([$Revision$])
......
......@@ -41,6 +41,7 @@
#include "account_const.h"
#include "client/ring_signal.h"
#include "upnp/upnp_context.h"
#include "audio/ringbufferpool.h"
#ifdef __APPLE__
#include <TargetConditionals.h>
......@@ -969,4 +970,16 @@ void pushNotificationReceived(const std::string& from, const std::map<std::strin
}
}
bool
isAudioMeterActive(const std::string& id)
{
return ring::Manager::instance().getRingBufferPool().isAudioMeterActive(id);
}
void
setAudioMeterState(const std::string& id, bool state)
{
ring::Manager::instance().getRingBufferPool().setAudioMeterState(id, state);
}
} // namespace DRing
......@@ -95,6 +95,7 @@ getSignalHandlers()
/* Audio */
exported_callback<DRing::AudioSignal::DeviceEvent>(),
exported_callback<DRing::AudioSignal::AudioMeter>(),
/* DataTransfer */
exported_callback<DRing::DataTransferSignal::DataTransferEvent>(),
......
......@@ -154,6 +154,38 @@ AudioFrame::mix(const AudioFrame& frame)
}
}
float
AudioFrame::calcRMS() const
{
double rms = 0.0;
auto fmt = static_cast<AVSampleFormat>(frame_->format);
bool planar = av_sample_fmt_is_planar(fmt);
int perChannel = planar ? frame_->nb_samples : frame_->nb_samples * frame_->channels;
int channels = planar ? frame_->channels : 1;
if (fmt == AV_SAMPLE_FMT_S16 || fmt == AV_SAMPLE_FMT_S16P) {
for (int c = 0; c < channels; ++c) {
auto buf = reinterpret_cast<int16_t*>(frame_->extended_data[c]);
for (int i = 0; i < perChannel; ++i) {
auto sample = buf[i] * 0.000030517578125f;
rms += sample * sample;
}
}
} else if (fmt == AV_SAMPLE_FMT_FLT || fmt == AV_SAMPLE_FMT_FLTP) {
for (int c = 0; c < channels; ++c) {
auto buf = reinterpret_cast<float*>(frame_->extended_data[c]);
for (int i = 0; i < perChannel; ++i) {
rms += buf[i] * buf[i];
}
}
} else {
// Should not happen
RING_ERR() << "Unsupported format for getting volume level: " << av_get_sample_fmt_name(fmt);
return 0.0;
}
// divide by the number of multi-byte samples
return sqrt(rms / (frame_->nb_samples * frame_->channels));
}
VideoFrame::~VideoFrame()
{
if (releaseBufferCb_)
......@@ -359,6 +391,21 @@ stopCamera()
ring::Manager::instance().getVideoManager().videoPreview.reset();
}
void
startAudioDevice()
{
ring::Manager::instance().initAudioDriver();
ring::Manager::instance().startAudioDriverStream();
ring::Manager::instance().getVideoManager().audioPreview = ring::getAudioInput(ring::RingBufferPool::DEFAULT_ID);
}
void
stopAudioDevice()
{
ring::Manager::instance().getVideoManager().audioPreview.reset();
ring::Manager::instance().checkAudio(); // stops audio layer if no calls
}
std::string
startLocalRecorder(const bool& audioOnly, const std::string& filepath)
{
......
......@@ -59,6 +59,7 @@ struct VideoManager
*/
std::map<std::string, std::weak_ptr<AudioInput>> audioInputs;
std::mutex audioMutex;
std::shared_ptr<AudioInput> audioPreview;
};
std::shared_ptr<video::VideoFrameActiveWriter> getVideoCamera();
......
......@@ -215,11 +215,30 @@ DRING_PUBLIC void setPushNotificationToken(const std::string& pushDeviceToken);
*/
DRING_PUBLIC void pushNotificationReceived(const std::string& from, const std::map<std::string, std::string>& data);
/**
* Returns whether or not the audio meter is enabled for ring buffer @id.
*
* NOTE If @id is empty, returns true if at least 1 audio meter is enabled.
*/
DRING_PUBLIC bool isAudioMeterActive(const std::string& id);
/**
* Enables/disables an audio meter for the specified @id.
*
* NOTE If @id is empty, applies to all ring buffers.
*/
DRING_PUBLIC void setAudioMeterState(const std::string& id, bool state);
struct DRING_PUBLIC AudioSignal {
struct DRING_PUBLIC DeviceEvent {
constexpr static const char* name = "audioDeviceEvent";
using cb_type = void(void);
};
// Linear audio level (between 0 and 1). To get level in dB: dB=20*log10(level)
struct DRING_PUBLIC AudioMeter {
constexpr static const char* name = "AudioMeter";
using cb_type = void(const std::string& id, float level);
};
};
// Configuration signal type definitions
......
......@@ -102,6 +102,7 @@ public:
AudioFrame(const ring::AudioFormat& format, size_t nb_samples = 0);
~AudioFrame() {};
void mix(const AudioFrame& o);
float calcRMS() const;
private:
void setFormat(const ring::AudioFormat& format);
......@@ -165,6 +166,8 @@ DRING_PUBLIC std::string getCurrentCodecName(const std::string& callID);
DRING_PUBLIC void startCamera();
DRING_PUBLIC void stopCamera();
DRING_PUBLIC bool hasCameraStarted();
DRING_PUBLIC void startAudioDevice();
DRING_PUBLIC void stopAudioDevice();
DRING_PUBLIC bool switchInput(const std::string& resource);
DRING_PUBLIC bool switchToCamera();
DRING_PUBLIC void registerSinkTarget(const std::string& sinkId, const SinkTarget& target);
......
......@@ -25,7 +25,7 @@
#include "ringbuffer.h"
#include "logger.h"
#include "client/ring_signal.h"
#include "media_buffer.h"
#include "libav_deps.h"
......@@ -39,6 +39,8 @@ namespace ring {
// corresponds to 160 ms (about 5 rtp packets)
static const size_t MIN_BUFFER_SIZE = 1024;
static constexpr const int RMS_SIGNAL_INTERVAL = 5;
RingBuffer::RingBuffer(const std::string& rbuf_id, size_t /*size*/, AudioFormat format)
: id(rbuf_id)
, endPos_(0)
......@@ -175,6 +177,16 @@ void RingBuffer::putToBuffer(std::shared_ptr<AudioFrame>&& data)
endPos_ = pos;
if (rmsSignal_) {
++rmsFrameCount_;
rmsLevel_ += newBuf->calcRMS();
if (rmsFrameCount_ == RMS_SIGNAL_INTERVAL) {
emitSignal<DRing::AudioSignal::AudioMeter>(id, rmsLevel_ / RMS_SIGNAL_INTERVAL);
rmsLevel_ = 0;
rmsFrameCount_ = 0;
}
}
for (auto& offset : readoffsets_) {
if (offset.second.callback)
offset.second.callback(newBuf);
......
......@@ -28,6 +28,7 @@
#include "audio_frame_resizer.h"
#include "resampler.h"
#include <atomic>
#include <condition_variable>
#include <mutex>
#include <chrono>
......@@ -144,6 +145,9 @@ public:
*/
void debug();
bool isAudioMeterActive() const { return rmsSignal_; }
void setAudioMeterState(bool state) { rmsSignal_ = state; }
private:
struct ReadOffset {
size_t offset;
......@@ -197,6 +201,10 @@ private:
Resampler resampler_;
AudioFrameResizer resizer_;
std::atomic_bool rmsSignal_ {false};
double rmsLevel_ {0};
int rmsFrameCount_ {0};
};
} // namespace ring
......@@ -404,4 +404,41 @@ RingBufferPool::flushAllBuffers()
}
}
bool
RingBufferPool::isAudioMeterActive(const std::string& id)
{
std::lock_guard<std::recursive_mutex> lk(stateLock_);
if (!id.empty()) {
if (auto rb = getRingBuffer(id)) {
return rb->isAudioMeterActive();
}
} else {
for (auto item = ringBufferMap_.begin(); item != ringBufferMap_.end(); ++item) {
if (const auto rb = item->second.lock()) {
if (rb->isAudioMeterActive()) {
return true;
}
}
}
}
return false;
}
void
RingBufferPool::setAudioMeterState(const std::string& id, bool state)
{
std::lock_guard<std::recursive_mutex> lk(stateLock_);
if (!id.empty()) {
if (auto rb = getRingBuffer(id)) {
rb->setAudioMeterState(state);
}
} else {
for (auto item = ringBufferMap_.begin(); item != ringBufferMap_.end(); ++item) {
if (const auto rb = item->second.lock()) {
rb->setAudioMeterState(state);
}
}
}
}
} // namespace ring
......@@ -117,6 +117,9 @@ public:
*/
std::shared_ptr<RingBuffer> getRingBuffer(const std::string& id) const;
bool isAudioMeterActive(const std::string& id);
void setAudioMeterState(const std::string& id, bool state);
private:
NON_COPYABLE(RingBufferPool);
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment