Compare commits
164 Commits
Author | SHA1 | Date | |
---|---|---|---|
8842650c33 | |||
d5bf128cee | |||
5cd86e272f | |||
740cbe9e02 | |||
ed890a273a | |||
068cd559e1 | |||
dc127f39a7 | |||
7a99a7008c | |||
70b451db7b | |||
2ab03a0914 | |||
2fa8c7d2db | |||
7c759ba8b0 | |||
6d9b452fde | |||
f7eb1c9a83 | |||
2d22e6dee4 | |||
4587bf759d | |||
e1e37cfe3c | |||
381934985a | |||
a8042885ac | |||
a71e68db50 | |||
1417578b3d | |||
96befa138c | |||
16a99804de | |||
75a39ed279 | |||
4d357ab77c | |||
d4f3dd49b4 | |||
4ec6d0555a | |||
a6a1182c4c | |||
a59c9c602b | |||
0c4d824d64 | |||
a5281856c9 | |||
0206a46d39 | |||
9475ef2202 | |||
edae00e719 | |||
fb695bc55f | |||
23a5b8fd3c | |||
273a93cfcf | |||
d105985d78 | |||
f8cfeb39e9 | |||
d5d3982d3c | |||
47341107ea | |||
90eaa87a4d | |||
b09a54b2c2 | |||
10aec174d5 | |||
d32ed194e8 | |||
70d0fbd715 | |||
302432e157 | |||
4ab8a677dc | |||
52e4a4c904 | |||
a0f6932ebe | |||
6e700dab69 | |||
35eaed7206 | |||
e7c963f2ce | |||
949d72e368 | |||
8d2a184658 | |||
c877a32d97 | |||
541468f0ca | |||
d2797effa3 | |||
1170fb1e1e | |||
65b9b3195c | |||
258830e913 | |||
d91da96798 | |||
b3897df682 | |||
3cacb56bb7 | |||
15a1973e28 | |||
ad7d47a8ba | |||
0948c607b6 | |||
60d04052c5 | |||
c1780ac657 | |||
e49cf0ec38 | |||
e1d641f684 | |||
4efd0a9f77 | |||
f6f8751332 | |||
abb28593ce | |||
115693b046 | |||
e4b055eb6d | |||
9866adff95 | |||
a8b0c55818 | |||
cac88e8be5 | |||
e9f6a3482c | |||
5d2e80f188 | |||
cfd4d5b13e | |||
06514aec63 | |||
4ded1ae67b | |||
1da974e3fa | |||
94f06f0946 | |||
d9eec8a455 | |||
eaecbcafb2 | |||
73b5d0a9b9 | |||
c2d0f35e7a | |||
ab99a57997 | |||
c8ebaf3521 | |||
52d00f7e30 | |||
309491a6d8 | |||
e7bfd32ccc | |||
6f283b52ab | |||
32bddfabea | |||
1944c826bc | |||
619bb60b26 | |||
c549e16ed1 | |||
01c9c4507f | |||
8c9d7bf07e | |||
44ef34db88 | |||
5781f223f6 | |||
e4c8ebe056 | |||
76b25a1377 | |||
ccc3ee663b | |||
0626661764 | |||
31db04a3ca | |||
0c7163b9db | |||
7d78cad8af | |||
912530ed20 | |||
d3f37199b9 | |||
a4748d84b0 | |||
8f847ec381 | |||
3a70f09dd3 | |||
568f63100b | |||
3e25916b37 | |||
5f9438dae6 | |||
99e65c58ce | |||
df71b07e9d | |||
2694195215 | |||
66450d1f3c | |||
76efea3aa7 | |||
7ab0dfc8ce | |||
15ff7c4cad | |||
9ab9b97f20 | |||
88d92aceab | |||
a2ce4352c8 | |||
84f43ccde8 | |||
38704c9cf3 | |||
910d0ec92b | |||
3b05c89765 | |||
e77b3fa46f | |||
12147f6d58 | |||
40bc60d6ae | |||
7778210269 | |||
6229210d51 | |||
5d0d5b5d97 | |||
1aa3c1e543 | |||
b90e32fe4e | |||
1f4df2a64d | |||
2efc1db6a9 | |||
e2d4654e20 | |||
2b8f1170a6 | |||
5c4743441e | |||
cb288439a4 | |||
69f741e8a6 | |||
4b4f47002b | |||
615c301961 | |||
dc07180e48 | |||
d3b235bab5 | |||
7c920ddebe | |||
bbc088ae4e | |||
fe195257d8 | |||
57d5df8118 | |||
59792cb0b8 | |||
cc557c4d60 | |||
956c5faebb | |||
cd0396c1f1 | |||
79f9b268bb | |||
b45f3c8deb | |||
f8a8de87e4 | |||
2183f0553c |
NEWS
android
doc
meson.buildpython/build
src
CommandLine.cxxCommandLine.hxxLogInit.cxxMain.cxxPartition.cxxPartition.hxxPlaylistFile.cxxRemoteTagCache.hxxSongPrint.cxxSongSave.cxxSongSave.hxxStateFile.cxxStateFileConfig.cxxTagPrint.cxxTimePrint.cxx
android
archive
command
DatabaseCommands.cxxFileCommands.cxxOtherCommands.cxxOutputCommands.cxxPlayerCommands.cxxStorageCommands.cxx
db
decoder
DecoderList.cxx
plugins
encoder
plugins
event
filter
fs
input
io
java
lib
crypto
curl
dbus
ffmpeg
icu
mixer
output
storage
system
tag
time
unix
util
subprojects
systemd
test
win32
68
NEWS
68
NEWS
@ -1,3 +1,71 @@
|
||||
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)
|
||||
* storage
|
||||
- curl: fix crash if web server does not understand WebDAV
|
||||
|
@ -2,10 +2,10 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="org.musicpd"
|
||||
android:installLocation="auto"
|
||||
android:versionCode="66"
|
||||
android:versionName="0.23.7">
|
||||
android:versionCode="71"
|
||||
android:versionName="0.23.12">
|
||||
|
||||
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="29"/>
|
||||
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="30"/>
|
||||
|
||||
<uses-feature android:name="android.software.leanback"
|
||||
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') != ''
|
||||
debug_apk = custom_target(
|
||||
'mpd-debug.apk',
|
||||
output: 'mpd-debug.apk',
|
||||
input: unsigned_apk,
|
||||
input: aligned_apk,
|
||||
command: [
|
||||
jarsigner,
|
||||
'-keystore', get_option('android_debug_keystore'),
|
||||
'-storepass', 'android',
|
||||
'-signedjar', '@OUTPUT@',
|
||||
'@INPUT@',
|
||||
'androiddebugkey',
|
||||
apksigner, 'sign',
|
||||
'--in', '@INPUT@',
|
||||
'--out', '@OUTPUT@',
|
||||
'--debuggable-apk-permitted',
|
||||
'-ks', get_option('android_debug_keystore'),
|
||||
'--ks-key-alias', 'androiddebugkey',
|
||||
'--ks-pass', 'pass:android',
|
||||
],
|
||||
build_by_default: true
|
||||
)
|
||||
@ -31,29 +43,16 @@ endif
|
||||
|
||||
if get_option('android_keystore') != '' and get_option('android_keyalias') != '' and get_option('android_keypass') != ''
|
||||
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',
|
||||
output: 'mpd.apk',
|
||||
input: unaligned_apk,
|
||||
input: aligned_apk,
|
||||
command: [
|
||||
android_zipalign,
|
||||
'-f', '4',
|
||||
'@INPUT@', '@OUTPUT@',
|
||||
apksigner, 'sign',
|
||||
'--in', '@INPUT@',
|
||||
'--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
|
||||
|
@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python3
|
||||
#!/usr/bin/env -S python3 -u
|
||||
|
||||
import os, os.path
|
||||
import sys, subprocess
|
||||
@ -100,6 +100,7 @@ class AndroidNdkToolchain:
|
||||
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')
|
||||
|
@ -17,7 +17,7 @@ android_dx = join_paths(android_build_tools_dir, 'dx')
|
||||
android_zipalign = join_paths(android_build_tools_dir, 'zipalign')
|
||||
|
||||
javac = find_program('javac')
|
||||
jarsigner = find_program('jarsigner')
|
||||
apksigner = find_program('apksigner')
|
||||
rsvg_convert = find_program('rsvg-convert')
|
||||
convert = find_program('convert')
|
||||
zip = find_program('zip')
|
||||
|
@ -38,7 +38,10 @@ author = 'Max Kellermann'
|
||||
# built documents.
|
||||
#
|
||||
# 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.
|
||||
#release = version + '~git'
|
||||
|
||||
@ -47,7 +50,7 @@ version = '0.23.8'
|
||||
#
|
||||
# This is also used if you do content translation via gettext catalogs.
|
||||
# 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
|
||||
# non-false value, then it is used:
|
||||
|
@ -11,6 +11,12 @@ Music Player Daemon
|
||||
client
|
||||
protocol
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
:caption: man pages:
|
||||
|
||||
mpd.1
|
||||
mpd.conf.5
|
||||
|
||||
Indices and tables
|
||||
==================
|
||||
|
@ -181,7 +181,7 @@
|
||||
#
|
||||
#database {
|
||||
# plugin "simple"
|
||||
# path "~/.local/share/mpd/db
|
||||
# path "~/.local/share/mpd/db"
|
||||
# cache_directory "~/.local/share/mpd/cache"
|
||||
#}
|
||||
#
|
||||
|
@ -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.
|
||||
* - **speed N**
|
||||
- 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
|
||||
----
|
||||
@ -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
|
||||
them to a blank value), general curl configuration from environment
|
||||
variables such as ``http_proxy`` or specified in :file:`~/.curlrc`
|
||||
will be in effect.
|
||||
variables such as ``http_proxy`` will be in effect.
|
||||
|
||||
User name and password are read from an optional :file:`~/.netrc`, :file:`~/.curlrc` is not read.
|
||||
|
||||
.. list-table::
|
||||
:widths: 20 80
|
||||
|
11
doc/user.rst
11
doc/user.rst
@ -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.
|
||||
|
||||
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
|
||||
<sles_output>` output plugin can be used for local playback.
|
||||
@ -197,7 +199,7 @@ Compiling for Android
|
||||
You need:
|
||||
|
||||
* 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
|
||||
<https://ninja-build.org/>`__
|
||||
* 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
|
||||
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
|
||||
prevents `Bit-perfect playback`_). To use a hardware mixer, set
|
||||
``replay_gain_handler`` to ``mixer`` in the ``audio_output`` section
|
||||
|
14
meson.build
14
meson.build
@ -1,7 +1,7 @@
|
||||
project(
|
||||
'mpd',
|
||||
['c', 'cpp'],
|
||||
version: '0.23.8',
|
||||
version: '0.23.13',
|
||||
meson_version: '>= 0.56.0',
|
||||
default_options: [
|
||||
'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('HAVE_GETPWNAM_R', compiler.has_function('getpwnam_r'))
|
||||
conf.set('HAVE_GETPWUID_R', compiler.has_function('getpwuid_r'))
|
||||
conf.set('HAVE_INITGROUPS', compiler.has_function('initgroups'))
|
||||
conf.set('HAVE_FNMATCH', compiler.has_function('fnmatch'))
|
||||
|
||||
@ -251,6 +250,14 @@ endif
|
||||
|
||||
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',
|
||||
'src/Log.cxx',
|
||||
@ -352,7 +359,7 @@ sources = [
|
||||
'src/TagStream.cxx',
|
||||
'src/TagAny.cxx',
|
||||
'src/TimePrint.cxx',
|
||||
'src/mixer/Volume.cxx',
|
||||
'src/mixer/Memento.cxx',
|
||||
'src/PlaylistFile.cxx',
|
||||
]
|
||||
|
||||
@ -382,6 +389,7 @@ endif
|
||||
|
||||
if enable_database
|
||||
sources += [
|
||||
'src/storage/StorageState.cxx',
|
||||
'src/queue/PlaylistUpdate.cxx',
|
||||
'src/command/StorageCommands.cxx',
|
||||
'src/command/DatabaseCommands.cxx',
|
||||
|
@ -45,14 +45,27 @@ class AutotoolsProject(MakeProject):
|
||||
'LDFLAGS=' + toolchain.ldflags + ' ' + self.ldflags,
|
||||
'LIBS=' + toolchain.libs + ' ' + self.libs,
|
||||
'AR=' + toolchain.ar,
|
||||
'ARFLAGS=' + toolchain.arflags,
|
||||
'RANLIB=' + toolchain.ranlib,
|
||||
'STRIP=' + toolchain.strip,
|
||||
'--host=' + toolchain.arch,
|
||||
'--prefix=' + toolchain.install_prefix,
|
||||
'--enable-silent-rules',
|
||||
'--disable-silent-rules',
|
||||
] + self.configure_args
|
||||
|
||||
subprocess.check_call(configure, cwd=build, env=toolchain.env)
|
||||
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
|
||||
|
||||
return build
|
||||
|
||||
def _build(self, toolchain):
|
||||
|
@ -1,4 +1,5 @@
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
|
||||
from build.project import Project
|
||||
@ -25,13 +26,33 @@ set(CMAKE_SYSTEM_PROCESSOR {toolchain.actual_arch.split('-', 1)[0]})
|
||||
set(CMAKE_C_COMPILER_TARGET {toolchain.actual_arch})
|
||||
set(CMAKE_CXX_COMPILER_TARGET {toolchain.actual_arch})
|
||||
|
||||
set(CMAKE_C_FLAGS "{toolchain.cflags} {toolchain.cppflags}")
|
||||
set(CMAKE_CXX_FLAGS "{toolchain.cxxflags} {toolchain.cppflags}")
|
||||
set(CMAKE_C_FLAGS_INIT "{toolchain.cflags} {toolchain.cppflags}")
|
||||
set(CMAKE_CXX_FLAGS_INIT "{toolchain.cxxflags} {toolchain.cppflags}")
|
||||
""")
|
||||
__write_cmake_compiler(f, 'C', toolchain.cc)
|
||||
__write_cmake_compiler(f, 'CXX', toolchain.cxx)
|
||||
|
||||
def configure(toolchain, src, build, args=()):
|
||||
if cmake_system_name == 'Darwin':
|
||||
# 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, src, build, args=(), env=None):
|
||||
cross_args = []
|
||||
|
||||
if toolchain.is_windows:
|
||||
@ -61,15 +82,23 @@ def configure(toolchain, src, build, args=()):
|
||||
'-GNinja',
|
||||
] + cross_args + args
|
||||
|
||||
subprocess.check_call(configure, env=toolchain.env, cwd=build)
|
||||
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):
|
||||
def __init__(self, url, md5, installed, configure_args=[],
|
||||
windows_configure_args=[],
|
||||
env=None,
|
||||
**kwargs):
|
||||
Project.__init__(self, url, md5, installed, **kwargs)
|
||||
self.configure_args = configure_args
|
||||
self.windows_configure_args = windows_configure_args
|
||||
self.env = env
|
||||
|
||||
def configure(self, toolchain):
|
||||
src = self.unpack(toolchain)
|
||||
@ -77,10 +106,10 @@ class CmakeProject(Project):
|
||||
configure_args = self.configure_args
|
||||
if toolchain.is_windows:
|
||||
configure_args = configure_args + self.windows_configure_args
|
||||
configure(toolchain, src, build, configure_args)
|
||||
configure(toolchain, src, build, configure_args, self.env)
|
||||
return build
|
||||
|
||||
def _build(self, toolchain):
|
||||
build = self.configure(toolchain)
|
||||
subprocess.check_call(['ninja', 'install'],
|
||||
subprocess.check_call(['ninja', '-v', 'install'],
|
||||
cwd=build, env=toolchain.env)
|
||||
|
@ -43,20 +43,22 @@ opus = AutotoolsProject(
|
||||
)
|
||||
|
||||
flac = AutotoolsProject(
|
||||
'http://downloads.xiph.org/releases/flac/flac-1.3.4.tar.xz',
|
||||
'8ff0607e75a322dd7cd6ec48f4f225471404ae2730d0ea945127b1355155e737',
|
||||
'http://downloads.xiph.org/releases/flac/flac-1.4.2.tar.xz',
|
||||
'e322d58a1f48d23d9dd38f432672865f6f79e73a6f9cc5a5f57fcaa83eb5a8e4',
|
||||
'lib/libFLAC.a',
|
||||
[
|
||||
'--disable-shared', '--enable-static',
|
||||
'--disable-stack-smash-protection',
|
||||
'--disable-xmms-plugin', '--disable-cpplibs',
|
||||
'--disable-doxygen-docs',
|
||||
'--disable-programs',
|
||||
],
|
||||
subdirs=['include', 'src/libFLAC'],
|
||||
)
|
||||
|
||||
zlib = ZlibProject(
|
||||
'http://zlib.net/zlib-1.2.12.tar.xz',
|
||||
'7db46b8d7726232a621befaab4a1c870f00a90805511c0e0090441dac57def18',
|
||||
'http://zlib.net/zlib-1.2.13.tar.xz',
|
||||
'd14c38e313afc35a9a8760dadf26042f51ea0f5d154b0630a31da0540107fb98',
|
||||
'lib/libz.a',
|
||||
)
|
||||
|
||||
@ -112,30 +114,31 @@ libmodplug = AutotoolsProject(
|
||||
)
|
||||
|
||||
libopenmpt = AutotoolsProject(
|
||||
'https://lib.openmpt.org/files/libopenmpt/src/libopenmpt-0.5.12+release.autotools.tar.gz',
|
||||
'892aea7a599b5d21842bebf463b5aafdad5711be7008dd84401920c6234820af',
|
||||
'https://lib.openmpt.org/files/libopenmpt/src/libopenmpt-0.6.6+release.autotools.tar.gz',
|
||||
'6ddb9e26a430620944891796fefb1bbb38bd9148f6cfc558810c0d3f269876c7',
|
||||
'lib/libopenmpt.a',
|
||||
[
|
||||
'--disable-shared', '--enable-static',
|
||||
'--disable-openmpt123',
|
||||
'--disable-examples',
|
||||
'--disable-tests',
|
||||
'--disable-doxygen-doc',
|
||||
'--without-mpg123', '--without-ogg', '--without-vorbis', '--without-vorbisfile',
|
||||
'--without-portaudio', '--without-portaudiocpp', '--without-sndfile',
|
||||
'--without-flac',
|
||||
],
|
||||
base='libopenmpt-0.5.12+release.autotools',
|
||||
base='libopenmpt-0.6.6+release.autotools',
|
||||
)
|
||||
|
||||
wildmidi = CmakeProject(
|
||||
'https://codeload.github.com/Mindwerks/wildmidi/tar.gz/wildmidi-0.4.4',
|
||||
'6f267c8d331e9859906837e2c197093fddec31829d2ebf7b958cf6b7ae935430',
|
||||
'https://github.com/Mindwerks/wildmidi/releases/download/wildmidi-0.4.5/wildmidi-0.4.5.tar.gz',
|
||||
'd5e7bef00a7aa47534a53d43b1265f8d3d27f6a28e7f563c1cdf02ff4fa35b99',
|
||||
'lib/libWildMidi.a',
|
||||
[
|
||||
'-DBUILD_SHARED_LIBS=OFF',
|
||||
'-DWANT_PLAYER=OFF',
|
||||
'-DWANT_STATIC=ON',
|
||||
],
|
||||
base='wildmidi-wildmidi-0.4.4',
|
||||
name='wildmidi',
|
||||
version='0.4.4',
|
||||
)
|
||||
|
||||
gme = CmakeProject(
|
||||
@ -151,8 +154,8 @@ gme = CmakeProject(
|
||||
)
|
||||
|
||||
ffmpeg = FfmpegProject(
|
||||
'http://ffmpeg.org/releases/ffmpeg-5.0.1.tar.xz',
|
||||
'ef2efae259ce80a240de48ec85ecb062cecca26e4352ffb3fda562c21a93007b',
|
||||
'http://ffmpeg.org/releases/ffmpeg-6.0.tar.xz',
|
||||
'57be87c22d9b49c112b6d24bc67d42508660e6b718b3db89c44e47e289137082',
|
||||
'lib/libavcodec.a',
|
||||
[
|
||||
'--disable-shared', '--enable-static',
|
||||
@ -166,18 +169,20 @@ ffmpeg = FfmpegProject(
|
||||
'--disable-swscale',
|
||||
'--disable-postproc',
|
||||
'--disable-avfilter',
|
||||
'--disable-lzo',
|
||||
'--disable-faan',
|
||||
'--disable-pixelutils',
|
||||
'--disable-network',
|
||||
'--disable-encoders',
|
||||
'--disable-hwaccels',
|
||||
'--disable-muxers',
|
||||
'--disable-protocols',
|
||||
'--disable-devices',
|
||||
'--disable-filters',
|
||||
'--disable-v4l2_m2m',
|
||||
|
||||
'--disable-sdl2',
|
||||
'--disable-vulkan',
|
||||
'--disable-xlib',
|
||||
|
||||
'--disable-parser=bmp',
|
||||
'--disable-parser=cavsvideo',
|
||||
@ -191,17 +196,22 @@ ffmpeg = FfmpegProject(
|
||||
'--disable-parser=h263',
|
||||
'--disable-parser=h264',
|
||||
'--disable-parser=hevc',
|
||||
'--disable-parser=jpeg2000',
|
||||
'--disable-parser=mjpeg',
|
||||
'--disable-parser=mlp',
|
||||
'--disable-parser=mpeg4video',
|
||||
'--disable-parser=mpegvideo',
|
||||
'--disable-parser=opus',
|
||||
'--disable-parser=qoi',
|
||||
'--disable-parser=rv30',
|
||||
'--disable-parser=rv40',
|
||||
'--disable-parser=vc1',
|
||||
'--disable-parser=vp3',
|
||||
'--disable-parser=vp8',
|
||||
'--disable-parser=vp9',
|
||||
'--disable-parser=png',
|
||||
'--disable-parser=pnm',
|
||||
'--disable-parser=webp',
|
||||
'--disable-parser=xma',
|
||||
|
||||
'--disable-demuxer=aqtitle',
|
||||
@ -217,6 +227,42 @@ ffmpeg = FfmpegProject(
|
||||
'--disable-demuxer=h264',
|
||||
'--disable-demuxer=ico',
|
||||
'--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=lrc',
|
||||
'--disable-demuxer=microdvd',
|
||||
@ -239,6 +285,7 @@ ffmpeg = FfmpegProject(
|
||||
'--disable-demuxer=tedcaptions',
|
||||
'--disable-demuxer=vobsub',
|
||||
'--disable-demuxer=vplayer',
|
||||
'--disable-demuxer=webm_dash_manifest',
|
||||
'--disable-demuxer=webvtt',
|
||||
'--disable-demuxer=yuv4mpegpipe',
|
||||
|
||||
@ -268,78 +315,179 @@ ffmpeg = FfmpegProject(
|
||||
'--disable-decoder=qdmc',
|
||||
|
||||
# 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=asv1',
|
||||
'--disable-decoder=asv2',
|
||||
'--disable-decoder=apng',
|
||||
'--disable-decoder=aura',
|
||||
'--disable-decoder=aura2',
|
||||
'--disable-decoder=avrn',
|
||||
'--disable-decoder=avrp',
|
||||
'--disable-decoder=avui',
|
||||
'--disable-decoder=ayuv',
|
||||
'--disable-decoder=bethsoftvid',
|
||||
'--disable-decoder=bfi',
|
||||
'--disable-decoder=bink',
|
||||
'--disable-decoder=bintext',
|
||||
'--disable-decoder=bitpacked',
|
||||
'--disable-decoder=bmp',
|
||||
'--disable-decoder=bmv_video',
|
||||
'--disable-decoder=brender_pix',
|
||||
'--disable-decoder=c93',
|
||||
'--disable-decoder=cavs',
|
||||
'--disable-decoder=ccaption',
|
||||
'--disable-decoder=cdgraphics',
|
||||
'--disable-decoder=cdtoons',
|
||||
'--disable-decoder=cdxl',
|
||||
'--disable-decoder=cfhd',
|
||||
'--disable-decoder=cinepak',
|
||||
'--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=dnxhd',
|
||||
'--disable-decoder=dpx',
|
||||
'--disable-decoder=dsicinvideo',
|
||||
'--disable-decoder=dvbsub',
|
||||
'--disable-decoder=dvdsub',
|
||||
'--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=ffv1',
|
||||
'--disable-decoder=ffvhuff',
|
||||
'--disable-decoder=ffwavesynth',
|
||||
'--disable-decoder=fic',
|
||||
'--disable-decoder=fits',
|
||||
'--disable-decoder=flashsv',
|
||||
'--disable-decoder=flashsv2',
|
||||
'--disable-decoder=flic',
|
||||
'--disable-decoder=flv',
|
||||
'--disable-decoder=fmvc',
|
||||
'--disable-decoder=fraps',
|
||||
'--disable-decoder=fourxm',
|
||||
'--disable-decoder=frwu',
|
||||
'--disable-decoder=g2m',
|
||||
'--disable-decoder=gdv',
|
||||
'--disable-decoder=gem',
|
||||
'--disable-decoder=gif',
|
||||
'--disable-decoder=h261',
|
||||
'--disable-decoder=h263',
|
||||
'--disable-decoder=h263i',
|
||||
'--disable-decoder=h263p',
|
||||
'--disable-decoder=h264',
|
||||
'--disable-decoder=hap',
|
||||
'--disable-decoder=hevc',
|
||||
'--disable-decoder=hnm4_video',
|
||||
'--disable-decoder=hq_hqa',
|
||||
'--disable-decoder=hqx',
|
||||
'--disable-decoder=huffyuv',
|
||||
'--disable-decoder=hymt',
|
||||
'--disable-decoder=idcin',
|
||||
'--disable-decoder=idf',
|
||||
'--disable-decoder=iff_ilbm',
|
||||
'--disable-decoder=imm4',
|
||||
'--disable-decoder=indeo2',
|
||||
'--disable-decoder=indeo3',
|
||||
'--disable-decoder=indeo4',
|
||||
'--disable-decoder=indeo5',
|
||||
'--disable-decoder=interplay_video',
|
||||
'--disable-decoder=ipu',
|
||||
'--disable-decoder=jacosub',
|
||||
'--disable-decoder=jpeg2000',
|
||||
'--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=mimic',
|
||||
'--disable-decoder=mjpeg',
|
||||
'--disable-decoder=mmvideo',
|
||||
'--disable-decoder=mpl2',
|
||||
'--disable-decoder=mobiclip',
|
||||
'--disable-decoder=motionpixels',
|
||||
'--disable-decoder=movtext',
|
||||
'--disable-decoder=mpeg1video',
|
||||
'--disable-decoder=mpeg2video',
|
||||
'--disable-decoder=mpeg4',
|
||||
'--disable-decoder=mpegvideo',
|
||||
'--disable-decoder=msa1',
|
||||
'--disable-decoder=mscc',
|
||||
'--disable-decoder=msmpeg4_crystalhd',
|
||||
'--disable-decoder=msmpeg4v1',
|
||||
'--disable-decoder=msmpeg4v2',
|
||||
'--disable-decoder=msmpeg4v3',
|
||||
'--disable-decoder=msp2',
|
||||
'--disable-decoder=msrle',
|
||||
'--disable-decoder=mss1',
|
||||
'--disable-decoder=msvideo1',
|
||||
'--disable-decoder=mszh',
|
||||
'--disable-decoder=mts2',
|
||||
'--disable-decoder=mv30',
|
||||
'--disable-decoder=mvc1',
|
||||
'--disable-decoder=mvc2',
|
||||
'--disable-decoder=mvdv',
|
||||
'--disable-decoder=mvha',
|
||||
'--disable-decoder=mwsc',
|
||||
'--disable-decoder=notchlc',
|
||||
'--disable-decoder=nuv',
|
||||
'--disable-decoder=on2avc',
|
||||
'--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=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=qoi',
|
||||
'--disable-decoder=qpeg',
|
||||
'--disable-decoder=qtrle',
|
||||
'--disable-decoder=rawvideo',
|
||||
'--disable-decoder=r10k',
|
||||
'--disable-decoder=r210',
|
||||
'--disable-decoder=rasc',
|
||||
'--disable-decoder=realtext',
|
||||
'--disable-decoder=rl2',
|
||||
'--disable-decoder=rpza',
|
||||
'--disable-decoder=roq',
|
||||
'--disable-decoder=roq_dpcm',
|
||||
'--disable-decoder=rscc',
|
||||
@ -348,53 +496,120 @@ ffmpeg = FfmpegProject(
|
||||
'--disable-decoder=rv30',
|
||||
'--disable-decoder=rv40',
|
||||
'--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=simbiosis_imx',
|
||||
'--disable-decoder=smc',
|
||||
'--disable-decoder=snow',
|
||||
'--disable-decoder=speedhq',
|
||||
'--disable-decoder=srgc',
|
||||
'--disable-decoder=srt',
|
||||
'--disable-decoder=ssa',
|
||||
'--disable-decoder=stl',
|
||||
'--disable-decoder=subrip',
|
||||
'--disable-decoder=subviewer',
|
||||
'--disable-decoder=subviewer1',
|
||||
'--disable-decoder=sunrast',
|
||||
'--disable-decoder=svq1',
|
||||
'--disable-decoder=svq3',
|
||||
'--disable-decoder=targa',
|
||||
'--disable-decoder=targa_y216',
|
||||
'--disable-decoder=text',
|
||||
'--disable-decoder=tiff',
|
||||
'--disable-decoder=tiertexseqvideo',
|
||||
'--disable-decoder=tmv',
|
||||
'--disable-decoder=truemotion1',
|
||||
'--disable-decoder=truemotion2',
|
||||
'--disable-decoder=truemotion2rt',
|
||||
'--disable-decoder=tscc',
|
||||
'--disable-decoder=tscc2',
|
||||
'--disable-decoder=twinvq',
|
||||
'--disable-decoder=txd',
|
||||
'--disable-decoder=ulti',
|
||||
'--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=vcr1',
|
||||
'--disable-decoder=vmdvideo',
|
||||
'--disable-decoder=vmnc',
|
||||
'--disable-decoder=vp3',
|
||||
'--disable-decoder=vp5',
|
||||
'--disable-decoder=vp6',
|
||||
'--disable-decoder=vp7',
|
||||
'--disable-decoder=vp8',
|
||||
'--disable-decoder=vp9',
|
||||
'--disable-decoder=vplayer',
|
||||
'--disable-decoder=vqa',
|
||||
'--disable-decoder=webvtt',
|
||||
'--disable-decoder=wcmv',
|
||||
'--disable-decoder=wmv1',
|
||||
'--disable-decoder=wmv2',
|
||||
'--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=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(
|
||||
'https://www.openssl.org/source/openssl-3.0.5.tar.gz',
|
||||
'aa7d8d9bef71ad6525c55ba11e5f4397889ce49c2c9349dcea6d3e4f0b024a7a',
|
||||
'https://www.openssl.org/source/openssl-3.1.0.tar.gz',
|
||||
'aaa925ad9828745c4cad9d9efeb273deca820f2cdcf2c3ac7d7c1212b7c497b4',
|
||||
'include/openssl/ossl_typ.h',
|
||||
)
|
||||
|
||||
curl = CmakeProject(
|
||||
'https://curl.se/download/curl-7.84.0.tar.xz',
|
||||
'2d118b43f547bfe5bae806d8d47b4e596ea5b25a6c1f080aef49fbcd817c5db8',
|
||||
'https://curl.se/download/curl-8.0.1.tar.xz',
|
||||
'0a381cd82f4d00a9a334438b8ca239afea5bfefcfa9a1025f2bf118e79e0b5f0',
|
||||
'lib/libcurl.a',
|
||||
[
|
||||
'-DBUILD_CURL_EXE=OFF',
|
||||
'-DBUILD_SHARED_LIBS=OFF',
|
||||
'-DCURL_DISABLE_VERBOSE_STRINGS=ON',
|
||||
'-DCURL_DISABLE_LDAP=ON',
|
||||
'-DCURL_DISABLE_TELNET=ON',
|
||||
'-DCURL_DISABLE_DICT=ON',
|
||||
@ -423,8 +638,8 @@ curl = CmakeProject(
|
||||
)
|
||||
|
||||
libnfs = AutotoolsProject(
|
||||
'https://github.com/sahlberg/libnfs/archive/libnfs-5.0.1.tar.gz',
|
||||
'7ef445410b42f36b9bad426608b53ccb9ccca4101e545c383f564c11db672ca8',
|
||||
'https://github.com/sahlberg/libnfs/archive/libnfs-5.0.2.tar.gz',
|
||||
'637e56643b19da9fba98f06847788c4dad308b723156a64748041035dcdf9bd3',
|
||||
'lib/libnfs.a',
|
||||
[
|
||||
'--disable-shared', '--enable-static',
|
||||
@ -435,7 +650,7 @@ libnfs = AutotoolsProject(
|
||||
|
||||
'--disable-utils', '--disable-examples',
|
||||
],
|
||||
base='libnfs-libnfs-5.0.1',
|
||||
base='libnfs-libnfs-5.0.2',
|
||||
autoreconf=True,
|
||||
)
|
||||
|
||||
@ -446,7 +661,7 @@ jack = JackProject(
|
||||
)
|
||||
|
||||
boost = BoostProject(
|
||||
'https://boostorg.jfrog.io/artifactory/main/release/1.79.0/source/boost_1_79_0.tar.bz2',
|
||||
'475d589d51a7f8b3ba2ba4eda022b170e562ca3b760ee922c146b6c65856ef39',
|
||||
'https://boostorg.jfrog.io/artifactory/main/release/1.81.0/source/boost_1_81_0.tar.bz2',
|
||||
'71feeed900fbccca04a3b4f2f84a7c217186f28a940ed8b7ed4725986baf99fa',
|
||||
'include/boost/version.hpp',
|
||||
)
|
||||
|
@ -1,4 +1,4 @@
|
||||
import subprocess
|
||||
import subprocess, multiprocessing
|
||||
|
||||
from build.project import Project
|
||||
|
||||
@ -10,7 +10,12 @@ class MakeProject(Project):
|
||||
self.install_target = install_target
|
||||
|
||||
def get_simultaneous_jobs(self):
|
||||
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):
|
||||
return ['--quiet', '-j' + str(self.get_simultaneous_jobs())]
|
||||
@ -19,7 +24,7 @@ class MakeProject(Project):
|
||||
return ['--quiet', self.install_target]
|
||||
|
||||
def make(self, toolchain, wd, args):
|
||||
subprocess.check_call(['/usr/bin/make'] + args,
|
||||
subprocess.check_call(['make'] + args,
|
||||
cwd=wd, env=toolchain.env)
|
||||
|
||||
def build_make(self, toolchain, wd, install=True):
|
||||
|
@ -1,4 +1,5 @@
|
||||
import os.path, subprocess, sys
|
||||
import os
|
||||
import subprocess
|
||||
import platform
|
||||
|
||||
from build.project import Project
|
||||
@ -82,8 +83,8 @@ endian = '{endian}'
|
||||
def configure(toolchain, src, build, args=()):
|
||||
cross_file = make_cross_file(toolchain)
|
||||
configure = [
|
||||
'meson',
|
||||
src, build,
|
||||
'meson', 'setup',
|
||||
build, src,
|
||||
|
||||
'--prefix', toolchain.install_prefix,
|
||||
|
||||
@ -115,5 +116,5 @@ class MesonProject(Project):
|
||||
|
||||
def _build(self, toolchain):
|
||||
build = self.configure(toolchain)
|
||||
subprocess.check_call(['ninja', 'install'],
|
||||
subprocess.check_call(['ninja', '-v', 'install'],
|
||||
cwd=build, env=toolchain.env)
|
||||
|
@ -14,13 +14,14 @@ class Project:
|
||||
if base is None:
|
||||
basename = os.path.basename(url)
|
||||
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)
|
||||
else:
|
||||
self.base = base
|
||||
|
||||
if name is None or version is None:
|
||||
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 version is None: version = m.group(2)
|
||||
|
||||
@ -55,8 +56,8 @@ class Project:
|
||||
parent_path = toolchain.src_path
|
||||
else:
|
||||
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:
|
||||
push_all(toolchain, path, self.patches)
|
||||
|
||||
@ -71,8 +72,10 @@ class Project:
|
||||
|
||||
return path
|
||||
|
||||
def make_build_path(self, toolchain):
|
||||
def make_build_path(self, toolchain, lazy=False):
|
||||
path = os.path.join(toolchain.build_path, self.base)
|
||||
if lazy and os.path.isdir(path):
|
||||
return path
|
||||
try:
|
||||
shutil.rmtree(path)
|
||||
except FileNotFoundError:
|
||||
|
@ -1,14 +1,16 @@
|
||||
import os, shutil, subprocess
|
||||
|
||||
def untar(tarball_path, parent_path, base):
|
||||
def untar(tarball_path, parent_path, base, lazy=False):
|
||||
path = os.path.join(parent_path, base)
|
||||
if lazy and os.path.isdir(path):
|
||||
return path
|
||||
try:
|
||||
shutil.rmtree(path)
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
os.makedirs(parent_path, exist_ok=True)
|
||||
try:
|
||||
subprocess.check_call(['/bin/tar', 'xfC', tarball_path, parent_path])
|
||||
subprocess.check_call(['tar', 'xfC', tarball_path, parent_path])
|
||||
except FileNotFoundError:
|
||||
import tarfile
|
||||
tar = tarfile.open(tarball_path)
|
||||
|
@ -1,22 +1,32 @@
|
||||
import os.path, subprocess
|
||||
import subprocess
|
||||
|
||||
from build.project import Project
|
||||
from build.makeproject import MakeProject
|
||||
|
||||
class ZlibProject(Project):
|
||||
class ZlibProject(MakeProject):
|
||||
def __init__(self, url, md5, installed,
|
||||
**kwargs):
|
||||
Project.__init__(self, url, md5, installed, **kwargs)
|
||||
MakeProject.__init__(self, url, md5, installed, **kwargs)
|
||||
|
||||
def get_make_args(self, toolchain):
|
||||
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):
|
||||
return [
|
||||
'RANLIB=' + toolchain.ranlib,
|
||||
self.install_target
|
||||
]
|
||||
|
||||
def _build(self, toolchain):
|
||||
src = self.unpack(toolchain, out_of_tree=False)
|
||||
|
||||
subprocess.check_call(['/usr/bin/make', '--quiet',
|
||||
'-f', 'win32/Makefile.gcc',
|
||||
'PREFIX=' + toolchain.arch + '-',
|
||||
'-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)
|
||||
subprocess.check_call(['./configure', '--prefix=' + toolchain.install_prefix, '--static'],
|
||||
cwd=src, env=toolchain.env)
|
||||
self.build_make(toolchain, src)
|
||||
|
@ -352,12 +352,16 @@ ParseCommandLine(int argc, char **argv, CommandLineOptions &options,
|
||||
break;
|
||||
|
||||
case OPTION_NO_DAEMON:
|
||||
#ifdef ENABLE_DAEMON
|
||||
options.daemon = false;
|
||||
#endif
|
||||
break;
|
||||
|
||||
#ifdef __linux__
|
||||
case OPTION_SYSTEMD:
|
||||
#ifdef ENABLE_DAEMON
|
||||
options.daemon = false;
|
||||
#endif
|
||||
options.systemd = true;
|
||||
break;
|
||||
#endif
|
||||
|
@ -20,11 +20,18 @@
|
||||
#ifndef MPD_COMMAND_LINE_HXX
|
||||
#define MPD_COMMAND_LINE_HXX
|
||||
|
||||
#include "config.h" // for ENABLE_DAEMON
|
||||
|
||||
struct ConfigData;
|
||||
|
||||
struct CommandLineOptions {
|
||||
bool kill = false;
|
||||
|
||||
#ifdef ENABLE_DAEMON
|
||||
bool daemon = true;
|
||||
#else
|
||||
static constexpr bool daemon = false;
|
||||
#endif
|
||||
|
||||
#ifdef __linux__
|
||||
bool systemd = false;
|
||||
|
@ -158,12 +158,15 @@ log_init(const ConfigData &config, bool verbose, bool use_stdout)
|
||||
getenv("NOTIFY_SOCKET") != nullptr) {
|
||||
/* if MPD was started as a systemd
|
||||
service, default to journal (which
|
||||
is connected to fd=2) */
|
||||
is connected to stdout&stderr) */
|
||||
out_fd = STDOUT_FILENO;
|
||||
return;
|
||||
}
|
||||
#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");
|
||||
#endif
|
||||
#ifdef HAVE_SYSLOG
|
||||
|
53
src/Main.cxx
53
src/Main.cxx
@ -482,7 +482,10 @@ MainConfigured(const CommandLineOptions &options,
|
||||
#ifndef ANDROID
|
||||
setup_log_output();
|
||||
|
||||
const ScopeSignalHandlersInit signal_handlers_init(instance);
|
||||
const ScopeSignalHandlersInit signal_handlers_init{
|
||||
instance,
|
||||
options.daemon,
|
||||
};
|
||||
#endif
|
||||
|
||||
instance.io_thread.Start();
|
||||
@ -590,19 +593,46 @@ MainConfigured(const CommandLineOptions &options,
|
||||
|
||||
#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
|
||||
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;
|
||||
ConfigData raw_config;
|
||||
|
||||
const auto sdcard = Environment::getExternalStorageDirectory();
|
||||
if (!sdcard.IsNull()) {
|
||||
const auto config_path =
|
||||
sdcard / Path::FromFS("mpd.conf");
|
||||
if (FileExists(config_path))
|
||||
ReadConfigFile(raw_config, config_path);
|
||||
}
|
||||
LoadConfigFile(env, 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::Object::Initialise(env);
|
||||
Java::File::Initialise(env);
|
||||
|
||||
Environment::Initialise(env);
|
||||
AtScopeExit(env) { Environment::Deinitialise(env); };
|
||||
|
||||
Context::Initialise(env);
|
||||
|
||||
context = new Context(env, _context);
|
||||
AtScopeExit() { delete context; };
|
||||
|
||||
@ -625,7 +658,7 @@ Java_org_musicpd_Bridge_run(JNIEnv *env, jclass, jobject _context, jobject _logL
|
||||
AtScopeExit() { delete logListener; };
|
||||
|
||||
try {
|
||||
AndroidMain();
|
||||
AndroidMain(env);
|
||||
} catch (...) {
|
||||
LogError(std::current_exception());
|
||||
}
|
||||
|
@ -23,7 +23,6 @@
|
||||
#include "Log.hxx"
|
||||
#include "lib/fmt/ExceptionFormatter.hxx"
|
||||
#include "song/DetachedSong.hxx"
|
||||
#include "mixer/Volume.hxx"
|
||||
#include "IdleFlags.hxx"
|
||||
#include "client/Listener.hxx"
|
||||
#include "client/Client.hxx"
|
||||
@ -206,7 +205,7 @@ Partition::OnBorderPause() noexcept
|
||||
void
|
||||
Partition::OnMixerVolumeChanged(Mixer &, int) noexcept
|
||||
{
|
||||
InvalidateHardwareVolume();
|
||||
mixer_memento.InvalidateHardwareVolume();
|
||||
|
||||
/* notify clients */
|
||||
EmitIdle(IDLE_MIXER);
|
||||
|
@ -25,6 +25,7 @@
|
||||
#include "queue/Listener.hxx"
|
||||
#include "output/MultipleOutputs.hxx"
|
||||
#include "mixer/Listener.hxx"
|
||||
#include "mixer/Memento.hxx"
|
||||
#include "player/Control.hxx"
|
||||
#include "player/Listener.hxx"
|
||||
#include "protocol/RangeArg.hxx"
|
||||
@ -76,6 +77,8 @@ struct Partition final : QueueListener, PlayerListener, MixerListener {
|
||||
|
||||
MultipleOutputs outputs;
|
||||
|
||||
MixerMemento mixer_memento;
|
||||
|
||||
PlayerControl pc;
|
||||
|
||||
ReplayGainMode replay_gain_mode = ReplayGainMode::OFF;
|
||||
|
@ -81,6 +81,9 @@ spl_valid_name(const char *name_utf8)
|
||||
*/
|
||||
|
||||
return std::strchr(name_utf8, '/') == nullptr &&
|
||||
#ifdef _WIN32
|
||||
std::strchr(name_utf8, '\\') == nullptr &&
|
||||
#endif
|
||||
std::strchr(name_utf8, '\n') == nullptr &&
|
||||
std::strchr(name_utf8, '\r') == nullptr;
|
||||
}
|
||||
|
@ -28,7 +28,11 @@
|
||||
#include <boost/intrusive/list.hpp>
|
||||
#include <boost/intrusive/unordered_set.hpp>
|
||||
|
||||
#include <array>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
class RemoteTagCacheHandler;
|
||||
|
||||
|
@ -24,6 +24,7 @@
|
||||
#include "TagPrint.hxx"
|
||||
#include "client/Response.hxx"
|
||||
#include "fs/Traits.hxx"
|
||||
#include "lib/fmt/AudioFormatFormatter.hxx"
|
||||
#include "time/ChronoUtil.hxx"
|
||||
#include "util/StringBuffer.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);
|
||||
|
||||
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);
|
||||
|
||||
@ -116,7 +117,7 @@ song_print_info(Response &r, const DetachedSong &song, bool base) noexcept
|
||||
time_print(r, "Last-Modified", song.GetLastModified());
|
||||
|
||||
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());
|
||||
|
||||
|
@ -63,6 +63,9 @@ song_save(BufferedOutputStream &os, const Song &song)
|
||||
if (song.audio_format.IsDefined())
|
||||
os.Format("Format: %s\n", ToString(song.audio_format).c_str());
|
||||
|
||||
if (song.in_playlist)
|
||||
os.Write("InPlaylist: yes\n");
|
||||
|
||||
if (!IsNegative(song.mtime))
|
||||
os.Format(SONG_MTIME ": %li\n",
|
||||
(long)std::chrono::system_clock::to_time_t(song.mtime));
|
||||
@ -86,7 +89,7 @@ song_save(BufferedOutputStream &os, const DetachedSong &song)
|
||||
|
||||
DetachedSong
|
||||
song_load(LineReader &file, const char *uri,
|
||||
std::string *target_r)
|
||||
std::string *target_r, bool *in_playlist_r)
|
||||
{
|
||||
DetachedSong song(uri);
|
||||
|
||||
@ -132,6 +135,9 @@ song_load(LineReader &file, const char *uri,
|
||||
|
||||
song.SetStartTime(SongTime::FromMS(start_ms));
|
||||
song.SetEndTime(SongTime::FromMS(end_ms));
|
||||
} else if (StringIsEqual(line, "InPlaylist")) {
|
||||
if (in_playlist_r != nullptr)
|
||||
*in_playlist_r = StringIsEqual(value, "yes");
|
||||
} else {
|
||||
throw FormatRuntimeError("unknown line in db: %s", line);
|
||||
}
|
||||
|
@ -44,6 +44,6 @@ song_save(BufferedOutputStream &os, const DetachedSong &song);
|
||||
*/
|
||||
DetachedSong
|
||||
song_load(LineReader &file, const char *uri,
|
||||
std::string *target_r=nullptr);
|
||||
std::string *target_r=nullptr, bool *in_playlist_r=nullptr);
|
||||
|
||||
#endif
|
||||
|
@ -27,7 +27,6 @@
|
||||
#include "storage/StorageState.hxx"
|
||||
#include "Partition.hxx"
|
||||
#include "Instance.hxx"
|
||||
#include "mixer/Volume.hxx"
|
||||
#include "SongLoader.hxx"
|
||||
#include "util/Domain.hxx"
|
||||
#include "Log.hxx"
|
||||
@ -47,7 +46,7 @@ StateFile::StateFile(StateFileConfig &&_config,
|
||||
void
|
||||
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_playlist_version = playlist_state_get_hash(partition.playlist,
|
||||
partition.pc);
|
||||
@ -59,7 +58,7 @@ StateFile::RememberVersions() noexcept
|
||||
bool
|
||||
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_playlist_version != playlist_state_get_hash(partition.playlist,
|
||||
partition.pc)
|
||||
@ -72,7 +71,7 @@ StateFile::IsModified() const noexcept
|
||||
inline void
|
||||
StateFile::Write(BufferedOutputStream &os)
|
||||
{
|
||||
save_sw_volume_state(os);
|
||||
partition.mixer_memento.SaveSoftwareVolumeState(os);
|
||||
audio_output_state_save(os, partition.outputs);
|
||||
|
||||
#ifdef ENABLE_DATABASE
|
||||
@ -125,7 +124,7 @@ try {
|
||||
|
||||
const char *line;
|
||||
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) ||
|
||||
playlist_state_restore(config, line, file, song_loader,
|
||||
partition.playlist,
|
||||
|
@ -32,7 +32,7 @@ StateFileConfig::StateFileConfig(const ConfigData &config)
|
||||
{
|
||||
#ifdef ANDROID
|
||||
if (path.IsNull()) {
|
||||
const auto cache_dir = GetUserCacheDir();
|
||||
const auto cache_dir = GetAppCacheDir();
|
||||
if (cache_dir.IsNull())
|
||||
return;
|
||||
|
||||
|
@ -35,8 +35,9 @@ tag_print_types(Response &r) noexcept
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -36,5 +36,5 @@ time_print(Response &r, const char *name,
|
||||
return;
|
||||
}
|
||||
|
||||
r.Fmt(FMT_STRING("{}: {}\n"), name, s);
|
||||
r.Fmt(FMT_STRING("{}: {}\n"), name, s.c_str());
|
||||
}
|
||||
|
@ -26,19 +26,30 @@
|
||||
|
||||
#include "AudioManager.hxx"
|
||||
|
||||
AllocatedPath
|
||||
Context::GetExternalFilesDir(JNIEnv *env, const char *_type) noexcept
|
||||
static jmethodID getExternalFilesDir_method,
|
||||
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())};
|
||||
jmethodID method = env->GetMethodID(cls, "getExternalFilesDir",
|
||||
"(Ljava/lang/String;)Ljava/io/File;");
|
||||
assert(method);
|
||||
getExternalFilesDir_method = env->GetMethodID(cls, "getExternalFilesDir",
|
||||
"(Ljava/lang/String;)Ljava/io/File;");
|
||||
getCacheDir_method = env->GetMethodID(cls, "getCacheDir",
|
||||
"()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)
|
||||
return nullptr;
|
||||
|
||||
@ -50,12 +61,7 @@ Context::GetCacheDir(JNIEnv *env) const noexcept
|
||||
{
|
||||
assert(env != nullptr);
|
||||
|
||||
Java::Class cls(env, env->GetObjectClass(Get()));
|
||||
jmethodID method = env->GetMethodID(cls, "getCacheDir",
|
||||
"()Ljava/io/File;");
|
||||
assert(method);
|
||||
|
||||
jobject file = env->CallObjectMethod(Get(), method);
|
||||
jobject file = env->CallObjectMethod(Get(), getCacheDir_method);
|
||||
if (Java::DiscardException(env) || file == nullptr)
|
||||
return nullptr;
|
||||
|
||||
@ -67,13 +73,8 @@ Context::GetAudioManager(JNIEnv *env) noexcept
|
||||
{
|
||||
assert(env != nullptr);
|
||||
|
||||
Java::Class cls(env, env->GetObjectClass(Get()));
|
||||
jmethodID method = env->GetMethodID(cls, "getSystemService",
|
||||
"(Ljava/lang/String;)Ljava/lang/Object;");
|
||||
assert(method);
|
||||
|
||||
Java::String name(env, "audio");
|
||||
jobject am = env->CallObjectMethod(Get(), method, name.Get());
|
||||
jobject am = env->CallObjectMethod(Get(), getSystemService_method, name.Get());
|
||||
if (Java::DiscardException(env) || am == nullptr)
|
||||
return nullptr;
|
||||
|
||||
|
@ -27,12 +27,21 @@ class AudioManager;
|
||||
|
||||
class Context : public Java::GlobalObject {
|
||||
public:
|
||||
/**
|
||||
* Global initialisation. Looks up the methods of the
|
||||
* Context Java class.
|
||||
*/
|
||||
static void Initialise(JNIEnv *env) noexcept;
|
||||
|
||||
Context(JNIEnv *env, jobject obj) noexcept
|
||||
:Java::GlobalObject(env, obj) {}
|
||||
|
||||
/**
|
||||
* @param type the subdirectory name; may be nullptr
|
||||
*/
|
||||
[[gnu::pure]]
|
||||
AllocatedPath GetExternalFilesDir(JNIEnv *env,
|
||||
const char *type) noexcept;
|
||||
const char *type=nullptr) noexcept;
|
||||
|
||||
[[gnu::pure]]
|
||||
AllocatedPath GetCacheDir(JNIEnv *env) const noexcept;
|
||||
|
@ -25,13 +25,13 @@
|
||||
#include "fs/AllocatedPath.hxx"
|
||||
|
||||
namespace Environment {
|
||||
static Java::TrivialClass cls;
|
||||
static jmethodID getExternalStorageDirectory_method;
|
||||
static jmethodID getExternalStoragePublicDirectory_method;
|
||||
}
|
||||
|
||||
static Java::TrivialClass cls;
|
||||
static jmethodID getExternalStorageDirectory_method;
|
||||
static jmethodID getExternalStoragePublicDirectory_method;
|
||||
|
||||
void
|
||||
Environment::Initialise(JNIEnv *env) noexcept
|
||||
Initialise(JNIEnv *env) noexcept
|
||||
{
|
||||
cls.Find(env, "android/os/Environment");
|
||||
|
||||
@ -45,16 +45,14 @@ Environment::Initialise(JNIEnv *env) noexcept
|
||||
}
|
||||
|
||||
void
|
||||
Environment::Deinitialise(JNIEnv *env) noexcept
|
||||
Deinitialise(JNIEnv *env) noexcept
|
||||
{
|
||||
cls.Clear(env);
|
||||
}
|
||||
|
||||
AllocatedPath
|
||||
Environment::getExternalStorageDirectory() noexcept
|
||||
getExternalStorageDirectory(JNIEnv *env) noexcept
|
||||
{
|
||||
JNIEnv *env = Java::GetEnv();
|
||||
|
||||
jobject file =
|
||||
env->CallStaticObjectMethod(cls,
|
||||
getExternalStorageDirectory_method);
|
||||
@ -65,20 +63,20 @@ Environment::getExternalStorageDirectory() noexcept
|
||||
}
|
||||
|
||||
AllocatedPath
|
||||
Environment::getExternalStoragePublicDirectory(const char *type) noexcept
|
||||
getExternalStoragePublicDirectory(JNIEnv *env, const char *type) noexcept
|
||||
{
|
||||
if (getExternalStoragePublicDirectory_method == nullptr)
|
||||
/* needs API level 8 */
|
||||
return nullptr;
|
||||
|
||||
JNIEnv *env = Java::GetEnv();
|
||||
|
||||
Java::String type2(env, type);
|
||||
jobject file = env->CallStaticObjectMethod(Environment::cls,
|
||||
Environment::getExternalStoragePublicDirectory_method,
|
||||
jobject file = env->CallStaticObjectMethod(cls,
|
||||
getExternalStoragePublicDirectory_method,
|
||||
type2.Get());
|
||||
if (file == nullptr)
|
||||
return nullptr;
|
||||
|
||||
return Java::File::ToAbsolutePath(env, file);
|
||||
}
|
||||
|
||||
} // namespace Environment
|
||||
|
@ -17,27 +17,29 @@
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#ifndef MPD_ANDROID_ENVIRONMENT_HXX
|
||||
#define MPD_ANDROID_ENVIRONMENT_HXX
|
||||
|
||||
#include "util/Compiler.h"
|
||||
#pragma once
|
||||
|
||||
#include <jni.h>
|
||||
|
||||
class AllocatedPath;
|
||||
|
||||
namespace Environment {
|
||||
void Initialise(JNIEnv *env) noexcept;
|
||||
void Deinitialise(JNIEnv *env) noexcept;
|
||||
|
||||
/**
|
||||
* Determine the mount point of the external SD card.
|
||||
*/
|
||||
[[gnu::pure]]
|
||||
AllocatedPath getExternalStorageDirectory() noexcept;
|
||||
void
|
||||
Initialise(JNIEnv *env) noexcept;
|
||||
|
||||
[[gnu::pure]]
|
||||
AllocatedPath getExternalStoragePublicDirectory(const char *type) noexcept;
|
||||
}
|
||||
void
|
||||
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(position <= fill);
|
||||
|
||||
return {&data[position], &data[fill]};
|
||||
return {data.data() + position, data.data() + fill};
|
||||
}
|
||||
|
||||
void Consume(size_t nbytes) noexcept {
|
||||
|
@ -22,6 +22,10 @@ if libzzip_dep.found()
|
||||
found_archive_plugin = true
|
||||
endif
|
||||
|
||||
if not found_archive_plugin
|
||||
subdir_done()
|
||||
endif
|
||||
|
||||
archive_plugins = static_library(
|
||||
'archive_plugins',
|
||||
archive_plugins_sources,
|
||||
|
@ -41,6 +41,8 @@
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include <limits.h> // for UINT_MAX
|
||||
|
||||
CommandResult
|
||||
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;
|
||||
}
|
||||
|
||||
#if defined(_WIN32) && GCC_CHECK_VERSION(4,6)
|
||||
#pragma GCC diagnostic pop
|
||||
#endif
|
||||
|
||||
gcc_pure
|
||||
static bool
|
||||
IsValidName(const StringView s) noexcept
|
||||
@ -130,7 +126,8 @@ public:
|
||||
explicit PrintCommentHandler(Response &_response) noexcept
|
||||
: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))
|
||||
response.Fmt(FMT_STRING("{}: {}\n"), key, value);
|
||||
}
|
||||
|
@ -33,7 +33,6 @@
|
||||
#include "TimePrint.hxx"
|
||||
#include "decoder/DecoderPrint.hxx"
|
||||
#include "ls.hxx"
|
||||
#include "mixer/Volume.hxx"
|
||||
#include "time/ChronoUtil.hxx"
|
||||
#include "util/UriUtil.hxx"
|
||||
#include "util/StringAPI.hxx"
|
||||
@ -325,7 +324,7 @@ handle_getvol(Client &client, Request, Response &r)
|
||||
{
|
||||
auto &partition = client.GetPartition();
|
||||
|
||||
const auto volume = volume_level_get(partition.outputs);
|
||||
const auto volume = partition.mixer_memento.GetVolume(partition.outputs);
|
||||
if (volume >= 0)
|
||||
r.Fmt(FMT_STRING("volume: {}\n"), volume);
|
||||
|
||||
@ -337,7 +336,9 @@ handle_setvol(Client &client, Request args, Response &)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
@ -346,9 +347,11 @@ handle_volume(Client &client, Request args, Response &r)
|
||||
{
|
||||
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) {
|
||||
r.Error(ACK_ERROR_SYSTEM, "No mixer");
|
||||
return CommandResult::ERROR;
|
||||
@ -360,8 +363,10 @@ handle_volume(Client &client, Request args, Response &r)
|
||||
else if (new_volume > 100)
|
||||
new_volume = 100;
|
||||
|
||||
if (new_volume != old_volume)
|
||||
volume_level_change(outputs, new_volume);
|
||||
if (new_volume != old_volume) {
|
||||
mixer_memento.SetVolume(outputs, new_volume);
|
||||
partition.EmitIdle(IDLE_MIXER);
|
||||
}
|
||||
|
||||
return CommandResult::OK;
|
||||
}
|
||||
|
@ -33,7 +33,11 @@ handle_enableoutput(Client &client, Request args, Response &r)
|
||||
assert(args.size == 1);
|
||||
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");
|
||||
return CommandResult::ERROR;
|
||||
}
|
||||
@ -47,7 +51,11 @@ handle_disableoutput(Client &client, Request args, Response &r)
|
||||
assert(args.size == 1);
|
||||
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");
|
||||
return CommandResult::ERROR;
|
||||
}
|
||||
@ -61,7 +69,11 @@ handle_toggleoutput(Client &client, Request args, Response &r)
|
||||
assert(args.size == 1);
|
||||
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");
|
||||
return CommandResult::ERROR;
|
||||
}
|
||||
|
@ -25,10 +25,10 @@
|
||||
#include "SingleMode.hxx"
|
||||
#include "client/Client.hxx"
|
||||
#include "client/Response.hxx"
|
||||
#include "mixer/Volume.hxx"
|
||||
#include "Partition.hxx"
|
||||
#include "Instance.hxx"
|
||||
#include "IdleFlags.hxx"
|
||||
#include "lib/fmt/AudioFormatFormatter.hxx"
|
||||
#include "util/StringBuffer.hxx"
|
||||
#include "util/ScopeExit.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 volume = volume_level_get(partition.outputs);
|
||||
const auto volume = partition.mixer_memento.GetVolume(partition.outputs);
|
||||
if (volume >= 0)
|
||||
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())
|
||||
r.Fmt(FMT_STRING(COMMAND_STATUS_AUDIO ": {}\n"),
|
||||
ToString(player_status.audio_format));
|
||||
player_status.audio_format);
|
||||
}
|
||||
|
||||
#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
|
||||
handle_listfiles_storage(Response &r, Storage &storage, const char *uri)
|
||||
{
|
||||
|
@ -24,6 +24,7 @@
|
||||
#include "config/Param.hxx"
|
||||
#include "config/Block.hxx"
|
||||
#include "fs/AllocatedPath.hxx"
|
||||
#include "fs/FileSystem.hxx"
|
||||
#include "fs/StandardDirectory.hxx"
|
||||
#include "util/RuntimeError.hxx"
|
||||
|
||||
@ -51,17 +52,30 @@ CreateConfiguredDatabase(const ConfigData &config,
|
||||
} else {
|
||||
/* if there is no override, use the cache directory */
|
||||
|
||||
const AllocatedPath cache_dir = GetUserCacheDir();
|
||||
const AllocatedPath cache_dir = GetAppCacheDir();
|
||||
if (cache_dir.IsNull())
|
||||
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();
|
||||
if (db_file_utf8.empty())
|
||||
return nullptr;
|
||||
|
||||
ConfigBlock block;
|
||||
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,
|
||||
listener, block);
|
||||
}
|
||||
|
@ -126,6 +126,18 @@ Directory::LookupTargetSong(std::string_view _target) noexcept
|
||||
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
|
||||
Directory::PruneEmpty() noexcept
|
||||
{
|
||||
|
@ -287,6 +287,14 @@ public:
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
|
@ -168,12 +168,14 @@ directory_load(LineReader &file, Directory &directory)
|
||||
throw FormatRuntimeError("Duplicate song '%s'", name);
|
||||
|
||||
std::string target;
|
||||
bool in_playlist = false;
|
||||
auto detached_song = song_load(file, name,
|
||||
&target);
|
||||
&target, &in_playlist);
|
||||
|
||||
auto song = std::make_unique<Song>(std::move(detached_song),
|
||||
directory);
|
||||
song->target = std::move(target);
|
||||
song->in_playlist = in_playlist;
|
||||
|
||||
directory.AddSong(std::move(song));
|
||||
} else if ((p = StringAfterPrefix(line, PLAYLIST_META_BEGIN))) {
|
||||
|
@ -51,6 +51,15 @@ LockFindSong(Directory &directory, std::string_view name) noexcept
|
||||
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
|
||||
UpdateWalk::UpdateArchiveTree(ArchiveFile &archive, Directory &directory,
|
||||
const char *name) noexcept
|
||||
@ -58,6 +67,9 @@ UpdateWalk::UpdateArchiveTree(ArchiveFile &archive, Directory &directory,
|
||||
const char *tmp = std::strchr(name, '/');
|
||||
if (tmp) {
|
||||
const std::string_view child_name(name, tmp - name);
|
||||
if (!IsAcceptableFilename(child_name))
|
||||
return;
|
||||
|
||||
//add dir is not there already
|
||||
Directory *subdir = LockMakeChild(directory, child_name);
|
||||
subdir->device = DEVICE_INARCHIVE;
|
||||
@ -65,11 +77,8 @@ UpdateWalk::UpdateArchiveTree(ArchiveFile &archive, Directory &directory,
|
||||
//create directories first
|
||||
UpdateArchiveTree(archive, *subdir, tmp + 1);
|
||||
} else {
|
||||
if (StringIsEmpty(name)) {
|
||||
LogWarning(update_domain,
|
||||
"archive returned directory only");
|
||||
if (!IsAcceptableFilename(name))
|
||||
return;
|
||||
}
|
||||
|
||||
//add file
|
||||
Song *song = LockFindSong(directory, name);
|
||||
|
@ -531,6 +531,7 @@ UpdateWalk::Walk(Directory &root, const char *path, bool discard) noexcept
|
||||
|
||||
{
|
||||
const ScopeDatabaseLock protect;
|
||||
root.ClearInPlaylist();
|
||||
PurgeDanglingFromPlaylists(root);
|
||||
}
|
||||
|
||||
|
@ -114,11 +114,11 @@ constexpr const struct DecoderPlugin *decoder_plugins[] = {
|
||||
#ifdef ENABLE_ADPLUG
|
||||
&adplug_decoder_plugin,
|
||||
#endif
|
||||
#ifdef ENABLE_FFMPEG
|
||||
&ffmpeg_decoder_plugin,
|
||||
#endif
|
||||
#ifdef ENABLE_GME
|
||||
&gme_decoder_plugin,
|
||||
#endif
|
||||
#ifdef ENABLE_FFMPEG
|
||||
&ffmpeg_decoder_plugin,
|
||||
#endif
|
||||
&pcm_decoder_plugin,
|
||||
nullptr
|
||||
|
@ -31,6 +31,7 @@
|
||||
#include "lib/ffmpeg/Format.hxx"
|
||||
#include "lib/ffmpeg/Codec.hxx"
|
||||
#include "lib/ffmpeg/SampleFormat.hxx"
|
||||
#include "lib/ffmpeg/LibFmt.hxx"
|
||||
#include "../DecoderAPI.hxx"
|
||||
#include "FfmpegMetaData.hxx"
|
||||
#include "FfmpegIo.hxx"
|
||||
@ -523,9 +524,15 @@ FfmpegDecode(DecoderClient &client, InputStream *input,
|
||||
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,
|
||||
sample_format,
|
||||
codec_context->channels);
|
||||
channels);
|
||||
|
||||
const SignedSongTime total_time =
|
||||
av_stream.duration != (int64_t)AV_NOPTS_VALUE
|
||||
@ -635,10 +642,17 @@ FfmpegScanStream(AVFormatContext &format_context, TagHandler &handler)
|
||||
AV_TIME_BASE_Q));
|
||||
|
||||
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 {
|
||||
handler.OnAudioFormat(CheckAudioFormat(codec_params.sample_rate,
|
||||
ffmpeg_sample_format(AVSampleFormat(codec_params.format)),
|
||||
codec_params.channels));
|
||||
channels));
|
||||
} catch (...) {
|
||||
}
|
||||
|
||||
|
@ -21,10 +21,13 @@
|
||||
#define __STDC_CONSTANT_MACROS
|
||||
|
||||
#include "FfmpegIo.hxx"
|
||||
#include "libavutil/mem.h"
|
||||
#include "../DecoderAPI.hxx"
|
||||
#include "input/InputStream.hxx"
|
||||
|
||||
extern "C" {
|
||||
#include <libavutil/mem.h>
|
||||
}
|
||||
|
||||
AvioStream::~AvioStream()
|
||||
{
|
||||
if (io != nullptr) {
|
||||
|
@ -56,20 +56,17 @@ struct GmeContainerPath {
|
||||
unsigned track;
|
||||
};
|
||||
|
||||
#if GME_VERSION >= 0x000600
|
||||
static int gme_accuracy;
|
||||
#endif
|
||||
static unsigned gme_default_fade;
|
||||
|
||||
static bool
|
||||
gme_plugin_init([[maybe_unused]] const ConfigBlock &block)
|
||||
{
|
||||
#if GME_VERSION >= 0x000600
|
||||
auto accuracy = block.GetBlockParam("accuracy");
|
||||
gme_accuracy = accuracy != nullptr
|
||||
? (int)accuracy->GetBoolValue()
|
||||
: -1;
|
||||
#endif
|
||||
|
||||
auto fade = block.GetBlockParam("default_fade");
|
||||
gme_default_fade = fade != nullptr
|
||||
? fade->GetUnsignedValue() * 1000
|
||||
@ -163,10 +160,8 @@ gme_file_decode(DecoderClient &client, Path path_fs)
|
||||
FmtDebug(gme_domain, "emulator type '{}'",
|
||||
gme_type_system(gme_type(emu)));
|
||||
|
||||
#if GME_VERSION >= 0x000600
|
||||
if (gme_accuracy >= 0)
|
||||
gme_enable_accuracy(emu, gme_accuracy);
|
||||
#endif
|
||||
|
||||
gme_info_t *ti;
|
||||
const char *gme_err = gme_track_info(emu, &ti, container.track);
|
||||
|
@ -798,6 +798,8 @@ MadDecoder::UpdateTimerNextFrame() noexcept
|
||||
DecoderCommand
|
||||
MadDecoder::SubmitPCM(size_t i, size_t pcm_length) noexcept
|
||||
{
|
||||
assert(i <= pcm_length);
|
||||
|
||||
size_t num_samples = pcm_length - i;
|
||||
|
||||
mad_fixed_to_24_buffer(output_buffer, synth.pcm,
|
||||
@ -843,7 +845,7 @@ MadDecoder::SynthAndSubmit() noexcept
|
||||
size_t pcm_length = synth.pcm.length;
|
||||
if (drop_end_samples &&
|
||||
current_frame == max_frames - drop_end_frames - 1) {
|
||||
if (drop_end_samples >= pcm_length)
|
||||
if (i + drop_end_samples >= pcm_length)
|
||||
return DecoderCommand::STOP;
|
||||
|
||||
pcm_length -= drop_end_samples;
|
||||
|
@ -81,7 +81,7 @@ if libfaad_dep.found()
|
||||
decoder_plugins_sources += 'FaadDecoderPlugin.cxx'
|
||||
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())
|
||||
if libgme_dep.found()
|
||||
decoder_plugins_sources += 'GmeDecoderPlugin.cxx'
|
||||
|
@ -38,6 +38,7 @@ class FlacEncoder final : public Encoder {
|
||||
|
||||
FLAC__StreamEncoder *const fse;
|
||||
const unsigned compression;
|
||||
const bool oggflac;
|
||||
|
||||
PcmBuffer expand_buffer;
|
||||
|
||||
@ -122,7 +123,7 @@ flac_encoder_init(const ConfigBlock &block)
|
||||
}
|
||||
|
||||
static void
|
||||
flac_encoder_setup(FLAC__StreamEncoder *fse, unsigned compression,
|
||||
flac_encoder_setup(FLAC__StreamEncoder *fse, unsigned compression, bool oggflac,
|
||||
const AudioFormat &audio_format)
|
||||
{
|
||||
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",
|
||||
audio_format.sample_rate);
|
||||
|
||||
if (!FLAC__stream_encoder_set_ogg_serial_number(fse,
|
||||
if (oggflac && !FLAC__stream_encoder_set_ogg_serial_number(fse,
|
||||
GenerateSerial()))
|
||||
throw FormatRuntimeError("error setting ogg serial number");
|
||||
}
|
||||
@ -166,11 +167,12 @@ FlacEncoder::FlacEncoder(AudioFormat _audio_format, FLAC__StreamEncoder *_fse, u
|
||||
:Encoder(_oggchaining),
|
||||
audio_format(_audio_format), fse(_fse),
|
||||
compression(_compression),
|
||||
oggflac(_oggflac),
|
||||
output_buffer(8192)
|
||||
{
|
||||
/* this immediately outputs data through callback */
|
||||
|
||||
auto init_status = _oggflac ?
|
||||
auto init_status = oggflac ?
|
||||
FLAC__stream_encoder_init_ogg_stream(fse,
|
||||
nullptr, WriteCallback,
|
||||
nullptr, nullptr, nullptr,
|
||||
@ -209,7 +211,7 @@ PreparedFlacEncoder::Open(AudioFormat &audio_format)
|
||||
throw std::runtime_error("FLAC__stream_encoder_new() failed");
|
||||
|
||||
try {
|
||||
flac_encoder_setup(fse, compression, audio_format);
|
||||
flac_encoder_setup(fse, compression, oggflac, audio_format);
|
||||
} catch (...) {
|
||||
FLAC__stream_encoder_delete(fse);
|
||||
throw;
|
||||
@ -222,7 +224,7 @@ void
|
||||
FlacEncoder::SendTag(const Tag &tag)
|
||||
{
|
||||
/* 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_VorbisComment_Entry entry;
|
||||
|
@ -272,9 +272,8 @@ EventLoop::Run() noexcept
|
||||
#endif
|
||||
|
||||
assert(IsInside());
|
||||
assert(!quit);
|
||||
#ifdef HAVE_THREADED_EVENT_LOOP
|
||||
assert(alive);
|
||||
assert(alive || quit);
|
||||
assert(busy);
|
||||
|
||||
wake_event.Schedule(SocketEvent::READ);
|
||||
@ -299,7 +298,7 @@ EventLoop::Run() noexcept
|
||||
|
||||
steady_clock_cache.flush();
|
||||
|
||||
do {
|
||||
while (!quit) {
|
||||
again = false;
|
||||
|
||||
/* invoke timers */
|
||||
@ -361,7 +360,7 @@ EventLoop::Run() noexcept
|
||||
|
||||
socket_event.Dispatch();
|
||||
}
|
||||
} while (!quit);
|
||||
}
|
||||
|
||||
#ifdef HAVE_THREADED_EVENT_LOOP
|
||||
#ifndef NDEBUG
|
||||
|
@ -40,10 +40,15 @@ FfmpegFilter::FfmpegFilter(const AudioFormat &in_audio_format,
|
||||
buffer_sink(_buffer_sink),
|
||||
in_format(Ffmpeg::ToFfmpegSampleFormat(in_audio_format.format)),
|
||||
in_sample_rate(in_audio_format.sample_rate),
|
||||
#if LIBAVUTIL_VERSION_INT < AV_VERSION_INT(57, 25, 100)
|
||||
in_channels(in_audio_format.channels),
|
||||
#endif
|
||||
in_audio_frame_size(in_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>
|
||||
@ -54,7 +59,11 @@ FfmpegFilter::FilterPCM(ConstBuffer<void> src)
|
||||
frame.Unref();
|
||||
frame->format = in_format;
|
||||
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;
|
||||
#endif
|
||||
frame->nb_samples = src.size / in_audio_frame_size;
|
||||
|
||||
frame.GetBuffer();
|
||||
|
@ -35,7 +35,13 @@ class FfmpegFilter final : public Filter {
|
||||
|
||||
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 out_audio_frame_size;
|
||||
|
@ -23,6 +23,8 @@
|
||||
#include "ReplayGainInfo.hxx"
|
||||
#include "ReplayGainConfig.hxx"
|
||||
#include "mixer/MixerControl.hxx"
|
||||
#include "mixer/MixerInternal.hxx"
|
||||
#include "mixer/Listener.hxx"
|
||||
#include "pcm/AudioFormat.hxx"
|
||||
#include "pcm/Volume.hxx"
|
||||
#include "util/ConstBuffer.hxx"
|
||||
@ -171,9 +173,11 @@ ReplayGainFilter::Update()
|
||||
try {
|
||||
mixer_set_volume(mixer, _volume);
|
||||
|
||||
/* TODO: emit this idle event only for the
|
||||
current partition */
|
||||
idle_add(IDLE_MIXER);
|
||||
/* invoke the mixer's listener manually, just
|
||||
in case the mixer implementation didn't do
|
||||
that already (this depends on the
|
||||
implementation) */
|
||||
mixer->listener.OnMixerVolumeChanged(*mixer, _volume);
|
||||
} catch (...) {
|
||||
LogError(std::current_exception(),
|
||||
"Failed to update hardware mixer");
|
||||
|
@ -20,7 +20,6 @@
|
||||
#include "Charset.hxx"
|
||||
#include "Features.hxx"
|
||||
#include "Domain.hxx"
|
||||
#include "Log.hxx"
|
||||
#include "lib/icu/Converter.hxx"
|
||||
#include "util/AllocatedString.hxx"
|
||||
#include "config.h"
|
||||
@ -45,11 +44,9 @@ SetFSCharset(const char *charset)
|
||||
assert(charset != nullptr);
|
||||
assert(fs_converter == nullptr);
|
||||
|
||||
fs_charset = charset;
|
||||
fs_converter = IcuConverter::Create(charset);
|
||||
assert(fs_converter != nullptr);
|
||||
|
||||
FmtDebug(path_domain,
|
||||
"SetFSCharset: fs charset is {}", fs_charset);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
@ -67,6 +67,16 @@ StatFile(Path file, struct stat &buf, bool follow_symlinks = true)
|
||||
|
||||
#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
|
||||
* error.
|
||||
|
@ -34,7 +34,6 @@
|
||||
#include <shlobj.h>
|
||||
#else
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <pwd.h>
|
||||
#endif
|
||||
|
||||
@ -53,6 +52,12 @@
|
||||
#include "Main.hxx"
|
||||
#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)
|
||||
class PasswdEntry
|
||||
{
|
||||
@ -74,15 +79,6 @@ public:
|
||||
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->() {
|
||||
assert(result != nullptr);
|
||||
return result;
|
||||
@ -254,7 +250,8 @@ GetUserMusicDir() noexcept
|
||||
#elif defined(USE_XDG)
|
||||
return GetUserDir("XDG_MUSIC_DIR");
|
||||
#elif defined(ANDROID)
|
||||
return Environment::getExternalStoragePublicDirectory("Music");
|
||||
return Environment::getExternalStoragePublicDirectory(Java::GetEnv(),
|
||||
"Music");
|
||||
#else
|
||||
return nullptr;
|
||||
#endif
|
||||
@ -283,6 +280,24 @@ GetUserCacheDir() noexcept
|
||||
#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
|
||||
GetUserRuntimeDir() noexcept
|
||||
{
|
||||
@ -296,7 +311,7 @@ GetUserRuntimeDir() noexcept
|
||||
AllocatedPath
|
||||
GetAppRuntimeDir() noexcept
|
||||
{
|
||||
#ifdef __linux__
|
||||
#if defined(__linux__) && !defined(ANDROID)
|
||||
/* systemd specific; see systemd.exec(5) */
|
||||
if (const char *runtime_directory = getenv("RUNTIME_DIRECTORY"))
|
||||
if (auto dir = StringView{runtime_directory}.Split(':').first;
|
||||
@ -306,8 +321,8 @@ GetAppRuntimeDir() noexcept
|
||||
|
||||
#ifdef USE_XDG
|
||||
if (const auto user_dir = GetUserRuntimeDir(); !user_dir.IsNull()) {
|
||||
auto dir = user_dir / Path::FromFS("mpd");
|
||||
mkdir(dir.c_str(), 0700);
|
||||
auto dir = user_dir / app_filename;
|
||||
CreateDirectoryNoThrow(dir);
|
||||
return dir;
|
||||
}
|
||||
#endif
|
||||
@ -350,10 +365,8 @@ GetHomeDir() noexcept
|
||||
if (const auto home = getenv("HOME");
|
||||
IsValidPathString(home) && IsValidDir(home))
|
||||
return AllocatedPath::FromFS(home);
|
||||
|
||||
if (PasswdEntry pw; pw.ReadByUid(getuid()))
|
||||
return SafePathFromFS(pw->pw_dir);
|
||||
#endif
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
@ -43,6 +43,13 @@ GetUserMusicDir() noexcept;
|
||||
AllocatedPath
|
||||
GetUserCacheDir() noexcept;
|
||||
|
||||
/**
|
||||
* Obtains cache directory for this application.
|
||||
*/
|
||||
[[gnu::const]]
|
||||
AllocatedPath
|
||||
GetAppCacheDir() noexcept;
|
||||
|
||||
/**
|
||||
* Obtains the runtime directory for the current user.
|
||||
*/
|
||||
|
@ -101,9 +101,17 @@ AsyncInputStream::Seek(std::unique_lock<Mutex> &lock,
|
||||
assert(IsReady());
|
||||
assert(seek_state == SeekState::NONE);
|
||||
|
||||
if (new_offset == offset)
|
||||
/* no-op */
|
||||
if (new_offset == offset) {
|
||||
/* 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;
|
||||
}
|
||||
|
||||
if (!IsSeekable())
|
||||
throw std::runtime_error("Not seekable");
|
||||
|
@ -45,6 +45,14 @@
|
||||
|
||||
#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 {
|
||||
cdrom_drive_t *const drv;
|
||||
CdIo_t *const cdio;
|
||||
@ -65,9 +73,7 @@ class CdioParanoiaInputStream final : public InputStream {
|
||||
lsn_from(_lsn_from),
|
||||
buffer_lsn(-1)
|
||||
{
|
||||
/* Set reading mode for full paranoia, but allow
|
||||
skipping sectors. */
|
||||
para.SetMode(PARANOIA_MODE_FULL^PARANOIA_MODE_NEVERSKIP);
|
||||
para.SetMode(mode_flags);
|
||||
|
||||
/* seek to beginning of the track */
|
||||
para.Seek(lsn_from);
|
||||
@ -98,11 +104,6 @@ class CdioParanoiaInputStream final : public InputStream {
|
||||
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
|
||||
input_cdio_init(EventLoop &, const ConfigBlock &block)
|
||||
{
|
||||
@ -117,6 +118,26 @@ input_cdio_init(EventLoop &, const ConfigBlock &block)
|
||||
value);
|
||||
}
|
||||
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 {
|
||||
|
@ -417,7 +417,6 @@ CurlInputStream::InitEasy()
|
||||
request->SetOption(CURLOPT_HTTP200ALIASES, http_200_aliases);
|
||||
request->SetOption(CURLOPT_FOLLOWLOCATION, 1L);
|
||||
request->SetOption(CURLOPT_MAXREDIRS, 5L);
|
||||
request->SetOption(CURLOPT_FAILONERROR, 1L);
|
||||
|
||||
/* this option eliminates the probe request when
|
||||
username/password are specified */
|
||||
@ -439,6 +438,14 @@ CurlInputStream::InitEasy()
|
||||
request->SetVerifyPeer(verify_peer);
|
||||
request->SetVerifyHost(verify_host);
|
||||
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
|
||||
|
@ -42,6 +42,8 @@
|
||||
#include "io/UniqueFileDescriptor.hxx"
|
||||
#endif
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
class Path;
|
||||
class FileInfo;
|
||||
|
||||
|
@ -49,9 +49,6 @@ Java::File::Initialise(JNIEnv *env) noexcept
|
||||
AllocatedPath
|
||||
Java::File::ToAbsolutePath(JNIEnv *env, jobject _file) noexcept
|
||||
{
|
||||
assert(env != nullptr);
|
||||
assert(_file != nullptr);
|
||||
|
||||
LocalObject file(env, _file);
|
||||
|
||||
const jstring path = GetAbsolutePath(env, file);
|
||||
|
@ -89,6 +89,16 @@ public:
|
||||
String(JNIEnv *_env, const char *_value) noexcept
|
||||
: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 {
|
||||
return {env, s, env->GetStringUTFChars(s, nullptr)};
|
||||
}
|
||||
|
@ -18,13 +18,13 @@ endif
|
||||
|
||||
conf.set('HAVE_MD5', crypto_md5_dep.found())
|
||||
|
||||
if libavutil_dep.found()
|
||||
if ffmpeg_util_dep.found()
|
||||
crypto_base64 = static_library(
|
||||
'crypto_base64',
|
||||
'Base64.cxx',
|
||||
include_directories: inc,
|
||||
dependencies: [
|
||||
libavutil_dep,
|
||||
ffmpeg_util_dep,
|
||||
],
|
||||
)
|
||||
|
||||
|
@ -186,10 +186,6 @@ public:
|
||||
SetOption(CURLOPT_POSTFIELDSIZE, (long)size);
|
||||
}
|
||||
|
||||
void SetHttpPost(const struct curl_httppost *post) {
|
||||
SetOption(CURLOPT_HTTPPOST, post);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
bool GetInfo(CURLINFO info, T value_r) const noexcept {
|
||||
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.
|
||||
*/
|
||||
[[gnu::pure]]
|
||||
int64_t GetContentLength() const noexcept {
|
||||
double value;
|
||||
return GetInfo(CURLINFO_CONTENT_LENGTH_DOWNLOAD, &value)
|
||||
? (int64_t)value
|
||||
curl_off_t GetContentLength() const noexcept {
|
||||
curl_off_t value;
|
||||
return GetInfo(CURLINFO_CONTENT_LENGTH_DOWNLOAD_T, &value)
|
||||
? value
|
||||
: -1;
|
||||
}
|
||||
|
||||
|
@ -123,6 +123,14 @@ public:
|
||||
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) {
|
||||
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())
|
||||
if not curl_dep.found()
|
||||
subdir_done()
|
||||
|
@ -36,6 +36,7 @@
|
||||
#include "Iter.hxx"
|
||||
#include "Values.hxx"
|
||||
|
||||
#include <cstdint>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace ODBus {
|
||||
|
@ -48,7 +48,11 @@ DetectFilterOutputFormat(const AudioFormat &in_audio_format,
|
||||
Frame frame;
|
||||
frame->format = ToFfmpegSampleFormat(in_audio_format.format);
|
||||
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;
|
||||
#endif
|
||||
frame->nb_samples = 1;
|
||||
|
||||
frame.GetBuffer();
|
||||
@ -75,8 +79,14 @@ DetectFilterOutputFormat(const AudioFormat &in_audio_format,
|
||||
if (sample_format == SampleFormat::UNDEFINED)
|
||||
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,
|
||||
frame->channels);
|
||||
out_channels);
|
||||
}
|
||||
|
||||
} // namespace Ffmpeg
|
||||
|
@ -38,7 +38,11 @@ InterleaveFrame(const AVFrame &frame, FfmpegBuffer &buffer)
|
||||
assert(frame.nb_samples > 0);
|
||||
|
||||
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;
|
||||
#endif
|
||||
const std::size_t n_frames = frame.nb_samples;
|
||||
|
||||
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
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "LogError.hxx"
|
||||
#include "Domain.hxx"
|
||||
#include "Log.hxx"
|
||||
#pragma once
|
||||
|
||||
extern "C" {
|
||||
#include <libavutil/error.h>
|
||||
#include <libavutil/samplefmt.h>
|
||||
}
|
||||
|
||||
void
|
||||
LogFfmpegError(int errnum)
|
||||
{
|
||||
char msg[256];
|
||||
av_strerror(errnum, msg, sizeof(msg));
|
||||
LogError(ffmpeg_domain, msg);
|
||||
}
|
||||
#include <fmt/format.h>
|
||||
|
||||
void
|
||||
LogFfmpegError(int errnum, const char *prefix)
|
||||
template<>
|
||||
struct fmt::formatter<AVSampleFormat> : formatter<string_view>
|
||||
{
|
||||
char msg[256];
|
||||
av_strerror(errnum, msg, sizeof(msg));
|
||||
FmtError(ffmpeg_domain, "{}: {}", prefix, msg);
|
||||
}
|
||||
template<typename FormatContext>
|
||||
auto format(const AVSampleFormat format, FormatContext &ctx) {
|
||||
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
|
||||
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
|
||||
ffmpeg_dep = dependency('', required: false)
|
||||
subdir_done()
|
||||
@ -30,17 +53,16 @@ ffmpeg = static_library(
|
||||
'ffmpeg',
|
||||
'Init.cxx',
|
||||
'Interleave.cxx',
|
||||
'LogError.cxx',
|
||||
'LogCallback.cxx',
|
||||
'Error.cxx',
|
||||
'Domain.cxx',
|
||||
ffmpeg_sources,
|
||||
include_directories: inc,
|
||||
dependencies: [
|
||||
ffmpeg_util_dep,
|
||||
libavformat_dep,
|
||||
libavcodec_dep,
|
||||
libavfilter_dep,
|
||||
libavutil_dep,
|
||||
log_dep,
|
||||
],
|
||||
)
|
||||
@ -48,9 +70,9 @@ ffmpeg = static_library(
|
||||
ffmpeg_dep = declare_dependency(
|
||||
link_with: ffmpeg,
|
||||
dependencies: [
|
||||
ffmpeg_util_dep,
|
||||
libavformat_dep,
|
||||
libavcodec_dep,
|
||||
libavfilter_dep,
|
||||
libavutil_dep,
|
||||
],
|
||||
)
|
||||
|
@ -18,17 +18,25 @@ if icu_dep.found()
|
||||
'Util.cxx',
|
||||
'Init.cxx',
|
||||
]
|
||||
elif not get_option('iconv').disabled()
|
||||
# an installed iconv library will make the builtin iconv() unavailable,
|
||||
# so search for the library first and pass it as (possible) dependency
|
||||
iconv_dep = compiler.find_library('libiconv', required: false)
|
||||
have_iconv = compiler.has_function('iconv',
|
||||
dependencies: iconv_dep,
|
||||
prefix : '#include <iconv.h>')
|
||||
if not have_iconv and get_option('iconv').enabled()
|
||||
error('iconv() not available')
|
||||
else
|
||||
if meson.version().version_compare('>= 0.60')
|
||||
iconv_dep = dependency('iconv', required: get_option('iconv'))
|
||||
conf.set('HAVE_ICONV', iconv_dep.found())
|
||||
elif not get_option('iconv').disabled()
|
||||
iconv_open_snippet = '''#include <iconv.h>
|
||||
int main() {
|
||||
iconv_open("","");
|
||||
}'''
|
||||
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
|
||||
conf.set('HAVE_ICONV', have_iconv)
|
||||
endif
|
||||
|
||||
icu = static_library(
|
||||
|
@ -17,14 +17,11 @@
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include "Volume.hxx"
|
||||
#include "Memento.hxx"
|
||||
#include "output/MultipleOutputs.hxx"
|
||||
#include "Idle.hxx"
|
||||
#include "util/StringCompare.hxx"
|
||||
#include "util/Domain.hxx"
|
||||
#include "system/PeriodClock.hxx"
|
||||
#include "io/BufferedOutputStream.hxx"
|
||||
#include "Log.hxx"
|
||||
|
||||
#include <cassert>
|
||||
|
||||
@ -32,24 +29,8 @@
|
||||
|
||||
#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
|
||||
volume_level_get(const MultipleOutputs &outputs) noexcept
|
||||
MixerMemento::GetVolume(const MultipleOutputs &outputs) noexcept
|
||||
{
|
||||
if (last_hardware_volume >= 0 &&
|
||||
!hardware_volume_clock.CheckUpdate(std::chrono::seconds(1)))
|
||||
@ -60,8 +41,8 @@ volume_level_get(const MultipleOutputs &outputs) noexcept
|
||||
return last_hardware_volume;
|
||||
}
|
||||
|
||||
static bool
|
||||
software_volume_change(MultipleOutputs &outputs, unsigned volume)
|
||||
inline bool
|
||||
MixerMemento::SetSoftwareVolume(MultipleOutputs &outputs, unsigned volume)
|
||||
{
|
||||
assert(volume <= 100);
|
||||
|
||||
@ -71,8 +52,8 @@ software_volume_change(MultipleOutputs &outputs, unsigned volume)
|
||||
return true;
|
||||
}
|
||||
|
||||
static void
|
||||
hardware_volume_change(MultipleOutputs &outputs, unsigned volume)
|
||||
inline void
|
||||
MixerMemento::SetHardwareVolume(MultipleOutputs &outputs, unsigned volume)
|
||||
{
|
||||
/* reset the cache */
|
||||
last_hardware_volume = -1;
|
||||
@ -81,19 +62,17 @@ hardware_volume_change(MultipleOutputs &outputs, unsigned volume)
|
||||
}
|
||||
|
||||
void
|
||||
volume_level_change(MultipleOutputs &outputs, unsigned volume)
|
||||
MixerMemento::SetVolume(MultipleOutputs &outputs, unsigned volume)
|
||||
{
|
||||
assert(volume <= 100);
|
||||
|
||||
volume_software_set = volume;
|
||||
|
||||
idle_add(IDLE_MIXER);
|
||||
|
||||
hardware_volume_change(outputs, volume);
|
||||
SetHardwareVolume(outputs, volume);
|
||||
}
|
||||
|
||||
bool
|
||||
read_sw_volume_state(const char *line, MultipleOutputs &outputs)
|
||||
MixerMemento::LoadSoftwareVolumeState(const char *line, MultipleOutputs &outputs)
|
||||
{
|
||||
char *end = nullptr;
|
||||
long int sv;
|
||||
@ -104,21 +83,13 @@ read_sw_volume_state(const char *line, MultipleOutputs &outputs)
|
||||
|
||||
sv = strtol(line, &end, 10);
|
||||
if (*end == 0 && sv >= 0 && sv <= 100)
|
||||
software_volume_change(outputs, sv);
|
||||
else
|
||||
FmtWarning(volume_domain,
|
||||
"Can't parse software volume: {}", line);
|
||||
SetSoftwareVolume(outputs, sv);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
save_sw_volume_state(BufferedOutputStream &os)
|
||||
MixerMemento::SaveSoftwareVolumeState(BufferedOutputStream &os) const
|
||||
{
|
||||
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
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);
|
||||
};
|
@ -188,8 +188,8 @@ MultipleOutputs::SetSoftwareVolume(unsigned volume) noexcept
|
||||
auto *mixer = ao->GetMixer();
|
||||
|
||||
if (mixer != nullptr &&
|
||||
(&mixer->plugin == &software_mixer_plugin ||
|
||||
&mixer->plugin == &null_mixer_plugin))
|
||||
(mixer->IsPlugin(software_mixer_plugin) ||
|
||||
mixer->IsPlugin(null_mixer_plugin)))
|
||||
mixer_set_volume(mixer, volume);
|
||||
}
|
||||
}
|
||||
|
@ -92,7 +92,7 @@ mixer_close(Mixer *mixer)
|
||||
void
|
||||
mixer_auto_close(Mixer *mixer)
|
||||
{
|
||||
if (!mixer->plugin.global)
|
||||
if (!mixer->IsGlobal())
|
||||
mixer_close(mixer);
|
||||
}
|
||||
|
||||
@ -103,7 +103,7 @@ mixer_get_volume(Mixer *mixer)
|
||||
|
||||
assert(mixer != nullptr);
|
||||
|
||||
if (mixer->plugin.global && !mixer->failure)
|
||||
if (mixer->IsGlobal() && !mixer->failure)
|
||||
mixer_open(mixer);
|
||||
|
||||
const std::scoped_lock<Mutex> protect(mixer->mutex);
|
||||
@ -128,7 +128,7 @@ mixer_set_volume(Mixer *mixer, unsigned volume)
|
||||
assert(mixer != nullptr);
|
||||
assert(volume <= 100);
|
||||
|
||||
if (mixer->plugin.global && !mixer->failure)
|
||||
if (mixer->IsGlobal() && !mixer->failure)
|
||||
mixer_open(mixer);
|
||||
|
||||
const std::scoped_lock<Mutex> protect(mixer->mutex);
|
||||
|
@ -1,55 +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_VOLUME_HXX
|
||||
#define MPD_VOLUME_HXX
|
||||
|
||||
class MultipleOutputs;
|
||||
class BufferedOutputStream;
|
||||
|
||||
void
|
||||
InvalidateHardwareVolume() noexcept;
|
||||
|
||||
[[gnu::pure]]
|
||||
int
|
||||
volume_level_get(const MultipleOutputs &outputs) noexcept;
|
||||
|
||||
/**
|
||||
* Throws on error.
|
||||
*/
|
||||
void
|
||||
volume_level_change(MultipleOutputs &outputs, unsigned volume);
|
||||
|
||||
bool
|
||||
read_sw_volume_state(const char *line, MultipleOutputs &outputs);
|
||||
|
||||
void
|
||||
save_sw_volume_state(BufferedOutputStream &os);
|
||||
|
||||
/**
|
||||
* 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
|
||||
sw_volume_state_get_hash() noexcept;
|
||||
|
||||
#endif
|
@ -28,13 +28,15 @@
|
||||
#include "MultipleOutputs.hxx"
|
||||
#include "Client.hxx"
|
||||
#include "mixer/MixerControl.hxx"
|
||||
#include "mixer/Volume.hxx"
|
||||
#include "mixer/Memento.hxx"
|
||||
#include "Idle.hxx"
|
||||
|
||||
extern unsigned audio_output_state_version;
|
||||
|
||||
bool
|
||||
audio_output_enable_index(MultipleOutputs &outputs, unsigned idx)
|
||||
audio_output_enable_index(MultipleOutputs &outputs,
|
||||
MixerMemento &mixer_memento,
|
||||
unsigned idx)
|
||||
{
|
||||
if (idx >= outputs.Size())
|
||||
return false;
|
||||
@ -46,7 +48,7 @@ audio_output_enable_index(MultipleOutputs &outputs, unsigned idx)
|
||||
idle_add(IDLE_OUTPUT);
|
||||
|
||||
if (ao.GetMixer() != nullptr) {
|
||||
InvalidateHardwareVolume();
|
||||
mixer_memento.InvalidateHardwareVolume();
|
||||
idle_add(IDLE_MIXER);
|
||||
}
|
||||
|
||||
@ -58,7 +60,9 @@ audio_output_enable_index(MultipleOutputs &outputs, unsigned idx)
|
||||
}
|
||||
|
||||
bool
|
||||
audio_output_disable_index(MultipleOutputs &outputs, unsigned idx)
|
||||
audio_output_disable_index(MultipleOutputs &outputs,
|
||||
MixerMemento &mixer_memento,
|
||||
unsigned idx)
|
||||
{
|
||||
if (idx >= outputs.Size())
|
||||
return false;
|
||||
@ -72,7 +76,7 @@ audio_output_disable_index(MultipleOutputs &outputs, unsigned idx)
|
||||
auto *mixer = ao.GetMixer();
|
||||
if (mixer != nullptr) {
|
||||
mixer_close(mixer);
|
||||
InvalidateHardwareVolume();
|
||||
mixer_memento.InvalidateHardwareVolume();
|
||||
idle_add(IDLE_MIXER);
|
||||
}
|
||||
|
||||
@ -84,7 +88,9 @@ audio_output_disable_index(MultipleOutputs &outputs, unsigned idx)
|
||||
}
|
||||
|
||||
bool
|
||||
audio_output_toggle_index(MultipleOutputs &outputs, unsigned idx)
|
||||
audio_output_toggle_index(MultipleOutputs &outputs,
|
||||
MixerMemento &mixer_memento,
|
||||
unsigned idx)
|
||||
{
|
||||
if (idx >= outputs.Size())
|
||||
return false;
|
||||
@ -97,7 +103,7 @@ audio_output_toggle_index(MultipleOutputs &outputs, unsigned idx)
|
||||
auto *mixer = ao.GetMixer();
|
||||
if (mixer != nullptr) {
|
||||
mixer_close(mixer);
|
||||
InvalidateHardwareVolume();
|
||||
mixer_memento.InvalidateHardwareVolume();
|
||||
idle_add(IDLE_MIXER);
|
||||
}
|
||||
}
|
||||
|
@ -28,26 +28,33 @@
|
||||
#define MPD_OUTPUT_COMMAND_HXX
|
||||
|
||||
class MultipleOutputs;
|
||||
class MixerMemento;
|
||||
|
||||
/**
|
||||
* Enables an audio output. Returns false if the specified output
|
||||
* does not exist.
|
||||
*/
|
||||
bool
|
||||
audio_output_enable_index(MultipleOutputs &outputs, unsigned idx);
|
||||
audio_output_enable_index(MultipleOutputs &outputs,
|
||||
MixerMemento &mixer_memento,
|
||||
unsigned idx);
|
||||
|
||||
/**
|
||||
* Disables an audio output. Returns false if the specified output
|
||||
* does not exist.
|
||||
*/
|
||||
bool
|
||||
audio_output_disable_index(MultipleOutputs &outputs, unsigned idx);
|
||||
audio_output_disable_index(MultipleOutputs &outputs,
|
||||
MixerMemento &mixer_memento,
|
||||
unsigned idx);
|
||||
|
||||
/**
|
||||
* Toggles an audio output. Returns false if the specified output
|
||||
* does not exist.
|
||||
*/
|
||||
bool
|
||||
audio_output_toggle_index(MultipleOutputs &outputs, unsigned idx);
|
||||
audio_output_toggle_index(MultipleOutputs &outputs,
|
||||
MixerMemento &mixer_memento,
|
||||
unsigned idx);
|
||||
|
||||
#endif
|
||||
|
@ -812,8 +812,12 @@ AlsaOutput::Open(AudioFormat &audio_format)
|
||||
fmt::format("Failed to open ALSA device \"{}\"",
|
||||
GetDevice()).c_str());
|
||||
|
||||
const char *pcm_name = snd_pcm_name(pcm);
|
||||
if (pcm_name == nullptr)
|
||||
pcm_name = "?";
|
||||
|
||||
FmtDebug(alsa_output_domain, "opened {} type={}",
|
||||
snd_pcm_name(pcm),
|
||||
pcm_name,
|
||||
snd_pcm_type_name(snd_pcm_type(pcm)));
|
||||
|
||||
#ifdef ENABLE_DSD
|
||||
|
@ -47,6 +47,15 @@
|
||||
|
||||
#include <memory>
|
||||
|
||||
// Backward compatibility from OSX 12.0 API change
|
||||
#if (__MAC_OS_X_VERSION_MAX_ALLOWED >= 120000)
|
||||
#define KAUDIO_OBJECT_PROPERTY_ELEMENT_MM kAudioObjectPropertyElementMain
|
||||
#define KAUDIO_HARDWARE_SERVICE_DEVICE_PROPERTY_VV kAudioHardwareServiceDeviceProperty_VirtualMainVolume
|
||||
#else
|
||||
#define KAUDIO_OBJECT_PROPERTY_ELEMENT_MM kAudioObjectPropertyElementMaster
|
||||
#define KAUDIO_HARDWARE_SERVICE_DEVICE_PROPERTY_VV kAudioHardwareServiceDeviceProperty_VirtualMasterVolume
|
||||
#endif
|
||||
|
||||
static constexpr unsigned MPD_OSX_BUFFER_TIME_MS = 100;
|
||||
|
||||
static StringBuffer<64>
|
||||
@ -160,13 +169,13 @@ OSXOutput::Create(EventLoop &, const ConfigBlock &block)
|
||||
static constexpr AudioObjectPropertyAddress default_system_output_device{
|
||||
kAudioHardwarePropertyDefaultSystemOutputDevice,
|
||||
kAudioObjectPropertyScopeOutput,
|
||||
kAudioObjectPropertyElementMain,
|
||||
KAUDIO_OBJECT_PROPERTY_ELEMENT_MM,
|
||||
};
|
||||
|
||||
static constexpr AudioObjectPropertyAddress default_output_device{
|
||||
kAudioHardwarePropertyDefaultOutputDevice,
|
||||
kAudioObjectPropertyScopeOutput,
|
||||
kAudioObjectPropertyElementMain
|
||||
KAUDIO_OBJECT_PROPERTY_ELEMENT_MM
|
||||
};
|
||||
|
||||
const auto &aopa =
|
||||
@ -195,9 +204,9 @@ int
|
||||
OSXOutput::GetVolume()
|
||||
{
|
||||
static constexpr AudioObjectPropertyAddress aopa = {
|
||||
kAudioHardwareServiceDeviceProperty_VirtualMainVolume,
|
||||
KAUDIO_HARDWARE_SERVICE_DEVICE_PROPERTY_VV,
|
||||
kAudioObjectPropertyScopeOutput,
|
||||
kAudioObjectPropertyElementMain,
|
||||
KAUDIO_OBJECT_PROPERTY_ELEMENT_MM,
|
||||
};
|
||||
|
||||
const auto vol = AudioObjectGetPropertyDataT<Float32>(dev_id,
|
||||
@ -211,9 +220,9 @@ OSXOutput::SetVolume(unsigned new_volume)
|
||||
{
|
||||
Float32 vol = new_volume / 100.0;
|
||||
static constexpr AudioObjectPropertyAddress aopa = {
|
||||
kAudioHardwareServiceDeviceProperty_VirtualMainVolume,
|
||||
KAUDIO_HARDWARE_SERVICE_DEVICE_PROPERTY_VV,
|
||||
kAudioObjectPropertyScopeOutput,
|
||||
kAudioObjectPropertyElementMain
|
||||
KAUDIO_OBJECT_PROPERTY_ELEMENT_MM
|
||||
};
|
||||
UInt32 size = sizeof(vol);
|
||||
OSStatus status = AudioObjectSetPropertyData(dev_id,
|
||||
@ -366,25 +375,25 @@ osx_output_set_device_format(AudioDeviceID dev_id,
|
||||
static constexpr AudioObjectPropertyAddress aopa_device_streams = {
|
||||
kAudioDevicePropertyStreams,
|
||||
kAudioObjectPropertyScopeOutput,
|
||||
kAudioObjectPropertyElementMain
|
||||
KAUDIO_OBJECT_PROPERTY_ELEMENT_MM
|
||||
};
|
||||
|
||||
static constexpr AudioObjectPropertyAddress aopa_stream_direction = {
|
||||
kAudioStreamPropertyDirection,
|
||||
kAudioObjectPropertyScopeOutput,
|
||||
kAudioObjectPropertyElementMain
|
||||
KAUDIO_OBJECT_PROPERTY_ELEMENT_MM
|
||||
};
|
||||
|
||||
static constexpr AudioObjectPropertyAddress aopa_stream_phys_formats = {
|
||||
kAudioStreamPropertyAvailablePhysicalFormats,
|
||||
kAudioObjectPropertyScopeOutput,
|
||||
kAudioObjectPropertyElementMain
|
||||
KAUDIO_OBJECT_PROPERTY_ELEMENT_MM
|
||||
};
|
||||
|
||||
static constexpr AudioObjectPropertyAddress aopa_stream_phys_format = {
|
||||
kAudioStreamPropertyPhysicalFormat,
|
||||
kAudioObjectPropertyScopeOutput,
|
||||
kAudioObjectPropertyElementMain
|
||||
KAUDIO_OBJECT_PROPERTY_ELEMENT_MM
|
||||
};
|
||||
|
||||
OSStatus err;
|
||||
@ -484,7 +493,7 @@ osx_output_hog_device(AudioDeviceID dev_id, bool hog) noexcept
|
||||
static constexpr AudioObjectPropertyAddress aopa = {
|
||||
kAudioDevicePropertyHogMode,
|
||||
kAudioObjectPropertyScopeOutput,
|
||||
kAudioObjectPropertyElementMain
|
||||
KAUDIO_OBJECT_PROPERTY_ELEMENT_MM
|
||||
};
|
||||
|
||||
pid_t hog_pid;
|
||||
@ -538,7 +547,7 @@ IsAudioDeviceName(AudioDeviceID id, const char *expected_name) noexcept
|
||||
static constexpr AudioObjectPropertyAddress aopa_name{
|
||||
kAudioObjectPropertyName,
|
||||
kAudioObjectPropertyScopeGlobal,
|
||||
kAudioObjectPropertyElementMain,
|
||||
KAUDIO_OBJECT_PROPERTY_ELEMENT_MM,
|
||||
};
|
||||
|
||||
char actual_name[256];
|
||||
@ -561,7 +570,7 @@ FindAudioDeviceByName(const char *name)
|
||||
static constexpr AudioObjectPropertyAddress aopa_hw_devices{
|
||||
kAudioHardwarePropertyDevices,
|
||||
kAudioObjectPropertyScopeGlobal,
|
||||
kAudioObjectPropertyElementMain,
|
||||
KAUDIO_OBJECT_PROPERTY_ELEMENT_MM,
|
||||
};
|
||||
|
||||
const auto ids =
|
||||
@ -618,7 +627,7 @@ osx_render(void *vdata,
|
||||
{
|
||||
OSXOutput *od = (OSXOutput *) vdata;
|
||||
|
||||
int count = in_number_frames * od->asbd.mBytesPerFrame;
|
||||
std::size_t count = in_number_frames * od->asbd.mBytesPerFrame;
|
||||
buffer_list->mBuffers[0].mDataByteSize =
|
||||
od->ring_buffer->pop((uint8_t *)buffer_list->mBuffers[0].mData,
|
||||
count);
|
||||
|
@ -514,6 +514,7 @@ PipeWireOutput::Open(AudioFormat &audio_format)
|
||||
PW_KEY_MEDIA_CATEGORY, "Playback",
|
||||
PW_KEY_MEDIA_ROLE, "Music",
|
||||
PW_KEY_APP_NAME, "Music Player Daemon",
|
||||
PW_KEY_APP_ICON_NAME, "mpd",
|
||||
nullptr);
|
||||
|
||||
pw_properties_setf(props, PW_KEY_NODE_NAME, "mpd.%s", name);
|
||||
@ -522,7 +523,13 @@ PipeWireOutput::Open(AudioFormat &audio_format)
|
||||
pw_properties_setf(props, PW_KEY_REMOTE_NAME, "%s", remote);
|
||||
|
||||
if (target != nullptr && target_id == PW_ID_ANY)
|
||||
pw_properties_setf(props, PW_KEY_NODE_TARGET, "%s", target);
|
||||
pw_properties_setf(props,
|
||||
#if PW_CHECK_VERSION(0, 3, 64)
|
||||
PW_KEY_TARGET_OBJECT,
|
||||
#else
|
||||
PW_KEY_NODE_TARGET,
|
||||
#endif
|
||||
"%s", target);
|
||||
|
||||
#ifdef PW_KEY_NODE_RATE
|
||||
/* ask PipeWire to change the graph sample rate to ours
|
||||
@ -966,6 +973,8 @@ PipeWireOutput::SendTag(const Tag &tag)
|
||||
|
||||
struct spa_dict dict = SPA_DICT_INIT(items, n_items);
|
||||
|
||||
const PipeWire::ThreadLoopLock lock(thread_loop);
|
||||
|
||||
auto rc = pw_stream_update_properties(stream, &dict);
|
||||
if (rc < 0)
|
||||
LogWarning(pipewire_output_domain, "Error updating properties");
|
||||
|
@ -17,7 +17,6 @@ storage_glue = static_library(
|
||||
'CompositeStorage.cxx',
|
||||
'MemoryDirectoryReader.cxx',
|
||||
'Configured.cxx',
|
||||
'StorageState.cxx',
|
||||
include_directories: inc,
|
||||
dependencies: [
|
||||
boost_dep,
|
||||
@ -31,4 +30,3 @@ storage_glue_dep = declare_dependency(
|
||||
storage_plugins_dep,
|
||||
],
|
||||
)
|
||||
|
||||
|
@ -22,6 +22,7 @@
|
||||
#include "storage/StorageInterface.hxx"
|
||||
#include "storage/FileInfo.hxx"
|
||||
#include "storage/MemoryDirectoryReader.hxx"
|
||||
#include "lib/curl/Error.hxx"
|
||||
#include "lib/curl/Init.hxx"
|
||||
#include "lib/curl/Global.hxx"
|
||||
#include "lib/curl/Slist.hxx"
|
||||
@ -34,7 +35,6 @@
|
||||
#include "event/InjectEvent.hxx"
|
||||
#include "thread/Mutex.hxx"
|
||||
#include "thread/Cond.hxx"
|
||||
#include "time/Parser.hxx"
|
||||
#include "util/ASCII.hxx"
|
||||
#include "util/RuntimeError.hxx"
|
||||
#include "util/StringCompare.hxx"
|
||||
@ -171,8 +171,9 @@ struct DavResponse {
|
||||
}
|
||||
};
|
||||
|
||||
[[gnu::pure]]
|
||||
static unsigned
|
||||
ParseStatus(const char *s)
|
||||
ParseStatus(const char *s) noexcept
|
||||
{
|
||||
/* skip the "HTTP/1.1" prefix */
|
||||
const char *space = std::strchr(s, ' ');
|
||||
@ -182,37 +183,37 @@ ParseStatus(const char *s)
|
||||
return strtoul(space + 1, nullptr, 10);
|
||||
}
|
||||
|
||||
[[gnu::pure]]
|
||||
static unsigned
|
||||
ParseStatus(const char *s, size_t length)
|
||||
ParseStatus(const char *s, size_t length) noexcept
|
||||
{
|
||||
return ParseStatus(std::string(s, length).c_str());
|
||||
}
|
||||
|
||||
[[gnu::pure]]
|
||||
static std::chrono::system_clock::time_point
|
||||
ParseTimeStamp(const char *s)
|
||||
ParseTimeStamp(const char *s) noexcept
|
||||
{
|
||||
try {
|
||||
// TODO: make this more robust
|
||||
return ParseTimePoint(s, "%a, %d %b %Y %T");
|
||||
} catch (...) {
|
||||
return std::chrono::system_clock::time_point::min();
|
||||
}
|
||||
return std::chrono::system_clock::from_time_t(curl_getdate(s, nullptr));
|
||||
}
|
||||
|
||||
[[gnu::pure]]
|
||||
static std::chrono::system_clock::time_point
|
||||
ParseTimeStamp(const char *s, size_t length)
|
||||
ParseTimeStamp(const char *s, size_t length) noexcept
|
||||
{
|
||||
return ParseTimeStamp(std::string(s, length).c_str());
|
||||
}
|
||||
|
||||
[[gnu::pure]]
|
||||
static uint64_t
|
||||
ParseU64(const char *s)
|
||||
ParseU64(const char *s) noexcept
|
||||
{
|
||||
return strtoull(s, nullptr, 10);
|
||||
}
|
||||
|
||||
[[gnu::pure]]
|
||||
static uint64_t
|
||||
ParseU64(const char *s, size_t length)
|
||||
ParseU64(const char *s, size_t length) noexcept
|
||||
{
|
||||
return ParseU64(std::string(s, length).c_str());
|
||||
}
|
||||
@ -278,6 +279,7 @@ public:
|
||||
"<a:resourcetype/>"
|
||||
"<a:getcontenttype/>"
|
||||
"<a:getcontentlength/>"
|
||||
"<a:getlastmodified/>"
|
||||
"</a:prop>"
|
||||
"</a:propfind>");
|
||||
}
|
||||
@ -299,8 +301,9 @@ private:
|
||||
/* virtual methods from CurlResponseHandler */
|
||||
void OnHeaders(unsigned status, Curl::Headers &&headers) final {
|
||||
if (status != 207)
|
||||
throw FormatRuntimeError("Status %d from WebDAV server; expected \"207 Multi-Status\"",
|
||||
status);
|
||||
throw HttpStatusError(status,
|
||||
StringFormat<80>("Status %u from WebDAV server; expected \"207 Multi-Status\"",
|
||||
status).c_str());
|
||||
|
||||
if (!IsXmlContentType(headers))
|
||||
throw std::runtime_error("Unexpected Content-Type from WebDAV server");
|
||||
|
@ -70,8 +70,11 @@ FormatLastError(DWORD code, const char *fmt, Args&&... args) noexcept
|
||||
{
|
||||
char buffer[512];
|
||||
const auto end = buffer + sizeof(buffer);
|
||||
size_t length = snprintf(buffer, sizeof(buffer) - 128,
|
||||
constexpr std::size_t max_prefix = sizeof(buffer) - 128;
|
||||
size_t length = snprintf(buffer, max_prefix,
|
||||
fmt, std::forward<Args>(args)...);
|
||||
if (length >= max_prefix)
|
||||
length = max_prefix - 1;
|
||||
char *p = buffer + length;
|
||||
*p++ = ':';
|
||||
*p++ = ' ';
|
||||
|
@ -263,8 +263,14 @@ TagBuilder::RemoveAll() noexcept
|
||||
void
|
||||
TagBuilder::RemoveType(TagType type) noexcept
|
||||
{
|
||||
if (items.empty())
|
||||
/* don't acquire the tag_pool_lock if we're not going
|
||||
to call tag_pool_put_item() anyway */
|
||||
return;
|
||||
|
||||
const auto begin = items.begin(), end = items.end();
|
||||
|
||||
const std::scoped_lock<Mutex> protect(tag_pool_lock);
|
||||
items.erase(std::remove_if(begin, end,
|
||||
[type](TagItem *item) {
|
||||
if (item->type != type)
|
||||
|
@ -1,57 +0,0 @@
|
||||
/*
|
||||
* Copyright 2014-2019 Max Kellermann <max.kellermann@gmail.com>
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* - Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* - Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the
|
||||
* distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
* FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
|
||||
* OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "Parser.hxx"
|
||||
#include "Convert.hxx"
|
||||
|
||||
#include <cassert>
|
||||
#include <stdexcept>
|
||||
|
||||
#include <time.h>
|
||||
|
||||
std::chrono::system_clock::time_point
|
||||
ParseTimePoint(const char *s, const char *format)
|
||||
{
|
||||
assert(s != nullptr);
|
||||
assert(format != nullptr);
|
||||
|
||||
#ifdef _WIN32
|
||||
/* TODO: emulate strptime()? */
|
||||
(void)s;
|
||||
(void)format;
|
||||
throw std::runtime_error("Time parsing not implemented on Windows");
|
||||
#else
|
||||
struct tm tm{};
|
||||
const char *end = strptime(s, format, &tm);
|
||||
if (end == nullptr || *end != 0)
|
||||
throw std::runtime_error("Failed to parse time stamp");
|
||||
|
||||
return TimeGm(tm);
|
||||
#endif /* !_WIN32 */
|
||||
}
|
@ -1,43 +0,0 @@
|
||||
/*
|
||||
* Copyright 2014-2019 Max Kellermann <max.kellermann@gmail.com>
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* - Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* - Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the
|
||||
* distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
* FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
|
||||
* OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef TIME_PARSER_HXX
|
||||
#define TIME_PARSER_HXX
|
||||
|
||||
#include <chrono>
|
||||
|
||||
/**
|
||||
* Parse a time stamp.
|
||||
*
|
||||
* Throws std::runtime_error on error.
|
||||
*/
|
||||
std::chrono::system_clock::time_point
|
||||
ParseTimePoint(const char *s, const char *format);
|
||||
|
||||
#endif
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user