Compare commits

...

79 Commits

Author SHA1 Message Date
Max Kellermann
d6bc5c35a7 release v0.19.4 2014-11-18 21:40:52 +01:00
Max Kellermann
dc03f003ac Merge tag 'v0.18.18' into v0.19.x 2014-11-18 21:38:44 +01:00
Max Kellermann
7aa2104596 release v0.18.18 2014-11-18 21:34:03 +01:00
Max Kellermann
460cfba6ff QueueCommands: workaround for buggy clients that send "add /" 2014-11-18 21:31:54 +01:00
Max Kellermann
c8b93d6573 Client: assume uid==0 is local socket
A negative uid value means it's not a "local socket" (PF_LOCAL).
uid==0 means user "root" connected.
2014-11-18 20:56:27 +01:00
Max Kellermann
3f5f96ac91 event/ServerSocket: fix get_remote_uid() error value
Must return -1 on error, not 0.  0 is root.
2014-11-18 20:53:59 +01:00
Max Kellermann
7c6b991de7 decoder/opus: add MIME types audio/ogg and application/ogg 2014-11-12 15:16:34 +01:00
Max Kellermann
82460aa49f configure.ac: prepare for 0.19.4 2014-11-12 15:16:07 +01:00
Florent Le Coz
7e7b403043 Construct a Null AllocatedPath if the filename conversion into UTF8 failed 2014-11-11 17:15:19 +01:00
Max Kellermann
e5217e6ce9 release v0.19.3 2014-11-11 11:21:42 +01:00
Max Kellermann
c98cb1d6f9 decoder/opus: support chained streams 2014-11-11 11:20:18 +01:00
Max Kellermann
ba6f2b0467 decoder/opus: move code to HandleEOS() 2014-11-11 11:20:16 +01:00
Max Kellermann
23465ad985 decoder/opus: improved error logging 2014-11-11 11:20:03 +01:00
Max Kellermann
7886a14b74 decoder/opus: fix mistyped LoadEOSPacket() return value 2014-11-11 11:18:51 +01:00
Max Kellermann
466b6a23cd decoder/opus: eliminate flag "found_opus"
Check opus_decoder!=nullptr instead.
2014-11-11 08:30:11 +01:00
Max Kellermann
4a04f73434 decoder/opus: add constexpr output_buffer_frames 2014-11-11 07:45:31 +01:00
Misty De Meo
134cb6a017 Main: fix compilation on OS X using non-Apple compilers
Commit d42c0f1dc5 added an OS X-specific
method of calling mpd_main_after_fork(), which uses Grand Central
Dispatch. Since this uses a block literal, it breaks compilation on
compilers which don't support the block extension, e.g. non-Apple
compilers. This affects users on older OS X releases with GCD (which
depend on older Clang releases, or Apple GCCs, which don't support the
C++11 features MPD needs); or which don't support GCD at all (10.5 and
lower).

This patch changes the #ifdef so that the non-GCD code is used
as it was on OS X before this patch if blocks aren't available, via
checking __BLOCKS__ macro.
2014-11-11 06:22:24 +01:00
Max Kellermann
8d036c4b7c pcm/SoxrResampler: round output buffer size up
The old formula calculates the output buffer size with "regular"
rounding (to the nearest integer), however sometimes, that is
insufficient and the last sample cannot be resampled.  This causes
audible distortions.  By changing the formula to consider the worst
case (always round up), this problem is eliminated.
2014-11-10 22:52:17 +01:00
Max Kellermann
c64ad78c7b decoder/ffmpeg: support opus 2014-11-10 18:00:30 +01:00
Max Kellermann
4a043a915f configure.ac: prepare for 0.18.1 2014-11-10 17:59:06 +01:00
Max Kellermann
8ff0d99092 decoder/audiofile: fix bit rate calculation 2014-11-10 09:00:50 +01:00
Max Kellermann
2e47cb12c4 test/FakeDecoderAPI: dump bit rate 2014-11-10 09:00:38 +01:00
Max Kellermann
ff6f1655f0 input/curl: ignore ResponseBoundary() while seeking
While seeking, metadata must not be updated.  ResponseBoundary() was
added in MPD 0.19.1, but I forgot to add the IsSeeking() check there.
This caused the "seekable" flag to reset.
2014-11-10 08:45:19 +01:00
Max Kellermann
b5ba94f1de tag/Set: do AlbumArtist/Artist fallback only if AlbumArtist is not disabled
On "list albumartist", songs that have no AlbumArtist tag will use the
Artist tag.  However, if AlbumArtist is disabled via
"metadata_to_use", the TagBuilder::AddItem() call is ignored, and
PrintUniqueTag() attempts to print a nullptr string.

This commit fixes the problem by attempting the fallback only if
AlbumArtist is not disabled.
2014-11-08 19:25:01 +01:00
Max Kellermann
cbf79769d3 db/Count: include cleanup 2014-11-08 19:21:42 +01:00
Max Kellermann
125eb01e03 decoder/ffmpeg: support opus 2014-11-07 19:22:26 +01:00
Max Kellermann
ccb13205f4 db/upnp: fix valgrind warning 2014-11-07 19:12:43 +01:00
Max Kellermann
6f23e91e33 lib/upnp/ContentDirectoryService: swap uri_apply_base() parameters
When uri_apply_base() was moved from db/upnp/Util.cpp to
util/UriUtil.cpp, the parameter order was changed, however without
swapping the parameters in the ContentDirectoryService constructor.
2014-11-07 18:43:00 +01:00
Max Kellermann
1bd8a322f5 input/AsyncInputStream: set Error when seeking unseekable
Fixes crash in the "audiofile" decoder while logging the seek error.
2014-11-07 13:57:57 +01:00
Max Kellermann
362e73bea8 input/Open: expose input_domain 2014-11-07 13:57:57 +01:00
Max Kellermann
9f8c2b3b56 configure.ac: prepare for 0.19.3 2014-11-04 09:24:09 +01:00
Max Kellermann
6a7f6cdacd release v0.19.2 2014-11-02 13:46:32 +01:00
Max Kellermann
5715342fe0 Merge tag 'v0.18.17' into v0.19.x 2014-11-02 13:46:09 +01:00
Max Kellermann
38a0d15190 release v0.18.17 2014-11-02 13:06:20 +01:00
Max Kellermann
56f763a4a8 input/curl: forget Content-Length (and more) after redirect
Fixes playback of redirected streams.
2014-11-02 13:00:28 +01:00
Max Kellermann
a2eb14f3b3 AsyncInputStream: add method ClearTag() 2014-11-02 13:00:28 +01:00
Max Kellermann
05c63af7c4 InputStream: add method ClearMimeType() 2014-11-02 12:59:16 +01:00
Max Kellermann
1f59701c46 Merge branch 'v0.18.x' into v0.19.x 2014-11-02 12:27:46 +01:00
Max Kellermann
ec3191f502 input/curl: fix curl_easy_setopt() parameter types 2014-11-02 11:55:48 +01:00
Max Kellermann
32b5654a6e Decoder, Playlist: ignore URI query string for plugin detection
Use the new uri_get_suffix() overload that removes the query string.
2014-11-02 11:54:26 +01:00
Max Kellermann
674091424e util/UriUtil: add uri_get_suffix() overload that ignores query string 2014-11-02 11:53:31 +01:00
Max Kellermann
6ad336743d PlaylistFile: don't allow empty playlist name 2014-11-02 11:52:48 +01:00
Max Kellermann
c882568ccd playlist/m3u: recognize the file suffix ".m3u8" 2014-11-02 11:50:56 +01:00
Max Kellermann
f6b2899dd2 decoder/faad: remove workaround for ancient libfaad2 ABI bug
Many years ago, FAAD had a serious ABI bug: the NeAACDecInit()
prototype in its header declared the "samplerate" parameter to be
"unsigned long *", but internally, the function assumed it was
"uint32_t *" instead.  On 32 bit machines, that was no difference, but
on 64 bit, this left one portion of the return value uninitialized;
and worse, on big-endian, the wrong word was filled.  This bug had to
be worked around in MPD (commit 9c4e97a6).

A few months later, the bug was fixed in the FAAD CVS in commit 1.117
on file libfaad/decoder.c; the commit message was:

 "Use public headers internally to prevent duplicate declarations"

The commit message was too brief at best; the problem was not
duplicate declarations, but a prototype mismatch.  No mention of the
bug fix in the ChangeLog.

The MPD project never learned about this bug fix, and so MPD would
always pass a "uin32_t *" dressed up as a "unsigned long *".  Nearly 6
years later, it's about time to fix this second ABI problem.  Let's
kill the workaround!
2014-11-02 11:50:56 +01:00
Steven OBrien
bccd4ef2f7 decoder/ffmpeg: recognize MIME type audio/aacp 2014-11-02 11:50:56 +01:00
Max Kellermann
94c240a026 configure.ac: show DSD in result 2014-11-02 11:50:56 +01:00
Max Kellermann
c50a0cf7bf output/roar: remove unnecessary "volatile" keyword
A mutex acts as a memory barrier, and thus "volatile" is not
necessary.
2014-11-02 11:50:56 +01:00
Max Kellermann
c37f7abb79 TagString: use g_strndup() for unterminated string
Fixes buffer overflow bug.
2014-11-02 11:48:13 +01:00
Max Kellermann
432ce9b1de configure.ac: prepare for 0.18.17 2014-11-02 11:41:40 +01:00
Max Kellermann
054323c2bc lib/upnp/Discovery: add missing stdlib.h include 2014-11-02 11:04:13 +01:00
Max Kellermann
a8770aa606 input/curl: fix curl_easy_setopt() parameter types 2014-11-01 14:09:30 +01:00
Max Kellermann
7d5442e103 Decoder, Playlist: ignore URI query string for plugin detection
Use the new uri_get_suffix() overload that removes the query string.
2014-11-01 13:41:18 +01:00
Max Kellermann
eab32f2e5d util/UriUtil: add uri_get_suffix() overload that ignores query string 2014-11-01 12:45:47 +01:00
NanoTech
d42c0f1dc5 Main: run the OS X native event loop after forking 2014-10-31 15:03:53 +01:00
NanoTech
6ad1e4d99a Revert "Main: run the OS X native event loop"
This reverts commit f0be48ff90
(except for the NEWS entry).

If libdispatch (GCD) is used before forking, it
can't safely be used again after forking.
2014-10-31 15:03:46 +01:00
Max Kellermann
7350144ab3 PlaylistFile: don't allow empty playlist name 2014-10-31 14:59:27 +01:00
Max Kellermann
54c591bd9d decoder/mad: fix negative replay gain values
Negating an unsigned integer does not work.
2014-10-28 22:22:30 +01:00
Max Kellermann
217d88f21f TextInputStream: don't ignore unterminated last line 2014-10-28 22:10:47 +01:00
Max Kellermann
394e3be482 playlist/m3u: recognize the file suffix ".m3u8" 2014-10-26 08:14:16 +01:00
Max Kellermann
d7f024c510 OutputThread: fall back to PCM if given DSD sample rate is not supported
Works around the "PCM conversion from f to dsd is not implemented"
error message that prevents DSD playback.
2014-10-25 22:06:08 +02:00
Max Kellermann
bc5a53574c decoder/faad: remove workaround for ancient libfaad2 ABI bug
Many years ago, FAAD had a serious ABI bug: the NeAACDecInit()
prototype in its header declared the "samplerate" parameter to be
"unsigned long *", but internally, the function assumed it was
"uint32_t *" instead.  On 32 bit machines, that was no difference, but
on 64 bit, this left one portion of the return value uninitialized;
and worse, on big-endian, the wrong word was filled.  This bug had to
be worked around in MPD (commit 9c4e97a6).

A few months later, the bug was fixed in the FAAD CVS in commit 1.117
on file libfaad/decoder.c; the commit message was:

 "Use public headers internally to prevent duplicate declarations"

The commit message was too brief at best; the problem was not
duplicate declarations, but a prototype mismatch.  No mention of the
bug fix in the ChangeLog.

The MPD project never learned about this bug fix, and so MPD would
always pass a "uin32_t *" dressed up as a "unsigned long *".  Nearly 6
years later, it's about time to fix this second ABI problem.  Let's
kill the workaround!
2014-10-25 20:42:50 +02:00
Max Kellermann
30df709736 configure.ac: update VERSION_REVISION 2014-10-25 00:33:25 +02:00
Steven OBrien
8cd17ce045 decoder/ffmpeg: recognize MIME type audio/aacp 2014-10-25 00:26:58 +02:00
Max Kellermann
1bfbced258 configure.ac: add storage plugin section to result 2014-10-25 00:21:18 +02:00
Max Kellermann
6ac5980a17 configure.ac: show DSD in result 2014-10-25 00:19:01 +02:00
Max Kellermann
2e24adae89 configure.ac: require xmlto for --enable-documentation 2014-10-25 00:14:25 +02:00
Max Kellermann
188b94cb3e test/test_archive: don't use GLib 2014-10-25 00:08:04 +02:00
Max Kellermann
c48733e34f fs/Charset: work around clang's -Wunused-const-variable
MPD_PATH_MAX_UTF8 is only used by GLib-specific code currently.
2014-10-25 00:07:25 +02:00
Max Kellermann
f36db9bb04 configure.ac: auto-disable plugins that require GLib when --disable-glib is used 2014-10-24 23:46:20 +02:00
Max Kellermann
30dd29e251 configure.ac: improve database dependency checks
Abort if --enable-libmpdclient or --enable-upnp are used with
--disable-database, instead of ignoring the mismatch silently.
2014-10-24 23:43:21 +02:00
Max Kellermann
6cf1acfb48 test/DumpDatabase, ...: no g_thread_init() calls when GLib is disabled 2014-10-24 18:30:30 +02:00
Max Kellermann
a7b09d3d1c OutputThread: close the output plugin after filter failure
Fixes memory leak because ao_plugin_close() never gets called.
2014-10-24 00:35:16 +02:00
Max Kellermann
8fc3768166 OutputThread: unlock mutex for CloseFilter()
Be consistent.
2014-10-24 00:29:03 +02:00
Max Kellermann
b07bddf742 output/roar: remove unnecessary "volatile" keyword
A mutex acts as a memory barrier, and thus "volatile" is not
necessary.
2014-10-23 23:29:56 +02:00
Max Kellermann
220f957cd8 pcm/ChannelsConverter: fix variable used to generate error message
Use the "_format" parameter instead of the (uninitialized) "format"
attribute.
2014-10-23 22:44:53 +02:00
Max Kellermann
8ce48d83eb pcm/FormatConverter: move check to Open()
Report unsupported format while opening the filter, not later when the
first conversion takes place.
2014-10-23 22:42:08 +02:00
Max Kellermann
200cdb6b0a pcm/PcmConvert: assign {src,dest}_format at the end
Fixes assertion failure in destructor by not assigning
{src,dest}_format when an error occurs.
2014-10-23 22:39:51 +02:00
Max Kellermann
d9fb40203a pcm/PcmConvert: make AudioFormat parameters "const" 2014-10-23 22:39:51 +02:00
Max Kellermann
2d9e972195 configure.ac: prepare for 0.19.2 2014-10-23 21:57:04 +02:00
53 changed files with 510 additions and 174 deletions

@@ -130,7 +130,6 @@ libmpd_a_SOURCES = \
src/IOThread.cxx src/IOThread.hxx \
src/Instance.cxx src/Instance.hxx \
src/win32/Win32Main.cxx \
src/osx/OSXMain.cxx \
src/GlobalEvents.cxx src/GlobalEvents.hxx \
src/MixRampInfo.hxx \
src/MusicBuffer.cxx src/MusicBuffer.hxx \
@@ -1113,6 +1112,7 @@ endif
#
libinput_a_SOURCES = \
src/input/Domain.cxx src/input/Domain.hxx \
src/input/Init.cxx src/input/Init.hxx \
src/input/Registry.cxx src/input/Registry.hxx \
src/input/Open.cxx \
@@ -2141,19 +2141,11 @@ user_DATA = $(wildcard doc/user/*.html)
developerdir = $(docdir)/developer
developer_DATA = $(wildcard doc/developer/*.html)
if HAVE_XMLTO
DOCBOOK_HTML = $(patsubst %.xml,%/index.html,$(DOCBOOK_FILES))
$(DOCBOOK_HTML): %/index.html: %.xml
$(XMLTO) -o $(@D) --stringparam chunker.output.encoding=utf-8 html --stringparam use.id.as.filename=1 $<
else
DOCBOOK_HTML =
endif
doc/api/html/index.html: doc/doxygen.conf
@$(MKDIR_P) $(@D)
$(DOXYGEN) $<

59
NEWS

@@ -1,3 +1,47 @@
ver 0.19.4 (2014/11/18)
* protocol
- workaround for buggy clients that send "add /"
* decoder
- ffmpeg: support opus
- opus: add MIME types audio/ogg and application/ogg
* fix crash on failed filename charset conversion
* fix local socket detection from uid=0 (root)
ver 0.19.3 (2014/11/11)
* protocol
- fix "(null)" result string to "list" when AlbumArtist is disabled
* database
- upnp: fix breakage due to malformed URIs
* input
- curl: another fix for redirected streams
* decoder
- audiofile: fix crash while playing streams
- audiofile: fix bit rate calculation
- ffmpeg: support opus
- opus: fix bogus duration on streams
- opus: support chained streams
- opus: improved error logging
* fix distorted audio with soxr resampler
* fix build failure on Mac OS X with non-Apple compilers
ver 0.19.2 (2014/11/02)
* input
- curl: fix redirected streams
* playlist
- don't allow empty playlist name
- m3u: don't ignore unterminated last line
- m3u: recognize the file suffix ".m3u8"
* decoder
- ignore URI query string for plugin detection
- faad: remove workaround for ancient libfaad2 ABI bug
- ffmpeg: recognize MIME type audio/aacp
- mad: fix negative replay gain values
* output
- fix memory leak after filter initialization error
- fall back to PCM if given DSD sample rate is not supported
* fix assertion failure on unsupported PCM conversion
* auto-disable plugins that require GLib when --disable-glib is used
ver 0.19.1 (2014/10/19)
* input
- mms: fix deadlock bug
@@ -85,6 +129,21 @@ ver 0.19 (2014/10/10)
* install systemd unit for socket activation
* Android port
ver 0.18.18 (2014/11/18)
* decoder
- ffmpeg: support opus
* fix crash on failed filename charset conversion
* fix local socket detection from uid=0 (root)
ver 0.18.17 (2014/11/02)
* playlist
- don't allow empty playlist name
- m3u: recognize the file suffix ".m3u8"
* decoder
- ignore URI query string for plugin detection
- faad: remove workaround for ancient libfaad2 ABI bug
- ffmpeg: recognize MIME type audio/aacp
ver 0.18.16 (2014/09/26)
* fix DSD breakage due to typo in configure.ac

@@ -1,10 +1,10 @@
AC_PREREQ(2.60)
AC_INIT(mpd, 0.19.1, musicpd-dev-team@lists.sourceforge.net)
AC_INIT(mpd, 0.19.4, musicpd-dev-team@lists.sourceforge.net)
VERSION_MAJOR=0
VERSION_MINOR=19
VERSION_REVISION=0
VERSION_REVISION=4
VERSION_EXTRA=0
AC_CONFIG_SRCDIR([src/Main.cxx])
@@ -293,7 +293,9 @@ fi
AC_ARG_ENABLE(libmpdclient,
AS_HELP_STRING([--enable-libmpdclient],
[enable support for the MPD client]),,
enable_libmpdclient=$database_auto)
enable_libmpdclient=auto)
MPD_DEPENDS([enable_libmpdclient], [enable_database],
[Cannot use --enable-libmpdclient with --disable-database])
AC_ARG_ENABLE(expat,
AS_HELP_STRING([--enable-expat],
@@ -303,7 +305,9 @@ AC_ARG_ENABLE(expat,
AC_ARG_ENABLE(upnp,
AS_HELP_STRING([--enable-upnp],
[enable UPnP client support (default: auto)]),,
enable_upnp=$database_auto)
enable_upnp=auto)
MPD_DEPENDS([enable_upnp], [enable_database],
[Cannot use --enable-upnp with --disable-database])
AC_ARG_ENABLE(adplug,
AS_HELP_STRING([--enable-adplug],
@@ -323,6 +327,8 @@ AC_ARG_ENABLE(ao,
AS_HELP_STRING([--enable-ao],
[enable support for libao]),,
enable_ao=auto)
MPD_DEPENDS([enable_ao], [enable_glib],
[Cannot use --enable-ao with --disable-glib])
AC_ARG_ENABLE(audiofile,
AS_HELP_STRING([--enable-audiofile],
@@ -343,6 +349,8 @@ AC_ARG_ENABLE(cdio-paranoia,
AS_HELP_STRING([--enable-cdio-paranoia],
[enable support for audio CD support]),,
enable_cdio_paranoia=auto)
MPD_DEPENDS([enable_cdio_paranoia], [enable_glib],
[Cannot use --enable-cdio-paranoia with --disable-glib])
AC_ARG_ENABLE(curl,
AS_HELP_STRING([--enable-curl],
@@ -398,11 +406,15 @@ AC_ARG_ENABLE(gme,
AS_HELP_STRING([--enable-gme],
[enable Blargg's game music emulator plugin]),,
enable_gme=auto)
MPD_DEPENDS([enable_gme], [enable_glib],
[Cannot use --enable-gme with --disable-glib])
AC_ARG_ENABLE(httpd-output,
AS_HELP_STRING([--enable-httpd-output],
[enables the HTTP server output]),,
[enable_httpd_output=auto])
MPD_DEPENDS([enable_httpd_output], [enable_glib],
[Cannot use --enable-httpd-output with --disable-glib])
AC_ARG_ENABLE(id3,
AS_HELP_STRING([--enable-id3],
@@ -428,6 +440,8 @@ AC_ARG_ENABLE(jack,
AS_HELP_STRING([--enable-jack],
[enable jack support]),,
enable_jack=auto)
MPD_DEPENDS([enable_jack], [enable_glib],
[Cannot use --enable-jack with --disable-glib])
AC_SYS_LARGEFILE
@@ -440,6 +454,8 @@ AC_ARG_ENABLE(soundcloud,
AS_HELP_STRING([--enable-soundcloud],
[enable support for soundcloud.com]),,
[enable_soundcloud=auto])
MPD_DEPENDS([enable_soundcloud], [enable_glib],
[Cannot use --enable-soundcloud with --disable-glib])
AC_ARG_ENABLE(lame-encoder,
AS_HELP_STRING([--enable-lame-encoder],
@@ -534,6 +550,8 @@ AC_ARG_ENABLE(sidplay,
AS_HELP_STRING([--enable-sidplay],
[enable C64 SID support via libsidplay2]),,
enable_sidplay=auto)
MPD_DEPENDS([enable_sidplay], [enable_glib],
[Cannot use --enable-sidplay with --disable-glib])
AC_ARG_ENABLE(shine-encoder,
AS_HELP_STRING([--enable-shine-encoder],
@@ -559,6 +577,8 @@ AC_ARG_ENABLE(sqlite,
AS_HELP_STRING([--enable-sqlite],
[enable support for the SQLite database]),,
[enable_sqlite=$database_auto])
MPD_DEPENDS([enable_sqlite], [enable_glib],
[Cannot use --enable-sqlite with --disable-glib])
AC_ARG_ENABLE(systemd-daemon,
AS_HELP_STRING([--enable-systemd-daemon],
@@ -599,6 +619,8 @@ AC_ARG_ENABLE(vorbis-encoder,
AS_HELP_STRING([--enable-vorbis-encoder],
[enable the Ogg Vorbis encoder]),,
[enable_vorbis_encoder=auto])
MPD_DEPENDS([enable_vorbis_encoder], [enable_glib],
[Cannot use --enable-vorbis-encoder with --disable-glib])
AC_ARG_ENABLE(wave-encoder,
AS_HELP_STRING([--enable-wave-encoder],
@@ -609,6 +631,8 @@ AC_ARG_ENABLE(wavpack,
AS_HELP_STRING([--enable-wavpack],
[enable WavPack support]),,
enable_wavpack=auto)
MPD_DEPENDS([enable_wavpack], [enable_glib],
[Cannot use --enable-wavpack with --disable-glib])
AC_ARG_ENABLE(werror,
AS_HELP_STRING([--enable-werror],
@@ -1709,8 +1733,11 @@ dnl Documentation
dnl ---------------------------------------------------------------------------
if test x$enable_documentation = xyes; then
AC_PATH_PROG(XMLTO, xmlto)
if test x$XMLTO = x; then
AC_MSG_ERROR([xmlto not found])
fi
AC_SUBST(XMLTO)
AM_CONDITIONAL(HAVE_XMLTO, test x$XMLTO != x)
AC_PATH_PROG(DOXYGEN, doxygen)
if test x$DOXYGEN = x; then
@@ -1718,8 +1745,6 @@ if test x$enable_documentation = xyes; then
fi
AC_SUBST(DOXYGEN)
else
AM_CONDITIONAL(HAVE_XMLTO, false)
fi
AM_CONDITIONAL(ENABLE_DOCUMENTATION, test x$enable_documentation = xyes)
@@ -1826,9 +1851,14 @@ results(ipv6, "IPv6")
results(tcp, "TCP")
results(un,[UNIX Domain Sockets])
printf '\nStorage support:\n\t'
results(nfs, [NFS])
results(smbclient, [SMB])
printf '\nFile format support:\n\t'
results(aac, [AAC])
results(adplug, [AdPlug])
results(dsd, [DSD])
results(sidplay, [C64 SID])
results(ffmpeg, [FFMPEG])
results(flac, [FLAC])

@@ -62,36 +62,7 @@ int main() {
CPPFLAGS=$oldcppflags
fi
if test x$enable_aac = xyes; then
oldcflags=$CFLAGS
oldlibs=$LIBS
oldcppflags=$CPPFLAGS
CFLAGS="$CFLAGS $FAAD_CFLAGS -Werror"
LIBS="$LIBS $FAAD_LIBS"
CPPFLAGS=$CFLAGS
AC_MSG_CHECKING(for broken libfaad headers)
AC_COMPILE_IFELSE([AC_LANG_SOURCE([
#include <faad.h>
#include <stddef.h>
#include <stdint.h>
int main() {
unsigned char channels;
uint32_t sample_rate;
NeAACDecInit2(NULL, NULL, 0, &sample_rate, &channels);
return 0;
}
])],
[AC_MSG_RESULT(correct)],
[AC_MSG_RESULT(broken);
AC_DEFINE(HAVE_FAAD_LONG, 1, [Define if faad.h uses the broken "unsigned long" pointers])])
CFLAGS=$oldcflags
LIBS=$oldlibs
CPPFLAGS=$oldcppflags
else
if test x$enable_aac = xno; then
FAAD_LIBS=""
FAAD_CFLAGS=""
fi

9
m4/mpd_depends.m4 Normal file

@@ -0,0 +1,9 @@
AC_DEFUN([MPD_DEPENDS], [
if test x$$2 = xno; then
if test x$$1 = xauto; then
$1=no
elif test x$$1 = xyes; then
AC_MSG_ERROR([$3])
fi
fi
])

@@ -114,6 +114,10 @@
#include <ws2tcpip.h>
#endif
#ifdef __BLOCKS__
#include <dispatch/dispatch.h>
#endif
#include <limits.h>
static constexpr unsigned DEFAULT_BUFFER_SIZE = 4096;
@@ -401,8 +405,6 @@ int main(int argc, char *argv[])
{
#ifdef WIN32
return win32_main(argc, argv);
#elif __APPLE__
return osx_main(argc, argv);
#else
return mpd_main(argc, argv);
#endif
@@ -410,6 +412,8 @@ int main(int argc, char *argv[])
#endif
static int mpd_main_after_fork(struct options);
#ifdef ANDROID
static inline
#endif
@@ -513,6 +517,27 @@ int mpd_main(int argc, char *argv[])
daemonize_begin(options.daemon);
#endif
#ifdef __BLOCKS__
/* Runs the OS X native event loop in the main thread, and runs
the rest of mpd_main on a new thread. This lets CoreAudio receive
route change notifications (e.g. plugging or unplugging headphones).
All hardware output on OS X ultimately uses CoreAudio internally.
This must be run after forking; if dispatch is called before forking,
the child process will have a broken internal dispatch state. */
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
exit(mpd_main_after_fork(options));
});
dispatch_main();
return EXIT_FAILURE; // unreachable, because dispatch_main never returns
#else
return mpd_main_after_fork(options);
#endif
}
static int mpd_main_after_fork(struct options options)
{
Error error;
GlobalEvents::Initialize(*instance->event_loop);
GlobalEvents::Register(GlobalEvents::IDLE, idle_event_emitted);
#ifdef WIN32

@@ -75,15 +75,4 @@ win32_app_stopping(void);
#endif
#ifdef __APPLE__
/* Runs the OS X native event loop in the main thread, and runs
* mpd_main on a new thread. This lets CoreAudio receive route
* change notifications (e.g. plugging or unplugging headphones).
* All hardware output on OS X ultimately uses CoreAudio internally.
*/
int osx_main(int argc, char *argv[]);
#endif
#endif

@@ -64,6 +64,10 @@ spl_global_init(void)
bool
spl_valid_name(const char *name_utf8)
{
if (*name_utf8 == 0)
/* empty name not allowed */
return false;
/*
* Not supporting '/' was done out of laziness, and we should
* really strive to support it in the future.

@@ -46,7 +46,8 @@ tag_stream_scan(InputStream &is, const tag_handler &handler, void *ctx)
{
assert(is.IsReady());
const char *const suffix = uri_get_suffix(is.GetURI());
UriSuffixBuffer suffix_buffer;
const char *const suffix = uri_get_suffix(is.GetURI(), suffix_buffer);
const char *const mime = is.GetMimeType();
if (suffix == nullptr && mime == nullptr)

@@ -127,7 +127,7 @@ public:
* a local (UNIX domain) socket?
*/
bool IsLocal() const {
return uid > 0;
return uid >= 0;
}
unsigned GetPermission() const {

@@ -41,7 +41,7 @@ Client::AllowFile(Path path_fs, Error &error) const
instance */
return true;
if (uid <= 0) {
if (uid < 0) {
/* unauthenticated client */
error.Set(ack_domain, ACK_ERROR_PERMISSION, "Access denied");
return false;

@@ -61,7 +61,16 @@ translate_uri(Client &client, const char *uri)
CommandResult
handle_add(Client &client, gcc_unused unsigned argc, char *argv[])
{
const char *const uri = translate_uri(client, argv[1]);
const char *uri = argv[1];
if (memcmp(uri, "/", 2) == 0)
/* this URI is malformed, but some clients are buggy
and use "add /" to add the whole database, which
was never intended to work, but once did; in order
to retain backwards compatibility, work around this
here */
uri = "";
uri = translate_uri(client, uri);
if (uri == nullptr)
return CommandResult::ERROR;

@@ -23,7 +23,7 @@
#include "Interface.hxx"
#include "client/Client.hxx"
#include "LightSong.hxx"
#include "tag/Set.hxx"
#include "tag/Tag.hxx"
#include <functional>
#include <map>

@@ -129,6 +129,7 @@ public:
state(NONE),
tag_type(TAG_NUM_OF_ITEM_TYPES)
{
m_tobj.clear();
}
protected:

@@ -237,7 +237,8 @@ static bool
decoder_run_stream_locked(Decoder &decoder, InputStream &is,
const char *uri, bool &tried_r)
{
const char *const suffix = uri_get_suffix(uri);
UriSuffixBuffer suffix_buffer;
const char *const suffix = uri_get_suffix(uri, suffix_buffer);
using namespace std::placeholders;
const auto f = std::bind(decoder_run_stream_plugin,

@@ -209,7 +209,7 @@ audiofile_stream_decode(Decoder &decoder, InputStream &is)
const auto total_time = audiofile_get_duration(fh);
const uint16_t kbit_rate = (uint16_t)
(is.GetSize() * uint64_t(8000) / total_time.ToMS());
(is.GetSize() * uint64_t(8) / total_time.ToMS());
const unsigned frame_size = (unsigned)
afGetVirtualFrameSize(fh, AF_DEFAULT_TRACK, true);

@@ -255,20 +255,12 @@ faad_decoder_init(NeAACDecHandle decoder, DecoderBuffer &buffer,
}
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
unsigned long sample_rate;
long nbytes = NeAACDecInit(decoder,
/* deconst hack, libfaad requires this */
const_cast<unsigned char *>(data.data),
data.size,
sample_rate_p, &channels);
&sample_rate, &channels);
if (nbytes < 0) {
error.Set(faad_decoder_domain, "Not an AAC stream");
return false;

@@ -657,7 +657,7 @@ static const char *const ffmpeg_suffixes[] = {
"mj2", "mjpeg", "mjpg", "mka", "mkv", "mlp", "mm", "mmf", "mov", "mp+",
"mp1", "mp2", "mp3", "mp4", "mpc", "mpeg", "mpg", "mpga", "mpp", "mpu",
"mve", "mvi", "mxf", "nc", "nsv", "nut", "nuv", "oga", "ogm", "ogv",
"ogx", "oma", "ogg", "omg", "psp", "pva", "qcp", "qt", "r3d", "ra",
"ogx", "oma", "ogg", "omg", "opus", "psp", "pva", "qcp", "qt", "r3d", "ra",
"ram", "rl2", "rm", "rmvb", "roq", "rpl", "rvc", "shn", "smk", "snd",
"sol", "son", "spx", "str", "swf", "tgi", "tgq", "tgv", "thp", "ts",
"tsp", "tta", "xa", "xvid", "uv", "uv2", "vb", "vid", "vob", "voc",
@@ -680,6 +680,7 @@ static const char *const ffmpeg_mime_types[] = {
"audio/8svx",
"audio/16sv",
"audio/aac",
"audio/aacp",
"audio/ac3",
"audio/aiff"
"audio/amr",
@@ -690,6 +691,7 @@ static const char *const ffmpeg_mime_types[] = {
"audio/mpeg",
"audio/musepack",
"audio/ogg",
"audio/opus",
"audio/qcelp",
"audio/vorbis",
"audio/vorbis+ogg",

@@ -657,7 +657,7 @@ parse_lame(struct lame *lame, struct mad_bitptr *ptr, int *bitlen)
unsigned name = mad_bit_read(ptr, 3); /* gain name */
unsigned orig = mad_bit_read(ptr, 3); /* gain originator */
unsigned sign = mad_bit_read(ptr, 1); /* sign bit */
unsigned gain = mad_bit_read(ptr, 9); /* gain*10 */
int gain = mad_bit_read(ptr, 9); /* gain*10 */
if (gain && name == 1 && orig != 0) {
lame->track_gain = ((sign ? -gain : gain) / 10.0) + adj;
FormatDebug(mad_domain, "LAME track gain found: %f",

@@ -39,15 +39,7 @@ static MP4TrackId
mp4_get_aac_track(MP4FileHandle handle, NeAACDecHandle decoder,
AudioFormat &audio_format, Error &error)
{
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_r = (unsigned long*)&sample_rate;
#else
uint32_t *sample_rate_r = sample_rate;
#endif
unsigned long sample_rate;
const MP4TrackId tracks = MP4GetNumberOfTracks(handle);
@@ -80,7 +72,7 @@ mp4_get_aac_track(MP4FileHandle handle, NeAACDecHandle decoder,
uint8_t channels;
int32_t nbytes = NeAACDecInit(decoder, buff, buff_size,
sample_rate_r, &channels);
&sample_rate, &channels);
free(buff);

@@ -40,6 +40,12 @@
static constexpr opus_int32 opus_sample_rate = 48000;
/**
* Allocate an output buffer for 16 bit PCM samples big enough to hold
* a quarter second, larger than 120ms required by libopus.
*/
static constexpr unsigned opus_output_buffer_frames = opus_sample_rate / 4;
gcc_pure
static bool
IsOpusHead(const ogg_packet &packet)
@@ -70,10 +76,16 @@ class MPDOpusDecoder {
OpusDecoder *opus_decoder;
opus_int16 *output_buffer;
unsigned output_size;
/**
* If non-zero, then a previous Opus stream has been found
* already with this number of channels. If opus_decoder is
* nullptr, then its end-of-stream packet has been found
* already.
*/
unsigned previous_channels;
bool os_initialized;
bool found_opus;
int opus_serialno;
@@ -86,8 +98,9 @@ public:
InputStream &_input_stream)
:decoder(_decoder), input_stream(_input_stream),
opus_decoder(nullptr),
output_buffer(nullptr), output_size(0),
os_initialized(false), found_opus(false) {}
output_buffer(nullptr),
previous_channels(0),
os_initialized(false) {}
~MPDOpusDecoder();
bool ReadFirstPage(OggSyncState &oy);
@@ -96,6 +109,7 @@ public:
DecoderCommand HandlePackets();
DecoderCommand HandlePacket(const ogg_packet &packet);
DecoderCommand HandleBOS(const ogg_packet &packet);
DecoderCommand HandleEOS();
DecoderCommand HandleTags(const ogg_packet &packet);
DecoderCommand HandleAudio(const ogg_packet &packet);
@@ -159,12 +173,14 @@ inline DecoderCommand
MPDOpusDecoder::HandlePacket(const ogg_packet &packet)
{
if (packet.e_o_s)
return DecoderCommand::STOP;
return HandleEOS();
if (packet.b_o_s)
return HandleBOS(packet);
else if (!found_opus)
else if (opus_decoder == nullptr) {
LogDebug(opus_domain, "BOS packet expected");
return DecoderCommand::STOP;
}
if (IsOpusTags(packet))
return HandleTags(packet);
@@ -184,7 +200,7 @@ LoadEOSPacket(InputStream &is, Decoder *decoder, int serialno,
/* we do this for local files only, because seeking
around remote files is expensive and not worth the
troubl */
return -1;
return false;
const auto old_offset = is.GetOffset();
@@ -225,19 +241,29 @@ MPDOpusDecoder::HandleBOS(const ogg_packet &packet)
{
assert(packet.b_o_s);
if (found_opus || !IsOpusHead(packet))
if (opus_decoder != nullptr || !IsOpusHead(packet)) {
LogDebug(opus_domain, "BOS packet must be OpusHead");
return DecoderCommand::STOP;
}
unsigned channels;
if (!ScanOpusHeader(packet.packet, packet.bytes, channels) ||
!audio_valid_channel_count(channels))
!audio_valid_channel_count(channels)) {
LogDebug(opus_domain, "Malformed BOS packet");
return DecoderCommand::STOP;
}
assert(opus_decoder == nullptr);
assert(output_buffer == nullptr);
assert((previous_channels == 0) == (output_buffer == nullptr));
if (previous_channels != 0 && channels != previous_channels) {
FormatWarning(opus_domain,
"Next stream has different channels (%u -> %u)",
previous_channels, channels);
return DecoderCommand::STOP;
}
opus_serialno = os.serialno;
found_opus = true;
/* TODO: parse attributes from the OpusHead (sample rate,
channels, ...) */
@@ -251,6 +277,13 @@ MPDOpusDecoder::HandleBOS(const ogg_packet &packet)
return DecoderCommand::STOP;
}
if (previous_channels != 0) {
/* decoder was already initialized by the previous
stream; skip the rest of this method */
LogDebug(opus_domain, "Found another stream");
return decoder_get_command(decoder);
}
eos_granulepos = LoadEOSGranulePos(input_stream, &decoder,
opus_serialno);
const auto duration = eos_granulepos >= 0
@@ -258,21 +291,36 @@ MPDOpusDecoder::HandleBOS(const ogg_packet &packet)
opus_sample_rate)
: SignedSongTime::Negative();
previous_channels = channels;
const AudioFormat audio_format(opus_sample_rate,
SampleFormat::S16, channels);
decoder_initialized(decoder, audio_format,
eos_granulepos > 0, duration);
frame_size = audio_format.GetFrameSize();
/* allocate an output buffer for 16 bit PCM samples big enough
to hold a quarter second, larger than 120ms required by
libopus */
output_size = audio_format.sample_rate / 4;
output_buffer = new opus_int16[output_size * audio_format.channels];
output_buffer = new opus_int16[opus_output_buffer_frames
* audio_format.channels];
return decoder_get_command(decoder);
}
inline DecoderCommand
MPDOpusDecoder::HandleEOS()
{
if (eos_granulepos < 0 && previous_channels != 0) {
/* allow chaining of (unseekable) streams */
assert(opus_decoder != nullptr);
assert(output_buffer != nullptr);
opus_decoder_destroy(opus_decoder);
opus_decoder = nullptr;
return decoder_get_command(decoder);
}
return DecoderCommand::STOP;
}
inline DecoderCommand
MPDOpusDecoder::HandleTags(const ogg_packet &packet)
{
@@ -304,10 +352,11 @@ MPDOpusDecoder::HandleAudio(const ogg_packet &packet)
int nframes = opus_decode(opus_decoder,
(const unsigned char*)packet.packet,
packet.bytes,
output_buffer, output_size,
output_buffer, opus_output_buffer_frames,
0);
if (nframes < 0) {
LogError(opus_domain, opus_strerror(nframes));
FormatError(opus_domain, "libopus error: %s",
opus_strerror(nframes));
return DecoderCommand::STOP;
}
@@ -461,6 +510,13 @@ static const char *const opus_suffixes[] = {
};
static const char *const opus_mime_types[] = {
/* the official MIME type (RFC 5334) */
"audio/ogg",
/* deprecated (RFC 5334) */
"application/ogg",
/* deprecated; from an early draft */
"audio/opus",
nullptr
};

@@ -130,7 +130,7 @@ get_remote_uid(int fd)
socklen_t len = sizeof (cred);
if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &cred, &len) < 0)
return 0;
return -1;
return cred.uid;
#else

@@ -46,7 +46,11 @@ AllocatedPath
AllocatedPath::FromUTF8(const char *path_utf8)
{
#ifdef HAVE_GLIB
return AllocatedPath(Donate(), ::PathFromUTF8(path_utf8));
char *path = ::PathFromUTF8(path_utf8);
if (path == nullptr)
return AllocatedPath::Null();
return AllocatedPath(Donate(), path);
#else
return FromFS(path_utf8);
#endif

@@ -34,6 +34,8 @@
#include <assert.h>
#include <string.h>
#ifdef HAVE_GLIB
/**
* Maximal number of bytes required to represent path name in UTF-8
* (including nul-terminator).
@@ -44,7 +46,6 @@
*/
static constexpr size_t MPD_PATH_MAX_UTF8 = (MPD_PATH_MAX - 1) * 4 + 1;
#ifdef HAVE_GLIB
static std::string fs_charset;
gcc_pure

@@ -19,6 +19,7 @@
#include "config.h"
#include "AsyncInputStream.hxx"
#include "Domain.hxx"
#include "tag/Tag.hxx"
#include "event/Call.hxx"
#include "thread/Cond.hxx"
@@ -113,8 +114,10 @@ AsyncInputStream::Seek(offset_type new_offset, Error &error)
/* no-op */
return true;
if (!IsSeekable())
if (!IsSeekable()) {
error.Set(input_domain, "Not seekable");
return false;
}
/* check if we can fast-forward the buffer */

@@ -83,6 +83,10 @@ protected:
*/
void SetTag(Tag *_tag);
void ClearTag() {
SetTag(nullptr);
}
void Pause();
bool IsPaused() const {

@@ -18,20 +18,7 @@
*/
#include "config.h"
#include "Main.hxx"
#include "Domain.hxx"
#include "util/Domain.hxx"
#ifdef __APPLE__
#include <stdlib.h>
#include <dispatch/dispatch.h>
int osx_main(int argc, char *argv[])
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
exit(mpd_main(argc, argv));
});
dispatch_main();
return EXIT_FAILURE; // unreachable, because dispatch_main never returns
}
#endif
const Domain input_domain("input");

27
src/input/Domain.hxx Normal file

@@ -0,0 +1,27 @@
/*
* 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_INPUT_DOMAIN_HXX
#define MPD_INPUT_DOMAIN_HXX
class Domain;
extern const Domain input_domain;
#endif

@@ -200,6 +200,10 @@ public:
return mime.empty() ? nullptr : mime.c_str();
}
void ClearMimeType() {
mime.clear();
}
gcc_nonnull_all
void SetMimeType(const char *_mime) {
assert(!ready);

@@ -22,14 +22,13 @@
#include "Registry.hxx"
#include "InputPlugin.hxx"
#include "LocalOpen.hxx"
#include "Domain.hxx"
#include "plugins/RewindInputPlugin.hxx"
#include "fs/Traits.hxx"
#include "fs/Path.hxx"
#include "util/Error.hxx"
#include "util/Domain.hxx"
static constexpr Domain input_domain("input");
InputStream *
InputStream::Open(const char *url,
Mutex &mutex, Cond &cond,

@@ -38,8 +38,8 @@ TextInputStream::ReadLine()
while (true) {
auto dest = buffer.Write();
if (dest.size < 2) {
/* end of file (or line too long): terminate
the current line */
/* line too long: terminate the current
line */
assert(!dest.IsEmpty());
dest[0] = 0;
@@ -66,7 +66,19 @@ TextInputStream::ReadLine()
if (line != nullptr)
return line;
if (nbytes == 0)
return nullptr;
if (nbytes == 0) {
/* end of file: see if there's an unterminated
line */
dest = buffer.Write();
assert(!dest.IsEmpty());
dest[0] = 0;
auto r = buffer.Read();
buffer.Clear();
return r.IsEmpty()
? nullptr
: r.data;
}
}
}

@@ -109,6 +109,13 @@ struct CurlInputStream final : public AsyncInputStream {
*/
void FreeEasyIndirect();
/**
* Called when a new response begins. This is used to discard
* headers from previous responses (for example authentication
* and redirects).
*/
void ResponseBoundary();
void HeaderReceived(const char *name, std::string &&value);
size_t DataReceived(const void *ptr, size_t size);
@@ -597,6 +604,24 @@ CurlInputStream::~CurlInputStream()
FreeEasyIndirect();
}
inline void
CurlInputStream::ResponseBoundary()
{
/* undo all effects of HeaderReceived() because the previous
response was not applicable for this stream */
if (IsSeekPending())
/* don't update metadata while seeking */
return;
seekable = false;
size = UNKNOWN_SIZE;
ClearMimeType();
ClearTag();
// TODO: reset the IcyInputStream?
}
inline void
CurlInputStream::HeaderReceived(const char *name, std::string &&value)
{
@@ -645,6 +670,11 @@ input_curl_headerfunction(void *ptr, size_t size, size_t nmemb, void *stream)
size *= nmemb;
const char *header = (const char *)ptr;
if (size > 5 && memcmp(header, "HTTP/", 5) == 0) {
c.ResponseBoundary();
return size;
}
const char *end = header + size;
char name[64];
@@ -720,10 +750,10 @@ CurlInputStream::InitEasy(Error &error)
input_curl_writefunction);
curl_easy_setopt(easy, CURLOPT_WRITEDATA, this);
curl_easy_setopt(easy, CURLOPT_HTTP200ALIASES, http_200_aliases);
curl_easy_setopt(easy, CURLOPT_FOLLOWLOCATION, 1);
curl_easy_setopt(easy, CURLOPT_NETRC, 1);
curl_easy_setopt(easy, CURLOPT_MAXREDIRS, 5);
curl_easy_setopt(easy, CURLOPT_FAILONERROR, true);
curl_easy_setopt(easy, CURLOPT_FOLLOWLOCATION, 1l);
curl_easy_setopt(easy, CURLOPT_NETRC, 1l);
curl_easy_setopt(easy, CURLOPT_MAXREDIRS, 5l);
curl_easy_setopt(easy, CURLOPT_FAILONERROR, 1l);
curl_easy_setopt(easy, CURLOPT_ERRORBUFFER, error_buffer);
curl_easy_setopt(easy, CURLOPT_NOPROGRESS, 1l);
curl_easy_setopt(easy, CURLOPT_NOSIGNAL, 1l);

@@ -29,7 +29,7 @@
ContentDirectoryService::ContentDirectoryService(const UPnPDevice &device,
const UPnPService &service)
:m_actionURL(uri_apply_base(device.URLBase, service.controlURL)),
:m_actionURL(uri_apply_base(service.controlURL, device.URLBase)),
m_serviceType(service.serviceType),
m_deviceId(device.UDN),
m_friendlyName(device.friendlyName),

@@ -26,6 +26,7 @@
#include <upnp/upnptools.h>
#include <stdlib.h>
#include <string.h>
// The service type string we are looking for.

@@ -383,7 +383,12 @@ private:
void Reopen();
AudioFormat OpenFilter(AudioFormat &format, Error &error_r);
/**
* Mutex must not be locked.
*/
void CloseFilter();
void ReopenFilter();
/**

@@ -22,6 +22,7 @@
#include "OutputAPI.hxx"
#include "Domain.hxx"
#include "pcm/PcmMix.hxx"
#include "pcm/Domain.hxx"
#include "notify.hxx"
#include "filter/FilterInternal.hxx"
#include "filter/plugins/ConvertFilterPlugin.hxx"
@@ -165,6 +166,10 @@ AudioOutput::Open()
out_audio_format.ApplyMask(config_audio_format);
mutex.unlock();
const AudioFormat retry_audio_format = out_audio_format;
retry_without_dsd:
success = ao_plugin_open(this, out_audio_format, error);
mutex.lock();
@@ -174,7 +179,10 @@ AudioOutput::Open()
FormatError(error, "Failed to open \"%s\" [%s]",
name, plugin.name);
mutex.unlock();
CloseFilter();
mutex.lock();
fail_timer.Update();
return;
}
@@ -184,7 +192,36 @@ AudioOutput::Open()
FormatError(error, "Failed to convert for \"%s\" [%s]",
name, plugin.name);
mutex.unlock();
ao_plugin_close(this);
if (error.IsDomain(pcm_domain) &&
out_audio_format.format == SampleFormat::DSD) {
/* if the audio output supports DSD, but not
the given sample rate, it asks MPD to
resample; resampling DSD however is not
implemented; our last resort is to give up
DSD and fall back to PCM */
// TODO: clean up this workaround
FormatError(output_domain, "Retrying without DSD");
out_audio_format = retry_audio_format;
out_audio_format.format = SampleFormat::FLOAT;
/* clear the Error to allow reusing it */
error.Clear();
/* sorry for the "goto" - this is a workaround
for the stable branch that should be as
unintrusive as possible */
goto retry_without_dsd;
}
CloseFilter();
mutex.lock();
fail_timer.Update();
return;
}
@@ -233,7 +270,10 @@ AudioOutput::ReopenFilter()
{
Error error;
mutex.unlock();
CloseFilter();
mutex.lock();
const AudioFormat filter_audio_format =
OpenFilter(in_audio_format, error);
if (!filter_audio_format.IsDefined() ||

@@ -46,7 +46,7 @@ class RoarOutput {
struct roar_connection con;
struct roar_audio_info info;
mutable Mutex mutex;
volatile bool alive;
bool alive;
public:
RoarOutput()

@@ -43,7 +43,7 @@ PcmChannelsConverter::Open(SampleFormat _format,
default:
error.Format(pcm_domain,
"PCM channel conversion for %s is not implemented",
sample_format_to_string(format));
sample_format_to_string(_format));
return false;
}

@@ -28,11 +28,31 @@
bool
PcmFormatConverter::Open(SampleFormat _src_format, SampleFormat _dest_format,
gcc_unused Error &error)
Error &error)
{
assert(_src_format != SampleFormat::UNDEFINED);
assert(_dest_format != SampleFormat::UNDEFINED);
switch (_dest_format) {
case SampleFormat::UNDEFINED:
assert(false);
gcc_unreachable();
case SampleFormat::S8:
case SampleFormat::DSD:
error.Format(pcm_domain,
"PCM conversion from %s to %s is not implemented",
sample_format_to_string(_src_format),
sample_format_to_string(_dest_format));
return nullptr;
case SampleFormat::S16:
case SampleFormat::S24_P32:
case SampleFormat::S32:
case SampleFormat::FLOAT:
break;
}
src_format = _src_format;
dest_format = _dest_format;
return true;
@@ -48,20 +68,14 @@ PcmFormatConverter::Close()
}
ConstBuffer<void>
PcmFormatConverter::Convert(ConstBuffer<void> src, Error &error)
PcmFormatConverter::Convert(ConstBuffer<void> src, gcc_unused Error &error)
{
switch (dest_format) {
case SampleFormat::UNDEFINED:
assert(false);
gcc_unreachable();
case SampleFormat::S8:
case SampleFormat::DSD:
error.Format(pcm_domain,
"PCM conversion from %s to %s is not implemented",
sample_format_to_string(src_format),
sample_format_to_string(dest_format));
return nullptr;
assert(false);
gcc_unreachable();
case SampleFormat::S16:
return pcm_convert_to_16(buffer, dither,

@@ -51,7 +51,7 @@ PcmConvert::~PcmConvert()
}
bool
PcmConvert::Open(AudioFormat _src_format, AudioFormat _dest_format,
PcmConvert::Open(const AudioFormat _src_format, const AudioFormat _dest_format,
Error &error)
{
assert(!src_format.IsValid());
@@ -59,36 +59,34 @@ PcmConvert::Open(AudioFormat _src_format, AudioFormat _dest_format,
assert(_src_format.IsValid());
assert(_dest_format.IsValid());
src_format = _src_format;
dest_format = _dest_format;
AudioFormat format = src_format;
AudioFormat format = _src_format;
if (format.format == SampleFormat::DSD)
format.format = SampleFormat::FLOAT;
enable_resampler = format.sample_rate != dest_format.sample_rate;
enable_resampler = format.sample_rate != _dest_format.sample_rate;
if (enable_resampler) {
if (!resampler.Open(format, dest_format.sample_rate, error))
if (!resampler.Open(format, _dest_format.sample_rate, error))
return false;
format.format = resampler.GetOutputSampleFormat();
format.sample_rate = dest_format.sample_rate;
format.sample_rate = _dest_format.sample_rate;
}
enable_format = format.format != dest_format.format;
enable_format = format.format != _dest_format.format;
if (enable_format &&
!format_converter.Open(format.format, dest_format.format, error)) {
!format_converter.Open(format.format, _dest_format.format,
error)) {
if (enable_resampler)
resampler.Close();
return false;
}
format.format = dest_format.format;
format.format = _dest_format.format;
enable_channels = format.channels != dest_format.channels;
enable_channels = format.channels != _dest_format.channels;
if (enable_channels &&
!channels_converter.Open(format.format, format.channels,
dest_format.channels, error)) {
_dest_format.channels, error)) {
if (enable_format)
format_converter.Close();
if (enable_resampler)
@@ -96,6 +94,9 @@ PcmConvert::Open(AudioFormat _src_format, AudioFormat _dest_format,
return false;
}
src_format = _src_format;
dest_format = _dest_format;
return true;
}

@@ -147,7 +147,8 @@ SoxrPcmResampler::Resample(ConstBuffer<void> src, Error &error)
const size_t n_frames = src.size / frame_size;
const size_t o_frames = size_t(n_frames * ratio + 0.5);
/* always round up: worst case output buffer size */
const size_t o_frames = size_t(n_frames * ratio) + 1;
float *output_buffer = (float *)buffer.Get(o_frames * frame_size);

@@ -139,12 +139,12 @@ static SongEnumerator *
playlist_list_open_uri_suffix(const char *uri, Mutex &mutex, Cond &cond,
const bool *tried)
{
const char *suffix;
SongEnumerator *playlist = nullptr;
assert(uri != nullptr);
suffix = uri_get_suffix(uri);
UriSuffixBuffer suffix_buffer;
const char *const suffix = uri_get_suffix(uri, suffix_buffer);
if (suffix == nullptr)
return nullptr;
@@ -257,7 +257,10 @@ playlist_list_open_stream(InputStream &is, const char *uri)
return playlist;
}
const char *suffix = uri != nullptr ? uri_get_suffix(uri) : nullptr;
UriSuffixBuffer suffix_buffer;
const char *suffix = uri != nullptr
? uri_get_suffix(uri, suffix_buffer)
: nullptr;
if (suffix != nullptr) {
auto playlist = playlist_list_open_stream_suffix(is, suffix);
if (playlist != nullptr)

@@ -130,6 +130,7 @@ ExtM3uPlaylist::NextSong()
static const char *const extm3u_suffixes[] = {
"m3u",
"m3u8",
nullptr
};

@@ -60,6 +60,7 @@ M3uPlaylist::NextSong()
static const char *const m3u_suffixes[] = {
"m3u",
"m3u8",
nullptr
};

@@ -19,6 +19,7 @@
#include "Set.hxx"
#include "TagBuilder.hxx"
#include "TagSettings.h"
#include <assert.h>
@@ -109,6 +110,7 @@ TagSet::InsertUnique(const Tag &tag,
if (!CheckUnique(type, tag, type, group_mask) &&
(type != TAG_ALBUM_ARTIST ||
ignore_tag_items[TAG_ALBUM_ARTIST] ||
/* fall back to "Artist" if no "AlbumArtist" was found */
!CheckUnique(type, tag, TAG_ARTIST, group_mask)))
InsertUnique(tag, type, nullptr, group_mask);

@@ -54,6 +54,23 @@ uri_get_suffix(const char *uri)
return suffix;
}
const char *
uri_get_suffix(const char *uri, UriSuffixBuffer &buffer)
{
const char *suffix = uri_get_suffix(uri);
if (suffix == nullptr)
return nullptr;
const char *q = strchr(suffix, '?');
if (q != nullptr && size_t(q - suffix) < sizeof(buffer.data)) {
memcpy(buffer.data, suffix, q - suffix);
buffer.data[q - suffix] = 0;
suffix = buffer.data;
}
return suffix;
}
static const char *
verify_uri_segment(const char *p)
{

@@ -42,6 +42,17 @@ gcc_pure
const char *
uri_get_suffix(const char *uri);
struct UriSuffixBuffer {
char data[8];
};
/**
* Returns the file name suffix, ignoring the query string.
*/
gcc_pure
const char *
uri_get_suffix(const char *uri, UriSuffixBuffer &buffer);
/**
* Returns true if this is a safe "local" URI:
*

@@ -33,7 +33,9 @@
#include "event/Loop.hxx"
#include "util/Error.hxx"
#ifdef HAVE_GLIB
#include <glib.h>
#endif
#include <iostream>
using std::cout;
@@ -107,8 +109,10 @@ main(int argc, char **argv)
/* initialize GLib */
#ifdef HAVE_GLIB
#if !GLIB_CHECK_VERSION(2,32,0)
g_thread_init(nullptr);
#endif
#endif
/* initialize MPD */

@@ -132,6 +132,12 @@ decoder_data(gcc_unused Decoder &decoder,
const void *data, size_t datalen,
gcc_unused uint16_t kbit_rate)
{
static uint16_t prev_kbit_rate;
if (kbit_rate != prev_kbit_rate) {
prev_kbit_rate = kbit_rate;
fprintf(stderr, "%u kbit/s\n", kbit_rate);
}
gcc_unused ssize_t nbytes = write(1, data, datalen);
return DecoderCommand::NONE;
}

@@ -28,7 +28,9 @@
#include "util/Error.hxx"
#include "Log.hxx"
#ifdef HAVE_GLIB
#include <glib.h>
#endif
#include <assert.h>
#include <string.h>
@@ -50,8 +52,10 @@ int main(int argc, gcc_unused char **argv)
return EXIT_FAILURE;
}
#ifdef HAVE_GLIB
#if !GLIB_CHECK_VERSION(2,32,0)
g_thread_init(NULL);
#endif
#endif
EventLoop event_loop;

@@ -7,8 +7,6 @@
#include <cppunit/ui/text/TestRunner.h>
#include <cppunit/extensions/HelperMacros.h>
#include <glib.h>
#include <string.h>
#include <stdlib.h>
@@ -29,22 +27,22 @@ ArchiveLookupTest::TestArchiveLookup()
char *path = strdup("");
CPPUNIT_ASSERT_EQUAL(false,
archive_lookup(path, &archive, &inpath, &suffix));
g_free(path);
free(path);
path = strdup(".");
CPPUNIT_ASSERT_EQUAL(false,
archive_lookup(path, &archive, &inpath, &suffix));
g_free(path);
free(path);
path = strdup("config.h");
CPPUNIT_ASSERT_EQUAL(false,
archive_lookup(path, &archive, &inpath, &suffix));
g_free(path);
free(path);
path = strdup("src/foo/bar");
CPPUNIT_ASSERT_EQUAL(false,
archive_lookup(path, &archive, &inpath, &suffix));
g_free(path);
free(path);
path = strdup("Makefile/foo/bar");
CPPUNIT_ASSERT_EQUAL(true,
@@ -53,7 +51,7 @@ ArchiveLookupTest::TestArchiveLookup()
CPPUNIT_ASSERT_EQUAL(0, strcmp(archive, "Makefile"));
CPPUNIT_ASSERT_EQUAL(0, strcmp(inpath, "foo/bar"));
CPPUNIT_ASSERT_EQUAL((const char *)nullptr, suffix);
g_free(path);
free(path);
path = strdup("config.h/foo/bar");
CPPUNIT_ASSERT_EQUAL(true,
@@ -62,7 +60,7 @@ ArchiveLookupTest::TestArchiveLookup()
CPPUNIT_ASSERT_EQUAL(0, strcmp(archive, "config.h"));
CPPUNIT_ASSERT_EQUAL(0, strcmp(inpath, "foo/bar"));
CPPUNIT_ASSERT_EQUAL(0, strcmp(suffix, "h"));
g_free(path);
free(path);
}
CPPUNIT_TEST_SUITE_REGISTRATION(ArchiveLookupTest);

@@ -34,6 +34,25 @@ public:
uri_get_suffix(".jpg"));
CPPUNIT_ASSERT_EQUAL((const char *)nullptr,
uri_get_suffix("/foo/.jpg"));
/* the first overload does not eliminate the query
string */
CPPUNIT_ASSERT_EQUAL(0, strcmp(uri_get_suffix("/foo/bar.jpg?query_string"),
"jpg?query_string"));
/* ... but the second one does */
UriSuffixBuffer buffer;
CPPUNIT_ASSERT_EQUAL(0, strcmp(uri_get_suffix("/foo/bar.jpg?query_string",
buffer),
"jpg"));
/* repeat some of the above tests with the second overload */
CPPUNIT_ASSERT_EQUAL((const char *)nullptr,
uri_get_suffix("/foo/bar", buffer));
CPPUNIT_ASSERT_EQUAL((const char *)nullptr,
uri_get_suffix("/foo.jpg/bar", buffer));
CPPUNIT_ASSERT_EQUAL(0, strcmp(uri_get_suffix("/foo/bar.jpg", buffer),
"jpg"));
}
void TestRemoveAuth() {

@@ -30,7 +30,9 @@
#include "fs/Path.hxx"
#include "util/Error.hxx"
#ifdef HAVE_GLIB
#include <glib.h>
#endif
#include <unistd.h>
#include <stdlib.h>
@@ -57,8 +59,10 @@ main(int argc, char **argv)
/* initialize GLib */
#ifdef HAVE_GLIB
#if !GLIB_CHECK_VERSION(2,32,0)
g_thread_init(NULL);
#endif
#endif
/* initialize MPD */