Compare commits
143 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
566787f041 | ||
![]() |
79b2366387 | ||
![]() |
5acea014b0 | ||
![]() |
b72801abf3 | ||
![]() |
23d5a2b862 | ||
![]() |
7715311117 | ||
![]() |
4c1cfca95b | ||
![]() |
e113ce9621 | ||
![]() |
e8213220e2 | ||
![]() |
83f9d2a963 | ||
![]() |
bf97ebf89f | ||
![]() |
5b22d27cbb | ||
![]() |
e907ff43ae | ||
![]() |
b18fc3a8d0 | ||
![]() |
a8e23c4140 | ||
![]() |
fc3861b421 | ||
![]() |
e81bb5d8f1 | ||
![]() |
32f4f15831 | ||
![]() |
e29c06b718 | ||
![]() |
d9d511f33e | ||
![]() |
c61a3b8d13 | ||
![]() |
e10b867fe6 | ||
![]() |
43e230f543 | ||
![]() |
b2ae5298a7 | ||
![]() |
17dd21ac7f | ||
![]() |
1a5e0ef7c9 | ||
![]() |
979a7a1dcc | ||
![]() |
962cf32ba7 | ||
![]() |
ae23682372 | ||
![]() |
540919f256 | ||
![]() |
398281cd76 | ||
![]() |
88446ccde9 | ||
![]() |
6238cc0734 | ||
![]() |
fd4823c507 | ||
![]() |
68bcfd8bf0 | ||
![]() |
1d332746af | ||
![]() |
f3e133c617 | ||
![]() |
1678a6eb59 | ||
![]() |
b4dc2c07d5 | ||
![]() |
d7838950d8 | ||
![]() |
2e93a83dd5 | ||
![]() |
db8b419b8c | ||
![]() |
990f631cbc | ||
![]() |
db46d84458 | ||
![]() |
9e6c4f8d80 | ||
![]() |
41b47f95c5 | ||
![]() |
15939fd87c | ||
![]() |
f63c343f68 | ||
![]() |
1a516e7744 | ||
![]() |
5c9d97775f | ||
![]() |
64aadcd13f | ||
![]() |
1f6a7d6462 | ||
![]() |
e44b953d9a | ||
![]() |
6c85020630 | ||
![]() |
9d910320f3 | ||
![]() |
c53074efc9 | ||
![]() |
3b51c53eca | ||
![]() |
0aa0ffb67b | ||
![]() |
33f70931dd | ||
![]() |
8830ea319f | ||
![]() |
cbcdc73f9a | ||
![]() |
4f6c54ecb3 | ||
![]() |
2bdf1b2284 | ||
![]() |
c876d6a51c | ||
![]() |
3c745b4bc6 | ||
![]() |
3a08a6ad72 | ||
![]() |
448b397cb8 | ||
![]() |
64a1386eb6 | ||
![]() |
77c2efe171 | ||
![]() |
587c0f6232 | ||
![]() |
64e8abf203 | ||
![]() |
6c40d2a656 | ||
![]() |
cf674e9273 | ||
![]() |
9bda0379af | ||
![]() |
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 |
45
NEWS
45
NEWS
@@ -1,3 +1,48 @@
|
|||||||
|
ver 0.21.26 (2020/09/21)
|
||||||
|
* database
|
||||||
|
- inotify: obey ".mpdignore" files
|
||||||
|
* output
|
||||||
|
- osx: fix crash bug
|
||||||
|
- sles: support floating point samples
|
||||||
|
* archive
|
||||||
|
- bzip2: fix crash on corrupt bzip2 file
|
||||||
|
- bzip2: flush output at end of input file
|
||||||
|
- iso9660: fix unaligned reads
|
||||||
|
- iso9660: support seeking
|
||||||
|
- zzip: fix crash on corrupt ZIP file
|
||||||
|
* decoder
|
||||||
|
- ffmpeg: remove "rtsp://" from the list of supported protocols
|
||||||
|
- ffmpeg: add "hls+http://" to the list of supported protocols
|
||||||
|
- opus: support the gain value from the Opus header
|
||||||
|
- sndfile: fix lost samples at end of file
|
||||||
|
* fix "single" mode bug after resuming playback
|
||||||
|
* the default log_level is "default", not "info"
|
||||||
|
|
||||||
|
ver 0.21.25 (2020/07/06)
|
||||||
|
* protocol:
|
||||||
|
- fix crash when using "rangeid" while playing
|
||||||
|
* database
|
||||||
|
- simple: automatically scan new mounts
|
||||||
|
- upnp: fix compatibility with Plex DLNA
|
||||||
|
* storage
|
||||||
|
- fix disappearing mounts after mounting twice
|
||||||
|
- udisks: fix reading ".mpdignore"
|
||||||
|
* input
|
||||||
|
- file: detect premature end of file
|
||||||
|
- smbclient: don't send credentials to MPD clients
|
||||||
|
* decoder
|
||||||
|
- opus: apply pre-skip and end trimming
|
||||||
|
- opus: fix memory leak
|
||||||
|
- opus: fix crash bug
|
||||||
|
- vorbis: fix crash bug
|
||||||
|
* output
|
||||||
|
- osx: improve sample rate selection
|
||||||
|
- osx: fix noise while stopping
|
||||||
|
* neighbor
|
||||||
|
- upnp: fix crash during shutdown
|
||||||
|
* Windows/Android:
|
||||||
|
- fix Boost detection after breaking change in Meson 0.54
|
||||||
|
|
||||||
ver 0.21.24 (2020/06/10)
|
ver 0.21.24 (2020/06/10)
|
||||||
* protocol
|
* protocol
|
||||||
- "tagtypes" requires no permissions
|
- "tagtypes" requires no permissions
|
||||||
|
@@ -2,8 +2,8 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
package="org.musicpd"
|
package="org.musicpd"
|
||||||
android:installLocation="auto"
|
android:installLocation="auto"
|
||||||
android:versionCode="47"
|
android:versionCode="49"
|
||||||
android:versionName="0.21.24">
|
android:versionName="0.21.26">
|
||||||
|
|
||||||
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="28"/>
|
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="28"/>
|
||||||
|
|
||||||
|
@@ -38,7 +38,7 @@ author = 'Max Kellermann'
|
|||||||
# built documents.
|
# built documents.
|
||||||
#
|
#
|
||||||
# The short X.Y version.
|
# The short X.Y version.
|
||||||
version = '0.21.24'
|
version = '0.21.26'
|
||||||
# The full version, including alpha/beta/rc tags.
|
# The full version, including alpha/beta/rc tags.
|
||||||
release = version
|
release = version
|
||||||
|
|
||||||
|
@@ -60,25 +60,25 @@ The default plugin which gives :program:`MPD` access to local files. It is used
|
|||||||
curl
|
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
|
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
|
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`:
|
See :ref:`input_nfs` for more information.
|
||||||
|
|
||||||
.. 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.
|
|
||||||
|
|
||||||
udisks
|
udisks
|
||||||
------
|
------
|
||||||
@@ -162,7 +162,10 @@ curl
|
|||||||
|
|
||||||
Opens remote files or streams over HTTP using libcurl.
|
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::
|
.. list-table::
|
||||||
:widths: 20 80
|
:widths: 20 80
|
||||||
@@ -182,7 +185,9 @@ Note that unless overridden by the below settings (e.g. by setting them to a bla
|
|||||||
ffmpeg
|
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
|
file
|
||||||
----
|
----
|
||||||
@@ -194,30 +199,51 @@ mms
|
|||||||
|
|
||||||
Plays streams with the MMS protocol using `libmms <https://launchpad.net/libmms>`_.
|
Plays streams with the MMS protocol using `libmms <https://launchpad.net/libmms>`_.
|
||||||
|
|
||||||
|
.. _input_nfs:
|
||||||
|
|
||||||
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
|
.. code-block:: none
|
||||||
|
|
||||||
mpc add nfs://servername/path/filename.ogg
|
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
|
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
|
.. code-block:: none
|
||||||
|
|
||||||
mpc add smb://servername/sharename/filename.ogg
|
mpc add smb://servername/sharename/filename.ogg
|
||||||
|
mpc add smb://username:password@servername/sharename/filename.ogg
|
||||||
|
|
||||||
qobuz
|
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
|
.. code-block:: none
|
||||||
|
|
||||||
@@ -243,7 +269,9 @@ Play songs from the commercial streaming service Qobuz. It plays URLs in the for
|
|||||||
tidal
|
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::
|
.. warning::
|
||||||
|
|
||||||
@@ -1044,7 +1072,8 @@ sles
|
|||||||
|
|
||||||
Plugin using the `OpenSL ES <https://www.khronos.org/opensles/>`__
|
Plugin using the `OpenSL ES <https://www.khronos.org/opensles/>`__
|
||||||
audio API. Its primary use is local playback on Android, where
|
audio API. Its primary use is local playback on Android, where
|
||||||
:ref:`ALSA <alsa_plugin>` is not available.
|
:ref:`ALSA <alsa_plugin>` is not available. It supports 16 bit and
|
||||||
|
floating point samples.
|
||||||
|
|
||||||
|
|
||||||
solaris
|
solaris
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
project(
|
project(
|
||||||
'mpd',
|
'mpd',
|
||||||
['c', 'cpp'],
|
['c', 'cpp'],
|
||||||
version: '0.21.24',
|
version: '0.21.26',
|
||||||
meson_version: '>= 0.49.0',
|
meson_version: '>= 0.49.0',
|
||||||
default_options: [
|
default_options: [
|
||||||
'c_std=c99',
|
'c_std=c99',
|
||||||
@@ -319,6 +319,8 @@ subdir('src/thread')
|
|||||||
subdir('src/net')
|
subdir('src/net')
|
||||||
subdir('src/event')
|
subdir('src/event')
|
||||||
|
|
||||||
|
subdir('src/apple')
|
||||||
|
|
||||||
subdir('src/lib/dbus')
|
subdir('src/lib/dbus')
|
||||||
subdir('src/lib/icu')
|
subdir('src/lib/icu')
|
||||||
subdir('src/lib/smbclient')
|
subdir('src/lib/smbclient')
|
||||||
|
@@ -10,8 +10,8 @@ from build.ffmpeg import FfmpegProject
|
|||||||
from build.boost import BoostProject
|
from build.boost import BoostProject
|
||||||
|
|
||||||
libmpdclient = MesonProject(
|
libmpdclient = MesonProject(
|
||||||
'https://www.musicpd.org/download/libmpdclient/2/libmpdclient-2.18.tar.xz',
|
'https://www.musicpd.org/download/libmpdclient/2/libmpdclient-2.19.tar.xz',
|
||||||
'4cb01e1f567e0169aca94875fb6e1200e7f5ce35b63a4df768ec1591fb1081fa',
|
'158aad4c2278ab08e76a3f2b0166c99b39fae00ee17231bd225c5a36e977a189',
|
||||||
'lib/libmpdclient.a',
|
'lib/libmpdclient.a',
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -25,8 +25,8 @@ libogg = AutotoolsProject(
|
|||||||
)
|
)
|
||||||
|
|
||||||
libvorbis = AutotoolsProject(
|
libvorbis = AutotoolsProject(
|
||||||
'http://downloads.xiph.org/releases/vorbis/libvorbis-1.3.6.tar.xz',
|
'http://downloads.xiph.org/releases/vorbis/libvorbis-1.3.7.tar.xz',
|
||||||
'af00bb5a784e7c9e69f56823de4637c350643deedaf333d0fa86ecdba6fcb415',
|
'b33cc4934322bcbf6efcbacf49e3ca01aadbea4114ec9589d1b1e9d20f72954b',
|
||||||
'lib/libvorbis.a',
|
'lib/libvorbis.a',
|
||||||
[
|
[
|
||||||
'--disable-shared', '--enable-static',
|
'--disable-shared', '--enable-static',
|
||||||
@@ -148,8 +148,8 @@ gme = CmakeProject(
|
|||||||
)
|
)
|
||||||
|
|
||||||
ffmpeg = FfmpegProject(
|
ffmpeg = FfmpegProject(
|
||||||
'http://ffmpeg.org/releases/ffmpeg-4.2.3.tar.xz',
|
'http://ffmpeg.org/releases/ffmpeg-4.3.1.tar.xz',
|
||||||
'9df6c90aed1337634c1fb026fb01c154c29c82a64ea71291ff2da9aacb9aad31',
|
'ad009240d46e307b4e03a213a0f49c11b650e445b1f8be0dda2a9212b34d2ffb',
|
||||||
'lib/libavcodec.a',
|
'lib/libavcodec.a',
|
||||||
[
|
[
|
||||||
'--disable-shared', '--enable-static',
|
'--disable-shared', '--enable-static',
|
||||||
@@ -377,8 +377,8 @@ ffmpeg = FfmpegProject(
|
|||||||
)
|
)
|
||||||
|
|
||||||
curl = AutotoolsProject(
|
curl = AutotoolsProject(
|
||||||
'http://curl.haxx.se/download/curl-7.70.0.tar.xz',
|
'http://curl.haxx.se/download/curl-7.72.0.tar.xz',
|
||||||
'032f43f2674008c761af19bf536374128c16241fb234699a55f9fb603fcfbae7',
|
'0ded0808c4d85f2ee0db86980ae610cc9d165e9ca9da466196cc73c346513713',
|
||||||
'lib/libcurl.a',
|
'lib/libcurl.a',
|
||||||
[
|
[
|
||||||
'--disable-shared', '--enable-static',
|
'--disable-shared', '--enable-static',
|
||||||
@@ -433,7 +433,7 @@ libnfs = AutotoolsProject(
|
|||||||
)
|
)
|
||||||
|
|
||||||
boost = BoostProject(
|
boost = BoostProject(
|
||||||
'https://dl.bintray.com/boostorg/release/1.73.0/source/boost_1_73_0.tar.bz2',
|
'https://dl.bintray.com/boostorg/release/1.74.0/source/boost_1_74_0.tar.bz2',
|
||||||
'4eb3b8d442b426dc35346235c8733b5ae35ba431690e38c6a8263dce9fcbb402',
|
'83bfc1507731a0906e387fc28b7ef5417d591429e51e788417fe9ff025e116b1',
|
||||||
'include/boost/version.hpp',
|
'include/boost/version.hpp',
|
||||||
)
|
)
|
||||||
|
@@ -91,7 +91,12 @@ def configure(toolchain, src, build, args=()):
|
|||||||
'--cross-file', cross_file,
|
'--cross-file', cross_file,
|
||||||
] + args
|
] + 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):
|
class MesonProject(Project):
|
||||||
def __init__(self, url, md5, installed, configure_args=[],
|
def __init__(self, url, md5, installed, configure_args=[],
|
||||||
|
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
|
* http://www.musicpd.org
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or modify
|
* This program is free software; you can redistribute it and/or modify
|
||||||
@@ -19,8 +19,7 @@
|
|||||||
|
|
||||||
#include "LogV.hxx"
|
#include "LogV.hxx"
|
||||||
#include "util/Domain.hxx"
|
#include "util/Domain.hxx"
|
||||||
|
#include "util/Exception.hxx"
|
||||||
#include <exception>
|
|
||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
@@ -29,20 +28,20 @@
|
|||||||
static constexpr Domain exception_domain("exception");
|
static constexpr Domain exception_domain("exception");
|
||||||
|
|
||||||
void
|
void
|
||||||
LogFormatV(const Domain &domain, LogLevel level,
|
LogFormatV(LogLevel level, const Domain &domain,
|
||||||
const char *fmt, va_list ap) noexcept
|
const char *fmt, va_list ap) noexcept
|
||||||
{
|
{
|
||||||
char msg[1024];
|
char msg[1024];
|
||||||
vsnprintf(msg, sizeof(msg), fmt, ap);
|
vsnprintf(msg, sizeof(msg), fmt, ap);
|
||||||
Log(domain, level, msg);
|
Log(level, domain, msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
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_list ap;
|
||||||
va_start(ap, fmt);
|
va_start(ap, fmt);
|
||||||
LogFormatV(domain, level, fmt, ap);
|
LogFormatV(level, domain, fmt, ap);
|
||||||
va_end(ap);
|
va_end(ap);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,7 +50,7 @@ FormatDebug(const Domain &domain, const char *fmt, ...) noexcept
|
|||||||
{
|
{
|
||||||
va_list ap;
|
va_list ap;
|
||||||
va_start(ap, fmt);
|
va_start(ap, fmt);
|
||||||
LogFormatV(domain, LogLevel::DEBUG, fmt, ap);
|
LogFormatV(LogLevel::DEBUG, domain, fmt, ap);
|
||||||
va_end(ap);
|
va_end(ap);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,7 +59,7 @@ FormatInfo(const Domain &domain, const char *fmt, ...) noexcept
|
|||||||
{
|
{
|
||||||
va_list ap;
|
va_list ap;
|
||||||
va_start(ap, fmt);
|
va_start(ap, fmt);
|
||||||
LogFormatV(domain, LogLevel::INFO, fmt, ap);
|
LogFormatV(LogLevel::INFO, domain, fmt, ap);
|
||||||
va_end(ap);
|
va_end(ap);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,7 +68,7 @@ FormatDefault(const Domain &domain, const char *fmt, ...) noexcept
|
|||||||
{
|
{
|
||||||
va_list ap;
|
va_list ap;
|
||||||
va_start(ap, fmt);
|
va_start(ap, fmt);
|
||||||
LogFormatV(domain, LogLevel::DEFAULT, fmt, ap);
|
LogFormatV(LogLevel::DEFAULT, domain, fmt, ap);
|
||||||
va_end(ap);
|
va_end(ap);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,7 +77,7 @@ FormatWarning(const Domain &domain, const char *fmt, ...) noexcept
|
|||||||
{
|
{
|
||||||
va_list ap;
|
va_list ap;
|
||||||
va_start(ap, fmt);
|
va_start(ap, fmt);
|
||||||
LogFormatV(domain, LogLevel::WARNING, fmt, ap);
|
LogFormatV(LogLevel::WARNING, domain, fmt, ap);
|
||||||
va_end(ap);
|
va_end(ap);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,42 +86,24 @@ FormatError(const Domain &domain, const char *fmt, ...) noexcept
|
|||||||
{
|
{
|
||||||
va_list ap;
|
va_list ap;
|
||||||
va_start(ap, fmt);
|
va_start(ap, fmt);
|
||||||
LogFormatV(domain, LogLevel::ERROR, fmt, ap);
|
LogFormatV(LogLevel::ERROR, domain, fmt, ap);
|
||||||
va_end(ap);
|
va_end(ap);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
LogError(const std::exception &e) noexcept
|
Log(LogLevel level, const std::exception &e) noexcept
|
||||||
{
|
{
|
||||||
Log(exception_domain, LogLevel::ERROR, e.what());
|
Log(level, exception_domain, GetFullMessage(e).c_str());
|
||||||
|
|
||||||
try {
|
|
||||||
std::rethrow_if_nested(e);
|
|
||||||
} catch (const std::exception &nested) {
|
|
||||||
LogError(nested, "nested");
|
|
||||||
} catch (...) {
|
|
||||||
Log(exception_domain, LogLevel::ERROR,
|
|
||||||
"Unrecognized nested exception");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
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());
|
LogFormat(level, exception_domain, "%s: %s", msg, GetFullMessage(e).c_str());
|
||||||
|
|
||||||
try {
|
|
||||||
std::rethrow_if_nested(e);
|
|
||||||
} catch (const std::exception &nested) {
|
|
||||||
LogError(nested);
|
|
||||||
} catch (...) {
|
|
||||||
Log(exception_domain, LogLevel::ERROR,
|
|
||||||
"Unrecognized nested exception");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
FormatError(const std::exception &e, const char *fmt, ...) noexcept
|
LogFormat(LogLevel level, const std::exception &e, const char *fmt, ...) noexcept
|
||||||
{
|
{
|
||||||
char msg[1024];
|
char msg[1024];
|
||||||
va_list ap;
|
va_list ap;
|
||||||
@@ -130,37 +111,24 @@ FormatError(const std::exception &e, const char *fmt, ...) noexcept
|
|||||||
vsnprintf(msg, sizeof(msg), fmt, ap);
|
vsnprintf(msg, sizeof(msg), fmt, ap);
|
||||||
va_end(ap);
|
va_end(ap);
|
||||||
|
|
||||||
LogError(e, msg);
|
Log(level, e, msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
LogError(const std::exception_ptr &ep) noexcept
|
Log(LogLevel level, const std::exception_ptr &ep) noexcept
|
||||||
{
|
{
|
||||||
try {
|
Log(level, exception_domain, GetFullMessage(ep).c_str());
|
||||||
std::rethrow_exception(ep);
|
|
||||||
} catch (const std::exception &e) {
|
|
||||||
LogError(e);
|
|
||||||
} catch (...) {
|
|
||||||
Log(exception_domain, LogLevel::ERROR,
|
|
||||||
"Unrecognized exception");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
LogError(const std::exception_ptr &ep, const char *msg) noexcept
|
Log(LogLevel level, const std::exception_ptr &ep, const char *msg) noexcept
|
||||||
{
|
{
|
||||||
try {
|
LogFormat(level, exception_domain, "%s: %s", msg,
|
||||||
std::rethrow_exception(ep);
|
GetFullMessage(ep).c_str());
|
||||||
} catch (const std::exception &e) {
|
|
||||||
LogError(e, msg);
|
|
||||||
} catch (...) {
|
|
||||||
FormatError(exception_domain,
|
|
||||||
"%s: Unrecognized exception", msg);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
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];
|
char msg[1024];
|
||||||
va_list ap;
|
va_list ap;
|
||||||
@@ -168,13 +136,13 @@ FormatError(const std::exception_ptr &ep, const char *fmt, ...) noexcept
|
|||||||
vsnprintf(msg, sizeof(msg), fmt, ap);
|
vsnprintf(msg, sizeof(msg), fmt, ap);
|
||||||
va_end(ap);
|
va_end(ap);
|
||||||
|
|
||||||
LogError(ep, msg);
|
Log(level, ep, msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
LogErrno(const Domain &domain, int e, const char *msg) noexcept
|
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
|
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
|
* http://www.musicpd.org
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or modify
|
* This program is free software; you can redistribute it and/or modify
|
||||||
@@ -28,16 +28,38 @@
|
|||||||
class Domain;
|
class Domain;
|
||||||
|
|
||||||
void
|
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)
|
gcc_printf(3,4)
|
||||||
void
|
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
|
static inline void
|
||||||
LogDebug(const Domain &domain, const char *msg) noexcept
|
LogDebug(const Domain &domain, const char *msg) noexcept
|
||||||
{
|
{
|
||||||
Log(domain, LogLevel::DEBUG, msg);
|
Log(LogLevel::DEBUG, domain, msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
gcc_printf(2,3)
|
gcc_printf(2,3)
|
||||||
@@ -47,7 +69,7 @@ FormatDebug(const Domain &domain, const char *fmt, ...) noexcept;
|
|||||||
static inline void
|
static inline void
|
||||||
LogInfo(const Domain &domain, const char *msg) noexcept
|
LogInfo(const Domain &domain, const char *msg) noexcept
|
||||||
{
|
{
|
||||||
Log(domain, LogLevel::INFO, msg);
|
Log(LogLevel::INFO, domain, msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
gcc_printf(2,3)
|
gcc_printf(2,3)
|
||||||
@@ -57,7 +79,7 @@ FormatInfo(const Domain &domain, const char *fmt, ...) noexcept;
|
|||||||
static inline void
|
static inline void
|
||||||
LogDefault(const Domain &domain, const char *msg) noexcept
|
LogDefault(const Domain &domain, const char *msg) noexcept
|
||||||
{
|
{
|
||||||
Log(domain, LogLevel::DEFAULT, msg);
|
Log(LogLevel::DEFAULT, domain, msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
gcc_printf(2,3)
|
gcc_printf(2,3)
|
||||||
@@ -67,7 +89,7 @@ FormatDefault(const Domain &domain, const char *fmt, ...) noexcept;
|
|||||||
static inline void
|
static inline void
|
||||||
LogWarning(const Domain &domain, const char *msg) noexcept
|
LogWarning(const Domain &domain, const char *msg) noexcept
|
||||||
{
|
{
|
||||||
Log(domain, LogLevel::WARNING, msg);
|
Log(LogLevel::WARNING, domain, msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
gcc_printf(2,3)
|
gcc_printf(2,3)
|
||||||
@@ -77,28 +99,47 @@ FormatWarning(const Domain &domain, const char *fmt, ...) noexcept;
|
|||||||
static inline void
|
static inline void
|
||||||
LogError(const Domain &domain, const char *msg) noexcept
|
LogError(const Domain &domain, const char *msg) noexcept
|
||||||
{
|
{
|
||||||
Log(domain, LogLevel::ERROR, msg);
|
Log(LogLevel::ERROR, domain, msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
inline void
|
||||||
LogError(const std::exception &e) noexcept;
|
LogError(const std::exception &e) noexcept
|
||||||
|
{
|
||||||
|
Log(LogLevel::ERROR, e);
|
||||||
|
}
|
||||||
|
|
||||||
void
|
inline void
|
||||||
LogError(const std::exception &e, const char *msg) noexcept;
|
LogError(const std::exception &e, const char *msg) noexcept
|
||||||
|
{
|
||||||
|
Log(LogLevel::ERROR, e, msg);
|
||||||
|
}
|
||||||
|
|
||||||
gcc_printf(2,3)
|
template<typename... Args>
|
||||||
void
|
inline void
|
||||||
FormatError(const std::exception &e, const char *fmt, ...) noexcept;
|
FormatError(const std::exception &e, const char *fmt, Args&&... args) noexcept
|
||||||
|
{
|
||||||
|
LogFormat(LogLevel::ERROR, e, fmt, std::forward<Args>(args)...);
|
||||||
|
}
|
||||||
|
|
||||||
void
|
inline void
|
||||||
LogError(const std::exception_ptr &ep) noexcept;
|
LogError(const std::exception_ptr &ep) noexcept
|
||||||
|
{
|
||||||
|
Log(LogLevel::ERROR, ep);
|
||||||
|
}
|
||||||
|
|
||||||
void
|
inline void
|
||||||
LogError(const std::exception_ptr &ep, const char *msg) noexcept;
|
LogError(const std::exception_ptr &ep, const char *msg) noexcept
|
||||||
|
{
|
||||||
|
Log(LogLevel::ERROR, ep, msg);
|
||||||
|
}
|
||||||
|
|
||||||
gcc_printf(2,3)
|
template<typename... Args>
|
||||||
void
|
inline void
|
||||||
FormatError(const std::exception_ptr &ep, const char *fmt, ...) noexcept;
|
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)
|
gcc_printf(2,3)
|
||||||
void
|
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
|
* http://www.musicpd.org
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or modify
|
* This program is free software; you can redistribute it and/or modify
|
||||||
@@ -61,7 +61,7 @@ ToAndroidLogLevel(LogLevel log_level) noexcept
|
|||||||
|
|
||||||
#else
|
#else
|
||||||
|
|
||||||
static LogLevel log_threshold = LogLevel::INFO;
|
static LogLevel log_threshold = LogLevel::DEFAULT;
|
||||||
|
|
||||||
static bool enable_timestamp;
|
static bool enable_timestamp;
|
||||||
|
|
||||||
@@ -176,7 +176,7 @@ FileLog(const Domain &domain, const char *message) noexcept
|
|||||||
#endif /* !ANDROID */
|
#endif /* !ANDROID */
|
||||||
|
|
||||||
void
|
void
|
||||||
Log(const Domain &domain, LogLevel level, const char *msg) noexcept
|
Log(LogLevel level, const Domain &domain, const char *msg) noexcept
|
||||||
{
|
{
|
||||||
#ifdef ANDROID
|
#ifdef ANDROID
|
||||||
__android_log_print(ToAndroidLogLevel(level), "MPD",
|
__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
|
* http://www.musicpd.org
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or modify
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2003-2018 The Music Player Daemon Project
|
* Copyright 2003-2019 The Music Player Daemon Project
|
||||||
* http://www.musicpd.org
|
* http://www.musicpd.org
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or modify
|
* This program is free software; you can redistribute it and/or modify
|
||||||
@@ -25,7 +25,7 @@
|
|||||||
#include <stdarg.h>
|
#include <stdarg.h>
|
||||||
|
|
||||||
void
|
void
|
||||||
LogFormatV(const Domain &domain, LogLevel level,
|
LogFormatV(LogLevel level, const Domain &domain,
|
||||||
const char *fmt, va_list ap) noexcept;
|
const char *fmt, va_list ap) noexcept;
|
||||||
|
|
||||||
#endif /* LOG_H */
|
#endif /* LOG_H */
|
||||||
|
@@ -101,7 +101,7 @@ initPermissions(const ConfigData &config)
|
|||||||
const char *separator = strchr(param.value.c_str(),
|
const char *separator = strchr(param.value.c_str(),
|
||||||
PERMISSION_PASSWORD_CHAR);
|
PERMISSION_PASSWORD_CHAR);
|
||||||
|
|
||||||
if (separator == NULL)
|
if (separator == nullptr)
|
||||||
throw FormatRuntimeError("\"%c\" not found in password string "
|
throw FormatRuntimeError("\"%c\" not found in password string "
|
||||||
"\"%s\", line %i",
|
"\"%s\", line %i",
|
||||||
PERMISSION_PASSWORD_CHAR,
|
PERMISSION_PASSWORD_CHAR,
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2003-2018 The Music Player Daemon Project
|
* Copyright 2003-2020 The Music Player Daemon Project
|
||||||
* http://www.musicpd.org
|
* http://www.musicpd.org
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or modify
|
* This program is free software; you can redistribute it and/or modify
|
||||||
@@ -27,10 +27,20 @@
|
|||||||
* that this plugin is unavailable. It will be disabled, and MPD can
|
* that this plugin is unavailable. It will be disabled, and MPD can
|
||||||
* continue initialization.
|
* continue initialization.
|
||||||
*/
|
*/
|
||||||
class PluginUnavailable final : public std::runtime_error {
|
class PluginUnavailable : public std::runtime_error {
|
||||||
public:
|
public:
|
||||||
explicit PluginUnavailable(const char *msg)
|
using std::runtime_error::runtime_error;
|
||||||
:std::runtime_error(msg) {}
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
#endif
|
||||||
|
@@ -23,9 +23,9 @@
|
|||||||
#include "config/Data.hxx"
|
#include "config/Data.hxx"
|
||||||
#include "util/RuntimeError.hxx"
|
#include "util/RuntimeError.hxx"
|
||||||
|
|
||||||
#include <assert.h>
|
#include <cassert>
|
||||||
#include <stdlib.h>
|
#include <cstdlib>
|
||||||
#include <math.h>
|
#include <cmath>
|
||||||
|
|
||||||
static float
|
static float
|
||||||
ParsePreamp(const char *s)
|
ParsePreamp(const char *s)
|
||||||
@@ -33,14 +33,14 @@ ParsePreamp(const char *s)
|
|||||||
assert(s != nullptr);
|
assert(s != nullptr);
|
||||||
|
|
||||||
char *endptr;
|
char *endptr;
|
||||||
float f = strtod(s, &endptr);
|
float f = std::strtof(s, &endptr);
|
||||||
if (endptr == s || *endptr != '\0')
|
if (endptr == s || *endptr != '\0')
|
||||||
throw std::invalid_argument("Not a numeric value");
|
throw std::invalid_argument("Not a numeric value");
|
||||||
|
|
||||||
if (f < -15 || f > 15)
|
if (f < -15.0f || f > 15.0f)
|
||||||
throw std::invalid_argument("Number must be between -15 and 15");
|
throw std::invalid_argument("Number must be between -15 and 15");
|
||||||
|
|
||||||
return pow(10, f / 20.0);
|
return std::pow(10.0f, f / 20.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
static float
|
static float
|
||||||
|
@@ -20,7 +20,7 @@
|
|||||||
#include "ReplayGainInfo.hxx"
|
#include "ReplayGainInfo.hxx"
|
||||||
#include "ReplayGainConfig.hxx"
|
#include "ReplayGainConfig.hxx"
|
||||||
|
|
||||||
#include <math.h>
|
#include <cmath>
|
||||||
|
|
||||||
float
|
float
|
||||||
ReplayGainTuple::CalculateScale(const ReplayGainConfig &config) const noexcept
|
ReplayGainTuple::CalculateScale(const ReplayGainConfig &config) const noexcept
|
||||||
@@ -28,13 +28,13 @@ ReplayGainTuple::CalculateScale(const ReplayGainConfig &config) const noexcept
|
|||||||
float scale;
|
float scale;
|
||||||
|
|
||||||
if (IsDefined()) {
|
if (IsDefined()) {
|
||||||
scale = pow(10.0, gain / 20.0);
|
scale = std::pow(10.0f, gain / 20.0f);
|
||||||
scale *= config.preamp;
|
scale *= config.preamp;
|
||||||
if (scale > 15.0)
|
if (scale > 15.0f)
|
||||||
scale = 15.0;
|
scale = 15.0f;
|
||||||
|
|
||||||
if (config.limit && scale * peak > 1.0)
|
if (config.limit && scale * peak > 1.0f)
|
||||||
scale = 1.0 / peak;
|
scale = 1.0f / peak;
|
||||||
} else
|
} else
|
||||||
scale = config.missing_preamp;
|
scale = config.missing_preamp;
|
||||||
|
|
||||||
|
@@ -79,6 +79,7 @@ Song::UpdateFile(Storage &storage) noexcept
|
|||||||
TagBuilder tag_builder;
|
TagBuilder tag_builder;
|
||||||
auto new_audio_format = AudioFormat::Undefined();
|
auto new_audio_format = AudioFormat::Undefined();
|
||||||
|
|
||||||
|
try {
|
||||||
const auto path_fs = storage.MapFS(relative_uri.c_str());
|
const auto path_fs = storage.MapFS(relative_uri.c_str());
|
||||||
if (path_fs.IsNull()) {
|
if (path_fs.IsNull()) {
|
||||||
const auto absolute_uri =
|
const auto absolute_uri =
|
||||||
@@ -91,6 +92,10 @@ Song::UpdateFile(Storage &storage) noexcept
|
|||||||
&new_audio_format))
|
&new_audio_format))
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
} catch (...) {
|
||||||
|
// TODO: log or propagate I/O errors?
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
mtime = info.mtime;
|
mtime = info.mtime;
|
||||||
audio_format = new_audio_format;
|
audio_format = new_audio_format;
|
||||||
@@ -153,8 +158,14 @@ DetachedSong::LoadFile(Path path) noexcept
|
|||||||
return false;
|
return false;
|
||||||
|
|
||||||
TagBuilder tag_builder;
|
TagBuilder tag_builder;
|
||||||
|
|
||||||
|
try {
|
||||||
if (!ScanFileTagsWithGeneric(path, tag_builder))
|
if (!ScanFileTagsWithGeneric(path, tag_builder))
|
||||||
return false;
|
return false;
|
||||||
|
} catch (...) {
|
||||||
|
// TODO: log or propagate I/O errors?
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
mtime = fi.GetModificationTime();
|
mtime = fi.GetModificationTime();
|
||||||
tag_builder.Commit(tag);
|
tag_builder.Commit(tag);
|
||||||
@@ -173,8 +184,14 @@ DetachedSong::Update() noexcept
|
|||||||
return LoadFile(path_fs);
|
return LoadFile(path_fs);
|
||||||
} else if (IsRemote()) {
|
} else if (IsRemote()) {
|
||||||
TagBuilder tag_builder;
|
TagBuilder tag_builder;
|
||||||
|
|
||||||
|
try {
|
||||||
if (!tag_stream_scan(uri.c_str(), tag_builder))
|
if (!tag_stream_scan(uri.c_str(), tag_builder))
|
||||||
return false;
|
return false;
|
||||||
|
} catch (...) {
|
||||||
|
// TODO: log or propagate I/O errors?
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
mtime = std::chrono::system_clock::time_point::min();
|
mtime = std::chrono::system_clock::time_point::min();
|
||||||
tag_builder.Commit(tag);
|
tag_builder.Commit(tag);
|
||||||
|
@@ -47,11 +47,11 @@ public:
|
|||||||
handler(_handler),
|
handler(_handler),
|
||||||
is(nullptr) {}
|
is(nullptr) {}
|
||||||
|
|
||||||
bool ScanFile(const DecoderPlugin &plugin) noexcept {
|
bool ScanFile(const DecoderPlugin &plugin) {
|
||||||
return plugin.ScanFile(path_fs, handler);
|
return plugin.ScanFile(path_fs, handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ScanStream(const DecoderPlugin &plugin) noexcept {
|
bool ScanStream(const DecoderPlugin &plugin) {
|
||||||
if (plugin.scan_stream == nullptr)
|
if (plugin.scan_stream == nullptr)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
@@ -73,14 +73,14 @@ public:
|
|||||||
return plugin.ScanStream(*is, handler);
|
return plugin.ScanStream(*is, handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Scan(const DecoderPlugin &plugin) noexcept {
|
bool Scan(const DecoderPlugin &plugin) {
|
||||||
return plugin.SupportsSuffix(suffix) &&
|
return plugin.SupportsSuffix(suffix) &&
|
||||||
(ScanFile(plugin) || ScanStream(plugin));
|
(ScanFile(plugin) || ScanStream(plugin));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
bool
|
bool
|
||||||
ScanFileTagsNoGeneric(Path path_fs, TagHandler &handler) noexcept
|
ScanFileTagsNoGeneric(Path path_fs, TagHandler &handler)
|
||||||
{
|
{
|
||||||
assert(!path_fs.IsNull());
|
assert(!path_fs.IsNull());
|
||||||
|
|
||||||
@@ -100,7 +100,7 @@ ScanFileTagsNoGeneric(Path path_fs, TagHandler &handler) noexcept
|
|||||||
|
|
||||||
bool
|
bool
|
||||||
ScanFileTagsWithGeneric(Path path, TagBuilder &builder,
|
ScanFileTagsWithGeneric(Path path, TagBuilder &builder,
|
||||||
AudioFormat *audio_format) noexcept
|
AudioFormat *audio_format)
|
||||||
{
|
{
|
||||||
FullTagHandler h(builder, 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
|
* but does not fall back to generic scanners (APE and ID3) if no tags
|
||||||
* were found (but the file was recognized).
|
* were found (but the file was recognized).
|
||||||
*
|
*
|
||||||
|
* Throws on I/O error.
|
||||||
|
*
|
||||||
* @return true if the file was recognized (even if no metadata was
|
* @return true if the file was recognized (even if no metadata was
|
||||||
* found)
|
* found)
|
||||||
*/
|
*/
|
||||||
bool
|
bool
|
||||||
ScanFileTagsNoGeneric(Path path, TagHandler &handler) noexcept;
|
ScanFileTagsNoGeneric(Path path, TagHandler &handler);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Scan the tags of a song file. Invokes matching decoder plugins,
|
* Scan the tags of a song file. Invokes matching decoder plugins,
|
||||||
* and falls back to generic scanners (APE and ID3) if no tags were
|
* and falls back to generic scanners (APE and ID3) if no tags were
|
||||||
* found (but the file was recognized).
|
* found (but the file was recognized).
|
||||||
*
|
*
|
||||||
|
* Throws on I/O error.
|
||||||
|
*
|
||||||
* @return true if the file was recognized (even if no metadata was
|
* @return true if the file was recognized (even if no metadata was
|
||||||
* found)
|
* found)
|
||||||
*/
|
*/
|
||||||
bool
|
bool
|
||||||
ScanFileTagsWithGeneric(Path path, TagBuilder &builder,
|
ScanFileTagsWithGeneric(Path path, TagBuilder &builder,
|
||||||
AudioFormat *audio_format=nullptr) noexcept;
|
AudioFormat *audio_format=nullptr);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@@ -45,7 +45,7 @@ CheckDecoderPlugin(const DecoderPlugin &plugin,
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
tag_stream_scan(InputStream &is, TagHandler &handler) noexcept
|
tag_stream_scan(InputStream &is, TagHandler &handler)
|
||||||
{
|
{
|
||||||
assert(is.IsReady());
|
assert(is.IsReady());
|
||||||
|
|
||||||
@@ -73,19 +73,17 @@ tag_stream_scan(InputStream &is, TagHandler &handler) noexcept
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
tag_stream_scan(const char *uri, TagHandler &handler) noexcept
|
tag_stream_scan(const char *uri, TagHandler &handler)
|
||||||
try {
|
{
|
||||||
Mutex mutex;
|
Mutex mutex;
|
||||||
|
|
||||||
auto is = InputStream::OpenReady(uri, mutex);
|
auto is = InputStream::OpenReady(uri, mutex);
|
||||||
return tag_stream_scan(*is, handler);
|
return tag_stream_scan(*is, handler);
|
||||||
} catch (const std::exception &e) {
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
tag_stream_scan(InputStream &is, TagBuilder &builder,
|
tag_stream_scan(InputStream &is, TagBuilder &builder,
|
||||||
AudioFormat *audio_format) noexcept
|
AudioFormat *audio_format)
|
||||||
{
|
{
|
||||||
assert(is.IsReady());
|
assert(is.IsReady());
|
||||||
|
|
||||||
@@ -102,12 +100,10 @@ tag_stream_scan(InputStream &is, TagBuilder &builder,
|
|||||||
|
|
||||||
bool
|
bool
|
||||||
tag_stream_scan(const char *uri, TagBuilder &builder,
|
tag_stream_scan(const char *uri, TagBuilder &builder,
|
||||||
AudioFormat *audio_format) noexcept
|
AudioFormat *audio_format)
|
||||||
try {
|
{
|
||||||
Mutex mutex;
|
Mutex mutex;
|
||||||
|
|
||||||
auto is = InputStream::OpenReady(uri, mutex);
|
auto is = InputStream::OpenReady(uri, mutex);
|
||||||
return tag_stream_scan(*is, builder, audio_format);
|
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
|
* Scan the tags of an #InputStream. Invokes matching decoder
|
||||||
* plugins, but does not invoke the special "APE" and "ID3" scanners.
|
* 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
|
* @return true if the file was recognized (even if no metadata was
|
||||||
* found)
|
* found)
|
||||||
*/
|
*/
|
||||||
bool
|
bool
|
||||||
tag_stream_scan(InputStream &is, TagHandler &handler) noexcept;
|
tag_stream_scan(InputStream &is, TagHandler &handler);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Throws on I/O error.
|
||||||
|
*/
|
||||||
bool
|
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
|
* Scan the tags of an #InputStream. Invokes matching decoder
|
||||||
* plugins, and falls back to generic scanners (APE and ID3) if no
|
* plugins, and falls back to generic scanners (APE and ID3) if no
|
||||||
* tags were found (but the file was recognized).
|
* 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
|
* @return true if the file was recognized (even if no metadata was
|
||||||
* found)
|
* found)
|
||||||
*/
|
*/
|
||||||
bool
|
bool
|
||||||
tag_stream_scan(InputStream &is, TagBuilder &builder,
|
tag_stream_scan(InputStream &is, TagBuilder &builder,
|
||||||
AudioFormat *audio_format=nullptr) noexcept;
|
AudioFormat *audio_format=nullptr);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Throws on I/O error.
|
||||||
|
*/
|
||||||
bool
|
bool
|
||||||
tag_stream_scan(const char *uri, TagBuilder &builder,
|
tag_stream_scan(const char *uri, TagBuilder &builder,
|
||||||
AudioFormat *audio_format=nullptr) noexcept;
|
AudioFormat *audio_format=nullptr);
|
||||||
|
|
||||||
#endif
|
#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,
|
||||||
|
],
|
||||||
|
)
|
@@ -58,9 +58,9 @@ public:
|
|||||||
class Bzip2InputStream final : public InputStream {
|
class Bzip2InputStream final : public InputStream {
|
||||||
std::shared_ptr<InputStream> input;
|
std::shared_ptr<InputStream> input;
|
||||||
|
|
||||||
bool eof = false;
|
bz_stream bzstream{};
|
||||||
|
|
||||||
bz_stream bzstream;
|
bool eof = false;
|
||||||
|
|
||||||
char buffer[5000];
|
char buffer[5000];
|
||||||
|
|
||||||
@@ -68,7 +68,7 @@ public:
|
|||||||
Bzip2InputStream(const std::shared_ptr<InputStream> &_input,
|
Bzip2InputStream(const std::shared_ptr<InputStream> &_input,
|
||||||
const char *uri,
|
const char *uri,
|
||||||
Mutex &mutex);
|
Mutex &mutex);
|
||||||
~Bzip2InputStream();
|
~Bzip2InputStream() noexcept override;
|
||||||
|
|
||||||
/* virtual methods from InputStream */
|
/* virtual methods from InputStream */
|
||||||
bool IsEOF() noexcept override;
|
bool IsEOF() noexcept override;
|
||||||
@@ -79,25 +79,6 @@ private:
|
|||||||
bool FillBuffer();
|
bool FillBuffer();
|
||||||
};
|
};
|
||||||
|
|
||||||
/* single archive handling allocation helpers */
|
|
||||||
|
|
||||||
inline void
|
|
||||||
Bzip2InputStream::Open()
|
|
||||||
{
|
|
||||||
bzstream.bzalloc = nullptr;
|
|
||||||
bzstream.bzfree = nullptr;
|
|
||||||
bzstream.opaque = nullptr;
|
|
||||||
|
|
||||||
bzstream.next_in = (char *)buffer;
|
|
||||||
bzstream.avail_in = 0;
|
|
||||||
|
|
||||||
int ret = BZ2_bzDecompressInit(&bzstream, 0, 0);
|
|
||||||
if (ret != BZ_OK)
|
|
||||||
throw std::runtime_error("BZ2_bzDecompressInit() has failed");
|
|
||||||
|
|
||||||
SetReady();
|
|
||||||
}
|
|
||||||
|
|
||||||
/* archive open && listing routine */
|
/* archive open && listing routine */
|
||||||
|
|
||||||
static std::unique_ptr<ArchiveFile>
|
static std::unique_ptr<ArchiveFile>
|
||||||
@@ -116,10 +97,16 @@ Bzip2InputStream::Bzip2InputStream(const std::shared_ptr<InputStream> &_input,
|
|||||||
:InputStream(_uri, _mutex),
|
:InputStream(_uri, _mutex),
|
||||||
input(_input)
|
input(_input)
|
||||||
{
|
{
|
||||||
Open();
|
bzstream.next_in = (char *)buffer;
|
||||||
|
|
||||||
|
int ret = BZ2_bzDecompressInit(&bzstream, 0, 0);
|
||||||
|
if (ret != BZ_OK)
|
||||||
|
throw std::runtime_error("BZ2_bzDecompressInit() has failed");
|
||||||
|
|
||||||
|
SetReady();
|
||||||
}
|
}
|
||||||
|
|
||||||
Bzip2InputStream::~Bzip2InputStream()
|
Bzip2InputStream::~Bzip2InputStream() noexcept
|
||||||
{
|
{
|
||||||
BZ2_bzDecompressEnd(&bzstream);
|
BZ2_bzDecompressEnd(&bzstream);
|
||||||
}
|
}
|
||||||
@@ -149,22 +136,18 @@ Bzip2InputStream::FillBuffer()
|
|||||||
size_t
|
size_t
|
||||||
Bzip2InputStream::Read(void *ptr, size_t length)
|
Bzip2InputStream::Read(void *ptr, size_t length)
|
||||||
{
|
{
|
||||||
const ScopeUnlock unlock(mutex);
|
|
||||||
|
|
||||||
int bz_result;
|
|
||||||
size_t nbytes = 0;
|
|
||||||
|
|
||||||
if (eof)
|
if (eof)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
|
const ScopeUnlock unlock(mutex);
|
||||||
|
|
||||||
bzstream.next_out = (char *)ptr;
|
bzstream.next_out = (char *)ptr;
|
||||||
bzstream.avail_out = length;
|
bzstream.avail_out = length;
|
||||||
|
|
||||||
do {
|
do {
|
||||||
if (!FillBuffer())
|
const bool had_input = FillBuffer();
|
||||||
return 0;
|
|
||||||
|
|
||||||
bz_result = BZ2_bzDecompress(&bzstream);
|
const int bz_result = BZ2_bzDecompress(&bzstream);
|
||||||
|
|
||||||
if (bz_result == BZ_STREAM_END) {
|
if (bz_result == BZ_STREAM_END) {
|
||||||
eof = true;
|
eof = true;
|
||||||
@@ -173,9 +156,12 @@ Bzip2InputStream::Read(void *ptr, size_t length)
|
|||||||
|
|
||||||
if (bz_result != BZ_OK)
|
if (bz_result != BZ_OK)
|
||||||
throw std::runtime_error("BZ2_bzDecompress() has failed");
|
throw std::runtime_error("BZ2_bzDecompress() has failed");
|
||||||
|
|
||||||
|
if (!had_input && bzstream.avail_out == length)
|
||||||
|
throw std::runtime_error("Unexpected end of bzip2 file");
|
||||||
} while (bzstream.avail_out == length);
|
} while (bzstream.avail_out == length);
|
||||||
|
|
||||||
nbytes = length - bzstream.avail_out;
|
const size_t nbytes = length - bzstream.avail_out;
|
||||||
offset += nbytes;
|
offset += nbytes;
|
||||||
|
|
||||||
return nbytes;
|
return nbytes;
|
||||||
|
@@ -29,14 +29,15 @@
|
|||||||
#include "fs/Path.hxx"
|
#include "fs/Path.hxx"
|
||||||
#include "util/RuntimeError.hxx"
|
#include "util/RuntimeError.hxx"
|
||||||
#include "util/StringCompare.hxx"
|
#include "util/StringCompare.hxx"
|
||||||
|
#include "util/WritableBuffer.hxx"
|
||||||
|
|
||||||
#include <cdio/iso9660.h>
|
#include <cdio/iso9660.h>
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
#define CEILING(x, y) ((x+(y-1))/y)
|
|
||||||
|
|
||||||
struct Iso9660 {
|
struct Iso9660 {
|
||||||
iso9660_t *const iso;
|
iso9660_t *const iso;
|
||||||
|
|
||||||
@@ -142,26 +143,86 @@ Iso9660ArchiveFile::Visit(ArchiveVisitor &visitor)
|
|||||||
class Iso9660InputStream final : public InputStream {
|
class Iso9660InputStream final : public InputStream {
|
||||||
std::shared_ptr<Iso9660> iso;
|
std::shared_ptr<Iso9660> iso;
|
||||||
|
|
||||||
iso9660_stat_t *statbuf;
|
const lsn_t lsn;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* libiso9660 can only read whole sectors at a time, and this
|
||||||
|
* buffer is used to store one whole sector and allow Read()
|
||||||
|
* to handle partial sector reads.
|
||||||
|
*/
|
||||||
|
class BlockBuffer {
|
||||||
|
size_t position = 0, fill = 0;
|
||||||
|
|
||||||
|
std::array<uint8_t, ISO_BLOCKSIZE> data;
|
||||||
|
|
||||||
|
public:
|
||||||
|
ConstBuffer<uint8_t> Read() const noexcept {
|
||||||
|
assert(fill <= data.size());
|
||||||
|
assert(position <= fill);
|
||||||
|
|
||||||
|
return {&data[position], &data[fill]};
|
||||||
|
}
|
||||||
|
|
||||||
|
void Consume(size_t nbytes) noexcept {
|
||||||
|
assert(nbytes <= Read().size);
|
||||||
|
|
||||||
|
position += nbytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
WritableBuffer<uint8_t> Write() noexcept {
|
||||||
|
assert(Read().empty());
|
||||||
|
|
||||||
|
return {data.data(), data.size()};
|
||||||
|
}
|
||||||
|
|
||||||
|
void Append(size_t nbytes) noexcept {
|
||||||
|
assert(Read().empty());
|
||||||
|
assert(nbytes <= data.size());
|
||||||
|
|
||||||
|
fill = nbytes;
|
||||||
|
position = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Clear() noexcept {
|
||||||
|
position = fill = 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
BlockBuffer buffer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Skip this number of bytes of the first sector after filling
|
||||||
|
* the buffer next time. This is used for seeking into the
|
||||||
|
* middle of a sector.
|
||||||
|
*/
|
||||||
|
size_t skip = 0;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Iso9660InputStream(const std::shared_ptr<Iso9660> &_iso,
|
Iso9660InputStream(const std::shared_ptr<Iso9660> &_iso,
|
||||||
const char *_uri,
|
const char *_uri,
|
||||||
Mutex &_mutex,
|
Mutex &_mutex,
|
||||||
iso9660_stat_t *_statbuf)
|
lsn_t _lsn, offset_type _size)
|
||||||
:InputStream(_uri, _mutex),
|
:InputStream(_uri, _mutex),
|
||||||
iso(_iso), statbuf(_statbuf) {
|
iso(_iso),
|
||||||
size = statbuf->size;
|
lsn(_lsn)
|
||||||
|
{
|
||||||
|
size = _size;
|
||||||
|
seekable = true;
|
||||||
SetReady();
|
SetReady();
|
||||||
}
|
}
|
||||||
|
|
||||||
~Iso9660InputStream() {
|
|
||||||
free(statbuf);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* virtual methods from InputStream */
|
/* virtual methods from InputStream */
|
||||||
bool IsEOF() noexcept override;
|
bool IsEOF() noexcept override;
|
||||||
size_t Read(void *ptr, size_t size) override;
|
size_t Read(void *ptr, size_t size) override;
|
||||||
|
|
||||||
|
void Seek(offset_type new_offset) override {
|
||||||
|
if (new_offset > size)
|
||||||
|
throw std::runtime_error("Invalid seek offset");
|
||||||
|
|
||||||
|
skip = new_offset % ISO_BLOCKSIZE;
|
||||||
|
offset = new_offset - skip;
|
||||||
|
buffer.Clear();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
InputStreamPtr
|
InputStreamPtr
|
||||||
@@ -173,42 +234,78 @@ Iso9660ArchiveFile::OpenStream(const char *pathname,
|
|||||||
throw FormatRuntimeError("not found in the ISO file: %s",
|
throw FormatRuntimeError("not found in the ISO file: %s",
|
||||||
pathname);
|
pathname);
|
||||||
|
|
||||||
|
const lsn_t lsn = statbuf->lsn;
|
||||||
|
const offset_type size = statbuf->size;
|
||||||
|
free(statbuf);
|
||||||
|
|
||||||
return std::make_unique<Iso9660InputStream>(iso, pathname, mutex,
|
return std::make_unique<Iso9660InputStream>(iso, pathname, mutex,
|
||||||
statbuf);
|
lsn, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t
|
size_t
|
||||||
Iso9660InputStream::Read(void *ptr, size_t read_size)
|
Iso9660InputStream::Read(void *ptr, size_t read_size)
|
||||||
{
|
{
|
||||||
const ScopeUnlock unlock(mutex);
|
const offset_type remaining = size - offset;
|
||||||
|
if (remaining == 0)
|
||||||
int readed = 0;
|
|
||||||
int no_blocks, cur_block;
|
|
||||||
size_t left_bytes = statbuf->size - offset;
|
|
||||||
|
|
||||||
if (left_bytes < read_size) {
|
|
||||||
no_blocks = CEILING(left_bytes, ISO_BLOCKSIZE);
|
|
||||||
} else {
|
|
||||||
no_blocks = read_size / ISO_BLOCKSIZE;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (no_blocks == 0)
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
cur_block = offset / ISO_BLOCKSIZE;
|
if (offset_type(read_size) > remaining)
|
||||||
|
read_size = remaining;
|
||||||
|
|
||||||
readed = iso->SeekRead(ptr, statbuf->lsn + cur_block, no_blocks);
|
auto r = buffer.Read();
|
||||||
|
|
||||||
if (readed != no_blocks * ISO_BLOCKSIZE)
|
if (r.empty()) {
|
||||||
throw FormatRuntimeError("error reading ISO file at lsn %lu",
|
/* the buffer is empty - read more data from the ISO file */
|
||||||
(unsigned long)cur_block);
|
|
||||||
|
|
||||||
if (left_bytes < read_size) {
|
assert(offset % ISO_BLOCKSIZE == 0);
|
||||||
readed = left_bytes;
|
|
||||||
|
const ScopeUnlock unlock(mutex);
|
||||||
|
|
||||||
|
const lsn_t read_lsn = lsn + offset / ISO_BLOCKSIZE;
|
||||||
|
|
||||||
|
if (read_size >= ISO_BLOCKSIZE) {
|
||||||
|
/* big read - read right into the caller's buffer */
|
||||||
|
|
||||||
|
auto nbytes = iso->SeekRead(ptr, read_lsn,
|
||||||
|
read_size / ISO_BLOCKSIZE);
|
||||||
|
if (nbytes <= 0)
|
||||||
|
throw std::runtime_error("Failed to read ISO9660 file");
|
||||||
|
|
||||||
|
offset += nbytes;
|
||||||
|
return nbytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
offset += readed;
|
/* fill the buffer */
|
||||||
return readed;
|
|
||||||
|
auto w = buffer.Write();
|
||||||
|
auto nbytes = iso->SeekRead(w.data, read_lsn,
|
||||||
|
w.size / ISO_BLOCKSIZE);
|
||||||
|
if (nbytes <= 0)
|
||||||
|
throw std::runtime_error("Failed to read ISO9660 file");
|
||||||
|
|
||||||
|
buffer.Append(nbytes);
|
||||||
|
|
||||||
|
r = buffer.Read();
|
||||||
|
|
||||||
|
if (skip > 0) {
|
||||||
|
if (skip >= r.size)
|
||||||
|
throw std::runtime_error("Premature end of ISO9660 track");
|
||||||
|
|
||||||
|
buffer.Consume(skip);
|
||||||
|
skip = 0;
|
||||||
|
|
||||||
|
r = buffer.Read();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(!r.empty());
|
||||||
|
assert(skip == 0);
|
||||||
|
|
||||||
|
size_t nbytes = std::min(read_size, r.size);
|
||||||
|
memcpy(ptr, r.data, nbytes);
|
||||||
|
buffer.Consume(nbytes);
|
||||||
|
offset += nbytes;
|
||||||
|
return nbytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
|
@@ -32,6 +32,8 @@
|
|||||||
|
|
||||||
#include <zzip/zzip.h>
|
#include <zzip/zzip.h>
|
||||||
|
|
||||||
|
#include <inttypes.h> /* for PRIoffset (PRIu64) */
|
||||||
|
|
||||||
struct ZzipDir {
|
struct ZzipDir {
|
||||||
ZZIP_DIR *const dir;
|
ZZIP_DIR *const dir;
|
||||||
|
|
||||||
@@ -54,10 +56,11 @@ class ZzipArchiveFile final : public ArchiveFile {
|
|||||||
std::shared_ptr<ZzipDir> dir;
|
std::shared_ptr<ZzipDir> dir;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
ZzipArchiveFile(std::shared_ptr<ZzipDir> &&_dir)
|
template<typename D>
|
||||||
:dir(std::move(_dir)) {}
|
explicit ZzipArchiveFile(D &&_dir) noexcept
|
||||||
|
:dir(std::forward<D>(_dir)) {}
|
||||||
|
|
||||||
virtual void Visit(ArchiveVisitor &visitor) override;
|
void Visit(ArchiveVisitor &visitor) override;
|
||||||
|
|
||||||
InputStreamPtr OpenStream(const char *path,
|
InputStreamPtr OpenStream(const char *path,
|
||||||
Mutex &mutex) override;
|
Mutex &mutex) override;
|
||||||
@@ -91,11 +94,12 @@ class ZzipInputStream final : public InputStream {
|
|||||||
ZZIP_FILE *const file;
|
ZZIP_FILE *const file;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
ZzipInputStream(const std::shared_ptr<ZzipDir> _dir, const char *_uri,
|
template<typename D>
|
||||||
|
ZzipInputStream(D &&_dir, const char *_uri,
|
||||||
Mutex &_mutex,
|
Mutex &_mutex,
|
||||||
ZZIP_FILE *_file)
|
ZZIP_FILE *_file)
|
||||||
:InputStream(_uri, _mutex),
|
:InputStream(_uri, _mutex),
|
||||||
dir(_dir), file(_file) {
|
dir(std::forward<D>(_dir)), file(_file) {
|
||||||
//we are seekable (but its not recommendent to do so)
|
//we are seekable (but its not recommendent to do so)
|
||||||
seekable = true;
|
seekable = true;
|
||||||
|
|
||||||
@@ -106,7 +110,7 @@ public:
|
|||||||
SetReady();
|
SetReady();
|
||||||
}
|
}
|
||||||
|
|
||||||
~ZzipInputStream() {
|
~ZzipInputStream() noexcept override {
|
||||||
zzip_file_close(file);
|
zzip_file_close(file);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -145,12 +149,17 @@ ZzipInputStream::Read(void *ptr, size_t read_size)
|
|||||||
{
|
{
|
||||||
const ScopeUnlock unlock(mutex);
|
const ScopeUnlock unlock(mutex);
|
||||||
|
|
||||||
int ret = zzip_file_read(file, ptr, read_size);
|
zzip_ssize_t nbytes = zzip_file_read(file, ptr, read_size);
|
||||||
if (ret < 0)
|
if (nbytes < 0)
|
||||||
throw std::runtime_error("zzip_file_read() has failed");
|
throw std::runtime_error("zzip_file_read() has failed");
|
||||||
|
|
||||||
|
if (nbytes == 0 && !IsEOF())
|
||||||
|
throw FormatRuntimeError("Unexpected end of file %s"
|
||||||
|
" at %" PRIoffset " of %" PRIoffset,
|
||||||
|
GetURI(), GetOffset(), GetSize());
|
||||||
|
|
||||||
offset = zzip_tell(file);
|
offset = zzip_tell(file);
|
||||||
return ret;
|
return nbytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
|
@@ -50,9 +50,7 @@ gcc_pure
|
|||||||
static bool
|
static bool
|
||||||
SkipNameFS(PathTraitsFS::const_pointer_type name_fs) noexcept
|
SkipNameFS(PathTraitsFS::const_pointer_type name_fs) noexcept
|
||||||
{
|
{
|
||||||
return name_fs[0] == '.' &&
|
return PathTraitsFS::IsSpecialFilename(name_fs);
|
||||||
(name_fs[1] == 0 ||
|
|
||||||
(name_fs[1] == '.' && name_fs[2] == 0));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
gcc_pure
|
gcc_pure
|
||||||
|
@@ -148,7 +148,7 @@ handle_status(Client &client, gcc_unused Request args, Response &r)
|
|||||||
playlist.GetConsume(),
|
playlist.GetConsume(),
|
||||||
(unsigned long)playlist.GetVersion(),
|
(unsigned long)playlist.GetVersion(),
|
||||||
playlist.GetLength(),
|
playlist.GetLength(),
|
||||||
pc.GetMixRampDb(),
|
(double)pc.GetMixRampDb(),
|
||||||
state);
|
state);
|
||||||
|
|
||||||
if (pc.GetCrossFade() > FloatDuration::zero())
|
if (pc.GetCrossFade() > FloatDuration::zero())
|
||||||
|
@@ -198,6 +198,16 @@ handle_mount(Client &client, Request args, Response &r)
|
|||||||
return CommandResult::ERROR;
|
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 &event_loop = instance.io_thread.GetEventLoop();
|
||||||
auto storage = CreateStorageURI(event_loop, remote_uri);
|
auto storage = CreateStorageURI(event_loop, remote_uri);
|
||||||
if (storage == nullptr) {
|
if (storage == nullptr) {
|
||||||
@@ -210,8 +220,10 @@ handle_mount(Client &client, Request args, Response &r)
|
|||||||
|
|
||||||
#ifdef ENABLE_DATABASE
|
#ifdef ENABLE_DATABASE
|
||||||
if (auto *db = dynamic_cast<SimpleDatabase *>(instance.GetDatabase())) {
|
if (auto *db = dynamic_cast<SimpleDatabase *>(instance.GetDatabase())) {
|
||||||
|
bool need_update;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
db->Mount(local_uri, remote_uri);
|
need_update = !db->Mount(local_uri, remote_uri);
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
composite.Unmount(local_uri);
|
composite.Unmount(local_uri);
|
||||||
throw;
|
throw;
|
||||||
@@ -220,6 +232,12 @@ handle_mount(Client &client, Request args, Response &r)
|
|||||||
// TODO: call Instance::OnDatabaseModified()?
|
// TODO: call Instance::OnDatabaseModified()?
|
||||||
// TODO: trigger database update?
|
// TODO: trigger database update?
|
||||||
instance.EmitIdle(IDLE_DATABASE);
|
instance.EmitIdle(IDLE_DATABASE);
|
||||||
|
|
||||||
|
if (need_update) {
|
||||||
|
UpdateService *update = client.GetInstance().update;
|
||||||
|
if (update != nullptr)
|
||||||
|
update->Enqueue(local_uri, false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@@ -428,7 +428,7 @@ IsUnsafeChar(char ch)
|
|||||||
return !IsSafeChar(ch);
|
return !IsSafeChar(ch);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
bool
|
||||||
SimpleDatabase::Mount(const char *local_uri, const char *storage_uri)
|
SimpleDatabase::Mount(const char *local_uri, const char *storage_uri)
|
||||||
{
|
{
|
||||||
if (cache_path.IsNull())
|
if (cache_path.IsNull())
|
||||||
@@ -447,9 +447,11 @@ SimpleDatabase::Mount(const char *local_uri, const char *storage_uri)
|
|||||||
compress);
|
compress);
|
||||||
db->Open();
|
db->Open();
|
||||||
|
|
||||||
// TODO: update the new database instance?
|
bool exists = db->FileExists();
|
||||||
|
|
||||||
Mount(local_uri, std::move(db));
|
Mount(local_uri, std::move(db));
|
||||||
|
|
||||||
|
return exists;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline DatabasePtr
|
inline DatabasePtr
|
||||||
|
@@ -103,9 +103,11 @@ public:
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Throws #std::runtime_error on error.
|
* Throws #std::runtime_error on error.
|
||||||
|
*
|
||||||
|
* @return false if the mounted database needs to be updated
|
||||||
*/
|
*/
|
||||||
gcc_nonnull_all
|
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
|
gcc_nonnull_all
|
||||||
bool Unmount(const char *uri) noexcept;
|
bool Unmount(const char *uri) noexcept;
|
||||||
|
@@ -65,7 +65,7 @@ ContentDirectoryService::readDirSlice(UpnpClient_Handle hdl,
|
|||||||
|
|
||||||
IXML_Document *response;
|
IXML_Document *response;
|
||||||
int code = UpnpSendAction(hdl, m_actionURL.c_str(), m_serviceType.c_str(),
|
int code = UpnpSendAction(hdl, m_actionURL.c_str(), m_serviceType.c_str(),
|
||||||
0 /*devUDN*/, request, &response);
|
nullptr /*devUDN*/, request, &response);
|
||||||
if (code != UPNP_E_SUCCESS)
|
if (code != UPNP_E_SUCCESS)
|
||||||
throw FormatRuntimeError("UpnpSendAction() failed: %s",
|
throw FormatRuntimeError("UpnpSendAction() failed: %s",
|
||||||
UpnpGetErrorMessage(code));
|
UpnpGetErrorMessage(code));
|
||||||
@@ -124,7 +124,7 @@ ContentDirectoryService::search(UpnpClient_Handle hdl,
|
|||||||
IXML_Document *_response;
|
IXML_Document *_response;
|
||||||
auto code = UpnpSendAction(hdl, m_actionURL.c_str(),
|
auto code = UpnpSendAction(hdl, m_actionURL.c_str(),
|
||||||
m_serviceType.c_str(),
|
m_serviceType.c_str(),
|
||||||
0 /*devUDN*/,
|
nullptr /*devUDN*/,
|
||||||
request.get(), &_response);
|
request.get(), &_response);
|
||||||
if (code != UPNP_E_SUCCESS)
|
if (code != UPNP_E_SUCCESS)
|
||||||
throw FormatRuntimeError("UpnpSendAction() failed: %s",
|
throw FormatRuntimeError("UpnpSendAction() failed: %s",
|
||||||
@@ -170,7 +170,7 @@ ContentDirectoryService::getMetadata(UpnpClient_Handle hdl,
|
|||||||
IXML_Document *_response;
|
IXML_Document *_response;
|
||||||
auto code = UpnpSendAction(hdl, m_actionURL.c_str(),
|
auto code = UpnpSendAction(hdl, m_actionURL.c_str(),
|
||||||
m_serviceType.c_str(),
|
m_serviceType.c_str(),
|
||||||
0 /*devUDN*/, request.get(), &_response);
|
nullptr /*devUDN*/, request.get(), &_response);
|
||||||
if (code != UPNP_E_SUCCESS)
|
if (code != UPNP_E_SUCCESS)
|
||||||
throw FormatRuntimeError("UpnpSendAction() failed: %s",
|
throw FormatRuntimeError("UpnpSendAction() failed: %s",
|
||||||
UpnpGetErrorMessage(code));
|
UpnpGetErrorMessage(code));
|
||||||
|
@@ -89,9 +89,18 @@ public:
|
|||||||
tag.Clear();
|
tag.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
gcc_pure
|
||||||
|
bool IsRoot() const noexcept {
|
||||||
|
return type == Type::CONTAINER && id == "0";
|
||||||
|
}
|
||||||
|
|
||||||
gcc_pure
|
gcc_pure
|
||||||
bool Check() const noexcept {
|
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 ||
|
(type != UPnPDirObject::Type::ITEM ||
|
||||||
item_class != UPnPDirObject::ItemClass::UNKNOWN);
|
item_class != UPnPDirObject::ItemClass::UNKNOWN);
|
||||||
}
|
}
|
||||||
|
@@ -79,7 +79,7 @@ path_in(const char *path, const char *possible_parent) noexcept
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
InotifyQueue::Enqueue(const char *uri_utf8)
|
InotifyQueue::Enqueue(const char *uri_utf8) noexcept
|
||||||
{
|
{
|
||||||
delay_event.Schedule(INOTIFY_UPDATE_DELAY);
|
delay_event.Schedule(INOTIFY_UPDATE_DELAY);
|
||||||
|
|
||||||
|
@@ -35,11 +35,11 @@ class InotifyQueue final {
|
|||||||
TimerEvent delay_event;
|
TimerEvent delay_event;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
InotifyQueue(EventLoop &_loop, UpdateService &_update)
|
InotifyQueue(EventLoop &_loop, UpdateService &_update) noexcept
|
||||||
:update(_update),
|
:update(_update),
|
||||||
delay_event(_loop, BIND_THIS_METHOD(OnDelay)) {}
|
delay_event(_loop, BIND_THIS_METHOD(OnDelay)) {}
|
||||||
|
|
||||||
void Enqueue(const char *uri_utf8);
|
void Enqueue(const char *uri_utf8) noexcept;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void OnDelay() noexcept;
|
void OnDelay() noexcept;
|
||||||
|
@@ -24,11 +24,11 @@
|
|||||||
#include "system/Error.hxx"
|
#include "system/Error.hxx"
|
||||||
#include "Log.hxx"
|
#include "Log.hxx"
|
||||||
|
|
||||||
|
#include <cerrno>
|
||||||
|
#include <climits>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
#include <sys/inotify.h>
|
#include <sys/inotify.h>
|
||||||
#include <unistd.h>
|
|
||||||
#include <errno.h>
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <limits.h>
|
|
||||||
|
|
||||||
bool
|
bool
|
||||||
InotifySource::OnSocketReady(gcc_unused unsigned flags) noexcept
|
InotifySource::OnSocketReady(gcc_unused unsigned flags) noexcept
|
||||||
@@ -48,7 +48,7 @@ InotifySource::OnSocketReady(gcc_unused unsigned flags) noexcept
|
|||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
const size_t remaining = end - p;
|
const size_t remaining = end - p;
|
||||||
const struct inotify_event *event =
|
const auto *event =
|
||||||
(const struct inotify_event *)p;
|
(const struct inotify_event *)p;
|
||||||
if (remaining < sizeof(*event) ||
|
if (remaining < sizeof(*event) ||
|
||||||
remaining < sizeof(*event) + event->len)
|
remaining < sizeof(*event) + event->len)
|
||||||
@@ -98,7 +98,7 @@ InotifySource::Add(const char *path_fs, unsigned mask)
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
InotifySource::Remove(unsigned wd)
|
InotifySource::Remove(unsigned wd) noexcept
|
||||||
{
|
{
|
||||||
auto ifd = GetSocket().ToFileDescriptor();
|
auto ifd = GetSocket().ToFileDescriptor();
|
||||||
int ret = inotify_rm_watch(ifd.Get(), wd);
|
int ret = inotify_rm_watch(ifd.Get(), wd);
|
||||||
|
@@ -21,9 +21,6 @@
|
|||||||
#define MPD_INOTIFY_SOURCE_HXX
|
#define MPD_INOTIFY_SOURCE_HXX
|
||||||
|
|
||||||
#include "event/SocketMonitor.hxx"
|
#include "event/SocketMonitor.hxx"
|
||||||
#include "util/Compiler.h"
|
|
||||||
|
|
||||||
class FileDescriptor;
|
|
||||||
|
|
||||||
typedef void (*mpd_inotify_callback_t)(int wd, unsigned mask,
|
typedef void (*mpd_inotify_callback_t)(int wd, unsigned mask,
|
||||||
const char *name, void *ctx);
|
const char *name, void *ctx);
|
||||||
@@ -45,7 +42,7 @@ public:
|
|||||||
InotifySource(EventLoop &_loop,
|
InotifySource(EventLoop &_loop,
|
||||||
mpd_inotify_callback_t callback, void *ctx);
|
mpd_inotify_callback_t callback, void *ctx);
|
||||||
|
|
||||||
~InotifySource() {
|
~InotifySource() noexcept {
|
||||||
Close();
|
Close();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,7 +60,7 @@ public:
|
|||||||
*
|
*
|
||||||
* @param wd the watch descriptor returned by mpd_inotify_source_add()
|
* @param wd the watch descriptor returned by mpd_inotify_source_add()
|
||||||
*/
|
*/
|
||||||
void Remove(unsigned wd);
|
void Remove(unsigned wd) noexcept;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool OnSocketReady(unsigned flags) noexcept override;
|
bool OnSocketReady(unsigned flags) noexcept override;
|
||||||
|
@@ -21,19 +21,25 @@
|
|||||||
#include "InotifySource.hxx"
|
#include "InotifySource.hxx"
|
||||||
#include "InotifyQueue.hxx"
|
#include "InotifyQueue.hxx"
|
||||||
#include "InotifyDomain.hxx"
|
#include "InotifyDomain.hxx"
|
||||||
|
#include "ExcludeList.hxx"
|
||||||
#include "storage/StorageInterface.hxx"
|
#include "storage/StorageInterface.hxx"
|
||||||
|
#include "input/InputStream.hxx"
|
||||||
|
#include "input/Error.hxx"
|
||||||
#include "fs/AllocatedPath.hxx"
|
#include "fs/AllocatedPath.hxx"
|
||||||
|
#include "fs/DirectoryReader.hxx"
|
||||||
#include "fs/FileInfo.hxx"
|
#include "fs/FileInfo.hxx"
|
||||||
#include "fs/Traits.hxx"
|
#include "fs/Traits.hxx"
|
||||||
|
#include "thread/Mutex.hxx"
|
||||||
|
#include "util/Compiler.h"
|
||||||
#include "Log.hxx"
|
#include "Log.hxx"
|
||||||
|
|
||||||
#include <string>
|
#include <cassert>
|
||||||
#include <map>
|
#include <cstring>
|
||||||
#include <forward_list>
|
#include <forward_list>
|
||||||
|
#include <map>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
#include <assert.h>
|
|
||||||
#include <sys/inotify.h>
|
#include <sys/inotify.h>
|
||||||
#include <string.h>
|
|
||||||
#include <dirent.h>
|
#include <dirent.h>
|
||||||
|
|
||||||
static constexpr unsigned IN_MASK =
|
static constexpr unsigned IN_MASK =
|
||||||
@@ -50,17 +56,28 @@ struct WatchDirectory {
|
|||||||
|
|
||||||
int descriptor;
|
int descriptor;
|
||||||
|
|
||||||
|
ExcludeList exclude_list;
|
||||||
|
|
||||||
std::forward_list<WatchDirectory> children;
|
std::forward_list<WatchDirectory> children;
|
||||||
|
|
||||||
template<typename N>
|
template<typename N>
|
||||||
WatchDirectory(WatchDirectory *_parent, N &&_name,
|
WatchDirectory(N &&_name,
|
||||||
int _descriptor)
|
int _descriptor)
|
||||||
:parent(_parent), name(std::forward<N>(_name)),
|
:parent(nullptr), name(std::forward<N>(_name)),
|
||||||
descriptor(_descriptor) {}
|
descriptor(_descriptor) {}
|
||||||
|
|
||||||
|
template<typename N>
|
||||||
|
WatchDirectory(WatchDirectory &_parent, N &&_name,
|
||||||
|
int _descriptor)
|
||||||
|
:parent(&_parent), name(std::forward<N>(_name)),
|
||||||
|
descriptor(_descriptor),
|
||||||
|
exclude_list(_parent.exclude_list) {}
|
||||||
|
|
||||||
WatchDirectory(const WatchDirectory &) = delete;
|
WatchDirectory(const WatchDirectory &) = delete;
|
||||||
WatchDirectory &operator=(const WatchDirectory &) = delete;
|
WatchDirectory &operator=(const WatchDirectory &) = delete;
|
||||||
|
|
||||||
|
void LoadExcludeList(Path directory_path) noexcept;
|
||||||
|
|
||||||
gcc_pure
|
gcc_pure
|
||||||
unsigned GetDepth() const noexcept;
|
unsigned GetDepth() const noexcept;
|
||||||
|
|
||||||
@@ -68,6 +85,18 @@ struct WatchDirectory {
|
|||||||
AllocatedPath GetUriFS() const noexcept;
|
AllocatedPath GetUriFS() const noexcept;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
void
|
||||||
|
WatchDirectory::LoadExcludeList(Path directory_path) noexcept
|
||||||
|
try {
|
||||||
|
Mutex mutex;
|
||||||
|
auto is = InputStream::OpenReady((directory_path / Path::FromFS(".mpdignore")).c_str(),
|
||||||
|
mutex);
|
||||||
|
exclude_list.Load(std::move(is));
|
||||||
|
} catch (...) {
|
||||||
|
if (!IsFileNotFound(std::current_exception()))
|
||||||
|
LogError(std::current_exception());
|
||||||
|
}
|
||||||
|
|
||||||
static InotifySource *inotify_source;
|
static InotifySource *inotify_source;
|
||||||
static InotifyQueue *inotify_queue;
|
static InotifyQueue *inotify_queue;
|
||||||
|
|
||||||
@@ -145,20 +174,19 @@ WatchDirectory::GetUriFS() const noexcept
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* we don't look at "." / ".." nor files with newlines in their name */
|
/* we don't look at "." / ".." nor files with newlines in their name */
|
||||||
static bool skip_path(const char *path)
|
gcc_pure
|
||||||
|
static bool
|
||||||
|
SkipFilename(Path name) noexcept
|
||||||
{
|
{
|
||||||
return PathTraitsFS::IsSpecialFilename(path) ||
|
return PathTraitsFS::IsSpecialFilename(name.c_str()) ||
|
||||||
strchr(path, '\n') != nullptr;
|
name.HasNewline();
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
recursive_watch_subdirectories(WatchDirectory *directory,
|
recursive_watch_subdirectories(WatchDirectory &parent,
|
||||||
const AllocatedPath &path_fs, unsigned depth)
|
const Path path_fs,
|
||||||
{
|
unsigned depth)
|
||||||
DIR *dir;
|
try {
|
||||||
struct dirent *ent;
|
|
||||||
|
|
||||||
assert(directory != nullptr);
|
|
||||||
assert(depth <= inotify_max_depth);
|
assert(depth <= inotify_max_depth);
|
||||||
assert(!path_fs.IsNull());
|
assert(!path_fs.IsNull());
|
||||||
|
|
||||||
@@ -167,20 +195,17 @@ recursive_watch_subdirectories(WatchDirectory *directory,
|
|||||||
if (depth > inotify_max_depth)
|
if (depth > inotify_max_depth)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
dir = opendir(path_fs.c_str());
|
DirectoryReader dir(path_fs);
|
||||||
if (dir == nullptr) {
|
while (dir.ReadEntry()) {
|
||||||
FormatErrno(inotify_domain,
|
|
||||||
"Failed to open directory %s", path_fs.c_str());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
while ((ent = readdir(dir))) {
|
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
if (skip_path(ent->d_name))
|
const Path name_fs = dir.GetEntry();
|
||||||
|
if (SkipFilename(name_fs))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (parent.exclude_list.Check(name_fs))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
const auto name_fs = Path::FromFS(ent->d_name);
|
|
||||||
const auto child_path_fs = path_fs / name_fs;
|
const auto child_path_fs = path_fs / name_fs;
|
||||||
|
|
||||||
FileInfo fi;
|
FileInfo fi;
|
||||||
@@ -209,17 +234,18 @@ recursive_watch_subdirectories(WatchDirectory *directory,
|
|||||||
/* already being watched */
|
/* already being watched */
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
directory->children.emplace_front(directory,
|
parent.children.emplace_front(parent,
|
||||||
name_fs,
|
name_fs,
|
||||||
ret);
|
ret);
|
||||||
child = &directory->children.front();
|
child = &parent.children.front();
|
||||||
|
child->LoadExcludeList(child_path_fs);
|
||||||
|
|
||||||
tree_add_watch_directory(child);
|
tree_add_watch_directory(child);
|
||||||
|
|
||||||
recursive_watch_subdirectories(child, child_path_fs, depth);
|
recursive_watch_subdirectories(*child, child_path_fs, depth);
|
||||||
}
|
}
|
||||||
|
} catch (...) {
|
||||||
closedir(dir);
|
LogError(std::current_exception());
|
||||||
}
|
}
|
||||||
|
|
||||||
gcc_pure
|
gcc_pure
|
||||||
@@ -240,8 +266,6 @@ mpd_inotify_callback(int wd, unsigned mask,
|
|||||||
{
|
{
|
||||||
WatchDirectory *directory;
|
WatchDirectory *directory;
|
||||||
|
|
||||||
/*FormatDebug(inotify_domain, "wd=%d mask=0x%x name='%s'", wd, mask, name);*/
|
|
||||||
|
|
||||||
directory = tree_find_watch_directory(wd);
|
directory = tree_find_watch_directory(wd);
|
||||||
if (directory == nullptr)
|
if (directory == nullptr)
|
||||||
return;
|
return;
|
||||||
@@ -263,7 +287,7 @@ mpd_inotify_callback(int wd, unsigned mask,
|
|||||||
? root
|
? root
|
||||||
: (root / uri_fs);
|
: (root / uri_fs);
|
||||||
|
|
||||||
recursive_watch_subdirectories(directory, path_fs,
|
recursive_watch_subdirectories(*directory, path_fs,
|
||||||
directory->GetDepth());
|
directory->GetDepth());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -318,11 +342,12 @@ mpd_inotify_init(EventLoop &loop, Storage &storage, UpdateService &update,
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
inotify_root = new WatchDirectory(nullptr, path, descriptor);
|
inotify_root = new WatchDirectory(path, descriptor);
|
||||||
|
inotify_root->LoadExcludeList(path);
|
||||||
|
|
||||||
tree_add_watch_directory(inotify_root);
|
tree_add_watch_directory(inotify_root);
|
||||||
|
|
||||||
recursive_watch_subdirectories(inotify_root, path, 0);
|
recursive_watch_subdirectories(*inotify_root, path, 0);
|
||||||
|
|
||||||
inotify_queue = new InotifyQueue(loop, update);
|
inotify_queue = new InotifyQueue(loop, update);
|
||||||
|
|
||||||
|
@@ -20,8 +20,6 @@
|
|||||||
#ifndef MPD_INOTIFY_UPDATE_HXX
|
#ifndef MPD_INOTIFY_UPDATE_HXX
|
||||||
#define MPD_INOTIFY_UPDATE_HXX
|
#define MPD_INOTIFY_UPDATE_HXX
|
||||||
|
|
||||||
#include "util/Compiler.h"
|
|
||||||
|
|
||||||
class EventLoop;
|
class EventLoop;
|
||||||
class Storage;
|
class Storage;
|
||||||
class UpdateService;
|
class UpdateService;
|
||||||
|
@@ -341,8 +341,8 @@ UpdateWalk::UpdateDirectory(Directory &directory,
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
Mutex mutex;
|
Mutex mutex;
|
||||||
auto is = InputStream::OpenReady(PathTraitsUTF8::Build(storage.MapUTF8(directory.GetPath()).c_str(),
|
auto is = InputStream::OpenReady(storage.MapUTF8(PathTraitsUTF8::Build(directory.GetPath(),
|
||||||
".mpdignore").c_str(),
|
".mpdignore").c_str()).c_str(),
|
||||||
mutex);
|
mutex);
|
||||||
child_exclude_list.Load(std::move(is));
|
child_exclude_list.Load(std::move(is));
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
|
@@ -33,11 +33,11 @@
|
|||||||
#include "util/ConstBuffer.hxx"
|
#include "util/ConstBuffer.hxx"
|
||||||
#include "util/StringBuffer.hxx"
|
#include "util/StringBuffer.hxx"
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <math.h>
|
|
||||||
|
|
||||||
DecoderBridge::~DecoderBridge()
|
DecoderBridge::~DecoderBridge()
|
||||||
{
|
{
|
||||||
@@ -597,7 +597,7 @@ DecoderBridge::SubmitReplayGain(const ReplayGainInfo *new_replay_gain_info)
|
|||||||
const auto &tuple = new_replay_gain_info->Get(rgm);
|
const auto &tuple = new_replay_gain_info->Get(rgm);
|
||||||
const auto scale =
|
const auto scale =
|
||||||
tuple.CalculateScale(dc.replay_gain_config);
|
tuple.CalculateScale(dc.replay_gain_config);
|
||||||
dc.replay_gain_db = 20.0 * log10f(scale);
|
dc.replay_gain_db = 20.0f * std::log10(scale);
|
||||||
}
|
}
|
||||||
|
|
||||||
replay_gain_info = *new_replay_gain_info;
|
replay_gain_info = *new_replay_gain_info;
|
||||||
|
@@ -26,7 +26,7 @@
|
|||||||
size_t
|
size_t
|
||||||
decoder_read(DecoderClient *client,
|
decoder_read(DecoderClient *client,
|
||||||
InputStream &is,
|
InputStream &is,
|
||||||
void *buffer, size_t length)
|
void *buffer, size_t length) noexcept
|
||||||
{
|
{
|
||||||
assert(buffer != nullptr);
|
assert(buffer != nullptr);
|
||||||
|
|
||||||
@@ -42,9 +42,30 @@ decoder_read(DecoderClient *client,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
size_t
|
||||||
|
decoder_read_much(DecoderClient *client, InputStream &is,
|
||||||
|
void *_buffer, size_t size) noexcept
|
||||||
|
{
|
||||||
|
uint8_t *buffer = (uint8_t *)_buffer;
|
||||||
|
|
||||||
|
size_t total = 0;
|
||||||
|
|
||||||
|
while (size > 0 && !is.LockIsEOF()) {
|
||||||
|
size_t nbytes = decoder_read(client, is, buffer, size);
|
||||||
|
if (nbytes == 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
total += nbytes;
|
||||||
|
buffer += nbytes;
|
||||||
|
size -= nbytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
return total;
|
||||||
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
decoder_read_full(DecoderClient *client, InputStream &is,
|
decoder_read_full(DecoderClient *client, InputStream &is,
|
||||||
void *_buffer, size_t size)
|
void *_buffer, size_t size) noexcept
|
||||||
{
|
{
|
||||||
uint8_t *buffer = (uint8_t *)_buffer;
|
uint8_t *buffer = (uint8_t *)_buffer;
|
||||||
|
|
||||||
@@ -61,7 +82,7 @@ decoder_read_full(DecoderClient *client, InputStream &is,
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
decoder_skip(DecoderClient *client, InputStream &is, size_t size)
|
decoder_skip(DecoderClient *client, InputStream &is, size_t size) noexcept
|
||||||
{
|
{
|
||||||
while (size > 0) {
|
while (size > 0) {
|
||||||
char buffer[1024];
|
char buffer[1024];
|
||||||
|
@@ -65,15 +65,27 @@ class StopDecoder {};
|
|||||||
*/
|
*/
|
||||||
size_t
|
size_t
|
||||||
decoder_read(DecoderClient *decoder, InputStream &is,
|
decoder_read(DecoderClient *decoder, InputStream &is,
|
||||||
void *buffer, size_t length);
|
void *buffer, size_t length) noexcept;
|
||||||
|
|
||||||
static inline size_t
|
static inline size_t
|
||||||
decoder_read(DecoderClient &decoder, InputStream &is,
|
decoder_read(DecoderClient &decoder, InputStream &is,
|
||||||
void *buffer, size_t length)
|
void *buffer, size_t length) noexcept
|
||||||
{
|
{
|
||||||
return decoder_read(&decoder, is, buffer, length);
|
return decoder_read(&decoder, is, buffer, length);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Blocking read from the input stream. Attempts to fill the buffer
|
||||||
|
* as much as possible, until either end-of-file is reached or an
|
||||||
|
* error occurs.
|
||||||
|
*
|
||||||
|
* @return the number of bytes read, or 0 if one of the following
|
||||||
|
* occurs: end of file; error; command (like SEEK or STOP).
|
||||||
|
*/
|
||||||
|
size_t
|
||||||
|
decoder_read_much(DecoderClient *decoder, InputStream &is,
|
||||||
|
void *buffer, size_t size) noexcept;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Blocking read from the input stream. Attempts to fill the buffer
|
* Blocking read from the input stream. Attempts to fill the buffer
|
||||||
* completely; there is no partial result.
|
* completely; there is no partial result.
|
||||||
@@ -83,7 +95,7 @@ decoder_read(DecoderClient &decoder, InputStream &is,
|
|||||||
*/
|
*/
|
||||||
bool
|
bool
|
||||||
decoder_read_full(DecoderClient *decoder, InputStream &is,
|
decoder_read_full(DecoderClient *decoder, InputStream &is,
|
||||||
void *buffer, size_t size);
|
void *buffer, size_t size) noexcept;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Skip data on the #InputStream.
|
* Skip data on the #InputStream.
|
||||||
@@ -91,6 +103,6 @@ decoder_read_full(DecoderClient *decoder, InputStream &is,
|
|||||||
* @return true on success, false on error or command
|
* @return true on success, false on error or command
|
||||||
*/
|
*/
|
||||||
bool
|
bool
|
||||||
decoder_skip(DecoderClient *decoder, InputStream &is, size_t size);
|
decoder_skip(DecoderClient *decoder, InputStream &is, size_t size) noexcept;
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@@ -69,16 +69,20 @@ struct DecoderPlugin {
|
|||||||
/**
|
/**
|
||||||
* Scan metadata of a file.
|
* Scan metadata of a file.
|
||||||
*
|
*
|
||||||
* @return false if the operation has failed
|
* Throws on I/O error.
|
||||||
|
*
|
||||||
|
* @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.
|
||||||
*
|
*
|
||||||
* @return false if the operation has failed
|
* Throws on I/O error.
|
||||||
|
*
|
||||||
|
* @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
|
* @brief Return a "virtual" filename for subtracks in
|
||||||
@@ -135,7 +139,7 @@ struct DecoderPlugin {
|
|||||||
* Read the tag of a file.
|
* Read the tag of a file.
|
||||||
*/
|
*/
|
||||||
template<typename P>
|
template<typename P>
|
||||||
bool ScanFile(P path_fs, TagHandler &handler) const noexcept {
|
bool ScanFile(P path_fs, TagHandler &handler) const {
|
||||||
return scan_file != nullptr
|
return scan_file != nullptr
|
||||||
? scan_file(path_fs, handler)
|
? scan_file(path_fs, handler)
|
||||||
: false;
|
: false;
|
||||||
@@ -144,7 +148,7 @@ struct DecoderPlugin {
|
|||||||
/**
|
/**
|
||||||
* Read the tag of a stream.
|
* 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
|
return scan_stream != nullptr
|
||||||
? scan_stream(is, handler)
|
? scan_stream(is, handler)
|
||||||
: false;
|
: false;
|
||||||
|
@@ -241,7 +241,7 @@ audiofile_stream_decode(DecoderClient &client, InputStream &is)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
audiofile_scan_stream(InputStream &is, TagHandler &handler) noexcept
|
audiofile_scan_stream(InputStream &is, TagHandler &handler)
|
||||||
{
|
{
|
||||||
if (!is.IsSeekable() || !is.KnownSize())
|
if (!is.IsSeekable() || !is.KnownSize())
|
||||||
return false;
|
return false;
|
||||||
|
@@ -20,7 +20,7 @@
|
|||||||
#ifndef MPD_DECODER_DSDLIB_HXX
|
#ifndef MPD_DECODER_DSDLIB_HXX
|
||||||
#define MPD_DECODER_DSDLIB_HXX
|
#define MPD_DECODER_DSDLIB_HXX
|
||||||
|
|
||||||
#include "system/ByteOrder.hxx"
|
#include "util/ByteOrder.hxx"
|
||||||
#include "input/Offset.hxx"
|
#include "input/Offset.hxx"
|
||||||
#include "util/Compiler.h"
|
#include "util/Compiler.h"
|
||||||
|
|
||||||
|
@@ -32,7 +32,7 @@
|
|||||||
#include "input/InputStream.hxx"
|
#include "input/InputStream.hxx"
|
||||||
#include "CheckAudioFormat.hxx"
|
#include "CheckAudioFormat.hxx"
|
||||||
#include "util/bit_reverse.h"
|
#include "util/bit_reverse.h"
|
||||||
#include "system/ByteOrder.hxx"
|
#include "util/ByteOrder.hxx"
|
||||||
#include "tag/Handler.hxx"
|
#include "tag/Handler.hxx"
|
||||||
#include "DsdLib.hxx"
|
#include "DsdLib.hxx"
|
||||||
#include "Log.hxx"
|
#include "Log.hxx"
|
||||||
@@ -449,7 +449,7 @@ dsdiff_stream_decode(DecoderClient &client, InputStream &is)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
dsdiff_scan_stream(InputStream &is, TagHandler &handler) noexcept
|
dsdiff_scan_stream(InputStream &is, TagHandler &handler)
|
||||||
{
|
{
|
||||||
DsdiffMetaData metadata;
|
DsdiffMetaData metadata;
|
||||||
DsdiffChunkHeader chunk_header;
|
DsdiffChunkHeader chunk_header;
|
||||||
|
@@ -33,7 +33,7 @@
|
|||||||
#include "input/InputStream.hxx"
|
#include "input/InputStream.hxx"
|
||||||
#include "CheckAudioFormat.hxx"
|
#include "CheckAudioFormat.hxx"
|
||||||
#include "util/bit_reverse.h"
|
#include "util/bit_reverse.h"
|
||||||
#include "system/ByteOrder.hxx"
|
#include "util/ByteOrder.hxx"
|
||||||
#include "DsdLib.hxx"
|
#include "DsdLib.hxx"
|
||||||
#include "tag/Handler.hxx"
|
#include "tag/Handler.hxx"
|
||||||
#include "Log.hxx"
|
#include "Log.hxx"
|
||||||
@@ -326,7 +326,7 @@ dsf_stream_decode(DecoderClient &client, InputStream &is)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
dsf_scan_stream(InputStream &is, TagHandler &handler) noexcept
|
dsf_scan_stream(InputStream &is, TagHandler &handler)
|
||||||
{
|
{
|
||||||
/* check DSF metadata */
|
/* check DSF metadata */
|
||||||
DsfMetaData metadata;
|
DsfMetaData metadata;
|
||||||
|
@@ -414,7 +414,7 @@ faad_stream_decode(DecoderClient &client, InputStream &is)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
faad_scan_stream(InputStream &is, TagHandler &handler) noexcept
|
faad_scan_stream(InputStream &is, TagHandler &handler)
|
||||||
{
|
{
|
||||||
auto result = faad_get_file_time(is);
|
auto result = faad_get_file_time(is);
|
||||||
if (!result.first)
|
if (!result.first)
|
||||||
|
@@ -646,8 +646,7 @@ ffmpeg_decode(DecoderClient &client, InputStream &input)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
FfmpegScanStream(AVFormatContext &format_context,
|
FfmpegScanStream(AVFormatContext &format_context, TagHandler &handler)
|
||||||
TagHandler &handler) noexcept
|
|
||||||
{
|
{
|
||||||
const int find_result =
|
const int find_result =
|
||||||
avformat_find_stream_info(&format_context, nullptr);
|
avformat_find_stream_info(&format_context, nullptr);
|
||||||
@@ -680,7 +679,7 @@ FfmpegScanStream(AVFormatContext &format_context,
|
|||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
ffmpeg_scan_stream(InputStream &is, TagHandler &handler) noexcept
|
ffmpeg_scan_stream(InputStream &is, TagHandler &handler)
|
||||||
{
|
{
|
||||||
AvioStream stream(nullptr, is);
|
AvioStream stream(nullptr, is);
|
||||||
if (!stream.Open())
|
if (!stream.Open())
|
||||||
|
@@ -69,7 +69,7 @@ flac_write_cb(const FLAC__StreamDecoder *dec, const FLAC__Frame *frame,
|
|||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
flac_scan_file(Path path_fs, TagHandler &handler) noexcept
|
flac_scan_file(Path path_fs, TagHandler &handler)
|
||||||
{
|
{
|
||||||
FlacMetadataChain chain;
|
FlacMetadataChain chain;
|
||||||
if (!chain.Read(NarrowPath(path_fs))) {
|
if (!chain.Read(NarrowPath(path_fs))) {
|
||||||
@@ -84,7 +84,7 @@ flac_scan_file(Path path_fs, TagHandler &handler) noexcept
|
|||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
flac_scan_stream(InputStream &is, TagHandler &handler) noexcept
|
flac_scan_stream(InputStream &is, TagHandler &handler)
|
||||||
{
|
{
|
||||||
FlacMetadataChain chain;
|
FlacMetadataChain chain;
|
||||||
if (!chain.Read(is)) {
|
if (!chain.Read(is)) {
|
||||||
@@ -313,7 +313,7 @@ oggflac_init(gcc_unused const ConfigBlock &block)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
oggflac_scan_file(Path path_fs, TagHandler &handler) noexcept
|
oggflac_scan_file(Path path_fs, TagHandler &handler)
|
||||||
{
|
{
|
||||||
FlacMetadataChain chain;
|
FlacMetadataChain chain;
|
||||||
if (!chain.ReadOgg(NarrowPath(path_fs))) {
|
if (!chain.ReadOgg(NarrowPath(path_fs))) {
|
||||||
@@ -328,7 +328,7 @@ oggflac_scan_file(Path path_fs, TagHandler &handler) noexcept
|
|||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
oggflac_scan_stream(InputStream &is, TagHandler &handler) noexcept
|
oggflac_scan_stream(InputStream &is, TagHandler &handler)
|
||||||
{
|
{
|
||||||
FlacMetadataChain chain;
|
FlacMetadataChain chain;
|
||||||
if (!chain.ReadOgg(is)) {
|
if (!chain.ReadOgg(is)) {
|
||||||
|
@@ -70,8 +70,8 @@ fluidsynth_mpd_log_function(int level,
|
|||||||
char *message,
|
char *message,
|
||||||
void *)
|
void *)
|
||||||
{
|
{
|
||||||
Log(fluidsynth_domain,
|
Log(fluidsynth_level_to_mpd(fluid_log_level(level)),
|
||||||
fluidsynth_level_to_mpd(fluid_log_level(level)),
|
fluidsynth_domain,
|
||||||
message);
|
message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -20,7 +20,7 @@
|
|||||||
#include "HybridDsdDecoderPlugin.hxx"
|
#include "HybridDsdDecoderPlugin.hxx"
|
||||||
#include "../DecoderAPI.hxx"
|
#include "../DecoderAPI.hxx"
|
||||||
#include "input/InputStream.hxx"
|
#include "input/InputStream.hxx"
|
||||||
#include "system/ByteOrder.hxx"
|
#include "util/ByteOrder.hxx"
|
||||||
#include "util/Domain.hxx"
|
#include "util/Domain.hxx"
|
||||||
#include "util/WritableBuffer.hxx"
|
#include "util/WritableBuffer.hxx"
|
||||||
#include "util/StaticFifoBuffer.hxx"
|
#include "util/StaticFifoBuffer.hxx"
|
||||||
|
@@ -617,8 +617,8 @@ parse_lame(struct lame *lame, struct mad_bitptr *ptr, int *bitlen) noexcept
|
|||||||
|
|
||||||
mad_bit_skip(ptr, 16);
|
mad_bit_skip(ptr, 16);
|
||||||
|
|
||||||
lame->peak = mad_f_todouble(mad_bit_read(ptr, 32) << 5); /* peak */
|
lame->peak = MAD_F(mad_bit_read(ptr, 32) << 5); /* peak */
|
||||||
FormatDebug(mad_domain, "LAME peak found: %f", lame->peak);
|
FormatDebug(mad_domain, "LAME peak found: %f", double(lame->peak));
|
||||||
|
|
||||||
lame->track_gain = 0;
|
lame->track_gain = 0;
|
||||||
unsigned name = mad_bit_read(ptr, 3); /* gain name */
|
unsigned name = mad_bit_read(ptr, 3); /* gain name */
|
||||||
@@ -626,9 +626,9 @@ parse_lame(struct lame *lame, struct mad_bitptr *ptr, int *bitlen) noexcept
|
|||||||
unsigned sign = mad_bit_read(ptr, 1); /* sign bit */
|
unsigned sign = mad_bit_read(ptr, 1); /* sign bit */
|
||||||
int gain = mad_bit_read(ptr, 9); /* gain*10 */
|
int gain = mad_bit_read(ptr, 9); /* gain*10 */
|
||||||
if (gain && name == 1 && orig != 0) {
|
if (gain && name == 1 && orig != 0) {
|
||||||
lame->track_gain = ((sign ? -gain : gain) / 10.0) + adj;
|
lame->track_gain = ((sign ? -gain : gain) / 10.0f) + adj;
|
||||||
FormatDebug(mad_domain, "LAME track gain found: %f",
|
FormatDebug(mad_domain, "LAME track gain found: %f",
|
||||||
lame->track_gain);
|
double(lame->track_gain));
|
||||||
}
|
}
|
||||||
|
|
||||||
/* tmz reports that this isn't currently written by any version of lame
|
/* tmz reports that this isn't currently written by any version of lame
|
||||||
@@ -644,7 +644,7 @@ parse_lame(struct lame *lame, struct mad_bitptr *ptr, int *bitlen) noexcept
|
|||||||
if (gain && name == 2 && orig != 0) {
|
if (gain && name == 2 && orig != 0) {
|
||||||
lame->album_gain = ((sign ? -gain : gain) / 10.0) + adj;
|
lame->album_gain = ((sign ? -gain : gain) / 10.0) + adj;
|
||||||
FormatDebug(mad_domain, "LAME album gain found: %f",
|
FormatDebug(mad_domain, "LAME album gain found: %f",
|
||||||
lame->track_gain);
|
double(lame->track_gain));
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
mad_bit_skip(ptr, 16);
|
mad_bit_skip(ptr, 16);
|
||||||
@@ -778,7 +778,7 @@ MadDecoder::DecodeFirstFrame(Tag *tag) noexcept
|
|||||||
/* Album gain isn't currently used. See comment in
|
/* Album gain isn't currently used. See comment in
|
||||||
* parse_lame() for details. -- jat */
|
* parse_lame() for details. -- jat */
|
||||||
if (client != nullptr && !found_replay_gain &&
|
if (client != nullptr && !found_replay_gain &&
|
||||||
lame.track_gain) {
|
lame.track_gain > 0.0f) {
|
||||||
ReplayGainInfo rgi;
|
ReplayGainInfo rgi;
|
||||||
rgi.Clear();
|
rgi.Clear();
|
||||||
rgi.track.gain = lame.track_gain;
|
rgi.track.gain = lame.track_gain;
|
||||||
@@ -1051,7 +1051,7 @@ MadDecoder::RunScan(TagHandler &handler) noexcept
|
|||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
mad_decoder_scan_stream(InputStream &is, TagHandler &handler) noexcept
|
mad_decoder_scan_stream(InputStream &is, TagHandler &handler)
|
||||||
{
|
{
|
||||||
MadDecoder data(nullptr, is);
|
MadDecoder data(nullptr, is);
|
||||||
return data.RunScan(handler);
|
return data.RunScan(handler);
|
||||||
|
@@ -275,7 +275,7 @@ mpcdec_get_file_duration(InputStream &is)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
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);
|
const auto duration = mpcdec_get_file_duration(is);
|
||||||
if (duration.IsNegative())
|
if (duration.IsNegative())
|
||||||
|
@@ -75,6 +75,25 @@ class MPDOpusDecoder final : public OggDecoder {
|
|||||||
OpusDecoder *opus_decoder = nullptr;
|
OpusDecoder *opus_decoder = nullptr;
|
||||||
opus_int16 *output_buffer = nullptr;
|
opus_int16 *output_buffer = nullptr;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The output gain from the Opus header. Initialized by
|
||||||
|
* OnOggBeginning().
|
||||||
|
*/
|
||||||
|
signed output_gain;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The pre-skip value from the Opus header. Initialized by
|
||||||
|
* OnOggBeginning().
|
||||||
|
*/
|
||||||
|
unsigned pre_skip;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The number of decoded samples which shall be skipped. At
|
||||||
|
* the beginning of the file, this gets set to #pre_skip (by
|
||||||
|
* OnOggBeginning()), and may also be set while seeking.
|
||||||
|
*/
|
||||||
|
unsigned skip;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If non-zero, then a previous Opus stream has been found
|
* If non-zero, then a previous Opus stream has been found
|
||||||
* already with this number of channels. If opus_decoder is
|
* already with this number of channels. If opus_decoder is
|
||||||
@@ -85,6 +104,13 @@ class MPDOpusDecoder final : public OggDecoder {
|
|||||||
|
|
||||||
size_t frame_size;
|
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:
|
public:
|
||||||
explicit MPDOpusDecoder(DecoderReader &reader)
|
explicit MPDOpusDecoder(DecoderReader &reader)
|
||||||
:OggDecoder(reader) {}
|
:OggDecoder(reader) {}
|
||||||
@@ -101,6 +127,13 @@ public:
|
|||||||
bool Seek(uint64_t where_frame);
|
bool Seek(uint64_t where_frame);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
void AddGranulepos(ogg_int64_t n) noexcept {
|
||||||
|
assert(n >= 0);
|
||||||
|
|
||||||
|
if (granulepos >= 0)
|
||||||
|
granulepos += n;
|
||||||
|
}
|
||||||
|
|
||||||
void HandleTags(const ogg_packet &packet);
|
void HandleTags(const ogg_packet &packet);
|
||||||
void HandleAudio(const ogg_packet &packet);
|
void HandleAudio(const ogg_packet &packet);
|
||||||
|
|
||||||
@@ -137,10 +170,13 @@ MPDOpusDecoder::OnOggBeginning(const ogg_packet &packet)
|
|||||||
throw std::runtime_error("BOS packet must be OpusHead");
|
throw std::runtime_error("BOS packet must be OpusHead");
|
||||||
|
|
||||||
unsigned channels;
|
unsigned channels;
|
||||||
if (!ScanOpusHeader(packet.packet, packet.bytes, channels) ||
|
if (!ScanOpusHeader(packet.packet, packet.bytes, channels, output_gain, pre_skip) ||
|
||||||
!audio_valid_channel_count(channels))
|
!audio_valid_channel_count(channels))
|
||||||
throw std::runtime_error("Malformed BOS packet");
|
throw std::runtime_error("Malformed BOS packet");
|
||||||
|
|
||||||
|
granulepos = 0;
|
||||||
|
skip = pre_skip;
|
||||||
|
|
||||||
assert(opus_decoder == nullptr);
|
assert(opus_decoder == nullptr);
|
||||||
assert(IsInitialized() == (output_buffer != nullptr));
|
assert(IsInitialized() == (output_buffer != nullptr));
|
||||||
|
|
||||||
@@ -177,6 +213,10 @@ MPDOpusDecoder::OnOggBeginning(const ogg_packet &packet)
|
|||||||
client.Ready(audio_format, eos_granulepos > 0, duration);
|
client.Ready(audio_format, eos_granulepos > 0, duration);
|
||||||
frame_size = audio_format.GetFrameSize();
|
frame_size = audio_format.GetFrameSize();
|
||||||
|
|
||||||
|
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
|
output_buffer = new opus_int16[opus_output_buffer_frames
|
||||||
* audio_format.channels];
|
* audio_format.channels];
|
||||||
|
|
||||||
@@ -205,6 +245,15 @@ MPDOpusDecoder::HandleTags(const ogg_packet &packet)
|
|||||||
ReplayGainInfo rgi;
|
ReplayGainInfo rgi;
|
||||||
rgi.Clear();
|
rgi.Clear();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Output gain is a Q7.8 fixed point number in dB that should be,
|
||||||
|
* applied unconditionally, but is often used specifically for
|
||||||
|
* ReplayGain. Add 5dB to compensate for the different
|
||||||
|
* reference levels between ReplayGain (89dB) and EBU R128 (-23 LUFS).
|
||||||
|
*/
|
||||||
|
rgi.track.gain = float(output_gain) / 256.0f + 5;
|
||||||
|
rgi.album.gain = float(output_gain) / 256.0f + 5;
|
||||||
|
|
||||||
TagBuilder tag_builder;
|
TagBuilder tag_builder;
|
||||||
AddTagHandler h(tag_builder);
|
AddTagHandler h(tag_builder);
|
||||||
|
|
||||||
@@ -231,22 +280,58 @@ MPDOpusDecoder::HandleAudio(const ogg_packet &packet)
|
|||||||
packet.bytes,
|
packet.bytes,
|
||||||
output_buffer, opus_output_buffer_frames,
|
output_buffer, opus_output_buffer_frames,
|
||||||
0);
|
0);
|
||||||
|
if (gcc_unlikely(nframes <= 0)) {
|
||||||
if (nframes < 0)
|
if (nframes < 0)
|
||||||
throw FormatRuntimeError("libopus error: %s",
|
throw FormatRuntimeError("libopus error: %s",
|
||||||
opus_strerror(nframes));
|
opus_strerror(nframes));
|
||||||
|
else
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (nframes > 0) {
|
/* 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;
|
const size_t nbytes = nframes * frame_size;
|
||||||
auto cmd = client.SubmitData(input_stream,
|
auto cmd = client.SubmitData(input_stream,
|
||||||
output_buffer, nbytes,
|
data, nbytes,
|
||||||
0);
|
0);
|
||||||
if (cmd != DecoderCommand::NONE)
|
if (cmd != DecoderCommand::NONE)
|
||||||
throw cmd;
|
throw cmd;
|
||||||
|
|
||||||
if (packet.granulepos > 0)
|
if (packet.granulepos > 0) {
|
||||||
client.SubmitTimestamp(FloatDuration(packet.granulepos)
|
granulepos = packet.granulepos;
|
||||||
|
client.SubmitTimestamp(FloatDuration(granulepos - pre_skip)
|
||||||
/ opus_sample_rate);
|
/ opus_sample_rate);
|
||||||
}
|
} else
|
||||||
|
AddGranulepos(nframes);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
@@ -258,8 +343,20 @@ MPDOpusDecoder::Seek(uint64_t where_frame)
|
|||||||
|
|
||||||
const ogg_int64_t where_granulepos(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 {
|
try {
|
||||||
SeekGranulePos(where_granulepos);
|
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;
|
return true;
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
return false;
|
return false;
|
||||||
@@ -302,13 +399,14 @@ mpd_opus_stream_decode(DecoderClient &client,
|
|||||||
|
|
||||||
static bool
|
static bool
|
||||||
ReadAndParseOpusHead(OggSyncState &sync, OggStreamState &stream,
|
ReadAndParseOpusHead(OggSyncState &sync, OggStreamState &stream,
|
||||||
unsigned &channels)
|
unsigned &channels, signed &output_gain, unsigned &pre_skip)
|
||||||
{
|
{
|
||||||
ogg_packet packet;
|
ogg_packet packet;
|
||||||
|
|
||||||
return OggReadPacket(sync, stream, packet) && packet.b_o_s &&
|
return OggReadPacket(sync, stream, packet) && packet.b_o_s &&
|
||||||
IsOpusHead(packet) &&
|
IsOpusHead(packet) &&
|
||||||
ScanOpusHeader(packet.packet, packet.bytes, channels) &&
|
ScanOpusHeader(packet.packet, packet.bytes, channels,
|
||||||
|
output_gain, pre_skip) &&
|
||||||
audio_valid_channel_count(channels);
|
audio_valid_channel_count(channels);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -327,11 +425,12 @@ ReadAndVisitOpusTags(OggSyncState &sync, OggStreamState &stream,
|
|||||||
|
|
||||||
static void
|
static void
|
||||||
VisitOpusDuration(InputStream &is, OggSyncState &sync, OggStreamState &stream,
|
VisitOpusDuration(InputStream &is, OggSyncState &sync, OggStreamState &stream,
|
||||||
TagHandler &handler)
|
ogg_int64_t pre_skip, TagHandler &handler)
|
||||||
{
|
{
|
||||||
ogg_packet packet;
|
ogg_packet packet;
|
||||||
|
|
||||||
if (OggSeekFindEOS(sync, stream, packet, is)) {
|
if (OggSeekFindEOS(sync, stream, packet, is) &&
|
||||||
|
packet.granulepos >= pre_skip) {
|
||||||
const auto duration =
|
const auto duration =
|
||||||
SongTime::FromScale<uint64_t>(packet.granulepos,
|
SongTime::FromScale<uint64_t>(packet.granulepos,
|
||||||
opus_sample_rate);
|
opus_sample_rate);
|
||||||
@@ -340,7 +439,7 @@ VisitOpusDuration(InputStream &is, OggSyncState &sync, OggStreamState &stream,
|
|||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
mpd_opus_scan_stream(InputStream &is, TagHandler &handler) noexcept
|
mpd_opus_scan_stream(InputStream &is, TagHandler &handler)
|
||||||
{
|
{
|
||||||
InputStreamReader reader(is);
|
InputStreamReader reader(is);
|
||||||
OggSyncState oy(reader);
|
OggSyncState oy(reader);
|
||||||
@@ -351,15 +450,16 @@ mpd_opus_scan_stream(InputStream &is, TagHandler &handler) noexcept
|
|||||||
|
|
||||||
OggStreamState os(first_page);
|
OggStreamState os(first_page);
|
||||||
|
|
||||||
unsigned channels;
|
unsigned channels, pre_skip;
|
||||||
if (!ReadAndParseOpusHead(oy, os, channels) ||
|
signed output_gain;
|
||||||
|
if (!ReadAndParseOpusHead(oy, os, channels, output_gain, pre_skip) ||
|
||||||
!ReadAndVisitOpusTags(oy, os, handler))
|
!ReadAndVisitOpusTags(oy, os, handler))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
handler.OnAudioFormat(AudioFormat(opus_sample_rate,
|
handler.OnAudioFormat(AudioFormat(opus_sample_rate,
|
||||||
SampleFormat::S16, channels));
|
SampleFormat::S16, channels));
|
||||||
|
|
||||||
VisitOpusDuration(is, oy, os, handler);
|
VisitOpusDuration(is, oy, os, pre_skip, handler);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -18,6 +18,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "OpusHead.hxx"
|
#include "OpusHead.hxx"
|
||||||
|
#include "util/ByteOrder.hxx"
|
||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
@@ -31,12 +32,16 @@ struct OpusHead {
|
|||||||
};
|
};
|
||||||
|
|
||||||
bool
|
bool
|
||||||
ScanOpusHeader(const void *data, size_t size, unsigned &channels_r)
|
ScanOpusHeader(const void *data, size_t size, unsigned &channels_r,
|
||||||
|
signed &output_gain_r, unsigned &pre_skip_r)
|
||||||
{
|
{
|
||||||
const OpusHead *h = (const OpusHead *)data;
|
const OpusHead *h = (const OpusHead *)data;
|
||||||
if (size < 19 || (h->version & 0xf0) != 0)
|
if (size < 19 || (h->version & 0xf0) != 0)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
output_gain_r = FromLE16S(h->output_gain);
|
||||||
|
|
||||||
channels_r = h->channels;
|
channels_r = h->channels;
|
||||||
|
pre_skip_r = FromLE16(h->pre_skip);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@@ -23,6 +23,7 @@
|
|||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
|
|
||||||
bool
|
bool
|
||||||
ScanOpusHeader(const void *data, size_t size, unsigned &channels_r);
|
ScanOpusHeader(const void *data, size_t size, unsigned &channels_r,
|
||||||
|
signed &output_gain_r, unsigned &pre_skip_r);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@@ -53,7 +53,7 @@ ScanOneOpusTag(const char *name, const char *value,
|
|||||||
char *endptr;
|
char *endptr;
|
||||||
long l = strtol(value, &endptr, 10);
|
long l = strtol(value, &endptr, 10);
|
||||||
if (endptr > value && *endptr == 0)
|
if (endptr > value && *endptr == 0)
|
||||||
rgi->track.gain = double(l) / 256.;
|
rgi->track.gain += float(l) / 256.0f;
|
||||||
} else if (rgi != nullptr &&
|
} else if (rgi != nullptr &&
|
||||||
StringEqualsCaseASCII(name, "R128_ALBUM_GAIN")) {
|
StringEqualsCaseASCII(name, "R128_ALBUM_GAIN")) {
|
||||||
/* R128_ALBUM_GAIN is a Q7.8 fixed point number in
|
/* R128_ALBUM_GAIN is a Q7.8 fixed point number in
|
||||||
@@ -62,7 +62,7 @@ ScanOneOpusTag(const char *name, const char *value,
|
|||||||
char *endptr;
|
char *endptr;
|
||||||
long l = strtol(value, &endptr, 10);
|
long l = strtol(value, &endptr, 10);
|
||||||
if (endptr > value && *endptr == 0)
|
if (endptr > value && *endptr == 0)
|
||||||
rgi->album.gain = double(l) / 256.;
|
rgi->album.gain += float(l) / 256.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
handler.OnPair(name, value);
|
handler.OnPair(name, value);
|
||||||
|
@@ -22,7 +22,7 @@
|
|||||||
#include "CheckAudioFormat.hxx"
|
#include "CheckAudioFormat.hxx"
|
||||||
#include "pcm/PcmPack.hxx"
|
#include "pcm/PcmPack.hxx"
|
||||||
#include "input/InputStream.hxx"
|
#include "input/InputStream.hxx"
|
||||||
#include "system/ByteOrder.hxx"
|
#include "util/ByteOrder.hxx"
|
||||||
#include "util/Domain.hxx"
|
#include "util/Domain.hxx"
|
||||||
#include "util/ByteReverse.hxx"
|
#include "util/ByteReverse.hxx"
|
||||||
#include "util/StaticFifoBuffer.hxx"
|
#include "util/StaticFifoBuffer.hxx"
|
||||||
|
@@ -35,7 +35,7 @@
|
|||||||
#include "util/Domain.hxx"
|
#include "util/Domain.hxx"
|
||||||
#include "util/AllocatedString.hxx"
|
#include "util/AllocatedString.hxx"
|
||||||
#include "util/CharUtil.hxx"
|
#include "util/CharUtil.hxx"
|
||||||
#include "system/ByteOrder.hxx"
|
#include "util/ByteOrder.hxx"
|
||||||
#include "Log.hxx"
|
#include "Log.hxx"
|
||||||
|
|
||||||
#ifdef HAVE_SIDPLAYFP
|
#ifdef HAVE_SIDPLAYFP
|
||||||
|
@@ -46,9 +46,7 @@ struct SndfileInputStream {
|
|||||||
size_t Read(void *buffer, size_t size) {
|
size_t Read(void *buffer, size_t size) {
|
||||||
/* libsndfile chokes on partial reads; therefore
|
/* libsndfile chokes on partial reads; therefore
|
||||||
always force full reads */
|
always force full reads */
|
||||||
return decoder_read_full(client, is, buffer, size)
|
return decoder_read_much(client, is, buffer, size);
|
||||||
? size
|
|
||||||
: 0;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -267,7 +265,7 @@ static constexpr struct {
|
|||||||
};
|
};
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
sndfile_scan_stream(InputStream &is, TagHandler &handler) noexcept
|
sndfile_scan_stream(InputStream &is, TagHandler &handler)
|
||||||
{
|
{
|
||||||
SF_INFO info;
|
SF_INFO info;
|
||||||
|
|
||||||
|
@@ -370,7 +370,7 @@ VisitVorbisDuration(InputStream &is,
|
|||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
vorbis_scan_stream(InputStream &is, TagHandler &handler) noexcept
|
vorbis_scan_stream(InputStream &is, TagHandler &handler)
|
||||||
{
|
{
|
||||||
/* initialize libogg */
|
/* initialize libogg */
|
||||||
|
|
||||||
|
@@ -614,7 +614,7 @@ wavpack_scan_file(Path path_fs, TagHandler &handler) noexcept
|
|||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
wavpack_scan_stream(InputStream &is, TagHandler &handler) noexcept
|
wavpack_scan_stream(InputStream &is, TagHandler &handler)
|
||||||
{
|
{
|
||||||
WavpackInput isp(nullptr, is);
|
WavpackInput isp(nullptr, is);
|
||||||
|
|
||||||
|
@@ -77,9 +77,9 @@ PreparedLameEncoder::PreparedLameEncoder(const ConfigBlock &block)
|
|||||||
if (value != nullptr) {
|
if (value != nullptr) {
|
||||||
/* a quality was configured (VBR) */
|
/* a quality was configured (VBR) */
|
||||||
|
|
||||||
quality = ParseDouble(value, &endptr);
|
quality = float(ParseDouble(value, &endptr));
|
||||||
|
|
||||||
if (*endptr != '\0' || quality < -1.0 || quality > 10.0)
|
if (*endptr != '\0' || quality < -1.0f || quality > 10.0f)
|
||||||
throw FormatRuntimeError("quality \"%s\" is not a number in the "
|
throw FormatRuntimeError("quality \"%s\" is not a number in the "
|
||||||
"range -1 to 10",
|
"range -1 to 10",
|
||||||
value);
|
value);
|
||||||
@@ -111,13 +111,13 @@ static void
|
|||||||
lame_encoder_setup(lame_global_flags *gfp, float quality, int bitrate,
|
lame_encoder_setup(lame_global_flags *gfp, float quality, int bitrate,
|
||||||
const AudioFormat &audio_format)
|
const AudioFormat &audio_format)
|
||||||
{
|
{
|
||||||
if (quality >= -1.0) {
|
if (quality >= -1.0f) {
|
||||||
/* a quality was configured (VBR) */
|
/* a quality was configured (VBR) */
|
||||||
|
|
||||||
if (0 != lame_set_VBR(gfp, vbr_rh))
|
if (0 != lame_set_VBR(gfp, vbr_rh))
|
||||||
throw std::runtime_error("error setting lame VBR mode");
|
throw std::runtime_error("error setting lame VBR mode");
|
||||||
|
|
||||||
if (0 != lame_set_VBR_q(gfp, quality))
|
if (0 != lame_set_VBR_q(gfp, int(quality)))
|
||||||
throw std::runtime_error("error setting lame VBR quality");
|
throw std::runtime_error("error setting lame VBR quality");
|
||||||
} else {
|
} else {
|
||||||
/* a bit rate was configured */
|
/* a bit rate was configured */
|
||||||
|
@@ -22,7 +22,7 @@
|
|||||||
#include "AudioFormat.hxx"
|
#include "AudioFormat.hxx"
|
||||||
#include "config/Domain.hxx"
|
#include "config/Domain.hxx"
|
||||||
#include "util/Alloc.hxx"
|
#include "util/Alloc.hxx"
|
||||||
#include "system/ByteOrder.hxx"
|
#include "util/ByteOrder.hxx"
|
||||||
#include "util/StringUtil.hxx"
|
#include "util/StringUtil.hxx"
|
||||||
|
|
||||||
#include <opus.h>
|
#include <opus.h>
|
||||||
|
@@ -95,9 +95,9 @@ PreparedTwolameEncoder::PreparedTwolameEncoder(const ConfigBlock &block)
|
|||||||
if (value != nullptr) {
|
if (value != nullptr) {
|
||||||
/* a quality was configured (VBR) */
|
/* a quality was configured (VBR) */
|
||||||
|
|
||||||
quality = ParseDouble(value, &endptr);
|
quality = float(ParseDouble(value, &endptr));
|
||||||
|
|
||||||
if (*endptr != '\0' || quality < -1.0 || quality > 10.0)
|
if (*endptr != '\0' || quality < -1.0f || quality > 10.0f)
|
||||||
throw FormatRuntimeError("quality \"%s\" is not a number in the "
|
throw FormatRuntimeError("quality \"%s\" is not a number in the "
|
||||||
"range -1 to 10",
|
"range -1 to 10",
|
||||||
value);
|
value);
|
||||||
@@ -132,7 +132,7 @@ static void
|
|||||||
twolame_encoder_setup(twolame_options *options, float quality, int bitrate,
|
twolame_encoder_setup(twolame_options *options, float quality, int bitrate,
|
||||||
const AudioFormat &audio_format)
|
const AudioFormat &audio_format)
|
||||||
{
|
{
|
||||||
if (quality >= -1.0) {
|
if (quality >= -1.0f) {
|
||||||
/* a quality was configured (VBR) */
|
/* a quality was configured (VBR) */
|
||||||
|
|
||||||
if (0 != twolame_set_VBR(options, true))
|
if (0 != twolame_set_VBR(options, true))
|
||||||
|
@@ -84,7 +84,7 @@ PreparedVorbisEncoder::PreparedVorbisEncoder(const ConfigBlock &block)
|
|||||||
char *endptr;
|
char *endptr;
|
||||||
quality = ParseDouble(value, &endptr);
|
quality = ParseDouble(value, &endptr);
|
||||||
|
|
||||||
if (*endptr != '\0' || quality < -1.0 || quality > 10.0)
|
if (*endptr != '\0' || quality < -1.0f || quality > 10.0f)
|
||||||
throw FormatRuntimeError("quality \"%s\" is not a number in the "
|
throw FormatRuntimeError("quality \"%s\" is not a number in the "
|
||||||
"range -1 to 10",
|
"range -1 to 10",
|
||||||
value);
|
value);
|
||||||
@@ -122,13 +122,13 @@ VorbisEncoder::VorbisEncoder(float quality, int bitrate,
|
|||||||
_audio_format.format = SampleFormat::FLOAT;
|
_audio_format.format = SampleFormat::FLOAT;
|
||||||
audio_format = _audio_format;
|
audio_format = _audio_format;
|
||||||
|
|
||||||
if (quality >= -1.0) {
|
if (quality >= -1.0f) {
|
||||||
/* a quality was configured (VBR) */
|
/* a quality was configured (VBR) */
|
||||||
|
|
||||||
if (0 != vorbis_encode_init_vbr(&vi,
|
if (0 != vorbis_encode_init_vbr(&vi,
|
||||||
audio_format.channels,
|
audio_format.channels,
|
||||||
audio_format.sample_rate,
|
audio_format.sample_rate,
|
||||||
quality * 0.1)) {
|
quality * 0.1f)) {
|
||||||
vorbis_info_clear(&vi);
|
vorbis_info_clear(&vi);
|
||||||
throw std::runtime_error("error initializing vorbis vbr");
|
throw std::runtime_error("error initializing vorbis vbr");
|
||||||
}
|
}
|
||||||
@@ -138,7 +138,7 @@ VorbisEncoder::VorbisEncoder(float quality, int bitrate,
|
|||||||
if (0 != vorbis_encode_init(&vi,
|
if (0 != vorbis_encode_init(&vi,
|
||||||
audio_format.channels,
|
audio_format.channels,
|
||||||
audio_format.sample_rate, -1.0,
|
audio_format.sample_rate, -1.0,
|
||||||
bitrate * 1000, -1.0)) {
|
bitrate * 1000, -1.0f)) {
|
||||||
vorbis_info_clear(&vi);
|
vorbis_info_clear(&vi);
|
||||||
throw std::runtime_error("error initializing vorbis encoder");
|
throw std::runtime_error("error initializing vorbis encoder");
|
||||||
}
|
}
|
||||||
|
@@ -19,7 +19,7 @@
|
|||||||
|
|
||||||
#include "WaveEncoderPlugin.hxx"
|
#include "WaveEncoderPlugin.hxx"
|
||||||
#include "../EncoderAPI.hxx"
|
#include "../EncoderAPI.hxx"
|
||||||
#include "system/ByteOrder.hxx"
|
#include "util/ByteOrder.hxx"
|
||||||
#include "util/DynamicFifoBuffer.hxx"
|
#include "util/DynamicFifoBuffer.hxx"
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
@@ -58,6 +58,11 @@ input_stream_global_init(const ConfigData &config, EventLoop &event_loop)
|
|||||||
if (plugin->init != nullptr)
|
if (plugin->init != nullptr)
|
||||||
plugin->init(event_loop, *block);
|
plugin->init(event_loop, *block);
|
||||||
input_plugins_enabled[i] = true;
|
input_plugins_enabled[i] = true;
|
||||||
|
} catch (const PluginUnconfigured &e) {
|
||||||
|
LogFormat(LogLevel::DEBUG, e,
|
||||||
|
"Input plugin '%s' is not configured",
|
||||||
|
plugin->name);
|
||||||
|
continue;
|
||||||
} catch (const PluginUnavailable &e) {
|
} catch (const PluginUnavailable &e) {
|
||||||
FormatError(e,
|
FormatError(e,
|
||||||
"Input plugin '%s' is unavailable",
|
"Input plugin '%s' is unavailable",
|
||||||
|
@@ -305,18 +305,18 @@ ConfigureCapture(snd_pcm_t *capture_handle,
|
|||||||
snd_pcm_hw_params_get_buffer_size_min(hw_params, &buffer_size_min);
|
snd_pcm_hw_params_get_buffer_size_min(hw_params, &buffer_size_min);
|
||||||
snd_pcm_hw_params_get_buffer_size_max(hw_params, &buffer_size_max);
|
snd_pcm_hw_params_get_buffer_size_max(hw_params, &buffer_size_max);
|
||||||
unsigned buffer_time_min, buffer_time_max;
|
unsigned buffer_time_min, buffer_time_max;
|
||||||
snd_pcm_hw_params_get_buffer_time_min(hw_params, &buffer_time_min, 0);
|
snd_pcm_hw_params_get_buffer_time_min(hw_params, &buffer_time_min, nullptr);
|
||||||
snd_pcm_hw_params_get_buffer_time_max(hw_params, &buffer_time_max, 0);
|
snd_pcm_hw_params_get_buffer_time_max(hw_params, &buffer_time_max, nullptr);
|
||||||
FormatDebug(alsa_input_domain, "buffer: size=%u..%u time=%u..%u",
|
FormatDebug(alsa_input_domain, "buffer: size=%u..%u time=%u..%u",
|
||||||
(unsigned)buffer_size_min, (unsigned)buffer_size_max,
|
(unsigned)buffer_size_min, (unsigned)buffer_size_max,
|
||||||
buffer_time_min, buffer_time_max);
|
buffer_time_min, buffer_time_max);
|
||||||
|
|
||||||
snd_pcm_uframes_t period_size_min, period_size_max;
|
snd_pcm_uframes_t period_size_min, period_size_max;
|
||||||
snd_pcm_hw_params_get_period_size_min(hw_params, &period_size_min, 0);
|
snd_pcm_hw_params_get_period_size_min(hw_params, &period_size_min, nullptr);
|
||||||
snd_pcm_hw_params_get_period_size_max(hw_params, &period_size_max, 0);
|
snd_pcm_hw_params_get_period_size_max(hw_params, &period_size_max, nullptr);
|
||||||
unsigned period_time_min, period_time_max;
|
unsigned period_time_min, period_time_max;
|
||||||
snd_pcm_hw_params_get_period_time_min(hw_params, &period_time_min, 0);
|
snd_pcm_hw_params_get_period_time_min(hw_params, &period_time_min, nullptr);
|
||||||
snd_pcm_hw_params_get_period_time_max(hw_params, &period_time_max, 0);
|
snd_pcm_hw_params_get_period_time_max(hw_params, &period_time_max, nullptr);
|
||||||
FormatDebug(alsa_input_domain, "period: size=%u..%u time=%u..%u",
|
FormatDebug(alsa_input_domain, "period: size=%u..%u time=%u..%u",
|
||||||
(unsigned)period_size_min, (unsigned)period_size_max,
|
(unsigned)period_size_min, (unsigned)period_size_max,
|
||||||
period_time_min, period_time_max);
|
period_time_min, period_time_max);
|
||||||
|
@@ -30,7 +30,7 @@
|
|||||||
#include "util/StringCompare.hxx"
|
#include "util/StringCompare.hxx"
|
||||||
#include "util/RuntimeError.hxx"
|
#include "util/RuntimeError.hxx"
|
||||||
#include "util/Domain.hxx"
|
#include "util/Domain.hxx"
|
||||||
#include "system/ByteOrder.hxx"
|
#include "util/ByteOrder.hxx"
|
||||||
#include "fs/AllocatedPath.hxx"
|
#include "fs/AllocatedPath.hxx"
|
||||||
#include "Log.hxx"
|
#include "Log.hxx"
|
||||||
#include "config/Block.hxx"
|
#include "config/Block.hxx"
|
||||||
|
@@ -139,8 +139,9 @@ FfmpegInputStream::Seek(offset_type new_offset)
|
|||||||
|
|
||||||
static constexpr const char *ffmpeg_prefixes[] = {
|
static constexpr const char *ffmpeg_prefixes[] = {
|
||||||
"gopher://",
|
"gopher://",
|
||||||
|
"hls+http://",
|
||||||
|
"hls+https://",
|
||||||
"rtp://",
|
"rtp://",
|
||||||
"rtsp://",
|
|
||||||
"rtmp://",
|
"rtmp://",
|
||||||
"rtmpt://",
|
"rtmpt://",
|
||||||
"rtmps://",
|
"rtmps://",
|
||||||
|
@@ -26,6 +26,8 @@
|
|||||||
#include "system/FileDescriptor.hxx"
|
#include "system/FileDescriptor.hxx"
|
||||||
#include "util/RuntimeError.hxx"
|
#include "util/RuntimeError.hxx"
|
||||||
|
|
||||||
|
#include <cinttypes> // for PRIu64 (PRIoffset)
|
||||||
|
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
|
|
||||||
@@ -94,6 +96,11 @@ FileInputStream::Read(void *ptr, size_t read_size)
|
|||||||
nbytes = reader.Read(ptr, 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;
|
offset += nbytes;
|
||||||
return nbytes;
|
return nbytes;
|
||||||
}
|
}
|
||||||
|
@@ -133,11 +133,11 @@ InitQobuzInput(EventLoop &event_loop, const ConfigBlock &block)
|
|||||||
|
|
||||||
const char *app_id = block.GetBlockValue("app_id");
|
const char *app_id = block.GetBlockValue("app_id");
|
||||||
if (app_id == nullptr)
|
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");
|
const char *app_secret = block.GetBlockValue("app_secret");
|
||||||
if (app_secret == nullptr)
|
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",
|
const char *device_manufacturer_id = block.GetBlockValue("device_manufacturer_id",
|
||||||
"df691fdc-fa36-11e7-9718-635337d7df8f");
|
"df691fdc-fa36-11e7-9718-635337d7df8f");
|
||||||
@@ -145,11 +145,11 @@ InitQobuzInput(EventLoop &event_loop, const ConfigBlock &block)
|
|||||||
const char *username = block.GetBlockValue("username");
|
const char *username = block.GetBlockValue("username");
|
||||||
const char *email = block.GetBlockValue("email");
|
const char *email = block.GetBlockValue("email");
|
||||||
if (username == nullptr && email == nullptr)
|
if (username == nullptr && email == nullptr)
|
||||||
throw PluginUnavailable("No Qobuz username configured");
|
throw PluginUnconfigured("No Qobuz username configured");
|
||||||
|
|
||||||
const char *password = block.GetBlockValue("password");
|
const char *password = block.GetBlockValue("password");
|
||||||
if (password == nullptr)
|
if (password == nullptr)
|
||||||
throw PluginUnavailable("No Qobuz password configured");
|
throw PluginUnconfigured("No Qobuz password configured");
|
||||||
|
|
||||||
const char *format_id = block.GetBlockValue("format_id", "5");
|
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");
|
const char *token = block.GetBlockValue("token");
|
||||||
if (token == nullptr)
|
if (token == nullptr)
|
||||||
throw PluginUnavailable("No Tidal application token configured");
|
throw PluginUnconfigured("No Tidal application token configured");
|
||||||
|
|
||||||
const char *username = block.GetBlockValue("username");
|
const char *username = block.GetBlockValue("username");
|
||||||
if (username == nullptr)
|
if (username == nullptr)
|
||||||
throw PluginUnavailable("No Tidal username configured");
|
throw PluginUnconfigured("No Tidal username configured");
|
||||||
|
|
||||||
const char *password = block.GetBlockValue("password");
|
const char *password = block.GetBlockValue("password");
|
||||||
if (password == nullptr)
|
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");
|
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 "HwSetup.hxx"
|
||||||
#include "Format.hxx"
|
#include "Format.hxx"
|
||||||
#include "system/ByteOrder.hxx"
|
#include "util/ByteOrder.hxx"
|
||||||
#include "util/Domain.hxx"
|
#include "util/Domain.hxx"
|
||||||
#include "util/RuntimeError.hxx"
|
#include "util/RuntimeError.hxx"
|
||||||
#include "AudioFormat.hxx"
|
#include "AudioFormat.hxx"
|
||||||
@@ -238,18 +238,18 @@ SetupHw(snd_pcm_t *pcm,
|
|||||||
snd_pcm_hw_params_get_buffer_size_min(hwparams, &buffer_size_min);
|
snd_pcm_hw_params_get_buffer_size_min(hwparams, &buffer_size_min);
|
||||||
snd_pcm_hw_params_get_buffer_size_max(hwparams, &buffer_size_max);
|
snd_pcm_hw_params_get_buffer_size_max(hwparams, &buffer_size_max);
|
||||||
unsigned buffer_time_min, buffer_time_max;
|
unsigned buffer_time_min, buffer_time_max;
|
||||||
snd_pcm_hw_params_get_buffer_time_min(hwparams, &buffer_time_min, 0);
|
snd_pcm_hw_params_get_buffer_time_min(hwparams, &buffer_time_min, nullptr);
|
||||||
snd_pcm_hw_params_get_buffer_time_max(hwparams, &buffer_time_max, 0);
|
snd_pcm_hw_params_get_buffer_time_max(hwparams, &buffer_time_max, nullptr);
|
||||||
FormatDebug(alsa_output_domain, "buffer: size=%u..%u time=%u..%u",
|
FormatDebug(alsa_output_domain, "buffer: size=%u..%u time=%u..%u",
|
||||||
(unsigned)buffer_size_min, (unsigned)buffer_size_max,
|
(unsigned)buffer_size_min, (unsigned)buffer_size_max,
|
||||||
buffer_time_min, buffer_time_max);
|
buffer_time_min, buffer_time_max);
|
||||||
|
|
||||||
snd_pcm_uframes_t period_size_min, period_size_max;
|
snd_pcm_uframes_t period_size_min, period_size_max;
|
||||||
snd_pcm_hw_params_get_period_size_min(hwparams, &period_size_min, 0);
|
snd_pcm_hw_params_get_period_size_min(hwparams, &period_size_min, nullptr);
|
||||||
snd_pcm_hw_params_get_period_size_max(hwparams, &period_size_max, 0);
|
snd_pcm_hw_params_get_period_size_max(hwparams, &period_size_max, nullptr);
|
||||||
unsigned period_time_min, period_time_max;
|
unsigned period_time_min, period_time_max;
|
||||||
snd_pcm_hw_params_get_period_time_min(hwparams, &period_time_min, 0);
|
snd_pcm_hw_params_get_period_time_min(hwparams, &period_time_min, nullptr);
|
||||||
snd_pcm_hw_params_get_period_time_max(hwparams, &period_time_max, 0);
|
snd_pcm_hw_params_get_period_time_max(hwparams, &period_time_max, nullptr);
|
||||||
FormatDebug(alsa_output_domain, "period: size=%u..%u time=%u..%u",
|
FormatDebug(alsa_output_domain, "period: size=%u..%u time=%u..%u",
|
||||||
(unsigned)period_size_min, (unsigned)period_size_max,
|
(unsigned)period_size_min, (unsigned)period_size_max,
|
||||||
period_time_min, period_time_max);
|
period_time_min, period_time_max);
|
||||||
|
@@ -62,6 +62,6 @@ FfmpegLogCallback(gcc_unused void *ptr, int level, const char *fmt, va_list vl)
|
|||||||
ffmpeg_domain.GetName(),
|
ffmpeg_domain.GetName(),
|
||||||
cls->item_name(ptr));
|
cls->item_name(ptr));
|
||||||
const Domain d(domain);
|
const Domain d(domain);
|
||||||
LogFormatV(d, FfmpegImportLogLevel(level), fmt, vl);
|
LogFormatV(FfmpegImportLogLevel(level), d, fmt, vl);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -18,7 +18,7 @@ if icu_dep.found()
|
|||||||
'Init.cxx',
|
'Init.cxx',
|
||||||
]
|
]
|
||||||
elif not get_option('iconv').disabled()
|
elif not get_option('iconv').disabled()
|
||||||
have_iconv = compiler.has_function('iconv')
|
have_iconv = compiler.has_function('iconv', prefix : '#include <iconv.h>')
|
||||||
conf.set('HAVE_ICONV', have_iconv)
|
conf.set('HAVE_ICONV', have_iconv)
|
||||||
if not have_iconv and get_option('iconv').enabled()
|
if not have_iconv and get_option('iconv').enabled()
|
||||||
error('iconv() not available')
|
error('iconv() not available')
|
||||||
|
@@ -60,7 +60,7 @@ ContentDirectoryService::getSearchCapabilities(UpnpClient_Handle hdl) const
|
|||||||
IXML_Document *_response;
|
IXML_Document *_response;
|
||||||
auto code = UpnpSendAction(hdl, m_actionURL.c_str(),
|
auto code = UpnpSendAction(hdl, m_actionURL.c_str(),
|
||||||
m_serviceType.c_str(),
|
m_serviceType.c_str(),
|
||||||
0 /*devUDN*/, request.get(), &_response);
|
nullptr /*devUDN*/, request.get(), &_response);
|
||||||
if (code != UPNP_E_SUCCESS)
|
if (code != UPNP_E_SUCCESS)
|
||||||
throw FormatRuntimeError("UpnpSendAction() failed: %s",
|
throw FormatRuntimeError("UpnpSendAction() failed: %s",
|
||||||
UpnpGetErrorMessage(code));
|
UpnpGetErrorMessage(code));
|
||||||
|
@@ -27,7 +27,7 @@
|
|||||||
MixerType
|
MixerType
|
||||||
mixer_type_parse(const char *input)
|
mixer_type_parse(const char *input)
|
||||||
{
|
{
|
||||||
assert(input != NULL);
|
assert(input != nullptr);
|
||||||
|
|
||||||
if (strcmp(input, "none") == 0 || strcmp(input, "disabled") == 0)
|
if (strcmp(input, "none") == 0 || strcmp(input, "disabled") == 0)
|
||||||
return MixerType::NONE;
|
return MixerType::NONE;
|
||||||
|
@@ -51,7 +51,7 @@ public:
|
|||||||
double _volume_scale_factor)
|
double _volume_scale_factor)
|
||||||
:Mixer(pulse_mixer_plugin, _listener),
|
:Mixer(pulse_mixer_plugin, _listener),
|
||||||
output(_output),
|
output(_output),
|
||||||
volume_scale_factor(_volume_scale_factor)
|
volume_scale_factor(float(_volume_scale_factor))
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -175,7 +175,7 @@ parse_volume_scale_factor(const char *value) {
|
|||||||
char *endptr;
|
char *endptr;
|
||||||
float factor = ParseFloat(value, &endptr);
|
float factor = ParseFloat(value, &endptr);
|
||||||
|
|
||||||
if (endptr == value || *endptr != '\0' || factor < 0.5 || factor > 5.0)
|
if (endptr == value || *endptr != '\0' || factor < 0.5f || factor > 5.0f)
|
||||||
throw FormatRuntimeError("\"%s\" is not a number in the "
|
throw FormatRuntimeError("\"%s\" is not a number in the "
|
||||||
"range 0.5 to 5.0",
|
"range 0.5 to 5.0",
|
||||||
value);
|
value);
|
||||||
@@ -190,7 +190,7 @@ pulse_mixer_init(gcc_unused EventLoop &event_loop, AudioOutput &ao,
|
|||||||
{
|
{
|
||||||
PulseOutput &po = (PulseOutput &)ao;
|
PulseOutput &po = (PulseOutput &)ao;
|
||||||
float scale = parse_volume_scale_factor(block.GetBlockValue("scale_volume"));
|
float scale = parse_volume_scale_factor(block.GetBlockValue("scale_volume"));
|
||||||
PulseMixer *pm = new PulseMixer(po, listener, scale);
|
auto *pm = new PulseMixer(po, listener, (double)scale);
|
||||||
|
|
||||||
pulse_output_set_mixer(po, *pm);
|
pulse_output_set_mixer(po, *pm);
|
||||||
|
|
||||||
@@ -216,7 +216,7 @@ PulseMixer::GetVolume()
|
|||||||
int
|
int
|
||||||
PulseMixer::GetVolumeInternal()
|
PulseMixer::GetVolumeInternal()
|
||||||
{
|
{
|
||||||
pa_volume_t max_pa_volume = volume_scale_factor * PA_VOLUME_NORM;
|
pa_volume_t max_pa_volume = pa_volume_t(volume_scale_factor * PA_VOLUME_NORM);
|
||||||
return online ?
|
return online ?
|
||||||
(int)((100 * (pa_cvolume_avg(&volume) + 1)) / max_pa_volume)
|
(int)((100 * (pa_cvolume_avg(&volume) + 1)) / max_pa_volume)
|
||||||
: -1;
|
: -1;
|
||||||
@@ -230,7 +230,7 @@ PulseMixer::SetVolume(unsigned new_volume)
|
|||||||
if (!online)
|
if (!online)
|
||||||
throw std::runtime_error("disconnected");
|
throw std::runtime_error("disconnected");
|
||||||
|
|
||||||
pa_volume_t max_pa_volume = volume_scale_factor * PA_VOLUME_NORM;
|
pa_volume_t max_pa_volume = pa_volume_t(volume_scale_factor * PA_VOLUME_NORM);
|
||||||
|
|
||||||
struct pa_cvolume cvolume;
|
struct pa_cvolume cvolume;
|
||||||
pa_cvolume_set(&cvolume, volume.channels,
|
pa_cvolume_set(&cvolume, volume.channels,
|
||||||
|
@@ -22,8 +22,8 @@
|
|||||||
#include "filter/plugins/VolumeFilterPlugin.hxx"
|
#include "filter/plugins/VolumeFilterPlugin.hxx"
|
||||||
#include "pcm/Volume.hxx"
|
#include "pcm/Volume.hxx"
|
||||||
|
|
||||||
#include <assert.h>
|
#include <cassert>
|
||||||
#include <math.h>
|
#include <cmath>
|
||||||
|
|
||||||
class SoftwareMixer final : public Mixer {
|
class SoftwareMixer final : public Mixer {
|
||||||
Filter *filter = nullptr;
|
Filter *filter = nullptr;
|
||||||
@@ -70,12 +70,12 @@ PercentVolumeToSoftwareVolume(unsigned volume) noexcept
|
|||||||
{
|
{
|
||||||
assert(volume <= 100);
|
assert(volume <= 100);
|
||||||
|
|
||||||
if (volume >= 100)
|
if (volume == 100)
|
||||||
return PCM_VOLUME_1;
|
return PCM_VOLUME_1;
|
||||||
else if (volume > 0)
|
else if (volume > 0)
|
||||||
return pcm_float_to_volume((exp(volume / 25.0) - 1) /
|
return pcm_float_to_volume((std::exp(volume / 25.0f) - 1) /
|
||||||
(54.5981500331F - 1));
|
(54.5981500331F - 1));
|
||||||
else
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -103,11 +103,6 @@ NeighborExplorer::List
|
|||||||
SmbclientNeighborExplorer::GetList() const noexcept
|
SmbclientNeighborExplorer::GetList() const noexcept
|
||||||
{
|
{
|
||||||
const std::lock_guard<Mutex> protect(mutex);
|
const std::lock_guard<Mutex> protect(mutex);
|
||||||
/*
|
|
||||||
List list;
|
|
||||||
for (const auto &i : servers)
|
|
||||||
list.emplace_front(i.Export());
|
|
||||||
*/
|
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -47,6 +47,11 @@ ToNeighborInfo(const UDisks2::Object &o) noexcept
|
|||||||
return {o.GetUri(), o.path};
|
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
|
class UdisksNeighborExplorer final
|
||||||
: public NeighborExplorer {
|
: public NeighborExplorer {
|
||||||
|
|
||||||
@@ -108,19 +113,19 @@ UdisksNeighborExplorer::DoOpen()
|
|||||||
|
|
||||||
auto &connection = GetConnection();
|
auto &connection = GetConnection();
|
||||||
|
|
||||||
|
/* this ugly try/catch cascade is only here because this
|
||||||
|
method has no RAII for this method - TODO: improve this */
|
||||||
try {
|
try {
|
||||||
Error error;
|
Error error;
|
||||||
dbus_bus_add_match(connection,
|
dbus_bus_add_match(connection, udisks_neighbor_match, error);
|
||||||
"type='signal',sender='" UDISKS2_INTERFACE "',"
|
|
||||||
"interface='" DBUS_OM_INTERFACE "',"
|
|
||||||
"path='" UDISKS2_PATH "'",
|
|
||||||
error);
|
|
||||||
error.CheckThrow("DBus AddMatch error");
|
error.CheckThrow("DBus AddMatch error");
|
||||||
|
|
||||||
|
try {
|
||||||
dbus_connection_add_filter(connection,
|
dbus_connection_add_filter(connection,
|
||||||
HandleMessage, this,
|
HandleMessage, this,
|
||||||
nullptr);
|
nullptr);
|
||||||
|
|
||||||
|
try {
|
||||||
auto msg = Message::NewMethodCall(UDISKS2_INTERFACE,
|
auto msg = Message::NewMethodCall(UDISKS2_INTERFACE,
|
||||||
UDISKS2_PATH,
|
UDISKS2_PATH,
|
||||||
DBUS_OM_INTERFACE,
|
DBUS_OM_INTERFACE,
|
||||||
@@ -128,6 +133,17 @@ UdisksNeighborExplorer::DoOpen()
|
|||||||
list_request.Send(connection, *msg.Get(),
|
list_request.Send(connection, *msg.Get(),
|
||||||
std::bind(&UdisksNeighborExplorer::OnListNotify,
|
std::bind(&UdisksNeighborExplorer::OnListNotify,
|
||||||
this, std::placeholders::_1));
|
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 (...) {
|
} catch (...) {
|
||||||
dbus_glue.Destruct();
|
dbus_glue.Destruct();
|
||||||
throw;
|
throw;
|
||||||
@@ -147,8 +163,10 @@ UdisksNeighborExplorer::DoClose() noexcept
|
|||||||
list_request.Cancel();
|
list_request.Cancel();
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: remove_match
|
auto &connection = GetConnection();
|
||||||
// TODO: remove_filter
|
|
||||||
|
dbus_connection_remove_filter(connection, HandleMessage, this);
|
||||||
|
dbus_bus_remove_match(connection, udisks_neighbor_match, nullptr);
|
||||||
|
|
||||||
dbus_glue.Destruct();
|
dbus_glue.Destruct();
|
||||||
}
|
}
|
||||||
|
@@ -31,7 +31,7 @@
|
|||||||
#define IPV4_ADDRESS_HXX
|
#define IPV4_ADDRESS_HXX
|
||||||
|
|
||||||
#include "SocketAddress.hxx"
|
#include "SocketAddress.hxx"
|
||||||
#include "system/ByteOrder.hxx"
|
#include "util/ByteOrder.hxx"
|
||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
|
@@ -31,7 +31,7 @@
|
|||||||
#define IPV6_ADDRESS_HXX
|
#define IPV6_ADDRESS_HXX
|
||||||
|
|
||||||
#include "SocketAddress.hxx"
|
#include "SocketAddress.hxx"
|
||||||
#include "system/ByteOrder.hxx"
|
#include "util/ByteOrder.hxx"
|
||||||
#include "util/Compiler.h"
|
#include "util/Compiler.h"
|
||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
@@ -39,6 +39,7 @@
|
|||||||
#include "config/Option.hxx"
|
#include "config/Option.hxx"
|
||||||
#include "config/Block.hxx"
|
#include "config/Block.hxx"
|
||||||
#include "util/RuntimeError.hxx"
|
#include "util/RuntimeError.hxx"
|
||||||
|
#include "util/StringAPI.hxx"
|
||||||
#include "util/StringFormat.hxx"
|
#include "util/StringFormat.hxx"
|
||||||
#include "Log.hxx"
|
#include "Log.hxx"
|
||||||
|
|
||||||
@@ -214,7 +215,7 @@ FilteredAudioOutput::Setup(EventLoop &event_loop,
|
|||||||
const char *replay_gain_handler =
|
const char *replay_gain_handler =
|
||||||
block.GetBlockValue("replay_gain_handler", "software");
|
block.GetBlockValue("replay_gain_handler", "software");
|
||||||
|
|
||||||
if (strcmp(replay_gain_handler, "none") != 0) {
|
if (!StringIsEqual(replay_gain_handler, "none")) {
|
||||||
prepared_replay_gain_filter =
|
prepared_replay_gain_filter =
|
||||||
NewReplayGainFilter(replay_gain_config);
|
NewReplayGainFilter(replay_gain_config);
|
||||||
assert(prepared_replay_gain_filter != nullptr);
|
assert(prepared_replay_gain_filter != nullptr);
|
||||||
@@ -240,14 +241,14 @@ FilteredAudioOutput::Setup(EventLoop &event_loop,
|
|||||||
|
|
||||||
/* use the hardware mixer for replay gain? */
|
/* use the hardware mixer for replay gain? */
|
||||||
|
|
||||||
if (strcmp(replay_gain_handler, "mixer") == 0) {
|
if (StringIsEqual(replay_gain_handler, "mixer")) {
|
||||||
if (mixer != nullptr)
|
if (mixer != nullptr)
|
||||||
replay_gain_filter_set_mixer(*prepared_replay_gain_filter,
|
replay_gain_filter_set_mixer(*prepared_replay_gain_filter,
|
||||||
mixer, 100);
|
mixer, 100);
|
||||||
else
|
else
|
||||||
FormatError(output_domain,
|
FormatError(output_domain,
|
||||||
"No such mixer for output '%s'", name);
|
"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) {
|
prepared_replay_gain_filter != nullptr) {
|
||||||
throw std::runtime_error("Invalid \"replay_gain_handler\" value");
|
throw std::runtime_error("Invalid \"replay_gain_handler\" value");
|
||||||
}
|
}
|
||||||
|
@@ -28,6 +28,7 @@
|
|||||||
#include "config/Data.hxx"
|
#include "config/Data.hxx"
|
||||||
#include "config/Option.hxx"
|
#include "config/Option.hxx"
|
||||||
#include "util/RuntimeError.hxx"
|
#include "util/RuntimeError.hxx"
|
||||||
|
#include "util/StringAPI.hxx"
|
||||||
|
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
|
|
||||||
@@ -147,7 +148,7 @@ AudioOutputControl *
|
|||||||
MultipleOutputs::FindByName(const char *name) noexcept
|
MultipleOutputs::FindByName(const char *name) noexcept
|
||||||
{
|
{
|
||||||
for (auto *i : outputs)
|
for (auto *i : outputs)
|
||||||
if (strcmp(i->GetName(), name) == 0)
|
if (StringIsEqual(i->GetName(), name))
|
||||||
return i;
|
return i;
|
||||||
|
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
@@ -38,8 +38,7 @@
|
|||||||
#include "plugins/sles/SlesOutputPlugin.hxx"
|
#include "plugins/sles/SlesOutputPlugin.hxx"
|
||||||
#include "plugins/SolarisOutputPlugin.hxx"
|
#include "plugins/SolarisOutputPlugin.hxx"
|
||||||
#include "plugins/WinmmOutputPlugin.hxx"
|
#include "plugins/WinmmOutputPlugin.hxx"
|
||||||
|
#include "util/StringAPI.hxx"
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
const AudioOutputPlugin *const audio_output_plugins[] = {
|
const AudioOutputPlugin *const audio_output_plugins[] = {
|
||||||
#ifdef HAVE_SHOUT
|
#ifdef HAVE_SHOUT
|
||||||
@@ -101,7 +100,7 @@ const AudioOutputPlugin *
|
|||||||
AudioOutputPlugin_get(const char *name)
|
AudioOutputPlugin_get(const char *name)
|
||||||
{
|
{
|
||||||
audio_output_plugins_for_each(plugin)
|
audio_output_plugins_for_each(plugin)
|
||||||
if (strcmp(plugin->name, name) == 0)
|
if (StringIsEqual(plugin->name, name))
|
||||||
return plugin;
|
return plugin;
|
||||||
|
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
@@ -187,7 +187,7 @@ AudioOutputSource::FilterChunk(const MusicChunk &chunk)
|
|||||||
only if the mix ratio is non-negative; a
|
only if the mix ratio is non-negative; a
|
||||||
negative mix ratio is a MixRamp special
|
negative mix ratio is a MixRamp special
|
||||||
case */
|
case */
|
||||||
mix_ratio = 1.0 - mix_ratio;
|
mix_ratio = 1.0f - mix_ratio;
|
||||||
|
|
||||||
void *dest = cross_fade_buffer.Get(other_data.size);
|
void *dest = cross_fade_buffer.Get(other_data.size);
|
||||||
memcpy(dest, other_data.data, other_data.size);
|
memcpy(dest, other_data.data, other_data.size);
|
||||||
|
@@ -70,7 +70,7 @@ audio_output_state_read(const char *line, MultipleOutputs &outputs)
|
|||||||
|
|
||||||
name = endptr + 1;
|
name = endptr + 1;
|
||||||
auto *ao = outputs.FindByName(name);
|
auto *ao = outputs.FindByName(name);
|
||||||
if (ao == NULL) {
|
if (ao == nullptr) {
|
||||||
FormatDebug(output_domain,
|
FormatDebug(output_domain,
|
||||||
"Ignoring device state for '%s'", name);
|
"Ignoring device state for '%s'", name);
|
||||||
return true;
|
return true;
|
||||||
|
@@ -939,7 +939,7 @@ AlsaOutput::Play(const void *chunk, size_t size)
|
|||||||
assert(size % in_frame_size == 0);
|
assert(size % in_frame_size == 0);
|
||||||
|
|
||||||
const auto e = pcm_export->Export({chunk, size});
|
const auto e = pcm_export->Export({chunk, size});
|
||||||
if (e.size == 0)
|
if (e.empty())
|
||||||
/* the DoP (DSD over PCM) filter converts two frames
|
/* the DoP (DSD over PCM) filter converts two frames
|
||||||
at a time and ignores the last odd frame; if there
|
at a time and ignores the last odd frame; if there
|
||||||
was only one frame (e.g. the last frame in the
|
was only one frame (e.g. the last frame in the
|
||||||
|
@@ -25,12 +25,11 @@
|
|||||||
#include "util/SplitString.hxx"
|
#include "util/SplitString.hxx"
|
||||||
#include "util/RuntimeError.hxx"
|
#include "util/RuntimeError.hxx"
|
||||||
#include "util/Domain.hxx"
|
#include "util/Domain.hxx"
|
||||||
|
#include "util/StringAPI.hxx"
|
||||||
#include "Log.hxx"
|
#include "Log.hxx"
|
||||||
|
|
||||||
#include <ao/ao.h>
|
#include <ao/ao.h>
|
||||||
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
/* An ao_sample_format, with all fields set to zero: */
|
/* An ao_sample_format, with all fields set to zero: */
|
||||||
static ao_sample_format OUR_AO_FORMAT_INITIALIZER;
|
static ao_sample_format OUR_AO_FORMAT_INITIALIZER;
|
||||||
|
|
||||||
@@ -105,7 +104,7 @@ AoOutput::AoOutput(const ConfigBlock &block)
|
|||||||
write_size(block.GetPositiveValue("write_size", 1024U))
|
write_size(block.GetPositiveValue("write_size", 1024U))
|
||||||
{
|
{
|
||||||
const char *value = block.GetBlockValue("driver", "default");
|
const char *value = block.GetBlockValue("driver", "default");
|
||||||
if (0 == strcmp(value, "default"))
|
if (StringIsEqual(value, "default"))
|
||||||
driver = ao_default_driver_id();
|
driver = ao_default_driver_id();
|
||||||
else
|
else
|
||||||
driver = ao_driver_id(value);
|
driver = ao_driver_id(value);
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user