Compare commits
69 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
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 |
AUTHORSMakefile.amNEWS
android
configure.acdoc
python/build
src
Main.cxxSongSave.cxx
db
decoder
encoder
plugins
filter
plugins
input
lib
curl
ffmpeg
nfs
mixer
plugins
net
pcm
player
playlist
protocol
queue
storage
plugins
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>
|
||||
|
@@ -497,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 \
|
||||
|
32
NEWS
32
NEWS
@@ -1,3 +1,35 @@
|
||||
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
|
||||
|
@@ -2,10 +2,10 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="org.musicpd"
|
||||
android:installLocation="auto"
|
||||
android:versionCode="15"
|
||||
android:versionName="0.20.16">
|
||||
android:versionCode="18"
|
||||
android:versionName="0.20.19">
|
||||
|
||||
<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,13 +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 = '-Os -g'
|
||||
common_flags += ' -fPIC'
|
||||
common_flags += ' -march=armv7-a -mfloat-abi=softfp'
|
||||
common_flags += ' ' + abi_info['cflags']
|
||||
|
||||
toolchain_bin = os.path.join(toolchain_path, 'bin')
|
||||
llvm_bin = os.path.join(llvm_path, 'bin')
|
||||
@@ -87,7 +106,7 @@ class AndroidNdkToolchain:
|
||||
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') + \
|
||||
@@ -95,7 +114,7 @@ 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
|
||||
|
||||
@@ -124,9 +143,9 @@ thirdparty_libs = [
|
||||
opus,
|
||||
flac,
|
||||
libid3tag,
|
||||
libmad,
|
||||
ffmpeg,
|
||||
curl,
|
||||
libnfs,
|
||||
boost,
|
||||
]
|
||||
|
||||
|
@@ -1,10 +1,10 @@
|
||||
AC_PREREQ(2.60)
|
||||
|
||||
AC_INIT(mpd, 0.20.16, musicpd-dev-team@lists.sourceforge.net)
|
||||
AC_INIT(mpd, 0.20.19, musicpd-dev-team@lists.sourceforge.net)
|
||||
|
||||
VERSION_MAJOR=0
|
||||
VERSION_MINOR=20
|
||||
VERSION_REVISION=16
|
||||
VERSION_REVISION=19
|
||||
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],
|
||||
|
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>
|
||||
|
@@ -5,6 +5,7 @@ from build.makeproject import MakeProject
|
||||
class AutotoolsProject(MakeProject):
|
||||
def __init__(self, url, md5, installed, configure_args=[],
|
||||
autogen=False,
|
||||
autoreconf=False,
|
||||
cppflags='',
|
||||
ldflags='',
|
||||
libs='',
|
||||
@@ -13,6 +14,7 @@ class AutotoolsProject(MakeProject):
|
||||
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
|
||||
@@ -28,6 +30,8 @@ class AutotoolsProject(MakeProject):
|
||||
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)
|
||||
|
||||
|
@@ -1,4 +1,6 @@
|
||||
import re
|
||||
from os.path import abspath
|
||||
|
||||
from build.project import Project
|
||||
from build.zlib import ZlibProject
|
||||
from build.autotools import AutotoolsProject
|
||||
@@ -15,12 +17,17 @@ libogg = AutotoolsProject(
|
||||
)
|
||||
|
||||
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',
|
||||
],
|
||||
|
||||
edits={
|
||||
# this option is not understood by clang
|
||||
'configure': lambda data: data.replace('-mno-ieee-fp', ' '),
|
||||
}
|
||||
)
|
||||
|
||||
opus = AutotoolsProject(
|
||||
@@ -98,8 +105,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',
|
||||
@@ -122,7 +129,6 @@ ffmpeg = FfmpegProject(
|
||||
'--disable-protocols',
|
||||
'--disable-devices',
|
||||
'--disable-filters',
|
||||
'--disable-filters',
|
||||
'--disable-v4l2_m2m',
|
||||
|
||||
'--disable-parser=bmp',
|
||||
@@ -140,7 +146,6 @@ ffmpeg = FfmpegProject(
|
||||
'--disable-parser=mjpeg',
|
||||
'--disable-parser=mlp',
|
||||
'--disable-parser=mpeg4video',
|
||||
'--disable-parser=mpegaudio',
|
||||
'--disable-parser=mpegvideo',
|
||||
'--disable-parser=opus',
|
||||
'--disable-parser=vc1',
|
||||
@@ -192,16 +197,6 @@ ffmpeg = FfmpegProject(
|
||||
# we don't need these decoders, because we have the dedicated
|
||||
# libraries
|
||||
'--disable-decoder=flac',
|
||||
'--disable-decoder=mp1',
|
||||
'--disable-decoder=mp1float',
|
||||
'--disable-decoder=mp2',
|
||||
'--disable-decoder=mp2float',
|
||||
'--disable-decoder=mp3',
|
||||
'--disable-decoder=mp3adu',
|
||||
'--disable-decoder=mp3adufloat',
|
||||
'--disable-decoder=mp3float',
|
||||
'--disable-decoder=mp3on4',
|
||||
'--disable-decoder=mp3on4float',
|
||||
'--disable-decoder=opus',
|
||||
'--disable-decoder=vorbis',
|
||||
|
||||
@@ -315,7 +310,7 @@ ffmpeg = FfmpegProject(
|
||||
'--disable-decoder=svq1',
|
||||
'--disable-decoder=svq3',
|
||||
'--disable-decoder=tiff',
|
||||
'--disable-decoder=mottiertexseqvideo',
|
||||
'--disable-decoder=tiertexseqvideo',
|
||||
'--disable-decoder=truemotion1',
|
||||
'--disable-decoder=truemotion2',
|
||||
'--disable-decoder=truemotion2rt',
|
||||
@@ -339,8 +334,8 @@ ffmpeg = FfmpegProject(
|
||||
)
|
||||
|
||||
curl = AutotoolsProject(
|
||||
'http://curl.haxx.se/download/curl-7.58.0.tar.xz',
|
||||
'6a813875243609eb75f37fa72044e4ad618b55ec15a4eafdac2df6a7e800e3e3',
|
||||
'http://curl.haxx.se/download/curl-7.59.0.tar.xz',
|
||||
'e44eaabdf916407585bf5c7939ff1161e6242b6b015d3f2f5b758b2a330461fc',
|
||||
'lib/libcurl.a',
|
||||
[
|
||||
'--disable-shared', '--enable-static',
|
||||
@@ -358,6 +353,23 @@ curl = AutotoolsProject(
|
||||
'--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(
|
||||
|
@@ -3,10 +3,12 @@ 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:
|
||||
@@ -18,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)
|
||||
|
||||
@@ -29,6 +31,10 @@ 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
|
||||
|
||||
@@ -51,6 +57,9 @@ class Project:
|
||||
parent_path = toolchain.build_path
|
||||
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:
|
||||
|
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')
|
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();
|
||||
|
@@ -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) {
|
||||
|
@@ -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
|
||||
|
@@ -29,6 +29,7 @@
|
||||
#include "Idle.hxx"
|
||||
#include "Log.hxx"
|
||||
#include "thread/Thread.hxx"
|
||||
#include "thread/Name.hxx"
|
||||
#include "thread/Util.hxx"
|
||||
|
||||
#ifndef NDEBUG
|
||||
@@ -113,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());
|
||||
|
@@ -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?
|
||||
|
@@ -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:
|
||||
|
@@ -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;
|
||||
|
||||
|
@@ -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);
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,7 +73,7 @@ ThreadInputStream::ThreadFunc()
|
||||
Open();
|
||||
} catch (...) {
|
||||
postponed_exception = std::current_exception();
|
||||
cond.broadcast();
|
||||
SetReady();
|
||||
return;
|
||||
}
|
||||
|
||||
|
@@ -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;
|
||||
@@ -76,7 +82,13 @@ public:
|
||||
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.
|
||||
@@ -90,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());
|
||||
|
||||
|
@@ -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
|
||||
}
|
||||
|
||||
|
@@ -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;
|
||||
|
@@ -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);
|
||||
|
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
|
@@ -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 */
|
||||
|
@@ -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 */
|
||||
|
||||
|
@@ -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().
|
||||
|
@@ -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);
|
||||
}
|
||||
|
||||
|
@@ -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))) {
|
||||
|
@@ -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;
|
||||
|
@@ -38,10 +38,12 @@
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
#if defined(__linux__) && !defined(ANDROID)
|
||||
#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,7 +81,7 @@ 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
|
||||
@@ -92,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
|
||||
|
@@ -23,6 +23,7 @@
|
||||
#include "storage/Registry.hxx"
|
||||
#include "storage/StorageInterface.hxx"
|
||||
#include "storage/FileInfo.hxx"
|
||||
#include "net/Init.hxx"
|
||||
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
@@ -70,7 +71,12 @@ Ls(Storage &storage, const char *path)
|
||||
char mtime_buffer[32];
|
||||
const char *mtime = " ";
|
||||
if (info.mtime > 0) {
|
||||
strftime(mtime_buffer, sizeof(mtime_buffer), "%F",
|
||||
strftime(mtime_buffer, sizeof(mtime_buffer),
|
||||
#ifdef _WIN32
|
||||
"%Y-%m-%d",
|
||||
#else
|
||||
"%F",
|
||||
#endif
|
||||
gmtime(&info.mtime));
|
||||
mtime = mtime_buffer;
|
||||
}
|
||||
@@ -95,6 +101,7 @@ try {
|
||||
const char *const command = argv[1];
|
||||
const char *const storage_uri = argv[2];
|
||||
|
||||
const ScopeNetInit net_init;
|
||||
const ScopeIOThread io_thread;
|
||||
|
||||
if (strcmp(command, "ls") == 0) {
|
||||
|
@@ -58,7 +58,8 @@ class CrossGccToolchain:
|
||||
|
||||
self.cflags = common_flags
|
||||
self.cxxflags = common_flags
|
||||
self.cppflags = '-isystem ' + os.path.join(install_prefix, 'include')
|
||||
self.cppflags = '-isystem ' + os.path.join(install_prefix, 'include') + \
|
||||
' -DWINVER=0x0600 -D_WIN32_WINNT=0x0600'
|
||||
self.ldflags = '-L' + os.path.join(install_prefix, 'lib')
|
||||
self.libs = ''
|
||||
|
||||
@@ -84,6 +85,7 @@ thirdparty_libs = [
|
||||
liblame,
|
||||
ffmpeg,
|
||||
curl,
|
||||
libnfs,
|
||||
boost,
|
||||
]
|
||||
|
||||
|
Reference in New Issue
Block a user