Compare commits
26 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
2784d65618 | ||
![]() |
47ea69233b | ||
![]() |
a884e37de1 | ||
![]() |
0102a8665a | ||
![]() |
d34ae0850c | ||
![]() |
6526de024a | ||
![]() |
5e1e92626c | ||
![]() |
7fee85c80a | ||
![]() |
5d87a274a5 | ||
![]() |
57e862712a | ||
![]() |
ddb5390d88 | ||
![]() |
fce20e514e | ||
![]() |
af66ed2505 | ||
![]() |
ea4068757d | ||
![]() |
2b10ecfa37 | ||
![]() |
f7eb2b697e | ||
![]() |
da67260c95 | ||
![]() |
ab9c9068d4 | ||
![]() |
6b4d7d7315 | ||
![]() |
313d1d5d83 | ||
![]() |
b7d6133593 | ||
![]() |
5b6bb114ad | ||
![]() |
56f082c9d4 | ||
![]() |
a1b798e555 | ||
![]() |
c91e08fbfd | ||
![]() |
f882434547 |
11
Makefile.am
11
Makefile.am
@@ -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
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
|
* 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
|
||||||
|
@@ -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
|
||||||
|
@@ -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>
|
||||||
|
45
doc/user.xml
45
doc/user.xml
@@ -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>
|
||||||
|
|
||||||
|
@@ -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 */
|
||||||
|
@@ -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"
|
||||||
|
@@ -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
|
||||||
|
@@ -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 */
|
||||||
|
@@ -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;
|
||||||
|
@@ -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.
|
||||||
|
@@ -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);
|
||||||
|
@@ -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);
|
||||||
|
@@ -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++) {
|
||||||
|
@@ -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
|
||||||
|
@@ -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;
|
||||||
|
@@ -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
|
||||||
|
@@ -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;
|
||||||
|
@@ -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 },
|
||||||
|
@@ -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);
|
||||||
|
@@ -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
|
||||||
|
@@ -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);
|
||||||
|
@@ -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);
|
||||||
}
|
}
|
||||||
|
@@ -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;
|
||||||
|
@@ -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;
|
||||||
|
@@ -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
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 ®istry = CppUnit::TestFactoryRegistry::getRegistry();
|
||||||
|
runner.addTest(registry.makeTest());
|
||||||
|
return runner.run() ? EXIT_SUCCESS : EXIT_FAILURE;
|
||||||
|
}
|
@@ -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);
|
||||||
|
Reference in New Issue
Block a user