Compare commits
127 Commits
v0.16_alph
...
v0.16.1
Author | SHA1 | Date | |
---|---|---|---|
![]() |
9ad862d3a6 | ||
![]() |
77d71c4ee6 | ||
![]() |
8c0afd8557 | ||
![]() |
2a56300f7b | ||
![]() |
5f06999686 | ||
![]() |
4c09aeb5a1 | ||
![]() |
af892e7e80 | ||
![]() |
0022fb100b | ||
![]() |
4f2d67dfb0 | ||
![]() |
b5645ab29f | ||
![]() |
3149d1abf9 | ||
![]() |
59a417fc84 | ||
![]() |
b75d53413d | ||
![]() |
c44a744c0b | ||
![]() |
76cddfab90 | ||
![]() |
60b4f6b3eb | ||
![]() |
546232b1c0 | ||
![]() |
42c5788de3 | ||
![]() |
fb00e7fddc | ||
![]() |
41fdcf328c | ||
![]() |
144ad7992e | ||
![]() |
a0dd1a1b8b | ||
![]() |
c360e69162 | ||
![]() |
da01c6ef5b | ||
![]() |
fcd2355f4f | ||
![]() |
748a8a6f42 | ||
![]() |
cb9965bab5 | ||
![]() |
429ed24c99 | ||
![]() |
1ab46472ab | ||
![]() |
f6bbe1332f | ||
![]() |
11613347be | ||
![]() |
8f46f1520c | ||
![]() |
f2893b0d0f | ||
![]() |
c7265f9689 | ||
![]() |
46ab8d18e2 | ||
![]() |
f384f8da93 | ||
![]() |
23cd8a74be | ||
![]() |
cc1debc948 | ||
![]() |
5a3aa1262a | ||
![]() |
ad52eb236d | ||
![]() |
d2c2cbd0ae | ||
![]() |
af4a93dbcf | ||
![]() |
4478b3ef74 | ||
![]() |
462bba8e2f | ||
![]() |
dec7090198 | ||
![]() |
83ec0e5552 | ||
![]() |
cc261872c2 | ||
![]() |
5223261f19 | ||
![]() |
c594afeee7 | ||
![]() |
32d10eedbd | ||
![]() |
dfd98eede7 | ||
![]() |
a728d7a026 | ||
![]() |
5a26320680 | ||
![]() |
90dc880e67 | ||
![]() |
e11ff967d0 | ||
![]() |
2dc6ed7b3a | ||
![]() |
ad430c6617 | ||
![]() |
e8d8bd4c0d | ||
![]() |
8d5fa754e8 | ||
![]() |
2ee047a1dd | ||
![]() |
9562f66741 | ||
![]() |
21223154aa | ||
![]() |
ec48b5ea3a | ||
![]() |
754015544f | ||
![]() |
3f89f77429 | ||
![]() |
9dee419b7c | ||
![]() |
7612bf1bfa | ||
![]() |
ad56e10e5b | ||
![]() |
75f4772ba2 | ||
![]() |
fe1b626f76 | ||
![]() |
4e94516912 | ||
![]() |
dadb6747ad | ||
![]() |
188e1b440e | ||
![]() |
a57f9e712d | ||
![]() |
a549d871f3 | ||
![]() |
b552e9a120 | ||
![]() |
e6fc88a758 | ||
![]() |
20004b7ee0 | ||
![]() |
84e037631d | ||
![]() |
18e3d0b504 | ||
![]() |
04c4398bfc | ||
![]() |
39e42394bd | ||
![]() |
a39e6b43e8 | ||
![]() |
5923cfcde3 | ||
![]() |
e69df36e4a | ||
![]() |
e10b872fc3 | ||
![]() |
2b78358af5 | ||
![]() |
e3f4c7b91c | ||
![]() |
a59ab3e2ee | ||
![]() |
28bcb8bdf5 | ||
![]() |
9af9fd1400 | ||
![]() |
0c80bd5fc0 | ||
![]() |
7563ece236 | ||
![]() |
a14cd97f56 | ||
![]() |
0955f33a86 | ||
![]() |
a016fb4048 | ||
![]() |
e8ebb1af91 | ||
![]() |
9fa3d7c4fa | ||
![]() |
54294366d5 | ||
![]() |
9423b456a1 | ||
![]() |
64209749fb | ||
![]() |
586b7601c6 | ||
![]() |
4425989898 | ||
![]() |
5b996ab880 | ||
![]() |
635cfbae13 | ||
![]() |
922e51e8a9 | ||
![]() |
6d9f1ad61f | ||
![]() |
841b9b3d63 | ||
![]() |
a3745ae7f3 | ||
![]() |
27d7013ff8 | ||
![]() |
27d3340af2 | ||
![]() |
4a7abc9d44 | ||
![]() |
589bb54111 | ||
![]() |
d953225531 | ||
![]() |
663815ead8 | ||
![]() |
bc87ec0059 | ||
![]() |
917434269c | ||
![]() |
a77506ae21 | ||
![]() |
ed5d297301 | ||
![]() |
64dacd175a | ||
![]() |
625e4755d1 | ||
![]() |
92b6ba9eff | ||
![]() |
68c02fc95a | ||
![]() |
d18c1b1a0a | ||
![]() |
c980fc653d | ||
![]() |
36782a977a | ||
![]() |
676739c426 |
.gitignoreINSTALLMakefile.amNEWSautogen.shconfigure.ac
doc
m4
src
ape.cape.haudio.caudio_check.hcmdline.ccommand.cconf.cconf.hdaemon.cdaemon.h
decoder
ffmpeg_decoder_plugin.cflac_metadata.cgme_decoder_plugin.cmad_decoder_plugin.cmikmod_decoder_plugin.cmp4ff_decoder_plugin.csidplay_decoder_plugin.cxx
decoder_control.cdecoder_list.cdecoder_thread.cdirectory.hencoder
event_pipe.cevent_pipe.hfd_util.cfd_util.hfilter
icy_server.cinotify_source.cinput
listen.clog.cmain.cmain.hmain_win32.cmixer
mixer_list.hmpd_error.hnotify.cnotify.houtput
alsa_plugin.cffado_output_plugin.chttpd_client.chttpd_internal.hhttpd_output_plugin.cosx_plugin.cshout_plugin.csolaris_output_plugin.cwinmm_output_plugin.cwinmm_output_plugin.h
output_all.coutput_control.coutput_internal.houtput_list.coutput_plugin.houtput_thread.cpath.cpcm_mix.cpermission.cpipe.hplayer_thread.cplaylist.cplaylist
playlist_database.cplaylist_list.cplaylist_song.cplaylist_state.cplaylist_vector.cplaylist_vector.hpoison.hreplay_gain_ape.creplay_gain_ape.hreplay_gain_config.cserver_socket.cserver_socket.hsig_handlers.ctag.ctag_ape.ctag_id3.ctimer.ctimer.hupdate.cupdate_walk.czeroconf-avahi.czeroconf-bonjour.c
1
.gitignore
vendored
1
.gitignore
vendored
@@ -33,6 +33,7 @@ ltmain.sh
|
||||
missing
|
||||
mkinstalldirs
|
||||
mpd
|
||||
mpd.exe
|
||||
stamp-h1
|
||||
tags
|
||||
*~
|
||||
|
3
INSTALL
3
INSTALL
@@ -59,6 +59,9 @@ You also need an encoder: either libvorbisenc (ogg), or liblame (mp3).
|
||||
OpenAL - http://kcat.strangesoft.net/openal.html
|
||||
Open Audio Library
|
||||
|
||||
libffado - http://www.ffado.org/
|
||||
For FireWire audio devices.
|
||||
|
||||
|
||||
Optional Input Dependencies
|
||||
---------------------------
|
||||
|
36
Makefile.am
36
Makefile.am
@@ -7,6 +7,8 @@ AM_CPPFLAGS += -DSYSTEM_CONFIG_FILE_LOCATION='"$(sysconfdir)/mpd.conf"'
|
||||
|
||||
bin_PROGRAMS = src/mpd
|
||||
|
||||
noinst_LIBRARIES =
|
||||
|
||||
src_mpd_CFLAGS = $(AM_CFLAGS) $(MPD_CFLAGS)
|
||||
src_mpd_CPPFLAGS = $(AM_CPPFLAGS) \
|
||||
$(LIBWRAP_CFLAGS) \
|
||||
@@ -34,6 +36,7 @@ mpd_headers = \
|
||||
src/check.h \
|
||||
src/notify.h \
|
||||
src/ack.h \
|
||||
src/ape.h \
|
||||
src/audio.h \
|
||||
src/audio_format.h \
|
||||
src/audio_check.h \
|
||||
@@ -111,6 +114,7 @@ mpd_headers = \
|
||||
src/icy_metadata.h \
|
||||
src/client.h \
|
||||
src/client_internal.h \
|
||||
src/server_socket.h \
|
||||
src/listen.h \
|
||||
src/log.h \
|
||||
src/ls.h \
|
||||
@@ -136,6 +140,7 @@ mpd_headers = \
|
||||
src/output/httpd_client.h \
|
||||
src/output/httpd_internal.h \
|
||||
src/output/pulse_output_plugin.h \
|
||||
src/output/winmm_output_plugin.h \
|
||||
src/page.h \
|
||||
src/pcm_buffer.h \
|
||||
src/pcm_utils.h \
|
||||
@@ -171,6 +176,7 @@ mpd_headers = \
|
||||
src/playlist/pls_playlist_plugin.h \
|
||||
src/playlist/xspf_playlist_plugin.h \
|
||||
src/playlist/asx_playlist_plugin.h \
|
||||
src/playlist/rss_playlist_plugin.h \
|
||||
src/playlist/lastfm_playlist_plugin.h \
|
||||
src/playlist/cue_playlist_plugin.h \
|
||||
src/playlist/flac_playlist_plugin.h \
|
||||
@@ -183,6 +189,7 @@ mpd_headers = \
|
||||
src/refcount.h \
|
||||
src/replay_gain_config.h \
|
||||
src/replay_gain_info.h \
|
||||
src/replay_gain_ape.h \
|
||||
src/sig_handlers.h \
|
||||
src/song.h \
|
||||
src/song_print.h \
|
||||
@@ -220,7 +227,8 @@ mpd_headers = \
|
||||
src/archive/iso9660_archive_plugin.h \
|
||||
src/archive/zzip_archive_plugin.h \
|
||||
src/input/archive_input_plugin.h \
|
||||
src/cue/cue_tag.h
|
||||
src/cue/cue_tag.h\
|
||||
src/mpd_error.h
|
||||
|
||||
src_mpd_SOURCES = \
|
||||
$(mpd_headers) \
|
||||
@@ -274,10 +282,12 @@ src_mpd_SOURCES = \
|
||||
src/client_process.c \
|
||||
src/client_read.c \
|
||||
src/client_write.c \
|
||||
src/server_socket.c \
|
||||
src/listen.c \
|
||||
src/log.c \
|
||||
src/ls.c \
|
||||
src/main.c \
|
||||
src/main_win32.c \
|
||||
src/event_pipe.c \
|
||||
src/daemon.c \
|
||||
src/AudioCompress/compress.c \
|
||||
@@ -406,6 +416,8 @@ TAG_LIBS = \
|
||||
$(ID3TAG_LIBS)
|
||||
|
||||
TAG_SRC = \
|
||||
src/ape.c \
|
||||
src/replay_gain_ape.c \
|
||||
src/tag_ape.c
|
||||
|
||||
if HAVE_ID3TAG
|
||||
@@ -422,7 +434,6 @@ DECODER_CFLAGS = \
|
||||
$(SNDFILE_CFLAGS) \
|
||||
$(AUDIOFILE_CFLAGS) \
|
||||
$(LIBMIKMOD_CFLAGS) \
|
||||
$(MODPLUG_CFLAGS) \
|
||||
$(GME_CFLAGS) \
|
||||
$(SIDPLAY_CFLAGS) \
|
||||
$(FLUIDSYNTH_CFLAGS) \
|
||||
@@ -438,7 +449,6 @@ DECODER_LIBS = \
|
||||
$(FLAC_LIBS) \
|
||||
$(SNDFILE_LIBS) \
|
||||
$(AUDIOFILE_LIBS) $(LIBMIKMOD_LIBS) \
|
||||
$(MODPLUG_LIBS) \
|
||||
$(GME_LIBS) \
|
||||
$(SIDPLAY_LIBS) \
|
||||
$(FLUIDSYNTH_LIBS) \
|
||||
@@ -511,7 +521,11 @@ DECODER_SRC += src/decoder/mikmod_decoder_plugin.c
|
||||
endif
|
||||
|
||||
if HAVE_MODPLUG
|
||||
DECODER_SRC += src/decoder/modplug_decoder_plugin.c
|
||||
libmodplug_decoder_plugin_a_SOURCES = src/decoder/modplug_decoder_plugin.c
|
||||
libmodplug_decoder_plugin_a_CFLAGS = $(src_mpd_CFLAGS) $(MODPLUG_CFLAGS)
|
||||
libmodplug_decoder_plugin_a_CPPFLAGS = $(src_mpd_CPPFLAGS)
|
||||
noinst_LIBRARIES += libmodplug_decoder_plugin.a
|
||||
DECODER_LIBS += libmodplug_decoder_plugin.a $(MODPLUG_LIBS)
|
||||
endif
|
||||
|
||||
if ENABLE_SIDPLAY
|
||||
@@ -634,6 +648,7 @@ endif
|
||||
OUTPUT_CFLAGS = \
|
||||
$(AO_CFLAGS) \
|
||||
$(ALSA_CFLAGS) \
|
||||
$(FFADO_CFLAGS) \
|
||||
$(JACK_CFLAGS) \
|
||||
$(OPENAL_CFLAGS) \
|
||||
$(PULSE_CFLAGS) \
|
||||
@@ -643,6 +658,7 @@ OUTPUT_LIBS = \
|
||||
$(LIBWRAP_LDFLAGS) \
|
||||
$(AO_LIBS) \
|
||||
$(ALSA_LIBS) \
|
||||
$(FFADO_LIBS) \
|
||||
$(JACK_LIBS) \
|
||||
$(OPENAL_LIBS) \
|
||||
$(PULSE_LIBS) \
|
||||
@@ -675,6 +691,10 @@ OUTPUT_SRC += src/output/alsa_plugin.c
|
||||
MIXER_SRC += src/mixer/alsa_mixer_plugin.c
|
||||
endif
|
||||
|
||||
if ENABLE_FFADO_OUTPUT
|
||||
OUTPUT_SRC += src/output/ffado_output_plugin.c
|
||||
endif
|
||||
|
||||
if HAVE_AO
|
||||
OUTPUT_SRC += src/output/ao_plugin.c
|
||||
endif
|
||||
@@ -732,8 +752,9 @@ if ENABLE_SOLARIS_OUTPUT
|
||||
OUTPUT_SRC += src/output/solaris_output_plugin.c
|
||||
endif
|
||||
|
||||
if ENABLE_WIN32_OUTPUT
|
||||
OUTPUT_SRC += src/output/win32_output_plugin.c
|
||||
if ENABLE_WINMM_OUTPUT
|
||||
OUTPUT_SRC += src/output/winmm_output_plugin.c
|
||||
MIXER_SRC += src/mixer/winmm_mixer_plugin.c
|
||||
endif
|
||||
|
||||
|
||||
@@ -747,6 +768,7 @@ PLAYLIST_SRC = \
|
||||
src/playlist/pls_playlist_plugin.c \
|
||||
src/playlist/xspf_playlist_plugin.c \
|
||||
src/playlist/asx_playlist_plugin.c \
|
||||
src/playlist/rss_playlist_plugin.c \
|
||||
src/playlist_list.c
|
||||
|
||||
if ENABLE_LASTFM
|
||||
@@ -994,6 +1016,7 @@ if HAVE_LIBSAMPLERATE
|
||||
test_run_convert_SOURCES += src/pcm_resample_libsamplerate.c
|
||||
endif
|
||||
|
||||
test_run_output_CFLAGS = $(AM_CFLAGS) $(MPD_CFLAGS)
|
||||
test_run_output_CPPFLAGS = $(AM_CPPFLAGS) \
|
||||
$(ENCODER_CFLAGS) \
|
||||
$(OUTPUT_CFLAGS)
|
||||
@@ -1029,6 +1052,7 @@ test_run_output_SOURCES = test/run_output.c \
|
||||
src/replay_gain_info.c \
|
||||
src/replay_gain_config.c \
|
||||
src/fd_util.c \
|
||||
src/server_socket.c \
|
||||
$(OUTPUT_SRC)
|
||||
|
||||
test_read_mixer_CPPFLAGS = $(AM_CPPFLAGS) \
|
||||
|
58
NEWS
58
NEWS
@@ -1,4 +1,20 @@
|
||||
ver 0.16 (20??/??/??)
|
||||
ver 0.16.1 (2010/01/09)
|
||||
* audio_check: fix parameter in prototype
|
||||
* add void casts to suppress "result unused" warnings (clang)
|
||||
* input:
|
||||
- ffado: disable by default
|
||||
* decoder:
|
||||
- mad: work around build failure on Solaris
|
||||
- resolve modplug vs. libsndfile cflags/headers conflict
|
||||
* output:
|
||||
- solaris: add missing parameter to open_cloexec() cal
|
||||
- osx: fix up audio format first, then apply it to device
|
||||
* player_thread: discard empty chunks while cross-fading
|
||||
* player_thread: fix assertion failure due to early seek
|
||||
* output_thread: fix double lock
|
||||
|
||||
|
||||
ver 0.16 (2010/12/11)
|
||||
* protocol:
|
||||
- send song modification time to client
|
||||
- added "update" idle event
|
||||
@@ -20,7 +36,9 @@ ver 0.16 (20??/??/??)
|
||||
* tags:
|
||||
- added tags "ArtistSort", "AlbumArtistSort"
|
||||
- id3: revised "performer" tag support
|
||||
- id3: support multiple values
|
||||
- ape: MusicBrainz tags
|
||||
- ape: support multiple values
|
||||
* decoders:
|
||||
- don't try a plugin twice (MIME type & suffix)
|
||||
- don't fall back to "mad" unless no plugin matches
|
||||
@@ -35,6 +53,8 @@ ver 0.16 (20??/??/??)
|
||||
- sidplay: support sub-tunes
|
||||
- sidplay: implemented songlength database
|
||||
- sidplay: support seeking
|
||||
- sidplay: play monaural SID tunes in mono
|
||||
- sidplay: play mus, str, prg, x00 files
|
||||
- wavpack: activate 32 bit support
|
||||
- wavpack: allow more than 2 channels
|
||||
- mp4ff: rename plugin "mp4" to "mp4ff"
|
||||
@@ -59,8 +79,11 @@ ver 0.16 (20??/??/??)
|
||||
- jack: support more than two audio channels
|
||||
- httpd: bind port when output is enabled
|
||||
- httpd: added name/genre/website configuration
|
||||
- httpd: implement "pause"
|
||||
- httpd: bind_to_address support (including IPv6)
|
||||
- oss: 24 bit support via OSS4
|
||||
- win32: new output plugin for Windows Wave
|
||||
- shout, httpd: more responsive to control commands
|
||||
- wildcards allowed in audio_format configuration
|
||||
- consistently lock audio output objects
|
||||
* player:
|
||||
@@ -83,6 +106,7 @@ ver 0.16 (20??/??/??)
|
||||
- fall back to track gain if album gain is unavailable
|
||||
- optionally use hardware mixer to apply replay gain
|
||||
- added mode "auto"
|
||||
- parse replay gain from APE tags
|
||||
* log unused/unknown block parameters
|
||||
* removed the deprecated "error_file" option
|
||||
* save state when stopped
|
||||
@@ -101,6 +125,38 @@ ver 0.16 (20??/??/??)
|
||||
* added test suite ("make check")
|
||||
* require GLib 2.12
|
||||
* added libwrap support
|
||||
* make single mode 'sticky'
|
||||
|
||||
|
||||
ver 0.15.16 (2010/??/??)
|
||||
* encoders:
|
||||
- lame: explicitly configure the output sample rate
|
||||
|
||||
|
||||
ver 0.15.15 (2010/11/08)
|
||||
* input:
|
||||
- rewind: fix assertion failure
|
||||
* output:
|
||||
- shout: artist comes first in stream title
|
||||
|
||||
|
||||
ver 0.15.14 (2010/11/06)
|
||||
* player_thread: fix assertion failure due to wrong music pipe on seek
|
||||
* output_thread: fix assertion failure due to race condition in OPEN
|
||||
* input:
|
||||
- rewind: fix double free bug
|
||||
* decoders:
|
||||
- mp4ff, ffmpeg: add extension ".m4b" (audio book)
|
||||
|
||||
|
||||
ver 0.15.13 (2010/10/10)
|
||||
* output_thread: fix race condition after CANCEL command
|
||||
* output:
|
||||
- httpd: fix random data in stream title
|
||||
- httpd: MIME type audio/ogg for Ogg Vorbis
|
||||
* input:
|
||||
- rewind: update MIME not only once
|
||||
- rewind: enable for MMS
|
||||
|
||||
|
||||
ver 0.15.12 (2010/07/20)
|
||||
|
@@ -16,7 +16,7 @@ if test -n "$AM_FORCE_VERSION"
|
||||
then
|
||||
AM_VERSIONS="$AM_FORCE_VERSION"
|
||||
else
|
||||
AM_VERSIONS='1.10'
|
||||
AM_VERSIONS='1.11 1.10'
|
||||
fi
|
||||
if test -n "$AC_FORCE_VERSION"
|
||||
then
|
||||
|
102
configure.ac
102
configure.ac
@@ -1,5 +1,5 @@
|
||||
AC_PREREQ(2.60)
|
||||
AC_INIT(mpd, 0.16~alpha2, musicpd-dev-team@lists.sourceforge.net)
|
||||
AC_INIT(mpd, 0.16.1, musicpd-dev-team@lists.sourceforge.net)
|
||||
AC_CONFIG_SRCDIR([src/main.c])
|
||||
AM_INIT_AUTOMAKE([foreign 1.10 dist-bzip2 subdir-objects])
|
||||
AM_CONFIG_HEADER(config.h)
|
||||
@@ -13,6 +13,7 @@ dnl Programs
|
||||
dnl ---------------------------------------------------------------------------
|
||||
AC_PROG_CC_C99
|
||||
AC_PROG_CXX
|
||||
AC_PROG_RANLIB
|
||||
|
||||
HAVE_CXX=yes
|
||||
if test x$CXX = xg++; then
|
||||
@@ -155,6 +156,10 @@ AC_ARG_ENABLE(documentation,
|
||||
[build documentation (default: disable)]),,
|
||||
[enable_documentation=no])
|
||||
|
||||
AC_ARG_ENABLE(ffado,
|
||||
AS_HELP_STRING([--enable-ffado], [enable libffado (FireWire) support]),,
|
||||
[enable_ffado=no])
|
||||
|
||||
AC_ARG_ENABLE(ffmpeg,
|
||||
AS_HELP_STRING([--enable-ffmpeg],
|
||||
[enable FFMPEG support]),,
|
||||
@@ -437,7 +442,7 @@ if test x$enable_tcp = xyes; then
|
||||
fi
|
||||
|
||||
case "$host_os" in
|
||||
mingw* | windows*)
|
||||
mingw* | windows* | cygwin*)
|
||||
enable_un=no
|
||||
;;
|
||||
esac
|
||||
@@ -630,7 +635,9 @@ fi
|
||||
AM_CONDITIONAL(ENABLE_LASTFM, test x$enable_lastfm = xyes)
|
||||
|
||||
dnl ---------------------------------- libogg ---------------------------------
|
||||
PKG_CHECK_MODULES(OGG, [ogg], enable_ogg=yes, enable_ogg=no)
|
||||
if test x$with_tremor == xno || test -z $with_tremor; then
|
||||
PKG_CHECK_MODULES(OGG, [ogg], enable_ogg=yes, enable_ogg=no)
|
||||
fi
|
||||
|
||||
dnl ---------------------------------- libmms ---------------------------------
|
||||
MPD_AUTO_PKG(mms, MMS, [libmms >= 0.4],
|
||||
@@ -843,15 +850,6 @@ if test x$enable_modplug = xyes; then
|
||||
fi
|
||||
AM_CONDITIONAL(HAVE_MODPLUG, test x$enable_modplug = xyes)
|
||||
|
||||
dnl --------------------------- sndfile/modplug test --------------------------
|
||||
if test x$enable_sndfile = xauto && test x$enable_modplug = xyes; then
|
||||
dnl If modplug is enabled, enable sndfile only if explicitly
|
||||
dnl requested - modplug's modplug/sndfile.h is known to
|
||||
dnl conflict with libsndfile's sndfile.h.
|
||||
AC_MSG_NOTICE([disabling libsndfile auto-detection, because the modplug decoder is enabled])
|
||||
enable_sndfile=no
|
||||
fi
|
||||
|
||||
dnl -------------------------------- libsndfile -------------------------------
|
||||
dnl See above test, which may disable this.
|
||||
MPD_AUTO_PKG(sndfile, SNDFILE, [sndfile],
|
||||
@@ -914,13 +912,13 @@ AM_CONDITIONAL(HAVE_MPCDEC, test x$enable_mpc = xyes)
|
||||
|
||||
dnl -------------------------------- Ogg Tremor -------------------------------
|
||||
if test x$with_tremor = xyes || test x$with_tremor = xno; then
|
||||
use_tremor="$with_tremor"
|
||||
enable_tremor="$with_tremor"
|
||||
else
|
||||
tremor_prefix="$with_tremor"
|
||||
use_tremor=yes
|
||||
enable_tremor=yes
|
||||
fi
|
||||
|
||||
if test x$use_tremor = xyes; then
|
||||
if test x$enable_tremor = xyes; then
|
||||
if test "x$tremor_libraries" != "x" ; then
|
||||
TREMOR_LIBS="-L$tremor_libraries"
|
||||
elif test "x$tremor_prefix" != "x" ; then
|
||||
@@ -951,7 +949,7 @@ AC_SUBST(TREMOR_LIBS)
|
||||
dnl --------------------------------- OggFLAC ---------------------------------
|
||||
dnl OggFLAC must go after Ogg Tremor
|
||||
|
||||
if test x$use_tremor = xyes && test $xenable_oggflac = xyes; then
|
||||
if test x$enable_tremor = xyes && test x$enable_oggflac = xyes; then
|
||||
AC_MSG_WARN([disabling OggFLAC support because it is incompatible with tremor])
|
||||
enable_oggflac=no
|
||||
fi
|
||||
@@ -968,8 +966,11 @@ fi
|
||||
AM_CONDITIONAL(HAVE_OGGFLAC, test x$enable_oggflac = xyes)
|
||||
|
||||
dnl -------------------------------- Ogg Vorbis -------------------------------
|
||||
if test x$enable_tremor != xno && test x$enable_vorbis = xyes; then
|
||||
if test x$enable_ogg = xyes; then
|
||||
if test x$enable_vorbis = xyes; then
|
||||
if test x$enable_tremor = xyes; then
|
||||
AC_MSG_WARN(["OggTremor detected, could not enable Vorbis."])
|
||||
enable_vorbis=no
|
||||
elif test x$enable_ogg = xyes; then
|
||||
PKG_CHECK_MODULES(VORBIS, [vorbis vorbisfile],
|
||||
AC_DEFINE(ENABLE_VORBIS_DECODER, 1, [Define for Ogg Vorbis support]),
|
||||
enable_vorbis=no)
|
||||
@@ -1065,6 +1066,7 @@ if
|
||||
test x$enable_mpg123 = xno &&
|
||||
test x$enable_oggflac = xno &&
|
||||
test x$enable_sidplay = xno &&
|
||||
test x$enable_tremor = xno &&
|
||||
test x$enable_vorbis = xno &&
|
||||
test x$enable_wavpack = xno &&
|
||||
test x$enable_wildmidi = xno &&
|
||||
@@ -1199,6 +1201,17 @@ fi
|
||||
|
||||
AM_CONDITIONAL(HAVE_ALSA, test x$enable_alsa = xyes)
|
||||
|
||||
dnl ----------------------------------- FFADO ---------------------------------
|
||||
|
||||
MPD_AUTO_PKG(ffado, FFADO, [libffado],
|
||||
[libffado output plugin], [libffado not found])
|
||||
|
||||
if test x$enable_ffado = xyes; then
|
||||
AC_DEFINE(ENABLE_FFADO_OUTPUT, 1, [Define to enable the libffado output plugin])
|
||||
fi
|
||||
|
||||
AM_CONDITIONAL(ENABLE_FFADO_OUTPUT, test x$enable_ffado = xyes)
|
||||
|
||||
dnl ----------------------------------- FIFO ----------------------------------
|
||||
if test x$enable_fifo = xyes; then
|
||||
AC_CHECK_FUNC([mkfifo],
|
||||
@@ -1369,26 +1382,27 @@ esac
|
||||
|
||||
AM_CONDITIONAL(ENABLE_SOLARIS_OUTPUT, test x$enable_solaris_output = xyes)
|
||||
|
||||
dnl --------------------------------- Solaris ---------------------------------
|
||||
dnl --------------------------------- WinMM ---------------------------------
|
||||
|
||||
case "$host_os" in
|
||||
mingw32* | windows*)
|
||||
AC_DEFINE(ENABLE_WIN32_OUTPUT, 1, [Define to enable WIN32 wave support])
|
||||
enable_win32_output=yes
|
||||
AC_DEFINE(ENABLE_WINMM_OUTPUT, 1, [Define to enable WinMM support])
|
||||
enable_winmm_output=yes
|
||||
MPD_LIBS="$MPD_LIBS -lwinmm"
|
||||
;;
|
||||
|
||||
*)
|
||||
enable_win32_output=no
|
||||
enable_winmm_output=no
|
||||
;;
|
||||
esac
|
||||
|
||||
AM_CONDITIONAL(ENABLE_WIN32_OUTPUT, test x$enable_win32_output = xyes)
|
||||
AM_CONDITIONAL(ENABLE_WINMM_OUTPUT, test x$enable_winmm_output = xyes)
|
||||
|
||||
dnl --------------------- Post Audio Output Plugins Tests ---------------------
|
||||
if
|
||||
test x$enable_alsa = xno &&
|
||||
test x$enable_ao = xno &&
|
||||
test x$enable_ffado = xno &&
|
||||
test x$enable_fifo = xno &&
|
||||
test x$enable_httpd_output = xno &&
|
||||
test x$enable_jack = xno &&
|
||||
@@ -1401,7 +1415,7 @@ if
|
||||
test x$enable_recorder_output = xno &&
|
||||
test x$enable_shout = xno &&
|
||||
test x$enable_solaris_output = xno &&
|
||||
test x$enable_win32_output = xno &&
|
||||
test x$enable_winmm_output = xno &&
|
||||
|
||||
AC_MSG_ERROR([No Audio Output types configured!])
|
||||
fi
|
||||
@@ -1473,23 +1487,23 @@ dnl ---------------------------------------------------------------------------
|
||||
echo ''
|
||||
echo '########### MPD CONFIGURATION ############'
|
||||
|
||||
echo -ne '\nArchive support:\n\t'
|
||||
printf '\nArchive support:\n\t'
|
||||
results(bzip2,[bzip2])
|
||||
results(iso9660,[ISO9660])
|
||||
results(zzip,[ZIP])
|
||||
|
||||
if test x$with_zeroconf != xno; then
|
||||
echo -ne '\nAutodiscovery support:\n\t'
|
||||
printf '\nAutodiscovery support:\n\t'
|
||||
results(avahi, [Avahi])
|
||||
results(bonjour, [Bonjour])
|
||||
fi
|
||||
|
||||
echo -ne '\nClient support:\n\t'
|
||||
printf '\nClient support:\n\t'
|
||||
results(ipv6, "IPv6")
|
||||
results(tcp, "TCP")
|
||||
results(un,[UNIX Domain Sockets])
|
||||
|
||||
echo -ne '\nFile format support:\n\t'
|
||||
printf '\nFile format support:\n\t'
|
||||
results(aac, [AAC])
|
||||
results(sidplay, [C64 SID])
|
||||
results(ffmpeg, [FFMPEG])
|
||||
@@ -1497,7 +1511,7 @@ results(flac, [FLAC])
|
||||
results(fluidsynth, [FluidSynth])
|
||||
results(gme, [GME])
|
||||
results(sndfile, [libsndfile])
|
||||
echo -ne '\n\t'
|
||||
printf '\n\t'
|
||||
results(mikmod, [MikMod])
|
||||
results(modplug, [MODPLUG])
|
||||
results(mad, [MAD])
|
||||
@@ -1505,46 +1519,47 @@ results(mpg123, [MPG123])
|
||||
results(mp4, [MP4])
|
||||
results(mpc, [Musepack])
|
||||
results(oggflac, [OggFLAC], flac)
|
||||
echo -ne '\n\t'
|
||||
results(with_tremor, [OggTremor])
|
||||
printf '\n\t'
|
||||
results(tremor, [OggTremor])
|
||||
results(vorbis, [OggVorbis])
|
||||
results(audiofile, [WAVE])
|
||||
results(wavpack, [WavPack])
|
||||
results(wildmidi, [WildMidi])
|
||||
|
||||
echo -en '\nOther features:\n\t'
|
||||
printf '\nOther features:\n\t'
|
||||
results(lsr, [libsamplerate])
|
||||
results(inotify, [inotify])
|
||||
results(sqlite, [SQLite])
|
||||
|
||||
echo -en '\nMetadata support:\n\t'
|
||||
printf '\nMetadata support:\n\t'
|
||||
results(cue,[cue])
|
||||
results(id3,[ID3])
|
||||
|
||||
echo -en '\nPlayback support:\n\t'
|
||||
printf '\nPlayback support:\n\t'
|
||||
results(alsa,ALSA)
|
||||
results(ffado,FFADO)
|
||||
results(fifo,FIFO)
|
||||
results(recorder_output,[File Recorder])
|
||||
results(httpd_output,[HTTP Daemon])
|
||||
results(jack,[JACK])
|
||||
results(ao,[libao])
|
||||
results(oss,[OSS])
|
||||
echo -ne '\n\t'
|
||||
printf '\n\t'
|
||||
results(openal,[OpenAL])
|
||||
results(osx, [OS X])
|
||||
results(pipe_output, [Pipeline])
|
||||
results(pulse, [PulseAudio])
|
||||
results(mvp, [Media MVP])
|
||||
results(shout, [SHOUTcast])
|
||||
echo -ne '\n\t'
|
||||
printf '\n\t'
|
||||
results(solaris, [Solaris])
|
||||
results(win32_output, [WIN32 wave])
|
||||
results(winmm_output, [WinMM])
|
||||
|
||||
if
|
||||
test x$enable_shout = xyes ||
|
||||
test x$enable_recorder = xyes ||
|
||||
test x$enable_httpd_output = xyes; then
|
||||
echo -en '\nStreaming encoder support:\n\t'
|
||||
printf '\nStreaming encoder support:\n\t'
|
||||
results(flac_encoder, [FLAC])
|
||||
results(lame_encoder, [LAME])
|
||||
results(vorbis_encoder, [Ogg Vorbis])
|
||||
@@ -1552,19 +1567,14 @@ if
|
||||
results(wave_encoder, [WAVE])
|
||||
fi
|
||||
|
||||
echo -en '\nStreaming support:\n\t'
|
||||
printf '\nStreaming support:\n\t'
|
||||
results(curl,[CURL])
|
||||
results(lastfm,[Last.FM])
|
||||
results(mms,[MMS])
|
||||
|
||||
echo -ne '\n\n##########################################\n\n'
|
||||
printf '\n\n##########################################\n\n'
|
||||
|
||||
if test x$enable_sndfile = xyes && test x$enable_modplug = xyes; then
|
||||
AC_MSG_WARN([compilation may fail, because libmodplug conflicts with libsndfile])
|
||||
AC_MSG_WARN([libmodplug ships modplug/sndfile.h, which hides libsndfile's sndfile.h])
|
||||
fi
|
||||
|
||||
echo -ne 'Generating files needed for compilation\n'
|
||||
echo 'Generating files needed for compilation'
|
||||
|
||||
dnl ---------------------------------------------------------------------------
|
||||
dnl Generate files
|
||||
|
@@ -260,6 +260,7 @@ input {
|
||||
# name "My HTTP Stream"
|
||||
# encoder "vorbis" # optional, vorbis or lame
|
||||
# port "8000"
|
||||
# bind_to_address "0.0.0.0" # optional, IPv4 or IPv6
|
||||
## quality "5.0" # do not define if bitrate is defined
|
||||
# bitrate "128" # do not define if quality is defined
|
||||
# format "44100:16:1"
|
||||
|
@@ -240,6 +240,12 @@
|
||||
<returnvalue>0 or 1</returnvalue>
|
||||
</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>
|
||||
<varname>random</varname>:
|
||||
<returnvalue>0 or 1</returnvalue>
|
||||
</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>
|
||||
<varname>single</varname>:
|
||||
@@ -1208,16 +1214,18 @@ OK
|
||||
<command>find</command>
|
||||
<arg choice="req"><replaceable>TYPE</replaceable></arg>
|
||||
<arg choice="req"><replaceable>WHAT</replaceable></arg>
|
||||
<arg choice="opt"><replaceable>...</replaceable></arg>
|
||||
</cmdsynopsis>
|
||||
</term>
|
||||
<listitem>
|
||||
<para>
|
||||
Finds songs in the db that are exactly
|
||||
<varname>WHAT</varname>. <varname>TYPE</varname> should
|
||||
be <parameter>album</parameter>,
|
||||
<parameter>artist</parameter>, or
|
||||
<parameter>title</parameter>. <varname>WHAT</varname>
|
||||
is what to find.
|
||||
<varname>WHAT</varname>. <varname>TYPE</varname> can
|
||||
be any tag supported by MPD, or one of the two special
|
||||
parameters — <parameter>file</parameter> to search by
|
||||
full path (relative to database root), and
|
||||
<parameter>any</parameter> to match against all
|
||||
available tags. <varname>WHAT</varname> is what to find.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
@@ -1227,14 +1235,14 @@ OK
|
||||
<command>findadd</command>
|
||||
<arg choice="req"><replaceable>TYPE</replaceable></arg>
|
||||
<arg choice="req"><replaceable>WHAT</replaceable></arg>
|
||||
<arg choice="opt"><replaceable>...</replaceable></arg>
|
||||
</cmdsynopsis>
|
||||
</term>
|
||||
<listitem>
|
||||
<para>
|
||||
Finds songs in the db that are exactly
|
||||
<varname>WHAT</varname> and adds them to current playlist.
|
||||
<varname>TYPE</varname> can be any tag supported by MPD.
|
||||
<varname>WHAT</varname> is what to find.
|
||||
Parameters have the same meaning as for <command>find</command>.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
@@ -1249,7 +1257,8 @@ OK
|
||||
<listitem>
|
||||
<para>
|
||||
Lists all tags of the specified type.
|
||||
<varname>TYPE</varname> should be album or artist.
|
||||
<varname>TYPE</varname> can be any tag supported by MPD or
|
||||
<parameter>file</parameter>.
|
||||
</para>
|
||||
<para>
|
||||
<varname>ARTIST</varname> is an optional parameter when
|
||||
@@ -1312,17 +1321,15 @@ OK
|
||||
<command>search</command>
|
||||
<arg choice="req"><replaceable>TYPE</replaceable></arg>
|
||||
<arg choice="req"><replaceable>WHAT</replaceable></arg>
|
||||
<arg choice="opt"><replaceable>...</replaceable></arg>
|
||||
</cmdsynopsis>
|
||||
</term>
|
||||
<listitem>
|
||||
<para>
|
||||
Searches for any song that contains
|
||||
<varname>WHAT</varname>. <varname>TYPE</varname> can be
|
||||
<parameter>title</parameter>,
|
||||
<parameter>artist</parameter>,
|
||||
<parameter>album</parameter> or
|
||||
<parameter>filename</parameter>. Search is not case
|
||||
sensitive.
|
||||
<varname>WHAT</varname>. Parameters have the same meaning
|
||||
as for <command>find</command>, except that search is not
|
||||
case sensitive.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
50
doc/user.xml
50
doc/user.xml
@@ -788,6 +788,43 @@ cd mpd-version</programlisting>
|
||||
</para>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<title><varname>ffado</varname></title>
|
||||
|
||||
<para>
|
||||
The <varname>ffado</varname> plugin connects to FireWire
|
||||
audio devices via <filename>libffado</filename>.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Warning: this plugin was not tested successfully. I just
|
||||
couldn't keep libffado2 from crashing. Use at your own
|
||||
risk.
|
||||
</para>
|
||||
|
||||
<informaltable>
|
||||
<tgroup cols="2">
|
||||
<thead>
|
||||
<row>
|
||||
<entry>Setting</entry>
|
||||
<entry>Description</entry>
|
||||
</row>
|
||||
</thead>
|
||||
<tbody>
|
||||
<row>
|
||||
<entry>
|
||||
<varname>device</varname>
|
||||
<parameter>NAME</parameter>
|
||||
</entry>
|
||||
<entry>
|
||||
Sets the device which should be used, e.g. "hw:0".
|
||||
</entry>
|
||||
</row>
|
||||
</tbody>
|
||||
</tgroup>
|
||||
</informaltable>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<title><varname>jack</varname></title>
|
||||
|
||||
@@ -914,8 +951,17 @@ cd mpd-version</programlisting>
|
||||
<parameter>P</parameter>
|
||||
</entry>
|
||||
<entry>
|
||||
Binds the HTTP server to the specified port (on all
|
||||
interfaces).
|
||||
Binds the HTTP server to the specified port.
|
||||
</entry>
|
||||
</row>
|
||||
<row>
|
||||
<entry>
|
||||
<varname>bind_to_address</varname>
|
||||
<parameter>ADDR</parameter>
|
||||
</entry>
|
||||
<entry>
|
||||
Binds the HTTP server to the specified address (IPv4 or
|
||||
IPv6). Multiple addresses in parallel are not supported.
|
||||
</entry>
|
||||
</row>
|
||||
<row>
|
||||
|
12
m4/faad.m4
12
m4/faad.m4
@@ -58,7 +58,7 @@ if test x$enable_aac = xyes; then
|
||||
fi
|
||||
if test x$enable_aac = xyes; then
|
||||
AC_MSG_CHECKING(that FAAD2 uses buffer and bufferlen)
|
||||
AC_COMPILE_IFELSE([
|
||||
AC_COMPILE_IFELSE([AC_LANG_SOURCE([
|
||||
#include <faad.h>
|
||||
|
||||
int main() {
|
||||
@@ -82,9 +82,9 @@ int main() {
|
||||
|
||||
return 0;
|
||||
}
|
||||
],[AC_MSG_RESULT(yes);AC_DEFINE(HAVE_FAAD_BUFLEN_FUNCS,1,[Define if FAAD2 uses buflen in function calls])],[AC_MSG_RESULT(no);
|
||||
])],[AC_MSG_RESULT(yes);AC_DEFINE(HAVE_FAAD_BUFLEN_FUNCS,1,[Define if FAAD2 uses buflen in function calls])],[AC_MSG_RESULT(no);
|
||||
AC_MSG_CHECKING(that FAAD2 can even be used)
|
||||
AC_COMPILE_IFELSE([
|
||||
AC_COMPILE_IFELSE([AC_LANG_SOURCE([
|
||||
#include <faad.h>
|
||||
|
||||
int main() {
|
||||
@@ -113,7 +113,7 @@ int main() {
|
||||
|
||||
return 0;
|
||||
}
|
||||
],AC_MSG_RESULT(yes),[AC_MSG_RESULT(no);enable_aac=no])
|
||||
])],AC_MSG_RESULT(yes),[AC_MSG_RESULT(no);enable_aac=no])
|
||||
])
|
||||
fi
|
||||
if test x$enable_aac = xyes; then
|
||||
@@ -136,7 +136,7 @@ if test x$enable_aac = xyes; then
|
||||
CPPFLAGS=$CFLAGS
|
||||
|
||||
AC_MSG_CHECKING(for broken libfaad headers)
|
||||
AC_COMPILE_IFELSE([
|
||||
AC_COMPILE_IFELSE([AC_LANG_SOURCE([
|
||||
#include <faad.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
@@ -148,7 +148,7 @@ int main() {
|
||||
faacDecInit2(NULL, NULL, 0, &sample_rate, &channels);
|
||||
return 0;
|
||||
}
|
||||
],
|
||||
])],
|
||||
[AC_MSG_RESULT(correct)],
|
||||
[AC_MSG_RESULT(broken);
|
||||
AC_DEFINE(HAVE_FAAD_LONG, 1, [Define if faad.h uses the broken "unsigned long" pointers])])
|
||||
|
@@ -4,9 +4,9 @@ AC_DEFUN([MPD_CHECK_FLAG],[
|
||||
[mpd_check_cflag_$var],[
|
||||
save_CFLAGS="$CFLAGS"
|
||||
CFLAGS="$CFLAGS $1"
|
||||
AC_COMPILE_IFELSE([
|
||||
AC_COMPILE_IFELSE([AC_LANG_SOURCE([
|
||||
int main(void) { return 0; }
|
||||
], [ eval "mpd_check_cflag_$var=yes"
|
||||
])], [ eval "mpd_check_cflag_$var=yes"
|
||||
], [ eval "mpd_check_cflag_$var=no" ])
|
||||
CFLAGS="$save_CFLAGS"
|
||||
])
|
||||
|
@@ -1,19 +1,19 @@
|
||||
AC_DEFUN([results], [
|
||||
dnl This is a hack to allow "with" names, otherwise "enable".
|
||||
num=`expr match $1 'with'`
|
||||
num=`expr $1 : 'with'`
|
||||
if test "$num" != "0"; then
|
||||
var="`echo '$'$1`"
|
||||
else
|
||||
var="`echo '$'enable_$1`"
|
||||
fi
|
||||
|
||||
echo -n '('
|
||||
printf '('
|
||||
if eval "test x$var = xyes"; then
|
||||
echo -n '+'
|
||||
printf '+'
|
||||
elif test -n "$3" && eval "test x$var = x$3"; then
|
||||
echo -n '+'
|
||||
printf '+'
|
||||
else
|
||||
echo -n '-'
|
||||
printf '-'
|
||||
fi
|
||||
echo -n "$2) "
|
||||
printf '%s) ' "$2"
|
||||
])
|
||||
|
113
src/ape.c
Normal file
113
src/ape.c
Normal file
@@ -0,0 +1,113 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2010 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 "config.h"
|
||||
#include "ape.h"
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
#include <stdint.h>
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
struct ape_footer {
|
||||
unsigned char id[8];
|
||||
uint32_t version;
|
||||
uint32_t length;
|
||||
uint32_t count;
|
||||
unsigned char flags[4];
|
||||
unsigned char reserved[8];
|
||||
};
|
||||
|
||||
static bool
|
||||
ape_scan_internal(FILE *fp, tag_ape_callback_t callback, void *ctx)
|
||||
{
|
||||
/* determine if file has an apeV2 tag */
|
||||
struct ape_footer footer;
|
||||
if (fseek(fp, -(long)sizeof(footer), SEEK_END) ||
|
||||
fread(&footer, 1, sizeof(footer), fp) != sizeof(footer) ||
|
||||
memcmp(footer.id, "APETAGEX", sizeof(footer.id)) != 0 ||
|
||||
GUINT32_FROM_LE(footer.version) != 2000)
|
||||
return false;
|
||||
|
||||
/* find beginning of ape tag */
|
||||
size_t remaining = GUINT32_FROM_LE(footer.length);
|
||||
if (remaining <= sizeof(footer) + 10 ||
|
||||
/* refuse to load more than one megabyte of tag data */
|
||||
remaining > 1024 * 1024 ||
|
||||
fseek(fp, -(long)remaining, SEEK_END))
|
||||
return false;
|
||||
|
||||
/* read tag into buffer */
|
||||
remaining -= sizeof(footer);
|
||||
assert(remaining > 10);
|
||||
|
||||
char *buffer = g_malloc(remaining);
|
||||
if (fread(buffer, 1, remaining, fp) != remaining)
|
||||
return false;
|
||||
|
||||
/* read tags */
|
||||
unsigned n = GUINT32_FROM_LE(footer.count);
|
||||
const char *p = buffer;
|
||||
while (n-- && remaining > 10) {
|
||||
size_t size = GUINT32_FROM_LE(*(const uint32_t *)p);
|
||||
p += 4;
|
||||
remaining -= 4;
|
||||
unsigned long flags = GUINT32_FROM_LE(*(const uint32_t *)p);
|
||||
p += 4;
|
||||
remaining -= 4;
|
||||
|
||||
/* get the key */
|
||||
const char *key = p;
|
||||
while (remaining > size && *p != '\0') {
|
||||
p++;
|
||||
remaining--;
|
||||
}
|
||||
p++;
|
||||
remaining--;
|
||||
|
||||
/* get the value */
|
||||
if (remaining < size)
|
||||
break;
|
||||
|
||||
if (!callback(flags, key, p, size, ctx))
|
||||
break;
|
||||
|
||||
p += size;
|
||||
remaining -= size;
|
||||
}
|
||||
|
||||
g_free(buffer);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
tag_ape_scan(const char *path_fs, tag_ape_callback_t callback, void *ctx)
|
||||
{
|
||||
FILE *fp;
|
||||
|
||||
fp = fopen(path_fs, "rb");
|
||||
if (fp == NULL)
|
||||
return false;
|
||||
|
||||
bool success = ape_scan_internal(fp, callback, ctx);
|
||||
fclose(fp);
|
||||
return success;
|
||||
}
|
42
src/ape.h
Normal file
42
src/ape.h
Normal file
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2010 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_APE_H
|
||||
#define MPD_APE_H
|
||||
|
||||
#include "check.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
|
||||
typedef bool (*tag_ape_callback_t)(unsigned long flags, const char *key,
|
||||
const char *value, size_t value_length,
|
||||
void *ctx);
|
||||
|
||||
/**
|
||||
* Scans the APE tag values from a file.
|
||||
*
|
||||
* @param path_fs the path of the file in filesystem encoding
|
||||
* @return false if the file could not be opened or if no APE tag is
|
||||
* present
|
||||
*/
|
||||
bool
|
||||
tag_ape_scan(const char *path_fs, tag_ape_callback_t callback, void *ctx);
|
||||
|
||||
#endif
|
@@ -25,6 +25,7 @@
|
||||
#include "output_plugin.h"
|
||||
#include "output_all.h"
|
||||
#include "conf.h"
|
||||
#include "mpd_error.h"
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
@@ -52,6 +53,7 @@ void initAudioConfig(void)
|
||||
ret = audio_format_parse(&configured_audio_format, param->value,
|
||||
true, &error);
|
||||
if (!ret)
|
||||
g_error("error parsing \"%s\" at line %i: %s",
|
||||
CONF_AUDIO_OUTPUT_FORMAT, param->line, error->message);
|
||||
MPD_ERROR("error parsing \"%s\" at line %i: %s",
|
||||
CONF_AUDIO_OUTPUT_FORMAT, param->line,
|
||||
error->message);
|
||||
}
|
||||
|
@@ -38,7 +38,7 @@ bool
|
||||
audio_check_sample_rate(unsigned long sample_rate, GError **error_r);
|
||||
|
||||
bool
|
||||
audio_check_sample_format(unsigned sample_format, GError **error_r);
|
||||
audio_check_sample_format(enum sample_format, GError **error_r);
|
||||
|
||||
bool
|
||||
audio_check_channel_count(unsigned sample_format, GError **error_r);
|
||||
|
@@ -26,6 +26,7 @@
|
||||
#include "decoder_plugin.h"
|
||||
#include "output_list.h"
|
||||
#include "ls.h"
|
||||
#include "mpd_error.h"
|
||||
|
||||
#ifdef ENABLE_ENCODER
|
||||
#include "encoder_list.h"
|
||||
@@ -155,10 +156,8 @@ parse_cmdline(int argc, char **argv, struct options *options,
|
||||
ret = g_option_context_parse(context, &argc, &argv, &error);
|
||||
g_option_context_free(context);
|
||||
|
||||
if (!ret) {
|
||||
g_error("option parsing failed: %s\n", error->message);
|
||||
exit(1);
|
||||
}
|
||||
if (!ret)
|
||||
MPD_ERROR("option parsing failed: %s\n", error->message);
|
||||
|
||||
if (option_version)
|
||||
version();
|
||||
|
@@ -1715,15 +1715,11 @@ handle_sticker_song(struct client *client, int argc, char *argv[])
|
||||
}
|
||||
|
||||
sticker = sticker_song_get(song);
|
||||
if (NULL == sticker) {
|
||||
command_error(client, ACK_ERROR_NO_EXIST,
|
||||
"no stickers found");
|
||||
return COMMAND_RETURN_ERROR;
|
||||
if (sticker) {
|
||||
sticker_print(client, sticker);
|
||||
sticker_free(sticker);
|
||||
}
|
||||
|
||||
sticker_print(client, sticker);
|
||||
sticker_free(sticker);
|
||||
|
||||
return COMMAND_RETURN_OK;
|
||||
/* set song song_id id key */
|
||||
} else if (argc == 6 && strcmp(argv[1], "set") == 0) {
|
||||
|
28
src/conf.c
28
src/conf.c
@@ -23,6 +23,7 @@
|
||||
#include "tokenizer.h"
|
||||
#include "path.h"
|
||||
#include "glib_compat.h"
|
||||
#include "mpd_error.h"
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
@@ -498,8 +499,8 @@ config_get_path(const char *name)
|
||||
|
||||
path = parsePath(param->value);
|
||||
if (path == NULL)
|
||||
g_error("error parsing \"%s\" at line %i\n",
|
||||
name, param->line);
|
||||
MPD_ERROR("error parsing \"%s\" at line %i\n",
|
||||
name, param->line);
|
||||
|
||||
g_free(param->value);
|
||||
return param->value = path;
|
||||
@@ -517,7 +518,8 @@ config_get_unsigned(const char *name, unsigned default_value)
|
||||
|
||||
value = strtol(param->value, &endptr, 0);
|
||||
if (*endptr != 0 || value < 0)
|
||||
g_error("Not a valid non-negative number in line %i", param->line);
|
||||
MPD_ERROR("Not a valid non-negative number in line %i",
|
||||
param->line);
|
||||
|
||||
return (unsigned)value;
|
||||
}
|
||||
@@ -534,10 +536,10 @@ config_get_positive(const char *name, unsigned default_value)
|
||||
|
||||
value = strtol(param->value, &endptr, 0);
|
||||
if (*endptr != 0)
|
||||
g_error("Not a valid number in line %i", param->line);
|
||||
MPD_ERROR("Not a valid number in line %i", param->line);
|
||||
|
||||
if (value <= 0)
|
||||
g_error("Not a positive number in line %i", param->line);
|
||||
MPD_ERROR("Not a positive number in line %i", param->line);
|
||||
|
||||
return (unsigned)value;
|
||||
}
|
||||
@@ -569,9 +571,9 @@ bool config_get_bool(const char *name, bool default_value)
|
||||
|
||||
success = get_bool(param->value, &value);
|
||||
if (!success)
|
||||
g_error("%s is not a boolean value (yes, true, 1) or "
|
||||
"(no, false, 0) on line %i\n",
|
||||
name, param->line);
|
||||
MPD_ERROR("%s is not a boolean value (yes, true, 1) or "
|
||||
"(no, false, 0) on line %i\n",
|
||||
name, param->line);
|
||||
|
||||
return value;
|
||||
}
|
||||
@@ -601,10 +603,10 @@ config_get_block_unsigned(const struct config_param *param, const char *name,
|
||||
|
||||
value = strtol(bp->value, &endptr, 0);
|
||||
if (*endptr != 0)
|
||||
g_error("Not a valid number in line %i", bp->line);
|
||||
MPD_ERROR("Not a valid number in line %i", bp->line);
|
||||
|
||||
if (value < 0)
|
||||
g_error("Not a positive number in line %i", bp->line);
|
||||
MPD_ERROR("Not a positive number in line %i", bp->line);
|
||||
|
||||
return (unsigned)value;
|
||||
}
|
||||
@@ -621,9 +623,9 @@ config_get_block_bool(const struct config_param *param, const char *name,
|
||||
|
||||
success = get_bool(bp->value, &value);
|
||||
if (!success)
|
||||
g_error("%s is not a boolean value (yes, true, 1) or "
|
||||
"(no, false, 0) on line %i\n",
|
||||
name, bp->line);
|
||||
MPD_ERROR("%s is not a boolean value (yes, true, 1) or "
|
||||
"(no, false, 0) on line %i\n",
|
||||
name, bp->line);
|
||||
|
||||
return value;
|
||||
}
|
||||
|
18
src/conf.h
18
src/conf.h
@@ -28,7 +28,7 @@
|
||||
#define CONF_FOLLOW_INSIDE_SYMLINKS "follow_inside_symlinks"
|
||||
#define CONF_FOLLOW_OUTSIDE_SYMLINKS "follow_outside_symlinks"
|
||||
#define CONF_DB_FILE "db_file"
|
||||
#define CONF_STICKER_FILE "sticker_file"
|
||||
#define CONF_STICKER_FILE "sticker_file"
|
||||
#define CONF_LOG_FILE "log_file"
|
||||
#define CONF_PID_FILE "pid_file"
|
||||
#define CONF_STATE_FILE "state_file"
|
||||
@@ -38,7 +38,7 @@
|
||||
#define CONF_PORT "port"
|
||||
#define CONF_LOG_LEVEL "log_level"
|
||||
#define CONF_ZEROCONF_NAME "zeroconf_name"
|
||||
#define CONF_ZEROCONF_ENABLED "zeroconf_enabled"
|
||||
#define CONF_ZEROCONF_ENABLED "zeroconf_enabled"
|
||||
#define CONF_PASSWORD "password"
|
||||
#define CONF_DEFAULT_PERMS "default_permissions"
|
||||
#define CONF_AUDIO_OUTPUT "audio_output"
|
||||
@@ -48,7 +48,7 @@
|
||||
#define CONF_REPLAYGAIN "replaygain"
|
||||
#define CONF_REPLAYGAIN_PREAMP "replaygain_preamp"
|
||||
#define CONF_REPLAYGAIN_MISSING_PREAMP "replaygain_missing_preamp"
|
||||
#define CONF_REPLAYGAIN_LIMIT "replaygain_limit"
|
||||
#define CONF_REPLAYGAIN_LIMIT "replaygain_limit"
|
||||
#define CONF_VOLUME_NORMALIZATION "volume_normalization"
|
||||
#define CONF_SAMPLERATE_CONVERTER "samplerate_converter"
|
||||
#define CONF_AUDIO_BUFFER_SIZE "audio_buffer_size"
|
||||
@@ -66,12 +66,12 @@
|
||||
#define CONF_ID3V1_ENCODING "id3v1_encoding"
|
||||
#define CONF_METADATA_TO_USE "metadata_to_use"
|
||||
#define CONF_SAVE_ABSOLUTE_PATHS "save_absolute_paths_in_playlists"
|
||||
#define CONF_DECODER "decoder"
|
||||
#define CONF_INPUT "input"
|
||||
#define CONF_GAPLESS_MP3_PLAYBACK "gapless_mp3_playback"
|
||||
#define CONF_PLAYLIST_PLUGIN "playlist_plugin"
|
||||
#define CONF_AUTO_UPDATE "auto_update"
|
||||
#define CONF_AUTO_UPDATE_DEPTH "auto_update_depth"
|
||||
#define CONF_DECODER "decoder"
|
||||
#define CONF_INPUT "input"
|
||||
#define CONF_GAPLESS_MP3_PLAYBACK "gapless_mp3_playback"
|
||||
#define CONF_PLAYLIST_PLUGIN "playlist_plugin"
|
||||
#define CONF_AUTO_UPDATE "auto_update"
|
||||
#define CONF_AUTO_UPDATE_DEPTH "auto_update_depth"
|
||||
|
||||
#define DEFAULT_PLAYLIST_MAX_LENGTH (1024*16)
|
||||
#define DEFAULT_PLAYLIST_SAVE_ABSOLUTE_PATHS false
|
||||
|
38
src/daemon.c
38
src/daemon.c
@@ -65,23 +65,23 @@ daemonize_kill(void)
|
||||
int pid, ret;
|
||||
|
||||
if (pidfile == NULL)
|
||||
g_error("no pid_file specified in the config file");
|
||||
MPD_ERROR("no pid_file specified in the config file");
|
||||
|
||||
fp = fopen(pidfile, "r");
|
||||
if (fp == NULL)
|
||||
g_error("unable to open pid file \"%s\": %s",
|
||||
pidfile, g_strerror(errno));
|
||||
MPD_ERROR("unable to open pid file \"%s\": %s",
|
||||
pidfile, g_strerror(errno));
|
||||
|
||||
if (fscanf(fp, "%i", &pid) != 1) {
|
||||
g_error("unable to read the pid from file \"%s\"",
|
||||
pidfile);
|
||||
MPD_ERROR("unable to read the pid from file \"%s\"",
|
||||
pidfile);
|
||||
}
|
||||
fclose(fp);
|
||||
|
||||
ret = kill(pid, SIGTERM);
|
||||
if (ret < 0)
|
||||
g_error("unable to kill proccess %i: %s",
|
||||
pid, g_strerror(errno));
|
||||
MPD_ERROR("unable to kill proccess %i: %s",
|
||||
pid, g_strerror(errno));
|
||||
|
||||
exit(EXIT_SUCCESS);
|
||||
}
|
||||
@@ -102,8 +102,8 @@ daemonize_set_user(void)
|
||||
/* set gid */
|
||||
if (user_gid != (gid_t)-1 && user_gid != getgid()) {
|
||||
if (setgid(user_gid) == -1) {
|
||||
g_error("cannot setgid to %d: %s",
|
||||
(int)user_gid, g_strerror(errno));
|
||||
MPD_ERROR("cannot setgid to %d: %s",
|
||||
(int)user_gid, g_strerror(errno));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,8 +121,8 @@ daemonize_set_user(void)
|
||||
/* set uid */
|
||||
if (user_uid != (uid_t)-1 && user_uid != getuid() &&
|
||||
setuid(user_uid) == -1) {
|
||||
g_error("cannot change to uid of user \"%s\": %s",
|
||||
user_name, g_strerror(errno));
|
||||
MPD_ERROR("cannot change to uid of user \"%s\": %s",
|
||||
user_name, g_strerror(errno));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -136,7 +136,7 @@ daemonize_detach(void)
|
||||
#ifdef HAVE_DAEMON
|
||||
|
||||
if (daemon(0, 1))
|
||||
g_error("daemon() failed: %s", g_strerror(errno));
|
||||
MPD_ERROR("daemon() failed: %s", g_strerror(errno));
|
||||
|
||||
#elif defined(HAVE_FORK)
|
||||
|
||||
@@ -144,7 +144,7 @@ daemonize_detach(void)
|
||||
|
||||
switch (fork()) {
|
||||
case -1:
|
||||
g_error("fork() failed: %s", g_strerror(errno));
|
||||
MPD_ERROR("fork() failed: %s", g_strerror(errno));
|
||||
case 0:
|
||||
break;
|
||||
default:
|
||||
@@ -155,14 +155,14 @@ daemonize_detach(void)
|
||||
/* release the current working directory */
|
||||
|
||||
if (chdir("/") < 0)
|
||||
g_error("problems changing to root directory");
|
||||
MPD_ERROR("problems changing to root directory");
|
||||
|
||||
/* detach from the current session */
|
||||
|
||||
setsid();
|
||||
|
||||
#else
|
||||
g_error("no support for daemonizing");
|
||||
MPD_ERROR("no support for daemonizing");
|
||||
#endif
|
||||
|
||||
g_debug("daemonized!");
|
||||
@@ -179,8 +179,8 @@ daemonize(bool detach)
|
||||
g_debug("opening pid file");
|
||||
fp = fopen(pidfile, "w+");
|
||||
if (!fp) {
|
||||
g_error("could not create pid file \"%s\": %s",
|
||||
pidfile, g_strerror(errno));
|
||||
MPD_ERROR("could not create pid file \"%s\": %s",
|
||||
pidfile, g_strerror(errno));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -200,7 +200,7 @@ daemonize_init(const char *user, const char *group, const char *_pidfile)
|
||||
if (user) {
|
||||
struct passwd *pwd = getpwnam(user);
|
||||
if (!pwd)
|
||||
g_error("no such user \"%s\"", user);
|
||||
MPD_ERROR("no such user \"%s\"", user);
|
||||
|
||||
user_uid = pwd->pw_uid;
|
||||
user_gid = pwd->pw_gid;
|
||||
@@ -214,7 +214,7 @@ daemonize_init(const char *user, const char *group, const char *_pidfile)
|
||||
if (group) {
|
||||
struct group *grp = grp = getgrnam(group);
|
||||
if (!grp)
|
||||
g_error("no such group \"%s\"", group);
|
||||
MPD_ERROR("no such group \"%s\"", group);
|
||||
user_gid = grp->gr_gid;
|
||||
had_group = true;
|
||||
}
|
||||
|
@@ -20,6 +20,8 @@
|
||||
#ifndef DAEMON_H
|
||||
#define DAEMON_H
|
||||
|
||||
#include "mpd_error.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#ifndef WIN32
|
||||
@@ -51,7 +53,7 @@ daemonize_kill(void);
|
||||
#include <glib.h>
|
||||
static inline void
|
||||
daemonize_kill(void)
|
||||
{ g_error("--kill is not available on WIN32"); }
|
||||
{ MPD_ERROR("--kill is not available on WIN32"); }
|
||||
#endif
|
||||
|
||||
/**
|
||||
|
@@ -231,16 +231,18 @@ static enum sample_format
|
||||
ffmpeg_sample_format(G_GNUC_UNUSED const AVCodecContext *codec_context)
|
||||
{
|
||||
#if LIBAVCODEC_VERSION_INT >= ((51<<16)+(41<<8)+0)
|
||||
int bits = (uint8_t) av_get_bits_per_sample_format(codec_context->sample_fmt);
|
||||
|
||||
/* XXX implement & test other sample formats */
|
||||
|
||||
switch (bits) {
|
||||
case 16:
|
||||
switch (codec_context->sample_fmt) {
|
||||
case SAMPLE_FMT_S16:
|
||||
return SAMPLE_FORMAT_S16;
|
||||
}
|
||||
|
||||
return SAMPLE_FORMAT_UNDEFINED;
|
||||
case SAMPLE_FMT_S32:
|
||||
return SAMPLE_FORMAT_S32;
|
||||
|
||||
default:
|
||||
g_warning("Unsupported libavcodec SampleFormat value: %d",
|
||||
codec_context->sample_fmt);
|
||||
return SAMPLE_FORMAT_UNDEFINED;
|
||||
}
|
||||
#else
|
||||
/* XXX fixme 16-bit for older ffmpeg (13 Aug 2007) */
|
||||
return SAMPLE_FORMAT_S16;
|
||||
@@ -522,7 +524,9 @@ static const char *const ffmpeg_suffixes[] = {
|
||||
"atrac", "au", "aud", "avi", "avm2", "avs", "bap", "bfi", "c93", "cak",
|
||||
"cin", "cmv", "cpk", "daud", "dct", "divx", "dts", "dv", "dvd", "dxa",
|
||||
"eac3", "film", "flac", "flc", "fli", "fll", "flx", "flv", "g726",
|
||||
"gsm", "gxf", "iss", "m1v", "m2v", "m2t", "m2ts", "m4a", "m4v", "mad",
|
||||
"gsm", "gxf", "iss", "m1v", "m2v", "m2t", "m2ts",
|
||||
"m4a", "m4b", "m4v",
|
||||
"mad",
|
||||
"mj2", "mjpeg", "mjpg", "mka", "mkv", "mlp", "mm", "mmf", "mov", "mp+",
|
||||
"mp1", "mp2", "mp3", "mp4", "mpc", "mpeg", "mpg", "mpga", "mpp", "mpu",
|
||||
"mve", "mvi", "mxf", "nc", "nsv", "nut", "nuv", "oga", "ogm", "ogv",
|
||||
|
@@ -87,7 +87,7 @@ flac_find_string_comment(const FLAC__StreamMetadata *block,
|
||||
int offset;
|
||||
size_t pos;
|
||||
int len;
|
||||
unsigned char tmp, *p;
|
||||
const unsigned char *p;
|
||||
|
||||
*str = NULL;
|
||||
offset = FLAC__metadata_object_vorbiscomment_find_entry_from(block, 0,
|
||||
@@ -101,10 +101,7 @@ flac_find_string_comment(const FLAC__StreamMetadata *block,
|
||||
return false;
|
||||
|
||||
p = &block->data.vorbis_comment.comments[offset].entry[pos];
|
||||
tmp = p[len];
|
||||
p[len] = '\0';
|
||||
*str = strdup((char *)p);
|
||||
p[len] = tmp;
|
||||
*str = g_strndup((const char *)p, len);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@@ -1,13 +1,21 @@
|
||||
#include "config.h"
|
||||
#include "../decoder_api.h"
|
||||
#include "audio_check.h"
|
||||
#include "uri.h"
|
||||
|
||||
#include <glib.h>
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <gme/gme.h>
|
||||
|
||||
#undef G_LOG_DOMAIN
|
||||
#define G_LOG_DOMAIN "gme"
|
||||
|
||||
#define SUBTUNE_PREFIX "tune_"
|
||||
|
||||
enum {
|
||||
GME_SAMPLE_RATE = 44100,
|
||||
GME_CHANNELS = 2,
|
||||
@@ -15,10 +23,91 @@ enum {
|
||||
GME_BUFFER_SAMPLES = GME_BUFFER_FRAMES * GME_CHANNELS,
|
||||
};
|
||||
|
||||
/**
|
||||
* returns the file path stripped of any /tune_xxx.* subtune
|
||||
* suffix
|
||||
*/
|
||||
static char *
|
||||
get_container_name(const char *path_fs)
|
||||
{
|
||||
const char *subtune_suffix = uri_get_suffix(path_fs);
|
||||
char *path_container = g_strdup(path_fs);
|
||||
char *pat = g_strconcat("*/" SUBTUNE_PREFIX "???.", subtune_suffix, NULL);
|
||||
GPatternSpec *path_with_subtune = g_pattern_spec_new(pat);
|
||||
g_free(pat);
|
||||
if (!g_pattern_match(path_with_subtune,
|
||||
strlen(path_container), path_container, NULL)) {
|
||||
g_pattern_spec_free(path_with_subtune);
|
||||
return path_container;
|
||||
}
|
||||
|
||||
char *ptr = g_strrstr(path_container, "/" SUBTUNE_PREFIX);
|
||||
if (ptr != NULL)
|
||||
*ptr='\0';
|
||||
|
||||
g_pattern_spec_free(path_with_subtune);
|
||||
return path_container;
|
||||
}
|
||||
|
||||
/**
|
||||
* returns tune number from file.nsf/tune_xxx.* style path or 0 if no subtune
|
||||
* is appended.
|
||||
*/
|
||||
static int
|
||||
get_song_num(const char *path_fs)
|
||||
{
|
||||
const char *subtune_suffix = uri_get_suffix(path_fs);
|
||||
char *pat = g_strconcat("*/" SUBTUNE_PREFIX "???.", subtune_suffix, NULL);
|
||||
GPatternSpec *path_with_subtune = g_pattern_spec_new(pat);
|
||||
g_free(pat);
|
||||
|
||||
if (g_pattern_match(path_with_subtune,
|
||||
strlen(path_fs), path_fs, NULL)) {
|
||||
char *sub = g_strrstr(path_fs, "/" SUBTUNE_PREFIX);
|
||||
g_pattern_spec_free(path_with_subtune);
|
||||
if(!sub)
|
||||
return 0;
|
||||
|
||||
sub += strlen("/" SUBTUNE_PREFIX);
|
||||
int song_num = strtol(sub, NULL, 10);
|
||||
|
||||
return song_num - 1;
|
||||
} else {
|
||||
g_pattern_spec_free(path_with_subtune);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static char *
|
||||
gme_container_scan(const char *path_fs, const unsigned int tnum)
|
||||
{
|
||||
Music_Emu *emu;
|
||||
const char* gme_err;
|
||||
unsigned int num_songs;
|
||||
|
||||
gme_err = gme_open_file(path_fs, &emu, GME_SAMPLE_RATE);
|
||||
if (gme_err != NULL) {
|
||||
g_warning("%s", gme_err);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
num_songs = gme_track_count(emu);
|
||||
/* if it only contains a single tune, don't treat as container */
|
||||
if (num_songs < 2)
|
||||
return NULL;
|
||||
|
||||
const char *subtune_suffix = uri_get_suffix(path_fs);
|
||||
if (tnum <= num_songs){
|
||||
char *subtune = g_strdup_printf(
|
||||
SUBTUNE_PREFIX "%03u.%s", tnum, subtune_suffix);
|
||||
return subtune;
|
||||
} else
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void
|
||||
gme_file_decode(struct decoder *decoder, const char *path_fs)
|
||||
{
|
||||
int track = 0; /* index of track to play */
|
||||
float song_len;
|
||||
Music_Emu *emu;
|
||||
gme_info_t *ti;
|
||||
@@ -26,13 +115,17 @@ gme_file_decode(struct decoder *decoder, const char *path_fs)
|
||||
enum decoder_command cmd;
|
||||
short buf[GME_BUFFER_SAMPLES];
|
||||
const char* gme_err;
|
||||
char *path_container = get_container_name(path_fs);
|
||||
int song_num = get_song_num(path_fs);
|
||||
|
||||
gme_err = gme_open_file(path_fs, &emu, GME_SAMPLE_RATE);
|
||||
gme_err = gme_open_file(path_container, &emu, GME_SAMPLE_RATE);
|
||||
g_free(path_container);
|
||||
if (gme_err != NULL) {
|
||||
g_warning("%s", gme_err);
|
||||
return;
|
||||
}
|
||||
if((gme_err = gme_track_info(emu, &ti, 0)) != NULL){
|
||||
|
||||
if((gme_err = gme_track_info(emu, &ti, song_num)) != NULL){
|
||||
g_warning("%s", gme_err);
|
||||
gme_delete(emu);
|
||||
return;
|
||||
@@ -57,7 +150,7 @@ gme_file_decode(struct decoder *decoder, const char *path_fs)
|
||||
|
||||
decoder_initialized(decoder, &audio_format, true, song_len);
|
||||
|
||||
if((gme_err = gme_start_track(emu, track)) != NULL)
|
||||
if((gme_err = gme_start_track(emu, song_num)) != NULL)
|
||||
g_warning("%s", gme_err);
|
||||
|
||||
/* play */
|
||||
@@ -90,13 +183,17 @@ gme_tag_dup(const char *path_fs)
|
||||
Music_Emu *emu;
|
||||
gme_info_t *ti;
|
||||
const char* gme_err;
|
||||
char *path_container=get_container_name(path_fs);
|
||||
int song_num;
|
||||
song_num=get_song_num(path_fs);
|
||||
|
||||
gme_err = gme_open_file(path_fs, &emu, GME_SAMPLE_RATE);
|
||||
gme_err = gme_open_file(path_container, &emu, GME_SAMPLE_RATE);
|
||||
g_free(path_container);
|
||||
if (gme_err != NULL) {
|
||||
g_warning("%s", gme_err);
|
||||
return NULL;
|
||||
}
|
||||
if((gme_err = gme_track_info(emu, &ti, 0)) != NULL){
|
||||
if((gme_err = gme_track_info(emu, &ti, song_num)) != NULL){
|
||||
g_warning("%s", gme_err);
|
||||
gme_delete(emu);
|
||||
return NULL;
|
||||
@@ -106,8 +203,16 @@ gme_tag_dup(const char *path_fs)
|
||||
if(ti != NULL){
|
||||
if(ti->length > 0)
|
||||
tag->time = ti->length / 1000;
|
||||
if(ti->song != NULL)
|
||||
tag_add_item(tag, TAG_TITLE, ti->song);
|
||||
if(ti->song != NULL){
|
||||
if(gme_track_count(emu) > 1){
|
||||
/* start numbering subtunes from 1 */
|
||||
char *tag_title=g_strdup_printf("%s (%d/%d)",
|
||||
ti->song, song_num+1, gme_track_count(emu));
|
||||
tag_add_item(tag, TAG_TITLE, tag_title);
|
||||
g_free(tag_title);
|
||||
}else
|
||||
tag_add_item(tag, TAG_TITLE, ti->song);
|
||||
}
|
||||
if(ti->author != NULL)
|
||||
tag_add_item(tag, TAG_ARTIST, ti->author);
|
||||
if(ti->game != NULL)
|
||||
@@ -135,4 +240,5 @@ const struct decoder_plugin gme_decoder_plugin = {
|
||||
.file_decode = gme_file_decode,
|
||||
.tag_dup = gme_tag_dup,
|
||||
.suffixes = gme_suffixes,
|
||||
.container_scan = gme_container_scan,
|
||||
};
|
||||
|
@@ -285,10 +285,10 @@ parse_id3_mixramp(char **mixramp_start, char **mixramp_end,
|
||||
(&frame->fields[2]));
|
||||
|
||||
if (g_ascii_strcasecmp(key, "mixramp_start") == 0) {
|
||||
*mixramp_start = strdup(value);
|
||||
*mixramp_start = g_strdup(value);
|
||||
found = true;
|
||||
} else if (g_ascii_strcasecmp(key, "mixramp_end") == 0) {
|
||||
*mixramp_end = strdup(value);
|
||||
*mixramp_end = g_strdup(value);
|
||||
found = true;
|
||||
}
|
||||
|
||||
@@ -547,14 +547,14 @@ enum {
|
||||
XING_SCALE = 0x00000008L
|
||||
};
|
||||
|
||||
struct version {
|
||||
struct lame_version {
|
||||
unsigned major;
|
||||
unsigned minor;
|
||||
};
|
||||
|
||||
struct lame {
|
||||
char encoder[10]; /* 9 byte encoder name/version ("LAME3.97b") */
|
||||
struct version version; /* struct containing just the version */
|
||||
struct lame_version version; /* struct containing just the version */
|
||||
float peak; /* replaygain peak */
|
||||
float track_gain; /* replaygain track gain */
|
||||
float album_gain; /* replaygain album gain */
|
||||
|
@@ -19,6 +19,7 @@
|
||||
|
||||
#include "config.h"
|
||||
#include "decoder_api.h"
|
||||
#include "mpd_error.h"
|
||||
|
||||
#include <glib.h>
|
||||
#include <mikmod.h>
|
||||
@@ -110,8 +111,8 @@ mikmod_decoder_init(const struct config_param *param)
|
||||
mikmod_sample_rate = config_get_block_unsigned(param, "sample_rate",
|
||||
44100);
|
||||
if (!audio_valid_sample_rate(mikmod_sample_rate))
|
||||
g_error("Invalid sample rate in line %d: %u",
|
||||
param->line, mikmod_sample_rate);
|
||||
MPD_ERROR("Invalid sample rate in line %d: %u",
|
||||
param->line, mikmod_sample_rate);
|
||||
|
||||
md_device = 0;
|
||||
md_reverb = 0;
|
||||
|
@@ -362,6 +362,10 @@ mp4ff_tag_name_parse(const char *name)
|
||||
if (type == TAG_NUM_OF_ITEM_TYPES)
|
||||
type = tag_name_parse_i(name);
|
||||
|
||||
if (g_ascii_strcasecmp(name, "albumartist") == 0 ||
|
||||
g_ascii_strcasecmp(name, "album_artist") == 0)
|
||||
return TAG_ALBUM_ARTIST;
|
||||
|
||||
return type;
|
||||
}
|
||||
|
||||
@@ -413,7 +417,13 @@ mp4_stream_tag(struct input_stream *is)
|
||||
return tag;
|
||||
}
|
||||
|
||||
static const char *const mp4_suffixes[] = { "m4a", "mp4", NULL };
|
||||
static const char *const mp4_suffixes[] = {
|
||||
"m4a",
|
||||
"m4b",
|
||||
"mp4",
|
||||
NULL
|
||||
};
|
||||
|
||||
static const char *const mp4_mime_types[] = { "audio/mp4", "audio/m4a", NULL };
|
||||
|
||||
const struct decoder_plugin mp4ff_decoder_plugin = {
|
||||
|
@@ -201,6 +201,7 @@ static void
|
||||
sidplay_file_decode(struct decoder *decoder, const char *path_fs)
|
||||
{
|
||||
int ret;
|
||||
int channels;
|
||||
|
||||
/* load the tune */
|
||||
|
||||
@@ -256,7 +257,7 @@ sidplay_file_decode(struct decoder *decoder, const char *path_fs)
|
||||
config.clockSpeed = SID2_CLOCK_CORRECT;
|
||||
config.frequency = 48000;
|
||||
config.optimisation = SID2_DEFAULT_OPTIMISATION;
|
||||
config.playback = sid2_stereo;
|
||||
|
||||
config.precision = 16;
|
||||
config.sidDefault = SID2_MOS6581;
|
||||
config.sidEmulation = &builder;
|
||||
@@ -267,6 +268,13 @@ sidplay_file_decode(struct decoder *decoder, const char *path_fs)
|
||||
#else
|
||||
config.sampleFormat = SID2_BIG_SIGNED;
|
||||
#endif
|
||||
if (tune.isStereo()) {
|
||||
config.playback = sid2_stereo;
|
||||
channels = 2;
|
||||
} else {
|
||||
config.playback = sid2_mono;
|
||||
channels = 1;
|
||||
}
|
||||
|
||||
iret = player.config(config);
|
||||
if (iret != 0) {
|
||||
@@ -277,7 +285,7 @@ sidplay_file_decode(struct decoder *decoder, const char *path_fs)
|
||||
/* initialize the MPD decoder */
|
||||
|
||||
struct audio_format audio_format;
|
||||
audio_format_init(&audio_format, 48000, SAMPLE_FORMAT_S16, 2);
|
||||
audio_format_init(&audio_format, 48000, SAMPLE_FORMAT_S16, channels);
|
||||
assert(audio_format_valid(&audio_format));
|
||||
|
||||
decoder_initialized(decoder, &audio_format, true, (float)song_len);
|
||||
@@ -399,6 +407,10 @@ sidplay_container_scan(const char *path_fs, const unsigned int tnum)
|
||||
|
||||
static const char *const sidplay_suffixes[] = {
|
||||
"sid",
|
||||
"mus",
|
||||
"str",
|
||||
"prg",
|
||||
"P00",
|
||||
NULL
|
||||
};
|
||||
|
||||
|
@@ -20,9 +20,9 @@
|
||||
#include "config.h"
|
||||
#include "decoder_control.h"
|
||||
#include "player_control.h"
|
||||
#include "pipe.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <malloc.h>
|
||||
|
||||
#undef G_LOG_DOMAIN
|
||||
#define G_LOG_DOMAIN "decoder_control"
|
||||
@@ -50,12 +50,9 @@ dc_deinit(struct decoder_control *dc)
|
||||
{
|
||||
g_cond_free(dc->cond);
|
||||
g_mutex_free(dc->mutex);
|
||||
if (dc->mixramp_start)
|
||||
free(dc->mixramp_start);
|
||||
if (dc->mixramp_end)
|
||||
free(dc->mixramp_end);
|
||||
if (dc->mixramp_prev_end)
|
||||
free(dc->mixramp_prev_end);
|
||||
g_free(dc->mixramp_start);
|
||||
g_free(dc->mixramp_end);
|
||||
g_free(dc->mixramp_prev_end);
|
||||
dc->mixramp_start = NULL;
|
||||
dc->mixramp_end = NULL;
|
||||
dc->mixramp_prev_end = NULL;
|
||||
@@ -110,6 +107,7 @@ dc_start(struct decoder_control *dc, struct song *song,
|
||||
assert(song != NULL);
|
||||
assert(buffer != NULL);
|
||||
assert(pipe != NULL);
|
||||
assert(music_pipe_empty(pipe));
|
||||
|
||||
dc->song = song;
|
||||
dc->buffer = buffer;
|
||||
@@ -172,8 +170,7 @@ dc_mixramp_start(struct decoder_control *dc, char *mixramp_start)
|
||||
{
|
||||
assert(dc != NULL);
|
||||
|
||||
if (dc->mixramp_start)
|
||||
free(dc->mixramp_start);
|
||||
g_free(dc->mixramp_start);
|
||||
dc->mixramp_start = mixramp_start;
|
||||
g_debug("mixramp_start = %s", mixramp_start ? mixramp_start : "NULL");
|
||||
}
|
||||
@@ -183,8 +180,7 @@ dc_mixramp_end(struct decoder_control *dc, char *mixramp_end)
|
||||
{
|
||||
assert(dc != NULL);
|
||||
|
||||
if (dc->mixramp_end)
|
||||
free(dc->mixramp_end);
|
||||
g_free(dc->mixramp_end);
|
||||
dc->mixramp_end = mixramp_end;
|
||||
g_debug("mixramp_end = %s", mixramp_end ? mixramp_end : "NULL");
|
||||
}
|
||||
@@ -194,8 +190,7 @@ dc_mixramp_prev_end(struct decoder_control *dc, char *mixramp_prev_end)
|
||||
{
|
||||
assert(dc != NULL);
|
||||
|
||||
if (dc->mixramp_prev_end)
|
||||
free(dc->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");
|
||||
}
|
||||
|
@@ -22,6 +22,7 @@
|
||||
#include "decoder_plugin.h"
|
||||
#include "utils.h"
|
||||
#include "conf.h"
|
||||
#include "mpd_error.h"
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
@@ -198,8 +199,8 @@ decoder_plugin_config(const char *plugin_name)
|
||||
const char *name =
|
||||
config_get_block_string(param, "plugin", NULL);
|
||||
if (name == NULL)
|
||||
g_error("decoder configuration without 'plugin' name in line %d",
|
||||
param->line);
|
||||
MPD_ERROR("decoder configuration without 'plugin' name in line %d",
|
||||
param->line);
|
||||
|
||||
if (strcmp(name, plugin_name) == 0)
|
||||
return param;
|
||||
|
@@ -24,6 +24,7 @@
|
||||
#include "decoder_list.h"
|
||||
#include "decoder_plugin.h"
|
||||
#include "decoder_api.h"
|
||||
#include "replay_gain_ape.h"
|
||||
#include "input_stream.h"
|
||||
#include "player_control.h"
|
||||
#include "pipe.h"
|
||||
@@ -32,6 +33,7 @@
|
||||
#include "mapper.h"
|
||||
#include "path.h"
|
||||
#include "uri.h"
|
||||
#include "mpd_error.h"
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
@@ -296,6 +298,18 @@ decoder_run_stream(struct decoder *decoder, const char *uri)
|
||||
return success;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to load replay gain data, and pass it to
|
||||
* decoder_replay_gain().
|
||||
*/
|
||||
static void
|
||||
decoder_load_replay_gain(struct decoder *decoder, const char *path_fs)
|
||||
{
|
||||
struct replay_gain_info info;
|
||||
if (replay_gain_ape_read(path_fs, &info))
|
||||
decoder_replay_gain(decoder, &info);
|
||||
}
|
||||
|
||||
/**
|
||||
* Try decoding a file.
|
||||
*/
|
||||
@@ -311,6 +325,8 @@ decoder_run_file(struct decoder *decoder, const char *path_fs)
|
||||
|
||||
decoder_unlock(dc);
|
||||
|
||||
decoder_load_replay_gain(decoder, path_fs);
|
||||
|
||||
while ((plugin = decoder_plugin_from_suffix(suffix, plugin)) != NULL) {
|
||||
if (plugin->file_decode != NULL) {
|
||||
decoder_lock(dc);
|
||||
@@ -479,5 +495,5 @@ decoder_thread_start(struct decoder_control *dc)
|
||||
|
||||
dc->thread = g_thread_create(decoder_task, dc, true, &e);
|
||||
if (dc->thread == NULL)
|
||||
g_error("Failed to spawn decoder task: %s", e->message);
|
||||
MPD_ERROR("Failed to spawn decoder task: %s", e->message);
|
||||
}
|
||||
|
@@ -30,8 +30,8 @@
|
||||
|
||||
#define DIRECTORY_DIR "directory: "
|
||||
|
||||
#define DEVICE_INARCHIVE (unsigned)(-1)
|
||||
#define DEVICE_CONTAINER (unsigned)(-2)
|
||||
#define DEVICE_INARCHIVE (dev_t)(-1)
|
||||
#define DEVICE_CONTAINER (dev_t)(-2)
|
||||
|
||||
struct directory {
|
||||
struct dirvec children;
|
||||
|
@@ -170,6 +170,13 @@ lame_encoder_setup(struct lame_encoder *encoder, GError **error)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (0 != lame_set_out_samplerate(encoder->gfp,
|
||||
encoder->audio_format.sample_rate)) {
|
||||
g_set_error(error, lame_encoder_quark(), 0,
|
||||
"error setting lame out sample rate");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (0 > lame_init_params(encoder->gfp)) {
|
||||
g_set_error(error, lame_encoder_quark(), 0,
|
||||
"error initializing lame params");
|
||||
|
@@ -22,6 +22,7 @@
|
||||
#include "encoder_plugin.h"
|
||||
#include "tag.h"
|
||||
#include "audio_format.h"
|
||||
#include "mpd_error.h"
|
||||
|
||||
#include <vorbis/vorbisenc.h>
|
||||
|
||||
@@ -374,7 +375,7 @@ vorbis_encoder_read(struct encoder *_encoder, void *_dest, size_t length)
|
||||
|
||||
if (nbytes > length)
|
||||
/* XXX better error handling */
|
||||
g_error("buffer too small");
|
||||
MPD_ERROR("buffer too small");
|
||||
|
||||
memcpy(dest, page.header, page.header_len);
|
||||
memcpy(dest + page.header_len, page.body, page.body_len);
|
||||
@@ -385,7 +386,7 @@ vorbis_encoder_read(struct encoder *_encoder, void *_dest, size_t length)
|
||||
static const char *
|
||||
vorbis_encoder_get_mime_type(G_GNUC_UNUSED struct encoder *_encoder)
|
||||
{
|
||||
return "application/x-ogg";
|
||||
return "audio/ogg";
|
||||
}
|
||||
|
||||
const struct encoder_plugin vorbis_encoder_plugin = {
|
||||
|
@@ -20,6 +20,7 @@
|
||||
#include "config.h"
|
||||
#include "event_pipe.h"
|
||||
#include "fd_util.h"
|
||||
#include "mpd_error.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <assert.h>
|
||||
@@ -68,7 +69,7 @@ main_notify_event(G_GNUC_UNUSED GIOChannel *source,
|
||||
buffer, sizeof(buffer),
|
||||
&bytes_read, &error);
|
||||
if (status == G_IO_STATUS_ERROR)
|
||||
g_error("error reading from pipe: %s", error->message);
|
||||
MPD_ERROR("error reading from pipe: %s", error->message);
|
||||
|
||||
bool events[PIPE_EVENT_MAX];
|
||||
g_mutex_lock(event_pipe_mutex);
|
||||
@@ -91,7 +92,7 @@ void event_pipe_init(void)
|
||||
|
||||
ret = pipe_cloexec_nonblock(event_pipe);
|
||||
if (ret < 0)
|
||||
g_error("Couldn't open pipe: %s", strerror(errno));
|
||||
MPD_ERROR("Couldn't open pipe: %s", strerror(errno));
|
||||
|
||||
#ifndef G_OS_WIN32
|
||||
channel = g_io_channel_unix_new(event_pipe[0]);
|
||||
@@ -116,7 +117,10 @@ void event_pipe_deinit(void)
|
||||
g_source_remove(event_pipe_source_id);
|
||||
g_io_channel_unref(event_channel);
|
||||
|
||||
#ifndef WIN32
|
||||
/* By some strange reason this call hangs on Win32 */
|
||||
close(event_pipe[0]);
|
||||
#endif
|
||||
close(event_pipe[1]);
|
||||
}
|
||||
|
||||
@@ -147,7 +151,7 @@ void event_pipe_emit(enum pipe_event event)
|
||||
|
||||
w = write(event_pipe[1], "", 1);
|
||||
if (w < 0 && errno != EAGAIN && errno != EINTR)
|
||||
g_error("error writing to pipe: %s", strerror(errno));
|
||||
MPD_ERROR("error writing to pipe: %s", strerror(errno));
|
||||
}
|
||||
|
||||
void event_pipe_emit_fast(enum pipe_event event)
|
||||
|
@@ -44,6 +44,9 @@ enum pipe_event {
|
||||
/** a hardware mixer plugin has detected a change */
|
||||
PIPE_EVENT_MIXER,
|
||||
|
||||
/** shutdown requested */
|
||||
PIPE_EVENT_SHUTDOWN,
|
||||
|
||||
PIPE_EVENT_MAX
|
||||
};
|
||||
|
||||
|
@@ -103,6 +103,16 @@ fd_set_nonblock(int fd)
|
||||
#endif
|
||||
}
|
||||
|
||||
int
|
||||
dup_cloexec(int oldfd)
|
||||
{
|
||||
int newfd = dup(oldfd);
|
||||
if (newfd >= 0)
|
||||
fd_set_nonblock(newfd);
|
||||
|
||||
return newfd;
|
||||
}
|
||||
|
||||
int
|
||||
open_cloexec(const char *path_fs, int flags, int mode)
|
||||
{
|
||||
@@ -174,6 +184,30 @@ pipe_cloexec_nonblock(int fd[2])
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifndef WIN32
|
||||
|
||||
int
|
||||
socketpair_cloexec(int domain, int type, int protocol, int sv[2])
|
||||
{
|
||||
int ret;
|
||||
|
||||
#ifdef SOCK_CLOEXEC
|
||||
ret = socketpair(domain, type | SOCK_CLOEXEC, protocol, sv);
|
||||
if (ret >= 0 || errno != EINVAL)
|
||||
return ret;
|
||||
#endif
|
||||
|
||||
ret = socketpair(domain, type, protocol, sv);
|
||||
if (ret >= 0) {
|
||||
fd_set_cloexec(sv[0], true);
|
||||
fd_set_cloexec(sv[1], true);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
int
|
||||
socket_cloexec_nonblock(int domain, int type, int protocol)
|
||||
{
|
||||
@@ -222,6 +256,33 @@ accept_cloexec_nonblock(int fd, struct sockaddr *address,
|
||||
return ret;
|
||||
}
|
||||
|
||||
#ifndef WIN32
|
||||
|
||||
ssize_t
|
||||
recvmsg_cloexec(int sockfd, struct msghdr *msg, int flags)
|
||||
{
|
||||
#ifdef MSG_CMSG_CLOEXEC
|
||||
flags |= MSG_CMSG_CLOEXEC;
|
||||
#endif
|
||||
|
||||
ssize_t result = recvmsg(sockfd, msg, flags);
|
||||
if (result >= 0) {
|
||||
struct cmsghdr *cmsg = CMSG_FIRSTHDR(msg);
|
||||
while (cmsg != NULL) {
|
||||
if (cmsg->cmsg_type == SCM_RIGHTS) {
|
||||
const int *fd_p = (const int *)CMSG_DATA(cmsg);
|
||||
fd_set_cloexec(*fd_p, true);
|
||||
}
|
||||
|
||||
cmsg = CMSG_NXTHDR(msg, cmsg);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_INOTIFY_INIT
|
||||
|
||||
int
|
||||
|
@@ -39,8 +39,23 @@
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#ifndef WIN32
|
||||
#if !defined(_GNU_SOURCE) && (defined(HAVE_PIPE2) || defined(HAVE_ACCEPT4))
|
||||
#define _GNU_SOURCE
|
||||
#endif
|
||||
|
||||
#include <sys/types.h>
|
||||
#endif
|
||||
|
||||
struct sockaddr;
|
||||
|
||||
/**
|
||||
* Wrapper for dup(), which sets the CLOEXEC flag on the new
|
||||
* descriptor.
|
||||
*/
|
||||
int
|
||||
dup_cloexec(int oldfd);
|
||||
|
||||
/**
|
||||
* Wrapper for open(), which sets the CLOEXEC flag (atomically if
|
||||
* supported by the OS).
|
||||
@@ -65,6 +80,17 @@ pipe_cloexec(int fd[2]);
|
||||
int
|
||||
pipe_cloexec_nonblock(int fd[2]);
|
||||
|
||||
#ifndef WIN32
|
||||
|
||||
/**
|
||||
* Wrapper for socketpair(), which sets the CLOEXEC flag (atomically
|
||||
* if supported by the OS).
|
||||
*/
|
||||
int
|
||||
socketpair_cloexec(int domain, int type, int protocol, int sv[2]);
|
||||
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Wrapper for socket(), which sets the CLOEXEC and the NONBLOCK flag
|
||||
* (atomically if supported by the OS).
|
||||
@@ -80,6 +106,20 @@ int
|
||||
accept_cloexec_nonblock(int fd, struct sockaddr *address,
|
||||
size_t *address_length_r);
|
||||
|
||||
|
||||
#ifndef WIN32
|
||||
|
||||
struct msghdr;
|
||||
|
||||
/**
|
||||
* Wrapper for recvmsg(), which sets the CLOEXEC flag (atomically if
|
||||
* supported by the OS).
|
||||
*/
|
||||
ssize_t
|
||||
recvmsg_cloexec(int sockfd, struct msghdr *msg, int flags);
|
||||
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Wrapper for inotify_init(), which sets the CLOEXEC flag (atomically
|
||||
* if supported by the OS).
|
||||
|
@@ -55,8 +55,16 @@ struct replay_gain_filter {
|
||||
struct replay_gain_info info;
|
||||
|
||||
/**
|
||||
* The current volume, between 0 and #PCM_VOLUME_1 (both
|
||||
* including).
|
||||
* The current volume, between 0 and a value that may or may not exceed
|
||||
* #PCM_VOLUME_1.
|
||||
*
|
||||
* If the default value of true is used for replaygain_limit, the
|
||||
* application of the volume to the signal will never cause clipping.
|
||||
*
|
||||
* On the other hand, if the user has set replaygain_limit to false,
|
||||
* the chance of clipping is explicitly preferred if that's required to
|
||||
* maintain a consistent audio level. Whether clipping will actually
|
||||
* occur depends on what value the user is using for replaygain_preamp.
|
||||
*/
|
||||
unsigned volume;
|
||||
|
||||
@@ -171,7 +179,7 @@ replay_gain_filter_filter(struct filter *_filter,
|
||||
|
||||
*dest_size_r = src_size;
|
||||
|
||||
if (filter->volume >= PCM_VOLUME_1)
|
||||
if (filter->volume == PCM_VOLUME_1)
|
||||
/* optimized special case: 100% volume = no-op */
|
||||
return src;
|
||||
|
||||
|
@@ -96,6 +96,7 @@ icy_server_metadata_page(const struct tag *tag, ...)
|
||||
gchar stream_title[(1 + 255 - 28) * 16]; // Length + Metadata -
|
||||
// "StreamTitle='';StreamUrl='';"
|
||||
// = 4081 - 28
|
||||
stream_title[0] = '\0';
|
||||
|
||||
last_item = -1;
|
||||
|
||||
|
@@ -21,6 +21,7 @@
|
||||
#include "inotify_source.h"
|
||||
#include "fifo_buffer.h"
|
||||
#include "fd_util.h"
|
||||
#include "mpd_error.h"
|
||||
|
||||
#include <sys/inotify.h>
|
||||
#include <unistd.h>
|
||||
@@ -68,13 +69,14 @@ mpd_inotify_in_event(G_GNUC_UNUSED GIOChannel *_source,
|
||||
|
||||
dest = fifo_buffer_write(source->buffer, &length);
|
||||
if (dest == NULL)
|
||||
g_error("buffer full");
|
||||
MPD_ERROR("buffer full");
|
||||
|
||||
nbytes = read(source->fd, dest, length);
|
||||
if (nbytes < 0)
|
||||
g_error("failed to read from inotify: %s", g_strerror(errno));
|
||||
MPD_ERROR("failed to read from inotify: %s",
|
||||
g_strerror(errno));
|
||||
if (nbytes == 0)
|
||||
g_error("end of file from inotify");
|
||||
MPD_ERROR("end of file from inotify");
|
||||
|
||||
fifo_buffer_append(source->buffer, nbytes);
|
||||
|
||||
|
@@ -264,7 +264,7 @@ input_curl_select(struct input_curl *c, GError **error_r)
|
||||
return -1;
|
||||
}
|
||||
|
||||
#if LIBCURL_VERSION_NUM >= 0x070f00
|
||||
#if LIBCURL_VERSION_NUM >= 0x070f04
|
||||
long timeout2;
|
||||
mcode = curl_multi_timeout(c->multi, &timeout2);
|
||||
if (mcode != CURLM_OK) {
|
||||
|
@@ -80,15 +80,18 @@ copy_attributes(struct input_rewind *r)
|
||||
struct input_stream *dest = &r->base;
|
||||
const struct input_stream *src = r->input;
|
||||
|
||||
assert(dest != src);
|
||||
assert(src->mime == NULL || dest->mime != src->mime);
|
||||
|
||||
dest->ready = src->ready;
|
||||
dest->seekable = src->seekable;
|
||||
dest->size = src->size;
|
||||
dest->offset = src->offset;
|
||||
|
||||
if (dest->mime == NULL && src->mime != NULL)
|
||||
/* this is set only once, and the duplicated pointer
|
||||
is freed by input_stream_close() */
|
||||
if (src->mime != NULL) {
|
||||
g_free(dest->mime);
|
||||
dest->mime = g_strdup(src->mime);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
|
372
src/listen.c
372
src/listen.c
@@ -19,333 +19,44 @@
|
||||
|
||||
#include "config.h"
|
||||
#include "listen.h"
|
||||
#include "socket_util.h"
|
||||
#include "server_socket.h"
|
||||
#include "client.h"
|
||||
#include "conf.h"
|
||||
#include "fd_util.h"
|
||||
#include "glib_compat.h"
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <unistd.h>
|
||||
#include <stdlib.h>
|
||||
#include <assert.h>
|
||||
|
||||
#ifdef WIN32
|
||||
#define WINVER 0x0501
|
||||
#include <ws2tcpip.h>
|
||||
#include <winsock.h>
|
||||
#else
|
||||
#include <netinet/in.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/un.h>
|
||||
#include <netdb.h>
|
||||
#endif
|
||||
|
||||
#undef G_LOG_DOMAIN
|
||||
#define G_LOG_DOMAIN "listen"
|
||||
|
||||
#define DEFAULT_PORT 6600
|
||||
|
||||
struct listen_socket {
|
||||
struct listen_socket *next;
|
||||
|
||||
int fd;
|
||||
|
||||
guint source_id;
|
||||
};
|
||||
|
||||
static struct listen_socket *listen_sockets;
|
||||
static struct server_socket *listen_socket;
|
||||
int listen_port;
|
||||
|
||||
static GQuark
|
||||
listen_quark(void)
|
||||
static void
|
||||
listen_callback(int fd, const struct sockaddr *address,
|
||||
size_t address_length, int uid, G_GNUC_UNUSED void *ctx)
|
||||
{
|
||||
return g_quark_from_static_string("listen");
|
||||
client_new(fd, address, address_length, uid);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
listen_in_event(GIOChannel *source, GIOCondition condition, gpointer data);
|
||||
|
||||
static bool
|
||||
listen_add_address(int pf, const struct sockaddr *addrp, socklen_t addrlen,
|
||||
GError **error)
|
||||
{
|
||||
char *address_string;
|
||||
int fd;
|
||||
struct listen_socket *ls;
|
||||
GIOChannel *channel;
|
||||
|
||||
address_string = sockaddr_to_string(addrp, addrlen, NULL);
|
||||
if (address_string != NULL) {
|
||||
g_debug("binding to socket address %s", address_string);
|
||||
g_free(address_string);
|
||||
}
|
||||
|
||||
fd = socket_bind_listen(pf, SOCK_STREAM, 0, addrp, addrlen, 5, error);
|
||||
if (fd < 0)
|
||||
return false;
|
||||
|
||||
ls = g_new(struct listen_socket, 1);
|
||||
ls->fd = fd;
|
||||
|
||||
channel = g_io_channel_unix_new(fd);
|
||||
ls->source_id = g_io_add_watch(channel, G_IO_IN,
|
||||
listen_in_event, GINT_TO_POINTER(fd));
|
||||
g_io_channel_unref(channel);
|
||||
|
||||
ls->next = listen_sockets;
|
||||
listen_sockets = ls;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#ifdef HAVE_TCP
|
||||
|
||||
/**
|
||||
* Add a listener on a port on all IPv4 interfaces.
|
||||
*
|
||||
* @param port the TCP port
|
||||
* @param error location to store the error occuring, or NULL to ignore errors
|
||||
* @return true on success
|
||||
*/
|
||||
static bool
|
||||
listen_add_port_ipv4(unsigned int port, GError **error)
|
||||
{
|
||||
struct sockaddr_in sin;
|
||||
const struct sockaddr *addrp = (const struct sockaddr *)&sin;
|
||||
socklen_t addrlen = sizeof(sin);
|
||||
|
||||
memset(&sin, 0, sizeof(sin));
|
||||
sin.sin_port = htons(port);
|
||||
sin.sin_family = AF_INET;
|
||||
sin.sin_addr.s_addr = INADDR_ANY;
|
||||
|
||||
return listen_add_address(PF_INET, addrp, addrlen, error);
|
||||
}
|
||||
|
||||
#ifdef HAVE_IPV6
|
||||
/**
|
||||
* Add a listener on a port on all IPv6 interfaces.
|
||||
*
|
||||
* @param port the TCP port
|
||||
* @param error location to store the error occuring, or NULL to ignore errors
|
||||
* @return true on success
|
||||
*/
|
||||
static bool
|
||||
listen_add_port_ipv6(unsigned int port, GError **error)
|
||||
{
|
||||
struct sockaddr_in6 sin;
|
||||
const struct sockaddr *addrp = (const struct sockaddr *)&sin;
|
||||
socklen_t addrlen = sizeof(sin);
|
||||
|
||||
memset(&sin, 0, sizeof(sin));
|
||||
sin.sin6_port = htons(port);
|
||||
sin.sin6_family = AF_INET6;
|
||||
|
||||
return listen_add_address(PF_INET6, addrp, addrlen, error);
|
||||
}
|
||||
#endif /* HAVE_IPV6 */
|
||||
|
||||
#endif /* HAVE_TCP */
|
||||
|
||||
/**
|
||||
* Add a listener on a port on all interfaces.
|
||||
*
|
||||
* @param port the TCP port
|
||||
* @param error location to store the error occuring, or NULL to ignore errors
|
||||
* @return true on success
|
||||
*/
|
||||
static bool
|
||||
listen_add_port(unsigned int port, GError **error)
|
||||
{
|
||||
#ifdef HAVE_TCP
|
||||
bool success;
|
||||
#ifdef HAVE_IPV6
|
||||
bool success6;
|
||||
GError *error2 = NULL;
|
||||
#endif
|
||||
|
||||
g_debug("binding to any address");
|
||||
|
||||
#ifdef HAVE_IPV6
|
||||
success6 = listen_add_port_ipv6(port, &error2);
|
||||
if (!success6) {
|
||||
if (error2->domain != listen_quark() ||
|
||||
(error2->code != EAFNOSUPPORT && error2->code != EINVAL &&
|
||||
error2->code != EPROTONOSUPPORT)) {
|
||||
g_propagate_error(error, error2);
|
||||
return false;
|
||||
}
|
||||
|
||||
/* although MPD was compiled with IPv6 support, this
|
||||
host does not have it - ignore this error */
|
||||
g_error_free(error2);
|
||||
}
|
||||
#endif
|
||||
|
||||
success = listen_add_port_ipv4(port, error);
|
||||
if (!success) {
|
||||
#ifdef HAVE_IPV6
|
||||
if (success6)
|
||||
/* non-critical: IPv6 listener is
|
||||
already set up */
|
||||
g_clear_error(error);
|
||||
else
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
#else /* HAVE_TCP */
|
||||
(void)port;
|
||||
|
||||
g_set_error(error, listen_quark(), 0,
|
||||
"TCP support is disabled");
|
||||
return false;
|
||||
#endif /* HAVE_TCP */
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves a host name, and adds listeners on all addresses in the
|
||||
* result set.
|
||||
*
|
||||
* @param hostname the host name to be resolved
|
||||
* @param port the TCP port
|
||||
* @param error location to store the error occuring, or NULL to ignore errors
|
||||
* @return true on success
|
||||
*/
|
||||
static bool
|
||||
listen_add_host(const char *hostname, unsigned port, GError **error_r)
|
||||
{
|
||||
#ifdef HAVE_TCP
|
||||
struct addrinfo hints, *ai, *i;
|
||||
char service[20];
|
||||
int ret;
|
||||
bool success;
|
||||
|
||||
g_debug("binding to address for %s", hostname);
|
||||
|
||||
memset(&hints, 0, sizeof(hints));
|
||||
hints.ai_flags = AI_PASSIVE;
|
||||
#ifdef AI_ADDRCONFIG
|
||||
hints.ai_flags |= AI_ADDRCONFIG;
|
||||
#endif
|
||||
hints.ai_family = PF_UNSPEC;
|
||||
hints.ai_socktype = SOCK_STREAM;
|
||||
hints.ai_protocol = IPPROTO_TCP;
|
||||
|
||||
g_snprintf(service, sizeof(service), "%u", port);
|
||||
|
||||
ret = getaddrinfo(hostname, service, &hints, &ai);
|
||||
if (ret != 0) {
|
||||
g_set_error(error_r, listen_quark(), ret,
|
||||
"Failed to look up host \"%s\": %s",
|
||||
hostname, gai_strerror(ret));
|
||||
return false;
|
||||
}
|
||||
|
||||
for (i = ai; i != NULL; i = i->ai_next) {
|
||||
GError *error = NULL;
|
||||
|
||||
success = listen_add_address(i->ai_family, i->ai_addr,
|
||||
i->ai_addrlen, &error);
|
||||
if (!success) {
|
||||
if (i == ai) {
|
||||
/* first bind has failed: fatal
|
||||
error */
|
||||
g_propagate_error(error_r, error);
|
||||
return false;
|
||||
} else {
|
||||
char *address_string =
|
||||
sockaddr_to_string(i->ai_addr,
|
||||
i->ai_addrlen,
|
||||
NULL);
|
||||
if (address_string == NULL)
|
||||
address_string = g_strdup("[unknown]");
|
||||
|
||||
g_warning("bind to %s failed: %s "
|
||||
"(continuing anyway, because at "
|
||||
"least one address is bound)",
|
||||
address_string, error->message);
|
||||
g_free(address_string);
|
||||
g_error_free(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
freeaddrinfo(ai);
|
||||
|
||||
return true;
|
||||
#else /* HAVE_TCP */
|
||||
|
||||
(void)hostname;
|
||||
(void)port;
|
||||
|
||||
g_set_error(error_r, listen_quark(), 0,
|
||||
"TCP support is disabled");
|
||||
return false;
|
||||
#endif /* HAVE_TCP */
|
||||
}
|
||||
|
||||
#ifdef HAVE_UN
|
||||
/**
|
||||
* Add a listener on a Unix domain socket.
|
||||
*
|
||||
* @param path the absolute socket path
|
||||
* @param error location to store the error occuring, or NULL to ignore errors
|
||||
* @return true on success
|
||||
*/
|
||||
static bool
|
||||
listen_add_path(const char *path, GError **error)
|
||||
{
|
||||
size_t path_length;
|
||||
struct sockaddr_un s_un;
|
||||
const struct sockaddr *addrp = (const struct sockaddr *)&s_un;
|
||||
socklen_t addrlen = sizeof(s_un);
|
||||
bool success;
|
||||
|
||||
path_length = strlen(path);
|
||||
if (path_length >= sizeof(s_un.sun_path)) {
|
||||
g_set_error(error, listen_quark(), 0,
|
||||
"unix socket path is too long");
|
||||
return false;
|
||||
}
|
||||
|
||||
unlink(path);
|
||||
|
||||
s_un.sun_family = AF_UNIX;
|
||||
memcpy(s_un.sun_path, path, path_length + 1);
|
||||
|
||||
success = listen_add_address(PF_UNIX, addrp, addrlen, error);
|
||||
if (!success)
|
||||
return false;
|
||||
|
||||
/* allow everybody to connect */
|
||||
chmod(path, 0666);
|
||||
|
||||
return true;
|
||||
}
|
||||
#endif /* HAVE_UN */
|
||||
|
||||
static bool
|
||||
listen_add_config_param(unsigned int port,
|
||||
const struct config_param *param,
|
||||
GError **error)
|
||||
GError **error_r)
|
||||
{
|
||||
assert(param != NULL);
|
||||
|
||||
if (0 == strcmp(param->value, "any")) {
|
||||
return listen_add_port(port, error);
|
||||
#ifdef HAVE_UN
|
||||
return server_socket_add_port(listen_socket, port, error_r);
|
||||
} else if (param->value[0] == '/') {
|
||||
return listen_add_path(param->value, error);
|
||||
#endif /* HAVE_UN */
|
||||
return server_socket_add_path(listen_socket, param->value,
|
||||
error_r);
|
||||
} else {
|
||||
return listen_add_host(param->value, port, error);
|
||||
return server_socket_add_host(listen_socket, param->value,
|
||||
port, error_r);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -358,6 +69,8 @@ listen_global_init(GError **error_r)
|
||||
bool success;
|
||||
GError *error = NULL;
|
||||
|
||||
listen_socket = server_socket_new(listen_callback, NULL);
|
||||
|
||||
if (param != NULL) {
|
||||
/* "bind_to_address" is configured, create listeners
|
||||
for all values */
|
||||
@@ -378,7 +91,7 @@ listen_global_init(GError **error_r)
|
||||
/* no "bind_to_address" configured, bind the
|
||||
configured port on all interfaces */
|
||||
|
||||
success = listen_add_port(port, &error);
|
||||
success = server_socket_add_port(listen_socket, port, error_r);
|
||||
if (!success) {
|
||||
g_propagate_prefixed_error(error_r, error,
|
||||
"Failed to listen on *:%d: ",
|
||||
@@ -387,6 +100,9 @@ listen_global_init(GError **error_r)
|
||||
}
|
||||
}
|
||||
|
||||
if (!server_socket_open(listen_socket, error_r))
|
||||
return false;
|
||||
|
||||
listen_port = port;
|
||||
return true;
|
||||
}
|
||||
@@ -395,55 +111,7 @@ void listen_global_finish(void)
|
||||
{
|
||||
g_debug("listen_global_finish called");
|
||||
|
||||
while (listen_sockets != NULL) {
|
||||
struct listen_socket *ls = listen_sockets;
|
||||
listen_sockets = ls->next;
|
||||
assert(listen_socket != NULL);
|
||||
|
||||
g_source_remove(ls->source_id);
|
||||
close(ls->fd);
|
||||
g_free(ls);
|
||||
}
|
||||
}
|
||||
|
||||
static int get_remote_uid(int fd)
|
||||
{
|
||||
#ifdef HAVE_STRUCT_UCRED
|
||||
struct ucred cred;
|
||||
socklen_t len = sizeof (cred);
|
||||
|
||||
if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &cred, &len) < 0)
|
||||
return 0;
|
||||
|
||||
return cred.uid;
|
||||
#else
|
||||
#ifdef HAVE_GETPEEREID
|
||||
uid_t euid;
|
||||
gid_t egid;
|
||||
|
||||
if (getpeereid(fd, &euid, &egid) == 0)
|
||||
return euid;
|
||||
#endif
|
||||
return -1;
|
||||
#endif
|
||||
}
|
||||
|
||||
static gboolean
|
||||
listen_in_event(G_GNUC_UNUSED GIOChannel *source,
|
||||
G_GNUC_UNUSED GIOCondition condition,
|
||||
gpointer data)
|
||||
{
|
||||
int listen_fd = GPOINTER_TO_INT(data), fd;
|
||||
struct sockaddr_storage sa;
|
||||
size_t sa_length = sizeof(sa);
|
||||
|
||||
fd = accept_cloexec_nonblock(listen_fd, (struct sockaddr*)&sa,
|
||||
&sa_length);
|
||||
if (fd >= 0) {
|
||||
client_new(fd, (struct sockaddr*)&sa, sa_length,
|
||||
get_remote_uid(fd));
|
||||
} else if (fd < 0 && errno != EINTR) {
|
||||
g_warning("Problems accept()'ing");
|
||||
}
|
||||
|
||||
return true;
|
||||
server_socket_free(listen_socket);
|
||||
}
|
||||
|
17
src/log.c
17
src/log.c
@@ -22,6 +22,7 @@
|
||||
#include "conf.h"
|
||||
#include "utils.h"
|
||||
#include "fd_util.h"
|
||||
#include "mpd_error.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <sys/types.h>
|
||||
@@ -60,9 +61,9 @@ static void redirect_logs(int fd)
|
||||
{
|
||||
assert(fd >= 0);
|
||||
if (dup2(fd, STDOUT_FILENO) < 0)
|
||||
g_error("problems dup2 stdout : %s\n", strerror(errno));
|
||||
MPD_ERROR("problems dup2 stdout : %s\n", strerror(errno));
|
||||
if (dup2(fd, STDERR_FILENO) < 0)
|
||||
g_error("problems dup2 stderr : %s\n", strerror(errno));
|
||||
MPD_ERROR("problems dup2 stderr : %s\n", strerror(errno));
|
||||
}
|
||||
|
||||
static const char *log_date(void)
|
||||
@@ -138,8 +139,8 @@ log_init_file(const char *path, unsigned line)
|
||||
out_filename = path;
|
||||
out_fd = open_log_file();
|
||||
if (out_fd < 0)
|
||||
g_error("problem opening log file \"%s\" (config line %u) for "
|
||||
"writing\n", path, line);
|
||||
MPD_ERROR("problem opening log file \"%s\" (config line %u) "
|
||||
"for writing\n", path, line);
|
||||
|
||||
g_log_set_default_handler(file_log_func, NULL);
|
||||
}
|
||||
@@ -216,8 +217,8 @@ parse_log_level(const char *value, unsigned line)
|
||||
else if (0 == strcmp(value, "verbose"))
|
||||
return G_LOG_LEVEL_DEBUG;
|
||||
else {
|
||||
g_error("unknown log level \"%s\" at line %u\n",
|
||||
value, line);
|
||||
MPD_ERROR("unknown log level \"%s\" at line %u\n",
|
||||
value, line);
|
||||
return G_LOG_LEVEL_MESSAGE;
|
||||
}
|
||||
}
|
||||
@@ -252,8 +253,8 @@ void log_init(bool verbose, bool use_stdout)
|
||||
available) */
|
||||
log_init_syslog();
|
||||
#else
|
||||
g_error("config parameter \"%s\" not found\n",
|
||||
CONF_LOG_FILE);
|
||||
MPD_ERROR("config parameter \"%s\" not found\n",
|
||||
CONF_LOG_FILE);
|
||||
#endif
|
||||
#ifdef HAVE_SYSLOG
|
||||
} else if (strcmp(param->value, "syslog") == 0) {
|
||||
|
53
src/main.c
53
src/main.c
@@ -54,6 +54,7 @@
|
||||
#include "dirvec.h"
|
||||
#include "songvec.h"
|
||||
#include "tag_pool.h"
|
||||
#include "mpd_error.h"
|
||||
|
||||
#ifdef ENABLE_INOTIFY
|
||||
#include "inotify_update.h"
|
||||
@@ -141,7 +142,7 @@ glue_db_init_and_load(void)
|
||||
}
|
||||
|
||||
if (path == NULL)
|
||||
g_error(CONF_DB_FILE " setting missing");
|
||||
MPD_ERROR(CONF_DB_FILE " setting missing");
|
||||
|
||||
db_init(path);
|
||||
|
||||
@@ -175,7 +176,7 @@ glue_sticker_init(void)
|
||||
success = sticker_global_init(config_get_path(CONF_STICKER_FILE),
|
||||
&error);
|
||||
if (!success)
|
||||
g_error("%s", error->message);
|
||||
MPD_ERROR("%s", error->message);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -197,14 +198,14 @@ static void winsock_init(void)
|
||||
retval = WSAStartup(MAKEWORD(2, 2), &sockinfo);
|
||||
if(retval != 0)
|
||||
{
|
||||
g_error("Attempt to open Winsock2 failed; error code %d\n",
|
||||
MPD_ERROR("Attempt to open Winsock2 failed; error code %d\n",
|
||||
retval);
|
||||
}
|
||||
|
||||
if (LOBYTE(sockinfo.wVersion) != 2)
|
||||
{
|
||||
g_error("We use Winsock2 but your version is either too new or "
|
||||
"old; please install Winsock 2.x\n");
|
||||
MPD_ERROR("We use Winsock2 but your version is either too new "
|
||||
"or old; please install Winsock 2.x\n");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -226,8 +227,8 @@ initialize_decoder_and_player(void)
|
||||
if (param != NULL) {
|
||||
buffer_size = strtol(param->value, &test, 10);
|
||||
if (*test != '\0' || buffer_size <= 0)
|
||||
g_error("buffer size \"%s\" is not a positive integer, "
|
||||
"line %i\n", param->value, param->line);
|
||||
MPD_ERROR("buffer size \"%s\" is not a positive integer, "
|
||||
"line %i\n", param->value, param->line);
|
||||
} else
|
||||
buffer_size = DEFAULT_BUFFER_SIZE;
|
||||
|
||||
@@ -236,15 +237,15 @@ initialize_decoder_and_player(void)
|
||||
buffered_chunks = buffer_size / CHUNK_SIZE;
|
||||
|
||||
if (buffered_chunks >= 1 << 15)
|
||||
g_error("buffer size \"%li\" is too big\n", (long)buffer_size);
|
||||
MPD_ERROR("buffer size \"%li\" is too big\n", (long)buffer_size);
|
||||
|
||||
param = config_get_param(CONF_BUFFER_BEFORE_PLAY);
|
||||
if (param != NULL) {
|
||||
perc = strtod(param->value, &test);
|
||||
if (*test != '%' || perc < 0 || perc > 100) {
|
||||
g_error("buffered before play \"%s\" is not a positive "
|
||||
"percentage and less than 100 percent, line %i",
|
||||
param->value, param->line);
|
||||
MPD_ERROR("buffered before play \"%s\" is not a positive "
|
||||
"percentage and less than 100 percent, line %i",
|
||||
param->value, param->line);
|
||||
}
|
||||
} else
|
||||
perc = DEFAULT_BUFFER_BEFORE_PLAY;
|
||||
@@ -269,7 +270,25 @@ idle_event_emitted(void)
|
||||
client_manager_idle_add(flags);
|
||||
}
|
||||
|
||||
/**
|
||||
* event_pipe callback function for PIPE_EVENT_SHUTDOWN
|
||||
*/
|
||||
static void
|
||||
shutdown_event_emitted(void)
|
||||
{
|
||||
g_main_loop_quit(main_loop);
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
#ifdef WIN32
|
||||
return win32_main(argc, argv);
|
||||
#else
|
||||
return mpd_main(argc, argv);
|
||||
#endif
|
||||
}
|
||||
|
||||
int mpd_main(int argc, char *argv[])
|
||||
{
|
||||
struct options options;
|
||||
clock_t start;
|
||||
@@ -324,6 +343,7 @@ int main(int argc, char *argv[])
|
||||
|
||||
event_pipe_init();
|
||||
event_pipe_register(PIPE_EVENT_IDLE, idle_event_emitted);
|
||||
event_pipe_register(PIPE_EVENT_SHUTDOWN, shutdown_event_emitted);
|
||||
|
||||
path_global_init();
|
||||
glue_mapper_init();
|
||||
@@ -371,7 +391,7 @@ int main(int argc, char *argv[])
|
||||
database */
|
||||
unsigned job = update_enqueue(NULL, true);
|
||||
if (job == 0)
|
||||
g_error("directory update failed");
|
||||
MPD_ERROR("directory update failed");
|
||||
}
|
||||
|
||||
glue_state_file_init();
|
||||
@@ -392,10 +412,17 @@ int main(int argc, char *argv[])
|
||||
playlist_state_restore() */
|
||||
pc_update_audio();
|
||||
|
||||
/* run the main loop */
|
||||
#ifdef WIN32
|
||||
win32_app_started();
|
||||
#endif
|
||||
|
||||
/* run the main loop */
|
||||
g_main_loop_run(main_loop);
|
||||
|
||||
#ifdef WIN32
|
||||
win32_app_stopping();
|
||||
#endif
|
||||
|
||||
/* cleanup */
|
||||
|
||||
g_main_loop_unref(main_loop);
|
||||
|
41
src/main.h
41
src/main.h
@@ -28,4 +28,45 @@ extern GMainLoop *main_loop;
|
||||
|
||||
extern GCond *main_cond;
|
||||
|
||||
/**
|
||||
* A entry point for application.
|
||||
* On non-Windows platforms this is called directly from main()
|
||||
* On Windows platform this is called from win32_main()
|
||||
* after doing some initialization.
|
||||
*/
|
||||
int mpd_main(int argc, char *argv[]);
|
||||
|
||||
#ifdef WIN32
|
||||
|
||||
/**
|
||||
* If program is run as windows service performs nessesary initialization
|
||||
* and then calls mpd_main() with specified arguments.
|
||||
* If program is run as a regular application calls mpd_main() immediately.
|
||||
*/
|
||||
int
|
||||
win32_main(int argc, char *argv[]);
|
||||
|
||||
/**
|
||||
* When running as a service reports to service control manager
|
||||
* that our service is started.
|
||||
* When running as a console application enables console handler that will
|
||||
* trigger PIPE_EVENT_SHUTDOWN when user closes console window
|
||||
* or presses Ctrl+C.
|
||||
* This function should be called just before entering main loop.
|
||||
*/
|
||||
void
|
||||
win32_app_started(void);
|
||||
|
||||
/**
|
||||
* When running as a service reports to service control manager
|
||||
* that our service is about to stop.
|
||||
* When running as a console application enables console handler that will
|
||||
* catch all shutdown requests and ignore them.
|
||||
* This function should be called just after leaving main loop.
|
||||
*/
|
||||
void
|
||||
win32_app_stopping(void);
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
155
src/main_win32.c
Normal file
155
src/main_win32.c
Normal file
@@ -0,0 +1,155 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2010 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 "config.h"
|
||||
#include "main.h"
|
||||
|
||||
#ifdef WIN32
|
||||
|
||||
#include "mpd_error.h"
|
||||
#include "event_pipe.h"
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
#define WINVER 0x0501
|
||||
#include <windows.h>
|
||||
|
||||
static int service_argc;
|
||||
static char **service_argv;
|
||||
static char service_name[] = "";
|
||||
static BOOL ignore_console_events;
|
||||
static SERVICE_STATUS_HANDLE service_handle;
|
||||
|
||||
static void WINAPI
|
||||
service_main(DWORD argc, CHAR *argv[]);
|
||||
|
||||
static SERVICE_TABLE_ENTRY service_registry[] = {
|
||||
{service_name, service_main},
|
||||
{NULL, NULL}
|
||||
};
|
||||
|
||||
static void
|
||||
service_notify_status(DWORD status_code)
|
||||
{
|
||||
SERVICE_STATUS current_status;
|
||||
|
||||
current_status.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
|
||||
current_status.dwControlsAccepted = status_code == SERVICE_START_PENDING
|
||||
? 0
|
||||
: SERVICE_ACCEPT_SHUTDOWN | SERVICE_ACCEPT_STOP;
|
||||
|
||||
current_status.dwCurrentState = status_code;
|
||||
current_status.dwWin32ExitCode = NO_ERROR;
|
||||
current_status.dwCheckPoint = 0;
|
||||
current_status.dwWaitHint = 1000;
|
||||
|
||||
SetServiceStatus(service_handle, ¤t_status);
|
||||
}
|
||||
|
||||
static DWORD WINAPI
|
||||
service_dispatcher(G_GNUC_UNUSED DWORD control, G_GNUC_UNUSED DWORD event_type,
|
||||
G_GNUC_UNUSED void *event_data, G_GNUC_UNUSED void *context)
|
||||
{
|
||||
switch (control) {
|
||||
case SERVICE_CONTROL_SHUTDOWN:
|
||||
case SERVICE_CONTROL_STOP:
|
||||
event_pipe_emit(PIPE_EVENT_SHUTDOWN);
|
||||
return NO_ERROR;
|
||||
default:
|
||||
return NO_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
static void WINAPI
|
||||
service_main(G_GNUC_UNUSED DWORD argc, G_GNUC_UNUSED CHAR *argv[])
|
||||
{
|
||||
DWORD error_code;
|
||||
gchar* error_message;
|
||||
|
||||
service_handle =
|
||||
RegisterServiceCtrlHandlerEx(service_name,
|
||||
service_dispatcher, NULL);
|
||||
|
||||
if (service_handle == 0) {
|
||||
error_code = GetLastError();
|
||||
error_message = g_win32_error_message(error_code);
|
||||
MPD_ERROR("RegisterServiceCtrlHandlerEx() failed: %s",
|
||||
error_message);
|
||||
}
|
||||
|
||||
service_notify_status(SERVICE_START_PENDING);
|
||||
mpd_main(service_argc, service_argv);
|
||||
service_notify_status(SERVICE_STOPPED);
|
||||
}
|
||||
|
||||
static BOOL WINAPI
|
||||
console_handler(DWORD event)
|
||||
{
|
||||
switch (event) {
|
||||
case CTRL_C_EVENT:
|
||||
case CTRL_CLOSE_EVENT:
|
||||
if (!ignore_console_events)
|
||||
event_pipe_emit(PIPE_EVENT_SHUTDOWN);
|
||||
return TRUE;
|
||||
default:
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
int win32_main(int argc, char *argv[])
|
||||
{
|
||||
DWORD error_code;
|
||||
gchar* error_message;
|
||||
|
||||
service_argc = argc;
|
||||
service_argv = argv;
|
||||
|
||||
if (StartServiceCtrlDispatcher(service_registry))
|
||||
return 0; /* run as service successefully */
|
||||
|
||||
error_code = GetLastError();
|
||||
if (error_code == ERROR_FAILED_SERVICE_CONTROLLER_CONNECT) {
|
||||
/* running as console app */
|
||||
SetConsoleTitle("Music Player Daemon");
|
||||
ignore_console_events = TRUE;
|
||||
SetConsoleCtrlHandler(console_handler, TRUE);
|
||||
return mpd_main(argc, argv);
|
||||
}
|
||||
|
||||
error_message = g_win32_error_message(error_code);
|
||||
MPD_ERROR("StartServiceCtrlDispatcher() failed: %s", error_message);
|
||||
}
|
||||
|
||||
void win32_app_started()
|
||||
{
|
||||
if (service_handle != 0)
|
||||
service_notify_status(SERVICE_RUNNING);
|
||||
else
|
||||
ignore_console_events = FALSE;
|
||||
}
|
||||
|
||||
void win32_app_stopping()
|
||||
{
|
||||
if (service_handle != 0)
|
||||
service_notify_status(SERVICE_STOP_PENDING);
|
||||
else
|
||||
ignore_console_events = TRUE;
|
||||
}
|
||||
|
||||
#endif
|
114
src/mixer/winmm_mixer_plugin.c
Normal file
114
src/mixer/winmm_mixer_plugin.c
Normal file
@@ -0,0 +1,114 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2010 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 "config.h"
|
||||
#include "mixer_api.h"
|
||||
#include "output_api.h"
|
||||
#include "output/winmm_output_plugin.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <math.h>
|
||||
#include <windows.h>
|
||||
|
||||
#undef G_LOG_DOMAIN
|
||||
#define G_LOG_DOMAIN "winmm_mixer"
|
||||
|
||||
struct winmm_mixer {
|
||||
struct mixer base;
|
||||
struct winmm_output *output;
|
||||
};
|
||||
|
||||
static inline GQuark
|
||||
winmm_mixer_quark(void)
|
||||
{
|
||||
return g_quark_from_static_string("winmm_mixer");
|
||||
}
|
||||
|
||||
static inline int
|
||||
winmm_volume_decode(DWORD volume)
|
||||
{
|
||||
return lround((volume & 0xFFFF) / 655.35);
|
||||
}
|
||||
|
||||
static inline DWORD
|
||||
winmm_volume_encode(int volume)
|
||||
{
|
||||
int value = lround(volume * 655.35);
|
||||
return MAKELONG(value, value);
|
||||
}
|
||||
|
||||
static struct mixer *
|
||||
winmm_mixer_init(void *ao, G_GNUC_UNUSED const struct config_param *param,
|
||||
G_GNUC_UNUSED GError **error_r)
|
||||
{
|
||||
assert(ao != NULL);
|
||||
|
||||
struct winmm_mixer *wm = g_new(struct winmm_mixer, 1);
|
||||
mixer_init(&wm->base, &winmm_mixer_plugin);
|
||||
wm->output = (struct winmm_output *) ao;
|
||||
|
||||
return &wm->base;
|
||||
}
|
||||
|
||||
static void
|
||||
winmm_mixer_finish(struct mixer *data)
|
||||
{
|
||||
g_free(data);
|
||||
}
|
||||
|
||||
static int
|
||||
winmm_mixer_get_volume(struct mixer *mixer, GError **error_r)
|
||||
{
|
||||
struct winmm_mixer *wm = (struct winmm_mixer *) mixer;
|
||||
DWORD volume;
|
||||
HWAVEOUT handle = winmm_output_get_handle(wm->output);
|
||||
MMRESULT result = waveOutGetVolume(handle, &volume);
|
||||
|
||||
if (result != MMSYSERR_NOERROR) {
|
||||
g_set_error(error_r, 0, winmm_mixer_quark(),
|
||||
"Failed to get winmm volume");
|
||||
return -1;
|
||||
}
|
||||
|
||||
return winmm_volume_decode(volume);
|
||||
}
|
||||
|
||||
static bool
|
||||
winmm_mixer_set_volume(struct mixer *mixer, unsigned volume, GError **error_r)
|
||||
{
|
||||
struct winmm_mixer *wm = (struct winmm_mixer *) mixer;
|
||||
DWORD value = winmm_volume_encode(volume);
|
||||
HWAVEOUT handle = winmm_output_get_handle(wm->output);
|
||||
MMRESULT result = waveOutSetVolume(handle, value);
|
||||
|
||||
if (result != MMSYSERR_NOERROR) {
|
||||
g_set_error(error_r, 0, winmm_mixer_quark(),
|
||||
"Failed to set winmm volume");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
const struct mixer_plugin winmm_mixer_plugin = {
|
||||
.init = winmm_mixer_init,
|
||||
.finish = winmm_mixer_finish,
|
||||
.get_volume = winmm_mixer_get_volume,
|
||||
.set_volume = winmm_mixer_set_volume,
|
||||
};
|
@@ -29,5 +29,6 @@ extern const struct mixer_plugin software_mixer_plugin;
|
||||
extern const struct mixer_plugin alsa_mixer_plugin;
|
||||
extern const struct mixer_plugin oss_mixer_plugin;
|
||||
extern const struct mixer_plugin pulse_mixer_plugin;
|
||||
extern const struct mixer_plugin winmm_mixer_plugin;
|
||||
|
||||
#endif
|
||||
|
36
src/mpd_error.h
Normal file
36
src/mpd_error.h
Normal file
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2010 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_ERROR_H
|
||||
#define MPD_ERROR_H
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
/* This macro is used as an intermediate step to a proper error handling
|
||||
* using GError in mpd. It is used for unrecoverable error conditions
|
||||
* and exits immediately. The long-term goal is to replace this macro by
|
||||
* proper error handling. */
|
||||
|
||||
#define MPD_ERROR(...) \
|
||||
do { \
|
||||
g_critical(__VA_ARGS__); \
|
||||
exit(EXIT_FAILURE); \
|
||||
} while(0)
|
||||
|
||||
#endif
|
@@ -49,3 +49,10 @@ void notify_signal(struct notify *notify)
|
||||
g_cond_signal(notify->cond);
|
||||
g_mutex_unlock(notify->mutex);
|
||||
}
|
||||
|
||||
void notify_clear(struct notify *notify)
|
||||
{
|
||||
g_mutex_lock(notify->mutex);
|
||||
notify->pending = false;
|
||||
g_mutex_unlock(notify->mutex);
|
||||
}
|
||||
|
@@ -45,4 +45,9 @@ void notify_wait(struct notify *notify);
|
||||
*/
|
||||
void notify_signal(struct notify *notify);
|
||||
|
||||
/**
|
||||
* Clears a pending notification.
|
||||
*/
|
||||
void notify_clear(struct notify *notify);
|
||||
|
||||
#endif
|
||||
|
@@ -408,6 +408,26 @@ configure_hw:
|
||||
}
|
||||
audio_format->sample_rate = sample_rate;
|
||||
|
||||
snd_pcm_uframes_t buffer_size_min, buffer_size_max;
|
||||
snd_pcm_hw_params_get_buffer_size_min(hwparams, &buffer_size_min);
|
||||
snd_pcm_hw_params_get_buffer_size_max(hwparams, &buffer_size_max);
|
||||
unsigned buffer_time_min, buffer_time_max;
|
||||
snd_pcm_hw_params_get_buffer_time_min(hwparams, &buffer_time_min, 0);
|
||||
snd_pcm_hw_params_get_buffer_time_max(hwparams, &buffer_time_max, 0);
|
||||
g_debug("buffer: size=%u..%u time=%u..%u",
|
||||
(unsigned)buffer_size_min, (unsigned)buffer_size_max,
|
||||
buffer_time_min, buffer_time_max);
|
||||
|
||||
snd_pcm_uframes_t period_size_min, period_size_max;
|
||||
snd_pcm_hw_params_get_period_size_min(hwparams, &period_size_min, 0);
|
||||
snd_pcm_hw_params_get_period_size_max(hwparams, &period_size_max, 0);
|
||||
unsigned period_time_min, period_time_max;
|
||||
snd_pcm_hw_params_get_period_time_min(hwparams, &period_time_min, 0);
|
||||
snd_pcm_hw_params_get_period_time_max(hwparams, &period_time_max, 0);
|
||||
g_debug("period: size=%u..%u time=%u..%u",
|
||||
(unsigned)period_size_min, (unsigned)period_size_max,
|
||||
period_time_min, period_time_max);
|
||||
|
||||
if (ad->buffer_time > 0) {
|
||||
buffer_time = ad->buffer_time;
|
||||
cmd = "snd_pcm_hw_params_set_buffer_time_near";
|
||||
|
347
src/output/ffado_output_plugin.c
Normal file
347
src/output/ffado_output_plugin.c
Normal file
@@ -0,0 +1,347 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2010 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Warning: this plugin was not tested successfully. I just couldn't
|
||||
* keep libffado2 from crashing. Use at your own risk.
|
||||
*
|
||||
* For details, see my Debian bug reports:
|
||||
*
|
||||
* http://bugs.debian.org/601657
|
||||
* http://bugs.debian.org/601659
|
||||
* http://bugs.debian.org/601663
|
||||
*
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "output_api.h"
|
||||
#include "timer.h"
|
||||
|
||||
#include <glib.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <libffado/ffado.h>
|
||||
|
||||
#undef G_LOG_DOMAIN
|
||||
#define G_LOG_DOMAIN "ffado"
|
||||
|
||||
enum {
|
||||
MAX_STREAMS = 8,
|
||||
};
|
||||
|
||||
struct mpd_ffado_stream {
|
||||
/** libffado's stream number */
|
||||
int number;
|
||||
|
||||
float *buffer;
|
||||
};
|
||||
|
||||
struct mpd_ffado_device {
|
||||
char *device_name;
|
||||
int verbose;
|
||||
unsigned period_size, nb_buffers;
|
||||
|
||||
ffado_device_t *dev;
|
||||
|
||||
/**
|
||||
* The current sample position inside the stream buffers. New
|
||||
* samples get appended at this position on all streams at the
|
||||
* same time. When the buffers are full
|
||||
* (buffer_position==period_size),
|
||||
* ffado_streaming_transfer_playback_buffers() gets called to
|
||||
* hand them over to libffado.
|
||||
*/
|
||||
unsigned buffer_position;
|
||||
|
||||
/**
|
||||
* The number of streams which are really used by MPD.
|
||||
*/
|
||||
int num_streams;
|
||||
struct mpd_ffado_stream streams[MAX_STREAMS];
|
||||
};
|
||||
|
||||
static inline GQuark
|
||||
ffado_output_quark(void)
|
||||
{
|
||||
return g_quark_from_static_string("ffado_output");
|
||||
}
|
||||
|
||||
static void *
|
||||
ffado_init(G_GNUC_UNUSED const struct audio_format *audio_format,
|
||||
const struct config_param *param,
|
||||
GError **error_r)
|
||||
{
|
||||
g_debug("using libffado version %s, API=%d",
|
||||
ffado_get_version(), ffado_get_api_version());
|
||||
|
||||
struct mpd_ffado_device *fd = g_new(struct mpd_ffado_device, 1);
|
||||
fd->device_name = config_dup_block_string(param, "device", NULL);
|
||||
fd->verbose = config_get_block_unsigned(param, "verbose", 0);
|
||||
|
||||
fd->period_size = config_get_block_unsigned(param, "period_size",
|
||||
1024);
|
||||
if (fd->period_size == 0 || fd->period_size > 1024 * 1024) {
|
||||
g_set_error(error_r, ffado_output_quark(), 0,
|
||||
"invalid period_size setting");
|
||||
return false;
|
||||
}
|
||||
|
||||
fd->nb_buffers = config_get_block_unsigned(param, "nb_buffers", 3);
|
||||
if (fd->nb_buffers == 0 || fd->nb_buffers > 1024) {
|
||||
g_set_error(error_r, ffado_output_quark(), 0,
|
||||
"invalid nb_buffers setting");
|
||||
return false;
|
||||
}
|
||||
|
||||
return fd;
|
||||
}
|
||||
|
||||
static void
|
||||
ffado_finish(void *data)
|
||||
{
|
||||
struct mpd_ffado_device *fd = data;
|
||||
|
||||
g_free(fd->device_name);
|
||||
g_free(fd);
|
||||
}
|
||||
|
||||
static bool
|
||||
ffado_configure_stream(ffado_device_t *dev, struct mpd_ffado_stream *stream,
|
||||
GError **error_r)
|
||||
{
|
||||
char *buffer = (char *)stream->buffer;
|
||||
if (ffado_streaming_set_playback_stream_buffer(dev, stream->number,
|
||||
buffer) != 0) {
|
||||
g_set_error(error_r, ffado_output_quark(), 0,
|
||||
"failed to configure stream buffer");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ffado_streaming_playback_stream_onoff(dev, stream->number,
|
||||
1) != 0) {
|
||||
g_set_error(error_r, ffado_output_quark(), 0,
|
||||
"failed to disable stream");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
ffado_configure(struct mpd_ffado_device *fd, struct audio_format *audio_format,
|
||||
GError **error_r)
|
||||
{
|
||||
assert(fd != NULL);
|
||||
assert(fd->dev != NULL);
|
||||
assert(audio_format->channels <= MAX_STREAMS);
|
||||
|
||||
if (ffado_streaming_set_audio_datatype(fd->dev,
|
||||
ffado_audio_datatype_float) != 0) {
|
||||
g_set_error(error_r, ffado_output_quark(), 0,
|
||||
"ffado_streaming_set_audio_datatype() failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
int num_streams = ffado_streaming_get_nb_playback_streams(fd->dev);
|
||||
if (num_streams < 0) {
|
||||
g_set_error(error_r, ffado_output_quark(), 0,
|
||||
"ffado_streaming_get_nb_playback_streams() failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
g_debug("there are %d playback streams", num_streams);
|
||||
|
||||
fd->num_streams = 0;
|
||||
for (int i = 0; i < num_streams; ++i) {
|
||||
char name[256];
|
||||
ffado_streaming_get_playback_stream_name(fd->dev, i, name,
|
||||
sizeof(name) - 1);
|
||||
|
||||
ffado_streaming_stream_type type =
|
||||
ffado_streaming_get_playback_stream_type(fd->dev, i);
|
||||
if (type != ffado_stream_type_audio) {
|
||||
g_debug("stream %d name='%s': not an audio stream",
|
||||
i, name);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (fd->num_streams >= audio_format->channels) {
|
||||
g_debug("stream %d name='%s': ignoring",
|
||||
i, name);
|
||||
continue;
|
||||
}
|
||||
|
||||
g_debug("stream %d name='%s'", i, name);
|
||||
|
||||
struct mpd_ffado_stream *stream =
|
||||
&fd->streams[fd->num_streams++];
|
||||
|
||||
stream->number = i;
|
||||
|
||||
/* allocated buffer is zeroed = silence */
|
||||
stream->buffer = g_new0(float, fd->period_size);
|
||||
|
||||
if (!ffado_configure_stream(fd->dev, stream, error_r))
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!audio_valid_channel_count(fd->num_streams)) {
|
||||
g_set_error(error_r, ffado_output_quark(), 0,
|
||||
"invalid channel count from libffado: %u",
|
||||
audio_format->channels);
|
||||
return false;
|
||||
}
|
||||
|
||||
g_debug("configured %d audio streams", fd->num_streams);
|
||||
|
||||
if (ffado_streaming_prepare(fd->dev) != 0) {
|
||||
g_set_error(error_r, ffado_output_quark(), 0,
|
||||
"ffado_streaming_prepare() failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ffado_streaming_start(fd->dev) != 0) {
|
||||
g_set_error(error_r, ffado_output_quark(), 0,
|
||||
"ffado_streaming_start() failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
audio_format->channels = fd->num_streams;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
ffado_open(void *data, struct audio_format *audio_format, GError **error_r)
|
||||
{
|
||||
struct mpd_ffado_device *fd = data;
|
||||
|
||||
/* will be converted to floating point, choose best input
|
||||
format */
|
||||
audio_format->format = SAMPLE_FORMAT_S24_P32;
|
||||
|
||||
ffado_device_info_t device_info;
|
||||
memset(&device_info, 0, sizeof(device_info));
|
||||
if (fd->device_name != NULL) {
|
||||
device_info.nb_device_spec_strings = 1;
|
||||
device_info.device_spec_strings = &fd->device_name;
|
||||
}
|
||||
|
||||
ffado_options_t options;
|
||||
memset(&options, 0, sizeof(options));
|
||||
options.sample_rate = audio_format->sample_rate;
|
||||
options.period_size = fd->period_size;
|
||||
options.nb_buffers = fd->nb_buffers;
|
||||
options.verbose = fd->verbose;
|
||||
|
||||
fd->dev = ffado_streaming_init(device_info, options);
|
||||
if (fd->dev == NULL) {
|
||||
g_set_error(error_r, ffado_output_quark(), 0,
|
||||
"ffado_streaming_init() failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ffado_configure(fd, audio_format, error_r)) {
|
||||
ffado_streaming_finish(fd->dev);
|
||||
|
||||
for (int i = 0; i < fd->num_streams; ++i) {
|
||||
struct mpd_ffado_stream *stream = &fd->streams[i];
|
||||
g_free(stream->buffer);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
fd->buffer_position = 0;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void
|
||||
ffado_close(void *data)
|
||||
{
|
||||
struct mpd_ffado_device *fd = data;
|
||||
|
||||
ffado_streaming_stop(fd->dev);
|
||||
ffado_streaming_finish(fd->dev);
|
||||
|
||||
for (int i = 0; i < fd->num_streams; ++i) {
|
||||
struct mpd_ffado_stream *stream = &fd->streams[i];
|
||||
g_free(stream->buffer);
|
||||
}
|
||||
}
|
||||
|
||||
static size_t
|
||||
ffado_play(void *data, const void *chunk, size_t size, GError **error_r)
|
||||
{
|
||||
struct mpd_ffado_device *fd = data;
|
||||
|
||||
/* wait for prefious buffer to finish (if it was full) */
|
||||
|
||||
if (fd->buffer_position >= fd->period_size) {
|
||||
switch (ffado_streaming_wait(fd->dev)) {
|
||||
case ffado_wait_ok:
|
||||
case ffado_wait_xrun:
|
||||
break;
|
||||
|
||||
default:
|
||||
g_set_error(error_r, ffado_output_quark(), 0,
|
||||
"ffado_streaming_wait() failed");
|
||||
return 0;
|
||||
}
|
||||
|
||||
fd->buffer_position = 0;
|
||||
}
|
||||
|
||||
/* copy samples to stream buffers, non-interleaved */
|
||||
|
||||
const int32_t *p = chunk;
|
||||
unsigned num_frames = size / sizeof(*p) / fd->num_streams;
|
||||
if (num_frames > fd->period_size - fd->buffer_position)
|
||||
num_frames = fd->period_size - fd->buffer_position;
|
||||
|
||||
for (unsigned i = num_frames; i > 0; --i) {
|
||||
for (int stream = 0; stream < fd->num_streams; ++stream)
|
||||
fd->streams[stream].buffer[fd->buffer_position] =
|
||||
*p++ / (float)(1 << 23);
|
||||
++fd->buffer_position;
|
||||
}
|
||||
|
||||
/* if buffer full, transfer to device */
|
||||
|
||||
if (fd->buffer_position >= fd->period_size &&
|
||||
/* libffado documentation says this function returns -1 on
|
||||
error, but that is a lie - it returns a boolean value,
|
||||
and "false" means error */
|
||||
!ffado_streaming_transfer_playback_buffers(fd->dev)) {
|
||||
g_set_error(error_r, ffado_output_quark(), 0,
|
||||
"ffado_streaming_transfer_playback_buffers() failed");
|
||||
return 0;
|
||||
}
|
||||
|
||||
return num_frames * sizeof(*p) * fd->num_streams;
|
||||
}
|
||||
|
||||
const struct audio_output_plugin ffado_output_plugin = {
|
||||
.name = "ffado",
|
||||
.init = ffado_init,
|
||||
.finish = ffado_finish,
|
||||
.open = ffado_open,
|
||||
.close = ffado_close,
|
||||
.play = ffado_play,
|
||||
};
|
@@ -29,6 +29,9 @@
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
|
||||
#undef G_LOG_DOMAIN
|
||||
#define G_LOG_DOMAIN "httpd_output"
|
||||
|
||||
struct httpd_client {
|
||||
/**
|
||||
* The httpd output object this client is connected to.
|
||||
|
@@ -29,12 +29,6 @@
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
#ifdef WIN32
|
||||
#include <winsock2.h>
|
||||
#include <ws2tcpip.h>
|
||||
#else
|
||||
#include <sys/socket.h>
|
||||
#endif
|
||||
#include <stdbool.h>
|
||||
|
||||
struct httpd_client;
|
||||
@@ -51,21 +45,19 @@ struct httpd_output {
|
||||
*/
|
||||
struct encoder *encoder;
|
||||
|
||||
/**
|
||||
* Number of bytes which were fed into the encoder, without
|
||||
* ever receiving new output. This is used to estimate
|
||||
* whether MPD should manually flush the encoder, to avoid
|
||||
* buffer underruns in the client.
|
||||
*/
|
||||
size_t unflushed_input;
|
||||
|
||||
/**
|
||||
* The MIME type produced by the #encoder.
|
||||
*/
|
||||
const char *content_type;
|
||||
|
||||
/**
|
||||
* The configured address of the listener socket.
|
||||
*/
|
||||
struct sockaddr_storage address;
|
||||
|
||||
/**
|
||||
* The size of #address.
|
||||
*/
|
||||
socklen_t address_size;
|
||||
|
||||
/**
|
||||
* This mutex protects the listener socket and the client
|
||||
* list.
|
||||
@@ -81,12 +73,7 @@ struct httpd_output {
|
||||
/**
|
||||
* The listener socket.
|
||||
*/
|
||||
int fd;
|
||||
|
||||
/**
|
||||
* A GLib main loop source id for the listener socket.
|
||||
*/
|
||||
guint source_id;
|
||||
struct server_socket *server_socket;
|
||||
|
||||
/**
|
||||
* The header page, which is sent to every client on connect.
|
||||
|
@@ -27,17 +27,11 @@
|
||||
#include "page.h"
|
||||
#include "icy_server.h"
|
||||
#include "fd_util.h"
|
||||
#include "server_socket.h"
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
#include <sys/types.h>
|
||||
#ifdef WIN32
|
||||
#include <winsock2.h>
|
||||
#include <ws2tcpip.h>
|
||||
#else
|
||||
#include <netinet/in.h>
|
||||
#include <netdb.h>
|
||||
#endif
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
|
||||
@@ -57,37 +51,20 @@ httpd_output_quark(void)
|
||||
return g_quark_from_static_string("httpd_output");
|
||||
}
|
||||
|
||||
static gboolean
|
||||
httpd_listen_in_event(G_GNUC_UNUSED GIOChannel *source,
|
||||
G_GNUC_UNUSED GIOCondition condition,
|
||||
gpointer data);
|
||||
static void
|
||||
httpd_listen_in_event(int fd, const struct sockaddr *address,
|
||||
size_t address_length, int uid, void *ctx);
|
||||
|
||||
static bool
|
||||
httpd_output_bind(struct httpd_output *httpd, GError **error_r)
|
||||
{
|
||||
GIOChannel *channel;
|
||||
|
||||
httpd->open = false;
|
||||
|
||||
/* create and set up listener socket */
|
||||
|
||||
httpd->fd = socket_bind_listen(PF_INET, SOCK_STREAM, 0,
|
||||
(struct sockaddr *)&httpd->address,
|
||||
httpd->address_size,
|
||||
16, error_r);
|
||||
if (httpd->fd < 0)
|
||||
return false;
|
||||
|
||||
g_mutex_lock(httpd->mutex);
|
||||
|
||||
channel = g_io_channel_unix_new(httpd->fd);
|
||||
httpd->source_id = g_io_add_watch(channel, G_IO_IN,
|
||||
httpd_listen_in_event, httpd);
|
||||
g_io_channel_unref(channel);
|
||||
|
||||
bool success = server_socket_open(httpd->server_socket, error_r);
|
||||
g_mutex_unlock(httpd->mutex);
|
||||
|
||||
return true;
|
||||
return success;
|
||||
}
|
||||
|
||||
static void
|
||||
@@ -96,10 +73,7 @@ httpd_output_unbind(struct httpd_output *httpd)
|
||||
assert(!httpd->open);
|
||||
|
||||
g_mutex_lock(httpd->mutex);
|
||||
|
||||
g_source_remove(httpd->source_id);
|
||||
close(httpd->fd);
|
||||
|
||||
server_socket_close(httpd->server_socket);
|
||||
g_mutex_unlock(httpd->mutex);
|
||||
}
|
||||
|
||||
@@ -109,10 +83,9 @@ httpd_output_init(G_GNUC_UNUSED const struct audio_format *audio_format,
|
||||
GError **error)
|
||||
{
|
||||
struct httpd_output *httpd = g_new(struct httpd_output, 1);
|
||||
const char *encoder_name;
|
||||
const char *encoder_name, *bind_to_address;
|
||||
const struct encoder_plugin *encoder_plugin;
|
||||
guint port;
|
||||
struct sockaddr_in *sin;
|
||||
|
||||
/* read configuration */
|
||||
httpd->name =
|
||||
@@ -134,14 +107,19 @@ httpd_output_init(G_GNUC_UNUSED const struct audio_format *audio_format,
|
||||
|
||||
httpd->clients_max = config_get_block_unsigned(param,"max_clients", 0);
|
||||
|
||||
/* initialize listen address */
|
||||
/* set up bind_to_address */
|
||||
|
||||
sin = (struct sockaddr_in *)&httpd->address;
|
||||
memset(sin, 0, sizeof(sin));
|
||||
sin->sin_port = htons(port);
|
||||
sin->sin_family = AF_INET;
|
||||
sin->sin_addr.s_addr = INADDR_ANY;
|
||||
httpd->address_size = sizeof(*sin);
|
||||
httpd->server_socket = server_socket_new(httpd_listen_in_event, httpd);
|
||||
|
||||
bind_to_address =
|
||||
config_get_block_string(param, "bind_to_address", NULL);
|
||||
bool success = bind_to_address != NULL &&
|
||||
strcmp(bind_to_address, "any") != 0
|
||||
? server_socket_add_host(httpd->server_socket, bind_to_address,
|
||||
port, error)
|
||||
: server_socket_add_port(httpd->server_socket, port, error);
|
||||
if (!success)
|
||||
return NULL;
|
||||
|
||||
/* initialize metadata */
|
||||
httpd->metadata = NULL;
|
||||
@@ -172,6 +150,7 @@ httpd_output_finish(void *data)
|
||||
page_unref(httpd->metadata);
|
||||
|
||||
encoder_finish(httpd->encoder);
|
||||
server_socket_free(httpd->server_socket);
|
||||
g_mutex_free(httpd->mutex);
|
||||
g_free(httpd);
|
||||
}
|
||||
@@ -195,27 +174,18 @@ httpd_client_add(struct httpd_output *httpd, int fd)
|
||||
httpd_client_send_metadata(client, httpd->metadata);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
httpd_listen_in_event(G_GNUC_UNUSED GIOChannel *source,
|
||||
G_GNUC_UNUSED GIOCondition condition,
|
||||
gpointer data)
|
||||
static void
|
||||
httpd_listen_in_event(int fd, const struct sockaddr *address,
|
||||
size_t address_length, G_GNUC_UNUSED int uid, void *ctx)
|
||||
{
|
||||
struct httpd_output *httpd = data;
|
||||
int fd;
|
||||
struct sockaddr_storage sa;
|
||||
size_t sa_length = sizeof(sa);
|
||||
|
||||
g_mutex_lock(httpd->mutex);
|
||||
struct httpd_output *httpd = ctx;
|
||||
|
||||
/* the listener socket has become readable - a client has
|
||||
connected */
|
||||
|
||||
fd = accept_cloexec_nonblock(httpd->fd, (struct sockaddr*)&sa,
|
||||
&sa_length);
|
||||
#ifdef HAVE_LIBWRAP
|
||||
struct sockaddr *sa_p = (struct sockaddr *)&sa;
|
||||
if (sa_p->sa_family != AF_UNIX) {
|
||||
char *hostaddr = sockaddr_to_string(sa_p, sa_length, NULL);
|
||||
if (address->sa_family != AF_UNIX) {
|
||||
char *hostaddr = sockaddr_to_string(address, address_length, NULL);
|
||||
const char *progname = g_get_prgname();
|
||||
|
||||
struct request_info req;
|
||||
@@ -230,12 +200,18 @@ httpd_listen_in_event(G_GNUC_UNUSED GIOChannel *source,
|
||||
g_free(hostaddr);
|
||||
close(fd);
|
||||
g_mutex_unlock(httpd->mutex);
|
||||
return true;
|
||||
return;
|
||||
}
|
||||
|
||||
g_free(hostaddr);
|
||||
}
|
||||
#else
|
||||
(void)address;
|
||||
(void)address_length;
|
||||
#endif /* HAVE_WRAP */
|
||||
|
||||
g_mutex_lock(httpd->mutex);
|
||||
|
||||
if (fd >= 0) {
|
||||
/* can we allow additional client */
|
||||
if (httpd->open &&
|
||||
@@ -249,8 +225,6 @@ httpd_listen_in_event(G_GNUC_UNUSED GIOChannel *source,
|
||||
}
|
||||
|
||||
g_mutex_unlock(httpd->mutex);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -262,12 +236,22 @@ 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
|
||||
buffer underruns */
|
||||
encoder_flush(httpd->encoder, NULL);
|
||||
httpd->unflushed_input = 0;
|
||||
}
|
||||
|
||||
do {
|
||||
nbytes = encoder_read(httpd->encoder, httpd->buffer + size,
|
||||
sizeof(httpd->buffer) - size);
|
||||
if (nbytes == 0)
|
||||
break;
|
||||
|
||||
httpd->unflushed_input = 0;
|
||||
|
||||
size += nbytes;
|
||||
} while (size < sizeof(httpd->buffer));
|
||||
|
||||
@@ -292,6 +276,9 @@ httpd_output_encoder_open(struct httpd_output *httpd,
|
||||
bytes of encoder output after opening it, because it has to
|
||||
be sent to every new client */
|
||||
httpd->header = httpd_output_read_page(httpd);
|
||||
|
||||
httpd->unflushed_input = 0;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -324,8 +311,6 @@ httpd_output_open(void *data, struct audio_format *audio_format,
|
||||
|
||||
success = httpd_output_encoder_open(httpd, audio_format, error);
|
||||
if (!success) {
|
||||
g_source_remove(httpd->source_id);
|
||||
close(httpd->fd);
|
||||
g_mutex_unlock(httpd->mutex);
|
||||
return false;
|
||||
}
|
||||
@@ -390,6 +375,16 @@ httpd_output_send_header(struct httpd_output *httpd,
|
||||
httpd_client_send(client, httpd->header);
|
||||
}
|
||||
|
||||
static unsigned
|
||||
httpd_output_delay(void *data)
|
||||
{
|
||||
struct httpd_output *httpd = data;
|
||||
|
||||
return httpd->timer->started
|
||||
? timer_delay(httpd->timer)
|
||||
: 0;
|
||||
}
|
||||
|
||||
static void
|
||||
httpd_client_check_queue(gpointer data, G_GNUC_UNUSED gpointer user_data)
|
||||
{
|
||||
@@ -451,6 +446,8 @@ httpd_output_encode_and_play(struct httpd_output *httpd,
|
||||
if (!success)
|
||||
return false;
|
||||
|
||||
httpd->unflushed_input += size;
|
||||
|
||||
httpd_output_encoder_to_clients(httpd);
|
||||
|
||||
return true;
|
||||
@@ -477,13 +474,29 @@ httpd_output_play(void *data, const void *chunk, size_t size, GError **error)
|
||||
|
||||
if (!httpd->timer->started)
|
||||
timer_start(httpd->timer);
|
||||
else
|
||||
timer_sync(httpd->timer);
|
||||
timer_add(httpd->timer, size);
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
static bool
|
||||
httpd_output_pause(void *data)
|
||||
{
|
||||
struct httpd_output *httpd = data;
|
||||
|
||||
g_mutex_lock(httpd->mutex);
|
||||
bool has_clients = httpd->clients != NULL;
|
||||
g_mutex_unlock(httpd->mutex);
|
||||
|
||||
if (has_clients) {
|
||||
static const char silence[1020];
|
||||
return httpd_output_play(data, silence, sizeof(silence), NULL);
|
||||
} else {
|
||||
g_usleep(100000);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
httpd_send_metadata(gpointer data, gpointer user_data)
|
||||
{
|
||||
@@ -570,7 +583,9 @@ const struct audio_output_plugin httpd_output_plugin = {
|
||||
.disable = httpd_output_disable,
|
||||
.open = httpd_output_open,
|
||||
.close = httpd_output_close,
|
||||
.delay = httpd_output_delay,
|
||||
.send_tag = httpd_output_tag,
|
||||
.play = httpd_output_play,
|
||||
.pause = httpd_output_pause,
|
||||
.cancel = httpd_output_cancel,
|
||||
};
|
||||
|
@@ -214,15 +214,6 @@ osx_output_open(void *data, struct audio_format *audio_format, GError **error)
|
||||
stream_description.mSampleRate = audio_format->sample_rate;
|
||||
stream_description.mFormatID = kAudioFormatLinearPCM;
|
||||
stream_description.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger;
|
||||
#if G_BYTE_ORDER == G_BIG_ENDIAN
|
||||
stream_description.mFormatFlags |= kLinearPCMFormatFlagIsBigEndian;
|
||||
#endif
|
||||
|
||||
stream_description.mBytesPerPacket =
|
||||
audio_format_frame_size(audio_format);
|
||||
stream_description.mFramesPerPacket = 1;
|
||||
stream_description.mBytesPerFrame = stream_description.mBytesPerPacket;
|
||||
stream_description.mChannelsPerFrame = audio_format->channels;
|
||||
|
||||
switch (audio_format->format) {
|
||||
case SAMPLE_FORMAT_S8:
|
||||
@@ -239,6 +230,16 @@ osx_output_open(void *data, struct audio_format *audio_format, GError **error)
|
||||
break;
|
||||
}
|
||||
|
||||
#if G_BYTE_ORDER == G_BIG_ENDIAN
|
||||
stream_description.mFormatFlags |= kLinearPCMFormatFlagIsBigEndian;
|
||||
#endif
|
||||
|
||||
stream_description.mBytesPerPacket =
|
||||
audio_format_frame_size(audio_format);
|
||||
stream_description.mFramesPerPacket = 1;
|
||||
stream_description.mBytesPerFrame = stream_description.mBytesPerPacket;
|
||||
stream_description.mChannelsPerFrame = audio_format->channels;
|
||||
|
||||
result = AudioUnitSetProperty(od->au, kAudioUnitProperty_StreamFormat,
|
||||
kAudioUnitScope_Input, 0,
|
||||
&stream_description,
|
||||
|
@@ -21,6 +21,7 @@
|
||||
#include "output_api.h"
|
||||
#include "encoder_plugin.h"
|
||||
#include "encoder_list.h"
|
||||
#include "mpd_error.h"
|
||||
|
||||
#include <shout/shout.h>
|
||||
#include <glib.h>
|
||||
@@ -101,8 +102,8 @@ static void free_shout_data(struct shout_data *sd)
|
||||
#define check_block_param(name) { \
|
||||
block_param = config_get_block_param(param, name); \
|
||||
if (!block_param) { \
|
||||
g_error("no \"%s\" defined for shout device defined at line " \
|
||||
"%i\n", name, param->line); \
|
||||
MPD_ERROR("no \"%s\" defined for shout device defined at line " \
|
||||
"%i\n", name, param->line); \
|
||||
} \
|
||||
}
|
||||
|
||||
@@ -341,7 +342,6 @@ write_page(struct shout_data *sd, GError **error)
|
||||
if (sd->buf.len == 0)
|
||||
return true;
|
||||
|
||||
shout_sync(sd->shout_conn);
|
||||
err = shout_send(sd->shout_conn, sd->buf.data, sd->buf.len);
|
||||
if (!handle_shout_error(sd, err, error))
|
||||
return false;
|
||||
@@ -440,6 +440,18 @@ my_shout_open_device(void *data, struct audio_format *audio_format,
|
||||
return true;
|
||||
}
|
||||
|
||||
static unsigned
|
||||
my_shout_delay(void *data)
|
||||
{
|
||||
struct shout_data *sd = (struct shout_data *)data;
|
||||
|
||||
int delay = shout_delay(sd->shout_conn);
|
||||
if (delay < 0)
|
||||
delay = 0;
|
||||
|
||||
return delay;
|
||||
}
|
||||
|
||||
static size_t
|
||||
my_shout_play(void *data, const void *chunk, size_t size, GError **error)
|
||||
{
|
||||
@@ -454,15 +466,8 @@ my_shout_play(void *data, const void *chunk, size_t size, GError **error)
|
||||
static bool
|
||||
my_shout_pause(void *data)
|
||||
{
|
||||
struct shout_data *sd = (struct shout_data *)data;
|
||||
static const char silence[1020];
|
||||
|
||||
if (shout_delay(sd->shout_conn) > 500) {
|
||||
/* cap the latency for unpause */
|
||||
g_usleep(500000);
|
||||
return true;
|
||||
}
|
||||
|
||||
return my_shout_play(data, silence, sizeof(silence), NULL);
|
||||
}
|
||||
|
||||
@@ -489,7 +494,7 @@ shout_tag_to_metadata(const struct tag *tag, char *dest, size_t size)
|
||||
}
|
||||
}
|
||||
|
||||
snprintf(dest, size, "%s - %s", title, artist);
|
||||
snprintf(dest, size, "%s - %s", artist, title);
|
||||
}
|
||||
|
||||
static void my_shout_set_tag(void *data,
|
||||
@@ -539,6 +544,7 @@ const struct audio_output_plugin shoutPlugin = {
|
||||
.init = my_shout_init_driver,
|
||||
.finish = my_shout_finish_driver,
|
||||
.open = my_shout_open_device,
|
||||
.delay = my_shout_delay,
|
||||
.play = my_shout_play,
|
||||
.pause = my_shout_pause,
|
||||
.cancel = my_shout_drop_buffered_audio,
|
||||
|
@@ -93,7 +93,7 @@ solaris_output_open(void *data, struct audio_format *audio_format,
|
||||
|
||||
/* open the device in non-blocking mode */
|
||||
|
||||
so->fd = open_cloexec(so->device, O_WRONLY|O_NONBLOCK);
|
||||
so->fd = open_cloexec(so->device, O_WRONLY|O_NONBLOCK, 0);
|
||||
if (so->fd < 0) {
|
||||
g_set_error(error, solaris_output_quark(), errno,
|
||||
"Failed to open %s: %s",
|
||||
|
@@ -20,19 +20,24 @@
|
||||
#include "config.h"
|
||||
#include "output_api.h"
|
||||
#include "pcm_buffer.h"
|
||||
#include "mixer_list.h"
|
||||
#include "winmm_output_plugin.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <windows.h>
|
||||
|
||||
#undef G_LOG_DOMAIN
|
||||
#define G_LOG_DOMAIN "win32_output"
|
||||
#define G_LOG_DOMAIN "winmm_output"
|
||||
|
||||
struct win32_buffer {
|
||||
struct winmm_buffer {
|
||||
struct pcm_buffer buffer;
|
||||
|
||||
WAVEHDR hdr;
|
||||
};
|
||||
|
||||
struct win32_output {
|
||||
struct winmm_output {
|
||||
UINT device_id;
|
||||
HWAVEOUT handle;
|
||||
|
||||
/**
|
||||
@@ -41,7 +46,7 @@ struct win32_output {
|
||||
*/
|
||||
HANDLE event;
|
||||
|
||||
struct win32_buffer buffers[8];
|
||||
struct winmm_buffer buffers[8];
|
||||
unsigned next_buffer;
|
||||
};
|
||||
|
||||
@@ -49,45 +54,80 @@ struct win32_output {
|
||||
* The quark used for GError.domain.
|
||||
*/
|
||||
static inline GQuark
|
||||
win32_output_quark(void)
|
||||
winmm_output_quark(void)
|
||||
{
|
||||
return g_quark_from_static_string("win32_output");
|
||||
return g_quark_from_static_string("winmm_output");
|
||||
}
|
||||
|
||||
HWAVEOUT
|
||||
winmm_output_get_handle(struct winmm_output* output)
|
||||
{
|
||||
return output->handle;
|
||||
}
|
||||
|
||||
static bool
|
||||
win32_output_test_default_device(void)
|
||||
winmm_output_test_default_device(void)
|
||||
{
|
||||
/* we assume that Wave is always available */
|
||||
return true;
|
||||
return waveOutGetNumDevs() > 0;
|
||||
}
|
||||
|
||||
static UINT
|
||||
get_device_id(const char *device_name)
|
||||
{
|
||||
/* if device is not specified use wave mapper */
|
||||
if (device_name == NULL)
|
||||
return WAVE_MAPPER;
|
||||
|
||||
/* check for device id */
|
||||
char *endptr;
|
||||
UINT id = strtoul(device_name, &endptr, 0);
|
||||
if (endptr > device_name && *endptr == 0)
|
||||
return id;
|
||||
|
||||
/* check for device name */
|
||||
for (UINT i = 0; i < waveOutGetNumDevs(); i++) {
|
||||
WAVEOUTCAPS caps;
|
||||
MMRESULT result = waveOutGetDevCaps(i, &caps, sizeof(caps));
|
||||
if (result != MMSYSERR_NOERROR)
|
||||
continue;
|
||||
/* szPname is only 32 chars long, so it is often truncated.
|
||||
Use partial match to work around this. */
|
||||
if (strstr(device_name, caps.szPname) == device_name)
|
||||
return i;
|
||||
}
|
||||
|
||||
/* fallback to wave mapper */
|
||||
return WAVE_MAPPER;
|
||||
}
|
||||
|
||||
static void *
|
||||
win32_output_init(G_GNUC_UNUSED const struct audio_format *audio_format,
|
||||
winmm_output_init(G_GNUC_UNUSED const struct audio_format *audio_format,
|
||||
G_GNUC_UNUSED const struct config_param *param,
|
||||
G_GNUC_UNUSED GError **error)
|
||||
{
|
||||
struct win32_output *wo = g_new(struct win32_output, 1);
|
||||
|
||||
struct winmm_output *wo = g_new(struct winmm_output, 1);
|
||||
const char *device = config_get_block_string(param, "device", NULL);
|
||||
wo->device_id = get_device_id(device);
|
||||
return wo;
|
||||
}
|
||||
|
||||
static void
|
||||
win32_output_finish(void *data)
|
||||
winmm_output_finish(void *data)
|
||||
{
|
||||
struct win32_output *wo = data;
|
||||
struct winmm_output *wo = data;
|
||||
|
||||
g_free(wo);
|
||||
}
|
||||
|
||||
static bool
|
||||
win32_output_open(void *data, struct audio_format *audio_format,
|
||||
winmm_output_open(void *data, struct audio_format *audio_format,
|
||||
GError **error_r)
|
||||
{
|
||||
struct win32_output *wo = data;
|
||||
struct winmm_output *wo = data;
|
||||
|
||||
wo->event = CreateEvent(NULL, false, false, NULL);
|
||||
if (wo->event == NULL) {
|
||||
g_set_error(error_r, win32_output_quark(), 0,
|
||||
g_set_error(error_r, winmm_output_quark(), 0,
|
||||
"CreateEvent() failed");
|
||||
return false;
|
||||
}
|
||||
@@ -119,11 +159,11 @@ win32_output_open(void *data, struct audio_format *audio_format,
|
||||
format.wBitsPerSample = audio_format_sample_size(audio_format) * 8;
|
||||
format.cbSize = 0;
|
||||
|
||||
MMRESULT result = waveOutOpen(&wo->handle, WAVE_MAPPER, &format,
|
||||
MMRESULT result = waveOutOpen(&wo->handle, wo->device_id, &format,
|
||||
(DWORD_PTR)wo->event, 0, CALLBACK_EVENT);
|
||||
if (result != MMSYSERR_NOERROR) {
|
||||
CloseHandle(wo->event);
|
||||
g_set_error(error_r, win32_output_quark(), result,
|
||||
g_set_error(error_r, winmm_output_quark(), result,
|
||||
"waveOutOpen() failed");
|
||||
return false;
|
||||
}
|
||||
@@ -139,9 +179,9 @@ win32_output_open(void *data, struct audio_format *audio_format,
|
||||
}
|
||||
|
||||
static void
|
||||
win32_output_close(void *data)
|
||||
winmm_output_close(void *data)
|
||||
{
|
||||
struct win32_output *wo = data;
|
||||
struct winmm_output *wo = data;
|
||||
|
||||
for (unsigned i = 0; i < G_N_ELEMENTS(wo->buffers); ++i)
|
||||
pcm_buffer_deinit(&wo->buffers[i].buffer);
|
||||
@@ -155,13 +195,13 @@ win32_output_close(void *data)
|
||||
* Copy data into a buffer, and prepare the wave header.
|
||||
*/
|
||||
static bool
|
||||
win32_set_buffer(struct win32_output *wo, struct win32_buffer *buffer,
|
||||
winmm_set_buffer(struct winmm_output *wo, struct winmm_buffer *buffer,
|
||||
const void *data, size_t size,
|
||||
GError **error_r)
|
||||
{
|
||||
void *dest = pcm_buffer_get(&buffer->buffer, size);
|
||||
if (dest == NULL) {
|
||||
g_set_error(error_r, win32_output_quark(), 0,
|
||||
g_set_error(error_r, winmm_output_quark(), 0,
|
||||
"Out of memory");
|
||||
return false;
|
||||
}
|
||||
@@ -175,7 +215,7 @@ win32_set_buffer(struct win32_output *wo, struct win32_buffer *buffer,
|
||||
MMRESULT result = waveOutPrepareHeader(wo->handle, &buffer->hdr,
|
||||
sizeof(buffer->hdr));
|
||||
if (result != MMSYSERR_NOERROR) {
|
||||
g_set_error(error_r, win32_output_quark(), result,
|
||||
g_set_error(error_r, winmm_output_quark(), result,
|
||||
"waveOutPrepareHeader() failed");
|
||||
return false;
|
||||
}
|
||||
@@ -187,7 +227,7 @@ win32_set_buffer(struct win32_output *wo, struct win32_buffer *buffer,
|
||||
* Wait until the buffer is finished.
|
||||
*/
|
||||
static bool
|
||||
win32_drain_buffer(struct win32_output *wo, struct win32_buffer *buffer,
|
||||
winmm_drain_buffer(struct winmm_output *wo, struct winmm_buffer *buffer,
|
||||
GError **error_r)
|
||||
{
|
||||
if ((buffer->hdr.dwFlags & WHDR_DONE) == WHDR_DONE)
|
||||
@@ -201,7 +241,7 @@ win32_drain_buffer(struct win32_output *wo, struct win32_buffer *buffer,
|
||||
if (result == MMSYSERR_NOERROR)
|
||||
return true;
|
||||
else if (result != WAVERR_STILLPLAYING) {
|
||||
g_set_error(error_r, win32_output_quark(), result,
|
||||
g_set_error(error_r, winmm_output_quark(), result,
|
||||
"waveOutUnprepareHeader() failed");
|
||||
return false;
|
||||
}
|
||||
@@ -212,14 +252,14 @@ win32_drain_buffer(struct win32_output *wo, struct win32_buffer *buffer,
|
||||
}
|
||||
|
||||
static size_t
|
||||
win32_output_play(void *data, const void *chunk, size_t size, GError **error_r)
|
||||
winmm_output_play(void *data, const void *chunk, size_t size, GError **error_r)
|
||||
{
|
||||
struct win32_output *wo = data;
|
||||
struct winmm_output *wo = data;
|
||||
|
||||
/* get the next buffer from the ring and prepare it */
|
||||
struct win32_buffer *buffer = &wo->buffers[wo->next_buffer];
|
||||
if (!win32_drain_buffer(wo, buffer, error_r) ||
|
||||
!win32_set_buffer(wo, buffer, chunk, size, error_r))
|
||||
struct winmm_buffer *buffer = &wo->buffers[wo->next_buffer];
|
||||
if (!winmm_drain_buffer(wo, buffer, error_r) ||
|
||||
!winmm_set_buffer(wo, buffer, chunk, size, error_r))
|
||||
return 0;
|
||||
|
||||
/* enqueue the buffer */
|
||||
@@ -228,7 +268,7 @@ win32_output_play(void *data, const void *chunk, size_t size, GError **error_r)
|
||||
if (result != MMSYSERR_NOERROR) {
|
||||
waveOutUnprepareHeader(wo->handle, &buffer->hdr,
|
||||
sizeof(buffer->hdr));
|
||||
g_set_error(error_r, win32_output_quark(), result,
|
||||
g_set_error(error_r, winmm_output_quark(), result,
|
||||
"waveOutWrite() failed");
|
||||
return 0;
|
||||
}
|
||||
@@ -241,56 +281,57 @@ win32_output_play(void *data, const void *chunk, size_t size, GError **error_r)
|
||||
}
|
||||
|
||||
static bool
|
||||
win32_drain_all_buffers(struct win32_output *wo, GError **error_r)
|
||||
winmm_drain_all_buffers(struct winmm_output *wo, GError **error_r)
|
||||
{
|
||||
for (unsigned i = wo->next_buffer; i < G_N_ELEMENTS(wo->buffers); ++i)
|
||||
if (!win32_drain_buffer(wo, &wo->buffers[i], error_r))
|
||||
if (!winmm_drain_buffer(wo, &wo->buffers[i], error_r))
|
||||
return false;
|
||||
|
||||
for (unsigned i = 0; i < wo->next_buffer; ++i)
|
||||
if (!win32_drain_buffer(wo, &wo->buffers[i], error_r))
|
||||
if (!winmm_drain_buffer(wo, &wo->buffers[i], error_r))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void
|
||||
win32_stop(struct win32_output *wo)
|
||||
winmm_stop(struct winmm_output *wo)
|
||||
{
|
||||
waveOutReset(wo->handle);
|
||||
|
||||
for (unsigned i = 0; i < G_N_ELEMENTS(wo->buffers); ++i) {
|
||||
struct win32_buffer *buffer = &wo->buffers[i];
|
||||
struct winmm_buffer *buffer = &wo->buffers[i];
|
||||
waveOutUnprepareHeader(wo->handle, &buffer->hdr,
|
||||
sizeof(buffer->hdr));
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
win32_output_drain(void *data)
|
||||
winmm_output_drain(void *data)
|
||||
{
|
||||
struct win32_output *wo = data;
|
||||
struct winmm_output *wo = data;
|
||||
|
||||
if (!win32_drain_all_buffers(wo, NULL))
|
||||
win32_stop(wo);
|
||||
if (!winmm_drain_all_buffers(wo, NULL))
|
||||
winmm_stop(wo);
|
||||
}
|
||||
|
||||
static void
|
||||
win32_output_cancel(void *data)
|
||||
winmm_output_cancel(void *data)
|
||||
{
|
||||
struct win32_output *wo = data;
|
||||
struct winmm_output *wo = data;
|
||||
|
||||
win32_stop(wo);
|
||||
winmm_stop(wo);
|
||||
}
|
||||
|
||||
const struct audio_output_plugin win32_output_plugin = {
|
||||
.name = "win32",
|
||||
.test_default_device = win32_output_test_default_device,
|
||||
.init = win32_output_init,
|
||||
.finish = win32_output_finish,
|
||||
.open = win32_output_open,
|
||||
.close = win32_output_close,
|
||||
.play = win32_output_play,
|
||||
.drain = win32_output_drain,
|
||||
.cancel = win32_output_cancel,
|
||||
const struct audio_output_plugin winmm_output_plugin = {
|
||||
.name = "winmm",
|
||||
.test_default_device = winmm_output_test_default_device,
|
||||
.init = winmm_output_init,
|
||||
.finish = winmm_output_finish,
|
||||
.open = winmm_output_open,
|
||||
.close = winmm_output_close,
|
||||
.play = winmm_output_play,
|
||||
.drain = winmm_output_drain,
|
||||
.cancel = winmm_output_cancel,
|
||||
.mixer_plugin = &winmm_mixer_plugin,
|
||||
};
|
29
src/output/winmm_output_plugin.h
Normal file
29
src/output/winmm_output_plugin.h
Normal file
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2010 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_WINMM_OUTPUT_PLUGIN_H
|
||||
#define MPD_WINMM_OUTPUT_PLUGIN_H
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
struct winmm_output;
|
||||
|
||||
HWAVEOUT winmm_output_get_handle(struct winmm_output*);
|
||||
|
||||
#endif
|
@@ -26,6 +26,7 @@
|
||||
#include "pipe.h"
|
||||
#include "buffer.h"
|
||||
#include "player_control.h"
|
||||
#include "mpd_error.h"
|
||||
|
||||
#ifndef NDEBUG
|
||||
#include "chunk.h"
|
||||
@@ -122,17 +123,17 @@ audio_output_all_init(void)
|
||||
|
||||
if (!audio_output_init(output, param, &error)) {
|
||||
if (param != NULL)
|
||||
g_error("line %i: %s",
|
||||
param->line, error->message);
|
||||
MPD_ERROR("line %i: %s",
|
||||
param->line, error->message);
|
||||
else
|
||||
g_error("%s", error->message);
|
||||
MPD_ERROR("%s", error->message);
|
||||
}
|
||||
|
||||
/* require output names to be unique: */
|
||||
for (j = 0; j < i; j++) {
|
||||
if (!strcmp(output->name, audio_outputs[j].name)) {
|
||||
g_error("output devices with identical "
|
||||
"names: %s\n", output->name);
|
||||
MPD_ERROR("output devices with identical "
|
||||
"names: %s\n", output->name);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -144,6 +145,7 @@ audio_output_all_finish(void)
|
||||
unsigned int i;
|
||||
|
||||
for (i = 0; i < num_audio_outputs; i++) {
|
||||
audio_output_disable(&audio_outputs[i]);
|
||||
audio_output_finish(&audio_outputs[i]);
|
||||
}
|
||||
|
||||
@@ -321,7 +323,7 @@ audio_output_all_open(const struct audio_format *audio_format,
|
||||
else
|
||||
/* if the pipe hasn't been cleared, the the audio
|
||||
format must not have changed */
|
||||
assert(music_pipe_size(g_mp) == 0 ||
|
||||
assert(music_pipe_empty(g_mp) ||
|
||||
audio_format_equals(audio_format,
|
||||
&input_audio_format));
|
||||
|
||||
@@ -434,7 +436,7 @@ audio_output_all_check(void)
|
||||
assert(g_mp != NULL);
|
||||
|
||||
while ((chunk = music_pipe_peek(g_mp)) != NULL) {
|
||||
assert(music_pipe_size(g_mp) > 0);
|
||||
assert(!music_pipe_empty(g_mp));
|
||||
|
||||
if (!chunk_is_consumed(chunk))
|
||||
/* at least one output is not finished playing
|
||||
|
@@ -102,6 +102,12 @@ audio_output_disable(struct audio_output *ao)
|
||||
g_mutex_unlock(ao->mutex);
|
||||
}
|
||||
|
||||
static void
|
||||
audio_output_close_locked(struct audio_output *ao);
|
||||
|
||||
/**
|
||||
* Object must be locked (and unlocked) by the caller.
|
||||
*/
|
||||
static bool
|
||||
audio_output_open(struct audio_output *ao,
|
||||
const struct audio_format *audio_format,
|
||||
@@ -173,6 +179,8 @@ audio_output_open(struct audio_output *ao,
|
||||
static void
|
||||
audio_output_close_locked(struct audio_output *ao)
|
||||
{
|
||||
assert(ao != NULL);
|
||||
|
||||
if (ao->mixer != NULL)
|
||||
mixer_auto_close(ao->mixer);
|
||||
|
||||
@@ -251,25 +259,6 @@ void audio_output_cancel(struct audio_output *ao)
|
||||
g_mutex_unlock(ao->mutex);
|
||||
}
|
||||
|
||||
void audio_output_close(struct audio_output *ao)
|
||||
{
|
||||
if (ao->mixer != NULL)
|
||||
mixer_auto_close(ao->mixer);
|
||||
|
||||
g_mutex_lock(ao->mutex);
|
||||
|
||||
assert(!ao->open || ao->fail_timer == NULL);
|
||||
|
||||
if (ao->open)
|
||||
ao_command(ao, AO_COMMAND_CLOSE);
|
||||
else if (ao->fail_timer != NULL) {
|
||||
g_timer_destroy(ao->fail_timer);
|
||||
ao->fail_timer = NULL;
|
||||
}
|
||||
|
||||
g_mutex_unlock(ao->mutex);
|
||||
}
|
||||
|
||||
void
|
||||
audio_output_release(struct audio_output *ao)
|
||||
{
|
||||
@@ -279,6 +268,16 @@ audio_output_release(struct audio_output *ao)
|
||||
audio_output_close(ao);
|
||||
}
|
||||
|
||||
void audio_output_close(struct audio_output *ao)
|
||||
{
|
||||
assert(ao != NULL);
|
||||
assert(!ao->open || ao->fail_timer == NULL);
|
||||
|
||||
g_mutex_lock(ao->mutex);
|
||||
audio_output_close_locked(ao);
|
||||
g_mutex_unlock(ao->mutex);
|
||||
}
|
||||
|
||||
void audio_output_finish(struct audio_output *ao)
|
||||
{
|
||||
audio_output_close(ao);
|
||||
|
@@ -196,7 +196,8 @@ struct audio_output {
|
||||
const struct music_pipe *pipe;
|
||||
|
||||
/**
|
||||
* This mutex protects #open, #chunk and #chunk_finished.
|
||||
* This mutex protects #open, #fail_timer, #chunk and
|
||||
* #chunk_finished.
|
||||
*/
|
||||
GMutex *mutex;
|
||||
|
||||
|
@@ -36,7 +36,8 @@ extern const struct audio_output_plugin mvp_output_plugin;
|
||||
extern const struct audio_output_plugin jack_output_plugin;
|
||||
extern const struct audio_output_plugin httpd_output_plugin;
|
||||
extern const struct audio_output_plugin recorder_output_plugin;
|
||||
extern const struct audio_output_plugin win32_output_plugin;
|
||||
extern const struct audio_output_plugin winmm_output_plugin;
|
||||
extern const struct audio_output_plugin ffado_output_plugin;
|
||||
|
||||
const struct audio_output_plugin *audio_output_plugins[] = {
|
||||
#ifdef HAVE_SHOUT
|
||||
@@ -82,8 +83,11 @@ const struct audio_output_plugin *audio_output_plugins[] = {
|
||||
#ifdef ENABLE_RECORDER_OUTPUT
|
||||
&recorder_output_plugin,
|
||||
#endif
|
||||
#ifdef ENABLE_WIN32_OUTPUT
|
||||
&win32_output_plugin,
|
||||
#ifdef ENABLE_WINMM_OUTPUT
|
||||
&winmm_output_plugin,
|
||||
#endif
|
||||
#ifdef ENABLE_FFADO_OUTPUT
|
||||
&ffado_output_plugin,
|
||||
#endif
|
||||
NULL
|
||||
};
|
||||
|
@@ -100,6 +100,16 @@ struct audio_output_plugin {
|
||||
*/
|
||||
void (*close)(void *data);
|
||||
|
||||
/**
|
||||
* Returns a positive number if the output thread shall delay
|
||||
* the next call to play() or pause(). This should be
|
||||
* implemented instead of doing a sleep inside the plugin,
|
||||
* because this allows MPD to listen to commands meanwhile.
|
||||
*
|
||||
* @return the number of milliseconds to wait
|
||||
*/
|
||||
unsigned (*delay)(void *data);
|
||||
|
||||
/**
|
||||
* Display metadata for the next chunk. Optional method,
|
||||
* because not all devices can display metadata.
|
||||
@@ -202,6 +212,14 @@ ao_plugin_close(const struct audio_output_plugin *plugin, void *data)
|
||||
plugin->close(data);
|
||||
}
|
||||
|
||||
static inline unsigned
|
||||
ao_plugin_delay(const struct audio_output_plugin *plugin, void *data)
|
||||
{
|
||||
return plugin->delay != NULL
|
||||
? plugin->delay(data)
|
||||
: 0;
|
||||
}
|
||||
|
||||
static inline void
|
||||
ao_plugin_send_tag(const struct audio_output_plugin *plugin,
|
||||
void *data, const struct tag *tag)
|
||||
|
@@ -28,6 +28,7 @@
|
||||
#include "filter_plugin.h"
|
||||
#include "filter/convert_filter_plugin.h"
|
||||
#include "filter/replay_gain_filter_plugin.h"
|
||||
#include "mpd_error.h"
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
@@ -133,10 +134,18 @@ ao_open(struct audio_output *ao)
|
||||
struct audio_format_string af_string;
|
||||
|
||||
assert(!ao->open);
|
||||
assert(ao->fail_timer == NULL);
|
||||
assert(ao->pipe != NULL);
|
||||
assert(ao->chunk == NULL);
|
||||
|
||||
if (ao->fail_timer != NULL) {
|
||||
/* this can only happen when this
|
||||
output thread fails while
|
||||
audio_output_open() is run in the
|
||||
player thread */
|
||||
g_timer_destroy(ao->fail_timer);
|
||||
ao->fail_timer = NULL;
|
||||
}
|
||||
|
||||
/* enable the device (just in case the last enable has failed) */
|
||||
|
||||
if (!ao_enable(ao))
|
||||
@@ -277,6 +286,30 @@ ao_reopen(struct audio_output *ao)
|
||||
ao_open(ao);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait until the output's delay reaches zero.
|
||||
*
|
||||
* @return true if playback should be continued, false if a command
|
||||
* was issued
|
||||
*/
|
||||
static bool
|
||||
ao_wait(struct audio_output *ao)
|
||||
{
|
||||
while (true) {
|
||||
unsigned delay = ao_plugin_delay(ao->plugin, ao->data);
|
||||
if (delay == 0)
|
||||
return true;
|
||||
|
||||
GTimeVal tv;
|
||||
g_get_current_time(&tv);
|
||||
g_time_val_add(&tv, delay * 1000);
|
||||
(void)g_cond_timed_wait(ao->cond, ao->mutex, &tv);
|
||||
|
||||
if (ao->command != AO_COMMAND_NONE)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static const char *
|
||||
ao_chunk_data(struct audio_output *ao, const struct music_chunk *chunk,
|
||||
struct filter *replay_gain_filter,
|
||||
@@ -413,6 +446,9 @@ ao_play_chunk(struct audio_output *ao, const struct music_chunk *chunk)
|
||||
while (size > 0 && ao->command == AO_COMMAND_NONE) {
|
||||
size_t nbytes;
|
||||
|
||||
if (!ao_wait(ao))
|
||||
break;
|
||||
|
||||
g_mutex_unlock(ao->mutex);
|
||||
nbytes = ao_plugin_play(ao->plugin, ao->data, data, size,
|
||||
&error);
|
||||
@@ -427,7 +463,9 @@ ao_play_chunk(struct audio_output *ao, const struct music_chunk *chunk)
|
||||
|
||||
/* don't automatically reopen this device for
|
||||
10 seconds */
|
||||
assert(ao->fail_timer == NULL);
|
||||
ao->fail_timer = g_timer_new();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -510,6 +548,9 @@ static void ao_pause(struct audio_output *ao)
|
||||
ao_command_finished(ao);
|
||||
|
||||
do {
|
||||
if (!ao_wait(ao))
|
||||
break;
|
||||
|
||||
g_mutex_unlock(ao->mutex);
|
||||
ret = ao_plugin_pause(ao->plugin, ao->data);
|
||||
g_mutex_lock(ao->mutex);
|
||||
@@ -629,5 +670,5 @@ void audio_output_thread_start(struct audio_output *ao)
|
||||
assert(ao->command == AO_COMMAND_NONE);
|
||||
|
||||
if (!(ao->thread = g_thread_create(audio_output_task, ao, true, &e)))
|
||||
g_error("Failed to spawn output task: %s\n", e->message);
|
||||
MPD_ERROR("Failed to spawn output task: %s\n", e->message);
|
||||
}
|
||||
|
@@ -20,6 +20,7 @@
|
||||
#include "config.h"
|
||||
#include "path.h"
|
||||
#include "conf.h"
|
||||
#include "mpd_error.h"
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
@@ -64,7 +65,7 @@ path_set_fs_charset(const char *charset)
|
||||
/* convert a space to ensure that the charset is valid */
|
||||
test = g_convert(" ", 1, charset, "UTF-8", NULL, NULL, NULL);
|
||||
if (test == NULL)
|
||||
g_error("invalid filesystem charset: %s", charset);
|
||||
MPD_ERROR("invalid filesystem charset: %s", charset);
|
||||
g_free(test);
|
||||
|
||||
g_free(fs_charset);
|
||||
|
@@ -22,6 +22,7 @@
|
||||
#include "pcm_volume.h"
|
||||
#include "pcm_utils.h"
|
||||
#include "audio_format.h"
|
||||
#include "mpd_error.h"
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
@@ -125,8 +126,8 @@ pcm_add_vol(void *buffer1, const void *buffer2, size_t size,
|
||||
break;
|
||||
|
||||
default:
|
||||
g_error("format %s not supported by pcm_add_vol",
|
||||
sample_format_to_string(format->format));
|
||||
MPD_ERROR("format %s not supported by pcm_add_vol",
|
||||
sample_format_to_string(format->format));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -208,8 +209,8 @@ pcm_add(void *buffer1, const void *buffer2, size_t size,
|
||||
break;
|
||||
|
||||
default:
|
||||
g_error("format %s not supported by pcm_add",
|
||||
sample_format_to_string(format->format));
|
||||
MPD_ERROR("format %s not supported by pcm_add",
|
||||
sample_format_to_string(format->format));
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -20,6 +20,7 @@
|
||||
#include "config.h"
|
||||
#include "permission.h"
|
||||
#include "conf.h"
|
||||
#include "mpd_error.h"
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
@@ -59,7 +60,7 @@ static unsigned parsePermissions(const char *string)
|
||||
} else if (strcmp(temp, PERMISSION_ADMIN_STRING) == 0) {
|
||||
permission |= PERMISSION_ADMIN;
|
||||
} else {
|
||||
g_error("unknown permission \"%s\"", temp);
|
||||
MPD_ERROR("unknown permission \"%s\"", temp);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,7 +91,7 @@ void initPermissions(void)
|
||||
strchr(param->value, PERMISSION_PASSWORD_CHAR);
|
||||
|
||||
if (separator == NULL)
|
||||
g_error("\"%c\" not found in password string "
|
||||
MPD_ERROR("\"%c\" not found in password string "
|
||||
"\"%s\", line %i",
|
||||
PERMISSION_PASSWORD_CHAR,
|
||||
param->value, param->line);
|
||||
|
@@ -99,4 +99,10 @@ music_pipe_push(struct music_pipe *mp, struct music_chunk *chunk);
|
||||
unsigned
|
||||
music_pipe_size(const struct music_pipe *mp);
|
||||
|
||||
static inline bool
|
||||
music_pipe_empty(const struct music_pipe *mp)
|
||||
{
|
||||
return music_pipe_size(mp) == 0;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
@@ -34,6 +34,7 @@
|
||||
#include "idle.h"
|
||||
#include "main.h"
|
||||
#include "buffer.h"
|
||||
#include "mpd_error.h"
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
@@ -147,6 +148,32 @@ player_dc_start(struct player *player, struct music_pipe *pipe)
|
||||
dc_start(dc, pc.next_song, player_buffer, pipe);
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the decoder still busy on the same song as the player?
|
||||
*
|
||||
* Note: this function does not check if the decoder is already
|
||||
* finished.
|
||||
*/
|
||||
static bool
|
||||
player_dc_at_current_song(const struct player *player)
|
||||
{
|
||||
assert(player != NULL);
|
||||
assert(player->pipe != NULL);
|
||||
|
||||
return player->dc->pipe == player->pipe;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the decoder is decoding the next song (or has begun
|
||||
* decoding it, or has finished doing it), and the player hasn't
|
||||
* switched to that song yet.
|
||||
*/
|
||||
static bool
|
||||
player_dc_at_next_song(const struct player *player)
|
||||
{
|
||||
return player->dc->pipe != NULL && !player_dc_at_current_song(player);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop the decoder and clears (and frees) its music pipe.
|
||||
*
|
||||
@@ -171,17 +198,6 @@ player_dc_stop(struct player *player)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the decoder is decoding the next song (or has begun
|
||||
* decoding it, or has finished doing it), and the player hasn't
|
||||
* switched to that song yet.
|
||||
*/
|
||||
static bool
|
||||
decoding_next_song(const struct player *player)
|
||||
{
|
||||
return player->dc->pipe != NULL && player->dc->pipe != player->pipe;
|
||||
}
|
||||
|
||||
/**
|
||||
* After the decoder has been started asynchronously, wait for the
|
||||
* "START" command to finish. The decoder may not be initialized yet,
|
||||
@@ -340,16 +356,9 @@ player_check_decoder_startup(struct player *player)
|
||||
static bool
|
||||
player_send_silence(struct player *player)
|
||||
{
|
||||
struct music_chunk *chunk;
|
||||
size_t frame_size =
|
||||
audio_format_frame_size(&player->play_audio_format);
|
||||
/* this formula ensures that we don't send
|
||||
partial frames */
|
||||
unsigned num_frames = sizeof(chunk->data) / frame_size;
|
||||
|
||||
assert(audio_format_defined(&player->play_audio_format));
|
||||
|
||||
chunk = music_buffer_allocate(player_buffer);
|
||||
struct music_chunk *chunk = music_buffer_allocate(player_buffer);
|
||||
if (chunk == NULL) {
|
||||
g_warning("Failed to allocate silence buffer");
|
||||
return false;
|
||||
@@ -359,6 +368,12 @@ player_send_silence(struct player *player)
|
||||
chunk->audio_format = player->play_audio_format;
|
||||
#endif
|
||||
|
||||
size_t frame_size =
|
||||
audio_format_frame_size(&player->play_audio_format);
|
||||
/* this formula ensures that we don't send
|
||||
partial frames */
|
||||
unsigned num_frames = sizeof(chunk->data) / frame_size;
|
||||
|
||||
chunk->times = -1.0; /* undefined time stamp */
|
||||
chunk->length = num_frames * frame_size;
|
||||
memset(chunk->data, 0, chunk->length);
|
||||
@@ -380,8 +395,6 @@ static bool player_seek_decoder(struct player *player)
|
||||
{
|
||||
struct song *song = pc.next_song;
|
||||
struct decoder_control *dc = player->dc;
|
||||
double where;
|
||||
bool ret;
|
||||
|
||||
assert(pc.next_song != NULL);
|
||||
|
||||
@@ -397,13 +410,20 @@ static bool player_seek_decoder(struct player *player)
|
||||
|
||||
/* re-start the decoder */
|
||||
player_dc_start(player, player->pipe);
|
||||
ret = player_wait_for_decoder(player);
|
||||
if (!ret) {
|
||||
if (!player_wait_for_decoder(player)) {
|
||||
/* decoder failure */
|
||||
player_command_finished();
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (!player_dc_at_current_song(player)) {
|
||||
/* the decoder is already decoding the "next" song,
|
||||
but it is the same song file; exchange the pipe */
|
||||
music_pipe_clear(player->pipe, player_buffer);
|
||||
music_pipe_free(player->pipe);
|
||||
player->pipe = dc->pipe;
|
||||
}
|
||||
|
||||
pc.next_song = NULL;
|
||||
player->queued = false;
|
||||
}
|
||||
@@ -411,8 +431,7 @@ static bool player_seek_decoder(struct player *player)
|
||||
/* wait for the decoder to complete initialization */
|
||||
|
||||
while (player->decoder_starting) {
|
||||
ret = player_check_decoder_startup(player);
|
||||
if (!ret) {
|
||||
if (!player_check_decoder_startup(player)) {
|
||||
/* decoder failure */
|
||||
player_command_finished();
|
||||
return false;
|
||||
@@ -421,14 +440,13 @@ static bool player_seek_decoder(struct player *player)
|
||||
|
||||
/* send the SEEK command */
|
||||
|
||||
where = pc.seek_where;
|
||||
double where = pc.seek_where;
|
||||
if (where > pc.total_time)
|
||||
where = pc.total_time - 0.1;
|
||||
if (where < 0.0)
|
||||
where = 0.0;
|
||||
|
||||
ret = dc_seek(dc, where + song->start_ms / 1000.0);
|
||||
if (!ret) {
|
||||
if (!dc_seek(dc, where + song->start_ms / 1000.0)) {
|
||||
/* decoder failure */
|
||||
player_command_finished();
|
||||
return false;
|
||||
@@ -472,7 +490,7 @@ static void player_process_command(struct player *player)
|
||||
case PLAYER_COMMAND_QUEUE:
|
||||
assert(pc.next_song != NULL);
|
||||
assert(!player->queued);
|
||||
assert(dc->pipe == NULL || dc->pipe == player->pipe);
|
||||
assert(!player_dc_at_next_song(player));
|
||||
|
||||
player->queued = true;
|
||||
player_command_finished_locked();
|
||||
@@ -526,7 +544,7 @@ static void player_process_command(struct player *player)
|
||||
return;
|
||||
}
|
||||
|
||||
if (decoding_next_song(player)) {
|
||||
if (player_dc_at_next_song(player)) {
|
||||
/* the decoder is already decoding the song -
|
||||
stop it and reset the position */
|
||||
player_unlock();
|
||||
@@ -559,14 +577,12 @@ static void player_process_command(struct player *player)
|
||||
static void
|
||||
update_song_tag(struct song *song, const struct tag *new_tag)
|
||||
{
|
||||
struct tag *old_tag;
|
||||
|
||||
if (song_is_file(song))
|
||||
/* don't update tags of local files, only remote
|
||||
streams may change tags dynamically */
|
||||
return;
|
||||
|
||||
old_tag = song->tag;
|
||||
struct tag *old_tag = song->tag;
|
||||
song->tag = tag_dup(new_tag);
|
||||
|
||||
if (old_tag != NULL)
|
||||
@@ -624,17 +640,16 @@ static bool
|
||||
play_next_chunk(struct player *player)
|
||||
{
|
||||
struct decoder_control *dc = player->dc;
|
||||
struct music_chunk *chunk = NULL;
|
||||
unsigned cross_fade_position;
|
||||
bool success;
|
||||
|
||||
if (!audio_output_all_wait(64))
|
||||
/* the output pipe is still large enough, don't send
|
||||
another chunk */
|
||||
return true;
|
||||
|
||||
unsigned cross_fade_position;
|
||||
struct music_chunk *chunk = NULL;
|
||||
if (player->xfade == XFADE_ENABLED &&
|
||||
decoding_next_song(player) &&
|
||||
player_dc_at_next_song(player) &&
|
||||
(cross_fade_position = music_pipe_size(player->pipe))
|
||||
<= player->cross_fade_chunks) {
|
||||
/* perform cross fade */
|
||||
@@ -670,6 +685,19 @@ play_next_chunk(struct player *player)
|
||||
chunk->mix_ratio = nan("");
|
||||
}
|
||||
|
||||
if (music_chunk_is_empty(other_chunk)) {
|
||||
/* the "other" chunk was a music_chunk
|
||||
which had only a tag, but no music
|
||||
data - we cannot cross-fade that;
|
||||
but since this happens only at the
|
||||
beginning of the new song, we can
|
||||
easily recover by throwing it away
|
||||
now */
|
||||
music_buffer_return(player_buffer,
|
||||
other_chunk);
|
||||
other_chunk = NULL;
|
||||
}
|
||||
|
||||
chunk->other = other_chunk;
|
||||
} else {
|
||||
/* there are not enough decoded chunks yet */
|
||||
@@ -708,9 +736,7 @@ play_next_chunk(struct player *player)
|
||||
|
||||
/* play the current chunk */
|
||||
|
||||
success = play_chunk(player->song, chunk, &player->play_audio_format);
|
||||
|
||||
if (!success) {
|
||||
if (!play_chunk(player->song, chunk, &player->play_audio_format)) {
|
||||
music_buffer_return(player_buffer, chunk);
|
||||
|
||||
player_lock();
|
||||
@@ -752,11 +778,9 @@ play_next_chunk(struct player *player)
|
||||
static bool
|
||||
player_song_border(struct player *player)
|
||||
{
|
||||
char *uri;
|
||||
|
||||
player->xfade = XFADE_UNKNOWN;
|
||||
|
||||
uri = song_get_uri(player->song);
|
||||
char *uri = song_get_uri(player->song);
|
||||
g_message("played \"%s\"", uri);
|
||||
g_free(uri);
|
||||
|
||||
@@ -851,16 +875,17 @@ static void do_play(struct decoder_control *dc)
|
||||
|
||||
if (player.decoder_starting) {
|
||||
/* wait until the decoder is initialized completely */
|
||||
bool success;
|
||||
const struct song *song;
|
||||
|
||||
success = player_check_decoder_startup(&player);
|
||||
if (!success)
|
||||
if (!player_check_decoder_startup(&player))
|
||||
break;
|
||||
|
||||
/* seek to the beginning of the range */
|
||||
song = decoder_current_song(dc);
|
||||
const struct song *song = decoder_current_song(dc);
|
||||
if (song != NULL && song->start_ms > 0 &&
|
||||
/* we must not send a seek command until
|
||||
the decoder is initialized
|
||||
completely */
|
||||
!player.decoder_starting &&
|
||||
!dc_seek(dc, song->start_ms / 1000.0))
|
||||
player_dc_stop(&player);
|
||||
|
||||
@@ -880,12 +905,13 @@ static void do_play(struct decoder_control *dc)
|
||||
dc->pipe == player.pipe) {
|
||||
/* the decoder has finished the current song;
|
||||
make it decode the next song */
|
||||
|
||||
assert(dc->pipe == NULL || dc->pipe == player.pipe);
|
||||
|
||||
player_dc_start(&player, music_pipe_new());
|
||||
}
|
||||
|
||||
if (decoding_next_song(&player) &&
|
||||
if (player_dc_at_next_song(&player) &&
|
||||
player.xfade == XFADE_UNKNOWN &&
|
||||
!decoder_lock_is_starting(dc)) {
|
||||
/* enable cross fading in this song? if yes,
|
||||
@@ -918,7 +944,7 @@ static void do_play(struct decoder_control *dc)
|
||||
if (pc.command == PLAYER_COMMAND_NONE)
|
||||
player_wait();
|
||||
continue;
|
||||
} else if (music_pipe_size(player.pipe) > 0) {
|
||||
} else if (!music_pipe_empty(player.pipe)) {
|
||||
/* at least one music chunk is ready - send it
|
||||
to the audio output */
|
||||
|
||||
@@ -930,7 +956,7 @@ static void do_play(struct decoder_control *dc)
|
||||
|
||||
/* XXX synchronize in a better way */
|
||||
g_usleep(10000);
|
||||
} else if (decoding_next_song(&player)) {
|
||||
} else if (player_dc_at_next_song(&player)) {
|
||||
/* at the beginning of a new song */
|
||||
|
||||
if (!player_song_border(&player))
|
||||
@@ -939,7 +965,7 @@ static void do_play(struct decoder_control *dc)
|
||||
/* check the size of the pipe again, because
|
||||
the decoder thread may have added something
|
||||
since we last checked */
|
||||
if (music_pipe_size(player.pipe) == 0) {
|
||||
if (music_pipe_empty(player.pipe)) {
|
||||
/* wait for the hardware to finish
|
||||
playback */
|
||||
audio_output_all_drain();
|
||||
@@ -1067,11 +1093,10 @@ static gpointer player_task(G_GNUC_UNUSED gpointer arg)
|
||||
|
||||
void player_create(void)
|
||||
{
|
||||
GError *e = NULL;
|
||||
|
||||
assert(pc.thread == NULL);
|
||||
|
||||
GError *e = NULL;
|
||||
pc.thread = g_thread_create(player_task, NULL, true, &e);
|
||||
if (pc.thread == NULL)
|
||||
g_error("Failed to spawn player task: %s", e->message);
|
||||
MPD_ERROR("Failed to spawn player task: %s", e->message);
|
||||
}
|
||||
|
@@ -108,11 +108,8 @@ playlist_song_started(struct playlist *playlist)
|
||||
playlist->current = playlist->queued;
|
||||
playlist->queued = -1;
|
||||
|
||||
/* Set pause and remove the single mode. */
|
||||
/* Pause if we are in single mode. */
|
||||
if(playlist->queue.single && !playlist->queue.repeat) {
|
||||
playlist->queue.single = false;
|
||||
idle_add(IDLE_OPTIONS);
|
||||
|
||||
pc_set_pause(true);
|
||||
}
|
||||
|
||||
@@ -238,7 +235,7 @@ playlist_sync(struct playlist *playlist)
|
||||
|
||||
/* make sure the queued song is always set (if
|
||||
possible) */
|
||||
if (pc.next_song == NULL && playlist->queued != -1)
|
||||
if (pc.next_song == NULL && playlist->queued < 0)
|
||||
playlist_update_queued_song(playlist, NULL);
|
||||
}
|
||||
}
|
||||
|
321
src/playlist/rss_playlist_plugin.c
Normal file
321
src/playlist/rss_playlist_plugin.c
Normal file
@@ -0,0 +1,321 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2010 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 "config.h"
|
||||
#include "playlist/rss_playlist_plugin.h"
|
||||
#include "playlist_plugin.h"
|
||||
#include "input_stream.h"
|
||||
#include "song.h"
|
||||
#include "tag.h"
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
|
||||
#undef G_LOG_DOMAIN
|
||||
#define G_LOG_DOMAIN "rss"
|
||||
|
||||
/**
|
||||
* This is the state object for the GLib XML parser.
|
||||
*/
|
||||
struct rss_parser {
|
||||
/**
|
||||
* The list of songs (in reverse order because that's faster
|
||||
* while adding).
|
||||
*/
|
||||
GSList *songs;
|
||||
|
||||
/**
|
||||
* The current position in the XML file.
|
||||
*/
|
||||
enum {
|
||||
ROOT, ITEM,
|
||||
} state;
|
||||
|
||||
/**
|
||||
* The current tag within the "entry" element. This is only
|
||||
* valid if state==ITEM. TAG_NUM_OF_ITEM_TYPES means there
|
||||
* is no (known) tag.
|
||||
*/
|
||||
enum tag_type tag;
|
||||
|
||||
/**
|
||||
* The current song. It is allocated after the "location"
|
||||
* element.
|
||||
*/
|
||||
struct song *song;
|
||||
};
|
||||
|
||||
static const gchar *
|
||||
get_attribute(const gchar **attribute_names, const gchar **attribute_values,
|
||||
const gchar *name)
|
||||
{
|
||||
for (unsigned i = 0; attribute_names[i] != NULL; ++i)
|
||||
if (g_ascii_strcasecmp(attribute_names[i], name) == 0)
|
||||
return attribute_values[i];
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void
|
||||
rss_start_element(G_GNUC_UNUSED GMarkupParseContext *context,
|
||||
const gchar *element_name,
|
||||
const gchar **attribute_names,
|
||||
const gchar **attribute_values,
|
||||
gpointer user_data, G_GNUC_UNUSED GError **error)
|
||||
{
|
||||
struct rss_parser *parser = user_data;
|
||||
|
||||
switch (parser->state) {
|
||||
case ROOT:
|
||||
if (g_ascii_strcasecmp(element_name, "item") == 0) {
|
||||
parser->state = ITEM;
|
||||
parser->song = song_remote_new("rss:");
|
||||
parser->tag = TAG_NUM_OF_ITEM_TYPES;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case ITEM:
|
||||
if (g_ascii_strcasecmp(element_name, "enclosure") == 0) {
|
||||
const gchar *href = get_attribute(attribute_names,
|
||||
attribute_values,
|
||||
"url");
|
||||
if (href != NULL) {
|
||||
/* create new song object, and copy
|
||||
the existing tag over; we cannot
|
||||
replace the existing song's URI,
|
||||
because that attribute is
|
||||
immutable */
|
||||
struct song *song = song_remote_new(href);
|
||||
|
||||
if (parser->song != NULL) {
|
||||
song->tag = parser->song->tag;
|
||||
parser->song->tag = NULL;
|
||||
song_free(parser->song);
|
||||
}
|
||||
|
||||
parser->song = song;
|
||||
}
|
||||
} else if (g_ascii_strcasecmp(element_name, "title") == 0)
|
||||
parser->tag = TAG_TITLE;
|
||||
else if (g_ascii_strcasecmp(element_name, "itunes:author") == 0)
|
||||
parser->tag = TAG_ARTIST;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
rss_end_element(G_GNUC_UNUSED GMarkupParseContext *context,
|
||||
const gchar *element_name,
|
||||
gpointer user_data, G_GNUC_UNUSED GError **error)
|
||||
{
|
||||
struct rss_parser *parser = user_data;
|
||||
|
||||
switch (parser->state) {
|
||||
case ROOT:
|
||||
break;
|
||||
|
||||
case ITEM:
|
||||
if (g_ascii_strcasecmp(element_name, "item") == 0) {
|
||||
if (strcmp(parser->song->uri, "rss:") != 0)
|
||||
parser->songs = g_slist_prepend(parser->songs,
|
||||
parser->song);
|
||||
else
|
||||
song_free(parser->song);
|
||||
|
||||
parser->state = ROOT;
|
||||
} else
|
||||
parser->tag = TAG_NUM_OF_ITEM_TYPES;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
rss_text(G_GNUC_UNUSED GMarkupParseContext *context,
|
||||
const gchar *text, gsize text_len,
|
||||
gpointer user_data, G_GNUC_UNUSED GError **error)
|
||||
{
|
||||
struct rss_parser *parser = user_data;
|
||||
|
||||
switch (parser->state) {
|
||||
case ROOT:
|
||||
break;
|
||||
|
||||
case ITEM:
|
||||
if (parser->tag != TAG_NUM_OF_ITEM_TYPES) {
|
||||
if (parser->song->tag == NULL)
|
||||
parser->song->tag = tag_new();
|
||||
tag_add_item_n(parser->song->tag, parser->tag,
|
||||
text, text_len);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static const GMarkupParser rss_parser = {
|
||||
.start_element = rss_start_element,
|
||||
.end_element = rss_end_element,
|
||||
.text = rss_text,
|
||||
};
|
||||
|
||||
static void
|
||||
song_free_callback(gpointer data, G_GNUC_UNUSED gpointer user_data)
|
||||
{
|
||||
struct song *song = data;
|
||||
|
||||
song_free(song);
|
||||
}
|
||||
|
||||
static void
|
||||
rss_parser_destroy(gpointer data)
|
||||
{
|
||||
struct rss_parser *parser = data;
|
||||
|
||||
if (parser->state >= ITEM)
|
||||
song_free(parser->song);
|
||||
|
||||
g_slist_foreach(parser->songs, song_free_callback, NULL);
|
||||
g_slist_free(parser->songs);
|
||||
}
|
||||
|
||||
/*
|
||||
* The playlist object
|
||||
*
|
||||
*/
|
||||
|
||||
struct rss_playlist {
|
||||
struct playlist_provider base;
|
||||
|
||||
GSList *songs;
|
||||
};
|
||||
|
||||
static struct playlist_provider *
|
||||
rss_open_stream(struct input_stream *is)
|
||||
{
|
||||
struct rss_parser parser = {
|
||||
.songs = NULL,
|
||||
.state = ROOT,
|
||||
};
|
||||
struct rss_playlist *playlist;
|
||||
GMarkupParseContext *context;
|
||||
char buffer[1024];
|
||||
size_t nbytes;
|
||||
bool success;
|
||||
GError *error = NULL;
|
||||
|
||||
/* parse the RSS XML file */
|
||||
|
||||
context = g_markup_parse_context_new(&rss_parser,
|
||||
G_MARKUP_TREAT_CDATA_AS_TEXT,
|
||||
&parser, rss_parser_destroy);
|
||||
|
||||
while (true) {
|
||||
nbytes = input_stream_read(is, buffer, sizeof(buffer), &error);
|
||||
if (nbytes == 0) {
|
||||
if (error != NULL) {
|
||||
g_markup_parse_context_free(context);
|
||||
g_warning("%s", error->message);
|
||||
g_error_free(error);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
success = g_markup_parse_context_parse(context, buffer, nbytes,
|
||||
&error);
|
||||
if (!success) {
|
||||
g_warning("XML parser failed: %s", error->message);
|
||||
g_error_free(error);
|
||||
g_markup_parse_context_free(context);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
success = g_markup_parse_context_end_parse(context, &error);
|
||||
if (!success) {
|
||||
g_warning("XML parser failed: %s", error->message);
|
||||
g_error_free(error);
|
||||
g_markup_parse_context_free(context);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* create a #rss_playlist object from the parsed song list */
|
||||
|
||||
playlist = g_new(struct rss_playlist, 1);
|
||||
playlist_provider_init(&playlist->base, &rss_playlist_plugin);
|
||||
playlist->songs = g_slist_reverse(parser.songs);
|
||||
parser.songs = NULL;
|
||||
|
||||
g_markup_parse_context_free(context);
|
||||
|
||||
return &playlist->base;
|
||||
}
|
||||
|
||||
static void
|
||||
rss_close(struct playlist_provider *_playlist)
|
||||
{
|
||||
struct rss_playlist *playlist = (struct rss_playlist *)_playlist;
|
||||
|
||||
g_slist_foreach(playlist->songs, song_free_callback, NULL);
|
||||
g_slist_free(playlist->songs);
|
||||
g_free(playlist);
|
||||
}
|
||||
|
||||
static struct song *
|
||||
rss_read(struct playlist_provider *_playlist)
|
||||
{
|
||||
struct rss_playlist *playlist = (struct rss_playlist *)_playlist;
|
||||
struct song *song;
|
||||
|
||||
if (playlist->songs == NULL)
|
||||
return NULL;
|
||||
|
||||
song = playlist->songs->data;
|
||||
playlist->songs = g_slist_remove(playlist->songs, song);
|
||||
|
||||
return song;
|
||||
}
|
||||
|
||||
static const char *const rss_suffixes[] = {
|
||||
"rss",
|
||||
NULL
|
||||
};
|
||||
|
||||
static const char *const rss_mime_types[] = {
|
||||
"application/rss+xml",
|
||||
"text/xml",
|
||||
NULL
|
||||
};
|
||||
|
||||
const struct playlist_plugin rss_playlist_plugin = {
|
||||
.name = "rss",
|
||||
|
||||
.open_stream = rss_open_stream,
|
||||
.close = rss_close,
|
||||
.read = rss_read,
|
||||
|
||||
.suffixes = rss_suffixes,
|
||||
.mime_types = rss_mime_types,
|
||||
};
|
25
src/playlist/rss_playlist_plugin.h
Normal file
25
src/playlist/rss_playlist_plugin.h
Normal file
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2010 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_PLAYLIST_RSS_PLAYLIST_PLUGIN_H
|
||||
#define MPD_PLAYLIST_RSS_PLAYLIST_PLUGIN_H
|
||||
|
||||
extern const struct playlist_plugin rss_playlist_plugin;
|
||||
|
||||
#endif
|
@@ -46,7 +46,9 @@ bool
|
||||
playlist_metadata_load(FILE *fp, struct playlist_vector *pv, const char *name,
|
||||
GString *buffer, GError **error_r)
|
||||
{
|
||||
struct playlist_metadata pm;
|
||||
struct playlist_metadata pm = {
|
||||
.mtime = 0,
|
||||
};
|
||||
char *line, *colon;
|
||||
const char *value;
|
||||
|
||||
|
@@ -26,6 +26,7 @@
|
||||
#include "playlist/lastfm_playlist_plugin.h"
|
||||
#include "playlist/pls_playlist_plugin.h"
|
||||
#include "playlist/asx_playlist_plugin.h"
|
||||
#include "playlist/rss_playlist_plugin.h"
|
||||
#include "playlist/cue_playlist_plugin.h"
|
||||
#include "playlist/flac_playlist_plugin.h"
|
||||
#include "input_stream.h"
|
||||
@@ -33,6 +34,7 @@
|
||||
#include "utils.h"
|
||||
#include "conf.h"
|
||||
#include "glib_compat.h"
|
||||
#include "mpd_error.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
@@ -44,6 +46,7 @@ static const struct playlist_plugin *const playlist_plugins[] = {
|
||||
&xspf_playlist_plugin,
|
||||
&pls_playlist_plugin,
|
||||
&asx_playlist_plugin,
|
||||
&rss_playlist_plugin,
|
||||
#ifdef ENABLE_LASTFM
|
||||
&lastfm_playlist_plugin,
|
||||
#endif
|
||||
@@ -76,7 +79,7 @@ playlist_plugin_config(const char *plugin_name)
|
||||
const char *name =
|
||||
config_get_block_string(param, "name", NULL);
|
||||
if (name == NULL)
|
||||
g_error("playlist configuration without 'plugin' name in line %d",
|
||||
MPD_ERROR("playlist configuration without 'plugin' name in line %d",
|
||||
param->line);
|
||||
|
||||
if (strcmp(name, plugin_name) == 0)
|
||||
|
@@ -72,6 +72,14 @@ apply_song_metadata(struct song *dest, const struct song *src)
|
||||
song_free(dest);
|
||||
}
|
||||
|
||||
if (dest->tag != NULL && dest->tag->time > 0 &&
|
||||
src->start_ms > 0 && src->end_ms == 0 &&
|
||||
src->start_ms / 1000 < (unsigned)dest->tag->time)
|
||||
/* the range is open-ended, and the playlist plugin
|
||||
did not know the total length of the song file
|
||||
(e.g. last track on a CUE file); fix it up here */
|
||||
tmp->tag->time = dest->tag->time - src->start_ms / 1000;
|
||||
|
||||
return tmp;
|
||||
}
|
||||
|
||||
|
@@ -59,7 +59,7 @@ playlist_state_save(FILE *fp, const struct playlist *playlist)
|
||||
|
||||
pc_get_status(&player_status);
|
||||
|
||||
fputs(PLAYLIST_STATE_FILE_STATE "\n", fp);
|
||||
fputs(PLAYLIST_STATE_FILE_STATE, fp);
|
||||
|
||||
if (playlist->playing) {
|
||||
switch (player_status.state) {
|
||||
|
@@ -93,16 +93,21 @@ playlist_vector_add(struct playlist_vector *pv,
|
||||
pv->head = pm;
|
||||
}
|
||||
|
||||
void
|
||||
bool
|
||||
playlist_vector_update_or_add(struct playlist_vector *pv,
|
||||
const char *name, time_t mtime)
|
||||
{
|
||||
struct playlist_metadata **pmp = playlist_vector_find_p(pv, name);
|
||||
if (pmp != NULL) {
|
||||
struct playlist_metadata *pm = *pmp;
|
||||
if (mtime == pm->mtime)
|
||||
return false;
|
||||
|
||||
pm->mtime = mtime;
|
||||
} else
|
||||
playlist_vector_add(pv, name, mtime);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
|
@@ -58,7 +58,10 @@ void
|
||||
playlist_vector_add(struct playlist_vector *pv,
|
||||
const char *name, time_t mtime);
|
||||
|
||||
void
|
||||
/**
|
||||
* @return true if the vector or one of its items was modified
|
||||
*/
|
||||
bool
|
||||
playlist_vector_update_or_add(struct playlist_vector *pv,
|
||||
const char *name, time_t mtime);
|
||||
|
||||
|
@@ -47,7 +47,7 @@ poison_noaccess(void *p, size_t length)
|
||||
memset(p, 0x01, length);
|
||||
|
||||
#ifdef HAVE_VALGRIND_MEMCHECK_H
|
||||
VALGRIND_MAKE_MEM_NOACCESS(p, length);
|
||||
(void)VALGRIND_MAKE_MEM_NOACCESS(p, length);
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
@@ -68,7 +68,7 @@ poison_undefined(void *p, size_t length)
|
||||
memset(p, 0x02, length);
|
||||
|
||||
#ifdef HAVE_VALGRIND_MEMCHECK_H
|
||||
VALGRIND_MAKE_MEM_UNDEFINED(p, length);
|
||||
(void)VALGRIND_MAKE_MEM_UNDEFINED(p, length);
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
78
src/replay_gain_ape.c
Normal file
78
src/replay_gain_ape.c
Normal file
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2010 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 "config.h"
|
||||
#include "replay_gain_ape.h"
|
||||
#include "replay_gain_info.h"
|
||||
#include "ape.h"
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
struct rg_ape_ctx {
|
||||
struct replay_gain_info *info;
|
||||
bool found;
|
||||
};
|
||||
|
||||
static bool
|
||||
replay_gain_ape_callback(unsigned long flags, const char *key,
|
||||
const char *_value, size_t value_length, void *_ctx)
|
||||
{
|
||||
struct rg_ape_ctx *ctx = _ctx;
|
||||
|
||||
/* we only care about utf-8 text tags */
|
||||
if ((flags & (0x3 << 1)) != 0)
|
||||
return true;
|
||||
|
||||
char value[16];
|
||||
if (value_length >= sizeof(value))
|
||||
return true;
|
||||
memcpy(value, _value, value_length);
|
||||
value[value_length] = 0;
|
||||
|
||||
if (g_ascii_strcasecmp(key, "replaygain_track_gain") == 0) {
|
||||
ctx->info->tuples[REPLAY_GAIN_TRACK].gain = atof(value);
|
||||
ctx->found = true;
|
||||
} else if (g_ascii_strcasecmp(key, "replaygain_album_gain") == 0) {
|
||||
ctx->info->tuples[REPLAY_GAIN_ALBUM].gain = atof(value);
|
||||
ctx->found = true;
|
||||
} else if (g_ascii_strcasecmp(key, "replaygain_track_peak") == 0) {
|
||||
ctx->info->tuples[REPLAY_GAIN_TRACK].peak = atof(value);
|
||||
ctx->found = true;
|
||||
} else if (g_ascii_strcasecmp(key, "replaygain_album_peak") == 0) {
|
||||
ctx->info->tuples[REPLAY_GAIN_ALBUM].peak = atof(value);
|
||||
ctx->found = true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
replay_gain_ape_read(const char *path_fs, struct replay_gain_info *info)
|
||||
{
|
||||
struct rg_ape_ctx ctx = {
|
||||
.info = info,
|
||||
.found = false,
|
||||
};
|
||||
|
||||
return tag_ape_scan(path_fs, replay_gain_ape_callback, &ctx) &&
|
||||
ctx.found;
|
||||
}
|
32
src/replay_gain_ape.h
Normal file
32
src/replay_gain_ape.h
Normal file
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2010 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_REPLAY_GAIN_APE_H
|
||||
#define MPD_REPLAY_GAIN_APE_H
|
||||
|
||||
#include "check.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
struct replay_gain_info;
|
||||
|
||||
bool
|
||||
replay_gain_ape_read(const char *path_fs, struct replay_gain_info *info);
|
||||
|
||||
#endif
|
@@ -22,6 +22,7 @@
|
||||
#include "playlist.h"
|
||||
#include "conf.h"
|
||||
#include "idle.h"
|
||||
#include "mpd_error.h"
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
@@ -91,8 +92,8 @@ void replay_gain_global_init(void)
|
||||
const struct config_param *param = config_get_param(CONF_REPLAYGAIN);
|
||||
|
||||
if (param != NULL && !replay_gain_set_mode_string(param->value)) {
|
||||
g_error("replaygain value \"%s\" at line %i is invalid\n",
|
||||
param->value, param->line);
|
||||
MPD_ERROR("replaygain value \"%s\" at line %i is invalid\n",
|
||||
param->value, param->line);
|
||||
}
|
||||
|
||||
param = config_get_param(CONF_REPLAYGAIN_PREAMP);
|
||||
@@ -102,13 +103,13 @@ void replay_gain_global_init(void)
|
||||
float f = strtod(param->value, &test);
|
||||
|
||||
if (*test != '\0') {
|
||||
g_error("Replaygain preamp \"%s\" is not a number at "
|
||||
"line %i\n", param->value, param->line);
|
||||
MPD_ERROR("Replaygain preamp \"%s\" is not a number at "
|
||||
"line %i\n", param->value, param->line);
|
||||
}
|
||||
|
||||
if (f < -15 || f > 15) {
|
||||
g_error("Replaygain preamp \"%s\" is not between -15 and"
|
||||
"15 at line %i\n", param->value, param->line);
|
||||
MPD_ERROR("Replaygain preamp \"%s\" is not between -15 and"
|
||||
"15 at line %i\n", param->value, param->line);
|
||||
}
|
||||
|
||||
replay_gain_preamp = pow(10, f / 20.0);
|
||||
@@ -121,13 +122,13 @@ void replay_gain_global_init(void)
|
||||
float f = strtod(param->value, &test);
|
||||
|
||||
if (*test != '\0') {
|
||||
g_error("Replaygain missing preamp \"%s\" is not a number at "
|
||||
"line %i\n", param->value, param->line);
|
||||
MPD_ERROR("Replaygain missing preamp \"%s\" is not a number at "
|
||||
"line %i\n", param->value, param->line);
|
||||
}
|
||||
|
||||
if (f < -15 || f > 15) {
|
||||
g_error("Replaygain missing preamp \"%s\" is not between -15 and"
|
||||
"15 at line %i\n", param->value, param->line);
|
||||
MPD_ERROR("Replaygain missing preamp \"%s\" is not between -15 and"
|
||||
"15 at line %i\n", param->value, param->line);
|
||||
}
|
||||
|
||||
replay_gain_missing_preamp = pow(10, f / 20.0);
|
||||
|
444
src/server_socket.c
Normal file
444
src/server_socket.c
Normal file
@@ -0,0 +1,444 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2010 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 "config.h"
|
||||
#include "server_socket.h"
|
||||
#include "socket_util.h"
|
||||
#include "fd_util.h"
|
||||
#include "glib_compat.h"
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <unistd.h>
|
||||
#include <stdlib.h>
|
||||
#include <assert.h>
|
||||
|
||||
#ifdef WIN32
|
||||
#define WINVER 0x0501
|
||||
#include <ws2tcpip.h>
|
||||
#include <winsock.h>
|
||||
#else
|
||||
#include <netinet/in.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/un.h>
|
||||
#include <netdb.h>
|
||||
#endif
|
||||
|
||||
#undef G_LOG_DOMAIN
|
||||
#define G_LOG_DOMAIN "listen"
|
||||
|
||||
#define DEFAULT_PORT 6600
|
||||
|
||||
struct one_socket {
|
||||
struct one_socket *next;
|
||||
struct server_socket *parent;
|
||||
|
||||
unsigned serial;
|
||||
|
||||
int fd;
|
||||
guint source_id;
|
||||
|
||||
char *path;
|
||||
|
||||
size_t address_length;
|
||||
struct sockaddr address;
|
||||
};
|
||||
|
||||
struct server_socket {
|
||||
server_socket_callback_t callback;
|
||||
void *callback_ctx;
|
||||
|
||||
struct one_socket *sockets, **sockets_tail_r;
|
||||
unsigned next_serial;
|
||||
};
|
||||
|
||||
static GQuark
|
||||
server_socket_quark(void)
|
||||
{
|
||||
return g_quark_from_static_string("server_socket");
|
||||
}
|
||||
|
||||
struct server_socket *
|
||||
server_socket_new(server_socket_callback_t callback, void *callback_ctx)
|
||||
{
|
||||
struct server_socket *ss = g_new(struct server_socket, 1);
|
||||
ss->callback = callback;
|
||||
ss->callback_ctx = callback_ctx;
|
||||
ss->sockets = NULL;
|
||||
ss->sockets_tail_r = &ss->sockets;
|
||||
ss->next_serial = 1;
|
||||
return ss;
|
||||
}
|
||||
|
||||
void
|
||||
server_socket_free(struct server_socket *ss)
|
||||
{
|
||||
server_socket_close(ss);
|
||||
|
||||
while (ss->sockets != NULL) {
|
||||
struct one_socket *s = ss->sockets;
|
||||
ss->sockets = s->next;
|
||||
|
||||
assert(s->fd < 0);
|
||||
|
||||
g_free(s->path);
|
||||
g_free(s);
|
||||
}
|
||||
|
||||
g_free(ss);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraper for sockaddr_to_string() which never fails.
|
||||
*/
|
||||
static char *
|
||||
one_socket_to_string(const struct one_socket *s)
|
||||
{
|
||||
char *p = sockaddr_to_string(&s->address, s->address_length, NULL);
|
||||
if (p == NULL)
|
||||
p = g_strdup("[unknown]");
|
||||
return p;
|
||||
}
|
||||
|
||||
static int
|
||||
get_remote_uid(int fd)
|
||||
{
|
||||
#ifdef HAVE_STRUCT_UCRED
|
||||
struct ucred cred;
|
||||
socklen_t len = sizeof (cred);
|
||||
|
||||
if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &cred, &len) < 0)
|
||||
return 0;
|
||||
|
||||
return cred.uid;
|
||||
#else
|
||||
#ifdef HAVE_GETPEEREID
|
||||
uid_t euid;
|
||||
gid_t egid;
|
||||
|
||||
if (getpeereid(fd, &euid, &egid) == 0)
|
||||
return euid;
|
||||
#else
|
||||
(void)fd;
|
||||
#endif
|
||||
return -1;
|
||||
#endif
|
||||
}
|
||||
|
||||
static gboolean
|
||||
server_socket_in_event(G_GNUC_UNUSED GIOChannel *source,
|
||||
G_GNUC_UNUSED GIOCondition condition,
|
||||
gpointer data)
|
||||
{
|
||||
struct one_socket *s = data;
|
||||
|
||||
struct sockaddr_storage address;
|
||||
size_t address_length = sizeof(address);
|
||||
int fd = accept_cloexec_nonblock(s->fd, (struct sockaddr*)&address,
|
||||
&address_length);
|
||||
if (fd >= 0)
|
||||
s->parent->callback(fd, (const struct sockaddr*)&address,
|
||||
address_length, get_remote_uid(fd),
|
||||
s->parent->callback_ctx);
|
||||
else
|
||||
g_warning("accept() failed: %s", g_strerror(errno));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
server_socket_open(struct server_socket *ss, GError **error_r)
|
||||
{
|
||||
struct one_socket *good = NULL, *bad = NULL;
|
||||
GError *last_error = NULL;
|
||||
|
||||
for (struct one_socket *s = ss->sockets; s != NULL; s = s->next) {
|
||||
assert(s->serial > 0);
|
||||
assert(good == NULL || s->serial >= good->serial);
|
||||
assert(s->fd < 0);
|
||||
|
||||
if (bad != NULL && s->serial != bad->serial) {
|
||||
server_socket_close(ss);
|
||||
g_propagate_error(error_r, last_error);
|
||||
return false;
|
||||
}
|
||||
|
||||
GError *error = NULL;
|
||||
s->fd = socket_bind_listen(s->address.sa_family, SOCK_STREAM, 0,
|
||||
&s->address, s->address_length, 5,
|
||||
&error);
|
||||
if (s->fd < 0) {
|
||||
if (good != NULL && good->serial == s->serial) {
|
||||
char *address_string = one_socket_to_string(s);
|
||||
char *good_string = one_socket_to_string(good);
|
||||
g_warning("bind to '%s' failed: %s "
|
||||
"(continuing anyway, because "
|
||||
"binding to '%s' succeeded)",
|
||||
address_string, error->message,
|
||||
good_string);
|
||||
g_free(address_string);
|
||||
g_free(good_string);
|
||||
g_error_free(error);
|
||||
} else if (bad == NULL) {
|
||||
bad = s;
|
||||
|
||||
char *address_string = one_socket_to_string(s);
|
||||
g_propagate_prefixed_error(&last_error, error,
|
||||
"Failed to bind to '%s': ",
|
||||
address_string);
|
||||
g_free(address_string);
|
||||
} else
|
||||
g_error_free(error);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* allow everybody to connect */
|
||||
|
||||
if (s->path != NULL)
|
||||
chmod(s->path, 0666);
|
||||
|
||||
/* register in the GLib main loop */
|
||||
|
||||
GIOChannel *channel = g_io_channel_unix_new(s->fd);
|
||||
s->source_id = g_io_add_watch(channel, G_IO_IN,
|
||||
server_socket_in_event, s);
|
||||
g_io_channel_unref(channel);
|
||||
|
||||
/* mark this socket as "good", and clear previous
|
||||
errors */
|
||||
|
||||
good = s;
|
||||
|
||||
if (bad != NULL) {
|
||||
bad = NULL;
|
||||
g_error_free(last_error);
|
||||
last_error = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
if (bad != NULL) {
|
||||
server_socket_close(ss);
|
||||
g_propagate_error(error_r, last_error);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
server_socket_close(struct server_socket *ss)
|
||||
{
|
||||
for (struct one_socket *s = ss->sockets; s != NULL; s = s->next) {
|
||||
if (s->fd < 0)
|
||||
continue;
|
||||
|
||||
g_source_remove(s->source_id);
|
||||
close(s->fd);
|
||||
s->fd = -1;
|
||||
}
|
||||
}
|
||||
|
||||
static struct one_socket *
|
||||
one_socket_new(unsigned serial, const struct sockaddr *address,
|
||||
size_t address_length)
|
||||
{
|
||||
assert(address != NULL);
|
||||
assert(address_length > 0);
|
||||
|
||||
struct one_socket *s = g_malloc(sizeof(*s) - sizeof(s->address) +
|
||||
address_length);
|
||||
s->next = NULL;
|
||||
s->serial = serial;
|
||||
s->fd = -1;
|
||||
s->path = NULL;
|
||||
s->address_length = address_length;
|
||||
memcpy(&s->address, address, address_length);
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
static struct one_socket *
|
||||
server_socket_add_address(struct server_socket *ss,
|
||||
const struct sockaddr *address,
|
||||
size_t address_length)
|
||||
{
|
||||
assert(ss != NULL);
|
||||
assert(ss->sockets_tail_r != NULL);
|
||||
assert(*ss->sockets_tail_r == NULL);
|
||||
|
||||
struct one_socket *s = one_socket_new(ss->next_serial,
|
||||
address, address_length);
|
||||
s->parent = ss;
|
||||
*ss->sockets_tail_r = s;
|
||||
ss->sockets_tail_r = &s->next;
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
#ifdef HAVE_TCP
|
||||
|
||||
/**
|
||||
* Add a listener on a port on all IPv4 interfaces.
|
||||
*
|
||||
* @param port the TCP port
|
||||
*/
|
||||
static void
|
||||
server_socket_add_port_ipv4(struct server_socket *ss, unsigned port)
|
||||
{
|
||||
struct sockaddr_in sin;
|
||||
memset(&sin, 0, sizeof(sin));
|
||||
sin.sin_port = htons(port);
|
||||
sin.sin_family = AF_INET;
|
||||
sin.sin_addr.s_addr = INADDR_ANY;
|
||||
|
||||
server_socket_add_address(ss, (const struct sockaddr *)&sin,
|
||||
sizeof(sin));
|
||||
}
|
||||
|
||||
#ifdef HAVE_IPV6
|
||||
/**
|
||||
* Add a listener on a port on all IPv6 interfaces.
|
||||
*
|
||||
* @param port the TCP port
|
||||
*/
|
||||
static void
|
||||
server_socket_add_port_ipv6(struct server_socket *ss, unsigned port)
|
||||
{
|
||||
struct sockaddr_in6 sin;
|
||||
memset(&sin, 0, sizeof(sin));
|
||||
sin.sin6_port = htons(port);
|
||||
sin.sin6_family = AF_INET6;
|
||||
|
||||
server_socket_add_address(ss, (const struct sockaddr *)&sin,
|
||||
sizeof(sin));
|
||||
}
|
||||
#endif /* HAVE_IPV6 */
|
||||
|
||||
#endif /* HAVE_TCP */
|
||||
|
||||
bool
|
||||
server_socket_add_port(struct server_socket *ss, unsigned port,
|
||||
GError **error_r)
|
||||
{
|
||||
#ifdef HAVE_TCP
|
||||
if (port == 0 || port > 0xffff) {
|
||||
g_set_error(error_r, server_socket_quark(), 0,
|
||||
"Invalid TCP port");
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef HAVE_IPV6
|
||||
server_socket_add_port_ipv6(ss, port);
|
||||
#endif
|
||||
server_socket_add_port_ipv4(ss, port);
|
||||
|
||||
++ss->next_serial;
|
||||
|
||||
return true;
|
||||
#else /* HAVE_TCP */
|
||||
(void)ss;
|
||||
(void)port;
|
||||
|
||||
g_set_error(error_r, server_socket_quark(), 0,
|
||||
"TCP support is disabled");
|
||||
return false;
|
||||
#endif /* HAVE_TCP */
|
||||
}
|
||||
|
||||
bool
|
||||
server_socket_add_host(struct server_socket *ss, const char *hostname,
|
||||
unsigned port, GError **error_r)
|
||||
{
|
||||
#ifdef HAVE_TCP
|
||||
struct addrinfo hints;
|
||||
memset(&hints, 0, sizeof(hints));
|
||||
hints.ai_flags = AI_PASSIVE;
|
||||
hints.ai_family = PF_UNSPEC;
|
||||
hints.ai_socktype = SOCK_STREAM;
|
||||
hints.ai_protocol = IPPROTO_TCP;
|
||||
|
||||
char service[20];
|
||||
g_snprintf(service, sizeof(service), "%u", port);
|
||||
|
||||
struct addrinfo *ai;
|
||||
int ret = getaddrinfo(hostname, service, &hints, &ai);
|
||||
if (ret != 0) {
|
||||
g_set_error(error_r, server_socket_quark(), ret,
|
||||
"Failed to look up host \"%s\": %s",
|
||||
hostname, gai_strerror(ret));
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const struct addrinfo *i = ai; i != NULL; i = i->ai_next)
|
||||
server_socket_add_address(ss, i->ai_addr, i->ai_addrlen);
|
||||
|
||||
freeaddrinfo(ai);
|
||||
|
||||
++ss->next_serial;
|
||||
|
||||
return true;
|
||||
#else /* HAVE_TCP */
|
||||
(void)ss;
|
||||
(void)hostname;
|
||||
(void)port;
|
||||
|
||||
g_set_error(error_r, server_socket_quark(), 0,
|
||||
"TCP support is disabled");
|
||||
return false;
|
||||
#endif /* HAVE_TCP */
|
||||
}
|
||||
|
||||
bool
|
||||
server_socket_add_path(struct server_socket *ss, const char *path,
|
||||
GError **error_r)
|
||||
{
|
||||
#ifdef HAVE_UN
|
||||
struct sockaddr_un s_un;
|
||||
|
||||
size_t path_length = strlen(path);
|
||||
if (path_length >= sizeof(s_un.sun_path)) {
|
||||
g_set_error(error_r, server_socket_quark(), 0,
|
||||
"UNIX socket path is too long");
|
||||
return false;
|
||||
}
|
||||
|
||||
unlink(path);
|
||||
|
||||
s_un.sun_family = AF_UNIX;
|
||||
memcpy(s_un.sun_path, path, path_length + 1);
|
||||
|
||||
struct one_socket *s =
|
||||
server_socket_add_address(ss, (const struct sockaddr *)&s_un,
|
||||
sizeof(s_un));
|
||||
s->path = g_strdup(path);
|
||||
|
||||
return true;
|
||||
#else /* !HAVE_UN */
|
||||
(void)ss;
|
||||
(void)path;
|
||||
|
||||
g_set_error(error_r, server_socket_quark(), 0,
|
||||
"UNIX domain socket support is disabled");
|
||||
return false;
|
||||
#endif /* !HAVE_UN */
|
||||
}
|
||||
|
84
src/server_socket.h
Normal file
84
src/server_socket.h
Normal file
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2010 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_SERVER_SOCKET_H
|
||||
#define MPD_SERVER_SOCKET_H
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
struct sockaddr;
|
||||
|
||||
typedef void (*server_socket_callback_t)(int fd,
|
||||
const struct sockaddr *address,
|
||||
size_t address_length, int uid,
|
||||
void *ctx);
|
||||
|
||||
struct server_socket *
|
||||
server_socket_new(server_socket_callback_t callback, void *callback_ctx);
|
||||
|
||||
void
|
||||
server_socket_free(struct server_socket *ss);
|
||||
|
||||
bool
|
||||
server_socket_open(struct server_socket *ss, GError **error_r);
|
||||
|
||||
void
|
||||
server_socket_close(struct server_socket *ss);
|
||||
|
||||
/**
|
||||
* Add a listener on a port on all interfaces.
|
||||
*
|
||||
* @param port the TCP port
|
||||
* @param error_r location to store the error occuring, or NULL to
|
||||
* ignore errors
|
||||
* @return true on success
|
||||
*/
|
||||
bool
|
||||
server_socket_add_port(struct server_socket *ss, unsigned port,
|
||||
GError **error_r);
|
||||
|
||||
/**
|
||||
* Resolves a host name, and adds listeners on all addresses in the
|
||||
* result set.
|
||||
*
|
||||
* @param hostname the host name to be resolved
|
||||
* @param port the TCP port
|
||||
* @param error_r location to store the error occuring, or NULL to
|
||||
* ignore errors
|
||||
* @return true on success
|
||||
*/
|
||||
bool
|
||||
server_socket_add_host(struct server_socket *ss, const char *hostname,
|
||||
unsigned port, GError **error_r);
|
||||
|
||||
/**
|
||||
* Add a listener on a Unix domain socket.
|
||||
*
|
||||
* @param path the absolute socket path
|
||||
* @param error_r location to store the error occuring, or NULL to
|
||||
* ignore errors
|
||||
* @return true on success
|
||||
*/
|
||||
bool
|
||||
server_socket_add_path(struct server_socket *ss, const char *path,
|
||||
GError **error_r);
|
||||
|
||||
#endif
|
@@ -25,6 +25,7 @@
|
||||
#include "log.h"
|
||||
#include "main.h"
|
||||
#include "event_pipe.h"
|
||||
#include "mpd_error.h"
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
@@ -46,7 +47,7 @@ static void
|
||||
x_sigaction(int signum, const struct sigaction *act)
|
||||
{
|
||||
if (sigaction(signum, act, NULL) < 0)
|
||||
g_error("sigaction() failed: %s", strerror(errno));
|
||||
MPD_ERROR("sigaction() failed: %s", strerror(errno));
|
||||
}
|
||||
|
||||
static void
|
||||
|
@@ -23,6 +23,7 @@
|
||||
#include "tag_pool.h"
|
||||
#include "conf.h"
|
||||
#include "song.h"
|
||||
#include "mpd_error.h"
|
||||
|
||||
#include <glib.h>
|
||||
#include <assert.h>
|
||||
@@ -138,8 +139,8 @@ void tag_lib_init(void)
|
||||
|
||||
type = tag_name_parse_i(c);
|
||||
if (type == TAG_NUM_OF_ITEM_TYPES)
|
||||
g_error("error parsing metadata item \"%s\"",
|
||||
c);
|
||||
MPD_ERROR("error parsing metadata item \"%s\"",
|
||||
c);
|
||||
|
||||
ignore_tag_items[type] = false;
|
||||
|
||||
|
128
src/tag_ape.c
128
src/tag_ape.c
@@ -21,11 +21,7 @@
|
||||
#include "tag_ape.h"
|
||||
#include "tag.h"
|
||||
#include "tag_table.h"
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include "ape.h"
|
||||
|
||||
static const char *const ape_tag_names[TAG_NUM_OF_ITEM_TYPES] = {
|
||||
[TAG_ALBUM_ARTIST] = "album artist",
|
||||
@@ -56,101 +52,45 @@ tag_ape_import_item(struct tag *tag, unsigned long flags,
|
||||
|
||||
if (tag == NULL)
|
||||
tag = tag_new();
|
||||
tag_add_item_n(tag, type, value, value_length);
|
||||
|
||||
const char *end = value + value_length;
|
||||
while (true) {
|
||||
/* multiple values are separated by null bytes */
|
||||
const char *n = memchr(value, 0, end - value);
|
||||
if (n != NULL) {
|
||||
if (n > value)
|
||||
tag_add_item_n(tag, type, value, n - value);
|
||||
value = n + 1;
|
||||
} else {
|
||||
if (end > value)
|
||||
tag_add_item_n(tag, type, value, end - value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return tag;
|
||||
}
|
||||
|
||||
struct tag_ape_ctx {
|
||||
struct tag *tag;
|
||||
};
|
||||
|
||||
static bool
|
||||
tag_ape_callback(unsigned long flags, const char *key,
|
||||
const char *value, size_t value_length, void *_ctx)
|
||||
{
|
||||
struct tag_ape_ctx *ctx = _ctx;
|
||||
|
||||
ctx->tag = tag_ape_import_item(ctx->tag, flags, key,
|
||||
value, value_length);
|
||||
return true;
|
||||
}
|
||||
|
||||
struct tag *
|
||||
tag_ape_load(const char *file)
|
||||
{
|
||||
struct tag *ret = NULL;
|
||||
FILE *fp;
|
||||
int tagCount;
|
||||
char *buffer = NULL;
|
||||
char *p;
|
||||
size_t tagLen;
|
||||
size_t size;
|
||||
unsigned long flags;
|
||||
char *key;
|
||||
struct tag_ape_ctx ctx = { .tag = NULL };
|
||||
|
||||
struct {
|
||||
unsigned char id[8];
|
||||
uint32_t version;
|
||||
uint32_t length;
|
||||
uint32_t tagCount;
|
||||
unsigned char flags[4];
|
||||
unsigned char reserved[8];
|
||||
} footer;
|
||||
|
||||
fp = fopen(file, "rb");
|
||||
if (!fp)
|
||||
return NULL;
|
||||
|
||||
/* determine if file has an apeV2 tag */
|
||||
if (fseek(fp, 0, SEEK_END))
|
||||
goto fail;
|
||||
size = (size_t)ftell(fp);
|
||||
if (fseek(fp, size - sizeof(footer), SEEK_SET))
|
||||
goto fail;
|
||||
if (fread(&footer, 1, sizeof(footer), fp) != sizeof(footer))
|
||||
goto fail;
|
||||
if (memcmp(footer.id, "APETAGEX", sizeof(footer.id)) != 0)
|
||||
goto fail;
|
||||
if (GUINT32_FROM_LE(footer.version) != 2000)
|
||||
goto fail;
|
||||
|
||||
/* find beginning of ape tag */
|
||||
tagLen = GUINT32_FROM_LE(footer.length);
|
||||
if (tagLen <= sizeof(footer) + 10)
|
||||
goto fail;
|
||||
if (tagLen > 1024 * 1024)
|
||||
/* refuse to load more than one megabyte of tag data */
|
||||
goto fail;
|
||||
if (fseek(fp, size - tagLen, SEEK_SET))
|
||||
goto fail;
|
||||
|
||||
/* read tag into buffer */
|
||||
tagLen -= sizeof(footer);
|
||||
assert(tagLen > 10);
|
||||
|
||||
buffer = g_malloc(tagLen);
|
||||
if (fread(buffer, 1, tagLen, fp) != tagLen)
|
||||
goto fail;
|
||||
|
||||
/* read tags */
|
||||
tagCount = GUINT32_FROM_LE(footer.tagCount);
|
||||
p = buffer;
|
||||
while (tagCount-- && tagLen > 10) {
|
||||
size = GUINT32_FROM_LE(*(const uint32_t *)p);
|
||||
p += 4;
|
||||
tagLen -= 4;
|
||||
flags = GUINT32_FROM_LE(*(const uint32_t *)p);
|
||||
p += 4;
|
||||
tagLen -= 4;
|
||||
|
||||
/* get the key */
|
||||
key = p;
|
||||
while (tagLen > size && *p != '\0') {
|
||||
p++;
|
||||
tagLen--;
|
||||
}
|
||||
p++;
|
||||
tagLen--;
|
||||
|
||||
/* get the value */
|
||||
if (tagLen < size)
|
||||
goto fail;
|
||||
|
||||
ret = tag_ape_import_item(ret, flags, key, p, size);
|
||||
|
||||
p += size;
|
||||
tagLen -= size;
|
||||
}
|
||||
|
||||
fail:
|
||||
if (fp)
|
||||
fclose(fp);
|
||||
g_free(buffer);
|
||||
return ret;
|
||||
tag_ape_scan(file, tag_ape_callback, &ctx);
|
||||
return ctx.tag;
|
||||
}
|
||||
|
@@ -126,17 +126,16 @@ import_id3_string(bool is_id3v1, const id3_ucs4_t *ucs4)
|
||||
* - string list
|
||||
*/
|
||||
static void
|
||||
tag_id3_import_text(struct tag *dest, struct id3_tag *tag, const char *id,
|
||||
enum tag_type type)
|
||||
tag_id3_import_text_frame(struct tag *dest, struct id3_tag *tag,
|
||||
const struct id3_frame *frame,
|
||||
enum tag_type type)
|
||||
{
|
||||
struct id3_frame const *frame;
|
||||
id3_ucs4_t const *ucs4;
|
||||
id3_utf8_t *utf8;
|
||||
union id3_field const *field;
|
||||
unsigned int nstrings, i;
|
||||
|
||||
frame = id3_tag_findframe(tag, id, 0);
|
||||
if (frame == NULL || frame->nfields != 2)
|
||||
if (frame->nfields != 2)
|
||||
return;
|
||||
|
||||
/* check the encoding field */
|
||||
@@ -170,6 +169,20 @@ tag_id3_import_text(struct tag *dest, struct id3_tag *tag, const char *id,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Import all text frames with the specified id (ID3v2.4.0 section
|
||||
* 4.2). This is a wrapper for tag_id3_import_text_frame().
|
||||
*/
|
||||
static void
|
||||
tag_id3_import_text(struct tag *dest, struct id3_tag *tag, const char *id,
|
||||
enum tag_type type)
|
||||
{
|
||||
const struct id3_frame *frame;
|
||||
for (unsigned i = 0;
|
||||
(frame = id3_tag_findframe(tag, id, i)) != NULL; ++i)
|
||||
tag_id3_import_text_frame(dest, tag, frame, type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Import a "Comment frame" (ID3v2.4.0 section 4.10). It
|
||||
* contains 4 fields:
|
||||
@@ -180,16 +193,15 @@ tag_id3_import_text(struct tag *dest, struct id3_tag *tag, const char *id,
|
||||
* - full string (we use this one)
|
||||
*/
|
||||
static void
|
||||
tag_id3_import_comment(struct tag *dest, struct id3_tag *tag, const char *id,
|
||||
enum tag_type type)
|
||||
tag_id3_import_comment_frame(struct tag *dest, struct id3_tag *tag,
|
||||
const struct id3_frame *frame,
|
||||
enum tag_type type)
|
||||
{
|
||||
struct id3_frame const *frame;
|
||||
id3_ucs4_t const *ucs4;
|
||||
id3_utf8_t *utf8;
|
||||
union id3_field const *field;
|
||||
|
||||
frame = id3_tag_findframe(tag, id, 0);
|
||||
if (frame == NULL || frame->nfields != 4)
|
||||
if (frame->nfields != 4)
|
||||
return;
|
||||
|
||||
/* for now I only read the 4th field, with the fullstring */
|
||||
@@ -209,6 +221,20 @@ tag_id3_import_comment(struct tag *dest, struct id3_tag *tag, const char *id,
|
||||
g_free(utf8);
|
||||
}
|
||||
|
||||
/**
|
||||
* Import all comment frames (ID3v2.4.0 section 4.10). This is a
|
||||
* wrapper for tag_id3_import_comment_frame().
|
||||
*/
|
||||
static void
|
||||
tag_id3_import_comment(struct tag *dest, struct id3_tag *tag, const char *id,
|
||||
enum tag_type type)
|
||||
{
|
||||
const struct id3_frame *frame;
|
||||
for (unsigned i = 0;
|
||||
(frame = id3_tag_findframe(tag, id, i)) != NULL; ++i)
|
||||
tag_id3_import_comment_frame(dest, tag, frame, type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a TXXX name, and convert it to a tag_type enum value.
|
||||
* Returns TAG_NUM_OF_ITEM_TYPES if the TXXX name is not understood.
|
||||
|
13
src/timer.c
13
src/timer.c
@@ -71,6 +71,19 @@ void timer_add(Timer *timer, int size)
|
||||
timer->time += ((uint64_t)size * 1000000) / timer->rate;
|
||||
}
|
||||
|
||||
unsigned
|
||||
timer_delay(const Timer *timer)
|
||||
{
|
||||
int64_t delay = (int64_t)(timer->time - now()) / 1000;
|
||||
if (delay < 0)
|
||||
return 0;
|
||||
|
||||
if (delay > G_MAXINT)
|
||||
delay = G_MAXINT;
|
||||
|
||||
return delay / 1000;
|
||||
}
|
||||
|
||||
void timer_sync(Timer *timer)
|
||||
{
|
||||
int64_t sleep_duration;
|
||||
|
@@ -40,6 +40,12 @@ void timer_reset(Timer *timer);
|
||||
|
||||
void timer_add(Timer *timer, int size);
|
||||
|
||||
/**
|
||||
* Returns the number of milliseconds to sleep to get back to sync.
|
||||
*/
|
||||
unsigned
|
||||
timer_delay(const Timer *timer);
|
||||
|
||||
void timer_sync(Timer *timer);
|
||||
|
||||
#endif
|
||||
|
@@ -28,6 +28,7 @@
|
||||
#include "idle.h"
|
||||
#include "stats.h"
|
||||
#include "main.h"
|
||||
#include "mpd_error.h"
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
@@ -93,7 +94,7 @@ spawn_update_task(const char *path)
|
||||
|
||||
update_thr = g_thread_create(update_task, g_strdup(path), TRUE, &e);
|
||||
if (update_thr == NULL)
|
||||
g_error("Failed to spawn update task: %s", e->message);
|
||||
MPD_ERROR("Failed to spawn update task: %s", e->message);
|
||||
|
||||
if (++update_task_id > update_task_id_max)
|
||||
update_task_id = 1;
|
||||
|
@@ -546,6 +546,31 @@ update_container_file( struct directory* directory,
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given permissions on the mapped file are given.
|
||||
*/
|
||||
static bool
|
||||
directory_child_access(const struct directory *directory,
|
||||
const char *name, int mode)
|
||||
{
|
||||
#ifdef WIN32
|
||||
/* access() is useless on WIN32 */
|
||||
(void)directory;
|
||||
(void)name;
|
||||
return true;
|
||||
#else
|
||||
char *path = map_directory_child_fs(directory, name);
|
||||
if (path == NULL)
|
||||
/* something went wrong, but that isn't a permission
|
||||
problem */
|
||||
return true;
|
||||
|
||||
bool success = access(path, mode) == 0 || errno != EACCES;
|
||||
g_free(path);
|
||||
return success;
|
||||
#endif
|
||||
}
|
||||
|
||||
static void
|
||||
update_regular_file(struct directory *directory,
|
||||
const char *name, const struct stat *st)
|
||||
@@ -562,6 +587,14 @@ update_regular_file(struct directory *directory,
|
||||
{
|
||||
struct song* song = songvec_find(&directory->songs, name);
|
||||
|
||||
if (!directory_child_access(directory, name, R_OK)) {
|
||||
g_warning("no read permissions on %s/%s",
|
||||
directory_get_path(directory), name);
|
||||
if (song != NULL)
|
||||
delete_song(directory, song);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(song != NULL && st->st_mtime == song->mtime &&
|
||||
!walk_discard) &&
|
||||
plugin->container_scan != NULL)
|
||||
@@ -604,7 +637,9 @@ update_regular_file(struct directory *directory,
|
||||
#endif
|
||||
|
||||
} else if (playlist_suffix_supported(suffix)) {
|
||||
playlist_vector_add(&directory->playlists, name, st->st_mtime);
|
||||
if (playlist_vector_update_or_add(&directory->playlists, name,
|
||||
st->st_mtime))
|
||||
modified = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -20,6 +20,7 @@
|
||||
#include "config.h"
|
||||
#include "zeroconf-internal.h"
|
||||
#include "listen.h"
|
||||
#include "mpd_error.h"
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
@@ -218,7 +219,7 @@ void init_avahi(const char *serviceName)
|
||||
g_debug("Initializing interface");
|
||||
|
||||
if (!avahi_is_valid_service_name(serviceName))
|
||||
g_error("Invalid zeroconf_name \"%s\"", serviceName);
|
||||
MPD_ERROR("Invalid zeroconf_name \"%s\"", serviceName);
|
||||
|
||||
avahiName = avahi_strdup(serviceName);
|
||||
|
||||
|
@@ -63,7 +63,7 @@ void init_zeroconf_osx(const char *serviceName)
|
||||
DNSServiceErrorType error = DNSServiceRegister(&dnsReference,
|
||||
0, 0, serviceName,
|
||||
SERVICE_TYPE, NULL, NULL,
|
||||
htons(listen_port), 0,
|
||||
g_htons(listen_port), 0,
|
||||
NULL,
|
||||
dnsRegisterCallback,
|
||||
NULL);
|
||||
|
Reference in New Issue
Block a user