Compare commits
25 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
f084bf7872 | ||
![]() |
1112d3907a | ||
![]() |
3464497880 | ||
![]() |
651f57bced | ||
![]() |
b4e72aba6c | ||
![]() |
061dd2dfef | ||
![]() |
5f4ec7de5b | ||
![]() |
6f81bb4b09 | ||
![]() |
4ed60a5711 | ||
![]() |
c93195c94b | ||
![]() |
f30adac4bb | ||
![]() |
a4e4217204 | ||
![]() |
8754d705a1 | ||
![]() |
23d4a2d6a5 | ||
![]() |
ce77b148d9 | ||
![]() |
be3eca39e8 | ||
![]() |
3413b1aeb4 | ||
![]() |
356d13e9dd | ||
![]() |
fa34bf0aaf | ||
![]() |
5d0941476a | ||
![]() |
5ff0bbd0f8 | ||
![]() |
a3764e533c | ||
![]() |
3e05cba30e | ||
![]() |
14b3c0f0af | ||
![]() |
67aff05051 |
NEWS
android
doc
meson.buildpython/build
src
CommandLine.cxxCommandLine.hxxMain.cxx
command
db
decoder
plugins
event
input
plugins
lib
alsa
ffmpeg
fmt
upnp
mixer
plugins
neighbor
plugins
output
systemd
win32
14
NEWS
14
NEWS
@@ -1,3 +1,17 @@
|
||||
ver 0.23.4 (2021/11/11)
|
||||
* protocol
|
||||
- add optional position parameter to "searchaddpl"
|
||||
* decoder
|
||||
- ffmpeg: support libavcodec 59
|
||||
* output
|
||||
- alsa: add option "thesycon_dsd_workaround" to work around device bug
|
||||
* fix crash on debug builds if startup fails
|
||||
* systemd
|
||||
- remove "RuntimeDirectory" directive because it caused problems
|
||||
- ignore the "pid_file" setting if started as systemd service
|
||||
* Windows
|
||||
- enable the "openmpt" decoder plugin
|
||||
|
||||
ver 0.23.3 (2021/10/31)
|
||||
* protocol
|
||||
- add optional position parameter to "add" and "playlistadd"
|
||||
|
@@ -2,8 +2,8 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="org.musicpd"
|
||||
android:installLocation="auto"
|
||||
android:versionCode="63"
|
||||
android:versionName="0.23.3">
|
||||
android:versionCode="64"
|
||||
android:versionName="0.23.4">
|
||||
|
||||
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="29"/>
|
||||
|
||||
|
@@ -38,7 +38,7 @@ author = 'Max Kellermann'
|
||||
# built documents.
|
||||
#
|
||||
# The short X.Y version.
|
||||
version = '0.23.3'
|
||||
version = '0.23.4'
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
#release = version + '~git'
|
||||
|
||||
|
@@ -26,22 +26,25 @@
|
||||
# files over an accepted protocol.
|
||||
#
|
||||
#db_file "~/.mpd/database"
|
||||
#
|
||||
|
||||
# These settings are the locations for the daemon log files for the daemon.
|
||||
# These logs are great for troubleshooting, depending on your log_level
|
||||
# settings.
|
||||
#
|
||||
# The special value "syslog" makes MPD use the local syslog daemon. This
|
||||
# setting defaults to logging to syslog.
|
||||
#
|
||||
#log_file "~/.mpd/log"
|
||||
# If you use systemd, do not configure a log_file. With systemd, MPD
|
||||
# defaults to the systemd journal, which is fine.
|
||||
#
|
||||
#log_file "~/.mpd/log"
|
||||
|
||||
# This setting sets the location of the file which stores the process ID
|
||||
# for use of mpd --kill and some init scripts. This setting is disabled by
|
||||
# default and the pid file will not be stored.
|
||||
#
|
||||
#pid_file "~/.mpd/pid"
|
||||
# If you use systemd, do not configure a pid_file.
|
||||
#
|
||||
#pid_file "~/.mpd/pid"
|
||||
|
||||
# This setting sets the location of the file which contains information about
|
||||
# most variables to get MPD back into the same general shape it was in before
|
||||
# it was brought down. This setting is disabled by default and the server
|
||||
|
@@ -61,6 +61,15 @@ upnp
|
||||
|
||||
Provides access to UPnP media servers.
|
||||
|
||||
.. list-table::
|
||||
:widths: 20 80
|
||||
:header-rows: 1
|
||||
|
||||
* - Setting
|
||||
- Description
|
||||
* - **interface**
|
||||
- Interface used to discover media servers. Decided by upnp if left unconfigured.
|
||||
|
||||
Storage plugins
|
||||
===============
|
||||
|
||||
@@ -841,6 +850,11 @@ The `Advanced Linux Sound Architecture (ALSA) <http://www.alsa-project.org/>`_ p
|
||||
("stop" or "pause") in DSD mode (native DSD or DoP). This is a
|
||||
workaround for some DACs which emit noise when stopping DSD
|
||||
playback.
|
||||
* - **thesycon_dsd_workaround yes|no**
|
||||
- If enabled, enables a workaround for a bug in Thesycon USB
|
||||
audio receivers. On these devices, playing DSD512 or PCM
|
||||
causes all subsequent attempts to play other DSD rates to fail,
|
||||
which can be fixed by briefly playing PCM at 44.1 kHz.
|
||||
* - **allowed_formats F1 F2 ...**
|
||||
- Specifies a list of allowed audio formats, separated by a space. All items may contain asterisks as a wild card, and may be followed by "=dop" to enable DoP (DSD over PCM) for this particular format. The first matching format is used, and if none matches, MPD chooses the best fallback of this list.
|
||||
|
||||
|
@@ -1225,7 +1225,7 @@ The music database
|
||||
|
||||
.. _command_searchaddpl:
|
||||
|
||||
:command:`searchaddpl {NAME} {FILTER} [sort {TYPE}] [window {START:END}]`
|
||||
:command:`searchaddpl {NAME} {FILTER} [sort {TYPE}] [window {START:END}] [position POS]`
|
||||
Search the database for songs matching
|
||||
``FILTER`` (see :ref:`Filters <filter_syntax>`) and add them to
|
||||
the playlist named ``NAME``.
|
||||
@@ -1234,6 +1234,9 @@ The music database
|
||||
|
||||
Parameters have the same meaning as for :ref:`search <command_search>`.
|
||||
|
||||
The ``position`` parameter specifies where the songs will be
|
||||
inserted. [#since_0_23_4]_
|
||||
|
||||
.. _command_update:
|
||||
|
||||
:command:`update [URI]`
|
||||
@@ -1655,3 +1658,4 @@ client-to-client messages are local to the current partition.
|
||||
.. [#since_0_23] Since :program:`MPD` 0.23
|
||||
.. [#since_0_23_1] Since :program:`MPD` 0.23.1
|
||||
.. [#since_0_23_3] Since :program:`MPD` 0.23.3
|
||||
.. [#since_0_23_4] Since :program:`MPD` 0.23.4
|
||||
|
@@ -1,7 +1,7 @@
|
||||
project(
|
||||
'mpd',
|
||||
['c', 'cpp'],
|
||||
version: '0.23.3',
|
||||
version: '0.23.4',
|
||||
meson_version: '>= 0.56.0',
|
||||
default_options: [
|
||||
'c_std=c11',
|
||||
@@ -44,7 +44,7 @@ version_conf = configuration_data()
|
||||
version_conf.set_quoted('PACKAGE', meson.project_name())
|
||||
version_conf.set_quoted('PACKAGE_NAME', meson.project_name())
|
||||
version_conf.set_quoted('VERSION', meson.project_version())
|
||||
version_conf.set_quoted('PROTOCOL_VERSION', '0.23.3')
|
||||
version_conf.set_quoted('PROTOCOL_VERSION', '0.23.4')
|
||||
configure_file(output: 'Version.h', configuration: version_conf)
|
||||
|
||||
conf = configuration_data()
|
||||
|
@@ -116,8 +116,12 @@ libopenmpt = AutotoolsProject(
|
||||
'892aea7a599b5d21842bebf463b5aafdad5711be7008dd84401920c6234820af',
|
||||
'lib/libopenmpt.a',
|
||||
[
|
||||
'--disable-shared', '--enable-static'
|
||||
'--disable-shared', '--enable-static',
|
||||
'--disable-openmpt123',
|
||||
'--without-mpg123', '--without-ogg', '--without-vorbis', '--without-vorbisfile',
|
||||
'--without-portaudio', '--without-portaudiocpp', '--without-sndfile',
|
||||
],
|
||||
base='libopenmpt-0.5.12+release.autotools',
|
||||
)
|
||||
|
||||
wildmidi = CmakeProject(
|
||||
|
@@ -86,6 +86,9 @@ enum Option {
|
||||
OPTION_KILL,
|
||||
OPTION_NO_CONFIG,
|
||||
OPTION_NO_DAEMON,
|
||||
#ifdef __linux__
|
||||
OPTION_SYSTEMD,
|
||||
#endif
|
||||
OPTION_STDOUT,
|
||||
OPTION_STDERR,
|
||||
OPTION_VERBOSE,
|
||||
@@ -98,6 +101,9 @@ static constexpr OptionDef option_defs[] = {
|
||||
{"kill", "kill the currently running mpd session"},
|
||||
{"no-config", "don't read from config"},
|
||||
{"no-daemon", "don't detach from console"},
|
||||
#ifdef __linux__
|
||||
{"systemd", "systemd service mode"},
|
||||
#endif
|
||||
{"stdout", nullptr}, // hidden, compatibility with old versions
|
||||
{"stderr", "print messages to stderr"},
|
||||
{"verbose", 'v', "verbose logging"},
|
||||
@@ -328,7 +334,7 @@ bool ConfigLoader::TryFile(const AllocatedPath &base_path, Path path)
|
||||
}
|
||||
|
||||
void
|
||||
ParseCommandLine(int argc, char **argv, struct options &options,
|
||||
ParseCommandLine(int argc, char **argv, CommandLineOptions &options,
|
||||
ConfigData &config)
|
||||
{
|
||||
bool use_config_file = true;
|
||||
@@ -349,6 +355,13 @@ ParseCommandLine(int argc, char **argv, struct options &options,
|
||||
options.daemon = false;
|
||||
break;
|
||||
|
||||
#ifdef __linux__
|
||||
case OPTION_SYSTEMD:
|
||||
options.daemon = false;
|
||||
options.systemd = true;
|
||||
break;
|
||||
#endif
|
||||
|
||||
case OPTION_STDOUT:
|
||||
case OPTION_STDERR:
|
||||
options.log_stderr = true;
|
||||
|
@@ -22,15 +22,20 @@
|
||||
|
||||
struct ConfigData;
|
||||
|
||||
struct options {
|
||||
struct CommandLineOptions {
|
||||
bool kill = false;
|
||||
bool daemon = true;
|
||||
|
||||
#ifdef __linux__
|
||||
bool systemd = false;
|
||||
#endif
|
||||
|
||||
bool log_stderr = false;
|
||||
bool verbose = false;
|
||||
};
|
||||
|
||||
void
|
||||
ParseCommandLine(int argc, char **argv, struct options &options,
|
||||
ParseCommandLine(int argc, char **argv, CommandLineOptions &options,
|
||||
ConfigData &config);
|
||||
|
||||
#endif
|
||||
|
25
src/Main.cxx
25
src/Main.cxx
@@ -142,14 +142,24 @@ struct Config {
|
||||
#ifdef ENABLE_DAEMON
|
||||
|
||||
static void
|
||||
glue_daemonize_init(const struct options *options,
|
||||
glue_daemonize_init(const CommandLineOptions &options,
|
||||
const ConfigData &config)
|
||||
{
|
||||
auto pid_file = config.GetPath(ConfigOption::PID_FILE);
|
||||
|
||||
#ifdef __linux__
|
||||
if (options.systemd && pid_file != nullptr) {
|
||||
pid_file = nullptr;
|
||||
fprintf(stderr,
|
||||
"Ignoring the 'pid_file' setting in systemd mode\n");
|
||||
}
|
||||
#endif
|
||||
|
||||
daemonize_init(config.GetString(ConfigOption::USER),
|
||||
config.GetString(ConfigOption::GROUP),
|
||||
config.GetPath(ConfigOption::PID_FILE));
|
||||
std::move(pid_file));
|
||||
|
||||
if (options->kill)
|
||||
if (options.kill)
|
||||
daemonize_kill();
|
||||
}
|
||||
|
||||
@@ -361,7 +371,8 @@ Instance::BeginShutdownPartitions() noexcept
|
||||
}
|
||||
|
||||
static inline void
|
||||
MainConfigured(const struct options &options, const ConfigData &raw_config)
|
||||
MainConfigured(const CommandLineOptions &options,
|
||||
const ConfigData &raw_config)
|
||||
{
|
||||
#ifdef ENABLE_DAEMON
|
||||
daemonize_close_stdin();
|
||||
@@ -384,7 +395,7 @@ MainConfigured(const struct options &options, const ConfigData &raw_config)
|
||||
const Config config(raw_config);
|
||||
|
||||
#ifdef ENABLE_DAEMON
|
||||
glue_daemonize_init(&options, raw_config);
|
||||
glue_daemonize_init(options, raw_config);
|
||||
#endif
|
||||
|
||||
TagLoadConfig(raw_config);
|
||||
@@ -582,7 +593,7 @@ MainConfigured(const struct options &options, const ConfigData &raw_config)
|
||||
static void
|
||||
AndroidMain()
|
||||
{
|
||||
struct options options;
|
||||
CommandLineOptions options;
|
||||
ConfigData raw_config;
|
||||
|
||||
const auto sdcard = Environment::getExternalStorageDirectory();
|
||||
@@ -642,7 +653,7 @@ Java_org_musicpd_Bridge_pause(JNIEnv *, jclass)
|
||||
static inline void
|
||||
MainOrThrow(int argc, char *argv[])
|
||||
{
|
||||
struct options options;
|
||||
CommandLineOptions options;
|
||||
ConfigData raw_config;
|
||||
|
||||
ParseCommandLine(argc, argv, options, raw_config);
|
||||
|
@@ -199,13 +199,20 @@ handle_searchaddpl(Client &client, Request args, Response &)
|
||||
{
|
||||
const char *playlist = args.shift();
|
||||
|
||||
const unsigned position = ParseQueuePosition(args, UINT_MAX);
|
||||
|
||||
SongFilter filter;
|
||||
const auto selection = ParseDatabaseSelection(args, true, filter);
|
||||
|
||||
const Database &db = client.GetDatabaseOrThrow();
|
||||
|
||||
search_add_to_playlist(db, client.GetStorage(),
|
||||
playlist, selection);
|
||||
if (position == UINT_MAX)
|
||||
search_add_to_playlist(db, client.GetStorage(),
|
||||
playlist, selection);
|
||||
else
|
||||
SearchInsertIntoPlaylist(db, client.GetStorage(), selection,
|
||||
playlist, position);
|
||||
|
||||
return CommandResult::OK;
|
||||
}
|
||||
|
||||
|
@@ -231,15 +231,14 @@ handle_playlistadd_position(Client &client, const char *playlist_name,
|
||||
editor.Insert(position, uri);
|
||||
} else {
|
||||
#ifdef ENABLE_DATABASE
|
||||
const auto &db = client.GetDatabaseOrThrow();
|
||||
const auto *storage = client.GetStorage();
|
||||
const DatabaseSelection selection(uri, true, nullptr);
|
||||
|
||||
db.Visit(selection, [&editor, &position, storage](const auto &song){
|
||||
editor.Insert(position,
|
||||
DatabaseDetachSong(storage, song));
|
||||
++position;
|
||||
});
|
||||
if (SearchInsertIntoPlaylist(client.GetDatabaseOrThrow(),
|
||||
client.GetStorage(),
|
||||
selection,
|
||||
editor, position) == 0)
|
||||
/* no song was found, don't need to save */
|
||||
return CommandResult::OK;
|
||||
#else
|
||||
(void)client;
|
||||
r.Error(ACK_ERROR_NO_EXIST, "No database");
|
||||
|
@@ -22,6 +22,7 @@
|
||||
#include "PlaylistFile.hxx"
|
||||
#include "Interface.hxx"
|
||||
#include "song/DetachedSong.hxx"
|
||||
#include "protocol/Ack.hxx"
|
||||
|
||||
#include <functional>
|
||||
|
||||
@@ -41,3 +42,42 @@ search_add_to_playlist(const Database &db, const Storage *storage,
|
||||
const auto f = [=](auto && arg1) { return AddSong(storage, playlist_path_utf8, arg1); };
|
||||
db.Visit(selection, f);
|
||||
}
|
||||
|
||||
unsigned
|
||||
SearchInsertIntoPlaylist(const Database &db, const Storage *storage,
|
||||
const DatabaseSelection &selection,
|
||||
PlaylistFileEditor &playlist,
|
||||
unsigned position)
|
||||
{
|
||||
assert(position <= playlist.size());
|
||||
|
||||
unsigned n = 0;
|
||||
|
||||
db.Visit(selection, [&playlist, &position, &n, storage](const auto &song){
|
||||
playlist.Insert(position + n,
|
||||
DatabaseDetachSong(storage, song));
|
||||
++position;
|
||||
++n;
|
||||
});
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
void
|
||||
SearchInsertIntoPlaylist(const Database &db, const Storage *storage,
|
||||
const DatabaseSelection &selection,
|
||||
const char *playlist_name,
|
||||
unsigned position)
|
||||
{
|
||||
PlaylistFileEditor editor{
|
||||
playlist_name,
|
||||
PlaylistFileEditor::LoadMode::TRY,
|
||||
};
|
||||
|
||||
if (position > editor.size())
|
||||
throw ProtocolError{ACK_ERROR_ARG, "Bad position"};
|
||||
|
||||
if (SearchInsertIntoPlaylist(db, storage, selection,
|
||||
editor, position) > 0)
|
||||
editor.Save();
|
||||
}
|
||||
|
@@ -25,6 +25,7 @@
|
||||
class Database;
|
||||
class Storage;
|
||||
struct DatabaseSelection;
|
||||
class PlaylistFileEditor;
|
||||
|
||||
gcc_nonnull(3)
|
||||
void
|
||||
@@ -32,4 +33,19 @@ search_add_to_playlist(const Database &db, const Storage *storage,
|
||||
const char *playlist_path_utf8,
|
||||
const DatabaseSelection &selection);
|
||||
|
||||
/**
|
||||
* @return the number of songs added
|
||||
*/
|
||||
unsigned
|
||||
SearchInsertIntoPlaylist(const Database &db, const Storage *storage,
|
||||
const DatabaseSelection &selection,
|
||||
PlaylistFileEditor &playlist,
|
||||
unsigned position);
|
||||
|
||||
void
|
||||
SearchInsertIntoPlaylist(const Database &db, const Storage *storage,
|
||||
const DatabaseSelection &selection,
|
||||
const char *playlist_name,
|
||||
unsigned position);
|
||||
|
||||
#endif
|
||||
|
@@ -39,6 +39,7 @@
|
||||
#include "util/ConstBuffer.hxx"
|
||||
#include "util/RecursiveMap.hxx"
|
||||
#include "util/SplitString.hxx"
|
||||
#include "config/Block.hxx"
|
||||
|
||||
#include <cassert>
|
||||
#include <string>
|
||||
@@ -76,10 +77,13 @@ class UpnpDatabase : public Database {
|
||||
UpnpClient_Handle handle;
|
||||
UPnPDeviceDirectory *discovery;
|
||||
|
||||
const char* interface;
|
||||
|
||||
public:
|
||||
explicit UpnpDatabase(EventLoop &_event_loop) noexcept
|
||||
explicit UpnpDatabase(EventLoop &_event_loop, const ConfigBlock &block) noexcept
|
||||
:Database(upnp_db_plugin),
|
||||
event_loop(_event_loop) {}
|
||||
event_loop(_event_loop),
|
||||
interface(block.GetBlockValue("interface", nullptr)) {}
|
||||
|
||||
static DatabasePtr Create(EventLoop &main_event_loop,
|
||||
EventLoop &io_event_loop,
|
||||
@@ -147,15 +151,15 @@ private:
|
||||
DatabasePtr
|
||||
UpnpDatabase::Create(EventLoop &, EventLoop &io_event_loop,
|
||||
[[maybe_unused]] DatabaseListener &listener,
|
||||
const ConfigBlock &) noexcept
|
||||
const ConfigBlock &block) noexcept
|
||||
{
|
||||
return std::make_unique<UpnpDatabase>(io_event_loop);
|
||||
return std::make_unique<UpnpDatabase>(io_event_loop, block);;
|
||||
}
|
||||
|
||||
void
|
||||
UpnpDatabase::Open()
|
||||
{
|
||||
handle = UpnpClientGlobalInit();
|
||||
handle = UpnpClientGlobalInit(interface);
|
||||
|
||||
discovery = new UPnPDeviceDirectory(event_loop, handle);
|
||||
try {
|
||||
|
@@ -502,7 +502,7 @@ FfmpegDecode(DecoderClient &client, InputStream *input,
|
||||
FmtDebug(ffmpeg_domain, "codec '{}'",
|
||||
codec_descriptor->name);
|
||||
|
||||
AVCodec *codec = avcodec_find_decoder(codec_params.codec_id);
|
||||
const AVCodec *codec = avcodec_find_decoder(codec_params.codec_id);
|
||||
|
||||
if (!codec) {
|
||||
LogError(ffmpeg_domain, "Unsupported audio codec");
|
||||
|
@@ -52,6 +52,13 @@ EventLoop::EventLoop(
|
||||
|
||||
EventLoop::~EventLoop() noexcept
|
||||
{
|
||||
#if defined(HAVE_URING) && !defined(NDEBUG)
|
||||
/* if Run() was never called (maybe because startup failed and
|
||||
an exception is pending), we need to destruct the
|
||||
Uring::Manager here or else the assertions below fail */
|
||||
uring.reset();
|
||||
#endif
|
||||
|
||||
assert(defer.empty());
|
||||
assert(idle.empty());
|
||||
#ifdef HAVE_THREADED_EVENT_LOOP
|
||||
|
@@ -26,6 +26,7 @@
|
||||
|
||||
#include "AlsaInputPlugin.hxx"
|
||||
#include "lib/alsa/NonBlock.hxx"
|
||||
#include "lib/alsa/Error.hxx"
|
||||
#include "lib/alsa/Format.hxx"
|
||||
#include "../InputPlugin.hxx"
|
||||
#include "../AsyncInputStream.hxx"
|
||||
@@ -332,28 +333,23 @@ AlsaInputStream::ConfigureCapture(AudioFormat audio_format)
|
||||
snd_pcm_hw_params_alloca(&hw_params);
|
||||
|
||||
if ((err = snd_pcm_hw_params_any(capture_handle, hw_params)) < 0)
|
||||
throw FormatRuntimeError("Cannot initialize hardware parameter structure (%s)",
|
||||
snd_strerror(err));
|
||||
throw Alsa::MakeError(err, "snd_pcm_hw_params_any() failed");
|
||||
|
||||
if ((err = snd_pcm_hw_params_set_access(capture_handle, hw_params,
|
||||
SND_PCM_ACCESS_RW_INTERLEAVED)) < 0)
|
||||
throw FormatRuntimeError("Cannot set access type (%s)",
|
||||
snd_strerror(err));
|
||||
throw Alsa::MakeError(err, "snd_pcm_hw_params_set_access() failed");
|
||||
|
||||
if ((err = snd_pcm_hw_params_set_format(capture_handle, hw_params,
|
||||
ToAlsaPcmFormat(audio_format.format))) < 0)
|
||||
throw FormatRuntimeError("Cannot set sample format (%s)",
|
||||
snd_strerror(err));
|
||||
throw Alsa::MakeError(err, "Cannot set sample format");
|
||||
|
||||
if ((err = snd_pcm_hw_params_set_channels(capture_handle,
|
||||
hw_params, audio_format.channels)) < 0)
|
||||
throw FormatRuntimeError("Cannot set channels (%s)",
|
||||
snd_strerror(err));
|
||||
throw Alsa::MakeError(err, "Cannot set channels");
|
||||
|
||||
if ((err = snd_pcm_hw_params_set_rate(capture_handle,
|
||||
hw_params, audio_format.sample_rate, 0)) < 0)
|
||||
throw FormatRuntimeError("Cannot set sample rate (%s)",
|
||||
snd_strerror(err));
|
||||
throw Alsa::MakeError(err, "Cannot set sample rate");
|
||||
|
||||
snd_pcm_uframes_t buffer_size_min, buffer_size_max;
|
||||
snd_pcm_hw_params_get_buffer_size_min(hw_params, &buffer_size_min);
|
||||
@@ -388,26 +384,22 @@ AlsaInputStream::ConfigureCapture(AudioFormat audio_format)
|
||||
int direction = -1;
|
||||
if ((err = snd_pcm_hw_params_set_period_size_near(capture_handle,
|
||||
hw_params, &period_size, &direction)) < 0)
|
||||
throw FormatRuntimeError("Cannot set period size (%s)",
|
||||
snd_strerror(err));
|
||||
throw Alsa::MakeError(err, "Cannot set period size");
|
||||
}
|
||||
|
||||
if ((err = snd_pcm_hw_params(capture_handle, hw_params)) < 0)
|
||||
throw FormatRuntimeError("Cannot set parameters (%s)",
|
||||
snd_strerror(err));
|
||||
throw Alsa::MakeError(err, "snd_pcm_hw_params() failed");
|
||||
|
||||
snd_pcm_uframes_t alsa_buffer_size;
|
||||
err = snd_pcm_hw_params_get_buffer_size(hw_params, &alsa_buffer_size);
|
||||
if (err < 0)
|
||||
throw FormatRuntimeError("snd_pcm_hw_params_get_buffer_size() failed: %s",
|
||||
snd_strerror(-err));
|
||||
throw Alsa::MakeError(err, "snd_pcm_hw_params_get_buffer_size() failed");
|
||||
|
||||
snd_pcm_uframes_t alsa_period_size;
|
||||
err = snd_pcm_hw_params_get_period_size(hw_params, &alsa_period_size,
|
||||
nullptr);
|
||||
if (err < 0)
|
||||
throw FormatRuntimeError("snd_pcm_hw_params_get_period_size() failed: %s",
|
||||
snd_strerror(-err));
|
||||
throw Alsa::MakeError(err, "snd_pcm_hw_params_get_period_size() failed");
|
||||
|
||||
FmtDebug(alsa_input_domain, "buffer_size={} period_size={}",
|
||||
alsa_buffer_size, alsa_period_size);
|
||||
@@ -418,8 +410,7 @@ AlsaInputStream::ConfigureCapture(AudioFormat audio_format)
|
||||
snd_pcm_sw_params_current(capture_handle, sw_params);
|
||||
|
||||
if ((err = snd_pcm_sw_params(capture_handle, sw_params)) < 0)
|
||||
throw FormatRuntimeError("unable to install sw params (%s)",
|
||||
snd_strerror(err));
|
||||
throw Alsa::MakeError(err, "snd_pcm_sw_params() failed");
|
||||
}
|
||||
|
||||
inline void
|
||||
@@ -430,8 +421,9 @@ AlsaInputStream::OpenDevice(const SourceSpec &spec)
|
||||
if ((err = snd_pcm_open(&capture_handle, spec.GetDeviceName(),
|
||||
SND_PCM_STREAM_CAPTURE,
|
||||
SND_PCM_NONBLOCK | global_config.mode)) < 0)
|
||||
throw FormatRuntimeError("Failed to open device: %s (%s)",
|
||||
spec.GetDeviceName(), snd_strerror(err));
|
||||
throw Alsa::MakeError(err,
|
||||
fmt::format("Failed to open device {}",
|
||||
spec.GetDeviceName()).c_str());
|
||||
|
||||
try {
|
||||
ConfigureCapture(spec.GetAudioFormat());
|
||||
|
44
src/lib/alsa/Error.cxx
Normal file
44
src/lib/alsa/Error.cxx
Normal file
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright 2021 Max Kellermann <max.kellermann@gmail.com>
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* - Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* - Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the
|
||||
* distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
* FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
|
||||
* OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "Error.hxx"
|
||||
|
||||
#include <alsa/error.h>
|
||||
|
||||
namespace Alsa {
|
||||
|
||||
ErrorCategory error_category;
|
||||
|
||||
std::string
|
||||
ErrorCategory::message(int condition) const
|
||||
{
|
||||
return snd_strerror(condition);
|
||||
}
|
||||
|
||||
} // namespace Avahi
|
53
src/lib/alsa/Error.hxx
Normal file
53
src/lib/alsa/Error.hxx
Normal file
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright 2021 Max Kellermann <max.kellermann@gmail.com>
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* - Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* - Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the
|
||||
* distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
* FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
|
||||
* OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <system_error>
|
||||
|
||||
namespace Alsa {
|
||||
|
||||
class ErrorCategory final : public std::error_category {
|
||||
public:
|
||||
const char *name() const noexcept override {
|
||||
return "libasound";
|
||||
}
|
||||
|
||||
std::string message(int condition) const override;
|
||||
};
|
||||
|
||||
extern ErrorCategory error_category;
|
||||
|
||||
inline std::system_error
|
||||
MakeError(int error, const char *msg) noexcept
|
||||
{
|
||||
return std::system_error(error, error_category, msg);
|
||||
}
|
||||
|
||||
} // namespace Avahi
|
@@ -18,7 +18,9 @@
|
||||
*/
|
||||
|
||||
#include "HwSetup.hxx"
|
||||
#include "Error.hxx"
|
||||
#include "Format.hxx"
|
||||
#include "lib/fmt/AudioFormatFormatter.hxx"
|
||||
#include "util/ByteOrder.hxx"
|
||||
#include "util/Domain.hxx"
|
||||
#include "util/RuntimeError.hxx"
|
||||
@@ -185,29 +187,27 @@ SetupHw(snd_pcm_t *pcm,
|
||||
/* configure HW params */
|
||||
err = snd_pcm_hw_params_any(pcm, hwparams);
|
||||
if (err < 0)
|
||||
throw FormatRuntimeError("snd_pcm_hw_params_any() failed: %s",
|
||||
snd_strerror(-err));
|
||||
throw Alsa::MakeError(err, "snd_pcm_hw_params_any() failed");
|
||||
|
||||
err = snd_pcm_hw_params_set_access(pcm, hwparams,
|
||||
SND_PCM_ACCESS_RW_INTERLEAVED);
|
||||
if (err < 0)
|
||||
throw FormatRuntimeError("snd_pcm_hw_params_set_access() failed: %s",
|
||||
snd_strerror(-err));
|
||||
throw Alsa::MakeError(err, "snd_pcm_hw_params_set_access() failed");
|
||||
|
||||
err = SetupSampleFormat(pcm, hwparams,
|
||||
audio_format.format, params);
|
||||
if (err < 0)
|
||||
throw FormatRuntimeError("Failed to configure format %s: %s",
|
||||
sample_format_to_string(audio_format.format),
|
||||
snd_strerror(-err));
|
||||
throw Alsa::MakeError(err,
|
||||
fmt::format("Failed to configure format {}",
|
||||
audio_format.format).c_str());
|
||||
|
||||
unsigned int channels = audio_format.channels;
|
||||
err = snd_pcm_hw_params_set_channels_near(pcm, hwparams,
|
||||
&channels);
|
||||
if (err < 0)
|
||||
throw FormatRuntimeError("Failed to configure %i channels: %s",
|
||||
(int)audio_format.channels,
|
||||
snd_strerror(-err));
|
||||
throw Alsa::MakeError(err,
|
||||
fmt::format("Failed to configure {} channels",
|
||||
audio_format.channels).c_str());
|
||||
|
||||
audio_format.channels = (int8_t)channels;
|
||||
|
||||
@@ -218,9 +218,9 @@ SetupHw(snd_pcm_t *pcm,
|
||||
err = snd_pcm_hw_params_set_rate_near(pcm, hwparams,
|
||||
&output_sample_rate, nullptr);
|
||||
if (err < 0)
|
||||
throw FormatRuntimeError("Failed to configure sample rate %u Hz: %s",
|
||||
requested_sample_rate,
|
||||
snd_strerror(-err));
|
||||
throw Alsa::MakeError(err,
|
||||
fmt::format("Failed to configure sample rate {} Hz",
|
||||
requested_sample_rate).c_str());
|
||||
|
||||
if (output_sample_rate == 0)
|
||||
throw FormatRuntimeError("Failed to configure sample rate %u Hz",
|
||||
@@ -253,8 +253,7 @@ SetupHw(snd_pcm_t *pcm,
|
||||
err = snd_pcm_hw_params_set_buffer_time_near(pcm, hwparams,
|
||||
&buffer_time, nullptr);
|
||||
if (err < 0)
|
||||
throw FormatRuntimeError("snd_pcm_hw_params_set_buffer_time_near() failed: %s",
|
||||
snd_strerror(-err));
|
||||
throw Alsa::MakeError(err, "snd_pcm_hw_params_set_buffer_time_near() failed");
|
||||
} else {
|
||||
err = snd_pcm_hw_params_get_buffer_time(hwparams, &buffer_time,
|
||||
nullptr);
|
||||
@@ -275,32 +274,27 @@ SetupHw(snd_pcm_t *pcm,
|
||||
err = snd_pcm_hw_params_set_period_time_near(pcm, hwparams,
|
||||
&period_time, nullptr);
|
||||
if (err < 0)
|
||||
throw FormatRuntimeError("snd_pcm_hw_params_set_period_time_near() failed: %s",
|
||||
snd_strerror(-err));
|
||||
throw Alsa::MakeError(err, "snd_pcm_hw_params_set_period_time_near() failed");
|
||||
}
|
||||
|
||||
err = snd_pcm_hw_params(pcm, hwparams);
|
||||
if (err < 0)
|
||||
throw FormatRuntimeError("snd_pcm_hw_params() failed: %s",
|
||||
snd_strerror(-err));
|
||||
throw Alsa::MakeError(err, "snd_pcm_hw_params() failed");
|
||||
|
||||
HwResult result;
|
||||
|
||||
err = snd_pcm_hw_params_get_format(hwparams, &result.format);
|
||||
if (err < 0)
|
||||
throw FormatRuntimeError("snd_pcm_hw_params_get_format() failed: %s",
|
||||
snd_strerror(-err));
|
||||
throw Alsa::MakeError(err, "snd_pcm_hw_params_get_format() failed");
|
||||
|
||||
err = snd_pcm_hw_params_get_buffer_size(hwparams, &result.buffer_size);
|
||||
if (err < 0)
|
||||
throw FormatRuntimeError("snd_pcm_hw_params_get_buffer_size() failed: %s",
|
||||
snd_strerror(-err));
|
||||
throw Alsa::MakeError(err, "snd_pcm_hw_params_get_buffer_size() failed");
|
||||
|
||||
err = snd_pcm_hw_params_get_period_size(hwparams, &result.period_size,
|
||||
nullptr);
|
||||
if (err < 0)
|
||||
throw FormatRuntimeError("snd_pcm_hw_params_get_period_size() failed: %s",
|
||||
snd_strerror(-err));
|
||||
throw Alsa::MakeError(err, "snd_pcm_hw_params_get_period_size() failed");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
@@ -18,6 +18,7 @@
|
||||
*/
|
||||
|
||||
#include "NonBlock.hxx"
|
||||
#include "Error.hxx"
|
||||
#include "event/MultiSocketMonitor.hxx"
|
||||
#include "util/RuntimeError.hxx"
|
||||
|
||||
@@ -29,8 +30,7 @@ AlsaNonBlockPcm::PrepareSockets(MultiSocketMonitor &m, snd_pcm_t *pcm)
|
||||
if (count == 0)
|
||||
throw std::runtime_error("snd_pcm_poll_descriptors_count() failed");
|
||||
else
|
||||
throw FormatRuntimeError("snd_pcm_poll_descriptors_count() failed: %s",
|
||||
snd_strerror(-count));
|
||||
throw Alsa::MakeError(count, "snd_pcm_poll_descriptors_count() failed");
|
||||
}
|
||||
|
||||
struct pollfd *pfds = pfd_buffer.Get(count);
|
||||
@@ -40,8 +40,7 @@ AlsaNonBlockPcm::PrepareSockets(MultiSocketMonitor &m, snd_pcm_t *pcm)
|
||||
if (count == 0)
|
||||
throw std::runtime_error("snd_pcm_poll_descriptors() failed");
|
||||
else
|
||||
throw FormatRuntimeError("snd_pcm_poll_descriptors() failed: %s",
|
||||
snd_strerror(-count));
|
||||
throw Alsa::MakeError(count, "snd_pcm_poll_descriptors() failed");
|
||||
}
|
||||
|
||||
m.ReplaceSocketList(pfds, count);
|
||||
@@ -71,8 +70,7 @@ AlsaNonBlockPcm::DispatchSockets(MultiSocketMonitor &m,
|
||||
unsigned short dummy;
|
||||
int err = snd_pcm_poll_descriptors_revents(pcm, pfds, i - pfds, &dummy);
|
||||
if (err < 0)
|
||||
throw FormatRuntimeError("snd_pcm_poll_descriptors_revents() failed: %s",
|
||||
snd_strerror(-err));
|
||||
throw Alsa::MakeError(err, "snd_pcm_poll_descriptors_revents() failed");
|
||||
}
|
||||
|
||||
Event::Duration
|
||||
|
@@ -14,6 +14,7 @@ conf.set('ENABLE_ALSA', true)
|
||||
alsa = static_library(
|
||||
'alsa',
|
||||
'Version.cxx',
|
||||
'Error.cxx',
|
||||
'AllowedFormat.cxx',
|
||||
'HwSetup.cxx',
|
||||
'NonBlock.cxx',
|
||||
|
@@ -36,7 +36,7 @@ class CodecContext {
|
||||
public:
|
||||
CodecContext() = default;
|
||||
|
||||
explicit CodecContext(AVCodec &codec)
|
||||
explicit CodecContext(const AVCodec &codec)
|
||||
:codec_context(avcodec_alloc_context3(&codec))
|
||||
{
|
||||
if (codec_context == nullptr)
|
||||
|
@@ -35,6 +35,16 @@
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
template<>
|
||||
struct fmt::formatter<SampleFormat> : formatter<string_view>
|
||||
{
|
||||
template<typename FormatContext>
|
||||
auto format(const SampleFormat format, FormatContext &ctx) {
|
||||
return formatter<string_view>::format(sample_format_to_string(format),
|
||||
ctx);
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct fmt::formatter<AudioFormat> : formatter<string_view>
|
||||
{
|
||||
|
@@ -57,9 +57,9 @@ DoInit()
|
||||
}
|
||||
|
||||
UpnpClient_Handle
|
||||
UpnpClientGlobalInit()
|
||||
UpnpClientGlobalInit(const char* iface)
|
||||
{
|
||||
UpnpGlobalInit();
|
||||
UpnpGlobalInit(iface);
|
||||
|
||||
try {
|
||||
const std::lock_guard<Mutex> protect(upnp_client_init_mutex);
|
||||
|
@@ -23,7 +23,7 @@
|
||||
#include "Compat.hxx"
|
||||
|
||||
UpnpClient_Handle
|
||||
UpnpClientGlobalInit();
|
||||
UpnpClientGlobalInit(const char* iface);
|
||||
|
||||
void
|
||||
UpnpClientGlobalFinish() noexcept;
|
||||
|
@@ -33,12 +33,13 @@ static Mutex upnp_init_mutex;
|
||||
static unsigned upnp_ref;
|
||||
|
||||
static void
|
||||
DoInit()
|
||||
DoInit(const char* iface)
|
||||
{
|
||||
|
||||
#ifdef UPNP_ENABLE_IPV6
|
||||
auto code = UpnpInit2(nullptr, 0);
|
||||
auto code = UpnpInit2(iface, 0);
|
||||
#else
|
||||
auto code = UpnpInit(nullptr, 0);
|
||||
auto code = UpnpInit(iface, 0);
|
||||
#endif
|
||||
if (code != UPNP_E_SUCCESS)
|
||||
throw FormatRuntimeError("UpnpInit() failed: %s",
|
||||
@@ -53,12 +54,12 @@ DoInit()
|
||||
}
|
||||
|
||||
void
|
||||
UpnpGlobalInit()
|
||||
UpnpGlobalInit(const char* iface)
|
||||
{
|
||||
const std::lock_guard<Mutex> protect(upnp_init_mutex);
|
||||
|
||||
if (upnp_ref == 0)
|
||||
DoInit();
|
||||
DoInit(iface);
|
||||
|
||||
++upnp_ref;
|
||||
}
|
||||
|
@@ -21,7 +21,7 @@
|
||||
#define MPD_UPNP_INIT_HXX
|
||||
|
||||
void
|
||||
UpnpGlobalInit();
|
||||
UpnpGlobalInit(const char* iface);
|
||||
|
||||
void
|
||||
UpnpGlobalFinish() noexcept;
|
||||
|
@@ -18,6 +18,7 @@
|
||||
*/
|
||||
|
||||
#include "lib/alsa/NonBlock.hxx"
|
||||
#include "lib/alsa/Error.hxx"
|
||||
#include "mixer/MixerInternal.hxx"
|
||||
#include "mixer/Listener.hxx"
|
||||
#include "output/OutputAPI.hxx"
|
||||
@@ -264,16 +265,15 @@ AlsaMixer::Setup()
|
||||
int err;
|
||||
|
||||
if ((err = snd_mixer_attach(handle, device)) < 0)
|
||||
throw FormatRuntimeError("failed to attach to %s: %s",
|
||||
device, snd_strerror(err));
|
||||
throw Alsa::MakeError(err,
|
||||
fmt::format("failed to attach to {}",
|
||||
device).c_str());
|
||||
|
||||
if ((err = snd_mixer_selem_register(handle, nullptr, nullptr)) < 0)
|
||||
throw FormatRuntimeError("snd_mixer_selem_register() failed: %s",
|
||||
snd_strerror(err));
|
||||
throw Alsa::MakeError(err, "snd_mixer_selem_register() failed");
|
||||
|
||||
if ((err = snd_mixer_load(handle)) < 0)
|
||||
throw FormatRuntimeError("snd_mixer_load() failed: %s\n",
|
||||
snd_strerror(err));
|
||||
throw Alsa::MakeError(err, "snd_mixer_load() failed");
|
||||
|
||||
elem = alsa_mixer_lookup_elem(handle, control, index);
|
||||
if (elem == nullptr)
|
||||
@@ -294,8 +294,7 @@ AlsaMixer::Open()
|
||||
|
||||
err = snd_mixer_open(&handle, 0);
|
||||
if (err < 0)
|
||||
throw FormatRuntimeError("snd_mixer_open() failed: %s",
|
||||
snd_strerror(err));
|
||||
throw Alsa::MakeError(err, "snd_mixer_open() failed");
|
||||
|
||||
try {
|
||||
Setup();
|
||||
@@ -325,8 +324,7 @@ AlsaMixer::GetVolume()
|
||||
|
||||
err = snd_mixer_handle_events(handle);
|
||||
if (err < 0)
|
||||
throw FormatRuntimeError("snd_mixer_handle_events() failed: %s",
|
||||
snd_strerror(err));
|
||||
throw Alsa::MakeError(err, "snd_mixer_handle_events() failed");
|
||||
|
||||
int volume = GetPercentVolume();
|
||||
if (resulting_volume >= 0 && volume == resulting_volume)
|
||||
@@ -343,8 +341,7 @@ AlsaMixer::SetVolume(unsigned volume)
|
||||
|
||||
int err = set_normalized_playback_volume(elem, 0.01*volume, 1);
|
||||
if (err < 0)
|
||||
throw FormatRuntimeError("failed to set ALSA volume: %s",
|
||||
snd_strerror(err));
|
||||
throw Alsa::MakeError(err, "failed to set ALSA volume");
|
||||
|
||||
desired_volume = volume;
|
||||
resulting_volume = GetPercentVolume();
|
||||
|
@@ -74,7 +74,7 @@ private:
|
||||
void
|
||||
UpnpNeighborExplorer::Open()
|
||||
{
|
||||
auto handle = UpnpClientGlobalInit();
|
||||
auto handle = UpnpClientGlobalInit(nullptr);
|
||||
|
||||
discovery = new UPnPDeviceDirectory(event_loop, handle, this);
|
||||
|
||||
|
@@ -20,6 +20,7 @@
|
||||
#include "config.h"
|
||||
#include "AlsaOutputPlugin.hxx"
|
||||
#include "lib/alsa/AllowedFormat.hxx"
|
||||
#include "lib/alsa/Error.hxx"
|
||||
#include "lib/alsa/HwSetup.hxx"
|
||||
#include "lib/alsa/NonBlock.hxx"
|
||||
#include "lib/alsa/PeriodBuffer.hxx"
|
||||
@@ -42,6 +43,10 @@
|
||||
#include "event/Call.hxx"
|
||||
#include "Log.hxx"
|
||||
|
||||
#ifdef ENABLE_DSD
|
||||
#include "util/AllocatedArray.hxx"
|
||||
#endif
|
||||
|
||||
#include <alsa/asoundlib.h>
|
||||
|
||||
#include <boost/lockfree/spsc_queue.hpp>
|
||||
@@ -101,6 +106,16 @@ class AlsaOutput final
|
||||
* Are we currently draining with #stop_dsd_silence?
|
||||
*/
|
||||
bool in_stop_dsd_silence;
|
||||
|
||||
/**
|
||||
* Enable the DSD sync workaround for Thesycon USB audio
|
||||
* receivers? On this device, playing DSD512 or PCM causes
|
||||
* all subsequent attempts to play other DSD rates to fail,
|
||||
* which can be fixed by briefly playing PCM at 44.1 kHz.
|
||||
*/
|
||||
const bool thesycon_dsd_workaround;
|
||||
|
||||
bool need_thesycon_dsd_workaround = thesycon_dsd_workaround;
|
||||
#endif
|
||||
|
||||
/** libasound's buffer_time setting (in microseconds) */
|
||||
@@ -432,6 +447,8 @@ AlsaOutput::AlsaOutput(EventLoop &_loop, const ConfigBlock &block)
|
||||
/* legacy name from MPD 0.18 and older: */
|
||||
block.GetBlockValue("dsd_usb", false)),
|
||||
stop_dsd_silence(block.GetBlockValue("stop_dsd_silence", false)),
|
||||
thesycon_dsd_workaround(block.GetBlockValue("thesycon_dsd_workaround",
|
||||
false)),
|
||||
#endif
|
||||
buffer_time(block.GetPositiveValue("buffer_time",
|
||||
MPD_ALSA_BUFFER_TIME_US)),
|
||||
@@ -519,24 +536,20 @@ AlsaSetupSw(snd_pcm_t *pcm, snd_pcm_uframes_t start_threshold,
|
||||
|
||||
int err = snd_pcm_sw_params_current(pcm, swparams);
|
||||
if (err < 0)
|
||||
throw FormatRuntimeError("snd_pcm_sw_params_current() failed: %s",
|
||||
snd_strerror(-err));
|
||||
throw Alsa::MakeError(err, "snd_pcm_sw_params_current() failed");
|
||||
|
||||
err = snd_pcm_sw_params_set_start_threshold(pcm, swparams,
|
||||
start_threshold);
|
||||
if (err < 0)
|
||||
throw FormatRuntimeError("snd_pcm_sw_params_set_start_threshold() failed: %s",
|
||||
snd_strerror(-err));
|
||||
throw Alsa::MakeError(err, "snd_pcm_sw_params_set_start_threshold() failed");
|
||||
|
||||
err = snd_pcm_sw_params_set_avail_min(pcm, swparams, avail_min);
|
||||
if (err < 0)
|
||||
throw FormatRuntimeError("snd_pcm_sw_params_set_avail_min() failed: %s",
|
||||
snd_strerror(-err));
|
||||
throw Alsa::MakeError(err, "snd_pcm_sw_params_set_avail_min() failed");
|
||||
|
||||
err = snd_pcm_sw_params(pcm, swparams);
|
||||
if (err < 0)
|
||||
throw FormatRuntimeError("snd_pcm_sw_params() failed: %s",
|
||||
snd_strerror(-err));
|
||||
throw Alsa::MakeError(err, "snd_pcm_sw_params() failed");
|
||||
}
|
||||
|
||||
inline void
|
||||
@@ -678,6 +691,97 @@ BestMatch(const std::forward_list<Alsa::AllowedFormat> &haystack,
|
||||
return haystack.front();
|
||||
}
|
||||
|
||||
#ifdef ENABLE_DSD
|
||||
|
||||
static void
|
||||
Play_44_1_Silence(snd_pcm_t *pcm)
|
||||
{
|
||||
snd_pcm_hw_params_t *hw;
|
||||
snd_pcm_hw_params_alloca(&hw);
|
||||
|
||||
int err;
|
||||
|
||||
err = snd_pcm_hw_params_any(pcm, hw);
|
||||
if (err < 0)
|
||||
throw Alsa::MakeError(err, "snd_pcm_hw_params_any() failed");
|
||||
|
||||
err = snd_pcm_hw_params_set_access(pcm, hw,
|
||||
SND_PCM_ACCESS_RW_INTERLEAVED);
|
||||
if (err < 0)
|
||||
throw Alsa::MakeError(err, "snd_pcm_hw_params_set_access() failed");
|
||||
|
||||
err = snd_pcm_hw_params_set_format(pcm, hw, SND_PCM_FORMAT_S16);
|
||||
if (err < 0)
|
||||
throw Alsa::MakeError(err, "snd_pcm_hw_params_set_format() failed");
|
||||
|
||||
unsigned channels = 1;
|
||||
err = snd_pcm_hw_params_set_channels_near(pcm, hw, &channels);
|
||||
if (err < 0)
|
||||
throw Alsa::MakeError(err, "snd_pcm_hw_params_set_channels_near() failed");
|
||||
|
||||
constexpr snd_pcm_uframes_t rate = 44100;
|
||||
err = snd_pcm_hw_params_set_rate(pcm, hw, rate, 0);
|
||||
if (err < 0)
|
||||
throw Alsa::MakeError(err, "snd_pcm_hw_params_set_rate() failed");
|
||||
|
||||
snd_pcm_uframes_t buffer_size = 1;
|
||||
err = snd_pcm_hw_params_set_buffer_size_near(pcm, hw, &buffer_size);
|
||||
if (err < 0)
|
||||
throw Alsa::MakeError(err, "snd_pcm_hw_params_set_buffer_size_near() failed");
|
||||
|
||||
snd_pcm_uframes_t period_size = 1;
|
||||
int dir = 0;
|
||||
err = snd_pcm_hw_params_set_period_size_near(pcm, hw, &period_size,
|
||||
&dir);
|
||||
if (err < 0)
|
||||
throw Alsa::MakeError(err, "snd_pcm_hw_params_set_period_size_near() failed");
|
||||
|
||||
err = snd_pcm_hw_params(pcm, hw);
|
||||
if (err < 0)
|
||||
throw Alsa::MakeError(err, "snd_pcm_hw_params() failed");
|
||||
|
||||
snd_pcm_sw_params_t *sw;
|
||||
snd_pcm_sw_params_alloca(&sw);
|
||||
|
||||
err = snd_pcm_sw_params_current(pcm, sw);
|
||||
if (err < 0)
|
||||
throw Alsa::MakeError(err, "snd_pcm_sw_params_current() failed");
|
||||
|
||||
err = snd_pcm_sw_params_set_start_threshold(pcm, sw, period_size);
|
||||
if (err < 0)
|
||||
throw Alsa::MakeError(err, "snd_pcm_sw_params_set_start_threshold() failed");
|
||||
|
||||
err = snd_pcm_sw_params(pcm, sw);
|
||||
if (err < 0)
|
||||
throw Alsa::MakeError(err, "snd_pcm_sw_params() failed");
|
||||
|
||||
err = snd_pcm_prepare(pcm);
|
||||
if (err < 0)
|
||||
throw Alsa::MakeError(err, "snd_pcm_prepare() failed");
|
||||
|
||||
AllocatedArray<int16_t> buffer{channels * period_size};
|
||||
std::fill(buffer.begin(), buffer.end(), 0);
|
||||
|
||||
/* play at least 250ms of silence */
|
||||
for (snd_pcm_uframes_t remaining_frames = rate / 4;;) {
|
||||
auto n = snd_pcm_writei(pcm, buffer.data(),
|
||||
period_size);
|
||||
if (n < 0)
|
||||
throw Alsa::MakeError(err, "snd_pcm_writei() failed");
|
||||
|
||||
if (snd_pcm_uframes_t(n) >= remaining_frames)
|
||||
break;
|
||||
|
||||
remaining_frames -= snd_pcm_uframes_t(n);
|
||||
}
|
||||
|
||||
err = snd_pcm_drain(pcm);
|
||||
if (err < 0)
|
||||
throw Alsa::MakeError(err, "snd_pcm_drain() failed");
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
void
|
||||
AlsaOutput::Open(AudioFormat &audio_format)
|
||||
{
|
||||
@@ -704,13 +808,30 @@ AlsaOutput::Open(AudioFormat &audio_format)
|
||||
int err = snd_pcm_open(&pcm, GetDevice(),
|
||||
SND_PCM_STREAM_PLAYBACK, mode);
|
||||
if (err < 0)
|
||||
throw FormatRuntimeError("Failed to open ALSA device \"%s\": %s",
|
||||
GetDevice(), snd_strerror(err));
|
||||
throw Alsa::MakeError(err,
|
||||
fmt::format("Failed to open ALSA device \"{}\"",
|
||||
GetDevice()).c_str());
|
||||
|
||||
FmtDebug(alsa_output_domain, "opened {} type={}",
|
||||
snd_pcm_name(pcm),
|
||||
snd_pcm_type_name(snd_pcm_type(pcm)));
|
||||
|
||||
#ifdef ENABLE_DSD
|
||||
if (need_thesycon_dsd_workaround &&
|
||||
audio_format.format == SampleFormat::DSD &&
|
||||
audio_format.sample_rate <= 256 * 44100 / 8) {
|
||||
LogDebug(alsa_output_domain, "Playing some 44.1 kHz silence");
|
||||
|
||||
try {
|
||||
Play_44_1_Silence(pcm);
|
||||
} catch (...) {
|
||||
LogError(std::current_exception());
|
||||
}
|
||||
|
||||
need_thesycon_dsd_workaround = false;
|
||||
}
|
||||
#endif
|
||||
|
||||
PcmExport::Params params;
|
||||
params.alsa_channel_order = true;
|
||||
|
||||
@@ -735,6 +856,11 @@ AlsaOutput::Open(AudioFormat &audio_format)
|
||||
use_dsd = audio_format.format == SampleFormat::DSD;
|
||||
in_stop_dsd_silence = false;
|
||||
|
||||
if (thesycon_dsd_workaround &&
|
||||
(!use_dsd ||
|
||||
audio_format.sample_rate > 256 * 44100 / 8))
|
||||
need_thesycon_dsd_workaround = true;
|
||||
|
||||
if (params.dsd_mode == PcmExport::DsdMode::DOP)
|
||||
LogDebug(alsa_output_domain, "DoP (DSD over PCM) enabled");
|
||||
#endif
|
||||
@@ -897,8 +1023,8 @@ AlsaOutput::DrainInternal()
|
||||
if (frames_written == -EAGAIN)
|
||||
return false;
|
||||
|
||||
throw FormatRuntimeError("snd_pcm_writei() failed: %s",
|
||||
snd_strerror(-frames_written));
|
||||
throw Alsa::MakeError(frames_written,
|
||||
"snd_pcm_writei() failed");
|
||||
}
|
||||
|
||||
/* need to call CopyRingToPeriodBuffer() and
|
||||
@@ -947,8 +1073,7 @@ AlsaOutput::DrainInternal()
|
||||
else if (result == -EAGAIN)
|
||||
return false;
|
||||
else
|
||||
throw FormatRuntimeError("snd_pcm_drain() failed: %s",
|
||||
snd_strerror(-result));
|
||||
throw Alsa::MakeError(result, "snd_pcm_drain() failed");
|
||||
}
|
||||
|
||||
void
|
||||
@@ -1147,8 +1272,7 @@ try {
|
||||
|
||||
int err = snd_pcm_prepare(pcm);
|
||||
if (err < 0)
|
||||
throw FormatRuntimeError("snd_pcm_prepare() failed: %s",
|
||||
snd_strerror(-err));
|
||||
throw Alsa::MakeError(err, "snd_pcm_prepare() failed");
|
||||
}
|
||||
|
||||
{
|
||||
@@ -1236,8 +1360,8 @@ try {
|
||||
return;
|
||||
|
||||
if (Recover(frames_written) < 0)
|
||||
throw FormatRuntimeError("snd_pcm_writei() failed: %s",
|
||||
snd_strerror(-frames_written));
|
||||
throw Alsa::MakeError(frames_written,
|
||||
"snd_pcm_writei() failed");
|
||||
|
||||
/* recovered; try again in the next DispatchSockets()
|
||||
call */
|
||||
|
@@ -55,6 +55,7 @@
|
||||
#include <boost/lockfree/spsc_queue.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
|
@@ -5,11 +5,7 @@ After=network.target sound.target
|
||||
|
||||
[Service]
|
||||
Type=notify
|
||||
ExecStart=@prefix@/bin/mpd --no-daemon
|
||||
|
||||
# Create /run/mpd (if MPD is launched without the socket unit and is
|
||||
# configured to bind listener sockets there).
|
||||
RuntimeDirectory=mpd
|
||||
ExecStart=@prefix@/bin/mpd --systemd
|
||||
|
||||
# Enable this setting to ask systemd to watch over MPD, see
|
||||
# systemd.service(5). This is disabled by default because it causes
|
||||
|
@@ -5,11 +5,7 @@ After=network.target sound.target
|
||||
|
||||
[Service]
|
||||
Type=notify
|
||||
ExecStart=@prefix@/bin/mpd --no-daemon
|
||||
|
||||
# Create /run/user/$UID/mpd (if MPD is launched without the socket
|
||||
# unit and is configured to bind listener sockets there).
|
||||
RuntimeDirectory=mpd
|
||||
ExecStart=@prefix@/bin/mpd --systemd
|
||||
|
||||
# Enable this setting to ask systemd to watch over MPD, see
|
||||
# systemd.service(5). This is disabled by default because it causes
|
||||
|
@@ -99,6 +99,7 @@ thirdparty_libs = [
|
||||
libid3tag,
|
||||
liblame,
|
||||
libmodplug,
|
||||
libopenmpt,
|
||||
wildmidi,
|
||||
gme,
|
||||
ffmpeg,
|
||||
|
Reference in New Issue
Block a user