Compare commits

..

215 Commits

Author SHA1 Message Date
Max Kellermann
feac1a3f56 release v0.23.14 2023-10-08 10:11:05 +02:00
Max Kellermann
f3c37e484e tag/Mask: add static_assert on the size of the mask 2023-10-08 10:01:00 +02:00
Max Kellermann
49130c2018 python/build/openssl: remove obsolete variable 2023-09-26 15:32:49 +02:00
Max Kellermann
94af199c49 python/build/openssl: add --cross-compile-prefix=... 2023-09-26 15:31:21 +02:00
Max Kellermann
2d25f6f57f python/build/openssl: pass arch only if cross-compiling 2023-09-26 15:31:11 +02:00
Max Kellermann
cf179ec294 python/build/openssl: pass --libdir=lib to Configure
Without this, the AMD64 build installs to "lib64/" which then cannot
be found by CURL.
2023-09-26 15:30:51 +02:00
Max Kellermann
4d6f220a2f python/build/cmake: write toolchain file only if cross-compiling 2023-09-26 15:29:45 +02:00
Max Kellermann
0ffbe5b5ea python/build/autotools: add --host=... only if cross-compiling 2023-09-26 15:27:05 +02:00
Max Kellermann
5b83c834ac python/build/meson: write cross-file only if cross-compiling 2023-09-26 15:26:17 +02:00
Max Kellermann
da7f32bddb python/build/toolchain: rename arch to host_triplet 2023-09-26 15:25:23 +02:00
Max Kellermann
9a5eac4ea9 python/build/toolchain: set arch=llvm_triple 2023-09-26 15:13:28 +02:00
Max Kellermann
6571b5d118 python/build/openssl: add option "no-makedepend"
We do not need "make" dependencies for one-time builds.
2023-09-26 15:05:14 +02:00
Max Kellermann
12dff8e382 python/build/openssl: use no-asm only on Windows 2023-09-26 15:03:50 +02:00
Max Kellermann
c4da87a0cb python/build/openssl: add configure variable 2023-09-26 15:03:24 +02:00
Max Kellermann
446f8f29d3 python/build/openssl: add Darwin archs 2023-09-26 15:02:02 +02:00
Max Kellermann
48cc76f114 python/build/toolchain: add is_android, is_darwin 2023-09-26 14:49:39 +02:00
Max Kellermann
a0892b852e build/python/autotools: add option per_arch_cflags 2023-09-26 14:33:24 +02:00
Max Kellermann
485c7805eb python/build/autotools: use list.extend() to append configure_args 2023-09-26 14:31:30 +02:00
Max Kellermann
23802f4489 python/build/tarball: Python type hints 2023-09-26 14:28:23 +02:00
Max Kellermann
3fedd978a2 python/build/meson: disable ccache because Meson detects it automatically 2023-09-26 14:27:43 +02:00
Max Kellermann
a9f1bed922 build/python/cmake: add cast to fix mypy warning 2023-09-26 14:25:51 +02:00
Max Kellermann
eb23788fec python/build: add support for fallback download URLs 2023-09-26 14:25:51 +02:00
Max Kellermann
f6d73555a6 python/build/libs: update OpenSSL toi 3.1.3 2023-09-26 14:21:43 +02:00
Max Kellermann
a56a709406 python/build/download: relative imports 2023-09-26 14:15:01 +02:00
Max Kellermann
5f253e66f6 python/build/toolchain.py: add AnyToolchain for type hints 2023-09-26 12:54:58 +02:00
Max Kellermann
4669f7e2b9 {android,win32}/build.py: move Toolchain classes to python/build/toolchain.py 2023-09-26 12:47:02 +02:00
Max Kellermann
4c90f88704 win32: rename CrossGccToolchain to MingwToolchain 2023-09-26 12:46:10 +02:00
Max Kellermann
a7213b78d6 win32/build.py: move code to class CrossGccToolchain 2023-09-26 12:20:53 +02:00
Max Kellermann
719333e16e android/build.py: move code to class AndroidNdkToolchain 2023-09-26 12:18:18 +02:00
Max Kellermann
100e471b49 android/build.py: remove duplicate import 2023-09-26 12:12:00 +02:00
Max Kellermann
3f2016e552 python: add type hints 2023-09-26 12:04:08 +02:00
Max Kellermann
dd89ea4505 android/AndroidManifest.xml: raise minSdkVersion to 24
This is needed to build libFLAC which uses ftello().
2023-09-26 12:04:05 +02:00
Max Kellermann
101e12cf9a modplug: add patch to remove the deprecated register keyword 2023-09-26 11:48:44 +02:00
Max Kellermann
f382808450 python/build/libs.py: update CURL to 8.2.1 2023-09-26 11:48:16 +02:00
Max Kellermann
0cbe3c2a93 python/build/libs.py: update OpenSSL to 3.1.2 2023-09-26 11:48:13 +02:00
Max Kellermann
4f0ae28359 python/build/libs.py: update zlib to 1.3 2023-09-26 11:48:09 +02:00
Max Kellermann
6a4250f485 python/build/libs.py: update Opus to 1.4 2023-09-26 11:48:06 +02:00
Max Kellermann
3322b29e6a python/build/libs.py: update FLAC to 1.4.3 2023-09-26 11:48:02 +02:00
naglis
33ac472601 doc/plugins.rst: change command to list PipeWire targets
The `dump` command was dropped[1] in favor of other tools.

[1]: 50bdebe4e8
2023-09-26 11:41:37 +02:00
Simon Arlott
561d6fd478 meson: Use correct prefix for systemd_system_unit_dir
systemd uses "rootprefix", not "prefix" for this value

059b1b31ad/src/core/systemd.pc.in (L23)
2023-06-02 14:33:13 +02:00
Naïm Favier
42a01822bf meson: use correct prefix for systemd dirs
See https://www.bassi.io/articles/2018/03/15/pkg-config-and-paths/

Fixes the build in nixpkgs
2023-06-02 14:33:09 +02:00
Shen-Ta Hsieh
38f1237d49 output/wasapi: cast to const char * for fmt 10 compatible 2023-06-02 14:29:43 +02:00
Shen-Ta Hsieh
8df77122e5 python/build/libs.py: use right cmake variable to disable SDL 2023-06-02 14:28:22 +02:00
Shen-Ta Hsieh
fef6b9df80 flac: Try InputStream interface if flac failed to read through a wchar_t path 2023-06-02 14:28:22 +02:00
Shen-Ta Hsieh
d52eac66db doc/mpdconf.example: add hardware mixer example config for wasapi 2023-06-02 14:27:24 +02:00
Shen-Ta Hsieh
70879f0abc thread/WindowsFuture: remove wrong address_of operator 2023-06-02 14:27:24 +02:00
Shen-Ta Hsieh
bcb393628e win32/ComWorker: rename variable name to prevent ambiguous 2023-06-02 14:22:11 +02:00
Max Kellermann
18d3a5c12b decoder/flac: add noexcept and inline 2023-06-02 14:22:11 +02:00
Simon Arlott
6ee3d0102b decoder/mad: Fix decode of LAME peak value
6d91b5c7b2 ("fix double promotions") changed
how LAME peak values are decoded, producing large incorrect values that
cause some MP3 files to play silently.

Restore the original decode from MAD fixed-point format to double and
document what it's doing.

Fixes 
2023-06-02 14:15:48 +02:00
Max Kellermann
fc9626e2f4 increment version number to 0.23.14 2023-06-02 14:15:14 +02:00
Simon Arlott
3bedd94fc8 doc: Fix syntax error
With sphinx-build 5.0.0:
doc/user.rst:728: ERROR: Unexpected indentation.
doc/user.rst:731: ERROR: Unexpected indentation.
2023-06-02 14:10:38 +02:00
Max Kellermann
8842650c33 release v0.23.13 2023-05-22 19:46:38 +02:00
Max Kellermann
d5bf128cee storage/curl: throw HttpStatusError 2023-05-22 19:29:46 +02:00
Max Kellermann
5cd86e272f input/curl: disable CURLOPT_FAILONERROR
Let OnHeaders() check the status.

The status checking code was added by commit 4f021cbced in 2011,
but in 2008, commit a8e81326d0 enabled `CURLOPT_FAILONERROR`, which
means the status checking code never had any effect.

This allows `LoadExcludeListOrLog()` to hide boring "404 Not Found"
log messages via `IsFileNotFound()`.
2023-05-22 19:03:12 +02:00
Max Kellermann
740cbe9e02 event/Loop: remove failing assert()
The `assert(!quit)` can fail if the `EventThread` gets stopped before
it enters `EventLoop::Run()`. There is a similar problem with `alive`,
which gets reset by `EventThread::Stop()`.

If that happens, then `EventLoop::Run()` should return immediately
without handling any events.
2023-05-22 18:14:25 +02:00
Max Kellermann
ed890a273a doc/user.rst: document the replaygain_missing_preamp setting
Closes https://github.com/MusicPlayerDaemon/MPD/issues/1785
2023-05-22 16:17:05 +02:00
Max Kellermann
068cd559e1 db/update/Walk: clear Song::in_playlist
Without clearing all `in_playlist` flags, the songs will never be
revealed again if they were hidden once by a CUE sheet, not even after
the CUE sheet gets deleted or modified.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/1784
2023-05-22 15:41:58 +02:00
Max Kellermann
dc127f39a7 util/ScopeExit: use std::declval()
Fixes GCC 10 error:

 error: cannot call member function `Foo` without object
2023-05-22 15:01:25 +02:00
Max Kellermann
7a99a7008c util/ScopeExit: use std::exchange() 2023-05-22 14:44:45 +02:00
Max Kellermann
70b451db7b util/ScopeExit: add noexcept 2023-05-22 14:44:17 +02:00
Max Kellermann
2ab03a0914 util/ScopeExit: allow the function to throw
Fixes crash inside AtScopeExit() in the WASAPI output plugin.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/1759
2023-05-22 14:43:23 +02:00
Max Kellermann
2fa8c7d2db lib/crypto/meson.build: link with ffmpeg_util_dep
This adds `MakeFfmpegError()` to the executable and fixes a linker
failure when `libavutil` is available, but `libavformat` and
`libavcodec` are not.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/1786
2023-05-22 14:05:13 +02:00
Max Kellermann
7c759ba8b0 lib/ffmpeg/meson.build: move libavutil helpers into separate library 2023-05-22 14:03:47 +02:00
Max Kellermann
6d9b452fde lib/ffmpeg/LogError: remove unused library 2023-05-22 13:48:33 +02:00
Michiel Beijen
f7eb1c9a83 Fix meson build warning for get_pkgconfig_variable
Otherwise, building will generate these warnings:

systemd/system/meson.build:5: WARNING: Project targeting '>= 0.56.0' but tried to use feature deprecated since '0.56.0': Dependency.get_pkgconfig_variable. use Dependency.get_variable(pkgconfig : ...) instead
systemd/user/meson.build:5: WARNING: Project targeting '>= 0.56.0' but tried to use feature deprecated since '0.56.0': Dependency.get_pkgconfig_variable. use Dependency.get_variable(pkgconfig : ...) instead
2023-05-21 21:07:14 +02:00
Max Kellermann
2d22e6dee4 subprojects: update sqlite to 3.41.2-2 2023-05-21 21:05:17 +02:00
Max Kellermann
4587bf759d subprojects: update expat to 2.5.0-2 2023-05-21 21:04:56 +02:00
Max Kellermann
e1e37cfe3c TagPrint, command/File: two more libfmt 10 workarounds
libfmt 10 doesn't know how to format a StringView, and doesn't cast to
std::string_view anymore.  The StringView class has been removed from
MPD 0.24 completely, and this is a stable-branch-only workaround.

Closes https://github.com/MusicPlayerDaemon/MPD/pull/1814
2023-05-21 21:03:20 +02:00
latex
381934985a reorder ffmpeg to be lower priority than gme
This should prevent ffmpeg from taking priority over the gme plugin.
The ffmpeg plugin is more buggy than gme.
One of the prominent bugs of preferring ffmpeg over gme is that ffmpeg
cannot seek SAP files while gme can. This should prevent that from
happening.
2023-05-21 20:58:53 +02:00
Max Kellermann
a8042885ac TimePrint: minor fixup for libfmt 10
libfmt version 10 has difficulties formatting a `StringBuffer`, and we
need to help it by explicitly invoking the `c_str()` method.
2023-05-21 20:58:19 +02:00
Max Kellermann
a71e68db50 command/player, SongPrint: use AudioFormatFormatter()
libfmt version 10 apparently doesn't know how to format a
`StringBuffer`, failing the MPD build.  Since we have a formatter
specialization for `AudioFormat`, let's use that - it's better and
easier to use.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/1807
2023-05-21 20:57:59 +02:00
Max Kellermann
1417578b3d db/update/Archive: validate directory names
Fixes assertion failure if the ZIP file contains a path that begins
with a slash.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/1793
2023-05-21 20:57:38 +02:00
Max Kellermann
96befa138c db/update/Archive: ignore filenames with newline character 2023-05-21 20:56:38 +02:00
Max Kellermann
16a99804de db/update/Archive: move check to IsAcceptableFilename() 2023-05-21 20:56:32 +02:00
Max Kellermann
75a39ed279 db/update/Archive: remove useless log message 2023-05-21 20:55:59 +02:00
Benjamin Cremer
4d357ab77c Fix syntax error in mpdconf.example 2023-05-21 20:51:57 +02:00
datasone
d4f3dd49b4 db/SimpleDatabasePlugin: store in_playlist value of songs into database
Fixes hide_playlist_targets not working after server restart

Currently, `hide_playlists_targets` works by skipping songs with
`in_playlist` value set to true in
[`Directory::Walk`](a57bcd0238/src/db/plugins/simple/Directory.cxx (L237)). But
`in_playlist` is not stored and only updated in
[`UpdateWalk::PurgeDanglingFromPlaylists`](a57bcd0238/src/db/update/Playlist.cxx (L139)),
which will only be executed while updating DB.

This causes the problem that playlist target songs are correctly
hidden after database update, but will remain visible after mpd server
restarted. This pr solves the problem by storing `in_playlist` value
of songs into the `SimpleDatabase` file.
2023-05-21 20:51:47 +02:00
kaliko
4ec6d0555a check systemd unit dir from systemd.pc in meson 2023-05-21 20:51:33 +02:00
Max Kellermann
a6a1182c4c python/build/libs.py: update OpenSSL to 3.1.0 2023-05-21 20:50:03 +02:00
Max Kellermann
a59c9c602b python/build/libs.py: update CURL to 8.0.1 2023-05-21 20:50:03 +02:00
Max Kellermann
0c4d824d64 subprojects/sqlite3.wrap: update to 3.41.0-1 2023-05-21 20:50:03 +02:00
Max Kellermann
a5281856c9 python/build/libs.py: update WildMidi to 0.4.5 2023-05-21 20:50:03 +02:00
Max Kellermann
0206a46d39 decoder/gme: require GME 0.6 or later
This allows dropping a few compile-time version checks and we can use
pkg-config to detect the library.
2023-05-21 20:50:03 +02:00
Max Kellermann
9475ef2202 fs/Charset: assign fs_charset
This got lost 8 years ago in commit 87c88fcb27

D'oh!
2023-05-21 20:50:03 +02:00
Max Kellermann
edae00e719 fs/Charset: remove useless log message 2023-05-21 20:50:03 +02:00
Max Kellermann
fb695bc55f command/{file,storage}: remove stray "#pragma GCC diagnostic pop" 2023-05-21 20:50:03 +02:00
Max Kellermann
23a5b8fd3c python/build/meson.py: remove unused import 2023-05-21 20:43:09 +02:00
Max Kellermann
273a93cfcf build/python/cmake: set CMAKE_C_FLAGS_INIT, not CMAKE_C_FLAGS
According to
https://cmake.org/cmake/help/latest/variable/CMAKE_LANG_FLAGS_INIT.html
the _INIT variables should be set in the toolchain file.
2023-05-21 20:43:05 +02:00
Max Kellermann
d105985d78 build/python/cmake: set CMAKE_OSX_SYSROOT on macOS 2023-05-21 20:43:00 +02:00
Max Kellermann
f8cfeb39e9 build/python/cmake: add "env" parameter 2023-05-21 20:42:51 +02:00
Max Kellermann
d5d3982d3c build/python/build/project.py: add "lazy" parameter to make_build_path() 2023-05-21 20:42:27 +02:00
Max Kellermann
47341107ea build/python/build/project.py: raise exception on regex mismatch 2023-05-21 20:42:18 +02:00
Max Kellermann
90eaa87a4d python/build/zlib.py: use autotools to be more portable
Right now, zlib is only built for Windows, but we may eventually
changed that, so don't hard-code `win32/Makefile.gcc`.
2023-05-21 20:42:12 +02:00
Max Kellermann
b09a54b2c2 python/build/autotools.py: use toolchain.arflags 2023-05-21 20:42:04 +02:00
Max Kellermann
10aec174d5 python/{cmake,autotools}: build in verbose mode
Make sure all the gory details are visible in CI logs.
2023-05-21 20:41:49 +02:00
Max Kellermann
d32ed194e8 python/build/autotools.py: dump config.log on configure error
For better error logs on CI.
2023-05-21 20:41:44 +02:00
Felix Hädicke
70d0fbd715 python/makeproject: do not use hard-code absolute path to make 2023-05-21 20:41:38 +02:00
Felix Hädicke
302432e157 python/makeproject: set appropriate build jobs count depending on the number of CPUs 2023-05-21 20:41:34 +02:00
Felix Hädicke
4ab8a677dc build/python: do not use absolute path for tar 2023-05-21 20:41:29 +02:00
Max Kellermann
52e4a4c904 build/python/build/project.py: lazy tarball extraction 2023-05-21 20:41:25 +02:00
Max Kellermann
a0f6932ebe unix/SignalHandlers: shut down if parent process dies in --no-daemon mode
By default, if the parent of a process dies, the process gets SIGHUP
and is supposed to shut down.  This however doesn't work for MPD,
because MPD redefines SIGHUP with a different meaning (like most
daemons do).

To work around this, we configure the kernel to send SIGTERM instead
of SIGHUP.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/1706
2023-05-21 20:40:47 +02:00
Max Kellermann
6e700dab69 CommandLine: hard-code daemon=false if ENABLE_DAEMON is not set 2023-05-21 20:40:39 +02:00
Max Kellermann
35eaed7206 python/build/libs.py: update FFmpeg to 6.0 2023-05-21 20:40:39 +02:00
Max Kellermann
e7c963f2ce python/build/libs.py: disable more unused FFmpeg features 2023-05-21 20:40:39 +02:00
Naïm Favier
949d72e368 output/PipeWire: lock thread loop in SendTag 2023-05-21 20:40:29 +02:00
Max Kellermann
8d2a184658 python/build/libs.py: update CURL to 7.88.1 2023-05-21 20:36:28 +02:00
Max Kellermann
c877a32d97 python/build/libs.py: update OpenSSL to 3.0.8 2023-05-21 20:36:23 +02:00
Max Kellermann
541468f0ca input/async: check for errors in Seek()
Fixes a busy loop in BufferingInputStream::RunThreadLocked() because
the method never learns that seeking is ignored, even though the HTTP
stream is already broken and can never be read; nobody cared to check
for errors.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/1727
2023-05-21 20:34:19 +02:00
Max Kellermann
d2797effa3 command/database: add missing include for UINT_MAX 2023-05-21 20:33:34 +02:00
Max Kellermann
1170fb1e1e output/osx: change type to std::size_t to fix -Wc++11-narrowing 2023-05-21 20:33:03 +02:00
Max Kellermann
65b9b3195c lib/dbus/AppendIter: add missing include 2023-05-21 20:33:03 +02:00
Max Kellermann
258830e913 increment version number to 0.23.13 2023-05-21 20:29:23 +02:00
Max Kellermann
d91da96798 release v0.23.12 2023-01-17 18:54:47 +01:00
Max Kellermann
b3897df682 decoder/mad: add assert() 2023-01-17 18:53:18 +01:00
Max Kellermann
3cacb56bb7 fs/StandardDirectory: don't fall back to getpwuid() without $HOME
If the environment variable $HOME does not exist, don't attempt to
obtain it from /etc/passwd; without $HOME, the calling process
indicates that it does not wish MPD to access the home directory.

This also prevents MPD from attempting to load
`/root/.config/mpd/mpd.conf` if MPD got started as global systemd
service.  Reading from there makes no sense, only /etc/mpd.conf shall
be used then.

This piece of code was initially added by commit 5d85792178.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/1687
2023-01-17 18:51:49 +01:00
Max Kellermann
15a1973e28 decoder/mad: fix integer underflow with very small files
When drop_start_samples and drop_end_samples overlap and are greater
than the actual number of samples, the `num_samples` calculation in
SubmitPCM() could underflow.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/1712
2023-01-17 17:41:37 +01:00
Max Kellermann
ad7d47a8ba output/PipeWire: use PW_KEY_TARGET_OBJECT with PipeWire 0.3.64
Closes https://github.com/MusicPlayerDaemon/MPD/issues/1721
2023-01-17 11:50:09 +01:00
Max Kellermann
0948c607b6 lib/curl/meson.build: require CURL 7.55.0 or later
For CURLINFO_CONTENT_LENGTH_DOWNLOAD_T (commit 4efd0a9f77).
2023-01-16 19:42:48 +01:00
Max Kellermann
60d04052c5 NEWS: mention the GCC13 fixes 2023-01-16 19:41:37 +01:00
Max Kellermann
c1780ac657 python/build/libs.py: update CURL to 7.87.0 2023-01-16 19:06:08 +01:00
Max Kellermann
e49cf0ec38 python/build/libs.py: update Boost to 1.81.0 2023-01-16 19:03:50 +01:00
Max Kellermann
e1d641f684 lib/curl/Easy: drop deprecated CURLOPT_HTTPPOST wrapper 2023-01-02 14:29:17 +01:00
Max Kellermann
4efd0a9f77 lib/curl/Easy: use CURLINFO_CONTENT_LENGTH_DOWNLOAD_T
CURLINFO_CONTENT_LENGTH_DOWNLOAD is deprecated and is ugly because it
uses floating point.
2023-01-02 14:28:42 +01:00
Max Kellermann
f6f8751332 io/FileReader: add missing include for uint64_t 2023-01-02 14:27:47 +01:00
gd
abb28593ce TagBuilder::RemoveType: added missing tag pool lock before call to tag_pool_put_item 2022-12-29 08:43:10 +01:00
Max Kellermann
115693b046 increment version number to 0.23.12 2022-12-29 08:42:02 +01:00
Đoàn Trần Công Danh
e4b055eb6d v0.23.x: RemoteTagCache: add missing include
Fix build with Boost 1.81.0. `<array>` was included by one of those boost headers,
however, it's no longer included as of Boost 1.81.0.

`master` doesn't use `std::array` in this file.

While we're at it, add all necessary inclusion files.
2022-12-01 08:29:23 +07:00
Max Kellermann
9866adff95 release v0.23.11 2022-11-28 16:55:46 +01:00
Max Kellermann
a8b0c55818 input/curl: make proxy verify setting optional
These settings do not work if CURL was compiled with
CURL_DISABLE_PROXY, and cause error "An unknown option was passed in
to libcurl".

Fixes regression by commit 7ab0dfc8ce
2022-11-28 16:14:01 +01:00
Max Kellermann
cac88e8be5 python/build/libs.py: re-enable verbose error strings
This compile-time option is not about debug logging, but about
curl_easy_strerror().

Closes https://github.com/MusicPlayerDaemon/MPD/issues/1670
2022-11-28 16:12:17 +01:00
Max Kellermann
e9f6a3482c db/Configured: add default "cache_directory" setting 2022-11-28 14:24:52 +01:00
Max Kellermann
5d2e80f188 db/Configured: use GetAppCacheDir() instead of GetUserCacheDir() 2022-11-28 14:20:15 +01:00
Max Kellermann
cfd4d5b13e StateFileConfig: use GetAppCacheDir() instead of GetUserCacheDir() 2022-11-28 14:20:14 +01:00
Max Kellermann
06514aec63 fs/StandardDirectory: add GetAppCacheDir() 2022-11-28 14:19:30 +01:00
Max Kellermann
4ded1ae67b fs/FileSystem: add CreateDirectoryNoThrow() 2022-11-28 14:19:08 +01:00
Max Kellermann
1da974e3fa fs/StandardDirectory: use PACKAGE_NAME from version.h 2022-11-28 14:05:34 +01:00
Max Kellermann
94f06f0946 fs/StandardDirectory: use mode=0777 in mkdir() call
Of course, mode=0700 is more secure, but allowing other users access
to new directories is a choice the user should make via umask().  If
the user-chosen umask allows everybody access, MPD should probably
respect that.
2022-11-28 14:04:47 +01:00
Max Kellermann
d9eec8a455 fs/StandardDirectory: do not use $RUNTIME_DIRECTORY on Android
This is systemd specific, and Android doesn't have systemd.
2022-11-28 10:44:50 +01:00
Max Kellermann
eaecbcafb2 PlaylistFile: disallow backslash in playlist names on Windows
The function spl_valid_name() should verify playlist names and prevent
path traversal, but it failed to do so on Windows, because it forgot
to check for backslashes.

This buggy piece of code was already present when stored playlists
were initially implemented in 2006 by commit 08003904d7, and
even during the many rounds of code refactoring, nobody ever bothered
to verify it.  D'oh!

(Thanks, Paul Arzelier)
2022-11-28 09:53:49 +01:00
Max Kellermann
73b5d0a9b9 system/Error: truncate the snprintf() return value
snprintf() does not return the (truncated) length actually written,
but the length that would be needed if the buffer were large enough.
This API usage mistake in FormatLastError() can lead to overflow of
the stack buffer, crashing the process (Windows only).

Closes https://github.com/MusicPlayerDaemon/MPD/issues/1676
2022-11-28 09:42:37 +01:00
Max Kellermann
c2d0f35e7a storage/meson.build: move StorageState.cxx to "mpd" executable
Fixes spurious linker errors.
2022-11-12 12:24:48 +01:00
Max Kellermann
ab99a57997 test/meson.build: reduce test_translate_song. dependencies 2022-11-12 12:17:35 +01:00
Max Kellermann
c8ebaf3521 python/build/meson.py: use "meson setup" instead of the deprecated syntax 2022-11-12 12:10:06 +01:00
Max Kellermann
52d00f7e30 subprojects: update fmt to 9.1.0 2022-11-11 19:22:39 +01:00
Max Kellermann
309491a6d8 subprojects: update expat to 2.5.0 2022-11-11 19:22:30 +01:00
gd
e7bfd32ccc doc/index.rst: added man pages links to suppress warnings: document isn't included in any toctree 2022-11-08 14:32:40 +01:00
gd
6f283b52ab doc/conf.py: set language = 'en' to suppress warning: Invalid configuration value found 2022-11-08 14:32:32 +01:00
Max Kellermann
32bddfabea archive/plugins/meson.build: do not generate empty library
If no archive library was found, return from the "plugins" directory
without creating "libarchive_plugins.a".  Empty static libraries are
unsupported on some operating systems such as macOS.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/1650
2022-11-03 20:36:00 +01:00
Max Kellermann
1944c826bc doc/conf.py: fix version regular expression
Commit 44ef34db88 was broken.
2022-11-03 20:33:08 +01:00
Max Kellermann
619bb60b26 python/build/libs.py: update FLAC to 1.4.2 2022-11-03 10:28:13 +01:00
Max Kellermann
c549e16ed1 python/build/libs.py: update CURL to 7.86.0 2022-11-03 10:28:13 +01:00
Max Kellermann
01c9c4507f python/build/libs.py: update OpenSSL to 3.0.7
Punycode hooray!
2022-11-03 10:28:13 +01:00
Max Kellermann
8c9d7bf07e increment version number to 0.23.11 2022-10-20 19:09:03 +02:00
Max Kellermann
44ef34db88 doc/conf.py: read version number from meson.build 2022-10-20 19:08:27 +02:00
jcorporation
5781f223f6 Document curl plugin .netrc and .curlrc behavior 2022-10-18 22:39:01 +02:00
Max Kellermann
e4c8ebe056 release v0.23.10 2022-10-14 23:51:41 +02:00
Max Kellermann
76b25a1377 output/alsa: add nullptr check for snd_pcm_name() return value
It is not explicitly documented whether snd_pcm_name() is allowed to
return NULL:
https://www.alsa-project.org/alsa-doc/alsa-lib/group___p_c_m.html#ga5031edc0422df8db1f70af056a12dd77

But apparently this is legal:
0222f45d11/src/pcm/pcm.c (L2761-L2762)

That's ... surprising!

Closes https://github.com/MusicPlayerDaemon/MPD/issues/1645
2022-10-14 23:14:30 +02:00
Max Kellermann
ccc3ee663b java/File: remove assertions to work around -Wtautological-pointer-compare 2022-10-14 23:00:35 +02:00
Max Kellermann
0626661764 android/Context: fix typo in assert() variable name
Closes https://github.com/MusicPlayerDaemon/MPD/issues/1644
2022-10-14 22:59:39 +02:00
Max Kellermann
31db04a3ca meson.build: suppress bogus clang 14 warning on libfmt headers 2022-10-14 22:54:34 +02:00
Max Kellermann
0c7163b9db subprojects: update expat 2022-10-14 22:46:30 +02:00
Max Kellermann
7d78cad8af doc/user.rst: update Android NDK requirement to 25b 2022-10-14 22:41:33 +02:00
Max Kellermann
912530ed20 test/meson.build: remove obsolete CURL workaround
This appears to have been fixed in some recent CURL version.
2022-10-14 22:41:33 +02:00
Max Kellermann
d3f37199b9 python/build/libs.py: update libnfs to 5.0.2 2022-10-14 22:41:33 +02:00
Max Kellermann
a4748d84b0 python/build/libs.py: update CURL to 7.85.0 2022-10-14 22:41:33 +02:00
Max Kellermann
8f847ec381 python/build/libs.py: update FFmpeg to 5.1.2 2022-10-14 22:41:33 +02:00
Max Kellermann
3a70f09dd3 python/build/libs.py: update libopenmpt to 0.6.6 2022-10-14 22:41:33 +02:00
Max Kellermann
568f63100b python/build/libs.py: update zlib to 1.2.13 2022-10-14 21:54:04 +02:00
Max Kellermann
3e25916b37 time/Parser: remove unused library 2022-09-30 18:17:03 +02:00
Max Kellermann
5f9438dae6 storage/curl: include cleanup 2022-09-30 18:16:46 +02:00
BurroCargado
99e65c58ce storage/curl: make timestamp parsing more robust
According to the latest WebDAV specification (RFC4918),
timestamp string in the getlastmodified property is formatted
as rfc1123-date, such as "Sun, 06 Nov 1994 08:49:37 GMT".
However, to process responses from servers in the older style
format specified in RFC2518, timestamps in the HTTP-date format
had better be accepted.

As described in the libcurl api documentation, curl_getdate() can handle
timestamp strings in HTTP-date formats, including rfc1123-date.

https://www.rfc-editor.org/rfc/rfc4918#section-15.7
https://www.rfc-editor.org/rfc/rfc2518.html#section-13.7
https://curl.se/libcurl/c/curl_getdate.html
2022-09-29 18:19:30 +02:00
BurroCargado
df71b07e9d storage/curl: fix can't get timestamp of remote file 2022-09-29 18:19:03 +02:00
Max Kellermann
2694195215 storage/curl: add noexcept and [[gnu::pure]] 2022-09-29 18:18:18 +02:00
Max Kellermann
66450d1f3c subprojects: update expat, fmt, sqlite3, vorbis 2022-09-28 11:34:33 +02:00
Max Kellermann
76efea3aa7 decoder/ffmpeg: add libfmt formatter for AVSampleFormat
Fixes compiler warning because formatting unscoped enums is deprecated
since libfmt 9.
2022-09-28 11:34:33 +02:00
jcorporation
7ab0dfc8ce Sets the curl proxy ssl verify options to the values of the host configuration options
This fixes 
2022-09-27 20:26:50 +02:00
Max Kellermann
15ff7c4cad Merge branch 'fix-oggflac-serial' of https://github.com/anthonyde/MPD into v0.23.x 2022-09-20 14:44:13 +02:00
Anthony DeRossi
9ab9b97f20 encoder/flac: only set a serial number for oggflac
This fixes a bug introduced in 87fa6bca where the FLAC encoder fails to
initialize unless libFLAC is built with Ogg support. When libFLAC is
built without Ogg support, FLAC__stream_encoder_set_ogg_serial_number
unconditionally returns false.
2022-09-16 17:58:41 -07:00
Max Kellermann
88d92aceab python/build/libs.py: update libFLAC to 1.4.0 2022-09-16 18:21:47 +02:00
Max Kellermann
a2ce4352c8 python/build/libs.py: update Boost to 1.80.0 2022-09-16 17:54:07 +02:00
Max Kellermann
84f43ccde8 LogInit: default to stderr on Windows
Don't require "log_file" setting, for "--no-config" operation.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/1600
2022-09-06 21:04:53 +02:00
Max Kellermann
38704c9cf3 LogInit: improve systemd/journald comment 2022-09-06 21:03:56 +02:00
Max Kellermann
910d0ec92b test/net/meson.build: add missing dependency 2022-09-06 20:44:24 +02:00
Max Kellermann
3b05c89765 archive/iso9660: fix off-by-one assertion failure
Calling data[fill] could trigger an assertion failure if
fill==data.size(), even if we call it only to take the address.

Instead of doing that, this commit changes the code to pointer
arithmetic.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/1556
2022-09-06 20:28:33 +02:00
Max Kellermann
e77b3fa46f increment version number to 0.23.10 2022-09-06 20:23:50 +02:00
Max Kellermann
12147f6d58 release v0.23.9 2022-08-18 18:20:54 +02:00
Max Kellermann
40bc60d6ae Main: load Android mpd.conf from ExternalFilesDir
See also https://github.com/MusicPlayerDaemon/MPD/issues/1061

Closes https://github.com/MusicPlayerDaemon/MPD/issues/1570
2022-08-18 18:17:43 +02:00
Max Kellermann
7778210269 Main: move code to TryReadConfigFile() 2022-08-18 18:12:21 +02:00
Max Kellermann
6229210d51 Main: move code to LoadConfigFile() 2022-08-18 18:11:49 +02:00
Max Kellermann
5d0d5b5d97 Android/Context: allow type=nullptr in GetExternalFilesDir() 2022-08-18 18:11:49 +02:00
Max Kellermann
1aa3c1e543 java/String: add static method Optional() 2022-08-18 18:10:16 +02:00
Max Kellermann
b90e32fe4e Android/Context: look up methods once during startup 2022-08-18 18:10:14 +02:00
Max Kellermann
1f4df2a64d android/Environment: pass JNIEnv to all functions 2022-08-18 18:09:54 +02:00
Max Kellermann
2efc1db6a9 android/Environment: no namespace indent 2022-08-18 18:08:45 +02:00
Max Kellermann
e2d4654e20 filter/ReplayGain: invoke the MixerListener after volume change
This ensures that Partition::OnMixerVolumeChanged() invokes
MixerMemento::InvalidateHardwareVolume(), clearing the cached volume
level.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/1526
2022-08-18 14:45:45 +02:00
Max Kellermann
2b8f1170a6 mixer/Control: use Mixer::IsGlobal() 2022-08-18 14:33:35 +02:00
Max Kellermann
5c4743441e mixer/All: use Mixer::IsPlugin() 2022-08-18 14:08:31 +02:00
Max Kellermann
cb288439a4 {android,win32}/build.py: make stdout/stderr unbuffered
Avoid excessive buffering if run by CI.
2022-08-08 23:48:23 +02:00
Max Kellermann
69f741e8a6 mixer/Memento: move IDLE_MIXER out of SetVolume()
Make this idle event per-partition.
2022-08-08 23:32:57 +02:00
Max Kellermann
4b4f47002b mixer/Volume: refactor to class MixerMemento, per partition
Eliminate global variables, convert them to MixerMemento fields.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/1583
2022-08-08 23:30:27 +02:00
Max Kellermann
615c301961 mixer/Volume: remove logging (mostly useless) 2022-08-08 23:13:14 +02:00
Max Kellermann
dc07180e48 input/CdioParanoia: add options "mode" and "skip"
Closes https://github.com/MusicPlayerDaemon/MPD/issues/1529
2022-08-08 22:53:48 +02:00
Max Kellermann
d3b235bab5 input/CdioParanoia: move global variables up 2022-08-08 22:38:28 +02:00
Max Kellermann
7c920ddebe filter/ffmpeg: fix FFmpeg 5.1 deprecation warnings 2022-08-08 21:34:26 +02:00
Dave Hocker
bbc088ae4e This PR provides forward and backward compatibility at macos SDK 12.0. At SDK 12.0, API function names were changed essentially replacing
occurrences of the word Master/master with Main/main. This change was test built on two different systems.

1. macos 10.15.7 with Xcode 12.4 and clang 12.0.0 on x86_64
2. macos 12.5 with Xcode 13.4.1 and clang 13.1.6 on arm64 (Apple silicon M1)

It should be noted that on macos 10.15.7 with Xcode 11.2 and clang 11.0, MPD will not build.
The MPD documentation states that clang 11.0 is the minimum requirement,
but clang 11.0 produces compile errors. Apparently the macos version
of clang 11.0 is not fully compliant.
2022-08-08 17:39:29 +02:00
Max Kellermann
fe195257d8 python/build/libs.py: update FFmpeg to 5.1 2022-07-27 11:04:14 +02:00
Max Kellermann
57d5df8118 decoder/ffmpeg: fix FFmpeg 5.1 deprecation warnings 2022-07-27 11:04:09 +02:00
Max Kellermann
59792cb0b8 decoder/ffmpeg: wrap FFmpeg include in "extern C"
Commit ebae25d175 added that #include, but forgot to wrap it in
"extern C", so the linker tried to look up C++ symbols, causing linker
failure.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/1582
2022-07-27 11:04:03 +02:00
Rosen Penev
cc557c4d60 meson: port ncpmc iconv solution
Properly deals with iconv, unlike the current solution. have_iconv fails
when libiconv CFLAGS are passed to the compiler. Tested under OpenWrt
with its CONFIG_BUILD_NLS, which adds libiconv include flags.

Signed-off-by: Rosen Penev <rosenp@gmail.com>
2022-07-20 08:03:24 +02:00
guihkx
956c5faebb output/PipeWire: set app icon
Closes 
2022-07-12 13:59:05 +02:00
Max Kellermann
cd0396c1f1 test/run_decoder: remove bogus assert() 2022-07-12 11:59:14 +02:00
Max Kellermann
79f9b268bb increment version number to 0.23.9 2022-07-12 11:50:47 +02:00
Max Kellermann
b45f3c8deb Android release 0.23.8 2022-07-12 11:48:41 +02:00
Max Kellermann
f8a8de87e4 android/AndroidManifest.xml: update targetSdkVersion to 30
Required by Google Play.
2022-07-12 11:48:41 +02:00
Max Kellermann
2183f0553c android/meson.build: use apksigner instead of jarsigner
This is required for targetSdkVersion=30.

apksigner requires running zipalign first.
2022-07-12 11:48:41 +02:00
133 changed files with 1788 additions and 996 deletions
NEWS
android
doc
meson.build
python/build
src
CommandLine.cxxCommandLine.hxxLogInit.cxxMain.cxxPartition.cxxPartition.hxxPlaylistFile.cxxRemoteTagCache.hxxSongPrint.cxxSongSave.cxxSongSave.hxxStateFile.cxxStateFileConfig.cxxTagPrint.cxxTimePrint.cxx
android
archive
command
db
decoder
encoder
event
filter
fs
input
io
java
lib
mixer
output
storage
system
tag
thread
time
unix
util
win32
subprojects
systemd
test
win32

79
NEWS

@@ -1,3 +1,82 @@
ver 0.23.14 (2023/10/08)
* decoder
- flac: fix scanning files with non-ASCII names on Windows
- mad: fix calculation of LAME peak values
* mixer
- wasapi: fix problem setting volume
* more libfmt 10 fixes
* fix auto-detected systemd unit directory
* Android
- require Android 7 or newer
ver 0.23.13 (2023/05/22)
* input
- curl: fix busy loop after connection failed
- curl: hide "404" log messages for non-existent ".mpdignore" files
* archive
- zzip: fix crash bug
* database
- simple: reveal hidden songs after deleting containing CUE
* decoder
- ffmpeg: reorder to a lower priority than "gme"
- gme: require GME 0.6 or later
* output
- pipewire: fix corruption bug due to missing lock
* Linux
- shut down if parent process dies in --no-daemon mode
- determine systemd unit directories via pkg-config
* support libfmt 10
ver 0.23.12 (2023/01/17)
* input
- curl: require CURL 7.55.0 or later
* decoder
- mad: fix integer underflow with very small files
* tags
- fix crash bug due to race condition
* output
- pipewire: adjust to PipeWire 0.3.64 API change
* fix build failures with GCC 13
ver 0.23.11 (2022/11/28)
* database
- simple: move default database to ~/.cache/mpd/db from ~/.cache/mpd.db
- simple: default "cache_directory" to ~/.cache/mpd/mounts
* macOS: fix build failure "no archive members specified"
* Windows
- fix crash bug (stack buffer overflow) after I/O errors
- fix path traversal bug because backslash was allowed in playlist names
* Android/Windows
- update OpenSSL to 3.0.7
- re-enable CURL's verbose error strings
ver 0.23.10 (2022/10/14)
* storage
- curl: fix file time stamps
* decoder
- ffmpeg: fix libfmt 9 compiler warning
* encoder
- flac: fix failure when libFLAC is built without Ogg support
* output
- alsa: fix crash bug
* Windows
- log to stdout by default, don't require "log_file" setting
ver 0.23.9 (2022/08/18)
* input
- cdio_paranoia: add options "mode" and "skip"
* decoder
- ffmpeg: support FFmpeg 5.1
* filter
- replay gain: fix delayed volume display with handler=mixer
* output
- pipewire: set app icon
* fix bogus volume levels with multiple partitions
* improve iconv detection
* macOS: fix macOS 10 build problem (0.23.8 regression)
* Android
- load mpd.conf from app data directory
ver 0.23.8 (2022/07/09) ver 0.23.8 (2022/07/09)
* storage * storage
- curl: fix crash if web server does not understand WebDAV - curl: fix crash if web server does not understand WebDAV

@@ -2,10 +2,10 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.musicpd" package="org.musicpd"
android:installLocation="auto" android:installLocation="auto"
android:versionCode="66" android:versionCode="72"
android:versionName="0.23.7"> android:versionName="0.23.14">
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="29"/> <uses-sdk android:minSdkVersion="24" android:targetSdkVersion="30"/>
<uses-feature android:name="android.software.leanback" <uses-feature android:name="android.software.leanback"
android:required="false" /> android:required="false" />

@@ -12,18 +12,30 @@ unsigned_apk = custom_target(
], ],
) )
aligned_apk = custom_target(
'mpd-aligned.apk',
output: 'mpd-aligned.apk',
input: unsigned_apk,
command: [
android_zipalign,
'-f', '4',
'@INPUT@', '@OUTPUT@',
],
)
if get_option('android_debug_keystore') != '' if get_option('android_debug_keystore') != ''
debug_apk = custom_target( debug_apk = custom_target(
'mpd-debug.apk', 'mpd-debug.apk',
output: 'mpd-debug.apk', output: 'mpd-debug.apk',
input: unsigned_apk, input: aligned_apk,
command: [ command: [
jarsigner, apksigner, 'sign',
'-keystore', get_option('android_debug_keystore'), '--in', '@INPUT@',
'-storepass', 'android', '--out', '@OUTPUT@',
'-signedjar', '@OUTPUT@', '--debuggable-apk-permitted',
'@INPUT@', '-ks', get_option('android_debug_keystore'),
'androiddebugkey', '--ks-key-alias', 'androiddebugkey',
'--ks-pass', 'pass:android',
], ],
build_by_default: true build_by_default: true
) )
@@ -31,29 +43,16 @@ endif
if get_option('android_keystore') != '' and get_option('android_keyalias') != '' and get_option('android_keypass') != '' if get_option('android_keystore') != '' and get_option('android_keyalias') != '' and get_option('android_keypass') != ''
unaligned_apk = custom_target( unaligned_apk = custom_target(
'mpd-unaligned.apk',
output: 'mpd-unaligned.apk',
input: unsigned_apk,
command: [
jarsigner,
'-digestalg', 'SHA1', '-sigalg', 'MD5withRSA',
'-keystore', get_option('android_keystore'),
'-storepass', get_option('android_keypass'),
'-signedjar', '@OUTPUT@',
'@INPUT@',
get_option('android_keyalias'),
],
)
apk = custom_target(
'mpd.apk', 'mpd.apk',
output: 'mpd.apk', output: 'mpd.apk',
input: unaligned_apk, input: aligned_apk,
command: [ command: [
android_zipalign, apksigner, 'sign',
'-f', '4', '--in', '@INPUT@',
'@INPUT@', '@OUTPUT@', '--out', '@OUTPUT@',
'-ks', get_option('android_keystore'),
'--ks-key-alias', get_option('android_keyalias'),
'--ks-pass', 'pass:' + get_option('android_keypass'),
], ],
build_by_default: true
) )
endif endif

@@ -1,4 +1,4 @@
#!/usr/bin/env python3 #!/usr/bin/env -S python3 -u
import os, os.path import os, os.path
import sys, subprocess import sys, subprocess
@@ -20,130 +20,13 @@ if not os.path.isdir(ndk_path):
print("NDK not found in", ndk_path, file=sys.stderr) print("NDK not found in", ndk_path, file=sys.stderr)
sys.exit(1) sys.exit(1)
android_abis = {
'armeabi-v7a': {
'arch': 'arm-linux-androideabi',
'ndk_arch': 'arm',
'llvm_triple': 'armv7-linux-androideabi',
'cflags': '-fpic -mfpu=neon -mfloat-abi=softfp',
},
'arm64-v8a': {
'arch': 'aarch64-linux-android',
'ndk_arch': 'arm64',
'llvm_triple': 'aarch64-linux-android',
'cflags': '-fpic',
},
'x86': {
'arch': 'i686-linux-android',
'ndk_arch': 'x86',
'llvm_triple': 'i686-linux-android',
'cflags': '-fPIC -march=i686 -mtune=intel -mssse3 -mfpmath=sse -m32',
},
'x86_64': {
'arch': 'x86_64-linux-android',
'ndk_arch': 'x86_64',
'llvm_triple': 'x86_64-linux-android',
'cflags': '-fPIC -m64',
},
}
# select the NDK target
abi_info = android_abis[android_abi]
arch = abi_info['arch']
# the path to the MPD sources # the path to the MPD sources
mpd_path = os.path.abspath(os.path.join(os.path.dirname(sys.argv[0]) or '.', '..')) mpd_path = os.path.abspath(os.path.join(os.path.dirname(sys.argv[0]) or '.', '..'))
sys.path[0] = os.path.join(mpd_path, 'python') sys.path[0] = os.path.join(mpd_path, 'python')
# output directories # output directories
from build.dirs import lib_path, tarball_path, src_path from build.dirs import lib_path, tarball_path, src_path
from build.meson import configure as run_meson from build.toolchain import AndroidNdkToolchain
arch_path = os.path.join(lib_path, arch)
build_path = os.path.join(arch_path, 'build')
# build host configuration
build_arch = 'linux-x86_64'
# set up the NDK toolchain
class AndroidNdkToolchain:
def __init__(self, tarball_path, src_path, build_path,
use_cxx):
self.tarball_path = tarball_path
self.src_path = src_path
self.build_path = build_path
ndk_arch = abi_info['ndk_arch']
android_api_level = '21'
install_prefix = os.path.join(arch_path, 'root')
self.arch = arch
self.actual_arch = arch
self.install_prefix = install_prefix
llvm_path = os.path.join(ndk_path, 'toolchains', 'llvm', 'prebuilt', build_arch)
llvm_triple = abi_info['llvm_triple'] + android_api_level
common_flags = '-Os -g'
common_flags += ' ' + abi_info['cflags']
llvm_bin = os.path.join(llvm_path, 'bin')
self.cc = os.path.join(llvm_bin, 'clang')
self.cxx = os.path.join(llvm_bin, 'clang++')
common_flags += ' -target ' + llvm_triple
common_flags += ' -fvisibility=hidden -fdata-sections -ffunction-sections'
self.ar = os.path.join(llvm_bin, 'llvm-ar')
self.ranlib = os.path.join(llvm_bin, 'llvm-ranlib')
self.nm = os.path.join(llvm_bin, 'llvm-nm')
self.strip = os.path.join(llvm_bin, 'llvm-strip')
self.cflags = common_flags
self.cxxflags = common_flags
self.cppflags = ' -isystem ' + os.path.join(install_prefix, 'include')
self.ldflags = ' -L' + os.path.join(install_prefix, 'lib') + \
' -Wl,--exclude-libs=ALL' + \
' ' + common_flags
self.ldflags = common_flags
self.libs = ''
self.is_arm = ndk_arch == 'arm'
self.is_armv7 = self.is_arm and 'armv7' in self.cflags
self.is_aarch64 = ndk_arch == 'arm64'
self.is_windows = False
libstdcxx_flags = ''
libstdcxx_cxxflags = ''
libstdcxx_ldflags = ''
libstdcxx_libs = '-static-libstdc++'
if self.is_armv7:
# On 32 bit ARM, clang generates no ".eh_frame" section;
# instead, the LLVM unwinder library is used for unwinding
# the stack after a C++ exception was thrown
libstdcxx_libs += ' -lunwind'
if use_cxx:
self.cxxflags += ' ' + libstdcxx_cxxflags
self.ldflags += ' ' + libstdcxx_ldflags
self.libs += ' ' + libstdcxx_libs
self.env = dict(os.environ)
# redirect pkg-config to use our root directory instead of the
# default one on the build host
import shutil
bin_dir = os.path.join(install_prefix, 'bin')
os.makedirs(bin_dir, exist_ok=True)
self.pkg_config = shutil.copy(os.path.join(mpd_path, 'build', 'pkg-config.sh'),
os.path.join(bin_dir, 'pkg-config'))
self.env['PKG_CONFIG'] = self.pkg_config
# a list of third-party libraries to be used by MPD on Android # a list of third-party libraries to be used by MPD on Android
from build.libs import * from build.libs import *
@@ -165,13 +48,17 @@ thirdparty_libs = [
# build the third-party libraries # build the third-party libraries
for x in thirdparty_libs: for x in thirdparty_libs:
toolchain = AndroidNdkToolchain(tarball_path, src_path, build_path, toolchain = AndroidNdkToolchain(mpd_path, lib_path,
tarball_path, src_path,
ndk_path, android_abi,
use_cxx=x.use_cxx) use_cxx=x.use_cxx)
if not x.is_installed(toolchain): if not x.is_installed(toolchain):
x.build(toolchain) x.build(toolchain)
# configure and build MPD # configure and build MPD
toolchain = AndroidNdkToolchain(tarball_path, src_path, build_path, toolchain = AndroidNdkToolchain(mpd_path, lib_path,
tarball_path, src_path,
ndk_path, android_abi,
use_cxx=True) use_cxx=True)
configure_args += [ configure_args += [

@@ -17,7 +17,7 @@ android_dx = join_paths(android_build_tools_dir, 'dx')
android_zipalign = join_paths(android_build_tools_dir, 'zipalign') android_zipalign = join_paths(android_build_tools_dir, 'zipalign')
javac = find_program('javac') javac = find_program('javac')
jarsigner = find_program('jarsigner') apksigner = find_program('apksigner')
rsvg_convert = find_program('rsvg-convert') rsvg_convert = find_program('rsvg-convert')
convert = find_program('convert') convert = find_program('convert')
zip = find_program('zip') zip = find_program('zip')

@@ -38,7 +38,10 @@ author = 'Max Kellermann'
# built documents. # built documents.
# #
# The short X.Y version. # The short X.Y version.
version = '0.23.8' with open('../meson.build') as f:
import re
version = re.match(r"project\([^\)]*\bversion:\s*'([^']+)'",
f.read(4096)).group(1)
# The full version, including alpha/beta/rc tags. # The full version, including alpha/beta/rc tags.
#release = version + '~git' #release = version + '~git'
@@ -47,7 +50,7 @@ version = '0.23.8'
# #
# This is also used if you do content translation via gettext catalogs. # This is also used if you do content translation via gettext catalogs.
# Usually you set "language" from the command line for these cases. # Usually you set "language" from the command line for these cases.
language = None language = "en"
# There are two options for replacing |today|: either, you set today to some # There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used: # non-false value, then it is used:

@@ -11,6 +11,12 @@ Music Player Daemon
client client
protocol protocol
.. toctree::
:maxdepth: 1
:caption: man pages:
mpd.1
mpd.conf.5
Indices and tables Indices and tables
================== ==================

@@ -181,7 +181,7 @@
# #
#database { #database {
# plugin "simple" # plugin "simple"
# path "~/.local/share/mpd/db # path "~/.local/share/mpd/db"
# cache_directory "~/.local/share/mpd/cache" # cache_directory "~/.local/share/mpd/cache"
#} #}
# #
@@ -314,6 +314,7 @@ input {
## device "Digital Audio (S/PDIF) (High Definition Audio Device)" # optional ## device "Digital Audio (S/PDIF) (High Definition Audio Device)" # optional
# or # or
## device "0" # optional ## device "0" # optional
## mixer_type "hardware" # optional
## Exclusive mode blocks all other audio source, and get best audio quality without resampling. ## Exclusive mode blocks all other audio source, and get best audio quality without resampling.
## exclusive "no" # optional ## exclusive "no" # optional
## Enumerate all devices in log. ## Enumerate all devices in log.

@@ -206,6 +206,11 @@ Plays audio CDs using libcdio. The URI has the form: "cdda://[DEVICE][/TRACK]".
- If the CD drive does not specify a byte order, MPD assumes it is the CPU's native byte order. This setting allows overriding this. - If the CD drive does not specify a byte order, MPD assumes it is the CPU's native byte order. This setting allows overriding this.
* - **speed N** * - **speed N**
- Request CDParanoia cap the extraction speed to Nx normal CD audio rotation speed, keeping the drive quiet. - Request CDParanoia cap the extraction speed to Nx normal CD audio rotation speed, keeping the drive quiet.
* - **mode disable|overlap|full**
- Set the paranoia mode; ``disable`` means no fixups, ``overlap``
performs overlapped reads, and ``full`` enables all options.
* - **skip yes|no**
- If set to ``no``, then never skip failed reads.
curl curl
---- ----
@@ -214,8 +219,9 @@ Opens remote files or streams over HTTP using libcurl.
Note that unless overridden by the below settings (e.g. by setting Note that unless overridden by the below settings (e.g. by setting
them to a blank value), general curl configuration from environment them to a blank value), general curl configuration from environment
variables such as ``http_proxy`` or specified in :file:`~/.curlrc` variables such as ``http_proxy`` will be in effect.
will be in effect.
User name and password are read from an optional :file:`~/.netrc`, :file:`~/.curlrc` is not read.
.. list-table:: .. list-table::
:widths: 20 80 :widths: 20 80
@@ -1109,7 +1115,7 @@ Connect to a `PipeWire <https://pipewire.org/>`_ server. Requires
* - **target NAME** * - **target NAME**
- Link to the given target. If not specified, let the PipeWire - Link to the given target. If not specified, let the PipeWire
manager select a target. To get a list of available targets, manager select a target. To get a list of available targets,
type ``pw-cli dump short Node`` type ``pw-cli ls Node``
* - **remote NAME** * - **remote NAME**
- The name of the remote to connect to. The default is - The name of the remote to connect to. The default is
``pipewire-0``. ``pipewire-0``.

@@ -36,7 +36,9 @@ Installing on Android
An experimental Android build is available on Google Play. After installing and launching it, :program:`MPD` will scan the music in your Music directory and you can control it as usual with a :program:`MPD` client. An experimental Android build is available on Google Play. After installing and launching it, :program:`MPD` will scan the music in your Music directory and you can control it as usual with a :program:`MPD` client.
If you need to tweak the configuration, you can create a file called :file:`mpd.conf` on the data partition (the directory which is returned by Android's :dfn:`getExternalStorageDirectory()` API function). If you need to tweak the configuration, you can create a file called
:file:`mpd.conf` in MPD's data directory on the external storage
(usually :file:`Android/data/org.musicpd/files/mpd.conf`).
ALSA is not available on Android; only the :ref:`OpenSL ES ALSA is not available on Android; only the :ref:`OpenSL ES
<sles_output>` output plugin can be used for local playback. <sles_output>` output plugin can be used for local playback.
@@ -197,7 +199,7 @@ Compiling for Android
You need: You need:
* Android SDK * Android SDK
* `Android NDK r23 <https://developer.android.com/ndk/downloads>`_ * `Android NDK r25b <https://developer.android.com/ndk/downloads>`_
* `Meson 0.56.0 <http://mesonbuild.com/>`__ and `Ninja * `Meson 0.56.0 <http://mesonbuild.com/>`__ and `Ninja
<https://ninja-build.org/>`__ <https://ninja-build.org/>`__
* cmake * cmake
@@ -609,6 +611,11 @@ If ReplayGain is enabled, then the setting ``replaygain_preamp`` is
set to a value (in dB) between ``-15`` and ``15``. This is the gain set to a value (in dB) between ``-15`` and ``15``. This is the gain
applied to songs with ReplayGain tags. applied to songs with ReplayGain tags.
On songs without ReplayGain tags, the setting
``replaygain_missing_preamp`` is used instead. If this setting is not
configured, then no ReplayGain is applied to such songs, and they will
appear too loud.
ReplayGain is usually implemented with a software volume filter (which ReplayGain is usually implemented with a software volume filter (which
prevents `Bit-perfect playback`_). To use a hardware mixer, set prevents `Bit-perfect playback`_). To use a hardware mixer, set
``replay_gain_handler`` to ``mixer`` in the ``audio_output`` section ``replay_gain_handler`` to ``mixer`` in the ``audio_output`` section
@@ -654,9 +661,11 @@ MPD enables MixRamp if:
- Cross-fade is enabled - Cross-fade is enabled
- :ref:`mixrampdelay <command_mixrampdelay>` is set to a positive - :ref:`mixrampdelay <command_mixrampdelay>` is set to a positive
value, e.g.:: value, e.g.::
mpc mixrampdelay 1 mpc mixrampdelay 1
- :ref:`mixrampdb <command_mixrampdb>` is set to a reasonable value, - :ref:`mixrampdb <command_mixrampdb>` is set to a reasonable value,
e.g.:: e.g.::
mpc mixrampdb -17 mpc mixrampdb -17
- both songs have MixRamp tags - both songs have MixRamp tags
- both songs have the same audio format (or :ref:`audio_output_format` - both songs have the same audio format (or :ref:`audio_output_format`

@@ -1,7 +1,7 @@
project( project(
'mpd', 'mpd',
['c', 'cpp'], ['c', 'cpp'],
version: '0.23.8', version: '0.23.14',
meson_version: '>= 0.56.0', meson_version: '>= 0.56.0',
default_options: [ default_options: [
'c_std=c11', 'c_std=c11',
@@ -205,7 +205,6 @@ enable_daemon = not is_windows and not is_android and get_option('daemon')
conf.set('ENABLE_DAEMON', enable_daemon) conf.set('ENABLE_DAEMON', enable_daemon)
conf.set('HAVE_GETPWNAM_R', compiler.has_function('getpwnam_r')) conf.set('HAVE_GETPWNAM_R', compiler.has_function('getpwnam_r'))
conf.set('HAVE_GETPWUID_R', compiler.has_function('getpwuid_r'))
conf.set('HAVE_INITGROUPS', compiler.has_function('initgroups')) conf.set('HAVE_INITGROUPS', compiler.has_function('initgroups'))
conf.set('HAVE_FNMATCH', compiler.has_function('fnmatch')) conf.set('HAVE_FNMATCH', compiler.has_function('fnmatch'))
@@ -251,6 +250,14 @@ endif
fmt_dep = dependency('fmt', fallback: ['fmt', 'fmt_dep']) fmt_dep = dependency('fmt', fallback: ['fmt', 'fmt_dep'])
if compiler.get_id() == 'clang' and compiler.version().version_compare('<15')
fmt_dep = declare_dependency(
dependencies: fmt_dep,
# suppress bogus clang 14 warning (the version in Android NDK r25b)
compile_args: ['-Wno-unused-local-typedef'],
)
endif
log = static_library( log = static_library(
'log', 'log',
'src/Log.cxx', 'src/Log.cxx',
@@ -352,7 +359,7 @@ sources = [
'src/TagStream.cxx', 'src/TagStream.cxx',
'src/TagAny.cxx', 'src/TagAny.cxx',
'src/TimePrint.cxx', 'src/TimePrint.cxx',
'src/mixer/Volume.cxx', 'src/mixer/Memento.cxx',
'src/PlaylistFile.cxx', 'src/PlaylistFile.cxx',
] ]
@@ -382,6 +389,7 @@ endif
if enable_database if enable_database
sources += [ sources += [
'src/storage/StorageState.cxx',
'src/queue/PlaylistUpdate.cxx', 'src/queue/PlaylistUpdate.cxx',
'src/command/StorageCommands.cxx', 'src/command/StorageCommands.cxx',
'src/command/DatabaseCommands.cxx', 'src/command/DatabaseCommands.cxx',

@@ -1,26 +1,32 @@
import os.path, subprocess, sys import os.path, subprocess, sys
from typing import Collection, Iterable, Optional, Sequence, Union
from collections.abc import Mapping
from build.makeproject import MakeProject from build.makeproject import MakeProject
from .toolchain import AnyToolchain
class AutotoolsProject(MakeProject): class AutotoolsProject(MakeProject):
def __init__(self, url, md5, installed, configure_args=[], def __init__(self, url: Union[str, Sequence[str]], md5: str, installed: str,
autogen=False, configure_args: Iterable[str]=[],
autoreconf=False, autogen: bool=False,
cppflags='', autoreconf: bool=False,
ldflags='', per_arch_cflags: Optional[Mapping[str, str]]=None,
libs='', cppflags: str='',
subdirs=None, ldflags: str='',
libs: str='',
subdirs: Optional[Collection[str]]=None,
**kwargs): **kwargs):
MakeProject.__init__(self, url, md5, installed, **kwargs) MakeProject.__init__(self, url, md5, installed, **kwargs)
self.configure_args = configure_args self.configure_args = configure_args
self.autogen = autogen self.autogen = autogen
self.autoreconf = autoreconf self.autoreconf = autoreconf
self.per_arch_cflags = per_arch_cflags
self.cppflags = cppflags self.cppflags = cppflags
self.ldflags = ldflags self.ldflags = ldflags
self.libs = libs self.libs = libs
self.subdirs = subdirs self.subdirs = subdirs
def configure(self, toolchain): def configure(self, toolchain: AnyToolchain) -> str:
src = self.unpack(toolchain) src = self.unpack(toolchain)
if self.autogen: if self.autogen:
if sys.platform == 'darwin': if sys.platform == 'darwin':
@@ -35,27 +41,48 @@ class AutotoolsProject(MakeProject):
build = self.make_build_path(toolchain) build = self.make_build_path(toolchain)
arch_cflags = ''
if self.per_arch_cflags is not None and toolchain.host_triplet is not None:
arch_cflags = self.per_arch_cflags.get(toolchain.host_triplet, '')
configure = [ configure = [
os.path.join(src, 'configure'), os.path.join(src, 'configure'),
'CC=' + toolchain.cc, 'CC=' + toolchain.cc,
'CXX=' + toolchain.cxx, 'CXX=' + toolchain.cxx,
'CFLAGS=' + toolchain.cflags, 'CFLAGS=' + toolchain.cflags + ' ' + arch_cflags,
'CXXFLAGS=' + toolchain.cxxflags, 'CXXFLAGS=' + toolchain.cxxflags + ' ' + arch_cflags,
'CPPFLAGS=' + toolchain.cppflags + ' ' + self.cppflags, 'CPPFLAGS=' + toolchain.cppflags + ' ' + self.cppflags,
'LDFLAGS=' + toolchain.ldflags + ' ' + self.ldflags, 'LDFLAGS=' + toolchain.ldflags + ' ' + self.ldflags,
'LIBS=' + toolchain.libs + ' ' + self.libs, 'LIBS=' + toolchain.libs + ' ' + self.libs,
'AR=' + toolchain.ar, 'AR=' + toolchain.ar,
'ARFLAGS=' + toolchain.arflags,
'RANLIB=' + toolchain.ranlib, 'RANLIB=' + toolchain.ranlib,
'STRIP=' + toolchain.strip, 'STRIP=' + toolchain.strip,
'--host=' + toolchain.arch,
'--prefix=' + toolchain.install_prefix, '--prefix=' + toolchain.install_prefix,
'--enable-silent-rules', '--disable-silent-rules',
] + self.configure_args ]
if toolchain.host_triplet is not None:
configure.append('--host=' + toolchain.host_triplet)
configure.extend(self.configure_args)
try:
print(configure)
subprocess.check_call(configure, cwd=build, env=toolchain.env)
except subprocess.CalledProcessError:
# dump config.log after a failed configure run
try:
with open(os.path.join(build, 'config.log')) as f:
sys.stdout.write(f.read())
except:
pass
# re-raise the exception
raise
subprocess.check_call(configure, cwd=build, env=toolchain.env)
return build return build
def _build(self, toolchain): def _build(self, toolchain: AnyToolchain) -> None:
build = self.configure(toolchain) build = self.configure(toolchain)
if self.subdirs is not None: if self.subdirs is not None:
for subdir in self.subdirs: for subdir in self.subdirs:

@@ -1,17 +1,21 @@
import os import os
import re
import subprocess import subprocess
from typing import cast, Optional, Sequence, TextIO, Union
from collections.abc import Mapping
from build.project import Project from build.project import Project
from .toolchain import AnyToolchain
def __write_cmake_compiler(f, language, compiler): def __write_cmake_compiler(f: TextIO, language: str, compiler: str) -> None:
s = compiler.split(' ', 1) s = compiler.split(' ', 1)
if len(s) == 2: if len(s) == 2:
print(f'set(CMAKE_{language}_COMPILER_LAUNCHER {s[0]})', file=f) print(f'set(CMAKE_{language}_COMPILER_LAUNCHER {s[0]})', file=f)
compiler = s[1] compiler = s[1]
print(f'set(CMAKE_{language}_COMPILER {compiler})', file=f) print(f'set(CMAKE_{language}_COMPILER {compiler})', file=f)
def __write_cmake_toolchain_file(f, toolchain): def __write_cmake_toolchain_file(f: TextIO, toolchain: AnyToolchain) -> None:
if '-darwin' in toolchain.actual_arch: if toolchain.is_darwin:
cmake_system_name = 'Darwin' cmake_system_name = 'Darwin'
elif toolchain.is_windows: elif toolchain.is_windows:
cmake_system_name = 'Windows' cmake_system_name = 'Windows'
@@ -20,67 +24,99 @@ def __write_cmake_toolchain_file(f, toolchain):
f.write(f""" f.write(f"""
set(CMAKE_SYSTEM_NAME {cmake_system_name}) set(CMAKE_SYSTEM_NAME {cmake_system_name})
set(CMAKE_SYSTEM_PROCESSOR {toolchain.actual_arch.split('-', 1)[0]}) set(CMAKE_SYSTEM_PROCESSOR {toolchain.host_triplet.split('-', 1)[0]})
set(CMAKE_C_COMPILER_TARGET {toolchain.actual_arch}) set(CMAKE_C_COMPILER_TARGET {toolchain.host_triplet})
set(CMAKE_CXX_COMPILER_TARGET {toolchain.actual_arch}) set(CMAKE_CXX_COMPILER_TARGET {toolchain.host_triplet})
set(CMAKE_C_FLAGS "{toolchain.cflags} {toolchain.cppflags}") set(CMAKE_C_FLAGS_INIT "{toolchain.cflags} {toolchain.cppflags}")
set(CMAKE_CXX_FLAGS "{toolchain.cxxflags} {toolchain.cppflags}") set(CMAKE_CXX_FLAGS_INIT "{toolchain.cxxflags} {toolchain.cppflags}")
""") """)
__write_cmake_compiler(f, 'C', toolchain.cc) __write_cmake_compiler(f, 'C', toolchain.cc)
__write_cmake_compiler(f, 'CXX', toolchain.cxx) __write_cmake_compiler(f, 'CXX', toolchain.cxx)
def configure(toolchain, src, build, args=()): if cmake_system_name == 'Darwin':
cross_args = [] # On macOS, cmake forcibly adds an "-isysroot" flag even if
# one is already present in the flags variable; this breaks
# cross-compiling for iOS, and can be worked around by setting
# the CMAKE_OSX_SYSROOT variable
# (https://cmake.org/cmake/help/latest/variable/CMAKE_OSX_SYSROOT.html).
m = re.search(r'-isysroot +(\S+)', toolchain.cflags)
if m:
sysroot = m.group(1)
print(f'set(CMAKE_OSX_SYSROOT {sysroot})', file=f)
# search libraries and headers only in the sysroot, not on
# the build host
f.write(f"""
set(CMAKE_FIND_ROOT_PATH "{toolchain.install_prefix};{sysroot}")
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
""")
def configure(toolchain: AnyToolchain, src: str, build: str, args: list[str]=[], env: Optional[Mapping[str, str]]=None) -> None:
cross_args: list[str] = []
if toolchain.is_windows: if toolchain.is_windows:
cross_args.append('-DCMAKE_RC_COMPILER=' + toolchain.windres) cross_args.append('-DCMAKE_RC_COMPILER=' + cast(str, toolchain.windres))
# Several targets need a sysroot to prevent pkg-config from
# looking for libraries on the build host (TODO: fix this
# properly); but we must not do that on Android because the NDK
# has a sysroot already
if '-android' not in toolchain.actual_arch and '-darwin' not in toolchain.actual_arch:
cross_args.append('-DCMAKE_SYSROOT=' + toolchain.install_prefix)
os.makedirs(build, exist_ok=True)
cmake_toolchain_file = os.path.join(build, 'cmake_toolchain_file')
with open(cmake_toolchain_file, 'w') as f:
__write_cmake_toolchain_file(f, toolchain)
configure = [ configure = [
'cmake', 'cmake',
src, src,
'-DCMAKE_TOOLCHAIN_FILE=' + cmake_toolchain_file,
'-DCMAKE_INSTALL_PREFIX=' + toolchain.install_prefix, '-DCMAKE_INSTALL_PREFIX=' + toolchain.install_prefix,
'-DCMAKE_BUILD_TYPE=release', '-DCMAKE_BUILD_TYPE=release',
'-GNinja', '-GNinja',
] + cross_args + args ] + cross_args + args
subprocess.check_call(configure, env=toolchain.env, cwd=build) if toolchain.host_triplet is not None:
# cross-compiling: write a toolchain file
os.makedirs(build, exist_ok=True)
# Several targets need a sysroot to prevent pkg-config from
# looking for libraries on the build host (TODO: fix this
# properly); but we must not do that on Android because the NDK
# has a sysroot already
if not toolchain.is_android and not toolchain.is_darwin:
cross_args.append('-DCMAKE_SYSROOT=' + toolchain.install_prefix)
cmake_toolchain_file = os.path.join(build, 'cmake_toolchain_file')
with open(cmake_toolchain_file, 'w') as f:
__write_cmake_toolchain_file(f, toolchain)
configure.append('-DCMAKE_TOOLCHAIN_FILE=' + cmake_toolchain_file)
if env is None:
env = toolchain.env
else:
env = {**toolchain.env, **env}
print(configure)
subprocess.check_call(configure, env=env, cwd=build)
class CmakeProject(Project): class CmakeProject(Project):
def __init__(self, url, md5, installed, configure_args=[], def __init__(self, url: Union[str, Sequence[str]], md5: str, installed: str,
windows_configure_args=[], configure_args: list[str]=[],
windows_configure_args: list[str]=[],
env: Optional[Mapping[str, str]]=None,
**kwargs): **kwargs):
Project.__init__(self, url, md5, installed, **kwargs) Project.__init__(self, url, md5, installed, **kwargs)
self.configure_args = configure_args self.configure_args = configure_args
self.windows_configure_args = windows_configure_args self.windows_configure_args = windows_configure_args
self.env = env
def configure(self, toolchain): def configure(self, toolchain: AnyToolchain) -> str:
src = self.unpack(toolchain) src = self.unpack(toolchain)
build = self.make_build_path(toolchain) build = self.make_build_path(toolchain)
configure_args = self.configure_args configure_args = self.configure_args
if toolchain.is_windows: if toolchain.is_windows:
configure_args = configure_args + self.windows_configure_args configure_args = configure_args + self.windows_configure_args
configure(toolchain, src, build, configure_args) configure(toolchain, src, build, configure_args, self.env)
return build return build
def _build(self, toolchain): def _build(self, toolchain: AnyToolchain) -> None:
build = self.configure(toolchain) build = self.configure(toolchain)
subprocess.check_call(['ninja', 'install'], subprocess.check_call(['ninja', '-v', 'install'],
cwd=build, env=toolchain.env) cwd=build, env=toolchain.env)

@@ -1,12 +1,50 @@
from build.verify import verify_file_digest from typing import Sequence, Union
import os import os
import sys
import urllib.request import urllib.request
def download_and_verify(url, md5, parent_path): from .verify import verify_file_digest
def __to_string_sequence(x: Union[str, Sequence[str]]) -> Sequence[str]:
if isinstance(x, str):
return (x,)
else:
return x
def __get_any(x: Union[str, Sequence[str]]) -> str:
if isinstance(x, str):
return x
else:
return x[0]
def __download_one(url: str, path: str) -> None:
print("download", url)
urllib.request.urlretrieve(url, path)
def __download(urls: Sequence[str], path: str) -> None:
for url in urls[:-1]:
try:
__download_one(url, path)
return
except:
print("download error:", sys.exc_info()[0])
__download_one(urls[-1], path)
def __download_and_verify_to(urls: Sequence[str], md5: str, path: str) -> None:
__download(urls, path)
if not verify_file_digest(path, md5):
raise RuntimeError("Digest mismatch")
def download_basename(urls: Union[str, Sequence[str]]) -> str:
return os.path.basename(__get_any(urls))
def download_and_verify(urls: Union[str, Sequence[str]], md5: str, parent_path: str) -> str:
"""Download a file, verify its MD5 checksum and return the local path.""" """Download a file, verify its MD5 checksum and return the local path."""
base = download_basename(urls)
os.makedirs(parent_path, exist_ok=True) os.makedirs(parent_path, exist_ok=True)
path = os.path.join(parent_path, os.path.basename(url)) path = os.path.join(parent_path, base)
try: try:
if verify_file_digest(path, md5): return path if verify_file_digest(path, md5): return path
@@ -16,11 +54,6 @@ def download_and_verify(url, md5, parent_path):
tmp_path = path + '.tmp' tmp_path = path + '.tmp'
print("download", url) __download_and_verify_to(__to_string_sequence(urls), md5, tmp_path)
urllib.request.urlretrieve(url, tmp_path)
if not verify_file_digest(tmp_path, md5):
os.unlink(tmp_path)
raise RuntimeError("Digest mismatch")
os.rename(tmp_path, path) os.rename(tmp_path, path)
return path return path

@@ -29,8 +29,8 @@ libogg = CmakeProject(
) )
opus = AutotoolsProject( opus = AutotoolsProject(
'https://archive.mozilla.org/pub/opus/opus-1.3.1.tar.gz', 'https://downloads.xiph.org/releases/opus/opus-1.4.tar.gz',
'65b58e1e25b2a114157014736a3d9dfeaad8d41be1c8179866f144a2fb44ff9d', 'c9b32b4253be5ae63d1ff16eea06b94b5f0f2951b7a02aceef58e3a3ce49c51f',
'lib/libopus.a', 'lib/libopus.a',
[ [
'--disable-shared', '--enable-static', '--disable-shared', '--enable-static',
@@ -43,20 +43,23 @@ opus = AutotoolsProject(
) )
flac = AutotoolsProject( flac = AutotoolsProject(
'http://downloads.xiph.org/releases/flac/flac-1.3.4.tar.xz', 'http://downloads.xiph.org/releases/flac/flac-1.4.3.tar.xz',
'8ff0607e75a322dd7cd6ec48f4f225471404ae2730d0ea945127b1355155e737', '6c58e69cd22348f441b861092b825e591d0b822e106de6eb0ee4d05d27205b70',
'lib/libFLAC.a', 'lib/libFLAC.a',
[ [
'--disable-shared', '--enable-static', '--disable-shared', '--enable-static',
'--disable-stack-smash-protection',
'--disable-xmms-plugin', '--disable-cpplibs', '--disable-xmms-plugin', '--disable-cpplibs',
'--disable-doxygen-docs', '--disable-doxygen-docs',
'--disable-programs',
], ],
subdirs=['include', 'src/libFLAC'], subdirs=['include', 'src/libFLAC'],
) )
zlib = ZlibProject( zlib = ZlibProject(
'http://zlib.net/zlib-1.2.12.tar.xz', ('http://zlib.net/zlib-1.3.tar.xz',
'7db46b8d7726232a621befaab4a1c870f00a90805511c0e0090441dac57def18', 'https://github.com/madler/zlib/releases/download/v1.3/zlib-1.3.tar.xz'),
'8a9ba2898e1d0d774eca6ba5b4627a11e5588ba85c8851336eb38de4683050a7',
'lib/libz.a', 'lib/libz.a',
) )
@@ -109,33 +112,35 @@ libmodplug = AutotoolsProject(
[ [
'--disable-shared', '--enable-static', '--disable-shared', '--enable-static',
], ],
patches='src/lib/modplug/patches',
) )
libopenmpt = AutotoolsProject( libopenmpt = AutotoolsProject(
'https://lib.openmpt.org/files/libopenmpt/src/libopenmpt-0.5.12+release.autotools.tar.gz', 'https://lib.openmpt.org/files/libopenmpt/src/libopenmpt-0.6.6+release.autotools.tar.gz',
'892aea7a599b5d21842bebf463b5aafdad5711be7008dd84401920c6234820af', '6ddb9e26a430620944891796fefb1bbb38bd9148f6cfc558810c0d3f269876c7',
'lib/libopenmpt.a', 'lib/libopenmpt.a',
[ [
'--disable-shared', '--enable-static', '--disable-shared', '--enable-static',
'--disable-openmpt123', '--disable-openmpt123',
'--disable-examples',
'--disable-tests',
'--disable-doxygen-doc',
'--without-mpg123', '--without-ogg', '--without-vorbis', '--without-vorbisfile', '--without-mpg123', '--without-ogg', '--without-vorbis', '--without-vorbisfile',
'--without-portaudio', '--without-portaudiocpp', '--without-sndfile', '--without-portaudio', '--without-portaudiocpp', '--without-sndfile',
'--without-flac',
], ],
base='libopenmpt-0.5.12+release.autotools', base='libopenmpt-0.6.6+release.autotools',
) )
wildmidi = CmakeProject( wildmidi = CmakeProject(
'https://codeload.github.com/Mindwerks/wildmidi/tar.gz/wildmidi-0.4.4', 'https://github.com/Mindwerks/wildmidi/releases/download/wildmidi-0.4.5/wildmidi-0.4.5.tar.gz',
'6f267c8d331e9859906837e2c197093fddec31829d2ebf7b958cf6b7ae935430', 'd5e7bef00a7aa47534a53d43b1265f8d3d27f6a28e7f563c1cdf02ff4fa35b99',
'lib/libWildMidi.a', 'lib/libWildMidi.a',
[ [
'-DBUILD_SHARED_LIBS=OFF', '-DBUILD_SHARED_LIBS=OFF',
'-DWANT_PLAYER=OFF', '-DWANT_PLAYER=OFF',
'-DWANT_STATIC=ON', '-DWANT_STATIC=ON',
], ],
base='wildmidi-wildmidi-0.4.4',
name='wildmidi',
version='0.4.4',
) )
gme = CmakeProject( gme = CmakeProject(
@@ -146,13 +151,13 @@ gme = CmakeProject(
'-DBUILD_SHARED_LIBS=OFF', '-DBUILD_SHARED_LIBS=OFF',
'-DENABLE_UBSAN=OFF', '-DENABLE_UBSAN=OFF',
'-DZLIB_INCLUDE_DIR=OFF', '-DZLIB_INCLUDE_DIR=OFF',
'-DSDL2_DIR=OFF', '-DCMAKE_DISABLE_FIND_PACKAGE_SDL2=ON',
], ],
) )
ffmpeg = FfmpegProject( ffmpeg = FfmpegProject(
'http://ffmpeg.org/releases/ffmpeg-5.0.1.tar.xz', 'http://ffmpeg.org/releases/ffmpeg-6.0.tar.xz',
'ef2efae259ce80a240de48ec85ecb062cecca26e4352ffb3fda562c21a93007b', '57be87c22d9b49c112b6d24bc67d42508660e6b718b3db89c44e47e289137082',
'lib/libavcodec.a', 'lib/libavcodec.a',
[ [
'--disable-shared', '--enable-static', '--disable-shared', '--enable-static',
@@ -166,18 +171,20 @@ ffmpeg = FfmpegProject(
'--disable-swscale', '--disable-swscale',
'--disable-postproc', '--disable-postproc',
'--disable-avfilter', '--disable-avfilter',
'--disable-lzo',
'--disable-faan', '--disable-faan',
'--disable-pixelutils', '--disable-pixelutils',
'--disable-network', '--disable-network',
'--disable-encoders', '--disable-encoders',
'--disable-hwaccels',
'--disable-muxers', '--disable-muxers',
'--disable-protocols', '--disable-protocols',
'--disable-devices', '--disable-devices',
'--disable-filters', '--disable-filters',
'--disable-v4l2_m2m', '--disable-v4l2_m2m',
'--disable-sdl2',
'--disable-vulkan', '--disable-vulkan',
'--disable-xlib',
'--disable-parser=bmp', '--disable-parser=bmp',
'--disable-parser=cavsvideo', '--disable-parser=cavsvideo',
@@ -191,17 +198,22 @@ ffmpeg = FfmpegProject(
'--disable-parser=h263', '--disable-parser=h263',
'--disable-parser=h264', '--disable-parser=h264',
'--disable-parser=hevc', '--disable-parser=hevc',
'--disable-parser=jpeg2000',
'--disable-parser=mjpeg', '--disable-parser=mjpeg',
'--disable-parser=mlp', '--disable-parser=mlp',
'--disable-parser=mpeg4video', '--disable-parser=mpeg4video',
'--disable-parser=mpegvideo', '--disable-parser=mpegvideo',
'--disable-parser=opus', '--disable-parser=opus',
'--disable-parser=qoi',
'--disable-parser=rv30',
'--disable-parser=rv40',
'--disable-parser=vc1', '--disable-parser=vc1',
'--disable-parser=vp3', '--disable-parser=vp3',
'--disable-parser=vp8', '--disable-parser=vp8',
'--disable-parser=vp9', '--disable-parser=vp9',
'--disable-parser=png', '--disable-parser=png',
'--disable-parser=pnm', '--disable-parser=pnm',
'--disable-parser=webp',
'--disable-parser=xma', '--disable-parser=xma',
'--disable-demuxer=aqtitle', '--disable-demuxer=aqtitle',
@@ -217,6 +229,42 @@ ffmpeg = FfmpegProject(
'--disable-demuxer=h264', '--disable-demuxer=h264',
'--disable-demuxer=ico', '--disable-demuxer=ico',
'--disable-demuxer=image2', '--disable-demuxer=image2',
'--disable-demuxer=image2pipe',
'--disable-demuxer=image_bmp_pipe',
'--disable-demuxer=image_cri_pipe',
'--disable-demuxer=image_dds_pipe',
'--disable-demuxer=image_dpx_pipe',
'--disable-demuxer=image_exr_pipe',
'--disable-demuxer=image_gem_pipe',
'--disable-demuxer=image_gif_pipe',
'--disable-demuxer=image_j2k_pipe',
'--disable-demuxer=image_jpeg_pipe',
'--disable-demuxer=image_jpegls_pipe',
'--disable-demuxer=image_jpegxl_pipe',
'--disable-demuxer=image_pam_pipe',
'--disable-demuxer=image_pbm_pipe',
'--disable-demuxer=image_pcx_pipe',
'--disable-demuxer=image_pfm_pipe',
'--disable-demuxer=image_pgm_pipe',
'--disable-demuxer=image_pgmyuv_pipe',
'--disable-demuxer=image_pgx_pipe',
'--disable-demuxer=image_phm_pipe',
'--disable-demuxer=image_photocd_pipe',
'--disable-demuxer=image_pictor_pipe',
'--disable-demuxer=image_png_pipe',
'--disable-demuxer=image_ppm_pipe',
'--disable-demuxer=image_psd_pipe',
'--disable-demuxer=image_qdraw_pipe',
'--disable-demuxer=image_qoi_pipe',
'--disable-demuxer=image_sgi_pipe',
'--disable-demuxer=image_sunrast_pipe',
'--disable-demuxer=image_svg_pipe',
'--disable-demuxer=image_tiff_pipe',
'--disable-demuxer=image_vbn_pipe',
'--disable-demuxer=image_webp_pipe',
'--disable-demuxer=image_xbm_pipe',
'--disable-demuxer=image_xpm_pipe',
'--disable-demuxer=image_xwd_pipe',
'--disable-demuxer=jacosub', '--disable-demuxer=jacosub',
'--disable-demuxer=lrc', '--disable-demuxer=lrc',
'--disable-demuxer=microdvd', '--disable-demuxer=microdvd',
@@ -239,6 +287,7 @@ ffmpeg = FfmpegProject(
'--disable-demuxer=tedcaptions', '--disable-demuxer=tedcaptions',
'--disable-demuxer=vobsub', '--disable-demuxer=vobsub',
'--disable-demuxer=vplayer', '--disable-demuxer=vplayer',
'--disable-demuxer=webm_dash_manifest',
'--disable-demuxer=webvtt', '--disable-demuxer=webvtt',
'--disable-demuxer=yuv4mpegpipe', '--disable-demuxer=yuv4mpegpipe',
@@ -268,78 +317,179 @@ ffmpeg = FfmpegProject(
'--disable-decoder=qdmc', '--disable-decoder=qdmc',
# disable lots of image and video codecs # disable lots of image and video codecs
'--disable-decoder=acelp_kelvin',
'--disable-decoder=agm',
'--disable-decoder=aic',
'--disable-decoder=alias_pix',
'--disable-decoder=ansi',
'--disable-decoder=apng',
'--disable-decoder=arbc',
'--disable-decoder=argo',
'--disable-decoder=ass', '--disable-decoder=ass',
'--disable-decoder=asv1', '--disable-decoder=asv1',
'--disable-decoder=asv2', '--disable-decoder=asv2',
'--disable-decoder=apng', '--disable-decoder=aura',
'--disable-decoder=aura2',
'--disable-decoder=avrn', '--disable-decoder=avrn',
'--disable-decoder=avrp', '--disable-decoder=avrp',
'--disable-decoder=avui',
'--disable-decoder=ayuv',
'--disable-decoder=bethsoftvid', '--disable-decoder=bethsoftvid',
'--disable-decoder=bfi',
'--disable-decoder=bink', '--disable-decoder=bink',
'--disable-decoder=bintext',
'--disable-decoder=bitpacked',
'--disable-decoder=bmp', '--disable-decoder=bmp',
'--disable-decoder=bmv_video', '--disable-decoder=bmv_video',
'--disable-decoder=brender_pix',
'--disable-decoder=c93',
'--disable-decoder=cavs', '--disable-decoder=cavs',
'--disable-decoder=ccaption', '--disable-decoder=ccaption',
'--disable-decoder=cdgraphics', '--disable-decoder=cdgraphics',
'--disable-decoder=cdtoons',
'--disable-decoder=cdxl',
'--disable-decoder=cfhd',
'--disable-decoder=cinepak',
'--disable-decoder=clearvideo', '--disable-decoder=clearvideo',
'--disable-decoder=cljr',
'--disable-decoder=cllc',
'--disable-decoder=cpia',
'--disable-decoder=cscd',
'--disable-decoder=cyuv',
'--disable-decoder=dds',
'--disable-decoder=dirac', '--disable-decoder=dirac',
'--disable-decoder=dnxhd',
'--disable-decoder=dpx',
'--disable-decoder=dsicinvideo', '--disable-decoder=dsicinvideo',
'--disable-decoder=dvbsub', '--disable-decoder=dvbsub',
'--disable-decoder=dvdsub', '--disable-decoder=dvdsub',
'--disable-decoder=dvvideo', '--disable-decoder=dvvideo',
'--disable-decoder=dxa',
'--disable-decoder=dxtory',
'--disable-decoder=dxv',
'--disable-decoder=eacmv',
'--disable-decoder=eamad',
'--disable-decoder=eatgq',
'--disable-decoder=eatgv',
'--disable-decoder=eatqi',
'--disable-decoder=eightbps',
'--disable-decoder=escape124',
'--disable-decoder=escape130',
'--disable-decoder=exr', '--disable-decoder=exr',
'--disable-decoder=ffv1', '--disable-decoder=ffv1',
'--disable-decoder=ffvhuff', '--disable-decoder=ffvhuff',
'--disable-decoder=ffwavesynth', '--disable-decoder=ffwavesynth',
'--disable-decoder=fic',
'--disable-decoder=fits',
'--disable-decoder=flashsv',
'--disable-decoder=flashsv2',
'--disable-decoder=flic', '--disable-decoder=flic',
'--disable-decoder=flv', '--disable-decoder=flv',
'--disable-decoder=fmvc',
'--disable-decoder=fraps', '--disable-decoder=fraps',
'--disable-decoder=fourxm',
'--disable-decoder=frwu',
'--disable-decoder=g2m',
'--disable-decoder=gdv',
'--disable-decoder=gem',
'--disable-decoder=gif', '--disable-decoder=gif',
'--disable-decoder=h261', '--disable-decoder=h261',
'--disable-decoder=h263', '--disable-decoder=h263',
'--disable-decoder=h263i', '--disable-decoder=h263i',
'--disable-decoder=h263p', '--disable-decoder=h263p',
'--disable-decoder=h264', '--disable-decoder=h264',
'--disable-decoder=hap',
'--disable-decoder=hevc', '--disable-decoder=hevc',
'--disable-decoder=hnm4_video', '--disable-decoder=hnm4_video',
'--disable-decoder=hq_hqa', '--disable-decoder=hq_hqa',
'--disable-decoder=hqx', '--disable-decoder=hqx',
'--disable-decoder=huffyuv',
'--disable-decoder=hymt',
'--disable-decoder=idcin', '--disable-decoder=idcin',
'--disable-decoder=idf',
'--disable-decoder=iff_ilbm', '--disable-decoder=iff_ilbm',
'--disable-decoder=imm4',
'--disable-decoder=indeo2', '--disable-decoder=indeo2',
'--disable-decoder=indeo3', '--disable-decoder=indeo3',
'--disable-decoder=indeo4', '--disable-decoder=indeo4',
'--disable-decoder=indeo5', '--disable-decoder=indeo5',
'--disable-decoder=interplay_video', '--disable-decoder=interplay_video',
'--disable-decoder=ipu',
'--disable-decoder=jacosub', '--disable-decoder=jacosub',
'--disable-decoder=jpeg2000', '--disable-decoder=jpeg2000',
'--disable-decoder=jpegls', '--disable-decoder=jpegls',
'--disable-decoder=jv',
'--disable-decoder=kgv1',
'--disable-decoder=kmvc',
'--disable-decoder=lagarith',
'--disable-decoder=loco',
'--disable-decoder=lscr',
'--disable-decoder=m101',
'--disable-decoder=magicyuv',
'--disable-decoder=mdec',
'--disable-decoder=microdvd', '--disable-decoder=microdvd',
'--disable-decoder=mimic', '--disable-decoder=mimic',
'--disable-decoder=mjpeg', '--disable-decoder=mjpeg',
'--disable-decoder=mmvideo', '--disable-decoder=mmvideo',
'--disable-decoder=mpl2', '--disable-decoder=mpl2',
'--disable-decoder=mobiclip',
'--disable-decoder=motionpixels', '--disable-decoder=motionpixels',
'--disable-decoder=movtext',
'--disable-decoder=mpeg1video', '--disable-decoder=mpeg1video',
'--disable-decoder=mpeg2video', '--disable-decoder=mpeg2video',
'--disable-decoder=mpeg4', '--disable-decoder=mpeg4',
'--disable-decoder=mpegvideo', '--disable-decoder=mpegvideo',
'--disable-decoder=msa1',
'--disable-decoder=mscc', '--disable-decoder=mscc',
'--disable-decoder=msmpeg4_crystalhd', '--disable-decoder=msmpeg4_crystalhd',
'--disable-decoder=msmpeg4v1', '--disable-decoder=msmpeg4v1',
'--disable-decoder=msmpeg4v2', '--disable-decoder=msmpeg4v2',
'--disable-decoder=msmpeg4v3', '--disable-decoder=msmpeg4v3',
'--disable-decoder=msp2',
'--disable-decoder=msrle',
'--disable-decoder=mss1',
'--disable-decoder=msvideo1', '--disable-decoder=msvideo1',
'--disable-decoder=mszh', '--disable-decoder=mszh',
'--disable-decoder=mts2',
'--disable-decoder=mv30',
'--disable-decoder=mvc1', '--disable-decoder=mvc1',
'--disable-decoder=mvc2', '--disable-decoder=mvc2',
'--disable-decoder=mvdv',
'--disable-decoder=mvha',
'--disable-decoder=mwsc',
'--disable-decoder=notchlc',
'--disable-decoder=nuv',
'--disable-decoder=on2avc', '--disable-decoder=on2avc',
'--disable-decoder=paf_video', '--disable-decoder=paf_video',
'--disable-decoder=pam',
'--disable-decoder=pbm',
'--disable-decoder=pcx',
'--disable-decoder=pgm',
'--disable-decoder=pgmyuv',
'--disable-decoder=pgssub',
'--disable-decoder=pgx',
'--disable-decoder=phm',
'--disable-decoder=photocd',
'--disable-decoder=png', '--disable-decoder=png',
'--disable-decoder=pictor',
'--disable-decoder=pixlet',
'--disable-decoder=pjs',
'--disable-decoder=ppm',
'--disable-decoder=prores',
'--disable-decoder=prosumer',
'--disable-decoder=psd',
'--disable-decoder=ptx',
'--disable-decoder=qdraw', '--disable-decoder=qdraw',
'--disable-decoder=qoi',
'--disable-decoder=qpeg', '--disable-decoder=qpeg',
'--disable-decoder=qtrle',
'--disable-decoder=rawvideo', '--disable-decoder=rawvideo',
'--disable-decoder=r10k',
'--disable-decoder=r210',
'--disable-decoder=rasc',
'--disable-decoder=realtext', '--disable-decoder=realtext',
'--disable-decoder=rl2',
'--disable-decoder=rpza',
'--disable-decoder=roq', '--disable-decoder=roq',
'--disable-decoder=roq_dpcm', '--disable-decoder=roq_dpcm',
'--disable-decoder=rscc', '--disable-decoder=rscc',
@@ -348,53 +498,122 @@ ffmpeg = FfmpegProject(
'--disable-decoder=rv30', '--disable-decoder=rv30',
'--disable-decoder=rv40', '--disable-decoder=rv40',
'--disable-decoder=sami', '--disable-decoder=sami',
'--disable-decoder=sanm',
'--disable-decoder=scpr',
'--disable-decoder=screenpresso',
'--disable-decoder=sga',
'--disable-decoder=sgi',
'--disable-decoder=sgirle',
'--disable-decoder=sheervideo', '--disable-decoder=sheervideo',
'--disable-decoder=simbiosis_imx',
'--disable-decoder=smc',
'--disable-decoder=snow', '--disable-decoder=snow',
'--disable-decoder=speedhq',
'--disable-decoder=srgc',
'--disable-decoder=srt', '--disable-decoder=srt',
'--disable-decoder=ssa',
'--disable-decoder=stl', '--disable-decoder=stl',
'--disable-decoder=subrip', '--disable-decoder=subrip',
'--disable-decoder=subviewer', '--disable-decoder=subviewer',
'--disable-decoder=subviewer1', '--disable-decoder=subviewer1',
'--disable-decoder=sunrast',
'--disable-decoder=svq1', '--disable-decoder=svq1',
'--disable-decoder=svq3', '--disable-decoder=svq3',
'--disable-decoder=targa',
'--disable-decoder=targa_y216',
'--disable-decoder=text',
'--disable-decoder=tiff', '--disable-decoder=tiff',
'--disable-decoder=tiertexseqvideo', '--disable-decoder=tiertexseqvideo',
'--disable-decoder=tmv',
'--disable-decoder=truemotion1', '--disable-decoder=truemotion1',
'--disable-decoder=truemotion2', '--disable-decoder=truemotion2',
'--disable-decoder=truemotion2rt', '--disable-decoder=truemotion2rt',
'--disable-decoder=tscc',
'--disable-decoder=tscc2',
'--disable-decoder=twinvq', '--disable-decoder=twinvq',
'--disable-decoder=txd',
'--disable-decoder=ulti',
'--disable-decoder=utvideo', '--disable-decoder=utvideo',
'--disable-decoder=v210',
'--disable-decoder=v210x',
'--disable-decoder=v308',
'--disable-decoder=v408',
'--disable-decoder=v410',
'--disable-decoder=vb',
'--disable-decoder=vble',
'--disable-decoder=vbn',
'--disable-decoder=vc1', '--disable-decoder=vc1',
'--disable-decoder=vcr1',
'--disable-decoder=vmdvideo', '--disable-decoder=vmdvideo',
'--disable-decoder=vmnc',
'--disable-decoder=vp3', '--disable-decoder=vp3',
'--disable-decoder=vp5', '--disable-decoder=vp5',
'--disable-decoder=vp6', '--disable-decoder=vp6',
'--disable-decoder=vp7', '--disable-decoder=vp7',
'--disable-decoder=vp8', '--disable-decoder=vp8',
'--disable-decoder=vp9', '--disable-decoder=vp9',
'--disable-decoder=vplayer',
'--disable-decoder=vqa', '--disable-decoder=vqa',
'--disable-decoder=webvtt', '--disable-decoder=webvtt',
'--disable-decoder=wcmv',
'--disable-decoder=wmv1', '--disable-decoder=wmv1',
'--disable-decoder=wmv2', '--disable-decoder=wmv2',
'--disable-decoder=wmv3', '--disable-decoder=wmv3',
'--disable-decoder=wnv1',
'--disable-decoder=wrapped_avframe',
'--disable-decoder=xan_wc3',
'--disable-decoder=xan_wc4',
'--disable-decoder=xbin',
'--disable-decoder=xbm',
'--disable-decoder=xface',
'--disable-decoder=xl',
'--disable-decoder=xpm',
'--disable-decoder=xsub',
'--disable-decoder=xwd',
'--disable-decoder=y41p',
'--disable-decoder=ylc',
'--disable-decoder=yop',
'--disable-decoder=yuv4', '--disable-decoder=yuv4',
'--disable-decoder=zero12v',
'--disable-decoder=zerocodec',
'--disable-decoder=zlib',
'--disable-decoder=zmbv',
'--disable-bsf=av1_frame_merge',
'--disable-bsf=av1_frame_split',
'--disable-bsf=av1_metadata',
'--disable-bsf=dts2pts',
'--disable-bsf=h264_metadata',
'--disable-bsf=h264_mp4toannexb',
'--disable-bsf=h264_redundant_pps',
'--disable-bsf=hevc_metadata',
'--disable-bsf=hevc_mp4toannexb',
'--disable-bsf=mjpeg2jpeg',
'--disable-bsf=opus_metadata',
'--disable-bsf=pgs_frame_merge',
'--disable-bsf=text2movsub',
'--disable-bsf=vp9_metadata',
'--disable-bsf=vp9_raw_reorder',
'--disable-bsf=vp9_superframe',
'--disable-bsf=vp9_superframe_split',
], ],
) )
openssl = OpenSSLProject( openssl = OpenSSLProject(
'https://www.openssl.org/source/openssl-3.0.5.tar.gz', ('https://www.openssl.org/source/openssl-3.1.3.tar.gz',
'aa7d8d9bef71ad6525c55ba11e5f4397889ce49c2c9349dcea6d3e4f0b024a7a', 'https://artfiles.org/openssl.org/source/openssl-3.1.3.tar.gz'),
'f0316a2ebd89e7f2352976445458689f80302093788c466692fb2a188b2eacf6',
'include/openssl/ossl_typ.h', 'include/openssl/ossl_typ.h',
) )
curl = CmakeProject( curl = CmakeProject(
'https://curl.se/download/curl-7.84.0.tar.xz', ('https://curl.se/download/curl-8.2.1.tar.xz',
'2d118b43f547bfe5bae806d8d47b4e596ea5b25a6c1f080aef49fbcd817c5db8', 'https://github.com/curl/curl/releases/download/curl-8_2_1/curl-8.2.1.tar.xz'),
'dd322f6bd0a20e6cebdfd388f69e98c3d183bed792cf4713c8a7ef498cba4894',
'lib/libcurl.a', 'lib/libcurl.a',
[ [
'-DBUILD_CURL_EXE=OFF', '-DBUILD_CURL_EXE=OFF',
'-DBUILD_SHARED_LIBS=OFF', '-DBUILD_SHARED_LIBS=OFF',
'-DCURL_DISABLE_VERBOSE_STRINGS=ON',
'-DCURL_DISABLE_LDAP=ON', '-DCURL_DISABLE_LDAP=ON',
'-DCURL_DISABLE_TELNET=ON', '-DCURL_DISABLE_TELNET=ON',
'-DCURL_DISABLE_DICT=ON', '-DCURL_DISABLE_DICT=ON',
@@ -423,8 +642,8 @@ curl = CmakeProject(
) )
libnfs = AutotoolsProject( libnfs = AutotoolsProject(
'https://github.com/sahlberg/libnfs/archive/libnfs-5.0.1.tar.gz', 'https://github.com/sahlberg/libnfs/archive/libnfs-5.0.2.tar.gz',
'7ef445410b42f36b9bad426608b53ccb9ccca4101e545c383f564c11db672ca8', '637e56643b19da9fba98f06847788c4dad308b723156a64748041035dcdf9bd3',
'lib/libnfs.a', 'lib/libnfs.a',
[ [
'--disable-shared', '--enable-static', '--disable-shared', '--enable-static',
@@ -435,7 +654,7 @@ libnfs = AutotoolsProject(
'--disable-utils', '--disable-examples', '--disable-utils', '--disable-examples',
], ],
base='libnfs-libnfs-5.0.1', base='libnfs-libnfs-5.0.2',
autoreconf=True, autoreconf=True,
) )
@@ -446,7 +665,7 @@ jack = JackProject(
) )
boost = BoostProject( boost = BoostProject(
'https://boostorg.jfrog.io/artifactory/main/release/1.79.0/source/boost_1_79_0.tar.bz2', 'https://boostorg.jfrog.io/artifactory/main/release/1.81.0/source/boost_1_81_0.tar.bz2',
'475d589d51a7f8b3ba2ba4eda022b170e562ca3b760ee922c146b6c65856ef39', '71feeed900fbccca04a3b4f2f84a7c217186f28a940ed8b7ed4725986baf99fa',
'include/boost/version.hpp', 'include/boost/version.hpp',
) )

@@ -1,28 +1,35 @@
import subprocess import subprocess, multiprocessing
from typing import Optional, Sequence, Union
from build.project import Project from build.project import Project
from .toolchain import AnyToolchain
class MakeProject(Project): class MakeProject(Project):
def __init__(self, url, md5, installed, def __init__(self, url: Union[str, Sequence[str]], md5: str, installed: str,
install_target='install', install_target: str='install',
**kwargs): **kwargs):
Project.__init__(self, url, md5, installed, **kwargs) Project.__init__(self, url, md5, installed, **kwargs)
self.install_target = install_target self.install_target = install_target
def get_simultaneous_jobs(self): def get_simultaneous_jobs(self) -> int:
return 12 try:
# use twice as many simultaneous jobs as we have CPU cores
return multiprocessing.cpu_count() * 2
except NotImplementedError:
# default to 12, if multiprocessing.cpu_count() is not implemented
return 12
def get_make_args(self, toolchain): def get_make_args(self, toolchain: AnyToolchain) -> list[str]:
return ['--quiet', '-j' + str(self.get_simultaneous_jobs())] return ['--quiet', '-j' + str(self.get_simultaneous_jobs())]
def get_make_install_args(self, toolchain): def get_make_install_args(self, toolchain: AnyToolchain) -> list[str]:
return ['--quiet', self.install_target] return ['--quiet', self.install_target]
def make(self, toolchain, wd, args): def make(self, toolchain: AnyToolchain, wd: str, args: list[str]) -> None:
subprocess.check_call(['/usr/bin/make'] + args, subprocess.check_call(['make'] + args,
cwd=wd, env=toolchain.env) cwd=wd, env=toolchain.env)
def build_make(self, toolchain, wd, install=True): def build_make(self, toolchain: AnyToolchain, wd: str, install: bool=True) -> None:
self.make(toolchain, wd, self.get_make_args(toolchain)) self.make(toolchain, wd, self.get_make_args(toolchain))
if install: if install:
self.make(toolchain, wd, self.get_make_install_args(toolchain)) self.make(toolchain, wd, self.get_make_install_args(toolchain))

@@ -1,9 +1,17 @@
import os.path, subprocess, sys import os
import subprocess
import platform import platform
from typing import Optional, Sequence, Union
from build.project import Project from build.project import Project
from .toolchain import AnyToolchain
def make_cross_file(toolchain): def __no_ccache(cmd: str) -> str:
if cmd.startswith('ccache '):
cmd = cmd[7:]
return cmd
def make_cross_file(toolchain: AnyToolchain) -> str:
if toolchain.is_windows: if toolchain.is_windows:
system = 'windows' system = 'windows'
windres = "windres = '%s'" % toolchain.windres windres = "windres = '%s'" % toolchain.windres
@@ -22,7 +30,7 @@ def make_cross_file(toolchain):
cpu = 'arm64-v8a' cpu = 'arm64-v8a'
else: else:
cpu_family = 'x86' cpu_family = 'x86'
if 'x86_64' in toolchain.arch: if 'x86_64' in toolchain.host_triplet:
cpu = 'x86_64' cpu = 'x86_64'
else: else:
cpu = 'i686' cpu = 'i686'
@@ -37,8 +45,8 @@ def make_cross_file(toolchain):
with open(path, 'w') as f: with open(path, 'w') as f:
f.write(f""" f.write(f"""
[binaries] [binaries]
c = '{toolchain.cc}' c = '{__no_ccache(toolchain.cc)}'
cpp = '{toolchain.cxx}' cpp = '{__no_ccache(toolchain.cxx)}'
ar = '{toolchain.ar}' ar = '{toolchain.ar}'
strip = '{toolchain.strip}' strip = '{toolchain.strip}'
pkgconfig = '{toolchain.pkg_config}' pkgconfig = '{toolchain.pkg_config}'
@@ -55,7 +63,7 @@ pkgconfig = '{toolchain.pkg_config}'
root = '{toolchain.install_prefix}' root = '{toolchain.install_prefix}'
""") """)
if 'android' in toolchain.arch: if toolchain.is_android:
f.write(""" f.write("""
# Keep Meson from executing Android-x86 test binariees # Keep Meson from executing Android-x86 test binariees
needs_exe_wrapper = true needs_exe_wrapper = true
@@ -79,21 +87,23 @@ endian = '{endian}'
""") """)
return path return path
def configure(toolchain, src, build, args=()): def configure(toolchain: AnyToolchain, src: str, build: str, args: list[str]=[]) -> None:
cross_file = make_cross_file(toolchain)
configure = [ configure = [
'meson', 'meson', 'setup',
src, build, build, src,
'--prefix', toolchain.install_prefix, '--prefix', toolchain.install_prefix,
'--buildtype', 'plain', '--buildtype', 'plain',
'--default-library=static', '--default-library=static',
'--cross-file', cross_file,
] + args ] + args
if toolchain.host_triplet is not None:
# cross-compiling: write a cross-file
cross_file = make_cross_file(toolchain)
configure.append(f'--cross-file={cross_file}')
env = toolchain.env.copy() env = toolchain.env.copy()
# Meson 0.54 requires the BOOST_ROOT environment variable # Meson 0.54 requires the BOOST_ROOT environment variable
@@ -102,18 +112,19 @@ def configure(toolchain, src, build, args=()):
subprocess.check_call(configure, env=env) subprocess.check_call(configure, env=env)
class MesonProject(Project): class MesonProject(Project):
def __init__(self, url, md5, installed, configure_args=[], def __init__(self, url: Union[str, Sequence[str]], md5: str, installed: str,
configure_args: list[str]=[],
**kwargs): **kwargs):
Project.__init__(self, url, md5, installed, **kwargs) Project.__init__(self, url, md5, installed, **kwargs)
self.configure_args = configure_args self.configure_args = configure_args
def configure(self, toolchain): def configure(self, toolchain: AnyToolchain) -> str:
src = self.unpack(toolchain) src = self.unpack(toolchain)
build = self.make_build_path(toolchain) build = self.make_build_path(toolchain)
configure(toolchain, src, build, self.configure_args) configure(toolchain, src, build, self.configure_args)
return build return build
def _build(self, toolchain): def _build(self, toolchain: AnyToolchain) -> None:
build = self.configure(toolchain) build = self.configure(toolchain)
subprocess.check_call(['ninja', 'install'], subprocess.check_call(['ninja', '-v', 'install'],
cwd=build, env=toolchain.env) cwd=build, env=toolchain.env)

@@ -1,13 +1,15 @@
import subprocess import subprocess
from typing import Optional, Sequence, Union
from build.makeproject import MakeProject from build.makeproject import MakeProject
from .toolchain import AnyToolchain
class OpenSSLProject(MakeProject): class OpenSSLProject(MakeProject):
def __init__(self, url, md5, installed, def __init__(self, url: Union[str, Sequence[str]], md5: str, installed: str,
**kwargs): **kwargs):
MakeProject.__init__(self, url, md5, installed, install_target='install_dev', **kwargs) MakeProject.__init__(self, url, md5, installed, install_target='install_dev', **kwargs)
def get_make_args(self, toolchain): def get_make_args(self, toolchain: AnyToolchain) -> list[str]:
return MakeProject.get_make_args(self, toolchain) + [ return MakeProject.get_make_args(self, toolchain) + [
'CC=' + toolchain.cc, 'CC=' + toolchain.cc,
'CFLAGS=' + toolchain.cflags, 'CFLAGS=' + toolchain.cflags,
@@ -17,45 +19,60 @@ class OpenSSLProject(MakeProject):
'build_libs', 'build_libs',
] ]
def get_make_install_args(self, toolchain): def get_make_install_args(self, toolchain: AnyToolchain) -> list[str]:
# OpenSSL's Makefile runs "ranlib" during installation # OpenSSL's Makefile runs "ranlib" during installation
return MakeProject.get_make_install_args(self, toolchain) + [ return MakeProject.get_make_install_args(self, toolchain) + [
'RANLIB=' + toolchain.ranlib, 'RANLIB=' + toolchain.ranlib,
] ]
def _build(self, toolchain): def _build(self, toolchain: AnyToolchain) -> None:
src = self.unpack(toolchain, out_of_tree=False) src = self.unpack(toolchain, out_of_tree=False)
# OpenSSL has a weird target architecture scheme with lots of # OpenSSL has a weird target architecture scheme with lots of
# hard-coded architectures; this table translates between our # hard-coded architectures; this table translates between our
# "toolchain_arch" (HOST_TRIPLET) and the OpenSSL target # host triplet and the OpenSSL target
openssl_archs = { openssl_archs = {
# not using "android-*" because those OpenSSL targets want # not using "android-*" because those OpenSSL targets want
# to know where the SDK is, but our own build scripts # to know where the SDK is, but our own build scripts
# prepared everything already to look like a regular Linux # prepared everything already to look like a regular Linux
# build # build
'arm-linux-androideabi': 'linux-generic32', 'armv7a-linux-androideabi': 'linux-generic32',
'aarch64-linux-android': 'linux-aarch64', 'aarch64-linux-android': 'linux-aarch64',
'i686-linux-android': 'linux-x86-clang', 'i686-linux-android': 'linux-x86-clang',
'x86_64-linux-android': 'linux-x86_64-clang', 'x86_64-linux-android': 'linux-x86_64-clang',
# Kobo # generic Linux
'arm-linux-gnueabihf': 'linux-generic32', 'arm-linux-gnueabihf': 'linux-generic32',
# Windows # Windows
'i686-w64-mingw32': 'mingw', 'i686-w64-mingw32': 'mingw',
'x86_64-w64-mingw32': 'mingw64', 'x86_64-w64-mingw32': 'mingw64',
# Apple
'x86_64-apple-darwin': 'darwin64-x86_64-cc',
'aarch64-apple-darwin': 'darwin64-arm64-cc',
} }
openssl_arch = openssl_archs[toolchain.arch] configure = [
'./Configure',
'no-shared',
'no-module',
'no-engine',
'no-static-engine',
'no-async',
'no-tests',
'no-makedepend',
'--libdir=lib', # no "lib64" on amd64, please
'--prefix=' + toolchain.install_prefix,
]
subprocess.check_call(['./Configure', if toolchain.is_windows:
'no-shared', # workaround for build failures
'no-module', 'no-engine', 'no-static-engine', configure.append('no-asm')
'no-async',
'no-tests', if toolchain.host_triplet is not None:
'no-asm', # "asm" causes build failures on Windows configure.append(openssl_archs[toolchain.host_triplet])
openssl_arch, configure.append(f'--cross-compile-prefix={toolchain.host_triplet}-')
'--prefix=' + toolchain.install_prefix],
cwd=src, env=toolchain.env) subprocess.check_call(configure, cwd=src, env=toolchain.env)
self.build_make(toolchain, src) self.build_make(toolchain, src)

@@ -1,26 +1,30 @@
import os, shutil import os, shutil
import re import re
from typing import cast, BinaryIO, Optional, Sequence, Union
from build.download import download_and_verify from build.download import download_basename, download_and_verify
from build.tar import untar from build.tar import untar
from build.quilt import push_all from build.quilt import push_all
from .toolchain import AnyToolchain
class Project: class Project:
def __init__(self, url, md5, installed, name=None, version=None, def __init__(self, url: Union[str, Sequence[str]], md5: str, installed: str,
base=None, name: Optional[str]=None, version: Optional[str]=None,
patches=None, base: Optional[str]=None,
patches: Optional[str]=None,
edits=None, edits=None,
use_cxx=False): use_cxx: bool=False):
if base is None: if base is None:
basename = os.path.basename(url) basename = download_basename(url)
m = re.match(r'^(.+)\.(tar(\.(gz|bz2|xz|lzma))?|zip)$', basename) m = re.match(r'^(.+)\.(tar(\.(gz|bz2|xz|lzma))?|zip)$', basename)
if not m: raise if not m: raise RuntimeError('Could not identify tarball name: ' + basename)
self.base = m.group(1) self.base = m.group(1)
else: else:
self.base = base self.base = base
if name is None or version is None: if name is None or version is None:
m = re.match(r'^([-\w]+)-(\d[\d.]*[a-z]?[\d.]*(?:-(?:alpha|beta)\d+)?)(\+.*)?$', self.base) m = re.match(r'^([-\w]+)-(\d[\d.]*[a-z]?[\d.]*(?:-(?:alpha|beta)\d+)?)(\+.*)?$', self.base)
if not m: raise RuntimeError('Could not identify tarball name: ' + self.base)
if name is None: name = m.group(1) if name is None: name = m.group(1)
if version is None: version = m.group(2) if version is None: version = m.group(2)
@@ -38,10 +42,10 @@ class Project:
self.edits = edits self.edits = edits
self.use_cxx = use_cxx self.use_cxx = use_cxx
def download(self, toolchain): def download(self, toolchain: AnyToolchain) -> str:
return download_and_verify(self.url, self.md5, toolchain.tarball_path) return download_and_verify(self.url, self.md5, toolchain.tarball_path)
def is_installed(self, toolchain): def is_installed(self, toolchain: AnyToolchain) -> bool:
tarball = self.download(toolchain) tarball = self.download(toolchain)
installed = os.path.join(toolchain.install_prefix, self.installed) installed = os.path.join(toolchain.install_prefix, self.installed)
tarball_mtime = os.path.getmtime(tarball) tarball_mtime = os.path.getmtime(tarball)
@@ -50,13 +54,13 @@ class Project:
except FileNotFoundError: except FileNotFoundError:
return False return False
def unpack(self, toolchain, out_of_tree=True): def unpack(self, toolchain: AnyToolchain, out_of_tree: bool=True) -> str:
if out_of_tree: if out_of_tree:
parent_path = toolchain.src_path parent_path = toolchain.src_path
else: else:
parent_path = toolchain.build_path parent_path = toolchain.build_path
path = untar(self.download(toolchain), parent_path, self.base) path = untar(self.download(toolchain), parent_path, self.base,
lazy=out_of_tree and self.patches is None)
if self.patches is not None: if self.patches is not None:
push_all(toolchain, path, self.patches) push_all(toolchain, path, self.patches)
@@ -71,8 +75,10 @@ class Project:
return path return path
def make_build_path(self, toolchain): def make_build_path(self, toolchain: AnyToolchain, lazy: bool=False) -> str:
path = os.path.join(toolchain.build_path, self.base) path = os.path.join(toolchain.build_path, self.base)
if lazy and os.path.isdir(path):
return path
try: try:
shutil.rmtree(path) shutil.rmtree(path)
except FileNotFoundError: except FileNotFoundError:
@@ -80,5 +86,5 @@ class Project:
os.makedirs(path, exist_ok=True) os.makedirs(path, exist_ok=True)
return path return path
def build(self, toolchain): def build(self, toolchain: AnyToolchain) -> None:
self._build(toolchain) self._build(toolchain)

@@ -1,9 +1,12 @@
import subprocess import subprocess
from typing import Union
def run_quilt(toolchain, cwd, patches_path, *args): from .toolchain import AnyToolchain
def run_quilt(toolchain: AnyToolchain, cwd: str, patches_path: str, *args: str) -> None:
env = dict(toolchain.env) env = dict(toolchain.env)
env['QUILT_PATCHES'] = patches_path env['QUILT_PATCHES'] = patches_path
subprocess.check_call(['quilt'] + list(args), cwd=cwd, env=env) subprocess.check_call(['quilt'] + list(args), cwd=cwd, env=env)
def push_all(toolchain, src_path, patches_path): def push_all(toolchain: AnyToolchain, src_path: str, patches_path: str) -> None:
run_quilt(toolchain, src_path, patches_path, 'push', '-a') run_quilt(toolchain, src_path, patches_path, 'push', '-a')

@@ -1,14 +1,17 @@
import os, shutil, subprocess import os, shutil, subprocess
def untar(tarball_path, parent_path, base): def untar(tarball_path: str, parent_path: str, base: str,
lazy: bool=False) -> str:
path = os.path.join(parent_path, base) path = os.path.join(parent_path, base)
if lazy and os.path.isdir(path):
return path
try: try:
shutil.rmtree(path) shutil.rmtree(path)
except FileNotFoundError: except FileNotFoundError:
pass pass
os.makedirs(parent_path, exist_ok=True) os.makedirs(parent_path, exist_ok=True)
try: try:
subprocess.check_call(['/bin/tar', 'xfC', tarball_path, parent_path]) subprocess.check_call(['tar', 'xfC', tarball_path, parent_path])
except FileNotFoundError: except FileNotFoundError:
import tarfile import tarfile
tar = tarfile.open(tarball_path) tar = tarfile.open(tarball_path)

175
python/build/toolchain.py Normal file

@@ -0,0 +1,175 @@
import os.path
import shutil
from typing import Union
android_abis = {
'armeabi-v7a': {
'arch': 'armv7a-linux-androideabi',
'ndk_arch': 'arm',
'cflags': '-fpic -mfpu=neon -mfloat-abi=softfp',
},
'arm64-v8a': {
'arch': 'aarch64-linux-android',
'ndk_arch': 'arm64',
'cflags': '-fpic',
},
'x86': {
'arch': 'i686-linux-android',
'ndk_arch': 'x86',
'cflags': '-fPIC -march=i686 -mtune=intel -mssse3 -mfpmath=sse -m32',
},
'x86_64': {
'arch': 'x86_64-linux-android',
'ndk_arch': 'x86_64',
'cflags': '-fPIC -m64',
},
}
class AndroidNdkToolchain:
def __init__(self, top_path: str, lib_path: str,
tarball_path: str, src_path: str,
ndk_path: str, android_abi: str,
use_cxx):
# build host configuration
build_arch = 'linux-x86_64'
# select the NDK target
abi_info = android_abis[android_abi]
host_triplet = abi_info['arch']
arch_path = os.path.join(lib_path, host_triplet)
self.tarball_path = tarball_path
self.src_path = src_path
self.build_path = os.path.join(arch_path, 'build')
ndk_arch = abi_info['ndk_arch']
android_api_level = '24'
install_prefix = os.path.join(arch_path, 'root')
self.host_triplet = host_triplet
self.install_prefix = install_prefix
llvm_path = os.path.join(ndk_path, 'toolchains', 'llvm', 'prebuilt', build_arch)
llvm_triple = host_triplet + android_api_level
common_flags = '-Os -g'
common_flags += ' ' + abi_info['cflags']
llvm_bin = os.path.join(llvm_path, 'bin')
self.cc = os.path.join(llvm_bin, 'clang')
self.cxx = os.path.join(llvm_bin, 'clang++')
common_flags += ' -target ' + llvm_triple
common_flags += ' -fvisibility=hidden -fdata-sections -ffunction-sections'
self.ar = os.path.join(llvm_bin, 'llvm-ar')
self.arflags = 'rcs'
self.ranlib = os.path.join(llvm_bin, 'llvm-ranlib')
self.nm = os.path.join(llvm_bin, 'llvm-nm')
self.strip = os.path.join(llvm_bin, 'llvm-strip')
self.cflags = common_flags
self.cxxflags = common_flags
self.cppflags = ' -isystem ' + os.path.join(install_prefix, 'include')
self.ldflags = ' -L' + os.path.join(install_prefix, 'lib') + \
' -Wl,--exclude-libs=ALL' + \
' ' + common_flags
self.ldflags = common_flags
self.libs = ''
self.is_arm = ndk_arch == 'arm'
self.is_armv7 = self.is_arm and 'armv7' in self.cflags
self.is_aarch64 = ndk_arch == 'arm64'
self.is_windows = False
self.is_android = True
self.is_darwin = False
libstdcxx_flags = ''
libstdcxx_cxxflags = ''
libstdcxx_ldflags = ''
libstdcxx_libs = '-static-libstdc++'
if self.is_armv7:
# On 32 bit ARM, clang generates no ".eh_frame" section;
# instead, the LLVM unwinder library is used for unwinding
# the stack after a C++ exception was thrown
libstdcxx_libs += ' -lunwind'
if use_cxx:
self.cxxflags += ' ' + libstdcxx_cxxflags
self.ldflags += ' ' + libstdcxx_ldflags
self.libs += ' ' + libstdcxx_libs
self.env = dict(os.environ)
# redirect pkg-config to use our root directory instead of the
# default one on the build host
bin_dir = os.path.join(install_prefix, 'bin')
os.makedirs(bin_dir, exist_ok=True)
self.pkg_config = shutil.copy(os.path.join(top_path, 'build', 'pkg-config.sh'),
os.path.join(bin_dir, 'pkg-config'))
self.env['PKG_CONFIG'] = self.pkg_config
class MingwToolchain:
def __init__(self, top_path: str,
toolchain_path, host_triplet, x64: bool,
tarball_path, src_path, build_path, install_prefix):
self.host_triplet = host_triplet
self.tarball_path = tarball_path
self.src_path = src_path
self.build_path = build_path
self.install_prefix = install_prefix
toolchain_bin = os.path.join(toolchain_path, 'bin')
self.cc = os.path.join(toolchain_bin, host_triplet + '-gcc')
self.cxx = os.path.join(toolchain_bin, host_triplet + '-g++')
self.ar = os.path.join(toolchain_bin, host_triplet + '-ar')
self.arflags = 'rcs'
self.ranlib = os.path.join(toolchain_bin, host_triplet + '-ranlib')
self.nm = os.path.join(toolchain_bin, host_triplet + '-nm')
self.strip = os.path.join(toolchain_bin, host_triplet + '-strip')
self.windres = os.path.join(toolchain_bin, host_triplet + '-windres')
common_flags = '-O2 -g'
if not x64:
# enable SSE support which is required for LAME
common_flags += ' -march=pentium3'
self.cflags = common_flags
self.cxxflags = common_flags
self.cppflags = '-isystem ' + os.path.join(install_prefix, 'include') + \
' -DWINVER=0x0600 -D_WIN32_WINNT=0x0600'
self.ldflags = '-L' + os.path.join(install_prefix, 'lib') + \
' -static-libstdc++ -static-libgcc'
self.libs = ''
# Explicitly disable _FORTIFY_SOURCE because it is broken with
# mingw. This prevents some libraries such as libFLAC to
# enable it.
self.cppflags += ' -D_FORTIFY_SOURCE=0'
self.is_arm = host_triplet.startswith('arm')
self.is_armv7 = self.is_arm and 'armv7' in self.cflags
self.is_aarch64 = host_triplet == 'aarch64'
self.is_windows = 'mingw32' in host_triplet
self.is_android = False
self.is_darwin = False
self.env = dict(os.environ)
# redirect pkg-config to use our root directory instead of the
# default one on the build host
import shutil
bin_dir = os.path.join(install_prefix, 'bin')
os.makedirs(bin_dir, exist_ok=True)
self.pkg_config = shutil.copy(os.path.join(top_path, 'build', 'pkg-config.sh'),
os.path.join(bin_dir, 'pkg-config'))
self.env['PKG_CONFIG'] = self.pkg_config
AnyToolchain = Union[AndroidNdkToolchain, MingwToolchain]

@@ -1,6 +1,7 @@
import hashlib import hashlib
from typing import cast, Any, BinaryIO
def feed_file(h, f): def feed_file(h: Any, f: BinaryIO) -> None:
"""Feed data read from an open file into the hashlib instance.""" """Feed data read from an open file into the hashlib instance."""
while True: while True:
@@ -10,20 +11,20 @@ def feed_file(h, f):
break break
h.update(data) h.update(data)
def feed_file_path(h, path): def feed_file_path(h: Any, path: str) -> None:
"""Feed data read from a file (to be opened by this function) into the hashlib instance.""" """Feed data read from a file (to be opened by this function) into the hashlib instance."""
with open(path, 'rb') as f: with open(path, 'rb') as f:
feed_file(h, f) feed_file(h, f)
def file_digest(algorithm, path): def file_digest(algorithm: Any, path: str) -> str:
"""Calculate the digest of a file and return it in hexadecimal notation.""" """Calculate the digest of a file and return it in hexadecimal notation."""
h = algorithm() h = algorithm()
feed_file_path(h, path) feed_file_path(h, path)
return h.hexdigest() return cast(str, h.hexdigest())
def guess_digest_algorithm(digest): def guess_digest_algorithm(digest: str) -> Any:
l = len(digest) l = len(digest)
if l == 32: if l == 32:
return hashlib.md5 return hashlib.md5
@@ -36,7 +37,7 @@ def guess_digest_algorithm(digest):
else: else:
return None return None
def verify_file_digest(path, expected_digest): def verify_file_digest(path: str, expected_digest: str) -> bool:
"""Verify the digest of a file, and return True if the digest matches with the given expected digest.""" """Verify the digest of a file, and return True if the digest matches with the given expected digest."""
algorithm = guess_digest_algorithm(expected_digest) algorithm = guess_digest_algorithm(expected_digest)

@@ -1,22 +1,34 @@
import os.path, subprocess import subprocess
from typing import Optional, Sequence, Union
from build.project import Project from build.makeproject import MakeProject
from .toolchain import AnyToolchain
class ZlibProject(Project): class ZlibProject(MakeProject):
def __init__(self, url, md5, installed, def __init__(self, url: Union[str, Sequence[str]], md5: str, installed: str,
**kwargs): **kwargs):
Project.__init__(self, url, md5, installed, **kwargs) MakeProject.__init__(self, url, md5, installed, **kwargs)
def _build(self, toolchain): def get_make_args(self, toolchain: AnyToolchain) -> list[str]:
return MakeProject.get_make_args(self, toolchain) + [
'CC=' + toolchain.cc + ' ' + toolchain.cppflags + ' ' + toolchain.cflags,
'CPP=' + toolchain.cc + ' -E ' + toolchain.cppflags,
'AR=' + toolchain.ar,
'ARFLAGS=' + toolchain.arflags,
'RANLIB=' + toolchain.ranlib,
'LDSHARED=' + toolchain.cc + ' -shared',
'libz.a'
]
def get_make_install_args(self, toolchain: AnyToolchain) -> list[str]:
return [
'RANLIB=' + toolchain.ranlib,
self.install_target
]
def _build(self, toolchain: AnyToolchain) -> None:
src = self.unpack(toolchain, out_of_tree=False) src = self.unpack(toolchain, out_of_tree=False)
subprocess.check_call(['/usr/bin/make', '--quiet', subprocess.check_call(['./configure', '--prefix=' + toolchain.install_prefix, '--static'],
'-f', 'win32/Makefile.gcc', cwd=src, env=toolchain.env)
'PREFIX=' + toolchain.arch + '-', self.build_make(toolchain, src)
'-j12',
'install',
'INCLUDE_PATH='+ os.path.join(toolchain.install_prefix, 'include'),
'LIBRARY_PATH=' + os.path.join(toolchain.install_prefix, 'lib'),
'BINARY_PATH=' + os.path.join(toolchain.install_prefix, 'bin'),
],
cwd=src, env=toolchain.env)

@@ -352,12 +352,16 @@ ParseCommandLine(int argc, char **argv, CommandLineOptions &options,
break; break;
case OPTION_NO_DAEMON: case OPTION_NO_DAEMON:
#ifdef ENABLE_DAEMON
options.daemon = false; options.daemon = false;
#endif
break; break;
#ifdef __linux__ #ifdef __linux__
case OPTION_SYSTEMD: case OPTION_SYSTEMD:
#ifdef ENABLE_DAEMON
options.daemon = false; options.daemon = false;
#endif
options.systemd = true; options.systemd = true;
break; break;
#endif #endif

@@ -20,11 +20,18 @@
#ifndef MPD_COMMAND_LINE_HXX #ifndef MPD_COMMAND_LINE_HXX
#define MPD_COMMAND_LINE_HXX #define MPD_COMMAND_LINE_HXX
#include "config.h" // for ENABLE_DAEMON
struct ConfigData; struct ConfigData;
struct CommandLineOptions { struct CommandLineOptions {
bool kill = false; bool kill = false;
#ifdef ENABLE_DAEMON
bool daemon = true; bool daemon = true;
#else
static constexpr bool daemon = false;
#endif
#ifdef __linux__ #ifdef __linux__
bool systemd = false; bool systemd = false;

@@ -158,12 +158,15 @@ log_init(const ConfigData &config, bool verbose, bool use_stdout)
getenv("NOTIFY_SOCKET") != nullptr) { getenv("NOTIFY_SOCKET") != nullptr) {
/* if MPD was started as a systemd /* if MPD was started as a systemd
service, default to journal (which service, default to journal (which
is connected to fd=2) */ is connected to stdout&stderr) */
out_fd = STDOUT_FILENO; out_fd = STDOUT_FILENO;
return; return;
} }
#endif #endif
#ifndef HAVE_SYSLOG #ifdef _WIN32
/* default to stdout on Windows */
out_fd = STDOUT_FILENO;
#elif !defined(HAVE_SYSLOG)
throw std::runtime_error("config parameter 'log_file' not found"); throw std::runtime_error("config parameter 'log_file' not found");
#endif #endif
#ifdef HAVE_SYSLOG #ifdef HAVE_SYSLOG

@@ -482,7 +482,10 @@ MainConfigured(const CommandLineOptions &options,
#ifndef ANDROID #ifndef ANDROID
setup_log_output(); setup_log_output();
const ScopeSignalHandlersInit signal_handlers_init(instance); const ScopeSignalHandlersInit signal_handlers_init{
instance,
options.daemon,
};
#endif #endif
instance.io_thread.Start(); instance.io_thread.Start();
@@ -590,19 +593,46 @@ MainConfigured(const CommandLineOptions &options,
#ifdef ANDROID #ifdef ANDROID
/**
* Wrapper for ReadConfigFile() which returns false if the file was
* not found.
*/
static bool
TryReadConfigFile(ConfigData &config, Path path)
{
if (!FileExists(path))
return false;
ReadConfigFile(config, path);
return true;
}
static void static void
AndroidMain() LoadConfigFile(JNIEnv *env, ConfigData &config)
{
/* try loading mpd.conf from
"Android/data/org.musicpd/files/mpd.conf" (the app specific
data directory) first */
if (const auto dir = context->GetExternalFilesDir(env);
!dir.IsNull() &&
TryReadConfigFile(config, dir / Path::FromFS("mpd.conf")))
return;
/* if that fails, attempt to load "mpd.conf" from the root of
the SD card (pre-0.23.9, ceases to work since Android
12) */
if (const auto dir = Environment::getExternalStorageDirectory(env);
!dir.IsNull())
TryReadConfigFile(config, dir / Path::FromFS("mpd.conf"));
}
static void
AndroidMain(JNIEnv *env)
{ {
CommandLineOptions options; CommandLineOptions options;
ConfigData raw_config; ConfigData raw_config;
const auto sdcard = Environment::getExternalStorageDirectory(); LoadConfigFile(env, raw_config);
if (!sdcard.IsNull()) {
const auto config_path =
sdcard / Path::FromFS("mpd.conf");
if (FileExists(config_path))
ReadConfigFile(raw_config, config_path);
}
MainConfigured(options, raw_config); MainConfigured(options, raw_config);
} }
@@ -614,9 +644,12 @@ Java_org_musicpd_Bridge_run(JNIEnv *env, jclass, jobject _context, jobject _logL
Java::Init(env); Java::Init(env);
Java::Object::Initialise(env); Java::Object::Initialise(env);
Java::File::Initialise(env); Java::File::Initialise(env);
Environment::Initialise(env); Environment::Initialise(env);
AtScopeExit(env) { Environment::Deinitialise(env); }; AtScopeExit(env) { Environment::Deinitialise(env); };
Context::Initialise(env);
context = new Context(env, _context); context = new Context(env, _context);
AtScopeExit() { delete context; }; AtScopeExit() { delete context; };
@@ -625,7 +658,7 @@ Java_org_musicpd_Bridge_run(JNIEnv *env, jclass, jobject _context, jobject _logL
AtScopeExit() { delete logListener; }; AtScopeExit() { delete logListener; };
try { try {
AndroidMain(); AndroidMain(env);
} catch (...) { } catch (...) {
LogError(std::current_exception()); LogError(std::current_exception());
} }

@@ -23,7 +23,6 @@
#include "Log.hxx" #include "Log.hxx"
#include "lib/fmt/ExceptionFormatter.hxx" #include "lib/fmt/ExceptionFormatter.hxx"
#include "song/DetachedSong.hxx" #include "song/DetachedSong.hxx"
#include "mixer/Volume.hxx"
#include "IdleFlags.hxx" #include "IdleFlags.hxx"
#include "client/Listener.hxx" #include "client/Listener.hxx"
#include "client/Client.hxx" #include "client/Client.hxx"
@@ -206,7 +205,7 @@ Partition::OnBorderPause() noexcept
void void
Partition::OnMixerVolumeChanged(Mixer &, int) noexcept Partition::OnMixerVolumeChanged(Mixer &, int) noexcept
{ {
InvalidateHardwareVolume(); mixer_memento.InvalidateHardwareVolume();
/* notify clients */ /* notify clients */
EmitIdle(IDLE_MIXER); EmitIdle(IDLE_MIXER);

@@ -25,6 +25,7 @@
#include "queue/Listener.hxx" #include "queue/Listener.hxx"
#include "output/MultipleOutputs.hxx" #include "output/MultipleOutputs.hxx"
#include "mixer/Listener.hxx" #include "mixer/Listener.hxx"
#include "mixer/Memento.hxx"
#include "player/Control.hxx" #include "player/Control.hxx"
#include "player/Listener.hxx" #include "player/Listener.hxx"
#include "protocol/RangeArg.hxx" #include "protocol/RangeArg.hxx"
@@ -76,6 +77,8 @@ struct Partition final : QueueListener, PlayerListener, MixerListener {
MultipleOutputs outputs; MultipleOutputs outputs;
MixerMemento mixer_memento;
PlayerControl pc; PlayerControl pc;
ReplayGainMode replay_gain_mode = ReplayGainMode::OFF; ReplayGainMode replay_gain_mode = ReplayGainMode::OFF;

@@ -81,6 +81,9 @@ spl_valid_name(const char *name_utf8)
*/ */
return std::strchr(name_utf8, '/') == nullptr && return std::strchr(name_utf8, '/') == nullptr &&
#ifdef _WIN32
std::strchr(name_utf8, '\\') == nullptr &&
#endif
std::strchr(name_utf8, '\n') == nullptr && std::strchr(name_utf8, '\n') == nullptr &&
std::strchr(name_utf8, '\r') == nullptr; std::strchr(name_utf8, '\r') == nullptr;
} }

@@ -28,7 +28,11 @@
#include <boost/intrusive/list.hpp> #include <boost/intrusive/list.hpp>
#include <boost/intrusive/unordered_set.hpp> #include <boost/intrusive/unordered_set.hpp>
#include <array>
#include <functional>
#include <memory>
#include <string> #include <string>
#include <utility>
class RemoteTagCacheHandler; class RemoteTagCacheHandler;

@@ -24,6 +24,7 @@
#include "TagPrint.hxx" #include "TagPrint.hxx"
#include "client/Response.hxx" #include "client/Response.hxx"
#include "fs/Traits.hxx" #include "fs/Traits.hxx"
#include "lib/fmt/AudioFormatFormatter.hxx"
#include "time/ChronoUtil.hxx" #include "time/ChronoUtil.hxx"
#include "util/StringBuffer.hxx" #include "util/StringBuffer.hxx"
#include "util/UriUtil.hxx" #include "util/UriUtil.hxx"
@@ -93,7 +94,7 @@ song_print_info(Response &r, const LightSong &song, bool base) noexcept
time_print(r, "Last-Modified", song.mtime); time_print(r, "Last-Modified", song.mtime);
if (song.audio_format.IsDefined()) if (song.audio_format.IsDefined())
r.Fmt(FMT_STRING("Format: {}\n"), ToString(song.audio_format)); r.Fmt(FMT_STRING("Format: {}\n"), song.audio_format);
tag_print_values(r, song.tag); tag_print_values(r, song.tag);
@@ -116,7 +117,7 @@ song_print_info(Response &r, const DetachedSong &song, bool base) noexcept
time_print(r, "Last-Modified", song.GetLastModified()); time_print(r, "Last-Modified", song.GetLastModified());
if (const auto &f = song.GetAudioFormat(); f.IsDefined()) if (const auto &f = song.GetAudioFormat(); f.IsDefined())
r.Fmt(FMT_STRING("Format: {}\n"), ToString(f)); r.Fmt(FMT_STRING("Format: {}\n"), f);
tag_print_values(r, song.GetTag()); tag_print_values(r, song.GetTag());

@@ -63,6 +63,9 @@ song_save(BufferedOutputStream &os, const Song &song)
if (song.audio_format.IsDefined()) if (song.audio_format.IsDefined())
os.Format("Format: %s\n", ToString(song.audio_format).c_str()); os.Format("Format: %s\n", ToString(song.audio_format).c_str());
if (song.in_playlist)
os.Write("InPlaylist: yes\n");
if (!IsNegative(song.mtime)) if (!IsNegative(song.mtime))
os.Format(SONG_MTIME ": %li\n", os.Format(SONG_MTIME ": %li\n",
(long)std::chrono::system_clock::to_time_t(song.mtime)); (long)std::chrono::system_clock::to_time_t(song.mtime));
@@ -86,7 +89,7 @@ song_save(BufferedOutputStream &os, const DetachedSong &song)
DetachedSong DetachedSong
song_load(LineReader &file, const char *uri, song_load(LineReader &file, const char *uri,
std::string *target_r) std::string *target_r, bool *in_playlist_r)
{ {
DetachedSong song(uri); DetachedSong song(uri);
@@ -132,6 +135,9 @@ song_load(LineReader &file, const char *uri,
song.SetStartTime(SongTime::FromMS(start_ms)); song.SetStartTime(SongTime::FromMS(start_ms));
song.SetEndTime(SongTime::FromMS(end_ms)); song.SetEndTime(SongTime::FromMS(end_ms));
} else if (StringIsEqual(line, "InPlaylist")) {
if (in_playlist_r != nullptr)
*in_playlist_r = StringIsEqual(value, "yes");
} else { } else {
throw FormatRuntimeError("unknown line in db: %s", line); throw FormatRuntimeError("unknown line in db: %s", line);
} }

@@ -44,6 +44,6 @@ song_save(BufferedOutputStream &os, const DetachedSong &song);
*/ */
DetachedSong DetachedSong
song_load(LineReader &file, const char *uri, song_load(LineReader &file, const char *uri,
std::string *target_r=nullptr); std::string *target_r=nullptr, bool *in_playlist_r=nullptr);
#endif #endif

@@ -27,7 +27,6 @@
#include "storage/StorageState.hxx" #include "storage/StorageState.hxx"
#include "Partition.hxx" #include "Partition.hxx"
#include "Instance.hxx" #include "Instance.hxx"
#include "mixer/Volume.hxx"
#include "SongLoader.hxx" #include "SongLoader.hxx"
#include "util/Domain.hxx" #include "util/Domain.hxx"
#include "Log.hxx" #include "Log.hxx"
@@ -47,7 +46,7 @@ StateFile::StateFile(StateFileConfig &&_config,
void void
StateFile::RememberVersions() noexcept StateFile::RememberVersions() noexcept
{ {
prev_volume_version = sw_volume_state_get_hash(); prev_volume_version = partition.mixer_memento.GetSoftwareVolumeStateHash();
prev_output_version = audio_output_state_get_version(); prev_output_version = audio_output_state_get_version();
prev_playlist_version = playlist_state_get_hash(partition.playlist, prev_playlist_version = playlist_state_get_hash(partition.playlist,
partition.pc); partition.pc);
@@ -59,7 +58,7 @@ StateFile::RememberVersions() noexcept
bool bool
StateFile::IsModified() const noexcept StateFile::IsModified() const noexcept
{ {
return prev_volume_version != sw_volume_state_get_hash() || return prev_volume_version != partition.mixer_memento.GetSoftwareVolumeStateHash() ||
prev_output_version != audio_output_state_get_version() || prev_output_version != audio_output_state_get_version() ||
prev_playlist_version != playlist_state_get_hash(partition.playlist, prev_playlist_version != playlist_state_get_hash(partition.playlist,
partition.pc) partition.pc)
@@ -72,7 +71,7 @@ StateFile::IsModified() const noexcept
inline void inline void
StateFile::Write(BufferedOutputStream &os) StateFile::Write(BufferedOutputStream &os)
{ {
save_sw_volume_state(os); partition.mixer_memento.SaveSoftwareVolumeState(os);
audio_output_state_save(os, partition.outputs); audio_output_state_save(os, partition.outputs);
#ifdef ENABLE_DATABASE #ifdef ENABLE_DATABASE
@@ -125,7 +124,7 @@ try {
const char *line; const char *line;
while ((line = file.ReadLine()) != nullptr) { while ((line = file.ReadLine()) != nullptr) {
success = read_sw_volume_state(line, partition.outputs) || success = partition.mixer_memento.LoadSoftwareVolumeState(line, partition.outputs) ||
audio_output_state_read(line, partition.outputs) || audio_output_state_read(line, partition.outputs) ||
playlist_state_restore(config, line, file, song_loader, playlist_state_restore(config, line, file, song_loader,
partition.playlist, partition.playlist,

@@ -32,7 +32,7 @@ StateFileConfig::StateFileConfig(const ConfigData &config)
{ {
#ifdef ANDROID #ifdef ANDROID
if (path.IsNull()) { if (path.IsNull()) {
const auto cache_dir = GetUserCacheDir(); const auto cache_dir = GetAppCacheDir();
if (cache_dir.IsNull()) if (cache_dir.IsNull())
return; return;

@@ -35,8 +35,9 @@ tag_print_types(Response &r) noexcept
} }
void void
tag_print(Response &r, TagType type, StringView value) noexcept tag_print(Response &r, TagType type, StringView _value) noexcept
{ {
const std::string_view value{_value};
r.Fmt(FMT_STRING("{}: {}\n"), tag_item_names[type], value); r.Fmt(FMT_STRING("{}: {}\n"), tag_item_names[type], value);
} }

@@ -36,5 +36,5 @@ time_print(Response &r, const char *name,
return; return;
} }
r.Fmt(FMT_STRING("{}: {}\n"), name, s); r.Fmt(FMT_STRING("{}: {}\n"), name, s.c_str());
} }

@@ -26,19 +26,30 @@
#include "AudioManager.hxx" #include "AudioManager.hxx"
AllocatedPath static jmethodID getExternalFilesDir_method,
Context::GetExternalFilesDir(JNIEnv *env, const char *_type) noexcept getCacheDir_method,
getSystemService_method;
void
Context::Initialise(JNIEnv *env) noexcept
{ {
assert(_type != nullptr); Java::Class cls{env, "android/content/Context"};
Java::Class cls{env, env->GetObjectClass(Get())}; getExternalFilesDir_method = env->GetMethodID(cls, "getExternalFilesDir",
jmethodID method = env->GetMethodID(cls, "getExternalFilesDir", "(Ljava/lang/String;)Ljava/io/File;");
"(Ljava/lang/String;)Ljava/io/File;"); getCacheDir_method = env->GetMethodID(cls, "getCacheDir",
assert(method); "()Ljava/io/File;");
getSystemService_method = env->GetMethodID(cls, "getSystemService",
"(Ljava/lang/String;)Ljava/lang/Object;");
}
Java::String type{env, _type}; AllocatedPath
Context::GetExternalFilesDir(JNIEnv *env, const char *type) noexcept
{
assert(type != nullptr);
jobject file = env->CallObjectMethod(Get(), method, type.Get()); jobject file = env->CallObjectMethod(Get(), getExternalFilesDir_method,
Java::String::Optional(env, type).Get());
if (Java::DiscardException(env) || file == nullptr) if (Java::DiscardException(env) || file == nullptr)
return nullptr; return nullptr;
@@ -50,12 +61,7 @@ Context::GetCacheDir(JNIEnv *env) const noexcept
{ {
assert(env != nullptr); assert(env != nullptr);
Java::Class cls(env, env->GetObjectClass(Get())); jobject file = env->CallObjectMethod(Get(), getCacheDir_method);
jmethodID method = env->GetMethodID(cls, "getCacheDir",
"()Ljava/io/File;");
assert(method);
jobject file = env->CallObjectMethod(Get(), method);
if (Java::DiscardException(env) || file == nullptr) if (Java::DiscardException(env) || file == nullptr)
return nullptr; return nullptr;
@@ -67,13 +73,8 @@ Context::GetAudioManager(JNIEnv *env) noexcept
{ {
assert(env != nullptr); assert(env != nullptr);
Java::Class cls(env, env->GetObjectClass(Get()));
jmethodID method = env->GetMethodID(cls, "getSystemService",
"(Ljava/lang/String;)Ljava/lang/Object;");
assert(method);
Java::String name(env, "audio"); Java::String name(env, "audio");
jobject am = env->CallObjectMethod(Get(), method, name.Get()); jobject am = env->CallObjectMethod(Get(), getSystemService_method, name.Get());
if (Java::DiscardException(env) || am == nullptr) if (Java::DiscardException(env) || am == nullptr)
return nullptr; return nullptr;

@@ -27,12 +27,21 @@ class AudioManager;
class Context : public Java::GlobalObject { class Context : public Java::GlobalObject {
public: public:
/**
* Global initialisation. Looks up the methods of the
* Context Java class.
*/
static void Initialise(JNIEnv *env) noexcept;
Context(JNIEnv *env, jobject obj) noexcept Context(JNIEnv *env, jobject obj) noexcept
:Java::GlobalObject(env, obj) {} :Java::GlobalObject(env, obj) {}
/**
* @param type the subdirectory name; may be nullptr
*/
[[gnu::pure]] [[gnu::pure]]
AllocatedPath GetExternalFilesDir(JNIEnv *env, AllocatedPath GetExternalFilesDir(JNIEnv *env,
const char *type) noexcept; const char *type=nullptr) noexcept;
[[gnu::pure]] [[gnu::pure]]
AllocatedPath GetCacheDir(JNIEnv *env) const noexcept; AllocatedPath GetCacheDir(JNIEnv *env) const noexcept;

@@ -25,13 +25,13 @@
#include "fs/AllocatedPath.hxx" #include "fs/AllocatedPath.hxx"
namespace Environment { namespace Environment {
static Java::TrivialClass cls;
static jmethodID getExternalStorageDirectory_method; static Java::TrivialClass cls;
static jmethodID getExternalStoragePublicDirectory_method; static jmethodID getExternalStorageDirectory_method;
} static jmethodID getExternalStoragePublicDirectory_method;
void void
Environment::Initialise(JNIEnv *env) noexcept Initialise(JNIEnv *env) noexcept
{ {
cls.Find(env, "android/os/Environment"); cls.Find(env, "android/os/Environment");
@@ -45,16 +45,14 @@ Environment::Initialise(JNIEnv *env) noexcept
} }
void void
Environment::Deinitialise(JNIEnv *env) noexcept Deinitialise(JNIEnv *env) noexcept
{ {
cls.Clear(env); cls.Clear(env);
} }
AllocatedPath AllocatedPath
Environment::getExternalStorageDirectory() noexcept getExternalStorageDirectory(JNIEnv *env) noexcept
{ {
JNIEnv *env = Java::GetEnv();
jobject file = jobject file =
env->CallStaticObjectMethod(cls, env->CallStaticObjectMethod(cls,
getExternalStorageDirectory_method); getExternalStorageDirectory_method);
@@ -65,20 +63,20 @@ Environment::getExternalStorageDirectory() noexcept
} }
AllocatedPath AllocatedPath
Environment::getExternalStoragePublicDirectory(const char *type) noexcept getExternalStoragePublicDirectory(JNIEnv *env, const char *type) noexcept
{ {
if (getExternalStoragePublicDirectory_method == nullptr) if (getExternalStoragePublicDirectory_method == nullptr)
/* needs API level 8 */ /* needs API level 8 */
return nullptr; return nullptr;
JNIEnv *env = Java::GetEnv();
Java::String type2(env, type); Java::String type2(env, type);
jobject file = env->CallStaticObjectMethod(Environment::cls, jobject file = env->CallStaticObjectMethod(cls,
Environment::getExternalStoragePublicDirectory_method, getExternalStoragePublicDirectory_method,
type2.Get()); type2.Get());
if (file == nullptr) if (file == nullptr)
return nullptr; return nullptr;
return Java::File::ToAbsolutePath(env, file); return Java::File::ToAbsolutePath(env, file);
} }
} // namespace Environment

@@ -17,27 +17,29 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/ */
#ifndef MPD_ANDROID_ENVIRONMENT_HXX #pragma once
#define MPD_ANDROID_ENVIRONMENT_HXX
#include "util/Compiler.h"
#include <jni.h> #include <jni.h>
class AllocatedPath; class AllocatedPath;
namespace Environment { namespace Environment {
void Initialise(JNIEnv *env) noexcept;
void Deinitialise(JNIEnv *env) noexcept;
/** void
* Determine the mount point of the external SD card. Initialise(JNIEnv *env) noexcept;
*/
[[gnu::pure]]
AllocatedPath getExternalStorageDirectory() noexcept;
[[gnu::pure]] void
AllocatedPath getExternalStoragePublicDirectory(const char *type) noexcept; Deinitialise(JNIEnv *env) noexcept;
}
#endif /**
* Determine the mount point of the external SD card.
*/
[[gnu::pure]]
AllocatedPath
getExternalStorageDirectory(JNIEnv *env) noexcept;
[[gnu::pure]]
AllocatedPath
getExternalStoragePublicDirectory(JNIEnv *env, const char *type) noexcept;
} // namespace Environment

@@ -166,7 +166,7 @@ class Iso9660InputStream final : public InputStream {
assert(fill <= data.size()); assert(fill <= data.size());
assert(position <= fill); assert(position <= fill);
return {&data[position], &data[fill]}; return {data.data() + position, data.data() + fill};
} }
void Consume(size_t nbytes) noexcept { void Consume(size_t nbytes) noexcept {

@@ -22,6 +22,10 @@ if libzzip_dep.found()
found_archive_plugin = true found_archive_plugin = true
endif endif
if not found_archive_plugin
subdir_done()
endif
archive_plugins = static_library( archive_plugins = static_library(
'archive_plugins', 'archive_plugins',
archive_plugins_sources, archive_plugins_sources,

@@ -41,6 +41,8 @@
#include <memory> #include <memory>
#include <vector> #include <vector>
#include <limits.h> // for UINT_MAX
CommandResult CommandResult
handle_listfiles_db(Client &client, Response &r, const char *uri) handle_listfiles_db(Client &client, Response &r, const char *uri)
{ {

@@ -100,10 +100,6 @@ handle_listfiles_local(Response &r, Path path_fs)
return CommandResult::OK; return CommandResult::OK;
} }
#if defined(_WIN32) && GCC_CHECK_VERSION(4,6)
#pragma GCC diagnostic pop
#endif
gcc_pure gcc_pure
static bool static bool
IsValidName(const StringView s) noexcept IsValidName(const StringView s) noexcept
@@ -130,7 +126,8 @@ public:
explicit PrintCommentHandler(Response &_response) noexcept explicit PrintCommentHandler(Response &_response) noexcept
:NullTagHandler(WANT_PAIR), response(_response) {} :NullTagHandler(WANT_PAIR), response(_response) {}
void OnPair(StringView key, StringView value) noexcept override { void OnPair(StringView _key, StringView _value) noexcept override {
const std::string_view key{_key}, value{_value};
if (IsValidName(key) && IsValidValue(value)) if (IsValidName(key) && IsValidValue(value))
response.Fmt(FMT_STRING("{}: {}\n"), key, value); response.Fmt(FMT_STRING("{}: {}\n"), key, value);
} }

@@ -33,7 +33,6 @@
#include "TimePrint.hxx" #include "TimePrint.hxx"
#include "decoder/DecoderPrint.hxx" #include "decoder/DecoderPrint.hxx"
#include "ls.hxx" #include "ls.hxx"
#include "mixer/Volume.hxx"
#include "time/ChronoUtil.hxx" #include "time/ChronoUtil.hxx"
#include "util/UriUtil.hxx" #include "util/UriUtil.hxx"
#include "util/StringAPI.hxx" #include "util/StringAPI.hxx"
@@ -325,7 +324,7 @@ handle_getvol(Client &client, Request, Response &r)
{ {
auto &partition = client.GetPartition(); auto &partition = client.GetPartition();
const auto volume = volume_level_get(partition.outputs); const auto volume = partition.mixer_memento.GetVolume(partition.outputs);
if (volume >= 0) if (volume >= 0)
r.Fmt(FMT_STRING("volume: {}\n"), volume); r.Fmt(FMT_STRING("volume: {}\n"), volume);
@@ -337,7 +336,9 @@ handle_setvol(Client &client, Request args, Response &)
{ {
unsigned level = args.ParseUnsigned(0, 100); unsigned level = args.ParseUnsigned(0, 100);
volume_level_change(client.GetPartition().outputs, level); auto &partition = client.GetPartition();
partition.mixer_memento.SetVolume(partition.outputs, level);
partition.EmitIdle(IDLE_MIXER);
return CommandResult::OK; return CommandResult::OK;
} }
@@ -346,9 +347,11 @@ handle_volume(Client &client, Request args, Response &r)
{ {
int relative = args.ParseInt(0, -100, 100); int relative = args.ParseInt(0, -100, 100);
auto &outputs = client.GetPartition().outputs; auto &partition = client.GetPartition();
auto &outputs = partition.outputs;
auto &mixer_memento = partition.mixer_memento;
const int old_volume = volume_level_get(outputs); const int old_volume = mixer_memento.GetVolume(outputs);
if (old_volume < 0) { if (old_volume < 0) {
r.Error(ACK_ERROR_SYSTEM, "No mixer"); r.Error(ACK_ERROR_SYSTEM, "No mixer");
return CommandResult::ERROR; return CommandResult::ERROR;
@@ -360,8 +363,10 @@ handle_volume(Client &client, Request args, Response &r)
else if (new_volume > 100) else if (new_volume > 100)
new_volume = 100; new_volume = 100;
if (new_volume != old_volume) if (new_volume != old_volume) {
volume_level_change(outputs, new_volume); mixer_memento.SetVolume(outputs, new_volume);
partition.EmitIdle(IDLE_MIXER);
}
return CommandResult::OK; return CommandResult::OK;
} }

@@ -33,7 +33,11 @@ handle_enableoutput(Client &client, Request args, Response &r)
assert(args.size == 1); assert(args.size == 1);
unsigned device = args.ParseUnsigned(0); unsigned device = args.ParseUnsigned(0);
if (!audio_output_enable_index(client.GetPartition().outputs, device)) { auto &partition = client.GetPartition();
if (!audio_output_enable_index(partition.outputs,
partition.mixer_memento,
device)) {
r.Error(ACK_ERROR_NO_EXIST, "No such audio output"); r.Error(ACK_ERROR_NO_EXIST, "No such audio output");
return CommandResult::ERROR; return CommandResult::ERROR;
} }
@@ -47,7 +51,11 @@ handle_disableoutput(Client &client, Request args, Response &r)
assert(args.size == 1); assert(args.size == 1);
unsigned device = args.ParseUnsigned(0); unsigned device = args.ParseUnsigned(0);
if (!audio_output_disable_index(client.GetPartition().outputs, device)) { auto &partition = client.GetPartition();
if (!audio_output_disable_index(partition.outputs,
partition.mixer_memento,
device)) {
r.Error(ACK_ERROR_NO_EXIST, "No such audio output"); r.Error(ACK_ERROR_NO_EXIST, "No such audio output");
return CommandResult::ERROR; return CommandResult::ERROR;
} }
@@ -61,7 +69,11 @@ handle_toggleoutput(Client &client, Request args, Response &r)
assert(args.size == 1); assert(args.size == 1);
unsigned device = args.ParseUnsigned(0); unsigned device = args.ParseUnsigned(0);
if (!audio_output_toggle_index(client.GetPartition().outputs, device)) { auto &partition = client.GetPartition();
if (!audio_output_toggle_index(partition.outputs,
partition.mixer_memento,
device)) {
r.Error(ACK_ERROR_NO_EXIST, "No such audio output"); r.Error(ACK_ERROR_NO_EXIST, "No such audio output");
return CommandResult::ERROR; return CommandResult::ERROR;
} }

@@ -25,10 +25,10 @@
#include "SingleMode.hxx" #include "SingleMode.hxx"
#include "client/Client.hxx" #include "client/Client.hxx"
#include "client/Response.hxx" #include "client/Response.hxx"
#include "mixer/Volume.hxx"
#include "Partition.hxx" #include "Partition.hxx"
#include "Instance.hxx" #include "Instance.hxx"
#include "IdleFlags.hxx" #include "IdleFlags.hxx"
#include "lib/fmt/AudioFormatFormatter.hxx"
#include "util/StringBuffer.hxx" #include "util/StringBuffer.hxx"
#include "util/ScopeExit.hxx" #include "util/ScopeExit.hxx"
#include "util/Exception.hxx" #include "util/Exception.hxx"
@@ -131,7 +131,7 @@ handle_status(Client &client, [[maybe_unused]] Request args, Response &r)
const auto &playlist = partition.playlist; const auto &playlist = partition.playlist;
const auto volume = volume_level_get(partition.outputs); const auto volume = partition.mixer_memento.GetVolume(partition.outputs);
if (volume >= 0) if (volume >= 0)
r.Fmt(FMT_STRING("volume: {}\n"), volume); r.Fmt(FMT_STRING("volume: {}\n"), volume);
@@ -186,7 +186,7 @@ handle_status(Client &client, [[maybe_unused]] Request args, Response &r)
if (player_status.audio_format.IsDefined()) if (player_status.audio_format.IsDefined())
r.Fmt(FMT_STRING(COMMAND_STATUS_AUDIO ": {}\n"), r.Fmt(FMT_STRING(COMMAND_STATUS_AUDIO ": {}\n"),
ToString(player_status.audio_format)); player_status.audio_format);
} }
#ifdef ENABLE_DATABASE #ifdef ENABLE_DATABASE

@@ -83,10 +83,6 @@ handle_listfiles_storage(Response &r, StorageDirectoryReader &reader)
} }
} }
#if defined(_WIN32) && GCC_CHECK_VERSION(4,6)
#pragma GCC diagnostic pop
#endif
CommandResult CommandResult
handle_listfiles_storage(Response &r, Storage &storage, const char *uri) handle_listfiles_storage(Response &r, Storage &storage, const char *uri)
{ {

@@ -24,6 +24,7 @@
#include "config/Param.hxx" #include "config/Param.hxx"
#include "config/Block.hxx" #include "config/Block.hxx"
#include "fs/AllocatedPath.hxx" #include "fs/AllocatedPath.hxx"
#include "fs/FileSystem.hxx"
#include "fs/StandardDirectory.hxx" #include "fs/StandardDirectory.hxx"
#include "util/RuntimeError.hxx" #include "util/RuntimeError.hxx"
@@ -51,17 +52,30 @@ CreateConfiguredDatabase(const ConfigData &config,
} else { } else {
/* if there is no override, use the cache directory */ /* if there is no override, use the cache directory */
const AllocatedPath cache_dir = GetUserCacheDir(); const AllocatedPath cache_dir = GetAppCacheDir();
if (cache_dir.IsNull()) if (cache_dir.IsNull())
return nullptr; return nullptr;
const auto db_file = cache_dir / Path::FromFS(PATH_LITERAL("mpd.db")); const auto db_file = cache_dir / Path::FromFS(PATH_LITERAL("db"));
auto db_file_utf8 = db_file.ToUTF8(); auto db_file_utf8 = db_file.ToUTF8();
if (db_file_utf8.empty()) if (db_file_utf8.empty())
return nullptr; return nullptr;
ConfigBlock block; ConfigBlock block;
block.AddBlockParam("path", std::move(db_file_utf8), -1); block.AddBlockParam("path", std::move(db_file_utf8), -1);
{
const auto mounts_dir = cache_dir
/ Path::FromFS(PATH_LITERAL("mounts"));
CreateDirectoryNoThrow(mounts_dir);
if (auto mounts_dir_utf8 = mounts_dir.ToUTF8();
!mounts_dir_utf8.empty())
block.AddBlockParam("cache_directory",
std::move(mounts_dir_utf8),
-1);
}
return DatabaseGlobalInit(main_event_loop, io_event_loop, return DatabaseGlobalInit(main_event_loop, io_event_loop,
listener, block); listener, block);
} }

@@ -126,6 +126,18 @@ Directory::LookupTargetSong(std::string_view _target) noexcept
return lr.directory->FindSong(lr.rest); return lr.directory->FindSong(lr.rest);
} }
void
Directory::ClearInPlaylist() noexcept
{
assert(holding_db_lock());
for (auto &child : children)
child.ClearInPlaylist();
for (auto &song : songs)
song.in_playlist = false;
}
void void
Directory::PruneEmpty() noexcept Directory::PruneEmpty() noexcept
{ {

@@ -287,6 +287,14 @@ public:
*/ */
SongPtr RemoveSong(Song *song) noexcept; SongPtr RemoveSong(Song *song) noexcept;
/**
* Recursively walk through the whole tree and set all
* `Song::in_playlist` fields to `false`.
*
* Caller must lock the #db_mutex.
*/
void ClearInPlaylist() noexcept;
/** /**
* Caller must lock the #db_mutex. * Caller must lock the #db_mutex.
*/ */

@@ -168,12 +168,14 @@ directory_load(LineReader &file, Directory &directory)
throw FormatRuntimeError("Duplicate song '%s'", name); throw FormatRuntimeError("Duplicate song '%s'", name);
std::string target; std::string target;
bool in_playlist = false;
auto detached_song = song_load(file, name, auto detached_song = song_load(file, name,
&target); &target, &in_playlist);
auto song = std::make_unique<Song>(std::move(detached_song), auto song = std::make_unique<Song>(std::move(detached_song),
directory); directory);
song->target = std::move(target); song->target = std::move(target);
song->in_playlist = in_playlist;
directory.AddSong(std::move(song)); directory.AddSong(std::move(song));
} else if ((p = StringAfterPrefix(line, PLAYLIST_META_BEGIN))) { } else if ((p = StringAfterPrefix(line, PLAYLIST_META_BEGIN))) {

@@ -51,6 +51,15 @@ LockFindSong(Directory &directory, std::string_view name) noexcept
return directory.FindSong(name); return directory.FindSong(name);
} }
[[gnu::pure]]
static bool
IsAcceptableFilename(std::string_view name) noexcept
{
return !name.empty() &&
/* newlines cannot be represented in MPD's protocol */
name.find('\n') == name.npos;
}
void void
UpdateWalk::UpdateArchiveTree(ArchiveFile &archive, Directory &directory, UpdateWalk::UpdateArchiveTree(ArchiveFile &archive, Directory &directory,
const char *name) noexcept const char *name) noexcept
@@ -58,6 +67,9 @@ UpdateWalk::UpdateArchiveTree(ArchiveFile &archive, Directory &directory,
const char *tmp = std::strchr(name, '/'); const char *tmp = std::strchr(name, '/');
if (tmp) { if (tmp) {
const std::string_view child_name(name, tmp - name); const std::string_view child_name(name, tmp - name);
if (!IsAcceptableFilename(child_name))
return;
//add dir is not there already //add dir is not there already
Directory *subdir = LockMakeChild(directory, child_name); Directory *subdir = LockMakeChild(directory, child_name);
subdir->device = DEVICE_INARCHIVE; subdir->device = DEVICE_INARCHIVE;
@@ -65,11 +77,8 @@ UpdateWalk::UpdateArchiveTree(ArchiveFile &archive, Directory &directory,
//create directories first //create directories first
UpdateArchiveTree(archive, *subdir, tmp + 1); UpdateArchiveTree(archive, *subdir, tmp + 1);
} else { } else {
if (StringIsEmpty(name)) { if (!IsAcceptableFilename(name))
LogWarning(update_domain,
"archive returned directory only");
return; return;
}
//add file //add file
Song *song = LockFindSong(directory, name); Song *song = LockFindSong(directory, name);

@@ -531,6 +531,7 @@ UpdateWalk::Walk(Directory &root, const char *path, bool discard) noexcept
{ {
const ScopeDatabaseLock protect; const ScopeDatabaseLock protect;
root.ClearInPlaylist();
PurgeDanglingFromPlaylists(root); PurgeDanglingFromPlaylists(root);
} }

@@ -114,11 +114,11 @@ constexpr const struct DecoderPlugin *decoder_plugins[] = {
#ifdef ENABLE_ADPLUG #ifdef ENABLE_ADPLUG
&adplug_decoder_plugin, &adplug_decoder_plugin,
#endif #endif
#ifdef ENABLE_FFMPEG
&ffmpeg_decoder_plugin,
#endif
#ifdef ENABLE_GME #ifdef ENABLE_GME
&gme_decoder_plugin, &gme_decoder_plugin,
#endif
#ifdef ENABLE_FFMPEG
&ffmpeg_decoder_plugin,
#endif #endif
&pcm_decoder_plugin, &pcm_decoder_plugin,
nullptr nullptr

@@ -31,6 +31,7 @@
#include "lib/ffmpeg/Format.hxx" #include "lib/ffmpeg/Format.hxx"
#include "lib/ffmpeg/Codec.hxx" #include "lib/ffmpeg/Codec.hxx"
#include "lib/ffmpeg/SampleFormat.hxx" #include "lib/ffmpeg/SampleFormat.hxx"
#include "lib/ffmpeg/LibFmt.hxx"
#include "../DecoderAPI.hxx" #include "../DecoderAPI.hxx"
#include "FfmpegMetaData.hxx" #include "FfmpegMetaData.hxx"
#include "FfmpegIo.hxx" #include "FfmpegIo.hxx"
@@ -523,9 +524,15 @@ FfmpegDecode(DecoderClient &client, InputStream *input,
return; return;
} }
#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(57, 25, 100)
const unsigned channels = codec_context->ch_layout.nb_channels;
#else
const unsigned channels = codec_context->channels;
#endif
const auto audio_format = CheckAudioFormat(codec_context->sample_rate, const auto audio_format = CheckAudioFormat(codec_context->sample_rate,
sample_format, sample_format,
codec_context->channels); channels);
const SignedSongTime total_time = const SignedSongTime total_time =
av_stream.duration != (int64_t)AV_NOPTS_VALUE av_stream.duration != (int64_t)AV_NOPTS_VALUE
@@ -635,10 +642,17 @@ FfmpegScanStream(AVFormatContext &format_context, TagHandler &handler)
AV_TIME_BASE_Q)); AV_TIME_BASE_Q));
const auto &codec_params = *stream.codecpar; const auto &codec_params = *stream.codecpar;
#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(57, 25, 100)
const unsigned channels = codec_params.ch_layout.nb_channels;
#else
const unsigned channels = codec_params.channels;
#endif
try { try {
handler.OnAudioFormat(CheckAudioFormat(codec_params.sample_rate, handler.OnAudioFormat(CheckAudioFormat(codec_params.sample_rate,
ffmpeg_sample_format(AVSampleFormat(codec_params.format)), ffmpeg_sample_format(AVSampleFormat(codec_params.format)),
codec_params.channels)); channels));
} catch (...) { } catch (...) {
} }

@@ -21,10 +21,13 @@
#define __STDC_CONSTANT_MACROS #define __STDC_CONSTANT_MACROS
#include "FfmpegIo.hxx" #include "FfmpegIo.hxx"
#include "libavutil/mem.h"
#include "../DecoderAPI.hxx" #include "../DecoderAPI.hxx"
#include "input/InputStream.hxx" #include "input/InputStream.hxx"
extern "C" {
#include <libavutil/mem.h>
}
AvioStream::~AvioStream() AvioStream::~AvioStream()
{ {
if (io != nullptr) { if (io != nullptr) {

@@ -30,7 +30,7 @@
bool bool
FlacDecoder::Initialize(unsigned sample_rate, unsigned bits_per_sample, FlacDecoder::Initialize(unsigned sample_rate, unsigned bits_per_sample,
unsigned channels, FLAC__uint64 total_frames) unsigned channels, FLAC__uint64 total_frames) noexcept
{ {
assert(!initialized); assert(!initialized);
assert(!unsupported); assert(!unsupported);
@@ -60,7 +60,7 @@ FlacDecoder::Initialize(unsigned sample_rate, unsigned bits_per_sample,
} }
inline void inline void
FlacDecoder::OnStreamInfo(const FLAC__StreamMetadata_StreamInfo &stream_info) FlacDecoder::OnStreamInfo(const FLAC__StreamMetadata_StreamInfo &stream_info) noexcept
{ {
if (initialized) if (initialized)
return; return;
@@ -72,7 +72,7 @@ FlacDecoder::OnStreamInfo(const FLAC__StreamMetadata_StreamInfo &stream_info)
} }
inline void inline void
FlacDecoder::OnVorbisComment(const FLAC__StreamMetadata_VorbisComment &vc) FlacDecoder::OnVorbisComment(const FLAC__StreamMetadata_VorbisComment &vc) noexcept
{ {
ReplayGainInfo rgi; ReplayGainInfo rgi;
if (flac_parse_replay_gain(rgi, vc)) if (flac_parse_replay_gain(rgi, vc))
@@ -86,7 +86,7 @@ FlacDecoder::OnVorbisComment(const FLAC__StreamMetadata_VorbisComment &vc)
} }
void void
FlacDecoder::OnMetadata(const FLAC__StreamMetadata &metadata) FlacDecoder::OnMetadata(const FLAC__StreamMetadata &metadata) noexcept
{ {
if (unsupported) if (unsupported)
return; return;
@@ -106,7 +106,7 @@ FlacDecoder::OnMetadata(const FLAC__StreamMetadata &metadata)
} }
inline bool inline bool
FlacDecoder::OnFirstFrame(const FLAC__FrameHeader &header) FlacDecoder::OnFirstFrame(const FLAC__FrameHeader &header) noexcept
{ {
if (unsupported) if (unsupported)
return false; return false;
@@ -139,7 +139,7 @@ FlacDecoder::GetDeltaPosition(const FLAC__StreamDecoder &sd)
FLAC__StreamDecoderWriteStatus FLAC__StreamDecoderWriteStatus
FlacDecoder::OnWrite(const FLAC__Frame &frame, FlacDecoder::OnWrite(const FLAC__Frame &frame,
const FLAC__int32 *const buf[], const FLAC__int32 *const buf[],
FLAC__uint64 nbytes) FLAC__uint64 nbytes) noexcept
{ {
if (!initialized && !OnFirstFrame(frame.header)) if (!initialized && !OnFirstFrame(frame.header))
return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT; return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT;

@@ -65,20 +65,21 @@ struct FlacDecoder : public FlacInput {
*/ */
ConstBuffer<void> chunk = nullptr; ConstBuffer<void> chunk = nullptr;
FlacDecoder(DecoderClient &_client, InputStream &_input_stream) FlacDecoder(DecoderClient &_client,
InputStream &_input_stream) noexcept
:FlacInput(_input_stream, &_client) {} :FlacInput(_input_stream, &_client) {}
/** /**
* Wrapper for DecoderClient::Ready(). * Wrapper for DecoderClient::Ready().
*/ */
bool Initialize(unsigned sample_rate, unsigned bits_per_sample, bool Initialize(unsigned sample_rate, unsigned bits_per_sample,
unsigned channels, FLAC__uint64 total_frames); unsigned channels, FLAC__uint64 total_frames) noexcept;
void OnMetadata(const FLAC__StreamMetadata &metadata); void OnMetadata(const FLAC__StreamMetadata &metadata) noexcept;
FLAC__StreamDecoderWriteStatus OnWrite(const FLAC__Frame &frame, FLAC__StreamDecoderWriteStatus OnWrite(const FLAC__Frame &frame,
const FLAC__int32 *const buf[], const FLAC__int32 *const buf[],
FLAC__uint64 nbytes); FLAC__uint64 nbytes) noexcept;
/** /**
* Calculate the delta (in bytes) between the last frame and * Calculate the delta (in bytes) between the last frame and
@@ -87,8 +88,8 @@ struct FlacDecoder : public FlacInput {
FLAC__uint64 GetDeltaPosition(const FLAC__StreamDecoder &sd); FLAC__uint64 GetDeltaPosition(const FLAC__StreamDecoder &sd);
private: private:
void OnStreamInfo(const FLAC__StreamMetadata_StreamInfo &stream_info); void OnStreamInfo(const FLAC__StreamMetadata_StreamInfo &stream_info) noexcept;
void OnVorbisComment(const FLAC__StreamMetadata_VorbisComment &vc); void OnVorbisComment(const FLAC__StreamMetadata_VorbisComment &vc) noexcept;
/** /**
* This function attempts to call DecoderClient::Ready() in case there * This function attempts to call DecoderClient::Ready() in case there
@@ -97,7 +98,7 @@ private:
* providing the STREAMINFO block from the beginning of the file * providing the STREAMINFO block from the beginning of the file
* (e.g. when seeking with SqueezeBox Server). * (e.g. when seeking with SqueezeBox Server).
*/ */
bool OnFirstFrame(const FLAC__FrameHeader &header); bool OnFirstFrame(const FLAC__FrameHeader &header) noexcept;
}; };
#endif /* _FLAC_COMMON_H */ #endif /* _FLAC_COMMON_H */

@@ -24,6 +24,7 @@
#include "lib/xiph/FlacMetadataChain.hxx" #include "lib/xiph/FlacMetadataChain.hxx"
#include "OggCodec.hxx" #include "OggCodec.hxx"
#include "input/InputStream.hxx" #include "input/InputStream.hxx"
#include "input/LocalOpen.hxx"
#include "fs/Path.hxx" #include "fs/Path.hxx"
#include "fs/NarrowPath.hxx" #include "fs/NarrowPath.hxx"
#include "Log.hxx" #include "Log.hxx"
@@ -32,7 +33,8 @@
#error libFLAC is too old #error libFLAC is too old
#endif #endif
static void flacPrintErroredState(FLAC__StreamDecoderState state) static void
flacPrintErroredState(FLAC__StreamDecoderState state) noexcept
{ {
switch (state) { switch (state) {
case FLAC__STREAM_DECODER_SEARCH_FOR_METADATA: case FLAC__STREAM_DECODER_SEARCH_FOR_METADATA:
@@ -53,8 +55,9 @@ static void flacPrintErroredState(FLAC__StreamDecoderState state)
LogError(flac_domain, FLAC__StreamDecoderStateString[state]); LogError(flac_domain, FLAC__StreamDecoderStateString[state]);
} }
static void flacMetadata([[maybe_unused]] const FLAC__StreamDecoder * dec, static void
const FLAC__StreamMetadata * block, void *vdata) flacMetadata([[maybe_unused]] const FLAC__StreamDecoder * dec,
const FLAC__StreamMetadata * block, void *vdata) noexcept
{ {
auto &fd = *(FlacDecoder *)vdata; auto &fd = *(FlacDecoder *)vdata;
fd.OnMetadata(*block); fd.OnMetadata(*block);
@@ -62,29 +65,45 @@ static void flacMetadata([[maybe_unused]] const FLAC__StreamDecoder * dec,
static FLAC__StreamDecoderWriteStatus static FLAC__StreamDecoderWriteStatus
flac_write_cb(const FLAC__StreamDecoder *dec, const FLAC__Frame *frame, flac_write_cb(const FLAC__StreamDecoder *dec, const FLAC__Frame *frame,
const FLAC__int32 *const buf[], void *vdata) const FLAC__int32 *const buf[], void *vdata) noexcept
{ {
auto &fd = *(FlacDecoder *)vdata; auto &fd = *(FlacDecoder *)vdata;
return fd.OnWrite(*frame, buf, fd.GetDeltaPosition(*dec)); return fd.OnWrite(*frame, buf, fd.GetDeltaPosition(*dec));
} }
static bool static bool
flac_scan_file(Path path_fs, TagHandler &handler) flac_scan_file(Path path_fs, TagHandler &handler) noexcept {
{
FlacMetadataChain chain; FlacMetadataChain chain;
if (!chain.Read(NarrowPath(path_fs))) { const bool succeed = [&chain, &path_fs]() noexcept {
// read by NarrowPath
if (chain.Read(NarrowPath(path_fs))) {
return true;
}
if (std::is_same_v<Path::value_type, char> ||
chain.GetStatus() != FLAC__METADATA_CHAIN_STATUS_ERROR_OPENING_FILE) {
return false;
}
// read by InputStream
Mutex mutex;
auto is = OpenLocalInputStream(path_fs, mutex);
if (is && chain.Read(*is)) {
return true;
}
return false;
}();
if (!succeed) {
FmtDebug(flac_domain, FmtDebug(flac_domain,
"Failed to read FLAC tags: {}", "Failed to read FLAC tags: {}",
chain.GetStatusString()); chain.GetStatusString());
return false; return false;
} }
chain.Scan(handler); chain.Scan(handler);
return true; return true;
} }
static bool static bool
flac_scan_stream(InputStream &is, TagHandler &handler) flac_scan_stream(InputStream &is, TagHandler &handler) noexcept
{ {
FlacMetadataChain chain; FlacMetadataChain chain;
if (!chain.Read(is)) { if (!chain.Read(is)) {
@@ -102,7 +121,7 @@ flac_scan_stream(InputStream &is, TagHandler &handler)
* Some glue code around FLAC__stream_decoder_new(). * Some glue code around FLAC__stream_decoder_new().
*/ */
static FlacStreamDecoder static FlacStreamDecoder
flac_decoder_new() flac_decoder_new() noexcept
{ {
FlacStreamDecoder sd; FlacStreamDecoder sd;
if(!FLAC__stream_decoder_set_metadata_respond(sd.get(), FLAC__METADATA_TYPE_VORBIS_COMMENT)) if(!FLAC__stream_decoder_set_metadata_respond(sd.get(), FLAC__METADATA_TYPE_VORBIS_COMMENT))
@@ -113,7 +132,7 @@ flac_decoder_new()
} }
static bool static bool
flac_decoder_initialize(FlacDecoder *data, FLAC__StreamDecoder *sd) flac_decoder_initialize(FlacDecoder *data, FLAC__StreamDecoder *sd) noexcept
{ {
if (!FLAC__stream_decoder_process_until_end_of_metadata(sd)) { if (!FLAC__stream_decoder_process_until_end_of_metadata(sd)) {
if (FLAC__stream_decoder_get_state(sd) != FLAC__STREAM_DECODER_END_OF_STREAM) if (FLAC__stream_decoder_get_state(sd) != FLAC__STREAM_DECODER_END_OF_STREAM)
@@ -231,7 +250,7 @@ flac_decoder_loop(FlacDecoder *data, FLAC__StreamDecoder *flac_dec)
} }
static FLAC__StreamDecoderInitStatus static FLAC__StreamDecoderInitStatus
stream_init_oggflac(FLAC__StreamDecoder *flac_dec, FlacDecoder *data) stream_init_oggflac(FLAC__StreamDecoder *flac_dec, FlacDecoder *data) noexcept
{ {
return FLAC__stream_decoder_init_ogg_stream(flac_dec, return FLAC__stream_decoder_init_ogg_stream(flac_dec,
FlacInput::Read, FlacInput::Read,
@@ -246,7 +265,7 @@ stream_init_oggflac(FLAC__StreamDecoder *flac_dec, FlacDecoder *data)
} }
static FLAC__StreamDecoderInitStatus static FLAC__StreamDecoderInitStatus
stream_init_flac(FLAC__StreamDecoder *flac_dec, FlacDecoder *data) stream_init_flac(FLAC__StreamDecoder *flac_dec, FlacDecoder *data) noexcept
{ {
return FLAC__stream_decoder_init_stream(flac_dec, return FLAC__stream_decoder_init_stream(flac_dec,
FlacInput::Read, FlacInput::Read,
@@ -261,7 +280,8 @@ stream_init_flac(FLAC__StreamDecoder *flac_dec, FlacDecoder *data)
} }
static FLAC__StreamDecoderInitStatus static FLAC__StreamDecoderInitStatus
stream_init(FLAC__StreamDecoder *flac_dec, FlacDecoder *data, bool is_ogg) stream_init(FLAC__StreamDecoder *flac_dec, FlacDecoder *data,
bool is_ogg) noexcept
{ {
return is_ogg return is_ogg
? stream_init_oggflac(flac_dec, data) ? stream_init_oggflac(flac_dec, data)
@@ -307,7 +327,7 @@ flac_decode(DecoderClient &client, InputStream &input_stream)
} }
static bool static bool
oggflac_init([[maybe_unused]] const ConfigBlock &block) oggflac_init([[maybe_unused]] const ConfigBlock &block) noexcept
{ {
return !!FLAC_API_SUPPORTS_OGG_FLAC; return !!FLAC_API_SUPPORTS_OGG_FLAC;
} }

@@ -22,12 +22,11 @@
#include "../DecoderAPI.hxx" #include "../DecoderAPI.hxx"
#include "input/InputStream.hxx" #include "input/InputStream.hxx"
#include "Log.hxx" #include "Log.hxx"
#include "util/Compiler.h"
#include <exception> #include <exception>
FLAC__StreamDecoderReadStatus inline FLAC__StreamDecoderReadStatus
FlacInput::Read(FLAC__byte buffer[], size_t *bytes) FlacInput::Read(FLAC__byte buffer[], size_t *bytes) noexcept
{ {
size_t r = decoder_read(client, input_stream, (void *)buffer, *bytes); size_t r = decoder_read(client, input_stream, (void *)buffer, *bytes);
*bytes = r; *bytes = r;
@@ -44,8 +43,8 @@ FlacInput::Read(FLAC__byte buffer[], size_t *bytes)
return FLAC__STREAM_DECODER_READ_STATUS_CONTINUE; return FLAC__STREAM_DECODER_READ_STATUS_CONTINUE;
} }
FLAC__StreamDecoderSeekStatus inline FLAC__StreamDecoderSeekStatus
FlacInput::Seek(FLAC__uint64 absolute_byte_offset) FlacInput::Seek(FLAC__uint64 absolute_byte_offset) noexcept
{ {
if (!input_stream.IsSeekable()) if (!input_stream.IsSeekable())
return FLAC__STREAM_DECODER_SEEK_STATUS_UNSUPPORTED; return FLAC__STREAM_DECODER_SEEK_STATUS_UNSUPPORTED;
@@ -59,8 +58,8 @@ FlacInput::Seek(FLAC__uint64 absolute_byte_offset)
} }
} }
FLAC__StreamDecoderTellStatus inline FLAC__StreamDecoderTellStatus
FlacInput::Tell(FLAC__uint64 *absolute_byte_offset) FlacInput::Tell(FLAC__uint64 *absolute_byte_offset) noexcept
{ {
if (!input_stream.IsSeekable()) if (!input_stream.IsSeekable())
return FLAC__STREAM_DECODER_TELL_STATUS_UNSUPPORTED; return FLAC__STREAM_DECODER_TELL_STATUS_UNSUPPORTED;
@@ -69,8 +68,8 @@ FlacInput::Tell(FLAC__uint64 *absolute_byte_offset)
return FLAC__STREAM_DECODER_TELL_STATUS_OK; return FLAC__STREAM_DECODER_TELL_STATUS_OK;
} }
FLAC__StreamDecoderLengthStatus inline FLAC__StreamDecoderLengthStatus
FlacInput::Length(FLAC__uint64 *stream_length) FlacInput::Length(FLAC__uint64 *stream_length) noexcept
{ {
if (!input_stream.KnownSize()) if (!input_stream.KnownSize())
return FLAC__STREAM_DECODER_LENGTH_STATUS_UNSUPPORTED; return FLAC__STREAM_DECODER_LENGTH_STATUS_UNSUPPORTED;
@@ -79,8 +78,8 @@ FlacInput::Length(FLAC__uint64 *stream_length)
return FLAC__STREAM_DECODER_LENGTH_STATUS_OK; return FLAC__STREAM_DECODER_LENGTH_STATUS_OK;
} }
FLAC__bool inline FLAC__bool
FlacInput::Eof() FlacInput::Eof() noexcept
{ {
return (client != nullptr && return (client != nullptr &&
client->GetCommand() != DecoderCommand::NONE && client->GetCommand() != DecoderCommand::NONE &&
@@ -88,8 +87,8 @@ FlacInput::Eof()
input_stream.LockIsEOF(); input_stream.LockIsEOF();
} }
void inline void
FlacInput::Error(FLAC__StreamDecoderErrorStatus status) FlacInput::Error(FLAC__StreamDecoderErrorStatus status) noexcept
{ {
if (client == nullptr || if (client == nullptr ||
client->GetCommand() != DecoderCommand::STOP) client->GetCommand() != DecoderCommand::STOP)
@@ -100,7 +99,7 @@ FlacInput::Error(FLAC__StreamDecoderErrorStatus status)
FLAC__StreamDecoderReadStatus FLAC__StreamDecoderReadStatus
FlacInput::Read([[maybe_unused]] const FLAC__StreamDecoder *flac_decoder, FlacInput::Read([[maybe_unused]] const FLAC__StreamDecoder *flac_decoder,
FLAC__byte buffer[], size_t *bytes, FLAC__byte buffer[], size_t *bytes,
void *client_data) void *client_data) noexcept
{ {
auto *i = (FlacInput *)client_data; auto *i = (FlacInput *)client_data;
@@ -109,7 +108,7 @@ FlacInput::Read([[maybe_unused]] const FLAC__StreamDecoder *flac_decoder,
FLAC__StreamDecoderSeekStatus FLAC__StreamDecoderSeekStatus
FlacInput::Seek([[maybe_unused]] const FLAC__StreamDecoder *flac_decoder, FlacInput::Seek([[maybe_unused]] const FLAC__StreamDecoder *flac_decoder,
FLAC__uint64 absolute_byte_offset, void *client_data) FLAC__uint64 absolute_byte_offset, void *client_data) noexcept
{ {
auto *i = (FlacInput *)client_data; auto *i = (FlacInput *)client_data;
@@ -118,7 +117,7 @@ FlacInput::Seek([[maybe_unused]] const FLAC__StreamDecoder *flac_decoder,
FLAC__StreamDecoderTellStatus FLAC__StreamDecoderTellStatus
FlacInput::Tell([[maybe_unused]] const FLAC__StreamDecoder *flac_decoder, FlacInput::Tell([[maybe_unused]] const FLAC__StreamDecoder *flac_decoder,
FLAC__uint64 *absolute_byte_offset, void *client_data) FLAC__uint64 *absolute_byte_offset, void *client_data) noexcept
{ {
auto *i = (FlacInput *)client_data; auto *i = (FlacInput *)client_data;
@@ -127,7 +126,7 @@ FlacInput::Tell([[maybe_unused]] const FLAC__StreamDecoder *flac_decoder,
FLAC__StreamDecoderLengthStatus FLAC__StreamDecoderLengthStatus
FlacInput::Length([[maybe_unused]] const FLAC__StreamDecoder *flac_decoder, FlacInput::Length([[maybe_unused]] const FLAC__StreamDecoder *flac_decoder,
FLAC__uint64 *stream_length, void *client_data) FLAC__uint64 *stream_length, void *client_data) noexcept
{ {
auto *i = (FlacInput *)client_data; auto *i = (FlacInput *)client_data;
@@ -136,7 +135,7 @@ FlacInput::Length([[maybe_unused]] const FLAC__StreamDecoder *flac_decoder,
FLAC__bool FLAC__bool
FlacInput::Eof([[maybe_unused]] const FLAC__StreamDecoder *flac_decoder, FlacInput::Eof([[maybe_unused]] const FLAC__StreamDecoder *flac_decoder,
void *client_data) void *client_data) noexcept
{ {
auto *i = (FlacInput *)client_data; auto *i = (FlacInput *)client_data;
@@ -145,7 +144,8 @@ FlacInput::Eof([[maybe_unused]] const FLAC__StreamDecoder *flac_decoder,
void void
FlacInput::Error([[maybe_unused]] const FLAC__StreamDecoder *decoder, FlacInput::Error([[maybe_unused]] const FLAC__StreamDecoder *decoder,
FLAC__StreamDecoderErrorStatus status, void *client_data) FLAC__StreamDecoderErrorStatus status,
void *client_data) noexcept
{ {
auto *i = (FlacInput *)client_data; auto *i = (FlacInput *)client_data;

@@ -48,36 +48,38 @@ public:
} }
protected: protected:
FLAC__StreamDecoderReadStatus Read(FLAC__byte buffer[], size_t *bytes); FLAC__StreamDecoderReadStatus Read(FLAC__byte buffer[], size_t *bytes) noexcept;
FLAC__StreamDecoderSeekStatus Seek(FLAC__uint64 absolute_byte_offset); FLAC__StreamDecoderSeekStatus Seek(FLAC__uint64 absolute_byte_offset) noexcept;
FLAC__StreamDecoderTellStatus Tell(FLAC__uint64 *absolute_byte_offset); FLAC__StreamDecoderTellStatus Tell(FLAC__uint64 *absolute_byte_offset) noexcept;
FLAC__StreamDecoderLengthStatus Length(FLAC__uint64 *stream_length); FLAC__StreamDecoderLengthStatus Length(FLAC__uint64 *stream_length) noexcept;
FLAC__bool Eof(); FLAC__bool Eof() noexcept;
void Error(FLAC__StreamDecoderErrorStatus status); void Error(FLAC__StreamDecoderErrorStatus status) noexcept;
public: public:
static FLAC__StreamDecoderReadStatus static FLAC__StreamDecoderReadStatus
Read(const FLAC__StreamDecoder *flac_decoder, Read(const FLAC__StreamDecoder *flac_decoder,
FLAC__byte buffer[], size_t *bytes, void *client_data); FLAC__byte buffer[], size_t *bytes, void *client_data) noexcept;
static FLAC__StreamDecoderSeekStatus static FLAC__StreamDecoderSeekStatus
Seek(const FLAC__StreamDecoder *flac_decoder, Seek(const FLAC__StreamDecoder *flac_decoder,
FLAC__uint64 absolute_byte_offset, void *client_data); FLAC__uint64 absolute_byte_offset, void *client_data) noexcept;
static FLAC__StreamDecoderTellStatus static FLAC__StreamDecoderTellStatus
Tell(const FLAC__StreamDecoder *flac_decoder, Tell(const FLAC__StreamDecoder *flac_decoder,
FLAC__uint64 *absolute_byte_offset, void *client_data); FLAC__uint64 *absolute_byte_offset, void *client_data) noexcept;
static FLAC__StreamDecoderLengthStatus static FLAC__StreamDecoderLengthStatus
Length(const FLAC__StreamDecoder *flac_decoder, Length(const FLAC__StreamDecoder *flac_decoder,
FLAC__uint64 *stream_length, void *client_data); FLAC__uint64 *stream_length, void *client_data) noexcept;
static FLAC__bool static FLAC__bool
Eof(const FLAC__StreamDecoder *flac_decoder, void *client_data); Eof(const FLAC__StreamDecoder *flac_decoder,
void *client_data) noexcept;
static void static void
Error(const FLAC__StreamDecoder *decoder, Error(const FLAC__StreamDecoder *decoder,
FLAC__StreamDecoderErrorStatus status, void *client_data); FLAC__StreamDecoderErrorStatus status,
void *client_data) noexcept;
}; };
#endif #endif

@@ -39,7 +39,8 @@ FlacPcmImport::Open(unsigned sample_rate, unsigned bits_per_sample,
template<typename T> template<typename T>
static void static void
FlacImportStereo(T *dest, const FLAC__int32 *const src[], size_t n_frames) FlacImportStereo(T *dest, const FLAC__int32 *const src[],
size_t n_frames) noexcept
{ {
for (size_t i = 0; i != n_frames; ++i) { for (size_t i = 0; i != n_frames; ++i) {
*dest++ = (T)src[0][i]; *dest++ = (T)src[0][i];
@@ -50,7 +51,7 @@ FlacImportStereo(T *dest, const FLAC__int32 *const src[], size_t n_frames)
template<typename T> template<typename T>
static void static void
FlacImportAny(T *dest, const FLAC__int32 *const src[], size_t n_frames, FlacImportAny(T *dest, const FLAC__int32 *const src[], size_t n_frames,
unsigned n_channels) unsigned n_channels) noexcept
{ {
for (size_t i = 0; i != n_frames; ++i) for (size_t i = 0; i != n_frames; ++i)
for (unsigned c = 0; c != n_channels; ++c) for (unsigned c = 0; c != n_channels; ++c)
@@ -60,7 +61,7 @@ FlacImportAny(T *dest, const FLAC__int32 *const src[], size_t n_frames,
template<typename T> template<typename T>
static void static void
FlacImport(T *dest, const FLAC__int32 *const src[], size_t n_frames, FlacImport(T *dest, const FLAC__int32 *const src[], size_t n_frames,
unsigned n_channels) unsigned n_channels) noexcept
{ {
if (n_channels == 2) if (n_channels == 2)
FlacImportStereo(dest, src, n_frames); FlacImportStereo(dest, src, n_frames);
@@ -71,7 +72,7 @@ FlacImport(T *dest, const FLAC__int32 *const src[], size_t n_frames,
template<typename T> template<typename T>
static ConstBuffer<void> static ConstBuffer<void>
FlacImport(PcmBuffer &buffer, const FLAC__int32 *const src[], size_t n_frames, FlacImport(PcmBuffer &buffer, const FLAC__int32 *const src[], size_t n_frames,
unsigned n_channels) unsigned n_channels) noexcept
{ {
size_t n_samples = n_frames * n_channels; size_t n_samples = n_frames * n_channels;
size_t dest_size = n_samples * sizeof(T); size_t dest_size = n_samples * sizeof(T);

@@ -43,7 +43,7 @@ public:
void Open(unsigned sample_rate, unsigned bits_per_sample, void Open(unsigned sample_rate, unsigned bits_per_sample,
unsigned channels); unsigned channels);
const AudioFormat &GetAudioFormat() const { const AudioFormat &GetAudioFormat() const noexcept {
return audio_format; return audio_format;
} }

@@ -56,20 +56,17 @@ struct GmeContainerPath {
unsigned track; unsigned track;
}; };
#if GME_VERSION >= 0x000600
static int gme_accuracy; static int gme_accuracy;
#endif
static unsigned gme_default_fade; static unsigned gme_default_fade;
static bool static bool
gme_plugin_init([[maybe_unused]] const ConfigBlock &block) gme_plugin_init([[maybe_unused]] const ConfigBlock &block)
{ {
#if GME_VERSION >= 0x000600
auto accuracy = block.GetBlockParam("accuracy"); auto accuracy = block.GetBlockParam("accuracy");
gme_accuracy = accuracy != nullptr gme_accuracy = accuracy != nullptr
? (int)accuracy->GetBoolValue() ? (int)accuracy->GetBoolValue()
: -1; : -1;
#endif
auto fade = block.GetBlockParam("default_fade"); auto fade = block.GetBlockParam("default_fade");
gme_default_fade = fade != nullptr gme_default_fade = fade != nullptr
? fade->GetUnsignedValue() * 1000 ? fade->GetUnsignedValue() * 1000
@@ -163,10 +160,8 @@ gme_file_decode(DecoderClient &client, Path path_fs)
FmtDebug(gme_domain, "emulator type '{}'", FmtDebug(gme_domain, "emulator type '{}'",
gme_type_system(gme_type(emu))); gme_type_system(gme_type(emu)));
#if GME_VERSION >= 0x000600
if (gme_accuracy >= 0) if (gme_accuracy >= 0)
gme_enable_accuracy(emu, gme_accuracy); gme_enable_accuracy(emu, gme_accuracy);
#endif
gme_info_t *ti; gme_info_t *ti;
const char *gme_err = gme_track_info(emu, &ti, container.track); const char *gme_err = gme_track_info(emu, &ti, container.track);

@@ -562,7 +562,21 @@ parse_lame(struct lame *lame, struct mad_bitptr *ptr, int *bitlen) noexcept
mad_bit_skip(ptr, 16); mad_bit_skip(ptr, 16);
lame->peak = MAD_F(mad_bit_read(ptr, 32) << 5); /* peak */ /* The lame peak value is a float multiplied by 2^23 and stored as an
* unsigned integer (it is always positive). MAD's fixed-point format uses
* 28 bits for the fractional part, so shift the 23 bit fraction up before
* converting to a float.
*/
unsigned long peak_int = mad_bit_read(ptr, 32);
#define LAME_PEAK_FRACBITS 23
#if MAD_F_FRACBITS > LAME_PEAK_FRACBITS
peak_int <<= (MAD_F_FRACBITS - LAME_PEAK_FRACBITS);
#elif LAME_PEAK_FRACBITS > MAD_F_FRACBITS
peak_int >>= (LAME_PEAK_FRACBITS - MAD_F_FRACBITS);
#endif
lame->peak = mad_f_todouble(peak_int); /* peak */
FmtDebug(mad_domain, "LAME peak found: {}", lame->peak); FmtDebug(mad_domain, "LAME peak found: {}", lame->peak);
lame->track_gain = 0; lame->track_gain = 0;
@@ -798,6 +812,8 @@ MadDecoder::UpdateTimerNextFrame() noexcept
DecoderCommand DecoderCommand
MadDecoder::SubmitPCM(size_t i, size_t pcm_length) noexcept MadDecoder::SubmitPCM(size_t i, size_t pcm_length) noexcept
{ {
assert(i <= pcm_length);
size_t num_samples = pcm_length - i; size_t num_samples = pcm_length - i;
mad_fixed_to_24_buffer(output_buffer, synth.pcm, mad_fixed_to_24_buffer(output_buffer, synth.pcm,
@@ -843,7 +859,7 @@ MadDecoder::SynthAndSubmit() noexcept
size_t pcm_length = synth.pcm.length; size_t pcm_length = synth.pcm.length;
if (drop_end_samples && if (drop_end_samples &&
current_frame == max_frames - drop_end_frames - 1) { current_frame == max_frames - drop_end_frames - 1) {
if (drop_end_samples >= pcm_length) if (i + drop_end_samples >= pcm_length)
return DecoderCommand::STOP; return DecoderCommand::STOP;
pcm_length -= drop_end_samples; pcm_length -= drop_end_samples;

@@ -81,7 +81,7 @@ if libfaad_dep.found()
decoder_plugins_sources += 'FaadDecoderPlugin.cxx' decoder_plugins_sources += 'FaadDecoderPlugin.cxx'
endif endif
libgme_dep = c_compiler.find_library('gme', required: get_option('gme')) libgme_dep = dependency('libgme', version: '>= 0.6', required: get_option('gme'))
decoder_features.set('ENABLE_GME', libgme_dep.found()) decoder_features.set('ENABLE_GME', libgme_dep.found())
if libgme_dep.found() if libgme_dep.found()
decoder_plugins_sources += 'GmeDecoderPlugin.cxx' decoder_plugins_sources += 'GmeDecoderPlugin.cxx'

@@ -38,6 +38,7 @@ class FlacEncoder final : public Encoder {
FLAC__StreamEncoder *const fse; FLAC__StreamEncoder *const fse;
const unsigned compression; const unsigned compression;
const bool oggflac;
PcmBuffer expand_buffer; PcmBuffer expand_buffer;
@@ -122,7 +123,7 @@ flac_encoder_init(const ConfigBlock &block)
} }
static void static void
flac_encoder_setup(FLAC__StreamEncoder *fse, unsigned compression, flac_encoder_setup(FLAC__StreamEncoder *fse, unsigned compression, bool oggflac,
const AudioFormat &audio_format) const AudioFormat &audio_format)
{ {
unsigned bits_per_sample; unsigned bits_per_sample;
@@ -157,7 +158,7 @@ flac_encoder_setup(FLAC__StreamEncoder *fse, unsigned compression,
throw FormatRuntimeError("error setting flac sample rate to %d", throw FormatRuntimeError("error setting flac sample rate to %d",
audio_format.sample_rate); audio_format.sample_rate);
if (!FLAC__stream_encoder_set_ogg_serial_number(fse, if (oggflac && !FLAC__stream_encoder_set_ogg_serial_number(fse,
GenerateSerial())) GenerateSerial()))
throw FormatRuntimeError("error setting ogg serial number"); throw FormatRuntimeError("error setting ogg serial number");
} }
@@ -166,11 +167,12 @@ FlacEncoder::FlacEncoder(AudioFormat _audio_format, FLAC__StreamEncoder *_fse, u
:Encoder(_oggchaining), :Encoder(_oggchaining),
audio_format(_audio_format), fse(_fse), audio_format(_audio_format), fse(_fse),
compression(_compression), compression(_compression),
oggflac(_oggflac),
output_buffer(8192) output_buffer(8192)
{ {
/* this immediately outputs data through callback */ /* this immediately outputs data through callback */
auto init_status = _oggflac ? auto init_status = oggflac ?
FLAC__stream_encoder_init_ogg_stream(fse, FLAC__stream_encoder_init_ogg_stream(fse,
nullptr, WriteCallback, nullptr, WriteCallback,
nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,
@@ -209,7 +211,7 @@ PreparedFlacEncoder::Open(AudioFormat &audio_format)
throw std::runtime_error("FLAC__stream_encoder_new() failed"); throw std::runtime_error("FLAC__stream_encoder_new() failed");
try { try {
flac_encoder_setup(fse, compression, audio_format); flac_encoder_setup(fse, compression, oggflac, audio_format);
} catch (...) { } catch (...) {
FLAC__stream_encoder_delete(fse); FLAC__stream_encoder_delete(fse);
throw; throw;
@@ -222,7 +224,7 @@ void
FlacEncoder::SendTag(const Tag &tag) FlacEncoder::SendTag(const Tag &tag)
{ {
/* re-initialize encoder since flac_encoder_finish resets everything */ /* re-initialize encoder since flac_encoder_finish resets everything */
flac_encoder_setup(fse, compression, audio_format); flac_encoder_setup(fse, compression, oggflac, audio_format);
FLAC__StreamMetadata *metadata = FLAC__metadata_object_new(FLAC__METADATA_TYPE_VORBIS_COMMENT); FLAC__StreamMetadata *metadata = FLAC__metadata_object_new(FLAC__METADATA_TYPE_VORBIS_COMMENT);
FLAC__StreamMetadata_VorbisComment_Entry entry; FLAC__StreamMetadata_VorbisComment_Entry entry;

@@ -272,9 +272,8 @@ EventLoop::Run() noexcept
#endif #endif
assert(IsInside()); assert(IsInside());
assert(!quit);
#ifdef HAVE_THREADED_EVENT_LOOP #ifdef HAVE_THREADED_EVENT_LOOP
assert(alive); assert(alive || quit);
assert(busy); assert(busy);
wake_event.Schedule(SocketEvent::READ); wake_event.Schedule(SocketEvent::READ);
@@ -299,7 +298,7 @@ EventLoop::Run() noexcept
steady_clock_cache.flush(); steady_clock_cache.flush();
do { while (!quit) {
again = false; again = false;
/* invoke timers */ /* invoke timers */
@@ -361,7 +360,7 @@ EventLoop::Run() noexcept
socket_event.Dispatch(); socket_event.Dispatch();
} }
} while (!quit); }
#ifdef HAVE_THREADED_EVENT_LOOP #ifdef HAVE_THREADED_EVENT_LOOP
#ifndef NDEBUG #ifndef NDEBUG

@@ -40,10 +40,15 @@ FfmpegFilter::FfmpegFilter(const AudioFormat &in_audio_format,
buffer_sink(_buffer_sink), buffer_sink(_buffer_sink),
in_format(Ffmpeg::ToFfmpegSampleFormat(in_audio_format.format)), in_format(Ffmpeg::ToFfmpegSampleFormat(in_audio_format.format)),
in_sample_rate(in_audio_format.sample_rate), in_sample_rate(in_audio_format.sample_rate),
#if LIBAVUTIL_VERSION_INT < AV_VERSION_INT(57, 25, 100)
in_channels(in_audio_format.channels), in_channels(in_audio_format.channels),
#endif
in_audio_frame_size(in_audio_format.GetFrameSize()), in_audio_frame_size(in_audio_format.GetFrameSize()),
out_audio_frame_size(_out_audio_format.GetFrameSize()) out_audio_frame_size(_out_audio_format.GetFrameSize())
{ {
#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(57, 25, 100)
av_channel_layout_default(&in_ch_layout, in_audio_format.channels);
#endif
} }
ConstBuffer<void> ConstBuffer<void>
@@ -54,7 +59,11 @@ FfmpegFilter::FilterPCM(ConstBuffer<void> src)
frame.Unref(); frame.Unref();
frame->format = in_format; frame->format = in_format;
frame->sample_rate = in_sample_rate; frame->sample_rate = in_sample_rate;
#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(57, 25, 100)
frame->ch_layout = in_ch_layout;
#else
frame->channels = in_channels; frame->channels = in_channels;
#endif
frame->nb_samples = src.size / in_audio_frame_size; frame->nb_samples = src.size / in_audio_frame_size;
frame.GetBuffer(); frame.GetBuffer();

@@ -35,7 +35,13 @@ class FfmpegFilter final : public Filter {
FfmpegBuffer interleave_buffer; FfmpegBuffer interleave_buffer;
const int in_format, in_sample_rate, in_channels; const int in_format, in_sample_rate;
#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(57, 25, 100)
AVChannelLayout in_ch_layout;
#else
const int in_channels;
#endif
const size_t in_audio_frame_size; const size_t in_audio_frame_size;
const size_t out_audio_frame_size; const size_t out_audio_frame_size;

@@ -23,6 +23,8 @@
#include "ReplayGainInfo.hxx" #include "ReplayGainInfo.hxx"
#include "ReplayGainConfig.hxx" #include "ReplayGainConfig.hxx"
#include "mixer/MixerControl.hxx" #include "mixer/MixerControl.hxx"
#include "mixer/MixerInternal.hxx"
#include "mixer/Listener.hxx"
#include "pcm/AudioFormat.hxx" #include "pcm/AudioFormat.hxx"
#include "pcm/Volume.hxx" #include "pcm/Volume.hxx"
#include "util/ConstBuffer.hxx" #include "util/ConstBuffer.hxx"
@@ -171,9 +173,11 @@ ReplayGainFilter::Update()
try { try {
mixer_set_volume(mixer, _volume); mixer_set_volume(mixer, _volume);
/* TODO: emit this idle event only for the /* invoke the mixer's listener manually, just
current partition */ in case the mixer implementation didn't do
idle_add(IDLE_MIXER); that already (this depends on the
implementation) */
mixer->listener.OnMixerVolumeChanged(*mixer, _volume);
} catch (...) { } catch (...) {
LogError(std::current_exception(), LogError(std::current_exception(),
"Failed to update hardware mixer"); "Failed to update hardware mixer");

@@ -20,7 +20,6 @@
#include "Charset.hxx" #include "Charset.hxx"
#include "Features.hxx" #include "Features.hxx"
#include "Domain.hxx" #include "Domain.hxx"
#include "Log.hxx"
#include "lib/icu/Converter.hxx" #include "lib/icu/Converter.hxx"
#include "util/AllocatedString.hxx" #include "util/AllocatedString.hxx"
#include "config.h" #include "config.h"
@@ -45,11 +44,9 @@ SetFSCharset(const char *charset)
assert(charset != nullptr); assert(charset != nullptr);
assert(fs_converter == nullptr); assert(fs_converter == nullptr);
fs_charset = charset;
fs_converter = IcuConverter::Create(charset); fs_converter = IcuConverter::Create(charset);
assert(fs_converter != nullptr); assert(fs_converter != nullptr);
FmtDebug(path_domain,
"SetFSCharset: fs charset is {}", fs_charset);
} }
#endif #endif

@@ -67,6 +67,16 @@ StatFile(Path file, struct stat &buf, bool follow_symlinks = true)
#endif #endif
static inline bool
CreateDirectoryNoThrow(Path path) noexcept
{
#ifdef _WIN32
return CreateDirectory(path.c_str(), nullptr);
#else
return mkdir(path.c_str(), 0777);
#endif
}
/** /**
* Truncate a file that exists already. Throws std::system_error on * Truncate a file that exists already. Throws std::system_error on
* error. * error.

@@ -34,7 +34,6 @@
#include <shlobj.h> #include <shlobj.h>
#else #else
#include <stdlib.h> #include <stdlib.h>
#include <unistd.h>
#include <pwd.h> #include <pwd.h>
#endif #endif
@@ -53,6 +52,12 @@
#include "Main.hxx" #include "Main.hxx"
#endif #endif
#ifdef USE_XDG
#include "Version.h" // for PACKAGE_NAME
#define APP_FILENAME PATH_LITERAL(PACKAGE_NAME)
static constexpr Path app_filename = Path::FromFS(APP_FILENAME);
#endif
#if !defined(_WIN32) && !defined(ANDROID) #if !defined(_WIN32) && !defined(ANDROID)
class PasswdEntry class PasswdEntry
{ {
@@ -74,15 +79,6 @@ public:
return result != nullptr; return result != nullptr;
} }
bool ReadByUid(uid_t uid) {
#ifdef HAVE_GETPWUID_R
getpwuid_r(uid, &pw, buf.data(), buf.size(), &result);
#else
result = getpwuid(uid);
#endif
return result != nullptr;
}
const passwd *operator->() { const passwd *operator->() {
assert(result != nullptr); assert(result != nullptr);
return result; return result;
@@ -254,7 +250,8 @@ GetUserMusicDir() noexcept
#elif defined(USE_XDG) #elif defined(USE_XDG)
return GetUserDir("XDG_MUSIC_DIR"); return GetUserDir("XDG_MUSIC_DIR");
#elif defined(ANDROID) #elif defined(ANDROID)
return Environment::getExternalStoragePublicDirectory("Music"); return Environment::getExternalStoragePublicDirectory(Java::GetEnv(),
"Music");
#else #else
return nullptr; return nullptr;
#endif #endif
@@ -283,6 +280,24 @@ GetUserCacheDir() noexcept
#endif #endif
} }
AllocatedPath
GetAppCacheDir() noexcept
{
#ifdef USE_XDG
if (const auto user_dir = GetUserCacheDir(); !user_dir.IsNull()) {
auto dir = user_dir / app_filename;
CreateDirectoryNoThrow(dir);
return dir;
}
return nullptr;
#elif defined(ANDROID)
return context->GetCacheDir(Java::GetEnv());
#else
return nullptr;
#endif
}
AllocatedPath AllocatedPath
GetUserRuntimeDir() noexcept GetUserRuntimeDir() noexcept
{ {
@@ -296,7 +311,7 @@ GetUserRuntimeDir() noexcept
AllocatedPath AllocatedPath
GetAppRuntimeDir() noexcept GetAppRuntimeDir() noexcept
{ {
#ifdef __linux__ #if defined(__linux__) && !defined(ANDROID)
/* systemd specific; see systemd.exec(5) */ /* systemd specific; see systemd.exec(5) */
if (const char *runtime_directory = getenv("RUNTIME_DIRECTORY")) if (const char *runtime_directory = getenv("RUNTIME_DIRECTORY"))
if (auto dir = StringView{runtime_directory}.Split(':').first; if (auto dir = StringView{runtime_directory}.Split(':').first;
@@ -306,8 +321,8 @@ GetAppRuntimeDir() noexcept
#ifdef USE_XDG #ifdef USE_XDG
if (const auto user_dir = GetUserRuntimeDir(); !user_dir.IsNull()) { if (const auto user_dir = GetUserRuntimeDir(); !user_dir.IsNull()) {
auto dir = user_dir / Path::FromFS("mpd"); auto dir = user_dir / app_filename;
mkdir(dir.c_str(), 0700); CreateDirectoryNoThrow(dir);
return dir; return dir;
} }
#endif #endif
@@ -350,10 +365,8 @@ GetHomeDir() noexcept
if (const auto home = getenv("HOME"); if (const auto home = getenv("HOME");
IsValidPathString(home) && IsValidDir(home)) IsValidPathString(home) && IsValidDir(home))
return AllocatedPath::FromFS(home); return AllocatedPath::FromFS(home);
if (PasswdEntry pw; pw.ReadByUid(getuid()))
return SafePathFromFS(pw->pw_dir);
#endif #endif
return nullptr; return nullptr;
} }

@@ -43,6 +43,13 @@ GetUserMusicDir() noexcept;
AllocatedPath AllocatedPath
GetUserCacheDir() noexcept; GetUserCacheDir() noexcept;
/**
* Obtains cache directory for this application.
*/
[[gnu::const]]
AllocatedPath
GetAppCacheDir() noexcept;
/** /**
* Obtains the runtime directory for the current user. * Obtains the runtime directory for the current user.
*/ */

@@ -101,9 +101,17 @@ AsyncInputStream::Seek(std::unique_lock<Mutex> &lock,
assert(IsReady()); assert(IsReady());
assert(seek_state == SeekState::NONE); assert(seek_state == SeekState::NONE);
if (new_offset == offset) if (new_offset == offset) {
/* no-op */ /* no-op, but if the stream is not open anymore (maybe
because it has failed), nothing can be read, so we
should check for errors here instead of pretending
everything's fine */
if (!open)
Check();
return; return;
}
if (!IsSeekable()) if (!IsSeekable())
throw std::runtime_error("Not seekable"); throw std::runtime_error("Not seekable");

@@ -45,6 +45,14 @@
#include <cdio/cd_types.h> #include <cdio/cd_types.h>
static constexpr Domain cdio_domain("cdio");
static bool default_reverse_endian;
static unsigned speed = 0;
/* Default to full paranoia, but allow skipping sectors. */
static int mode_flags = PARANOIA_MODE_FULL^PARANOIA_MODE_NEVERSKIP;
class CdioParanoiaInputStream final : public InputStream { class CdioParanoiaInputStream final : public InputStream {
cdrom_drive_t *const drv; cdrom_drive_t *const drv;
CdIo_t *const cdio; CdIo_t *const cdio;
@@ -65,9 +73,7 @@ class CdioParanoiaInputStream final : public InputStream {
lsn_from(_lsn_from), lsn_from(_lsn_from),
buffer_lsn(-1) buffer_lsn(-1)
{ {
/* Set reading mode for full paranoia, but allow para.SetMode(mode_flags);
skipping sectors. */
para.SetMode(PARANOIA_MODE_FULL^PARANOIA_MODE_NEVERSKIP);
/* seek to beginning of the track */ /* seek to beginning of the track */
para.Seek(lsn_from); para.Seek(lsn_from);
@@ -98,11 +104,6 @@ class CdioParanoiaInputStream final : public InputStream {
void Seek(std::unique_lock<Mutex> &lock, offset_type offset) override; void Seek(std::unique_lock<Mutex> &lock, offset_type offset) override;
}; };
static constexpr Domain cdio_domain("cdio");
static bool default_reverse_endian;
static unsigned speed = 0;
static void static void
input_cdio_init(EventLoop &, const ConfigBlock &block) input_cdio_init(EventLoop &, const ConfigBlock &block)
{ {
@@ -117,6 +118,26 @@ input_cdio_init(EventLoop &, const ConfigBlock &block)
value); value);
} }
speed = block.GetBlockValue("speed",0U); speed = block.GetBlockValue("speed",0U);
if (const auto *param = block.GetBlockParam("mode")) {
param->With([](const char *s){
if (StringIsEqual(s, "disable"))
mode_flags = PARANOIA_MODE_DISABLE;
else if (StringIsEqual(s, "overlap"))
mode_flags = PARANOIA_MODE_OVERLAP;
else if (StringIsEqual(s, "full"))
mode_flags = PARANOIA_MODE_FULL;
else
throw std::invalid_argument{"Invalid paranoia mode"};
});
}
if (const auto *param = block.GetBlockParam("skip")) {
if (param->GetBoolValue())
mode_flags &= ~PARANOIA_MODE_NEVERSKIP;
else
mode_flags |= PARANOIA_MODE_NEVERSKIP;
}
} }
struct CdioUri { struct CdioUri {

@@ -417,7 +417,6 @@ CurlInputStream::InitEasy()
request->SetOption(CURLOPT_HTTP200ALIASES, http_200_aliases); request->SetOption(CURLOPT_HTTP200ALIASES, http_200_aliases);
request->SetOption(CURLOPT_FOLLOWLOCATION, 1L); request->SetOption(CURLOPT_FOLLOWLOCATION, 1L);
request->SetOption(CURLOPT_MAXREDIRS, 5L); request->SetOption(CURLOPT_MAXREDIRS, 5L);
request->SetOption(CURLOPT_FAILONERROR, 1L);
/* this option eliminates the probe request when /* this option eliminates the probe request when
username/password are specified */ username/password are specified */
@@ -439,6 +438,14 @@ CurlInputStream::InitEasy()
request->SetVerifyPeer(verify_peer); request->SetVerifyPeer(verify_peer);
request->SetVerifyHost(verify_host); request->SetVerifyHost(verify_host);
request->SetOption(CURLOPT_HTTPHEADER, request_headers.Get()); request->SetOption(CURLOPT_HTTPHEADER, request_headers.Get());
try {
request->SetProxyVerifyPeer(verify_peer);
request->SetProxyVerifyHost(verify_host);
} catch (...) {
/* these methods fail if libCURL was compiled with
CURL_DISABLE_PROXY; ignore silently */
}
} }
void void

@@ -42,6 +42,8 @@
#include "io/UniqueFileDescriptor.hxx" #include "io/UniqueFileDescriptor.hxx"
#endif #endif
#include <cstdint>
class Path; class Path;
class FileInfo; class FileInfo;

@@ -49,9 +49,6 @@ Java::File::Initialise(JNIEnv *env) noexcept
AllocatedPath AllocatedPath
Java::File::ToAbsolutePath(JNIEnv *env, jobject _file) noexcept Java::File::ToAbsolutePath(JNIEnv *env, jobject _file) noexcept
{ {
assert(env != nullptr);
assert(_file != nullptr);
LocalObject file(env, _file); LocalObject file(env, _file);
const jstring path = GetAbsolutePath(env, file); const jstring path = GetAbsolutePath(env, file);

@@ -89,6 +89,16 @@ public:
String(JNIEnv *_env, const char *_value) noexcept String(JNIEnv *_env, const char *_value) noexcept
:LocalRef<jstring>(_env, _env->NewStringUTF(_value)) {} :LocalRef<jstring>(_env, _env->NewStringUTF(_value)) {}
/**
* This constructor allows passing a nullptr value, which maps
* to a "null" in Java.
*/
static String Optional(JNIEnv *_env, const char *_value) noexcept {
return _value != nullptr
? String{_env, _value}
: String{};
}
static StringUTFChars GetUTFChars(JNIEnv *env, jstring s) noexcept { static StringUTFChars GetUTFChars(JNIEnv *env, jstring s) noexcept {
return {env, s, env->GetStringUTFChars(s, nullptr)}; return {env, s, env->GetStringUTFChars(s, nullptr)};
} }

@@ -18,13 +18,13 @@ endif
conf.set('HAVE_MD5', crypto_md5_dep.found()) conf.set('HAVE_MD5', crypto_md5_dep.found())
if libavutil_dep.found() if ffmpeg_util_dep.found()
crypto_base64 = static_library( crypto_base64 = static_library(
'crypto_base64', 'crypto_base64',
'Base64.cxx', 'Base64.cxx',
include_directories: inc, include_directories: inc,
dependencies: [ dependencies: [
libavutil_dep, ffmpeg_util_dep,
], ],
) )

@@ -186,10 +186,6 @@ public:
SetOption(CURLOPT_POSTFIELDSIZE, (long)size); SetOption(CURLOPT_POSTFIELDSIZE, (long)size);
} }
void SetHttpPost(const struct curl_httppost *post) {
SetOption(CURLOPT_HTTPPOST, post);
}
template<typename T> template<typename T>
bool GetInfo(CURLINFO info, T value_r) const noexcept { bool GetInfo(CURLINFO info, T value_r) const noexcept {
return ::curl_easy_getinfo(handle, info, value_r) == CURLE_OK; return ::curl_easy_getinfo(handle, info, value_r) == CURLE_OK;
@@ -199,10 +195,10 @@ public:
* Returns the response body's size, or -1 if that is unknown. * Returns the response body's size, or -1 if that is unknown.
*/ */
[[gnu::pure]] [[gnu::pure]]
int64_t GetContentLength() const noexcept { curl_off_t GetContentLength() const noexcept {
double value; curl_off_t value;
return GetInfo(CURLINFO_CONTENT_LENGTH_DOWNLOAD, &value) return GetInfo(CURLINFO_CONTENT_LENGTH_DOWNLOAD_T, &value)
? (int64_t)value ? value
: -1; : -1;
} }

@@ -123,6 +123,14 @@ public:
easy.SetVerifyPeer(value); easy.SetVerifyPeer(value);
} }
void SetProxyVerifyHost(bool value) {
easy.SetOption(CURLOPT_PROXY_SSL_VERIFYHOST, value ? 2L : 0L);
}
void SetProxyVerifyPeer(bool value) {
easy.SetOption(CURLOPT_PROXY_SSL_VERIFYPEER, value);
}
void SetNoBody(bool value=true) { void SetNoBody(bool value=true) {
easy.SetNoBody(value); easy.SetNoBody(value);
} }

@@ -1,4 +1,4 @@
curl_dep = dependency('libcurl', version: '>= 7.33', required: get_option('curl')) curl_dep = dependency('libcurl', version: '>= 7.55', required: get_option('curl'))
conf.set('ENABLE_CURL', curl_dep.found()) conf.set('ENABLE_CURL', curl_dep.found())
if not curl_dep.found() if not curl_dep.found()
subdir_done() subdir_done()

@@ -36,6 +36,7 @@
#include "Iter.hxx" #include "Iter.hxx"
#include "Values.hxx" #include "Values.hxx"
#include <cstdint>
#include <stdexcept> #include <stdexcept>
namespace ODBus { namespace ODBus {

@@ -48,7 +48,11 @@ DetectFilterOutputFormat(const AudioFormat &in_audio_format,
Frame frame; Frame frame;
frame->format = ToFfmpegSampleFormat(in_audio_format.format); frame->format = ToFfmpegSampleFormat(in_audio_format.format);
frame->sample_rate = in_audio_format.sample_rate; frame->sample_rate = in_audio_format.sample_rate;
#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(57, 25, 100)
av_channel_layout_default(&frame->ch_layout, in_audio_format.channels);
#else
frame->channels = in_audio_format.channels; frame->channels = in_audio_format.channels;
#endif
frame->nb_samples = 1; frame->nb_samples = 1;
frame.GetBuffer(); frame.GetBuffer();
@@ -75,8 +79,14 @@ DetectFilterOutputFormat(const AudioFormat &in_audio_format,
if (sample_format == SampleFormat::UNDEFINED) if (sample_format == SampleFormat::UNDEFINED)
throw std::runtime_error("Unsupported FFmpeg sample format"); throw std::runtime_error("Unsupported FFmpeg sample format");
#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(57, 25, 100)
const unsigned out_channels = frame->ch_layout.nb_channels;
#else
const unsigned out_channels = frame->channels;
#endif
return CheckAudioFormat(frame->sample_rate, sample_format, return CheckAudioFormat(frame->sample_rate, sample_format,
frame->channels); out_channels);
} }
} // namespace Ffmpeg } // namespace Ffmpeg

@@ -38,7 +38,11 @@ InterleaveFrame(const AVFrame &frame, FfmpegBuffer &buffer)
assert(frame.nb_samples > 0); assert(frame.nb_samples > 0);
const auto format = AVSampleFormat(frame.format); const auto format = AVSampleFormat(frame.format);
#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(57, 25, 100)
const unsigned channels = frame.ch_layout.nb_channels;
#else
const unsigned channels = frame.channels; const unsigned channels = frame.channels;
#endif
const std::size_t n_frames = frame.nb_samples; const std::size_t n_frames = frame.nb_samples;
int plane_size; int plane_size;

@@ -1,5 +1,5 @@
/* /*
* Copyright 2003-2021 The Music Player Daemon Project * Copyright 2003-2022 The Music Player Daemon Project
* http://www.musicpd.org * http://www.musicpd.org
* *
* This program is free software; you can redistribute it and/or modify * This program is free software; you can redistribute it and/or modify
@@ -17,26 +17,23 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/ */
#include "LogError.hxx" #pragma once
#include "Domain.hxx"
#include "Log.hxx"
extern "C" { extern "C" {
#include <libavutil/error.h> #include <libavutil/samplefmt.h>
} }
void #include <fmt/format.h>
LogFfmpegError(int errnum)
{
char msg[256];
av_strerror(errnum, msg, sizeof(msg));
LogError(ffmpeg_domain, msg);
}
void template<>
LogFfmpegError(int errnum, const char *prefix) struct fmt::formatter<AVSampleFormat> : formatter<string_view>
{ {
char msg[256]; template<typename FormatContext>
av_strerror(errnum, msg, sizeof(msg)); auto format(const AVSampleFormat format, FormatContext &ctx) {
FmtError(ffmpeg_domain, "{}: {}", prefix, msg); const char *name = av_get_sample_fmt_name(format);
} if (name == nullptr)
name = "?";
return formatter<string_view>::format(name, ctx);
}
};

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

@@ -13,6 +13,29 @@ else
endif endif
conf.set('HAVE_LIBAVFILTER', libavfilter_dep.found()) conf.set('HAVE_LIBAVFILTER', libavfilter_dep.found())
if not libavutil_dep.found()
ffmpeg_util_dep = dependency('', required: false)
ffmpeg_dep = dependency('', required: false)
subdir_done()
endif
ffmpeg_util = static_library(
'ffmpeg_util',
'Interleave.cxx',
'Error.cxx',
include_directories: inc,
dependencies: [
libavutil_dep,
],
)
ffmpeg_util_dep = declare_dependency(
link_with: ffmpeg_util,
dependencies: [
libavutil_dep,
],
)
if not enable_ffmpeg if not enable_ffmpeg
ffmpeg_dep = dependency('', required: false) ffmpeg_dep = dependency('', required: false)
subdir_done() subdir_done()
@@ -30,17 +53,16 @@ ffmpeg = static_library(
'ffmpeg', 'ffmpeg',
'Init.cxx', 'Init.cxx',
'Interleave.cxx', 'Interleave.cxx',
'LogError.cxx',
'LogCallback.cxx', 'LogCallback.cxx',
'Error.cxx', 'Error.cxx',
'Domain.cxx', 'Domain.cxx',
ffmpeg_sources, ffmpeg_sources,
include_directories: inc, include_directories: inc,
dependencies: [ dependencies: [
ffmpeg_util_dep,
libavformat_dep, libavformat_dep,
libavcodec_dep, libavcodec_dep,
libavfilter_dep, libavfilter_dep,
libavutil_dep,
log_dep, log_dep,
], ],
) )
@@ -48,9 +70,9 @@ ffmpeg = static_library(
ffmpeg_dep = declare_dependency( ffmpeg_dep = declare_dependency(
link_with: ffmpeg, link_with: ffmpeg,
dependencies: [ dependencies: [
ffmpeg_util_dep,
libavformat_dep, libavformat_dep,
libavcodec_dep, libavcodec_dep,
libavfilter_dep, libavfilter_dep,
libavutil_dep,
], ],
) )

@@ -18,17 +18,25 @@ if icu_dep.found()
'Util.cxx', 'Util.cxx',
'Init.cxx', 'Init.cxx',
] ]
elif not get_option('iconv').disabled() else
# an installed iconv library will make the builtin iconv() unavailable, if meson.version().version_compare('>= 0.60')
# so search for the library first and pass it as (possible) dependency iconv_dep = dependency('iconv', required: get_option('iconv'))
iconv_dep = compiler.find_library('libiconv', required: false) conf.set('HAVE_ICONV', iconv_dep.found())
have_iconv = compiler.has_function('iconv', elif not get_option('iconv').disabled()
dependencies: iconv_dep, iconv_open_snippet = '''#include <iconv.h>
prefix : '#include <iconv.h>') int main() {
if not have_iconv and get_option('iconv').enabled() iconv_open("","");
error('iconv() not available') }'''
have_iconv = compiler.links(iconv_open_snippet, name: 'iconv_open')
if not have_iconv
iconv_dep = compiler.find_library('iconv', required: false)
have_iconv = compiler.links(iconv_open_snippet, dependencies: iconv_dep, name: 'iconv_open')
endif
if not have_iconv and get_option('iconv').enabled()
error('iconv() not available')
endif
conf.set('HAVE_ICONV', have_iconv)
endif endif
conf.set('HAVE_ICONV', have_iconv)
endif endif
icu = static_library( icu = static_library(

@@ -0,0 +1,22 @@
Index: libmodplug-0.8.9.0/src/fastmix.cpp
===================================================================
--- libmodplug-0.8.9.0.orig/src/fastmix.cpp
+++ libmodplug-0.8.9.0/src/fastmix.cpp
@@ -288,7 +288,7 @@ CzWINDOWEDFIR sfir;
// MIXING MACROS
// ----------------------------------------------------------------------------
#define SNDMIX_BEGINSAMPLELOOP8\
- register MODCHANNEL * const pChn = pChannel;\
+ MODCHANNEL * const pChn = pChannel;\
nPos = pChn->nPosLo;\
const signed char *p = (signed char *)(pChn->pCurrentSample+pChn->nPos);\
if (pChn->dwFlags & CHN_STEREO) p += pChn->nPos;\
@@ -296,7 +296,7 @@ CzWINDOWEDFIR sfir;
do {
#define SNDMIX_BEGINSAMPLELOOP16\
- register MODCHANNEL * const pChn = pChannel;\
+ MODCHANNEL * const pChn = pChannel;\
nPos = pChn->nPosLo;\
const signed short *p = (signed short *)(pChn->pCurrentSample+(pChn->nPos*2));\
if (pChn->dwFlags & CHN_STEREO) p += pChn->nPos;\

@@ -0,0 +1 @@
no_register

@@ -17,14 +17,11 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/ */
#include "Volume.hxx" #include "Memento.hxx"
#include "output/MultipleOutputs.hxx" #include "output/MultipleOutputs.hxx"
#include "Idle.hxx" #include "Idle.hxx"
#include "util/StringCompare.hxx" #include "util/StringCompare.hxx"
#include "util/Domain.hxx"
#include "system/PeriodClock.hxx"
#include "io/BufferedOutputStream.hxx" #include "io/BufferedOutputStream.hxx"
#include "Log.hxx"
#include <cassert> #include <cassert>
@@ -32,24 +29,8 @@
#define SW_VOLUME_STATE "sw_volume: " #define SW_VOLUME_STATE "sw_volume: "
static constexpr Domain volume_domain("volume");
static unsigned volume_software_set = 100;
/** the cached hardware mixer value; invalid if negative */
static int last_hardware_volume = -1;
/** the age of #last_hardware_volume */
static PeriodClock hardware_volume_clock;
void
InvalidateHardwareVolume() noexcept
{
/* flush the hardware volume cache */
last_hardware_volume = -1;
}
int int
volume_level_get(const MultipleOutputs &outputs) noexcept MixerMemento::GetVolume(const MultipleOutputs &outputs) noexcept
{ {
if (last_hardware_volume >= 0 && if (last_hardware_volume >= 0 &&
!hardware_volume_clock.CheckUpdate(std::chrono::seconds(1))) !hardware_volume_clock.CheckUpdate(std::chrono::seconds(1)))
@@ -60,8 +41,8 @@ volume_level_get(const MultipleOutputs &outputs) noexcept
return last_hardware_volume; return last_hardware_volume;
} }
static bool inline bool
software_volume_change(MultipleOutputs &outputs, unsigned volume) MixerMemento::SetSoftwareVolume(MultipleOutputs &outputs, unsigned volume)
{ {
assert(volume <= 100); assert(volume <= 100);
@@ -71,8 +52,8 @@ software_volume_change(MultipleOutputs &outputs, unsigned volume)
return true; return true;
} }
static void inline void
hardware_volume_change(MultipleOutputs &outputs, unsigned volume) MixerMemento::SetHardwareVolume(MultipleOutputs &outputs, unsigned volume)
{ {
/* reset the cache */ /* reset the cache */
last_hardware_volume = -1; last_hardware_volume = -1;
@@ -81,19 +62,17 @@ hardware_volume_change(MultipleOutputs &outputs, unsigned volume)
} }
void void
volume_level_change(MultipleOutputs &outputs, unsigned volume) MixerMemento::SetVolume(MultipleOutputs &outputs, unsigned volume)
{ {
assert(volume <= 100); assert(volume <= 100);
volume_software_set = volume; volume_software_set = volume;
idle_add(IDLE_MIXER); SetHardwareVolume(outputs, volume);
hardware_volume_change(outputs, volume);
} }
bool bool
read_sw_volume_state(const char *line, MultipleOutputs &outputs) MixerMemento::LoadSoftwareVolumeState(const char *line, MultipleOutputs &outputs)
{ {
char *end = nullptr; char *end = nullptr;
long int sv; long int sv;
@@ -104,21 +83,13 @@ read_sw_volume_state(const char *line, MultipleOutputs &outputs)
sv = strtol(line, &end, 10); sv = strtol(line, &end, 10);
if (*end == 0 && sv >= 0 && sv <= 100) if (*end == 0 && sv >= 0 && sv <= 100)
software_volume_change(outputs, sv); SetSoftwareVolume(outputs, sv);
else
FmtWarning(volume_domain,
"Can't parse software volume: {}", line);
return true; return true;
} }
void void
save_sw_volume_state(BufferedOutputStream &os) MixerMemento::SaveSoftwareVolumeState(BufferedOutputStream &os) const
{ {
os.Format(SW_VOLUME_STATE "%u\n", volume_software_set); os.Format(SW_VOLUME_STATE "%u\n", volume_software_set);
} }
unsigned
sw_volume_state_get_hash() noexcept
{
return volume_software_set;
}

75
src/mixer/Memento.hxx Normal file

@@ -0,0 +1,75 @@
/*
* Copyright 2003-2021 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#pragma once
#include "system/PeriodClock.hxx"
class MultipleOutputs;
class BufferedOutputStream;
/**
* Cache for hardware/software volume levels.
*/
class MixerMemento {
unsigned volume_software_set = 100;
/** the cached hardware mixer value; invalid if negative */
int last_hardware_volume = -1;
/** the age of #last_hardware_volume */
PeriodClock hardware_volume_clock;
public:
/**
* Flush the hardware volume cache.
*/
void InvalidateHardwareVolume() noexcept {
last_hardware_volume = -1;
}
[[gnu::pure]]
int GetVolume(const MultipleOutputs &outputs) noexcept;
/**
* Throws on error.
*
* Note: the caller is responsible for emitting #IDLE_MIXER.
*/
void SetVolume(MultipleOutputs &outputs, unsigned volume);
bool LoadSoftwareVolumeState(const char *line, MultipleOutputs &outputs);
void SaveSoftwareVolumeState(BufferedOutputStream &os) const;
/**
* Generates a hash number for the current state of the software
* volume control. This is used by timer_save_state_file() to
* determine whether the state has changed and the state file should
* be saved.
*/
[[gnu::pure]]
unsigned GetSoftwareVolumeStateHash() const noexcept {
return volume_software_set;
}
private:
bool SetSoftwareVolume(MultipleOutputs &outputs, unsigned volume);
void SetHardwareVolume(MultipleOutputs &outputs, unsigned volume);
};

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