Compare commits
142 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
a40510c241 | ||
![]() |
ac8dce6599 | ||
![]() |
190d525099 | ||
![]() |
1b6666fa39 | ||
![]() |
1dd01c99e8 | ||
![]() |
d50b30a498 | ||
![]() |
42a3a87f13 | ||
![]() |
9dfedbe619 | ||
![]() |
88957b4c9d | ||
![]() |
b2f2c9322b | ||
![]() |
3be2051808 | ||
![]() |
ff32b0dc9b | ||
![]() |
c1869a11af | ||
![]() |
e22a4fdba4 | ||
![]() |
29a7b2c5b5 | ||
![]() |
3b6c285c2a | ||
![]() |
575d1786af | ||
![]() |
bc1c927952 | ||
![]() |
f95bc85f91 | ||
![]() |
4015195314 | ||
![]() |
c3d883c6cb | ||
![]() |
097e30321b | ||
![]() |
b6ddeaacf2 | ||
![]() |
b0c60ec124 | ||
![]() |
f3b788703e | ||
![]() |
4bb83781e8 | ||
![]() |
a73195b7cc | ||
![]() |
1bd00b8a9a | ||
![]() |
d84eaeafc5 | ||
![]() |
20ae84bff9 | ||
![]() |
7372c931b3 | ||
![]() |
29e1b6e465 | ||
![]() |
eda06993f8 | ||
![]() |
4b30ef1cf2 | ||
![]() |
e92e5e8eb8 | ||
![]() |
4e5271fcdf | ||
![]() |
3c55487a16 | ||
![]() |
76a1cae5d8 | ||
![]() |
81a97315e3 | ||
![]() |
53c14d97a6 | ||
![]() |
69a82eec17 | ||
![]() |
45cadef22f | ||
![]() |
0a033fb10a | ||
![]() |
591afa0647 | ||
![]() |
05eac20ffe | ||
![]() |
38d263ac19 | ||
![]() |
f71c204eef | ||
![]() |
51147203be | ||
![]() |
a931686317 | ||
![]() |
5bd322bdcf | ||
![]() |
bb097109f0 | ||
![]() |
2ab6c40ff1 | ||
![]() |
68bb738af2 | ||
![]() |
6b968beede | ||
![]() |
f68dd1bffb | ||
![]() |
f92b71ca99 | ||
![]() |
2b79fe2d6a | ||
![]() |
44dd9af276 | ||
![]() |
d3013d4f8c | ||
![]() |
678524ad21 | ||
![]() |
32a64481f2 | ||
![]() |
1776015c6c | ||
![]() |
f1c71a26e3 | ||
![]() |
e78ab767d3 | ||
![]() |
f01eb2f95d | ||
![]() |
1450e45d97 | ||
![]() |
ec8cba369c | ||
![]() |
f4c248f406 | ||
![]() |
f3b2a58646 | ||
![]() |
c6f89c42b2 | ||
![]() |
5e93cfdd9e | ||
![]() |
d91d5a3ab5 | ||
![]() |
907c045f33 | ||
![]() |
90f189eb54 | ||
![]() |
4abd5b2112 | ||
![]() |
df9a665994 | ||
![]() |
7a098ca0ed | ||
![]() |
33716732a1 | ||
![]() |
97ae594375 | ||
![]() |
3f321ae9a0 | ||
![]() |
161d32a7e7 | ||
![]() |
d7137586a9 | ||
![]() |
cd0c06ba6e | ||
![]() |
899ab63d91 | ||
![]() |
1097820a5a | ||
![]() |
39114f91a7 | ||
![]() |
4f01387edf | ||
![]() |
de3e0585f1 | ||
![]() |
f85f25ba82 | ||
![]() |
10a2c179f9 | ||
![]() |
6eea56861b | ||
![]() |
21fd2064ae | ||
![]() |
dcbab8e37a | ||
![]() |
5677278251 | ||
![]() |
a83bee993d | ||
![]() |
96a31f554a | ||
![]() |
d14ec6aea5 | ||
![]() |
917cedf893 | ||
![]() |
193dd71600 | ||
![]() |
6c293a3d7f | ||
![]() |
e847ddf011 | ||
![]() |
7e8b448985 | ||
![]() |
d1f3a87c08 | ||
![]() |
9f8145e590 | ||
![]() |
791efc171a | ||
![]() |
144312a525 | ||
![]() |
92684112ed | ||
![]() |
ef114ee6cb | ||
![]() |
667f209742 | ||
![]() |
4ad0747c78 | ||
![]() |
c5cf66402c | ||
![]() |
05417049eb | ||
![]() |
c7b0c46d9f | ||
![]() |
df578c91ad | ||
![]() |
70008c47c9 | ||
![]() |
938affef32 | ||
![]() |
a3c33000ee | ||
![]() |
1e54b7b294 | ||
![]() |
cc0dbcf3f4 | ||
![]() |
c5a2cadccc | ||
![]() |
9aa43416b6 | ||
![]() |
8364029db8 | ||
![]() |
d842d21be0 | ||
![]() |
3514fd2433 | ||
![]() |
6778ff27ea | ||
![]() |
f32315d699 | ||
![]() |
8b754b24b6 | ||
![]() |
b1bee9ff38 | ||
![]() |
569be2d402 | ||
![]() |
78a73eac53 | ||
![]() |
533cb99c33 | ||
![]() |
79726940dc | ||
![]() |
27c7891169 | ||
![]() |
7a3a793a12 | ||
![]() |
8088469eca | ||
![]() |
3dcb082015 | ||
![]() |
bece023028 | ||
![]() |
9c4df66925 | ||
![]() |
2b43ceb6c6 | ||
![]() |
c143adba91 | ||
![]() |
142fdc8d86 | ||
![]() |
67778dcd3d |
2
AUTHORS
2
AUTHORS
@@ -5,7 +5,7 @@ The following people have contributed code to MPD:
|
|||||||
|
|
||||||
Warren Dukes <warren.dukes@gmail.com>
|
Warren Dukes <warren.dukes@gmail.com>
|
||||||
Avuton Olrich <avuton@gmail.com>
|
Avuton Olrich <avuton@gmail.com>
|
||||||
Max Kellermann <max@duempel.org>
|
Max Kellermann <max.kellermann@gmail.com>
|
||||||
Laszlo Ashin <kodest@gmail.com>
|
Laszlo Ashin <kodest@gmail.com>
|
||||||
Viliam Mateicka <viliam.mateicka@gmail.com>
|
Viliam Mateicka <viliam.mateicka@gmail.com>
|
||||||
Eric Wollesen <encoded@xmtp.net>
|
Eric Wollesen <encoded@xmtp.net>
|
||||||
|
14
Makefile.am
14
Makefile.am
@@ -415,6 +415,7 @@ libutil_a_SOURCES = \
|
|||||||
src/util/CharUtil.hxx \
|
src/util/CharUtil.hxx \
|
||||||
src/util/NumberParser.hxx \
|
src/util/NumberParser.hxx \
|
||||||
src/util/MimeType.cxx src/util/MimeType.hxx \
|
src/util/MimeType.cxx src/util/MimeType.hxx \
|
||||||
|
src/util/StringBuffer.hxx \
|
||||||
src/util/StringPointer.hxx \
|
src/util/StringPointer.hxx \
|
||||||
src/util/StringView.cxx src/util/StringView.hxx \
|
src/util/StringView.cxx src/util/StringView.hxx \
|
||||||
src/util/AllocatedString.cxx src/util/AllocatedString.hxx \
|
src/util/AllocatedString.cxx src/util/AllocatedString.hxx \
|
||||||
@@ -539,6 +540,10 @@ ICU_LDADD = libicu.a $(ICU_LIBS)
|
|||||||
# PCM library
|
# PCM library
|
||||||
|
|
||||||
libpcm_a_SOURCES = \
|
libpcm_a_SOURCES = \
|
||||||
|
src/CheckAudioFormat.cxx src/CheckAudioFormat.hxx \
|
||||||
|
src/AudioFormat.cxx src/AudioFormat.hxx \
|
||||||
|
src/AudioParser.cxx src/AudioParser.hxx \
|
||||||
|
src/pcm/SampleFormat.cxx src/pcm/SampleFormat.hxx \
|
||||||
src/pcm/Traits.hxx \
|
src/pcm/Traits.hxx \
|
||||||
src/pcm/Interleave.cxx src/pcm/Interleave.hxx \
|
src/pcm/Interleave.cxx src/pcm/Interleave.hxx \
|
||||||
src/pcm/PcmBuffer.cxx src/pcm/PcmBuffer.hxx \
|
src/pcm/PcmBuffer.cxx src/pcm/PcmBuffer.hxx \
|
||||||
@@ -575,6 +580,8 @@ PCM_LIBS = \
|
|||||||
|
|
||||||
if ENABLE_DSD
|
if ENABLE_DSD
|
||||||
libpcm_a_SOURCES += \
|
libpcm_a_SOURCES += \
|
||||||
|
src/pcm/Dsd16.cxx src/pcm/Dsd16.hxx \
|
||||||
|
src/pcm/Dsd32.cxx src/pcm/Dsd32.hxx \
|
||||||
src/pcm/PcmDsd.cxx src/pcm/PcmDsd.hxx \
|
src/pcm/PcmDsd.cxx src/pcm/PcmDsd.hxx \
|
||||||
src/pcm/dsd2pcm/dsd2pcm.c src/pcm/dsd2pcm/dsd2pcm.h
|
src/pcm/dsd2pcm/dsd2pcm.c src/pcm/dsd2pcm/dsd2pcm.h
|
||||||
endif
|
endif
|
||||||
@@ -613,7 +620,8 @@ libxiph_a_SOURCES += \
|
|||||||
src/lib/xiph/OggStreamState.hxx
|
src/lib/xiph/OggStreamState.hxx
|
||||||
endif
|
endif
|
||||||
|
|
||||||
XIPH_LIBS = libxiph.a
|
XIPH_LIBS = libxiph.a \
|
||||||
|
$(OGG_LIBS)
|
||||||
|
|
||||||
endif
|
endif
|
||||||
|
|
||||||
@@ -869,9 +877,6 @@ ARCHIVE_LIBS =
|
|||||||
endif
|
endif
|
||||||
|
|
||||||
libbasic_a_SOURCES = \
|
libbasic_a_SOURCES = \
|
||||||
src/CheckAudioFormat.cxx src/CheckAudioFormat.hxx \
|
|
||||||
src/AudioFormat.cxx src/AudioFormat.hxx \
|
|
||||||
src/AudioParser.cxx src/AudioParser.hxx \
|
|
||||||
src/ReplayGainConfig.hxx \
|
src/ReplayGainConfig.hxx \
|
||||||
src/ReplayGainMode.cxx src/ReplayGainMode.hxx \
|
src/ReplayGainMode.cxx src/ReplayGainMode.hxx \
|
||||||
src/ReplayGainInfo.cxx src/ReplayGainInfo.hxx
|
src/ReplayGainInfo.cxx src/ReplayGainInfo.hxx
|
||||||
@@ -2225,6 +2230,7 @@ test_test_icy_parser_LDADD = \
|
|||||||
endif
|
endif
|
||||||
|
|
||||||
test_test_pcm_SOURCES = \
|
test_test_pcm_SOURCES = \
|
||||||
|
test/TestAudioFormat.cxx test/TestAudioFormat.hxx \
|
||||||
test/test_pcm_util.hxx \
|
test/test_pcm_util.hxx \
|
||||||
test/test_pcm_dither.cxx \
|
test/test_pcm_dither.cxx \
|
||||||
test/test_pcm_pack.cxx \
|
test/test_pcm_pack.cxx \
|
||||||
|
57
NEWS
57
NEWS
@@ -1,3 +1,60 @@
|
|||||||
|
ver 0.20.6 (2017/03/10)
|
||||||
|
* input
|
||||||
|
- curl: fix headers after HTTP redirect to Shoutcast server
|
||||||
|
* decoder
|
||||||
|
- ffmpeg: re-enable as fallback
|
||||||
|
- mpcdec: fix crash (division by zero) after seeking
|
||||||
|
- sidplay: make compatible with libsidplayfp < 1.8
|
||||||
|
* fix stream tags after automatic song change
|
||||||
|
* workaround for GCC 4.9.4 / libstdc++ bug (build failure)
|
||||||
|
|
||||||
|
ver 0.20.5 (2017/02/20)
|
||||||
|
* tags
|
||||||
|
- id3: fix memory leak on corrupt ID3 tags
|
||||||
|
* decoder
|
||||||
|
- sidplay: don't require libsidutils when building with libsidplayfp
|
||||||
|
* output
|
||||||
|
- httpd: fix two buffer overflows in IcyMetaData length calculation
|
||||||
|
* mixer
|
||||||
|
- alsa: fix crash bug
|
||||||
|
|
||||||
|
ver 0.20.4 (2017/02/01)
|
||||||
|
* input
|
||||||
|
- nfs: fix freeze after reconnect
|
||||||
|
* output
|
||||||
|
- sndio: work around a libroar C++ incompatibility
|
||||||
|
* workaround for GCC 4.9 "constexpr" bug
|
||||||
|
* fix FreeBSD build failure
|
||||||
|
|
||||||
|
ver 0.20.3 (2017/01/25)
|
||||||
|
* protocol
|
||||||
|
- "playlistadd" creates new playlist if it does not exist, as documented
|
||||||
|
* database
|
||||||
|
- proxy: fix error "terminate called after throwing ..."
|
||||||
|
- proxy: make connect errors during startup non-fatal
|
||||||
|
* neighbor
|
||||||
|
- upnp: fix premature expiry
|
||||||
|
* replay gain: don't reset ReplayGain levels when unpausing playback
|
||||||
|
* silence surround channels when converting from stereo
|
||||||
|
* use shortcuts such as "dsd64" in log messages
|
||||||
|
|
||||||
|
ver 0.20.2 (2017/01/15)
|
||||||
|
* input
|
||||||
|
- alsa: fix crash bug
|
||||||
|
- alsa: fix buffer overruns
|
||||||
|
* decoder
|
||||||
|
- flac: add options "probesize" and "analyzeduration"
|
||||||
|
* resampler
|
||||||
|
- libsamplerate: reset state after seeking
|
||||||
|
* output
|
||||||
|
- fix static noise after changing to a different audio format
|
||||||
|
- alsa: fix the DSD_U32 sample rate
|
||||||
|
- alsa: fix the DSD_U32 byte order
|
||||||
|
- alsa: support DSD_U16
|
||||||
|
- recorder: fix error "Failed to create : No such file or directory"
|
||||||
|
* playlist
|
||||||
|
- cue: fix skipping songs
|
||||||
|
|
||||||
ver 0.20.1 (2017/01/09)
|
ver 0.20.1 (2017/01/09)
|
||||||
* input
|
* input
|
||||||
- curl: fix crash bug
|
- curl: fix crash bug
|
||||||
|
@@ -87,9 +87,14 @@ class AndroidNdkToolchain:
|
|||||||
self.is_armv7 = self.is_arm and 'armv7' in self.cflags
|
self.is_armv7 = self.is_arm and 'armv7' in self.cflags
|
||||||
self.is_windows = False
|
self.is_windows = False
|
||||||
|
|
||||||
libstdcxx_path = os.path.join(ndk_path, 'sources/cxx-stl/gnu-libstdc++', gcc_version)
|
libcxx_path = os.path.join(ndk_path, 'sources/cxx-stl/llvm-libc++')
|
||||||
libstdcxx_cppflags = '-isystem ' + os.path.join(libstdcxx_path, 'include') + ' -isystem ' + os.path.join(libstdcxx_path, 'libs', android_abi, 'include')
|
libcxx_libs_path = os.path.join(libcxx_path, 'libs', android_abi)
|
||||||
libstdcxx_ldadd = os.path.join(libstdcxx_path, 'libs', android_abi, 'libgnustl_static.a')
|
|
||||||
|
libstdcxx_cppflags = '-nostdinc++ -isystem ' + os.path.join(libcxx_path, 'include') + ' -isystem ' + os.path.join(ndk_path, 'sources/android/support/include')
|
||||||
|
libstdcxx_ldadd = os.path.join(libcxx_libs_path, 'libc++_static.a') + ' ' + os.path.join(libcxx_libs_path, 'libc++abi.a')
|
||||||
|
|
||||||
|
if self.is_armv7:
|
||||||
|
libstdcxx_ldadd += ' ' + os.path.join(libcxx_libs_path, 'libunwind.a')
|
||||||
|
|
||||||
if use_cxx:
|
if use_cxx:
|
||||||
self.libs += ' ' + libstdcxx_ldadd
|
self.libs += ' ' + libstdcxx_ldadd
|
||||||
|
@@ -1,10 +1,10 @@
|
|||||||
AC_PREREQ(2.60)
|
AC_PREREQ(2.60)
|
||||||
|
|
||||||
AC_INIT(mpd, 0.20.1, musicpd-dev-team@lists.sourceforge.net)
|
AC_INIT(mpd, 0.20.6, musicpd-dev-team@lists.sourceforge.net)
|
||||||
|
|
||||||
VERSION_MAJOR=0
|
VERSION_MAJOR=0
|
||||||
VERSION_MINOR=20
|
VERSION_MINOR=20
|
||||||
VERSION_REVISION=1
|
VERSION_REVISION=6
|
||||||
VERSION_EXTRA=0
|
VERSION_EXTRA=0
|
||||||
|
|
||||||
AC_CONFIG_SRCDIR([src/Main.cxx])
|
AC_CONFIG_SRCDIR([src/Main.cxx])
|
||||||
@@ -992,7 +992,7 @@ AM_CONDITIONAL(ENABLE_VORBIS_DECODER, test x$enable_vorbis = xyes || test x$enab
|
|||||||
dnl --------------------------------- sidplay ---------------------------------
|
dnl --------------------------------- sidplay ---------------------------------
|
||||||
if test x$enable_sidplay != xno; then
|
if test x$enable_sidplay != xno; then
|
||||||
dnl Check for libsidplayfp first
|
dnl Check for libsidplayfp first
|
||||||
PKG_CHECK_MODULES([SIDPLAY], [libsidplayfp libsidutils],
|
PKG_CHECK_MODULES([SIDPLAY], [libsidplayfp],
|
||||||
[found_sidplayfp=yes],
|
[found_sidplayfp=yes],
|
||||||
[found_sidplayfp=no])
|
[found_sidplayfp=no])
|
||||||
found_sidplay=$found_sidplayfp
|
found_sidplay=$found_sidplayfp
|
||||||
|
@@ -55,7 +55,8 @@
|
|||||||
|
|
||||||
<listitem>
|
<listitem>
|
||||||
<para>
|
<para>
|
||||||
<varname>track</varname>: the track number within the album.
|
<varname>track</varname>: the decimal track number within the
|
||||||
|
album.
|
||||||
</para>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
|
|
||||||
@@ -103,7 +104,8 @@
|
|||||||
|
|
||||||
<listitem>
|
<listitem>
|
||||||
<para>
|
<para>
|
||||||
<varname>disc</varname>: the disc number in a multi-disc album.
|
<varname>disc</varname>: the decimal disc number in a multi-disc
|
||||||
|
album.
|
||||||
</para>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
|
|
||||||
|
@@ -50,6 +50,6 @@ If you find a bug, please report it at
|
|||||||
.br
|
.br
|
||||||
<\fBhttp://bugs.musicpd.org/bug_report_page.php\fP>.
|
<\fBhttp://bugs.musicpd.org/bug_report_page.php\fP>.
|
||||||
.SH AUTHORS
|
.SH AUTHORS
|
||||||
Max Kellermann <max@duempel.org>
|
Max Kellermann <max.kellermann@gmail.com>
|
||||||
|
|
||||||
Special thanks to all the people that provided feedback and patches.
|
Special thanks to all the people that provided feedback and patches.
|
||||||
|
@@ -573,7 +573,12 @@
|
|||||||
<listitem>
|
<listitem>
|
||||||
<para>
|
<para>
|
||||||
<varname>audio</varname>:
|
<varname>audio</varname>:
|
||||||
<returnvalue>sampleRate:bits:channels</returnvalue>
|
<returnvalue>
|
||||||
|
The format emitted by the decoder plugin during
|
||||||
|
playback, format:
|
||||||
|
"<replaceable>samplerate:bits:channels</replaceable>".
|
||||||
|
Check the user manual for a detailed explanation.
|
||||||
|
</returnvalue>
|
||||||
</para>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
<listitem>
|
<listitem>
|
||||||
|
230
doc/user.xml
230
doc/user.xml
@@ -135,6 +135,91 @@ apt-get install g++ \
|
|||||||
</para>
|
</para>
|
||||||
|
|
||||||
<programlisting>make install</programlisting>
|
<programlisting>make install</programlisting>
|
||||||
|
|
||||||
|
<section id="windows_build">
|
||||||
|
<title>Compiling for Windows</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Even though it does not "feel" like a Windows application,
|
||||||
|
<application>MPD</application> works well under Windows.
|
||||||
|
Its build process follows the "Linux style", and may seem
|
||||||
|
awkward for Windows people (who are not used to compiling
|
||||||
|
their software, anyway).
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Basically, there are three ways to compile
|
||||||
|
<application>MPD</application> for Windows:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<orderedlist>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Build on Windows for Windows. All you need to do is
|
||||||
|
described above already: configure and make.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
For Windows users, this is kind of unusual, because few
|
||||||
|
Windows users have a GNU toolchain and a UNIX shell
|
||||||
|
installed.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Build on Linux for Windows. This is described above
|
||||||
|
already: configure and make. You need the <ulink
|
||||||
|
url="https://mingw-w64.org/"><application>mingw-w64</application>
|
||||||
|
cross compiler</ulink>. Pass
|
||||||
|
<parameter>--host=i686-w64-mingw32</parameter> (32 bit)
|
||||||
|
or <parameter>--host=x86_64-w64-mingw32</parameter> (64
|
||||||
|
bit) to configure.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
This is somewhat natural for Linux users. Many
|
||||||
|
distributions have <application>mingw-w64</application>
|
||||||
|
packages. The remaining difficulty here is installing
|
||||||
|
all the external libraries. And
|
||||||
|
<application>MPD</application> usually needs many,
|
||||||
|
making this method cumbersome for the casual user.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Build on Linux for Windows using the
|
||||||
|
<application>MPD</application>'s library build script.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</orderedlist>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
This section is about the latter.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Just like with the native build, unpack the
|
||||||
|
<application>MPD</application> source tarball and change
|
||||||
|
into the directory. Then, instead of
|
||||||
|
<command>./configure</command>, type:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting>./win32/build.py --64</programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
This downloads various library sources, and then configures
|
||||||
|
and builds <application>MPD</application> (for x64; to build
|
||||||
|
a 32 bit binary, pass <parameter>--32</parameter>). The
|
||||||
|
resulting EXE files is linked statically, i.e. it contains
|
||||||
|
all the libraries already, and you do not need carry DLLs
|
||||||
|
around. It is large, but easy to use. If you wish to have
|
||||||
|
a small <filename>mpd.exe</filename> with DLLs, you need to
|
||||||
|
compile manually, without the <filename>build.py</filename>
|
||||||
|
script.
|
||||||
|
</para>
|
||||||
|
</section>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section id="systemd_socket">
|
<section id="systemd_socket">
|
||||||
@@ -576,10 +661,11 @@ systemctl start mpd.socket</programlisting>
|
|||||||
</entry>
|
</entry>
|
||||||
<entry>
|
<entry>
|
||||||
<para>
|
<para>
|
||||||
Always open the audio output with the specified audio
|
Always open the audio output with the specified
|
||||||
format (samplerate:bits:channels), regardless of the
|
audio format
|
||||||
format of the input file. This is optional for most
|
(<replaceable>samplerate:bits:channels</replaceable>),
|
||||||
plugins.
|
regardless of the format of the input file. This is
|
||||||
|
optional for most plugins.
|
||||||
</para>
|
</para>
|
||||||
<para>
|
<para>
|
||||||
Any of the three attributes may be an asterisk to
|
Any of the three attributes may be an asterisk to
|
||||||
@@ -596,7 +682,20 @@ systemctl start mpd.socket</programlisting>
|
|||||||
24 bit integer samples padded to 32 bit),
|
24 bit integer samples padded to 32 bit),
|
||||||
<varname>32</varname> (signed 32 bit integer
|
<varname>32</varname> (signed 32 bit integer
|
||||||
samples), <varname>f</varname> (32 bit floating
|
samples), <varname>f</varname> (32 bit floating
|
||||||
point, -1.0 to 1.0).
|
point, -1.0 to 1.0), "<varname>dsd</varname>" means
|
||||||
|
DSD (Direct Stream Digital). For DSD, there are
|
||||||
|
special cases such as "<varname>dsd64</varname>",
|
||||||
|
which allows you to omit the sample rate
|
||||||
|
(e.g. <parameter>dsd512:2</parameter> for stereo
|
||||||
|
DSD512, i.e. 22.5792 MHz).
|
||||||
|
</para>
|
||||||
|
<para>
|
||||||
|
The sample rate is special for DSD:
|
||||||
|
<application>MPD</application> counts the number of
|
||||||
|
bytes, not bits. Thus, a DSD "bit" rate of 22.5792
|
||||||
|
MHz (DSD512) is 2822400 from
|
||||||
|
<application>MPD</application>'s point of view
|
||||||
|
(44100*512/8).
|
||||||
</para>
|
</para>
|
||||||
</entry>
|
</entry>
|
||||||
</row>
|
</row>
|
||||||
@@ -742,9 +841,11 @@ systemctl start mpd.socket</programlisting>
|
|||||||
<title>Configuring playlist plugins</title>
|
<title>Configuring playlist plugins</title>
|
||||||
|
|
||||||
<para>
|
<para>
|
||||||
Playlist plugins are used to load remote playlists. This is
|
Playlist plugins are used to load remote playlists (protocol
|
||||||
not related to <application>MPD</application>'s playlist
|
commands <command>load</command>,
|
||||||
directory.
|
<command>listplaylist</command> and
|
||||||
|
<command>listplaylistinfo</command>). This is not related to
|
||||||
|
<application>MPD</application>'s playlist directory.
|
||||||
</para>
|
</para>
|
||||||
|
|
||||||
<para>
|
<para>
|
||||||
@@ -1800,6 +1901,13 @@ run</programlisting>
|
|||||||
database.
|
database.
|
||||||
</para>
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Note that unless overridden by the below settings (e.g. by
|
||||||
|
setting them to a blank value), general curl configuration
|
||||||
|
from environment variables such as http_proxy or specified
|
||||||
|
in ~/.curlrc will be in effect.
|
||||||
|
</para>
|
||||||
|
|
||||||
<informaltable>
|
<informaltable>
|
||||||
<tgroup cols="2">
|
<tgroup cols="2">
|
||||||
<thead>
|
<thead>
|
||||||
@@ -2206,6 +2314,48 @@ run</programlisting>
|
|||||||
Decodes various codecs using
|
Decodes various codecs using
|
||||||
<application>FFmpeg</application>.
|
<application>FFmpeg</application>.
|
||||||
</para>
|
</para>
|
||||||
|
|
||||||
|
<informaltable>
|
||||||
|
<tgroup cols="2">
|
||||||
|
<thead>
|
||||||
|
<row>
|
||||||
|
<entry>Setting</entry>
|
||||||
|
<entry>Description</entry>
|
||||||
|
</row>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<row>
|
||||||
|
<entry>
|
||||||
|
<varname>analyzeduration</varname>
|
||||||
|
<parameter>VALUE</parameter>
|
||||||
|
</entry>
|
||||||
|
<entry>
|
||||||
|
Sets the FFmpeg muxer option
|
||||||
|
<varname>analyzeduration</varname>, which specifies
|
||||||
|
how many microseconds are analyzed to probe the
|
||||||
|
input. The <ulink
|
||||||
|
url="https://ffmpeg.org/ffmpeg-formats.html">FFmpeg
|
||||||
|
formats documentation</ulink> has more information.
|
||||||
|
</entry>
|
||||||
|
</row>
|
||||||
|
|
||||||
|
<row>
|
||||||
|
<entry>
|
||||||
|
<varname>probesize</varname>
|
||||||
|
<parameter>VALUE</parameter>
|
||||||
|
</entry>
|
||||||
|
<entry>
|
||||||
|
Sets the FFmpeg muxer option
|
||||||
|
<varname>probesize</varname>, which specifies
|
||||||
|
probing size in bytes, i.e. the size of the data to
|
||||||
|
analyze to get stream information. The <ulink
|
||||||
|
url="https://ffmpeg.org/ffmpeg-formats.html">FFmpeg
|
||||||
|
formats documentation</ulink> has more information.
|
||||||
|
</entry>
|
||||||
|
</row>
|
||||||
|
</tbody>
|
||||||
|
</tgroup>
|
||||||
|
</informaltable>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section id="flac_decoder">
|
<section id="flac_decoder">
|
||||||
@@ -4151,6 +4301,22 @@ run</programlisting>
|
|||||||
<section id="playlist_plugins">
|
<section id="playlist_plugins">
|
||||||
<title>Playlist plugins</title>
|
<title>Playlist plugins</title>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<title><varname>asx</varname></title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Reads <filename>.asx</filename> playlist files.
|
||||||
|
</para>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<title><varname>cue</varname></title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Reads <filename>.cue</filename> files.
|
||||||
|
</para>
|
||||||
|
</section>
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
<title><varname>embcue</varname></title>
|
<title><varname>embcue</varname></title>
|
||||||
|
|
||||||
@@ -4175,6 +4341,15 @@ run</programlisting>
|
|||||||
</para>
|
</para>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<title><varname>flac</varname></title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Reads the <varname>cuesheet</varname> metablock from a FLAC
|
||||||
|
file.
|
||||||
|
</para>
|
||||||
|
</section>
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
<title><varname>pls</varname></title>
|
<title><varname>pls</varname></title>
|
||||||
|
|
||||||
@@ -4183,6 +4358,45 @@ run</programlisting>
|
|||||||
</para>
|
</para>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<title><varname>rss</varname></title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Reads music links from <filename>.rss</filename> files.
|
||||||
|
</para>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<title><varname>soundcloud</varname></title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Download playlist from SoundCloud. It accepts URIs starting
|
||||||
|
with <filename>soundcloud://</filename>.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<informaltable>
|
||||||
|
<tgroup cols="2">
|
||||||
|
<thead>
|
||||||
|
<row>
|
||||||
|
<entry>Setting</entry>
|
||||||
|
<entry>Description</entry>
|
||||||
|
</row>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<row>
|
||||||
|
<entry>
|
||||||
|
<varname>apikey</varname>
|
||||||
|
<parameter>KEY</parameter>
|
||||||
|
</entry>
|
||||||
|
<entry>
|
||||||
|
An API key to access the SoundCloud servers.
|
||||||
|
</entry>
|
||||||
|
</row>
|
||||||
|
</tbody>
|
||||||
|
</tgroup>
|
||||||
|
</informaltable>
|
||||||
|
</section>
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
<title><varname>xspf</varname></title>
|
<title><varname>xspf</varname></title>
|
||||||
|
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
# Check if "struct ucred" is available.
|
# Check if "struct ucred" is available.
|
||||||
#
|
#
|
||||||
# Author: Max Kellermann <max@duempel.org>
|
# Author: Max Kellermann <max.kellermann@gmail.com>
|
||||||
|
|
||||||
AC_DEFUN([STRUCT_UCRED],[
|
AC_DEFUN([STRUCT_UCRED],[
|
||||||
AC_MSG_CHECKING([for struct ucred])
|
AC_MSG_CHECKING([for struct ucred])
|
||||||
|
@@ -19,8 +19,8 @@ libvorbis = AutotoolsProject(
|
|||||||
)
|
)
|
||||||
|
|
||||||
opus = AutotoolsProject(
|
opus = AutotoolsProject(
|
||||||
'http://downloads.xiph.org/releases/opus/opus-1.1.3.tar.gz',
|
'http://downloads.xiph.org/releases/opus/opus-1.1.4.tar.gz',
|
||||||
'32bbb6b557fe1b6066adc0ae1f08b629',
|
'9122b6b380081dd2665189f97bfd777f04f92dc3ab6698eea1dbb27ad59d8692',
|
||||||
'lib/libopus.a',
|
'lib/libopus.a',
|
||||||
['--disable-shared', '--enable-static'],
|
['--disable-shared', '--enable-static'],
|
||||||
)
|
)
|
||||||
@@ -58,8 +58,8 @@ libmad = AutotoolsProject(
|
|||||||
)
|
)
|
||||||
|
|
||||||
ffmpeg = FfmpegProject(
|
ffmpeg = FfmpegProject(
|
||||||
'http://ffmpeg.org/releases/ffmpeg-3.2.2.tar.xz',
|
'http://ffmpeg.org/releases/ffmpeg-3.2.4.tar.xz',
|
||||||
'3f01bd1fe1a17a277f8c84869e5d9192b4b978cb660872aa2b54c3cc8a2fedfc',
|
'6e38ff14f080c98b58cf5967573501b8cb586e3a173b591f3807d8f0660daf7a',
|
||||||
'lib/libavcodec.a',
|
'lib/libavcodec.a',
|
||||||
[
|
[
|
||||||
'--disable-shared', '--enable-static',
|
'--disable-shared', '--enable-static',
|
||||||
|
@@ -18,6 +18,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "AudioFormat.hxx"
|
#include "AudioFormat.hxx"
|
||||||
|
#include "util/StringBuffer.hxx"
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
@@ -40,46 +41,24 @@ AudioFormat::ApplyMask(AudioFormat mask)
|
|||||||
assert(IsValid());
|
assert(IsValid());
|
||||||
}
|
}
|
||||||
|
|
||||||
const char *
|
StringBuffer<24>
|
||||||
sample_format_to_string(SampleFormat format)
|
ToString(const AudioFormat af)
|
||||||
{
|
{
|
||||||
switch (format) {
|
StringBuffer<24> buffer;
|
||||||
case SampleFormat::UNDEFINED:
|
|
||||||
return "?";
|
|
||||||
|
|
||||||
case SampleFormat::S8:
|
if (af.format == SampleFormat::DSD && af.sample_rate > 0 &&
|
||||||
return "8";
|
af.sample_rate % 44100 == 0) {
|
||||||
|
/* use shortcuts such as "dsd64" which implies the
|
||||||
case SampleFormat::S16:
|
sample rate */
|
||||||
return "16";
|
snprintf(buffer.data(), buffer.capacity(), "dsd%u:%u",
|
||||||
|
af.sample_rate * 8 / 44100,
|
||||||
case SampleFormat::S24_P32:
|
af.channels);
|
||||||
return "24";
|
return buffer;
|
||||||
|
|
||||||
case SampleFormat::S32:
|
|
||||||
return "32";
|
|
||||||
|
|
||||||
case SampleFormat::FLOAT:
|
|
||||||
return "f";
|
|
||||||
|
|
||||||
case SampleFormat::DSD:
|
|
||||||
return "dsd";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* unreachable */
|
snprintf(buffer.data(), buffer.capacity(), "%u:%s:%u",
|
||||||
assert(false);
|
|
||||||
gcc_unreachable();
|
|
||||||
}
|
|
||||||
|
|
||||||
const char *
|
|
||||||
audio_format_to_string(const AudioFormat af,
|
|
||||||
struct audio_format_string *s)
|
|
||||||
{
|
|
||||||
assert(s != nullptr);
|
|
||||||
|
|
||||||
snprintf(s->buffer, sizeof(s->buffer), "%u:%s:%u",
|
|
||||||
af.sample_rate, sample_format_to_string(af.format),
|
af.sample_rate, sample_format_to_string(af.format),
|
||||||
af.channels);
|
af.channels);
|
||||||
|
|
||||||
return s->buffer;
|
return buffer;
|
||||||
}
|
}
|
||||||
|
@@ -20,47 +20,14 @@
|
|||||||
#ifndef MPD_AUDIO_FORMAT_HXX
|
#ifndef MPD_AUDIO_FORMAT_HXX
|
||||||
#define MPD_AUDIO_FORMAT_HXX
|
#define MPD_AUDIO_FORMAT_HXX
|
||||||
|
|
||||||
|
#include "pcm/SampleFormat.hxx"
|
||||||
#include "Compiler.h"
|
#include "Compiler.h"
|
||||||
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
#if defined(WIN32) && GCC_CHECK_VERSION(4,6)
|
template<size_t CAPACITY> class StringBuffer;
|
||||||
/* on WIN32, "FLOAT" is already defined, and this triggers -Wshadow */
|
|
||||||
#pragma GCC diagnostic push
|
|
||||||
#pragma GCC diagnostic ignored "-Wshadow"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
enum class SampleFormat : uint8_t {
|
|
||||||
UNDEFINED = 0,
|
|
||||||
|
|
||||||
S8,
|
|
||||||
S16,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Signed 24 bit integer samples, packed in 32 bit integers
|
|
||||||
* (the most significant byte is filled with the sign bit).
|
|
||||||
*/
|
|
||||||
S24_P32,
|
|
||||||
|
|
||||||
S32,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 32 bit floating point samples in the host's format. The
|
|
||||||
* range is -1.0f to +1.0f.
|
|
||||||
*/
|
|
||||||
FLOAT,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Direct Stream Digital. 1-bit samples; each frame has one
|
|
||||||
* byte (8 samples) per channel.
|
|
||||||
*/
|
|
||||||
DSD,
|
|
||||||
};
|
|
||||||
|
|
||||||
#if defined(WIN32) && GCC_CHECK_VERSION(4,6)
|
|
||||||
#pragma GCC diagnostic pop
|
|
||||||
#endif
|
|
||||||
|
|
||||||
static constexpr unsigned MAX_CHANNELS = 8;
|
static constexpr unsigned MAX_CHANNELS = 8;
|
||||||
|
|
||||||
@@ -183,13 +150,6 @@ struct AudioFormat {
|
|||||||
double GetTimeToSize() const;
|
double GetTimeToSize() const;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Buffer for audio_format_string().
|
|
||||||
*/
|
|
||||||
struct audio_format_string {
|
|
||||||
char buffer[24];
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks whether the sample rate is valid.
|
* Checks whether the sample rate is valid.
|
||||||
*
|
*
|
||||||
@@ -201,28 +161,6 @@ audio_valid_sample_rate(unsigned sample_rate)
|
|||||||
return sample_rate > 0 && sample_rate < (1 << 30);
|
return sample_rate > 0 && sample_rate < (1 << 30);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks whether the sample format is valid.
|
|
||||||
*/
|
|
||||||
static inline bool
|
|
||||||
audio_valid_sample_format(SampleFormat format)
|
|
||||||
{
|
|
||||||
switch (format) {
|
|
||||||
case SampleFormat::S8:
|
|
||||||
case SampleFormat::S16:
|
|
||||||
case SampleFormat::S24_P32:
|
|
||||||
case SampleFormat::S32:
|
|
||||||
case SampleFormat::FLOAT:
|
|
||||||
case SampleFormat::DSD:
|
|
||||||
return true;
|
|
||||||
|
|
||||||
case SampleFormat::UNDEFINED:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks whether the number of channels is valid.
|
* Checks whether the number of channels is valid.
|
||||||
*/
|
*/
|
||||||
@@ -258,34 +196,6 @@ AudioFormat::IsMaskValid() const
|
|||||||
(channels == 0 || audio_valid_channel_count(channels));
|
(channels == 0 || audio_valid_channel_count(channels));
|
||||||
}
|
}
|
||||||
|
|
||||||
gcc_const
|
|
||||||
static inline unsigned
|
|
||||||
sample_format_size(SampleFormat format)
|
|
||||||
{
|
|
||||||
switch (format) {
|
|
||||||
case SampleFormat::S8:
|
|
||||||
return 1;
|
|
||||||
|
|
||||||
case SampleFormat::S16:
|
|
||||||
return 2;
|
|
||||||
|
|
||||||
case SampleFormat::S24_P32:
|
|
||||||
case SampleFormat::S32:
|
|
||||||
case SampleFormat::FLOAT:
|
|
||||||
return 4;
|
|
||||||
|
|
||||||
case SampleFormat::DSD:
|
|
||||||
/* each frame has 8 samples per channel */
|
|
||||||
return 1;
|
|
||||||
|
|
||||||
case SampleFormat::UNDEFINED:
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
assert(false);
|
|
||||||
gcc_unreachable();
|
|
||||||
}
|
|
||||||
|
|
||||||
inline unsigned
|
inline unsigned
|
||||||
AudioFormat::GetSampleSize() const
|
AudioFormat::GetSampleSize() const
|
||||||
{
|
{
|
||||||
@@ -304,28 +214,15 @@ AudioFormat::GetTimeToSize() const
|
|||||||
return sample_rate * GetFrameSize();
|
return sample_rate * GetFrameSize();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Renders a #SampleFormat enum into a string, e.g. for printing it
|
|
||||||
* in a log file.
|
|
||||||
*
|
|
||||||
* @param format a #SampleFormat enum value
|
|
||||||
* @return the string
|
|
||||||
*/
|
|
||||||
gcc_pure gcc_malloc
|
|
||||||
const char *
|
|
||||||
sample_format_to_string(SampleFormat format);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Renders the #AudioFormat object into a string, e.g. for printing
|
* Renders the #AudioFormat object into a string, e.g. for printing
|
||||||
* it in a log file.
|
* it in a log file.
|
||||||
*
|
*
|
||||||
* @param af the #AudioFormat object
|
* @param af the #AudioFormat object
|
||||||
* @param s a buffer to print into
|
* @return the string buffer
|
||||||
* @return the string, or nullptr if the #AudioFormat object is invalid
|
|
||||||
*/
|
*/
|
||||||
gcc_pure gcc_malloc
|
gcc_const
|
||||||
const char *
|
StringBuffer<24>
|
||||||
audio_format_to_string(AudioFormat af,
|
ToString(AudioFormat af);
|
||||||
struct audio_format_string *s);
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@@ -137,6 +137,26 @@ ParseAudioFormat(const char *src, bool mask)
|
|||||||
AudioFormat dest;
|
AudioFormat dest;
|
||||||
dest.Clear();
|
dest.Clear();
|
||||||
|
|
||||||
|
if (strncmp(src, "dsd", 3) == 0) {
|
||||||
|
/* allow format specifications such as "dsd64" which
|
||||||
|
implies the sample rate */
|
||||||
|
|
||||||
|
char *endptr;
|
||||||
|
auto dsd = strtoul(src + 3, &endptr, 10);
|
||||||
|
if (endptr > src + 3 && *endptr == ':' &&
|
||||||
|
dsd >= 32 && dsd <= 4096 && dsd % 2 == 0) {
|
||||||
|
dest.sample_rate = dsd * 44100 / 8;
|
||||||
|
dest.format = SampleFormat::DSD;
|
||||||
|
|
||||||
|
src = endptr + 1;
|
||||||
|
dest.channels = ParseChannelCount(src, mask, &src);
|
||||||
|
if (*src != 0)
|
||||||
|
throw FormatRuntimeError("Extra data after channel count: %s", src);
|
||||||
|
|
||||||
|
return dest;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* parse sample rate */
|
/* parse sample rate */
|
||||||
|
|
||||||
dest.sample_rate = ParseSampleRate(src, mask, &src);
|
dest.sample_rate = ParseSampleRate(src, mask, &src);
|
||||||
|
@@ -107,7 +107,7 @@ static void version(void)
|
|||||||
"\n"
|
"\n"
|
||||||
"\n"
|
"\n"
|
||||||
"Copyright (C) 2003-2007 Warren Dukes <warren.dukes@gmail.com>\n"
|
"Copyright (C) 2003-2007 Warren Dukes <warren.dukes@gmail.com>\n"
|
||||||
"Copyright (C) 2008-2015 Max Kellermann <max@duempel.org>\n"
|
"Copyright 2008-2017 Max Kellermann <max.kellermann@gmail.com>\n"
|
||||||
"This is free software; see the source for copying conditions. There is NO\n"
|
"This is free software; see the source for copying conditions. There is NO\n"
|
||||||
"warranty; not even MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
|
"warranty; not even MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
|
||||||
|
|
||||||
|
@@ -63,18 +63,18 @@ class DetachedSong {
|
|||||||
|
|
||||||
Tag tag;
|
Tag tag;
|
||||||
|
|
||||||
time_t mtime;
|
time_t mtime = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Start of this sub-song within the file.
|
* Start of this sub-song within the file.
|
||||||
*/
|
*/
|
||||||
SongTime start_time;
|
SongTime start_time = SongTime::zero();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* End of this sub-song within the file.
|
* End of this sub-song within the file.
|
||||||
* Unused if zero.
|
* Unused if zero.
|
||||||
*/
|
*/
|
||||||
SongTime end_time;
|
SongTime end_time = SongTime::zero();
|
||||||
|
|
||||||
explicit DetachedSong(const LightSong &other);
|
explicit DetachedSong(const LightSong &other);
|
||||||
|
|
||||||
@@ -82,26 +82,18 @@ public:
|
|||||||
explicit DetachedSong(const DetachedSong &) = default;
|
explicit DetachedSong(const DetachedSong &) = default;
|
||||||
|
|
||||||
explicit DetachedSong(const char *_uri)
|
explicit DetachedSong(const char *_uri)
|
||||||
:uri(_uri),
|
:uri(_uri) {}
|
||||||
mtime(0),
|
|
||||||
start_time(SongTime::zero()), end_time(SongTime::zero()) {}
|
|
||||||
|
|
||||||
explicit DetachedSong(const std::string &_uri)
|
explicit DetachedSong(const std::string &_uri)
|
||||||
:uri(_uri),
|
:uri(_uri) {}
|
||||||
mtime(0),
|
|
||||||
start_time(SongTime::zero()), end_time(SongTime::zero()) {}
|
|
||||||
|
|
||||||
explicit DetachedSong(std::string &&_uri)
|
explicit DetachedSong(std::string &&_uri)
|
||||||
:uri(std::move(_uri)),
|
:uri(std::move(_uri)) {}
|
||||||
mtime(0),
|
|
||||||
start_time(SongTime::zero()), end_time(SongTime::zero()) {}
|
|
||||||
|
|
||||||
template<typename U>
|
template<typename U>
|
||||||
DetachedSong(U &&_uri, Tag &&_tag)
|
DetachedSong(U &&_uri, Tag &&_tag)
|
||||||
:uri(std::forward<U>(_uri)),
|
:uri(std::forward<U>(_uri)),
|
||||||
tag(std::move(_tag)),
|
tag(std::move(_tag)) {}
|
||||||
mtime(0),
|
|
||||||
start_time(SongTime::zero()), end_time(SongTime::zero()) {}
|
|
||||||
|
|
||||||
DetachedSong(DetachedSong &&) = default;
|
DetachedSong(DetachedSong &&) = default;
|
||||||
|
|
||||||
@@ -146,7 +138,9 @@ public:
|
|||||||
*/
|
*/
|
||||||
gcc_pure
|
gcc_pure
|
||||||
bool IsSame(const DetachedSong &other) const {
|
bool IsSame(const DetachedSong &other) const {
|
||||||
return uri == other.uri;
|
return uri == other.uri &&
|
||||||
|
start_time == other.start_time &&
|
||||||
|
end_time == other.end_time;
|
||||||
}
|
}
|
||||||
|
|
||||||
gcc_pure gcc_nonnull_all
|
gcc_pure gcc_nonnull_all
|
||||||
|
@@ -203,7 +203,11 @@ glue_db_init_and_load(void)
|
|||||||
"because the database does not need it");
|
"because the database does not need it");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
instance->database->Open();
|
instance->database->Open();
|
||||||
|
} catch (...) {
|
||||||
|
std::throw_with_nested(std::runtime_error("Failed to open database plugin"));
|
||||||
|
}
|
||||||
|
|
||||||
if (!instance->database->IsPlugin(simple_db_plugin))
|
if (!instance->database->IsPlugin(simple_db_plugin))
|
||||||
return true;
|
return true;
|
||||||
|
@@ -141,9 +141,9 @@ Partition::OnMixerVolumeChanged(gcc_unused Mixer &mixer, gcc_unused int volume)
|
|||||||
void
|
void
|
||||||
Partition::OnGlobalEvent(unsigned mask)
|
Partition::OnGlobalEvent(unsigned mask)
|
||||||
{
|
{
|
||||||
if ((mask & TAG_MODIFIED) != 0)
|
|
||||||
TagModified();
|
|
||||||
|
|
||||||
if ((mask & SYNC_WITH_PLAYER) != 0)
|
if ((mask & SYNC_WITH_PLAYER) != 0)
|
||||||
SyncWithPlayer();
|
SyncWithPlayer();
|
||||||
|
|
||||||
|
if ((mask & TAG_MODIFIED) != 0)
|
||||||
|
TagModified();
|
||||||
}
|
}
|
||||||
|
@@ -335,7 +335,7 @@ try {
|
|||||||
const auto path_fs = spl_map_to_fs(utf8path);
|
const auto path_fs = spl_map_to_fs(utf8path);
|
||||||
assert(!path_fs.IsNull());
|
assert(!path_fs.IsNull());
|
||||||
|
|
||||||
FileOutputStream fos(path_fs, FileOutputStream::Mode::APPEND_EXISTING);
|
FileOutputStream fos(path_fs, FileOutputStream::Mode::APPEND_OR_CREATE);
|
||||||
|
|
||||||
if (fos.Tell() / (MPD_PATH_MAX + 1) >= playlist_max_length)
|
if (fos.Tell() / (MPD_PATH_MAX + 1) >= playlist_max_length)
|
||||||
throw PlaylistError(PlaylistResult::TOO_LARGE,
|
throw PlaylistError(PlaylistResult::TOO_LARGE,
|
||||||
|
@@ -29,6 +29,29 @@
|
|||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
|
||||||
|
#define GLIBCXX_490 20140422
|
||||||
|
#define GLIBCXX_491 20140716
|
||||||
|
#define GLIBCXX_492 20141030
|
||||||
|
#define GLIBCXX_492_Debian_9 20141220
|
||||||
|
#define GLIBCXX_493 20150626
|
||||||
|
#define GLIBCXX_494 20160803
|
||||||
|
#define GLIBCXX_49X_NDK_r13b 20150123
|
||||||
|
|
||||||
|
/* the big mess attempts to detect whether we're compiling with
|
||||||
|
libstdc++ 4.9.x; __GLIBCXX__ is a date tag and cannot be used to
|
||||||
|
check the major version; and just checking the compiler version
|
||||||
|
isn't enough, because somebody could use an old libstdc++ with
|
||||||
|
clang - SIGH! */
|
||||||
|
#if GCC_OLDER_THAN(5,0) || (defined(__GLIBCXX__) && \
|
||||||
|
(__GLIBCXX__ == GLIBCXX_490 || __GLIBCXX__ == GLIBCXX_491 || \
|
||||||
|
__GLIBCXX__ == GLIBCXX_492 || \
|
||||||
|
__GLIBCXX__ == GLIBCXX_492_Debian_9 || \
|
||||||
|
__GLIBCXX__ == GLIBCXX_493 || \
|
||||||
|
__GLIBCXX__ == GLIBCXX_494 || \
|
||||||
|
__GLIBCXX__ == GLIBCXX_49X_NDK_r13b))
|
||||||
|
#define GLIBCXX_49X
|
||||||
|
#endif
|
||||||
|
|
||||||
gcc_const
|
gcc_const
|
||||||
static enum ack
|
static enum ack
|
||||||
ToAck(PlaylistResult result)
|
ToAck(PlaylistResult result)
|
||||||
@@ -100,13 +123,13 @@ ToAck(std::exception_ptr ep)
|
|||||||
return ACK_ERROR_SYSTEM;
|
return ACK_ERROR_SYSTEM;
|
||||||
} catch (const std::invalid_argument &e) {
|
} catch (const std::invalid_argument &e) {
|
||||||
return ACK_ERROR_ARG;
|
return ACK_ERROR_ARG;
|
||||||
#if defined(__GLIBCXX__) && __GLIBCXX__ < 20151204
|
#ifdef GLIBCXX_49X
|
||||||
} catch (const std::exception &e) {
|
} catch (const std::exception &e) {
|
||||||
#else
|
#else
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
#endif
|
#endif
|
||||||
try {
|
try {
|
||||||
#if defined(__GLIBCXX__) && __GLIBCXX__ < 20151204
|
#ifdef GLIBCXX_49X
|
||||||
/* workaround for g++ 4.x: no overload for
|
/* workaround for g++ 4.x: no overload for
|
||||||
rethrow_exception(exception_ptr) */
|
rethrow_exception(exception_ptr) */
|
||||||
std::rethrow_if_nested(e);
|
std::rethrow_if_nested(e);
|
||||||
|
@@ -30,6 +30,7 @@
|
|||||||
#include "Instance.hxx"
|
#include "Instance.hxx"
|
||||||
#include "Idle.hxx"
|
#include "Idle.hxx"
|
||||||
#include "AudioFormat.hxx"
|
#include "AudioFormat.hxx"
|
||||||
|
#include "util/StringBuffer.hxx"
|
||||||
#include "util/ScopeExit.hxx"
|
#include "util/ScopeExit.hxx"
|
||||||
#include "util/Exception.hxx"
|
#include "util/Exception.hxx"
|
||||||
|
|
||||||
@@ -171,13 +172,9 @@ handle_status(Client &client, gcc_unused Request args, Response &r)
|
|||||||
r.Format("duration: %1.3f\n",
|
r.Format("duration: %1.3f\n",
|
||||||
player_status.total_time.ToDoubleS());
|
player_status.total_time.ToDoubleS());
|
||||||
|
|
||||||
if (player_status.audio_format.IsDefined()) {
|
if (player_status.audio_format.IsDefined())
|
||||||
struct audio_format_string af_string;
|
|
||||||
|
|
||||||
r.Format(COMMAND_STATUS_AUDIO ": %s\n",
|
r.Format(COMMAND_STATUS_AUDIO ": %s\n",
|
||||||
audio_format_to_string(player_status.audio_format,
|
ToString(player_status.audio_format).c_str());
|
||||||
&af_string));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef ENABLE_DATABASE
|
#ifdef ENABLE_DATABASE
|
||||||
|
@@ -62,7 +62,7 @@ Print(Response &r, TagType group, const TagCountMap &m)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static void
|
||||||
stats_visitor_song(SearchStats &stats, const LightSong &song)
|
stats_visitor_song(SearchStats &stats, const LightSong &song)
|
||||||
{
|
{
|
||||||
stats.n_songs++;
|
stats.n_songs++;
|
||||||
@@ -70,8 +70,6 @@ stats_visitor_song(SearchStats &stats, const LightSong &song)
|
|||||||
const auto duration = song.GetDuration();
|
const auto duration = song.GetDuration();
|
||||||
if (!duration.IsNegative())
|
if (!duration.IsNegative())
|
||||||
stats.total_duration += duration;
|
stats.total_duration += duration;
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
@@ -94,7 +92,7 @@ CollectGroupCounts(TagCountMap &map, TagType group, const Tag &tag)
|
|||||||
return found;
|
return found;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static void
|
||||||
GroupCountVisitor(TagCountMap &map, TagType group, const LightSong &song)
|
GroupCountVisitor(TagCountMap &map, TagType group, const LightSong &song)
|
||||||
{
|
{
|
||||||
assert(song.tag != nullptr);
|
assert(song.tag != nullptr);
|
||||||
@@ -103,8 +101,6 @@ GroupCountVisitor(TagCountMap &map, TagType group, const LightSong &song)
|
|||||||
if (!CollectGroupCounts(map, group, tag) && group == TAG_ALBUM_ARTIST)
|
if (!CollectGroupCounts(map, group, tag) && group == TAG_ALBUM_ARTIST)
|
||||||
/* fall back to "Artist" if no "AlbumArtist" was found */
|
/* fall back to "Artist" if no "AlbumArtist" was found */
|
||||||
CollectGroupCounts(map, TAG_ARTIST, tag);
|
CollectGroupCounts(map, TAG_ARTIST, tag);
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
@@ -37,5 +37,10 @@ DatabaseGlobalInit(EventLoop &loop, DatabaseListener &listener,
|
|||||||
throw FormatRuntimeError("No such database plugin: %s",
|
throw FormatRuntimeError("No such database plugin: %s",
|
||||||
plugin_name);
|
plugin_name);
|
||||||
|
|
||||||
|
try {
|
||||||
return plugin->create(loop, listener, block);
|
return plugin->create(loop, listener, block);
|
||||||
|
} catch (...) {
|
||||||
|
std::throw_with_nested(FormatRuntimeError("Failed to initialize database plugin '%s'",
|
||||||
|
plugin_name));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -27,13 +27,12 @@
|
|||||||
|
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
|
||||||
static bool
|
static void
|
||||||
AddSong(const Storage &storage, const char *playlist_path_utf8,
|
AddSong(const Storage &storage, const char *playlist_path_utf8,
|
||||||
const LightSong &song)
|
const LightSong &song)
|
||||||
{
|
{
|
||||||
spl_append_song(playlist_path_utf8,
|
spl_append_song(playlist_path_utf8,
|
||||||
DatabaseDetachSong(storage, song));
|
DatabaseDetachSong(storage, song));
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
@@ -49,16 +49,14 @@ PrintDirectoryURI(Response &r, bool base, const LightDirectory &directory)
|
|||||||
ApplyBaseFlag(directory.GetPath(), base));
|
ApplyBaseFlag(directory.GetPath(), base));
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static void
|
||||||
PrintDirectoryBrief(Response &r, bool base, const LightDirectory &directory)
|
PrintDirectoryBrief(Response &r, bool base, const LightDirectory &directory)
|
||||||
{
|
{
|
||||||
if (!directory.IsRoot())
|
if (!directory.IsRoot())
|
||||||
PrintDirectoryURI(r, base, directory);
|
PrintDirectoryURI(r, base, directory);
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static void
|
||||||
PrintDirectoryFull(Response &r, bool base, const LightDirectory &directory)
|
PrintDirectoryFull(Response &r, bool base, const LightDirectory &directory)
|
||||||
{
|
{
|
||||||
if (!directory.IsRoot()) {
|
if (!directory.IsRoot()) {
|
||||||
@@ -67,8 +65,6 @@ PrintDirectoryFull(Response &r, bool base, const LightDirectory &directory)
|
|||||||
if (directory.mtime > 0)
|
if (directory.mtime > 0)
|
||||||
time_print(r, "Last-Modified", directory.mtime);
|
time_print(r, "Last-Modified", directory.mtime);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@@ -96,7 +92,7 @@ print_playlist_in_directory(Response &r, bool base,
|
|||||||
directory->GetPath(), name_utf8);
|
directory->GetPath(), name_utf8);
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static void
|
||||||
PrintSongBrief(Response &r, Partition &partition,
|
PrintSongBrief(Response &r, Partition &partition,
|
||||||
bool base, const LightSong &song)
|
bool base, const LightSong &song)
|
||||||
{
|
{
|
||||||
@@ -106,11 +102,9 @@ PrintSongBrief(Response &r, Partition &partition,
|
|||||||
/* this song file has an embedded CUE sheet */
|
/* this song file has an embedded CUE sheet */
|
||||||
print_playlist_in_directory(r, base,
|
print_playlist_in_directory(r, base,
|
||||||
song.directory, song.uri);
|
song.directory, song.uri);
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static void
|
||||||
PrintSongFull(Response &r, Partition &partition,
|
PrintSongFull(Response &r, Partition &partition,
|
||||||
bool base, const LightSong &song)
|
bool base, const LightSong &song)
|
||||||
{
|
{
|
||||||
@@ -120,21 +114,18 @@ PrintSongFull(Response &r, Partition &partition,
|
|||||||
/* this song file has an embedded CUE sheet */
|
/* this song file has an embedded CUE sheet */
|
||||||
print_playlist_in_directory(r, base,
|
print_playlist_in_directory(r, base,
|
||||||
song.directory, song.uri);
|
song.directory, song.uri);
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static void
|
||||||
PrintPlaylistBrief(Response &r, bool base,
|
PrintPlaylistBrief(Response &r, bool base,
|
||||||
const PlaylistInfo &playlist,
|
const PlaylistInfo &playlist,
|
||||||
const LightDirectory &directory)
|
const LightDirectory &directory)
|
||||||
{
|
{
|
||||||
print_playlist_in_directory(r, base,
|
print_playlist_in_directory(r, base,
|
||||||
&directory, playlist.name.c_str());
|
&directory, playlist.name.c_str());
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static void
|
||||||
PrintPlaylistFull(Response &r, bool base,
|
PrintPlaylistFull(Response &r, bool base,
|
||||||
const PlaylistInfo &playlist,
|
const PlaylistInfo &playlist,
|
||||||
const LightDirectory &directory)
|
const LightDirectory &directory)
|
||||||
@@ -144,8 +135,6 @@ PrintPlaylistFull(Response &r, bool base,
|
|||||||
|
|
||||||
if (playlist.mtime > 0)
|
if (playlist.mtime > 0)
|
||||||
time_print(r, "Last-Modified", playlist.mtime);
|
time_print(r, "Last-Modified", playlist.mtime);
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
@@ -191,15 +180,13 @@ db_selection_print(Response &r, Partition &partition,
|
|||||||
0, std::numeric_limits<int>::max());
|
0, std::numeric_limits<int>::max());
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static void
|
||||||
PrintSongURIVisitor(Response &r, Partition &partition, const LightSong &song)
|
PrintSongURIVisitor(Response &r, Partition &partition, const LightSong &song)
|
||||||
{
|
{
|
||||||
song_print_uri(r, partition, song);
|
song_print_uri(r, partition, song);
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static void
|
||||||
PrintUniqueTag(Response &r, TagType tag_type,
|
PrintUniqueTag(Response &r, TagType tag_type,
|
||||||
const Tag &tag)
|
const Tag &tag)
|
||||||
{
|
{
|
||||||
@@ -211,8 +198,6 @@ PrintUniqueTag(Response &r, TagType tag_type,
|
|||||||
if (item.type != tag_type)
|
if (item.type != tag_type)
|
||||||
r.Format("%s: %s\n",
|
r.Format("%s: %s\n",
|
||||||
tag_item_names[item.type], item.value);
|
tag_item_names[item.type], item.value);
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
@@ -27,14 +27,13 @@
|
|||||||
|
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
|
||||||
static bool
|
static void
|
||||||
AddToQueue(Partition &partition, const LightSong &song)
|
AddToQueue(Partition &partition, const LightSong &song)
|
||||||
{
|
{
|
||||||
const Storage &storage = *partition.instance.storage;
|
const Storage &storage = *partition.instance.storage;
|
||||||
partition.playlist.AppendSong(partition.pc,
|
partition.playlist.AppendSong(partition.pc,
|
||||||
DatabaseDetachSong(storage,
|
DatabaseDetachSong(storage,
|
||||||
song));
|
song));
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
@@ -67,15 +67,13 @@ StatsVisitTag(DatabaseStats &stats, StringSet &artists, StringSet &albums,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static void
|
||||||
StatsVisitSong(DatabaseStats &stats, StringSet &artists, StringSet &albums,
|
StatsVisitSong(DatabaseStats &stats, StringSet &artists, StringSet &albums,
|
||||||
const LightSong &song)
|
const LightSong &song)
|
||||||
{
|
{
|
||||||
++stats.song_count;
|
++stats.song_count;
|
||||||
|
|
||||||
StatsVisitTag(stats, artists, albums, *song.tag);
|
StatsVisitTag(stats, artists, albums, *song.tag);
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
DatabaseStats
|
DatabaseStats
|
||||||
|
@@ -34,6 +34,7 @@
|
|||||||
#include "tag/TagBuilder.hxx"
|
#include "tag/TagBuilder.hxx"
|
||||||
#include "tag/Tag.hxx"
|
#include "tag/Tag.hxx"
|
||||||
#include "util/ScopeExit.hxx"
|
#include "util/ScopeExit.hxx"
|
||||||
|
#include "util/RuntimeError.hxx"
|
||||||
#include "protocol/Ack.hxx"
|
#include "protocol/Ack.hxx"
|
||||||
#include "event/SocketMonitor.hxx"
|
#include "event/SocketMonitor.hxx"
|
||||||
#include "event/IdleMonitor.hxx"
|
#include "event/IdleMonitor.hxx"
|
||||||
@@ -46,7 +47,7 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <list>
|
#include <list>
|
||||||
|
|
||||||
class LibmpdclientError final : std::runtime_error {
|
class LibmpdclientError final : public std::runtime_error {
|
||||||
enum mpd_error code;
|
enum mpd_error code;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
@@ -108,8 +109,8 @@ public:
|
|||||||
static Database *Create(EventLoop &loop, DatabaseListener &listener,
|
static Database *Create(EventLoop &loop, DatabaseListener &listener,
|
||||||
const ConfigBlock &block);
|
const ConfigBlock &block);
|
||||||
|
|
||||||
virtual void Open() override;
|
void Open() override;
|
||||||
virtual void Close() override;
|
void Close() override;
|
||||||
const LightSong *GetSong(const char *uri_utf8) const override;
|
const LightSong *GetSong(const char *uri_utf8) const override;
|
||||||
void ReturnSong(const LightSong *song) const override;
|
void ReturnSong(const LightSong *song) const override;
|
||||||
|
|
||||||
@@ -126,7 +127,7 @@ public:
|
|||||||
|
|
||||||
unsigned Update(const char *uri_utf8, bool discard) override;
|
unsigned Update(const char *uri_utf8, bool discard) override;
|
||||||
|
|
||||||
virtual time_t GetUpdateStamp() const override {
|
time_t GetUpdateStamp() const override {
|
||||||
return update_stamp;
|
return update_stamp;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -138,10 +139,10 @@ private:
|
|||||||
void Disconnect();
|
void Disconnect();
|
||||||
|
|
||||||
/* virtual methods from SocketMonitor */
|
/* virtual methods from SocketMonitor */
|
||||||
virtual bool OnSocketReady(unsigned flags) override;
|
bool OnSocketReady(unsigned flags) override;
|
||||||
|
|
||||||
/* virtual methods from IdleMonitor */
|
/* virtual methods from IdleMonitor */
|
||||||
virtual void OnIdle() override;
|
void OnIdle() override;
|
||||||
};
|
};
|
||||||
|
|
||||||
static constexpr struct {
|
static constexpr struct {
|
||||||
@@ -345,9 +346,15 @@ ProxyDatabase::Create(EventLoop &loop, DatabaseListener &listener,
|
|||||||
void
|
void
|
||||||
ProxyDatabase::Open()
|
ProxyDatabase::Open()
|
||||||
{
|
{
|
||||||
Connect();
|
|
||||||
|
|
||||||
update_stamp = 0;
|
update_stamp = 0;
|
||||||
|
|
||||||
|
try {
|
||||||
|
Connect();
|
||||||
|
} catch (const std::runtime_error &error) {
|
||||||
|
/* this error is non-fatal, because this plugin will
|
||||||
|
attempt to reconnect again automatically */
|
||||||
|
LogError(error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
@@ -371,7 +378,10 @@ ProxyDatabase::Connect()
|
|||||||
mpd_connection_free(connection);
|
mpd_connection_free(connection);
|
||||||
connection = nullptr;
|
connection = nullptr;
|
||||||
|
|
||||||
throw;
|
std::throw_with_nested(host.empty()
|
||||||
|
? std::runtime_error("Failed to connect to remote MPD")
|
||||||
|
: FormatRuntimeError("Failed to connect to remote MPD '%s'",
|
||||||
|
host.c_str()));
|
||||||
}
|
}
|
||||||
|
|
||||||
#if LIBMPDCLIENT_CHECK_VERSION(2, 10, 0)
|
#if LIBMPDCLIENT_CHECK_VERSION(2, 10, 0)
|
||||||
|
@@ -25,6 +25,13 @@
|
|||||||
#include "db/Interface.hxx"
|
#include "db/Interface.hxx"
|
||||||
#include "fs/Traits.hxx"
|
#include "fs/Traits.hxx"
|
||||||
|
|
||||||
|
#ifdef _LIBCPP_VERSION
|
||||||
|
/* workaround for "error: incomplete type 'PlaylistInfo' used in type
|
||||||
|
trait expression" with libc++ version 3900 (from Android NDK
|
||||||
|
r13b) */
|
||||||
|
#include "db/PlaylistInfo.hxx"
|
||||||
|
#endif
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
struct PrefixedLightDirectory : LightDirectory {
|
struct PrefixedLightDirectory : LightDirectory {
|
||||||
|
@@ -108,8 +108,8 @@ public:
|
|||||||
bool Unmount(const char *uri);
|
bool Unmount(const char *uri);
|
||||||
|
|
||||||
/* virtual methods from class Database */
|
/* virtual methods from class Database */
|
||||||
virtual void Open() override;
|
void Open() override;
|
||||||
virtual void Close() override;
|
void Close() override;
|
||||||
|
|
||||||
const LightSong *GetSong(const char *uri_utf8) const override;
|
const LightSong *GetSong(const char *uri_utf8) const override;
|
||||||
void ReturnSong(const LightSong *song) const override;
|
void ReturnSong(const LightSong *song) const override;
|
||||||
@@ -125,7 +125,7 @@ public:
|
|||||||
|
|
||||||
DatabaseStats GetStats(const DatabaseSelection &selection) const override;
|
DatabaseStats GetStats(const DatabaseSelection &selection) const override;
|
||||||
|
|
||||||
virtual time_t GetUpdateStamp() const override {
|
time_t GetUpdateStamp() const override {
|
||||||
return mtime;
|
return mtime;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -180,7 +180,7 @@ UpnpDatabase::ReturnSong(const LightSong *_song) const
|
|||||||
const LightSong *
|
const LightSong *
|
||||||
UpnpDatabase::GetSong(const char *uri) const
|
UpnpDatabase::GetSong(const char *uri) const
|
||||||
{
|
{
|
||||||
auto vpath = stringToTokens(uri, "/", true);
|
auto vpath = stringToTokens(uri, '/');
|
||||||
if (vpath.size() < 2)
|
if (vpath.size() < 2)
|
||||||
throw DatabaseError(DatabaseErrorCode::NOT_FOUND,
|
throw DatabaseError(DatabaseErrorCode::NOT_FOUND,
|
||||||
"No such song");
|
"No such song");
|
||||||
@@ -577,7 +577,7 @@ UpnpDatabase::Visit(const DatabaseSelection &selection,
|
|||||||
VisitSong visit_song,
|
VisitSong visit_song,
|
||||||
VisitPlaylist visit_playlist) const
|
VisitPlaylist visit_playlist) const
|
||||||
{
|
{
|
||||||
auto vpath = stringToTokens(selection.uri, "/", true);
|
auto vpath = stringToTokens(selection.uri, '/');
|
||||||
if (vpath.empty()) {
|
if (vpath.empty()) {
|
||||||
for (const auto &server : discovery->GetDirectories()) {
|
for (const auto &server : discovery->GetDirectories()) {
|
||||||
if (visit_directory) {
|
if (visit_directory) {
|
||||||
|
@@ -32,6 +32,7 @@
|
|||||||
#include "Log.hxx"
|
#include "Log.hxx"
|
||||||
#include "input/InputStream.hxx"
|
#include "input/InputStream.hxx"
|
||||||
#include "util/ConstBuffer.hxx"
|
#include "util/ConstBuffer.hxx"
|
||||||
|
#include "util/StringBuffer.hxx"
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
@@ -246,15 +247,13 @@ void
|
|||||||
DecoderBridge::Ready(const AudioFormat audio_format,
|
DecoderBridge::Ready(const AudioFormat audio_format,
|
||||||
bool seekable, SignedSongTime duration)
|
bool seekable, SignedSongTime duration)
|
||||||
{
|
{
|
||||||
struct audio_format_string af_string;
|
|
||||||
|
|
||||||
assert(convert == nullptr);
|
assert(convert == nullptr);
|
||||||
assert(stream_tag == nullptr);
|
assert(stream_tag == nullptr);
|
||||||
assert(decoder_tag == nullptr);
|
assert(decoder_tag == nullptr);
|
||||||
assert(!seeking);
|
assert(!seeking);
|
||||||
|
|
||||||
FormatDebug(decoder_domain, "audio_format=%s, seekable=%s",
|
FormatDebug(decoder_domain, "audio_format=%s, seekable=%s",
|
||||||
audio_format_to_string(audio_format, &af_string),
|
ToString(audio_format).c_str(),
|
||||||
seekable ? "true" : "false");
|
seekable ? "true" : "false");
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -264,8 +263,7 @@ DecoderBridge::Ready(const AudioFormat audio_format,
|
|||||||
|
|
||||||
if (dc.in_audio_format != dc.out_audio_format) {
|
if (dc.in_audio_format != dc.out_audio_format) {
|
||||||
FormatDebug(decoder_domain, "converting to %s",
|
FormatDebug(decoder_domain, "converting to %s",
|
||||||
audio_format_to_string(dc.out_audio_format,
|
ToString(dc.out_audio_format).c_str());
|
||||||
&af_string));
|
|
||||||
|
|
||||||
convert = new PcmConvert();
|
convert = new PcmConvert();
|
||||||
|
|
||||||
@@ -317,6 +315,9 @@ DecoderBridge::CommandFinished()
|
|||||||
|
|
||||||
dc.pipe->Clear(*dc.buffer);
|
dc.pipe->Clear(*dc.buffer);
|
||||||
|
|
||||||
|
if (convert != nullptr)
|
||||||
|
convert->Reset();
|
||||||
|
|
||||||
timestamp = dc.seek_time.ToDoubleS();
|
timestamp = dc.seek_time.ToDoubleS();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -235,7 +235,7 @@ decoder_run_stream_fallback(DecoderBridge &bridge, InputStream &is)
|
|||||||
{
|
{
|
||||||
const struct DecoderPlugin *plugin;
|
const struct DecoderPlugin *plugin;
|
||||||
|
|
||||||
#ifdef HAVE_FFMPEG
|
#ifdef ENABLE_FFMPEG
|
||||||
plugin = decoder_plugin_from_name("ffmpeg");
|
plugin = decoder_plugin_from_name("ffmpeg");
|
||||||
#else
|
#else
|
||||||
plugin = decoder_plugin_from_name("mad");
|
plugin = decoder_plugin_from_name("mad");
|
||||||
|
@@ -56,6 +56,11 @@ extern "C" {
|
|||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Muxer options to be passed to avformat_open_input().
|
||||||
|
*/
|
||||||
|
static AVDictionary *avformat_options = nullptr;
|
||||||
|
|
||||||
static AVFormatContext *
|
static AVFormatContext *
|
||||||
FfmpegOpenInput(AVIOContext *pb,
|
FfmpegOpenInput(AVIOContext *pb,
|
||||||
const char *filename,
|
const char *filename,
|
||||||
@@ -67,7 +72,11 @@ FfmpegOpenInput(AVIOContext *pb,
|
|||||||
|
|
||||||
context->pb = pb;
|
context->pb = pb;
|
||||||
|
|
||||||
int err = avformat_open_input(&context, filename, fmt, nullptr);
|
AVDictionary *options = nullptr;
|
||||||
|
AtScopeExit(&options) { av_dict_free(&options); };
|
||||||
|
av_dict_copy(&options, avformat_options, 0);
|
||||||
|
|
||||||
|
int err = avformat_open_input(&context, filename, fmt, &options);
|
||||||
if (err < 0)
|
if (err < 0)
|
||||||
throw MakeFfmpegError(err, "avformat_open_input() failed");
|
throw MakeFfmpegError(err, "avformat_open_input() failed");
|
||||||
|
|
||||||
@@ -75,12 +84,30 @@ FfmpegOpenInput(AVIOContext *pb,
|
|||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
ffmpeg_init(gcc_unused const ConfigBlock &block)
|
ffmpeg_init(const ConfigBlock &block)
|
||||||
{
|
{
|
||||||
FfmpegInit();
|
FfmpegInit();
|
||||||
|
|
||||||
|
static constexpr const char *option_names[] = {
|
||||||
|
"probesize",
|
||||||
|
"analyzeduration",
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const char *name : option_names) {
|
||||||
|
const char *value = block.GetBlockValue(name);
|
||||||
|
if (value != nullptr)
|
||||||
|
av_dict_set(&avformat_options, name, value, 0);
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
ffmpeg_finish()
|
||||||
|
{
|
||||||
|
av_dict_free(&avformat_options);
|
||||||
|
}
|
||||||
|
|
||||||
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 25, 0) /* FFmpeg 3.1 */
|
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 25, 0) /* FFmpeg 3.1 */
|
||||||
|
|
||||||
gcc_pure
|
gcc_pure
|
||||||
@@ -967,7 +994,7 @@ static const char *const ffmpeg_mime_types[] = {
|
|||||||
const struct DecoderPlugin ffmpeg_decoder_plugin = {
|
const struct DecoderPlugin ffmpeg_decoder_plugin = {
|
||||||
"ffmpeg",
|
"ffmpeg",
|
||||||
ffmpeg_init,
|
ffmpeg_init,
|
||||||
nullptr,
|
ffmpeg_finish,
|
||||||
ffmpeg_decode,
|
ffmpeg_decode,
|
||||||
nullptr,
|
nullptr,
|
||||||
nullptr,
|
nullptr,
|
||||||
|
@@ -207,6 +207,15 @@ mpcdec_decode(DecoderClient &client, InputStream &is)
|
|||||||
if (frame.bits == -1)
|
if (frame.bits == -1)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
if (frame.samples <= 0) {
|
||||||
|
/* empty frame - this has been observed to
|
||||||
|
happen spuriously after seeking; skip this
|
||||||
|
obscure frame, and hope libmpcdec
|
||||||
|
recovers */
|
||||||
|
cmd = client.GetCommand();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
mpc_uint32_t ret = frame.samples;
|
mpc_uint32_t ret = frame.samples;
|
||||||
ret *= info.channels;
|
ret *= info.channels;
|
||||||
|
|
||||||
|
@@ -50,6 +50,10 @@
|
|||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
|
||||||
|
#ifdef HAVE_SIDPLAYFP
|
||||||
|
#define LIBSIDPLAYFP_VERSION GCC_MAKE_VERSION(LIBSIDPLAYFP_VERSION_MAJ, LIBSIDPLAYFP_VERSION_MIN, LIBSIDPLAYFP_VERSION_LEV)
|
||||||
|
#endif
|
||||||
|
|
||||||
#define SUBTUNE_PREFIX "tune_"
|
#define SUBTUNE_PREFIX "tune_"
|
||||||
|
|
||||||
static constexpr Domain sidplay_domain("sidplay");
|
static constexpr Domain sidplay_domain("sidplay");
|
||||||
@@ -285,7 +289,11 @@ sidplay_file_decode(DecoderClient &client, Path path_fs)
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef HAVE_SIDPLAYFP
|
#ifdef HAVE_SIDPLAYFP
|
||||||
|
#if LIBSIDPLAYFP_VERSION >= GCC_MAKE_VERSION(1,8,0)
|
||||||
const bool stereo = tune.getInfo()->sidChips() >= 2;
|
const bool stereo = tune.getInfo()->sidChips() >= 2;
|
||||||
|
#else
|
||||||
|
const bool stereo = tune.getInfo()->isStereo();
|
||||||
|
#endif
|
||||||
#else
|
#else
|
||||||
const bool stereo = tune.isStereo();
|
const bool stereo = tune.isStereo();
|
||||||
#endif
|
#endif
|
||||||
|
@@ -34,6 +34,8 @@
|
|||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
|
#include <cstdlib>
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
|
||||||
#define ERRORLEN 80
|
#define ERRORLEN 80
|
||||||
|
@@ -79,7 +79,7 @@ private:
|
|||||||
void
|
void
|
||||||
BlockingCall(EventLoop &loop, std::function<void()> &&f)
|
BlockingCall(EventLoop &loop, std::function<void()> &&f)
|
||||||
{
|
{
|
||||||
if (loop.IsInside()) {
|
if (loop.IsInsideOrNull()) {
|
||||||
/* we're already inside the loop - we can simply call
|
/* we're already inside the loop - we can simply call
|
||||||
the function */
|
the function */
|
||||||
f();
|
f();
|
||||||
|
@@ -222,8 +222,9 @@ EventLoop::Run()
|
|||||||
#ifndef NDEBUG
|
#ifndef NDEBUG
|
||||||
assert(busy);
|
assert(busy);
|
||||||
assert(thread.IsInside());
|
assert(thread.IsInside());
|
||||||
thread = ThreadId::Null();
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
thread = ThreadId::Null();
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
@@ -212,12 +212,16 @@ public:
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifndef NDEBUG
|
/**
|
||||||
|
* Like IsInside(), but also returns true if the thread has
|
||||||
|
* already ended (or was not started yet). This is useful for
|
||||||
|
* code which may run during startup or shutdown, when events
|
||||||
|
* are not yet/anymore handled.
|
||||||
|
*/
|
||||||
gcc_pure
|
gcc_pure
|
||||||
bool IsInsideOrNull() const {
|
bool IsInsideOrNull() const {
|
||||||
return thread.IsNull() || thread.IsInside();
|
return thread.IsNull() || thread.IsInside();
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif /* MAIN_NOTIFY_H */
|
#endif /* MAIN_NOTIFY_H */
|
||||||
|
@@ -28,12 +28,18 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
MultiSocketMonitor::MultiSocketMonitor(EventLoop &_loop)
|
MultiSocketMonitor::MultiSocketMonitor(EventLoop &_loop)
|
||||||
:IdleMonitor(_loop), TimeoutMonitor(_loop), ready(false) {
|
:IdleMonitor(_loop), TimeoutMonitor(_loop) {
|
||||||
}
|
}
|
||||||
|
|
||||||
MultiSocketMonitor::~MultiSocketMonitor()
|
void
|
||||||
|
MultiSocketMonitor::Reset()
|
||||||
{
|
{
|
||||||
// TODO
|
assert(GetEventLoop().IsInsideOrNull());
|
||||||
|
|
||||||
|
fds.clear();
|
||||||
|
IdleMonitor::Cancel();
|
||||||
|
TimeoutMonitor::Cancel();
|
||||||
|
ready = refresh = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
@@ -96,7 +96,19 @@ class MultiSocketMonitor : IdleMonitor, TimeoutMonitor
|
|||||||
|
|
||||||
friend class SingleFD;
|
friend class SingleFD;
|
||||||
|
|
||||||
bool ready, refresh;
|
/**
|
||||||
|
* DispatchSockets() should be called.
|
||||||
|
*/
|
||||||
|
bool ready = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PrepareSockets() should be called.
|
||||||
|
*
|
||||||
|
* Note that this doesn't need to be initialized by the
|
||||||
|
* constructor; this class is activated with the first
|
||||||
|
* InvalidateSockets() call, which initializes this flag.
|
||||||
|
*/
|
||||||
|
bool refresh;
|
||||||
|
|
||||||
std::forward_list<SingleFD> fds;
|
std::forward_list<SingleFD> fds;
|
||||||
|
|
||||||
@@ -107,11 +119,26 @@ public:
|
|||||||
static constexpr unsigned HANGUP = SocketMonitor::HANGUP;
|
static constexpr unsigned HANGUP = SocketMonitor::HANGUP;
|
||||||
|
|
||||||
MultiSocketMonitor(EventLoop &_loop);
|
MultiSocketMonitor(EventLoop &_loop);
|
||||||
~MultiSocketMonitor();
|
|
||||||
|
|
||||||
using IdleMonitor::GetEventLoop;
|
using IdleMonitor::GetEventLoop;
|
||||||
|
|
||||||
public:
|
/**
|
||||||
|
* Clear the socket list and disable all #EventLoop
|
||||||
|
* registrations. Run this in the #EventLoop thread before
|
||||||
|
* destroying this object.
|
||||||
|
*
|
||||||
|
* Later, this object can be reused and reactivated by calling
|
||||||
|
* InvalidateSockets().
|
||||||
|
*
|
||||||
|
* Note that this class doesn't have a destructor which calls
|
||||||
|
* this method, because this would be racy and thus pointless:
|
||||||
|
* at the time ~MultiSocketMonitor() is called, our virtual
|
||||||
|
* methods have been morphed to be pure again, and in the
|
||||||
|
* meantime the #EventLoop thread could invoke those pure
|
||||||
|
* methods.
|
||||||
|
*/
|
||||||
|
void Reset();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Invalidate the socket list. A call to PrepareSockets() is
|
* Invalidate the socket list. A call to PrepareSockets() is
|
||||||
* scheduled which will then update the list.
|
* scheduled which will then update the list.
|
||||||
@@ -121,12 +148,19 @@ public:
|
|||||||
IdleMonitor::Schedule();
|
IdleMonitor::Schedule();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add one socket to the list of monitored sockets.
|
||||||
|
*
|
||||||
|
* May only be called from PrepareSockets().
|
||||||
|
*/
|
||||||
void AddSocket(int fd, unsigned events) {
|
void AddSocket(int fd, unsigned events) {
|
||||||
fds.emplace_front(*this, fd, events);
|
fds.emplace_front(*this, fd, events);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove all sockets.
|
* Remove all sockets.
|
||||||
|
*
|
||||||
|
* May only be called from PrepareSockets().
|
||||||
*/
|
*/
|
||||||
void ClearSocketList();
|
void ClearSocketList();
|
||||||
|
|
||||||
@@ -135,6 +169,8 @@ public:
|
|||||||
* each one; its return value is the events bit mask. A
|
* each one; its return value is the events bit mask. A
|
||||||
* return value of 0 means the socket will be removed from the
|
* return value of 0 means the socket will be removed from the
|
||||||
* list.
|
* list.
|
||||||
|
*
|
||||||
|
* May only be called from PrepareSockets().
|
||||||
*/
|
*/
|
||||||
template<typename E>
|
template<typename E>
|
||||||
void UpdateSocketList(E &&e) {
|
void UpdateSocketList(E &&e) {
|
||||||
@@ -157,15 +193,26 @@ public:
|
|||||||
/**
|
/**
|
||||||
* Replace the socket list with the given file descriptors.
|
* Replace the socket list with the given file descriptors.
|
||||||
* The given pollfd array will be modified by this method.
|
* The given pollfd array will be modified by this method.
|
||||||
|
*
|
||||||
|
* May only be called from PrepareSockets().
|
||||||
*/
|
*/
|
||||||
void ReplaceSocketList(pollfd *pfds, unsigned n);
|
void ReplaceSocketList(pollfd *pfds, unsigned n);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
/**
|
/**
|
||||||
|
* Override this method and update the socket registrations.
|
||||||
|
* To do that, call AddSocket(), ClearSocketList(),
|
||||||
|
* UpdateSocketList() and ReplaceSocketList().
|
||||||
|
*
|
||||||
* @return timeout or a negative value for no timeout
|
* @return timeout or a negative value for no timeout
|
||||||
*/
|
*/
|
||||||
virtual std::chrono::steady_clock::duration PrepareSockets() = 0;
|
virtual std::chrono::steady_clock::duration PrepareSockets() = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* At least one socket is ready or the timeout has expired.
|
||||||
|
* This method should be used to perform I/O.
|
||||||
|
*/
|
||||||
virtual void DispatchSockets() = 0;
|
virtual void DispatchSockets() = 0;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
@@ -52,6 +52,12 @@ public:
|
|||||||
return out_audio_format;
|
return out_audio_format;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset the filter's state, e.g. drop/flush buffers.
|
||||||
|
*/
|
||||||
|
virtual void Reset() {
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Filters a block of PCM data.
|
* Filters a block of PCM data.
|
||||||
*
|
*
|
||||||
@@ -60,7 +66,7 @@ public:
|
|||||||
* @param src the input buffer
|
* @param src the input buffer
|
||||||
* @return the destination buffer on success (will be
|
* @return the destination buffer on success (will be
|
||||||
* invalidated by deleting this object or the next FilterPCM()
|
* invalidated by deleting this object or the next FilterPCM()
|
||||||
* call)
|
* or Reset() call)
|
||||||
*/
|
*/
|
||||||
virtual ConstBuffer<void> FilterPCM(ConstBuffer<void> src) = 0;
|
virtual ConstBuffer<void> FilterPCM(ConstBuffer<void> src) = 0;
|
||||||
};
|
};
|
||||||
|
@@ -48,6 +48,13 @@ public:
|
|||||||
:Filter(_filter->GetOutAudioFormat()),
|
:Filter(_filter->GetOutAudioFormat()),
|
||||||
filter(std::move(_filter)), convert(std::move(_convert)) {}
|
filter(std::move(_filter)), convert(std::move(_convert)) {}
|
||||||
|
|
||||||
|
void Reset() override {
|
||||||
|
filter->Reset();
|
||||||
|
|
||||||
|
if (convert)
|
||||||
|
convert->Reset();
|
||||||
|
}
|
||||||
|
|
||||||
ConstBuffer<void> FilterPCM(ConstBuffer<void> src) override;
|
ConstBuffer<void> FilterPCM(ConstBuffer<void> src) override;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -24,6 +24,7 @@
|
|||||||
#include "filter/FilterRegistry.hxx"
|
#include "filter/FilterRegistry.hxx"
|
||||||
#include "AudioFormat.hxx"
|
#include "AudioFormat.hxx"
|
||||||
#include "util/ConstBuffer.hxx"
|
#include "util/ConstBuffer.hxx"
|
||||||
|
#include "util/StringBuffer.hxx"
|
||||||
#include "util/RuntimeError.hxx"
|
#include "util/RuntimeError.hxx"
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
@@ -61,6 +62,7 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* virtual methods from class Filter */
|
/* virtual methods from class Filter */
|
||||||
|
void Reset() override;
|
||||||
ConstBuffer<void> FilterPCM(ConstBuffer<void> src) override;
|
ConstBuffer<void> FilterPCM(ConstBuffer<void> src) override;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -107,10 +109,9 @@ PreparedChainFilter::Child::Open(const AudioFormat &prev_audio_format)
|
|||||||
if (conv_audio_format != prev_audio_format) {
|
if (conv_audio_format != prev_audio_format) {
|
||||||
delete new_filter;
|
delete new_filter;
|
||||||
|
|
||||||
struct audio_format_string s;
|
|
||||||
throw FormatRuntimeError("Audio format not supported by filter '%s': %s",
|
throw FormatRuntimeError("Audio format not supported by filter '%s': %s",
|
||||||
name,
|
name,
|
||||||
audio_format_to_string(prev_audio_format, &s));
|
ToString(prev_audio_format).c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
return new_filter;
|
return new_filter;
|
||||||
@@ -130,6 +131,13 @@ PreparedChainFilter::Open(AudioFormat &in_audio_format)
|
|||||||
return chain.release();
|
return chain.release();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
ChainFilter::Reset()
|
||||||
|
{
|
||||||
|
for (auto &child : children)
|
||||||
|
child.filter->Reset();
|
||||||
|
}
|
||||||
|
|
||||||
ConstBuffer<void>
|
ConstBuffer<void>
|
||||||
ChainFilter::FilterPCM(ConstBuffer<void> src)
|
ChainFilter::FilterPCM(ConstBuffer<void> src)
|
||||||
{
|
{
|
||||||
|
@@ -52,6 +52,10 @@ public:
|
|||||||
|
|
||||||
void Set(const AudioFormat &_out_audio_format);
|
void Set(const AudioFormat &_out_audio_format);
|
||||||
|
|
||||||
|
void Reset() override {
|
||||||
|
state.Reset();
|
||||||
|
}
|
||||||
|
|
||||||
ConstBuffer<void> FilterPCM(ConstBuffer<void> src) override;
|
ConstBuffer<void> FilterPCM(ConstBuffer<void> src) override;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -28,11 +28,11 @@
|
|||||||
#include "AlsaInputPlugin.hxx"
|
#include "AlsaInputPlugin.hxx"
|
||||||
#include "../InputPlugin.hxx"
|
#include "../InputPlugin.hxx"
|
||||||
#include "../AsyncInputStream.hxx"
|
#include "../AsyncInputStream.hxx"
|
||||||
|
#include "event/Call.hxx"
|
||||||
#include "util/Domain.hxx"
|
#include "util/Domain.hxx"
|
||||||
#include "util/RuntimeError.hxx"
|
#include "util/RuntimeError.hxx"
|
||||||
#include "util/StringCompare.hxx"
|
#include "util/StringCompare.hxx"
|
||||||
#include "util/ReusableArray.hxx"
|
#include "util/ReusableArray.hxx"
|
||||||
#include "util/ScopeExit.hxx"
|
|
||||||
|
|
||||||
#include "Log.hxx"
|
#include "Log.hxx"
|
||||||
#include "event/MultiSocketMonitor.hxx"
|
#include "event/MultiSocketMonitor.hxx"
|
||||||
@@ -56,31 +56,30 @@ static constexpr unsigned int default_rate = 44100; // cd quality
|
|||||||
static constexpr size_t ALSA_MAX_BUFFERED = default_rate * default_channels * 2;
|
static constexpr size_t ALSA_MAX_BUFFERED = default_rate * default_channels * 2;
|
||||||
static constexpr size_t ALSA_RESUME_AT = ALSA_MAX_BUFFERED / 2;
|
static constexpr size_t ALSA_RESUME_AT = ALSA_MAX_BUFFERED / 2;
|
||||||
|
|
||||||
/**
|
|
||||||
* This value should be the same as the read buffer size defined in
|
|
||||||
* PcmDecoderPlugin.cxx:pcm_stream_decode().
|
|
||||||
* We use it to calculate how many audio frames to buffer in the alsa driver
|
|
||||||
* before reading from the device. snd_pcm_readi() blocks until that many
|
|
||||||
* frames are ready.
|
|
||||||
*/
|
|
||||||
static constexpr size_t read_buffer_size = 4096;
|
|
||||||
|
|
||||||
class AlsaInputStream final
|
class AlsaInputStream final
|
||||||
: public AsyncInputStream,
|
: public AsyncInputStream,
|
||||||
MultiSocketMonitor, DeferredMonitor {
|
MultiSocketMonitor, DeferredMonitor {
|
||||||
snd_pcm_t *capture_handle;
|
|
||||||
size_t frame_size;
|
/**
|
||||||
|
* The configured name of the ALSA device.
|
||||||
|
*/
|
||||||
|
const std::string device;
|
||||||
|
|
||||||
|
snd_pcm_t *const capture_handle;
|
||||||
|
const size_t frame_size;
|
||||||
|
|
||||||
ReusableArray<pollfd> pfd_buffer;
|
ReusableArray<pollfd> pfd_buffer;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
AlsaInputStream(EventLoop &loop,
|
AlsaInputStream(EventLoop &loop,
|
||||||
const char *_uri, Mutex &_mutex, Cond &_cond,
|
const char *_uri, Mutex &_mutex, Cond &_cond,
|
||||||
|
const char *_device,
|
||||||
snd_pcm_t *_handle, int _frame_size)
|
snd_pcm_t *_handle, int _frame_size)
|
||||||
:AsyncInputStream(_uri, _mutex, _cond,
|
:AsyncInputStream(_uri, _mutex, _cond,
|
||||||
ALSA_MAX_BUFFERED, ALSA_RESUME_AT),
|
ALSA_MAX_BUFFERED, ALSA_RESUME_AT),
|
||||||
MultiSocketMonitor(loop),
|
MultiSocketMonitor(loop),
|
||||||
DeferredMonitor(loop),
|
DeferredMonitor(loop),
|
||||||
|
device(_device),
|
||||||
capture_handle(_handle),
|
capture_handle(_handle),
|
||||||
frame_size(_frame_size)
|
frame_size(_frame_size)
|
||||||
{
|
{
|
||||||
@@ -99,6 +98,11 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
~AlsaInputStream() {
|
~AlsaInputStream() {
|
||||||
|
BlockingCall(MultiSocketMonitor::GetEventLoop(), [this](){
|
||||||
|
MultiSocketMonitor::Reset();
|
||||||
|
DeferredMonitor::Cancel();
|
||||||
|
});
|
||||||
|
|
||||||
snd_pcm_close(capture_handle);
|
snd_pcm_close(capture_handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -162,7 +166,7 @@ AlsaInputStream::Create(const char *uri, Mutex &mutex, Cond &cond)
|
|||||||
int frame_size = snd_pcm_format_width(format) / 8 * channels;
|
int frame_size = snd_pcm_format_width(format) / 8 * channels;
|
||||||
return new AlsaInputStream(io_thread_get(),
|
return new AlsaInputStream(io_thread_get(),
|
||||||
uri, mutex, cond,
|
uri, mutex, cond,
|
||||||
handle, frame_size);
|
device, handle, frame_size);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::chrono::steady_clock::duration
|
std::chrono::steady_clock::duration
|
||||||
@@ -174,7 +178,7 @@ AlsaInputStream::PrepareSockets()
|
|||||||
}
|
}
|
||||||
|
|
||||||
int count = snd_pcm_poll_descriptors_count(capture_handle);
|
int count = snd_pcm_poll_descriptors_count(capture_handle);
|
||||||
if (count < 0) {
|
if (count <= 0) {
|
||||||
ClearSocketList();
|
ClearSocketList();
|
||||||
return std::chrono::steady_clock::duration(-1);
|
return std::chrono::steady_clock::duration(-1);
|
||||||
}
|
}
|
||||||
@@ -205,6 +209,9 @@ AlsaInputStream::DispatchSockets()
|
|||||||
snd_pcm_sframes_t n_frames;
|
snd_pcm_sframes_t n_frames;
|
||||||
while ((n_frames = snd_pcm_readi(capture_handle,
|
while ((n_frames = snd_pcm_readi(capture_handle,
|
||||||
w.data, w_frames)) < 0) {
|
w.data, w_frames)) < 0) {
|
||||||
|
if (n_frames == -EAGAIN)
|
||||||
|
return;
|
||||||
|
|
||||||
if (Recover(n_frames) < 0) {
|
if (Recover(n_frames) < 0) {
|
||||||
postponed_exception = std::make_exception_ptr(std::runtime_error("PCM error - stream aborted"));
|
postponed_exception = std::make_exception_ptr(std::runtime_error("PCM error - stream aborted"));
|
||||||
cond.broadcast();
|
cond.broadcast();
|
||||||
@@ -221,20 +228,51 @@ AlsaInputStream::Recover(int err)
|
|||||||
{
|
{
|
||||||
switch(err) {
|
switch(err) {
|
||||||
case -EPIPE:
|
case -EPIPE:
|
||||||
LogDebug(alsa_input_domain, "Buffer Overrun");
|
FormatDebug(alsa_input_domain,
|
||||||
// drop through
|
"Overrun on ALSA capture device \"%s\"",
|
||||||
|
device.c_str());
|
||||||
|
break;
|
||||||
|
|
||||||
|
case -ESTRPIPE:
|
||||||
|
FormatDebug(alsa_input_domain,
|
||||||
|
"ALSA capture device \"%s\" was suspended",
|
||||||
|
device.c_str());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (snd_pcm_state(capture_handle)) {
|
||||||
|
case SND_PCM_STATE_PAUSED:
|
||||||
|
err = snd_pcm_pause(capture_handle, /* disable */ 0);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SND_PCM_STATE_SUSPENDED:
|
||||||
|
err = snd_pcm_resume(capture_handle);
|
||||||
|
if (err == -EAGAIN)
|
||||||
|
return 0;
|
||||||
|
/* fall-through to snd_pcm_prepare: */
|
||||||
#if GCC_CHECK_VERSION(7,0)
|
#if GCC_CHECK_VERSION(7,0)
|
||||||
[[fallthrough]];
|
[[fallthrough]];
|
||||||
#endif
|
#endif
|
||||||
|
case SND_PCM_STATE_OPEN:
|
||||||
case -ESTRPIPE:
|
case SND_PCM_STATE_SETUP:
|
||||||
case -EINTR:
|
case SND_PCM_STATE_XRUN:
|
||||||
err = snd_pcm_recover(capture_handle, err, 1);
|
err = snd_pcm_prepare(capture_handle);
|
||||||
|
if (err == 0)
|
||||||
|
err = snd_pcm_start(capture_handle);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SND_PCM_STATE_DISCONNECTED:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SND_PCM_STATE_PREPARED:
|
||||||
|
case SND_PCM_STATE_RUNNING:
|
||||||
|
case SND_PCM_STATE_DRAINING:
|
||||||
|
/* this is no error, so just keep running */
|
||||||
|
err = 0;
|
||||||
break;
|
break;
|
||||||
default:
|
|
||||||
// something broken somewhere, give up
|
|
||||||
err = -1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -245,13 +283,7 @@ ConfigureCapture(snd_pcm_t *capture_handle,
|
|||||||
int err;
|
int err;
|
||||||
|
|
||||||
snd_pcm_hw_params_t *hw_params;
|
snd_pcm_hw_params_t *hw_params;
|
||||||
if ((err = snd_pcm_hw_params_malloc(&hw_params)) < 0)
|
snd_pcm_hw_params_alloca(&hw_params);
|
||||||
throw FormatRuntimeError("Cannot allocate hardware parameter structure (%s)",
|
|
||||||
snd_strerror(err));
|
|
||||||
|
|
||||||
AtScopeExit(hw_params) {
|
|
||||||
snd_pcm_hw_params_free(hw_params);
|
|
||||||
};
|
|
||||||
|
|
||||||
if ((err = snd_pcm_hw_params_any(capture_handle, hw_params)) < 0)
|
if ((err = snd_pcm_hw_params_any(capture_handle, hw_params)) < 0)
|
||||||
throw FormatRuntimeError("Cannot initialize hardware parameter structure (%s)",
|
throw FormatRuntimeError("Cannot initialize hardware parameter structure (%s)",
|
||||||
@@ -273,37 +305,68 @@ ConfigureCapture(snd_pcm_t *capture_handle,
|
|||||||
throw FormatRuntimeError("Cannot set sample rate (%s)",
|
throw FormatRuntimeError("Cannot set sample rate (%s)",
|
||||||
snd_strerror(err));
|
snd_strerror(err));
|
||||||
|
|
||||||
/* period needs to be big enough so that poll() doesn't fire too often,
|
snd_pcm_uframes_t buffer_size_min, buffer_size_max;
|
||||||
* but small enough that buffer overruns don't occur if Read() is not
|
snd_pcm_hw_params_get_buffer_size_min(hw_params, &buffer_size_min);
|
||||||
* invoked often enough.
|
snd_pcm_hw_params_get_buffer_size_max(hw_params, &buffer_size_max);
|
||||||
* the calculation here is empirical; however all measurements were
|
unsigned buffer_time_min, buffer_time_max;
|
||||||
* done using 44100:16:2. When we extend this plugin to support
|
snd_pcm_hw_params_get_buffer_time_min(hw_params, &buffer_time_min, 0);
|
||||||
* other audio formats then this may need to be revisited */
|
snd_pcm_hw_params_get_buffer_time_max(hw_params, &buffer_time_max, 0);
|
||||||
snd_pcm_uframes_t period = read_buffer_size * 2;
|
FormatDebug(alsa_input_domain, "buffer: size=%u..%u time=%u..%u",
|
||||||
|
(unsigned)buffer_size_min, (unsigned)buffer_size_max,
|
||||||
|
buffer_time_min, buffer_time_max);
|
||||||
|
|
||||||
|
snd_pcm_uframes_t period_size_min, period_size_max;
|
||||||
|
snd_pcm_hw_params_get_period_size_min(hw_params, &period_size_min, 0);
|
||||||
|
snd_pcm_hw_params_get_period_size_max(hw_params, &period_size_max, 0);
|
||||||
|
unsigned period_time_min, period_time_max;
|
||||||
|
snd_pcm_hw_params_get_period_time_min(hw_params, &period_time_min, 0);
|
||||||
|
snd_pcm_hw_params_get_period_time_max(hw_params, &period_time_max, 0);
|
||||||
|
FormatDebug(alsa_input_domain, "period: size=%u..%u time=%u..%u",
|
||||||
|
(unsigned)period_size_min, (unsigned)period_size_max,
|
||||||
|
period_time_min, period_time_max);
|
||||||
|
|
||||||
|
/* choose the maximum possible buffer_size ... */
|
||||||
|
snd_pcm_hw_params_set_buffer_size(capture_handle, hw_params,
|
||||||
|
buffer_size_max);
|
||||||
|
|
||||||
|
/* ... and calculate the period_size to have four periods in
|
||||||
|
one buffer; this way, we get woken up often enough to avoid
|
||||||
|
buffer overruns, but not too often */
|
||||||
|
snd_pcm_uframes_t buffer_size;
|
||||||
|
if (snd_pcm_hw_params_get_buffer_size(hw_params, &buffer_size) == 0) {
|
||||||
|
snd_pcm_uframes_t period_size = buffer_size / 4;
|
||||||
int direction = -1;
|
int direction = -1;
|
||||||
if ((err = snd_pcm_hw_params_set_period_size_near(capture_handle, hw_params,
|
if ((err = snd_pcm_hw_params_set_period_size_near(capture_handle, hw_params,
|
||||||
&period, &direction)) < 0)
|
&period_size, &direction)) < 0)
|
||||||
throw FormatRuntimeError("Cannot set period size (%s)",
|
throw FormatRuntimeError("Cannot set period size (%s)",
|
||||||
snd_strerror(err));
|
snd_strerror(err));
|
||||||
|
}
|
||||||
|
|
||||||
if ((err = snd_pcm_hw_params(capture_handle, hw_params)) < 0)
|
if ((err = snd_pcm_hw_params(capture_handle, hw_params)) < 0)
|
||||||
throw FormatRuntimeError("Cannot set parameters (%s)",
|
throw FormatRuntimeError("Cannot set parameters (%s)",
|
||||||
snd_strerror(err));
|
snd_strerror(err));
|
||||||
|
|
||||||
|
snd_pcm_uframes_t alsa_buffer_size;
|
||||||
|
err = snd_pcm_hw_params_get_buffer_size(hw_params, &alsa_buffer_size);
|
||||||
|
if (err < 0)
|
||||||
|
throw FormatRuntimeError("snd_pcm_hw_params_get_buffer_size() failed: %s",
|
||||||
|
snd_strerror(-err));
|
||||||
|
|
||||||
|
snd_pcm_uframes_t alsa_period_size;
|
||||||
|
err = snd_pcm_hw_params_get_period_size(hw_params, &alsa_period_size,
|
||||||
|
nullptr);
|
||||||
|
if (err < 0)
|
||||||
|
throw FormatRuntimeError("snd_pcm_hw_params_get_period_size() failed: %s",
|
||||||
|
snd_strerror(-err));
|
||||||
|
|
||||||
|
FormatDebug(alsa_input_domain, "buffer_size=%u period_size=%u",
|
||||||
|
(unsigned)alsa_buffer_size, (unsigned)alsa_period_size);
|
||||||
|
|
||||||
snd_pcm_sw_params_t *sw_params;
|
snd_pcm_sw_params_t *sw_params;
|
||||||
|
snd_pcm_sw_params_alloca(&sw_params);
|
||||||
|
|
||||||
snd_pcm_sw_params_malloc(&sw_params);
|
|
||||||
snd_pcm_sw_params_current(capture_handle, sw_params);
|
snd_pcm_sw_params_current(capture_handle, sw_params);
|
||||||
|
|
||||||
AtScopeExit(sw_params) {
|
|
||||||
snd_pcm_sw_params_free(sw_params);
|
|
||||||
};
|
|
||||||
|
|
||||||
if ((err = snd_pcm_sw_params_set_start_threshold(capture_handle, sw_params,
|
|
||||||
period)) < 0)
|
|
||||||
throw FormatRuntimeError("unable to set start threshold (%s)",
|
|
||||||
snd_strerror(err));
|
|
||||||
|
|
||||||
if ((err = snd_pcm_sw_params(capture_handle, sw_params)) < 0)
|
if ((err = snd_pcm_sw_params(capture_handle, sw_params)) < 0)
|
||||||
throw FormatRuntimeError("unable to install sw params (%s)",
|
throw FormatRuntimeError("unable to install sw params (%s)",
|
||||||
snd_strerror(err));
|
snd_strerror(err));
|
||||||
@@ -316,7 +379,8 @@ AlsaInputStream::OpenDevice(const char *device,
|
|||||||
snd_pcm_t *capture_handle;
|
snd_pcm_t *capture_handle;
|
||||||
int err;
|
int err;
|
||||||
if ((err = snd_pcm_open(&capture_handle, device,
|
if ((err = snd_pcm_open(&capture_handle, device,
|
||||||
SND_PCM_STREAM_CAPTURE, 0)) < 0)
|
SND_PCM_STREAM_CAPTURE,
|
||||||
|
SND_PCM_NONBLOCK)) < 0)
|
||||||
throw FormatRuntimeError("Failed to open device: %s (%s)",
|
throw FormatRuntimeError("Failed to open device: %s (%s)",
|
||||||
device, snd_strerror(err));
|
device, snd_strerror(err));
|
||||||
|
|
||||||
|
@@ -398,7 +398,12 @@ CurlInputStream::SeekInternal(offset_type new_offset)
|
|||||||
/* send the "Range" header */
|
/* send the "Range" header */
|
||||||
|
|
||||||
if (offset > 0) {
|
if (offset > 0) {
|
||||||
sprintf(range, "%lld-", (long long)offset);
|
#ifdef WIN32
|
||||||
|
// TODO: what can we use on Windows to format 64 bit?
|
||||||
|
sprintf(range, "%lu-", (long)offset);
|
||||||
|
#else
|
||||||
|
sprintf(range, "%llu-", (unsigned long long)offset);
|
||||||
|
#endif
|
||||||
request->SetOption(CURLOPT_RANGE, range);
|
request->SetOption(CURLOPT_RANGE, range);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2010-2011 Max Kellermann <max@duempel.org>
|
* Copyright (C) 2010-2011 Max Kellermann <max.kellermann@gmail.com>
|
||||||
*
|
*
|
||||||
* Redistribution and use in source and binary forms, with or without
|
* Redistribution and use in source and binary forms, with or without
|
||||||
* modification, are permitted provided that the following conditions
|
* modification, are permitted provided that the following conditions
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2010-2012 Max Kellermann <max@duempel.org>
|
* Copyright (C) 2010-2012 Max Kellermann <max.kellermann@gmail.com>
|
||||||
*
|
*
|
||||||
* Redistribution and use in source and binary forms, with or without
|
* Redistribution and use in source and binary forms, with or without
|
||||||
* modification, are permitted provided that the following conditions
|
* modification, are permitted provided that the following conditions
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2010-2014 Max Kellermann <max@duempel.org>
|
* Copyright (C) 2010-2014 Max Kellermann <max.kellermann@gmail.com>
|
||||||
*
|
*
|
||||||
* Redistribution and use in source and binary forms, with or without
|
* Redistribution and use in source and binary forms, with or without
|
||||||
* modification, are permitted provided that the following conditions
|
* modification, are permitted provided that the following conditions
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2010-2014 Max Kellermann <max@duempel.org>
|
* Copyright (C) 2010-2014 Max Kellermann <max.kellermann@gmail.com>
|
||||||
*
|
*
|
||||||
* Redistribution and use in source and binary forms, with or without
|
* Redistribution and use in source and binary forms, with or without
|
||||||
* modification, are permitted provided that the following conditions
|
* modification, are permitted provided that the following conditions
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2010-2011 Max Kellermann <max@duempel.org>
|
* Copyright (C) 2010-2011 Max Kellermann <max.kellermann@gmail.com>
|
||||||
*
|
*
|
||||||
* Redistribution and use in source and binary forms, with or without
|
* Redistribution and use in source and binary forms, with or without
|
||||||
* modification, are permitted provided that the following conditions
|
* modification, are permitted provided that the following conditions
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2010-2011 Max Kellermann <max@duempel.org>
|
* Copyright (C) 2010-2011 Max Kellermann <max.kellermann@gmail.com>
|
||||||
*
|
*
|
||||||
* Redistribution and use in source and binary forms, with or without
|
* Redistribution and use in source and binary forms, with or without
|
||||||
* modification, are permitted provided that the following conditions
|
* modification, are permitted provided that the following conditions
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2010-2011 Max Kellermann <max@duempel.org>
|
* Copyright (C) 2010-2011 Max Kellermann <max.kellermann@gmail.com>
|
||||||
*
|
*
|
||||||
* Redistribution and use in source and binary forms, with or without
|
* Redistribution and use in source and binary forms, with or without
|
||||||
* modification, are permitted provided that the following conditions
|
* modification, are permitted provided that the following conditions
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2010-2011 Max Kellermann <max@duempel.org>
|
* Copyright (C) 2010-2011 Max Kellermann <max.kellermann@gmail.com>
|
||||||
*
|
*
|
||||||
* Redistribution and use in source and binary forms, with or without
|
* Redistribution and use in source and binary forms, with or without
|
||||||
* modification, are permitted provided that the following conditions
|
* modification, are permitted provided that the following conditions
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2010-2011 Max Kellermann <max@duempel.org>
|
* Copyright (C) 2010-2011 Max Kellermann <max.kellermann@gmail.com>
|
||||||
*
|
*
|
||||||
* Redistribution and use in source and binary forms, with or without
|
* Redistribution and use in source and binary forms, with or without
|
||||||
* modification, are permitted provided that the following conditions
|
* modification, are permitted provided that the following conditions
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2010-2011 Max Kellermann <max@duempel.org>
|
* Copyright (C) 2010-2011 Max Kellermann <max.kellermann@gmail.com>
|
||||||
*
|
*
|
||||||
* Redistribution and use in source and binary forms, with or without
|
* Redistribution and use in source and binary forms, with or without
|
||||||
* modification, are permitted provided that the following conditions
|
* modification, are permitted provided that the following conditions
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2016 Max Kellermann <max@duempel.org>
|
* Copyright (C) 2016 Max Kellermann <max.kellermann@gmail.com>
|
||||||
*
|
*
|
||||||
* Redistribution and use in source and binary forms, with or without
|
* Redistribution and use in source and binary forms, with or without
|
||||||
* modification, are permitted provided that the following conditions
|
* modification, are permitted provided that the following conditions
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2008-2016 Max Kellermann <max@duempel.org>
|
* Copyright (C) 2008-2016 Max Kellermann <max.kellermann@gmail.com>
|
||||||
*
|
*
|
||||||
* Redistribution and use in source and binary forms, with or without
|
* Redistribution and use in source and binary forms, with or without
|
||||||
* modification, are permitted provided that the following conditions
|
* modification, are permitted provided that the following conditions
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2008-2016 Max Kellermann <max@duempel.org>
|
* Copyright (C) 2008-2016 Max Kellermann <max.kellermann@gmail.com>
|
||||||
*
|
*
|
||||||
* Redistribution and use in source and binary forms, with or without
|
* Redistribution and use in source and binary forms, with or without
|
||||||
* modification, are permitted provided that the following conditions
|
* modification, are permitted provided that the following conditions
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2008-2017 Max Kellermann <max@duempel.org>
|
* Copyright (C) 2008-2017 Max Kellermann <max.kellermann@gmail.com>
|
||||||
*
|
*
|
||||||
* Redistribution and use in source and binary forms, with or without
|
* Redistribution and use in source and binary forms, with or without
|
||||||
* modification, are permitted provided that the following conditions
|
* modification, are permitted provided that the following conditions
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2016 Max Kellermann <max@duempel.org>
|
* Copyright (C) 2016 Max Kellermann <max.kellermann@gmail.com>
|
||||||
*
|
*
|
||||||
* Redistribution and use in source and binary forms, with or without
|
* Redistribution and use in source and binary forms, with or without
|
||||||
* modification, are permitted provided that the following conditions
|
* modification, are permitted provided that the following conditions
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2008-2017 Max Kellermann <max@duempel.org>
|
* Copyright (C) 2008-2017 Max Kellermann <max.kellermann@gmail.com>
|
||||||
*
|
*
|
||||||
* Redistribution and use in source and binary forms, with or without
|
* Redistribution and use in source and binary forms, with or without
|
||||||
* modification, are permitted provided that the following conditions
|
* modification, are permitted provided that the following conditions
|
||||||
@@ -168,13 +168,23 @@ CurlRequest::Done(CURLcode result)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
gcc_pure
|
||||||
|
static bool
|
||||||
|
IsResponseBoundaryHeader(StringView s)
|
||||||
|
{
|
||||||
|
return s.size > 5 && (memcmp(s.data, "HTTP/", 5) == 0 ||
|
||||||
|
/* the proprietary "ICY 200 OK" is
|
||||||
|
emitted by Shoutcast */
|
||||||
|
memcmp(s.data, "ICY 2", 5) == 0);
|
||||||
|
}
|
||||||
|
|
||||||
inline void
|
inline void
|
||||||
CurlRequest::HeaderFunction(StringView s)
|
CurlRequest::HeaderFunction(StringView s)
|
||||||
{
|
{
|
||||||
if (state > State::HEADERS)
|
if (state > State::HEADERS)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (s.size > 5 && memcmp(s.data, "HTTP/", 5) == 0) {
|
if (IsResponseBoundaryHeader(s)) {
|
||||||
/* this is the boundary to a new response, for example
|
/* this is the boundary to a new response, for example
|
||||||
after a redirect */
|
after a redirect */
|
||||||
headers.clear();
|
headers.clear();
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2008-2017 Max Kellermann <max@duempel.org>
|
* Copyright (C) 2008-2017 Max Kellermann <max.kellermann@gmail.com>
|
||||||
*
|
*
|
||||||
* Redistribution and use in source and binary forms, with or without
|
* Redistribution and use in source and binary forms, with or without
|
||||||
* modification, are permitted provided that the following conditions
|
* modification, are permitted provided that the following conditions
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2008-2017 Max Kellermann <max@duempel.org>
|
* Copyright (C) 2008-2017 Max Kellermann <max.kellermann@gmail.com>
|
||||||
*
|
*
|
||||||
* Redistribution and use in source and binary forms, with or without
|
* Redistribution and use in source and binary forms, with or without
|
||||||
* modification, are permitted provided that the following conditions
|
* modification, are permitted provided that the following conditions
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2017 Max Kellermann <max@duempel.org>
|
* Copyright (C) 2017 Max Kellermann <max.kellermann@gmail.com>
|
||||||
*
|
*
|
||||||
* Redistribution and use in source and binary forms, with or without
|
* Redistribution and use in source and binary forms, with or without
|
||||||
* modification, are permitted provided that the following conditions
|
* modification, are permitted provided that the following conditions
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2017 Max Kellermann <max@duempel.org>
|
* Copyright (C) 2017 Max Kellermann <max.kellermann@gmail.com>
|
||||||
*
|
*
|
||||||
* Redistribution and use in source and binary forms, with or without
|
* Redistribution and use in source and binary forms, with or without
|
||||||
* modification, are permitted provided that the following conditions
|
* modification, are permitted provided that the following conditions
|
||||||
|
@@ -24,9 +24,21 @@
|
|||||||
|
|
||||||
#include <exception>
|
#include <exception>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callbacks for an asynchronous libnfs operation.
|
||||||
|
*
|
||||||
|
* Note that no callback is invoked for cancelled operations.
|
||||||
|
*/
|
||||||
class NfsCallback {
|
class NfsCallback {
|
||||||
public:
|
public:
|
||||||
|
/**
|
||||||
|
* The operation completed successfully.
|
||||||
|
*/
|
||||||
virtual void OnNfsCallback(unsigned status, void *data) = 0;
|
virtual void OnNfsCallback(unsigned status, void *data) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An error has occurred.
|
||||||
|
*/
|
||||||
virtual void OnNfsError(std::exception_ptr &&e) = 0;
|
virtual void OnNfsError(std::exception_ptr &&e) = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -396,6 +396,17 @@ NfsConnection::ScheduleSocket()
|
|||||||
assert(GetEventLoop().IsInside());
|
assert(GetEventLoop().IsInside());
|
||||||
assert(context != nullptr);
|
assert(context != nullptr);
|
||||||
|
|
||||||
|
const int which_events = nfs_which_events(context);
|
||||||
|
|
||||||
|
if (which_events == POLLOUT && SocketMonitor::IsDefined())
|
||||||
|
/* kludge: if libnfs asks only for POLLOUT, it means
|
||||||
|
that it is currently waiting for the connect() to
|
||||||
|
finish - rpc_reconnect_requeue() may have been
|
||||||
|
called from inside nfs_service(); we must now
|
||||||
|
unregister the old socket and register the new one
|
||||||
|
instead */
|
||||||
|
SocketMonitor::Steal();
|
||||||
|
|
||||||
if (!SocketMonitor::IsDefined()) {
|
if (!SocketMonitor::IsDefined()) {
|
||||||
int _fd = nfs_get_fd(context);
|
int _fd = nfs_get_fd(context);
|
||||||
if (_fd < 0)
|
if (_fd < 0)
|
||||||
@@ -405,7 +416,8 @@ NfsConnection::ScheduleSocket()
|
|||||||
SocketMonitor::Open(_fd);
|
SocketMonitor::Open(_fd);
|
||||||
}
|
}
|
||||||
|
|
||||||
SocketMonitor::Schedule(libnfs_to_events(nfs_which_events(context)));
|
SocketMonitor::Schedule(libnfs_to_events(which_events)
|
||||||
|
| SocketMonitor::HANGUP);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline int
|
inline int
|
||||||
@@ -442,10 +454,14 @@ NfsConnection::OnSocketReady(unsigned flags)
|
|||||||
bool closed = false;
|
bool closed = false;
|
||||||
|
|
||||||
const bool was_mounted = mount_finished;
|
const bool was_mounted = mount_finished;
|
||||||
if (!mount_finished)
|
if (!mount_finished || (flags & SocketMonitor::HANGUP) != 0)
|
||||||
/* until the mount is finished, the NFS client may use
|
/* until the mount is finished, the NFS client may use
|
||||||
various sockets, therefore we unregister and
|
various sockets, therefore we unregister and
|
||||||
re-register it each time */
|
re-register it each time */
|
||||||
|
/* also re-register the socket if we got a HANGUP,
|
||||||
|
which is a sure sign that libnfs will close the
|
||||||
|
socket, which can lead to a race condition if
|
||||||
|
epoll_ctl() is called later */
|
||||||
SocketMonitor::Steal();
|
SocketMonitor::Steal();
|
||||||
|
|
||||||
const int result = Service(flags);
|
const int result = Service(flags);
|
||||||
|
@@ -35,6 +35,14 @@
|
|||||||
struct nfsfh;
|
struct nfsfh;
|
||||||
class NfsConnection;
|
class NfsConnection;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A helper class which helps with reading from a file. It obtains a
|
||||||
|
* connection lease (#NfsLease), opens the given file, "stats" the
|
||||||
|
* file, and finally allos you to read its contents.
|
||||||
|
*
|
||||||
|
* To get started, derive your class from it and implement the pure
|
||||||
|
* virtual methods, construct an instance, and call Open().
|
||||||
|
*/
|
||||||
class NfsFileReader : NfsLease, NfsCallback, DeferredMonitor {
|
class NfsFileReader : NfsLease, NfsCallback, DeferredMonitor {
|
||||||
enum class State {
|
enum class State {
|
||||||
INITIAL,
|
INITIAL,
|
||||||
@@ -63,14 +71,30 @@ public:
|
|||||||
void DeferClose();
|
void DeferClose();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Open the file. This method is thread-safe.
|
||||||
|
*
|
||||||
* Throws std::runtime_error on error.
|
* Throws std::runtime_error on error.
|
||||||
*/
|
*/
|
||||||
void Open(const char *uri);
|
void Open(const char *uri);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Attempt to read from the file. This may only be done after
|
||||||
|
* OnNfsFileOpen() has been called. Only one read operation
|
||||||
|
* may be performed at a time.
|
||||||
|
*
|
||||||
|
* This method is not thread-safe and must be called from
|
||||||
|
* within the I/O thread.
|
||||||
|
*
|
||||||
* Throws std::runtime_error on error.
|
* Throws std::runtime_error on error.
|
||||||
*/
|
*/
|
||||||
void Read(uint64_t offset, size_t size);
|
void Read(uint64_t offset, size_t size);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cancel the most recent Read() call.
|
||||||
|
*
|
||||||
|
* This method is not thread-safe and must be called from
|
||||||
|
* within the I/O thread.
|
||||||
|
*/
|
||||||
void CancelRead();
|
void CancelRead();
|
||||||
|
|
||||||
bool IsIdle() const {
|
bool IsIdle() const {
|
||||||
@@ -78,8 +102,27 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
/**
|
||||||
|
* The file has been opened successfully. It is a regular
|
||||||
|
* file, and its size is known. It is ready to be read from
|
||||||
|
* using Read().
|
||||||
|
*
|
||||||
|
* This method will be called from within the I/O thread.
|
||||||
|
*/
|
||||||
virtual void OnNfsFileOpen(uint64_t size) = 0;
|
virtual void OnNfsFileOpen(uint64_t size) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A Read() has completed successfully.
|
||||||
|
*
|
||||||
|
* This method will be called from within the I/O thread.
|
||||||
|
*/
|
||||||
virtual void OnNfsFileRead(const void *data, size_t size) = 0;
|
virtual void OnNfsFileRead(const void *data, size_t size) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An error has occurred, which can be either while waiting
|
||||||
|
* for OnNfsFileOpen(), or while waiting for OnNfsFileRead(),
|
||||||
|
* or if disconnected while idle.
|
||||||
|
*/
|
||||||
virtual void OnNfsFileError(std::exception_ptr &&e) = 0;
|
virtual void OnNfsFileError(std::exception_ptr &&e) = 0;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
@@ -62,7 +62,7 @@ class UPnPDeviceDirectory final : UpnpCallback {
|
|||||||
DiscoveredTask(const Upnp_Discovery *disco)
|
DiscoveredTask(const Upnp_Discovery *disco)
|
||||||
:url(disco->Location),
|
:url(disco->Location),
|
||||||
device_id(disco->DeviceId),
|
device_id(disco->DeviceId),
|
||||||
expires(disco->Expires) {}
|
expires(std::chrono::seconds(disco->Expires)) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -71,20 +71,19 @@ path_getfather(const std::string &s)
|
|||||||
|
|
||||||
std::list<std::string>
|
std::list<std::string>
|
||||||
stringToTokens(const std::string &str,
|
stringToTokens(const std::string &str,
|
||||||
const char *delims, bool skipinit)
|
const char delim)
|
||||||
{
|
{
|
||||||
std::list<std::string> tokens;
|
std::list<std::string> tokens;
|
||||||
|
|
||||||
std::string::size_type startPos = 0;
|
std::string::size_type startPos = str.find_first_not_of(delim, 0);
|
||||||
|
|
||||||
// Skip initial delims, return empty if this eats all.
|
// Skip initial delims, return empty if this eats all.
|
||||||
if (skipinit &&
|
if (startPos == std::string::npos)
|
||||||
(startPos = str.find_first_not_of(delims, 0)) == std::string::npos)
|
|
||||||
return tokens;
|
return tokens;
|
||||||
|
|
||||||
while (startPos < str.size()) {
|
while (startPos < str.size()) {
|
||||||
// Find next delimiter or end of string (end of token)
|
// Find next delimiter or end of string (end of token)
|
||||||
auto pos = str.find_first_of(delims, startPos);
|
auto pos = str.find_first_of(delim, startPos);
|
||||||
|
|
||||||
// Add token to the vector and adjust start
|
// Add token to the vector and adjust start
|
||||||
if (pos == std::string::npos) {
|
if (pos == std::string::npos) {
|
||||||
|
@@ -33,8 +33,7 @@ path_getfather(const std::string &s);
|
|||||||
|
|
||||||
gcc_pure
|
gcc_pure
|
||||||
std::list<std::string>
|
std::list<std::string>
|
||||||
stringToTokens(const std::string &str,
|
stringToTokens(const std::string &str, char delim);
|
||||||
const char *delims = "/", bool skipinit = true);
|
|
||||||
|
|
||||||
template <class T>
|
template <class T>
|
||||||
bool
|
bool
|
||||||
|
@@ -52,11 +52,11 @@ class WorkQueue {
|
|||||||
|
|
||||||
// Status
|
// Status
|
||||||
// Worker threads having called exit
|
// Worker threads having called exit
|
||||||
unsigned n_workers_exited;
|
unsigned n_workers_exited = 0;
|
||||||
bool ok;
|
bool ok = false;
|
||||||
|
|
||||||
unsigned n_threads;
|
unsigned n_threads = 0;
|
||||||
pthread_t *threads;
|
pthread_t *threads = nullptr;
|
||||||
|
|
||||||
// Synchronization
|
// Synchronization
|
||||||
std::queue<T> queue;
|
std::queue<T> queue;
|
||||||
@@ -68,11 +68,8 @@ public:
|
|||||||
/** Create a WorkQueue
|
/** Create a WorkQueue
|
||||||
* @param _name for message printing
|
* @param _name for message printing
|
||||||
*/
|
*/
|
||||||
WorkQueue(const char *_name)
|
explicit WorkQueue(const char *_name)
|
||||||
:name(_name),
|
:name(_name)
|
||||||
n_workers_exited(0),
|
|
||||||
ok(false),
|
|
||||||
n_threads(0), threads(nullptr)
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,6 +77,9 @@ public:
|
|||||||
setTerminateAndWait();
|
setTerminateAndWait();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
WorkQueue(const WorkQueue &) = delete;
|
||||||
|
WorkQueue &operator=(const WorkQueue &) = delete;
|
||||||
|
|
||||||
/** Start the worker threads.
|
/** Start the worker threads.
|
||||||
*
|
*
|
||||||
* @param nworkers number of threads copies to start.
|
* @param nworkers number of threads copies to start.
|
||||||
@@ -97,6 +97,7 @@ public:
|
|||||||
assert(n_threads == 0);
|
assert(n_threads == 0);
|
||||||
assert(threads == nullptr);
|
assert(threads == nullptr);
|
||||||
|
|
||||||
|
ok = true;
|
||||||
n_threads = nworkers;
|
n_threads = nworkers;
|
||||||
threads = new pthread_t[n_threads];
|
threads = new pthread_t[n_threads];
|
||||||
|
|
||||||
@@ -109,7 +110,6 @@ public:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ok = true;
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -23,6 +23,7 @@
|
|||||||
#include "output/OutputAPI.hxx"
|
#include "output/OutputAPI.hxx"
|
||||||
#include "event/MultiSocketMonitor.hxx"
|
#include "event/MultiSocketMonitor.hxx"
|
||||||
#include "event/DeferredMonitor.hxx"
|
#include "event/DeferredMonitor.hxx"
|
||||||
|
#include "event/Call.hxx"
|
||||||
#include "util/ASCII.hxx"
|
#include "util/ASCII.hxx"
|
||||||
#include "util/ReusableArray.hxx"
|
#include "util/ReusableArray.hxx"
|
||||||
#include "util/Domain.hxx"
|
#include "util/Domain.hxx"
|
||||||
@@ -53,6 +54,13 @@ public:
|
|||||||
DeferredMonitor::Schedule();
|
DeferredMonitor::Schedule();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
~AlsaMixerMonitor() {
|
||||||
|
BlockingCall(MultiSocketMonitor::GetEventLoop(), [this](){
|
||||||
|
MultiSocketMonitor::Reset();
|
||||||
|
DeferredMonitor::Cancel();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
virtual void RunDeferred() override {
|
virtual void RunDeferred() override {
|
||||||
InvalidateSockets();
|
InvalidateSockets();
|
||||||
@@ -102,7 +110,7 @@ AlsaMixerMonitor::PrepareSockets()
|
|||||||
}
|
}
|
||||||
|
|
||||||
int count = snd_mixer_poll_descriptors_count(mixer);
|
int count = snd_mixer_poll_descriptors_count(mixer);
|
||||||
if (count < 0)
|
if (count <= 0)
|
||||||
count = 0;
|
count = 0;
|
||||||
|
|
||||||
struct pollfd *pfds = pfd_buffer.Get(count);
|
struct pollfd *pfds = pfd_buffer.Get(count);
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2012-2015 Max Kellermann <max@duempel.org>
|
* Copyright (C) 2012-2015 Max Kellermann <max.kellermann@gmail.com>
|
||||||
*
|
*
|
||||||
* Redistribution and use in source and binary forms, with or without
|
* Redistribution and use in source and binary forms, with or without
|
||||||
* modification, are permitted provided that the following conditions
|
* modification, are permitted provided that the following conditions
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2012-2015 Max Kellermann <max@duempel.org>
|
* Copyright (C) 2012-2015 Max Kellermann <max.kellermann@gmail.com>
|
||||||
*
|
*
|
||||||
* Redistribution and use in source and binary forms, with or without
|
* Redistribution and use in source and binary forms, with or without
|
||||||
* modification, are permitted provided that the following conditions
|
* modification, are permitted provided that the following conditions
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2012-2015 Max Kellermann <max@duempel.org>
|
* Copyright (C) 2012-2015 Max Kellermann <max.kellermann@gmail.com>
|
||||||
*
|
*
|
||||||
* Redistribution and use in source and binary forms, with or without
|
* Redistribution and use in source and binary forms, with or without
|
||||||
* modification, are permitted provided that the following conditions
|
* modification, are permitted provided that the following conditions
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2012-2015 Max Kellermann <max@duempel.org>
|
* Copyright (C) 2012-2015 Max Kellermann <max.kellermann@gmail.com>
|
||||||
*
|
*
|
||||||
* Redistribution and use in source and binary forms, with or without
|
* Redistribution and use in source and binary forms, with or without
|
||||||
* modification, are permitted provided that the following conditions
|
* modification, are permitted provided that the following conditions
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2012-2015 Max Kellermann <max@duempel.org>
|
* Copyright (C) 2012-2015 Max Kellermann <max.kellermann@gmail.com>
|
||||||
*
|
*
|
||||||
* Redistribution and use in source and binary forms, with or without
|
* Redistribution and use in source and binary forms, with or without
|
||||||
* modification, are permitted provided that the following conditions
|
* modification, are permitted provided that the following conditions
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2012-2015 Max Kellermann <max@duempel.org>
|
* Copyright (C) 2012-2015 Max Kellermann <max.kellermann@gmail.com>
|
||||||
*
|
*
|
||||||
* Redistribution and use in source and binary forms, with or without
|
* Redistribution and use in source and binary forms, with or without
|
||||||
* modification, are permitted provided that the following conditions
|
* modification, are permitted provided that the following conditions
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2011-2015 Max Kellermann <max@duempel.org>
|
* Copyright (C) 2011-2015 Max Kellermann <max.kellermann@gmail.com>
|
||||||
*
|
*
|
||||||
* Redistribution and use in source and binary forms, with or without
|
* Redistribution and use in source and binary forms, with or without
|
||||||
* modification, are permitted provided that the following conditions
|
* modification, are permitted provided that the following conditions
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2011-2015 Max Kellermann <max@duempel.org>
|
* Copyright (C) 2011-2015 Max Kellermann <max.kellermann@gmail.com>
|
||||||
*
|
*
|
||||||
* Redistribution and use in source and binary forms, with or without
|
* Redistribution and use in source and binary forms, with or without
|
||||||
* modification, are permitted provided that the following conditions
|
* modification, are permitted provided that the following conditions
|
||||||
|
@@ -35,6 +35,7 @@
|
|||||||
#include "thread/Slack.hxx"
|
#include "thread/Slack.hxx"
|
||||||
#include "thread/Name.hxx"
|
#include "thread/Name.hxx"
|
||||||
#include "util/ConstBuffer.hxx"
|
#include "util/ConstBuffer.hxx"
|
||||||
|
#include "util/StringBuffer.hxx"
|
||||||
#include "util/ScopeExit.hxx"
|
#include "util/ScopeExit.hxx"
|
||||||
#include "util/RuntimeError.hxx"
|
#include "util/RuntimeError.hxx"
|
||||||
#include "Log.hxx"
|
#include "Log.hxx"
|
||||||
@@ -111,8 +112,7 @@ AudioOutput::Open()
|
|||||||
f = source.Open(request.audio_format, *request.pipe,
|
f = source.Open(request.audio_format, *request.pipe,
|
||||||
prepared_replay_gain_filter,
|
prepared_replay_gain_filter,
|
||||||
prepared_other_replay_gain_filter,
|
prepared_other_replay_gain_filter,
|
||||||
prepared_filter)
|
prepared_filter);
|
||||||
.WithMask(config_audio_format);
|
|
||||||
|
|
||||||
if (mixer != nullptr && mixer->IsPlugin(software_mixer_plugin))
|
if (mixer != nullptr && mixer->IsPlugin(software_mixer_plugin))
|
||||||
software_mixer_set_filter(*mixer, volume_filter.Get());
|
software_mixer_set_filter(*mixer, volume_filter.Get());
|
||||||
@@ -121,14 +121,16 @@ AudioOutput::Open()
|
|||||||
name, plugin.name));
|
name, plugin.name));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (open && f != filter_audio_format) {
|
const auto cf = f.WithMask(config_audio_format);
|
||||||
|
|
||||||
|
if (open && cf != filter_audio_format) {
|
||||||
/* if the filter's output format changes, the output
|
/* if the filter's output format changes, the output
|
||||||
must be reopened as well */
|
must be reopened as well */
|
||||||
CloseOutput(true);
|
CloseOutput(true);
|
||||||
open = false;
|
open = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
filter_audio_format = f;
|
filter_audio_format = cf;
|
||||||
|
|
||||||
if (!open) {
|
if (!open) {
|
||||||
try {
|
try {
|
||||||
@@ -139,7 +141,25 @@ AudioOutput::Open()
|
|||||||
}
|
}
|
||||||
|
|
||||||
open = true;
|
open = true;
|
||||||
|
} else if (f != out_audio_format) {
|
||||||
|
/* reconfigure the final ConvertFilter for its new
|
||||||
|
input AudioFormat */
|
||||||
|
|
||||||
|
try {
|
||||||
|
convert_filter_set(convert_filter.Get(),
|
||||||
|
out_audio_format);
|
||||||
|
} catch (const std::runtime_error &e) {
|
||||||
|
Close(false);
|
||||||
|
std::throw_with_nested(FormatRuntimeError("Failed to convert for \"%s\" [%s]",
|
||||||
|
name, plugin.name));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (f != source.GetInputAudioFormat() || f != out_audio_format)
|
||||||
|
FormatDebug(output_domain, "converting in=%s -> f=%s -> out=%s",
|
||||||
|
ToString(source.GetInputAudioFormat()).c_str(),
|
||||||
|
ToString(f).c_str(),
|
||||||
|
ToString(out_audio_format).c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
@@ -154,6 +174,11 @@ AudioOutput::OpenOutputAndConvert(AudioFormat desired_audio_format)
|
|||||||
name, plugin.name));
|
name, plugin.name));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FormatDebug(output_domain,
|
||||||
|
"opened plugin=%s name=\"%s\" audio_format=%s",
|
||||||
|
plugin.name, name,
|
||||||
|
ToString(out_audio_format).c_str());
|
||||||
|
|
||||||
try {
|
try {
|
||||||
convert_filter_set(convert_filter.Get(), out_audio_format);
|
convert_filter_set(convert_filter.Get(), out_audio_format);
|
||||||
} catch (const std::runtime_error &e) {
|
} catch (const std::runtime_error &e) {
|
||||||
@@ -177,17 +202,6 @@ AudioOutput::OpenOutputAndConvert(AudioFormat desired_audio_format)
|
|||||||
std::throw_with_nested(FormatRuntimeError("Failed to convert for \"%s\" [%s]",
|
std::throw_with_nested(FormatRuntimeError("Failed to convert for \"%s\" [%s]",
|
||||||
name, plugin.name));
|
name, plugin.name));
|
||||||
}
|
}
|
||||||
|
|
||||||
struct audio_format_string af_string;
|
|
||||||
FormatDebug(output_domain,
|
|
||||||
"opened plugin=%s name=\"%s\" audio_format=%s",
|
|
||||||
plugin.name, name,
|
|
||||||
audio_format_to_string(out_audio_format, &af_string));
|
|
||||||
|
|
||||||
if (source.GetInputAudioFormat() != out_audio_format)
|
|
||||||
FormatDebug(output_domain, "converting from %s",
|
|
||||||
audio_format_to_string(source.GetInputAudioFormat(),
|
|
||||||
&af_string));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
@@ -70,6 +70,22 @@ AudioOutputSource::Close()
|
|||||||
CloseFilter();
|
CloseFilter();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
AudioOutputSource::Cancel()
|
||||||
|
{
|
||||||
|
current_chunk = nullptr;
|
||||||
|
pipe.Cancel();
|
||||||
|
|
||||||
|
if (replay_gain_filter_instance != nullptr)
|
||||||
|
replay_gain_filter_instance->Reset();
|
||||||
|
|
||||||
|
if (other_replay_gain_filter_instance != nullptr)
|
||||||
|
other_replay_gain_filter_instance->Reset();
|
||||||
|
|
||||||
|
if (filter_instance != nullptr)
|
||||||
|
filter_instance->Reset();
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
AudioOutputSource::OpenFilter(AudioFormat audio_format,
|
AudioOutputSource::OpenFilter(AudioFormat audio_format,
|
||||||
PreparedFilter *prepared_replay_gain_filter,
|
PreparedFilter *prepared_replay_gain_filter,
|
||||||
@@ -79,13 +95,17 @@ try {
|
|||||||
assert(audio_format.IsValid());
|
assert(audio_format.IsValid());
|
||||||
|
|
||||||
/* the replay_gain filter cannot fail here */
|
/* the replay_gain filter cannot fail here */
|
||||||
if (prepared_replay_gain_filter != nullptr)
|
if (prepared_replay_gain_filter != nullptr) {
|
||||||
|
replay_gain_serial = 0;
|
||||||
replay_gain_filter_instance =
|
replay_gain_filter_instance =
|
||||||
prepared_replay_gain_filter->Open(audio_format);
|
prepared_replay_gain_filter->Open(audio_format);
|
||||||
|
}
|
||||||
|
|
||||||
if (prepared_other_replay_gain_filter != nullptr)
|
if (prepared_other_replay_gain_filter != nullptr) {
|
||||||
|
other_replay_gain_serial = 0;
|
||||||
other_replay_gain_filter_instance =
|
other_replay_gain_filter_instance =
|
||||||
prepared_other_replay_gain_filter->Open(audio_format);
|
prepared_other_replay_gain_filter->Open(audio_format);
|
||||||
|
}
|
||||||
|
|
||||||
filter_instance = prepared_filter->Open(audio_format);
|
filter_instance = prepared_filter->Open(audio_format);
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
|
@@ -64,13 +64,13 @@ class AudioOutputSource {
|
|||||||
* The serial number of the last replay gain info. 0 means no
|
* The serial number of the last replay gain info. 0 means no
|
||||||
* replay gain info was available.
|
* replay gain info was available.
|
||||||
*/
|
*/
|
||||||
unsigned replay_gain_serial = 0;
|
unsigned replay_gain_serial;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The serial number of the last replay gain info by the
|
* The serial number of the last replay gain info by the
|
||||||
* "other" chunk during cross-fading.
|
* "other" chunk during cross-fading.
|
||||||
*/
|
*/
|
||||||
unsigned other_replay_gain_serial = 0;
|
unsigned other_replay_gain_serial;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The replay_gain_filter_plugin instance of this audio
|
* The replay_gain_filter_plugin instance of this audio
|
||||||
@@ -139,11 +139,7 @@ public:
|
|||||||
PreparedFilter *prepared_filter);
|
PreparedFilter *prepared_filter);
|
||||||
|
|
||||||
void Close();
|
void Close();
|
||||||
|
void Cancel();
|
||||||
void Cancel() {
|
|
||||||
current_chunk = nullptr;
|
|
||||||
pipe.Cancel();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ensure that ReadTag() or PeekData() return any input.
|
* Ensure that ReadTag() or PeekData() return any input.
|
||||||
|
@@ -50,7 +50,9 @@ static constexpr unsigned MPD_ALSA_BUFFER_TIME_US = 500000;
|
|||||||
|
|
||||||
static constexpr unsigned MPD_ALSA_RETRY_NR = 5;
|
static constexpr unsigned MPD_ALSA_RETRY_NR = 5;
|
||||||
|
|
||||||
struct AlsaOutput {
|
class AlsaOutput {
|
||||||
|
friend struct AudioOutputWrapper<AlsaOutput>;
|
||||||
|
|
||||||
AudioOutput base;
|
AudioOutput base;
|
||||||
|
|
||||||
Manual<PcmExport> pcm_export;
|
Manual<PcmExport> pcm_export;
|
||||||
@@ -121,6 +123,7 @@ struct AlsaOutput {
|
|||||||
*/
|
*/
|
||||||
uint8_t *silence;
|
uint8_t *silence;
|
||||||
|
|
||||||
|
public:
|
||||||
AlsaOutput(const ConfigBlock &block);
|
AlsaOutput(const ConfigBlock &block);
|
||||||
|
|
||||||
~AlsaOutput() {
|
~AlsaOutput() {
|
||||||
@@ -141,11 +144,20 @@ struct AlsaOutput {
|
|||||||
void Open(AudioFormat &audio_format);
|
void Open(AudioFormat &audio_format);
|
||||||
void Close();
|
void Close();
|
||||||
|
|
||||||
|
size_t PlayRaw(ConstBuffer<void> data);
|
||||||
size_t Play(const void *chunk, size_t size);
|
size_t Play(const void *chunk, size_t size);
|
||||||
void Drain();
|
void Drain();
|
||||||
void Cancel();
|
void Cancel();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
/**
|
||||||
|
* Set up the snd_pcm_t object which was opened by the caller.
|
||||||
|
* Set up the configured settings and the audio format.
|
||||||
|
*
|
||||||
|
* Throws #std::runtime_error on error.
|
||||||
|
*/
|
||||||
|
void Setup(AudioFormat &audio_format, PcmExport::Params ¶ms);
|
||||||
|
|
||||||
#ifdef ENABLE_DSD
|
#ifdef ENABLE_DSD
|
||||||
void SetupDop(AudioFormat audio_format,
|
void SetupDop(AudioFormat audio_format,
|
||||||
PcmExport::Params ¶ms);
|
PcmExport::Params ¶ms);
|
||||||
@@ -384,7 +396,7 @@ AlsaTryFormatOrByteSwap(snd_pcm_t *pcm, snd_pcm_hw_params_t *hwparams,
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Attempts to configure the specified sample format. On DSD_U8
|
* Attempts to configure the specified sample format. On DSD_U8
|
||||||
* failure, attempt to switch to DSD_U32.
|
* failure, attempt to switch to DSD_U32 or DSD_U16.
|
||||||
*/
|
*/
|
||||||
static int
|
static int
|
||||||
AlsaTryFormatDsd(snd_pcm_t *pcm, snd_pcm_hw_params_t *hwparams,
|
AlsaTryFormatDsd(snd_pcm_t *pcm, snd_pcm_hw_params_t *hwparams,
|
||||||
@@ -393,8 +405,10 @@ AlsaTryFormatDsd(snd_pcm_t *pcm, snd_pcm_hw_params_t *hwparams,
|
|||||||
int err = AlsaTryFormatOrByteSwap(pcm, hwparams, fmt, params);
|
int err = AlsaTryFormatOrByteSwap(pcm, hwparams, fmt, params);
|
||||||
|
|
||||||
#if defined(ENABLE_DSD) && defined(HAVE_ALSA_DSD_U32)
|
#if defined(ENABLE_DSD) && defined(HAVE_ALSA_DSD_U32)
|
||||||
if (err == 0)
|
if (err == 0) {
|
||||||
|
params.dsd_u16 = false;
|
||||||
params.dsd_u32 = false;
|
params.dsd_u32 = false;
|
||||||
|
}
|
||||||
|
|
||||||
if (err == -EINVAL && fmt == SND_PCM_FORMAT_DSD_U8) {
|
if (err == -EINVAL && fmt == SND_PCM_FORMAT_DSD_U8) {
|
||||||
/* attempt to switch to DSD_U32 */
|
/* attempt to switch to DSD_U32 */
|
||||||
@@ -404,6 +418,20 @@ AlsaTryFormatDsd(snd_pcm_t *pcm, snd_pcm_hw_params_t *hwparams,
|
|||||||
err = AlsaTryFormatOrByteSwap(pcm, hwparams, fmt, params);
|
err = AlsaTryFormatOrByteSwap(pcm, hwparams, fmt, params);
|
||||||
if (err == 0)
|
if (err == 0)
|
||||||
params.dsd_u32 = true;
|
params.dsd_u32 = true;
|
||||||
|
else
|
||||||
|
fmt = SND_PCM_FORMAT_DSD_U8;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (err == -EINVAL && fmt == SND_PCM_FORMAT_DSD_U8) {
|
||||||
|
/* attempt to switch to DSD_U16 */
|
||||||
|
fmt = IsLittleEndian()
|
||||||
|
? SND_PCM_FORMAT_DSD_U16_LE
|
||||||
|
: SND_PCM_FORMAT_DSD_U16_BE;
|
||||||
|
err = AlsaTryFormatOrByteSwap(pcm, hwparams, fmt, params);
|
||||||
|
if (err == 0)
|
||||||
|
params.dsd_u16 = true;
|
||||||
|
else
|
||||||
|
fmt = SND_PCM_FORMAT_DSD_U8;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -460,51 +488,44 @@ AlsaSetupFormat(snd_pcm_t *pcm, snd_pcm_hw_params_t *hwparams,
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set up the snd_pcm_t object which was opened by the caller. Set up
|
* Wrapper for snd_pcm_hw_params().
|
||||||
* the configured settings and the audio format.
|
|
||||||
*
|
*
|
||||||
* Throws #std::runtime_error on error.
|
* @param buffer_time the configured buffer time, or 0 if not configured
|
||||||
|
* @param period_time the configured period time, or 0 if not configured
|
||||||
|
* @param audio_format an #AudioFormat to be configured (or modified)
|
||||||
|
* by this function
|
||||||
|
* @param params to be modified by this function
|
||||||
*/
|
*/
|
||||||
static void
|
static void
|
||||||
AlsaSetup(AlsaOutput *ad, AudioFormat &audio_format,
|
AlsaSetupHw(snd_pcm_t *pcm, snd_pcm_hw_params_t *hwparams,
|
||||||
PcmExport::Params ¶ms)
|
unsigned buffer_time, unsigned period_time,
|
||||||
|
AudioFormat &audio_format, PcmExport::Params ¶ms)
|
||||||
{
|
{
|
||||||
unsigned int sample_rate = audio_format.sample_rate;
|
|
||||||
unsigned int channels = audio_format.channels;
|
|
||||||
int err;
|
int err;
|
||||||
unsigned retry = MPD_ALSA_RETRY_NR;
|
unsigned retry = MPD_ALSA_RETRY_NR;
|
||||||
unsigned int period_time, period_time_ro;
|
unsigned int period_time_ro = period_time;
|
||||||
unsigned int buffer_time;
|
|
||||||
|
|
||||||
period_time_ro = period_time = ad->period_time;
|
|
||||||
configure_hw:
|
configure_hw:
|
||||||
/* configure HW params */
|
/* configure HW params */
|
||||||
snd_pcm_hw_params_t *hwparams;
|
err = snd_pcm_hw_params_any(pcm, hwparams);
|
||||||
snd_pcm_hw_params_alloca(&hwparams);
|
|
||||||
err = snd_pcm_hw_params_any(ad->pcm, hwparams);
|
|
||||||
if (err < 0)
|
if (err < 0)
|
||||||
throw FormatRuntimeError("snd_pcm_hw_params_any() failed: %s",
|
throw FormatRuntimeError("snd_pcm_hw_params_any() failed: %s",
|
||||||
snd_strerror(-err));
|
snd_strerror(-err));
|
||||||
|
|
||||||
err = snd_pcm_hw_params_set_access(ad->pcm, hwparams,
|
err = snd_pcm_hw_params_set_access(pcm, hwparams,
|
||||||
SND_PCM_ACCESS_RW_INTERLEAVED);
|
SND_PCM_ACCESS_RW_INTERLEAVED);
|
||||||
if (err < 0)
|
if (err < 0)
|
||||||
throw FormatRuntimeError("snd_pcm_hw_params_set_access() failed: %s",
|
throw FormatRuntimeError("snd_pcm_hw_params_set_access() failed: %s",
|
||||||
snd_strerror(-err));
|
snd_strerror(-err));
|
||||||
|
|
||||||
err = AlsaSetupFormat(ad->pcm, hwparams, audio_format, params);
|
err = AlsaSetupFormat(pcm, hwparams, audio_format, params);
|
||||||
if (err < 0)
|
if (err < 0)
|
||||||
throw FormatRuntimeError("Failed to configure format %s: %s",
|
throw FormatRuntimeError("Failed to configure format %s: %s",
|
||||||
sample_format_to_string(audio_format.format),
|
sample_format_to_string(audio_format.format),
|
||||||
snd_strerror(-err));
|
snd_strerror(-err));
|
||||||
|
|
||||||
snd_pcm_format_t format;
|
unsigned int channels = audio_format.channels;
|
||||||
if (snd_pcm_hw_params_get_format(hwparams, &format) == 0)
|
err = snd_pcm_hw_params_set_channels_near(pcm, hwparams,
|
||||||
FormatDebug(alsa_output_domain,
|
|
||||||
"format=%s (%s)", snd_pcm_format_name(format),
|
|
||||||
snd_pcm_format_description(format));
|
|
||||||
|
|
||||||
err = snd_pcm_hw_params_set_channels_near(ad->pcm, hwparams,
|
|
||||||
&channels);
|
&channels);
|
||||||
if (err < 0)
|
if (err < 0)
|
||||||
throw FormatRuntimeError("Failed to configure %i channels: %s",
|
throw FormatRuntimeError("Failed to configure %i channels: %s",
|
||||||
@@ -513,18 +534,23 @@ configure_hw:
|
|||||||
|
|
||||||
audio_format.channels = (int8_t)channels;
|
audio_format.channels = (int8_t)channels;
|
||||||
|
|
||||||
err = snd_pcm_hw_params_set_rate_near(ad->pcm, hwparams,
|
const unsigned requested_sample_rate =
|
||||||
&sample_rate, nullptr);
|
params.CalcOutputSampleRate(audio_format.sample_rate);
|
||||||
|
unsigned output_sample_rate = requested_sample_rate;
|
||||||
|
|
||||||
|
err = snd_pcm_hw_params_set_rate_near(pcm, hwparams,
|
||||||
|
&output_sample_rate, nullptr);
|
||||||
if (err < 0)
|
if (err < 0)
|
||||||
throw FormatRuntimeError("Failed to configure sample rate %u Hz: %s",
|
throw FormatRuntimeError("Failed to configure sample rate %u Hz: %s",
|
||||||
audio_format.sample_rate,
|
requested_sample_rate,
|
||||||
snd_strerror(-err));
|
snd_strerror(-err));
|
||||||
|
|
||||||
if (sample_rate == 0)
|
if (output_sample_rate == 0)
|
||||||
throw FormatRuntimeError("Failed to configure sample rate %u Hz",
|
throw FormatRuntimeError("Failed to configure sample rate %u Hz",
|
||||||
audio_format.sample_rate);
|
audio_format.sample_rate);
|
||||||
|
|
||||||
audio_format.sample_rate = sample_rate;
|
if (output_sample_rate != requested_sample_rate)
|
||||||
|
audio_format.sample_rate = params.CalcInputSampleRate(output_sample_rate);
|
||||||
|
|
||||||
snd_pcm_uframes_t buffer_size_min, buffer_size_max;
|
snd_pcm_uframes_t buffer_size_min, buffer_size_max;
|
||||||
snd_pcm_hw_params_get_buffer_size_min(hwparams, &buffer_size_min);
|
snd_pcm_hw_params_get_buffer_size_min(hwparams, &buffer_size_min);
|
||||||
@@ -546,9 +572,8 @@ configure_hw:
|
|||||||
(unsigned)period_size_min, (unsigned)period_size_max,
|
(unsigned)period_size_min, (unsigned)period_size_max,
|
||||||
period_time_min, period_time_max);
|
period_time_min, period_time_max);
|
||||||
|
|
||||||
if (ad->buffer_time > 0) {
|
if (buffer_time > 0) {
|
||||||
buffer_time = ad->buffer_time;
|
err = snd_pcm_hw_params_set_buffer_time_near(pcm, hwparams,
|
||||||
err = snd_pcm_hw_params_set_buffer_time_near(ad->pcm, hwparams,
|
|
||||||
&buffer_time, nullptr);
|
&buffer_time, nullptr);
|
||||||
if (err < 0)
|
if (err < 0)
|
||||||
throw FormatRuntimeError("snd_pcm_hw_params_set_buffer_time_near() failed: %s",
|
throw FormatRuntimeError("snd_pcm_hw_params_set_buffer_time_near() failed: %s",
|
||||||
@@ -570,14 +595,14 @@ configure_hw:
|
|||||||
|
|
||||||
if (period_time_ro > 0) {
|
if (period_time_ro > 0) {
|
||||||
period_time = period_time_ro;
|
period_time = period_time_ro;
|
||||||
err = snd_pcm_hw_params_set_period_time_near(ad->pcm, hwparams,
|
err = snd_pcm_hw_params_set_period_time_near(pcm, hwparams,
|
||||||
&period_time, nullptr);
|
&period_time, nullptr);
|
||||||
if (err < 0)
|
if (err < 0)
|
||||||
throw FormatRuntimeError("snd_pcm_hw_params_set_period_time_near() failed: %s",
|
throw FormatRuntimeError("snd_pcm_hw_params_set_period_time_near() failed: %s",
|
||||||
snd_strerror(-err));
|
snd_strerror(-err));
|
||||||
}
|
}
|
||||||
|
|
||||||
err = snd_pcm_hw_params(ad->pcm, hwparams);
|
err = snd_pcm_hw_params(pcm, hwparams);
|
||||||
if (err == -EPIPE && --retry > 0 && period_time_ro > 0) {
|
if (err == -EPIPE && --retry > 0 && period_time_ro > 0) {
|
||||||
period_time_ro = period_time_ro >> 1;
|
period_time_ro = period_time_ro >> 1;
|
||||||
goto configure_hw;
|
goto configure_hw;
|
||||||
@@ -587,9 +612,59 @@ configure_hw:
|
|||||||
if (retry != MPD_ALSA_RETRY_NR)
|
if (retry != MPD_ALSA_RETRY_NR)
|
||||||
FormatDebug(alsa_output_domain,
|
FormatDebug(alsa_output_domain,
|
||||||
"ALSA period_time set to %d", period_time);
|
"ALSA period_time set to %d", period_time);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrapper for snd_pcm_sw_params().
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
AlsaSetupSw(snd_pcm_t *pcm, snd_pcm_uframes_t start_threshold,
|
||||||
|
snd_pcm_uframes_t avail_min)
|
||||||
|
{
|
||||||
|
snd_pcm_sw_params_t *swparams;
|
||||||
|
snd_pcm_sw_params_alloca(&swparams);
|
||||||
|
|
||||||
|
int err = snd_pcm_sw_params_current(pcm, swparams);
|
||||||
|
if (err < 0)
|
||||||
|
throw FormatRuntimeError("snd_pcm_sw_params_current() failed: %s",
|
||||||
|
snd_strerror(-err));
|
||||||
|
|
||||||
|
err = snd_pcm_sw_params_set_start_threshold(pcm, swparams,
|
||||||
|
start_threshold);
|
||||||
|
if (err < 0)
|
||||||
|
throw FormatRuntimeError("snd_pcm_sw_params_set_start_threshold() failed: %s",
|
||||||
|
snd_strerror(-err));
|
||||||
|
|
||||||
|
err = snd_pcm_sw_params_set_avail_min(pcm, swparams, avail_min);
|
||||||
|
if (err < 0)
|
||||||
|
throw FormatRuntimeError("snd_pcm_sw_params_set_avail_min() failed: %s",
|
||||||
|
snd_strerror(-err));
|
||||||
|
|
||||||
|
err = snd_pcm_sw_params(pcm, swparams);
|
||||||
|
if (err < 0)
|
||||||
|
throw FormatRuntimeError("snd_pcm_sw_params() failed: %s",
|
||||||
|
snd_strerror(-err));
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void
|
||||||
|
AlsaOutput::Setup(AudioFormat &audio_format,
|
||||||
|
PcmExport::Params ¶ms)
|
||||||
|
{
|
||||||
|
snd_pcm_hw_params_t *hwparams;
|
||||||
|
snd_pcm_hw_params_alloca(&hwparams);
|
||||||
|
|
||||||
|
AlsaSetupHw(pcm, hwparams,
|
||||||
|
buffer_time, period_time,
|
||||||
|
audio_format, params);
|
||||||
|
|
||||||
|
snd_pcm_format_t format;
|
||||||
|
if (snd_pcm_hw_params_get_format(hwparams, &format) == 0)
|
||||||
|
FormatDebug(alsa_output_domain,
|
||||||
|
"format=%s (%s)", snd_pcm_format_name(format),
|
||||||
|
snd_pcm_format_description(format));
|
||||||
|
|
||||||
snd_pcm_uframes_t alsa_buffer_size;
|
snd_pcm_uframes_t alsa_buffer_size;
|
||||||
err = snd_pcm_hw_params_get_buffer_size(hwparams, &alsa_buffer_size);
|
int err = snd_pcm_hw_params_get_buffer_size(hwparams, &alsa_buffer_size);
|
||||||
if (err < 0)
|
if (err < 0)
|
||||||
throw FormatRuntimeError("snd_pcm_hw_params_get_buffer_size() failed: %s",
|
throw FormatRuntimeError("snd_pcm_hw_params_get_buffer_size() failed: %s",
|
||||||
snd_strerror(-err));
|
snd_strerror(-err));
|
||||||
@@ -601,32 +676,8 @@ configure_hw:
|
|||||||
throw FormatRuntimeError("snd_pcm_hw_params_get_period_size() failed: %s",
|
throw FormatRuntimeError("snd_pcm_hw_params_get_period_size() failed: %s",
|
||||||
snd_strerror(-err));
|
snd_strerror(-err));
|
||||||
|
|
||||||
/* configure SW params */
|
AlsaSetupSw(pcm, alsa_buffer_size - alsa_period_size,
|
||||||
snd_pcm_sw_params_t *swparams;
|
|
||||||
snd_pcm_sw_params_alloca(&swparams);
|
|
||||||
|
|
||||||
err = snd_pcm_sw_params_current(ad->pcm, swparams);
|
|
||||||
if (err < 0)
|
|
||||||
throw FormatRuntimeError("snd_pcm_sw_params_current() failed: %s",
|
|
||||||
snd_strerror(-err));
|
|
||||||
|
|
||||||
err = snd_pcm_sw_params_set_start_threshold(ad->pcm, swparams,
|
|
||||||
alsa_buffer_size -
|
|
||||||
alsa_period_size);
|
alsa_period_size);
|
||||||
if (err < 0)
|
|
||||||
throw FormatRuntimeError("snd_pcm_sw_params_set_start_threshold() failed: %s",
|
|
||||||
snd_strerror(-err));
|
|
||||||
|
|
||||||
err = snd_pcm_sw_params_set_avail_min(ad->pcm, swparams,
|
|
||||||
alsa_period_size);
|
|
||||||
if (err < 0)
|
|
||||||
throw FormatRuntimeError("snd_pcm_sw_params_set_avail_min() failed: %s",
|
|
||||||
snd_strerror(-err));
|
|
||||||
|
|
||||||
err = snd_pcm_sw_params(ad->pcm, swparams);
|
|
||||||
if (err < 0)
|
|
||||||
throw FormatRuntimeError("snd_pcm_sw_params() failed: %s",
|
|
||||||
snd_strerror(-err));
|
|
||||||
|
|
||||||
FormatDebug(alsa_output_domain, "buffer_size=%u period_size=%u",
|
FormatDebug(alsa_output_domain, "buffer_size=%u period_size=%u",
|
||||||
(unsigned)alsa_buffer_size, (unsigned)alsa_period_size);
|
(unsigned)alsa_buffer_size, (unsigned)alsa_period_size);
|
||||||
@@ -639,13 +690,12 @@ configure_hw:
|
|||||||
happen again. */
|
happen again. */
|
||||||
alsa_period_size = 1;
|
alsa_period_size = 1;
|
||||||
|
|
||||||
ad->period_frames = alsa_period_size;
|
period_frames = alsa_period_size;
|
||||||
ad->period_position = 0;
|
period_position = 0;
|
||||||
|
|
||||||
ad->silence = new uint8_t[snd_pcm_frames_to_bytes(ad->pcm,
|
silence = new uint8_t[snd_pcm_frames_to_bytes(pcm, alsa_period_size)];
|
||||||
alsa_period_size)];
|
snd_pcm_format_set_silence(format, silence,
|
||||||
snd_pcm_format_set_silence(format, ad->silence,
|
alsa_period_size * audio_format.channels);
|
||||||
alsa_period_size * channels);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -662,11 +712,10 @@ AlsaOutput::SetupDop(const AudioFormat audio_format,
|
|||||||
|
|
||||||
AudioFormat dop_format = audio_format;
|
AudioFormat dop_format = audio_format;
|
||||||
dop_format.format = SampleFormat::S24_P32;
|
dop_format.format = SampleFormat::S24_P32;
|
||||||
dop_format.sample_rate /= 2;
|
|
||||||
|
|
||||||
const AudioFormat check = dop_format;
|
const AudioFormat check = dop_format;
|
||||||
|
|
||||||
AlsaSetup(this, dop_format, params);
|
Setup(dop_format, params);
|
||||||
|
|
||||||
/* if the device allows only 32 bit, shift all DoP
|
/* if the device allows only 32 bit, shift all DoP
|
||||||
samples left by 8 bit and leave the lower 8 bit cleared;
|
samples left by 8 bit and leave the lower 8 bit cleared;
|
||||||
@@ -694,17 +743,18 @@ AlsaOutput::SetupOrDop(AudioFormat &audio_format, PcmExport::Params ¶ms)
|
|||||||
std::exception_ptr dop_error;
|
std::exception_ptr dop_error;
|
||||||
if (dop && audio_format.format == SampleFormat::DSD) {
|
if (dop && audio_format.format == SampleFormat::DSD) {
|
||||||
try {
|
try {
|
||||||
SetupDop(audio_format, params);
|
|
||||||
params.dop = true;
|
params.dop = true;
|
||||||
|
SetupDop(audio_format, params);
|
||||||
return;
|
return;
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
dop_error = std::current_exception();
|
dop_error = std::current_exception();
|
||||||
|
params.dop = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
#endif
|
#endif
|
||||||
AlsaSetup(this, audio_format, params);
|
Setup(audio_format, params);
|
||||||
#ifdef ENABLE_DSD
|
#ifdef ENABLE_DSD
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
if (dop_error)
|
if (dop_error)
|
||||||
@@ -742,6 +792,11 @@ AlsaOutput::Open(AudioFormat &audio_format)
|
|||||||
GetDevice()));
|
GetDevice()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef ENABLE_DSD
|
||||||
|
if (params.dop)
|
||||||
|
FormatDebug(alsa_output_domain, "DoP (DSD over PCM) enabled");
|
||||||
|
#endif
|
||||||
|
|
||||||
pcm_export->Open(audio_format.format,
|
pcm_export->Open(audio_format.format,
|
||||||
audio_format.channels,
|
audio_format.channels,
|
||||||
params);
|
params);
|
||||||
@@ -777,6 +832,7 @@ AlsaOutput::Recover(int err)
|
|||||||
#if GCC_CHECK_VERSION(7,0)
|
#if GCC_CHECK_VERSION(7,0)
|
||||||
[[fallthrough]];
|
[[fallthrough]];
|
||||||
#endif
|
#endif
|
||||||
|
case SND_PCM_STATE_OPEN:
|
||||||
case SND_PCM_STATE_SETUP:
|
case SND_PCM_STATE_SETUP:
|
||||||
case SND_PCM_STATE_XRUN:
|
case SND_PCM_STATE_XRUN:
|
||||||
period_position = 0;
|
period_position = 0;
|
||||||
@@ -785,12 +841,11 @@ AlsaOutput::Recover(int err)
|
|||||||
case SND_PCM_STATE_DISCONNECTED:
|
case SND_PCM_STATE_DISCONNECTED:
|
||||||
break;
|
break;
|
||||||
/* this is no error, so just keep running */
|
/* this is no error, so just keep running */
|
||||||
|
case SND_PCM_STATE_PREPARED:
|
||||||
case SND_PCM_STATE_RUNNING:
|
case SND_PCM_STATE_RUNNING:
|
||||||
|
case SND_PCM_STATE_DRAINING:
|
||||||
err = 0;
|
err = 0;
|
||||||
break;
|
break;
|
||||||
default:
|
|
||||||
/* unknown state, do nothing */
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return err;
|
return err;
|
||||||
@@ -822,6 +877,8 @@ AlsaOutput::Cancel()
|
|||||||
must_prepare = true;
|
must_prepare = true;
|
||||||
|
|
||||||
snd_pcm_drop(pcm);
|
snd_pcm_drop(pcm);
|
||||||
|
|
||||||
|
pcm_export->Reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void
|
inline void
|
||||||
@@ -831,6 +888,36 @@ AlsaOutput::Close()
|
|||||||
delete[] silence;
|
delete[] silence;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline size_t
|
||||||
|
AlsaOutput::PlayRaw(ConstBuffer<void> data)
|
||||||
|
{
|
||||||
|
if (data.IsEmpty())
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
assert(data.size % out_frame_size == 0);
|
||||||
|
|
||||||
|
const size_t n_frames = data.size / out_frame_size;
|
||||||
|
assert(n_frames > 0);
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
const auto frames_written = snd_pcm_writei(pcm, data.data,
|
||||||
|
n_frames);
|
||||||
|
if (frames_written > 0) {
|
||||||
|
period_position = (period_position + frames_written)
|
||||||
|
% period_frames;
|
||||||
|
|
||||||
|
return frames_written * out_frame_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (frames_written < 0 && frames_written != -EAGAIN &&
|
||||||
|
frames_written != -EINTR &&
|
||||||
|
Recover(frames_written) < 0)
|
||||||
|
throw FormatRuntimeError("snd_pcm_writei() failed: %s",
|
||||||
|
snd_strerror(-frames_written));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
inline size_t
|
inline size_t
|
||||||
AlsaOutput::Play(const void *chunk, size_t size)
|
AlsaOutput::Play(const void *chunk, size_t size)
|
||||||
{
|
{
|
||||||
@@ -856,29 +943,8 @@ AlsaOutput::Play(const void *chunk, size_t size)
|
|||||||
been played */
|
been played */
|
||||||
return size;
|
return size;
|
||||||
|
|
||||||
chunk = e.data;
|
const size_t bytes_written = PlayRaw(e);
|
||||||
size = e.size;
|
|
||||||
|
|
||||||
assert(size % out_frame_size == 0);
|
|
||||||
|
|
||||||
size /= out_frame_size;
|
|
||||||
assert(size > 0);
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
snd_pcm_sframes_t ret = snd_pcm_writei(pcm, chunk, size);
|
|
||||||
if (ret > 0) {
|
|
||||||
period_position = (period_position + ret)
|
|
||||||
% period_frames;
|
|
||||||
|
|
||||||
size_t bytes_written = ret * out_frame_size;
|
|
||||||
return pcm_export->CalcSourceSize(bytes_written);
|
return pcm_export->CalcSourceSize(bytes_written);
|
||||||
}
|
|
||||||
|
|
||||||
if (ret < 0 && ret != -EAGAIN && ret != -EINTR &&
|
|
||||||
Recover(ret) < 0)
|
|
||||||
throw FormatRuntimeError("snd_pcm_writei() failed: %s",
|
|
||||||
snd_strerror(-ret));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
typedef AudioOutputWrapper<AlsaOutput> Wrapper;
|
typedef AudioOutputWrapper<AlsaOutput> Wrapper;
|
||||||
|
@@ -659,6 +659,10 @@ OssOutput::Cancel()
|
|||||||
ioctl(fd, SNDCTL_DSP_RESET, 0);
|
ioctl(fd, SNDCTL_DSP_RESET, 0);
|
||||||
DoClose();
|
DoClose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef AFMT_S24_PACKED
|
||||||
|
pcm_export->Reset();
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
inline size_t
|
inline size_t
|
||||||
|
@@ -274,7 +274,7 @@ RecorderOutput::ReopenFormat(AllocatedPath &&new_path)
|
|||||||
assert(path.IsNull());
|
assert(path.IsNull());
|
||||||
assert(file == nullptr);
|
assert(file == nullptr);
|
||||||
|
|
||||||
FileOutputStream *new_file = new FileOutputStream(path);
|
FileOutputStream *new_file = new FileOutputStream(new_path);
|
||||||
|
|
||||||
AudioFormat new_audio_format = effective_audio_format;
|
AudioFormat new_audio_format = effective_audio_format;
|
||||||
|
|
||||||
|
@@ -24,8 +24,16 @@
|
|||||||
#include "util/Domain.hxx"
|
#include "util/Domain.hxx"
|
||||||
#include "Log.hxx"
|
#include "Log.hxx"
|
||||||
|
|
||||||
|
/* work around a C++ incompatibility if the sndio API is emulated by
|
||||||
|
libroar: libroar's "struct roar_service_stream" has a member named
|
||||||
|
"new", which is an illegal identifier in C++ */
|
||||||
|
#define new new_
|
||||||
|
|
||||||
#include <sndio.h>
|
#include <sndio.h>
|
||||||
|
|
||||||
|
/* undo the libroar workaround */
|
||||||
|
#undef new
|
||||||
|
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
|
|
||||||
#ifndef SIO_DEVANY
|
#ifndef SIO_DEVANY
|
||||||
|
@@ -60,7 +60,11 @@ icy_server_metadata_string(const char *stream_title, const char* stream_url)
|
|||||||
{
|
{
|
||||||
// The leading n is a placeholder for the length information
|
// The leading n is a placeholder for the length information
|
||||||
auto icy_metadata = FormatString("nStreamTitle='%s';"
|
auto icy_metadata = FormatString("nStreamTitle='%s';"
|
||||||
"StreamUrl='%s';",
|
"StreamUrl='%s';"
|
||||||
|
/* pad 15 spaces just in case
|
||||||
|
the length needs to be
|
||||||
|
rounded up */
|
||||||
|
" ",
|
||||||
stream_title,
|
stream_title,
|
||||||
stream_url);
|
stream_url);
|
||||||
|
|
||||||
@@ -68,7 +72,7 @@ icy_server_metadata_string(const char *stream_title, const char* stream_url)
|
|||||||
|
|
||||||
meta_length--; // subtract placeholder
|
meta_length--; // subtract placeholder
|
||||||
|
|
||||||
meta_length = ((int)meta_length / 16) + 1;
|
meta_length = meta_length / 16;
|
||||||
|
|
||||||
icy_metadata[0] = meta_length;
|
icy_metadata[0] = meta_length;
|
||||||
|
|
||||||
@@ -109,5 +113,5 @@ icy_server_metadata_page(const Tag &tag, const TagType *types)
|
|||||||
if (icy_string.IsNull())
|
if (icy_string.IsNull())
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
|
||||||
return Page::Copy(icy_string.c_str(), (icy_string[0] * 16) + 1);
|
return Page::Copy(icy_string.c_str(), uint8_t(icy_string[0]) * 16 + 1);
|
||||||
}
|
}
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2011-2012 Max Kellermann <max@duempel.org>
|
* Copyright (C) 2011-2012 Max Kellermann <max.kellermann@gmail.com>
|
||||||
*
|
*
|
||||||
* Redistribution and use in source and binary forms, with or without
|
* Redistribution and use in source and binary forms, with or without
|
||||||
* modification, are permitted provided that the following conditions
|
* modification, are permitted provided that the following conditions
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2011-2012 Max Kellermann <max@duempel.org>
|
* Copyright (C) 2011-2012 Max Kellermann <max.kellermann@gmail.com>
|
||||||
*
|
*
|
||||||
* Redistribution and use in source and binary forms, with or without
|
* Redistribution and use in source and binary forms, with or without
|
||||||
* modification, are permitted provided that the following conditions
|
* modification, are permitted provided that the following conditions
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2011-2012 Max Kellermann <max@duempel.org>
|
* Copyright (C) 2011-2012 Max Kellermann <max.kellermann@gmail.com>
|
||||||
*
|
*
|
||||||
* Redistribution and use in source and binary forms, with or without
|
* Redistribution and use in source and binary forms, with or without
|
||||||
* modification, are permitted provided that the following conditions
|
* modification, are permitted provided that the following conditions
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2011-2012 Max Kellermann <max@duempel.org>
|
* Copyright (C) 2011-2012 Max Kellermann <max.kellermann@gmail.com>
|
||||||
*
|
*
|
||||||
* Redistribution and use in source and binary forms, with or without
|
* Redistribution and use in source and binary forms, with or without
|
||||||
* modification, are permitted provided that the following conditions
|
* modification, are permitted provided that the following conditions
|
||||||
|
@@ -21,7 +21,7 @@
|
|||||||
#define MPD_PCM_CHANNELS_CONVERTER_HXX
|
#define MPD_PCM_CHANNELS_CONVERTER_HXX
|
||||||
|
|
||||||
#include "check.h"
|
#include "check.h"
|
||||||
#include "AudioFormat.hxx"
|
#include "SampleFormat.hxx"
|
||||||
#include "PcmBuffer.hxx"
|
#include "PcmBuffer.hxx"
|
||||||
|
|
||||||
#ifndef NDEBUG
|
#ifndef NDEBUG
|
||||||
|
62
src/pcm/Dsd16.cxx
Normal file
62
src/pcm/Dsd16.cxx
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2003-2017 The Music Player Daemon Project
|
||||||
|
* http://www.musicpd.org
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along
|
||||||
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
#include "Dsd16.hxx"
|
||||||
|
#include "PcmBuffer.hxx"
|
||||||
|
#include "util/ConstBuffer.hxx"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a 16 bit integer from two bytes.
|
||||||
|
*/
|
||||||
|
static constexpr inline uint16_t
|
||||||
|
Construct16(uint8_t a, uint8_t b)
|
||||||
|
{
|
||||||
|
/* "a" is the oldest byte, which must be in the most
|
||||||
|
significant byte */
|
||||||
|
|
||||||
|
return uint16_t(b) | (uint16_t(a) << 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
static constexpr inline uint16_t
|
||||||
|
Dsd8To16Sample(const uint8_t *src, unsigned channels)
|
||||||
|
{
|
||||||
|
return Construct16(src[0], src[channels]);
|
||||||
|
}
|
||||||
|
|
||||||
|
ConstBuffer<uint16_t>
|
||||||
|
Dsd8To16(PcmBuffer &buffer, unsigned channels, ConstBuffer<uint8_t> _src)
|
||||||
|
{
|
||||||
|
const size_t in_frames = _src.size / channels;
|
||||||
|
const size_t out_frames = in_frames / 2;
|
||||||
|
const size_t out_samples = out_frames * channels;
|
||||||
|
|
||||||
|
const uint8_t *src = _src.data;
|
||||||
|
uint16_t *const dest0 = buffer.GetT<uint16_t>(out_samples);
|
||||||
|
uint16_t *dest = dest0;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < out_frames; ++i) {
|
||||||
|
for (size_t c = 0; c < channels; ++c)
|
||||||
|
*dest++ = Dsd8To16Sample(src++, channels);
|
||||||
|
|
||||||
|
src += channels;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {dest0, out_samples};
|
||||||
|
}
|
36
src/pcm/Dsd16.hxx
Normal file
36
src/pcm/Dsd16.hxx
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2003-2017 The Music Player Daemon Project
|
||||||
|
* http://www.musicpd.org
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along
|
||||||
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef MPD_PCM_DSD_16_HXX
|
||||||
|
#define MPD_PCM_DSD_16_HXX
|
||||||
|
|
||||||
|
#include "check.h"
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
template<typename T> struct ConstBuffer;
|
||||||
|
class PcmBuffer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert DSD_U8 to DSD_U16 (native endian, oldest bits in MSB).
|
||||||
|
*/
|
||||||
|
ConstBuffer<uint16_t>
|
||||||
|
Dsd8To16(PcmBuffer &buffer, unsigned channels, ConstBuffer<uint8_t> src);
|
||||||
|
|
||||||
|
#endif
|
64
src/pcm/Dsd32.cxx
Normal file
64
src/pcm/Dsd32.cxx
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2003-2017 The Music Player Daemon Project
|
||||||
|
* http://www.musicpd.org
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along
|
||||||
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
#include "Dsd32.hxx"
|
||||||
|
#include "PcmBuffer.hxx"
|
||||||
|
#include "util/ConstBuffer.hxx"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a 32 bit integer from four bytes.
|
||||||
|
*/
|
||||||
|
static constexpr inline uint32_t
|
||||||
|
Construct32(uint8_t a, uint8_t b, uint8_t c, uint8_t d)
|
||||||
|
{
|
||||||
|
/* "a" is the oldest byte, which must be in the most
|
||||||
|
significant byte */
|
||||||
|
|
||||||
|
return uint32_t(d) | (uint32_t(c) << 8) |
|
||||||
|
(uint32_t(b) << 16) | (uint32_t(a) << 24);
|
||||||
|
}
|
||||||
|
|
||||||
|
static constexpr inline uint32_t
|
||||||
|
Dsd8To32Sample(const uint8_t *src, unsigned channels)
|
||||||
|
{
|
||||||
|
return Construct32(src[0], src[channels],
|
||||||
|
src[2 * channels], src[3 * channels]);
|
||||||
|
}
|
||||||
|
|
||||||
|
ConstBuffer<uint32_t>
|
||||||
|
Dsd8To32(PcmBuffer &buffer, unsigned channels, ConstBuffer<uint8_t> _src)
|
||||||
|
{
|
||||||
|
const size_t in_frames = _src.size / channels;
|
||||||
|
const size_t out_frames = in_frames / 4;
|
||||||
|
const size_t out_samples = out_frames * channels;
|
||||||
|
|
||||||
|
const uint8_t *src = _src.data;
|
||||||
|
uint32_t *const dest0 = buffer.GetT<uint32_t>(out_samples);
|
||||||
|
uint32_t *dest = dest0;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < out_frames; ++i) {
|
||||||
|
for (size_t c = 0; c < channels; ++c)
|
||||||
|
*dest++ = Dsd8To32Sample(src++, channels);
|
||||||
|
|
||||||
|
src += 3 * channels;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {dest0, out_samples};
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user