Compare commits

..

83 Commits

Author SHA1 Message Date
Max Kellermann
43df4a7500 release v0.21.3 2018-11-16 13:27:58 +01:00
Max Kellermann
4cdcaa8630 output/alsa: don't call snd_pcm_drain() if nothing was written
Works around a problem where MPD goes into a busy loop because
snd_pcm_drain() always returns `-EAGAIN` without making any progress
(fixes ).

This problem was triggered by snd_pcm_drain() after snd_pcm_cancel()
and snd_pcm_prepare(), but without submitting any data with
snd_pcm_writei().

I believe this is a kernel bug: in non-blocking mode, the kernel's
snd_pcm_drain() function returns early.  In this mode, it only checks
whether snd_pcm_drain_done() has been called already, but
snd_pcm_drain_done() is never called if no data was submitted.

In blocking mode, the following `for` loop detects this condition, so
snd_pcm_drain_done() is not necessary, but without this extra check,
we get `-EAGAIN` forever.
2018-11-16 12:49:37 +01:00
Volodymyr Medvid
04f632296f test/meson.build: run_storage depends on event lib
test/run_storage.cxx depends on EventThread/EventLoop from libevent.a.
Depend on it explicitly. This addresses build failure with
-Dtest=true -Dcurl=disabled -Ddbus=disabled
2018-11-15 19:01:43 +02:00
Max Kellermann
7c8dbcfaac doc/protocol.rst: song position is 0-based 2018-11-15 12:34:23 +01:00
Max Kellermann
436ba3c96c output/alsa: drain the whole ring_buffer, not just one period
This fixes a problem which caused a failure with snd_pcm_writei()
because snd_pcm_drain() had already been called in the previous
iteration.  This commit makes sure that snd_pcm_drain() is only called
after the final snd_pcm_writei() call.

This fixes discarded samples at the end of playback.
2018-11-14 13:35:17 +01:00
Max Kellermann
5d12f52873 output/alsa: clear error after reopening device
When a playback error has occurred, MPD would never recover until one
restarts MPD.
2018-11-14 13:20:54 +01:00
Max Kellermann
a8bf8ede01 event/Thread: reduce the RTIO timer slack to 10us
MPD's default is 100ms, which is too long for the real-time I/O
thread.  The OutputThread has 100us, but the real-time I/O thread
might have tighter deadlines.

This change has currently no effect (I believe), because nobody uses
timers on the RTIO thread.
2018-11-14 12:11:57 +01:00
Max Kellermann
8682183bc3 LogInit: default to journal if MPD was started as systemd service 2018-11-14 12:07:22 +01:00
Max Kellermann
94c31d0da9 doc/mpdconf.example: no, logging is not disabled without log_file 2018-11-14 12:07:22 +01:00
Max Kellermann
464a4cbeec python/build/libs.py: upgrade FFmpeg to 4.1 2018-11-14 11:50:51 +01:00
Max Kellermann
9f0cbf418a python/build/libs.py: upgrade CURL to 7.62.0 2018-11-14 11:50:51 +01:00
Max Kellermann
b477f86c92 output/alsa: don't lock the mutex in CancelInternal()
CancelInternal() doesn't need to be protected because it is called
synchronously from Cancel().
2018-11-14 11:50:51 +01:00
Max Kellermann
020371f145 output/alsa: wake up the client thread after generating silence
Fixes a theoretical race condition which could occur in Drain() (but
was extremely unlikely).
2018-11-14 11:48:55 +01:00
Max Kellermann
ccafe3f3cf output/alsa: don't generate silence if ALSA-PCM buffer has enough data
If our `ring_buffer` is smaller than the ALSA-PCM buffer (if the
latter has more than the 4 periods we allocate), it can happen that
the start threshold is crossed and ALSA switches to
`SND_PCM_STATE_RUNNING`, but the `ring_buffer` is empty.  In this
case, MPDD will generate silence, even though the ALSA-PCM buffer has
enough data.  This causes stuttering ().

This commit amends an older workaround for a similar problem (commit
e08598e7e2) by adding a snd_pcm_avail()
check, and only generate silence if there is less than one period of
data in the ALSA-PCM buffer.

Fixes 
2018-11-14 11:17:59 +01:00
Max Kellermann
3830748de5 output/alsa: clear the period_buffer in LockCaughtError()
The method Cancel() assumes that the `period_buffer` must be empty
when `active==false`, but that is not the case when Play() fails.

Of course the assertion in Cancel() is not 100% correct, but I decided
to rather fix this in LockCaughtError() because the `period_buffer`
should only be accessed from within the RTIO thread, and this is the
only code path where `active` can be set to `false` with a non-empty
`period_buffer`.

Fixes 
2018-11-14 10:24:08 +01:00
Max Kellermann
1a43f5145d output/alsa: throw on snd_pcm_writei() error while draining
This implements real error handling, and avoids calling
CancelInternal() from this code path.
2018-11-14 10:08:29 +01:00
Max Kellermann
7f143a83c1 output/alsa: fix wrong use of errno
alsa-lib doesn't set errno, it returns errors as negative integers.
2018-11-14 10:07:23 +01:00
Max Kellermann
6ccc254179 output/alsa: throw after snd_pcm_drain() error 2018-11-14 10:04:10 +01:00
Max Kellermann
7db2450447 output/alsa: refactor the drain EAGAIN workaround 2018-11-14 10:00:50 +01:00
Max Kellermann
6c2a6a65e0 output/alsa: remove snd_pcm_state() check from DrainInternal()
This check was added 9 years ago in commit
4dc25d3908 to work around a dmix bug
which I assume has been fixed long ago.

Removing this fixes another corner case: if draining is requested
before the start threshold is reached, the PCM is still in
SND_PCM_STATE_PREPARED but not yet SND_PCM_STATE_RUNNING, which means
the submitted data will never be played.  This corner case is
realistic when playing songs shorter than the ALSA buffer (if the
buffer is very large).
2018-11-14 09:48:24 +01:00
Max Kellermann
4247a757b3 output/alsa: call snd_pcm_prepare() if draining is requested early
This fixes a corner case which has probably never occurred and
probably never will: if Cancel() is called, and then Play() followed
by Drain(), the plugin should really play that data.  However
currently, this never happens, because snd_pcm_prepare() is never
called.
2018-11-14 09:43:14 +01:00
Max Kellermann
57e34823d8 increment version number to 0.21.3 2018-11-12 13:59:17 +01:00
Max Kellermann
3c93decdf0 release v0.21.2 2018-11-12 13:33:04 +01:00
Max Kellermann
89e7a5018d doc/protocol.rst: explain song positions vs ids 2018-11-12 13:19:10 +01:00
Max Kellermann
7235b46e5e doc/protocol.rst: rename "current playlist" to "queue" 2018-11-12 13:12:29 +01:00
Max Kellermann
0852226a48 doc/protocol.rst: deprecated close and kill 2018-11-12 13:03:09 +01:00
Max Kellermann
e20d215abf doc/protocol.rst: more markup 2018-11-12 13:01:43 +01:00
Max Kellermann
e4b9b67e24 doc/protocol.rst: deprecation 2018-11-12 12:57:53 +01:00
Max Kellermann
685b78828d doc/protocol.rst: mention that unknown lines may be omitted 2018-11-12 12:57:45 +01:00
Max Kellermann
060908d5c4 song/Filter: add operator "contains"
Closes 
2018-11-12 12:49:01 +01:00
Max Kellermann
0b0f4c61f1 doc/protocol.rst: remove documentation about == matching substrings
I added this sentence in commit
5271e81ebe, but this was merely
documented the legacy status quo, which has always been undocumented
for old-style filters.

But for new filters, using "==" for sub strings was a surprising
"feature", which I removed in commit
ac0852b4e3.
2018-11-12 12:45:40 +01:00
Max Kellermann
228bf7eb09 output/thread: cancel the AudioOutputSource() instead of closing it
This fixes the assertion failure due to calling
AudioOutputSource::Close() twice.
2018-11-12 12:24:25 +01:00
Max Kellermann
5eaf2b8fc3 output/control: always close the AudioOutputSource in RELEASE
Fixes a crash bug with `always_on` outputs which occurs because the
`AudioOutputSource` still has a pointer to an outdated `MusicChunk`.

Fixes 
2018-11-12 12:21:59 +01:00
Max Kellermann
e097fef79e output/control: add command RELEASE
With the new command, the decision to pause or close the output moves
into the output thread.
2018-11-12 12:09:37 +01:00
Max Kellermann
9a813cd3b1 output/Thread: update comment 2018-11-12 12:09:02 +01:00
Max Kellermann
1c60c8e014 output/Filtered: catch Drain() exceptions in CloseOutput() 2018-11-12 12:05:54 +01:00
Max Kellermann
eddda95900 output/interface: document that Drain() may throw 2018-11-12 12:04:42 +01:00
Max Kellermann
72184dccfc song/StringFilter: support regular expressions with "=~" and "!~"
This feature requires `libpcre`.
2018-11-11 12:55:35 +01:00
Max Kellermann
fee75dc766 {output,mixer}/alsa: use snd_pcm_poll_descriptors_revents()
This call was missing, causing very high CPU usage when the ALSA
output plugin was used with dmix.

Closes 
2018-11-11 12:37:29 +01:00
Max Kellermann
ba5c856f15 events/MultiSocketMonitor: add method ForEachResult() 2018-11-11 12:37:28 +01:00
Max Kellermann
12308a0f55 lib/alsa/NonBlock: move the functions into a class managing the state 2018-11-11 12:37:25 +01:00
Max Kellermann
a958abde2f Merge branch 'fix_362' of git://github.com/miccoli/MPD 2018-11-11 12:37:13 +01:00
Max Kellermann
583208db7e output/httpd: fix nullptr dereference crash bug
When `metadata_sent` is `false`, the plugin assumes there is metadata
which must be sent, even if no metadata page was passed to the plugin.
Initializing it to `true` avoids dereferencing this `nullptr`.

Fixes 
2018-11-08 09:37:18 +01:00
Max Kellermann
7b5ba15170 song/Filter: move code to ParseStringFilter() 2018-11-08 00:02:10 +01:00
Max Kellermann
d5e0d49f86 song/{Tag,Uri}SongFilter: pass StringFilter&& to constructor 2018-11-07 23:57:42 +01:00
Max Kellermann
73b22d82aa song/StringFilter: move negated flag from containing class 2018-11-07 23:47:31 +01:00
Max Kellermann
db51cc4e02 lib/zlib/meson.build: add zlib_dep to declare_dependency
Fixes potential compiler error when zlib is installed in a
non-standard directory.
2018-11-07 23:32:23 +01:00
Max Kellermann
be8a52a914 NEWS: mention the ENABLE_ZLIB fix 2018-11-07 23:26:33 +01:00
Max Kellermann
ad597a8ff0 lib/zlib/meson.build: define ENABLE_ZLIB
Fixes 
2018-11-07 23:24:58 +01:00
Max Kellermann
b1fe105904 output/Source: reset current_chunk in Open()
If the output is already open, the `current_chunk` pointer may be
bogus and out of sync with `SharedPipeConsumer::chunk`, leading to an
assertion failure in `SharedPipeConsumer::Consume()`.

Fixes 
2018-11-07 00:17:48 +01:00
Max Kellermann
451b142e3a player/Thread: finish decoder startup before checking the buffer
This fixes a valgrind warning because `buffer_before_play`
initialization needs to know the audio format from the decoder.
2018-11-06 23:52:26 +01:00
Max Kellermann
2833625266 doc/user.rst: more markup 2018-11-06 22:38:34 +01:00
Max Kellermann
0464028872 doc/user.rst: add information about debug build 2018-11-06 22:38:24 +01:00
Max Kellermann
98985c03b0 check.h: remove obsolete ENABLE_LARGEFILE check
Meson always enables large file support on the compiler command line,
thus config.h doesn't need to be included anymore.  We'll remove the
whole `check.h` header soon.

Closes 
2018-11-05 21:25:59 +01:00
Max Kellermann
793fd8c479 decoder/ffmpeg: eliminate GetSampleFormat() 2018-11-04 22:36:17 +01:00
Max Kellermann
6c602811df decoder/ffmepg: fill AudioFormat from AVCodecContext, not AVCodecParameters
`AVCodecParameters` contains values from the codec detected by
avformat_find_stream_info(), but after avcodec_open2(), a different
codec might be selected with a different `AVSampleFormat`.  This leads
to misinterpretation of data returned from FFmpeg, leading to random
noise or silence.

This was observed with FFmpeg 4.0.2 and a TS container file containing
MP2.  A mp3-float codec was detected returning `AV_SAMPLE_FMT_FLTP`,
but finally the `mpegaudiodec_fixed.c` was used, returning
`AV_SAMPLE_FMT_S16`.

By using the audio format from `AVCodecContext`, we ensure that MPD
and FFmpeg always agree on the actual audio format in the buffer.

This removes the FFmpeg bug workaround from commit e1b032cbad which I
assume is obsolete after 7 years.

Fixes 
2018-11-04 22:30:50 +01:00
Stefano Miccoli
6d48a5684a clamp 'set_normalized_volume' to valid values also for ALSA softvol
ensure that valid mixer values are set also when the ALSA driver
does not report a valid dB range ('set_raw' fallback)

correct a bug in which volume is assumed to lie in [0..100]
instead of [0..1]
2018-11-04 22:21:56 +01:00
Max Kellermann
bd115a4008 decoder/ffmpeg: use AtScopeExit() to call av_packet_unref() 2018-11-04 22:01:33 +01:00
Max Kellermann
08272cdee2 decoder/ffmpeg: require FFmpeg 3.1 or later
Drop some compatibility code.
2018-11-04 21:55:06 +01:00
Max Kellermann
b14a5141a6 increment version number to 0.21.2 2018-11-04 19:47:04 +01:00
Max Kellermann
aa0e4500c6 release v0.21.1 2018-11-04 14:08:16 +01:00
Fabian Muscariello
4e6b8edf72 doc/protocol.rst: add missing backticks 2018-11-04 14:04:57 +01:00
Max Kellermann
ac0852b4e3 song/Filter: operator "==" never searches substrings in filter expressions
The protocol documentation says that the difference between `find` and
`search` is that `search` is case insensitive, but that's only half
the truth: `search` also searches for sub strings instead of matching
the whole string.  This part is undocumented and unfortunate, but at
this point, we can't change it.

However leaking this surprising behavior to the new filter expressions
was a bad idea; the "==" operator should never match substrings.  For
people who need that, we should add a new operator.
2018-11-04 13:57:34 +01:00
Max Kellermann
6fe43ed969 song/StringFilter: add flag substring
Prepare to stop using substrings for filter expressions.
2018-11-04 13:49:47 +01:00
Max Kellermann
b34bc06624 song/StringFilter: use std::string::operator== 2018-11-04 13:49:38 +01:00
Max Kellermann
08e41e60e5 meson.build: downgrade Boost 1.67 error to warning
Some Boost 1.67 packages apparently have a workaround for the bug, so
let them build MPD.
2018-11-04 12:45:22 +01:00
Max Kellermann
10ec478a9c meson.build: refuse to build with buggy Boost version 1.67 2018-11-04 12:31:49 +01:00
Max Kellermann
86f1074905 lib/xiph/meson.build: the Vorbis encoder requires the Vorbis decoder
Without the Vorbis decoder, `libvorbis` is never detected, leading to
linker failures when attempting to build the Vorbis encoder.
2018-11-04 12:21:23 +01:00
Max Kellermann
8e66b855a3 doc/protocol.rst: mention that sub-expressios must be enclosed in parantheses
Closes 
2018-11-04 12:12:38 +01:00
Max Kellermann
e3bc85d7bf meson.build: require Meson 0.47.2
Meson 0.47.1 suffers from a bug which breaks linking the MPD
executable because the `-lpthread` flag is not propagated from our
`thread.a`.

See https://github.com/mesonbuild/meson/pull/3895

Closes 
2018-11-04 11:54:40 +01:00
Max Kellermann
6f242836e6 lib/xiph/meson.build: fix typo, replace and with or
Fixes linker failure when building without FLAC support.

Closes 
2018-11-04 11:36:28 +01:00
Max Kellermann
f2c926f3b6 zeroconf/glue: add fallback value for HOST_NAME_MAX
`HOST_NAME_MAX` is not a portable macro; it is undefined on some
systems.

Closes 
2018-11-04 11:12:03 +01:00
Max Kellermann
aba18924ee win32/build.py: link libstdc++ and libcc statically
Fixes 
2018-11-04 11:10:00 +01:00
Max Kellermann
aa6bef54dd python/build/zlib.py: build zlib as a static library
Fixes one part of 
2018-11-04 11:10:00 +01:00
Max Kellermann
528f5b9cb9 song/Filter: allow escaping quotes in filter expressions
Closes 
2018-11-02 19:15:08 +01:00
Max Kellermann
96ae0ec93a remove some autotools remains
Closes 
2018-11-02 18:55:49 +01:00
Max Kellermann
5a5229b499 net/IPv[46]Address: make the initializers more portable
Thanks to C++14, we can declare and fill variables inside `constexpr`
functions.  This means me can stop make assumptions on the `struct`
layouts without losing `constexpr`.

Closes 
2018-11-02 17:47:43 +01:00
Max Kellermann
bba22c9c8c system/FileDescriptor: check __linux__ instead of __linux
`__linux` is the deprecated non-standard macros which appears to be
not present at all on PowerPC.

Closes 
2018-11-02 16:50:38 +01:00
Max Kellermann
694c437a2c NEWS: mention the FFmpeg build fix 2018-11-02 16:50:35 +01:00
Max Kellermann
587172efa3 Merge branch 'patch-1' of git://github.com/joerg-krause/MPD 2018-11-01 19:17:10 +01:00
Max Kellermann
2a926063b2 src/lib/ffmpeg/meson.build: copy dependencies into ffmpeg_dep
Apparently, Meson propagates the linker flags but not the compiler
flags from a `static_library`'s dependencies list.

Closes 
2018-11-01 19:14:00 +01:00
Jörg Krause
d6f239e54f meson: fix typo in options plugins comment 2018-11-01 19:06:54 +01:00
Max Kellermann
b8989fafeb increment version number to 0.21.1 2018-11-01 17:23:47 +01:00
61 changed files with 1070 additions and 471 deletions

80
.gitignore vendored

@@ -1,88 +1,8 @@
*.Plo
*.Po
*.a
*.d
*.la
*.lo
*.o
*.exe
*~
.#*
.stgit*
.deps
.dirstamp
tags
/Makefile
/Makefile.in
/aclocal.m4
/autom4te.cache
/config.h
/config.h.in
/config.log
/config.mk
/config.status
/config_detected.h
/config_detected.mk
/configure
/configure.lineno
/depmode
/libtool
/ltmain.sh
/mkinstalldirs
/output/
/src/mpd
/systemd/system/mpd.service
/systemd/user/mpd.service
/stamp-h1
/src/dsd2pcm/dsd2pcm
/src/win32/mpd_win32_rc.rc
/doc/doxygen.conf
/doc/protocol.html
/doc/protocol
/doc/user
/doc/developer
/doc/sticker
/doc/api
/test/software_volume
/test/run_convert
/test/run_decoder
/test/read_tags
/test/run_filter
/test/run_encoder
/test/run_output
/test/read_conf
/test/run_input
/test/read_mixer
/test/dump_playlist
/test/run_normalize
/test/tmp
/test/run_inotify
/test/test_queue_priority
/test/test_protocol
/test/run_ntp_server
/test/run_resolver
/test/run_tcp_connect
/test/test_pcm
/test/dump_rva2
/test/dump_text_file
/test/test_util
/test/test_byte_reverse
/test/test_mixramp
/test/test_vorbis_encoder
/test/DumpDatabase
/lib/
/*.tar.gz
/*.tar.bz2
/*.tar.xz
/mpd-*/
__pycache__/

37
NEWS

@@ -1,3 +1,40 @@
ver 0.21.3 (2018/11/16)
* output
- alsa: fix crash bug
- alsa: fix stuttering at start of playback
- alsa: fix discarded samples at end of song
- alsa: clear error after reopening device
* log: default to journal if MPD was started as systemd service
ver 0.21.2 (2018/11/12)
* protocol
- operator "=~" matches a regular expression
- operator "contains" matches substrings
* decoder
- ffmpeg: require FFmpeg 3.1 or later
- ffmpeg: fix broken sound with certain codecs
* output
- alsa: fix high CPU usage with dmix
- httpd: fix three crash bugs
* mixer
- alsa: fix more rounding errors
* fix zlib support
ver 0.21.1 (2018/11/04)
* protocol
- allow escaping quotes in filter expressions
- operator "==" never searches substrings in filter expressions
* decoder
- ffmpeg: fix build failure with non-standard FFmpeg installation path
- flac: fix linker failure when building without FLAC support
* encoder
- vorbis: fix linker failure when building without Vorbis decoder
* fix build failure on Linux-PowerPC
* fix build failure on FreeBSD
* eliminate DLL dependencies on Windows
* add warning about buggy Boost version 1.67
* require Meson 0.47.2 because a Meson 0.47.1 bug breaks our build
ver 0.21 (2018/10/31)
* configuration
- add "include" directive, allows including config files

1
android/.gitignore vendored

@@ -1 +0,0 @@
/build

@@ -2,8 +2,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.musicpd"
android:installLocation="auto"
android:versionCode="23"
android:versionName="0.21">
android:versionCode="25"
android:versionName="0.21.3">
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="26"/>

@@ -1,11 +0,0 @@
#!/bin/sh
set -e
rm -rf config.cache build
mkdir build
aclocal -I m4 $ACLOCAL_FLAGS
autoheader
automake --add-missing $AUTOMAKE_FLAGS
autoconf

2
doc/.gitignore vendored

@@ -1,2 +0,0 @@
/html/
/doctrees/

@@ -38,9 +38,9 @@ author = 'Max Kellermann'
# built documents.
#
# The short X.Y version.
version = '0.21'
version = '0.21.3'
# The full version, including alpha/beta/rc tags.
release = '0.21~git'
release = version
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.

@@ -32,7 +32,7 @@
# settings.
#
# The special value "syslog" makes MPD use the local syslog daemon. This
# setting defaults to logging to syslog, otherwise logging is disabled.
# setting defaults to logging to syslog.
#
#log_file "~/.mpd/log"
#

@@ -8,7 +8,7 @@ General protocol syntax
Protocol overview
=================
The ``MPD`` command protocol exchanges
The :program:`MPD` command protocol exchanges
line-based text records between client and server over TCP.
Once the client is connected to the server, they conduct a
conversation until the client closes the connection. The
@@ -152,10 +152,15 @@ of:
``VALUE`` in ``AlbumArtist``
and falls back to ``Artist`` tags if
``AlbumArtist`` does not exist.
``VALUE`` is what to find. The
`find` commands specify an exact value
and are case-sensitive; the `search`
commands specify a sub string and ignore case.
``VALUE`` is what to find.
- ``(TAG contains 'VALUE')`` checks if the given value is a substring
of the tag value.
- ``(TAG =~ 'VALUE')`` and ``(TAG !~ 'VALUE')`` use a Perl-compatible
regular expression instead of doing a simple string comparison.
(This feature is only available if :program:`MPD` was compiled with
:file:`libpcre`)
- ``(file == 'VALUE')``: match the full song URI
(relative to the music directory).
@@ -175,22 +180,58 @@ of:
matches the audio format with the given mask (i.e. one
or more attributes may be "*").
- ``(!EXPRESSION)``: negate an expression.
- ``(!EXPRESSION)``: negate an expression. Note that each expression
must be enclosed in parantheses, e.g. :code:`(!(artist == 'VALUE'))`
(which is equivalent to :code:`(artist != 'VALUE')`)
- ``(EXPRESSION1 AND EXPRESSION2 ...)``: combine two or
more expressions with logical "and".
more expressions with logical "and". Note that each expression must
be enclosed in parantheses, e.g. :code:`((artist == 'FOO') AND
(album == 'BAR'))`
The :command:`find` commands are case sensitive, which
:command:`search` and related commands ignore case.
Prior to MPD 0.21, the syntax looked like this::
find TYPE VALUE
Escaping String Values
----------------------
String values are quoted with single or double quotes, and special
characters within those values must be escaped with the backslash
(``\``). Keep in mind that the backslash is also the escape character
on the protocol level, which means you may need to use double
backslash.
Example expression which matches an artist named ``foo'bar"``::
(artist "foo\'bar\"")
At the protocol level, the command must look like this::
find "(artist \"foo\\'bar\\\"\")"
The double quotes enclosing the artist name must be escaped because
they are inside a double-quoted ``find`` parameter. The single quote
inside that artist name must be escaped with two backslashes; one to
escape the single quote, and another one because the backslash inside
the string inside the parameter needs to be escaped as well. The
double quote has three confusing backslashes: two to build one
backslash, and another one to escape the double quote on the protocol
level. Phew!
To reduce confusion, you should use a library such as `libmpdclient
<https://www.musicpd.org/libs/libmpdclient/>`_ which escapes command
arguments for you.
.. _tags:
Tags
====
The following tags are supported by
``MPD``:
The following tags are supported by :program:`MPD`:
* **artist**: the artist name. Its meaning is not well-defined; see "*composer*" and "*performer*" for more specific tags.
* **artistsort**: same as artist, but for sorting. This usually omits prefixes such as "The".
@@ -216,7 +257,7 @@ The following tags are supported by
* **musicbrainz_workid**: the work id in the `MusicBrainz <https://picard.musicbrainz.org/docs/mappings/>`_ database.
There can be multiple values for some of these tags. For
example, ``MPD`` may return multiple
example, :program:`MPD` may return multiple
lines with a ``performer`` tag. A tag value is
a UTF-8 string.
@@ -259,16 +300,17 @@ Recipes
Queuing
=======
Often, users run ``MPD`` with :ref:`random <command_random>` enabled,
but want to be able to insert songs "before" the rest of the playlist.
That is commonly called "queuing".
Often, users run :program:`MPD` with :ref:`random <command_random>`
enabled, but want to be able to insert songs "before" the rest of the
playlist. That is commonly called "queuing".
``MPD`` implements this by allowing the client to specify a "priority"
for each song in the playlist (commands :ref:`priod <command_prio>`
and :ref:`priodid <command_prioid>`). A higher priority means that
the song is going to be played before the other songs.
:program:`MPD` implements this by allowing the client to specify a
"priority" for each song in the playlist (commands :ref:`priod
<command_prio>` and :ref:`priodid <command_prioid>`). A higher
priority means that the song is going to be played before the other
songs.
In "random" mode, ``MPD`` maintains an
In "random" mode, :program:`MPD` maintains an
internal randomized sequence of songs. In this sequence,
songs with a higher priority come first, and all songs with
the same priority are shuffled (by default, all songs are
@@ -293,9 +335,9 @@ Command reference
commands using song ids should be used instead of the commands
that manipulate and control playback based on playlist
position. Using song ids is a safer method when multiple
clients are interacting with ``MPD``.
clients are interacting with :program:`MPD`.
Querying ``MPD``'s status
Querying :program:`MPD`'s status
================================
:command:`clearerror`
@@ -310,7 +352,7 @@ Querying ``MPD``'s status
:command:`idle [SUBSYSTEMS...]` [#since_0_14]_
Waits until there is a noteworthy change in one or more
of ``MPD``'s subsystems. As soon
of :program:`MPD`'s subsystems. As soon
as there is one, it lists all changed systems in a line
in the format ``changed:
SUBSYSTEM``, where SUBSYSTEM is one of the
@@ -319,7 +361,7 @@ Querying ``MPD``'s status
- ``database``: the song database has been modified after :ref:`update <command_update>`.
- ``update``: a database update has started or finished. If the database was modified during the update, the ``database`` event is also emitted.
- ``stored_playlist``: a stored playlist has been modified, renamed, created or deleted
- ``playlist``: the current playlist has been modified
- ``playlist``: the queue (i.e. the current playlist) has been modified
- ``player``: the player has been started, stopped or seeked
- ``mixer``: the volume has been changed
- ``output``: an audio output has been added, removed or modified (e.g. renamed, enabled or disabled)
@@ -340,11 +382,11 @@ Querying ``MPD``'s status
to wait for events as long as mpd runs. The
`idle` command can be canceled by
sending the command `noidle` (no other
commands are allowed). ``MPD``
commands are allowed). :program:`MPD`
will then leave `idle` mode and print
results immediately; might be empty at this time.
If the optional ``SUBSYSTEMS`` argument
is used, ``MPD`` will only send
is used, :program:`MPD` will only send
notifications when something changed in one of the
specified subsytems.
@@ -354,19 +396,21 @@ Querying ``MPD``'s status
Reports the current status of the player and the volume
level.
- ``volume``: ``0-100`` or ``-1`` if the volume cannot be determined
- ``volume``: ``0-100`` (deprecated: ``-1`` if the volume cannot
be determined)
- ``repeat``: ``0`` or ``1``
- ``random``: ``0`` or ``1``
- ``single`` [#since_0_15]_: ``0``, ``1``, or ``oneshot`` [#since_0_21]_
- ``consume`` [#since_0_15]_: ``0`` or ``1``
- ``playlist``: 31-bit unsigned integer, the playlist version number
- ``playlistlength``: integer, the length of the playlist
- ``state``: ``play``, ``stop, or ``pause``
- ``state``: ``play``, ``stop``, or ``pause``
- ``song``: playlist song number of the current song stopped on or playing
- ``songid``: playlist songid of the current song stopped on or playing
- ``nextsong`` [#since_0_15]_: playlist song number of the next song to be played
- ``nextsongid`` [#since_0_15]_: playlist songid of the next song to be played
- ``time``: total time elapsed (of current playing/paused song)
(deprecated, use ``elapsed`` instead)
- ``elapsed`` [#since_0_16]_: Total time elapsed within the current song, but with higher resolution.
- ``duration`` [#since_0_20]_: Duration of the current song in seconds.
- ``bitrate``: instantaneous bitrate in kbps
@@ -377,6 +421,10 @@ Querying ``MPD``'s status
- ``updating_db``: ``job id``
- ``error``: if there is an error, returns message here
:program:`MPD` may omit lines which have no (known) value. Older
:program:`MPD` versions used to have a "magic" value for
"unknown", e.g. ":samp:`volume: -1`".
:command:`stats`
Displays statistics.
@@ -490,8 +538,36 @@ Controlling playback
:command:`stop`
Stops playing.
The current playlist
====================
The Queue
=========
.. note:: The "queue" used to be called "current playlist" or just
"playlist", but that was deemed confusing, because
"playlists" are also files containing a sequence of songs.
Those "playlist files" or "stored playlists" can be
:ref:`loaded into the queue <command_load>` and the queue
can be :ref:`saved into a playlist file <command_save>`, but
they are not to be confused with the queue.
Many of the command names in this section reflect the old
naming convention, but for the sake of compatibility, we
cannot rename commands.
There are two ways to address songs within the queue: by their
position and by their id.
The position is a 0-based index. It is unstable by design: if you
move, delete or insert songs, all following indices will change, and a
client can never be sure what song is behind a given index/position.
Song ids on the other hand are stable: an id is assigned to a song
when it is added, and will stay the same, no matter how much it is
moved around. Adding the same song twice will assign different ids to
them, and a deleted-and-readded song will have a new id. This way, a
client can always be sure the correct song is being used.
Many commands come in two flavors, one for each address type.
Whenever possible, ids should be used.
:command:`add {URI}`
Adds the file ``URI`` to the playlist
@@ -507,7 +583,7 @@ The current playlist
OK
:command:`clear`
Clears the current playlist.
Clears the queue.
.. _command_delete:
@@ -532,13 +608,13 @@ The current playlist
:command:`playlist`
Displays the current playlist.
Displays the queue.
Do not use this, instead use :ref:`playlistinfo
<command_playlistinfo>`.
:command:`playlistfind {TAG} {NEEDLE}`
Finds songs in the current playlist with strict
Finds songs in the queue with strict
matching.
:command:`playlistid {SONGID}`
@@ -556,7 +632,7 @@ The current playlist
:command:`playlistsearch {TAG} {NEEDLE}`
Searches case-insensitively for partial matches in the
current playlist.
queue.
:command:`plchanges {VERSION} [START:END]`
Displays changed songs currently in the playlist since
@@ -593,7 +669,7 @@ The current playlist
but address the songs with their id.
:command:`rangeid {ID} {START:END}` [#since_0_19]_
Since ``MPD``
Since :program:`MPD`
0.19 Specifies the portion of the
song that shall be played. ``START`` and
``END`` are offsets in seconds
@@ -603,7 +679,7 @@ The current playlist
playing cannot be manipulated this way.
:command:`shuffle [START:END]`
Shuffles the current playlist.
Shuffles the queue.
``START:END`` is optional and specifies
a range of songs.
@@ -657,6 +733,8 @@ remote playlists (absolute URI with a supported scheme).
between clients and the server, clients should not
compare this value with their local clock.
.. _command_load:
:command:`load {NAME} [START:END]`
Loads the playlist into the current queue. Playlist
plugins are supported. A range may be specified to load
@@ -687,8 +765,10 @@ remote playlists (absolute URI with a supported scheme).
Removes the playlist `NAME.m3u` from
the playlist directory.
.. _command_save:
:command:`save {NAME}`
Saves the current playlist to
Saves the queue to
`NAME.m3u` in the playlist directory.
The music database
@@ -773,7 +853,7 @@ The music database
:command:`list {TYPE} {FILTER} [group {GROUPTYPE}]`
Lists unique tags values of the specified type.
``TYPE`` can be any tag supported by
``MPD`` or
:program:`MPD` or
*file*.
Additional arguments may specify a :ref:`filter <filter_syntax>`.
@@ -792,10 +872,10 @@ The music database
``URI``.
Do not use this command. Do not manage a client-side
copy of ``MPD``'s database. That
copy of :program:`MPD`'s database. That
is fragile and adds huge overhead. It will break with
large databases. Instead, query
``MPD`` whenever you need
:program:`MPD` whenever you need
something.
.. _command_listallinfo:
@@ -806,16 +886,16 @@ The music database
as :ref:`lsinfo <command_lsinfo>`
Do not use this command. Do not manage a client-side
copy of ``MPD``'s database. That
copy of :program:`MPD`'s database. That
is fragile and adds huge overhead. It will break with
large databases. Instead, query
``MPD`` whenever you need
:program:`MPD` whenever you need
something.
:command:`listfiles {URI}`
Lists the contents of the directory
``URI``, including files are not
recognized by ``MPD``.
recognized by :program:`MPD`.
``URI`` can be a path relative to the
music directory or an URI understood by one of the
storage plugins. The response contains at least one
@@ -922,7 +1002,7 @@ Multiple storages can be "mounted" together, similar to the
`mount` command on many operating
systems, but without cooperation from the kernel. No
superuser privileges are necessary, beause this mapping exists
only inside the ``MPD`` process
only inside the :program:`MPD` process
.. _command_mount:
@@ -965,15 +1045,15 @@ Stickers
"Stickers" [#since_0_15]_ are pieces of
information attached to existing
``MPD`` objects (e.g. song files,
:program:`MPD` objects (e.g. song files,
directories, albums). Clients can create arbitrary name/value
pairs. ``MPD`` itself does not assume
pairs. :program:`MPD` itself does not assume
any special meaning in them.
The goal is to allow clients to share additional (possibly
dynamic) information about songs, which is neither stored on
the client (not available to other clients), nor stored in the
song files (``MPD`` has no write
song files (:program:`MPD` has no write
access).
Client developers should create a standard for common sticker
@@ -1015,14 +1095,21 @@ Connection settings
===================
:command:`close`
Closes the connection to ``MPD``.
``MPD`` will try to send the
Closes the connection to :program:`MPD`.
:program:`MPD` will try to send the
remaining output buffer before it actually closes the
connection, but that cannot be guaranteed. This command
will not generate a response.
Clients should not use this command; instead, they should just
close the socket.
:command:`kill`
Kills ``MPD``.
Kills :program:`MPD`.
Do not use this command. Send ``SIGTERM`` to :program:`MPD`
instead, or better: let your service manager handle :program:`MPD`
shutdown (e.g. :command:`systemctl stop mpd`).
:command:`password {PASSWORD}`
This is used for authentication with the server.
@@ -1056,7 +1143,7 @@ Connection settings
:command:`tagtypes clear`
Clear the list of tag types this client is interested
in. This means that ``MPD`` will
in. This means that :program:`MPD` will
not send any tags to this client.
:command:`tagtypes all`

@@ -54,7 +54,7 @@ Download the source tarball from the `MPD home page <https://musicpd.org>`_ and
In any case, you need:
* a C++14 compiler (e.g. gcc 6.0 or clang 3.9)
* `Meson 0.47 <http://mesonbuild.com/>`__ and `Ninja
* `Meson 0.47.2 <http://mesonbuild.com/>`__ and `Ninja
<https://ninja-build.org/>`__
* Boost 1.58
* pkg-config
@@ -67,6 +67,7 @@ For example, the following installs a fairly complete list of build dependencies
.. code-block:: none
apt install g++ \
libpcre3-dev \
libmad0-dev libmpg123-dev libid3tag0-dev \
libflac-dev libvorbis-dev libopus-dev \
libadplug-dev libaudiofile-dev libsndfile1-dev libfaad-dev \
@@ -967,7 +968,16 @@ All :program:`MPD` crashes are bugs which must be fixed by a developer, and you
A crash bug report needs to contain a "backtrace".
First of all, your :program:`MPD` executable must not be "stripped" (i.e. debug information deleted). The executables shipped with Linux distributions are usually stripped, but some have so-called "debug" packages (package mpd-dbg or mpd-dbgsym on Debian, mpd-debug on other distributions). Make sure this package is installed.
First of all, your :program:`MPD` executable must not be "stripped"
(i.e. debug information deleted). The executables shipped with Linux
distributions are usually stripped, but some have so-called "debug"
packages (package :file:`mpd-dbgsym` or :file:`mpd-dbg` on Debian,
:file:`mpd-debug` on other distributions). Make sure this package is
installed.
If you built :program:`MPD` from sources, please recompile with Meson
option ":code:`--buildtype=debug -Db_ndebug=false`", because this will
add more helpful information to the backtrace.
You can extract the backtrace from a core dump, or by running :program:`MPD` in a debugger, e.g.:
@@ -976,4 +986,5 @@ You can extract the backtrace from a core dump, or by running :program:`MPD` in
gdb --args mpd --stdout --no-daemon --verbose
run
As soon as you have reproduced the crash, type "bt" on the gdb command prompt. Copy the output to your bug report.
As soon as you have reproduced the crash, type ":command:`bt`" on the
gdb command prompt. Copy the output to your bug report.

@@ -1,8 +1,8 @@
project(
'mpd',
['c', 'cpp'],
version: '0.21',
meson_version: '>= 0.47',
version: '0.21.3',
meson_version: '>= 0.47.2',
default_options: [
'c_std=c99',
'cpp_std=c++14'
@@ -172,6 +172,11 @@ inc = include_directories(
)
boost_dep = dependency('boost', version: '>= 1.58')
if boost_dep.version() == '1.67'
# https://github.com/MusicPlayerDaemon/MPD/pull/384
# https://github.com/boostorg/lockfree/commit/12726cda009a855073b9bedbdce57b6ce7763da2
warning('Your Boost version 1.67 is known to be buggy, and the MPD build will fail. Please upgrade to Boost 1.68 or later.')
endif
sources = [
version_cxx,
@@ -310,6 +315,7 @@ subdir('src/lib/gcrypt')
subdir('src/lib/wrap')
subdir('src/lib/nfs')
subdir('src/lib/oss')
subdir('src/lib/pcre')
subdir('src/lib/pulse')
subdir('src/lib/sndio')
subdir('src/lib/sqlite')

@@ -133,7 +133,7 @@ option('wavpack', type: 'feature', description: 'WavPack decoder plugin')
option('wildmidi', type: 'feature', description: 'WildMidi decoder plugin')
#
# Decoder plugins
# Encoder plugins
#
option('vorbisenc', type: 'feature', description: 'Vorbis encoder plugin')
@@ -176,6 +176,7 @@ option('expat', type: 'feature', description: 'Expat XML support')
option('icu', type: 'feature', description: 'Use libicu for Unicode')
option('iconv', type: 'feature', description: 'Use iconv() for character set conversion')
option('libwrap', type: 'feature', description: 'libwrap support')
option('pcre', type: 'feature', description: 'Enable regular expression support (using libpcre)')
option('sqlite', type: 'feature', description: 'SQLite database support (for stickers)')
option('yajl', type: 'feature', description: 'libyajl for YAML support')
option('zlib', type: 'feature', description: 'zlib support (for database compression)')

@@ -112,8 +112,8 @@ liblame = AutotoolsProject(
)
ffmpeg = FfmpegProject(
'http://ffmpeg.org/releases/ffmpeg-4.0.2.tar.xz',
'a95c0cc9eb990e94031d2183f2e6e444cc61c99f6f182d1575c433d62afb2f97',
'http://ffmpeg.org/releases/ffmpeg-4.1.tar.xz',
'a38ec4d026efb58506a99ad5cd23d5a9793b4bf415f2c4c2e9c1bb444acd1994',
'lib/libavcodec.a',
[
'--disable-shared', '--enable-static',
@@ -341,8 +341,8 @@ ffmpeg = FfmpegProject(
)
curl = AutotoolsProject(
'http://curl.haxx.se/download/curl-7.61.1.tar.xz',
'3d5913d6a39bd22e68e34dff697fd6e4c3c81563f580c76fca2009315cd81891',
'http://curl.haxx.se/download/curl-7.62.0.tar.xz',
'dab5643a5fe775ae92570b9f3df6b0ef4bc2a827a959361fb130c73b721275c1',
'lib/libcurl.a',
[
'--disable-shared', '--enable-static',

@@ -18,5 +18,5 @@ class ZlibProject(Project):
'INCLUDE_PATH='+ os.path.join(toolchain.install_prefix, 'include'),
'LIBRARY_PATH=' + os.path.join(toolchain.install_prefix, 'lib'),
'BINARY_PATH=' + os.path.join(toolchain.install_prefix, 'bin'),
'SHARED_MODE=1'],
],
cwd=src, env=toolchain.env)

@@ -30,6 +30,10 @@
#include "util/RuntimeError.hxx"
#include "system/Error.hxx"
#ifdef ENABLE_SYSTEMD_DAEMON
#include <systemd/sd-daemon.h>
#endif
#include <assert.h>
#include <string.h>
#include <fcntl.h>
@@ -139,6 +143,16 @@ log_init(const ConfigData &config, bool verbose, bool use_stdout)
if (param == nullptr) {
/* no configuration: default to syslog (if
available) */
#ifdef ENABLE_SYSTEMD_DAEMON
if (sd_booted() &&
getenv("NOTIFY_SOCKET") != nullptr) {
/* if MPD was started as a systemd
service, default to journal (which
is connected to fd=2) */
out_fd = STDOUT_FILENO;
return;
}
#endif
#ifndef HAVE_SYSLOG
throw std::runtime_error("config parameter 'log_file' not found");
#endif

@@ -37,11 +37,4 @@
#error config.h missing
#endif
#if defined(__linux__) && !defined(NDEBUG) && defined(ENABLE_LARGEFILE) && \
defined(_FEATURES_H) && defined(__i386__) && \
!defined(__USE_FILE_OFFSET64)
/* on i386, check if LFS is enabled */
#error config.h was included too late
#endif
#endif

@@ -105,45 +105,11 @@ ffmpeg_finish() noexcept
av_dict_free(&avformat_options);
}
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 25, 0) /* FFmpeg 3.1 */
gcc_pure
static const AVCodecParameters &
GetCodecParameters(const AVStream &stream) noexcept
{
return *stream.codecpar;
}
gcc_pure
static AVSampleFormat
GetSampleFormat(const AVCodecParameters &codec_params) noexcept
{
return AVSampleFormat(codec_params.format);
}
#else
gcc_pure
static const AVCodecContext &
GetCodecParameters(const AVStream &stream) noexcept
{
return *stream.codec;
}
gcc_pure
static AVSampleFormat
GetSampleFormat(const AVCodecContext &codec_context) noexcept
{
return codec_context.sample_fmt;
}
#endif
gcc_pure
static bool
IsAudio(const AVStream &stream) noexcept
{
return GetCodecParameters(stream).codec_type == AVMEDIA_TYPE_AUDIO;
return stream.codecpar->codec_type == AVMEDIA_TYPE_AUDIO;
}
gcc_pure
@@ -278,8 +244,6 @@ FfmpegSendFrame(DecoderClient &client, InputStream &is,
codec_context.bit_rate / 1000);
}
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 37, 0)
static DecoderCommand
FfmpegReceiveFrames(DecoderClient &client, InputStream &is,
AVCodecContext &codec_context,
@@ -324,8 +288,6 @@ FfmpegReceiveFrames(DecoderClient &client, InputStream &is,
}
}
#endif
/**
* Decode an #AVPacket and send the resulting PCM data to the decoder
* API.
@@ -357,7 +319,6 @@ ffmpeg_send_packet(DecoderClient &client, InputStream &is,
stream.time_base));
}
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 37, 0)
bool eof = false;
int err = avcodec_send_packet(&codec_context, &packet);
@@ -386,30 +347,6 @@ ffmpeg_send_packet(DecoderClient &client, InputStream &is,
if (eof)
cmd = DecoderCommand::STOP;
#else
DecoderCommand cmd = DecoderCommand::NONE;
while (packet.size > 0 && cmd == DecoderCommand::NONE) {
int got_frame = 0;
int len = avcodec_decode_audio4(&codec_context,
&frame, &got_frame,
&packet);
if (len < 0) {
/* if error, we skip the frame */
LogFfmpegError(len, "decoding failed, frame skipped");
break;
}
packet.data += len;
packet.size -= len;
if (!got_frame || frame.nb_samples <= 0)
continue;
cmd = FfmpegSendFrame(client, is, codec_context,
frame, skip_bytes,
buffer);
}
#endif
return cmd;
}
@@ -585,11 +522,7 @@ FfmpegDecode(DecoderClient &client, InputStream &input,
AVStream &av_stream = *format_context.streams[audio_stream];
#if LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(57, 5, 0)
AVCodecContext *codec_context = av_stream.codec;
#endif
const auto &codec_params = GetCodecParameters(av_stream);
const auto &codec_params = *av_stream.codecpar;
const AVCodecDescriptor *codec_descriptor =
avcodec_descriptor_get(codec_params.codec_id);
@@ -604,7 +537,6 @@ FfmpegDecode(DecoderClient &client, InputStream &input,
return;
}
#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(57, 5, 0)
AVCodecContext *codec_context = avcodec_alloc_context3(codec);
if (codec_context == nullptr) {
LogError(ffmpeg_domain, "avcodec_alloc_context3() failed");
@@ -615,26 +547,7 @@ FfmpegDecode(DecoderClient &client, InputStream &input,
avcodec_free_context(&codec_context);
};
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 25, 0) /* FFmpeg 3.1 */
avcodec_parameters_to_context(codec_context, av_stream.codecpar);
#endif
#endif
const SampleFormat sample_format =
ffmpeg_sample_format(GetSampleFormat(codec_params));
if (sample_format == SampleFormat::UNDEFINED) {
// (error message already done by ffmpeg_sample_format())
return;
}
const auto audio_format = CheckAudioFormat(codec_params.sample_rate,
sample_format,
codec_params.channels);
/* the audio format must be read from AVCodecContext by now,
because avcodec_open() has been demonstrated to fill bogus
values into AVCodecContext.channels - a change that will be
reverted later by avcodec_decode_audio3() */
const int open_result = avcodec_open2(codec_context, codec, nullptr);
if (open_result < 0) {
@@ -642,11 +555,16 @@ FfmpegDecode(DecoderClient &client, InputStream &input,
return;
}
#if LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(57, 5, 0)
AtScopeExit(codec_context) {
avcodec_close(codec_context);
};
#endif
const SampleFormat sample_format =
ffmpeg_sample_format(codec_context->sample_fmt);
if (sample_format == SampleFormat::UNDEFINED) {
// (error message already done by ffmpeg_sample_format())
return;
}
const auto audio_format = CheckAudioFormat(codec_context->sample_rate,
sample_format,
codec_context->channels);
const SignedSongTime total_time =
av_stream.duration != (int64_t)AV_NOPTS_VALUE
@@ -697,6 +615,10 @@ FfmpegDecode(DecoderClient &client, InputStream &input,
/* end of file */
break;
AtScopeExit(&packet) {
av_packet_unref(&packet);
};
FfmpegCheckTag(client, input, format_context, audio_stream);
if (packet.size > 0 && packet.stream_index == audio_stream) {
@@ -710,12 +632,6 @@ FfmpegDecode(DecoderClient &client, InputStream &input,
min_frame = 0;
} else
cmd = client.GetCommand();
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(56, 25, 100)
av_packet_unref(&packet);
#else
av_free_packet(&packet);
#endif
}
}
@@ -769,10 +685,10 @@ FfmpegScanStream(AVFormatContext &format_context,
handler.OnDuration(FromFfmpegTime(format_context.duration,
AV_TIME_BASE_Q));
const auto &codec_params = GetCodecParameters(stream);
const auto &codec_params = *stream.codecpar;
try {
handler.OnAudioFormat(CheckAudioFormat(codec_params.sample_rate,
ffmpeg_sample_format(GetSampleFormat(codec_params)),
ffmpeg_sample_format(AVSampleFormat(codec_params.format)),
codec_params.channels));
} catch (...) {
}

@@ -194,6 +194,19 @@ public:
void ReplaceSocketList(pollfd *pfds, unsigned n) noexcept;
#endif
/**
* Invoke a function for each socket which has become ready.
*/
template<typename F>
void ForEachReturnedEvent(F &&f) noexcept {
for (auto &i : fds) {
if (i.GetReturnedEvents() != 0) {
f(i.GetSocket(), i.GetReturnedEvents());
i.ClearReturnedEvents();
}
}
}
protected:
/**
* Override this method and update the socket registrations.

@@ -20,6 +20,7 @@
#include "config.h"
#include "Thread.hxx"
#include "thread/Name.hxx"
#include "thread/Slack.hxx"
#include "thread/Util.hxx"
#include "Log.hxx"
@@ -46,6 +47,8 @@ EventThread::Run() noexcept
SetThreadName(realtime ? "rtio" : "io");
if (realtime) {
SetThreadTimerSlackUS(10);
try {
SetThreadRealtime();
} catch (...) {

@@ -34,7 +34,6 @@
#include "util/Domain.hxx"
#include "util/RuntimeError.hxx"
#include "util/StringCompare.hxx"
#include "util/ReusableArray.hxx"
#include "util/ASCII.hxx"
#include "Log.hxx"
#include "event/MultiSocketMonitor.hxx"
@@ -69,7 +68,7 @@ class AlsaInputStream final
snd_pcm_t *const capture_handle;
const size_t frame_size;
ReusableArray<pollfd> pfd_buffer;
AlsaNonBlockPcm non_block;
DeferEvent defer_invalidate_sockets;
@@ -180,12 +179,14 @@ AlsaInputStream::PrepareSockets() noexcept
return std::chrono::steady_clock::duration(-1);
}
return PrepareAlsaPcmSockets(*this, capture_handle, pfd_buffer);
return non_block.PrepareSockets(*this, capture_handle);
}
void
AlsaInputStream::DispatchSockets() noexcept
{
non_block.DispatchSockets(*this, capture_handle);
const std::lock_guard<Mutex> protect(mutex);
auto w = PrepareWriteBuffer();

@@ -23,8 +23,7 @@
#include "util/RuntimeError.hxx"
std::chrono::steady_clock::duration
PrepareAlsaPcmSockets(MultiSocketMonitor &m, snd_pcm_t *pcm,
ReusableArray<pollfd> &pfd_buffer)
AlsaNonBlockPcm::PrepareSockets(MultiSocketMonitor &m, snd_pcm_t *pcm)
{
int count = snd_pcm_poll_descriptors_count(pcm);
if (count <= 0) {
@@ -50,9 +49,32 @@ PrepareAlsaPcmSockets(MultiSocketMonitor &m, snd_pcm_t *pcm,
return std::chrono::steady_clock::duration(-1);
}
void
AlsaNonBlockPcm::DispatchSockets(MultiSocketMonitor &m,
snd_pcm_t *pcm) noexcept
{
int count = snd_pcm_poll_descriptors_count(pcm);
if (count <= 0)
return;
const auto pfds = pfd_buffer.Get(count), end = pfds + count;
auto *i = pfds;
m.ForEachReturnedEvent([&i, end](SocketDescriptor s, unsigned events){
if (i >= end)
return;
i->fd = s.Get();
i->events = i->revents = events;
++i;
});
unsigned short dummy;
snd_pcm_poll_descriptors_revents(pcm, pfds, i - pfds, &dummy);
}
std::chrono::steady_clock::duration
PrepareAlsaMixerSockets(MultiSocketMonitor &m, snd_mixer_t *mixer,
ReusableArray<pollfd> &pfd_buffer) noexcept
AlsaNonBlockMixer::PrepareSockets(MultiSocketMonitor &m, snd_mixer_t *mixer) noexcept
{
int count = snd_mixer_poll_descriptors_count(mixer);
if (count <= 0) {
@@ -69,3 +91,27 @@ PrepareAlsaMixerSockets(MultiSocketMonitor &m, snd_mixer_t *mixer,
m.ReplaceSocketList(pfds, count);
return std::chrono::steady_clock::duration(-1);
}
void
AlsaNonBlockMixer::DispatchSockets(MultiSocketMonitor &m,
snd_mixer_t *mixer) noexcept
{
int count = snd_mixer_poll_descriptors_count(mixer);
if (count <= 0)
return;
const auto pfds = pfd_buffer.Get(count), end = pfds + count;
auto *i = pfds;
m.ForEachReturnedEvent([&i, end](SocketDescriptor s, unsigned events){
if (i >= end)
return;
i->fd = s.Get();
i->events = i->revents = events;
++i;
});
unsigned short dummy;
snd_mixer_poll_descriptors_revents(mixer, pfds, i - pfds, &dummy);
}

@@ -30,23 +30,42 @@
class MultiSocketMonitor;
/**
* Update #MultiSocketMonitor's socket list from
* snd_pcm_poll_descriptors(). To be called from
* MultiSocketMonitor::PrepareSockets().
*
* Throws exception on error.
* Helper class for #MultiSocketMonitor's virtual methods which
* manages the file descriptors for a #snd_pcm_t.
*/
std::chrono::steady_clock::duration
PrepareAlsaPcmSockets(MultiSocketMonitor &m, snd_pcm_t *pcm,
ReusableArray<pollfd> &pfd_buffer);
class AlsaNonBlockPcm {
ReusableArray<pollfd> pfd_buffer;
public:
/**
* Throws on error.
*/
std::chrono::steady_clock::duration PrepareSockets(MultiSocketMonitor &m,
snd_pcm_t *pcm);
/**
* Wrapper for snd_pcm_poll_descriptors_revents(), to be
* called from MultiSocketMonitor::DispatchSockets().
*/
void DispatchSockets(MultiSocketMonitor &m, snd_pcm_t *pcm) noexcept;
};
/**
* Update #MultiSocketMonitor's socket list from
* snd_mixer_poll_descriptors(). To be called from
* MultiSocketMonitor::PrepareSockets().
* Helper class for #MultiSocketMonitor's virtual methods which
* manages the file descriptors for a #snd_mixer_t.
*/
std::chrono::steady_clock::duration
PrepareAlsaMixerSockets(MultiSocketMonitor &m, snd_mixer_t *mixer,
ReusableArray<pollfd> &pfd_buffer) noexcept;
class AlsaNonBlockMixer {
ReusableArray<pollfd> pfd_buffer;
public:
std::chrono::steady_clock::duration PrepareSockets(MultiSocketMonitor &m,
snd_mixer_t *mixer) noexcept;
/**
* Wrapper for snd_mixer_poll_descriptors_revents(), to be
* called from MultiSocketMonitor::DispatchSockets().
*/
void DispatchSockets(MultiSocketMonitor &m, snd_mixer_t *mixer) noexcept;
};
#endif

@@ -1,6 +1,6 @@
libavformat_dep = dependency('libavformat', version: '>= 56.1', required: get_option('ffmpeg'))
libavcodec_dep = dependency('libavcodec', version: '>= 56.1', required: get_option('ffmpeg'))
libavutil_dep = dependency('libavutil', version: '>= 54.3', required: get_option('ffmpeg'))
libavformat_dep = dependency('libavformat', version: '>= 57.40', required: get_option('ffmpeg'))
libavcodec_dep = dependency('libavcodec', version: '>= 57.48', required: get_option('ffmpeg'))
libavutil_dep = dependency('libavutil', version: '>= 55.27', required: get_option('ffmpeg'))
enable_ffmpeg = libavformat_dep.found() and libavcodec_dep.found() and libavutil_dep.found()
conf.set('ENABLE_FFMPEG', enable_ffmpeg)
@@ -26,4 +26,9 @@ ffmpeg = static_library(
ffmpeg_dep = declare_dependency(
link_with: ffmpeg,
dependencies: [
libavformat_dep,
libavcodec_dep,
libavutil_dep,
],
)

@@ -0,0 +1,66 @@
/*
* Copyright 2007-2018 Content Management AG
* All rights reserved.
*
* author: Max Kellermann <mk@cm4all.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.
*/
#ifndef REGEX_POINTER_HXX
#define REGEX_POINTER_HXX
#include "util/StringView.hxx"
#include "util/Compiler.h"
#include <pcre.h>
#include <array>
class RegexPointer {
protected:
pcre *re = nullptr;
pcre_extra *extra = nullptr;
unsigned n_capture = 0;
public:
constexpr bool IsDefined() const noexcept {
return re != nullptr;
}
gcc_pure
bool Match(StringView s) const noexcept {
/* we don't need the data written to ovector, but PCRE can
omit internal allocations if we pass a buffer to
pcre_exec() */
std::array<int, 16> ovector;
return pcre_exec(re, extra, s.data, s.size,
0, 0, &ovector.front(), ovector.size()) >= 0;
}
};
#endif

@@ -0,0 +1,71 @@
/*
* Copyright 2007-2018 Content Management AG
* All rights reserved.
*
* author: Max Kellermann <mk@cm4all.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 "UniqueRegex.hxx"
#include "util/RuntimeError.hxx"
void
UniqueRegex::Compile(const char *pattern, bool anchored, bool capture,
bool caseless)
{
constexpr int default_options = PCRE_DOTALL|PCRE_NO_AUTO_CAPTURE|PCRE_UTF8;
int options = default_options;
if (anchored)
options |= PCRE_ANCHORED;
if (capture)
options &= ~PCRE_NO_AUTO_CAPTURE;
if (caseless)
options |= PCRE_CASELESS;
const char *error_string;
int error_offset;
re = pcre_compile(pattern, options, &error_string, &error_offset, nullptr);
if (re == nullptr)
throw FormatRuntimeError("Error in regex at offset %d: %s",
error_offset, error_string);
int study_options = 0;
#ifdef PCRE_CONFIG_JIT
study_options |= PCRE_STUDY_JIT_COMPILE;
#endif
extra = pcre_study(re, study_options, &error_string);
if (extra == nullptr && error_string != nullptr) {
pcre_free(re);
re = nullptr;
throw FormatRuntimeError("Regex study error: %s", error_string);
}
int n;
if (capture && pcre_fullinfo(re, extra, PCRE_INFO_CAPTURECOUNT, &n) == 0)
n_capture = n;
}

@@ -0,0 +1,79 @@
/*
* Copyright 2007-2018 Content Management AG
* All rights reserved.
*
* author: Max Kellermann <mk@cm4all.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.
*/
#ifndef UNIQUE_REGEX_HXX
#define UNIQUE_REGEX_HXX
#include "RegexPointer.hxx"
#include "util/StringView.hxx"
#include <algorithm>
#include <pcre.h>
class UniqueRegex : public RegexPointer {
public:
UniqueRegex() = default;
UniqueRegex(const char *pattern, bool anchored, bool capture,
bool caseless) {
Compile(pattern, anchored, capture, caseless);
}
UniqueRegex(UniqueRegex &&src) noexcept:RegexPointer(src) {
src.re = nullptr;
src.extra = nullptr;
}
~UniqueRegex() noexcept {
pcre_free(re);
#ifdef PCRE_CONFIG_JIT
pcre_free_study(extra);
#else
pcre_free(extra);
#endif
}
UniqueRegex &operator=(UniqueRegex &&src) {
using std::swap;
swap<RegexPointer>(*this, src);
return *this;
}
/**
* Throws std::runtime_error on error.
*/
void Compile(const char *pattern, bool anchored, bool capture,
bool caseless);
};
#endif

21
src/lib/pcre/meson.build Normal file

@@ -0,0 +1,21 @@
pcre_dep = dependency('libpcre', required: get_option('pcre'))
conf.set('HAVE_PCRE', pcre_dep.found())
if not pcre_dep.found()
subdir_done()
endif
pcre = static_library(
'pcre',
'UniqueRegex.cxx',
include_directories: inc,
dependencies: [
pcre_dep,
],
)
pcre_dep = declare_dependency(
link_with: pcre,
dependencies: [
pcre_dep,
],
)

@@ -2,10 +2,15 @@ libflac_dep = dependency('flac', version: '>= 1.2', required: get_option('flac')
libopus_dep = dependency('opus', required: get_option('opus'))
libvorbis_dep = dependency('vorbis', required: get_option('vorbis'))
if need_encoder
libvorbisenc_dep = dependency('vorbisenc', required: get_option('vorbisenc'))
else
libvorbisenc_dep = dependency('', required: false)
libvorbisenc_dep = dependency('', required: false)
if need_encoder and not get_option('vorbisenc').disabled()
if libvorbis_dep.found()
libvorbisenc_dep = dependency('vorbisenc', required: get_option('vorbisenc'))
elif get_option('vorbisenc').enabled()
error('Cannot build the Vorbis encoder without the Vorbis decoder')
else
message('Disabling the Vorbis encoder because the Vorbis decoder is disabled as well')
endif
endif
if libopus_dep.found() or libvorbis_dep.found() or libvorbisenc_dep.found()
@@ -14,7 +19,7 @@ else
libogg_dep = dependency('', required: false)
endif
if not libogg_dep.found() or not libflac_dep.found()
if not libogg_dep.found() and not libflac_dep.found()
xiph_dep = dependency('', required: false)
ogg_dep = dependency('', required: false)
flac_dep = dependency('', required: false)

@@ -1,4 +1,5 @@
zlib_dep = dependency('zlib', required: get_option('zlib'))
conf.set('ENABLE_ZLIB', zlib_dep.found())
if not zlib_dep.found()
subdir_done()
endif
@@ -14,4 +15,7 @@ zlib = static_library(
zlib_dep = declare_dependency(
link_with: zlib,
dependencies: [
zlib_dep,
],
)

@@ -26,7 +26,6 @@
#include "event/DeferEvent.hxx"
#include "event/Call.hxx"
#include "util/ASCII.hxx"
#include "util/ReusableArray.hxx"
#include "util/Domain.hxx"
#include "util/RuntimeError.hxx"
#include "Log.hxx"
@@ -48,7 +47,7 @@ class AlsaMixerMonitor final : MultiSocketMonitor {
snd_mixer_t *mixer;
ReusableArray<pollfd> pfd_buffer;
AlsaNonBlockMixer non_block;
public:
AlsaMixerMonitor(EventLoop &_loop, snd_mixer_t *_mixer)
@@ -110,7 +109,7 @@ AlsaMixerMonitor::PrepareSockets() noexcept
return std::chrono::steady_clock::duration(-1);
}
return PrepareAlsaMixerSockets(*this, mixer, pfd_buffer);
return non_block.PrepareSockets(*this, mixer);
}
void
@@ -118,6 +117,8 @@ AlsaMixerMonitor::DispatchSockets() noexcept
{
assert(mixer != nullptr);
non_block.DispatchSockets(*this, mixer);
int err = snd_mixer_handle_events(mixer);
if (err < 0) {
FormatError(alsa_mixer_domain,

@@ -135,6 +135,13 @@ static int set_normalized_volume(snd_mixer_elem_t *elem,
if (err < 0)
return err;
/* two special cases to avoid rounding errors at 0% and
100% */
if (volume <= 0)
return set_raw[ctl_dir](elem, min);
else if (volume >= 1)
return set_raw[ctl_dir](elem, max);
value = lrint_dir(volume * (max - min), dir) + min;
return set_raw[ctl_dir](elem, value);
}
@@ -143,7 +150,7 @@ static int set_normalized_volume(snd_mixer_elem_t *elem,
100% */
if (volume <= 0)
return set_dB[ctl_dir](elem, min, dir);
else if (volume >= 100)
else if (volume >= 1)
return set_dB[ctl_dir](elem, max, dir);
if (use_linear_dB_scale(min, max)) {

@@ -53,20 +53,6 @@ class IPv4Address {
uint8_t c, uint8_t d) noexcept {
return {{{ a, b, c, d }}};
}
/**
* @param x the 32 bit IP address in network byte order
*/
static constexpr struct in_addr ConstructInAddrBE(uint32_t x) noexcept {
return (struct in_addr){{.S_addr=x}};
}
/**
* @param x the 32 bit IP address in host byte order
*/
static constexpr struct in_addr ConstructInAddr(uint32_t x) noexcept {
return ConstructInAddr(x >> 24, x >> 16, x >> 8, x);
}
#else
#ifdef __BIONIC__
@@ -78,11 +64,19 @@ class IPv4Address {
return ToBE32((a << 24) | (b << 16) | (c << 8) | d);
}
static constexpr struct in_addr ConstructInAddr(uint8_t a, uint8_t b,
uint8_t c, uint8_t d) noexcept {
return { ConstructInAddrT(a, b, c, d) };
}
#endif
/**
* @param x the 32 bit IP address in network byte order
*/
static constexpr struct in_addr ConstructInAddrBE(uint32_t x) noexcept {
return { x };
struct in_addr ia{};
ia.s_addr = x;
return ia;
}
/**
@@ -92,26 +86,16 @@ class IPv4Address {
return ConstructInAddrBE(ToBE32(x));
}
static constexpr struct in_addr ConstructInAddr(uint8_t a, uint8_t b,
uint8_t c, uint8_t d) noexcept {
return { ConstructInAddrT(a, b, c, d) };
}
#endif
/**
* @param port the port number in host byte order
*/
static constexpr struct sockaddr_in Construct(struct in_addr address,
uint16_t port) noexcept {
return {
#ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN
sizeof(struct sockaddr_in),
#endif
AF_INET,
ToBE16(port),
address,
{},
};
struct sockaddr_in sin{};
sin.sin_family = AF_INET;
sin.sin_port = ToBE16(port);
sin.sin_addr = address;
return sin;
}
/**

@@ -68,16 +68,12 @@ class IPv6Address {
static constexpr struct sockaddr_in6 Construct(struct in6_addr address,
uint16_t port,
uint32_t scope_id) noexcept {
return {
#if defined(__APPLE__)
sizeof(struct sockaddr_in6),
#endif
AF_INET6,
ToBE16(port),
0,
address,
scope_id,
};
struct sockaddr_in6 sin{};
sin.sin6_family = AF_INET6;
sin.sin6_port = ToBE16(port);
sin.sin6_addr = address;
sin.sin6_scope_id = scope_id;
return sin;
}
public:

@@ -10,14 +10,6 @@ if have_tcp and not get_option('ipv6').disabled()
if not have_ipv6 and get_option('ipv6').enabled()
error('IPv6 not supported by OS')
endif
conf.set('HAVE_STRUCT_SOCKADDR_IN_SIN_LEN', c_compiler.has_member('struct sockaddr_in', 'sin_len', prefix: '''
#ifdef _WIN32
#include <winsock2.h>
#include <ws2tcpip.h>
#else
#include <netinet/in.h>
#endif'''))
else
have_ipv6 = false
endif

@@ -347,10 +347,22 @@ AudioOutputControl::LockAllowPlay() noexcept
void
AudioOutputControl::LockRelease() noexcept
{
if (always_on)
LockPauseAsync();
if (output->mixer != nullptr &&
(!always_on || !output->SupportsPause()))
/* the device has no pause mode: close the mixer,
unless its "global" flag is set (checked by
mixer_auto_close()) */
mixer_auto_close(output->mixer);
const std::lock_guard<Mutex> protect(mutex);
assert(!open || !fail_timer.IsDefined());
assert(allow_play);
if (IsOpen())
CommandWait(Command::RELEASE);
else
LockCloseWait();
fail_timer.Reset();
}
void

@@ -131,6 +131,12 @@ class AudioOutputControl {
CLOSE,
PAUSE,
/**
* Close or pause the device, depending on the
* #always_on setting.
*/
RELEASE,
/**
* Drains the internal (hardware) buffers of the device. This
* operation may take a while to complete.

@@ -124,9 +124,14 @@ FilteredAudioOutput::OpenOutputAndConvert(AudioFormat desired_audio_format)
void
FilteredAudioOutput::CloseOutput(bool drain) noexcept
{
if (drain)
Drain();
else
if (drain) {
try {
Drain();
} catch (...) {
FormatError(std::current_exception(),
"Failed to drain %s", GetLogName());
}
} else
Cancel();
output->Close();

@@ -159,6 +159,8 @@ public:
/**
* Wait until the device has finished playing.
*
* Throws on error.
*/
virtual void Drain() {}

@@ -41,8 +41,10 @@ AudioOutputSource::Open(const AudioFormat audio_format, const MusicPipe &_pipe,
{
assert(audio_format.IsValid());
if (!IsOpen() || &_pipe != &pipe.GetPipe())
if (!IsOpen() || &_pipe != &pipe.GetPipe()) {
current_chunk = nullptr;
pipe.Init(_pipe);
}
/* (re)open the filter */

@@ -443,8 +443,7 @@ AudioOutputControl::Task() noexcept
case Command::PAUSE:
if (!open) {
/* the output has failed after
audio_output_all_pause() has
submitted the PAUSE command; bail
the PAUSE command was submitted; bail
out */
CommandFinished();
break;
@@ -457,6 +456,35 @@ AudioOutputControl::Task() noexcept
the new command first */
continue;
case Command::RELEASE:
if (!open) {
/* the output has failed after
the PAUSE command was submitted; bail
out */
CommandFinished();
break;
}
if (always_on) {
/* in "always_on" mode, the output is
paused instead of being closed;
however we need to flush the
AudioOutputSource because its data
have been invalidated by stopping
the actual playback */
source.Cancel();
InternalPause();
} else {
InternalClose(false);
CommandFinished();
}
/* don't "break" here: this might cause
Play() to be called when command==CLOSE
ends the paused state - "continue" checks
the new command first */
continue;
case Command::DRAIN:
if (open)
InternalDrain();

@@ -33,6 +33,7 @@
#include "util/RuntimeError.hxx"
#include "util/Domain.hxx"
#include "util/ConstBuffer.hxx"
#include "util/ScopeExit.hxx"
#include "util/StringView.hxx"
#include "event/MultiSocketMonitor.hxx"
#include "event/DeferEvent.hxx"
@@ -108,6 +109,13 @@ class AlsaOutput final
*/
snd_pcm_uframes_t period_frames;
/**
* If snd_pcm_avail() goes above this value and no more data
* is available in the #ring_buffer, we need to play some
* silence.
*/
snd_pcm_sframes_t max_avail_frames;
/**
* Is this a buggy alsa-lib version, which needs a workaround
* for the snd_pcm_drain() bug always returning -EAGAIN? See
@@ -139,6 +147,16 @@ class AlsaOutput final
*/
bool must_prepare;
/**
* Has snd_pcm_writei() been called successfully at least once
* since the PCM was prepared?
*
* This is necessary to work around a kernel bug which causes
* snd_pcm_drain() to return -EAGAIN forever in non-blocking
* mode if snd_pcm_writei() was never called.
*/
bool written;
bool drain;
/**
@@ -148,10 +166,7 @@ class AlsaOutput final
*/
uint8_t *silence;
/**
* For PrepareAlsaPcmSockets().
*/
ReusableArray<pollfd> pfd_buffer;
AlsaNonBlockPcm non_block;
/**
* For copying data from OutputThread to IOThread.
@@ -260,10 +275,12 @@ private:
/**
* Drain all buffers. To be run in #EventLoop's thread.
*
* Throws on error.
*
* @return true if draining is complete, false if this method
* needs to be called again later
*/
bool DrainInternal() noexcept;
bool DrainInternal();
/**
* Stop playback immediately, dropping all buffers. To be run
@@ -298,14 +315,18 @@ private:
auto frames_written = snd_pcm_writei(pcm, period_buffer.GetHead(),
period_buffer.GetFrames(out_frame_size));
if (frames_written > 0)
if (frames_written > 0) {
written = true;
period_buffer.ConsumeFrames(frames_written,
out_frame_size);
}
return frames_written;
}
void LockCaughtError() noexcept {
period_buffer.Clear();
const std::lock_guard<Mutex> lock(mutex);
error = std::current_exception();
active = false;
@@ -480,6 +501,10 @@ AlsaOutput::Setup(AudioFormat &audio_format,
period_frames = alsa_period_size;
/* generate silence if there's less than once period of data
in the ALSA-PCM buffer */
max_avail_frames = hw_result.buffer_size - hw_result.period_size;
silence = new uint8_t[snd_pcm_frames_to_bytes(pcm, alsa_period_size)];
snd_pcm_format_set_silence(hw_result.format, silence,
alsa_period_size * audio_format.channels);
@@ -660,6 +685,8 @@ AlsaOutput::Open(AudioFormat &audio_format)
active = false;
must_prepare = false;
written = false;
error = {};
}
inline int
@@ -691,6 +718,7 @@ AlsaOutput::Recover(int err) noexcept
case SND_PCM_STATE_SETUP:
case SND_PCM_STATE_XRUN:
period_buffer.Rewind();
written = false;
err = snd_pcm_prepare(pcm);
break;
case SND_PCM_STATE_DISCONNECTED:
@@ -713,13 +741,8 @@ AlsaOutput::Recover(int err) noexcept
}
inline bool
AlsaOutput::DrainInternal() noexcept
AlsaOutput::DrainInternal()
{
if (snd_pcm_state(pcm) != SND_PCM_STATE_RUNNING) {
CancelInternal();
return true;
}
/* drain ring_buffer */
CopyRingToPeriodBuffer();
@@ -732,28 +755,42 @@ AlsaOutput::DrainInternal() noexcept
/* drain period_buffer */
if (!period_buffer.IsEmpty()) {
auto frames_written = WriteFromPeriodBuffer();
if (frames_written < 0 && errno != EAGAIN) {
CancelInternal();
return true;
if (frames_written < 0) {
if (frames_written == -EAGAIN)
return false;
throw FormatRuntimeError("snd_pcm_writei() failed: %s",
snd_strerror(-frames_written));
}
if (!period_buffer.IsEmpty())
/* need to call WriteFromPeriodBuffer() again
in the next iteration, so don't finish the
drain just yet */
return false;
/* need to call CopyRingToPeriodBuffer() and
WriteFromPeriodBuffer() again in the next
iteration, so don't finish the drain just yet */
return period_buffer.IsEmpty();
}
if (!written)
/* if nothing has ever been written to the PCM, we
don't need to drain it */
return true;
/* .. and finally drain the ALSA hardware buffer */
int result;
if (work_around_drain_bug) {
snd_pcm_nonblock(pcm, 0);
bool result = snd_pcm_drain(pcm) != -EAGAIN;
result = snd_pcm_drain(pcm);
snd_pcm_nonblock(pcm, 1);
return result;
}
} else
result = snd_pcm_drain(pcm);
return snd_pcm_drain(pcm) != -EAGAIN;
if (result == 0)
return true;
else if (result == -EAGAIN)
return false;
else
throw FormatRuntimeError("snd_pcm_drain() failed: %s",
snd_strerror(-result));
}
void
@@ -778,6 +815,9 @@ AlsaOutput::Drain()
inline void
AlsaOutput::CancelInternal() noexcept
{
/* this method doesn't need to lock the mutex because while it
runs, the calling thread is blocked inside Cancel() */
must_prepare = true;
snd_pcm_drop(pcm);
@@ -786,10 +826,7 @@ AlsaOutput::CancelInternal() noexcept
period_buffer.Clear();
ring_buffer->reset();
{
const std::lock_guard<Mutex> lock(mutex);
active = false;
}
active = false;
MultiSocketMonitor::Reset();
defer_invalidate_sockets.Cancel();
@@ -881,7 +918,7 @@ AlsaOutput::PrepareSockets() noexcept
}
try {
return PrepareAlsaPcmSockets(*this, pcm, pfd_buffer);
return non_block.PrepareSockets(*this, pcm);
} catch (...) {
ClearSocketList();
LockCaughtError();
@@ -892,6 +929,18 @@ AlsaOutput::PrepareSockets() noexcept
void
AlsaOutput::DispatchSockets() noexcept
try {
non_block.DispatchSockets(*this, pcm);
if (must_prepare) {
must_prepare = false;
written = false;
int err = snd_pcm_prepare(pcm);
if (err < 0)
throw FormatRuntimeError("snd_pcm_prepare() failed: %s",
snd_strerror(-err));
}
{
const std::lock_guard<Mutex> lock(mutex);
@@ -912,19 +961,11 @@ try {
}
}
if (must_prepare) {
must_prepare = false;
int err = snd_pcm_prepare(pcm);
if (err < 0)
throw FormatRuntimeError("snd_pcm_prepare() failed: %s",
snd_strerror(-err));
}
CopyRingToPeriodBuffer();
if (period_buffer.IsEmpty()) {
if (snd_pcm_state(pcm) == SND_PCM_STATE_PREPARED) {
if (snd_pcm_state(pcm) == SND_PCM_STATE_PREPARED ||
snd_pcm_avail(pcm) <= max_avail_frames) {
/* at SND_PCM_STATE_PREPARED (not yet switched
to SND_PCM_STATE_RUNNING), we have no
pressure to fill the ALSA buffer, because
@@ -934,10 +975,16 @@ try {
monitoring the ALSA file descriptor, and
let it be reactivated by Play()/Activate()
whenever more data arrives */
/* the same applies when there is still enough
data in the ALSA-PCM buffer (determined by
snd_pcm_avail()); this can happend at the
start of playback, when our ring_buffer is
smaller than the ALSA-PCM buffer */
{
const std::lock_guard<Mutex> lock(mutex);
active = false;
cond.signal();
}
/* avoid race condition: see if data has

@@ -98,8 +98,11 @@ class HttpdClient final
/**
* If the current metadata was already sent to the client.
*
* Initialized to `true` because there is no metadata #Page
* pending to be sent.
*/
bool metadata_sent = false;
bool metadata_sent = true;
/**
* The amount of streaming data between each metadata block

@@ -23,7 +23,8 @@
* between the decoder thread and the output thread(s): it receives
* #MusicChunk objects from the decoder, optionally mixes them
* (cross-fading), applies software volume, and sends them to the
* audio outputs via audio_output_all_play().
* audio outputs via PlayerOutputs::Play()
* (i.e. MultipleOutputs::Play()).
*
* It is controlled by the main thread (the playlist code), see
* Control.hxx. The playlist enqueues new songs into the player
@@ -970,6 +971,15 @@ Player::Run() noexcept
pc.CommandFinished();
while (ProcessCommand()) {
if (decoder_starting) {
/* wait until the decoder is initialized completely */
if (!CheckDecoderStartup())
break;
continue;
}
if (buffering) {
/* buffering at the start of the song - wait
until the buffer is large enough, to
@@ -987,15 +997,6 @@ Player::Run() noexcept
}
}
if (decoder_starting) {
/* wait until the decoder is initialized completely */
if (!CheckDecoderStartup())
break;
continue;
}
if (dc.IsIdle() && queued && dc.pipe == pipe) {
/* the decoder has finished the current song;
make it decode the next song */

@@ -19,13 +19,14 @@
#include "config.h"
#include "BaseSongFilter.hxx"
#include "Escape.hxx"
#include "LightSong.hxx"
#include "util/UriUtil.hxx"
std::string
BaseSongFilter::ToExpression() const noexcept
{
return "(base \"" + value + "\")";
return "(base \"" + EscapeFilterString(value) + "\")";
}
bool

41
src/song/Escape.cxx Normal file

@@ -0,0 +1,41 @@
/*
* Copyright 2003-2018 The Music Player Daemon Project
* http://www.musicpd.org
*
* 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 2 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "Escape.hxx"
static constexpr bool
MustEscape(char ch) noexcept
{
return ch == '"' || ch == '\'' || ch == '\\';
}
std::string
EscapeFilterString(const std::string &src) noexcept
{
std::string result;
result.reserve(src.length() + 16);
for (char ch : src) {
if (MustEscape(ch))
result.push_back('\\');
result.push_back(ch);
}
return result;
}

31
src/song/Escape.hxx Normal file

@@ -0,0 +1,31 @@
/*
* Copyright 2003-2018 The Music Player Daemon Project
* http://www.musicpd.org
*
* 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 2 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef MPD_SONG_ESCAPE_HXX
#define MPD_SONG_ESCAPE_HXX
#include "util/Compiler.h"
#include <string>
gcc_pure
std::string
EscapeFilterString(const std::string &src) noexcept;
#endif

@@ -91,8 +91,10 @@ locate_parse_type(const char *str) noexcept
SongFilter::SongFilter(TagType tag, const char *value, bool fold_case)
{
and_filter.AddItem(std::make_unique<TagSongFilter>(tag, value,
fold_case, false));
/* for compatibility with MPD 0.20 and older, "fold_case" also
switches on "substring" */
and_filter.AddItem(std::make_unique<TagSongFilter>(tag,
StringFilter(value, fold_case, fold_case, false)));
}
SongFilter::~SongFilter()
@@ -173,13 +175,76 @@ ExpectQuoted(const char *&s)
if (!IsQuote(quote))
throw std::runtime_error("Quoted string expected");
const char *begin = s;
const char *end = strchr(s, quote);
if (end == nullptr)
throw std::runtime_error("Closing quote not found");
char buffer[4096];
size_t length = 0;
s = StripLeft(end + 1);
return {begin, end};
while (*s != quote) {
if (*s == '\\')
/* backslash escapes the following character */
++s;
if (*s == 0)
throw std::runtime_error("Closing quote not found");
buffer[length++] = *s++;
if (length >= sizeof(buffer))
throw std::runtime_error("Quoted value is too long");
}
s = StripLeft(s + 1);
return {buffer, length};
}
/**
* Parse a string operator and its second operand and convert it to a
* #StringFilter.
*
* Throws on error.
*/
static StringFilter
ParseStringFilter(const char *&s, bool fold_case)
{
if (auto after_contains = StringAfterPrefixIgnoreCase(s, "contains ")) {
s = StripLeft(after_contains);
auto value = ExpectQuoted(s);
return StringFilter(std::move(value),
fold_case, true, false);
}
if (auto after_not_contains = StringAfterPrefixIgnoreCase(s, "!contains ")) {
s = StripLeft(after_not_contains);
auto value = ExpectQuoted(s);
return StringFilter(std::move(value),
fold_case, true, true);
}
bool negated = false;
#ifdef HAVE_PCRE
if ((s[0] == '!' || s[0] == '=') && s[1] == '~') {
negated = s[0] == '!';
s = StripLeft(s + 2);
auto value = ExpectQuoted(s);
StringFilter f(std::move(value), fold_case, false, negated);
f.SetRegex(std::make_shared<UniqueRegex>(f.GetValue().c_str(),
false, false,
fold_case));
return f;
}
#endif
if (s[0] == '!' && s[1] == '=')
negated = true;
else if (s[0] != '=' || s[1] != '=')
throw std::runtime_error("'==' or '!=' expected");
s = StripLeft(s + 2);
auto value = ExpectQuoted(s);
return StringFilter(std::move(value),
fold_case, false, negated);
}
ISongFilterPtr
@@ -264,14 +329,7 @@ SongFilter::ParseExpression(const char *&s, bool fold_case)
return std::make_unique<AudioFormatSongFilter>(value);
} else {
bool negated = false;
if (s[0] == '!' && s[1] == '=')
negated = true;
else if (s[0] != '=' || s[1] != '=')
throw std::runtime_error("'==' or '!=' expected");
s = StripLeft(s + 2);
auto value = ExpectQuoted(s);
auto string_filter = ParseStringFilter(s, fold_case);
if (*s != ')')
throw std::runtime_error("')' expected");
@@ -281,13 +339,10 @@ SongFilter::ParseExpression(const char *&s, bool fold_case)
type = TAG_NUM_OF_ITEM_TYPES;
if (type == LOCATE_TAG_FILE_TYPE)
return std::make_unique<UriSongFilter>(std::move(value),
fold_case,
negated);
return std::make_unique<UriSongFilter>(std::move(string_filter));
return std::make_unique<TagSongFilter>(TagType(type),
std::move(value),
fold_case, negated);
std::move(string_filter));
}
}
@@ -312,19 +367,25 @@ SongFilter::Parse(const char *tag_string, const char *value, bool fold_case)
break;
case LOCATE_TAG_FILE_TYPE:
and_filter.AddItem(std::make_unique<UriSongFilter>(value,
fold_case,
false));
/* for compatibility with MPD 0.20 and older,
"fold_case" also switches on "substring" */
and_filter.AddItem(std::make_unique<UriSongFilter>(StringFilter(value,
fold_case,
fold_case,
false)));
break;
default:
if (tag == LOCATE_TAG_ANY_TYPE)
tag = TAG_NUM_OF_ITEM_TYPES;
/* for compatibility with MPD 0.20 and older,
"fold_case" also switches on "substring" */
and_filter.AddItem(std::make_unique<TagSongFilter>(TagType(tag),
value,
fold_case,
false));
StringFilter(value,
fold_case,
fold_case,
false)));
break;
}
}

@@ -58,11 +58,11 @@ OptimizeSongFilter(ISongFilterPtr f) noexcept
/* #TagSongFilter has its own "negated" flag,
so we can drop the #NotSongFilter
container */
tf->negated = !tf->negated;
tf->ToggleNegated();
return child;
} else if (auto *uf = dynamic_cast<UriSongFilter *>(child.get())) {
/* same for #UriSongFilter */
uf->negated = !uf->negated;
uf->ToggleNegated();
return child;
}

@@ -23,17 +23,32 @@
#include <assert.h>
bool
StringFilter::Match(const char *s) const noexcept
inline bool
StringFilter::MatchWithoutNegation(const char *s) const noexcept
{
#if !CLANG_CHECK_VERSION(3,6)
/* disabled on clang due to -Wtautological-pointer-compare */
assert(s != nullptr);
#endif
#ifdef HAVE_PCRE
if (regex)
return regex->Match(s);
#endif
if (fold_case) {
return fold_case.IsIn(s);
return substring
? fold_case.IsIn(s)
: fold_case == s;
} else {
return StringIsEqual(s, value.c_str());
return substring
? StringFind(s, value.c_str()) != nullptr
: value == s;
}
}
bool
StringFilter::Match(const char *s) const noexcept
{
return MatchWithoutNegation(s) != negated;
}

@@ -23,7 +23,12 @@
#include "lib/icu/Compare.hxx"
#include "util/Compiler.h"
#ifdef HAVE_PCRE
#include "lib/pcre/UniqueRegex.hxx"
#endif
#include <string>
#include <memory>
class StringFilter {
std::string value;
@@ -33,18 +38,45 @@ class StringFilter {
*/
IcuCompare fold_case;
#ifdef HAVE_PCRE
std::shared_ptr<UniqueRegex> regex;
#endif
/**
* Search for substrings instead of matching the whole string?
*/
bool substring;
bool negated;
public:
template<typename V>
StringFilter(V &&_value, bool _fold_case)
StringFilter(V &&_value, bool _fold_case, bool _substring, bool _negated)
:value(std::forward<V>(_value)),
fold_case(_fold_case
? IcuCompare(value.c_str())
: IcuCompare()) {}
: IcuCompare()),
substring(_substring), negated(_negated) {}
bool empty() const noexcept {
return value.empty();
}
bool IsRegex() const noexcept {
#ifdef HAVE_PCRE
return !!regex;
#else
return false;
#endif
}
#ifdef HAVE_PCRE
template<typename R>
void SetRegex(R &&_regex) noexcept {
regex = std::forward<R>(_regex);
}
#endif
const auto &GetValue() const noexcept {
return value;
}
@@ -53,8 +85,28 @@ public:
return fold_case;
}
bool IsNegated() const noexcept {
return negated;
}
void ToggleNegated() noexcept {
negated = !negated;
}
const char *GetOperator() const noexcept {
return IsRegex()
? (negated ? "!~" : "=~")
: (substring
? (negated ? "!contains" : "contains")
: (negated ? "!=" : "=="));
}
gcc_pure
bool Match(const char *s) const noexcept;
private:
gcc_pure
bool MatchWithoutNegation(const char *s) const noexcept;
};
#endif

@@ -19,6 +19,7 @@
#include "config.h"
#include "TagSongFilter.hxx"
#include "Escape.hxx"
#include "LightSong.hxx"
#include "tag/Tag.hxx"
#include "tag/Fallback.hxx"
@@ -30,7 +31,8 @@ TagSongFilter::ToExpression() const noexcept
? "any"
: tag_item_names[type];
return std::string("(") + name + " " + (negated ? "!=" : "==") + " \"" + filter.GetValue() + "\")";
return std::string("(") + name + " " + filter.GetOperator()
+ " \"" + EscapeFilterString(filter.GetValue()) + "\")";
}
bool
@@ -88,5 +90,5 @@ TagSongFilter::MatchNN(const Tag &tag) const noexcept
bool
TagSongFilter::Match(const LightSong &song) const noexcept
{
return MatchNN(song.tag) != negated;
return MatchNN(song.tag);
}

@@ -34,17 +34,11 @@ struct LightSong;
class TagSongFilter final : public ISongFilter {
TagType type;
bool negated;
StringFilter filter;
friend ISongFilterPtr OptimizeSongFilter(ISongFilterPtr) noexcept;
public:
template<typename V>
TagSongFilter(TagType _type, V &&_value, bool fold_case, bool _negated)
:type(_type), negated(_negated),
filter(std::forward<V>(_value), fold_case) {}
TagSongFilter(TagType _type, StringFilter &&_filter) noexcept
:type(_type), filter(std::move(_filter)) {}
TagType GetTagType() const {
return type;
@@ -59,7 +53,11 @@ public:
}
bool IsNegated() const noexcept {
return negated;
return filter.IsNegated();
}
void ToggleNegated() noexcept {
filter.ToggleNegated();
}
ISongFilterPtr Clone() const noexcept override {

@@ -19,16 +19,18 @@
#include "config.h"
#include "UriSongFilter.hxx"
#include "Escape.hxx"
#include "LightSong.hxx"
std::string
UriSongFilter::ToExpression() const noexcept
{
return std::string("(file ") + (negated ? "!=" : "==") + " \"" + filter.GetValue() + "\")";
return std::string("(file ") + filter.GetOperator()
+ " \"" + EscapeFilterString(filter.GetValue()) + "\")";
}
bool
UriSongFilter::Match(const LightSong &song) const noexcept
{
return filter.Match(song.GetURI().c_str()) != negated;
return filter.Match(song.GetURI().c_str());
}

@@ -26,15 +26,9 @@
class UriSongFilter final : public ISongFilter {
StringFilter filter;
bool negated;
friend ISongFilterPtr OptimizeSongFilter(ISongFilterPtr) noexcept;
public:
template<typename V>
UriSongFilter(V &&_value, bool fold_case, bool _negated)
:filter(std::forward<V>(_value), fold_case),
negated(_negated) {}
UriSongFilter(StringFilter &&_filter) noexcept
:filter(std::move(_filter)) {}
const auto &GetValue() const noexcept {
return filter.GetValue();
@@ -45,7 +39,11 @@ public:
}
bool IsNegated() const noexcept {
return negated;
return filter.IsNegated();
}
void ToggleNegated() noexcept {
filter.ToggleNegated();
}
ISongFilterPtr Clone() const noexcept override {

@@ -1,6 +1,7 @@
song = static_library(
'song',
'DetachedSong.cxx',
'Escape.cxx',
'StringFilter.cxx',
'UriSongFilter.cxx',
'BaseSongFilter.cxx',
@@ -18,6 +19,7 @@ song_dep = declare_dependency(
link_with: song,
dependencies: [
icu_dep,
pcre_dep,
tag_dep,
util_dep,
],

@@ -76,7 +76,7 @@ FileDescriptor::IsSocket() const noexcept
#endif
#ifdef __linux
#ifdef __linux__
bool
FileDescriptor::Open(FileDescriptor dir, const char *pathname,

@@ -116,7 +116,7 @@ public:
return FileDescriptor(-1);
}
#ifdef __linux
#ifdef __linux__
bool Open(FileDescriptor dir, const char *pathname,
int flags, mode_t mode=0666) noexcept;
#endif

@@ -32,6 +32,12 @@
#include <unistd.h>
#include <limits.h>
#ifndef HOST_NAME_MAX
/* HOST_NAME_MAX is not a portable macro; it is undefined on some
systems */
#define HOST_NAME_MAX 255
#endif
static constexpr Domain zeroconf_domain("zeroconf");
/* The default service name to publish

2
test/.gitignore vendored

@@ -1,2 +0,0 @@
/run_neighbor_explorer
/ReadApeTags

@@ -239,6 +239,7 @@ if enable_database
'../src/LogBackend.cxx',
include_directories: inc,
dependencies: [
event_dep,
storage_glue_dep,
],
)

@@ -62,7 +62,8 @@ class CrossGccToolchain:
self.cxxflags = common_flags
self.cppflags = '-isystem ' + os.path.join(install_prefix, 'include') + \
' -DWINVER=0x0600 -D_WIN32_WINNT=0x0600'
self.ldflags = '-L' + os.path.join(install_prefix, 'lib')
self.ldflags = '-L' + os.path.join(install_prefix, 'lib') + \
' -static-libstdc++ -static-libgcc'
self.libs = ''
self.is_arm = arch.startswith('arm')