Compare commits

...

180 Commits

Author SHA1 Message Date
Max Kellermann
a40510c241 release v0.20.6 2017-03-10 16:57:59 +01:00
Max Kellermann
ac8dce6599 lib/curl/Request: "ICY 200 OK" is a response boundary header 2017-03-10 16:28:02 +01:00
Max Kellermann
190d525099 lib/curl/Request: move code to IsResponseBoundaryHeader() 2017-03-10 16:24:30 +01:00
Max Kellermann
1b6666fa39 Partition: handle SYNC_WITH_PLAYER before TAG_MODIFIED
The TAG_MODIFIED handler (i.e. playlist::TagModified()) works only if
the modified song is the current song - something that is not updated
until SYNC_WITH_PLAYER is finished.  This fixes tag updates right
after a new song is started.
2017-03-10 16:11:34 +01:00
Max Kellermann
1dd01c99e8 decoder/sidplay: make compatible with libsidplayfp < 1.8
https://bugs.musicpd.org/view.php?id=4665
2017-03-10 13:48:52 +01:00
Thomas Zander
d50b30a498 Add missing include for cstdlib, otherwise free() is undefined 2017-03-07 20:02:36 +01:00
Max Kellermann
42a3a87f13 util/HugeAllocator: paranoid check for sysconf()<0
Just in case.
2017-03-01 21:50:26 +01:00
Ben Boeckel
9dfedbe619 ReusableArray: fix build error on GCC7
GCC7 outputs the following error without this change:

    src/util/ReusableArray.hxx:61:35: error: no matching function for call to ‘swap(size_t&, const size_t&)’
       std::swap(capacity, src.capacity);

which can be resolved by just using an rvalue-reference rather than a
const rvalue-reference.

Signed-off-by: Ben Boeckel <mathstuf@gmail.com>
2017-03-01 19:38:41 +01:00
Max Kellermann
88957b4c9d android/build.py: build with libc++ instead of GNU libstdc++
Android is migrating away from GCC, and libstdc++ will disappear
eventually.
2017-03-01 17:31:26 +01:00
Max Kellermann
b2f2c9322b db/simple/Mount: workaround for libc++ 2017-03-01 17:31:26 +01:00
Max Kellermann
3be2051808 decoder/Thread: check ENABLE_FFMPEG, not HAVE_FFMPEG
This repairs the damage to commit 74dbaade6f done by commit
b3f5b4932c
2017-03-01 17:06:23 +01:00
Max Kellermann
ff32b0dc9b input/curl: use %lu instead of %llu
Fixes a GCC warning because %llu appears to be unsupported by the
Windows standard library.
2017-03-01 16:44:11 +01:00
Max Kellermann
c1869a11af input/curl: format Range offset as unsigned 2017-03-01 16:44:08 +01:00
Max Kellermann
e22a4fdba4 command/Error: improve libstdc++ 4.9.x detection for std::rethrow_if_nested() workaround 2017-03-01 16:38:22 +01:00
Max Kellermann
29a7b2c5b5 decoder/mpcdec: ignore empty frames
https://bugs.musicpd.org/view.php?id=4656 describes a crash due to
division by zero because frame.samples==0.  This should never happen,
but apparently can happen after seeking.  The best we can do is to
just ignore this frame.
2017-03-01 16:13:21 +01:00
Max Kellermann
3b6c285c2a configure.ac: prepare for 0.20.6 2017-03-01 16:13:21 +01:00
Max Kellermann
575d1786af release v0.20.5 2017-02-20 21:51:31 +01:00
Max Kellermann
bc1c927952 util/TimeFormat: suppress -Wunused on Windows 2017-02-20 21:44:01 +01:00
Max Kellermann
f95bc85f91 python/build/libs: upgrade FFmpeg to 3.2.4 2017-02-20 21:13:43 +01:00
Max Kellermann
4015195314 doc/user: instructions to compile the Windows binary 2017-02-20 21:06:43 +01:00
Max Kellermann
c3d883c6cb win32/build.py: default to x64 build 2017-02-20 21:06:43 +01:00
Max Kellermann
097e30321b win32/build.py: add option --32 2017-02-20 21:06:31 +01:00
Max Kellermann
b6ddeaacf2 win32/build.py: convert argument parser to loop
Allow multiple arguments.
2017-02-20 21:06:21 +01:00
Max Kellermann
b0c60ec124 win32/build.py: add variable "x64", assign host_arch later 2017-02-20 21:04:34 +01:00
Max Kellermann
f3b788703e tag/Handler: improve snprintf() return value check 2017-02-19 19:34:13 +01:00
Max Kellermann
4bb83781e8 output/httpd/IcyMetaDataServer: cast length to unsigned
Fixes another buffer overflow: if the stream has a very long title or
URL, resulting in a metadata string of more than 2 kB, icy_string[0]
is a negative value, which gets casted to size_t - ouch!

 https://bugs.musicpd.org/view.php?id=4652
2017-02-19 19:28:52 +01:00
Max Kellermann
a73195b7cc output/httpd/IcyMetaDataServer: pad the string with 15 spaces
Fixes a buffer overflow due to the bad formula rounding the buffer
size up.  At the same time, remove the "+1" from the meta_length
calculation, which takes the padding into account and at the same time
implements proper rounding.
2017-02-19 19:27:37 +01:00
Max Kellermann
1bd00b8a9a output/httpd/IcyMetaDataServer: remove the int cast
Why did this cast exist??
2017-02-19 19:27:37 +01:00
Max Kellermann
d84eaeafc5 doc/include/tags.xml: clarify that track/disc are decimal 2017-02-18 19:01:04 +01:00
Max Kellermann
20ae84bff9 {input,mixer}/alsa: cancel the DeferredMonitor in the destructor
Yet another potential crash bug fix.
2017-02-10 15:05:49 +01:00
Max Kellermann
7372c931b3 event/Loop: make IsInsideOrNull() available in the NDEBUG build
Fixes build breakage by commit 4e5271fcdf7; and this method does make
sense in non-debug builds.
2017-02-09 21:21:49 +01:00
Max Kellermann
29e1b6e465 mixer/alsa: reset the MultiSocketMonitor in the destructor
Fixes potential crash bug.
2017-02-09 21:13:19 +01:00
Max Kellermann
eda06993f8 event/MultiSocketMonitor: add method Reset() 2017-02-09 21:12:23 +01:00
Max Kellermann
4b30ef1cf2 event/MultiSocketMonitor: use C++11 initializer 2017-02-09 21:12:23 +01:00
Max Kellermann
e92e5e8eb8 event/MultiSocketMonitor: more API documentation
Now ClearSocketList() may only be called from PrepareSockets().
Calling it before destroying the object doesn't work properly, because
it doesn't unregister the TimeoutMonitor and the IdleMonitor.  Some of
its callers need to be fixed.
2017-02-09 21:12:23 +01:00
Max Kellermann
4e5271fcdf event/Call: allow usage during shutdown
Change EventLoop::IsInside() call to EventLoop::IsInsideOrNull().
This means that BlockingCall() may be used during shutdown, after the
main EventLoop::Run() has finished.  This is important because mixers
are currently registered in the main EventLoop.
2017-02-09 21:12:23 +01:00
Max Kellermann
3c55487a16 configure.ac: don't require libsidutils when building with libsidplayfp
The libsidplayfp fork has merged libsidutils into the main library.
The libsidutils we used to link with was part of the original
libsidplay project.
2017-02-09 13:09:03 +01:00
Max Kellermann
76a1cae5d8 {input,mixer}/alsa: fix off-by-one bug in count check
Doesn't make a practical difference - but it's more correct this way.
2017-02-09 12:46:49 +01:00
Max Kellermann
81a97315e3 NEWS: mention ID3 memory leak fix 2017-02-08 08:44:47 +01:00
Max Kellermann
53c14d97a6 lib/nfs/FileReader: remove debug line 2017-02-08 08:43:56 +01:00
Max Kellermann
69a82eec17 tag/TagId3: use AtScopeExit() for exception-safety 2017-02-06 23:32:07 +01:00
Max Kellermann
45cadef22f configure.ac: prepare for 0.20.5 2017-02-06 23:28:36 +01:00
Max Kellermann
0a033fb10a release v0.20.4 2017-02-01 21:59:36 +01:00
Max Kellermann
591afa0647 lib/nfs/Connection: detect socket hangup and unregister from epoll
Fixes race condition when epoll_ctl() gets called after the socket has
been closed, which may affect a different socket created by another
thread meanwhile.
2017-02-01 21:44:20 +01:00
Max Kellermann
05eac20ffe lib/nfs/Connection: detect libnfs reconnect
When rpc_reconnect_requeue() gets called from inside nfs_service(),
the NfsInputStream can stall completely because the old socket has
been unregistered from epoll automatically, but the new one has never
been registered.  Therefore, nfs_service() will never be called again.

This kludge attempts to detect this condition by checking
nfs_which_events()==POLLOUT.

https://bugs.musicpd.org/view.php?id=4081
2017-02-01 21:36:58 +01:00
Max Kellermann
38d263ac19 output/sndio: work around a libroar C++ incompatibility
Same as in commit e02d8ad8d2, but this time for the sndio plugin
which can be emulated by libroar.
2017-02-01 19:53:23 +01:00
Thomas Zander
f71c204eef Correct method types to match Interface.hxx 2017-01-31 21:22:02 +01:00
Thomas Zander
51147203be free() require cstdlib to be included 2017-01-31 21:21:37 +01:00
Max Kellermann
a931686317 pcm/SampleFormat: workaround for GCC 4.9 "constexpr" bug
GCC 4.9 has incomplete C++14 support.  Specifically, it doesn't allow
switch/case in "constexpr" functions.
2017-01-27 11:02:58 +01:00
Max Kellermann
5bd322bdcf python/libs: upgrade Opus to 1.1.4 2017-01-27 08:47:58 +01:00
Max Kellermann
bb097109f0 configure.ac: prepare for 0.20.4 2017-01-27 08:47:36 +01:00
Max Kellermann
2ab6c40ff1 release v0.20.3 2017-01-25 08:53:16 +01:00
Max Kellermann
68bb738af2 input/alsa: use snd_pcm_?w_params_alloca() 2017-01-25 08:47:20 +01:00
Max Kellermann
6b968beede output/alsa: convert to class, make attributes private 2017-01-24 23:08:16 +01:00
Max Kellermann
f68dd1bffb output/alsa: make AlsaSetup() an AlsaOutput method 2017-01-24 23:06:33 +01:00
Max Kellermann
f92b71ca99 output/alsa: move code from AlsaSetup() to AlsaSetupSw() 2017-01-24 23:05:29 +01:00
Max Kellermann
2b79fe2d6a output/alsa: move code from AlsaSetup() to AlsaSetupHw() 2017-01-24 22:48:48 +01:00
Max Kellermann
44dd9af276 lib/upnp/Util: pass single delimiter character to stringToTokens() 2017-01-23 19:34:55 +01:00
Max Kellermann
d3013d4f8c lib/upnp/Util: remove parameter "skipinit", always true 2017-01-23 19:28:07 +01:00
Max Kellermann
678524ad21 lib/upnp/WorkQueue: fix race condition
With "ok==false", newly created threads may quit instantly.
2017-01-23 19:25:30 +01:00
Max Kellermann
32a64481f2 lib/upnp: fix bad std::chrono cast
libupnp provides seconds, not whatever time unit is used by
std::chrono::steady_clock.
2017-01-23 19:16:14 +01:00
Max Kellermann
1776015c6c db/simple: drop redundant "virtual" 2017-01-23 18:57:23 +01:00
Max Kellermann
f1c71a26e3 db/proxy: drop redundant "virtual" 2017-01-23 18:56:45 +01:00
Max Kellermann
e78ab767d3 db/proxy: make connect errors during startup non-fatal 2017-01-23 18:55:40 +01:00
Max Kellermann
f01eb2f95d db/proxy: improve Connect() error message 2017-01-23 18:55:18 +01:00
Max Kellermann
1450e45d97 Main, db/Glue: improve error messages 2017-01-23 18:52:16 +01:00
Max Kellermann
ec8cba369c lib/upnp/WorkQueue: disallow copying 2017-01-23 18:35:58 +01:00
Max Kellermann
f4c248f406 lib/upnp/WorkQueue: make constructor explicit 2017-01-23 18:35:47 +01:00
Max Kellermann
f3b2a58646 lib/upnp/WorkQueue: use C++11 initializers 2017-01-23 18:35:22 +01:00
Max Kellermann
c6f89c42b2 db/proxy: make the base class of LibmpdclientError public
If the base class is not accessible, the "catching" the base class
won't work.  This caused the fatal error:

 terminate called after throwing an instance of 'LibmpdclientError'
2017-01-23 18:28:40 +01:00
Max Kellermann
5e93cfdd9e output/Source: reset the ReplayGain serials ion OpenFilter()
Each close/open cycle resets the Filter's state, because a new Filter
instance is being created.  That results in the serials
(replay_gain_serial and other_replay_gain_serial) being out of sync
with the internal ReplayGainFilter state.

So instead of initializing those serials once, we need to initialize
them each time we create new ReplayGainFilter instances, i.e. in
OpenFilter().

 https://bugs.musicpd.org/view.php?id=4632
2017-01-23 17:55:04 +01:00
Max Kellermann
d91d5a3ab5 playlist/SoundCloud: eliminate unnecessary casted variable 2017-01-20 17:16:11 +01:00
Max Kellermann
907c045f33 doc/user: add missing playlist plugins 2017-01-20 17:09:19 +01:00
Max Kellermann
90f189eb54 doc/user: mention which commands are available with playlist plugins 2017-01-20 16:59:07 +01:00
Florian Schlichting
4abd5b2112 doc/user: document effect of http_proxy envvar on curl plugin 2017-01-20 16:52:02 +01:00
Max Kellermann
df9a665994 pcm/Traits: add "SILENCE" attribute 2017-01-20 15:57:09 +01:00
Max Kellermann
7a098ca0ed pcm/Traits: add specialization for SampleFormat::DSD 2017-01-20 15:48:30 +01:00
Max Kellermann
33716732a1 pcm/PcmChannels: silence surround channels when converting from stereo
Previously, there was no special code to convert stereo to
multi-channel.  The generic solution for this was to convert to mono,
and then copy the result to all channels.  That's a pretty bad
solution, but at least something which always renders audio.  MPD does
something, instead of failing.

Now that MPD has proper support for multi-channel (by defining the
channel order), we can do better than that.  It is a (somewhat) common
case to play back stereo music on a DAC which can only do
multi-channel.  The best approach here is to copy the stereo channels
to front-left and front-right, and apply the "silence" pattern to all
other channels.
2017-01-19 10:53:41 +01:00
Max Kellermann
97ae594375 DetachedSong: use C++11 initializers 2017-01-18 13:13:36 +01:00
Max Kellermann
3f321ae9a0 pcm/SampleFormat: make the two inline functions "constexpr" 2017-01-17 22:52:09 +01:00
Max Kellermann
161d32a7e7 AudioFormat: update ToString() API documentatio 2017-01-17 22:48:34 +01:00
Max Kellermann
d7137586a9 Audio{Format,Parser}: use shortcuts such as "dsd64" in log messages 2017-01-17 22:42:23 +01:00
Max Kellermann
cd0c06ba6e doc/protocol: refer to user manual for status/audio 2017-01-17 22:42:23 +01:00
Max Kellermann
899ab63d91 doc/user: document the "dsd" sample format 2017-01-17 22:36:44 +01:00
Max Kellermann
1097820a5a doc/user: add <replaceable> element 2017-01-17 22:36:44 +01:00
Max Kellermann
39114f91a7 AudioFormat: replace struct audio_format_string with class StringBuffer, return it 2017-01-17 22:18:21 +01:00
Max Kellermann
4f01387edf util/StringBuffer: new utility class 2017-01-17 22:03:42 +01:00
Max Kellermann
de3e0585f1 AudioFormat: move enum SampleFormat to pcm/SampleFormat.hxx 2017-01-17 22:01:01 +01:00
Max Kellermann
f85f25ba82 test: add AudioFormat unit test 2017-01-17 12:02:41 +01:00
Max Kellermann
10a2c179f9 Makefile.am: move AudioFormat.cxx to libpcm.a 2017-01-17 12:01:49 +01:00
Max Kellermann
6eea56861b AUTHORS, ...: update my email address 2017-01-17 11:54:55 +01:00
Jörg Krause
21fd2064ae Makefile.am: fix linking xiph with ogg
The internal static xiph library needs to link with libogg. Otherwise
building mpd will fail:

```
/mips-linux-gnu/bin/ld: libxiph.a(libxiph_a-OggVisitor.o): undefined
reference to symbol 'ogg_stream_packetout'
```

Signed-off-by: Jörg Krause <joerg.krause@embedded.rocks>
2017-01-17 11:24:20 +01:00
Max Kellermann
dcbab8e37a PlaylistFile: "playlistadd" creates new playlist if it does not exist, as documented 2017-01-16 20:55:19 +01:00
Max Kellermann
5677278251 CommandLine: update copyright year 2017-01-16 12:04:04 +01:00
Max Kellermann
a83bee993d configure.ac: prepare for 0.20.3 2017-01-16 12:03:22 +01:00
Max Kellermann
96a31f554a release v0.20.2 2017-01-15 01:28:00 +01:00
Max Kellermann
d14ec6aea5 output/Thread: reconfigure ConvertFilter for its new input AudioFormat
If the input AudioFormat changes but the out_audio_format doesn't
change (e.g. because there is a fixed "format" setting in this
"audio_output" section), the ConvertFilter needs to be reconfigured.
This didn't happen, resulting in awful static noise after changing
songs.
2017-01-15 01:24:17 +01:00
Max Kellermann
917cedf893 output/Thread: move AudioFormat logging code around 2017-01-15 01:23:49 +01:00
Max Kellermann
193dd71600 output/Thread: remember the original filter audio format in local variable 2017-01-15 01:21:14 +01:00
Max Kellermann
6c293a3d7f lib/nfs: add more API documentation 2017-01-15 00:58:49 +01:00
Max Kellermann
e847ddf011 DetachedSong: compare start_time and end_time in IsSame()
This method is used by DecoderControl::IsCurrentSong(), which is used
by the player thread to check whether the current decoder instance can
be reused to seek.  When switching to another song in the same CUE
sheet, previously DetachedSong::IsSame() returned true, and thus the
old decoder instance was used for the new song, not considering the
new end_time.  This led to the old decoder quickly quitting.
2017-01-15 00:54:25 +01:00
Max Kellermann
7e8b448985 input/alsa: set period_size=buffer_size/4
This way, we have four periods instead of the default of two.  With
only two periods, we don't get woken up often enough, and we
frequently encounter buffer overruns.  With four periods, we have more
time to breathe, and the buffer overruns magically disappear.
2017-01-14 21:50:28 +01:00
Max Kellermann
d1f3a87c08 input/alsa: remove the start_threshold setting
This setting is mostly useless for capture devices.  There's no point
in configuring it.
2017-01-14 21:47:37 +01:00
Max Kellermann
9f8145e590 input/alsa: dump buffer/period sizes 2017-01-14 21:09:57 +01:00
Steven O'Brien
791efc171a input/alsa: enable non-blocking mode 2017-01-14 20:59:57 +01:00
Steven O'Brien
144312a525 input/alsa: handle EAGAIN 2017-01-14 20:59:23 +01:00
Max Kellermann
92684112ed input/alsa: call snd_pcm_start() after snd_pcm_prepare()
This is necessary because we'll never get woken up again by
epoll_wait() after a buffer overrun recovery, unless we start the PCM
explicitly before returning to the I/O loop.
2017-01-14 20:58:30 +01:00
Max Kellermann
ef114ee6cb input/alsa: improve logging in Recover()
Copy yet more code from the ALSA output plugin.
2017-01-14 20:52:41 +01:00
Max Kellermann
667f209742 input/alsa: check snd_pcm_state() in Recover()
Copy some good code from the ALSA output plugin.
2017-01-14 20:51:51 +01:00
Max Kellermann
4ad0747c78 output/alsa: explicitly mention all snd_pcm_state() enums
I want a compiler warning when a new state needs to be considered
here.
2017-01-14 20:49:15 +01:00
Max Kellermann
c5cf66402c input/alsa: make two attributes "const" 2017-01-13 20:26:36 +01:00
Max Kellermann
05417049eb input/alsa: clear sockets from within IOThread
Fixes assertion failure in implicit destructor.
2017-01-13 20:17:16 +01:00
Max Kellermann
c7b0c46d9f output/recorder: fix typo in variable name
Fixes the dreaded error "Failed to create : No such file or
directory".

 https://bugs.musicpd.org/view.php?id=4625
2017-01-12 21:36:32 +01:00
Max Kellermann
df578c91ad output/alsa: log DoP mode 2017-01-11 22:50:40 +01:00
Max Kellermann
70008c47c9 output/alsa: support DSD_U16 2017-01-11 22:47:21 +01:00
Max Kellermann
938affef32 pcm/export: support DSD_U16 2017-01-11 22:47:12 +01:00
Max Kellermann
a3c33000ee pcm/Dsd32: include cleanup 2017-01-11 22:47:12 +01:00
Max Kellermann
1e54b7b294 test/test_pcm: fix the DSD_U32 byte order
The unit test was wrong as well.  D'oh!
2017-01-11 22:39:23 +01:00
Max Kellermann
cc0dbcf3f4 pcm/Dsd32: fix the byte order
The byte order of DSD_U32 was wrong from the start.  The oldest bits
must be in the MSB, not in the LSB, according to
snd_pcm_format_descriptions in alsa-lib.
2017-01-11 22:25:54 +01:00
Max Kellermann
c5a2cadccc pcm/Export: convert to class, make members private 2017-01-11 21:48:43 +01:00
Max Kellermann
9aa43416b6 pcm/dop: remove unnecessary assertions 2017-01-11 21:48:43 +01:00
Max Kellermann
8364029db8 output/alsa: move code to PlayRaw() 2017-01-11 21:38:05 +01:00
Max Kellermann
d842d21be0 util/ReusableArray: add method GetCapacity() 2017-01-11 20:37:12 +01:00
Max Kellermann
3514fd2433 util/ReusableArray: add move constructor/operator 2017-01-11 20:37:12 +01:00
Max Kellermann
6778ff27ea util/ReusableArray: use C++11 initializers 2017-01-11 20:33:01 +01:00
Max Kellermann
f32315d699 pcm/Export: remove obsolete gcc warning suppression 2017-01-11 20:31:48 +01:00
Max Kellermann
8b754b24b6 pcm/Buffer: update API documentation 2017-01-11 20:24:32 +01:00
Max Kellermann
b1bee9ff38 test/test_pcm: enable the DSD unit tests
These were disabled by accident.
2017-01-11 20:06:10 +01:00
Max Kellermann
569be2d402 test/test_pcm_export: fix TestDop() sample rate results 2017-01-11 20:06:10 +01:00
Max Kellermann
78a73eac53 pcm/Export: add (dummy) method Cancel()
We'll have some code for it soon.
2017-01-11 15:41:28 +01:00
Max Kellermann
533cb99c33 output/Source: reset all filters in Cancel() 2017-01-11 15:39:18 +01:00
Max Kellermann
79726940dc output/Source: un-inline Cancel() 2017-01-11 15:39:00 +01:00
Max Kellermann
27c7891169 filter/Internal: add method Reset() 2017-01-11 15:34:25 +01:00
Max Kellermann
7a3a793a12 decoder/Bridge: call PcmConvert::Reset() after seeking 2017-01-11 15:32:57 +01:00
Max Kellermann
8088469eca pcm/Convert: add method Reset() 2017-01-11 15:30:30 +01:00
Max Kellermann
3dcb082015 pcm/Resampler: add method Reset()
Hook for src_reset(), not yet used.
2017-01-11 15:26:48 +01:00
Max Kellermann
bece023028 pcm/PcmDsd: move Dsd8To32() to Dsd32.cxx 2017-01-11 15:22:43 +01:00
Max Kellermann
9c4df66925 pcm/Export: halve the sample rate for DoP
Move this sample rate fixup from the ALSA output plugin to PcmExport,
where it belongs.
2017-01-11 10:33:23 +01:00
Max Kellermann
2b43ceb6c6 pcm/Export: DSD_U32 quarters the sample rate
DSD_U32 packs four bytes instead of one large "sample", thus the
sample rate is one quarter of the input sample rate.  This fixes a
rather critical DSD_U32 playback problem.
2017-01-11 10:14:41 +01:00
Max Kellermann
c143adba91 pcm/Export: add CalcOutputSampleRate(), CalcInputSampleRate()
Prepare for DSD sample rate fixups.
2017-01-10 23:48:26 +01:00
Max Kellermann
142fdc8d86 decoder/flac: add options "probesize" and "analyzeduration"
https://bugs.musicpd.org/view.php?id=3876
2017-01-10 23:05:04 +01:00
Max Kellermann
67778dcd3d configure.ac: prepare for 0.20.2 2017-01-10 23:01:42 +01:00
Max Kellermann
ed80863eac release v0.20.1 2017-01-09 18:10:18 +01:00
Max Kellermann
c3fc84de12 input/curl: wake up client thread after seek to end of file
Call SeekDone() to avoid the freeze bug.
2017-01-09 18:08:33 +01:00
Max Kellermann
904f83cd85 doc/developer: add GitHub reference 2017-01-09 17:19:15 +01:00
Max Kellermann
28bf100a50 doc/developer: more code style 2017-01-09 17:13:28 +01:00
Max Kellermann
accbd4e82a doc/developer: change C++11 to C++14 2017-01-09 17:13:28 +01:00
Max Kellermann
d7f478c154 doc/developer: add XML ids 2017-01-09 17:13:28 +01:00
Wieland Hoffmann
8f7f13fea4 doc/user: Replace "It used used" with "It is used" 2017-01-08 18:23:13 +01:00
Max Kellermann
c82b03a74c decoder/wavpack: fix crash bug 2017-01-08 14:54:12 +01:00
Max Kellermann
58fb36bdb9 storage/http: new storage plugin 2017-01-08 14:40:20 +01:00
Max Kellermann
4297a7b0a4 lib/curl/Request: move exception handling out of the WRITEFUNCTION
libcurl's WRITEFUNCTION is pretty fragile; if we destroy the CURL*
instance or even unregister it using curl_multi_remove_handle(),
libcurl will crash instantly.  But still we need to be able to handle
exceptions from inside the WRITEFUNCTION, and call
CurlResponseHandler::OnError(), which may destroy the whole thing.  As
a workaround, I use DeferredMonitor to postpone the OnError() call
into a stack frame which is allowed to destroy the request.
2017-01-08 14:36:27 +01:00
Max Kellermann
1bab6d0dd7 lib/curl/Request: move catch clause out of FinishHeaders
Let the caller decide what to do with the exception.
2017-01-08 14:36:27 +01:00
Max Kellermann
13b85edbe2 lib/curl/Request: postpone the curl_easy_cleanup() call
When the request is done, only unregister the CURL* handle, but do not
delete it yet - it may still be needed for CURLINFO_RESPONSE_CODE.
2017-01-08 13:51:53 +01:00
Max Kellermann
dc53098e43 lib/curl/Request: allow Stop() to be called twice
Convert assertion to runtime check.  This is useful because this is a
public method, and the caller has no chance to check if the object is
still registered.
2017-01-08 13:51:53 +01:00
Max Kellermann
3c66feff5a lib/curl/Global: defer the ReadInfo() call
Fixes a crash that can occur due to recursion from InvalidateSockets()
to ReadInfo() to CurlRequest callbacks.
2017-01-08 12:46:35 +01:00
Max Kellermann
218c3bc0d5 lib/curl/Multi: fix typo 2017-01-08 12:46:35 +01:00
Max Kellermann
9f5eddcd13 lib/curl/Global: move code to UpdateTimeout() 2017-01-08 12:44:07 +01:00
Max Kellermann
3cba76552b lib/curl/Global: drop redundant ">=0" check 2017-01-08 12:44:04 +01:00
Max Kellermann
e98a8b624b lib/curl/Global: drop redundant "virtual" 2017-01-08 12:41:26 +01:00
Max Kellermann
6c6947b01f util/UriUtil: add uri_get_path() 2017-01-08 11:05:58 +01:00
Max Kellermann
78c91e9e5b test/run_storage: don't print unknown time stamps 2017-01-08 10:41:08 +01:00
Max Kellermann
44493ca0c4 util/TimeParser: add "pure" attribute 2017-01-08 10:41:08 +01:00
Max Kellermann
42acf78b09 util/TimeParser: wrapper for strptime()
Move code from SongFilter.cxx.
2017-01-07 22:11:45 +01:00
TermeHansen
3aa9f8af18 Rewrite of AlsaMixerPlugin to use volume_mapping
Changed AlsaMixerPlugin to use the get and set normalized functions from volume_mapping of alsa-utils/alsamixer
Changed volume_mapping set volume to be for all channels and not per channel
added volume_mapping files to Makefile.am
2017-01-07 16:30:19 +01:00
TermeHansen
8a32ee30a5 Adding volume_mapping from alsa-utils/alsamixer
source:
http://git.alsa-project.org/?p=alsa-utils.git;a=blob_plain;f=alsamixer/volume_mapping.c;hb=HEAD
http://git.alsa-project.org/?p=alsa-utils.git;a=blob_plain;f=alsamixer/volume_mapping.h;hb=HEAD
2017-01-07 16:26:36 +01:00
Max Kellermann
981dc0626b lib/expat/ExpatParser: add constructor overload for XML_ParserCreateNS() 2017-01-07 16:17:53 +01:00
Max Kellermann
8986d14e98 lib/expat/ExpatParser: make constructors "explicit" 2017-01-07 16:15:11 +01:00
Max Kellermann
5163b1a624 lib/curl/Request: require the caller to explicitly register the request
This allows constructing an instance in any thread, and register it
inside the IOThread later.
2017-01-07 16:01:58 +01:00
Max Kellermann
860aa9d6d0 lib/expat/ExpatParser: move InputStream overload to separate source file
Eliminate one unnecessary dependency for debug programs which don't
need the InputStream API.
2017-01-07 15:46:36 +01:00
Max Kellermann
64dc5212f9 Makefile.am: add variable CURL_SOURCES 2017-01-07 14:19:24 +01:00
Max Kellermann
6cff3214f3 lib/curl/Slist: new wrapper for curl_slist 2017-01-06 19:37:31 +01:00
Max Kellermann
fd910bd5e9 db/upnp: use "override" instead of "virtual" 2017-01-06 19:35:58 +01:00
Max Kellermann
c6086bed41 filter/Internal: remove the default constructor
Not used.  Force implementations to initialize out_audio_format.
2017-01-06 12:45:52 +01:00
Max Kellermann
1a9dfdfab8 filter/AutoConvert: initialize Filter::out_audio_format 2017-01-06 12:44:55 +01:00
Max Kellermann
5284cd11a9 filter/AutoConvert: remove obsolete NULL check 2017-01-06 12:35:06 +01:00
Max Kellermann
d1a47cffad filter/convert: remove obsolete method prototype 2017-01-06 12:34:39 +01:00
Max Kellermann
f469595eee filter/Internal: remove obsolete doxygen line 2017-01-06 12:34:39 +01:00
Max Kellermann
9cfc52f114 filter/Internal: add assertion to constructor 2017-01-06 11:17:55 +01:00
Max Kellermann
30bfb756c2 configure.ac: prepare for 0.20.1 2017-01-05 19:36:32 +01:00
215 changed files with 3581 additions and 860 deletions
AUTHORSMakefile.amNEWS
android
configure.ac
doc
m4
python/build
src
AudioFormat.cxxAudioFormat.hxxAudioParser.cxxCommandLine.cxxDetachedSong.hxxMain.cxxPartition.cxxPlaylistFile.cxxSongFilter.cxx
command
db
decoder
event
filter
input
java
lib
mixer
net
output
pcm
playlist
storage
system
tag
thread
util
test
win32

@@ -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>

@@ -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

@@ -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

@@ -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>

@@ -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

@@ -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);
}

@@ -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));

@@ -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);
}

@@ -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 &params);
#ifdef ENABLE_DSD
void SetupDop(AudioFormat audio_format,
PcmExport::Params &params);
@@ -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 &params)
AlsaSetupHw(snd_pcm_t *pcm, snd_pcm_hw_params_t *hwparams,
unsigned buffer_time, unsigned period_time,
AudioFormat &audio_format, PcmExport::Params &params)
{
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 &params)
{
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 &params)
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