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