Compare commits

...

26 Commits

Author SHA1 Message Date
Max Kellermann
2784d65618 release v0.18.9 2014-03-02 11:25:01 +01:00
Max Kellermann
47ea69233b output/alsa: remove the obsolete Raspberry Pi workaround
Has been superseded by the previous commit.
2014-03-02 11:22:04 +01:00
Max Kellermann
a884e37de1 output/alsa: call snd_pcm_prepare() after snd_pcm_drop()
Don't wait for an optimistic write to fail.  This is an improved
workaround for the infamous Raspberry Pi bug (see commit af991765).
It works much better and comes without the negative side effects.  The
old workaround is now obsolete.
2014-03-02 11:12:25 +01:00
Max Kellermann
0102a8665a event/SignalMonitor: fix build failure due to missing signal.h include 2014-03-02 10:21:31 +01:00
Max Kellermann
d34ae0850c AllCommands: "findadd" requires the "add" permission 2014-02-27 23:08:22 +01:00
Max Kellermann
6526de024a output/pulse: remove bogus g_free() call 2014-02-24 21:23:49 +01:00
Max Kellermann
5e1e92626c event/SignalMonitor: unblock signals after fork
Fixes hanging child process in the "pipe" output plugin.
2014-02-18 19:13:50 +01:00
Max Kellermann
7fee85c80a configure.ac: fix linker failure when libvorbis/libogg are static
Link libvorbisfile first, followed to libvorbis and finally libogg.
This order is necessary because libvorbisfile depends on libvorbis.
2014-02-18 18:39:19 +01:00
Max Kellermann
5d87a274a5 configure.ac: link the Vorbis encoder with libogg
Fixes another linker failure.  Similar to commit ea406875
2014-02-17 19:42:38 +01:00
Max Kellermann
57e862712a configure.ac: prepare for 0.18.9 2014-02-09 22:58:14 +01:00
Max Kellermann
ddb5390d88 release v0.18.8 2014-02-07 00:06:31 +01:00
Max Kellermann
fce20e514e NEWS: fix 0.18.7 release year 2014-02-07 00:06:31 +01:00
Max Kellermann
af66ed2505 doc/user: document the RoarAudio output plugin 2014-02-06 21:46:29 +01:00
Max Kellermann
ea4068757d configure.ac: link the Vorbis encoder with libvorbis
Since the encoder plugin uses a libvorbis function (and not only
libvorbisenc functions), we need to link with libvorbis explicitly.
2014-02-06 21:32:50 +01:00
Max Kellermann
2b10ecfa37 IcyMetadataParser: more robust tag parser
Allow semicolons and single quotes in the stream title.  This is not
part of any specification, but found in real life.
2014-01-27 10:08:21 +01:00
Max Kellermann
f7eb2b697e test/test_icy_parser: unit test for IcyMetaDataParser.cxx 2014-01-27 09:51:31 +01:00
Max Kellermann
da67260c95 new developer mailing list 2014-01-20 17:20:57 +01:00
Max Kellermann
ab9c9068d4 Queue: rename struct queue to Queue
Works around a build failure on Solaris because annoyingly, Solaris
reserves the name "queue".  This rename was pending anyway.
2014-01-20 08:57:46 +01:00
Max Kellermann
6b4d7d7315 Queue: make the constructor "explicit" 2014-01-20 08:57:41 +01:00
Max Kellermann
313d1d5d83 decoder/ffmpeg: support libav v10_alpha1 2014-01-15 11:33:18 +01:00
Max Kellermann
b7d6133593 decoder/ffmpeg: include cleanup 2014-01-15 11:31:51 +01:00
Max Kellermann
5b6bb114ad decoder/ffmpeg: check for av_samples_get_buffer_size() errors
Fixes potential nullptr dereference.
2014-01-15 11:25:58 +01:00
Max Kellermann
56f082c9d4 util/PeakBuffer: fix nullptr dereference when peak_size==0 2014-01-15 11:24:29 +01:00
Max Kellermann
a1b798e555 SongFilter, TagConfig: cast TAG_NUM_OF_ITEM_TYPES to integer
Fixes clang warning.
2014-01-15 11:23:41 +01:00
Max Kellermann
c91e08fbfd OutputAPI: fix typo in include guard 2014-01-15 11:22:59 +01:00
Max Kellermann
f882434547 configure.ac: prepare for 0.18.8 2014-01-15 11:22:06 +01:00
28 changed files with 362 additions and 127 deletions

View File

@@ -1061,6 +1061,7 @@ C_TESTS = \
test/test_util \ test/test_util \
test/test_byte_reverse \ test/test_byte_reverse \
test/test_mixramp \ test/test_mixramp \
test/test_icy_parser \
test/test_pcm \ test/test_pcm \
test/test_queue_priority test/test_queue_priority
@@ -1496,6 +1497,16 @@ test_test_mixramp_LDADD = \
$(GLIB_LIBS) \ $(GLIB_LIBS) \
$(CPPUNIT_LIBS) $(CPPUNIT_LIBS)
test_test_icy_parser_SOURCES = \
src/Log.cxx \
test/test_icy_parser.cxx
test_test_icy_parser_CPPFLAGS = $(AM_CPPFLAGS) $(CPPUNIT_CFLAGS) -DCPPUNIT_HAVE_RTTI=0
test_test_icy_parser_CXXFLAGS = $(AM_CXXFLAGS) -Wno-error=deprecated-declarations
test_test_icy_parser_LDADD = \
libtag.a \
$(GLIB_LIBS) \
$(CPPUNIT_LIBS)
test_test_pcm_SOURCES = \ test_test_pcm_SOURCES = \
test/test_pcm_util.hxx \ test/test_pcm_util.hxx \
test/test_pcm_dither.cxx \ test/test_pcm_dither.cxx \

25
NEWS
View File

@@ -1,4 +1,27 @@
ver 0.18.7 (2013/01/13) ver 0.18.9 (2014/03/02)
* protocol
- "findadd" requires the "add" permission
* output
- alsa: improved workaround for noise after manual song change
* decoder
- vorbis: fix linker failure when libvorbis/libogg are static
* encoder
- vorbis: fix another linker failure
* output
- pipe: fix hanging child process due to blocked signals
* fix build failure due to missing signal.h include
ver 0.18.8 (2014/02/07)
* decoder
- ffmpeg: support libav v10_alpha1
* encoder
- vorbis: fix linker failure
* output
- roar: documentation
* more robust Icy-Metadata parser
* fix Solaris build failure
ver 0.18.7 (2014/01/13)
* playlist * playlist
- pls: fix crash after parser error - pls: fix crash after parser error
- soundcloud: fix build failure with libyajl 2.0.1 - soundcloud: fix build failure with libyajl 2.0.1

View File

@@ -1,6 +1,6 @@
AC_PREREQ(2.60) AC_PREREQ(2.60)
AC_INIT(mpd, 0.18.7, musicpd-dev-team@lists.sourceforge.net) AC_INIT(mpd, 0.18.9, mpd-devel@musicpd.org)
VERSION_MAJOR=0 VERSION_MAJOR=0
VERSION_MINOR=18 VERSION_MINOR=18
@@ -1023,7 +1023,7 @@ if test x$enable_tremor = xyes; then
fi fi
fi fi
MPD_AUTO_PKG(vorbis, VORBIS, [vorbis vorbisfile ogg], MPD_AUTO_PKG(vorbis, VORBIS, [vorbisfile vorbis ogg],
[Ogg Vorbis decoder], [libvorbis not found]) [Ogg Vorbis decoder], [libvorbis not found])
if test x$enable_vorbis = xyes; then if test x$enable_vorbis = xyes; then
AC_DEFINE(ENABLE_VORBIS_DECODER, 1, [Define for Ogg Vorbis support]) AC_DEFINE(ENABLE_VORBIS_DECODER, 1, [Define for Ogg Vorbis support])
@@ -1139,7 +1139,7 @@ fi
AM_CONDITIONAL(ENABLE_FLAC_ENCODER, test x$enable_flac_encoder = xyes) AM_CONDITIONAL(ENABLE_FLAC_ENCODER, test x$enable_flac_encoder = xyes)
dnl ---------------------------- Ogg Vorbis Encoder --------------------------- dnl ---------------------------- Ogg Vorbis Encoder ---------------------------
MPD_AUTO_PKG(vorbis_encoder, VORBISENC, [vorbisenc], MPD_AUTO_PKG(vorbis_encoder, VORBISENC, [vorbisenc vorbis ogg],
[Ogg Vorbis encoder], [libvorbisenc not found]) [Ogg Vorbis encoder], [libvorbisenc not found])
if test x$enable_vorbis_encoder = xyes; then if test x$enable_vorbis_encoder = xyes; then

View File

@@ -155,7 +155,7 @@ foo(const char *abc, int xyz)
<para> <para>
Send your patches to the mailing list: Send your patches to the mailing list:
musicpd-dev-team@lists.sourceforge.net mpd-devel@musicpd.org
</para> </para>
</chapter> </chapter>
</book> </book>

View File

@@ -1852,6 +1852,51 @@ systemctl start mpd.socket</programlisting>
</informaltable> </informaltable>
</section> </section>
<section>
<title><varname>roar</varname></title>
<para>
The <varname>roar</varname> plugin connects to a <ulink
url="http://roaraudio.keep-cool.org/">RoarAudio</ulink>
server.
</para>
<informaltable>
<tgroup cols="2">
<thead>
<row>
<entry>Setting</entry>
<entry>Description</entry>
</row>
</thead>
<tbody>
<row>
<entry>
<varname>server</varname>
<parameter>HOSTNAME</parameter>
</entry>
<entry>
The host name of the RoarAudio server. If not
specified, then MPD will connect to the default
locations.
</entry>
</row>
<row>
<entry>
<varname>role</varname>
<parameter>ROLE</parameter>
</entry>
<entry>
The "role" that MPD registers itself as in the
RoarAudio server. The default is "music".
</entry>
</row>
</tbody>
</tgroup>
</informaltable>
</section>
<section> <section>
<title><varname>recorder</varname></title> <title><varname>recorder</varname></title>

View File

@@ -81,31 +81,85 @@ icy_add_item(Tag &tag, TagType type, const char *value)
} }
static void static void
icy_parse_tag_item(Tag &tag, const char *item) icy_parse_tag_item(Tag &tag, const char *name, const char *value)
{ {
gchar **p = g_strsplit(item, "=", 0); if (strcmp(name, "StreamTitle") == 0)
icy_add_item(tag, TAG_TITLE, value);
else
FormatDebug(icy_metadata_domain,
"unknown icy-tag: '%s'", name);
}
if (p[0] != nullptr && p[1] != nullptr) { /**
if (strcmp(p[0], "StreamTitle") == 0) * Find a single quote that is followed by a semicolon (or by the end
icy_add_item(tag, TAG_TITLE, p[1]); * of the string). If that fails, return the first single quote. If
else * that also fails, return #end.
FormatDebug(icy_metadata_domain, */
"unknown icy-tag: '%s'", p[0]); static char *
find_end_quote(char *p, char *const end)
{
char *fallback = std::find(p, end, '\'');
if (fallback >= end - 1 || fallback[1] == ';')
return fallback;
p = fallback + 1;
while (true) {
p = std::find(p, end, '\'');
if (p == end)
return fallback;
if (p == end - 1 || p[1] == ';')
return p;
++p;
} }
g_strfreev(p);
} }
static Tag * static Tag *
icy_parse_tag(const char *p) icy_parse_tag(char *p, char *const end)
{ {
assert(p != nullptr);
assert(end != nullptr);
assert(p <= end);
Tag *tag = new Tag(); Tag *tag = new Tag();
gchar **items = g_strsplit(p, ";", 0);
for (unsigned i = 0; items[i] != nullptr; ++i) while (p != end) {
icy_parse_tag_item(*tag, items[i]); const char *const name = p;
char *eq = std::find(p, end, '=');
if (eq == end)
break;
g_strfreev(items); *eq = 0;
p = eq + 1;
if (*p != '\'') {
/* syntax error; skip to the next semicolon,
try to recover */
char *semicolon = std::find(p, end, ';');
if (semicolon == end)
break;
p = semicolon + 1;
continue;
}
++p;
const char *const value = p;
char *quote = find_end_quote(p, end);
if (quote == end)
break;
*quote = 0;
p = quote + 1;
icy_parse_tag_item(*tag, name, value);
char *semicolon = std::find(p, end, ';');
if (semicolon == end)
break;
p = semicolon + 1;
}
return tag; return tag;
} }
@@ -152,15 +206,11 @@ IcyMetaDataParser::Meta(const void *data, size_t length)
++length; ++length;
if (meta_position == meta_size) { if (meta_position == meta_size) {
/* null-terminate the string */
meta_data[meta_size] = 0;
/* parse */ /* parse */
delete tag; delete tag;
tag = icy_parse_tag(meta_data); tag = icy_parse_tag(meta_data, meta_data + meta_size);
g_free(meta_data); g_free(meta_data);
/* change back to normal data mode */ /* change back to normal data mode */

View File

@@ -18,7 +18,7 @@
*/ */
#ifndef MPD_OUTPUT_API_HXX #ifndef MPD_OUTPUT_API_HXX
#define MPD_OUTPUT_API_HxX #define MPD_OUTPUT_API_HXX
#include "OutputPlugin.hxx" #include "OutputPlugin.hxx"
#include "OutputInternal.hxx" #include "OutputInternal.hxx"

View File

@@ -30,7 +30,7 @@ struct playlist {
/** /**
* The song queue - it contains the "real" playlist. * The song queue - it contains the "real" playlist.
*/ */
struct queue queue; struct Queue queue;
/** /**
* This value is true if the player is currently playing (or * This value is true if the player is currently playing (or

View File

@@ -40,7 +40,7 @@
void void
playlist_print_uris(Client &client, const playlist &playlist) playlist_print_uris(Client &client, const playlist &playlist)
{ {
const queue &queue = playlist.queue; const Queue &queue = playlist.queue;
queue_print_uris(client, queue, 0, queue.GetLength()); queue_print_uris(client, queue, 0, queue.GetLength());
} }
@@ -49,7 +49,7 @@ bool
playlist_print_info(Client &client, const playlist &playlist, playlist_print_info(Client &client, const playlist &playlist,
unsigned start, unsigned end) unsigned start, unsigned end)
{ {
const queue &queue = playlist.queue; const Queue &queue = playlist.queue;
if (end > queue.GetLength()) if (end > queue.GetLength())
/* correct the "end" offset */ /* correct the "end" offset */

View File

@@ -65,7 +65,7 @@ playlist_print_uri(FILE *file, const char *uri)
} }
PlaylistResult PlaylistResult
spl_save_queue(const char *name_utf8, const queue &queue) spl_save_queue(const char *name_utf8, const Queue &queue)
{ {
if (map_spl_path().IsNull()) if (map_spl_path().IsNull())
return PlaylistResult::DISABLED; return PlaylistResult::DISABLED;

View File

@@ -25,7 +25,7 @@
#include <stdio.h> #include <stdio.h>
struct Song; struct Song;
struct queue; struct Queue;
struct playlist; struct playlist;
struct PlayerControl; struct PlayerControl;
class Error; class Error;
@@ -40,7 +40,7 @@ playlist_print_uri(FILE *fp, const char *uri);
* Saves a queue object into a stored playlist file. * Saves a queue object into a stored playlist file.
*/ */
PlaylistResult PlaylistResult
spl_save_queue(const char *name_utf8, const queue &queue); spl_save_queue(const char *name_utf8, const Queue &queue);
/** /**
* Saves a playlist object into a stored playlist file. * Saves a playlist object into a stored playlist file.

View File

@@ -23,7 +23,7 @@
#include <stdlib.h> #include <stdlib.h>
queue::queue(unsigned _max_length) Queue::Queue(unsigned _max_length)
:max_length(_max_length), length(0), :max_length(_max_length), length(0),
version(1), version(1),
items(new Item[max_length]), items(new Item[max_length]),
@@ -36,7 +36,7 @@ queue::queue(unsigned _max_length)
{ {
} }
queue::~queue() Queue::~Queue()
{ {
Clear(); Clear();
@@ -45,7 +45,7 @@ queue::~queue()
} }
int int
queue::GetNextOrder(unsigned _order) const Queue::GetNextOrder(unsigned _order) const
{ {
assert(_order < length); assert(_order < length);
@@ -62,7 +62,7 @@ queue::GetNextOrder(unsigned _order) const
} }
void void
queue::IncrementVersion() Queue::IncrementVersion()
{ {
static unsigned long max = ((uint32_t) 1 << 31) - 1; static unsigned long max = ((uint32_t) 1 << 31) - 1;
@@ -77,7 +77,7 @@ queue::IncrementVersion()
} }
void void
queue::ModifyAtOrder(unsigned _order) Queue::ModifyAtOrder(unsigned _order)
{ {
assert(_order < length); assert(_order < length);
@@ -86,7 +86,7 @@ queue::ModifyAtOrder(unsigned _order)
} }
unsigned unsigned
queue::Append(Song *song, uint8_t priority) Queue::Append(Song *song, uint8_t priority)
{ {
assert(!IsFull()); assert(!IsFull());
@@ -105,7 +105,7 @@ queue::Append(Song *song, uint8_t priority)
} }
void void
queue::SwapPositions(unsigned position1, unsigned position2) Queue::SwapPositions(unsigned position1, unsigned position2)
{ {
unsigned id1 = items[position1].id; unsigned id1 = items[position1].id;
unsigned id2 = items[position2].id; unsigned id2 = items[position2].id;
@@ -120,7 +120,7 @@ queue::SwapPositions(unsigned position1, unsigned position2)
} }
void void
queue::MovePostion(unsigned from, unsigned to) Queue::MovePostion(unsigned from, unsigned to)
{ {
const Item tmp = items[from]; const Item tmp = items[from];
@@ -156,7 +156,7 @@ queue::MovePostion(unsigned from, unsigned to)
} }
void void
queue::MoveRange(unsigned start, unsigned end, unsigned to) Queue::MoveRange(unsigned start, unsigned end, unsigned to)
{ {
Item tmp[end - start]; Item tmp[end - start];
// Copy the original block [start,end-1] // Copy the original block [start,end-1]
@@ -198,7 +198,7 @@ queue::MoveRange(unsigned start, unsigned end, unsigned to)
} }
void void
queue::MoveOrder(unsigned from_order, unsigned to_order) Queue::MoveOrder(unsigned from_order, unsigned to_order)
{ {
assert(from_order < length); assert(from_order < length);
assert(to_order <= length); assert(to_order <= length);
@@ -217,7 +217,7 @@ queue::MoveOrder(unsigned from_order, unsigned to_order)
} }
void void
queue::DeletePosition(unsigned position) Queue::DeletePosition(unsigned position)
{ {
assert(position < length); assert(position < length);
@@ -254,7 +254,7 @@ queue::DeletePosition(unsigned position)
} }
void void
queue::Clear() Queue::Clear()
{ {
for (unsigned i = 0; i < length; i++) { for (unsigned i = 0; i < length; i++) {
Item *item = &items[i]; Item *item = &items[i];
@@ -270,7 +270,7 @@ queue::Clear()
} }
static void static void
queue_sort_order_by_priority(struct queue *queue, unsigned start, unsigned end) queue_sort_order_by_priority(Queue *queue, unsigned start, unsigned end)
{ {
assert(queue != nullptr); assert(queue != nullptr);
assert(queue->random); assert(queue->random);
@@ -278,8 +278,8 @@ queue_sort_order_by_priority(struct queue *queue, unsigned start, unsigned end)
assert(end <= queue->length); assert(end <= queue->length);
auto cmp = [queue](unsigned a_pos, unsigned b_pos){ auto cmp = [queue](unsigned a_pos, unsigned b_pos){
const queue::Item &a = queue->items[a_pos]; const Queue::Item &a = queue->items[a_pos];
const queue::Item &b = queue->items[b_pos]; const Queue::Item &b = queue->items[b_pos];
return a.priority > b.priority; return a.priority > b.priority;
}; };
@@ -288,7 +288,7 @@ queue_sort_order_by_priority(struct queue *queue, unsigned start, unsigned end)
} }
void void
queue::ShuffleOrderRange(unsigned start, unsigned end) Queue::ShuffleOrderRange(unsigned start, unsigned end)
{ {
assert(random); assert(random);
assert(start <= end); assert(start <= end);
@@ -303,7 +303,7 @@ queue::ShuffleOrderRange(unsigned start, unsigned end)
* priority group. * priority group.
*/ */
void void
queue::ShuffleOrderRangeWithPriority(unsigned start, unsigned end) Queue::ShuffleOrderRangeWithPriority(unsigned start, unsigned end)
{ {
assert(random); assert(random);
assert(start <= end); assert(start <= end);
@@ -337,13 +337,13 @@ queue::ShuffleOrderRangeWithPriority(unsigned start, unsigned end)
} }
void void
queue::ShuffleOrder() Queue::ShuffleOrder()
{ {
ShuffleOrderRangeWithPriority(0, length); ShuffleOrderRangeWithPriority(0, length);
} }
void void
queue::ShuffleOrderFirst(unsigned start, unsigned end) Queue::ShuffleOrderFirst(unsigned start, unsigned end)
{ {
rand.AutoCreate(); rand.AutoCreate();
@@ -352,7 +352,7 @@ queue::ShuffleOrderFirst(unsigned start, unsigned end)
} }
void void
queue::ShuffleOrderLast(unsigned start, unsigned end) Queue::ShuffleOrderLast(unsigned start, unsigned end)
{ {
rand.AutoCreate(); rand.AutoCreate();
@@ -361,7 +361,7 @@ queue::ShuffleOrderLast(unsigned start, unsigned end)
} }
void void
queue::ShuffleRange(unsigned start, unsigned end) Queue::ShuffleRange(unsigned start, unsigned end)
{ {
assert(start <= end); assert(start <= end);
assert(end <= length); assert(end <= length);
@@ -377,7 +377,7 @@ queue::ShuffleRange(unsigned start, unsigned end)
} }
unsigned unsigned
queue::FindPriorityOrder(unsigned start_order, uint8_t priority, Queue::FindPriorityOrder(unsigned start_order, uint8_t priority,
unsigned exclude_order) const unsigned exclude_order) const
{ {
assert(random); assert(random);
@@ -394,7 +394,7 @@ queue::FindPriorityOrder(unsigned start_order, uint8_t priority,
} }
unsigned unsigned
queue::CountSamePriority(unsigned start_order, uint8_t priority) const Queue::CountSamePriority(unsigned start_order, uint8_t priority) const
{ {
assert(random); assert(random);
assert(start_order <= length); assert(start_order <= length);
@@ -410,7 +410,7 @@ queue::CountSamePriority(unsigned start_order, uint8_t priority) const
} }
bool bool
queue::SetPriority(unsigned position, uint8_t priority, int after_order) Queue::SetPriority(unsigned position, uint8_t priority, int after_order)
{ {
assert(position < length); assert(position < length);
@@ -468,7 +468,7 @@ queue::SetPriority(unsigned position, uint8_t priority, int after_order)
} }
bool bool
queue::SetPriorityRange(unsigned start_position, unsigned end_position, Queue::SetPriorityRange(unsigned start_position, unsigned end_position,
uint8_t priority, int after_order) uint8_t priority, int after_order)
{ {
assert(start_position <= end_position); assert(start_position <= end_position);

View File

@@ -41,7 +41,7 @@ struct Song;
* - the unique id (which stays the same, regardless of moves) * - the unique id (which stays the same, regardless of moves)
* - the order number (which only differs from "position" in random mode) * - the order number (which only differs from "position" in random mode)
*/ */
struct queue { struct Queue {
/** /**
* reserve max_length * HASH_MULT elements in the id * reserve max_length * HASH_MULT elements in the id
* number space * number space
@@ -103,16 +103,16 @@ struct queue {
/** random number generator for shuffle and random mode */ /** random number generator for shuffle and random mode */
LazyRandomEngine rand; LazyRandomEngine rand;
queue(unsigned max_length); explicit Queue(unsigned max_length);
/** /**
* Deinitializes a queue object. It does not free the queue * Deinitializes a queue object. It does not free the queue
* pointer itself. * pointer itself.
*/ */
~queue(); ~Queue();
queue(const queue &other) = delete; Queue(const Queue &) = delete;
queue &operator=(const queue &other) = delete; Queue &operator=(const Queue &) = delete;
unsigned GetLength() const { unsigned GetLength() const {
assert(length <= max_length); assert(length <= max_length);

View File

@@ -38,7 +38,7 @@ extern "C" {
* @param end the index of the last song (excluding) * @param end the index of the last song (excluding)
*/ */
static void static void
queue_print_song_info(Client &client, const queue &queue, queue_print_song_info(Client &client, const Queue &queue,
unsigned position) unsigned position)
{ {
song_print_info(client, queue.Get(position)); song_print_info(client, queue.Get(position));
@@ -51,7 +51,7 @@ queue_print_song_info(Client &client, const queue &queue,
} }
void void
queue_print_info(Client &client, const queue &queue, queue_print_info(Client &client, const Queue &queue,
unsigned start, unsigned end) unsigned start, unsigned end)
{ {
assert(start <= end); assert(start <= end);
@@ -62,7 +62,7 @@ queue_print_info(Client &client, const queue &queue,
} }
void void
queue_print_uris(Client &client, const queue &queue, queue_print_uris(Client &client, const Queue &queue,
unsigned start, unsigned end) unsigned start, unsigned end)
{ {
assert(start <= end); assert(start <= end);
@@ -75,7 +75,7 @@ queue_print_uris(Client &client, const queue &queue,
} }
void void
queue_print_changes_info(Client &client, const queue &queue, queue_print_changes_info(Client &client, const Queue &queue,
uint32_t version) uint32_t version)
{ {
for (unsigned i = 0; i < queue.GetLength(); i++) { for (unsigned i = 0; i < queue.GetLength(); i++) {
@@ -85,7 +85,7 @@ queue_print_changes_info(Client &client, const queue &queue,
} }
void void
queue_print_changes_position(Client &client, const queue &queue, queue_print_changes_position(Client &client, const Queue &queue,
uint32_t version) uint32_t version)
{ {
for (unsigned i = 0; i < queue.GetLength(); i++) for (unsigned i = 0; i < queue.GetLength(); i++)
@@ -95,7 +95,7 @@ queue_print_changes_position(Client &client, const queue &queue,
} }
void void
queue_find(Client &client, const queue &queue, queue_find(Client &client, const Queue &queue,
const SongFilter &filter) const SongFilter &filter)
{ {
for (unsigned i = 0; i < queue.GetLength(); i++) { for (unsigned i = 0; i < queue.GetLength(); i++) {

View File

@@ -27,28 +27,28 @@
#include <stdint.h> #include <stdint.h>
struct queue; struct Queue;
class SongFilter; class SongFilter;
class Client; class Client;
void void
queue_print_info(Client &client, const queue &queue, queue_print_info(Client &client, const Queue &queue,
unsigned start, unsigned end); unsigned start, unsigned end);
void void
queue_print_uris(Client &client, const queue &queue, queue_print_uris(Client &client, const Queue &queue,
unsigned start, unsigned end); unsigned start, unsigned end);
void void
queue_print_changes_info(Client &client, const queue &queue, queue_print_changes_info(Client &client, const Queue &queue,
uint32_t version); uint32_t version);
void void
queue_print_changes_position(Client &client, const queue &queue, queue_print_changes_position(Client &client, const Queue &queue,
uint32_t version); uint32_t version);
void void
queue_find(Client &client, const queue &queue, queue_find(Client &client, const Queue &queue,
const SongFilter &filter); const SongFilter &filter);
#endif #endif

View File

@@ -60,7 +60,7 @@ queue_save_song(FILE *fp, int idx, const Song &song)
} }
void void
queue_save(FILE *fp, const queue &queue) queue_save(FILE *fp, const Queue &queue)
{ {
for (unsigned i = 0; i < queue.GetLength(); i++) { for (unsigned i = 0; i < queue.GetLength(); i++) {
uint8_t prio = queue.GetPriorityAtPosition(i); uint8_t prio = queue.GetPriorityAtPosition(i);
@@ -72,7 +72,7 @@ queue_save(FILE *fp, const queue &queue)
} }
void void
queue_load_song(TextFile &file, const char *line, queue &queue) queue_load_song(TextFile &file, const char *line, Queue &queue)
{ {
if (queue.IsFull()) if (queue.IsFull())
return; return;

View File

@@ -27,16 +27,16 @@
#include <stdio.h> #include <stdio.h>
struct queue; struct Queue;
class TextFile; class TextFile;
void void
queue_save(FILE *fp, const queue &queue); queue_save(FILE *fp, const Queue &queue);
/** /**
* Loads one song from the state file and appends it to the queue. * Loads one song from the state file and appends it to the queue.
*/ */
void void
queue_load_song(TextFile &file, const char *line, queue &queue); queue_load_song(TextFile &file, const char *line, Queue &queue);
#endif #endif

View File

@@ -101,7 +101,7 @@ bool
SongFilter::Item::Match(const Tag &_tag) const SongFilter::Item::Match(const Tag &_tag) const
{ {
bool visited_types[TAG_NUM_OF_ITEM_TYPES]; bool visited_types[TAG_NUM_OF_ITEM_TYPES];
std::fill_n(visited_types, TAG_NUM_OF_ITEM_TYPES, false); std::fill_n(visited_types, size_t(TAG_NUM_OF_ITEM_TYPES), false);
for (unsigned i = 0; i < _tag.num_items; i++) { for (unsigned i = 0; i < _tag.num_items; i++) {
visited_types[_tag.items[i]->type] = true; visited_types[_tag.items[i]->type] = true;

View File

@@ -90,7 +90,7 @@ static const struct command commands[] = {
{ "disableoutput", PERMISSION_ADMIN, 1, 1, handle_disableoutput }, { "disableoutput", PERMISSION_ADMIN, 1, 1, handle_disableoutput },
{ "enableoutput", PERMISSION_ADMIN, 1, 1, handle_enableoutput }, { "enableoutput", PERMISSION_ADMIN, 1, 1, handle_enableoutput },
{ "find", PERMISSION_READ, 2, -1, handle_find }, { "find", PERMISSION_READ, 2, -1, handle_find },
{ "findadd", PERMISSION_READ, 2, -1, handle_findadd}, { "findadd", PERMISSION_ADD, 2, -1, handle_findadd},
{ "idle", PERMISSION_READ, 0, -1, handle_idle }, { "idle", PERMISSION_READ, 0, -1, handle_idle },
{ "kill", PERMISSION_ADMIN, -1, -1, handle_kill }, { "kill", PERMISSION_ADMIN, -1, -1, handle_kill },
{ "list", PERMISSION_READ, 1, -1, handle_list }, { "list", PERMISSION_READ, 1, -1, handle_list },

View File

@@ -38,7 +38,10 @@ extern "C" {
#include <libavutil/avutil.h> #include <libavutil/avutil.h>
#include <libavutil/log.h> #include <libavutil/log.h>
#include <libavutil/mathematics.h> #include <libavutil/mathematics.h>
#include <libavutil/dict.h>
#if LIBAVUTIL_VERSION_MAJOR >= 53
#include <libavutil/frame.h>
#endif
} }
#include <assert.h> #include <assert.h>
@@ -223,6 +226,9 @@ copy_interleave_frame(const AVCodecContext *codec_context,
codec_context->channels, codec_context->channels,
frame->nb_samples, frame->nb_samples,
codec_context->sample_fmt, 1); codec_context->sample_fmt, 1);
if (data_size <= 0)
return data_size;
if (av_sample_fmt_is_planar(codec_context->sample_fmt) && if (av_sample_fmt_is_planar(codec_context->sample_fmt) &&
codec_context->channels > 1) { codec_context->channels > 1) {
if(*global_buffer_size < data_size) { if(*global_buffer_size < data_size) {
@@ -451,7 +457,11 @@ ffmpeg_decode(Decoder &decoder, InputStream &input)
decoder_initialized(decoder, audio_format, decoder_initialized(decoder, audio_format,
input.seekable, total_time); input.seekable, total_time);
#if LIBAVUTIL_VERSION_MAJOR >= 53
AVFrame *frame = av_frame_alloc();
#else
AVFrame *frame = avcodec_alloc_frame(); AVFrame *frame = avcodec_alloc_frame();
#endif
if (!frame) { if (!frame) {
LogError(ffmpeg_domain, "Could not allocate frame"); LogError(ffmpeg_domain, "Could not allocate frame");
avformat_close_input(&format_context); avformat_close_input(&format_context);
@@ -495,7 +505,9 @@ ffmpeg_decode(Decoder &decoder, InputStream &input)
} }
} while (cmd != DecoderCommand::STOP); } while (cmd != DecoderCommand::STOP);
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(54, 28, 0) #if LIBAVUTIL_VERSION_MAJOR >= 53
av_frame_free(&frame);
#elif LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(54, 28, 0)
avcodec_free_frame(&frame); avcodec_free_frame(&frame);
#else #else
av_freep(&frame); av_freep(&frame);

View File

@@ -21,8 +21,6 @@
#define MPD_FFMPEG_METADATA_HXX #define MPD_FFMPEG_METADATA_HXX
extern "C" { extern "C" {
#include <libavformat/avformat.h>
#include <libavutil/avutil.h>
#include <libavutil/dict.h> #include <libavutil/dict.h>
} }
@@ -35,6 +33,6 @@ struct tag_handler;
void void
ffmpeg_scan_dictionary(AVDictionary *dict, ffmpeg_scan_dictionary(AVDictionary *dict,
const struct tag_handler *handler, void *handler_ctx); const tag_handler *handler, void *handler_ctx);
#endif #endif

View File

@@ -39,6 +39,12 @@
#include <algorithm> #include <algorithm>
#ifdef USE_SIGNALFD
#include <pthread.h>
#endif
#include <signal.h>
class SignalMonitor final : private SocketMonitor { class SignalMonitor final : private SocketMonitor {
#ifdef USE_SIGNALFD #ifdef USE_SIGNALFD
SignalFD fd; SignalFD fd;
@@ -99,7 +105,21 @@ static std::atomic_bool signal_pending[MAX_SIGNAL];
static Manual<SignalMonitor> monitor; static Manual<SignalMonitor> monitor;
#ifndef USE_SIGNALFD #ifdef USE_SIGNALFD
/**
* This is a pthread_atfork() callback that unblocks the signals that
* were blocked for our signalfd(). Without this, our child processes
* would inherit the blocked signals.
*/
static void
at_fork_child()
{
sigprocmask(SIG_UNBLOCK, &signal_mask, nullptr);
}
#else
static void static void
SignalCallback(int signo) SignalCallback(int signo)
{ {
@@ -108,6 +128,7 @@ SignalCallback(int signo)
if (!signal_pending[signo].exchange(true)) if (!signal_pending[signo].exchange(true))
monitor->WakeUp(); monitor->WakeUp();
} }
#endif #endif
void void
@@ -115,6 +136,8 @@ SignalMonitorInit(EventLoop &loop)
{ {
#ifdef USE_SIGNALFD #ifdef USE_SIGNALFD
sigemptyset(&signal_mask); sigemptyset(&signal_mask);
pthread_atfork(nullptr, nullptr, at_fork_child);
#endif #endif
monitor.Construct(loop); monitor.Construct(loop);

View File

@@ -106,12 +106,16 @@ struct AlsaOutput {
snd_pcm_uframes_t period_position; snd_pcm_uframes_t period_position;
/** /**
* Set to non-zero when the Raspberry Pi workaround has been * Do we need to call snd_pcm_prepare() before the next write?
* activated in alsa_recover(); decremented by each write. * It means that we put the device to SND_PCM_STATE_SETUP by
* This will avoid activating it again, leading to an endless * calling snd_pcm_drop().
* loop. This problem was observed with a "RME Digi9636/52". *
* Without this flag, we could easily recover after a failed
* optimistic write (returning -EBADFD), but the Raspberry Pi
* audio driver is infamous for generating ugly artefacts from
* this.
*/ */
unsigned pi_workaround; bool must_prepare;
/** /**
* This buffer gets allocated after opening the ALSA device. * This buffer gets allocated after opening the ALSA device.
@@ -676,8 +680,6 @@ alsa_open(struct audio_output *ao, AudioFormat &audio_format, Error &error)
{ {
AlsaOutput *ad = (AlsaOutput *)ao; AlsaOutput *ad = (AlsaOutput *)ao;
ad->pi_workaround = 0;
int err = snd_pcm_open(&ad->pcm, alsa_device(ad), int err = snd_pcm_open(&ad->pcm, alsa_device(ad),
SND_PCM_STREAM_PLAYBACK, ad->mode); SND_PCM_STREAM_PLAYBACK, ad->mode);
if (err < 0) { if (err < 0) {
@@ -699,6 +701,8 @@ alsa_open(struct audio_output *ao, AudioFormat &audio_format, Error &error)
ad->in_frame_size = audio_format.GetFrameSize(); ad->in_frame_size = audio_format.GetFrameSize();
ad->out_frame_size = ad->pcm_export->GetFrameSize(audio_format); ad->out_frame_size = ad->pcm_export->GetFrameSize(audio_format);
ad->must_prepare = false;
return true; return true;
} }
@@ -736,29 +740,6 @@ alsa_recover(AlsaOutput *ad, int err)
case SND_PCM_STATE_XRUN: case SND_PCM_STATE_XRUN:
ad->period_position = 0; ad->period_position = 0;
err = snd_pcm_prepare(ad->pcm); err = snd_pcm_prepare(ad->pcm);
if (err == 0 && ad->pi_workaround == 0) {
/* this works around a driver bug observed on
the Raspberry Pi: after snd_pcm_drop(), the
whole ring buffer must be invalidated, but
the snd_pcm_prepare() call above makes the
driver play random data that just happens
to be still in the buffer; by adding and
cancelling some silence, this bug does not
occur */
alsa_write_silence(ad, ad->period_frames);
/* cancel the silence data right away to avoid
increasing latency; even though this
function call invalidates the portion of
silence, the driver seems to avoid the
bug */
snd_pcm_reset(ad->pcm);
/* disable the workaround for some time */
ad->pi_workaround = 8;
}
break; break;
case SND_PCM_STATE_DISCONNECTED: case SND_PCM_STATE_DISCONNECTED:
break; break;
@@ -801,6 +782,7 @@ alsa_cancel(struct audio_output *ao)
AlsaOutput *ad = (AlsaOutput *)ao; AlsaOutput *ad = (AlsaOutput *)ao;
ad->period_position = 0; ad->period_position = 0;
ad->must_prepare = true;
snd_pcm_drop(ad->pcm); snd_pcm_drop(ad->pcm);
} }
@@ -822,6 +804,16 @@ alsa_play(struct audio_output *ao, const void *chunk, size_t size,
assert(size % ad->in_frame_size == 0); assert(size % ad->in_frame_size == 0);
if (ad->must_prepare) {
ad->must_prepare = false;
int err = snd_pcm_prepare(ad->pcm);
if (err < 0) {
error.Set(alsa_output_domain, err, snd_strerror(-err));
return 0;
}
}
chunk = ad->pcm_export->Export(chunk, size, size); chunk = ad->pcm_export->Export(chunk, size, size);
assert(size % ad->out_frame_size == 0); assert(size % ad->out_frame_size == 0);
@@ -834,9 +826,6 @@ alsa_play(struct audio_output *ao, const void *chunk, size_t size,
ad->period_position = (ad->period_position + ret) ad->period_position = (ad->period_position + ret)
% ad->period_frames; % ad->period_frames;
if (ad->pi_workaround > 0)
--ad->pi_workaround;
size_t bytes_written = ret * ad->out_frame_size; size_t bytes_written = ret * ad->out_frame_size;
return ad->pcm_export->CalcSourceSize(bytes_written); return ad->pcm_export->CalcSourceSize(bytes_written);
} }

View File

@@ -369,8 +369,6 @@ pulse_output_enable(struct audio_output *ao, Error &error)
po->mainloop = pa_threaded_mainloop_new(); po->mainloop = pa_threaded_mainloop_new();
if (po->mainloop == nullptr) { if (po->mainloop == nullptr) {
g_free(po);
error.Set(pulse_output_domain, error.Set(pulse_output_domain,
"pa_threaded_mainloop_new() has failed"); "pa_threaded_mainloop_new() has failed");
return false; return false;

View File

@@ -39,7 +39,7 @@ TagLoadConfig()
if (value == nullptr) if (value == nullptr)
return; return;
std::fill_n(ignore_tag_items, TAG_NUM_OF_ITEM_TYPES, true); std::fill_n(ignore_tag_items, size_t(TAG_NUM_OF_ITEM_TYPES), true);
if (StringEqualsCaseASCII(value, "none")) if (StringEqualsCaseASCII(value, "none"))
return; return;

View File

@@ -130,8 +130,9 @@ PeakBuffer::Append(const void *data, size_t length)
return true; return true;
} }
if (peak_buffer == nullptr && peak_size > 0) { if (peak_buffer == nullptr) {
peak_buffer = (fifo_buffer *)HugeAllocate(peak_size); if (peak_size > 0)
peak_buffer = (fifo_buffer *)HugeAllocate(peak_size);
if (peak_buffer == nullptr) if (peak_buffer == nullptr)
return false; return false;

85
test/test_icy_parser.cxx Normal file
View File

@@ -0,0 +1,85 @@
/*
* Unit tests for class IcyMetaDataParser.
*/
#include "config.h"
/* include the .cxx file to get access to internal functions */
#include "IcyMetaDataParser.cxx"
#include <cppunit/TestFixture.h>
#include <cppunit/extensions/TestFactoryRegistry.h>
#include <cppunit/ui/text/TestRunner.h>
#include <cppunit/extensions/HelperMacros.h>
#include <string>
#include <string.h>
static Tag *
icy_parse_tag(const char *p)
{
char *q = strdup(p);
Tag *tag = icy_parse_tag(q, q + strlen(q));
free(q);
return tag;
}
static void
CompareTagTitle(const Tag &tag, const std::string &title)
{
CPPUNIT_ASSERT_EQUAL(1u, tag.num_items);
const TagItem &item = *tag.items[0];
CPPUNIT_ASSERT_EQUAL(TAG_TITLE, item.type);
CPPUNIT_ASSERT_EQUAL(title, std::string(item.value));
}
static void
TestIcyParserTitle(const char *input, const char *title)
{
Tag *tag = icy_parse_tag(input);
CompareTagTitle(*tag, title);
delete tag;
}
static void
TestIcyParserEmpty(const char *input)
{
Tag *tag = icy_parse_tag(input);
CPPUNIT_ASSERT_EQUAL(0u, tag->num_items);
delete tag;
}
class IcyTest : public CppUnit::TestFixture {
CPPUNIT_TEST_SUITE(IcyTest);
CPPUNIT_TEST(TestIcyMetadataParser);
CPPUNIT_TEST_SUITE_END();
public:
void TestIcyMetadataParser() {
TestIcyParserEmpty("foo=bar;");
TestIcyParserTitle("StreamTitle='foo bar'", "foo bar");
TestIcyParserTitle("StreamTitle='foo bar';", "foo bar");
TestIcyParserTitle("StreamTitle='foo\"bar';", "foo\"bar");
TestIcyParserTitle("StreamTitle='foo=bar';", "foo=bar");
TestIcyParserTitle("a=b;StreamTitle='foo';", "foo");
TestIcyParserTitle("a=;StreamTitle='foo';", "foo");
TestIcyParserTitle("a=b;StreamTitle='foo';c=d", "foo");
TestIcyParserTitle("a=b;StreamTitle='foo'", "foo");
TestIcyParserTitle("a='b;c';StreamTitle='foo;bar'", "foo;bar");
TestIcyParserTitle("a='b'c';StreamTitle='foo'bar'", "foo'bar");
TestIcyParserTitle("StreamTitle='fo'o'b'ar';a='b'c'd'", "fo'o'b'ar");
}
};
CPPUNIT_TEST_SUITE_REGISTRATION(IcyTest);
int
main(gcc_unused int argc, gcc_unused char **argv)
{
CppUnit::TextUi::TestRunner runner;
auto &registry = CppUnit::TestFactoryRegistry::getRegistry();
runner.addTest(registry.makeTest());
return runner.run() ? EXIT_SUCCESS : EXIT_FAILURE;
}

View File

@@ -26,7 +26,7 @@ Song::Free()
} }
static void static void
check_descending_priority(const struct queue *queue, check_descending_priority(const Queue *queue,
unsigned start_order) unsigned start_order)
{ {
assert(start_order < queue->GetLength()); assert(start_order < queue->GetLength());
@@ -55,7 +55,7 @@ QueuePriorityTest::TestPriority()
{ {
static Song songs[16]; static Song songs[16];
struct queue queue(32); Queue queue(32);
for (unsigned i = 0; i < ARRAY_SIZE(songs); ++i) for (unsigned i = 0; i < ARRAY_SIZE(songs); ++i)
queue.Append(&songs[i], 0); queue.Append(&songs[i], 0);