diff --git a/bin/dbus/cx.ring.Ring.ConfigurationManager.xml b/bin/dbus/cx.ring.Ring.ConfigurationManager.xml index 517157d08a982d3e834d00f85cf375352d5d9d0f..2de4955e3fe187706c243ecf54a4f4e0dcc7fc83 100644 --- a/bin/dbus/cx.ring.Ring.ConfigurationManager.xml +++ b/bin/dbus/cx.ring.Ring.ConfigurationManager.xml @@ -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"> diff --git a/bin/dbus/cx.ring.Ring.VideoManager.xml b/bin/dbus/cx.ring.Ring.VideoManager.xml index c1b648a841ff9cafeee46e582de18315a8f2d1cd..c5a79d3ab1d199375db4a592692be6a0412f96f2 100644 --- a/bin/dbus/cx.ring.Ring.VideoManager.xml +++ b/bin/dbus/cx.ring.Ring.VideoManager.xml @@ -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> diff --git a/bin/dbus/dbusclient.cpp b/bin/dbus/dbusclient.cpp index d412fe37585b6b096fdb9615a9306090dd286e06..ac97d336b73cba044eeb800e62d3c683f0489b22 100644 --- a/bin/dbus/dbusclient.cpp +++ b/bin/dbus/dbusclient.cpp @@ -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 = { diff --git a/bin/dbus/dbusconfigurationmanager.cpp b/bin/dbus/dbusconfigurationmanager.cpp index 248e94198d7155d75a5d1b0ed32979844ca98cc8..387b497cb968b738a63375a1973f0216c833a420 100644 --- a/bin/dbus/dbusconfigurationmanager.cpp +++ b/bin/dbus/dbusconfigurationmanager.cpp @@ -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); +} diff --git a/bin/dbus/dbusconfigurationmanager.h b/bin/dbus/dbusconfigurationmanager.h index b12f7e0f51493fe4a831c35560e5cac16a911eaf..e7097dbe06a3ba656fd2b3d263d0e6ca19e3a9c8 100644 --- a/bin/dbus/dbusconfigurationmanager.h +++ b/bin/dbus/dbusconfigurationmanager.h @@ -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__ diff --git a/bin/dbus/dbusvideomanager.cpp b/bin/dbus/dbusvideomanager.cpp index 8c8ee6d5d9af2a978841135238aec18e21331c6b..e867e5c1a7d7df6fc915cd3ad1f0a8d11486e19a 100644 --- a/bin/dbus/dbusvideomanager.cpp +++ b/bin/dbus/dbusvideomanager.cpp @@ -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)) { diff --git a/bin/dbus/dbusvideomanager.h b/bin/dbus/dbusvideomanager.h index 1471143156fb7ac9a6ec08093819e515b0b727c4..c0d0427d3cbf0a90836ab99e8165b3091cc99c1b 100644 --- a/bin/dbus/dbusvideomanager.h +++ b/bin/dbus/dbusvideomanager.h @@ -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(); diff --git a/bin/jni/configurationmanager.i b/bin/jni/configurationmanager.i index 71bb314e482c4d6d169975295da6a2d1899a17e9..dcc5ff06834ad5a033a4ec537b4920ae934968b0 100644 --- a/bin/jni/configurationmanager.i +++ b/bin/jni/configurationmanager.i @@ -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*/){} }; diff --git a/bin/jni/videomanager.i b/bin/jni/videomanager.i index 06f63f46237d7796d2e48e2b65c6b7c445f3265c..a84d6c83f96839c1949518641075b299337e0dd4 100644 --- a/bin/jni/videomanager.i +++ b/bin/jni/videomanager.i @@ -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); diff --git a/bin/nodejs/configurationmanager.i b/bin/nodejs/configurationmanager.i index 100fa81a4e99a32675c950ad2edd7665f099d793..17deafcf1fd98c1f3f171a9aa9ecd73afe57b566 100644 --- a/bin/nodejs/configurationmanager.i +++ b/bin/nodejs/configurationmanager.i @@ -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*/){} }; diff --git a/bin/nodejs/videomanager.i b/bin/nodejs/videomanager.i index 5e61935fb18193274ab1830cebf2bd8d4e436829..ba119b4d92017c3640643f0661d7c7441e9dbc4e 100644 --- a/bin/nodejs/videomanager.i +++ b/bin/nodejs/videomanager.i @@ -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); diff --git a/configure.ac b/configure.ac index 6781dececb0143c9d90aeab01ff7585b522c86f6..28a045b2aa269ca5cf5df195059c0b36a6c7d680 100644 --- a/configure.ac +++ b/configure.ac @@ -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$]) diff --git a/src/client/configurationmanager.cpp b/src/client/configurationmanager.cpp index c567bf8a8f4e531a64178dad43551c455137a926..e1257106c5274b6f6d8796dc294defad22668348 100644 --- a/src/client/configurationmanager.cpp +++ b/src/client/configurationmanager.cpp @@ -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 diff --git a/src/client/ring_signal.cpp b/src/client/ring_signal.cpp index bd0a3751037b78f687e8bc2878f519cebb51cda1..a40925ecebbf7878f862dfce55274c667e19dff2 100644 --- a/src/client/ring_signal.cpp +++ b/src/client/ring_signal.cpp @@ -95,6 +95,7 @@ getSignalHandlers() /* Audio */ exported_callback<DRing::AudioSignal::DeviceEvent>(), + exported_callback<DRing::AudioSignal::AudioMeter>(), /* DataTransfer */ exported_callback<DRing::DataTransferSignal::DataTransferEvent>(), diff --git a/src/client/videomanager.cpp b/src/client/videomanager.cpp index c26f0773170fb24c4a91934d085521148a637c38..b99ae6378c5a1c1a1ef42fd419fcbf9f4183a487 100644 --- a/src/client/videomanager.cpp +++ b/src/client/videomanager.cpp @@ -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) { diff --git a/src/client/videomanager.h b/src/client/videomanager.h index fe65f591e56b2e03d8cb8411a66bf706730b6791..98e8912b8b5a38827d8b188e01508d669f22d7ea 100644 --- a/src/client/videomanager.h +++ b/src/client/videomanager.h @@ -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(); diff --git a/src/dring/configurationmanager_interface.h b/src/dring/configurationmanager_interface.h index e0cca93be34c633484cadf990727dd3d4d537f6d..1eabe1bcbc059e38d7b57adf9b1479b00cd4b0d9 100644 --- a/src/dring/configurationmanager_interface.h +++ b/src/dring/configurationmanager_interface.h @@ -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 diff --git a/src/dring/videomanager_interface.h b/src/dring/videomanager_interface.h index e53c011352400e6a2d2a8163e6a54f2c9f98010b..44bb0d144bc8688056bd3e70a8e042c2db472125 100644 --- a/src/dring/videomanager_interface.h +++ b/src/dring/videomanager_interface.h @@ -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); diff --git a/src/media/audio/ringbuffer.cpp b/src/media/audio/ringbuffer.cpp index f9374ba48ebb670aba9c454d35ef44fa8a895432..9a10c1dd1cc14deb50b5c5a3e4c4bdb2bd5fc3ec 100644 --- a/src/media/audio/ringbuffer.cpp +++ b/src/media/audio/ringbuffer.cpp @@ -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); diff --git a/src/media/audio/ringbuffer.h b/src/media/audio/ringbuffer.h index 9a159347759c6485adf0ca8f71e5c0885e46eeeb..9eba33d64d27123a9e69bc57d080a1ab33b646da 100644 --- a/src/media/audio/ringbuffer.h +++ b/src/media/audio/ringbuffer.h @@ -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 diff --git a/src/media/audio/ringbufferpool.cpp b/src/media/audio/ringbufferpool.cpp index 01086b47b0351e07d9195d04d8834588f2c35a9a..bf7630c25f56c91bab8377f686a4311161f1aada 100644 --- a/src/media/audio/ringbufferpool.cpp +++ b/src/media/audio/ringbufferpool.cpp @@ -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 diff --git a/src/media/audio/ringbufferpool.h b/src/media/audio/ringbufferpool.h index f4fc5e91ef0223c4035443570e794a5db07d79b6..4234406f7674dc082f78d273274818a6430bdd90 100644 --- a/src/media/audio/ringbufferpool.h +++ b/src/media/audio/ringbufferpool.h @@ -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);