Compare commits
212 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
f24bcc7f42 | ||
![]() |
89800324cb | ||
![]() |
050e30418c | ||
![]() |
5397d18ed9 | ||
![]() |
42eb69f46f | ||
![]() |
f1ad21d2bf | ||
![]() |
535a099a27 | ||
![]() |
50003f6ad2 | ||
![]() |
0914644d2b | ||
![]() |
5ad6e7fec5 | ||
![]() |
0bb943ba3e | ||
![]() |
80a0cf694f | ||
![]() |
0c9e25b3c4 | ||
![]() |
943a67c805 | ||
![]() |
881d91f86b | ||
![]() |
54d57fdcc2 | ||
![]() |
f6f30d6d64 | ||
![]() |
4013fa15b9 | ||
![]() |
ac1b844c15 | ||
![]() |
b8614048d4 | ||
![]() |
aed0d13591 | ||
![]() |
9d02103ebe | ||
![]() |
61784c2144 | ||
![]() |
7059215795 | ||
![]() |
2190cc7927 | ||
![]() |
75dc9506c2 | ||
![]() |
4f11fa0d41 | ||
![]() |
ce7ec2b3f5 | ||
![]() |
fada4aa529 | ||
![]() |
aa0e121ade | ||
![]() |
b4700039fd | ||
![]() |
ab41c16eb5 | ||
![]() |
04101f37b8 | ||
![]() |
8c31370534 | ||
![]() |
2306b0d78c | ||
![]() |
cb1a9045e6 | ||
![]() |
e92af06664 | ||
![]() |
af20a1c994 | ||
![]() |
756560eac3 | ||
![]() |
dca0519336 | ||
![]() |
b9a7f30443 | ||
![]() |
32a17a997a | ||
![]() |
bf41d1ad2b | ||
![]() |
d27e534a85 | ||
![]() |
6d54928d7c | ||
![]() |
0dffe05bf7 | ||
![]() |
9ef1f10319 | ||
![]() |
23fcfdbd2a | ||
![]() |
3401d26d4c | ||
![]() |
256753ea46 | ||
![]() |
76cd5f8595 | ||
![]() |
5684025847 | ||
![]() |
744bd1eadc | ||
![]() |
2bc127bb43 | ||
![]() |
7770298a65 | ||
![]() |
fa50cdb39e | ||
![]() |
816ef12088 | ||
![]() |
5ff786e59c | ||
![]() |
80fe88e8f6 | ||
![]() |
a1afe9afc6 | ||
![]() |
fe598e7d30 | ||
![]() |
4475b8ca04 | ||
![]() |
a714bdb0ce | ||
![]() |
087874620f | ||
![]() |
f1116c9258 | ||
![]() |
d01fb6730a | ||
![]() |
7bfe6a3304 | ||
![]() |
9a577f8060 | ||
![]() |
d75a0d714e | ||
![]() |
9be3a1554e | ||
![]() |
7764719513 | ||
![]() |
dcbb9fe07c | ||
![]() |
e3b347820a | ||
![]() |
a84bf5a92e | ||
![]() |
732bdc800d | ||
![]() |
a8661b5931 | ||
![]() |
5680a3a4b7 | ||
![]() |
15ce8eb487 | ||
![]() |
b7744be208 | ||
![]() |
63c5d66016 | ||
![]() |
d09bd9178f | ||
![]() |
7d8b1860c3 | ||
![]() |
b06825829b | ||
![]() |
ba4cd47fd8 | ||
![]() |
bbe403f141 | ||
![]() |
5df2707d98 | ||
![]() |
4859ea468f | ||
![]() |
2a8830db70 | ||
![]() |
fed9b6fd74 | ||
![]() |
b02890eb8a | ||
![]() |
da882a6eb6 | ||
![]() |
aeb89aa9d6 | ||
![]() |
f885807ecc | ||
![]() |
b826fd71f0 | ||
![]() |
ae35df1126 | ||
![]() |
80e55f6bfc | ||
![]() |
e7411c0c4b | ||
![]() |
e9af692973 | ||
![]() |
0cf90ee8b6 | ||
![]() |
dc3c0c8866 | ||
![]() |
1c46bb1ba6 | ||
![]() |
2e8f42c6ad | ||
![]() |
2b301ffd2c | ||
![]() |
ef0765ca10 | ||
![]() |
9766ac6db3 | ||
![]() |
32799ff682 | ||
![]() |
ce093be12c | ||
![]() |
2c276770f0 | ||
![]() |
75a592f629 | ||
![]() |
13ce07d181 | ||
![]() |
d659c7df19 | ||
![]() |
f8403a1d29 | ||
![]() |
ebb952c4ad | ||
![]() |
bea3b954a5 | ||
![]() |
129d8e89b9 | ||
![]() |
65778a3774 | ||
![]() |
d9841668ff | ||
![]() |
85d27cbcb9 | ||
![]() |
9b95e65bd9 | ||
![]() |
12a86c4975 | ||
![]() |
0b9435858b | ||
![]() |
f0386459ee | ||
![]() |
e98d4670b8 | ||
![]() |
56cc42b752 | ||
![]() |
ead208987d | ||
![]() |
364acc8949 | ||
![]() |
a8f4d2b6fc | ||
![]() |
0eb113e7c6 | ||
![]() |
96a9670c69 | ||
![]() |
dcc5ce6792 | ||
![]() |
23d08820a2 | ||
![]() |
b9b906ab20 | ||
![]() |
964804a4c2 | ||
![]() |
92495d2b0b | ||
![]() |
9270829b5b | ||
![]() |
b6243a9945 | ||
![]() |
496f88653d | ||
![]() |
5ef645df97 | ||
![]() |
bf49c9e4e2 | ||
![]() |
0da9c91af2 | ||
![]() |
193e637dd9 | ||
![]() |
928bee933d | ||
![]() |
4d1720c886 | ||
![]() |
8f8ed87327 | ||
![]() |
28a441c977 | ||
![]() |
8cf50b08f2 | ||
![]() |
818b7e0641 | ||
![]() |
e70f40fac1 | ||
![]() |
bc89ca92b4 | ||
![]() |
b968e1b6de | ||
![]() |
6c9f9c136b | ||
![]() |
9bff5f9e36 | ||
![]() |
2bf26a2ff8 | ||
![]() |
e33b50d9c5 | ||
![]() |
21fa44c0d5 | ||
![]() |
44444e1b89 | ||
![]() |
ca450663d0 | ||
![]() |
f3d16f6d1b | ||
![]() |
4464cdcc67 | ||
![]() |
2d61e526de | ||
![]() |
7723c481db | ||
![]() |
0ed10542cc | ||
![]() |
ab830f9afd | ||
![]() |
d4d2bc072e | ||
![]() |
bcccc8f66c | ||
![]() |
848c63e2d5 | ||
![]() |
f6d0310f9c | ||
![]() |
3ef043392c | ||
![]() |
864d6f312d | ||
![]() |
f44c67de09 | ||
![]() |
ae19bda1f2 | ||
![]() |
f2d8fd769d | ||
![]() |
9661062ae2 | ||
![]() |
2a07354cad | ||
![]() |
fc18fd571c | ||
![]() |
51abed9732 | ||
![]() |
d00afc912c | ||
![]() |
9d0fe725eb | ||
![]() |
8a432c9b7f | ||
![]() |
187204f03c | ||
![]() |
5e5fadb5f2 | ||
![]() |
952c793235 | ||
![]() |
3e3d8c7f9d | ||
![]() |
9b99a9897a | ||
![]() |
4f56fdc397 | ||
![]() |
c87d6825ec | ||
![]() |
00830a20e3 | ||
![]() |
d39d2874b4 | ||
![]() |
a0a74951b8 | ||
![]() |
779a6855ff | ||
![]() |
f7ed7446ae | ||
![]() |
9d44a6d2ae | ||
![]() |
10da9ee7ba | ||
![]() |
f9eff31205 | ||
![]() |
1d74a029a2 | ||
![]() |
6b8ca514bb | ||
![]() |
f51e555154 | ||
![]() |
61a3c69a06 | ||
![]() |
089615a01e | ||
![]() |
52bee8f81f | ||
![]() |
adc25e648f | ||
![]() |
31da8eac9b | ||
![]() |
e00464435b | ||
![]() |
b81138bda1 | ||
![]() |
6de088140b | ||
![]() |
86d0534638 | ||
![]() |
1033dbca2b | ||
![]() |
b955334882 | ||
![]() |
90ea3bf985 | ||
![]() |
83b0871248 | ||
![]() |
d8aec4b2dc | ||
![]() |
39b302dcad |
.travis.ymlNEWS
android
doc
meson.buildpython/build
src
LocateUri.cxxPlaylistDatabase.cxxReplayGainInfo.hxxSongPrint.cxxSongSave.cxxSongUpdate.cxxStats.cxxTimePrint.cxx
archive
plugins
client
command
config
db
decoder
event
input
lib
cdio
curl
Easy.hxxEscape.cxxEscape.hxxForm.cxxGlobal.cxxGlobal.hxxHandler.hxxInit.cxxMulti.hxxRequest.cxxRequest.hxxSlist.hxxString.hxxVersion.cxxVersion.hxxmeson.build
gcrypt
icu
nfs
sqlite
xiph
neighbor
net
output
Thread.cxx
plugins
pcm
player
playlist
protocol
queue
song
storage
plugins
system
tag
time
util
Compiler.hPrintException.cxxPrintException.hxxStaticFifoBuffer.hxxStringBuffer.hxxStringFormat.hxxformat.cmeson.build
zeroconf
test
107
.travis.yml
107
.travis.yml
@@ -2,6 +2,72 @@ language: cpp
|
||||
|
||||
matrix:
|
||||
include:
|
||||
# Ubuntu Bionic (18.04) with GCC 7
|
||||
- os: linux
|
||||
dist: bionic
|
||||
addons:
|
||||
apt:
|
||||
sources:
|
||||
- sourceline: 'ppa:deadsnakes/ppa' # for Python 3.7 (required by Meson)
|
||||
packages:
|
||||
- libgtest-dev
|
||||
- libboost-dev
|
||||
- python3.6
|
||||
- python3-urllib3
|
||||
- ninja-build
|
||||
before_install:
|
||||
- wget https://bootstrap.pypa.io/get-pip.py
|
||||
- /usr/bin/python3.6 get-pip.py --user --no-cache-dir
|
||||
install:
|
||||
- /usr/bin/python3.6 $HOME/.local/bin/pip install --user meson --no-cache-dir
|
||||
env:
|
||||
- MATRIX_EVAL="export PATH=\$HOME/.local/bin:\$PATH"
|
||||
|
||||
# Ubuntu Bionic (18.04) with GCC 7 on big-endian
|
||||
- os: linux
|
||||
arch: s390x
|
||||
dist: bionic
|
||||
addons:
|
||||
apt:
|
||||
sources:
|
||||
- sourceline: 'ppa:deadsnakes/ppa' # for Python 3.7 (required by Meson)
|
||||
packages:
|
||||
- libgtest-dev
|
||||
- libboost-dev
|
||||
- python3.6
|
||||
- python3-urllib3
|
||||
- ninja-build
|
||||
before_install:
|
||||
- wget https://bootstrap.pypa.io/get-pip.py
|
||||
- /usr/bin/python3.6 get-pip.py --user --no-cache-dir
|
||||
install:
|
||||
- /usr/bin/python3.6 $HOME/.local/bin/pip install --user meson --no-cache-dir
|
||||
env:
|
||||
- MATRIX_EVAL="export PATH=\$HOME/.local/bin:\$PATH"
|
||||
|
||||
# Ubuntu Bionic (18.04) with GCC 7 on ARM64
|
||||
- os: linux
|
||||
arch: arm64
|
||||
dist: bionic
|
||||
addons:
|
||||
apt:
|
||||
sources:
|
||||
- sourceline: 'ppa:deadsnakes/ppa' # for Python 3.7 (required by Meson)
|
||||
packages:
|
||||
- libgtest-dev
|
||||
- libboost-dev
|
||||
- python3.6
|
||||
- python3-urllib3
|
||||
- ninja-build
|
||||
before_install:
|
||||
- wget https://bootstrap.pypa.io/get-pip.py
|
||||
- /usr/bin/python3.6 get-pip.py --user --no-cache-dir
|
||||
install:
|
||||
- /usr/bin/python3.6 $HOME/.local/bin/pip install --user meson --no-cache-dir
|
||||
env:
|
||||
- MATRIX_EVAL="export PATH=\$HOME/.local/bin:\$PATH"
|
||||
|
||||
# Ubuntu Trusty (16.04) with GCC 6
|
||||
- os: linux
|
||||
dist: trusty
|
||||
addons:
|
||||
@@ -20,13 +86,14 @@ matrix:
|
||||
- ninja-build
|
||||
before_install:
|
||||
- wget https://bootstrap.pypa.io/get-pip.py
|
||||
- /usr/bin/python3.6 get-pip.py --user
|
||||
- /usr/bin/python3.6 get-pip.py --user --no-cache-dir
|
||||
install:
|
||||
- /usr/bin/python3.6 $HOME/.local/bin/pip install --user meson
|
||||
- /usr/bin/python3.6 $HOME/.local/bin/pip install --user meson --no-cache-dir
|
||||
env:
|
||||
# use gold as workaround for https://sourceware.org/bugzilla/show_bug.cgi?id=17068
|
||||
- MATRIX_EVAL="export CC=gcc-6 CXX=g++-6 LDFLAGS=-fuse-ld=gold PATH=$HOME/.local/bin:$PATH"
|
||||
- MATRIX_EVAL="export CC='ccache gcc-6' CXX='ccache g++-6' LDFLAGS=-fuse-ld=gold PATH=\$HOME/.local/bin:\$PATH"
|
||||
|
||||
# Ubuntu Trusty (16.04) with GCC 8
|
||||
- os: linux
|
||||
dist: trusty
|
||||
addons:
|
||||
@@ -45,31 +112,43 @@ matrix:
|
||||
- ninja-build
|
||||
before_install:
|
||||
- wget https://bootstrap.pypa.io/get-pip.py
|
||||
- /usr/bin/python3.6 get-pip.py --user
|
||||
- /usr/bin/python3.6 get-pip.py --user --no-cache-dir
|
||||
install:
|
||||
- /usr/bin/python3.6 $HOME/.local/bin/pip install --user meson
|
||||
- /usr/bin/python3.6 $HOME/.local/bin/pip install --user meson --no-cache-dir
|
||||
env:
|
||||
# use gold as workaround for https://sourceware.org/bugzilla/show_bug.cgi?id=17068
|
||||
- MATRIX_EVAL="export CC=gcc-8 CXX=g++-8 LDFLAGS=-fuse-ld=gold PATH=$HOME/.local/bin:$PATH"
|
||||
- MATRIX_EVAL="export CC='ccache gcc-8' CXX='ccache g++-8' LDFLAGS=-fuse-ld=gold PATH=\$HOME/.local/bin:\$PATH"
|
||||
|
||||
- os: osx
|
||||
osx_image: xcode9.3beta
|
||||
osx_image: xcode9.4
|
||||
addons:
|
||||
homebrew:
|
||||
packages:
|
||||
- ccache
|
||||
- meson
|
||||
env:
|
||||
- MATRIX_EVAL=""
|
||||
- MATRIX_EVAL="export PATH=/usr/local/opt/ccache/libexec:$PATH HOMEBREW_NO_ANALYTICS=1"
|
||||
|
||||
cache:
|
||||
- apt
|
||||
- ccache
|
||||
apt: true
|
||||
ccache: true
|
||||
directories:
|
||||
- $HOME/Library/Caches/Homebrew
|
||||
|
||||
before_cache:
|
||||
- test "$TRAVIS_OS_NAME" != "osx" || brew cleanup
|
||||
|
||||
before_install:
|
||||
- eval "${MATRIX_EVAL}"
|
||||
# C++14
|
||||
- test "$TRAVIS_OS_NAME" != "osx" || brew update
|
||||
|
||||
install:
|
||||
# C++14
|
||||
- test "$TRAVIS_OS_NAME" != "osx" || brew install ccache meson
|
||||
- test "$TRAVIS_OS_NAME" != "osx" || brew install --HEAD https://gist.githubusercontent.com/Kronuz/96ac10fbd8472eb1e7566d740c4034f8/raw/gtest.rb
|
||||
|
||||
# Work around "Target /usr/local/lib/libgtest.a is a symlink
|
||||
# belonging to nss. You can unlink it" during gtest install
|
||||
- test "$TRAVIS_OS_NAME" != "osx" || brew unlink nss
|
||||
|
||||
- test "$TRAVIS_OS_NAME" != "osx" || brew install https://gist.githubusercontent.com/Kronuz/96ac10fbd8472eb1e7566d740c4034f8/raw/gtest.rb
|
||||
|
||||
before_script:
|
||||
- ccache -s
|
||||
|
86
NEWS
86
NEWS
@@ -1,3 +1,89 @@
|
||||
ver 0.21.20 (2020/02/16)
|
||||
* decoder
|
||||
- audiofile, ffmpeg, sndfile: handle MIME type "audio/wav"
|
||||
- ffmpeg: fix playback of AIFF and TTA
|
||||
- vorbis, opus: fix seeking in small files
|
||||
* fix backwards seeking on ARM (and other non-x86 CPUs)
|
||||
|
||||
ver 0.21.19 (2020/01/17)
|
||||
* configuration
|
||||
- allow overriding top-level settings in includes
|
||||
* output
|
||||
- pulse: obey Pulse's maximum sample rate (fixes DSD128 playback)
|
||||
* fix build failure with clang 10
|
||||
* fix build failure with Android NDK r20
|
||||
|
||||
ver 0.21.18 (2019/12/24)
|
||||
* protocol
|
||||
- work around Mac OS X bug in the ISO 8601 parser
|
||||
* output
|
||||
- alsa: fix hang bug with ALSA "null" outputs
|
||||
* storage
|
||||
- curl: fix crash bug
|
||||
* drop support for CURL versions older than 7.32.0
|
||||
* reduce unnecessary CPU wakeups
|
||||
|
||||
ver 0.21.17 (2019/12/16)
|
||||
* protocol
|
||||
- relax the ISO 8601 parser: allow omitting field separators, the
|
||||
time of day and the "Z" suffix
|
||||
* archive
|
||||
- zzip: improve error reporting
|
||||
* outputs
|
||||
- jack: mark ports as terminal
|
||||
- shout: declare metadata as UTF-8
|
||||
* fix build failure with -Ddatabase=false
|
||||
|
||||
ver 0.21.16 (2019/10/16)
|
||||
* queue
|
||||
- fix relative destination offset when moving a range
|
||||
* storage
|
||||
- curl: request the "resourcetype" property to fix database update
|
||||
- curl: URL-encode more paths
|
||||
- curl: follow redirects for collections without trailing slash
|
||||
* update
|
||||
- fix crash when music_directory is not a directory
|
||||
* fix build with iconv() instead of ICU
|
||||
|
||||
ver 0.21.15 (2019/09/25)
|
||||
* decoder
|
||||
- dsdiff, dsf: fix displayed bit rate
|
||||
- mpcdec: fix bogus ReplayGain values
|
||||
* output
|
||||
- solaris: fix build with glibc 2.30
|
||||
|
||||
ver 0.21.14 (2019/08/21)
|
||||
* decoder
|
||||
- sidplay: show track durations in database
|
||||
- sidplay: convert tag values from Windows-1252 charset
|
||||
- sidplay: strip text from "Date" tag
|
||||
* player
|
||||
- fix crash after song change
|
||||
- fix seek position after restarting the decoder
|
||||
* protocol
|
||||
- include command name in error responses
|
||||
|
||||
ver 0.21.13 (2019/08/06)
|
||||
* input
|
||||
- cdio_paranoia: require libcdio-paranoia 10.2+0.93+1
|
||||
* decoder
|
||||
- mad: fix crackling sound (0.21.12 regression)
|
||||
* output
|
||||
- jack: improved Windows compatibility
|
||||
|
||||
ver 0.21.12 (2019/08/03)
|
||||
* decoder
|
||||
- mad: update bit rate after seeking
|
||||
- mad: fix several bugs preventing the plugin from decoding the last frame
|
||||
- opus: ignore case in replay gain tag names
|
||||
- opus, vorbis: decode the "end of stream" packet
|
||||
* output
|
||||
- jack: fix mono-to-stereo conversion
|
||||
* player
|
||||
- don't restart unseekable song after failed seek attempt
|
||||
* Windows
|
||||
- support backslash in relative URIs loaded from playlists
|
||||
|
||||
ver 0.21.11 (2019/07/03)
|
||||
* input
|
||||
- tidal: deprecated because Tidal has changed the protocol
|
||||
|
@@ -2,10 +2,10 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="org.musicpd"
|
||||
android:installLocation="auto"
|
||||
android:versionCode="34"
|
||||
android:versionName="0.21.11">
|
||||
android:versionCode="43"
|
||||
android:versionName="0.21.20">
|
||||
|
||||
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="26"/>
|
||||
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="28"/>
|
||||
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK"/>
|
||||
|
@@ -25,16 +25,15 @@ android_abis = {
|
||||
'arch': 'arm-linux-androideabi',
|
||||
'ndk_arch': 'arm',
|
||||
'toolchain_arch': 'arm-linux-androideabi',
|
||||
'llvm_triple': 'armv7-none-linux-androideabi',
|
||||
'llvm_triple': 'armv7-linux-androideabi',
|
||||
'cflags': '-march=armv7-a -mfpu=vfp -mfloat-abi=softfp',
|
||||
},
|
||||
|
||||
'arm64-v8a': {
|
||||
'android_api_level': '21',
|
||||
'arch': 'aarch64-linux-android',
|
||||
'ndk_arch': 'arm64',
|
||||
'toolchain_arch': 'aarch64-linux-android',
|
||||
'llvm_triple': 'aarch64-none-linux-android',
|
||||
'llvm_triple': 'aarch64-linux-android',
|
||||
'cflags': '',
|
||||
},
|
||||
|
||||
@@ -42,9 +41,17 @@ android_abis = {
|
||||
'arch': 'i686-linux-android',
|
||||
'ndk_arch': 'x86',
|
||||
'toolchain_arch': 'x86',
|
||||
'llvm_triple': 'i686-none-linux-android',
|
||||
'llvm_triple': 'i686-linux-android',
|
||||
'cflags': '-march=i686 -mtune=intel -mssse3 -mfpmath=sse -m32',
|
||||
},
|
||||
|
||||
'x86_64': {
|
||||
'arch': 'x86_64-linux-android',
|
||||
'ndk_arch': 'x86_64',
|
||||
'toolchain_arch': 'x86_64',
|
||||
'llvm_triple': 'x86_64-linux-android',
|
||||
'cflags': '-m64',
|
||||
},
|
||||
}
|
||||
|
||||
# select the NDK target
|
||||
@@ -76,24 +83,18 @@ class AndroidNdkToolchain:
|
||||
|
||||
ndk_arch = abi_info['ndk_arch']
|
||||
android_api_level = '21'
|
||||
ndk_platform = 'android-' + android_api_level
|
||||
|
||||
# select the NDK compiler
|
||||
gcc_version = '4.9'
|
||||
|
||||
ndk_platform_path = os.path.join(ndk_path, 'platforms', ndk_platform)
|
||||
sysroot = os.path.join(ndk_path, 'sysroot')
|
||||
target_root = os.path.join(ndk_platform_path, 'arch-' + ndk_arch)
|
||||
|
||||
install_prefix = os.path.join(arch_path, 'root')
|
||||
|
||||
self.arch = arch
|
||||
self.install_prefix = install_prefix
|
||||
self.sysroot = sysroot
|
||||
|
||||
toolchain_path = os.path.join(ndk_path, 'toolchains', abi_info['toolchain_arch'] + '-' + gcc_version, 'prebuilt', build_arch)
|
||||
llvm_path = os.path.join(ndk_path, 'toolchains', 'llvm', 'prebuilt', build_arch)
|
||||
llvm_triple = abi_info['llvm_triple']
|
||||
llvm_triple = abi_info['llvm_triple'] + android_api_level
|
||||
|
||||
common_flags = '-Os -g'
|
||||
common_flags += ' -fPIC'
|
||||
@@ -107,6 +108,9 @@ class AndroidNdkToolchain:
|
||||
|
||||
common_flags += ' -fvisibility=hidden -fdata-sections -ffunction-sections'
|
||||
|
||||
# required flags from https://android.googlesource.com/platform/ndk/+/ndk-release-r20/docs/BuildSystemMaintainers.md#additional-required-arguments
|
||||
common_flags += ' -fno-addrsig'
|
||||
|
||||
self.ar = os.path.join(toolchain_bin, arch + '-ar')
|
||||
self.ranlib = os.path.join(toolchain_bin, arch + '-ranlib')
|
||||
self.nm = os.path.join(toolchain_bin, arch + '-nm')
|
||||
@@ -114,15 +118,11 @@ class AndroidNdkToolchain:
|
||||
|
||||
self.cflags = common_flags
|
||||
self.cxxflags = common_flags
|
||||
self.cppflags = '--sysroot=' + sysroot + \
|
||||
' -isystem ' + os.path.join(install_prefix, 'include') + \
|
||||
' -isystem ' + os.path.join(sysroot, 'usr', 'include', arch) + \
|
||||
' -D__ANDROID_API__=' + android_api_level
|
||||
self.ldflags = '--sysroot=' + sysroot + \
|
||||
' -L' + os.path.join(install_prefix, 'lib') + \
|
||||
' -L' + os.path.join(target_root, 'usr', 'lib') + \
|
||||
' -B' + os.path.join(target_root, 'usr', 'lib') + \
|
||||
self.cppflags = ' -isystem ' + os.path.join(install_prefix, 'include')
|
||||
self.ldflags = ' -L' + os.path.join(install_prefix, 'lib') + \
|
||||
' -Wl,--exclude-libs=ALL' + \
|
||||
' ' + common_flags
|
||||
self.ldflags = common_flags
|
||||
self.libs = ''
|
||||
|
||||
self.is_arm = ndk_arch == 'arm'
|
||||
@@ -130,13 +130,10 @@ class AndroidNdkToolchain:
|
||||
self.is_aarch64 = ndk_arch == 'arm64'
|
||||
self.is_windows = False
|
||||
|
||||
libcxx_path = os.path.join(ndk_path, 'sources/cxx-stl/llvm-libc++')
|
||||
libcxx_libs_path = os.path.join(libcxx_path, 'libs', android_abi)
|
||||
|
||||
libstdcxx_flags = ''
|
||||
libstdcxx_cxxflags = libstdcxx_flags + ' -isystem ' + os.path.join(libcxx_path, 'include') + ' -isystem ' + os.path.join(ndk_path, 'sources/android/support/include')
|
||||
libstdcxx_ldflags = libstdcxx_flags + ' -L' + libcxx_libs_path
|
||||
libstdcxx_libs = '-lc++_static -lc++abi'
|
||||
libstdcxx_cxxflags = ''
|
||||
libstdcxx_ldflags = ''
|
||||
libstdcxx_libs = '-static-libstdc++'
|
||||
|
||||
if self.is_armv7:
|
||||
# On 32 bit ARM, clang generates no ".eh_frame" section;
|
||||
|
@@ -38,7 +38,7 @@ author = 'Max Kellermann'
|
||||
# built documents.
|
||||
#
|
||||
# The short X.Y version.
|
||||
version = '0.21.11'
|
||||
version = '0.21.20'
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = version
|
||||
|
||||
|
@@ -42,7 +42,7 @@ Provides access to the database of another :program:`MPD` instance using libmpdc
|
||||
* - **password**
|
||||
- The password used to log in to the "master" :program:`MPD` instance.
|
||||
* - **keepalive yes|no**
|
||||
- Send TCP keepalive packets to the "master" :program:`MPD` instance? This option can help avoid certain firewalls dropping inactive connections, at the expensive of a very small amount of additional network traffic. Disabled by default.
|
||||
- Send TCP keepalive packets to the "master" :program:`MPD` instance? This option can help avoid certain firewalls dropping inactive connections, at the expense of a very small amount of additional network traffic. Disabled by default.
|
||||
|
||||
upnp
|
||||
----
|
||||
@@ -1069,7 +1069,7 @@ Filter plugins
|
||||
normalize
|
||||
---------
|
||||
|
||||
Normalize the volume during playback (at the expensve of quality).
|
||||
Normalize the volume during playback (at the expense of quality).
|
||||
|
||||
|
||||
null
|
||||
|
@@ -824,7 +824,8 @@ The music database
|
||||
albumart
|
||||
size: 1024768
|
||||
binary: 8192
|
||||
<8192 bytes>OK
|
||||
<8192 bytes>
|
||||
OK
|
||||
|
||||
:command:`count {FILTER} [group {GROUPTYPE}]`
|
||||
Count the number of songs and their total playtime in
|
||||
|
12
doc/user.rst
12
doc/user.rst
@@ -62,16 +62,16 @@ In any case, you need:
|
||||
Each plugin usually needs a codec library, which you also need to
|
||||
install. Check the :doc:`plugins` for details about required libraries
|
||||
|
||||
For example, the following installs a fairly complete list of build dependencies on Debian Jessie:
|
||||
For example, the following installs a fairly complete list of build dependencies on Debian Buster:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
apt install g++ \
|
||||
apt install meson g++ \
|
||||
libpcre3-dev \
|
||||
libmad0-dev libmpg123-dev libid3tag0-dev \
|
||||
libflac-dev libvorbis-dev libopus-dev \
|
||||
libflac-dev libvorbis-dev libopus-dev libogg-dev \
|
||||
libadplug-dev libaudiofile-dev libsndfile1-dev libfaad-dev \
|
||||
libfluidsynth-dev libgme-dev libmikmod2-dev libmodplug-dev \
|
||||
libfluidsynth-dev libgme-dev libmikmod-dev libmodplug-dev \
|
||||
libmpcdec-dev libwavpack-dev libwildmidi-dev \
|
||||
libsidplay2-dev libsidutils-dev libresid-builder-dev \
|
||||
libavcodec-dev libavformat-dev \
|
||||
@@ -91,7 +91,9 @@ For example, the following installs a fairly complete list of build dependencies
|
||||
libsystemd-dev \
|
||||
libgtest-dev \
|
||||
libboost-dev \
|
||||
libicu-dev
|
||||
libicu-dev \
|
||||
libchromaprint-dev \
|
||||
libgcrypt20-dev
|
||||
|
||||
|
||||
Now configure the source tree:
|
||||
|
18
meson.build
18
meson.build
@@ -1,7 +1,7 @@
|
||||
project(
|
||||
'mpd',
|
||||
['c', 'cpp'],
|
||||
version: '0.21.11',
|
||||
version: '0.21.20',
|
||||
meson_version: '>= 0.49.0',
|
||||
default_options: [
|
||||
'c_std=c99',
|
||||
@@ -15,6 +15,12 @@ version_cxx = vcs_tag(input: 'src/GitVersion.cxx', output: 'GitVersion.cxx')
|
||||
compiler = meson.get_compiler('cpp')
|
||||
c_compiler = meson.get_compiler('c')
|
||||
|
||||
if compiler.get_id() == 'gcc' and compiler.version().version_compare('<6')
|
||||
warning('Your GCC version is too old. You need at least version 6.')
|
||||
elif compiler.get_id() == 'clang' and compiler.version().version_compare('<3')
|
||||
warning('Your clang version is too old. You need at least version 3.')
|
||||
endif
|
||||
|
||||
conf = configuration_data()
|
||||
conf.set_quoted('PACKAGE', meson.project_name())
|
||||
conf.set_quoted('PACKAGE_NAME', meson.project_name())
|
||||
@@ -82,6 +88,10 @@ test_ldflags = [
|
||||
]
|
||||
|
||||
if get_option('buildtype') != 'debug'
|
||||
test_cxxflags += [
|
||||
'-ffunction-sections',
|
||||
'-fdata-sections',
|
||||
]
|
||||
test_cflags += [
|
||||
'-ffunction-sections',
|
||||
'-fdata-sections',
|
||||
@@ -298,6 +308,7 @@ if enable_database
|
||||
endif
|
||||
|
||||
subdir('src/util')
|
||||
subdir('src/time')
|
||||
subdir('src/system')
|
||||
subdir('src/thread')
|
||||
subdir('src/event')
|
||||
@@ -379,8 +390,11 @@ endif
|
||||
if archive_glue_dep.found()
|
||||
sources += [
|
||||
'src/TagArchive.cxx',
|
||||
'src/db/update/Archive.cxx',
|
||||
]
|
||||
|
||||
if enable_database
|
||||
sources += ['src/db/update/Archive.cxx']
|
||||
endif
|
||||
endif
|
||||
|
||||
if is_windows
|
||||
|
@@ -9,14 +9,14 @@ from build.ffmpeg import FfmpegProject
|
||||
from build.boost import BoostProject
|
||||
|
||||
libmpdclient = MesonProject(
|
||||
'https://www.musicpd.org/download/libmpdclient/2/libmpdclient-2.16.tar.xz',
|
||||
'fa6bdab67c0e0490302b38f00c27b4959735c3ec8aef7a88327adb1407654464',
|
||||
'https://www.musicpd.org/download/libmpdclient/2/libmpdclient-2.17.tar.xz',
|
||||
'ee9b8f1c7e95b65c8f18a354daf7b16bfcd455fc52a0f3b5abe402316bce3559',
|
||||
'lib/libmpdclient.a',
|
||||
)
|
||||
|
||||
libogg = AutotoolsProject(
|
||||
'http://downloads.xiph.org/releases/ogg/libogg-1.3.3.tar.xz',
|
||||
'4f3fc6178a533d392064f14776b23c397ed4b9f48f5de297aba73b643f955c08',
|
||||
'http://downloads.xiph.org/releases/ogg/libogg-1.3.4.tar.xz',
|
||||
'c163bc12bc300c401b6aa35907ac682671ea376f13ae0969a220f7ddf71893fe',
|
||||
'lib/libogg.a',
|
||||
[
|
||||
'--disable-shared', '--enable-static',
|
||||
@@ -38,8 +38,8 @@ libvorbis = AutotoolsProject(
|
||||
)
|
||||
|
||||
opus = AutotoolsProject(
|
||||
'https://archive.mozilla.org/pub/opus/opus-1.3.tar.gz',
|
||||
'4f3d69aefdf2dbaf9825408e452a8a414ffc60494c70633560700398820dc550',
|
||||
'https://archive.mozilla.org/pub/opus/opus-1.3.1.tar.gz',
|
||||
'65b58e1e25b2a114157014736a3d9dfeaad8d41be1c8179866f144a2fb44ff9d',
|
||||
'lib/libopus.a',
|
||||
[
|
||||
'--disable-shared', '--enable-static',
|
||||
@@ -52,8 +52,8 @@ opus = AutotoolsProject(
|
||||
)
|
||||
|
||||
flac = AutotoolsProject(
|
||||
'http://downloads.xiph.org/releases/flac/flac-1.3.2.tar.xz',
|
||||
'91cfc3ed61dc40f47f050a109b08610667d73477af6ef36dcad31c31a4a8d53f',
|
||||
'http://downloads.xiph.org/releases/flac/flac-1.3.3.tar.xz',
|
||||
'213e82bd716c9de6db2f98bcadbc4c24c7e2efe8c75939a1a84e28539c4e1748',
|
||||
'lib/libFLAC.a',
|
||||
[
|
||||
'--disable-shared', '--enable-static',
|
||||
@@ -112,8 +112,8 @@ liblame = AutotoolsProject(
|
||||
)
|
||||
|
||||
ffmpeg = FfmpegProject(
|
||||
'http://ffmpeg.org/releases/ffmpeg-4.1.3.tar.xz',
|
||||
'0c3020452880581a8face91595b239198078645e7d7184273b8bcc7758beb63d',
|
||||
'http://ffmpeg.org/releases/ffmpeg-4.2.2.tar.xz',
|
||||
'cb754255ab0ee2ea5f66f8850e1bd6ad5cac1cd855d0a2f4990fb8c668b0d29c',
|
||||
'lib/libavcodec.a',
|
||||
[
|
||||
'--disable-shared', '--enable-static',
|
||||
@@ -341,8 +341,8 @@ ffmpeg = FfmpegProject(
|
||||
)
|
||||
|
||||
curl = AutotoolsProject(
|
||||
'http://curl.haxx.se/download/curl-7.64.1.tar.xz',
|
||||
'9252332a7f871ce37bfa7f78bdd0a0e3924d8187cc27cb57c76c9474a7168fb3',
|
||||
'http://curl.haxx.se/download/curl-7.68.0.tar.xz',
|
||||
'b724240722276a27f6e770b952121a3afd097129d8c9fe18e6272dc34192035a',
|
||||
'lib/libcurl.a',
|
||||
[
|
||||
'--disable-shared', '--enable-static',
|
||||
@@ -358,6 +358,11 @@ curl = AutotoolsProject(
|
||||
'--disable-manual',
|
||||
'--disable-threaded-resolver', '--disable-verbose', '--disable-sspi',
|
||||
'--disable-crypto-auth', '--disable-ntlm-wb', '--disable-tls-srp', '--disable-cookies',
|
||||
'--disable-doh',
|
||||
'--disable-mime',
|
||||
'--disable-netrc',
|
||||
'--disable-progress-meter',
|
||||
'--disable-alt-svc',
|
||||
'--without-ssl', '--without-gnutls', '--without-nss', '--without-libssh2',
|
||||
],
|
||||
|
||||
@@ -365,8 +370,8 @@ curl = AutotoolsProject(
|
||||
)
|
||||
|
||||
libexpat = AutotoolsProject(
|
||||
'https://github.com/libexpat/libexpat/releases/download/R_2_2_6/expat-2.2.6.tar.bz2',
|
||||
'17b43c2716d521369f82fc2dc70f359860e90fa440bea65b3b85f0b246ea81f2',
|
||||
'https://github.com/libexpat/libexpat/releases/download/R_2_2_9/expat-2.2.9.tar.bz2',
|
||||
'f1063084dc4302a427dabcca499c8312b3a32a29b7d2506653ecc8f950a9a237',
|
||||
'lib/libexpat.a',
|
||||
[
|
||||
'--disable-shared', '--enable-static',
|
||||
@@ -392,7 +397,7 @@ libnfs = AutotoolsProject(
|
||||
)
|
||||
|
||||
boost = BoostProject(
|
||||
'http://downloads.sourceforge.net/project/boost/boost/1.70.0/boost_1_70_0.tar.bz2',
|
||||
'430ae8354789de4fd19ee52f3b1f739e1fba576f0aded0897c3c2bc00fb38778',
|
||||
'https://dl.bintray.com/boostorg/release/1.72.0/source/boost_1_72_0.tar.bz2',
|
||||
'59c9b274bc451cf91a9ba1dd2c7fdcaf5d60b1b3aa83f2c9fa143417cc660722',
|
||||
'include/boost/version.hpp',
|
||||
)
|
||||
|
@@ -29,6 +29,8 @@
|
||||
#include "storage/StorageInterface.hxx"
|
||||
#endif
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
static LocatedUri
|
||||
LocateFileUri(const char *uri, const Client *client
|
||||
#ifdef ENABLE_DATABASE
|
||||
|
@@ -21,8 +21,8 @@
|
||||
#include "db/PlaylistVector.hxx"
|
||||
#include "fs/io/TextFile.hxx"
|
||||
#include "fs/io/BufferedOutputStream.hxx"
|
||||
#include "time/ChronoUtil.hxx"
|
||||
#include "util/StringStrip.hxx"
|
||||
#include "util/ChronoUtil.hxx"
|
||||
#include "util/RuntimeError.hxx"
|
||||
|
||||
#include <string.h>
|
||||
|
@@ -38,6 +38,10 @@ struct ReplayGainTuple {
|
||||
return gain > -100;
|
||||
}
|
||||
|
||||
static constexpr ReplayGainTuple Undefined() noexcept {
|
||||
return {-200.0f, 0.0f};
|
||||
}
|
||||
|
||||
gcc_pure
|
||||
float CalculateScale(const ReplayGainConfig &config) const noexcept;
|
||||
};
|
||||
@@ -49,6 +53,13 @@ struct ReplayGainInfo {
|
||||
return track.IsDefined() || album.IsDefined();
|
||||
}
|
||||
|
||||
static constexpr ReplayGainInfo Undefined() noexcept {
|
||||
return {
|
||||
ReplayGainTuple::Undefined(),
|
||||
ReplayGainTuple::Undefined(),
|
||||
};
|
||||
}
|
||||
|
||||
const ReplayGainTuple &Get(ReplayGainMode mode) const noexcept {
|
||||
return mode == ReplayGainMode::ALBUM
|
||||
? (album.IsDefined() ? album : track)
|
||||
|
@@ -27,7 +27,7 @@
|
||||
#include "TagPrint.hxx"
|
||||
#include "client/Response.hxx"
|
||||
#include "fs/Traits.hxx"
|
||||
#include "util/ChronoUtil.hxx"
|
||||
#include "time/ChronoUtil.hxx"
|
||||
#include "util/UriUtil.hxx"
|
||||
|
||||
#define SONG_FILE "file: "
|
||||
|
@@ -27,7 +27,7 @@
|
||||
#include "tag/ParseName.hxx"
|
||||
#include "tag/Tag.hxx"
|
||||
#include "tag/Builder.hxx"
|
||||
#include "util/ChronoUtil.hxx"
|
||||
#include "time/ChronoUtil.hxx"
|
||||
#include "util/StringAPI.hxx"
|
||||
#include "util/StringBuffer.hxx"
|
||||
#include "util/StringStrip.hxx"
|
||||
|
@@ -98,8 +98,6 @@ Song::UpdateFile(Storage &storage) noexcept
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_ARCHIVE
|
||||
|
||||
Song *
|
||||
@@ -145,6 +143,8 @@ Song::UpdateFileInArchive(ArchiveFile &archive) noexcept
|
||||
|
||||
#endif
|
||||
|
||||
#endif /* ENABLE_DATABASE */
|
||||
|
||||
bool
|
||||
DetachedSong::LoadFile(Path path) noexcept
|
||||
{
|
||||
|
@@ -28,7 +28,7 @@
|
||||
#include "db/Stats.hxx"
|
||||
#include "system/Clock.hxx"
|
||||
#include "Log.hxx"
|
||||
#include "util/ChronoUtil.hxx"
|
||||
#include "time/ChronoUtil.hxx"
|
||||
|
||||
#include <chrono>
|
||||
#include <cmath>
|
||||
|
@@ -19,7 +19,7 @@
|
||||
|
||||
#include "TimePrint.hxx"
|
||||
#include "client/Response.hxx"
|
||||
#include "util/TimeISO8601.hxx"
|
||||
#include "time/ISO8601.hxx"
|
||||
|
||||
void
|
||||
time_print(Response &r, const char *name,
|
||||
|
@@ -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
|
||||
@@ -27,6 +27,7 @@
|
||||
#include "../ArchiveVisitor.hxx"
|
||||
#include "input/InputStream.hxx"
|
||||
#include "fs/Path.hxx"
|
||||
#include "system/Error.hxx"
|
||||
#include "util/RuntimeError.hxx"
|
||||
|
||||
#include <zzip/zzip.h>
|
||||
@@ -120,9 +121,19 @@ ZzipArchiveFile::OpenStream(const char *pathname,
|
||||
Mutex &mutex)
|
||||
{
|
||||
ZZIP_FILE *_file = zzip_file_open(dir->dir, pathname, 0);
|
||||
if (_file == nullptr)
|
||||
throw FormatRuntimeError("not found in the ZIP file: %s",
|
||||
pathname);
|
||||
if (_file == nullptr) {
|
||||
const auto error = (zzip_error_t)zzip_error(dir->dir);
|
||||
switch (error) {
|
||||
case ZZIP_ENOENT:
|
||||
throw FormatFileNotFound("Failed to open '%s' in ZIP file",
|
||||
pathname);
|
||||
|
||||
default:
|
||||
throw FormatRuntimeError("Failed to open '%s' in ZIP file: %s",
|
||||
pathname,
|
||||
zzip_strerror(error));
|
||||
}
|
||||
}
|
||||
|
||||
return std::make_unique<ZzipInputStream>(dir, pathname,
|
||||
mutex,
|
||||
|
@@ -35,6 +35,6 @@ extern size_t client_max_command_list_size;
|
||||
extern size_t client_max_output_buffer_size;
|
||||
|
||||
CommandResult
|
||||
client_process_line(Client &client, char *line);
|
||||
client_process_line(Client &client, char *line) noexcept;
|
||||
|
||||
#endif
|
||||
|
@@ -30,7 +30,7 @@
|
||||
|
||||
static CommandResult
|
||||
client_process_command_list(Client &client, bool list_ok,
|
||||
std::list<std::string> &&list)
|
||||
std::list<std::string> &&list) noexcept
|
||||
{
|
||||
CommandResult ret = CommandResult::OK;
|
||||
unsigned num = 0;
|
||||
@@ -51,7 +51,7 @@ client_process_command_list(Client &client, bool list_ok,
|
||||
}
|
||||
|
||||
CommandResult
|
||||
client_process_line(Client &client, char *line)
|
||||
client_process_line(Client &client, char *line) noexcept
|
||||
{
|
||||
CommandResult ret;
|
||||
|
||||
|
@@ -206,9 +206,10 @@ static constexpr struct command commands[] = {
|
||||
|
||||
static constexpr unsigned num_commands = ARRAY_SIZE(commands);
|
||||
|
||||
gcc_pure
|
||||
static bool
|
||||
command_available(gcc_unused const Partition &partition,
|
||||
gcc_unused const struct command *cmd)
|
||||
gcc_unused const struct command *cmd) noexcept
|
||||
{
|
||||
#ifdef ENABLE_SQLITE
|
||||
if (StringIsEqual(cmd->cmd, "sticker"))
|
||||
@@ -235,7 +236,7 @@ command_available(gcc_unused const Partition &partition,
|
||||
|
||||
static CommandResult
|
||||
PrintAvailableCommands(Response &r, const Partition &partition,
|
||||
unsigned permission)
|
||||
unsigned permission) noexcept
|
||||
{
|
||||
for (unsigned i = 0; i < num_commands; ++i) {
|
||||
const struct command *cmd = &commands[i];
|
||||
@@ -249,7 +250,7 @@ PrintAvailableCommands(Response &r, const Partition &partition,
|
||||
}
|
||||
|
||||
static CommandResult
|
||||
PrintUnavailableCommands(Response &r, unsigned permission)
|
||||
PrintUnavailableCommands(Response &r, unsigned permission) noexcept
|
||||
{
|
||||
for (unsigned i = 0; i < num_commands; ++i) {
|
||||
const struct command *cmd = &commands[i];
|
||||
@@ -276,7 +277,7 @@ handle_not_commands(Client &client, gcc_unused Request request, Response &r)
|
||||
}
|
||||
|
||||
void
|
||||
command_init()
|
||||
command_init() noexcept
|
||||
{
|
||||
#ifndef NDEBUG
|
||||
/* ensure that the command list is sorted */
|
||||
@@ -285,8 +286,9 @@ command_init()
|
||||
#endif
|
||||
}
|
||||
|
||||
gcc_pure
|
||||
static const struct command *
|
||||
command_lookup(const char *name)
|
||||
command_lookup(const char *name) noexcept
|
||||
{
|
||||
unsigned a = 0, b = num_commands, i;
|
||||
|
||||
@@ -308,7 +310,7 @@ command_lookup(const char *name)
|
||||
|
||||
static bool
|
||||
command_check_request(const struct command *cmd, Response &r,
|
||||
unsigned permission, Request args)
|
||||
unsigned permission, Request args) noexcept
|
||||
{
|
||||
if (cmd->permission != (permission & cmd->permission)) {
|
||||
r.FormatError(ACK_ERROR_PERMISSION,
|
||||
@@ -342,7 +344,7 @@ command_check_request(const struct command *cmd, Response &r,
|
||||
|
||||
static const struct command *
|
||||
command_checked_lookup(Response &r, unsigned permission,
|
||||
const char *cmd_name, Request args)
|
||||
const char *cmd_name, Request args) noexcept
|
||||
{
|
||||
const struct command *cmd = command_lookup(cmd_name);
|
||||
if (cmd == nullptr) {
|
||||
@@ -360,8 +362,8 @@ command_checked_lookup(Response &r, unsigned permission,
|
||||
}
|
||||
|
||||
CommandResult
|
||||
command_process(Client &client, unsigned num, char *line)
|
||||
try {
|
||||
command_process(Client &client, unsigned num, char *line) noexcept
|
||||
{
|
||||
Response r(client, num);
|
||||
|
||||
/* get the command name (first word on the line) */
|
||||
@@ -389,34 +391,33 @@ try {
|
||||
char *argv[COMMAND_ARGV_MAX];
|
||||
Request args(argv, 0);
|
||||
|
||||
/* now parse the arguments (quoted or unquoted) */
|
||||
try {
|
||||
/* now parse the arguments (quoted or unquoted) */
|
||||
|
||||
while (true) {
|
||||
if (args.size == COMMAND_ARGV_MAX) {
|
||||
r.Error(ACK_ERROR_ARG, "Too many arguments");
|
||||
return CommandResult::ERROR;
|
||||
while (true) {
|
||||
if (args.size == COMMAND_ARGV_MAX) {
|
||||
r.Error(ACK_ERROR_ARG, "Too many arguments");
|
||||
return CommandResult::ERROR;
|
||||
}
|
||||
|
||||
char *a = tokenizer.NextParam();
|
||||
if (a == nullptr)
|
||||
break;
|
||||
|
||||
argv[args.size++] = a;
|
||||
}
|
||||
|
||||
char *a = tokenizer.NextParam();
|
||||
if (a == nullptr)
|
||||
break;
|
||||
/* look up and invoke the command handler */
|
||||
|
||||
argv[args.size++] = a;
|
||||
const struct command *cmd =
|
||||
command_checked_lookup(r, client.GetPermission(),
|
||||
cmd_name, args);
|
||||
if (cmd == nullptr)
|
||||
return CommandResult::ERROR;
|
||||
|
||||
return cmd->handler(client, args, r);
|
||||
} catch (...) {
|
||||
PrintError(r, std::current_exception());
|
||||
return CommandResult::ERROR;
|
||||
}
|
||||
|
||||
/* look up and invoke the command handler */
|
||||
|
||||
const struct command *cmd =
|
||||
command_checked_lookup(r, client.GetPermission(),
|
||||
cmd_name, args);
|
||||
|
||||
CommandResult ret = cmd
|
||||
? cmd->handler(client, args, r)
|
||||
: CommandResult::ERROR;
|
||||
|
||||
return ret;
|
||||
} catch (const std::exception &e) {
|
||||
Response r(client, num);
|
||||
PrintError(r, std::current_exception());
|
||||
return CommandResult::ERROR;
|
||||
}
|
||||
|
@@ -25,12 +25,9 @@
|
||||
class Client;
|
||||
|
||||
void
|
||||
command_init();
|
||||
|
||||
void
|
||||
command_finish();
|
||||
command_init() noexcept;
|
||||
|
||||
CommandResult
|
||||
command_process(Client &client, unsigned num, char *line);
|
||||
command_process(Client &client, unsigned num, char *line) noexcept;
|
||||
|
||||
#endif
|
||||
|
@@ -35,7 +35,7 @@
|
||||
#include "decoder/DecoderPrint.hxx"
|
||||
#include "ls.hxx"
|
||||
#include "mixer/Volume.hxx"
|
||||
#include "util/ChronoUtil.hxx"
|
||||
#include "time/ChronoUtil.hxx"
|
||||
#include "util/UriUtil.hxx"
|
||||
#include "util/StringAPI.hxx"
|
||||
#include "fs/AllocatedPath.hxx"
|
||||
|
@@ -37,9 +37,9 @@
|
||||
#include "client/Response.hxx"
|
||||
#include "Mapper.hxx"
|
||||
#include "fs/AllocatedPath.hxx"
|
||||
#include "time/ChronoUtil.hxx"
|
||||
#include "util/UriUtil.hxx"
|
||||
#include "util/ConstBuffer.hxx"
|
||||
#include "util/ChronoUtil.hxx"
|
||||
#include "LocateUri.hxx"
|
||||
|
||||
bool
|
||||
|
@@ -23,8 +23,8 @@
|
||||
#include "StorageCommands.hxx"
|
||||
#include "Request.hxx"
|
||||
#include "CommandError.hxx"
|
||||
#include "time/ChronoUtil.hxx"
|
||||
#include "util/UriUtil.hxx"
|
||||
#include "util/ChronoUtil.hxx"
|
||||
#include "util/ConstBuffer.hxx"
|
||||
#include "fs/Traits.hxx"
|
||||
#include "client/Client.hxx"
|
||||
|
@@ -153,11 +153,9 @@ ReadConfigParam(ConfigData &config_data, BufferedReader &reader,
|
||||
name, reader.GetLineNumber());
|
||||
|
||||
if (!option.repeatable)
|
||||
if (const auto *param = config_data.GetParam(o))
|
||||
throw FormatRuntimeError("config parameter \"%s\" is first defined "
|
||||
"on line %d and redefined on line %u\n",
|
||||
name, param->line,
|
||||
reader.GetLineNumber());
|
||||
/* if the option is not repeatable, override the old
|
||||
value by removing it first */
|
||||
config_data.GetParamList(o).clear();
|
||||
|
||||
/* now parse the block or the value */
|
||||
|
||||
|
@@ -34,7 +34,7 @@
|
||||
#include "PlaylistInfo.hxx"
|
||||
#include "Interface.hxx"
|
||||
#include "fs/Traits.hxx"
|
||||
#include "util/ChronoUtil.hxx"
|
||||
#include "time/ChronoUtil.hxx"
|
||||
#include "util/RecursiveMap.hxx"
|
||||
|
||||
#include <functional>
|
||||
|
@@ -25,7 +25,7 @@
|
||||
#include "PlaylistDatabase.hxx"
|
||||
#include "fs/io/TextFile.hxx"
|
||||
#include "fs/io/BufferedOutputStream.hxx"
|
||||
#include "util/ChronoUtil.hxx"
|
||||
#include "time/ChronoUtil.hxx"
|
||||
#include "util/StringCompare.hxx"
|
||||
#include "util/NumberParser.hxx"
|
||||
#include "util/RuntimeError.hxx"
|
||||
|
@@ -493,6 +493,12 @@ UpdateWalk::Walk(Directory &root, const char *path, bool discard) noexcept
|
||||
if (!GetInfo(storage, "", info))
|
||||
return false;
|
||||
|
||||
if (!info.IsDirectory()) {
|
||||
FormatError(update_domain, "Not a directory: %s",
|
||||
storage.MapUTF8("").c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
ExcludeList exclude_list;
|
||||
|
||||
UpdateDirectory(root, exclude_list, info);
|
||||
|
@@ -147,6 +147,18 @@ DecoderControl::Seek(SongTime t)
|
||||
seek_error = false;
|
||||
SynchronousCommandLocked(DecoderCommand::SEEK);
|
||||
|
||||
while (state == DecoderState::START)
|
||||
/* If the decoder falls back to DecoderState::START,
|
||||
this means that our SEEK command arrived too late,
|
||||
and the decoder had meanwhile finished decoding and
|
||||
went idle. Our SEEK command is finished, but that
|
||||
means only that the decoder thread has launched the
|
||||
decoder. To work around illegal states, we wait
|
||||
until the decoder plugin has become ready. This is
|
||||
a kludge, built on top of the "late seek" kludge.
|
||||
Not exactly elegant, sorry. */
|
||||
WaitForDecoder();
|
||||
|
||||
if (seek_error)
|
||||
throw std::runtime_error("Decoder failed to seek");
|
||||
}
|
||||
|
@@ -320,6 +320,11 @@ public:
|
||||
gcc_pure
|
||||
bool IsCurrentSong(const DetachedSong &_song) const noexcept;
|
||||
|
||||
gcc_pure
|
||||
bool IsUnseekableCurrentSong(const DetachedSong &_song) const noexcept {
|
||||
return !seekable && IsCurrentSong(_song);
|
||||
}
|
||||
|
||||
gcc_pure
|
||||
bool IsSeekableCurrentSong(const DetachedSong &_song) const noexcept {
|
||||
return seekable && IsCurrentSong(_song);
|
||||
|
@@ -455,6 +455,11 @@ static void
|
||||
decoder_run_song(DecoderControl &dc,
|
||||
const DetachedSong &song, const char *uri, Path path_fs)
|
||||
{
|
||||
if (dc.command == DecoderCommand::SEEK)
|
||||
/* if the SEEK command arrived too late, start the
|
||||
decoder at the seek position */
|
||||
dc.start_time = dc.seek_time;
|
||||
|
||||
DecoderBridge bridge(dc, dc.start_time.IsPositive(),
|
||||
/* pass the song tag only if it's
|
||||
authoritative, i.e. if it's a local
|
||||
|
@@ -269,6 +269,8 @@ static const char *const audiofile_suffixes[] = {
|
||||
};
|
||||
|
||||
static const char *const audiofile_mime_types[] = {
|
||||
"audio/wav",
|
||||
"audio/aiff",
|
||||
"audio/x-wav",
|
||||
"audio/x-aiff",
|
||||
nullptr
|
||||
|
@@ -362,6 +362,7 @@ dsdiff_decode_chunk(DecoderClient &client, InputStream &is,
|
||||
unsigned channels, unsigned sample_rate,
|
||||
const offset_type total_bytes)
|
||||
{
|
||||
const unsigned kbit_rate = channels * sample_rate / 1000;
|
||||
const offset_type start_offset = is.GetOffset();
|
||||
|
||||
uint8_t buffer[8192];
|
||||
@@ -408,7 +409,7 @@ dsdiff_decode_chunk(DecoderClient &client, InputStream &is,
|
||||
bit_reverse_buffer(buffer, buffer + nbytes);
|
||||
|
||||
cmd = client.SubmitData(is, buffer, nbytes,
|
||||
sample_rate / 1000);
|
||||
kbit_rate);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@@ -256,6 +256,7 @@ dsf_decode_chunk(DecoderClient &client, InputStream &is,
|
||||
offset_type n_blocks,
|
||||
bool bitreverse)
|
||||
{
|
||||
const unsigned kbit_rate = channels * sample_rate / 1000;
|
||||
const size_t block_size = channels * DSF_BLOCK_SIZE;
|
||||
const offset_type start_offset = is.GetOffset();
|
||||
|
||||
@@ -291,7 +292,7 @@ dsf_decode_chunk(DecoderClient &client, InputStream &is,
|
||||
|
||||
cmd = client.SubmitData(is,
|
||||
interleaved_buffer, block_size,
|
||||
sample_rate / 1000);
|
||||
kbit_rate);
|
||||
++i;
|
||||
}
|
||||
|
||||
|
@@ -762,7 +762,7 @@ static const char *const ffmpeg_mime_types[] = {
|
||||
"audio/aac",
|
||||
"audio/aacp",
|
||||
"audio/ac3",
|
||||
"audio/aiff"
|
||||
"audio/aiff",
|
||||
"audio/amr",
|
||||
"audio/basic",
|
||||
"audio/flac",
|
||||
@@ -775,12 +775,13 @@ static const char *const ffmpeg_mime_types[] = {
|
||||
"audio/qcelp",
|
||||
"audio/vorbis",
|
||||
"audio/vorbis+ogg",
|
||||
"audio/wav",
|
||||
"audio/x-8svx",
|
||||
"audio/x-16sv",
|
||||
"audio/x-aac",
|
||||
"audio/x-ac3",
|
||||
"audio/x-adx",
|
||||
"audio/x-aiff"
|
||||
"audio/x-aiff",
|
||||
"audio/x-alaw",
|
||||
"audio/x-au",
|
||||
"audio/x-dca",
|
||||
@@ -800,7 +801,7 @@ static const char *const ffmpeg_mime_types[] = {
|
||||
"audio/x-pn-realaudio",
|
||||
"audio/x-pn-multirate-realaudio",
|
||||
"audio/x-speex",
|
||||
"audio/x-tta"
|
||||
"audio/x-tta",
|
||||
"audio/x-voc",
|
||||
"audio/x-wav",
|
||||
"audio/x-wma",
|
||||
|
@@ -21,14 +21,13 @@
|
||||
#include "MadDecoderPlugin.hxx"
|
||||
#include "../DecoderAPI.hxx"
|
||||
#include "input/InputStream.hxx"
|
||||
#include "config/Block.hxx"
|
||||
#include "tag/Id3Scan.hxx"
|
||||
#include "tag/Id3ReplayGain.hxx"
|
||||
#include "tag/Rva2.hxx"
|
||||
#include "tag/Handler.hxx"
|
||||
#include "tag/ReplayGain.hxx"
|
||||
#include "tag/MixRamp.hxx"
|
||||
#include "CheckAudioFormat.hxx"
|
||||
#include "util/Clamp.hxx"
|
||||
#include "util/StringCompare.hxx"
|
||||
#include "util/Domain.hxx"
|
||||
#include "Log.hxx"
|
||||
@@ -40,8 +39,6 @@
|
||||
#include <id3tag.h>
|
||||
#endif
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
@@ -49,17 +46,17 @@
|
||||
|
||||
static constexpr unsigned long FRAMES_CUSHION = 2000;
|
||||
|
||||
enum mp3_action {
|
||||
DECODE_SKIP = -3,
|
||||
DECODE_BREAK = -2,
|
||||
DECODE_CONT = -1,
|
||||
DECODE_OK = 0
|
||||
enum class MadDecoderAction {
|
||||
SKIP,
|
||||
BREAK,
|
||||
CONT,
|
||||
OK
|
||||
};
|
||||
|
||||
enum muteframe {
|
||||
MUTEFRAME_NONE,
|
||||
MUTEFRAME_SKIP,
|
||||
MUTEFRAME_SEEK
|
||||
enum class MadDecoderMuteFrame {
|
||||
NONE,
|
||||
SKIP,
|
||||
SEEK
|
||||
};
|
||||
|
||||
/* the number of samples of silence the decoder inserts at start */
|
||||
@@ -79,7 +76,7 @@ ToSongTime(mad_timer_t t) noexcept
|
||||
}
|
||||
|
||||
static inline int32_t
|
||||
mad_fixed_to_24_sample(mad_fixed_t sample)
|
||||
mad_fixed_to_24_sample(mad_fixed_t sample) noexcept
|
||||
{
|
||||
static constexpr unsigned bits = 24;
|
||||
static constexpr mad_fixed_t MIN = -MAD_F_ONE;
|
||||
@@ -88,73 +85,79 @@ mad_fixed_to_24_sample(mad_fixed_t sample)
|
||||
/* round */
|
||||
sample = sample + (1L << (MAD_F_FRACBITS - bits));
|
||||
|
||||
/* clip */
|
||||
if (gcc_unlikely(sample > MAX))
|
||||
sample = MAX;
|
||||
else if (gcc_unlikely(sample < MIN))
|
||||
sample = MIN;
|
||||
|
||||
/* quantize */
|
||||
return sample >> (MAD_F_FRACBITS + 1 - bits);
|
||||
return Clamp(sample, MIN, MAX)
|
||||
>> (MAD_F_FRACBITS + 1 - bits);
|
||||
}
|
||||
|
||||
static void
|
||||
mad_fixed_to_24_buffer(int32_t *dest, const struct mad_synth *synth,
|
||||
unsigned int start, unsigned int end,
|
||||
mad_fixed_to_24_buffer(int32_t *dest, const struct mad_pcm &src,
|
||||
size_t start, size_t end,
|
||||
unsigned int num_channels)
|
||||
{
|
||||
for (unsigned i = start; i < end; ++i)
|
||||
for (size_t i = start; i < end; ++i)
|
||||
for (unsigned c = 0; c < num_channels; ++c)
|
||||
*dest++ = mad_fixed_to_24_sample(synth->pcm.samples[c][i]);
|
||||
*dest++ = mad_fixed_to_24_sample(src.samples[c][i]);
|
||||
}
|
||||
|
||||
static bool
|
||||
mp3_plugin_init(const ConfigBlock &block)
|
||||
mad_plugin_init(const ConfigBlock &block)
|
||||
{
|
||||
gapless_playback = block.GetBlockValue("gapless",
|
||||
DEFAULT_GAPLESS_MP3_PLAYBACK);
|
||||
return true;
|
||||
}
|
||||
|
||||
struct MadDecoder {
|
||||
class MadDecoder {
|
||||
static constexpr size_t READ_BUFFER_SIZE = 40960;
|
||||
static constexpr size_t MP3_DATA_OUTPUT_BUFFER_SIZE = 2048;
|
||||
|
||||
struct mad_stream stream;
|
||||
struct mad_frame frame;
|
||||
struct mad_synth synth;
|
||||
mad_timer_t timer;
|
||||
unsigned char input_buffer[READ_BUFFER_SIZE];
|
||||
int32_t output_buffer[MP3_DATA_OUTPUT_BUFFER_SIZE];
|
||||
int32_t output_buffer[sizeof(mad_pcm::samples) / sizeof(mad_fixed_t)];
|
||||
SignedSongTime total_time;
|
||||
SongTime elapsed_time;
|
||||
SongTime seek_time;
|
||||
enum muteframe mute_frame = MUTEFRAME_NONE;
|
||||
MadDecoderMuteFrame mute_frame = MadDecoderMuteFrame::NONE;
|
||||
long *frame_offsets = nullptr;
|
||||
mad_timer_t *times = nullptr;
|
||||
unsigned long highest_frame = 0;
|
||||
unsigned long max_frames = 0;
|
||||
unsigned long current_frame = 0;
|
||||
unsigned int drop_start_frames = 0;
|
||||
unsigned int drop_end_frames = 0;
|
||||
size_t highest_frame = 0;
|
||||
size_t max_frames = 0;
|
||||
size_t current_frame = 0;
|
||||
unsigned int drop_start_frames;
|
||||
unsigned int drop_end_frames;
|
||||
unsigned int drop_start_samples = 0;
|
||||
unsigned int drop_end_samples = 0;
|
||||
bool found_replay_gain = false;
|
||||
bool found_first_frame = false;
|
||||
bool decoded_first_frame = false;
|
||||
unsigned long bit_rate;
|
||||
|
||||
/**
|
||||
* If this flag is true, then end-of-file was seen and a
|
||||
* padding of 8 zero bytes were appended to #input_buffer, to
|
||||
* allow libmad to decode the last frame.
|
||||
*/
|
||||
bool was_eof = false;
|
||||
|
||||
DecoderClient *const client;
|
||||
InputStream &input_stream;
|
||||
enum mad_layer layer = mad_layer(0);
|
||||
|
||||
MadDecoder(DecoderClient *client, InputStream &input_stream);
|
||||
~MadDecoder();
|
||||
public:
|
||||
MadDecoder(DecoderClient *client, InputStream &input_stream) noexcept;
|
||||
~MadDecoder() noexcept;
|
||||
|
||||
bool Seek(long offset);
|
||||
bool FillBuffer();
|
||||
void ParseId3(size_t tagsize, Tag *tag);
|
||||
enum mp3_action DecodeNextFrameHeader(Tag *tag);
|
||||
enum mp3_action DecodeNextFrame();
|
||||
void RunDecoder() noexcept;
|
||||
bool RunScan(TagHandler &handler) noexcept;
|
||||
|
||||
private:
|
||||
bool Seek(long offset) noexcept;
|
||||
bool FillBuffer() noexcept;
|
||||
void ParseId3(size_t tagsize, Tag *tag) noexcept;
|
||||
MadDecoderAction DecodeNextFrameHeader(Tag *tag) noexcept;
|
||||
MadDecoderAction DecodeNextFrame() noexcept;
|
||||
|
||||
gcc_pure
|
||||
offset_type ThisFrameOffset() const noexcept;
|
||||
@@ -165,11 +168,11 @@ struct MadDecoder {
|
||||
/**
|
||||
* Attempt to calulcate the length of the song from filesize
|
||||
*/
|
||||
void FileSizeToSongLength();
|
||||
void FileSizeToSongLength() noexcept;
|
||||
|
||||
bool DecodeFirstFrame(Tag *tag);
|
||||
bool DecodeFirstFrame(Tag *tag) noexcept;
|
||||
|
||||
void AllocateBuffers() {
|
||||
void AllocateBuffers() noexcept {
|
||||
assert(max_frames > 0);
|
||||
assert(frame_offsets == nullptr);
|
||||
assert(times == nullptr);
|
||||
@@ -179,27 +182,39 @@ struct MadDecoder {
|
||||
}
|
||||
|
||||
gcc_pure
|
||||
long TimeToFrame(SongTime t) const noexcept;
|
||||
size_t TimeToFrame(SongTime t) const noexcept;
|
||||
|
||||
void UpdateTimerNextFrame();
|
||||
/**
|
||||
* Record the current frame's offset in the "frame_offsets"
|
||||
* buffer and go forward to the next frame, updating the
|
||||
* attributes "current_frame" and "timer".
|
||||
*/
|
||||
void UpdateTimerNextFrame() noexcept;
|
||||
|
||||
/**
|
||||
* Sends the synthesized current frame via
|
||||
* DecoderClient::SubmitData().
|
||||
*/
|
||||
DecoderCommand SendPCM(unsigned i, unsigned pcm_length);
|
||||
DecoderCommand SubmitPCM(size_t start, size_t n) noexcept;
|
||||
|
||||
/**
|
||||
* Synthesize the current frame and send it via
|
||||
* DecoderClient::SubmitData().
|
||||
*/
|
||||
DecoderCommand SyncAndSend();
|
||||
DecoderCommand SynthAndSubmit() noexcept;
|
||||
|
||||
bool Read();
|
||||
/**
|
||||
* @return false to stop decoding
|
||||
*/
|
||||
bool HandleCurrentFrame() noexcept;
|
||||
|
||||
bool LoadNextFrame() noexcept;
|
||||
|
||||
bool Read() noexcept;
|
||||
};
|
||||
|
||||
MadDecoder::MadDecoder(DecoderClient *_client,
|
||||
InputStream &_input_stream)
|
||||
InputStream &_input_stream) noexcept
|
||||
:client(_client), input_stream(_input_stream)
|
||||
{
|
||||
mad_stream_init(&stream);
|
||||
@@ -210,7 +225,7 @@ MadDecoder::MadDecoder(DecoderClient *_client,
|
||||
}
|
||||
|
||||
inline bool
|
||||
MadDecoder::Seek(long offset)
|
||||
MadDecoder::Seek(long offset) noexcept
|
||||
{
|
||||
try {
|
||||
input_stream.LockSeek(offset);
|
||||
@@ -225,32 +240,38 @@ MadDecoder::Seek(long offset)
|
||||
}
|
||||
|
||||
inline bool
|
||||
MadDecoder::FillBuffer()
|
||||
MadDecoder::FillBuffer() noexcept
|
||||
{
|
||||
size_t remaining, length;
|
||||
unsigned char *dest;
|
||||
/* amount of rest data still residing in the buffer */
|
||||
size_t rest_size = 0;
|
||||
|
||||
size_t max_read_size = sizeof(input_buffer);
|
||||
unsigned char *dest = input_buffer;
|
||||
|
||||
if (stream.next_frame != nullptr) {
|
||||
remaining = stream.bufend - stream.next_frame;
|
||||
memmove(input_buffer, stream.next_frame, remaining);
|
||||
dest = input_buffer + remaining;
|
||||
length = READ_BUFFER_SIZE - remaining;
|
||||
} else {
|
||||
remaining = 0;
|
||||
length = READ_BUFFER_SIZE;
|
||||
dest = input_buffer;
|
||||
rest_size = stream.bufend - stream.next_frame;
|
||||
memmove(input_buffer, stream.next_frame, rest_size);
|
||||
dest += rest_size;
|
||||
max_read_size -= rest_size;
|
||||
}
|
||||
|
||||
/* we've exhausted the read buffer, so give up!, these potential
|
||||
* mp3 frames are way too big, and thus unlikely to be mp3 frames */
|
||||
if (length == 0)
|
||||
if (max_read_size == 0)
|
||||
return false;
|
||||
|
||||
length = decoder_read(client, input_stream, dest, length);
|
||||
if (length == 0)
|
||||
return false;
|
||||
size_t nbytes = decoder_read(client, input_stream,
|
||||
dest, max_read_size);
|
||||
if (nbytes == 0) {
|
||||
if (was_eof || max_read_size < MAD_BUFFER_GUARD)
|
||||
return false;
|
||||
|
||||
mad_stream_buffer(&stream, input_buffer, length + remaining);
|
||||
was_eof = true;
|
||||
nbytes = MAD_BUFFER_GUARD;
|
||||
memset(dest, 0, nbytes);
|
||||
}
|
||||
|
||||
mad_stream_buffer(&stream, input_buffer, rest_size + nbytes);
|
||||
stream.error = MAD_ERROR_NONE;
|
||||
|
||||
return true;
|
||||
@@ -286,7 +307,7 @@ parse_id3_mixramp(struct id3_tag *tag) noexcept
|
||||
#endif
|
||||
|
||||
inline void
|
||||
MadDecoder::ParseId3(size_t tagsize, Tag *mpd_tag)
|
||||
MadDecoder::ParseId3(size_t tagsize, Tag *mpd_tag) noexcept
|
||||
{
|
||||
#ifdef ENABLE_ID3TAG
|
||||
std::unique_ptr<id3_byte_t[]> allocated;
|
||||
@@ -354,7 +375,7 @@ MadDecoder::ParseId3(size_t tagsize, Tag *mpd_tag)
|
||||
* of the ID3 frame.
|
||||
*/
|
||||
static signed long
|
||||
id3_tag_query(const void *p0, size_t length)
|
||||
id3_tag_query(const void *p0, size_t length) noexcept
|
||||
{
|
||||
const char *p = (const char *)p0;
|
||||
|
||||
@@ -364,26 +385,26 @@ id3_tag_query(const void *p0, size_t length)
|
||||
}
|
||||
#endif /* !ENABLE_ID3TAG */
|
||||
|
||||
static enum mp3_action
|
||||
RecoverFrameError(struct mad_stream &stream)
|
||||
static MadDecoderAction
|
||||
RecoverFrameError(const struct mad_stream &stream) noexcept
|
||||
{
|
||||
if (MAD_RECOVERABLE(stream.error))
|
||||
return DECODE_SKIP;
|
||||
return MadDecoderAction::SKIP;
|
||||
else if (stream.error == MAD_ERROR_BUFLEN)
|
||||
return DECODE_CONT;
|
||||
return MadDecoderAction::CONT;
|
||||
|
||||
FormatWarning(mad_domain,
|
||||
"unrecoverable frame level error: %s",
|
||||
mad_stream_errorstr(&stream));
|
||||
return DECODE_BREAK;
|
||||
return MadDecoderAction::BREAK;
|
||||
}
|
||||
|
||||
enum mp3_action
|
||||
MadDecoder::DecodeNextFrameHeader(Tag *tag)
|
||||
MadDecoderAction
|
||||
MadDecoder::DecodeNextFrameHeader(Tag *tag) noexcept
|
||||
{
|
||||
if ((stream.buffer == nullptr || stream.error == MAD_ERROR_BUFLEN) &&
|
||||
!FillBuffer())
|
||||
return DECODE_BREAK;
|
||||
return MadDecoderAction::BREAK;
|
||||
|
||||
if (mad_header_decode(&frame.header, &stream)) {
|
||||
if (stream.error == MAD_ERROR_LOSTSYNC && stream.this_frame) {
|
||||
@@ -393,7 +414,7 @@ MadDecoder::DecodeNextFrameHeader(Tag *tag)
|
||||
|
||||
if (tagsize > 0) {
|
||||
ParseId3((size_t)tagsize, tag);
|
||||
return DECODE_CONT;
|
||||
return MadDecoderAction::CONT;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -404,24 +425,24 @@ MadDecoder::DecodeNextFrameHeader(Tag *tag)
|
||||
if (layer == (mad_layer)0) {
|
||||
if (new_layer != MAD_LAYER_II && new_layer != MAD_LAYER_III) {
|
||||
/* Only layer 2 and 3 have been tested to work */
|
||||
return DECODE_SKIP;
|
||||
return MadDecoderAction::SKIP;
|
||||
}
|
||||
|
||||
layer = new_layer;
|
||||
} else if (new_layer != layer) {
|
||||
/* Don't decode frames with a different layer than the first */
|
||||
return DECODE_SKIP;
|
||||
return MadDecoderAction::SKIP;
|
||||
}
|
||||
|
||||
return DECODE_OK;
|
||||
return MadDecoderAction::OK;
|
||||
}
|
||||
|
||||
enum mp3_action
|
||||
MadDecoder::DecodeNextFrame()
|
||||
MadDecoderAction
|
||||
MadDecoder::DecodeNextFrame() noexcept
|
||||
{
|
||||
if ((stream.buffer == nullptr || stream.error == MAD_ERROR_BUFLEN) &&
|
||||
!FillBuffer())
|
||||
return DECODE_BREAK;
|
||||
return MadDecoderAction::BREAK;
|
||||
|
||||
if (mad_frame_decode(&frame, &stream)) {
|
||||
if (stream.error == MAD_ERROR_LOSTSYNC) {
|
||||
@@ -430,14 +451,14 @@ MadDecoder::DecodeNextFrame()
|
||||
stream.this_frame);
|
||||
if (tagsize > 0) {
|
||||
mad_stream_skip(&stream, tagsize);
|
||||
return DECODE_CONT;
|
||||
return MadDecoderAction::CONT;
|
||||
}
|
||||
}
|
||||
|
||||
return RecoverFrameError(stream);
|
||||
}
|
||||
|
||||
return DECODE_OK;
|
||||
return MadDecoderAction::OK;
|
||||
}
|
||||
|
||||
/* xing stuff stolen from alsaplayer, and heavily modified by jat */
|
||||
@@ -476,7 +497,7 @@ struct lame {
|
||||
};
|
||||
|
||||
static bool
|
||||
parse_xing(struct xing *xing, struct mad_bitptr *ptr, int *oldbitlen)
|
||||
parse_xing(struct xing *xing, struct mad_bitptr *ptr, int *oldbitlen) noexcept
|
||||
{
|
||||
int bitlen = *oldbitlen;
|
||||
|
||||
@@ -556,7 +577,7 @@ parse_xing(struct xing *xing, struct mad_bitptr *ptr, int *oldbitlen)
|
||||
}
|
||||
|
||||
static bool
|
||||
parse_lame(struct lame *lame, struct mad_bitptr *ptr, int *bitlen)
|
||||
parse_lame(struct lame *lame, struct mad_bitptr *ptr, int *bitlen) noexcept
|
||||
{
|
||||
/* Unlike the xing header, the lame tag has a fixed length. Fail if
|
||||
* not all 36 bytes (288 bits) are there. */
|
||||
@@ -647,7 +668,7 @@ parse_lame(struct lame *lame, struct mad_bitptr *ptr, int *bitlen)
|
||||
}
|
||||
|
||||
static inline SongTime
|
||||
mp3_frame_duration(const struct mad_frame *frame)
|
||||
mad_frame_duration(const struct mad_frame *frame) noexcept
|
||||
{
|
||||
return ToSongTime(frame->header.duration);
|
||||
}
|
||||
@@ -672,12 +693,12 @@ MadDecoder::RestIncludingThisFrame() const noexcept
|
||||
}
|
||||
|
||||
inline void
|
||||
MadDecoder::FileSizeToSongLength()
|
||||
MadDecoder::FileSizeToSongLength() noexcept
|
||||
{
|
||||
if (input_stream.KnownSize()) {
|
||||
offset_type rest = RestIncludingThisFrame();
|
||||
|
||||
const SongTime frame_duration = mp3_frame_duration(&frame);
|
||||
const SongTime frame_duration = mad_frame_duration(&frame);
|
||||
const SongTime duration =
|
||||
SongTime::FromScale<uint64_t>(rest,
|
||||
frame.header.bitrate / 8);
|
||||
@@ -694,25 +715,30 @@ MadDecoder::FileSizeToSongLength()
|
||||
}
|
||||
|
||||
inline bool
|
||||
MadDecoder::DecodeFirstFrame(Tag *tag)
|
||||
MadDecoder::DecodeFirstFrame(Tag *tag) noexcept
|
||||
{
|
||||
struct xing xing;
|
||||
|
||||
#if GCC_CHECK_VERSION(10,0)
|
||||
/* work around bogus -Wuninitialized in GCC 10 */
|
||||
xing.frames = 0;
|
||||
#endif
|
||||
|
||||
while (true) {
|
||||
enum mp3_action ret;
|
||||
MadDecoderAction ret;
|
||||
do {
|
||||
ret = DecodeNextFrameHeader(tag);
|
||||
} while (ret == DECODE_CONT);
|
||||
if (ret == DECODE_BREAK)
|
||||
} while (ret == MadDecoderAction::CONT);
|
||||
if (ret == MadDecoderAction::BREAK)
|
||||
return false;
|
||||
if (ret == DECODE_SKIP) continue;
|
||||
if (ret == MadDecoderAction::SKIP) continue;
|
||||
|
||||
do {
|
||||
ret = DecodeNextFrame();
|
||||
} while (ret == DECODE_CONT);
|
||||
if (ret == DECODE_BREAK)
|
||||
} while (ret == MadDecoderAction::CONT);
|
||||
if (ret == MadDecoderAction::BREAK)
|
||||
return false;
|
||||
if (ret == DECODE_OK) break;
|
||||
if (ret == MadDecoderAction::OK) break;
|
||||
}
|
||||
|
||||
struct mad_bitptr ptr = stream.anc_ptr;
|
||||
@@ -724,7 +750,7 @@ MadDecoder::DecodeFirstFrame(Tag *tag)
|
||||
* if an xing tag exists, use that!
|
||||
*/
|
||||
if (parse_xing(&xing, &ptr, &bitlen)) {
|
||||
mute_frame = MUTEFRAME_SKIP;
|
||||
mute_frame = MadDecoderMuteFrame::SKIP;
|
||||
|
||||
if ((xing.flags & XING_FRAMES) && xing.frames) {
|
||||
mad_timer_t duration = frame.header.duration;
|
||||
@@ -736,9 +762,17 @@ MadDecoder::DecodeFirstFrame(Tag *tag)
|
||||
struct lame lame;
|
||||
if (parse_lame(&lame, &ptr, &bitlen)) {
|
||||
if (gapless_playback && input_stream.IsSeekable()) {
|
||||
/* libmad inserts 529 samples of
|
||||
silence at the beginning and
|
||||
removes those 529 samples at the
|
||||
end */
|
||||
drop_start_samples = lame.encoder_delay +
|
||||
DECODERDELAY;
|
||||
drop_end_samples = lame.encoder_padding;
|
||||
if (drop_end_samples > DECODERDELAY)
|
||||
drop_end_samples -= DECODERDELAY;
|
||||
else
|
||||
drop_end_samples = 0;
|
||||
}
|
||||
|
||||
/* Album gain isn't currently used. See comment in
|
||||
@@ -759,7 +793,7 @@ MadDecoder::DecodeFirstFrame(Tag *tag)
|
||||
|
||||
if (max_frames > 8 * 1024 * 1024) {
|
||||
FormatWarning(mad_domain,
|
||||
"mp3 file header indicates too many frames: %lu",
|
||||
"mp3 file header indicates too many frames: %zu",
|
||||
max_frames);
|
||||
return false;
|
||||
}
|
||||
@@ -767,7 +801,7 @@ MadDecoder::DecodeFirstFrame(Tag *tag)
|
||||
return true;
|
||||
}
|
||||
|
||||
MadDecoder::~MadDecoder()
|
||||
MadDecoder::~MadDecoder() noexcept
|
||||
{
|
||||
mad_synth_finish(&synth);
|
||||
mad_frame_finish(&frame);
|
||||
@@ -777,10 +811,10 @@ MadDecoder::~MadDecoder()
|
||||
delete[] times;
|
||||
}
|
||||
|
||||
long
|
||||
size_t
|
||||
MadDecoder::TimeToFrame(SongTime t) const noexcept
|
||||
{
|
||||
unsigned long i;
|
||||
size_t i;
|
||||
|
||||
for (i = 0; i < highest_frame; ++i) {
|
||||
auto frame_time = ToSongTime(times[i]);
|
||||
@@ -792,12 +826,11 @@ MadDecoder::TimeToFrame(SongTime t) const noexcept
|
||||
}
|
||||
|
||||
void
|
||||
MadDecoder::UpdateTimerNextFrame()
|
||||
MadDecoder::UpdateTimerNextFrame() noexcept
|
||||
{
|
||||
if (current_frame >= highest_frame) {
|
||||
/* record this frame's properties in frame_offsets
|
||||
(for seeking) and times */
|
||||
bit_rate = frame.header.bitrate;
|
||||
|
||||
if (current_frame >= max_frames)
|
||||
/* cap current_frame */
|
||||
@@ -818,36 +851,22 @@ MadDecoder::UpdateTimerNextFrame()
|
||||
}
|
||||
|
||||
DecoderCommand
|
||||
MadDecoder::SendPCM(unsigned i, unsigned pcm_length)
|
||||
MadDecoder::SubmitPCM(size_t i, size_t pcm_length) noexcept
|
||||
{
|
||||
unsigned max_samples = sizeof(output_buffer) /
|
||||
sizeof(output_buffer[0]) /
|
||||
MAD_NCHANNELS(&frame.header);
|
||||
size_t num_samples = pcm_length - i;
|
||||
|
||||
while (i < pcm_length) {
|
||||
unsigned int num_samples = pcm_length - i;
|
||||
if (num_samples > max_samples)
|
||||
num_samples = max_samples;
|
||||
mad_fixed_to_24_buffer(output_buffer, synth.pcm,
|
||||
i, i + num_samples,
|
||||
MAD_NCHANNELS(&frame.header));
|
||||
num_samples *= MAD_NCHANNELS(&frame.header);
|
||||
|
||||
i += num_samples;
|
||||
|
||||
mad_fixed_to_24_buffer(output_buffer, &synth,
|
||||
i - num_samples, i,
|
||||
MAD_NCHANNELS(&frame.header));
|
||||
num_samples *= MAD_NCHANNELS(&frame.header);
|
||||
|
||||
auto cmd = client->SubmitData(input_stream, output_buffer,
|
||||
sizeof(output_buffer[0]) * num_samples,
|
||||
bit_rate / 1000);
|
||||
if (cmd != DecoderCommand::NONE)
|
||||
return cmd;
|
||||
}
|
||||
|
||||
return DecoderCommand::NONE;
|
||||
return client->SubmitData(input_stream, output_buffer,
|
||||
sizeof(output_buffer[0]) * num_samples,
|
||||
frame.header.bitrate / 1000);
|
||||
}
|
||||
|
||||
inline DecoderCommand
|
||||
MadDecoder::SyncAndSend()
|
||||
MadDecoder::SynthAndSubmit() noexcept
|
||||
{
|
||||
mad_synth_frame(&synth, &frame);
|
||||
|
||||
@@ -864,33 +883,33 @@ MadDecoder::SyncAndSend()
|
||||
drop_start_frames--;
|
||||
return DecoderCommand::NONE;
|
||||
} else if ((drop_end_frames > 0) &&
|
||||
(current_frame == (max_frames + 1 - drop_end_frames))) {
|
||||
current_frame == max_frames - drop_end_frames) {
|
||||
/* stop decoding, effectively dropping all remaining
|
||||
frames */
|
||||
return DecoderCommand::STOP;
|
||||
}
|
||||
|
||||
unsigned i = 0;
|
||||
size_t i = 0;
|
||||
if (!decoded_first_frame) {
|
||||
i = drop_start_samples;
|
||||
decoded_first_frame = true;
|
||||
}
|
||||
|
||||
unsigned pcm_length = synth.pcm.length;
|
||||
size_t pcm_length = synth.pcm.length;
|
||||
if (drop_end_samples &&
|
||||
(current_frame == max_frames - drop_end_frames)) {
|
||||
current_frame == max_frames - drop_end_frames - 1) {
|
||||
if (drop_end_samples >= pcm_length)
|
||||
pcm_length = 0;
|
||||
else
|
||||
pcm_length -= drop_end_samples;
|
||||
return DecoderCommand::STOP;
|
||||
|
||||
pcm_length -= drop_end_samples;
|
||||
}
|
||||
|
||||
auto cmd = SendPCM(i, pcm_length);
|
||||
auto cmd = SubmitPCM(i, pcm_length);
|
||||
if (cmd != DecoderCommand::NONE)
|
||||
return cmd;
|
||||
|
||||
if (drop_end_samples &&
|
||||
(current_frame == max_frames - drop_end_frames))
|
||||
current_frame == max_frames - drop_end_frames - 1)
|
||||
/* stop decoding, effectively dropping
|
||||
* all remaining samples */
|
||||
return DecoderCommand::STOP;
|
||||
@@ -899,44 +918,51 @@ MadDecoder::SyncAndSend()
|
||||
}
|
||||
|
||||
inline bool
|
||||
MadDecoder::Read()
|
||||
MadDecoder::HandleCurrentFrame() noexcept
|
||||
{
|
||||
UpdateTimerNextFrame();
|
||||
|
||||
switch (mute_frame) {
|
||||
DecoderCommand cmd;
|
||||
|
||||
case MUTEFRAME_SKIP:
|
||||
mute_frame = MUTEFRAME_NONE;
|
||||
case MadDecoderMuteFrame::SKIP:
|
||||
mute_frame = MadDecoderMuteFrame::NONE;
|
||||
break;
|
||||
case MUTEFRAME_SEEK:
|
||||
case MadDecoderMuteFrame::SEEK:
|
||||
if (elapsed_time >= seek_time)
|
||||
mute_frame = MUTEFRAME_NONE;
|
||||
mute_frame = MadDecoderMuteFrame::NONE;
|
||||
UpdateTimerNextFrame();
|
||||
break;
|
||||
case MUTEFRAME_NONE:
|
||||
cmd = SyncAndSend();
|
||||
case MadDecoderMuteFrame::NONE:
|
||||
cmd = SynthAndSubmit();
|
||||
UpdateTimerNextFrame();
|
||||
if (cmd == DecoderCommand::SEEK) {
|
||||
assert(input_stream.IsSeekable());
|
||||
|
||||
const auto t = client->GetSeekTime();
|
||||
unsigned long j = TimeToFrame(t);
|
||||
size_t j = TimeToFrame(t);
|
||||
if (j < highest_frame) {
|
||||
if (Seek(frame_offsets[j])) {
|
||||
current_frame = j;
|
||||
was_eof = false;
|
||||
client->CommandFinished();
|
||||
} else
|
||||
client->SeekError();
|
||||
} else {
|
||||
seek_time = t;
|
||||
mute_frame = MUTEFRAME_SEEK;
|
||||
mute_frame = MadDecoderMuteFrame::SEEK;
|
||||
client->CommandFinished();
|
||||
}
|
||||
} else if (cmd != DecoderCommand::NONE)
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
inline bool
|
||||
MadDecoder::LoadNextFrame() noexcept
|
||||
{
|
||||
while (true) {
|
||||
enum mp3_action ret;
|
||||
MadDecoderAction ret;
|
||||
do {
|
||||
Tag tag;
|
||||
|
||||
@@ -945,84 +971,104 @@ MadDecoder::Read()
|
||||
if (!tag.IsEmpty())
|
||||
client->SubmitTag(input_stream,
|
||||
std::move(tag));
|
||||
} while (ret == DECODE_CONT);
|
||||
if (ret == DECODE_BREAK)
|
||||
} while (ret == MadDecoderAction::CONT);
|
||||
if (ret == MadDecoderAction::BREAK)
|
||||
return false;
|
||||
|
||||
const bool skip = ret == DECODE_SKIP;
|
||||
const bool skip = ret == MadDecoderAction::SKIP;
|
||||
|
||||
if (mute_frame == MUTEFRAME_NONE) {
|
||||
if (mute_frame == MadDecoderMuteFrame::NONE) {
|
||||
do {
|
||||
ret = DecodeNextFrame();
|
||||
} while (ret == DECODE_CONT);
|
||||
if (ret == DECODE_BREAK)
|
||||
} while (ret == MadDecoderAction::CONT);
|
||||
if (ret == MadDecoderAction::BREAK)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!skip && ret == DECODE_OK)
|
||||
if (!skip && ret == MadDecoderAction::OK)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
mp3_decode(DecoderClient &client, InputStream &input_stream)
|
||||
inline bool
|
||||
MadDecoder::Read() noexcept
|
||||
{
|
||||
MadDecoder data(&client, input_stream);
|
||||
return HandleCurrentFrame() &&
|
||||
LoadNextFrame();
|
||||
}
|
||||
|
||||
inline void
|
||||
MadDecoder::RunDecoder() noexcept
|
||||
{
|
||||
assert(client != nullptr);
|
||||
|
||||
Tag tag;
|
||||
if (!data.DecodeFirstFrame(&tag)) {
|
||||
if (client.GetCommand() == DecoderCommand::NONE)
|
||||
if (!DecodeFirstFrame(&tag)) {
|
||||
if (client->GetCommand() == DecoderCommand::NONE)
|
||||
LogError(mad_domain,
|
||||
"input/Input does not appear to be a mp3 bit stream");
|
||||
"input does not appear to be a mp3 bit stream");
|
||||
return;
|
||||
}
|
||||
|
||||
data.AllocateBuffers();
|
||||
AllocateBuffers();
|
||||
|
||||
client.Ready(CheckAudioFormat(data.frame.header.samplerate,
|
||||
SampleFormat::S24_P32,
|
||||
MAD_NCHANNELS(&data.frame.header)),
|
||||
input_stream.IsSeekable(),
|
||||
data.total_time);
|
||||
client->Ready(CheckAudioFormat(frame.header.samplerate,
|
||||
SampleFormat::S24_P32,
|
||||
MAD_NCHANNELS(&frame.header)),
|
||||
input_stream.IsSeekable(),
|
||||
total_time);
|
||||
|
||||
if (!tag.IsEmpty())
|
||||
client.SubmitTag(input_stream, std::move(tag));
|
||||
client->SubmitTag(input_stream, std::move(tag));
|
||||
|
||||
while (data.Read()) {}
|
||||
while (Read()) {}
|
||||
}
|
||||
|
||||
static bool
|
||||
mad_decoder_scan_stream(InputStream &is, TagHandler &handler) noexcept
|
||||
static void
|
||||
mad_decode(DecoderClient &client, InputStream &input_stream)
|
||||
{
|
||||
MadDecoder data(nullptr, is);
|
||||
if (!data.DecodeFirstFrame(nullptr))
|
||||
MadDecoder data(&client, input_stream);
|
||||
data.RunDecoder();
|
||||
}
|
||||
|
||||
inline bool
|
||||
MadDecoder::RunScan(TagHandler &handler) noexcept
|
||||
{
|
||||
if (!DecodeFirstFrame(nullptr))
|
||||
return false;
|
||||
|
||||
if (!data.total_time.IsNegative())
|
||||
handler.OnDuration(SongTime(data.total_time));
|
||||
if (!total_time.IsNegative())
|
||||
handler.OnDuration(SongTime(total_time));
|
||||
|
||||
try {
|
||||
handler.OnAudioFormat(CheckAudioFormat(data.frame.header.samplerate,
|
||||
handler.OnAudioFormat(CheckAudioFormat(frame.header.samplerate,
|
||||
SampleFormat::S24_P32,
|
||||
MAD_NCHANNELS(&data.frame.header)));
|
||||
MAD_NCHANNELS(&frame.header)));
|
||||
} catch (...) {
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static const char *const mp3_suffixes[] = { "mp3", "mp2", nullptr };
|
||||
static const char *const mp3_mime_types[] = { "audio/mpeg", nullptr };
|
||||
static bool
|
||||
mad_decoder_scan_stream(InputStream &is, TagHandler &handler) noexcept
|
||||
{
|
||||
MadDecoder data(nullptr, is);
|
||||
return data.RunScan(handler);
|
||||
}
|
||||
|
||||
static const char *const mad_suffixes[] = { "mp3", "mp2", nullptr };
|
||||
static const char *const mad_mime_types[] = { "audio/mpeg", nullptr };
|
||||
|
||||
const struct DecoderPlugin mad_decoder_plugin = {
|
||||
"mad",
|
||||
mp3_plugin_init,
|
||||
mad_plugin_init,
|
||||
nullptr,
|
||||
mp3_decode,
|
||||
mad_decode,
|
||||
nullptr,
|
||||
nullptr,
|
||||
mad_decoder_scan_stream,
|
||||
nullptr,
|
||||
mp3_suffixes,
|
||||
mp3_mime_types,
|
||||
mad_suffixes,
|
||||
mad_mime_types,
|
||||
};
|
||||
|
@@ -137,6 +137,28 @@ mpc_to_mpd_buffer(MpcdecSampleTraits::pointer_type dest,
|
||||
*dest++ = mpc_to_mpd_sample(*src++);
|
||||
}
|
||||
|
||||
static constexpr ReplayGainTuple
|
||||
ImportMpcdecReplayGain(mpc_uint16_t gain, mpc_uint16_t peak) noexcept
|
||||
{
|
||||
auto t = ReplayGainTuple::Undefined();
|
||||
|
||||
if (gain != 0 && peak != 0) {
|
||||
t.gain = MPC_OLD_GAIN_REF - (gain / 256.);
|
||||
t.peak = pow(10, peak / 256. / 20) / 32767;
|
||||
}
|
||||
|
||||
return t;
|
||||
}
|
||||
|
||||
static constexpr ReplayGainInfo
|
||||
ImportMpcdecReplayGain(const mpc_streaminfo &info) noexcept
|
||||
{
|
||||
auto rgi = ReplayGainInfo::Undefined();
|
||||
rgi.album = ImportMpcdecReplayGain(info.gain_album, info.peak_album);
|
||||
rgi.track = ImportMpcdecReplayGain(info.gain_title, info.peak_title);
|
||||
return rgi;
|
||||
}
|
||||
|
||||
static void
|
||||
mpcdec_decode(DecoderClient &client, InputStream &is)
|
||||
{
|
||||
@@ -167,14 +189,11 @@ mpcdec_decode(DecoderClient &client, InputStream &is)
|
||||
mpcdec_sample_format,
|
||||
info.channels);
|
||||
|
||||
ReplayGainInfo rgi;
|
||||
rgi.Clear();
|
||||
rgi.album.gain = MPC_OLD_GAIN_REF - (info.gain_album / 256.);
|
||||
rgi.album.peak = pow(10, info.peak_album / 256. / 20) / 32767;
|
||||
rgi.track.gain = MPC_OLD_GAIN_REF - (info.gain_title / 256.);
|
||||
rgi.track.peak = pow(10, info.peak_title / 256. / 20) / 32767;
|
||||
|
||||
client.SubmitReplayGain(&rgi);
|
||||
{
|
||||
const auto rgi = ImportMpcdecReplayGain(info);
|
||||
if (rgi.IsDefined())
|
||||
client.SubmitReplayGain(&rgi);
|
||||
}
|
||||
|
||||
client.Ready(audio_format, is.IsSeekable(),
|
||||
SongTime::FromS(mpc_streaminfo_get_length(&info)));
|
||||
|
@@ -47,8 +47,12 @@ OggDecoder::LoadEndPacket(ogg_packet &packet) const
|
||||
DecoderReader reader(client, input_stream);
|
||||
OggSyncState sync2(reader);
|
||||
OggStreamState stream2(GetSerialNo());
|
||||
|
||||
/* passing synced=false because we're inside an
|
||||
OggVisitor callback, and our InputStream may be in
|
||||
the middle of an Ogg packet */
|
||||
result = OggSeekFindEOS(sync2, stream2, packet,
|
||||
input_stream);
|
||||
input_stream, false);
|
||||
}
|
||||
|
||||
/* restore the previous file position */
|
||||
|
@@ -22,12 +22,12 @@
|
||||
#include "lib/xiph/XiphTags.hxx"
|
||||
#include "tag/Handler.hxx"
|
||||
#include "tag/ParseName.hxx"
|
||||
#include "util/ASCII.hxx"
|
||||
#include "ReplayGainInfo.hxx"
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
gcc_pure
|
||||
@@ -46,7 +46,7 @@ ScanOneOpusTag(const char *name, const char *value,
|
||||
ReplayGainInfo *rgi,
|
||||
TagHandler &handler) noexcept
|
||||
{
|
||||
if (rgi != nullptr && strcmp(name, "R128_TRACK_GAIN") == 0) {
|
||||
if (rgi != nullptr && StringEqualsCaseASCII(name, "R128_TRACK_GAIN")) {
|
||||
/* R128_TRACK_GAIN is a Q7.8 fixed point number in
|
||||
dB */
|
||||
|
||||
@@ -54,7 +54,8 @@ ScanOneOpusTag(const char *name, const char *value,
|
||||
long l = strtol(value, &endptr, 10);
|
||||
if (endptr > value && *endptr == 0)
|
||||
rgi->track.gain = double(l) / 256.;
|
||||
} else if (rgi != nullptr && strcmp(name, "R128_ALBUM_GAIN") == 0) {
|
||||
} else if (rgi != nullptr &&
|
||||
StringEqualsCaseASCII(name, "R128_ALBUM_GAIN")) {
|
||||
/* R128_ALBUM_GAIN is a Q7.8 fixed point number in
|
||||
dB */
|
||||
|
||||
|
@@ -25,6 +25,7 @@
|
||||
#include "song/DetachedSong.hxx"
|
||||
#include "fs/Path.hxx"
|
||||
#include "fs/AllocatedPath.hxx"
|
||||
#include "lib/icu/Converter.hxx"
|
||||
#ifdef HAVE_SIDPLAYFP
|
||||
#include "fs/io/FileReader.hxx"
|
||||
#include "util/RuntimeError.hxx"
|
||||
@@ -32,6 +33,8 @@
|
||||
#include "util/Macros.hxx"
|
||||
#include "util/StringFormat.hxx"
|
||||
#include "util/Domain.hxx"
|
||||
#include "util/AllocatedString.hxx"
|
||||
#include "util/CharUtil.hxx"
|
||||
#include "system/ByteOrder.hxx"
|
||||
#include "Log.hxx"
|
||||
|
||||
@@ -432,19 +435,70 @@ sidplay_file_decode(DecoderClient &client, Path path_fs)
|
||||
} while (cmd != DecoderCommand::STOP);
|
||||
}
|
||||
|
||||
static AllocatedString<char>
|
||||
Windows1252ToUTF8(const char *s) noexcept
|
||||
{
|
||||
#ifdef HAVE_ICU_CONVERTER
|
||||
try {
|
||||
std::unique_ptr<IcuConverter>
|
||||
converter(IcuConverter::Create("windows-1252"));
|
||||
|
||||
return converter->ToUTF8(s);
|
||||
} catch (...) { }
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Fallback to not transcoding windows-1252 to utf-8, that may result
|
||||
* in invalid utf-8 unless nonprintable characters are replaced.
|
||||
*/
|
||||
auto t = AllocatedString<char>::Duplicate(s);
|
||||
|
||||
for (size_t i = 0; t[i] != AllocatedString<char>::SENTINEL; i++)
|
||||
if (!IsPrintableASCII(t[i]))
|
||||
t[i] = '?';
|
||||
|
||||
return t;
|
||||
}
|
||||
|
||||
gcc_pure
|
||||
static const char *
|
||||
static AllocatedString<char>
|
||||
GetInfoString(const SidTuneInfo &info, unsigned i) noexcept
|
||||
{
|
||||
#ifdef HAVE_SIDPLAYFP
|
||||
return info.numberOfInfoStrings() > i
|
||||
const char *s = info.numberOfInfoStrings() > i
|
||||
? info.infoString(i)
|
||||
: nullptr;
|
||||
: "";
|
||||
#else
|
||||
return info.numberOfInfoStrings > i
|
||||
const char *s = info.numberOfInfoStrings > i
|
||||
? info.infoString[i]
|
||||
: nullptr;
|
||||
: "";
|
||||
#endif
|
||||
|
||||
return Windows1252ToUTF8(s);
|
||||
}
|
||||
|
||||
gcc_pure
|
||||
static AllocatedString<char>
|
||||
GetDateString(const SidTuneInfo &info) noexcept
|
||||
{
|
||||
/*
|
||||
* Field 2 is called <released>, previously used as <copyright>.
|
||||
* It is formatted <year><space><company or author or group>,
|
||||
* where <year> may be <YYYY>, <YYY?>, <YY??> or <YYYY-YY>, for
|
||||
* example "1987", "199?", "19??" or "1985-87". The <company or
|
||||
* author or group> may be for example Rob Hubbard. A full field
|
||||
* may be for example "1987 Rob Hubbard".
|
||||
*/
|
||||
AllocatedString<char> release = GetInfoString(info, 2);
|
||||
|
||||
/* Keep the <year> part only for the date. */
|
||||
for (size_t i = 0; release[i] != AllocatedString<char>::SENTINEL; i++)
|
||||
if (std::isspace(release[i])) {
|
||||
release[i] = AllocatedString<char>::SENTINEL;
|
||||
break;
|
||||
}
|
||||
|
||||
return release;
|
||||
}
|
||||
|
||||
static void
|
||||
@@ -452,27 +506,25 @@ ScanSidTuneInfo(const SidTuneInfo &info, unsigned track, unsigned n_tracks,
|
||||
TagHandler &handler) noexcept
|
||||
{
|
||||
/* title */
|
||||
const char *title = GetInfoString(info, 0);
|
||||
if (title == nullptr)
|
||||
title = "";
|
||||
const auto title = GetInfoString(info, 0);
|
||||
|
||||
if (n_tracks > 1) {
|
||||
const auto tag_title =
|
||||
StringFormat<1024>("%s (%u/%u)",
|
||||
title, track, n_tracks);
|
||||
handler.OnTag(TAG_TITLE, tag_title);
|
||||
title.c_str(), track, n_tracks);
|
||||
handler.OnTag(TAG_TITLE, tag_title.c_str());
|
||||
} else
|
||||
handler.OnTag(TAG_TITLE, title);
|
||||
handler.OnTag(TAG_TITLE, title.c_str());
|
||||
|
||||
/* artist */
|
||||
const char *artist = GetInfoString(info, 1);
|
||||
if (artist != nullptr)
|
||||
handler.OnTag(TAG_ARTIST, artist);
|
||||
const auto artist = GetInfoString(info, 1);
|
||||
if (!artist.empty())
|
||||
handler.OnTag(TAG_ARTIST, artist.c_str());
|
||||
|
||||
/* date */
|
||||
const char *date = GetInfoString(info, 2);
|
||||
if (date != nullptr)
|
||||
handler.OnTag(TAG_DATE, date);
|
||||
const auto date = GetDateString(info);
|
||||
if (!date.empty())
|
||||
handler.OnTag(TAG_DATE, date.c_str());
|
||||
|
||||
/* track */
|
||||
handler.OnTag(TAG_TRACK, StringFormat<16>("%u", track));
|
||||
@@ -547,6 +599,10 @@ sidplay_container_scan(Path path_fs)
|
||||
AddTagHandler h(tag_builder);
|
||||
ScanSidTuneInfo(info, i, n_tracks, h);
|
||||
|
||||
const SignedSongTime duration = get_song_length(tune);
|
||||
if (!duration.IsNegative())
|
||||
h.OnDuration(SongTime(duration));
|
||||
|
||||
char track_name[32];
|
||||
/* Construct container/tune path names, eg.
|
||||
Delta.sid/tune_001.sid */
|
||||
|
@@ -322,6 +322,8 @@ static const char *const sndfile_suffixes[] = {
|
||||
};
|
||||
|
||||
static const char *const sndfile_mime_types[] = {
|
||||
"audio/wav",
|
||||
"audio/aiff",
|
||||
"audio/x-wav",
|
||||
"audio/x-aiff",
|
||||
|
||||
|
@@ -137,7 +137,8 @@ static constexpr int
|
||||
ExportTimeoutMS(std::chrono::steady_clock::duration timeout)
|
||||
{
|
||||
return timeout >= timeout.zero()
|
||||
? int(std::chrono::duration_cast<std::chrono::milliseconds>(timeout).count())
|
||||
/* round up (+1) to avoid unnecessary wakeups */
|
||||
? int(std::chrono::duration_cast<std::chrono::milliseconds>(timeout).count()) + 1
|
||||
: -1;
|
||||
}
|
||||
|
||||
@@ -220,7 +221,6 @@ EventLoop::Run() noexcept
|
||||
} while (!quit);
|
||||
|
||||
#ifndef NDEBUG
|
||||
assert(busy);
|
||||
assert(thread.IsInside());
|
||||
#endif
|
||||
}
|
||||
|
@@ -22,6 +22,10 @@
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#ifdef USE_EPOLL
|
||||
#include <errno.h>
|
||||
#endif
|
||||
|
||||
#ifndef _WIN32
|
||||
#include <poll.h>
|
||||
#endif
|
||||
@@ -37,17 +41,42 @@ MultiSocketMonitor::Reset() noexcept
|
||||
assert(GetEventLoop().IsInside());
|
||||
|
||||
fds.clear();
|
||||
#ifdef USE_EPOLL
|
||||
always_ready_fds.clear();
|
||||
#endif
|
||||
IdleMonitor::Cancel();
|
||||
timeout_event.Cancel();
|
||||
ready = refresh = false;
|
||||
}
|
||||
|
||||
bool
|
||||
MultiSocketMonitor::AddSocket(SocketDescriptor fd, unsigned events) noexcept
|
||||
{
|
||||
fds.emplace_front(*this, fd);
|
||||
bool success = fds.front().Schedule(events);
|
||||
if (!success) {
|
||||
fds.pop_front();
|
||||
|
||||
#ifdef USE_EPOLL
|
||||
if (errno == EPERM)
|
||||
/* not supported by epoll (e.g. "/dev/null"):
|
||||
add it to the "always ready" list */
|
||||
always_ready_fds.push_front({fd, events});
|
||||
#endif
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
void
|
||||
MultiSocketMonitor::ClearSocketList() noexcept
|
||||
{
|
||||
assert(GetEventLoop().IsInside());
|
||||
|
||||
fds.clear();
|
||||
#ifdef USE_EPOLL
|
||||
always_ready_fds.clear();
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifndef _WIN32
|
||||
@@ -55,6 +84,10 @@ MultiSocketMonitor::ClearSocketList() noexcept
|
||||
void
|
||||
MultiSocketMonitor::ReplaceSocketList(pollfd *pfds, unsigned n) noexcept
|
||||
{
|
||||
#ifdef USE_EPOLL
|
||||
always_ready_fds.clear();
|
||||
#endif
|
||||
|
||||
pollfd *const end = pfds + n;
|
||||
|
||||
UpdateSocketList([pfds, end](SocketDescriptor fd) -> unsigned {
|
||||
@@ -64,9 +97,7 @@ MultiSocketMonitor::ReplaceSocketList(pollfd *pfds, unsigned n) noexcept
|
||||
if (i == end)
|
||||
return 0;
|
||||
|
||||
auto events = i->events;
|
||||
i->events = 0;
|
||||
return events;
|
||||
return std::exchange(i->events, 0);
|
||||
});
|
||||
|
||||
for (auto i = pfds; i != end; ++i)
|
||||
@@ -79,7 +110,20 @@ MultiSocketMonitor::ReplaceSocketList(pollfd *pfds, unsigned n) noexcept
|
||||
void
|
||||
MultiSocketMonitor::Prepare() noexcept
|
||||
{
|
||||
const auto timeout = PrepareSockets();
|
||||
auto timeout = PrepareSockets();
|
||||
|
||||
#ifdef USE_EPOLL
|
||||
if (!always_ready_fds.empty()) {
|
||||
/* if there was at least one file descriptor not
|
||||
supported by epoll, install a very short timeout
|
||||
because we assume it's always ready */
|
||||
constexpr std::chrono::steady_clock::duration ready_timeout =
|
||||
std::chrono::milliseconds(1);
|
||||
if (timeout < timeout.zero() || timeout > ready_timeout)
|
||||
timeout = ready_timeout;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (timeout >= timeout.zero())
|
||||
timeout_event.Schedule(timeout);
|
||||
else
|
||||
|
@@ -50,12 +50,10 @@ class MultiSocketMonitor : IdleMonitor
|
||||
unsigned revents;
|
||||
|
||||
public:
|
||||
SingleFD(MultiSocketMonitor &_multi, SocketDescriptor _fd,
|
||||
unsigned events) noexcept
|
||||
SingleFD(MultiSocketMonitor &_multi,
|
||||
SocketDescriptor _fd) noexcept
|
||||
:SocketMonitor(_fd, _multi.GetEventLoop()),
|
||||
multi(_multi), revents(0) {
|
||||
Schedule(events);
|
||||
}
|
||||
multi(_multi), revents(0) {}
|
||||
|
||||
SocketDescriptor GetSocket() const noexcept {
|
||||
return SocketMonitor::GetSocket();
|
||||
@@ -86,8 +84,6 @@ class MultiSocketMonitor : IdleMonitor
|
||||
}
|
||||
};
|
||||
|
||||
friend class SingleFD;
|
||||
|
||||
TimerEvent timeout_event;
|
||||
|
||||
/**
|
||||
@@ -106,6 +102,21 @@ class MultiSocketMonitor : IdleMonitor
|
||||
|
||||
std::forward_list<SingleFD> fds;
|
||||
|
||||
#ifdef USE_EPOLL
|
||||
struct AlwaysReady {
|
||||
const SocketDescriptor fd;
|
||||
const unsigned revents;
|
||||
};
|
||||
|
||||
/**
|
||||
* A list of file descriptors which are always ready. This is
|
||||
* a kludge needed because the ALSA output plugin gives us a
|
||||
* file descriptor to /dev/null, which is incompatible with
|
||||
* epoll (epoll_ctl() returns -EPERM).
|
||||
*/
|
||||
std::forward_list<AlwaysReady> always_ready_fds;
|
||||
#endif
|
||||
|
||||
public:
|
||||
static constexpr unsigned READ = SocketMonitor::READ;
|
||||
static constexpr unsigned WRITE = SocketMonitor::WRITE;
|
||||
@@ -147,9 +158,7 @@ public:
|
||||
*
|
||||
* May only be called from PrepareSockets().
|
||||
*/
|
||||
void AddSocket(SocketDescriptor fd, unsigned events) noexcept {
|
||||
fds.emplace_front(*this, fd, events);
|
||||
}
|
||||
bool AddSocket(SocketDescriptor fd, unsigned events) noexcept;
|
||||
|
||||
/**
|
||||
* Remove all sockets.
|
||||
@@ -204,6 +213,11 @@ public:
|
||||
i.ClearReturnedEvents();
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef USE_EPOLL
|
||||
for (const auto &i : always_ready_fds)
|
||||
f(i.fd, i.revents);
|
||||
#endif
|
||||
}
|
||||
|
||||
protected:
|
||||
@@ -232,7 +246,6 @@ private:
|
||||
|
||||
void OnTimeout() noexcept {
|
||||
SetReady();
|
||||
IdleMonitor::Schedule();
|
||||
}
|
||||
|
||||
virtual void OnIdle() noexcept final;
|
||||
|
@@ -68,20 +68,24 @@ SocketMonitor::Close() noexcept
|
||||
Steal().Close();
|
||||
}
|
||||
|
||||
void
|
||||
bool
|
||||
SocketMonitor::Schedule(unsigned flags) noexcept
|
||||
{
|
||||
assert(IsDefined());
|
||||
|
||||
if (flags == GetScheduledFlags())
|
||||
return;
|
||||
return true;
|
||||
|
||||
bool success;
|
||||
if (scheduled_flags == 0)
|
||||
loop.AddFD(fd.Get(), flags, *this);
|
||||
success = loop.AddFD(fd.Get(), flags, *this);
|
||||
else if (flags == 0)
|
||||
loop.RemoveFD(fd.Get(), *this);
|
||||
success = loop.RemoveFD(fd.Get(), *this);
|
||||
else
|
||||
loop.ModifyFD(fd.Get(), flags, *this);
|
||||
success = loop.ModifyFD(fd.Get(), flags, *this);
|
||||
|
||||
scheduled_flags = flags;
|
||||
if (success)
|
||||
scheduled_flags = flags;
|
||||
|
||||
return success;
|
||||
}
|
||||
|
@@ -98,18 +98,22 @@ public:
|
||||
return scheduled_flags;
|
||||
}
|
||||
|
||||
void Schedule(unsigned flags) noexcept;
|
||||
/**
|
||||
* @return true on success, false on error (with errno set if
|
||||
* USE_EPOLL is defined)
|
||||
*/
|
||||
bool Schedule(unsigned flags) noexcept;
|
||||
|
||||
void Cancel() noexcept {
|
||||
Schedule(0);
|
||||
}
|
||||
|
||||
void ScheduleRead() noexcept {
|
||||
Schedule(GetScheduledFlags() | READ | HANGUP | ERROR);
|
||||
bool ScheduleRead() noexcept {
|
||||
return Schedule(GetScheduledFlags() | READ | HANGUP | ERROR);
|
||||
}
|
||||
|
||||
void ScheduleWrite() noexcept {
|
||||
Schedule(GetScheduledFlags() | WRITE);
|
||||
bool ScheduleWrite() noexcept {
|
||||
return Schedule(GetScheduledFlags() | WRITE);
|
||||
}
|
||||
|
||||
void CancelRead() noexcept {
|
||||
|
@@ -35,6 +35,9 @@ input_glue = static_library(
|
||||
'BufferedInputStream.cxx',
|
||||
'MaybeBufferedInputStream.cxx',
|
||||
include_directories: inc,
|
||||
dependencies: [
|
||||
boost_dep,
|
||||
],
|
||||
)
|
||||
|
||||
input_glue_dep = declare_dependency(
|
||||
|
@@ -298,11 +298,7 @@ CdioParanoiaInputStream::Read(void *ptr, size_t length)
|
||||
if (s_err) {
|
||||
FormatError(cdio_domain,
|
||||
"paranoia_read: %s", s_err);
|
||||
#if LIBCDIO_VERSION_NUM >= 90
|
||||
cdio_cddap_free_messages(s_err);
|
||||
#else
|
||||
free(s_err);
|
||||
#endif
|
||||
}
|
||||
|
||||
throw;
|
||||
|
@@ -180,7 +180,6 @@ CurlInputStream::FreeEasyIndirect() noexcept
|
||||
{
|
||||
BlockingCall(GetEventLoop(), [this](){
|
||||
FreeEasy();
|
||||
(*curl_init)->InvalidateSockets();
|
||||
});
|
||||
}
|
||||
|
||||
|
@@ -6,7 +6,7 @@ if alsa_dep.found()
|
||||
input_plugins_sources += 'AlsaInputPlugin.cxx'
|
||||
endif
|
||||
|
||||
libcdio_paranoia_dep = dependency('libcdio_paranoia', version: '>= 0.4', required: get_option('cdio_paranoia'))
|
||||
libcdio_paranoia_dep = dependency('libcdio_paranoia', version: '>= 10.2+0.93+1', required: get_option('cdio_paranoia'))
|
||||
conf.set('ENABLE_CDIO_PARANOIA', libcdio_paranoia_dep.found())
|
||||
if libcdio_paranoia_dep.found()
|
||||
input_plugins_sources += 'CdioParanoiaInputPlugin.cxx'
|
||||
|
@@ -34,11 +34,7 @@
|
||||
#include "util/Compiler.h"
|
||||
|
||||
#include <cdio/version.h>
|
||||
#if LIBCDIO_VERSION_NUM >= 90
|
||||
#include <cdio/paranoia/paranoia.h>
|
||||
#else
|
||||
#include <cdio/paranoia.h>
|
||||
#endif
|
||||
|
||||
#include <stdexcept>
|
||||
#include <utility>
|
||||
|
@@ -30,6 +30,8 @@
|
||||
#ifndef CURL_EASY_HXX
|
||||
#define CURL_EASY_HXX
|
||||
|
||||
#include "String.hxx"
|
||||
|
||||
#include <curl/curl.h>
|
||||
|
||||
#include <utility>
|
||||
@@ -88,8 +90,8 @@ public:
|
||||
throw std::runtime_error(curl_easy_strerror(code));
|
||||
}
|
||||
|
||||
char *Escape(const char *string, int length=0) const noexcept {
|
||||
return curl_easy_escape(handle, string, length);
|
||||
CurlString Escape(const char *string, int length=0) const noexcept {
|
||||
return CurlString(curl_easy_escape(handle, string, length));
|
||||
}
|
||||
};
|
||||
|
||||
|
71
src/lib/curl/Escape.cxx
Normal file
71
src/lib/curl/Escape.cxx
Normal file
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright (C) 2018 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 "Escape.hxx"
|
||||
#include "Easy.hxx"
|
||||
#include "String.hxx"
|
||||
#include "util/IterableSplitString.hxx"
|
||||
|
||||
std::string
|
||||
CurlEscapeUriPath(CURL *curl, StringView src) noexcept
|
||||
{
|
||||
std::string dest;
|
||||
|
||||
for (const auto i : IterableSplitString(src, '/')) {
|
||||
CurlString escaped(curl_easy_escape(curl, i.data, i.size));
|
||||
if (!dest.empty())
|
||||
dest.push_back('/');
|
||||
dest += escaped.c_str();
|
||||
}
|
||||
|
||||
return dest;
|
||||
}
|
||||
|
||||
std::string
|
||||
CurlEscapeUriPath(StringView src) noexcept
|
||||
{
|
||||
CurlEasy easy;
|
||||
return CurlEscapeUriPath(easy.Get(), src);
|
||||
}
|
||||
|
||||
std::string
|
||||
CurlUnescape(CURL *curl, StringView src) noexcept
|
||||
{
|
||||
int outlength;
|
||||
CurlString tmp(curl_easy_unescape(curl, src.data, src.size,
|
||||
&outlength));
|
||||
return std::string(tmp.c_str(), outlength);
|
||||
}
|
||||
|
||||
std::string
|
||||
CurlUnescape(StringView src) noexcept
|
||||
{
|
||||
CurlEasy easy;
|
||||
return CurlUnescape(easy.Get(), src);
|
||||
}
|
@@ -1,8 +1,5 @@
|
||||
/*
|
||||
* Copyright 2007-2017 Content Management AG
|
||||
* All rights reserved.
|
||||
*
|
||||
* author: Max Kellermann <mk@cm4all.com>
|
||||
* Copyright (C) 2018 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
|
||||
@@ -30,32 +27,25 @@
|
||||
* OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "TimeISO8601.hxx"
|
||||
#include "TimeConvert.hxx"
|
||||
#include "TimeParser.hxx"
|
||||
#ifndef CURL_ESCAPE_HXX
|
||||
#define CURL_ESCAPE_HXX
|
||||
|
||||
#include <curl/curl.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
struct StringView;
|
||||
|
||||
std::string
|
||||
CurlEscapeUriPath(CURL *curl, StringView src) noexcept;
|
||||
|
||||
std::string
|
||||
CurlEscapeUriPath(StringView src) noexcept;
|
||||
|
||||
std::string
|
||||
CurlUnescape(CURL *curl, StringView src) noexcept;
|
||||
|
||||
std::string
|
||||
CurlUnescape(StringView src) noexcept;
|
||||
|
||||
StringBuffer<64>
|
||||
FormatISO8601(const struct tm &tm) noexcept
|
||||
{
|
||||
StringBuffer<64> buffer;
|
||||
strftime(buffer.data(), buffer.capacity(),
|
||||
#ifdef _WIN32
|
||||
"%Y-%m-%dT%H:%M:%SZ",
|
||||
#else
|
||||
"%FT%TZ",
|
||||
#endif
|
||||
&tm);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
StringBuffer<64>
|
||||
FormatISO8601(std::chrono::system_clock::time_point tp)
|
||||
{
|
||||
return FormatISO8601(GmTime(tp));
|
||||
}
|
||||
|
||||
std::chrono::system_clock::time_point
|
||||
ParseISO8601(const char *s)
|
||||
{
|
||||
return ParseTimePoint(s, "%FT%TZ");
|
||||
}
|
@@ -28,6 +28,7 @@
|
||||
*/
|
||||
|
||||
#include "Form.hxx"
|
||||
#include "String.hxx"
|
||||
|
||||
std::string
|
||||
EncodeForm(CURL *curl,
|
||||
@@ -43,12 +44,10 @@ EncodeForm(CURL *curl,
|
||||
result.push_back('=');
|
||||
|
||||
if (!i.second.empty()) {
|
||||
char *value = curl_easy_escape(curl, i.second.data(),
|
||||
i.second.length());
|
||||
if (value != nullptr) {
|
||||
CurlString value(curl_easy_escape(curl, i.second.data(),
|
||||
i.second.length()));
|
||||
if (value)
|
||||
result.append(value);
|
||||
curl_free(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2008-2016 Max Kellermann <max.kellermann@gmail.com>
|
||||
* Copyright 2008-2016 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
|
||||
@@ -181,8 +181,6 @@ CurlGlobal::Remove(CURL *easy) noexcept
|
||||
assert(easy != nullptr);
|
||||
|
||||
curl_multi_remove_handle(multi.Get(), easy);
|
||||
|
||||
InvalidateSockets();
|
||||
}
|
||||
|
||||
static CurlRequest *
|
||||
@@ -227,12 +225,12 @@ CurlGlobal::UpdateTimeout(long timeout_ms) noexcept
|
||||
return;
|
||||
}
|
||||
|
||||
if (timeout_ms < 10)
|
||||
/* CURL 7.21.1 likes to report "timeout=0", which
|
||||
if (timeout_ms < 1)
|
||||
/* CURL's threaded resolver sets a timeout of 0ms, which
|
||||
means we're running in a busy loop. Quite a bad
|
||||
idea to waste so much CPU. Let's use a lower limit
|
||||
of 10ms. */
|
||||
timeout_ms = 10;
|
||||
of 1ms. */
|
||||
timeout_ms = 1;
|
||||
|
||||
timeout_event.Schedule(std::chrono::milliseconds(timeout_ms));
|
||||
}
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2008-2016 Max Kellermann <max.kellermann@gmail.com>
|
||||
* Copyright 2008-2016 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
|
||||
@@ -74,16 +74,6 @@ public:
|
||||
SocketAction(CURL_SOCKET_TIMEOUT, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* This is a kludge to allow pausing/resuming a stream with
|
||||
* libcurl < 7.32.0. Read the curl_easy_pause manpage for
|
||||
* more information.
|
||||
*/
|
||||
void ResumeSockets() {
|
||||
int running_handles;
|
||||
curl_multi_socket_all(multi.Get(), &running_handles);
|
||||
}
|
||||
|
||||
private:
|
||||
void UpdateTimeout(long timeout_ms) noexcept;
|
||||
static int TimerFunction(CURLM *global, long timeout_ms,
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2008-2017 Max Kellermann <max.kellermann@gmail.com>
|
||||
* Copyright 2008-2018 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
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2008-2017 Max Kellermann <max.kellermann@gmail.com>
|
||||
* Copyright 2008-2018 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
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Max Kellermann <max.kellermann@gmail.com>
|
||||
* Copyright 2016-2018 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
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2008-2017 Max Kellermann <max.kellermann@gmail.com>
|
||||
* Copyright 2008-2018 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
|
||||
@@ -30,7 +30,6 @@
|
||||
#include "config.h"
|
||||
#include "Request.hxx"
|
||||
#include "Global.hxx"
|
||||
#include "Version.hxx"
|
||||
#include "Handler.hxx"
|
||||
#include "event/Call.hxx"
|
||||
#include "util/RuntimeError.hxx"
|
||||
@@ -124,12 +123,6 @@ CurlRequest::Resume() noexcept
|
||||
|
||||
curl_easy_pause(easy.Get(), CURLPAUSE_CONT);
|
||||
|
||||
if (IsCurlOlderThan(0x072000))
|
||||
/* libcurl older than 7.32.0 does not update
|
||||
its sockets after curl_easy_pause(); force
|
||||
libcurl to do it now */
|
||||
global.ResumeSockets();
|
||||
|
||||
global.InvalidateSockets();
|
||||
}
|
||||
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2008-2017 Max Kellermann <max.kellermann@gmail.com>
|
||||
* Copyright 2008-2018 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
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2008-2017 Max Kellermann <max.kellermann@gmail.com>
|
||||
* Copyright 2008-2018 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
|
||||
@@ -33,6 +33,7 @@
|
||||
#include <curl/curl.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <stdexcept>
|
||||
|
||||
/**
|
||||
* OO wrapper for "struct curl_slist *".
|
||||
|
77
src/lib/curl/String.hxx
Normal file
77
src/lib/curl/String.hxx
Normal file
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Copyright 2019 Max Kellermann <max.kellermann@gmail.com>
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* - Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* - Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the
|
||||
* distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
* FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
|
||||
* OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef CURL_STRING_HXX
|
||||
#define CURL_STRING_HXX
|
||||
|
||||
#include <curl/curl.h>
|
||||
|
||||
#include <utility>
|
||||
|
||||
/**
|
||||
* An OO wrapper for an allocated string to be freed with curl_free().
|
||||
*/
|
||||
class CurlString {
|
||||
char *p = nullptr;
|
||||
|
||||
public:
|
||||
CurlString() noexcept = default;
|
||||
CurlString(std::nullptr_t) noexcept {}
|
||||
|
||||
explicit CurlString(char *_p) noexcept
|
||||
:p(_p) {}
|
||||
|
||||
CurlString(CurlString &&src) noexcept
|
||||
:p(std::exchange(src.p, nullptr)) {}
|
||||
|
||||
~CurlString() noexcept {
|
||||
if (p != nullptr)
|
||||
curl_free(p);
|
||||
}
|
||||
|
||||
CurlString &operator=(CurlString &&src) noexcept {
|
||||
using std::swap;
|
||||
swap(p, src.p);
|
||||
return *this;
|
||||
}
|
||||
|
||||
operator bool() const noexcept {
|
||||
return p != nullptr;
|
||||
}
|
||||
|
||||
operator const char *() const noexcept {
|
||||
return p;
|
||||
}
|
||||
|
||||
const char *c_str() const noexcept {
|
||||
return p;
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Max Kellermann <max.kellermann@gmail.com>
|
||||
* Copyright 2017-2018 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
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Max Kellermann <max.kellermann@gmail.com>
|
||||
* Copyright 2017-2018 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
|
||||
|
@@ -1,4 +1,4 @@
|
||||
curl_dep = dependency('libcurl', version: '>= 7.18', required: get_option('curl'))
|
||||
curl_dep = dependency('libcurl', version: '>= 7.32', required: get_option('curl'))
|
||||
conf.set('ENABLE_CURL', curl_dep.found())
|
||||
if not curl_dep.found()
|
||||
subdir_done()
|
||||
@@ -11,6 +11,7 @@ curl = static_library(
|
||||
'Init.cxx',
|
||||
'Global.cxx',
|
||||
'Request.cxx',
|
||||
'Escape.cxx',
|
||||
'Form.cxx',
|
||||
include_directories: inc,
|
||||
dependencies: [
|
||||
|
@@ -35,6 +35,8 @@
|
||||
|
||||
#include <array>
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
template<typename T> struct ConstBuffer;
|
||||
|
||||
gcc_pure
|
||||
|
@@ -20,7 +20,7 @@ if icu_dep.found()
|
||||
elif not get_option('iconv').disabled()
|
||||
have_iconv = compiler.has_function('iconv')
|
||||
conf.set('HAVE_ICONV', have_iconv)
|
||||
if get_option('iconv').enabled()
|
||||
if not have_iconv and get_option('iconv').enabled()
|
||||
error('iconv() not available')
|
||||
endif
|
||||
endif
|
||||
|
@@ -25,6 +25,7 @@
|
||||
#include "util/ASCII.hxx"
|
||||
|
||||
#include <utility>
|
||||
#include <stdexcept>
|
||||
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
|
@@ -1,4 +1,9 @@
|
||||
sqlite_dep = dependency('sqlite3', version: '>= 3.7.3', required: get_option('sqlite'))
|
||||
if enable_database
|
||||
sqlite_dep = dependency('sqlite3', version: '>= 3.7.3', required: get_option('sqlite'))
|
||||
else
|
||||
sqlite_dep = dependency('', required: false)
|
||||
endif
|
||||
|
||||
conf.set('ENABLE_SQLITE', sqlite_dep.found())
|
||||
if not sqlite_dep.found()
|
||||
subdir_done()
|
||||
|
@@ -57,13 +57,14 @@ OggSeekPageAtOffset(OggSyncState &oy, ogg_stream_state &os, InputStream &is,
|
||||
|
||||
bool
|
||||
OggSeekFindEOS(OggSyncState &oy, ogg_stream_state &os, ogg_packet &packet,
|
||||
InputStream &is)
|
||||
InputStream &is, bool synced)
|
||||
{
|
||||
if (!is.KnownSize())
|
||||
return false;
|
||||
|
||||
if (is.GetRest() < 65536)
|
||||
return OggFindEOS(oy, os, packet);
|
||||
return (synced || oy.ExpectPageSeekIn(os)) &&
|
||||
OggFindEOS(oy, os, packet);
|
||||
|
||||
if (!is.CheapSeeking())
|
||||
return false;
|
||||
|
@@ -47,10 +47,13 @@ OggSeekPageAtOffset(OggSyncState &oy, ogg_stream_state &os, InputStream &is,
|
||||
* Try to find the end-of-stream (EOS) packet. Seek to the end of the
|
||||
* file if necessary.
|
||||
*
|
||||
* @param synced is the #OggSyncState currently synced? If not, then
|
||||
* we need to use ogg_sync_pageseek() instead of ogg_sync_pageout(),
|
||||
* which is more expensive
|
||||
* @return true if the EOS packet was found
|
||||
*/
|
||||
bool
|
||||
OggSeekFindEOS(OggSyncState &oy, ogg_stream_state &os, ogg_packet &packet,
|
||||
InputStream &is);
|
||||
InputStream &is, bool synced=true);
|
||||
|
||||
#endif
|
||||
|
@@ -69,12 +69,12 @@ OggVisitor::HandlePacket(const ogg_packet &packet)
|
||||
/* fail if BOS is missing */
|
||||
throw std::runtime_error("BOS packet expected");
|
||||
|
||||
OnOggPacket(packet);
|
||||
|
||||
if (packet.e_o_s) {
|
||||
EndStream();
|
||||
return;
|
||||
}
|
||||
|
||||
OnOggPacket(packet);
|
||||
}
|
||||
|
||||
inline void
|
||||
|
@@ -69,8 +69,21 @@ private:
|
||||
void HandlePackets();
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Called when the "beginning of stream" packet has been seen.
|
||||
*
|
||||
* @param packet the "beginning of stream" packet
|
||||
*/
|
||||
virtual void OnOggBeginning(const ogg_packet &packet) = 0;
|
||||
|
||||
/**
|
||||
* Called for each follow-up packet.
|
||||
*/
|
||||
virtual void OnOggPacket(const ogg_packet &packet) = 0;
|
||||
|
||||
/**
|
||||
* Called after the "end of stream" packet has been processed.
|
||||
*/
|
||||
virtual void OnOggEnd() = 0;
|
||||
};
|
||||
|
||||
|
@@ -49,6 +49,9 @@ xiph = static_library(
|
||||
'VorbisComments.cxx',
|
||||
'XiphTags.cxx',
|
||||
include_directories: inc,
|
||||
dependencies: [
|
||||
libvorbis_dep,
|
||||
],
|
||||
)
|
||||
|
||||
xiph_dep = declare_dependency(
|
||||
|
@@ -1,4 +1,4 @@
|
||||
if not get_option('neighbor')
|
||||
if not get_option('neighbor') or not enable_database
|
||||
conf.set('ENABLE_NEIGHBOR_PLUGINS', false)
|
||||
neighbor_glue_dep = dependency('', required: false)
|
||||
subdir_done()
|
||||
|
@@ -31,6 +31,7 @@
|
||||
#define STATIC_SOCKET_ADDRESS_HXX
|
||||
|
||||
#include "SocketAddress.hxx"
|
||||
#include "Features.hxx"
|
||||
#include "util/Compiler.h"
|
||||
|
||||
#include <assert.h>
|
||||
|
@@ -159,6 +159,7 @@ AudioOutputControl::InternalOpen(const AudioFormat in_audio_format,
|
||||
} catch (...) {
|
||||
LogError(std::current_exception());
|
||||
Failure(std::current_exception());
|
||||
return;
|
||||
}
|
||||
|
||||
if (f != in_audio_format || f != output->out_audio_format)
|
||||
@@ -458,7 +459,7 @@ AudioOutputControl::Task() noexcept
|
||||
case Command::RELEASE:
|
||||
if (!open) {
|
||||
/* the output has failed after
|
||||
the PAUSE command was submitted; bail
|
||||
the RELEASE command was submitted; bail
|
||||
out */
|
||||
CommandFinished();
|
||||
break;
|
||||
|
@@ -28,6 +28,8 @@
|
||||
#include "util/Domain.hxx"
|
||||
#include "Log.hxx"
|
||||
|
||||
#include <atomic>
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
#include <jack/jack.h>
|
||||
@@ -69,13 +71,13 @@ struct JackOutput final : AudioOutput {
|
||||
jack_client_t *client;
|
||||
jack_ringbuffer_t *ringbuffer[MAX_PORTS];
|
||||
|
||||
bool shutdown;
|
||||
std::atomic_bool shutdown;
|
||||
|
||||
/**
|
||||
* While this flag is set, the "process" callback generates
|
||||
* silence.
|
||||
*/
|
||||
bool pause;
|
||||
std::atomic_bool pause;
|
||||
|
||||
explicit JackOutput(const ConfigBlock &block);
|
||||
|
||||
@@ -401,10 +403,11 @@ JackOutput::Connect()
|
||||
jack_on_shutdown(client, mpd_jack_shutdown, this);
|
||||
|
||||
for (unsigned i = 0; i < num_source_ports; ++i) {
|
||||
unsigned long portflags = JackPortIsOutput | JackPortIsTerminal;
|
||||
ports[i] = jack_port_register(client,
|
||||
source_ports[i].c_str(),
|
||||
JACK_DEFAULT_AUDIO_TYPE,
|
||||
JackPortIsOutput, 0);
|
||||
portflags, 0);
|
||||
if (ports[i] == nullptr) {
|
||||
Disconnect();
|
||||
throw FormatRuntimeError("Cannot register output port \"%s\"",
|
||||
@@ -529,7 +532,10 @@ JackOutput::Start()
|
||||
jports = nullptr;
|
||||
}
|
||||
|
||||
AtScopeExit(jports) { free(jports); };
|
||||
AtScopeExit(jports) {
|
||||
if (jports != nullptr)
|
||||
jack_free(jports);
|
||||
};
|
||||
|
||||
assert(num_dports > 0);
|
||||
|
||||
@@ -540,7 +546,7 @@ JackOutput::Start()
|
||||
std::fill(dports + num_dports, dports + audio_format.channels,
|
||||
dports[0]);
|
||||
} else if (num_dports > audio_format.channels) {
|
||||
if (audio_format.channels == 1 && num_dports > 2) {
|
||||
if (audio_format.channels == 1 && num_dports >= 2) {
|
||||
/* mono input file: connect the one source
|
||||
channel to the both destination channels */
|
||||
duplicate_port = dports[1];
|
||||
@@ -602,7 +608,7 @@ JackOutput::WriteSamples(const float *src, size_t n_frames)
|
||||
const unsigned n_channels = audio_format.channels;
|
||||
|
||||
float *dest[MAX_CHANNELS];
|
||||
size_t space = -1;
|
||||
size_t space = SIZE_MAX;
|
||||
for (unsigned i = 0; i < n_channels; ++i) {
|
||||
jack_ringbuffer_data_t d[2];
|
||||
jack_ringbuffer_get_write_vector(ringbuffer[i], d);
|
||||
|
@@ -650,7 +650,7 @@ PulseOutput::Open(AudioFormat &audio_format)
|
||||
break;
|
||||
}
|
||||
|
||||
ss.rate = audio_format.sample_rate;
|
||||
ss.rate = std::min(audio_format.sample_rate, PA_RATE_MAX);
|
||||
ss.channels = audio_format.channels;
|
||||
|
||||
/* create a stream .. */
|
||||
|
@@ -383,6 +383,7 @@ ShoutOutput::SendTag(const Tag &tag)
|
||||
shout_tag_to_metadata(tag, song, sizeof(song));
|
||||
|
||||
shout_metadata_add(meta, "song", song);
|
||||
shout_metadata_add(meta, "charset", "UTF-8");
|
||||
if (SHOUTERR_SUCCESS != shout_set_metadata(shout_conn, meta)) {
|
||||
LogWarning(shout_output_domain,
|
||||
"error setting shout metadata");
|
||||
|
@@ -22,7 +22,6 @@
|
||||
#include "system/FileDescriptor.hxx"
|
||||
#include "system/Error.hxx"
|
||||
|
||||
#include <sys/stropts.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
@@ -31,11 +30,18 @@
|
||||
|
||||
#ifdef __sun
|
||||
#include <sys/audio.h>
|
||||
#include <sys/stropts.h>
|
||||
#else
|
||||
|
||||
/* some fake declarations that allow build this plugin on systems
|
||||
other than Solaris, just to see if it compiles */
|
||||
|
||||
#include <sys/ioctl.h>
|
||||
|
||||
#ifndef I_FLUSH
|
||||
#define I_FLUSH 0
|
||||
#endif
|
||||
|
||||
#define AUDIO_GETINFO 0
|
||||
#define AUDIO_SETINFO 0
|
||||
#define AUDIO_ENCODING_LINEAR 0
|
||||
|
@@ -21,6 +21,8 @@
|
||||
#include "ConfiguredResampler.hxx"
|
||||
#include "util/ConstBuffer.hxx"
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
void
|
||||
|
@@ -46,6 +46,7 @@
|
||||
#include "CrossFade.hxx"
|
||||
#include "tag/Tag.hxx"
|
||||
#include "Idle.hxx"
|
||||
#include "util/Compiler.h"
|
||||
#include "util/Domain.hxx"
|
||||
#include "thread/Name.hxx"
|
||||
#include "Log.hxx"
|
||||
@@ -599,6 +600,19 @@ Player::SeekDecoder() noexcept
|
||||
{
|
||||
assert(pc.next_song != nullptr);
|
||||
|
||||
if (pc.seek_time > SongTime::zero() && // TODO: allow this only if the song duration is known
|
||||
dc.IsUnseekableCurrentSong(*pc.next_song)) {
|
||||
/* seeking into the current song; but we already know
|
||||
it's not seekable, so let's fail early */
|
||||
/* note the seek_time>0 check: if seeking to the
|
||||
beginning, we can simply restart the decoder */
|
||||
pc.next_song.reset();
|
||||
pc.SetError(PlayerError::DECODER,
|
||||
std::make_exception_ptr(std::runtime_error("Not seekable")));
|
||||
pc.CommandFinished();
|
||||
return true;
|
||||
}
|
||||
|
||||
CancelPendingSeek();
|
||||
|
||||
{
|
||||
@@ -1158,6 +1172,7 @@ try {
|
||||
}
|
||||
|
||||
/* fall through */
|
||||
gcc_fallthrough;
|
||||
|
||||
case PlayerCommand::PAUSE:
|
||||
next_song.reset();
|
||||
|
@@ -25,6 +25,9 @@
|
||||
#include "util/UriUtil.hxx"
|
||||
#include "song/DetachedSong.hxx"
|
||||
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
|
||||
#include <string.h>
|
||||
|
||||
static void
|
||||
@@ -66,6 +69,22 @@ playlist_check_translate_song(DetachedSong &song, const char *base_uri,
|
||||
base_uri = nullptr;
|
||||
|
||||
const char *uri = song.GetURI();
|
||||
|
||||
#ifdef _WIN32
|
||||
if (!PathTraitsUTF8::IsAbsolute(uri) && strchr(uri, '\\') != nullptr) {
|
||||
/* Windows uses the backslash as path separator, but
|
||||
the MPD protocol uses the (forward) slash by
|
||||
definition; to allow backslashes in relative URIs
|
||||
loaded from playlist files, this step converts all
|
||||
backslashes to (forward) slashes */
|
||||
|
||||
std::string new_uri(uri);
|
||||
std::replace(new_uri.begin(), new_uri.end(), '\\', '/');
|
||||
song.SetURI(std::move(new_uri));
|
||||
uri = song.GetURI();
|
||||
}
|
||||
#endif
|
||||
|
||||
if (base_uri != nullptr && !uri_has_scheme(uri) &&
|
||||
!PathTraitsUTF8::IsAbsolute(uri))
|
||||
song.SetURI(PathTraitsUTF8::Build(base_uri, uri));
|
||||
|
@@ -175,5 +175,5 @@ SignedSongTime
|
||||
ParseCommandArgSignedSongTime(const char *s)
|
||||
{
|
||||
auto value = ParseCommandArgFloat(s);
|
||||
return SongTime::FromS(value);
|
||||
return SignedSongTime::FromS(value);
|
||||
}
|
||||
|
@@ -353,7 +353,7 @@ playlist::MoveRange(PlayerControl &pc, unsigned start, unsigned end, int to)
|
||||
return;
|
||||
to = (currentSong + abs(to)) % GetLength();
|
||||
if (start < (unsigned)to)
|
||||
to--;
|
||||
to -= end - start;
|
||||
}
|
||||
|
||||
queue.MoveRange(start, end, to);
|
||||
|
@@ -29,8 +29,9 @@
|
||||
#include "AudioParser.hxx"
|
||||
#include "tag/ParseName.hxx"
|
||||
#include "tag/Tag.hxx"
|
||||
#include "time/ChronoUtil.hxx"
|
||||
#include "time/ISO8601.hxx"
|
||||
#include "util/CharUtil.hxx"
|
||||
#include "util/ChronoUtil.hxx"
|
||||
#include "util/ConstBuffer.hxx"
|
||||
#include "util/RuntimeError.hxx"
|
||||
#include "util/StringAPI.hxx"
|
||||
@@ -38,7 +39,6 @@
|
||||
#include "util/StringStrip.hxx"
|
||||
#include "util/StringView.hxx"
|
||||
#include "util/ASCII.hxx"
|
||||
#include "util/TimeISO8601.hxx"
|
||||
#include "util/UriUtil.hxx"
|
||||
#include "lib/icu/CaseFold.hxx"
|
||||
|
||||
@@ -113,14 +113,19 @@ ParseTimeStamp(const char *s)
|
||||
{
|
||||
assert(s != nullptr);
|
||||
|
||||
char *endptr;
|
||||
unsigned long long value = strtoull(s, &endptr, 10);
|
||||
if (*endptr == 0 && endptr > s)
|
||||
/* it's an integral UNIX time stamp */
|
||||
return std::chrono::system_clock::from_time_t((time_t)value);
|
||||
try {
|
||||
/* try ISO 8601 */
|
||||
return ParseISO8601(s).first;
|
||||
} catch (...) {
|
||||
char *endptr;
|
||||
unsigned long long value = strtoull(s, &endptr, 10);
|
||||
if (*endptr == 0 && endptr > s)
|
||||
/* it's an integral UNIX time stamp */
|
||||
return std::chrono::system_clock::from_time_t((time_t)value);
|
||||
|
||||
/* try ISO 8601 */
|
||||
return ParseISO8601(s);
|
||||
/* rethrow the ParseISO8601() error */
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
static constexpr bool
|
||||
|
@@ -19,7 +19,8 @@
|
||||
|
||||
#include "ModifiedSinceSongFilter.hxx"
|
||||
#include "LightSong.hxx"
|
||||
#include "util/TimeISO8601.hxx"
|
||||
#include "time/ISO8601.hxx"
|
||||
#include "util/StringBuffer.hxx"
|
||||
|
||||
std::string
|
||||
ModifiedSinceSongFilter::ToExpression() const noexcept
|
||||
|
@@ -24,6 +24,7 @@ song_dep = declare_dependency(
|
||||
icu_dep,
|
||||
pcre_dep,
|
||||
tag_dep,
|
||||
time_dep,
|
||||
util_dep,
|
||||
],
|
||||
)
|
||||
|
@@ -25,21 +25,23 @@
|
||||
#include "lib/curl/Init.hxx"
|
||||
#include "lib/curl/Global.hxx"
|
||||
#include "lib/curl/Slist.hxx"
|
||||
#include "lib/curl/String.hxx"
|
||||
#include "lib/curl/Request.hxx"
|
||||
#include "lib/curl/Handler.hxx"
|
||||
#include "lib/curl/Escape.hxx"
|
||||
#include "lib/expat/ExpatParser.hxx"
|
||||
#include "fs/Traits.hxx"
|
||||
#include "event/Call.hxx"
|
||||
#include "event/DeferEvent.hxx"
|
||||
#include "thread/Mutex.hxx"
|
||||
#include "thread/Cond.hxx"
|
||||
#include "time/ChronoUtil.hxx"
|
||||
#include "time/Parser.hxx"
|
||||
#include "util/ASCII.hxx"
|
||||
#include "util/ChronoUtil.hxx"
|
||||
#include "util/IterableSplitString.hxx"
|
||||
#include "time/ChronoUtil.hxx"
|
||||
#include "util/RuntimeError.hxx"
|
||||
#include "util/StringCompare.hxx"
|
||||
#include "util/StringFormat.hxx"
|
||||
#include "util/TimeParser.hxx"
|
||||
#include "util/UriUtil.hxx"
|
||||
|
||||
#include <algorithm>
|
||||
@@ -77,26 +79,15 @@ CurlStorage::MapUTF8(const char *uri_utf8) const noexcept
|
||||
if (StringIsEmpty(uri_utf8))
|
||||
return base;
|
||||
|
||||
CurlEasy easy;
|
||||
std::string path_esc;
|
||||
|
||||
for (auto elt: IterableSplitString(uri_utf8, '/')) {
|
||||
char *elt_esc = easy.Escape(elt.data, elt.size);
|
||||
if (!path_esc.empty())
|
||||
path_esc.push_back('/');
|
||||
path_esc += elt_esc;
|
||||
curl_free(elt_esc);
|
||||
}
|
||||
|
||||
std::string path_esc = CurlEscapeUriPath(uri_utf8);
|
||||
return PathTraitsUTF8::Build(base.c_str(), path_esc.c_str());
|
||||
}
|
||||
|
||||
const char *
|
||||
CurlStorage::MapToRelativeUTF8(const char *uri_utf8) const noexcept
|
||||
{
|
||||
// TODO: escape/unescape?
|
||||
|
||||
return PathTraitsUTF8::Relative(base.c_str(), uri_utf8);
|
||||
return PathTraitsUTF8::Relative(base.c_str(),
|
||||
CurlUnescape(uri_utf8).c_str());
|
||||
}
|
||||
|
||||
class BlockingHttpRequest : protected CurlResponseHandler {
|
||||
@@ -118,7 +109,9 @@ public:
|
||||
BIND_THIS_METHOD(OnDeferredStart)),
|
||||
request(curl, uri, *this) {
|
||||
// TODO: use CurlInputStream's configuration
|
||||
}
|
||||
|
||||
void DeferStart() noexcept {
|
||||
/* start the transfer inside the IOThread */
|
||||
defer_start.Schedule();
|
||||
}
|
||||
@@ -132,6 +125,10 @@ public:
|
||||
std::rethrow_exception(postponed_error);
|
||||
}
|
||||
|
||||
CURL *GetEasy() noexcept {
|
||||
return request.Get();
|
||||
}
|
||||
|
||||
protected:
|
||||
void SetDone() {
|
||||
assert(!done);
|
||||
@@ -269,6 +266,8 @@ public:
|
||||
CommonExpatParser(ExpatNamespaceSeparator{'|'})
|
||||
{
|
||||
request.SetOption(CURLOPT_CUSTOMREQUEST, "PROPFIND");
|
||||
request.SetOption(CURLOPT_FOLLOWLOCATION, 1l);
|
||||
request.SetOption(CURLOPT_MAXREDIRS, 1l);
|
||||
|
||||
request_headers.Append(StringFormat<40>("depth: %u", depth));
|
||||
|
||||
@@ -277,6 +276,7 @@ public:
|
||||
request.SetOption(CURLOPT_POSTFIELDS,
|
||||
"<?xml version=\"1.0\"?>\n"
|
||||
"<a:propfind xmlns:a=\"DAV:\">"
|
||||
"<a:prop><a:resourcetype/></a:prop>"
|
||||
"<a:prop><a:getcontenttype/></a:prop>"
|
||||
"<a:prop><a:getcontentlength/></a:prop>"
|
||||
"</a:propfind>");
|
||||
@@ -284,6 +284,8 @@ public:
|
||||
// TODO: send request body
|
||||
}
|
||||
|
||||
using BlockingHttpRequest::GetEasy;
|
||||
using BlockingHttpRequest::DeferStart;
|
||||
using BlockingHttpRequest::Wait;
|
||||
|
||||
protected:
|
||||
@@ -431,6 +433,7 @@ public:
|
||||
}
|
||||
|
||||
const StorageFileInfo &Perform() {
|
||||
DeferStart();
|
||||
Wait();
|
||||
return info;
|
||||
}
|
||||
@@ -454,9 +457,7 @@ CurlStorage::GetInfo(const char *uri_utf8, gcc_unused bool follow)
|
||||
{
|
||||
// TODO: escape the given URI
|
||||
|
||||
std::string uri = base;
|
||||
uri += uri_utf8;
|
||||
|
||||
const auto uri = MapUTF8(uri_utf8);
|
||||
return HttpGetInfoOperation(*curl, uri.c_str()).Perform();
|
||||
}
|
||||
|
||||
@@ -484,6 +485,7 @@ public:
|
||||
base_path(UriPathOrSlash(uri)) {}
|
||||
|
||||
std::unique_ptr<StorageDirectoryReader> Perform() {
|
||||
DeferStart();
|
||||
Wait();
|
||||
return ToReader();
|
||||
}
|
||||
@@ -503,7 +505,11 @@ private:
|
||||
if (path == nullptr)
|
||||
return nullptr;
|
||||
|
||||
path = StringAfterPrefix(path, base_path.c_str());
|
||||
/* kludge: ignoring case in this comparison to avoid
|
||||
false negatives if the web server uses a different
|
||||
case in hex digits in escaped characters; TODO:
|
||||
implement properly */
|
||||
path = StringAfterPrefixIgnoreCase(path, base_path.c_str());
|
||||
if (path == nullptr || *path == 0)
|
||||
return nullptr;
|
||||
|
||||
@@ -529,10 +535,7 @@ protected:
|
||||
if (escaped_name.IsNull())
|
||||
return;
|
||||
|
||||
// TODO: unescape
|
||||
const auto name = escaped_name;
|
||||
|
||||
entries.emplace_front(std::string(name.data, name.size));
|
||||
entries.emplace_front(CurlUnescape(GetEasy(), escaped_name));
|
||||
|
||||
auto &info = entries.front().info;
|
||||
info = StorageFileInfo(r.collection
|
||||
@@ -546,10 +549,7 @@ protected:
|
||||
std::unique_ptr<StorageDirectoryReader>
|
||||
CurlStorage::OpenDirectory(const char *uri_utf8)
|
||||
{
|
||||
// TODO: escape the given URI
|
||||
|
||||
std::string uri = base;
|
||||
uri += uri_utf8;
|
||||
std::string uri = MapUTF8(uri_utf8);
|
||||
|
||||
/* collection URIs must end with a slash */
|
||||
if (uri.back() != '/')
|
||||
|
@@ -57,5 +57,6 @@ storage_plugins_dep = declare_dependency(
|
||||
dependencies: [
|
||||
storage_api_dep,
|
||||
fs_dep,
|
||||
time_dep,
|
||||
],
|
||||
)
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2018 Max Kellermann <max.kellermann@gmail.com>
|
||||
* Copyright 2013-2019 Max Kellermann <max.kellermann@gmail.com>
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2013-2015 Max Kellermann <max.kellermann@gmail.com>
|
||||
* Copyright 2013-2015 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
|
||||
@@ -147,6 +147,26 @@ FormatErrno(const char *fmt, Args&&... args) noexcept
|
||||
return FormatErrno(errno, fmt, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
static inline std::system_error
|
||||
FormatFileNotFound(const char *fmt, Args&&... args) noexcept
|
||||
{
|
||||
#ifdef _WIN32
|
||||
return FormatLastError(ERROR_FILE_NOT_FOUND, fmt,
|
||||
std::forward<Args>(args)...);
|
||||
#else
|
||||
return FormatErrno(ENOENT, fmt, std::forward<Args>(args)...);
|
||||
#endif
|
||||
}
|
||||
|
||||
gcc_pure
|
||||
inline bool
|
||||
IsErrno(const std::system_error &e, int code) noexcept
|
||||
{
|
||||
return e.code().category() == ErrnoCategory() &&
|
||||
e.code().value() == code;
|
||||
}
|
||||
|
||||
gcc_pure
|
||||
static inline bool
|
||||
IsFileNotFound(const std::system_error &e) noexcept
|
||||
@@ -155,8 +175,7 @@ IsFileNotFound(const std::system_error &e) noexcept
|
||||
return e.code().category() == std::system_category() &&
|
||||
e.code().value() == ERROR_FILE_NOT_FOUND;
|
||||
#else
|
||||
return e.code().category() == ErrnoCategory() &&
|
||||
e.code().value() == ENOENT;
|
||||
return IsErrno(e, ENOENT);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -168,8 +187,7 @@ IsPathNotFound(const std::system_error &e) noexcept
|
||||
return e.code().category() == std::system_category() &&
|
||||
e.code().value() == ERROR_PATH_NOT_FOUND;
|
||||
#else
|
||||
return e.code().category() == ErrnoCategory() &&
|
||||
e.code().value() == ENOTDIR;
|
||||
return IsErrno(e, ENOTDIR);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -181,8 +199,7 @@ IsAccessDenied(const std::system_error &e) noexcept
|
||||
return e.code().category() == std::system_category() &&
|
||||
e.code().value() == ERROR_ACCESS_DENIED;
|
||||
#else
|
||||
return e.code().category() == ErrnoCategory() &&
|
||||
e.code().value() == EACCES;
|
||||
return IsErrno(e, EACCES);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
@@ -20,9 +20,9 @@
|
||||
#include "Format.hxx"
|
||||
#include "Tag.hxx"
|
||||
#include "ParseName.hxx"
|
||||
#include "time/Convert.hxx"
|
||||
#include "util/format.h"
|
||||
#include "util/TruncateString.hxx"
|
||||
#include "util/TimeConvert.hxx"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
|
@@ -48,6 +48,7 @@ tag = static_library(
|
||||
tag_dep = declare_dependency(
|
||||
link_with: tag,
|
||||
dependencies: [
|
||||
time_dep,
|
||||
util_dep,
|
||||
],
|
||||
)
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user