Implemented volume control for jukebox

This commit is contained in:
emeric
2026-02-18 00:12:32 +01:00
parent 5f6d3e796a
commit 7a30bb98f6
9 changed files with 137 additions and 51 deletions
@@ -238,6 +238,15 @@ namespace lms::audio::alsa
return ::snd_pcm_state(_pcm.get()) == SND_PCM_STATE_PAUSED;
}
void AudioOutputStream::setVolume(float)
{
}
float AudioOutputStream::getVolume() const
{
return 1.F;
}
std::chrono::microseconds AudioOutputStream::getPlaybackTime() const
{
::snd_pcm_sframes_t delayFrames{};
@@ -52,15 +52,18 @@ namespace lms::audio::alsa
void asyncWrite(std::span<const std::byte> buffer, WriteCompletionCallback cb) override;
void asyncDrain(DrainCompletionCallback cb) override;
void pause() override;
void resume() override;
bool isPaused() const override;
std::chrono::microseconds getPlaybackTime() const override;
std::chrono::microseconds getLatency() const override;
void flush() override;
void pause() override;
void resume() override;
bool isPaused() const override;
void setVolume(float volume) override;
float getVolume() const override;
void stop();
void setupAllDescriptors();
void releaseAllDescriptors();
@@ -19,10 +19,13 @@
#include "AudioOutputStream.hpp"
#include <algorithm>
#include <boost/asio/post.hpp>
#include <pulse/context.h>
#include <pulse/def.h>
#include <pulse/error.h>
#include <pulse/introspect.h>
#include <pulse/operation.h>
#include <pulse/proplist.h>
#include <pulse/stream.h>
@@ -195,47 +198,6 @@ namespace lms::audio::pulseaudio
}
}
void AudioOutputStream::pause()
{
LMS_LOG(AUDIO_OUTPUT_STREAM, DEBUG, "Pausing stream");
MainLoopScopedLock lock{ _mainLoop };
pa_operation* op{ ::pa_stream_cork(_stream.get(), 1, nullptr, nullptr) };
if (!op)
throw PaException("pa_stream_cork (pause) failed", pa_context_errno(_context));
::pa_operation_unref(op);
}
void AudioOutputStream::resume()
{
LMS_LOG(AUDIO_OUTPUT_STREAM, DEBUG, "Resuming stream");
MainLoopScopedLock lock{ _mainLoop };
{
pa_operation* op{ ::pa_stream_cork(_stream.get(), 0, nullptr, nullptr) };
if (!op)
throw PaException("pa_stream_cork (resume) failed", pa_context_errno(_context));
::pa_operation_unref(op);
}
{
pa_operation* op{ ::pa_stream_trigger(_stream.get(), NULL, NULL) };
if (!op)
throw PaException("pa_stream_trigger failed", pa_context_errno(_context));
::pa_operation_unref(op);
}
}
bool AudioOutputStream::isPaused() const
{
MainLoopScopedLock lock{ _mainLoop };
return pa_stream_is_corked(_stream.get());
}
std::chrono::microseconds AudioOutputStream::getPlaybackTime() const
{
pa_usec_t duration{};
@@ -279,6 +241,78 @@ namespace lms::audio::pulseaudio
}
}
void AudioOutputStream::pause()
{
LMS_LOG(AUDIO_OUTPUT_STREAM, DEBUG, "Pausing stream");
MainLoopScopedLock lock{ _mainLoop };
pa_operation* op{ ::pa_stream_cork(_stream.get(), 1, nullptr, nullptr) };
if (!op)
throw PaException("pa_stream_cork (pause) failed", pa_context_errno(_context));
::pa_operation_unref(op);
}
void AudioOutputStream::resume()
{
LMS_LOG(AUDIO_OUTPUT_STREAM, DEBUG, "Resuming stream");
MainLoopScopedLock lock{ _mainLoop };
{
pa_operation* op{ ::pa_stream_cork(_stream.get(), 0, nullptr, nullptr) };
if (!op)
throw PaException("pa_stream_cork (resume) failed", pa_context_errno(_context));
::pa_operation_unref(op);
}
{
pa_operation* op{ ::pa_stream_trigger(_stream.get(), NULL, NULL) };
if (!op)
throw PaException("pa_stream_trigger failed", pa_context_errno(_context));
::pa_operation_unref(op);
}
}
bool AudioOutputStream::isPaused() const
{
MainLoopScopedLock lock{ _mainLoop };
return pa_stream_is_corked(_stream.get());
}
void AudioOutputStream::setVolume(float volume)
{
MainLoopScopedLock lock{ _mainLoop };
if (volume < 0.F || volume > 1.F)
throw Exception{ "Volume must be in range 0-1" };
const pa_volume_t paVol{ pa_sw_volume_from_linear(volume) };
pa_cvolume paChanVol;
pa_cvolume_set(&paChanVol, _outputParameters.channelCount, paVol);
pa_operation* op{ ::pa_context_set_sink_input_volume(
_context,
pa_stream_get_index(_stream.get()),
&paChanVol,
nullptr,
nullptr) };
if (!op)
throw PaException("pa_context_set_sink_input_volume failed", pa_context_errno(_context));
::pa_operation_unref(op);
_currentVolume = volume;
}
float AudioOutputStream::getVolume() const
{
return _currentVolume;
}
void AudioOutputStream::connect()
{
constexpr pa_stream_flags_t flags{ static_cast<pa_stream_flags_t>(
@@ -62,15 +62,18 @@ namespace lms::audio::pulseaudio
void asyncWrite(std::span<const std::byte> buffer, WriteCompletionCallback cb) override;
void asyncDrain(DrainCompletionCallback cb) override;
void pause() override;
void resume() override;
bool isPaused() const override;
std::chrono::microseconds getPlaybackTime() const override;
std::chrono::microseconds getLatency() const override;
void flush() override;
void pause() override;
void resume() override;
bool isPaused() const override;
void setVolume(float volume) override;
float getVolume() const override;
void connect();
void onStateChanged();
void onWriteRequested(std::size_t writableSize);
@@ -83,6 +86,7 @@ namespace lms::audio::pulseaudio
pa_threaded_mainloop* _mainLoop;
const PcmParameters _outputParameters;
PaStreamPtr _stream;
float _currentVolume{ 1.F };
WaitReadyCallback _waitReadyCallback;
@@ -60,6 +60,9 @@ namespace lms::audio
virtual void pause() = 0;
virtual void resume() = 0;
virtual bool isPaused() const = 0;
virtual void setVolume(float volume) = 0;
virtual float getVolume() const = 0;
};
class IAudioOutputContext
@@ -118,6 +118,20 @@ namespace lms::jukebox
return _outputStream->isPaused();
}
void JukeboxService::setVolume(float volume)
{
std::unique_lock lock{ _mutex };
_outputStream->setVolume(volume);
}
float JukeboxService::getVolume() const
{
std::shared_lock lock{ _mutex };
return _outputStream->getVolume();
}
std::optional<std::size_t> JukeboxService::getCurrentTrackIndex() const
{
std::shared_lock lock{ _mutex };
@@ -51,6 +51,9 @@ namespace lms::jukebox
void resume() override;
bool isPaused() const override;
void setVolume(float volume) override;
float getVolume() const override;
std::optional<std::size_t> getCurrentTrackIndex() const override;
std::chrono::microseconds getPlaybackTrackTime() const override;
@@ -48,6 +48,9 @@ namespace lms::jukebox
virtual void resume() = 0;
virtual bool isPaused() const = 0;
virtual void setVolume(float volume) = 0; // from 0 to 1
virtual float getVolume() const = 0;
virtual std::optional<std::size_t> getCurrentTrackIndex() const = 0; // may be unset if queue is cleared while playing
virtual std::chrono::microseconds getPlaybackTrackTime() const = 0;
+15 -2
View File
@@ -44,7 +44,7 @@ namespace lms::api::subsonic
statusNode.setAttribute("currentIndex", jukeboxService.getCurrentTrackIndex() ? *jukeboxService.getCurrentTrackIndex() : -1); // required
statusNode.setAttribute("playing", !jukeboxService.isPaused()); // required
statusNode.setAttribute("position", std::chrono::duration_cast<std::chrono::seconds>(jukeboxService.getPlaybackTrackTime()).count());
statusNode.setAttribute("gain", 1.f);
statusNode.setAttribute("gain", jukeboxService.getVolume());
return statusNode;
}
@@ -158,6 +158,19 @@ namespace lms::api::subsonic
return response;
}
Response handleJukeboxSetGain(RequestContext& context, jukebox::IJukeboxService& jukeboxService)
{
const auto gain{ getMandatoryParameterAs<float>(context.getParameters(), "gain") };
if (gain < 0 || gain > 1)
throw BadParameterGenericError{ "gain", "gain must be between 0.0 and 1.0" };
jukeboxService.setVolume(gain); // consider gain is linear
Response response{ Response::createOkResponse(context.getServerProtocolVersion()) };
response.addNode("jukeboxStatus", createJukeboxStatusNode(jukeboxService));
return response;
}
using Actionhandler = std::function<Response(RequestContext& context, jukebox::IJukeboxService& jukeboxService)>;
static const std::unordered_map<std::string, Actionhandler> actionHandlers{
{ "get", detail::handleJukeboxGet },
@@ -170,7 +183,7 @@ namespace lms::api::subsonic
{ "clear", detail::handleJukeboxClear },
{ "remove", detail::handleJukeboxRemove },
{ "shuffle", detail::handleJukeboxShuffle },
{ "setGain", detail::handleJukeboxStatus }, // not implemented
{ "setGain", detail::handleJukeboxSetGain },
};
} // namespace detail