Compare commits

...

138 Commits

Author SHA1 Message Date
Max Kellermann
a3f3c7ba24 release v0.18.12 2014-07-30 10:30:17 +02:00
Max Kellermann
94efeb2845 decoder/dsdiff: simplify dsdlib_skip() call 2014-07-12 20:51:00 +02:00
Max Kellermann
a73834436f decoder/dsdiff: simplify loop condition, merge branches 2014-07-12 20:46:24 +02:00
Max Kellermann
85f4aeca05 decoder/dsdiff: ignore garbage null byte at end of file
Failure to read another chunk header is not fatal.  Continue to read
metadata.
2014-07-12 20:41:26 +02:00
Max Kellermann
7db84a961a decoder/dsdiff: fix metadata parser bug (uninitialized variables) 2014-07-12 20:41:26 +02:00
Max Kellermann
a960e2ef48 decoder/faad: estimate song duration for remote files
Previously, MPD tried to slurp the whole song file, count the number
of frames and calculate the song duration from that.  That however is
extremely expensive for remote files, and will delay playback for a
long time.  Workaround: check only the first 128 frames and try to
extrapolate from here.  Fixes Mantis ticket 0004035.
2014-07-12 00:37:00 +02:00
Max Kellermann
4fe272a7fb DecoderBuffer: add method _available() 2014-07-12 00:35:32 +02:00
Max Kellermann
a7d9f248ea DecoderBuffer: add method _get_stream() 2014-07-12 00:23:22 +02:00
Max Kellermann
06aa689383 decoder/faad: bail out early if sample rate is invalid 2014-07-12 00:23:11 +02:00
Max Kellermann
835b0c44cd decoder/faad: use adts_check_frame() in faad_song_duration()
Eliminate more duplicate code.
2014-07-12 00:18:02 +02:00
Max Kellermann
54b6f8a4ae decoder/faad: test "seekable" after ADTS frame check
Don't bother to check for ADIF just because the stream is not
seekable.
2014-07-12 00:17:51 +02:00
Max Kellermann
18787ebe8f decoder/faad: move code to faad_decoder_new()
Merge some duplicate code.
2014-07-12 00:17:43 +02:00
Max Kellermann
47e8fcf37e decoder/faad: remove unnecessary read
Eliminate some overhead when the caller doesn't need the buffer.
2014-07-12 00:17:30 +02:00
Max Kellermann
5958b78459 DecoderBuffer: add "pure" attributes 2014-07-12 00:16:41 +02:00
Max Kellermann
9d9697b366 DecoderBuffer: add method _clear() 2014-07-12 00:15:35 +02:00
Max Kellermann
6585e18571 decoder/faad: check sample_rate, not frames_per_second
Checking the integer is faster, easier and more reliable.
2014-07-11 23:12:08 +02:00
Max Kellermann
6f1b4292f0 decoder/faad: make variables more local 2014-07-11 22:52:31 +02:00
Max Kellermann
ef9ef03b1f decoder/faad: use MAX_CHANNELS
.. instead of declaring a new constant.
2014-07-11 22:40:28 +02:00
Max Kellermann
ecb67a1ed1 decoder/sndfile: use decoder_read_full()
Replaces the loop in sndfile_vio_read(), eliminating duplicate and
fragile code.
2014-07-11 21:18:44 +02:00
Max Kellermann
0ef843f138 decoder/sndfile: use decoder_read()
.. instead of InputStream::LockRead(). The former is cancellable.
2014-07-11 21:18:44 +02:00
Max Kellermann
eb79d83051 decoder/sndfile: log seek errors 2014-07-11 21:18:44 +02:00
Max Kellermann
ca1a11493d decoder/audiofile: log seek errors 2014-07-11 21:18:44 +02:00
Max Kellermann
69bb086ba5 decoder/audiofile: fix typo in comment 2014-07-11 21:18:44 +02:00
Max Kellermann
11a5ee821b PlaylistEdit: postpone UpdateQueuedSong() when adding multiple songs
Implement a "bulk" edit mode that postpones both UpdateQueuedSong()
and OnModified().  This way, the playlist version gets incremented
only once.  More importantly: when adding multiple songs to a queue
that consists of only one song, the first song that got added will
always be played next.  By postponing this choice, all newly added
songs get a chance to become the next song.  Fixes the second (and
last) part of Mantis ticket 0004005.
2014-07-11 20:22:35 +02:00
Max Kellermann
a8a85143f6 QueueCommands: make "result" more local 2014-07-11 20:22:35 +02:00
Max Kellermann
e2cc328eef Playlist: randomize next song when enabling "random" mode while not playing
Don't restore the current song after shufflung when MPD is stopped
(but still remembers the current song internally).  Fixes the first
part of Mantis ticket 0004005.
2014-07-11 19:41:39 +02:00
Max Kellermann
344d10a8e3 PlaylistControl: update code comment 2014-07-11 19:29:25 +02:00
Joff
09384df32c decoder/dsd: use decoder_read_full() where appropriate
Addresses Mantis ticket 0004015.

[mk: use decoder_read_full() only when needed, and a few formal
changes]
2014-07-09 19:18:36 +02:00
Max Kellermann
20538516b9 decoder/audiofile: use decoder_read_full()
Works around WAV stream playback bug, because libaudiofile does not
like partial reads (Mantis 0004028).
2014-07-09 19:05:20 +02:00
Max Kellermann
0759421d11 DecoderAPI: add function decoder_read_full()
Move code from the "mad" plugin.
2014-07-09 19:03:58 +02:00
Max Kellermann
bf7417981f DecoderAPI: add function decoder_skip()
Move code from the "mad" plugin.
2014-07-09 19:03:31 +02:00
Max Kellermann
dba41e2e4a test: merge duplicate code to FakeDecoderAPI.cxx 2014-07-09 19:01:38 +02:00
Max Kellermann
bc6472bb9e decoder/audiofile: use decoder_read()
.. instead of InputStream::LockRead(). The former is cancellable.
2014-07-09 18:57:50 +02:00
Gustavo Zacarias
d4bd947bf5 playlist/PlsPlaylistPlugin: fix build failure due to missing stdio.h include
Signed-off-by: Gustavo Zacarias <gustavo@zacarias.com.ar>
2014-07-09 17:41:31 +02:00
Gustavo Zacarias
d8e8eabf60 output/HttpdClient: fix build failure due to missing stdio.h include
Signed-off-by: Gustavo Zacarias <gustavo@zacarias.com.ar>
2014-07-09 17:41:31 +02:00
Gustavo Zacarias
a70443af31 decoder/OpusDecoderPlugin: fix build failure due to missing stdio.h include
Signed-off-by: Gustavo Zacarias <gustavo@zacarias.com.ar>
2014-07-09 17:41:31 +02:00
Gustavo Zacarias
3f221e2edb decoder/AudiofileDecoderPlugin: fix build failure due to missing stdio.h include
Signed-off-by: Gustavo Zacarias <gustavo@zacarias.com.ar>
2014-07-09 17:41:31 +02:00
Max Kellermann
848ed14788 db/proxy: fall back to recursive walk on old libmpdclient/MPD
Error message was 'too few arguments for "find"' because the "base"
constraint was not supported, and no other constraints remained.
2014-06-23 09:18:11 +02:00
Max Kellermann
4c8a5dfb05 db/proxy: use mpd_song_get_{start,end}() only with libmpdclient >= 2.3 2014-06-23 09:17:35 +02:00
Max Kellermann
4f61ba766d configure.ac: prepare for 0.18.12 2014-06-23 09:14:35 +02:00
Max Kellermann
8bfdb4ed0c release v0.18.11 2014-05-12 18:20:26 +02:00
Max Kellermann
70bd35abe2 decoder/OggUtil: allow skipping up to 32 kB after seek
Fixes missing song length on high-latency Opus files.

According to tests with 320 kbit/s opus files with 60ms packets, we
need to skip up to 29 kB.
2014-04-29 11:56:05 +02:00
Max Kellermann
0efb67b51e DeferredMonitor: fix race condition when using GLib event loop
Turns out the lock-free code using atomics was not thread-safe.  The
given callback could be invoked by GLib before the source_id attribute
was assigned.  This commit changes the DeferredMonitor class to use a
Mutex to block the event loop until source_id is assigned.  This bug
does not exist in the 0.19 branch because it does not use the GLib
main loop anymore.
2014-04-26 22:11:23 +02:00
Max Kellermann
54ebf2a699 configure.ac: prepare for 0.18.11 2014-04-26 22:08:08 +02:00
Max Kellermann
d0119548c1 release v0.18.10 2014-04-10 13:36:38 +02:00
Marcello Desantis
95ac6071b9 decoder/sndfile: work around libsndfile bug on partial read 2014-04-09 23:58:56 +02:00
Weng Xuetian
3a4e667078 PlaylistEdit: don't interrupt playback when current song gets deleted 2014-04-09 23:10:14 +02:00
Max Kellermann
ce18c36ed9 decoder/ffmpeg: handle unknown stream start time 2014-03-18 09:16:09 +01:00
Max Kellermann
8e39cf62e7 decoder/ffmpeg: pass AVSEEK_FLAG_ANY to av_seek_frame()
This corrects a major mistake from commit 724a59aa - there was one
small thing that commit was supposed to do, and it failed.
AV_TIME_BASE is not a seek flag.
2014-03-18 09:10:36 +01:00
Max Kellermann
a9e351e00d decoder/gme: fix memory leak in container_scan() 2014-03-06 13:12:39 +01:00
Max Kellermann
d65841a2db configure.ac: prepare for 0.18.10 2014-03-06 13:08:30 +01:00
Max Kellermann
2784d65618 release v0.18.9 2014-03-02 11:25:01 +01:00
Max Kellermann
47ea69233b output/alsa: remove the obsolete Raspberry Pi workaround
Has been superseded by the previous commit.
2014-03-02 11:22:04 +01:00
Max Kellermann
a884e37de1 output/alsa: call snd_pcm_prepare() after snd_pcm_drop()
Don't wait for an optimistic write to fail.  This is an improved
workaround for the infamous Raspberry Pi bug (see commit af991765).
It works much better and comes without the negative side effects.  The
old workaround is now obsolete.
2014-03-02 11:12:25 +01:00
Max Kellermann
0102a8665a event/SignalMonitor: fix build failure due to missing signal.h include 2014-03-02 10:21:31 +01:00
Max Kellermann
d34ae0850c AllCommands: "findadd" requires the "add" permission 2014-02-27 23:08:22 +01:00
Max Kellermann
6526de024a output/pulse: remove bogus g_free() call 2014-02-24 21:23:49 +01:00
Max Kellermann
5e1e92626c event/SignalMonitor: unblock signals after fork
Fixes hanging child process in the "pipe" output plugin.
2014-02-18 19:13:50 +01:00
Max Kellermann
7fee85c80a configure.ac: fix linker failure when libvorbis/libogg are static
Link libvorbisfile first, followed to libvorbis and finally libogg.
This order is necessary because libvorbisfile depends on libvorbis.
2014-02-18 18:39:19 +01:00
Max Kellermann
5d87a274a5 configure.ac: link the Vorbis encoder with libogg
Fixes another linker failure.  Similar to commit ea406875
2014-02-17 19:42:38 +01:00
Max Kellermann
57e862712a configure.ac: prepare for 0.18.9 2014-02-09 22:58:14 +01:00
Max Kellermann
ddb5390d88 release v0.18.8 2014-02-07 00:06:31 +01:00
Max Kellermann
fce20e514e NEWS: fix 0.18.7 release year 2014-02-07 00:06:31 +01:00
Max Kellermann
af66ed2505 doc/user: document the RoarAudio output plugin 2014-02-06 21:46:29 +01:00
Max Kellermann
ea4068757d configure.ac: link the Vorbis encoder with libvorbis
Since the encoder plugin uses a libvorbis function (and not only
libvorbisenc functions), we need to link with libvorbis explicitly.
2014-02-06 21:32:50 +01:00
Max Kellermann
2b10ecfa37 IcyMetadataParser: more robust tag parser
Allow semicolons and single quotes in the stream title.  This is not
part of any specification, but found in real life.
2014-01-27 10:08:21 +01:00
Max Kellermann
f7eb2b697e test/test_icy_parser: unit test for IcyMetaDataParser.cxx 2014-01-27 09:51:31 +01:00
Max Kellermann
da67260c95 new developer mailing list 2014-01-20 17:20:57 +01:00
Max Kellermann
ab9c9068d4 Queue: rename struct queue to Queue
Works around a build failure on Solaris because annoyingly, Solaris
reserves the name "queue".  This rename was pending anyway.
2014-01-20 08:57:46 +01:00
Max Kellermann
6b4d7d7315 Queue: make the constructor "explicit" 2014-01-20 08:57:41 +01:00
Max Kellermann
313d1d5d83 decoder/ffmpeg: support libav v10_alpha1 2014-01-15 11:33:18 +01:00
Max Kellermann
b7d6133593 decoder/ffmpeg: include cleanup 2014-01-15 11:31:51 +01:00
Max Kellermann
5b6bb114ad decoder/ffmpeg: check for av_samples_get_buffer_size() errors
Fixes potential nullptr dereference.
2014-01-15 11:25:58 +01:00
Max Kellermann
56f082c9d4 util/PeakBuffer: fix nullptr dereference when peak_size==0 2014-01-15 11:24:29 +01:00
Max Kellermann
a1b798e555 SongFilter, TagConfig: cast TAG_NUM_OF_ITEM_TYPES to integer
Fixes clang warning.
2014-01-15 11:23:41 +01:00
Max Kellermann
c91e08fbfd OutputAPI: fix typo in include guard 2014-01-15 11:22:59 +01:00
Max Kellermann
f882434547 configure.ac: prepare for 0.18.8 2014-01-15 11:22:06 +01:00
Max Kellermann
05ad335ae9 release v0.18.7 2014-01-13 11:39:27 +01:00
Max Kellermann
7faeb2ff2b configure.ac: reject libmpcdec SV7 in configure script
Look for symbol "mpc_demux_init" which does not exist in SV7.  This
avoids build failures when SV7 was found by configure.ac.
2014-01-11 21:02:12 +01:00
Max Kellermann
fdd76b3461 decoder/faad: fix memory leak 2014-01-08 22:11:00 +01:00
Max Kellermann
e490e5d0ab playlist/pls: don't free stack buffer 2014-01-08 19:50:44 +01:00
Max Kellermann
3a05c421e0 doc/user: fix typo 2014-01-07 18:06:58 +01:00
Max Kellermann
afc70c120e util/UriUtil: uri_get_suffix() fails if name begins with dot
A file called ".jpg" is not a JPEG file with an empty name; it is
merely a hidden file.
2013-12-29 17:40:51 +01:00
Max Kellermann
d7f80eab68 configure.ac: improved check for libyajl 1.0
If we have libyajl 2.0.1 (without a pkg-config file), our configure.ac
would assume this is the libyajl 1.0 API, because the function
yajl_alloc() exists in both.  This commit changes the library check to
the function yajl_parse_complete() which was removed in the 2.0 API.
This fixes build failure with libyajl 2.0.1.
2013-12-29 14:12:33 +01:00
Max Kellermann
e30b356eb0 daemon: no initgroups() when already running as the configured user
We can assume that initgroups() would be a no-op in that case, however
initgroups() is not allowed for unprivileged users anyway.
2013-12-29 13:59:05 +01:00
Max Kellermann
09a0803116 Daemon: fix typo in comment 2013-12-29 13:59:05 +01:00
Max Kellermann
20ffedc745 Daemon: simplify nested "if" 2013-12-29 13:57:12 +01:00
Max Kellermann
0b1ad27ba8 Daemon: fix typo in cast 2013-12-29 13:47:29 +01:00
Max Kellermann
6a1b2f0387 configure.ac: prepare for 0.18.7 2013-12-29 10:40:59 +01:00
Max Kellermann
fb34519b96 release v0.18.6 2013-12-24 12:01:01 +01:00
Max Kellermann
91fed47648 PlayerThread: log the last song that was played 2013-12-24 11:58:10 +01:00
Max Kellermann
c05691b546 OutputControl: update both ReplayGainFilters
The "mode" of the second ReplayGainFilter was never set, and thus
replay gain was never applied to the new song during cross-fade.
2013-12-24 11:53:21 +01:00
Steven O'Brien
6b3b8c6f2e fix FfmpegDecoderPlugin to use relative timestamps 2013-12-20 22:28:33 +01:00
Max Kellermann
a191db84f2 util/Error: add missing <algorithm> include
For std::move().
2013-12-19 10:58:20 +01:00
Michal Smucr
e4d69f38b0 riff: recognize upper-case "ID3" chunk name
Some tagging libraries (eg. TagLib) produce that variant.
2013-12-19 09:35:54 +01:00
Max Kellermann
97fc001180 input/cdio: fix typo in #include path
Broken by commit 3b0fea5f
2013-12-17 08:58:00 +01:00
Max Kellermann
42a09ff17a mixer/alsa: fix deadlock
This deadlock was a regression by commit 8e38b4f8.  Since we currently
can't resolve this, let's revert the commit, and add a GLib specific
workaround for the build failure.
2013-12-15 19:07:25 +01:00
Max Kellermann
c170fed6f9 .gitignore: ignore "test-driver"
File generated by automake version 1.14.
2013-12-14 12:33:20 +01:00
Max Kellermann
8e38b4f83c mixer/alsa: use BlockingCall() instead of EventLoop::AddCall()
This is safer, and works without epoll().  Fixes a build failure with
uClibc, which does not support epoll().
2013-12-13 14:35:36 +01:00
Max Kellermann
db4ae19246 doc/mpd.conf.5: remove redundant documentation
The real and detailed documentation is in the user manual.
2013-12-11 21:04:28 +01:00
Max Kellermann
82a89c6bfe doc/user: document the "ao" output
Move from doc/mpd.conf.5.
2013-12-11 21:03:11 +01:00
Max Kellermann
166c70cab3 doc/user: document the "fifo" output
Move from doc/mpd.conf.5.
2013-12-11 20:58:06 +01:00
Max Kellermann
de78fe38c8 doc/user: document shout option "protocol" 2013-12-11 20:54:42 +01:00
mobidyc
96fa69ff6b SongUpdate: accept files without metadata
If the file was recognized by a decoder plugin, accept it - don't
require metadata.
2013-12-10 19:34:35 +01:00
Max Kellermann
39d94bd3ea TagFile: add return value API documentation 2013-12-10 19:32:26 +01:00
Lukas Stabe
695ca29274 output/osx: fix build failure 2013-12-10 19:19:31 +01:00
Max Kellermann
02e8da6c98 NEWS: add openal line 2013-12-10 19:19:27 +01:00
Lukas Stabe
0ea5f4ac3a output/openal: check __APPLE__ instead of HAVE_OSX
On OSX, the configure-flag --enable-osx is used to enable the
unsupported osx output. It sets the HAVE_OSX preprocessor define.

src/output/OpenALOutputPlugin.cxx uses this define to determine wether
it is building on OSX, and imports different headers (which have
nothing to do with the osx output) depending on wether or not it is
set.
2013-12-02 13:08:52 +01:00
Max Kellermann
47c50c079d decoder/ffmpeg: use IgnoreError instead of local Error instance 2013-11-28 00:05:26 +01:00
Gaetan Bisson
3b0fea5fae input/cdio_paranoia: support libcdio-paranoia 0.90 2013-11-27 08:25:17 +01:00
Max Kellermann
443516cdda configure.ac: prepare for 0.18.6 2013-11-27 08:25:17 +01:00
Max Kellermann
57e0cc5442 release v0.18.5 2013-11-23 18:30:12 +01:00
Max Kellermann
73f45d87d5 decoder/{dsf,dsdiff}: eliminate useless assignments 2013-11-23 18:30:12 +01:00
Max Kellermann
ae88ba986e archive/iso9660: eliminate useless assignments 2013-11-23 18:30:12 +01:00
Max Kellermann
d6247902ec input/curl: work around stream resume bug (fixed in libcurl 7.32.0) 2013-11-23 12:13:41 +01:00
Max Kellermann
a566c28a49 input/curl: add global variable "curl_version" 2013-11-23 12:08:46 +01:00
Max Kellermann
2eddb63a83 input/curl: dump version number 2013-11-23 12:02:39 +01:00
Max Kellermann
fc7d5b055d PcmResampleLibsamplerate: clip 24 bit data
Using pcm_resample_lsr_32() for 24 bit samples works, but may cause 24
bit overflows.  This commit makes 24 bit a special case with explicit
clipping.
2013-11-22 23:27:56 +01:00
Max Kellermann
87c8953e8e PcmResample: un-inline Resample24() 2013-11-22 23:24:40 +01:00
Max Kellermann
45d27a52f1 PcmUtils: add function PcmClampN() 2013-11-22 23:24:40 +01:00
Max Kellermann
d22acc59c9 db/proxy: implement method GetUpdateStamp() 2013-11-22 00:45:27 +01:00
Max Kellermann
c064e8d62f DatabasePlugin: add method GetUpdateStamp()
Refactor SimpleDatabase::GetLastModified() to be generic for all
plugins.  Remove the SimpleDatabase assumption from db_stats_print(),
allowing it to be implemented by all database plugins.
2013-11-22 00:35:29 +01:00
Max Kellermann
099a2cb586 Stats: print db statistics only if db is available
Fixes crash on "stats" in certain configurations.
2013-11-22 00:27:37 +01:00
Max Kellermann
042fe2a9d0 Stats: print more unsigned integers 2013-11-22 00:23:27 +01:00
Max Kellermann
ff1c1107f3 Stats: auto-reload statistics with proxy plugin 2013-11-22 00:19:28 +01:00
Max Kellermann
41a3fd0fd0 Stats: use struct DatabaseStats
Eliminate redundant declaration.
2013-11-22 00:12:12 +01:00
Max Kellermann
b9169a5670 Stats: move the GTimer variable out of struct stats 2013-11-22 00:10:53 +01:00
Max Kellermann
2ecd5fa28d Stats: don't export the global variable "stats"
Unused outside of Stats.cxx.
2013-11-22 00:08:43 +01:00
Max Kellermann
e719b6cc41 Stats: print db_update as unsigned integer 2013-11-22 00:04:58 +01:00
Max Kellermann
67b8124a1d Mapper: _get_music_directory_utf8() may return nullptr
If no music_directory is configured, return nullptr instead of an
empty string.  This fixes a crash when db_file is configured without
music_directory.
2013-11-22 00:02:17 +01:00
Florian Schlichting
51ec499c89 system/ByteOrder: testing for endianness in a more general way
Fixes build tests on ia64 and mipsel by testing for endianness in a
more general / portable way.
2013-11-21 23:04:49 +01:00
Max Kellermann
3a4df25db2 util/ByteOrder: add comments 2013-11-21 23:04:31 +01:00
Max Kellermann
37cf78ef45 util/ByteOrder: indent the preprocessor directives
Improve readability.
2013-11-21 23:03:28 +01:00
Max Kellermann
727c622659 configure.ac: auto-detect fluidsynth by default
The option "--enable-fluidsynth" was documented to be "auto" by
default, when it was really "no".
2013-11-21 22:50:58 +01:00
Max Kellermann
74a778e65f configure.ac: prepare for 0.18.5 2013-11-18 20:03:30 +01:00
Denis Krjuchkov
1fc0c9fe8a NEWS: add version 0.18.5 change log 2013-11-18 16:40:00 +06:00
Denis Krjuchkov
740d8ec5ab system/fd_util.h: avoid symbol conflict with statically linked libmpdclient 2013-11-18 16:35:22 +06:00
Denis Krjuchkov
0bcc477d46 decoder/OggFind.cxx: include stdio.h for SEEK_END definition 2013-11-18 16:28:39 +06:00
95 changed files with 1507 additions and 767 deletions
.gitignoreMakefile.amNEWSconfigure.ac
doc
src
test

1
.gitignore vendored

@@ -31,6 +31,7 @@ libtool
ltmain.sh
missing
mkinstalldirs
/test-driver
mpd
mpd.service
stamp-h1

@@ -181,6 +181,7 @@ src_mpd_SOURCES = \
src/PlaylistInfo.hxx \
src/PlaylistDatabase.cxx src/PlaylistDatabase.hxx \
src/PlaylistUpdate.cxx \
src/BulkEdit.hxx \
src/IdTable.hxx \
src/Queue.cxx src/Queue.hxx \
src/QueuePrint.cxx src/QueuePrint.hxx \
@@ -1061,6 +1062,7 @@ C_TESTS = \
test/test_util \
test/test_byte_reverse \
test/test_mixramp \
test/test_icy_parser \
test/test_pcm \
test/test_queue_priority
@@ -1217,6 +1219,7 @@ test_dump_playlist_LDADD = \
libpcm.a \
$(GLIB_LIBS)
test_dump_playlist_SOURCES = test/dump_playlist.cxx \
test/FakeDecoderAPI.cxx \
$(DECODER_SRC) \
src/Log.cxx \
src/IOThread.cxx \
@@ -1270,6 +1273,7 @@ test_read_tags_LDADD = \
libutil.a \
$(GLIB_LIBS)
test_read_tags_SOURCES = test/read_tags.cxx \
test/FakeDecoderAPI.cxx \
src/Log.cxx \
src/IOThread.cxx \
src/ReplayGainInfo.cxx \
@@ -1496,6 +1500,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 \

91
NEWS

@@ -1,3 +1,94 @@
ver 0.18.12 (2014/07/30)
* database
- proxy: fix build failure with libmpdclient 2.2
- proxy: fix add/search and other commands with libmpdclient < 2.9
* decoder
- audiofile: improve responsiveness
- audiofile: fix WAV stream playback
- dsdiff, dsf: fix stream playback
- dsdiff: fix metadata parser bug (uninitialized variables)
- faad: estimate song duration for remote files
- sndfile: improve responsiveness
* randomize next song when enabling "random" mode while not playing
* randomize next song when adding to single-song queue
ver 0.18.11 (2014/05/12)
* decoder
- opus: fix missing song length on high-latency files
* fix race condition when using GLib event loop (non-Linux)
ver 0.18.10 (2014/04/10)
* decoder
- ffmpeg: fix seeking bug
- ffmpeg: handle unknown stream start time
- gme: fix memory leak
- sndfile: work around libsndfile bug on partial read
* don't interrupt playback when current song gets deleted
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

@@ -1,6 +1,6 @@
AC_PREREQ(2.60)
AC_INIT(mpd, 0.18.4, musicpd-dev-team@lists.sourceforge.net)
AC_INIT(mpd, 0.18.12, 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>

@@ -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

@@ -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>

41
src/BulkEdit.hxx Normal file

@@ -0,0 +1,41 @@
/*
* Copyright (C) 2003-2014 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_BULK_EDIT_HXX
#define MPD_BULK_EDIT_HXX
#include "Partition.hxx"
/**
* Begin a "bulk edit" and commit it automatically.
*/
class ScopeBulkEdit {
Partition &partition;
public:
ScopeBulkEdit(Partition &_partition):partition(_partition) {
partition.playlist.BeginBulk();
}
~ScopeBulkEdit() {
partition.playlist.CommitBulk(partition.pc);
}
};
#endif

@@ -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 {

@@ -30,6 +30,18 @@ DatabaseSelection::DatabaseSelection(const char *_uri, bool _recursive,
uri = filter->GetBase();
}
bool
DatabaseSelection::IsEmpty() const
{
return uri.empty() && (filter == nullptr || filter->IsEmpty());
}
bool
DatabaseSelection::HasOtherThanBase() const
{
return filter != nullptr && filter->HasOtherThanBase();
}
bool
DatabaseSelection::Match(const Song &song) const
{

@@ -44,6 +44,15 @@ struct DatabaseSelection {
DatabaseSelection(const char *_uri, bool _recursive,
const SongFilter *_filter=nullptr);
gcc_pure
bool IsEmpty() const;
/**
* Does this selection contain constraints other than "base"?
*/
gcc_pure
bool HasOtherThanBase() const;
gcc_pure
bool Match(const Song &song) const;
};

@@ -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

@@ -292,6 +292,40 @@ decoder_read(Decoder *decoder,
return nbytes;
}
bool
decoder_read_full(Decoder *decoder, InputStream &is,
void *_buffer, size_t size)
{
uint8_t *buffer = (uint8_t *)_buffer;
while (size > 0) {
size_t nbytes = decoder_read(decoder, is, buffer, size);
if (nbytes == 0)
return false;
buffer += nbytes;
size -= nbytes;
}
return true;
}
bool
decoder_skip(Decoder *decoder, InputStream &is, size_t size)
{
while (size > 0) {
char buffer[1024];
size_t nbytes = decoder_read(decoder, is, buffer,
std::min(sizeof(buffer), size));
if (nbytes == 0)
return false;
size -= nbytes;
}
return true;
}
void
decoder_timestamp(Decoder &decoder, double t)
{

@@ -112,6 +112,25 @@ decoder_read(Decoder &decoder, InputStream &is,
return decoder_read(&decoder, is, buffer, length);
}
/**
* Blocking read from the input stream. Attempts to fill the buffer
* completely; there is no partial result.
*
* @return true on success, false on error or command or not enough
* data
*/
bool
decoder_read_full(Decoder *decoder, InputStream &is,
void *buffer, size_t size);
/**
* Skip data on the #InputStream.
*
* @return true on success, false on error or command
*/
bool
decoder_skip(Decoder *decoder, InputStream &is, size_t size);
/**
* Sets the time stamp for the next data chunk [seconds]. The MPD
* core automatically counts it up, and a decoder plugin only needs to

@@ -70,6 +70,12 @@ decoder_buffer_free(DecoderBuffer *buffer)
g_free(buffer);
}
const InputStream &
decoder_buffer_get_stream(const DecoderBuffer *buffer)
{
return *buffer->is;
}
bool
decoder_buffer_is_empty(const DecoderBuffer *buffer)
{
@@ -82,6 +88,12 @@ decoder_buffer_is_full(const DecoderBuffer *buffer)
return buffer->consumed == 0 && buffer->length == buffer->size;
}
void
decoder_buffer_clear(DecoderBuffer *buffer)
{
buffer->length = buffer->consumed = 0;
}
static void
decoder_buffer_shift(DecoderBuffer *buffer)
{
@@ -118,6 +130,12 @@ decoder_buffer_fill(DecoderBuffer *buffer)
return true;
}
size_t
decoder_buffer_available(const DecoderBuffer *buffer)
{
return buffer->length - buffer->consumed;;
}
const void *
decoder_buffer_read(const DecoderBuffer *buffer, size_t *length_r)
{

@@ -20,6 +20,8 @@
#ifndef MPD_DECODER_BUFFER_HXX
#define MPD_DECODER_BUFFER_HXX
#include "Compiler.h"
#include <stddef.h>
/**
@@ -50,12 +52,21 @@ decoder_buffer_new(Decoder *decoder, InputStream &is,
void
decoder_buffer_free(DecoderBuffer *buffer);
gcc_pure
const InputStream &
decoder_buffer_get_stream(const DecoderBuffer *buffer);
gcc_pure
bool
decoder_buffer_is_empty(const DecoderBuffer *buffer);
gcc_pure
bool
decoder_buffer_is_full(const DecoderBuffer *buffer);
void
decoder_buffer_clear(DecoderBuffer *buffer);
/**
* Read data from the input_stream and append it to the buffer.
*
@@ -66,6 +77,13 @@ decoder_buffer_is_full(const DecoderBuffer *buffer);
bool
decoder_buffer_fill(DecoderBuffer *buffer);
/**
* How many bytes are stored in the buffer?
*/
gcc_pure
size_t
decoder_buffer_available(const DecoderBuffer *buffer);
/**
* Reads data from the buffer. This data is not yet consumed, you
* have to call decoder_buffer_consume() to do that. The returned

@@ -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();

@@ -103,6 +103,12 @@ playlist::UpdateQueuedSong(PlayerControl &pc, const Song *prev)
if (!playing)
return;
if (prev == nullptr && bulk_edit)
/* postponed until CommitBulk() to avoid always
queueing the first song that is being added (in
random mode) */
return;
assert(!queue.IsEmpty());
assert((queued < 0) == (prev == nullptr));
@@ -294,7 +300,9 @@ playlist::SetRandom(PlayerControl &pc, bool status)
if (queue.random) {
/* shuffle the queue order, but preserve current */
const int current_position = GetCurrentPosition();
const int current_position = playing
? GetCurrentPosition()
: -1;
queue.ShuffleOrder();

@@ -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
@@ -45,6 +45,18 @@ struct playlist {
*/
bool stop_on_error;
/**
* If true, then a bulk edit has been initiated by
* BeginBulk(), and UpdateQueuedSong() and OnModified() will
* be postponed until CommitBulk()
*/
bool bulk_edit;
/**
* Has the queue been modified during bulk edit mode?
*/
bool bulk_modified;
/**
* Number of errors since playback was started. If this
* number exceeds the length of the playlist, MPD gives up,
@@ -69,7 +81,9 @@ struct playlist {
int queued;
playlist(unsigned max_length)
:queue(max_length), playing(false), current(-1), queued(-1) {
:queue(max_length), playing(false),
bulk_edit(false),
current(-1), queued(-1) {
}
~playlist() {
@@ -126,6 +140,9 @@ protected:
void UpdateQueuedSong(PlayerControl &pc, const Song *prev);
public:
void BeginBulk();
void CommitBulk(PlayerControl &pc);
void Clear(PlayerControl &pc);
/**

@@ -153,7 +153,7 @@ playlist::PlayNext(PlayerControl &pc)
queue.ShuffleOrder();
/* note that current and queued are
now invalid, but playlist_play_order() will
now invalid, but PlayOrder() will
discard them anyway */
}

@@ -40,6 +40,12 @@
void
playlist::OnModified()
{
if (bulk_edit) {
/* postponed to CommitBulk() */
bulk_modified = true;
return;
}
queue.IncrementVersion();
idle_add(IDLE_PLAYLIST);
@@ -56,6 +62,35 @@ playlist::Clear(PlayerControl &pc)
OnModified();
}
void
playlist::BeginBulk()
{
assert(!bulk_edit);
bulk_edit = true;
bulk_modified = false;
}
void
playlist::CommitBulk(PlayerControl &pc)
{
assert(bulk_edit);
bulk_edit = false;
if (!bulk_modified)
return;
if (queued < 0)
/* if no song was queued, UpdateQueuedSong() is being
ignored in "bulk" edit mode; now that we have
shuffled all new songs, we can pick a random one
(instead of always picking the first one that was
added) */
UpdateQueuedSong(pc, nullptr);
OnModified();
}
PlaylistResult
playlist::AppendFile(PlayerControl &pc,
const char *path_utf8, unsigned *added_id)
@@ -234,12 +269,8 @@ playlist::DeleteInternal(PlayerControl &pc,
if (playing && current == (int)songOrder) {
const bool paused = pc.GetState() == PlayerState::PAUSE;
/* the current song is going to be deleted: stop the player */
pc.Stop();
playing = false;
/* see which song is going to be played instead */
/* the current song is going to be deleted: see which
song is going to be played instead */
current = queue.GetNextOrder(current);
if (current == (int)songOrder)
@@ -248,10 +279,12 @@ playlist::DeleteInternal(PlayerControl &pc,
if (current >= 0 && !paused)
/* play the song after the deleted one */
PlayOrder(pc, current);
else
/* no songs left to play, stop playback
completely */
Stop(pc);
else {
/* stop the player */
pc.Stop();
playing = false;
}
*queued_p = nullptr;
} else if (current == (int)songOrder)

@@ -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;
@@ -203,6 +203,16 @@ SongFilter::Match(const Song &song) const
return true;
}
bool
SongFilter::HasOtherThanBase() const
{
for (const auto &i : items)
if (i.GetTag() != LOCATE_TAG_BASE_TYPE)
return true;
return false;
}
std::string
SongFilter::GetBase() const
{

@@ -109,6 +109,11 @@ public:
return items;
}
gcc_pure
bool IsEmpty() const {
return items.empty();
}
/**
* Is there at least one item with "fold case" enabled?
*/
@@ -121,6 +126,12 @@ public:
return false;
}
/**
* Does this filter contain constraints other than "base"?
*/
gcc_pure
bool HasOtherThanBase() const;
/**
* Returns the "base" specification (if there is one) or an
* empty string.

@@ -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 },

@@ -30,6 +30,7 @@
#include "util/Error.hxx"
#include "SongFilter.hxx"
#include "protocol/Result.hxx"
#include "BulkEdit.hxx"
#include <assert.h>
#include <string.h>
@@ -92,6 +93,8 @@ handle_match_add(Client &client, int argc, char *argv[], bool fold_case)
return CommandResult::ERROR;
}
const ScopeBulkEdit bulk_edit(client.partition);
const DatabaseSelection selection("", true, &filter);
Error error;
return AddFromDatabase(client.partition, selection, error)

@@ -26,6 +26,7 @@
#include "PlaylistFile.hxx"
#include "PlaylistVector.hxx"
#include "PlaylistQueue.hxx"
#include "BulkEdit.hxx"
#include "TimePrint.hxx"
#include "Client.hxx"
#include "protocol/ArgParser.hxx"
@@ -67,6 +68,8 @@ handle_load(Client &client, int argc, char *argv[])
} else if (!check_range(client, &start_index, &end_index, argv[2]))
return CommandResult::ERROR;
const ScopeBulkEdit bulk_edit(client.partition);
const PlaylistResult result =
playlist_open_into_queue(argv[1],
start_index, end_index,

@@ -28,6 +28,7 @@
#include "ClientFile.hxx"
#include "Client.hxx"
#include "Partition.hxx"
#include "BulkEdit.hxx"
#include "protocol/ArgParser.hxx"
#include "protocol/Result.hxx"
#include "ls.hxx"
@@ -43,7 +44,6 @@ CommandResult
handle_add(Client &client, gcc_unused int argc, char *argv[])
{
char *uri = argv[1];
PlaylistResult result;
if (memcmp(uri, "file:///", 8) == 0) {
const char *path_utf8 = uri + 7;
@@ -59,7 +59,7 @@ handle_add(Client &client, gcc_unused int argc, char *argv[])
if (!client_allow_file(client, path_fs, error))
return print_error(client, error);
result = client.partition.AppendFile(path_utf8);
auto result = client.partition.AppendFile(path_utf8);
return print_playlist_result(client, result);
}
@@ -70,10 +70,12 @@ handle_add(Client &client, gcc_unused int argc, char *argv[])
return CommandResult::ERROR;
}
result = client.partition.AppendURI(uri);
auto result = client.partition.AppendURI(uri);
return print_playlist_result(client, result);
}
const ScopeBulkEdit bulk_edit(client.partition);
const DatabaseSelection selection(uri, true);
Error error;
return AddFromDatabase(client.partition, selection, error)

@@ -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 &param,
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 &param, Error &error);
@@ -237,6 +244,7 @@ ProxyDatabase::Open(Error &error)
return false;
root = Directory::NewRoot();
update_stamp = 0;
return true;
}
@@ -390,8 +398,13 @@ Convert(const struct mpd_song *song)
Song *s = Song::NewDetached(mpd_song_get_uri(song));
s->mtime = mpd_song_get_last_modified(song);
#if LIBMPDCLIENT_CHECK_VERSION(2,3,0)
s->start_ms = mpd_song_get_start(song) * 1000;
s->end_ms = mpd_song_get_end(song) * 1000;
#else
s->start_ms = s->end_ms = 0;
#endif
TagBuilder tag;
tag.SetTime(mpd_song_get_duration(song));
@@ -553,6 +566,23 @@ SearchSongs(struct mpd_connection *connection,
return result && CheckError(connection, error);
}
/**
* Check whether we can use the "base" constraint. Requires
* libmpdclient 2.9 and MPD 0.18.
*/
gcc_pure
static bool
ServerSupportsSearchBase(const struct mpd_connection *connection)
{
#if LIBMPDCLIENT_CHECK_VERSION(2,9,0)
return mpd_connection_cmp_server_version(connection, 0, 18, 0) >= 0;
#else
(void)connection;
return false;
#endif
}
bool
ProxyDatabase::Visit(const DatabaseSelection &selection,
VisitDirectory visit_directory,
@@ -564,7 +594,10 @@ ProxyDatabase::Visit(const DatabaseSelection &selection,
if (!const_cast<ProxyDatabase *>(this)->EnsureConnected(error))
return nullptr;
if (!visit_directory && !visit_playlist && selection.recursive)
if (!visit_directory && !visit_playlist && selection.recursive &&
(ServerSupportsSearchBase(connection)
? !selection.IsEmpty()
: selection.HasOtherThanBase()))
/* this optimized code path can only be used under
certain conditions */
return ::SearchSongs(connection, selection, visit_song, error);
@@ -631,6 +664,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 &param,
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 &param, Error &error);

@@ -31,12 +31,27 @@
#include <af_vfs.h>
#include <assert.h>
#include <stdio.h>
/* pick 1020 since its devisible for 8,16,24, and 32-bit audio */
#define CHUNK_SIZE 1020
static constexpr Domain audiofile_domain("audiofile");
struct AudioFileInputStream {
Decoder *const decoder;
InputStream &is;
size_t Read(void *buffer, size_t size) {
/* libaudiofile does not like partial reads at all,
and will abort playback; therefore always force full
reads */
return decoder_read_full(decoder, is, buffer, size)
? size
: 0;
}
};
static int audiofile_get_duration(const char *file)
{
int total_time;
@@ -54,29 +69,26 @@ static int audiofile_get_duration(const char *file)
static ssize_t
audiofile_file_read(AFvirtualfile *vfile, void *data, size_t length)
{
InputStream &is = *(InputStream *)vfile->closure;
AudioFileInputStream &afis = *(AudioFileInputStream *)vfile->closure;
Error error;
size_t nbytes = is.LockRead(data, length, error);
if (nbytes == 0 && error.IsDefined()) {
LogError(error);
return -1;
}
return nbytes;
return afis.Read(data, length);
}
static AFfileoffset
audiofile_file_length(AFvirtualfile *vfile)
{
InputStream &is = *(InputStream *)vfile->closure;
AudioFileInputStream &afis = *(AudioFileInputStream *)vfile->closure;
InputStream &is = afis.is;
return is.GetSize();
}
static AFfileoffset
audiofile_file_tell(AFvirtualfile *vfile)
{
InputStream &is = *(InputStream *)vfile->closure;
AudioFileInputStream &afis = *(AudioFileInputStream *)vfile->closure;
InputStream &is = afis.is;
return is.GetOffset();
}
@@ -91,11 +103,14 @@ audiofile_file_destroy(AFvirtualfile *vfile)
static AFfileoffset
audiofile_file_seek(AFvirtualfile *vfile, AFfileoffset offset, int is_relative)
{
InputStream &is = *(InputStream *)vfile->closure;
AudioFileInputStream &afis = *(AudioFileInputStream *)vfile->closure;
InputStream &is = afis.is;
int whence = (is_relative ? SEEK_CUR : SEEK_SET);
Error error;
if (is.LockSeek(offset, whence, error)) {
LogError(error, "Seek failed");
return is.GetOffset();
} else {
return -1;
@@ -103,10 +118,10 @@ audiofile_file_seek(AFvirtualfile *vfile, AFfileoffset offset, int is_relative)
}
static AFvirtualfile *
setup_virtual_fops(InputStream &stream)
setup_virtual_fops(AudioFileInputStream &afis)
{
AFvirtualfile *vf = new AFvirtualfile();
vf->closure = &stream;
vf->closure = &afis;
vf->write = nullptr;
vf->read = audiofile_file_read;
vf->length = audiofile_file_length;
@@ -173,7 +188,8 @@ audiofile_stream_decode(Decoder &decoder, InputStream &is)
return;
}
vf = setup_virtual_fops(is);
AudioFileInputStream afis{&decoder, is};
vf = setup_virtual_fops(afis);
af_fp = afOpenVirtualFile(vf, "r", nullptr);
if (af_fp == AF_NULL_FILEHANDLE) {

@@ -49,14 +49,6 @@ DsdId::Equals(const char *s) const
return memcmp(value, s, sizeof(value)) == 0;
}
bool
dsdlib_read(Decoder *decoder, InputStream &is,
void *data, size_t length)
{
size_t nbytes = decoder_read(decoder, is, data, length);
return nbytes == length;
}
/**
* Skip the #input_stream to the specified offset.
*/
@@ -149,7 +141,7 @@ dsdlib_tag_id3(InputStream &is,
id3_byte_t *dsdid3data;
dsdid3data = dsdid3;
if (!dsdlib_read(nullptr, is, dsdid3data, count))
if (!decoder_read_full(nullptr, is, dsdid3data, count))
return;
id3_tag = id3_tag_parse(dsdid3data, count);

@@ -58,10 +58,6 @@ public:
}
};
bool
dsdlib_read(Decoder *decoder, InputStream &is,
void *data, size_t length);
bool
dsdlib_skip_to(Decoder *decoder, InputStream &is,
int64_t offset);

@@ -93,14 +93,14 @@ static bool
dsdiff_read_id(Decoder *decoder, InputStream &is,
DsdId *id)
{
return dsdlib_read(decoder, is, id, sizeof(*id));
return decoder_read_full(decoder, is, id, sizeof(*id));
}
static bool
dsdiff_read_chunk_header(Decoder *decoder, InputStream &is,
DsdiffChunkHeader *header)
{
return dsdlib_read(decoder, is, header, sizeof(*header));
return decoder_read_full(decoder, is, header, sizeof(*header));
}
static bool
@@ -112,8 +112,7 @@ dsdiff_read_payload(Decoder *decoder, InputStream &is,
if (size != (uint64_t)length)
return false;
size_t nbytes = decoder_read(decoder, is, data, length);
return nbytes == length;
return decoder_read_full(decoder, is, data, length);
}
/**
@@ -145,8 +144,8 @@ dsdiff_read_prop_snd(Decoder *decoder, InputStream &is,
} else if (header.id.Equals("CHNL")) {
uint16_t channels;
if (header.GetSize() < sizeof(channels) ||
!dsdlib_read(decoder, is,
&channels, sizeof(channels)) ||
!decoder_read_full(decoder, is,
&channels, sizeof(channels)) ||
!dsdlib_skip_to(decoder, is, chunk_end_offset))
return false;
@@ -154,8 +153,8 @@ dsdiff_read_prop_snd(Decoder *decoder, InputStream &is,
} else if (header.id.Equals("CMPR")) {
DsdId type;
if (header.GetSize() < sizeof(type) ||
!dsdlib_read(decoder, is,
&type, sizeof(type)) ||
!decoder_read_full(decoder, is,
&type, sizeof(type)) ||
!dsdlib_skip_to(decoder, is, chunk_end_offset))
return false;
@@ -208,7 +207,7 @@ dsdiff_handle_native_tag(InputStream &is,
struct dsdiff_native_tag metatag;
if (!dsdlib_read(nullptr, is, &metatag, sizeof(metatag)))
if (!decoder_read_full(nullptr, is, &metatag, sizeof(metatag)))
return;
uint32_t length = FromBE32(metatag.size);
@@ -221,7 +220,7 @@ dsdiff_handle_native_tag(InputStream &is,
char *label;
label = string;
if (!dsdlib_read(nullptr, is, label, (size_t)length))
if (!decoder_read_full(nullptr, is, label, (size_t)length))
return;
string[length] = '\0';
@@ -251,15 +250,17 @@ dsdiff_read_metadata_extra(Decoder *decoder, InputStream &is,
if (!dsdiff_read_chunk_header(decoder, is, chunk_header))
return false;
metadata->diar_offset = 0;
metadata->diti_offset = 0;
#ifdef HAVE_ID3TAG
metadata->id3_size = 0;
metadata->id3_offset = 0;
#endif
/* Now process all the remaining chunk headers in the stream
and record their position and size */
const auto size = is.GetSize();
while (is.GetOffset() < size) {
do {
uint64_t chunk_size = chunk_header->GetSize();
/* DIIN chunk, is directly followed by other chunks */
@@ -285,17 +286,11 @@ dsdiff_read_metadata_extra(Decoder *decoder, InputStream &is,
metadata->id3_size = chunk_size;
}
#endif
if (chunk_size != 0) {
if (!dsdlib_skip(decoder, is, chunk_size))
break;
}
if (is.GetOffset() < size) {
if (!dsdiff_read_chunk_header(decoder, is, chunk_header))
return false;
}
chunk_size = 0;
}
if (!dsdlib_skip(decoder, is, chunk_size))
break;
} while (dsdiff_read_chunk_header(decoder, is, chunk_header));
/* done processing chunk headers, process tags if any */
#ifdef HAVE_ID3TAG
@@ -329,7 +324,7 @@ dsdiff_read_metadata(Decoder *decoder, InputStream &is,
DsdiffChunkHeader *chunk_header)
{
DsdiffHeader header;
if (!dsdlib_read(decoder, is, &header, sizeof(header)) ||
if (!decoder_read_full(decoder, is, &header, sizeof(header)) ||
!header.id.Equals("FRM8") ||
!header.format.Equals("DSD "))
return false;
@@ -385,17 +380,17 @@ 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;
}
size_t nbytes = decoder_read(decoder, is, buffer, now_size);
if (nbytes != now_size)
if (!decoder_read_full(&decoder, is, buffer, now_size))
return false;
const size_t nbytes = now_size;
chunk_size -= nbytes;
if (lsbitfirst)

@@ -103,7 +103,7 @@ dsf_read_metadata(Decoder *decoder, InputStream &is,
DsfMetaData *metadata)
{
DsfHeader dsf_header;
if (!dsdlib_read(decoder, is, &dsf_header, sizeof(dsf_header)) ||
if (!decoder_read_full(decoder, is, &dsf_header, sizeof(dsf_header)) ||
!dsf_header.id.Equals("DSD "))
return false;
@@ -117,7 +117,8 @@ dsf_read_metadata(Decoder *decoder, InputStream &is,
/* read the 'fmt ' chunk of the DSF file */
DsfFmtChunk dsf_fmt_chunk;
if (!dsdlib_read(decoder, is, &dsf_fmt_chunk, sizeof(dsf_fmt_chunk)) ||
if (!decoder_read_full(decoder, is,
&dsf_fmt_chunk, sizeof(dsf_fmt_chunk)) ||
!dsf_fmt_chunk.id.Equals("fmt "))
return false;
@@ -143,7 +144,7 @@ dsf_read_metadata(Decoder *decoder, InputStream &is,
/* read the 'data' chunk of the DSF file */
DsfDataChunk data_chunk;
if (!dsdlib_read(decoder, is, &data_chunk, sizeof(data_chunk)) ||
if (!decoder_read_full(decoder, is, &data_chunk, sizeof(data_chunk)) ||
!data_chunk.id.Equals("data"))
return false;
@@ -240,17 +241,17 @@ 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;
}
size_t nbytes = decoder_read(&decoder, is, buffer, now_size);
if (nbytes != now_size)
if (!decoder_read_full(&decoder, is, buffer, now_size))
return false;
const size_t nbytes = now_size;
chunk_size -= nbytes;
if (bitreverse)

@@ -34,8 +34,6 @@
#include <string.h>
#include <unistd.h>
#define AAC_MAX_CHANNELS 6
static const unsigned adts_sample_rates[] =
{ 96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050,
16000, 12000, 11025, 8000, 7350, 0, 0, 0
@@ -66,16 +64,13 @@ adts_check_frame(const unsigned char *data)
static size_t
adts_find_frame(DecoderBuffer *buffer)
{
size_t length, frame_length;
bool ret;
while (true) {
size_t length;
const uint8_t *data = (const uint8_t *)
decoder_buffer_read(buffer, &length);
if (data == nullptr || length < 8) {
/* not enough data yet */
ret = decoder_buffer_fill(buffer);
if (!ret)
if (!decoder_buffer_fill(buffer))
/* failed */
return 0;
@@ -86,7 +81,7 @@ adts_find_frame(DecoderBuffer *buffer)
const uint8_t *p = (const uint8_t *)memchr(data, 0xff, length);
if (p == nullptr) {
/* no marker - discard the buffer */
decoder_buffer_consume(buffer, length);
decoder_buffer_clear(buffer);
continue;
}
@@ -97,7 +92,7 @@ adts_find_frame(DecoderBuffer *buffer)
}
/* is it a frame? */
frame_length = adts_check_frame(data);
const size_t frame_length = adts_check_frame(data);
if (frame_length == 0) {
/* it's just some random 0xff byte; discard it
and continue searching */
@@ -109,15 +104,11 @@ adts_find_frame(DecoderBuffer *buffer)
/* available buffer size is smaller than the
frame will be - attempt to read more
data */
ret = decoder_buffer_fill(buffer);
if (!ret) {
if (!decoder_buffer_fill(buffer)) {
/* not enough data; discard this frame
to prevent a possible buffer
overflow */
data = (const uint8_t *)
decoder_buffer_read(buffer, &length);
if (data != nullptr)
decoder_buffer_consume(buffer, length);
decoder_buffer_clear(buffer);
}
continue;
@@ -131,13 +122,18 @@ adts_find_frame(DecoderBuffer *buffer)
static float
adts_song_duration(DecoderBuffer *buffer)
{
unsigned int frames, frame_length;
const InputStream &is = decoder_buffer_get_stream(buffer);
const bool estimate = !is.CheapSeeking();
const auto file_size = is.GetSize();
if (estimate && file_size <= 0)
return -1;
unsigned sample_rate = 0;
float frames_per_second;
/* Read all frames to ensure correct time and bitrate */
unsigned frames;
for (frames = 0;; frames++) {
frame_length = adts_find_frame(buffer);
const unsigned frame_length = adts_find_frame(buffer);
if (frame_length == 0)
break;
@@ -150,36 +146,52 @@ adts_song_duration(DecoderBuffer *buffer)
assert(frame_length <= buffer_length);
sample_rate = adts_sample_rates[(data[2] & 0x3c) >> 2];
if (sample_rate == 0)
break;
}
decoder_buffer_consume(buffer, frame_length);
if (estimate && frames == 128) {
/* if this is a remote file, don't slurp the
whole file just for checking the song
duration; instead, stop after some time and
extrapolate the song duration from what we
have until now */
const auto offset = is.GetOffset()
- decoder_buffer_available(buffer);
if (offset <= 0)
return -1;
frames = (frames * file_size) / offset;
break;
}
}
frames_per_second = (float)sample_rate / 1024.0;
if (frames_per_second <= 0)
if (sample_rate == 0)
return -1;
float frames_per_second = (float)sample_rate / 1024.0;
assert(frames_per_second > 0);
return (float)frames / frames_per_second;
}
static float
faad_song_duration(DecoderBuffer *buffer, InputStream &is)
{
size_t fileread;
size_t tagsize;
size_t length;
bool success;
const auto size = is.GetSize();
fileread = size >= 0 ? size : 0;
const size_t fileread = size >= 0 ? size : 0;
decoder_buffer_fill(buffer);
size_t length;
const uint8_t *data = (const uint8_t *)
decoder_buffer_read(buffer, &length);
if (data == nullptr)
return -1;
tagsize = 0;
size_t tagsize = 0;
if (length >= 10 && !memcmp(data, "ID3", 3)) {
/* skip the ID3 tag */
@@ -188,7 +200,7 @@ faad_song_duration(DecoderBuffer *buffer, InputStream &is)
tagsize += 10;
success = decoder_buffer_skip(buffer, tagsize) &&
const bool success = decoder_buffer_skip(buffer, tagsize) &&
decoder_buffer_fill(buffer);
if (!success)
return -1;
@@ -198,22 +210,20 @@ faad_song_duration(DecoderBuffer *buffer, InputStream &is)
return -1;
}
if (is.IsSeekable() && length >= 2 &&
data[0] == 0xFF && ((data[1] & 0xF6) == 0xF0)) {
if (length >= 8 && adts_check_frame(data) > 0) {
/* obtain the duration from the ADTS header */
if (!is.IsSeekable())
return -1;
float song_length = adts_song_duration(buffer);
is.LockSeek(tagsize, SEEK_SET, IgnoreError());
data = (const uint8_t *)decoder_buffer_read(buffer, &length);
if (data != nullptr)
decoder_buffer_consume(buffer, length);
decoder_buffer_fill(buffer);
decoder_buffer_clear(buffer);
return song_length;
} else if (length >= 5 && memcmp(data, "ADIF", 4) == 0) {
/* obtain the duration from the ADIF header */
unsigned bit_rate;
size_t skip_size = (data[4] & 0x80) ? 9 : 0;
if (8 + skip_size > length)
@@ -221,7 +231,7 @@ faad_song_duration(DecoderBuffer *buffer, InputStream &is)
header */
return -1;
bit_rate = ((data[4 + skip_size] & 0x0F) << 19) |
unsigned bit_rate = ((data[4 + skip_size] & 0x0F) << 19) |
(data[5 + skip_size] << 11) |
(data[6 + skip_size] << 3) |
(data[7 + skip_size] & 0xE0);
@@ -234,6 +244,21 @@ faad_song_duration(DecoderBuffer *buffer, InputStream &is)
return -1;
}
static NeAACDecHandle
faad_decoder_new()
{
const NeAACDecHandle decoder = NeAACDecOpen();
NeAACDecConfigurationPtr config =
NeAACDecGetCurrentConfiguration(decoder);
config->outputFormat = FAAD_FMT_16BIT;
config->downMatrix = 1;
config->dontUpSampleImplicitSBR = 0;
NeAACDecSetConfiguration(decoder, config);
return decoder;
}
/**
* Wrapper for NeAACDecInit() which works around some API
* inconsistencies in libfaad.
@@ -242,17 +267,6 @@ static bool
faad_decoder_init(NeAACDecHandle decoder, DecoderBuffer *buffer,
AudioFormat &audio_format, Error &error)
{
int32_t nbytes;
uint32_t sample_rate;
uint8_t channels;
#ifdef HAVE_FAAD_LONG
/* neaacdec.h declares all arguments as "unsigned long", but
internally expects uint32_t pointers. To avoid gcc
warnings, use this workaround. */
unsigned long *sample_rate_p = (unsigned long *)(void *)&sample_rate;
#else
uint32_t *sample_rate_p = &sample_rate;
#endif
size_t length;
const unsigned char *data = (const unsigned char *)
@@ -262,11 +276,21 @@ faad_decoder_init(NeAACDecHandle decoder, DecoderBuffer *buffer,
return false;
}
nbytes = NeAACDecInit(decoder,
/* deconst hack, libfaad requires this */
const_cast<unsigned char *>(data),
length,
sample_rate_p, &channels);
uint8_t channels;
uint32_t sample_rate;
#ifdef HAVE_FAAD_LONG
/* neaacdec.h declares all arguments as "unsigned long", but
internally expects uint32_t pointers. To avoid gcc
warnings, use this workaround. */
unsigned long *sample_rate_p = (unsigned long *)(void *)&sample_rate;
#else
uint32_t *sample_rate_p = &sample_rate;
#endif
long nbytes = NeAACDecInit(decoder,
/* deconst hack, libfaad requires this */
const_cast<unsigned char *>(data),
length,
sample_rate_p, &channels);
if (nbytes < 0) {
error.Set(faad_decoder_domain, "Not an AAC stream");
return false;
@@ -306,29 +330,21 @@ faad_decoder_decode(NeAACDecHandle decoder, DecoderBuffer *buffer,
static float
faad_get_file_time_float(InputStream &is)
{
DecoderBuffer *buffer;
float length;
DecoderBuffer *const buffer =
decoder_buffer_new(nullptr, is,
FAAD_MIN_STREAMSIZE * MAX_CHANNELS);
buffer = decoder_buffer_new(nullptr, is,
FAAD_MIN_STREAMSIZE * AAC_MAX_CHANNELS);
length = faad_song_duration(buffer, is);
float length = faad_song_duration(buffer, is);
if (length < 0) {
bool ret;
AudioFormat audio_format;
NeAACDecHandle decoder = NeAACDecOpen();
NeAACDecConfigurationPtr config =
NeAACDecGetCurrentConfiguration(decoder);
config->outputFormat = FAAD_FMT_16BIT;
NeAACDecSetConfiguration(decoder, config);
NeAACDecHandle decoder = faad_decoder_new();
decoder_buffer_fill(buffer);
ret = faad_decoder_init(decoder, buffer, audio_format,
IgnoreError());
if (ret)
if (faad_decoder_init(decoder, buffer, audio_format,
IgnoreError()))
length = 0;
NeAACDecClose(decoder);
@@ -347,38 +363,25 @@ faad_get_file_time_float(InputStream &is)
static int
faad_get_file_time(InputStream &is)
{
int file_time = -1;
float length;
float length = faad_get_file_time_float(is);
if (length < 0)
return -1;
if ((length = faad_get_file_time_float(is)) >= 0)
file_time = length + 0.5;
return file_time;
return int(length + 0.5);
}
static void
faad_stream_decode(Decoder &mpd_decoder, InputStream &is)
{
float total_time = 0;
AudioFormat audio_format;
bool ret;
uint16_t bit_rate = 0;
DecoderBuffer *buffer;
DecoderBuffer *const buffer =
decoder_buffer_new(&mpd_decoder, is,
FAAD_MIN_STREAMSIZE * MAX_CHANNELS);
buffer = decoder_buffer_new(&mpd_decoder, is,
FAAD_MIN_STREAMSIZE * AAC_MAX_CHANNELS);
total_time = faad_song_duration(buffer, is);
const float total_time = faad_song_duration(buffer, is);
/* create the libfaad decoder */
NeAACDecHandle decoder = NeAACDecOpen();
NeAACDecConfigurationPtr config =
NeAACDecGetCurrentConfiguration(decoder);
config->outputFormat = FAAD_FMT_16BIT;
config->downMatrix = 1;
config->dontUpSampleImplicitSBR = 0;
NeAACDecSetConfiguration(decoder, config);
const NeAACDecHandle decoder = faad_decoder_new();
while (!decoder_buffer_is_full(buffer) && !is.LockIsEOF() &&
decoder_get_command(mpd_decoder) == DecoderCommand::NONE) {
@@ -389,10 +392,11 @@ faad_stream_decode(Decoder &mpd_decoder, InputStream &is)
/* initialize it */
Error error;
ret = faad_decoder_init(decoder, buffer, audio_format, error);
if (!ret) {
AudioFormat audio_format;
if (!faad_decoder_init(decoder, buffer, audio_format, error)) {
LogError(error);
NeAACDecClose(decoder);
decoder_buffer_free(buffer);
return;
}
@@ -403,21 +407,20 @@ faad_stream_decode(Decoder &mpd_decoder, InputStream &is)
/* the decoder loop */
DecoderCommand cmd;
uint16_t bit_rate = 0;
do {
size_t frame_size;
const void *decoded;
NeAACDecFrameInfo frame_info;
/* find the next frame */
frame_size = adts_find_frame(buffer);
const size_t frame_size = adts_find_frame(buffer);
if (frame_size == 0)
/* end of file */
break;
/* decode it */
decoded = faad_decoder_decode(decoder, buffer, &frame_info);
NeAACDecFrameInfo frame_info;
const void *const decoded =
faad_decoder_decode(decoder, buffer, &frame_info);
if (frame_info.error > 0) {
FormatWarning(faad_decoder_domain,
@@ -461,6 +464,7 @@ faad_stream_decode(Decoder &mpd_decoder, InputStream &is)
/* cleanup */
NeAACDecClose(decoder);
decoder_buffer_free(buffer);
}
static bool
@@ -468,7 +472,6 @@ faad_scan_stream(InputStream &is,
const struct tag_handler *handler, void *handler_ctx)
{
int file_time = faad_get_file_time(is);
if (file_time < 0)
return false;

@@ -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;
@@ -195,6 +197,29 @@ time_to_ffmpeg(double t, const AVRational time_base)
time_base);
}
/**
* Replace #AV_NOPTS_VALUE with the given fallback.
*/
static constexpr int64_t
timestamp_fallback(int64_t t, int64_t fallback)
{
return gcc_likely(t != int64_t(AV_NOPTS_VALUE))
? t
: fallback;
}
/**
* Accessor for AVStream::start_time that replaces AV_NOPTS_VALUE with
* zero. We can't use AV_NOPTS_VALUE in calculations, and we simply
* assume that the stream's start time is zero, which appears to be
* the best way out of that situation.
*/
static int64_t
start_time_fallback(const AVStream &stream)
{
return timestamp_fallback(stream.start_time, 0);
}
static void
copy_interleave_frame2(uint8_t *dest, uint8_t **src,
unsigned nframes, unsigned nchannels,
@@ -224,6 +249,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 +280,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 - start_time_fallback(*stream),
stream->time_base));
AVPacket packet2 = *packet;
@@ -342,11 +371,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 +480,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 +504,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,10 +515,11 @@ 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) +
start_time_fallback(*av_stream);
if (av_seek_frame(format_context, audio_stream, where,
AV_TIME_BASE) < 0)
AVSEEK_FLAG_ANY) < 0)
decoder_seek_error(decoder);
else {
avcodec_flush_buffers(codec_context);
@@ -496,7 +528,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

@@ -117,6 +117,7 @@ gme_container_scan(const char *path_fs, const unsigned int tnum)
}
const unsigned num_songs = gme_track_count(emu);
gme_delete(emu);
/* if it only contains a single tune, don't treat as container */
if (num_songs < 2)
return nullptr;

@@ -353,18 +353,8 @@ MadDecoder::ParseId3(size_t tagsize, Tag **mpd_tag)
memcpy(allocated, stream.this_frame, count);
mad_stream_skip(&(stream), count);
while (count < tagsize) {
size_t len;
len = decoder_read(decoder, input_stream,
allocated + count, tagsize - count);
if (len == 0)
break;
else
count += len;
}
if (count != tagsize) {
if (!decoder_read_full(decoder, input_stream,
allocated + count, tagsize - count)) {
LogDebug(mad_domain, "error parsing ID3 tag");
g_free(allocated);
return;
@@ -413,20 +403,7 @@ MadDecoder::ParseId3(size_t tagsize, Tag **mpd_tag)
mad_stream_skip(&stream, tagsize);
} else {
mad_stream_skip(&stream, count);
while (count < tagsize) {
size_t len = tagsize - count;
char ignored[1024];
if (len > sizeof(ignored))
len = sizeof(ignored);
len = decoder_read(decoder, input_stream,
ignored, len);
if (len == 0)
break;
else
count += len;
}
decoder_skip(decoder, input_stream, tagsize - count);
}
#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)
{

@@ -81,7 +81,7 @@ bool
OggExpectPageSeek(ogg_sync_state &oy, ogg_page &page,
Decoder *decoder, InputStream &input_stream)
{
size_t remaining_skipped = 16384;
size_t remaining_skipped = 32768;
while (true) {
int r = ogg_sync_pageseek(&oy, &page);

@@ -40,6 +40,7 @@
#include <glib.h>
#include <string.h>
#include <stdio.h>
static constexpr opus_int32 opus_sample_rate = 48000;

@@ -31,10 +31,24 @@
static constexpr Domain sndfile_domain("sndfile");
struct SndfileInputStream {
Decoder *const decoder;
InputStream &is;
size_t Read(void *buffer, size_t size) {
/* libsndfile chokes on partial reads; therefore
always force full reads */
return decoder_read_full(decoder, is, buffer, size)
? size
: 0;
}
};
static sf_count_t
sndfile_vio_get_filelen(void *user_data)
{
const InputStream &is = *(const InputStream *)user_data;
SndfileInputStream &sis = *(SndfileInputStream *)user_data;
const InputStream &is = sis.is;
return is.GetSize();
}
@@ -42,10 +56,14 @@ sndfile_vio_get_filelen(void *user_data)
static sf_count_t
sndfile_vio_seek(sf_count_t offset, int whence, void *user_data)
{
InputStream &is = *(InputStream *)user_data;
SndfileInputStream &sis = *(SndfileInputStream *)user_data;
InputStream &is = sis.is;
if (!is.LockSeek(offset, whence, IgnoreError()))
Error error;
if (!is.LockSeek(offset, whence, error)) {
LogError(error, "Seek failed");
return -1;
}
return is.GetOffset();
}
@@ -53,16 +71,9 @@ sndfile_vio_seek(sf_count_t offset, int whence, void *user_data)
static sf_count_t
sndfile_vio_read(void *ptr, sf_count_t count, void *user_data)
{
InputStream &is = *(InputStream *)user_data;
SndfileInputStream &sis = *(SndfileInputStream *)user_data;
Error error;
size_t nbytes = is.LockRead(ptr, count, error);
if (nbytes == 0 && error.IsDefined()) {
LogError(error);
return -1;
}
return nbytes;
return sis.Read(ptr, count);
}
static sf_count_t
@@ -77,7 +88,8 @@ sndfile_vio_write(gcc_unused const void *ptr,
static sf_count_t
sndfile_vio_tell(void *user_data)
{
const InputStream &is = *(const InputStream *)user_data;
SndfileInputStream &sis = *(SndfileInputStream *)user_data;
const InputStream &is = sis.is;
return is.GetOffset();
}
@@ -123,7 +135,8 @@ sndfile_stream_decode(Decoder &decoder, InputStream &is)
info.format = 0;
sf = sf_open_virtual(&vio, SFM_READ, &info, &is);
SndfileInputStream sis{&decoder, is};
sf = sf_open_virtual(&vio, SFM_READ, &info, &sis);
if (sf == nullptr) {
LogWarning(sndfile_domain, "sf_open_virtual() failed");
return;

@@ -27,9 +27,11 @@ DeferredMonitor::Cancel()
#ifdef USE_EPOLL
pending = false;
#else
const auto id = source_id.exchange(0);
if (id != 0)
g_source_remove(id);
const ScopeLock protect(mutex);
if (source_id != 0) {
g_source_remove(source_id);
source_id = 0;
}
#endif
}
@@ -40,10 +42,9 @@ DeferredMonitor::Schedule()
if (!pending.exchange(true))
fd.Write();
#else
const unsigned id = loop.AddIdle(Callback, this);
const auto old_id = source_id.exchange(id);
if (old_id != 0)
g_source_remove(old_id);
const ScopeLock protect(mutex);
if (source_id == 0)
source_id = loop.AddIdle(Callback, this);
#endif
}
@@ -65,9 +66,16 @@ DeferredMonitor::OnSocketReady(unsigned)
void
DeferredMonitor::Run()
{
const auto id = source_id.exchange(0);
if (id != 0)
RunDeferred();
{
const ScopeLock protect(mutex);
if (source_id == 0)
/* cancelled */
return;
source_id = 0;
}
RunDeferred();
}
gboolean

@@ -27,6 +27,7 @@
#include "SocketMonitor.hxx"
#include "WakeFD.hxx"
#else
#include "thread/Mutex.hxx"
#include <glib.h>
#endif
@@ -48,7 +49,9 @@ class DeferredMonitor
#else
EventLoop &loop;
std::atomic<guint> source_id;
Mutex mutex;
guint source_id;
#endif
public:

@@ -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);

@@ -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 &param, 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,7 @@
#include <assert.h>
#include <string.h>
#include <stdio.h>
HttpdClient::~HttpdClient()
{

@@ -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

@@ -31,6 +31,7 @@
#include <glib.h>
#include <string>
#include <stdio.h>
static constexpr Domain pls_domain("pls");
@@ -68,7 +69,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>

@@ -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;

@@ -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;

144
test/FakeDecoderAPI.cxx Normal file

@@ -0,0 +1,144 @@
/*
* Copyright (C) 2003-2012 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 "config.h"
#include "DecoderAPI.hxx"
#include "InputStream.hxx"
#include "util/Error.hxx"
#include "Compiler.h"
#include <glib.h>
#include <unistd.h>
void
decoder_initialized(gcc_unused Decoder &decoder,
gcc_unused const AudioFormat audio_format,
gcc_unused bool seekable,
gcc_unused float total_time)
{
}
DecoderCommand
decoder_get_command(gcc_unused Decoder &decoder)
{
return DecoderCommand::NONE;
}
void
decoder_command_finished(gcc_unused Decoder &decoder)
{
}
double
decoder_seek_where(gcc_unused Decoder &decoder)
{
return 1.0;
}
void
decoder_seek_error(gcc_unused Decoder &decoder)
{
}
size_t
decoder_read(gcc_unused Decoder *decoder,
InputStream &is,
void *buffer, size_t length)
{
return is.LockRead(buffer, length, IgnoreError());
}
bool
decoder_read_full(Decoder *decoder, InputStream &is,
void *_buffer, size_t size)
{
uint8_t *buffer = (uint8_t *)_buffer;
while (size > 0) {
size_t nbytes = decoder_read(decoder, is, buffer, size);
if (nbytes == 0)
return false;
buffer += nbytes;
size -= nbytes;
}
return true;
}
bool
decoder_skip(Decoder *decoder, InputStream &is, size_t size)
{
while (size > 0) {
char buffer[1024];
size_t nbytes = decoder_read(decoder, is, buffer,
std::min(sizeof(buffer), size));
if (nbytes == 0)
return false;
size -= nbytes;
}
return true;
}
void
decoder_timestamp(gcc_unused Decoder &decoder,
gcc_unused double t)
{
}
DecoderCommand
decoder_data(gcc_unused Decoder &decoder,
gcc_unused InputStream *is,
const void *data, size_t datalen,
gcc_unused uint16_t kbit_rate)
{
gcc_unused ssize_t nbytes = write(1, data, datalen);
return DecoderCommand::NONE;
}
DecoderCommand
decoder_tag(gcc_unused Decoder &decoder,
gcc_unused InputStream *is,
gcc_unused Tag &&tag)
{
return DecoderCommand::NONE;
}
void
decoder_replay_gain(gcc_unused Decoder &decoder,
const ReplayGainInfo *rgi)
{
const ReplayGainTuple *tuple = &rgi->tuples[REPLAY_GAIN_ALBUM];
if (tuple->IsDefined())
g_printerr("replay_gain[album]: gain=%f peak=%f\n",
tuple->gain, tuple->peak);
tuple = &rgi->tuples[REPLAY_GAIN_TRACK];
if (tuple->IsDefined())
g_printerr("replay_gain[track]: gain=%f peak=%f\n",
tuple->gain, tuple->peak);
}
void
decoder_mixramp(gcc_unused Decoder &decoder, gcc_unused MixRampInfo &&mix_ramp)
{
}

@@ -24,7 +24,6 @@
#include "Directory.hxx"
#include "InputStream.hxx"
#include "ConfigGlobal.hxx"
#include "DecoderAPI.hxx"
#include "DecoderList.hxx"
#include "InputInit.hxx"
#include "IOThread.hxx"
@@ -53,88 +52,6 @@ my_log_func(const gchar *log_domain, gcc_unused GLogLevelFlags log_level,
g_printerr("%s\n", message);
}
void
decoder_initialized(gcc_unused Decoder &decoder,
gcc_unused const AudioFormat audio_format,
gcc_unused bool seekable,
gcc_unused float total_time)
{
}
DecoderCommand
decoder_get_command(gcc_unused Decoder &decoder)
{
return DecoderCommand::NONE;
}
void
decoder_command_finished(gcc_unused Decoder &decoder)
{
}
double
decoder_seek_where(gcc_unused Decoder &decoder)
{
return 1.0;
}
void
decoder_seek_error(gcc_unused Decoder &decoder)
{
}
size_t
decoder_read(gcc_unused Decoder *decoder,
InputStream &is,
void *buffer, size_t length)
{
return is.LockRead(buffer, length, IgnoreError());
}
void
decoder_timestamp(gcc_unused Decoder &decoder,
gcc_unused double t)
{
}
DecoderCommand
decoder_data(gcc_unused Decoder &decoder,
gcc_unused InputStream *is,
const void *data, size_t datalen,
gcc_unused uint16_t kbit_rate)
{
gcc_unused ssize_t nbytes = write(1, data, datalen);
return DecoderCommand::NONE;
}
DecoderCommand
decoder_tag(gcc_unused Decoder &decoder,
gcc_unused InputStream *is,
gcc_unused Tag &&tag)
{
return DecoderCommand::NONE;
}
void
decoder_replay_gain(gcc_unused Decoder &decoder,
const ReplayGainInfo *rgi)
{
const ReplayGainTuple *tuple = &rgi->tuples[REPLAY_GAIN_ALBUM];
if (tuple->IsDefined())
g_printerr("replay_gain[album]: gain=%f peak=%f\n",
tuple->gain, tuple->peak);
tuple = &rgi->tuples[REPLAY_GAIN_TRACK];
if (tuple->IsDefined())
g_printerr("replay_gain[track]: gain=%f peak=%f\n",
tuple->gain, tuple->peak);
}
void
decoder_mixramp(gcc_unused Decoder &decoder, gcc_unused MixRampInfo &&mix_ramp)
{
}
int main(int argc, char **argv)
{
const char *uri;

@@ -20,7 +20,7 @@
#include "config.h"
#include "IOThread.hxx"
#include "DecoderList.hxx"
#include "DecoderAPI.hxx"
#include "DecoderPlugin.hxx"
#include "InputInit.hxx"
#include "InputStream.hxx"
#include "AudioFormat.hxx"
@@ -42,79 +42,6 @@
#include <locale.h>
#endif
void
decoder_initialized(gcc_unused Decoder &decoder,
gcc_unused const AudioFormat audio_format,
gcc_unused bool seekable,
gcc_unused float total_time)
{
}
DecoderCommand
decoder_get_command(gcc_unused Decoder &decoder)
{
return DecoderCommand::NONE;
}
void
decoder_command_finished(gcc_unused Decoder &decoder)
{
}
double
decoder_seek_where(gcc_unused Decoder &decoder)
{
return 1.0;
}
void
decoder_seek_error(gcc_unused Decoder &decoder)
{
}
size_t
decoder_read(gcc_unused Decoder *decoder,
InputStream &is,
void *buffer, size_t length)
{
return is.LockRead(buffer, length, IgnoreError());
}
void
decoder_timestamp(gcc_unused Decoder &decoder,
gcc_unused double t)
{
}
DecoderCommand
decoder_data(gcc_unused Decoder &decoder,
gcc_unused InputStream *is,
const void *data, size_t datalen,
gcc_unused uint16_t kbit_rate)
{
gcc_unused ssize_t nbytes = write(1, data, datalen);
return DecoderCommand::NONE;
}
DecoderCommand
decoder_tag(gcc_unused Decoder &decoder,
gcc_unused InputStream *is,
gcc_unused Tag &&tag)
{
return DecoderCommand::NONE;
}
void
decoder_replay_gain(gcc_unused Decoder &decoder,
gcc_unused const ReplayGainInfo *replay_gain_info)
{
}
void
decoder_mixramp(gcc_unused Decoder &decoder, gcc_unused MixRampInfo &&mix_ramp)
{
}
static bool empty = true;
static void

@@ -101,6 +101,40 @@ decoder_read(gcc_unused Decoder *decoder,
return is.LockRead(buffer, length, IgnoreError());
}
bool
decoder_read_full(Decoder *decoder, InputStream &is,
void *_buffer, size_t size)
{
uint8_t *buffer = (uint8_t *)_buffer;
while (size > 0) {
size_t nbytes = decoder_read(decoder, is, buffer, size);
if (nbytes == 0)
return false;
buffer += nbytes;
size -= nbytes;
}
return true;
}
bool
decoder_skip(Decoder *decoder, InputStream &is, size_t size)
{
while (size > 0) {
char buffer[1024];
size_t nbytes = decoder_read(decoder, is, buffer,
std::min(sizeof(buffer), size));
if (nbytes == 0)
return false;
size -= nbytes;
}
return true;
}
void
decoder_timestamp(gcc_unused Decoder &decoder,
gcc_unused double t)

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 &registry = 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() {