diff --git a/doc/configuration.md b/doc/configuration.md index a332e9f0..64427998 100644 --- a/doc/configuration.md +++ b/doc/configuration.md @@ -35,7 +35,6 @@ Supported parameters for all source types: - `codec`: Override the global codec - `sampleformat`: Override the global sample format - `chunk_ms`: Override the global `chunk_ms` -- `dryout_ms`: Supported by blocking sources: when no new data is read from the source, send silence to the clients - `controlscript`: Script to control the stream source and read and provide meta data, see [stream_plugin.md](json_rpc_api/stream_plugin.md) - `controlscriptparams`: Control script command line arguments, must be url-encoded (use `%20` instead of a space " "), e.g. `--mopidy-host=192.168.42.23%20--debug` @@ -46,7 +45,7 @@ Available audio source types are: Captures audio from a named pipe ```sh -pipe:///?name=[&mode=create][&dryout_ms=2000] +pipe:///?name=[&mode=create] ``` `mode` can be `create` or `read`. Sometimes your audio source might insist in creating the pipe itself. So the pipe creation mode can by changed to "not create, but only read mode", using the `mode` option set to `read` @@ -60,7 +59,7 @@ See [stackexchange](https://unix.stackexchange.com/questions/503111/group-permis Launches librespot and reads audio from stdout ```sh -librespot:///?name=[&dryout_ms=2000][&username=&password=][&devicename=Snapcast][&bitrate=320][&wd_timeout=7800][&volume=100][&onevent=""][&normalize=false][&autoplay=false][&cache=""][&disable_audio_cache=false][&killall=false][¶ms=extra-params] +librespot:///?name=[&username=&password=][&devicename=Snapcast][&bitrate=320][&wd_timeout=7800][&volume=100][&onevent=""][&normalize=false][&autoplay=false][&cache=""][&disable_audio_cache=false][&killall=false][¶ms=extra-params] ``` Note that you need to have the librespot binary on your machine and the sampleformat will be set to `44100:16:2` @@ -91,7 +90,7 @@ Parameters introduced by Snapclient: Launches [shairport-sync](https://github.com/mikebrady/shairport-sync) and reads audio from stdout ```sh -airplay:///?name=[&dryout_ms=2000][&devicename=Snapcast][&port=5000][&password=] +airplay:///?name=[&devicename=Snapcast][&port=5000][&password=] ``` Note that you need to have the shairport-sync binary on your machine and the sampleformat will be set to `44100:16:2` @@ -118,7 +117,7 @@ file:///?name= Launches a process and reads audio from stdout ```sh -process:///?name=[&dryout_ms=2000][&wd_timeout=0][&log_stderr=false][¶ms=] +process:///?name=[&wd_timeout=0][&log_stderr=false][¶ms=] ``` #### Available parameters diff --git a/server/etc/snapserver.conf b/server/etc/snapserver.conf index 0afa9fe8..5f2e30c2 100644 --- a/server/etc/snapserver.conf +++ b/server/etc/snapserver.conf @@ -118,15 +118,14 @@ doc_root = /usr/share/snapserver/snapweb # parameters have the form "key=value", they are concatenated with an "&" character # parameter "name" is mandatory for all sources, while codec, sampleformat and chunk_ms are optional # and will override the default codec, sampleformat or chunk_ms settings -# Blocking sources support the dryout_ms parameter: when no new data is read from the source, send silence to the clients # Available types are: -# pipe: pipe:///?name=[&mode=create][&dryout_ms=2000], mode can be "create" or "read" -# librespot: librespot:///?name=[&dryout_ms=2000][&username=&password=][&devicename=Snapcast][&bitrate=320][&wd_timeout=7800][&volume=100][&onevent=""][&nomalize=false][&autoplay=false][¶ms=] +# pipe: pipe:///?name=[&mode=create], mode can be "create" or "read" +# librespot: librespot:///?name=[&username=&password=][&devicename=Snapcast][&bitrate=320][&wd_timeout=7800][&volume=100][&onevent=""][&nomalize=false][&autoplay=false][¶ms=] # note that you need to have the librespot binary on your machine # sampleformat will be set to "44100:16:2" # file: file:///?name= -# process: process:///?name=[&dryout_ms=2000][&wd_timeout=0][&log_stderr=false][¶ms=] -# airplay: airplay:///?name=[&dryout_ms=2000][&port=5000] +# process: process:///?name=[&wd_timeout=0][&log_stderr=false][¶ms=] +# airplay: airplay:///?name=[&port=5000] # note that you need to have the airplay binary on your machine # sampleformat will be set to "44100:16:2" # tcp server: tcp://:?name=[&mode=server] @@ -134,7 +133,6 @@ doc_root = /usr/share/snapserver/snapweb # 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 # Default sample format: :: #sampleformat = 48000:16:2 diff --git a/server/streamreader/alsa_stream.cpp b/server/streamreader/alsa_stream.cpp index f16c546a..b991e2ef 100644 --- a/server/streamreader/alsa_stream.cpp +++ b/server/streamreader/alsa_stream.cpp @@ -83,9 +83,6 @@ void AlsaStream::start() { LOG(DEBUG, LOG_TAG) << "Start, sampleformat: " << sampleFormat_.toString() << "\n"; - // idle_bytes_ = 0; - // max_idle_bytes_ = sampleFormat_.rate() * sampleFormat_.frameSize() * dryout_ms_ / 1000; - initAlsa(); first_ = true; tvEncodedChunk_ = std::chrono::steady_clock::now(); @@ -263,7 +260,6 @@ void AlsaStream::do_read() LOG(INFO, LOG_TAG) << "No data availabale, playing silence.\n"; // no data available, fill with silence memset(chunk_->payload + len, 0, toRead - len); - // idle_bytes_ += toRead - len; break; } else if (count == 0) diff --git a/server/streamreader/posix_stream.cpp b/server/streamreader/posix_stream.cpp deleted file mode 100644 index c0bea2a5..00000000 --- a/server/streamreader/posix_stream.cpp +++ /dev/null @@ -1,177 +0,0 @@ -/*** - This file is part of snapcast - Copyright (C) 2014-2021 Johannes Pohl - - 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 3 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 . -***/ - -// prototype/interface header file -#include "posix_stream.hpp" - -// local headers -#include "common/aixlog.hpp" -#include "common/snap_exception.hpp" -#include "common/str_compat.hpp" - -// standard headers -#include -#include - - -using namespace std; -using namespace std::chrono_literals; - -namespace streamreader -{ - -static constexpr auto LOG_TAG = "PosixStream"; -static constexpr auto kResyncTolerance = 50ms; - -PosixStream::PosixStream(PcmStream::Listener* pcmListener, boost::asio::io_context& ioc, const ServerSettings& server_settings, const StreamUri& uri) - : AsioStream(pcmListener, ioc, server_settings, uri) -{ - if (uri_.query.find("dryout_ms") != uri_.query.end()) - dryout_ms_ = cpt::stoul(uri_.query["dryout_ms"]); - else - dryout_ms_ = 2000; -} - - -void PosixStream::connect() -{ - if (!active_) - return; - - idle_bytes_ = 0; - max_idle_bytes_ = sampleFormat_.rate() * sampleFormat_.frameSize() * dryout_ms_ / 1000; - - try - { - do_connect(); - } - catch (const std::exception& e) - { - LOG(ERROR, LOG_TAG) << "Connect exception: " << e.what() << "\n"; - wait(read_timer_, 100ms, [this] { connect(); }); - } -} - - -void PosixStream::do_disconnect() -{ - if (stream_ && stream_->is_open()) - stream_->close(); -} - - -void PosixStream::do_read() -{ - try - { - if (!stream_->is_open()) - throw SnapException("failed to open stream: \"" + uri_.path + "\""); - - if (first_) - { - LOG(TRACE, LOG_TAG) << "First read, initializing nextTick to now\n"; - nextTick_ = std::chrono::steady_clock::now(); - } - - int toRead = chunk_->payloadSize; - auto duration = chunk_->duration(); - int len = 0; - do - { - int count = read(stream_->native_handle(), chunk_->payload + len, toRead - len); - if (count < 0) - { - // no data available, fill with silence - memset(chunk_->payload + len, 0, toRead - len); - - // avoid overflow after 186min 24s silence (at 48000:16:2) - if (idle_bytes_ <= max_idle_bytes_) - idle_bytes_ += toRead - len; - break; - } - else if (count == 0) - { - throw SnapException("end of file"); - } - else - { - // LOG(DEBUG, LOG_TAG) << "count: " << count << "\n"; - len += count; - bytes_read_ += len; - idle_bytes_ = 0; - } - } while (len < toRead); - - // LOG(DEBUG, LOG_TAG) << "Received " << len << "/" << toRead << " bytes\n"; - if (first_) - { - first_ = false; - // initialize the stream's base timestamp to now minus the chunk's duration - tvEncodedChunk_ = std::chrono::steady_clock::now() - duration; - } - - if ((idle_bytes_ == 0) || (idle_bytes_ <= max_idle_bytes_)) - { - // the encoder will update the tvEncodedChunk when a chunk is encoded - chunkRead(*chunk_); - } - else - { - // no data available - // set first_ = true will cause the timestamps to be updated without encoding - first_ = true; - } - - nextTick_ += duration; - auto currentTick = std::chrono::steady_clock::now(); - auto next_read = nextTick_ - currentTick; - if (next_read >= 0ms) - { - // synchronize reads to an interval of chunk_ms_ - wait(read_timer_, nextTick_ - currentTick, [this] { do_read(); }); - return; - } - else if (next_read >= -kResyncTolerance) - { - LOG(INFO, LOG_TAG) << "next read < 0 (" << getName() << "): " << std::chrono::duration_cast(next_read).count() / 1000. - << " ms\n"; - boost::asio::post(strand_, [this] { do_read(); }); - } - else - { - // reading chunk_ms_ took longer than chunk_ms_ - resync(-next_read); - first_ = true; - wait(read_timer_, duration + kResyncTolerance, [this] { do_read(); }); - } - - lastException_ = ""; - } - catch (const std::exception& e) - { - if (lastException_ != e.what()) - { - LOG(ERROR, LOG_TAG) << "Exception: " << e.what() << std::endl; - lastException_ = e.what(); - } - disconnect(); - wait(read_timer_, 100ms, [this] { connect(); }); - } -} - -} // namespace streamreader diff --git a/server/streamreader/posix_stream.hpp b/server/streamreader/posix_stream.hpp deleted file mode 100644 index ee103378..00000000 --- a/server/streamreader/posix_stream.hpp +++ /dev/null @@ -1,55 +0,0 @@ -/*** - This file is part of snapcast - Copyright (C) 2014-2021 Johannes Pohl - - 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 3 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 . -***/ - -#ifndef POSIX_STREAM_HPP -#define POSIX_STREAM_HPP - -// local headers -#include "asio_stream.hpp" - -namespace streamreader -{ - -using boost::asio::posix::stream_descriptor; - - -/// Reads and decodes PCM data from a file descriptor -/** - * Reads PCM from a file descriptor and passes the data to an encoder. - * Implements EncoderListener to get the encoded data. - * Data is passed to the PcmStream::Listener - */ -class PosixStream : public AsioStream -{ -public: - /// ctor. Encoded PCM data is passed to the PipeListener - PosixStream(PcmStream::Listener* pcmListener, boost::asio::io_context& ioc, const ServerSettings& server_settings, const StreamUri& uri); - -protected: - void connect() override; - void do_disconnect() override; - void do_read() override; - std::string lastException_; - size_t dryout_ms_; - int idle_bytes_; - int max_idle_bytes_; -}; - -} // namespace streamreader - -#endif