Compare commits

...

160 Commits

Author SHA1 Message Date
Max Kellermann
ab197b6d43 release v0.20.20 2018-05-22 12:40:18 +02:00
Max Kellermann
16b0e53a36 android/AndroidManifest.xml: increment version number to 0.20.20 2018-05-22 12:40:11 +02:00
Max Kellermann
bc14a6038e Makefile.am: invoke javac with source/target 1.6
Fixes:

```
error: Source option 5 is no longer supported. Use 6 or later.
error: Target option 1.5 is no longer supported. Use 1.6 or later.
```
2018-05-22 12:37:01 +02:00
Max Kellermann
626329a1cc python/build/meson.py: create build_path if it does not exist 2018-05-22 12:31:37 +02:00
Max Kellermann
8bf250c228 python/build/libs: upgrade CURL to 7.60.0 2018-05-22 11:17:19 +02:00
Max Kellermann
62127bbb12 python/build/libs.py: add libmpdclient 2018-05-12 16:24:24 +02:00
Max Kellermann
786ac87b76 python/build: add support for Meson/ninja based projects 2018-05-12 15:00:17 +02:00
Max Kellermann
c76f4ac89b player/Thread: pause all outputs in single mode
This mostly affects the Pulse output plugin which needs to "cork" the
stream (closes ).
2018-05-12 14:44:07 +02:00
Michal Koutenský
d495ec71a8 decoder/opus: add support for R128_ALBUM_GAIN tag 2018-05-07 10:57:03 +02:00
Max Kellermann
b763852f57 decoder/dsd: allow 4 MB ID3 tags
Closes 
2018-05-07 10:53:48 +02:00
Max Kellermann
6522d2f722 decoder/{dsdiff,dsf}: support more MIME types
These are used by DSD-streaming servers.  For example, MiniDLNA uses
"audio/x-dsd".
2018-05-03 12:02:11 +02:00
Max Kellermann
ac61fd1d78 {input,output}/alsa: work around -Wswitch due to SND_PCM_STATE_PRIVATE1 2018-05-03 11:59:18 +02:00
Max Kellermann
c44d1566fa SongFilter: fix "modified-since" filter
Error message sent to client was "basic_string::_M_construct null not
valid" due to passing nullptr to the std::string constructor.

Regression caused by commit 386688b87a
2018-04-30 20:34:25 +02:00
Max Kellermann
80dc7c2f74 increment version number to 0.20.20 2018-04-30 20:32:36 +02:00
Max Kellermann
7b94f0e36b release v0.20.19 2018-04-26 19:57:04 +02:00
Max Kellermann
504e8d564a android/AndroidManifest.xml: increment version number to 0.20.19 2018-04-26 19:56:39 +02:00
Max Kellermann
ac395429c3 db/proxy: implement the group_mask parameter in VisitUniqueTags()
Closes 
2018-04-26 19:43:33 +02:00
Max Kellermann
388768b3a6 db/proxy: call mpd_search_cancel() after search error
Fixes "search already in progress" errors.
2018-04-26 19:41:19 +02:00
Max Kellermann
5c4169e64e python/build/libs.py: upgrade FFmpeg to 4.0 2018-04-26 19:16:16 +02:00
Max Kellermann
d40e9de2d2 python/build/libs.py: upgrade libvorbis to 1.3.6 2018-04-26 19:14:26 +02:00
Max Kellermann
1e54297be8 lib/ffmpeg/Init: fix av_register_all() deprecation warning
av_register_all() was deprecated in
FFmpeg/FFmpeg@0694d87024
2018-04-25 21:35:33 +02:00
Max Kellermann
44b200240f player/Thread: never reuse decoder when switching radio streams
When switching to another song manually, the player checks if the
decoder is already decoding that song; if so, it will attempt to reuse
it by seeking it to the new position.  That however fails if the
decoder is not seekable (e.g. a radio stream) which leaves the user
unable to switch to that song with the bogus error message "Not
seekable".
2018-04-25 21:19:26 +02:00
Max Kellermann
a2340c313f pcm/PcmDop: round down to the nearest multiple of 4 DSD bytes
There was a discrepancy between what was written to the buffer and the
size returned by pcm_dsd_to_dop(): the "for" loop uses num_frames/2,
rounding down, while the return value is num_samples which is
num_frames*channels, without rounding.  This could cause undefined
data at the end of the destination buffer if the source buffer size
was not aligned to multiples of 8 bytes (4 DSD bytes per channel).

The latter however can occur in the 0.21 branch after commit
a06bf388d9

Closes 
2018-03-15 20:02:00 +01:00
Max Kellermann
37b07a5e7c pcm/PcmDop: use size_t 2018-03-15 20:00:14 +01:00
Max Kellermann
73013a3c04 input/thread: move code to Stop()
Fixes crash due to "pure virtual method called" in the "mms" input
plugin.  Closes 
2018-03-15 19:29:55 +01:00
Max Kellermann
e8099f01b5 python/build/libs: upgrade CURL to 7.59.0 2018-03-15 11:24:50 +01:00
Max Kellermann
672bdd3a56 doc/user.xml: clarify where mpd.conf is read from on Android
Closes 
2018-03-15 11:22:38 +01:00
Max Kellermann
c2c2c29658 input/thread: set InputStream::ready after Open() failure
Without setting the "ready" flag, the caller will wait in WaitReady()
forever, locking up MPD.  Closes 
2018-03-14 13:15:03 +01:00
Michal Smucr
c745e14f47 Bump minimum required version of Boost to 1.54.
lockfree library used by ALSA output plugin is part of Boost from version 1.53,
so this can be theoretically the lowest required version, however
there are issues which are resolved from 1.54 onwards.
2018-03-09 09:23:48 +01:00
Max Kellermann
e8f08cda53 AUTHORS: add various recent contributors 2018-03-05 19:23:36 +01:00
Max Kellermann
8266ab5588 android/build.py: support the x86 ABI
First commit for issue 
2018-03-04 20:46:46 +01:00
Max Kellermann
ea552208fc android/build.py: add ABI parameter 2018-03-04 20:43:59 +01:00
Max Kellermann
e86015a72a android/build.py: convert ndk_arch to local variable 2018-03-04 20:32:50 +01:00
Max Kellermann
cf7ec2c9d3 doc/user.xml: add section about compiling for Android 2018-03-04 20:19:22 +01:00
Max Kellermann
dadd3ca671 protocol/ArgParser: disallow negative seek times
Instead of stopping playback (due to seek time overflow), reject the
seek command.  Closes 

Relative negative values (with "seekcur") are still allowed, and MPD
will fix the resulting position if it turns out to be negative.  But
the "seek" and "seekid" commands use an unsigned time stamp which must
not be negative.
2018-03-04 11:46:11 +01:00
Christian Kröner
79535212c8 Get rid of GCD on macOS which breaks debug builds
With Grand Central Dispatch used in Main.cxx, debug builds on macOS
crash as the IsInside() assertion gets triggered in the event loop. As
a simple fix, usage of GCD is removed. Plugging and unplugging
headphones or changes of the default output device was tested without
issues. Whatever the original commit tried to fix by GCD probably does
not need fixing anymore.
2018-03-04 10:43:55 +01:00
Max Kellermann
ef5f96a193 increment version number to 0.20.19 2018-03-04 10:42:05 +01:00
Max Kellermann
418f71ec0f net/Init: work around -Werror=unused-variable 2018-02-24 23:17:36 +01:00
Max Kellermann
0ebeaa9ac2 release v0.20.18 2018-02-24 22:55:06 +01:00
Max Kellermann
25cd47b8dc win32/build.py: enable libnfs
Now that all build failures have been fixed, we can enable the
feature.
2018-02-24 22:44:42 +01:00
Max Kellermann
cd48d981b5 storage/nfs: use PathTraitsFS::const_pointer_type 2018-02-24 22:44:42 +01:00
Max Kellermann
774d26b982 storage/nfs: assume UTF-8 when accessing NFS from Windows
Fixes two build failures with libnfs on Windows.
2018-02-24 22:44:42 +01:00
Max Kellermann
f3e683bd6f test/run_storage: fallback for %F on Windows 2018-02-24 22:44:42 +01:00
Max Kellermann
50ce0c0d9d test/run_storage: initialize WinSock 2018-02-24 22:44:34 +01:00
Max Kellermann
5b80711d75 Main: move WinSock initialization to class ScopeNetInit 2018-02-24 22:44:27 +01:00
Max Kellermann
666e456551 win32/build.py: add -DWINVER=0x0600 -D_WIN32_WINNT=0x0600
configure.ac sets this, but this wasn't used for compiling third-party
libraries.  This setting however is important for libnfs, which adds
fallback definitions for POLLIN and POLLOUT with bogus values.
2018-02-24 22:44:11 +01:00
Max Kellermann
31794ac376 lib/nfs/FileReader: move sys/stat.h to header because "struct stat" may be macro
It indeed is a macro on Windows.
2018-02-24 22:03:38 +01:00
Max Kellermann
2141fdf06e lib/nfs/Connection: use winsock2.h instead of poll.h on Windows 2018-02-24 22:02:42 +01:00
tpoeiras
3f3e0739c4 Fix curl storage plugin failure if the authentication method is different than basic. 2018-02-24 21:59:13 +01:00
Max Kellermann
ebed7e2147 playlist/cue/Parser: parse tags after "INDEX 01"
Instead of setting state=IGNORE_TRACK, ignore only the following
"INDEX" lines.

Correction for commit 8461d71b52.  Closes 
2018-02-24 21:29:16 +01:00
Max Kellermann
53f5d4c710 android/build.py: disable libmad
Let FFmpeg do the MP3 decoding.  See commit
a4de96508d
2018-02-24 10:52:40 +01:00
Max Kellermann
139a4054c5 python/build/libs.py: remove duplicate FFmpeg option and fix typo
Closes 
2018-02-24 10:52:09 +01:00
Max Kellermann
a4de96508d python/build/libs.py: re-enable FFmpeg MP3 decoder
libmad has been unmaintained for a long time, and it fails to build on
Windows.  I could go and fix libmad's broken configure script, but I
prefer to just assign MP3 decoding to FFmpeg for now.

Closes 
2018-02-24 10:49:05 +01:00
Max Kellermann
a7582aaf15 python/build/libs.py: update FFmpeg to 3.4.2 2018-02-24 10:47:46 +01:00
Max Kellermann
c5c1c64a81 python/build/libs.py: add libnfs
Enable the NFS storage plugin on Android.

Closes 
2018-02-20 22:47:17 +01:00
Max Kellermann
992c52ce7f python/build/autotools.py: add autoreconf support 2018-02-20 22:46:54 +01:00
Max Kellermann
026aef7465 decoder/flac: move the SubmitData() call out of the callback
This addresses two problems:

1. the libFLAC write callback had to send an error status to its
caller when SubmitData() returned a command; this disrupted libFLAC
and the resulting command could not be used for anything;

2. the libFLAC function FLAC__stream_decoder_seek_absolute() also
calls the write callback, but its result cannot be used, because
seeking is still in progress, so we lose all data from one FLAC frame.
By moving the SubmitData() call until after CommandFinished(), we
avoid losing this data.  This fixes another part of 
2018-02-17 13:33:53 +01:00
Max Kellermann
b53a23b51b decoder/flac: call FlacSubmitToClient() again after seeking
See code comment.
2018-02-17 13:33:51 +01:00
Max Kellermann
2aad015392 decoder/flac: move code to FlacSubmitToClient() 2018-02-17 13:33:48 +01:00
Max Kellermann
986ec877b0 decoder/Bridge: truncate last chunk at the exact end_time
Instead of passing whole chunks to the MusicPipe and checking the
end_time after each chunk, truncate the last chunk if it would exceed
the end_time.  This requires keeping track of the absolute PCM frame
number.

This fixes a problem with gapless CUE song transitions: a small part
of the following song was always played twice.

Closes 
2018-02-17 13:10:00 +01:00
cathugger
c43ea74b30 encoder/opus: initialize granulepos to 0
it was uninitialized before
2018-02-17 01:22:17 +01:00
Max Kellermann
79981f3cda increment version number to 0.20.18 2018-02-17 01:21:46 +01:00
Max Kellermann
c2940a8385 release v0.20.17 2018-02-11 13:02:53 +01:00
Max Kellermann
bede564618 mixer/alsa: work around rounding error at volume 0
Due to rounding errors, a slightly negative value can be passed to
set_normalized_volume(), which will make the log10() call fail.
Actually, volume 0 is already failing because log10(0) is illegal.  So
let's fix this by implementing two corner cases: <=0 and >=100.

Closes 
2018-02-10 09:07:51 +01:00
Max Kellermann
e0ca4b865a android: require SDK version 14
Closes .
2018-02-10 00:03:23 +01:00
Max Kellermann
31c206bf80 android/build.py: add -mfpu=vfp, explicitly disabling NEON
Apparently, clang defaults to NEON when ARMv7 is used.  Not all ARMv7
CPUs we target have NEON, so we need to disable that.
2018-02-10 00:00:57 +01:00
Max Kellermann
9187a08106 lib/curl: remove .netrc support on Android
Not needed on Android, and the implementation uses getpwuid_r() which
is unavailable on old Android versions.
2018-02-09 23:14:29 +01:00
Max Kellermann
3859a50466 python/build/libs.py: convert CURL edit to quilt patch 2018-02-09 23:14:27 +01:00
Max Kellermann
927071e085 python/build/project.py: add quilt support 2018-02-09 22:59:12 +01:00
Max Kellermann
6ba918b203 input/file: don't use posix_fadvise() on Android
Requires Android API 21, but we want to support older versions as
well.
2018-02-09 22:54:22 +01:00
Max Kellermann
e8b70dbca4 SongSave, queue/PlaylistState, tag/ReplayGain: use portable atof() wrappers
For Android pre-5.0 compatibility ().
2018-02-09 22:54:22 +01:00
Max Kellermann
0f8d223c7f protocol/ArgParser: move strtof()/strtod() switch to util/NumberParser.hxx 2018-02-09 22:54:22 +01:00
Max Kellermann
19a2885fd5 protocol/ArgParser: use strtod() instead of strtof() on Android
For Android pre-5.0 compatibility ().
2018-02-09 22:54:22 +01:00
Max Kellermann
b8a094470b python/build/libs.py: build only the library 2018-02-09 22:54:22 +01:00
Max Kellermann
2988bb77e8 python/build/project: allow trailing digit after letter in version number
For version numbers such as OpenSSH's, e.g.: "7.2p2"
2018-02-09 22:54:22 +01:00
Max Kellermann
738317bf34 doc/user: document MPD on Android
Closes 
2018-02-09 19:11:39 +01:00
Max Kellermann
e46fbd0780 filter/convert: set the PcmConvert instance only if it was initialized
Fixes valgrind warning.
2018-02-09 19:05:45 +01:00
Max Kellermann
56b74ad990 filter/convert: add method IsActive() 2018-02-09 19:04:45 +01:00
Max Kellermann
6de92bb42b pcm/Order: fix size calculation with 8 channels
This was a buffer overflow bug which could cause MPD crahes when
playing back 8 channels with the ALSA output plugin.

Closes 
2018-02-09 19:01:12 +01:00
Max Kellermann
c801936e53 db/update/Service: set the update thread name 2018-02-09 18:48:14 +01:00
Max Kellermann
817656504d thread/Util: implement system call wrapper for sched_setscheduler()
There is a POSIX definition for sched_setscheduler(), but Linux does
not implement that; instead of changing the process's scheduler, it
only affects one thread.  This has caused some confusion among
application developers and C library developers.

While glibc implements Linux semantics, Musl has made their
sched_setscheduler() function an always-failing no-op, causing the
error message "sched_setscheduler failed: Function not implemented".

 http://git.musl-libc.org/cgit/musl/commit/src/sched/sched_setscheduler.c?id=1e21e78bf7a5c24c217446d8760be7b7188711c2

Instead of relying on the C library which may be unreliable here, we
now roll our own system call wrapper.

Closes 
2018-02-09 18:43:45 +01:00
Max Kellermann
6f00f97b66 thread/Util: rename ioprio_set() to linux_ioprio_set()
Juse in cas glibc gets a wrapper for the system call which would then
conflict with ours.
2018-02-09 18:43:45 +01:00
Max Kellermann
5acb978f8f increment version number to 0.20.17 2018-02-09 18:43:45 +01:00
Max Kellermann
975a4ae871 release v0.20.16 2018-02-03 19:55:07 +01:00
Max Kellermann
56aaf3c73e python/build/libs: upgrade CURL to 7.58.0 2018-02-03 19:46:31 +01:00
Max Kellermann
12fd1cad0c archive/iso9660: libcdio 2.0 compatibility
Closes 
2018-02-03 19:32:31 +01:00
Max Kellermann
e573cbf032 db/update/Queue: work around GCC7 -Wuninitialized 2018-02-01 19:53:42 +01:00
Max Kellermann
dead461542 lib/upnp/Init: enable IPv6 2018-01-31 18:15:46 +01:00
Max Kellermann
3d5da1ac73 lib/upnp/Init: use nullptr instead of 0 2018-01-31 18:14:26 +01:00
Max Kellermann
ec408ca6a6 output/pulse: fix crash during auto-detection
The PulseOutput needs to be "enabled" before WaitConnection() may be
called.

Closes 
2018-01-30 10:06:36 +01:00
Max Kellermann
ea66cdd6a5 test/read_mixer: another kludge to work around -Wnull-dereference 2018-01-23 16:42:25 +01:00
Max Kellermann
f762e8034f test/NullMixerListener: new class to fix -Wnull-dereference 2018-01-23 16:28:56 +01:00
Max Kellermann
bb1e369f30 playlist/SoundCloud: fix -Wunused-lambda-capture 2018-01-23 09:57:52 +01:00
Max Kellermann
8376578921 db/simple/Mount: drop mount point prefix from LOCATE_TAG_BASE_TYPE
Fixes search within mount points, resulting in error "No such
directory".

Closes 
2018-01-19 23:52:57 +01:00
Max Kellermann
ed2354cd9d SongFilter: allow copying items 2018-01-19 23:52:03 +01:00
Max Kellermann
386688b87a SongFilter: use std::string instead of AllocatedString 2018-01-19 23:51:42 +01:00
Max Kellermann
38d56dddf1 lib/icu/Compare: allow copying 2018-01-19 23:49:50 +01:00
Max Kellermann
e8975942ec Makefile.am: link libicu.a before libutil.a
libicu.a depends on libutil.a.
2018-01-19 23:38:24 +01:00
Max Kellermann
3ca80a7336 util/RefCount, db/simple/Mount: remove obsolete libc++ workarounds
No longer a problem with NDK r16.
2018-01-19 23:19:46 +01:00
Max Kellermann
d029dae7ad Makefile.am: use Android SDK build-tools 27.0.0 2018-01-19 23:04:54 +01:00
Max Kellermann
9e058732ee android/build.py: add -fpic
Android native code should be position-independent.

The NDK build scripts use "-fpic" instead of "-fPIC" for ARM, but that
doesn't work with FFmpeg's assembly code, because it requires
R_ARM_MOVW_ABS_NC which is unavailable with "-fpic".
2018-01-19 22:40:59 +01:00
Max Kellermann
cad5d11261 android/build.py: simplify libc++ flags
By telling clang which implementation to use, we avoid the dependency
on libstdc++.so.
2018-01-19 22:36:19 +01:00
Max Kellermann
fcaedec2ab {android,win32}/build.py: move "-O* -g" to common_flags 2018-01-19 12:33:28 +01:00
Max Kellermann
ead9d59e88 python/build/libs.py: build only libFLAC, no programs 2018-01-19 12:33:03 +01:00
Max Kellermann
34b8a17ccd python/build/autotools.py: add "subdir" parameter 2018-01-19 11:39:36 +01:00
Max Kellermann
a53d081c39 python/build/libs.py: disable libFLAC API documentation 2018-01-19 11:38:24 +01:00
Max Kellermann
823134e4ba python/build/libs.py: disable Opus documentation and extra programs 2018-01-19 11:32:40 +01:00
Max Kellermann
272167b4fc python/build/libs.py: update LAME to 3.100 2018-01-18 22:07:28 +01:00
Max Kellermann
92f09bba94 Makefile.am: rename JAVA_SOURCES to JAVA_SOURCE_PATHS
Work around automake warning:

    Makefile.am:310: warning: variable 'JAVA_SOURCES' is defined but no program or
    Makefile.am:310: library has 'JAVA' as canonical name (possible typo)

Closes 
2018-01-18 22:05:04 +01:00
Max Kellermann
1f50bdb230 event/Loop: use std::atomic_bool for the "quit" variable
Fixes thread sanitizer warnings.
2018-01-08 10:06:23 +01:00
Max Kellermann
2eef4e6716 thread/Thread: add debug attribute "inside_handle"
This attribute shall be used only for IsInside() to make this safe
against a race condition described in :

> There is no requirement on the implementation that the ID of the
> created thread be available before the newly created thread starts
> executing.

http://pubs.opengroup.org/onlinepubs/009695399/functions/pthread_create.html):

This means that on some pthread implementations (e.g. Haiku), the
assert(thread.IsInside()) could fail.

Closes 
2018-01-08 09:58:18 +01:00
Max Kellermann
d989dbfec4 thread/Thread: make IsInside() debug-only
This method is only used inside assert().
2018-01-08 09:56:39 +01:00
Max Kellermann
ca9fcec364 thread/Thread: fix indent 2018-01-08 09:49:08 +01:00
Max Kellermann
354104f9a9 thread/{Thread,Id}: use defaul-initialized pthread_t as "undefined" value
Use the "==" operator instead of pthread_equal().

This allows us to eliminate two boolean flags which are there to avoid
race conditions, and made the thing so fragile that I got tons of
(correct) thread sanitizer warnings.
2018-01-07 17:20:26 +01:00
Max Kellermann
8649ea3d6f thread/Thread: use BoundMethod 2018-01-07 17:20:26 +01:00
Max Kellermann
752ff12c37 thread/Thread: move code to Run() 2018-01-07 17:20:26 +01:00
Max Kellermann
4bb89b1755 MusicPipe: lock the mutex in Peek() and GetSize() 2018-01-07 17:20:22 +01:00
Max Kellermann
0ef553d30e increment version number to 0.20.16 2018-01-06 13:15:47 +01:00
Max Kellermann
43a62aef07 android: release 0.20.15 2018-01-05 18:09:56 +01:00
Max Kellermann
ed4d0aa909 release v0.20.15 2018-01-05 17:55:25 +01:00
Max Kellermann
023ce4e720 python/build/libs.py: disable even more FFmpeg modules 2018-01-05 17:41:58 +01:00
Max Kellermann
368d9359dd python/build/libs.py: update libogg to 1.3.3 2018-01-05 17:41:58 +01:00
Max Kellermann
d98c19d561 python/build/libs.py: disable more FFmpeg modules 2018-01-05 16:49:28 +01:00
Max Kellermann
cab77e35e0 queue/PlaylistControl: fix crash after seek failure
This completes the bug fix commit
2065e3290452377b2931f3129b230c8cc536cbc8; if we clear "queued" then we
must clear "queued_song" as well, or another variant of the assertion
fails.
2018-01-05 13:00:24 +01:00
Max Kellermann
e3e90b4b93 python/build/libs.py: disable libcurl SMB support
MPD doesn't use this feature anyway.
2018-01-05 12:00:29 +01:00
Max Kellermann
f8c69893e1 python/build/libs.py: disable lots of useless FFmpeg features 2018-01-05 11:11:20 +01:00
Max Kellermann
49678a0893 python/build/libs.py: suppress "visibility default" in libopus build 2018-01-05 10:33:53 +01:00
Max Kellermann
d667b5b48c python/build/libs.py: work around libid3tag CFLAGS bug 2018-01-05 10:25:16 +01:00
Max Kellermann
9cba55b39c python/build/project.py: add "edits" parameter to edit source files 2018-01-05 10:06:22 +01:00
Max Kellermann
c2cbb7b8ce output/haiku: remove unimplemented Cancel() method 2018-01-05 10:05:14 +01:00
Felix Hädicke
8217d75ca1 build/python: refactoring: introduce new class MakeProject
This introduces a the new class MakeProject, which is used as a base
class for all Makefile based thirdparty libraries.
2018-01-05 08:17:17 +01:00
Max Kellermann
1ca70d9759 build/python/autotools: add properties "ldflags", "libs", "install_target" 2018-01-05 08:17:15 +01:00
Felix Hädicke
4303aaa9b8 build/python: use "glibtoolize", not "libtoolize" when compiling on OS X
On OS X, the "libtoolize" command is some Apple tool. The libtoolize
we want is named "glibtoolize" in Homebrew.
2018-01-05 07:55:59 +01:00
Max Kellermann
7b56bae289 python/build/libs.py: pass --disable-debugging to libid3tag and libmad 2018-01-05 07:30:09 +01:00
Max Kellermann
4183416b3e python/build/libs.py: reindent arrays 2018-01-05 07:27:09 +01:00
Max Kellermann
a60dee57ce python/libs: upgrade Boost to 1.66.0 2018-01-05 07:16:38 +01:00
Max Kellermann
5724656acb android/build.py: enable function/data sections in static libraries
.. and make all library symbols hidden by default.

Saves big amounts of .text section size with --gc-sections, because
only this allows discarding unused functions from those (static)
third-party libraries.
2018-01-04 23:33:07 +01:00
Max Kellermann
329f9cd9fe thread/Util: no ioprio_set() on Android due to seccomp/SIGSYS 2018-01-04 19:33:14 +01:00
Felix Hädicke
fbdb8b406e Makefile.am: build Android APK package without Ant
In current Android SDK releases, Ant support was removed. Move the
necessary build steps from the former Ant build system to our Makefile,
and call the required build tools from the Android SDK (aapt and dx),
Java SDK (javac) and Info-ZIP (zip) directly.

[mk: copied from Felix's commit
e52b906dba971a1173f9e8f83d32b52ee9f89af3 in the XCSoar project)
2018-01-04 18:55:54 +01:00
Max Kellermann
85d0bbd957 Makefile.am: add variable ZIPALIGN 2018-01-04 18:55:45 +01:00
Max Kellermann
414f00d6ae Makefile.am: add variable ANDROID_SDK_PLATFORM 2018-01-04 18:47:52 +01:00
Max Kellermann
17b0add058 filter/Observer: pass Reset() to underlying Filter
Wohooooo, the method Filter::Reset() has been broken because no
implementation of it has ever been called for a loooong time.
And nobody ever noticed it.  WTF.
2018-01-02 22:13:14 +01:00
Max Kellermann
c68ed40661 pcm/SoxrResampler: implement method Reset() 2018-01-02 21:53:09 +01:00
Max Kellermann
ff624075a8 storage/State: check if a CompositeStorage exists; fixes nullptr dereference
Fixes another crash bug caused by commit
64d141f71e
2018-01-02 14:13:26 +01:00
Max Kellermann
08db28469d storage/State: make mount errors non-fatal
Fixes crash bug caused by commit
64d141f71e
2018-01-02 14:07:52 +01:00
Max Kellermann
a20b326807 storage/State: fix memory leak after database mount failure
Caused by commit 64d141f71e

This wasn't a serious memory leak, because after a mount failure, MPD
would abort anyway, which is subject to the next commit.
2018-01-02 14:05:07 +01:00
Max Kellermann
4db1b1b250 storage/State: remove useless #ifdef ENABLE_DATABASE
This source file isn't compiled when the database is disabled.
2018-01-02 13:48:16 +01:00
Max Kellermann
ff6b263b48 increment version number to 0.20.15 2018-01-02 13:46:03 +01:00
Max Kellermann
c0bf052fa9 release v0.20.14 2018-01-01 17:55:38 +01:00
loujine
5419cff925 [doc] Fix outdated MusicBrainz URLs (closes ) 2018-01-01 17:30:24 +01:00
Max Kellermann
eee10ad2ed input/curl: add missing mutex locks to OnEnd(), OnError() 2017-12-26 20:01:13 +01:00
Max Kellermann
98472a8104 pcm/SampleFormat: remove wrong "malloc" attribute 2017-12-23 08:38:22 +01:00
Max Kellermann
d094c168aa archive/{iso9660,zzip}: unlock the mutex during I/O
Similar to commit 31ab78ae8e
2017-12-22 16:09:03 +01:00
Max Kellermann
4b18460bc6 archive/bz2: unlock the archive mutex and lock the file mutex
Fixes deadlock because FileInputStream::Read() unlocks the mutex
(which was not locked) and then locks it, keeping it locked.  This can
result in a deadlock.  This happens because the archive and the file
mutex are different.
2017-12-22 16:02:23 +01:00
Max Kellermann
412c0a965c util/WStringAPI: fix indent 2017-12-21 18:45:26 +01:00
Ilya ilyxa Tyshchenko
2becf79223 correct action for compile on Solaris 11.3 X86 2017-12-21 18:42:36 +01:00
Max Kellermann
43ec96d4a0 command/Error: translate std::{length_error,out_of_range} to ACK_ERROR_ARG 2017-12-21 10:22:04 +01:00
Max Kellermann
3d1d779da7 storage/State: use std::set instead of sorting a std::list 2017-12-21 10:22:00 +01:00
Max Kellermann
c88056ba83 db/simple: fix file corruption in the presence of mount points
If a directory is a mount point, omit the "directory: " as well.

This bug is years old, but has become more visible now that mount
points are persistent in the state file.
2017-12-21 10:16:52 +01:00
Max Kellermann
e769751221 increment version number to 0.20.14 2017-12-21 10:15:16 +01:00
102 changed files with 1518 additions and 454 deletions
AUTHORSMakefile.amNEWS
android
configure.ac
doc
python/build
src
test
win32

@@ -31,3 +31,8 @@ The following people have contributed code to MPD:
Jean-Francois Dockes <jf@dockes.org>
Yue Wang <yuleopen@gmail.com>
Matthew Leon Grinshpun <ml@matthewleon.com>
Dimitris Papastamos <sin@2f30.org>
Florian Schlichting <fsfs@debian.org>
François Revol <revol@free.fr>
Jacob Vosmaer <contact@jacobvosmaer.nl>
Thomas Guillem <thomas@gllm.fr>

@@ -61,8 +61,8 @@ src_mpd_LDADD = \
libnet.a \
$(FS_LIBS) \
libsystem.a \
libutil.a \
$(ICU_LDADD) \
libutil.a \
$(SYSTEMD_DAEMON_LIBS)
src_mpd_SOURCES = \
@@ -285,30 +285,62 @@ libmain_a_CPPFLAGS = $(AM_CPPFLAGS) -Iandroid/build/include
src_mpd_LDADD += libandroid.a libjava.a
all-local: android/build/bin/$(APK_NAME)-debug.apk
all-local: android/build/$(APK_NAME)-debug.apk
clean-local:
rm -rf android/build
libmpd.so: $(filter %.a,$(src_mpd_LDADD)) libmain.a
$(AM_V_CXXLD)$(CXXLD) -shared -Wl,--no-undefined,-shared,-Bsymbolic -llog -lz -o $@ $(AM_CXXFLAGS) $(CXXFLAGS) $(LDFLAGS) src/libmain_a-Main.o $(src_mpd_LDADD) $(LIBS)
android/build/build.xml: android/AndroidManifest.xml
rm -rf android/build
mkdir -p android/build/include android/build/res android/build/src/org
ln -s $(abs_srcdir)/android/AndroidManifest.xml $(abs_srcdir)/android/custom_rules.xml android/build
ln -s $(abs_srcdir)/android/src android/build/src/org/musicpd
ln -s $(abs_srcdir)/android/res/values $(abs_srcdir)/android/res/layout android/build/res
$(ANDROID_SDK)/tools/android update project --path android/build --target android-17 --name $(APK_NAME)
ANDROID_SDK_BUILD_TOOLS_VERSION = 27.0.0
ANDROID_SDK_PLATFORM = android-17
android/build/bin/classes/org/musicpd/Bridge.class: android/src/Bridge.java android/build/build.xml android/build/res/drawable/icon.png
cd android/build && ant compile-jni-classes
ANDROID_BUILD_TOOLS_DIR = $(ANDROID_SDK)/build-tools/$(ANDROID_SDK_BUILD_TOOLS_VERSION)
ANDROID_SDK_PLATFORM_DIR = $(ANDROID_SDK)/platforms/$(ANDROID_SDK_PLATFORM)
android/build/include/org_musicpd_Bridge.h: android/build/bin/classes/org/musicpd/Bridge.class
javah -classpath $(ANDROID_SDK)/platforms/android-17/android.jar:android/build/bin/classes -d $(@D) org.musicpd.Bridge
JAVAC = javac
AAPT = $(ANDROID_BUILD_TOOLS_DIR)/aapt
DX = $(ANDROID_BUILD_TOOLS_DIR)/dx
ZIPALIGN = $(ANDROID_BUILD_TOOLS_DIR)/zipalign
ANDROID_XML_RES := $(wildcard $(srcdir)/android/res/*/*.xml)
ANDROID_XML_RES_COPIES := $(patsubst $(srcdir)/android/%,android/build/%,$(ANDROID_XML_RES))
JAVA_SOURCE_NAMES = Bridge.java Loader.java Main.java
JAVA_SOURCE_PATHS = $(addprefix $(srcdir)/android/src/,$(JAVA_SOURCE_NAMES))
JAVA_CLASSFILES_DIR = android/build/classes
$(ANDROID_XML_RES_COPIES): $(ANDROID_XML_RES)
@$(MKDIR_P) $(dir $@)
cp $(patsubst android/build/%,$(srcdir)/android/%,$@) $@
android/build/resources.apk: $(ANDROID_XML_RES_COPIES) android/build/res/drawable/icon.png
@$(MKDIR_P) android/build/gen
$(AAPT) package -f -m --auto-add-overlay \
--custom-package org.musicpd \
-M $(srcdir)/android/AndroidManifest.xml \
-S android/build/res \
-J android/build/gen \
-I $(ANDROID_SDK_PLATFORM_DIR)/android.jar \
-F android/build/resources.apk
# R.java is generated by aapt, when resources.apk is generated
android/build/gen/org/musicpd/R.java: android/build/resources.apk
android/build/classes.dex: $(JAVA_SOURCE_PATHS) android/build/gen/org/musicpd/R.java
@$(MKDIR_P) $(JAVA_CLASSFILES_DIR)
$(JAVAC) -source 1.6 -target 1.6 -Xlint:-options \
-cp $(ANDROID_SDK_PLATFORM_DIR)/android.jar:$(JAVA_CLASSFILES_DIR) \
-d $(JAVA_CLASSFILES_DIR) $^
$(DX) --dex --output $@ $(JAVA_CLASSFILES_DIR)
android/build/include/org_musicpd_Bridge.h: android/build/classes.dex
javah -classpath $(ANDROID_SDK_PLATFORM_DIR)/android.jar:$(JAVA_CLASSFILES_DIR) -d $(@D) org.musicpd.Bridge
BUILT_SOURCES = android/build/include/org_musicpd_Bridge.h
android/build/libs/armeabi-v7a/libmpd.so: libmpd.so android/build/build.xml
android/build/lib/armeabi-v7a/libmpd.so: libmpd.so
mkdir -p $(@D)
rm -f $@
$(STRIP) -o $@ $<
@@ -317,24 +349,19 @@ android/build/res/drawable/icon.png: mpd.svg
mkdir -p $(@D)
rsvg-convert --width=48 --height=48 $< -o $@
APK_DEPS = android/build/res/drawable/icon.png \
android/build/libs/armeabi-v7a/libmpd.so \
$(wildcard $(srcdir)/android/src/*.java) \
android/build/build.xml
.DELETE_ON_ERROR: android/build/unsigned.apk
android/build/unsigned.apk: android/build/classes.dex android/build/resources.apk android/build/lib/armeabi-v7a/libmpd.so
cp android/build/resources.apk $@
cd $(dir $@) && zip -q -r $(notdir $@) classes.dex lib
android/build/bin/$(APK_NAME)-debug.apk: $(APK_DEPS)
cd android/build && ant nodeps debug
android/build/$(APK_NAME)-debug.apk: android/build/unsigned.apk
jarsigner -keystore $(HOME)/.android/debug.keystore -storepass android -signedjar $@ $< androiddebugkey
android/build/bin/$(APK_NAME)-release-unsigned.apk: $(APK_DEPS)
cd android/build && ant nodeps release
android/build/bin/$(APK_NAME)-release-unaligned.apk: android/build/bin/$(APK_NAME)-release-unsigned.apk
android/build/$(APK_NAME)-release-unaligned.apk: android/build/unsigned.apk
jarsigner -digestalg SHA1 -sigalg MD5withRSA -storepass:env ANDROID_KEYSTORE_PASS -keystore $(ANDROID_KEYSTORE) -signedjar $@ $< $(ANDROID_KEY_ALIAS)
ANDROID_SDK_BUILD_TOOLS_VERSION = 20.0.0
android/build/bin/$(APK_NAME).apk: android/build/bin/$(APK_NAME)-release-unaligned.apk
$(ANDROID_SDK)/build-tools/$(ANDROID_SDK_BUILD_TOOLS_VERSION)/zipalign -f 4 $< $@
android/build/$(APK_NAME).apk: android/build/$(APK_NAME)-release-unaligned.apk
$(ZIPALIGN) -f 4 $< $@
endif
@@ -470,6 +497,7 @@ libthread_a_SOURCES = \
libnet_a_SOURCES = \
src/net/Features.hxx \
src/net/Init.hxx \
src/net/ToString.cxx src/net/ToString.hxx \
src/net/Resolver.cxx src/net/Resolver.hxx \
src/net/StaticSocketAddress.cxx src/net/StaticSocketAddress.hxx \
@@ -2132,6 +2160,7 @@ test_run_output_LDADD = $(MPD_LIBS) \
libutil.a
test_run_output_SOURCES = test/run_output.cxx \
test/ScopeIOThread.hxx \
test/NullMixerListener.hxx \
src/Log.cxx src/LogBackend.cxx \
src/IOThread.cxx \
src/output/Domain.cxx \
@@ -2155,6 +2184,7 @@ test_read_mixer_LDADD = \
libsystem.a \
libutil.a
test_read_mixer_SOURCES = test/read_mixer.cxx \
test/NullMixerListener.hxx \
src/Log.cxx src/LogBackend.cxx \
src/mixer/MixerControl.cxx \
src/filter/FilterPlugin.cxx \

73
NEWS

@@ -1,3 +1,76 @@
ver 0.20.20 (2018/05/22)
* protocol
- fix "modified-since" filter regression
* output
- pulse: cork stream when paused due to "single" mode
* decoder
- dsdiff, dsf: support more MIME types
- dsdiff, dsf: allow 4 MB ID3 tags
- opus: support R128_ALBUM_GAIN tag
* Android, Windows
- enable the "proxy" database plugin
ver 0.20.19 (2018/04/26)
* protocol
- validate absolute seek time, reject negative values
* database
- proxy: fix "search already in progress" errors
- proxy: implement "list ... group"
* input
- mms: fix lockup bug and a crash bug
* decoder
- ffmpeg: fix av_register_all() deprecation warning (FFmpeg 4.0)
* player
- fix spurious "Not seekable" error when switching radio streams
* macOS: fix crash bug
ver 0.20.18 (2018/02/24)
* input
- curl: allow authentication methods other than "Basic"
* decoder
- flac: improve seeking precision
* fix gapless CUE song transitions
* Android, Windows
- enable the NFS storage plugin
ver 0.20.17 (2018/02/11)
* output
- alsa: fix crash bug with 8 channels
* mixer
- alsa: fix rounding error at volume 0
* fix real-time and idle scheduling with Musl
* Android
- fix compatibility with Android 4.0
ver 0.20.16 (2018/02/03)
* output
- pulse: fix crash during auto-detection
* database
- simple: fix search within mount points
- upnp: enable IPv6
* archive
- iso9660: libcdio 2.0 compatibility
* fix crash in debug build on Haiku and other operating systems
ver 0.20.15 (2018/01/05)
* queue: fix crash after seek failure
* resampler
- soxr: clear internal state after manual song change
* state file
- make mount point restore errors non-fatal
- fix crash when restoring mounts with incompatible database plugin
* Android
- build without Ant
- fix for SIGSYS crash
ver 0.20.14 (2018/01/01)
* database
- simple: fix file corruption in the presence of mount points
* archive
- bz2: fix deadlock
- reduce lock contention, fixing lots of xrun problems
* fix Solaris build failure
ver 0.20.13 (2017/12/18)
* output
- osx: set up ring buffer to hold at least 100ms

@@ -2,10 +2,10 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.musicpd"
android:installLocation="auto"
android:versionCode="13"
android:versionName="0.19.9">
android:versionCode="19"
android:versionName="0.20.20">
<uses-sdk android:minSdkVersion="9" android:targetSdkVersion="17"/>
<uses-sdk android:minSdkVersion="14" android:targetSdkVersion="17"/>
<application android:icon="@drawable/icon" android:label="@string/app_name">
<activity android:name=".Main"

@@ -3,13 +3,14 @@
import os, os.path
import sys, subprocess
if len(sys.argv) < 3:
print("Usage: build.py SDK_PATH NDK_PATH [configure_args...]", file=sys.stderr)
if len(sys.argv) < 4:
print("Usage: build.py SDK_PATH NDK_PATH ABI [configure_args...]", file=sys.stderr)
sys.exit(1)
sdk_path = sys.argv[1]
ndk_path = sys.argv[2]
configure_args = sys.argv[3:]
android_abi = sys.argv[3]
configure_args = sys.argv[4:]
if not os.path.isfile(os.path.join(sdk_path, 'tools', 'android')):
print("SDK not found in", ndk_path, file=sys.stderr)
@@ -19,8 +20,27 @@ if not os.path.isdir(ndk_path):
print("NDK not found in", ndk_path, file=sys.stderr)
sys.exit(1)
android_abis = {
'armeabi-v7a': {
'arch': 'arm-linux-androideabi',
'ndk_arch': 'arm',
'toolchain_arch': 'arm-linux-androideabi',
'llvm_triple': 'armv7-none-linux-androideabi',
'cflags': '-march=armv7-a -mfpu=vfp -mfloat-abi=softfp',
},
'x86': {
'arch': 'i686-linux-android',
'ndk_arch': 'x86',
'toolchain_arch': 'x86',
'llvm_triple': 'i686-none-linux-android',
'cflags': '-march=i686 -mtune=intel -mssse3 -mfpmath=sse -m32',
},
}
# select the NDK target
arch = 'arm-linux-androideabi'
abi_info = android_abis[android_abi]
arch = abi_info['arch']
# the path to the MPD sources
mpd_path = os.path.abspath(os.path.join(os.path.dirname(sys.argv[0]) or '.', '..'))
@@ -44,16 +64,15 @@ class AndroidNdkToolchain:
self.src_path = src_path
self.build_path = build_path
self.ndk_arch = 'arm'
android_abi = 'armeabi-v7a'
ndk_platform = 'android-21'
ndk_arch = abi_info['ndk_arch']
ndk_platform = 'android-14'
# select the NDK compiler
gcc_version = '4.9'
ndk_platform_path = os.path.join(ndk_path, 'platforms', ndk_platform)
sysroot = os.path.join(ndk_path, 'sysroot')
target_root = os.path.join(ndk_platform_path, 'arch-' + self.ndk_arch)
target_root = os.path.join(ndk_platform_path, 'arch-' + ndk_arch)
install_prefix = os.path.join(arch_path, 'root')
@@ -61,11 +80,13 @@ class AndroidNdkToolchain:
self.install_prefix = install_prefix
self.sysroot = sysroot
toolchain_path = os.path.join(ndk_path, 'toolchains', arch + '-' + gcc_version, 'prebuilt', build_arch)
toolchain_path = os.path.join(ndk_path, 'toolchains', abi_info['toolchain_arch'] + '-' + gcc_version, 'prebuilt', build_arch)
llvm_path = os.path.join(ndk_path, 'toolchains', 'llvm', 'prebuilt', build_arch)
llvm_triple = 'armv7-none-linux-androideabi'
llvm_triple = abi_info['llvm_triple']
common_flags = '-march=armv7-a -mfloat-abi=softfp'
common_flags = '-Os -g'
common_flags += ' -fPIC'
common_flags += ' ' + abi_info['cflags']
toolchain_bin = os.path.join(toolchain_path, 'bin')
llvm_bin = os.path.join(llvm_path, 'bin')
@@ -73,17 +94,19 @@ class AndroidNdkToolchain:
self.cxx = os.path.join(llvm_bin, 'clang++')
common_flags += ' -target ' + llvm_triple + ' -integrated-as -gcc-toolchain ' + toolchain_path
common_flags += ' -fvisibility=hidden -fdata-sections -ffunction-sections'
self.ar = os.path.join(toolchain_bin, arch + '-ar')
self.ranlib = os.path.join(toolchain_bin, arch + '-ranlib')
self.nm = os.path.join(toolchain_bin, arch + '-nm')
self.strip = os.path.join(toolchain_bin, arch + '-strip')
self.cflags = '-Os -g ' + common_flags
self.cxxflags = '-Os -g ' + common_flags
self.cflags = common_flags
self.cxxflags = common_flags
self.cppflags = '--sysroot=' + sysroot + \
' -isystem ' + os.path.join(install_prefix, 'include') + \
' -isystem ' + os.path.join(sysroot, 'usr', 'include', arch) + \
' -D__ANDROID_API__=21'
' -D__ANDROID_API__=14'
self.ldflags = '--sysroot=' + sysroot + \
' -L' + os.path.join(install_prefix, 'lib') + \
' -L' + os.path.join(target_root, 'usr', 'lib') + \
@@ -91,22 +114,20 @@ class AndroidNdkToolchain:
' ' + common_flags
self.libs = ''
self.is_arm = self.ndk_arch == 'arm'
self.is_arm = ndk_arch == 'arm'
self.is_armv7 = self.is_arm and 'armv7' in self.cflags
self.is_windows = False
libcxx_path = os.path.join(ndk_path, 'sources/cxx-stl/llvm-libc++')
libcxx_libs_path = os.path.join(libcxx_path, 'libs', android_abi)
libstdcxx_cppflags = '-nostdinc++ -isystem ' + os.path.join(libcxx_path, 'include') + ' -isystem ' + os.path.join(ndk_path, 'sources/android/support/include')
libstdcxx_ldadd = os.path.join(libcxx_libs_path, 'libc++_static.a') + ' ' + os.path.join(libcxx_libs_path, 'libc++abi.a')
if self.is_armv7:
libstdcxx_ldadd += ' ' + os.path.join(libcxx_libs_path, 'libunwind.a')
libstdcxx_flags = '-stdlib=libc++'
libstdcxx_cxxflags = libstdcxx_flags + ' -isystem ' + os.path.join(libcxx_path, 'include') + ' -isystem ' + os.path.join(ndk_path, 'sources/android/support/include')
libstdcxx_ldflags = libstdcxx_flags + ' -static-libstdc++ -L' + libcxx_libs_path
if use_cxx:
self.libs += ' ' + libstdcxx_ldadd
self.cppflags += ' ' + libstdcxx_cppflags
self.cxxflags += ' ' + libstdcxx_cxxflags
self.ldflags += ' ' + libstdcxx_ldflags
self.env = dict(os.environ)
@@ -117,14 +138,15 @@ class AndroidNdkToolchain:
# a list of third-party libraries to be used by MPD on Android
from build.libs import *
thirdparty_libs = [
libmpdclient,
libogg,
libvorbis,
opus,
flac,
libid3tag,
libmad,
ffmpeg,
curl,
libnfs,
boost,
]

@@ -1,10 +1,10 @@
AC_PREREQ(2.60)
AC_INIT(mpd, 0.20.13, musicpd-dev-team@lists.sourceforge.net)
AC_INIT(mpd, 0.20.20, musicpd-dev-team@lists.sourceforge.net)
VERSION_MAJOR=0
VERSION_MINOR=20
VERSION_REVISION=13
VERSION_REVISION=20
VERSION_EXTRA=0
AC_CONFIG_SRCDIR([src/Main.cxx])
@@ -454,7 +454,7 @@ dnl ---------------------------------------------------------------------------
dnl Mandatory Libraries
dnl ---------------------------------------------------------------------------
AX_BOOST_BASE([1.46],, [AC_MSG_ERROR([Boost not found])])
AX_BOOST_BASE([1.54],, [AC_MSG_ERROR([Boost not found])])
AC_ARG_ENABLE(icu,
AS_HELP_STRING([--enable-icu],

@@ -113,7 +113,7 @@
<para>
<varname>musicbrainz_artistid</varname>: the artist id in the
<ulink
url="http://musicbrainz.org/doc/MusicBrainzTag">MusicBrainz</ulink>
url="https://picard.musicbrainz.org/docs/mappings/">MusicBrainz</ulink>
database.
</para>
</listitem>
@@ -122,7 +122,7 @@
<para>
<varname>musicbrainz_albumid</varname>: the album id in the
<ulink
url="http://musicbrainz.org/doc/MusicBrainzTag">MusicBrainz</ulink>
url="https://picard.musicbrainz.org/docs/mappings/">MusicBrainz</ulink>
database.
</para>
</listitem>
@@ -131,7 +131,7 @@
<para>
<varname>musicbrainz_albumartistid</varname>: the album artist
id in the <ulink
url="http://musicbrainz.org/doc/MusicBrainzTag">MusicBrainz</ulink>
url="https://picard.musicbrainz.org/docs/mappings/">MusicBrainz</ulink>
database.
</para>
</listitem>
@@ -140,7 +140,7 @@
<para>
<varname>musicbrainz_trackid</varname>: the track id in the
<ulink
url="http://musicbrainz.org/doc/MusicBrainzTag">MusicBrainz</ulink>
url="https://picard.musicbrainz.org/docs/mappings/">MusicBrainz</ulink>
database.
</para>
</listitem>
@@ -149,7 +149,7 @@
<para>
<varname>musicbrainz_releasetrackid</varname>: the release track
id in the <ulink
url="http://musicbrainz.org/doc/MusicBrainzTag">MusicBrainz</ulink>
url="https://picard.musicbrainz.org/docs/mappings/">MusicBrainz</ulink>
database.
</para>
</listitem>

@@ -66,6 +66,26 @@
</para>
</section>
<section id="install_android">
<title>Installing on Android</title>
<para>
An experimental Android build is available on <ulink
url="https://play.google.com/store/apps/details?id=org.musicpd">Google
Play</ulink>. After installing and launching it, MPD will
scan the music in your <filename>Music</filename> directory
and you can control it as usual with a MPD client.
</para>
<para>
If you need to tweak the configuration, you can create a file
called <filename>mpd.conf</filename> on the data partition
(the directory which is returned by Android's <ulink
url="https://developer.android.com/reference/android/os/Environment.html#getExternalStorageDirectory()">getExternalStorageDirectory()</ulink>
API function).
</para>
</section>
<section id="install_source">
<title>Compiling from source</title>
@@ -94,7 +114,7 @@ cd mpd-version</programlisting>
<listitem>
<para>
<ulink url="http://www.boost.org/">Boost 1.46</ulink>
<ulink url="http://www.boost.org/">Boost 1.54</ulink>
</para>
</listitem>
@@ -248,6 +268,59 @@ apt-get install g++ \
script.
</para>
</section>
<section id="android_build">
<title>Compiling for Android</title>
<para>
MPD can be compiled as an Android app. It can be installed
easily with <link linkend="install_android">Google
Play</link>, but if you want to build it from source, follow
this section.
</para>
<para>
You need:
</para>
<itemizedlist>
<listitem>
<para>
Android SDK
</para>
</listitem>
<listitem>
<para>
<ulink
url="https://developer.android.com/ndk/downloads/index.html">Android
NDK</ulink>
</para>
</listitem>
</itemizedlist>
<para>
Just like with the native build, unpack the
<application>MPD</application> source tarball and change
into the directory. Then, instead of
<command>./configure</command>, type:
</para>
<programlisting>./android/build.py SDK_PATH NDK_PATH ABI
make android/build/mpd-debug.apk</programlisting>
<para>
<varname>SDK_PATH</varname> is the absolute path where you
installed the Android SDK; <varname>NDK_PATH</varname> is
the Android NDK installation path; <varname>ABI</varname> is
the Android ABI to be built, e.g. "armeabi-v7a".
</para>
<para>
This downloads various library sources, and then configures
and builds <application>MPD</application>.
</para>
</section>
</section>
<section id="systemd_socket">
@@ -323,7 +396,9 @@ systemctl start mpd.socket</programlisting>
<application>MPD</application> as a user daemon (and not as a
system daemon), the configuration is read from
<filename>$XDG_CONFIG_HOME/mpd/mpd.conf</filename> (usually
<filename>~/.config/mpd/mpd.conf</filename>).
<filename>~/.config/mpd/mpd.conf</filename>). On Android,
<filename>mpd.conf</filename> will be loaded from the
top-level directory of the data partition.
</para>
<para>

@@ -1,24 +1,37 @@
import os.path, subprocess
import os.path, subprocess, sys
from build.project import Project
from build.makeproject import MakeProject
class AutotoolsProject(Project):
class AutotoolsProject(MakeProject):
def __init__(self, url, md5, installed, configure_args=[],
autogen=False,
autoreconf=False,
cppflags='',
ldflags='',
libs='',
subdirs=None,
**kwargs):
Project.__init__(self, url, md5, installed, **kwargs)
MakeProject.__init__(self, url, md5, installed, **kwargs)
self.configure_args = configure_args
self.autogen = autogen
self.autoreconf = autoreconf
self.cppflags = cppflags
self.ldflags = ldflags
self.libs = libs
self.subdirs = subdirs
def build(self, toolchain):
def configure(self, toolchain):
src = self.unpack(toolchain)
if self.autogen:
subprocess.check_call(['libtoolize', '--force'], cwd=src)
if sys.platform == 'darwin':
subprocess.check_call(['glibtoolize', '--force'], cwd=src)
else:
subprocess.check_call(['libtoolize', '--force'], cwd=src)
subprocess.check_call(['aclocal'], cwd=src)
subprocess.check_call(['automake', '--add-missing', '--force-missing', '--foreign'], cwd=src)
subprocess.check_call(['autoconf'], cwd=src)
if self.autoreconf:
subprocess.check_call(['autoreconf', '-vif'], cwd=src)
build = self.make_build_path(toolchain)
@@ -29,8 +42,8 @@ class AutotoolsProject(Project):
'CFLAGS=' + toolchain.cflags,
'CXXFLAGS=' + toolchain.cxxflags,
'CPPFLAGS=' + toolchain.cppflags + ' ' + self.cppflags,
'LDFLAGS=' + toolchain.ldflags,
'LIBS=' + toolchain.libs,
'LDFLAGS=' + toolchain.ldflags + ' ' + self.ldflags,
'LIBS=' + toolchain.libs + ' ' + self.libs,
'AR=' + toolchain.ar,
'RANLIB=' + toolchain.ranlib,
'STRIP=' + toolchain.strip,
@@ -40,7 +53,12 @@ class AutotoolsProject(Project):
] + self.configure_args
subprocess.check_call(configure, cwd=build, env=toolchain.env)
subprocess.check_call(['/usr/bin/make', '--quiet', '-j12'],
cwd=build, env=toolchain.env)
subprocess.check_call(['/usr/bin/make', '--quiet', 'install'],
cwd=build, env=toolchain.env)
return build
def build(self, toolchain):
build = self.configure(toolchain)
if self.subdirs is not None:
for subdir in self.subdirs:
MakeProject.build(self, toolchain, os.path.join(build, subdir))
else:
MakeProject.build(self, toolchain, build)

@@ -1,28 +1,54 @@
import re
from os.path import abspath
from build.project import Project
from build.zlib import ZlibProject
from build.meson import MesonProject
from build.autotools import AutotoolsProject
from build.ffmpeg import FfmpegProject
from build.boost import BoostProject
libmpdclient = MesonProject(
'https://www.musicpd.org/download/libmpdclient/2/libmpdclient-2.14.tar.xz',
'0a84e2791bfe3077cf22ee1784c805d5bb550803dffe56a39aa3690a38061372',
'lib/libmpdclient.a',
)
libogg = AutotoolsProject(
'http://downloads.xiph.org/releases/ogg/libogg-1.3.2.tar.xz',
'5c3a34309d8b98640827e5d0991a4015',
'http://downloads.xiph.org/releases/ogg/libogg-1.3.3.tar.xz',
'4f3fc6178a533d392064f14776b23c397ed4b9f48f5de297aba73b643f955c08',
'lib/libogg.a',
['--disable-shared', '--enable-static'],
[
'--disable-shared', '--enable-static',
],
)
libvorbis = AutotoolsProject(
'http://downloads.xiph.org/releases/vorbis/libvorbis-1.3.5.tar.xz',
'28cb28097c07a735d6af56e598e1c90f',
'http://downloads.xiph.org/releases/vorbis/libvorbis-1.3.6.tar.xz',
'af00bb5a784e7c9e69f56823de4637c350643deedaf333d0fa86ecdba6fcb415',
'lib/libvorbis.a',
['--disable-shared', '--enable-static'],
[
'--disable-shared', '--enable-static',
],
edits={
# this option is not understood by clang
'configure': lambda data: data.replace('-mno-ieee-fp', ' '),
}
)
opus = AutotoolsProject(
'https://archive.mozilla.org/pub/opus/opus-1.2.1.tar.gz',
'cfafd339ccd9c5ef8d6ab15d7e1a412c054bf4cb4ecbbbcc78c12ef2def70732',
'lib/libopus.a',
['--disable-shared', '--enable-static'],
[
'--disable-shared', '--enable-static',
'--disable-doc',
'--disable-extra-programs',
],
# suppress "visibility default" from opus_defines.h
cppflags='-DOPUS_EXPORT=',
)
flac = AutotoolsProject(
@@ -32,7 +58,9 @@ flac = AutotoolsProject(
[
'--disable-shared', '--enable-static',
'--disable-xmms-plugin', '--disable-cpplibs',
'--disable-doxygen-docs',
],
subdirs=['include', 'src/libFLAC'],
)
zlib = ZlibProject(
@@ -45,21 +73,36 @@ libid3tag = AutotoolsProject(
'ftp://ftp.mars.org/pub/mpeg/libid3tag-0.15.1b.tar.gz',
'e5808ad997ba32c498803822078748c3',
'lib/libid3tag.a',
['--disable-shared', '--enable-static'],
[
'--disable-shared', '--enable-static',
# without this, libid3tag's configure.ac ignores -O* and -f*
'--disable-debugging',
],
autogen=True,
edits={
# fix bug in libid3tag's configure.ac which discards all but the last optimization flag
'configure.ac': lambda data: re.sub(r'optimize="\$1"', r'optimize="$optimize $1"', data, count=1),
}
)
libmad = AutotoolsProject(
'ftp://ftp.mars.org/pub/mpeg/libmad-0.15.1b.tar.gz',
'1be543bc30c56fb6bea1d7bf6a64e66c',
'lib/libmad.a',
['--disable-shared', '--enable-static'],
[
'--disable-shared', '--enable-static',
# without this, libmad's configure.ac ignores -O* and -f*
'--disable-debugging',
],
autogen=True,
)
liblame = AutotoolsProject(
'http://downloads.sourceforge.net/project/lame/lame/3.99/lame-3.99.5.tar.gz',
'24346b4158e4af3bd9f2e194bb23eb473c75fb7377011523353196b19b9a23ff',
'http://downloads.sourceforge.net/project/lame/lame/3.100/lame-3.100.tar.gz',
'ddfe36cab873794038ae2c1210557ad34857a4b6bdc515785d1da9e175b1da1e',
'lib/libmp3lame.a',
[
'--disable-shared', '--enable-static',
@@ -69,8 +112,8 @@ liblame = AutotoolsProject(
)
ffmpeg = FfmpegProject(
'http://ffmpeg.org/releases/ffmpeg-3.4.1.tar.xz',
'5a77278a63741efa74e26bf197b9bb09ac6381b9757391b922407210f0f991c0',
'http://ffmpeg.org/releases/ffmpeg-4.0.tar.xz',
'ed945daf40b124e77a685893cc025d086f638bc703183460aff49508edb3a43f',
'lib/libavcodec.a',
[
'--disable-shared', '--enable-static',
@@ -89,21 +132,217 @@ ffmpeg = FfmpegProject(
'--disable-pixelutils',
'--disable-network',
'--disable-encoders',
'--disable-muxers',
'--disable-protocols',
'--disable-devices',
'--disable-filters',
'--disable-v4l2_m2m',
# clang misinterprets the "B0" in hevc_mvs.c as binary
# literal, which breaks the build; but we don't need that
# video codec anyway
'--disable-parser=bmp',
'--disable-parser=cavsvideo',
'--disable-parser=dvbsub',
'--disable-parser=dvdsub',
'--disable-parser=dvd_nav',
'--disable-parser=flac',
'--disable-parser=g729',
'--disable-parser=gsm',
'--disable-parser=h261',
'--disable-parser=h263',
'--disable-parser=h264',
'--disable-parser=hevc',
'--disable-parser=mjpeg',
'--disable-parser=mlp',
'--disable-parser=mpeg4video',
'--disable-parser=mpegvideo',
'--disable-parser=opus',
'--disable-parser=vc1',
'--disable-parser=vp3',
'--disable-parser=vp8',
'--disable-parser=vp9',
'--disable-parser=png',
'--disable-parser=pnm',
'--disable-parser=xma',
'--disable-demuxer=aqtitle',
'--disable-demuxer=ass',
'--disable-demuxer=bethsoftvid',
'--disable-demuxer=bink',
'--disable-demuxer=cavsvideo',
'--disable-demuxer=cdxl',
'--disable-demuxer=dvbsub',
'--disable-demuxer=dvbtxt',
'--disable-demuxer=h261',
'--disable-demuxer=h263',
'--disable-demuxer=h264',
'--disable-demuxer=ico',
'--disable-demuxer=image2',
'--disable-demuxer=jacosub',
'--disable-demuxer=lrc',
'--disable-demuxer=microdvd',
'--disable-demuxer=mjpeg',
'--disable-demuxer=mjpeg_2000',
'--disable-demuxer=mpegps',
'--disable-demuxer=mpegvideo',
'--disable-demuxer=mpl2',
'--disable-demuxer=mpsub',
'--disable-demuxer=pjs',
'--disable-demuxer=rawvideo',
'--disable-demuxer=realtext',
'--disable-demuxer=sami',
'--disable-demuxer=scc',
'--disable-demuxer=srt',
'--disable-demuxer=stl',
'--disable-demuxer=subviewer',
'--disable-demuxer=subviewer1',
'--disable-demuxer=swf',
'--disable-demuxer=tedcaptions',
'--disable-demuxer=vobsub',
'--disable-demuxer=vplayer',
'--disable-demuxer=webvtt',
'--disable-demuxer=yuv4mpegpipe',
# we don't need these decoders, because we have the dedicated
# libraries
'--disable-decoder=flac',
'--disable-decoder=opus',
'--disable-decoder=vorbis',
# audio codecs nobody uses
'--disable-decoder=atrac1',
'--disable-decoder=atrac3',
'--disable-decoder=atrac3al',
'--disable-decoder=atrac3p',
'--disable-decoder=atrac3pal',
'--disable-decoder=binkaudio_dct',
'--disable-decoder=binkaudio_rdft',
'--disable-decoder=bmv_audio',
'--disable-decoder=dsicinaudio',
'--disable-decoder=dvaudio',
'--disable-decoder=metasound',
'--disable-decoder=paf_audio',
'--disable-decoder=ra_144',
'--disable-decoder=ra_288',
'--disable-decoder=ralf',
'--disable-decoder=qdm2',
'--disable-decoder=qdmc',
# disable lots of image and video codecs
'--disable-decoder=ass',
'--disable-decoder=asv1',
'--disable-decoder=asv2',
'--disable-decoder=apng',
'--disable-decoder=avrn',
'--disable-decoder=avrp',
'--disable-decoder=bethsoftvid',
'--disable-decoder=bink',
'--disable-decoder=bmp',
'--disable-decoder=bmv_video',
'--disable-decoder=cavs',
'--disable-decoder=ccaption',
'--disable-decoder=cdgraphics',
'--disable-decoder=clearvideo',
'--disable-decoder=dirac',
'--disable-decoder=dsicinvideo',
'--disable-decoder=dvbsub',
'--disable-decoder=dvdsub',
'--disable-decoder=dvvideo',
'--disable-decoder=exr',
'--disable-decoder=ffv1',
'--disable-decoder=ffvhuff',
'--disable-decoder=ffwavesynth',
'--disable-decoder=flic',
'--disable-decoder=flv',
'--disable-decoder=fraps',
'--disable-decoder=gif',
'--disable-decoder=h261',
'--disable-decoder=h263',
'--disable-decoder=h263i',
'--disable-decoder=h263p',
'--disable-decoder=h264',
'--disable-decoder=hevc',
'--disable-decoder=hnm4_video',
'--disable-decoder=hq_hqa',
'--disable-decoder=hqx',
'--disable-decoder=idcin',
'--disable-decoder=iff_ilbm',
'--disable-decoder=indeo2',
'--disable-decoder=indeo3',
'--disable-decoder=indeo4',
'--disable-decoder=indeo5',
'--disable-decoder=interplay_video',
'--disable-decoder=jacosub',
'--disable-decoder=jpeg2000',
'--disable-decoder=jpegls',
'--disable-decoder=microdvd',
'--disable-decoder=mimic',
'--disable-decoder=mjpeg',
'--disable-decoder=mmvideo',
'--disable-decoder=mpl2',
'--disable-decoder=motionpixels',
'--disable-decoder=mpeg1video',
'--disable-decoder=mpeg2video',
'--disable-decoder=mpeg4',
'--disable-decoder=mpegvideo',
'--disable-decoder=mscc',
'--disable-decoder=msmpeg4_crystalhd',
'--disable-decoder=msmpeg4v1',
'--disable-decoder=msmpeg4v2',
'--disable-decoder=msmpeg4v3',
'--disable-decoder=msvideo1',
'--disable-decoder=mszh',
'--disable-decoder=mvc1',
'--disable-decoder=mvc2',
'--disable-decoder=on2avc',
'--disable-decoder=paf_video',
'--disable-decoder=png',
'--disable-decoder=qdraw',
'--disable-decoder=qpeg',
'--disable-decoder=rawvideo',
'--disable-decoder=realtext',
'--disable-decoder=roq',
'--disable-decoder=roq_dpcm',
'--disable-decoder=rscc',
'--disable-decoder=rv10',
'--disable-decoder=rv20',
'--disable-decoder=rv30',
'--disable-decoder=rv40',
'--disable-decoder=sami',
'--disable-decoder=sheervideo',
'--disable-decoder=snow',
'--disable-decoder=srt',
'--disable-decoder=stl',
'--disable-decoder=subrip',
'--disable-decoder=subviewer',
'--disable-decoder=subviewer1',
'--disable-decoder=svq1',
'--disable-decoder=svq3',
'--disable-decoder=tiff',
'--disable-decoder=tiertexseqvideo',
'--disable-decoder=truemotion1',
'--disable-decoder=truemotion2',
'--disable-decoder=truemotion2rt',
'--disable-decoder=twinvq',
'--disable-decoder=utvideo',
'--disable-decoder=vc1',
'--disable-decoder=vmdvideo',
'--disable-decoder=vp3',
'--disable-decoder=vp5',
'--disable-decoder=vp6',
'--disable-decoder=vp7',
'--disable-decoder=vp8',
'--disable-decoder=vp9',
'--disable-decoder=vqa',
'--disable-decoder=webvtt',
'--disable-decoder=wmv1',
'--disable-decoder=wmv2',
'--disable-decoder=wmv3',
'--disable-decoder=yuv4',
],
)
curl = AutotoolsProject(
'http://curl.haxx.se/download/curl-7.57.0.tar.xz',
'f5f6fd3c72b7b8389969f4fb671ed8532fa9b5bb7a5cae7ca89bc1cea45c7878',
'http://curl.haxx.se/download/curl-7.60.0.tar.xz',
'8736ff8ded89ddf7e926eec7b16f82597d029fc1469f3a551f1fafaac164e6a0',
'lib/libcurl.a',
[
'--disable-shared', '--enable-static',
@@ -114,16 +353,34 @@ curl = AutotoolsProject(
'--disable-ldap', '--disable-ldaps',
'--disable-rtsp', '--disable-proxy', '--disable-dict', '--disable-telnet',
'--disable-tftp', '--disable-pop3', '--disable-imap', '--disable-smtp',
'--disable-smb',
'--disable-gopher',
'--disable-manual',
'--disable-threaded-resolver', '--disable-verbose', '--disable-sspi',
'--disable-crypto-auth', '--disable-ntlm-wb', '--disable-tls-srp', '--disable-cookies',
'--without-ssl', '--without-gnutls', '--without-nss', '--without-libssh2',
],
patches='src/lib/curl/patches',
)
libnfs = AutotoolsProject(
'https://github.com/sahlberg/libnfs/archive/libnfs-2.0.0.tar.gz',
'7ea6cd8fa6c461d01091e584d424d28e137d23ff4b65b95d01a3fd0ef95d120e',
'lib/libnfs.a',
[
'--disable-shared', '--enable-static',
'--disable-debug',
# work around -Wtautological-compare
'--disable-werror',
],
base='libnfs-libnfs-2.0.0',
autoreconf=True,
)
boost = BoostProject(
'http://downloads.sourceforge.net/project/boost/boost/1.65.1/boost_1_65_1.tar.bz2',
'9807a5d16566c57fd74fb522764e0b134a8bbe6b6e8967b83afefd30dcd3be81',
'http://downloads.sourceforge.net/project/boost/boost/1.66.0/boost_1_66_0.tar.bz2',
'5721818253e6a0989583192f96782c4a98eb6204965316df9f5ad75819225ca9',
'include/boost/version.hpp',
)

@@ -0,0 +1,28 @@
import subprocess
from build.project import Project
class MakeProject(Project):
def __init__(self, url, md5, installed,
install_target='install',
**kwargs):
Project.__init__(self, url, md5, installed, **kwargs)
self.install_target = install_target
def get_simultaneous_jobs(self):
return 12
def get_make_args(self, toolchain):
return ['--quiet', '-j' + str(self.get_simultaneous_jobs())]
def get_make_install_args(self, toolchain):
return ['--quiet', self.install_target]
def make(self, toolchain, wd, args):
subprocess.check_call(['/usr/bin/make'] + args,
cwd=wd, env=toolchain.env)
def build(self, toolchain, wd, install=True):
self.make(toolchain, wd, self.get_make_args(toolchain))
if install:
self.make(toolchain, wd, self.get_make_install_args(toolchain))

96
python/build/meson.py Normal file

@@ -0,0 +1,96 @@
import os.path, subprocess, sys
from build.project import Project
class MesonProject(Project):
def __init__(self, url, md5, installed, configure_args=[],
**kwargs):
Project.__init__(self, url, md5, installed, **kwargs)
self.configure_args = configure_args
def _make_cross_file(self, toolchain):
if toolchain.is_windows:
system = 'windows'
else:
system = 'linux'
if toolchain.is_arm:
cpu_family = 'arm'
if toolchain.is_armv7:
cpu = 'armv7'
else:
cpu = 'armv6'
else:
cpu_family = 'x86'
if 'x86_64' in toolchain.arch:
cpu = 'x86_64'
else:
cpu = 'i686'
# TODO: support more CPUs
endian = 'little'
# TODO: write pkg-config wrapper
path = os.path.join(toolchain.build_path, 'meson.cross')
os.makedirs(toolchain.build_path, exist_ok=True)
with open(path, 'w') as f:
f.write("""
[binaries]
c = '%s'
cpp = '%s'
ar = '%s'
strip = '%s'
[properties]
root = '%s'
c_args = %s
c_link_args = %s
cpp_args = %s
cpp_link_args = %s
[host_machine]
system = '%s'
cpu_family = '%s'
cpu = '%s'
endian = '%s'
""" % (toolchain.cc, toolchain.cxx, toolchain.ar, toolchain.strip,
toolchain.install_prefix,
repr((toolchain.cppflags + ' ' + toolchain.cflags).split()),
repr(toolchain.ldflags.split()),
repr((toolchain.cppflags + ' ' + toolchain.cxxflags).split()),
repr(toolchain.ldflags.split()),
system, cpu_family, cpu, endian))
return path
def configure(self, toolchain):
src = self.unpack(toolchain)
cross_file = self._make_cross_file(toolchain)
build = self.make_build_path(toolchain)
configure = [
'meson',
src, build,
'--prefix', toolchain.install_prefix,
# this is necessary because Meson uses Debian's build machine
# MultiArch path (e.g. "lib/x86_64-linux-gnu") for cross
# builds, which is obviously wrong
'--libdir', 'lib',
'--buildtype', 'plain',
'--default-library=static',
'--cross-file', cross_file,
] + self.configure_args
subprocess.check_call(configure, env=toolchain.env)
return build
def build(self, toolchain):
build = self.configure(toolchain)
subprocess.check_call(['ninja', 'install'],
cwd=build, env=toolchain.env)

@@ -3,10 +3,13 @@ import re
from build.download import download_and_verify
from build.tar import untar
from build.quilt import push_all
class Project:
def __init__(self, url, md5, installed, name=None, version=None,
base=None,
patches=None,
edits=None,
use_cxx=False):
if base is None:
basename = os.path.basename(url)
@@ -17,7 +20,7 @@ class Project:
self.base = base
if name is None or version is None:
m = re.match(r'^([-\w]+)-(\d[\d.]*[a-z]?)$', self.base)
m = re.match(r'^([-\w]+)-(\d[\d.]*[a-z]?[\d.]*)$', self.base)
if name is None: name = m.group(1)
if version is None: version = m.group(2)
@@ -28,6 +31,11 @@ class Project:
self.md5 = md5
self.installed = installed
if patches is not None:
srcdir = os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
patches = os.path.join(srcdir, patches)
self.patches = patches
self.edits = edits
self.use_cxx = use_cxx
def download(self, toolchain):
@@ -47,7 +55,21 @@ class Project:
parent_path = toolchain.src_path
else:
parent_path = toolchain.build_path
return untar(self.download(toolchain), parent_path, self.base)
path = untar(self.download(toolchain), parent_path, self.base)
if self.patches is not None:
push_all(toolchain, path, self.patches)
if self.edits is not None:
for filename, function in self.edits.items():
with open(os.path.join(path, filename), 'r+t') as f:
old_data = f.read()
new_data = function(old_data)
f.seek(0)
f.truncate(0)
f.write(new_data)
return path
def make_build_path(self, toolchain):
path = os.path.join(toolchain.build_path, self.base)

9
python/build/quilt.py Normal file

@@ -0,0 +1,9 @@
import subprocess
def run_quilt(toolchain, cwd, patches_path, *args):
env = dict(toolchain.env)
env['QUILT_PATCHES'] = patches_path
subprocess.check_call(['quilt'] + list(args), cwd=cwd, env=env)
def push_all(toolchain, src_path, patches_path):
run_quilt(toolchain, src_path, patches_path, 'push', '-a')

@@ -27,12 +27,17 @@
#include <assert.h>
static struct {
static struct IOThread {
Mutex mutex;
Cond cond;
EventLoop *loop;
Thread thread;
IOThread():thread(BIND_THIS_METHOD(Run)) {}
private:
void Run() noexcept;
} io;
void
@@ -44,15 +49,15 @@ io_thread_run(void)
io.loop->Run();
}
static void
io_thread_func(gcc_unused void *arg)
inline void
IOThread::Run() noexcept
{
SetThreadName("io");
/* lock+unlock to synchronize with io_thread_start(), to be
sure that io.thread is set */
io.mutex.lock();
io.mutex.unlock();
mutex.lock();
mutex.unlock();
io_thread_run();
}
@@ -73,7 +78,7 @@ io_thread_start()
assert(!io.thread.IsDefined());
const std::lock_guard<Mutex> protect(io.mutex);
io.thread.Start(io_thread_func, nullptr);
io.thread.Start();
}
void
@@ -103,8 +108,12 @@ io_thread_get() noexcept
return *io.loop;
}
#ifndef NDEBUG
bool
io_thread_inside() noexcept
{
return io.thread.IsInside();
}
#endif

@@ -20,6 +20,7 @@
#ifndef MPD_IO_THREAD_HXX
#define MPD_IO_THREAD_HXX
#include "check.h"
#include "Compiler.h"
class EventLoop;
@@ -53,6 +54,8 @@ gcc_const
EventLoop &
io_thread_get() noexcept;
#ifndef NDEBUG
/**
* Is the current thread the I/O thread?
*/
@@ -61,3 +64,5 @@ bool
io_thread_inside() noexcept;
#endif
#endif

@@ -50,6 +50,7 @@
#include "unix/SignalHandlers.hxx"
#include "system/FatalError.hxx"
#include "thread/Slack.hxx"
#include "net/Init.hxx"
#include "lib/icu/Init.hxx"
#include "config/ConfigGlobal.hxx"
#include "config/Param.hxx"
@@ -106,15 +107,6 @@
#include <locale.h>
#endif
#ifdef _WIN32
#include <winsock2.h>
#include <ws2tcpip.h>
#endif
#ifdef __BLOCKS__
#include <dispatch/dispatch.h>
#endif
#include <limits.h>
static constexpr size_t KILOBYTE = 1024;
@@ -284,25 +276,6 @@ glue_state_file_init()
instance->state_file->Read();
}
/**
* Windows-only initialization of the Winsock2 library.
*/
static void winsock_init(void)
{
#ifdef _WIN32
WSADATA sockinfo;
int retval = WSAStartup(MAKEWORD(2, 2), &sockinfo);
if(retval != 0)
FormatFatalError("Attempt to open Winsock2 failed; error code %d",
retval);
if (LOBYTE(sockinfo.wVersion) != 2)
FatalError("We use Winsock2 but your version is either too new "
"or old; please install Winsock 2.x");
#endif
}
/**
* Initialize the decoder and player core, including the music pipe.
*/
@@ -451,7 +424,8 @@ try {
IcuInit();
winsock_init();
const ScopeNetInit net_init;
io_thread_init();
config_global_init();
@@ -505,21 +479,8 @@ try {
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(config));
});
dispatch_main();
return EXIT_FAILURE; // unreachable, because dispatch_main never returns
#else
return mpd_main_after_fork(config);
#endif
} catch (const std::exception &e) {
LogError(e);
return EXIT_FAILURE;
@@ -702,10 +663,6 @@ try {
daemonize_finish();
#endif
#ifdef _WIN32
WSACleanup();
#endif
IcuFinish();
log_deinit();

@@ -95,6 +95,7 @@ public:
*/
gcc_pure
const MusicChunk *Peek() const noexcept {
const std::lock_guard<Mutex> protect(mutex);
return head;
}
@@ -120,6 +121,7 @@ public:
*/
gcc_pure
unsigned GetSize() const noexcept {
const std::lock_guard<Mutex> protect(mutex);
return size;
}

@@ -24,6 +24,8 @@
#include "tag/Tag.hxx"
#include "util/ConstBuffer.hxx"
#include "util/StringAPI.hxx"
#include "util/StringCompare.hxx"
#include "util/StringView.hxx"
#include "util/ASCII.hxx"
#include "util/TimeParser.hxx"
#include "util/UriUtil.hxx"
@@ -59,13 +61,13 @@ locate_parse_type(const char *str) noexcept
SongFilter::Item::Item(unsigned _tag, const char *_value, bool _fold_case)
:tag(_tag),
value(AllocatedString<>::Duplicate(_value)),
value(_value),
fold_case(_fold_case ? IcuCompare(value.c_str()) : IcuCompare())
{
}
SongFilter::Item::Item(unsigned _tag, time_t _time)
:tag(_tag), value(nullptr), time(_time)
:tag(_tag), time(_time)
{
}
@@ -274,3 +276,33 @@ SongFilter::GetBase() const noexcept
return nullptr;
}
SongFilter
SongFilter::WithoutBasePrefix(const char *_prefix) const noexcept
{
const StringView prefix(_prefix);
SongFilter result;
for (const auto &i : items) {
if (i.GetTag() == LOCATE_TAG_BASE_TYPE) {
const char *s = StringAfterPrefix(i.GetValue(), prefix);
if (s != nullptr) {
if (*s == 0)
continue;
if (*s == '/') {
++s;
if (*s != 0)
result.items.emplace_back(LOCATE_TAG_BASE_TYPE, s);
continue;
}
}
}
result.items.emplace_back(i);
}
return result;
}

@@ -21,9 +21,9 @@
#define MPD_SONG_FILTER_HXX
#include "lib/icu/Compare.hxx"
#include "util/AllocatedString.hxx"
#include "Compiler.h"
#include <string>
#include <list>
#include <stdint.h>
@@ -49,7 +49,7 @@ public:
class Item {
uint8_t tag;
AllocatedString<> value;
std::string value;
/**
* This value is only set if case folding is enabled.
@@ -66,11 +66,6 @@ public:
Item(unsigned tag, const char *value, bool fold_case=false);
Item(unsigned tag, time_t time);
Item(const Item &other) = delete;
Item(Item &&) = default;
Item &operator=(const Item &other) = delete;
unsigned GetTag() const {
return tag;
}
@@ -157,6 +152,13 @@ public:
*/
gcc_pure
const char *GetBase() const noexcept;
/**
* Create a copy of the filter with the given prefix stripped
* from all #LOCATE_TAG_BASE_TYPE items. This is used to
* filter songs in mounted databases.
*/
SongFilter WithoutBasePrefix(const char *prefix) const noexcept;
};
/**

@@ -28,6 +28,7 @@
#include "tag/TagBuilder.hxx"
#include "util/StringUtil.hxx"
#include "util/RuntimeError.hxx"
#include "util/NumberParser.hxx"
#include <string.h>
#include <stdlib.h>
@@ -94,7 +95,7 @@ song_load(TextFile &file, const char *uri)
if ((type = tag_name_parse(line)) != TAG_NUM_OF_ITEM_TYPES) {
tag.AddItem(type, value);
} else if (strcmp(line, "Time") == 0) {
tag.SetDuration(SignedSongTime::FromS(atof(value)));
tag.SetDuration(SignedSongTime::FromS(ParseDouble(value)));
} else if (strcmp(line, "Playlist") == 0) {
tag.SetHasPlaylist(strcmp(value, "yes") == 0);
} else if (strcmp(line, SONG_MTIME) == 0) {

@@ -162,7 +162,7 @@ Bzip2InputStream::FillBuffer()
if (bzstream.avail_in > 0)
return true;
size_t count = archive->istream->Read(buffer, sizeof(buffer));
size_t count = archive->istream->LockRead(buffer, sizeof(buffer));
if (count == 0)
return false;
@@ -174,6 +174,8 @@ Bzip2InputStream::FillBuffer()
size_t
Bzip2InputStream::Read(void *ptr, size_t length)
{
const ScopeUnlock unlock(mutex);
int bz_result;
size_t nbytes = 0;
@@ -224,4 +226,3 @@ const ArchivePlugin bz2_archive_plugin = {
bz2_open,
bz2_extensions,
};

@@ -115,7 +115,12 @@ Iso9660ArchiveFile::Visit(char *path, size_t length, size_t capacity,
visitor.VisitArchiveEntry(path + 1);
}
}
#if LIBCDIO_VERSION_NUM >= 20000
iso9660_filelist_free(entlist);
#else
_cdio_list_free (entlist, true);
#endif
}
static ArchiveFile *
@@ -182,6 +187,8 @@ Iso9660ArchiveFile::OpenStream(const char *pathname,
size_t
Iso9660InputStream::Read(void *ptr, size_t read_size)
{
const ScopeUnlock unlock(mutex);
int readed = 0;
int no_blocks, cur_block;
size_t left_bytes = statbuf->size - offset;

@@ -138,6 +138,8 @@ ZzipArchiveFile::OpenStream(const char *pathname,
size_t
ZzipInputStream::Read(void *ptr, size_t read_size)
{
const ScopeUnlock unlock(mutex);
int ret = zzip_file_read(file, ptr, read_size);
if (ret < 0)
throw std::runtime_error("zzip_file_read() has failed");
@@ -155,6 +157,8 @@ ZzipInputStream::IsEOF() noexcept
void
ZzipInputStream::Seek(offset_type new_offset)
{
const ScopeUnlock unlock(mutex);
zzip_off_t ofs = zzip_seek(file, new_offset, SEEK_SET);
if (ofs < 0)
throw std::runtime_error("zzip_seek() has failed");

@@ -123,6 +123,10 @@ ToAck(std::exception_ptr ep) noexcept
return ACK_ERROR_SYSTEM;
} catch (const std::invalid_argument &e) {
return ACK_ERROR_ARG;
} catch (const std::length_error &e) {
return ACK_ERROR_ARG;
} catch (const std::out_of_range &e) {
return ACK_ERROR_ARG;
#ifdef GLIBCXX_49X
} catch (const std::exception &e) {
#else

@@ -325,6 +325,34 @@ SendConstraints(mpd_connection *connection, const DatabaseSelection &selection)
return true;
}
static bool
SendGroupMask(mpd_connection *connection, tag_mask_t mask)
{
#if LIBMPDCLIENT_CHECK_VERSION(2,12,0)
for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) {
if ((mask & (tag_mask_t(1) << i)) == 0)
continue;
const auto tag = Convert(TagType(i));
if (tag == MPD_TAG_COUNT)
throw std::runtime_error("Unsupported tag");
if (!mpd_search_add_group_tag(connection, tag))
return false;
}
return true;
#else
(void)connection;
(void)mask;
if (mask != 0)
throw std::runtime_error("Grouping requires libmpdclient 2.12");
return true;
#endif
}
ProxyDatabase::ProxyDatabase(EventLoop &_loop, DatabaseListener &_listener,
const ConfigBlock &block)
:Database(proxy_db_plugin),
@@ -682,7 +710,7 @@ static void
SearchSongs(struct mpd_connection *connection,
const DatabaseSelection &selection,
VisitSong visit_song)
{
try {
assert(selection.recursive);
assert(visit_song);
@@ -709,6 +737,11 @@ SearchSongs(struct mpd_connection *connection,
if (!mpd_response_finish(connection))
ThrowError(connection);
} catch (...) {
if (connection != nullptr)
mpd_search_cancel(connection);
throw;
}
/**
@@ -756,9 +789,9 @@ ProxyDatabase::Visit(const DatabaseSelection &selection,
void
ProxyDatabase::VisitUniqueTags(const DatabaseSelection &selection,
TagType tag_type,
gcc_unused tag_mask_t group_mask,
tag_mask_t group_mask,
VisitTag visit_tag) const
{
try {
// TODO: eliminate the const_cast
const_cast<ProxyDatabase *>(this)->EnsureConnected();
@@ -767,32 +800,47 @@ ProxyDatabase::VisitUniqueTags(const DatabaseSelection &selection,
throw std::runtime_error("Unsupported tag");
if (!mpd_search_db_tags(connection, tag_type2) ||
!SendConstraints(connection, selection))
!SendConstraints(connection, selection) ||
!SendGroupMask(connection, group_mask))
ThrowError(connection);
// TODO: use group_mask
if (!mpd_search_commit(connection))
ThrowError(connection);
while (auto *pair = mpd_recv_pair_tag(connection, tag_type2)) {
TagBuilder builder;
while (auto *pair = mpd_recv_pair(connection)) {
AtScopeExit(this, pair) {
mpd_return_pair(connection, pair);
};
TagBuilder tag;
tag.AddItem(tag_type, pair->value);
const auto current_type = tag_name_parse_i(pair->name);
if (current_type == TAG_NUM_OF_ITEM_TYPES)
continue;
if (tag.IsEmpty())
if (current_type == tag_type && !builder.IsEmpty()) {
try {
visit_tag(builder.Commit());
} catch (...) {
mpd_response_finish(connection);
throw;
}
}
builder.AddItem(current_type, pair->value);
if (!builder.HasType(current_type))
/* if no tag item has been added, then the
given value was not acceptable
(e.g. empty); forcefully insert an empty
tag in this case, as the caller expects the
given tag type to be present */
tag.AddEmptyItem(tag_type);
builder.AddEmptyItem(current_type);
}
if (!builder.IsEmpty()) {
try {
visit_tag(tag.Commit());
visit_tag(builder.Commit());
} catch (...) {
mpd_response_finish(connection);
throw;
@@ -801,6 +849,11 @@ ProxyDatabase::VisitUniqueTags(const DatabaseSelection &selection,
if (!mpd_response_finish(connection))
ThrowError(connection);
} catch (...) {
if (connection != nullptr)
mpd_search_cancel(connection);
throw;
}
DatabaseStats

@@ -82,10 +82,11 @@ directory_save(BufferedOutputStream &os, const Directory &directory)
}
for (const auto &child : directory.children) {
os.Format(DIRECTORY_DIR "%s\n", child.GetName());
if (child.IsMount())
continue;
if (!child.IsMount())
directory_save(os, child);
os.Format(DIRECTORY_DIR "%s\n", child.GetName());
directory_save(os, child);
}
for (const auto &song : directory.songs)

@@ -20,18 +20,12 @@
#include "config.h"
#include "Mount.hxx"
#include "PrefixedLightSong.hxx"
#include "SongFilter.hxx"
#include "db/Selection.hxx"
#include "db/LightDirectory.hxx"
#include "db/Interface.hxx"
#include "fs/Traits.hxx"
#ifdef _LIBCPP_VERSION
/* workaround for "error: incomplete type 'PlaylistInfo' used in type
trait expression" with libc++ version 3900 (from Android NDK
r13b) */
#include "db/PlaylistInfo.hxx"
#endif
#include <string>
struct PrefixedLightDirectory : LightDirectory {
@@ -93,5 +87,16 @@ WalkMount(const char *base, const Database &db,
vp = std::bind(PrefixVisitPlaylist,
base, std::ref(visit_playlist), _1, _2);
SongFilter prefix_filter;
if (base != nullptr && filter != nullptr) {
/* if the SongFilter contains a LOCATE_TAG_BASE_TYPE
item, copy the SongFilter and drop the mount point
from the filter, because the mounted database
doesn't know its own location within MPD's VFS */
prefix_filter = filter->WithoutBasePrefix(base);
filter = &prefix_filter;
}
db.Visit(DatabaseSelection(uri, recursive, filter), vd, vs, vp);
}

@@ -49,6 +49,10 @@ struct UpdateQueueItem {
bool IsDefined() const {
return id != 0;
}
void Clear() {
id = 0;
}
};
class UpdateQueue {

@@ -29,6 +29,7 @@
#include "Idle.hxx"
#include "Log.hxx"
#include "thread/Thread.hxx"
#include "thread/Name.hxx"
#include "thread/Util.hxx"
#ifndef NDEBUG
@@ -43,6 +44,7 @@ UpdateService::UpdateService(EventLoop &_loop, SimpleDatabase &_db,
:DeferredMonitor(_loop),
db(_db), storage(_storage),
listener(_listener),
update_thread(BIND_THIS_METHOD(Task)),
update_task_id(0),
walk(nullptr)
{
@@ -112,6 +114,8 @@ UpdateService::Task()
{
assert(walk != nullptr);
SetThreadName("update");
if (!next.path_utf8.empty())
FormatDebug(update_domain, "starting: %s",
next.path_utf8.c_str());
@@ -140,13 +144,6 @@ UpdateService::Task()
DeferredMonitor::Schedule();
}
void
UpdateService::Task(void *ctx)
{
UpdateService &service = *(UpdateService *)ctx;
return service.Task();
}
void
UpdateService::StartThread(UpdateQueueItem &&i)
{
@@ -158,7 +155,7 @@ UpdateService::StartThread(UpdateQueueItem &&i)
next = std::move(i);
walk = new UpdateWalk(GetEventLoop(), listener, *next.storage);
update_thread.Start(Task, this);
update_thread.Start();
FormatDebug(update_domain,
"spawned thread for update job id %i", next.id);
@@ -258,7 +255,7 @@ UpdateService::RunDeferred()
delete walk;
walk = nullptr;
next = UpdateQueueItem();
next.Clear();
idle_add(IDLE_UPDATE);

@@ -98,7 +98,6 @@ private:
/* the update thread */
void Task();
static void Task(void *ctx);
void StartThread(UpdateQueueItem &&i);

@@ -300,6 +300,7 @@ DecoderBridge::CommandFinished()
initial_seek_running = false;
timestamp = dc.start_time.ToDoubleS();
absolute_frame = dc.start_time.ToScale<uint64_t>(dc.in_audio_format.sample_rate);
return;
}
@@ -319,6 +320,7 @@ DecoderBridge::CommandFinished()
convert->Reset();
timestamp = dc.seek_time.ToDoubleS();
absolute_frame = dc.seek_time.ToScale<uint64_t>(dc.in_audio_format.sample_rate);
}
dc.command = DecoderCommand::NONE;
@@ -427,6 +429,7 @@ DecoderBridge::SubmitTimestamp(double t)
assert(t >= 0);
timestamp = t;
absolute_frame = uint64_t(t * dc.in_audio_format.sample_rate);
}
DecoderCommand
@@ -464,6 +467,29 @@ DecoderBridge::SubmitData(InputStream *is,
return cmd;
}
cmd = DecoderCommand::NONE;
const size_t frame_size = dc.in_audio_format.GetFrameSize();
size_t data_frames = length / frame_size;
if (dc.end_time.IsPositive()) {
/* enforce the given end time */
const uint64_t end_frame =
dc.end_time.ToScale<uint64_t>(dc.in_audio_format.sample_rate);
if (absolute_frame >= end_frame)
return DecoderCommand::STOP;
const uint64_t remaining_frames = end_frame - absolute_frame;
if (data_frames >= remaining_frames) {
/* past the end of the range: truncate this
data submission and stop the decoder */
data_frames = remaining_frames;
length = data_frames * frame_size;
cmd = DecoderCommand::STOP;
}
}
if (convert != nullptr) {
assert(dc.in_audio_format != dc.out_audio_format);
@@ -521,15 +547,11 @@ DecoderBridge::SubmitData(InputStream *is,
timestamp += (double)nbytes /
dc.out_audio_format.GetTimeToSize();
if (dc.end_time.IsPositive() &&
timestamp >= dc.end_time.ToDoubleS())
/* the end of this range has been reached:
stop decoding */
return DecoderCommand::STOP;
}
return DecoderCommand::NONE;
absolute_frame += data_frames;
return cmd;
}
DecoderCommand

@@ -49,6 +49,11 @@ public:
*/
double timestamp = 0;
/**
* The time stamp of the next data chunk, in PCM frames.
*/
uint64_t absolute_frame = 0;
/**
* Is the initial seek (to the start position of the sub-song)
* pending, or has it been performed already?

@@ -30,7 +30,8 @@
DecoderControl::DecoderControl(Mutex &_mutex, Cond &_client_cond,
const AudioFormat _configured_audio_format,
const ReplayGainConfig &_replay_gain_config)
:mutex(_mutex), client_cond(_client_cond),
:thread(BIND_THIS_METHOD(RunThread)),
mutex(_mutex), client_cond(_client_cond),
configured_audio_format(_configured_audio_format),
replay_gain_config(_replay_gain_config) {}

@@ -308,9 +308,14 @@ struct DecoderControl {
bool IsCurrentSong(const DetachedSong &_song) const noexcept;
gcc_pure
bool LockIsCurrentSong(const DetachedSong &_song) const noexcept {
bool IsSeekableCurrentSong(const DetachedSong &_song) const noexcept {
return seekable && IsCurrentSong(_song);
}
gcc_pure
bool LockIsSeeakbleCurrentSong(const DetachedSong &_song) const noexcept {
const std::lock_guard<Mutex> protect(mutex);
return IsCurrentSong(_song);
return IsSeekableCurrentSong(_song);
}
private:
@@ -415,6 +420,9 @@ public:
* mixramp_start/mixramp_end.
*/
void CycleMixRamp();
private:
void RunThread();
};
#endif

@@ -513,30 +513,28 @@ try {
dc.client_cond.signal();
}
static void
decoder_task(void *arg)
void
DecoderControl::RunThread()
{
DecoderControl &dc = *(DecoderControl *)arg;
SetThreadName("decoder");
const std::lock_guard<Mutex> protect(dc.mutex);
const std::lock_guard<Mutex> protect(mutex);
do {
assert(dc.state == DecoderState::STOP ||
dc.state == DecoderState::ERROR);
assert(state == DecoderState::STOP ||
state == DecoderState::ERROR);
switch (dc.command) {
switch (command) {
case DecoderCommand::START:
dc.CycleMixRamp();
dc.replay_gain_prev_db = dc.replay_gain_db;
dc.replay_gain_db = 0;
CycleMixRamp();
replay_gain_prev_db = replay_gain_db;
replay_gain_db = 0;
decoder_run(dc);
decoder_run(*this);
if (dc.state == DecoderState::ERROR) {
if (state == DecoderState::ERROR) {
try {
std::rethrow_exception(dc.error);
std::rethrow_exception(error);
} catch (const std::exception &e) {
LogError(e);
} catch (...) {
@@ -552,20 +550,20 @@ decoder_task(void *arg)
/* we need to clear the pipe here; usually the
PlayerThread is responsible, but it is not
aware that the decoder has finished */
dc.pipe->Clear(*dc.buffer);
pipe->Clear(*buffer);
decoder_run(dc);
decoder_run(*this);
break;
case DecoderCommand::STOP:
dc.CommandFinishedLocked();
CommandFinishedLocked();
break;
case DecoderCommand::NONE:
dc.Wait();
Wait();
break;
}
} while (dc.command != DecoderCommand::NONE || !dc.quit);
} while (command != DecoderCommand::NONE || !quit);
}
void
@@ -574,5 +572,5 @@ decoder_thread_start(DecoderControl &dc)
assert(!dc.thread.IsDefined());
dc.quit = false;
dc.thread.Start(decoder_task, &dc);
dc.thread.Start();
}

@@ -128,7 +128,7 @@ dsdlib_tag_id3(InputStream &is,
return;
const auto count64 = size - tagoffset;
if (count64 < 10 || count64 > 1024 * 1024)
if (count64 < 10 || count64 > 4 * 1024 * 1024)
return;
if (!dsdlib_skip_to(nullptr, is, tagoffset))

@@ -484,6 +484,8 @@ static const char *const dsdiff_suffixes[] = {
static const char *const dsdiff_mime_types[] = {
"application/x-dff",
"audio/x-dff",
"audio/x-dsd",
nullptr
};

@@ -358,6 +358,8 @@ static const char *const dsf_suffixes[] = {
static const char *const dsf_mime_types[] = {
"application/x-dsf",
"audio/x-dsf",
"audio/x-dsd",
nullptr
};

@@ -24,7 +24,6 @@
#include "config.h"
#include "FlacCommon.hxx"
#include "FlacMetadata.hxx"
#include "util/ConstBuffer.hxx"
#include "Log.hxx"
#include <stdexcept>
@@ -143,25 +142,10 @@ FlacDecoder::OnWrite(const FLAC__Frame &frame,
if (!initialized && !OnFirstFrame(frame.header))
return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT;
const auto data = pcm_import.Import(buf, frame.header.blocksize);
chunk = pcm_import.Import(buf, frame.header.blocksize);
unsigned bit_rate = nbytes * 8 * frame.header.sample_rate /
kbit_rate = nbytes * 8 * frame.header.sample_rate /
(1000 * frame.header.blocksize);
auto cmd = GetClient()->SubmitData(GetInputStream(),
data.data, data.size,
bit_rate);
switch (cmd) {
case DecoderCommand::NONE:
case DecoderCommand::START:
break;
case DecoderCommand::STOP:
return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT;
case DecoderCommand::SEEK:
return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE;
}
return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE;
}

@@ -27,6 +27,7 @@
#include "FlacInput.hxx"
#include "FlacPcm.hxx"
#include "../DecoderAPI.hxx"
#include "util/ConstBuffer.hxx"
#include <FLAC/stream_decoder.h>
@@ -41,6 +42,12 @@ struct FlacDecoder : public FlacInput {
*/
bool unsupported = false;
/**
* The kbit_rate parameter for the next
* DecoderBridge::SubmitData() call.
*/
uint16_t kbit_rate;
FlacPcmImport pcm_import;
/**
@@ -51,6 +58,13 @@ struct FlacDecoder : public FlacInput {
Tag tag;
/**
* Decoded PCM data obtained by our libFLAC write callback.
* If this is non-empty, then DecoderBridge::SubmitData()
* should be called.
*/
ConstBuffer<void> chunk = nullptr;
FlacDecoder(DecoderClient &_client, InputStream &_input_stream)
:FlacInput(_input_stream, &_client) {}

@@ -139,19 +139,40 @@ flac_decoder_initialize(FlacDecoder *data, FLAC__StreamDecoder *sd)
return data->initialized;
}
static DecoderCommand
FlacSubmitToClient(DecoderClient &client, FlacDecoder &d) noexcept
{
if (d.tag.IsEmpty() && d.chunk.IsEmpty())
return client.GetCommand();
if (!d.tag.IsEmpty()) {
auto cmd = client.SubmitTag(d.GetInputStream(),
std::move(d.tag));
d.tag.Clear();
if (cmd != DecoderCommand::NONE)
return cmd;
}
if (!d.chunk.IsEmpty()) {
auto cmd = client.SubmitData(d.GetInputStream(),
d.chunk.data,
d.chunk.size,
d.kbit_rate);
d.chunk = nullptr;
if (cmd != DecoderCommand::NONE)
return cmd;
}
return DecoderCommand::NONE;
}
static void
flac_decoder_loop(FlacDecoder *data, FLAC__StreamDecoder *flac_dec)
{
DecoderClient &client = *data->GetClient();
while (true) {
DecoderCommand cmd;
if (!data->tag.IsEmpty()) {
cmd = client.SubmitTag(data->GetInputStream(),
std::move(data->tag));
data->tag.Clear();
} else
cmd = client.GetCommand();
DecoderCommand cmd = FlacSubmitToClient(client, *data);
if (cmd == DecoderCommand::SEEK) {
FLAC__uint64 seek_sample = client.GetSeekFrame();
@@ -160,6 +181,11 @@ flac_decoder_loop(FlacDecoder *data, FLAC__StreamDecoder *flac_dec)
client.CommandFinished();
} else
client.SeekError();
/* FLAC__stream_decoder_seek_absolute()
decodes one frame and may have provided
data to be submitted to the client */
continue;
} else if (cmd == DecoderCommand::STOP)
break;

@@ -53,6 +53,14 @@ ScanOneOpusTag(const char *name, const char *value,
long l = strtol(value, &endptr, 10);
if (endptr > value && *endptr == 0)
rgi->track.gain = double(l) / 256.;
} else if (rgi != nullptr && strcmp(name, "R128_ALBUM_GAIN") == 0) {
/* R128_ALBUM_GAIN is a Q7.8 fixed point number in
dB */
char *endptr;
long l = strtol(value, &endptr, 10);
if (endptr > value && *endptr == 0)
rgi->album.gain = double(l) / 256.;
}
tag_handler_invoke_pair(handler, ctx, name, value);

@@ -52,7 +52,7 @@ class OpusEncoder final : public OggEncoder {
ogg_int64_t packetno = 0;
ogg_int64_t granulepos;
ogg_int64_t granulepos = 0;
public:
OpusEncoder(AudioFormat &_audio_format, ::OpusEncoder *_enc);

@@ -27,7 +27,7 @@
#include <algorithm>
EventLoop::EventLoop()
:SocketMonitor(*this)
:SocketMonitor(*this), quit(false)
{
SocketMonitor::Open(wake_fd.Get());
SocketMonitor::Schedule(SocketMonitor::READ);
@@ -46,7 +46,9 @@ EventLoop::~EventLoop()
void
EventLoop::Break()
{
quit = true;
if (quit.exchange(true))
return;
wake_fd.Write();
}

@@ -30,6 +30,7 @@
#include "SocketMonitor.hxx"
#include <chrono>
#include <atomic>
#include <list>
#include <set>
@@ -82,7 +83,7 @@ class EventLoop final : SocketMonitor
std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now();
bool quit = false;
std::atomic_bool quit;
/**
* True when the object has been modified and another check is

@@ -73,6 +73,10 @@ public:
return filter;
}
void Reset() override {
filter->Reset();
}
ConstBuffer<void> FilterPCM(ConstBuffer<void> src) override {
return filter->FilterPCM(src);
}

@@ -53,10 +53,16 @@ public:
void Set(const AudioFormat &_out_audio_format);
void Reset() override {
state.Reset();
if (IsActive())
state.Reset();
}
ConstBuffer<void> FilterPCM(ConstBuffer<void> src) override;
private:
bool IsActive() const noexcept {
return out_audio_format != in_audio_format;
}
};
class PreparedConvertFilter final : public PreparedFilter {
@@ -80,7 +86,7 @@ ConvertFilter::Set(const AudioFormat &_out_audio_format)
/* no change */
return;
if (out_audio_format != in_audio_format) {
if (IsActive()) {
out_audio_format = in_audio_format;
state.Close();
}
@@ -111,7 +117,7 @@ ConvertFilter::~ConvertFilter()
{
assert(in_audio_format.IsValid());
if (out_audio_format != in_audio_format)
if (IsActive())
state.Close();
}
@@ -120,11 +126,10 @@ ConvertFilter::FilterPCM(ConstBuffer<void> src)
{
assert(in_audio_format.IsValid());
if (out_audio_format == in_audio_format)
return IsActive()
? state.Convert(src)
/* optimized special case: no-op */
return src;
return state.Convert(src);
: src;
}
const FilterPlugin convert_filter_plugin = {

@@ -26,8 +26,12 @@
#include <assert.h>
#include <string.h>
ThreadInputStream::~ThreadInputStream()
void
ThreadInputStream::Stop() noexcept
{
if (!thread.IsDefined())
return;
{
const std::lock_guard<Mutex> lock(mutex);
close = true;
@@ -42,6 +46,7 @@ ThreadInputStream::~ThreadInputStream()
buffer->Clear();
HugeFree(buffer->Write().data, buffer_size);
delete buffer;
buffer = nullptr;
}
}
@@ -54,10 +59,10 @@ ThreadInputStream::Start()
assert(p != nullptr);
buffer = new CircularBuffer<uint8_t>((uint8_t *)p, buffer_size);
thread.Start(ThreadFunc, this);
thread.Start();
}
inline void
void
ThreadInputStream::ThreadFunc()
{
FormatThreadName("input:%s", plugin);
@@ -68,7 +73,7 @@ ThreadInputStream::ThreadFunc()
Open();
} catch (...) {
postponed_exception = std::current_exception();
cond.broadcast();
SetReady();
return;
}
@@ -107,13 +112,6 @@ ThreadInputStream::ThreadFunc()
Close();
}
void
ThreadInputStream::ThreadFunc(void *ctx)
{
ThreadInputStream &tis = *(ThreadInputStream *)ctx;
tis.ThreadFunc();
}
void
ThreadInputStream::Check()
{

@@ -27,6 +27,7 @@
#include <exception>
#include <assert.h>
#include <stdint.h>
template<typename T> class CircularBuffer;
@@ -39,6 +40,11 @@ template<typename T> class CircularBuffer;
* manages the thread and the buffer.
*
* This works only for "streams": unknown length, no seeking, no tags.
*
* The implementation must call Stop() before its destruction
* completes. This cannot be done in ~ThreadInputStream() because at
* this point, the class has been morphed back to #ThreadInputStream
* and the still-running thread will crash due to pure method call.
*/
class ThreadInputStream : public InputStream {
const char *const plugin;
@@ -73,9 +79,16 @@ public:
size_t _buffer_size)
:InputStream(_uri, _mutex, _cond),
plugin(_plugin),
thread(BIND_THIS_METHOD(ThreadFunc)),
buffer_size(_buffer_size) {}
virtual ~ThreadInputStream();
#ifndef NDEBUG
~ThreadInputStream() override {
/* Stop() must have been called already */
assert(!thread.IsDefined());
assert(buffer == nullptr);
}
#endif
/**
* Initialize the object and start the thread.
@@ -89,6 +102,12 @@ public:
size_t Read(void *ptr, size_t size) override final;
protected:
/**
* Stop the thread and free the buffer. This must be called
* before destruction of this object completes.
*/
void Stop() noexcept;
void SetMimeType(const char *_mime) {
assert(thread.IsInside());
@@ -138,7 +157,6 @@ protected:
private:
void ThreadFunc();
static void ThreadFunc(void *ctx);
};
#endif

@@ -270,6 +270,12 @@ AlsaInputStream::Recover(int err)
/* this is no error, so just keep running */
err = 0;
break;
default:
/* this default case is just here to work around
-Wswitch due to SND_PCM_STATE_PRIVATE1 (libasound
1.1.6) */
break;
}

@@ -266,6 +266,7 @@ CurlInputStream::OnData(ConstBuffer<void> data)
void
CurlInputStream::OnEnd()
{
const std::lock_guard<Mutex> protect(mutex);
cond.broadcast();
AsyncInputStream::SetClosed();
@@ -274,6 +275,7 @@ CurlInputStream::OnEnd()
void
CurlInputStream::OnError(std::exception_ptr e)
{
const std::lock_guard<Mutex> protect(mutex);
postponed_exception = std::move(e);
if (IsSeekPending())

@@ -65,9 +65,12 @@ OpenFileInputStream(Path path,
throw FormatRuntimeError("Not a regular file: %s",
path.c_str());
#if !defined(__BIONIC__) || __ANDROID_API__ >= 21
/* posix_fadvise() requires Android API 21 */
#ifdef POSIX_FADV_SEQUENTIAL
posix_fadvise(reader.GetFD().Get(), (off_t)0, info.GetSize(),
POSIX_FADV_SEQUENTIAL);
#endif
#endif
return InputStreamPtr(new FileInputStream(path.ToUTF8().c_str(),

@@ -39,6 +39,10 @@ public:
MMS_BUFFER_SIZE) {
}
~MmsInputStream() noexcept override {
Stop();
}
protected:
virtual void Open() override;
virtual size_t ThreadRead(void *ptr, size_t size) override;

@@ -62,6 +62,7 @@ CurlRequest::CurlRequest(CurlGlobal &_global, const char *url,
easy.SetOption(CURLOPT_NOPROGRESS, 1l);
easy.SetOption(CURLOPT_NOSIGNAL, 1l);
easy.SetOption(CURLOPT_CONNECTTIMEOUT, 10l);
easy.SetOption(CURLOPT_HTTPAUTH, (long) CURLAUTH_ANY);
easy.SetOption(CURLOPT_URL, url);
}

@@ -0,0 +1,20 @@
Index: curl-7.58.0/lib/url.c
===================================================================
--- curl-7.58.0.orig/lib/url.c
+++ curl-7.58.0/lib/url.c
@@ -3503,6 +3503,7 @@ static CURLcode override_login(struct Cu
}
conn->bits.netrc = FALSE;
+#ifndef __BIONIC__
if(data->set.use_netrc != CURL_NETRC_IGNORED) {
int ret = Curl_parsenetrc(conn->host.name,
userp, passwdp,
@@ -3524,6 +3525,7 @@ static CURLcode override_login(struct Cu
conn->bits.user_passwd = TRUE; /* enable user+password */
}
}
+#endif
return CURLE_OK;
}

@@ -0,0 +1,15 @@
Index: curl-7.58.0/Makefile.in
===================================================================
--- curl-7.58.0.orig/Makefile.in
+++ curl-7.58.0/Makefile.in
@@ -641,8 +641,8 @@ CLEANFILES = $(VC6_LIBDSP) $(VC6_SRCDSP)
$(VC14_LIBVCXPROJ) $(VC14_SRCVCXPROJ) $(VC15_LIBVCXPROJ) $(VC15_SRCVCXPROJ)
bin_SCRIPTS = curl-config
-SUBDIRS = lib src
-DIST_SUBDIRS = $(SUBDIRS) tests packages scripts include docs
+SUBDIRS = lib
+DIST_SUBDIRS = $(SUBDIRS) include
pkgconfigdir = $(libdir)/pkgconfig
pkgconfig_DATA = libcurl.pc
LIB_VAUTH_CFILES = vauth/vauth.c vauth/cleartext.c vauth/cram.c \

@@ -0,0 +1,2 @@
only_lib.patch
no_netrc.patch

@@ -33,6 +33,9 @@ FfmpegInit()
{
av_log_set_callback(FfmpegLogCallback);
#if LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(58, 9, 100)
/* deprecated as of FFmpeg 4.0 */
av_register_all();
#endif
}

@@ -37,6 +37,18 @@ public:
explicit IcuCompare(const char *needle) noexcept;
IcuCompare(const IcuCompare &src) noexcept
:needle(src
? AllocatedString<>::Duplicate(src.needle.c_str())
: nullptr) {}
IcuCompare &operator=(const IcuCompare &src) noexcept {
needle = src
? AllocatedString<>::Duplicate(src.needle.c_str())
: nullptr;
return *this;
}
IcuCompare(IcuCompare &&) = default;
IcuCompare &operator=(IcuCompare &&) = default;

@@ -31,7 +31,11 @@ extern "C" {
#include <utility>
#ifdef _WIN32
#include <winsock2.h>
#else
#include <poll.h> /* for POLLIN, POLLOUT */
#endif
static constexpr std::chrono::steady_clock::duration NFS_MOUNT_TIMEOUT =
std::chrono::minutes(1);

@@ -31,7 +31,6 @@
#include <assert.h>
#include <string.h>
#include <fcntl.h>
#include <sys/stat.h>
NfsFileReader::NfsFileReader()
:DeferredMonitor(io_thread_get()), state(State::INITIAL)

@@ -31,6 +31,7 @@
#include <stdint.h>
#include <stddef.h>
#include <sys/stat.h>
struct nfsfh;
class NfsConnection;

@@ -34,7 +34,11 @@ static unsigned upnp_ref;
static void
DoInit()
{
auto code = UpnpInit(0, 0);
#ifdef UPNP_ENABLE_IPV6
auto code = UpnpInit2(nullptr, 0);
#else
auto code = UpnpInit(nullptr, 0);
#endif
if (code != UPNP_E_SUCCESS)
throw FormatRuntimeError("UpnpInit() failed: %s",
UpnpGetErrorMessage(code));

@@ -139,6 +139,13 @@ static int set_normalized_volume(snd_mixer_elem_t *elem,
return set_raw[ctl_dir](elem, value);
}
/* two special cases to avoid rounding errors at 0% and
100% */
if (volume <= 0)
return set_dB[ctl_dir](elem, min, dir);
else if (volume >= 100)
return set_dB[ctl_dir](elem, max, dir);
if (use_linear_dB_scale(min, max)) {
value = lrint_dir(volume * (max - min), dir) + min;
return set_dB[ctl_dir](elem, value, dir);

@@ -69,7 +69,8 @@ class SmbclientNeighborExplorer final : public NeighborExplorer {
public:
SmbclientNeighborExplorer(NeighborListener &_listener)
:NeighborExplorer(_listener) {}
:NeighborExplorer(_listener),
thread(BIND_THIS_METHOD(ThreadFunc)) {}
/* virtual methods from class NeighborExplorer */
void Open() override;
@@ -79,14 +80,13 @@ public:
private:
void Run();
void ThreadFunc();
static void ThreadFunc(void *ctx);
};
void
SmbclientNeighborExplorer::Open()
{
quit = false;
thread.Start(ThreadFunc, this);
thread.Start();
}
void
@@ -239,6 +239,8 @@ SmbclientNeighborExplorer::Run()
inline void
SmbclientNeighborExplorer::ThreadFunc()
{
SetThreadName("smbclient");
mutex.lock();
while (!quit) {
@@ -257,15 +259,6 @@ SmbclientNeighborExplorer::ThreadFunc()
mutex.unlock();
}
void
SmbclientNeighborExplorer::ThreadFunc(void *ctx)
{
SetThreadName("smbclient");
SmbclientNeighborExplorer &e = *(SmbclientNeighborExplorer *)ctx;
e.ThreadFunc();
}
static NeighborExplorer *
smbclient_neighbor_create(gcc_unused EventLoop &loop,
NeighborListener &listener,

49
src/net/Init.hxx Normal file

@@ -0,0 +1,49 @@
/*
* Copyright 2003-2017 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 NET_INIT_HXX
#define NET_INIT_HXX
#include "check.h"
#include "SocketError.hxx"
#ifdef _WIN32
#include <winsock2.h>
#endif
class ScopeNetInit {
#ifdef _WIN32
public:
ScopeNetInit() {
WSADATA sockinfo;
int retval = WSAStartup(MAKEWORD(2, 2), &sockinfo);
if (retval != 0)
throw MakeSocketError(retval, "WSAStartup() failed");
}
~ScopeNetInit() noexcept {
WSACleanup();
}
#else
public:
ScopeNetInit() {}
#endif
};
#endif

@@ -51,7 +51,8 @@
AudioOutput::AudioOutput(const AudioOutputPlugin &_plugin,
const ConfigBlock &block)
:plugin(_plugin)
:plugin(_plugin),
thread(BIND_THIS_METHOD(Task))
{
assert(plugin.finish != nullptr);
assert(plugin.open != nullptr);

@@ -515,7 +515,6 @@ private:
* The OutputThread.
*/
void Task();
static void Task(void *arg);
};
/**

@@ -396,7 +396,7 @@ AudioOutput::Pause()
pause = false;
}
inline void
void
AudioOutput::Task()
{
FormatThreadName("output:%s", name);
@@ -512,17 +512,10 @@ AudioOutput::Task()
}
}
void
AudioOutput::Task(void *arg)
{
AudioOutput *ao = (AudioOutput *)arg;
ao->Task();
}
void
AudioOutput::StartThread()
{
assert(command == Command::NONE);
thread.Start(Task, this);
thread.Start();
}

@@ -846,6 +846,12 @@ AlsaOutput::Recover(int err)
case SND_PCM_STATE_DRAINING:
err = 0;
break;
default:
/* this default case is just here to work around
-Wswitch due to SND_PCM_STATE_PRIVATE1 (libasound
1.1.6) */
break;
}
return err;

@@ -77,7 +77,6 @@ public:
void Close();
size_t Play(const void *chunk, size_t size);
void Cancel();
std::chrono::steady_clock::duration Delay() noexcept;

@@ -27,6 +27,7 @@
#include "../Wrapper.hxx"
#include "mixer/MixerList.hxx"
#include "mixer/plugins/PulseMixerPlugin.hxx"
#include "util/ScopeExit.hxx"
#include "Log.hxx"
#include <pulse/thread-mainloop.h>
@@ -854,7 +855,10 @@ PulseOutput::TestDefaultDevice()
try {
const ConfigBlock empty;
PulseOutput po(empty);
po.Enable();
AtScopeExit(&po) { po.Disable(); };
po.WaitConnection();
return true;
} catch (const std::runtime_error &e) {
return false;

@@ -88,7 +88,7 @@ static inline ConstBuffer<V>
ToAlsaChannelOrder71(PcmBuffer &buffer, ConstBuffer<V> src)
{
auto dest = buffer.GetT<V>(src.size);
ToAlsaChannelOrder71(dest, src.data, src.size / 6);
ToAlsaChannelOrder71(dest, src.data, src.size / 8);
return { dest, src.size };
}

@@ -46,19 +46,20 @@ pcm_dsd_to_dop(PcmBuffer &buffer, unsigned channels,
assert(audio_valid_channel_count(channels));
assert(_src.size % channels == 0);
const unsigned num_src_samples = _src.size;
const unsigned num_src_frames = num_src_samples / channels;
const size_t num_src_samples = _src.size;
const size_t num_src_frames = num_src_samples / channels;
/* this rounds down and discards the last odd frame; not
/* this rounds down and discards up to 3 odd frames; not
elegant, but good enough for now */
const unsigned num_frames = num_src_frames / 2;
const unsigned num_samples = num_frames * channels;
const size_t num_dop_quads = num_src_frames / 4;
const size_t num_frames = num_dop_quads * 2;
const size_t num_samples = num_frames * channels;
uint32_t *const dest0 = (uint32_t *)buffer.GetT<uint32_t>(num_samples),
*dest = dest0;
auto src = _src.data;
for (unsigned i = num_frames / 2; i > 0; --i) {
for (size_t i = num_dop_quads; i > 0; --i) {
for (unsigned c = channels; c > 0; --c) {
/* each 24 bit sample has 16 DSD sample bits
plus the magic 0x05 marker */

@@ -122,7 +122,7 @@ sample_format_size(SampleFormat format)
* @param format a #SampleFormat enum value
* @return the string
*/
gcc_pure gcc_malloc
gcc_pure
const char *
sample_format_to_string(SampleFormat format) noexcept;

@@ -139,6 +139,14 @@ SoxrPcmResampler::Close()
soxr_delete(soxr);
}
void
SoxrPcmResampler::Reset()
{
#if SOXR_THIS_VERSION >= SOXR_VERSION(0,1,2)
soxr_clear(soxr);
#endif
}
ConstBuffer<void>
SoxrPcmResampler::Resample(ConstBuffer<void> src)
{

@@ -41,6 +41,7 @@ class SoxrPcmResampler final : public PcmResampler {
public:
AudioFormat Open(AudioFormat &af, unsigned new_sample_rate) override;
void Close() override;
void Reset() override;
ConstBuffer<void> Resample(ConstBuffer<void> src) override;
};

@@ -37,6 +37,7 @@ PlayerControl::PlayerControl(PlayerListener &_listener,
buffer_chunks(_buffer_chunks),
buffered_before_play(_buffered_before_play),
configured_audio_format(_configured_audio_format),
thread(BIND_THIS_METHOD(RunThread)),
replay_gain_config(_replay_gain_config)
{
}

@@ -537,6 +537,9 @@ public:
void ApplyEnabled() override {
LockUpdateAudio();
}
private:
void RunThread();
};
#endif

@@ -584,7 +584,7 @@ Player::SeekDecoder()
const SongTime start_time = pc.next_song->GetStartTime();
if (!dc.LockIsCurrentSong(*pc.next_song)) {
if (!dc.LockIsSeeakbleCurrentSong(*pc.next_song)) {
/* the decoder is already decoding the "next" song -
stop it and start the previous song again */
@@ -950,6 +950,7 @@ Player::SongBorder()
const bool border_pause = pc.LockApplyBorderPause();
if (border_pause) {
paused = true;
pc.outputs.Pause();
idle_add(IDLE_PLAYER);
}
}
@@ -1147,91 +1148,89 @@ do_play(PlayerControl &pc, DecoderControl &dc,
player.Run();
}
static void
player_task(void *arg)
void
PlayerControl::RunThread()
{
PlayerControl &pc = *(PlayerControl *)arg;
SetThreadName("player");
DecoderControl dc(pc.mutex, pc.cond,
pc.configured_audio_format,
pc.replay_gain_config);
DecoderControl dc(mutex, cond,
configured_audio_format,
replay_gain_config);
decoder_thread_start(dc);
MusicBuffer buffer(pc.buffer_chunks);
MusicBuffer buffer(buffer_chunks);
pc.Lock();
Lock();
while (1) {
switch (pc.command) {
switch (command) {
case PlayerCommand::SEEK:
case PlayerCommand::QUEUE:
assert(pc.next_song != nullptr);
assert(next_song != nullptr);
pc.Unlock();
do_play(pc, dc, buffer);
pc.listener.OnPlayerSync();
pc.Lock();
Unlock();
do_play(*this, dc, buffer);
listener.OnPlayerSync();
Lock();
break;
case PlayerCommand::STOP:
pc.Unlock();
pc.outputs.Cancel();
pc.Lock();
Unlock();
outputs.Cancel();
Lock();
/* fall through */
case PlayerCommand::PAUSE:
delete pc.next_song;
pc.next_song = nullptr;
delete next_song;
next_song = nullptr;
pc.CommandFinished();
CommandFinished();
break;
case PlayerCommand::CLOSE_AUDIO:
pc.Unlock();
Unlock();
pc.outputs.Release();
outputs.Release();
pc.Lock();
pc.CommandFinished();
Lock();
CommandFinished();
assert(buffer.IsEmptyUnsafe());
break;
case PlayerCommand::UPDATE_AUDIO:
pc.Unlock();
pc.outputs.EnableDisable();
pc.Lock();
pc.CommandFinished();
Unlock();
outputs.EnableDisable();
Lock();
CommandFinished();
break;
case PlayerCommand::EXIT:
pc.Unlock();
Unlock();
dc.Quit();
pc.outputs.Close();
outputs.Close();
pc.LockCommandFinished();
LockCommandFinished();
return;
case PlayerCommand::CANCEL:
delete pc.next_song;
pc.next_song = nullptr;
delete next_song;
next_song = nullptr;
pc.CommandFinished();
CommandFinished();
break;
case PlayerCommand::REFRESH:
/* no-op when not playing */
pc.CommandFinished();
CommandFinished();
break;
case PlayerCommand::NONE:
pc.Wait();
Wait();
break;
}
}
@@ -1242,5 +1241,5 @@ StartPlayerThread(PlayerControl &pc)
{
assert(!pc.thread.IsDefined());
pc.thread.Start(player_task, &pc);
pc.thread.Start();
}

@@ -229,6 +229,8 @@ CueParser::Feed2(char *p) noexcept
}
state = TRACK;
ignore_index = false;
current.reset(new DetachedSong(filename));
assert(!current->GetTag().IsDefined());
@@ -238,6 +240,9 @@ CueParser::Feed2(char *p) noexcept
} else if (state == IGNORE_TRACK) {
return;
} else if (state == TRACK && strcmp(command, "INDEX") == 0) {
if (ignore_index)
return;
const char *nr = cue_next_token(&p);
if (nr == nullptr)
return;
@@ -255,7 +260,7 @@ CueParser::Feed2(char *p) noexcept
current->SetStartTime(SongTime::FromMS(position_ms));
if(strcmp(nr, "00") != 0 || previous == nullptr)
state = IGNORE_TRACK;
ignore_index = true;
}
}

@@ -87,6 +87,13 @@ class CueParser {
*/
std::unique_ptr<DetachedSong> finished;
/**
* Ignore "INDEX" lines? Only up the first one after "00" is
* used. If there is a pregap (INDEX 00..01), it is assigned
* to the previous song.
*/
bool ignore_index;
/**
* Tracks whether Finish() has been called. If true, then all
* remaining (partial) results will be delivered by Get().

@@ -313,7 +313,7 @@ soundcloud_open_uri(const char *uri, Mutex &mutex, Cond &cond)
SoundCloudJsonData data;
yajl_handle hand = yajl_alloc(&parse_callbacks, nullptr, &data);
AtScopeExit(hand, &data) { yajl_free(hand); };
AtScopeExit(hand) { yajl_free(hand); };
int ret = soundcloud_parse_json(u, hand, mutex, cond);

@@ -21,6 +21,7 @@
#include "ArgParser.hxx"
#include "Ack.hxx"
#include "Chrono.hxx"
#include "util/NumberParser.hxx"
#include <stdlib.h>
@@ -151,7 +152,7 @@ float
ParseCommandArgFloat(const char *s)
{
char *endptr;
auto value = strtof(s, &endptr);
auto value = ParseFloat(s, &endptr);
if (endptr == s || *endptr != 0)
throw FormatProtocolError(ACK_ERROR_ARG,
"Float expected: %s", s);
@@ -163,6 +164,10 @@ SongTime
ParseCommandArgSongTime(const char *s)
{
auto value = ParseCommandArgFloat(s);
if (value < 0)
throw FormatProtocolError(ACK_ERROR_ARG,
"Negative value not allowed: %s", s);
return SongTime::FromS(value);
}

@@ -212,8 +212,6 @@ playlist::SeekSongOrder(PlayerControl &pc, unsigned i, SongTime seek_time)
{
assert(queue.IsValidOrder(i));
const DetachedSong *queued_song = GetQueuedSong();
pc.LockClearError();
stop_on_error = true;
error_count = 0;
@@ -226,8 +224,6 @@ playlist::SeekSongOrder(PlayerControl &pc, unsigned i, SongTime seek_time)
playing = true;
current = i;
queued_song = nullptr;
}
queued = -1;
@@ -235,7 +231,7 @@ playlist::SeekSongOrder(PlayerControl &pc, unsigned i, SongTime seek_time)
try {
pc.LockSeek(new DetachedSong(queue.GetOrder(i)), seek_time);
} catch (...) {
UpdateQueuedSong(pc, queued_song);
UpdateQueuedSong(pc, nullptr);
throw;
}

@@ -35,6 +35,7 @@
#include "util/CharUtil.hxx"
#include "util/StringAPI.hxx"
#include "util/StringCompare.hxx"
#include "util/NumberParser.hxx"
#include "Log.hxx"
#include <string.h>
@@ -148,7 +149,7 @@ playlist_state_restore(const char *line, TextFile &file,
while ((line = file.ReadLine()) != nullptr) {
const char *p;
if ((p = StringAfterPrefix(line, PLAYLIST_STATE_FILE_TIME))) {
seek_time = SongTime::FromS(atof(p));
seek_time = SongTime::FromS(ParseDouble(p));
} else if ((p = StringAfterPrefix(line, PLAYLIST_STATE_FILE_REPEAT))) {
playlist.SetRepeat(pc, StringIsEqual(p, "1"));
} else if ((p = StringAfterPrefix(line, PLAYLIST_STATE_FILE_SINGLE))) {
@@ -158,12 +159,12 @@ playlist_state_restore(const char *line, TextFile &file,
} else if ((p = StringAfterPrefix(line, PLAYLIST_STATE_FILE_CROSSFADE))) {
pc.SetCrossFade(atoi(p));
} else if ((p = StringAfterPrefix(line, PLAYLIST_STATE_FILE_MIXRAMPDB))) {
pc.SetMixRampDb(atof(p));
pc.SetMixRampDb(ParseFloat(p));
} else if ((p = StringAfterPrefix(line, PLAYLIST_STATE_FILE_MIXRAMPDELAY))) {
/* this check discards "nan" which was used
prior to MPD 0.18 */
if (IsDigitASCII(*p))
pc.SetMixRampDelay(atof(p));
pc.SetMixRampDelay(ParseFloat(p));
} else if ((p = StringAfterPrefix(line, PLAYLIST_STATE_FILE_RANDOM))) {
random_mode = StringIsEqual(p, "1");
} else if ((p = StringAfterPrefix(line, PLAYLIST_STATE_FILE_CURRENT))) {

@@ -35,7 +35,7 @@
#include "IOThread.hxx"
#include "Log.hxx"
#include <list>
#include <set>
#include <boost/crc.hpp>
#define MOUNT_STATE_BEGIN "mount_begin"
@@ -48,6 +48,9 @@ static constexpr Domain storage_domain("storage");
void
storage_state_save(BufferedOutputStream &os, const Instance &instance)
{
if (instance.storage == nullptr)
return;
const auto visitor = [&os](const char *mount_uri, const Storage &storage) {
std::string uri = storage.MapUTF8("");
if (uri.empty() || StringIsEmpty(mount_uri))
@@ -85,6 +88,12 @@ storage_state_restore(const char *line, TextFile &file, Instance &instance)
FormatError(storage_domain, "Unrecognized line in mountpoint state: %s", line);
}
if (instance.storage == nullptr)
/* without storage (a CompositeStorage instance), we
cannot mount, and therefore we silently ignore the
state file */
return true;
if (url.empty() || uri.empty()) {
LogError(storage_domain, "Missing value in mountpoint state.");
return true;
@@ -99,16 +108,18 @@ storage_state_restore(const char *line, TextFile &file, Instance &instance)
return true;
}
#ifdef ENABLE_DATABASE
Database *db = instance.database;
if (db != nullptr && db->IsPlugin(simple_db_plugin)) {
try {
((SimpleDatabase *)db)->Mount(uri.c_str(), url.c_str());
} catch (...) {
throw;
delete storage;
FormatError(std::current_exception(),
"Failed to restore mount to %s",
url.c_str());
return true;
}
}
#endif
((CompositeStorage*)instance.storage)->Mount(uri.c_str(), storage);
@@ -118,16 +129,17 @@ storage_state_restore(const char *line, TextFile &file, Instance &instance)
unsigned
storage_state_get_hash(const Instance &instance)
{
std::list<std::string> mounts;
if (instance.storage == nullptr)
return 0;
std::set<std::string> mounts;
const auto visitor = [&mounts](const char *mount_uri, const Storage &storage) {
mounts.push_back(std::string(mount_uri) + ":" + storage.MapUTF8(""));
mounts.emplace(std::string(mount_uri) + ":" + storage.MapUTF8(""));
};
((CompositeStorage*)instance.storage)->VisitMounts(visitor);
mounts.sort();
boost::crc_32_type result;
for (auto mount: mounts) {

@@ -219,7 +219,12 @@ UriToNfsPath(const char *_uri_utf8)
std::string uri_utf8("/");
uri_utf8.append(_uri_utf8);
#ifdef _WIN32
/* assume UTF-8 when accessing NFS from Windows */
return uri_utf8;
#else
return AllocatedPath::FromUTF8Throw(uri_utf8.c_str()).Steal();
#endif
}
std::string
@@ -291,7 +296,7 @@ NfsStorage::GetInfo(const char *uri_utf8, gcc_unused bool follow)
gcc_pure
static bool
SkipNameFS(const char *name) noexcept
SkipNameFS(PathTraitsFS::const_pointer_type name) noexcept
{
return name[0] == '.' &&
(name[1] == 0 ||
@@ -358,7 +363,14 @@ NfsListDirectoryOperation::CollectEntries(struct nfsdir *dir)
const struct nfsdirent *ent;
while ((ent = connection.ReadDirectory(dir)) != nullptr) {
#ifdef _WIN32
/* assume UTF-8 when accessing NFS from Windows */
const auto name_fs = AllocatedPath::FromUTF8Throw(ent->name);
if (name_fs.IsNull())
continue;
#else
const Path name_fs = Path::FromFS(ent->name);
#endif
if (SkipNameFS(name_fs.c_str()))
continue;

@@ -22,6 +22,7 @@
#include "VorbisComment.hxx"
#include "ReplayGainInfo.hxx"
#include "util/ASCII.hxx"
#include "util/NumberParser.hxx"
#include <assert.h>
#include <stdlib.h>
@@ -33,16 +34,16 @@ ParseReplayGainTagTemplate(ReplayGainInfo &info, const T t)
const char *value;
if ((value = t["replaygain_track_gain"]) != nullptr) {
info.track.gain = atof(value);
info.track.gain = ParseFloat(value);
return true;
} else if ((value = t["replaygain_album_gain"]) != nullptr) {
info.album.gain = atof(value);
info.album.gain = ParseFloat(value);
return true;
} else if ((value = t["replaygain_track_peak"]) != nullptr) {
info.track.peak = atof(value);
info.track.peak = ParseFloat(value);
return true;
} else if ((value = t["replaygain_album_peak"]) != nullptr) {
info.album.peak = atof(value);
info.album.peak = ParseFloat(value);
return true;
} else
return false;

@@ -52,13 +52,11 @@ public:
constexpr ThreadId(pthread_t _id):id(_id) {}
#endif
gcc_const
static ThreadId Null() noexcept {
static constexpr ThreadId Null() noexcept {
#ifdef _WIN32
return 0;
#else
static ThreadId null;
return null;
return pthread_t();
#endif
}
@@ -81,11 +79,13 @@ public:
gcc_pure
bool operator==(const ThreadId &other) const noexcept {
#ifdef _WIN32
/* note: not using pthread_equal() because that
function "is undefined if either thread ID is not
valid so we can't safely use it on
default-constructed values" (comment from
libstdc++) - and if both libstdc++ and libc++ get
away with this, we can do it as well */
return id == other.id;
#else
return pthread_equal(id, other.id);
#endif
}
/**

@@ -25,39 +25,21 @@
#include "java/Global.hxx"
#endif
bool
Thread::Start(void (*_f)(void *ctx), void *_ctx)
void
Thread::Start()
{
assert(!IsDefined());
f = _f;
ctx = _ctx;
#ifdef _WIN32
handle = ::CreateThread(nullptr, 0, ThreadProc, this, 0, &id);
if (handle == nullptr)
throw MakeLastError("Failed to create thread");
#else
#ifndef NDEBUG
creating = true;
#endif
int e = pthread_create(&handle, nullptr, ThreadProc, this);
if (e != 0) {
#ifndef NDEBUG
creating = false;
#endif
if (e != 0)
throw MakeErrno(e, "Failed to create thread");
}
defined = true;
#ifndef NDEBUG
creating = false;
#endif
#endif
return true;
}
void
@@ -72,7 +54,17 @@ Thread::Join()
handle = nullptr;
#else
pthread_join(handle, nullptr);
defined = false;
handle = pthread_t();
#endif
}
inline void
Thread::Run()
{
f();
#ifdef ANDROID
Java::DetachCurrentThread();
#endif
}
@@ -83,7 +75,7 @@ Thread::ThreadProc(LPVOID ctx)
{
Thread &thread = *(Thread *)ctx;
thread.f(thread.ctx);
thread.Run();
return 0;
}
@@ -95,18 +87,10 @@ Thread::ThreadProc(void *ctx)
Thread &thread = *(Thread *)ctx;
#ifndef NDEBUG
/* this works around a race condition that causes an assertion
failure due to IsInside() spuriously returning false right
after the thread has been created, and the calling thread
hasn't initialised "defined" yet */
thread.defined = true;
thread.inside_handle = pthread_self();
#endif
thread.f(thread.ctx);
#ifdef ANDROID
Java::DetachCurrentThread();
#endif
thread.Run();
return nullptr;
}

@@ -21,6 +21,7 @@
#define MPD_THREAD_HXX
#include "check.h"
#include "util/BindMethod.hxx"
#include "Compiler.h"
#ifdef _WIN32
@@ -32,28 +33,28 @@
#include <assert.h>
class Thread {
typedef BoundMethod<void()> Function;
const Function f;
#ifdef _WIN32
HANDLE handle = nullptr;
DWORD id;
#else
pthread_t handle;
bool defined = false;
pthread_t handle = pthread_t();
#ifndef NDEBUG
/**
* The thread is currently being created. This is a workaround for
* IsInside(), which may return false until pthread_create() has
* initialised the #handle.
* This handle is only used by IsInside(), and is set by the
* thread function. Since #handle is set by pthread_create()
* which is racy, we need this attribute for early checks
* inside the thread function.
*/
bool creating = false;
pthread_t inside_handle = pthread_t();
#endif
#endif
void (*f)(void *ctx);
void *ctx;
public:
Thread() = default;
explicit Thread(Function _f):f(_f) {}
Thread(const Thread &) = delete;
@@ -69,10 +70,11 @@ public:
#ifdef _WIN32
return handle != nullptr;
#else
return defined;
return handle != pthread_t();
#endif
}
}
#ifndef NDEBUG
/**
* Check if this thread is the current thread.
*/
@@ -81,18 +83,23 @@ public:
#ifdef _WIN32
return GetCurrentThreadId() == id;
#else
#ifdef NDEBUG
constexpr bool creating = false;
#endif
return IsDefined() && (creating ||
pthread_equal(pthread_self(), handle));
/* note: not using pthread_equal() because that
function "is undefined if either thread ID is not
valid so we can't safely use it on
default-constructed values" (comment from
libstdc++) - and if both libstdc++ and libc++ get
away with this, we can do it as well */
return pthread_self() == inside_handle;
#endif
}
#endif
bool Start(void (*f)(void *ctx), void *ctx);
void Start();
void Join();
private:
void Run();
#ifdef _WIN32
static DWORD WINAPI ThreadProc(LPVOID ctx);
#else

@@ -40,8 +40,10 @@
#ifdef __linux__
#ifndef ANDROID
static int
ioprio_set(int which, int who, int ioprio)
linux_ioprio_set(int which, int who, int ioprio)
{
return syscall(__NR_ioprio_set, which, who, ioprio);
}
@@ -55,7 +57,20 @@ ioprio_set_idle()
static constexpr int _IOPRIO_IDLE =
(_IOPRIO_CLASS_IDLE << _IOPRIO_CLASS_SHIFT) | 7;
ioprio_set(_IOPRIO_WHO_PROCESS, 0, _IOPRIO_IDLE);
linux_ioprio_set(_IOPRIO_WHO_PROCESS, 0, _IOPRIO_IDLE);
}
#endif /* !ANDROID */
/**
* Wrapper for the "sched_setscheduler" system call. We don't use the
* one from the C library because Musl has an intentionally broken
* implementation.
*/
static int
linux_sched_setscheduler(pid_t pid, int sched, const struct sched_param *param)
{
return syscall(__NR_sched_setscheduler, pid, sched, param);
}
#endif
@@ -66,10 +81,14 @@ SetThreadIdlePriority()
#ifdef __linux__
#ifdef SCHED_IDLE
static struct sched_param sched_param;
sched_setscheduler(0, SCHED_IDLE, &sched_param);
linux_sched_setscheduler(0, SCHED_IDLE, &sched_param);
#endif
#ifndef ANDROID
/* this system call is forbidden via seccomp on Android 8 and
leads to crash (SIGSYS) */
ioprio_set_idle();
#endif
#elif defined(_WIN32)
SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_IDLE);
@@ -88,7 +107,7 @@ SetThreadRealtime()
policy |= SCHED_RESET_ON_FORK;
#endif
if (sched_setscheduler(0, policy, &sched_param) < 0)
if (linux_sched_setscheduler(0, policy, &sched_param) < 0)
throw MakeErrno("sched_setscheduler failed");
#endif // __linux__
};

@@ -78,7 +78,12 @@ ParseDouble(const char *p, char **endptr=nullptr)
static inline float
ParseFloat(const char *p, char **endptr=nullptr)
{
#if defined(__BIONIC__) && __ANDROID_API__ < 21
/* strtof() requires API level 21 */
return (float)ParseDouble(p, endptr);
#else
return strtof(p, endptr);
#endif
}
#endif

@@ -42,11 +42,6 @@ class RefCount {
std::atomic_uint n;
public:
#ifndef _LIBCPP_VERSION
/* the "constexpr" is missing in libc++'s "atomic"
implementation */
constexpr
#endif
RefCount():n(1) {}
void Increment() {

@@ -103,11 +103,13 @@ UnsafeCopyStringP(wchar_t *dest, const wchar_t *src) noexcept
{
#if defined(_WIN32) || defined(__BIONIC__) || defined(__OpenBSD__) || \
defined(__NetBSD__)
/* emulate wcpcpy() */
UnsafeCopyString(dest, src);
return dest + StringLength(dest);
/* emulate wcpcpy() */
UnsafeCopyString(dest, src);
return dest + StringLength(dest);
#elif defined(__sun) && defined (__SVR4)
return std::wcpcpy(dest, src);
#else
return wcpcpy(dest, src);
return wcpcpy(dest, src);
#endif
}
@@ -140,7 +142,11 @@ gcc_malloc gcc_nonnull_all
static inline wchar_t *
DuplicateString(const wchar_t *p)
{
#if defined(__sun) && defined (__SVR4)
return std::wcsdup(p);
#else
return wcsdup(p);
#endif
}
#endif

@@ -0,0 +1,30 @@
/*
* Copyright 2003-2018 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef NULL_MIXER_LISTENER_HXX
#define NULL_MIXER_LISTENER_HXX
#include "mixer/Listener.hxx"
class NullMixerListener : public MixerListener {
public:
void OnMixerVolumeChanged(Mixer &, int) override {}
};
#endif

@@ -18,6 +18,7 @@
*/
#include "config.h"
#include "NullMixerListener.hxx"
#include "mixer/MixerControl.hxx"
#include "mixer/MixerList.hxx"
#include "filter/FilterRegistry.hxx"
@@ -50,9 +51,14 @@ try {
EventLoop event_loop;
NullMixerListener mixer_listener;
Mixer *mixer = mixer_new(event_loop, alsa_mixer_plugin,
*(AudioOutput *)nullptr,
*(MixerListener *)nullptr,
/* ugly dangerous dummy pointer to
make the compiler happy; this
works with most mixers, because
they don't need the AudioOutput */
*(AudioOutput *)0x1,
mixer_listener,
ConfigBlock());
mixer_open(mixer);

@@ -18,6 +18,7 @@
*/
#include "config.h"
#include "NullMixerListener.hxx"
#include "output/Internal.hxx"
#include "output/OutputPlugin.hxx"
#include "output/Client.hxx"
@@ -44,6 +45,8 @@
#include <stdlib.h>
#include <stdio.h>
void AudioOutput::Task() {}
class DummyAudioOutputClient final : public AudioOutputClient {
public:
/* virtual methods from AudioOutputClient */
@@ -62,7 +65,9 @@ filter_plugin_by_name(gcc_unused const char *name) noexcept
}
static AudioOutput *
load_audio_output(EventLoop &event_loop, AudioOutputClient &client,
load_audio_output(EventLoop &event_loop,
NullMixerListener &mixer_listener,
AudioOutputClient &client,
const char *name)
{
const auto *param = config_find_block(ConfigBlockOption::AUDIO_OUTPUT,
@@ -72,7 +77,7 @@ load_audio_output(EventLoop &event_loop, AudioOutputClient &client,
name);
return audio_output_new(event_loop, ReplayGainConfig(), *param,
*(MixerListener *)nullptr,
mixer_listener,
client);
}
@@ -142,8 +147,10 @@ try {
/* initialize the audio output */
NullMixerListener mixer_listener;
DummyAudioOutputClient client;
AudioOutput *ao = load_audio_output(event_loop, client, argv[2]);
AudioOutput *ao = load_audio_output(event_loop, mixer_listener,
client, argv[2]);
/* parse the audio format */

Some files were not shown because too many files have changed in this diff Show More