Compare commits
83 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
43df4a7500 | ||
![]() |
4cdcaa8630 | ||
![]() |
04f632296f | ||
![]() |
7c8dbcfaac | ||
![]() |
436ba3c96c | ||
![]() |
5d12f52873 | ||
![]() |
a8bf8ede01 | ||
![]() |
8682183bc3 | ||
![]() |
94c31d0da9 | ||
![]() |
464a4cbeec | ||
![]() |
9f0cbf418a | ||
![]() |
b477f86c92 | ||
![]() |
020371f145 | ||
![]() |
ccafe3f3cf | ||
![]() |
3830748de5 | ||
![]() |
1a43f5145d | ||
![]() |
7f143a83c1 | ||
![]() |
6ccc254179 | ||
![]() |
7db2450447 | ||
![]() |
6c2a6a65e0 | ||
![]() |
4247a757b3 | ||
![]() |
57e34823d8 | ||
![]() |
3c93decdf0 | ||
![]() |
89e7a5018d | ||
![]() |
7235b46e5e | ||
![]() |
0852226a48 | ||
![]() |
e20d215abf | ||
![]() |
e4b9b67e24 | ||
![]() |
685b78828d | ||
![]() |
060908d5c4 | ||
![]() |
0b0f4c61f1 | ||
![]() |
228bf7eb09 | ||
![]() |
5eaf2b8fc3 | ||
![]() |
e097fef79e | ||
![]() |
9a813cd3b1 | ||
![]() |
1c60c8e014 | ||
![]() |
eddda95900 | ||
![]() |
72184dccfc | ||
![]() |
fee75dc766 | ||
![]() |
ba5c856f15 | ||
![]() |
12308a0f55 | ||
![]() |
a958abde2f | ||
![]() |
583208db7e | ||
![]() |
7b5ba15170 | ||
![]() |
d5e0d49f86 | ||
![]() |
73b22d82aa | ||
![]() |
db51cc4e02 | ||
![]() |
be8a52a914 | ||
![]() |
ad597a8ff0 | ||
![]() |
b1fe105904 | ||
![]() |
451b142e3a | ||
![]() |
2833625266 | ||
![]() |
0464028872 | ||
![]() |
98985c03b0 | ||
![]() |
793fd8c479 | ||
![]() |
6c602811df | ||
![]() |
6d48a5684a | ||
![]() |
bd115a4008 | ||
![]() |
08272cdee2 | ||
![]() |
b14a5141a6 | ||
![]() |
aa0e4500c6 | ||
![]() |
4e6b8edf72 | ||
![]() |
ac0852b4e3 | ||
![]() |
6fe43ed969 | ||
![]() |
b34bc06624 | ||
![]() |
08e41e60e5 | ||
![]() |
10ec478a9c | ||
![]() |
86f1074905 | ||
![]() |
8e66b855a3 | ||
![]() |
e3bc85d7bf | ||
![]() |
6f242836e6 | ||
![]() |
f2c926f3b6 | ||
![]() |
aba18924ee | ||
![]() |
aa6bef54dd | ||
![]() |
528f5b9cb9 | ||
![]() |
96ae0ec93a | ||
![]() |
5a5229b499 | ||
![]() |
bba22c9c8c | ||
![]() |
694c437a2c | ||
![]() |
587172efa3 | ||
![]() |
2a926063b2 | ||
![]() |
d6f239e54f | ||
![]() |
b8989fafeb |
.gitignoreNEWS
android
autogen.shdoc
meson.buildmeson_options.txtpython/build
src
LogInit.cxxcheck.h
decoder
plugins
event
input
plugins
lib
alsa
ffmpeg
pcre
xiph
zlib
mixer
net
output
player
song
BaseSongFilter.cxxEscape.cxxEscape.hxxFilter.cxxOptimizeFilter.cxxStringFilter.cxxStringFilter.hxxTagSongFilter.cxxTagSongFilter.hxxUriSongFilter.cxxUriSongFilter.hxxmeson.build
system
zeroconf
test
win32
80
.gitignore
vendored
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
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
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"/>
|
||||
|
||||
|
11
autogen.sh
11
autogen.sh
@@ -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
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"
|
||||
#
|
||||
|
185
doc/protocol.rst
185
doc/protocol.rst
@@ -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`
|
||||
|
17
doc/user.rst
17
doc/user.rst
@@ -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.
|
||||
|
10
meson.build
10
meson.build
@@ -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,
|
||||
],
|
||||
)
|
||||
|
66
src/lib/pcre/RegexPointer.hxx
Normal file
66
src/lib/pcre/RegexPointer.hxx
Normal file
@@ -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
|
71
src/lib/pcre/UniqueRegex.cxx
Normal file
71
src/lib/pcre/UniqueRegex.cxx
Normal file
@@ -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;
|
||||
}
|
79
src/lib/pcre/UniqueRegex.hxx
Normal file
79
src/lib/pcre/UniqueRegex.hxx
Normal file
@@ -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
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
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
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
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')
|
||||
|
Reference in New Issue
Block a user