Compare commits
180 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 | ||
![]() |
ed80863eac | ||
![]() |
c3fc84de12 | ||
![]() |
904f83cd85 | ||
![]() |
28bf100a50 | ||
![]() |
accbd4e82a | ||
![]() |
d7f478c154 | ||
![]() |
8f7f13fea4 | ||
![]() |
c82b03a74c | ||
![]() |
58fb36bdb9 | ||
![]() |
4297a7b0a4 | ||
![]() |
1bab6d0dd7 | ||
![]() |
13b85edbe2 | ||
![]() |
dc53098e43 | ||
![]() |
3c66feff5a | ||
![]() |
218c3bc0d5 | ||
![]() |
9f5eddcd13 | ||
![]() |
3cba76552b | ||
![]() |
e98a8b624b | ||
![]() |
6c6947b01f | ||
![]() |
78c91e9e5b | ||
![]() |
44493ca0c4 | ||
![]() |
42acf78b09 | ||
![]() |
3aa9f8af18 | ||
![]() |
8a32ee30a5 | ||
![]() |
981dc0626b | ||
![]() |
8986d14e98 | ||
![]() |
5163b1a624 | ||
![]() |
860aa9d6d0 | ||
![]() |
64dc5212f9 | ||
![]() |
6cff3214f3 | ||
![]() |
fd910bd5e9 | ||
![]() |
c6086bed41 | ||
![]() |
1a9dfdfab8 | ||
![]() |
5284cd11a9 | ||
![]() |
d1a47cffad | ||
![]() |
f469595eee | ||
![]() |
9cfc52f114 | ||
![]() |
30bfb756c2 |
AUTHORSMakefile.amNEWS
android
configure.acdoc
m4
python/build
src
AudioFormat.cxxAudioFormat.hxxAudioParser.cxxCommandLine.cxxDetachedSong.hxxMain.cxxPartition.cxxPlaylistFile.cxxSongFilter.cxx
command
db
decoder
event
filter
input
java
lib
curl
Easy.hxxGlobal.cxxGlobal.hxxHandler.hxxMulti.hxxRequest.cxxRequest.hxxSlist.hxxVersion.cxxVersion.hxx
expat
nfs
upnp
mixer
net
AllocatedSocketAddress.cxxAllocatedSocketAddress.hxxSocketAddress.cxxSocketAddress.hxxStaticSocketAddress.cxxStaticSocketAddress.hxxToString.cxxToString.hxx
output
pcm
ChannelsConverter.hxxDsd16.cxxDsd16.hxxDsd32.cxxDsd32.hxxFormatConverter.hxxGlueResampler.cxxGlueResampler.hxxLibsamplerateResampler.cxxLibsamplerateResampler.hxxOrder.hxxPcmBuffer.hxxPcmChannels.cxxPcmConvert.cxxPcmConvert.hxxPcmDop.cxxPcmDsd.cxxPcmDsd.hxxPcmExport.cxxPcmExport.hxxPcmFormat.hxxPcmMix.cxxPcmMix.hxxResampler.hxxSampleFormat.cxxSampleFormat.hxxSilence.cxxTraits.hxxVolume.cxxVolume.hxx
playlist
plugins
storage
system
tag
thread
util
ASCII.hxxAllocatedArray.hxxAllocatedString.cxxAllocatedString.hxxBindMethod.hxxCast.hxxCharUtil.hxxCircularBuffer.hxxClamp.hxxConstBuffer.hxxDeleteDisposer.hxxDomain.hxxDynamicFifoBuffer.hxxException.cxxException.hxxForeignFifoBuffer.hxxHugeAllocator.cxxHugeAllocator.hxxIterableSplitString.hxxMacros.hxxManual.hxxNumberParser.hxxReusableArray.hxxRuntimeError.hxxScopeExit.hxxSplitString.cxxSplitString.hxxStaticFifoBuffer.hxxStringAPI.hxxStringBuffer.hxxStringCompare.cxxStringCompare.hxxStringPointer.hxxStringView.cxxStringView.hxxTextFile.hxxTimeParser.cxxTimeParser.hxxTokenizer.cxxTokenizer.hxxUTF8.cxxUTF8.hxxUriUtil.cxxUriUtil.hxxVarSize.hxxWStringAPI.hxxWStringCompare.hxxWritableBuffer.hxx
test
FakeDecoderAPI.cxxTestAudioFormat.cxxTestAudioFormat.hxxread_tags.cxxrun_filter.cxxrun_output.cxxrun_storage.cxxtest_pcm_all.hxxtest_pcm_channels.cxxtest_pcm_dither.cxxtest_pcm_export.cxxtest_pcm_format.cxxtest_pcm_main.cxxtest_pcm_pack.cxx
win32
2
AUTHORS
2
AUTHORS
@@ -5,7 +5,7 @@ The following people have contributed code to MPD:
|
||||
|
||||
Warren Dukes <warren.dukes@gmail.com>
|
||||
Avuton Olrich <avuton@gmail.com>
|
||||
Max Kellermann <max@duempel.org>
|
||||
Max Kellermann <max.kellermann@gmail.com>
|
||||
Laszlo Ashin <kodest@gmail.com>
|
||||
Viliam Mateicka <viliam.mateicka@gmail.com>
|
||||
Eric Wollesen <encoded@xmtp.net>
|
||||
|
50
Makefile.am
50
Makefile.am
@@ -233,6 +233,15 @@ libmpd_a_SOURCES += \
|
||||
src/db/Selection.cxx src/db/Selection.hxx
|
||||
endif
|
||||
|
||||
CURL_SOURCES = \
|
||||
src/lib/curl/Version.cxx src/lib/curl/Version.hxx \
|
||||
src/lib/curl/Global.cxx src/lib/curl/Global.hxx \
|
||||
src/lib/curl/Request.cxx src/lib/curl/Request.hxx \
|
||||
src/lib/curl/Handler.hxx \
|
||||
src/lib/curl/Easy.hxx \
|
||||
src/lib/curl/Multi.hxx \
|
||||
src/lib/curl/Slist.hxx
|
||||
|
||||
UPNP_SOURCES = \
|
||||
src/lib/upnp/Init.cxx src/lib/upnp/Init.hxx \
|
||||
src/lib/upnp/ClientInit.cxx src/lib/upnp/ClientInit.hxx \
|
||||
@@ -406,6 +415,7 @@ libutil_a_SOURCES = \
|
||||
src/util/CharUtil.hxx \
|
||||
src/util/NumberParser.hxx \
|
||||
src/util/MimeType.cxx src/util/MimeType.hxx \
|
||||
src/util/StringBuffer.hxx \
|
||||
src/util/StringPointer.hxx \
|
||||
src/util/StringView.cxx src/util/StringView.hxx \
|
||||
src/util/AllocatedString.cxx src/util/AllocatedString.hxx \
|
||||
@@ -420,6 +430,7 @@ libutil_a_SOURCES = \
|
||||
src/util/FormatString.cxx src/util/FormatString.hxx \
|
||||
src/util/Tokenizer.cxx src/util/Tokenizer.hxx \
|
||||
src/util/TextFile.hxx \
|
||||
src/util/TimeParser.cxx src/util/TimeParser.hxx \
|
||||
src/util/UriUtil.cxx src/util/UriUtil.hxx \
|
||||
src/util/Manual.hxx \
|
||||
src/util/RefCount.hxx \
|
||||
@@ -529,6 +540,10 @@ ICU_LDADD = libicu.a $(ICU_LIBS)
|
||||
# PCM library
|
||||
|
||||
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/Interleave.cxx src/pcm/Interleave.hxx \
|
||||
src/pcm/PcmBuffer.cxx src/pcm/PcmBuffer.hxx \
|
||||
@@ -565,6 +580,8 @@ PCM_LIBS = \
|
||||
|
||||
if ENABLE_DSD
|
||||
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/dsd2pcm/dsd2pcm.c src/pcm/dsd2pcm/dsd2pcm.h
|
||||
endif
|
||||
@@ -603,7 +620,8 @@ libxiph_a_SOURCES += \
|
||||
src/lib/xiph/OggStreamState.hxx
|
||||
endif
|
||||
|
||||
XIPH_LIBS = libxiph.a
|
||||
XIPH_LIBS = libxiph.a \
|
||||
$(OGG_LIBS)
|
||||
|
||||
endif
|
||||
|
||||
@@ -689,6 +707,8 @@ libstorage_a_CPPFLAGS = $(AM_CPPFLAGS) \
|
||||
|
||||
STORAGE_LIBS = \
|
||||
libstorage.a \
|
||||
$(CURL_LIBS) \
|
||||
$(EXPAT_LIBS) \
|
||||
$(NFS_LIBS) \
|
||||
$(SMBCLIENT_LIBS)
|
||||
|
||||
@@ -704,6 +724,12 @@ libstorage_a_SOURCES += \
|
||||
src/storage/plugins/NfsStorage.cxx src/storage/plugins/NfsStorage.hxx
|
||||
endif
|
||||
|
||||
if ENABLE_WEBDAV
|
||||
libstorage_a_SOURCES += \
|
||||
src/lib/expat/ExpatParser.cxx \
|
||||
src/storage/plugins/CurlStorage.cxx src/storage/plugins/CurlStorage.hxx
|
||||
endif
|
||||
|
||||
endif
|
||||
|
||||
# neighbor plugins
|
||||
@@ -851,9 +877,6 @@ ARCHIVE_LIBS =
|
||||
endif
|
||||
|
||||
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/ReplayGainMode.cxx src/ReplayGainMode.hxx \
|
||||
src/ReplayGainInfo.cxx src/ReplayGainInfo.hxx
|
||||
@@ -1279,12 +1302,7 @@ if ENABLE_CURL
|
||||
libinput_a_SOURCES += \
|
||||
src/input/IcyInputStream.cxx src/input/IcyInputStream.hxx \
|
||||
src/input/plugins/CurlInputPlugin.cxx src/input/plugins/CurlInputPlugin.hxx \
|
||||
src/lib/curl/Version.cxx src/lib/curl/Version.hxx \
|
||||
src/lib/curl/Global.cxx src/lib/curl/Global.hxx \
|
||||
src/lib/curl/Request.cxx src/lib/curl/Request.hxx \
|
||||
src/lib/curl/Handler.hxx \
|
||||
src/lib/curl/Easy.hxx \
|
||||
src/lib/curl/Multi.hxx \
|
||||
$(CURL_SOURCES) \
|
||||
src/IcyMetaDataParser.cxx src/IcyMetaDataParser.hxx
|
||||
endif
|
||||
|
||||
@@ -1382,6 +1400,7 @@ libmixer_plugins_a_SOURCES = \
|
||||
src/mixer/plugins/NullMixerPlugin.cxx \
|
||||
src/mixer/plugins/SoftwareMixerPlugin.cxx \
|
||||
src/mixer/plugins/SoftwareMixerPlugin.hxx
|
||||
|
||||
libmixer_plugins_a_CPPFLAGS = $(AM_CPPFLAGS) \
|
||||
$(ALSA_CFLAGS) \
|
||||
$(PULSE_CFLAGS)
|
||||
@@ -1390,7 +1409,10 @@ if ENABLE_ALSA
|
||||
liboutput_plugins_a_SOURCES += \
|
||||
src/output/plugins/AlsaOutputPlugin.cxx \
|
||||
src/output/plugins/AlsaOutputPlugin.hxx
|
||||
libmixer_plugins_a_SOURCES += src/mixer/plugins/AlsaMixerPlugin.cxx
|
||||
libmixer_plugins_a_SOURCES += \
|
||||
src/mixer/plugins/volume_mapping.h \
|
||||
src/mixer/plugins/volume_mapping.c \
|
||||
src/mixer/plugins/AlsaMixerPlugin.cxx
|
||||
endif
|
||||
|
||||
if ANDROID
|
||||
@@ -1573,6 +1595,7 @@ endif
|
||||
|
||||
if ENABLE_EXPAT
|
||||
libplaylist_plugins_a_SOURCES += \
|
||||
src/lib/expat/StreamExpatParser.cxx \
|
||||
src/lib/expat/ExpatParser.cxx src/lib/expat/ExpatParser.hxx \
|
||||
src/playlist/plugins/XspfPlaylistPlugin.cxx \
|
||||
src/playlist/plugins/XspfPlaylistPlugin.hxx \
|
||||
@@ -1758,6 +1781,10 @@ test_run_storage_SOURCES = \
|
||||
test/ScopeIOThread.hxx \
|
||||
test/run_storage.cxx
|
||||
|
||||
if ENABLE_WEBDAV
|
||||
test_run_storage_SOURCES += $(CURL_SOURCES)
|
||||
endif
|
||||
|
||||
endif
|
||||
|
||||
test_run_input_LDADD = \
|
||||
@@ -2203,6 +2230,7 @@ test_test_icy_parser_LDADD = \
|
||||
endif
|
||||
|
||||
test_test_pcm_SOURCES = \
|
||||
test/TestAudioFormat.cxx test/TestAudioFormat.hxx \
|
||||
test/test_pcm_util.hxx \
|
||||
test/test_pcm_dither.cxx \
|
||||
test/test_pcm_pack.cxx \
|
||||
|
69
NEWS
69
NEWS
@@ -1,3 +1,72 @@
|
||||
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)
|
||||
* input
|
||||
- curl: fix crash bug
|
||||
- curl: fix freeze bug
|
||||
* decoder
|
||||
- wavpack: fix crash bug
|
||||
* storage
|
||||
- curl: new storage plugin for WebDAV (work in progress)
|
||||
* mixer
|
||||
- alsa: normalize displayed volume according to human perception
|
||||
* fix crash with volume_normalization enabled
|
||||
|
||||
ver 0.20 (2017/01/04)
|
||||
* protocol
|
||||
- "commands" returns playlist commands only if playlist_directory configured
|
||||
|
@@ -87,9 +87,14 @@ class AndroidNdkToolchain:
|
||||
self.is_armv7 = self.is_arm and 'armv7' in self.cflags
|
||||
self.is_windows = False
|
||||
|
||||
libstdcxx_path = os.path.join(ndk_path, 'sources/cxx-stl/gnu-libstdc++', gcc_version)
|
||||
libstdcxx_cppflags = '-isystem ' + os.path.join(libstdcxx_path, 'include') + ' -isystem ' + os.path.join(libstdcxx_path, 'libs', android_abi, 'include')
|
||||
libstdcxx_ldadd = os.path.join(libstdcxx_path, 'libs', android_abi, 'libgnustl_static.a')
|
||||
libcxx_path = os.path.join(ndk_path, 'sources/cxx-stl/llvm-libc++')
|
||||
libcxx_libs_path = os.path.join(libcxx_path, 'libs', android_abi)
|
||||
|
||||
libstdcxx_cppflags = '-nostdinc++ -isystem ' + os.path.join(libcxx_path, 'include') + ' -isystem ' + os.path.join(ndk_path, 'sources/android/support/include')
|
||||
libstdcxx_ldadd = os.path.join(libcxx_libs_path, 'libc++_static.a') + ' ' + os.path.join(libcxx_libs_path, 'libc++abi.a')
|
||||
|
||||
if self.is_armv7:
|
||||
libstdcxx_ldadd += ' ' + os.path.join(libcxx_libs_path, 'libunwind.a')
|
||||
|
||||
if use_cxx:
|
||||
self.libs += ' ' + libstdcxx_ldadd
|
||||
|
19
configure.ac
19
configure.ac
@@ -1,10 +1,10 @@
|
||||
AC_PREREQ(2.60)
|
||||
|
||||
AC_INIT(mpd, 0.20, musicpd-dev-team@lists.sourceforge.net)
|
||||
AC_INIT(mpd, 0.20.6, musicpd-dev-team@lists.sourceforge.net)
|
||||
|
||||
VERSION_MAJOR=0
|
||||
VERSION_MINOR=20
|
||||
VERSION_REVISION=0
|
||||
VERSION_REVISION=6
|
||||
VERSION_EXTRA=0
|
||||
|
||||
AC_CONFIG_SRCDIR([src/Main.cxx])
|
||||
@@ -720,6 +720,19 @@ fi
|
||||
MPD_ENABLE_AUTO_PKG(mms, MMS, [libmms >= 0.4],
|
||||
[libmms mms:// protocol support], [libmms not found])
|
||||
|
||||
dnl ---------------------------------------------------------------------------
|
||||
dnl Storage Plugins
|
||||
dnl ---------------------------------------------------------------------------
|
||||
|
||||
MPD_ENABLE_AUTO(webdav, WEBDAV, [WebDAV storage plugin],
|
||||
[WebDAV requires libcurl and libexpat],
|
||||
[auto],
|
||||
[if test x$enable_curl = xyes && test x$enable_expat = xyes; then
|
||||
found_webdav=yes
|
||||
else
|
||||
found_webdav=no
|
||||
fi])
|
||||
|
||||
dnl ---------------------------------------------------------------------------
|
||||
dnl Playlist Plugins
|
||||
dnl ---------------------------------------------------------------------------
|
||||
@@ -979,7 +992,7 @@ AM_CONDITIONAL(ENABLE_VORBIS_DECODER, test x$enable_vorbis = xyes || test x$enab
|
||||
dnl --------------------------------- sidplay ---------------------------------
|
||||
if test x$enable_sidplay != xno; then
|
||||
dnl Check for libsidplayfp first
|
||||
PKG_CHECK_MODULES([SIDPLAY], [libsidplayfp libsidutils],
|
||||
PKG_CHECK_MODULES([SIDPLAY], [libsidplayfp],
|
||||
[found_sidplayfp=yes],
|
||||
[found_sidplayfp=no])
|
||||
found_sidplay=$found_sidplayfp
|
||||
|
@@ -5,7 +5,7 @@
|
||||
<book>
|
||||
<title>The Music Player Daemon - Developer's Manual</title>
|
||||
|
||||
<chapter>
|
||||
<chapter id="introduction">
|
||||
<title>Introduction</title>
|
||||
|
||||
<para>
|
||||
@@ -21,7 +21,7 @@
|
||||
</para>
|
||||
</chapter>
|
||||
|
||||
<chapter>
|
||||
<chapter id="code_style">
|
||||
<title>Code Style</title>
|
||||
|
||||
<itemizedlist>
|
||||
@@ -40,21 +40,47 @@
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
the code should be C++11 compliant, and must compile with
|
||||
comment your code, document your APIs
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
the code should be C++14 compliant, and must compile with
|
||||
<application>GCC</application> 4.9 and
|
||||
<application>clang</application> 3.4
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
report error conditions with C++ exceptions, preferable
|
||||
derived from <varname>std::runtime_error</varname>
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
all code must be exception-safe
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
classes and functions names use CamelCase; variables are
|
||||
lower-case with words separated by underscore
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
Some example code:
|
||||
</para>
|
||||
|
||||
<programlisting lang="C">static inline int
|
||||
foo(const char *abc, int xyz)
|
||||
Foo(const char *abc, int xyz)
|
||||
{
|
||||
if (abc == NULL) {
|
||||
if (abc == nullptr) {
|
||||
LogWarning("Foo happened!");
|
||||
return -1;
|
||||
}
|
||||
@@ -66,7 +92,7 @@ foo(const char *abc, int xyz)
|
||||
</itemizedlist>
|
||||
</chapter>
|
||||
|
||||
<chapter>
|
||||
<chapter id="hacking">
|
||||
<title>Hacking The Source</title>
|
||||
|
||||
<para>
|
||||
@@ -151,7 +177,7 @@ foo(const char *abc, int xyz)
|
||||
</section>
|
||||
</chapter>
|
||||
|
||||
<chapter>
|
||||
<chapter id="submitting_patches">
|
||||
<title>Submitting Patches</title>
|
||||
|
||||
<para>
|
||||
@@ -167,9 +193,16 @@ foo(const char *abc, int xyz)
|
||||
url="http://git.musicpd.org/account-policy.html">an account on
|
||||
git.musicpd.org</ulink>, but any public git repository will do.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
There is <ulink url="https://github.com/MaxKellermann/MPD">a
|
||||
mirror of the <application>MPD</application> git repository on
|
||||
GitHub</ulink>, and you can use that as well to submit pull
|
||||
requests.
|
||||
</para>
|
||||
</chapter>
|
||||
|
||||
<chapter>
|
||||
<chapter id="tools">
|
||||
<title>Development Tools</title>
|
||||
|
||||
<section>
|
||||
|
@@ -55,7 +55,8 @@
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
<varname>track</varname>: the track number within the album.
|
||||
<varname>track</varname>: the decimal track number within the
|
||||
album.
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
@@ -103,7 +104,8 @@
|
||||
|
||||
<listitem>
|
||||
<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>
|
||||
</listitem>
|
||||
|
||||
|
@@ -50,6 +50,6 @@ If you find a bug, please report it at
|
||||
.br
|
||||
<\fBhttp://bugs.musicpd.org/bug_report_page.php\fP>.
|
||||
.SH AUTHORS
|
||||
Max Kellermann <max@duempel.org>
|
||||
Max Kellermann <max.kellermann@gmail.com>
|
||||
|
||||
Special thanks to all the people that provided feedback and patches.
|
||||
|
@@ -573,7 +573,12 @@
|
||||
<listitem>
|
||||
<para>
|
||||
<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>
|
||||
</listitem>
|
||||
<listitem>
|
||||
|
246
doc/user.xml
246
doc/user.xml
@@ -135,6 +135,91 @@ apt-get install g++ \
|
||||
</para>
|
||||
|
||||
<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 id="systemd_socket">
|
||||
@@ -576,10 +661,11 @@ systemctl start mpd.socket</programlisting>
|
||||
</entry>
|
||||
<entry>
|
||||
<para>
|
||||
Always open the audio output with the specified audio
|
||||
format (samplerate:bits:channels), regardless of the
|
||||
format of the input file. This is optional for most
|
||||
plugins.
|
||||
Always open the audio output with the specified
|
||||
audio format
|
||||
(<replaceable>samplerate:bits:channels</replaceable>),
|
||||
regardless of the format of the input file. This is
|
||||
optional for most plugins.
|
||||
</para>
|
||||
<para>
|
||||
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),
|
||||
<varname>32</varname> (signed 32 bit integer
|
||||
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>
|
||||
</entry>
|
||||
</row>
|
||||
@@ -742,9 +841,11 @@ systemctl start mpd.socket</programlisting>
|
||||
<title>Configuring playlist plugins</title>
|
||||
|
||||
<para>
|
||||
Playlist plugins are used to load remote playlists. This is
|
||||
not related to <application>MPD</application>'s playlist
|
||||
directory.
|
||||
Playlist plugins are used to load remote playlists (protocol
|
||||
commands <command>load</command>,
|
||||
<command>listplaylist</command> and
|
||||
<command>listplaylistinfo</command>). This is not related to
|
||||
<application>MPD</application>'s playlist directory.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
@@ -1800,6 +1901,13 @@ run</programlisting>
|
||||
database.
|
||||
</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>
|
||||
<tgroup cols="2">
|
||||
<thead>
|
||||
@@ -1868,11 +1976,23 @@ run</programlisting>
|
||||
</para>
|
||||
</section>
|
||||
|
||||
<section id="curl_storage">
|
||||
<title><varname>curl</varname></title>
|
||||
|
||||
<para>
|
||||
A WebDAV client using <filename>libcurl</filename>. It is
|
||||
used when <varname>music_directory</varname> contains a
|
||||
<parameter>http://</parameter> or
|
||||
<parameter>https://</parameter> URI, for example
|
||||
"<parameter>https://the.server/dav/</parameter>".
|
||||
</para>
|
||||
</section>
|
||||
|
||||
<section id="smbclient_storage">
|
||||
<title><varname>smbclient</varname></title>
|
||||
|
||||
<para>
|
||||
Load music files from a SMB/CIFS server. It used used when
|
||||
Load music files from a SMB/CIFS server. It is used when
|
||||
<varname>music_directory</varname> contains a
|
||||
<parameter>smb://</parameter> URI, for example
|
||||
"<parameter>smb://myfileserver/Music</parameter>".
|
||||
@@ -1883,7 +2003,7 @@ run</programlisting>
|
||||
<title><varname>nfs</varname></title>
|
||||
|
||||
<para>
|
||||
Load music files from a NFS server. It used used when
|
||||
Load music files from a NFS server. It is used when
|
||||
<varname>music_directory</varname> contains a
|
||||
<parameter>nfs://</parameter> URI according to <ulink
|
||||
url="http://tools.ietf.org/html/rfc2224">RFC2224</ulink>,
|
||||
@@ -2194,6 +2314,48 @@ run</programlisting>
|
||||
Decodes various codecs using
|
||||
<application>FFmpeg</application>.
|
||||
</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 id="flac_decoder">
|
||||
@@ -4139,6 +4301,22 @@ run</programlisting>
|
||||
<section id="playlist_plugins">
|
||||
<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>
|
||||
<title><varname>embcue</varname></title>
|
||||
|
||||
@@ -4163,6 +4341,15 @@ run</programlisting>
|
||||
</para>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<title><varname>flac</varname></title>
|
||||
|
||||
<para>
|
||||
Reads the <varname>cuesheet</varname> metablock from a FLAC
|
||||
file.
|
||||
</para>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<title><varname>pls</varname></title>
|
||||
|
||||
@@ -4171,6 +4358,45 @@ run</programlisting>
|
||||
</para>
|
||||
</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>
|
||||
<title><varname>xspf</varname></title>
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
# Check if "struct ucred" is available.
|
||||
#
|
||||
# Author: Max Kellermann <max@duempel.org>
|
||||
# Author: Max Kellermann <max.kellermann@gmail.com>
|
||||
|
||||
AC_DEFUN([STRUCT_UCRED],[
|
||||
AC_MSG_CHECKING([for struct ucred])
|
||||
|
@@ -19,8 +19,8 @@ libvorbis = AutotoolsProject(
|
||||
)
|
||||
|
||||
opus = AutotoolsProject(
|
||||
'http://downloads.xiph.org/releases/opus/opus-1.1.3.tar.gz',
|
||||
'32bbb6b557fe1b6066adc0ae1f08b629',
|
||||
'http://downloads.xiph.org/releases/opus/opus-1.1.4.tar.gz',
|
||||
'9122b6b380081dd2665189f97bfd777f04f92dc3ab6698eea1dbb27ad59d8692',
|
||||
'lib/libopus.a',
|
||||
['--disable-shared', '--enable-static'],
|
||||
)
|
||||
@@ -58,8 +58,8 @@ libmad = AutotoolsProject(
|
||||
)
|
||||
|
||||
ffmpeg = FfmpegProject(
|
||||
'http://ffmpeg.org/releases/ffmpeg-3.2.2.tar.xz',
|
||||
'3f01bd1fe1a17a277f8c84869e5d9192b4b978cb660872aa2b54c3cc8a2fedfc',
|
||||
'http://ffmpeg.org/releases/ffmpeg-3.2.4.tar.xz',
|
||||
'6e38ff14f080c98b58cf5967573501b8cb586e3a173b591f3807d8f0660daf7a',
|
||||
'lib/libavcodec.a',
|
||||
[
|
||||
'--disable-shared', '--enable-static',
|
||||
|
@@ -18,6 +18,7 @@
|
||||
*/
|
||||
|
||||
#include "AudioFormat.hxx"
|
||||
#include "util/StringBuffer.hxx"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
@@ -40,46 +41,24 @@ AudioFormat::ApplyMask(AudioFormat mask)
|
||||
assert(IsValid());
|
||||
}
|
||||
|
||||
const char *
|
||||
sample_format_to_string(SampleFormat format)
|
||||
StringBuffer<24>
|
||||
ToString(const AudioFormat af)
|
||||
{
|
||||
switch (format) {
|
||||
case SampleFormat::UNDEFINED:
|
||||
return "?";
|
||||
StringBuffer<24> buffer;
|
||||
|
||||
case SampleFormat::S8:
|
||||
return "8";
|
||||
|
||||
case SampleFormat::S16:
|
||||
return "16";
|
||||
|
||||
case SampleFormat::S24_P32:
|
||||
return "24";
|
||||
|
||||
case SampleFormat::S32:
|
||||
return "32";
|
||||
|
||||
case SampleFormat::FLOAT:
|
||||
return "f";
|
||||
|
||||
case SampleFormat::DSD:
|
||||
return "dsd";
|
||||
if (af.format == SampleFormat::DSD && af.sample_rate > 0 &&
|
||||
af.sample_rate % 44100 == 0) {
|
||||
/* use shortcuts such as "dsd64" which implies the
|
||||
sample rate */
|
||||
snprintf(buffer.data(), buffer.capacity(), "dsd%u:%u",
|
||||
af.sample_rate * 8 / 44100,
|
||||
af.channels);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
/* unreachable */
|
||||
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",
|
||||
snprintf(buffer.data(), buffer.capacity(), "%u:%s:%u",
|
||||
af.sample_rate, sample_format_to_string(af.format),
|
||||
af.channels);
|
||||
|
||||
return s->buffer;
|
||||
return buffer;
|
||||
}
|
||||
|
@@ -20,47 +20,14 @@
|
||||
#ifndef MPD_AUDIO_FORMAT_HXX
|
||||
#define MPD_AUDIO_FORMAT_HXX
|
||||
|
||||
#include "pcm/SampleFormat.hxx"
|
||||
#include "Compiler.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <assert.h>
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#if defined(WIN32) && GCC_CHECK_VERSION(4,6)
|
||||
/* 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
|
||||
template<size_t CAPACITY> class StringBuffer;
|
||||
|
||||
static constexpr unsigned MAX_CHANNELS = 8;
|
||||
|
||||
@@ -183,13 +150,6 @@ struct AudioFormat {
|
||||
double GetTimeToSize() const;
|
||||
};
|
||||
|
||||
/**
|
||||
* Buffer for audio_format_string().
|
||||
*/
|
||||
struct audio_format_string {
|
||||
char buffer[24];
|
||||
};
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
@@ -258,34 +196,6 @@ AudioFormat::IsMaskValid() const
|
||||
(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
|
||||
AudioFormat::GetSampleSize() const
|
||||
{
|
||||
@@ -304,28 +214,15 @@ AudioFormat::GetTimeToSize() const
|
||||
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
|
||||
* it in a log file.
|
||||
*
|
||||
* @param af the #AudioFormat object
|
||||
* @param s a buffer to print into
|
||||
* @return the string, or nullptr if the #AudioFormat object is invalid
|
||||
* @return the string buffer
|
||||
*/
|
||||
gcc_pure gcc_malloc
|
||||
const char *
|
||||
audio_format_to_string(AudioFormat af,
|
||||
struct audio_format_string *s);
|
||||
gcc_const
|
||||
StringBuffer<24>
|
||||
ToString(AudioFormat af);
|
||||
|
||||
#endif
|
||||
|
@@ -137,6 +137,26 @@ ParseAudioFormat(const char *src, bool mask)
|
||||
AudioFormat dest;
|
||||
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 */
|
||||
|
||||
dest.sample_rate = ParseSampleRate(src, mask, &src);
|
||||
|
@@ -107,7 +107,7 @@ static void version(void)
|
||||
"\n"
|
||||
"\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"
|
||||
"warranty; not even MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
|
||||
|
||||
|
@@ -63,18 +63,18 @@ class DetachedSong {
|
||||
|
||||
Tag tag;
|
||||
|
||||
time_t mtime;
|
||||
time_t mtime = 0;
|
||||
|
||||
/**
|
||||
* Start of this sub-song within the file.
|
||||
*/
|
||||
SongTime start_time;
|
||||
SongTime start_time = SongTime::zero();
|
||||
|
||||
/**
|
||||
* End of this sub-song within the file.
|
||||
* Unused if zero.
|
||||
*/
|
||||
SongTime end_time;
|
||||
SongTime end_time = SongTime::zero();
|
||||
|
||||
explicit DetachedSong(const LightSong &other);
|
||||
|
||||
@@ -82,26 +82,18 @@ public:
|
||||
explicit DetachedSong(const DetachedSong &) = default;
|
||||
|
||||
explicit DetachedSong(const char *_uri)
|
||||
:uri(_uri),
|
||||
mtime(0),
|
||||
start_time(SongTime::zero()), end_time(SongTime::zero()) {}
|
||||
:uri(_uri) {}
|
||||
|
||||
explicit DetachedSong(const std::string &_uri)
|
||||
:uri(_uri),
|
||||
mtime(0),
|
||||
start_time(SongTime::zero()), end_time(SongTime::zero()) {}
|
||||
:uri(_uri) {}
|
||||
|
||||
explicit DetachedSong(std::string &&_uri)
|
||||
:uri(std::move(_uri)),
|
||||
mtime(0),
|
||||
start_time(SongTime::zero()), end_time(SongTime::zero()) {}
|
||||
:uri(std::move(_uri)) {}
|
||||
|
||||
template<typename U>
|
||||
DetachedSong(U &&_uri, Tag &&_tag)
|
||||
:uri(std::forward<U>(_uri)),
|
||||
tag(std::move(_tag)),
|
||||
mtime(0),
|
||||
start_time(SongTime::zero()), end_time(SongTime::zero()) {}
|
||||
tag(std::move(_tag)) {}
|
||||
|
||||
DetachedSong(DetachedSong &&) = default;
|
||||
|
||||
@@ -146,7 +138,9 @@ public:
|
||||
*/
|
||||
gcc_pure
|
||||
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
|
||||
|
@@ -203,7 +203,11 @@ glue_db_init_and_load(void)
|
||||
"because the database does not need it");
|
||||
}
|
||||
|
||||
instance->database->Open();
|
||||
try {
|
||||
instance->database->Open();
|
||||
} catch (...) {
|
||||
std::throw_with_nested(std::runtime_error("Failed to open database plugin"));
|
||||
}
|
||||
|
||||
if (!instance->database->IsPlugin(simple_db_plugin))
|
||||
return true;
|
||||
|
@@ -141,9 +141,9 @@ Partition::OnMixerVolumeChanged(gcc_unused Mixer &mixer, gcc_unused int volume)
|
||||
void
|
||||
Partition::OnGlobalEvent(unsigned mask)
|
||||
{
|
||||
if ((mask & TAG_MODIFIED) != 0)
|
||||
TagModified();
|
||||
|
||||
if ((mask & SYNC_WITH_PLAYER) != 0)
|
||||
SyncWithPlayer();
|
||||
|
||||
if ((mask & TAG_MODIFIED) != 0)
|
||||
TagModified();
|
||||
}
|
||||
|
@@ -335,7 +335,7 @@ try {
|
||||
const auto path_fs = spl_map_to_fs(utf8path);
|
||||
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)
|
||||
throw PlaylistError(PlaylistResult::TOO_LARGE,
|
||||
|
@@ -25,9 +25,12 @@
|
||||
#include "util/ConstBuffer.hxx"
|
||||
#include "util/StringAPI.hxx"
|
||||
#include "util/ASCII.hxx"
|
||||
#include "util/TimeParser.hxx"
|
||||
#include "util/UriUtil.hxx"
|
||||
#include "lib/icu/Collate.hxx"
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
@@ -180,24 +183,6 @@ SongFilter::~SongFilter()
|
||||
/* this destructor exists here just so it won't get inlined */
|
||||
}
|
||||
|
||||
#if !defined(__GLIBC__) && !defined(WIN32)
|
||||
|
||||
/**
|
||||
* Determine the time zone offset in a portable way.
|
||||
*/
|
||||
gcc_const
|
||||
static time_t
|
||||
GetTimeZoneOffset()
|
||||
{
|
||||
time_t t = 1234567890;
|
||||
struct tm tm;
|
||||
tm.tm_isdst = 0;
|
||||
gmtime_r(&t, &tm);
|
||||
return t - mktime(&tm);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
gcc_pure
|
||||
static time_t
|
||||
ParseTimeStamp(const char *s)
|
||||
@@ -210,26 +195,13 @@ ParseTimeStamp(const char *s)
|
||||
/* it's an integral UNIX time stamp */
|
||||
return (time_t)value;
|
||||
|
||||
#ifdef WIN32
|
||||
/* TODO: emulate strptime()? */
|
||||
return 0;
|
||||
#else
|
||||
/* try ISO 8601 */
|
||||
|
||||
struct tm tm;
|
||||
const char *end = strptime(s, "%FT%TZ", &tm);
|
||||
if (end == nullptr || *end != 0)
|
||||
try {
|
||||
/* try ISO 8601 */
|
||||
const auto t = ParseTimePoint(s, "%FT%TZ");
|
||||
return std::chrono::system_clock::to_time_t(t);
|
||||
} catch (const std::runtime_error &) {
|
||||
return 0;
|
||||
|
||||
#ifdef __GLIBC__
|
||||
/* timegm() is a GNU extension */
|
||||
return timegm(&tm);
|
||||
#else
|
||||
tm.tm_isdst = 0;
|
||||
return mktime(&tm) + GetTimeZoneOffset();
|
||||
#endif /* !__GLIBC__ */
|
||||
|
||||
#endif /* !WIN32 */
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
|
@@ -29,6 +29,29 @@
|
||||
|
||||
#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
|
||||
static enum ack
|
||||
ToAck(PlaylistResult result)
|
||||
@@ -100,13 +123,13 @@ ToAck(std::exception_ptr ep)
|
||||
return ACK_ERROR_SYSTEM;
|
||||
} catch (const std::invalid_argument &e) {
|
||||
return ACK_ERROR_ARG;
|
||||
#if defined(__GLIBCXX__) && __GLIBCXX__ < 20151204
|
||||
#ifdef GLIBCXX_49X
|
||||
} catch (const std::exception &e) {
|
||||
#else
|
||||
} catch (...) {
|
||||
#endif
|
||||
try {
|
||||
#if defined(__GLIBCXX__) && __GLIBCXX__ < 20151204
|
||||
#ifdef GLIBCXX_49X
|
||||
/* workaround for g++ 4.x: no overload for
|
||||
rethrow_exception(exception_ptr) */
|
||||
std::rethrow_if_nested(e);
|
||||
|
@@ -30,6 +30,7 @@
|
||||
#include "Instance.hxx"
|
||||
#include "Idle.hxx"
|
||||
#include "AudioFormat.hxx"
|
||||
#include "util/StringBuffer.hxx"
|
||||
#include "util/ScopeExit.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",
|
||||
player_status.total_time.ToDoubleS());
|
||||
|
||||
if (player_status.audio_format.IsDefined()) {
|
||||
struct audio_format_string af_string;
|
||||
|
||||
if (player_status.audio_format.IsDefined())
|
||||
r.Format(COMMAND_STATUS_AUDIO ": %s\n",
|
||||
audio_format_to_string(player_status.audio_format,
|
||||
&af_string));
|
||||
}
|
||||
ToString(player_status.audio_format).c_str());
|
||||
}
|
||||
|
||||
#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.n_songs++;
|
||||
@@ -70,8 +70,6 @@ stats_visitor_song(SearchStats &stats, const LightSong &song)
|
||||
const auto duration = song.GetDuration();
|
||||
if (!duration.IsNegative())
|
||||
stats.total_duration += duration;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
@@ -94,7 +92,7 @@ CollectGroupCounts(TagCountMap &map, TagType group, const Tag &tag)
|
||||
return found;
|
||||
}
|
||||
|
||||
static bool
|
||||
static void
|
||||
GroupCountVisitor(TagCountMap &map, TagType group, const LightSong &song)
|
||||
{
|
||||
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)
|
||||
/* fall back to "Artist" if no "AlbumArtist" was found */
|
||||
CollectGroupCounts(map, TAG_ARTIST, tag);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
|
@@ -37,5 +37,10 @@ DatabaseGlobalInit(EventLoop &loop, DatabaseListener &listener,
|
||||
throw FormatRuntimeError("No such database plugin: %s",
|
||||
plugin_name);
|
||||
|
||||
return plugin->create(loop, listener, block);
|
||||
try {
|
||||
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>
|
||||
|
||||
static bool
|
||||
static void
|
||||
AddSong(const Storage &storage, const char *playlist_path_utf8,
|
||||
const LightSong &song)
|
||||
{
|
||||
spl_append_song(playlist_path_utf8,
|
||||
DatabaseDetachSong(storage, song));
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
|
@@ -49,16 +49,14 @@ PrintDirectoryURI(Response &r, bool base, const LightDirectory &directory)
|
||||
ApplyBaseFlag(directory.GetPath(), base));
|
||||
}
|
||||
|
||||
static bool
|
||||
static void
|
||||
PrintDirectoryBrief(Response &r, bool base, const LightDirectory &directory)
|
||||
{
|
||||
if (!directory.IsRoot())
|
||||
PrintDirectoryURI(r, base, directory);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
static void
|
||||
PrintDirectoryFull(Response &r, bool base, const LightDirectory &directory)
|
||||
{
|
||||
if (!directory.IsRoot()) {
|
||||
@@ -67,8 +65,6 @@ PrintDirectoryFull(Response &r, bool base, const LightDirectory &directory)
|
||||
if (directory.mtime > 0)
|
||||
time_print(r, "Last-Modified", directory.mtime);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void
|
||||
@@ -96,7 +92,7 @@ print_playlist_in_directory(Response &r, bool base,
|
||||
directory->GetPath(), name_utf8);
|
||||
}
|
||||
|
||||
static bool
|
||||
static void
|
||||
PrintSongBrief(Response &r, Partition &partition,
|
||||
bool base, const LightSong &song)
|
||||
{
|
||||
@@ -106,11 +102,9 @@ PrintSongBrief(Response &r, Partition &partition,
|
||||
/* this song file has an embedded CUE sheet */
|
||||
print_playlist_in_directory(r, base,
|
||||
song.directory, song.uri);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
static void
|
||||
PrintSongFull(Response &r, Partition &partition,
|
||||
bool base, const LightSong &song)
|
||||
{
|
||||
@@ -120,21 +114,18 @@ PrintSongFull(Response &r, Partition &partition,
|
||||
/* this song file has an embedded CUE sheet */
|
||||
print_playlist_in_directory(r, base,
|
||||
song.directory, song.uri);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
static void
|
||||
PrintPlaylistBrief(Response &r, bool base,
|
||||
const PlaylistInfo &playlist,
|
||||
const LightDirectory &directory)
|
||||
{
|
||||
print_playlist_in_directory(r, base,
|
||||
&directory, playlist.name.c_str());
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
static void
|
||||
PrintPlaylistFull(Response &r, bool base,
|
||||
const PlaylistInfo &playlist,
|
||||
const LightDirectory &directory)
|
||||
@@ -144,8 +135,6 @@ PrintPlaylistFull(Response &r, bool base,
|
||||
|
||||
if (playlist.mtime > 0)
|
||||
time_print(r, "Last-Modified", playlist.mtime);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
@@ -191,15 +180,13 @@ db_selection_print(Response &r, Partition &partition,
|
||||
0, std::numeric_limits<int>::max());
|
||||
}
|
||||
|
||||
static bool
|
||||
static void
|
||||
PrintSongURIVisitor(Response &r, Partition &partition, const LightSong &song)
|
||||
{
|
||||
song_print_uri(r, partition, song);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
static void
|
||||
PrintUniqueTag(Response &r, TagType tag_type,
|
||||
const Tag &tag)
|
||||
{
|
||||
@@ -211,8 +198,6 @@ PrintUniqueTag(Response &r, TagType tag_type,
|
||||
if (item.type != tag_type)
|
||||
r.Format("%s: %s\n",
|
||||
tag_item_names[item.type], item.value);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
|
@@ -27,14 +27,13 @@
|
||||
|
||||
#include <functional>
|
||||
|
||||
static bool
|
||||
static void
|
||||
AddToQueue(Partition &partition, const LightSong &song)
|
||||
{
|
||||
const Storage &storage = *partition.instance.storage;
|
||||
partition.playlist.AppendSong(partition.pc,
|
||||
DatabaseDetachSong(storage,
|
||||
song));
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
|
@@ -67,15 +67,13 @@ StatsVisitTag(DatabaseStats &stats, StringSet &artists, StringSet &albums,
|
||||
}
|
||||
}
|
||||
|
||||
static bool
|
||||
static void
|
||||
StatsVisitSong(DatabaseStats &stats, StringSet &artists, StringSet &albums,
|
||||
const LightSong &song)
|
||||
{
|
||||
++stats.song_count;
|
||||
|
||||
StatsVisitTag(stats, artists, albums, *song.tag);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
DatabaseStats
|
||||
|
@@ -34,6 +34,7 @@
|
||||
#include "tag/TagBuilder.hxx"
|
||||
#include "tag/Tag.hxx"
|
||||
#include "util/ScopeExit.hxx"
|
||||
#include "util/RuntimeError.hxx"
|
||||
#include "protocol/Ack.hxx"
|
||||
#include "event/SocketMonitor.hxx"
|
||||
#include "event/IdleMonitor.hxx"
|
||||
@@ -46,7 +47,7 @@
|
||||
#include <string>
|
||||
#include <list>
|
||||
|
||||
class LibmpdclientError final : std::runtime_error {
|
||||
class LibmpdclientError final : public std::runtime_error {
|
||||
enum mpd_error code;
|
||||
|
||||
public:
|
||||
@@ -108,8 +109,8 @@ public:
|
||||
static Database *Create(EventLoop &loop, DatabaseListener &listener,
|
||||
const ConfigBlock &block);
|
||||
|
||||
virtual void Open() override;
|
||||
virtual void Close() override;
|
||||
void Open() override;
|
||||
void Close() override;
|
||||
const LightSong *GetSong(const char *uri_utf8) const override;
|
||||
void ReturnSong(const LightSong *song) const override;
|
||||
|
||||
@@ -126,7 +127,7 @@ public:
|
||||
|
||||
unsigned Update(const char *uri_utf8, bool discard) override;
|
||||
|
||||
virtual time_t GetUpdateStamp() const override {
|
||||
time_t GetUpdateStamp() const override {
|
||||
return update_stamp;
|
||||
}
|
||||
|
||||
@@ -138,10 +139,10 @@ private:
|
||||
void Disconnect();
|
||||
|
||||
/* virtual methods from SocketMonitor */
|
||||
virtual bool OnSocketReady(unsigned flags) override;
|
||||
bool OnSocketReady(unsigned flags) override;
|
||||
|
||||
/* virtual methods from IdleMonitor */
|
||||
virtual void OnIdle() override;
|
||||
void OnIdle() override;
|
||||
};
|
||||
|
||||
static constexpr struct {
|
||||
@@ -345,9 +346,15 @@ ProxyDatabase::Create(EventLoop &loop, DatabaseListener &listener,
|
||||
void
|
||||
ProxyDatabase::Open()
|
||||
{
|
||||
Connect();
|
||||
|
||||
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
|
||||
@@ -371,7 +378,10 @@ ProxyDatabase::Connect()
|
||||
mpd_connection_free(connection);
|
||||
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)
|
||||
|
@@ -25,6 +25,13 @@
|
||||
#include "db/Interface.hxx"
|
||||
#include "fs/Traits.hxx"
|
||||
|
||||
#ifdef _LIBCPP_VERSION
|
||||
/* workaround for "error: incomplete type 'PlaylistInfo' used in type
|
||||
trait expression" with libc++ version 3900 (from Android NDK
|
||||
r13b) */
|
||||
#include "db/PlaylistInfo.hxx"
|
||||
#endif
|
||||
|
||||
#include <string>
|
||||
|
||||
struct PrefixedLightDirectory : LightDirectory {
|
||||
|
@@ -108,8 +108,8 @@ public:
|
||||
bool Unmount(const char *uri);
|
||||
|
||||
/* virtual methods from class Database */
|
||||
virtual void Open() override;
|
||||
virtual void Close() override;
|
||||
void Open() override;
|
||||
void Close() override;
|
||||
|
||||
const LightSong *GetSong(const char *uri_utf8) const override;
|
||||
void ReturnSong(const LightSong *song) const override;
|
||||
@@ -125,7 +125,7 @@ public:
|
||||
|
||||
DatabaseStats GetStats(const DatabaseSelection &selection) const override;
|
||||
|
||||
virtual time_t GetUpdateStamp() const override {
|
||||
time_t GetUpdateStamp() const override {
|
||||
return mtime;
|
||||
}
|
||||
|
||||
|
@@ -124,7 +124,7 @@ public:
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual void StartElement(const XML_Char *name, const XML_Char **attrs)
|
||||
void StartElement(const XML_Char *name, const XML_Char **attrs) override
|
||||
{
|
||||
if (object.type != UPnPDirObject::Type::UNKNOWN &&
|
||||
tag_type == TAG_NUM_OF_ITEM_TYPES) {
|
||||
@@ -188,7 +188,7 @@ protected:
|
||||
}
|
||||
}
|
||||
|
||||
virtual void EndElement(const XML_Char *name)
|
||||
void EndElement(const XML_Char *name) override
|
||||
{
|
||||
if (tag_type != TAG_NUM_OF_ITEM_TYPES) {
|
||||
assert(object.type != UPnPDirObject::Type::UNKNOWN);
|
||||
@@ -212,7 +212,7 @@ protected:
|
||||
state = NONE;
|
||||
}
|
||||
|
||||
virtual void CharacterData(const XML_Char *s, int len)
|
||||
void CharacterData(const XML_Char *s, int len) override
|
||||
{
|
||||
if (tag_type != TAG_NUM_OF_ITEM_TYPES) {
|
||||
assert(object.type != UPnPDirObject::Type::UNKNOWN);
|
||||
|
@@ -77,9 +77,9 @@ public:
|
||||
static Database *Create(EventLoop &loop, DatabaseListener &listener,
|
||||
const ConfigBlock &block);
|
||||
|
||||
virtual void Open() override;
|
||||
virtual void Close() override;
|
||||
virtual const LightSong *GetSong(const char *uri_utf8) const override;
|
||||
void Open() override;
|
||||
void Close() override;
|
||||
const LightSong *GetSong(const char *uri_utf8) const override;
|
||||
void ReturnSong(const LightSong *song) const override;
|
||||
|
||||
void Visit(const DatabaseSelection &selection,
|
||||
@@ -180,7 +180,7 @@ UpnpDatabase::ReturnSong(const LightSong *_song) const
|
||||
const LightSong *
|
||||
UpnpDatabase::GetSong(const char *uri) const
|
||||
{
|
||||
auto vpath = stringToTokens(uri, "/", true);
|
||||
auto vpath = stringToTokens(uri, '/');
|
||||
if (vpath.size() < 2)
|
||||
throw DatabaseError(DatabaseErrorCode::NOT_FOUND,
|
||||
"No such song");
|
||||
@@ -577,7 +577,7 @@ UpnpDatabase::Visit(const DatabaseSelection &selection,
|
||||
VisitSong visit_song,
|
||||
VisitPlaylist visit_playlist) const
|
||||
{
|
||||
auto vpath = stringToTokens(selection.uri, "/", true);
|
||||
auto vpath = stringToTokens(selection.uri, '/');
|
||||
if (vpath.empty()) {
|
||||
for (const auto &server : discovery->GetDirectories()) {
|
||||
if (visit_directory) {
|
||||
|
@@ -32,6 +32,7 @@
|
||||
#include "Log.hxx"
|
||||
#include "input/InputStream.hxx"
|
||||
#include "util/ConstBuffer.hxx"
|
||||
#include "util/StringBuffer.hxx"
|
||||
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
@@ -246,15 +247,13 @@ void
|
||||
DecoderBridge::Ready(const AudioFormat audio_format,
|
||||
bool seekable, SignedSongTime duration)
|
||||
{
|
||||
struct audio_format_string af_string;
|
||||
|
||||
assert(convert == nullptr);
|
||||
assert(stream_tag == nullptr);
|
||||
assert(decoder_tag == nullptr);
|
||||
assert(!seeking);
|
||||
|
||||
FormatDebug(decoder_domain, "audio_format=%s, seekable=%s",
|
||||
audio_format_to_string(audio_format, &af_string),
|
||||
ToString(audio_format).c_str(),
|
||||
seekable ? "true" : "false");
|
||||
|
||||
{
|
||||
@@ -264,8 +263,7 @@ DecoderBridge::Ready(const AudioFormat audio_format,
|
||||
|
||||
if (dc.in_audio_format != dc.out_audio_format) {
|
||||
FormatDebug(decoder_domain, "converting to %s",
|
||||
audio_format_to_string(dc.out_audio_format,
|
||||
&af_string));
|
||||
ToString(dc.out_audio_format).c_str());
|
||||
|
||||
convert = new PcmConvert();
|
||||
|
||||
@@ -317,6 +315,9 @@ DecoderBridge::CommandFinished()
|
||||
|
||||
dc.pipe->Clear(*dc.buffer);
|
||||
|
||||
if (convert != nullptr)
|
||||
convert->Reset();
|
||||
|
||||
timestamp = dc.seek_time.ToDoubleS();
|
||||
}
|
||||
|
||||
|
@@ -235,7 +235,7 @@ decoder_run_stream_fallback(DecoderBridge &bridge, InputStream &is)
|
||||
{
|
||||
const struct DecoderPlugin *plugin;
|
||||
|
||||
#ifdef HAVE_FFMPEG
|
||||
#ifdef ENABLE_FFMPEG
|
||||
plugin = decoder_plugin_from_name("ffmpeg");
|
||||
#else
|
||||
plugin = decoder_plugin_from_name("mad");
|
||||
|
@@ -56,6 +56,11 @@ extern "C" {
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
|
||||
/**
|
||||
* Muxer options to be passed to avformat_open_input().
|
||||
*/
|
||||
static AVDictionary *avformat_options = nullptr;
|
||||
|
||||
static AVFormatContext *
|
||||
FfmpegOpenInput(AVIOContext *pb,
|
||||
const char *filename,
|
||||
@@ -67,7 +72,11 @@ FfmpegOpenInput(AVIOContext *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)
|
||||
throw MakeFfmpegError(err, "avformat_open_input() failed");
|
||||
|
||||
@@ -75,12 +84,30 @@ FfmpegOpenInput(AVIOContext *pb,
|
||||
}
|
||||
|
||||
static bool
|
||||
ffmpeg_init(gcc_unused const ConfigBlock &block)
|
||||
ffmpeg_init(const ConfigBlock &block)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
static void
|
||||
ffmpeg_finish()
|
||||
{
|
||||
av_dict_free(&avformat_options);
|
||||
}
|
||||
|
||||
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 25, 0) /* FFmpeg 3.1 */
|
||||
|
||||
gcc_pure
|
||||
@@ -967,7 +994,7 @@ static const char *const ffmpeg_mime_types[] = {
|
||||
const struct DecoderPlugin ffmpeg_decoder_plugin = {
|
||||
"ffmpeg",
|
||||
ffmpeg_init,
|
||||
nullptr,
|
||||
ffmpeg_finish,
|
||||
ffmpeg_decode,
|
||||
nullptr,
|
||||
nullptr,
|
||||
|
@@ -207,6 +207,15 @@ mpcdec_decode(DecoderClient &client, InputStream &is)
|
||||
if (frame.bits == -1)
|
||||
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;
|
||||
ret *= info.channels;
|
||||
|
||||
|
@@ -50,6 +50,10 @@
|
||||
#include <string.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_"
|
||||
|
||||
static constexpr Domain sidplay_domain("sidplay");
|
||||
@@ -285,7 +289,11 @@ sidplay_file_decode(DecoderClient &client, Path path_fs)
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_SIDPLAYFP
|
||||
#if LIBSIDPLAYFP_VERSION >= GCC_MAKE_VERSION(1,8,0)
|
||||
const bool stereo = tune.getInfo()->sidChips() >= 2;
|
||||
#else
|
||||
const bool stereo = tune.getInfo()->isStereo();
|
||||
#endif
|
||||
#else
|
||||
const bool stereo = tune.isStereo();
|
||||
#endif
|
||||
|
@@ -34,6 +34,8 @@
|
||||
#include <stdexcept>
|
||||
#include <memory>
|
||||
|
||||
#include <cstdlib>
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
#define ERRORLEN 80
|
||||
@@ -536,7 +538,7 @@ wavpack_streamdecode(DecoderClient &client, InputStream &is)
|
||||
auto is_wvc = wavpack_open_wvc(client, is.GetURI());
|
||||
if (is_wvc) {
|
||||
open_flags |= OPEN_WVC;
|
||||
can_seek &= wvc->is.IsSeekable();
|
||||
can_seek &= is_wvc->IsSeekable();
|
||||
|
||||
wvc.reset(new WavpackInput(&client, *is_wvc));
|
||||
}
|
||||
|
@@ -79,7 +79,7 @@ private:
|
||||
void
|
||||
BlockingCall(EventLoop &loop, std::function<void()> &&f)
|
||||
{
|
||||
if (loop.IsInside()) {
|
||||
if (loop.IsInsideOrNull()) {
|
||||
/* we're already inside the loop - we can simply call
|
||||
the function */
|
||||
f();
|
||||
|
@@ -222,8 +222,9 @@ EventLoop::Run()
|
||||
#ifndef NDEBUG
|
||||
assert(busy);
|
||||
assert(thread.IsInside());
|
||||
thread = ThreadId::Null();
|
||||
#endif
|
||||
|
||||
thread = ThreadId::Null();
|
||||
}
|
||||
|
||||
void
|
||||
|
@@ -212,12 +212,16 @@ public:
|
||||
}
|
||||
#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
|
||||
bool IsInsideOrNull() const {
|
||||
return thread.IsNull() || thread.IsInside();
|
||||
}
|
||||
#endif
|
||||
};
|
||||
|
||||
#endif /* MAIN_NOTIFY_H */
|
||||
|
@@ -28,12 +28,18 @@
|
||||
#endif
|
||||
|
||||
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
|
||||
|
@@ -96,7 +96,19 @@ class MultiSocketMonitor : IdleMonitor, TimeoutMonitor
|
||||
|
||||
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;
|
||||
|
||||
@@ -107,11 +119,26 @@ public:
|
||||
static constexpr unsigned HANGUP = SocketMonitor::HANGUP;
|
||||
|
||||
MultiSocketMonitor(EventLoop &_loop);
|
||||
~MultiSocketMonitor();
|
||||
|
||||
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
|
||||
* scheduled which will then update the list.
|
||||
@@ -121,12 +148,19 @@ public:
|
||||
IdleMonitor::Schedule();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add one socket to the list of monitored sockets.
|
||||
*
|
||||
* May only be called from PrepareSockets().
|
||||
*/
|
||||
void AddSocket(int fd, unsigned events) {
|
||||
fds.emplace_front(*this, fd, events);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all sockets.
|
||||
*
|
||||
* May only be called from PrepareSockets().
|
||||
*/
|
||||
void ClearSocketList();
|
||||
|
||||
@@ -135,6 +169,8 @@ public:
|
||||
* each one; its return value is the events bit mask. A
|
||||
* return value of 0 means the socket will be removed from the
|
||||
* list.
|
||||
*
|
||||
* May only be called from PrepareSockets().
|
||||
*/
|
||||
template<typename E>
|
||||
void UpdateSocketList(E &&e) {
|
||||
@@ -157,15 +193,26 @@ public:
|
||||
/**
|
||||
* Replace the socket list with the given file descriptors.
|
||||
* The given pollfd array will be modified by this method.
|
||||
*
|
||||
* May only be called from PrepareSockets().
|
||||
*/
|
||||
void ReplaceSocketList(pollfd *pfds, unsigned n);
|
||||
#endif
|
||||
|
||||
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
|
||||
*/
|
||||
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;
|
||||
|
||||
private:
|
||||
|
@@ -27,6 +27,7 @@
|
||||
|
||||
#include "AudioFormat.hxx"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stddef.h>
|
||||
|
||||
struct AudioFormat;
|
||||
@@ -36,9 +37,10 @@ class Filter {
|
||||
protected:
|
||||
AudioFormat out_audio_format;
|
||||
|
||||
Filter() = default;
|
||||
explicit Filter(AudioFormat _out_audio_format)
|
||||
:out_audio_format(_out_audio_format) {}
|
||||
:out_audio_format(_out_audio_format) {
|
||||
assert(out_audio_format.IsValid());
|
||||
}
|
||||
|
||||
public:
|
||||
virtual ~Filter() {}
|
||||
@@ -50,16 +52,21 @@ public:
|
||||
return out_audio_format;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the filter's state, e.g. drop/flush buffers.
|
||||
*/
|
||||
virtual void Reset() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters a block of PCM data.
|
||||
*
|
||||
* Throws std::runtime_error on error.
|
||||
*
|
||||
* @param src the input buffer
|
||||
* @param error location to store the error occurring
|
||||
* @return the destination buffer on success (will be
|
||||
* invalidated by deleting this object or the next FilterPCM()
|
||||
* call)
|
||||
* or Reset() call)
|
||||
*/
|
||||
virtual ConstBuffer<void> FilterPCM(ConstBuffer<void> src) = 0;
|
||||
};
|
||||
|
@@ -45,7 +45,15 @@ class AutoConvertFilter final : public Filter {
|
||||
public:
|
||||
AutoConvertFilter(std::unique_ptr<Filter> &&_filter,
|
||||
std::unique_ptr<Filter> &&_convert)
|
||||
:filter(std::move(_filter)), convert(std::move(_convert)) {}
|
||||
:Filter(_filter->GetOutAudioFormat()),
|
||||
filter(std::move(_filter)), convert(std::move(_convert)) {}
|
||||
|
||||
void Reset() override {
|
||||
filter->Reset();
|
||||
|
||||
if (convert)
|
||||
convert->Reset();
|
||||
}
|
||||
|
||||
ConstBuffer<void> FilterPCM(ConstBuffer<void> src) override;
|
||||
};
|
||||
@@ -92,11 +100,8 @@ PreparedAutoConvertFilter::Open(AudioFormat &in_audio_format)
|
||||
ConstBuffer<void>
|
||||
AutoConvertFilter::FilterPCM(ConstBuffer<void> src)
|
||||
{
|
||||
if (convert != nullptr) {
|
||||
if (convert != nullptr)
|
||||
src = convert->FilterPCM(src);
|
||||
if (src.IsNull())
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return filter->FilterPCM(src);
|
||||
}
|
||||
|
@@ -24,6 +24,7 @@
|
||||
#include "filter/FilterRegistry.hxx"
|
||||
#include "AudioFormat.hxx"
|
||||
#include "util/ConstBuffer.hxx"
|
||||
#include "util/StringBuffer.hxx"
|
||||
#include "util/RuntimeError.hxx"
|
||||
|
||||
#include <memory>
|
||||
@@ -61,6 +62,7 @@ public:
|
||||
}
|
||||
|
||||
/* virtual methods from class Filter */
|
||||
void Reset() 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) {
|
||||
delete new_filter;
|
||||
|
||||
struct audio_format_string s;
|
||||
throw FormatRuntimeError("Audio format not supported by filter '%s': %s",
|
||||
name,
|
||||
audio_format_to_string(prev_audio_format, &s));
|
||||
ToString(prev_audio_format).c_str());
|
||||
}
|
||||
|
||||
return new_filter;
|
||||
@@ -130,6 +131,13 @@ PreparedChainFilter::Open(AudioFormat &in_audio_format)
|
||||
return chain.release();
|
||||
}
|
||||
|
||||
void
|
||||
ChainFilter::Reset()
|
||||
{
|
||||
for (auto &child : children)
|
||||
child.filter->Reset();
|
||||
}
|
||||
|
||||
ConstBuffer<void>
|
||||
ChainFilter::FilterPCM(ConstBuffer<void> src)
|
||||
{
|
||||
|
@@ -52,13 +52,15 @@ public:
|
||||
|
||||
void Set(const AudioFormat &_out_audio_format);
|
||||
|
||||
void Reset() override {
|
||||
state.Reset();
|
||||
}
|
||||
|
||||
ConstBuffer<void> FilterPCM(ConstBuffer<void> src) override;
|
||||
};
|
||||
|
||||
class PreparedConvertFilter final : public PreparedFilter {
|
||||
public:
|
||||
void Set(const AudioFormat &_out_audio_format);
|
||||
|
||||
Filter *Open(AudioFormat &af) override;
|
||||
};
|
||||
|
||||
|
@@ -28,11 +28,11 @@
|
||||
#include "AlsaInputPlugin.hxx"
|
||||
#include "../InputPlugin.hxx"
|
||||
#include "../AsyncInputStream.hxx"
|
||||
#include "event/Call.hxx"
|
||||
#include "util/Domain.hxx"
|
||||
#include "util/RuntimeError.hxx"
|
||||
#include "util/StringCompare.hxx"
|
||||
#include "util/ReusableArray.hxx"
|
||||
#include "util/ScopeExit.hxx"
|
||||
|
||||
#include "Log.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_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
|
||||
: public AsyncInputStream,
|
||||
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;
|
||||
|
||||
public:
|
||||
AlsaInputStream(EventLoop &loop,
|
||||
const char *_uri, Mutex &_mutex, Cond &_cond,
|
||||
const char *_device,
|
||||
snd_pcm_t *_handle, int _frame_size)
|
||||
:AsyncInputStream(_uri, _mutex, _cond,
|
||||
ALSA_MAX_BUFFERED, ALSA_RESUME_AT),
|
||||
MultiSocketMonitor(loop),
|
||||
DeferredMonitor(loop),
|
||||
device(_device),
|
||||
capture_handle(_handle),
|
||||
frame_size(_frame_size)
|
||||
{
|
||||
@@ -99,6 +98,11 @@ public:
|
||||
}
|
||||
|
||||
~AlsaInputStream() {
|
||||
BlockingCall(MultiSocketMonitor::GetEventLoop(), [this](){
|
||||
MultiSocketMonitor::Reset();
|
||||
DeferredMonitor::Cancel();
|
||||
});
|
||||
|
||||
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;
|
||||
return new AlsaInputStream(io_thread_get(),
|
||||
uri, mutex, cond,
|
||||
handle, frame_size);
|
||||
device, handle, frame_size);
|
||||
}
|
||||
|
||||
std::chrono::steady_clock::duration
|
||||
@@ -174,7 +178,7 @@ AlsaInputStream::PrepareSockets()
|
||||
}
|
||||
|
||||
int count = snd_pcm_poll_descriptors_count(capture_handle);
|
||||
if (count < 0) {
|
||||
if (count <= 0) {
|
||||
ClearSocketList();
|
||||
return std::chrono::steady_clock::duration(-1);
|
||||
}
|
||||
@@ -205,6 +209,9 @@ AlsaInputStream::DispatchSockets()
|
||||
snd_pcm_sframes_t n_frames;
|
||||
while ((n_frames = snd_pcm_readi(capture_handle,
|
||||
w.data, w_frames)) < 0) {
|
||||
if (n_frames == -EAGAIN)
|
||||
return;
|
||||
|
||||
if (Recover(n_frames) < 0) {
|
||||
postponed_exception = std::make_exception_ptr(std::runtime_error("PCM error - stream aborted"));
|
||||
cond.broadcast();
|
||||
@@ -221,20 +228,51 @@ AlsaInputStream::Recover(int err)
|
||||
{
|
||||
switch(err) {
|
||||
case -EPIPE:
|
||||
LogDebug(alsa_input_domain, "Buffer Overrun");
|
||||
// drop through
|
||||
FormatDebug(alsa_input_domain,
|
||||
"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)
|
||||
[[fallthrough]];
|
||||
#endif
|
||||
|
||||
case -ESTRPIPE:
|
||||
case -EINTR:
|
||||
err = snd_pcm_recover(capture_handle, err, 1);
|
||||
case SND_PCM_STATE_OPEN:
|
||||
case SND_PCM_STATE_SETUP:
|
||||
case SND_PCM_STATE_XRUN:
|
||||
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;
|
||||
default:
|
||||
// something broken somewhere, give up
|
||||
err = -1;
|
||||
}
|
||||
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
@@ -245,13 +283,7 @@ ConfigureCapture(snd_pcm_t *capture_handle,
|
||||
int err;
|
||||
|
||||
snd_pcm_hw_params_t *hw_params;
|
||||
if ((err = snd_pcm_hw_params_malloc(&hw_params)) < 0)
|
||||
throw FormatRuntimeError("Cannot allocate hardware parameter structure (%s)",
|
||||
snd_strerror(err));
|
||||
|
||||
AtScopeExit(hw_params) {
|
||||
snd_pcm_hw_params_free(hw_params);
|
||||
};
|
||||
snd_pcm_hw_params_alloca(&hw_params);
|
||||
|
||||
if ((err = snd_pcm_hw_params_any(capture_handle, hw_params)) < 0)
|
||||
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)",
|
||||
snd_strerror(err));
|
||||
|
||||
/* period needs to be big enough so that poll() doesn't fire too often,
|
||||
* but small enough that buffer overruns don't occur if Read() is not
|
||||
* invoked often enough.
|
||||
* the calculation here is empirical; however all measurements were
|
||||
* done using 44100:16:2. When we extend this plugin to support
|
||||
* other audio formats then this may need to be revisited */
|
||||
snd_pcm_uframes_t period = read_buffer_size * 2;
|
||||
int direction = -1;
|
||||
if ((err = snd_pcm_hw_params_set_period_size_near(capture_handle, hw_params,
|
||||
&period, &direction)) < 0)
|
||||
throw FormatRuntimeError("Cannot set period size (%s)",
|
||||
snd_strerror(err));
|
||||
snd_pcm_uframes_t buffer_size_min, buffer_size_max;
|
||||
snd_pcm_hw_params_get_buffer_size_min(hw_params, &buffer_size_min);
|
||||
snd_pcm_hw_params_get_buffer_size_max(hw_params, &buffer_size_max);
|
||||
unsigned buffer_time_min, buffer_time_max;
|
||||
snd_pcm_hw_params_get_buffer_time_min(hw_params, &buffer_time_min, 0);
|
||||
snd_pcm_hw_params_get_buffer_time_max(hw_params, &buffer_time_max, 0);
|
||||
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;
|
||||
if ((err = snd_pcm_hw_params_set_period_size_near(capture_handle, hw_params,
|
||||
&period_size, &direction)) < 0)
|
||||
throw FormatRuntimeError("Cannot set period size (%s)",
|
||||
snd_strerror(err));
|
||||
}
|
||||
|
||||
if ((err = snd_pcm_hw_params(capture_handle, hw_params)) < 0)
|
||||
throw FormatRuntimeError("Cannot set parameters (%s)",
|
||||
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_alloca(&sw_params);
|
||||
|
||||
snd_pcm_sw_params_malloc(&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)
|
||||
throw FormatRuntimeError("unable to install sw params (%s)",
|
||||
snd_strerror(err));
|
||||
@@ -316,7 +379,8 @@ AlsaInputStream::OpenDevice(const char *device,
|
||||
snd_pcm_t *capture_handle;
|
||||
int err;
|
||||
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)",
|
||||
device, snd_strerror(err));
|
||||
|
||||
|
@@ -23,6 +23,7 @@
|
||||
#include "lib/curl/Global.hxx"
|
||||
#include "lib/curl/Request.hxx"
|
||||
#include "lib/curl/Handler.hxx"
|
||||
#include "lib/curl/Slist.hxx"
|
||||
#include "../AsyncInputStream.hxx"
|
||||
#include "../IcyInputStream.hxx"
|
||||
#include "../InputPlugin.hxx"
|
||||
@@ -64,7 +65,7 @@ struct CurlInputStream final : public AsyncInputStream, CurlResponseHandler {
|
||||
/* some buffers which were passed to libcurl, which we have
|
||||
too free */
|
||||
char range[32];
|
||||
struct curl_slist *request_headers;
|
||||
CurlSlist request_headers;
|
||||
|
||||
CurlRequest *request = nullptr;
|
||||
|
||||
@@ -75,7 +76,6 @@ struct CurlInputStream final : public AsyncInputStream, CurlResponseHandler {
|
||||
:AsyncInputStream(_url, _mutex, _cond,
|
||||
CURL_MAX_BUFFERED,
|
||||
CURL_RESUME_AT),
|
||||
request_headers(nullptr),
|
||||
icy(new IcyInputStream(this)) {
|
||||
}
|
||||
|
||||
@@ -155,8 +155,7 @@ CurlInputStream::FreeEasy()
|
||||
delete request;
|
||||
request = nullptr;
|
||||
|
||||
curl_slist_free_all(request_headers);
|
||||
request_headers = nullptr;
|
||||
request_headers.Clear();
|
||||
}
|
||||
|
||||
void
|
||||
@@ -371,10 +370,11 @@ CurlInputStream::InitEasy()
|
||||
request->SetOption(CURLOPT_SSL_VERIFYPEER, verify_peer ? 1l : 0l);
|
||||
request->SetOption(CURLOPT_SSL_VERIFYHOST, verify_host ? 2l : 0l);
|
||||
|
||||
request_headers = nullptr;
|
||||
request_headers = curl_slist_append(request_headers,
|
||||
"Icy-Metadata: 1");
|
||||
request->SetOption(CURLOPT_HTTPHEADER, request_headers);
|
||||
request_headers.Clear();
|
||||
request_headers.Append("Icy-Metadata: 1");
|
||||
request->SetOption(CURLOPT_HTTPHEADER, request_headers.Get());
|
||||
|
||||
request->Start();
|
||||
}
|
||||
|
||||
void
|
||||
@@ -385,18 +385,25 @@ CurlInputStream::SeekInternal(offset_type new_offset)
|
||||
FreeEasy();
|
||||
|
||||
offset = new_offset;
|
||||
if (offset == size)
|
||||
if (offset == size) {
|
||||
/* seek to EOF: simulate empty result; avoid
|
||||
triggering a "416 Requested Range Not Satisfiable"
|
||||
response */
|
||||
SeekDone();
|
||||
return;
|
||||
}
|
||||
|
||||
InitEasy();
|
||||
|
||||
/* send the "Range" header */
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@@ -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
|
||||
* 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
|
||||
* 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
|
||||
* 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
|
||||
* 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
|
||||
* 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
|
||||
* 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
|
||||
* 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
|
||||
* 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
|
||||
* 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
|
||||
* 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
|
||||
* 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
|
||||
* modification, are permitted provided that the following conditions
|
||||
@@ -96,7 +96,7 @@ private:
|
||||
};
|
||||
|
||||
CurlGlobal::CurlGlobal(EventLoop &_loop)
|
||||
:TimeoutMonitor(_loop)
|
||||
:TimeoutMonitor(_loop), DeferredMonitor(_loop)
|
||||
{
|
||||
multi.SetOption(CURLMOPT_SOCKETFUNCTION, CurlSocket::SocketFunction);
|
||||
multi.SetOption(CURLMOPT_SOCKETDATA, this);
|
||||
@@ -217,25 +217,31 @@ CurlGlobal::ReadInfo()
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
CurlGlobal::TimerFunction(gcc_unused CURLM *_global, long timeout_ms, void *userp)
|
||||
inline void
|
||||
CurlGlobal::UpdateTimeout(long timeout_ms)
|
||||
{
|
||||
auto &global = *(CurlGlobal *)userp;
|
||||
assert(_global == global.multi.Get());
|
||||
|
||||
if (timeout_ms < 0) {
|
||||
global.Cancel();
|
||||
return 0;
|
||||
TimeoutMonitor::Cancel();
|
||||
return;
|
||||
}
|
||||
|
||||
if (timeout_ms >= 0 && timeout_ms < 10)
|
||||
if (timeout_ms < 10)
|
||||
/* CURL 7.21.1 likes to report "timeout=0", which
|
||||
means we're running in a busy loop. Quite a bad
|
||||
idea to waste so much CPU. Let's use a lower limit
|
||||
of 10ms. */
|
||||
timeout_ms = 10;
|
||||
|
||||
global.Schedule(std::chrono::milliseconds(timeout_ms));
|
||||
TimeoutMonitor::Schedule(std::chrono::milliseconds(timeout_ms));
|
||||
}
|
||||
|
||||
int
|
||||
CurlGlobal::TimerFunction(gcc_unused CURLM *_global, long timeout_ms, void *userp)
|
||||
{
|
||||
auto &global = *(CurlGlobal *)userp;
|
||||
assert(_global == global.multi.Get());
|
||||
|
||||
global.UpdateTimeout(timeout_ms);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -256,5 +262,11 @@ CurlGlobal::SocketAction(curl_socket_t fd, int ev_bitmask)
|
||||
"curl_multi_socket_action() failed: %s",
|
||||
curl_multi_strerror(mcode));
|
||||
|
||||
DeferredMonitor::Schedule();
|
||||
}
|
||||
|
||||
void
|
||||
CurlGlobal::RunDeferred()
|
||||
{
|
||||
ReadInfo();
|
||||
}
|
||||
|
@@ -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
|
||||
* modification, are permitted provided that the following conditions
|
||||
@@ -32,6 +32,7 @@
|
||||
|
||||
#include "Multi.hxx"
|
||||
#include "event/TimeoutMonitor.hxx"
|
||||
#include "event/DeferredMonitor.hxx"
|
||||
|
||||
class CurlSocket;
|
||||
class CurlRequest;
|
||||
@@ -39,12 +40,14 @@ class CurlRequest;
|
||||
/**
|
||||
* Manager for the global CURLM object.
|
||||
*/
|
||||
class CurlGlobal final : private TimeoutMonitor {
|
||||
class CurlGlobal final : TimeoutMonitor, DeferredMonitor {
|
||||
CurlMulti multi;
|
||||
|
||||
public:
|
||||
explicit CurlGlobal(EventLoop &_loop);
|
||||
|
||||
using TimeoutMonitor::GetEventLoop;
|
||||
|
||||
void Add(CURL *easy, CurlRequest &request);
|
||||
void Remove(CURL *easy);
|
||||
|
||||
@@ -76,9 +79,14 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
void UpdateTimeout(long timeout_ms);
|
||||
static int TimerFunction(CURLM *global, long timeout_ms, void *userp);
|
||||
|
||||
virtual void OnTimeout() override;
|
||||
/* virtual methods from class TimeoutMonitor */
|
||||
void OnTimeout() override;
|
||||
|
||||
/* virtual methods from class DeferredMonitor */
|
||||
void RunDeferred() override;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
@@ -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
|
||||
* 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
|
||||
* modification, are permitted provided that the following conditions
|
||||
@@ -76,7 +76,7 @@ public:
|
||||
return *this;
|
||||
}
|
||||
|
||||
CURL *Get() {
|
||||
CURLM *Get() {
|
||||
return handle;
|
||||
}
|
||||
|
||||
|
@@ -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
|
||||
* modification, are permitted provided that the following conditions
|
||||
@@ -46,7 +46,8 @@
|
||||
|
||||
CurlRequest::CurlRequest(CurlGlobal &_global, const char *url,
|
||||
CurlResponseHandler &_handler)
|
||||
:global(_global), handler(_handler)
|
||||
:DeferredMonitor(_global.GetEventLoop()),
|
||||
global(_global), handler(_handler)
|
||||
{
|
||||
error_buffer[0] = 0;
|
||||
|
||||
@@ -62,8 +63,6 @@ CurlRequest::CurlRequest(CurlGlobal &_global, const char *url,
|
||||
easy.SetOption(CURLOPT_NOSIGNAL, 1l);
|
||||
easy.SetOption(CURLOPT_CONNECTTIMEOUT, 10l);
|
||||
easy.SetOption(CURLOPT_URL, url);
|
||||
|
||||
global.Add(easy.Get(), *this);
|
||||
}
|
||||
|
||||
CurlRequest::~CurlRequest()
|
||||
@@ -71,19 +70,40 @@ CurlRequest::~CurlRequest()
|
||||
FreeEasy();
|
||||
}
|
||||
|
||||
void
|
||||
CurlRequest::Start()
|
||||
{
|
||||
assert(!registered);
|
||||
|
||||
global.Add(easy.Get(), *this);
|
||||
registered = true;
|
||||
}
|
||||
|
||||
void
|
||||
CurlRequest::Stop()
|
||||
{
|
||||
if (!registered)
|
||||
return;
|
||||
|
||||
global.Remove(easy.Get());
|
||||
registered = false;
|
||||
}
|
||||
|
||||
void
|
||||
CurlRequest::FreeEasy()
|
||||
{
|
||||
if (!easy)
|
||||
return;
|
||||
|
||||
global.Remove(easy.Get());
|
||||
Stop();
|
||||
easy = nullptr;
|
||||
}
|
||||
|
||||
void
|
||||
CurlRequest::Resume()
|
||||
{
|
||||
assert(registered);
|
||||
|
||||
curl_easy_pause(easy.Get(), CURLPAUSE_CONT);
|
||||
|
||||
if (IsCurlOlderThan(0x072000))
|
||||
@@ -95,32 +115,24 @@ CurlRequest::Resume()
|
||||
global.InvalidateSockets();
|
||||
}
|
||||
|
||||
bool
|
||||
void
|
||||
CurlRequest::FinishHeaders()
|
||||
{
|
||||
if (state != State::HEADERS)
|
||||
return true;
|
||||
return;
|
||||
|
||||
state = State::BODY;
|
||||
|
||||
long status = 0;
|
||||
curl_easy_getinfo(easy.Get(), CURLINFO_RESPONSE_CODE, &status);
|
||||
|
||||
try {
|
||||
handler.OnHeaders(status, std::move(headers));
|
||||
return true;
|
||||
} catch (...) {
|
||||
state = State::CLOSED;
|
||||
handler.OnError(std::current_exception());
|
||||
return false;
|
||||
}
|
||||
handler.OnHeaders(status, std::move(headers));
|
||||
}
|
||||
|
||||
void
|
||||
CurlRequest::FinishBody()
|
||||
{
|
||||
if (!FinishHeaders())
|
||||
return;
|
||||
FinishHeaders();
|
||||
|
||||
if (state != State::BODY)
|
||||
return;
|
||||
@@ -132,7 +144,7 @@ CurlRequest::FinishBody()
|
||||
void
|
||||
CurlRequest::Done(CURLcode result)
|
||||
{
|
||||
FreeEasy();
|
||||
Stop();
|
||||
|
||||
try {
|
||||
if (result != CURLE_OK) {
|
||||
@@ -148,7 +160,22 @@ CurlRequest::Done(CURLcode result)
|
||||
return;
|
||||
}
|
||||
|
||||
FinishBody();
|
||||
try {
|
||||
FinishBody();
|
||||
} catch (...) {
|
||||
state = State::CLOSED;
|
||||
handler.OnError(std::current_exception());
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
@@ -157,7 +184,7 @@ CurlRequest::HeaderFunction(StringView s)
|
||||
if (state > State::HEADERS)
|
||||
return;
|
||||
|
||||
if (s.size > 5 && memcmp(s.data, "HTTP/", 5) == 0) {
|
||||
if (IsResponseBoundaryHeader(s)) {
|
||||
/* this is the boundary to a new response, for example
|
||||
after a redirect */
|
||||
headers.clear();
|
||||
@@ -202,18 +229,19 @@ CurlRequest::DataReceived(const void *ptr, size_t received_size)
|
||||
{
|
||||
assert(received_size > 0);
|
||||
|
||||
if (!FinishHeaders())
|
||||
return 0;
|
||||
|
||||
try {
|
||||
FinishHeaders();
|
||||
handler.OnData({ptr, received_size});
|
||||
return received_size;
|
||||
} catch (Pause) {
|
||||
return CURL_WRITEFUNC_PAUSE;
|
||||
} catch (...) {
|
||||
state = State::CLOSED;
|
||||
handler.OnError(std::current_exception());
|
||||
return 0;
|
||||
/* move the CurlResponseHandler::OnError() call into a
|
||||
"safe" stack frame */
|
||||
postponed_error = std::current_exception();
|
||||
DeferredMonitor::Schedule();
|
||||
return CURL_WRITEFUNC_PAUSE;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -229,3 +257,11 @@ CurlRequest::WriteFunction(void *ptr, size_t size, size_t nmemb, void *stream)
|
||||
|
||||
return c.DataReceived(ptr, size);
|
||||
}
|
||||
|
||||
void
|
||||
CurlRequest::RunDeferred()
|
||||
{
|
||||
assert(postponed_error);
|
||||
|
||||
handler.OnError(postponed_error);
|
||||
}
|
||||
|
@@ -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
|
||||
* modification, are permitted provided that the following conditions
|
||||
@@ -31,15 +31,17 @@
|
||||
#define CURL_REQUEST_HXX
|
||||
|
||||
#include "Easy.hxx"
|
||||
#include "event/DeferredMonitor.hxx"
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <exception>
|
||||
|
||||
struct StringView;
|
||||
class CurlGlobal;
|
||||
class CurlResponseHandler;
|
||||
|
||||
class CurlRequest {
|
||||
class CurlRequest final : DeferredMonitor {
|
||||
CurlGlobal &global;
|
||||
|
||||
CurlResponseHandler &handler;
|
||||
@@ -55,10 +57,25 @@ class CurlRequest {
|
||||
|
||||
std::multimap<std::string, std::string> headers;
|
||||
|
||||
/**
|
||||
* An exception caught by DataReceived(), which will be
|
||||
* forwarded into a "safe" stack frame by
|
||||
* DeferredMonitor::RunDeferred(). This works around the
|
||||
* problem that libcurl crashes if you call
|
||||
* curl_multi_remove_handle() from within the WRITEFUNCTION
|
||||
* (i.e. DataReceived()).
|
||||
*/
|
||||
std::exception_ptr postponed_error;
|
||||
|
||||
/** error message provided by libcurl */
|
||||
char error_buffer[CURL_ERROR_SIZE];
|
||||
|
||||
bool registered = false;
|
||||
|
||||
public:
|
||||
/**
|
||||
* To start sending the request, call Start().
|
||||
*/
|
||||
CurlRequest(CurlGlobal &_global, const char *url,
|
||||
CurlResponseHandler &_handler);
|
||||
~CurlRequest();
|
||||
@@ -66,6 +83,21 @@ public:
|
||||
CurlRequest(const CurlRequest &) = delete;
|
||||
CurlRequest &operator=(const CurlRequest &) = delete;
|
||||
|
||||
/**
|
||||
* Register this request via CurlGlobal::Add(), which starts
|
||||
* the request.
|
||||
*
|
||||
* This method must be called in the event loop thread.
|
||||
*/
|
||||
void Start();
|
||||
|
||||
/**
|
||||
* Unregister this request via CurlGlobal::Remove().
|
||||
*
|
||||
* This method must be called in the event loop thread.
|
||||
*/
|
||||
void Stop();
|
||||
|
||||
CURL *Get() {
|
||||
return easy.Get();
|
||||
}
|
||||
@@ -95,7 +127,7 @@ private:
|
||||
*/
|
||||
void FreeEasy();
|
||||
|
||||
bool FinishHeaders();
|
||||
void FinishHeaders();
|
||||
void FinishBody();
|
||||
|
||||
size_t DataReceived(const void *ptr, size_t size);
|
||||
@@ -109,6 +141,9 @@ private:
|
||||
/** called by curl when new data is available */
|
||||
static size_t WriteFunction(void *ptr, size_t size, size_t nmemb,
|
||||
void *stream);
|
||||
|
||||
/* virtual methods from class DeferredMonitor */
|
||||
void RunDeferred() override;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
76
src/lib/curl/Slist.hxx
Normal file
76
src/lib/curl/Slist.hxx
Normal file
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Copyright (C) 2008-2017 Max Kellermann <max.kellermann@gmail.com>
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* - Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* - Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the
|
||||
* distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
* FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
|
||||
* OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef CURL_SLIST_HXX
|
||||
#define CURL_SLIST_HXX
|
||||
|
||||
#include <curl/curl.h>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
/**
|
||||
* OO wrapper for "struct curl_slist *".
|
||||
*/
|
||||
class CurlSlist {
|
||||
struct curl_slist *head = nullptr;
|
||||
|
||||
public:
|
||||
CurlSlist() = default;
|
||||
|
||||
CurlSlist(CurlSlist &&src)
|
||||
:head(std::exchange(src.head, nullptr)) {}
|
||||
|
||||
~CurlSlist() {
|
||||
if (head != nullptr)
|
||||
curl_slist_free_all(head);
|
||||
}
|
||||
|
||||
CurlSlist &operator=(CurlSlist &&src) {
|
||||
std::swap(head, src.head);
|
||||
return *this;
|
||||
}
|
||||
|
||||
struct curl_slist *Get() {
|
||||
return head;
|
||||
}
|
||||
|
||||
void Clear() {
|
||||
curl_slist_free_all(head);
|
||||
head = nullptr;
|
||||
}
|
||||
|
||||
void Append(const char *value) {
|
||||
auto *new_head = curl_slist_append(head, value);
|
||||
if (new_head == nullptr)
|
||||
throw std::runtime_error("curl_slist_append() failed");
|
||||
head = new_head;
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
@@ -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
|
||||
* 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
|
||||
* modification, are permitted provided that the following conditions
|
||||
|
@@ -19,7 +19,6 @@
|
||||
|
||||
#include "config.h"
|
||||
#include "ExpatParser.hxx"
|
||||
#include "input/InputStream.hxx"
|
||||
#include "util/ASCII.hxx"
|
||||
|
||||
#include <string.h>
|
||||
@@ -31,23 +30,6 @@ ExpatParser::Parse(const char *data, size_t length, bool is_final)
|
||||
throw ExpatError(parser);
|
||||
}
|
||||
|
||||
void
|
||||
ExpatParser::Parse(InputStream &is)
|
||||
{
|
||||
assert(is.IsReady());
|
||||
|
||||
while (true) {
|
||||
char buffer[4096];
|
||||
size_t nbytes = is.LockRead(buffer, sizeof(buffer));
|
||||
if (nbytes == 0)
|
||||
break;
|
||||
|
||||
Parse(buffer, nbytes, false);
|
||||
}
|
||||
|
||||
Parse("", 0, true);
|
||||
}
|
||||
|
||||
const char *
|
||||
ExpatParser::GetAttribute(const XML_Char **atts,
|
||||
const char *name)
|
||||
|
@@ -31,22 +31,31 @@ class InputStream;
|
||||
|
||||
class ExpatError final : public std::runtime_error {
|
||||
public:
|
||||
ExpatError(XML_Error code)
|
||||
explicit ExpatError(XML_Error code)
|
||||
:std::runtime_error(XML_ErrorString(code)) {}
|
||||
|
||||
ExpatError(XML_Parser parser)
|
||||
explicit ExpatError(XML_Parser parser)
|
||||
:ExpatError(XML_GetErrorCode(parser)) {}
|
||||
};
|
||||
|
||||
struct ExpatNamespaceSeparator {
|
||||
char separator;
|
||||
};
|
||||
|
||||
class ExpatParser final {
|
||||
const XML_Parser parser;
|
||||
|
||||
public:
|
||||
ExpatParser(void *userData)
|
||||
explicit ExpatParser(void *userData)
|
||||
:parser(XML_ParserCreate(nullptr)) {
|
||||
XML_SetUserData(parser, userData);
|
||||
}
|
||||
|
||||
ExpatParser(ExpatNamespaceSeparator ns, void *userData)
|
||||
:parser(XML_ParserCreateNS(nullptr, ns.separator)) {
|
||||
XML_SetUserData(parser, userData);
|
||||
}
|
||||
|
||||
~ExpatParser() {
|
||||
XML_ParserFree(parser);
|
||||
}
|
||||
@@ -89,6 +98,12 @@ public:
|
||||
parser.SetCharacterDataHandler(CharacterData);
|
||||
}
|
||||
|
||||
explicit CommonExpatParser(ExpatNamespaceSeparator ns)
|
||||
:parser(ns, this) {
|
||||
parser.SetElementHandler(StartElement, EndElement);
|
||||
parser.SetCharacterDataHandler(CharacterData);
|
||||
}
|
||||
|
||||
void Parse(const char *data, size_t length, bool is_final) {
|
||||
parser.Parse(data, length, is_final);
|
||||
}
|
||||
|
39
src/lib/expat/StreamExpatParser.cxx
Normal file
39
src/lib/expat/StreamExpatParser.cxx
Normal file
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* 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 "ExpatParser.hxx"
|
||||
#include "input/InputStream.hxx"
|
||||
|
||||
void
|
||||
ExpatParser::Parse(InputStream &is)
|
||||
{
|
||||
assert(is.IsReady());
|
||||
|
||||
while (true) {
|
||||
char buffer[4096];
|
||||
size_t nbytes = is.LockRead(buffer, sizeof(buffer));
|
||||
if (nbytes == 0)
|
||||
break;
|
||||
|
||||
Parse(buffer, nbytes, false);
|
||||
}
|
||||
|
||||
Parse("", 0, true);
|
||||
}
|
@@ -24,9 +24,21 @@
|
||||
|
||||
#include <exception>
|
||||
|
||||
/**
|
||||
* Callbacks for an asynchronous libnfs operation.
|
||||
*
|
||||
* Note that no callback is invoked for cancelled operations.
|
||||
*/
|
||||
class NfsCallback {
|
||||
public:
|
||||
/**
|
||||
* The operation completed successfully.
|
||||
*/
|
||||
virtual void OnNfsCallback(unsigned status, void *data) = 0;
|
||||
|
||||
/**
|
||||
* An error has occurred.
|
||||
*/
|
||||
virtual void OnNfsError(std::exception_ptr &&e) = 0;
|
||||
};
|
||||
|
||||
|
@@ -396,6 +396,17 @@ NfsConnection::ScheduleSocket()
|
||||
assert(GetEventLoop().IsInside());
|
||||
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()) {
|
||||
int _fd = nfs_get_fd(context);
|
||||
if (_fd < 0)
|
||||
@@ -405,7 +416,8 @@ NfsConnection::ScheduleSocket()
|
||||
SocketMonitor::Open(_fd);
|
||||
}
|
||||
|
||||
SocketMonitor::Schedule(libnfs_to_events(nfs_which_events(context)));
|
||||
SocketMonitor::Schedule(libnfs_to_events(which_events)
|
||||
| SocketMonitor::HANGUP);
|
||||
}
|
||||
|
||||
inline int
|
||||
@@ -442,10 +454,14 @@ NfsConnection::OnSocketReady(unsigned flags)
|
||||
bool closed = false;
|
||||
|
||||
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
|
||||
various sockets, therefore we unregister and
|
||||
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();
|
||||
|
||||
const int result = Service(flags);
|
||||
|
@@ -35,6 +35,14 @@
|
||||
struct nfsfh;
|
||||
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 {
|
||||
enum class State {
|
||||
INITIAL,
|
||||
@@ -63,14 +71,30 @@ public:
|
||||
void DeferClose();
|
||||
|
||||
/**
|
||||
* Open the file. This method is thread-safe.
|
||||
*
|
||||
* Throws std::runtime_error on error.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
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();
|
||||
|
||||
bool IsIdle() const {
|
||||
@@ -78,8 +102,27 @@ public:
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
private:
|
||||
|
@@ -62,7 +62,7 @@ class UPnPDeviceDirectory final : UpnpCallback {
|
||||
DiscoveredTask(const Upnp_Discovery *disco)
|
||||
:url(disco->Location),
|
||||
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>
|
||||
stringToTokens(const std::string &str,
|
||||
const char *delims, bool skipinit)
|
||||
const char delim)
|
||||
{
|
||||
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.
|
||||
if (skipinit &&
|
||||
(startPos = str.find_first_not_of(delims, 0)) == std::string::npos)
|
||||
if (startPos == std::string::npos)
|
||||
return tokens;
|
||||
|
||||
while (startPos < str.size()) {
|
||||
// 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
|
||||
if (pos == std::string::npos) {
|
||||
|
@@ -33,8 +33,7 @@ path_getfather(const std::string &s);
|
||||
|
||||
gcc_pure
|
||||
std::list<std::string>
|
||||
stringToTokens(const std::string &str,
|
||||
const char *delims = "/", bool skipinit = true);
|
||||
stringToTokens(const std::string &str, char delim);
|
||||
|
||||
template <class T>
|
||||
bool
|
||||
|
@@ -52,11 +52,11 @@ class WorkQueue {
|
||||
|
||||
// Status
|
||||
// Worker threads having called exit
|
||||
unsigned n_workers_exited;
|
||||
bool ok;
|
||||
unsigned n_workers_exited = 0;
|
||||
bool ok = false;
|
||||
|
||||
unsigned n_threads;
|
||||
pthread_t *threads;
|
||||
unsigned n_threads = 0;
|
||||
pthread_t *threads = nullptr;
|
||||
|
||||
// Synchronization
|
||||
std::queue<T> queue;
|
||||
@@ -68,11 +68,8 @@ public:
|
||||
/** Create a WorkQueue
|
||||
* @param _name for message printing
|
||||
*/
|
||||
WorkQueue(const char *_name)
|
||||
:name(_name),
|
||||
n_workers_exited(0),
|
||||
ok(false),
|
||||
n_threads(0), threads(nullptr)
|
||||
explicit WorkQueue(const char *_name)
|
||||
:name(_name)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -80,6 +77,9 @@ public:
|
||||
setTerminateAndWait();
|
||||
}
|
||||
|
||||
WorkQueue(const WorkQueue &) = delete;
|
||||
WorkQueue &operator=(const WorkQueue &) = delete;
|
||||
|
||||
/** Start the worker threads.
|
||||
*
|
||||
* @param nworkers number of threads copies to start.
|
||||
@@ -97,6 +97,7 @@ public:
|
||||
assert(n_threads == 0);
|
||||
assert(threads == nullptr);
|
||||
|
||||
ok = true;
|
||||
n_threads = nworkers;
|
||||
threads = new pthread_t[n_threads];
|
||||
|
||||
@@ -109,7 +110,6 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
ok = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@@ -23,17 +23,21 @@
|
||||
#include "output/OutputAPI.hxx"
|
||||
#include "event/MultiSocketMonitor.hxx"
|
||||
#include "event/DeferredMonitor.hxx"
|
||||
#include "event/Call.hxx"
|
||||
#include "util/ASCII.hxx"
|
||||
#include "util/ReusableArray.hxx"
|
||||
#include "util/Clamp.hxx"
|
||||
#include "util/Domain.hxx"
|
||||
#include "util/RuntimeError.hxx"
|
||||
#include "Log.hxx"
|
||||
|
||||
#include <algorithm>
|
||||
extern "C" {
|
||||
#include "volume_mapping.h"
|
||||
}
|
||||
|
||||
#include <alsa/asoundlib.h>
|
||||
|
||||
#include <math.h>
|
||||
|
||||
#define VOLUME_MIXER_ALSA_DEFAULT "default"
|
||||
#define VOLUME_MIXER_ALSA_CONTROL_DEFAULT "PCM"
|
||||
static constexpr unsigned VOLUME_MIXER_ALSA_INDEX_DEFAULT = 0;
|
||||
@@ -50,6 +54,13 @@ public:
|
||||
DeferredMonitor::Schedule();
|
||||
}
|
||||
|
||||
~AlsaMixerMonitor() {
|
||||
BlockingCall(MultiSocketMonitor::GetEventLoop(), [this](){
|
||||
MultiSocketMonitor::Reset();
|
||||
DeferredMonitor::Cancel();
|
||||
});
|
||||
}
|
||||
|
||||
private:
|
||||
virtual void RunDeferred() override {
|
||||
InvalidateSockets();
|
||||
@@ -68,9 +79,6 @@ class AlsaMixer final : public Mixer {
|
||||
|
||||
snd_mixer_t *handle;
|
||||
snd_mixer_elem_t *elem;
|
||||
long volume_min;
|
||||
long volume_max;
|
||||
int volume_set;
|
||||
|
||||
AlsaMixerMonitor *monitor;
|
||||
|
||||
@@ -102,7 +110,7 @@ AlsaMixerMonitor::PrepareSockets()
|
||||
}
|
||||
|
||||
int count = snd_mixer_poll_descriptors_count(mixer);
|
||||
if (count < 0)
|
||||
if (count <= 0)
|
||||
count = 0;
|
||||
|
||||
struct pollfd *pfds = pfd_buffer.Get(count);
|
||||
@@ -228,9 +236,6 @@ AlsaMixer::Setup()
|
||||
if (elem == nullptr)
|
||||
throw FormatRuntimeError("no such mixer control: %s", control);
|
||||
|
||||
snd_mixer_selem_get_playback_volume_range(elem, &volume_min,
|
||||
&volume_max);
|
||||
|
||||
snd_mixer_elem_set_callback_private(elem, this);
|
||||
snd_mixer_elem_set_callback(elem, alsa_mixer_elem_callback);
|
||||
|
||||
@@ -242,8 +247,6 @@ AlsaMixer::Open()
|
||||
{
|
||||
int err;
|
||||
|
||||
volume_set = -1;
|
||||
|
||||
err = snd_mixer_open(&handle, 0);
|
||||
if (err < 0)
|
||||
throw FormatRuntimeError("snd_mixer_open() failed: %s",
|
||||
@@ -272,8 +275,6 @@ int
|
||||
AlsaMixer::GetVolume()
|
||||
{
|
||||
int err;
|
||||
int ret;
|
||||
long level;
|
||||
|
||||
assert(handle != nullptr);
|
||||
|
||||
@@ -282,43 +283,15 @@ AlsaMixer::GetVolume()
|
||||
throw FormatRuntimeError("snd_mixer_handle_events() failed: %s",
|
||||
snd_strerror(err));
|
||||
|
||||
err = snd_mixer_selem_get_playback_volume(elem,
|
||||
SND_MIXER_SCHN_FRONT_LEFT,
|
||||
&level);
|
||||
if (err < 0)
|
||||
throw FormatRuntimeError("failed to read ALSA volume: %s",
|
||||
snd_strerror(err));
|
||||
|
||||
ret = ((volume_set / 100.0) * (volume_max - volume_min)
|
||||
+ volume_min) + 0.5;
|
||||
if (volume_set > 0 && ret == level) {
|
||||
ret = volume_set;
|
||||
} else {
|
||||
ret = (int)(100 * (((float)(level - volume_min)) /
|
||||
(volume_max - volume_min)) + 0.5);
|
||||
}
|
||||
|
||||
return ret;
|
||||
return lrint(100 * get_normalized_playback_volume(elem, SND_MIXER_SCHN_FRONT_LEFT));
|
||||
}
|
||||
|
||||
void
|
||||
AlsaMixer::SetVolume(unsigned volume)
|
||||
{
|
||||
float vol;
|
||||
long level;
|
||||
int err;
|
||||
|
||||
assert(handle != nullptr);
|
||||
|
||||
vol = volume;
|
||||
|
||||
volume_set = vol + 0.5;
|
||||
|
||||
level = (long)(((vol / 100.0) * (volume_max - volume_min) +
|
||||
volume_min) + 0.5);
|
||||
level = Clamp(level, volume_min, volume_max);
|
||||
|
||||
err = snd_mixer_selem_set_playback_volume_all(elem, level);
|
||||
int err = set_normalized_playback_volume(elem, 0.01*volume, 1);
|
||||
if (err < 0)
|
||||
throw FormatRuntimeError("failed to set ALSA volume: %s",
|
||||
snd_strerror(err));
|
||||
|
180
src/mixer/plugins/volume_mapping.c
Normal file
180
src/mixer/plugins/volume_mapping.c
Normal file
@@ -0,0 +1,180 @@
|
||||
/*
|
||||
* Copyright (c) 2010 Clemens Ladisch <clemens@ladisch.de>
|
||||
*
|
||||
* Permission to use, copy, modify, and/or distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
/*
|
||||
* The functions in this file map the value ranges of ALSA mixer controls onto
|
||||
* the interval 0..1.
|
||||
*
|
||||
* The mapping is designed so that the position in the interval is proportional
|
||||
* to the volume as a human ear would perceive it (i.e., the position is the
|
||||
* cubic root of the linear sample multiplication factor). For controls with
|
||||
* a small range (24 dB or less), the mapping is linear in the dB values so
|
||||
* that each step has the same size visually. Only for controls without dB
|
||||
* information, a linear mapping of the hardware volume register values is used
|
||||
* (this is the same algorithm as used in the old alsamixer).
|
||||
*
|
||||
* When setting the volume, 'dir' is the rounding direction:
|
||||
* -1/0/1 = down/nearest/up.
|
||||
*/
|
||||
|
||||
#include <math.h>
|
||||
#include <stdbool.h>
|
||||
#include "volume_mapping.h"
|
||||
|
||||
#ifdef __UCLIBC__
|
||||
/* 10^x = 10^(log e^x) = (e^x)^log10 = e^(x * log 10) */
|
||||
#define exp10(x) (exp((x) * log(10)))
|
||||
#endif /* __UCLIBC__ */
|
||||
|
||||
#define MAX_LINEAR_DB_SCALE 24
|
||||
|
||||
static inline bool use_linear_dB_scale(long dBmin, long dBmax)
|
||||
{
|
||||
return dBmax - dBmin <= MAX_LINEAR_DB_SCALE * 100;
|
||||
}
|
||||
|
||||
static long lrint_dir(double x, int dir)
|
||||
{
|
||||
if (dir > 0)
|
||||
return lrint(ceil(x));
|
||||
else if (dir < 0)
|
||||
return lrint(floor(x));
|
||||
else
|
||||
return lrint(x);
|
||||
}
|
||||
|
||||
enum ctl_dir { PLAYBACK, CAPTURE };
|
||||
|
||||
static int (* const get_dB_range[2])(snd_mixer_elem_t *, long *, long *) = {
|
||||
snd_mixer_selem_get_playback_dB_range,
|
||||
snd_mixer_selem_get_capture_dB_range,
|
||||
};
|
||||
static int (* const get_raw_range[2])(snd_mixer_elem_t *, long *, long *) = {
|
||||
snd_mixer_selem_get_playback_volume_range,
|
||||
snd_mixer_selem_get_capture_volume_range,
|
||||
};
|
||||
static int (* const get_dB[2])(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t, long *) = {
|
||||
snd_mixer_selem_get_playback_dB,
|
||||
snd_mixer_selem_get_capture_dB,
|
||||
};
|
||||
static int (* const get_raw[2])(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t, long *) = {
|
||||
snd_mixer_selem_get_playback_volume,
|
||||
snd_mixer_selem_get_capture_volume,
|
||||
};
|
||||
static int (* const set_dB[2])(snd_mixer_elem_t *, long, int) = {
|
||||
snd_mixer_selem_set_playback_dB_all,
|
||||
snd_mixer_selem_set_capture_dB_all,
|
||||
};
|
||||
static int (* const set_raw[2])(snd_mixer_elem_t *, long) = {
|
||||
snd_mixer_selem_set_playback_volume_all,
|
||||
snd_mixer_selem_set_capture_volume_all,
|
||||
};
|
||||
|
||||
static double get_normalized_volume(snd_mixer_elem_t *elem,
|
||||
snd_mixer_selem_channel_id_t channel,
|
||||
enum ctl_dir ctl_dir)
|
||||
{
|
||||
long min, max, value;
|
||||
double normalized, min_norm;
|
||||
int err;
|
||||
|
||||
err = get_dB_range[ctl_dir](elem, &min, &max);
|
||||
if (err < 0 || min >= max) {
|
||||
err = get_raw_range[ctl_dir](elem, &min, &max);
|
||||
if (err < 0 || min == max)
|
||||
return 0;
|
||||
|
||||
err = get_raw[ctl_dir](elem, channel, &value);
|
||||
if (err < 0)
|
||||
return 0;
|
||||
|
||||
return (value - min) / (double)(max - min);
|
||||
}
|
||||
|
||||
err = get_dB[ctl_dir](elem, channel, &value);
|
||||
if (err < 0)
|
||||
return 0;
|
||||
|
||||
if (use_linear_dB_scale(min, max))
|
||||
return (value - min) / (double)(max - min);
|
||||
|
||||
normalized = exp10((value - max) / 6000.0);
|
||||
if (min != SND_CTL_TLV_DB_GAIN_MUTE) {
|
||||
min_norm = exp10((min - max) / 6000.0);
|
||||
normalized = (normalized - min_norm) / (1 - min_norm);
|
||||
}
|
||||
|
||||
return normalized;
|
||||
}
|
||||
|
||||
static int set_normalized_volume(snd_mixer_elem_t *elem,
|
||||
double volume,
|
||||
int dir,
|
||||
enum ctl_dir ctl_dir)
|
||||
{
|
||||
long min, max, value;
|
||||
double min_norm;
|
||||
int err;
|
||||
|
||||
err = get_dB_range[ctl_dir](elem, &min, &max);
|
||||
if (err < 0 || min >= max) {
|
||||
err = get_raw_range[ctl_dir](elem, &min, &max);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
value = lrint_dir(volume * (max - min), dir) + min;
|
||||
return set_raw[ctl_dir](elem, value);
|
||||
}
|
||||
|
||||
if (use_linear_dB_scale(min, max)) {
|
||||
value = lrint_dir(volume * (max - min), dir) + min;
|
||||
return set_dB[ctl_dir](elem, value, dir);
|
||||
}
|
||||
|
||||
if (min != SND_CTL_TLV_DB_GAIN_MUTE) {
|
||||
min_norm = exp10((min - max) / 6000.0);
|
||||
volume = volume * (1 - min_norm) + min_norm;
|
||||
}
|
||||
value = lrint_dir(6000.0 * log10(volume), dir) + max;
|
||||
return set_dB[ctl_dir](elem, value, dir);
|
||||
}
|
||||
|
||||
double get_normalized_playback_volume(snd_mixer_elem_t *elem,
|
||||
snd_mixer_selem_channel_id_t channel)
|
||||
{
|
||||
return get_normalized_volume(elem, channel, PLAYBACK);
|
||||
}
|
||||
|
||||
double get_normalized_capture_volume(snd_mixer_elem_t *elem,
|
||||
snd_mixer_selem_channel_id_t channel)
|
||||
{
|
||||
return get_normalized_volume(elem, channel, CAPTURE);
|
||||
}
|
||||
|
||||
|
||||
int set_normalized_playback_volume(snd_mixer_elem_t *elem,
|
||||
double volume,
|
||||
int dir)
|
||||
{
|
||||
return set_normalized_volume(elem, volume, dir, PLAYBACK);
|
||||
}
|
||||
|
||||
int set_normalized_capture_volume(snd_mixer_elem_t *elem,
|
||||
double volume,
|
||||
int dir)
|
||||
{
|
||||
return set_normalized_volume(elem, volume, dir, CAPTURE);
|
||||
}
|
17
src/mixer/plugins/volume_mapping.h
Normal file
17
src/mixer/plugins/volume_mapping.h
Normal file
@@ -0,0 +1,17 @@
|
||||
#ifndef VOLUME_MAPPING_H_INCLUDED
|
||||
#define VOLUME_MAPPING_H_INCLUDED
|
||||
|
||||
#include <alsa/asoundlib.h>
|
||||
|
||||
double get_normalized_playback_volume(snd_mixer_elem_t *elem,
|
||||
snd_mixer_selem_channel_id_t channel);
|
||||
double get_normalized_capture_volume(snd_mixer_elem_t *elem,
|
||||
snd_mixer_selem_channel_id_t channel);
|
||||
int set_normalized_playback_volume(snd_mixer_elem_t *elem,
|
||||
double volume,
|
||||
int dir);
|
||||
int set_normalized_capture_volume(snd_mixer_elem_t *elem,
|
||||
double volume,
|
||||
int dir);
|
||||
|
||||
#endif
|
@@ -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
|
||||
* 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
|
||||
* 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
|
||||
* 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
|
||||
* 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
|
||||
* 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
|
||||
* 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
|
||||
* 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
|
||||
* modification, are permitted provided that the following conditions
|
||||
|
@@ -35,6 +35,7 @@
|
||||
#include "thread/Slack.hxx"
|
||||
#include "thread/Name.hxx"
|
||||
#include "util/ConstBuffer.hxx"
|
||||
#include "util/StringBuffer.hxx"
|
||||
#include "util/ScopeExit.hxx"
|
||||
#include "util/RuntimeError.hxx"
|
||||
#include "Log.hxx"
|
||||
@@ -111,8 +112,7 @@ AudioOutput::Open()
|
||||
f = source.Open(request.audio_format, *request.pipe,
|
||||
prepared_replay_gain_filter,
|
||||
prepared_other_replay_gain_filter,
|
||||
prepared_filter)
|
||||
.WithMask(config_audio_format);
|
||||
prepared_filter);
|
||||
|
||||
if (mixer != nullptr && mixer->IsPlugin(software_mixer_plugin))
|
||||
software_mixer_set_filter(*mixer, volume_filter.Get());
|
||||
@@ -121,14 +121,16 @@ AudioOutput::Open()
|
||||
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
|
||||
must be reopened as well */
|
||||
CloseOutput(true);
|
||||
open = false;
|
||||
}
|
||||
|
||||
filter_audio_format = f;
|
||||
filter_audio_format = cf;
|
||||
|
||||
if (!open) {
|
||||
try {
|
||||
@@ -139,7 +141,25 @@ AudioOutput::Open()
|
||||
}
|
||||
|
||||
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
|
||||
@@ -154,6 +174,11 @@ AudioOutput::OpenOutputAndConvert(AudioFormat desired_audio_format)
|
||||
name, plugin.name));
|
||||
}
|
||||
|
||||
FormatDebug(output_domain,
|
||||
"opened plugin=%s name=\"%s\" audio_format=%s",
|
||||
plugin.name, name,
|
||||
ToString(out_audio_format).c_str());
|
||||
|
||||
try {
|
||||
convert_filter_set(convert_filter.Get(), out_audio_format);
|
||||
} 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]",
|
||||
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
|
||||
|
@@ -70,6 +70,22 @@ AudioOutputSource::Close()
|
||||
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
|
||||
AudioOutputSource::OpenFilter(AudioFormat audio_format,
|
||||
PreparedFilter *prepared_replay_gain_filter,
|
||||
@@ -79,13 +95,17 @@ try {
|
||||
assert(audio_format.IsValid());
|
||||
|
||||
/* 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 =
|
||||
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 =
|
||||
prepared_other_replay_gain_filter->Open(audio_format);
|
||||
}
|
||||
|
||||
filter_instance = prepared_filter->Open(audio_format);
|
||||
} catch (...) {
|
||||
|
@@ -64,13 +64,13 @@ class AudioOutputSource {
|
||||
* The serial number of the last replay gain info. 0 means no
|
||||
* 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
|
||||
* "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
|
||||
@@ -139,11 +139,7 @@ public:
|
||||
PreparedFilter *prepared_filter);
|
||||
|
||||
void Close();
|
||||
|
||||
void Cancel() {
|
||||
current_chunk = nullptr;
|
||||
pipe.Cancel();
|
||||
}
|
||||
void Cancel();
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
struct AlsaOutput {
|
||||
class AlsaOutput {
|
||||
friend struct AudioOutputWrapper<AlsaOutput>;
|
||||
|
||||
AudioOutput base;
|
||||
|
||||
Manual<PcmExport> pcm_export;
|
||||
@@ -121,6 +123,7 @@ struct AlsaOutput {
|
||||
*/
|
||||
uint8_t *silence;
|
||||
|
||||
public:
|
||||
AlsaOutput(const ConfigBlock &block);
|
||||
|
||||
~AlsaOutput() {
|
||||
@@ -141,11 +144,20 @@ struct AlsaOutput {
|
||||
void Open(AudioFormat &audio_format);
|
||||
void Close();
|
||||
|
||||
size_t PlayRaw(ConstBuffer<void> data);
|
||||
size_t Play(const void *chunk, size_t size);
|
||||
void Drain();
|
||||
void Cancel();
|
||||
|
||||
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
|
||||
void SetupDop(AudioFormat audio_format,
|
||||
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
|
||||
* failure, attempt to switch to DSD_U32.
|
||||
* failure, attempt to switch to DSD_U32 or DSD_U16.
|
||||
*/
|
||||
static int
|
||||
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);
|
||||
|
||||
#if defined(ENABLE_DSD) && defined(HAVE_ALSA_DSD_U32)
|
||||
if (err == 0)
|
||||
if (err == 0) {
|
||||
params.dsd_u16 = false;
|
||||
params.dsd_u32 = false;
|
||||
}
|
||||
|
||||
if (err == -EINVAL && fmt == SND_PCM_FORMAT_DSD_U8) {
|
||||
/* 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);
|
||||
if (err == 0)
|
||||
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
|
||||
|
||||
@@ -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
|
||||
* the configured settings and the audio format.
|
||||
* Wrapper for snd_pcm_hw_params().
|
||||
*
|
||||
* 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
|
||||
AlsaSetup(AlsaOutput *ad, AudioFormat &audio_format,
|
||||
PcmExport::Params ¶ms)
|
||||
AlsaSetupHw(snd_pcm_t *pcm, snd_pcm_hw_params_t *hwparams,
|
||||
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;
|
||||
unsigned retry = MPD_ALSA_RETRY_NR;
|
||||
unsigned int period_time, period_time_ro;
|
||||
unsigned int buffer_time;
|
||||
unsigned int period_time_ro = period_time;
|
||||
|
||||
period_time_ro = period_time = ad->period_time;
|
||||
configure_hw:
|
||||
/* configure HW params */
|
||||
snd_pcm_hw_params_t *hwparams;
|
||||
snd_pcm_hw_params_alloca(&hwparams);
|
||||
err = snd_pcm_hw_params_any(ad->pcm, hwparams);
|
||||
err = snd_pcm_hw_params_any(pcm, hwparams);
|
||||
if (err < 0)
|
||||
throw FormatRuntimeError("snd_pcm_hw_params_any() failed: %s",
|
||||
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);
|
||||
if (err < 0)
|
||||
throw FormatRuntimeError("snd_pcm_hw_params_set_access() failed: %s",
|
||||
snd_strerror(-err));
|
||||
|
||||
err = AlsaSetupFormat(ad->pcm, hwparams, audio_format, params);
|
||||
err = AlsaSetupFormat(pcm, hwparams, audio_format, params);
|
||||
if (err < 0)
|
||||
throw FormatRuntimeError("Failed to configure format %s: %s",
|
||||
sample_format_to_string(audio_format.format),
|
||||
snd_strerror(-err));
|
||||
|
||||
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));
|
||||
|
||||
err = snd_pcm_hw_params_set_channels_near(ad->pcm, hwparams,
|
||||
unsigned int channels = audio_format.channels;
|
||||
err = snd_pcm_hw_params_set_channels_near(pcm, hwparams,
|
||||
&channels);
|
||||
if (err < 0)
|
||||
throw FormatRuntimeError("Failed to configure %i channels: %s",
|
||||
@@ -513,18 +534,23 @@ configure_hw:
|
||||
|
||||
audio_format.channels = (int8_t)channels;
|
||||
|
||||
err = snd_pcm_hw_params_set_rate_near(ad->pcm, hwparams,
|
||||
&sample_rate, nullptr);
|
||||
const unsigned requested_sample_rate =
|
||||
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)
|
||||
throw FormatRuntimeError("Failed to configure sample rate %u Hz: %s",
|
||||
audio_format.sample_rate,
|
||||
requested_sample_rate,
|
||||
snd_strerror(-err));
|
||||
|
||||
if (sample_rate == 0)
|
||||
if (output_sample_rate == 0)
|
||||
throw FormatRuntimeError("Failed to configure sample rate %u Hz",
|
||||
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_hw_params_get_buffer_size_min(hwparams, &buffer_size_min);
|
||||
@@ -546,9 +572,8 @@ configure_hw:
|
||||
(unsigned)period_size_min, (unsigned)period_size_max,
|
||||
period_time_min, period_time_max);
|
||||
|
||||
if (ad->buffer_time > 0) {
|
||||
buffer_time = ad->buffer_time;
|
||||
err = snd_pcm_hw_params_set_buffer_time_near(ad->pcm, hwparams,
|
||||
if (buffer_time > 0) {
|
||||
err = snd_pcm_hw_params_set_buffer_time_near(pcm, hwparams,
|
||||
&buffer_time, nullptr);
|
||||
if (err < 0)
|
||||
throw FormatRuntimeError("snd_pcm_hw_params_set_buffer_time_near() failed: %s",
|
||||
@@ -570,14 +595,14 @@ configure_hw:
|
||||
|
||||
if (period_time_ro > 0) {
|
||||
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);
|
||||
if (err < 0)
|
||||
throw FormatRuntimeError("snd_pcm_hw_params_set_period_time_near() failed: %s",
|
||||
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) {
|
||||
period_time_ro = period_time_ro >> 1;
|
||||
goto configure_hw;
|
||||
@@ -587,9 +612,59 @@ configure_hw:
|
||||
if (retry != MPD_ALSA_RETRY_NR)
|
||||
FormatDebug(alsa_output_domain,
|
||||
"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;
|
||||
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)
|
||||
throw FormatRuntimeError("snd_pcm_hw_params_get_buffer_size() failed: %s",
|
||||
snd_strerror(-err));
|
||||
@@ -601,32 +676,8 @@ configure_hw:
|
||||
throw FormatRuntimeError("snd_pcm_hw_params_get_period_size() failed: %s",
|
||||
snd_strerror(-err));
|
||||
|
||||
/* configure SW params */
|
||||
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);
|
||||
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));
|
||||
AlsaSetupSw(pcm, alsa_buffer_size - alsa_period_size,
|
||||
alsa_period_size);
|
||||
|
||||
FormatDebug(alsa_output_domain, "buffer_size=%u period_size=%u",
|
||||
(unsigned)alsa_buffer_size, (unsigned)alsa_period_size);
|
||||
@@ -639,13 +690,12 @@ configure_hw:
|
||||
happen again. */
|
||||
alsa_period_size = 1;
|
||||
|
||||
ad->period_frames = alsa_period_size;
|
||||
ad->period_position = 0;
|
||||
period_frames = alsa_period_size;
|
||||
period_position = 0;
|
||||
|
||||
ad->silence = new uint8_t[snd_pcm_frames_to_bytes(ad->pcm,
|
||||
alsa_period_size)];
|
||||
snd_pcm_format_set_silence(format, ad->silence,
|
||||
alsa_period_size * channels);
|
||||
silence = new uint8_t[snd_pcm_frames_to_bytes(pcm, alsa_period_size)];
|
||||
snd_pcm_format_set_silence(format, silence,
|
||||
alsa_period_size * audio_format.channels);
|
||||
|
||||
}
|
||||
|
||||
@@ -662,11 +712,10 @@ AlsaOutput::SetupDop(const AudioFormat audio_format,
|
||||
|
||||
AudioFormat dop_format = audio_format;
|
||||
dop_format.format = SampleFormat::S24_P32;
|
||||
dop_format.sample_rate /= 2;
|
||||
|
||||
const AudioFormat check = dop_format;
|
||||
|
||||
AlsaSetup(this, dop_format, params);
|
||||
Setup(dop_format, params);
|
||||
|
||||
/* if the device allows only 32 bit, shift all DoP
|
||||
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;
|
||||
if (dop && audio_format.format == SampleFormat::DSD) {
|
||||
try {
|
||||
SetupDop(audio_format, params);
|
||||
params.dop = true;
|
||||
SetupDop(audio_format, params);
|
||||
return;
|
||||
} catch (...) {
|
||||
dop_error = std::current_exception();
|
||||
params.dop = false;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
#endif
|
||||
AlsaSetup(this, audio_format, params);
|
||||
Setup(audio_format, params);
|
||||
#ifdef ENABLE_DSD
|
||||
} catch (...) {
|
||||
if (dop_error)
|
||||
@@ -742,6 +792,11 @@ AlsaOutput::Open(AudioFormat &audio_format)
|
||||
GetDevice()));
|
||||
}
|
||||
|
||||
#ifdef ENABLE_DSD
|
||||
if (params.dop)
|
||||
FormatDebug(alsa_output_domain, "DoP (DSD over PCM) enabled");
|
||||
#endif
|
||||
|
||||
pcm_export->Open(audio_format.format,
|
||||
audio_format.channels,
|
||||
params);
|
||||
@@ -777,6 +832,7 @@ AlsaOutput::Recover(int err)
|
||||
#if GCC_CHECK_VERSION(7,0)
|
||||
[[fallthrough]];
|
||||
#endif
|
||||
case SND_PCM_STATE_OPEN:
|
||||
case SND_PCM_STATE_SETUP:
|
||||
case SND_PCM_STATE_XRUN:
|
||||
period_position = 0;
|
||||
@@ -785,12 +841,11 @@ AlsaOutput::Recover(int err)
|
||||
case SND_PCM_STATE_DISCONNECTED:
|
||||
break;
|
||||
/* this is no error, so just keep running */
|
||||
case SND_PCM_STATE_PREPARED:
|
||||
case SND_PCM_STATE_RUNNING:
|
||||
case SND_PCM_STATE_DRAINING:
|
||||
err = 0;
|
||||
break;
|
||||
default:
|
||||
/* unknown state, do nothing */
|
||||
break;
|
||||
}
|
||||
|
||||
return err;
|
||||
@@ -822,6 +877,8 @@ AlsaOutput::Cancel()
|
||||
must_prepare = true;
|
||||
|
||||
snd_pcm_drop(pcm);
|
||||
|
||||
pcm_export->Reset();
|
||||
}
|
||||
|
||||
inline void
|
||||
@@ -831,6 +888,36 @@ AlsaOutput::Close()
|
||||
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
|
||||
AlsaOutput::Play(const void *chunk, size_t size)
|
||||
{
|
||||
@@ -856,29 +943,8 @@ AlsaOutput::Play(const void *chunk, size_t size)
|
||||
been played */
|
||||
return size;
|
||||
|
||||
chunk = e.data;
|
||||
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);
|
||||
}
|
||||
|
||||
if (ret < 0 && ret != -EAGAIN && ret != -EINTR &&
|
||||
Recover(ret) < 0)
|
||||
throw FormatRuntimeError("snd_pcm_writei() failed: %s",
|
||||
snd_strerror(-ret));
|
||||
}
|
||||
const size_t bytes_written = PlayRaw(e);
|
||||
return pcm_export->CalcSourceSize(bytes_written);
|
||||
}
|
||||
|
||||
typedef AudioOutputWrapper<AlsaOutput> Wrapper;
|
||||
|
@@ -659,6 +659,10 @@ OssOutput::Cancel()
|
||||
ioctl(fd, SNDCTL_DSP_RESET, 0);
|
||||
DoClose();
|
||||
}
|
||||
|
||||
#ifdef AFMT_S24_PACKED
|
||||
pcm_export->Reset();
|
||||
#endif
|
||||
}
|
||||
|
||||
inline size_t
|
||||
|
@@ -274,7 +274,7 @@ RecorderOutput::ReopenFormat(AllocatedPath &&new_path)
|
||||
assert(path.IsNull());
|
||||
assert(file == nullptr);
|
||||
|
||||
FileOutputStream *new_file = new FileOutputStream(path);
|
||||
FileOutputStream *new_file = new FileOutputStream(new_path);
|
||||
|
||||
AudioFormat new_audio_format = effective_audio_format;
|
||||
|
||||
|
@@ -24,8 +24,16 @@
|
||||
#include "util/Domain.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>
|
||||
|
||||
/* undo the libroar workaround */
|
||||
#undef new
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
#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
|
||||
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_url);
|
||||
|
||||
@@ -68,7 +72,7 @@ icy_server_metadata_string(const char *stream_title, const char* stream_url)
|
||||
|
||||
meta_length--; // subtract placeholder
|
||||
|
||||
meta_length = ((int)meta_length / 16) + 1;
|
||||
meta_length = meta_length / 16;
|
||||
|
||||
icy_metadata[0] = meta_length;
|
||||
|
||||
@@ -109,5 +113,5 @@ icy_server_metadata_page(const Tag &tag, const TagType *types)
|
||||
if (icy_string.IsNull())
|
||||
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);
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user