release v0.19.11
-----BEGIN PGP SIGNATURE----- Version: GnuPG v2 iQIcBAABCAAGBQJWL0cEAAoJECNuiljG20USkHkP/0m3kFEEgqauAIbI1t0TkKOp 2ii5iHZeMAlsxGEc5SX5fFoXM6STbXq/3+OXBf+OnABh9b03o744QAAwh1ei9tiQ kMysbN2fbpHkuchx1JfrOU1ad3qfWXQri8csTtx5eRYpgyqF7Mfl1SoY1nkpherd j4MVq7MVqBhwCqpAfJvTFTSOlNrJ4bBcvIgGslhFYhRxMRM83KgFS//lHvbXWnOg fjYEO34nz0rjCfz6x2r7ZQBLeQVr9n6h24iYhSTnU7Xq9o2ezWlVRIm9YVhxoZKf /MRJuAzaHhGID8IvX7dPxdQJ+feUhQXSv8HSjOJBO6R2dqIScE3D6EIBHy8Cj9Bk O2D1SgmR+2NnjNz2GUjCIKHm9c9jTgv+rnZ2l8hweS2oUQOPHbCtOoCNAfwyP+/Y ms1CavNl7bUuvWrM1ipM2ZK6QfW9P4F1dtmwqtJCsqdGFyQyfACcxqmlkfxhB2vI NyvKAOn/TlWWQscF9id3r90sEir/J9e7IJ6oZh+uvyIfOtt8wR/Jm5/H1MA7j3iX XmNbe1GY3WHjCH2lRr3tIRKAE8I3HAtBzwhvq/miSESrkEnJ06VMatkoMRQT63gy 62yaCg+ZWiBRp07ygiedYuGL19pDOhqRjY3U/b/0EHMb9ux083nuUTssqgzzO2OP 9OPao5CPs4M4QvmiG/wF =RdQt -----END PGP SIGNATURE----- Merge tag 'v0.19.11'
This commit is contained in:
commit
94f850a588
11
NEWS
11
NEWS
|
@ -42,6 +42,17 @@ ver 0.20 (not yet released)
|
||||||
* update
|
* update
|
||||||
- apply .mpdignore matches to subdirectories
|
- apply .mpdignore matches to subdirectories
|
||||||
|
|
||||||
|
ver 0.19.11 (2015/10/27)
|
||||||
|
* tags
|
||||||
|
- ape: fix buffer overflow
|
||||||
|
* decoder
|
||||||
|
- ffmpeg: fix crash due to wrong avio_alloc_context() call
|
||||||
|
- gme: don't loop forever, fall back to GME's default play length
|
||||||
|
* encoder
|
||||||
|
- flac: fix crash with 32 bit playback
|
||||||
|
* mixer
|
||||||
|
- fix mixer lag after enabling/disabling output
|
||||||
|
|
||||||
ver 0.19.10 (2015/06/21)
|
ver 0.19.10 (2015/06/21)
|
||||||
* input
|
* input
|
||||||
- curl: fix deadlock on small responses
|
- curl: fix deadlock on small responses
|
||||||
|
|
52
doc/user.xml
52
doc/user.xml
|
@ -1025,6 +1025,58 @@ database {
|
||||||
plugin).
|
plugin).
|
||||||
</para>
|
</para>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<section id="realtime">
|
||||||
|
<title>Real-Time Scheduling</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
On Linux, <application>MPD</application> attempts to configure
|
||||||
|
<ulink
|
||||||
|
url="https://en.wikipedia.org/wiki/Real-time_computing">real-time
|
||||||
|
scheduling</ulink> for some threads that benefit from it.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
This is only possible you allow <application>MPD</application>
|
||||||
|
to do it. This privilege is controlled by
|
||||||
|
<varname>RLIMIT_RTPRIO</varname>
|
||||||
|
<varname>RLIMIT_RTTIME</varname>. You can configure this
|
||||||
|
privilege with <command>ulimit</command> before launching
|
||||||
|
<application>MPD</application>:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting>ulimit -HS -r 50; mpd</programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Or you can use the <command>prlimit</command> program from the
|
||||||
|
<application>util-linux</application> package:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting>prlimit --rtprio=50 --rttime=unlimited mpd</programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
The <application>systemd</application> service file shipped
|
||||||
|
with <application>MPD</application> comes with this setting.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
This works only if the Linux kernel was compiled with
|
||||||
|
<varname>CONFIG_RT_GROUP_SCHED</varname> disabled. Use the
|
||||||
|
following command to check this option for your current
|
||||||
|
kernel:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting>zgrep ^CONFIG_RT_GROUP_SCHED /proc/config.gz</programlisting>
|
||||||
|
|
||||||
|
<note>
|
||||||
|
<para>
|
||||||
|
There is a rumor that real-time scheduling improves audio
|
||||||
|
quality. That is not true. All it does is reduce the
|
||||||
|
probability of skipping (audio buffer xruns) when the
|
||||||
|
computer is under heavy load.
|
||||||
|
</para>
|
||||||
|
</note>
|
||||||
|
</section>
|
||||||
</chapter>
|
</chapter>
|
||||||
|
|
||||||
<chapter id="use">
|
<chapter id="use">
|
||||||
|
|
|
@ -28,7 +28,10 @@
|
||||||
|
|
||||||
AvioStream::~AvioStream()
|
AvioStream::~AvioStream()
|
||||||
{
|
{
|
||||||
av_free(io);
|
if (io != nullptr) {
|
||||||
|
av_free(io->buffer);
|
||||||
|
av_free(io);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
inline int
|
inline int
|
||||||
|
@ -90,9 +93,18 @@ AvioStream::_Seek(void *opaque, int64_t pos, int whence)
|
||||||
bool
|
bool
|
||||||
AvioStream::Open()
|
AvioStream::Open()
|
||||||
{
|
{
|
||||||
io = avio_alloc_context(buffer, sizeof(buffer),
|
constexpr size_t BUFFER_SIZE = 8192;
|
||||||
|
auto buffer = (unsigned char *)av_malloc(BUFFER_SIZE);
|
||||||
|
if (buffer == nullptr)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
io = avio_alloc_context(buffer, BUFFER_SIZE,
|
||||||
false, this,
|
false, this,
|
||||||
_Read, nullptr,
|
_Read, nullptr,
|
||||||
input.IsSeekable() ? _Seek : nullptr);
|
input.IsSeekable() ? _Seek : nullptr);
|
||||||
|
/* If avio_alloc_context() fails, who frees the buffer? The
|
||||||
|
libavformat API documentation does not specify this, it
|
||||||
|
only says that AVIOContext.buffer must be freed in the end,
|
||||||
|
however no AVIOContext exists in that failure code path. */
|
||||||
return io != nullptr;
|
return io != nullptr;
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,8 +37,6 @@ struct AvioStream {
|
||||||
|
|
||||||
AVIOContext *io;
|
AVIOContext *io;
|
||||||
|
|
||||||
uint8_t buffer[8192];
|
|
||||||
|
|
||||||
AvioStream(Decoder *_decoder, InputStream &_input)
|
AvioStream(Decoder *_decoder, InputStream &_input)
|
||||||
:decoder(_decoder), input(_input), io(nullptr) {}
|
:decoder(_decoder), input(_input), io(nullptr) {}
|
||||||
|
|
||||||
|
|
|
@ -157,8 +157,11 @@ gme_file_decode(Decoder &decoder, Path path_fs)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const SignedSongTime song_len = ti->length > 0
|
const int length = ti->play_length;
|
||||||
? SignedSongTime::FromMS(ti->length)
|
gme_free_info(ti);
|
||||||
|
|
||||||
|
const SignedSongTime song_len = length > 0
|
||||||
|
? SignedSongTime::FromMS(length)
|
||||||
: SignedSongTime::Negative();
|
: SignedSongTime::Negative();
|
||||||
|
|
||||||
/* initialize the MPD decoder */
|
/* initialize the MPD decoder */
|
||||||
|
@ -169,7 +172,6 @@ gme_file_decode(Decoder &decoder, Path path_fs)
|
||||||
SampleFormat::S16, GME_CHANNELS,
|
SampleFormat::S16, GME_CHANNELS,
|
||||||
error)) {
|
error)) {
|
||||||
LogError(error);
|
LogError(error);
|
||||||
gme_free_info(ti);
|
|
||||||
gme_delete(emu);
|
gme_delete(emu);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -180,8 +182,8 @@ gme_file_decode(Decoder &decoder, Path path_fs)
|
||||||
if (gme_err != nullptr)
|
if (gme_err != nullptr)
|
||||||
LogWarning(gme_domain, gme_err);
|
LogWarning(gme_domain, gme_err);
|
||||||
|
|
||||||
if (ti->length > 0)
|
if (length > 0)
|
||||||
gme_set_fade(emu, ti->length);
|
gme_set_fade(emu, length);
|
||||||
|
|
||||||
/* play */
|
/* play */
|
||||||
DecoderCommand cmd;
|
DecoderCommand cmd;
|
||||||
|
@ -197,16 +199,17 @@ gme_file_decode(Decoder &decoder, Path path_fs)
|
||||||
if (cmd == DecoderCommand::SEEK) {
|
if (cmd == DecoderCommand::SEEK) {
|
||||||
unsigned where = decoder_seek_time(decoder).ToMS();
|
unsigned where = decoder_seek_time(decoder).ToMS();
|
||||||
gme_err = gme_seek(emu, where);
|
gme_err = gme_seek(emu, where);
|
||||||
if (gme_err != nullptr)
|
if (gme_err != nullptr) {
|
||||||
LogWarning(gme_domain, gme_err);
|
LogWarning(gme_domain, gme_err);
|
||||||
decoder_command_finished(decoder);
|
decoder_seek_error(decoder);
|
||||||
|
} else
|
||||||
|
decoder_command_finished(decoder);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (gme_track_ended(emu))
|
if (gme_track_ended(emu))
|
||||||
break;
|
break;
|
||||||
} while (cmd != DecoderCommand::STOP);
|
} while (cmd != DecoderCommand::STOP);
|
||||||
|
|
||||||
gme_free_info(ti);
|
|
||||||
gme_delete(emu);
|
gme_delete(emu);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -214,9 +217,9 @@ static void
|
||||||
ScanGmeInfo(const gme_info_t &info, unsigned song_num, int track_count,
|
ScanGmeInfo(const gme_info_t &info, unsigned song_num, int track_count,
|
||||||
const struct tag_handler *handler, void *handler_ctx)
|
const struct tag_handler *handler, void *handler_ctx)
|
||||||
{
|
{
|
||||||
if (info.length > 0)
|
if (info.play_length > 0)
|
||||||
tag_handler_invoke_duration(handler, handler_ctx,
|
tag_handler_invoke_duration(handler, handler_ctx,
|
||||||
SongTime::FromMS(info.length));
|
SongTime::FromMS(info.play_length));
|
||||||
|
|
||||||
if (info.song != nullptr) {
|
if (info.song != nullptr) {
|
||||||
if (track_count > 1) {
|
if (track_count > 1) {
|
||||||
|
|
|
@ -157,8 +157,6 @@ flac_encoder_open(Encoder *_encoder, AudioFormat &audio_format, Error &error)
|
||||||
struct flac_encoder *encoder = (struct flac_encoder *)_encoder;
|
struct flac_encoder *encoder = (struct flac_encoder *)_encoder;
|
||||||
unsigned bits_per_sample;
|
unsigned bits_per_sample;
|
||||||
|
|
||||||
encoder->audio_format = audio_format;
|
|
||||||
|
|
||||||
/* FIXME: flac should support 32bit as well */
|
/* FIXME: flac should support 32bit as well */
|
||||||
switch (audio_format.format) {
|
switch (audio_format.format) {
|
||||||
case SampleFormat::S8:
|
case SampleFormat::S8:
|
||||||
|
@ -178,6 +176,8 @@ flac_encoder_open(Encoder *_encoder, AudioFormat &audio_format, Error &error)
|
||||||
audio_format.format = SampleFormat::S24_P32;
|
audio_format.format = SampleFormat::S24_P32;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
encoder->audio_format = audio_format;
|
||||||
|
|
||||||
/* allocate the encoder */
|
/* allocate the encoder */
|
||||||
encoder->fse = FLAC__stream_encoder_new();
|
encoder->fse = FLAC__stream_encoder_new();
|
||||||
if (encoder->fse == nullptr) {
|
if (encoder->fse == nullptr) {
|
||||||
|
|
|
@ -30,6 +30,7 @@
|
||||||
#include "Internal.hxx"
|
#include "Internal.hxx"
|
||||||
#include "player/Control.hxx"
|
#include "player/Control.hxx"
|
||||||
#include "mixer/MixerControl.hxx"
|
#include "mixer/MixerControl.hxx"
|
||||||
|
#include "mixer/Volume.hxx"
|
||||||
#include "Idle.hxx"
|
#include "Idle.hxx"
|
||||||
|
|
||||||
extern unsigned audio_output_state_version;
|
extern unsigned audio_output_state_version;
|
||||||
|
@ -47,6 +48,11 @@ audio_output_enable_index(MultipleOutputs &outputs, unsigned idx)
|
||||||
ao.enabled = true;
|
ao.enabled = true;
|
||||||
idle_add(IDLE_OUTPUT);
|
idle_add(IDLE_OUTPUT);
|
||||||
|
|
||||||
|
if (ao.mixer != nullptr) {
|
||||||
|
InvalidateHardwareVolume();
|
||||||
|
idle_add(IDLE_MIXER);
|
||||||
|
}
|
||||||
|
|
||||||
ao.player_control->UpdateAudio();
|
ao.player_control->UpdateAudio();
|
||||||
|
|
||||||
++audio_output_state_version;
|
++audio_output_state_version;
|
||||||
|
@ -70,6 +76,7 @@ audio_output_disable_index(MultipleOutputs &outputs, unsigned idx)
|
||||||
Mixer *mixer = ao.mixer;
|
Mixer *mixer = ao.mixer;
|
||||||
if (mixer != nullptr) {
|
if (mixer != nullptr) {
|
||||||
mixer_close(mixer);
|
mixer_close(mixer);
|
||||||
|
InvalidateHardwareVolume();
|
||||||
idle_add(IDLE_MIXER);
|
idle_add(IDLE_MIXER);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -94,6 +101,7 @@ audio_output_toggle_index(MultipleOutputs &outputs, unsigned idx)
|
||||||
Mixer *mixer = ao.mixer;
|
Mixer *mixer = ao.mixer;
|
||||||
if (mixer != nullptr) {
|
if (mixer != nullptr) {
|
||||||
mixer_close(mixer);
|
mixer_close(mixer);
|
||||||
|
InvalidateHardwareVolume();
|
||||||
idle_add(IDLE_MIXER);
|
idle_add(IDLE_MIXER);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,7 +51,6 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t Play(const void *chunk, size_t size, Error &error);
|
size_t Play(const void *chunk, size_t size, Error &error);
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
inline bool
|
inline bool
|
||||||
|
|
|
@ -79,12 +79,12 @@ ape_scan_internal(FILE *fp, ApeTagCallback callback)
|
||||||
|
|
||||||
/* get the key */
|
/* get the key */
|
||||||
const char *key = p;
|
const char *key = p;
|
||||||
while (remaining > size && *p != '\0') {
|
const char *key_end = (const char *)memchr(p, '\0', remaining);
|
||||||
p++;
|
if (key_end == nullptr)
|
||||||
remaining--;
|
break;
|
||||||
}
|
|
||||||
p++;
|
p = key_end + 1;
|
||||||
remaining--;
|
remaining -= p - key;
|
||||||
|
|
||||||
/* get the value */
|
/* get the value */
|
||||||
if (remaining < size)
|
if (remaining < size)
|
||||||
|
|
|
@ -10,15 +10,6 @@ ExecStart=@prefix@/bin/mpd --no-daemon
|
||||||
LimitRTPRIO=50
|
LimitRTPRIO=50
|
||||||
LimitRTTIME=-1
|
LimitRTTIME=-1
|
||||||
|
|
||||||
# move MPD to a top-level cgroup, as real-time budget assignment fails
|
|
||||||
# in cgroup /system/mpd.service, because /system has a zero real-time
|
|
||||||
# budget; see
|
|
||||||
# http://www.freedesktop.org/wiki/Software/systemd/MyServiceCantGetRealtime/
|
|
||||||
ControlGroup=cpu:/mpd
|
|
||||||
|
|
||||||
# assign a real-time budget
|
|
||||||
ControlGroupAttribute=cpu.rt_runtime_us 500000
|
|
||||||
|
|
||||||
# disallow writing to /usr, /bin, /sbin, ...
|
# disallow writing to /usr, /bin, /sbin, ...
|
||||||
ProtectSystem=yes
|
ProtectSystem=yes
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue