Compare commits

...

143 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
146 changed files with 2149 additions and 1033 deletions
NEWS
android
doc
meson.build
python/build
src
Log.cxxLog.hxxLogBackend.cxxLogBackend.hxxLogV.hxxPermission.cxxPluginUnavailable.hxxReplayGainGlobal.cxxReplayGainInfo.cxxSongUpdate.cxxTagFile.cxxTagFile.hxxTagStream.cxxTagStream.hxx
apple
archive
command
db
decoder
encoder
input
lib
mixer
neighbor
net
output
pcm
player
queue
storage
system
tag
unix
util
test
win32

45
NEWS

@@ -1,3 +1,48 @@
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

@@ -2,8 +2,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.musicpd"
android:installLocation="auto"
android:versionCode="47"
android:versionName="0.21.24">
android:versionCode="49"
android:versionName="0.21.26">
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="28"/>

@@ -38,7 +38,7 @@ author = 'Max Kellermann'
# built documents.
#
# The short X.Y version.
version = '0.21.24'
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

@@ -1,7 +1,7 @@
project(
'mpd',
['c', 'cpp'],
version: '0.21.24',
version: '0.21.26',
meson_version: '>= 0.49.0',
default_options: [
'c_std=c99',
@@ -319,6 +319,8 @@ subdir('src/thread')
subdir('src/net')
subdir('src/event')
subdir('src/apple')
subdir('src/lib/dbus')
subdir('src/lib/icu')
subdir('src/lib/smbclient')

@@ -10,8 +10,8 @@ from build.ffmpeg import FfmpegProject
from build.boost import BoostProject
libmpdclient = MesonProject(
'https://www.musicpd.org/download/libmpdclient/2/libmpdclient-2.18.tar.xz',
'4cb01e1f567e0169aca94875fb6e1200e7f5ce35b63a4df768ec1591fb1081fa',
'https://www.musicpd.org/download/libmpdclient/2/libmpdclient-2.19.tar.xz',
'158aad4c2278ab08e76a3f2b0166c99b39fae00ee17231bd225c5a36e977a189',
'lib/libmpdclient.a',
)
@@ -25,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',
@@ -148,8 +148,8 @@ gme = CmakeProject(
)
ffmpeg = FfmpegProject(
'http://ffmpeg.org/releases/ffmpeg-4.2.3.tar.xz',
'9df6c90aed1337634c1fb026fb01c154c29c82a64ea71291ff2da9aacb9aad31',
'http://ffmpeg.org/releases/ffmpeg-4.3.1.tar.xz',
'ad009240d46e307b4e03a213a0f49c11b650e445b1f8be0dda2a9212b34d2ffb',
'lib/libavcodec.a',
[
'--disable-shared', '--enable-static',
@@ -377,8 +377,8 @@ ffmpeg = FfmpegProject(
)
curl = AutotoolsProject(
'http://curl.haxx.se/download/curl-7.70.0.tar.xz',
'032f43f2674008c761af19bf536374128c16241fb234699a55f9fb603fcfbae7',
'http://curl.haxx.se/download/curl-7.72.0.tar.xz',
'0ded0808c4d85f2ee0db86980ae610cc9d165e9ca9da466196cc73c346513713',
'lib/libcurl.a',
[
'--disable-shared', '--enable-static',
@@ -433,7 +433,7 @@ libnfs = AutotoolsProject(
)
boost = BoostProject(
'https://dl.bintray.com/boostorg/release/1.73.0/source/boost_1_73_0.tar.bz2',
'4eb3b8d442b426dc35346235c8733b5ae35ba431690e38c6a8263dce9fcbb402',
'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=[],

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

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

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

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;

@@ -29,14 +29,15 @@
#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;
@@ -142,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
@@ -173,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

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

@@ -148,7 +148,7 @@ 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())

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

@@ -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,9 +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();
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,19 +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 =
@@ -50,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;
@@ -68,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;
@@ -145,20 +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 PathTraitsFS::IsSpecialFilename(path) ||
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;

@@ -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,11 +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()
{
@@ -597,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;

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

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

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

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

@@ -646,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);
@@ -680,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,8 +70,8 @@ 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);
}

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

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

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

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

@@ -77,9 +77,9 @@ PreparedLameEncoder::PreparedLameEncoder(const ConfigBlock &block)
if (value != nullptr) {
/* a quality was configured (VBR) */
quality = ParseDouble(value, &endptr);
quality = float(ParseDouble(value, &endptr));
if (*endptr != '\0' || quality < -1.0 || quality > 10.0)
if (*endptr != '\0' || quality < -1.0f || quality > 10.0f)
throw FormatRuntimeError("quality \"%s\" is not a number in the "
"range -1 to 10",
value);
@@ -111,13 +111,13 @@ static void
lame_encoder_setup(lame_global_flags *gfp, float quality, int bitrate,
const AudioFormat &audio_format)
{
if (quality >= -1.0) {
if (quality >= -1.0f) {
/* a quality was configured (VBR) */
if (0 != lame_set_VBR(gfp, vbr_rh))
throw std::runtime_error("error setting lame VBR mode");
if (0 != lame_set_VBR_q(gfp, quality))
if (0 != lame_set_VBR_q(gfp, int(quality)))
throw std::runtime_error("error setting lame VBR quality");
} else {
/* a bit rate was configured */

@@ -22,7 +22,7 @@
#include "AudioFormat.hxx"
#include "config/Domain.hxx"
#include "util/Alloc.hxx"
#include "system/ByteOrder.hxx"
#include "util/ByteOrder.hxx"
#include "util/StringUtil.hxx"
#include <opus.h>

@@ -95,9 +95,9 @@ PreparedTwolameEncoder::PreparedTwolameEncoder(const ConfigBlock &block)
if (value != nullptr) {
/* a quality was configured (VBR) */
quality = ParseDouble(value, &endptr);
quality = float(ParseDouble(value, &endptr));
if (*endptr != '\0' || quality < -1.0 || quality > 10.0)
if (*endptr != '\0' || quality < -1.0f || quality > 10.0f)
throw FormatRuntimeError("quality \"%s\" is not a number in the "
"range -1 to 10",
value);
@@ -132,7 +132,7 @@ static void
twolame_encoder_setup(twolame_options *options, float quality, int bitrate,
const AudioFormat &audio_format)
{
if (quality >= -1.0) {
if (quality >= -1.0f) {
/* a quality was configured (VBR) */
if (0 != twolame_set_VBR(options, true))

@@ -84,7 +84,7 @@ PreparedVorbisEncoder::PreparedVorbisEncoder(const ConfigBlock &block)
char *endptr;
quality = ParseDouble(value, &endptr);
if (*endptr != '\0' || quality < -1.0 || quality > 10.0)
if (*endptr != '\0' || quality < -1.0f || quality > 10.0f)
throw FormatRuntimeError("quality \"%s\" is not a number in the "
"range -1 to 10",
value);
@@ -122,13 +122,13 @@ VorbisEncoder::VorbisEncoder(float quality, int bitrate,
_audio_format.format = SampleFormat::FLOAT;
audio_format = _audio_format;
if (quality >= -1.0) {
if (quality >= -1.0f) {
/* a quality was configured (VBR) */
if (0 != vorbis_encode_init_vbr(&vi,
audio_format.channels,
audio_format.sample_rate,
quality * 0.1)) {
quality * 0.1f)) {
vorbis_info_clear(&vi);
throw std::runtime_error("error initializing vorbis vbr");
}
@@ -138,7 +138,7 @@ VorbisEncoder::VorbisEncoder(float quality, int bitrate,
if (0 != vorbis_encode_init(&vi,
audio_format.channels,
audio_format.sample_rate, -1.0,
bitrate * 1000, -1.0)) {
bitrate * 1000, -1.0f)) {
vorbis_info_clear(&vi);
throw std::runtime_error("error initializing vorbis encoder");
}

@@ -19,7 +19,7 @@
#include "WaveEncoderPlugin.hxx"
#include "../EncoderAPI.hxx"
#include "system/ByteOrder.hxx"
#include "util/ByteOrder.hxx"
#include "util/DynamicFifoBuffer.hxx"
#include <assert.h>

@@ -58,6 +58,11 @@ input_stream_global_init(const ConfigData &config, EventLoop &event_loop)
if (plugin->init != nullptr)
plugin->init(event_loop, *block);
input_plugins_enabled[i] = true;
} catch (const PluginUnconfigured &e) {
LogFormat(LogLevel::DEBUG, e,
"Input plugin '%s' is not configured",
plugin->name);
continue;
} catch (const PluginUnavailable &e) {
FormatError(e,
"Input plugin '%s' is unavailable",

@@ -305,18 +305,18 @@ ConfigureCapture(snd_pcm_t *capture_handle,
snd_pcm_hw_params_get_buffer_size_min(hw_params, &buffer_size_min);
snd_pcm_hw_params_get_buffer_size_max(hw_params, &buffer_size_max);
unsigned buffer_time_min, buffer_time_max;
snd_pcm_hw_params_get_buffer_time_min(hw_params, &buffer_time_min, 0);
snd_pcm_hw_params_get_buffer_time_max(hw_params, &buffer_time_max, 0);
snd_pcm_hw_params_get_buffer_time_min(hw_params, &buffer_time_min, nullptr);
snd_pcm_hw_params_get_buffer_time_max(hw_params, &buffer_time_max, nullptr);
FormatDebug(alsa_input_domain, "buffer: size=%u..%u time=%u..%u",
(unsigned)buffer_size_min, (unsigned)buffer_size_max,
buffer_time_min, buffer_time_max);
snd_pcm_uframes_t period_size_min, period_size_max;
snd_pcm_hw_params_get_period_size_min(hw_params, &period_size_min, 0);
snd_pcm_hw_params_get_period_size_max(hw_params, &period_size_max, 0);
snd_pcm_hw_params_get_period_size_min(hw_params, &period_size_min, nullptr);
snd_pcm_hw_params_get_period_size_max(hw_params, &period_size_max, nullptr);
unsigned period_time_min, period_time_max;
snd_pcm_hw_params_get_period_time_min(hw_params, &period_time_min, 0);
snd_pcm_hw_params_get_period_time_max(hw_params, &period_time_max, 0);
snd_pcm_hw_params_get_period_time_min(hw_params, &period_time_min, nullptr);
snd_pcm_hw_params_get_period_time_max(hw_params, &period_time_max, nullptr);
FormatDebug(alsa_input_domain, "period: size=%u..%u time=%u..%u",
(unsigned)period_size_min, (unsigned)period_size_max,
period_time_min, period_time_max);

@@ -30,7 +30,7 @@
#include "util/StringCompare.hxx"
#include "util/RuntimeError.hxx"
#include "util/Domain.hxx"
#include "system/ByteOrder.hxx"
#include "util/ByteOrder.hxx"
#include "fs/AllocatedPath.hxx"
#include "Log.hxx"
#include "config/Block.hxx"

@@ -139,8 +139,9 @@ FfmpegInputStream::Seek(offset_type new_offset)
static constexpr const char *ffmpeg_prefixes[] = {
"gopher://",
"hls+http://",
"hls+https://",
"rtp://",
"rtsp://",
"rtmp://",
"rtmpt://",
"rtmps://",

@@ -26,6 +26,8 @@
#include "system/FileDescriptor.hxx"
#include "util/RuntimeError.hxx"
#include <cinttypes> // for PRIu64 (PRIoffset)
#include <sys/stat.h>
#include <fcntl.h>
@@ -94,6 +96,11 @@ FileInputStream::Read(void *ptr, size_t read_size)
nbytes = reader.Read(ptr, read_size);
}
if (nbytes == 0 && !IsEOF())
throw FormatRuntimeError("Unexpected end of file %s"
" at %" PRIoffset " of %" PRIoffset,
GetURI(), GetOffset(), GetSize());
offset += nbytes;
return nbytes;
}

@@ -133,11 +133,11 @@ InitQobuzInput(EventLoop &event_loop, const ConfigBlock &block)
const char *app_id = block.GetBlockValue("app_id");
if (app_id == nullptr)
throw PluginUnavailable("No Qobuz app_id configured");
throw PluginUnconfigured("No Qobuz app_id configured");
const char *app_secret = block.GetBlockValue("app_secret");
if (app_secret == nullptr)
throw PluginUnavailable("No Qobuz app_secret configured");
throw PluginUnconfigured("No Qobuz app_secret configured");
const char *device_manufacturer_id = block.GetBlockValue("device_manufacturer_id",
"df691fdc-fa36-11e7-9718-635337d7df8f");
@@ -145,11 +145,11 @@ InitQobuzInput(EventLoop &event_loop, const ConfigBlock &block)
const char *username = block.GetBlockValue("username");
const char *email = block.GetBlockValue("email");
if (username == nullptr && email == nullptr)
throw PluginUnavailable("No Qobuz username configured");
throw PluginUnconfigured("No Qobuz username configured");
const char *password = block.GetBlockValue("password");
if (password == nullptr)
throw PluginUnavailable("No Qobuz password configured");
throw PluginUnconfigured("No Qobuz password configured");
const char *format_id = block.GetBlockValue("format_id", "5");

@@ -170,15 +170,15 @@ InitTidalInput(EventLoop &event_loop, const ConfigBlock &block)
const char *token = block.GetBlockValue("token");
if (token == nullptr)
throw PluginUnavailable("No Tidal application token configured");
throw PluginUnconfigured("No Tidal application token configured");
const char *username = block.GetBlockValue("username");
if (username == nullptr)
throw PluginUnavailable("No Tidal username configured");
throw PluginUnconfigured("No Tidal username configured");
const char *password = block.GetBlockValue("password");
if (password == nullptr)
throw PluginUnavailable("No Tidal password configured");
throw PluginUnconfigured("No Tidal password configured");
FormatWarning(tidal_domain, "The Tidal input plugin is deprecated because Tidal has changed the protocol and doesn't share documentation");

@@ -19,7 +19,7 @@
#include "HwSetup.hxx"
#include "Format.hxx"
#include "system/ByteOrder.hxx"
#include "util/ByteOrder.hxx"
#include "util/Domain.hxx"
#include "util/RuntimeError.hxx"
#include "AudioFormat.hxx"
@@ -238,18 +238,18 @@ SetupHw(snd_pcm_t *pcm,
snd_pcm_hw_params_get_buffer_size_min(hwparams, &buffer_size_min);
snd_pcm_hw_params_get_buffer_size_max(hwparams, &buffer_size_max);
unsigned buffer_time_min, buffer_time_max;
snd_pcm_hw_params_get_buffer_time_min(hwparams, &buffer_time_min, 0);
snd_pcm_hw_params_get_buffer_time_max(hwparams, &buffer_time_max, 0);
snd_pcm_hw_params_get_buffer_time_min(hwparams, &buffer_time_min, nullptr);
snd_pcm_hw_params_get_buffer_time_max(hwparams, &buffer_time_max, nullptr);
FormatDebug(alsa_output_domain, "buffer: size=%u..%u time=%u..%u",
(unsigned)buffer_size_min, (unsigned)buffer_size_max,
buffer_time_min, buffer_time_max);
snd_pcm_uframes_t period_size_min, period_size_max;
snd_pcm_hw_params_get_period_size_min(hwparams, &period_size_min, 0);
snd_pcm_hw_params_get_period_size_max(hwparams, &period_size_max, 0);
snd_pcm_hw_params_get_period_size_min(hwparams, &period_size_min, nullptr);
snd_pcm_hw_params_get_period_size_max(hwparams, &period_size_max, nullptr);
unsigned period_time_min, period_time_max;
snd_pcm_hw_params_get_period_time_min(hwparams, &period_time_min, 0);
snd_pcm_hw_params_get_period_time_max(hwparams, &period_time_max, 0);
snd_pcm_hw_params_get_period_time_min(hwparams, &period_time_min, nullptr);
snd_pcm_hw_params_get_period_time_max(hwparams, &period_time_max, nullptr);
FormatDebug(alsa_output_domain, "period: size=%u..%u time=%u..%u",
(unsigned)period_size_min, (unsigned)period_size_max,
period_time_min, period_time_max);

@@ -62,6 +62,6 @@ FfmpegLogCallback(gcc_unused void *ptr, int level, const char *fmt, va_list vl)
ffmpeg_domain.GetName(),
cls->item_name(ptr));
const Domain d(domain);
LogFormatV(d, FfmpegImportLogLevel(level), fmt, vl);
LogFormatV(FfmpegImportLogLevel(level), d, fmt, vl);
}
}

@@ -18,7 +18,7 @@ if icu_dep.found()
'Init.cxx',
]
elif not get_option('iconv').disabled()
have_iconv = compiler.has_function('iconv')
have_iconv = compiler.has_function('iconv', prefix : '#include <iconv.h>')
conf.set('HAVE_ICONV', have_iconv)
if not have_iconv and get_option('iconv').enabled()
error('iconv() not available')

@@ -60,7 +60,7 @@ ContentDirectoryService::getSearchCapabilities(UpnpClient_Handle hdl) const
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));

@@ -27,7 +27,7 @@
MixerType
mixer_type_parse(const char *input)
{
assert(input != NULL);
assert(input != nullptr);
if (strcmp(input, "none") == 0 || strcmp(input, "disabled") == 0)
return MixerType::NONE;

@@ -51,7 +51,7 @@ public:
double _volume_scale_factor)
:Mixer(pulse_mixer_plugin, _listener),
output(_output),
volume_scale_factor(_volume_scale_factor)
volume_scale_factor(float(_volume_scale_factor))
{
}
@@ -175,7 +175,7 @@ parse_volume_scale_factor(const char *value) {
char *endptr;
float factor = ParseFloat(value, &endptr);
if (endptr == value || *endptr != '\0' || factor < 0.5 || factor > 5.0)
if (endptr == value || *endptr != '\0' || factor < 0.5f || factor > 5.0f)
throw FormatRuntimeError("\"%s\" is not a number in the "
"range 0.5 to 5.0",
value);
@@ -190,7 +190,7 @@ pulse_mixer_init(gcc_unused EventLoop &event_loop, AudioOutput &ao,
{
PulseOutput &po = (PulseOutput &)ao;
float scale = parse_volume_scale_factor(block.GetBlockValue("scale_volume"));
PulseMixer *pm = new PulseMixer(po, listener, scale);
auto *pm = new PulseMixer(po, listener, (double)scale);
pulse_output_set_mixer(po, *pm);
@@ -216,7 +216,7 @@ PulseMixer::GetVolume()
int
PulseMixer::GetVolumeInternal()
{
pa_volume_t max_pa_volume = volume_scale_factor * PA_VOLUME_NORM;
pa_volume_t max_pa_volume = pa_volume_t(volume_scale_factor * PA_VOLUME_NORM);
return online ?
(int)((100 * (pa_cvolume_avg(&volume) + 1)) / max_pa_volume)
: -1;
@@ -230,7 +230,7 @@ PulseMixer::SetVolume(unsigned new_volume)
if (!online)
throw std::runtime_error("disconnected");
pa_volume_t max_pa_volume = volume_scale_factor * PA_VOLUME_NORM;
pa_volume_t max_pa_volume = pa_volume_t(volume_scale_factor * PA_VOLUME_NORM);
struct pa_cvolume cvolume;
pa_cvolume_set(&cvolume, volume.channels,

@@ -22,8 +22,8 @@
#include "filter/plugins/VolumeFilterPlugin.hxx"
#include "pcm/Volume.hxx"
#include <assert.h>
#include <math.h>
#include <cassert>
#include <cmath>
class SoftwareMixer final : public Mixer {
Filter *filter = nullptr;
@@ -70,13 +70,13 @@ PercentVolumeToSoftwareVolume(unsigned volume) noexcept
{
assert(volume <= 100);
if (volume >= 100)
if (volume == 100)
return PCM_VOLUME_1;
else if (volume > 0)
return pcm_float_to_volume((exp(volume / 25.0) - 1) /
return pcm_float_to_volume((std::exp(volume / 25.0f) - 1) /
(54.5981500331F - 1));
else
return 0;
return 0;
}
void

@@ -103,11 +103,6 @@ NeighborExplorer::List
SmbclientNeighborExplorer::GetList() const noexcept
{
const std::lock_guard<Mutex> protect(mutex);
/*
List list;
for (const auto &i : servers)
list.emplace_front(i.Export());
*/
return list;
}

@@ -47,6 +47,11 @@ ToNeighborInfo(const UDisks2::Object &o) noexcept
return {o.GetUri(), o.path};
}
static constexpr char udisks_neighbor_match[] =
"type='signal',sender='" UDISKS2_INTERFACE "',"
"interface='" DBUS_OM_INTERFACE "',"
"path='" UDISKS2_PATH "'";
class UdisksNeighborExplorer final
: public NeighborExplorer {
@@ -108,26 +113,37 @@ UdisksNeighborExplorer::DoOpen()
auto &connection = GetConnection();
/* this ugly try/catch cascade is only here because this
method has no RAII for this method - TODO: improve this */
try {
Error error;
dbus_bus_add_match(connection,
"type='signal',sender='" UDISKS2_INTERFACE "',"
"interface='" DBUS_OM_INTERFACE "',"
"path='" UDISKS2_PATH "'",
error);
dbus_bus_add_match(connection, udisks_neighbor_match, error);
error.CheckThrow("DBus AddMatch error");
dbus_connection_add_filter(connection,
HandleMessage, this,
nullptr);
try {
dbus_connection_add_filter(connection,
HandleMessage, this,
nullptr);
auto msg = Message::NewMethodCall(UDISKS2_INTERFACE,
UDISKS2_PATH,
DBUS_OM_INTERFACE,
"GetManagedObjects");
list_request.Send(connection, *msg.Get(),
std::bind(&UdisksNeighborExplorer::OnListNotify,
this, std::placeholders::_1));
try {
auto msg = Message::NewMethodCall(UDISKS2_INTERFACE,
UDISKS2_PATH,
DBUS_OM_INTERFACE,
"GetManagedObjects");
list_request.Send(connection, *msg.Get(),
std::bind(&UdisksNeighborExplorer::OnListNotify,
this, std::placeholders::_1));
} catch (...) {
dbus_connection_remove_filter(connection,
HandleMessage,
this);
throw;
}
} catch (...) {
dbus_bus_remove_match(connection,
udisks_neighbor_match, nullptr);
throw;
}
} catch (...) {
dbus_glue.Destruct();
throw;
@@ -147,8 +163,10 @@ UdisksNeighborExplorer::DoClose() noexcept
list_request.Cancel();
}
// TODO: remove_match
// TODO: remove_filter
auto &connection = GetConnection();
dbus_connection_remove_filter(connection, HandleMessage, this);
dbus_bus_remove_match(connection, udisks_neighbor_match, nullptr);
dbus_glue.Destruct();
}

@@ -31,7 +31,7 @@
#define IPV4_ADDRESS_HXX
#include "SocketAddress.hxx"
#include "system/ByteOrder.hxx"
#include "util/ByteOrder.hxx"
#include <stdint.h>

@@ -31,7 +31,7 @@
#define IPV6_ADDRESS_HXX
#include "SocketAddress.hxx"
#include "system/ByteOrder.hxx"
#include "util/ByteOrder.hxx"
#include "util/Compiler.h"
#include <stdint.h>

@@ -39,6 +39,7 @@
#include "config/Option.hxx"
#include "config/Block.hxx"
#include "util/RuntimeError.hxx"
#include "util/StringAPI.hxx"
#include "util/StringFormat.hxx"
#include "Log.hxx"
@@ -214,7 +215,7 @@ FilteredAudioOutput::Setup(EventLoop &event_loop,
const char *replay_gain_handler =
block.GetBlockValue("replay_gain_handler", "software");
if (strcmp(replay_gain_handler, "none") != 0) {
if (!StringIsEqual(replay_gain_handler, "none")) {
prepared_replay_gain_filter =
NewReplayGainFilter(replay_gain_config);
assert(prepared_replay_gain_filter != nullptr);
@@ -240,14 +241,14 @@ FilteredAudioOutput::Setup(EventLoop &event_loop,
/* use the hardware mixer for replay gain? */
if (strcmp(replay_gain_handler, "mixer") == 0) {
if (StringIsEqual(replay_gain_handler, "mixer")) {
if (mixer != nullptr)
replay_gain_filter_set_mixer(*prepared_replay_gain_filter,
mixer, 100);
else
FormatError(output_domain,
"No such mixer for output '%s'", name);
} else if (strcmp(replay_gain_handler, "software") != 0 &&
} else if (!StringIsEqual(replay_gain_handler, "software") &&
prepared_replay_gain_filter != nullptr) {
throw std::runtime_error("Invalid \"replay_gain_handler\" value");
}

@@ -28,6 +28,7 @@
#include "config/Data.hxx"
#include "config/Option.hxx"
#include "util/RuntimeError.hxx"
#include "util/StringAPI.hxx"
#include <stdexcept>
@@ -147,7 +148,7 @@ AudioOutputControl *
MultipleOutputs::FindByName(const char *name) noexcept
{
for (auto *i : outputs)
if (strcmp(i->GetName(), name) == 0)
if (StringIsEqual(i->GetName(), name))
return i;
return nullptr;

@@ -38,8 +38,7 @@
#include "plugins/sles/SlesOutputPlugin.hxx"
#include "plugins/SolarisOutputPlugin.hxx"
#include "plugins/WinmmOutputPlugin.hxx"
#include <string.h>
#include "util/StringAPI.hxx"
const AudioOutputPlugin *const audio_output_plugins[] = {
#ifdef HAVE_SHOUT
@@ -101,7 +100,7 @@ const AudioOutputPlugin *
AudioOutputPlugin_get(const char *name)
{
audio_output_plugins_for_each(plugin)
if (strcmp(plugin->name, name) == 0)
if (StringIsEqual(plugin->name, name))
return plugin;
return nullptr;

@@ -187,7 +187,7 @@ AudioOutputSource::FilterChunk(const MusicChunk &chunk)
only if the mix ratio is non-negative; a
negative mix ratio is a MixRamp special
case */
mix_ratio = 1.0 - mix_ratio;
mix_ratio = 1.0f - mix_ratio;
void *dest = cross_fade_buffer.Get(other_data.size);
memcpy(dest, other_data.data, other_data.size);

@@ -70,7 +70,7 @@ audio_output_state_read(const char *line, MultipleOutputs &outputs)
name = endptr + 1;
auto *ao = outputs.FindByName(name);
if (ao == NULL) {
if (ao == nullptr) {
FormatDebug(output_domain,
"Ignoring device state for '%s'", name);
return true;

@@ -939,7 +939,7 @@ AlsaOutput::Play(const void *chunk, size_t size)
assert(size % in_frame_size == 0);
const auto e = pcm_export->Export({chunk, size});
if (e.size == 0)
if (e.empty())
/* the DoP (DSD over PCM) filter converts two frames
at a time and ignores the last odd frame; if there
was only one frame (e.g. the last frame in the

@@ -25,12 +25,11 @@
#include "util/SplitString.hxx"
#include "util/RuntimeError.hxx"
#include "util/Domain.hxx"
#include "util/StringAPI.hxx"
#include "Log.hxx"
#include <ao/ao.h>
#include <string.h>
/* An ao_sample_format, with all fields set to zero: */
static ao_sample_format OUR_AO_FORMAT_INITIALIZER;
@@ -105,7 +104,7 @@ AoOutput::AoOutput(const ConfigBlock &block)
write_size(block.GetPositiveValue("write_size", 1024U))
{
const char *value = block.GetBlockValue("driver", "default");
if (0 == strcmp(value, "default"))
if (StringIsEqual(value, "default"))
driver = ao_default_driver_id();
else
driver = ao_driver_id(value);

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