diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index d16c8f91..9a51392d 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -5,7 +5,8 @@ set(CLIENT_SOURCES stream.cpp time_provider.cpp decoder/pcm_decoder.cpp - player/player.cpp) + player/player.cpp + player/file_player.cpp) set(CLIENT_LIBRARIES ${CMAKE_THREAD_LIBS_INIT} ${ATOMIC_LIBRARY} common) diff --git a/client/Makefile b/client/Makefile index b4508e13..634c011a 100644 --- a/client/Makefile +++ b/client/Makefile @@ -44,7 +44,7 @@ endif CXXFLAGS += $(ADD_CFLAGS) -std=c++14 -Wall -Wextra -Wpedantic -Wno-unused-function -DBOOST_ERROR_CODE_HEADER_ONLY -DHAS_FLAC -DHAS_OGG -DHAS_OPUS -DVERSION=\"$(VERSION)\" -I. -I.. -I../common LDFLAGS += $(ADD_LDFLAGS) -logg -lFLAC -lopus -lsoxr -OBJ = snapclient.o stream.o client_connection.o time_provider.o player/player.o decoder/pcm_decoder.o decoder/ogg_decoder.o decoder/flac_decoder.o decoder/opus_decoder.o controller.o ../common/sample_format.o +OBJ = snapclient.o stream.o client_connection.o time_provider.o player/player.o player/file_player.o ecoder/pcm_decoder.o decoder/ogg_decoder.o decoder/flac_decoder.o decoder/opus_decoder.o controller.o ../common/sample_format.o ifneq (,$(TARGET)) diff --git a/client/controller.cpp b/client/controller.cpp index 48755bf4..368fa17c 100644 --- a/client/controller.cpp +++ b/client/controller.cpp @@ -47,6 +47,7 @@ #ifdef HAS_WASAPI #include "player/wasapi_player.h" #endif +#include "player/file_player.hpp" #include "browseZeroConf/browse_mdns.hpp" #include "common/aixlog.hpp" @@ -168,6 +169,9 @@ void Controller::getNextMessage() if (!player_) player_ = createPlayer(settings_.player, "wasapi"); #endif + if (!player_ && (settings_.player.player_name == "file")) + player_ = createPlayer(settings_.player, "file"); + if (!player_) throw SnapException("No audio player support"); diff --git a/client/player/file_player.cpp b/client/player/file_player.cpp new file mode 100644 index 00000000..f919e53a --- /dev/null +++ b/client/player/file_player.cpp @@ -0,0 +1,99 @@ +/*** + This file is part of snapcast + Copyright (C) 2014-2020 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 . +***/ + +#include +#include + +#include "common/aixlog.hpp" +#include "common/snap_exception.hpp" +#include "common/str_compat.hpp" +#include "file_player.hpp" + +using namespace std; + +static constexpr auto LOG_TAG = "FilePlayer"; +static constexpr auto kDefaultBuffer = 50ms; + + +FilePlayer::FilePlayer(boost::asio::io_context& io_context, const ClientSettings::Player& settings, std::shared_ptr stream) + : Player(io_context, settings, stream), timer_(io_context) +{ +} + + +FilePlayer::~FilePlayer() +{ + LOG(DEBUG, LOG_TAG) << "Destructor\n"; + stop(); +} + + +bool FilePlayer::needsThread() const +{ + return false; +} + + +void FilePlayer::requestAudio() +{ + auto numFrames = static_cast(stream_->getFormat().msRate() * kDefaultBuffer.count()); + auto needed = numFrames * stream_->getFormat().frameSize(); + if (buffer_.size() < needed) + buffer_.resize(needed); + + if (!stream_->getPlayerChunk(buffer_.data(), 100ms, numFrames)) + { + // LOG(INFO, LOG_TAG) << "Failed to get chunk. Playing silence.\n"; + memset(buffer_.data(), 0, needed); + } + else + { + adjustVolume(static_cast(buffer_.data()), numFrames); + } + loop(); +} + + +void FilePlayer::loop() +{ + next_request_ += kDefaultBuffer; + auto now = std::chrono::steady_clock::now(); + if (next_request_ < now) + next_request_ = now + 1ms; + + timer_.expires_at(next_request_); + timer_.async_wait([this](boost::system::error_code ec) { + if (ec) + return; + requestAudio(); + }); +} + + +void FilePlayer::start() +{ + next_request_ = std::chrono::steady_clock::now(); + loop(); +} + + +void FilePlayer::stop() +{ + LOG(INFO, LOG_TAG) << "Stop\n"; + timer_.cancel(); +} diff --git a/client/player/file_player.hpp b/client/player/file_player.hpp new file mode 100644 index 00000000..aadb697b --- /dev/null +++ b/client/player/file_player.hpp @@ -0,0 +1,47 @@ +/*** + This file is part of snapcast + Copyright (C) 2014-2020 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 FILE_PLAYER_HPP +#define FILE_PLAYER_HPP + +#include "player.hpp" + + +/// File Player +/// Used for testing and doesn't even write the received audio to file at the moment, +/// but just discards it +class FilePlayer : public Player +{ +public: + FilePlayer(boost::asio::io_context& io_context, const ClientSettings::Player& settings, std::shared_ptr stream); + virtual ~FilePlayer(); + + void start() override; + void stop() override; + +protected: + void requestAudio(); + void loop(); + bool needsThread() const override; + boost::asio::steady_timer timer_; + std::vector buffer_; + std::chrono::time_point next_request_; +}; + + +#endif diff --git a/client/snapclient.cpp b/client/snapclient.cpp index e82d5cfc..fbdbcd0e 100644 --- a/client/snapclient.cpp +++ b/client/snapclient.cpp @@ -139,7 +139,9 @@ int main(int argc, char** argv) /*auto instanceValue =*/op.add>("i", "instance", "instance id", 1, &settings.instance); /*auto hostIdValue =*/op.add>("", "hostID", "unique host id", "", &settings.host_id); #if defined(HAS_OBOE) && defined(HAS_OPENSL) - op.add>("", "player", "audio backend", "", &settings.player.player_name); + op.add>("", "player", "audio backend (oboe, opensl)", "oboe", &settings.player.player_name); +#else + op.add, Attribute::hidden>("", "player", "audio backend (, file)", "", &settings.player.player_name); #endif #ifdef HAS_SOXR auto sample_format = op.add>("", "sampleformat", "resample audio stream to ::", "");