Compare commits

...

272 Commits

Author SHA1 Message Date
Max Kellermann
566787f041 release v0.21.26 2020-09-21 15:14:43 +02:00
Max Kellermann
79b2366387 archive/iso9660: fix odd seeking bug (assertion failure)
Skip the beginning of a sector if the last seek was odd, and clear the
buffer on seek.
2020-09-21 15:11:21 +02:00
Max Kellermann
5acea014b0 archive/iso9660: remove unused macro CEILING() 2020-09-21 15:11:17 +02:00
Max Kellermann
b72801abf3 util/ByteOrder: add FromLE16S() 2020-09-21 11:15:45 +02:00
Desuwa
23d5a2b862 Support opus header gain tags and match opus playback volume to other tracks when ReplayGain is enabled. 2020-09-21 10:51:06 +02:00
Rosen Penev
7715311117 fix double promotions
Found with -Wdouble-promotion

Signed-off-by: Rosen Penev <rosenp@gmail.com>
2020-09-21 09:13:51 +02:00
Max Kellermann
4c1cfca95b db/update/InotifyUpdate: pass path by value to recursive_watch_subdirectories() 2020-09-17 14:18:29 +02:00
Max Kellermann
e113ce9621 db/update/InotifyUpdate: obey .mpdignore files
Closes https://github.com/MusicPlayerDaemon/MPD/issues/846
2020-09-17 14:17:17 +02:00
Max Kellermann
e8213220e2 db/update/InotifyUpdate: split the WatchDirectory constructor 2020-09-16 21:08:22 +02:00
Max Kellermann
83f9d2a963 db/update/InotifyUpdate: use class DirectoryReader 2020-09-16 21:02:07 +02:00
Max Kellermann
bf97ebf89f db/update/InotifyUpdate: convert pointer to reference 2020-09-16 20:59:41 +02:00
Max Kellermann
5b22d27cbb db/update/InotifyUpdate: remove commented log call 2020-09-16 20:59:40 +02:00
Max Kellermann
e907ff43ae command/file, storage/{nfs,smbclient}: use PathTraitsFS::IsSpecialFilename()
Eliminate some duplicate code.
2020-09-16 20:57:46 +02:00
Max Kellermann
b18fc3a8d0 db/update/InotifySource: use auto 2020-09-16 20:40:27 +02:00
Max Kellermann
a8e23c4140 db/update/InotifySource: add noexcept 2020-09-16 20:40:06 +02:00
Max Kellermann
fc3861b421 db/update/InotifyQueue: add noexcept 2020-09-16 20:40:03 +02:00
Max Kellermann
e81bb5d8f1 db/update/Inotify*: include cleanup 2020-09-16 20:39:44 +02:00
Max Kellermann
32f4f15831 player/Thread: call OnPlayerSync() in SeekDecoder()
This fixes a spurious "single" mode bug which occurs when using "play"
or "seek" to start playback on the song that is currently paused: in
that case, the main thread never queues the next song, and at the end
of the song, the player thread exits Run(), stopping playback, and
after that, the main thread starts the next song without considering
"single" mode.

By calling OnPlayerSync(), we ensure that the main thread gets a
chance to queue the next song before the player thread exits the Run()
loop.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/850
2020-09-16 20:36:19 +02:00
Max Kellermann
e29c06b718 player/Thread: add another code comment explaining OnPlayerSync() 2020-09-16 20:12:52 +02:00
Max Kellermann
d9d511f33e player/Thread: update function name in comment 2020-09-16 20:12:05 +02:00
Max Kellermann
c61a3b8d13 LogBackend: change the initial log_threshold to DEFAULT
The log levels have always been very confusing (and badly named), but
this was most confusing: if there's a log level called "default", why
is it not the default?

Closes https://github.com/MusicPlayerDaemon/MPD/issues/926
2020-09-16 17:17:34 +02:00
Max Kellermann
e10b867fe6 decoder/ffmpeg: add "hls+http://" to the list of supported protocols 2020-09-16 16:36:07 +02:00
Max Kellermann
43e230f543 decoder/ffmpeg: remove "rtsp://" from the list of supported protocols
FFmpeg implements RTSP as a demuxer, not as a protocol handler.  Thus,
avio_open() cannot be used, and our input plugin cannot handle RTSP.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/930
2020-09-16 16:32:31 +02:00
Max Kellermann
b2ae5298a7 archive/iso9660: implement seeking 2020-09-07 21:13:28 +02:00
Max Kellermann
17dd21ac7f archive/iso9660: fix unaligned reads
Oh the horror!  This plugin cannot possibly ever have worked.  It was
broken from the start, when it was added in commit 37796699cf nearly
twelve (!) years ago.

The plugin would always read at sector boundaries, so it could only
ever work at multiples of 2 kB.
2020-09-07 21:08:46 +02:00
Max Kellermann
1a5e0ef7c9 test/test_archive_iso9660.sh: use an odd chunk size to trigger bug
This makes the unit test fail.  D'oh!
2020-09-07 20:53:46 +02:00
Max Kellermann
979a7a1dcc test/run_input: add option --chunk-size 2020-09-07 20:52:37 +02:00
Max Kellermann
962cf32ba7 test/run_input: pass FileDescriptor to dump_input_stream() 2020-09-07 20:14:39 +02:00
Max Kellermann
ae23682372 system/FileDescriptor: add method FullWrite() 2020-09-07 20:13:43 +02:00
Max Kellermann
540919f256 *: use nullptr instead of NULL 2020-09-07 20:08:27 +02:00
Max Kellermann
398281cd76 io/FileDescriptor: add method FullRead() 2020-09-07 20:07:47 +02:00
Max Kellermann
88446ccde9 test/run_filter: use Filter::Flush() 2020-09-07 20:07:40 +02:00
Max Kellermann
6238cc0734 test/run_filter: pass ConstBuffer<void> to FullWrite() 2020-09-07 20:07:33 +02:00
Max Kellermann
fd4823c507 test/run_filter: fix error message 2020-09-07 20:07:29 +02:00
Max Kellermann
68bcfd8bf0 test/run_filter: check for partial writes 2020-09-07 20:07:24 +02:00
Max Kellermann
1d332746af test/run_filter: move code to WriteOrThrow() 2020-09-07 20:07:18 +02:00
Max Kellermann
f3e133c617 test/run_filter: use class FileDescriptor 2020-09-07 20:07:13 +02:00
Max Kellermann
1678a6eb59 test/run_filter: ensure that partial frames will not get passed to the filter 2020-09-07 20:07:08 +02:00
Max Kellermann
b4dc2c07d5 test/run_filter: move the buffer into the loop 2020-09-07 20:07:03 +02:00
Max Kellermann
d7838950d8 test/run_input: use WithBufferedOutputStream() 2020-09-07 20:04:13 +02:00
Max Kellermann
2e93a83dd5 test/run_input: convert pointer to reference 2020-09-07 20:02:12 +02:00
Max Kellermann
db8b419b8c archive/iso9660: free iso9660_stat_t as early as possible 2020-09-04 18:17:24 +02:00
Max Kellermann
990f631cbc archive/bzip2: make variables more local 2020-09-04 18:02:22 +02:00
Max Kellermann
db46d84458 archive/bzip2: move the eof check out of the ScopeUnlock 2020-09-04 18:01:29 +02:00
Max Kellermann
9e6c4f8d80 archive/bzip2: throw on unexpected input EOF
Don't silently return 0 when there is no more data, because this may
crash the caller.  And flush output even if input EOF has been reached.
2020-09-04 17:54:53 +02:00
Max Kellermann
41b47f95c5 archive/bzip2: simplify bz_stream initializer 2020-09-04 17:52:04 +02:00
Max Kellermann
15939fd87c archive/bzip2: fold Open() into constructor 2020-09-04 17:51:41 +02:00
Max Kellermann
f63c343f68 archive/bzip2: reorder fields to improve packing 2020-09-04 17:51:22 +02:00
Max Kellermann
1a516e7744 archive/bzip2: add override 2020-09-04 17:51:21 +02:00
Max Kellermann
5c9d97775f python/build/libs.py: update Boost to 1.74.0 2020-09-04 14:49:11 +02:00
Max Kellermann
64aadcd13f python/build/libs.py: update CURL to 7.72.0 2020-09-04 14:48:40 +02:00
Max Kellermann
1f6a7d6462 archive/zzip: fix crash on corrupt ZIP file
Sometimes, zzip_file_read() returns 0 even though the end of the file
was not reached.  This causes assertion failures in
DecoderBridge::Read().

Closes https://github.com/MusicPlayerDaemon/MPD/issues/935
2020-09-04 14:34:54 +02:00
Max Kellermann
e44b953d9a archive/zzip: use zzip_ssize_t to avoid integer overflows 2020-09-04 14:33:44 +02:00
Max Kellermann
6c85020630 archive/zzip: add override 2020-09-04 14:33:44 +02:00
Max Kellermann
9d910320f3 archive/zzip: pass std::shared_ptr as template parameter
This eliminates a tiny amount of overhead because the compiler can
choose how to pass the parameter.
2020-09-04 14:33:44 +02:00
Max Kellermann
c53074efc9 archive/zzip: add explicit 2020-09-04 14:33:11 +02:00
Max Kellermann
3b51c53eca win32/build.py: add -D_FORTIFY_SOURCE=0
This fixes the Windows build.  Linking failed because some packages
(e.g. libFLAC) default to enabling `_FORTIFY_SOURCE`, which is broken
in recent mingw versions
(https://github.com/msys2/MINGW-packages/issues/5803).
2020-09-04 14:33:11 +02:00
Max Kellermann
0aa0ffb67b decoder/sndfile: allow partial reads at end of file
While libsndfile doesn't like partial reads in the middle of a file
(see commit 95ac6071b9), it allows partial reads at the end of a file.
It doesn't pay attention to the file size when issuing a read.

Commit ecb67a1ed1 (MPD 0.18.12) was a regression: previously,
partial reads at the end of a file were possible, but switching to
decoder_read_full() made this an error condition.  This way, a portion
at the end of each file was lost, leading to corruption with gapless
playback (https://github.com/MusicPlayerDaemon/MPD/issues/936).

This fix switches to the newly introduced function
decoder_read_much(), which does the same as the code before commit
ecb67a1ed1.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/936
2020-09-04 13:35:00 +02:00
Max Kellermann
33f70931dd decoder/API: add decoder_read_much() 2020-09-04 13:35:00 +02:00
Max Kellermann
8830ea319f decoder/API: add noexcept 2020-09-04 13:35:00 +02:00
Max Kellermann
cbcdc73f9a system/ByteOrder: add noexcept 2020-08-14 16:36:24 +02:00
Max Kellermann
4f6c54ecb3 output/osx: catch kAudioDevicePropertyHogMode errors
Our AudioObjectGetPropertyDataT() wrapper throws exception on error,
and calling it from OSXOutput::Disable() can cause MPD crash due to
std::terminate().

Closes https://github.com/MusicPlayerDaemon/MPD/issues/932
2020-08-14 16:33:43 +02:00
Max Kellermann
2bdf1b2284 test/meson.build: add explicit dependency from run_output on libevent.a
We could exclude that feature if neither ALSA nor httpd are enabled,
but that's too complicated for this small debug program.
2020-08-14 14:40:39 +02:00
Rosen Penev
c876d6a51c lib/icu: fix build without libc iconv support
Need to check for it in iconv.h. Otherwise meson prefixes a __builtin variant in the check.
2020-07-23 14:09:43 +02:00
Max Kellermann
3c745b4bc6 neighbor/smbclient: remove obsolete commented code 2020-07-20 18:13:38 +02:00
Max Kellermann
3a08a6ad72 doc/plugins.rst: document sample formats for OpenSLES 2020-07-20 15:27:41 +02:00
Max Kellermann
448b397cb8 output/sles: support floating point samples
According to https://developer.android.com/ndk/guides/audio/opensl/android-extensions

This feature was mentioned in https://github.com/MusicPlayerDaemon/MPD/issues/922
2020-07-20 15:23:50 +02:00
Max Kellermann
64a1386eb6 output/sles: move SampleFormat selection to switch/case block 2020-07-20 14:47:36 +02:00
Max Kellermann
77c2efe171 python/build/libs.py: update CURL to 7.71.1 2020-07-20 12:37:54 +02:00
Max Kellermann
587c0f6232 python/build/libs.py: update FFmpeg to 4.3.1 2020-07-20 12:37:16 +02:00
Max Kellermann
64e8abf203 python/build/libs.py: update libvorbis to 1.3.7 2020-07-20 12:34:16 +02:00
Max Kellermann
6c40d2a656 python/build/libs.py: update libmpdclient to 2.19 2020-07-20 12:32:19 +02:00
Max Kellermann
cf674e9273 input/Init: downgrade PluginUnconfigured to LogLevel::DEBUG
`LogLevel::INFO` is logged by default, but this message shall only
appear with `--verbose`.

This finally solves https://github.com/MusicPlayerDaemon/MPD/issues/430
2020-07-16 13:19:14 +02:00
Max Kellermann
9bda0379af increment version number to 0.21.26 2020-07-16 12:53:22 +02:00
Max Kellermann
c67372f8af release v0.21.25 2020-07-06 21:41:53 +02:00
Max Kellermann
00789de7d4 db/upnp/Object: root nodes are allowed to omit parent_id and name
This fixes compatibility with Plex DLNA.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/851
2020-07-06 21:36:30 +02:00
Max Kellermann
5ece9685c2 PluginUnavailable: backport class PluginUnconfigured from master
Stop bothering people about the Tidal/Qobuz plugins.
2020-07-06 21:08:22 +02:00
Max Kellermann
e7c5a42821 Log: add Log() and LogFormat() overloads with std::exception_ptr
Make LogError()/FormatError() wrappers for those.  Now we can log
exceptions with a lower level.
2020-07-06 21:08:04 +02:00
Max Kellermann
36e6079c57 Log: make LogLevel the first parameter
Prepare for templated functions.
2020-07-06 21:07:26 +02:00
Max Kellermann
e5f23678ca Log: use GetFullMessage() to print exceptions
Print all nested exceptions on a single line to avoid confusion.
2020-07-06 21:07:16 +02:00
Max Kellermann
749ad7cd83 PluginUnavailable: inherit the base class constructor 2020-07-06 20:40:25 +02:00
Max Kellermann
0b59f4eaee doc/plugins.rst: merge redundant nfs:// documentation 2020-07-06 20:37:58 +02:00
Max Kellermann
402663de74 doc/plugins.rst: more markup 2020-07-06 20:34:04 +02:00
Max Kellermann
eaa66c7ee3 doc/plugins.rst: add smb:// with password example
Closes https://github.com/MusicPlayerDaemon/MPD/issues/864
2020-07-06 20:32:41 +02:00
Max Kellermann
996714d6ff doc/plugins.rst: more markup 2020-07-06 20:32:11 +02:00
Max Kellermann
fe48e5596f command/storage: automatically scan new mounts
Closes https://github.com/MusicPlayerDaemon/MPD/issues/841
2020-07-06 20:23:41 +02:00
Max Kellermann
d7744d2b8e command/storage: check if storage is already mounted
Mounting one storage URI twice on different mount points can lead to
conflicts with the database cache file, and it doesn't make a lot of
sense.

But most importantly, our udisks storage plugin will unmount the disk
from the kernel VFS, and if two exist, they will compete with each
others.  We could (and should) fix this in the udisks storage plugin,
but for now, this workaround is good enough (and useful).
2020-07-06 18:02:47 +02:00
Max Kellermann
33ee35ab92 command/storage: check if mount point is busy
When mounting something over a directory that is already a mount
point, CompositeStorage::Mount() silently overwrites the previously
mounted storage, disposing it.  After that, SimpleDatabase::Mount()
will fail and handle_mount() will roll back the
CompositeStorage::Mount() command, effectively unmounting what was
there before (and also leaking memory).

Closes https://github.com/MusicPlayerDaemon/MPD/issues/918
2020-07-06 17:49:38 +02:00
Max Kellermann
5b291ff768 db/update/Walk: pass concatenated .mpdignore URI to storage.MapUTF8()
Fixes the "Unrecognized URI" error with the udisks storage plugin,
which is caused by the kludge in UdisksStorage::MapUTF8().
2020-07-06 17:19:38 +02:00
Max Kellermann
39d6816a6d neighbor/upnp: roll back changes if DoOpen() fails 2020-07-06 16:23:58 +02:00
Max Kellermann
6517b2d2ac neighbor/upnp: remove D-Bus filter and match in Close()
Fixes use-after-free crash bug during MPD shutdown.
2020-07-06 16:15:18 +02:00
Max Kellermann
bfdf13dca3 decoder/Plugin: allow scan_{file,stream}() to throw
Bug  is about an I/O exception thrown where none was allowed,
leading to crash via std::terminate().  However, instead of catching
and logging the error inside the decoder plugin, it should be able to
propagate the I/O error to the MPD core, so MPD can avoid trying other
decoder plugins.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/915
2020-07-06 14:13:34 +02:00
Max Kellermann
daefc61aa4 output/osx: postpone start until the end of Play()
Wait until there is data in the ring buffer.
2020-07-02 15:26:38 +02:00
Max Kellermann
6fed6e50e4 output/osx: merge some duplicate code 2020-07-02 15:25:51 +02:00
Max Kellermann
bc9e074822 output/osx: postpone start until the first Play() call
Wait until there is some data; don't let our render callback be
invoked without any data.
2020-07-02 15:21:54 +02:00
Max Kellermann
8047102542 output/osx: don't restart AudioUnit at the end of Cancel()
We shouldn't restart the AudioUnit while the ring buffer is empty, or
else our render callback may emit noise.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/771
2020-07-02 15:20:43 +02:00
Max Kellermann
fe5b81e180 output/osx: check started in Close() and Cancel() 2020-07-02 15:19:40 +02:00
Max Kellermann
f032925c2d output/osx: add started flag
This will keep track of AudioOutputUnitStart() and
AudioOutputUnitStop().  This will provide some separation between "not
(yet) (re)started" and "paused".
2020-07-02 15:18:37 +02:00
Max Kellermann
8125a5dddb output/osx: don't uninitialize AudioUnit if restart fails
This shall be done by Close(), which will be called automatically
after an error.
2020-07-02 15:10:03 +02:00
Max Kellermann
154170e475 output/osx: clear pause flag only after successful AudioOutputUnitStart() 2020-07-02 15:08:59 +02:00
Max Kellermann
fb83936feb apple/AudioUnit: add AudioUnitSetPropertyT() 2020-07-02 14:59:40 +02:00
Max Kellermann
db8bf52f7d apple/AudioObject: add AudioObjectGetStringProperty() 2020-07-02 13:50:05 +02:00
Max Kellermann
756f0b8027 apple: build static library
Move build rules from src/output/plugins/meson.build
2020-07-02 13:49:54 +02:00
Max Kellermann
b1fba8d3d7 apple/AudioObject: add missing inline 2020-07-02 13:49:52 +02:00
Max Kellermann
e606044271 apple/AudioUnit: library wrapping AudioUnit*() functions 2020-07-01 23:02:22 +02:00
Max Kellermann
bcbb3371ff apple/AudioUnit: rename to AudioObject.hxx 2020-07-01 22:49:03 +02:00
Max Kellermann
de632882d1 output/osx: move code to FindAudioDeviceByName() 2020-07-01 22:48:12 +02:00
Max Kellermann
745e492d15 output/osx: use [[maybe_unused]] 2020-07-01 22:41:00 +02:00
Max Kellermann
c5dc615efe output/osx: use IsDigitASCII() 2020-07-01 22:39:54 +02:00
Max Kellermann
beeb02025e output/osx: use range-based for 2020-07-01 22:06:36 +02:00
Max Kellermann
cdf7062597 apple/AudioUnit: wrapper functions for AudioObject properties 2020-07-01 22:05:11 +02:00
Max Kellermann
346084da1e apple/Throw: new helper library replacing osx_os_status_to_cstring() 2020-07-01 22:05:11 +02:00
Max Kellermann
bbceb5eb91 output/osx: silently ignore some errors in osx_output_set_device() 2020-07-01 22:05:11 +02:00
Max Kellermann
90d85319c2 apple/ErrorRef: new library wrapping CFErrorRef 2020-07-01 22:05:10 +02:00
Max Kellermann
3d03683e7d output: use StringIsEqual() 2020-07-01 22:04:26 +02:00
Max Kellermann
d8a74802d1 apple/StringRef: new library wrapping CFStringRef 2020-07-01 22:01:53 +02:00
Max Kellermann
191919d1b1 output/osx: remove trailing newline from exception messages 2020-07-01 22:01:51 +02:00
Max Kellermann
df38e7565b util/HugeAllocator: import std::swap() 2020-07-01 21:56:58 +02:00
Max Kellermann
cb49a03fd7 util/HugeAllocator: add noexcept 2020-07-01 21:56:54 +02:00
Max Kellermann
faee5bbb78 decoder/opus: implement End Trimming (RFC7845 4.4)
Closes https://github.com/MusicPlayerDaemon/MPD/issues/867
2020-07-01 21:26:34 +02:00
Max Kellermann
7befab7e83 decoder/opus: keep track of the granulepos
Will be needed for End Trimming (RFC7845 4.4,
https://github.com/MusicPlayerDaemon/MPD/issues/867).
2020-07-01 21:21:06 +02:00
Max Kellermann
4244e61214 decoder/opus: simplify indentation in HandleAudio() 2020-07-01 21:19:52 +02:00
Max Kellermann
46eab05045 decoder/opus: allocate buffer only in the first chained song
Fixes memory leak.  That's what we get for
2020-07-01 21:07:49 +02:00
Max Kellermann
5ca137c73c decoder/opus: add API docs 2020-07-01 20:55:18 +02:00
Max Kellermann
760238fe16 decoder/opus: apply pre-skip (RFC7845 4.2)
Fixes the first part of
https://github.com/MusicPlayerDaemon/MPD/issues/867
2020-07-01 20:44:53 +02:00
Max Kellermann
a99b4abae8 decoder/OpusHead: return pre-skip 2020-07-01 17:51:07 +02:00
Max Kellermann
472881cb95 util/ByteOrder: remove redundant inline keywords from constexpr functions 2020-07-01 17:50:34 +02:00
Max Kellermann
c4efc37ad8 system/ByteOrder: move to util/ 2020-07-01 17:49:57 +02:00
Max Kellermann
691b6a236e output/osx: improve sample rate selection
The formula in osx_output_score_sample_rate() to detect multiples of
the source sample rate was broken: when given a 44.1 kHz input file,
it preferred 16 kHz over 48 kHz, because its `frac_portion(16)=0.75`
is smaller than `frac_portion(48)=0.91`.

That formula, introduced by commit 40a1ebee29, looks completely
wrong.  It doesn't do what the code comment pretends it does.

Instead of using that `frac_portion` to calculate a score, this patch
adds to the score only if `frac_portion` is nearly `0` or `1`.  This
means that the factor is nearly integer.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/904
2020-07-01 17:38:08 +02:00
Max Kellermann
5c7243d3ad output/osx: make several fields const 2020-07-01 17:35:39 +02:00
Max Kellermann
44cfdff39a output/osx: make variables more local 2020-07-01 17:35:33 +02:00
Max Kellermann
5eedda691a output/osx: make more AudioObjectPropertyAddress instances static constexpr 2020-07-01 17:35:19 +02:00
Max Kellermann
a30d5e1b6a output/osx: make AudioObjectPropertyAddress variables static constexpr 2020-07-01 17:34:12 +02:00
Max Kellermann
8ef09a0a71 output/osx: don't use C99 designated initializers
Fixes `-Wpedantic`.
2020-07-01 17:34:06 +02:00
Max Kellermann
e8044663b3 output/{alsa,osx}: use ConstBuffer::empty() 2020-07-01 17:32:37 +02:00
Max Kellermann
8444c33514 output/osx: don't use variable-length arrays 2020-07-01 17:31:46 +02:00
Max Kellermann
2b7328b434 output/osx: fix coding style 2020-07-01 17:11:02 +02:00
Max Kellermann
ca705e1e37 python/build/meson.py: set BOOST_ROOT for Meson 0.54
Commit
08224dafcb
changed Meson to require BOOST_ROOT for cross builds.
2020-07-01 16:55:28 +02:00
Max Kellermann
d9f9b3df10 input/file: detect premature end of file
A bug report (https://github.com/MusicPlayerDaemon/MPD/issues/912)
suggests that on Linux, reading on `cifs` files may rarely return 0 (=
end of file) before the end of the file has really been reached.  But
that's just a theory which I need to validate, so this runtime check
shall catch this condition before the assertion in
DecoderBridge::Read() crashes MPD.  Let's see.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/912
2020-07-01 15:14:27 +02:00
Max Kellermann
a43ee97746 util/UriUtil: strip credentials from smb:// URIs
Closes https://github.com/MusicPlayerDaemon/MPD/issues/910
2020-06-22 22:48:56 +02:00
Max Kellermann
43c32372e7 util/UriUtil: make schemes array static 2020-06-22 22:48:07 +02:00
Max Kellermann
5716cde1fb queue/PlaylistEdit: fix crash in SetSongIdRange() while playing
An assertion failure in UpdateQueuedSong() could trigger because the
`prev` parameter is always `nullptr`, but `queued` may be set.  And in
fact, calling UpdateQueuedSong() is only necessary when the queued
song was edited, to re-queue it with the new range.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/901
2020-06-11 07:07:02 +02:00
Max Kellermann
b7a99b4a4b increment version number to 0.21.25 2020-06-11 06:29:08 +02:00
Max Kellermann
24741c5d06 release v0.21.24 2020-06-10 22:48:50 +02:00
Max Kellermann
6b3a282db4 lib/curl/Request: don't enable CURLOPT_NETRC on Windows
Our Windows build is built with `--disable-netrc`, and that makes
CURLOPT_NETRC fail, causing failures with all streams.  D'oh!

Closes https://github.com/MusicPlayerDaemon/MPD/issues/886
2020-06-10 22:46:42 +02:00
Max Kellermann
7583cfe9b7 {android,win32}/build.py: enable the GME decoder plugin
Closes https://github.com/MusicPlayerDaemon/MPD/issues/891
2020-06-10 21:33:29 +02:00
Max Kellermann
aafc9ce75b decoder/gme: use class NarrowPath() for Windows compatibility 2020-06-10 21:22:00 +02:00
Max Kellermann
fea326530b decoder/gme: simplify LoadGmeAndM3u() by moving code to ReplaceSuffix() 2020-06-10 21:20:49 +02:00
Max Kellermann
8925cc17d8 decoder/gme: use StringAfterPrefix() 2020-06-10 21:11:08 +02:00
Max Kellermann
14412c867f add a few IWYU pragmas 2020-06-10 21:10:33 +02:00
Max Kellermann
c5cc256bf2 decoder/gme: use Path::GetSuffix() 2020-06-10 21:02:07 +02:00
Max Kellermann
563c7318f9 fs/AllocatedPath: add method GetSuffix() 2020-06-10 21:00:41 +02:00
Max Kellermann
e92129f449 doc/protocol.rst: clarify the term "UNIX time"
Closes https://github.com/MusicPlayerDaemon/MPD/issues/893
2020-06-10 20:42:42 +02:00
Max Kellermann
374cc51f77 decoder/Bridge: add flag to make initial seek errors fatal
When the client wants to seek, but the decoder has already finished
decoding the current song, the player restarts the decoder with an
initial seek at the new position.  When this initial seek fails, MPD
pretends nothing has happened and plays this song from the start.

With this new flag, a restarted decoder marks the initial seek as
"essential" and fails the decoder if that seek fails.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/895
2020-06-10 17:49:10 +02:00
Rosen Penev
1008d5f67c use cwchar include
Needed for std::wmemchr under libcxx

Signed-off-by: Rosen Penev <rosenp@gmail.com>
2020-05-30 13:22:04 +02:00
Shen-Ta Hsieh
8e07ea7ad8 src/db: fitting libmpdclient interface 2020-05-29 19:00:16 +02:00
Max Kellermann
d751df0a73 storage/State: disable -Wcomma to work around Boost compiler warning 2020-05-28 14:00:31 +02:00
Max Kellermann
2c084781b0 output/openal: disable -Wdeprecated-declarations on Apple 2020-05-28 13:59:52 +02:00
Max Kellermann
ae7d550a01 meson.build: remove -Wall -Wextra, to be set by Meson 2020-05-28 13:19:34 +02:00
Max Kellermann
30d97fe8a0 meson.build: fix the WildMidi check when the feature is disabled
Fixes regression from commit 69f09648a4
2020-05-27 16:06:49 +02:00
Max Kellermann
5cb0080052 meson.build: default to warning_level=2
This branch isn't yet ready for level 3 (`-Wpedantic`) due to several
C++ violations (e.g. variable length arrays).  These are already
cleaned up in the master branch (0.22).
2020-05-27 15:57:13 +02:00
Max Kellermann
8e4ca23727 lib/ffmpeg/Time: replace C99 compound literal with C++ initializer list 2020-05-27 15:54:34 +02:00
Max Kellermann
bdc861f058 util/TemplateString: remove extra semicolon 2020-05-27 15:46:55 +02:00
Rosen Penev
8925040262 remove some more extra semicolons
Signed-off-by: Rosen Penev <rosenp@gmail.com>
2020-05-27 15:36:53 +02:00
Max Kellermann
c065950ced .travis.yml: disable zzip on OS X to fix Travis-CI build failure 2020-05-27 15:31:34 +02:00
Max Kellermann
257a77fa35 {android,win32}/build.py: build libmodplug and WildMidi
Closes https://github.com/MusicPlayerDaemon/MPD/issues/866
2020-05-27 15:03:49 +02:00
Max Kellermann
4e5d6e560b decoder/modplug: assume ModPlug is built as static library on Windows 2020-05-27 15:03:46 +02:00
Max Kellermann
d276d8eda2 decoder/wildmidi: assume WildMidi is built as static library on Windows 2020-05-27 15:03:35 +02:00
Max Kellermann
ebcb5e9368 decoder/wildmidi: use NarrowPath, fixing the Windows build 2020-05-27 15:03:33 +02:00
Max Kellermann
69f09648a4 meson.build: attempt to detect WildMidi using pkg-config
The WildMidi project added the pkg-config file in version 0.3.3, but
unfortunately, Debian still doesn't ship it 4 years later:

 https://bugs.debian.org/916631

However, for cross-compiling, the pkg-config file is very helpful.
2020-05-27 15:03:16 +02:00
Max Kellermann
9adda30c38 NEWS: move two lines below Windows/Android 2020-05-27 14:33:43 +02:00
Max Kellermann
d2d4a0251e .gitignore: add emacs lsp-mode files 2020-05-26 21:07:56 +02:00
Max Kellermann
f7b6431b6f meson.build: work around Meson bug detecting strndup() on Windows
Work around Meson bug https://github.com/mesonbuild/meson/issues/3672
2020-05-26 20:50:56 +02:00
Max Kellermann
03b9bd3a9e python/build/libs.py: update FFmpeg to 4.2.3 2020-05-26 18:54:56 +02:00
Max Kellermann
61aed60f6d python/build/libs.py: update CURL to 7.70.0 2020-05-07 14:18:55 +02:00
Max Kellermann
2cc323c9fe python/build/libs.py: update Boost to 1.73.0 2020-05-07 14:18:21 +02:00
Max Kellermann
f24ab120ee android/build.py: use -fpic instead of -fPIC on ARM/Aarch64
Sync with the Android NDK build scripts.
2020-05-07 13:58:36 +02:00
Max Kellermann
68349bc55c android/build.py: use -mfpu=vfpv3-d16 on ARMv7
This flag is used by the Android NDK build scripts as well, and this
fixes a build failure (assembler error) with FFmpeg and NDK r21.
2020-05-07 13:50:33 +02:00
Max Kellermann
209364adf2 db/simple: fix crash when mounting twice
The `db->close()` call was a `nullptr` dereference because the `db`
variable had already been moved.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/839
2020-05-05 18:57:29 +02:00
Max Kellermann
24afdee35c command/all: "tagtypes" requires no permissions
The command is used to configure the client's connection, and this
shouldn't require any permissions.  The client should be able to do
that before sending a password.
2020-04-30 13:08:09 +02:00
Max Kellermann
7aea285361 Revert "Fix unsafe float comparison."
This reverts commit a5273d6992.  It was
wrong and broke the MixRamp unit test.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/844
2020-04-30 06:57:36 +02:00
Rosen Penev
47a7707df1 Math.hxx: fix wrong macro name
_GLIBCXX_USE_C99_MATH_TR1 is the correct one.

_GLIBCXX_USE_C99_MATH is always defined.
2020-04-29 23:20:04 +02:00
Max Kellermann
6fdae1139f increment version number to 0.21.24 2020-04-29 23:20:04 +02:00
Max Kellermann
6c240f667c release v0.21.23 2020-04-23 17:46:20 +02:00
Max Kellermann
3040ddb5ec lib/nfs/FileReader: use struct stat64 on Windows 32-bit
libnfs is compiled with `-D_FILE_OFFSET_BITS=64`, but Meson decides
not to enable this mode.  We could force this mode, but then again,
these days, nobody should be using 32-bit Windows ... so this is a
kludge only for debugging with 32-bit WINE.
2020-04-23 17:32:34 +02:00
Max Kellermann
fdb28eb0c4 fs/NarrowPath: preserve nullptr in Path operator
Fixes Path::IsNull() checks on Windows.
2020-04-23 17:10:28 +02:00
Max Kellermann
7ded244a61 lib/nfs/Connection: pass POLLHUP and POLLERR to nfs_service() 2020-04-23 16:58:53 +02:00
Max Kellermann
8ed533acf3 event/SocketMonitor: handle epoll_ctl()=EBADF/ENOENT in Schedule()
This fixes a freeze bug in the NFS input/storage plugins: when libnfs
auto-reconnets after a failure, it installs the new socket on the same
file descriptor number.  MPD's attempt to unregister the old socket by
calling SocketMonitor::Steal() from NfsConnection::ScheduleSocket()
fails because the new/old socket number is not registered in epoll, so
epoll_ctl() returns ENOENT.  The problem is that it left
`scheduled_flags`, and so subsequent Schedule() calls will use
`EPOLL_CTL_MOD`, which will fail again and again.  Instead, we need to
use `EPOLL_CTL_ADD` to register the new socket.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/806

Closes https://github.com/MusicPlayerDaemon/MPD/issues/756
2020-04-23 16:58:26 +02:00
Max Kellermann
a27580d0cc lib/nfs/Connection: don't pass HANGUP to Schedule()
This flag is output-only.
2020-04-23 15:21:04 +02:00
Max Kellermann
905db05cf9 zeroconf/AvahiPoll: don't pass ERROR|HANGUP to Schedule()
These flags are output-only.
2020-04-23 15:19:24 +02:00
Max Kellermann
4242aee21e event/SocketMonitor: remove HANGUP|ERROR from ScheduleRead()
These flags are output-only.  Using them here is misleading.
2020-04-23 15:18:18 +02:00
Max Kellermann
e71bd2a08b event/PollGroupWinSelect: make EVENT_{READ,WRITE} static 2020-04-23 15:10:57 +02:00
Max Kellermann
e53a4d0a9e lib/nfs/FileReader: reset state in OnNfsCallback()
The object's state is `IDLE` when OnNfsCallback() gets invoked, so
let's use the start of the method to reset the `state` field.
2020-04-23 14:54:52 +02:00
Max Kellermann
159389164a lib/nfs/FileReader: set state=IDLE before invoking callback
Fixes assertion failure if the callback fails.
2020-04-23 14:51:43 +02:00
geneticdrift
0a92fbc18e tag/Fallback: add tag fallback for AlbumSort
Closes https://github.com/MusicPlayerDaemon/MPD/issues/832
2020-04-22 22:00:38 +02:00
Max Kellermann
138c29320b gme: adapt to API change in the upcoming version 0.7.0
Closes https://github.com/MusicPlayerDaemon/MPD/issues/833
2020-04-22 21:53:00 +02:00
Max Kellermann
8f00dbea45 lib/icu/Compare: add Windows implementation
Using CompareStringEx() and FindNLSStringEx().

Implements a missing piece for
https://github.com/MusicPlayerDaemon/MPD/issues/820
2020-04-22 21:42:12 +02:00
Max Kellermann
f3fd2eb618 lib/icu/Compare: use AllocatedString::Clone() 2020-04-22 21:39:13 +02:00
Max Kellermann
fc92db83cf lib/icu/Collate: use NORM_IGNORECASE instead of LINGUISTIC_IGNORECASE
LINGUISTIC_IGNORECASE is unimplemented on Wine, but since we don't
have any locale support (yet), and we're using LOCALE_NAME_INVARIANT,
NORM_IGNORECASE should essentially be the same, so why bother.
2020-04-22 21:39:13 +02:00
Max Kellermann
3b0f8d5516 lib/icu/CaseFold: remove Windows implementation
Reverts commit fb3564fbe7

LCMapStringEx() doesn't do what I imagined it would do 5 years ago.
D'oh!

Closes https://github.com/MusicPlayerDaemon/MPD/issues/820
2020-04-22 19:32:36 +02:00
Rosen Penev
a5273d6992 Fix unsafe float comparison.
Switching == to >= should be safe here since the next if is the opposite.

Signed-off-by: Rosen Penev <rosenp@gmail.com>
2020-04-22 18:21:40 +02:00
Thomas Guillem
b18074f899 storage/curl: fix path comparison when the server escapes differently
Unescape the base path and the path coming from the server (href) to fix the
comparison when the server uses different escaped characters.

The outputted name need to be unescaped. Doing that before or after the
HrefToEscapedName() call should not change the current behavior.
2020-04-15 13:50:12 +02:00
Thomas Guillem
3d8067a041 storage/curl: fix href when file has a '&' char
If the file name is "Hello & bye", 3 CharacterData events will be sent with the
State::HREF state:
 - "Hello%20"
 - "&"
 - "%20bye"

Reproduced with files hosted on an apache2 DAV server: 2.4.38-3+deb10u3.
2020-04-15 13:18:16 +02:00
Florian Heese
f6fe001fa9 Added missing channel order setups for ALSA 2020-04-15 13:13:09 +02:00
Max Kellermann
32a5bf043b player/Thread: drain outputs at end of song in "single" mode
Without this, the Pause() call would drop the ring buffers and would
skip a considerable portion of the end of the song.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/824
2020-04-14 16:07:03 +02:00
Max Kellermann
8d2079482f Merge branch 'lkj' of git://github.com/neheb/MPD into v0.21.x 2020-04-14 13:12:42 +02:00
Max Kellermann
c331c75fde increment version number to 0.21.23 2020-04-14 13:12:36 +02:00
Rosen Penev
6080c3b4ba Math.hxx: move cmath include out of define
The _GLIBCXX_USE_C99_MATH macro is defined in glibcxx by c++config.h, which
gets included by every header. Which means a header needs to be present.

(cherry picked from commit 79e9aff338)
2020-04-09 14:24:06 -07:00
Max Kellermann
5ccfcffcc1 release v0.21.22 2020-04-02 17:48:56 +02:00
Max Kellermann
afe2aaa5f6 fs/io/GzipOutputStream: increase buffer size to 16 kB
Reduce I/O overhead.
2020-04-02 17:17:58 +02:00
Max Kellermann
9b11caa0e6 fs/io/BufferedReader: larger default buffer (4 kB -> 16 kB)
Reduce I/O overhead.
2020-04-02 17:17:27 +02:00
Max Kellermann
a689b881d3 test/meson.build: work around linker failure due to statically linked CURL 2020-04-02 17:16:05 +02:00
Max Kellermann
e94c436264 src/event/meson.build: depend in libnet.a
The event library uses various libnet.a classes,
e.g. SocketDescriptor.
2020-04-02 17:16:05 +02:00
Max Kellermann
bad829509e test/ShutdownHandler: add inline to work around Windows linker problems 2020-04-02 17:16:05 +02:00
Max Kellermann
9c66b0414a test/*: fix Windows build using class FromNarrowPath 2020-04-02 17:16:05 +02:00
Max Kellermann
4d453a8313 fs/NarrowPath: add class FromNarrowPath
Move code from ParseCommandLine().
2020-04-02 17:15:34 +02:00
Max Kellermann
61d7b436a2 fs/NarrowPath: un-inline Windows constructor 2020-04-02 16:27:44 +02:00
Max Kellermann
cdddaf21b0 db/simple/Directory: optimize GetName() using the parent's path
This method gets called a lot during MPD startup, via FindChild() and
directory_load_subdir(), so this is worth optimizing at the expense of
code readability.

This speeds up MPD startup by 10%.
2020-04-02 16:12:08 +02:00
Max Kellermann
b267ba5f0a tag/Pool: enlarge hash table
This consumes more memory (plus 48 kB on 32 bit systems), but reduces
the number of hash collisions, speeding up MPD startup with large
databases.
2020-04-02 15:45:35 +02:00
Max Kellermann
8270043053 Revert "decoder/ffmpeg: copy the AVPacket in ffmpeg_send_packet()"
This reverts commit eb192137d6.

This is no longer necessary because we require FFmpeg 3.1 or newer
since MPD 0.21.2.

This fixes a deprecation warning because the implicit AVPacket copy
constructor copies the deprecated attribute `convergence_duration`.
2020-04-01 17:30:28 +02:00
Max Kellermann
c00ce42bca python/build/libs.py: update libmpdclient to 2.18 2020-04-01 17:17:30 +02:00
Max Kellermann
3852ddbbce .travis.yml: install more packages on OSX
Enable lots of plugins for better CI coverage.
2020-04-01 16:37:45 +02:00
Max Kellermann
672bc3ab67 time/Convert: fix GetTimeZoneOffset() on Windows
Was using the wrong parameter.
2020-04-01 16:21:29 +02:00
Max Kellermann
62229f14da test/time: add test for LocalTime(), GmTime() 2020-04-01 16:21:29 +02:00
Max Kellermann
a4c925c8d7 test/meson.build: move TestTime to time/ 2020-04-01 16:12:01 +02:00
Max Kellermann
60610e90b1 test/net/TestIPv[46]Address: fix Windows build errors 2020-04-01 16:09:24 +02:00
Max Kellermann
90184e0ce7 python/build/libs.py: update CURL to 7.69.1 2020-04-01 15:49:16 +02:00
Max Kellermann
9c3e1d450a fs/io/GunzipReader: increase buffer size to 64 kB
Reduces I/O overhead while reading a compressed database file.
2020-03-31 15:07:39 +02:00
Thomas Guillem
60f2116202 android/Settings: remove the EXPIRIMENTAL text
Using MPD from Android since quite some times now. I consider it very stable
now.
2020-03-26 17:31:31 +01:00
Thomas Guillem
4ff2532330 android: add TV support
TODO: Not sure the app could be accepted on the play store without a valid
banner.
2020-03-26 17:31:20 +01:00
Thomas Guillem
9c15760c4d android/Main: handle API26 NotificationChannel
This seems to be required on recent Android versions (tested with Android 10).
This is also required for android TV services (cf. next commit).

This is done using Java reflection so that the project doesn't depend on
android compat libs.
2020-03-26 17:30:55 +01:00
Max Kellermann
e1c43ec65f Merge branch 'ucl' of git://github.com/neheb/MPD into v0.21.x 2020-03-26 17:28:21 +01:00
Thomas Guillem
4dd10894ba lib/curl/Request: fix Exception "error" on Android
Apparently, it's not possible to change CURLOPT_NETRC on Android.
2020-03-26 17:26:14 +01:00
Rosen Penev
608d7ec1e7 [clang-tidy] change integer prefixes to uppercase
Found with readability-uppercase-literal-suffix

Signed-off-by: Rosen Penev <rosenp@gmail.com>
2020-03-26 17:25:20 +01:00
Max Kellermann
8474599ed6 lib/curl/Easy: add method Unpause() 2020-03-26 17:22:10 +01:00
Max Kellermann
ab39f64fc0 lib/curl/Easy: add setter functions 2020-03-26 17:21:30 +01:00
Max Kellermann
185fbca282 lib/curl/Global: make ReadInfo() private 2020-03-26 17:20:10 +01:00
Max Kellermann
6e3b2fd844 lib/curl/Global: remove redundant API docs 2020-03-26 17:20:06 +01:00
Max Kellermann
dab39dc778 lib/curl: fix coding style 2020-03-26 17:19:48 +01:00
Max Kellermann
8cd5e79fbd event/*, ...: make GetEventLoop() const 2020-03-26 17:19:13 +01:00
Max Kellermann
1de3ac6c78 lib/curl/Init: add const overloads 2020-03-26 17:18:27 +01:00
Max Kellermann
abe06a5fa6 lib/curl/Init: add noexcept 2020-03-26 17:18:23 +01:00
Rosen Penev
85c27840a3 treewide: use boost::lround when std::round is unavailable
This is the case with uClibc-ng currently.

Signed-off-by: Rosen Penev <rosenp@gmail.com>
(cherry picked from commit 769cd0ee9f0cf8ceb026aa751b5d4a390bb5dbdc)
(changed define to match master)
2020-03-25 18:54:15 -07:00
Thomas Guillem
81c16273c5 output/sles: use the AndroidMixerPlugin 2020-03-25 20:08:53 +01:00
Thomas Guillem
801ae86b5d mixer: add AndroidMixerPlugin 2020-03-25 20:07:26 +01:00
Thomas Guillem
5619fd0bba android: Context: add GetAudioManager 2020-03-25 20:07:25 +01:00
Thomas Guillem
200258c7c3 android: add AudioManager 2020-03-25 20:07:24 +01:00
Max Kellermann
5418bb49fb android/Context: add noexcept 2020-03-25 20:07:18 +01:00
Max Kellermann
3449c14ff5 java/Object: rename class Object to GlobalObject 2020-03-25 20:07:15 +01:00
kowalcj0
36a89e8fe7 Support RSS feeds with application/xml MIME-type 2020-03-22 10:49:38 +01:00
Max Kellermann
8e6a21a9c2 increment version number to 0.21.22 2020-03-22 10:48:53 +01:00
Max Kellermann
c560ec8ea6 release v0.21.21 2020-03-19 15:22:28 +01:00
Max Kellermann
56c234b410 raise default "max_connections" value to 100
Documentation says the limit is 5, but it was really 10 (at least
since 2004).  But since MPD wants to promote using many small clients
idling around, and these clients consume only very few resources, it
seems reasonable to raise this limit's default value.
2020-03-19 13:30:46 +01:00
Max Kellermann
82743dfd02 playlist/asx: concatenate multiple CharacterData fragments
Similar to c45f113856
2020-03-12 21:07:37 +01:00
Max Kellermann
33694642bd playlist/asx: add State::TAG 2020-03-12 20:42:16 +01:00
Max Kellermann
c71242d743 playlist/asx: use tag_table to convert element name to TagType 2020-03-12 20:40:18 +01:00
Max Kellermann
c45f113856 playlist/xspf: concatenate multiple CharacterData fragments
Closes https://github.com/MusicPlayerDaemon/MPD/issues/781
2020-03-12 08:02:58 +01:00
Max Kellermann
e0a8fd398c playlist/xspf: add State::TAG 2020-03-12 08:00:54 +01:00
Max Kellermann
3e97058151 playlist/xspf: move location.empty() check to _start_element() 2020-03-11 20:54:53 +01:00
Max Kellermann
51b1dd8672 playlist/xspf: use tag_table to convert element name to TagType 2020-03-11 20:51:47 +01:00
Max Kellermann
98a7d8da6c playlist/xspf: use C++11 initializer 2020-03-11 20:51:10 +01:00
Max Kellermann
acb29f792f tag/Mask: fix yet another typo, this time in Unset()
Similar to commits e8f2f98048 and
ff1ff1e54a

Closes https://github.com/MusicPlayerDaemon/MPD/issues/783
2020-03-11 20:34:02 +01:00
Max Kellermann
cd364023ae .travis.yml: rename "matrix" to "jobs"
Travis has changed the canonical name for this a while ago.

(Now really.  The last commit for this was empty.)
2020-03-07 09:31:46 +01:00
Max Kellermann
8d34a1cfc6 archive/iso9660: skip empty filenames
Aparently, libcdio sometimes returns empty filenames, causing MPD
crashes.  This shouldn't really happen, and I consider this a libcdio
bug - but if it happens, people blame MPD, so let's add a check.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/776
2020-03-07 09:30:56 +01:00
Max Kellermann
73a1f078a6 archive/iso9660: use IsSpecialFilename() 2020-03-07 09:30:56 +01:00
Max Kellermann
b7ce452308 fs/Traits: add IsSpecialFilename()
Merge some duplicate code in a central library.
2020-03-07 09:30:56 +01:00
Max Kellermann
5faf76051d .travis.yml: force updating homebrew on OSX
Workaround for Travis failures as described in
 https://travis-ci.community/t/macos-build-fails-because-of-homebrew-bundle-unknown-command/7296/18
2020-03-07 09:30:56 +01:00
Max Kellermann
5fe70a3417 .travis.yml: rename "matrix" to "jobs"
Travis has changed the canonical name for this a while ago.
2020-03-07 09:30:56 +01:00
Thomas Klausner
7a68b1e71f Adapt SolarisOutputPlugin.cxx to be usable on NetBSD. 2020-02-29 10:05:29 +01:00
Thomas Klausner
d5468dfe89 Add missing header.
Fixes
../src/time/ISO8601.cxx:67:24: error: use of undeclared identifier 'strtoul'
        unsigned long value = strtoul(s, &endptr, 10);
                              ^
../src/time/ISO8601.cxx:77:14: error: use of undeclared identifier 'strtoul'
                        minutes = strtoul(s, &endptr, 10);
                                  ^

on NetBSD with clang 9.0.0.
2020-02-29 10:04:54 +01:00
John Regan
976372ff63 gme: check for empty metadata strings instead of nullptr
Using libgme 0.6.2 on macOS, it appears that gme_info_t strings can be
empty, which creates weird track titles: (001/050)

This adds an additional check for an empty string.
2020-02-25 20:12:08 +01:00
Max Kellermann
9abb686eeb increment version number to 0.21.21 2020-02-16 20:48:46 +01:00
263 changed files with 3609 additions and 1448 deletions
.gitignore.travis.ymlNEWS
android
doc
meson.build
python/build
src
AudioFormat.hxxCommandLine.cxxLog.cxxLog.hxxLogBackend.cxxLogBackend.hxxLogV.hxxMain.cxxPermission.cxxPluginUnavailable.hxxReplayGainGlobal.cxxReplayGainInfo.cxxSongUpdate.cxxStats.cxxTagFile.cxxTagFile.hxxTagStream.cxxTagStream.hxx
android
apple
archive
command
db
decoder
encoder
event
fs
input
java
lib
mixer
neighbor
net
output
pcm
player
playlist
queue
storage
system
tag
thread
time
unix
util
zeroconf
test
win32

3
.gitignore vendored

@@ -6,3 +6,6 @@
/output/
__pycache__/
/.clangd/
/compile_commands.json

@@ -1,6 +1,6 @@
language: cpp
matrix:
jobs:
include:
# Ubuntu Bionic (18.04) with GCC 7
- os: linux
@@ -126,6 +126,24 @@ matrix:
packages:
- ccache
- meson
- icu4c
- ffmpeg
- libnfs
- yajl
- libupnp
- libid3tag
- chromaprint
- libsamplerate
- libsoxr
# libzzip appears to be broken on Homebrew: "ld: library not found for -lzzip"
#- libzzip
- flac
- opus
- libvorbis
- faad2
- wavpack
- libmpdclient
update: true
env:
- MATRIX_EVAL="export PATH=/usr/local/opt/ccache/libexec:$PATH HOMEBREW_NO_ANALYTICS=1"

111
NEWS

@@ -1,3 +1,114 @@
ver 0.21.26 (2020/09/21)
* database
- inotify: obey ".mpdignore" files
* output
- osx: fix crash bug
- sles: support floating point samples
* archive
- bzip2: fix crash on corrupt bzip2 file
- bzip2: flush output at end of input file
- iso9660: fix unaligned reads
- iso9660: support seeking
- zzip: fix crash on corrupt ZIP file
* decoder
- ffmpeg: remove "rtsp://" from the list of supported protocols
- ffmpeg: add "hls+http://" to the list of supported protocols
- opus: support the gain value from the Opus header
- sndfile: fix lost samples at end of file
* fix "single" mode bug after resuming playback
* the default log_level is "default", not "info"
ver 0.21.25 (2020/07/06)
* protocol:
- fix crash when using "rangeid" while playing
* database
- simple: automatically scan new mounts
- upnp: fix compatibility with Plex DLNA
* storage
- fix disappearing mounts after mounting twice
- udisks: fix reading ".mpdignore"
* input
- file: detect premature end of file
- smbclient: don't send credentials to MPD clients
* decoder
- opus: apply pre-skip and end trimming
- opus: fix memory leak
- opus: fix crash bug
- vorbis: fix crash bug
* output
- osx: improve sample rate selection
- osx: fix noise while stopping
* neighbor
- upnp: fix crash during shutdown
* Windows/Android:
- fix Boost detection after breaking change in Meson 0.54
ver 0.21.24 (2020/06/10)
* protocol
- "tagtypes" requires no permissions
* database
- simple: fix crash when mounting twice
* decoder
- modplug: fix Windows build failure
- wildmidi: attempt to detect WildMidi using pkg-config
- wildmidi: fix Windows build failure
* player
- don't restart current song if seeking beyond end
* Android
- enable the decoder plugins GME, ModPlug and WildMidi
- fix build failure with Android NDK r21
* Windows
- fix stream playback
- enable the decoder plugins GME, ModPlug and WildMidi
- work around Meson bug breaking the Windows build with GCC 10
* fix unit test failure
ver 0.21.23 (2020/04/23)
* protocol
- add tag fallback for AlbumSort
* storage
- curl: fix corrupt "href" values in the presence of XML entities
- curl: unescape "href" values
* input
- nfs: fix crash bug
- nfs: fix freeze bug on reconnect
* decoder
- gme: adapt to API change in the upcoming version 0.7.0
* output
- alsa: implement channel mapping for 5.0 and 7.0
* player
- drain outputs at end of song in "single" mode
* Windows
- fix case insensitive search
ver 0.21.22 (2020/04/02)
* database
- simple: optimize startup
* input
- curl: fix streaming errors on Android
* playlist
- rss: support MIME type application/xml
* mixer
- android: new mixer plugin for "sles" output
* Android
- TV support
* Windows
- fix time zone offset check
* fix build failures with uClibc-ng
ver 0.21.21 (2020/03/19)
* configuration
- fix bug in "metadata_to_use" setting
* playlist
- asx, xspf: fix corrupt tags in the presence of XML entities
* archive
- iso9660: skip empty file names to work around libcdio bug
* decoder
- gme: ignore empty tags
* output
- solaris: port to NetBSD
* raise default "max_connections" value to 100
ver 0.21.20 (2020/02/16)
* decoder
- audiofile, ffmpeg, sndfile: handle MIME type "audio/wav"

@@ -2,18 +2,25 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.musicpd"
android:installLocation="auto"
android:versionCode="43"
android:versionName="0.21.20">
android:versionCode="49"
android:versionName="0.21.26">
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="28"/>
<uses-feature android:name="android.software.leanback"
android:required="false" />
<uses-feature android:name="android.hardware.touchscreen"
android:required="false" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<application android:allowBackup="true"
android:icon="@drawable/icon"
android:banner="@drawable/icon"
android:label="@string/app_name">
<activity android:name=".Settings"
android:label="@string/app_name">
@@ -22,6 +29,14 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".Settings"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
</intent-filter>
</activity>
<receiver android:name=".Receiver">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />

@@ -26,7 +26,7 @@ android_abis = {
'ndk_arch': 'arm',
'toolchain_arch': 'arm-linux-androideabi',
'llvm_triple': 'armv7-linux-androideabi',
'cflags': '-march=armv7-a -mfpu=vfp -mfloat-abi=softfp',
'cflags': '-fpic -march=armv7-a -mfpu=vfpv3-d16 -mfloat-abi=softfp',
},
'arm64-v8a': {
@@ -34,7 +34,7 @@ android_abis = {
'ndk_arch': 'arm64',
'toolchain_arch': 'aarch64-linux-android',
'llvm_triple': 'aarch64-linux-android',
'cflags': '',
'cflags': '-fpic',
},
'x86': {
@@ -42,7 +42,7 @@ android_abis = {
'ndk_arch': 'x86',
'toolchain_arch': 'x86',
'llvm_triple': 'i686-linux-android',
'cflags': '-march=i686 -mtune=intel -mssse3 -mfpmath=sse -m32',
'cflags': '-fPIC -march=i686 -mtune=intel -mssse3 -mfpmath=sse -m32',
},
'x86_64': {
@@ -50,7 +50,7 @@ android_abis = {
'ndk_arch': 'x86_64',
'toolchain_arch': 'x86_64',
'llvm_triple': 'x86_64-linux-android',
'cflags': '-m64',
'cflags': '-fPIC -m64',
},
}
@@ -97,7 +97,6 @@ class AndroidNdkToolchain:
llvm_triple = abi_info['llvm_triple'] + android_api_level
common_flags = '-Os -g'
common_flags += ' -fPIC'
common_flags += ' ' + abi_info['cflags']
toolchain_bin = os.path.join(toolchain_path, 'bin')
@@ -169,6 +168,9 @@ thirdparty_libs = [
opus,
flac,
libid3tag,
libmodplug,
wildmidi,
gme,
ffmpeg,
curl,
libexpat,

@@ -21,6 +21,7 @@ package org.musicpd;
import android.annotation.TargetApi;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.ComponentName;
@@ -35,6 +36,9 @@ import android.os.RemoteException;
import android.util.Log;
import android.widget.RemoteViews;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
public class Main extends Service implements Runnable {
private static final String TAG = "Main";
private static final String REMOTE_ERROR = "MPD process was killed";
@@ -156,11 +160,36 @@ public class Main extends Service implements Runnable {
sendMessage(MSG_SEND_STATUS, mStatus, 0, mError);
}
private Notification.Builder createNotificationBuilderWithChannel() {
final NotificationManager notificationManager = (NotificationManager) this.getSystemService(Context.NOTIFICATION_SERVICE);
if (notificationManager == null)
return null;
final String id = "org.musicpd";
final String name = "MPD service";
final int importance = 3; /* NotificationManager.IMPORTANCE_DEFAULT */
try {
Class<?> ncClass = Class.forName("android.app.NotificationChannel");
Constructor<?> ncCtor = ncClass.getConstructor(String.class, CharSequence.class, int.class);
Object nc = ncCtor.newInstance(id, name, importance);
Method nmCreateNotificationChannelMethod =
NotificationManager.class.getMethod("createNotificationChannel", ncClass);
nmCreateNotificationChannelMethod.invoke(notificationManager, nc);
Constructor nbCtor = Notification.Builder.class.getConstructor(Context.class, String.class);
return (Notification.Builder) nbCtor.newInstance(this, id);
} catch (Exception e)
{
Log.e(TAG, "error creating the NotificationChannel", e);
return null;
}
}
private void start() {
if (mThread != null)
return;
mThread = new Thread(this);
mThread.start();
final Intent mainIntent = new Intent(this, Settings.class);
mainIntent.setAction("android.intent.action.MAIN");
@@ -168,13 +197,25 @@ public class Main extends Service implements Runnable {
final PendingIntent contentIntent = PendingIntent.getActivity(this, 0,
mainIntent, PendingIntent.FLAG_CANCEL_CURRENT);
Notification notification = new Notification.Builder(this)
.setContentTitle(getText(R.string.notification_title_mpd_running))
Notification.Builder nBuilder;
if (Build.VERSION.SDK_INT >= 26 /* Build.VERSION_CODES.O */)
{
nBuilder = createNotificationBuilderWithChannel();
if (nBuilder == null)
return;
}
else
nBuilder = new Notification.Builder(this);
Notification notification = nBuilder.setContentTitle(getText(R.string.notification_title_mpd_running))
.setContentText(getText(R.string.notification_text_mpd_running))
.setSmallIcon(R.drawable.notification_icon)
.setContentIntent(contentIntent)
.build();
mThread = new Thread(this);
mThread.start();
startForeground(R.string.notification_title_mpd_running, notification);
startService(new Intent(this, Main.class));
}

@@ -105,12 +105,13 @@ public class Settings extends Activity {
else
mRunButton.setChecked(false);
mFirstRun = true;
mTextStatus.setText("");
break;
case MSG_STARTED:
Log.d(TAG, "onStarted");
mRunButton.setChecked(true);
mFirstRun = true;
mTextStatus.setText("CAUTION: this version is EXPERIMENTAL!"); // XXX
mTextStatus.setText("MPD service started");
break;
case MSG_LOG:
if (mLogListArray.size() > MAX_LOGS)

@@ -38,7 +38,7 @@ author = 'Max Kellermann'
# built documents.
#
# The short X.Y version.
version = '0.21.20'
version = '0.21.26'
# The full version, including alpha/beta/rc tags.
release = version

@@ -60,25 +60,25 @@ The default plugin which gives :program:`MPD` access to local files. It is used
curl
----
A WebDAV client using libcurl. It is used when :code:`music_directory` contains a http:// or https:// URI, for example :samp:`https://the.server/dav/`.
A WebDAV client using libcurl. It is used when :code:`music_directory`
contains a ``http://`` or ``https://`` URI, for example
:samp:`https://the.server/dav/`.
smbclient
---------
Load music files from a SMB/CIFS server. It is used when :code:`music_directory` contains a smb:// URI, for example :samp:`smb://myfileserver/Music`.
Load music files from a SMB/CIFS server. It is used when
:code:`music_directory` contains a ``smb://`` URI, for example
:samp:`smb://myfileserver/Music`.
nfs
---
Load music files from a NFS server. It is used when :code:`music_directory` contains a nfs:// URI according to RFC2224, for example :samp:`nfs://servername/path`.
Load music files from a NFS server. It is used when
:code:`music_directory` contains a ``nfs://`` URI according to
RFC2224, for example :samp:`nfs://servername/path`.
This plugin uses libnfs, which supports only NFS version 3. Since :program:`MPD` is not allowed to bind to "privileged ports", the NFS server needs to enable the "insecure" setting; example :file:`/etc/exports`:
.. code-block:: none
/srv/mp3 192.168.1.55(ro,insecure)
Don't fear: "insecure" does not mean that your NFS server is insecure. A few decades ago, people thought the concept of "privileged ports" would make network services "secure", which was a fallacy. The absence of this obsolete "security" measure means little.
See :ref:`input_nfs` for more information.
udisks
------
@@ -162,7 +162,10 @@ curl
Opens remote files or streams over HTTP using libcurl.
Note that unless overridden by the below settings (e.g. by setting them to a blank value), general curl configuration from environment variables such as http_proxy or specified in :file:`~/.curlrc` will be in effect.
Note that unless overridden by the below settings (e.g. by setting
them to a blank value), general curl configuration from environment
variables such as ``http_proxy`` or specified in :file:`~/.curlrc`
will be in effect.
.. list-table::
:widths: 20 80
@@ -182,7 +185,9 @@ Note that unless overridden by the below settings (e.g. by setting them to a bla
ffmpeg
------
Access to various network protocols implemented by the FFmpeg library: gopher://, rtp://, rtsp://, rtmp://, rtmpt://, rtmps://
Access to various network protocols implemented by the FFmpeg library:
``gopher://``, ``rtp://``, ``rtsp://``, ``rtmp://``, ``rtmpt://``,
``rtmps://``
file
----
@@ -194,30 +199,51 @@ mms
Plays streams with the MMS protocol using `libmms <https://launchpad.net/libmms>`_.
.. _input_nfs:
nfs
---
Allows :program:`MPD` to access files on NFSv3 servers without actually mounting them (i.e. in userspace, without help from the kernel's VFS layer). All URIs with the nfs:// scheme are used according to RFC2224. Example:
Allows :program:`MPD` to access files on NFS servers without actually
mounting them (i.e. with :program:`libnfs` in userspace, without help
from the kernel's VFS layer). All URIs with the ``nfs://`` scheme are
used according to RFC2224. Example:
.. code-block:: none
mpc add nfs://servername/path/filename.ogg
Note that this usually requires enabling the "insecure" flag in the server's /etc/exports file, because :program:`MPD` cannot bind to so-called "privileged" ports. Don't fear: this will not make your file server insecure; the flag was named in a time long ago when privileged ports were thought to be meaningful for security. By today's standards, NFSv3 is not secure at all, and if you believe it is, you're already doomed.
This plugin uses :program:`libnfs`, which supports only NFS version 3.
Since :program:`MPD` is not allowed to bind to so-called "privileged
ports", the NFS server needs to enable the ``insecure`` setting;
example :file:`/etc/exports`:
.. code-block:: none
/srv/mp3 192.168.1.55(ro,insecure)
Don't fear: this will not make your file server insecure; the flag was
named a time long ago when privileged ports were thought to be
meaningful for security. By today's standards, NFSv3 is not secure at
all, and if you believe it is, you're already doomed.
smbclient
---------
Allows :program:`MPD` to access files on SMB/CIFS servers (e.g. Samba or Microsoft Windows). All URIs with the smb:// scheme are used. Example:
Allows :program:`MPD` to access files on SMB/CIFS servers (e.g. Samba
or Microsoft Windows). All URIs with the ``smb://`` scheme are
used. Example:
.. code-block:: none
mpc add smb://servername/sharename/filename.ogg
mpc add smb://username:password@servername/sharename/filename.ogg
qobuz
-----
Play songs from the commercial streaming service Qobuz. It plays URLs in the form qobuz://track/ID, e.g.:
Play songs from the commercial streaming service Qobuz. It plays URLs
in the form ``qobuz://track/ID``, e.g.:
.. code-block:: none
@@ -243,7 +269,9 @@ Play songs from the commercial streaming service Qobuz. It plays URLs in the for
tidal
-----
Play songs from the commercial streaming service `Tidal <http://tidal.com/>`_. It plays URLs in the form tidal://track/ID, e.g.:
Play songs from the commercial streaming service `Tidal
<http://tidal.com/>`_. It plays URLs in the form ``tidal://track/ID``,
e.g.:
.. warning::
@@ -1044,7 +1072,8 @@ sles
Plugin using the `OpenSL ES <https://www.khronos.org/opensles/>`__
audio API. Its primary use is local playback on Android, where
:ref:`ALSA <alsa_plugin>` is not available.
:ref:`ALSA <alsa_plugin>` is not available. It supports 16 bit and
floating point samples.
solaris

@@ -464,7 +464,8 @@ Querying :program:`MPD`'s status
- ``songs``: number of songs
- ``uptime``: daemon uptime in seconds
- ``db_playtime``: sum of all song times in the database in seconds
- ``db_update``: last db update in UNIX time
- ``db_update``: last db update in UNIX time (seconds since
1970-01-01 UTC)
- ``playtime``: time length of music played
Playback options

@@ -695,7 +695,7 @@ These settings are various limitations to prevent :program:`MPD` from using too
* - **connection_timeout SECONDS**
- If a client does not send any new data in this time period, the connection is closed. Clients waiting in "idle" mode are excluded from this. Default is 60.
* - **max_connections NUMBER**
- This specifies the maximum number of clients that can be connected to :program:`MPD` at the same time. Default is 5.
- This specifies the maximum number of clients that can be connected to :program:`MPD` at the same time. Default is 100.
* - **max_playlist_length NUMBER**
- The maximum number of songs that can be in the playlist. Default is 16384.
* - **max_command_list_size KBYTES**

@@ -1,11 +1,12 @@
project(
'mpd',
['c', 'cpp'],
version: '0.21.20',
version: '0.21.26',
meson_version: '>= 0.49.0',
default_options: [
'c_std=c99',
'cpp_std=c++14'
'cpp_std=c++14',
'warning_level=2',
],
license: 'GPLv2+',
)
@@ -40,9 +41,6 @@ common_cxxflags = [
]
test_common_flags = [
'-Wall',
'-Wextra',
'-fvisibility=hidden',
'-ffast-math',
@@ -142,7 +140,13 @@ conf.set('HAVE_GETPWNAM_R', compiler.has_function('getpwnam_r'))
conf.set('HAVE_GETPWUID_R', compiler.has_function('getpwuid_r'))
conf.set('HAVE_INITGROUPS', compiler.has_function('initgroups'))
conf.set('HAVE_FNMATCH', compiler.has_function('fnmatch'))
conf.set('HAVE_STRNDUP', compiler.has_function('strndup', prefix: '#define _GNU_SOURCE\n#include <string.h>'))
# Explicitly exclude Windows in this check because
# https://github.com/mesonbuild/meson/issues/3672 (reported in 2018,
# still not fixed in 2020) causes Meson to believe it exists, because
# __builtin_strndup() exists (but strndup() still cannot be used).
conf.set('HAVE_STRNDUP', not is_windows and compiler.has_function('strndup', prefix: '#define _GNU_SOURCE\n#include <string.h>'))
conf.set('HAVE_STRCASESTR', compiler.has_function('strcasestr'))
conf.set('HAVE_PRCTL', is_linux)
@@ -290,6 +294,7 @@ if not is_android
else
sources += [
'src/android/Context.cxx',
'src/android/AudioManager.cxx',
'src/android/Environment.cxx',
'src/android/LogListener.cxx',
]
@@ -311,8 +316,11 @@ subdir('src/util')
subdir('src/time')
subdir('src/system')
subdir('src/thread')
subdir('src/net')
subdir('src/event')
subdir('src/apple')
subdir('src/lib/dbus')
subdir('src/lib/icu')
subdir('src/lib/smbclient')
@@ -335,7 +343,6 @@ subdir('src/lib/yajl')
subdir('src/fs')
subdir('src/config')
subdir('src/net')
subdir('src/tag')
subdir('src/pcm')
subdir('src/neighbor')

45
python/build/cmake.py Normal file

@@ -0,0 +1,45 @@
import subprocess
from build.project import Project
def configure(toolchain, src, build, args=()):
cross_args = []
if toolchain.is_windows:
cross_args.append('-DCMAKE_SYSTEM_NAME=Windows')
cross_args.append('-DCMAKE_RC_COMPILER=' + toolchain.windres)
configure = [
'cmake',
src,
'-DCMAKE_INSTALL_PREFIX=' + toolchain.install_prefix,
'-DCMAKE_BUILD_TYPE=release',
'-DCMAKE_C_COMPILER=' + toolchain.cc,
'-DCMAKE_CXX_COMPILER=' + toolchain.cxx,
'-DCMAKE_C_FLAGS=' + toolchain.cflags + ' ' + toolchain.cppflags,
'-DCMAKE_CXX_FLAGS=' + toolchain.cxxflags + ' ' + toolchain.cppflags,
'-GNinja',
] + cross_args + args
subprocess.check_call(configure, env=toolchain.env, cwd=build)
class CmakeProject(Project):
def __init__(self, url, md5, installed, configure_args=[],
**kwargs):
Project.__init__(self, url, md5, installed, **kwargs)
self.configure_args = configure_args
def configure(self, toolchain):
src = self.unpack(toolchain)
build = self.make_build_path(toolchain)
configure(toolchain, src, build, self.configure_args)
return build
def build(self, toolchain):
build = self.configure(toolchain)
subprocess.check_call(['ninja', 'install'],
cwd=build, env=toolchain.env)

@@ -4,13 +4,14 @@ from os.path import abspath
from build.project import Project
from build.zlib import ZlibProject
from build.meson import MesonProject
from build.cmake import CmakeProject
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.17.tar.xz',
'ee9b8f1c7e95b65c8f18a354daf7b16bfcd455fc52a0f3b5abe402316bce3559',
'https://www.musicpd.org/download/libmpdclient/2/libmpdclient-2.19.tar.xz',
'158aad4c2278ab08e76a3f2b0166c99b39fae00ee17231bd225c5a36e977a189',
'lib/libmpdclient.a',
)
@@ -24,8 +25,8 @@ libogg = AutotoolsProject(
)
libvorbis = AutotoolsProject(
'http://downloads.xiph.org/releases/vorbis/libvorbis-1.3.6.tar.xz',
'af00bb5a784e7c9e69f56823de4637c350643deedaf333d0fa86ecdba6fcb415',
'http://downloads.xiph.org/releases/vorbis/libvorbis-1.3.7.tar.xz',
'b33cc4934322bcbf6efcbacf49e3ca01aadbea4114ec9589d1b1e9d20f72954b',
'lib/libvorbis.a',
[
'--disable-shared', '--enable-static',
@@ -111,9 +112,44 @@ liblame = AutotoolsProject(
],
)
libmodplug = AutotoolsProject(
'https://downloads.sourceforge.net/modplug-xmms/libmodplug/0.8.9.0/libmodplug-0.8.9.0.tar.gz',
'457ca5a6c179656d66c01505c0d95fafaead4329b9dbaa0f997d00a3508ad9de',
'lib/libmodplug.a',
[
'--disable-shared', '--enable-static',
],
)
wildmidi = CmakeProject(
'https://codeload.github.com/Mindwerks/wildmidi/tar.gz/wildmidi-0.4.3',
'498e5a96455bb4b91b37188ad6dcb070824e92c44f5ed452b90adbaec8eef3c5',
'lib/libWildMidi.a',
[
'-DBUILD_SHARED_LIBS=OFF',
'-DWANT_PLAYER=OFF',
'-DWANT_STATIC=ON',
],
base='wildmidi-wildmidi-0.4.3',
name='wildmidi',
version='0.4.3',
)
gme = CmakeProject(
'https://bitbucket.org/mpyne/game-music-emu/downloads/game-music-emu-0.6.3.tar.xz',
'aba34e53ef0ec6a34b58b84e28bf8cfbccee6585cebca25333604c35db3e051d',
'lib/libgme.a',
[
'-DBUILD_SHARED_LIBS=OFF',
'-DENABLE_UBSAN=OFF',
'-DZLIB_INCLUDE_DIR=OFF',
'-DSDL2_DIR=OFF',
],
)
ffmpeg = FfmpegProject(
'http://ffmpeg.org/releases/ffmpeg-4.2.2.tar.xz',
'cb754255ab0ee2ea5f66f8850e1bd6ad5cac1cd855d0a2f4990fb8c668b0d29c',
'http://ffmpeg.org/releases/ffmpeg-4.3.1.tar.xz',
'ad009240d46e307b4e03a213a0f49c11b650e445b1f8be0dda2a9212b34d2ffb',
'lib/libavcodec.a',
[
'--disable-shared', '--enable-static',
@@ -341,8 +377,8 @@ ffmpeg = FfmpegProject(
)
curl = AutotoolsProject(
'http://curl.haxx.se/download/curl-7.68.0.tar.xz',
'b724240722276a27f6e770b952121a3afd097129d8c9fe18e6272dc34192035a',
'http://curl.haxx.se/download/curl-7.72.0.tar.xz',
'0ded0808c4d85f2ee0db86980ae610cc9d165e9ca9da466196cc73c346513713',
'lib/libcurl.a',
[
'--disable-shared', '--enable-static',
@@ -397,7 +433,7 @@ libnfs = AutotoolsProject(
)
boost = BoostProject(
'https://dl.bintray.com/boostorg/release/1.72.0/source/boost_1_72_0.tar.bz2',
'59c9b274bc451cf91a9ba1dd2c7fdcaf5d60b1b3aa83f2c9fa143417cc660722',
'https://dl.bintray.com/boostorg/release/1.74.0/source/boost_1_74_0.tar.bz2',
'83bfc1507731a0906e387fc28b7ef5417d591429e51e788417fe9ff025e116b1',
'include/boost/version.hpp',
)

@@ -91,7 +91,12 @@ def configure(toolchain, src, build, args=()):
'--cross-file', cross_file,
] + args
subprocess.check_call(configure, env=toolchain.env)
env = toolchain.env.copy()
# Meson 0.54 requires the BOOST_ROOT environment variable
env['BOOST_ROOT'] = toolchain.install_prefix
subprocess.check_call(configure, env=env)
class MesonProject(Project):
def __init__(self, url, md5, installed, configure_args=[],

@@ -20,7 +20,7 @@
#ifndef MPD_AUDIO_FORMAT_HXX
#define MPD_AUDIO_FORMAT_HXX
#include "pcm/SampleFormat.hxx"
#include "pcm/SampleFormat.hxx" // IWYU pragma: export
#include "util/Compiler.h"
#include <chrono>

@@ -33,11 +33,11 @@
#include "playlist/PlaylistRegistry.hxx"
#include "playlist/PlaylistPlugin.hxx"
#include "fs/AllocatedPath.hxx"
#include "fs/NarrowPath.hxx"
#include "fs/Traits.hxx"
#include "fs/FileSystem.hxx"
#include "fs/StandardDirectory.hxx"
#include "system/Error.hxx"
#include "util/Macros.hxx"
#include "util/RuntimeError.hxx"
#include "util/Domain.hxx"
#include "util/OptionDef.hxx"
@@ -380,17 +380,7 @@ ParseCommandLine(int argc, char **argv, struct options &options,
if (config_file != nullptr) {
/* use specified configuration file */
#ifdef _UNICODE
wchar_t buffer[MAX_PATH];
auto result = MultiByteToWideChar(CP_ACP, 0, config_file, -1,
buffer, ARRAY_SIZE(buffer));
if (result <= 0)
throw MakeLastError("MultiByteToWideChar() failed");
ReadConfigFile(config, Path::FromFS(buffer));
#else
ReadConfigFile(config, Path::FromFS(config_file));
#endif
ReadConfigFile(config, FromNarrowPath(config_file));
return;
}

@@ -1,5 +1,5 @@
/*
* Copyright 2003-2018 The Music Player Daemon Project
* Copyright 2003-2019 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -19,8 +19,7 @@
#include "LogV.hxx"
#include "util/Domain.hxx"
#include <exception>
#include "util/Exception.hxx"
#include <stdio.h>
#include <string.h>
@@ -29,20 +28,20 @@
static constexpr Domain exception_domain("exception");
void
LogFormatV(const Domain &domain, LogLevel level,
LogFormatV(LogLevel level, const Domain &domain,
const char *fmt, va_list ap) noexcept
{
char msg[1024];
vsnprintf(msg, sizeof(msg), fmt, ap);
Log(domain, level, msg);
Log(level, domain, msg);
}
void
LogFormat(const Domain &domain, LogLevel level, const char *fmt, ...) noexcept
LogFormat(LogLevel level, const Domain &domain, const char *fmt, ...) noexcept
{
va_list ap;
va_start(ap, fmt);
LogFormatV(domain, level, fmt, ap);
LogFormatV(level, domain, fmt, ap);
va_end(ap);
}
@@ -51,7 +50,7 @@ FormatDebug(const Domain &domain, const char *fmt, ...) noexcept
{
va_list ap;
va_start(ap, fmt);
LogFormatV(domain, LogLevel::DEBUG, fmt, ap);
LogFormatV(LogLevel::DEBUG, domain, fmt, ap);
va_end(ap);
}
@@ -60,7 +59,7 @@ FormatInfo(const Domain &domain, const char *fmt, ...) noexcept
{
va_list ap;
va_start(ap, fmt);
LogFormatV(domain, LogLevel::INFO, fmt, ap);
LogFormatV(LogLevel::INFO, domain, fmt, ap);
va_end(ap);
}
@@ -69,7 +68,7 @@ FormatDefault(const Domain &domain, const char *fmt, ...) noexcept
{
va_list ap;
va_start(ap, fmt);
LogFormatV(domain, LogLevel::DEFAULT, fmt, ap);
LogFormatV(LogLevel::DEFAULT, domain, fmt, ap);
va_end(ap);
}
@@ -78,7 +77,7 @@ FormatWarning(const Domain &domain, const char *fmt, ...) noexcept
{
va_list ap;
va_start(ap, fmt);
LogFormatV(domain, LogLevel::WARNING, fmt, ap);
LogFormatV(LogLevel::WARNING, domain, fmt, ap);
va_end(ap);
}
@@ -87,42 +86,24 @@ FormatError(const Domain &domain, const char *fmt, ...) noexcept
{
va_list ap;
va_start(ap, fmt);
LogFormatV(domain, LogLevel::ERROR, fmt, ap);
LogFormatV(LogLevel::ERROR, domain, fmt, ap);
va_end(ap);
}
void
LogError(const std::exception &e) noexcept
Log(LogLevel level, const std::exception &e) noexcept
{
Log(exception_domain, LogLevel::ERROR, e.what());
try {
std::rethrow_if_nested(e);
} catch (const std::exception &nested) {
LogError(nested, "nested");
} catch (...) {
Log(exception_domain, LogLevel::ERROR,
"Unrecognized nested exception");
}
Log(level, exception_domain, GetFullMessage(e).c_str());
}
void
LogError(const std::exception &e, const char *msg) noexcept
Log(LogLevel level, const std::exception &e, const char *msg) noexcept
{
FormatError(exception_domain, "%s: %s", msg, e.what());
try {
std::rethrow_if_nested(e);
} catch (const std::exception &nested) {
LogError(nested);
} catch (...) {
Log(exception_domain, LogLevel::ERROR,
"Unrecognized nested exception");
}
LogFormat(level, exception_domain, "%s: %s", msg, GetFullMessage(e).c_str());
}
void
FormatError(const std::exception &e, const char *fmt, ...) noexcept
LogFormat(LogLevel level, const std::exception &e, const char *fmt, ...) noexcept
{
char msg[1024];
va_list ap;
@@ -130,37 +111,24 @@ FormatError(const std::exception &e, const char *fmt, ...) noexcept
vsnprintf(msg, sizeof(msg), fmt, ap);
va_end(ap);
LogError(e, msg);
Log(level, e, msg);
}
void
LogError(const std::exception_ptr &ep) noexcept
Log(LogLevel level, const std::exception_ptr &ep) noexcept
{
try {
std::rethrow_exception(ep);
} catch (const std::exception &e) {
LogError(e);
} catch (...) {
Log(exception_domain, LogLevel::ERROR,
"Unrecognized exception");
}
Log(level, exception_domain, GetFullMessage(ep).c_str());
}
void
LogError(const std::exception_ptr &ep, const char *msg) noexcept
Log(LogLevel level, const std::exception_ptr &ep, const char *msg) noexcept
{
try {
std::rethrow_exception(ep);
} catch (const std::exception &e) {
LogError(e, msg);
} catch (...) {
FormatError(exception_domain,
"%s: Unrecognized exception", msg);
}
LogFormat(level, exception_domain, "%s: %s", msg,
GetFullMessage(ep).c_str());
}
void
FormatError(const std::exception_ptr &ep, const char *fmt, ...) noexcept
LogFormat(LogLevel level, const std::exception_ptr &ep, const char *fmt, ...) noexcept
{
char msg[1024];
va_list ap;
@@ -168,13 +136,13 @@ FormatError(const std::exception_ptr &ep, const char *fmt, ...) noexcept
vsnprintf(msg, sizeof(msg), fmt, ap);
va_end(ap);
LogError(ep, msg);
Log(level, ep, msg);
}
void
LogErrno(const Domain &domain, int e, const char *msg) noexcept
{
LogFormat(domain, LogLevel::ERROR, "%s: %s", msg, strerror(e));
LogFormat(LogLevel::ERROR, domain, "%s: %s", msg, strerror(e));
}
void

@@ -1,5 +1,5 @@
/*
* Copyright 2003-2018 The Music Player Daemon Project
* Copyright 2003-2019 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -28,16 +28,38 @@
class Domain;
void
Log(const Domain &domain, LogLevel level, const char *msg) noexcept;
Log(LogLevel level, const Domain &domain, const char *msg) noexcept;
gcc_printf(3,4)
void
LogFormat(const Domain &domain, LogLevel level, const char *fmt, ...) noexcept;
LogFormat(LogLevel level, const Domain &domain, const char *fmt, ...) noexcept;
void
Log(LogLevel level, const std::exception &e) noexcept;
void
Log(LogLevel level, const std::exception &e, const char *msg) noexcept;
gcc_printf(3,4)
void
LogFormat(LogLevel level, const std::exception &e,
const char *fmt, ...) noexcept;
void
Log(LogLevel level, const std::exception_ptr &ep) noexcept;
void
Log(LogLevel level, const std::exception_ptr &ep, const char *msg) noexcept;
gcc_printf(3,4)
void
LogFormat(LogLevel level, const std::exception_ptr &ep,
const char *fmt, ...) noexcept;
static inline void
LogDebug(const Domain &domain, const char *msg) noexcept
{
Log(domain, LogLevel::DEBUG, msg);
Log(LogLevel::DEBUG, domain, msg);
}
gcc_printf(2,3)
@@ -47,7 +69,7 @@ FormatDebug(const Domain &domain, const char *fmt, ...) noexcept;
static inline void
LogInfo(const Domain &domain, const char *msg) noexcept
{
Log(domain, LogLevel::INFO, msg);
Log(LogLevel::INFO, domain, msg);
}
gcc_printf(2,3)
@@ -57,7 +79,7 @@ FormatInfo(const Domain &domain, const char *fmt, ...) noexcept;
static inline void
LogDefault(const Domain &domain, const char *msg) noexcept
{
Log(domain, LogLevel::DEFAULT, msg);
Log(LogLevel::DEFAULT, domain, msg);
}
gcc_printf(2,3)
@@ -67,7 +89,7 @@ FormatDefault(const Domain &domain, const char *fmt, ...) noexcept;
static inline void
LogWarning(const Domain &domain, const char *msg) noexcept
{
Log(domain, LogLevel::WARNING, msg);
Log(LogLevel::WARNING, domain, msg);
}
gcc_printf(2,3)
@@ -77,28 +99,47 @@ FormatWarning(const Domain &domain, const char *fmt, ...) noexcept;
static inline void
LogError(const Domain &domain, const char *msg) noexcept
{
Log(domain, LogLevel::ERROR, msg);
Log(LogLevel::ERROR, domain, msg);
}
void
LogError(const std::exception &e) noexcept;
inline void
LogError(const std::exception &e) noexcept
{
Log(LogLevel::ERROR, e);
}
void
LogError(const std::exception &e, const char *msg) noexcept;
inline void
LogError(const std::exception &e, const char *msg) noexcept
{
Log(LogLevel::ERROR, e, msg);
}
gcc_printf(2,3)
void
FormatError(const std::exception &e, const char *fmt, ...) noexcept;
template<typename... Args>
inline void
FormatError(const std::exception &e, const char *fmt, Args&&... args) noexcept
{
LogFormat(LogLevel::ERROR, e, fmt, std::forward<Args>(args)...);
}
void
LogError(const std::exception_ptr &ep) noexcept;
inline void
LogError(const std::exception_ptr &ep) noexcept
{
Log(LogLevel::ERROR, ep);
}
void
LogError(const std::exception_ptr &ep, const char *msg) noexcept;
inline void
LogError(const std::exception_ptr &ep, const char *msg) noexcept
{
Log(LogLevel::ERROR, ep, msg);
}
gcc_printf(2,3)
void
FormatError(const std::exception_ptr &ep, const char *fmt, ...) noexcept;
template<typename... Args>
inline void
FormatError(const std::exception_ptr &ep,
const char *fmt, Args&&... args) noexcept
{
LogFormat(LogLevel::ERROR, ep, fmt, std::forward<Args>(args)...);
}
gcc_printf(2,3)
void

@@ -1,5 +1,5 @@
/*
* Copyright 2003-2018 The Music Player Daemon Project
* Copyright 2003-2019 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -61,7 +61,7 @@ ToAndroidLogLevel(LogLevel log_level) noexcept
#else
static LogLevel log_threshold = LogLevel::INFO;
static LogLevel log_threshold = LogLevel::DEFAULT;
static bool enable_timestamp;
@@ -176,7 +176,7 @@ FileLog(const Domain &domain, const char *message) noexcept
#endif /* !ANDROID */
void
Log(const Domain &domain, LogLevel level, const char *msg) noexcept
Log(LogLevel level, const Domain &domain, const char *msg) noexcept
{
#ifdef ANDROID
__android_log_print(ToAndroidLogLevel(level), "MPD",

@@ -1,5 +1,5 @@
/*
* Copyright 2003-2018 The Music Player Daemon Project
* Copyright 2003-2019 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify

@@ -1,5 +1,5 @@
/*
* Copyright 2003-2018 The Music Player Daemon Project
* Copyright 2003-2019 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -25,7 +25,7 @@
#include <stdarg.h>
void
LogFormatV(const Domain &domain, LogLevel level,
LogFormatV(LogLevel level, const Domain &domain,
const char *fmt, va_list ap) noexcept;
#endif /* LOG_H */

@@ -460,7 +460,7 @@ MainOrThrow(int argc, char *argv[])
#endif
const unsigned max_clients =
raw_config.GetPositive(ConfigOption::MAX_CONN, 10);
raw_config.GetPositive(ConfigOption::MAX_CONN, 100);
instance->client_list = new ClientList(max_clients);
initialize_decoder_and_player(raw_config, config.replay_gain);

@@ -101,7 +101,7 @@ initPermissions(const ConfigData &config)
const char *separator = strchr(param.value.c_str(),
PERMISSION_PASSWORD_CHAR);
if (separator == NULL)
if (separator == nullptr)
throw FormatRuntimeError("\"%c\" not found in password string "
"\"%s\", line %i",
PERMISSION_PASSWORD_CHAR,

@@ -1,5 +1,5 @@
/*
* Copyright 2003-2018 The Music Player Daemon Project
* Copyright 2003-2020 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -27,10 +27,20 @@
* that this plugin is unavailable. It will be disabled, and MPD can
* continue initialization.
*/
class PluginUnavailable final : public std::runtime_error {
class PluginUnavailable : public std::runtime_error {
public:
explicit PluginUnavailable(const char *msg)
:std::runtime_error(msg) {}
using std::runtime_error::runtime_error;
};
/**
* Like #PluginUnavailable, but denotes that the plugin is not
* available because it was not explicitly enabled in the
* configuration. The message may describe the necessary steps to
* enable it.
*/
class PluginUnconfigured : public PluginUnavailable {
public:
using PluginUnavailable::PluginUnavailable;
};
#endif

@@ -23,9 +23,9 @@
#include "config/Data.hxx"
#include "util/RuntimeError.hxx"
#include <assert.h>
#include <stdlib.h>
#include <math.h>
#include <cassert>
#include <cstdlib>
#include <cmath>
static float
ParsePreamp(const char *s)
@@ -33,14 +33,14 @@ ParsePreamp(const char *s)
assert(s != nullptr);
char *endptr;
float f = strtod(s, &endptr);
float f = std::strtof(s, &endptr);
if (endptr == s || *endptr != '\0')
throw std::invalid_argument("Not a numeric value");
if (f < -15 || f > 15)
if (f < -15.0f || f > 15.0f)
throw std::invalid_argument("Number must be between -15 and 15");
return pow(10, f / 20.0);
return std::pow(10.0f, f / 20.0f);
}
static float

@@ -20,7 +20,7 @@
#include "ReplayGainInfo.hxx"
#include "ReplayGainConfig.hxx"
#include <math.h>
#include <cmath>
float
ReplayGainTuple::CalculateScale(const ReplayGainConfig &config) const noexcept
@@ -28,13 +28,13 @@ ReplayGainTuple::CalculateScale(const ReplayGainConfig &config) const noexcept
float scale;
if (IsDefined()) {
scale = pow(10.0, gain / 20.0);
scale = std::pow(10.0f, gain / 20.0f);
scale *= config.preamp;
if (scale > 15.0)
scale = 15.0;
if (scale > 15.0f)
scale = 15.0f;
if (config.limit && scale * peak > 1.0)
scale = 1.0 / peak;
if (config.limit && scale * peak > 1.0f)
scale = 1.0f / peak;
} else
scale = config.missing_preamp;

@@ -79,17 +79,22 @@ Song::UpdateFile(Storage &storage) noexcept
TagBuilder tag_builder;
auto new_audio_format = AudioFormat::Undefined();
const auto path_fs = storage.MapFS(relative_uri.c_str());
if (path_fs.IsNull()) {
const auto absolute_uri =
storage.MapUTF8(relative_uri.c_str());
if (!tag_stream_scan(absolute_uri.c_str(), tag_builder,
&new_audio_format))
return false;
} else {
if (!ScanFileTagsWithGeneric(path_fs, tag_builder,
try {
const auto path_fs = storage.MapFS(relative_uri.c_str());
if (path_fs.IsNull()) {
const auto absolute_uri =
storage.MapUTF8(relative_uri.c_str());
if (!tag_stream_scan(absolute_uri.c_str(), tag_builder,
&new_audio_format))
return false;
return false;
} else {
if (!ScanFileTagsWithGeneric(path_fs, tag_builder,
&new_audio_format))
return false;
}
} catch (...) {
// TODO: log or propagate I/O errors?
return false;
}
mtime = info.mtime;
@@ -153,8 +158,14 @@ DetachedSong::LoadFile(Path path) noexcept
return false;
TagBuilder tag_builder;
if (!ScanFileTagsWithGeneric(path, tag_builder))
try {
if (!ScanFileTagsWithGeneric(path, tag_builder))
return false;
} catch (...) {
// TODO: log or propagate I/O errors?
return false;
}
mtime = fi.GetModificationTime();
tag_builder.Commit(tag);
@@ -173,8 +184,14 @@ DetachedSong::Update() noexcept
return LoadFile(path_fs);
} else if (IsRemote()) {
TagBuilder tag_builder;
if (!tag_stream_scan(uri.c_str(), tag_builder))
try {
if (!tag_stream_scan(uri.c_str(), tag_builder))
return false;
} catch (...) {
// TODO: log or propagate I/O errors?
return false;
}
mtime = std::chrono::system_clock::time_point::min();
tag_builder.Commit(tag);

@@ -29,9 +29,9 @@
#include "system/Clock.hxx"
#include "Log.hxx"
#include "time/ChronoUtil.hxx"
#include "util/Math.hxx"
#include <chrono>
#include <cmath>
#ifndef _WIN32
/**
@@ -121,7 +121,7 @@ stats_print(Response &r, const Partition &partition)
#else
(unsigned)std::chrono::duration_cast<std::chrono::seconds>(std::chrono::steady_clock::now() - start_time).count(),
#endif
std::lround(partition.pc.GetTotalPlayTime().count()));
lround(partition.pc.GetTotalPlayTime().count()));
#ifdef ENABLE_DATABASE
const Database *db = partition.instance.GetDatabase();

@@ -47,11 +47,11 @@ public:
handler(_handler),
is(nullptr) {}
bool ScanFile(const DecoderPlugin &plugin) noexcept {
bool ScanFile(const DecoderPlugin &plugin) {
return plugin.ScanFile(path_fs, handler);
}
bool ScanStream(const DecoderPlugin &plugin) noexcept {
bool ScanStream(const DecoderPlugin &plugin) {
if (plugin.scan_stream == nullptr)
return false;
@@ -73,14 +73,14 @@ public:
return plugin.ScanStream(*is, handler);
}
bool Scan(const DecoderPlugin &plugin) noexcept {
bool Scan(const DecoderPlugin &plugin) {
return plugin.SupportsSuffix(suffix) &&
(ScanFile(plugin) || ScanStream(plugin));
}
};
bool
ScanFileTagsNoGeneric(Path path_fs, TagHandler &handler) noexcept
ScanFileTagsNoGeneric(Path path_fs, TagHandler &handler)
{
assert(!path_fs.IsNull());
@@ -100,7 +100,7 @@ ScanFileTagsNoGeneric(Path path_fs, TagHandler &handler) noexcept
bool
ScanFileTagsWithGeneric(Path path, TagBuilder &builder,
AudioFormat *audio_format) noexcept
AudioFormat *audio_format)
{
FullTagHandler h(builder, audio_format);

@@ -30,22 +30,26 @@ class TagBuilder;
* but does not fall back to generic scanners (APE and ID3) if no tags
* were found (but the file was recognized).
*
* Throws on I/O error.
*
* @return true if the file was recognized (even if no metadata was
* found)
*/
bool
ScanFileTagsNoGeneric(Path path, TagHandler &handler) noexcept;
ScanFileTagsNoGeneric(Path path, TagHandler &handler);
/**
* Scan the tags of a song file. Invokes matching decoder plugins,
* and falls back to generic scanners (APE and ID3) if no tags were
* found (but the file was recognized).
*
* Throws on I/O error.
*
* @return true if the file was recognized (even if no metadata was
* found)
*/
bool
ScanFileTagsWithGeneric(Path path, TagBuilder &builder,
AudioFormat *audio_format=nullptr) noexcept;
AudioFormat *audio_format=nullptr);
#endif

@@ -45,7 +45,7 @@ CheckDecoderPlugin(const DecoderPlugin &plugin,
}
bool
tag_stream_scan(InputStream &is, TagHandler &handler) noexcept
tag_stream_scan(InputStream &is, TagHandler &handler)
{
assert(is.IsReady());
@@ -73,19 +73,17 @@ tag_stream_scan(InputStream &is, TagHandler &handler) noexcept
}
bool
tag_stream_scan(const char *uri, TagHandler &handler) noexcept
try {
tag_stream_scan(const char *uri, TagHandler &handler)
{
Mutex mutex;
auto is = InputStream::OpenReady(uri, mutex);
return tag_stream_scan(*is, handler);
} catch (const std::exception &e) {
return false;
}
bool
tag_stream_scan(InputStream &is, TagBuilder &builder,
AudioFormat *audio_format) noexcept
AudioFormat *audio_format)
{
assert(is.IsReady());
@@ -102,12 +100,10 @@ tag_stream_scan(InputStream &is, TagBuilder &builder,
bool
tag_stream_scan(const char *uri, TagBuilder &builder,
AudioFormat *audio_format) noexcept
try {
AudioFormat *audio_format)
{
Mutex mutex;
auto is = InputStream::OpenReady(uri, mutex);
return tag_stream_scan(*is, builder, audio_format);
} catch (const std::exception &e) {
return false;
}

@@ -29,29 +29,39 @@ class TagBuilder;
* Scan the tags of an #InputStream. Invokes matching decoder
* plugins, but does not invoke the special "APE" and "ID3" scanners.
*
* Throws on I/O error.
*
* @return true if the file was recognized (even if no metadata was
* found)
*/
bool
tag_stream_scan(InputStream &is, TagHandler &handler) noexcept;
tag_stream_scan(InputStream &is, TagHandler &handler);
/**
* Throws on I/O error.
*/
bool
tag_stream_scan(const char *uri, TagHandler &handler) noexcept;
tag_stream_scan(const char *uri, TagHandler &handler);
/**
* Scan the tags of an #InputStream. Invokes matching decoder
* plugins, and falls back to generic scanners (APE and ID3) if no
* tags were found (but the file was recognized).
*
* Throws on I/O error.
*
* @return true if the file was recognized (even if no metadata was
* found)
*/
bool
tag_stream_scan(InputStream &is, TagBuilder &builder,
AudioFormat *audio_format=nullptr) noexcept;
AudioFormat *audio_format=nullptr);
/**
* Throws on I/O error.
*/
bool
tag_stream_scan(const char *uri, TagBuilder &builder,
AudioFormat *audio_format=nullptr) noexcept;
AudioFormat *audio_format=nullptr);
#endif

@@ -0,0 +1,56 @@
/*
* Copyright 2003-2020 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "AudioManager.hxx"
#include "java/Class.hxx"
#include "java/Exception.hxx"
#include "java/File.hxx"
#define STREAM_MUSIC 3
AudioManager::AudioManager(JNIEnv *env, jobject obj) noexcept
: Java::GlobalObject(env, obj)
{
Java::Class cls(env, env->GetObjectClass(Get()));
jmethodID method = env->GetMethodID(cls, "getStreamMaxVolume", "(I)I");
assert(method);
maxVolume = env->CallIntMethod(Get(), method, STREAM_MUSIC);
getStreamVolumeMethod = env->GetMethodID(cls, "getStreamVolume", "(I)I");
assert(getStreamVolumeMethod);
setStreamVolumeMethod = env->GetMethodID(cls, "setStreamVolume", "(III)V");
assert(setStreamVolumeMethod);
}
int
AudioManager::GetVolume(JNIEnv *env)
{
if (maxVolume == 0)
return 0;
return env->CallIntMethod(Get(), getStreamVolumeMethod, STREAM_MUSIC);
}
void
AudioManager::SetVolume(JNIEnv *env, int volume)
{
if (maxVolume == 0)
return;
env->CallVoidMethod(Get(), setStreamVolumeMethod, STREAM_MUSIC, volume, 0);
}

@@ -0,0 +1,42 @@
/*
* Copyright 2003-2020 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef MPD_ANDROID_AUDIO_MANAGER_HXX
#define MPD_ANDROID_AUDIO_MANAGER_HXX
#include "java/Object.hxx"
class AudioManager : public Java::GlobalObject {
int maxVolume;
jmethodID getStreamVolumeMethod;
jmethodID setStreamVolumeMethod;
public:
AudioManager(JNIEnv *env, jobject obj) noexcept;
AudioManager(std::nullptr_t) noexcept { maxVolume = 0; }
~AudioManager() noexcept {}
int GetMaxVolume() { return maxVolume; }
int GetVolume(JNIEnv *env);
void SetVolume(JNIEnv *env, int);
};
#endif

@@ -1,5 +1,5 @@
/*
* Copyright 2003-2018 The Music Player Daemon Project
* Copyright 2003-2019 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -20,10 +20,13 @@
#include "Context.hxx"
#include "java/Class.hxx"
#include "java/File.hxx"
#include "java/String.hxx"
#include "fs/AllocatedPath.hxx"
#include "AudioManager.hxx"
AllocatedPath
Context::GetCacheDir(JNIEnv *env) const
Context::GetCacheDir(JNIEnv *env) const noexcept
{
assert(env != nullptr);
@@ -40,3 +43,21 @@ Context::GetCacheDir(JNIEnv *env) const
return Java::File::ToAbsolutePath(env, file);
}
AudioManager *
Context::GetAudioManager(JNIEnv *env) noexcept
{
assert(env != nullptr);
Java::Class cls(env, env->GetObjectClass(Get()));
jmethodID method = env->GetMethodID(cls, "getSystemService",
"(Ljava/lang/String;)Ljava/lang/Object;");
assert(method);
Java::String name(env, "audio");
jobject am = env->CallObjectMethod(Get(), method, name.Get());
if (Java::DiscardException(env) || am == nullptr)
return nullptr;
return new AudioManager(env, am);
}

@@ -1,5 +1,5 @@
/*
* Copyright 2003-2018 The Music Player Daemon Project
* Copyright 2003-2019 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -23,13 +23,18 @@
#include "java/Object.hxx"
class AllocatedPath;
class AudioManager;
class Context : public Java::Object {
class Context : public Java::GlobalObject {
public:
Context(JNIEnv *env, jobject obj):Java::Object(env, obj) {}
Context(JNIEnv *env, jobject obj) noexcept
:Java::GlobalObject(env, obj) {}
gcc_pure
AllocatedPath GetCacheDir(JNIEnv *env) const;
AllocatedPath GetCacheDir(JNIEnv *env) const noexcept;
gcc_pure
AudioManager *GetAudioManager(JNIEnv *env) noexcept;
};
#endif

@@ -22,9 +22,9 @@
#include "java/Object.hxx"
class LogListener : public Java::Object {
class LogListener : public Java::GlobalObject {
public:
LogListener(JNIEnv *env, jobject obj):Java::Object(env, obj) {}
LogListener(JNIEnv *env, jobject obj):Java::GlobalObject(env, obj) {}
void OnLog(JNIEnv *env, int priority, const char *fmt, ...) const;
};

40
src/apple/AudioObject.cxx Normal file

@@ -0,0 +1,40 @@
/*
* Copyright 2020 Max Kellermann <max.kellermann@gmail.com>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the
* distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
* OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "AudioObject.hxx"
#include "StringRef.hxx"
Apple::StringRef
AudioObjectGetStringProperty(AudioObjectID inObjectID,
const AudioObjectPropertyAddress &inAddress)
{
auto s = AudioObjectGetPropertyDataT<CFStringRef>(inObjectID,
inAddress);
return Apple::StringRef(s);
}

105
src/apple/AudioObject.hxx Normal file

@@ -0,0 +1,105 @@
/*
* Copyright 2020 Max Kellermann <max.kellermann@gmail.com>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the
* distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
* OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef APPLE_AUDIO_OBJECT_HXX
#define APPLE_AUDIO_OBJECT_HXX
#include "Throw.hxx"
#include "util/AllocatedArray.hxx"
#include <CoreAudio/AudioHardware.h>
#include <cstddef>
namespace Apple {
class StringRef;
}
inline std::size_t
AudioObjectGetPropertyDataSize(AudioObjectID inObjectID,
const AudioObjectPropertyAddress &inAddress)
{
UInt32 size;
OSStatus status = AudioObjectGetPropertyDataSize(inObjectID,
&inAddress,
0, nullptr, &size);
if (status != noErr)
Apple::ThrowOSStatus(status);
return size;
}
template<typename T>
T
AudioObjectGetPropertyDataT(AudioObjectID inObjectID,
const AudioObjectPropertyAddress &inAddress)
{
OSStatus status;
UInt32 size = sizeof(T);
T value;
status = AudioObjectGetPropertyData(inObjectID, &inAddress,
0, nullptr,
&size, &value);
if (status != noErr)
Apple::ThrowOSStatus(status);
return value;
}
Apple::StringRef
AudioObjectGetStringProperty(AudioObjectID inObjectID,
const AudioObjectPropertyAddress &inAddress);
template<typename T>
AllocatedArray<T>
AudioObjectGetPropertyDataArray(AudioObjectID inObjectID,
const AudioObjectPropertyAddress &inAddress)
{
OSStatus status;
UInt32 size;
status = AudioObjectGetPropertyDataSize(inObjectID,
&inAddress,
0, nullptr, &size);
if (status != noErr)
Apple::ThrowOSStatus(status);
AllocatedArray<T> result(size / sizeof(T));
status = AudioObjectGetPropertyData(inObjectID, &inAddress,
0, nullptr,
&size, result.begin());
if (status != noErr)
Apple::ThrowOSStatus(status);
return result;
}
#endif

111
src/apple/AudioUnit.hxx Normal file

@@ -0,0 +1,111 @@
/*
* Copyright 2020 Max Kellermann <max.kellermann@gmail.com>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the
* distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
* OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef APPLE_AUDIO_UNIT_HXX
#define APPLE_AUDIO_UNIT_HXX
#include "Throw.hxx"
#include <AudioUnit/AudioUnit.h>
template<typename T>
T
AudioUnitGetPropertyT(AudioUnit inUnit, AudioUnitPropertyID inID,
AudioUnitScope inScope,
AudioUnitElement inElement)
{
UInt32 size = sizeof(T);
T value;
OSStatus status = AudioUnitGetProperty(inUnit, inID, inScope,
inElement,
&value, &size);
if (status != noErr)
Apple::ThrowOSStatus(status);
return value;
}
template<typename T>
void
AudioUnitSetPropertyT(AudioUnit inUnit, AudioUnitPropertyID inID,
AudioUnitScope inScope,
AudioUnitElement inElement,
const T &value)
{
OSStatus status = AudioUnitSetProperty(inUnit, inID, inScope,
inElement,
&value, sizeof(value));
if (status != noErr)
Apple::ThrowOSStatus(status);
}
inline void
AudioUnitSetCurrentDevice(AudioUnit inUnit, const AudioDeviceID &value)
{
AudioUnitSetPropertyT(inUnit, kAudioOutputUnitProperty_CurrentDevice,
kAudioUnitScope_Global, 0,
value);
}
inline void
AudioUnitSetInputStreamFormat(AudioUnit inUnit,
const AudioStreamBasicDescription &value)
{
AudioUnitSetPropertyT(inUnit, kAudioUnitProperty_StreamFormat,
kAudioUnitScope_Input, 0,
value);
}
inline void
AudioUnitSetInputRenderCallback(AudioUnit inUnit,
const AURenderCallbackStruct &value)
{
AudioUnitSetPropertyT(inUnit, kAudioUnitProperty_SetRenderCallback,
kAudioUnitScope_Input, 0,
value);
}
inline UInt32
AudioUnitGetBufferFrameSize(AudioUnit inUnit)
{
return AudioUnitGetPropertyT<UInt32>(inUnit,
kAudioDevicePropertyBufferFrameSize,
kAudioUnitScope_Global, 0);
}
inline void
AudioUnitSetBufferFrameSize(AudioUnit inUnit, const UInt32 &value)
{
AudioUnitSetPropertyT(inUnit, kAudioDevicePropertyBufferFrameSize,
kAudioUnitScope_Global, 0,
value);
}
#endif

75
src/apple/ErrorRef.hxx Normal file

@@ -0,0 +1,75 @@
/*
* Copyright 2020 Max Kellermann <max.kellermann@gmail.com>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the
* distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
* OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef APPLE_ERROR_REF_HXX
#define APPLE_ERROR_REF_HXX
#include <CoreFoundation/CFError.h>
#include <utility>
namespace Apple {
class ErrorRef {
CFErrorRef ref = nullptr;
public:
explicit ErrorRef(CFErrorRef _ref) noexcept
:ref(_ref) {}
ErrorRef(CFAllocatorRef allocator, CFErrorDomain domain,
CFIndex code, CFDictionaryRef userInfo) noexcept
:ref(CFErrorCreate(allocator, domain, code, userInfo)) {}
ErrorRef(ErrorRef &&src) noexcept
:ref(std::exchange(src.ref, nullptr)) {}
~ErrorRef() noexcept {
if (ref)
CFRelease(ref);
}
ErrorRef &operator=(ErrorRef &&src) noexcept {
using std::swap;
swap(ref, src.ref);
return *this;
}
operator bool() const noexcept {
return ref != nullptr;
}
CFStringRef CopyDescription() const noexcept {
return CFErrorCopyDescription(ref);
}
};
} // namespace Apple
#endif

73
src/apple/StringRef.hxx Normal file

@@ -0,0 +1,73 @@
/*
* Copyright 2020 Max Kellermann <max.kellermann@gmail.com>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the
* distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
* OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef APPLE_STRING_REF_HXX
#define APPLE_STRING_REF_HXX
#include <CoreFoundation/CFString.h>
#include <utility>
namespace Apple {
class StringRef {
CFStringRef ref = nullptr;
public:
explicit StringRef(CFStringRef _ref) noexcept
:ref(_ref) {}
StringRef(StringRef &&src) noexcept
:ref(std::exchange(src.ref, nullptr)) {}
~StringRef() noexcept {
if (ref)
CFRelease(ref);
}
StringRef &operator=(StringRef &&src) noexcept {
using std::swap;
swap(ref, src.ref);
return *this;
}
operator bool() const noexcept {
return ref != nullptr;
}
bool GetCString(char *buffer, std::size_t size,
CFStringEncoding encoding=kCFStringEncodingUTF8) const noexcept
{
return CFStringGetCString(ref, buffer, size, encoding);
}
};
} // namespace Apple
#endif

67
src/apple/Throw.cxx Normal file

@@ -0,0 +1,67 @@
/*
* Copyright 2020 Max Kellermann <max.kellermann@gmail.com>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the
* distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
* OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "Throw.hxx"
#include "ErrorRef.hxx"
#include "StringRef.hxx"
#include <stdexcept>
namespace Apple {
void
ThrowOSStatus(OSStatus status)
{
const Apple::ErrorRef cferr(nullptr, kCFErrorDomainOSStatus,
status, nullptr);
const Apple::StringRef cfstr(cferr.CopyDescription());
char msg[1024];
if (!cfstr.GetCString(msg, sizeof(msg)))
throw std::runtime_error("Unknown OSStatus");
throw std::runtime_error(msg);
}
void
ThrowOSStatus(OSStatus status, const char *_msg)
{
const Apple::ErrorRef cferr(nullptr, kCFErrorDomainOSStatus,
status, nullptr);
const Apple::StringRef cfstr(cferr.CopyDescription());
char msg[1024];
strcpy(msg, _msg);
size_t length = strlen(msg);
cfstr.GetCString(msg + length, sizeof(msg) - length);
throw std::runtime_error(msg);
}
} // namespace Apple

45
src/apple/Throw.hxx Normal file

@@ -0,0 +1,45 @@
/*
* Copyright 2020 Max Kellermann <max.kellermann@gmail.com>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the
* distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
* OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef APPLE_THROW_HXX
#define APPLE_THROW_HXX
#include <CoreFoundation/CFBase.h>
namespace Apple {
void
ThrowOSStatus(OSStatus status);
void
ThrowOSStatus(OSStatus status, const char *msg);
} // namespace Apple
#endif

28
src/apple/meson.build Normal file

@@ -0,0 +1,28 @@
if not is_darwin
apple_dep = dependency('', required: false)
subdir_done()
endif
audiounit_dep = declare_dependency(
link_args: ['-framework', 'AudioUnit', '-framework', 'CoreAudio', '-framework', 'CoreServices'],
dependencies: [
boost_dep,
],
)
apple = static_library(
'apple',
'AudioObject.cxx',
'Throw.cxx',
include_directories: inc,
dependencies: [
audiounit_dep,
],
)
apple_dep = declare_dependency(
link_with: apple,
dependencies: [
audiounit_dep,
],
)

@@ -58,9 +58,9 @@ public:
class Bzip2InputStream final : public InputStream {
std::shared_ptr<InputStream> input;
bool eof = false;
bz_stream bzstream{};
bz_stream bzstream;
bool eof = false;
char buffer[5000];
@@ -68,7 +68,7 @@ public:
Bzip2InputStream(const std::shared_ptr<InputStream> &_input,
const char *uri,
Mutex &mutex);
~Bzip2InputStream();
~Bzip2InputStream() noexcept override;
/* virtual methods from InputStream */
bool IsEOF() noexcept override;
@@ -79,25 +79,6 @@ private:
bool FillBuffer();
};
/* single archive handling allocation helpers */
inline void
Bzip2InputStream::Open()
{
bzstream.bzalloc = nullptr;
bzstream.bzfree = nullptr;
bzstream.opaque = nullptr;
bzstream.next_in = (char *)buffer;
bzstream.avail_in = 0;
int ret = BZ2_bzDecompressInit(&bzstream, 0, 0);
if (ret != BZ_OK)
throw std::runtime_error("BZ2_bzDecompressInit() has failed");
SetReady();
}
/* archive open && listing routine */
static std::unique_ptr<ArchiveFile>
@@ -116,10 +97,16 @@ Bzip2InputStream::Bzip2InputStream(const std::shared_ptr<InputStream> &_input,
:InputStream(_uri, _mutex),
input(_input)
{
Open();
bzstream.next_in = (char *)buffer;
int ret = BZ2_bzDecompressInit(&bzstream, 0, 0);
if (ret != BZ_OK)
throw std::runtime_error("BZ2_bzDecompressInit() has failed");
SetReady();
}
Bzip2InputStream::~Bzip2InputStream()
Bzip2InputStream::~Bzip2InputStream() noexcept
{
BZ2_bzDecompressEnd(&bzstream);
}
@@ -149,22 +136,18 @@ Bzip2InputStream::FillBuffer()
size_t
Bzip2InputStream::Read(void *ptr, size_t length)
{
const ScopeUnlock unlock(mutex);
int bz_result;
size_t nbytes = 0;
if (eof)
return 0;
const ScopeUnlock unlock(mutex);
bzstream.next_out = (char *)ptr;
bzstream.avail_out = length;
do {
if (!FillBuffer())
return 0;
const bool had_input = FillBuffer();
bz_result = BZ2_bzDecompress(&bzstream);
const int bz_result = BZ2_bzDecompress(&bzstream);
if (bz_result == BZ_STREAM_END) {
eof = true;
@@ -173,9 +156,12 @@ Bzip2InputStream::Read(void *ptr, size_t length)
if (bz_result != BZ_OK)
throw std::runtime_error("BZ2_bzDecompress() has failed");
if (!had_input && bzstream.avail_out == length)
throw std::runtime_error("Unexpected end of bzip2 file");
} while (bzstream.avail_out == length);
nbytes = length - bzstream.avail_out;
const size_t nbytes = length - bzstream.avail_out;
offset += nbytes;
return nbytes;

@@ -28,14 +28,16 @@
#include "input/InputStream.hxx"
#include "fs/Path.hxx"
#include "util/RuntimeError.hxx"
#include "util/StringCompare.hxx"
#include "util/WritableBuffer.hxx"
#include <cdio/iso9660.h>
#include <array>
#include <stdlib.h>
#include <string.h>
#define CEILING(x, y) ((x+(y-1))/y)
struct Iso9660 {
iso9660_t *const iso;
@@ -93,7 +95,10 @@ Iso9660ArchiveFile::Visit(char *path, size_t length, size_t capacity,
auto *statbuf = (iso9660_stat_t *)
_cdio_list_node_data(entnode);
const char *filename = statbuf->filename;
if (strcmp(filename, ".") == 0 || strcmp(filename, "..") == 0)
if (StringIsEmpty(filename) ||
PathTraitsUTF8::IsSpecialFilename(filename))
/* skip empty names (libcdio bug?) */
/* skip special names like "." and ".." */
continue;
size_t filename_length = strlen(filename);
@@ -138,26 +143,86 @@ Iso9660ArchiveFile::Visit(ArchiveVisitor &visitor)
class Iso9660InputStream final : public InputStream {
std::shared_ptr<Iso9660> iso;
iso9660_stat_t *statbuf;
const lsn_t lsn;
/**
* libiso9660 can only read whole sectors at a time, and this
* buffer is used to store one whole sector and allow Read()
* to handle partial sector reads.
*/
class BlockBuffer {
size_t position = 0, fill = 0;
std::array<uint8_t, ISO_BLOCKSIZE> data;
public:
ConstBuffer<uint8_t> Read() const noexcept {
assert(fill <= data.size());
assert(position <= fill);
return {&data[position], &data[fill]};
}
void Consume(size_t nbytes) noexcept {
assert(nbytes <= Read().size);
position += nbytes;
}
WritableBuffer<uint8_t> Write() noexcept {
assert(Read().empty());
return {data.data(), data.size()};
}
void Append(size_t nbytes) noexcept {
assert(Read().empty());
assert(nbytes <= data.size());
fill = nbytes;
position = 0;
}
void Clear() noexcept {
position = fill = 0;
}
};
BlockBuffer buffer;
/**
* Skip this number of bytes of the first sector after filling
* the buffer next time. This is used for seeking into the
* middle of a sector.
*/
size_t skip = 0;
public:
Iso9660InputStream(const std::shared_ptr<Iso9660> &_iso,
const char *_uri,
Mutex &_mutex,
iso9660_stat_t *_statbuf)
lsn_t _lsn, offset_type _size)
:InputStream(_uri, _mutex),
iso(_iso), statbuf(_statbuf) {
size = statbuf->size;
iso(_iso),
lsn(_lsn)
{
size = _size;
seekable = true;
SetReady();
}
~Iso9660InputStream() {
free(statbuf);
}
/* virtual methods from InputStream */
bool IsEOF() noexcept override;
size_t Read(void *ptr, size_t size) override;
void Seek(offset_type new_offset) override {
if (new_offset > size)
throw std::runtime_error("Invalid seek offset");
skip = new_offset % ISO_BLOCKSIZE;
offset = new_offset - skip;
buffer.Clear();
}
};
InputStreamPtr
@@ -169,42 +234,78 @@ Iso9660ArchiveFile::OpenStream(const char *pathname,
throw FormatRuntimeError("not found in the ISO file: %s",
pathname);
const lsn_t lsn = statbuf->lsn;
const offset_type size = statbuf->size;
free(statbuf);
return std::make_unique<Iso9660InputStream>(iso, pathname, mutex,
statbuf);
lsn, size);
}
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;
if (left_bytes < read_size) {
no_blocks = CEILING(left_bytes, ISO_BLOCKSIZE);
} else {
no_blocks = read_size / ISO_BLOCKSIZE;
}
if (no_blocks == 0)
const offset_type remaining = size - offset;
if (remaining == 0)
return 0;
cur_block = offset / ISO_BLOCKSIZE;
if (offset_type(read_size) > remaining)
read_size = remaining;
readed = iso->SeekRead(ptr, statbuf->lsn + cur_block, no_blocks);
auto r = buffer.Read();
if (readed != no_blocks * ISO_BLOCKSIZE)
throw FormatRuntimeError("error reading ISO file at lsn %lu",
(unsigned long)cur_block);
if (r.empty()) {
/* the buffer is empty - read more data from the ISO file */
if (left_bytes < read_size) {
readed = left_bytes;
assert(offset % ISO_BLOCKSIZE == 0);
const ScopeUnlock unlock(mutex);
const lsn_t read_lsn = lsn + offset / ISO_BLOCKSIZE;
if (read_size >= ISO_BLOCKSIZE) {
/* big read - read right into the caller's buffer */
auto nbytes = iso->SeekRead(ptr, read_lsn,
read_size / ISO_BLOCKSIZE);
if (nbytes <= 0)
throw std::runtime_error("Failed to read ISO9660 file");
offset += nbytes;
return nbytes;
}
/* fill the buffer */
auto w = buffer.Write();
auto nbytes = iso->SeekRead(w.data, read_lsn,
w.size / ISO_BLOCKSIZE);
if (nbytes <= 0)
throw std::runtime_error("Failed to read ISO9660 file");
buffer.Append(nbytes);
r = buffer.Read();
if (skip > 0) {
if (skip >= r.size)
throw std::runtime_error("Premature end of ISO9660 track");
buffer.Consume(skip);
skip = 0;
r = buffer.Read();
}
}
offset += readed;
return readed;
assert(!r.empty());
assert(skip == 0);
size_t nbytes = std::min(read_size, r.size);
memcpy(ptr, r.data, nbytes);
buffer.Consume(nbytes);
offset += nbytes;
return nbytes;
}
bool

@@ -32,6 +32,8 @@
#include <zzip/zzip.h>
#include <inttypes.h> /* for PRIoffset (PRIu64) */
struct ZzipDir {
ZZIP_DIR *const dir;
@@ -54,10 +56,11 @@ class ZzipArchiveFile final : public ArchiveFile {
std::shared_ptr<ZzipDir> dir;
public:
ZzipArchiveFile(std::shared_ptr<ZzipDir> &&_dir)
:dir(std::move(_dir)) {}
template<typename D>
explicit ZzipArchiveFile(D &&_dir) noexcept
:dir(std::forward<D>(_dir)) {}
virtual void Visit(ArchiveVisitor &visitor) override;
void Visit(ArchiveVisitor &visitor) override;
InputStreamPtr OpenStream(const char *path,
Mutex &mutex) override;
@@ -91,11 +94,12 @@ class ZzipInputStream final : public InputStream {
ZZIP_FILE *const file;
public:
ZzipInputStream(const std::shared_ptr<ZzipDir> _dir, const char *_uri,
template<typename D>
ZzipInputStream(D &&_dir, const char *_uri,
Mutex &_mutex,
ZZIP_FILE *_file)
:InputStream(_uri, _mutex),
dir(_dir), file(_file) {
dir(std::forward<D>(_dir)), file(_file) {
//we are seekable (but its not recommendent to do so)
seekable = true;
@@ -106,7 +110,7 @@ public:
SetReady();
}
~ZzipInputStream() {
~ZzipInputStream() noexcept override {
zzip_file_close(file);
}
@@ -145,12 +149,17 @@ ZzipInputStream::Read(void *ptr, size_t read_size)
{
const ScopeUnlock unlock(mutex);
int ret = zzip_file_read(file, ptr, read_size);
if (ret < 0)
zzip_ssize_t nbytes = zzip_file_read(file, ptr, read_size);
if (nbytes < 0)
throw std::runtime_error("zzip_file_read() has failed");
if (nbytes == 0 && !IsEOF())
throw FormatRuntimeError("Unexpected end of file %s"
" at %" PRIoffset " of %" PRIoffset,
GetURI(), GetOffset(), GetSize());
offset = zzip_tell(file);
return ret;
return nbytes;
}
bool

@@ -193,7 +193,7 @@ static constexpr struct command commands[] = {
{ "subscribe", PERMISSION_READ, 1, 1, handle_subscribe },
{ "swap", PERMISSION_CONTROL, 2, 2, handle_swap },
{ "swapid", PERMISSION_CONTROL, 2, 2, handle_swapid },
{ "tagtypes", PERMISSION_READ, 0, -1, handle_tagtypes },
{ "tagtypes", PERMISSION_NONE, 0, -1, handle_tagtypes },
{ "toggleoutput", PERMISSION_ADMIN, 1, 1, handle_toggleoutput },
#ifdef ENABLE_DATABASE
{ "unmount", PERMISSION_ADMIN, 1, 1, handle_unmount },

@@ -50,9 +50,7 @@ gcc_pure
static bool
SkipNameFS(PathTraitsFS::const_pointer_type name_fs) noexcept
{
return name_fs[0] == '.' &&
(name_fs[1] == 0 ||
(name_fs[1] == '.' && name_fs[2] == 0));
return PathTraitsFS::IsSpecialFilename(name_fs);
}
gcc_pure

@@ -34,13 +34,12 @@
#include "util/StringBuffer.hxx"
#include "util/ScopeExit.hxx"
#include "util/Exception.hxx"
#include "util/Math.hxx"
#ifdef ENABLE_DATABASE
#include "db/update/Service.hxx"
#endif
#include <cmath>
#define COMMAND_STATUS_STATE "state"
#define COMMAND_STATUS_REPEAT "repeat"
#define COMMAND_STATUS_SINGLE "single"
@@ -149,12 +148,12 @@ handle_status(Client &client, gcc_unused Request args, Response &r)
playlist.GetConsume(),
(unsigned long)playlist.GetVersion(),
playlist.GetLength(),
pc.GetMixRampDb(),
(double)pc.GetMixRampDb(),
state);
if (pc.GetCrossFade() > FloatDuration::zero())
r.Format(COMMAND_STATUS_CROSSFADE ": %lu\n",
std::lround(pc.GetCrossFade().count()));
lround(pc.GetCrossFade().count()));
if (pc.GetMixRampDelay() > FloatDuration::zero())
r.Format(COMMAND_STATUS_MIXRAMPDELAY ": %f\n",
@@ -173,7 +172,7 @@ handle_status(Client &client, gcc_unused Request args, Response &r)
COMMAND_STATUS_BITRATE ": %u\n",
player_status.elapsed_time.RoundS(),
player_status.total_time.IsNegative()
? 0u
? 0U
: unsigned(player_status.total_time.RoundS()),
player_status.elapsed_time.ToDoubleS(),
player_status.bit_rate);

@@ -198,6 +198,16 @@ handle_mount(Client &client, Request args, Response &r)
return CommandResult::ERROR;
}
if (composite.IsMountPoint(local_uri)) {
r.Error(ACK_ERROR_ARG, "Mount point busy");
return CommandResult::ERROR;
}
if (composite.IsMounted(remote_uri)) {
r.Error(ACK_ERROR_ARG, "This storage is already mounted");
return CommandResult::ERROR;
}
auto &event_loop = instance.io_thread.GetEventLoop();
auto storage = CreateStorageURI(event_loop, remote_uri);
if (storage == nullptr) {
@@ -210,8 +220,10 @@ handle_mount(Client &client, Request args, Response &r)
#ifdef ENABLE_DATABASE
if (auto *db = dynamic_cast<SimpleDatabase *>(instance.GetDatabase())) {
bool need_update;
try {
db->Mount(local_uri, remote_uri);
need_update = !db->Mount(local_uri, remote_uri);
} catch (...) {
composite.Unmount(local_uri);
throw;
@@ -220,6 +232,12 @@ handle_mount(Client &client, Request args, Response &r)
// TODO: call Instance::OnDatabaseModified()?
// TODO: trigger database update?
instance.EmitIdle(IDLE_DATABASE);
if (need_update) {
UpdateService *update = client.GetInstance().update;
if (update != nullptr)
update->Enqueue(local_uri, false);
}
}
#endif

@@ -448,7 +448,7 @@ ProxyDatabase::ProxyDatabase(EventLoop &_loop, DatabaseListener &_listener,
listener(_listener),
host(block.GetBlockValue("host", "")),
password(block.GetBlockValue("password", "")),
port(block.GetBlockValue("port", 0u)),
port(block.GetBlockValue("port", 0U)),
keepalive(block.GetBlockValue("keepalive", false))
{
}
@@ -493,9 +493,13 @@ ProxyDatabase::Connect()
try {
CheckError(connection);
if (mpd_connection_cmp_server_version(connection, 0, 19, 0) < 0)
throw FormatRuntimeError("Connect to MPD %s, but this plugin requires at least version 0.19",
mpd_connection_get_server_version(connection));
if (mpd_connection_cmp_server_version(connection, 0, 19, 0) < 0) {
const unsigned *version =
mpd_connection_get_server_version(connection);
throw FormatRuntimeError("Connect to MPD %u.%u.%u, but this "
"plugin requires at least version 0.19",
version[0], version[1], version[2]);
}
if (!password.empty() &&
!mpd_run_password(connection, password.c_str()))
@@ -517,7 +521,7 @@ ProxyDatabase::Connect()
(void)keepalive;
#endif
idle_received = ~0u;
idle_received = ~0U;
is_idle = false;
SocketMonitor::Open(SocketDescriptor(mpd_async_get_fd(mpd_connection_get_async(connection))));

@@ -32,6 +32,7 @@
#include "fs/Traits.hxx"
#include "util/Alloc.hxx"
#include "util/DeleteDisposer.hxx"
#include "util/StringCompare.hxx"
#include <assert.h>
#include <string.h>
@@ -69,7 +70,15 @@ Directory::GetName() const noexcept
{
assert(!IsRoot());
return PathTraitsUTF8::GetBase(path.c_str());
if (parent->IsRoot())
return path.c_str();
assert(StringAfterPrefix(path.c_str(), parent->path.c_str()) != nullptr);
assert(*StringAfterPrefix(path.c_str(), parent->path.c_str()) == PathTraitsUTF8::SEPARATOR);
/* strip the parent directory path and the slash separator
from this directory's path, and the base name remains */
return path.c_str() + parent->path.length() + 1;
}
Directory *

@@ -428,7 +428,7 @@ IsUnsafeChar(char ch)
return !IsSafeChar(ch);
}
void
bool
SimpleDatabase::Mount(const char *local_uri, const char *storage_uri)
{
if (cache_path.IsNull())
@@ -447,14 +447,11 @@ SimpleDatabase::Mount(const char *local_uri, const char *storage_uri)
compress);
db->Open();
// TODO: update the new database instance?
bool exists = db->FileExists();
try {
Mount(local_uri, std::move(db));
} catch (...) {
db->Close();
throw;
}
Mount(local_uri, std::move(db));
return exists;
}
inline DatabasePtr

@@ -103,9 +103,11 @@ public:
/**
* Throws #std::runtime_error on error.
*
* @return false if the mounted database needs to be updated
*/
gcc_nonnull_all
void Mount(const char *local_uri, const char *storage_uri);
bool Mount(const char *local_uri, const char *storage_uri);
gcc_nonnull_all
bool Unmount(const char *uri) noexcept;

@@ -65,7 +65,7 @@ ContentDirectoryService::readDirSlice(UpnpClient_Handle hdl,
IXML_Document *response;
int code = UpnpSendAction(hdl, m_actionURL.c_str(), m_serviceType.c_str(),
0 /*devUDN*/, request, &response);
nullptr /*devUDN*/, request, &response);
if (code != UPNP_E_SUCCESS)
throw FormatRuntimeError("UpnpSendAction() failed: %s",
UpnpGetErrorMessage(code));
@@ -124,7 +124,7 @@ ContentDirectoryService::search(UpnpClient_Handle hdl,
IXML_Document *_response;
auto code = UpnpSendAction(hdl, m_actionURL.c_str(),
m_serviceType.c_str(),
0 /*devUDN*/,
nullptr /*devUDN*/,
request.get(), &_response);
if (code != UPNP_E_SUCCESS)
throw FormatRuntimeError("UpnpSendAction() failed: %s",
@@ -170,7 +170,7 @@ ContentDirectoryService::getMetadata(UpnpClient_Handle hdl,
IXML_Document *_response;
auto code = UpnpSendAction(hdl, m_actionURL.c_str(),
m_serviceType.c_str(),
0 /*devUDN*/, request.get(), &_response);
nullptr /*devUDN*/, request.get(), &_response);
if (code != UPNP_E_SUCCESS)
throw FormatRuntimeError("UpnpSendAction() failed: %s",
UpnpGetErrorMessage(code));

@@ -89,9 +89,18 @@ public:
tag.Clear();
}
gcc_pure
bool IsRoot() const noexcept {
return type == Type::CONTAINER && id == "0";
}
gcc_pure
bool Check() const noexcept {
return !id.empty() && !parent_id.empty() && !name.empty() &&
return !id.empty() &&
/* root nodes don't need a parent id and a
name */
(IsRoot() || (!parent_id.empty() &&
!name.empty())) &&
(type != UPnPDirObject::Type::ITEM ||
item_class != UPnPDirObject::ItemClass::UNKNOWN);
}

@@ -79,7 +79,7 @@ path_in(const char *path, const char *possible_parent) noexcept
}
void
InotifyQueue::Enqueue(const char *uri_utf8)
InotifyQueue::Enqueue(const char *uri_utf8) noexcept
{
delay_event.Schedule(INOTIFY_UPDATE_DELAY);

@@ -35,11 +35,11 @@ class InotifyQueue final {
TimerEvent delay_event;
public:
InotifyQueue(EventLoop &_loop, UpdateService &_update)
InotifyQueue(EventLoop &_loop, UpdateService &_update) noexcept
:update(_update),
delay_event(_loop, BIND_THIS_METHOD(OnDelay)) {}
void Enqueue(const char *uri_utf8);
void Enqueue(const char *uri_utf8) noexcept;
private:
void OnDelay() noexcept;

@@ -24,11 +24,11 @@
#include "system/Error.hxx"
#include "Log.hxx"
#include <cerrno>
#include <climits>
#include <cstdint>
#include <sys/inotify.h>
#include <unistd.h>
#include <errno.h>
#include <stdint.h>
#include <limits.h>
bool
InotifySource::OnSocketReady(gcc_unused unsigned flags) noexcept
@@ -48,7 +48,7 @@ InotifySource::OnSocketReady(gcc_unused unsigned flags) noexcept
while (true) {
const size_t remaining = end - p;
const struct inotify_event *event =
const auto *event =
(const struct inotify_event *)p;
if (remaining < sizeof(*event) ||
remaining < sizeof(*event) + event->len)
@@ -98,7 +98,7 @@ InotifySource::Add(const char *path_fs, unsigned mask)
}
void
InotifySource::Remove(unsigned wd)
InotifySource::Remove(unsigned wd) noexcept
{
auto ifd = GetSocket().ToFileDescriptor();
int ret = inotify_rm_watch(ifd.Get(), wd);

@@ -21,9 +21,6 @@
#define MPD_INOTIFY_SOURCE_HXX
#include "event/SocketMonitor.hxx"
#include "util/Compiler.h"
class FileDescriptor;
typedef void (*mpd_inotify_callback_t)(int wd, unsigned mask,
const char *name, void *ctx);
@@ -45,7 +42,7 @@ public:
InotifySource(EventLoop &_loop,
mpd_inotify_callback_t callback, void *ctx);
~InotifySource() {
~InotifySource() noexcept {
Close();
}
@@ -63,7 +60,7 @@ public:
*
* @param wd the watch descriptor returned by mpd_inotify_source_add()
*/
void Remove(unsigned wd);
void Remove(unsigned wd) noexcept;
private:
bool OnSocketReady(unsigned flags) noexcept override;

@@ -21,18 +21,25 @@
#include "InotifySource.hxx"
#include "InotifyQueue.hxx"
#include "InotifyDomain.hxx"
#include "ExcludeList.hxx"
#include "storage/StorageInterface.hxx"
#include "input/InputStream.hxx"
#include "input/Error.hxx"
#include "fs/AllocatedPath.hxx"
#include "fs/DirectoryReader.hxx"
#include "fs/FileInfo.hxx"
#include "fs/Traits.hxx"
#include "thread/Mutex.hxx"
#include "util/Compiler.h"
#include "Log.hxx"
#include <string>
#include <map>
#include <cassert>
#include <cstring>
#include <forward_list>
#include <map>
#include <string>
#include <assert.h>
#include <sys/inotify.h>
#include <string.h>
#include <dirent.h>
static constexpr unsigned IN_MASK =
@@ -49,17 +56,28 @@ struct WatchDirectory {
int descriptor;
ExcludeList exclude_list;
std::forward_list<WatchDirectory> children;
template<typename N>
WatchDirectory(WatchDirectory *_parent, N &&_name,
WatchDirectory(N &&_name,
int _descriptor)
:parent(_parent), name(std::forward<N>(_name)),
:parent(nullptr), name(std::forward<N>(_name)),
descriptor(_descriptor) {}
template<typename N>
WatchDirectory(WatchDirectory &_parent, N &&_name,
int _descriptor)
:parent(&_parent), name(std::forward<N>(_name)),
descriptor(_descriptor),
exclude_list(_parent.exclude_list) {}
WatchDirectory(const WatchDirectory &) = delete;
WatchDirectory &operator=(const WatchDirectory &) = delete;
void LoadExcludeList(Path directory_path) noexcept;
gcc_pure
unsigned GetDepth() const noexcept;
@@ -67,6 +85,18 @@ struct WatchDirectory {
AllocatedPath GetUriFS() const noexcept;
};
void
WatchDirectory::LoadExcludeList(Path directory_path) noexcept
try {
Mutex mutex;
auto is = InputStream::OpenReady((directory_path / Path::FromFS(".mpdignore")).c_str(),
mutex);
exclude_list.Load(std::move(is));
} catch (...) {
if (!IsFileNotFound(std::current_exception()))
LogError(std::current_exception());
}
static InotifySource *inotify_source;
static InotifyQueue *inotify_queue;
@@ -144,21 +174,19 @@ WatchDirectory::GetUriFS() const noexcept
}
/* we don't look at "." / ".." nor files with newlines in their name */
static bool skip_path(const char *path)
gcc_pure
static bool
SkipFilename(Path name) noexcept
{
return (path[0] == '.' && path[1] == 0) ||
(path[0] == '.' && path[1] == '.' && path[2] == 0) ||
strchr(path, '\n') != nullptr;
return PathTraitsFS::IsSpecialFilename(name.c_str()) ||
name.HasNewline();
}
static void
recursive_watch_subdirectories(WatchDirectory *directory,
const AllocatedPath &path_fs, unsigned depth)
{
DIR *dir;
struct dirent *ent;
assert(directory != nullptr);
recursive_watch_subdirectories(WatchDirectory &parent,
const Path path_fs,
unsigned depth)
try {
assert(depth <= inotify_max_depth);
assert(!path_fs.IsNull());
@@ -167,20 +195,17 @@ recursive_watch_subdirectories(WatchDirectory *directory,
if (depth > inotify_max_depth)
return;
dir = opendir(path_fs.c_str());
if (dir == nullptr) {
FormatErrno(inotify_domain,
"Failed to open directory %s", path_fs.c_str());
return;
}
while ((ent = readdir(dir))) {
DirectoryReader dir(path_fs);
while (dir.ReadEntry()) {
int ret;
if (skip_path(ent->d_name))
const Path name_fs = dir.GetEntry();
if (SkipFilename(name_fs))
continue;
if (parent.exclude_list.Check(name_fs))
continue;
const auto name_fs = Path::FromFS(ent->d_name);
const auto child_path_fs = path_fs / name_fs;
FileInfo fi;
@@ -209,17 +234,18 @@ recursive_watch_subdirectories(WatchDirectory *directory,
/* already being watched */
continue;
directory->children.emplace_front(directory,
name_fs,
ret);
child = &directory->children.front();
parent.children.emplace_front(parent,
name_fs,
ret);
child = &parent.children.front();
child->LoadExcludeList(child_path_fs);
tree_add_watch_directory(child);
recursive_watch_subdirectories(child, child_path_fs, depth);
recursive_watch_subdirectories(*child, child_path_fs, depth);
}
closedir(dir);
} catch (...) {
LogError(std::current_exception());
}
gcc_pure
@@ -240,8 +266,6 @@ mpd_inotify_callback(int wd, unsigned mask,
{
WatchDirectory *directory;
/*FormatDebug(inotify_domain, "wd=%d mask=0x%x name='%s'", wd, mask, name);*/
directory = tree_find_watch_directory(wd);
if (directory == nullptr)
return;
@@ -263,7 +287,7 @@ mpd_inotify_callback(int wd, unsigned mask,
? root
: (root / uri_fs);
recursive_watch_subdirectories(directory, path_fs,
recursive_watch_subdirectories(*directory, path_fs,
directory->GetDepth());
}
@@ -318,11 +342,12 @@ mpd_inotify_init(EventLoop &loop, Storage &storage, UpdateService &update,
return;
}
inotify_root = new WatchDirectory(nullptr, path, descriptor);
inotify_root = new WatchDirectory(path, descriptor);
inotify_root->LoadExcludeList(path);
tree_add_watch_directory(inotify_root);
recursive_watch_subdirectories(inotify_root, path, 0);
recursive_watch_subdirectories(*inotify_root, path, 0);
inotify_queue = new InotifyQueue(loop, update);

@@ -20,8 +20,6 @@
#ifndef MPD_INOTIFY_UPDATE_HXX
#define MPD_INOTIFY_UPDATE_HXX
#include "util/Compiler.h"
class EventLoop;
class Storage;
class UpdateService;

@@ -66,7 +66,7 @@ public:
~UpdateService();
EventLoop &GetEventLoop() noexcept {
auto &GetEventLoop() const noexcept {
return defer.GetEventLoop();
}

@@ -237,7 +237,7 @@ try {
LogError(std::current_exception());
}
/* we don't look at "." / ".." nor files with newlines in their name */
/* we don't look at files with newlines in their name */
gcc_pure
static bool
skip_path(const char *name_utf8) noexcept
@@ -341,8 +341,8 @@ UpdateWalk::UpdateDirectory(Directory &directory,
try {
Mutex mutex;
auto is = InputStream::OpenReady(PathTraitsUTF8::Build(storage.MapUTF8(directory.GetPath()).c_str(),
".mpdignore").c_str(),
auto is = InputStream::OpenReady(storage.MapUTF8(PathTraitsUTF8::Build(directory.GetPath(),
".mpdignore").c_str()).c_str(),
mutex);
child_exclude_list.Load(std::move(is));
} catch (...) {

@@ -33,9 +33,11 @@
#include "util/ConstBuffer.hxx"
#include "util/StringBuffer.hxx"
#include <cmath>
#include <stdexcept>
#include <assert.h>
#include <string.h>
#include <math.h>
DecoderBridge::~DecoderBridge()
{
@@ -344,6 +346,10 @@ DecoderBridge::SeekError()
/* d'oh, we can't seek to the sub-song start position,
what now? - no idea, ignoring the problem for now. */
initial_seek_running = false;
if (initial_seek_essential)
error = std::make_exception_ptr(std::runtime_error("Decoder failed to seek"));
return;
}
@@ -591,7 +597,7 @@ DecoderBridge::SubmitReplayGain(const ReplayGainInfo *new_replay_gain_info)
const auto &tuple = new_replay_gain_info->Get(rgm);
const auto scale =
tuple.CalculateScale(dc.replay_gain_config);
dc.replay_gain_db = 20.0 * log10f(scale);
dc.replay_gain_db = 20.0f * std::log10(scale);
}
replay_gain_info = *new_replay_gain_info;

@@ -62,6 +62,11 @@ public:
*/
bool initial_seek_pending;
/**
* Are initial seek failures fatal?
*/
const bool initial_seek_essential;
/**
* Is the initial seek currently running? During this time,
* the decoder command is SEEK. This flag is set by
@@ -107,9 +112,11 @@ public:
std::exception_ptr error;
DecoderBridge(DecoderControl &_dc, bool _initial_seek_pending,
bool _initial_seek_essential,
std::unique_ptr<Tag> _tag)
:dc(_dc),
initial_seek_pending(_initial_seek_pending),
initial_seek_essential(_initial_seek_essential),
song_tag(std::move(_tag)) {}
~DecoderBridge();

@@ -90,6 +90,7 @@ DecoderControl::IsCurrentSong(const DetachedSong &_song) const noexcept
void
DecoderControl::Start(std::unique_ptr<DetachedSong> _song,
SongTime _start_time, SongTime _end_time,
bool _initial_seek_essential,
MusicBuffer &_buffer,
std::shared_ptr<MusicPipe> _pipe) noexcept
{
@@ -99,6 +100,7 @@ DecoderControl::Start(std::unique_ptr<DetachedSong> _song,
song = std::move(_song);
start_time = _start_time;
end_time = _end_time;
initial_seek_essential = _initial_seek_essential;
buffer = &_buffer;
pipe = std::move(_pipe);

@@ -117,6 +117,12 @@ public:
bool seek_error;
bool seekable;
/**
* @see #DecoderBridge::initial_seek_essential
*/
bool initial_seek_essential;
SongTime seek_time;
private:
@@ -398,11 +404,14 @@ public:
* owned and freed by the decoder
* @param start_time see #DecoderControl
* @param end_time see #DecoderControl
* @param initial_seek_essential see
* #DecoderBridge::initial_seek_essential
* @param pipe the pipe which receives the decoded chunks (owned by
* the caller)
*/
void Start(std::unique_ptr<DetachedSong> song,
SongTime start_time, SongTime end_time,
bool initial_seek_essential,
MusicBuffer &buffer,
std::shared_ptr<MusicPipe> pipe) noexcept;

@@ -26,7 +26,7 @@
size_t
decoder_read(DecoderClient *client,
InputStream &is,
void *buffer, size_t length)
void *buffer, size_t length) noexcept
{
assert(buffer != nullptr);
@@ -42,9 +42,30 @@ decoder_read(DecoderClient *client,
}
}
size_t
decoder_read_much(DecoderClient *client, InputStream &is,
void *_buffer, size_t size) noexcept
{
uint8_t *buffer = (uint8_t *)_buffer;
size_t total = 0;
while (size > 0 && !is.LockIsEOF()) {
size_t nbytes = decoder_read(client, is, buffer, size);
if (nbytes == 0)
return false;
total += nbytes;
buffer += nbytes;
size -= nbytes;
}
return total;
}
bool
decoder_read_full(DecoderClient *client, InputStream &is,
void *_buffer, size_t size)
void *_buffer, size_t size) noexcept
{
uint8_t *buffer = (uint8_t *)_buffer;
@@ -61,7 +82,7 @@ decoder_read_full(DecoderClient *client, InputStream &is,
}
bool
decoder_skip(DecoderClient *client, InputStream &is, size_t size)
decoder_skip(DecoderClient *client, InputStream &is, size_t size) noexcept
{
while (size > 0) {
char buffer[1024];

@@ -65,15 +65,27 @@ class StopDecoder {};
*/
size_t
decoder_read(DecoderClient *decoder, InputStream &is,
void *buffer, size_t length);
void *buffer, size_t length) noexcept;
static inline size_t
decoder_read(DecoderClient &decoder, InputStream &is,
void *buffer, size_t length)
void *buffer, size_t length) noexcept
{
return decoder_read(&decoder, is, buffer, length);
}
/**
* Blocking read from the input stream. Attempts to fill the buffer
* as much as possible, until either end-of-file is reached or an
* error occurs.
*
* @return the number of bytes read, or 0 if one of the following
* occurs: end of file; error; command (like SEEK or STOP).
*/
size_t
decoder_read_much(DecoderClient *decoder, InputStream &is,
void *buffer, size_t size) noexcept;
/**
* Blocking read from the input stream. Attempts to fill the buffer
* completely; there is no partial result.
@@ -83,7 +95,7 @@ decoder_read(DecoderClient &decoder, InputStream &is,
*/
bool
decoder_read_full(DecoderClient *decoder, InputStream &is,
void *buffer, size_t size);
void *buffer, size_t size) noexcept;
/**
* Skip data on the #InputStream.
@@ -91,6 +103,6 @@ decoder_read_full(DecoderClient *decoder, InputStream &is,
* @return true on success, false on error or command
*/
bool
decoder_skip(DecoderClient *decoder, InputStream &is, size_t size);
decoder_skip(DecoderClient *decoder, InputStream &is, size_t size) noexcept;
#endif

@@ -22,7 +22,7 @@
#include "util/Compiler.h"
#include <forward_list>
#include <forward_list> // IWYU pragma: export
struct ConfigBlock;
class InputStream;
@@ -67,18 +67,22 @@ struct DecoderPlugin {
void (*file_decode)(DecoderClient &client, Path path_fs);
/**
* Scan metadata of a file.
* Scan metadata of a file.
*
* Throws on I/O error.
*
* @return false if the operation has failed
* @return false if the file was not recognized
*/
bool (*scan_file)(Path path_fs, TagHandler &handler) noexcept;
bool (*scan_file)(Path path_fs, TagHandler &handler);
/**
* Scan metadata of a file.
* Scan metadata of a stream.
*
* Throws on I/O error.
*
* @return false if the operation has failed
* @return false if the stream was not recognized
*/
bool (*scan_stream)(InputStream &is, TagHandler &handler) noexcept;
bool (*scan_stream)(InputStream &is, TagHandler &handler);
/**
* @brief Return a "virtual" filename for subtracks in
@@ -135,7 +139,7 @@ struct DecoderPlugin {
* Read the tag of a file.
*/
template<typename P>
bool ScanFile(P path_fs, TagHandler &handler) const noexcept {
bool ScanFile(P path_fs, TagHandler &handler) const {
return scan_file != nullptr
? scan_file(path_fs, handler)
: false;
@@ -144,7 +148,7 @@ struct DecoderPlugin {
/**
* Read the tag of a stream.
*/
bool ScanStream(InputStream &is, TagHandler &handler) const noexcept {
bool ScanStream(InputStream &is, TagHandler &handler) const {
return scan_stream != nullptr
? scan_stream(is, handler)
: false;

@@ -461,6 +461,7 @@ decoder_run_song(DecoderControl &dc,
dc.start_time = dc.seek_time;
DecoderBridge bridge(dc, dc.start_time.IsPositive(),
dc.initial_seek_essential,
/* pass the song tag only if it's
authoritative, i.e. if it's a local
file - tags on "stream" songs are just

@@ -41,7 +41,7 @@ adplug_init(const ConfigBlock &block)
FormatDebug(adplug_domain, "adplug %s",
CAdPlug::get_version().c_str());
sample_rate = block.GetPositiveValue("sample_rate", 48000u);
sample_rate = block.GetPositiveValue("sample_rate", 48000U);
CheckSampleRate(sample_rate);
return true;

@@ -241,7 +241,7 @@ audiofile_stream_decode(DecoderClient &client, InputStream &is)
}
static bool
audiofile_scan_stream(InputStream &is, TagHandler &handler) noexcept
audiofile_scan_stream(InputStream &is, TagHandler &handler)
{
if (!is.IsSeekable() || !is.KnownSize())
return false;

@@ -20,7 +20,7 @@
#ifndef MPD_DECODER_DSDLIB_HXX
#define MPD_DECODER_DSDLIB_HXX
#include "system/ByteOrder.hxx"
#include "util/ByteOrder.hxx"
#include "input/Offset.hxx"
#include "util/Compiler.h"

@@ -32,7 +32,7 @@
#include "input/InputStream.hxx"
#include "CheckAudioFormat.hxx"
#include "util/bit_reverse.h"
#include "system/ByteOrder.hxx"
#include "util/ByteOrder.hxx"
#include "tag/Handler.hxx"
#include "DsdLib.hxx"
#include "Log.hxx"
@@ -449,7 +449,7 @@ dsdiff_stream_decode(DecoderClient &client, InputStream &is)
}
static bool
dsdiff_scan_stream(InputStream &is, TagHandler &handler) noexcept
dsdiff_scan_stream(InputStream &is, TagHandler &handler)
{
DsdiffMetaData metadata;
DsdiffChunkHeader chunk_header;

@@ -33,7 +33,7 @@
#include "input/InputStream.hxx"
#include "CheckAudioFormat.hxx"
#include "util/bit_reverse.h"
#include "system/ByteOrder.hxx"
#include "util/ByteOrder.hxx"
#include "DsdLib.hxx"
#include "tag/Handler.hxx"
#include "Log.hxx"
@@ -326,7 +326,7 @@ dsf_stream_decode(DecoderClient &client, InputStream &is)
}
static bool
dsf_scan_stream(InputStream &is, TagHandler &handler) noexcept
dsf_scan_stream(InputStream &is, TagHandler &handler)
{
/* check DSF metadata */
DsfMetaData metadata;

@@ -26,11 +26,11 @@
#include "util/ScopeExit.hxx"
#include "util/ConstBuffer.hxx"
#include "util/Domain.hxx"
#include "util/Math.hxx"
#include "Log.hxx"
#include <neaacdec.h>
#include <cmath>
#include <exception>
#include <assert.h>
@@ -414,7 +414,7 @@ faad_stream_decode(DecoderClient &client, InputStream &is)
}
static bool
faad_scan_stream(InputStream &is, TagHandler &handler) noexcept
faad_scan_stream(InputStream &is, TagHandler &handler)
{
auto result = faad_get_file_time(is);
if (!result.first)

@@ -297,7 +297,7 @@ FfmpegReceiveFrames(DecoderClient &client, InputStream &is,
*/
static DecoderCommand
ffmpeg_send_packet(DecoderClient &client, InputStream &is,
AVPacket &&packet,
const AVPacket &packet,
AVCodecContext &codec_context,
const AVStream &stream,
AVFrame &frame,
@@ -350,24 +350,6 @@ ffmpeg_send_packet(DecoderClient &client, InputStream &is,
return cmd;
}
static DecoderCommand
ffmpeg_send_packet(DecoderClient &client, InputStream &is,
const AVPacket &packet,
AVCodecContext &codec_context,
const AVStream &stream,
AVFrame &frame,
uint64_t min_frame, size_t pcm_frame_size,
FfmpegBuffer &buffer)
{
return ffmpeg_send_packet(client, is,
/* copy the AVPacket, because FFmpeg
< 3.0 requires this */
AVPacket(packet),
codec_context, stream,
frame, min_frame, pcm_frame_size,
buffer);
}
gcc_const
static SampleFormat
ffmpeg_sample_format(enum AVSampleFormat sample_fmt) noexcept
@@ -664,8 +646,7 @@ ffmpeg_decode(DecoderClient &client, InputStream &input)
}
static bool
FfmpegScanStream(AVFormatContext &format_context,
TagHandler &handler) noexcept
FfmpegScanStream(AVFormatContext &format_context, TagHandler &handler)
{
const int find_result =
avformat_find_stream_info(&format_context, nullptr);
@@ -698,7 +679,7 @@ FfmpegScanStream(AVFormatContext &format_context,
}
static bool
ffmpeg_scan_stream(InputStream &is, TagHandler &handler) noexcept
ffmpeg_scan_stream(InputStream &is, TagHandler &handler)
{
AvioStream stream(nullptr, is);
if (!stream.Open())

@@ -69,7 +69,7 @@ flac_write_cb(const FLAC__StreamDecoder *dec, const FLAC__Frame *frame,
}
static bool
flac_scan_file(Path path_fs, TagHandler &handler) noexcept
flac_scan_file(Path path_fs, TagHandler &handler)
{
FlacMetadataChain chain;
if (!chain.Read(NarrowPath(path_fs))) {
@@ -84,7 +84,7 @@ flac_scan_file(Path path_fs, TagHandler &handler) noexcept
}
static bool
flac_scan_stream(InputStream &is, TagHandler &handler) noexcept
flac_scan_stream(InputStream &is, TagHandler &handler)
{
FlacMetadataChain chain;
if (!chain.Read(is)) {
@@ -313,7 +313,7 @@ oggflac_init(gcc_unused const ConfigBlock &block)
}
static bool
oggflac_scan_file(Path path_fs, TagHandler &handler) noexcept
oggflac_scan_file(Path path_fs, TagHandler &handler)
{
FlacMetadataChain chain;
if (!chain.ReadOgg(NarrowPath(path_fs))) {
@@ -328,7 +328,7 @@ oggflac_scan_file(Path path_fs, TagHandler &handler) noexcept
}
static bool
oggflac_scan_stream(InputStream &is, TagHandler &handler) noexcept
oggflac_scan_stream(InputStream &is, TagHandler &handler)
{
FlacMetadataChain chain;
if (!chain.ReadOgg(is)) {

@@ -70,15 +70,15 @@ fluidsynth_mpd_log_function(int level,
char *message,
void *)
{
Log(fluidsynth_domain,
fluidsynth_level_to_mpd(fluid_log_level(level)),
Log(fluidsynth_level_to_mpd(fluid_log_level(level)),
fluidsynth_domain,
message);
}
static bool
fluidsynth_init(const ConfigBlock &block)
{
sample_rate = block.GetPositiveValue("sample_rate", 48000u);
sample_rate = block.GetPositiveValue("sample_rate", 48000U);
CheckSampleRate(sample_rate);
soundfont_path = block.GetBlockValue("soundfont",

@@ -27,9 +27,10 @@
#include "fs/Path.hxx"
#include "fs/AllocatedPath.hxx"
#include "fs/FileSystem.hxx"
#include "fs/NarrowPath.hxx"
#include "util/ScopeExit.hxx"
#include "util/StringCompare.hxx"
#include "util/StringFormat.hxx"
#include "util/UriUtil.hxx"
#include "util/Domain.hxx"
#include "Log.hxx"
@@ -37,7 +38,6 @@
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#define SUBTUNE_PREFIX "tune_"
@@ -75,11 +75,10 @@ gcc_pure
static unsigned
ParseSubtuneName(const char *base) noexcept
{
if (memcmp(base, SUBTUNE_PREFIX, sizeof(SUBTUNE_PREFIX) - 1) != 0)
base = StringAfterPrefix(base, SUBTUNE_PREFIX);
if (base == nullptr)
return 0;
base += sizeof(SUBTUNE_PREFIX) - 1;
char *endptr;
auto track = strtoul(base, &endptr, 10);
if (endptr == base || *endptr != '.')
@@ -98,41 +97,46 @@ ParseContainerPath(Path path_fs)
const Path base = path_fs.GetBase();
unsigned track;
if (base.IsNull() ||
(track = ParseSubtuneName(base.c_str())) < 1)
(track = ParseSubtuneName(NarrowPath(base))) < 1)
return { AllocatedPath(path_fs), 0 };
return { path_fs.GetDirectoryName(), track - 1 };
}
static AllocatedPath
ReplaceSuffix(Path src,
const PathTraitsFS::const_pointer_type new_suffix) noexcept
{
const auto *old_suffix = src.GetSuffix();
if (old_suffix == nullptr)
return nullptr;
PathTraitsFS::string s(src.c_str(), old_suffix);
s += new_suffix;
return AllocatedPath::FromFS(std::move(s));
}
static Music_Emu*
LoadGmeAndM3u(GmeContainerPath container) {
const char *path = container.path.c_str();
const char *suffix = uri_get_suffix(path);
Music_Emu *emu;
const char *gme_err =
gme_open_file(path, &emu, GME_SAMPLE_RATE);
gme_open_file(NarrowPath(container.path), &emu, GME_SAMPLE_RATE);
if (gme_err != nullptr) {
LogWarning(gme_domain, gme_err);
return nullptr;
}
if(suffix == nullptr) {
return emu;
}
std::string m3u_path(path,suffix);
m3u_path += "m3u";
const auto m3u_path = ReplaceSuffix(container.path,
PATH_LITERAL("m3u"));
/*
* Some GME formats lose metadata if you attempt to
* load a non-existant M3U file, so check that one
* exists before loading.
*/
if(FileExists(Path::FromFS(m3u_path.c_str()))) {
gme_load_m3u(emu,m3u_path.c_str());
}
if (!m3u_path.IsNull() && FileExists(m3u_path))
gme_load_m3u(emu, NarrowPath(m3u_path));
return emu;
}
@@ -184,7 +188,11 @@ gme_file_decode(DecoderClient &client, Path path_fs)
LogWarning(gme_domain, gme_err);
if (length > 0)
gme_set_fade(emu, length);
gme_set_fade(emu, length
#if GME_VERSION >= 0x000700
, 8000
#endif
);
/* play */
DecoderCommand cmd;
@@ -222,7 +230,7 @@ ScanGmeInfo(const gme_info_t &info, unsigned song_num, int track_count,
if (track_count > 1)
handler.OnTag(TAG_TRACK, StringFormat<16>("%u", song_num + 1));
if (info.song != nullptr) {
if (!StringIsEmpty(info.song)) {
if (track_count > 1) {
/* start numbering subtunes from 1 */
const auto tag_title =
@@ -234,16 +242,16 @@ ScanGmeInfo(const gme_info_t &info, unsigned song_num, int track_count,
handler.OnTag(TAG_TITLE, info.song);
}
if (info.author != nullptr)
if (!StringIsEmpty(info.author))
handler.OnTag(TAG_ARTIST, info.author);
if (info.game != nullptr)
if (!StringIsEmpty(info.game))
handler.OnTag(TAG_ALBUM, info.game);
if (info.comment != nullptr)
if (!StringIsEmpty(info.comment))
handler.OnTag(TAG_COMMENT, info.comment);
if (info.copyright != nullptr)
if (!StringIsEmpty(info.copyright))
handler.OnTag(TAG_DATE, info.copyright);
}
@@ -298,7 +306,7 @@ gme_container_scan(Path path_fs)
if (num_songs < 2)
return list;
const char *subtune_suffix = uri_get_suffix(path_fs.c_str());
const auto *subtune_suffix = path_fs.GetSuffix();
TagBuilder tag_builder;

@@ -20,7 +20,7 @@
#include "HybridDsdDecoderPlugin.hxx"
#include "../DecoderAPI.hxx"
#include "input/InputStream.hxx"
#include "system/ByteOrder.hxx"
#include "util/ByteOrder.hxx"
#include "util/Domain.hxx"
#include "util/WritableBuffer.hxx"
#include "util/StaticFifoBuffer.hxx"
@@ -186,7 +186,7 @@ HybridDsdDecode(DecoderClient &client, InputStream &input)
client.Ready(result.first, true, duration);
frame_size = result.first.GetFrameSize();
kbit_rate = frame_size * result.first.sample_rate /
(1024u / 8u);
(1024U / 8U);
total_frames = result.second / frame_size;
} catch (UnsupportedFile) {
/* not a Hybrid-DSD file; let the next decoder plugin
@@ -236,7 +236,7 @@ HybridDsdDecode(DecoderClient &client, InputStream &input)
/* fill the buffer */
auto w = buffer.Write();
if (!w.empty()) {
if (remaining_bytes < (1<<30ull) &&
if (remaining_bytes < (1<<30ULL) &&
w.size > size_t(remaining_bytes))
w.size = remaining_bytes;

@@ -617,8 +617,8 @@ parse_lame(struct lame *lame, struct mad_bitptr *ptr, int *bitlen) noexcept
mad_bit_skip(ptr, 16);
lame->peak = mad_f_todouble(mad_bit_read(ptr, 32) << 5); /* peak */
FormatDebug(mad_domain, "LAME peak found: %f", lame->peak);
lame->peak = MAD_F(mad_bit_read(ptr, 32) << 5); /* peak */
FormatDebug(mad_domain, "LAME peak found: %f", double(lame->peak));
lame->track_gain = 0;
unsigned name = mad_bit_read(ptr, 3); /* gain name */
@@ -626,9 +626,9 @@ parse_lame(struct lame *lame, struct mad_bitptr *ptr, int *bitlen) noexcept
unsigned sign = mad_bit_read(ptr, 1); /* sign bit */
int gain = mad_bit_read(ptr, 9); /* gain*10 */
if (gain && name == 1 && orig != 0) {
lame->track_gain = ((sign ? -gain : gain) / 10.0) + adj;
lame->track_gain = ((sign ? -gain : gain) / 10.0f) + adj;
FormatDebug(mad_domain, "LAME track gain found: %f",
lame->track_gain);
double(lame->track_gain));
}
/* tmz reports that this isn't currently written by any version of lame
@@ -644,7 +644,7 @@ parse_lame(struct lame *lame, struct mad_bitptr *ptr, int *bitlen) noexcept
if (gain && name == 2 && orig != 0) {
lame->album_gain = ((sign ? -gain : gain) / 10.0) + adj;
FormatDebug(mad_domain, "LAME album gain found: %f",
lame->track_gain);
double(lame->track_gain));
}
#else
mad_bit_skip(ptr, 16);
@@ -778,7 +778,7 @@ MadDecoder::DecodeFirstFrame(Tag *tag) noexcept
/* Album gain isn't currently used. See comment in
* parse_lame() for details. -- jat */
if (client != nullptr && !found_replay_gain &&
lame.track_gain) {
lame.track_gain > 0.0f) {
ReplayGainInfo rgi;
rgi.Clear();
rgi.track.gain = lame.track_gain;
@@ -1051,7 +1051,7 @@ MadDecoder::RunScan(TagHandler &handler) noexcept
}
static bool
mad_decoder_scan_stream(InputStream &is, TagHandler &handler) noexcept
mad_decoder_scan_stream(InputStream &is, TagHandler &handler)
{
MadDecoder data(nullptr, is);
return data.RunScan(handler);

@@ -107,7 +107,7 @@ mikmod_decoder_init(const ConfigBlock &block)
static char params[] = "";
mikmod_loop = block.GetBlockValue("loop", false);
mikmod_sample_rate = block.GetPositiveValue("sample_rate", 44100u);
mikmod_sample_rate = block.GetPositiveValue("sample_rate", 44100U);
if (!audio_valid_sample_rate(mikmod_sample_rate))
throw FormatRuntimeError("Invalid sample rate in line %d: %u",
block.line, mikmod_sample_rate);

@@ -26,8 +26,13 @@
#include "util/RuntimeError.hxx"
#include "Log.hxx"
#include <libmodplug/modplug.h>
#ifdef _WIN32
/* assume ModPlug is built as static library on Windows; without
this, linking to the static library would fail */
#define MODPLUG_STATIC
#endif
#include <libmodplug/modplug.h>
#include <assert.h>

@@ -275,7 +275,7 @@ mpcdec_get_file_duration(InputStream &is)
}
static bool
mpcdec_scan_stream(InputStream &is, TagHandler &handler) noexcept
mpcdec_scan_stream(InputStream &is, TagHandler &handler)
{
const auto duration = mpcdec_get_file_duration(is);
if (duration.IsNegative())

@@ -75,6 +75,25 @@ class MPDOpusDecoder final : public OggDecoder {
OpusDecoder *opus_decoder = nullptr;
opus_int16 *output_buffer = nullptr;
/**
* The output gain from the Opus header. Initialized by
* OnOggBeginning().
*/
signed output_gain;
/**
* The pre-skip value from the Opus header. Initialized by
* OnOggBeginning().
*/
unsigned pre_skip;
/**
* The number of decoded samples which shall be skipped. At
* the beginning of the file, this gets set to #pre_skip (by
* OnOggBeginning()), and may also be set while seeking.
*/
unsigned skip;
/**
* If non-zero, then a previous Opus stream has been found
* already with this number of channels. If opus_decoder is
@@ -85,6 +104,13 @@ class MPDOpusDecoder final : public OggDecoder {
size_t frame_size;
/**
* The granulepos of the next sample to be submitted to
* DecoderClient::SubmitData(). Negative if unkown.
* Initialized by OnOggBeginning().
*/
ogg_int64_t granulepos;
public:
explicit MPDOpusDecoder(DecoderReader &reader)
:OggDecoder(reader) {}
@@ -101,6 +127,13 @@ public:
bool Seek(uint64_t where_frame);
private:
void AddGranulepos(ogg_int64_t n) noexcept {
assert(n >= 0);
if (granulepos >= 0)
granulepos += n;
}
void HandleTags(const ogg_packet &packet);
void HandleAudio(const ogg_packet &packet);
@@ -137,10 +170,13 @@ MPDOpusDecoder::OnOggBeginning(const ogg_packet &packet)
throw std::runtime_error("BOS packet must be OpusHead");
unsigned channels;
if (!ScanOpusHeader(packet.packet, packet.bytes, channels) ||
if (!ScanOpusHeader(packet.packet, packet.bytes, channels, output_gain, pre_skip) ||
!audio_valid_channel_count(channels))
throw std::runtime_error("Malformed BOS packet");
granulepos = 0;
skip = pre_skip;
assert(opus_decoder == nullptr);
assert(IsInitialized() == (output_buffer != nullptr));
@@ -177,8 +213,12 @@ MPDOpusDecoder::OnOggBeginning(const ogg_packet &packet)
client.Ready(audio_format, eos_granulepos > 0, duration);
frame_size = audio_format.GetFrameSize();
output_buffer = new opus_int16[opus_output_buffer_frames
* audio_format.channels];
if (output_buffer == nullptr)
/* note: if we ever support changing the channel count
in chained streams, we need to reallocate this
buffer instead of keeping it */
output_buffer = new opus_int16[opus_output_buffer_frames
* audio_format.channels];
auto cmd = client.GetCommand();
if (cmd != DecoderCommand::NONE)
@@ -205,6 +245,15 @@ MPDOpusDecoder::HandleTags(const ogg_packet &packet)
ReplayGainInfo rgi;
rgi.Clear();
/**
* Output gain is a Q7.8 fixed point number in dB that should be,
* applied unconditionally, but is often used specifically for
* ReplayGain. Add 5dB to compensate for the different
* reference levels between ReplayGain (89dB) and EBU R128 (-23 LUFS).
*/
rgi.track.gain = float(output_gain) / 256.0f + 5;
rgi.album.gain = float(output_gain) / 256.0f + 5;
TagBuilder tag_builder;
AddTagHandler h(tag_builder);
@@ -231,22 +280,58 @@ MPDOpusDecoder::HandleAudio(const ogg_packet &packet)
packet.bytes,
output_buffer, opus_output_buffer_frames,
0);
if (nframes < 0)
throw FormatRuntimeError("libopus error: %s",
opus_strerror(nframes));
if (nframes > 0) {
const size_t nbytes = nframes * frame_size;
auto cmd = client.SubmitData(input_stream,
output_buffer, nbytes,
0);
if (cmd != DecoderCommand::NONE)
throw cmd;
if (packet.granulepos > 0)
client.SubmitTimestamp(FloatDuration(packet.granulepos)
/ opus_sample_rate);
if (gcc_unlikely(nframes <= 0)) {
if (nframes < 0)
throw FormatRuntimeError("libopus error: %s",
opus_strerror(nframes));
else
return;
}
/* apply the "skip" value */
if (skip >= (unsigned)nframes) {
skip -= nframes;
AddGranulepos(nframes);
return;
}
const opus_int16 *data = output_buffer;
data += skip * previous_channels;
nframes -= skip;
AddGranulepos(skip);
skip = 0;
if (packet.e_o_s && packet.granulepos > 0 && granulepos >= 0) {
/* End Trimming (RFC7845 4.4): "The page with the 'end
of stream' flag set MAY have a granule position
that indicates the page contains less audio data
than would normally be returned by decoding up
through the final packet. This is used to end the
stream somewhere other than an even frame
boundary. [...] The remaining samples are
discarded. */
ogg_int64_t remaining = packet.granulepos - granulepos;
if (remaining <= 0)
return;
if (remaining < nframes)
nframes = remaining;
}
/* submit decoded samples to the DecoderClient */
const size_t nbytes = nframes * frame_size;
auto cmd = client.SubmitData(input_stream,
data, nbytes,
0);
if (cmd != DecoderCommand::NONE)
throw cmd;
if (packet.granulepos > 0) {
granulepos = packet.granulepos;
client.SubmitTimestamp(FloatDuration(granulepos - pre_skip)
/ opus_sample_rate);
} else
AddGranulepos(nframes);
}
bool
@@ -258,8 +343,20 @@ MPDOpusDecoder::Seek(uint64_t where_frame)
const ogg_int64_t where_granulepos(where_frame);
/* we don't know the exact granulepos after seeking, so let's
set it to -1 - it will be set after the next packet which
declares its granulepos */
granulepos = -1;
try {
SeekGranulePos(where_granulepos);
/* since all frame numbers are offset by the file's
pre-skip value, we need to apply it here as well;
we could just seek to "where_frame+pre_skip" as
well, but I think by decoding those samples and
discard them, we're safer */
skip = pre_skip;
return true;
} catch (...) {
return false;
@@ -302,13 +399,14 @@ mpd_opus_stream_decode(DecoderClient &client,
static bool
ReadAndParseOpusHead(OggSyncState &sync, OggStreamState &stream,
unsigned &channels)
unsigned &channels, signed &output_gain, unsigned &pre_skip)
{
ogg_packet packet;
return OggReadPacket(sync, stream, packet) && packet.b_o_s &&
IsOpusHead(packet) &&
ScanOpusHeader(packet.packet, packet.bytes, channels) &&
ScanOpusHeader(packet.packet, packet.bytes, channels,
output_gain, pre_skip) &&
audio_valid_channel_count(channels);
}
@@ -327,11 +425,12 @@ ReadAndVisitOpusTags(OggSyncState &sync, OggStreamState &stream,
static void
VisitOpusDuration(InputStream &is, OggSyncState &sync, OggStreamState &stream,
TagHandler &handler)
ogg_int64_t pre_skip, TagHandler &handler)
{
ogg_packet packet;
if (OggSeekFindEOS(sync, stream, packet, is)) {
if (OggSeekFindEOS(sync, stream, packet, is) &&
packet.granulepos >= pre_skip) {
const auto duration =
SongTime::FromScale<uint64_t>(packet.granulepos,
opus_sample_rate);
@@ -340,7 +439,7 @@ VisitOpusDuration(InputStream &is, OggSyncState &sync, OggStreamState &stream,
}
static bool
mpd_opus_scan_stream(InputStream &is, TagHandler &handler) noexcept
mpd_opus_scan_stream(InputStream &is, TagHandler &handler)
{
InputStreamReader reader(is);
OggSyncState oy(reader);
@@ -351,15 +450,16 @@ mpd_opus_scan_stream(InputStream &is, TagHandler &handler) noexcept
OggStreamState os(first_page);
unsigned channels;
if (!ReadAndParseOpusHead(oy, os, channels) ||
unsigned channels, pre_skip;
signed output_gain;
if (!ReadAndParseOpusHead(oy, os, channels, output_gain, pre_skip) ||
!ReadAndVisitOpusTags(oy, os, handler))
return false;
handler.OnAudioFormat(AudioFormat(opus_sample_rate,
SampleFormat::S16, channels));
VisitOpusDuration(is, oy, os, handler);
VisitOpusDuration(is, oy, os, pre_skip, handler);
return true;
}

@@ -18,6 +18,7 @@
*/
#include "OpusHead.hxx"
#include "util/ByteOrder.hxx"
#include <stdint.h>
@@ -31,12 +32,16 @@ struct OpusHead {
};
bool
ScanOpusHeader(const void *data, size_t size, unsigned &channels_r)
ScanOpusHeader(const void *data, size_t size, unsigned &channels_r,
signed &output_gain_r, unsigned &pre_skip_r)
{
const OpusHead *h = (const OpusHead *)data;
if (size < 19 || (h->version & 0xf0) != 0)
return false;
output_gain_r = FromLE16S(h->output_gain);
channels_r = h->channels;
pre_skip_r = FromLE16(h->pre_skip);
return true;
}

@@ -23,6 +23,7 @@
#include <stddef.h>
bool
ScanOpusHeader(const void *data, size_t size, unsigned &channels_r);
ScanOpusHeader(const void *data, size_t size, unsigned &channels_r,
signed &output_gain_r, unsigned &pre_skip_r);
#endif

@@ -53,7 +53,7 @@ ScanOneOpusTag(const char *name, const char *value,
char *endptr;
long l = strtol(value, &endptr, 10);
if (endptr > value && *endptr == 0)
rgi->track.gain = double(l) / 256.;
rgi->track.gain += float(l) / 256.0f;
} else if (rgi != nullptr &&
StringEqualsCaseASCII(name, "R128_ALBUM_GAIN")) {
/* R128_ALBUM_GAIN is a Q7.8 fixed point number in
@@ -62,7 +62,7 @@ ScanOneOpusTag(const char *name, const char *value,
char *endptr;
long l = strtol(value, &endptr, 10);
if (endptr > value && *endptr == 0)
rgi->album.gain = double(l) / 256.;
rgi->album.gain += float(l) / 256.0f;
}
handler.OnPair(name, value);

@@ -22,7 +22,7 @@
#include "CheckAudioFormat.hxx"
#include "pcm/PcmPack.hxx"
#include "input/InputStream.hxx"
#include "system/ByteOrder.hxx"
#include "util/ByteOrder.hxx"
#include "util/Domain.hxx"
#include "util/ByteReverse.hxx"
#include "util/StaticFifoBuffer.hxx"

@@ -35,7 +35,7 @@
#include "util/Domain.hxx"
#include "util/AllocatedString.hxx"
#include "util/CharUtil.hxx"
#include "system/ByteOrder.hxx"
#include "util/ByteOrder.hxx"
#include "Log.hxx"
#ifdef HAVE_SIDPLAYFP
@@ -115,7 +115,7 @@ sidplay_init(const ConfigBlock &block)
if (!database_path.IsNull())
songlength_database = sidplay_load_songlength_db(database_path);
default_songlength = block.GetPositiveValue("default_songlength", 0u);
default_songlength = block.GetPositiveValue("default_songlength", 0U);
all_files_are_containers =
block.GetBlockValue("all_files_are_containers", true);
@@ -387,7 +387,7 @@ sidplay_file_decode(DecoderClient &client, Path path_fs)
const unsigned timebase = player.timebase();
#endif
const unsigned end = duration.IsNegative()
? 0u
? 0U
: duration.ToScale<uint64_t>(timebase);
DecoderCommand cmd;

@@ -46,9 +46,7 @@ struct SndfileInputStream {
size_t Read(void *buffer, size_t size) {
/* libsndfile chokes on partial reads; therefore
always force full reads */
return decoder_read_full(client, is, buffer, size)
? size
: 0;
return decoder_read_much(client, is, buffer, size);
}
};
@@ -267,7 +265,7 @@ static constexpr struct {
};
static bool
sndfile_scan_stream(InputStream &is, TagHandler &handler) noexcept
sndfile_scan_stream(InputStream &is, TagHandler &handler)
{
SF_INFO info;

@@ -370,7 +370,7 @@ VisitVorbisDuration(InputStream &is,
}
static bool
vorbis_scan_stream(InputStream &is, TagHandler &handler) noexcept
vorbis_scan_stream(InputStream &is, TagHandler &handler)
{
/* initialize libogg */

@@ -614,7 +614,7 @@ wavpack_scan_file(Path path_fs, TagHandler &handler) noexcept
}
static bool
wavpack_scan_stream(InputStream &is, TagHandler &handler) noexcept
wavpack_scan_stream(InputStream &is, TagHandler &handler)
{
WavpackInput isp(nullptr, is);

@@ -25,9 +25,16 @@
#include "fs/AllocatedPath.hxx"
#include "fs/FileSystem.hxx"
#include "fs/Path.hxx"
#include "fs/NarrowPath.hxx"
#include "Log.hxx"
#include "PluginUnavailable.hxx"
#ifdef _WIN32
/* assume WildMidi is built as static library on Windows; without
this, linking to the static library would fail */
#define WILDMIDI_STATIC
#endif
extern "C" {
#include <wildmidi_lib.h>
}
@@ -53,7 +60,8 @@ wildmidi_init(const ConfigBlock &block)
AtScopeExit() { WildMidi_ClearError(); };
#endif
if (WildMidi_Init(path.c_str(), wildmidi_audio_format.sample_rate,
if (WildMidi_Init(NarrowPath(path),
wildmidi_audio_format.sample_rate,
0) != 0) {
#ifdef LIBWILDMIDI_VERSION
/* WildMidi_GetError() requires libwildmidi 0.4 */
@@ -96,7 +104,7 @@ wildmidi_file_decode(DecoderClient &client, Path path_fs)
midi *wm;
const struct _WM_Info *info;
wm = WildMidi_Open(path_fs.c_str());
wm = WildMidi_Open(NarrowPath(path_fs));
if (wm == nullptr)
return;
@@ -136,7 +144,7 @@ wildmidi_file_decode(DecoderClient &client, Path path_fs)
static bool
wildmidi_scan_file(Path path_fs, TagHandler &handler) noexcept
{
midi *wm = WildMidi_Open(path_fs.c_str());
midi *wm = WildMidi_Open(NarrowPath(path_fs));
if (wm == nullptr)
return false;

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