Commit eb2f413c authored by Max Kellermann's avatar Max Kellermann

Merge branch 'v0.16.x'

Conflicts: NEWS configure.ac
parents e54748d3 736fd0e2
......@@ -19,7 +19,14 @@ ver 0.17 (2011/??/??)
* cue: show CUE track numbers
ver 0.16.3 (2011/??/??)
ver 0.16.4 (2011/??/??)
* fix memory leaks
* decoder:
- ffmpeg: workaround for semantic API change in recent ffmpeg versions
- flac: validate the sample rate when scanning the tag
ver 0.16.3 (2011/06/04)
* fix assertion failure in audio format mask parser
* fix NULL pointer dereference in playlist parser
* fix playlist files in base music directory
......
......@@ -557,15 +557,16 @@ no|avahi|bonjour)
;;
esac
enable_avahi=no
enable_bounjour=no
if test x$with_zeroconf != xno; then
if test x$with_zeroconf = xavahi || test x$with_zeroconf = xauto; then
PKG_CHECK_MODULES([AVAHI], [avahi-client avahi-glib],
[found_avahi=1;AC_DEFINE([HAVE_AVAHI], 1, [Define to enable Avahi Zeroconf support])]
MPD_LIBS="$MPD_LIBS $AVAHI_LIBS" MPD_CFLAGS="$MPD_CFLAGS $AVAHI_CFLAGS",
[found_avahi=0])
[enable_avahi=yes;AC_DEFINE([HAVE_AVAHI], 1, [Define to enable Avahi Zeroconf support])]
MPD_LIBS="$MPD_LIBS $AVAHI_LIBS" MPD_CFLAGS="$MPD_CFLAGS $AVAHI_CFLAGS")
fi
if test x$found_avahi = x1; then
if test x$enable_avahi = xyes; then
with_zeroconf=avahi
elif test x$with_zeroconf = xavahi; then
AC_MSG_ERROR([Avahi support requested but not found])
......@@ -573,13 +574,12 @@ if test x$with_zeroconf != xno; then
if test x$with_zeroconf = xbonjour || test x$with_zeroconf = xauto; then
AC_CHECK_HEADER(dns_sd.h,
[found_bonjour=1;AC_DEFINE([HAVE_BONJOUR], 1, [Define to enable Bonjour Zeroconf support])],
[found_bonjour=0])
[enable_bonjour=yes;AC_DEFINE([HAVE_BONJOUR], 1, [Define to enable Bonjour Zeroconf support])])
AC_CHECK_LIB(dns_sd, DNSServiceRegister,
MPD_LIBS="$MPD_LIBS -ldns_sd")
fi
if test x$found_bonjour = x1; then
if test x$enable_bonjour = xyes; then
with_zeroconf=bonjour
elif test x$with_zeroconf = xbonjour; then
AC_MSG_ERROR([Bonjour support requested but not found])
......
......@@ -8,12 +8,38 @@
<title>General protocol syntax</title>
<section>
<title>Requests</title>
<title>Protocol overview</title>
<para>
The MPD command protocol exchanges line-based text records
between client and server over TCP. Once the client is
connected to the server, they conduct a conversation until the
client closes the connection. The conversation flow is always
initiated by the client.
</para>
<para>
If arguments contain spaces, they should be surrounded by double quotation
marks.
The client transmits a command sequence, terminated by the
newline character <constant>\n</constant>. The server will
respond with one or more lines, the last of which will be a
completion code.
</para>
<para>
When the client connects to the server, the server will answer
with the following line:
<synopsis>OK MPD version</synopsis>
where <varname>version</varname> is a version identifier such as
0.12.2. This version identifier is the version of the protocol
spoken, not the real version of the daemon. (There is no way to
retrieve this real version identifier from the connection.)
</para>
</section>
<section>
<title>Requests</title>
<cmdsynopsis>
<command>COMMAND</command>
......@@ -21,6 +47,16 @@
</cmdsynopsis>
<para>
If arguments contain spaces, they should be surrounded by double
quotation marks.
</para>
<para>
Argument strings are separated from the command and any other
arguments by linear white-space (' ' or '\t').
</para>
<para>
All data between the client and the server is encoded in
UTF-8. (Note: In UTF-8 all standard ansi characters, 0-127 are
the same as a standard ansi encoding. Also, no ansi character
......@@ -38,13 +74,97 @@
<title>Responses</title>
<para>
A command returns <returnvalue>OK</returnvalue> on completion
or <returnvalue>ACK some error</returnvalue> on failure.
These denote the end of command execution.
A command returns <returnvalue>OK</returnvalue> on completion or
<returnvalue>ACK some error</returnvalue> on failure. These
denote the end of command execution.
</para>
</section>
<section>
<title>Failure responses</title>
<para>
The nature of the error can be gleaned from the information
that follows the <returnvalue>ACK</returnvalue>.
<returnvalue>ACK</returnvalue> lines are of the form:
<synopsis>ACK [error@command_listNum] {current_command} message_text\n</synopsis>
These responses are generated by a call to
<function>commandError</function>. They contain four separate
terms. Let's look at each of them:
<itemizedlist>
<listitem>
<para>
<returnvalue>error</returnvalue>: numeric value of one
of the <constant>ACK_ERROR</constant> constants defined
in <filename>ack.h</filename>.
</para>
</listitem>
<listitem>
<para>
<returnvalue>command_listNum</returnvalue>:
offset of the command that caused the error in a <link
linkend="command_lists">Command List</link>.
An error will always cause a command list to terminate
at the command that causes the error.
</para>
</listitem>
<listitem>
<para>
<returnvalue>current_command</returnvalue>:
name of the command, in a <link
linkend="command_lists">Command List</link>,
that was executing when the error occurred.
</para>
</listitem>
<listitem>
<para>
<returnvalue>message_text</returnvalue>:
some (hopefully) informative text that describes the
nature of the error.
</para>
</listitem>
</itemizedlist>
</para>
<example>
<title>foo</title>
<para>
An example might help. Consider the following sequence
sent from the client to the server.
<synopsis>
command_list_begin
volume 86
play 10240
status
command_list_end
</synopsis>
</para>
<para>
The server responds with:
<returnvalue>
ACK [50@1] {play} song doesn't exist: "10240"
</returnvalue>
</para>
<para>
This tells us that the play command, which was the
second in the list (the first or only command is
numbered 0), failed with error 50. The number 50
translates to <constant>ACK_ERROR_NO_EXIST</constant>--the
song doesn't exist. This is reiterated by the message text
which also tells us which song doesn't exist.
</para>
</example>
</section>
</section>
<section id="command_lists">
<title>Command lists</title>
<para>
......
......@@ -456,7 +456,7 @@ cd mpd-version</programlisting>
</para>
<para>
To configure a filter, add a
To configure a playlist plugin, add a
<varname>playlist_plugin</varname> block to
<filename>mpd.conf</filename>:
</para>
......
......@@ -60,8 +60,10 @@ ape_scan_internal(FILE *fp, tag_ape_callback_t callback, void *ctx)
assert(remaining > 10);
char *buffer = g_malloc(remaining);
if (fread(buffer, 1, remaining, fp) != remaining)
if (fread(buffer, 1, remaining, fp) != remaining) {
g_free(buffer);
return false;
}
/* read tags */
unsigned n = GUINT32_FROM_LE(footer.count);
......
......@@ -372,6 +372,7 @@ config_read_file(const char *file, GError **error_r)
assert(*line != 0);
g_propagate_prefixed_error(error_r, error,
"line %i: ", count);
fclose(fp);
return false;
}
......@@ -383,6 +384,7 @@ config_read_file(const char *file, GError **error_r)
g_set_error(error_r, config_quark(), 0,
"unrecognized parameter in config file at "
"line %i: %s\n", count, name);
fclose(fp);
return false;
}
......@@ -392,6 +394,7 @@ config_read_file(const char *file, GError **error_r)
"config parameter \"%s\" is first defined "
"on line %i and redefined on line %i\n",
name, param->line, count);
fclose(fp);
return false;
}
......@@ -403,6 +406,7 @@ config_read_file(const char *file, GError **error_r)
if (*line != '{') {
g_set_error(error_r, config_quark(), 0,
"line %i: '{' expected", count);
fclose(fp);
return false;
}
......@@ -411,12 +415,15 @@ config_read_file(const char *file, GError **error_r)
g_set_error(error_r, config_quark(), 0,
"line %i: Unknown tokens after '{'",
count);
fclose(fp);
return false;
}
param = config_read_block(fp, &count, string, error_r);
if (param == NULL)
if (param == NULL) {
fclose(fp);
return false;
}
} else {
/* a string value */
......@@ -433,6 +440,7 @@ config_read_file(const char *file, GError **error_r)
g_error_free(error);
}
fclose(fp);
return false;
}
......@@ -440,6 +448,7 @@ config_read_file(const char *file, GError **error_r)
g_set_error(error_r, config_quark(), 0,
"line %i: Unknown tokens after value",
count);
fclose(fp);
return false;
}
......
......@@ -180,7 +180,7 @@ db_check(void)
}
/* Check if we can write to the directory */
if (access(dirPath, R_OK | W_OK)) {
if (access(dirPath, X_OK | W_OK)) {
g_warning("Can't create db file in \"%s\": %s",
dirPath, strerror(errno));
g_free(dirPath);
......
......@@ -138,6 +138,33 @@ mpd_ffmpeg_stream_open(struct decoder *decoder, struct input_stream *input)
return stream;
}
/**
* API compatibility wrapper for av_open_input_stream() and
* avformat_open_input().
*/
static int
mpd_ffmpeg_open_input(AVFormatContext **ic_ptr,
#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(52,101,0)
AVIOContext *pb,
#else
ByteIOContext *pb,
#endif
const char *filename,
AVInputFormat *fmt)
{
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(53,1,3)
AVFormatContext *context = avformat_alloc_context();
if (context == NULL)
return AVERROR(ENOMEM);
context->pb = pb;
*ic_ptr = context;
return avformat_open_input(ic_ptr, filename, fmt, NULL);
#else
return av_open_input_stream(ic_ptr, pb, filename, fmt, NULL);
#endif
}
static void
mpd_ffmpeg_stream_close(struct mpd_ffmpeg_stream *stream)
{
......@@ -317,9 +344,9 @@ ffmpeg_decode(struct decoder *decoder, struct input_stream *input)
}
//ffmpeg works with ours "fileops" helper
AVFormatContext *format_context;
if (av_open_input_stream(&format_context, stream->io, input->uri,
input_format, NULL) != 0) {
AVFormatContext *format_context = NULL;
if (mpd_ffmpeg_open_input(&format_context, stream->io, input->uri,
input_format) != 0) {
g_warning("Open failed\n");
mpd_ffmpeg_stream_close(stream);
return;
......@@ -441,13 +468,26 @@ static const ffmpeg_tag_map ffmpeg_tag_maps[] = {
};
static bool
ffmpeg_copy_metadata(struct tag *tag, AVMetadata *m,
ffmpeg_copy_metadata(struct tag *tag,
#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,1,0)
AVDictionary *m,
#else
AVMetadata *m,
#endif
const ffmpeg_tag_map tag_map)
{
#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,1,0)
AVDictionaryEntry *mt = NULL;
while ((mt = av_dict_get(m, tag_map.name, mt, 0)) != NULL)
tag_add_item(tag, tag_map.type, mt->value);
#else
AVMetadataTag *mt = NULL;
while ((mt = av_metadata_get(m, tag_map.name, mt, 0)) != NULL)
tag_add_item(tag, tag_map.type, mt->value);
#endif
return mt != NULL;
}
......@@ -463,9 +503,9 @@ ffmpeg_stream_tag(struct input_stream *is)
if (stream == NULL)
return NULL;
AVFormatContext *f;
if (av_open_input_stream(&f, stream->io, is->uri,
input_format, NULL) != 0) {
AVFormatContext *f = NULL;
if (mpd_ffmpeg_open_input(&f, stream->io, is->uri,
input_format) != 0) {
mpd_ffmpeg_stream_close(stream);
return NULL;
}
......
......@@ -224,6 +224,7 @@ flac_tag_apply_metadata(struct tag *tag, const char *track,
break;
case FLAC__METADATA_TYPE_STREAMINFO:
if (block->data.stream_info.sample_rate > 0)
tag->time = flac_duration(&block->data.stream_info);
break;
......
......@@ -20,6 +20,7 @@
#ifndef MPD_FLAC_METADATA_H
#define MPD_FLAC_METADATA_H
#include <assert.h>
#include <stdbool.h>
#include <FLAC/metadata.h>
......@@ -29,6 +30,8 @@ struct replay_gain_info;
static inline unsigned
flac_duration(const FLAC__StreamMetadata_StreamInfo *stream_info)
{
assert(stream_info->sample_rate > 0);
return (stream_info->total_samples + stream_info->sample_rate - 1) /
stream_info->sample_rate;
}
......
......@@ -106,12 +106,14 @@ ao_output_init(G_GNUC_UNUSED const struct audio_format *audio_format,
g_set_error(error, ao_output_quark(), 0,
"\"%s\" is not a valid ao driver",
value);
g_free(ad);
return NULL;
}
if ((ai = ao_driver_info(ad->driver)) == NULL) {
g_set_error(error, ao_output_quark(), 0,
"problems getting driver info");
g_free(ad);
return NULL;
}
......@@ -129,6 +131,7 @@ ao_output_init(G_GNUC_UNUSED const struct audio_format *audio_format,
g_set_error(error, ao_output_quark(), 0,
"problems parsing options \"%s\"",
options[i]);
g_free(ad);
return NULL;
}
......
......@@ -103,6 +103,7 @@ httpd_output_init(G_GNUC_UNUSED const struct audio_format *audio_format,
if (encoder_plugin == NULL) {
g_set_error(error, httpd_output_quark(), 0,
"No such encoder: %s", encoder_name);
g_free(httpd);
return NULL;
}
......
......@@ -79,23 +79,27 @@ recorder_output_init(G_GNUC_UNUSED const struct audio_format *audio_format,
if (encoder_plugin == NULL) {
g_set_error(error_r, recorder_output_quark(), 0,
"No such encoder: %s", encoder_name);
return NULL;
goto failure;
}
recorder->path = config_get_block_string(param, "path", NULL);
if (recorder->path == NULL) {
g_set_error(error_r, recorder_output_quark(), 0,
"'path' not configured");
return NULL;
goto failure;
}
/* initialize encoder */
recorder->encoder = encoder_init(encoder_plugin, param, error_r);
if (recorder->encoder == NULL)
return NULL;
goto failure;
return recorder;
failure:
g_free(recorder);
return NULL;
}
static void
......
......@@ -152,7 +152,7 @@ my_shout_init_driver(const struct audio_format *audio_format,
if (port == 0) {
g_set_error(error, shout_output_quark(), 0,
"shout port must be configured");
return NULL;
goto failure;
}
check_block_param("password");
......@@ -174,21 +174,21 @@ my_shout_init_driver(const struct audio_format *audio_format,
"shout quality \"%s\" is not a number in the "
"range -1 to 10, line %i",
value, param->line);
return NULL;
goto failure;
}
if (config_get_block_string(param, "bitrate", NULL) != NULL) {
g_set_error(error, shout_output_quark(), 0,
"quality and bitrate are "
"both defined");
return NULL;
goto failure;
}
} else {
value = config_get_block_string(param, "bitrate", NULL);
if (value == NULL) {
g_set_error(error, shout_output_quark(), 0,
"neither bitrate nor quality defined");
return NULL;
goto failure;
}
sd->bitrate = strtol(value, &test, 10);
......@@ -196,7 +196,7 @@ my_shout_init_driver(const struct audio_format *audio_format,
if (*test != '\0' || sd->bitrate <= 0) {
g_set_error(error, shout_output_quark(), 0,
"bitrate must be a positive integer");
return NULL;
goto failure;
}
}
......@@ -206,12 +206,12 @@ my_shout_init_driver(const struct audio_format *audio_format,
g_set_error(error, shout_output_quark(), 0,
"couldn't find shout encoder plugin \"%s\"",
encoding);
return NULL;
goto failure;
}
sd->encoder = encoder_init(encoder_plugin, param, error);
if (sd->encoder == NULL)
return NULL;
goto failure;
if (strcmp(encoding, "mp3") == 0 || strcmp(encoding, "lame") == 0)
shout_format = SHOUT_FORMAT_MP3;
......@@ -225,7 +225,7 @@ my_shout_init_driver(const struct audio_format *audio_format,
g_set_error(error, shout_output_quark(), 0,
"you cannot stream \"%s\" to shoutcast, use mp3",
encoding);
return NULL;
goto failure;
} else if (0 == strcmp(value, "shoutcast"))
protocol = SHOUT_PROTOCOL_ICY;
else if (0 == strcmp(value, "icecast1"))
......@@ -237,7 +237,7 @@ my_shout_init_driver(const struct audio_format *audio_format,
"shout protocol \"%s\" is not \"shoutcast\" or "
"\"icecast1\"or \"icecast2\"",
value);
return NULL;
goto failure;
}
} else {
protocol = SHOUT_PROTOCOL_HTTP;
......@@ -256,7 +256,7 @@ my_shout_init_driver(const struct audio_format *audio_format,
shout_set_agent(sd->shout_conn, "MPD") != SHOUTERR_SUCCESS) {
g_set_error(error, shout_output_quark(), 0,
"%s", shout_get_error(sd->shout_conn));
return NULL;
goto failure;
}
/* optional paramters */
......@@ -267,14 +267,14 @@ my_shout_init_driver(const struct audio_format *audio_format,
if (value != NULL && shout_set_genre(sd->shout_conn, value)) {
g_set_error(error, shout_output_quark(), 0,
"%s", shout_get_error(sd->shout_conn));
return NULL;
goto failure;
}
value = config_get_block_string(param, "description", NULL);
if (value != NULL && shout_set_description(sd->shout_conn, value)) {
g_set_error(error, shout_output_quark(), 0,
"%s", shout_get_error(sd->shout_conn));
return NULL;
goto failure;
}
value = config_get_block_string(param, "url", NULL);
......@@ -307,6 +307,10 @@ my_shout_init_driver(const struct audio_format *audio_format,
}
return sd;
failure:
free_shout_data(sd);
return NULL;
}
static bool
......
......@@ -579,8 +579,10 @@ sticker_load(const char *type, const char *uri)
bool success;
success = sticker_list_values(sticker->table, type, uri);
if (!success)
if (!success) {
sticker_free(sticker);
return NULL;
}
if (g_hash_table_size(sticker->table) == 0) {
/* don't return empty sticker objects */
......
......@@ -37,30 +37,28 @@ my_log_func(G_GNUC_UNUSED const gchar *log_domain,
int main(int argc, char **argv)
{
const char *path, *name, *value;
GError *error = NULL;
bool success;
int ret;
if (argc != 3) {
g_printerr("Usage: read_conf FILE SETTING\n");
return 1;
}
path = argv[1];
name = argv[2];
const char *path = argv[1];
const char *name = argv[2];
g_log_set_default_handler(my_log_func, NULL);
config_global_init();
success = config_read_file(path, &error);
GError *error = NULL;
bool success = config_read_file(path, &error);
if (!success) {
g_printerr("%s:", error->message);
g_error_free(error);
return 1;
}
value = config_get_string(name, NULL);
const char *value = config_get_string(name, NULL);
int ret;
if (value != NULL) {
g_print("%s\n", value);
ret = 0;
......@@ -70,5 +68,5 @@ int main(int argc, char **argv)
}
config_global_finish();
return 0;
return ret;
}
......@@ -106,7 +106,6 @@ int main(int argc, char **argv)
struct filter *filter;
const struct audio_format *out_audio_format;
char buffer[4096];
size_t frame_size;
if (argc < 3 || argc > 4) {
g_printerr("Usage: run_filter CONFIG NAME [FORMAT] <IN\n");
......@@ -162,8 +161,6 @@ int main(int argc, char **argv)
g_printerr("audio_format=%s\n",
audio_format_to_string(out_audio_format, &af_string));
frame_size = audio_format_frame_size(&audio_format);
/* play */
while (true) {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment