Compare commits

...

47 Commits

Author SHA1 Message Date
Avuton Olrich
76e3dec723 mpd version 0.17.2 2012-09-30 03:27:38 -07:00
Max Kellermann
ba6ef53ef9 decoder_control: remove MixRamp debug messages
These are confusing, and since MixRamp development has ceased, not
useful to anybody.
2012-09-25 11:08:16 +02:00
Max Kellermann
c93a28c641 configure.ac: don't auto-detect the Vorbis encoder when Tremor is enabled
libvorbisidec and libvorbis export the same symbols, which is a
dangerous thing.  Since libvorbisenc depends on libvorbis, this can
get nasty, so let's disable the Vorbis encoder unless the user
explicitly wants it.
2012-09-25 10:41:39 +02:00
Max Kellermann
7088a679a2 decoder/wavpack: support all APEv2 tags
WavPack tags are always APEv2, by definition.  Reuse the tag_table
from tag_ape.c, instead of rolling our own.
2012-09-25 09:37:16 +02:00
Max Kellermann
04c02a1eb8 locate: cast enum tag_type to int before comparing with integer
Avoids clang pickiness.  This code is not correct, but we'll fix that
another day.
2012-09-22 09:48:27 +02:00
Max Kellermann
41487426f5 decoder/_ogg_common: fix buffer size check
Fixes potential access to uninitialised memory.
2012-09-04 11:22:15 +02:00
Max Kellermann
0d24250aa7 decoder/_ogg_common: simplify the large "if" expression 2012-09-04 11:22:05 +02:00
Wieland Hoffmann
2050e2f886 mpd.conf(5): Use the correct default value for max_playlist_length 2012-09-03 22:49:51 +02:00
Max Kellermann
013e8479af AudioCompress: abort on out-of-memory
This library crashes on out-of-memory (NULL pointer dereference).
There's not much useful MPD can do in such a situation, so let's
explicitly abort instead, just like GLib does.
2012-09-03 22:45:33 +02:00
Max Kellermann
27535a7f78 update_walk: fix unsafe readlink() usage 2012-09-03 22:41:04 +02:00
Max Kellermann
acaa725478 playlist/cue: map "PERFORMER" to "artist" or "album artist"
Implements Mantis ticket 0003549.
2012-08-25 09:56:14 +02:00
Max Kellermann
f351550534 player_thread: disable cross-fading in "single" mode
This commit reimplements the core of the "single" mode.  Instead of
doing the detection in the playlist code from the outside, it is moved
to the player thread, which gets a new option called "border_pause".
It will now pause playback exactly at the beginning of the new song,
making the feature more reliable.

Now that the player thread knows what will happen, it can suppress
cross-fading.

Fixes mantis tickets 0003055 and 0003166.
2012-08-25 09:38:41 +02:00
Max Kellermann
66ecf39efe command: make "single" a bool 2012-08-21 19:38:08 +02:00
Max Kellermann
5ad21d7e98 queue_save: save song priorities 2012-08-21 19:17:14 +02:00
Max Kellermann
ef5125f8f4 playlist_print: fix memory leak 2012-08-16 00:01:01 +02:00
Max Kellermann
bf2e07074b playlist_song: pass const song to _check_load_song() 2012-08-16 00:00:50 +02:00
Max Kellermann
20695ef369 playlist_song: fix user-after-free bug 2012-08-16 00:00:21 +02:00
Max Kellermann
9374e0f445 player_thread: add local variable "start_ms"
Just in case "song" becomes invalid at some point.
2012-08-15 22:51:48 +02:00
Max Kellermann
19ed233118 playlist: fix unprotected player_control access 2012-08-15 22:47:08 +02:00
Max Kellermann
faa4fff4dd filter/volume: include cleanup 2012-08-15 22:45:03 +02:00
Max Kellermann
2276e7677b mapper: fix potential crash in file permission check 2012-08-15 22:44:13 +02:00
Max Kellermann
93f9c2ab6b doc/user: add wildmidi documentation 2012-08-15 01:03:16 +02:00
Max Kellermann
4a993cd79e decoder/fluidsynth: add "sample_rate" setting 2012-08-15 00:57:32 +02:00
Max Kellermann
02325d2ede decoder/fluidsynth: add "soundfont" setting
Replaces the old global "soundfont" which never worked.
2012-08-15 00:51:45 +02:00
Max Kellermann
9c83464b95 configure.ac: auto-detect libfluidsynth
Now that the libfluidsynth API was sanitized, we can enable the plugin
automatically if libfluidsynth is installed.
2012-08-15 00:48:52 +02:00
Max Kellermann
b1bbd70f0f decoder/fluidsynth: stop playback at end of file
Use libfluidsynth's new function fluid_player_get_status().
2012-08-15 00:47:10 +02:00
Max Kellermann
c31d11bfe0 decoder/fluidsynth: don't duplicate path
The libfluidsynth now accepts const strings.
2012-08-15 00:39:22 +02:00
Max Kellermann
c8ec85d649 decoder/fluidsynth: check if file is really a MIDI
Use fluid_is_midifile() to verify the file format.
2012-08-15 00:36:04 +02:00
Max Kellermann
e291f3d257 decoder/fluidsynth: remove throttle (requires libfluidsynth 1.1)
The libfluidsynth API is now sane, and does not require real-time
decoding.
2012-08-15 00:29:38 +02:00
Max Kellermann
dc22846d58 log: store duplicated path string
Don't free the string right after calling log_init_file().  Add a new
function log_deinit() that frees the string on shutdown.

This fixes cycling the log file after SIGHUP (Mantis ticket 0003524).
2012-08-14 23:16:46 +02:00
Max Kellermann
c9aaabb5d4 output/jack: implement method delay()
Eliminate the g_usleep() call.
2012-08-14 22:47:25 +02:00
Max Kellermann
335d5d5d72 output/pulse: implement method delay()
Reduce command latency while paused.
2012-08-14 22:30:46 +02:00
Max Kellermann
51d793bec1 output/pulse: simplify _wait_stream()
One large loop and only one pa_stream_get_state() call.
2012-08-14 22:22:55 +02:00
Max Kellermann
249dcd967e output/httpd: move delay from _pause() to _delay() 2012-08-14 21:54:47 +02:00
Max Kellermann
302972e9fc output/httpd: fix throttling bug after resuming playback
Reset the timer when paused and no client is connected.

This fixes Mantis ticket 0003527.
2012-08-14 21:39:33 +02:00
Max Kellermann
31b380b266 output/httpd: move code to _has_clients() 2012-08-14 20:22:32 +02:00
Max Kellermann
a869dfea85 timer: use monotonic clock if available 2012-08-14 19:07:31 +02:00
Max Kellermann
12838c6294 input/ffmpeg: remove fallback AV_VERSION_INT definition
This is part of libavutil.
2012-08-14 19:07:27 +02:00
Wieland Hoffmann
49c7102547 mpd.conf(5): Document the existence of musicbrainz_ tags
Additionally, update mpdconf.example to refer to mpd.conf(5) for the
complete list of tags instead of trying to repeat it.
2012-08-14 09:43:18 +02:00
Max Kellermann
1ae8972859 mapper: fix non-UTF8 music directory name
Duplicate the music_dir variable: one encoded in UTF-8, and another
one using the configured filesystem character set.  This fixes an
ancient MPD bug.
2012-08-14 02:28:04 +02:00
Max Kellermann
adcd2c8eac playlist_song: use map_to_relative_path() 2012-08-14 02:25:19 +02:00
Max Kellermann
45ff355835 playlist_song: improve const-correctness 2012-08-14 02:24:16 +02:00
Max Kellermann
f8bf3afeae playlist_song: move code to playlist_check_load_song() 2012-08-14 02:17:25 +02:00
Max Kellermann
f703da1516 valgrind.suppressions: suppressions for GStaticMutex and more 2012-08-14 01:58:17 +02:00
Max Kellermann
a582deee2c input_stream, main: remove obsolete GLib version checks
MPD requires GLib 2.16.
2012-08-14 01:57:53 +02:00
Anton Khirnov
12be9e818f client_file: remove pure attribute from client_allow_file().
That function is not pure, it writes to error.

When marked as pure, the compiler is allowed to assume it does not do
anything to error, so it can remain NULL, which would result in an
invalid read in print_error().
2012-08-13 07:55:40 +02:00
Avuton Olrich
281cd7c057 Modify version string to post-release version 0.17.2~git 2012-07-31 19:05:52 -07:00
51 changed files with 698 additions and 339 deletions

View File

@@ -103,7 +103,7 @@ libsidplay2 - http://sidplay2.sourceforge.net/
For C64 SID support.
libfluidsynth - http://fluidsynth.resonance.org/
For MIDI support (DO NOT USE - use libwildmidi instead)
For MIDI support.
libwildmidi - http://wildmidi.sourceforge.net/
For MIDI support.

View File

@@ -227,6 +227,7 @@ src_mpd_SOURCES = \
$(OUTPUT_API_SRC) \
$(MIXER_API_SRC) \
src/glib_socket.h \
src/clock.c src/clock.h \
src/notify.c \
src/audio_config.c src/audio_config.h \
src/audio_check.c \
@@ -1069,7 +1070,6 @@ test_dump_playlist_SOURCES = test/dump_playlist.c \
src/audio_check.c src/pcm_buffer.c \
src/text_input_stream.c src/fifo_buffer.c \
src/cue/cue_parser.c src/cue/cue_parser.h \
src/timer.c \
src/fd_util.c
if HAVE_FLAC
@@ -1096,7 +1096,6 @@ test_run_decoder_SOURCES = test/run_decoder.c \
src/fd_util.c \
src/audio_check.c \
src/audio_format.c \
src/timer.c \
$(ARCHIVE_SRC) \
$(INPUT_SRC) \
$(TAG_SRC) \
@@ -1118,7 +1117,6 @@ test_read_tags_SOURCES = test/read_tags.c \
src/uri.c \
src/fd_util.c \
src/audio_check.c \
src/timer.c \
$(DECODER_SRC)
if HAVE_ID3TAG
@@ -1240,7 +1238,7 @@ test_run_output_SOURCES = test/run_output.c \
src/audio_check.c \
src/audio_format.c \
src/audio_parser.c \
src/timer.c \
src/timer.c src/clock.c \
src/tag.c src/tag_pool.c \
src/fifo_buffer.c src/growing_fifo.c \
src/page.c \

24
NEWS
View File

@@ -1,3 +1,27 @@
ver 0.17.2 (2012/09/30)
* protocol:
- fix crash in local file check
* decoder:
- fluidsynth: remove throttle (requires libfluidsynth 1.1)
- fluidsynth: stop playback at end of file
- fluidsynth: check MIDI file format while scanning
- fluidsynth: add sample rate setting
- wavpack: support all APEv2 tags
* output:
- httpd: use monotonic clock, avoid hiccups after system clock adjustment
- httpd: fix throttling bug after resuming playback
* playlist:
- cue: map "PERFORMER" to "artist" or "album artist"
* mapper: fix non-UTF8 music directory name
* mapper: fix potential crash in file permission check
* playlist: fix use-after-free bug
* playlist: fix memory leak
* state_file: save song priorities
* player: disable cross-fading in "single" mode
* update: fix unsafe readlink() usage
* configure.ac:
- don't auto-detect the vorbis encoder when Tremor is enabled
ver 0.17.1 (2012/07/31)
* protocol:
- require appropriate permissions for searchadd{,pl}

View File

@@ -1,6 +1,6 @@
AC_PREREQ(2.60)
AC_INIT(mpd, 0.17.1, musicpd-dev-team@lists.sourceforge.net)
AC_INIT(mpd, 0.17.2, musicpd-dev-team@lists.sourceforge.net)
VERSION_MAJOR=0
VERSION_MINOR=17
@@ -217,8 +217,8 @@ AC_ARG_ENABLE(flac,
AC_ARG_ENABLE(fluidsynth,
AS_HELP_STRING([--enable-fluidsynth],
[enable MIDI support via fluidsynth (default: disable)]),,
enable_fluidsynth=no)
[enable MIDI support via fluidsynth (default: auto)]),,
enable_fluidsynth=auto)
AC_ARG_ENABLE(gme,
AS_HELP_STRING([--enable-gme],
@@ -845,7 +845,7 @@ enable_flac_encoder=$enable_flac
dnl -------------------------------- FluidSynth -------------------------------
if test x$enable_fluidsynth = xyes; then
PKG_CHECK_MODULES(FLUIDSYNTH, [fluidsynth],
PKG_CHECK_MODULES(FLUIDSYNTH, [fluidsynth >= 1.1],
AC_DEFINE(ENABLE_FLUIDSYNTH, 1, [Define for fluidsynth support]),
enable_fluidsynth=no)
fi
@@ -999,6 +999,11 @@ if test x$enable_tremor = xyes; then
AC_MSG_WARN(["OggTremor detected, could not enable Vorbis."])
fi
enable_vorbis=no
if test x$enable_vorbis_encoder = xauto; then
AC_MSG_WARN([OggTremor detected, disabling the Vorbis encoder plugin.])
enable_vorbis_encoder=no
fi
fi
MPD_AUTO_PKG(vorbis, VORBIS, [vorbis vorbisfile ogg],

View File

@@ -216,7 +216,7 @@ default is 5.
.TP
.B max_playlist_length <number>
This specifies the maximum number of songs that can be in the playlist. The
default is 4096.
default is 16384.
.TP
.B max_command_list_size <size in KiB>
This specifies the maximum size a command list can be. The default is 2048.
@@ -252,11 +252,12 @@ when saving playlists. The default is "no".
This specifies the tag types that will be scanned for and made available to
clients. Note that you must recreate (not update) your database for changes to
this parameter to take effect. Possible values are artist, album, title,
track, name, genre, date, composer, performer, comment, and disc. Multiple
tags may be specified as a comma separated list. An example value is
"artist,album,title,track". The special value "none" may be used alone to
disable all metadata. The default is to use all known tag types except for
comments.
track, name, genre, date, composer, performer, comment, disc,
musicbrainz_artistid, musicbrainz_albumid, musicbrainz_albumartistid,
musicbrainz_trackid. Multiple tags may be specified as a comma separated list.
An example value is "artist,album,title,track". The special value "none" may
be used alone to disable all metadata. The default is to use all known tag
types except for comments and those starting with "musicbrainz".
.TP
.B auto_update <yes or no>
This specifies the wheter to support automatic update of music database when

View File

@@ -114,9 +114,8 @@
#save_absolute_paths_in_playlists "no"
#
# This setting defines a list of tag types that will be extracted during the
# audio file discovery process. Optionally, 'comment' can be added to this
# list.
#
# audio file discovery process. The complete list of possible values can be
# found in the mpd.conf man page.
#metadata_to_use "artist,album,title,track,name,genre,date,composer,performer,disc"
#
# This setting enables automatic update of MPD's database when files in

View File

@@ -818,6 +818,46 @@ systemctl start mpd.socket</programlisting>
</section>
<section>
<title><varname>fluidsynth</varname></title>
<para>
MIDI decoder based on libfluidsynth.
</para>
<informaltable>
<tgroup cols="2">
<thead>
<row>
<entry>Setting</entry>
<entry>Description</entry>
</row>
</thead>
<tbody>
<row>
<entry>
<varname>sample_rate</varname>
</entry>
<entry>
The sample rate that shall be synthesized by the
plugin. Defaults to 48000.
</entry>
</row>
<row>
<entry>
<varname>soundfont</varname>
</entry>
<entry>
The absolute path of the soundfont file. Defaults
to
<filename>/usr/share/sounds/sf2/FluidR3_GM.sf2</filename>.
</entry>
</row>
</tbody>
</tgroup>
</informaltable>
</section>
<section>
<title><varname>mikmod</varname></title>
@@ -847,6 +887,37 @@ systemctl start mpd.socket</programlisting>
</tgroup>
</informaltable>
</section>
<section>
<title><varname>wildmidi</varname></title>
<para>
MIDI decoder based on libwildmidi.
</para>
<informaltable>
<tgroup cols="2">
<thead>
<row>
<entry>Setting</entry>
<entry>Description</entry>
</row>
</thead>
<tbody>
<row>
<entry>
<varname>config_file</varname>
</entry>
<entry>
The absolute path of the timidity config file. Defaults
to
<filename>/etc/timidity/timidity.cfg</filename>.
</entry>
</row>
</tbody>
</tgroup>
</informaltable>
</section>
</section>
<section>

View File

@@ -33,6 +33,9 @@ struct Compressor {
struct Compressor *Compressor_new(unsigned int history)
{
struct Compressor *obj = malloc(sizeof(struct Compressor));
if (obj == NULL)
/* out of memory, not much we can do */
abort();
obj->prefs.target = TARGET;
obj->prefs.maxgain = GAINMAX;
@@ -61,6 +64,10 @@ void Compressor_delete(struct Compressor *obj)
static int *resizeArray(int *data, int newsz, int oldsz)
{
data = realloc(data, newsz*sizeof(int));
if (data == NULL)
/* out of memory, not much we can do */
abort();
if (newsz > oldsz)
memset(data + oldsz, 0, sizeof(int)*(newsz - oldsz));
return data;

View File

@@ -35,7 +35,6 @@ struct client;
* @param path_fs the absolute path name in filesystem encoding
* @return true if access is allowed
*/
G_GNUC_PURE
bool
client_allow_file(const struct client *client, const char *path_fs,
GError **error_r);

95
src/clock.c Normal file
View File

@@ -0,0 +1,95 @@
/*
* Copyright (C) 2003-2012 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "clock.h"
#ifdef WIN32
#include <windows.h>
#elif defined(__APPLE__)
#include <mach/mach_time.h>
#else
#include <time.h>
#endif
unsigned
monotonic_clock_ms(void)
{
#ifdef WIN32
return GetTickCount();
#elif defined(__APPLE__) /* OS X does not define CLOCK_MONOTONIC */
static mach_timebase_info_data_t base;
if (base.denom == 0)
(void)mach_timebase_info(&base);
return (unsigned)((mach_absolute_time() * base.numer)
/ (1000000 * base.denom));
#elif defined(CLOCK_MONOTONIC)
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
return ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
#else
/* we have no monotonic clock, fall back to gettimeofday() */
struct timeval tv;
gettimeofday(&tv, 0);
return tv.tv_sec * 1000 + tv.tv_usec / 1000;
#endif
}
uint64_t
monotonic_clock_us(void)
{
#ifdef WIN32
LARGE_INTEGER l_value, l_frequency;
if (!QueryPerformanceCounter(&l_value) ||
!QueryPerformanceFrequency(&l_frequency))
return 0;
uint64_t value = l_value.QuadPart;
uint64_t frequency = l_frequency.QuadPart;
if (frequency > 1000000) {
value *= 10000;
value /= frequency / 100;
} else if (frequency < 1000000) {
value *= 10000;
value /= frequency;
value *= 100;
}
return value;
#elif defined(__APPLE__) /* OS X does not define CLOCK_MONOTONIC */
static mach_timebase_info_data_t base;
if (base.denom == 0)
(void)mach_timebase_info(&base);
return ((uint64_t)mach_absolute_time() * (uint64_t)base.numer)
/ (1000 * (uint64_t)base.denom);
#elif defined(CLOCK_MONOTONIC)
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
return (uint64_t)ts.tv_sec * 1000000 + (uint64_t)(ts.tv_nsec / 1000);
#else
/* we have no monotonic clock, fall back to gettimeofday() */
struct timeval tv;
gettimeofday(&tv, 0);
return (uint64_t)tv.tv_sec * 1000 + (uint64_t)(tv.tv_usec) / 1000(;
#endif
}

41
src/clock.h Normal file
View File

@@ -0,0 +1,41 @@
/*
* Copyright (C) 2003-2012 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef MPD_CLOCK_H
#define MPD_CLOCK_H
#include <glib.h>
#include <stdint.h>
/**
* Returns the value of a monotonic clock in milliseconds.
*/
G_GNUC_PURE
unsigned
monotonic_clock_ms(void);
/**
* Returns the value of a monotonic clock in microseconds.
*/
G_GNUC_PURE
uint64_t
monotonic_clock_us(void);
#endif

View File

@@ -1069,7 +1069,7 @@ handle_next(G_GNUC_UNUSED struct client *client,
{
/* single mode is not considered when this is user who
* wants to change song. */
int single = g_playlist.queue.single;
const bool single = g_playlist.queue.single;
g_playlist.queue.single = false;
playlist_next(&g_playlist, client->player_control);
@@ -1544,7 +1544,7 @@ handle_config(struct client *client,
return COMMAND_RETURN_ERROR;
}
const char *path = mapper_get_music_directory();
const char *path = mapper_get_music_directory_utf8();
if (path != NULL)
client_printf(client, "music_directory: %s\n", path);

View File

@@ -216,9 +216,19 @@ cue_parser_feed2(struct cue_parser *parser, char *p)
if (tag != NULL)
cue_parse_rem(p, tag);
} else if (strcmp(command, "PERFORMER") == 0) {
/* MPD knows a "performer" tag, but it is not a good
match for this CUE tag; from the Hydrogenaudio
Knowledgebase: "At top-level this will specify the
CD artist, while at track-level it specifies the
track artist." */
enum tag_type type = parser->state == TRACK
? TAG_ARTIST
: TAG_ALBUM_ARTIST;
struct tag *tag = cue_current_tag(parser);
if (tag != NULL)
cue_add_tag(tag, TAG_PERFORMER, p);
cue_add_tag(tag, type, p);
} else if (strcmp(command, "TITLE") == 0) {
if (parser->state == HEADER)
cue_add_tag(parser->tag, TAG_ALBUM, p);

View File

@@ -124,11 +124,10 @@ void flac_metadata_common_cb(const FLAC__StreamMetadata * block,
case FLAC__METADATA_TYPE_VORBIS_COMMENT:
if (flac_parse_replay_gain(&rgi, block))
replay_gain_db = decoder_replay_gain(data->decoder, &rgi);
if (flac_parse_mixramp(&mixramp_start, &mixramp_end, block)) {
g_debug("setting mixramp_tags");
if (flac_parse_mixramp(&mixramp_start, &mixramp_end, block))
decoder_mixramp(data->decoder, replay_gain_db,
mixramp_start, mixramp_end);
}
if (data->tag != NULL)
flac_vorbis_comments_to_tag(data->tag, NULL,

View File

@@ -33,12 +33,14 @@ ogg_stream_type ogg_stream_type_detect(struct input_stream *inStream)
size_t r;
r = decoder_read(NULL, inStream, buf, sizeof(buf));
if (r >= 32 && memcmp(buf, "OggS", 4) == 0 && (
(memcmp(buf+29, "FLAC", 4) == 0
&& memcmp(buf+37, "fLaC", 4) == 0)
|| (memcmp(buf+28, "FLAC", 4) == 0)
|| (memcmp(buf+28, "fLaC", 4) == 0))) {
if (r < sizeof(buf) || memcmp(buf, "OggS", 4) != 0)
return VORBIS;
if ((memcmp(buf + 29, "FLAC", 4) == 0 &&
memcmp(buf + 37, "fLaC", 4) == 0) ||
memcmp(buf + 28, "FLAC", 4) == 0 ||
memcmp(buf + 28, "fLaC", 4) == 0)
return FLAC;
}
return VORBIS;
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2003-2011 The Music Player Daemon Project
* Copyright (C) 2003-2012 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -17,18 +17,9 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
/*
* WARNING! This plugin suffers from major shortcomings in the
* libfluidsynth API, which render it practically unusable. For a
* discussion, see the post on the fluidsynth mailing list:
*
* http://www.mail-archive.com/fluid-dev@nongnu.org/msg01099.html
*
*/
#include "config.h"
#include "decoder_api.h"
#include "timer.h"
#include "audio_check.h"
#include "conf.h"
#include <glib.h>
@@ -38,6 +29,9 @@
#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "fluidsynth"
static unsigned sample_rate;
static const char *soundfont_path;
/**
* Convert a fluidsynth log level to a GLib log level.
*/
@@ -75,8 +69,21 @@ fluidsynth_mpd_log_function(int level, char *message, G_GNUC_UNUSED void *data)
}
static bool
fluidsynth_init(G_GNUC_UNUSED const struct config_param *param)
fluidsynth_init(const struct config_param *param)
{
GError *error = NULL;
sample_rate = config_get_block_unsigned(param, "sample_rate", 48000);
if (!audio_check_sample_rate(sample_rate, &error)) {
g_warning("%s\n", error->message);
g_error_free(error);
return false;
}
soundfont_path =
config_get_block_string(param, "soundfont",
"/usr/share/sounds/sf2/FluidR3_GM.sf2");
fluid_set_log_function(LAST_LOG_LEVEL,
fluidsynth_mpd_log_function, NULL);
@@ -86,36 +93,24 @@ fluidsynth_init(G_GNUC_UNUSED const struct config_param *param)
static void
fluidsynth_file_decode(struct decoder *decoder, const char *path_fs)
{
static const struct audio_format audio_format = {
.sample_rate = 48000,
.format = SAMPLE_FORMAT_S16,
.channels = 2,
};
char setting_sample_rate[] = "synth.sample-rate";
/*
char setting_verbose[] = "synth.verbose";
char setting_yes[] = "yes";
*/
const char *soundfont_path;
fluid_settings_t *settings;
fluid_synth_t *synth;
fluid_player_t *player;
char *path_dup;
int ret;
struct timer *timer;
enum decoder_command cmd;
soundfont_path =
config_get_string("soundfont",
"/usr/share/sounds/sf2/FluidR3_GM.sf2");
/* set up fluid settings */
settings = new_fluid_settings();
if (settings == NULL)
return;
fluid_settings_setnum(settings, setting_sample_rate, 48000);
fluid_settings_setnum(settings, setting_sample_rate, sample_rate);
/*
fluid_settings_setstr(settings, setting_verbose, setting_yes);
@@ -146,11 +141,7 @@ fluidsynth_file_decode(struct decoder *decoder, const char *path_fs)
return;
}
/* temporarily duplicate the path_fs string, because
fluidsynth wants a writable string */
path_dup = g_strdup(path_fs);
ret = fluid_player_add(player, path_dup);
g_free(path_dup);
ret = fluid_player_add(player, path_fs);
if (ret != 0) {
g_warning("fluid_player_add() failed");
delete_fluid_player(player);
@@ -170,47 +161,34 @@ fluidsynth_file_decode(struct decoder *decoder, const char *path_fs)
return;
}
/* set up a timer for synchronization; fluidsynth always
decodes in real time, which forces us to synchronize */
/* XXX is there any way to switch off real-time decoding? */
timer = timer_new(&audio_format);
timer_start(timer);
/* initialization complete - announce the audio format to the
MPD core */
struct audio_format audio_format;
audio_format_init(&audio_format, sample_rate, SAMPLE_FORMAT_S16, 2);
decoder_initialized(decoder, &audio_format, false, -1);
do {
while (fluid_player_get_status(player) == FLUID_PLAYER_PLAYING) {
int16_t buffer[2048];
const unsigned max_frames = G_N_ELEMENTS(buffer) / 2;
/* synchronize with the fluid player */
timer_add(timer, sizeof(buffer));
timer_sync(timer);
/* read samples from fluidsynth and send them to the
MPD core */
ret = fluid_synth_write_s16(synth, max_frames,
buffer, 0, 2,
buffer, 1, 2);
/* XXX how do we see whether the player is done? We
can't access the private attribute
player->status */
if (ret != 0)
break;
cmd = decoder_data(decoder, NULL, buffer, sizeof(buffer),
0);
} while (cmd == DECODE_COMMAND_NONE);
if (cmd != DECODE_COMMAND_NONE)
break;
}
/* clean up */
timer_free(timer);
fluid_player_stop(player);
fluid_player_join(player);
@@ -224,10 +202,7 @@ fluidsynth_scan_file(const char *file,
G_GNUC_UNUSED const struct tag_handler *handler,
G_GNUC_UNUSED void *handler_ctx)
{
/* to be implemented */
(void)file;
return true;
return fluid_is_midifile(file);
}
static const char *const fluidsynth_suffixes[] = {

View File

@@ -365,12 +365,11 @@ static void mp3_parse_id3(struct mp3_data *data, size_t tagsize,
replay_gain_db = decoder_replay_gain(data->decoder, &rgi);
data->found_replay_gain = true;
}
if (parse_id3_mixramp(&mixramp_start, &mixramp_end, id3_tag)) {
g_debug("setting mixramp_tags");
if (parse_id3_mixramp(&mixramp_start, &mixramp_end, id3_tag))
decoder_mixramp(data->decoder, replay_gain_db,
mixramp_start, mixramp_end);
}
}
id3_tag_delete(id3_tag);

View File

@@ -24,6 +24,7 @@
#include "utils.h"
#include "tag_table.h"
#include "tag_handler.h"
#include "tag_ape.h"
#include <wavpack/wavpack.h>
#include <glib.h>
@@ -38,21 +39,6 @@
#define ERRORLEN 80
static const struct tag_table wavpack_tags[] = {
{ "artist", TAG_ARTIST },
{ "album", TAG_ALBUM },
{ "title", TAG_TITLE },
{ "track", TAG_TRACK },
{ "name", TAG_NAME },
{ "genre", TAG_GENRE },
{ "date", TAG_DATE },
{ "composer", TAG_COMPOSER },
{ "performer", TAG_PERFORMER },
{ "comment", TAG_COMMENT },
{ "disc", TAG_DISC },
{ NULL, TAG_NUM_OF_ITEM_TYPES }
};
/** A pointer type for format converter function. */
typedef void (*format_samples_t)(
int bytes_per_sample,
@@ -321,7 +307,17 @@ wavpack_scan_file(const char *fname,
WavpackGetNumSamples(wpc) /
WavpackGetSampleRate(wpc));
for (const struct tag_table *i = wavpack_tags; i->name != NULL; ++i)
/* the WavPack format implies APEv2 tags, which means we can
reuse the mapping from tag_ape.c */
for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) {
const char *name = tag_item_names[i];
if (name != NULL)
wavpack_scan_tag_item(wpc, name, (enum tag_type)i,
handler, handler_ctx);
}
for (const struct tag_table *i = ape_tags; i->name != NULL; ++i)
wavpack_scan_tag_item(wpc, i->name, i->type,
handler, handler_ctx);

View File

@@ -169,7 +169,6 @@ dc_mixramp_start(struct decoder_control *dc, char *mixramp_start)
g_free(dc->mixramp_start);
dc->mixramp_start = mixramp_start;
g_debug("mixramp_start = %s", mixramp_start ? mixramp_start : "NULL");
}
void
@@ -179,7 +178,6 @@ dc_mixramp_end(struct decoder_control *dc, char *mixramp_end)
g_free(dc->mixramp_end);
dc->mixramp_end = mixramp_end;
g_debug("mixramp_end = %s", mixramp_end ? mixramp_end : "NULL");
}
void
@@ -189,5 +187,4 @@ dc_mixramp_prev_end(struct decoder_control *dc, char *mixramp_prev_end)
g_free(dc->mixramp_prev_end);
dc->mixramp_prev_end = mixramp_prev_end;
g_debug("mixramp_prev_end = %s", mixramp_prev_end ? mixramp_prev_end : "NULL");
}

View File

@@ -468,7 +468,6 @@ decoder_task(gpointer arg)
switch (dc->command) {
case DECODE_COMMAND_START:
g_debug("clearing mixramp tags");
dc_mixramp_start(dc, NULL);
dc_mixramp_prev_end(dc, dc->mixramp_end);
dc->mixramp_end = NULL; /* Don't free, it's copied above. */

View File

@@ -26,7 +26,6 @@
#include "pcm_buffer.h"
#include "pcm_volume.h"
#include "audio_format.h"
#include "player_control.h"
#include <assert.h>
#include <string.h>

View File

@@ -266,7 +266,7 @@ mpd_inotify_callback(int wd, unsigned mask,
(mask & IN_ISDIR) != 0) {
/* a sub directory was changed: register those in
inotify */
const char *root = mapper_get_music_directory();
const char *root = mapper_get_music_directory_fs();
const char *path_fs;
char *allocated = NULL;
@@ -308,7 +308,7 @@ mpd_inotify_init(unsigned max_depth)
g_debug("initializing inotify");
const char *path = mapper_get_music_directory();
const char *path = mapper_get_music_directory_fs();
if (path == NULL) {
g_debug("no music directory configured");
return;

View File

@@ -22,16 +22,13 @@
#include "input_internal.h"
#include "input_plugin.h"
#include <libavutil/avutil.h>
#include <libavformat/avio.h>
#include <libavformat/avformat.h>
#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "input_ffmpeg"
#ifndef AV_VERSION_INT
#define AV_VERSION_INT(a, b, c) (a<<16 | b<<8 | c)
#endif
struct input_ffmpeg {
struct input_stream base;

View File

@@ -29,10 +29,6 @@
#include <stdbool.h>
#include <sys/types.h>
#if !GLIB_CHECK_VERSION(2,14,0)
typedef gint64 goffset;
#endif
struct input_stream {
/**
* the plugin which implements this input stream

View File

@@ -127,7 +127,7 @@ locate_tag_search(const struct song *song, enum tag_type type, const char *str)
{
bool ret = false;
if (type == LOCATE_TAG_FILE_TYPE || type == LOCATE_TAG_ANY_TYPE) {
if (type == LOCATE_TAG_FILE_TYPE || (int)type == LOCATE_TAG_ANY_TYPE) {
char *uri = song_get_uri(song);
char *p = g_utf8_casefold(uri, -1);
g_free(uri);
@@ -147,7 +147,7 @@ locate_tag_search(const struct song *song, enum tag_type type, const char *str)
for (unsigned i = 0; i < song->tag->num_items && !ret; i++) {
visited_types[song->tag->items[i]->type] = true;
if (type != LOCATE_TAG_ANY_TYPE &&
if ((int)type != LOCATE_TAG_ANY_TYPE &&
song->tag->items[i]->type != type) {
continue;
}
@@ -185,7 +185,7 @@ locate_song_search(const struct song *song,
static bool
locate_tag_match(const struct song *song, enum tag_type type, const char *str)
{
if (type == LOCATE_TAG_FILE_TYPE || type == LOCATE_TAG_ANY_TYPE) {
if (type == LOCATE_TAG_FILE_TYPE || (int)type == LOCATE_TAG_ANY_TYPE) {
char *uri = song_get_uri(song);
bool matches = strcmp(str, uri) == 0;
g_free(uri);
@@ -205,7 +205,7 @@ locate_tag_match(const struct song *song, enum tag_type type, const char *str)
for (unsigned i = 0; i < song->tag->num_items; i++) {
visited_types[song->tag->items[i]->type] = true;
if (type != LOCATE_TAG_ANY_TYPE &&
if ((int)type != LOCATE_TAG_ANY_TYPE &&
song->tag->items[i]->type != type) {
continue;
}

View File

@@ -55,7 +55,7 @@ static const char *log_charset;
static bool stdout_mode = true;
static int out_fd;
static const char *out_filename;
static char *out_filename;
static void redirect_logs(int fd)
{
@@ -134,14 +134,15 @@ open_log_file(void)
}
static bool
log_init_file(const char *path, unsigned line, GError **error_r)
log_init_file(unsigned line, GError **error_r)
{
out_filename = path;
assert(out_filename != NULL);
out_fd = open_log_file();
if (out_fd < 0) {
g_set_error(error_r, log_quark(), errno,
"failed to open log file \"%s\" (config line %u): %s",
path, line, g_strerror(errno));
out_filename, line, g_strerror(errno));
return false;
}
@@ -271,22 +272,33 @@ log_init(bool verbose, bool use_stdout, GError **error_r)
return true;
#endif
} else {
GError *error = NULL;
char *path = config_dup_path(CONF_LOG_FILE, &error);
if (path == NULL) {
assert(error != NULL);
g_propagate_error(error_r, error);
return false;
}
bool success = log_init_file(path, param->line,
error_r);
g_free(path);
return success;
out_filename = config_dup_path(CONF_LOG_FILE, error_r);
return out_filename != NULL &&
log_init_file(param->line, error_r);
}
}
}
static void
close_log_files(void)
{
if (stdout_mode)
return;
#ifdef HAVE_SYSLOG
if (out_filename == NULL)
closelog();
#endif
}
void
log_deinit(void)
{
close_log_files();
g_free(out_filename);
}
void setup_log_output(bool use_stdout)
{
fflush(NULL);
@@ -327,15 +339,3 @@ int cycle_log_files(void)
g_debug("Done cycling log files\n");
return 0;
}
void close_log_files(void)
{
if (stdout_mode)
return;
#ifdef HAVE_SYSLOG
if (out_filename == NULL)
closelog();
#endif
}

View File

@@ -44,10 +44,11 @@ log_early_init(bool verbose);
bool
log_init(bool verbose, bool use_stdout, GError **error_r);
void
log_deinit(void);
void setup_log_output(bool use_stdout);
int cycle_log_files(void);
void close_log_files(void);
#endif /* LOG_H */

View File

@@ -135,10 +135,8 @@ glue_mapper_init(GError **error_r)
return false;
}
#if GLIB_CHECK_VERSION(2,14,0)
if (music_dir == NULL)
music_dir = g_strdup(g_get_user_special_dir(G_USER_DIRECTORY_MUSIC));
#endif
mapper_init(music_dir, playlist_dir);
@@ -538,6 +536,6 @@ int mpd_main(int argc, char *argv[])
WSACleanup();
#endif
close_log_files();
log_deinit();
return EXIT_SUCCESS;
}

View File

@@ -36,10 +36,24 @@
#include <errno.h>
#include <dirent.h>
static char *music_dir;
static size_t music_dir_length;
/**
* The absolute path of the music directory encoded in UTF-8.
*/
static char *music_dir_utf8;
static size_t music_dir_utf8_length;
static char *playlist_dir;
/**
* The absolute path of the music directory encoded in the filesystem
* character set.
*/
static char *music_dir_fs;
static size_t music_dir_fs_length;
/**
* The absolute path of the playlist directory encoded in the
* filesystem character set.
*/
static char *playlist_dir_fs;
/**
* Duplicate a string, chop all trailing slashes.
@@ -79,27 +93,28 @@ check_directory(const char *path)
#endif
DIR *dir = opendir(path);
if (dir == NULL && errno == EACCES)
g_warning("No permission to read directory: %s", path);
else
if (dir != NULL)
closedir(dir);
else if (errno == EACCES)
g_warning("No permission to read directory: %s", path);
}
static void
mapper_set_music_dir(const char *path)
mapper_set_music_dir(const char *path_utf8)
{
check_directory(path);
music_dir_utf8 = strdup_chop_slash(path_utf8);
music_dir_utf8_length = strlen(music_dir_utf8);
music_dir = strdup_chop_slash(path);
music_dir_length = strlen(music_dir);
music_dir_fs = utf8_to_fs_charset(music_dir_utf8);
check_directory(music_dir_fs);
music_dir_fs_length = strlen(music_dir_fs);
}
static void
mapper_set_playlist_dir(const char *path)
mapper_set_playlist_dir(const char *path_utf8)
{
check_directory(path);
playlist_dir = g_strdup(path);
playlist_dir_fs = utf8_to_fs_charset(path_utf8);
check_directory(playlist_dir_fs);
}
void mapper_init(const char *_music_dir, const char *_playlist_dir)
@@ -113,23 +128,31 @@ void mapper_init(const char *_music_dir, const char *_playlist_dir)
void mapper_finish(void)
{
g_free(music_dir);
g_free(playlist_dir);
g_free(music_dir_utf8);
g_free(music_dir_fs);
g_free(playlist_dir_fs);
}
const char *
mapper_get_music_directory(void)
mapper_get_music_directory_utf8(void)
{
return music_dir;
return music_dir_utf8;
}
const char *
mapper_get_music_directory_fs(void)
{
return music_dir_fs;
}
const char *
map_to_relative_path(const char *path_utf8)
{
return music_dir != NULL &&
memcmp(path_utf8, music_dir, music_dir_length) == 0 &&
G_IS_DIR_SEPARATOR(path_utf8[music_dir_length])
? path_utf8 + music_dir_length + 1
return music_dir_utf8 != NULL &&
memcmp(path_utf8, music_dir_utf8,
music_dir_utf8_length) == 0 &&
G_IS_DIR_SEPARATOR(path_utf8[music_dir_utf8_length])
? path_utf8 + music_dir_utf8_length + 1
: path_utf8;
}
@@ -141,14 +164,14 @@ map_uri_fs(const char *uri)
assert(uri != NULL);
assert(*uri != '/');
if (music_dir == NULL)
if (music_dir_fs == NULL)
return NULL;
uri_fs = utf8_to_fs_charset(uri);
if (uri_fs == NULL)
return NULL;
path_fs = g_build_filename(music_dir, uri_fs, NULL);
path_fs = g_build_filename(music_dir_fs, uri_fs, NULL);
g_free(uri_fs);
return path_fs;
@@ -157,10 +180,11 @@ map_uri_fs(const char *uri)
char *
map_directory_fs(const struct directory *directory)
{
assert(music_dir != NULL);
assert(music_dir_utf8 != NULL);
assert(music_dir_fs != NULL);
if (directory_is_root(directory))
return g_strdup(music_dir);
return g_strdup(music_dir_fs);
return map_uri_fs(directory_get_path(directory));
}
@@ -168,9 +192,10 @@ map_directory_fs(const struct directory *directory)
char *
map_directory_child_fs(const struct directory *directory, const char *name)
{
char *name_fs, *parent_fs, *path;
assert(music_dir_utf8 != NULL);
assert(music_dir_fs != NULL);
assert(music_dir != NULL);
char *name_fs, *parent_fs, *path;
/* check for invalid or unauthorized base names */
if (*name == 0 || strchr(name, '/') != NULL ||
@@ -208,11 +233,11 @@ map_song_fs(const struct song *song)
char *
map_fs_to_utf8(const char *path_fs)
{
if (music_dir != NULL &&
strncmp(path_fs, music_dir, music_dir_length) == 0 &&
G_IS_DIR_SEPARATOR(path_fs[music_dir_length]))
if (music_dir_fs != NULL &&
strncmp(path_fs, music_dir_fs, music_dir_fs_length) == 0 &&
G_IS_DIR_SEPARATOR(path_fs[music_dir_fs_length]))
/* remove musicDir prefix */
path_fs += music_dir_length + 1;
path_fs += music_dir_fs_length + 1;
else if (G_IS_DIR_SEPARATOR(path_fs[0]))
/* not within musicDir */
return NULL;
@@ -226,7 +251,7 @@ map_fs_to_utf8(const char *path_fs)
const char *
map_spl_path(void)
{
return playlist_dir;
return playlist_dir_fs;
}
char *
@@ -234,7 +259,7 @@ map_spl_utf8_to_fs(const char *name)
{
char *filename_utf8, *filename_fs, *path;
if (playlist_dir == NULL)
if (playlist_dir_fs == NULL)
return NULL;
filename_utf8 = g_strconcat(name, PLAYLIST_FILE_SUFFIX, NULL);
@@ -243,7 +268,7 @@ map_spl_utf8_to_fs(const char *name)
if (filename_fs == NULL)
return NULL;
path = g_build_filename(playlist_dir, filename_fs, NULL);
path = g_build_filename(playlist_dir_fs, filename_fs, NULL);
g_free(filename_fs);
return path;

View File

@@ -36,9 +36,20 @@ void mapper_init(const char *_music_dir, const char *_playlist_dir);
void mapper_finish(void);
/**
* Return the absolute path of the music directory encoded in UTF-8.
*/
G_GNUC_CONST
const char *
mapper_get_music_directory(void);
mapper_get_music_directory_utf8(void);
/**
* Return the absolute path of the music directory encoded in the
* filesystem character set.
*/
G_GNUC_CONST
const char *
mapper_get_music_directory_fs(void);
/**
* Returns true if a music directory was configured.
@@ -47,7 +58,7 @@ G_GNUC_CONST
static inline bool
mapper_has_music_directory(void)
{
return mapper_get_music_directory() != NULL;
return mapper_get_music_directory_utf8() != NULL;
}
/**

View File

@@ -53,6 +53,31 @@ httpd_output_quark(void)
return g_quark_from_static_string("httpd_output");
}
/**
* Check whether there is at least one client.
*
* Caller must lock the mutex.
*/
G_GNUC_PURE
static bool
httpd_output_has_clients(const struct httpd_output *httpd)
{
return httpd->clients != NULL;
}
/**
* Check whether there is at least one client.
*/
G_GNUC_PURE
static bool
httpd_output_lock_has_clients(const struct httpd_output *httpd)
{
g_mutex_lock(httpd->mutex);
bool result = httpd_output_has_clients(httpd);
g_mutex_unlock(httpd->mutex);
return result;
}
static void
httpd_listen_in_event(int fd, const struct sockaddr *address,
size_t address_length, int uid, void *ctx);
@@ -397,6 +422,19 @@ httpd_output_delay(struct audio_output *ao)
{
struct httpd_output *httpd = (struct httpd_output *)ao;
if (!httpd_output_lock_has_clients(httpd) && httpd->base.pause) {
/* if there's no client and this output is paused,
then httpd_output_pause() will not do anything, it
will not fill the buffer and it will not update the
timer; therefore, we reset the timer here */
timer_reset(httpd->timer);
/* some arbitrary delay that is long enough to avoid
consuming too much CPU, and short enough to notice
new clients quickly enough */
return 1000;
}
return httpd->timer->started
? timer_delay(httpd->timer)
: 0;
@@ -475,13 +513,8 @@ httpd_output_play(struct audio_output *ao, const void *chunk, size_t size,
GError **error)
{
struct httpd_output *httpd = (struct httpd_output *)ao;
bool has_clients;
g_mutex_lock(httpd->mutex);
has_clients = httpd->clients != NULL;
g_mutex_unlock(httpd->mutex);
if (has_clients) {
if (httpd_output_lock_has_clients(httpd)) {
bool success;
success = httpd_output_encode_and_play(httpd, chunk, size,
@@ -502,16 +535,11 @@ httpd_output_pause(struct audio_output *ao)
{
struct httpd_output *httpd = (struct httpd_output *)ao;
g_mutex_lock(httpd->mutex);
bool has_clients = httpd->clients != NULL;
g_mutex_unlock(httpd->mutex);
if (has_clients) {
if (httpd_output_lock_has_clients(httpd)) {
static const char silence[1020];
return httpd_output_play(ao, silence, sizeof(silence),
NULL) > 0;
} else {
g_usleep(100000);
return true;
}
}

View File

@@ -608,6 +608,16 @@ mpd_jack_close(G_GNUC_UNUSED struct audio_output *ao)
mpd_jack_stop(jd);
}
static unsigned
mpd_jack_delay(struct audio_output *ao)
{
struct jack_data *jd = (struct jack_data *)ao;
return jd->base.pause && jd->pause && !jd->shutdown
? 1000
: 0;
}
static inline jack_default_audio_sample_t
sample_16_to_jack(int16_t sample)
{
@@ -727,10 +737,6 @@ mpd_jack_pause(struct audio_output *ao)
jd->pause = true;
/* due to a MPD API limitation, we have to sleep a little bit
here, to avoid hogging the CPU */
g_usleep(50000);
return true;
}
@@ -742,6 +748,7 @@ const struct audio_output_plugin jack_output_plugin = {
.enable = mpd_jack_enable,
.disable = mpd_jack_disable,
.open = mpd_jack_open,
.delay = mpd_jack_delay,
.play = mpd_jack_play,
.pause = mpd_jack_pause,
.close = mpd_jack_close,

View File

@@ -681,35 +681,6 @@ pulse_output_close(struct audio_output *ao)
pa_threaded_mainloop_unlock(po->mainloop);
}
/**
* Check if the stream is (already) connected, and waits for a signal
* if not. The mainloop must be locked before calling this function.
*
* @return the current stream state
*/
static pa_stream_state_t
pulse_output_check_stream(struct pulse_output *po)
{
pa_stream_state_t state = pa_stream_get_state(po->stream);
assert(po->mainloop != NULL);
switch (state) {
case PA_STREAM_READY:
case PA_STREAM_FAILED:
case PA_STREAM_TERMINATED:
case PA_STREAM_UNCONNECTED:
break;
case PA_STREAM_CREATING:
pa_threaded_mainloop_wait(po->mainloop);
state = pa_stream_get_state(po->stream);
break;
}
return state;
}
/**
* Check if the stream is (already) connected, and waits if not. The
* mainloop must be locked before calling this function.
@@ -719,35 +690,25 @@ pulse_output_check_stream(struct pulse_output *po)
static bool
pulse_output_wait_stream(struct pulse_output *po, GError **error_r)
{
pa_stream_state_t state = pa_stream_get_state(po->stream);
switch (state) {
while (true) {
switch (pa_stream_get_state(po->stream)) {
case PA_STREAM_READY:
return true;
case PA_STREAM_FAILED:
case PA_STREAM_TERMINATED:
case PA_STREAM_UNCONNECTED:
g_set_error(error_r, pulse_output_quark(), 0,
"disconnected");
return false;
case PA_STREAM_CREATING:
break;
}
do {
state = pulse_output_check_stream(po);
} while (state == PA_STREAM_CREATING);
if (state != PA_STREAM_READY) {
g_set_error(error_r, pulse_output_quark(), 0,
g_set_error(error_r, pulse_output_quark(),
pa_context_errno(po->context),
"failed to connect the stream: %s",
pa_strerror(pa_context_errno(po->context)));
return false;
}
return true;
case PA_STREAM_CREATING:
pa_threaded_mainloop_wait(po->mainloop);
break;
}
}
}
/**
@@ -801,6 +762,24 @@ pulse_output_stream_pause(struct pulse_output *po, bool pause,
return true;
}
static unsigned
pulse_output_delay(struct audio_output *ao)
{
struct pulse_output *po = (struct pulse_output *)ao;
unsigned result = 0;
pa_threaded_mainloop_lock(po->mainloop);
if (po->base.pause && pulse_output_stream_is_paused(po) &&
pa_stream_get_state(po->stream) == PA_STREAM_READY)
/* idle while paused */
result = 1000;
pa_threaded_mainloop_unlock(po->mainloop);
return result;
}
static size_t
pulse_output_play(struct audio_output *ao, const void *chunk, size_t size,
GError **error_r)
@@ -928,13 +907,8 @@ pulse_output_pause(struct audio_output *ao)
/* cork the stream */
if (pulse_output_stream_is_paused(po)) {
/* already paused; due to a MPD API limitation, we
have to sleep a little bit here, to avoid hogging
the CPU */
g_usleep(50000);
} else if (!pulse_output_stream_pause(po, true, &error)) {
if (!pulse_output_stream_is_paused(po) &&
!pulse_output_stream_pause(po, true, &error)) {
pa_threaded_mainloop_unlock(po->mainloop);
g_warning("%s", error->message);
g_error_free(error);
@@ -971,6 +945,7 @@ const struct audio_output_plugin pulse_output_plugin = {
.enable = pulse_output_enable,
.disable = pulse_output_disable,
.open = pulse_output_open,
.delay = pulse_output_delay,
.play = pulse_output_play,
.cancel = pulse_output_cancel,
.pause = pulse_output_pause,

View File

@@ -210,6 +210,14 @@ pc_set_pause(struct player_control *pc, bool pause_flag)
player_unlock(pc);
}
void
pc_set_border_pause(struct player_control *pc, bool border_pause)
{
player_lock(pc);
pc->border_pause = border_pause;
player_unlock(pc);
}
void
pc_get_status(struct player_control *pc, struct player_status *status)
{

View File

@@ -115,6 +115,15 @@ struct player_control {
float mixramp_db;
float mixramp_delay_seconds;
double total_play_time;
/**
* If this flag is set, then the player will be auto-paused at
* the end of the song, before the next song starts to play.
*
* This is a copy of the queue's "single" flag most of the
* time.
*/
bool border_pause;
};
struct player_control *
@@ -207,6 +216,12 @@ pc_set_pause(struct player_control *pc, bool pause_flag);
void
pc_pause(struct player_control *pc);
/**
* Set the player's #border_pause flag.
*/
void
pc_set_border_pause(struct player_control *pc, bool border_pause);
void
pc_kill(struct player_control *pc);

View File

@@ -450,6 +450,8 @@ static bool player_seek_decoder(struct player *player)
assert(pc->next_song != NULL);
const unsigned start_ms = song->start_ms;
if (decoder_current_song(dc) != song) {
/* the decoder is already decoding the "next" song -
stop it and start the previous song again */
@@ -498,7 +500,7 @@ static bool player_seek_decoder(struct player *player)
if (where < 0.0)
where = 0.0;
if (!dc_seek(dc, where + song->start_ms / 1000.0)) {
if (!dc_seek(dc, where + start_ms / 1000.0)) {
/* decoder failure */
player_command_finished(pc);
return false;
@@ -840,6 +842,16 @@ player_song_border(struct player *player)
if (!player_wait_for_decoder(player))
return false;
struct player_control *const pc = player->pc;
player_lock(pc);
if (pc->border_pause) {
player->paused = true;
pc->state = PLAYER_STATE_PAUSE;
}
player_unlock(pc);
return true;
}
@@ -955,7 +967,10 @@ static void do_play(struct player_control *pc, struct decoder_control *dc)
player_dc_start(&player, music_pipe_new());
}
if (player_dc_at_next_song(&player) &&
if (/* no cross-fading if MPD is going to pause at the
end of the current song */
!pc->border_pause &&
player_dc_at_next_song(&player) &&
player.xfade == XFADE_UNKNOWN &&
!decoder_lock_is_starting(dc)) {
/* enable cross fading in this song? if yes,

View File

@@ -109,11 +109,6 @@ playlist_song_started(struct playlist *playlist, struct player_control *pc)
playlist->current = playlist->queued;
playlist->queued = -1;
/* Pause if we are in single mode. */
if(playlist->queue.single && !playlist->queue.repeat) {
pc_set_pause(pc, true);
}
if(playlist->queue.consume)
playlist_delete(playlist, pc,
queue_order_to_position(&playlist->queue,
@@ -239,9 +234,13 @@ playlist_sync(struct playlist *playlist, struct player_control *pc)
if (pc_next_song == NULL && playlist->queued != -1)
playlist_song_started(playlist, pc);
player_lock(pc);
pc_next_song = pc->next_song;
player_unlock(pc);
/* make sure the queued song is always set (if
possible) */
if (pc->next_song == NULL && playlist->queued < 0)
if (pc_next_song == NULL && playlist->queued < 0)
playlist_update_queued_song(playlist, pc, NULL);
}
}
@@ -306,7 +305,11 @@ playlist_set_repeat(struct playlist *playlist, struct player_control *pc,
if (status == playlist->queue.repeat)
return;
playlist->queue.repeat = status;
struct queue *queue = &playlist->queue;
queue->repeat = status;
pc_set_border_pause(pc, queue->single && !queue->repeat);
/* if the last song is currently being played, the "next song"
might change when repeat mode is toggled */
@@ -334,7 +337,11 @@ playlist_set_single(struct playlist *playlist, struct player_control *pc,
if (status == playlist->queue.single)
return;
playlist->queue.single = status;
struct queue *queue = &playlist->queue;
queue->single = status;
pc_set_border_pause(pc, queue->single && !queue->repeat);
/* if the last song is currently being played, the "next song"
might change when single mode is toggled */

View File

@@ -58,12 +58,10 @@ lastfm_init(const struct config_param *param)
lastfm_config.user = g_uri_escape_string(user, NULL, false);
#if GLIB_CHECK_VERSION(2,16,0)
if (strlen(passwd) != 32)
lastfm_config.md5 = g_compute_checksum_for_string(G_CHECKSUM_MD5,
passwd, strlen(passwd));
else
#endif
lastfm_config.md5 = g_strdup(passwd);
return true;

View File

@@ -83,7 +83,7 @@ playlist_append_song(struct playlist *playlist, struct player_control *pc,
queued = playlist_get_queued_song(playlist);
id = queue_append(&playlist->queue, song);
id = queue_append(&playlist->queue, song, 0);
if (playlist->queue.random) {
/* shuffle the new song into the list of remaining

View File

@@ -163,6 +163,9 @@ playlist_provider_print(struct client *client, const char *uri,
song_print_info(client, song);
else
song_print_uri(client, song);
if (!song_in_database(song))
song_free(song);
}
g_free(base_uri);

View File

@@ -69,7 +69,6 @@ apply_song_metadata(struct song *dest, const struct song *src)
} else {
tmp = song_file_new(dest->uri, NULL);
merge_song_metadata(tmp, dest, src);
song_free(dest);
}
if (dest->tag != NULL && dest->tag->time > 0 &&
@@ -80,20 +79,42 @@ apply_song_metadata(struct song *dest, const struct song *src)
(e.g. last track on a CUE file); fix it up here */
tmp->tag->time = dest->tag->time - src->start_ms / 1000;
if (!song_in_database(dest))
song_free(dest);
return tmp;
}
static struct song *
playlist_check_load_song(const struct song *song, const char *uri, bool secure)
{
struct song *dest;
if (uri_has_scheme(uri)) {
dest = song_remote_new(uri);
} else if (g_path_is_absolute(uri) && secure) {
dest = song_file_load(uri, NULL);
if (dest == NULL)
return NULL;
} else {
dest = db_get_song(uri);
if (dest == NULL)
/* not found in database */
return NULL;
}
return apply_song_metadata(dest, song);
}
struct song *
playlist_check_translate_song(struct song *song, const char *base_uri,
bool secure)
{
struct song *dest;
if (song_in_database(song))
/* already ok */
return song;
char *uri = song->uri;
const char *uri = song->uri;
if (uri_has_scheme(uri)) {
if (uri_supported_scheme(uri))
@@ -115,11 +136,11 @@ playlist_check_translate_song(struct song *song, const char *base_uri,
if (g_path_is_absolute(uri)) {
/* XXX fs_charset vs utf8? */
const char *prefix = mapper_get_music_directory();
const char *suffix = map_to_relative_path(uri);
assert(suffix != NULL);
if (prefix != NULL && g_str_has_prefix(uri, prefix) &&
uri[strlen(prefix)] == '/')
uri += strlen(prefix) + 1;
if (suffix != uri)
uri = suffix;
else if (!secure) {
/* local files must be relative to the music
directory when "secure" is enabled */
@@ -130,32 +151,12 @@ playlist_check_translate_song(struct song *song, const char *base_uri,
base_uri = NULL;
}
char *allocated = NULL;
if (base_uri != NULL)
uri = g_build_filename(base_uri, uri, NULL);
else
uri = g_strdup(uri);
uri = allocated = g_build_filename(base_uri, uri, NULL);
if (uri_has_scheme(uri)) {
dest = song_remote_new(uri);
g_free(uri);
} else if (g_path_is_absolute(uri) && secure) {
dest = song_file_load(uri, NULL);
if (dest == NULL) {
struct song *dest = playlist_check_load_song(song, uri, secure);
song_free(song);
return NULL;
}
} else {
dest = db_get_song(uri);
g_free(uri);
if (dest == NULL) {
/* not found in database */
song_free(song);
return dest;
}
}
dest = apply_song_metadata(dest, song);
song_free(song);
g_free(allocated);
return dest;
}

View File

@@ -96,7 +96,7 @@ queue_modify_all(struct queue *queue)
}
unsigned
queue_append(struct queue *queue, struct song *song)
queue_append(struct queue *queue, struct song *song, uint8_t priority)
{
unsigned id = queue_generate_id(queue);
@@ -106,7 +106,7 @@ queue_append(struct queue *queue, struct song *song)
.song = song,
.id = id,
.version = queue->version,
.priority = 0,
.priority = priority,
};
queue->order[queue->length] = queue->length;

View File

@@ -280,9 +280,11 @@ queue_modify_all(struct queue *queue);
*
* If a song is not in the database (determined by
* song_in_database()), it is freed when removed from the queue.
*
* @param priority the priority of this new queue item
*/
unsigned
queue_append(struct queue *queue, struct song *song);
queue_append(struct queue *queue, struct song *song, uint8_t priority);
/**
* Swaps two songs, addressed by their position.

View File

@@ -24,9 +24,12 @@
#include "uri.h"
#include "database.h"
#include "song_save.h"
#include "text_file.h"
#include <stdlib.h>
#define PRIO_LABEL "Prio: "
static void
queue_save_database_song(FILE *fp, int idx, const struct song *song)
{
@@ -54,8 +57,13 @@ queue_save_song(FILE *fp, int idx, const struct song *song)
void
queue_save(FILE *fp, const struct queue *queue)
{
for (unsigned i = 0; i < queue_length(queue); i++)
for (unsigned i = 0; i < queue_length(queue); i++) {
uint8_t prio = queue_get_priority_at_position(queue, i);
if (prio != 0)
fprintf(fp, PRIO_LABEL "%u\n", prio);
queue_save_song(fp, i, queue_get(queue, i));
}
}
static struct song *
@@ -75,6 +83,15 @@ queue_load_song(FILE *fp, GString *buffer, const char *line,
if (queue_is_full(queue))
return;
uint8_t priority = 0;
if (g_str_has_prefix(line, PRIO_LABEL)) {
priority = strtoul(line + sizeof(PRIO_LABEL) - 1, NULL, 10);
line = read_text_line(fp, buffer);
if (line == NULL)
return;
}
if (g_str_has_prefix(line, SONG_BEGIN)) {
const char *uri = line + sizeof(SONG_BEGIN) - 1;
if (!uri_has_scheme(uri) && !g_path_is_absolute(uri))
@@ -102,5 +119,5 @@ queue_load_song(FILE *fp, GString *buffer, const char *line,
return;
}
queue_append(queue, song);
queue_append(queue, song, priority);
}

View File

@@ -24,7 +24,7 @@
#include "tag_handler.h"
#include "ape.h"
static const struct tag_table ape_tags[] = {
const struct tag_table ape_tags[] = {
{ "album artist", TAG_ALBUM_ARTIST },
{ "year", TAG_DATE },
{ NULL, TAG_NUM_OF_ITEM_TYPES }

View File

@@ -20,10 +20,14 @@
#ifndef MPD_TAG_APE_H
#define MPD_TAG_APE_H
#include "tag_table.h"
#include <stdbool.h>
struct tag_handler;
extern const struct tag_table ape_tags[];
/**
* Scan the APE tags of a file.
*

View File

@@ -20,23 +20,14 @@
#include "config.h"
#include "timer.h"
#include "audio_format.h"
#include "clock.h"
#include <glib.h>
#include <assert.h>
#include <limits.h>
#include <sys/time.h>
#include <stddef.h>
static uint64_t now(void)
{
struct timeval tv;
gettimeofday(&tv, NULL);
return ((uint64_t)tv.tv_sec * 1000000) + tv.tv_usec;
}
struct timer *timer_new(const struct audio_format *af)
{
struct timer *timer = g_new(struct timer, 1);
@@ -54,7 +45,7 @@ void timer_free(struct timer *timer)
void timer_start(struct timer *timer)
{
timer->time = now();
timer->time = monotonic_clock_us();
timer->started = 1;
}
@@ -74,7 +65,7 @@ void timer_add(struct timer *timer, int size)
unsigned
timer_delay(const struct timer *timer)
{
int64_t delay = (int64_t)(timer->time - now()) / 1000;
int64_t delay = (int64_t)(timer->time - monotonic_clock_us()) / 1000;
if (delay < 0)
return 0;
@@ -90,7 +81,7 @@ void timer_sync(struct timer *timer)
assert(timer->started);
sleep_duration = timer->time - now();
sleep_duration = timer->time - monotonic_clock_us();
if (sleep_duration > 0)
g_usleep(sleep_duration);
}

View File

@@ -283,12 +283,20 @@ skip_symlink(const struct directory *directory, const char *utf8_name)
return true;
char buffer[MPD_PATH_MAX];
ssize_t ret = readlink(path_fs, buffer, sizeof(buffer));
ssize_t length = readlink(path_fs, buffer, sizeof(buffer));
g_free(path_fs);
if (ret < 0)
if (length < 0)
/* don't skip if this is not a symlink */
return errno != EINVAL;
if ((size_t)length >= sizeof(buffer))
/* skip symlinks when the buffer is too small for the
link target */
return true;
/* null-terminate the buffer, because readlink() will not */
buffer[length] = 0;
if (!follow_inside_symlinks && !follow_outside_symlinks) {
/* ignore all symlinks */
return true;

View File

@@ -20,7 +20,6 @@
#include "config.h"
#include "volume.h"
#include "conf.h"
#include "player_control.h"
#include "idle.h"
#include "pcm_volume.h"
#include "output_all.h"

View File

@@ -41,7 +41,7 @@ main(G_GNUC_UNUSED int argc, G_GNUC_UNUSED char **argv)
queue_init(&queue, 32);
for (unsigned i = 0; i < G_N_ELEMENTS(songs); ++i)
queue_append(&queue, &songs[i]);
queue_append(&queue, &songs[i], 0);
assert(queue_length(&queue) == G_N_ELEMENTS(songs));

View File

@@ -39,6 +39,14 @@
fun:g_main_loop_run
}
{
<insert_a_suppression_name_here>
Memcheck:Leak
fun:*alloc
...
fun:g_once_init_leave
}
{
g_log
Memcheck:Leak
@@ -57,6 +65,14 @@
fun:g_slice_*
}
{
<insert_a_suppression_name_here>
Memcheck:Leak
fun:*alloc
...
fun:g_static_mutex_get_mutex_impl
}
{
g_private
Memcheck:Leak
@@ -483,3 +499,24 @@
fun:?alloc
fun:snd1_dlobj_cache_get
}
{
<insert_a_suppression_name_here>
Memcheck:Leak
fun:_Znwm
...
obj:*/libjack.so*
fun:call_init
fun:_dl_init
}
{
<insert_a_suppression_name_here>
Memcheck:Leak
fun:*alloc
fun:_dl_allocate_tls
...
obj:*/libffado.so*
fun:call_init
fun:_dl_init
}