Compare commits

..

100 Commits

Author SHA1 Message Date
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
181 changed files with 1860 additions and 635 deletions

View File

@@ -5,7 +5,7 @@ The following people have contributed code to MPD:
Warren Dukes <warren.dukes@gmail.com> Warren Dukes <warren.dukes@gmail.com>
Avuton Olrich <avuton@gmail.com> Avuton Olrich <avuton@gmail.com>
Max Kellermann <max@duempel.org> Max Kellermann <max.kellermann@gmail.com>
Laszlo Ashin <kodest@gmail.com> Laszlo Ashin <kodest@gmail.com>
Viliam Mateicka <viliam.mateicka@gmail.com> Viliam Mateicka <viliam.mateicka@gmail.com>
Eric Wollesen <encoded@xmtp.net> Eric Wollesen <encoded@xmtp.net>

View File

@@ -415,6 +415,7 @@ libutil_a_SOURCES = \
src/util/CharUtil.hxx \ src/util/CharUtil.hxx \
src/util/NumberParser.hxx \ src/util/NumberParser.hxx \
src/util/MimeType.cxx src/util/MimeType.hxx \ src/util/MimeType.cxx src/util/MimeType.hxx \
src/util/StringBuffer.hxx \
src/util/StringPointer.hxx \ src/util/StringPointer.hxx \
src/util/StringView.cxx src/util/StringView.hxx \ src/util/StringView.cxx src/util/StringView.hxx \
src/util/AllocatedString.cxx src/util/AllocatedString.hxx \ src/util/AllocatedString.cxx src/util/AllocatedString.hxx \
@@ -539,6 +540,10 @@ ICU_LDADD = libicu.a $(ICU_LIBS)
# PCM library # PCM library
libpcm_a_SOURCES = \ libpcm_a_SOURCES = \
src/CheckAudioFormat.cxx src/CheckAudioFormat.hxx \
src/AudioFormat.cxx src/AudioFormat.hxx \
src/AudioParser.cxx src/AudioParser.hxx \
src/pcm/SampleFormat.cxx src/pcm/SampleFormat.hxx \
src/pcm/Traits.hxx \ src/pcm/Traits.hxx \
src/pcm/Interleave.cxx src/pcm/Interleave.hxx \ src/pcm/Interleave.cxx src/pcm/Interleave.hxx \
src/pcm/PcmBuffer.cxx src/pcm/PcmBuffer.hxx \ src/pcm/PcmBuffer.cxx src/pcm/PcmBuffer.hxx \
@@ -575,6 +580,8 @@ PCM_LIBS = \
if ENABLE_DSD if ENABLE_DSD
libpcm_a_SOURCES += \ libpcm_a_SOURCES += \
src/pcm/Dsd16.cxx src/pcm/Dsd16.hxx \
src/pcm/Dsd32.cxx src/pcm/Dsd32.hxx \
src/pcm/PcmDsd.cxx src/pcm/PcmDsd.hxx \ src/pcm/PcmDsd.cxx src/pcm/PcmDsd.hxx \
src/pcm/dsd2pcm/dsd2pcm.c src/pcm/dsd2pcm/dsd2pcm.h src/pcm/dsd2pcm/dsd2pcm.c src/pcm/dsd2pcm/dsd2pcm.h
endif endif
@@ -613,7 +620,8 @@ libxiph_a_SOURCES += \
src/lib/xiph/OggStreamState.hxx src/lib/xiph/OggStreamState.hxx
endif endif
XIPH_LIBS = libxiph.a XIPH_LIBS = libxiph.a \
$(OGG_LIBS)
endif endif
@@ -869,9 +877,6 @@ ARCHIVE_LIBS =
endif endif
libbasic_a_SOURCES = \ libbasic_a_SOURCES = \
src/CheckAudioFormat.cxx src/CheckAudioFormat.hxx \
src/AudioFormat.cxx src/AudioFormat.hxx \
src/AudioParser.cxx src/AudioParser.hxx \
src/ReplayGainConfig.hxx \ src/ReplayGainConfig.hxx \
src/ReplayGainMode.cxx src/ReplayGainMode.hxx \ src/ReplayGainMode.cxx src/ReplayGainMode.hxx \
src/ReplayGainInfo.cxx src/ReplayGainInfo.hxx src/ReplayGainInfo.cxx src/ReplayGainInfo.hxx
@@ -2225,6 +2230,7 @@ test_test_icy_parser_LDADD = \
endif endif
test_test_pcm_SOURCES = \ test_test_pcm_SOURCES = \
test/TestAudioFormat.cxx test/TestAudioFormat.hxx \
test/test_pcm_util.hxx \ test/test_pcm_util.hxx \
test/test_pcm_dither.cxx \ test/test_pcm_dither.cxx \
test/test_pcm_pack.cxx \ test/test_pcm_pack.cxx \

37
NEWS
View File

@@ -1,3 +1,40 @@
ver 0.20.4 (2017/02/01)
* input
- nfs: fix freeze after reconnect
* output
- sndio: work around a libroar C++ incompatibility
* workaround for GCC 4.9 "constexpr" bug
* fix FreeBSD build failure
ver 0.20.3 (2017/01/25)
* protocol
- "playlistadd" creates new playlist if it does not exist, as documented
* database
- proxy: fix error "terminate called after throwing ..."
- proxy: make connect errors during startup non-fatal
* neighbor
- upnp: fix premature expiry
* replay gain: don't reset ReplayGain levels when unpausing playback
* silence surround channels when converting from stereo
* use shortcuts such as "dsd64" in log messages
ver 0.20.2 (2017/01/15)
* input
- alsa: fix crash bug
- alsa: fix buffer overruns
* decoder
- flac: add options "probesize" and "analyzeduration"
* resampler
- libsamplerate: reset state after seeking
* output
- fix static noise after changing to a different audio format
- alsa: fix the DSD_U32 sample rate
- alsa: fix the DSD_U32 byte order
- alsa: support DSD_U16
- recorder: fix error "Failed to create : No such file or directory"
* playlist
- cue: fix skipping songs
ver 0.20.1 (2017/01/09) ver 0.20.1 (2017/01/09)
* input * input
- curl: fix crash bug - curl: fix crash bug

View File

@@ -1,10 +1,10 @@
AC_PREREQ(2.60) AC_PREREQ(2.60)
AC_INIT(mpd, 0.20.1, musicpd-dev-team@lists.sourceforge.net) AC_INIT(mpd, 0.20.4, musicpd-dev-team@lists.sourceforge.net)
VERSION_MAJOR=0 VERSION_MAJOR=0
VERSION_MINOR=20 VERSION_MINOR=20
VERSION_REVISION=1 VERSION_REVISION=4
VERSION_EXTRA=0 VERSION_EXTRA=0
AC_CONFIG_SRCDIR([src/Main.cxx]) AC_CONFIG_SRCDIR([src/Main.cxx])

View File

@@ -50,6 +50,6 @@ If you find a bug, please report it at
.br .br
<\fBhttp://bugs.musicpd.org/bug_report_page.php\fP>. <\fBhttp://bugs.musicpd.org/bug_report_page.php\fP>.
.SH AUTHORS .SH AUTHORS
Max Kellermann <max@duempel.org> Max Kellermann <max.kellermann@gmail.com>
Special thanks to all the people that provided feedback and patches. Special thanks to all the people that provided feedback and patches.

View File

@@ -573,7 +573,12 @@
<listitem> <listitem>
<para> <para>
<varname>audio</varname>: <varname>audio</varname>:
<returnvalue>sampleRate:bits:channels</returnvalue> <returnvalue>
The format emitted by the decoder plugin during
playback, format:
"<replaceable>samplerate:bits:channels</replaceable>".
Check the user manual for a detailed explanation.
</returnvalue>
</para> </para>
</listitem> </listitem>
<listitem> <listitem>

View File

@@ -576,10 +576,11 @@ systemctl start mpd.socket</programlisting>
</entry> </entry>
<entry> <entry>
<para> <para>
Always open the audio output with the specified audio Always open the audio output with the specified
format (samplerate:bits:channels), regardless of the audio format
format of the input file. This is optional for most (<replaceable>samplerate:bits:channels</replaceable>),
plugins. regardless of the format of the input file. This is
optional for most plugins.
</para> </para>
<para> <para>
Any of the three attributes may be an asterisk to Any of the three attributes may be an asterisk to
@@ -596,7 +597,20 @@ systemctl start mpd.socket</programlisting>
24 bit integer samples padded to 32 bit), 24 bit integer samples padded to 32 bit),
<varname>32</varname> (signed 32 bit integer <varname>32</varname> (signed 32 bit integer
samples), <varname>f</varname> (32 bit floating samples), <varname>f</varname> (32 bit floating
point, -1.0 to 1.0). point, -1.0 to 1.0), "<varname>dsd</varname>" means
DSD (Direct Stream Digital). For DSD, there are
special cases such as "<varname>dsd64</varname>",
which allows you to omit the sample rate
(e.g. <parameter>dsd512:2</parameter> for stereo
DSD512, i.e. 22.5792 MHz).
</para>
<para>
The sample rate is special for DSD:
<application>MPD</application> counts the number of
bytes, not bits. Thus, a DSD "bit" rate of 22.5792
MHz (DSD512) is 2822400 from
<application>MPD</application>'s point of view
(44100*512/8).
</para> </para>
</entry> </entry>
</row> </row>
@@ -742,9 +756,11 @@ systemctl start mpd.socket</programlisting>
<title>Configuring playlist plugins</title> <title>Configuring playlist plugins</title>
<para> <para>
Playlist plugins are used to load remote playlists. This is Playlist plugins are used to load remote playlists (protocol
not related to <application>MPD</application>'s playlist commands <command>load</command>,
directory. <command>listplaylist</command> and
<command>listplaylistinfo</command>). This is not related to
<application>MPD</application>'s playlist directory.
</para> </para>
<para> <para>
@@ -1800,6 +1816,13 @@ run</programlisting>
database. database.
</para> </para>
<para>
Note that unless overridden by the below settings (e.g. by
setting them to a blank value), general curl configuration
from environment variables such as http_proxy or specified
in ~/.curlrc will be in effect.
</para>
<informaltable> <informaltable>
<tgroup cols="2"> <tgroup cols="2">
<thead> <thead>
@@ -2206,6 +2229,48 @@ run</programlisting>
Decodes various codecs using Decodes various codecs using
<application>FFmpeg</application>. <application>FFmpeg</application>.
</para> </para>
<informaltable>
<tgroup cols="2">
<thead>
<row>
<entry>Setting</entry>
<entry>Description</entry>
</row>
</thead>
<tbody>
<row>
<entry>
<varname>analyzeduration</varname>
<parameter>VALUE</parameter>
</entry>
<entry>
Sets the FFmpeg muxer option
<varname>analyzeduration</varname>, which specifies
how many microseconds are analyzed to probe the
input. The <ulink
url="https://ffmpeg.org/ffmpeg-formats.html">FFmpeg
formats documentation</ulink> has more information.
</entry>
</row>
<row>
<entry>
<varname>probesize</varname>
<parameter>VALUE</parameter>
</entry>
<entry>
Sets the FFmpeg muxer option
<varname>probesize</varname>, which specifies
probing size in bytes, i.e. the size of the data to
analyze to get stream information. The <ulink
url="https://ffmpeg.org/ffmpeg-formats.html">FFmpeg
formats documentation</ulink> has more information.
</entry>
</row>
</tbody>
</tgroup>
</informaltable>
</section> </section>
<section id="flac_decoder"> <section id="flac_decoder">
@@ -4151,6 +4216,22 @@ run</programlisting>
<section id="playlist_plugins"> <section id="playlist_plugins">
<title>Playlist plugins</title> <title>Playlist plugins</title>
<section>
<title><varname>asx</varname></title>
<para>
Reads <filename>.asx</filename> playlist files.
</para>
</section>
<section>
<title><varname>cue</varname></title>
<para>
Reads <filename>.cue</filename> files.
</para>
</section>
<section> <section>
<title><varname>embcue</varname></title> <title><varname>embcue</varname></title>
@@ -4175,6 +4256,15 @@ run</programlisting>
</para> </para>
</section> </section>
<section>
<title><varname>flac</varname></title>
<para>
Reads the <varname>cuesheet</varname> metablock from a FLAC
file.
</para>
</section>
<section> <section>
<title><varname>pls</varname></title> <title><varname>pls</varname></title>
@@ -4183,6 +4273,45 @@ run</programlisting>
</para> </para>
</section> </section>
<section>
<title><varname>rss</varname></title>
<para>
Reads music links from <filename>.rss</filename> files.
</para>
</section>
<section>
<title><varname>soundcloud</varname></title>
<para>
Download playlist from SoundCloud. It accepts URIs starting
with <filename>soundcloud://</filename>.
</para>
<informaltable>
<tgroup cols="2">
<thead>
<row>
<entry>Setting</entry>
<entry>Description</entry>
</row>
</thead>
<tbody>
<row>
<entry>
<varname>apikey</varname>
<parameter>KEY</parameter>
</entry>
<entry>
An API key to access the SoundCloud servers.
</entry>
</row>
</tbody>
</tgroup>
</informaltable>
</section>
<section> <section>
<title><varname>xspf</varname></title> <title><varname>xspf</varname></title>

View File

@@ -1,6 +1,6 @@
# Check if "struct ucred" is available. # Check if "struct ucred" is available.
# #
# Author: Max Kellermann <max@duempel.org> # Author: Max Kellermann <max.kellermann@gmail.com>
AC_DEFUN([STRUCT_UCRED],[ AC_DEFUN([STRUCT_UCRED],[
AC_MSG_CHECKING([for struct ucred]) AC_MSG_CHECKING([for struct ucred])

View File

@@ -19,8 +19,8 @@ libvorbis = AutotoolsProject(
) )
opus = AutotoolsProject( opus = AutotoolsProject(
'http://downloads.xiph.org/releases/opus/opus-1.1.3.tar.gz', 'http://downloads.xiph.org/releases/opus/opus-1.1.4.tar.gz',
'32bbb6b557fe1b6066adc0ae1f08b629', '9122b6b380081dd2665189f97bfd777f04f92dc3ab6698eea1dbb27ad59d8692',
'lib/libopus.a', 'lib/libopus.a',
['--disable-shared', '--enable-static'], ['--disable-shared', '--enable-static'],
) )

View File

@@ -18,6 +18,7 @@
*/ */
#include "AudioFormat.hxx" #include "AudioFormat.hxx"
#include "util/StringBuffer.hxx"
#include <assert.h> #include <assert.h>
#include <stdio.h> #include <stdio.h>
@@ -40,46 +41,24 @@ AudioFormat::ApplyMask(AudioFormat mask)
assert(IsValid()); assert(IsValid());
} }
const char * StringBuffer<24>
sample_format_to_string(SampleFormat format) ToString(const AudioFormat af)
{ {
switch (format) { StringBuffer<24> buffer;
case SampleFormat::UNDEFINED:
return "?";
case SampleFormat::S8: if (af.format == SampleFormat::DSD && af.sample_rate > 0 &&
return "8"; af.sample_rate % 44100 == 0) {
/* use shortcuts such as "dsd64" which implies the
case SampleFormat::S16: sample rate */
return "16"; snprintf(buffer.data(), buffer.capacity(), "dsd%u:%u",
af.sample_rate * 8 / 44100,
case SampleFormat::S24_P32: af.channels);
return "24"; return buffer;
case SampleFormat::S32:
return "32";
case SampleFormat::FLOAT:
return "f";
case SampleFormat::DSD:
return "dsd";
} }
/* unreachable */ snprintf(buffer.data(), buffer.capacity(), "%u:%s:%u",
assert(false);
gcc_unreachable();
}
const char *
audio_format_to_string(const AudioFormat af,
struct audio_format_string *s)
{
assert(s != nullptr);
snprintf(s->buffer, sizeof(s->buffer), "%u:%s:%u",
af.sample_rate, sample_format_to_string(af.format), af.sample_rate, sample_format_to_string(af.format),
af.channels); af.channels);
return s->buffer; return buffer;
} }

View File

@@ -20,47 +20,14 @@
#ifndef MPD_AUDIO_FORMAT_HXX #ifndef MPD_AUDIO_FORMAT_HXX
#define MPD_AUDIO_FORMAT_HXX #define MPD_AUDIO_FORMAT_HXX
#include "pcm/SampleFormat.hxx"
#include "Compiler.h" #include "Compiler.h"
#include <stdint.h>
#include <assert.h> #include <assert.h>
#include <stdint.h>
#include <stddef.h>
#if defined(WIN32) && GCC_CHECK_VERSION(4,6) template<size_t CAPACITY> class StringBuffer;
/* on WIN32, "FLOAT" is already defined, and this triggers -Wshadow */
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wshadow"
#endif
enum class SampleFormat : uint8_t {
UNDEFINED = 0,
S8,
S16,
/**
* Signed 24 bit integer samples, packed in 32 bit integers
* (the most significant byte is filled with the sign bit).
*/
S24_P32,
S32,
/**
* 32 bit floating point samples in the host's format. The
* range is -1.0f to +1.0f.
*/
FLOAT,
/**
* Direct Stream Digital. 1-bit samples; each frame has one
* byte (8 samples) per channel.
*/
DSD,
};
#if defined(WIN32) && GCC_CHECK_VERSION(4,6)
#pragma GCC diagnostic pop
#endif
static constexpr unsigned MAX_CHANNELS = 8; static constexpr unsigned MAX_CHANNELS = 8;
@@ -183,13 +150,6 @@ struct AudioFormat {
double GetTimeToSize() const; double GetTimeToSize() const;
}; };
/**
* Buffer for audio_format_string().
*/
struct audio_format_string {
char buffer[24];
};
/** /**
* Checks whether the sample rate is valid. * Checks whether the sample rate is valid.
* *
@@ -201,28 +161,6 @@ audio_valid_sample_rate(unsigned sample_rate)
return sample_rate > 0 && sample_rate < (1 << 30); return sample_rate > 0 && sample_rate < (1 << 30);
} }
/**
* Checks whether the sample format is valid.
*/
static inline bool
audio_valid_sample_format(SampleFormat format)
{
switch (format) {
case SampleFormat::S8:
case SampleFormat::S16:
case SampleFormat::S24_P32:
case SampleFormat::S32:
case SampleFormat::FLOAT:
case SampleFormat::DSD:
return true;
case SampleFormat::UNDEFINED:
break;
}
return false;
}
/** /**
* Checks whether the number of channels is valid. * Checks whether the number of channels is valid.
*/ */
@@ -258,34 +196,6 @@ AudioFormat::IsMaskValid() const
(channels == 0 || audio_valid_channel_count(channels)); (channels == 0 || audio_valid_channel_count(channels));
} }
gcc_const
static inline unsigned
sample_format_size(SampleFormat format)
{
switch (format) {
case SampleFormat::S8:
return 1;
case SampleFormat::S16:
return 2;
case SampleFormat::S24_P32:
case SampleFormat::S32:
case SampleFormat::FLOAT:
return 4;
case SampleFormat::DSD:
/* each frame has 8 samples per channel */
return 1;
case SampleFormat::UNDEFINED:
return 0;
}
assert(false);
gcc_unreachable();
}
inline unsigned inline unsigned
AudioFormat::GetSampleSize() const AudioFormat::GetSampleSize() const
{ {
@@ -304,28 +214,15 @@ AudioFormat::GetTimeToSize() const
return sample_rate * GetFrameSize(); return sample_rate * GetFrameSize();
} }
/**
* Renders a #SampleFormat enum into a string, e.g. for printing it
* in a log file.
*
* @param format a #SampleFormat enum value
* @return the string
*/
gcc_pure gcc_malloc
const char *
sample_format_to_string(SampleFormat format);
/** /**
* Renders the #AudioFormat object into a string, e.g. for printing * Renders the #AudioFormat object into a string, e.g. for printing
* it in a log file. * it in a log file.
* *
* @param af the #AudioFormat object * @param af the #AudioFormat object
* @param s a buffer to print into * @return the string buffer
* @return the string, or nullptr if the #AudioFormat object is invalid
*/ */
gcc_pure gcc_malloc gcc_const
const char * StringBuffer<24>
audio_format_to_string(AudioFormat af, ToString(AudioFormat af);
struct audio_format_string *s);
#endif #endif

View File

@@ -137,6 +137,26 @@ ParseAudioFormat(const char *src, bool mask)
AudioFormat dest; AudioFormat dest;
dest.Clear(); dest.Clear();
if (strncmp(src, "dsd", 3) == 0) {
/* allow format specifications such as "dsd64" which
implies the sample rate */
char *endptr;
auto dsd = strtoul(src + 3, &endptr, 10);
if (endptr > src + 3 && *endptr == ':' &&
dsd >= 32 && dsd <= 4096 && dsd % 2 == 0) {
dest.sample_rate = dsd * 44100 / 8;
dest.format = SampleFormat::DSD;
src = endptr + 1;
dest.channels = ParseChannelCount(src, mask, &src);
if (*src != 0)
throw FormatRuntimeError("Extra data after channel count: %s", src);
return dest;
}
}
/* parse sample rate */ /* parse sample rate */
dest.sample_rate = ParseSampleRate(src, mask, &src); dest.sample_rate = ParseSampleRate(src, mask, &src);

View File

@@ -107,7 +107,7 @@ static void version(void)
"\n" "\n"
"\n" "\n"
"Copyright (C) 2003-2007 Warren Dukes <warren.dukes@gmail.com>\n" "Copyright (C) 2003-2007 Warren Dukes <warren.dukes@gmail.com>\n"
"Copyright (C) 2008-2015 Max Kellermann <max@duempel.org>\n" "Copyright 2008-2017 Max Kellermann <max.kellermann@gmail.com>\n"
"This is free software; see the source for copying conditions. There is NO\n" "This is free software; see the source for copying conditions. There is NO\n"
"warranty; not even MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n" "warranty; not even MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"

View File

@@ -63,18 +63,18 @@ class DetachedSong {
Tag tag; Tag tag;
time_t mtime; time_t mtime = 0;
/** /**
* Start of this sub-song within the file. * Start of this sub-song within the file.
*/ */
SongTime start_time; SongTime start_time = SongTime::zero();
/** /**
* End of this sub-song within the file. * End of this sub-song within the file.
* Unused if zero. * Unused if zero.
*/ */
SongTime end_time; SongTime end_time = SongTime::zero();
explicit DetachedSong(const LightSong &other); explicit DetachedSong(const LightSong &other);
@@ -82,26 +82,18 @@ public:
explicit DetachedSong(const DetachedSong &) = default; explicit DetachedSong(const DetachedSong &) = default;
explicit DetachedSong(const char *_uri) explicit DetachedSong(const char *_uri)
:uri(_uri), :uri(_uri) {}
mtime(0),
start_time(SongTime::zero()), end_time(SongTime::zero()) {}
explicit DetachedSong(const std::string &_uri) explicit DetachedSong(const std::string &_uri)
:uri(_uri), :uri(_uri) {}
mtime(0),
start_time(SongTime::zero()), end_time(SongTime::zero()) {}
explicit DetachedSong(std::string &&_uri) explicit DetachedSong(std::string &&_uri)
:uri(std::move(_uri)), :uri(std::move(_uri)) {}
mtime(0),
start_time(SongTime::zero()), end_time(SongTime::zero()) {}
template<typename U> template<typename U>
DetachedSong(U &&_uri, Tag &&_tag) DetachedSong(U &&_uri, Tag &&_tag)
:uri(std::forward<U>(_uri)), :uri(std::forward<U>(_uri)),
tag(std::move(_tag)), tag(std::move(_tag)) {}
mtime(0),
start_time(SongTime::zero()), end_time(SongTime::zero()) {}
DetachedSong(DetachedSong &&) = default; DetachedSong(DetachedSong &&) = default;
@@ -146,7 +138,9 @@ public:
*/ */
gcc_pure gcc_pure
bool IsSame(const DetachedSong &other) const { bool IsSame(const DetachedSong &other) const {
return uri == other.uri; return uri == other.uri &&
start_time == other.start_time &&
end_time == other.end_time;
} }
gcc_pure gcc_nonnull_all gcc_pure gcc_nonnull_all

View File

@@ -203,7 +203,11 @@ glue_db_init_and_load(void)
"because the database does not need it"); "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)) if (!instance->database->IsPlugin(simple_db_plugin))
return true; return true;

View File

@@ -335,7 +335,7 @@ try {
const auto path_fs = spl_map_to_fs(utf8path); const auto path_fs = spl_map_to_fs(utf8path);
assert(!path_fs.IsNull()); assert(!path_fs.IsNull());
FileOutputStream fos(path_fs, FileOutputStream::Mode::APPEND_EXISTING); FileOutputStream fos(path_fs, FileOutputStream::Mode::APPEND_OR_CREATE);
if (fos.Tell() / (MPD_PATH_MAX + 1) >= playlist_max_length) if (fos.Tell() / (MPD_PATH_MAX + 1) >= playlist_max_length)
throw PlaylistError(PlaylistResult::TOO_LARGE, throw PlaylistError(PlaylistResult::TOO_LARGE,

View File

@@ -30,6 +30,7 @@
#include "Instance.hxx" #include "Instance.hxx"
#include "Idle.hxx" #include "Idle.hxx"
#include "AudioFormat.hxx" #include "AudioFormat.hxx"
#include "util/StringBuffer.hxx"
#include "util/ScopeExit.hxx" #include "util/ScopeExit.hxx"
#include "util/Exception.hxx" #include "util/Exception.hxx"
@@ -171,13 +172,9 @@ handle_status(Client &client, gcc_unused Request args, Response &r)
r.Format("duration: %1.3f\n", r.Format("duration: %1.3f\n",
player_status.total_time.ToDoubleS()); player_status.total_time.ToDoubleS());
if (player_status.audio_format.IsDefined()) { if (player_status.audio_format.IsDefined())
struct audio_format_string af_string;
r.Format(COMMAND_STATUS_AUDIO ": %s\n", r.Format(COMMAND_STATUS_AUDIO ": %s\n",
audio_format_to_string(player_status.audio_format, ToString(player_status.audio_format).c_str());
&af_string));
}
} }
#ifdef ENABLE_DATABASE #ifdef ENABLE_DATABASE

View File

@@ -62,7 +62,7 @@ Print(Response &r, TagType group, const TagCountMap &m)
} }
} }
static bool static void
stats_visitor_song(SearchStats &stats, const LightSong &song) stats_visitor_song(SearchStats &stats, const LightSong &song)
{ {
stats.n_songs++; stats.n_songs++;
@@ -70,8 +70,6 @@ stats_visitor_song(SearchStats &stats, const LightSong &song)
const auto duration = song.GetDuration(); const auto duration = song.GetDuration();
if (!duration.IsNegative()) if (!duration.IsNegative())
stats.total_duration += duration; stats.total_duration += duration;
return true;
} }
static bool static bool
@@ -94,7 +92,7 @@ CollectGroupCounts(TagCountMap &map, TagType group, const Tag &tag)
return found; return found;
} }
static bool static void
GroupCountVisitor(TagCountMap &map, TagType group, const LightSong &song) GroupCountVisitor(TagCountMap &map, TagType group, const LightSong &song)
{ {
assert(song.tag != nullptr); assert(song.tag != nullptr);
@@ -103,8 +101,6 @@ GroupCountVisitor(TagCountMap &map, TagType group, const LightSong &song)
if (!CollectGroupCounts(map, group, tag) && group == TAG_ALBUM_ARTIST) if (!CollectGroupCounts(map, group, tag) && group == TAG_ALBUM_ARTIST)
/* fall back to "Artist" if no "AlbumArtist" was found */ /* fall back to "Artist" if no "AlbumArtist" was found */
CollectGroupCounts(map, TAG_ARTIST, tag); CollectGroupCounts(map, TAG_ARTIST, tag);
return true;
} }
void void

View File

@@ -37,5 +37,10 @@ DatabaseGlobalInit(EventLoop &loop, DatabaseListener &listener,
throw FormatRuntimeError("No such database plugin: %s", throw FormatRuntimeError("No such database plugin: %s",
plugin_name); plugin_name);
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));
}
} }

View File

@@ -27,13 +27,12 @@
#include <functional> #include <functional>
static bool static void
AddSong(const Storage &storage, const char *playlist_path_utf8, AddSong(const Storage &storage, const char *playlist_path_utf8,
const LightSong &song) const LightSong &song)
{ {
spl_append_song(playlist_path_utf8, spl_append_song(playlist_path_utf8,
DatabaseDetachSong(storage, song)); DatabaseDetachSong(storage, song));
return true;
} }
void void

View File

@@ -49,16 +49,14 @@ PrintDirectoryURI(Response &r, bool base, const LightDirectory &directory)
ApplyBaseFlag(directory.GetPath(), base)); ApplyBaseFlag(directory.GetPath(), base));
} }
static bool static void
PrintDirectoryBrief(Response &r, bool base, const LightDirectory &directory) PrintDirectoryBrief(Response &r, bool base, const LightDirectory &directory)
{ {
if (!directory.IsRoot()) if (!directory.IsRoot())
PrintDirectoryURI(r, base, directory); PrintDirectoryURI(r, base, directory);
return true;
} }
static bool static void
PrintDirectoryFull(Response &r, bool base, const LightDirectory &directory) PrintDirectoryFull(Response &r, bool base, const LightDirectory &directory)
{ {
if (!directory.IsRoot()) { if (!directory.IsRoot()) {
@@ -67,8 +65,6 @@ PrintDirectoryFull(Response &r, bool base, const LightDirectory &directory)
if (directory.mtime > 0) if (directory.mtime > 0)
time_print(r, "Last-Modified", directory.mtime); time_print(r, "Last-Modified", directory.mtime);
} }
return true;
} }
static void static void
@@ -96,7 +92,7 @@ print_playlist_in_directory(Response &r, bool base,
directory->GetPath(), name_utf8); directory->GetPath(), name_utf8);
} }
static bool static void
PrintSongBrief(Response &r, Partition &partition, PrintSongBrief(Response &r, Partition &partition,
bool base, const LightSong &song) bool base, const LightSong &song)
{ {
@@ -106,11 +102,9 @@ PrintSongBrief(Response &r, Partition &partition,
/* this song file has an embedded CUE sheet */ /* this song file has an embedded CUE sheet */
print_playlist_in_directory(r, base, print_playlist_in_directory(r, base,
song.directory, song.uri); song.directory, song.uri);
return true;
} }
static bool static void
PrintSongFull(Response &r, Partition &partition, PrintSongFull(Response &r, Partition &partition,
bool base, const LightSong &song) bool base, const LightSong &song)
{ {
@@ -120,21 +114,18 @@ PrintSongFull(Response &r, Partition &partition,
/* this song file has an embedded CUE sheet */ /* this song file has an embedded CUE sheet */
print_playlist_in_directory(r, base, print_playlist_in_directory(r, base,
song.directory, song.uri); song.directory, song.uri);
return true;
} }
static bool static void
PrintPlaylistBrief(Response &r, bool base, PrintPlaylistBrief(Response &r, bool base,
const PlaylistInfo &playlist, const PlaylistInfo &playlist,
const LightDirectory &directory) const LightDirectory &directory)
{ {
print_playlist_in_directory(r, base, print_playlist_in_directory(r, base,
&directory, playlist.name.c_str()); &directory, playlist.name.c_str());
return true;
} }
static bool static void
PrintPlaylistFull(Response &r, bool base, PrintPlaylistFull(Response &r, bool base,
const PlaylistInfo &playlist, const PlaylistInfo &playlist,
const LightDirectory &directory) const LightDirectory &directory)
@@ -144,8 +135,6 @@ PrintPlaylistFull(Response &r, bool base,
if (playlist.mtime > 0) if (playlist.mtime > 0)
time_print(r, "Last-Modified", playlist.mtime); time_print(r, "Last-Modified", playlist.mtime);
return true;
} }
void void
@@ -191,15 +180,13 @@ db_selection_print(Response &r, Partition &partition,
0, std::numeric_limits<int>::max()); 0, std::numeric_limits<int>::max());
} }
static bool static void
PrintSongURIVisitor(Response &r, Partition &partition, const LightSong &song) PrintSongURIVisitor(Response &r, Partition &partition, const LightSong &song)
{ {
song_print_uri(r, partition, song); song_print_uri(r, partition, song);
return true;
} }
static bool static void
PrintUniqueTag(Response &r, TagType tag_type, PrintUniqueTag(Response &r, TagType tag_type,
const Tag &tag) const Tag &tag)
{ {
@@ -211,8 +198,6 @@ PrintUniqueTag(Response &r, TagType tag_type,
if (item.type != tag_type) if (item.type != tag_type)
r.Format("%s: %s\n", r.Format("%s: %s\n",
tag_item_names[item.type], item.value); tag_item_names[item.type], item.value);
return true;
} }
void void

View File

@@ -27,14 +27,13 @@
#include <functional> #include <functional>
static bool static void
AddToQueue(Partition &partition, const LightSong &song) AddToQueue(Partition &partition, const LightSong &song)
{ {
const Storage &storage = *partition.instance.storage; const Storage &storage = *partition.instance.storage;
partition.playlist.AppendSong(partition.pc, partition.playlist.AppendSong(partition.pc,
DatabaseDetachSong(storage, DatabaseDetachSong(storage,
song)); song));
return true;
} }
void void

View File

@@ -67,15 +67,13 @@ StatsVisitTag(DatabaseStats &stats, StringSet &artists, StringSet &albums,
} }
} }
static bool static void
StatsVisitSong(DatabaseStats &stats, StringSet &artists, StringSet &albums, StatsVisitSong(DatabaseStats &stats, StringSet &artists, StringSet &albums,
const LightSong &song) const LightSong &song)
{ {
++stats.song_count; ++stats.song_count;
StatsVisitTag(stats, artists, albums, *song.tag); StatsVisitTag(stats, artists, albums, *song.tag);
return true;
} }
DatabaseStats DatabaseStats

View File

@@ -34,6 +34,7 @@
#include "tag/TagBuilder.hxx" #include "tag/TagBuilder.hxx"
#include "tag/Tag.hxx" #include "tag/Tag.hxx"
#include "util/ScopeExit.hxx" #include "util/ScopeExit.hxx"
#include "util/RuntimeError.hxx"
#include "protocol/Ack.hxx" #include "protocol/Ack.hxx"
#include "event/SocketMonitor.hxx" #include "event/SocketMonitor.hxx"
#include "event/IdleMonitor.hxx" #include "event/IdleMonitor.hxx"
@@ -46,7 +47,7 @@
#include <string> #include <string>
#include <list> #include <list>
class LibmpdclientError final : std::runtime_error { class LibmpdclientError final : public std::runtime_error {
enum mpd_error code; enum mpd_error code;
public: public:
@@ -108,8 +109,8 @@ public:
static Database *Create(EventLoop &loop, DatabaseListener &listener, static Database *Create(EventLoop &loop, DatabaseListener &listener,
const ConfigBlock &block); const ConfigBlock &block);
virtual void Open() override; void Open() override;
virtual void Close() override; void Close() override;
const LightSong *GetSong(const char *uri_utf8) const override; const LightSong *GetSong(const char *uri_utf8) const override;
void ReturnSong(const LightSong *song) const override; void ReturnSong(const LightSong *song) const override;
@@ -126,7 +127,7 @@ public:
unsigned Update(const char *uri_utf8, bool discard) override; unsigned Update(const char *uri_utf8, bool discard) override;
virtual time_t GetUpdateStamp() const override { time_t GetUpdateStamp() const override {
return update_stamp; return update_stamp;
} }
@@ -138,10 +139,10 @@ private:
void Disconnect(); void Disconnect();
/* virtual methods from SocketMonitor */ /* virtual methods from SocketMonitor */
virtual bool OnSocketReady(unsigned flags) override; bool OnSocketReady(unsigned flags) override;
/* virtual methods from IdleMonitor */ /* virtual methods from IdleMonitor */
virtual void OnIdle() override; void OnIdle() override;
}; };
static constexpr struct { static constexpr struct {
@@ -345,9 +346,15 @@ ProxyDatabase::Create(EventLoop &loop, DatabaseListener &listener,
void void
ProxyDatabase::Open() ProxyDatabase::Open()
{ {
Connect();
update_stamp = 0; update_stamp = 0;
try {
Connect();
} catch (const std::runtime_error &error) {
/* this error is non-fatal, because this plugin will
attempt to reconnect again automatically */
LogError(error);
}
} }
void void
@@ -371,7 +378,10 @@ ProxyDatabase::Connect()
mpd_connection_free(connection); mpd_connection_free(connection);
connection = nullptr; connection = nullptr;
throw; std::throw_with_nested(host.empty()
? std::runtime_error("Failed to connect to remote MPD")
: FormatRuntimeError("Failed to connect to remote MPD '%s'",
host.c_str()));
} }
#if LIBMPDCLIENT_CHECK_VERSION(2, 10, 0) #if LIBMPDCLIENT_CHECK_VERSION(2, 10, 0)

View File

@@ -108,8 +108,8 @@ public:
bool Unmount(const char *uri); bool Unmount(const char *uri);
/* virtual methods from class Database */ /* virtual methods from class Database */
virtual void Open() override; void Open() override;
virtual void Close() override; void Close() override;
const LightSong *GetSong(const char *uri_utf8) const override; const LightSong *GetSong(const char *uri_utf8) const override;
void ReturnSong(const LightSong *song) const override; void ReturnSong(const LightSong *song) const override;
@@ -125,7 +125,7 @@ public:
DatabaseStats GetStats(const DatabaseSelection &selection) const override; DatabaseStats GetStats(const DatabaseSelection &selection) const override;
virtual time_t GetUpdateStamp() const override { time_t GetUpdateStamp() const override {
return mtime; return mtime;
} }

View File

@@ -180,7 +180,7 @@ UpnpDatabase::ReturnSong(const LightSong *_song) const
const LightSong * const LightSong *
UpnpDatabase::GetSong(const char *uri) const UpnpDatabase::GetSong(const char *uri) const
{ {
auto vpath = stringToTokens(uri, "/", true); auto vpath = stringToTokens(uri, '/');
if (vpath.size() < 2) if (vpath.size() < 2)
throw DatabaseError(DatabaseErrorCode::NOT_FOUND, throw DatabaseError(DatabaseErrorCode::NOT_FOUND,
"No such song"); "No such song");
@@ -577,7 +577,7 @@ UpnpDatabase::Visit(const DatabaseSelection &selection,
VisitSong visit_song, VisitSong visit_song,
VisitPlaylist visit_playlist) const VisitPlaylist visit_playlist) const
{ {
auto vpath = stringToTokens(selection.uri, "/", true); auto vpath = stringToTokens(selection.uri, '/');
if (vpath.empty()) { if (vpath.empty()) {
for (const auto &server : discovery->GetDirectories()) { for (const auto &server : discovery->GetDirectories()) {
if (visit_directory) { if (visit_directory) {

View File

@@ -32,6 +32,7 @@
#include "Log.hxx" #include "Log.hxx"
#include "input/InputStream.hxx" #include "input/InputStream.hxx"
#include "util/ConstBuffer.hxx" #include "util/ConstBuffer.hxx"
#include "util/StringBuffer.hxx"
#include <assert.h> #include <assert.h>
#include <string.h> #include <string.h>
@@ -246,15 +247,13 @@ void
DecoderBridge::Ready(const AudioFormat audio_format, DecoderBridge::Ready(const AudioFormat audio_format,
bool seekable, SignedSongTime duration) bool seekable, SignedSongTime duration)
{ {
struct audio_format_string af_string;
assert(convert == nullptr); assert(convert == nullptr);
assert(stream_tag == nullptr); assert(stream_tag == nullptr);
assert(decoder_tag == nullptr); assert(decoder_tag == nullptr);
assert(!seeking); assert(!seeking);
FormatDebug(decoder_domain, "audio_format=%s, seekable=%s", FormatDebug(decoder_domain, "audio_format=%s, seekable=%s",
audio_format_to_string(audio_format, &af_string), ToString(audio_format).c_str(),
seekable ? "true" : "false"); seekable ? "true" : "false");
{ {
@@ -264,8 +263,7 @@ DecoderBridge::Ready(const AudioFormat audio_format,
if (dc.in_audio_format != dc.out_audio_format) { if (dc.in_audio_format != dc.out_audio_format) {
FormatDebug(decoder_domain, "converting to %s", FormatDebug(decoder_domain, "converting to %s",
audio_format_to_string(dc.out_audio_format, ToString(dc.out_audio_format).c_str());
&af_string));
convert = new PcmConvert(); convert = new PcmConvert();
@@ -317,6 +315,9 @@ DecoderBridge::CommandFinished()
dc.pipe->Clear(*dc.buffer); dc.pipe->Clear(*dc.buffer);
if (convert != nullptr)
convert->Reset();
timestamp = dc.seek_time.ToDoubleS(); timestamp = dc.seek_time.ToDoubleS();
} }

View File

@@ -56,6 +56,11 @@ extern "C" {
#include <assert.h> #include <assert.h>
#include <string.h> #include <string.h>
/**
* Muxer options to be passed to avformat_open_input().
*/
static AVDictionary *avformat_options = nullptr;
static AVFormatContext * static AVFormatContext *
FfmpegOpenInput(AVIOContext *pb, FfmpegOpenInput(AVIOContext *pb,
const char *filename, const char *filename,
@@ -67,7 +72,11 @@ FfmpegOpenInput(AVIOContext *pb,
context->pb = pb; context->pb = pb;
int err = avformat_open_input(&context, filename, fmt, nullptr); AVDictionary *options = nullptr;
AtScopeExit(&options) { av_dict_free(&options); };
av_dict_copy(&options, avformat_options, 0);
int err = avformat_open_input(&context, filename, fmt, &options);
if (err < 0) if (err < 0)
throw MakeFfmpegError(err, "avformat_open_input() failed"); throw MakeFfmpegError(err, "avformat_open_input() failed");
@@ -75,12 +84,30 @@ FfmpegOpenInput(AVIOContext *pb,
} }
static bool static bool
ffmpeg_init(gcc_unused const ConfigBlock &block) ffmpeg_init(const ConfigBlock &block)
{ {
FfmpegInit(); FfmpegInit();
static constexpr const char *option_names[] = {
"probesize",
"analyzeduration",
};
for (const char *name : option_names) {
const char *value = block.GetBlockValue(name);
if (value != nullptr)
av_dict_set(&avformat_options, name, value, 0);
}
return true; return true;
} }
static void
ffmpeg_finish()
{
av_dict_free(&avformat_options);
}
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 25, 0) /* FFmpeg 3.1 */ #if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 25, 0) /* FFmpeg 3.1 */
gcc_pure gcc_pure
@@ -967,7 +994,7 @@ static const char *const ffmpeg_mime_types[] = {
const struct DecoderPlugin ffmpeg_decoder_plugin = { const struct DecoderPlugin ffmpeg_decoder_plugin = {
"ffmpeg", "ffmpeg",
ffmpeg_init, ffmpeg_init,
nullptr, ffmpeg_finish,
ffmpeg_decode, ffmpeg_decode,
nullptr, nullptr,
nullptr, nullptr,

View File

@@ -52,6 +52,12 @@ public:
return out_audio_format; return out_audio_format;
} }
/**
* Reset the filter's state, e.g. drop/flush buffers.
*/
virtual void Reset() {
}
/** /**
* Filters a block of PCM data. * Filters a block of PCM data.
* *
@@ -60,7 +66,7 @@ public:
* @param src the input buffer * @param src the input buffer
* @return the destination buffer on success (will be * @return the destination buffer on success (will be
* invalidated by deleting this object or the next FilterPCM() * invalidated by deleting this object or the next FilterPCM()
* call) * or Reset() call)
*/ */
virtual ConstBuffer<void> FilterPCM(ConstBuffer<void> src) = 0; virtual ConstBuffer<void> FilterPCM(ConstBuffer<void> src) = 0;
}; };

View File

@@ -48,6 +48,13 @@ public:
:Filter(_filter->GetOutAudioFormat()), :Filter(_filter->GetOutAudioFormat()),
filter(std::move(_filter)), convert(std::move(_convert)) {} filter(std::move(_filter)), convert(std::move(_convert)) {}
void Reset() override {
filter->Reset();
if (convert)
convert->Reset();
}
ConstBuffer<void> FilterPCM(ConstBuffer<void> src) override; ConstBuffer<void> FilterPCM(ConstBuffer<void> src) override;
}; };

View File

@@ -24,6 +24,7 @@
#include "filter/FilterRegistry.hxx" #include "filter/FilterRegistry.hxx"
#include "AudioFormat.hxx" #include "AudioFormat.hxx"
#include "util/ConstBuffer.hxx" #include "util/ConstBuffer.hxx"
#include "util/StringBuffer.hxx"
#include "util/RuntimeError.hxx" #include "util/RuntimeError.hxx"
#include <memory> #include <memory>
@@ -61,6 +62,7 @@ public:
} }
/* virtual methods from class Filter */ /* virtual methods from class Filter */
void Reset() override;
ConstBuffer<void> FilterPCM(ConstBuffer<void> src) override; ConstBuffer<void> FilterPCM(ConstBuffer<void> src) override;
}; };
@@ -107,10 +109,9 @@ PreparedChainFilter::Child::Open(const AudioFormat &prev_audio_format)
if (conv_audio_format != prev_audio_format) { if (conv_audio_format != prev_audio_format) {
delete new_filter; delete new_filter;
struct audio_format_string s;
throw FormatRuntimeError("Audio format not supported by filter '%s': %s", throw FormatRuntimeError("Audio format not supported by filter '%s': %s",
name, name,
audio_format_to_string(prev_audio_format, &s)); ToString(prev_audio_format).c_str());
} }
return new_filter; return new_filter;
@@ -130,6 +131,13 @@ PreparedChainFilter::Open(AudioFormat &in_audio_format)
return chain.release(); return chain.release();
} }
void
ChainFilter::Reset()
{
for (auto &child : children)
child.filter->Reset();
}
ConstBuffer<void> ConstBuffer<void>
ChainFilter::FilterPCM(ConstBuffer<void> src) ChainFilter::FilterPCM(ConstBuffer<void> src)
{ {

View File

@@ -52,6 +52,10 @@ public:
void Set(const AudioFormat &_out_audio_format); void Set(const AudioFormat &_out_audio_format);
void Reset() override {
state.Reset();
}
ConstBuffer<void> FilterPCM(ConstBuffer<void> src) override; ConstBuffer<void> FilterPCM(ConstBuffer<void> src) override;
}; };

View File

@@ -28,11 +28,11 @@
#include "AlsaInputPlugin.hxx" #include "AlsaInputPlugin.hxx"
#include "../InputPlugin.hxx" #include "../InputPlugin.hxx"
#include "../AsyncInputStream.hxx" #include "../AsyncInputStream.hxx"
#include "event/Call.hxx"
#include "util/Domain.hxx" #include "util/Domain.hxx"
#include "util/RuntimeError.hxx" #include "util/RuntimeError.hxx"
#include "util/StringCompare.hxx" #include "util/StringCompare.hxx"
#include "util/ReusableArray.hxx" #include "util/ReusableArray.hxx"
#include "util/ScopeExit.hxx"
#include "Log.hxx" #include "Log.hxx"
#include "event/MultiSocketMonitor.hxx" #include "event/MultiSocketMonitor.hxx"
@@ -56,31 +56,30 @@ static constexpr unsigned int default_rate = 44100; // cd quality
static constexpr size_t ALSA_MAX_BUFFERED = default_rate * default_channels * 2; static constexpr size_t ALSA_MAX_BUFFERED = default_rate * default_channels * 2;
static constexpr size_t ALSA_RESUME_AT = ALSA_MAX_BUFFERED / 2; static constexpr size_t ALSA_RESUME_AT = ALSA_MAX_BUFFERED / 2;
/**
* This value should be the same as the read buffer size defined in
* PcmDecoderPlugin.cxx:pcm_stream_decode().
* We use it to calculate how many audio frames to buffer in the alsa driver
* before reading from the device. snd_pcm_readi() blocks until that many
* frames are ready.
*/
static constexpr size_t read_buffer_size = 4096;
class AlsaInputStream final class AlsaInputStream final
: public AsyncInputStream, : public AsyncInputStream,
MultiSocketMonitor, DeferredMonitor { MultiSocketMonitor, DeferredMonitor {
snd_pcm_t *capture_handle;
size_t frame_size; /**
* The configured name of the ALSA device.
*/
const std::string device;
snd_pcm_t *const capture_handle;
const size_t frame_size;
ReusableArray<pollfd> pfd_buffer; ReusableArray<pollfd> pfd_buffer;
public: public:
AlsaInputStream(EventLoop &loop, AlsaInputStream(EventLoop &loop,
const char *_uri, Mutex &_mutex, Cond &_cond, const char *_uri, Mutex &_mutex, Cond &_cond,
const char *_device,
snd_pcm_t *_handle, int _frame_size) snd_pcm_t *_handle, int _frame_size)
:AsyncInputStream(_uri, _mutex, _cond, :AsyncInputStream(_uri, _mutex, _cond,
ALSA_MAX_BUFFERED, ALSA_RESUME_AT), ALSA_MAX_BUFFERED, ALSA_RESUME_AT),
MultiSocketMonitor(loop), MultiSocketMonitor(loop),
DeferredMonitor(loop), DeferredMonitor(loop),
device(_device),
capture_handle(_handle), capture_handle(_handle),
frame_size(_frame_size) frame_size(_frame_size)
{ {
@@ -99,6 +98,14 @@ public:
} }
~AlsaInputStream() { ~AlsaInputStream() {
/* ClearSocketList must be called from within the
IOThread; if we don't do it manually here, the
~MultiSocketMonitor() will do it in the current
thread */
BlockingCall(MultiSocketMonitor::GetEventLoop(), [this](){
ClearSocketList();
});
snd_pcm_close(capture_handle); snd_pcm_close(capture_handle);
} }
@@ -162,7 +169,7 @@ AlsaInputStream::Create(const char *uri, Mutex &mutex, Cond &cond)
int frame_size = snd_pcm_format_width(format) / 8 * channels; int frame_size = snd_pcm_format_width(format) / 8 * channels;
return new AlsaInputStream(io_thread_get(), return new AlsaInputStream(io_thread_get(),
uri, mutex, cond, uri, mutex, cond,
handle, frame_size); device, handle, frame_size);
} }
std::chrono::steady_clock::duration std::chrono::steady_clock::duration
@@ -205,6 +212,9 @@ AlsaInputStream::DispatchSockets()
snd_pcm_sframes_t n_frames; snd_pcm_sframes_t n_frames;
while ((n_frames = snd_pcm_readi(capture_handle, while ((n_frames = snd_pcm_readi(capture_handle,
w.data, w_frames)) < 0) { w.data, w_frames)) < 0) {
if (n_frames == -EAGAIN)
return;
if (Recover(n_frames) < 0) { if (Recover(n_frames) < 0) {
postponed_exception = std::make_exception_ptr(std::runtime_error("PCM error - stream aborted")); postponed_exception = std::make_exception_ptr(std::runtime_error("PCM error - stream aborted"));
cond.broadcast(); cond.broadcast();
@@ -221,20 +231,51 @@ AlsaInputStream::Recover(int err)
{ {
switch(err) { switch(err) {
case -EPIPE: case -EPIPE:
LogDebug(alsa_input_domain, "Buffer Overrun"); FormatDebug(alsa_input_domain,
// drop through "Overrun on ALSA capture device \"%s\"",
device.c_str());
break;
case -ESTRPIPE:
FormatDebug(alsa_input_domain,
"ALSA capture device \"%s\" was suspended",
device.c_str());
break;
}
switch (snd_pcm_state(capture_handle)) {
case SND_PCM_STATE_PAUSED:
err = snd_pcm_pause(capture_handle, /* disable */ 0);
break;
case SND_PCM_STATE_SUSPENDED:
err = snd_pcm_resume(capture_handle);
if (err == -EAGAIN)
return 0;
/* fall-through to snd_pcm_prepare: */
#if GCC_CHECK_VERSION(7,0) #if GCC_CHECK_VERSION(7,0)
[[fallthrough]]; [[fallthrough]];
#endif #endif
case SND_PCM_STATE_OPEN:
case -ESTRPIPE: case SND_PCM_STATE_SETUP:
case -EINTR: case SND_PCM_STATE_XRUN:
err = snd_pcm_recover(capture_handle, err, 1); err = snd_pcm_prepare(capture_handle);
if (err == 0)
err = snd_pcm_start(capture_handle);
break;
case SND_PCM_STATE_DISCONNECTED:
break;
case SND_PCM_STATE_PREPARED:
case SND_PCM_STATE_RUNNING:
case SND_PCM_STATE_DRAINING:
/* this is no error, so just keep running */
err = 0;
break; break;
default:
// something broken somewhere, give up
err = -1;
} }
return err; return err;
} }
@@ -245,13 +286,7 @@ ConfigureCapture(snd_pcm_t *capture_handle,
int err; int err;
snd_pcm_hw_params_t *hw_params; snd_pcm_hw_params_t *hw_params;
if ((err = snd_pcm_hw_params_malloc(&hw_params)) < 0) snd_pcm_hw_params_alloca(&hw_params);
throw FormatRuntimeError("Cannot allocate hardware parameter structure (%s)",
snd_strerror(err));
AtScopeExit(hw_params) {
snd_pcm_hw_params_free(hw_params);
};
if ((err = snd_pcm_hw_params_any(capture_handle, hw_params)) < 0) if ((err = snd_pcm_hw_params_any(capture_handle, hw_params)) < 0)
throw FormatRuntimeError("Cannot initialize hardware parameter structure (%s)", throw FormatRuntimeError("Cannot initialize hardware parameter structure (%s)",
@@ -273,37 +308,68 @@ ConfigureCapture(snd_pcm_t *capture_handle,
throw FormatRuntimeError("Cannot set sample rate (%s)", throw FormatRuntimeError("Cannot set sample rate (%s)",
snd_strerror(err)); snd_strerror(err));
/* period needs to be big enough so that poll() doesn't fire too often, snd_pcm_uframes_t buffer_size_min, buffer_size_max;
* but small enough that buffer overruns don't occur if Read() is not snd_pcm_hw_params_get_buffer_size_min(hw_params, &buffer_size_min);
* invoked often enough. snd_pcm_hw_params_get_buffer_size_max(hw_params, &buffer_size_max);
* the calculation here is empirical; however all measurements were unsigned buffer_time_min, buffer_time_max;
* done using 44100:16:2. When we extend this plugin to support snd_pcm_hw_params_get_buffer_time_min(hw_params, &buffer_time_min, 0);
* other audio formats then this may need to be revisited */ snd_pcm_hw_params_get_buffer_time_max(hw_params, &buffer_time_max, 0);
snd_pcm_uframes_t period = read_buffer_size * 2; FormatDebug(alsa_input_domain, "buffer: size=%u..%u time=%u..%u",
int direction = -1; (unsigned)buffer_size_min, (unsigned)buffer_size_max,
if ((err = snd_pcm_hw_params_set_period_size_near(capture_handle, hw_params, buffer_time_min, buffer_time_max);
&period, &direction)) < 0)
throw FormatRuntimeError("Cannot set period size (%s)", snd_pcm_uframes_t period_size_min, period_size_max;
snd_strerror(err)); 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) if ((err = snd_pcm_hw_params(capture_handle, hw_params)) < 0)
throw FormatRuntimeError("Cannot set parameters (%s)", throw FormatRuntimeError("Cannot set parameters (%s)",
snd_strerror(err)); snd_strerror(err));
snd_pcm_uframes_t alsa_buffer_size;
err = snd_pcm_hw_params_get_buffer_size(hw_params, &alsa_buffer_size);
if (err < 0)
throw FormatRuntimeError("snd_pcm_hw_params_get_buffer_size() failed: %s",
snd_strerror(-err));
snd_pcm_uframes_t alsa_period_size;
err = snd_pcm_hw_params_get_period_size(hw_params, &alsa_period_size,
nullptr);
if (err < 0)
throw FormatRuntimeError("snd_pcm_hw_params_get_period_size() failed: %s",
snd_strerror(-err));
FormatDebug(alsa_input_domain, "buffer_size=%u period_size=%u",
(unsigned)alsa_buffer_size, (unsigned)alsa_period_size);
snd_pcm_sw_params_t *sw_params; snd_pcm_sw_params_t *sw_params;
snd_pcm_sw_params_alloca(&sw_params);
snd_pcm_sw_params_malloc(&sw_params);
snd_pcm_sw_params_current(capture_handle, sw_params); snd_pcm_sw_params_current(capture_handle, sw_params);
AtScopeExit(sw_params) {
snd_pcm_sw_params_free(sw_params);
};
if ((err = snd_pcm_sw_params_set_start_threshold(capture_handle, sw_params,
period)) < 0)
throw FormatRuntimeError("unable to set start threshold (%s)",
snd_strerror(err));
if ((err = snd_pcm_sw_params(capture_handle, sw_params)) < 0) if ((err = snd_pcm_sw_params(capture_handle, sw_params)) < 0)
throw FormatRuntimeError("unable to install sw params (%s)", throw FormatRuntimeError("unable to install sw params (%s)",
snd_strerror(err)); snd_strerror(err));
@@ -316,7 +382,8 @@ AlsaInputStream::OpenDevice(const char *device,
snd_pcm_t *capture_handle; snd_pcm_t *capture_handle;
int err; int err;
if ((err = snd_pcm_open(&capture_handle, device, if ((err = snd_pcm_open(&capture_handle, device,
SND_PCM_STREAM_CAPTURE, 0)) < 0) SND_PCM_STREAM_CAPTURE,
SND_PCM_NONBLOCK)) < 0)
throw FormatRuntimeError("Failed to open device: %s (%s)", throw FormatRuntimeError("Failed to open device: %s (%s)",
device, snd_strerror(err)); device, snd_strerror(err));

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2010-2011 Max Kellermann <max@duempel.org> * Copyright (C) 2010-2011 Max Kellermann <max.kellermann@gmail.com>
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions * modification, are permitted provided that the following conditions

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2010-2012 Max Kellermann <max@duempel.org> * Copyright (C) 2010-2012 Max Kellermann <max.kellermann@gmail.com>
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions * modification, are permitted provided that the following conditions

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2010-2014 Max Kellermann <max@duempel.org> * Copyright (C) 2010-2014 Max Kellermann <max.kellermann@gmail.com>
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions * modification, are permitted provided that the following conditions

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2010-2014 Max Kellermann <max@duempel.org> * Copyright (C) 2010-2014 Max Kellermann <max.kellermann@gmail.com>
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions * modification, are permitted provided that the following conditions

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2010-2011 Max Kellermann <max@duempel.org> * Copyright (C) 2010-2011 Max Kellermann <max.kellermann@gmail.com>
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions * modification, are permitted provided that the following conditions

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2010-2011 Max Kellermann <max@duempel.org> * Copyright (C) 2010-2011 Max Kellermann <max.kellermann@gmail.com>
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions * modification, are permitted provided that the following conditions

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2010-2011 Max Kellermann <max@duempel.org> * Copyright (C) 2010-2011 Max Kellermann <max.kellermann@gmail.com>
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions * modification, are permitted provided that the following conditions

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2010-2011 Max Kellermann <max@duempel.org> * Copyright (C) 2010-2011 Max Kellermann <max.kellermann@gmail.com>
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions * modification, are permitted provided that the following conditions

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2010-2011 Max Kellermann <max@duempel.org> * Copyright (C) 2010-2011 Max Kellermann <max.kellermann@gmail.com>
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions * modification, are permitted provided that the following conditions

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2010-2011 Max Kellermann <max@duempel.org> * Copyright (C) 2010-2011 Max Kellermann <max.kellermann@gmail.com>
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions * modification, are permitted provided that the following conditions

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2016 Max Kellermann <max@duempel.org> * Copyright (C) 2016 Max Kellermann <max.kellermann@gmail.com>
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions * modification, are permitted provided that the following conditions

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2008-2016 Max Kellermann <max@duempel.org> * Copyright (C) 2008-2016 Max Kellermann <max.kellermann@gmail.com>
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions * modification, are permitted provided that the following conditions

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2008-2016 Max Kellermann <max@duempel.org> * Copyright (C) 2008-2016 Max Kellermann <max.kellermann@gmail.com>
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions * modification, are permitted provided that the following conditions

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2008-2017 Max Kellermann <max@duempel.org> * Copyright (C) 2008-2017 Max Kellermann <max.kellermann@gmail.com>
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions * modification, are permitted provided that the following conditions

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2016 Max Kellermann <max@duempel.org> * Copyright (C) 2016 Max Kellermann <max.kellermann@gmail.com>
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions * modification, are permitted provided that the following conditions

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2008-2017 Max Kellermann <max@duempel.org> * Copyright (C) 2008-2017 Max Kellermann <max.kellermann@gmail.com>
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions * modification, are permitted provided that the following conditions

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2008-2017 Max Kellermann <max@duempel.org> * Copyright (C) 2008-2017 Max Kellermann <max.kellermann@gmail.com>
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions * modification, are permitted provided that the following conditions

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2008-2017 Max Kellermann <max@duempel.org> * Copyright (C) 2008-2017 Max Kellermann <max.kellermann@gmail.com>
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions * modification, are permitted provided that the following conditions

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2017 Max Kellermann <max@duempel.org> * Copyright (C) 2017 Max Kellermann <max.kellermann@gmail.com>
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions * modification, are permitted provided that the following conditions

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2017 Max Kellermann <max@duempel.org> * Copyright (C) 2017 Max Kellermann <max.kellermann@gmail.com>
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions * modification, are permitted provided that the following conditions

View File

@@ -24,9 +24,21 @@
#include <exception> #include <exception>
/**
* Callbacks for an asynchronous libnfs operation.
*
* Note that no callback is invoked for cancelled operations.
*/
class NfsCallback { class NfsCallback {
public: public:
/**
* The operation completed successfully.
*/
virtual void OnNfsCallback(unsigned status, void *data) = 0; virtual void OnNfsCallback(unsigned status, void *data) = 0;
/**
* An error has occurred.
*/
virtual void OnNfsError(std::exception_ptr &&e) = 0; virtual void OnNfsError(std::exception_ptr &&e) = 0;
}; };

View File

@@ -396,6 +396,17 @@ NfsConnection::ScheduleSocket()
assert(GetEventLoop().IsInside()); assert(GetEventLoop().IsInside());
assert(context != nullptr); assert(context != nullptr);
const int which_events = nfs_which_events(context);
if (which_events == POLLOUT && SocketMonitor::IsDefined())
/* kludge: if libnfs asks only for POLLOUT, it means
that it is currently waiting for the connect() to
finish - rpc_reconnect_requeue() may have been
called from inside nfs_service(); we must now
unregister the old socket and register the new one
instead */
SocketMonitor::Steal();
if (!SocketMonitor::IsDefined()) { if (!SocketMonitor::IsDefined()) {
int _fd = nfs_get_fd(context); int _fd = nfs_get_fd(context);
if (_fd < 0) if (_fd < 0)
@@ -405,7 +416,8 @@ NfsConnection::ScheduleSocket()
SocketMonitor::Open(_fd); SocketMonitor::Open(_fd);
} }
SocketMonitor::Schedule(libnfs_to_events(nfs_which_events(context))); SocketMonitor::Schedule(libnfs_to_events(which_events)
| SocketMonitor::HANGUP);
} }
inline int inline int
@@ -442,10 +454,14 @@ NfsConnection::OnSocketReady(unsigned flags)
bool closed = false; bool closed = false;
const bool was_mounted = mount_finished; const bool was_mounted = mount_finished;
if (!mount_finished) if (!mount_finished || (flags & SocketMonitor::HANGUP) != 0)
/* until the mount is finished, the NFS client may use /* until the mount is finished, the NFS client may use
various sockets, therefore we unregister and various sockets, therefore we unregister and
re-register it each time */ re-register it each time */
/* also re-register the socket if we got a HANGUP,
which is a sure sign that libnfs will close the
socket, which can lead to a race condition if
epoll_ctl() is called later */
SocketMonitor::Steal(); SocketMonitor::Steal();
const int result = Service(flags); const int result = Service(flags);

View File

@@ -35,6 +35,14 @@
struct nfsfh; struct nfsfh;
class NfsConnection; class NfsConnection;
/**
* A helper class which helps with reading from a file. It obtains a
* connection lease (#NfsLease), opens the given file, "stats" the
* file, and finally allos you to read its contents.
*
* To get started, derive your class from it and implement the pure
* virtual methods, construct an instance, and call Open().
*/
class NfsFileReader : NfsLease, NfsCallback, DeferredMonitor { class NfsFileReader : NfsLease, NfsCallback, DeferredMonitor {
enum class State { enum class State {
INITIAL, INITIAL,
@@ -63,14 +71,30 @@ public:
void DeferClose(); void DeferClose();
/** /**
* Open the file. This method is thread-safe.
*
* Throws std::runtime_error on error. * Throws std::runtime_error on error.
*/ */
void Open(const char *uri); void Open(const char *uri);
/** /**
* Attempt to read from the file. This may only be done after
* OnNfsFileOpen() has been called. Only one read operation
* may be performed at a time.
*
* This method is not thread-safe and must be called from
* within the I/O thread.
*
* Throws std::runtime_error on error. * Throws std::runtime_error on error.
*/ */
void Read(uint64_t offset, size_t size); void Read(uint64_t offset, size_t size);
/**
* Cancel the most recent Read() call.
*
* This method is not thread-safe and must be called from
* within the I/O thread.
*/
void CancelRead(); void CancelRead();
bool IsIdle() const { bool IsIdle() const {
@@ -78,8 +102,27 @@ public:
} }
protected: protected:
/**
* The file has been opened successfully. It is a regular
* file, and its size is known. It is ready to be read from
* using Read().
*
* This method will be called from within the I/O thread.
*/
virtual void OnNfsFileOpen(uint64_t size) = 0; virtual void OnNfsFileOpen(uint64_t size) = 0;
/**
* A Read() has completed successfully.
*
* This method will be called from within the I/O thread.
*/
virtual void OnNfsFileRead(const void *data, size_t size) = 0; virtual void OnNfsFileRead(const void *data, size_t size) = 0;
/**
* An error has occurred, which can be either while waiting
* for OnNfsFileOpen(), or while waiting for OnNfsFileRead(),
* or if disconnected while idle.
*/
virtual void OnNfsFileError(std::exception_ptr &&e) = 0; virtual void OnNfsFileError(std::exception_ptr &&e) = 0;
private: private:

View File

@@ -62,7 +62,7 @@ class UPnPDeviceDirectory final : UpnpCallback {
DiscoveredTask(const Upnp_Discovery *disco) DiscoveredTask(const Upnp_Discovery *disco)
:url(disco->Location), :url(disco->Location),
device_id(disco->DeviceId), device_id(disco->DeviceId),
expires(disco->Expires) {} expires(std::chrono::seconds(disco->Expires)) {}
}; };
/** /**

View File

@@ -71,20 +71,19 @@ path_getfather(const std::string &s)
std::list<std::string> std::list<std::string>
stringToTokens(const std::string &str, stringToTokens(const std::string &str,
const char *delims, bool skipinit) const char delim)
{ {
std::list<std::string> tokens; std::list<std::string> tokens;
std::string::size_type startPos = 0; std::string::size_type startPos = str.find_first_not_of(delim, 0);
// Skip initial delims, return empty if this eats all. // Skip initial delims, return empty if this eats all.
if (skipinit && if (startPos == std::string::npos)
(startPos = str.find_first_not_of(delims, 0)) == std::string::npos)
return tokens; return tokens;
while (startPos < str.size()) { while (startPos < str.size()) {
// Find next delimiter or end of string (end of token) // Find next delimiter or end of string (end of token)
auto pos = str.find_first_of(delims, startPos); auto pos = str.find_first_of(delim, startPos);
// Add token to the vector and adjust start // Add token to the vector and adjust start
if (pos == std::string::npos) { if (pos == std::string::npos) {

View File

@@ -33,8 +33,7 @@ path_getfather(const std::string &s);
gcc_pure gcc_pure
std::list<std::string> std::list<std::string>
stringToTokens(const std::string &str, stringToTokens(const std::string &str, char delim);
const char *delims = "/", bool skipinit = true);
template <class T> template <class T>
bool bool

View File

@@ -52,11 +52,11 @@ class WorkQueue {
// Status // Status
// Worker threads having called exit // Worker threads having called exit
unsigned n_workers_exited; unsigned n_workers_exited = 0;
bool ok; bool ok = false;
unsigned n_threads; unsigned n_threads = 0;
pthread_t *threads; pthread_t *threads = nullptr;
// Synchronization // Synchronization
std::queue<T> queue; std::queue<T> queue;
@@ -68,11 +68,8 @@ public:
/** Create a WorkQueue /** Create a WorkQueue
* @param _name for message printing * @param _name for message printing
*/ */
WorkQueue(const char *_name) explicit WorkQueue(const char *_name)
:name(_name), :name(_name)
n_workers_exited(0),
ok(false),
n_threads(0), threads(nullptr)
{ {
} }
@@ -80,6 +77,9 @@ public:
setTerminateAndWait(); setTerminateAndWait();
} }
WorkQueue(const WorkQueue &) = delete;
WorkQueue &operator=(const WorkQueue &) = delete;
/** Start the worker threads. /** Start the worker threads.
* *
* @param nworkers number of threads copies to start. * @param nworkers number of threads copies to start.
@@ -97,6 +97,7 @@ public:
assert(n_threads == 0); assert(n_threads == 0);
assert(threads == nullptr); assert(threads == nullptr);
ok = true;
n_threads = nworkers; n_threads = nworkers;
threads = new pthread_t[n_threads]; threads = new pthread_t[n_threads];
@@ -109,7 +110,6 @@ public:
} }
} }
ok = true;
return true; return true;
} }

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2012-2015 Max Kellermann <max@duempel.org> * Copyright (C) 2012-2015 Max Kellermann <max.kellermann@gmail.com>
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions * modification, are permitted provided that the following conditions

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2012-2015 Max Kellermann <max@duempel.org> * Copyright (C) 2012-2015 Max Kellermann <max.kellermann@gmail.com>
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions * modification, are permitted provided that the following conditions

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2012-2015 Max Kellermann <max@duempel.org> * Copyright (C) 2012-2015 Max Kellermann <max.kellermann@gmail.com>
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions * modification, are permitted provided that the following conditions

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2012-2015 Max Kellermann <max@duempel.org> * Copyright (C) 2012-2015 Max Kellermann <max.kellermann@gmail.com>
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions * modification, are permitted provided that the following conditions

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2012-2015 Max Kellermann <max@duempel.org> * Copyright (C) 2012-2015 Max Kellermann <max.kellermann@gmail.com>
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions * modification, are permitted provided that the following conditions

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2012-2015 Max Kellermann <max@duempel.org> * Copyright (C) 2012-2015 Max Kellermann <max.kellermann@gmail.com>
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions * modification, are permitted provided that the following conditions

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2011-2015 Max Kellermann <max@duempel.org> * Copyright (C) 2011-2015 Max Kellermann <max.kellermann@gmail.com>
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions * modification, are permitted provided that the following conditions

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2011-2015 Max Kellermann <max@duempel.org> * Copyright (C) 2011-2015 Max Kellermann <max.kellermann@gmail.com>
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions * modification, are permitted provided that the following conditions

View File

@@ -35,6 +35,7 @@
#include "thread/Slack.hxx" #include "thread/Slack.hxx"
#include "thread/Name.hxx" #include "thread/Name.hxx"
#include "util/ConstBuffer.hxx" #include "util/ConstBuffer.hxx"
#include "util/StringBuffer.hxx"
#include "util/ScopeExit.hxx" #include "util/ScopeExit.hxx"
#include "util/RuntimeError.hxx" #include "util/RuntimeError.hxx"
#include "Log.hxx" #include "Log.hxx"
@@ -111,8 +112,7 @@ AudioOutput::Open()
f = source.Open(request.audio_format, *request.pipe, f = source.Open(request.audio_format, *request.pipe,
prepared_replay_gain_filter, prepared_replay_gain_filter,
prepared_other_replay_gain_filter, prepared_other_replay_gain_filter,
prepared_filter) prepared_filter);
.WithMask(config_audio_format);
if (mixer != nullptr && mixer->IsPlugin(software_mixer_plugin)) if (mixer != nullptr && mixer->IsPlugin(software_mixer_plugin))
software_mixer_set_filter(*mixer, volume_filter.Get()); software_mixer_set_filter(*mixer, volume_filter.Get());
@@ -121,14 +121,16 @@ AudioOutput::Open()
name, plugin.name)); name, plugin.name));
} }
if (open && f != filter_audio_format) { const auto cf = f.WithMask(config_audio_format);
if (open && cf != filter_audio_format) {
/* if the filter's output format changes, the output /* if the filter's output format changes, the output
must be reopened as well */ must be reopened as well */
CloseOutput(true); CloseOutput(true);
open = false; open = false;
} }
filter_audio_format = f; filter_audio_format = cf;
if (!open) { if (!open) {
try { try {
@@ -139,7 +141,25 @@ AudioOutput::Open()
} }
open = true; open = true;
} else if (f != out_audio_format) {
/* reconfigure the final ConvertFilter for its new
input AudioFormat */
try {
convert_filter_set(convert_filter.Get(),
out_audio_format);
} catch (const std::runtime_error &e) {
Close(false);
std::throw_with_nested(FormatRuntimeError("Failed to convert for \"%s\" [%s]",
name, plugin.name));
}
} }
if (f != source.GetInputAudioFormat() || f != out_audio_format)
FormatDebug(output_domain, "converting in=%s -> f=%s -> out=%s",
ToString(source.GetInputAudioFormat()).c_str(),
ToString(f).c_str(),
ToString(out_audio_format).c_str());
} }
void void
@@ -154,6 +174,11 @@ AudioOutput::OpenOutputAndConvert(AudioFormat desired_audio_format)
name, plugin.name)); name, plugin.name));
} }
FormatDebug(output_domain,
"opened plugin=%s name=\"%s\" audio_format=%s",
plugin.name, name,
ToString(out_audio_format).c_str());
try { try {
convert_filter_set(convert_filter.Get(), out_audio_format); convert_filter_set(convert_filter.Get(), out_audio_format);
} catch (const std::runtime_error &e) { } catch (const std::runtime_error &e) {
@@ -177,17 +202,6 @@ AudioOutput::OpenOutputAndConvert(AudioFormat desired_audio_format)
std::throw_with_nested(FormatRuntimeError("Failed to convert for \"%s\" [%s]", std::throw_with_nested(FormatRuntimeError("Failed to convert for \"%s\" [%s]",
name, plugin.name)); name, plugin.name));
} }
struct audio_format_string af_string;
FormatDebug(output_domain,
"opened plugin=%s name=\"%s\" audio_format=%s",
plugin.name, name,
audio_format_to_string(out_audio_format, &af_string));
if (source.GetInputAudioFormat() != out_audio_format)
FormatDebug(output_domain, "converting from %s",
audio_format_to_string(source.GetInputAudioFormat(),
&af_string));
} }
void void

View File

@@ -70,6 +70,22 @@ AudioOutputSource::Close()
CloseFilter(); CloseFilter();
} }
void
AudioOutputSource::Cancel()
{
current_chunk = nullptr;
pipe.Cancel();
if (replay_gain_filter_instance != nullptr)
replay_gain_filter_instance->Reset();
if (other_replay_gain_filter_instance != nullptr)
other_replay_gain_filter_instance->Reset();
if (filter_instance != nullptr)
filter_instance->Reset();
}
void void
AudioOutputSource::OpenFilter(AudioFormat audio_format, AudioOutputSource::OpenFilter(AudioFormat audio_format,
PreparedFilter *prepared_replay_gain_filter, PreparedFilter *prepared_replay_gain_filter,
@@ -79,13 +95,17 @@ try {
assert(audio_format.IsValid()); assert(audio_format.IsValid());
/* the replay_gain filter cannot fail here */ /* the replay_gain filter cannot fail here */
if (prepared_replay_gain_filter != nullptr) if (prepared_replay_gain_filter != nullptr) {
replay_gain_serial = 0;
replay_gain_filter_instance = replay_gain_filter_instance =
prepared_replay_gain_filter->Open(audio_format); prepared_replay_gain_filter->Open(audio_format);
}
if (prepared_other_replay_gain_filter != nullptr) if (prepared_other_replay_gain_filter != nullptr) {
other_replay_gain_serial = 0;
other_replay_gain_filter_instance = other_replay_gain_filter_instance =
prepared_other_replay_gain_filter->Open(audio_format); prepared_other_replay_gain_filter->Open(audio_format);
}
filter_instance = prepared_filter->Open(audio_format); filter_instance = prepared_filter->Open(audio_format);
} catch (...) { } catch (...) {

View File

@@ -64,13 +64,13 @@ class AudioOutputSource {
* The serial number of the last replay gain info. 0 means no * The serial number of the last replay gain info. 0 means no
* replay gain info was available. * replay gain info was available.
*/ */
unsigned replay_gain_serial = 0; unsigned replay_gain_serial;
/** /**
* The serial number of the last replay gain info by the * The serial number of the last replay gain info by the
* "other" chunk during cross-fading. * "other" chunk during cross-fading.
*/ */
unsigned other_replay_gain_serial = 0; unsigned other_replay_gain_serial;
/** /**
* The replay_gain_filter_plugin instance of this audio * The replay_gain_filter_plugin instance of this audio
@@ -139,11 +139,7 @@ public:
PreparedFilter *prepared_filter); PreparedFilter *prepared_filter);
void Close(); void Close();
void Cancel();
void Cancel() {
current_chunk = nullptr;
pipe.Cancel();
}
/** /**
* Ensure that ReadTag() or PeekData() return any input. * Ensure that ReadTag() or PeekData() return any input.

View File

@@ -50,7 +50,9 @@ static constexpr unsigned MPD_ALSA_BUFFER_TIME_US = 500000;
static constexpr unsigned MPD_ALSA_RETRY_NR = 5; static constexpr unsigned MPD_ALSA_RETRY_NR = 5;
struct AlsaOutput { class AlsaOutput {
friend struct AudioOutputWrapper<AlsaOutput>;
AudioOutput base; AudioOutput base;
Manual<PcmExport> pcm_export; Manual<PcmExport> pcm_export;
@@ -121,6 +123,7 @@ struct AlsaOutput {
*/ */
uint8_t *silence; uint8_t *silence;
public:
AlsaOutput(const ConfigBlock &block); AlsaOutput(const ConfigBlock &block);
~AlsaOutput() { ~AlsaOutput() {
@@ -141,11 +144,20 @@ struct AlsaOutput {
void Open(AudioFormat &audio_format); void Open(AudioFormat &audio_format);
void Close(); void Close();
size_t PlayRaw(ConstBuffer<void> data);
size_t Play(const void *chunk, size_t size); size_t Play(const void *chunk, size_t size);
void Drain(); void Drain();
void Cancel(); void Cancel();
private: private:
/**
* Set up the snd_pcm_t object which was opened by the caller.
* Set up the configured settings and the audio format.
*
* Throws #std::runtime_error on error.
*/
void Setup(AudioFormat &audio_format, PcmExport::Params &params);
#ifdef ENABLE_DSD #ifdef ENABLE_DSD
void SetupDop(AudioFormat audio_format, void SetupDop(AudioFormat audio_format,
PcmExport::Params &params); 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 * Attempts to configure the specified sample format. On DSD_U8
* failure, attempt to switch to DSD_U32. * failure, attempt to switch to DSD_U32 or DSD_U16.
*/ */
static int static int
AlsaTryFormatDsd(snd_pcm_t *pcm, snd_pcm_hw_params_t *hwparams, AlsaTryFormatDsd(snd_pcm_t *pcm, snd_pcm_hw_params_t *hwparams,
@@ -393,8 +405,10 @@ AlsaTryFormatDsd(snd_pcm_t *pcm, snd_pcm_hw_params_t *hwparams,
int err = AlsaTryFormatOrByteSwap(pcm, hwparams, fmt, params); int err = AlsaTryFormatOrByteSwap(pcm, hwparams, fmt, params);
#if defined(ENABLE_DSD) && defined(HAVE_ALSA_DSD_U32) #if defined(ENABLE_DSD) && defined(HAVE_ALSA_DSD_U32)
if (err == 0) if (err == 0) {
params.dsd_u16 = false;
params.dsd_u32 = false; params.dsd_u32 = false;
}
if (err == -EINVAL && fmt == SND_PCM_FORMAT_DSD_U8) { if (err == -EINVAL && fmt == SND_PCM_FORMAT_DSD_U8) {
/* attempt to switch to DSD_U32 */ /* attempt to switch to DSD_U32 */
@@ -404,6 +418,20 @@ AlsaTryFormatDsd(snd_pcm_t *pcm, snd_pcm_hw_params_t *hwparams,
err = AlsaTryFormatOrByteSwap(pcm, hwparams, fmt, params); err = AlsaTryFormatOrByteSwap(pcm, hwparams, fmt, params);
if (err == 0) if (err == 0)
params.dsd_u32 = true; params.dsd_u32 = true;
else
fmt = SND_PCM_FORMAT_DSD_U8;
}
if (err == -EINVAL && fmt == SND_PCM_FORMAT_DSD_U8) {
/* attempt to switch to DSD_U16 */
fmt = IsLittleEndian()
? SND_PCM_FORMAT_DSD_U16_LE
: SND_PCM_FORMAT_DSD_U16_BE;
err = AlsaTryFormatOrByteSwap(pcm, hwparams, fmt, params);
if (err == 0)
params.dsd_u16 = true;
else
fmt = SND_PCM_FORMAT_DSD_U8;
} }
#endif #endif
@@ -460,51 +488,44 @@ AlsaSetupFormat(snd_pcm_t *pcm, snd_pcm_hw_params_t *hwparams,
} }
/** /**
* Set up the snd_pcm_t object which was opened by the caller. Set up * Wrapper for snd_pcm_hw_params().
* the configured settings and the audio format.
* *
* Throws #std::runtime_error on error. * @param buffer_time the configured buffer time, or 0 if not configured
* @param period_time the configured period time, or 0 if not configured
* @param audio_format an #AudioFormat to be configured (or modified)
* by this function
* @param params to be modified by this function
*/ */
static void static void
AlsaSetup(AlsaOutput *ad, AudioFormat &audio_format, AlsaSetupHw(snd_pcm_t *pcm, snd_pcm_hw_params_t *hwparams,
PcmExport::Params &params) 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; int err;
unsigned retry = MPD_ALSA_RETRY_NR; unsigned retry = MPD_ALSA_RETRY_NR;
unsigned int period_time, period_time_ro; unsigned int period_time_ro = period_time;
unsigned int buffer_time;
period_time_ro = period_time = ad->period_time;
configure_hw: configure_hw:
/* configure HW params */ /* configure HW params */
snd_pcm_hw_params_t *hwparams; err = snd_pcm_hw_params_any(pcm, hwparams);
snd_pcm_hw_params_alloca(&hwparams);
err = snd_pcm_hw_params_any(ad->pcm, hwparams);
if (err < 0) if (err < 0)
throw FormatRuntimeError("snd_pcm_hw_params_any() failed: %s", throw FormatRuntimeError("snd_pcm_hw_params_any() failed: %s",
snd_strerror(-err)); snd_strerror(-err));
err = snd_pcm_hw_params_set_access(ad->pcm, hwparams, err = snd_pcm_hw_params_set_access(pcm, hwparams,
SND_PCM_ACCESS_RW_INTERLEAVED); SND_PCM_ACCESS_RW_INTERLEAVED);
if (err < 0) if (err < 0)
throw FormatRuntimeError("snd_pcm_hw_params_set_access() failed: %s", throw FormatRuntimeError("snd_pcm_hw_params_set_access() failed: %s",
snd_strerror(-err)); snd_strerror(-err));
err = AlsaSetupFormat(ad->pcm, hwparams, audio_format, params); err = AlsaSetupFormat(pcm, hwparams, audio_format, params);
if (err < 0) if (err < 0)
throw FormatRuntimeError("Failed to configure format %s: %s", throw FormatRuntimeError("Failed to configure format %s: %s",
sample_format_to_string(audio_format.format), sample_format_to_string(audio_format.format),
snd_strerror(-err)); snd_strerror(-err));
snd_pcm_format_t format; unsigned int channels = audio_format.channels;
if (snd_pcm_hw_params_get_format(hwparams, &format) == 0) err = snd_pcm_hw_params_set_channels_near(pcm, hwparams,
FormatDebug(alsa_output_domain,
"format=%s (%s)", snd_pcm_format_name(format),
snd_pcm_format_description(format));
err = snd_pcm_hw_params_set_channels_near(ad->pcm, hwparams,
&channels); &channels);
if (err < 0) if (err < 0)
throw FormatRuntimeError("Failed to configure %i channels: %s", throw FormatRuntimeError("Failed to configure %i channels: %s",
@@ -513,18 +534,23 @@ configure_hw:
audio_format.channels = (int8_t)channels; audio_format.channels = (int8_t)channels;
err = snd_pcm_hw_params_set_rate_near(ad->pcm, hwparams, const unsigned requested_sample_rate =
&sample_rate, nullptr); params.CalcOutputSampleRate(audio_format.sample_rate);
unsigned output_sample_rate = requested_sample_rate;
err = snd_pcm_hw_params_set_rate_near(pcm, hwparams,
&output_sample_rate, nullptr);
if (err < 0) if (err < 0)
throw FormatRuntimeError("Failed to configure sample rate %u Hz: %s", throw FormatRuntimeError("Failed to configure sample rate %u Hz: %s",
audio_format.sample_rate, requested_sample_rate,
snd_strerror(-err)); snd_strerror(-err));
if (sample_rate == 0) if (output_sample_rate == 0)
throw FormatRuntimeError("Failed to configure sample rate %u Hz", throw FormatRuntimeError("Failed to configure sample rate %u Hz",
audio_format.sample_rate); audio_format.sample_rate);
audio_format.sample_rate = sample_rate; if (output_sample_rate != requested_sample_rate)
audio_format.sample_rate = params.CalcInputSampleRate(output_sample_rate);
snd_pcm_uframes_t buffer_size_min, buffer_size_max; snd_pcm_uframes_t buffer_size_min, buffer_size_max;
snd_pcm_hw_params_get_buffer_size_min(hwparams, &buffer_size_min); snd_pcm_hw_params_get_buffer_size_min(hwparams, &buffer_size_min);
@@ -546,9 +572,8 @@ configure_hw:
(unsigned)period_size_min, (unsigned)period_size_max, (unsigned)period_size_min, (unsigned)period_size_max,
period_time_min, period_time_max); period_time_min, period_time_max);
if (ad->buffer_time > 0) { if (buffer_time > 0) {
buffer_time = ad->buffer_time; err = snd_pcm_hw_params_set_buffer_time_near(pcm, hwparams,
err = snd_pcm_hw_params_set_buffer_time_near(ad->pcm, hwparams,
&buffer_time, nullptr); &buffer_time, nullptr);
if (err < 0) if (err < 0)
throw FormatRuntimeError("snd_pcm_hw_params_set_buffer_time_near() failed: %s", throw FormatRuntimeError("snd_pcm_hw_params_set_buffer_time_near() failed: %s",
@@ -570,14 +595,14 @@ configure_hw:
if (period_time_ro > 0) { if (period_time_ro > 0) {
period_time = period_time_ro; period_time = period_time_ro;
err = snd_pcm_hw_params_set_period_time_near(ad->pcm, hwparams, err = snd_pcm_hw_params_set_period_time_near(pcm, hwparams,
&period_time, nullptr); &period_time, nullptr);
if (err < 0) if (err < 0)
throw FormatRuntimeError("snd_pcm_hw_params_set_period_time_near() failed: %s", throw FormatRuntimeError("snd_pcm_hw_params_set_period_time_near() failed: %s",
snd_strerror(-err)); snd_strerror(-err));
} }
err = snd_pcm_hw_params(ad->pcm, hwparams); err = snd_pcm_hw_params(pcm, hwparams);
if (err == -EPIPE && --retry > 0 && period_time_ro > 0) { if (err == -EPIPE && --retry > 0 && period_time_ro > 0) {
period_time_ro = period_time_ro >> 1; period_time_ro = period_time_ro >> 1;
goto configure_hw; goto configure_hw;
@@ -587,9 +612,59 @@ configure_hw:
if (retry != MPD_ALSA_RETRY_NR) if (retry != MPD_ALSA_RETRY_NR)
FormatDebug(alsa_output_domain, FormatDebug(alsa_output_domain,
"ALSA period_time set to %d", period_time); "ALSA period_time set to %d", period_time);
}
/**
* Wrapper for snd_pcm_sw_params().
*/
static void
AlsaSetupSw(snd_pcm_t *pcm, snd_pcm_uframes_t start_threshold,
snd_pcm_uframes_t avail_min)
{
snd_pcm_sw_params_t *swparams;
snd_pcm_sw_params_alloca(&swparams);
int err = snd_pcm_sw_params_current(pcm, swparams);
if (err < 0)
throw FormatRuntimeError("snd_pcm_sw_params_current() failed: %s",
snd_strerror(-err));
err = snd_pcm_sw_params_set_start_threshold(pcm, swparams,
start_threshold);
if (err < 0)
throw FormatRuntimeError("snd_pcm_sw_params_set_start_threshold() failed: %s",
snd_strerror(-err));
err = snd_pcm_sw_params_set_avail_min(pcm, swparams, avail_min);
if (err < 0)
throw FormatRuntimeError("snd_pcm_sw_params_set_avail_min() failed: %s",
snd_strerror(-err));
err = snd_pcm_sw_params(pcm, swparams);
if (err < 0)
throw FormatRuntimeError("snd_pcm_sw_params() failed: %s",
snd_strerror(-err));
}
inline void
AlsaOutput::Setup(AudioFormat &audio_format,
PcmExport::Params &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; snd_pcm_uframes_t alsa_buffer_size;
err = snd_pcm_hw_params_get_buffer_size(hwparams, &alsa_buffer_size); int err = snd_pcm_hw_params_get_buffer_size(hwparams, &alsa_buffer_size);
if (err < 0) if (err < 0)
throw FormatRuntimeError("snd_pcm_hw_params_get_buffer_size() failed: %s", throw FormatRuntimeError("snd_pcm_hw_params_get_buffer_size() failed: %s",
snd_strerror(-err)); snd_strerror(-err));
@@ -601,32 +676,8 @@ configure_hw:
throw FormatRuntimeError("snd_pcm_hw_params_get_period_size() failed: %s", throw FormatRuntimeError("snd_pcm_hw_params_get_period_size() failed: %s",
snd_strerror(-err)); snd_strerror(-err));
/* configure SW params */ AlsaSetupSw(pcm, alsa_buffer_size - alsa_period_size,
snd_pcm_sw_params_t *swparams; alsa_period_size);
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));
FormatDebug(alsa_output_domain, "buffer_size=%u period_size=%u", FormatDebug(alsa_output_domain, "buffer_size=%u period_size=%u",
(unsigned)alsa_buffer_size, (unsigned)alsa_period_size); (unsigned)alsa_buffer_size, (unsigned)alsa_period_size);
@@ -639,13 +690,12 @@ configure_hw:
happen again. */ happen again. */
alsa_period_size = 1; alsa_period_size = 1;
ad->period_frames = alsa_period_size; period_frames = alsa_period_size;
ad->period_position = 0; period_position = 0;
ad->silence = new uint8_t[snd_pcm_frames_to_bytes(ad->pcm, silence = new uint8_t[snd_pcm_frames_to_bytes(pcm, alsa_period_size)];
alsa_period_size)]; snd_pcm_format_set_silence(format, silence,
snd_pcm_format_set_silence(format, ad->silence, alsa_period_size * audio_format.channels);
alsa_period_size * channels);
} }
@@ -662,11 +712,10 @@ AlsaOutput::SetupDop(const AudioFormat audio_format,
AudioFormat dop_format = audio_format; AudioFormat dop_format = audio_format;
dop_format.format = SampleFormat::S24_P32; dop_format.format = SampleFormat::S24_P32;
dop_format.sample_rate /= 2;
const AudioFormat check = dop_format; const AudioFormat check = dop_format;
AlsaSetup(this, dop_format, params); Setup(dop_format, params);
/* if the device allows only 32 bit, shift all DoP /* if the device allows only 32 bit, shift all DoP
samples left by 8 bit and leave the lower 8 bit cleared; samples left by 8 bit and leave the lower 8 bit cleared;
@@ -694,17 +743,18 @@ AlsaOutput::SetupOrDop(AudioFormat &audio_format, PcmExport::Params &params)
std::exception_ptr dop_error; std::exception_ptr dop_error;
if (dop && audio_format.format == SampleFormat::DSD) { if (dop && audio_format.format == SampleFormat::DSD) {
try { try {
SetupDop(audio_format, params);
params.dop = true; params.dop = true;
SetupDop(audio_format, params);
return; return;
} catch (...) { } catch (...) {
dop_error = std::current_exception(); dop_error = std::current_exception();
params.dop = false;
} }
} }
try { try {
#endif #endif
AlsaSetup(this, audio_format, params); Setup(audio_format, params);
#ifdef ENABLE_DSD #ifdef ENABLE_DSD
} catch (...) { } catch (...) {
if (dop_error) if (dop_error)
@@ -742,6 +792,11 @@ AlsaOutput::Open(AudioFormat &audio_format)
GetDevice())); GetDevice()));
} }
#ifdef ENABLE_DSD
if (params.dop)
FormatDebug(alsa_output_domain, "DoP (DSD over PCM) enabled");
#endif
pcm_export->Open(audio_format.format, pcm_export->Open(audio_format.format,
audio_format.channels, audio_format.channels,
params); params);
@@ -777,6 +832,7 @@ AlsaOutput::Recover(int err)
#if GCC_CHECK_VERSION(7,0) #if GCC_CHECK_VERSION(7,0)
[[fallthrough]]; [[fallthrough]];
#endif #endif
case SND_PCM_STATE_OPEN:
case SND_PCM_STATE_SETUP: case SND_PCM_STATE_SETUP:
case SND_PCM_STATE_XRUN: case SND_PCM_STATE_XRUN:
period_position = 0; period_position = 0;
@@ -785,12 +841,11 @@ AlsaOutput::Recover(int err)
case SND_PCM_STATE_DISCONNECTED: case SND_PCM_STATE_DISCONNECTED:
break; break;
/* this is no error, so just keep running */ /* this is no error, so just keep running */
case SND_PCM_STATE_PREPARED:
case SND_PCM_STATE_RUNNING: case SND_PCM_STATE_RUNNING:
case SND_PCM_STATE_DRAINING:
err = 0; err = 0;
break; break;
default:
/* unknown state, do nothing */
break;
} }
return err; return err;
@@ -822,6 +877,8 @@ AlsaOutput::Cancel()
must_prepare = true; must_prepare = true;
snd_pcm_drop(pcm); snd_pcm_drop(pcm);
pcm_export->Reset();
} }
inline void inline void
@@ -831,6 +888,36 @@ AlsaOutput::Close()
delete[] silence; delete[] silence;
} }
inline size_t
AlsaOutput::PlayRaw(ConstBuffer<void> data)
{
if (data.IsEmpty())
return 0;
assert(data.size % out_frame_size == 0);
const size_t n_frames = data.size / out_frame_size;
assert(n_frames > 0);
while (true) {
const auto frames_written = snd_pcm_writei(pcm, data.data,
n_frames);
if (frames_written > 0) {
period_position = (period_position + frames_written)
% period_frames;
return frames_written * out_frame_size;
}
if (frames_written < 0 && frames_written != -EAGAIN &&
frames_written != -EINTR &&
Recover(frames_written) < 0)
throw FormatRuntimeError("snd_pcm_writei() failed: %s",
snd_strerror(-frames_written));
}
}
inline size_t inline size_t
AlsaOutput::Play(const void *chunk, size_t size) AlsaOutput::Play(const void *chunk, size_t size)
{ {
@@ -856,29 +943,8 @@ AlsaOutput::Play(const void *chunk, size_t size)
been played */ been played */
return size; return size;
chunk = e.data; const size_t bytes_written = PlayRaw(e);
size = e.size; return pcm_export->CalcSourceSize(bytes_written);
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));
}
} }
typedef AudioOutputWrapper<AlsaOutput> Wrapper; typedef AudioOutputWrapper<AlsaOutput> Wrapper;

View File

@@ -659,6 +659,10 @@ OssOutput::Cancel()
ioctl(fd, SNDCTL_DSP_RESET, 0); ioctl(fd, SNDCTL_DSP_RESET, 0);
DoClose(); DoClose();
} }
#ifdef AFMT_S24_PACKED
pcm_export->Reset();
#endif
} }
inline size_t inline size_t

View File

@@ -274,7 +274,7 @@ RecorderOutput::ReopenFormat(AllocatedPath &&new_path)
assert(path.IsNull()); assert(path.IsNull());
assert(file == nullptr); assert(file == nullptr);
FileOutputStream *new_file = new FileOutputStream(path); FileOutputStream *new_file = new FileOutputStream(new_path);
AudioFormat new_audio_format = effective_audio_format; AudioFormat new_audio_format = effective_audio_format;

View File

@@ -24,8 +24,16 @@
#include "util/Domain.hxx" #include "util/Domain.hxx"
#include "Log.hxx" #include "Log.hxx"
/* work around a C++ incompatibility if the sndio API is emulated by
libroar: libroar's "struct roar_service_stream" has a member named
"new", which is an illegal identifier in C++ */
#define new new_
#include <sndio.h> #include <sndio.h>
/* undo the libroar workaround */
#undef new
#include <stdexcept> #include <stdexcept>
#ifndef SIO_DEVANY #ifndef SIO_DEVANY

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2011-2012 Max Kellermann <max@duempel.org> * Copyright (C) 2011-2012 Max Kellermann <max.kellermann@gmail.com>
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions * modification, are permitted provided that the following conditions

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2011-2012 Max Kellermann <max@duempel.org> * Copyright (C) 2011-2012 Max Kellermann <max.kellermann@gmail.com>
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions * modification, are permitted provided that the following conditions

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2011-2012 Max Kellermann <max@duempel.org> * Copyright (C) 2011-2012 Max Kellermann <max.kellermann@gmail.com>
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions * modification, are permitted provided that the following conditions

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2011-2012 Max Kellermann <max@duempel.org> * Copyright (C) 2011-2012 Max Kellermann <max.kellermann@gmail.com>
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions * modification, are permitted provided that the following conditions

View File

@@ -21,7 +21,7 @@
#define MPD_PCM_CHANNELS_CONVERTER_HXX #define MPD_PCM_CHANNELS_CONVERTER_HXX
#include "check.h" #include "check.h"
#include "AudioFormat.hxx" #include "SampleFormat.hxx"
#include "PcmBuffer.hxx" #include "PcmBuffer.hxx"
#ifndef NDEBUG #ifndef NDEBUG

62
src/pcm/Dsd16.cxx Normal file
View File

@@ -0,0 +1,62 @@
/*
* Copyright 2003-2017 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "config.h"
#include "Dsd16.hxx"
#include "PcmBuffer.hxx"
#include "util/ConstBuffer.hxx"
/**
* Construct a 16 bit integer from two bytes.
*/
static constexpr inline uint16_t
Construct16(uint8_t a, uint8_t b)
{
/* "a" is the oldest byte, which must be in the most
significant byte */
return uint16_t(b) | (uint16_t(a) << 8);
}
static constexpr inline uint16_t
Dsd8To16Sample(const uint8_t *src, unsigned channels)
{
return Construct16(src[0], src[channels]);
}
ConstBuffer<uint16_t>
Dsd8To16(PcmBuffer &buffer, unsigned channels, ConstBuffer<uint8_t> _src)
{
const size_t in_frames = _src.size / channels;
const size_t out_frames = in_frames / 2;
const size_t out_samples = out_frames * channels;
const uint8_t *src = _src.data;
uint16_t *const dest0 = buffer.GetT<uint16_t>(out_samples);
uint16_t *dest = dest0;
for (size_t i = 0; i < out_frames; ++i) {
for (size_t c = 0; c < channels; ++c)
*dest++ = Dsd8To16Sample(src++, channels);
src += channels;
}
return {dest0, out_samples};
}

36
src/pcm/Dsd16.hxx Normal file
View File

@@ -0,0 +1,36 @@
/*
* Copyright 2003-2017 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef MPD_PCM_DSD_16_HXX
#define MPD_PCM_DSD_16_HXX
#include "check.h"
#include <stdint.h>
template<typename T> struct ConstBuffer;
class PcmBuffer;
/**
* Convert DSD_U8 to DSD_U16 (native endian, oldest bits in MSB).
*/
ConstBuffer<uint16_t>
Dsd8To16(PcmBuffer &buffer, unsigned channels, ConstBuffer<uint8_t> src);
#endif

64
src/pcm/Dsd32.cxx Normal file
View File

@@ -0,0 +1,64 @@
/*
* Copyright 2003-2017 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "config.h"
#include "Dsd32.hxx"
#include "PcmBuffer.hxx"
#include "util/ConstBuffer.hxx"
/**
* Construct a 32 bit integer from four bytes.
*/
static constexpr inline uint32_t
Construct32(uint8_t a, uint8_t b, uint8_t c, uint8_t d)
{
/* "a" is the oldest byte, which must be in the most
significant byte */
return uint32_t(d) | (uint32_t(c) << 8) |
(uint32_t(b) << 16) | (uint32_t(a) << 24);
}
static constexpr inline uint32_t
Dsd8To32Sample(const uint8_t *src, unsigned channels)
{
return Construct32(src[0], src[channels],
src[2 * channels], src[3 * channels]);
}
ConstBuffer<uint32_t>
Dsd8To32(PcmBuffer &buffer, unsigned channels, ConstBuffer<uint8_t> _src)
{
const size_t in_frames = _src.size / channels;
const size_t out_frames = in_frames / 4;
const size_t out_samples = out_frames * channels;
const uint8_t *src = _src.data;
uint32_t *const dest0 = buffer.GetT<uint32_t>(out_samples);
uint32_t *dest = dest0;
for (size_t i = 0; i < out_frames; ++i) {
for (size_t c = 0; c < channels; ++c)
*dest++ = Dsd8To32Sample(src++, channels);
src += 3 * channels;
}
return {dest0, out_samples};
}

36
src/pcm/Dsd32.hxx Normal file
View File

@@ -0,0 +1,36 @@
/*
* Copyright 2003-2017 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef MPD_PCM_DSD_32_HXX
#define MPD_PCM_DSD_32_HXX
#include "check.h"
#include <stdint.h>
template<typename T> struct ConstBuffer;
class PcmBuffer;
/**
* Convert DSD_U8 to DSD_U32 (native endian, oldest bits in MSB).
*/
ConstBuffer<uint32_t>
Dsd8To32(PcmBuffer &buffer, unsigned channels, ConstBuffer<uint8_t> src);
#endif

View File

@@ -21,7 +21,7 @@
#define MPD_PCM_FORMAT_CONVERTER_HXX #define MPD_PCM_FORMAT_CONVERTER_HXX
#include "check.h" #include "check.h"
#include "AudioFormat.hxx" #include "SampleFormat.hxx"
#include "PcmBuffer.hxx" #include "PcmBuffer.hxx"
#include "PcmDither.hxx" #include "PcmDither.hxx"

View File

@@ -65,6 +65,12 @@ GluePcmResampler::Close()
resampler->Close(); resampler->Close();
} }
void
GluePcmResampler::Reset()
{
resampler->Reset();
}
ConstBuffer<void> ConstBuffer<void>
GluePcmResampler::Resample(ConstBuffer<void> src) GluePcmResampler::Resample(ConstBuffer<void> src)
{ {

View File

@@ -55,6 +55,11 @@ public:
return output_sample_format; return output_sample_format;
} }
/**
* @see PcmResampler::Reset()
*/
void Reset();
ConstBuffer<void> Resample(ConstBuffer<void> src); ConstBuffer<void> Resample(ConstBuffer<void> src);
}; };

View File

@@ -114,6 +114,12 @@ LibsampleratePcmResampler::Close()
state = src_delete(state); state = src_delete(state);
} }
void
LibsampleratePcmResampler::Reset()
{
src_reset(state);
}
inline ConstBuffer<float> inline ConstBuffer<float>
LibsampleratePcmResampler::Resample2(ConstBuffer<float> src) LibsampleratePcmResampler::Resample2(ConstBuffer<float> src)
{ {

View File

@@ -44,6 +44,7 @@ class LibsampleratePcmResampler final : public PcmResampler {
public: public:
AudioFormat Open(AudioFormat &af, unsigned new_sample_rate) override; AudioFormat Open(AudioFormat &af, unsigned new_sample_rate) override;
void Close() override; void Close() override;
void Reset() override;
ConstBuffer<void> Resample(ConstBuffer<void> src) override; ConstBuffer<void> Resample(ConstBuffer<void> src) override;
private: private:

View File

@@ -21,7 +21,7 @@
#define MPD_PCM_ORDER_HXX #define MPD_PCM_ORDER_HXX
#include "check.h" #include "check.h"
#include "AudioFormat.hxx" #include "SampleFormat.hxx"
class PcmBuffer; class PcmBuffer;
template<typename T> struct ConstBuffer; template<typename T> struct ConstBuffer;

View File

@@ -40,7 +40,7 @@ public:
/** /**
* Get the buffer, and guarantee a minimum size. This buffer becomes * Get the buffer, and guarantee a minimum size. This buffer becomes
* invalid with the next pcm_buffer_get() call. * invalid with the next Get() call.
* *
* This function will never return nullptr, even if size is * This function will never return nullptr, even if size is
* zero, because the PCM library uses the nullptr return value * zero, because the PCM library uses the nullptr return value

View File

@@ -20,9 +20,14 @@
#include "config.h" #include "config.h"
#include "PcmChannels.hxx" #include "PcmChannels.hxx"
#include "PcmBuffer.hxx" #include "PcmBuffer.hxx"
#include "Silence.hxx"
#include "Traits.hxx" #include "Traits.hxx"
#include "AudioFormat.hxx" #include "AudioFormat.hxx"
#include "util/ConstBuffer.hxx" #include "util/ConstBuffer.hxx"
#include "util/WritableBuffer.hxx"
#include <array>
#include <algorithm>
#include <assert.h> #include <assert.h>
@@ -90,6 +95,38 @@ NToStereo(typename Traits::pointer_type dest,
return dest; return dest;
} }
/**
* Convert stereo to N channels (where N > 2). Left and right map to
* the first two channels (front left and front right), and the
* remaining (surround) channels are filled with silence.
*/
template<SampleFormat F, class Traits=SampleTraits<F>>
static typename Traits::pointer_type
StereoToN(typename Traits::pointer_type dest,
unsigned dest_channels,
typename Traits::const_pointer_type src,
typename Traits::const_pointer_type end)
{
assert(dest_channels > 2);
assert((end - src) % 2 == 0);
std::array<typename Traits::value_type, MAX_CHANNELS - 2> silence;
PcmSilence({&silence.front(), sizeof(silence)}, F);
while (src != end) {
/* copy left/right to front-left/front-right, which is
the first two channels in all multi-channel
configurations **/
*dest++ = *src++;
*dest++ = *src++;
/* all other channels are silent */
dest = std::copy_n(silence.begin(), dest_channels - 2, dest);
}
return dest;
}
template<SampleFormat F, class Traits=SampleTraits<F>> template<SampleFormat F, class Traits=SampleTraits<F>>
static typename Traits::pointer_type static typename Traits::pointer_type
NToM(typename Traits::pointer_type dest, NToM(typename Traits::pointer_type dest,
@@ -133,6 +170,9 @@ ConvertChannels(PcmBuffer &buffer,
StereoToMono<F>(dest, src.begin(), src.end()); StereoToMono<F>(dest, src.begin(), src.end());
else if (dest_channels == 2) else if (dest_channels == 2)
NToStereo<F>(dest, src_channels, src.begin(), src.end()); NToStereo<F>(dest, src_channels, src.begin(), src.end());
else if (src_channels == 2 && dest_channels > 2)
StereoToN<F, Traits>(dest, dest_channels,
src.begin(), src.end());
else else
NToM<F>(dest, dest_channels, NToM<F>(dest, dest_channels,
src_channels, src.begin(), src.end()); src_channels, src.begin(), src.end());

View File

@@ -20,7 +20,6 @@
#include "config.h" #include "config.h"
#include "PcmConvert.hxx" #include "PcmConvert.hxx"
#include "ConfiguredResampler.hxx" #include "ConfiguredResampler.hxx"
#include "AudioFormat.hxx"
#include "util/ConstBuffer.hxx" #include "util/ConstBuffer.hxx"
#include <assert.h> #include <assert.h>
@@ -117,6 +116,17 @@ PcmConvert::Close()
#endif #endif
} }
void
PcmConvert::Reset()
{
if (enable_resampler)
resampler.Reset();
#ifdef ENABLE_DSD
dsd.Reset();
#endif
}
ConstBuffer<void> ConstBuffer<void>
PcmConvert::Convert(ConstBuffer<void> buffer) PcmConvert::Convert(ConstBuffer<void> buffer)
{ {

View File

@@ -67,6 +67,11 @@ public:
*/ */
void Close(); void Close();
/**
* Reset the filter's state, e.g. drop/flush buffers.
*/
void Reset();
/** /**
* Converts PCM data between two audio formats. * Converts PCM data between two audio formats.
* *

View File

@@ -44,8 +44,6 @@ pcm_dsd_to_dop(PcmBuffer &buffer, unsigned channels,
ConstBuffer<uint8_t> _src) ConstBuffer<uint8_t> _src)
{ {
assert(audio_valid_channel_count(channels)); assert(audio_valid_channel_count(channels));
assert(!_src.IsNull());
assert(_src.size > 0);
assert(_src.size % channels == 0); assert(_src.size % channels == 0);
const unsigned num_src_samples = _src.size; const unsigned num_src_samples = _src.size;

View File

@@ -71,41 +71,3 @@ PcmDsd::ToFloat(unsigned channels, ConstBuffer<uint8_t> src)
return { dest, num_samples }; return { dest, num_samples };
} }
/**
* Construct a 32 bit integer from four bytes.
*/
static constexpr inline uint32_t
Construct32(uint8_t a, uint8_t b, uint8_t c, uint8_t d)
{
return uint32_t(a) | (uint32_t(b) << 8) |
(uint32_t(c) << 16) | (uint32_t(d) << 24);
}
static constexpr inline uint32_t
Dsd8To32Sample(const uint8_t *src, unsigned channels)
{
return Construct32(src[0], src[channels],
src[2 * channels], src[3 * channels]);
}
ConstBuffer<uint32_t>
Dsd8To32(PcmBuffer &buffer, unsigned channels, ConstBuffer<uint8_t> _src)
{
const size_t in_frames = _src.size / channels;
const size_t out_frames = in_frames / 4;
const size_t out_samples = out_frames * channels;
const uint8_t *src = _src.data;
uint32_t *const dest0 = buffer.GetT<uint32_t>(out_samples);
uint32_t *dest = dest0;
for (size_t i = 0; i < out_frames; ++i) {
for (size_t c = 0; c < channels; ++c)
*dest++ = Dsd8To32Sample(src++, channels);
src += 3 * channels;
}
return {dest0, out_samples};
}

View File

@@ -48,10 +48,4 @@ public:
ConstBuffer<uint8_t> src); ConstBuffer<uint8_t> src);
}; };
/**
* Convert DSD_U8 to DSD_U32 (native endian).
*/
ConstBuffer<uint32_t>
Dsd8To32(PcmBuffer &buffer, unsigned channels, ConstBuffer<uint8_t> src);
#endif #endif

View File

@@ -19,12 +19,15 @@
#include "config.h" #include "config.h"
#include "PcmExport.hxx" #include "PcmExport.hxx"
#include "AudioFormat.hxx"
#include "Order.hxx" #include "Order.hxx"
#include "PcmPack.hxx" #include "PcmPack.hxx"
#include "util/ByteReverse.hxx" #include "util/ByteReverse.hxx"
#include "util/ConstBuffer.hxx" #include "util/ConstBuffer.hxx"
#ifdef ENABLE_DSD #ifdef ENABLE_DSD
#include "Dsd16.hxx"
#include "Dsd32.hxx"
#include "PcmDsd.hxx" #include "PcmDsd.hxx"
#include "PcmDop.hxx" #include "PcmDop.hxx"
#endif #endif
@@ -41,9 +44,15 @@ PcmExport::Open(SampleFormat sample_format, unsigned _channels,
: SampleFormat::UNDEFINED; : SampleFormat::UNDEFINED;
#ifdef ENABLE_DSD #ifdef ENABLE_DSD
assert(!params.dsd_u32 || !params.dop); assert((params.dsd_u16 + params.dsd_u32 + params.dop) <= 1);
assert(!params.dop || audio_valid_channel_count(_channels)); assert(!params.dop || audio_valid_channel_count(_channels));
dsd_u16 = params.dsd_u16 && sample_format == SampleFormat::DSD;
if (dsd_u16)
/* after the conversion to DSD_U16, the DSD samples
are stuffed inside fake 16 bit samples */
sample_format = SampleFormat::S16;
dsd_u32 = params.dsd_u32 && sample_format == SampleFormat::DSD; dsd_u32 = params.dsd_u32 && sample_format == SampleFormat::DSD;
if (dsd_u32) if (dsd_u32)
/* after the conversion to DSD_U32, the DSD samples /* after the conversion to DSD_U32, the DSD samples
@@ -55,8 +64,6 @@ PcmExport::Open(SampleFormat sample_format, unsigned _channels,
/* after the conversion to DoP, the DSD /* after the conversion to DoP, the DSD
samples are stuffed inside fake 24 bit samples */ samples are stuffed inside fake 24 bit samples */
sample_format = SampleFormat::S24_P32; sample_format = SampleFormat::S24_P32;
#else
(void)_channels;
#endif #endif
shift8 = params.shift8 && sample_format == SampleFormat::S24_P32; shift8 = params.shift8 && sample_format == SampleFormat::S24_P32;
@@ -84,6 +91,9 @@ PcmExport::GetFrameSize(const AudioFormat &audio_format) const
return audio_format.channels * 3; return audio_format.channels * 3;
#ifdef ENABLE_DSD #ifdef ENABLE_DSD
if (dsd_u16)
return channels * 2;
if (dsd_u32) if (dsd_u32)
return channels * 4; return channels * 4;
@@ -98,6 +108,46 @@ PcmExport::GetFrameSize(const AudioFormat &audio_format) const
return audio_format.GetFrameSize(); return audio_format.GetFrameSize();
} }
unsigned
PcmExport::Params::CalcOutputSampleRate(unsigned sample_rate) const
{
#ifdef ENABLE_DSD
if (dsd_u16)
/* DSD_U16 combines two 8-bit "samples" in one 16-bit
"sample" */
sample_rate /= 2;
if (dsd_u32)
/* DSD_U32 combines four 8-bit "samples" in one 32-bit
"sample" */
sample_rate /= 4;
if (dop)
/* DoP packs two 8-bit "samples" in one 24-bit
"sample" */
sample_rate /= 2;
#endif
return sample_rate;
}
unsigned
PcmExport::Params::CalcInputSampleRate(unsigned sample_rate) const
{
#ifdef ENABLE_DSD
if (dsd_u16)
sample_rate *= 2;
if (dsd_u32)
sample_rate *= 4;
if (dop)
sample_rate *= 2;
#endif
return sample_rate;
}
ConstBuffer<void> ConstBuffer<void>
PcmExport::Export(ConstBuffer<void> data) PcmExport::Export(ConstBuffer<void> data)
{ {
@@ -106,6 +156,11 @@ PcmExport::Export(ConstBuffer<void> data)
alsa_channel_order, channels); alsa_channel_order, channels);
#ifdef ENABLE_DSD #ifdef ENABLE_DSD
if (dsd_u16)
data = Dsd8To16(dop_buffer, channels,
ConstBuffer<uint8_t>::FromVoid(data))
.ToVoid();
if (dsd_u32) if (dsd_u32)
data = Dsd8To32(dop_buffer, channels, data = Dsd8To32(dop_buffer, channels,
ConstBuffer<uint8_t>::FromVoid(data)) ConstBuffer<uint8_t>::FromVoid(data))

View File

@@ -21,28 +21,18 @@
#define PCM_EXPORT_HXX #define PCM_EXPORT_HXX
#include "check.h" #include "check.h"
#include "SampleFormat.hxx"
#include "PcmBuffer.hxx" #include "PcmBuffer.hxx"
#include "AudioFormat.hxx"
template<typename T> struct ConstBuffer; template<typename T> struct ConstBuffer;
struct AudioFormat;
/** /**
* An object that handles export of PCM samples to some instance * An object that handles export of PCM samples to some instance
* outside of MPD. It has a few more options to tweak the binary * outside of MPD. It has a few more options to tweak the binary
* representation which are not supported by the pcm_convert library. * representation which are not supported by the pcm_convert library.
*/ */
struct PcmExport { class PcmExport {
struct Params {
bool alsa_channel_order = false;
#ifdef ENABLE_DSD
bool dsd_u32 = false;
bool dop = false;
#endif
bool shift8 = false;
bool pack24 = false;
bool reverse_endian = false;
};
/** /**
* This buffer is used to reorder channels. * This buffer is used to reorder channels.
* *
@@ -90,6 +80,11 @@ struct PcmExport {
SampleFormat alsa_channel_order; SampleFormat alsa_channel_order;
#ifdef ENABLE_DSD #ifdef ENABLE_DSD
/**
* Convert DSD (U8) to DSD_U16?
*/
bool dsd_u16;
/** /**
* Convert DSD (U8) to DSD_U32? * Convert DSD (U8) to DSD_U32?
*/ */
@@ -121,6 +116,34 @@ struct PcmExport {
*/ */
uint8_t reverse_endian; uint8_t reverse_endian;
public:
struct Params {
bool alsa_channel_order = false;
#ifdef ENABLE_DSD
bool dsd_u16 = false;
bool dsd_u32 = false;
bool dop = false;
#endif
bool shift8 = false;
bool pack24 = false;
bool reverse_endian = false;
/**
* Calculate the output sample rate, given a specific input
* sample rate. Usually, both are the same; however, with
* DSD_U32, four input bytes (= 4 * 8 bits) are combined to
* one output word (32 bits), dividing the sample rate by 4.
*/
gcc_pure
unsigned CalcOutputSampleRate(unsigned input_sample_rate) const;
/**
* The inverse of CalcOutputSampleRate().
*/
gcc_pure
unsigned CalcInputSampleRate(unsigned output_sample_rate) const;
};
/** /**
* Open the object. * Open the object.
* *
@@ -134,6 +157,12 @@ struct PcmExport {
void Open(SampleFormat sample_format, unsigned channels, void Open(SampleFormat sample_format, unsigned channels,
Params params); Params params);
/**
* Reset the filter's state, e.g. drop/flush buffers.
*/
void Reset() {
}
/** /**
* Calculate the size of one output frame. * Calculate the size of one output frame.
*/ */

View File

@@ -20,7 +20,7 @@
#ifndef MPD_PCM_FORMAT_HXX #ifndef MPD_PCM_FORMAT_HXX
#define MPD_PCM_FORMAT_HXX #define MPD_PCM_FORMAT_HXX
#include "AudioFormat.hxx" #include "SampleFormat.hxx"
#include <stdint.h> #include <stdint.h>

Some files were not shown because too many files have changed in this diff Show More