Compare commits
109 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c67372f8af | ||
|
|
00789de7d4 | ||
|
|
5ece9685c2 | ||
|
|
e7c5a42821 | ||
|
|
36e6079c57 | ||
|
|
e5f23678ca | ||
|
|
749ad7cd83 | ||
|
|
0b59f4eaee | ||
|
|
402663de74 | ||
|
|
eaa66c7ee3 | ||
|
|
996714d6ff | ||
|
|
fe48e5596f | ||
|
|
d7744d2b8e | ||
|
|
33ee35ab92 | ||
|
|
5b291ff768 | ||
|
|
39d6816a6d | ||
|
|
6517b2d2ac | ||
|
|
bfdf13dca3 | ||
|
|
daefc61aa4 | ||
|
|
6fed6e50e4 | ||
|
|
bc9e074822 | ||
|
|
8047102542 | ||
|
|
fe5b81e180 | ||
|
|
f032925c2d | ||
|
|
8125a5dddb | ||
|
|
154170e475 | ||
|
|
fb83936feb | ||
|
|
db8bf52f7d | ||
|
|
756f0b8027 | ||
|
|
b1fba8d3d7 | ||
|
|
e606044271 | ||
|
|
bcbb3371ff | ||
|
|
de632882d1 | ||
|
|
745e492d15 | ||
|
|
c5dc615efe | ||
|
|
beeb02025e | ||
|
|
cdf7062597 | ||
|
|
346084da1e | ||
|
|
bbceb5eb91 | ||
|
|
90d85319c2 | ||
|
|
3d03683e7d | ||
|
|
d8a74802d1 | ||
|
|
191919d1b1 | ||
|
|
df38e7565b | ||
|
|
cb49a03fd7 | ||
|
|
faee5bbb78 | ||
|
|
7befab7e83 | ||
|
|
4244e61214 | ||
|
|
46eab05045 | ||
|
|
5ca137c73c | ||
|
|
760238fe16 | ||
|
|
a99b4abae8 | ||
|
|
472881cb95 | ||
|
|
c4efc37ad8 | ||
|
|
691b6a236e | ||
|
|
5c7243d3ad | ||
|
|
44cfdff39a | ||
|
|
5eedda691a | ||
|
|
a30d5e1b6a | ||
|
|
8ef09a0a71 | ||
|
|
e8044663b3 | ||
|
|
8444c33514 | ||
|
|
2b7328b434 | ||
|
|
ca705e1e37 | ||
|
|
d9f9b3df10 | ||
|
|
a43ee97746 | ||
|
|
43c32372e7 | ||
|
|
5716cde1fb | ||
|
|
b7a99b4a4b | ||
|
|
24741c5d06 | ||
|
|
6b3a282db4 | ||
|
|
7583cfe9b7 | ||
|
|
aafc9ce75b | ||
|
|
fea326530b | ||
|
|
8925cc17d8 | ||
|
|
14412c867f | ||
|
|
c5cc256bf2 | ||
|
|
563c7318f9 | ||
|
|
e92129f449 | ||
|
|
374cc51f77 | ||
|
|
1008d5f67c | ||
|
|
8e07ea7ad8 | ||
|
|
d751df0a73 | ||
|
|
2c084781b0 | ||
|
|
ae7d550a01 | ||
|
|
30d97fe8a0 | ||
|
|
5cb0080052 | ||
|
|
8e4ca23727 | ||
|
|
bdc861f058 | ||
|
|
8925040262 | ||
|
|
c065950ced | ||
|
|
257a77fa35 | ||
|
|
4e5d6e560b | ||
|
|
d276d8eda2 | ||
|
|
ebcb5e9368 | ||
|
|
69f09648a4 | ||
|
|
9adda30c38 | ||
|
|
d2d4a0251e | ||
|
|
f7b6431b6f | ||
|
|
03b9bd3a9e | ||
|
|
61aed60f6d | ||
|
|
2cc323c9fe | ||
|
|
f24ab120ee | ||
|
|
68349bc55c | ||
|
|
209364adf2 | ||
|
|
24afdee35c | ||
|
|
7aea285361 | ||
|
|
47a7707df1 | ||
|
|
6fdae1139f |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -6,3 +6,6 @@
|
||||
/output/
|
||||
|
||||
__pycache__/
|
||||
|
||||
/.clangd/
|
||||
/compile_commands.json
|
||||
|
||||
@@ -135,7 +135,8 @@ jobs:
|
||||
- chromaprint
|
||||
- libsamplerate
|
||||
- libsoxr
|
||||
- libzzip
|
||||
# libzzip appears to be broken on Homebrew: "ld: library not found for -lzzip"
|
||||
#- libzzip
|
||||
- flac
|
||||
- opus
|
||||
- libvorbis
|
||||
|
||||
45
NEWS
45
NEWS
@@ -1,3 +1,48 @@
|
||||
ver 0.21.25 (2020/07/06)
|
||||
* protocol:
|
||||
- fix crash when using "rangeid" while playing
|
||||
* database
|
||||
- simple: automatically scan new mounts
|
||||
- upnp: fix compatibility with Plex DLNA
|
||||
* storage
|
||||
- fix disappearing mounts after mounting twice
|
||||
- udisks: fix reading ".mpdignore"
|
||||
* input
|
||||
- file: detect premature end of file
|
||||
- smbclient: don't send credentials to MPD clients
|
||||
* decoder
|
||||
- opus: apply pre-skip and end trimming
|
||||
- opus: fix memory leak
|
||||
- opus: fix crash bug
|
||||
- vorbis: fix crash bug
|
||||
* output
|
||||
- osx: improve sample rate selection
|
||||
- osx: fix noise while stopping
|
||||
* neighbor
|
||||
- upnp: fix crash during shutdown
|
||||
* Windows/Android:
|
||||
- fix Boost detection after breaking change in Meson 0.54
|
||||
|
||||
ver 0.21.24 (2020/06/10)
|
||||
* protocol
|
||||
- "tagtypes" requires no permissions
|
||||
* database
|
||||
- simple: fix crash when mounting twice
|
||||
* decoder
|
||||
- modplug: fix Windows build failure
|
||||
- wildmidi: attempt to detect WildMidi using pkg-config
|
||||
- wildmidi: fix Windows build failure
|
||||
* player
|
||||
- don't restart current song if seeking beyond end
|
||||
* Android
|
||||
- enable the decoder plugins GME, ModPlug and WildMidi
|
||||
- fix build failure with Android NDK r21
|
||||
* Windows
|
||||
- fix stream playback
|
||||
- enable the decoder plugins GME, ModPlug and WildMidi
|
||||
- work around Meson bug breaking the Windows build with GCC 10
|
||||
* fix unit test failure
|
||||
|
||||
ver 0.21.23 (2020/04/23)
|
||||
* protocol
|
||||
- add tag fallback for AlbumSort
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="org.musicpd"
|
||||
android:installLocation="auto"
|
||||
android:versionCode="46"
|
||||
android:versionName="0.21.23">
|
||||
android:versionCode="48"
|
||||
android:versionName="0.21.25">
|
||||
|
||||
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="28"/>
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ android_abis = {
|
||||
'ndk_arch': 'arm',
|
||||
'toolchain_arch': 'arm-linux-androideabi',
|
||||
'llvm_triple': 'armv7-linux-androideabi',
|
||||
'cflags': '-march=armv7-a -mfpu=vfp -mfloat-abi=softfp',
|
||||
'cflags': '-fpic -march=armv7-a -mfpu=vfpv3-d16 -mfloat-abi=softfp',
|
||||
},
|
||||
|
||||
'arm64-v8a': {
|
||||
@@ -34,7 +34,7 @@ android_abis = {
|
||||
'ndk_arch': 'arm64',
|
||||
'toolchain_arch': 'aarch64-linux-android',
|
||||
'llvm_triple': 'aarch64-linux-android',
|
||||
'cflags': '',
|
||||
'cflags': '-fpic',
|
||||
},
|
||||
|
||||
'x86': {
|
||||
@@ -42,7 +42,7 @@ android_abis = {
|
||||
'ndk_arch': 'x86',
|
||||
'toolchain_arch': 'x86',
|
||||
'llvm_triple': 'i686-linux-android',
|
||||
'cflags': '-march=i686 -mtune=intel -mssse3 -mfpmath=sse -m32',
|
||||
'cflags': '-fPIC -march=i686 -mtune=intel -mssse3 -mfpmath=sse -m32',
|
||||
},
|
||||
|
||||
'x86_64': {
|
||||
@@ -50,7 +50,7 @@ android_abis = {
|
||||
'ndk_arch': 'x86_64',
|
||||
'toolchain_arch': 'x86_64',
|
||||
'llvm_triple': 'x86_64-linux-android',
|
||||
'cflags': '-m64',
|
||||
'cflags': '-fPIC -m64',
|
||||
},
|
||||
}
|
||||
|
||||
@@ -97,7 +97,6 @@ class AndroidNdkToolchain:
|
||||
llvm_triple = abi_info['llvm_triple'] + android_api_level
|
||||
|
||||
common_flags = '-Os -g'
|
||||
common_flags += ' -fPIC'
|
||||
common_flags += ' ' + abi_info['cflags']
|
||||
|
||||
toolchain_bin = os.path.join(toolchain_path, 'bin')
|
||||
@@ -169,6 +168,9 @@ thirdparty_libs = [
|
||||
opus,
|
||||
flac,
|
||||
libid3tag,
|
||||
libmodplug,
|
||||
wildmidi,
|
||||
gme,
|
||||
ffmpeg,
|
||||
curl,
|
||||
libexpat,
|
||||
|
||||
@@ -38,7 +38,7 @@ author = 'Max Kellermann'
|
||||
# built documents.
|
||||
#
|
||||
# The short X.Y version.
|
||||
version = '0.21.23'
|
||||
version = '0.21.25'
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = version
|
||||
|
||||
|
||||
@@ -60,25 +60,25 @@ The default plugin which gives :program:`MPD` access to local files. It is used
|
||||
curl
|
||||
----
|
||||
|
||||
A WebDAV client using libcurl. It is used when :code:`music_directory` contains a http:// or https:// URI, for example :samp:`https://the.server/dav/`.
|
||||
A WebDAV client using libcurl. It is used when :code:`music_directory`
|
||||
contains a ``http://`` or ``https://`` URI, for example
|
||||
:samp:`https://the.server/dav/`.
|
||||
|
||||
smbclient
|
||||
---------
|
||||
|
||||
Load music files from a SMB/CIFS server. It is used when :code:`music_directory` contains a smb:// URI, for example :samp:`smb://myfileserver/Music`.
|
||||
Load music files from a SMB/CIFS server. It is used when
|
||||
:code:`music_directory` contains a ``smb://`` URI, for example
|
||||
:samp:`smb://myfileserver/Music`.
|
||||
|
||||
nfs
|
||||
---
|
||||
|
||||
Load music files from a NFS server. It is used when :code:`music_directory` contains a nfs:// URI according to RFC2224, for example :samp:`nfs://servername/path`.
|
||||
Load music files from a NFS server. It is used when
|
||||
:code:`music_directory` contains a ``nfs://`` URI according to
|
||||
RFC2224, for example :samp:`nfs://servername/path`.
|
||||
|
||||
This plugin uses libnfs, which supports only NFS version 3. Since :program:`MPD` is not allowed to bind to "privileged ports", the NFS server needs to enable the "insecure" setting; example :file:`/etc/exports`:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
/srv/mp3 192.168.1.55(ro,insecure)
|
||||
|
||||
Don't fear: "insecure" does not mean that your NFS server is insecure. A few decades ago, people thought the concept of "privileged ports" would make network services "secure", which was a fallacy. The absence of this obsolete "security" measure means little.
|
||||
See :ref:`input_nfs` for more information.
|
||||
|
||||
udisks
|
||||
------
|
||||
@@ -162,7 +162,10 @@ curl
|
||||
|
||||
Opens remote files or streams over HTTP using libcurl.
|
||||
|
||||
Note that unless overridden by the below settings (e.g. by setting them to a blank value), general curl configuration from environment variables such as http_proxy or specified in :file:`~/.curlrc` will be in effect.
|
||||
Note that unless overridden by the below settings (e.g. by setting
|
||||
them to a blank value), general curl configuration from environment
|
||||
variables such as ``http_proxy`` or specified in :file:`~/.curlrc`
|
||||
will be in effect.
|
||||
|
||||
.. list-table::
|
||||
:widths: 20 80
|
||||
@@ -182,7 +185,9 @@ Note that unless overridden by the below settings (e.g. by setting them to a bla
|
||||
ffmpeg
|
||||
------
|
||||
|
||||
Access to various network protocols implemented by the FFmpeg library: gopher://, rtp://, rtsp://, rtmp://, rtmpt://, rtmps://
|
||||
Access to various network protocols implemented by the FFmpeg library:
|
||||
``gopher://``, ``rtp://``, ``rtsp://``, ``rtmp://``, ``rtmpt://``,
|
||||
``rtmps://``
|
||||
|
||||
file
|
||||
----
|
||||
@@ -194,30 +199,51 @@ mms
|
||||
|
||||
Plays streams with the MMS protocol using `libmms <https://launchpad.net/libmms>`_.
|
||||
|
||||
.. _input_nfs:
|
||||
|
||||
nfs
|
||||
---
|
||||
|
||||
Allows :program:`MPD` to access files on NFSv3 servers without actually mounting them (i.e. in userspace, without help from the kernel's VFS layer). All URIs with the nfs:// scheme are used according to RFC2224. Example:
|
||||
Allows :program:`MPD` to access files on NFS servers without actually
|
||||
mounting them (i.e. with :program:`libnfs` in userspace, without help
|
||||
from the kernel's VFS layer). All URIs with the ``nfs://`` scheme are
|
||||
used according to RFC2224. Example:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
mpc add nfs://servername/path/filename.ogg
|
||||
|
||||
Note that this usually requires enabling the "insecure" flag in the server's /etc/exports file, because :program:`MPD` cannot bind to so-called "privileged" ports. Don't fear: this will not make your file server insecure; the flag was named in a time long ago when privileged ports were thought to be meaningful for security. By today's standards, NFSv3 is not secure at all, and if you believe it is, you're already doomed.
|
||||
This plugin uses :program:`libnfs`, which supports only NFS version 3.
|
||||
Since :program:`MPD` is not allowed to bind to so-called "privileged
|
||||
ports", the NFS server needs to enable the ``insecure`` setting;
|
||||
example :file:`/etc/exports`:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
/srv/mp3 192.168.1.55(ro,insecure)
|
||||
|
||||
Don't fear: this will not make your file server insecure; the flag was
|
||||
named a time long ago when privileged ports were thought to be
|
||||
meaningful for security. By today's standards, NFSv3 is not secure at
|
||||
all, and if you believe it is, you're already doomed.
|
||||
|
||||
smbclient
|
||||
---------
|
||||
|
||||
Allows :program:`MPD` to access files on SMB/CIFS servers (e.g. Samba or Microsoft Windows). All URIs with the smb:// scheme are used. Example:
|
||||
Allows :program:`MPD` to access files on SMB/CIFS servers (e.g. Samba
|
||||
or Microsoft Windows). All URIs with the ``smb://`` scheme are
|
||||
used. Example:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
mpc add smb://servername/sharename/filename.ogg
|
||||
mpc add smb://username:password@servername/sharename/filename.ogg
|
||||
|
||||
qobuz
|
||||
-----
|
||||
|
||||
Play songs from the commercial streaming service Qobuz. It plays URLs in the form qobuz://track/ID, e.g.:
|
||||
Play songs from the commercial streaming service Qobuz. It plays URLs
|
||||
in the form ``qobuz://track/ID``, e.g.:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
@@ -243,7 +269,9 @@ Play songs from the commercial streaming service Qobuz. It plays URLs in the for
|
||||
tidal
|
||||
-----
|
||||
|
||||
Play songs from the commercial streaming service `Tidal <http://tidal.com/>`_. It plays URLs in the form tidal://track/ID, e.g.:
|
||||
Play songs from the commercial streaming service `Tidal
|
||||
<http://tidal.com/>`_. It plays URLs in the form ``tidal://track/ID``,
|
||||
e.g.:
|
||||
|
||||
.. warning::
|
||||
|
||||
|
||||
@@ -464,7 +464,8 @@ Querying :program:`MPD`'s status
|
||||
- ``songs``: number of songs
|
||||
- ``uptime``: daemon uptime in seconds
|
||||
- ``db_playtime``: sum of all song times in the database in seconds
|
||||
- ``db_update``: last db update in UNIX time
|
||||
- ``db_update``: last db update in UNIX time (seconds since
|
||||
1970-01-01 UTC)
|
||||
- ``playtime``: time length of music played
|
||||
|
||||
Playback options
|
||||
|
||||
18
meson.build
18
meson.build
@@ -1,11 +1,12 @@
|
||||
project(
|
||||
'mpd',
|
||||
['c', 'cpp'],
|
||||
version: '0.21.23',
|
||||
version: '0.21.25',
|
||||
meson_version: '>= 0.49.0',
|
||||
default_options: [
|
||||
'c_std=c99',
|
||||
'cpp_std=c++14'
|
||||
'cpp_std=c++14',
|
||||
'warning_level=2',
|
||||
],
|
||||
license: 'GPLv2+',
|
||||
)
|
||||
@@ -40,9 +41,6 @@ common_cxxflags = [
|
||||
]
|
||||
|
||||
test_common_flags = [
|
||||
'-Wall',
|
||||
'-Wextra',
|
||||
|
||||
'-fvisibility=hidden',
|
||||
|
||||
'-ffast-math',
|
||||
@@ -142,7 +140,13 @@ conf.set('HAVE_GETPWNAM_R', compiler.has_function('getpwnam_r'))
|
||||
conf.set('HAVE_GETPWUID_R', compiler.has_function('getpwuid_r'))
|
||||
conf.set('HAVE_INITGROUPS', compiler.has_function('initgroups'))
|
||||
conf.set('HAVE_FNMATCH', compiler.has_function('fnmatch'))
|
||||
conf.set('HAVE_STRNDUP', compiler.has_function('strndup', prefix: '#define _GNU_SOURCE\n#include <string.h>'))
|
||||
|
||||
# Explicitly exclude Windows in this check because
|
||||
# https://github.com/mesonbuild/meson/issues/3672 (reported in 2018,
|
||||
# still not fixed in 2020) causes Meson to believe it exists, because
|
||||
# __builtin_strndup() exists (but strndup() still cannot be used).
|
||||
conf.set('HAVE_STRNDUP', not is_windows and compiler.has_function('strndup', prefix: '#define _GNU_SOURCE\n#include <string.h>'))
|
||||
|
||||
conf.set('HAVE_STRCASESTR', compiler.has_function('strcasestr'))
|
||||
|
||||
conf.set('HAVE_PRCTL', is_linux)
|
||||
@@ -315,6 +319,8 @@ subdir('src/thread')
|
||||
subdir('src/net')
|
||||
subdir('src/event')
|
||||
|
||||
subdir('src/apple')
|
||||
|
||||
subdir('src/lib/dbus')
|
||||
subdir('src/lib/icu')
|
||||
subdir('src/lib/smbclient')
|
||||
|
||||
45
python/build/cmake.py
Normal file
45
python/build/cmake.py
Normal file
@@ -0,0 +1,45 @@
|
||||
import subprocess
|
||||
|
||||
from build.project import Project
|
||||
|
||||
def configure(toolchain, src, build, args=()):
|
||||
cross_args = []
|
||||
|
||||
if toolchain.is_windows:
|
||||
cross_args.append('-DCMAKE_SYSTEM_NAME=Windows')
|
||||
cross_args.append('-DCMAKE_RC_COMPILER=' + toolchain.windres)
|
||||
|
||||
configure = [
|
||||
'cmake',
|
||||
src,
|
||||
|
||||
'-DCMAKE_INSTALL_PREFIX=' + toolchain.install_prefix,
|
||||
'-DCMAKE_BUILD_TYPE=release',
|
||||
|
||||
'-DCMAKE_C_COMPILER=' + toolchain.cc,
|
||||
'-DCMAKE_CXX_COMPILER=' + toolchain.cxx,
|
||||
|
||||
'-DCMAKE_C_FLAGS=' + toolchain.cflags + ' ' + toolchain.cppflags,
|
||||
'-DCMAKE_CXX_FLAGS=' + toolchain.cxxflags + ' ' + toolchain.cppflags,
|
||||
|
||||
'-GNinja',
|
||||
] + cross_args + args
|
||||
|
||||
subprocess.check_call(configure, env=toolchain.env, cwd=build)
|
||||
|
||||
class CmakeProject(Project):
|
||||
def __init__(self, url, md5, installed, configure_args=[],
|
||||
**kwargs):
|
||||
Project.__init__(self, url, md5, installed, **kwargs)
|
||||
self.configure_args = configure_args
|
||||
|
||||
def configure(self, toolchain):
|
||||
src = self.unpack(toolchain)
|
||||
build = self.make_build_path(toolchain)
|
||||
configure(toolchain, src, build, self.configure_args)
|
||||
return build
|
||||
|
||||
def build(self, toolchain):
|
||||
build = self.configure(toolchain)
|
||||
subprocess.check_call(['ninja', 'install'],
|
||||
cwd=build, env=toolchain.env)
|
||||
@@ -4,6 +4,7 @@ from os.path import abspath
|
||||
from build.project import Project
|
||||
from build.zlib import ZlibProject
|
||||
from build.meson import MesonProject
|
||||
from build.cmake import CmakeProject
|
||||
from build.autotools import AutotoolsProject
|
||||
from build.ffmpeg import FfmpegProject
|
||||
from build.boost import BoostProject
|
||||
@@ -111,9 +112,44 @@ liblame = AutotoolsProject(
|
||||
],
|
||||
)
|
||||
|
||||
libmodplug = AutotoolsProject(
|
||||
'https://downloads.sourceforge.net/modplug-xmms/libmodplug/0.8.9.0/libmodplug-0.8.9.0.tar.gz',
|
||||
'457ca5a6c179656d66c01505c0d95fafaead4329b9dbaa0f997d00a3508ad9de',
|
||||
'lib/libmodplug.a',
|
||||
[
|
||||
'--disable-shared', '--enable-static',
|
||||
],
|
||||
)
|
||||
|
||||
wildmidi = CmakeProject(
|
||||
'https://codeload.github.com/Mindwerks/wildmidi/tar.gz/wildmidi-0.4.3',
|
||||
'498e5a96455bb4b91b37188ad6dcb070824e92c44f5ed452b90adbaec8eef3c5',
|
||||
'lib/libWildMidi.a',
|
||||
[
|
||||
'-DBUILD_SHARED_LIBS=OFF',
|
||||
'-DWANT_PLAYER=OFF',
|
||||
'-DWANT_STATIC=ON',
|
||||
],
|
||||
base='wildmidi-wildmidi-0.4.3',
|
||||
name='wildmidi',
|
||||
version='0.4.3',
|
||||
)
|
||||
|
||||
gme = CmakeProject(
|
||||
'https://bitbucket.org/mpyne/game-music-emu/downloads/game-music-emu-0.6.3.tar.xz',
|
||||
'aba34e53ef0ec6a34b58b84e28bf8cfbccee6585cebca25333604c35db3e051d',
|
||||
'lib/libgme.a',
|
||||
[
|
||||
'-DBUILD_SHARED_LIBS=OFF',
|
||||
'-DENABLE_UBSAN=OFF',
|
||||
'-DZLIB_INCLUDE_DIR=OFF',
|
||||
'-DSDL2_DIR=OFF',
|
||||
],
|
||||
)
|
||||
|
||||
ffmpeg = FfmpegProject(
|
||||
'http://ffmpeg.org/releases/ffmpeg-4.2.2.tar.xz',
|
||||
'cb754255ab0ee2ea5f66f8850e1bd6ad5cac1cd855d0a2f4990fb8c668b0d29c',
|
||||
'http://ffmpeg.org/releases/ffmpeg-4.2.3.tar.xz',
|
||||
'9df6c90aed1337634c1fb026fb01c154c29c82a64ea71291ff2da9aacb9aad31',
|
||||
'lib/libavcodec.a',
|
||||
[
|
||||
'--disable-shared', '--enable-static',
|
||||
@@ -341,8 +377,8 @@ ffmpeg = FfmpegProject(
|
||||
)
|
||||
|
||||
curl = AutotoolsProject(
|
||||
'http://curl.haxx.se/download/curl-7.69.1.tar.xz',
|
||||
'03c7d5e6697f7b7e40ada1b2256e565a555657398e6c1fcfa4cb251ccd819d4f',
|
||||
'http://curl.haxx.se/download/curl-7.70.0.tar.xz',
|
||||
'032f43f2674008c761af19bf536374128c16241fb234699a55f9fb603fcfbae7',
|
||||
'lib/libcurl.a',
|
||||
[
|
||||
'--disable-shared', '--enable-static',
|
||||
@@ -397,7 +433,7 @@ libnfs = AutotoolsProject(
|
||||
)
|
||||
|
||||
boost = BoostProject(
|
||||
'https://dl.bintray.com/boostorg/release/1.72.0/source/boost_1_72_0.tar.bz2',
|
||||
'59c9b274bc451cf91a9ba1dd2c7fdcaf5d60b1b3aa83f2c9fa143417cc660722',
|
||||
'https://dl.bintray.com/boostorg/release/1.73.0/source/boost_1_73_0.tar.bz2',
|
||||
'4eb3b8d442b426dc35346235c8733b5ae35ba431690e38c6a8263dce9fcbb402',
|
||||
'include/boost/version.hpp',
|
||||
)
|
||||
|
||||
@@ -91,7 +91,12 @@ def configure(toolchain, src, build, args=()):
|
||||
'--cross-file', cross_file,
|
||||
] + args
|
||||
|
||||
subprocess.check_call(configure, env=toolchain.env)
|
||||
env = toolchain.env.copy()
|
||||
|
||||
# Meson 0.54 requires the BOOST_ROOT environment variable
|
||||
env['BOOST_ROOT'] = toolchain.install_prefix
|
||||
|
||||
subprocess.check_call(configure, env=env)
|
||||
|
||||
class MesonProject(Project):
|
||||
def __init__(self, url, md5, installed, configure_args=[],
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
#ifndef MPD_AUDIO_FORMAT_HXX
|
||||
#define MPD_AUDIO_FORMAT_HXX
|
||||
|
||||
#include "pcm/SampleFormat.hxx"
|
||||
#include "pcm/SampleFormat.hxx" // IWYU pragma: export
|
||||
#include "util/Compiler.h"
|
||||
|
||||
#include <chrono>
|
||||
|
||||
82
src/Log.cxx
82
src/Log.cxx
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2003-2018 The Music Player Daemon Project
|
||||
* Copyright 2003-2019 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
@@ -19,8 +19,7 @@
|
||||
|
||||
#include "LogV.hxx"
|
||||
#include "util/Domain.hxx"
|
||||
|
||||
#include <exception>
|
||||
#include "util/Exception.hxx"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
@@ -29,20 +28,20 @@
|
||||
static constexpr Domain exception_domain("exception");
|
||||
|
||||
void
|
||||
LogFormatV(const Domain &domain, LogLevel level,
|
||||
LogFormatV(LogLevel level, const Domain &domain,
|
||||
const char *fmt, va_list ap) noexcept
|
||||
{
|
||||
char msg[1024];
|
||||
vsnprintf(msg, sizeof(msg), fmt, ap);
|
||||
Log(domain, level, msg);
|
||||
Log(level, domain, msg);
|
||||
}
|
||||
|
||||
void
|
||||
LogFormat(const Domain &domain, LogLevel level, const char *fmt, ...) noexcept
|
||||
LogFormat(LogLevel level, const Domain &domain, const char *fmt, ...) noexcept
|
||||
{
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
LogFormatV(domain, level, fmt, ap);
|
||||
LogFormatV(level, domain, fmt, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
@@ -51,7 +50,7 @@ FormatDebug(const Domain &domain, const char *fmt, ...) noexcept
|
||||
{
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
LogFormatV(domain, LogLevel::DEBUG, fmt, ap);
|
||||
LogFormatV(LogLevel::DEBUG, domain, fmt, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
@@ -60,7 +59,7 @@ FormatInfo(const Domain &domain, const char *fmt, ...) noexcept
|
||||
{
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
LogFormatV(domain, LogLevel::INFO, fmt, ap);
|
||||
LogFormatV(LogLevel::INFO, domain, fmt, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
@@ -69,7 +68,7 @@ FormatDefault(const Domain &domain, const char *fmt, ...) noexcept
|
||||
{
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
LogFormatV(domain, LogLevel::DEFAULT, fmt, ap);
|
||||
LogFormatV(LogLevel::DEFAULT, domain, fmt, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
@@ -78,7 +77,7 @@ FormatWarning(const Domain &domain, const char *fmt, ...) noexcept
|
||||
{
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
LogFormatV(domain, LogLevel::WARNING, fmt, ap);
|
||||
LogFormatV(LogLevel::WARNING, domain, fmt, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
@@ -87,42 +86,24 @@ FormatError(const Domain &domain, const char *fmt, ...) noexcept
|
||||
{
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
LogFormatV(domain, LogLevel::ERROR, fmt, ap);
|
||||
LogFormatV(LogLevel::ERROR, domain, fmt, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
void
|
||||
LogError(const std::exception &e) noexcept
|
||||
Log(LogLevel level, const std::exception &e) noexcept
|
||||
{
|
||||
Log(exception_domain, LogLevel::ERROR, e.what());
|
||||
|
||||
try {
|
||||
std::rethrow_if_nested(e);
|
||||
} catch (const std::exception &nested) {
|
||||
LogError(nested, "nested");
|
||||
} catch (...) {
|
||||
Log(exception_domain, LogLevel::ERROR,
|
||||
"Unrecognized nested exception");
|
||||
}
|
||||
Log(level, exception_domain, GetFullMessage(e).c_str());
|
||||
}
|
||||
|
||||
void
|
||||
LogError(const std::exception &e, const char *msg) noexcept
|
||||
Log(LogLevel level, const std::exception &e, const char *msg) noexcept
|
||||
{
|
||||
FormatError(exception_domain, "%s: %s", msg, e.what());
|
||||
|
||||
try {
|
||||
std::rethrow_if_nested(e);
|
||||
} catch (const std::exception &nested) {
|
||||
LogError(nested);
|
||||
} catch (...) {
|
||||
Log(exception_domain, LogLevel::ERROR,
|
||||
"Unrecognized nested exception");
|
||||
}
|
||||
LogFormat(level, exception_domain, "%s: %s", msg, GetFullMessage(e).c_str());
|
||||
}
|
||||
|
||||
void
|
||||
FormatError(const std::exception &e, const char *fmt, ...) noexcept
|
||||
LogFormat(LogLevel level, const std::exception &e, const char *fmt, ...) noexcept
|
||||
{
|
||||
char msg[1024];
|
||||
va_list ap;
|
||||
@@ -130,37 +111,24 @@ FormatError(const std::exception &e, const char *fmt, ...) noexcept
|
||||
vsnprintf(msg, sizeof(msg), fmt, ap);
|
||||
va_end(ap);
|
||||
|
||||
LogError(e, msg);
|
||||
Log(level, e, msg);
|
||||
}
|
||||
|
||||
void
|
||||
LogError(const std::exception_ptr &ep) noexcept
|
||||
Log(LogLevel level, const std::exception_ptr &ep) noexcept
|
||||
{
|
||||
try {
|
||||
std::rethrow_exception(ep);
|
||||
} catch (const std::exception &e) {
|
||||
LogError(e);
|
||||
} catch (...) {
|
||||
Log(exception_domain, LogLevel::ERROR,
|
||||
"Unrecognized exception");
|
||||
}
|
||||
Log(level, exception_domain, GetFullMessage(ep).c_str());
|
||||
}
|
||||
|
||||
void
|
||||
LogError(const std::exception_ptr &ep, const char *msg) noexcept
|
||||
Log(LogLevel level, const std::exception_ptr &ep, const char *msg) noexcept
|
||||
{
|
||||
try {
|
||||
std::rethrow_exception(ep);
|
||||
} catch (const std::exception &e) {
|
||||
LogError(e, msg);
|
||||
} catch (...) {
|
||||
FormatError(exception_domain,
|
||||
"%s: Unrecognized exception", msg);
|
||||
}
|
||||
LogFormat(level, exception_domain, "%s: %s", msg,
|
||||
GetFullMessage(ep).c_str());
|
||||
}
|
||||
|
||||
void
|
||||
FormatError(const std::exception_ptr &ep, const char *fmt, ...) noexcept
|
||||
LogFormat(LogLevel level, const std::exception_ptr &ep, const char *fmt, ...) noexcept
|
||||
{
|
||||
char msg[1024];
|
||||
va_list ap;
|
||||
@@ -168,13 +136,13 @@ FormatError(const std::exception_ptr &ep, const char *fmt, ...) noexcept
|
||||
vsnprintf(msg, sizeof(msg), fmt, ap);
|
||||
va_end(ap);
|
||||
|
||||
LogError(ep, msg);
|
||||
Log(level, ep, msg);
|
||||
}
|
||||
|
||||
void
|
||||
LogErrno(const Domain &domain, int e, const char *msg) noexcept
|
||||
{
|
||||
LogFormat(domain, LogLevel::ERROR, "%s: %s", msg, strerror(e));
|
||||
LogFormat(LogLevel::ERROR, domain, "%s: %s", msg, strerror(e));
|
||||
}
|
||||
|
||||
void
|
||||
|
||||
85
src/Log.hxx
85
src/Log.hxx
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2003-2018 The Music Player Daemon Project
|
||||
* Copyright 2003-2019 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
@@ -28,16 +28,38 @@
|
||||
class Domain;
|
||||
|
||||
void
|
||||
Log(const Domain &domain, LogLevel level, const char *msg) noexcept;
|
||||
Log(LogLevel level, const Domain &domain, const char *msg) noexcept;
|
||||
|
||||
gcc_printf(3,4)
|
||||
void
|
||||
LogFormat(const Domain &domain, LogLevel level, const char *fmt, ...) noexcept;
|
||||
LogFormat(LogLevel level, const Domain &domain, const char *fmt, ...) noexcept;
|
||||
|
||||
void
|
||||
Log(LogLevel level, const std::exception &e) noexcept;
|
||||
|
||||
void
|
||||
Log(LogLevel level, const std::exception &e, const char *msg) noexcept;
|
||||
|
||||
gcc_printf(3,4)
|
||||
void
|
||||
LogFormat(LogLevel level, const std::exception &e,
|
||||
const char *fmt, ...) noexcept;
|
||||
|
||||
void
|
||||
Log(LogLevel level, const std::exception_ptr &ep) noexcept;
|
||||
|
||||
void
|
||||
Log(LogLevel level, const std::exception_ptr &ep, const char *msg) noexcept;
|
||||
|
||||
gcc_printf(3,4)
|
||||
void
|
||||
LogFormat(LogLevel level, const std::exception_ptr &ep,
|
||||
const char *fmt, ...) noexcept;
|
||||
|
||||
static inline void
|
||||
LogDebug(const Domain &domain, const char *msg) noexcept
|
||||
{
|
||||
Log(domain, LogLevel::DEBUG, msg);
|
||||
Log(LogLevel::DEBUG, domain, msg);
|
||||
}
|
||||
|
||||
gcc_printf(2,3)
|
||||
@@ -47,7 +69,7 @@ FormatDebug(const Domain &domain, const char *fmt, ...) noexcept;
|
||||
static inline void
|
||||
LogInfo(const Domain &domain, const char *msg) noexcept
|
||||
{
|
||||
Log(domain, LogLevel::INFO, msg);
|
||||
Log(LogLevel::INFO, domain, msg);
|
||||
}
|
||||
|
||||
gcc_printf(2,3)
|
||||
@@ -57,7 +79,7 @@ FormatInfo(const Domain &domain, const char *fmt, ...) noexcept;
|
||||
static inline void
|
||||
LogDefault(const Domain &domain, const char *msg) noexcept
|
||||
{
|
||||
Log(domain, LogLevel::DEFAULT, msg);
|
||||
Log(LogLevel::DEFAULT, domain, msg);
|
||||
}
|
||||
|
||||
gcc_printf(2,3)
|
||||
@@ -67,7 +89,7 @@ FormatDefault(const Domain &domain, const char *fmt, ...) noexcept;
|
||||
static inline void
|
||||
LogWarning(const Domain &domain, const char *msg) noexcept
|
||||
{
|
||||
Log(domain, LogLevel::WARNING, msg);
|
||||
Log(LogLevel::WARNING, domain, msg);
|
||||
}
|
||||
|
||||
gcc_printf(2,3)
|
||||
@@ -77,28 +99,47 @@ FormatWarning(const Domain &domain, const char *fmt, ...) noexcept;
|
||||
static inline void
|
||||
LogError(const Domain &domain, const char *msg) noexcept
|
||||
{
|
||||
Log(domain, LogLevel::ERROR, msg);
|
||||
Log(LogLevel::ERROR, domain, msg);
|
||||
}
|
||||
|
||||
void
|
||||
LogError(const std::exception &e) noexcept;
|
||||
inline void
|
||||
LogError(const std::exception &e) noexcept
|
||||
{
|
||||
Log(LogLevel::ERROR, e);
|
||||
}
|
||||
|
||||
void
|
||||
LogError(const std::exception &e, const char *msg) noexcept;
|
||||
inline void
|
||||
LogError(const std::exception &e, const char *msg) noexcept
|
||||
{
|
||||
Log(LogLevel::ERROR, e, msg);
|
||||
}
|
||||
|
||||
gcc_printf(2,3)
|
||||
void
|
||||
FormatError(const std::exception &e, const char *fmt, ...) noexcept;
|
||||
template<typename... Args>
|
||||
inline void
|
||||
FormatError(const std::exception &e, const char *fmt, Args&&... args) noexcept
|
||||
{
|
||||
LogFormat(LogLevel::ERROR, e, fmt, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
void
|
||||
LogError(const std::exception_ptr &ep) noexcept;
|
||||
inline void
|
||||
LogError(const std::exception_ptr &ep) noexcept
|
||||
{
|
||||
Log(LogLevel::ERROR, ep);
|
||||
}
|
||||
|
||||
void
|
||||
LogError(const std::exception_ptr &ep, const char *msg) noexcept;
|
||||
inline void
|
||||
LogError(const std::exception_ptr &ep, const char *msg) noexcept
|
||||
{
|
||||
Log(LogLevel::ERROR, ep, msg);
|
||||
}
|
||||
|
||||
gcc_printf(2,3)
|
||||
void
|
||||
FormatError(const std::exception_ptr &ep, const char *fmt, ...) noexcept;
|
||||
template<typename... Args>
|
||||
inline void
|
||||
FormatError(const std::exception_ptr &ep,
|
||||
const char *fmt, Args&&... args) noexcept
|
||||
{
|
||||
LogFormat(LogLevel::ERROR, ep, fmt, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
gcc_printf(2,3)
|
||||
void
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2003-2018 The Music Player Daemon Project
|
||||
* Copyright 2003-2019 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
@@ -176,7 +176,7 @@ FileLog(const Domain &domain, const char *message) noexcept
|
||||
#endif /* !ANDROID */
|
||||
|
||||
void
|
||||
Log(const Domain &domain, LogLevel level, const char *msg) noexcept
|
||||
Log(LogLevel level, const Domain &domain, const char *msg) noexcept
|
||||
{
|
||||
#ifdef ANDROID
|
||||
__android_log_print(ToAndroidLogLevel(level), "MPD",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2003-2018 The Music Player Daemon Project
|
||||
* Copyright 2003-2019 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2003-2018 The Music Player Daemon Project
|
||||
* Copyright 2003-2019 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
@@ -25,7 +25,7 @@
|
||||
#include <stdarg.h>
|
||||
|
||||
void
|
||||
LogFormatV(const Domain &domain, LogLevel level,
|
||||
LogFormatV(LogLevel level, const Domain &domain,
|
||||
const char *fmt, va_list ap) noexcept;
|
||||
|
||||
#endif /* LOG_H */
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2003-2018 The Music Player Daemon Project
|
||||
* Copyright 2003-2020 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
@@ -27,10 +27,20 @@
|
||||
* that this plugin is unavailable. It will be disabled, and MPD can
|
||||
* continue initialization.
|
||||
*/
|
||||
class PluginUnavailable final : public std::runtime_error {
|
||||
class PluginUnavailable : public std::runtime_error {
|
||||
public:
|
||||
explicit PluginUnavailable(const char *msg)
|
||||
:std::runtime_error(msg) {}
|
||||
using std::runtime_error::runtime_error;
|
||||
};
|
||||
|
||||
/**
|
||||
* Like #PluginUnavailable, but denotes that the plugin is not
|
||||
* available because it was not explicitly enabled in the
|
||||
* configuration. The message may describe the necessary steps to
|
||||
* enable it.
|
||||
*/
|
||||
class PluginUnconfigured : public PluginUnavailable {
|
||||
public:
|
||||
using PluginUnavailable::PluginUnavailable;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -79,17 +79,22 @@ Song::UpdateFile(Storage &storage) noexcept
|
||||
TagBuilder tag_builder;
|
||||
auto new_audio_format = AudioFormat::Undefined();
|
||||
|
||||
const auto path_fs = storage.MapFS(relative_uri.c_str());
|
||||
if (path_fs.IsNull()) {
|
||||
const auto absolute_uri =
|
||||
storage.MapUTF8(relative_uri.c_str());
|
||||
if (!tag_stream_scan(absolute_uri.c_str(), tag_builder,
|
||||
&new_audio_format))
|
||||
return false;
|
||||
} else {
|
||||
if (!ScanFileTagsWithGeneric(path_fs, tag_builder,
|
||||
try {
|
||||
const auto path_fs = storage.MapFS(relative_uri.c_str());
|
||||
if (path_fs.IsNull()) {
|
||||
const auto absolute_uri =
|
||||
storage.MapUTF8(relative_uri.c_str());
|
||||
if (!tag_stream_scan(absolute_uri.c_str(), tag_builder,
|
||||
&new_audio_format))
|
||||
return false;
|
||||
return false;
|
||||
} else {
|
||||
if (!ScanFileTagsWithGeneric(path_fs, tag_builder,
|
||||
&new_audio_format))
|
||||
return false;
|
||||
}
|
||||
} catch (...) {
|
||||
// TODO: log or propagate I/O errors?
|
||||
return false;
|
||||
}
|
||||
|
||||
mtime = info.mtime;
|
||||
@@ -153,8 +158,14 @@ DetachedSong::LoadFile(Path path) noexcept
|
||||
return false;
|
||||
|
||||
TagBuilder tag_builder;
|
||||
if (!ScanFileTagsWithGeneric(path, tag_builder))
|
||||
|
||||
try {
|
||||
if (!ScanFileTagsWithGeneric(path, tag_builder))
|
||||
return false;
|
||||
} catch (...) {
|
||||
// TODO: log or propagate I/O errors?
|
||||
return false;
|
||||
}
|
||||
|
||||
mtime = fi.GetModificationTime();
|
||||
tag_builder.Commit(tag);
|
||||
@@ -173,8 +184,14 @@ DetachedSong::Update() noexcept
|
||||
return LoadFile(path_fs);
|
||||
} else if (IsRemote()) {
|
||||
TagBuilder tag_builder;
|
||||
if (!tag_stream_scan(uri.c_str(), tag_builder))
|
||||
|
||||
try {
|
||||
if (!tag_stream_scan(uri.c_str(), tag_builder))
|
||||
return false;
|
||||
} catch (...) {
|
||||
// TODO: log or propagate I/O errors?
|
||||
return false;
|
||||
}
|
||||
|
||||
mtime = std::chrono::system_clock::time_point::min();
|
||||
tag_builder.Commit(tag);
|
||||
|
||||
@@ -47,11 +47,11 @@ public:
|
||||
handler(_handler),
|
||||
is(nullptr) {}
|
||||
|
||||
bool ScanFile(const DecoderPlugin &plugin) noexcept {
|
||||
bool ScanFile(const DecoderPlugin &plugin) {
|
||||
return plugin.ScanFile(path_fs, handler);
|
||||
}
|
||||
|
||||
bool ScanStream(const DecoderPlugin &plugin) noexcept {
|
||||
bool ScanStream(const DecoderPlugin &plugin) {
|
||||
if (plugin.scan_stream == nullptr)
|
||||
return false;
|
||||
|
||||
@@ -73,14 +73,14 @@ public:
|
||||
return plugin.ScanStream(*is, handler);
|
||||
}
|
||||
|
||||
bool Scan(const DecoderPlugin &plugin) noexcept {
|
||||
bool Scan(const DecoderPlugin &plugin) {
|
||||
return plugin.SupportsSuffix(suffix) &&
|
||||
(ScanFile(plugin) || ScanStream(plugin));
|
||||
}
|
||||
};
|
||||
|
||||
bool
|
||||
ScanFileTagsNoGeneric(Path path_fs, TagHandler &handler) noexcept
|
||||
ScanFileTagsNoGeneric(Path path_fs, TagHandler &handler)
|
||||
{
|
||||
assert(!path_fs.IsNull());
|
||||
|
||||
@@ -100,7 +100,7 @@ ScanFileTagsNoGeneric(Path path_fs, TagHandler &handler) noexcept
|
||||
|
||||
bool
|
||||
ScanFileTagsWithGeneric(Path path, TagBuilder &builder,
|
||||
AudioFormat *audio_format) noexcept
|
||||
AudioFormat *audio_format)
|
||||
{
|
||||
FullTagHandler h(builder, audio_format);
|
||||
|
||||
|
||||
@@ -30,22 +30,26 @@ class TagBuilder;
|
||||
* but does not fall back to generic scanners (APE and ID3) if no tags
|
||||
* were found (but the file was recognized).
|
||||
*
|
||||
* Throws on I/O error.
|
||||
*
|
||||
* @return true if the file was recognized (even if no metadata was
|
||||
* found)
|
||||
*/
|
||||
bool
|
||||
ScanFileTagsNoGeneric(Path path, TagHandler &handler) noexcept;
|
||||
ScanFileTagsNoGeneric(Path path, TagHandler &handler);
|
||||
|
||||
/**
|
||||
* Scan the tags of a song file. Invokes matching decoder plugins,
|
||||
* and falls back to generic scanners (APE and ID3) if no tags were
|
||||
* found (but the file was recognized).
|
||||
*
|
||||
* Throws on I/O error.
|
||||
*
|
||||
* @return true if the file was recognized (even if no metadata was
|
||||
* found)
|
||||
*/
|
||||
bool
|
||||
ScanFileTagsWithGeneric(Path path, TagBuilder &builder,
|
||||
AudioFormat *audio_format=nullptr) noexcept;
|
||||
AudioFormat *audio_format=nullptr);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -45,7 +45,7 @@ CheckDecoderPlugin(const DecoderPlugin &plugin,
|
||||
}
|
||||
|
||||
bool
|
||||
tag_stream_scan(InputStream &is, TagHandler &handler) noexcept
|
||||
tag_stream_scan(InputStream &is, TagHandler &handler)
|
||||
{
|
||||
assert(is.IsReady());
|
||||
|
||||
@@ -73,19 +73,17 @@ tag_stream_scan(InputStream &is, TagHandler &handler) noexcept
|
||||
}
|
||||
|
||||
bool
|
||||
tag_stream_scan(const char *uri, TagHandler &handler) noexcept
|
||||
try {
|
||||
tag_stream_scan(const char *uri, TagHandler &handler)
|
||||
{
|
||||
Mutex mutex;
|
||||
|
||||
auto is = InputStream::OpenReady(uri, mutex);
|
||||
return tag_stream_scan(*is, handler);
|
||||
} catch (const std::exception &e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool
|
||||
tag_stream_scan(InputStream &is, TagBuilder &builder,
|
||||
AudioFormat *audio_format) noexcept
|
||||
AudioFormat *audio_format)
|
||||
{
|
||||
assert(is.IsReady());
|
||||
|
||||
@@ -102,12 +100,10 @@ tag_stream_scan(InputStream &is, TagBuilder &builder,
|
||||
|
||||
bool
|
||||
tag_stream_scan(const char *uri, TagBuilder &builder,
|
||||
AudioFormat *audio_format) noexcept
|
||||
try {
|
||||
AudioFormat *audio_format)
|
||||
{
|
||||
Mutex mutex;
|
||||
|
||||
auto is = InputStream::OpenReady(uri, mutex);
|
||||
return tag_stream_scan(*is, builder, audio_format);
|
||||
} catch (const std::exception &e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -29,29 +29,39 @@ class TagBuilder;
|
||||
* Scan the tags of an #InputStream. Invokes matching decoder
|
||||
* plugins, but does not invoke the special "APE" and "ID3" scanners.
|
||||
*
|
||||
* Throws on I/O error.
|
||||
*
|
||||
* @return true if the file was recognized (even if no metadata was
|
||||
* found)
|
||||
*/
|
||||
bool
|
||||
tag_stream_scan(InputStream &is, TagHandler &handler) noexcept;
|
||||
tag_stream_scan(InputStream &is, TagHandler &handler);
|
||||
|
||||
/**
|
||||
* Throws on I/O error.
|
||||
*/
|
||||
bool
|
||||
tag_stream_scan(const char *uri, TagHandler &handler) noexcept;
|
||||
tag_stream_scan(const char *uri, TagHandler &handler);
|
||||
|
||||
/**
|
||||
* Scan the tags of an #InputStream. Invokes matching decoder
|
||||
* plugins, and falls back to generic scanners (APE and ID3) if no
|
||||
* tags were found (but the file was recognized).
|
||||
*
|
||||
* Throws on I/O error.
|
||||
*
|
||||
* @return true if the file was recognized (even if no metadata was
|
||||
* found)
|
||||
*/
|
||||
bool
|
||||
tag_stream_scan(InputStream &is, TagBuilder &builder,
|
||||
AudioFormat *audio_format=nullptr) noexcept;
|
||||
AudioFormat *audio_format=nullptr);
|
||||
|
||||
/**
|
||||
* Throws on I/O error.
|
||||
*/
|
||||
bool
|
||||
tag_stream_scan(const char *uri, TagBuilder &builder,
|
||||
AudioFormat *audio_format=nullptr) noexcept;
|
||||
AudioFormat *audio_format=nullptr);
|
||||
|
||||
#endif
|
||||
|
||||
40
src/apple/AudioObject.cxx
Normal file
40
src/apple/AudioObject.cxx
Normal file
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright 2020 Max Kellermann <max.kellermann@gmail.com>
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* - Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* - Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the
|
||||
* distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
* FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
|
||||
* OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "AudioObject.hxx"
|
||||
#include "StringRef.hxx"
|
||||
|
||||
Apple::StringRef
|
||||
AudioObjectGetStringProperty(AudioObjectID inObjectID,
|
||||
const AudioObjectPropertyAddress &inAddress)
|
||||
{
|
||||
auto s = AudioObjectGetPropertyDataT<CFStringRef>(inObjectID,
|
||||
inAddress);
|
||||
return Apple::StringRef(s);
|
||||
}
|
||||
105
src/apple/AudioObject.hxx
Normal file
105
src/apple/AudioObject.hxx
Normal file
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
* Copyright 2020 Max Kellermann <max.kellermann@gmail.com>
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* - Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* - Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the
|
||||
* distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
* FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
|
||||
* OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef APPLE_AUDIO_OBJECT_HXX
|
||||
#define APPLE_AUDIO_OBJECT_HXX
|
||||
|
||||
#include "Throw.hxx"
|
||||
#include "util/AllocatedArray.hxx"
|
||||
|
||||
#include <CoreAudio/AudioHardware.h>
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
namespace Apple {
|
||||
class StringRef;
|
||||
}
|
||||
|
||||
inline std::size_t
|
||||
AudioObjectGetPropertyDataSize(AudioObjectID inObjectID,
|
||||
const AudioObjectPropertyAddress &inAddress)
|
||||
{
|
||||
UInt32 size;
|
||||
OSStatus status = AudioObjectGetPropertyDataSize(inObjectID,
|
||||
&inAddress,
|
||||
0, nullptr, &size);
|
||||
if (status != noErr)
|
||||
Apple::ThrowOSStatus(status);
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
T
|
||||
AudioObjectGetPropertyDataT(AudioObjectID inObjectID,
|
||||
const AudioObjectPropertyAddress &inAddress)
|
||||
{
|
||||
OSStatus status;
|
||||
UInt32 size = sizeof(T);
|
||||
T value;
|
||||
|
||||
status = AudioObjectGetPropertyData(inObjectID, &inAddress,
|
||||
0, nullptr,
|
||||
&size, &value);
|
||||
if (status != noErr)
|
||||
Apple::ThrowOSStatus(status);
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
Apple::StringRef
|
||||
AudioObjectGetStringProperty(AudioObjectID inObjectID,
|
||||
const AudioObjectPropertyAddress &inAddress);
|
||||
|
||||
template<typename T>
|
||||
AllocatedArray<T>
|
||||
AudioObjectGetPropertyDataArray(AudioObjectID inObjectID,
|
||||
const AudioObjectPropertyAddress &inAddress)
|
||||
{
|
||||
OSStatus status;
|
||||
UInt32 size;
|
||||
|
||||
status = AudioObjectGetPropertyDataSize(inObjectID,
|
||||
&inAddress,
|
||||
0, nullptr, &size);
|
||||
if (status != noErr)
|
||||
Apple::ThrowOSStatus(status);
|
||||
|
||||
AllocatedArray<T> result(size / sizeof(T));
|
||||
|
||||
status = AudioObjectGetPropertyData(inObjectID, &inAddress,
|
||||
0, nullptr,
|
||||
&size, result.begin());
|
||||
if (status != noErr)
|
||||
Apple::ThrowOSStatus(status);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
#endif
|
||||
111
src/apple/AudioUnit.hxx
Normal file
111
src/apple/AudioUnit.hxx
Normal file
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
* Copyright 2020 Max Kellermann <max.kellermann@gmail.com>
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* - Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* - Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the
|
||||
* distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
* FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
|
||||
* OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef APPLE_AUDIO_UNIT_HXX
|
||||
#define APPLE_AUDIO_UNIT_HXX
|
||||
|
||||
#include "Throw.hxx"
|
||||
|
||||
#include <AudioUnit/AudioUnit.h>
|
||||
|
||||
template<typename T>
|
||||
T
|
||||
AudioUnitGetPropertyT(AudioUnit inUnit, AudioUnitPropertyID inID,
|
||||
AudioUnitScope inScope,
|
||||
AudioUnitElement inElement)
|
||||
{
|
||||
UInt32 size = sizeof(T);
|
||||
T value;
|
||||
|
||||
OSStatus status = AudioUnitGetProperty(inUnit, inID, inScope,
|
||||
inElement,
|
||||
&value, &size);
|
||||
if (status != noErr)
|
||||
Apple::ThrowOSStatus(status);
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void
|
||||
AudioUnitSetPropertyT(AudioUnit inUnit, AudioUnitPropertyID inID,
|
||||
AudioUnitScope inScope,
|
||||
AudioUnitElement inElement,
|
||||
const T &value)
|
||||
{
|
||||
OSStatus status = AudioUnitSetProperty(inUnit, inID, inScope,
|
||||
inElement,
|
||||
&value, sizeof(value));
|
||||
if (status != noErr)
|
||||
Apple::ThrowOSStatus(status);
|
||||
}
|
||||
|
||||
inline void
|
||||
AudioUnitSetCurrentDevice(AudioUnit inUnit, const AudioDeviceID &value)
|
||||
{
|
||||
AudioUnitSetPropertyT(inUnit, kAudioOutputUnitProperty_CurrentDevice,
|
||||
kAudioUnitScope_Global, 0,
|
||||
value);
|
||||
}
|
||||
|
||||
inline void
|
||||
AudioUnitSetInputStreamFormat(AudioUnit inUnit,
|
||||
const AudioStreamBasicDescription &value)
|
||||
{
|
||||
AudioUnitSetPropertyT(inUnit, kAudioUnitProperty_StreamFormat,
|
||||
kAudioUnitScope_Input, 0,
|
||||
value);
|
||||
}
|
||||
|
||||
inline void
|
||||
AudioUnitSetInputRenderCallback(AudioUnit inUnit,
|
||||
const AURenderCallbackStruct &value)
|
||||
{
|
||||
AudioUnitSetPropertyT(inUnit, kAudioUnitProperty_SetRenderCallback,
|
||||
kAudioUnitScope_Input, 0,
|
||||
value);
|
||||
}
|
||||
|
||||
inline UInt32
|
||||
AudioUnitGetBufferFrameSize(AudioUnit inUnit)
|
||||
{
|
||||
return AudioUnitGetPropertyT<UInt32>(inUnit,
|
||||
kAudioDevicePropertyBufferFrameSize,
|
||||
kAudioUnitScope_Global, 0);
|
||||
}
|
||||
|
||||
inline void
|
||||
AudioUnitSetBufferFrameSize(AudioUnit inUnit, const UInt32 &value)
|
||||
{
|
||||
AudioUnitSetPropertyT(inUnit, kAudioDevicePropertyBufferFrameSize,
|
||||
kAudioUnitScope_Global, 0,
|
||||
value);
|
||||
}
|
||||
|
||||
#endif
|
||||
75
src/apple/ErrorRef.hxx
Normal file
75
src/apple/ErrorRef.hxx
Normal file
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright 2020 Max Kellermann <max.kellermann@gmail.com>
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* - Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* - Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the
|
||||
* distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
* FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
|
||||
* OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef APPLE_ERROR_REF_HXX
|
||||
#define APPLE_ERROR_REF_HXX
|
||||
|
||||
#include <CoreFoundation/CFError.h>
|
||||
|
||||
#include <utility>
|
||||
|
||||
namespace Apple {
|
||||
|
||||
class ErrorRef {
|
||||
CFErrorRef ref = nullptr;
|
||||
|
||||
public:
|
||||
explicit ErrorRef(CFErrorRef _ref) noexcept
|
||||
:ref(_ref) {}
|
||||
|
||||
ErrorRef(CFAllocatorRef allocator, CFErrorDomain domain,
|
||||
CFIndex code, CFDictionaryRef userInfo) noexcept
|
||||
:ref(CFErrorCreate(allocator, domain, code, userInfo)) {}
|
||||
|
||||
ErrorRef(ErrorRef &&src) noexcept
|
||||
:ref(std::exchange(src.ref, nullptr)) {}
|
||||
|
||||
~ErrorRef() noexcept {
|
||||
if (ref)
|
||||
CFRelease(ref);
|
||||
}
|
||||
|
||||
ErrorRef &operator=(ErrorRef &&src) noexcept {
|
||||
using std::swap;
|
||||
swap(ref, src.ref);
|
||||
return *this;
|
||||
}
|
||||
|
||||
operator bool() const noexcept {
|
||||
return ref != nullptr;
|
||||
}
|
||||
|
||||
CFStringRef CopyDescription() const noexcept {
|
||||
return CFErrorCopyDescription(ref);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace Apple
|
||||
|
||||
#endif
|
||||
73
src/apple/StringRef.hxx
Normal file
73
src/apple/StringRef.hxx
Normal file
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright 2020 Max Kellermann <max.kellermann@gmail.com>
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* - Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* - Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the
|
||||
* distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
* FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
|
||||
* OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef APPLE_STRING_REF_HXX
|
||||
#define APPLE_STRING_REF_HXX
|
||||
|
||||
#include <CoreFoundation/CFString.h>
|
||||
|
||||
#include <utility>
|
||||
|
||||
namespace Apple {
|
||||
|
||||
class StringRef {
|
||||
CFStringRef ref = nullptr;
|
||||
|
||||
public:
|
||||
explicit StringRef(CFStringRef _ref) noexcept
|
||||
:ref(_ref) {}
|
||||
|
||||
StringRef(StringRef &&src) noexcept
|
||||
:ref(std::exchange(src.ref, nullptr)) {}
|
||||
|
||||
~StringRef() noexcept {
|
||||
if (ref)
|
||||
CFRelease(ref);
|
||||
}
|
||||
|
||||
StringRef &operator=(StringRef &&src) noexcept {
|
||||
using std::swap;
|
||||
swap(ref, src.ref);
|
||||
return *this;
|
||||
}
|
||||
|
||||
operator bool() const noexcept {
|
||||
return ref != nullptr;
|
||||
}
|
||||
|
||||
bool GetCString(char *buffer, std::size_t size,
|
||||
CFStringEncoding encoding=kCFStringEncodingUTF8) const noexcept
|
||||
{
|
||||
return CFStringGetCString(ref, buffer, size, encoding);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace Apple
|
||||
|
||||
#endif
|
||||
67
src/apple/Throw.cxx
Normal file
67
src/apple/Throw.cxx
Normal file
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright 2020 Max Kellermann <max.kellermann@gmail.com>
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* - Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* - Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the
|
||||
* distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
* FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
|
||||
* OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "Throw.hxx"
|
||||
#include "ErrorRef.hxx"
|
||||
#include "StringRef.hxx"
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
namespace Apple {
|
||||
|
||||
void
|
||||
ThrowOSStatus(OSStatus status)
|
||||
{
|
||||
const Apple::ErrorRef cferr(nullptr, kCFErrorDomainOSStatus,
|
||||
status, nullptr);
|
||||
const Apple::StringRef cfstr(cferr.CopyDescription());
|
||||
|
||||
char msg[1024];
|
||||
if (!cfstr.GetCString(msg, sizeof(msg)))
|
||||
throw std::runtime_error("Unknown OSStatus");
|
||||
|
||||
throw std::runtime_error(msg);
|
||||
}
|
||||
|
||||
void
|
||||
ThrowOSStatus(OSStatus status, const char *_msg)
|
||||
{
|
||||
const Apple::ErrorRef cferr(nullptr, kCFErrorDomainOSStatus,
|
||||
status, nullptr);
|
||||
const Apple::StringRef cfstr(cferr.CopyDescription());
|
||||
|
||||
char msg[1024];
|
||||
strcpy(msg, _msg);
|
||||
size_t length = strlen(msg);
|
||||
|
||||
cfstr.GetCString(msg + length, sizeof(msg) - length);
|
||||
throw std::runtime_error(msg);
|
||||
}
|
||||
|
||||
} // namespace Apple
|
||||
45
src/apple/Throw.hxx
Normal file
45
src/apple/Throw.hxx
Normal file
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright 2020 Max Kellermann <max.kellermann@gmail.com>
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* - Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* - Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the
|
||||
* distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
* FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
|
||||
* OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef APPLE_THROW_HXX
|
||||
#define APPLE_THROW_HXX
|
||||
|
||||
#include <CoreFoundation/CFBase.h>
|
||||
|
||||
namespace Apple {
|
||||
|
||||
void
|
||||
ThrowOSStatus(OSStatus status);
|
||||
|
||||
void
|
||||
ThrowOSStatus(OSStatus status, const char *msg);
|
||||
|
||||
} // namespace Apple
|
||||
|
||||
#endif
|
||||
28
src/apple/meson.build
Normal file
28
src/apple/meson.build
Normal file
@@ -0,0 +1,28 @@
|
||||
if not is_darwin
|
||||
apple_dep = dependency('', required: false)
|
||||
subdir_done()
|
||||
endif
|
||||
|
||||
audiounit_dep = declare_dependency(
|
||||
link_args: ['-framework', 'AudioUnit', '-framework', 'CoreAudio', '-framework', 'CoreServices'],
|
||||
dependencies: [
|
||||
boost_dep,
|
||||
],
|
||||
)
|
||||
|
||||
apple = static_library(
|
||||
'apple',
|
||||
'AudioObject.cxx',
|
||||
'Throw.cxx',
|
||||
include_directories: inc,
|
||||
dependencies: [
|
||||
audiounit_dep,
|
||||
],
|
||||
)
|
||||
|
||||
apple_dep = declare_dependency(
|
||||
link_with: apple,
|
||||
dependencies: [
|
||||
audiounit_dep,
|
||||
],
|
||||
)
|
||||
@@ -193,7 +193,7 @@ static constexpr struct command commands[] = {
|
||||
{ "subscribe", PERMISSION_READ, 1, 1, handle_subscribe },
|
||||
{ "swap", PERMISSION_CONTROL, 2, 2, handle_swap },
|
||||
{ "swapid", PERMISSION_CONTROL, 2, 2, handle_swapid },
|
||||
{ "tagtypes", PERMISSION_READ, 0, -1, handle_tagtypes },
|
||||
{ "tagtypes", PERMISSION_NONE, 0, -1, handle_tagtypes },
|
||||
{ "toggleoutput", PERMISSION_ADMIN, 1, 1, handle_toggleoutput },
|
||||
#ifdef ENABLE_DATABASE
|
||||
{ "unmount", PERMISSION_ADMIN, 1, 1, handle_unmount },
|
||||
|
||||
@@ -198,6 +198,16 @@ handle_mount(Client &client, Request args, Response &r)
|
||||
return CommandResult::ERROR;
|
||||
}
|
||||
|
||||
if (composite.IsMountPoint(local_uri)) {
|
||||
r.Error(ACK_ERROR_ARG, "Mount point busy");
|
||||
return CommandResult::ERROR;
|
||||
}
|
||||
|
||||
if (composite.IsMounted(remote_uri)) {
|
||||
r.Error(ACK_ERROR_ARG, "This storage is already mounted");
|
||||
return CommandResult::ERROR;
|
||||
}
|
||||
|
||||
auto &event_loop = instance.io_thread.GetEventLoop();
|
||||
auto storage = CreateStorageURI(event_loop, remote_uri);
|
||||
if (storage == nullptr) {
|
||||
@@ -210,8 +220,10 @@ handle_mount(Client &client, Request args, Response &r)
|
||||
|
||||
#ifdef ENABLE_DATABASE
|
||||
if (auto *db = dynamic_cast<SimpleDatabase *>(instance.GetDatabase())) {
|
||||
bool need_update;
|
||||
|
||||
try {
|
||||
db->Mount(local_uri, remote_uri);
|
||||
need_update = !db->Mount(local_uri, remote_uri);
|
||||
} catch (...) {
|
||||
composite.Unmount(local_uri);
|
||||
throw;
|
||||
@@ -220,6 +232,12 @@ handle_mount(Client &client, Request args, Response &r)
|
||||
// TODO: call Instance::OnDatabaseModified()?
|
||||
// TODO: trigger database update?
|
||||
instance.EmitIdle(IDLE_DATABASE);
|
||||
|
||||
if (need_update) {
|
||||
UpdateService *update = client.GetInstance().update;
|
||||
if (update != nullptr)
|
||||
update->Enqueue(local_uri, false);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
@@ -493,9 +493,13 @@ ProxyDatabase::Connect()
|
||||
try {
|
||||
CheckError(connection);
|
||||
|
||||
if (mpd_connection_cmp_server_version(connection, 0, 19, 0) < 0)
|
||||
throw FormatRuntimeError("Connect to MPD %s, but this plugin requires at least version 0.19",
|
||||
mpd_connection_get_server_version(connection));
|
||||
if (mpd_connection_cmp_server_version(connection, 0, 19, 0) < 0) {
|
||||
const unsigned *version =
|
||||
mpd_connection_get_server_version(connection);
|
||||
throw FormatRuntimeError("Connect to MPD %u.%u.%u, but this "
|
||||
"plugin requires at least version 0.19",
|
||||
version[0], version[1], version[2]);
|
||||
}
|
||||
|
||||
if (!password.empty() &&
|
||||
!mpd_run_password(connection, password.c_str()))
|
||||
|
||||
@@ -428,7 +428,7 @@ IsUnsafeChar(char ch)
|
||||
return !IsSafeChar(ch);
|
||||
}
|
||||
|
||||
void
|
||||
bool
|
||||
SimpleDatabase::Mount(const char *local_uri, const char *storage_uri)
|
||||
{
|
||||
if (cache_path.IsNull())
|
||||
@@ -447,14 +447,11 @@ SimpleDatabase::Mount(const char *local_uri, const char *storage_uri)
|
||||
compress);
|
||||
db->Open();
|
||||
|
||||
// TODO: update the new database instance?
|
||||
bool exists = db->FileExists();
|
||||
|
||||
try {
|
||||
Mount(local_uri, std::move(db));
|
||||
} catch (...) {
|
||||
db->Close();
|
||||
throw;
|
||||
}
|
||||
Mount(local_uri, std::move(db));
|
||||
|
||||
return exists;
|
||||
}
|
||||
|
||||
inline DatabasePtr
|
||||
|
||||
@@ -103,9 +103,11 @@ public:
|
||||
|
||||
/**
|
||||
* Throws #std::runtime_error on error.
|
||||
*
|
||||
* @return false if the mounted database needs to be updated
|
||||
*/
|
||||
gcc_nonnull_all
|
||||
void Mount(const char *local_uri, const char *storage_uri);
|
||||
bool Mount(const char *local_uri, const char *storage_uri);
|
||||
|
||||
gcc_nonnull_all
|
||||
bool Unmount(const char *uri) noexcept;
|
||||
|
||||
@@ -89,9 +89,18 @@ public:
|
||||
tag.Clear();
|
||||
}
|
||||
|
||||
gcc_pure
|
||||
bool IsRoot() const noexcept {
|
||||
return type == Type::CONTAINER && id == "0";
|
||||
}
|
||||
|
||||
gcc_pure
|
||||
bool Check() const noexcept {
|
||||
return !id.empty() && !parent_id.empty() && !name.empty() &&
|
||||
return !id.empty() &&
|
||||
/* root nodes don't need a parent id and a
|
||||
name */
|
||||
(IsRoot() || (!parent_id.empty() &&
|
||||
!name.empty())) &&
|
||||
(type != UPnPDirObject::Type::ITEM ||
|
||||
item_class != UPnPDirObject::ItemClass::UNKNOWN);
|
||||
}
|
||||
|
||||
@@ -341,8 +341,8 @@ UpdateWalk::UpdateDirectory(Directory &directory,
|
||||
|
||||
try {
|
||||
Mutex mutex;
|
||||
auto is = InputStream::OpenReady(PathTraitsUTF8::Build(storage.MapUTF8(directory.GetPath()).c_str(),
|
||||
".mpdignore").c_str(),
|
||||
auto is = InputStream::OpenReady(storage.MapUTF8(PathTraitsUTF8::Build(directory.GetPath(),
|
||||
".mpdignore").c_str()).c_str(),
|
||||
mutex);
|
||||
child_exclude_list.Load(std::move(is));
|
||||
} catch (...) {
|
||||
|
||||
@@ -33,6 +33,8 @@
|
||||
#include "util/ConstBuffer.hxx"
|
||||
#include "util/StringBuffer.hxx"
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
@@ -344,6 +346,10 @@ DecoderBridge::SeekError()
|
||||
/* d'oh, we can't seek to the sub-song start position,
|
||||
what now? - no idea, ignoring the problem for now. */
|
||||
initial_seek_running = false;
|
||||
|
||||
if (initial_seek_essential)
|
||||
error = std::make_exception_ptr(std::runtime_error("Decoder failed to seek"));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -62,6 +62,11 @@ public:
|
||||
*/
|
||||
bool initial_seek_pending;
|
||||
|
||||
/**
|
||||
* Are initial seek failures fatal?
|
||||
*/
|
||||
const bool initial_seek_essential;
|
||||
|
||||
/**
|
||||
* Is the initial seek currently running? During this time,
|
||||
* the decoder command is SEEK. This flag is set by
|
||||
@@ -107,9 +112,11 @@ public:
|
||||
std::exception_ptr error;
|
||||
|
||||
DecoderBridge(DecoderControl &_dc, bool _initial_seek_pending,
|
||||
bool _initial_seek_essential,
|
||||
std::unique_ptr<Tag> _tag)
|
||||
:dc(_dc),
|
||||
initial_seek_pending(_initial_seek_pending),
|
||||
initial_seek_essential(_initial_seek_essential),
|
||||
song_tag(std::move(_tag)) {}
|
||||
|
||||
~DecoderBridge();
|
||||
|
||||
@@ -90,6 +90,7 @@ DecoderControl::IsCurrentSong(const DetachedSong &_song) const noexcept
|
||||
void
|
||||
DecoderControl::Start(std::unique_ptr<DetachedSong> _song,
|
||||
SongTime _start_time, SongTime _end_time,
|
||||
bool _initial_seek_essential,
|
||||
MusicBuffer &_buffer,
|
||||
std::shared_ptr<MusicPipe> _pipe) noexcept
|
||||
{
|
||||
@@ -99,6 +100,7 @@ DecoderControl::Start(std::unique_ptr<DetachedSong> _song,
|
||||
song = std::move(_song);
|
||||
start_time = _start_time;
|
||||
end_time = _end_time;
|
||||
initial_seek_essential = _initial_seek_essential;
|
||||
buffer = &_buffer;
|
||||
pipe = std::move(_pipe);
|
||||
|
||||
|
||||
@@ -117,6 +117,12 @@ public:
|
||||
|
||||
bool seek_error;
|
||||
bool seekable;
|
||||
|
||||
/**
|
||||
* @see #DecoderBridge::initial_seek_essential
|
||||
*/
|
||||
bool initial_seek_essential;
|
||||
|
||||
SongTime seek_time;
|
||||
|
||||
private:
|
||||
@@ -398,11 +404,14 @@ public:
|
||||
* owned and freed by the decoder
|
||||
* @param start_time see #DecoderControl
|
||||
* @param end_time see #DecoderControl
|
||||
* @param initial_seek_essential see
|
||||
* #DecoderBridge::initial_seek_essential
|
||||
* @param pipe the pipe which receives the decoded chunks (owned by
|
||||
* the caller)
|
||||
*/
|
||||
void Start(std::unique_ptr<DetachedSong> song,
|
||||
SongTime start_time, SongTime end_time,
|
||||
bool initial_seek_essential,
|
||||
MusicBuffer &buffer,
|
||||
std::shared_ptr<MusicPipe> pipe) noexcept;
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
|
||||
#include "util/Compiler.h"
|
||||
|
||||
#include <forward_list>
|
||||
#include <forward_list> // IWYU pragma: export
|
||||
|
||||
struct ConfigBlock;
|
||||
class InputStream;
|
||||
@@ -67,18 +67,22 @@ struct DecoderPlugin {
|
||||
void (*file_decode)(DecoderClient &client, Path path_fs);
|
||||
|
||||
/**
|
||||
* Scan metadata of a file.
|
||||
* Scan metadata of a file.
|
||||
*
|
||||
* Throws on I/O error.
|
||||
*
|
||||
* @return false if the operation has failed
|
||||
* @return false if the file was not recognized
|
||||
*/
|
||||
bool (*scan_file)(Path path_fs, TagHandler &handler) noexcept;
|
||||
bool (*scan_file)(Path path_fs, TagHandler &handler);
|
||||
|
||||
/**
|
||||
* Scan metadata of a file.
|
||||
* Scan metadata of a stream.
|
||||
*
|
||||
* Throws on I/O error.
|
||||
*
|
||||
* @return false if the operation has failed
|
||||
* @return false if the stream was not recognized
|
||||
*/
|
||||
bool (*scan_stream)(InputStream &is, TagHandler &handler) noexcept;
|
||||
bool (*scan_stream)(InputStream &is, TagHandler &handler);
|
||||
|
||||
/**
|
||||
* @brief Return a "virtual" filename for subtracks in
|
||||
@@ -135,7 +139,7 @@ struct DecoderPlugin {
|
||||
* Read the tag of a file.
|
||||
*/
|
||||
template<typename P>
|
||||
bool ScanFile(P path_fs, TagHandler &handler) const noexcept {
|
||||
bool ScanFile(P path_fs, TagHandler &handler) const {
|
||||
return scan_file != nullptr
|
||||
? scan_file(path_fs, handler)
|
||||
: false;
|
||||
@@ -144,7 +148,7 @@ struct DecoderPlugin {
|
||||
/**
|
||||
* Read the tag of a stream.
|
||||
*/
|
||||
bool ScanStream(InputStream &is, TagHandler &handler) const noexcept {
|
||||
bool ScanStream(InputStream &is, TagHandler &handler) const {
|
||||
return scan_stream != nullptr
|
||||
? scan_stream(is, handler)
|
||||
: false;
|
||||
|
||||
@@ -461,6 +461,7 @@ decoder_run_song(DecoderControl &dc,
|
||||
dc.start_time = dc.seek_time;
|
||||
|
||||
DecoderBridge bridge(dc, dc.start_time.IsPositive(),
|
||||
dc.initial_seek_essential,
|
||||
/* pass the song tag only if it's
|
||||
authoritative, i.e. if it's a local
|
||||
file - tags on "stream" songs are just
|
||||
|
||||
@@ -241,7 +241,7 @@ audiofile_stream_decode(DecoderClient &client, InputStream &is)
|
||||
}
|
||||
|
||||
static bool
|
||||
audiofile_scan_stream(InputStream &is, TagHandler &handler) noexcept
|
||||
audiofile_scan_stream(InputStream &is, TagHandler &handler)
|
||||
{
|
||||
if (!is.IsSeekable() || !is.KnownSize())
|
||||
return false;
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
#ifndef MPD_DECODER_DSDLIB_HXX
|
||||
#define MPD_DECODER_DSDLIB_HXX
|
||||
|
||||
#include "system/ByteOrder.hxx"
|
||||
#include "util/ByteOrder.hxx"
|
||||
#include "input/Offset.hxx"
|
||||
#include "util/Compiler.h"
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
#include "input/InputStream.hxx"
|
||||
#include "CheckAudioFormat.hxx"
|
||||
#include "util/bit_reverse.h"
|
||||
#include "system/ByteOrder.hxx"
|
||||
#include "util/ByteOrder.hxx"
|
||||
#include "tag/Handler.hxx"
|
||||
#include "DsdLib.hxx"
|
||||
#include "Log.hxx"
|
||||
@@ -449,7 +449,7 @@ dsdiff_stream_decode(DecoderClient &client, InputStream &is)
|
||||
}
|
||||
|
||||
static bool
|
||||
dsdiff_scan_stream(InputStream &is, TagHandler &handler) noexcept
|
||||
dsdiff_scan_stream(InputStream &is, TagHandler &handler)
|
||||
{
|
||||
DsdiffMetaData metadata;
|
||||
DsdiffChunkHeader chunk_header;
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
#include "input/InputStream.hxx"
|
||||
#include "CheckAudioFormat.hxx"
|
||||
#include "util/bit_reverse.h"
|
||||
#include "system/ByteOrder.hxx"
|
||||
#include "util/ByteOrder.hxx"
|
||||
#include "DsdLib.hxx"
|
||||
#include "tag/Handler.hxx"
|
||||
#include "Log.hxx"
|
||||
@@ -326,7 +326,7 @@ dsf_stream_decode(DecoderClient &client, InputStream &is)
|
||||
}
|
||||
|
||||
static bool
|
||||
dsf_scan_stream(InputStream &is, TagHandler &handler) noexcept
|
||||
dsf_scan_stream(InputStream &is, TagHandler &handler)
|
||||
{
|
||||
/* check DSF metadata */
|
||||
DsfMetaData metadata;
|
||||
|
||||
@@ -414,7 +414,7 @@ faad_stream_decode(DecoderClient &client, InputStream &is)
|
||||
}
|
||||
|
||||
static bool
|
||||
faad_scan_stream(InputStream &is, TagHandler &handler) noexcept
|
||||
faad_scan_stream(InputStream &is, TagHandler &handler)
|
||||
{
|
||||
auto result = faad_get_file_time(is);
|
||||
if (!result.first)
|
||||
|
||||
@@ -646,8 +646,7 @@ ffmpeg_decode(DecoderClient &client, InputStream &input)
|
||||
}
|
||||
|
||||
static bool
|
||||
FfmpegScanStream(AVFormatContext &format_context,
|
||||
TagHandler &handler) noexcept
|
||||
FfmpegScanStream(AVFormatContext &format_context, TagHandler &handler)
|
||||
{
|
||||
const int find_result =
|
||||
avformat_find_stream_info(&format_context, nullptr);
|
||||
@@ -680,7 +679,7 @@ FfmpegScanStream(AVFormatContext &format_context,
|
||||
}
|
||||
|
||||
static bool
|
||||
ffmpeg_scan_stream(InputStream &is, TagHandler &handler) noexcept
|
||||
ffmpeg_scan_stream(InputStream &is, TagHandler &handler)
|
||||
{
|
||||
AvioStream stream(nullptr, is);
|
||||
if (!stream.Open())
|
||||
|
||||
@@ -69,7 +69,7 @@ flac_write_cb(const FLAC__StreamDecoder *dec, const FLAC__Frame *frame,
|
||||
}
|
||||
|
||||
static bool
|
||||
flac_scan_file(Path path_fs, TagHandler &handler) noexcept
|
||||
flac_scan_file(Path path_fs, TagHandler &handler)
|
||||
{
|
||||
FlacMetadataChain chain;
|
||||
if (!chain.Read(NarrowPath(path_fs))) {
|
||||
@@ -84,7 +84,7 @@ flac_scan_file(Path path_fs, TagHandler &handler) noexcept
|
||||
}
|
||||
|
||||
static bool
|
||||
flac_scan_stream(InputStream &is, TagHandler &handler) noexcept
|
||||
flac_scan_stream(InputStream &is, TagHandler &handler)
|
||||
{
|
||||
FlacMetadataChain chain;
|
||||
if (!chain.Read(is)) {
|
||||
@@ -313,7 +313,7 @@ oggflac_init(gcc_unused const ConfigBlock &block)
|
||||
}
|
||||
|
||||
static bool
|
||||
oggflac_scan_file(Path path_fs, TagHandler &handler) noexcept
|
||||
oggflac_scan_file(Path path_fs, TagHandler &handler)
|
||||
{
|
||||
FlacMetadataChain chain;
|
||||
if (!chain.ReadOgg(NarrowPath(path_fs))) {
|
||||
@@ -328,7 +328,7 @@ oggflac_scan_file(Path path_fs, TagHandler &handler) noexcept
|
||||
}
|
||||
|
||||
static bool
|
||||
oggflac_scan_stream(InputStream &is, TagHandler &handler) noexcept
|
||||
oggflac_scan_stream(InputStream &is, TagHandler &handler)
|
||||
{
|
||||
FlacMetadataChain chain;
|
||||
if (!chain.ReadOgg(is)) {
|
||||
|
||||
@@ -70,8 +70,8 @@ fluidsynth_mpd_log_function(int level,
|
||||
char *message,
|
||||
void *)
|
||||
{
|
||||
Log(fluidsynth_domain,
|
||||
fluidsynth_level_to_mpd(fluid_log_level(level)),
|
||||
Log(fluidsynth_level_to_mpd(fluid_log_level(level)),
|
||||
fluidsynth_domain,
|
||||
message);
|
||||
}
|
||||
|
||||
|
||||
@@ -27,10 +27,10 @@
|
||||
#include "fs/Path.hxx"
|
||||
#include "fs/AllocatedPath.hxx"
|
||||
#include "fs/FileSystem.hxx"
|
||||
#include "fs/NarrowPath.hxx"
|
||||
#include "util/ScopeExit.hxx"
|
||||
#include "util/StringCompare.hxx"
|
||||
#include "util/StringFormat.hxx"
|
||||
#include "util/UriUtil.hxx"
|
||||
#include "util/Domain.hxx"
|
||||
#include "Log.hxx"
|
||||
|
||||
@@ -38,7 +38,6 @@
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#define SUBTUNE_PREFIX "tune_"
|
||||
|
||||
@@ -76,11 +75,10 @@ gcc_pure
|
||||
static unsigned
|
||||
ParseSubtuneName(const char *base) noexcept
|
||||
{
|
||||
if (memcmp(base, SUBTUNE_PREFIX, sizeof(SUBTUNE_PREFIX) - 1) != 0)
|
||||
base = StringAfterPrefix(base, SUBTUNE_PREFIX);
|
||||
if (base == nullptr)
|
||||
return 0;
|
||||
|
||||
base += sizeof(SUBTUNE_PREFIX) - 1;
|
||||
|
||||
char *endptr;
|
||||
auto track = strtoul(base, &endptr, 10);
|
||||
if (endptr == base || *endptr != '.')
|
||||
@@ -99,41 +97,46 @@ ParseContainerPath(Path path_fs)
|
||||
const Path base = path_fs.GetBase();
|
||||
unsigned track;
|
||||
if (base.IsNull() ||
|
||||
(track = ParseSubtuneName(base.c_str())) < 1)
|
||||
(track = ParseSubtuneName(NarrowPath(base))) < 1)
|
||||
return { AllocatedPath(path_fs), 0 };
|
||||
|
||||
return { path_fs.GetDirectoryName(), track - 1 };
|
||||
}
|
||||
|
||||
static AllocatedPath
|
||||
ReplaceSuffix(Path src,
|
||||
const PathTraitsFS::const_pointer_type new_suffix) noexcept
|
||||
{
|
||||
const auto *old_suffix = src.GetSuffix();
|
||||
if (old_suffix == nullptr)
|
||||
return nullptr;
|
||||
|
||||
PathTraitsFS::string s(src.c_str(), old_suffix);
|
||||
s += new_suffix;
|
||||
return AllocatedPath::FromFS(std::move(s));
|
||||
}
|
||||
|
||||
static Music_Emu*
|
||||
LoadGmeAndM3u(GmeContainerPath container) {
|
||||
|
||||
const char *path = container.path.c_str();
|
||||
const char *suffix = uri_get_suffix(path);
|
||||
|
||||
Music_Emu *emu;
|
||||
const char *gme_err =
|
||||
gme_open_file(path, &emu, GME_SAMPLE_RATE);
|
||||
gme_open_file(NarrowPath(container.path), &emu, GME_SAMPLE_RATE);
|
||||
if (gme_err != nullptr) {
|
||||
LogWarning(gme_domain, gme_err);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if(suffix == nullptr) {
|
||||
return emu;
|
||||
}
|
||||
|
||||
std::string m3u_path(path,suffix);
|
||||
m3u_path += "m3u";
|
||||
|
||||
const auto m3u_path = ReplaceSuffix(container.path,
|
||||
PATH_LITERAL("m3u"));
|
||||
/*
|
||||
* Some GME formats lose metadata if you attempt to
|
||||
* load a non-existant M3U file, so check that one
|
||||
* exists before loading.
|
||||
*/
|
||||
if(FileExists(Path::FromFS(m3u_path.c_str()))) {
|
||||
gme_load_m3u(emu,m3u_path.c_str());
|
||||
}
|
||||
if (!m3u_path.IsNull() && FileExists(m3u_path))
|
||||
gme_load_m3u(emu, NarrowPath(m3u_path));
|
||||
|
||||
return emu;
|
||||
}
|
||||
|
||||
@@ -303,7 +306,7 @@ gme_container_scan(Path path_fs)
|
||||
if (num_songs < 2)
|
||||
return list;
|
||||
|
||||
const char *subtune_suffix = uri_get_suffix(path_fs.c_str());
|
||||
const auto *subtune_suffix = path_fs.GetSuffix();
|
||||
|
||||
TagBuilder tag_builder;
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
#include "HybridDsdDecoderPlugin.hxx"
|
||||
#include "../DecoderAPI.hxx"
|
||||
#include "input/InputStream.hxx"
|
||||
#include "system/ByteOrder.hxx"
|
||||
#include "util/ByteOrder.hxx"
|
||||
#include "util/Domain.hxx"
|
||||
#include "util/WritableBuffer.hxx"
|
||||
#include "util/StaticFifoBuffer.hxx"
|
||||
|
||||
@@ -1051,7 +1051,7 @@ MadDecoder::RunScan(TagHandler &handler) noexcept
|
||||
}
|
||||
|
||||
static bool
|
||||
mad_decoder_scan_stream(InputStream &is, TagHandler &handler) noexcept
|
||||
mad_decoder_scan_stream(InputStream &is, TagHandler &handler)
|
||||
{
|
||||
MadDecoder data(nullptr, is);
|
||||
return data.RunScan(handler);
|
||||
|
||||
@@ -26,8 +26,13 @@
|
||||
#include "util/RuntimeError.hxx"
|
||||
#include "Log.hxx"
|
||||
|
||||
#include <libmodplug/modplug.h>
|
||||
#ifdef _WIN32
|
||||
/* assume ModPlug is built as static library on Windows; without
|
||||
this, linking to the static library would fail */
|
||||
#define MODPLUG_STATIC
|
||||
#endif
|
||||
|
||||
#include <libmodplug/modplug.h>
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
|
||||
@@ -275,7 +275,7 @@ mpcdec_get_file_duration(InputStream &is)
|
||||
}
|
||||
|
||||
static bool
|
||||
mpcdec_scan_stream(InputStream &is, TagHandler &handler) noexcept
|
||||
mpcdec_scan_stream(InputStream &is, TagHandler &handler)
|
||||
{
|
||||
const auto duration = mpcdec_get_file_duration(is);
|
||||
if (duration.IsNegative())
|
||||
|
||||
@@ -75,6 +75,19 @@ class MPDOpusDecoder final : public OggDecoder {
|
||||
OpusDecoder *opus_decoder = nullptr;
|
||||
opus_int16 *output_buffer = nullptr;
|
||||
|
||||
/**
|
||||
* The pre-skip value from the Opus header. Initialized by
|
||||
* OnOggBeginning().
|
||||
*/
|
||||
unsigned pre_skip;
|
||||
|
||||
/**
|
||||
* The number of decoded samples which shall be skipped. At
|
||||
* the beginning of the file, this gets set to #pre_skip (by
|
||||
* OnOggBeginning()), and may also be set while seeking.
|
||||
*/
|
||||
unsigned skip;
|
||||
|
||||
/**
|
||||
* If non-zero, then a previous Opus stream has been found
|
||||
* already with this number of channels. If opus_decoder is
|
||||
@@ -85,6 +98,13 @@ class MPDOpusDecoder final : public OggDecoder {
|
||||
|
||||
size_t frame_size;
|
||||
|
||||
/**
|
||||
* The granulepos of the next sample to be submitted to
|
||||
* DecoderClient::SubmitData(). Negative if unkown.
|
||||
* Initialized by OnOggBeginning().
|
||||
*/
|
||||
ogg_int64_t granulepos;
|
||||
|
||||
public:
|
||||
explicit MPDOpusDecoder(DecoderReader &reader)
|
||||
:OggDecoder(reader) {}
|
||||
@@ -101,6 +121,13 @@ public:
|
||||
bool Seek(uint64_t where_frame);
|
||||
|
||||
private:
|
||||
void AddGranulepos(ogg_int64_t n) noexcept {
|
||||
assert(n >= 0);
|
||||
|
||||
if (granulepos >= 0)
|
||||
granulepos += n;
|
||||
}
|
||||
|
||||
void HandleTags(const ogg_packet &packet);
|
||||
void HandleAudio(const ogg_packet &packet);
|
||||
|
||||
@@ -137,10 +164,13 @@ MPDOpusDecoder::OnOggBeginning(const ogg_packet &packet)
|
||||
throw std::runtime_error("BOS packet must be OpusHead");
|
||||
|
||||
unsigned channels;
|
||||
if (!ScanOpusHeader(packet.packet, packet.bytes, channels) ||
|
||||
if (!ScanOpusHeader(packet.packet, packet.bytes, channels, pre_skip) ||
|
||||
!audio_valid_channel_count(channels))
|
||||
throw std::runtime_error("Malformed BOS packet");
|
||||
|
||||
granulepos = 0;
|
||||
skip = pre_skip;
|
||||
|
||||
assert(opus_decoder == nullptr);
|
||||
assert(IsInitialized() == (output_buffer != nullptr));
|
||||
|
||||
@@ -177,8 +207,12 @@ MPDOpusDecoder::OnOggBeginning(const ogg_packet &packet)
|
||||
client.Ready(audio_format, eos_granulepos > 0, duration);
|
||||
frame_size = audio_format.GetFrameSize();
|
||||
|
||||
output_buffer = new opus_int16[opus_output_buffer_frames
|
||||
* audio_format.channels];
|
||||
if (output_buffer == nullptr)
|
||||
/* note: if we ever support changing the channel count
|
||||
in chained streams, we need to reallocate this
|
||||
buffer instead of keeping it */
|
||||
output_buffer = new opus_int16[opus_output_buffer_frames
|
||||
* audio_format.channels];
|
||||
|
||||
auto cmd = client.GetCommand();
|
||||
if (cmd != DecoderCommand::NONE)
|
||||
@@ -231,22 +265,58 @@ MPDOpusDecoder::HandleAudio(const ogg_packet &packet)
|
||||
packet.bytes,
|
||||
output_buffer, opus_output_buffer_frames,
|
||||
0);
|
||||
if (nframes < 0)
|
||||
throw FormatRuntimeError("libopus error: %s",
|
||||
opus_strerror(nframes));
|
||||
|
||||
if (nframes > 0) {
|
||||
const size_t nbytes = nframes * frame_size;
|
||||
auto cmd = client.SubmitData(input_stream,
|
||||
output_buffer, nbytes,
|
||||
0);
|
||||
if (cmd != DecoderCommand::NONE)
|
||||
throw cmd;
|
||||
|
||||
if (packet.granulepos > 0)
|
||||
client.SubmitTimestamp(FloatDuration(packet.granulepos)
|
||||
/ opus_sample_rate);
|
||||
if (gcc_unlikely(nframes <= 0)) {
|
||||
if (nframes < 0)
|
||||
throw FormatRuntimeError("libopus error: %s",
|
||||
opus_strerror(nframes));
|
||||
else
|
||||
return;
|
||||
}
|
||||
|
||||
/* apply the "skip" value */
|
||||
if (skip >= (unsigned)nframes) {
|
||||
skip -= nframes;
|
||||
AddGranulepos(nframes);
|
||||
return;
|
||||
}
|
||||
|
||||
const opus_int16 *data = output_buffer;
|
||||
data += skip * previous_channels;
|
||||
nframes -= skip;
|
||||
AddGranulepos(skip);
|
||||
skip = 0;
|
||||
|
||||
if (packet.e_o_s && packet.granulepos > 0 && granulepos >= 0) {
|
||||
/* End Trimming (RFC7845 4.4): "The page with the 'end
|
||||
of stream' flag set MAY have a granule position
|
||||
that indicates the page contains less audio data
|
||||
than would normally be returned by decoding up
|
||||
through the final packet. This is used to end the
|
||||
stream somewhere other than an even frame
|
||||
boundary. [...] The remaining samples are
|
||||
discarded. */
|
||||
ogg_int64_t remaining = packet.granulepos - granulepos;
|
||||
if (remaining <= 0)
|
||||
return;
|
||||
|
||||
if (remaining < nframes)
|
||||
nframes = remaining;
|
||||
}
|
||||
|
||||
/* submit decoded samples to the DecoderClient */
|
||||
const size_t nbytes = nframes * frame_size;
|
||||
auto cmd = client.SubmitData(input_stream,
|
||||
data, nbytes,
|
||||
0);
|
||||
if (cmd != DecoderCommand::NONE)
|
||||
throw cmd;
|
||||
|
||||
if (packet.granulepos > 0) {
|
||||
granulepos = packet.granulepos;
|
||||
client.SubmitTimestamp(FloatDuration(granulepos - pre_skip)
|
||||
/ opus_sample_rate);
|
||||
} else
|
||||
AddGranulepos(nframes);
|
||||
}
|
||||
|
||||
bool
|
||||
@@ -258,8 +328,20 @@ MPDOpusDecoder::Seek(uint64_t where_frame)
|
||||
|
||||
const ogg_int64_t where_granulepos(where_frame);
|
||||
|
||||
/* we don't know the exact granulepos after seeking, so let's
|
||||
set it to -1 - it will be set after the next packet which
|
||||
declares its granulepos */
|
||||
granulepos = -1;
|
||||
|
||||
try {
|
||||
SeekGranulePos(where_granulepos);
|
||||
|
||||
/* since all frame numbers are offset by the file's
|
||||
pre-skip value, we need to apply it here as well;
|
||||
we could just seek to "where_frame+pre_skip" as
|
||||
well, but I think by decoding those samples and
|
||||
discard them, we're safer */
|
||||
skip = pre_skip;
|
||||
return true;
|
||||
} catch (...) {
|
||||
return false;
|
||||
@@ -302,13 +384,14 @@ mpd_opus_stream_decode(DecoderClient &client,
|
||||
|
||||
static bool
|
||||
ReadAndParseOpusHead(OggSyncState &sync, OggStreamState &stream,
|
||||
unsigned &channels)
|
||||
unsigned &channels, unsigned &pre_skip)
|
||||
{
|
||||
ogg_packet packet;
|
||||
|
||||
return OggReadPacket(sync, stream, packet) && packet.b_o_s &&
|
||||
IsOpusHead(packet) &&
|
||||
ScanOpusHeader(packet.packet, packet.bytes, channels) &&
|
||||
ScanOpusHeader(packet.packet, packet.bytes, channels,
|
||||
pre_skip) &&
|
||||
audio_valid_channel_count(channels);
|
||||
}
|
||||
|
||||
@@ -327,11 +410,12 @@ ReadAndVisitOpusTags(OggSyncState &sync, OggStreamState &stream,
|
||||
|
||||
static void
|
||||
VisitOpusDuration(InputStream &is, OggSyncState &sync, OggStreamState &stream,
|
||||
TagHandler &handler)
|
||||
ogg_int64_t pre_skip, TagHandler &handler)
|
||||
{
|
||||
ogg_packet packet;
|
||||
|
||||
if (OggSeekFindEOS(sync, stream, packet, is)) {
|
||||
if (OggSeekFindEOS(sync, stream, packet, is) &&
|
||||
packet.granulepos >= pre_skip) {
|
||||
const auto duration =
|
||||
SongTime::FromScale<uint64_t>(packet.granulepos,
|
||||
opus_sample_rate);
|
||||
@@ -340,7 +424,7 @@ VisitOpusDuration(InputStream &is, OggSyncState &sync, OggStreamState &stream,
|
||||
}
|
||||
|
||||
static bool
|
||||
mpd_opus_scan_stream(InputStream &is, TagHandler &handler) noexcept
|
||||
mpd_opus_scan_stream(InputStream &is, TagHandler &handler)
|
||||
{
|
||||
InputStreamReader reader(is);
|
||||
OggSyncState oy(reader);
|
||||
@@ -351,15 +435,15 @@ mpd_opus_scan_stream(InputStream &is, TagHandler &handler) noexcept
|
||||
|
||||
OggStreamState os(first_page);
|
||||
|
||||
unsigned channels;
|
||||
if (!ReadAndParseOpusHead(oy, os, channels) ||
|
||||
unsigned channels, pre_skip;
|
||||
if (!ReadAndParseOpusHead(oy, os, channels, pre_skip) ||
|
||||
!ReadAndVisitOpusTags(oy, os, handler))
|
||||
return false;
|
||||
|
||||
handler.OnAudioFormat(AudioFormat(opus_sample_rate,
|
||||
SampleFormat::S16, channels));
|
||||
|
||||
VisitOpusDuration(is, oy, os, handler);
|
||||
VisitOpusDuration(is, oy, os, pre_skip, handler);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
*/
|
||||
|
||||
#include "OpusHead.hxx"
|
||||
#include "util/ByteOrder.hxx"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
@@ -31,12 +32,14 @@ struct OpusHead {
|
||||
};
|
||||
|
||||
bool
|
||||
ScanOpusHeader(const void *data, size_t size, unsigned &channels_r)
|
||||
ScanOpusHeader(const void *data, size_t size, unsigned &channels_r,
|
||||
unsigned &pre_skip_r)
|
||||
{
|
||||
const OpusHead *h = (const OpusHead *)data;
|
||||
if (size < 19 || (h->version & 0xf0) != 0)
|
||||
return false;
|
||||
|
||||
channels_r = h->channels;
|
||||
pre_skip_r = FromLE16(h->pre_skip);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
#include <stddef.h>
|
||||
|
||||
bool
|
||||
ScanOpusHeader(const void *data, size_t size, unsigned &channels_r);
|
||||
ScanOpusHeader(const void *data, size_t size, unsigned &channels_r,
|
||||
unsigned &pre_skip_r);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
#include "CheckAudioFormat.hxx"
|
||||
#include "pcm/PcmPack.hxx"
|
||||
#include "input/InputStream.hxx"
|
||||
#include "system/ByteOrder.hxx"
|
||||
#include "util/ByteOrder.hxx"
|
||||
#include "util/Domain.hxx"
|
||||
#include "util/ByteReverse.hxx"
|
||||
#include "util/StaticFifoBuffer.hxx"
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
#include "util/Domain.hxx"
|
||||
#include "util/AllocatedString.hxx"
|
||||
#include "util/CharUtil.hxx"
|
||||
#include "system/ByteOrder.hxx"
|
||||
#include "util/ByteOrder.hxx"
|
||||
#include "Log.hxx"
|
||||
|
||||
#ifdef HAVE_SIDPLAYFP
|
||||
|
||||
@@ -267,7 +267,7 @@ static constexpr struct {
|
||||
};
|
||||
|
||||
static bool
|
||||
sndfile_scan_stream(InputStream &is, TagHandler &handler) noexcept
|
||||
sndfile_scan_stream(InputStream &is, TagHandler &handler)
|
||||
{
|
||||
SF_INFO info;
|
||||
|
||||
|
||||
@@ -370,7 +370,7 @@ VisitVorbisDuration(InputStream &is,
|
||||
}
|
||||
|
||||
static bool
|
||||
vorbis_scan_stream(InputStream &is, TagHandler &handler) noexcept
|
||||
vorbis_scan_stream(InputStream &is, TagHandler &handler)
|
||||
{
|
||||
/* initialize libogg */
|
||||
|
||||
|
||||
@@ -614,7 +614,7 @@ wavpack_scan_file(Path path_fs, TagHandler &handler) noexcept
|
||||
}
|
||||
|
||||
static bool
|
||||
wavpack_scan_stream(InputStream &is, TagHandler &handler) noexcept
|
||||
wavpack_scan_stream(InputStream &is, TagHandler &handler)
|
||||
{
|
||||
WavpackInput isp(nullptr, is);
|
||||
|
||||
|
||||
@@ -25,9 +25,16 @@
|
||||
#include "fs/AllocatedPath.hxx"
|
||||
#include "fs/FileSystem.hxx"
|
||||
#include "fs/Path.hxx"
|
||||
#include "fs/NarrowPath.hxx"
|
||||
#include "Log.hxx"
|
||||
#include "PluginUnavailable.hxx"
|
||||
|
||||
#ifdef _WIN32
|
||||
/* assume WildMidi is built as static library on Windows; without
|
||||
this, linking to the static library would fail */
|
||||
#define WILDMIDI_STATIC
|
||||
#endif
|
||||
|
||||
extern "C" {
|
||||
#include <wildmidi_lib.h>
|
||||
}
|
||||
@@ -53,7 +60,8 @@ wildmidi_init(const ConfigBlock &block)
|
||||
AtScopeExit() { WildMidi_ClearError(); };
|
||||
#endif
|
||||
|
||||
if (WildMidi_Init(path.c_str(), wildmidi_audio_format.sample_rate,
|
||||
if (WildMidi_Init(NarrowPath(path),
|
||||
wildmidi_audio_format.sample_rate,
|
||||
0) != 0) {
|
||||
#ifdef LIBWILDMIDI_VERSION
|
||||
/* WildMidi_GetError() requires libwildmidi 0.4 */
|
||||
@@ -96,7 +104,7 @@ wildmidi_file_decode(DecoderClient &client, Path path_fs)
|
||||
midi *wm;
|
||||
const struct _WM_Info *info;
|
||||
|
||||
wm = WildMidi_Open(path_fs.c_str());
|
||||
wm = WildMidi_Open(NarrowPath(path_fs));
|
||||
if (wm == nullptr)
|
||||
return;
|
||||
|
||||
@@ -136,7 +144,7 @@ wildmidi_file_decode(DecoderClient &client, Path path_fs)
|
||||
static bool
|
||||
wildmidi_scan_file(Path path_fs, TagHandler &handler) noexcept
|
||||
{
|
||||
midi *wm = WildMidi_Open(path_fs.c_str());
|
||||
midi *wm = WildMidi_Open(NarrowPath(path_fs));
|
||||
if (wm == nullptr)
|
||||
return false;
|
||||
|
||||
|
||||
@@ -129,7 +129,16 @@ if wavpack_dep.found()
|
||||
decoder_plugins_sources += 'WavpackDecoderPlugin.cxx'
|
||||
endif
|
||||
|
||||
wildmidi_dep = c_compiler.find_library('WildMidi', required: get_option('wildmidi'))
|
||||
wildmidi_required = get_option('wildmidi')
|
||||
if wildmidi_required.enabled()
|
||||
# if the user has force-enabled WildMidi, allow the pkg-config test
|
||||
# to fail; after that, the find_library() check must succeed
|
||||
wildmidi_required = false
|
||||
endif
|
||||
wildmidi_dep = dependency('wildmidi', required: wildmidi_required)
|
||||
if not wildmidi_dep.found()
|
||||
wildmidi_dep = c_compiler.find_library('WildMidi', required: get_option('wildmidi'))
|
||||
endif
|
||||
conf.set('ENABLE_WILDMIDI', wildmidi_dep.found())
|
||||
if wildmidi_dep.found()
|
||||
decoder_plugins_sources += 'WildmidiDecoderPlugin.cxx'
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
#include "AudioFormat.hxx"
|
||||
#include "config/Domain.hxx"
|
||||
#include "util/Alloc.hxx"
|
||||
#include "system/ByteOrder.hxx"
|
||||
#include "util/ByteOrder.hxx"
|
||||
#include "util/StringUtil.hxx"
|
||||
|
||||
#include <opus.h>
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
#include "WaveEncoderPlugin.hxx"
|
||||
#include "../EncoderAPI.hxx"
|
||||
#include "system/ByteOrder.hxx"
|
||||
#include "util/ByteOrder.hxx"
|
||||
#include "util/DynamicFifoBuffer.hxx"
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
@@ -285,6 +285,11 @@ public:
|
||||
bool IsAbsolute() const noexcept {
|
||||
return Traits::IsAbsolute(c_str());
|
||||
}
|
||||
|
||||
gcc_pure
|
||||
const_pointer_type GetSuffix() const noexcept {
|
||||
return ((Path)*this).GetSuffix();
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -58,6 +58,11 @@ input_stream_global_init(const ConfigData &config, EventLoop &event_loop)
|
||||
if (plugin->init != nullptr)
|
||||
plugin->init(event_loop, *block);
|
||||
input_plugins_enabled[i] = true;
|
||||
} catch (const PluginUnconfigured &e) {
|
||||
LogFormat(LogLevel::INFO, e,
|
||||
"Input plugin '%s' is not configured",
|
||||
plugin->name);
|
||||
continue;
|
||||
} catch (const PluginUnavailable &e) {
|
||||
FormatError(e,
|
||||
"Input plugin '%s' is unavailable",
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
#include "util/StringCompare.hxx"
|
||||
#include "util/RuntimeError.hxx"
|
||||
#include "util/Domain.hxx"
|
||||
#include "system/ByteOrder.hxx"
|
||||
#include "util/ByteOrder.hxx"
|
||||
#include "fs/AllocatedPath.hxx"
|
||||
#include "Log.hxx"
|
||||
#include "config/Block.hxx"
|
||||
|
||||
@@ -26,6 +26,8 @@
|
||||
#include "system/FileDescriptor.hxx"
|
||||
#include "util/RuntimeError.hxx"
|
||||
|
||||
#include <cinttypes> // for PRIu64 (PRIoffset)
|
||||
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
@@ -94,6 +96,11 @@ FileInputStream::Read(void *ptr, size_t read_size)
|
||||
nbytes = reader.Read(ptr, read_size);
|
||||
}
|
||||
|
||||
if (nbytes == 0 && !IsEOF())
|
||||
throw FormatRuntimeError("Unexpected end of file %s"
|
||||
" at %" PRIoffset " of %" PRIoffset,
|
||||
GetURI(), GetOffset(), GetSize());
|
||||
|
||||
offset += nbytes;
|
||||
return nbytes;
|
||||
}
|
||||
|
||||
@@ -133,11 +133,11 @@ InitQobuzInput(EventLoop &event_loop, const ConfigBlock &block)
|
||||
|
||||
const char *app_id = block.GetBlockValue("app_id");
|
||||
if (app_id == nullptr)
|
||||
throw PluginUnavailable("No Qobuz app_id configured");
|
||||
throw PluginUnconfigured("No Qobuz app_id configured");
|
||||
|
||||
const char *app_secret = block.GetBlockValue("app_secret");
|
||||
if (app_secret == nullptr)
|
||||
throw PluginUnavailable("No Qobuz app_secret configured");
|
||||
throw PluginUnconfigured("No Qobuz app_secret configured");
|
||||
|
||||
const char *device_manufacturer_id = block.GetBlockValue("device_manufacturer_id",
|
||||
"df691fdc-fa36-11e7-9718-635337d7df8f");
|
||||
@@ -145,11 +145,11 @@ InitQobuzInput(EventLoop &event_loop, const ConfigBlock &block)
|
||||
const char *username = block.GetBlockValue("username");
|
||||
const char *email = block.GetBlockValue("email");
|
||||
if (username == nullptr && email == nullptr)
|
||||
throw PluginUnavailable("No Qobuz username configured");
|
||||
throw PluginUnconfigured("No Qobuz username configured");
|
||||
|
||||
const char *password = block.GetBlockValue("password");
|
||||
if (password == nullptr)
|
||||
throw PluginUnavailable("No Qobuz password configured");
|
||||
throw PluginUnconfigured("No Qobuz password configured");
|
||||
|
||||
const char *format_id = block.GetBlockValue("format_id", "5");
|
||||
|
||||
|
||||
@@ -170,15 +170,15 @@ InitTidalInput(EventLoop &event_loop, const ConfigBlock &block)
|
||||
|
||||
const char *token = block.GetBlockValue("token");
|
||||
if (token == nullptr)
|
||||
throw PluginUnavailable("No Tidal application token configured");
|
||||
throw PluginUnconfigured("No Tidal application token configured");
|
||||
|
||||
const char *username = block.GetBlockValue("username");
|
||||
if (username == nullptr)
|
||||
throw PluginUnavailable("No Tidal username configured");
|
||||
throw PluginUnconfigured("No Tidal username configured");
|
||||
|
||||
const char *password = block.GetBlockValue("password");
|
||||
if (password == nullptr)
|
||||
throw PluginUnavailable("No Tidal password configured");
|
||||
throw PluginUnconfigured("No Tidal password configured");
|
||||
|
||||
FormatWarning(tidal_domain, "The Tidal input plugin is deprecated because Tidal has changed the protocol and doesn't share documentation");
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
#include "HwSetup.hxx"
|
||||
#include "Format.hxx"
|
||||
#include "system/ByteOrder.hxx"
|
||||
#include "util/ByteOrder.hxx"
|
||||
#include "util/Domain.hxx"
|
||||
#include "util/RuntimeError.hxx"
|
||||
#include "AudioFormat.hxx"
|
||||
|
||||
@@ -56,7 +56,7 @@ CurlRequest::CurlRequest(CurlGlobal &_global,
|
||||
easy.SetUserAgent("Music Player Daemon " VERSION);
|
||||
easy.SetHeaderFunction(_HeaderFunction, this);
|
||||
easy.SetWriteFunction(WriteFunction, this);
|
||||
#ifndef ANDROID
|
||||
#if !defined(ANDROID) && !defined(_WIN32)
|
||||
easy.SetOption(CURLOPT_NETRC, 1L);
|
||||
#endif
|
||||
easy.SetErrorBuffer(error_buffer);
|
||||
|
||||
@@ -86,7 +86,7 @@ struct WrapVariant : BasicValue<T> {
|
||||
template<typename T>
|
||||
static WrapVariant<T> Variant(const T &_value) noexcept {
|
||||
return WrapVariant<T>(_value);
|
||||
};
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
struct WrapFixedArray {
|
||||
@@ -103,7 +103,7 @@ template<typename T>
|
||||
static WrapFixedArray<T> FixedArray(const T *_data,
|
||||
size_t _size) noexcept {
|
||||
return WrapFixedArray<T>(_data, _size);
|
||||
};
|
||||
}
|
||||
|
||||
template<typename... T>
|
||||
struct WrapStruct {
|
||||
@@ -118,7 +118,7 @@ struct WrapStruct {
|
||||
template<typename... T>
|
||||
static WrapStruct<T...> Struct(const T&... values) noexcept {
|
||||
return WrapStruct<T...>(values...);
|
||||
};
|
||||
}
|
||||
|
||||
} /* namespace ODBus */
|
||||
|
||||
|
||||
@@ -62,6 +62,6 @@ FfmpegLogCallback(gcc_unused void *ptr, int level, const char *fmt, va_list vl)
|
||||
ffmpeg_domain.GetName(),
|
||||
cls->item_name(ptr));
|
||||
const Domain d(domain);
|
||||
LogFormatV(d, FfmpegImportLogLevel(level), fmt, vl);
|
||||
LogFormatV(FfmpegImportLogLevel(level), d, fmt, vl);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ FfmpegTimeToDouble(int64_t t, const AVRational time_base) noexcept
|
||||
{
|
||||
assert(t != (int64_t)AV_NOPTS_VALUE);
|
||||
|
||||
return FloatDuration(av_rescale_q(t, time_base, (AVRational){1, 1024}))
|
||||
return FloatDuration(av_rescale_q(t, time_base, {1, 1024}))
|
||||
/ 1024;
|
||||
}
|
||||
|
||||
@@ -69,7 +69,7 @@ FromFfmpegTime(int64_t t, const AVRational time_base) noexcept
|
||||
assert(t != (int64_t)AV_NOPTS_VALUE);
|
||||
|
||||
return SongTime::FromMS(av_rescale_q(t, time_base,
|
||||
(AVRational){1, 1000}));
|
||||
{1, 1000}));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -41,6 +41,6 @@ public:
|
||||
LockGuard &operator=(const LockGuard &) = delete;
|
||||
};
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -30,6 +30,6 @@ namespace ixmlwrap {
|
||||
const char *getFirstElementValue(IXML_Document *doc,
|
||||
const char *name) noexcept;
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
#endif /* _IXMLWRAP_H_INCLUDED_ */
|
||||
|
||||
@@ -47,6 +47,11 @@ ToNeighborInfo(const UDisks2::Object &o) noexcept
|
||||
return {o.GetUri(), o.path};
|
||||
}
|
||||
|
||||
static constexpr char udisks_neighbor_match[] =
|
||||
"type='signal',sender='" UDISKS2_INTERFACE "',"
|
||||
"interface='" DBUS_OM_INTERFACE "',"
|
||||
"path='" UDISKS2_PATH "'";
|
||||
|
||||
class UdisksNeighborExplorer final
|
||||
: public NeighborExplorer {
|
||||
|
||||
@@ -108,26 +113,37 @@ UdisksNeighborExplorer::DoOpen()
|
||||
|
||||
auto &connection = GetConnection();
|
||||
|
||||
/* this ugly try/catch cascade is only here because this
|
||||
method has no RAII for this method - TODO: improve this */
|
||||
try {
|
||||
Error error;
|
||||
dbus_bus_add_match(connection,
|
||||
"type='signal',sender='" UDISKS2_INTERFACE "',"
|
||||
"interface='" DBUS_OM_INTERFACE "',"
|
||||
"path='" UDISKS2_PATH "'",
|
||||
error);
|
||||
dbus_bus_add_match(connection, udisks_neighbor_match, error);
|
||||
error.CheckThrow("DBus AddMatch error");
|
||||
|
||||
dbus_connection_add_filter(connection,
|
||||
HandleMessage, this,
|
||||
nullptr);
|
||||
try {
|
||||
dbus_connection_add_filter(connection,
|
||||
HandleMessage, this,
|
||||
nullptr);
|
||||
|
||||
auto msg = Message::NewMethodCall(UDISKS2_INTERFACE,
|
||||
UDISKS2_PATH,
|
||||
DBUS_OM_INTERFACE,
|
||||
"GetManagedObjects");
|
||||
list_request.Send(connection, *msg.Get(),
|
||||
std::bind(&UdisksNeighborExplorer::OnListNotify,
|
||||
this, std::placeholders::_1));
|
||||
try {
|
||||
auto msg = Message::NewMethodCall(UDISKS2_INTERFACE,
|
||||
UDISKS2_PATH,
|
||||
DBUS_OM_INTERFACE,
|
||||
"GetManagedObjects");
|
||||
list_request.Send(connection, *msg.Get(),
|
||||
std::bind(&UdisksNeighborExplorer::OnListNotify,
|
||||
this, std::placeholders::_1));
|
||||
} catch (...) {
|
||||
dbus_connection_remove_filter(connection,
|
||||
HandleMessage,
|
||||
this);
|
||||
throw;
|
||||
}
|
||||
} catch (...) {
|
||||
dbus_bus_remove_match(connection,
|
||||
udisks_neighbor_match, nullptr);
|
||||
throw;
|
||||
}
|
||||
} catch (...) {
|
||||
dbus_glue.Destruct();
|
||||
throw;
|
||||
@@ -147,8 +163,10 @@ UdisksNeighborExplorer::DoClose() noexcept
|
||||
list_request.Cancel();
|
||||
}
|
||||
|
||||
// TODO: remove_match
|
||||
// TODO: remove_filter
|
||||
auto &connection = GetConnection();
|
||||
|
||||
dbus_connection_remove_filter(connection, HandleMessage, this);
|
||||
dbus_bus_remove_match(connection, udisks_neighbor_match, nullptr);
|
||||
|
||||
dbus_glue.Destruct();
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
#define IPV4_ADDRESS_HXX
|
||||
|
||||
#include "SocketAddress.hxx"
|
||||
#include "system/ByteOrder.hxx"
|
||||
#include "util/ByteOrder.hxx"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
#define IPV6_ADDRESS_HXX
|
||||
|
||||
#include "SocketAddress.hxx"
|
||||
#include "system/ByteOrder.hxx"
|
||||
#include "util/ByteOrder.hxx"
|
||||
#include "util/Compiler.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
@@ -39,6 +39,7 @@
|
||||
#include "config/Option.hxx"
|
||||
#include "config/Block.hxx"
|
||||
#include "util/RuntimeError.hxx"
|
||||
#include "util/StringAPI.hxx"
|
||||
#include "util/StringFormat.hxx"
|
||||
#include "Log.hxx"
|
||||
|
||||
@@ -214,7 +215,7 @@ FilteredAudioOutput::Setup(EventLoop &event_loop,
|
||||
const char *replay_gain_handler =
|
||||
block.GetBlockValue("replay_gain_handler", "software");
|
||||
|
||||
if (strcmp(replay_gain_handler, "none") != 0) {
|
||||
if (!StringIsEqual(replay_gain_handler, "none")) {
|
||||
prepared_replay_gain_filter =
|
||||
NewReplayGainFilter(replay_gain_config);
|
||||
assert(prepared_replay_gain_filter != nullptr);
|
||||
@@ -240,14 +241,14 @@ FilteredAudioOutput::Setup(EventLoop &event_loop,
|
||||
|
||||
/* use the hardware mixer for replay gain? */
|
||||
|
||||
if (strcmp(replay_gain_handler, "mixer") == 0) {
|
||||
if (StringIsEqual(replay_gain_handler, "mixer")) {
|
||||
if (mixer != nullptr)
|
||||
replay_gain_filter_set_mixer(*prepared_replay_gain_filter,
|
||||
mixer, 100);
|
||||
else
|
||||
FormatError(output_domain,
|
||||
"No such mixer for output '%s'", name);
|
||||
} else if (strcmp(replay_gain_handler, "software") != 0 &&
|
||||
} else if (!StringIsEqual(replay_gain_handler, "software") &&
|
||||
prepared_replay_gain_filter != nullptr) {
|
||||
throw std::runtime_error("Invalid \"replay_gain_handler\" value");
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
#include "config/Data.hxx"
|
||||
#include "config/Option.hxx"
|
||||
#include "util/RuntimeError.hxx"
|
||||
#include "util/StringAPI.hxx"
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
@@ -147,7 +148,7 @@ AudioOutputControl *
|
||||
MultipleOutputs::FindByName(const char *name) noexcept
|
||||
{
|
||||
for (auto *i : outputs)
|
||||
if (strcmp(i->GetName(), name) == 0)
|
||||
if (StringIsEqual(i->GetName(), name))
|
||||
return i;
|
||||
|
||||
return nullptr;
|
||||
|
||||
@@ -38,8 +38,7 @@
|
||||
#include "plugins/sles/SlesOutputPlugin.hxx"
|
||||
#include "plugins/SolarisOutputPlugin.hxx"
|
||||
#include "plugins/WinmmOutputPlugin.hxx"
|
||||
|
||||
#include <string.h>
|
||||
#include "util/StringAPI.hxx"
|
||||
|
||||
const AudioOutputPlugin *const audio_output_plugins[] = {
|
||||
#ifdef HAVE_SHOUT
|
||||
@@ -101,7 +100,7 @@ const AudioOutputPlugin *
|
||||
AudioOutputPlugin_get(const char *name)
|
||||
{
|
||||
audio_output_plugins_for_each(plugin)
|
||||
if (strcmp(plugin->name, name) == 0)
|
||||
if (StringIsEqual(plugin->name, name))
|
||||
return plugin;
|
||||
|
||||
return nullptr;
|
||||
|
||||
@@ -939,7 +939,7 @@ AlsaOutput::Play(const void *chunk, size_t size)
|
||||
assert(size % in_frame_size == 0);
|
||||
|
||||
const auto e = pcm_export->Export({chunk, size});
|
||||
if (e.size == 0)
|
||||
if (e.empty())
|
||||
/* the DoP (DSD over PCM) filter converts two frames
|
||||
at a time and ignores the last odd frame; if there
|
||||
was only one frame (e.g. the last frame in the
|
||||
|
||||
@@ -25,12 +25,11 @@
|
||||
#include "util/SplitString.hxx"
|
||||
#include "util/RuntimeError.hxx"
|
||||
#include "util/Domain.hxx"
|
||||
#include "util/StringAPI.hxx"
|
||||
#include "Log.hxx"
|
||||
|
||||
#include <ao/ao.h>
|
||||
|
||||
#include <string.h>
|
||||
|
||||
/* An ao_sample_format, with all fields set to zero: */
|
||||
static ao_sample_format OUR_AO_FORMAT_INITIALIZER;
|
||||
|
||||
@@ -105,7 +104,7 @@ AoOutput::AoOutput(const ConfigBlock &block)
|
||||
write_size(block.GetPositiveValue("write_size", 1024U))
|
||||
{
|
||||
const char *value = block.GetBlockValue("driver", "default");
|
||||
if (0 == strcmp(value, "default"))
|
||||
if (StringIsEqual(value, "default"))
|
||||
driver = ao_default_driver_id();
|
||||
else
|
||||
driver = ao_driver_id(value);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -29,6 +29,9 @@
|
||||
#else
|
||||
#include <OpenAL/al.h>
|
||||
#include <OpenAL/alc.h>
|
||||
/* on macOS, OpenAL is deprecated, but since the user asked to enable
|
||||
this plugin, let's ignore the compiler warnings */
|
||||
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
|
||||
#endif
|
||||
|
||||
class OpenALOutput final : AudioOutput {
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
#include "util/ConstBuffer.hxx"
|
||||
#include "util/Domain.hxx"
|
||||
#include "util/Macros.hxx"
|
||||
#include "system/ByteOrder.hxx"
|
||||
#include "util/ByteOrder.hxx"
|
||||
#include "Log.hxx"
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
@@ -35,7 +35,6 @@
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
|
||||
static constexpr unsigned DEFAULT_CONN_TIMEOUT = 2;
|
||||
@@ -123,15 +122,15 @@ ShoutOutput::ShoutOutput(const ConfigBlock &block)
|
||||
unsigned protocol;
|
||||
const char *value = block.GetBlockValue("protocol");
|
||||
if (value != nullptr) {
|
||||
if (0 == strcmp(value, "shoutcast") &&
|
||||
if (StringIsEqual(value, "shoutcast") &&
|
||||
!StringIsEqual(mime_type, "audio/mpeg"))
|
||||
throw FormatRuntimeError("you cannot stream \"%s\" to shoutcast, use mp3",
|
||||
mime_type);
|
||||
else if (0 == strcmp(value, "shoutcast"))
|
||||
else if (StringIsEqual(value, "shoutcast"))
|
||||
protocol = SHOUT_PROTOCOL_ICY;
|
||||
else if (0 == strcmp(value, "icecast1"))
|
||||
else if (StringIsEqual(value, "icecast1"))
|
||||
protocol = SHOUT_PROTOCOL_XAUDIOCAST;
|
||||
else if (0 == strcmp(value, "icecast2"))
|
||||
else if (StringIsEqual(value, "icecast2"))
|
||||
protocol = SHOUT_PROTOCOL_HTTP;
|
||||
else
|
||||
throw FormatRuntimeError("shout protocol \"%s\" is not \"shoutcast\" or "
|
||||
@@ -145,15 +144,15 @@ ShoutOutput::ShoutOutput(const ConfigBlock &block)
|
||||
unsigned tls;
|
||||
value = block.GetBlockValue("tls");
|
||||
if (value != nullptr) {
|
||||
if (0 == strcmp(value, "disabled"))
|
||||
if (StringIsEqual(value, "disabled"))
|
||||
tls = SHOUT_TLS_DISABLED;
|
||||
else if(0 == strcmp(value, "auto"))
|
||||
else if (StringIsEqual(value, "auto"))
|
||||
tls = SHOUT_TLS_AUTO;
|
||||
else if(0 == strcmp(value, "auto_no_plain"))
|
||||
else if (StringIsEqual(value, "auto_no_plain"))
|
||||
tls = SHOUT_TLS_AUTO_NO_PLAIN;
|
||||
else if(0 == strcmp(value, "rfc2818"))
|
||||
else if (StringIsEqual(value, "rfc2818"))
|
||||
tls = SHOUT_TLS_RFC2818;
|
||||
else if(0 == strcmp(value, "rfc2817"))
|
||||
else if (StringIsEqual(value, "rfc2817"))
|
||||
tls = SHOUT_TLS_RFC2817;
|
||||
else
|
||||
throw FormatRuntimeError("invalid shout TLS option \"%s\"", value);
|
||||
|
||||
@@ -72,17 +72,9 @@ if enable_oss
|
||||
endif
|
||||
|
||||
if is_darwin
|
||||
output_plugins_sources += 'OSXOutputPlugin.cxx'
|
||||
audiounit_dep = declare_dependency(
|
||||
link_args: [
|
||||
'-framework', 'AudioUnit', '-framework', 'CoreAudio', '-framework', 'CoreServices',
|
||||
],
|
||||
dependencies: [
|
||||
boost_dep,
|
||||
],
|
||||
)
|
||||
else
|
||||
audiounit_dep = dependency('', required: false)
|
||||
output_plugins_sources += [
|
||||
'OSXOutputPlugin.cxx',
|
||||
]
|
||||
endif
|
||||
conf.set('HAVE_OSX', is_darwin)
|
||||
|
||||
@@ -145,7 +137,7 @@ output_plugins = static_library(
|
||||
include_directories: inc,
|
||||
dependencies: [
|
||||
alsa_dep,
|
||||
audiounit_dep,
|
||||
apple_dep,
|
||||
libao_dep,
|
||||
libjack_dep,
|
||||
pulse_dep,
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
#include "thread/Cond.hxx"
|
||||
#include "util/Macros.hxx"
|
||||
#include "util/Domain.hxx"
|
||||
#include "system/ByteOrder.hxx"
|
||||
#include "util/ByteOrder.hxx"
|
||||
#include "mixer/MixerList.hxx"
|
||||
#include "Log.hxx"
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
*/
|
||||
|
||||
#include "PcmPack.hxx"
|
||||
#include "system/ByteOrder.hxx"
|
||||
#include "util/ByteOrder.hxx"
|
||||
|
||||
static void
|
||||
pack_sample(uint8_t *dest, const int32_t *src0) noexcept
|
||||
|
||||
@@ -62,7 +62,7 @@ mixramp_interpolate(const char *ramp_list, float required_db) noexcept
|
||||
++ramp_list;
|
||||
|
||||
/* Check for exact match. */
|
||||
if (db >= required_db) {
|
||||
if (db == required_db) {
|
||||
return duration;
|
||||
}
|
||||
|
||||
|
||||
@@ -223,7 +223,8 @@ private:
|
||||
*
|
||||
* Caller must lock the mutex.
|
||||
*/
|
||||
void StartDecoder(std::shared_ptr<MusicPipe> pipe) noexcept;
|
||||
void StartDecoder(std::shared_ptr<MusicPipe> pipe,
|
||||
bool initial_seek_essential) noexcept;
|
||||
|
||||
/**
|
||||
* The decoder has acknowledged the "START" command (see
|
||||
@@ -364,7 +365,8 @@ public:
|
||||
};
|
||||
|
||||
void
|
||||
Player::StartDecoder(std::shared_ptr<MusicPipe> _pipe) noexcept
|
||||
Player::StartDecoder(std::shared_ptr<MusicPipe> _pipe,
|
||||
bool initial_seek_essential) noexcept
|
||||
{
|
||||
assert(queued || pc.command == PlayerCommand::SEEK);
|
||||
assert(pc.next_song != nullptr);
|
||||
@@ -376,6 +378,7 @@ Player::StartDecoder(std::shared_ptr<MusicPipe> _pipe) noexcept
|
||||
|
||||
dc.Start(std::make_unique<DetachedSong>(*pc.next_song),
|
||||
start_time, pc.next_song->GetEndTime(),
|
||||
initial_seek_essential,
|
||||
buffer, std::move(_pipe));
|
||||
}
|
||||
|
||||
@@ -633,7 +636,7 @@ Player::SeekDecoder() noexcept
|
||||
pipe->Clear();
|
||||
|
||||
/* re-start the decoder */
|
||||
StartDecoder(pipe);
|
||||
StartDecoder(pipe, true);
|
||||
ActivateDecoder();
|
||||
|
||||
pc.seeking = true;
|
||||
@@ -711,7 +714,7 @@ Player::ProcessCommand() noexcept
|
||||
pc.CommandFinished();
|
||||
|
||||
if (dc.IsIdle())
|
||||
StartDecoder(std::make_shared<MusicPipe>());
|
||||
StartDecoder(std::make_shared<MusicPipe>(), false);
|
||||
|
||||
break;
|
||||
|
||||
@@ -982,7 +985,7 @@ Player::Run() noexcept
|
||||
|
||||
const std::lock_guard<Mutex> lock(pc.mutex);
|
||||
|
||||
StartDecoder(pipe);
|
||||
StartDecoder(pipe, true);
|
||||
ActivateDecoder();
|
||||
|
||||
pc.state = PlayerState::PLAY;
|
||||
@@ -1022,7 +1025,7 @@ Player::Run() noexcept
|
||||
|
||||
assert(dc.pipe == nullptr || dc.pipe == pipe);
|
||||
|
||||
StartDecoder(std::make_shared<MusicPipe>());
|
||||
StartDecoder(std::make_shared<MusicPipe>(), false);
|
||||
}
|
||||
|
||||
if (/* no cross-fading if MPD is going to pause at the
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user