Compare commits
79 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ffb3a9f526 | ||
|
|
9761abf3b5 | ||
|
|
599a562170 | ||
|
|
d29a251547 | ||
|
|
31da4bc566 | ||
|
|
0f1a180e15 | ||
|
|
01a45a53aa | ||
|
|
a9a5907a0f | ||
|
|
8fb20fcdf8 | ||
|
|
72bf226608 | ||
|
|
d4b5699403 | ||
|
|
1dc27be015 | ||
|
|
230a3eb400 | ||
|
|
e39382dedd | ||
|
|
fd016f4507 | ||
|
|
9d728b365d | ||
|
|
ddc0283339 | ||
|
|
03a401e477 | ||
|
|
9994521b8c | ||
|
|
adbe8c409a | ||
|
|
58e600f408 | ||
|
|
d34e55c370 | ||
|
|
fbcbcdc001 | ||
|
|
4227a325a5 | ||
|
|
d115507502 | ||
|
|
43d8252050 | ||
|
|
674b4ab647 | ||
|
|
fe8fc1081a | ||
|
|
c7748fedab | ||
|
|
c392efb481 | ||
|
|
1ddd9dd52a | ||
|
|
f672e4016f | ||
|
|
76e3dec723 | ||
|
|
ba6ef53ef9 | ||
|
|
c93a28c641 | ||
|
|
7088a679a2 | ||
|
|
04c02a1eb8 | ||
|
|
41487426f5 | ||
|
|
0d24250aa7 | ||
|
|
2050e2f886 | ||
|
|
013e8479af | ||
|
|
27535a7f78 | ||
|
|
acaa725478 | ||
|
|
f351550534 | ||
|
|
66ecf39efe | ||
|
|
5ad21d7e98 | ||
|
|
ef5125f8f4 | ||
|
|
bf2e07074b | ||
|
|
20695ef369 | ||
|
|
9374e0f445 | ||
|
|
19ed233118 | ||
|
|
faa4fff4dd | ||
|
|
2276e7677b | ||
|
|
93f9c2ab6b | ||
|
|
4a993cd79e | ||
|
|
02325d2ede | ||
|
|
9c83464b95 | ||
|
|
b1bbd70f0f | ||
|
|
c31d11bfe0 | ||
|
|
c8ec85d649 | ||
|
|
e291f3d257 | ||
|
|
dc22846d58 | ||
|
|
c9aaabb5d4 | ||
|
|
335d5d5d72 | ||
|
|
51d793bec1 | ||
|
|
249dcd967e | ||
|
|
302972e9fc | ||
|
|
31b380b266 | ||
|
|
a869dfea85 | ||
|
|
12838c6294 | ||
|
|
49c7102547 | ||
|
|
1ae8972859 | ||
|
|
adcd2c8eac | ||
|
|
45ff355835 | ||
|
|
f8bf3afeae | ||
|
|
f703da1516 | ||
|
|
a582deee2c | ||
|
|
12be9e818f | ||
|
|
281cd7c057 |
4
.gitignore
vendored
4
.gitignore
vendored
@@ -67,3 +67,7 @@ test/run_ntp_server
|
||||
test/run_resolver
|
||||
test/run_tcp_connect
|
||||
test/test_pcm
|
||||
test/dump_rva2
|
||||
test/dump_text_file
|
||||
test/test_byte_reverse
|
||||
test/test_vorbis_encoder
|
||||
|
||||
2
INSTALL
2
INSTALL
@@ -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.
|
||||
|
||||
@@ -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 \
|
||||
|
||||
37
NEWS
37
NEWS
@@ -1,3 +1,40 @@
|
||||
ver 0.17.3 (2013/01/06)
|
||||
* output:
|
||||
- osx: fix pops during playback
|
||||
- recorder: fix I/O error check
|
||||
- shout: fix memory leak in error handler
|
||||
- recorder, shout: support Ogg packets that span more than one page
|
||||
* decoder:
|
||||
- ffmpeg: ignore negative time stamps
|
||||
- ffmpeg: support planar audio
|
||||
* playlist:
|
||||
- cue: fix memory leak
|
||||
- cue: fix CUE files with only one track
|
||||
|
||||
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}
|
||||
|
||||
13
configure.ac
13
configure.ac
@@ -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.3, 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],
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -113,10 +113,9 @@
|
||||
#
|
||||
#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.
|
||||
#
|
||||
# This setting defines a list of tag types that will be extracted during the
|
||||
# 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
|
||||
|
||||
71
doc/user.xml
71
doc/user.xml
@@ -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>
|
||||
|
||||
@@ -73,7 +73,8 @@ AC_DEFUN([MPD_AUTO_PKG_LIB], [
|
||||
[eval "found_$1=yes"],
|
||||
AC_CHECK_LIB($4, $5,
|
||||
[eval "found_$1=yes $2_LIBS='$6' $2_CFLAGS='$7'"],
|
||||
[eval "found_$1=no"]))
|
||||
[eval "found_$1=no"],
|
||||
[$6]))
|
||||
fi
|
||||
|
||||
MPD_AUTO_RESULT([$1], [$8], [$9])
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[Unit]
|
||||
Description=Music Player Daemon
|
||||
After=sound.target
|
||||
After=network.target sound.target
|
||||
|
||||
[Service]
|
||||
ExecStart=@prefix@/bin/mpd --no-daemon
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
95
src/clock.c
Normal 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
41
src/clock.h
Normal 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
|
||||
@@ -213,12 +213,12 @@ parse_cmdline(int argc, char **argv, struct options *options,
|
||||
if(g_file_test(system_path,
|
||||
G_FILE_TEST_IS_REGULAR)) {
|
||||
ret = config_read_file(system_path,error_r);
|
||||
g_free(system_path);
|
||||
break;
|
||||
}
|
||||
++i;;
|
||||
} else
|
||||
g_free(system_path);
|
||||
++i;
|
||||
}
|
||||
g_free(system_path);
|
||||
g_free(&system_config_dirs);
|
||||
}
|
||||
#else /* G_OS_WIN32 */
|
||||
char *path2;
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -58,9 +58,35 @@ struct cue_parser {
|
||||
|
||||
char *filename;
|
||||
|
||||
struct song *current, *previous, *finished;
|
||||
/**
|
||||
* The song currently being edited.
|
||||
*/
|
||||
struct song *current;
|
||||
|
||||
/**
|
||||
* The previous song. It is remembered because its end_time
|
||||
* will be set to the current song's start time.
|
||||
*/
|
||||
struct song *previous;
|
||||
|
||||
/**
|
||||
* A song that is completely finished and can be returned to
|
||||
* the caller via cue_parser_get().
|
||||
*/
|
||||
struct song *finished;
|
||||
|
||||
/**
|
||||
* Set to true after previous.end_time has been updated to the
|
||||
* start time of the current song.
|
||||
*/
|
||||
bool last_updated;
|
||||
|
||||
/**
|
||||
* Tracks whether cue_parser_finish() has been called. If
|
||||
* true, then all remaining (partial) results will be
|
||||
* delivered by cue_parser_get().
|
||||
*/
|
||||
bool end;
|
||||
};
|
||||
|
||||
struct cue_parser *
|
||||
@@ -73,6 +99,7 @@ cue_parser_new(void)
|
||||
parser->current = NULL;
|
||||
parser->previous = NULL;
|
||||
parser->finished = NULL;
|
||||
parser->end = false;
|
||||
return parser;
|
||||
}
|
||||
|
||||
@@ -85,6 +112,9 @@ cue_parser_free(struct cue_parser *parser)
|
||||
if (parser->current != NULL)
|
||||
song_free(parser->current);
|
||||
|
||||
if (parser->previous != NULL)
|
||||
song_free(parser->previous);
|
||||
|
||||
if (parser->finished != NULL)
|
||||
song_free(parser->finished);
|
||||
|
||||
@@ -201,10 +231,32 @@ cue_parse_position(const char *p)
|
||||
return minutes * 60000 + seconds * 1000 + frames * 1000 / 75;
|
||||
}
|
||||
|
||||
/**
|
||||
* Commit the current song. It will be moved to "previous", so the
|
||||
* next song may soon edit its end time (using the next song's start
|
||||
* time).
|
||||
*/
|
||||
static void
|
||||
cue_parser_commit(struct cue_parser *parser)
|
||||
{
|
||||
/* the caller of this library must call cue_parser_get() often
|
||||
enough */
|
||||
assert(parser->finished == NULL);
|
||||
assert(!parser->end);
|
||||
|
||||
if (parser->current == NULL)
|
||||
return;
|
||||
|
||||
parser->finished = parser->previous;
|
||||
parser->previous = parser->current;
|
||||
parser->current = NULL;
|
||||
}
|
||||
|
||||
static void
|
||||
cue_parser_feed2(struct cue_parser *parser, char *p)
|
||||
{
|
||||
assert(parser != NULL);
|
||||
assert(!parser->end);
|
||||
assert(p != NULL);
|
||||
|
||||
const char *command = cue_next_token(&p);
|
||||
@@ -216,16 +268,26 @@ 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);
|
||||
else if (parser->state == TRACK)
|
||||
cue_add_tag(parser->current->tag, TAG_TITLE, p);
|
||||
} else if (strcmp(command, "FILE") == 0) {
|
||||
cue_parser_finish(parser);
|
||||
cue_parser_commit(parser);
|
||||
|
||||
const char *filename = cue_next_value(&p);
|
||||
if (filename == NULL)
|
||||
@@ -248,7 +310,7 @@ cue_parser_feed2(struct cue_parser *parser, char *p)
|
||||
} else if (parser->state == IGNORE_FILE) {
|
||||
return;
|
||||
} else if (strcmp(command, "TRACK") == 0) {
|
||||
cue_parser_finish(parser);
|
||||
cue_parser_commit(parser);
|
||||
|
||||
const char *nr = cue_next_token(&p);
|
||||
if (nr == NULL)
|
||||
@@ -300,6 +362,7 @@ void
|
||||
cue_parser_feed(struct cue_parser *parser, const char *line)
|
||||
{
|
||||
assert(parser != NULL);
|
||||
assert(!parser->end);
|
||||
assert(line != NULL);
|
||||
|
||||
char *allocated = g_strdup(line);
|
||||
@@ -310,12 +373,12 @@ cue_parser_feed(struct cue_parser *parser, const char *line)
|
||||
void
|
||||
cue_parser_finish(struct cue_parser *parser)
|
||||
{
|
||||
if (parser->finished != NULL)
|
||||
song_free(parser->finished);
|
||||
if (parser->end)
|
||||
/* has already been called, ignore */
|
||||
return;
|
||||
|
||||
parser->finished = parser->previous;
|
||||
parser->previous = parser->current;
|
||||
parser->current = NULL;
|
||||
cue_parser_commit(parser);
|
||||
parser->end = true;
|
||||
}
|
||||
|
||||
struct song *
|
||||
@@ -323,6 +386,15 @@ cue_parser_get(struct cue_parser *parser)
|
||||
{
|
||||
assert(parser != NULL);
|
||||
|
||||
if (parser->finished == NULL && parser->end) {
|
||||
/* cue_parser_finish() has been called already:
|
||||
deliver all remaining (partial) results */
|
||||
assert(parser->current == NULL);
|
||||
|
||||
parser->finished = parser->previous;
|
||||
parser->previous = NULL;
|
||||
}
|
||||
|
||||
struct song *song = parser->finished;
|
||||
parser->finished = NULL;
|
||||
return song;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -234,6 +234,21 @@ time_to_ffmpeg(double t, const AVRational time_base)
|
||||
}
|
||||
|
||||
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(53,25,0)
|
||||
|
||||
static void
|
||||
copy_interleave_frame2(uint8_t *dest, uint8_t **src,
|
||||
unsigned nframes, unsigned nchannels,
|
||||
unsigned sample_size)
|
||||
{
|
||||
for (unsigned frame = 0; frame < nframes; ++frame) {
|
||||
for (unsigned channel = 0; channel < nchannels; ++channel) {
|
||||
memcpy(dest, src[channel] + frame * sample_size,
|
||||
sample_size);
|
||||
dest += sample_size;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy PCM data from a AVFrame to an interleaved buffer.
|
||||
*/
|
||||
@@ -254,11 +269,10 @@ copy_interleave_frame(const AVCodecContext *codec_context,
|
||||
|
||||
if (av_sample_fmt_is_planar(codec_context->sample_fmt) &&
|
||||
codec_context->channels > 1) {
|
||||
for (int i = 0, channels = codec_context->channels;
|
||||
i < channels; i++) {
|
||||
memcpy(buffer, frame->extended_data[i], plane_size);
|
||||
buffer += plane_size;
|
||||
}
|
||||
copy_interleave_frame2(buffer, frame->extended_data,
|
||||
frame->nb_samples,
|
||||
codec_context->channels,
|
||||
av_get_bytes_per_sample(codec_context->sample_fmt));
|
||||
} else {
|
||||
memcpy(buffer, frame->extended_data[0], data_size);
|
||||
}
|
||||
@@ -273,7 +287,7 @@ ffmpeg_send_packet(struct decoder *decoder, struct input_stream *is,
|
||||
AVCodecContext *codec_context,
|
||||
const AVRational *time_base)
|
||||
{
|
||||
if (packet->pts != (int64_t)AV_NOPTS_VALUE)
|
||||
if (packet->pts >= 0 && packet->pts != (int64_t)AV_NOPTS_VALUE)
|
||||
decoder_timestamp(decoder,
|
||||
time_from_ffmpeg(packet->pts, *time_base));
|
||||
|
||||
@@ -352,12 +366,20 @@ ffmpeg_send_packet(struct decoder *decoder, struct input_stream *is,
|
||||
return cmd;
|
||||
}
|
||||
|
||||
#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(52, 94, 1)
|
||||
#define AVSampleFormat SampleFormat
|
||||
#endif
|
||||
|
||||
G_GNUC_CONST
|
||||
static enum sample_format
|
||||
ffmpeg_sample_format(G_GNUC_UNUSED const AVCodecContext *codec_context)
|
||||
ffmpeg_sample_format(enum AVSampleFormat sample_fmt)
|
||||
{
|
||||
switch (codec_context->sample_fmt) {
|
||||
switch (sample_fmt) {
|
||||
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(52, 94, 1)
|
||||
case AV_SAMPLE_FMT_S16:
|
||||
#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(51,17,0)
|
||||
case AV_SAMPLE_FMT_S16P:
|
||||
#endif
|
||||
#else
|
||||
case SAMPLE_FMT_S16:
|
||||
#endif
|
||||
@@ -365,16 +387,30 @@ ffmpeg_sample_format(G_GNUC_UNUSED const AVCodecContext *codec_context)
|
||||
|
||||
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(52, 94, 1)
|
||||
case AV_SAMPLE_FMT_S32:
|
||||
#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(51,17,0)
|
||||
case AV_SAMPLE_FMT_S32P:
|
||||
#endif
|
||||
#else
|
||||
case SAMPLE_FMT_S32:
|
||||
#endif
|
||||
return SAMPLE_FORMAT_S32;
|
||||
|
||||
default:
|
||||
g_warning("Unsupported libavcodec SampleFormat value: %d",
|
||||
codec_context->sample_fmt);
|
||||
return SAMPLE_FORMAT_UNDEFINED;
|
||||
break;
|
||||
}
|
||||
|
||||
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(52, 94, 1)
|
||||
char buffer[64];
|
||||
const char *name = av_get_sample_fmt_string(buffer, sizeof(buffer),
|
||||
sample_fmt);
|
||||
if (name != NULL)
|
||||
g_warning("Unsupported libavcodec SampleFormat value: %s (%d)",
|
||||
name, sample_fmt);
|
||||
else
|
||||
#endif
|
||||
g_warning("Unsupported libavcodec SampleFormat value: %d",
|
||||
sample_fmt);
|
||||
return SAMPLE_FORMAT_UNDEFINED;
|
||||
}
|
||||
|
||||
static AVInputFormat *
|
||||
@@ -485,11 +521,16 @@ ffmpeg_decode(struct decoder *decoder, struct input_stream *input)
|
||||
return;
|
||||
}
|
||||
|
||||
const enum sample_format sample_format =
|
||||
ffmpeg_sample_format(codec_context->sample_fmt);
|
||||
if (sample_format == SAMPLE_FORMAT_UNDEFINED)
|
||||
return;
|
||||
|
||||
GError *error = NULL;
|
||||
struct audio_format audio_format;
|
||||
if (!audio_format_init_checked(&audio_format,
|
||||
codec_context->sample_rate,
|
||||
ffmpeg_sample_format(codec_context),
|
||||
sample_format,
|
||||
codec_context->channels, &error)) {
|
||||
g_warning("%s", error->message);
|
||||
g_error_free(error);
|
||||
|
||||
@@ -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[] = {
|
||||
|
||||
@@ -365,11 +365,10 @@ 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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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. */
|
||||
|
||||
@@ -65,13 +65,11 @@ static bool
|
||||
vorbis_encoder_configure(struct vorbis_encoder *encoder,
|
||||
const struct config_param *param, GError **error)
|
||||
{
|
||||
const char *value;
|
||||
char *endptr;
|
||||
|
||||
value = config_get_block_string(param, "quality", NULL);
|
||||
const char *value = config_get_block_string(param, "quality", NULL);
|
||||
if (value != NULL) {
|
||||
/* a quality was configured (VBR) */
|
||||
|
||||
char *endptr;
|
||||
encoder->quality = g_ascii_strtod(value, &endptr);
|
||||
|
||||
if (*endptr != '\0' || encoder->quality < -1.0 ||
|
||||
@@ -103,8 +101,9 @@ vorbis_encoder_configure(struct vorbis_encoder *encoder,
|
||||
}
|
||||
|
||||
encoder->quality = -2.0;
|
||||
encoder->bitrate = g_ascii_strtoll(value, &endptr, 10);
|
||||
|
||||
char *endptr;
|
||||
encoder->bitrate = g_ascii_strtoll(value, &endptr, 10);
|
||||
if (*endptr != '\0' || encoder->bitrate <= 0) {
|
||||
g_set_error(error, vorbis_encoder_quark(), 0,
|
||||
"bitrate at line %i should be a positive integer",
|
||||
@@ -119,9 +118,7 @@ vorbis_encoder_configure(struct vorbis_encoder *encoder,
|
||||
static struct encoder *
|
||||
vorbis_encoder_init(const struct config_param *param, GError **error)
|
||||
{
|
||||
struct vorbis_encoder *encoder;
|
||||
|
||||
encoder = g_new(struct vorbis_encoder, 1);
|
||||
struct vorbis_encoder *encoder = g_new(struct vorbis_encoder, 1);
|
||||
encoder_struct_init(&encoder->encoder, &vorbis_encoder_plugin);
|
||||
|
||||
/* load configuration from "param" */
|
||||
@@ -211,14 +208,12 @@ vorbis_encoder_open(struct encoder *_encoder,
|
||||
GError **error)
|
||||
{
|
||||
struct vorbis_encoder *encoder = (struct vorbis_encoder *)_encoder;
|
||||
bool ret;
|
||||
|
||||
audio_format->format = SAMPLE_FORMAT_S16;
|
||||
|
||||
encoder->audio_format = *audio_format;
|
||||
|
||||
ret = vorbis_encoder_reinit(encoder, error);
|
||||
if (!ret)
|
||||
if (!vorbis_encoder_reinit(encoder, error))
|
||||
return false;
|
||||
|
||||
vorbis_encoder_send_header(encoder);
|
||||
@@ -251,11 +246,10 @@ static void
|
||||
vorbis_encoder_blockout(struct vorbis_encoder *encoder)
|
||||
{
|
||||
while (vorbis_analysis_blockout(&encoder->vd, &encoder->vb) == 1) {
|
||||
ogg_packet packet;
|
||||
|
||||
vorbis_analysis(&encoder->vb, NULL);
|
||||
vorbis_bitrate_addblock(&encoder->vb);
|
||||
|
||||
ogg_packet packet;
|
||||
while (vorbis_bitrate_flushpacket(&encoder->vd, &packet))
|
||||
ogg_stream_packetin(&encoder->os, &packet);
|
||||
}
|
||||
@@ -344,9 +338,9 @@ vorbis_encoder_write(struct encoder *_encoder,
|
||||
G_GNUC_UNUSED GError **error)
|
||||
{
|
||||
struct vorbis_encoder *encoder = (struct vorbis_encoder *)_encoder;
|
||||
unsigned num_frames;
|
||||
|
||||
num_frames = length / audio_format_frame_size(&encoder->audio_format);
|
||||
unsigned num_frames = length
|
||||
/ audio_format_frame_size(&encoder->audio_format);
|
||||
|
||||
/* this is for only 16-bit audio */
|
||||
|
||||
@@ -364,12 +358,10 @@ static size_t
|
||||
vorbis_encoder_read(struct encoder *_encoder, void *_dest, size_t length)
|
||||
{
|
||||
struct vorbis_encoder *encoder = (struct vorbis_encoder *)_encoder;
|
||||
ogg_page page;
|
||||
int ret;
|
||||
unsigned char *dest = _dest;
|
||||
size_t nbytes;
|
||||
|
||||
ret = ogg_stream_pageout(&encoder->os, &page);
|
||||
ogg_page page;
|
||||
int ret = ogg_stream_pageout(&encoder->os, &page);
|
||||
if (ret == 0 && encoder->flush) {
|
||||
encoder->flush = false;
|
||||
ret = ogg_stream_flush(&encoder->os, &page);
|
||||
@@ -381,7 +373,7 @@ vorbis_encoder_read(struct encoder *_encoder, void *_dest, size_t length)
|
||||
|
||||
assert(page.header_len > 0 || page.body_len > 0);
|
||||
|
||||
nbytes = (size_t)page.header_len + (size_t)page.body_len;
|
||||
size_t nbytes = (size_t)page.header_len + (size_t)page.body_len;
|
||||
|
||||
if (nbytes > length)
|
||||
/* XXX better error handling */
|
||||
|
||||
@@ -119,6 +119,10 @@ encoder_finish(struct encoder *encoder)
|
||||
* Before you free it, you must call encoder_close(). You may open
|
||||
* and close (reuse) one encoder any number of times.
|
||||
*
|
||||
* After this function returns successfully and before the first
|
||||
* encoder_write() call, you should invoke encoder_read() to obtain
|
||||
* the file header.
|
||||
*
|
||||
* @param encoder the encoder
|
||||
* @param audio_format the encoder's input audio format; the plugin
|
||||
* may modify the struct to adapt it to its abilities
|
||||
@@ -291,6 +295,8 @@ encoder_write(struct encoder *encoder, const void *data, size_t length,
|
||||
/**
|
||||
* Reads encoded data from the encoder.
|
||||
*
|
||||
* Call this repeatedly until no more data is returned.
|
||||
*
|
||||
* @param encoder the encoder
|
||||
* @param dest the destination buffer to copy to
|
||||
* @param length the maximum length of the destination buffer
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
56
src/log.c
56
src/log.c
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
95
src/mapper.c
95
src/mapper.c
@@ -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;
|
||||
|
||||
15
src/mapper.h
15
src/mapper.h
@@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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);
|
||||
@@ -89,10 +114,6 @@ httpd_output_init(const struct config_param *param,
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const char *encoder_name, *bind_to_address;
|
||||
const struct encoder_plugin *encoder_plugin;
|
||||
guint port;
|
||||
|
||||
/* read configuration */
|
||||
httpd->name =
|
||||
config_get_block_string(param, "name", "Set name in config");
|
||||
@@ -101,10 +122,12 @@ httpd_output_init(const struct config_param *param,
|
||||
httpd->website =
|
||||
config_get_block_string(param, "website", "Set website in config");
|
||||
|
||||
port = config_get_block_unsigned(param, "port", 8000);
|
||||
guint port = config_get_block_unsigned(param, "port", 8000);
|
||||
|
||||
encoder_name = config_get_block_string(param, "encoder", "vorbis");
|
||||
encoder_plugin = encoder_plugin_get(encoder_name);
|
||||
const char *encoder_name =
|
||||
config_get_block_string(param, "encoder", "vorbis");
|
||||
const struct encoder_plugin *encoder_plugin =
|
||||
encoder_plugin_get(encoder_name);
|
||||
if (encoder_plugin == NULL) {
|
||||
g_set_error(error, httpd_output_quark(), 0,
|
||||
"No such encoder: %s", encoder_name);
|
||||
@@ -119,7 +142,7 @@ httpd_output_init(const struct config_param *param,
|
||||
|
||||
httpd->server_socket = server_socket_new(httpd_listen_in_event, httpd);
|
||||
|
||||
bind_to_address =
|
||||
const char *bind_to_address =
|
||||
config_get_block_string(param, "bind_to_address", NULL);
|
||||
bool success = bind_to_address != NULL &&
|
||||
strcmp(bind_to_address, "any") != 0
|
||||
@@ -250,8 +273,6 @@ httpd_listen_in_event(int fd, const struct sockaddr *address,
|
||||
static struct page *
|
||||
httpd_output_read_page(struct httpd_output *httpd)
|
||||
{
|
||||
size_t size = 0, nbytes;
|
||||
|
||||
if (httpd->unflushed_input >= 65536) {
|
||||
/* we have fed a lot of input into the encoder, but it
|
||||
didn't give anything back yet - flush now to avoid
|
||||
@@ -260,9 +281,11 @@ httpd_output_read_page(struct httpd_output *httpd)
|
||||
httpd->unflushed_input = 0;
|
||||
}
|
||||
|
||||
size_t size = 0;
|
||||
do {
|
||||
nbytes = encoder_read(httpd->encoder, httpd->buffer + size,
|
||||
sizeof(httpd->buffer) - size);
|
||||
size_t nbytes = encoder_read(httpd->encoder,
|
||||
httpd->buffer + size,
|
||||
sizeof(httpd->buffer) - size);
|
||||
if (nbytes == 0)
|
||||
break;
|
||||
|
||||
@@ -282,10 +305,7 @@ httpd_output_encoder_open(struct httpd_output *httpd,
|
||||
struct audio_format *audio_format,
|
||||
GError **error)
|
||||
{
|
||||
bool success;
|
||||
|
||||
success = encoder_open(httpd->encoder, audio_format, error);
|
||||
if (!success)
|
||||
if (!encoder_open(httpd->encoder, audio_format, error))
|
||||
return false;
|
||||
|
||||
/* we have to remember the encoder header, i.e. the first
|
||||
@@ -319,14 +339,12 @@ httpd_output_open(struct audio_output *ao, struct audio_format *audio_format,
|
||||
GError **error)
|
||||
{
|
||||
struct httpd_output *httpd = (struct httpd_output *)ao;
|
||||
bool success;
|
||||
|
||||
g_mutex_lock(httpd->mutex);
|
||||
|
||||
/* open the encoder */
|
||||
|
||||
success = httpd_output_encoder_open(httpd, audio_format, error);
|
||||
if (!success) {
|
||||
if (!httpd_output_encoder_open(httpd, audio_format, error)) {
|
||||
g_mutex_unlock(httpd->mutex);
|
||||
return false;
|
||||
}
|
||||
@@ -397,6 +415,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;
|
||||
@@ -457,10 +488,7 @@ static bool
|
||||
httpd_output_encode_and_play(struct httpd_output *httpd,
|
||||
const void *chunk, size_t size, GError **error)
|
||||
{
|
||||
bool success;
|
||||
|
||||
success = encoder_write(httpd->encoder, chunk, size, error);
|
||||
if (!success)
|
||||
if (!encoder_write(httpd->encoder, chunk, size, error))
|
||||
return false;
|
||||
|
||||
httpd->unflushed_input += size;
|
||||
@@ -472,21 +500,12 @@ httpd_output_encode_and_play(struct httpd_output *httpd,
|
||||
|
||||
static size_t
|
||||
httpd_output_play(struct audio_output *ao, const void *chunk, size_t size,
|
||||
GError **error)
|
||||
GError **error_r)
|
||||
{
|
||||
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) {
|
||||
bool success;
|
||||
|
||||
success = httpd_output_encode_and_play(httpd, chunk, size,
|
||||
error);
|
||||
if (!success)
|
||||
if (httpd_output_lock_has_clients(httpd)) {
|
||||
if (!httpd_output_encode_and_play(httpd, chunk, size, error_r))
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -502,16 +521,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;
|
||||
}
|
||||
}
|
||||
@@ -534,7 +548,6 @@ httpd_output_tag(struct audio_output *ao, const struct tag *tag)
|
||||
|
||||
if (httpd->encoder->plugin->tag != NULL) {
|
||||
/* embed encoder tags */
|
||||
struct page *page;
|
||||
|
||||
/* flush the current stream, and end it */
|
||||
|
||||
@@ -550,7 +563,7 @@ httpd_output_tag(struct audio_output *ao, const struct tag *tag)
|
||||
used as the new "header" page, which is sent to all
|
||||
new clients */
|
||||
|
||||
page = httpd_output_read_page(httpd);
|
||||
struct page *page = httpd_output_read_page(httpd);
|
||||
if (page != NULL) {
|
||||
if (httpd->header != NULL)
|
||||
page_unref(httpd->header);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -228,9 +228,13 @@ osx_render(void *vdata,
|
||||
g_cond_signal(od->condition);
|
||||
g_mutex_unlock(od->mutex);
|
||||
|
||||
if (nbytes < buffer_size)
|
||||
memset((unsigned char*)buffer->mData + nbytes, 0,
|
||||
buffer_size - nbytes);
|
||||
buffer->mDataByteSize = nbytes;
|
||||
|
||||
unsigned i;
|
||||
for (i = 1; i < buffer_list->mNumberBuffers; ++i) {
|
||||
buffer = &buffer_list->mBuffers[i];
|
||||
buffer->mDataByteSize = 0;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
while (true) {
|
||||
switch (pa_stream_get_state(po->stream)) {
|
||||
case PA_STREAM_READY:
|
||||
return true;
|
||||
|
||||
switch (state) {
|
||||
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(),
|
||||
pa_context_errno(po->context),
|
||||
"failed to connect the stream: %s",
|
||||
pa_strerror(pa_context_errno(po->context)));
|
||||
return false;
|
||||
|
||||
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;
|
||||
case PA_STREAM_CREATING:
|
||||
pa_threaded_mainloop_wait(po->mainloop);
|
||||
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,
|
||||
"failed to connect the stream: %s",
|
||||
pa_strerror(pa_context_errno(po->context)));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -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,
|
||||
|
||||
@@ -77,13 +77,12 @@ recorder_output_init(const struct config_param *param, GError **error_r)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const char *encoder_name;
|
||||
const struct encoder_plugin *encoder_plugin;
|
||||
|
||||
/* read configuration */
|
||||
|
||||
encoder_name = config_get_block_string(param, "encoder", "vorbis");
|
||||
encoder_plugin = encoder_plugin_get(encoder_name);
|
||||
const char *encoder_name =
|
||||
config_get_block_string(param, "encoder", "vorbis");
|
||||
const struct encoder_plugin *encoder_plugin =
|
||||
encoder_plugin_get(encoder_name);
|
||||
if (encoder_plugin == NULL) {
|
||||
g_set_error(error_r, recorder_output_quark(), 0,
|
||||
"No such encoder: %s", encoder_name);
|
||||
@@ -121,33 +120,22 @@ recorder_output_finish(struct audio_output *ao)
|
||||
g_free(recorder);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes pending data from the encoder to the output file.
|
||||
*/
|
||||
static bool
|
||||
recorder_output_encoder_to_file(struct recorder_output *recorder,
|
||||
GError **error_r)
|
||||
recorder_write_to_file(struct recorder_output *recorder,
|
||||
const void *_data, size_t length,
|
||||
GError **error_r)
|
||||
{
|
||||
size_t size = 0, position, nbytes;
|
||||
assert(length > 0);
|
||||
|
||||
assert(recorder->fd >= 0);
|
||||
const int fd = recorder->fd;
|
||||
|
||||
/* read from the encoder */
|
||||
const uint8_t *data = (const uint8_t *)_data, *end = data + length;
|
||||
|
||||
size = encoder_read(recorder->encoder, recorder->buffer,
|
||||
sizeof(recorder->buffer));
|
||||
if (size == 0)
|
||||
return true;
|
||||
|
||||
/* write everything into the file */
|
||||
|
||||
position = 0;
|
||||
while (true) {
|
||||
nbytes = write(recorder->fd, recorder->buffer + position,
|
||||
size - position);
|
||||
ssize_t nbytes = write(fd, data, end - data);
|
||||
if (nbytes > 0) {
|
||||
position += (size_t)nbytes;
|
||||
if (position >= size)
|
||||
data += nbytes;
|
||||
if (data == end)
|
||||
return true;
|
||||
} else if (nbytes == 0) {
|
||||
/* shouldn't happen for files */
|
||||
@@ -163,13 +151,37 @@ recorder_output_encoder_to_file(struct recorder_output *recorder,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes pending data from the encoder to the output file.
|
||||
*/
|
||||
static bool
|
||||
recorder_output_encoder_to_file(struct recorder_output *recorder,
|
||||
GError **error_r)
|
||||
{
|
||||
assert(recorder->fd >= 0);
|
||||
|
||||
while (true) {
|
||||
/* read from the encoder */
|
||||
|
||||
size_t size = encoder_read(recorder->encoder, recorder->buffer,
|
||||
sizeof(recorder->buffer));
|
||||
if (size == 0)
|
||||
return true;
|
||||
|
||||
/* write everything into the file */
|
||||
|
||||
if (!recorder_write_to_file(recorder, recorder->buffer, size,
|
||||
error_r))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static bool
|
||||
recorder_output_open(struct audio_output *ao,
|
||||
struct audio_format *audio_format,
|
||||
GError **error_r)
|
||||
{
|
||||
struct recorder_output *recorder = (struct recorder_output *)ao;
|
||||
bool success;
|
||||
|
||||
/* create the output file */
|
||||
|
||||
@@ -185,8 +197,14 @@ recorder_output_open(struct audio_output *ao,
|
||||
|
||||
/* open the encoder */
|
||||
|
||||
success = encoder_open(recorder->encoder, audio_format, error_r);
|
||||
if (!success) {
|
||||
if (!encoder_open(recorder->encoder, audio_format, error_r)) {
|
||||
close(recorder->fd);
|
||||
unlink(recorder->path);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!recorder_output_encoder_to_file(recorder, error_r)) {
|
||||
encoder_close(recorder->encoder);
|
||||
close(recorder->fd);
|
||||
unlink(recorder->path);
|
||||
return false;
|
||||
|
||||
@@ -36,11 +36,6 @@
|
||||
|
||||
#define DEFAULT_CONN_TIMEOUT 2
|
||||
|
||||
struct shout_buffer {
|
||||
unsigned char data[32768];
|
||||
size_t len;
|
||||
};
|
||||
|
||||
struct shout_data {
|
||||
struct audio_output base;
|
||||
|
||||
@@ -54,7 +49,7 @@ struct shout_data {
|
||||
|
||||
int timeout;
|
||||
|
||||
struct shout_buffer buf;
|
||||
uint8_t buffer[32768];
|
||||
};
|
||||
|
||||
static int shout_init_count;
|
||||
@@ -114,24 +109,7 @@ static struct audio_output *
|
||||
my_shout_init_driver(const struct config_param *param,
|
||||
GError **error)
|
||||
{
|
||||
struct shout_data *sd;
|
||||
char *test;
|
||||
unsigned port;
|
||||
char *host;
|
||||
char *mount;
|
||||
char *passwd;
|
||||
const char *encoding;
|
||||
const struct encoder_plugin *encoder_plugin;
|
||||
unsigned shout_format;
|
||||
unsigned protocol;
|
||||
const char *user;
|
||||
char *name;
|
||||
const char *value;
|
||||
const struct block_param *block_param;
|
||||
int public;
|
||||
|
||||
sd = new_shout_data();
|
||||
|
||||
struct shout_data *sd = new_shout_data();
|
||||
if (!ao_base_init(&sd->base, &shout_output_plugin, param, error)) {
|
||||
free_shout_data(sd);
|
||||
return NULL;
|
||||
@@ -152,13 +130,14 @@ my_shout_init_driver(const struct config_param *param,
|
||||
|
||||
shout_init_count++;
|
||||
|
||||
const struct block_param *block_param;
|
||||
check_block_param("host");
|
||||
host = block_param->value;
|
||||
char *host = block_param->value;
|
||||
|
||||
check_block_param("mount");
|
||||
mount = block_param->value;
|
||||
char *mount = block_param->value;
|
||||
|
||||
port = config_get_block_unsigned(param, "port", 0);
|
||||
unsigned port = config_get_block_unsigned(param, "port", 0);
|
||||
if (port == 0) {
|
||||
g_set_error(error, shout_output_quark(), 0,
|
||||
"shout port must be configured");
|
||||
@@ -166,17 +145,18 @@ my_shout_init_driver(const struct config_param *param,
|
||||
}
|
||||
|
||||
check_block_param("password");
|
||||
passwd = block_param->value;
|
||||
const char *passwd = block_param->value;
|
||||
|
||||
check_block_param("name");
|
||||
name = block_param->value;
|
||||
const char *name = block_param->value;
|
||||
|
||||
public = config_get_block_bool(param, "public", false);
|
||||
bool public = config_get_block_bool(param, "public", false);
|
||||
|
||||
user = config_get_block_string(param, "user", "source");
|
||||
const char *user = config_get_block_string(param, "user", "source");
|
||||
|
||||
value = config_get_block_string(param, "quality", NULL);
|
||||
const char *value = config_get_block_string(param, "quality", NULL);
|
||||
if (value != NULL) {
|
||||
char *test;
|
||||
sd->quality = strtod(value, &test);
|
||||
|
||||
if (*test != '\0' || sd->quality < -1.0 || sd->quality > 10.0) {
|
||||
@@ -201,6 +181,7 @@ my_shout_init_driver(const struct config_param *param,
|
||||
goto failure;
|
||||
}
|
||||
|
||||
char *test;
|
||||
sd->bitrate = strtol(value, &test, 10);
|
||||
|
||||
if (*test != '\0' || sd->bitrate <= 0) {
|
||||
@@ -210,8 +191,10 @@ my_shout_init_driver(const struct config_param *param,
|
||||
}
|
||||
}
|
||||
|
||||
encoding = config_get_block_string(param, "encoding", "ogg");
|
||||
encoder_plugin = shout_encoder_plugin_get(encoding);
|
||||
const char *encoding = config_get_block_string(param, "encoding",
|
||||
"ogg");
|
||||
const struct encoder_plugin *encoder_plugin =
|
||||
shout_encoder_plugin_get(encoding);
|
||||
if (encoder_plugin == NULL) {
|
||||
g_set_error(error, shout_output_quark(), 0,
|
||||
"couldn't find shout encoder plugin \"%s\"",
|
||||
@@ -223,11 +206,13 @@ my_shout_init_driver(const struct config_param *param,
|
||||
if (sd->encoder == NULL)
|
||||
goto failure;
|
||||
|
||||
unsigned shout_format;
|
||||
if (strcmp(encoding, "mp3") == 0 || strcmp(encoding, "lame") == 0)
|
||||
shout_format = SHOUT_FORMAT_MP3;
|
||||
else
|
||||
shout_format = SHOUT_FORMAT_OGG;
|
||||
|
||||
unsigned protocol;
|
||||
value = config_get_block_string(param, "protocol", NULL);
|
||||
if (value != NULL) {
|
||||
if (0 == strcmp(value, "shoutcast") &&
|
||||
@@ -355,26 +340,24 @@ handle_shout_error(struct shout_data *sd, int err, GError **error)
|
||||
static bool
|
||||
write_page(struct shout_data *sd, GError **error)
|
||||
{
|
||||
int err;
|
||||
|
||||
assert(sd->encoder != NULL);
|
||||
|
||||
sd->buf.len = encoder_read(sd->encoder,
|
||||
sd->buf.data, sizeof(sd->buf.data));
|
||||
if (sd->buf.len == 0)
|
||||
return true;
|
||||
while (true) {
|
||||
size_t nbytes = encoder_read(sd->encoder,
|
||||
sd->buffer, sizeof(sd->buffer));
|
||||
if (nbytes == 0)
|
||||
return true;
|
||||
|
||||
err = shout_send(sd->shout_conn, sd->buf.data, sd->buf.len);
|
||||
if (!handle_shout_error(sd, err, error))
|
||||
return false;
|
||||
int err = shout_send(sd->shout_conn, sd->buffer, nbytes);
|
||||
if (!handle_shout_error(sd, err, error))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void close_shout_conn(struct shout_data * sd)
|
||||
{
|
||||
sd->buf.len = 0;
|
||||
|
||||
if (sd->encoder != NULL) {
|
||||
if (encoder_end(sd->encoder, NULL))
|
||||
write_page(sd, NULL);
|
||||
@@ -425,10 +408,7 @@ my_shout_close_device(struct audio_output *ao)
|
||||
static bool
|
||||
shout_connect(struct shout_data *sd, GError **error)
|
||||
{
|
||||
int state;
|
||||
|
||||
state = shout_open(sd->shout_conn);
|
||||
switch (state) {
|
||||
switch (shout_open(sd->shout_conn)) {
|
||||
case SHOUTERR_SUCCESS:
|
||||
case SHOUTERR_CONNECTED:
|
||||
return true;
|
||||
@@ -448,17 +428,17 @@ my_shout_open_device(struct audio_output *ao, struct audio_format *audio_format,
|
||||
GError **error)
|
||||
{
|
||||
struct shout_data *sd = (struct shout_data *)ao;
|
||||
bool ret;
|
||||
|
||||
ret = shout_connect(sd, error);
|
||||
if (!ret)
|
||||
if (!shout_connect(sd, error))
|
||||
return false;
|
||||
|
||||
sd->buf.len = 0;
|
||||
if (!encoder_open(sd->encoder, audio_format, error)) {
|
||||
shout_close(sd->shout_conn);
|
||||
return false;
|
||||
}
|
||||
|
||||
ret = encoder_open(sd->encoder, audio_format, error) &&
|
||||
write_page(sd, error);
|
||||
if (!ret) {
|
||||
if (!write_page(sd, error)) {
|
||||
encoder_close(sd->encoder);
|
||||
shout_close(sd->shout_conn);
|
||||
return false;
|
||||
}
|
||||
@@ -528,32 +508,27 @@ static void my_shout_set_tag(struct audio_output *ao,
|
||||
const struct tag *tag)
|
||||
{
|
||||
struct shout_data *sd = (struct shout_data *)ao;
|
||||
bool ret;
|
||||
GError *error = NULL;
|
||||
|
||||
if (sd->encoder->plugin->tag != NULL) {
|
||||
/* encoder plugin supports stream tags */
|
||||
|
||||
ret = encoder_pre_tag(sd->encoder, &error);
|
||||
if (!ret) {
|
||||
if (!encoder_pre_tag(sd->encoder, &error)) {
|
||||
g_warning("%s", error->message);
|
||||
g_error_free(error);
|
||||
return;
|
||||
}
|
||||
|
||||
ret = write_page(sd, NULL);
|
||||
if (!ret)
|
||||
if (!write_page(sd, NULL))
|
||||
return;
|
||||
|
||||
ret = encoder_tag(sd->encoder, tag, &error);
|
||||
if (!ret) {
|
||||
if (!encoder_tag(sd->encoder, tag, &error)) {
|
||||
g_warning("%s", error->message);
|
||||
g_error_free(error);
|
||||
}
|
||||
} else {
|
||||
/* no stream tag support: fall back to icy-metadata */
|
||||
char song[1024];
|
||||
|
||||
shout_tag_to_metadata(tag, song, sizeof(song));
|
||||
|
||||
shout_metadata_add(sd->shout_meta, "song", song);
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -137,7 +137,8 @@ playlist_load_spl(struct playlist *playlist, struct player_control *pc,
|
||||
*p = '/';
|
||||
p++;
|
||||
}
|
||||
if ((playlist_append_uri(playlist, pc, temp, NULL)) != PLAYLIST_RESULT_SUCCESS) {
|
||||
if ((playlist_append_uri(playlist, pc, temp2,
|
||||
NULL)) != PLAYLIST_RESULT_SUCCESS) {
|
||||
g_warning("can't add file \"%s\"", temp2);
|
||||
}
|
||||
g_free(temp2);
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
#include "mapper.h"
|
||||
#include "song.h"
|
||||
#include "uri.h"
|
||||
#include "path.h"
|
||||
#include "ls.h"
|
||||
#include "tag.h"
|
||||
|
||||
@@ -62,14 +63,19 @@ apply_song_metadata(struct song *dest, const struct song *src)
|
||||
if (path_fs == NULL)
|
||||
return dest;
|
||||
|
||||
tmp = song_file_new(path_fs, NULL);
|
||||
g_free(path_fs);
|
||||
char *path_utf8 = fs_charset_to_utf8(path_fs);
|
||||
if (path_utf8 != NULL)
|
||||
g_free(path_fs);
|
||||
else
|
||||
path_utf8 = path_fs;
|
||||
|
||||
tmp = song_file_new(path_utf8, NULL);
|
||||
g_free(path_utf8);
|
||||
|
||||
merge_song_metadata(tmp, dest, 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 +86,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 +143,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 +158,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) {
|
||||
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);
|
||||
struct song *dest = playlist_check_load_song(song, uri, secure);
|
||||
song_free(song);
|
||||
|
||||
g_free(allocated);
|
||||
return dest;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
17
src/timer.c
17
src/timer.c
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -161,7 +161,7 @@ int main(int argc, char **argv)
|
||||
config_global_init();
|
||||
success = config_read_file(argv[1], &error);
|
||||
if (!success) {
|
||||
g_printerr("%s:", error->message);
|
||||
g_printerr("%s\n", error->message);
|
||||
g_error_free(error);
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -99,14 +99,15 @@ int main(int argc, char **argv)
|
||||
}
|
||||
}
|
||||
|
||||
ret = encoder_open(encoder, &audio_format, &error);
|
||||
if (encoder == NULL) {
|
||||
if (!encoder_open(encoder, &audio_format, &error)) {
|
||||
g_printerr("Failed to open encoder: %s\n",
|
||||
error->message);
|
||||
g_error_free(error);
|
||||
return 1;
|
||||
}
|
||||
|
||||
encoder_to_stdout(encoder);
|
||||
|
||||
/* do it */
|
||||
|
||||
while ((nbytes = read(0, buffer, sizeof(buffer))) > 0) {
|
||||
|
||||
@@ -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));
|
||||
|
||||
|
||||
@@ -67,6 +67,8 @@ main(G_GNUC_UNUSED int argc, G_GNUC_UNUSED char **argv)
|
||||
success = encoder_open(encoder, &audio_format, NULL);
|
||||
assert(success);
|
||||
|
||||
encoder_to_stdout(encoder);
|
||||
|
||||
/* write a block of data */
|
||||
|
||||
success = encoder_write(encoder, zero, sizeof(zero), NULL);
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user