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

@@ -1061,6 +1061,7 @@ C_TESTS = \
test/test_util \
test/test_byte_reverse \
test/test_mixramp \
test/test_icy_parser \
test/test_pcm \
test/test_queue_priority
@@ -1496,6 +1497,16 @@ test_test_mixramp_LDADD = \
$(GLIB_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_util.hxx \
test/test_pcm_dither.cxx \

25
NEWS

@@ -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
- pls: fix crash after parser error
- soundcloud: fix build failure with libyajl 2.0.1

@@ -1,6 +1,6 @@
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_MINOR=18
@@ -1023,7 +1023,7 @@ if test x$enable_tremor = xyes; then
fi
fi
MPD_AUTO_PKG(vorbis, VORBIS, [vorbis vorbisfile ogg],
MPD_AUTO_PKG(vorbis, VORBIS, [vorbisfile vorbis ogg],
[Ogg Vorbis decoder], [libvorbis not found])
if test x$enable_vorbis = xyes; then
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)
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])
if test x$enable_vorbis_encoder = xyes; then

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

@@ -1852,6 +1852,51 @@ systemctl start mpd.socket</programlisting>
</informaltable>
</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>
<title><varname>recorder</varname></title>

@@ -81,31 +81,85 @@ icy_add_item(Tag &tag, TagType type, const char *value)
}
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)
icy_add_item(tag, TAG_TITLE, p[1]);
else
FormatDebug(icy_metadata_domain,
"unknown icy-tag: '%s'", p[0]);
/**
* Find a single quote that is followed by a semicolon (or by the end
* of the string). If that fails, return the first single quote. If
* that also fails, return #end.
*/
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 *
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();
gchar **items = g_strsplit(p, ";", 0);
for (unsigned i = 0; items[i] != nullptr; ++i)
icy_parse_tag_item(*tag, items[i]);
while (p != end) {
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;
}
@@ -152,15 +206,11 @@ IcyMetaDataParser::Meta(const void *data, size_t length)
++length;
if (meta_position == meta_size) {
/* null-terminate the string */
meta_data[meta_size] = 0;
/* parse */
delete tag;
tag = icy_parse_tag(meta_data);
tag = icy_parse_tag(meta_data, meta_data + meta_size);
g_free(meta_data);
/* change back to normal data mode */

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

@@ -30,7 +30,7 @@ struct 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

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

@@ -65,7 +65,7 @@ playlist_print_uri(FILE *file, const char *uri)
}
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())
return PlaylistResult::DISABLED;

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

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

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

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

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

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

@@ -27,16 +27,16 @@
#include <stdio.h>
struct queue;
struct Queue;
class TextFile;
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.
*/
void
queue_load_song(TextFile &file, const char *line, queue &queue);
queue_load_song(TextFile &file, const char *line, Queue &queue);
#endif

@@ -101,7 +101,7 @@ bool
SongFilter::Item::Match(const Tag &_tag) const
{
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++) {
visited_types[_tag.items[i]->type] = true;

@@ -90,7 +90,7 @@ static const struct command commands[] = {
{ "disableoutput", PERMISSION_ADMIN, 1, 1, handle_disableoutput },
{ "enableoutput", PERMISSION_ADMIN, 1, 1, handle_enableoutput },
{ "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 },
{ "kill", PERMISSION_ADMIN, -1, -1, handle_kill },
{ "list", PERMISSION_READ, 1, -1, handle_list },

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

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

@@ -39,6 +39,12 @@
#include <algorithm>
#ifdef USE_SIGNALFD
#include <pthread.h>
#endif
#include <signal.h>
class SignalMonitor final : private SocketMonitor {
#ifdef USE_SIGNALFD
SignalFD fd;
@@ -99,7 +105,21 @@ static std::atomic_bool signal_pending[MAX_SIGNAL];
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
SignalCallback(int signo)
{
@@ -108,6 +128,7 @@ SignalCallback(int signo)
if (!signal_pending[signo].exchange(true))
monitor->WakeUp();
}
#endif
void
@@ -115,6 +136,8 @@ SignalMonitorInit(EventLoop &loop)
{
#ifdef USE_SIGNALFD
sigemptyset(&signal_mask);
pthread_atfork(nullptr, nullptr, at_fork_child);
#endif
monitor.Construct(loop);

@@ -106,12 +106,16 @@ struct AlsaOutput {
snd_pcm_uframes_t period_position;
/**
* Set to non-zero when the Raspberry Pi workaround has been
* activated in alsa_recover(); decremented by each write.
* This will avoid activating it again, leading to an endless
* loop. This problem was observed with a "RME Digi9636/52".
* Do we need to call snd_pcm_prepare() before the next write?
* It means that we put the device to SND_PCM_STATE_SETUP by
* calling snd_pcm_drop().
*
* 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.
@@ -676,8 +680,6 @@ alsa_open(struct audio_output *ao, AudioFormat &audio_format, Error &error)
{
AlsaOutput *ad = (AlsaOutput *)ao;
ad->pi_workaround = 0;
int err = snd_pcm_open(&ad->pcm, alsa_device(ad),
SND_PCM_STREAM_PLAYBACK, ad->mode);
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->out_frame_size = ad->pcm_export->GetFrameSize(audio_format);
ad->must_prepare = false;
return true;
}
@@ -736,29 +740,6 @@ alsa_recover(AlsaOutput *ad, int err)
case SND_PCM_STATE_XRUN:
ad->period_position = 0;
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;
case SND_PCM_STATE_DISCONNECTED:
break;
@@ -801,6 +782,7 @@ alsa_cancel(struct audio_output *ao)
AlsaOutput *ad = (AlsaOutput *)ao;
ad->period_position = 0;
ad->must_prepare = true;
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);
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);
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_frames;
if (ad->pi_workaround > 0)
--ad->pi_workaround;
size_t bytes_written = ret * ad->out_frame_size;
return ad->pcm_export->CalcSourceSize(bytes_written);
}

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

@@ -39,7 +39,7 @@ TagLoadConfig()
if (value == nullptr)
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"))
return;

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

85
test/test_icy_parser.cxx Normal 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;
}

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