From 216714bd3303d9a260ecef9ad7be74b5550c7cab Mon Sep 17 00:00:00 2001 From: badaix Date: Wed, 5 May 2021 21:59:36 +0200 Subject: [PATCH] Add silence threshold for alsa stream --- common/message/wire_chunk.hpp | 6 ++++ doc/configuration.md | 3 +- server/etc/snapserver.conf | 2 +- server/streamreader/alsa_stream.cpp | 50 +++++++++++++++++++++++++++-- server/streamreader/alsa_stream.hpp | 4 +++ 5 files changed, 61 insertions(+), 4 deletions(-) diff --git a/common/message/wire_chunk.hpp b/common/message/wire_chunk.hpp index 028ea5ea..75f00ef6 100644 --- a/common/message/wire_chunk.hpp +++ b/common/message/wire_chunk.hpp @@ -77,6 +77,12 @@ public: uint32_t payloadSize; char* payload; + template + std::pair getPayload() const + { + return std::make_pair(reinterpret_cast(payload), payloadSize / sizeof(T)); + } + protected: void doserialize(std::ostream& stream) const override { diff --git a/doc/configuration.md b/doc/configuration.md index 35a74a9f..c4f286bb 100644 --- a/doc/configuration.md +++ b/doc/configuration.md @@ -142,13 +142,14 @@ output = audioresample ! audioconvert ! audio/x-raw,rate=48000,channels=2,format Captures audio from an alsa device ```sh -alsa://?name=&device=[&send_silence=false][&idle_threshold=100] +alsa://?name=&device=[&send_silence=false][&idle_threshold=100][&silence_threshold_percent=0.0] ``` #### Available parameters - `device`: alsa device name or identifier, e.g. `default` or `hw:0,0` or `hw:0,0,0` - `idle_threshold`: switch stream state from playing to idle after receiving `idle_threshold` milliseconds of silence +- `silence_threshold_percent`: percent (float) of the max amplitude to be considered as silence - `send_silence`: forward silence to clients when stream state is `idle` The output of any audio player that uses alsa can be redirected to Snapcast by using an alsa loopback device: diff --git a/server/etc/snapserver.conf b/server/etc/snapserver.conf index 8a34b53d..9d05bc23 100644 --- a/server/etc/snapserver.conf +++ b/server/etc/snapserver.conf @@ -125,7 +125,7 @@ doc_root = /usr/share/snapserver/snapweb # sampleformat will be set to "44100:16:2" # tcp server: tcp://:?name=[&mode=server] # tcp client: tcp://:?name=&mode=client -# alsa: alsa://?name=&device=[&send_silence=false][&idle_threshold=100] +# alsa: alsa://?name=&device=[&send_silence=false][&idle_threshold=100][&silence_threshold_percent=0.0] # meta: meta://///.../?name= source = pipe:///tmp/snapfifo?name=default #source = tcp://127.0.0.1?name=mopidy_tcp diff --git a/server/streamreader/alsa_stream.cpp b/server/streamreader/alsa_stream.cpp index 4ed68066..6a4867bd 100644 --- a/server/streamreader/alsa_stream.cpp +++ b/server/streamreader/alsa_stream.cpp @@ -71,7 +71,18 @@ AlsaStream::AlsaStream(PcmListener* pcmListener, boost::asio::io_context& ioc, c device_ = uri_.getQuery("device", "hw:0"); send_silence_ = (uri_.getQuery("send_silence", "false") == "true"); idle_threshold_ = std::chrono::milliseconds(std::max(cpt::stoi(uri_.getQuery("idle_threshold", "100")), 10)); - LOG(DEBUG, LOG_TAG) << "Device: " << device_ << "\n"; + double silence_threshold_percent = 0.; + try + { + silence_threshold_percent = cpt::stod(uri_.getQuery("silence_threshold_percent", "0")); + } + catch (...) + { + } + int32_t max_amplitude = std::pow(2, sampleFormat_.bits() - 1) - 1; + silence_threshold_ = max_amplitude * (silence_threshold_percent / 100.); + LOG(DEBUG, LOG_TAG) << "Device: " << device_ << ", silence threshold percent: " << silence_threshold_percent + << ", silence threshold amplitude: " << silence_threshold_ << "\n"; } @@ -166,6 +177,41 @@ void AlsaStream::uninitAlsa() } +bool AlsaStream::isSilent(const msg::PcmChunk& chunk) const +{ + if (silence_threshold_ == 0) + return (std::memcmp(chunk.payload, silent_chunk_.data(), silent_chunk_.size()) == 0); + + if (sampleFormat_.sampleSize() == 1) + { + auto payload = chunk.getPayload(); + for (size_t n = 0; n < payload.second; ++n) + { + if (abs(payload.first[n]) > silence_threshold_) + return false; + } + } + else if (sampleFormat_.sampleSize() == 2) + { + auto payload = chunk.getPayload(); + for (size_t n = 0; n < payload.second; ++n) + { + if (abs(payload.first[n]) > silence_threshold_) + return false; + } + } + else if (sampleFormat_.sampleSize() == 4) + { + auto payload = chunk.getPayload(); + for (size_t n = 0; n < payload.second; ++n) + { + if (abs(payload.first[n]) > silence_threshold_) + return false; + } + } + return true; +} + void AlsaStream::do_read() { try @@ -210,7 +256,7 @@ void AlsaStream::do_read() } } while (len < toRead); - if (std::memcmp(chunk_->payload, silent_chunk_.data(), silent_chunk_.size()) == 0) + if (isSilent(*chunk_)) { silence_ += chunk_->duration(); if (silence_ > idle_threshold_) diff --git a/server/streamreader/alsa_stream.hpp b/server/streamreader/alsa_stream.hpp index af7fa234..d0e78bea 100644 --- a/server/streamreader/alsa_stream.hpp +++ b/server/streamreader/alsa_stream.hpp @@ -47,6 +47,9 @@ protected: void initAlsa(); void uninitAlsa(); + /// check if the chunk's volume is below the silence threshold + bool isSilent(const msg::PcmChunk& chunk) const; + snd_pcm_t* handle_; std::unique_ptr chunk_; bool first_; @@ -61,6 +64,7 @@ protected: bool send_silence_; /// silence duration before switching the stream to idle std::chrono::milliseconds idle_threshold_; + int32_t silence_threshold_ = 0; }; } // namespace streamreader