Compare commits
44 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
2ab6c40ff1 | ||
![]() |
68bb738af2 | ||
![]() |
6b968beede | ||
![]() |
f68dd1bffb | ||
![]() |
f92b71ca99 | ||
![]() |
2b79fe2d6a | ||
![]() |
44dd9af276 | ||
![]() |
d3013d4f8c | ||
![]() |
678524ad21 | ||
![]() |
32a64481f2 | ||
![]() |
1776015c6c | ||
![]() |
f1c71a26e3 | ||
![]() |
e78ab767d3 | ||
![]() |
f01eb2f95d | ||
![]() |
1450e45d97 | ||
![]() |
ec8cba369c | ||
![]() |
f4c248f406 | ||
![]() |
f3b2a58646 | ||
![]() |
c6f89c42b2 | ||
![]() |
5e93cfdd9e | ||
![]() |
d91d5a3ab5 | ||
![]() |
907c045f33 | ||
![]() |
90f189eb54 | ||
![]() |
4abd5b2112 | ||
![]() |
df9a665994 | ||
![]() |
7a098ca0ed | ||
![]() |
33716732a1 | ||
![]() |
97ae594375 | ||
![]() |
3f321ae9a0 | ||
![]() |
161d32a7e7 | ||
![]() |
d7137586a9 | ||
![]() |
cd0c06ba6e | ||
![]() |
899ab63d91 | ||
![]() |
1097820a5a | ||
![]() |
39114f91a7 | ||
![]() |
4f01387edf | ||
![]() |
de3e0585f1 | ||
![]() |
f85f25ba82 | ||
![]() |
10a2c179f9 | ||
![]() |
6eea56861b | ||
![]() |
21fd2064ae | ||
![]() |
dcbab8e37a | ||
![]() |
5677278251 | ||
![]() |
a83bee993d |
AUTHORSMakefile.amNEWSconfigure.ac
doc
m4
src
AudioFormat.cxxAudioFormat.hxxAudioParser.cxxCommandLine.cxxDetachedSong.hxxMain.cxxPlaylistFile.cxx
command
db
DatabaseGlue.cxx
plugins
decoder
filter
plugins
input
plugins
java
lib
curl
Easy.hxxGlobal.cxxGlobal.hxxHandler.hxxMulti.hxxRequest.cxxRequest.hxxSlist.hxxVersion.cxxVersion.hxx
upnp
net
AllocatedSocketAddress.cxxAllocatedSocketAddress.hxxSocketAddress.cxxSocketAddress.hxxStaticSocketAddress.cxxStaticSocketAddress.hxxToString.cxxToString.hxx
output
pcm
ChannelsConverter.hxxFormatConverter.hxxOrder.hxxPcmChannels.cxxPcmConvert.cxxPcmExport.cxxPcmExport.hxxPcmFormat.hxxPcmMix.cxxPcmMix.hxxSampleFormat.cxxSampleFormat.hxxSilence.cxxTraits.hxxVolume.cxxVolume.hxx
playlist
plugins
system
thread
util
ASCII.hxxAllocatedArray.hxxAllocatedString.cxxAllocatedString.hxxBindMethod.hxxCast.hxxCharUtil.hxxCircularBuffer.hxxClamp.hxxConstBuffer.hxxDeleteDisposer.hxxDomain.hxxDynamicFifoBuffer.hxxException.cxxException.hxxForeignFifoBuffer.hxxHugeAllocator.cxxHugeAllocator.hxxIterableSplitString.hxxMacros.hxxManual.hxxNumberParser.hxxReusableArray.hxxRuntimeError.hxxScopeExit.hxxSplitString.cxxSplitString.hxxStaticFifoBuffer.hxxStringAPI.hxxStringBuffer.hxxStringCompare.cxxStringCompare.hxxStringPointer.hxxStringView.cxxStringView.hxxTextFile.hxxTimeParser.cxxTimeParser.hxxTokenizer.cxxTokenizer.hxxUTF8.cxxUTF8.hxxVarSize.hxxWStringAPI.hxxWStringCompare.hxxWritableBuffer.hxx
test
2
AUTHORS
2
AUTHORS
@@ -5,7 +5,7 @@ The following people have contributed code to MPD:
|
||||
|
||||
Warren Dukes <warren.dukes@gmail.com>
|
||||
Avuton Olrich <avuton@gmail.com>
|
||||
Max Kellermann <max@duempel.org>
|
||||
Max Kellermann <max.kellermann@gmail.com>
|
||||
Laszlo Ashin <kodest@gmail.com>
|
||||
Viliam Mateicka <viliam.mateicka@gmail.com>
|
||||
Eric Wollesen <encoded@xmtp.net>
|
||||
|
12
Makefile.am
12
Makefile.am
@@ -415,6 +415,7 @@ libutil_a_SOURCES = \
|
||||
src/util/CharUtil.hxx \
|
||||
src/util/NumberParser.hxx \
|
||||
src/util/MimeType.cxx src/util/MimeType.hxx \
|
||||
src/util/StringBuffer.hxx \
|
||||
src/util/StringPointer.hxx \
|
||||
src/util/StringView.cxx src/util/StringView.hxx \
|
||||
src/util/AllocatedString.cxx src/util/AllocatedString.hxx \
|
||||
@@ -539,6 +540,10 @@ ICU_LDADD = libicu.a $(ICU_LIBS)
|
||||
# PCM library
|
||||
|
||||
libpcm_a_SOURCES = \
|
||||
src/CheckAudioFormat.cxx src/CheckAudioFormat.hxx \
|
||||
src/AudioFormat.cxx src/AudioFormat.hxx \
|
||||
src/AudioParser.cxx src/AudioParser.hxx \
|
||||
src/pcm/SampleFormat.cxx src/pcm/SampleFormat.hxx \
|
||||
src/pcm/Traits.hxx \
|
||||
src/pcm/Interleave.cxx src/pcm/Interleave.hxx \
|
||||
src/pcm/PcmBuffer.cxx src/pcm/PcmBuffer.hxx \
|
||||
@@ -615,7 +620,8 @@ libxiph_a_SOURCES += \
|
||||
src/lib/xiph/OggStreamState.hxx
|
||||
endif
|
||||
|
||||
XIPH_LIBS = libxiph.a
|
||||
XIPH_LIBS = libxiph.a \
|
||||
$(OGG_LIBS)
|
||||
|
||||
endif
|
||||
|
||||
@@ -871,9 +877,6 @@ ARCHIVE_LIBS =
|
||||
endif
|
||||
|
||||
libbasic_a_SOURCES = \
|
||||
src/CheckAudioFormat.cxx src/CheckAudioFormat.hxx \
|
||||
src/AudioFormat.cxx src/AudioFormat.hxx \
|
||||
src/AudioParser.cxx src/AudioParser.hxx \
|
||||
src/ReplayGainConfig.hxx \
|
||||
src/ReplayGainMode.cxx src/ReplayGainMode.hxx \
|
||||
src/ReplayGainInfo.cxx src/ReplayGainInfo.hxx
|
||||
@@ -2227,6 +2230,7 @@ test_test_icy_parser_LDADD = \
|
||||
endif
|
||||
|
||||
test_test_pcm_SOURCES = \
|
||||
test/TestAudioFormat.cxx test/TestAudioFormat.hxx \
|
||||
test/test_pcm_util.hxx \
|
||||
test/test_pcm_dither.cxx \
|
||||
test/test_pcm_pack.cxx \
|
||||
|
12
NEWS
12
NEWS
@@ -1,3 +1,15 @@
|
||||
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
|
||||
|
@@ -1,10 +1,10 @@
|
||||
AC_PREREQ(2.60)
|
||||
|
||||
AC_INIT(mpd, 0.20.2, musicpd-dev-team@lists.sourceforge.net)
|
||||
AC_INIT(mpd, 0.20.3, musicpd-dev-team@lists.sourceforge.net)
|
||||
|
||||
VERSION_MAJOR=0
|
||||
VERSION_MINOR=20
|
||||
VERSION_REVISION=1
|
||||
VERSION_REVISION=3
|
||||
VERSION_EXTRA=0
|
||||
|
||||
AC_CONFIG_SRCDIR([src/Main.cxx])
|
||||
|
@@ -50,6 +50,6 @@ If you find a bug, please report it at
|
||||
.br
|
||||
<\fBhttp://bugs.musicpd.org/bug_report_page.php\fP>.
|
||||
.SH AUTHORS
|
||||
Max Kellermann <max@duempel.org>
|
||||
Max Kellermann <max.kellermann@gmail.com>
|
||||
|
||||
Special thanks to all the people that provided feedback and patches.
|
||||
|
@@ -573,7 +573,12 @@
|
||||
<listitem>
|
||||
<para>
|
||||
<varname>audio</varname>:
|
||||
<returnvalue>sampleRate:bits:channels</returnvalue>
|
||||
<returnvalue>
|
||||
The format emitted by the decoder plugin during
|
||||
playback, format:
|
||||
"<replaceable>samplerate:bits:channels</replaceable>".
|
||||
Check the user manual for a detailed explanation.
|
||||
</returnvalue>
|
||||
</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
|
103
doc/user.xml
103
doc/user.xml
@@ -576,10 +576,11 @@ systemctl start mpd.socket</programlisting>
|
||||
</entry>
|
||||
<entry>
|
||||
<para>
|
||||
Always open the audio output with the specified audio
|
||||
format (samplerate:bits:channels), regardless of the
|
||||
format of the input file. This is optional for most
|
||||
plugins.
|
||||
Always open the audio output with the specified
|
||||
audio format
|
||||
(<replaceable>samplerate:bits:channels</replaceable>),
|
||||
regardless of the format of the input file. This is
|
||||
optional for most plugins.
|
||||
</para>
|
||||
<para>
|
||||
Any of the three attributes may be an asterisk to
|
||||
@@ -596,7 +597,20 @@ systemctl start mpd.socket</programlisting>
|
||||
24 bit integer samples padded to 32 bit),
|
||||
<varname>32</varname> (signed 32 bit integer
|
||||
samples), <varname>f</varname> (32 bit floating
|
||||
point, -1.0 to 1.0).
|
||||
point, -1.0 to 1.0), "<varname>dsd</varname>" means
|
||||
DSD (Direct Stream Digital). For DSD, there are
|
||||
special cases such as "<varname>dsd64</varname>",
|
||||
which allows you to omit the sample rate
|
||||
(e.g. <parameter>dsd512:2</parameter> for stereo
|
||||
DSD512, i.e. 22.5792 MHz).
|
||||
</para>
|
||||
<para>
|
||||
The sample rate is special for DSD:
|
||||
<application>MPD</application> counts the number of
|
||||
bytes, not bits. Thus, a DSD "bit" rate of 22.5792
|
||||
MHz (DSD512) is 2822400 from
|
||||
<application>MPD</application>'s point of view
|
||||
(44100*512/8).
|
||||
</para>
|
||||
</entry>
|
||||
</row>
|
||||
@@ -742,9 +756,11 @@ systemctl start mpd.socket</programlisting>
|
||||
<title>Configuring playlist plugins</title>
|
||||
|
||||
<para>
|
||||
Playlist plugins are used to load remote playlists. This is
|
||||
not related to <application>MPD</application>'s playlist
|
||||
directory.
|
||||
Playlist plugins are used to load remote playlists (protocol
|
||||
commands <command>load</command>,
|
||||
<command>listplaylist</command> and
|
||||
<command>listplaylistinfo</command>). This is not related to
|
||||
<application>MPD</application>'s playlist directory.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
@@ -1800,6 +1816,13 @@ run</programlisting>
|
||||
database.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Note that unless overridden by the below settings (e.g. by
|
||||
setting them to a blank value), general curl configuration
|
||||
from environment variables such as http_proxy or specified
|
||||
in ~/.curlrc will be in effect.
|
||||
</para>
|
||||
|
||||
<informaltable>
|
||||
<tgroup cols="2">
|
||||
<thead>
|
||||
@@ -4193,6 +4216,22 @@ run</programlisting>
|
||||
<section id="playlist_plugins">
|
||||
<title>Playlist plugins</title>
|
||||
|
||||
<section>
|
||||
<title><varname>asx</varname></title>
|
||||
|
||||
<para>
|
||||
Reads <filename>.asx</filename> playlist files.
|
||||
</para>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<title><varname>cue</varname></title>
|
||||
|
||||
<para>
|
||||
Reads <filename>.cue</filename> files.
|
||||
</para>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<title><varname>embcue</varname></title>
|
||||
|
||||
@@ -4217,6 +4256,15 @@ run</programlisting>
|
||||
</para>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<title><varname>flac</varname></title>
|
||||
|
||||
<para>
|
||||
Reads the <varname>cuesheet</varname> metablock from a FLAC
|
||||
file.
|
||||
</para>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<title><varname>pls</varname></title>
|
||||
|
||||
@@ -4225,6 +4273,45 @@ run</programlisting>
|
||||
</para>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<title><varname>rss</varname></title>
|
||||
|
||||
<para>
|
||||
Reads music links from <filename>.rss</filename> files.
|
||||
</para>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<title><varname>soundcloud</varname></title>
|
||||
|
||||
<para>
|
||||
Download playlist from SoundCloud. It accepts URIs starting
|
||||
with <filename>soundcloud://</filename>.
|
||||
</para>
|
||||
|
||||
<informaltable>
|
||||
<tgroup cols="2">
|
||||
<thead>
|
||||
<row>
|
||||
<entry>Setting</entry>
|
||||
<entry>Description</entry>
|
||||
</row>
|
||||
</thead>
|
||||
<tbody>
|
||||
<row>
|
||||
<entry>
|
||||
<varname>apikey</varname>
|
||||
<parameter>KEY</parameter>
|
||||
</entry>
|
||||
<entry>
|
||||
An API key to access the SoundCloud servers.
|
||||
</entry>
|
||||
</row>
|
||||
</tbody>
|
||||
</tgroup>
|
||||
</informaltable>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<title><varname>xspf</varname></title>
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
# Check if "struct ucred" is available.
|
||||
#
|
||||
# Author: Max Kellermann <max@duempel.org>
|
||||
# Author: Max Kellermann <max.kellermann@gmail.com>
|
||||
|
||||
AC_DEFUN([STRUCT_UCRED],[
|
||||
AC_MSG_CHECKING([for struct ucred])
|
||||
|
@@ -18,6 +18,7 @@
|
||||
*/
|
||||
|
||||
#include "AudioFormat.hxx"
|
||||
#include "util/StringBuffer.hxx"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
@@ -40,46 +41,24 @@ AudioFormat::ApplyMask(AudioFormat mask)
|
||||
assert(IsValid());
|
||||
}
|
||||
|
||||
const char *
|
||||
sample_format_to_string(SampleFormat format)
|
||||
StringBuffer<24>
|
||||
ToString(const AudioFormat af)
|
||||
{
|
||||
switch (format) {
|
||||
case SampleFormat::UNDEFINED:
|
||||
return "?";
|
||||
StringBuffer<24> buffer;
|
||||
|
||||
case SampleFormat::S8:
|
||||
return "8";
|
||||
|
||||
case SampleFormat::S16:
|
||||
return "16";
|
||||
|
||||
case SampleFormat::S24_P32:
|
||||
return "24";
|
||||
|
||||
case SampleFormat::S32:
|
||||
return "32";
|
||||
|
||||
case SampleFormat::FLOAT:
|
||||
return "f";
|
||||
|
||||
case SampleFormat::DSD:
|
||||
return "dsd";
|
||||
if (af.format == SampleFormat::DSD && af.sample_rate > 0 &&
|
||||
af.sample_rate % 44100 == 0) {
|
||||
/* use shortcuts such as "dsd64" which implies the
|
||||
sample rate */
|
||||
snprintf(buffer.data(), buffer.capacity(), "dsd%u:%u",
|
||||
af.sample_rate * 8 / 44100,
|
||||
af.channels);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
/* unreachable */
|
||||
assert(false);
|
||||
gcc_unreachable();
|
||||
}
|
||||
|
||||
const char *
|
||||
audio_format_to_string(const AudioFormat af,
|
||||
struct audio_format_string *s)
|
||||
{
|
||||
assert(s != nullptr);
|
||||
|
||||
snprintf(s->buffer, sizeof(s->buffer), "%u:%s:%u",
|
||||
snprintf(buffer.data(), buffer.capacity(), "%u:%s:%u",
|
||||
af.sample_rate, sample_format_to_string(af.format),
|
||||
af.channels);
|
||||
|
||||
return s->buffer;
|
||||
return buffer;
|
||||
}
|
||||
|
@@ -20,47 +20,14 @@
|
||||
#ifndef MPD_AUDIO_FORMAT_HXX
|
||||
#define MPD_AUDIO_FORMAT_HXX
|
||||
|
||||
#include "pcm/SampleFormat.hxx"
|
||||
#include "Compiler.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <assert.h>
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#if defined(WIN32) && GCC_CHECK_VERSION(4,6)
|
||||
/* on WIN32, "FLOAT" is already defined, and this triggers -Wshadow */
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wshadow"
|
||||
#endif
|
||||
|
||||
enum class SampleFormat : uint8_t {
|
||||
UNDEFINED = 0,
|
||||
|
||||
S8,
|
||||
S16,
|
||||
|
||||
/**
|
||||
* Signed 24 bit integer samples, packed in 32 bit integers
|
||||
* (the most significant byte is filled with the sign bit).
|
||||
*/
|
||||
S24_P32,
|
||||
|
||||
S32,
|
||||
|
||||
/**
|
||||
* 32 bit floating point samples in the host's format. The
|
||||
* range is -1.0f to +1.0f.
|
||||
*/
|
||||
FLOAT,
|
||||
|
||||
/**
|
||||
* Direct Stream Digital. 1-bit samples; each frame has one
|
||||
* byte (8 samples) per channel.
|
||||
*/
|
||||
DSD,
|
||||
};
|
||||
|
||||
#if defined(WIN32) && GCC_CHECK_VERSION(4,6)
|
||||
#pragma GCC diagnostic pop
|
||||
#endif
|
||||
template<size_t CAPACITY> class StringBuffer;
|
||||
|
||||
static constexpr unsigned MAX_CHANNELS = 8;
|
||||
|
||||
@@ -183,13 +150,6 @@ struct AudioFormat {
|
||||
double GetTimeToSize() const;
|
||||
};
|
||||
|
||||
/**
|
||||
* Buffer for audio_format_string().
|
||||
*/
|
||||
struct audio_format_string {
|
||||
char buffer[24];
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks whether the sample rate is valid.
|
||||
*
|
||||
@@ -201,28 +161,6 @@ audio_valid_sample_rate(unsigned sample_rate)
|
||||
return sample_rate > 0 && sample_rate < (1 << 30);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the sample format is valid.
|
||||
*/
|
||||
static inline bool
|
||||
audio_valid_sample_format(SampleFormat format)
|
||||
{
|
||||
switch (format) {
|
||||
case SampleFormat::S8:
|
||||
case SampleFormat::S16:
|
||||
case SampleFormat::S24_P32:
|
||||
case SampleFormat::S32:
|
||||
case SampleFormat::FLOAT:
|
||||
case SampleFormat::DSD:
|
||||
return true;
|
||||
|
||||
case SampleFormat::UNDEFINED:
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the number of channels is valid.
|
||||
*/
|
||||
@@ -258,34 +196,6 @@ AudioFormat::IsMaskValid() const
|
||||
(channels == 0 || audio_valid_channel_count(channels));
|
||||
}
|
||||
|
||||
gcc_const
|
||||
static inline unsigned
|
||||
sample_format_size(SampleFormat format)
|
||||
{
|
||||
switch (format) {
|
||||
case SampleFormat::S8:
|
||||
return 1;
|
||||
|
||||
case SampleFormat::S16:
|
||||
return 2;
|
||||
|
||||
case SampleFormat::S24_P32:
|
||||
case SampleFormat::S32:
|
||||
case SampleFormat::FLOAT:
|
||||
return 4;
|
||||
|
||||
case SampleFormat::DSD:
|
||||
/* each frame has 8 samples per channel */
|
||||
return 1;
|
||||
|
||||
case SampleFormat::UNDEFINED:
|
||||
return 0;
|
||||
}
|
||||
|
||||
assert(false);
|
||||
gcc_unreachable();
|
||||
}
|
||||
|
||||
inline unsigned
|
||||
AudioFormat::GetSampleSize() const
|
||||
{
|
||||
@@ -304,28 +214,15 @@ AudioFormat::GetTimeToSize() const
|
||||
return sample_rate * GetFrameSize();
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a #SampleFormat enum into a string, e.g. for printing it
|
||||
* in a log file.
|
||||
*
|
||||
* @param format a #SampleFormat enum value
|
||||
* @return the string
|
||||
*/
|
||||
gcc_pure gcc_malloc
|
||||
const char *
|
||||
sample_format_to_string(SampleFormat format);
|
||||
|
||||
/**
|
||||
* Renders the #AudioFormat object into a string, e.g. for printing
|
||||
* it in a log file.
|
||||
*
|
||||
* @param af the #AudioFormat object
|
||||
* @param s a buffer to print into
|
||||
* @return the string, or nullptr if the #AudioFormat object is invalid
|
||||
* @return the string buffer
|
||||
*/
|
||||
gcc_pure gcc_malloc
|
||||
const char *
|
||||
audio_format_to_string(AudioFormat af,
|
||||
struct audio_format_string *s);
|
||||
gcc_const
|
||||
StringBuffer<24>
|
||||
ToString(AudioFormat af);
|
||||
|
||||
#endif
|
||||
|
@@ -137,6 +137,26 @@ ParseAudioFormat(const char *src, bool mask)
|
||||
AudioFormat dest;
|
||||
dest.Clear();
|
||||
|
||||
if (strncmp(src, "dsd", 3) == 0) {
|
||||
/* allow format specifications such as "dsd64" which
|
||||
implies the sample rate */
|
||||
|
||||
char *endptr;
|
||||
auto dsd = strtoul(src + 3, &endptr, 10);
|
||||
if (endptr > src + 3 && *endptr == ':' &&
|
||||
dsd >= 32 && dsd <= 4096 && dsd % 2 == 0) {
|
||||
dest.sample_rate = dsd * 44100 / 8;
|
||||
dest.format = SampleFormat::DSD;
|
||||
|
||||
src = endptr + 1;
|
||||
dest.channels = ParseChannelCount(src, mask, &src);
|
||||
if (*src != 0)
|
||||
throw FormatRuntimeError("Extra data after channel count: %s", src);
|
||||
|
||||
return dest;
|
||||
}
|
||||
}
|
||||
|
||||
/* parse sample rate */
|
||||
|
||||
dest.sample_rate = ParseSampleRate(src, mask, &src);
|
||||
|
@@ -107,7 +107,7 @@ static void version(void)
|
||||
"\n"
|
||||
"\n"
|
||||
"Copyright (C) 2003-2007 Warren Dukes <warren.dukes@gmail.com>\n"
|
||||
"Copyright (C) 2008-2015 Max Kellermann <max@duempel.org>\n"
|
||||
"Copyright 2008-2017 Max Kellermann <max.kellermann@gmail.com>\n"
|
||||
"This is free software; see the source for copying conditions. There is NO\n"
|
||||
"warranty; not even MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
|
||||
|
||||
|
@@ -63,18 +63,18 @@ class DetachedSong {
|
||||
|
||||
Tag tag;
|
||||
|
||||
time_t mtime;
|
||||
time_t mtime = 0;
|
||||
|
||||
/**
|
||||
* Start of this sub-song within the file.
|
||||
*/
|
||||
SongTime start_time;
|
||||
SongTime start_time = SongTime::zero();
|
||||
|
||||
/**
|
||||
* End of this sub-song within the file.
|
||||
* Unused if zero.
|
||||
*/
|
||||
SongTime end_time;
|
||||
SongTime end_time = SongTime::zero();
|
||||
|
||||
explicit DetachedSong(const LightSong &other);
|
||||
|
||||
@@ -82,26 +82,18 @@ public:
|
||||
explicit DetachedSong(const DetachedSong &) = default;
|
||||
|
||||
explicit DetachedSong(const char *_uri)
|
||||
:uri(_uri),
|
||||
mtime(0),
|
||||
start_time(SongTime::zero()), end_time(SongTime::zero()) {}
|
||||
:uri(_uri) {}
|
||||
|
||||
explicit DetachedSong(const std::string &_uri)
|
||||
:uri(_uri),
|
||||
mtime(0),
|
||||
start_time(SongTime::zero()), end_time(SongTime::zero()) {}
|
||||
:uri(_uri) {}
|
||||
|
||||
explicit DetachedSong(std::string &&_uri)
|
||||
:uri(std::move(_uri)),
|
||||
mtime(0),
|
||||
start_time(SongTime::zero()), end_time(SongTime::zero()) {}
|
||||
:uri(std::move(_uri)) {}
|
||||
|
||||
template<typename U>
|
||||
DetachedSong(U &&_uri, Tag &&_tag)
|
||||
:uri(std::forward<U>(_uri)),
|
||||
tag(std::move(_tag)),
|
||||
mtime(0),
|
||||
start_time(SongTime::zero()), end_time(SongTime::zero()) {}
|
||||
tag(std::move(_tag)) {}
|
||||
|
||||
DetachedSong(DetachedSong &&) = default;
|
||||
|
||||
|
@@ -203,7 +203,11 @@ glue_db_init_and_load(void)
|
||||
"because the database does not need it");
|
||||
}
|
||||
|
||||
instance->database->Open();
|
||||
try {
|
||||
instance->database->Open();
|
||||
} catch (...) {
|
||||
std::throw_with_nested(std::runtime_error("Failed to open database plugin"));
|
||||
}
|
||||
|
||||
if (!instance->database->IsPlugin(simple_db_plugin))
|
||||
return true;
|
||||
|
@@ -335,7 +335,7 @@ try {
|
||||
const auto path_fs = spl_map_to_fs(utf8path);
|
||||
assert(!path_fs.IsNull());
|
||||
|
||||
FileOutputStream fos(path_fs, FileOutputStream::Mode::APPEND_EXISTING);
|
||||
FileOutputStream fos(path_fs, FileOutputStream::Mode::APPEND_OR_CREATE);
|
||||
|
||||
if (fos.Tell() / (MPD_PATH_MAX + 1) >= playlist_max_length)
|
||||
throw PlaylistError(PlaylistResult::TOO_LARGE,
|
||||
|
@@ -30,6 +30,7 @@
|
||||
#include "Instance.hxx"
|
||||
#include "Idle.hxx"
|
||||
#include "AudioFormat.hxx"
|
||||
#include "util/StringBuffer.hxx"
|
||||
#include "util/ScopeExit.hxx"
|
||||
#include "util/Exception.hxx"
|
||||
|
||||
@@ -171,13 +172,9 @@ handle_status(Client &client, gcc_unused Request args, Response &r)
|
||||
r.Format("duration: %1.3f\n",
|
||||
player_status.total_time.ToDoubleS());
|
||||
|
||||
if (player_status.audio_format.IsDefined()) {
|
||||
struct audio_format_string af_string;
|
||||
|
||||
if (player_status.audio_format.IsDefined())
|
||||
r.Format(COMMAND_STATUS_AUDIO ": %s\n",
|
||||
audio_format_to_string(player_status.audio_format,
|
||||
&af_string));
|
||||
}
|
||||
ToString(player_status.audio_format).c_str());
|
||||
}
|
||||
|
||||
#ifdef ENABLE_DATABASE
|
||||
|
@@ -37,5 +37,10 @@ DatabaseGlobalInit(EventLoop &loop, DatabaseListener &listener,
|
||||
throw FormatRuntimeError("No such database plugin: %s",
|
||||
plugin_name);
|
||||
|
||||
return plugin->create(loop, listener, block);
|
||||
try {
|
||||
return plugin->create(loop, listener, block);
|
||||
} catch (...) {
|
||||
std::throw_with_nested(FormatRuntimeError("Failed to initialize database plugin '%s'",
|
||||
plugin_name));
|
||||
}
|
||||
}
|
||||
|
@@ -34,6 +34,7 @@
|
||||
#include "tag/TagBuilder.hxx"
|
||||
#include "tag/Tag.hxx"
|
||||
#include "util/ScopeExit.hxx"
|
||||
#include "util/RuntimeError.hxx"
|
||||
#include "protocol/Ack.hxx"
|
||||
#include "event/SocketMonitor.hxx"
|
||||
#include "event/IdleMonitor.hxx"
|
||||
@@ -46,7 +47,7 @@
|
||||
#include <string>
|
||||
#include <list>
|
||||
|
||||
class LibmpdclientError final : std::runtime_error {
|
||||
class LibmpdclientError final : public std::runtime_error {
|
||||
enum mpd_error code;
|
||||
|
||||
public:
|
||||
@@ -108,8 +109,8 @@ public:
|
||||
static Database *Create(EventLoop &loop, DatabaseListener &listener,
|
||||
const ConfigBlock &block);
|
||||
|
||||
virtual void Open() override;
|
||||
virtual void Close() override;
|
||||
void Open() override;
|
||||
void Close() override;
|
||||
const LightSong *GetSong(const char *uri_utf8) const override;
|
||||
void ReturnSong(const LightSong *song) const override;
|
||||
|
||||
@@ -126,7 +127,7 @@ public:
|
||||
|
||||
unsigned Update(const char *uri_utf8, bool discard) override;
|
||||
|
||||
virtual time_t GetUpdateStamp() const override {
|
||||
time_t GetUpdateStamp() const override {
|
||||
return update_stamp;
|
||||
}
|
||||
|
||||
@@ -138,10 +139,10 @@ private:
|
||||
void Disconnect();
|
||||
|
||||
/* virtual methods from SocketMonitor */
|
||||
virtual bool OnSocketReady(unsigned flags) override;
|
||||
bool OnSocketReady(unsigned flags) override;
|
||||
|
||||
/* virtual methods from IdleMonitor */
|
||||
virtual void OnIdle() override;
|
||||
void OnIdle() override;
|
||||
};
|
||||
|
||||
static constexpr struct {
|
||||
@@ -345,9 +346,15 @@ ProxyDatabase::Create(EventLoop &loop, DatabaseListener &listener,
|
||||
void
|
||||
ProxyDatabase::Open()
|
||||
{
|
||||
Connect();
|
||||
|
||||
update_stamp = 0;
|
||||
|
||||
try {
|
||||
Connect();
|
||||
} catch (const std::runtime_error &error) {
|
||||
/* this error is non-fatal, because this plugin will
|
||||
attempt to reconnect again automatically */
|
||||
LogError(error);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
@@ -371,7 +378,10 @@ ProxyDatabase::Connect()
|
||||
mpd_connection_free(connection);
|
||||
connection = nullptr;
|
||||
|
||||
throw;
|
||||
std::throw_with_nested(host.empty()
|
||||
? std::runtime_error("Failed to connect to remote MPD")
|
||||
: FormatRuntimeError("Failed to connect to remote MPD '%s'",
|
||||
host.c_str()));
|
||||
}
|
||||
|
||||
#if LIBMPDCLIENT_CHECK_VERSION(2, 10, 0)
|
||||
|
@@ -108,8 +108,8 @@ public:
|
||||
bool Unmount(const char *uri);
|
||||
|
||||
/* virtual methods from class Database */
|
||||
virtual void Open() override;
|
||||
virtual void Close() override;
|
||||
void Open() override;
|
||||
void Close() override;
|
||||
|
||||
const LightSong *GetSong(const char *uri_utf8) const override;
|
||||
void ReturnSong(const LightSong *song) const override;
|
||||
@@ -125,7 +125,7 @@ public:
|
||||
|
||||
DatabaseStats GetStats(const DatabaseSelection &selection) const override;
|
||||
|
||||
virtual time_t GetUpdateStamp() const override {
|
||||
time_t GetUpdateStamp() const override {
|
||||
return mtime;
|
||||
}
|
||||
|
||||
|
@@ -180,7 +180,7 @@ UpnpDatabase::ReturnSong(const LightSong *_song) const
|
||||
const LightSong *
|
||||
UpnpDatabase::GetSong(const char *uri) const
|
||||
{
|
||||
auto vpath = stringToTokens(uri, "/", true);
|
||||
auto vpath = stringToTokens(uri, '/');
|
||||
if (vpath.size() < 2)
|
||||
throw DatabaseError(DatabaseErrorCode::NOT_FOUND,
|
||||
"No such song");
|
||||
@@ -577,7 +577,7 @@ UpnpDatabase::Visit(const DatabaseSelection &selection,
|
||||
VisitSong visit_song,
|
||||
VisitPlaylist visit_playlist) const
|
||||
{
|
||||
auto vpath = stringToTokens(selection.uri, "/", true);
|
||||
auto vpath = stringToTokens(selection.uri, '/');
|
||||
if (vpath.empty()) {
|
||||
for (const auto &server : discovery->GetDirectories()) {
|
||||
if (visit_directory) {
|
||||
|
@@ -32,6 +32,7 @@
|
||||
#include "Log.hxx"
|
||||
#include "input/InputStream.hxx"
|
||||
#include "util/ConstBuffer.hxx"
|
||||
#include "util/StringBuffer.hxx"
|
||||
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
@@ -246,15 +247,13 @@ void
|
||||
DecoderBridge::Ready(const AudioFormat audio_format,
|
||||
bool seekable, SignedSongTime duration)
|
||||
{
|
||||
struct audio_format_string af_string;
|
||||
|
||||
assert(convert == nullptr);
|
||||
assert(stream_tag == nullptr);
|
||||
assert(decoder_tag == nullptr);
|
||||
assert(!seeking);
|
||||
|
||||
FormatDebug(decoder_domain, "audio_format=%s, seekable=%s",
|
||||
audio_format_to_string(audio_format, &af_string),
|
||||
ToString(audio_format).c_str(),
|
||||
seekable ? "true" : "false");
|
||||
|
||||
{
|
||||
@@ -264,8 +263,7 @@ DecoderBridge::Ready(const AudioFormat audio_format,
|
||||
|
||||
if (dc.in_audio_format != dc.out_audio_format) {
|
||||
FormatDebug(decoder_domain, "converting to %s",
|
||||
audio_format_to_string(dc.out_audio_format,
|
||||
&af_string));
|
||||
ToString(dc.out_audio_format).c_str());
|
||||
|
||||
convert = new PcmConvert();
|
||||
|
||||
|
@@ -24,6 +24,7 @@
|
||||
#include "filter/FilterRegistry.hxx"
|
||||
#include "AudioFormat.hxx"
|
||||
#include "util/ConstBuffer.hxx"
|
||||
#include "util/StringBuffer.hxx"
|
||||
#include "util/RuntimeError.hxx"
|
||||
|
||||
#include <memory>
|
||||
@@ -108,10 +109,9 @@ PreparedChainFilter::Child::Open(const AudioFormat &prev_audio_format)
|
||||
if (conv_audio_format != prev_audio_format) {
|
||||
delete new_filter;
|
||||
|
||||
struct audio_format_string s;
|
||||
throw FormatRuntimeError("Audio format not supported by filter '%s': %s",
|
||||
name,
|
||||
audio_format_to_string(prev_audio_format, &s));
|
||||
ToString(prev_audio_format).c_str());
|
||||
}
|
||||
|
||||
return new_filter;
|
||||
|
@@ -33,7 +33,6 @@
|
||||
#include "util/RuntimeError.hxx"
|
||||
#include "util/StringCompare.hxx"
|
||||
#include "util/ReusableArray.hxx"
|
||||
#include "util/ScopeExit.hxx"
|
||||
|
||||
#include "Log.hxx"
|
||||
#include "event/MultiSocketMonitor.hxx"
|
||||
@@ -287,13 +286,7 @@ ConfigureCapture(snd_pcm_t *capture_handle,
|
||||
int err;
|
||||
|
||||
snd_pcm_hw_params_t *hw_params;
|
||||
if ((err = snd_pcm_hw_params_malloc(&hw_params)) < 0)
|
||||
throw FormatRuntimeError("Cannot allocate hardware parameter structure (%s)",
|
||||
snd_strerror(err));
|
||||
|
||||
AtScopeExit(hw_params) {
|
||||
snd_pcm_hw_params_free(hw_params);
|
||||
};
|
||||
snd_pcm_hw_params_alloca(&hw_params);
|
||||
|
||||
if ((err = snd_pcm_hw_params_any(capture_handle, hw_params)) < 0)
|
||||
throw FormatRuntimeError("Cannot initialize hardware parameter structure (%s)",
|
||||
@@ -373,14 +366,10 @@ ConfigureCapture(snd_pcm_t *capture_handle,
|
||||
(unsigned)alsa_buffer_size, (unsigned)alsa_period_size);
|
||||
|
||||
snd_pcm_sw_params_t *sw_params;
|
||||
snd_pcm_sw_params_alloca(&sw_params);
|
||||
|
||||
snd_pcm_sw_params_malloc(&sw_params);
|
||||
snd_pcm_sw_params_current(capture_handle, sw_params);
|
||||
|
||||
AtScopeExit(sw_params) {
|
||||
snd_pcm_sw_params_free(sw_params);
|
||||
};
|
||||
|
||||
if ((err = snd_pcm_sw_params(capture_handle, sw_params)) < 0)
|
||||
throw FormatRuntimeError("unable to install sw params (%s)",
|
||||
snd_strerror(err));
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2010-2011 Max Kellermann <max@duempel.org>
|
||||
* Copyright (C) 2010-2011 Max Kellermann <max.kellermann@gmail.com>
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2010-2012 Max Kellermann <max@duempel.org>
|
||||
* Copyright (C) 2010-2012 Max Kellermann <max.kellermann@gmail.com>
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2010-2014 Max Kellermann <max@duempel.org>
|
||||
* Copyright (C) 2010-2014 Max Kellermann <max.kellermann@gmail.com>
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2010-2014 Max Kellermann <max@duempel.org>
|
||||
* Copyright (C) 2010-2014 Max Kellermann <max.kellermann@gmail.com>
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2010-2011 Max Kellermann <max@duempel.org>
|
||||
* Copyright (C) 2010-2011 Max Kellermann <max.kellermann@gmail.com>
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2010-2011 Max Kellermann <max@duempel.org>
|
||||
* Copyright (C) 2010-2011 Max Kellermann <max.kellermann@gmail.com>
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2010-2011 Max Kellermann <max@duempel.org>
|
||||
* Copyright (C) 2010-2011 Max Kellermann <max.kellermann@gmail.com>
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2010-2011 Max Kellermann <max@duempel.org>
|
||||
* Copyright (C) 2010-2011 Max Kellermann <max.kellermann@gmail.com>
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2010-2011 Max Kellermann <max@duempel.org>
|
||||
* Copyright (C) 2010-2011 Max Kellermann <max.kellermann@gmail.com>
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2010-2011 Max Kellermann <max@duempel.org>
|
||||
* Copyright (C) 2010-2011 Max Kellermann <max.kellermann@gmail.com>
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Max Kellermann <max@duempel.org>
|
||||
* Copyright (C) 2016 Max Kellermann <max.kellermann@gmail.com>
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2008-2016 Max Kellermann <max@duempel.org>
|
||||
* Copyright (C) 2008-2016 Max Kellermann <max.kellermann@gmail.com>
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2008-2016 Max Kellermann <max@duempel.org>
|
||||
* Copyright (C) 2008-2016 Max Kellermann <max.kellermann@gmail.com>
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2008-2017 Max Kellermann <max@duempel.org>
|
||||
* Copyright (C) 2008-2017 Max Kellermann <max.kellermann@gmail.com>
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Max Kellermann <max@duempel.org>
|
||||
* Copyright (C) 2016 Max Kellermann <max.kellermann@gmail.com>
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2008-2017 Max Kellermann <max@duempel.org>
|
||||
* Copyright (C) 2008-2017 Max Kellermann <max.kellermann@gmail.com>
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2008-2017 Max Kellermann <max@duempel.org>
|
||||
* Copyright (C) 2008-2017 Max Kellermann <max.kellermann@gmail.com>
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2008-2017 Max Kellermann <max@duempel.org>
|
||||
* Copyright (C) 2008-2017 Max Kellermann <max.kellermann@gmail.com>
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Max Kellermann <max@duempel.org>
|
||||
* Copyright (C) 2017 Max Kellermann <max.kellermann@gmail.com>
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Max Kellermann <max@duempel.org>
|
||||
* Copyright (C) 2017 Max Kellermann <max.kellermann@gmail.com>
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
|
@@ -62,7 +62,7 @@ class UPnPDeviceDirectory final : UpnpCallback {
|
||||
DiscoveredTask(const Upnp_Discovery *disco)
|
||||
:url(disco->Location),
|
||||
device_id(disco->DeviceId),
|
||||
expires(disco->Expires) {}
|
||||
expires(std::chrono::seconds(disco->Expires)) {}
|
||||
};
|
||||
|
||||
/**
|
||||
|
@@ -71,20 +71,19 @@ path_getfather(const std::string &s)
|
||||
|
||||
std::list<std::string>
|
||||
stringToTokens(const std::string &str,
|
||||
const char *delims, bool skipinit)
|
||||
const char delim)
|
||||
{
|
||||
std::list<std::string> tokens;
|
||||
|
||||
std::string::size_type startPos = 0;
|
||||
std::string::size_type startPos = str.find_first_not_of(delim, 0);
|
||||
|
||||
// Skip initial delims, return empty if this eats all.
|
||||
if (skipinit &&
|
||||
(startPos = str.find_first_not_of(delims, 0)) == std::string::npos)
|
||||
if (startPos == std::string::npos)
|
||||
return tokens;
|
||||
|
||||
while (startPos < str.size()) {
|
||||
// Find next delimiter or end of string (end of token)
|
||||
auto pos = str.find_first_of(delims, startPos);
|
||||
auto pos = str.find_first_of(delim, startPos);
|
||||
|
||||
// Add token to the vector and adjust start
|
||||
if (pos == std::string::npos) {
|
||||
|
@@ -33,8 +33,7 @@ path_getfather(const std::string &s);
|
||||
|
||||
gcc_pure
|
||||
std::list<std::string>
|
||||
stringToTokens(const std::string &str,
|
||||
const char *delims = "/", bool skipinit = true);
|
||||
stringToTokens(const std::string &str, char delim);
|
||||
|
||||
template <class T>
|
||||
bool
|
||||
|
@@ -52,11 +52,11 @@ class WorkQueue {
|
||||
|
||||
// Status
|
||||
// Worker threads having called exit
|
||||
unsigned n_workers_exited;
|
||||
bool ok;
|
||||
unsigned n_workers_exited = 0;
|
||||
bool ok = false;
|
||||
|
||||
unsigned n_threads;
|
||||
pthread_t *threads;
|
||||
unsigned n_threads = 0;
|
||||
pthread_t *threads = nullptr;
|
||||
|
||||
// Synchronization
|
||||
std::queue<T> queue;
|
||||
@@ -68,11 +68,8 @@ public:
|
||||
/** Create a WorkQueue
|
||||
* @param _name for message printing
|
||||
*/
|
||||
WorkQueue(const char *_name)
|
||||
:name(_name),
|
||||
n_workers_exited(0),
|
||||
ok(false),
|
||||
n_threads(0), threads(nullptr)
|
||||
explicit WorkQueue(const char *_name)
|
||||
:name(_name)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -80,6 +77,9 @@ public:
|
||||
setTerminateAndWait();
|
||||
}
|
||||
|
||||
WorkQueue(const WorkQueue &) = delete;
|
||||
WorkQueue &operator=(const WorkQueue &) = delete;
|
||||
|
||||
/** Start the worker threads.
|
||||
*
|
||||
* @param nworkers number of threads copies to start.
|
||||
@@ -97,6 +97,7 @@ public:
|
||||
assert(n_threads == 0);
|
||||
assert(threads == nullptr);
|
||||
|
||||
ok = true;
|
||||
n_threads = nworkers;
|
||||
threads = new pthread_t[n_threads];
|
||||
|
||||
@@ -109,7 +110,6 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
ok = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2015 Max Kellermann <max@duempel.org>
|
||||
* Copyright (C) 2012-2015 Max Kellermann <max.kellermann@gmail.com>
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2015 Max Kellermann <max@duempel.org>
|
||||
* Copyright (C) 2012-2015 Max Kellermann <max.kellermann@gmail.com>
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2015 Max Kellermann <max@duempel.org>
|
||||
* Copyright (C) 2012-2015 Max Kellermann <max.kellermann@gmail.com>
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2015 Max Kellermann <max@duempel.org>
|
||||
* Copyright (C) 2012-2015 Max Kellermann <max.kellermann@gmail.com>
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2015 Max Kellermann <max@duempel.org>
|
||||
* Copyright (C) 2012-2015 Max Kellermann <max.kellermann@gmail.com>
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2015 Max Kellermann <max@duempel.org>
|
||||
* Copyright (C) 2012-2015 Max Kellermann <max.kellermann@gmail.com>
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2011-2015 Max Kellermann <max@duempel.org>
|
||||
* Copyright (C) 2011-2015 Max Kellermann <max.kellermann@gmail.com>
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2011-2015 Max Kellermann <max@duempel.org>
|
||||
* Copyright (C) 2011-2015 Max Kellermann <max.kellermann@gmail.com>
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
|
@@ -35,6 +35,7 @@
|
||||
#include "thread/Slack.hxx"
|
||||
#include "thread/Name.hxx"
|
||||
#include "util/ConstBuffer.hxx"
|
||||
#include "util/StringBuffer.hxx"
|
||||
#include "util/ScopeExit.hxx"
|
||||
#include "util/RuntimeError.hxx"
|
||||
#include "Log.hxx"
|
||||
@@ -154,14 +155,11 @@ AudioOutput::Open()
|
||||
}
|
||||
}
|
||||
|
||||
if (f != source.GetInputAudioFormat() || f != out_audio_format) {
|
||||
struct audio_format_string afs1, afs2, afs3;
|
||||
if (f != source.GetInputAudioFormat() || f != out_audio_format)
|
||||
FormatDebug(output_domain, "converting in=%s -> f=%s -> out=%s",
|
||||
audio_format_to_string(source.GetInputAudioFormat(),
|
||||
&afs1),
|
||||
audio_format_to_string(f, &afs2),
|
||||
audio_format_to_string(out_audio_format, &afs3));
|
||||
}
|
||||
ToString(source.GetInputAudioFormat()).c_str(),
|
||||
ToString(f).c_str(),
|
||||
ToString(out_audio_format).c_str());
|
||||
}
|
||||
|
||||
void
|
||||
@@ -176,11 +174,10 @@ AudioOutput::OpenOutputAndConvert(AudioFormat desired_audio_format)
|
||||
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));
|
||||
ToString(out_audio_format).c_str());
|
||||
|
||||
try {
|
||||
convert_filter_set(convert_filter.Get(), out_audio_format);
|
||||
|
@@ -95,13 +95,17 @@ try {
|
||||
assert(audio_format.IsValid());
|
||||
|
||||
/* the replay_gain filter cannot fail here */
|
||||
if (prepared_replay_gain_filter != nullptr)
|
||||
if (prepared_replay_gain_filter != nullptr) {
|
||||
replay_gain_serial = 0;
|
||||
replay_gain_filter_instance =
|
||||
prepared_replay_gain_filter->Open(audio_format);
|
||||
}
|
||||
|
||||
if (prepared_other_replay_gain_filter != nullptr)
|
||||
if (prepared_other_replay_gain_filter != nullptr) {
|
||||
other_replay_gain_serial = 0;
|
||||
other_replay_gain_filter_instance =
|
||||
prepared_other_replay_gain_filter->Open(audio_format);
|
||||
}
|
||||
|
||||
filter_instance = prepared_filter->Open(audio_format);
|
||||
} catch (...) {
|
||||
|
@@ -64,13 +64,13 @@ class AudioOutputSource {
|
||||
* The serial number of the last replay gain info. 0 means no
|
||||
* replay gain info was available.
|
||||
*/
|
||||
unsigned replay_gain_serial = 0;
|
||||
unsigned replay_gain_serial;
|
||||
|
||||
/**
|
||||
* The serial number of the last replay gain info by the
|
||||
* "other" chunk during cross-fading.
|
||||
*/
|
||||
unsigned other_replay_gain_serial = 0;
|
||||
unsigned other_replay_gain_serial;
|
||||
|
||||
/**
|
||||
* The replay_gain_filter_plugin instance of this audio
|
||||
|
@@ -50,7 +50,9 @@ static constexpr unsigned MPD_ALSA_BUFFER_TIME_US = 500000;
|
||||
|
||||
static constexpr unsigned MPD_ALSA_RETRY_NR = 5;
|
||||
|
||||
struct AlsaOutput {
|
||||
class AlsaOutput {
|
||||
friend struct AudioOutputWrapper<AlsaOutput>;
|
||||
|
||||
AudioOutput base;
|
||||
|
||||
Manual<PcmExport> pcm_export;
|
||||
@@ -121,6 +123,7 @@ struct AlsaOutput {
|
||||
*/
|
||||
uint8_t *silence;
|
||||
|
||||
public:
|
||||
AlsaOutput(const ConfigBlock &block);
|
||||
|
||||
~AlsaOutput() {
|
||||
@@ -147,6 +150,14 @@ struct AlsaOutput {
|
||||
void Cancel();
|
||||
|
||||
private:
|
||||
/**
|
||||
* Set up the snd_pcm_t object which was opened by the caller.
|
||||
* Set up the configured settings and the audio format.
|
||||
*
|
||||
* Throws #std::runtime_error on error.
|
||||
*/
|
||||
void Setup(AudioFormat &audio_format, PcmExport::Params ¶ms);
|
||||
|
||||
#ifdef ENABLE_DSD
|
||||
void SetupDop(AudioFormat audio_format,
|
||||
PcmExport::Params ¶ms);
|
||||
@@ -477,50 +488,44 @@ AlsaSetupFormat(snd_pcm_t *pcm, snd_pcm_hw_params_t *hwparams,
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up the snd_pcm_t object which was opened by the caller. Set up
|
||||
* the configured settings and the audio format.
|
||||
* Wrapper for snd_pcm_hw_params().
|
||||
*
|
||||
* Throws #std::runtime_error on error.
|
||||
* @param buffer_time the configured buffer time, or 0 if not configured
|
||||
* @param period_time the configured period time, or 0 if not configured
|
||||
* @param audio_format an #AudioFormat to be configured (or modified)
|
||||
* by this function
|
||||
* @param params to be modified by this function
|
||||
*/
|
||||
static void
|
||||
AlsaSetup(AlsaOutput *ad, AudioFormat &audio_format,
|
||||
PcmExport::Params ¶ms)
|
||||
AlsaSetupHw(snd_pcm_t *pcm, snd_pcm_hw_params_t *hwparams,
|
||||
unsigned buffer_time, unsigned period_time,
|
||||
AudioFormat &audio_format, PcmExport::Params ¶ms)
|
||||
{
|
||||
unsigned int channels = audio_format.channels;
|
||||
int err;
|
||||
unsigned retry = MPD_ALSA_RETRY_NR;
|
||||
unsigned int period_time, period_time_ro;
|
||||
unsigned int buffer_time;
|
||||
unsigned int period_time_ro = period_time;
|
||||
|
||||
period_time_ro = period_time = ad->period_time;
|
||||
configure_hw:
|
||||
/* configure HW params */
|
||||
snd_pcm_hw_params_t *hwparams;
|
||||
snd_pcm_hw_params_alloca(&hwparams);
|
||||
err = snd_pcm_hw_params_any(ad->pcm, hwparams);
|
||||
err = snd_pcm_hw_params_any(pcm, hwparams);
|
||||
if (err < 0)
|
||||
throw FormatRuntimeError("snd_pcm_hw_params_any() failed: %s",
|
||||
snd_strerror(-err));
|
||||
|
||||
err = snd_pcm_hw_params_set_access(ad->pcm, hwparams,
|
||||
err = snd_pcm_hw_params_set_access(pcm, hwparams,
|
||||
SND_PCM_ACCESS_RW_INTERLEAVED);
|
||||
if (err < 0)
|
||||
throw FormatRuntimeError("snd_pcm_hw_params_set_access() failed: %s",
|
||||
snd_strerror(-err));
|
||||
|
||||
err = AlsaSetupFormat(ad->pcm, hwparams, audio_format, params);
|
||||
err = AlsaSetupFormat(pcm, hwparams, audio_format, params);
|
||||
if (err < 0)
|
||||
throw FormatRuntimeError("Failed to configure format %s: %s",
|
||||
sample_format_to_string(audio_format.format),
|
||||
snd_strerror(-err));
|
||||
|
||||
snd_pcm_format_t format;
|
||||
if (snd_pcm_hw_params_get_format(hwparams, &format) == 0)
|
||||
FormatDebug(alsa_output_domain,
|
||||
"format=%s (%s)", snd_pcm_format_name(format),
|
||||
snd_pcm_format_description(format));
|
||||
|
||||
err = snd_pcm_hw_params_set_channels_near(ad->pcm, hwparams,
|
||||
unsigned int channels = audio_format.channels;
|
||||
err = snd_pcm_hw_params_set_channels_near(pcm, hwparams,
|
||||
&channels);
|
||||
if (err < 0)
|
||||
throw FormatRuntimeError("Failed to configure %i channels: %s",
|
||||
@@ -533,7 +538,7 @@ configure_hw:
|
||||
params.CalcOutputSampleRate(audio_format.sample_rate);
|
||||
unsigned output_sample_rate = requested_sample_rate;
|
||||
|
||||
err = snd_pcm_hw_params_set_rate_near(ad->pcm, hwparams,
|
||||
err = snd_pcm_hw_params_set_rate_near(pcm, hwparams,
|
||||
&output_sample_rate, nullptr);
|
||||
if (err < 0)
|
||||
throw FormatRuntimeError("Failed to configure sample rate %u Hz: %s",
|
||||
@@ -567,9 +572,8 @@ configure_hw:
|
||||
(unsigned)period_size_min, (unsigned)period_size_max,
|
||||
period_time_min, period_time_max);
|
||||
|
||||
if (ad->buffer_time > 0) {
|
||||
buffer_time = ad->buffer_time;
|
||||
err = snd_pcm_hw_params_set_buffer_time_near(ad->pcm, hwparams,
|
||||
if (buffer_time > 0) {
|
||||
err = snd_pcm_hw_params_set_buffer_time_near(pcm, hwparams,
|
||||
&buffer_time, nullptr);
|
||||
if (err < 0)
|
||||
throw FormatRuntimeError("snd_pcm_hw_params_set_buffer_time_near() failed: %s",
|
||||
@@ -591,14 +595,14 @@ configure_hw:
|
||||
|
||||
if (period_time_ro > 0) {
|
||||
period_time = period_time_ro;
|
||||
err = snd_pcm_hw_params_set_period_time_near(ad->pcm, hwparams,
|
||||
err = snd_pcm_hw_params_set_period_time_near(pcm, hwparams,
|
||||
&period_time, nullptr);
|
||||
if (err < 0)
|
||||
throw FormatRuntimeError("snd_pcm_hw_params_set_period_time_near() failed: %s",
|
||||
snd_strerror(-err));
|
||||
}
|
||||
|
||||
err = snd_pcm_hw_params(ad->pcm, hwparams);
|
||||
err = snd_pcm_hw_params(pcm, hwparams);
|
||||
if (err == -EPIPE && --retry > 0 && period_time_ro > 0) {
|
||||
period_time_ro = period_time_ro >> 1;
|
||||
goto configure_hw;
|
||||
@@ -608,9 +612,59 @@ configure_hw:
|
||||
if (retry != MPD_ALSA_RETRY_NR)
|
||||
FormatDebug(alsa_output_domain,
|
||||
"ALSA period_time set to %d", period_time);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper for snd_pcm_sw_params().
|
||||
*/
|
||||
static void
|
||||
AlsaSetupSw(snd_pcm_t *pcm, snd_pcm_uframes_t start_threshold,
|
||||
snd_pcm_uframes_t avail_min)
|
||||
{
|
||||
snd_pcm_sw_params_t *swparams;
|
||||
snd_pcm_sw_params_alloca(&swparams);
|
||||
|
||||
int err = snd_pcm_sw_params_current(pcm, swparams);
|
||||
if (err < 0)
|
||||
throw FormatRuntimeError("snd_pcm_sw_params_current() failed: %s",
|
||||
snd_strerror(-err));
|
||||
|
||||
err = snd_pcm_sw_params_set_start_threshold(pcm, swparams,
|
||||
start_threshold);
|
||||
if (err < 0)
|
||||
throw FormatRuntimeError("snd_pcm_sw_params_set_start_threshold() failed: %s",
|
||||
snd_strerror(-err));
|
||||
|
||||
err = snd_pcm_sw_params_set_avail_min(pcm, swparams, avail_min);
|
||||
if (err < 0)
|
||||
throw FormatRuntimeError("snd_pcm_sw_params_set_avail_min() failed: %s",
|
||||
snd_strerror(-err));
|
||||
|
||||
err = snd_pcm_sw_params(pcm, swparams);
|
||||
if (err < 0)
|
||||
throw FormatRuntimeError("snd_pcm_sw_params() failed: %s",
|
||||
snd_strerror(-err));
|
||||
}
|
||||
|
||||
inline void
|
||||
AlsaOutput::Setup(AudioFormat &audio_format,
|
||||
PcmExport::Params ¶ms)
|
||||
{
|
||||
snd_pcm_hw_params_t *hwparams;
|
||||
snd_pcm_hw_params_alloca(&hwparams);
|
||||
|
||||
AlsaSetupHw(pcm, hwparams,
|
||||
buffer_time, period_time,
|
||||
audio_format, params);
|
||||
|
||||
snd_pcm_format_t format;
|
||||
if (snd_pcm_hw_params_get_format(hwparams, &format) == 0)
|
||||
FormatDebug(alsa_output_domain,
|
||||
"format=%s (%s)", snd_pcm_format_name(format),
|
||||
snd_pcm_format_description(format));
|
||||
|
||||
snd_pcm_uframes_t alsa_buffer_size;
|
||||
err = snd_pcm_hw_params_get_buffer_size(hwparams, &alsa_buffer_size);
|
||||
int err = snd_pcm_hw_params_get_buffer_size(hwparams, &alsa_buffer_size);
|
||||
if (err < 0)
|
||||
throw FormatRuntimeError("snd_pcm_hw_params_get_buffer_size() failed: %s",
|
||||
snd_strerror(-err));
|
||||
@@ -622,32 +676,8 @@ configure_hw:
|
||||
throw FormatRuntimeError("snd_pcm_hw_params_get_period_size() failed: %s",
|
||||
snd_strerror(-err));
|
||||
|
||||
/* configure SW params */
|
||||
snd_pcm_sw_params_t *swparams;
|
||||
snd_pcm_sw_params_alloca(&swparams);
|
||||
|
||||
err = snd_pcm_sw_params_current(ad->pcm, swparams);
|
||||
if (err < 0)
|
||||
throw FormatRuntimeError("snd_pcm_sw_params_current() failed: %s",
|
||||
snd_strerror(-err));
|
||||
|
||||
err = snd_pcm_sw_params_set_start_threshold(ad->pcm, swparams,
|
||||
alsa_buffer_size -
|
||||
alsa_period_size);
|
||||
if (err < 0)
|
||||
throw FormatRuntimeError("snd_pcm_sw_params_set_start_threshold() failed: %s",
|
||||
snd_strerror(-err));
|
||||
|
||||
err = snd_pcm_sw_params_set_avail_min(ad->pcm, swparams,
|
||||
alsa_period_size);
|
||||
if (err < 0)
|
||||
throw FormatRuntimeError("snd_pcm_sw_params_set_avail_min() failed: %s",
|
||||
snd_strerror(-err));
|
||||
|
||||
err = snd_pcm_sw_params(ad->pcm, swparams);
|
||||
if (err < 0)
|
||||
throw FormatRuntimeError("snd_pcm_sw_params() failed: %s",
|
||||
snd_strerror(-err));
|
||||
AlsaSetupSw(pcm, alsa_buffer_size - alsa_period_size,
|
||||
alsa_period_size);
|
||||
|
||||
FormatDebug(alsa_output_domain, "buffer_size=%u period_size=%u",
|
||||
(unsigned)alsa_buffer_size, (unsigned)alsa_period_size);
|
||||
@@ -660,13 +690,12 @@ configure_hw:
|
||||
happen again. */
|
||||
alsa_period_size = 1;
|
||||
|
||||
ad->period_frames = alsa_period_size;
|
||||
ad->period_position = 0;
|
||||
period_frames = alsa_period_size;
|
||||
period_position = 0;
|
||||
|
||||
ad->silence = new uint8_t[snd_pcm_frames_to_bytes(ad->pcm,
|
||||
alsa_period_size)];
|
||||
snd_pcm_format_set_silence(format, ad->silence,
|
||||
alsa_period_size * channels);
|
||||
silence = new uint8_t[snd_pcm_frames_to_bytes(pcm, alsa_period_size)];
|
||||
snd_pcm_format_set_silence(format, silence,
|
||||
alsa_period_size * audio_format.channels);
|
||||
|
||||
}
|
||||
|
||||
@@ -686,7 +715,7 @@ AlsaOutput::SetupDop(const AudioFormat audio_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
|
||||
samples left by 8 bit and leave the lower 8 bit cleared;
|
||||
@@ -725,7 +754,7 @@ AlsaOutput::SetupOrDop(AudioFormat &audio_format, PcmExport::Params ¶ms)
|
||||
|
||||
try {
|
||||
#endif
|
||||
AlsaSetup(this, audio_format, params);
|
||||
Setup(audio_format, params);
|
||||
#ifdef ENABLE_DSD
|
||||
} catch (...) {
|
||||
if (dop_error)
|
||||
|
@@ -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
|
||||
* modification, are permitted provided that the following conditions
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2011-2012 Max Kellermann <max@duempel.org>
|
||||
* Copyright (C) 2011-2012 Max Kellermann <max.kellermann@gmail.com>
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2011-2012 Max Kellermann <max@duempel.org>
|
||||
* Copyright (C) 2011-2012 Max Kellermann <max.kellermann@gmail.com>
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2011-2012 Max Kellermann <max@duempel.org>
|
||||
* Copyright (C) 2011-2012 Max Kellermann <max.kellermann@gmail.com>
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
|
@@ -21,7 +21,7 @@
|
||||
#define MPD_PCM_CHANNELS_CONVERTER_HXX
|
||||
|
||||
#include "check.h"
|
||||
#include "AudioFormat.hxx"
|
||||
#include "SampleFormat.hxx"
|
||||
#include "PcmBuffer.hxx"
|
||||
|
||||
#ifndef NDEBUG
|
||||
|
@@ -21,7 +21,7 @@
|
||||
#define MPD_PCM_FORMAT_CONVERTER_HXX
|
||||
|
||||
#include "check.h"
|
||||
#include "AudioFormat.hxx"
|
||||
#include "SampleFormat.hxx"
|
||||
#include "PcmBuffer.hxx"
|
||||
#include "PcmDither.hxx"
|
||||
|
||||
|
@@ -21,7 +21,7 @@
|
||||
#define MPD_PCM_ORDER_HXX
|
||||
|
||||
#include "check.h"
|
||||
#include "AudioFormat.hxx"
|
||||
#include "SampleFormat.hxx"
|
||||
|
||||
class PcmBuffer;
|
||||
template<typename T> struct ConstBuffer;
|
||||
|
@@ -20,9 +20,14 @@
|
||||
#include "config.h"
|
||||
#include "PcmChannels.hxx"
|
||||
#include "PcmBuffer.hxx"
|
||||
#include "Silence.hxx"
|
||||
#include "Traits.hxx"
|
||||
#include "AudioFormat.hxx"
|
||||
#include "util/ConstBuffer.hxx"
|
||||
#include "util/WritableBuffer.hxx"
|
||||
|
||||
#include <array>
|
||||
#include <algorithm>
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
@@ -90,6 +95,38 @@ NToStereo(typename Traits::pointer_type 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>>
|
||||
static typename Traits::pointer_type
|
||||
NToM(typename Traits::pointer_type dest,
|
||||
@@ -133,6 +170,9 @@ ConvertChannels(PcmBuffer &buffer,
|
||||
StereoToMono<F>(dest, src.begin(), src.end());
|
||||
else if (dest_channels == 2)
|
||||
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
|
||||
NToM<F>(dest, dest_channels,
|
||||
src_channels, src.begin(), src.end());
|
||||
|
@@ -20,7 +20,6 @@
|
||||
#include "config.h"
|
||||
#include "PcmConvert.hxx"
|
||||
#include "ConfiguredResampler.hxx"
|
||||
#include "AudioFormat.hxx"
|
||||
#include "util/ConstBuffer.hxx"
|
||||
|
||||
#include <assert.h>
|
||||
|
@@ -19,6 +19,7 @@
|
||||
|
||||
#include "config.h"
|
||||
#include "PcmExport.hxx"
|
||||
#include "AudioFormat.hxx"
|
||||
#include "Order.hxx"
|
||||
#include "PcmPack.hxx"
|
||||
#include "util/ByteReverse.hxx"
|
||||
|
@@ -21,10 +21,11 @@
|
||||
#define PCM_EXPORT_HXX
|
||||
|
||||
#include "check.h"
|
||||
#include "SampleFormat.hxx"
|
||||
#include "PcmBuffer.hxx"
|
||||
#include "AudioFormat.hxx"
|
||||
|
||||
template<typename T> struct ConstBuffer;
|
||||
struct AudioFormat;
|
||||
|
||||
/**
|
||||
* An object that handles export of PCM samples to some instance
|
||||
|
@@ -20,7 +20,7 @@
|
||||
#ifndef MPD_PCM_FORMAT_HXX
|
||||
#define MPD_PCM_FORMAT_HXX
|
||||
|
||||
#include "AudioFormat.hxx"
|
||||
#include "SampleFormat.hxx"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
|
@@ -21,7 +21,6 @@
|
||||
#include "PcmMix.hxx"
|
||||
#include "Volume.hxx"
|
||||
#include "PcmUtils.hxx"
|
||||
#include "AudioFormat.hxx"
|
||||
#include "Traits.hxx"
|
||||
#include "util/Clamp.hxx"
|
||||
|
||||
|
@@ -20,7 +20,7 @@
|
||||
#ifndef MPD_PCM_MIX_HXX
|
||||
#define MPD_PCM_MIX_HXX
|
||||
|
||||
#include "AudioFormat.hxx"
|
||||
#include "SampleFormat.hxx"
|
||||
#include "Compiler.h"
|
||||
|
||||
#include <stddef.h>
|
||||
|
53
src/pcm/SampleFormat.cxx
Normal file
53
src/pcm/SampleFormat.cxx
Normal file
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* 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 "SampleFormat.hxx"
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
const char *
|
||||
sample_format_to_string(SampleFormat format)
|
||||
{
|
||||
switch (format) {
|
||||
case SampleFormat::UNDEFINED:
|
||||
return "?";
|
||||
|
||||
case SampleFormat::S8:
|
||||
return "8";
|
||||
|
||||
case SampleFormat::S16:
|
||||
return "16";
|
||||
|
||||
case SampleFormat::S24_P32:
|
||||
return "24";
|
||||
|
||||
case SampleFormat::S32:
|
||||
return "32";
|
||||
|
||||
case SampleFormat::FLOAT:
|
||||
return "f";
|
||||
|
||||
case SampleFormat::DSD:
|
||||
return "dsd";
|
||||
}
|
||||
|
||||
/* unreachable */
|
||||
assert(false);
|
||||
gcc_unreachable();
|
||||
}
|
123
src/pcm/SampleFormat.hxx
Normal file
123
src/pcm/SampleFormat.hxx
Normal file
@@ -0,0 +1,123 @@
|
||||
/*
|
||||
* 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_SAMPLE_FORMAT_HXX
|
||||
#define MPD_PCM_SAMPLE_FORMAT_HXX
|
||||
|
||||
#include "Compiler.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#if defined(WIN32) && GCC_CHECK_VERSION(4,6)
|
||||
/* on WIN32, "FLOAT" is already defined, and this triggers -Wshadow */
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wshadow"
|
||||
#endif
|
||||
|
||||
enum class SampleFormat : uint8_t {
|
||||
UNDEFINED = 0,
|
||||
|
||||
S8,
|
||||
S16,
|
||||
|
||||
/**
|
||||
* Signed 24 bit integer samples, packed in 32 bit integers
|
||||
* (the most significant byte is filled with the sign bit).
|
||||
*/
|
||||
S24_P32,
|
||||
|
||||
S32,
|
||||
|
||||
/**
|
||||
* 32 bit floating point samples in the host's format. The
|
||||
* range is -1.0f to +1.0f.
|
||||
*/
|
||||
FLOAT,
|
||||
|
||||
/**
|
||||
* Direct Stream Digital. 1-bit samples; each frame has one
|
||||
* byte (8 samples) per channel.
|
||||
*/
|
||||
DSD,
|
||||
};
|
||||
|
||||
#if defined(WIN32) && GCC_CHECK_VERSION(4,6)
|
||||
#pragma GCC diagnostic pop
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Checks whether the sample format is valid.
|
||||
*/
|
||||
static constexpr 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;
|
||||
}
|
||||
|
||||
static constexpr 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;
|
||||
}
|
||||
|
||||
gcc_unreachable();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
|
||||
#endif
|
@@ -19,7 +19,8 @@
|
||||
|
||||
#include "config.h"
|
||||
#include "Silence.hxx"
|
||||
#include "AudioFormat.hxx"
|
||||
#include "Traits.hxx"
|
||||
#include "SampleFormat.hxx"
|
||||
#include "util/WritableBuffer.hxx"
|
||||
|
||||
#include <string.h>
|
||||
@@ -29,7 +30,7 @@ PcmSilence(WritableBuffer<void> dest, SampleFormat format)
|
||||
{
|
||||
uint8_t pattern = 0;
|
||||
if (format == SampleFormat::DSD)
|
||||
pattern = 0x69;
|
||||
pattern = SampleTraits<SampleFormat::DSD>::SILENCE;
|
||||
|
||||
memset(dest.data, pattern, dest.size);
|
||||
}
|
||||
|
@@ -21,7 +21,7 @@
|
||||
#define MPD_PCM_TRAITS_HXX
|
||||
|
||||
#include "check.h"
|
||||
#include "AudioFormat.hxx"
|
||||
#include "SampleFormat.hxx"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
@@ -85,6 +85,11 @@ struct SampleTraits<SampleFormat::S8> {
|
||||
* The maximum sample value.
|
||||
*/
|
||||
static constexpr value_type MAX = (sum_type(1) << (BITS - 1)) - 1;
|
||||
|
||||
/**
|
||||
* A value which represents "silence".
|
||||
*/
|
||||
static constexpr value_type SILENCE = 0;
|
||||
};
|
||||
|
||||
template<>
|
||||
@@ -101,6 +106,7 @@ struct SampleTraits<SampleFormat::S16> {
|
||||
|
||||
static constexpr value_type MIN = -(sum_type(1) << (BITS - 1));
|
||||
static constexpr value_type MAX = (sum_type(1) << (BITS - 1)) - 1;
|
||||
static constexpr value_type SILENCE = 0;
|
||||
};
|
||||
|
||||
template<>
|
||||
@@ -117,6 +123,7 @@ struct SampleTraits<SampleFormat::S32> {
|
||||
|
||||
static constexpr value_type MIN = -(sum_type(1) << (BITS - 1));
|
||||
static constexpr value_type MAX = (sum_type(1) << (BITS - 1)) - 1;
|
||||
static constexpr value_type SILENCE = 0;
|
||||
};
|
||||
|
||||
template<>
|
||||
@@ -133,6 +140,7 @@ struct SampleTraits<SampleFormat::S24_P32> {
|
||||
|
||||
static constexpr value_type MIN = -(sum_type(1) << (BITS - 1));
|
||||
static constexpr value_type MAX = (sum_type(1) << (BITS - 1)) - 1;
|
||||
static constexpr value_type SILENCE = 0;
|
||||
};
|
||||
|
||||
template<>
|
||||
@@ -148,6 +156,18 @@ struct SampleTraits<SampleFormat::FLOAT> {
|
||||
|
||||
static constexpr value_type MIN = -1;
|
||||
static constexpr value_type MAX = 1;
|
||||
static constexpr value_type SILENCE = 0;
|
||||
};
|
||||
|
||||
template<>
|
||||
struct SampleTraits<SampleFormat::DSD> {
|
||||
typedef uint8_t value_type;
|
||||
typedef value_type *pointer_type;
|
||||
typedef const value_type *const_pointer_type;
|
||||
|
||||
static constexpr size_t SAMPLE_SIZE = sizeof(value_type);
|
||||
|
||||
static constexpr value_type SILENCE = 0x69;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
@@ -27,6 +27,7 @@
|
||||
|
||||
#include "PcmDither.cxx" // including the .cxx file to get inlined templates
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
|
@@ -20,7 +20,7 @@
|
||||
#ifndef MPD_PCM_VOLUME_HXX
|
||||
#define MPD_PCM_VOLUME_HXX
|
||||
|
||||
#include "AudioFormat.hxx"
|
||||
#include "SampleFormat.hxx"
|
||||
#include "PcmBuffer.hxx"
|
||||
#include "PcmDither.hxx"
|
||||
|
||||
|
@@ -238,8 +238,7 @@ try {
|
||||
bool done = false;
|
||||
|
||||
while (!done) {
|
||||
char buffer[4096];
|
||||
unsigned char *ubuffer = (unsigned char *)buffer;
|
||||
unsigned char buffer[4096];
|
||||
const size_t nbytes =
|
||||
input_stream->Read(buffer, sizeof(buffer));
|
||||
if (nbytes == 0)
|
||||
@@ -248,10 +247,10 @@ try {
|
||||
if (done) {
|
||||
stat = yajl_complete_parse(hand);
|
||||
} else
|
||||
stat = yajl_parse(hand, ubuffer, nbytes);
|
||||
stat = yajl_parse(hand, buffer, nbytes);
|
||||
|
||||
if (stat != yajl_status_ok) {
|
||||
unsigned char *str = yajl_get_error(hand, 1, ubuffer, nbytes);
|
||||
unsigned char *str = yajl_get_error(hand, 1, buffer, nbytes);
|
||||
LogError(soundcloud_domain, (const char *)str);
|
||||
yajl_free_error(hand, str);
|
||||
break;
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2011-2015 Max Kellermann <max@duempel.org>,
|
||||
* Copyright (C) 2011-2015 Max Kellermann <max.kellermann@gmail.com>,
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2013-2015 Max Kellermann <max@duempel.org>
|
||||
* Copyright (C) 2013-2015 Max Kellermann <max.kellermann@gmail.com>
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2015 Max Kellermann <max@duempel.org>
|
||||
* Copyright (C) 2012-2015 Max Kellermann <max.kellermann@gmail.com>
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2015 Max Kellermann <max@duempel.org>
|
||||
* Copyright (C) 2012-2015 Max Kellermann <max.kellermann@gmail.com>
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2009-2015 Max Kellermann <max@duempel.org>
|
||||
* Copyright (C) 2009-2015 Max Kellermann <max.kellermann@gmail.com>
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2009-2013 Max Kellermann <max@duempel.org>
|
||||
* Copyright (C) 2009-2013 Max Kellermann <max.kellermann@gmail.com>
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2009-2016 Max Kellermann <max@duempel.org>
|
||||
* Copyright (C) 2009-2016 Max Kellermann <max.kellermann@gmail.com>
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2009-2015 Max Kellermann <max@duempel.org>
|
||||
* Copyright (C) 2009-2015 Max Kellermann <max.kellermann@gmail.com>
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2009-2015 Max Kellermann <max@duempel.org>
|
||||
* Copyright (C) 2009-2015 Max Kellermann <max.kellermann@gmail.com>
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2014-2016 Max Kellermann <max@duempel.org>
|
||||
* Copyright (C) 2014-2016 Max Kellermann <max.kellermann@gmail.com>
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2014-2016 Max Kellermann <max@duempel.org>
|
||||
* Copyright (C) 2014-2016 Max Kellermann <max.kellermann@gmail.com>
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2009-2013 Max Kellermann <max@duempel.org>
|
||||
* Copyright (C) 2009-2013 Max Kellermann <max.kellermann@gmail.com>
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Max Kellermann <max@duempel.org>
|
||||
* Copyright (C) 2013 Max Kellermann <max.kellermann@gmail.com>
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2010-2016 Max Kellermann <max@duempel.org>
|
||||
* Copyright (C) 2010-2016 Max Kellermann <max.kellermann@gmail.com>
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Max Kellermann <max@duempel.org>
|
||||
* Copyright (C) 2015 Max Kellermann <max.kellermann@gmail.com>
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Max Kellermann <max@duempel.org>
|
||||
* Copyright (C) 2015 Max Kellermann <max.kellermann@gmail.com>
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Max Kellermann <max@duempel.org>
|
||||
* Copyright (C) 2016 Max Kellermann <max.kellermann@gmail.com>
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2013-2014 Max Kellermann <max@duempel.org>
|
||||
* Copyright (C) 2013-2014 Max Kellermann <max.kellermann@gmail.com>
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2011-2014 Max Kellermann <max@duempel.org>
|
||||
* Copyright (C) 2011-2014 Max Kellermann <max.kellermann@gmail.com>
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Max Kellermann <max@duempel.org>
|
||||
* Copyright (C) 2014 Max Kellermann <max.kellermann@gmail.com>
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user