Compare commits
176 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
2b97b124bd | ||
![]() |
d042ab87da | ||
![]() |
588303b78d | ||
![]() |
36704c5e18 | ||
![]() |
5834843b8a | ||
![]() |
762f3afb9d | ||
![]() |
7fb2f15a1a | ||
![]() |
7456dccd3a | ||
![]() |
245f41bb7e | ||
![]() |
9bfb844cfa | ||
![]() |
d790d3ba3c | ||
![]() |
c3dbc92766 | ||
![]() |
0bd25f1e17 | ||
![]() |
a4cd7411e8 | ||
![]() |
bf276f6235 | ||
![]() |
d916890a8f | ||
![]() |
071cacc9a4 | ||
![]() |
33f33323af | ||
![]() |
388fae2c47 | ||
![]() |
9f878b77e9 | ||
![]() |
a547d2aaba | ||
![]() |
c013026821 | ||
![]() |
96b48a2404 | ||
![]() |
9612975c2c | ||
![]() |
41bfd45a2e | ||
![]() |
bbdcbd1f08 | ||
![]() |
6b3c525a9d | ||
![]() |
83aed7051c | ||
![]() |
77c6e45e65 | ||
![]() |
8825393660 | ||
![]() |
2b9246c6ad | ||
![]() |
a9edb4de28 | ||
![]() |
a076ddf38c | ||
![]() |
cafc266e0b | ||
![]() |
a3d020eff9 | ||
![]() |
8412d94d05 | ||
![]() |
d1c5bb956a | ||
![]() |
70986bc120 | ||
![]() |
f31fe8b865 | ||
![]() |
142a9fe530 | ||
![]() |
4dd2ad9b27 | ||
![]() |
62f7375804 | ||
![]() |
543296b5ba | ||
![]() |
5fee130d00 | ||
![]() |
073facea70 | ||
![]() |
dbe3b6eee4 | ||
![]() |
df97049647 | ||
![]() |
42c5f68362 | ||
![]() |
cc19e760cf | ||
![]() |
0ff22a16fa | ||
![]() |
47360ec906 | ||
![]() |
087a9938d2 | ||
![]() |
26d8e41a6b | ||
![]() |
750ae1d3f3 | ||
![]() |
f8a9a7a108 | ||
![]() |
eb192137d6 | ||
![]() |
c25b464f37 | ||
![]() |
710b48d410 | ||
![]() |
5e77a8199d | ||
![]() |
6637db086b | ||
![]() |
a271a55da7 | ||
![]() |
6eeec6cbfa | ||
![]() |
5e3f3b0400 | ||
![]() |
923c402f69 | ||
![]() |
4fed0b991c | ||
![]() |
f28c746b6b | ||
![]() |
ab95027fc6 | ||
![]() |
ed3bc4ab63 | ||
![]() |
68064f1aa6 | ||
![]() |
475ac76a5f | ||
![]() |
79d4f8674c | ||
![]() |
e42eed4d4c | ||
![]() |
4a7042e847 | ||
![]() |
7f36923eb4 | ||
![]() |
2ca8d69126 | ||
![]() |
70367d70c8 | ||
![]() |
e6389ff5a1 | ||
![]() |
b46cf57d98 | ||
![]() |
6f59d71e07 | ||
![]() |
f9130f42a2 | ||
![]() |
faf2eeaa99 | ||
![]() |
1c7de0b4ac | ||
![]() |
58487e484f | ||
![]() |
104075f3e0 | ||
![]() |
b8097eaf2e | ||
![]() |
5eb0cbc887 | ||
![]() |
ba8e579e9b | ||
![]() |
072e39c9cf | ||
![]() |
8dc3f3b21a | ||
![]() |
faf0c950fe | ||
![]() |
4ecd325371 | ||
![]() |
5771d67202 | ||
![]() |
75c8aecffa | ||
![]() |
aa5d05eaa4 | ||
![]() |
15735552f4 | ||
![]() |
d6d9dc9d95 | ||
![]() |
dc57966dc3 | ||
![]() |
04ed50fb0f | ||
![]() |
c9553411bb | ||
![]() |
62221adf55 | ||
![]() |
a6bf4746c6 | ||
![]() |
72637d00e8 | ||
![]() |
27d4b15925 | ||
![]() |
7a77767e66 | ||
![]() |
1b26621860 | ||
![]() |
3db5f4d0aa | ||
![]() |
b2a6e327bf | ||
![]() |
9aec5fe907 | ||
![]() |
c731a82b71 | ||
![]() |
e6fad97edc | ||
![]() |
70495aada1 | ||
![]() |
f243f615ef | ||
![]() |
807c72b2f1 | ||
![]() |
74dbaade6f | ||
![]() |
53677172f2 | ||
![]() |
bef0ccf42a | ||
![]() |
ff35aa07dc | ||
![]() |
a3afd5178c | ||
![]() |
f1285a6dfd | ||
![]() |
cf7c1afb93 | ||
![]() |
e140a28073 | ||
![]() |
de61c3b962 | ||
![]() |
c46fc4531b | ||
![]() |
065a9ed10f | ||
![]() |
e44c0254f7 | ||
![]() |
13f9f0315f | ||
![]() |
1532ffe215 | ||
![]() |
b24cbc68ba | ||
![]() |
976fdd76c1 | ||
![]() |
bbda335e02 | ||
![]() |
d2dd6f7c70 | ||
![]() |
e9a544fa98 | ||
![]() |
79f2f8cddc | ||
![]() |
39fa949345 | ||
![]() |
e1d7a5cbf5 | ||
![]() |
f3cefaf043 | ||
![]() |
b3460f3f54 | ||
![]() |
1e0ad1f6bf | ||
![]() |
4abcb08cc9 | ||
![]() |
81e7833711 | ||
![]() |
82e261ad33 | ||
![]() |
cae2811762 | ||
![]() |
09112c6869 | ||
![]() |
77aaf1baee | ||
![]() |
6626c2d00d | ||
![]() |
315f9d98f6 | ||
![]() |
f087518e7a | ||
![]() |
db9997a106 | ||
![]() |
0cbfb610f2 | ||
![]() |
f901cd042b | ||
![]() |
5719207dfa | ||
![]() |
a84fbbe327 | ||
![]() |
93c97972b9 | ||
![]() |
ac61d43720 | ||
![]() |
1958f78cc1 | ||
![]() |
a7ee64a25b | ||
![]() |
2a58f22649 | ||
![]() |
f066bb7716 | ||
![]() |
4e3d182189 | ||
![]() |
205fba74cf | ||
![]() |
a9bcf8d50d | ||
![]() |
b0ff3bc7a3 | ||
![]() |
06301e279c | ||
![]() |
6d6f274648 | ||
![]() |
9acefcb256 | ||
![]() |
e4d0293a31 | ||
![]() |
ae77542a11 | ||
![]() |
980187f856 | ||
![]() |
327a8e6c59 | ||
![]() |
d11e2724c4 | ||
![]() |
f768ca3a2d | ||
![]() |
947e902288 | ||
![]() |
3436a646b5 | ||
![]() |
aed0af1e00 | ||
![]() |
0d7ee2b014 | ||
![]() |
2f5fd91bd8 |
INSTALLMakefile.amNEWSconfigure.ac
doc
src
Compiler.hDetachedSong.hxxMain.cxxPlayerThread.cxx
archive
client
db
update
decoder
encoder
event
filter
fs
input
lib
ffmpeg
nfs
mixer
notify.hxxoutput
pcm
playlist
plugins
protocol
queue
storage
plugins
system
tag
thread
unix
util
systemd
test
2
INSTALL
2
INSTALL
@@ -12,7 +12,7 @@ install MPD. If more information is desired, read the user manual:
|
||||
Dependencies
|
||||
------------
|
||||
|
||||
gcc 4.6 or later - http://gcc.gnu.org/
|
||||
gcc 4.7 or later - http://gcc.gnu.org/
|
||||
clang 3.2 or later - http://clang.llvm.org/
|
||||
Any other C++11 compliant compiler should also work.
|
||||
|
||||
|
21
Makefile.am
21
Makefile.am
@@ -359,6 +359,7 @@ libutil_a_SOURCES = \
|
||||
src/util/Clamp.hxx \
|
||||
src/util/Alloc.cxx src/util/Alloc.hxx \
|
||||
src/util/VarSize.hxx \
|
||||
src/util/ScopeExit.hxx \
|
||||
src/util/Error.cxx src/util/Error.hxx \
|
||||
src/util/Domain.hxx \
|
||||
src/util/ReusableArray.hxx \
|
||||
@@ -462,11 +463,13 @@ ICU_LDADD = libicu.a $(ICU_LIBS)
|
||||
libpcm_a_SOURCES = \
|
||||
src/pcm/Domain.cxx src/pcm/Domain.hxx \
|
||||
src/pcm/Traits.hxx \
|
||||
src/pcm/Interleave.cxx src/pcm/Interleave.hxx \
|
||||
src/pcm/PcmBuffer.cxx src/pcm/PcmBuffer.hxx \
|
||||
src/pcm/PcmExport.cxx src/pcm/PcmExport.hxx \
|
||||
src/pcm/PcmConvert.cxx src/pcm/PcmConvert.hxx \
|
||||
src/pcm/PcmDop.cxx src/pcm/PcmDop.hxx \
|
||||
src/pcm/Volume.cxx src/pcm/Volume.hxx \
|
||||
src/pcm/Silence.cxx src/pcm/Silence.hxx \
|
||||
src/pcm/PcmMix.cxx src/pcm/PcmMix.hxx \
|
||||
src/pcm/PcmChannels.cxx src/pcm/PcmChannels.hxx \
|
||||
src/pcm/PcmPack.cxx src/pcm/PcmPack.hxx \
|
||||
@@ -527,7 +530,7 @@ libfs_a_SOURCES = \
|
||||
src/fs/Traits.cxx src/fs/Traits.hxx \
|
||||
src/fs/Config.cxx src/fs/Config.hxx \
|
||||
src/fs/Charset.cxx src/fs/Charset.hxx \
|
||||
src/fs/Path.cxx src/fs/Path.hxx \
|
||||
src/fs/Path.cxx src/fs/Path2.cxx src/fs/Path.hxx \
|
||||
src/fs/AllocatedPath.cxx src/fs/AllocatedPath.hxx \
|
||||
src/fs/FileSystem.cxx src/fs/FileSystem.hxx \
|
||||
src/fs/StandardDirectory.cxx src/fs/StandardDirectory.hxx \
|
||||
@@ -798,6 +801,11 @@ endif
|
||||
if HAVE_FFMPEG
|
||||
noinst_LIBRARIES += libffmpeg.a
|
||||
libffmpeg_a_SOURCES = \
|
||||
src/lib/ffmpeg/Init.cxx src/lib/ffmpeg/Init.hxx \
|
||||
src/lib/ffmpeg/Time.hxx \
|
||||
src/lib/ffmpeg/Buffer.hxx \
|
||||
src/lib/ffmpeg/LogError.cxx src/lib/ffmpeg/LogError.hxx \
|
||||
src/lib/ffmpeg/LogCallback.cxx src/lib/ffmpeg/LogCallback.hxx \
|
||||
src/lib/ffmpeg/Error.cxx src/lib/ffmpeg/Error.hxx \
|
||||
src/lib/ffmpeg/Domain.cxx src/lib/ffmpeg/Domain.hxx
|
||||
libffmpeg_a_CPPFLAGS = $(AM_CPPFLAGS) \
|
||||
@@ -979,6 +987,8 @@ endif
|
||||
|
||||
if HAVE_FFMPEG
|
||||
libdecoder_a_SOURCES += \
|
||||
src/decoder/plugins/FfmpegIo.cxx \
|
||||
src/decoder/plugins/FfmpegIo.hxx \
|
||||
src/decoder/plugins/FfmpegMetaData.cxx \
|
||||
src/decoder/plugins/FfmpegMetaData.hxx \
|
||||
src/decoder/plugins/FfmpegDecoderPlugin.cxx \
|
||||
@@ -1219,6 +1229,7 @@ liboutput_plugins_a_SOURCES = \
|
||||
|
||||
MIXER_LIBS = \
|
||||
libmixer_plugins.a \
|
||||
$(ALSA_LIBS) \
|
||||
$(PULSE_LIBS)
|
||||
|
||||
MIXER_API_SRC = \
|
||||
@@ -1865,6 +1876,7 @@ test_run_convert_SOURCES = test/run_convert.cxx \
|
||||
src/AudioParser.cxx
|
||||
test_run_convert_LDADD = \
|
||||
$(PCM_LIBS) \
|
||||
libconf.a \
|
||||
libutil.a \
|
||||
$(GLIB_LIBS)
|
||||
|
||||
@@ -2108,7 +2120,9 @@ developer_DATA = $(wildcard doc/developer/*.html)
|
||||
|
||||
DOCBOOK_HTML = $(patsubst %.xml,%/index.html,$(DOCBOOK_FILES))
|
||||
|
||||
$(DOCBOOK_HTML): %/index.html: %.xml
|
||||
DOCBOOK_INCLUDES = $(wildcard $(srcdir)/doc/include/*.xml)
|
||||
|
||||
$(DOCBOOK_HTML): %/index.html: %.xml $(DOCBOOK_INCLUDES)
|
||||
$(XMLTO) -o $(@D) --stringparam chunker.output.encoding=utf-8 html --stringparam use.id.as.filename=1 $<
|
||||
|
||||
doc/api/html/index.html: doc/doxygen.conf
|
||||
@@ -2149,8 +2163,9 @@ EXTRA_DIST = $(doc_DATA) autogen.sh \
|
||||
test/test_archive_bzip2.sh \
|
||||
test/test_archive_iso9660.sh \
|
||||
test/test_archive_zzip.sh \
|
||||
$(wildcard scripts/*.sh) \
|
||||
$(wildcard $(srcdir)/scripts/*.rb) \
|
||||
$(man_MANS) $(DOCBOOK_FILES) doc/mpdconf.example doc/doxygen.conf \
|
||||
$(wildcard $(srcdir)/doc/include/*.xml) \
|
||||
systemd/mpd.socket \
|
||||
android/AndroidManifest.xml \
|
||||
android/build.py \
|
||||
|
87
NEWS
87
NEWS
@@ -1,3 +1,90 @@
|
||||
ver 0.19.18 (2016/08/05)
|
||||
* decoder
|
||||
- ffmpeg: fix crash with older FFmpeg versions (< 3.0)
|
||||
- ffmpeg: log detailed error message
|
||||
- ffmpeg: support FFmpeg 3.1
|
||||
- sidplay: detect libsidplay2 with pkg-config
|
||||
- sidplay: log detailed error message
|
||||
- sidplay: read the "date" tag
|
||||
- sidplay: allow building with libsidplayfp instead of libsidplay2
|
||||
* output
|
||||
- shout: recognize setting "encoder" instead of "encoding"
|
||||
* fix memory leak after stream failure
|
||||
* fix build failure with Boost 1.61
|
||||
* require gcc 4.7 or newer
|
||||
|
||||
ver 0.19.17 (2016/07/09)
|
||||
* decoder
|
||||
- flac: fix assertion failure while seeking
|
||||
- flac: fix stream duration indicator
|
||||
- fix seek problems in several plugins
|
||||
* fix spurious seek error "Failed to allocate silence buffer"
|
||||
* replay gain: fix "replay_gain_handler mixer" setting
|
||||
* DSD: use 0x69 as silence pattern
|
||||
* fix use-after-free bug on "close" and "kill"
|
||||
|
||||
ver 0.19.16 (2016/06/13)
|
||||
* faster seeking
|
||||
* fix system include path order
|
||||
* add missing DocBook file to tarball
|
||||
|
||||
ver 0.19.15 (2016/04/30)
|
||||
* decoder
|
||||
- ffmpeg: support FFmpeg 3.0
|
||||
- ffmpeg: use as fallback instead of "mad" if no plugin matches
|
||||
- opus: support bigger OpusTags packets
|
||||
* fix more build failures on non-glibc builds due to constexpr Mutex
|
||||
* fix build failure due to missing include
|
||||
* fix unit test on Alpha
|
||||
|
||||
ver 0.19.14 (2016/03/18)
|
||||
* decoder
|
||||
- dsdiff: fix off-by-one buffer overflow
|
||||
- opus: limit tag size to 64 kB
|
||||
* archive
|
||||
- iso9660: fix buffer overflow
|
||||
* fix quadratic runtime bug in the tag pool
|
||||
* fix build failures on non-glibc builds due to constexpr Mutex
|
||||
|
||||
ver 0.19.13 (2016/02/23)
|
||||
* tags
|
||||
- aiff, riff: fix ID3 chunk padding
|
||||
* decoder
|
||||
- ffmpeg: support the TAK codec
|
||||
* fix disappearing duration of remote songs during playback
|
||||
* initialize supplementary groups with glibc 2.19+
|
||||
|
||||
ver 0.19.12 (2015/12/15)
|
||||
* fix assertion failure on malformed UTF-8 tag
|
||||
* fix build failure on non-Linux systems
|
||||
* fix LimitRTTIME in systemd unit file
|
||||
|
||||
ver 0.19.11 (2015/10/27)
|
||||
* tags
|
||||
- ape: fix buffer overflow
|
||||
* decoder
|
||||
- ffmpeg: fix crash due to wrong avio_alloc_context() call
|
||||
- gme: don't loop forever, fall back to GME's default play length
|
||||
* encoder
|
||||
- flac: fix crash with 32 bit playback
|
||||
* mixer
|
||||
- fix mixer lag after enabling/disabling output
|
||||
|
||||
ver 0.19.10 (2015/06/21)
|
||||
* input
|
||||
- curl: fix deadlock on small responses
|
||||
- smbclient: fix DFF playback
|
||||
* decoder
|
||||
- ffmpeg: improve seeking accuracy
|
||||
- fix stuck stream tags
|
||||
* encoder
|
||||
- opus: fix bogus granulepos
|
||||
* output
|
||||
- fix failure to open device right after booting
|
||||
* neighbor
|
||||
- nfs: fix deadlock when connecting
|
||||
* fix "single" mode breakage due to queue edits
|
||||
|
||||
ver 0.19.9 (2015/02/06)
|
||||
* decoder
|
||||
- dsdiff, dsf: raise ID3 tag limit to 1 MB
|
||||
|
42
configure.ac
42
configure.ac
@@ -1,10 +1,10 @@
|
||||
AC_PREREQ(2.60)
|
||||
|
||||
AC_INIT(mpd, 0.19.9, musicpd-dev-team@lists.sourceforge.net)
|
||||
AC_INIT(mpd, 0.19.18, musicpd-dev-team@lists.sourceforge.net)
|
||||
|
||||
VERSION_MAJOR=0
|
||||
VERSION_MINOR=19
|
||||
VERSION_REVISION=9
|
||||
VERSION_REVISION=18
|
||||
VERSION_EXTRA=0
|
||||
|
||||
AC_CONFIG_SRCDIR([src/Main.cxx])
|
||||
@@ -206,6 +206,7 @@ if test x$host_is_linux = xyes; then
|
||||
fi
|
||||
|
||||
AC_CHECK_FUNCS(getpwnam_r getpwuid_r)
|
||||
AC_CHECK_FUNCS(initgroups)
|
||||
AC_CHECK_FUNCS(strndup)
|
||||
|
||||
if test x$host_is_linux = xyes; then
|
||||
@@ -541,8 +542,6 @@ AC_ARG_ENABLE(sidplay,
|
||||
AS_HELP_STRING([--enable-sidplay],
|
||||
[enable C64 SID support via libsidplay2]),,
|
||||
enable_sidplay=auto)
|
||||
MPD_DEPENDS([enable_sidplay], [enable_glib],
|
||||
[Cannot use --enable-sidplay with --disable-glib])
|
||||
|
||||
AC_ARG_ENABLE(shine-encoder,
|
||||
AS_HELP_STRING([--enable-shine-encoder],
|
||||
@@ -702,12 +701,6 @@ AC_ARG_ENABLE(glib,
|
||||
if test x$enable_glib = xyes; then
|
||||
PKG_CHECK_MODULES([GLIB], [glib-2.0 >= 2.28 gthread-2.0],,
|
||||
[AC_MSG_ERROR([GLib 2.28 is required])])
|
||||
|
||||
if test x$GCC = xyes; then
|
||||
# suppress warnings in the GLib headers
|
||||
GLIB_CFLAGS=`echo $GLIB_CFLAGS |sed -e 's,-I/,-isystem /,g'`
|
||||
fi
|
||||
|
||||
AC_DEFINE(HAVE_GLIB, 1, [Define if GLib is used])
|
||||
fi
|
||||
AM_CONDITIONAL(HAVE_GLIB, test x$enable_glib = xyes)
|
||||
@@ -1340,31 +1333,36 @@ AM_CONDITIONAL(ENABLE_VORBIS_DECODER, test x$enable_vorbis = xyes || test x$enab
|
||||
|
||||
dnl --------------------------------- sidplay ---------------------------------
|
||||
if test x$enable_sidplay != xno; then
|
||||
# we're not using pkg-config here
|
||||
# because libsidplay2's .pc file requires libtool
|
||||
AC_CHECK_LIB([sidplay2],[main],[found_sidplay=yes],[found_sidplay=no],[])
|
||||
dnl Check for libsidplayfp first
|
||||
PKG_CHECK_MODULES([SIDPLAY], [libsidplayfp libsidutils],
|
||||
[found_sidplayfp=yes],
|
||||
[found_sidplayfp=no])
|
||||
found_sidplay=$found_sidplayfp
|
||||
fi
|
||||
|
||||
if test x$enable_sidplay != xno && test x$found_sidplayfp = xno; then
|
||||
PKG_CHECK_MODULES([SIDPLAY], [libsidplay2 libsidutils],
|
||||
[found_sidplay=yes],
|
||||
[found_sidplay=no])
|
||||
|
||||
MPD_AUTO_PRE(sidplay, [sidplay decoder plugin],
|
||||
[libsidplay2 not found])
|
||||
fi
|
||||
|
||||
if test x$enable_sidplay != xno; then
|
||||
if test x$enable_sidplay != xno && test x$found_sidplayfp = xno; then
|
||||
AC_CHECK_LIB([resid-builder], [main],
|
||||
[found_sidplay=yes], [found_sidplay=no])
|
||||
|
||||
if test x$found_sidplay = xyes; then
|
||||
AC_CHECK_LIB([sidutils],[main],[:],[found_sidplay=no],[])
|
||||
fi
|
||||
|
||||
MPD_AUTO_RESULT(sidplay, [sidplay decoder plugin],
|
||||
[libresid-builder or libsidutils not found])
|
||||
[libresid-builder not found])
|
||||
fi
|
||||
|
||||
if test x$enable_sidplay = xyes; then
|
||||
AC_SUBST(SIDPLAY_LIBS,"-lsidplay2 -lresid-builder -lsidutils")
|
||||
AC_SUBST(SIDPLAY_CFLAGS,)
|
||||
|
||||
SIDPLAY_LIBS="$SIDPLAY_LIBS -lresid-builder"
|
||||
AC_DEFINE(ENABLE_SIDPLAY, 1, [Define for libsidplay2 support])
|
||||
if test x$found_sidplayfp = xyes; then
|
||||
AC_DEFINE(HAVE_SIDPLAYFP, 1, [Define if libsidplayfp is used instead of libsidplay2])
|
||||
fi
|
||||
fi
|
||||
|
||||
AM_CONDITIONAL(ENABLE_SIDPLAY, test x$enable_sidplay = xyes)
|
||||
|
@@ -1,6 +1,7 @@
|
||||
<?xml version='1.0' encoding="utf-8"?>
|
||||
<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
|
||||
"docbook/dtd/xml/4.2/docbookx.dtd">
|
||||
<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
|
||||
"http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd">
|
||||
|
||||
<book>
|
||||
<title>The Music Player Daemon - Developer's Manual</title>
|
||||
|
||||
@@ -40,7 +41,7 @@
|
||||
<listitem>
|
||||
<para>
|
||||
the code should be C++11 compliant, and must compile with
|
||||
<application>GCC</application> 4.6 and
|
||||
<application>GCC</application> 4.7 and
|
||||
<application>clang</application> 3.2
|
||||
</para>
|
||||
</listitem>
|
||||
|
154
doc/include/tags.xml
Normal file
154
doc/include/tags.xml
Normal file
@@ -0,0 +1,154 @@
|
||||
<?xml version='1.0' encoding="utf-8"?>
|
||||
<!DOCTYPE itemizedlist PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
|
||||
"http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd">
|
||||
|
||||
<itemizedlist>
|
||||
<listitem>
|
||||
<para>
|
||||
<varname>artist</varname>: the artist name. Its meaning is not
|
||||
well-defined; see <varname>composer</varname> and
|
||||
<varname>performer</varname> for more specific tags.
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
<varname>artistsort</varname>: same as
|
||||
<varname>artist</varname>, but for sorting. This usually omits
|
||||
prefixes such as "The".
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
<varname>album</varname>: the album name.
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
<varname>albumsort</varname>: same as <varname>album</varname>,
|
||||
but for sorting.
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
<varname>albumartist</varname>: on multi-artist albums, this is
|
||||
the artist name which shall be used for the whole album. The
|
||||
exact meaning of this tag is not well-defined.
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
<varname>albumartistsort</varname>: same as
|
||||
<varname>albumartist</varname>, but for sorting.
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
<varname>title</varname>: the song title.
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
<varname>track</varname>: the track number within the album.
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
<varname>name</varname>: a name for this song. This is not the
|
||||
song title. The exact meaning of this tag is not well-defined.
|
||||
It is often used by badly configured internet radio stations
|
||||
with broken tags to squeeze both the artist name and the song
|
||||
title in one tag.
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
<varname>genre</varname>: the music genre.
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
<varname>date</varname>: the song's release date. This is
|
||||
usually a 4-digit year.
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
<varname>composer</varname>: the artist who composed the song.
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
<varname>performer</varname>: the artist who performed the song.
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
<varname>comment</varname>: a human-readable comment about this
|
||||
song. The exact meaning of this tag is not well-defined.
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
<varname>disc</varname>: the disc number in a multi-disc album.
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
<varname>musicbrainz_artistid</varname>: the artist id in the
|
||||
<ulink
|
||||
url="http://musicbrainz.org/doc/MusicBrainzTag">MusicBrainz</ulink>
|
||||
database.
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
<varname>musicbrainz_albumid</varname>: the album id in the
|
||||
<ulink
|
||||
url="http://musicbrainz.org/doc/MusicBrainzTag">MusicBrainz</ulink>
|
||||
database.
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
<varname>musicbrainz_albumartistid</varname>: the album artist
|
||||
id in the <ulink
|
||||
url="http://musicbrainz.org/doc/MusicBrainzTag">MusicBrainz</ulink>
|
||||
database.
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
<varname>musicbrainz_trackid</varname>: the track id in the
|
||||
<ulink
|
||||
url="http://musicbrainz.org/doc/MusicBrainzTag">MusicBrainz</ulink>
|
||||
database.
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
<varname>musicbrainz_releasetrackid</varname>: the release track
|
||||
id in the <ulink
|
||||
url="http://musicbrainz.org/doc/MusicBrainzTag">MusicBrainz</ulink>
|
||||
database.
|
||||
</para>
|
||||
</listitem>
|
||||
</itemizedlist>
|
@@ -174,18 +174,6 @@ MP3 playback.
|
||||
This specifies whether relative or absolute paths for song filenames are used
|
||||
when saving playlists. The default is "no".
|
||||
.TP
|
||||
.B metadata_to_use <tags>
|
||||
This specifies the tag types that will be scanned for and made available to
|
||||
clients. Note that you must recreate (not update) your database for changes to
|
||||
this parameter to take effect. Possible values are artist, album, title,
|
||||
track, name, genre, date, composer, performer, comment, disc,
|
||||
musicbrainz_artistid, musicbrainz_albumid, musicbrainz_albumartistid,
|
||||
musicbrainz_releasetrackid, musicbrainz_trackid. Multiple tags may be specified
|
||||
as a comma separated list.
|
||||
An example value is "artist,album,title,track". The special value "none" may
|
||||
be used alone to disable all metadata. The default is to use all known tag
|
||||
types except for comments and those starting with "musicbrainz".
|
||||
.TP
|
||||
.B auto_update <yes or no>
|
||||
This specifies the whether to support automatic update of music database when
|
||||
files are changed in music_directory. The default is to disable autoupdate
|
||||
|
@@ -115,7 +115,7 @@
|
||||
#
|
||||
# This setting defines a list of tag types that will be extracted during the
|
||||
# audio file discovery process. The complete list of possible values can be
|
||||
# found in the mpd.conf man page.
|
||||
# found in the user manual.
|
||||
#metadata_to_use "artist,album,title,track,name,genre,date,composer,performer,disc"
|
||||
#
|
||||
# This setting enables automatic update of MPD's database when files in
|
||||
@@ -231,7 +231,7 @@ input {
|
||||
#
|
||||
#audio_output {
|
||||
# type "shout"
|
||||
# encoding "ogg" # optional
|
||||
# encoder "vorbis" # optional
|
||||
# name "My Shout Stream"
|
||||
# host "localhost"
|
||||
# port "8000"
|
||||
|
@@ -1,6 +1,7 @@
|
||||
<?xml version='1.0' encoding="utf-8"?>
|
||||
<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
|
||||
"docbook/dtd/xml/4.2/docbookx.dtd">
|
||||
<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
|
||||
"http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd">
|
||||
|
||||
<book>
|
||||
<title>The Music Player Daemon protocol</title>
|
||||
|
||||
@@ -201,6 +202,25 @@
|
||||
omitted, then the maximum possible value is assumed.
|
||||
</para>
|
||||
</section>
|
||||
|
||||
<section id="tags">
|
||||
<title>Tags</title>
|
||||
|
||||
<para>
|
||||
The following tags are supported by
|
||||
<application>MPD</application>:
|
||||
</para>
|
||||
|
||||
<xi:include href="include/tags.xml"
|
||||
xmlns:xi="http://www.w3.org/2001/XInclude"/>
|
||||
|
||||
<para>
|
||||
There can be multiple values for some of these tags. For
|
||||
example, <application>MPD</application> may return multiple
|
||||
lines with a <varname>performer</varname> tag. A tag value is
|
||||
a UTF-8 string.
|
||||
</para>
|
||||
</section>
|
||||
</chapter>
|
||||
|
||||
<chapter id="recipes">
|
||||
|
102
doc/user.xml
102
doc/user.xml
@@ -1,6 +1,7 @@
|
||||
<?xml version='1.0' encoding="utf-8"?>
|
||||
<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
|
||||
"docbook/dtd/xml/4.2/docbookx.dtd">
|
||||
<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
|
||||
"http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd">
|
||||
|
||||
<book>
|
||||
<title>The Music Player Daemon - User's Manual</title>
|
||||
|
||||
@@ -16,7 +17,7 @@
|
||||
<application>MPD</application> (Music Player Daemon) is, as the
|
||||
name suggests, a server software allowing you to remotely play
|
||||
your music, handle playlists, deliver music (HTTP streams with
|
||||
various sub-protocols) and organizze playlists.
|
||||
various sub-protocols) and organize playlists.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
@@ -944,6 +945,33 @@ systemctl start mpd.socket</programlisting>
|
||||
<section id="config_other">
|
||||
<title>Other Settings</title>
|
||||
|
||||
<informaltable>
|
||||
<tgroup cols="2">
|
||||
<thead>
|
||||
<row>
|
||||
<entry>Setting</entry>
|
||||
<entry>Description</entry>
|
||||
</row>
|
||||
</thead>
|
||||
<tbody>
|
||||
<row>
|
||||
<entry>
|
||||
<varname>metadata_to_use</varname>
|
||||
<parameter>TAG1,TAG2,...</parameter>
|
||||
</entry>
|
||||
<entry>
|
||||
Use only the specified tags, and ignore the others.
|
||||
This setting can reduce the database size and
|
||||
<application>MPD</application>'s memory usage by
|
||||
omitting unused tags. By default, all tags but
|
||||
<varname>comment</varname> are enabled. The special
|
||||
value "none" disables all tags.
|
||||
</entry>
|
||||
</row>
|
||||
</tbody>
|
||||
</tgroup>
|
||||
</informaltable>
|
||||
|
||||
<section>
|
||||
<title>The State File</title>
|
||||
|
||||
@@ -1190,6 +1218,58 @@ database {
|
||||
plugin).
|
||||
</para>
|
||||
</section>
|
||||
|
||||
<section id="realtime">
|
||||
<title>Real-Time Scheduling</title>
|
||||
|
||||
<para>
|
||||
On Linux, <application>MPD</application> attempts to configure
|
||||
<ulink
|
||||
url="https://en.wikipedia.org/wiki/Real-time_computing">real-time
|
||||
scheduling</ulink> for some threads that benefit from it.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
This is only possible you allow <application>MPD</application>
|
||||
to do it. This privilege is controlled by
|
||||
<varname>RLIMIT_RTPRIO</varname>
|
||||
<varname>RLIMIT_RTTIME</varname>. You can configure this
|
||||
privilege with <command>ulimit</command> before launching
|
||||
<application>MPD</application>:
|
||||
</para>
|
||||
|
||||
<programlisting>ulimit -HS -r 50; mpd</programlisting>
|
||||
|
||||
<para>
|
||||
Or you can use the <command>prlimit</command> program from the
|
||||
<application>util-linux</application> package:
|
||||
</para>
|
||||
|
||||
<programlisting>prlimit --rtprio=50 --rttime=unlimited mpd</programlisting>
|
||||
|
||||
<para>
|
||||
The <application>systemd</application> service file shipped
|
||||
with <application>MPD</application> comes with this setting.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
This works only if the Linux kernel was compiled with
|
||||
<varname>CONFIG_RT_GROUP_SCHED</varname> disabled. Use the
|
||||
following command to check this option for your current
|
||||
kernel:
|
||||
</para>
|
||||
|
||||
<programlisting>zgrep ^CONFIG_RT_GROUP_SCHED /proc/config.gz</programlisting>
|
||||
|
||||
<note>
|
||||
<para>
|
||||
There is a rumor that real-time scheduling improves audio
|
||||
quality. That is not true. All it does is reduce the
|
||||
probability of skipping (audio buffer xruns) when the
|
||||
computer is under heavy load.
|
||||
</para>
|
||||
</note>
|
||||
</section>
|
||||
</chapter>
|
||||
|
||||
<chapter id="use">
|
||||
@@ -1245,6 +1325,19 @@ database {
|
||||
</para>
|
||||
</section>
|
||||
|
||||
<section id="tags">
|
||||
<title>Metadata</title>
|
||||
|
||||
<para>
|
||||
When scanning or playing a song,
|
||||
<application>MPD</application> parses its metadata. The
|
||||
following tags are supported:
|
||||
</para>
|
||||
|
||||
<xi:include href="include/tags.xml"
|
||||
xmlns:xi="http://www.w3.org/2001/XInclude"/>
|
||||
</section>
|
||||
|
||||
<section id="queue">
|
||||
<title>The queue</title>
|
||||
|
||||
@@ -2595,7 +2688,8 @@ buffer_size: 16384</programlisting>
|
||||
/ <ulink
|
||||
url="http://icecast.org/"><application>IceCast</application></ulink>.
|
||||
HTTP streaming clients like
|
||||
<application>mplayer</application> can connect to it.
|
||||
<application>mplayer</application>, <application>VLC</application>,
|
||||
and <application>mpv</application> can connect to it.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
|
@@ -45,7 +45,7 @@
|
||||
# error Sorry, your clang version is too old. You need at least version 3.1.
|
||||
# endif
|
||||
#elif defined(__GNUC__)
|
||||
# if GCC_OLDER_THAN(4,6)
|
||||
# if GCC_OLDER_THAN(4,7)
|
||||
# error Sorry, your gcc version is too old. You need at least version 4.6.
|
||||
# endif
|
||||
#else
|
||||
|
@@ -188,6 +188,14 @@ public:
|
||||
tag = std::move(other.tag);
|
||||
}
|
||||
|
||||
/**
|
||||
* Similar to the MoveTagFrom(), but move only the #TagItem
|
||||
* array.
|
||||
*/
|
||||
void MoveTagItemsFrom(DetachedSong &&other) {
|
||||
tag.MoveItemsFrom(std::move(other.tag));
|
||||
}
|
||||
|
||||
time_t GetLastModified() const {
|
||||
return mtime;
|
||||
}
|
||||
|
@@ -54,7 +54,6 @@
|
||||
#include "system/FatalError.hxx"
|
||||
#include "util/UriUtil.hxx"
|
||||
#include "util/Error.hxx"
|
||||
#include "util/Domain.hxx"
|
||||
#include "thread/Id.hxx"
|
||||
#include "thread/Slack.hxx"
|
||||
#include "lib/icu/Init.hxx"
|
||||
@@ -123,8 +122,6 @@
|
||||
static constexpr unsigned DEFAULT_BUFFER_SIZE = 4096;
|
||||
static constexpr unsigned DEFAULT_BUFFER_BEFORE_PLAY = 10;
|
||||
|
||||
static constexpr Domain main_domain("main");
|
||||
|
||||
#ifdef ANDROID
|
||||
Context *context;
|
||||
#endif
|
||||
@@ -633,7 +630,7 @@ static int mpd_main_after_fork(struct options options)
|
||||
config_get_unsigned(CONF_AUTO_UPDATE_DEPTH,
|
||||
INT_MAX));
|
||||
#else
|
||||
FormatWarning(main_domain,
|
||||
FormatWarning(config_domain,
|
||||
"inotify: auto_update was disabled. enable during compilation phase");
|
||||
#endif
|
||||
}
|
||||
|
@@ -25,6 +25,7 @@
|
||||
#include "MusicPipe.hxx"
|
||||
#include "MusicBuffer.hxx"
|
||||
#include "MusicChunk.hxx"
|
||||
#include "pcm/Silence.hxx"
|
||||
#include "DetachedSong.hxx"
|
||||
#include "system/FatalError.hxx"
|
||||
#include "CrossFade.hxx"
|
||||
@@ -486,8 +487,12 @@ Player::SendSilence()
|
||||
|
||||
MusicChunk *chunk = buffer.Allocate();
|
||||
if (chunk == nullptr) {
|
||||
LogError(player_domain, "Failed to allocate silence buffer");
|
||||
return false;
|
||||
/* this is non-fatal, because this means that the
|
||||
decoder has filled to buffer completely meanwhile;
|
||||
by ignoring the error, we work around this race
|
||||
condition */
|
||||
LogDebug(player_domain, "Failed to allocate silence buffer");
|
||||
return true;
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
@@ -501,7 +506,7 @@ Player::SendSilence()
|
||||
|
||||
chunk->time = SignedSongTime::Negative(); /* undefined time stamp */
|
||||
chunk->length = num_frames * frame_size;
|
||||
memset(chunk->data, 0, chunk->length);
|
||||
PcmSilence({chunk->data, chunk->length}, play_audio_format.format);
|
||||
|
||||
Error error;
|
||||
if (!pc.outputs.Play(chunk, error)) {
|
||||
@@ -518,6 +523,8 @@ Player::SeekDecoder()
|
||||
{
|
||||
assert(pc.next_song != nullptr);
|
||||
|
||||
pc.outputs.Cancel();
|
||||
|
||||
const SongTime start_time = pc.next_song->GetStartTime();
|
||||
|
||||
if (!dc.LockIsCurrentSong(*pc.next_song)) {
|
||||
@@ -583,8 +590,6 @@ Player::SeekDecoder()
|
||||
/* re-fill the buffer after seeking */
|
||||
buffering = true;
|
||||
|
||||
pc.outputs.Cancel();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -612,6 +617,12 @@ Player::ProcessCommand()
|
||||
|
||||
queued = true;
|
||||
pc.CommandFinished();
|
||||
|
||||
pc.Unlock();
|
||||
if (dc.LockIsIdle())
|
||||
StartDecoder(*new MusicPipe());
|
||||
pc.Lock();
|
||||
|
||||
break;
|
||||
|
||||
case PlayerCommand::PAUSE:
|
||||
|
@@ -53,7 +53,7 @@ public:
|
||||
|
||||
Bzip2ArchiveFile(Path path, InputStream *_is)
|
||||
:ArchiveFile(bz2_archive_plugin),
|
||||
name(PathTraitsFS::GetBase(path.c_str())),
|
||||
name(path.GetBase().c_str()),
|
||||
istream(_is) {
|
||||
// remove .bz2 suffix
|
||||
const size_t len = name.length();
|
||||
|
@@ -66,7 +66,11 @@ public:
|
||||
return iso9660_iso_seek_read(iso, ptr, start, i_size);
|
||||
}
|
||||
|
||||
void Visit(const char *path, ArchiveVisitor &visitor);
|
||||
/**
|
||||
* @param capacity the path buffer size
|
||||
*/
|
||||
void Visit(char *path, size_t length, size_t capacity,
|
||||
ArchiveVisitor &visitor);
|
||||
|
||||
virtual void Close() override {
|
||||
Unref();
|
||||
@@ -84,32 +88,36 @@ static constexpr Domain iso9660_domain("iso9660");
|
||||
/* archive open && listing routine */
|
||||
|
||||
inline void
|
||||
Iso9660ArchiveFile::Visit(const char *psz_path, ArchiveVisitor &visitor)
|
||||
Iso9660ArchiveFile::Visit(char *path, size_t length, size_t capacity,
|
||||
ArchiveVisitor &visitor)
|
||||
{
|
||||
CdioList_t *entlist;
|
||||
CdioListNode_t *entnode;
|
||||
iso9660_stat_t *statbuf;
|
||||
char pathname[4096];
|
||||
|
||||
entlist = iso9660_ifs_readdir (iso, psz_path);
|
||||
auto *entlist = iso9660_ifs_readdir(iso, path);
|
||||
if (!entlist) {
|
||||
return;
|
||||
}
|
||||
/* Iterate over the list of nodes that iso9660_ifs_readdir gives */
|
||||
CdioListNode_t *entnode;
|
||||
_CDIO_LIST_FOREACH (entnode, entlist) {
|
||||
statbuf = (iso9660_stat_t *) _cdio_list_node_data (entnode);
|
||||
auto *statbuf = (iso9660_stat_t *)
|
||||
_cdio_list_node_data(entnode);
|
||||
const char *filename = statbuf->filename;
|
||||
if (strcmp(filename, ".") == 0 || strcmp(filename, "..") == 0)
|
||||
continue;
|
||||
|
||||
strcpy(pathname, psz_path);
|
||||
strcat(pathname, statbuf->filename);
|
||||
size_t filename_length = strlen(filename);
|
||||
if (length + filename_length + 1 >= capacity)
|
||||
/* file name is too long */
|
||||
continue;
|
||||
|
||||
memcpy(path + length, filename, filename_length + 1);
|
||||
size_t new_length = length + filename_length;
|
||||
|
||||
if (iso9660_stat_s::_STAT_DIR == statbuf->type ) {
|
||||
if (strcmp(statbuf->filename, ".") && strcmp(statbuf->filename, "..")) {
|
||||
strcat(pathname, "/");
|
||||
Visit(pathname, visitor);
|
||||
}
|
||||
memcpy(path + new_length, "/", 2);
|
||||
Visit(path, new_length + 1, capacity, visitor);
|
||||
} else {
|
||||
//remove leading /
|
||||
visitor.VisitArchiveEntry(pathname + 1);
|
||||
visitor.VisitArchiveEntry(path + 1);
|
||||
}
|
||||
}
|
||||
_cdio_list_free (entlist, true);
|
||||
@@ -133,7 +141,8 @@ iso9660_archive_open(Path pathname, Error &error)
|
||||
void
|
||||
Iso9660ArchiveFile::Visit(ArchiveVisitor &visitor)
|
||||
{
|
||||
Visit("/", visitor);
|
||||
char path[4096] = "/";
|
||||
Visit(path, 1, sizeof(path), visitor);
|
||||
}
|
||||
|
||||
/* single archive handling */
|
||||
|
@@ -52,8 +52,8 @@ Client::OnSocketInput(void *data, size_t length)
|
||||
break;
|
||||
|
||||
case CommandResult::KILL:
|
||||
Close();
|
||||
partition.instance.event_loop->Break();
|
||||
Close();
|
||||
return InputResult::CLOSED;
|
||||
|
||||
case CommandResult::FINISH:
|
||||
|
@@ -34,7 +34,9 @@
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
|
||||
#ifdef HAVE_GLIB
|
||||
static constexpr Domain exclude_list_domain("exclude_list");
|
||||
#endif
|
||||
|
||||
bool
|
||||
ExcludeList::LoadFile(Path path_fs)
|
||||
|
@@ -301,7 +301,8 @@ decoder_check_cancel_read(const Decoder *decoder)
|
||||
/* ignore the SEEK command during initialization, the plugin
|
||||
should handle that after it has initialized successfully */
|
||||
if (dc.command == DecoderCommand::SEEK &&
|
||||
(dc.state == DecoderState::START || decoder->seeking))
|
||||
(dc.state == DecoderState::START || decoder->seeking ||
|
||||
decoder->initial_seek_running))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
@@ -433,8 +434,11 @@ update_stream_tag(Decoder &decoder, InputStream *is)
|
||||
|
||||
/* no stream tag present - submit the song tag
|
||||
instead */
|
||||
decoder.song_tag = nullptr;
|
||||
}
|
||||
} else
|
||||
/* discard the song tag; we don't need it */
|
||||
delete decoder.song_tag;
|
||||
|
||||
decoder.song_tag = nullptr;
|
||||
|
||||
delete decoder.stream_tag;
|
||||
decoder.stream_tag = tag;
|
||||
@@ -566,7 +570,7 @@ decoder_tag(Decoder &decoder, InputStream *is,
|
||||
/* save the tag */
|
||||
|
||||
delete decoder.decoder_tag;
|
||||
decoder.decoder_tag = new Tag(tag);
|
||||
decoder.decoder_tag = new Tag(std::move(tag));
|
||||
|
||||
/* check for a new stream tag */
|
||||
|
||||
|
@@ -25,6 +25,7 @@
|
||||
#include "util/ConstBuffer.hxx"
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
struct Decoder;
|
||||
class InputStream;
|
||||
|
@@ -98,6 +98,7 @@ decoder_input_stream_open(DecoderControl &dc, const char *uri)
|
||||
|
||||
if (!is->Check(error)) {
|
||||
dc.Unlock();
|
||||
delete is;
|
||||
|
||||
LogError(error);
|
||||
return nullptr;
|
||||
@@ -255,7 +256,11 @@ decoder_run_stream_fallback(Decoder &decoder, InputStream &is)
|
||||
{
|
||||
const struct DecoderPlugin *plugin;
|
||||
|
||||
#ifdef HAVE_FFMPEG
|
||||
plugin = decoder_plugin_from_name("ffmpeg");
|
||||
#else
|
||||
plugin = decoder_plugin_from_name("mad");
|
||||
#endif
|
||||
return plugin != nullptr && plugin->stream_decode != nullptr &&
|
||||
decoder_stream_decode(*plugin, decoder, is);
|
||||
}
|
||||
@@ -380,7 +385,11 @@ decoder_run_song(DecoderControl &dc,
|
||||
const DetachedSong &song, const char *uri, Path path_fs)
|
||||
{
|
||||
Decoder decoder(dc, dc.start_time.IsPositive(),
|
||||
new Tag(song.GetTag()));
|
||||
/* pass the song tag only if it's
|
||||
authoritative, i.e. if it's a local file -
|
||||
tags on "stream" songs are just remembered
|
||||
from the last time we played it*/
|
||||
song.IsFile() ? new Tag(song.GetTag()) : nullptr);
|
||||
int ret;
|
||||
|
||||
dc.state = DecoderState::START;
|
||||
|
@@ -205,7 +205,7 @@ dsdiff_handle_native_tag(InputStream &is,
|
||||
if (length == 0 || length > 60)
|
||||
return;
|
||||
|
||||
char string[length];
|
||||
char string[length + 1];
|
||||
char *label;
|
||||
label = string;
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
100
src/decoder/plugins/FfmpegIo.cxx
Normal file
100
src/decoder/plugins/FfmpegIo.cxx
Normal file
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2016 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.
|
||||
*/
|
||||
|
||||
/* necessary because libavutil/common.h uses UINT64_C */
|
||||
#define __STDC_CONSTANT_MACROS
|
||||
|
||||
#include "config.h"
|
||||
#include "FfmpegIo.hxx"
|
||||
#include "../DecoderAPI.hxx"
|
||||
#include "input/InputStream.hxx"
|
||||
#include "util/Error.hxx"
|
||||
|
||||
AvioStream::~AvioStream()
|
||||
{
|
||||
if (io != nullptr) {
|
||||
av_free(io->buffer);
|
||||
av_free(io);
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
mpd_ffmpeg_stream_read(void *opaque, uint8_t *buf, int size)
|
||||
{
|
||||
AvioStream *stream = (AvioStream *)opaque;
|
||||
|
||||
return decoder_read(stream->decoder, stream->input,
|
||||
(void *)buf, size);
|
||||
}
|
||||
|
||||
static int64_t
|
||||
mpd_ffmpeg_stream_seek(void *opaque, int64_t pos, int whence)
|
||||
{
|
||||
AvioStream *stream = (AvioStream *)opaque;
|
||||
|
||||
switch (whence) {
|
||||
case SEEK_SET:
|
||||
break;
|
||||
|
||||
case SEEK_CUR:
|
||||
pos += stream->input.GetOffset();
|
||||
break;
|
||||
|
||||
case SEEK_END:
|
||||
if (!stream->input.KnownSize())
|
||||
return -1;
|
||||
|
||||
pos += stream->input.GetSize();
|
||||
break;
|
||||
|
||||
case AVSEEK_SIZE:
|
||||
if (!stream->input.KnownSize())
|
||||
return -1;
|
||||
|
||||
return stream->input.GetSize();
|
||||
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!stream->input.LockSeek(pos, IgnoreError()))
|
||||
return -1;
|
||||
|
||||
return stream->input.GetOffset();
|
||||
}
|
||||
|
||||
bool
|
||||
AvioStream::Open()
|
||||
{
|
||||
constexpr size_t BUFFER_SIZE = 8192;
|
||||
auto buffer = (unsigned char *)av_malloc(BUFFER_SIZE);
|
||||
if (buffer == nullptr)
|
||||
return false;
|
||||
|
||||
io = avio_alloc_context(buffer, BUFFER_SIZE,
|
||||
false, this,
|
||||
mpd_ffmpeg_stream_read, nullptr,
|
||||
input.IsSeekable()
|
||||
? mpd_ffmpeg_stream_seek : nullptr);
|
||||
/* If avio_alloc_context() fails, who frees the buffer? The
|
||||
libavformat API documentation does not specify this, it
|
||||
only says that AVIOContext.buffer must be freed in the end,
|
||||
however no AVIOContext exists in that failure code path. */
|
||||
return io != nullptr;
|
||||
}
|
48
src/decoder/plugins/FfmpegIo.hxx
Normal file
48
src/decoder/plugins/FfmpegIo.hxx
Normal file
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2016 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 MPD_FFMPEG_IO_HXX
|
||||
#define MPD_FFMPEG_IO_HXX
|
||||
|
||||
#include "check.h"
|
||||
|
||||
extern "C" {
|
||||
#include "libavformat/avio.h"
|
||||
}
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
class InputStream;
|
||||
struct Decoder;
|
||||
|
||||
struct AvioStream {
|
||||
Decoder *const decoder;
|
||||
InputStream &input;
|
||||
|
||||
AVIOContext *io;
|
||||
|
||||
AvioStream(Decoder *_decoder, InputStream &_input)
|
||||
:decoder(_decoder), input(_input), io(nullptr) {}
|
||||
|
||||
~AvioStream();
|
||||
|
||||
bool Open();
|
||||
};
|
||||
|
||||
#endif
|
@@ -36,9 +36,9 @@ static const struct tag_table ffmpeg_tags[] = {
|
||||
};
|
||||
|
||||
static void
|
||||
ffmpeg_copy_metadata(TagType type,
|
||||
AVDictionary *m, const char *name,
|
||||
const struct tag_handler *handler, void *handler_ctx)
|
||||
FfmpegScanTag(TagType type,
|
||||
AVDictionary *m, const char *name,
|
||||
const struct tag_handler *handler, void *handler_ctx)
|
||||
{
|
||||
AVDictionaryEntry *mt = nullptr;
|
||||
|
||||
@@ -48,8 +48,8 @@ ffmpeg_copy_metadata(TagType type,
|
||||
}
|
||||
|
||||
static void
|
||||
ffmpeg_scan_pairs(AVDictionary *dict,
|
||||
const struct tag_handler *handler, void *handler_ctx)
|
||||
FfmpegScanPairs(AVDictionary *dict,
|
||||
const struct tag_handler *handler, void *handler_ctx)
|
||||
{
|
||||
AVDictionaryEntry *i = nullptr;
|
||||
|
||||
@@ -59,18 +59,20 @@ ffmpeg_scan_pairs(AVDictionary *dict,
|
||||
}
|
||||
|
||||
void
|
||||
ffmpeg_scan_dictionary(AVDictionary *dict,
|
||||
const struct tag_handler *handler, void *handler_ctx)
|
||||
FfmpegScanDictionary(AVDictionary *dict,
|
||||
const struct tag_handler *handler, void *handler_ctx)
|
||||
{
|
||||
for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i)
|
||||
ffmpeg_copy_metadata(TagType(i), dict, tag_item_names[i],
|
||||
handler, handler_ctx);
|
||||
if (handler->tag != nullptr) {
|
||||
for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i)
|
||||
FfmpegScanTag(TagType(i), dict, tag_item_names[i],
|
||||
handler, handler_ctx);
|
||||
|
||||
for (const struct tag_table *i = ffmpeg_tags;
|
||||
i->name != nullptr; ++i)
|
||||
ffmpeg_copy_metadata(i->type, dict, i->name,
|
||||
handler, handler_ctx);
|
||||
for (const struct tag_table *i = ffmpeg_tags;
|
||||
i->name != nullptr; ++i)
|
||||
FfmpegScanTag(i->type, dict, i->name,
|
||||
handler, handler_ctx);
|
||||
}
|
||||
|
||||
if (handler->pair != nullptr)
|
||||
ffmpeg_scan_pairs(dict, handler, handler_ctx);
|
||||
FfmpegScanPairs(dict, handler, handler_ctx);
|
||||
}
|
||||
|
@@ -32,7 +32,7 @@ extern "C" {
|
||||
struct tag_handler;
|
||||
|
||||
void
|
||||
ffmpeg_scan_dictionary(AVDictionary *dict,
|
||||
const tag_handler *handler, void *handler_ctx);
|
||||
FfmpegScanDictionary(AVDictionary *dict,
|
||||
const tag_handler *handler, void *handler_ctx);
|
||||
|
||||
#endif
|
||||
|
@@ -33,7 +33,7 @@ flac_data::flac_data(Decoder &_decoder,
|
||||
InputStream &_input_stream)
|
||||
:FlacInput(_input_stream, &_decoder),
|
||||
initialized(false), unsupported(false),
|
||||
total_frames(0), first_frame(0), next_frame(0), position(0),
|
||||
position(0),
|
||||
decoder(_decoder), input_stream(_input_stream)
|
||||
{
|
||||
}
|
||||
@@ -59,6 +59,38 @@ flac_sample_format(unsigned bits_per_sample)
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
flac_data::Initialize(unsigned sample_rate, unsigned bits_per_sample,
|
||||
unsigned channels, FLAC__uint64 total_frames)
|
||||
{
|
||||
assert(!initialized);
|
||||
assert(!unsupported);
|
||||
|
||||
::Error error;
|
||||
if (!audio_format_init_checked(audio_format,
|
||||
sample_rate,
|
||||
flac_sample_format(bits_per_sample),
|
||||
channels, error)) {
|
||||
LogError(error);
|
||||
unsupported = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
frame_size = audio_format.GetFrameSize();
|
||||
|
||||
const auto duration = total_frames > 0
|
||||
? SignedSongTime::FromScale<uint64_t>(total_frames,
|
||||
audio_format.sample_rate)
|
||||
: SignedSongTime::Negative();
|
||||
|
||||
decoder_initialized(decoder, audio_format,
|
||||
input_stream.IsSeekable(),
|
||||
duration);
|
||||
|
||||
initialized = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
static void
|
||||
flac_got_stream_info(struct flac_data *data,
|
||||
const FLAC__StreamMetadata_StreamInfo *stream_info)
|
||||
@@ -66,22 +98,10 @@ flac_got_stream_info(struct flac_data *data,
|
||||
if (data->initialized || data->unsupported)
|
||||
return;
|
||||
|
||||
Error error;
|
||||
if (!audio_format_init_checked(data->audio_format,
|
||||
stream_info->sample_rate,
|
||||
flac_sample_format(stream_info->bits_per_sample),
|
||||
stream_info->channels, error)) {
|
||||
LogError(error);
|
||||
data->unsupported = true;
|
||||
return;
|
||||
}
|
||||
|
||||
data->frame_size = data->audio_format.GetFrameSize();
|
||||
|
||||
if (data->total_frames == 0)
|
||||
data->total_frames = stream_info->total_samples;
|
||||
|
||||
data->initialized = true;
|
||||
data->Initialize(stream_info->sample_rate,
|
||||
stream_info->bits_per_sample,
|
||||
stream_info->channels,
|
||||
stream_info->total_samples);
|
||||
}
|
||||
|
||||
void flac_metadata_common_cb(const FLAC__StreamMetadata * block,
|
||||
@@ -125,28 +145,11 @@ flac_got_first_frame(struct flac_data *data, const FLAC__FrameHeader *header)
|
||||
if (data->unsupported)
|
||||
return false;
|
||||
|
||||
Error error;
|
||||
if (!audio_format_init_checked(data->audio_format,
|
||||
header->sample_rate,
|
||||
flac_sample_format(header->bits_per_sample),
|
||||
header->channels, error)) {
|
||||
LogError(error);
|
||||
data->unsupported = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
data->frame_size = data->audio_format.GetFrameSize();
|
||||
|
||||
const auto duration = SongTime::FromScale<uint64_t>(data->total_frames,
|
||||
data->audio_format.sample_rate);
|
||||
|
||||
decoder_initialized(data->decoder, data->audio_format,
|
||||
data->input_stream.IsSeekable(),
|
||||
duration);
|
||||
|
||||
data->initialized = true;
|
||||
|
||||
return true;
|
||||
return data->Initialize(header->sample_rate,
|
||||
header->bits_per_sample,
|
||||
header->channels,
|
||||
/* unknown duration */
|
||||
0);
|
||||
}
|
||||
|
||||
FLAC__StreamDecoderWriteStatus
|
||||
@@ -155,7 +158,6 @@ flac_common_write(struct flac_data *data, const FLAC__Frame * frame,
|
||||
FLAC__uint64 nbytes)
|
||||
{
|
||||
void *buffer;
|
||||
unsigned bit_rate;
|
||||
|
||||
if (!data->initialized && !flac_got_first_frame(data, &frame->header))
|
||||
return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT;
|
||||
@@ -167,16 +169,12 @@ flac_common_write(struct flac_data *data, const FLAC__Frame * frame,
|
||||
data->audio_format.format, buf,
|
||||
0, frame->header.blocksize);
|
||||
|
||||
if (nbytes > 0)
|
||||
bit_rate = nbytes * 8 * frame->header.sample_rate /
|
||||
(1000 * frame->header.blocksize);
|
||||
else
|
||||
bit_rate = 0;
|
||||
unsigned bit_rate = nbytes * 8 * frame->header.sample_rate /
|
||||
(1000 * frame->header.blocksize);
|
||||
|
||||
auto cmd = decoder_data(data->decoder, data->input_stream,
|
||||
buffer, buffer_size,
|
||||
bit_rate);
|
||||
data->next_frame += frame->header.blocksize;
|
||||
switch (cmd) {
|
||||
case DecoderCommand::NONE:
|
||||
case DecoderCommand::START:
|
||||
|
@@ -55,23 +55,9 @@ struct flac_data : public FlacInput {
|
||||
AudioFormat audio_format;
|
||||
|
||||
/**
|
||||
* The total number of frames in this song. The decoder
|
||||
* plugin may initialize this attribute to override the value
|
||||
* provided by libFLAC (e.g. for sub songs from a CUE sheet).
|
||||
* End of last frame's position within the stream. This is
|
||||
* used for bit rate calculations.
|
||||
*/
|
||||
FLAC__uint64 total_frames;
|
||||
|
||||
/**
|
||||
* The number of the first frame in this song. This is only
|
||||
* non-zero if playing sub songs from a CUE sheet.
|
||||
*/
|
||||
FLAC__uint64 first_frame;
|
||||
|
||||
/**
|
||||
* The number of the next frame which is going to be decoded.
|
||||
*/
|
||||
FLAC__uint64 next_frame;
|
||||
|
||||
FLAC__uint64 position;
|
||||
|
||||
Decoder &decoder;
|
||||
@@ -80,6 +66,12 @@ struct flac_data : public FlacInput {
|
||||
Tag tag;
|
||||
|
||||
flac_data(Decoder &decoder, InputStream &input_stream);
|
||||
|
||||
/**
|
||||
* Wrapper for decoder_initialized().
|
||||
*/
|
||||
bool Initialize(unsigned sample_rate, unsigned bits_per_sample,
|
||||
unsigned channels, FLAC__uint64 total_frames);
|
||||
};
|
||||
|
||||
void flac_metadata_common_cb(const FLAC__StreamMetadata * block,
|
||||
|
@@ -132,26 +132,16 @@ flac_decoder_new(void)
|
||||
}
|
||||
|
||||
static bool
|
||||
flac_decoder_initialize(struct flac_data *data, FLAC__StreamDecoder *sd,
|
||||
FLAC__uint64 duration)
|
||||
flac_decoder_initialize(struct flac_data *data, FLAC__StreamDecoder *sd)
|
||||
{
|
||||
data->total_frames = duration;
|
||||
|
||||
if (!FLAC__stream_decoder_process_until_end_of_metadata(sd)) {
|
||||
LogWarning(flac_domain, "problem reading metadata");
|
||||
if (FLAC__stream_decoder_get_state(sd) != FLAC__STREAM_DECODER_END_OF_STREAM)
|
||||
LogWarning(flac_domain, "problem reading metadata");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (data->initialized) {
|
||||
/* done */
|
||||
|
||||
const auto duration2 =
|
||||
SongTime::FromScale<uint64_t>(data->total_frames,
|
||||
data->audio_format.sample_rate);
|
||||
|
||||
decoder_initialized(data->decoder, data->audio_format,
|
||||
data->input_stream.IsSeekable(),
|
||||
duration2);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -167,13 +157,10 @@ flac_decoder_initialize(struct flac_data *data, FLAC__StreamDecoder *sd,
|
||||
}
|
||||
|
||||
static void
|
||||
flac_decoder_loop(struct flac_data *data, FLAC__StreamDecoder *flac_dec,
|
||||
FLAC__uint64 t_start, FLAC__uint64 t_end)
|
||||
flac_decoder_loop(struct flac_data *data, FLAC__StreamDecoder *flac_dec)
|
||||
{
|
||||
Decoder &decoder = data->decoder;
|
||||
|
||||
data->first_frame = t_start;
|
||||
|
||||
while (true) {
|
||||
DecoderCommand cmd;
|
||||
if (!data->tag.IsEmpty()) {
|
||||
@@ -184,24 +171,49 @@ flac_decoder_loop(struct flac_data *data, FLAC__StreamDecoder *flac_dec,
|
||||
cmd = decoder_get_command(decoder);
|
||||
|
||||
if (cmd == DecoderCommand::SEEK) {
|
||||
FLAC__uint64 seek_sample = t_start +
|
||||
FLAC__uint64 seek_sample =
|
||||
decoder_seek_where_frame(decoder);
|
||||
if (seek_sample >= t_start &&
|
||||
(t_end == 0 || seek_sample <= t_end) &&
|
||||
FLAC__stream_decoder_seek_absolute(flac_dec, seek_sample)) {
|
||||
data->next_frame = seek_sample;
|
||||
if (FLAC__stream_decoder_seek_absolute(flac_dec, seek_sample)) {
|
||||
data->position = 0;
|
||||
decoder_command_finished(decoder);
|
||||
} else
|
||||
decoder_seek_error(decoder);
|
||||
} else if (cmd == DecoderCommand::STOP ||
|
||||
FLAC__stream_decoder_get_state(flac_dec) == FLAC__STREAM_DECODER_END_OF_STREAM)
|
||||
} else if (cmd == DecoderCommand::STOP)
|
||||
break;
|
||||
|
||||
if (t_end != 0 && data->next_frame >= t_end)
|
||||
/* end of this sub track */
|
||||
switch (FLAC__stream_decoder_get_state(flac_dec)) {
|
||||
case FLAC__STREAM_DECODER_SEARCH_FOR_METADATA:
|
||||
case FLAC__STREAM_DECODER_READ_METADATA:
|
||||
case FLAC__STREAM_DECODER_SEARCH_FOR_FRAME_SYNC:
|
||||
case FLAC__STREAM_DECODER_READ_FRAME:
|
||||
/* continue decoding */
|
||||
break;
|
||||
|
||||
case FLAC__STREAM_DECODER_END_OF_STREAM:
|
||||
/* regular end of stream */
|
||||
return;
|
||||
|
||||
case FLAC__STREAM_DECODER_SEEK_ERROR:
|
||||
/* try to recover from seek error */
|
||||
if (!FLAC__stream_decoder_flush(flac_dec)) {
|
||||
LogError(flac_domain, "FLAC__stream_decoder_flush() failed");
|
||||
return;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case FLAC__STREAM_DECODER_OGG_ERROR:
|
||||
case FLAC__STREAM_DECODER_ABORTED:
|
||||
case FLAC__STREAM_DECODER_MEMORY_ALLOCATION_ERROR:
|
||||
/* an error, fatal enough for us to abort the
|
||||
decoder */
|
||||
return;
|
||||
|
||||
case FLAC__STREAM_DECODER_UNINITIALIZED:
|
||||
/* we shouldn't see this, ever - bail out */
|
||||
return;
|
||||
}
|
||||
|
||||
if (!FLAC__stream_decoder_process_single(flac_dec) &&
|
||||
decoder_get_command(decoder) == DecoderCommand::NONE) {
|
||||
/* a failure that was not triggered by a
|
||||
@@ -250,6 +262,24 @@ stream_init(FLAC__StreamDecoder *flac_dec, struct flac_data *data, bool is_ogg)
|
||||
: stream_init_flac(flac_dec, data);
|
||||
}
|
||||
|
||||
static bool
|
||||
FlacInitAndDecode(struct flac_data &data, FLAC__StreamDecoder *sd, bool is_ogg)
|
||||
{
|
||||
auto init_status = stream_init(sd, &data, is_ogg);
|
||||
if (init_status != FLAC__STREAM_DECODER_INIT_STATUS_OK) {
|
||||
LogWarning(flac_domain,
|
||||
FLAC__StreamDecoderInitStatusString[init_status]);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool result = flac_decoder_initialize(&data, sd);
|
||||
if (result)
|
||||
flac_decoder_loop(&data, sd);
|
||||
|
||||
FLAC__stream_decoder_finish(sd);
|
||||
return result;
|
||||
}
|
||||
|
||||
static void
|
||||
flac_decode_internal(Decoder &decoder,
|
||||
InputStream &input_stream,
|
||||
@@ -263,24 +293,8 @@ flac_decode_internal(Decoder &decoder,
|
||||
|
||||
struct flac_data data(decoder, input_stream);
|
||||
|
||||
FLAC__StreamDecoderInitStatus status =
|
||||
stream_init(flac_dec, &data, is_ogg);
|
||||
if (status != FLAC__STREAM_DECODER_INIT_STATUS_OK) {
|
||||
FLAC__stream_decoder_delete(flac_dec);
|
||||
LogWarning(flac_domain,
|
||||
FLAC__StreamDecoderInitStatusString[status]);
|
||||
return;
|
||||
}
|
||||
FlacInitAndDecode(data, flac_dec, is_ogg);
|
||||
|
||||
if (!flac_decoder_initialize(&data, flac_dec, 0)) {
|
||||
FLAC__stream_decoder_finish(flac_dec);
|
||||
FLAC__stream_decoder_delete(flac_dec);
|
||||
return;
|
||||
}
|
||||
|
||||
flac_decoder_loop(&data, flac_dec, 0, 0);
|
||||
|
||||
FLAC__stream_decoder_finish(flac_dec);
|
||||
FLAC__stream_decoder_delete(flac_dec);
|
||||
}
|
||||
|
||||
|
@@ -20,6 +20,7 @@
|
||||
#include "config.h"
|
||||
#include "FlacIOHandle.hxx"
|
||||
#include "util/Error.hxx"
|
||||
#include "Log.hxx"
|
||||
#include "Compiler.h"
|
||||
|
||||
#include <errno.h>
|
||||
@@ -87,7 +88,13 @@ FlacIOSeek(FLAC__IOHandle handle, FLAC__int64 _offset, int whence)
|
||||
return -1;
|
||||
}
|
||||
|
||||
return is->LockSeek(offset, IgnoreError()) ? 0 : -1;
|
||||
Error error;
|
||||
if (!is->LockSeek(offset, error)) {
|
||||
LogError(error);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static FLAC__int64
|
||||
|
@@ -156,8 +156,11 @@ gme_file_decode(Decoder &decoder, Path path_fs)
|
||||
return;
|
||||
}
|
||||
|
||||
const SignedSongTime song_len = ti->length > 0
|
||||
? SignedSongTime::FromMS(ti->length)
|
||||
const int length = ti->play_length;
|
||||
gme_free_info(ti);
|
||||
|
||||
const SignedSongTime song_len = length > 0
|
||||
? SignedSongTime::FromMS(length)
|
||||
: SignedSongTime::Negative();
|
||||
|
||||
/* initialize the MPD decoder */
|
||||
@@ -168,7 +171,6 @@ gme_file_decode(Decoder &decoder, Path path_fs)
|
||||
SampleFormat::S16, GME_CHANNELS,
|
||||
error)) {
|
||||
LogError(error);
|
||||
gme_free_info(ti);
|
||||
gme_delete(emu);
|
||||
return;
|
||||
}
|
||||
@@ -179,8 +181,8 @@ gme_file_decode(Decoder &decoder, Path path_fs)
|
||||
if (gme_err != nullptr)
|
||||
LogWarning(gme_domain, gme_err);
|
||||
|
||||
if (ti->length > 0)
|
||||
gme_set_fade(emu, ti->length);
|
||||
if (length > 0)
|
||||
gme_set_fade(emu, length);
|
||||
|
||||
/* play */
|
||||
DecoderCommand cmd;
|
||||
@@ -196,16 +198,17 @@ gme_file_decode(Decoder &decoder, Path path_fs)
|
||||
if (cmd == DecoderCommand::SEEK) {
|
||||
unsigned where = decoder_seek_time(decoder).ToMS();
|
||||
gme_err = gme_seek(emu, where);
|
||||
if (gme_err != nullptr)
|
||||
if (gme_err != nullptr) {
|
||||
LogWarning(gme_domain, gme_err);
|
||||
decoder_command_finished(decoder);
|
||||
decoder_seek_error(decoder);
|
||||
} else
|
||||
decoder_command_finished(decoder);
|
||||
}
|
||||
|
||||
if (gme_track_ended(emu))
|
||||
break;
|
||||
} while (cmd != DecoderCommand::STOP);
|
||||
|
||||
gme_free_info(ti);
|
||||
gme_delete(emu);
|
||||
}
|
||||
|
||||
@@ -236,9 +239,9 @@ gme_scan_file(Path path_fs,
|
||||
|
||||
assert(ti != nullptr);
|
||||
|
||||
if (ti->length > 0)
|
||||
if (ti->play_length > 0)
|
||||
tag_handler_invoke_duration(handler, handler_ctx,
|
||||
SongTime::FromMS(ti->length));
|
||||
SongTime::FromMS(ti->play_length));
|
||||
|
||||
if (ti->song != nullptr) {
|
||||
if (gme_track_count(emu) > 1) {
|
||||
|
@@ -22,10 +22,12 @@
|
||||
#include "../DecoderAPI.hxx"
|
||||
#include "input/InputStream.hxx"
|
||||
#include "CheckAudioFormat.hxx"
|
||||
#include "pcm/Traits.hxx"
|
||||
#include "tag/TagHandler.hxx"
|
||||
#include "util/Error.hxx"
|
||||
#include "util/Domain.hxx"
|
||||
#include "util/Macros.hxx"
|
||||
#include "util/Clamp.hxx"
|
||||
#include "Log.hxx"
|
||||
|
||||
#include <mpc/mpcdec.h>
|
||||
@@ -42,6 +44,9 @@ struct mpc_decoder_data {
|
||||
|
||||
static constexpr Domain mpcdec_domain("mpcdec");
|
||||
|
||||
static constexpr SampleFormat mpcdec_sample_format = SampleFormat::S24_P32;
|
||||
typedef SampleTraits<mpcdec_sample_format> MpcdecSampleTraits;
|
||||
|
||||
static mpc_int32_t
|
||||
mpc_read_cb(mpc_reader *reader, void *ptr, mpc_int32_t size)
|
||||
{
|
||||
@@ -91,18 +96,15 @@ mpc_getsize_cb(mpc_reader *reader)
|
||||
}
|
||||
|
||||
/* this _looks_ performance-critical, don't de-inline -- eric */
|
||||
static inline int32_t
|
||||
static inline MpcdecSampleTraits::value_type
|
||||
mpc_to_mpd_sample(MPC_SAMPLE_FORMAT sample)
|
||||
{
|
||||
/* only doing 16-bit audio for now */
|
||||
int32_t val;
|
||||
MpcdecSampleTraits::value_type val;
|
||||
|
||||
enum {
|
||||
bits = 24,
|
||||
};
|
||||
|
||||
const int clip_min = -1 << (bits - 1);
|
||||
const int clip_max = (1 << (bits - 1)) - 1;
|
||||
constexpr int bits = MpcdecSampleTraits::BITS;
|
||||
constexpr auto clip_min = MpcdecSampleTraits::MIN;
|
||||
constexpr auto clip_max = MpcdecSampleTraits::MAX;
|
||||
|
||||
#ifdef MPC_FIXED_POINT
|
||||
const int shift = bits - MPC_FIXED_POINT_SCALE_SHIFT;
|
||||
@@ -117,16 +119,12 @@ mpc_to_mpd_sample(MPC_SAMPLE_FORMAT sample)
|
||||
val = sample * float_scale;
|
||||
#endif
|
||||
|
||||
if (val < clip_min)
|
||||
val = clip_min;
|
||||
else if (val > clip_max)
|
||||
val = clip_max;
|
||||
|
||||
return val;
|
||||
return Clamp(val, clip_min, clip_max);
|
||||
}
|
||||
|
||||
static void
|
||||
mpc_to_mpd_buffer(int32_t *dest, const MPC_SAMPLE_FORMAT *src,
|
||||
mpc_to_mpd_buffer(MpcdecSampleTraits::pointer_type dest,
|
||||
const MPC_SAMPLE_FORMAT *src,
|
||||
unsigned num_samples)
|
||||
{
|
||||
while (num_samples-- > 0)
|
||||
@@ -162,7 +160,7 @@ mpcdec_decode(Decoder &mpd_decoder, InputStream &is)
|
||||
Error error;
|
||||
AudioFormat audio_format;
|
||||
if (!audio_format_init_checked(audio_format, info.sample_freq,
|
||||
SampleFormat::S24_P32,
|
||||
mpcdec_sample_format,
|
||||
info.channels, error)) {
|
||||
LogError(error);
|
||||
mpc_demux_exit(demux);
|
||||
@@ -214,7 +212,7 @@ mpcdec_decode(Decoder &mpd_decoder, InputStream &is)
|
||||
mpc_uint32_t ret = frame.samples;
|
||||
ret *= info.channels;
|
||||
|
||||
int32_t chunk[ARRAY_SIZE(sample_buffer)];
|
||||
MpcdecSampleTraits::value_type chunk[ARRAY_SIZE(sample_buffer)];
|
||||
mpc_to_mpd_buffer(chunk, sample_buffer, ret);
|
||||
|
||||
long bit_rate = vbr_update_bits * audio_format.sample_rate
|
||||
|
@@ -441,13 +441,15 @@ mpd_opus_scan_stream(InputStream &is,
|
||||
if (!oy.ExpectFirstPage(os))
|
||||
return false;
|
||||
|
||||
/* read at most two more pages */
|
||||
unsigned remaining_pages = 2;
|
||||
/* read at most 64 more pages */
|
||||
unsigned remaining_pages = 64;
|
||||
|
||||
unsigned remaining_packets = 4;
|
||||
|
||||
bool result = false;
|
||||
|
||||
ogg_packet packet;
|
||||
while (true) {
|
||||
while (remaining_packets > 0) {
|
||||
int r = ogg_stream_packetout(&os, &packet);
|
||||
if (r < 0) {
|
||||
result = false;
|
||||
@@ -466,6 +468,8 @@ mpd_opus_scan_stream(InputStream &is,
|
||||
continue;
|
||||
}
|
||||
|
||||
--remaining_packets;
|
||||
|
||||
if (packet.b_o_s) {
|
||||
if (!IsOpusHead(packet))
|
||||
break;
|
||||
|
@@ -85,7 +85,7 @@ public:
|
||||
|
||||
char *ReadString() {
|
||||
uint32_t length;
|
||||
if (!ReadWord(length))
|
||||
if (!ReadWord(length) || length >= 65536)
|
||||
return nullptr;
|
||||
|
||||
const char *src = (const char *)Read(length);
|
||||
|
@@ -22,67 +22,61 @@
|
||||
#include "../DecoderAPI.hxx"
|
||||
#include "tag/TagHandler.hxx"
|
||||
#include "fs/Path.hxx"
|
||||
#include "fs/AllocatedPath.hxx"
|
||||
#include "util/Macros.hxx"
|
||||
#include "util/FormatString.hxx"
|
||||
#include "util/Domain.hxx"
|
||||
#include "util/Error.hxx"
|
||||
#include "system/ByteOrder.hxx"
|
||||
#include "system/FatalError.hxx"
|
||||
#include "Log.hxx"
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <glib.h>
|
||||
|
||||
#ifdef HAVE_SIDPLAYFP
|
||||
#include <sidplayfp/sidplayfp.h>
|
||||
#include <sidplayfp/SidInfo.h>
|
||||
#include <sidplayfp/SidConfig.h>
|
||||
#include <sidplayfp/SidTune.h>
|
||||
#include <sidplayfp/SidTuneInfo.h>
|
||||
#include <sidplayfp/builders/resid.h>
|
||||
#include <sidplayfp/builders/residfp.h>
|
||||
#include <sidplayfp/SidDatabase.h>
|
||||
#else
|
||||
#include <sidplay/sidplay2.h>
|
||||
#include <sidplay/builders/resid.h>
|
||||
#include <sidplay/utils/SidTuneMod.h>
|
||||
#include <sidplay/utils/SidDatabase.h>
|
||||
#endif
|
||||
|
||||
#define SUBTUNE_PREFIX "tune_"
|
||||
|
||||
static constexpr Domain sidplay_domain("sidplay");
|
||||
|
||||
static GPatternSpec *path_with_subtune;
|
||||
static const char *songlength_file;
|
||||
static GKeyFile *songlength_database;
|
||||
static SidDatabase *songlength_database;
|
||||
|
||||
static bool all_files_are_containers;
|
||||
static unsigned default_songlength;
|
||||
|
||||
static bool filter_setting;
|
||||
|
||||
static GKeyFile *
|
||||
sidplay_load_songlength_db(const char *path)
|
||||
static SidDatabase *
|
||||
sidplay_load_songlength_db(const Path path)
|
||||
{
|
||||
GError *error = nullptr;
|
||||
gchar *data;
|
||||
gsize size;
|
||||
|
||||
if (!g_file_get_contents(path, &data, &size, &error)) {
|
||||
SidDatabase *db = new SidDatabase();
|
||||
#ifdef HAVE_SIDPLAYFP
|
||||
bool error = !db->open(path.c_str());
|
||||
#else
|
||||
bool error = db->open(path.c_str()) < 0;
|
||||
#endif
|
||||
if (error) {
|
||||
FormatError(sidplay_domain,
|
||||
"unable to read songlengths file %s: %s",
|
||||
path, error->message);
|
||||
g_error_free(error);
|
||||
path.c_str(), db->error());
|
||||
delete db;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/* replace any ; comment characters with # */
|
||||
for (gsize i = 0; i < size; i++)
|
||||
if (data[i] == ';')
|
||||
data[i] = '#';
|
||||
|
||||
GKeyFile *db = g_key_file_new();
|
||||
bool success = g_key_file_load_from_data(db, data, size,
|
||||
G_KEY_FILE_NONE, &error);
|
||||
g_free(data);
|
||||
if (!success) {
|
||||
FormatError(sidplay_domain,
|
||||
"unable to parse songlengths file %s: %s",
|
||||
path, error->message);
|
||||
g_error_free(error);
|
||||
g_key_file_free(db);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
g_key_file_set_list_separator(db, ' ');
|
||||
return db;
|
||||
}
|
||||
|
||||
@@ -90,18 +84,18 @@ static bool
|
||||
sidplay_init(const config_param ¶m)
|
||||
{
|
||||
/* read the songlengths database file */
|
||||
songlength_file = param.GetBlockValue("songlength_database");
|
||||
if (songlength_file != nullptr)
|
||||
songlength_database = sidplay_load_songlength_db(songlength_file);
|
||||
Error error;
|
||||
const auto database_path = param.GetBlockPath("songlength_database", error);
|
||||
if (!database_path.IsNull())
|
||||
songlength_database = sidplay_load_songlength_db(database_path);
|
||||
else if (error.IsDefined())
|
||||
FatalError(error);
|
||||
|
||||
default_songlength = param.GetBlockValue("default_songlength", 0u);
|
||||
|
||||
all_files_are_containers =
|
||||
param.GetBlockValue("all_files_are_containers", true);
|
||||
|
||||
path_with_subtune=g_pattern_spec_new(
|
||||
"*/" SUBTUNE_PREFIX "???.sid");
|
||||
|
||||
filter_setting = param.GetBlockValue("filter", true);
|
||||
|
||||
return true;
|
||||
@@ -110,98 +104,81 @@ sidplay_init(const config_param ¶m)
|
||||
static void
|
||||
sidplay_finish()
|
||||
{
|
||||
g_pattern_spec_free(path_with_subtune);
|
||||
|
||||
if(songlength_database)
|
||||
g_key_file_free(songlength_database);
|
||||
delete songlength_database;
|
||||
}
|
||||
|
||||
/**
|
||||
* returns the file path stripped of any /tune_xxx.sid subtune
|
||||
* suffix
|
||||
*/
|
||||
static char *
|
||||
get_container_name(Path path_fs)
|
||||
{
|
||||
char *path_container = strdup(path_fs.c_str());
|
||||
struct SidplayContainerPath {
|
||||
AllocatedPath path;
|
||||
unsigned track;
|
||||
};
|
||||
|
||||
if(!g_pattern_match(path_with_subtune,
|
||||
strlen(path_container), path_container, nullptr))
|
||||
return path_container;
|
||||
|
||||
char *ptr=g_strrstr(path_container, "/" SUBTUNE_PREFIX);
|
||||
if(ptr) *ptr='\0';
|
||||
|
||||
return path_container;
|
||||
}
|
||||
|
||||
/**
|
||||
* returns tune number from file.sid/tune_xxx.sid style path or 1 if
|
||||
* no subtune is appended
|
||||
*/
|
||||
gcc_pure
|
||||
static unsigned
|
||||
get_song_num(const char *path_fs)
|
||||
ParseSubtuneName(const char *base)
|
||||
{
|
||||
if(g_pattern_match(path_with_subtune,
|
||||
strlen(path_fs), path_fs, nullptr)) {
|
||||
char *sub=g_strrstr(path_fs, "/" SUBTUNE_PREFIX);
|
||||
if(!sub) return 1;
|
||||
if (memcmp(base, SUBTUNE_PREFIX, sizeof(SUBTUNE_PREFIX) - 1) != 0)
|
||||
return 0;
|
||||
|
||||
sub+=strlen("/" SUBTUNE_PREFIX);
|
||||
int song_num=strtol(sub, nullptr, 10);
|
||||
base += sizeof(SUBTUNE_PREFIX) - 1;
|
||||
|
||||
if (errno == EINVAL)
|
||||
return 1;
|
||||
else
|
||||
return song_num;
|
||||
} else
|
||||
return 1;
|
||||
char *endptr;
|
||||
auto track = strtoul(base, &endptr, 10);
|
||||
if (endptr == base || *endptr != '.')
|
||||
return 0;
|
||||
|
||||
return track;
|
||||
}
|
||||
|
||||
/* get the song length in seconds */
|
||||
/**
|
||||
* returns the file path stripped of any /tune_xxx.* subtune suffix
|
||||
* and the track number (or 1 if no "tune_xxx" suffix is present).
|
||||
*/
|
||||
static SidplayContainerPath
|
||||
ParseContainerPath(Path path_fs)
|
||||
{
|
||||
const Path base = path_fs.GetBase();
|
||||
unsigned track;
|
||||
if (base.IsNull() ||
|
||||
(track = ParseSubtuneName(base.c_str())) < 1)
|
||||
return { AllocatedPath(path_fs), 1 };
|
||||
|
||||
return { path_fs.GetDirectoryName(), track };
|
||||
}
|
||||
|
||||
#ifdef HAVE_SIDPLAYFP
|
||||
|
||||
static SignedSongTime
|
||||
get_song_length(Path path_fs)
|
||||
get_song_length(SidTune &tune)
|
||||
{
|
||||
if (songlength_database == nullptr)
|
||||
return SignedSongTime::Negative();
|
||||
|
||||
char *sid_file = get_container_name(path_fs);
|
||||
SidTuneMod tune(sid_file);
|
||||
free(sid_file);
|
||||
if(!tune) {
|
||||
LogWarning(sidplay_domain,
|
||||
"failed to load file for calculating md5 sum");
|
||||
const auto length = songlength_database->length(tune);
|
||||
if (length < 0)
|
||||
return SignedSongTime::Negative();
|
||||
}
|
||||
char md5sum[SIDTUNE_MD5_LENGTH+1];
|
||||
tune.createMD5(md5sum);
|
||||
|
||||
const unsigned song_num = get_song_num(path_fs.c_str());
|
||||
|
||||
gsize num_items;
|
||||
gchar **values=g_key_file_get_string_list(songlength_database,
|
||||
"Database", md5sum, &num_items, nullptr);
|
||||
if(!values || song_num>num_items) {
|
||||
g_strfreev(values);
|
||||
return SignedSongTime::Negative();
|
||||
}
|
||||
|
||||
int minutes=strtol(values[song_num-1], nullptr, 10);
|
||||
if(errno==EINVAL) minutes=0;
|
||||
|
||||
int seconds;
|
||||
char *ptr=strchr(values[song_num-1], ':');
|
||||
if(ptr) {
|
||||
seconds=strtol(ptr+1, nullptr, 10);
|
||||
if(errno==EINVAL) seconds=0;
|
||||
} else
|
||||
seconds=0;
|
||||
|
||||
g_strfreev(values);
|
||||
|
||||
return SignedSongTime::FromS((minutes * 60) + seconds);
|
||||
return SignedSongTime::FromS(length);
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
static SignedSongTime
|
||||
get_song_length(SidTuneMod &tune)
|
||||
{
|
||||
assert(tune);
|
||||
|
||||
if (songlength_database == nullptr)
|
||||
return SignedSongTime::Negative();
|
||||
|
||||
const auto length = songlength_database->length(tune);
|
||||
if (length < 0)
|
||||
return SignedSongTime::Negative();
|
||||
|
||||
return SignedSongTime::FromS(length);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
static void
|
||||
sidplay_file_decode(Decoder &decoder, Path path_fs)
|
||||
{
|
||||
@@ -209,26 +186,43 @@ sidplay_file_decode(Decoder &decoder, Path path_fs)
|
||||
|
||||
/* load the tune */
|
||||
|
||||
char *path_container=get_container_name(path_fs);
|
||||
SidTune tune(path_container, nullptr, true);
|
||||
free(path_container);
|
||||
if (!tune) {
|
||||
LogWarning(sidplay_domain, "failed to load file");
|
||||
const auto container = ParseContainerPath(path_fs);
|
||||
#ifdef HAVE_SIDPLAYFP
|
||||
SidTune tune(container.path.c_str());
|
||||
#else
|
||||
SidTuneMod tune(container.path.c_str());
|
||||
#endif
|
||||
if (!tune.getStatus()) {
|
||||
#ifdef HAVE_SIDPLAYFP
|
||||
const char *error = tune.statusString();
|
||||
#else
|
||||
const char *error = tune.getInfo().statusString;
|
||||
#endif
|
||||
FormatWarning(sidplay_domain, "failed to load file: %s",
|
||||
error);
|
||||
return;
|
||||
}
|
||||
|
||||
const int song_num = get_song_num(path_fs.c_str());
|
||||
const int song_num = container.track;
|
||||
tune.selectSong(song_num);
|
||||
|
||||
auto duration = get_song_length(path_fs);
|
||||
auto duration = get_song_length(tune);
|
||||
if (duration.IsNegative() && default_songlength > 0)
|
||||
duration = SongTime::FromS(default_songlength);
|
||||
|
||||
/* initialize the player */
|
||||
|
||||
#ifdef HAVE_SIDPLAYFP
|
||||
sidplayfp player;
|
||||
#else
|
||||
sidplay2 player;
|
||||
int iret = player.load(&tune);
|
||||
if (iret != 0) {
|
||||
#endif
|
||||
#ifdef HAVE_SIDPLAYFP
|
||||
bool error = !player.load(&tune);
|
||||
#else
|
||||
bool error = player.load(&tune) < 0;
|
||||
#endif
|
||||
if (error) {
|
||||
FormatWarning(sidplay_domain,
|
||||
"sidplay2.load() failed: %s", player.error());
|
||||
return;
|
||||
@@ -236,53 +230,104 @@ sidplay_file_decode(Decoder &decoder, Path path_fs)
|
||||
|
||||
/* initialize the builder */
|
||||
|
||||
ReSIDBuilder builder("ReSID");
|
||||
if (!builder) {
|
||||
LogWarning(sidplay_domain,
|
||||
"failed to initialize ReSIDBuilder");
|
||||
#ifdef HAVE_SIDPLAYFP
|
||||
ReSIDfpBuilder builder("ReSID");
|
||||
if (!builder.getStatus()) {
|
||||
FormatWarning(sidplay_domain,
|
||||
"failed to initialize ReSIDfpBuilder: %s",
|
||||
builder.error());
|
||||
return;
|
||||
}
|
||||
|
||||
builder.create(player.info().maxsids());
|
||||
if (!builder.getStatus()) {
|
||||
FormatWarning(sidplay_domain,
|
||||
"ReSIDfpBuilder.create() failed: %s",
|
||||
builder.error());
|
||||
return;
|
||||
}
|
||||
#else
|
||||
ReSIDBuilder builder("ReSID");
|
||||
builder.create(player.info().maxsids);
|
||||
if (!builder) {
|
||||
LogWarning(sidplay_domain, "ReSIDBuilder.create() failed");
|
||||
FormatWarning(sidplay_domain, "ReSIDBuilder.create() failed: %s",
|
||||
builder.error());
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
builder.filter(filter_setting);
|
||||
if (!builder) {
|
||||
LogWarning(sidplay_domain, "ReSIDBuilder.filter() failed");
|
||||
#ifdef HAVE_SIDPLAYFP
|
||||
if (!builder.getStatus()) {
|
||||
FormatWarning(sidplay_domain,
|
||||
"ReSIDfpBuilder.filter() failed: %s",
|
||||
builder.error());
|
||||
return;
|
||||
}
|
||||
#else
|
||||
if (!builder) {
|
||||
FormatWarning(sidplay_domain, "ReSIDBuilder.filter() failed: %s",
|
||||
builder.error());
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* configure the player */
|
||||
|
||||
sid2_config_t config = player.config();
|
||||
auto config = player.config();
|
||||
|
||||
#ifndef HAVE_SIDPLAYFP
|
||||
config.clockDefault = SID2_CLOCK_PAL;
|
||||
config.clockForced = true;
|
||||
config.clockSpeed = SID2_CLOCK_CORRECT;
|
||||
#endif
|
||||
config.frequency = 48000;
|
||||
#ifndef HAVE_SIDPLAYFP
|
||||
config.optimisation = SID2_DEFAULT_OPTIMISATION;
|
||||
|
||||
config.precision = 16;
|
||||
config.sidDefault = SID2_MOS6581;
|
||||
#endif
|
||||
config.sidEmulation = &builder;
|
||||
#ifdef HAVE_SIDPLAYFP
|
||||
config.samplingMethod = SidConfig::INTERPOLATE;
|
||||
config.fastSampling = false;
|
||||
#else
|
||||
config.sidModel = SID2_MODEL_CORRECT;
|
||||
config.sidSamples = true;
|
||||
config.sampleFormat = IsLittleEndian()
|
||||
? SID2_LITTLE_SIGNED
|
||||
: SID2_BIG_SIGNED;
|
||||
if (tune.isStereo()) {
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_SIDPLAYFP
|
||||
const bool stereo = tune.getInfo()->sidChips() >= 2;
|
||||
#else
|
||||
const bool stereo = tune.isStereo();
|
||||
#endif
|
||||
|
||||
if (stereo) {
|
||||
#ifdef HAVE_SIDPLAYFP
|
||||
config.playback = SidConfig::STEREO;
|
||||
#else
|
||||
config.playback = sid2_stereo;
|
||||
#endif
|
||||
channels = 2;
|
||||
} else {
|
||||
#ifdef HAVE_SIDPLAYFP
|
||||
config.playback = SidConfig::MONO;
|
||||
#else
|
||||
config.playback = sid2_mono;
|
||||
#endif
|
||||
channels = 1;
|
||||
}
|
||||
|
||||
iret = player.config(config);
|
||||
if (iret != 0) {
|
||||
#ifdef HAVE_SIDPLAYFP
|
||||
error = !player.config(config);
|
||||
#else
|
||||
error = player.config(config) < 0;
|
||||
#endif
|
||||
if (error) {
|
||||
FormatWarning(sidplay_domain,
|
||||
"sidplay2.config() failed: %s", player.error());
|
||||
return;
|
||||
@@ -297,17 +342,21 @@ sidplay_file_decode(Decoder &decoder, Path path_fs)
|
||||
|
||||
/* .. and play */
|
||||
|
||||
#ifdef HAVE_SIDPLAYFP
|
||||
constexpr unsigned timebase = 1;
|
||||
#else
|
||||
const unsigned timebase = player.timebase();
|
||||
#endif
|
||||
const unsigned end = duration.IsNegative()
|
||||
? 0u
|
||||
: duration.ToScale<uint64_t>(timebase);
|
||||
|
||||
DecoderCommand cmd;
|
||||
do {
|
||||
char buffer[4096];
|
||||
short buffer[4096];
|
||||
size_t nbytes;
|
||||
|
||||
nbytes = player.play(buffer, sizeof(buffer));
|
||||
nbytes = player.play(buffer, ARRAY_SIZE(buffer));
|
||||
if (nbytes == 0)
|
||||
break;
|
||||
|
||||
@@ -328,7 +377,7 @@ sidplay_file_decode(Decoder &decoder, Path path_fs)
|
||||
|
||||
/* ignore data until target time is reached */
|
||||
while(data_time<target_time) {
|
||||
nbytes=player.play(buffer, sizeof(buffer));
|
||||
nbytes=player.play(buffer, ARRAY_SIZE(buffer));
|
||||
if(nbytes==0)
|
||||
break;
|
||||
data_time = player.time();
|
||||
@@ -343,41 +392,72 @@ sidplay_file_decode(Decoder &decoder, Path path_fs)
|
||||
} while (cmd != DecoderCommand::STOP);
|
||||
}
|
||||
|
||||
gcc_pure
|
||||
static const char *
|
||||
GetInfoString(const SidTuneInfo &info, unsigned i)
|
||||
{
|
||||
#ifdef HAVE_SIDPLAYFP
|
||||
return info.numberOfInfoStrings() > i
|
||||
? info.infoString(i)
|
||||
: nullptr;
|
||||
#else
|
||||
return info.numberOfInfoStrings > i
|
||||
? info.infoString[i]
|
||||
: nullptr;
|
||||
#endif
|
||||
}
|
||||
|
||||
static bool
|
||||
sidplay_scan_file(Path path_fs,
|
||||
const struct tag_handler *handler, void *handler_ctx)
|
||||
{
|
||||
const int song_num = get_song_num(path_fs.c_str());
|
||||
char *path_container=get_container_name(path_fs);
|
||||
const auto container = ParseContainerPath(path_fs);
|
||||
const unsigned song_num = container.track;
|
||||
|
||||
SidTune tune(path_container, nullptr, true);
|
||||
free(path_container);
|
||||
if (!tune)
|
||||
#ifdef HAVE_SIDPLAYFP
|
||||
SidTune tune(container.path.c_str());
|
||||
#else
|
||||
SidTuneMod tune(container.path.c_str());
|
||||
#endif
|
||||
if (!tune.getStatus())
|
||||
return false;
|
||||
|
||||
tune.selectSong(song_num);
|
||||
|
||||
#ifdef HAVE_SIDPLAYFP
|
||||
const SidTuneInfo &info = *tune.getInfo();
|
||||
const unsigned n_tracks = info.songs();
|
||||
#else
|
||||
const SidTuneInfo &info = tune.getInfo();
|
||||
const unsigned n_tracks = info.songs;
|
||||
#endif
|
||||
|
||||
/* title */
|
||||
const char *title;
|
||||
if (info.numberOfInfoStrings > 0 && info.infoString[0] != nullptr)
|
||||
title=info.infoString[0];
|
||||
else
|
||||
title="";
|
||||
const char *title = GetInfoString(info, 0);
|
||||
if (title == nullptr)
|
||||
title = "";
|
||||
|
||||
if(info.songs>1) {
|
||||
if (n_tracks > 1) {
|
||||
char tag_title[1024];
|
||||
snprintf(tag_title, sizeof(tag_title),
|
||||
"%s (%d/%d)",
|
||||
title, song_num, info.songs);
|
||||
"%s (%d/%u)",
|
||||
title, song_num, n_tracks);
|
||||
tag_handler_invoke_tag(handler, handler_ctx,
|
||||
TAG_TITLE, tag_title);
|
||||
} else
|
||||
tag_handler_invoke_tag(handler, handler_ctx, TAG_TITLE, title);
|
||||
|
||||
/* artist */
|
||||
if (info.numberOfInfoStrings > 1 && info.infoString[1] != nullptr)
|
||||
const char *artist = GetInfoString(info, 1);
|
||||
if (artist != nullptr)
|
||||
tag_handler_invoke_tag(handler, handler_ctx, TAG_ARTIST,
|
||||
info.infoString[1]);
|
||||
artist);
|
||||
|
||||
/* date */
|
||||
const char *date = GetInfoString(info, 2);
|
||||
if (date != nullptr)
|
||||
tag_handler_invoke_tag(handler, handler_ctx, TAG_DATE,
|
||||
date);
|
||||
|
||||
/* track */
|
||||
char track[16];
|
||||
@@ -385,7 +465,7 @@ sidplay_scan_file(Path path_fs,
|
||||
tag_handler_invoke_tag(handler, handler_ctx, TAG_TRACK, track);
|
||||
|
||||
/* time */
|
||||
const auto duration = get_song_length(path_fs);
|
||||
const auto duration = get_song_length(tune);
|
||||
if (!duration.IsNegative())
|
||||
tag_handler_invoke_duration(handler, handler_ctx,
|
||||
SongTime(duration));
|
||||
@@ -397,19 +477,25 @@ static char *
|
||||
sidplay_container_scan(Path path_fs, const unsigned int tnum)
|
||||
{
|
||||
SidTune tune(path_fs.c_str(), nullptr, true);
|
||||
if (!tune)
|
||||
if (!tune.getStatus())
|
||||
return nullptr;
|
||||
|
||||
const SidTuneInfo &info=tune.getInfo();
|
||||
#ifdef HAVE_SIDPLAYFP
|
||||
const SidTuneInfo &info = *tune.getInfo();
|
||||
const unsigned n_tracks = info.songs();
|
||||
#else
|
||||
const SidTuneInfo &info = tune.getInfo();
|
||||
const unsigned n_tracks = info.songs;
|
||||
#endif
|
||||
|
||||
/* Don't treat sids containing a single tune
|
||||
as containers */
|
||||
if(!all_files_are_containers && info.songs<2)
|
||||
if(!all_files_are_containers && n_tracks < 2)
|
||||
return nullptr;
|
||||
|
||||
/* Construct container/tune path names, eg.
|
||||
Delta.sid/tune_001.sid */
|
||||
if(tnum<=info.songs) {
|
||||
if (tnum <= n_tracks) {
|
||||
return FormatNew(SUBTUNE_PREFIX "%03u.sid", tnum);
|
||||
} else
|
||||
return nullptr;
|
||||
|
@@ -157,8 +157,6 @@ flac_encoder_open(Encoder *_encoder, AudioFormat &audio_format, Error &error)
|
||||
struct flac_encoder *encoder = (struct flac_encoder *)_encoder;
|
||||
unsigned bits_per_sample;
|
||||
|
||||
encoder->audio_format = audio_format;
|
||||
|
||||
/* FIXME: flac should support 32bit as well */
|
||||
switch (audio_format.format) {
|
||||
case SampleFormat::S8:
|
||||
@@ -178,6 +176,8 @@ flac_encoder_open(Encoder *_encoder, AudioFormat &audio_format, Error &error)
|
||||
audio_format.format = SampleFormat::S24_P32;
|
||||
}
|
||||
|
||||
encoder->audio_format = audio_format;
|
||||
|
||||
/* allocate the encoder */
|
||||
encoder->fse = FLAC__stream_encoder_new();
|
||||
if (encoder->fse == nullptr) {
|
||||
|
@@ -66,7 +66,7 @@ struct opus_encoder {
|
||||
|
||||
ogg_int64_t granulepos;
|
||||
|
||||
opus_encoder():encoder(opus_encoder_plugin) {}
|
||||
opus_encoder():encoder(opus_encoder_plugin), granulepos(0) {}
|
||||
};
|
||||
|
||||
static constexpr Domain opus_encoder_domain("opus_encoder");
|
||||
|
@@ -26,7 +26,6 @@
|
||||
#include "util/NumberParser.hxx"
|
||||
#include "util/DynamicFifoBuffer.hxx"
|
||||
#include "util/Error.hxx"
|
||||
#include "util/Domain.hxx"
|
||||
|
||||
extern "C"
|
||||
{
|
||||
@@ -60,8 +59,6 @@ struct ShineEncoder {
|
||||
bool WriteChunk(bool flush);
|
||||
};
|
||||
|
||||
static constexpr Domain shine_encoder_domain("shine_encoder");
|
||||
|
||||
inline bool
|
||||
ShineEncoder::Configure(const config_param ¶m,
|
||||
gcc_unused Error &error)
|
||||
|
@@ -27,6 +27,8 @@
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
|
||||
static constexpr uint16_t WAVE_FORMAT_PCM = 1;
|
||||
|
||||
struct WaveEncoder {
|
||||
Encoder encoder;
|
||||
unsigned bits;
|
||||
@@ -64,15 +66,15 @@ fill_wave_header(struct wave_header *header, int channels, int bits,
|
||||
header->id_fmt = ToLE32(0x20746d66);
|
||||
header->id_data = ToLE32(0x61746164);
|
||||
|
||||
/* wave format */
|
||||
header->format = ToLE16(1); // PCM_FORMAT
|
||||
/* wave format */
|
||||
header->format = ToLE16(WAVE_FORMAT_PCM);
|
||||
header->channels = ToLE16(channels);
|
||||
header->bits = ToLE16(bits);
|
||||
header->freq = ToLE32(freq);
|
||||
header->blocksize = ToLE16(block_size);
|
||||
header->byterate = ToLE32(freq * block_size);
|
||||
|
||||
/* chunk sizes (fake data length) */
|
||||
/* chunk sizes (fake data length) */
|
||||
header->fmt_size = ToLE32(16);
|
||||
header->data_size = ToLE32(data_size);
|
||||
header->riff_size = ToLE32(4 + (8 + 16) + (8 + data_size));
|
||||
|
@@ -118,9 +118,15 @@ BufferedSocket::OnSocketReady(unsigned flags)
|
||||
if (flags & READ) {
|
||||
assert(!input.IsFull());
|
||||
|
||||
if (!ReadToBuffer() || !ResumeInput())
|
||||
if (!ReadToBuffer())
|
||||
return false;
|
||||
|
||||
if (!ResumeInput())
|
||||
/* we must return "true" here or
|
||||
SocketMonitor::Dispatch() will call
|
||||
Cancel() on a freed object */
|
||||
return true;
|
||||
|
||||
if (!input.IsFull())
|
||||
ScheduleRead();
|
||||
}
|
||||
|
@@ -134,8 +134,6 @@ ReplayGainFilter::Update()
|
||||
volume = pcm_float_to_volume(scale);
|
||||
}
|
||||
|
||||
pv.SetVolume(volume);
|
||||
|
||||
if (mixer != nullptr) {
|
||||
/* update the hardware mixer volume */
|
||||
|
||||
@@ -146,7 +144,8 @@ ReplayGainFilter::Update()
|
||||
Error error;
|
||||
if (!mixer_set_volume(mixer, _volume, error))
|
||||
LogError(error, "Failed to update hardware mixer");
|
||||
}
|
||||
} else
|
||||
pv.SetVolume(volume);
|
||||
}
|
||||
|
||||
static Filter *
|
||||
@@ -174,7 +173,9 @@ ReplayGainFilter::Close()
|
||||
ConstBuffer<void>
|
||||
ReplayGainFilter::FilterPCM(ConstBuffer<void> src, gcc_unused Error &error)
|
||||
{
|
||||
return pv.Apply(src);
|
||||
return mixer != nullptr
|
||||
? src
|
||||
: pv.Apply(src);
|
||||
}
|
||||
|
||||
const struct filter_plugin replay_gain_filter_plugin = {
|
||||
|
@@ -47,9 +47,11 @@
|
||||
#include "filter/FilterInternal.hxx"
|
||||
#include "filter/FilterRegistry.hxx"
|
||||
#include "pcm/PcmBuffer.hxx"
|
||||
#include "pcm/Silence.hxx"
|
||||
#include "util/StringUtil.hxx"
|
||||
#include "util/Error.hxx"
|
||||
#include "util/ConstBuffer.hxx"
|
||||
#include "util/WritableBuffer.hxx"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
@@ -266,9 +268,8 @@ RouteFilter::FilterPCM(ConstBuffer<void> src, gcc_unused Error &error)
|
||||
(unsigned)sources[c] >= input_format.channels) {
|
||||
// No source for this destination output,
|
||||
// give it zeroes as input
|
||||
memset(chan_destination,
|
||||
0x00,
|
||||
bytes_per_frame_per_channel);
|
||||
PcmSilence({chan_destination, bytes_per_frame_per_channel},
|
||||
input_format.format);
|
||||
} else {
|
||||
// Get the data from channel sources[c]
|
||||
// and copy it to the output
|
||||
|
@@ -26,7 +26,6 @@
|
||||
#include "AudioFormat.hxx"
|
||||
#include "util/ConstBuffer.hxx"
|
||||
#include "util/Error.hxx"
|
||||
#include "util/Domain.hxx"
|
||||
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
@@ -50,8 +49,6 @@ public:
|
||||
Error &error) override;
|
||||
};
|
||||
|
||||
static constexpr Domain volume_domain("pcm_volume");
|
||||
|
||||
static Filter *
|
||||
volume_filter_init(gcc_unused const config_param ¶m,
|
||||
gcc_unused Error &error)
|
||||
|
@@ -252,7 +252,7 @@ public:
|
||||
void ChopSeparators();
|
||||
|
||||
gcc_pure
|
||||
bool IsAbsolute() {
|
||||
bool IsAbsolute() const {
|
||||
return PathTraitsFS::IsAbsolute(c_str());
|
||||
}
|
||||
};
|
||||
|
@@ -29,6 +29,8 @@
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
|
||||
class AllocatedPath;
|
||||
|
||||
/**
|
||||
* A path name in the native file system character set.
|
||||
*
|
||||
@@ -128,6 +130,22 @@ public:
|
||||
gcc_pure
|
||||
std::string ToUTF8() const;
|
||||
|
||||
/**
|
||||
* Determine the "base" file name.
|
||||
* The return value points inside this object.
|
||||
*/
|
||||
gcc_pure
|
||||
Path GetBase() const {
|
||||
return FromFS(PathTraitsFS::GetBase(value));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets directory name of this path.
|
||||
* Returns a "nulled" instance on error.
|
||||
*/
|
||||
gcc_pure
|
||||
AllocatedPath GetDirectoryName() const;
|
||||
|
||||
/**
|
||||
* Determine the relative part of the given path to this
|
||||
* object, not including the directory separator. Returns an
|
||||
@@ -140,7 +158,7 @@ public:
|
||||
}
|
||||
|
||||
gcc_pure
|
||||
bool IsAbsolute() {
|
||||
bool IsAbsolute() const {
|
||||
return PathTraitsFS::IsAbsolute(c_str());
|
||||
}
|
||||
};
|
||||
|
28
src/fs/Path2.cxx
Normal file
28
src/fs/Path2.cxx
Normal file
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2014 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.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "Path.hxx"
|
||||
#include "AllocatedPath.hxx"
|
||||
|
||||
AllocatedPath
|
||||
Path::GetDirectoryName() const
|
||||
{
|
||||
return AllocatedPath::FromFS(PathTraitsFS::GetParent(c_str()));
|
||||
}
|
@@ -453,6 +453,8 @@ CurlInputStream::RequestDone(CURLcode result, long status)
|
||||
SeekDone();
|
||||
else if (!IsReady())
|
||||
SetReady();
|
||||
else
|
||||
cond.broadcast();
|
||||
}
|
||||
|
||||
static void
|
||||
|
@@ -132,6 +132,7 @@ SmbclientInputStream::Read(void *ptr, size_t read_size, Error &error)
|
||||
nbytes = 0;
|
||||
}
|
||||
|
||||
offset += nbytes;
|
||||
return nbytes;
|
||||
}
|
||||
|
||||
|
72
src/lib/ffmpeg/Buffer.hxx
Normal file
72
src/lib/ffmpeg/Buffer.hxx
Normal file
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2014 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 MPD_FFMPEG_BUFFER_HXX
|
||||
#define MPD_FFMPEG_BUFFER_HXX
|
||||
|
||||
extern "C" {
|
||||
#include <libavutil/mem.h>
|
||||
|
||||
#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(52, 18, 0)
|
||||
#define HAVE_AV_FAST_MALLOC
|
||||
#else
|
||||
#include <libavcodec/avcodec.h>
|
||||
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(52, 25, 0)
|
||||
#define HAVE_AV_FAST_MALLOC
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
/* suppress the ffmpeg compatibility macro */
|
||||
#ifdef SampleFormat
|
||||
#undef SampleFormat
|
||||
#endif
|
||||
|
||||
class FfmpegBuffer {
|
||||
void *data;
|
||||
unsigned size;
|
||||
|
||||
public:
|
||||
FfmpegBuffer():data(nullptr), size(0) {}
|
||||
|
||||
~FfmpegBuffer() {
|
||||
av_free(data);
|
||||
}
|
||||
|
||||
void *Get(size_t min_size) {
|
||||
#ifdef HAVE_AV_FAST_MALLOC
|
||||
av_fast_malloc(&data, &size, min_size);
|
||||
#else
|
||||
void *new_data = av_fast_realloc(data, &size, min_size);
|
||||
if (new_data == nullptr)
|
||||
return AVERROR(ENOMEM);
|
||||
data = new_data;
|
||||
#endif
|
||||
return data;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
T *GetT(size_t n) {
|
||||
return (T *)Get(n * sizeof(T));
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
38
src/lib/ffmpeg/Init.cxx
Normal file
38
src/lib/ffmpeg/Init.cxx
Normal file
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2014 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.
|
||||
*/
|
||||
|
||||
/* necessary because libavutil/common.h uses UINT64_C */
|
||||
#define __STDC_CONSTANT_MACROS
|
||||
|
||||
#include "config.h"
|
||||
#include "Init.hxx"
|
||||
#include "LogCallback.hxx"
|
||||
|
||||
extern "C" {
|
||||
#include <libavformat/avformat.h>
|
||||
}
|
||||
|
||||
void
|
||||
FfmpegInit()
|
||||
{
|
||||
av_log_set_callback(FfmpegLogCallback);
|
||||
|
||||
av_register_all();
|
||||
}
|
||||
|
26
src/lib/ffmpeg/Init.hxx
Normal file
26
src/lib/ffmpeg/Init.hxx
Normal file
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2014 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 MPD_FFMPEG_INIT_HXX
|
||||
#define MPD_FFMPEG_INIT_HXX
|
||||
|
||||
void
|
||||
FfmpegInit();
|
||||
|
||||
#endif
|
66
src/lib/ffmpeg/LogCallback.cxx
Normal file
66
src/lib/ffmpeg/LogCallback.cxx
Normal file
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2014 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.
|
||||
*/
|
||||
|
||||
/* necessary because libavutil/common.h uses UINT64_C */
|
||||
#define __STDC_CONSTANT_MACROS
|
||||
|
||||
#include "config.h"
|
||||
#include "LogCallback.hxx"
|
||||
#include "Domain.hxx"
|
||||
#include "LogV.hxx"
|
||||
#include "util/Domain.hxx"
|
||||
|
||||
extern "C" {
|
||||
#include <libavutil/log.h>
|
||||
}
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
gcc_const
|
||||
static LogLevel
|
||||
FfmpegImportLogLevel(int level)
|
||||
{
|
||||
if (level <= AV_LOG_FATAL)
|
||||
return LogLevel::ERROR;
|
||||
|
||||
if (level <= AV_LOG_WARNING)
|
||||
return LogLevel::WARNING;
|
||||
|
||||
if (level <= AV_LOG_INFO)
|
||||
return LogLevel::INFO;
|
||||
|
||||
return LogLevel::DEBUG;
|
||||
}
|
||||
|
||||
void
|
||||
FfmpegLogCallback(gcc_unused void *ptr, int level, const char *fmt, va_list vl)
|
||||
{
|
||||
const AVClass * cls = nullptr;
|
||||
|
||||
if (ptr != nullptr)
|
||||
cls = *(const AVClass *const*)ptr;
|
||||
|
||||
if (cls != nullptr) {
|
||||
char domain[64];
|
||||
snprintf(domain, sizeof(domain), "%s/%s",
|
||||
ffmpeg_domain.GetName(), cls->item_name(ptr));
|
||||
const Domain d(domain);
|
||||
LogFormatV(d, FfmpegImportLogLevel(level), fmt, vl);
|
||||
}
|
||||
}
|
30
src/lib/ffmpeg/LogCallback.hxx
Normal file
30
src/lib/ffmpeg/LogCallback.hxx
Normal file
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2014 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 MPD_FFMPEG_LOG_CALLBACK_HXX
|
||||
#define MPD_FFMPEG_LOG_CALLBACK_HXX
|
||||
|
||||
#include "check.h"
|
||||
|
||||
#include <stdarg.h>
|
||||
|
||||
void
|
||||
FfmpegLogCallback(void *ptr, int level, const char *fmt, va_list vl);
|
||||
|
||||
#endif
|
45
src/lib/ffmpeg/LogError.cxx
Normal file
45
src/lib/ffmpeg/LogError.cxx
Normal file
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2014 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.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "LogError.hxx"
|
||||
#include "Domain.hxx"
|
||||
#include "Log.hxx"
|
||||
|
||||
#include <cstdint> /* needed due to libavutil bug */
|
||||
|
||||
extern "C" {
|
||||
#include <libavutil/error.h>
|
||||
}
|
||||
|
||||
void
|
||||
LogFfmpegError(int errnum)
|
||||
{
|
||||
char msg[256];
|
||||
av_strerror(errnum, msg, sizeof(msg));
|
||||
LogError(ffmpeg_domain, msg);
|
||||
}
|
||||
|
||||
void
|
||||
LogFfmpegError(int errnum, const char *prefix)
|
||||
{
|
||||
char msg[256];
|
||||
av_strerror(errnum, msg, sizeof(msg));
|
||||
FormatError(ffmpeg_domain, "%s: %s", prefix, msg);
|
||||
}
|
29
src/lib/ffmpeg/LogError.hxx
Normal file
29
src/lib/ffmpeg/LogError.hxx
Normal file
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2014 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 MPD_FFMPEG_LOG_ERROR_HXX
|
||||
#define MPD_FFMPEG_LOG_ERROR_HXX
|
||||
|
||||
void
|
||||
LogFfmpegError(int errnum);
|
||||
|
||||
void
|
||||
LogFfmpegError(int errnum, const char *prefix);
|
||||
|
||||
#endif
|
104
src/lib/ffmpeg/Time.hxx
Normal file
104
src/lib/ffmpeg/Time.hxx
Normal file
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2014 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 MPD_FFMPEG_TIME_HXX
|
||||
#define MPD_FFMPEG_TIME_HXX
|
||||
|
||||
#include "Chrono.hxx"
|
||||
#include "Compiler.h"
|
||||
|
||||
extern "C" {
|
||||
#include <libavutil/avutil.h>
|
||||
#include <libavutil/mathematics.h>
|
||||
}
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdint.h>
|
||||
|
||||
/* suppress the ffmpeg compatibility macro */
|
||||
#ifdef SampleFormat
|
||||
#undef SampleFormat
|
||||
#endif
|
||||
|
||||
gcc_const
|
||||
static inline double
|
||||
FfmpegTimeToDouble(int64_t t, const AVRational time_base)
|
||||
{
|
||||
assert(t != (int64_t)AV_NOPTS_VALUE);
|
||||
|
||||
return (double)av_rescale_q(t, time_base, (AVRational){1, 1024})
|
||||
/ (double)1024;
|
||||
}
|
||||
|
||||
template<typename Ratio>
|
||||
static inline constexpr AVRational
|
||||
RatioToAVRational()
|
||||
{
|
||||
return { Ratio::num, Ratio::den };
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a FFmpeg time stamp to a #SongTime.
|
||||
*/
|
||||
gcc_const
|
||||
static inline SongTime
|
||||
FromFfmpegTime(int64_t t, const AVRational time_base)
|
||||
{
|
||||
assert(t != (int64_t)AV_NOPTS_VALUE);
|
||||
|
||||
return SongTime::FromMS(av_rescale_q(t, time_base,
|
||||
(AVRational){1, 1000}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a FFmpeg time stamp to a #SignedSongTime.
|
||||
*/
|
||||
gcc_const
|
||||
static inline SignedSongTime
|
||||
FromFfmpegTimeChecked(int64_t t, const AVRational time_base)
|
||||
{
|
||||
return t != (int64_t)AV_NOPTS_VALUE
|
||||
? SignedSongTime(FromFfmpegTime(t, time_base))
|
||||
: SignedSongTime::Negative();
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a #SongTime to a FFmpeg time stamp with the given base.
|
||||
*/
|
||||
gcc_const
|
||||
static inline int64_t
|
||||
ToFfmpegTime(SongTime t, const AVRational time_base)
|
||||
{
|
||||
return av_rescale_q(t.count(),
|
||||
RatioToAVRational<SongTime::period>(),
|
||||
time_base);
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace #AV_NOPTS_VALUE with the given fallback.
|
||||
*/
|
||||
static constexpr int64_t
|
||||
FfmpegTimestampFallback(int64_t t, int64_t fallback)
|
||||
{
|
||||
return gcc_likely(t != int64_t(AV_NOPTS_VALUE))
|
||||
? t
|
||||
: fallback;
|
||||
}
|
||||
|
||||
#endif
|
@@ -59,6 +59,18 @@ NfsManager::Compare::operator()(const ManagedConnection &a,
|
||||
return result < 0;
|
||||
}
|
||||
|
||||
inline bool
|
||||
NfsManager::Compare::operator()(const ManagedConnection &a,
|
||||
const ManagedConnection &b) const
|
||||
{
|
||||
int result = strcmp(a.GetServer(), b.GetServer());
|
||||
if (result != 0)
|
||||
return result < 0;
|
||||
|
||||
result = strcmp(a.GetExportName(), b.GetExportName());
|
||||
return result < 0;
|
||||
}
|
||||
|
||||
NfsManager::~NfsManager()
|
||||
{
|
||||
assert(GetEventLoop().IsInside());
|
||||
|
@@ -64,6 +64,10 @@ class NfsManager final : IdleMonitor {
|
||||
gcc_pure
|
||||
bool operator()(const ManagedConnection &a,
|
||||
const LookupKey b) const;
|
||||
|
||||
gcc_pure
|
||||
bool operator()(const ManagedConnection &a,
|
||||
const ManagedConnection &b) const;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@@ -25,13 +25,10 @@
|
||||
#include "output/Internal.hxx"
|
||||
#include "pcm/Volume.hxx"
|
||||
#include "util/Error.hxx"
|
||||
#include "util/Domain.hxx"
|
||||
#include "Log.hxx"
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
static constexpr Domain mixer_domain("mixer");
|
||||
|
||||
static int
|
||||
output_mixer_get_volume(const AudioOutput &ao)
|
||||
{
|
||||
|
@@ -218,7 +218,7 @@ PulseMixer::SetVolume(unsigned new_volume, Error &error)
|
||||
|
||||
struct pa_cvolume cvolume;
|
||||
pa_cvolume_set(&cvolume, volume.channels,
|
||||
(pa_volume_t)new_volume * PA_VOLUME_NORM / 100 + 0.5);
|
||||
(new_volume * PA_VOLUME_NORM + 50) / 100);
|
||||
bool success = pulse_output_set_volume(output, &cvolume, error);
|
||||
if (success)
|
||||
volume = cvolume;
|
||||
|
@@ -28,7 +28,7 @@ struct notify {
|
||||
Cond cond;
|
||||
bool pending;
|
||||
|
||||
#if !defined(WIN32) && !defined(__NetBSD__) && !defined(__BIONIC__)
|
||||
#ifdef __GLIBC__
|
||||
constexpr
|
||||
#endif
|
||||
notify():pending(false) {}
|
||||
|
@@ -30,6 +30,7 @@
|
||||
#include "Internal.hxx"
|
||||
#include "PlayerControl.hxx"
|
||||
#include "mixer/MixerControl.hxx"
|
||||
#include "mixer/Volume.hxx"
|
||||
#include "Idle.hxx"
|
||||
|
||||
extern unsigned audio_output_state_version;
|
||||
@@ -47,6 +48,11 @@ audio_output_enable_index(MultipleOutputs &outputs, unsigned idx)
|
||||
ao.enabled = true;
|
||||
idle_add(IDLE_OUTPUT);
|
||||
|
||||
if (ao.mixer != nullptr) {
|
||||
InvalidateHardwareVolume();
|
||||
idle_add(IDLE_MIXER);
|
||||
}
|
||||
|
||||
ao.player_control->UpdateAudio();
|
||||
|
||||
++audio_output_state_version;
|
||||
@@ -70,6 +76,7 @@ audio_output_disable_index(MultipleOutputs &outputs, unsigned idx)
|
||||
Mixer *mixer = ao.mixer;
|
||||
if (mixer != nullptr) {
|
||||
mixer_close(mixer);
|
||||
InvalidateHardwareVolume();
|
||||
idle_add(IDLE_MIXER);
|
||||
}
|
||||
|
||||
@@ -94,6 +101,7 @@ audio_output_toggle_index(MultipleOutputs &outputs, unsigned idx)
|
||||
Mixer *mixer = ao.mixer;
|
||||
if (mixer != nullptr) {
|
||||
mixer_close(mixer);
|
||||
InvalidateHardwareVolume();
|
||||
idle_add(IDLE_MIXER);
|
||||
}
|
||||
}
|
||||
|
@@ -184,7 +184,8 @@ AudioOutput::LockUpdate(const AudioFormat audio_format,
|
||||
const ScopeLock protect(mutex);
|
||||
|
||||
if (enabled && really_enabled) {
|
||||
if (fail_timer.Check(REOPEN_AFTER * 1000)) {
|
||||
if (!fail_timer.IsDefined() ||
|
||||
fail_timer.Check(REOPEN_AFTER * 1000)) {
|
||||
return Open(audio_format, mp);
|
||||
}
|
||||
} else if (IsOpen())
|
||||
|
@@ -22,7 +22,6 @@
|
||||
#include "../OutputAPI.hxx"
|
||||
#include "config/ConfigError.hxx"
|
||||
#include "util/Error.hxx"
|
||||
#include "util/Domain.hxx"
|
||||
|
||||
#include <string>
|
||||
|
||||
@@ -44,8 +43,6 @@ struct PipeOutput {
|
||||
bool Configure(const config_param ¶m, Error &error);
|
||||
};
|
||||
|
||||
static constexpr Domain pipe_output_domain("pipe_output");
|
||||
|
||||
inline bool
|
||||
PipeOutput::Configure(const config_param ¶m, Error &error)
|
||||
{
|
||||
|
@@ -164,7 +164,9 @@ ShoutOutput::Configure(const config_param ¶m, Error &error)
|
||||
}
|
||||
}
|
||||
|
||||
const char *encoding = param.GetBlockValue("encoding", "ogg");
|
||||
const char *encoding = param.GetBlockValue("encoder", nullptr);
|
||||
if (encoding == nullptr)
|
||||
encoding = param.GetBlockValue("encoding", "vorbis");
|
||||
const auto encoder_plugin = shout_encoder_plugin_get(encoding);
|
||||
if (encoder_plugin == nullptr) {
|
||||
error.Format(config_domain,
|
||||
@@ -247,7 +249,6 @@ ShoutOutput::Configure(const config_param ¶m, Error &error)
|
||||
|
||||
{
|
||||
char temp[11];
|
||||
memset(temp, 0, sizeof(temp));
|
||||
|
||||
snprintf(temp, sizeof(temp), "%u", audio_format.channels);
|
||||
shout_set_audio_info(shout_conn, SHOUT_AI_CHANNELS, temp);
|
||||
|
47
src/pcm/Interleave.cxx
Normal file
47
src/pcm/Interleave.cxx
Normal file
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2015 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.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "Interleave.hxx"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
static void
|
||||
GenericPcmInterleave(uint8_t *dest, ConstBuffer<const uint8_t *> src,
|
||||
size_t n_frames, size_t sample_size)
|
||||
{
|
||||
for (size_t frame = 0; frame < n_frames; ++frame) {
|
||||
for (size_t channel = 0; channel < src.size; ++channel) {
|
||||
memcpy(dest, src[channel] + frame * sample_size,
|
||||
sample_size);
|
||||
dest += sample_size;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
PcmInterleave(void *dest, ConstBuffer<const void *> src,
|
||||
size_t n_frames, size_t sample_size)
|
||||
{
|
||||
GenericPcmInterleave((uint8_t *)dest,
|
||||
ConstBuffer<const uint8_t *>((const uint8_t *const*)src.data,
|
||||
src.size),
|
||||
n_frames, sample_size);
|
||||
}
|
33
src/pcm/Interleave.hxx
Normal file
33
src/pcm/Interleave.hxx
Normal file
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2015 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 MPD_PCM_INTERLEAVE_HXX
|
||||
#define MPD_PCM_INTERLEAVE_HXX
|
||||
|
||||
#include "check.h"
|
||||
#include "util/ConstBuffer.hxx"
|
||||
|
||||
/**
|
||||
* Interleave planar PCM samples from #src to #dest.
|
||||
*/
|
||||
void
|
||||
PcmInterleave(void *dest, ConstBuffer<const void *> src,
|
||||
size_t n_frames, size_t sample_size);
|
||||
|
||||
#endif
|
35
src/pcm/Silence.cxx
Normal file
35
src/pcm/Silence.cxx
Normal file
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2016 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.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "Silence.hxx"
|
||||
#include "AudioFormat.hxx"
|
||||
#include "util/WritableBuffer.hxx"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
void
|
||||
PcmSilence(WritableBuffer<void> dest, SampleFormat format)
|
||||
{
|
||||
uint8_t pattern = 0;
|
||||
if (format == SampleFormat::DSD)
|
||||
pattern = 0x69;
|
||||
|
||||
memset(dest.data, pattern, dest.size);
|
||||
}
|
36
src/pcm/Silence.hxx
Normal file
36
src/pcm/Silence.hxx
Normal file
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2016 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 MPD_PCM_SILENCE_HXX
|
||||
#define MPD_PCM_SILENCE_HXX
|
||||
|
||||
#include "check.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
template<typename T> struct WritableBuffer;
|
||||
enum class SampleFormat : uint8_t;
|
||||
|
||||
/**
|
||||
* Fill the given buffer with the format-specific silence pattern.
|
||||
*/
|
||||
void
|
||||
PcmSilence(WritableBuffer<void> dest, SampleFormat format);
|
||||
|
||||
#endif
|
@@ -19,10 +19,12 @@
|
||||
|
||||
#include "config.h"
|
||||
#include "Volume.hxx"
|
||||
#include "Silence.hxx"
|
||||
#include "Domain.hxx"
|
||||
#include "PcmUtils.hxx"
|
||||
#include "Traits.hxx"
|
||||
#include "util/ConstBuffer.hxx"
|
||||
#include "util/WritableBuffer.hxx"
|
||||
#include "util/Error.hxx"
|
||||
|
||||
#include "PcmDither.cxx" // including the .cxx file to get inlined templates
|
||||
@@ -134,9 +136,7 @@ PcmVolume::Apply(ConstBuffer<void> src)
|
||||
|
||||
if (volume == 0) {
|
||||
/* optimized special case: 0% volume = memset(0) */
|
||||
/* TODO: is this valid for all sample formats? What
|
||||
about floating point? */
|
||||
memset(data, 0, src.size);
|
||||
PcmSilence({data, src.size}, format);
|
||||
return { data, src.size };
|
||||
}
|
||||
|
||||
|
@@ -25,14 +25,11 @@
|
||||
#include "input/InputStream.hxx"
|
||||
#include "tag/TagBuilder.hxx"
|
||||
#include "util/Error.hxx"
|
||||
#include "util/Domain.hxx"
|
||||
#include "lib/expat/ExpatParser.hxx"
|
||||
#include "Log.hxx"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
static constexpr Domain xspf_domain("xspf");
|
||||
|
||||
/**
|
||||
* This is the state object for the GLib XML parser.
|
||||
*/
|
||||
|
@@ -92,7 +92,7 @@ check_range(Client &client, unsigned *value_r1, unsigned *value_r2,
|
||||
return false;
|
||||
}
|
||||
|
||||
if (unsigned(value) > std::numeric_limits<unsigned>::max()) {
|
||||
if (value > std::numeric_limits<int>::max()) {
|
||||
command_error(client, ACK_ERROR_ARG,
|
||||
"Number too large: %s", s);
|
||||
return false;
|
||||
@@ -117,7 +117,7 @@ check_range(Client &client, unsigned *value_r1, unsigned *value_r2,
|
||||
return false;
|
||||
}
|
||||
|
||||
if (unsigned(value) > std::numeric_limits<unsigned>::max()) {
|
||||
if (value > std::numeric_limits<int>::max()) {
|
||||
command_error(client, ACK_ERROR_ARG,
|
||||
"Number too large: %s", s);
|
||||
return false;
|
||||
|
@@ -37,7 +37,7 @@ playlist::TagModified(DetachedSong &&song)
|
||||
|
||||
DetachedSong ¤t_song = queue.GetOrder(current);
|
||||
if (song.IsSame(current_song))
|
||||
current_song.MoveTagFrom(std::move(song));
|
||||
current_song.MoveTagItemsFrom(std::move(song));
|
||||
|
||||
queue.ModifyAtOrder(current);
|
||||
queue.IncrementVersion();
|
||||
|
@@ -177,6 +177,8 @@ private:
|
||||
mutex.unlock();
|
||||
DeferredMonitor::Schedule();
|
||||
mutex.lock();
|
||||
if (state == State::INITIAL)
|
||||
cond.wait(mutex);
|
||||
break;
|
||||
|
||||
case State::CONNECTING:
|
||||
@@ -188,8 +190,6 @@ private:
|
||||
error.Set(last_error);
|
||||
return false;
|
||||
}
|
||||
|
||||
cond.wait(mutex);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -63,7 +63,7 @@ protected:
|
||||
}
|
||||
|
||||
public:
|
||||
bool IsDefined() const {
|
||||
constexpr bool IsDefined() const {
|
||||
return last != 0;
|
||||
}
|
||||
|
||||
|
@@ -84,14 +84,14 @@ aiff_seek_id3(FILE *file)
|
||||
underflow when casting to off_t */
|
||||
return 0;
|
||||
|
||||
if (size % 2 != 0)
|
||||
/* pad byte */
|
||||
++size;
|
||||
|
||||
if (memcmp(chunk.id, "ID3 ", 4) == 0)
|
||||
/* found it! */
|
||||
return size;
|
||||
|
||||
if (size % 2 != 0)
|
||||
/* pad byte */
|
||||
++size;
|
||||
|
||||
if (fseek(file, size, SEEK_CUR) != 0)
|
||||
return 0;
|
||||
}
|
||||
|
@@ -78,12 +78,12 @@ ape_scan_internal(FILE *fp, ApeTagCallback callback)
|
||||
|
||||
/* get the key */
|
||||
const char *key = p;
|
||||
while (remaining > size && *p != '\0') {
|
||||
p++;
|
||||
remaining--;
|
||||
}
|
||||
p++;
|
||||
remaining--;
|
||||
const char *key_end = (const char *)memchr(p, '\0', remaining);
|
||||
if (key_end == nullptr)
|
||||
break;
|
||||
|
||||
p = key_end + 1;
|
||||
remaining -= p - key;
|
||||
|
||||
/* get the value */
|
||||
if (remaining < size)
|
||||
|
@@ -82,15 +82,15 @@ riff_seek_id3(FILE *file)
|
||||
underflow when casting to off_t */
|
||||
return 0;
|
||||
|
||||
if (size % 2 != 0)
|
||||
/* pad byte */
|
||||
++size;
|
||||
|
||||
if (memcmp(chunk.id, "id3 ", 4) == 0 ||
|
||||
memcmp(chunk.id, "ID3 ", 4) == 0)
|
||||
/* found it! */
|
||||
return size;
|
||||
|
||||
if (size % 2 != 0)
|
||||
/* pad byte */
|
||||
++size;
|
||||
|
||||
if (fseek(file, size, SEEK_CUR) != 0)
|
||||
return 0;
|
||||
}
|
||||
|
@@ -80,9 +80,17 @@ struct Tag {
|
||||
Tag &operator=(Tag &&other) {
|
||||
duration = other.duration;
|
||||
has_playlist = other.has_playlist;
|
||||
MoveItemsFrom(std::move(other));
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Similar to the move operator, but move only the #TagItem
|
||||
* array.
|
||||
*/
|
||||
void MoveItemsFrom(Tag &&other) {
|
||||
std::swap(items, other.items);
|
||||
std::swap(num_items, other.num_items);
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -23,19 +23,23 @@
|
||||
#include "util/Cast.hxx"
|
||||
#include "util/VarSize.hxx"
|
||||
|
||||
#include <limits>
|
||||
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
Mutex tag_pool_lock;
|
||||
|
||||
static constexpr size_t NUM_SLOTS = 4096;
|
||||
static constexpr size_t NUM_SLOTS = 4093;
|
||||
|
||||
struct TagPoolSlot {
|
||||
TagPoolSlot *next;
|
||||
unsigned char ref;
|
||||
TagItem item;
|
||||
|
||||
static constexpr unsigned MAX_REF = std::numeric_limits<decltype(ref)>::max();
|
||||
|
||||
TagPoolSlot(TagPoolSlot *_next, TagType type,
|
||||
const char *value, size_t length)
|
||||
:next(_next), ref(1) {
|
||||
@@ -116,7 +120,7 @@ tag_pool_get_item(TagType type, const char *value, size_t length)
|
||||
if (slot->item.type == type &&
|
||||
length == strlen(slot->item.value) &&
|
||||
memcmp(value, slot->item.value, length) == 0 &&
|
||||
slot->ref < 0xff) {
|
||||
slot->ref < TagPoolSlot::MAX_REF) {
|
||||
assert(slot->ref > 0);
|
||||
++slot->ref;
|
||||
return &slot->item;
|
||||
@@ -135,19 +139,15 @@ tag_pool_dup_item(TagItem *item)
|
||||
|
||||
assert(slot->ref > 0);
|
||||
|
||||
if (slot->ref < 0xff) {
|
||||
if (slot->ref < TagPoolSlot::MAX_REF) {
|
||||
++slot->ref;
|
||||
return item;
|
||||
} else {
|
||||
/* the reference counter overflows above 0xff;
|
||||
duplicate the item, and start with 1 */
|
||||
/* the reference counter overflows above MAX_REF;
|
||||
obtain a reference to a different TagPoolSlot which
|
||||
isn't yet "full" */
|
||||
size_t length = strlen(item->value);
|
||||
auto slot_p = tag_value_slot_p(item->type,
|
||||
item->value, length);
|
||||
slot = TagPoolSlot::Create(*slot_p, item->type,
|
||||
item->value, strlen(item->value));
|
||||
*slot_p = slot;
|
||||
return &slot->item;
|
||||
return tag_pool_get_item(item->type, item->value, length);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -40,9 +40,9 @@ FindInvalidUTF8(const char *p, const char *const end)
|
||||
/* now call the other SequenceLengthUTF8() overload
|
||||
which also validates the continuations */
|
||||
const size_t t = SequenceLengthUTF8(p);
|
||||
assert(s == t);
|
||||
if (t == 0)
|
||||
return p;
|
||||
assert(s == t);
|
||||
|
||||
p += s;
|
||||
}
|
||||
|
@@ -41,9 +41,13 @@ class PosixCond {
|
||||
pthread_cond_t cond;
|
||||
|
||||
public:
|
||||
#if defined(__NetBSD__) || defined(__BIONIC__)
|
||||
/* NetBSD's PTHREAD_COND_INITIALIZER is not compatible with
|
||||
"constexpr" */
|
||||
#ifdef __GLIBC__
|
||||
/* optimized constexpr constructor for pthread implementations
|
||||
that support it */
|
||||
constexpr PosixCond():cond(PTHREAD_COND_INITIALIZER) {}
|
||||
#else
|
||||
/* slow fallback for pthread implementations that are not
|
||||
compatible with "constexpr" */
|
||||
PosixCond() {
|
||||
pthread_cond_init(&cond, nullptr);
|
||||
}
|
||||
@@ -51,10 +55,6 @@ public:
|
||||
~PosixCond() {
|
||||
pthread_cond_destroy(&cond);
|
||||
}
|
||||
#else
|
||||
/* optimized constexpr constructor for sane POSIX
|
||||
implementations */
|
||||
constexpr PosixCond():cond(PTHREAD_COND_INITIALIZER) {}
|
||||
#endif
|
||||
|
||||
PosixCond(const PosixCond &other) = delete;
|
||||
|
@@ -41,9 +41,13 @@ class PosixMutex {
|
||||
pthread_mutex_t mutex;
|
||||
|
||||
public:
|
||||
#if defined(__NetBSD__) || defined(__BIONIC__)
|
||||
/* NetBSD's PTHREAD_MUTEX_INITIALIZER is not compatible with
|
||||
"constexpr" */
|
||||
#ifdef __GLIBC__
|
||||
/* optimized constexpr constructor for pthread implementations
|
||||
that support it */
|
||||
constexpr PosixMutex():mutex(PTHREAD_MUTEX_INITIALIZER) {}
|
||||
#else
|
||||
/* slow fallback for pthread implementations that are not
|
||||
compatible with "constexpr" */
|
||||
PosixMutex() {
|
||||
pthread_mutex_init(&mutex, nullptr);
|
||||
}
|
||||
@@ -51,10 +55,6 @@ public:
|
||||
~PosixMutex() {
|
||||
pthread_mutex_destroy(&mutex);
|
||||
}
|
||||
#else
|
||||
/* optimized constexpr constructor for sane POSIX
|
||||
implementations */
|
||||
constexpr PosixMutex():mutex(PTHREAD_MUTEX_INITIALIZER) {}
|
||||
#endif
|
||||
|
||||
PosixMutex(const PosixMutex &other) = delete;
|
||||
|
@@ -22,7 +22,6 @@
|
||||
#include "system/FatalError.hxx"
|
||||
#include "fs/AllocatedPath.hxx"
|
||||
#include "fs/FileSystem.hxx"
|
||||
#include "util/Domain.hxx"
|
||||
#include "PidFile.hxx"
|
||||
#include "Log.hxx"
|
||||
|
||||
@@ -37,8 +36,6 @@
|
||||
#include <grp.h>
|
||||
#endif
|
||||
|
||||
static constexpr Domain daemon_domain("daemon");
|
||||
|
||||
#ifndef WIN32
|
||||
|
||||
/** the Unix user name which MPD runs as */
|
||||
@@ -113,7 +110,7 @@ daemonize_set_user(void)
|
||||
(int)user_gid);
|
||||
}
|
||||
|
||||
#ifdef _BSD_SOURCE
|
||||
#ifdef HAVE_INITGROUPS
|
||||
/* init supplementary groups
|
||||
* (must be done before we change our uid)
|
||||
*/
|
||||
|
@@ -46,7 +46,7 @@ static size_t
|
||||
AlignToPageSize(size_t size)
|
||||
{
|
||||
static const long page_size = sysconf(_SC_PAGESIZE);
|
||||
if (page_size > 0)
|
||||
if (page_size == 0)
|
||||
return size;
|
||||
|
||||
size_t ps(page_size);
|
||||
|
89
src/util/ScopeExit.hxx
Normal file
89
src/util/ScopeExit.hxx
Normal file
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Max Kellermann <max@duempel.org>
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* - Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* - Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the
|
||||
* distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
* FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
|
||||
* OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef SCOPE_EXIT_HXX
|
||||
#define SCOPE_EXIT_HXX
|
||||
|
||||
#include "Compiler.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
/**
|
||||
* Internal class. Do not use directly.
|
||||
*/
|
||||
template<typename F>
|
||||
class ScopeExitGuard : F {
|
||||
bool enabled = true;
|
||||
|
||||
public:
|
||||
explicit ScopeExitGuard(F &&f):F(std::forward<F>(f)) {}
|
||||
|
||||
ScopeExitGuard(ScopeExitGuard &&src)
|
||||
:F(std::move(src)) {
|
||||
src.enabled = false;
|
||||
}
|
||||
|
||||
~ScopeExitGuard() {
|
||||
if (enabled)
|
||||
F::operator()();
|
||||
}
|
||||
|
||||
ScopeExitGuard(const ScopeExitGuard &) = delete;
|
||||
ScopeExitGuard &operator=(const ScopeExitGuard &) = delete;
|
||||
};
|
||||
|
||||
/**
|
||||
* Internal class. Do not use directly.
|
||||
*/
|
||||
struct ScopeExitTag {
|
||||
/* this operator is a trick so we don't need to close
|
||||
parantheses at the end of the expression AtScopeExit()
|
||||
call */
|
||||
template<typename F>
|
||||
ScopeExitGuard<F> operator+(F &&f) {
|
||||
return ScopeExitGuard<F>(std::forward<F>(f));
|
||||
}
|
||||
};
|
||||
|
||||
#define ScopeExitCat(a, b) a ## b
|
||||
#define ScopeExitName(line) ScopeExitCat(at_scope_exit_, line)
|
||||
|
||||
/**
|
||||
* Call the block after this macro at the end of the current scope.
|
||||
* Parameters are lambda captures.
|
||||
*
|
||||
* This is exception-safe, however the given code block must not throw
|
||||
* exceptions.
|
||||
*
|
||||
* This attempts to be a better boost/scope_exit.hpp, without all of
|
||||
* Boost's compile-time and runtime bloat.
|
||||
*/
|
||||
#define AtScopeExit(...) auto ScopeExitName(__LINE__) = ScopeExitTag() + [__VA_ARGS__]()
|
||||
|
||||
#endif
|
@@ -7,16 +7,7 @@ ExecStart=@prefix@/bin/mpd --no-daemon
|
||||
|
||||
# allow MPD to use real-time priority 50
|
||||
LimitRTPRIO=50
|
||||
LimitRTTIME=-1
|
||||
|
||||
# move MPD to a top-level cgroup, as real-time budget assignment fails
|
||||
# in cgroup /system/mpd.service, because /system has a zero real-time
|
||||
# budget; see
|
||||
# http://www.freedesktop.org/wiki/Software/systemd/MyServiceCantGetRealtime/
|
||||
ControlGroup=cpu:/mpd
|
||||
|
||||
# assign a real-time budget
|
||||
ControlGroupAttribute=cpu.rt_runtime_us 500000
|
||||
LimitRTTIME=infinity
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
|
@@ -49,9 +49,9 @@ CPPUNIT_TEST_SUITE_REGISTRATION(ByteReverseTest);
|
||||
void
|
||||
ByteReverseTest::TestByteReverse2()
|
||||
{
|
||||
static const char src[] = "123456";
|
||||
static const char src[] gcc_alignas(uint16_t, 2) = "123456";
|
||||
static const char result[] = "214365";
|
||||
static uint8_t dest[ARRAY_SIZE(src)];
|
||||
static uint8_t dest[ARRAY_SIZE(src)] gcc_alignas(uint16_t, 2);
|
||||
|
||||
reverse_bytes(dest, (const uint8_t *)src,
|
||||
(const uint8_t *)(src + ARRAY_SIZE(src) - 1), 2);
|
||||
@@ -73,9 +73,9 @@ ByteReverseTest::TestByteReverse3()
|
||||
void
|
||||
ByteReverseTest::TestByteReverse4()
|
||||
{
|
||||
static const char src[] = "12345678";
|
||||
static const char src[] gcc_alignas(uint32_t, 4) = "12345678";
|
||||
static const char result[] = "43218765";
|
||||
static uint8_t dest[ARRAY_SIZE(src)];
|
||||
static uint8_t dest[ARRAY_SIZE(src)] gcc_alignas(uint32_t, 4);
|
||||
|
||||
reverse_bytes(dest, (const uint8_t *)src,
|
||||
(const uint8_t *)(src + ARRAY_SIZE(src) - 1), 4);
|
||||
|
Reference in New Issue
Block a user