Compare commits

...

107 Commits

Author SHA1 Message Date
Max Kellermann
86e8b3b4bd release v0.18.13 2014-08-31 14:50:23 +02:00
Max Kellermann
a26ead035a PlaylistControl: use SeekSongOrder(current) to keep current song
The "current" attribute is a "song order", not a "song position".
This is usually the same - except in random mode.  Fixes Mantis ticket
0004073.
2014-08-31 14:44:20 +02:00
Max Kellermann
704be54c3a PlaylistControl: move code to new method SeekSongOrder() 2014-08-31 14:23:06 +02:00
Max Kellermann
2406152576 output/alsa: fix endless loop at end of file in dsd_usb mode 2014-08-31 14:01:57 +02:00
Max Kellermann
af260b5a64 output/{alsa,oss}: add assertions 2014-08-31 14:00:09 +02:00
Joachim Fasting
4efa96df21 doc/protocol: fix description of "stats" response
Fix incorrect description of the "songs" field and add missing
"albums" field.

Signed-off-by: Joachim Fasting <joachifm@fastmail.fm>
2014-08-31 13:16:39 +02:00
Max Kellermann
8b62127770 decoder/gme: fix song duration
The unit of gme_info_t::length is milliseconds, not centiseconds.
2014-08-29 23:03:29 +02:00
Max Kellermann
f06fe1ea98 event/TimeoutMonitor: really reset "active" flag before invoking OnTimeout()
The previous commit was broken.  D'oh!
2014-08-24 13:19:50 +02:00
Max Kellermann
d16fb79708 event/TimeoutMonitor: reset "active" flag before invoking OnTimeout()
The IsActive() method returned true even if the timer was not active,
after it completed once.  This broke the state file timer, and the
state file was not saved periodically.
2014-08-24 13:13:12 +02:00
Thomas Klausner
c38f29ce56 system/ByteOrder: <endian.h> is a non-standard header that only Linux provides. 2014-08-23 14:27:44 +02:00
Max Kellermann
78abcd7df7 decoer/dsdiff: fix endless loop on malformed file
Same bug as in the previous commit.
2014-08-21 12:48:03 +02:00
Max Kellermann
23dce21647 decoer/dsf: fix endless loop on malformed file
When the data chunk size is not a multiple of the frame size, the last
partial frame lead to an endless loop.  We fix this by checking
chunk_sze>=frame instead of chunk_sze>0.  This way, the partial frame
is simply skipped.
2014-08-21 12:37:22 +02:00
François Revol
40280fa6cf util: Fix header for strcasecmp
According to POSIX and both OSX and Linux manpages,
strcasecmp comes from strings.h, not string.h.

Most OSes also have them available in string.h,
but we just fixed the headers on Haiku and it now
only provides them in strings.h.

We might want to fall back to string.h for other
OSes though...

cf.
http://pubs.opengroup.org/onlinepubs/009695399/functions/strcasecmp.html
http://linux.die.net/man/3/strcasecmp
https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man3/strcasecmp.3.html
2014-08-16 06:51:13 +02:00
Max Kellermann
fe9299ceff decoder/ffmpeg: use avcodec_descriptor_get() to determine codec name
In version 11, both ffmpeg and libav deprecate
AVCodecContext::codec_name.  The function avcodec_descriptor_get() has
been introduced long ago.
2014-08-13 18:40:39 +02:00
Max Kellermann
c3f111a56c event/BufferedSocket: fix inversed buffer check
This was broken by commit 84d20d9e, which deleted the "!" from the
check.
2014-08-07 16:03:44 +02:00
François Revol
250318329f Makefile.am: fix dependencies for win32
It happened to me when doing the Haiku port, src/mpd failed to
be relinked properly when editing source files, and likely also
happens on win32, although I didn't try this change.

When building for windows, src_mpd_DEPENDENCIES is overriden.

Automake then disables the default version which contains all
the static libraries. In Makefile.in:
@HAVE_WINDOWS_FALSE@src_mpd_DEPENDENCIES = libmpd.a \

Instead we use EXTRA_src_mpd_DEPENDENCIES which is meant for this.
2014-08-02 08:48:44 +02:00
Max Kellermann
14c538c9c7 Win32Main: move to win32/ 2014-08-02 08:48:30 +02:00
Max Kellermann
abe4c57663 configure.ac: prepare for 0.18.13 2014-08-02 08:45:44 +02:00
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
75 changed files with 1213 additions and 549 deletions

2
.gitignore vendored

@@ -40,7 +40,7 @@ tags
.#*
.stgit*
src/dsd2pcm/dsd2pcm
src/win/mpd_win32_rc.rc
src/win32/mpd_win32_rc.rc
doc/doxygen.conf
doc/protocol.html
doc/protocol

@@ -151,7 +151,7 @@ src_mpd_SOURCES = \
src/IOThread.cxx src/IOThread.hxx \
src/Main.cxx src/Main.hxx \
src/Instance.cxx src/Instance.hxx \
src/Win32Main.cxx \
src/win32/Win32Main.cxx \
src/GlobalEvents.cxx src/GlobalEvents.hxx \
src/Daemon.cxx src/Daemon.hxx \
src/AudioCompress/compress.c \
@@ -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 \
@@ -210,14 +211,14 @@ src_mpd_SOURCES = \
# Windows resource file
#
src/win/mpd_win32_rc.$(OBJEXT): src/win/mpd_win32_rc.rc
src/win32/mpd_win32_rc.$(OBJEXT): src/win32/mpd_win32_rc.rc
$(WINDRES) -i $< -o $@
if HAVE_WINDOWS
noinst_DATA = src/win/mpd_win32_rc.rc
noinst_DATA = src/win32/mpd_win32_rc.rc
src_mpd_DEPENDENCIES = src/win/mpd_win32_rc.$(OBJEXT)
src_mpd_LDFLAGS = -Wl,src/win/mpd_win32_rc.$(OBJEXT)
EXTRA_src_mpd_DEPENDENCIES = src/win32/mpd_win32_rc.$(OBJEXT)
src_mpd_LDFLAGS = -Wl,src/win32/mpd_win32_rc.$(OBJEXT)
endif
if ENABLE_DESPOTIFY
@@ -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 \
@@ -1619,4 +1633,4 @@ EXTRA_DIST = $(doc_DATA) autogen.sh \
test/test_archive_zzip.sh \
$(wildcard scripts/*.sh) \
$(man_MANS) $(DOCBOOK_FILES) doc/mpdconf.example doc/doxygen.conf \
src/win/mpd_win32_rc.rc.in src/win/mpd.ico
src/win32/mpd_win32_rc.rc.in src/win32/mpd.ico

72
NEWS

@@ -1,3 +1,75 @@
ver 0.18.13 (2014/08/31)
* protocol
- don't change song on "seekcur" in random mode
* decoder
- dsdiff, dsf: fix endless loop on malformed file
- ffmpeg: support ffmpeg/libav version 11
- gme: fix song duration
* output
- alsa: fix endless loop at end of file in dsd_usb mode
* fix state file saver
* fix build failure on Darwin
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

@@ -1,6 +1,6 @@
AC_PREREQ(2.60)
AC_INIT(mpd, 0.18.6, musicpd-dev-team@lists.sourceforge.net)
AC_INIT(mpd, 0.18.13, mpd-devel@musicpd.org)
VERSION_MAJOR=0
VERSION_MINOR=18
@@ -70,7 +70,7 @@ host_is_darwin=no
case "$host_os" in
mingw32* | windows*)
AC_CONFIG_FILES([
src/win/mpd_win32_rc.rc
src/win32/mpd_win32_rc.rc
])
AC_CHECK_TOOL(WINDRES, windres)
AM_CPPFLAGS="$AM_CPPFLAGS -DWIN32_LEAN_AND_MEAN"
@@ -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
@@ -960,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])
@@ -1023,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])
@@ -1139,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>

@@ -576,7 +576,12 @@
</listitem>
<listitem>
<para>
<varname>songs</varname>: number of albums
<varname>albums</varname>: number of albums
</para>
</listitem>
<listitem>
<para>
<varname>songs</varname>: number of songs
</para>
</listitem>
<listitem>

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

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

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

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

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

@@ -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);
/**
@@ -217,6 +234,10 @@ public:
void PlayPrevious(PlayerControl &pc);
PlaylistResult SeekSongOrder(PlayerControl &pc,
unsigned song_order,
float seek_time);
PlaylistResult SeekSongPosition(PlayerControl &pc,
unsigned song_position,
float seek_time);

@@ -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 */
}
@@ -190,17 +190,12 @@ playlist::PlayPrevious(PlayerControl &pc)
}
PlaylistResult
playlist::SeekSongPosition(PlayerControl &pc, unsigned song, float seek_time)
playlist::SeekSongOrder(PlayerControl &pc, unsigned i, float seek_time)
{
if (!queue.IsValidPosition(song))
return PlaylistResult::BAD_RANGE;
assert(queue.IsValidOrder(i));
const Song *queued_song = GetQueuedSong();
unsigned i = queue.random
? queue.PositionToOrder(song)
: song;
pc.ClearError();
stop_on_error = true;
error_count = 0;
@@ -228,6 +223,19 @@ playlist::SeekSongPosition(PlayerControl &pc, unsigned song, float seek_time)
return PlaylistResult::SUCCESS;
}
PlaylistResult
playlist::SeekSongPosition(PlayerControl &pc, unsigned song, float seek_time)
{
if (!queue.IsValidPosition(song))
return PlaylistResult::BAD_RANGE;
unsigned i = queue.random
? queue.PositionToOrder(song)
: song;
return SeekSongOrder(pc, i, seek_time);
}
PlaylistResult
playlist::SeekSongId(PlayerControl &pc, unsigned id, float seek_time)
{
@@ -257,5 +265,5 @@ playlist::SeekCurrent(PlayerControl &pc, float seek_time, bool relative)
if (seek_time < 0)
seek_time = 0;
return SeekSongPosition(pc, current, seek_time);
return SeekSongOrder(pc, current, seek_time);
}

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

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

@@ -398,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));
@@ -561,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,
@@ -572,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);

@@ -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,16 +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;
}
}
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
@@ -328,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;
@@ -381,7 +377,7 @@ dsdiff_decode_chunk(Decoder &decoder, InputStream &is,
const unsigned buffer_samples = buffer_frames * frame_size;
const size_t buffer_size = buffer_samples * sample_size;
while (chunk_size > 0) {
while (chunk_size >= frame_size) {
/* see how much aligned data from the remaining chunk
fits into the local buffer */
size_t now_size = buffer_size;
@@ -391,10 +387,10 @@ dsdiff_decode_chunk(Decoder &decoder, InputStream &is,
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;
@@ -237,7 +238,7 @@ dsf_decode_chunk(Decoder &decoder, InputStream &is,
const unsigned buffer_samples = buffer_frames * frame_size;
const size_t buffer_size = buffer_samples * sample_size;
while (chunk_size > 0) {
while (chunk_size >= frame_size) {
/* see how much aligned data from the remaining chunk
fits into the local buffer */
size_t now_size = buffer_size;
@@ -247,10 +248,10 @@ dsf_decode_chunk(Decoder &decoder, InputStream &is,
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>
@@ -194,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,
@@ -223,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) {
@@ -257,7 +286,7 @@ ffmpeg_send_packet(Decoder &decoder, InputStream &is,
{
if (packet->pts >= 0 && packet->pts != (int64_t)AV_NOPTS_VALUE)
decoder_timestamp(decoder,
time_from_ffmpeg(packet->pts - stream->start_time,
time_from_ffmpeg(packet->pts - start_time_fallback(*stream),
stream->time_base));
AVPacket packet2 = *packet;
@@ -404,9 +433,18 @@ ffmpeg_decode(Decoder &decoder, InputStream &input)
AVStream *av_stream = format_context->streams[audio_stream];
AVCodecContext *codec_context = av_stream->codec;
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(54, 25, 0)
const AVCodecDescriptor *codec_descriptor =
avcodec_descriptor_get(codec_context->codec_id);
if (codec_descriptor != nullptr)
FormatDebug(ffmpeg_domain, "codec '%s'",
codec_descriptor->name);
#else
if (codec_context->codec_name[0] != 0)
FormatDebug(ffmpeg_domain, "codec '%s'",
codec_context->codec_name);
#endif
AVCodec *codec = avcodec_find_decoder(codec_context->codec_id);
@@ -451,7 +489,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);
@@ -483,10 +525,10 @@ ffmpeg_decode(Decoder &decoder, InputStream &input)
int64_t where =
time_to_ffmpeg(decoder_seek_where(decoder),
av_stream->time_base) +
av_stream->start_time;
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);
@@ -495,7 +537,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;
@@ -234,7 +235,7 @@ gme_scan_file(const char *path_fs,
if (ti->length > 0)
tag_handler_invoke_duration(handler, handler_ctx,
ti->length / 100);
ti->length / 1000);
if (ti->song != nullptr) {
if (gme_track_count(emu) > 1) {

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

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

@@ -118,7 +118,7 @@ BufferedSocket::OnSocketReady(unsigned flags)
if (!ReadToBuffer() || !ResumeInput())
return false;
if (input.IsFull())
if (!input.IsFull())
ScheduleRead();
}

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

@@ -64,7 +64,9 @@ TimeoutMonitor::ScheduleSeconds(unsigned s)
void
TimeoutMonitor::Run()
{
#ifndef USE_EPOLL
#ifdef USE_EPOLL
active = false;
#else
Cancel();
#endif

@@ -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);
}
@@ -820,13 +802,34 @@ alsa_play(struct audio_output *ao, const void *chunk, size_t size,
{
AlsaOutput *ad = (AlsaOutput *)ao;
assert(size > 0);
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;
}
}
const size_t original_size = size;
chunk = ad->pcm_export->Export(chunk, size, size);
if (size == 0)
/* the DoP (DSD over PCM) filter converts two frames
at a time and ignores the last odd frame; if there
was only one frame (e.g. the last frame in the
file), the result is empty; to avoid an endless
loop, bail out here, and pretend the one frame has
been played */
return original_size;
assert(size % ad->out_frame_size == 0);
size /= ad->out_frame_size;
assert(size > 0);
while (true) {
snd_pcm_sframes_t ret = ad->writei(ad->pcm, chunk, size);
@@ -834,9 +837,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()
{

@@ -727,6 +727,8 @@ oss_output_play(struct audio_output *ao, const void *chunk, size_t size,
OssOutput *od = (OssOutput *)ao;
ssize_t ret;
assert(size > 0);
/* reopen the device since it was closed by dropBufferedAudio */
if (od->fd < 0 && !oss_reopen(od, error))
return 0;
@@ -735,6 +737,8 @@ oss_output_play(struct audio_output *ao, const void *chunk, size_t size,
chunk = od->pcm_export->Export(chunk, size, size);
#endif
assert(size > 0);
while (true) {
ret = write(od->fd, chunk, size);
if (ret > 0) {

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

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

@@ -40,6 +40,16 @@
/* well-known big-endian */
# define IS_LITTLE_ENDIAN false
# define IS_BIG_ENDIAN true
#elif defined(__APPLE__)
/* compile-time check for MacOS */
# include <machine/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
#else
/* generic compile-time check */
# include <endian.h>

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

@@ -33,7 +33,7 @@
#include "Compiler.h"
#include <assert.h>
#include <string.h>
#include <strings.h>
/**
* Determine whether two strings are equal, ignoring case for ASCII

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

Before

(image error) Size: 345 KiB

After

(image error) Size: 345 KiB

@@ -3,7 +3,7 @@
#define VERSION_NUMBER @VERSION_MAJOR@,@VERSION_MINOR@,@VERSION_REVISION@,@VERSION_EXTRA@
#define VERSION_NUMBER_STR "@VERSION_MAJOR@,@VERSION_MINOR@,@VERSION_REVISION@,@VERSION_EXTRA@"
MPD_ICON ICON "@top_srcdir@/src/win/mpd.ico"
MPD_ICON ICON "@top_srcdir@/src/win32/mpd.ico"
1 VERSIONINFO
FILETYPE VFT_APP

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() {