Compare commits
160 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
ab197b6d43 | ||
![]() |
16b0e53a36 | ||
![]() |
bc14a6038e | ||
![]() |
626329a1cc | ||
![]() |
8bf250c228 | ||
![]() |
62127bbb12 | ||
![]() |
786ac87b76 | ||
![]() |
c76f4ac89b | ||
![]() |
d495ec71a8 | ||
![]() |
b763852f57 | ||
![]() |
6522d2f722 | ||
![]() |
ac61fd1d78 | ||
![]() |
c44d1566fa | ||
![]() |
80dc7c2f74 | ||
![]() |
7b94f0e36b | ||
![]() |
504e8d564a | ||
![]() |
ac395429c3 | ||
![]() |
388768b3a6 | ||
![]() |
5c4169e64e | ||
![]() |
d40e9de2d2 | ||
![]() |
1e54297be8 | ||
![]() |
44b200240f | ||
![]() |
a2340c313f | ||
![]() |
37b07a5e7c | ||
![]() |
73013a3c04 | ||
![]() |
e8099f01b5 | ||
![]() |
672bdd3a56 | ||
![]() |
c2c2c29658 | ||
![]() |
c745e14f47 | ||
![]() |
e8f08cda53 | ||
![]() |
8266ab5588 | ||
![]() |
ea552208fc | ||
![]() |
e86015a72a | ||
![]() |
cf7ec2c9d3 | ||
![]() |
dadd3ca671 | ||
![]() |
79535212c8 | ||
![]() |
ef5f96a193 | ||
![]() |
418f71ec0f | ||
![]() |
0ebeaa9ac2 | ||
![]() |
25cd47b8dc | ||
![]() |
cd48d981b5 | ||
![]() |
774d26b982 | ||
![]() |
f3e683bd6f | ||
![]() |
50ce0c0d9d | ||
![]() |
5b80711d75 | ||
![]() |
666e456551 | ||
![]() |
31794ac376 | ||
![]() |
2141fdf06e | ||
![]() |
3f3e0739c4 | ||
![]() |
ebed7e2147 | ||
![]() |
53f5d4c710 | ||
![]() |
139a4054c5 | ||
![]() |
a4de96508d | ||
![]() |
a7582aaf15 | ||
![]() |
c5c1c64a81 | ||
![]() |
992c52ce7f | ||
![]() |
026aef7465 | ||
![]() |
b53a23b51b | ||
![]() |
2aad015392 | ||
![]() |
986ec877b0 | ||
![]() |
c43ea74b30 | ||
![]() |
79981f3cda | ||
![]() |
c2940a8385 | ||
![]() |
bede564618 | ||
![]() |
e0ca4b865a | ||
![]() |
31c206bf80 | ||
![]() |
9187a08106 | ||
![]() |
3859a50466 | ||
![]() |
927071e085 | ||
![]() |
6ba918b203 | ||
![]() |
e8b70dbca4 | ||
![]() |
0f8d223c7f | ||
![]() |
19a2885fd5 | ||
![]() |
b8a094470b | ||
![]() |
2988bb77e8 | ||
![]() |
738317bf34 | ||
![]() |
e46fbd0780 | ||
![]() |
56b74ad990 | ||
![]() |
6de92bb42b | ||
![]() |
c801936e53 | ||
![]() |
817656504d | ||
![]() |
6f00f97b66 | ||
![]() |
5acb978f8f | ||
![]() |
975a4ae871 | ||
![]() |
56aaf3c73e | ||
![]() |
12fd1cad0c | ||
![]() |
e573cbf032 | ||
![]() |
dead461542 | ||
![]() |
3d5da1ac73 | ||
![]() |
ec408ca6a6 | ||
![]() |
ea66cdd6a5 | ||
![]() |
f762e8034f | ||
![]() |
bb1e369f30 | ||
![]() |
8376578921 | ||
![]() |
ed2354cd9d | ||
![]() |
386688b87a | ||
![]() |
38d56dddf1 | ||
![]() |
e8975942ec | ||
![]() |
3ca80a7336 | ||
![]() |
d029dae7ad | ||
![]() |
9e058732ee | ||
![]() |
cad5d11261 | ||
![]() |
fcaedec2ab | ||
![]() |
ead9d59e88 | ||
![]() |
34b8a17ccd | ||
![]() |
a53d081c39 | ||
![]() |
823134e4ba | ||
![]() |
272167b4fc | ||
![]() |
92f09bba94 | ||
![]() |
1f50bdb230 | ||
![]() |
2eef4e6716 | ||
![]() |
d989dbfec4 | ||
![]() |
ca9fcec364 | ||
![]() |
354104f9a9 | ||
![]() |
8649ea3d6f | ||
![]() |
752ff12c37 | ||
![]() |
4bb89b1755 | ||
![]() |
0ef553d30e | ||
![]() |
43a62aef07 | ||
![]() |
ed4d0aa909 | ||
![]() |
023ce4e720 | ||
![]() |
368d9359dd | ||
![]() |
d98c19d561 | ||
![]() |
cab77e35e0 | ||
![]() |
e3e90b4b93 | ||
![]() |
f8c69893e1 | ||
![]() |
49678a0893 | ||
![]() |
d667b5b48c | ||
![]() |
9cba55b39c | ||
![]() |
c2cbb7b8ce | ||
![]() |
8217d75ca1 | ||
![]() |
1ca70d9759 | ||
![]() |
4303aaa9b8 | ||
![]() |
7b56bae289 | ||
![]() |
4183416b3e | ||
![]() |
a60dee57ce | ||
![]() |
5724656acb | ||
![]() |
329f9cd9fe | ||
![]() |
fbdb8b406e | ||
![]() |
85d0bbd957 | ||
![]() |
414f00d6ae | ||
![]() |
17b0add058 | ||
![]() |
c68ed40661 | ||
![]() |
ff624075a8 | ||
![]() |
08db28469d | ||
![]() |
a20b326807 | ||
![]() |
4db1b1b250 | ||
![]() |
ff6b263b48 | ||
![]() |
c0bf052fa9 | ||
![]() |
5419cff925 | ||
![]() |
eee10ad2ed | ||
![]() |
98472a8104 | ||
![]() |
d094c168aa | ||
![]() |
4b18460bc6 | ||
![]() |
412c0a965c | ||
![]() |
2becf79223 | ||
![]() |
43ec96d4a0 | ||
![]() |
3d1d779da7 | ||
![]() |
c88056ba83 | ||
![]() |
e769751221 |
AUTHORSMakefile.amNEWS
android
configure.acdoc
python/build
src
IOThread.cxxIOThread.hxxMain.cxxMusicPipe.hxxSongFilter.cxxSongFilter.hxxSongSave.cxx
archive
command
db
decoder
encoder
plugins
event
filter
input
lib
curl
ffmpeg
icu
nfs
upnp
mixer
plugins
neighbor
plugins
net
output
pcm
player
playlist
protocol
queue
storage
tag
thread
util
test
win32
5
AUTHORS
5
AUTHORS
@@ -31,3 +31,8 @@ The following people have contributed code to MPD:
|
||||
Jean-Francois Dockes <jf@dockes.org>
|
||||
Yue Wang <yuleopen@gmail.com>
|
||||
Matthew Leon Grinshpun <ml@matthewleon.com>
|
||||
Dimitris Papastamos <sin@2f30.org>
|
||||
Florian Schlichting <fsfs@debian.org>
|
||||
François Revol <revol@free.fr>
|
||||
Jacob Vosmaer <contact@jacobvosmaer.nl>
|
||||
Thomas Guillem <thomas@gllm.fr>
|
||||
|
86
Makefile.am
86
Makefile.am
@@ -61,8 +61,8 @@ src_mpd_LDADD = \
|
||||
libnet.a \
|
||||
$(FS_LIBS) \
|
||||
libsystem.a \
|
||||
libutil.a \
|
||||
$(ICU_LDADD) \
|
||||
libutil.a \
|
||||
$(SYSTEMD_DAEMON_LIBS)
|
||||
|
||||
src_mpd_SOURCES = \
|
||||
@@ -285,30 +285,62 @@ libmain_a_CPPFLAGS = $(AM_CPPFLAGS) -Iandroid/build/include
|
||||
|
||||
src_mpd_LDADD += libandroid.a libjava.a
|
||||
|
||||
all-local: android/build/bin/$(APK_NAME)-debug.apk
|
||||
all-local: android/build/$(APK_NAME)-debug.apk
|
||||
clean-local:
|
||||
rm -rf android/build
|
||||
|
||||
libmpd.so: $(filter %.a,$(src_mpd_LDADD)) libmain.a
|
||||
$(AM_V_CXXLD)$(CXXLD) -shared -Wl,--no-undefined,-shared,-Bsymbolic -llog -lz -o $@ $(AM_CXXFLAGS) $(CXXFLAGS) $(LDFLAGS) src/libmain_a-Main.o $(src_mpd_LDADD) $(LIBS)
|
||||
|
||||
android/build/build.xml: android/AndroidManifest.xml
|
||||
rm -rf android/build
|
||||
mkdir -p android/build/include android/build/res android/build/src/org
|
||||
ln -s $(abs_srcdir)/android/AndroidManifest.xml $(abs_srcdir)/android/custom_rules.xml android/build
|
||||
ln -s $(abs_srcdir)/android/src android/build/src/org/musicpd
|
||||
ln -s $(abs_srcdir)/android/res/values $(abs_srcdir)/android/res/layout android/build/res
|
||||
$(ANDROID_SDK)/tools/android update project --path android/build --target android-17 --name $(APK_NAME)
|
||||
ANDROID_SDK_BUILD_TOOLS_VERSION = 27.0.0
|
||||
ANDROID_SDK_PLATFORM = android-17
|
||||
|
||||
android/build/bin/classes/org/musicpd/Bridge.class: android/src/Bridge.java android/build/build.xml android/build/res/drawable/icon.png
|
||||
cd android/build && ant compile-jni-classes
|
||||
ANDROID_BUILD_TOOLS_DIR = $(ANDROID_SDK)/build-tools/$(ANDROID_SDK_BUILD_TOOLS_VERSION)
|
||||
ANDROID_SDK_PLATFORM_DIR = $(ANDROID_SDK)/platforms/$(ANDROID_SDK_PLATFORM)
|
||||
|
||||
android/build/include/org_musicpd_Bridge.h: android/build/bin/classes/org/musicpd/Bridge.class
|
||||
javah -classpath $(ANDROID_SDK)/platforms/android-17/android.jar:android/build/bin/classes -d $(@D) org.musicpd.Bridge
|
||||
JAVAC = javac
|
||||
AAPT = $(ANDROID_BUILD_TOOLS_DIR)/aapt
|
||||
DX = $(ANDROID_BUILD_TOOLS_DIR)/dx
|
||||
ZIPALIGN = $(ANDROID_BUILD_TOOLS_DIR)/zipalign
|
||||
|
||||
ANDROID_XML_RES := $(wildcard $(srcdir)/android/res/*/*.xml)
|
||||
ANDROID_XML_RES_COPIES := $(patsubst $(srcdir)/android/%,android/build/%,$(ANDROID_XML_RES))
|
||||
|
||||
JAVA_SOURCE_NAMES = Bridge.java Loader.java Main.java
|
||||
JAVA_SOURCE_PATHS = $(addprefix $(srcdir)/android/src/,$(JAVA_SOURCE_NAMES))
|
||||
|
||||
JAVA_CLASSFILES_DIR = android/build/classes
|
||||
|
||||
$(ANDROID_XML_RES_COPIES): $(ANDROID_XML_RES)
|
||||
@$(MKDIR_P) $(dir $@)
|
||||
cp $(patsubst android/build/%,$(srcdir)/android/%,$@) $@
|
||||
|
||||
android/build/resources.apk: $(ANDROID_XML_RES_COPIES) android/build/res/drawable/icon.png
|
||||
@$(MKDIR_P) android/build/gen
|
||||
$(AAPT) package -f -m --auto-add-overlay \
|
||||
--custom-package org.musicpd \
|
||||
-M $(srcdir)/android/AndroidManifest.xml \
|
||||
-S android/build/res \
|
||||
-J android/build/gen \
|
||||
-I $(ANDROID_SDK_PLATFORM_DIR)/android.jar \
|
||||
-F android/build/resources.apk
|
||||
|
||||
# R.java is generated by aapt, when resources.apk is generated
|
||||
android/build/gen/org/musicpd/R.java: android/build/resources.apk
|
||||
|
||||
android/build/classes.dex: $(JAVA_SOURCE_PATHS) android/build/gen/org/musicpd/R.java
|
||||
@$(MKDIR_P) $(JAVA_CLASSFILES_DIR)
|
||||
$(JAVAC) -source 1.6 -target 1.6 -Xlint:-options \
|
||||
-cp $(ANDROID_SDK_PLATFORM_DIR)/android.jar:$(JAVA_CLASSFILES_DIR) \
|
||||
-d $(JAVA_CLASSFILES_DIR) $^
|
||||
$(DX) --dex --output $@ $(JAVA_CLASSFILES_DIR)
|
||||
|
||||
android/build/include/org_musicpd_Bridge.h: android/build/classes.dex
|
||||
javah -classpath $(ANDROID_SDK_PLATFORM_DIR)/android.jar:$(JAVA_CLASSFILES_DIR) -d $(@D) org.musicpd.Bridge
|
||||
|
||||
BUILT_SOURCES = android/build/include/org_musicpd_Bridge.h
|
||||
|
||||
android/build/libs/armeabi-v7a/libmpd.so: libmpd.so android/build/build.xml
|
||||
android/build/lib/armeabi-v7a/libmpd.so: libmpd.so
|
||||
mkdir -p $(@D)
|
||||
rm -f $@
|
||||
$(STRIP) -o $@ $<
|
||||
@@ -317,24 +349,19 @@ android/build/res/drawable/icon.png: mpd.svg
|
||||
mkdir -p $(@D)
|
||||
rsvg-convert --width=48 --height=48 $< -o $@
|
||||
|
||||
APK_DEPS = android/build/res/drawable/icon.png \
|
||||
android/build/libs/armeabi-v7a/libmpd.so \
|
||||
$(wildcard $(srcdir)/android/src/*.java) \
|
||||
android/build/build.xml
|
||||
.DELETE_ON_ERROR: android/build/unsigned.apk
|
||||
android/build/unsigned.apk: android/build/classes.dex android/build/resources.apk android/build/lib/armeabi-v7a/libmpd.so
|
||||
cp android/build/resources.apk $@
|
||||
cd $(dir $@) && zip -q -r $(notdir $@) classes.dex lib
|
||||
|
||||
android/build/bin/$(APK_NAME)-debug.apk: $(APK_DEPS)
|
||||
cd android/build && ant nodeps debug
|
||||
android/build/$(APK_NAME)-debug.apk: android/build/unsigned.apk
|
||||
jarsigner -keystore $(HOME)/.android/debug.keystore -storepass android -signedjar $@ $< androiddebugkey
|
||||
|
||||
android/build/bin/$(APK_NAME)-release-unsigned.apk: $(APK_DEPS)
|
||||
cd android/build && ant nodeps release
|
||||
|
||||
android/build/bin/$(APK_NAME)-release-unaligned.apk: android/build/bin/$(APK_NAME)-release-unsigned.apk
|
||||
android/build/$(APK_NAME)-release-unaligned.apk: android/build/unsigned.apk
|
||||
jarsigner -digestalg SHA1 -sigalg MD5withRSA -storepass:env ANDROID_KEYSTORE_PASS -keystore $(ANDROID_KEYSTORE) -signedjar $@ $< $(ANDROID_KEY_ALIAS)
|
||||
|
||||
ANDROID_SDK_BUILD_TOOLS_VERSION = 20.0.0
|
||||
|
||||
android/build/bin/$(APK_NAME).apk: android/build/bin/$(APK_NAME)-release-unaligned.apk
|
||||
$(ANDROID_SDK)/build-tools/$(ANDROID_SDK_BUILD_TOOLS_VERSION)/zipalign -f 4 $< $@
|
||||
android/build/$(APK_NAME).apk: android/build/$(APK_NAME)-release-unaligned.apk
|
||||
$(ZIPALIGN) -f 4 $< $@
|
||||
|
||||
endif
|
||||
|
||||
@@ -470,6 +497,7 @@ libthread_a_SOURCES = \
|
||||
|
||||
libnet_a_SOURCES = \
|
||||
src/net/Features.hxx \
|
||||
src/net/Init.hxx \
|
||||
src/net/ToString.cxx src/net/ToString.hxx \
|
||||
src/net/Resolver.cxx src/net/Resolver.hxx \
|
||||
src/net/StaticSocketAddress.cxx src/net/StaticSocketAddress.hxx \
|
||||
@@ -2132,6 +2160,7 @@ test_run_output_LDADD = $(MPD_LIBS) \
|
||||
libutil.a
|
||||
test_run_output_SOURCES = test/run_output.cxx \
|
||||
test/ScopeIOThread.hxx \
|
||||
test/NullMixerListener.hxx \
|
||||
src/Log.cxx src/LogBackend.cxx \
|
||||
src/IOThread.cxx \
|
||||
src/output/Domain.cxx \
|
||||
@@ -2155,6 +2184,7 @@ test_read_mixer_LDADD = \
|
||||
libsystem.a \
|
||||
libutil.a
|
||||
test_read_mixer_SOURCES = test/read_mixer.cxx \
|
||||
test/NullMixerListener.hxx \
|
||||
src/Log.cxx src/LogBackend.cxx \
|
||||
src/mixer/MixerControl.cxx \
|
||||
src/filter/FilterPlugin.cxx \
|
||||
|
73
NEWS
73
NEWS
@@ -1,3 +1,76 @@
|
||||
ver 0.20.20 (2018/05/22)
|
||||
* protocol
|
||||
- fix "modified-since" filter regression
|
||||
* output
|
||||
- pulse: cork stream when paused due to "single" mode
|
||||
* decoder
|
||||
- dsdiff, dsf: support more MIME types
|
||||
- dsdiff, dsf: allow 4 MB ID3 tags
|
||||
- opus: support R128_ALBUM_GAIN tag
|
||||
* Android, Windows
|
||||
- enable the "proxy" database plugin
|
||||
|
||||
ver 0.20.19 (2018/04/26)
|
||||
* protocol
|
||||
- validate absolute seek time, reject negative values
|
||||
* database
|
||||
- proxy: fix "search already in progress" errors
|
||||
- proxy: implement "list ... group"
|
||||
* input
|
||||
- mms: fix lockup bug and a crash bug
|
||||
* decoder
|
||||
- ffmpeg: fix av_register_all() deprecation warning (FFmpeg 4.0)
|
||||
* player
|
||||
- fix spurious "Not seekable" error when switching radio streams
|
||||
* macOS: fix crash bug
|
||||
|
||||
ver 0.20.18 (2018/02/24)
|
||||
* input
|
||||
- curl: allow authentication methods other than "Basic"
|
||||
* decoder
|
||||
- flac: improve seeking precision
|
||||
* fix gapless CUE song transitions
|
||||
* Android, Windows
|
||||
- enable the NFS storage plugin
|
||||
|
||||
ver 0.20.17 (2018/02/11)
|
||||
* output
|
||||
- alsa: fix crash bug with 8 channels
|
||||
* mixer
|
||||
- alsa: fix rounding error at volume 0
|
||||
* fix real-time and idle scheduling with Musl
|
||||
* Android
|
||||
- fix compatibility with Android 4.0
|
||||
|
||||
ver 0.20.16 (2018/02/03)
|
||||
* output
|
||||
- pulse: fix crash during auto-detection
|
||||
* database
|
||||
- simple: fix search within mount points
|
||||
- upnp: enable IPv6
|
||||
* archive
|
||||
- iso9660: libcdio 2.0 compatibility
|
||||
* fix crash in debug build on Haiku and other operating systems
|
||||
|
||||
ver 0.20.15 (2018/01/05)
|
||||
* queue: fix crash after seek failure
|
||||
* resampler
|
||||
- soxr: clear internal state after manual song change
|
||||
* state file
|
||||
- make mount point restore errors non-fatal
|
||||
- fix crash when restoring mounts with incompatible database plugin
|
||||
* Android
|
||||
- build without Ant
|
||||
- fix for SIGSYS crash
|
||||
|
||||
ver 0.20.14 (2018/01/01)
|
||||
* database
|
||||
- simple: fix file corruption in the presence of mount points
|
||||
* archive
|
||||
- bz2: fix deadlock
|
||||
- reduce lock contention, fixing lots of xrun problems
|
||||
* fix Solaris build failure
|
||||
|
||||
ver 0.20.13 (2017/12/18)
|
||||
* output
|
||||
- osx: set up ring buffer to hold at least 100ms
|
||||
|
@@ -2,10 +2,10 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="org.musicpd"
|
||||
android:installLocation="auto"
|
||||
android:versionCode="13"
|
||||
android:versionName="0.19.9">
|
||||
android:versionCode="19"
|
||||
android:versionName="0.20.20">
|
||||
|
||||
<uses-sdk android:minSdkVersion="9" android:targetSdkVersion="17"/>
|
||||
<uses-sdk android:minSdkVersion="14" android:targetSdkVersion="17"/>
|
||||
|
||||
<application android:icon="@drawable/icon" android:label="@string/app_name">
|
||||
<activity android:name=".Main"
|
||||
|
@@ -3,13 +3,14 @@
|
||||
import os, os.path
|
||||
import sys, subprocess
|
||||
|
||||
if len(sys.argv) < 3:
|
||||
print("Usage: build.py SDK_PATH NDK_PATH [configure_args...]", file=sys.stderr)
|
||||
if len(sys.argv) < 4:
|
||||
print("Usage: build.py SDK_PATH NDK_PATH ABI [configure_args...]", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
sdk_path = sys.argv[1]
|
||||
ndk_path = sys.argv[2]
|
||||
configure_args = sys.argv[3:]
|
||||
android_abi = sys.argv[3]
|
||||
configure_args = sys.argv[4:]
|
||||
|
||||
if not os.path.isfile(os.path.join(sdk_path, 'tools', 'android')):
|
||||
print("SDK not found in", ndk_path, file=sys.stderr)
|
||||
@@ -19,8 +20,27 @@ if not os.path.isdir(ndk_path):
|
||||
print("NDK not found in", ndk_path, file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
android_abis = {
|
||||
'armeabi-v7a': {
|
||||
'arch': 'arm-linux-androideabi',
|
||||
'ndk_arch': 'arm',
|
||||
'toolchain_arch': 'arm-linux-androideabi',
|
||||
'llvm_triple': 'armv7-none-linux-androideabi',
|
||||
'cflags': '-march=armv7-a -mfpu=vfp -mfloat-abi=softfp',
|
||||
},
|
||||
|
||||
'x86': {
|
||||
'arch': 'i686-linux-android',
|
||||
'ndk_arch': 'x86',
|
||||
'toolchain_arch': 'x86',
|
||||
'llvm_triple': 'i686-none-linux-android',
|
||||
'cflags': '-march=i686 -mtune=intel -mssse3 -mfpmath=sse -m32',
|
||||
},
|
||||
}
|
||||
|
||||
# select the NDK target
|
||||
arch = 'arm-linux-androideabi'
|
||||
abi_info = android_abis[android_abi]
|
||||
arch = abi_info['arch']
|
||||
|
||||
# the path to the MPD sources
|
||||
mpd_path = os.path.abspath(os.path.join(os.path.dirname(sys.argv[0]) or '.', '..'))
|
||||
@@ -44,16 +64,15 @@ class AndroidNdkToolchain:
|
||||
self.src_path = src_path
|
||||
self.build_path = build_path
|
||||
|
||||
self.ndk_arch = 'arm'
|
||||
android_abi = 'armeabi-v7a'
|
||||
ndk_platform = 'android-21'
|
||||
ndk_arch = abi_info['ndk_arch']
|
||||
ndk_platform = 'android-14'
|
||||
|
||||
# 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-' + self.ndk_arch)
|
||||
target_root = os.path.join(ndk_platform_path, 'arch-' + ndk_arch)
|
||||
|
||||
install_prefix = os.path.join(arch_path, 'root')
|
||||
|
||||
@@ -61,11 +80,13 @@ class AndroidNdkToolchain:
|
||||
self.install_prefix = install_prefix
|
||||
self.sysroot = sysroot
|
||||
|
||||
toolchain_path = os.path.join(ndk_path, 'toolchains', arch + '-' + gcc_version, 'prebuilt', build_arch)
|
||||
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 = 'armv7-none-linux-androideabi'
|
||||
llvm_triple = abi_info['llvm_triple']
|
||||
|
||||
common_flags = '-march=armv7-a -mfloat-abi=softfp'
|
||||
common_flags = '-Os -g'
|
||||
common_flags += ' -fPIC'
|
||||
common_flags += ' ' + abi_info['cflags']
|
||||
|
||||
toolchain_bin = os.path.join(toolchain_path, 'bin')
|
||||
llvm_bin = os.path.join(llvm_path, 'bin')
|
||||
@@ -73,17 +94,19 @@ class AndroidNdkToolchain:
|
||||
self.cxx = os.path.join(llvm_bin, 'clang++')
|
||||
common_flags += ' -target ' + llvm_triple + ' -integrated-as -gcc-toolchain ' + toolchain_path
|
||||
|
||||
common_flags += ' -fvisibility=hidden -fdata-sections -ffunction-sections'
|
||||
|
||||
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')
|
||||
self.strip = os.path.join(toolchain_bin, arch + '-strip')
|
||||
|
||||
self.cflags = '-Os -g ' + common_flags
|
||||
self.cxxflags = '-Os -g ' + common_flags
|
||||
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__=21'
|
||||
' -D__ANDROID_API__=14'
|
||||
self.ldflags = '--sysroot=' + sysroot + \
|
||||
' -L' + os.path.join(install_prefix, 'lib') + \
|
||||
' -L' + os.path.join(target_root, 'usr', 'lib') + \
|
||||
@@ -91,22 +114,20 @@ class AndroidNdkToolchain:
|
||||
' ' + common_flags
|
||||
self.libs = ''
|
||||
|
||||
self.is_arm = self.ndk_arch == 'arm'
|
||||
self.is_arm = ndk_arch == 'arm'
|
||||
self.is_armv7 = self.is_arm and 'armv7' in self.cflags
|
||||
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_cppflags = '-nostdinc++ -isystem ' + os.path.join(libcxx_path, 'include') + ' -isystem ' + os.path.join(ndk_path, 'sources/android/support/include')
|
||||
libstdcxx_ldadd = os.path.join(libcxx_libs_path, 'libc++_static.a') + ' ' + os.path.join(libcxx_libs_path, 'libc++abi.a')
|
||||
|
||||
if self.is_armv7:
|
||||
libstdcxx_ldadd += ' ' + os.path.join(libcxx_libs_path, 'libunwind.a')
|
||||
libstdcxx_flags = '-stdlib=libc++'
|
||||
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 + ' -static-libstdc++ -L' + libcxx_libs_path
|
||||
|
||||
if use_cxx:
|
||||
self.libs += ' ' + libstdcxx_ldadd
|
||||
self.cppflags += ' ' + libstdcxx_cppflags
|
||||
self.cxxflags += ' ' + libstdcxx_cxxflags
|
||||
self.ldflags += ' ' + libstdcxx_ldflags
|
||||
|
||||
self.env = dict(os.environ)
|
||||
|
||||
@@ -117,14 +138,15 @@ class AndroidNdkToolchain:
|
||||
# a list of third-party libraries to be used by MPD on Android
|
||||
from build.libs import *
|
||||
thirdparty_libs = [
|
||||
libmpdclient,
|
||||
libogg,
|
||||
libvorbis,
|
||||
opus,
|
||||
flac,
|
||||
libid3tag,
|
||||
libmad,
|
||||
ffmpeg,
|
||||
curl,
|
||||
libnfs,
|
||||
boost,
|
||||
]
|
||||
|
||||
|
@@ -1,10 +1,10 @@
|
||||
AC_PREREQ(2.60)
|
||||
|
||||
AC_INIT(mpd, 0.20.13, musicpd-dev-team@lists.sourceforge.net)
|
||||
AC_INIT(mpd, 0.20.20, musicpd-dev-team@lists.sourceforge.net)
|
||||
|
||||
VERSION_MAJOR=0
|
||||
VERSION_MINOR=20
|
||||
VERSION_REVISION=13
|
||||
VERSION_REVISION=20
|
||||
VERSION_EXTRA=0
|
||||
|
||||
AC_CONFIG_SRCDIR([src/Main.cxx])
|
||||
@@ -454,7 +454,7 @@ dnl ---------------------------------------------------------------------------
|
||||
dnl Mandatory Libraries
|
||||
dnl ---------------------------------------------------------------------------
|
||||
|
||||
AX_BOOST_BASE([1.46],, [AC_MSG_ERROR([Boost not found])])
|
||||
AX_BOOST_BASE([1.54],, [AC_MSG_ERROR([Boost not found])])
|
||||
|
||||
AC_ARG_ENABLE(icu,
|
||||
AS_HELP_STRING([--enable-icu],
|
||||
|
@@ -113,7 +113,7 @@
|
||||
<para>
|
||||
<varname>musicbrainz_artistid</varname>: the artist id in the
|
||||
<ulink
|
||||
url="http://musicbrainz.org/doc/MusicBrainzTag">MusicBrainz</ulink>
|
||||
url="https://picard.musicbrainz.org/docs/mappings/">MusicBrainz</ulink>
|
||||
database.
|
||||
</para>
|
||||
</listitem>
|
||||
@@ -122,7 +122,7 @@
|
||||
<para>
|
||||
<varname>musicbrainz_albumid</varname>: the album id in the
|
||||
<ulink
|
||||
url="http://musicbrainz.org/doc/MusicBrainzTag">MusicBrainz</ulink>
|
||||
url="https://picard.musicbrainz.org/docs/mappings/">MusicBrainz</ulink>
|
||||
database.
|
||||
</para>
|
||||
</listitem>
|
||||
@@ -131,7 +131,7 @@
|
||||
<para>
|
||||
<varname>musicbrainz_albumartistid</varname>: the album artist
|
||||
id in the <ulink
|
||||
url="http://musicbrainz.org/doc/MusicBrainzTag">MusicBrainz</ulink>
|
||||
url="https://picard.musicbrainz.org/docs/mappings/">MusicBrainz</ulink>
|
||||
database.
|
||||
</para>
|
||||
</listitem>
|
||||
@@ -140,7 +140,7 @@
|
||||
<para>
|
||||
<varname>musicbrainz_trackid</varname>: the track id in the
|
||||
<ulink
|
||||
url="http://musicbrainz.org/doc/MusicBrainzTag">MusicBrainz</ulink>
|
||||
url="https://picard.musicbrainz.org/docs/mappings/">MusicBrainz</ulink>
|
||||
database.
|
||||
</para>
|
||||
</listitem>
|
||||
@@ -149,7 +149,7 @@
|
||||
<para>
|
||||
<varname>musicbrainz_releasetrackid</varname>: the release track
|
||||
id in the <ulink
|
||||
url="http://musicbrainz.org/doc/MusicBrainzTag">MusicBrainz</ulink>
|
||||
url="https://picard.musicbrainz.org/docs/mappings/">MusicBrainz</ulink>
|
||||
database.
|
||||
</para>
|
||||
</listitem>
|
||||
|
79
doc/user.xml
79
doc/user.xml
@@ -66,6 +66,26 @@
|
||||
</para>
|
||||
</section>
|
||||
|
||||
<section id="install_android">
|
||||
<title>Installing on Android</title>
|
||||
|
||||
<para>
|
||||
An experimental Android build is available on <ulink
|
||||
url="https://play.google.com/store/apps/details?id=org.musicpd">Google
|
||||
Play</ulink>. After installing and launching it, MPD will
|
||||
scan the music in your <filename>Music</filename> directory
|
||||
and you can control it as usual with a MPD client.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
If you need to tweak the configuration, you can create a file
|
||||
called <filename>mpd.conf</filename> on the data partition
|
||||
(the directory which is returned by Android's <ulink
|
||||
url="https://developer.android.com/reference/android/os/Environment.html#getExternalStorageDirectory()">getExternalStorageDirectory()</ulink>
|
||||
API function).
|
||||
</para>
|
||||
</section>
|
||||
|
||||
<section id="install_source">
|
||||
<title>Compiling from source</title>
|
||||
|
||||
@@ -94,7 +114,7 @@ cd mpd-version</programlisting>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
<ulink url="http://www.boost.org/">Boost 1.46</ulink>
|
||||
<ulink url="http://www.boost.org/">Boost 1.54</ulink>
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
@@ -248,6 +268,59 @@ apt-get install g++ \
|
||||
script.
|
||||
</para>
|
||||
</section>
|
||||
|
||||
<section id="android_build">
|
||||
<title>Compiling for Android</title>
|
||||
|
||||
<para>
|
||||
MPD can be compiled as an Android app. It can be installed
|
||||
easily with <link linkend="install_android">Google
|
||||
Play</link>, but if you want to build it from source, follow
|
||||
this section.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
You need:
|
||||
</para>
|
||||
|
||||
<itemizedlist>
|
||||
<listitem>
|
||||
<para>
|
||||
Android SDK
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
<ulink
|
||||
url="https://developer.android.com/ndk/downloads/index.html">Android
|
||||
NDK</ulink>
|
||||
</para>
|
||||
</listitem>
|
||||
</itemizedlist>
|
||||
|
||||
<para>
|
||||
Just like with the native build, unpack the
|
||||
<application>MPD</application> source tarball and change
|
||||
into the directory. Then, instead of
|
||||
<command>./configure</command>, type:
|
||||
</para>
|
||||
|
||||
<programlisting>./android/build.py SDK_PATH NDK_PATH ABI
|
||||
make android/build/mpd-debug.apk</programlisting>
|
||||
|
||||
<para>
|
||||
<varname>SDK_PATH</varname> is the absolute path where you
|
||||
installed the Android SDK; <varname>NDK_PATH</varname> is
|
||||
the Android NDK installation path; <varname>ABI</varname> is
|
||||
the Android ABI to be built, e.g. "armeabi-v7a".
|
||||
</para>
|
||||
|
||||
<para>
|
||||
This downloads various library sources, and then configures
|
||||
and builds <application>MPD</application>.
|
||||
</para>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<section id="systemd_socket">
|
||||
@@ -323,7 +396,9 @@ systemctl start mpd.socket</programlisting>
|
||||
<application>MPD</application> as a user daemon (and not as a
|
||||
system daemon), the configuration is read from
|
||||
<filename>$XDG_CONFIG_HOME/mpd/mpd.conf</filename> (usually
|
||||
<filename>~/.config/mpd/mpd.conf</filename>).
|
||||
<filename>~/.config/mpd/mpd.conf</filename>). On Android,
|
||||
<filename>mpd.conf</filename> will be loaded from the
|
||||
top-level directory of the data partition.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
|
@@ -1,24 +1,37 @@
|
||||
import os.path, subprocess
|
||||
import os.path, subprocess, sys
|
||||
|
||||
from build.project import Project
|
||||
from build.makeproject import MakeProject
|
||||
|
||||
class AutotoolsProject(Project):
|
||||
class AutotoolsProject(MakeProject):
|
||||
def __init__(self, url, md5, installed, configure_args=[],
|
||||
autogen=False,
|
||||
autoreconf=False,
|
||||
cppflags='',
|
||||
ldflags='',
|
||||
libs='',
|
||||
subdirs=None,
|
||||
**kwargs):
|
||||
Project.__init__(self, url, md5, installed, **kwargs)
|
||||
MakeProject.__init__(self, url, md5, installed, **kwargs)
|
||||
self.configure_args = configure_args
|
||||
self.autogen = autogen
|
||||
self.autoreconf = autoreconf
|
||||
self.cppflags = cppflags
|
||||
self.ldflags = ldflags
|
||||
self.libs = libs
|
||||
self.subdirs = subdirs
|
||||
|
||||
def build(self, toolchain):
|
||||
def configure(self, toolchain):
|
||||
src = self.unpack(toolchain)
|
||||
if self.autogen:
|
||||
subprocess.check_call(['libtoolize', '--force'], cwd=src)
|
||||
if sys.platform == 'darwin':
|
||||
subprocess.check_call(['glibtoolize', '--force'], cwd=src)
|
||||
else:
|
||||
subprocess.check_call(['libtoolize', '--force'], cwd=src)
|
||||
subprocess.check_call(['aclocal'], cwd=src)
|
||||
subprocess.check_call(['automake', '--add-missing', '--force-missing', '--foreign'], cwd=src)
|
||||
subprocess.check_call(['autoconf'], cwd=src)
|
||||
if self.autoreconf:
|
||||
subprocess.check_call(['autoreconf', '-vif'], cwd=src)
|
||||
|
||||
build = self.make_build_path(toolchain)
|
||||
|
||||
@@ -29,8 +42,8 @@ class AutotoolsProject(Project):
|
||||
'CFLAGS=' + toolchain.cflags,
|
||||
'CXXFLAGS=' + toolchain.cxxflags,
|
||||
'CPPFLAGS=' + toolchain.cppflags + ' ' + self.cppflags,
|
||||
'LDFLAGS=' + toolchain.ldflags,
|
||||
'LIBS=' + toolchain.libs,
|
||||
'LDFLAGS=' + toolchain.ldflags + ' ' + self.ldflags,
|
||||
'LIBS=' + toolchain.libs + ' ' + self.libs,
|
||||
'AR=' + toolchain.ar,
|
||||
'RANLIB=' + toolchain.ranlib,
|
||||
'STRIP=' + toolchain.strip,
|
||||
@@ -40,7 +53,12 @@ class AutotoolsProject(Project):
|
||||
] + self.configure_args
|
||||
|
||||
subprocess.check_call(configure, cwd=build, env=toolchain.env)
|
||||
subprocess.check_call(['/usr/bin/make', '--quiet', '-j12'],
|
||||
cwd=build, env=toolchain.env)
|
||||
subprocess.check_call(['/usr/bin/make', '--quiet', 'install'],
|
||||
cwd=build, env=toolchain.env)
|
||||
return build
|
||||
|
||||
def build(self, toolchain):
|
||||
build = self.configure(toolchain)
|
||||
if self.subdirs is not None:
|
||||
for subdir in self.subdirs:
|
||||
MakeProject.build(self, toolchain, os.path.join(build, subdir))
|
||||
else:
|
||||
MakeProject.build(self, toolchain, build)
|
||||
|
@@ -1,28 +1,54 @@
|
||||
import re
|
||||
from os.path import abspath
|
||||
|
||||
from build.project import Project
|
||||
from build.zlib import ZlibProject
|
||||
from build.meson import MesonProject
|
||||
from build.autotools import AutotoolsProject
|
||||
from build.ffmpeg import FfmpegProject
|
||||
from build.boost import BoostProject
|
||||
|
||||
libmpdclient = MesonProject(
|
||||
'https://www.musicpd.org/download/libmpdclient/2/libmpdclient-2.14.tar.xz',
|
||||
'0a84e2791bfe3077cf22ee1784c805d5bb550803dffe56a39aa3690a38061372',
|
||||
'lib/libmpdclient.a',
|
||||
)
|
||||
|
||||
libogg = AutotoolsProject(
|
||||
'http://downloads.xiph.org/releases/ogg/libogg-1.3.2.tar.xz',
|
||||
'5c3a34309d8b98640827e5d0991a4015',
|
||||
'http://downloads.xiph.org/releases/ogg/libogg-1.3.3.tar.xz',
|
||||
'4f3fc6178a533d392064f14776b23c397ed4b9f48f5de297aba73b643f955c08',
|
||||
'lib/libogg.a',
|
||||
['--disable-shared', '--enable-static'],
|
||||
[
|
||||
'--disable-shared', '--enable-static',
|
||||
],
|
||||
)
|
||||
|
||||
libvorbis = AutotoolsProject(
|
||||
'http://downloads.xiph.org/releases/vorbis/libvorbis-1.3.5.tar.xz',
|
||||
'28cb28097c07a735d6af56e598e1c90f',
|
||||
'http://downloads.xiph.org/releases/vorbis/libvorbis-1.3.6.tar.xz',
|
||||
'af00bb5a784e7c9e69f56823de4637c350643deedaf333d0fa86ecdba6fcb415',
|
||||
'lib/libvorbis.a',
|
||||
['--disable-shared', '--enable-static'],
|
||||
[
|
||||
'--disable-shared', '--enable-static',
|
||||
],
|
||||
|
||||
edits={
|
||||
# this option is not understood by clang
|
||||
'configure': lambda data: data.replace('-mno-ieee-fp', ' '),
|
||||
}
|
||||
)
|
||||
|
||||
opus = AutotoolsProject(
|
||||
'https://archive.mozilla.org/pub/opus/opus-1.2.1.tar.gz',
|
||||
'cfafd339ccd9c5ef8d6ab15d7e1a412c054bf4cb4ecbbbcc78c12ef2def70732',
|
||||
'lib/libopus.a',
|
||||
['--disable-shared', '--enable-static'],
|
||||
[
|
||||
'--disable-shared', '--enable-static',
|
||||
'--disable-doc',
|
||||
'--disable-extra-programs',
|
||||
],
|
||||
|
||||
# suppress "visibility default" from opus_defines.h
|
||||
cppflags='-DOPUS_EXPORT=',
|
||||
)
|
||||
|
||||
flac = AutotoolsProject(
|
||||
@@ -32,7 +58,9 @@ flac = AutotoolsProject(
|
||||
[
|
||||
'--disable-shared', '--enable-static',
|
||||
'--disable-xmms-plugin', '--disable-cpplibs',
|
||||
'--disable-doxygen-docs',
|
||||
],
|
||||
subdirs=['include', 'src/libFLAC'],
|
||||
)
|
||||
|
||||
zlib = ZlibProject(
|
||||
@@ -45,21 +73,36 @@ libid3tag = AutotoolsProject(
|
||||
'ftp://ftp.mars.org/pub/mpeg/libid3tag-0.15.1b.tar.gz',
|
||||
'e5808ad997ba32c498803822078748c3',
|
||||
'lib/libid3tag.a',
|
||||
['--disable-shared', '--enable-static'],
|
||||
[
|
||||
'--disable-shared', '--enable-static',
|
||||
|
||||
# without this, libid3tag's configure.ac ignores -O* and -f*
|
||||
'--disable-debugging',
|
||||
],
|
||||
autogen=True,
|
||||
|
||||
edits={
|
||||
# fix bug in libid3tag's configure.ac which discards all but the last optimization flag
|
||||
'configure.ac': lambda data: re.sub(r'optimize="\$1"', r'optimize="$optimize $1"', data, count=1),
|
||||
}
|
||||
)
|
||||
|
||||
libmad = AutotoolsProject(
|
||||
'ftp://ftp.mars.org/pub/mpeg/libmad-0.15.1b.tar.gz',
|
||||
'1be543bc30c56fb6bea1d7bf6a64e66c',
|
||||
'lib/libmad.a',
|
||||
['--disable-shared', '--enable-static'],
|
||||
[
|
||||
'--disable-shared', '--enable-static',
|
||||
|
||||
# without this, libmad's configure.ac ignores -O* and -f*
|
||||
'--disable-debugging',
|
||||
],
|
||||
autogen=True,
|
||||
)
|
||||
|
||||
liblame = AutotoolsProject(
|
||||
'http://downloads.sourceforge.net/project/lame/lame/3.99/lame-3.99.5.tar.gz',
|
||||
'24346b4158e4af3bd9f2e194bb23eb473c75fb7377011523353196b19b9a23ff',
|
||||
'http://downloads.sourceforge.net/project/lame/lame/3.100/lame-3.100.tar.gz',
|
||||
'ddfe36cab873794038ae2c1210557ad34857a4b6bdc515785d1da9e175b1da1e',
|
||||
'lib/libmp3lame.a',
|
||||
[
|
||||
'--disable-shared', '--enable-static',
|
||||
@@ -69,8 +112,8 @@ liblame = AutotoolsProject(
|
||||
)
|
||||
|
||||
ffmpeg = FfmpegProject(
|
||||
'http://ffmpeg.org/releases/ffmpeg-3.4.1.tar.xz',
|
||||
'5a77278a63741efa74e26bf197b9bb09ac6381b9757391b922407210f0f991c0',
|
||||
'http://ffmpeg.org/releases/ffmpeg-4.0.tar.xz',
|
||||
'ed945daf40b124e77a685893cc025d086f638bc703183460aff49508edb3a43f',
|
||||
'lib/libavcodec.a',
|
||||
[
|
||||
'--disable-shared', '--enable-static',
|
||||
@@ -89,21 +132,217 @@ ffmpeg = FfmpegProject(
|
||||
'--disable-pixelutils',
|
||||
'--disable-network',
|
||||
'--disable-encoders',
|
||||
'--disable-muxers',
|
||||
'--disable-protocols',
|
||||
'--disable-devices',
|
||||
'--disable-filters',
|
||||
'--disable-v4l2_m2m',
|
||||
|
||||
# clang misinterprets the "B0" in hevc_mvs.c as binary
|
||||
# literal, which breaks the build; but we don't need that
|
||||
# video codec anyway
|
||||
'--disable-parser=bmp',
|
||||
'--disable-parser=cavsvideo',
|
||||
'--disable-parser=dvbsub',
|
||||
'--disable-parser=dvdsub',
|
||||
'--disable-parser=dvd_nav',
|
||||
'--disable-parser=flac',
|
||||
'--disable-parser=g729',
|
||||
'--disable-parser=gsm',
|
||||
'--disable-parser=h261',
|
||||
'--disable-parser=h263',
|
||||
'--disable-parser=h264',
|
||||
'--disable-parser=hevc',
|
||||
'--disable-parser=mjpeg',
|
||||
'--disable-parser=mlp',
|
||||
'--disable-parser=mpeg4video',
|
||||
'--disable-parser=mpegvideo',
|
||||
'--disable-parser=opus',
|
||||
'--disable-parser=vc1',
|
||||
'--disable-parser=vp3',
|
||||
'--disable-parser=vp8',
|
||||
'--disable-parser=vp9',
|
||||
'--disable-parser=png',
|
||||
'--disable-parser=pnm',
|
||||
'--disable-parser=xma',
|
||||
|
||||
'--disable-demuxer=aqtitle',
|
||||
'--disable-demuxer=ass',
|
||||
'--disable-demuxer=bethsoftvid',
|
||||
'--disable-demuxer=bink',
|
||||
'--disable-demuxer=cavsvideo',
|
||||
'--disable-demuxer=cdxl',
|
||||
'--disable-demuxer=dvbsub',
|
||||
'--disable-demuxer=dvbtxt',
|
||||
'--disable-demuxer=h261',
|
||||
'--disable-demuxer=h263',
|
||||
'--disable-demuxer=h264',
|
||||
'--disable-demuxer=ico',
|
||||
'--disable-demuxer=image2',
|
||||
'--disable-demuxer=jacosub',
|
||||
'--disable-demuxer=lrc',
|
||||
'--disable-demuxer=microdvd',
|
||||
'--disable-demuxer=mjpeg',
|
||||
'--disable-demuxer=mjpeg_2000',
|
||||
'--disable-demuxer=mpegps',
|
||||
'--disable-demuxer=mpegvideo',
|
||||
'--disable-demuxer=mpl2',
|
||||
'--disable-demuxer=mpsub',
|
||||
'--disable-demuxer=pjs',
|
||||
'--disable-demuxer=rawvideo',
|
||||
'--disable-demuxer=realtext',
|
||||
'--disable-demuxer=sami',
|
||||
'--disable-demuxer=scc',
|
||||
'--disable-demuxer=srt',
|
||||
'--disable-demuxer=stl',
|
||||
'--disable-demuxer=subviewer',
|
||||
'--disable-demuxer=subviewer1',
|
||||
'--disable-demuxer=swf',
|
||||
'--disable-demuxer=tedcaptions',
|
||||
'--disable-demuxer=vobsub',
|
||||
'--disable-demuxer=vplayer',
|
||||
'--disable-demuxer=webvtt',
|
||||
'--disable-demuxer=yuv4mpegpipe',
|
||||
|
||||
# we don't need these decoders, because we have the dedicated
|
||||
# libraries
|
||||
'--disable-decoder=flac',
|
||||
'--disable-decoder=opus',
|
||||
'--disable-decoder=vorbis',
|
||||
|
||||
# audio codecs nobody uses
|
||||
'--disable-decoder=atrac1',
|
||||
'--disable-decoder=atrac3',
|
||||
'--disable-decoder=atrac3al',
|
||||
'--disable-decoder=atrac3p',
|
||||
'--disable-decoder=atrac3pal',
|
||||
'--disable-decoder=binkaudio_dct',
|
||||
'--disable-decoder=binkaudio_rdft',
|
||||
'--disable-decoder=bmv_audio',
|
||||
'--disable-decoder=dsicinaudio',
|
||||
'--disable-decoder=dvaudio',
|
||||
'--disable-decoder=metasound',
|
||||
'--disable-decoder=paf_audio',
|
||||
'--disable-decoder=ra_144',
|
||||
'--disable-decoder=ra_288',
|
||||
'--disable-decoder=ralf',
|
||||
'--disable-decoder=qdm2',
|
||||
'--disable-decoder=qdmc',
|
||||
|
||||
# disable lots of image and video codecs
|
||||
'--disable-decoder=ass',
|
||||
'--disable-decoder=asv1',
|
||||
'--disable-decoder=asv2',
|
||||
'--disable-decoder=apng',
|
||||
'--disable-decoder=avrn',
|
||||
'--disable-decoder=avrp',
|
||||
'--disable-decoder=bethsoftvid',
|
||||
'--disable-decoder=bink',
|
||||
'--disable-decoder=bmp',
|
||||
'--disable-decoder=bmv_video',
|
||||
'--disable-decoder=cavs',
|
||||
'--disable-decoder=ccaption',
|
||||
'--disable-decoder=cdgraphics',
|
||||
'--disable-decoder=clearvideo',
|
||||
'--disable-decoder=dirac',
|
||||
'--disable-decoder=dsicinvideo',
|
||||
'--disable-decoder=dvbsub',
|
||||
'--disable-decoder=dvdsub',
|
||||
'--disable-decoder=dvvideo',
|
||||
'--disable-decoder=exr',
|
||||
'--disable-decoder=ffv1',
|
||||
'--disable-decoder=ffvhuff',
|
||||
'--disable-decoder=ffwavesynth',
|
||||
'--disable-decoder=flic',
|
||||
'--disable-decoder=flv',
|
||||
'--disable-decoder=fraps',
|
||||
'--disable-decoder=gif',
|
||||
'--disable-decoder=h261',
|
||||
'--disable-decoder=h263',
|
||||
'--disable-decoder=h263i',
|
||||
'--disable-decoder=h263p',
|
||||
'--disable-decoder=h264',
|
||||
'--disable-decoder=hevc',
|
||||
'--disable-decoder=hnm4_video',
|
||||
'--disable-decoder=hq_hqa',
|
||||
'--disable-decoder=hqx',
|
||||
'--disable-decoder=idcin',
|
||||
'--disable-decoder=iff_ilbm',
|
||||
'--disable-decoder=indeo2',
|
||||
'--disable-decoder=indeo3',
|
||||
'--disable-decoder=indeo4',
|
||||
'--disable-decoder=indeo5',
|
||||
'--disable-decoder=interplay_video',
|
||||
'--disable-decoder=jacosub',
|
||||
'--disable-decoder=jpeg2000',
|
||||
'--disable-decoder=jpegls',
|
||||
'--disable-decoder=microdvd',
|
||||
'--disable-decoder=mimic',
|
||||
'--disable-decoder=mjpeg',
|
||||
'--disable-decoder=mmvideo',
|
||||
'--disable-decoder=mpl2',
|
||||
'--disable-decoder=motionpixels',
|
||||
'--disable-decoder=mpeg1video',
|
||||
'--disable-decoder=mpeg2video',
|
||||
'--disable-decoder=mpeg4',
|
||||
'--disable-decoder=mpegvideo',
|
||||
'--disable-decoder=mscc',
|
||||
'--disable-decoder=msmpeg4_crystalhd',
|
||||
'--disable-decoder=msmpeg4v1',
|
||||
'--disable-decoder=msmpeg4v2',
|
||||
'--disable-decoder=msmpeg4v3',
|
||||
'--disable-decoder=msvideo1',
|
||||
'--disable-decoder=mszh',
|
||||
'--disable-decoder=mvc1',
|
||||
'--disable-decoder=mvc2',
|
||||
'--disable-decoder=on2avc',
|
||||
'--disable-decoder=paf_video',
|
||||
'--disable-decoder=png',
|
||||
'--disable-decoder=qdraw',
|
||||
'--disable-decoder=qpeg',
|
||||
'--disable-decoder=rawvideo',
|
||||
'--disable-decoder=realtext',
|
||||
'--disable-decoder=roq',
|
||||
'--disable-decoder=roq_dpcm',
|
||||
'--disable-decoder=rscc',
|
||||
'--disable-decoder=rv10',
|
||||
'--disable-decoder=rv20',
|
||||
'--disable-decoder=rv30',
|
||||
'--disable-decoder=rv40',
|
||||
'--disable-decoder=sami',
|
||||
'--disable-decoder=sheervideo',
|
||||
'--disable-decoder=snow',
|
||||
'--disable-decoder=srt',
|
||||
'--disable-decoder=stl',
|
||||
'--disable-decoder=subrip',
|
||||
'--disable-decoder=subviewer',
|
||||
'--disable-decoder=subviewer1',
|
||||
'--disable-decoder=svq1',
|
||||
'--disable-decoder=svq3',
|
||||
'--disable-decoder=tiff',
|
||||
'--disable-decoder=tiertexseqvideo',
|
||||
'--disable-decoder=truemotion1',
|
||||
'--disable-decoder=truemotion2',
|
||||
'--disable-decoder=truemotion2rt',
|
||||
'--disable-decoder=twinvq',
|
||||
'--disable-decoder=utvideo',
|
||||
'--disable-decoder=vc1',
|
||||
'--disable-decoder=vmdvideo',
|
||||
'--disable-decoder=vp3',
|
||||
'--disable-decoder=vp5',
|
||||
'--disable-decoder=vp6',
|
||||
'--disable-decoder=vp7',
|
||||
'--disable-decoder=vp8',
|
||||
'--disable-decoder=vp9',
|
||||
'--disable-decoder=vqa',
|
||||
'--disable-decoder=webvtt',
|
||||
'--disable-decoder=wmv1',
|
||||
'--disable-decoder=wmv2',
|
||||
'--disable-decoder=wmv3',
|
||||
'--disable-decoder=yuv4',
|
||||
],
|
||||
)
|
||||
|
||||
curl = AutotoolsProject(
|
||||
'http://curl.haxx.se/download/curl-7.57.0.tar.xz',
|
||||
'f5f6fd3c72b7b8389969f4fb671ed8532fa9b5bb7a5cae7ca89bc1cea45c7878',
|
||||
'http://curl.haxx.se/download/curl-7.60.0.tar.xz',
|
||||
'8736ff8ded89ddf7e926eec7b16f82597d029fc1469f3a551f1fafaac164e6a0',
|
||||
'lib/libcurl.a',
|
||||
[
|
||||
'--disable-shared', '--enable-static',
|
||||
@@ -114,16 +353,34 @@ curl = AutotoolsProject(
|
||||
'--disable-ldap', '--disable-ldaps',
|
||||
'--disable-rtsp', '--disable-proxy', '--disable-dict', '--disable-telnet',
|
||||
'--disable-tftp', '--disable-pop3', '--disable-imap', '--disable-smtp',
|
||||
'--disable-smb',
|
||||
'--disable-gopher',
|
||||
'--disable-manual',
|
||||
'--disable-threaded-resolver', '--disable-verbose', '--disable-sspi',
|
||||
'--disable-crypto-auth', '--disable-ntlm-wb', '--disable-tls-srp', '--disable-cookies',
|
||||
'--without-ssl', '--without-gnutls', '--without-nss', '--without-libssh2',
|
||||
],
|
||||
|
||||
patches='src/lib/curl/patches',
|
||||
)
|
||||
|
||||
libnfs = AutotoolsProject(
|
||||
'https://github.com/sahlberg/libnfs/archive/libnfs-2.0.0.tar.gz',
|
||||
'7ea6cd8fa6c461d01091e584d424d28e137d23ff4b65b95d01a3fd0ef95d120e',
|
||||
'lib/libnfs.a',
|
||||
[
|
||||
'--disable-shared', '--enable-static',
|
||||
'--disable-debug',
|
||||
|
||||
# work around -Wtautological-compare
|
||||
'--disable-werror',
|
||||
],
|
||||
base='libnfs-libnfs-2.0.0',
|
||||
autoreconf=True,
|
||||
)
|
||||
|
||||
boost = BoostProject(
|
||||
'http://downloads.sourceforge.net/project/boost/boost/1.65.1/boost_1_65_1.tar.bz2',
|
||||
'9807a5d16566c57fd74fb522764e0b134a8bbe6b6e8967b83afefd30dcd3be81',
|
||||
'http://downloads.sourceforge.net/project/boost/boost/1.66.0/boost_1_66_0.tar.bz2',
|
||||
'5721818253e6a0989583192f96782c4a98eb6204965316df9f5ad75819225ca9',
|
||||
'include/boost/version.hpp',
|
||||
)
|
||||
|
28
python/build/makeproject.py
Normal file
28
python/build/makeproject.py
Normal file
@@ -0,0 +1,28 @@
|
||||
import subprocess
|
||||
|
||||
from build.project import Project
|
||||
|
||||
class MakeProject(Project):
|
||||
def __init__(self, url, md5, installed,
|
||||
install_target='install',
|
||||
**kwargs):
|
||||
Project.__init__(self, url, md5, installed, **kwargs)
|
||||
self.install_target = install_target
|
||||
|
||||
def get_simultaneous_jobs(self):
|
||||
return 12
|
||||
|
||||
def get_make_args(self, toolchain):
|
||||
return ['--quiet', '-j' + str(self.get_simultaneous_jobs())]
|
||||
|
||||
def get_make_install_args(self, toolchain):
|
||||
return ['--quiet', self.install_target]
|
||||
|
||||
def make(self, toolchain, wd, args):
|
||||
subprocess.check_call(['/usr/bin/make'] + args,
|
||||
cwd=wd, env=toolchain.env)
|
||||
|
||||
def build(self, toolchain, wd, install=True):
|
||||
self.make(toolchain, wd, self.get_make_args(toolchain))
|
||||
if install:
|
||||
self.make(toolchain, wd, self.get_make_install_args(toolchain))
|
96
python/build/meson.py
Normal file
96
python/build/meson.py
Normal file
@@ -0,0 +1,96 @@
|
||||
import os.path, subprocess, sys
|
||||
|
||||
from build.project import Project
|
||||
|
||||
class MesonProject(Project):
|
||||
def __init__(self, url, md5, installed, configure_args=[],
|
||||
**kwargs):
|
||||
Project.__init__(self, url, md5, installed, **kwargs)
|
||||
self.configure_args = configure_args
|
||||
|
||||
def _make_cross_file(self, toolchain):
|
||||
if toolchain.is_windows:
|
||||
system = 'windows'
|
||||
else:
|
||||
system = 'linux'
|
||||
|
||||
if toolchain.is_arm:
|
||||
cpu_family = 'arm'
|
||||
if toolchain.is_armv7:
|
||||
cpu = 'armv7'
|
||||
else:
|
||||
cpu = 'armv6'
|
||||
else:
|
||||
cpu_family = 'x86'
|
||||
if 'x86_64' in toolchain.arch:
|
||||
cpu = 'x86_64'
|
||||
else:
|
||||
cpu = 'i686'
|
||||
|
||||
# TODO: support more CPUs
|
||||
endian = 'little'
|
||||
|
||||
# TODO: write pkg-config wrapper
|
||||
|
||||
path = os.path.join(toolchain.build_path, 'meson.cross')
|
||||
os.makedirs(toolchain.build_path, exist_ok=True)
|
||||
with open(path, 'w') as f:
|
||||
f.write("""
|
||||
[binaries]
|
||||
c = '%s'
|
||||
cpp = '%s'
|
||||
ar = '%s'
|
||||
strip = '%s'
|
||||
|
||||
[properties]
|
||||
root = '%s'
|
||||
|
||||
c_args = %s
|
||||
c_link_args = %s
|
||||
|
||||
cpp_args = %s
|
||||
cpp_link_args = %s
|
||||
|
||||
[host_machine]
|
||||
system = '%s'
|
||||
cpu_family = '%s'
|
||||
cpu = '%s'
|
||||
endian = '%s'
|
||||
""" % (toolchain.cc, toolchain.cxx, toolchain.ar, toolchain.strip,
|
||||
toolchain.install_prefix,
|
||||
repr((toolchain.cppflags + ' ' + toolchain.cflags).split()),
|
||||
repr(toolchain.ldflags.split()),
|
||||
repr((toolchain.cppflags + ' ' + toolchain.cxxflags).split()),
|
||||
repr(toolchain.ldflags.split()),
|
||||
system, cpu_family, cpu, endian))
|
||||
return path
|
||||
|
||||
def configure(self, toolchain):
|
||||
src = self.unpack(toolchain)
|
||||
cross_file = self._make_cross_file(toolchain)
|
||||
build = self.make_build_path(toolchain)
|
||||
configure = [
|
||||
'meson',
|
||||
src, build,
|
||||
|
||||
'--prefix', toolchain.install_prefix,
|
||||
|
||||
# this is necessary because Meson uses Debian's build machine
|
||||
# MultiArch path (e.g. "lib/x86_64-linux-gnu") for cross
|
||||
# builds, which is obviously wrong
|
||||
'--libdir', 'lib',
|
||||
|
||||
'--buildtype', 'plain',
|
||||
|
||||
'--default-library=static',
|
||||
|
||||
'--cross-file', cross_file,
|
||||
] + self.configure_args
|
||||
|
||||
subprocess.check_call(configure, env=toolchain.env)
|
||||
return build
|
||||
|
||||
def build(self, toolchain):
|
||||
build = self.configure(toolchain)
|
||||
subprocess.check_call(['ninja', 'install'],
|
||||
cwd=build, env=toolchain.env)
|
@@ -3,10 +3,13 @@ import re
|
||||
|
||||
from build.download import download_and_verify
|
||||
from build.tar import untar
|
||||
from build.quilt import push_all
|
||||
|
||||
class Project:
|
||||
def __init__(self, url, md5, installed, name=None, version=None,
|
||||
base=None,
|
||||
patches=None,
|
||||
edits=None,
|
||||
use_cxx=False):
|
||||
if base is None:
|
||||
basename = os.path.basename(url)
|
||||
@@ -17,7 +20,7 @@ class Project:
|
||||
self.base = base
|
||||
|
||||
if name is None or version is None:
|
||||
m = re.match(r'^([-\w]+)-(\d[\d.]*[a-z]?)$', self.base)
|
||||
m = re.match(r'^([-\w]+)-(\d[\d.]*[a-z]?[\d.]*)$', self.base)
|
||||
if name is None: name = m.group(1)
|
||||
if version is None: version = m.group(2)
|
||||
|
||||
@@ -28,6 +31,11 @@ class Project:
|
||||
self.md5 = md5
|
||||
self.installed = installed
|
||||
|
||||
if patches is not None:
|
||||
srcdir = os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
|
||||
patches = os.path.join(srcdir, patches)
|
||||
self.patches = patches
|
||||
self.edits = edits
|
||||
self.use_cxx = use_cxx
|
||||
|
||||
def download(self, toolchain):
|
||||
@@ -47,7 +55,21 @@ class Project:
|
||||
parent_path = toolchain.src_path
|
||||
else:
|
||||
parent_path = toolchain.build_path
|
||||
return untar(self.download(toolchain), parent_path, self.base)
|
||||
path = untar(self.download(toolchain), parent_path, self.base)
|
||||
|
||||
if self.patches is not None:
|
||||
push_all(toolchain, path, self.patches)
|
||||
|
||||
if self.edits is not None:
|
||||
for filename, function in self.edits.items():
|
||||
with open(os.path.join(path, filename), 'r+t') as f:
|
||||
old_data = f.read()
|
||||
new_data = function(old_data)
|
||||
f.seek(0)
|
||||
f.truncate(0)
|
||||
f.write(new_data)
|
||||
|
||||
return path
|
||||
|
||||
def make_build_path(self, toolchain):
|
||||
path = os.path.join(toolchain.build_path, self.base)
|
||||
|
9
python/build/quilt.py
Normal file
9
python/build/quilt.py
Normal file
@@ -0,0 +1,9 @@
|
||||
import subprocess
|
||||
|
||||
def run_quilt(toolchain, cwd, patches_path, *args):
|
||||
env = dict(toolchain.env)
|
||||
env['QUILT_PATCHES'] = patches_path
|
||||
subprocess.check_call(['quilt'] + list(args), cwd=cwd, env=env)
|
||||
|
||||
def push_all(toolchain, src_path, patches_path):
|
||||
run_quilt(toolchain, src_path, patches_path, 'push', '-a')
|
@@ -27,12 +27,17 @@
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
static struct {
|
||||
static struct IOThread {
|
||||
Mutex mutex;
|
||||
Cond cond;
|
||||
|
||||
EventLoop *loop;
|
||||
Thread thread;
|
||||
|
||||
IOThread():thread(BIND_THIS_METHOD(Run)) {}
|
||||
|
||||
private:
|
||||
void Run() noexcept;
|
||||
} io;
|
||||
|
||||
void
|
||||
@@ -44,15 +49,15 @@ io_thread_run(void)
|
||||
io.loop->Run();
|
||||
}
|
||||
|
||||
static void
|
||||
io_thread_func(gcc_unused void *arg)
|
||||
inline void
|
||||
IOThread::Run() noexcept
|
||||
{
|
||||
SetThreadName("io");
|
||||
|
||||
/* lock+unlock to synchronize with io_thread_start(), to be
|
||||
sure that io.thread is set */
|
||||
io.mutex.lock();
|
||||
io.mutex.unlock();
|
||||
mutex.lock();
|
||||
mutex.unlock();
|
||||
|
||||
io_thread_run();
|
||||
}
|
||||
@@ -73,7 +78,7 @@ io_thread_start()
|
||||
assert(!io.thread.IsDefined());
|
||||
|
||||
const std::lock_guard<Mutex> protect(io.mutex);
|
||||
io.thread.Start(io_thread_func, nullptr);
|
||||
io.thread.Start();
|
||||
}
|
||||
|
||||
void
|
||||
@@ -103,8 +108,12 @@ io_thread_get() noexcept
|
||||
return *io.loop;
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
|
||||
bool
|
||||
io_thread_inside() noexcept
|
||||
{
|
||||
return io.thread.IsInside();
|
||||
}
|
||||
|
||||
#endif
|
||||
|
@@ -20,6 +20,7 @@
|
||||
#ifndef MPD_IO_THREAD_HXX
|
||||
#define MPD_IO_THREAD_HXX
|
||||
|
||||
#include "check.h"
|
||||
#include "Compiler.h"
|
||||
|
||||
class EventLoop;
|
||||
@@ -53,6 +54,8 @@ gcc_const
|
||||
EventLoop &
|
||||
io_thread_get() noexcept;
|
||||
|
||||
#ifndef NDEBUG
|
||||
|
||||
/**
|
||||
* Is the current thread the I/O thread?
|
||||
*/
|
||||
@@ -61,3 +64,5 @@ bool
|
||||
io_thread_inside() noexcept;
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
51
src/Main.cxx
51
src/Main.cxx
@@ -50,6 +50,7 @@
|
||||
#include "unix/SignalHandlers.hxx"
|
||||
#include "system/FatalError.hxx"
|
||||
#include "thread/Slack.hxx"
|
||||
#include "net/Init.hxx"
|
||||
#include "lib/icu/Init.hxx"
|
||||
#include "config/ConfigGlobal.hxx"
|
||||
#include "config/Param.hxx"
|
||||
@@ -106,15 +107,6 @@
|
||||
#include <locale.h>
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <winsock2.h>
|
||||
#include <ws2tcpip.h>
|
||||
#endif
|
||||
|
||||
#ifdef __BLOCKS__
|
||||
#include <dispatch/dispatch.h>
|
||||
#endif
|
||||
|
||||
#include <limits.h>
|
||||
|
||||
static constexpr size_t KILOBYTE = 1024;
|
||||
@@ -284,25 +276,6 @@ glue_state_file_init()
|
||||
instance->state_file->Read();
|
||||
}
|
||||
|
||||
/**
|
||||
* Windows-only initialization of the Winsock2 library.
|
||||
*/
|
||||
static void winsock_init(void)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
WSADATA sockinfo;
|
||||
|
||||
int retval = WSAStartup(MAKEWORD(2, 2), &sockinfo);
|
||||
if(retval != 0)
|
||||
FormatFatalError("Attempt to open Winsock2 failed; error code %d",
|
||||
retval);
|
||||
|
||||
if (LOBYTE(sockinfo.wVersion) != 2)
|
||||
FatalError("We use Winsock2 but your version is either too new "
|
||||
"or old; please install Winsock 2.x");
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the decoder and player core, including the music pipe.
|
||||
*/
|
||||
@@ -451,7 +424,8 @@ try {
|
||||
|
||||
IcuInit();
|
||||
|
||||
winsock_init();
|
||||
const ScopeNetInit net_init;
|
||||
|
||||
io_thread_init();
|
||||
config_global_init();
|
||||
|
||||
@@ -505,21 +479,8 @@ try {
|
||||
daemonize_begin(options.daemon);
|
||||
#endif
|
||||
|
||||
#ifdef __BLOCKS__
|
||||
/* Runs the OS X native event loop in the main thread, and runs
|
||||
the rest of mpd_main on a new thread. This lets CoreAudio receive
|
||||
route change notifications (e.g. plugging or unplugging headphones).
|
||||
All hardware output on OS X ultimately uses CoreAudio internally.
|
||||
This must be run after forking; if dispatch is called before forking,
|
||||
the child process will have a broken internal dispatch state. */
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||
exit(mpd_main_after_fork(config));
|
||||
});
|
||||
dispatch_main();
|
||||
return EXIT_FAILURE; // unreachable, because dispatch_main never returns
|
||||
#else
|
||||
return mpd_main_after_fork(config);
|
||||
#endif
|
||||
|
||||
} catch (const std::exception &e) {
|
||||
LogError(e);
|
||||
return EXIT_FAILURE;
|
||||
@@ -702,10 +663,6 @@ try {
|
||||
daemonize_finish();
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
WSACleanup();
|
||||
#endif
|
||||
|
||||
IcuFinish();
|
||||
|
||||
log_deinit();
|
||||
|
@@ -95,6 +95,7 @@ public:
|
||||
*/
|
||||
gcc_pure
|
||||
const MusicChunk *Peek() const noexcept {
|
||||
const std::lock_guard<Mutex> protect(mutex);
|
||||
return head;
|
||||
}
|
||||
|
||||
@@ -120,6 +121,7 @@ public:
|
||||
*/
|
||||
gcc_pure
|
||||
unsigned GetSize() const noexcept {
|
||||
const std::lock_guard<Mutex> protect(mutex);
|
||||
return size;
|
||||
}
|
||||
|
||||
|
@@ -24,6 +24,8 @@
|
||||
#include "tag/Tag.hxx"
|
||||
#include "util/ConstBuffer.hxx"
|
||||
#include "util/StringAPI.hxx"
|
||||
#include "util/StringCompare.hxx"
|
||||
#include "util/StringView.hxx"
|
||||
#include "util/ASCII.hxx"
|
||||
#include "util/TimeParser.hxx"
|
||||
#include "util/UriUtil.hxx"
|
||||
@@ -59,13 +61,13 @@ locate_parse_type(const char *str) noexcept
|
||||
|
||||
SongFilter::Item::Item(unsigned _tag, const char *_value, bool _fold_case)
|
||||
:tag(_tag),
|
||||
value(AllocatedString<>::Duplicate(_value)),
|
||||
value(_value),
|
||||
fold_case(_fold_case ? IcuCompare(value.c_str()) : IcuCompare())
|
||||
{
|
||||
}
|
||||
|
||||
SongFilter::Item::Item(unsigned _tag, time_t _time)
|
||||
:tag(_tag), value(nullptr), time(_time)
|
||||
:tag(_tag), time(_time)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -274,3 +276,33 @@ SongFilter::GetBase() const noexcept
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
SongFilter
|
||||
SongFilter::WithoutBasePrefix(const char *_prefix) const noexcept
|
||||
{
|
||||
const StringView prefix(_prefix);
|
||||
SongFilter result;
|
||||
|
||||
for (const auto &i : items) {
|
||||
if (i.GetTag() == LOCATE_TAG_BASE_TYPE) {
|
||||
const char *s = StringAfterPrefix(i.GetValue(), prefix);
|
||||
if (s != nullptr) {
|
||||
if (*s == 0)
|
||||
continue;
|
||||
|
||||
if (*s == '/') {
|
||||
++s;
|
||||
|
||||
if (*s != 0)
|
||||
result.items.emplace_back(LOCATE_TAG_BASE_TYPE, s);
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result.items.emplace_back(i);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
@@ -21,9 +21,9 @@
|
||||
#define MPD_SONG_FILTER_HXX
|
||||
|
||||
#include "lib/icu/Compare.hxx"
|
||||
#include "util/AllocatedString.hxx"
|
||||
#include "Compiler.h"
|
||||
|
||||
#include <string>
|
||||
#include <list>
|
||||
|
||||
#include <stdint.h>
|
||||
@@ -49,7 +49,7 @@ public:
|
||||
class Item {
|
||||
uint8_t tag;
|
||||
|
||||
AllocatedString<> value;
|
||||
std::string value;
|
||||
|
||||
/**
|
||||
* This value is only set if case folding is enabled.
|
||||
@@ -66,11 +66,6 @@ public:
|
||||
Item(unsigned tag, const char *value, bool fold_case=false);
|
||||
Item(unsigned tag, time_t time);
|
||||
|
||||
Item(const Item &other) = delete;
|
||||
Item(Item &&) = default;
|
||||
|
||||
Item &operator=(const Item &other) = delete;
|
||||
|
||||
unsigned GetTag() const {
|
||||
return tag;
|
||||
}
|
||||
@@ -157,6 +152,13 @@ public:
|
||||
*/
|
||||
gcc_pure
|
||||
const char *GetBase() const noexcept;
|
||||
|
||||
/**
|
||||
* Create a copy of the filter with the given prefix stripped
|
||||
* from all #LOCATE_TAG_BASE_TYPE items. This is used to
|
||||
* filter songs in mounted databases.
|
||||
*/
|
||||
SongFilter WithoutBasePrefix(const char *prefix) const noexcept;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@@ -28,6 +28,7 @@
|
||||
#include "tag/TagBuilder.hxx"
|
||||
#include "util/StringUtil.hxx"
|
||||
#include "util/RuntimeError.hxx"
|
||||
#include "util/NumberParser.hxx"
|
||||
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
@@ -94,7 +95,7 @@ song_load(TextFile &file, const char *uri)
|
||||
if ((type = tag_name_parse(line)) != TAG_NUM_OF_ITEM_TYPES) {
|
||||
tag.AddItem(type, value);
|
||||
} else if (strcmp(line, "Time") == 0) {
|
||||
tag.SetDuration(SignedSongTime::FromS(atof(value)));
|
||||
tag.SetDuration(SignedSongTime::FromS(ParseDouble(value)));
|
||||
} else if (strcmp(line, "Playlist") == 0) {
|
||||
tag.SetHasPlaylist(strcmp(value, "yes") == 0);
|
||||
} else if (strcmp(line, SONG_MTIME) == 0) {
|
||||
|
@@ -162,7 +162,7 @@ Bzip2InputStream::FillBuffer()
|
||||
if (bzstream.avail_in > 0)
|
||||
return true;
|
||||
|
||||
size_t count = archive->istream->Read(buffer, sizeof(buffer));
|
||||
size_t count = archive->istream->LockRead(buffer, sizeof(buffer));
|
||||
if (count == 0)
|
||||
return false;
|
||||
|
||||
@@ -174,6 +174,8 @@ Bzip2InputStream::FillBuffer()
|
||||
size_t
|
||||
Bzip2InputStream::Read(void *ptr, size_t length)
|
||||
{
|
||||
const ScopeUnlock unlock(mutex);
|
||||
|
||||
int bz_result;
|
||||
size_t nbytes = 0;
|
||||
|
||||
@@ -224,4 +226,3 @@ const ArchivePlugin bz2_archive_plugin = {
|
||||
bz2_open,
|
||||
bz2_extensions,
|
||||
};
|
||||
|
||||
|
@@ -115,7 +115,12 @@ Iso9660ArchiveFile::Visit(char *path, size_t length, size_t capacity,
|
||||
visitor.VisitArchiveEntry(path + 1);
|
||||
}
|
||||
}
|
||||
|
||||
#if LIBCDIO_VERSION_NUM >= 20000
|
||||
iso9660_filelist_free(entlist);
|
||||
#else
|
||||
_cdio_list_free (entlist, true);
|
||||
#endif
|
||||
}
|
||||
|
||||
static ArchiveFile *
|
||||
@@ -182,6 +187,8 @@ Iso9660ArchiveFile::OpenStream(const char *pathname,
|
||||
size_t
|
||||
Iso9660InputStream::Read(void *ptr, size_t read_size)
|
||||
{
|
||||
const ScopeUnlock unlock(mutex);
|
||||
|
||||
int readed = 0;
|
||||
int no_blocks, cur_block;
|
||||
size_t left_bytes = statbuf->size - offset;
|
||||
|
@@ -138,6 +138,8 @@ ZzipArchiveFile::OpenStream(const char *pathname,
|
||||
size_t
|
||||
ZzipInputStream::Read(void *ptr, size_t read_size)
|
||||
{
|
||||
const ScopeUnlock unlock(mutex);
|
||||
|
||||
int ret = zzip_file_read(file, ptr, read_size);
|
||||
if (ret < 0)
|
||||
throw std::runtime_error("zzip_file_read() has failed");
|
||||
@@ -155,6 +157,8 @@ ZzipInputStream::IsEOF() noexcept
|
||||
void
|
||||
ZzipInputStream::Seek(offset_type new_offset)
|
||||
{
|
||||
const ScopeUnlock unlock(mutex);
|
||||
|
||||
zzip_off_t ofs = zzip_seek(file, new_offset, SEEK_SET);
|
||||
if (ofs < 0)
|
||||
throw std::runtime_error("zzip_seek() has failed");
|
||||
|
@@ -123,6 +123,10 @@ ToAck(std::exception_ptr ep) noexcept
|
||||
return ACK_ERROR_SYSTEM;
|
||||
} catch (const std::invalid_argument &e) {
|
||||
return ACK_ERROR_ARG;
|
||||
} catch (const std::length_error &e) {
|
||||
return ACK_ERROR_ARG;
|
||||
} catch (const std::out_of_range &e) {
|
||||
return ACK_ERROR_ARG;
|
||||
#ifdef GLIBCXX_49X
|
||||
} catch (const std::exception &e) {
|
||||
#else
|
||||
|
@@ -325,6 +325,34 @@ SendConstraints(mpd_connection *connection, const DatabaseSelection &selection)
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
SendGroupMask(mpd_connection *connection, tag_mask_t mask)
|
||||
{
|
||||
#if LIBMPDCLIENT_CHECK_VERSION(2,12,0)
|
||||
for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) {
|
||||
if ((mask & (tag_mask_t(1) << i)) == 0)
|
||||
continue;
|
||||
|
||||
const auto tag = Convert(TagType(i));
|
||||
if (tag == MPD_TAG_COUNT)
|
||||
throw std::runtime_error("Unsupported tag");
|
||||
|
||||
if (!mpd_search_add_group_tag(connection, tag))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
#else
|
||||
(void)connection;
|
||||
(void)mask;
|
||||
|
||||
if (mask != 0)
|
||||
throw std::runtime_error("Grouping requires libmpdclient 2.12");
|
||||
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
ProxyDatabase::ProxyDatabase(EventLoop &_loop, DatabaseListener &_listener,
|
||||
const ConfigBlock &block)
|
||||
:Database(proxy_db_plugin),
|
||||
@@ -682,7 +710,7 @@ static void
|
||||
SearchSongs(struct mpd_connection *connection,
|
||||
const DatabaseSelection &selection,
|
||||
VisitSong visit_song)
|
||||
{
|
||||
try {
|
||||
assert(selection.recursive);
|
||||
assert(visit_song);
|
||||
|
||||
@@ -709,6 +737,11 @@ SearchSongs(struct mpd_connection *connection,
|
||||
|
||||
if (!mpd_response_finish(connection))
|
||||
ThrowError(connection);
|
||||
} catch (...) {
|
||||
if (connection != nullptr)
|
||||
mpd_search_cancel(connection);
|
||||
|
||||
throw;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -756,9 +789,9 @@ ProxyDatabase::Visit(const DatabaseSelection &selection,
|
||||
void
|
||||
ProxyDatabase::VisitUniqueTags(const DatabaseSelection &selection,
|
||||
TagType tag_type,
|
||||
gcc_unused tag_mask_t group_mask,
|
||||
tag_mask_t group_mask,
|
||||
VisitTag visit_tag) const
|
||||
{
|
||||
try {
|
||||
// TODO: eliminate the const_cast
|
||||
const_cast<ProxyDatabase *>(this)->EnsureConnected();
|
||||
|
||||
@@ -767,32 +800,47 @@ ProxyDatabase::VisitUniqueTags(const DatabaseSelection &selection,
|
||||
throw std::runtime_error("Unsupported tag");
|
||||
|
||||
if (!mpd_search_db_tags(connection, tag_type2) ||
|
||||
!SendConstraints(connection, selection))
|
||||
!SendConstraints(connection, selection) ||
|
||||
!SendGroupMask(connection, group_mask))
|
||||
ThrowError(connection);
|
||||
|
||||
// TODO: use group_mask
|
||||
|
||||
if (!mpd_search_commit(connection))
|
||||
ThrowError(connection);
|
||||
|
||||
while (auto *pair = mpd_recv_pair_tag(connection, tag_type2)) {
|
||||
TagBuilder builder;
|
||||
|
||||
while (auto *pair = mpd_recv_pair(connection)) {
|
||||
AtScopeExit(this, pair) {
|
||||
mpd_return_pair(connection, pair);
|
||||
};
|
||||
|
||||
TagBuilder tag;
|
||||
tag.AddItem(tag_type, pair->value);
|
||||
const auto current_type = tag_name_parse_i(pair->name);
|
||||
if (current_type == TAG_NUM_OF_ITEM_TYPES)
|
||||
continue;
|
||||
|
||||
if (tag.IsEmpty())
|
||||
if (current_type == tag_type && !builder.IsEmpty()) {
|
||||
try {
|
||||
visit_tag(builder.Commit());
|
||||
} catch (...) {
|
||||
mpd_response_finish(connection);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
builder.AddItem(current_type, pair->value);
|
||||
|
||||
if (!builder.HasType(current_type))
|
||||
/* if no tag item has been added, then the
|
||||
given value was not acceptable
|
||||
(e.g. empty); forcefully insert an empty
|
||||
tag in this case, as the caller expects the
|
||||
given tag type to be present */
|
||||
tag.AddEmptyItem(tag_type);
|
||||
builder.AddEmptyItem(current_type);
|
||||
}
|
||||
|
||||
if (!builder.IsEmpty()) {
|
||||
try {
|
||||
visit_tag(tag.Commit());
|
||||
visit_tag(builder.Commit());
|
||||
} catch (...) {
|
||||
mpd_response_finish(connection);
|
||||
throw;
|
||||
@@ -801,6 +849,11 @@ ProxyDatabase::VisitUniqueTags(const DatabaseSelection &selection,
|
||||
|
||||
if (!mpd_response_finish(connection))
|
||||
ThrowError(connection);
|
||||
} catch (...) {
|
||||
if (connection != nullptr)
|
||||
mpd_search_cancel(connection);
|
||||
|
||||
throw;
|
||||
}
|
||||
|
||||
DatabaseStats
|
||||
|
@@ -82,10 +82,11 @@ directory_save(BufferedOutputStream &os, const Directory &directory)
|
||||
}
|
||||
|
||||
for (const auto &child : directory.children) {
|
||||
os.Format(DIRECTORY_DIR "%s\n", child.GetName());
|
||||
if (child.IsMount())
|
||||
continue;
|
||||
|
||||
if (!child.IsMount())
|
||||
directory_save(os, child);
|
||||
os.Format(DIRECTORY_DIR "%s\n", child.GetName());
|
||||
directory_save(os, child);
|
||||
}
|
||||
|
||||
for (const auto &song : directory.songs)
|
||||
|
@@ -20,18 +20,12 @@
|
||||
#include "config.h"
|
||||
#include "Mount.hxx"
|
||||
#include "PrefixedLightSong.hxx"
|
||||
#include "SongFilter.hxx"
|
||||
#include "db/Selection.hxx"
|
||||
#include "db/LightDirectory.hxx"
|
||||
#include "db/Interface.hxx"
|
||||
#include "fs/Traits.hxx"
|
||||
|
||||
#ifdef _LIBCPP_VERSION
|
||||
/* workaround for "error: incomplete type 'PlaylistInfo' used in type
|
||||
trait expression" with libc++ version 3900 (from Android NDK
|
||||
r13b) */
|
||||
#include "db/PlaylistInfo.hxx"
|
||||
#endif
|
||||
|
||||
#include <string>
|
||||
|
||||
struct PrefixedLightDirectory : LightDirectory {
|
||||
@@ -93,5 +87,16 @@ WalkMount(const char *base, const Database &db,
|
||||
vp = std::bind(PrefixVisitPlaylist,
|
||||
base, std::ref(visit_playlist), _1, _2);
|
||||
|
||||
SongFilter prefix_filter;
|
||||
|
||||
if (base != nullptr && filter != nullptr) {
|
||||
/* if the SongFilter contains a LOCATE_TAG_BASE_TYPE
|
||||
item, copy the SongFilter and drop the mount point
|
||||
from the filter, because the mounted database
|
||||
doesn't know its own location within MPD's VFS */
|
||||
prefix_filter = filter->WithoutBasePrefix(base);
|
||||
filter = &prefix_filter;
|
||||
}
|
||||
|
||||
db.Visit(DatabaseSelection(uri, recursive, filter), vd, vs, vp);
|
||||
}
|
||||
|
@@ -49,6 +49,10 @@ struct UpdateQueueItem {
|
||||
bool IsDefined() const {
|
||||
return id != 0;
|
||||
}
|
||||
|
||||
void Clear() {
|
||||
id = 0;
|
||||
}
|
||||
};
|
||||
|
||||
class UpdateQueue {
|
||||
|
@@ -29,6 +29,7 @@
|
||||
#include "Idle.hxx"
|
||||
#include "Log.hxx"
|
||||
#include "thread/Thread.hxx"
|
||||
#include "thread/Name.hxx"
|
||||
#include "thread/Util.hxx"
|
||||
|
||||
#ifndef NDEBUG
|
||||
@@ -43,6 +44,7 @@ UpdateService::UpdateService(EventLoop &_loop, SimpleDatabase &_db,
|
||||
:DeferredMonitor(_loop),
|
||||
db(_db), storage(_storage),
|
||||
listener(_listener),
|
||||
update_thread(BIND_THIS_METHOD(Task)),
|
||||
update_task_id(0),
|
||||
walk(nullptr)
|
||||
{
|
||||
@@ -112,6 +114,8 @@ UpdateService::Task()
|
||||
{
|
||||
assert(walk != nullptr);
|
||||
|
||||
SetThreadName("update");
|
||||
|
||||
if (!next.path_utf8.empty())
|
||||
FormatDebug(update_domain, "starting: %s",
|
||||
next.path_utf8.c_str());
|
||||
@@ -140,13 +144,6 @@ UpdateService::Task()
|
||||
DeferredMonitor::Schedule();
|
||||
}
|
||||
|
||||
void
|
||||
UpdateService::Task(void *ctx)
|
||||
{
|
||||
UpdateService &service = *(UpdateService *)ctx;
|
||||
return service.Task();
|
||||
}
|
||||
|
||||
void
|
||||
UpdateService::StartThread(UpdateQueueItem &&i)
|
||||
{
|
||||
@@ -158,7 +155,7 @@ UpdateService::StartThread(UpdateQueueItem &&i)
|
||||
next = std::move(i);
|
||||
walk = new UpdateWalk(GetEventLoop(), listener, *next.storage);
|
||||
|
||||
update_thread.Start(Task, this);
|
||||
update_thread.Start();
|
||||
|
||||
FormatDebug(update_domain,
|
||||
"spawned thread for update job id %i", next.id);
|
||||
@@ -258,7 +255,7 @@ UpdateService::RunDeferred()
|
||||
delete walk;
|
||||
walk = nullptr;
|
||||
|
||||
next = UpdateQueueItem();
|
||||
next.Clear();
|
||||
|
||||
idle_add(IDLE_UPDATE);
|
||||
|
||||
|
@@ -98,7 +98,6 @@ private:
|
||||
|
||||
/* the update thread */
|
||||
void Task();
|
||||
static void Task(void *ctx);
|
||||
|
||||
void StartThread(UpdateQueueItem &&i);
|
||||
|
||||
|
@@ -300,6 +300,7 @@ DecoderBridge::CommandFinished()
|
||||
|
||||
initial_seek_running = false;
|
||||
timestamp = dc.start_time.ToDoubleS();
|
||||
absolute_frame = dc.start_time.ToScale<uint64_t>(dc.in_audio_format.sample_rate);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -319,6 +320,7 @@ DecoderBridge::CommandFinished()
|
||||
convert->Reset();
|
||||
|
||||
timestamp = dc.seek_time.ToDoubleS();
|
||||
absolute_frame = dc.seek_time.ToScale<uint64_t>(dc.in_audio_format.sample_rate);
|
||||
}
|
||||
|
||||
dc.command = DecoderCommand::NONE;
|
||||
@@ -427,6 +429,7 @@ DecoderBridge::SubmitTimestamp(double t)
|
||||
assert(t >= 0);
|
||||
|
||||
timestamp = t;
|
||||
absolute_frame = uint64_t(t * dc.in_audio_format.sample_rate);
|
||||
}
|
||||
|
||||
DecoderCommand
|
||||
@@ -464,6 +467,29 @@ DecoderBridge::SubmitData(InputStream *is,
|
||||
return cmd;
|
||||
}
|
||||
|
||||
cmd = DecoderCommand::NONE;
|
||||
|
||||
const size_t frame_size = dc.in_audio_format.GetFrameSize();
|
||||
size_t data_frames = length / frame_size;
|
||||
|
||||
if (dc.end_time.IsPositive()) {
|
||||
/* enforce the given end time */
|
||||
|
||||
const uint64_t end_frame =
|
||||
dc.end_time.ToScale<uint64_t>(dc.in_audio_format.sample_rate);
|
||||
if (absolute_frame >= end_frame)
|
||||
return DecoderCommand::STOP;
|
||||
|
||||
const uint64_t remaining_frames = end_frame - absolute_frame;
|
||||
if (data_frames >= remaining_frames) {
|
||||
/* past the end of the range: truncate this
|
||||
data submission and stop the decoder */
|
||||
data_frames = remaining_frames;
|
||||
length = data_frames * frame_size;
|
||||
cmd = DecoderCommand::STOP;
|
||||
}
|
||||
}
|
||||
|
||||
if (convert != nullptr) {
|
||||
assert(dc.in_audio_format != dc.out_audio_format);
|
||||
|
||||
@@ -521,15 +547,11 @@ DecoderBridge::SubmitData(InputStream *is,
|
||||
|
||||
timestamp += (double)nbytes /
|
||||
dc.out_audio_format.GetTimeToSize();
|
||||
|
||||
if (dc.end_time.IsPositive() &&
|
||||
timestamp >= dc.end_time.ToDoubleS())
|
||||
/* the end of this range has been reached:
|
||||
stop decoding */
|
||||
return DecoderCommand::STOP;
|
||||
}
|
||||
|
||||
return DecoderCommand::NONE;
|
||||
absolute_frame += data_frames;
|
||||
|
||||
return cmd;
|
||||
}
|
||||
|
||||
DecoderCommand
|
||||
|
@@ -49,6 +49,11 @@ public:
|
||||
*/
|
||||
double timestamp = 0;
|
||||
|
||||
/**
|
||||
* The time stamp of the next data chunk, in PCM frames.
|
||||
*/
|
||||
uint64_t absolute_frame = 0;
|
||||
|
||||
/**
|
||||
* Is the initial seek (to the start position of the sub-song)
|
||||
* pending, or has it been performed already?
|
||||
|
@@ -30,7 +30,8 @@
|
||||
DecoderControl::DecoderControl(Mutex &_mutex, Cond &_client_cond,
|
||||
const AudioFormat _configured_audio_format,
|
||||
const ReplayGainConfig &_replay_gain_config)
|
||||
:mutex(_mutex), client_cond(_client_cond),
|
||||
:thread(BIND_THIS_METHOD(RunThread)),
|
||||
mutex(_mutex), client_cond(_client_cond),
|
||||
configured_audio_format(_configured_audio_format),
|
||||
replay_gain_config(_replay_gain_config) {}
|
||||
|
||||
|
@@ -308,9 +308,14 @@ struct DecoderControl {
|
||||
bool IsCurrentSong(const DetachedSong &_song) const noexcept;
|
||||
|
||||
gcc_pure
|
||||
bool LockIsCurrentSong(const DetachedSong &_song) const noexcept {
|
||||
bool IsSeekableCurrentSong(const DetachedSong &_song) const noexcept {
|
||||
return seekable && IsCurrentSong(_song);
|
||||
}
|
||||
|
||||
gcc_pure
|
||||
bool LockIsSeeakbleCurrentSong(const DetachedSong &_song) const noexcept {
|
||||
const std::lock_guard<Mutex> protect(mutex);
|
||||
return IsCurrentSong(_song);
|
||||
return IsSeekableCurrentSong(_song);
|
||||
}
|
||||
|
||||
private:
|
||||
@@ -415,6 +420,9 @@ public:
|
||||
* mixramp_start/mixramp_end.
|
||||
*/
|
||||
void CycleMixRamp();
|
||||
|
||||
private:
|
||||
void RunThread();
|
||||
};
|
||||
|
||||
#endif
|
||||
|
@@ -513,30 +513,28 @@ try {
|
||||
dc.client_cond.signal();
|
||||
}
|
||||
|
||||
static void
|
||||
decoder_task(void *arg)
|
||||
void
|
||||
DecoderControl::RunThread()
|
||||
{
|
||||
DecoderControl &dc = *(DecoderControl *)arg;
|
||||
|
||||
SetThreadName("decoder");
|
||||
|
||||
const std::lock_guard<Mutex> protect(dc.mutex);
|
||||
const std::lock_guard<Mutex> protect(mutex);
|
||||
|
||||
do {
|
||||
assert(dc.state == DecoderState::STOP ||
|
||||
dc.state == DecoderState::ERROR);
|
||||
assert(state == DecoderState::STOP ||
|
||||
state == DecoderState::ERROR);
|
||||
|
||||
switch (dc.command) {
|
||||
switch (command) {
|
||||
case DecoderCommand::START:
|
||||
dc.CycleMixRamp();
|
||||
dc.replay_gain_prev_db = dc.replay_gain_db;
|
||||
dc.replay_gain_db = 0;
|
||||
CycleMixRamp();
|
||||
replay_gain_prev_db = replay_gain_db;
|
||||
replay_gain_db = 0;
|
||||
|
||||
decoder_run(dc);
|
||||
decoder_run(*this);
|
||||
|
||||
if (dc.state == DecoderState::ERROR) {
|
||||
if (state == DecoderState::ERROR) {
|
||||
try {
|
||||
std::rethrow_exception(dc.error);
|
||||
std::rethrow_exception(error);
|
||||
} catch (const std::exception &e) {
|
||||
LogError(e);
|
||||
} catch (...) {
|
||||
@@ -552,20 +550,20 @@ decoder_task(void *arg)
|
||||
/* we need to clear the pipe here; usually the
|
||||
PlayerThread is responsible, but it is not
|
||||
aware that the decoder has finished */
|
||||
dc.pipe->Clear(*dc.buffer);
|
||||
pipe->Clear(*buffer);
|
||||
|
||||
decoder_run(dc);
|
||||
decoder_run(*this);
|
||||
break;
|
||||
|
||||
case DecoderCommand::STOP:
|
||||
dc.CommandFinishedLocked();
|
||||
CommandFinishedLocked();
|
||||
break;
|
||||
|
||||
case DecoderCommand::NONE:
|
||||
dc.Wait();
|
||||
Wait();
|
||||
break;
|
||||
}
|
||||
} while (dc.command != DecoderCommand::NONE || !dc.quit);
|
||||
} while (command != DecoderCommand::NONE || !quit);
|
||||
}
|
||||
|
||||
void
|
||||
@@ -574,5 +572,5 @@ decoder_thread_start(DecoderControl &dc)
|
||||
assert(!dc.thread.IsDefined());
|
||||
|
||||
dc.quit = false;
|
||||
dc.thread.Start(decoder_task, &dc);
|
||||
dc.thread.Start();
|
||||
}
|
||||
|
@@ -128,7 +128,7 @@ dsdlib_tag_id3(InputStream &is,
|
||||
return;
|
||||
|
||||
const auto count64 = size - tagoffset;
|
||||
if (count64 < 10 || count64 > 1024 * 1024)
|
||||
if (count64 < 10 || count64 > 4 * 1024 * 1024)
|
||||
return;
|
||||
|
||||
if (!dsdlib_skip_to(nullptr, is, tagoffset))
|
||||
|
@@ -484,6 +484,8 @@ static const char *const dsdiff_suffixes[] = {
|
||||
|
||||
static const char *const dsdiff_mime_types[] = {
|
||||
"application/x-dff",
|
||||
"audio/x-dff",
|
||||
"audio/x-dsd",
|
||||
nullptr
|
||||
};
|
||||
|
||||
|
@@ -358,6 +358,8 @@ static const char *const dsf_suffixes[] = {
|
||||
|
||||
static const char *const dsf_mime_types[] = {
|
||||
"application/x-dsf",
|
||||
"audio/x-dsf",
|
||||
"audio/x-dsd",
|
||||
nullptr
|
||||
};
|
||||
|
||||
|
@@ -24,7 +24,6 @@
|
||||
#include "config.h"
|
||||
#include "FlacCommon.hxx"
|
||||
#include "FlacMetadata.hxx"
|
||||
#include "util/ConstBuffer.hxx"
|
||||
#include "Log.hxx"
|
||||
|
||||
#include <stdexcept>
|
||||
@@ -143,25 +142,10 @@ FlacDecoder::OnWrite(const FLAC__Frame &frame,
|
||||
if (!initialized && !OnFirstFrame(frame.header))
|
||||
return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT;
|
||||
|
||||
const auto data = pcm_import.Import(buf, frame.header.blocksize);
|
||||
chunk = pcm_import.Import(buf, frame.header.blocksize);
|
||||
|
||||
unsigned bit_rate = nbytes * 8 * frame.header.sample_rate /
|
||||
kbit_rate = nbytes * 8 * frame.header.sample_rate /
|
||||
(1000 * frame.header.blocksize);
|
||||
|
||||
auto cmd = GetClient()->SubmitData(GetInputStream(),
|
||||
data.data, data.size,
|
||||
bit_rate);
|
||||
switch (cmd) {
|
||||
case DecoderCommand::NONE:
|
||||
case DecoderCommand::START:
|
||||
break;
|
||||
|
||||
case DecoderCommand::STOP:
|
||||
return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT;
|
||||
|
||||
case DecoderCommand::SEEK:
|
||||
return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE;
|
||||
}
|
||||
|
||||
return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE;
|
||||
}
|
||||
|
@@ -27,6 +27,7 @@
|
||||
#include "FlacInput.hxx"
|
||||
#include "FlacPcm.hxx"
|
||||
#include "../DecoderAPI.hxx"
|
||||
#include "util/ConstBuffer.hxx"
|
||||
|
||||
#include <FLAC/stream_decoder.h>
|
||||
|
||||
@@ -41,6 +42,12 @@ struct FlacDecoder : public FlacInput {
|
||||
*/
|
||||
bool unsupported = false;
|
||||
|
||||
/**
|
||||
* The kbit_rate parameter for the next
|
||||
* DecoderBridge::SubmitData() call.
|
||||
*/
|
||||
uint16_t kbit_rate;
|
||||
|
||||
FlacPcmImport pcm_import;
|
||||
|
||||
/**
|
||||
@@ -51,6 +58,13 @@ struct FlacDecoder : public FlacInput {
|
||||
|
||||
Tag tag;
|
||||
|
||||
/**
|
||||
* Decoded PCM data obtained by our libFLAC write callback.
|
||||
* If this is non-empty, then DecoderBridge::SubmitData()
|
||||
* should be called.
|
||||
*/
|
||||
ConstBuffer<void> chunk = nullptr;
|
||||
|
||||
FlacDecoder(DecoderClient &_client, InputStream &_input_stream)
|
||||
:FlacInput(_input_stream, &_client) {}
|
||||
|
||||
|
@@ -139,19 +139,40 @@ flac_decoder_initialize(FlacDecoder *data, FLAC__StreamDecoder *sd)
|
||||
return data->initialized;
|
||||
}
|
||||
|
||||
static DecoderCommand
|
||||
FlacSubmitToClient(DecoderClient &client, FlacDecoder &d) noexcept
|
||||
{
|
||||
if (d.tag.IsEmpty() && d.chunk.IsEmpty())
|
||||
return client.GetCommand();
|
||||
|
||||
if (!d.tag.IsEmpty()) {
|
||||
auto cmd = client.SubmitTag(d.GetInputStream(),
|
||||
std::move(d.tag));
|
||||
d.tag.Clear();
|
||||
if (cmd != DecoderCommand::NONE)
|
||||
return cmd;
|
||||
}
|
||||
|
||||
if (!d.chunk.IsEmpty()) {
|
||||
auto cmd = client.SubmitData(d.GetInputStream(),
|
||||
d.chunk.data,
|
||||
d.chunk.size,
|
||||
d.kbit_rate);
|
||||
d.chunk = nullptr;
|
||||
if (cmd != DecoderCommand::NONE)
|
||||
return cmd;
|
||||
}
|
||||
|
||||
return DecoderCommand::NONE;
|
||||
}
|
||||
|
||||
static void
|
||||
flac_decoder_loop(FlacDecoder *data, FLAC__StreamDecoder *flac_dec)
|
||||
{
|
||||
DecoderClient &client = *data->GetClient();
|
||||
|
||||
while (true) {
|
||||
DecoderCommand cmd;
|
||||
if (!data->tag.IsEmpty()) {
|
||||
cmd = client.SubmitTag(data->GetInputStream(),
|
||||
std::move(data->tag));
|
||||
data->tag.Clear();
|
||||
} else
|
||||
cmd = client.GetCommand();
|
||||
DecoderCommand cmd = FlacSubmitToClient(client, *data);
|
||||
|
||||
if (cmd == DecoderCommand::SEEK) {
|
||||
FLAC__uint64 seek_sample = client.GetSeekFrame();
|
||||
@@ -160,6 +181,11 @@ flac_decoder_loop(FlacDecoder *data, FLAC__StreamDecoder *flac_dec)
|
||||
client.CommandFinished();
|
||||
} else
|
||||
client.SeekError();
|
||||
|
||||
/* FLAC__stream_decoder_seek_absolute()
|
||||
decodes one frame and may have provided
|
||||
data to be submitted to the client */
|
||||
continue;
|
||||
} else if (cmd == DecoderCommand::STOP)
|
||||
break;
|
||||
|
||||
|
@@ -53,6 +53,14 @@ 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) {
|
||||
/* R128_ALBUM_GAIN is a Q7.8 fixed point number in
|
||||
dB */
|
||||
|
||||
char *endptr;
|
||||
long l = strtol(value, &endptr, 10);
|
||||
if (endptr > value && *endptr == 0)
|
||||
rgi->album.gain = double(l) / 256.;
|
||||
}
|
||||
|
||||
tag_handler_invoke_pair(handler, ctx, name, value);
|
||||
|
@@ -52,7 +52,7 @@ class OpusEncoder final : public OggEncoder {
|
||||
|
||||
ogg_int64_t packetno = 0;
|
||||
|
||||
ogg_int64_t granulepos;
|
||||
ogg_int64_t granulepos = 0;
|
||||
|
||||
public:
|
||||
OpusEncoder(AudioFormat &_audio_format, ::OpusEncoder *_enc);
|
||||
|
@@ -27,7 +27,7 @@
|
||||
#include <algorithm>
|
||||
|
||||
EventLoop::EventLoop()
|
||||
:SocketMonitor(*this)
|
||||
:SocketMonitor(*this), quit(false)
|
||||
{
|
||||
SocketMonitor::Open(wake_fd.Get());
|
||||
SocketMonitor::Schedule(SocketMonitor::READ);
|
||||
@@ -46,7 +46,9 @@ EventLoop::~EventLoop()
|
||||
void
|
||||
EventLoop::Break()
|
||||
{
|
||||
quit = true;
|
||||
if (quit.exchange(true))
|
||||
return;
|
||||
|
||||
wake_fd.Write();
|
||||
}
|
||||
|
||||
|
@@ -30,6 +30,7 @@
|
||||
#include "SocketMonitor.hxx"
|
||||
|
||||
#include <chrono>
|
||||
#include <atomic>
|
||||
#include <list>
|
||||
#include <set>
|
||||
|
||||
@@ -82,7 +83,7 @@ class EventLoop final : SocketMonitor
|
||||
|
||||
std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now();
|
||||
|
||||
bool quit = false;
|
||||
std::atomic_bool quit;
|
||||
|
||||
/**
|
||||
* True when the object has been modified and another check is
|
||||
|
@@ -73,6 +73,10 @@ public:
|
||||
return filter;
|
||||
}
|
||||
|
||||
void Reset() override {
|
||||
filter->Reset();
|
||||
}
|
||||
|
||||
ConstBuffer<void> FilterPCM(ConstBuffer<void> src) override {
|
||||
return filter->FilterPCM(src);
|
||||
}
|
||||
|
@@ -53,10 +53,16 @@ public:
|
||||
void Set(const AudioFormat &_out_audio_format);
|
||||
|
||||
void Reset() override {
|
||||
state.Reset();
|
||||
if (IsActive())
|
||||
state.Reset();
|
||||
}
|
||||
|
||||
ConstBuffer<void> FilterPCM(ConstBuffer<void> src) override;
|
||||
|
||||
private:
|
||||
bool IsActive() const noexcept {
|
||||
return out_audio_format != in_audio_format;
|
||||
}
|
||||
};
|
||||
|
||||
class PreparedConvertFilter final : public PreparedFilter {
|
||||
@@ -80,7 +86,7 @@ ConvertFilter::Set(const AudioFormat &_out_audio_format)
|
||||
/* no change */
|
||||
return;
|
||||
|
||||
if (out_audio_format != in_audio_format) {
|
||||
if (IsActive()) {
|
||||
out_audio_format = in_audio_format;
|
||||
state.Close();
|
||||
}
|
||||
@@ -111,7 +117,7 @@ ConvertFilter::~ConvertFilter()
|
||||
{
|
||||
assert(in_audio_format.IsValid());
|
||||
|
||||
if (out_audio_format != in_audio_format)
|
||||
if (IsActive())
|
||||
state.Close();
|
||||
}
|
||||
|
||||
@@ -120,11 +126,10 @@ ConvertFilter::FilterPCM(ConstBuffer<void> src)
|
||||
{
|
||||
assert(in_audio_format.IsValid());
|
||||
|
||||
if (out_audio_format == in_audio_format)
|
||||
return IsActive()
|
||||
? state.Convert(src)
|
||||
/* optimized special case: no-op */
|
||||
return src;
|
||||
|
||||
return state.Convert(src);
|
||||
: src;
|
||||
}
|
||||
|
||||
const FilterPlugin convert_filter_plugin = {
|
||||
|
@@ -26,8 +26,12 @@
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
|
||||
ThreadInputStream::~ThreadInputStream()
|
||||
void
|
||||
ThreadInputStream::Stop() noexcept
|
||||
{
|
||||
if (!thread.IsDefined())
|
||||
return;
|
||||
|
||||
{
|
||||
const std::lock_guard<Mutex> lock(mutex);
|
||||
close = true;
|
||||
@@ -42,6 +46,7 @@ ThreadInputStream::~ThreadInputStream()
|
||||
buffer->Clear();
|
||||
HugeFree(buffer->Write().data, buffer_size);
|
||||
delete buffer;
|
||||
buffer = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,10 +59,10 @@ ThreadInputStream::Start()
|
||||
assert(p != nullptr);
|
||||
|
||||
buffer = new CircularBuffer<uint8_t>((uint8_t *)p, buffer_size);
|
||||
thread.Start(ThreadFunc, this);
|
||||
thread.Start();
|
||||
}
|
||||
|
||||
inline void
|
||||
void
|
||||
ThreadInputStream::ThreadFunc()
|
||||
{
|
||||
FormatThreadName("input:%s", plugin);
|
||||
@@ -68,7 +73,7 @@ ThreadInputStream::ThreadFunc()
|
||||
Open();
|
||||
} catch (...) {
|
||||
postponed_exception = std::current_exception();
|
||||
cond.broadcast();
|
||||
SetReady();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -107,13 +112,6 @@ ThreadInputStream::ThreadFunc()
|
||||
Close();
|
||||
}
|
||||
|
||||
void
|
||||
ThreadInputStream::ThreadFunc(void *ctx)
|
||||
{
|
||||
ThreadInputStream &tis = *(ThreadInputStream *)ctx;
|
||||
tis.ThreadFunc();
|
||||
}
|
||||
|
||||
void
|
||||
ThreadInputStream::Check()
|
||||
{
|
||||
|
@@ -27,6 +27,7 @@
|
||||
|
||||
#include <exception>
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdint.h>
|
||||
|
||||
template<typename T> class CircularBuffer;
|
||||
@@ -39,6 +40,11 @@ template<typename T> class CircularBuffer;
|
||||
* manages the thread and the buffer.
|
||||
*
|
||||
* This works only for "streams": unknown length, no seeking, no tags.
|
||||
*
|
||||
* The implementation must call Stop() before its destruction
|
||||
* completes. This cannot be done in ~ThreadInputStream() because at
|
||||
* this point, the class has been morphed back to #ThreadInputStream
|
||||
* and the still-running thread will crash due to pure method call.
|
||||
*/
|
||||
class ThreadInputStream : public InputStream {
|
||||
const char *const plugin;
|
||||
@@ -73,9 +79,16 @@ public:
|
||||
size_t _buffer_size)
|
||||
:InputStream(_uri, _mutex, _cond),
|
||||
plugin(_plugin),
|
||||
thread(BIND_THIS_METHOD(ThreadFunc)),
|
||||
buffer_size(_buffer_size) {}
|
||||
|
||||
virtual ~ThreadInputStream();
|
||||
#ifndef NDEBUG
|
||||
~ThreadInputStream() override {
|
||||
/* Stop() must have been called already */
|
||||
assert(!thread.IsDefined());
|
||||
assert(buffer == nullptr);
|
||||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Initialize the object and start the thread.
|
||||
@@ -89,6 +102,12 @@ public:
|
||||
size_t Read(void *ptr, size_t size) override final;
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Stop the thread and free the buffer. This must be called
|
||||
* before destruction of this object completes.
|
||||
*/
|
||||
void Stop() noexcept;
|
||||
|
||||
void SetMimeType(const char *_mime) {
|
||||
assert(thread.IsInside());
|
||||
|
||||
@@ -138,7 +157,6 @@ protected:
|
||||
|
||||
private:
|
||||
void ThreadFunc();
|
||||
static void ThreadFunc(void *ctx);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
@@ -270,6 +270,12 @@ AlsaInputStream::Recover(int err)
|
||||
/* this is no error, so just keep running */
|
||||
err = 0;
|
||||
break;
|
||||
|
||||
default:
|
||||
/* this default case is just here to work around
|
||||
-Wswitch due to SND_PCM_STATE_PRIVATE1 (libasound
|
||||
1.1.6) */
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
|
@@ -266,6 +266,7 @@ CurlInputStream::OnData(ConstBuffer<void> data)
|
||||
void
|
||||
CurlInputStream::OnEnd()
|
||||
{
|
||||
const std::lock_guard<Mutex> protect(mutex);
|
||||
cond.broadcast();
|
||||
|
||||
AsyncInputStream::SetClosed();
|
||||
@@ -274,6 +275,7 @@ CurlInputStream::OnEnd()
|
||||
void
|
||||
CurlInputStream::OnError(std::exception_ptr e)
|
||||
{
|
||||
const std::lock_guard<Mutex> protect(mutex);
|
||||
postponed_exception = std::move(e);
|
||||
|
||||
if (IsSeekPending())
|
||||
|
@@ -65,9 +65,12 @@ OpenFileInputStream(Path path,
|
||||
throw FormatRuntimeError("Not a regular file: %s",
|
||||
path.c_str());
|
||||
|
||||
#if !defined(__BIONIC__) || __ANDROID_API__ >= 21
|
||||
/* posix_fadvise() requires Android API 21 */
|
||||
#ifdef POSIX_FADV_SEQUENTIAL
|
||||
posix_fadvise(reader.GetFD().Get(), (off_t)0, info.GetSize(),
|
||||
POSIX_FADV_SEQUENTIAL);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
return InputStreamPtr(new FileInputStream(path.ToUTF8().c_str(),
|
||||
|
@@ -39,6 +39,10 @@ public:
|
||||
MMS_BUFFER_SIZE) {
|
||||
}
|
||||
|
||||
~MmsInputStream() noexcept override {
|
||||
Stop();
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual void Open() override;
|
||||
virtual size_t ThreadRead(void *ptr, size_t size) override;
|
||||
|
@@ -62,6 +62,7 @@ CurlRequest::CurlRequest(CurlGlobal &_global, const char *url,
|
||||
easy.SetOption(CURLOPT_NOPROGRESS, 1l);
|
||||
easy.SetOption(CURLOPT_NOSIGNAL, 1l);
|
||||
easy.SetOption(CURLOPT_CONNECTTIMEOUT, 10l);
|
||||
easy.SetOption(CURLOPT_HTTPAUTH, (long) CURLAUTH_ANY);
|
||||
easy.SetOption(CURLOPT_URL, url);
|
||||
}
|
||||
|
||||
|
20
src/lib/curl/patches/no_netrc.patch
Normal file
20
src/lib/curl/patches/no_netrc.patch
Normal file
@@ -0,0 +1,20 @@
|
||||
Index: curl-7.58.0/lib/url.c
|
||||
===================================================================
|
||||
--- curl-7.58.0.orig/lib/url.c
|
||||
+++ curl-7.58.0/lib/url.c
|
||||
@@ -3503,6 +3503,7 @@ static CURLcode override_login(struct Cu
|
||||
}
|
||||
|
||||
conn->bits.netrc = FALSE;
|
||||
+#ifndef __BIONIC__
|
||||
if(data->set.use_netrc != CURL_NETRC_IGNORED) {
|
||||
int ret = Curl_parsenetrc(conn->host.name,
|
||||
userp, passwdp,
|
||||
@@ -3524,6 +3525,7 @@ static CURLcode override_login(struct Cu
|
||||
conn->bits.user_passwd = TRUE; /* enable user+password */
|
||||
}
|
||||
}
|
||||
+#endif
|
||||
|
||||
return CURLE_OK;
|
||||
}
|
15
src/lib/curl/patches/only_lib.patch
Normal file
15
src/lib/curl/patches/only_lib.patch
Normal file
@@ -0,0 +1,15 @@
|
||||
Index: curl-7.58.0/Makefile.in
|
||||
===================================================================
|
||||
--- curl-7.58.0.orig/Makefile.in
|
||||
+++ curl-7.58.0/Makefile.in
|
||||
@@ -641,8 +641,8 @@ CLEANFILES = $(VC6_LIBDSP) $(VC6_SRCDSP)
|
||||
$(VC14_LIBVCXPROJ) $(VC14_SRCVCXPROJ) $(VC15_LIBVCXPROJ) $(VC15_SRCVCXPROJ)
|
||||
|
||||
bin_SCRIPTS = curl-config
|
||||
-SUBDIRS = lib src
|
||||
-DIST_SUBDIRS = $(SUBDIRS) tests packages scripts include docs
|
||||
+SUBDIRS = lib
|
||||
+DIST_SUBDIRS = $(SUBDIRS) include
|
||||
pkgconfigdir = $(libdir)/pkgconfig
|
||||
pkgconfig_DATA = libcurl.pc
|
||||
LIB_VAUTH_CFILES = vauth/vauth.c vauth/cleartext.c vauth/cram.c \
|
2
src/lib/curl/patches/series
Normal file
2
src/lib/curl/patches/series
Normal file
@@ -0,0 +1,2 @@
|
||||
only_lib.patch
|
||||
no_netrc.patch
|
@@ -33,6 +33,9 @@ FfmpegInit()
|
||||
{
|
||||
av_log_set_callback(FfmpegLogCallback);
|
||||
|
||||
#if LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(58, 9, 100)
|
||||
/* deprecated as of FFmpeg 4.0 */
|
||||
av_register_all();
|
||||
#endif
|
||||
}
|
||||
|
||||
|
@@ -37,6 +37,18 @@ public:
|
||||
|
||||
explicit IcuCompare(const char *needle) noexcept;
|
||||
|
||||
IcuCompare(const IcuCompare &src) noexcept
|
||||
:needle(src
|
||||
? AllocatedString<>::Duplicate(src.needle.c_str())
|
||||
: nullptr) {}
|
||||
|
||||
IcuCompare &operator=(const IcuCompare &src) noexcept {
|
||||
needle = src
|
||||
? AllocatedString<>::Duplicate(src.needle.c_str())
|
||||
: nullptr;
|
||||
return *this;
|
||||
}
|
||||
|
||||
IcuCompare(IcuCompare &&) = default;
|
||||
IcuCompare &operator=(IcuCompare &&) = default;
|
||||
|
||||
|
@@ -31,7 +31,11 @@ extern "C" {
|
||||
|
||||
#include <utility>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <winsock2.h>
|
||||
#else
|
||||
#include <poll.h> /* for POLLIN, POLLOUT */
|
||||
#endif
|
||||
|
||||
static constexpr std::chrono::steady_clock::duration NFS_MOUNT_TIMEOUT =
|
||||
std::chrono::minutes(1);
|
||||
|
@@ -31,7 +31,6 @@
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
NfsFileReader::NfsFileReader()
|
||||
:DeferredMonitor(io_thread_get()), state(State::INITIAL)
|
||||
|
@@ -31,6 +31,7 @@
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
struct nfsfh;
|
||||
class NfsConnection;
|
||||
|
@@ -34,7 +34,11 @@ static unsigned upnp_ref;
|
||||
static void
|
||||
DoInit()
|
||||
{
|
||||
auto code = UpnpInit(0, 0);
|
||||
#ifdef UPNP_ENABLE_IPV6
|
||||
auto code = UpnpInit2(nullptr, 0);
|
||||
#else
|
||||
auto code = UpnpInit(nullptr, 0);
|
||||
#endif
|
||||
if (code != UPNP_E_SUCCESS)
|
||||
throw FormatRuntimeError("UpnpInit() failed: %s",
|
||||
UpnpGetErrorMessage(code));
|
||||
|
@@ -139,6 +139,13 @@ static int set_normalized_volume(snd_mixer_elem_t *elem,
|
||||
return set_raw[ctl_dir](elem, value);
|
||||
}
|
||||
|
||||
/* two special cases to avoid rounding errors at 0% and
|
||||
100% */
|
||||
if (volume <= 0)
|
||||
return set_dB[ctl_dir](elem, min, dir);
|
||||
else if (volume >= 100)
|
||||
return set_dB[ctl_dir](elem, max, dir);
|
||||
|
||||
if (use_linear_dB_scale(min, max)) {
|
||||
value = lrint_dir(volume * (max - min), dir) + min;
|
||||
return set_dB[ctl_dir](elem, value, dir);
|
||||
|
@@ -69,7 +69,8 @@ class SmbclientNeighborExplorer final : public NeighborExplorer {
|
||||
|
||||
public:
|
||||
SmbclientNeighborExplorer(NeighborListener &_listener)
|
||||
:NeighborExplorer(_listener) {}
|
||||
:NeighborExplorer(_listener),
|
||||
thread(BIND_THIS_METHOD(ThreadFunc)) {}
|
||||
|
||||
/* virtual methods from class NeighborExplorer */
|
||||
void Open() override;
|
||||
@@ -79,14 +80,13 @@ public:
|
||||
private:
|
||||
void Run();
|
||||
void ThreadFunc();
|
||||
static void ThreadFunc(void *ctx);
|
||||
};
|
||||
|
||||
void
|
||||
SmbclientNeighborExplorer::Open()
|
||||
{
|
||||
quit = false;
|
||||
thread.Start(ThreadFunc, this);
|
||||
thread.Start();
|
||||
}
|
||||
|
||||
void
|
||||
@@ -239,6 +239,8 @@ SmbclientNeighborExplorer::Run()
|
||||
inline void
|
||||
SmbclientNeighborExplorer::ThreadFunc()
|
||||
{
|
||||
SetThreadName("smbclient");
|
||||
|
||||
mutex.lock();
|
||||
|
||||
while (!quit) {
|
||||
@@ -257,15 +259,6 @@ SmbclientNeighborExplorer::ThreadFunc()
|
||||
mutex.unlock();
|
||||
}
|
||||
|
||||
void
|
||||
SmbclientNeighborExplorer::ThreadFunc(void *ctx)
|
||||
{
|
||||
SetThreadName("smbclient");
|
||||
|
||||
SmbclientNeighborExplorer &e = *(SmbclientNeighborExplorer *)ctx;
|
||||
e.ThreadFunc();
|
||||
}
|
||||
|
||||
static NeighborExplorer *
|
||||
smbclient_neighbor_create(gcc_unused EventLoop &loop,
|
||||
NeighborListener &listener,
|
||||
|
49
src/net/Init.hxx
Normal file
49
src/net/Init.hxx
Normal file
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright 2003-2017 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#ifndef NET_INIT_HXX
|
||||
#define NET_INIT_HXX
|
||||
|
||||
#include "check.h"
|
||||
#include "SocketError.hxx"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <winsock2.h>
|
||||
#endif
|
||||
|
||||
class ScopeNetInit {
|
||||
#ifdef _WIN32
|
||||
public:
|
||||
ScopeNetInit() {
|
||||
WSADATA sockinfo;
|
||||
int retval = WSAStartup(MAKEWORD(2, 2), &sockinfo);
|
||||
if (retval != 0)
|
||||
throw MakeSocketError(retval, "WSAStartup() failed");
|
||||
}
|
||||
|
||||
~ScopeNetInit() noexcept {
|
||||
WSACleanup();
|
||||
}
|
||||
#else
|
||||
public:
|
||||
ScopeNetInit() {}
|
||||
#endif
|
||||
};
|
||||
|
||||
#endif
|
@@ -51,7 +51,8 @@
|
||||
|
||||
AudioOutput::AudioOutput(const AudioOutputPlugin &_plugin,
|
||||
const ConfigBlock &block)
|
||||
:plugin(_plugin)
|
||||
:plugin(_plugin),
|
||||
thread(BIND_THIS_METHOD(Task))
|
||||
{
|
||||
assert(plugin.finish != nullptr);
|
||||
assert(plugin.open != nullptr);
|
||||
|
@@ -515,7 +515,6 @@ private:
|
||||
* The OutputThread.
|
||||
*/
|
||||
void Task();
|
||||
static void Task(void *arg);
|
||||
};
|
||||
|
||||
/**
|
||||
|
@@ -396,7 +396,7 @@ AudioOutput::Pause()
|
||||
pause = false;
|
||||
}
|
||||
|
||||
inline void
|
||||
void
|
||||
AudioOutput::Task()
|
||||
{
|
||||
FormatThreadName("output:%s", name);
|
||||
@@ -512,17 +512,10 @@ AudioOutput::Task()
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
AudioOutput::Task(void *arg)
|
||||
{
|
||||
AudioOutput *ao = (AudioOutput *)arg;
|
||||
ao->Task();
|
||||
}
|
||||
|
||||
void
|
||||
AudioOutput::StartThread()
|
||||
{
|
||||
assert(command == Command::NONE);
|
||||
|
||||
thread.Start(Task, this);
|
||||
thread.Start();
|
||||
}
|
||||
|
@@ -846,6 +846,12 @@ AlsaOutput::Recover(int err)
|
||||
case SND_PCM_STATE_DRAINING:
|
||||
err = 0;
|
||||
break;
|
||||
|
||||
default:
|
||||
/* this default case is just here to work around
|
||||
-Wswitch due to SND_PCM_STATE_PRIVATE1 (libasound
|
||||
1.1.6) */
|
||||
break;
|
||||
}
|
||||
|
||||
return err;
|
||||
|
@@ -77,7 +77,6 @@ public:
|
||||
void Close();
|
||||
|
||||
size_t Play(const void *chunk, size_t size);
|
||||
void Cancel();
|
||||
|
||||
std::chrono::steady_clock::duration Delay() noexcept;
|
||||
|
||||
|
@@ -27,6 +27,7 @@
|
||||
#include "../Wrapper.hxx"
|
||||
#include "mixer/MixerList.hxx"
|
||||
#include "mixer/plugins/PulseMixerPlugin.hxx"
|
||||
#include "util/ScopeExit.hxx"
|
||||
#include "Log.hxx"
|
||||
|
||||
#include <pulse/thread-mainloop.h>
|
||||
@@ -854,7 +855,10 @@ PulseOutput::TestDefaultDevice()
|
||||
try {
|
||||
const ConfigBlock empty;
|
||||
PulseOutput po(empty);
|
||||
po.Enable();
|
||||
AtScopeExit(&po) { po.Disable(); };
|
||||
po.WaitConnection();
|
||||
|
||||
return true;
|
||||
} catch (const std::runtime_error &e) {
|
||||
return false;
|
||||
|
@@ -88,7 +88,7 @@ static inline ConstBuffer<V>
|
||||
ToAlsaChannelOrder71(PcmBuffer &buffer, ConstBuffer<V> src)
|
||||
{
|
||||
auto dest = buffer.GetT<V>(src.size);
|
||||
ToAlsaChannelOrder71(dest, src.data, src.size / 6);
|
||||
ToAlsaChannelOrder71(dest, src.data, src.size / 8);
|
||||
return { dest, src.size };
|
||||
}
|
||||
|
||||
|
@@ -46,19 +46,20 @@ pcm_dsd_to_dop(PcmBuffer &buffer, unsigned channels,
|
||||
assert(audio_valid_channel_count(channels));
|
||||
assert(_src.size % channels == 0);
|
||||
|
||||
const unsigned num_src_samples = _src.size;
|
||||
const unsigned num_src_frames = num_src_samples / channels;
|
||||
const size_t num_src_samples = _src.size;
|
||||
const size_t num_src_frames = num_src_samples / channels;
|
||||
|
||||
/* this rounds down and discards the last odd frame; not
|
||||
/* this rounds down and discards up to 3 odd frames; not
|
||||
elegant, but good enough for now */
|
||||
const unsigned num_frames = num_src_frames / 2;
|
||||
const unsigned num_samples = num_frames * channels;
|
||||
const size_t num_dop_quads = num_src_frames / 4;
|
||||
const size_t num_frames = num_dop_quads * 2;
|
||||
const size_t num_samples = num_frames * channels;
|
||||
|
||||
uint32_t *const dest0 = (uint32_t *)buffer.GetT<uint32_t>(num_samples),
|
||||
*dest = dest0;
|
||||
|
||||
auto src = _src.data;
|
||||
for (unsigned i = num_frames / 2; i > 0; --i) {
|
||||
for (size_t i = num_dop_quads; i > 0; --i) {
|
||||
for (unsigned c = channels; c > 0; --c) {
|
||||
/* each 24 bit sample has 16 DSD sample bits
|
||||
plus the magic 0x05 marker */
|
||||
|
@@ -122,7 +122,7 @@ sample_format_size(SampleFormat format)
|
||||
* @param format a #SampleFormat enum value
|
||||
* @return the string
|
||||
*/
|
||||
gcc_pure gcc_malloc
|
||||
gcc_pure
|
||||
const char *
|
||||
sample_format_to_string(SampleFormat format) noexcept;
|
||||
|
||||
|
@@ -139,6 +139,14 @@ SoxrPcmResampler::Close()
|
||||
soxr_delete(soxr);
|
||||
}
|
||||
|
||||
void
|
||||
SoxrPcmResampler::Reset()
|
||||
{
|
||||
#if SOXR_THIS_VERSION >= SOXR_VERSION(0,1,2)
|
||||
soxr_clear(soxr);
|
||||
#endif
|
||||
}
|
||||
|
||||
ConstBuffer<void>
|
||||
SoxrPcmResampler::Resample(ConstBuffer<void> src)
|
||||
{
|
||||
|
@@ -41,6 +41,7 @@ class SoxrPcmResampler final : public PcmResampler {
|
||||
public:
|
||||
AudioFormat Open(AudioFormat &af, unsigned new_sample_rate) override;
|
||||
void Close() override;
|
||||
void Reset() override;
|
||||
ConstBuffer<void> Resample(ConstBuffer<void> src) override;
|
||||
};
|
||||
|
||||
|
@@ -37,6 +37,7 @@ PlayerControl::PlayerControl(PlayerListener &_listener,
|
||||
buffer_chunks(_buffer_chunks),
|
||||
buffered_before_play(_buffered_before_play),
|
||||
configured_audio_format(_configured_audio_format),
|
||||
thread(BIND_THIS_METHOD(RunThread)),
|
||||
replay_gain_config(_replay_gain_config)
|
||||
{
|
||||
}
|
||||
|
@@ -537,6 +537,9 @@ public:
|
||||
void ApplyEnabled() override {
|
||||
LockUpdateAudio();
|
||||
}
|
||||
|
||||
private:
|
||||
void RunThread();
|
||||
};
|
||||
|
||||
#endif
|
||||
|
@@ -584,7 +584,7 @@ Player::SeekDecoder()
|
||||
|
||||
const SongTime start_time = pc.next_song->GetStartTime();
|
||||
|
||||
if (!dc.LockIsCurrentSong(*pc.next_song)) {
|
||||
if (!dc.LockIsSeeakbleCurrentSong(*pc.next_song)) {
|
||||
/* the decoder is already decoding the "next" song -
|
||||
stop it and start the previous song again */
|
||||
|
||||
@@ -950,6 +950,7 @@ Player::SongBorder()
|
||||
const bool border_pause = pc.LockApplyBorderPause();
|
||||
if (border_pause) {
|
||||
paused = true;
|
||||
pc.outputs.Pause();
|
||||
idle_add(IDLE_PLAYER);
|
||||
}
|
||||
}
|
||||
@@ -1147,91 +1148,89 @@ do_play(PlayerControl &pc, DecoderControl &dc,
|
||||
player.Run();
|
||||
}
|
||||
|
||||
static void
|
||||
player_task(void *arg)
|
||||
void
|
||||
PlayerControl::RunThread()
|
||||
{
|
||||
PlayerControl &pc = *(PlayerControl *)arg;
|
||||
|
||||
SetThreadName("player");
|
||||
|
||||
DecoderControl dc(pc.mutex, pc.cond,
|
||||
pc.configured_audio_format,
|
||||
pc.replay_gain_config);
|
||||
DecoderControl dc(mutex, cond,
|
||||
configured_audio_format,
|
||||
replay_gain_config);
|
||||
decoder_thread_start(dc);
|
||||
|
||||
MusicBuffer buffer(pc.buffer_chunks);
|
||||
MusicBuffer buffer(buffer_chunks);
|
||||
|
||||
pc.Lock();
|
||||
Lock();
|
||||
|
||||
while (1) {
|
||||
switch (pc.command) {
|
||||
switch (command) {
|
||||
case PlayerCommand::SEEK:
|
||||
case PlayerCommand::QUEUE:
|
||||
assert(pc.next_song != nullptr);
|
||||
assert(next_song != nullptr);
|
||||
|
||||
pc.Unlock();
|
||||
do_play(pc, dc, buffer);
|
||||
pc.listener.OnPlayerSync();
|
||||
pc.Lock();
|
||||
Unlock();
|
||||
do_play(*this, dc, buffer);
|
||||
listener.OnPlayerSync();
|
||||
Lock();
|
||||
break;
|
||||
|
||||
case PlayerCommand::STOP:
|
||||
pc.Unlock();
|
||||
pc.outputs.Cancel();
|
||||
pc.Lock();
|
||||
Unlock();
|
||||
outputs.Cancel();
|
||||
Lock();
|
||||
|
||||
/* fall through */
|
||||
|
||||
case PlayerCommand::PAUSE:
|
||||
delete pc.next_song;
|
||||
pc.next_song = nullptr;
|
||||
delete next_song;
|
||||
next_song = nullptr;
|
||||
|
||||
pc.CommandFinished();
|
||||
CommandFinished();
|
||||
break;
|
||||
|
||||
case PlayerCommand::CLOSE_AUDIO:
|
||||
pc.Unlock();
|
||||
Unlock();
|
||||
|
||||
pc.outputs.Release();
|
||||
outputs.Release();
|
||||
|
||||
pc.Lock();
|
||||
pc.CommandFinished();
|
||||
Lock();
|
||||
CommandFinished();
|
||||
|
||||
assert(buffer.IsEmptyUnsafe());
|
||||
|
||||
break;
|
||||
|
||||
case PlayerCommand::UPDATE_AUDIO:
|
||||
pc.Unlock();
|
||||
pc.outputs.EnableDisable();
|
||||
pc.Lock();
|
||||
pc.CommandFinished();
|
||||
Unlock();
|
||||
outputs.EnableDisable();
|
||||
Lock();
|
||||
CommandFinished();
|
||||
break;
|
||||
|
||||
case PlayerCommand::EXIT:
|
||||
pc.Unlock();
|
||||
Unlock();
|
||||
|
||||
dc.Quit();
|
||||
|
||||
pc.outputs.Close();
|
||||
outputs.Close();
|
||||
|
||||
pc.LockCommandFinished();
|
||||
LockCommandFinished();
|
||||
return;
|
||||
|
||||
case PlayerCommand::CANCEL:
|
||||
delete pc.next_song;
|
||||
pc.next_song = nullptr;
|
||||
delete next_song;
|
||||
next_song = nullptr;
|
||||
|
||||
pc.CommandFinished();
|
||||
CommandFinished();
|
||||
break;
|
||||
|
||||
case PlayerCommand::REFRESH:
|
||||
/* no-op when not playing */
|
||||
pc.CommandFinished();
|
||||
CommandFinished();
|
||||
break;
|
||||
|
||||
case PlayerCommand::NONE:
|
||||
pc.Wait();
|
||||
Wait();
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -1242,5 +1241,5 @@ StartPlayerThread(PlayerControl &pc)
|
||||
{
|
||||
assert(!pc.thread.IsDefined());
|
||||
|
||||
pc.thread.Start(player_task, &pc);
|
||||
pc.thread.Start();
|
||||
}
|
||||
|
@@ -229,6 +229,8 @@ CueParser::Feed2(char *p) noexcept
|
||||
}
|
||||
|
||||
state = TRACK;
|
||||
ignore_index = false;
|
||||
|
||||
current.reset(new DetachedSong(filename));
|
||||
assert(!current->GetTag().IsDefined());
|
||||
|
||||
@@ -238,6 +240,9 @@ CueParser::Feed2(char *p) noexcept
|
||||
} else if (state == IGNORE_TRACK) {
|
||||
return;
|
||||
} else if (state == TRACK && strcmp(command, "INDEX") == 0) {
|
||||
if (ignore_index)
|
||||
return;
|
||||
|
||||
const char *nr = cue_next_token(&p);
|
||||
if (nr == nullptr)
|
||||
return;
|
||||
@@ -255,7 +260,7 @@ CueParser::Feed2(char *p) noexcept
|
||||
|
||||
current->SetStartTime(SongTime::FromMS(position_ms));
|
||||
if(strcmp(nr, "00") != 0 || previous == nullptr)
|
||||
state = IGNORE_TRACK;
|
||||
ignore_index = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -87,6 +87,13 @@ class CueParser {
|
||||
*/
|
||||
std::unique_ptr<DetachedSong> finished;
|
||||
|
||||
/**
|
||||
* Ignore "INDEX" lines? Only up the first one after "00" is
|
||||
* used. If there is a pregap (INDEX 00..01), it is assigned
|
||||
* to the previous song.
|
||||
*/
|
||||
bool ignore_index;
|
||||
|
||||
/**
|
||||
* Tracks whether Finish() has been called. If true, then all
|
||||
* remaining (partial) results will be delivered by Get().
|
||||
|
@@ -313,7 +313,7 @@ soundcloud_open_uri(const char *uri, Mutex &mutex, Cond &cond)
|
||||
|
||||
SoundCloudJsonData data;
|
||||
yajl_handle hand = yajl_alloc(&parse_callbacks, nullptr, &data);
|
||||
AtScopeExit(hand, &data) { yajl_free(hand); };
|
||||
AtScopeExit(hand) { yajl_free(hand); };
|
||||
|
||||
int ret = soundcloud_parse_json(u, hand, mutex, cond);
|
||||
|
||||
|
@@ -21,6 +21,7 @@
|
||||
#include "ArgParser.hxx"
|
||||
#include "Ack.hxx"
|
||||
#include "Chrono.hxx"
|
||||
#include "util/NumberParser.hxx"
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
@@ -151,7 +152,7 @@ float
|
||||
ParseCommandArgFloat(const char *s)
|
||||
{
|
||||
char *endptr;
|
||||
auto value = strtof(s, &endptr);
|
||||
auto value = ParseFloat(s, &endptr);
|
||||
if (endptr == s || *endptr != 0)
|
||||
throw FormatProtocolError(ACK_ERROR_ARG,
|
||||
"Float expected: %s", s);
|
||||
@@ -163,6 +164,10 @@ SongTime
|
||||
ParseCommandArgSongTime(const char *s)
|
||||
{
|
||||
auto value = ParseCommandArgFloat(s);
|
||||
if (value < 0)
|
||||
throw FormatProtocolError(ACK_ERROR_ARG,
|
||||
"Negative value not allowed: %s", s);
|
||||
|
||||
return SongTime::FromS(value);
|
||||
}
|
||||
|
||||
|
@@ -212,8 +212,6 @@ playlist::SeekSongOrder(PlayerControl &pc, unsigned i, SongTime seek_time)
|
||||
{
|
||||
assert(queue.IsValidOrder(i));
|
||||
|
||||
const DetachedSong *queued_song = GetQueuedSong();
|
||||
|
||||
pc.LockClearError();
|
||||
stop_on_error = true;
|
||||
error_count = 0;
|
||||
@@ -226,8 +224,6 @@ playlist::SeekSongOrder(PlayerControl &pc, unsigned i, SongTime seek_time)
|
||||
|
||||
playing = true;
|
||||
current = i;
|
||||
|
||||
queued_song = nullptr;
|
||||
}
|
||||
|
||||
queued = -1;
|
||||
@@ -235,7 +231,7 @@ playlist::SeekSongOrder(PlayerControl &pc, unsigned i, SongTime seek_time)
|
||||
try {
|
||||
pc.LockSeek(new DetachedSong(queue.GetOrder(i)), seek_time);
|
||||
} catch (...) {
|
||||
UpdateQueuedSong(pc, queued_song);
|
||||
UpdateQueuedSong(pc, nullptr);
|
||||
throw;
|
||||
}
|
||||
|
||||
|
@@ -35,6 +35,7 @@
|
||||
#include "util/CharUtil.hxx"
|
||||
#include "util/StringAPI.hxx"
|
||||
#include "util/StringCompare.hxx"
|
||||
#include "util/NumberParser.hxx"
|
||||
#include "Log.hxx"
|
||||
|
||||
#include <string.h>
|
||||
@@ -148,7 +149,7 @@ playlist_state_restore(const char *line, TextFile &file,
|
||||
while ((line = file.ReadLine()) != nullptr) {
|
||||
const char *p;
|
||||
if ((p = StringAfterPrefix(line, PLAYLIST_STATE_FILE_TIME))) {
|
||||
seek_time = SongTime::FromS(atof(p));
|
||||
seek_time = SongTime::FromS(ParseDouble(p));
|
||||
} else if ((p = StringAfterPrefix(line, PLAYLIST_STATE_FILE_REPEAT))) {
|
||||
playlist.SetRepeat(pc, StringIsEqual(p, "1"));
|
||||
} else if ((p = StringAfterPrefix(line, PLAYLIST_STATE_FILE_SINGLE))) {
|
||||
@@ -158,12 +159,12 @@ playlist_state_restore(const char *line, TextFile &file,
|
||||
} else if ((p = StringAfterPrefix(line, PLAYLIST_STATE_FILE_CROSSFADE))) {
|
||||
pc.SetCrossFade(atoi(p));
|
||||
} else if ((p = StringAfterPrefix(line, PLAYLIST_STATE_FILE_MIXRAMPDB))) {
|
||||
pc.SetMixRampDb(atof(p));
|
||||
pc.SetMixRampDb(ParseFloat(p));
|
||||
} else if ((p = StringAfterPrefix(line, PLAYLIST_STATE_FILE_MIXRAMPDELAY))) {
|
||||
/* this check discards "nan" which was used
|
||||
prior to MPD 0.18 */
|
||||
if (IsDigitASCII(*p))
|
||||
pc.SetMixRampDelay(atof(p));
|
||||
pc.SetMixRampDelay(ParseFloat(p));
|
||||
} else if ((p = StringAfterPrefix(line, PLAYLIST_STATE_FILE_RANDOM))) {
|
||||
random_mode = StringIsEqual(p, "1");
|
||||
} else if ((p = StringAfterPrefix(line, PLAYLIST_STATE_FILE_CURRENT))) {
|
||||
|
@@ -35,7 +35,7 @@
|
||||
#include "IOThread.hxx"
|
||||
#include "Log.hxx"
|
||||
|
||||
#include <list>
|
||||
#include <set>
|
||||
#include <boost/crc.hpp>
|
||||
|
||||
#define MOUNT_STATE_BEGIN "mount_begin"
|
||||
@@ -48,6 +48,9 @@ static constexpr Domain storage_domain("storage");
|
||||
void
|
||||
storage_state_save(BufferedOutputStream &os, const Instance &instance)
|
||||
{
|
||||
if (instance.storage == nullptr)
|
||||
return;
|
||||
|
||||
const auto visitor = [&os](const char *mount_uri, const Storage &storage) {
|
||||
std::string uri = storage.MapUTF8("");
|
||||
if (uri.empty() || StringIsEmpty(mount_uri))
|
||||
@@ -85,6 +88,12 @@ storage_state_restore(const char *line, TextFile &file, Instance &instance)
|
||||
FormatError(storage_domain, "Unrecognized line in mountpoint state: %s", line);
|
||||
}
|
||||
|
||||
if (instance.storage == nullptr)
|
||||
/* without storage (a CompositeStorage instance), we
|
||||
cannot mount, and therefore we silently ignore the
|
||||
state file */
|
||||
return true;
|
||||
|
||||
if (url.empty() || uri.empty()) {
|
||||
LogError(storage_domain, "Missing value in mountpoint state.");
|
||||
return true;
|
||||
@@ -99,16 +108,18 @@ storage_state_restore(const char *line, TextFile &file, Instance &instance)
|
||||
return true;
|
||||
}
|
||||
|
||||
#ifdef ENABLE_DATABASE
|
||||
Database *db = instance.database;
|
||||
if (db != nullptr && db->IsPlugin(simple_db_plugin)) {
|
||||
try {
|
||||
((SimpleDatabase *)db)->Mount(uri.c_str(), url.c_str());
|
||||
} catch (...) {
|
||||
throw;
|
||||
delete storage;
|
||||
FormatError(std::current_exception(),
|
||||
"Failed to restore mount to %s",
|
||||
url.c_str());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
((CompositeStorage*)instance.storage)->Mount(uri.c_str(), storage);
|
||||
|
||||
@@ -118,16 +129,17 @@ storage_state_restore(const char *line, TextFile &file, Instance &instance)
|
||||
unsigned
|
||||
storage_state_get_hash(const Instance &instance)
|
||||
{
|
||||
std::list<std::string> mounts;
|
||||
if (instance.storage == nullptr)
|
||||
return 0;
|
||||
|
||||
std::set<std::string> mounts;
|
||||
|
||||
const auto visitor = [&mounts](const char *mount_uri, const Storage &storage) {
|
||||
mounts.push_back(std::string(mount_uri) + ":" + storage.MapUTF8(""));
|
||||
mounts.emplace(std::string(mount_uri) + ":" + storage.MapUTF8(""));
|
||||
};
|
||||
|
||||
((CompositeStorage*)instance.storage)->VisitMounts(visitor);
|
||||
|
||||
mounts.sort();
|
||||
|
||||
boost::crc_32_type result;
|
||||
|
||||
for (auto mount: mounts) {
|
||||
|
@@ -219,7 +219,12 @@ UriToNfsPath(const char *_uri_utf8)
|
||||
std::string uri_utf8("/");
|
||||
uri_utf8.append(_uri_utf8);
|
||||
|
||||
#ifdef _WIN32
|
||||
/* assume UTF-8 when accessing NFS from Windows */
|
||||
return uri_utf8;
|
||||
#else
|
||||
return AllocatedPath::FromUTF8Throw(uri_utf8.c_str()).Steal();
|
||||
#endif
|
||||
}
|
||||
|
||||
std::string
|
||||
@@ -291,7 +296,7 @@ NfsStorage::GetInfo(const char *uri_utf8, gcc_unused bool follow)
|
||||
|
||||
gcc_pure
|
||||
static bool
|
||||
SkipNameFS(const char *name) noexcept
|
||||
SkipNameFS(PathTraitsFS::const_pointer_type name) noexcept
|
||||
{
|
||||
return name[0] == '.' &&
|
||||
(name[1] == 0 ||
|
||||
@@ -358,7 +363,14 @@ NfsListDirectoryOperation::CollectEntries(struct nfsdir *dir)
|
||||
|
||||
const struct nfsdirent *ent;
|
||||
while ((ent = connection.ReadDirectory(dir)) != nullptr) {
|
||||
#ifdef _WIN32
|
||||
/* assume UTF-8 when accessing NFS from Windows */
|
||||
const auto name_fs = AllocatedPath::FromUTF8Throw(ent->name);
|
||||
if (name_fs.IsNull())
|
||||
continue;
|
||||
#else
|
||||
const Path name_fs = Path::FromFS(ent->name);
|
||||
#endif
|
||||
if (SkipNameFS(name_fs.c_str()))
|
||||
continue;
|
||||
|
||||
|
@@ -22,6 +22,7 @@
|
||||
#include "VorbisComment.hxx"
|
||||
#include "ReplayGainInfo.hxx"
|
||||
#include "util/ASCII.hxx"
|
||||
#include "util/NumberParser.hxx"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
@@ -33,16 +34,16 @@ ParseReplayGainTagTemplate(ReplayGainInfo &info, const T t)
|
||||
const char *value;
|
||||
|
||||
if ((value = t["replaygain_track_gain"]) != nullptr) {
|
||||
info.track.gain = atof(value);
|
||||
info.track.gain = ParseFloat(value);
|
||||
return true;
|
||||
} else if ((value = t["replaygain_album_gain"]) != nullptr) {
|
||||
info.album.gain = atof(value);
|
||||
info.album.gain = ParseFloat(value);
|
||||
return true;
|
||||
} else if ((value = t["replaygain_track_peak"]) != nullptr) {
|
||||
info.track.peak = atof(value);
|
||||
info.track.peak = ParseFloat(value);
|
||||
return true;
|
||||
} else if ((value = t["replaygain_album_peak"]) != nullptr) {
|
||||
info.album.peak = atof(value);
|
||||
info.album.peak = ParseFloat(value);
|
||||
return true;
|
||||
} else
|
||||
return false;
|
||||
|
@@ -52,13 +52,11 @@ public:
|
||||
constexpr ThreadId(pthread_t _id):id(_id) {}
|
||||
#endif
|
||||
|
||||
gcc_const
|
||||
static ThreadId Null() noexcept {
|
||||
static constexpr ThreadId Null() noexcept {
|
||||
#ifdef _WIN32
|
||||
return 0;
|
||||
#else
|
||||
static ThreadId null;
|
||||
return null;
|
||||
return pthread_t();
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -81,11 +79,13 @@ public:
|
||||
|
||||
gcc_pure
|
||||
bool operator==(const ThreadId &other) const noexcept {
|
||||
#ifdef _WIN32
|
||||
/* note: not using pthread_equal() because that
|
||||
function "is undefined if either thread ID is not
|
||||
valid so we can't safely use it on
|
||||
default-constructed values" (comment from
|
||||
libstdc++) - and if both libstdc++ and libc++ get
|
||||
away with this, we can do it as well */
|
||||
return id == other.id;
|
||||
#else
|
||||
return pthread_equal(id, other.id);
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -25,39 +25,21 @@
|
||||
#include "java/Global.hxx"
|
||||
#endif
|
||||
|
||||
bool
|
||||
Thread::Start(void (*_f)(void *ctx), void *_ctx)
|
||||
void
|
||||
Thread::Start()
|
||||
{
|
||||
assert(!IsDefined());
|
||||
|
||||
f = _f;
|
||||
ctx = _ctx;
|
||||
|
||||
#ifdef _WIN32
|
||||
handle = ::CreateThread(nullptr, 0, ThreadProc, this, 0, &id);
|
||||
if (handle == nullptr)
|
||||
throw MakeLastError("Failed to create thread");
|
||||
#else
|
||||
#ifndef NDEBUG
|
||||
creating = true;
|
||||
#endif
|
||||
|
||||
int e = pthread_create(&handle, nullptr, ThreadProc, this);
|
||||
|
||||
if (e != 0) {
|
||||
#ifndef NDEBUG
|
||||
creating = false;
|
||||
#endif
|
||||
if (e != 0)
|
||||
throw MakeErrno(e, "Failed to create thread");
|
||||
}
|
||||
|
||||
defined = true;
|
||||
#ifndef NDEBUG
|
||||
creating = false;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
@@ -72,7 +54,17 @@ Thread::Join()
|
||||
handle = nullptr;
|
||||
#else
|
||||
pthread_join(handle, nullptr);
|
||||
defined = false;
|
||||
handle = pthread_t();
|
||||
#endif
|
||||
}
|
||||
|
||||
inline void
|
||||
Thread::Run()
|
||||
{
|
||||
f();
|
||||
|
||||
#ifdef ANDROID
|
||||
Java::DetachCurrentThread();
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -83,7 +75,7 @@ Thread::ThreadProc(LPVOID ctx)
|
||||
{
|
||||
Thread &thread = *(Thread *)ctx;
|
||||
|
||||
thread.f(thread.ctx);
|
||||
thread.Run();
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -95,18 +87,10 @@ Thread::ThreadProc(void *ctx)
|
||||
Thread &thread = *(Thread *)ctx;
|
||||
|
||||
#ifndef NDEBUG
|
||||
/* this works around a race condition that causes an assertion
|
||||
failure due to IsInside() spuriously returning false right
|
||||
after the thread has been created, and the calling thread
|
||||
hasn't initialised "defined" yet */
|
||||
thread.defined = true;
|
||||
thread.inside_handle = pthread_self();
|
||||
#endif
|
||||
|
||||
thread.f(thread.ctx);
|
||||
|
||||
#ifdef ANDROID
|
||||
Java::DetachCurrentThread();
|
||||
#endif
|
||||
thread.Run();
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
@@ -21,6 +21,7 @@
|
||||
#define MPD_THREAD_HXX
|
||||
|
||||
#include "check.h"
|
||||
#include "util/BindMethod.hxx"
|
||||
#include "Compiler.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
@@ -32,28 +33,28 @@
|
||||
#include <assert.h>
|
||||
|
||||
class Thread {
|
||||
typedef BoundMethod<void()> Function;
|
||||
const Function f;
|
||||
|
||||
#ifdef _WIN32
|
||||
HANDLE handle = nullptr;
|
||||
DWORD id;
|
||||
#else
|
||||
pthread_t handle;
|
||||
bool defined = false;
|
||||
pthread_t handle = pthread_t();
|
||||
|
||||
#ifndef NDEBUG
|
||||
/**
|
||||
* The thread is currently being created. This is a workaround for
|
||||
* IsInside(), which may return false until pthread_create() has
|
||||
* initialised the #handle.
|
||||
* This handle is only used by IsInside(), and is set by the
|
||||
* thread function. Since #handle is set by pthread_create()
|
||||
* which is racy, we need this attribute for early checks
|
||||
* inside the thread function.
|
||||
*/
|
||||
bool creating = false;
|
||||
pthread_t inside_handle = pthread_t();
|
||||
#endif
|
||||
#endif
|
||||
|
||||
void (*f)(void *ctx);
|
||||
void *ctx;
|
||||
|
||||
public:
|
||||
Thread() = default;
|
||||
explicit Thread(Function _f):f(_f) {}
|
||||
|
||||
Thread(const Thread &) = delete;
|
||||
|
||||
@@ -69,10 +70,11 @@ public:
|
||||
#ifdef _WIN32
|
||||
return handle != nullptr;
|
||||
#else
|
||||
return defined;
|
||||
return handle != pthread_t();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
/**
|
||||
* Check if this thread is the current thread.
|
||||
*/
|
||||
@@ -81,18 +83,23 @@ public:
|
||||
#ifdef _WIN32
|
||||
return GetCurrentThreadId() == id;
|
||||
#else
|
||||
#ifdef NDEBUG
|
||||
constexpr bool creating = false;
|
||||
#endif
|
||||
return IsDefined() && (creating ||
|
||||
pthread_equal(pthread_self(), handle));
|
||||
/* note: not using pthread_equal() because that
|
||||
function "is undefined if either thread ID is not
|
||||
valid so we can't safely use it on
|
||||
default-constructed values" (comment from
|
||||
libstdc++) - and if both libstdc++ and libc++ get
|
||||
away with this, we can do it as well */
|
||||
return pthread_self() == inside_handle;
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
bool Start(void (*f)(void *ctx), void *ctx);
|
||||
void Start();
|
||||
void Join();
|
||||
|
||||
private:
|
||||
void Run();
|
||||
|
||||
#ifdef _WIN32
|
||||
static DWORD WINAPI ThreadProc(LPVOID ctx);
|
||||
#else
|
||||
|
@@ -40,8 +40,10 @@
|
||||
|
||||
#ifdef __linux__
|
||||
|
||||
#ifndef ANDROID
|
||||
|
||||
static int
|
||||
ioprio_set(int which, int who, int ioprio)
|
||||
linux_ioprio_set(int which, int who, int ioprio)
|
||||
{
|
||||
return syscall(__NR_ioprio_set, which, who, ioprio);
|
||||
}
|
||||
@@ -55,7 +57,20 @@ ioprio_set_idle()
|
||||
static constexpr int _IOPRIO_IDLE =
|
||||
(_IOPRIO_CLASS_IDLE << _IOPRIO_CLASS_SHIFT) | 7;
|
||||
|
||||
ioprio_set(_IOPRIO_WHO_PROCESS, 0, _IOPRIO_IDLE);
|
||||
linux_ioprio_set(_IOPRIO_WHO_PROCESS, 0, _IOPRIO_IDLE);
|
||||
}
|
||||
|
||||
#endif /* !ANDROID */
|
||||
|
||||
/**
|
||||
* Wrapper for the "sched_setscheduler" system call. We don't use the
|
||||
* one from the C library because Musl has an intentionally broken
|
||||
* implementation.
|
||||
*/
|
||||
static int
|
||||
linux_sched_setscheduler(pid_t pid, int sched, const struct sched_param *param)
|
||||
{
|
||||
return syscall(__NR_sched_setscheduler, pid, sched, param);
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -66,10 +81,14 @@ SetThreadIdlePriority()
|
||||
#ifdef __linux__
|
||||
#ifdef SCHED_IDLE
|
||||
static struct sched_param sched_param;
|
||||
sched_setscheduler(0, SCHED_IDLE, &sched_param);
|
||||
linux_sched_setscheduler(0, SCHED_IDLE, &sched_param);
|
||||
#endif
|
||||
|
||||
#ifndef ANDROID
|
||||
/* this system call is forbidden via seccomp on Android 8 and
|
||||
leads to crash (SIGSYS) */
|
||||
ioprio_set_idle();
|
||||
#endif
|
||||
|
||||
#elif defined(_WIN32)
|
||||
SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_IDLE);
|
||||
@@ -88,7 +107,7 @@ SetThreadRealtime()
|
||||
policy |= SCHED_RESET_ON_FORK;
|
||||
#endif
|
||||
|
||||
if (sched_setscheduler(0, policy, &sched_param) < 0)
|
||||
if (linux_sched_setscheduler(0, policy, &sched_param) < 0)
|
||||
throw MakeErrno("sched_setscheduler failed");
|
||||
#endif // __linux__
|
||||
};
|
||||
|
@@ -78,7 +78,12 @@ ParseDouble(const char *p, char **endptr=nullptr)
|
||||
static inline float
|
||||
ParseFloat(const char *p, char **endptr=nullptr)
|
||||
{
|
||||
#if defined(__BIONIC__) && __ANDROID_API__ < 21
|
||||
/* strtof() requires API level 21 */
|
||||
return (float)ParseDouble(p, endptr);
|
||||
#else
|
||||
return strtof(p, endptr);
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif
|
||||
|
@@ -42,11 +42,6 @@ class RefCount {
|
||||
std::atomic_uint n;
|
||||
|
||||
public:
|
||||
#ifndef _LIBCPP_VERSION
|
||||
/* the "constexpr" is missing in libc++'s "atomic"
|
||||
implementation */
|
||||
constexpr
|
||||
#endif
|
||||
RefCount():n(1) {}
|
||||
|
||||
void Increment() {
|
||||
|
@@ -103,11 +103,13 @@ UnsafeCopyStringP(wchar_t *dest, const wchar_t *src) noexcept
|
||||
{
|
||||
#if defined(_WIN32) || defined(__BIONIC__) || defined(__OpenBSD__) || \
|
||||
defined(__NetBSD__)
|
||||
/* emulate wcpcpy() */
|
||||
UnsafeCopyString(dest, src);
|
||||
return dest + StringLength(dest);
|
||||
/* emulate wcpcpy() */
|
||||
UnsafeCopyString(dest, src);
|
||||
return dest + StringLength(dest);
|
||||
#elif defined(__sun) && defined (__SVR4)
|
||||
return std::wcpcpy(dest, src);
|
||||
#else
|
||||
return wcpcpy(dest, src);
|
||||
return wcpcpy(dest, src);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -140,7 +142,11 @@ gcc_malloc gcc_nonnull_all
|
||||
static inline wchar_t *
|
||||
DuplicateString(const wchar_t *p)
|
||||
{
|
||||
#if defined(__sun) && defined (__SVR4)
|
||||
return std::wcsdup(p);
|
||||
#else
|
||||
return wcsdup(p);
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif
|
||||
|
30
test/NullMixerListener.hxx
Normal file
30
test/NullMixerListener.hxx
Normal file
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright 2003-2018 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#ifndef NULL_MIXER_LISTENER_HXX
|
||||
#define NULL_MIXER_LISTENER_HXX
|
||||
|
||||
#include "mixer/Listener.hxx"
|
||||
|
||||
class NullMixerListener : public MixerListener {
|
||||
public:
|
||||
void OnMixerVolumeChanged(Mixer &, int) override {}
|
||||
};
|
||||
|
||||
#endif
|
@@ -18,6 +18,7 @@
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "NullMixerListener.hxx"
|
||||
#include "mixer/MixerControl.hxx"
|
||||
#include "mixer/MixerList.hxx"
|
||||
#include "filter/FilterRegistry.hxx"
|
||||
@@ -50,9 +51,14 @@ try {
|
||||
|
||||
EventLoop event_loop;
|
||||
|
||||
NullMixerListener mixer_listener;
|
||||
Mixer *mixer = mixer_new(event_loop, alsa_mixer_plugin,
|
||||
*(AudioOutput *)nullptr,
|
||||
*(MixerListener *)nullptr,
|
||||
/* ugly dangerous dummy pointer to
|
||||
make the compiler happy; this
|
||||
works with most mixers, because
|
||||
they don't need the AudioOutput */
|
||||
*(AudioOutput *)0x1,
|
||||
mixer_listener,
|
||||
ConfigBlock());
|
||||
|
||||
mixer_open(mixer);
|
||||
|
@@ -18,6 +18,7 @@
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "NullMixerListener.hxx"
|
||||
#include "output/Internal.hxx"
|
||||
#include "output/OutputPlugin.hxx"
|
||||
#include "output/Client.hxx"
|
||||
@@ -44,6 +45,8 @@
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
|
||||
void AudioOutput::Task() {}
|
||||
|
||||
class DummyAudioOutputClient final : public AudioOutputClient {
|
||||
public:
|
||||
/* virtual methods from AudioOutputClient */
|
||||
@@ -62,7 +65,9 @@ filter_plugin_by_name(gcc_unused const char *name) noexcept
|
||||
}
|
||||
|
||||
static AudioOutput *
|
||||
load_audio_output(EventLoop &event_loop, AudioOutputClient &client,
|
||||
load_audio_output(EventLoop &event_loop,
|
||||
NullMixerListener &mixer_listener,
|
||||
AudioOutputClient &client,
|
||||
const char *name)
|
||||
{
|
||||
const auto *param = config_find_block(ConfigBlockOption::AUDIO_OUTPUT,
|
||||
@@ -72,7 +77,7 @@ load_audio_output(EventLoop &event_loop, AudioOutputClient &client,
|
||||
name);
|
||||
|
||||
return audio_output_new(event_loop, ReplayGainConfig(), *param,
|
||||
*(MixerListener *)nullptr,
|
||||
mixer_listener,
|
||||
client);
|
||||
}
|
||||
|
||||
@@ -142,8 +147,10 @@ try {
|
||||
|
||||
/* initialize the audio output */
|
||||
|
||||
NullMixerListener mixer_listener;
|
||||
DummyAudioOutputClient client;
|
||||
AudioOutput *ao = load_audio_output(event_loop, client, argv[2]);
|
||||
AudioOutput *ao = load_audio_output(event_loop, mixer_listener,
|
||||
client, argv[2]);
|
||||
|
||||
/* parse the audio format */
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user