Compare commits
98 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
2784d65618 | ||
![]() |
47ea69233b | ||
![]() |
a884e37de1 | ||
![]() |
0102a8665a | ||
![]() |
d34ae0850c | ||
![]() |
6526de024a | ||
![]() |
5e1e92626c | ||
![]() |
7fee85c80a | ||
![]() |
5d87a274a5 | ||
![]() |
57e862712a | ||
![]() |
ddb5390d88 | ||
![]() |
fce20e514e | ||
![]() |
af66ed2505 | ||
![]() |
ea4068757d | ||
![]() |
2b10ecfa37 | ||
![]() |
f7eb2b697e | ||
![]() |
da67260c95 | ||
![]() |
ab9c9068d4 | ||
![]() |
6b4d7d7315 | ||
![]() |
313d1d5d83 | ||
![]() |
b7d6133593 | ||
![]() |
5b6bb114ad | ||
![]() |
56f082c9d4 | ||
![]() |
a1b798e555 | ||
![]() |
c91e08fbfd | ||
![]() |
f882434547 | ||
![]() |
05ad335ae9 | ||
![]() |
7faeb2ff2b | ||
![]() |
fdd76b3461 | ||
![]() |
e490e5d0ab | ||
![]() |
3a05c421e0 | ||
![]() |
afc70c120e | ||
![]() |
d7f80eab68 | ||
![]() |
e30b356eb0 | ||
![]() |
09a0803116 | ||
![]() |
20ffedc745 | ||
![]() |
0b1ad27ba8 | ||
![]() |
6a1b2f0387 | ||
![]() |
fb34519b96 | ||
![]() |
91fed47648 | ||
![]() |
c05691b546 | ||
![]() |
6b3b8c6f2e | ||
![]() |
a191db84f2 | ||
![]() |
e4d69f38b0 | ||
![]() |
97fc001180 | ||
![]() |
42a09ff17a | ||
![]() |
c170fed6f9 | ||
![]() |
8e38b4f83c | ||
![]() |
db4ae19246 | ||
![]() |
82a89c6bfe | ||
![]() |
166c70cab3 | ||
![]() |
de78fe38c8 | ||
![]() |
96fa69ff6b | ||
![]() |
39d94bd3ea | ||
![]() |
695ca29274 | ||
![]() |
02e8da6c98 | ||
![]() |
0ea5f4ac3a | ||
![]() |
47c50c079d | ||
![]() |
3b0fea5fae | ||
![]() |
443516cdda | ||
![]() |
57e0cc5442 | ||
![]() |
73f45d87d5 | ||
![]() |
ae88ba986e | ||
![]() |
d6247902ec | ||
![]() |
a566c28a49 | ||
![]() |
2eddb63a83 | ||
![]() |
fc7d5b055d | ||
![]() |
87c8953e8e | ||
![]() |
45d27a52f1 | ||
![]() |
d22acc59c9 | ||
![]() |
c064e8d62f | ||
![]() |
099a2cb586 | ||
![]() |
042fe2a9d0 | ||
![]() |
ff1c1107f3 | ||
![]() |
41a3fd0fd0 | ||
![]() |
b9169a5670 | ||
![]() |
2ecd5fa28d | ||
![]() |
e719b6cc41 | ||
![]() |
67b8124a1d | ||
![]() |
51ec499c89 | ||
![]() |
3a4df25db2 | ||
![]() |
37cf78ef45 | ||
![]() |
727c622659 | ||
![]() |
74a778e65f | ||
![]() |
1fc0c9fe8a | ||
![]() |
740d8ec5ab | ||
![]() |
0bcc477d46 | ||
![]() |
35297f8d4f | ||
![]() |
6f4202408c | ||
![]() |
0b6548a282 | ||
![]() |
faf5821816 | ||
![]() |
188673b746 | ||
![]() |
287c70e361 | ||
![]() |
f6b44af998 | ||
![]() |
84c3d9674f | ||
![]() |
47d3758820 | ||
![]() |
573ff3a24f | ||
![]() |
32fcc22cb3 |
.gitignoreMakefile.amNEWSconfigure.ac
doc
src
Daemon.cxxDatabaseGlue.cxxDatabasePlugin.hxxDatabaseSimple.hxxDecoderAPI.cxxDecoderThread.cxxDespotifyUtils.cxxIcyMetaDataParser.cxxMapper.cxxMapper.hxxOutputAPI.hxxOutputControl.cxxPlayerThread.cxxPlaylist.hxxPlaylistPrint.cxxPlaylistSave.cxxPlaylistSave.hxxQueue.cxxQueue.hxxQueuePrint.cxxQueuePrint.hxxQueueSave.cxxQueueSave.hxxSongFilter.cxxSongSave.cxxSongUpdate.cxxStats.cxxStats.hxxTagFile.hxx
archive
command
db
decoder
DsdLib.hxxDsdiffDecoderPlugin.cxxDsfDecoderPlugin.cxxFaadDecoderPlugin.cxxFfmpegDecoderPlugin.cxxFfmpegMetaData.hxxOggFind.cxx
encoder
event
filter
input
mixer
output
pcm
playlist
system
tag
util
test
1
.gitignore
vendored
1
.gitignore
vendored
@@ -31,6 +31,7 @@ libtool
|
||||
ltmain.sh
|
||||
missing
|
||||
mkinstalldirs
|
||||
/test-driver
|
||||
mpd
|
||||
mpd.service
|
||||
stamp-h1
|
||||
|
11
Makefile.am
11
Makefile.am
@@ -1061,6 +1061,7 @@ C_TESTS = \
|
||||
test/test_util \
|
||||
test/test_byte_reverse \
|
||||
test/test_mixramp \
|
||||
test/test_icy_parser \
|
||||
test/test_pcm \
|
||||
test/test_queue_priority
|
||||
|
||||
@@ -1496,6 +1497,16 @@ test_test_mixramp_LDADD = \
|
||||
$(GLIB_LIBS) \
|
||||
$(CPPUNIT_LIBS)
|
||||
|
||||
test_test_icy_parser_SOURCES = \
|
||||
src/Log.cxx \
|
||||
test/test_icy_parser.cxx
|
||||
test_test_icy_parser_CPPFLAGS = $(AM_CPPFLAGS) $(CPPUNIT_CFLAGS) -DCPPUNIT_HAVE_RTTI=0
|
||||
test_test_icy_parser_CXXFLAGS = $(AM_CXXFLAGS) -Wno-error=deprecated-declarations
|
||||
test_test_icy_parser_LDADD = \
|
||||
libtag.a \
|
||||
$(GLIB_LIBS) \
|
||||
$(CPPUNIT_LIBS)
|
||||
|
||||
test_test_pcm_SOURCES = \
|
||||
test/test_pcm_util.hxx \
|
||||
test/test_pcm_dither.cxx \
|
||||
|
70
NEWS
70
NEWS
@@ -1,3 +1,73 @@
|
||||
ver 0.18.9 (2014/03/02)
|
||||
* protocol
|
||||
- "findadd" requires the "add" permission
|
||||
* output
|
||||
- alsa: improved workaround for noise after manual song change
|
||||
* decoder
|
||||
- vorbis: fix linker failure when libvorbis/libogg are static
|
||||
* encoder
|
||||
- vorbis: fix another linker failure
|
||||
* output
|
||||
- pipe: fix hanging child process due to blocked signals
|
||||
* fix build failure due to missing signal.h include
|
||||
|
||||
ver 0.18.8 (2014/02/07)
|
||||
* decoder
|
||||
- ffmpeg: support libav v10_alpha1
|
||||
* encoder
|
||||
- vorbis: fix linker failure
|
||||
* output
|
||||
- roar: documentation
|
||||
* more robust Icy-Metadata parser
|
||||
* fix Solaris build failure
|
||||
|
||||
ver 0.18.7 (2014/01/13)
|
||||
* playlist
|
||||
- pls: fix crash after parser error
|
||||
- soundcloud: fix build failure with libyajl 2.0.1
|
||||
* decoder
|
||||
- faad: fix memory leak
|
||||
- mpcdec: reject libmpcdec SV7 in configure script
|
||||
* daemon: don't initialize supplementary groups when already running
|
||||
as the configured user
|
||||
|
||||
ver 0.18.6 (2013/12/24)
|
||||
* input
|
||||
- cdio_paranoia: support libcdio-paranoia 0.90
|
||||
* tags
|
||||
- riff: recognize upper-case "ID3" chunk name
|
||||
* decoder
|
||||
- ffmpeg: use relative timestamps
|
||||
* output
|
||||
- openal: fix build failure on Mac OS X
|
||||
- osx: fix build failure
|
||||
* mixer
|
||||
- alsa: fix build failure with uClibc
|
||||
* fix replay gain during cross-fade
|
||||
* accept files without metadata
|
||||
|
||||
ver 0.18.5 (2013/11/23)
|
||||
* configuration
|
||||
- fix crash when db_file is configured without music_directory
|
||||
- fix crash on "stats" without db_file/music_directory
|
||||
* database
|
||||
- proxy: auto-reload statistics
|
||||
- proxy: provide "db_update" in "stats" response
|
||||
* input
|
||||
- curl: work around stream resume bug (fixed in libcurl 7.32.0)
|
||||
* decoder
|
||||
- fluidsynth: auto-detect by default
|
||||
* clip 24 bit data from libsamplerate
|
||||
* fix ia64, mipsel and other little-endian architectures
|
||||
* fix build failures due to missing includes
|
||||
* fix build failure with static libmpdclient
|
||||
|
||||
ver 0.18.4 (2013/11/13)
|
||||
* decoder
|
||||
- dsdiff: fix byte order bug
|
||||
* fix build failures due to missing includes
|
||||
* libc++ compatibility
|
||||
|
||||
ver 0.18.3 (2013/11/08)
|
||||
* fix stuck MPD after song change (0.18.2 regression)
|
||||
|
||||
|
19
configure.ac
19
configure.ac
@@ -1,6 +1,6 @@
|
||||
AC_PREREQ(2.60)
|
||||
|
||||
AC_INIT(mpd, 0.18.3, musicpd-dev-team@lists.sourceforge.net)
|
||||
AC_INIT(mpd, 0.18.9, mpd-devel@musicpd.org)
|
||||
|
||||
VERSION_MAJOR=0
|
||||
VERSION_MINOR=18
|
||||
@@ -732,7 +732,7 @@ dnl --------------------------------- Soundcloud ------------------------------
|
||||
if test x$enable_soundcloud != xno; then
|
||||
PKG_CHECK_MODULES([YAJL], [yajl >= 2.0],
|
||||
[found_soundcloud=yes],
|
||||
AC_CHECK_LIB([yajl], [yajl_alloc],
|
||||
AC_CHECK_LIB([yajl], [yajl_parse_complete],
|
||||
[found_soundcloud=yes YAJL_CFLAGS=-DHAVE_YAJL1 YAJL_LIBS=-lyajl],
|
||||
[found_soundcloud=no]))
|
||||
fi
|
||||
@@ -749,6 +749,7 @@ MPD_AUTO_PKG(cdio_paranoia, CDIO_PARANOIA, [libcdio_paranoia],
|
||||
if test x$enable_cdio_paranoia = xyes; then
|
||||
AC_DEFINE([ENABLE_CDIO_PARANOIA], 1,
|
||||
[Define to enable libcdio_paranoia support])
|
||||
AC_CHECK_HEADERS(cdio/paranoia/paranoia.h)
|
||||
fi
|
||||
|
||||
AM_CONDITIONAL(ENABLE_CDIO_PARANOIA, test x$enable_cdio_paranoia = xyes)
|
||||
@@ -874,10 +875,12 @@ AM_CONDITIONAL(HAVE_FLAC, test x$enable_flac = xyes)
|
||||
enable_flac_encoder=$enable_flac
|
||||
|
||||
dnl -------------------------------- FluidSynth -------------------------------
|
||||
|
||||
MPD_AUTO_PKG(fluidsynth, FLUIDSYNTH, [fluidsynth >= 1.1],
|
||||
[fluidsynth decoder], [fluidsynth not found])
|
||||
|
||||
if test x$enable_fluidsynth = xyes; then
|
||||
PKG_CHECK_MODULES(FLUIDSYNTH, [fluidsynth >= 1.1],
|
||||
AC_DEFINE(ENABLE_FLUIDSYNTH, 1, [Define for fluidsynth support]),
|
||||
enable_fluidsynth=no)
|
||||
AC_DEFINE(ENABLE_FLUIDSYNTH, 1, [Define for fluidsynth support])
|
||||
fi
|
||||
|
||||
AM_CONDITIONAL(ENABLE_FLUIDSYNTH, test x$enable_fluidsynth = xyes)
|
||||
@@ -957,7 +960,7 @@ AM_CONDITIONAL(ENABLE_SNDFILE, test x$enable_sndfile = xyes)
|
||||
|
||||
dnl --------------------------------- musepack --------------------------------
|
||||
|
||||
MPD_AUTO_LIB(mpc, MPCDEC, mpcdec, main, [-lmpcdec], [],
|
||||
MPD_AUTO_LIB(mpc, MPCDEC, mpcdec, mpc_demux_init, [-lmpcdec], [],
|
||||
[mpcdec], [libmpcdec not found])
|
||||
if test x$enable_mpc = xyes; then
|
||||
AC_DEFINE(HAVE_MPCDEC, 1, [Define to use libmpcdec for MPC decoding])
|
||||
@@ -1020,7 +1023,7 @@ if test x$enable_tremor = xyes; then
|
||||
fi
|
||||
fi
|
||||
|
||||
MPD_AUTO_PKG(vorbis, VORBIS, [vorbis vorbisfile ogg],
|
||||
MPD_AUTO_PKG(vorbis, VORBIS, [vorbisfile vorbis ogg],
|
||||
[Ogg Vorbis decoder], [libvorbis not found])
|
||||
if test x$enable_vorbis = xyes; then
|
||||
AC_DEFINE(ENABLE_VORBIS_DECODER, 1, [Define for Ogg Vorbis support])
|
||||
@@ -1136,7 +1139,7 @@ fi
|
||||
AM_CONDITIONAL(ENABLE_FLAC_ENCODER, test x$enable_flac_encoder = xyes)
|
||||
|
||||
dnl ---------------------------- Ogg Vorbis Encoder ---------------------------
|
||||
MPD_AUTO_PKG(vorbis_encoder, VORBISENC, [vorbisenc],
|
||||
MPD_AUTO_PKG(vorbis_encoder, VORBISENC, [vorbisenc vorbis ogg],
|
||||
[Ogg Vorbis encoder], [libvorbisenc not found])
|
||||
|
||||
if test x$enable_vorbis_encoder = xyes; then
|
||||
|
@@ -155,7 +155,7 @@ foo(const char *abc, int xyz)
|
||||
|
||||
<para>
|
||||
Send your patches to the mailing list:
|
||||
musicpd-dev-team@lists.sourceforge.net
|
||||
mpd-devel@musicpd.org
|
||||
</para>
|
||||
</chapter>
|
||||
</book>
|
||||
|
131
doc/mpd.conf.5
131
doc/mpd.conf.5
@@ -363,137 +363,6 @@ errors on bandwidth-limited devices. Some users have reported good results
|
||||
with this set to 50000, but not all devices support values this high. Most
|
||||
users do not need to change this. The default is 256000000 / sample_rate(kHz),
|
||||
or 5804 microseconds for CD-quality audio.
|
||||
.SH OPTIONAL OSS OUTPUT PARAMETERS
|
||||
.TP
|
||||
.B device <dev>
|
||||
This specifies the device to use for audio output. The default is "/dev/dsp".
|
||||
.TP
|
||||
.B mixer_device <mixer dev>
|
||||
This specifies which mixer to use. The default is "/dev/mixer".
|
||||
.TP
|
||||
.B mixer_control <mixer ctrl>
|
||||
This specifies which mixer control to use (sometimes referred to as the
|
||||
"device"). The default is to use the main PCM mixer. An example is "Pcm".
|
||||
.SH OPTIONAL PULSE OUTPUT PARAMETERS
|
||||
.TP
|
||||
.B server <server list>
|
||||
A space separated list of servers to try to connect to. See
|
||||
<\fBhttp://www.pulseaudio.org/wiki/ServerStrings\fP> for more details. The
|
||||
default is to let PulseAudio choose a server.
|
||||
If you specify more than one server name, MPD tries to connect to one
|
||||
after another until it successfully establishes a connection.
|
||||
.TP
|
||||
.B sink <sink>
|
||||
The sink to output to. The default is to let PulseAudio choose a sink.
|
||||
.SH OPTIONAL JACK OUTPUT PARAMETERS
|
||||
.TP
|
||||
.B client_name <name>
|
||||
The client name to use when connecting to JACK. The output ports <name>:left
|
||||
and <name>:right will also be created for the left and right channels,
|
||||
respectively.
|
||||
.TP
|
||||
.B ports <left_port,right_port>
|
||||
This specifies the left and right ports to connect to for the left and right
|
||||
channels, respectively. The default is to let JACK choose a pair of ports.
|
||||
.TP
|
||||
.B ringbuffer_size <size in bytes>
|
||||
This specifies the size of the ringbuffer in bytes. The default is 32768.
|
||||
.SH OPTIONAL AO OUTPUT PARAMETERS
|
||||
.TP
|
||||
.B driver <driver>
|
||||
This specifies the libao driver to use for audio output. Possible values
|
||||
depend on what libao drivers are available. See
|
||||
<\fBhttp://www.xiph.org/ao/doc/drivers.html\fP> for information on some
|
||||
commonly used drivers. Typical values for Linux include "oss" and "alsa09".
|
||||
The default is "default", which causes libao to select an appropriate plugin.
|
||||
.TP
|
||||
.B options <opts>
|
||||
This specifies the options to use for the selected libao driver. For oss, the
|
||||
only option available is "dsp". For alsa09, the available options are: "dev",
|
||||
"buf_size", and "periods". See <\fBhttp://www.xiph.org/ao/doc/drivers.html\fP>
|
||||
for available options for some commonly used drivers. Options are assigned
|
||||
using "=", and ";" is used to separate options. An example for oss:
|
||||
"dsp=/dev/dsp". An example for alsa09: "dev=hw:0,0;buf_size=4096". The
|
||||
default is "".
|
||||
.TP
|
||||
.B write_size <size in bytes>
|
||||
This specifies how many bytes to write to the audio device at once. This
|
||||
parameter is to work around a bug in older versions of libao on sound cards
|
||||
with very small buffers. The default is 1024.
|
||||
.SH REQUIRED FIFO OUTPUT PARAMETERS
|
||||
.TP
|
||||
.B path <path>
|
||||
This specifies the path of the FIFO to output to. Must be an absolute path.
|
||||
If the path does not exist it will be created when mpd is started, and removed
|
||||
when mpd is stopped. The FIFO will be created with the same user and group as
|
||||
mpd is running as. Default permissions can be modified by using the builtin
|
||||
shell command "umask". If a FIFO already exists at the specified path it will
|
||||
be reused, and will \fBnot\fP be removed when mpd is stopped. You can use the
|
||||
"mkfifo" command to create this, and then you may modify the permissions to
|
||||
your liking.
|
||||
.SH REQUIRED SHOUT OUTPUT PARAMETERS
|
||||
.TP
|
||||
.B name <name>
|
||||
This specifies not only the unique audio output name, but also the stream
|
||||
title.
|
||||
.TP
|
||||
.B host <hostname>
|
||||
This specifies the hostname of the icecast server to connect to.
|
||||
.TP
|
||||
.B port <port>
|
||||
This specifies the port of the icecast server to connect to.
|
||||
.TP
|
||||
.B mount <mountpoint>
|
||||
This specifies the icecast mountpoint to use.
|
||||
.TP
|
||||
.B password <password>
|
||||
This specifies the password to use when logging in to the icecast server.
|
||||
.TP
|
||||
.B quality <quality>
|
||||
This specifies the encoding quality to use. The value must be between 0
|
||||
and 10. Fractional values, such as 2.5, are permitted. Either the quality or
|
||||
the bitrate parameter must be specified, but not both. For Ogg, a
|
||||
higher quality number produces higher quality output. For MP3, it's
|
||||
just the opposite, with lower numbers producing higher quality output.
|
||||
.TP
|
||||
.B bitrate <kbps>
|
||||
This specifies the bitrate to use for encoding. Either the quality or the
|
||||
bitrate parameter must be specified, but not both.
|
||||
.TP
|
||||
.B format <sample_rate:bits:channels>
|
||||
This specifies the sample rate, bits per sample, and number of channels to use
|
||||
for encoding.
|
||||
.SH OPTIONAL SHOUT OUTPUT PARAMETERS
|
||||
.TP
|
||||
.B encoding <encoding>
|
||||
This specifies which output encoding to use. Should be either "ogg"
|
||||
or "mp3", "mp3" is needed for shoutcast streaming. The default is "ogg".
|
||||
.TP
|
||||
.B protocol <protocol>
|
||||
This specifies the protocol that wil be used to connect to the
|
||||
icecast/shoutcast server. The options are "shoutcast", "icecast1" and
|
||||
"icecast2". The default is "icecast2".
|
||||
.TP
|
||||
.B user <username>
|
||||
This specifies the username to use when logging in to the icecast server. The
|
||||
default is "source".
|
||||
.TP
|
||||
.B public <yes or no>
|
||||
This specifies whether to request that the stream be listed in all public
|
||||
stream directories that the icecast server knows about. The default is no.
|
||||
.TP
|
||||
.B timeout <seconds>
|
||||
This specifies the number of seconds to wait before giving up on trying to
|
||||
connect to the icecast server. The default is 2 seconds.
|
||||
.TP
|
||||
.B description <description>
|
||||
This specifies a description of the stream.
|
||||
.TP
|
||||
.B url <url>
|
||||
This specifies a URL associated with the stream.
|
||||
.TP
|
||||
.B genre <genre>
|
||||
This specifies the genre(s) of the stream.
|
||||
.SH FILES
|
||||
.TP
|
||||
.BI ~/.mpdconf
|
||||
|
@@ -210,7 +210,6 @@ input {
|
||||
# type "alsa"
|
||||
# name "My ALSA Device"
|
||||
## device "hw:0,0" # optional
|
||||
## format "44100:16:2" # optional
|
||||
## mixer_type "hardware" # optional
|
||||
## mixer_device "default" # optional
|
||||
## mixer_control "PCM" # optional
|
||||
@@ -223,7 +222,6 @@ input {
|
||||
# type "oss"
|
||||
# name "My OSS Device"
|
||||
## device "/dev/dsp" # optional
|
||||
## format "44100:16:2" # optional
|
||||
## mixer_type "hardware" # optional
|
||||
## mixer_device "/dev/mixer" # optional
|
||||
## mixer_control "PCM" # optional
|
||||
@@ -327,12 +325,6 @@ input {
|
||||
# mixer_type "none" # optional
|
||||
#}
|
||||
#
|
||||
# This setting will change all decoded audio to be converted to the specified
|
||||
# format before being passed to the audio outputs. By default, this setting is
|
||||
# disabled.
|
||||
#
|
||||
#audio_output_format "44100:16:2"
|
||||
#
|
||||
# If MPD has been compiled with libsamplerate support, this setting specifies
|
||||
# the sample rate converter to use. Possible values can be found in the
|
||||
# mpd.conf man page or the libsamplerate documentation. By default, this is
|
||||
|
144
doc/user.xml
144
doc/user.xml
@@ -829,7 +829,7 @@ systemctl start mpd.socket</programlisting>
|
||||
<tbody>
|
||||
<row>
|
||||
<entry>
|
||||
<varname>default_bute_order</varname>
|
||||
<varname>default_byte_order</varname>
|
||||
<parameter>little_endian|big_endian</parameter>
|
||||
</entry>
|
||||
<entry>
|
||||
@@ -1398,6 +1398,59 @@ systemctl start mpd.socket</programlisting>
|
||||
The <varname>ao</varname> plugin uses the portable
|
||||
<filename>libao</filename> library.
|
||||
</para>
|
||||
|
||||
<informaltable>
|
||||
<tgroup cols="2">
|
||||
<thead>
|
||||
<row>
|
||||
<entry>Setting</entry>
|
||||
<entry>Description</entry>
|
||||
</row>
|
||||
</thead>
|
||||
<tbody>
|
||||
<row>
|
||||
<entry>
|
||||
<varname>driver</varname>
|
||||
<parameter>D</parameter>
|
||||
</entry>
|
||||
<entry>
|
||||
The <filename>libao</filename> driver to use for
|
||||
audio output. Possible values depend on what libao
|
||||
drivers are available. See <ulink
|
||||
url="http://www.xiph.org/ao/doc/drivers.html">http://www.xiph.org/ao/doc/drivers.html</ulink>
|
||||
for information on some commonly used drivers.
|
||||
Typical values for Linux include "oss" and "alsa09".
|
||||
The default is "default", which causes libao to
|
||||
select an appropriate plugin.
|
||||
</entry>
|
||||
</row>
|
||||
|
||||
<row>
|
||||
<entry>
|
||||
<varname>options</varname>
|
||||
<parameter>O</parameter>
|
||||
</entry>
|
||||
<entry>
|
||||
Options to pass to the selected
|
||||
<filename>libao</filename> driver.
|
||||
</entry>
|
||||
</row>
|
||||
|
||||
<row>
|
||||
<entry>
|
||||
<varname>write_size</varname>
|
||||
<parameter>O</parameter>
|
||||
</entry>
|
||||
<entry>
|
||||
This specifies how many bytes to write to the audio
|
||||
device at once. This parameter is to work around a
|
||||
bug in older versions of libao on sound cards with
|
||||
very small buffers. The default is 1024.
|
||||
</entry>
|
||||
</row>
|
||||
</tbody>
|
||||
</tgroup>
|
||||
</informaltable>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
@@ -1408,6 +1461,38 @@ systemctl start mpd.socket</programlisting>
|
||||
FIFO (First In, First Out) file. The data can be read by
|
||||
another program.
|
||||
</para>
|
||||
|
||||
<informaltable>
|
||||
<tgroup cols="2">
|
||||
<thead>
|
||||
<row>
|
||||
<entry>Setting</entry>
|
||||
<entry>Description</entry>
|
||||
</row>
|
||||
</thead>
|
||||
<tbody>
|
||||
<row>
|
||||
<entry>
|
||||
<varname>path</varname>
|
||||
<parameter>P</parameter>
|
||||
</entry>
|
||||
<entry>
|
||||
This specifies the path of the FIFO to write to.
|
||||
Must be an absolute path. If the path does not
|
||||
exist, it will be created when MPD is started, and
|
||||
removed when MPD is stopped. The FIFO will be
|
||||
created with the same user and group as MPD is
|
||||
running as. Default permissions can be modified by
|
||||
using the builtin shell command "umask". If a FIFO
|
||||
already exists at the specified path it will be
|
||||
reused, and will not be removed when MPD is stopped.
|
||||
You can use the "mkfifo" command to create this, and
|
||||
then you may modify the permissions to your liking.
|
||||
</entry>
|
||||
</row>
|
||||
</tbody>
|
||||
</tgroup>
|
||||
</informaltable>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
@@ -1767,6 +1852,51 @@ systemctl start mpd.socket</programlisting>
|
||||
</informaltable>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<title><varname>roar</varname></title>
|
||||
|
||||
<para>
|
||||
The <varname>roar</varname> plugin connects to a <ulink
|
||||
url="http://roaraudio.keep-cool.org/">RoarAudio</ulink>
|
||||
server.
|
||||
</para>
|
||||
|
||||
<informaltable>
|
||||
<tgroup cols="2">
|
||||
<thead>
|
||||
<row>
|
||||
<entry>Setting</entry>
|
||||
<entry>Description</entry>
|
||||
</row>
|
||||
</thead>
|
||||
<tbody>
|
||||
<row>
|
||||
<entry>
|
||||
<varname>server</varname>
|
||||
<parameter>HOSTNAME</parameter>
|
||||
</entry>
|
||||
<entry>
|
||||
The host name of the RoarAudio server. If not
|
||||
specified, then MPD will connect to the default
|
||||
locations.
|
||||
</entry>
|
||||
</row>
|
||||
|
||||
<row>
|
||||
<entry>
|
||||
<varname>role</varname>
|
||||
<parameter>ROLE</parameter>
|
||||
</entry>
|
||||
<entry>
|
||||
The "role" that MPD registers itself as in the
|
||||
RoarAudio server. The default is "music".
|
||||
</entry>
|
||||
</row>
|
||||
</tbody>
|
||||
</tgroup>
|
||||
</informaltable>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<title><varname>recorder</varname></title>
|
||||
|
||||
@@ -1883,6 +2013,18 @@ systemctl start mpd.socket</programlisting>
|
||||
Defaults to 2 seconds.
|
||||
</entry>
|
||||
</row>
|
||||
<row>
|
||||
<entry>
|
||||
<varname>protocol</varname>
|
||||
<parameter>icecast2|icecast1|shoutcast</parameter>
|
||||
</entry>
|
||||
<entry>
|
||||
Specifies the protocol that wil be used to connect
|
||||
to the icecast/shoutcast server. The default
|
||||
is "<parameter>icecast2</parameter>".
|
||||
|
||||
</entry>
|
||||
</row>
|
||||
<row>
|
||||
<entry>
|
||||
<varname>mount</varname>
|
||||
|
@@ -52,7 +52,7 @@ static char *user_name;
|
||||
static uid_t user_uid = (uid_t)-1;
|
||||
|
||||
/** the Unix group id which MPD runs as */
|
||||
static gid_t user_gid = (pid_t)-1;
|
||||
static gid_t user_gid = (gid_t)-1;
|
||||
|
||||
/** the absolute path of the pidfile */
|
||||
static AllocatedPath pidfile = AllocatedPath::Null();
|
||||
@@ -106,18 +106,21 @@ daemonize_set_user(void)
|
||||
return;
|
||||
|
||||
/* set gid */
|
||||
if (user_gid != (gid_t)-1 && user_gid != getgid()) {
|
||||
if (setgid(user_gid) == -1) {
|
||||
FormatFatalSystemError("Failed to set group %d",
|
||||
(int)user_gid);
|
||||
}
|
||||
if (user_gid != (gid_t)-1 && user_gid != getgid() &&
|
||||
setgid(user_gid) == -1) {
|
||||
FormatFatalSystemError("Failed to set group %d",
|
||||
(int)user_gid);
|
||||
}
|
||||
|
||||
#ifdef _BSD_SOURCE
|
||||
/* init suplementary groups
|
||||
/* init supplementary groups
|
||||
* (must be done before we change our uid)
|
||||
*/
|
||||
if (!had_group && initgroups(user_name, user_gid) == -1) {
|
||||
if (!had_group &&
|
||||
/* no need to set the new user's supplementary groups if
|
||||
we are already this user */
|
||||
user_uid != getuid() &&
|
||||
initgroups(user_name, user_gid) == -1) {
|
||||
FormatFatalSystemError("Failed to set supplementary groups "
|
||||
"of user \"%s\"",
|
||||
user_name);
|
||||
|
@@ -148,12 +148,12 @@ DatabaseGlobalOpen(Error &error)
|
||||
return true;
|
||||
}
|
||||
|
||||
time_t
|
||||
db_get_mtime(void)
|
||||
bool
|
||||
db_exists()
|
||||
{
|
||||
assert(db != nullptr);
|
||||
assert(db_is_open);
|
||||
assert(db_is_simple());
|
||||
|
||||
return ((SimpleDatabase *)db)->GetLastModified();
|
||||
return ((SimpleDatabase *)db)->GetUpdateStamp() > 0;
|
||||
}
|
||||
|
@@ -30,6 +30,8 @@
|
||||
#include "tag/TagType.h"
|
||||
#include "Compiler.h"
|
||||
|
||||
#include <time.h>
|
||||
|
||||
struct config_param;
|
||||
struct DatabaseSelection;
|
||||
struct db_visitor;
|
||||
@@ -132,6 +134,13 @@ public:
|
||||
virtual bool GetStats(const DatabaseSelection &selection,
|
||||
DatabaseStats &stats,
|
||||
Error &error) const = 0;
|
||||
|
||||
/**
|
||||
* Returns the time stamp of the last database update.
|
||||
* Returns 0 if that is not not known/available.
|
||||
*/
|
||||
gcc_pure
|
||||
virtual time_t GetUpdateStamp() const = 0;
|
||||
};
|
||||
|
||||
struct DatabasePlugin {
|
||||
|
@@ -62,25 +62,13 @@ db_get_directory(const char *name);
|
||||
bool
|
||||
db_save(Error &error);
|
||||
|
||||
/**
|
||||
* May only be used if db_is_simple() returns true.
|
||||
*/
|
||||
gcc_pure
|
||||
time_t
|
||||
db_get_mtime(void);
|
||||
|
||||
/**
|
||||
* Returns true if there is a valid database file on the disk.
|
||||
*
|
||||
* May only be used if db_is_simple() returns true.
|
||||
*/
|
||||
gcc_pure
|
||||
static inline bool
|
||||
db_exists(void)
|
||||
{
|
||||
/* mtime is set only if the database file was loaded or saved
|
||||
successfully */
|
||||
return db_get_mtime() > 0;
|
||||
}
|
||||
bool
|
||||
db_exists();
|
||||
|
||||
#endif
|
||||
|
@@ -35,6 +35,7 @@
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
|
||||
void
|
||||
decoder_initialized(Decoder &decoder,
|
||||
|
@@ -38,6 +38,8 @@
|
||||
#include "tag/ApeReplayGain.hxx"
|
||||
#include "Log.hxx"
|
||||
|
||||
#include <functional>
|
||||
|
||||
static constexpr Domain decoder_thread_domain("decoder_thread");
|
||||
|
||||
/**
|
||||
|
@@ -28,6 +28,8 @@ extern "C" {
|
||||
#include <despotify.h>
|
||||
}
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
const Domain despotify_domain("despotify");
|
||||
|
||||
static struct despotify_session *g_session;
|
||||
|
@@ -81,31 +81,85 @@ icy_add_item(Tag &tag, TagType type, const char *value)
|
||||
}
|
||||
|
||||
static void
|
||||
icy_parse_tag_item(Tag &tag, const char *item)
|
||||
icy_parse_tag_item(Tag &tag, const char *name, const char *value)
|
||||
{
|
||||
gchar **p = g_strsplit(item, "=", 0);
|
||||
if (strcmp(name, "StreamTitle") == 0)
|
||||
icy_add_item(tag, TAG_TITLE, value);
|
||||
else
|
||||
FormatDebug(icy_metadata_domain,
|
||||
"unknown icy-tag: '%s'", name);
|
||||
}
|
||||
|
||||
if (p[0] != nullptr && p[1] != nullptr) {
|
||||
if (strcmp(p[0], "StreamTitle") == 0)
|
||||
icy_add_item(tag, TAG_TITLE, p[1]);
|
||||
else
|
||||
FormatDebug(icy_metadata_domain,
|
||||
"unknown icy-tag: '%s'", p[0]);
|
||||
/**
|
||||
* Find a single quote that is followed by a semicolon (or by the end
|
||||
* of the string). If that fails, return the first single quote. If
|
||||
* that also fails, return #end.
|
||||
*/
|
||||
static char *
|
||||
find_end_quote(char *p, char *const end)
|
||||
{
|
||||
char *fallback = std::find(p, end, '\'');
|
||||
if (fallback >= end - 1 || fallback[1] == ';')
|
||||
return fallback;
|
||||
|
||||
p = fallback + 1;
|
||||
while (true) {
|
||||
p = std::find(p, end, '\'');
|
||||
if (p == end)
|
||||
return fallback;
|
||||
|
||||
if (p == end - 1 || p[1] == ';')
|
||||
return p;
|
||||
|
||||
++p;
|
||||
}
|
||||
|
||||
g_strfreev(p);
|
||||
}
|
||||
|
||||
static Tag *
|
||||
icy_parse_tag(const char *p)
|
||||
icy_parse_tag(char *p, char *const end)
|
||||
{
|
||||
assert(p != nullptr);
|
||||
assert(end != nullptr);
|
||||
assert(p <= end);
|
||||
|
||||
Tag *tag = new Tag();
|
||||
gchar **items = g_strsplit(p, ";", 0);
|
||||
|
||||
for (unsigned i = 0; items[i] != nullptr; ++i)
|
||||
icy_parse_tag_item(*tag, items[i]);
|
||||
while (p != end) {
|
||||
const char *const name = p;
|
||||
char *eq = std::find(p, end, '=');
|
||||
if (eq == end)
|
||||
break;
|
||||
|
||||
g_strfreev(items);
|
||||
*eq = 0;
|
||||
p = eq + 1;
|
||||
|
||||
if (*p != '\'') {
|
||||
/* syntax error; skip to the next semicolon,
|
||||
try to recover */
|
||||
char *semicolon = std::find(p, end, ';');
|
||||
if (semicolon == end)
|
||||
break;
|
||||
p = semicolon + 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
++p;
|
||||
|
||||
const char *const value = p;
|
||||
char *quote = find_end_quote(p, end);
|
||||
if (quote == end)
|
||||
break;
|
||||
|
||||
*quote = 0;
|
||||
p = quote + 1;
|
||||
|
||||
icy_parse_tag_item(*tag, name, value);
|
||||
|
||||
char *semicolon = std::find(p, end, ';');
|
||||
if (semicolon == end)
|
||||
break;
|
||||
p = semicolon + 1;
|
||||
}
|
||||
|
||||
return tag;
|
||||
}
|
||||
@@ -152,15 +206,11 @@ IcyMetaDataParser::Meta(const void *data, size_t length)
|
||||
++length;
|
||||
|
||||
if (meta_position == meta_size) {
|
||||
/* null-terminate the string */
|
||||
|
||||
meta_data[meta_size] = 0;
|
||||
|
||||
/* parse */
|
||||
|
||||
delete tag;
|
||||
|
||||
tag = icy_parse_tag(meta_data);
|
||||
tag = icy_parse_tag(meta_data, meta_data + meta_size);
|
||||
g_free(meta_data);
|
||||
|
||||
/* change back to normal data mode */
|
||||
|
@@ -133,7 +133,9 @@ void mapper_finish(void)
|
||||
const char *
|
||||
mapper_get_music_directory_utf8(void)
|
||||
{
|
||||
return music_dir_utf8.c_str();
|
||||
return music_dir_utf8.empty()
|
||||
? nullptr
|
||||
: music_dir_utf8.c_str();
|
||||
}
|
||||
|
||||
const AllocatedPath &
|
||||
|
@@ -41,7 +41,8 @@ mapper_init(AllocatedPath &&music_dir, AllocatedPath &&playlist_dir);
|
||||
void mapper_finish(void);
|
||||
|
||||
/**
|
||||
* Return the absolute path of the music directory encoded in UTF-8.
|
||||
* Return the absolute path of the music directory encoded in UTF-8 or
|
||||
* nullptr if no music directory was configured.
|
||||
*/
|
||||
gcc_const
|
||||
const char *
|
||||
|
@@ -18,7 +18,7 @@
|
||||
*/
|
||||
|
||||
#ifndef MPD_OUTPUT_API_HXX
|
||||
#define MPD_OUTPUT_API_HxX
|
||||
#define MPD_OUTPUT_API_HXX
|
||||
|
||||
#include "OutputPlugin.hxx"
|
||||
#include "OutputInternal.hxx"
|
||||
|
@@ -101,6 +101,8 @@ audio_output_set_replay_gain_mode(struct audio_output *ao,
|
||||
{
|
||||
if (ao->replay_gain_filter != nullptr)
|
||||
replay_gain_filter_set_mode(ao->replay_gain_filter, mode);
|
||||
if (ao->other_replay_gain_filter != nullptr)
|
||||
replay_gain_filter_set_mode(ao->other_replay_gain_filter, mode);
|
||||
}
|
||||
|
||||
void
|
||||
|
@@ -1081,8 +1081,11 @@ Player::Run()
|
||||
|
||||
delete cross_fade_tag;
|
||||
|
||||
if (song != nullptr)
|
||||
if (song != nullptr) {
|
||||
const auto uri = song->GetURI();
|
||||
FormatDefault(player_domain, "played \"%s\"", uri.c_str());
|
||||
song->Free();
|
||||
}
|
||||
|
||||
pc.Lock();
|
||||
|
||||
|
@@ -30,7 +30,7 @@ struct playlist {
|
||||
/**
|
||||
* The song queue - it contains the "real" playlist.
|
||||
*/
|
||||
struct queue queue;
|
||||
struct Queue queue;
|
||||
|
||||
/**
|
||||
* This value is true if the player is currently playing (or
|
||||
|
@@ -40,7 +40,7 @@
|
||||
void
|
||||
playlist_print_uris(Client &client, const playlist &playlist)
|
||||
{
|
||||
const queue &queue = playlist.queue;
|
||||
const Queue &queue = playlist.queue;
|
||||
|
||||
queue_print_uris(client, queue, 0, queue.GetLength());
|
||||
}
|
||||
@@ -49,7 +49,7 @@ bool
|
||||
playlist_print_info(Client &client, const playlist &playlist,
|
||||
unsigned start, unsigned end)
|
||||
{
|
||||
const queue &queue = playlist.queue;
|
||||
const Queue &queue = playlist.queue;
|
||||
|
||||
if (end > queue.GetLength())
|
||||
/* correct the "end" offset */
|
||||
|
@@ -65,7 +65,7 @@ playlist_print_uri(FILE *file, const char *uri)
|
||||
}
|
||||
|
||||
PlaylistResult
|
||||
spl_save_queue(const char *name_utf8, const queue &queue)
|
||||
spl_save_queue(const char *name_utf8, const Queue &queue)
|
||||
{
|
||||
if (map_spl_path().IsNull())
|
||||
return PlaylistResult::DISABLED;
|
||||
|
@@ -25,7 +25,7 @@
|
||||
#include <stdio.h>
|
||||
|
||||
struct Song;
|
||||
struct queue;
|
||||
struct Queue;
|
||||
struct playlist;
|
||||
struct PlayerControl;
|
||||
class Error;
|
||||
@@ -40,7 +40,7 @@ playlist_print_uri(FILE *fp, const char *uri);
|
||||
* Saves a queue object into a stored playlist file.
|
||||
*/
|
||||
PlaylistResult
|
||||
spl_save_queue(const char *name_utf8, const queue &queue);
|
||||
spl_save_queue(const char *name_utf8, const Queue &queue);
|
||||
|
||||
/**
|
||||
* Saves a playlist object into a stored playlist file.
|
||||
|
@@ -23,7 +23,7 @@
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
queue::queue(unsigned _max_length)
|
||||
Queue::Queue(unsigned _max_length)
|
||||
:max_length(_max_length), length(0),
|
||||
version(1),
|
||||
items(new Item[max_length]),
|
||||
@@ -36,7 +36,7 @@ queue::queue(unsigned _max_length)
|
||||
{
|
||||
}
|
||||
|
||||
queue::~queue()
|
||||
Queue::~Queue()
|
||||
{
|
||||
Clear();
|
||||
|
||||
@@ -45,7 +45,7 @@ queue::~queue()
|
||||
}
|
||||
|
||||
int
|
||||
queue::GetNextOrder(unsigned _order) const
|
||||
Queue::GetNextOrder(unsigned _order) const
|
||||
{
|
||||
assert(_order < length);
|
||||
|
||||
@@ -62,7 +62,7 @@ queue::GetNextOrder(unsigned _order) const
|
||||
}
|
||||
|
||||
void
|
||||
queue::IncrementVersion()
|
||||
Queue::IncrementVersion()
|
||||
{
|
||||
static unsigned long max = ((uint32_t) 1 << 31) - 1;
|
||||
|
||||
@@ -77,7 +77,7 @@ queue::IncrementVersion()
|
||||
}
|
||||
|
||||
void
|
||||
queue::ModifyAtOrder(unsigned _order)
|
||||
Queue::ModifyAtOrder(unsigned _order)
|
||||
{
|
||||
assert(_order < length);
|
||||
|
||||
@@ -86,7 +86,7 @@ queue::ModifyAtOrder(unsigned _order)
|
||||
}
|
||||
|
||||
unsigned
|
||||
queue::Append(Song *song, uint8_t priority)
|
||||
Queue::Append(Song *song, uint8_t priority)
|
||||
{
|
||||
assert(!IsFull());
|
||||
|
||||
@@ -105,7 +105,7 @@ queue::Append(Song *song, uint8_t priority)
|
||||
}
|
||||
|
||||
void
|
||||
queue::SwapPositions(unsigned position1, unsigned position2)
|
||||
Queue::SwapPositions(unsigned position1, unsigned position2)
|
||||
{
|
||||
unsigned id1 = items[position1].id;
|
||||
unsigned id2 = items[position2].id;
|
||||
@@ -120,7 +120,7 @@ queue::SwapPositions(unsigned position1, unsigned position2)
|
||||
}
|
||||
|
||||
void
|
||||
queue::MovePostion(unsigned from, unsigned to)
|
||||
Queue::MovePostion(unsigned from, unsigned to)
|
||||
{
|
||||
const Item tmp = items[from];
|
||||
|
||||
@@ -156,7 +156,7 @@ queue::MovePostion(unsigned from, unsigned to)
|
||||
}
|
||||
|
||||
void
|
||||
queue::MoveRange(unsigned start, unsigned end, unsigned to)
|
||||
Queue::MoveRange(unsigned start, unsigned end, unsigned to)
|
||||
{
|
||||
Item tmp[end - start];
|
||||
// Copy the original block [start,end-1]
|
||||
@@ -198,7 +198,7 @@ queue::MoveRange(unsigned start, unsigned end, unsigned to)
|
||||
}
|
||||
|
||||
void
|
||||
queue::MoveOrder(unsigned from_order, unsigned to_order)
|
||||
Queue::MoveOrder(unsigned from_order, unsigned to_order)
|
||||
{
|
||||
assert(from_order < length);
|
||||
assert(to_order <= length);
|
||||
@@ -217,7 +217,7 @@ queue::MoveOrder(unsigned from_order, unsigned to_order)
|
||||
}
|
||||
|
||||
void
|
||||
queue::DeletePosition(unsigned position)
|
||||
Queue::DeletePosition(unsigned position)
|
||||
{
|
||||
assert(position < length);
|
||||
|
||||
@@ -254,7 +254,7 @@ queue::DeletePosition(unsigned position)
|
||||
}
|
||||
|
||||
void
|
||||
queue::Clear()
|
||||
Queue::Clear()
|
||||
{
|
||||
for (unsigned i = 0; i < length; i++) {
|
||||
Item *item = &items[i];
|
||||
@@ -270,7 +270,7 @@ queue::Clear()
|
||||
}
|
||||
|
||||
static void
|
||||
queue_sort_order_by_priority(struct queue *queue, unsigned start, unsigned end)
|
||||
queue_sort_order_by_priority(Queue *queue, unsigned start, unsigned end)
|
||||
{
|
||||
assert(queue != nullptr);
|
||||
assert(queue->random);
|
||||
@@ -278,8 +278,8 @@ queue_sort_order_by_priority(struct queue *queue, unsigned start, unsigned end)
|
||||
assert(end <= queue->length);
|
||||
|
||||
auto cmp = [queue](unsigned a_pos, unsigned b_pos){
|
||||
const queue::Item &a = queue->items[a_pos];
|
||||
const queue::Item &b = queue->items[b_pos];
|
||||
const Queue::Item &a = queue->items[a_pos];
|
||||
const Queue::Item &b = queue->items[b_pos];
|
||||
|
||||
return a.priority > b.priority;
|
||||
};
|
||||
@@ -288,7 +288,7 @@ queue_sort_order_by_priority(struct queue *queue, unsigned start, unsigned end)
|
||||
}
|
||||
|
||||
void
|
||||
queue::ShuffleOrderRange(unsigned start, unsigned end)
|
||||
Queue::ShuffleOrderRange(unsigned start, unsigned end)
|
||||
{
|
||||
assert(random);
|
||||
assert(start <= end);
|
||||
@@ -303,7 +303,7 @@ queue::ShuffleOrderRange(unsigned start, unsigned end)
|
||||
* priority group.
|
||||
*/
|
||||
void
|
||||
queue::ShuffleOrderRangeWithPriority(unsigned start, unsigned end)
|
||||
Queue::ShuffleOrderRangeWithPriority(unsigned start, unsigned end)
|
||||
{
|
||||
assert(random);
|
||||
assert(start <= end);
|
||||
@@ -337,13 +337,13 @@ queue::ShuffleOrderRangeWithPriority(unsigned start, unsigned end)
|
||||
}
|
||||
|
||||
void
|
||||
queue::ShuffleOrder()
|
||||
Queue::ShuffleOrder()
|
||||
{
|
||||
ShuffleOrderRangeWithPriority(0, length);
|
||||
}
|
||||
|
||||
void
|
||||
queue::ShuffleOrderFirst(unsigned start, unsigned end)
|
||||
Queue::ShuffleOrderFirst(unsigned start, unsigned end)
|
||||
{
|
||||
rand.AutoCreate();
|
||||
|
||||
@@ -352,7 +352,7 @@ queue::ShuffleOrderFirst(unsigned start, unsigned end)
|
||||
}
|
||||
|
||||
void
|
||||
queue::ShuffleOrderLast(unsigned start, unsigned end)
|
||||
Queue::ShuffleOrderLast(unsigned start, unsigned end)
|
||||
{
|
||||
rand.AutoCreate();
|
||||
|
||||
@@ -361,7 +361,7 @@ queue::ShuffleOrderLast(unsigned start, unsigned end)
|
||||
}
|
||||
|
||||
void
|
||||
queue::ShuffleRange(unsigned start, unsigned end)
|
||||
Queue::ShuffleRange(unsigned start, unsigned end)
|
||||
{
|
||||
assert(start <= end);
|
||||
assert(end <= length);
|
||||
@@ -377,7 +377,7 @@ queue::ShuffleRange(unsigned start, unsigned end)
|
||||
}
|
||||
|
||||
unsigned
|
||||
queue::FindPriorityOrder(unsigned start_order, uint8_t priority,
|
||||
Queue::FindPriorityOrder(unsigned start_order, uint8_t priority,
|
||||
unsigned exclude_order) const
|
||||
{
|
||||
assert(random);
|
||||
@@ -394,7 +394,7 @@ queue::FindPriorityOrder(unsigned start_order, uint8_t priority,
|
||||
}
|
||||
|
||||
unsigned
|
||||
queue::CountSamePriority(unsigned start_order, uint8_t priority) const
|
||||
Queue::CountSamePriority(unsigned start_order, uint8_t priority) const
|
||||
{
|
||||
assert(random);
|
||||
assert(start_order <= length);
|
||||
@@ -410,7 +410,7 @@ queue::CountSamePriority(unsigned start_order, uint8_t priority) const
|
||||
}
|
||||
|
||||
bool
|
||||
queue::SetPriority(unsigned position, uint8_t priority, int after_order)
|
||||
Queue::SetPriority(unsigned position, uint8_t priority, int after_order)
|
||||
{
|
||||
assert(position < length);
|
||||
|
||||
@@ -468,7 +468,7 @@ queue::SetPriority(unsigned position, uint8_t priority, int after_order)
|
||||
}
|
||||
|
||||
bool
|
||||
queue::SetPriorityRange(unsigned start_position, unsigned end_position,
|
||||
Queue::SetPriorityRange(unsigned start_position, unsigned end_position,
|
||||
uint8_t priority, int after_order)
|
||||
{
|
||||
assert(start_position <= end_position);
|
||||
|
@@ -41,7 +41,7 @@ struct Song;
|
||||
* - the unique id (which stays the same, regardless of moves)
|
||||
* - the order number (which only differs from "position" in random mode)
|
||||
*/
|
||||
struct queue {
|
||||
struct Queue {
|
||||
/**
|
||||
* reserve max_length * HASH_MULT elements in the id
|
||||
* number space
|
||||
@@ -103,16 +103,16 @@ struct queue {
|
||||
/** random number generator for shuffle and random mode */
|
||||
LazyRandomEngine rand;
|
||||
|
||||
queue(unsigned max_length);
|
||||
explicit Queue(unsigned max_length);
|
||||
|
||||
/**
|
||||
* Deinitializes a queue object. It does not free the queue
|
||||
* pointer itself.
|
||||
*/
|
||||
~queue();
|
||||
~Queue();
|
||||
|
||||
queue(const queue &other) = delete;
|
||||
queue &operator=(const queue &other) = delete;
|
||||
Queue(const Queue &) = delete;
|
||||
Queue &operator=(const Queue &) = delete;
|
||||
|
||||
unsigned GetLength() const {
|
||||
assert(length <= max_length);
|
||||
|
@@ -38,7 +38,7 @@ extern "C" {
|
||||
* @param end the index of the last song (excluding)
|
||||
*/
|
||||
static void
|
||||
queue_print_song_info(Client &client, const queue &queue,
|
||||
queue_print_song_info(Client &client, const Queue &queue,
|
||||
unsigned position)
|
||||
{
|
||||
song_print_info(client, queue.Get(position));
|
||||
@@ -51,7 +51,7 @@ queue_print_song_info(Client &client, const queue &queue,
|
||||
}
|
||||
|
||||
void
|
||||
queue_print_info(Client &client, const queue &queue,
|
||||
queue_print_info(Client &client, const Queue &queue,
|
||||
unsigned start, unsigned end)
|
||||
{
|
||||
assert(start <= end);
|
||||
@@ -62,7 +62,7 @@ queue_print_info(Client &client, const queue &queue,
|
||||
}
|
||||
|
||||
void
|
||||
queue_print_uris(Client &client, const queue &queue,
|
||||
queue_print_uris(Client &client, const Queue &queue,
|
||||
unsigned start, unsigned end)
|
||||
{
|
||||
assert(start <= end);
|
||||
@@ -75,7 +75,7 @@ queue_print_uris(Client &client, const queue &queue,
|
||||
}
|
||||
|
||||
void
|
||||
queue_print_changes_info(Client &client, const queue &queue,
|
||||
queue_print_changes_info(Client &client, const Queue &queue,
|
||||
uint32_t version)
|
||||
{
|
||||
for (unsigned i = 0; i < queue.GetLength(); i++) {
|
||||
@@ -85,7 +85,7 @@ queue_print_changes_info(Client &client, const queue &queue,
|
||||
}
|
||||
|
||||
void
|
||||
queue_print_changes_position(Client &client, const queue &queue,
|
||||
queue_print_changes_position(Client &client, const Queue &queue,
|
||||
uint32_t version)
|
||||
{
|
||||
for (unsigned i = 0; i < queue.GetLength(); i++)
|
||||
@@ -95,7 +95,7 @@ queue_print_changes_position(Client &client, const queue &queue,
|
||||
}
|
||||
|
||||
void
|
||||
queue_find(Client &client, const queue &queue,
|
||||
queue_find(Client &client, const Queue &queue,
|
||||
const SongFilter &filter)
|
||||
{
|
||||
for (unsigned i = 0; i < queue.GetLength(); i++) {
|
||||
|
@@ -27,28 +27,28 @@
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
struct queue;
|
||||
struct Queue;
|
||||
class SongFilter;
|
||||
class Client;
|
||||
|
||||
void
|
||||
queue_print_info(Client &client, const queue &queue,
|
||||
queue_print_info(Client &client, const Queue &queue,
|
||||
unsigned start, unsigned end);
|
||||
|
||||
void
|
||||
queue_print_uris(Client &client, const queue &queue,
|
||||
queue_print_uris(Client &client, const Queue &queue,
|
||||
unsigned start, unsigned end);
|
||||
|
||||
void
|
||||
queue_print_changes_info(Client &client, const queue &queue,
|
||||
queue_print_changes_info(Client &client, const Queue &queue,
|
||||
uint32_t version);
|
||||
|
||||
void
|
||||
queue_print_changes_position(Client &client, const queue &queue,
|
||||
queue_print_changes_position(Client &client, const Queue &queue,
|
||||
uint32_t version);
|
||||
|
||||
void
|
||||
queue_find(Client &client, const queue &queue,
|
||||
queue_find(Client &client, const Queue &queue,
|
||||
const SongFilter &filter);
|
||||
|
||||
#endif
|
||||
|
@@ -60,7 +60,7 @@ queue_save_song(FILE *fp, int idx, const Song &song)
|
||||
}
|
||||
|
||||
void
|
||||
queue_save(FILE *fp, const queue &queue)
|
||||
queue_save(FILE *fp, const Queue &queue)
|
||||
{
|
||||
for (unsigned i = 0; i < queue.GetLength(); i++) {
|
||||
uint8_t prio = queue.GetPriorityAtPosition(i);
|
||||
@@ -72,7 +72,7 @@ queue_save(FILE *fp, const queue &queue)
|
||||
}
|
||||
|
||||
void
|
||||
queue_load_song(TextFile &file, const char *line, queue &queue)
|
||||
queue_load_song(TextFile &file, const char *line, Queue &queue)
|
||||
{
|
||||
if (queue.IsFull())
|
||||
return;
|
||||
|
@@ -27,16 +27,16 @@
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
struct queue;
|
||||
struct Queue;
|
||||
class TextFile;
|
||||
|
||||
void
|
||||
queue_save(FILE *fp, const queue &queue);
|
||||
queue_save(FILE *fp, const Queue &queue);
|
||||
|
||||
/**
|
||||
* Loads one song from the state file and appends it to the queue.
|
||||
*/
|
||||
void
|
||||
queue_load_song(TextFile &file, const char *line, queue &queue);
|
||||
queue_load_song(TextFile &file, const char *line, Queue &queue);
|
||||
|
||||
#endif
|
||||
|
@@ -101,7 +101,7 @@ bool
|
||||
SongFilter::Item::Match(const Tag &_tag) const
|
||||
{
|
||||
bool visited_types[TAG_NUM_OF_ITEM_TYPES];
|
||||
std::fill_n(visited_types, TAG_NUM_OF_ITEM_TYPES, false);
|
||||
std::fill_n(visited_types, size_t(TAG_NUM_OF_ITEM_TYPES), false);
|
||||
|
||||
for (unsigned i = 0; i < _tag.num_items; i++) {
|
||||
visited_types[_tag.items[i]->type] = true;
|
||||
|
@@ -30,6 +30,7 @@
|
||||
#include "util/Domain.hxx"
|
||||
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#define SONG_MTIME "mtime"
|
||||
#define SONG_END "song_end"
|
||||
|
@@ -95,8 +95,7 @@ Song::UpdateFile()
|
||||
|
||||
TagBuilder tag_builder;
|
||||
if (!tag_file_scan(path_fs,
|
||||
&full_tag_handler, &tag_builder) ||
|
||||
!tag_builder.IsDefined())
|
||||
&full_tag_handler, &tag_builder))
|
||||
return false;
|
||||
|
||||
if (tag_builder.IsEmpty())
|
||||
|
@@ -30,59 +30,75 @@
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
struct stats stats;
|
||||
static GTimer *uptime;
|
||||
static DatabaseStats stats;
|
||||
|
||||
void stats_global_init(void)
|
||||
{
|
||||
stats.timer = g_timer_new();
|
||||
uptime = g_timer_new();
|
||||
}
|
||||
|
||||
void stats_global_finish(void)
|
||||
{
|
||||
g_timer_destroy(stats.timer);
|
||||
g_timer_destroy(uptime);
|
||||
}
|
||||
|
||||
void stats_update(void)
|
||||
{
|
||||
assert(GetDatabase() != nullptr);
|
||||
|
||||
Error error;
|
||||
|
||||
DatabaseStats stats2;
|
||||
|
||||
const DatabaseSelection selection("", true);
|
||||
if (GetDatabase()->GetStats(selection, stats2, error)) {
|
||||
stats.song_count = stats2.song_count;
|
||||
stats.song_duration = stats2.total_duration;
|
||||
stats.artist_count = stats2.artist_count;
|
||||
stats.album_count = stats2.album_count;
|
||||
stats = stats2;
|
||||
} else {
|
||||
LogError(error);
|
||||
|
||||
stats.song_count = 0;
|
||||
stats.song_duration = 0;
|
||||
stats.artist_count = 0;
|
||||
stats.album_count = 0;
|
||||
stats.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
db_stats_print(Client &client)
|
||||
{
|
||||
assert(GetDatabase() != nullptr);
|
||||
|
||||
if (!db_is_simple())
|
||||
/* reload statistics if we're using the "proxy"
|
||||
database plugin */
|
||||
/* TODO: move this into the "proxy" database plugin as
|
||||
an "idle" handler */
|
||||
stats_update();
|
||||
|
||||
client_printf(client,
|
||||
"artists: %u\n"
|
||||
"albums: %u\n"
|
||||
"songs: %u\n"
|
||||
"db_playtime: %lu\n",
|
||||
stats.artist_count,
|
||||
stats.album_count,
|
||||
stats.song_count,
|
||||
stats.total_duration);
|
||||
|
||||
const time_t update_stamp = GetDatabase()->GetUpdateStamp();
|
||||
if (update_stamp > 0)
|
||||
client_printf(client,
|
||||
"db_update: %lu\n",
|
||||
(unsigned long)update_stamp);
|
||||
}
|
||||
|
||||
void
|
||||
stats_print(Client &client)
|
||||
{
|
||||
client_printf(client,
|
||||
"artists: %u\n"
|
||||
"albums: %u\n"
|
||||
"songs: %i\n"
|
||||
"uptime: %li\n"
|
||||
"playtime: %li\n"
|
||||
"db_playtime: %li\n",
|
||||
stats.artist_count,
|
||||
stats.album_count,
|
||||
stats.song_count,
|
||||
(long)g_timer_elapsed(stats.timer, NULL),
|
||||
(long)(client.player_control.GetTotalPlayTime() + 0.5),
|
||||
stats.song_duration);
|
||||
"uptime: %lu\n"
|
||||
"playtime: %lu\n",
|
||||
(unsigned long)g_timer_elapsed(uptime, NULL),
|
||||
(unsigned long)(client.player_control.GetTotalPlayTime() + 0.5));
|
||||
|
||||
if (db_is_simple())
|
||||
client_printf(client,
|
||||
"db_update: %li\n",
|
||||
(long)db_get_mtime());
|
||||
if (GetDatabase() != nullptr)
|
||||
db_stats_print(client);
|
||||
}
|
||||
|
@@ -21,26 +21,6 @@
|
||||
#define MPD_STATS_HXX
|
||||
|
||||
class Client;
|
||||
typedef struct _GTimer GTimer;
|
||||
|
||||
struct stats {
|
||||
GTimer *timer;
|
||||
|
||||
/** number of song files in the music directory */
|
||||
unsigned song_count;
|
||||
|
||||
/** sum of all song durations in the music directory (in
|
||||
seconds) */
|
||||
unsigned long song_duration;
|
||||
|
||||
/** number of distinct artist names in the music directory */
|
||||
unsigned artist_count;
|
||||
|
||||
/** number of distinct album names in the music directory */
|
||||
unsigned album_count;
|
||||
};
|
||||
|
||||
extern struct stats stats;
|
||||
|
||||
void stats_global_init(void);
|
||||
|
||||
|
@@ -28,6 +28,9 @@ struct tag_handler;
|
||||
/**
|
||||
* Scan the tags of a song file. Invokes matching decoder plugins,
|
||||
* but does not invoke the special "APE" and "ID3" scanners.
|
||||
*
|
||||
* @return true if the file was recognized (even if no metadata was
|
||||
* found)
|
||||
*/
|
||||
bool
|
||||
tag_file_scan(Path path,
|
||||
|
@@ -190,18 +190,16 @@ iso9660_input_read(InputStream *is, void *ptr, size_t size,
|
||||
Error &error)
|
||||
{
|
||||
Iso9660InputStream *iis = (Iso9660InputStream *)is;
|
||||
int toread, readed = 0;
|
||||
int readed = 0;
|
||||
int no_blocks, cur_block;
|
||||
size_t left_bytes = iis->statbuf->size - is->offset;
|
||||
|
||||
size = (size * ISO_BLOCKSIZE) / ISO_BLOCKSIZE;
|
||||
|
||||
if (left_bytes < size) {
|
||||
toread = left_bytes;
|
||||
no_blocks = CEILING(left_bytes,ISO_BLOCKSIZE);
|
||||
} else {
|
||||
toread = size;
|
||||
no_blocks = toread / ISO_BLOCKSIZE;
|
||||
no_blocks = size / ISO_BLOCKSIZE;
|
||||
}
|
||||
if (no_blocks > 0) {
|
||||
|
||||
|
@@ -90,7 +90,7 @@ static const struct command commands[] = {
|
||||
{ "disableoutput", PERMISSION_ADMIN, 1, 1, handle_disableoutput },
|
||||
{ "enableoutput", PERMISSION_ADMIN, 1, 1, handle_enableoutput },
|
||||
{ "find", PERMISSION_READ, 2, -1, handle_find },
|
||||
{ "findadd", PERMISSION_READ, 2, -1, handle_findadd},
|
||||
{ "findadd", PERMISSION_ADD, 2, -1, handle_findadd},
|
||||
{ "idle", PERMISSION_READ, 0, -1, handle_idle },
|
||||
{ "kill", PERMISSION_ADMIN, -1, -1, handle_kill },
|
||||
{ "list", PERMISSION_READ, 1, -1, handle_list },
|
||||
|
@@ -46,6 +46,9 @@ class ProxyDatabase : public Database {
|
||||
struct mpd_connection *connection;
|
||||
Directory *root;
|
||||
|
||||
/* this is mutable because GetStats() must be "const" */
|
||||
mutable time_t update_stamp;
|
||||
|
||||
public:
|
||||
static Database *Create(const config_param ¶m,
|
||||
Error &error);
|
||||
@@ -71,6 +74,10 @@ public:
|
||||
DatabaseStats &stats,
|
||||
Error &error) const override;
|
||||
|
||||
virtual time_t GetUpdateStamp() const override {
|
||||
return update_stamp;
|
||||
}
|
||||
|
||||
private:
|
||||
bool Configure(const config_param ¶m, Error &error);
|
||||
|
||||
@@ -237,6 +244,7 @@ ProxyDatabase::Open(Error &error)
|
||||
return false;
|
||||
|
||||
root = Directory::NewRoot();
|
||||
update_stamp = 0;
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -631,6 +639,8 @@ ProxyDatabase::GetStats(const DatabaseSelection &selection,
|
||||
if (stats2 == nullptr)
|
||||
return CheckError(connection, error);
|
||||
|
||||
update_stamp = (time_t)mpd_stats_get_db_update_time(stats2);
|
||||
|
||||
stats.song_count = mpd_stats_get_number_of_songs(stats2);
|
||||
stats.total_duration = mpd_stats_get_db_play_time(stats2);
|
||||
stats.artist_count = mpd_stats_get_number_of_artists(stats2);
|
||||
|
@@ -26,8 +26,6 @@
|
||||
|
||||
#include <cassert>
|
||||
|
||||
#include <time.h>
|
||||
|
||||
struct Directory;
|
||||
|
||||
class SimpleDatabase : public Database {
|
||||
@@ -55,11 +53,6 @@ public:
|
||||
|
||||
bool Save(Error &error);
|
||||
|
||||
gcc_pure
|
||||
time_t GetLastModified() const {
|
||||
return mtime;
|
||||
}
|
||||
|
||||
static Database *Create(const config_param ¶m,
|
||||
Error &error);
|
||||
|
||||
@@ -85,6 +78,10 @@ public:
|
||||
DatabaseStats &stats,
|
||||
Error &error) const override;
|
||||
|
||||
virtual time_t GetUpdateStamp() const override {
|
||||
return mtime;
|
||||
}
|
||||
|
||||
protected:
|
||||
bool Configure(const config_param ¶m, Error &error);
|
||||
|
||||
|
@@ -47,6 +47,17 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
class DffDsdUint64 {
|
||||
uint32_t hi;
|
||||
uint32_t lo;
|
||||
|
||||
public:
|
||||
constexpr uint64_t Read() const {
|
||||
return (uint64_t(FromBE32(hi)) << 32) |
|
||||
uint64_t(FromBE32(lo));
|
||||
}
|
||||
};
|
||||
|
||||
bool
|
||||
dsdlib_read(Decoder *decoder, InputStream &is,
|
||||
void *data, size_t length);
|
||||
|
@@ -43,13 +43,13 @@
|
||||
|
||||
struct DsdiffHeader {
|
||||
DsdId id;
|
||||
DsdUint64 size;
|
||||
DffDsdUint64 size;
|
||||
DsdId format;
|
||||
};
|
||||
|
||||
struct DsdiffChunkHeader {
|
||||
DsdId id;
|
||||
DsdUint64 size;
|
||||
DffDsdUint64 size;
|
||||
|
||||
/**
|
||||
* Read the "size" attribute from the specified header, converting it
|
||||
@@ -294,7 +294,6 @@ dsdiff_read_metadata_extra(Decoder *decoder, InputStream &is,
|
||||
if (!dsdiff_read_chunk_header(decoder, is, chunk_header))
|
||||
return false;
|
||||
}
|
||||
chunk_size = 0;
|
||||
}
|
||||
/* done processing chunk headers, process tags if any */
|
||||
|
||||
@@ -385,10 +384,10 @@ dsdiff_decode_chunk(Decoder &decoder, InputStream &is,
|
||||
while (chunk_size > 0) {
|
||||
/* see how much aligned data from the remaining chunk
|
||||
fits into the local buffer */
|
||||
unsigned now_frames = buffer_frames;
|
||||
size_t now_size = buffer_size;
|
||||
if (chunk_size < (uint64_t)now_size) {
|
||||
now_frames = (unsigned)chunk_size / frame_size;
|
||||
unsigned now_frames =
|
||||
(unsigned)chunk_size / frame_size;
|
||||
now_size = now_frames * frame_size;
|
||||
}
|
||||
|
||||
|
@@ -240,10 +240,10 @@ dsf_decode_chunk(Decoder &decoder, InputStream &is,
|
||||
while (chunk_size > 0) {
|
||||
/* see how much aligned data from the remaining chunk
|
||||
fits into the local buffer */
|
||||
unsigned now_frames = buffer_frames;
|
||||
size_t now_size = buffer_size;
|
||||
if (chunk_size < (uint64_t)now_size) {
|
||||
now_frames = (unsigned)chunk_size / frame_size;
|
||||
unsigned now_frames =
|
||||
(unsigned)chunk_size / frame_size;
|
||||
now_size = now_frames * frame_size;
|
||||
}
|
||||
|
||||
|
@@ -393,6 +393,7 @@ faad_stream_decode(Decoder &mpd_decoder, InputStream &is)
|
||||
if (!ret) {
|
||||
LogError(error);
|
||||
NeAACDecClose(decoder);
|
||||
decoder_buffer_free(buffer);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -461,6 +462,7 @@ faad_stream_decode(Decoder &mpd_decoder, InputStream &is)
|
||||
/* cleanup */
|
||||
|
||||
NeAACDecClose(decoder);
|
||||
decoder_buffer_free(buffer);
|
||||
}
|
||||
|
||||
static bool
|
||||
|
@@ -38,7 +38,10 @@ extern "C" {
|
||||
#include <libavutil/avutil.h>
|
||||
#include <libavutil/log.h>
|
||||
#include <libavutil/mathematics.h>
|
||||
#include <libavutil/dict.h>
|
||||
|
||||
#if LIBAVUTIL_VERSION_MAJOR >= 53
|
||||
#include <libavutil/frame.h>
|
||||
#endif
|
||||
}
|
||||
|
||||
#include <assert.h>
|
||||
@@ -120,8 +123,7 @@ mpd_ffmpeg_stream_seek(void *opaque, int64_t pos, int whence)
|
||||
if (whence == AVSEEK_SIZE)
|
||||
return stream->input.size;
|
||||
|
||||
Error error;
|
||||
if (!stream->input.LockSeek(pos, whence, error))
|
||||
if (!stream->input.LockSeek(pos, whence, IgnoreError()))
|
||||
return -1;
|
||||
|
||||
return stream->input.offset;
|
||||
@@ -224,6 +226,9 @@ copy_interleave_frame(const AVCodecContext *codec_context,
|
||||
codec_context->channels,
|
||||
frame->nb_samples,
|
||||
codec_context->sample_fmt, 1);
|
||||
if (data_size <= 0)
|
||||
return data_size;
|
||||
|
||||
if (av_sample_fmt_is_planar(codec_context->sample_fmt) &&
|
||||
codec_context->channels > 1) {
|
||||
if(*global_buffer_size < data_size) {
|
||||
@@ -252,13 +257,14 @@ static DecoderCommand
|
||||
ffmpeg_send_packet(Decoder &decoder, InputStream &is,
|
||||
const AVPacket *packet,
|
||||
AVCodecContext *codec_context,
|
||||
const AVRational *time_base,
|
||||
const AVStream *stream,
|
||||
AVFrame *frame,
|
||||
uint8_t **buffer, int *buffer_size)
|
||||
{
|
||||
if (packet->pts >= 0 && packet->pts != (int64_t)AV_NOPTS_VALUE)
|
||||
decoder_timestamp(decoder,
|
||||
time_from_ffmpeg(packet->pts, *time_base));
|
||||
time_from_ffmpeg(packet->pts - stream->start_time,
|
||||
stream->time_base));
|
||||
|
||||
AVPacket packet2 = *packet;
|
||||
|
||||
@@ -342,11 +348,9 @@ ffmpeg_probe(Decoder *decoder, InputStream &is)
|
||||
PADDING = 16,
|
||||
};
|
||||
|
||||
Error error;
|
||||
|
||||
unsigned char buffer[BUFFER_SIZE];
|
||||
size_t nbytes = decoder_read(decoder, is, buffer, BUFFER_SIZE);
|
||||
if (nbytes <= PADDING || !is.LockRewind(error))
|
||||
if (nbytes <= PADDING || !is.LockRewind(IgnoreError()))
|
||||
return nullptr;
|
||||
|
||||
/* some ffmpeg parsers (e.g. ac3_parser.c) read a few bytes
|
||||
@@ -453,7 +457,11 @@ ffmpeg_decode(Decoder &decoder, InputStream &input)
|
||||
decoder_initialized(decoder, audio_format,
|
||||
input.seekable, total_time);
|
||||
|
||||
#if LIBAVUTIL_VERSION_MAJOR >= 53
|
||||
AVFrame *frame = av_frame_alloc();
|
||||
#else
|
||||
AVFrame *frame = avcodec_alloc_frame();
|
||||
#endif
|
||||
if (!frame) {
|
||||
LogError(ffmpeg_domain, "Could not allocate frame");
|
||||
avformat_close_input(&format_context);
|
||||
@@ -473,7 +481,7 @@ ffmpeg_decode(Decoder &decoder, InputStream &input)
|
||||
if (packet.stream_index == audio_stream)
|
||||
cmd = ffmpeg_send_packet(decoder, input,
|
||||
&packet, codec_context,
|
||||
&av_stream->time_base,
|
||||
av_stream,
|
||||
frame,
|
||||
&interleaved_buffer, &interleaved_buffer_size);
|
||||
else
|
||||
@@ -484,7 +492,8 @@ ffmpeg_decode(Decoder &decoder, InputStream &input)
|
||||
if (cmd == DecoderCommand::SEEK) {
|
||||
int64_t where =
|
||||
time_to_ffmpeg(decoder_seek_where(decoder),
|
||||
av_stream->time_base);
|
||||
av_stream->time_base) +
|
||||
av_stream->start_time;
|
||||
|
||||
if (av_seek_frame(format_context, audio_stream, where,
|
||||
AV_TIME_BASE) < 0)
|
||||
@@ -496,7 +505,9 @@ ffmpeg_decode(Decoder &decoder, InputStream &input)
|
||||
}
|
||||
} while (cmd != DecoderCommand::STOP);
|
||||
|
||||
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(54, 28, 0)
|
||||
#if LIBAVUTIL_VERSION_MAJOR >= 53
|
||||
av_frame_free(&frame);
|
||||
#elif LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(54, 28, 0)
|
||||
avcodec_free_frame(&frame);
|
||||
#else
|
||||
av_freep(&frame);
|
||||
|
@@ -21,8 +21,6 @@
|
||||
#define MPD_FFMPEG_METADATA_HXX
|
||||
|
||||
extern "C" {
|
||||
#include <libavformat/avformat.h>
|
||||
#include <libavutil/avutil.h>
|
||||
#include <libavutil/dict.h>
|
||||
}
|
||||
|
||||
@@ -35,6 +33,6 @@ struct tag_handler;
|
||||
|
||||
void
|
||||
ffmpeg_scan_dictionary(AVDictionary *dict,
|
||||
const struct tag_handler *handler, void *handler_ctx);
|
||||
const tag_handler *handler, void *handler_ctx);
|
||||
|
||||
#endif
|
||||
|
@@ -22,6 +22,8 @@
|
||||
#include "OggSyncState.hxx"
|
||||
#include "util/Error.hxx"
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
bool
|
||||
OggFindEOS(OggSyncState &oy, ogg_stream_state &os, ogg_packet &packet)
|
||||
{
|
||||
|
@@ -34,6 +34,7 @@
|
||||
#include <glib.h>
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
struct opus_encoder {
|
||||
/** the base class */
|
||||
|
@@ -39,6 +39,12 @@
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#ifdef USE_SIGNALFD
|
||||
#include <pthread.h>
|
||||
#endif
|
||||
|
||||
#include <signal.h>
|
||||
|
||||
class SignalMonitor final : private SocketMonitor {
|
||||
#ifdef USE_SIGNALFD
|
||||
SignalFD fd;
|
||||
@@ -99,7 +105,21 @@ static std::atomic_bool signal_pending[MAX_SIGNAL];
|
||||
|
||||
static Manual<SignalMonitor> monitor;
|
||||
|
||||
#ifndef USE_SIGNALFD
|
||||
#ifdef USE_SIGNALFD
|
||||
|
||||
/**
|
||||
* This is a pthread_atfork() callback that unblocks the signals that
|
||||
* were blocked for our signalfd(). Without this, our child processes
|
||||
* would inherit the blocked signals.
|
||||
*/
|
||||
static void
|
||||
at_fork_child()
|
||||
{
|
||||
sigprocmask(SIG_UNBLOCK, &signal_mask, nullptr);
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
static void
|
||||
SignalCallback(int signo)
|
||||
{
|
||||
@@ -108,6 +128,7 @@ SignalCallback(int signo)
|
||||
if (!signal_pending[signo].exchange(true))
|
||||
monitor->WakeUp();
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
void
|
||||
@@ -115,6 +136,8 @@ SignalMonitorInit(EventLoop &loop)
|
||||
{
|
||||
#ifdef USE_SIGNALFD
|
||||
sigemptyset(&signal_mask);
|
||||
|
||||
pthread_atfork(nullptr, nullptr, at_fork_child);
|
||||
#endif
|
||||
|
||||
monitor.Construct(loop);
|
||||
|
@@ -56,6 +56,7 @@
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
class RouteFilter final : public Filter {
|
||||
/**
|
||||
|
@@ -41,7 +41,12 @@
|
||||
#include <glib.h>
|
||||
#include <assert.h>
|
||||
|
||||
#ifdef HAVE_CDIO_PARANOIA_PARANOIA_H
|
||||
#include <cdio/paranoia/paranoia.h>
|
||||
#else
|
||||
#include <cdio/paranoia.h>
|
||||
#endif
|
||||
|
||||
#include <cdio/cd_types.h>
|
||||
|
||||
struct CdioParanoiaInputStream {
|
||||
|
@@ -273,12 +273,27 @@ public:
|
||||
SocketAction(CURL_SOCKET_TIMEOUT, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* This is a kludge to allow pausing/resuming a stream with
|
||||
* libcurl < 7.32.0. Read the curl_easy_pause manpage for
|
||||
* more information.
|
||||
*/
|
||||
void ResumeSockets() {
|
||||
int running_handles;
|
||||
curl_multi_socket_all(multi, &running_handles);
|
||||
}
|
||||
|
||||
private:
|
||||
static int TimerFunction(CURLM *multi, long timeout_ms, void *userp);
|
||||
|
||||
virtual void OnTimeout() override;
|
||||
};
|
||||
|
||||
/**
|
||||
* libcurl version number encoded in a 24 bit integer.
|
||||
*/
|
||||
static unsigned curl_version_num;
|
||||
|
||||
/** libcurl should accept "ICY 200 OK" */
|
||||
static struct curl_slist *http_200_aliases;
|
||||
|
||||
@@ -330,6 +345,13 @@ input_curl_resume(struct input_curl *c)
|
||||
if (c->paused) {
|
||||
c->paused = false;
|
||||
curl_easy_pause(c->easy, CURLPAUSE_CONT);
|
||||
|
||||
if (curl_version_num < 0x072000)
|
||||
/* libcurl older than 7.32.0 does not update
|
||||
its sockets after curl_easy_pause(); force
|
||||
libcurl to do it now */
|
||||
curl_multi->ResumeSockets();
|
||||
|
||||
curl_multi->InvalidateSockets();
|
||||
}
|
||||
}
|
||||
@@ -586,6 +608,16 @@ input_curl_init(const config_param ¶m, Error &error)
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto version_info = curl_version_info(CURLVERSION_FIRST);
|
||||
if (version_info != nullptr) {
|
||||
FormatDebug(curl_domain, "version %s", version_info->version);
|
||||
if (version_info->features & CURL_VERSION_SSL)
|
||||
FormatDebug(curl_domain, "with %s",
|
||||
version_info->ssl_version);
|
||||
|
||||
curl_version_num = version_info->version_num;
|
||||
}
|
||||
|
||||
http_200_aliases = curl_slist_append(http_200_aliases, "ICY 200 OK");
|
||||
|
||||
proxy = param.GetBlockValue("proxy");
|
||||
|
@@ -24,6 +24,7 @@
|
||||
#include "Main.hxx"
|
||||
#include "event/MultiSocketMonitor.hxx"
|
||||
#include "event/Loop.hxx"
|
||||
#include "event/Call.hxx"
|
||||
#include "util/ASCII.hxx"
|
||||
#include "util/ReusableArray.hxx"
|
||||
#include "util/Error.hxx"
|
||||
@@ -46,10 +47,22 @@ class AlsaMixerMonitor final : private MultiSocketMonitor {
|
||||
public:
|
||||
AlsaMixerMonitor(EventLoop &_loop, snd_mixer_t *_mixer)
|
||||
:MultiSocketMonitor(_loop), mixer(_mixer) {
|
||||
#ifdef USE_EPOLL
|
||||
_loop.AddCall([this](){ InvalidateSockets(); });
|
||||
#else
|
||||
_loop.AddIdle(InitAlsaMixerMonitor, this);
|
||||
#endif
|
||||
}
|
||||
|
||||
private:
|
||||
#ifndef USE_EPOLL
|
||||
static gboolean InitAlsaMixerMonitor(gpointer data) {
|
||||
AlsaMixerMonitor &amm = *(AlsaMixerMonitor *)data;
|
||||
amm.InvalidateSockets();
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
virtual int PrepareSockets() override;
|
||||
virtual void DispatchSockets() override;
|
||||
};
|
||||
|
@@ -106,12 +106,16 @@ struct AlsaOutput {
|
||||
snd_pcm_uframes_t period_position;
|
||||
|
||||
/**
|
||||
* Set to non-zero when the Raspberry Pi workaround has been
|
||||
* activated in alsa_recover(); decremented by each write.
|
||||
* This will avoid activating it again, leading to an endless
|
||||
* loop. This problem was observed with a "RME Digi9636/52".
|
||||
* Do we need to call snd_pcm_prepare() before the next write?
|
||||
* It means that we put the device to SND_PCM_STATE_SETUP by
|
||||
* calling snd_pcm_drop().
|
||||
*
|
||||
* Without this flag, we could easily recover after a failed
|
||||
* optimistic write (returning -EBADFD), but the Raspberry Pi
|
||||
* audio driver is infamous for generating ugly artefacts from
|
||||
* this.
|
||||
*/
|
||||
unsigned pi_workaround;
|
||||
bool must_prepare;
|
||||
|
||||
/**
|
||||
* This buffer gets allocated after opening the ALSA device.
|
||||
@@ -676,8 +680,6 @@ alsa_open(struct audio_output *ao, AudioFormat &audio_format, Error &error)
|
||||
{
|
||||
AlsaOutput *ad = (AlsaOutput *)ao;
|
||||
|
||||
ad->pi_workaround = 0;
|
||||
|
||||
int err = snd_pcm_open(&ad->pcm, alsa_device(ad),
|
||||
SND_PCM_STREAM_PLAYBACK, ad->mode);
|
||||
if (err < 0) {
|
||||
@@ -699,6 +701,8 @@ alsa_open(struct audio_output *ao, AudioFormat &audio_format, Error &error)
|
||||
ad->in_frame_size = audio_format.GetFrameSize();
|
||||
ad->out_frame_size = ad->pcm_export->GetFrameSize(audio_format);
|
||||
|
||||
ad->must_prepare = false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -736,29 +740,6 @@ alsa_recover(AlsaOutput *ad, int err)
|
||||
case SND_PCM_STATE_XRUN:
|
||||
ad->period_position = 0;
|
||||
err = snd_pcm_prepare(ad->pcm);
|
||||
|
||||
if (err == 0 && ad->pi_workaround == 0) {
|
||||
/* this works around a driver bug observed on
|
||||
the Raspberry Pi: after snd_pcm_drop(), the
|
||||
whole ring buffer must be invalidated, but
|
||||
the snd_pcm_prepare() call above makes the
|
||||
driver play random data that just happens
|
||||
to be still in the buffer; by adding and
|
||||
cancelling some silence, this bug does not
|
||||
occur */
|
||||
alsa_write_silence(ad, ad->period_frames);
|
||||
|
||||
/* cancel the silence data right away to avoid
|
||||
increasing latency; even though this
|
||||
function call invalidates the portion of
|
||||
silence, the driver seems to avoid the
|
||||
bug */
|
||||
snd_pcm_reset(ad->pcm);
|
||||
|
||||
/* disable the workaround for some time */
|
||||
ad->pi_workaround = 8;
|
||||
}
|
||||
|
||||
break;
|
||||
case SND_PCM_STATE_DISCONNECTED:
|
||||
break;
|
||||
@@ -801,6 +782,7 @@ alsa_cancel(struct audio_output *ao)
|
||||
AlsaOutput *ad = (AlsaOutput *)ao;
|
||||
|
||||
ad->period_position = 0;
|
||||
ad->must_prepare = true;
|
||||
|
||||
snd_pcm_drop(ad->pcm);
|
||||
}
|
||||
@@ -822,6 +804,16 @@ alsa_play(struct audio_output *ao, const void *chunk, size_t size,
|
||||
|
||||
assert(size % ad->in_frame_size == 0);
|
||||
|
||||
if (ad->must_prepare) {
|
||||
ad->must_prepare = false;
|
||||
|
||||
int err = snd_pcm_prepare(ad->pcm);
|
||||
if (err < 0) {
|
||||
error.Set(alsa_output_domain, err, snd_strerror(-err));
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
chunk = ad->pcm_export->Export(chunk, size, size);
|
||||
|
||||
assert(size % ad->out_frame_size == 0);
|
||||
@@ -834,9 +826,6 @@ alsa_play(struct audio_output *ao, const void *chunk, size_t size,
|
||||
ad->period_position = (ad->period_position + ret)
|
||||
% ad->period_frames;
|
||||
|
||||
if (ad->pi_workaround > 0)
|
||||
--ad->pi_workaround;
|
||||
|
||||
size_t bytes_written = ret * ad->out_frame_size;
|
||||
return ad->pcm_export->CalcSourceSize(bytes_written);
|
||||
}
|
||||
|
@@ -30,6 +30,11 @@
|
||||
#include "thread/Mutex.hxx"
|
||||
#include "event/ServerSocket.hxx"
|
||||
|
||||
#ifdef _LIBCPP_VERSION
|
||||
/* can't use incomplete template arguments with libc++ */
|
||||
#include "HttpdClient.hxx"
|
||||
#endif
|
||||
|
||||
#include <forward_list>
|
||||
|
||||
struct config_param;
|
||||
|
@@ -363,9 +363,9 @@ osx_output_open(struct audio_output *ao, AudioFormat &audio_format,
|
||||
|
||||
OSStatus status = AudioUnitInitialize(od->au);
|
||||
if (status != noErr) {
|
||||
error.Set(osx_output_domain, status,
|
||||
"Unable to initialize OS X audio unit: %s",
|
||||
GetMacOSStatusCommentString(status));
|
||||
error.Format(osx_output_domain, status,
|
||||
"Unable to initialize OS X audio unit: %s",
|
||||
GetMacOSStatusCommentString(status));
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@@ -25,7 +25,7 @@
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
#ifndef HAVE_OSX
|
||||
#ifndef __APPLE__
|
||||
#include <AL/al.h>
|
||||
#include <AL/alc.h>
|
||||
#else
|
||||
|
@@ -369,8 +369,6 @@ pulse_output_enable(struct audio_output *ao, Error &error)
|
||||
|
||||
po->mainloop = pa_threaded_mainloop_new();
|
||||
if (po->mainloop == nullptr) {
|
||||
g_free(po);
|
||||
|
||||
error.Set(pulse_output_domain,
|
||||
"pa_threaded_mainloop_new() has failed");
|
||||
return false;
|
||||
|
@@ -148,3 +148,26 @@ PcmResampler::Resample32(unsigned channels, unsigned src_rate,
|
||||
src_rate, src_buffer, src_size,
|
||||
dest_rate, dest_size_r);
|
||||
}
|
||||
|
||||
const int32_t *
|
||||
PcmResampler::Resample24(unsigned channels, unsigned src_rate,
|
||||
const int32_t *src_buffer, size_t src_size,
|
||||
unsigned dest_rate, size_t *dest_size_r,
|
||||
Error &error_r)
|
||||
{
|
||||
#ifdef HAVE_LIBSAMPLERATE
|
||||
if (pcm_resample_lsr_enabled())
|
||||
return pcm_resample_lsr_24(this, channels,
|
||||
src_rate, src_buffer, src_size,
|
||||
dest_rate, dest_size_r,
|
||||
error_r);
|
||||
#else
|
||||
(void)error_r;
|
||||
#endif
|
||||
|
||||
/* reuse the 32 bit code - the resampler code doesn't care if
|
||||
the upper 8 bits are actually used */
|
||||
return pcm_resample_fallback_32(this, channels,
|
||||
src_rate, src_buffer, src_size,
|
||||
dest_rate, dest_size_r);
|
||||
}
|
||||
|
@@ -124,13 +124,7 @@ struct PcmResampler {
|
||||
const int32_t *Resample24(unsigned channels, unsigned src_rate,
|
||||
const int32_t *src_buffer, size_t src_size,
|
||||
unsigned dest_rate, size_t *dest_size_r,
|
||||
Error &error_r)
|
||||
{
|
||||
/* reuse the 32 bit code - the resampler code doesn't care if
|
||||
the upper 8 bits are actually used */
|
||||
return Resample32(channels, src_rate, src_buffer, src_size,
|
||||
dest_rate, dest_size_r, error_r);
|
||||
}
|
||||
Error &error_r);
|
||||
};
|
||||
|
||||
bool
|
||||
|
@@ -69,6 +69,15 @@ pcm_resample_lsr_32(PcmResampler *state,
|
||||
unsigned dest_rate, size_t *dest_size_r,
|
||||
Error &error);
|
||||
|
||||
const int32_t *
|
||||
pcm_resample_lsr_24(PcmResampler *state,
|
||||
unsigned channels,
|
||||
unsigned src_rate,
|
||||
const int32_t *src_buffer,
|
||||
size_t src_size,
|
||||
unsigned dest_rate, size_t *dest_size_r,
|
||||
Error &error);
|
||||
|
||||
#endif
|
||||
|
||||
const int16_t *
|
||||
|
@@ -19,6 +19,7 @@
|
||||
|
||||
#include "config.h"
|
||||
#include "PcmResampleInternal.hxx"
|
||||
#include "PcmUtils.hxx"
|
||||
#include "util/ASCII.hxx"
|
||||
#include "util/Error.hxx"
|
||||
#include "util/Domain.hxx"
|
||||
@@ -283,3 +284,27 @@ pcm_resample_lsr_32(PcmResampler *state,
|
||||
|
||||
return dest_buffer;
|
||||
}
|
||||
|
||||
const int32_t *
|
||||
pcm_resample_lsr_24(PcmResampler *state,
|
||||
unsigned channels,
|
||||
unsigned src_rate,
|
||||
const int32_t *src_buffer, size_t src_size,
|
||||
unsigned dest_rate, size_t *dest_size_r,
|
||||
Error &error)
|
||||
{
|
||||
const auto result = pcm_resample_lsr_32(state, channels,
|
||||
src_rate, src_buffer, src_size,
|
||||
dest_rate, dest_size_r,
|
||||
error);
|
||||
if (result != nullptr)
|
||||
/* src_float_to_int_array() clamps for 32 bit
|
||||
integers; now make sure everything's fine for 24
|
||||
bit */
|
||||
/* TODO: eliminate the 32 bit clamp to reduce overhead */
|
||||
PcmClampN<int32_t, int32_t, 24>(const_cast<int32_t *>(result),
|
||||
result,
|
||||
*dest_size_r / sizeof(*result));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
@@ -63,4 +63,16 @@ PcmClamp(U x)
|
||||
return T(x);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the values in this buffer are within the range of the
|
||||
* provided bit size, and clamps them whenever necessary.
|
||||
*/
|
||||
template<typename T, typename U, unsigned bits>
|
||||
static inline void
|
||||
PcmClampN(T *dest, const U *src, unsigned n)
|
||||
{
|
||||
while (n-- > 0)
|
||||
*dest++ = PcmClamp<T, U, bits>(*src++);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
@@ -68,7 +68,6 @@ pls_parser(GKeyFile *keyfile, std::forward_list<SongPointer> &songs)
|
||||
FormatError(pls_domain, "Invalid PLS entry %s: '%s'",
|
||||
key, error->message);
|
||||
g_error_free(error);
|
||||
g_free(key);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@@ -33,11 +33,23 @@
|
||||
#include <stdint.h>
|
||||
|
||||
#if defined(__i386__) || defined(__x86_64__) || defined(__ARMEL__)
|
||||
#define IS_LITTLE_ENDIAN true
|
||||
#define IS_BIG_ENDIAN false
|
||||
/* well-known little-endian */
|
||||
# define IS_LITTLE_ENDIAN true
|
||||
# define IS_BIG_ENDIAN false
|
||||
#elif defined(__MIPSEB__)
|
||||
/* well-known big-endian */
|
||||
# define IS_LITTLE_ENDIAN false
|
||||
# define IS_BIG_ENDIAN true
|
||||
#else
|
||||
#define IS_LITTLE_ENDIAN false
|
||||
#define IS_BIG_ENDIAN true
|
||||
/* generic compile-time check */
|
||||
# include <endian.h>
|
||||
# if __BYTE_ORDER == __LITTLE_ENDIAN
|
||||
# define IS_LITTLE_ENDIAN true
|
||||
# define IS_BIG_ENDIAN false
|
||||
# else
|
||||
# define IS_LITTLE_ENDIAN false
|
||||
# define IS_BIG_ENDIAN true
|
||||
# endif
|
||||
#endif
|
||||
|
||||
static inline constexpr bool
|
||||
|
@@ -104,6 +104,11 @@ socketpair_cloexec_nonblock(int domain, int type, int protocol, int sv[2]);
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_LIBMPDCLIENT
|
||||
/* Avoid symbol conflict with statically linked libmpdclient */
|
||||
#define socket_cloexec_nonblock socket_cloexec_nonblock_noconflict
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Wrapper for socket(), which sets the CLOEXEC and the NONBLOCK flag
|
||||
* (atomically if supported by the OS).
|
||||
|
@@ -87,7 +87,8 @@ riff_seek_id3(FILE *file)
|
||||
/* pad byte */
|
||||
++size;
|
||||
|
||||
if (memcmp(chunk.id, "id3 ", 4) == 0)
|
||||
if (memcmp(chunk.id, "id3 ", 4) == 0 ||
|
||||
memcmp(chunk.id, "ID3 ", 4) == 0)
|
||||
/* found it! */
|
||||
return size;
|
||||
|
||||
|
@@ -39,7 +39,7 @@ TagLoadConfig()
|
||||
if (value == nullptr)
|
||||
return;
|
||||
|
||||
std::fill_n(ignore_tag_items, TAG_NUM_OF_ITEM_TYPES, true);
|
||||
std::fill_n(ignore_tag_items, size_t(TAG_NUM_OF_ITEM_TYPES), true);
|
||||
|
||||
if (StringEqualsCaseASCII(value, "none"))
|
||||
return;
|
||||
|
@@ -24,6 +24,7 @@
|
||||
#include "Compiler.h"
|
||||
|
||||
#include <string>
|
||||
#include <algorithm>
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
|
@@ -49,12 +49,12 @@ public:
|
||||
*/
|
||||
void AutoCreate();
|
||||
|
||||
result_type min() const {
|
||||
return engine->min();
|
||||
static constexpr result_type min() {
|
||||
return std::mt19937::min();
|
||||
}
|
||||
|
||||
result_type max() const {
|
||||
return engine->max();
|
||||
static constexpr result_type max() {
|
||||
return std::mt19937::max();
|
||||
}
|
||||
|
||||
result_type operator()() {
|
||||
|
@@ -130,8 +130,9 @@ PeakBuffer::Append(const void *data, size_t length)
|
||||
return true;
|
||||
}
|
||||
|
||||
if (peak_buffer == nullptr && peak_size > 0) {
|
||||
peak_buffer = (fifo_buffer *)HugeAllocate(peak_size);
|
||||
if (peak_buffer == nullptr) {
|
||||
if (peak_size > 0)
|
||||
peak_buffer = (fifo_buffer *)HugeAllocate(peak_size);
|
||||
if (peak_buffer == nullptr)
|
||||
return false;
|
||||
|
||||
|
@@ -42,7 +42,12 @@ class RefCount {
|
||||
std::atomic_uint n;
|
||||
|
||||
public:
|
||||
constexpr RefCount():n(1) {}
|
||||
#ifndef _LIBCPP_VERSION
|
||||
/* the "constexpr" is missing in libc++'s "atomic"
|
||||
implementation */
|
||||
constexpr
|
||||
#endif
|
||||
RefCount():n(1) {}
|
||||
|
||||
void Increment() {
|
||||
++n;
|
||||
|
@@ -32,7 +32,8 @@ const char *
|
||||
uri_get_suffix(const char *uri)
|
||||
{
|
||||
const char *suffix = strrchr(uri, '.');
|
||||
if (suffix == nullptr)
|
||||
if (suffix == nullptr || suffix == uri ||
|
||||
suffix[-1] == '/' || suffix[-1] == '\\')
|
||||
return nullptr;
|
||||
|
||||
++suffix;
|
||||
|
@@ -36,6 +36,7 @@
|
||||
|
||||
#include <assert.h>
|
||||
#include <stddef.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
|
||||
static void
|
||||
|
85
test/test_icy_parser.cxx
Normal file
85
test/test_icy_parser.cxx
Normal file
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* Unit tests for class IcyMetaDataParser.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
/* include the .cxx file to get access to internal functions */
|
||||
#include "IcyMetaDataParser.cxx"
|
||||
|
||||
#include <cppunit/TestFixture.h>
|
||||
#include <cppunit/extensions/TestFactoryRegistry.h>
|
||||
#include <cppunit/ui/text/TestRunner.h>
|
||||
#include <cppunit/extensions/HelperMacros.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <string.h>
|
||||
|
||||
static Tag *
|
||||
icy_parse_tag(const char *p)
|
||||
{
|
||||
char *q = strdup(p);
|
||||
Tag *tag = icy_parse_tag(q, q + strlen(q));
|
||||
free(q);
|
||||
return tag;
|
||||
}
|
||||
|
||||
static void
|
||||
CompareTagTitle(const Tag &tag, const std::string &title)
|
||||
{
|
||||
CPPUNIT_ASSERT_EQUAL(1u, tag.num_items);
|
||||
|
||||
const TagItem &item = *tag.items[0];
|
||||
CPPUNIT_ASSERT_EQUAL(TAG_TITLE, item.type);
|
||||
CPPUNIT_ASSERT_EQUAL(title, std::string(item.value));
|
||||
}
|
||||
|
||||
static void
|
||||
TestIcyParserTitle(const char *input, const char *title)
|
||||
{
|
||||
Tag *tag = icy_parse_tag(input);
|
||||
CompareTagTitle(*tag, title);
|
||||
delete tag;
|
||||
}
|
||||
|
||||
static void
|
||||
TestIcyParserEmpty(const char *input)
|
||||
{
|
||||
Tag *tag = icy_parse_tag(input);
|
||||
CPPUNIT_ASSERT_EQUAL(0u, tag->num_items);
|
||||
delete tag;
|
||||
}
|
||||
|
||||
class IcyTest : public CppUnit::TestFixture {
|
||||
CPPUNIT_TEST_SUITE(IcyTest);
|
||||
CPPUNIT_TEST(TestIcyMetadataParser);
|
||||
CPPUNIT_TEST_SUITE_END();
|
||||
|
||||
public:
|
||||
void TestIcyMetadataParser() {
|
||||
TestIcyParserEmpty("foo=bar;");
|
||||
TestIcyParserTitle("StreamTitle='foo bar'", "foo bar");
|
||||
TestIcyParserTitle("StreamTitle='foo bar';", "foo bar");
|
||||
TestIcyParserTitle("StreamTitle='foo\"bar';", "foo\"bar");
|
||||
TestIcyParserTitle("StreamTitle='foo=bar';", "foo=bar");
|
||||
TestIcyParserTitle("a=b;StreamTitle='foo';", "foo");
|
||||
TestIcyParserTitle("a=;StreamTitle='foo';", "foo");
|
||||
TestIcyParserTitle("a=b;StreamTitle='foo';c=d", "foo");
|
||||
TestIcyParserTitle("a=b;StreamTitle='foo'", "foo");
|
||||
TestIcyParserTitle("a='b;c';StreamTitle='foo;bar'", "foo;bar");
|
||||
TestIcyParserTitle("a='b'c';StreamTitle='foo'bar'", "foo'bar");
|
||||
TestIcyParserTitle("StreamTitle='fo'o'b'ar';a='b'c'd'", "fo'o'b'ar");
|
||||
}
|
||||
};
|
||||
|
||||
CPPUNIT_TEST_SUITE_REGISTRATION(IcyTest);
|
||||
|
||||
int
|
||||
main(gcc_unused int argc, gcc_unused char **argv)
|
||||
{
|
||||
CppUnit::TextUi::TestRunner runner;
|
||||
auto ®istry = CppUnit::TestFactoryRegistry::getRegistry();
|
||||
runner.addTest(registry.makeTest());
|
||||
return runner.run() ? EXIT_SUCCESS : EXIT_FAILURE;
|
||||
}
|
@@ -26,7 +26,7 @@ Song::Free()
|
||||
}
|
||||
|
||||
static void
|
||||
check_descending_priority(const struct queue *queue,
|
||||
check_descending_priority(const Queue *queue,
|
||||
unsigned start_order)
|
||||
{
|
||||
assert(start_order < queue->GetLength());
|
||||
@@ -55,7 +55,7 @@ QueuePriorityTest::TestPriority()
|
||||
{
|
||||
static Song songs[16];
|
||||
|
||||
struct queue queue(32);
|
||||
Queue queue(32);
|
||||
|
||||
for (unsigned i = 0; i < ARRAY_SIZE(songs); ++i)
|
||||
queue.Append(&songs[i], 0);
|
||||
|
@@ -29,6 +29,10 @@ public:
|
||||
"jpg"));
|
||||
CPPUNIT_ASSERT_EQUAL(0, strcmp(uri_get_suffix("/foo.png/bar.jpg"),
|
||||
"jpg"));
|
||||
CPPUNIT_ASSERT_EQUAL((const char *)nullptr,
|
||||
uri_get_suffix(".jpg"));
|
||||
CPPUNIT_ASSERT_EQUAL((const char *)nullptr,
|
||||
uri_get_suffix("/foo/.jpg"));
|
||||
}
|
||||
|
||||
void TestRemoveAuth() {
|
||||
|
Reference in New Issue
Block a user