Compare commits
125 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
db1ce4eeeb | ||
|
|
5acee73fc8 | ||
|
|
466c337bcb | ||
|
|
98a468a101 | ||
|
|
47c58c01d1 | ||
|
|
a9edf85a69 | ||
|
|
e7a1862517 | ||
|
|
d8e423df1a | ||
|
|
09aa0dc676 | ||
|
|
83174de420 | ||
|
|
8ff0197a43 | ||
|
|
de0f46b947 | ||
|
|
79eb7623ef | ||
|
|
b9e64d0472 | ||
|
|
4f500149af | ||
|
|
103832742d | ||
|
|
3e7e0bcb18 | ||
|
|
7d3d8f20ab | ||
|
|
e1e3ce980a | ||
|
|
7855a32579 | ||
|
|
9c92afa5fe | ||
|
|
66235fddff | ||
|
|
f9c5d026f4 | ||
|
|
48eb3ff8d9 | ||
|
|
5646dcc791 | ||
|
|
5d9876e338 | ||
|
|
083340a937 | ||
|
|
378fa5ee6a | ||
|
|
4764daf3c2 | ||
|
|
6357496d17 | ||
|
|
001e2a604b | ||
|
|
f370911c15 | ||
|
|
39d52762d1 | ||
|
|
8d45d0d104 | ||
|
|
abd1949825 | ||
|
|
4e6bc77a70 | ||
|
|
531948358b | ||
|
|
0d3ec9c324 | ||
|
|
21caca4aea | ||
|
|
fbf3edf07d | ||
|
|
76fcf25898 | ||
|
|
56257f072b | ||
|
|
44401158e8 | ||
|
|
97b4a6b51f | ||
|
|
f405d27c56 | ||
|
|
3a9697adf2 | ||
|
|
96ad5b8444 | ||
|
|
097e5dfbdc | ||
|
|
2ef7ee6ca7 | ||
|
|
2685b53b30 | ||
|
|
533e4fcdad | ||
|
|
f5d3859238 | ||
|
|
ef39da5973 | ||
|
|
81e8c4bbff | ||
|
|
8ca3642429 | ||
|
|
1dc000c06a | ||
|
|
e1b032cbad | ||
|
|
6f365c30eb | ||
|
|
718e180423 | ||
|
|
cead5e5bd7 | ||
|
|
cf15629aea | ||
|
|
a727d0bb0b | ||
|
|
0a218ee56a | ||
|
|
74beefcaf6 | ||
|
|
399a3abefc | ||
|
|
cee5036aca | ||
|
|
790823abb4 | ||
|
|
f546849352 | ||
|
|
a85af593f1 | ||
|
|
07067f8b95 | ||
|
|
a1e824ada0 | ||
|
|
f5f1bfbef1 | ||
|
|
cd108ba3aa | ||
|
|
2bb5bfa74e | ||
|
|
624e7a447d | ||
|
|
ef40e362c9 | ||
|
|
6452461c39 | ||
|
|
c30c46cd5f | ||
|
|
d394017926 | ||
|
|
04525c0259 | ||
|
|
fa5e06f95d | ||
|
|
3041409334 | ||
|
|
71536eb412 | ||
|
|
fe77230d84 | ||
|
|
5ed0eb51d1 | ||
|
|
72a1ca3b99 | ||
|
|
72ff9bd3e6 | ||
|
|
039b354490 | ||
|
|
b2f03e76ff | ||
|
|
63b33b6ec5 | ||
|
|
23670795db | ||
|
|
8ea6c113b5 | ||
|
|
37f026a0a6 | ||
|
|
f67136df19 | ||
|
|
e07073ff28 | ||
|
|
64b0ba6da7 | ||
|
|
99d4ae0c1a | ||
|
|
f185b35088 | ||
|
|
83f6498aac | ||
|
|
525a791987 | ||
|
|
fb19210cfd | ||
|
|
b0b2c5b3e0 | ||
|
|
29742d23d3 | ||
|
|
c476819cb1 | ||
|
|
77a56c7c5a | ||
|
|
46deb7ca82 | ||
|
|
b03f9ece05 | ||
|
|
1d8840412f | ||
|
|
df1152ee0f | ||
|
|
79435dbdec | ||
|
|
27206368da | ||
|
|
443e96381a | ||
|
|
1cbba4fc59 | ||
|
|
344b6dd179 | ||
|
|
d8c829fa0c | ||
|
|
2ed870c854 | ||
|
|
ce35ba9ac9 | ||
|
|
724a59aaf7 | ||
|
|
42d8c2981f | ||
|
|
9aa91e0f17 | ||
|
|
5aabee8996 | ||
|
|
48a84ca23e | ||
|
|
c345c5ebae | ||
|
|
5cf4ce9318 | ||
|
|
5469941f2b |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -34,11 +34,13 @@ missing
|
||||
mkinstalldirs
|
||||
mpd
|
||||
mpd.exe
|
||||
mpd.service
|
||||
stamp-h1
|
||||
tags
|
||||
*~
|
||||
.#*
|
||||
.stgit*
|
||||
doc/doxygen.conf
|
||||
doc/protocol.html
|
||||
doc/protocol
|
||||
doc/user
|
||||
|
||||
48
Makefile.am
48
Makefile.am
@@ -1,7 +1,7 @@
|
||||
ACLOCAL_AMFLAGS = -I m4
|
||||
AUTOMAKE_OPTIONS = foreign 1.10 dist-bzip2 subdir-objects
|
||||
|
||||
AM_CPPFLAGS = -I$(srcdir)/src $(GLIB_CFLAGS)
|
||||
AM_CPPFLAGS += -I$(srcdir)/src $(GLIB_CFLAGS)
|
||||
|
||||
AM_CPPFLAGS += -DSYSTEM_CONFIG_FILE_LOCATION='"$(sysconfdir)/mpd.conf"'
|
||||
|
||||
@@ -85,7 +85,6 @@ mpd_headers = \
|
||||
src/encoder_api.h \
|
||||
src/exclude.h \
|
||||
src/fd_util.h \
|
||||
src/fifo_buffer.h \
|
||||
src/glib_compat.h \
|
||||
src/update.h \
|
||||
src/update_internal.h \
|
||||
@@ -144,7 +143,6 @@ mpd_headers = \
|
||||
src/output/pulse_output_plugin.h \
|
||||
src/output/winmm_output_plugin.h \
|
||||
src/page.h \
|
||||
src/pcm_buffer.h \
|
||||
src/pcm_utils.h \
|
||||
src/pcm_convert.h \
|
||||
src/pcm_volume.h \
|
||||
@@ -243,6 +241,7 @@ src_mpd_SOURCES = \
|
||||
$(OUTPUT_API_SRC) $(OUTPUT_SRC) \
|
||||
$(MIXER_API_SRC) $(MIXER_SRC) \
|
||||
$(FILTER_SRC) \
|
||||
src/glib_socket.h \
|
||||
src/notify.c \
|
||||
src/audio.c \
|
||||
src/audio_check.c \
|
||||
@@ -266,7 +265,8 @@ src_mpd_SOURCES = \
|
||||
src/dirvec.c \
|
||||
src/exclude.c \
|
||||
src/fd_util.c \
|
||||
src/fifo_buffer.c \
|
||||
src/fifo_buffer.c src/fifo_buffer.h \
|
||||
src/growing_fifo.c src/growing_fifo.h \
|
||||
src/filter_config.c \
|
||||
src/filter_plugin.c \
|
||||
src/filter_registry.c \
|
||||
@@ -299,6 +299,7 @@ src_mpd_SOURCES = \
|
||||
src/path.c \
|
||||
src/mapper.c \
|
||||
src/page.c \
|
||||
src/pcm_buffer.c src/pcm_buffer.h \
|
||||
src/pcm_convert.c \
|
||||
src/pcm_volume.c \
|
||||
src/pcm_mix.c \
|
||||
@@ -801,6 +802,15 @@ FILTER_SRC = \
|
||||
src/filter/volume_filter_plugin.c
|
||||
|
||||
|
||||
#
|
||||
# systemd unit
|
||||
#
|
||||
|
||||
if HAVE_SYSTEMD
|
||||
systemdsystemunit_DATA = \
|
||||
mpd.service
|
||||
endif
|
||||
|
||||
#
|
||||
# Sparse code analysis
|
||||
#
|
||||
@@ -918,6 +928,7 @@ test_run_decoder_SOURCES = test/run_decoder.c \
|
||||
src/audio_check.c \
|
||||
src/audio_format.c \
|
||||
src/timer.c \
|
||||
src/pcm_buffer.c \
|
||||
$(ARCHIVE_SRC) \
|
||||
$(INPUT_SRC) \
|
||||
$(TAG_SRC) \
|
||||
@@ -940,6 +951,7 @@ test_read_tags_SOURCES = test/read_tags.c \
|
||||
src/fd_util.c \
|
||||
src/audio_check.c \
|
||||
src/timer.c \
|
||||
src/pcm_buffer.c \
|
||||
$(ARCHIVE_SRC) \
|
||||
$(INPUT_SRC) \
|
||||
$(TAG_SRC) \
|
||||
@@ -958,6 +970,7 @@ test_run_filter_SOURCES = test/run_filter.c \
|
||||
src/pcm_format.c src/pcm_channels.c src/pcm_dither.c \
|
||||
src/pcm_pack.c \
|
||||
src/pcm_resample.c src/pcm_resample_fallback.c \
|
||||
src/pcm_buffer.c \
|
||||
src/audio_check.c \
|
||||
src/audio_format.c \
|
||||
src/audio_parser.c \
|
||||
@@ -980,6 +993,8 @@ test_run_encoder_SOURCES = test/run_encoder.c \
|
||||
src/audio_check.c \
|
||||
src/audio_format.c \
|
||||
src/audio_parser.c \
|
||||
src/pcm_buffer.c \
|
||||
src/fifo_buffer.c src/growing_fifo.c \
|
||||
$(ENCODER_SRC)
|
||||
test_run_encoder_CPPFLAGS = $(AM_CPPFLAGS) \
|
||||
$(ENCODER_CFLAGS)
|
||||
@@ -988,6 +1003,26 @@ test_run_encoder_LDADD = $(MPD_LIBS) \
|
||||
$(GLIB_LIBS)
|
||||
endif
|
||||
|
||||
if ENABLE_VORBIS_ENCODER
|
||||
noinst_PROGRAMS += test/test_vorbis_encoder
|
||||
test_test_vorbis_encoder_SOURCES = test/test_vorbis_encoder.c \
|
||||
test/stdbin.h \
|
||||
src/conf.c src/tokenizer.c \
|
||||
src/utils.c \
|
||||
src/tag.c src/tag_pool.c \
|
||||
src/audio_check.c \
|
||||
src/audio_format.c \
|
||||
src/audio_parser.c \
|
||||
src/pcm_buffer.c \
|
||||
src/fifo_buffer.c src/growing_fifo.c \
|
||||
$(ENCODER_SRC)
|
||||
test_test_vorbis_encoder_CPPFLAGS = $(AM_CPPFLAGS) \
|
||||
$(ENCODER_CFLAGS)
|
||||
test_test_vorbis_encoder_LDADD = $(MPD_LIBS) \
|
||||
$(ENCODER_LIBS) \
|
||||
$(GLIB_LIBS)
|
||||
endif
|
||||
|
||||
test_software_volume_SOURCES = test/software_volume.c \
|
||||
test/stdbin.h \
|
||||
src/audio_check.c \
|
||||
@@ -1009,6 +1044,7 @@ test_run_convert_SOURCES = test/run_convert.c \
|
||||
src/audio_format.c \
|
||||
src/audio_check.c \
|
||||
src/audio_parser.c \
|
||||
src/pcm_buffer.c \
|
||||
src/pcm_channels.c \
|
||||
src/pcm_format.c \
|
||||
src/pcm_pack.c \
|
||||
@@ -1042,7 +1078,7 @@ test_run_output_SOURCES = test/run_output.c \
|
||||
src/audio_parser.c \
|
||||
src/timer.c \
|
||||
src/tag.c src/tag_pool.c \
|
||||
src/fifo_buffer.c \
|
||||
src/fifo_buffer.c src/growing_fifo.c \
|
||||
src/page.c \
|
||||
src/socket_util.c \
|
||||
src/output_init.c src/output_list.c \
|
||||
@@ -1059,6 +1095,7 @@ test_run_output_SOURCES = test/run_output.c \
|
||||
src/filter/normalize_filter_plugin.c \
|
||||
src/filter/volume_filter_plugin.c \
|
||||
src/pcm_volume.c \
|
||||
src/pcm_buffer.c \
|
||||
src/AudioCompress/compress.c \
|
||||
src/replay_gain_info.c \
|
||||
src/replay_gain_config.c \
|
||||
@@ -1077,6 +1114,7 @@ test_read_mixer_SOURCES = test/read_mixer.c \
|
||||
src/filter_plugin.c \
|
||||
src/filter/volume_filter_plugin.c \
|
||||
src/fd_util.c \
|
||||
src/pcm_buffer.c \
|
||||
$(MIXER_SRC)
|
||||
|
||||
if ENABLE_BZIP2_TEST
|
||||
|
||||
69
NEWS
69
NEWS
@@ -1,3 +1,72 @@
|
||||
ver 0.16.8 (2012/04/04)
|
||||
* fix for libsamplerate assertion failure
|
||||
* decoder:
|
||||
- vorbis (and others): fix seeking at startup
|
||||
- ffmpeg: read the "year" tag
|
||||
* encoder:
|
||||
- vorbis: generate end-of-stream packet before tag
|
||||
- vorbis: generate end-of-stream packet when playback ends
|
||||
* output:
|
||||
- jack: check for connection failure before starting playback
|
||||
- jack: workaround for libjack1 crash bug
|
||||
- osx: fix stuttering due to buffering bug
|
||||
* fix endless loop in text file reader
|
||||
* update: skip symlinks in path that is to be updated
|
||||
|
||||
|
||||
ver 0.16.7 (2012/02/04)
|
||||
* input:
|
||||
- ffmpeg: support libavformat 0.7
|
||||
* decoder:
|
||||
- ffmpeg: support libavformat 0.8, libavcodec 0.9
|
||||
- ffmpeg: support all MPD tags
|
||||
* output:
|
||||
- httpd: fix excessive buffering
|
||||
- openal: force 16 bit playback, as 8 bit doesn't work
|
||||
- osx: remove sleep call from render callback
|
||||
- osx: clear render buffer when there's not enough data
|
||||
* fix moving after current song
|
||||
|
||||
|
||||
ver 0.16.6 (2011/12/01)
|
||||
* decoder:
|
||||
- fix assertion failure when resuming streams
|
||||
- ffmpeg: work around bogus channel count
|
||||
* encoder:
|
||||
- flac, null, wave: fix buffer corruption bug
|
||||
- wave: support packed 24 bit samples
|
||||
* mapper: fix the bogus "not a directory" error message
|
||||
* mapper: check "x" and "r" permissions on music directory
|
||||
* log: print reason for failure
|
||||
* event_pipe: fix WIN32 regression
|
||||
* define WINVER in ./configure
|
||||
* WIN32: autodetect filesystem encoding
|
||||
|
||||
|
||||
ver 0.16.5 (2011/10/09)
|
||||
* configure.ac
|
||||
- disable assertions in the non-debugging build
|
||||
- show solaris plugin result correctly
|
||||
- add option --enable-solaris-output
|
||||
* pcm_format: fix 32-to-24 bit conversion (the "silence" bug)
|
||||
* input:
|
||||
- rewind: reduce heap usage
|
||||
* decoder:
|
||||
- ffmpeg: higher precision timestamps
|
||||
- ffmpeg: don't require key frame for seeking
|
||||
- fix CUE track seeking
|
||||
* output:
|
||||
- openal: auto-fallback to mono if channel count is unsupported
|
||||
* player:
|
||||
- make seeking to CUE track more reliable
|
||||
- the "seek" command works when MPD is stopped
|
||||
- restore song position from state file (bug fix)
|
||||
- fix crash that sometimes occurred when audio device fails on startup
|
||||
- fix absolute path support in playlists
|
||||
* WIN32: close sockets properly
|
||||
* install systemd service file if systemd is available
|
||||
|
||||
|
||||
ver 0.16.4 (2011/09/01)
|
||||
* don't abort configure when avahi is not found
|
||||
* auto-detect libmad without pkg-config
|
||||
|
||||
60
configure.ac
60
configure.ac
@@ -1,5 +1,5 @@
|
||||
AC_PREREQ(2.60)
|
||||
AC_INIT(mpd, 0.16.4, musicpd-dev-team@lists.sourceforge.net)
|
||||
AC_INIT(mpd, 0.16.8, 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)
|
||||
@@ -33,11 +33,28 @@ fi
|
||||
AC_PROG_INSTALL
|
||||
AC_PROG_MAKE_SET
|
||||
PKG_PROG_PKG_CONFIG
|
||||
AC_ARG_WITH([systemdsystemunitdir],
|
||||
AS_HELP_STRING([--with-systemdsystemunitdir=DIR], [Directory for systemd service files]),
|
||||
[], [with_systemdsystemunitdir=no])
|
||||
if test "x$with_systemdsystemunitdir" = xyes; then
|
||||
AC_MSG_CHECKING(for systemd)
|
||||
with_systemdsystemunitdir=$($PKG_CONFIG --variable=systemdsystemunitdir systemd)
|
||||
if test -z "$with_systemdsystemunitdir"; then
|
||||
AC_MSG_ERROR([Failed to detect systemd])
|
||||
fi
|
||||
AC_MSG_RESULT([$with_systemdsystemunitdir])
|
||||
fi
|
||||
if test "x$with_systemdsystemunitdir" != xno; then
|
||||
AC_SUBST([systemdsystemunitdir], [$with_systemdsystemunitdir])
|
||||
fi
|
||||
AM_CONDITIONAL(HAVE_SYSTEMD, [test -n "$with_systemdsystemunitdir" -a "x$with_systemdsystemunitdir" != xno ])
|
||||
|
||||
dnl ---------------------------------------------------------------------------
|
||||
dnl Declare Variables
|
||||
dnl ---------------------------------------------------------------------------
|
||||
AC_SUBST(AM_CPPFLAGS,"")
|
||||
AC_SUBST(AM_CFLAGS,"")
|
||||
AC_SUBST(AM_CXXFLAGS,"")
|
||||
|
||||
AC_SUBST(MPD_LIBS)
|
||||
AC_SUBST(MPD_CFLAGS)
|
||||
@@ -51,6 +68,7 @@ AC_CANONICAL_HOST
|
||||
|
||||
case "$host_os" in
|
||||
mingw32* | windows*)
|
||||
AM_CPPFLAGS="$AM_CPPFLAGS -DWINVER=0x0501"
|
||||
MPD_LIBS="$MPD_LIBS -lws2_32"
|
||||
;;
|
||||
esac
|
||||
@@ -197,7 +215,7 @@ AC_ARG_ENABLE(httpd-output,
|
||||
|
||||
AC_ARG_ENABLE(id3,
|
||||
AS_HELP_STRING([--enable-id3],
|
||||
[disable id3 support]),,
|
||||
[enable id3 support]),,
|
||||
enable_id3=auto)
|
||||
|
||||
AC_ARG_ENABLE(inotify,
|
||||
@@ -322,6 +340,11 @@ AC_ARG_ENABLE(sndfile,
|
||||
[enable sndfile support]),,
|
||||
enable_sndfile=auto)
|
||||
|
||||
AC_ARG_ENABLE(solaris_output,
|
||||
AS_HELP_STRING([--enable-solaris-output],
|
||||
[enables the Solaris /dev/audio output]),,
|
||||
[enable_solaris_output=auto])
|
||||
|
||||
AC_ARG_ENABLE(sqlite,
|
||||
AS_HELP_STRING([--enable-sqlite],
|
||||
[enable support for the SQLite database]),,
|
||||
@@ -409,6 +432,11 @@ dnl ---------------------------------------------------------------------------
|
||||
PKG_CHECK_MODULES([GLIB], [glib-2.0 >= 2.12 gthread-2.0],,
|
||||
[AC_MSG_ERROR([GLib 2.12 is required])])
|
||||
|
||||
if test x$GCC = xyes; then
|
||||
# suppress warnings in the GLib headers
|
||||
GLIB_CFLAGS=`echo $GLIB_CFLAGS |sed -e 's,-I/,-isystem /,g'`
|
||||
fi
|
||||
|
||||
dnl ---------------------------------------------------------------------------
|
||||
dnl Protocol Options
|
||||
dnl ---------------------------------------------------------------------------
|
||||
@@ -423,7 +451,11 @@ if test x$enable_ipv6 = xyes; then
|
||||
AC_EGREP_CPP([AP_maGiC_VALUE],
|
||||
[
|
||||
#include <sys/types.h>
|
||||
#ifdef WIN32
|
||||
#include <winsock2.h>
|
||||
#else
|
||||
#include <sys/socket.h>
|
||||
#endif
|
||||
#include <netdb.h>
|
||||
#ifdef PF_INET6
|
||||
#ifdef AF_INET6
|
||||
@@ -1368,16 +1400,22 @@ fi
|
||||
AM_CONDITIONAL(HAVE_SHOUT, test x$enable_shout = xyes)
|
||||
|
||||
dnl --------------------------------- Solaris ---------------------------------
|
||||
case "$host_os" in
|
||||
|
||||
if test x$enable_solaris_output = xauto; then
|
||||
case "$host_os" in
|
||||
solaris*)
|
||||
AC_DEFINE(ENABLE_SOLARIS_OUTPUT, 1, [Define to enable Solaris /dev/audio support])
|
||||
enable_solaris_output=yes
|
||||
;;
|
||||
|
||||
*)
|
||||
enable_solaris_output=no
|
||||
;;
|
||||
esac
|
||||
esac
|
||||
fi
|
||||
|
||||
if test x$enable_solaris_output = xyes; then
|
||||
AC_DEFINE(ENABLE_SOLARIS_OUTPUT, 1, [Define to enable Solaris /dev/audio support])
|
||||
fi
|
||||
|
||||
AM_CONDITIONAL(ENABLE_SOLARIS_OUTPUT, test x$enable_solaris_output = xyes)
|
||||
|
||||
@@ -1449,17 +1487,15 @@ dnl CFLAGS
|
||||
dnl ---------------------------------------------------------------------------
|
||||
|
||||
dnl ---------------------------------- debug ----------------------------------
|
||||
#if test "x$enable_debug" = xno; then
|
||||
# don't set NDEBUG for now, until MPD is stable
|
||||
#AM_CFLAGS="$AM_CFLAGS -DNDEBUG"
|
||||
#fi
|
||||
if test "x$enable_debug" = xno; then
|
||||
AM_CPPFLAGS="$AM_CPPFLAGS -DNDEBUG"
|
||||
fi
|
||||
|
||||
dnl ----------------------------------- GCC -----------------------------------
|
||||
if test x$GCC = xyes
|
||||
then
|
||||
MPD_CHECK_FLAG([-Wall])
|
||||
MPD_CHECK_FLAG([-Wextra])
|
||||
MPD_CHECK_FLAG([-Wno-deprecated-declarations])
|
||||
MPD_CHECK_FLAG([-Wmissing-prototypes])
|
||||
MPD_CHECK_FLAG([-Wshadow])
|
||||
MPD_CHECK_FLAG([-Wpointer-arith])
|
||||
@@ -1478,6 +1514,7 @@ fi
|
||||
dnl ---------------------------- warnings as errors ---------------------------
|
||||
if test "x$enable_werror" = xyes; then
|
||||
AM_CFLAGS="$AM_CFLAGS -Werror -pedantic-errors"
|
||||
AM_CXXFLAGS="$AM_CXXFLAGS -Werror"
|
||||
fi
|
||||
|
||||
dnl ---------------------------------------------------------------------------
|
||||
@@ -1551,7 +1588,7 @@ results(pulse, [PulseAudio])
|
||||
results(mvp, [Media MVP])
|
||||
results(shout, [SHOUTcast])
|
||||
printf '\n\t'
|
||||
results(solaris, [Solaris])
|
||||
results(solaris_output, [Solaris])
|
||||
results(winmm_output, [WinMM])
|
||||
|
||||
if
|
||||
@@ -1579,5 +1616,6 @@ dnl ---------------------------------------------------------------------------
|
||||
dnl Generate files
|
||||
dnl ---------------------------------------------------------------------------
|
||||
AC_OUTPUT(Makefile)
|
||||
AC_OUTPUT(mpd.service)
|
||||
|
||||
echo 'MPD is ready for compilation, type "make" to begin.'
|
||||
|
||||
9
mpd.service.in
Normal file
9
mpd.service.in
Normal file
@@ -0,0 +1,9 @@
|
||||
[Unit]
|
||||
Description=Music Player Daemon
|
||||
After=sound.target
|
||||
|
||||
[Service]
|
||||
ExecStart=@prefix@/bin/mpd --no-daemon
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
@@ -19,9 +19,11 @@
|
||||
|
||||
#include "config.h"
|
||||
#include "client_internal.h"
|
||||
#include "fd_util.h"
|
||||
#include "fifo_buffer.h"
|
||||
#include "socket_util.h"
|
||||
#include "permission.h"
|
||||
#include "glib_socket.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <sys/types.h>
|
||||
@@ -66,7 +68,7 @@ void client_new(int fd, const struct sockaddr *sa, size_t sa_length, int uid)
|
||||
progname, hostaddr);
|
||||
|
||||
g_free(hostaddr);
|
||||
close(fd);
|
||||
close_socket(fd);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -76,17 +78,13 @@ void client_new(int fd, const struct sockaddr *sa, size_t sa_length, int uid)
|
||||
|
||||
if (client_list_is_full()) {
|
||||
g_warning("Max Connections Reached!");
|
||||
close(fd);
|
||||
close_socket(fd);
|
||||
return;
|
||||
}
|
||||
|
||||
client = g_new0(struct client, 1);
|
||||
|
||||
#ifndef G_OS_WIN32
|
||||
client->channel = g_io_channel_unix_new(fd);
|
||||
#else
|
||||
client->channel = g_io_channel_win32_new_socket(fd);
|
||||
#endif
|
||||
client->channel = g_io_channel_new_socket(fd);
|
||||
/* GLib is responsible for closing the file descriptor */
|
||||
g_io_channel_set_close_on_unref(client->channel, true);
|
||||
/* NULL encoding means the stream is binary safe; the MPD
|
||||
|
||||
@@ -194,8 +194,6 @@ parse_cmdline(int argc, char **argv, struct options *options,
|
||||
if(g_file_test(system_path,
|
||||
G_FILE_TEST_IS_REGULAR)) {
|
||||
ret = config_read_file(system_path,error_r);
|
||||
g_free(system_path);
|
||||
g_free(&system_config_dirs);
|
||||
break;
|
||||
}
|
||||
++i;;
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
#include <af_vfs.h>
|
||||
#include <assert.h>
|
||||
#include <glib.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#undef G_LOG_DOMAIN
|
||||
#define G_LOG_DOMAIN "audiofile"
|
||||
@@ -63,14 +64,14 @@ audiofile_file_read(AFvirtualfile *vfile, void *data, size_t length)
|
||||
return nbytes;
|
||||
}
|
||||
|
||||
static long
|
||||
static AFfileoffset
|
||||
audiofile_file_length(AFvirtualfile *vfile)
|
||||
{
|
||||
struct input_stream *is = (struct input_stream *) vfile->closure;
|
||||
return is->size;
|
||||
}
|
||||
|
||||
static long
|
||||
static AFfileoffset
|
||||
audiofile_file_tell(AFvirtualfile *vfile)
|
||||
{
|
||||
struct input_stream *is = (struct input_stream *) vfile->closure;
|
||||
@@ -85,8 +86,8 @@ audiofile_file_destroy(AFvirtualfile *vfile)
|
||||
vfile->closure = NULL;
|
||||
}
|
||||
|
||||
static long
|
||||
audiofile_file_seek(AFvirtualfile *vfile, long offset, int is_relative)
|
||||
static AFfileoffset
|
||||
audiofile_file_seek(AFvirtualfile *vfile, AFfileoffset offset, int is_relative)
|
||||
{
|
||||
struct input_stream *is = (struct input_stream *) vfile->closure;
|
||||
int whence = (is_relative ? SEEK_CUR : SEEK_SET);
|
||||
|
||||
@@ -40,7 +40,12 @@
|
||||
#include <libavcodec/avcodec.h>
|
||||
#include <libavformat/avformat.h>
|
||||
#include <libavformat/avio.h>
|
||||
#include <libavutil/avutil.h>
|
||||
#include <libavutil/log.h>
|
||||
#include <libavutil/mathematics.h>
|
||||
#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(51,5,0)
|
||||
#include <libavutil/dict.h>
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#undef G_LOG_DOMAIN
|
||||
@@ -126,11 +131,19 @@ mpd_ffmpeg_stream_open(struct decoder *decoder, struct input_stream *input)
|
||||
struct mpd_ffmpeg_stream *stream = g_new(struct mpd_ffmpeg_stream, 1);
|
||||
stream->decoder = decoder;
|
||||
stream->input = input;
|
||||
#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(52,101,0)
|
||||
stream->io = avio_alloc_context(stream->buffer, sizeof(stream->buffer),
|
||||
false, stream,
|
||||
mpd_ffmpeg_stream_read, NULL,
|
||||
input->seekable
|
||||
? mpd_ffmpeg_stream_seek : NULL);
|
||||
#else
|
||||
stream->io = av_alloc_put_byte(stream->buffer, sizeof(stream->buffer),
|
||||
false, stream,
|
||||
mpd_ffmpeg_stream_read, NULL,
|
||||
input->seekable
|
||||
? mpd_ffmpeg_stream_seek : NULL);
|
||||
#endif
|
||||
if (stream->io == NULL) {
|
||||
g_free(stream);
|
||||
return NULL;
|
||||
@@ -199,6 +212,7 @@ ffmpeg_find_audio_stream(const AVFormatContext *format_context)
|
||||
return -1;
|
||||
}
|
||||
|
||||
#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(53,25,0)
|
||||
/**
|
||||
* On some platforms, libavcodec wants the output buffer aligned to 16
|
||||
* bytes (because it uses SSE/Altivec internally). This function
|
||||
@@ -213,6 +227,59 @@ align16(void *p, size_t *length_p)
|
||||
*length_p -= add;
|
||||
return (char *)p + add;
|
||||
}
|
||||
#endif
|
||||
|
||||
G_GNUC_CONST
|
||||
static double
|
||||
time_from_ffmpeg(int64_t t, const AVRational time_base)
|
||||
{
|
||||
assert(t != (int64_t)AV_NOPTS_VALUE);
|
||||
|
||||
return (double)av_rescale_q(t, time_base, (AVRational){1, 1024})
|
||||
/ (double)1024;
|
||||
}
|
||||
|
||||
G_GNUC_CONST
|
||||
static int64_t
|
||||
time_to_ffmpeg(double t, const AVRational time_base)
|
||||
{
|
||||
return av_rescale_q((int64_t)(t * 1024), (AVRational){1, 1024},
|
||||
time_base);
|
||||
}
|
||||
|
||||
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(53,25,0)
|
||||
/**
|
||||
* Copy PCM data from a AVFrame to an interleaved buffer.
|
||||
*/
|
||||
static int
|
||||
copy_interleave_frame(const AVCodecContext *codec_context,
|
||||
const AVFrame *frame,
|
||||
uint8_t *buffer, size_t buffer_size)
|
||||
{
|
||||
int plane_size;
|
||||
const int data_size =
|
||||
av_samples_get_buffer_size(&plane_size,
|
||||
codec_context->channels,
|
||||
frame->nb_samples,
|
||||
codec_context->sample_fmt, 1);
|
||||
if (buffer_size < (size_t)data_size)
|
||||
/* buffer is too small - shouldn't happen */
|
||||
return AVERROR(EINVAL);
|
||||
|
||||
if (av_sample_fmt_is_planar(codec_context->sample_fmt) &&
|
||||
codec_context->channels > 1) {
|
||||
for (int i = 0, channels = codec_context->channels;
|
||||
i < channels; i++) {
|
||||
memcpy(buffer, frame->extended_data[i], plane_size);
|
||||
buffer += plane_size;
|
||||
}
|
||||
} else {
|
||||
memcpy(buffer, frame->extended_data[0], data_size);
|
||||
}
|
||||
|
||||
return data_size;
|
||||
}
|
||||
#endif
|
||||
|
||||
static enum decoder_command
|
||||
ffmpeg_send_packet(struct decoder *decoder, struct input_stream *is,
|
||||
@@ -222,8 +289,7 @@ ffmpeg_send_packet(struct decoder *decoder, struct input_stream *is,
|
||||
{
|
||||
if (packet->pts != (int64_t)AV_NOPTS_VALUE)
|
||||
decoder_timestamp(decoder,
|
||||
av_rescale_q(packet->pts, *time_base,
|
||||
(AVRational){1, 1}));
|
||||
time_from_ffmpeg(packet->pts, *time_base));
|
||||
|
||||
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(52,25,0)
|
||||
AVPacket packet2 = *packet;
|
||||
@@ -232,9 +298,15 @@ ffmpeg_send_packet(struct decoder *decoder, struct input_stream *is,
|
||||
int packet_size = packet->size;
|
||||
#endif
|
||||
|
||||
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(53,25,0)
|
||||
uint8_t aligned_buffer[(AVCODEC_MAX_AUDIO_FRAME_SIZE * 3) / 2 + 16];
|
||||
const size_t buffer_size = sizeof(aligned_buffer);
|
||||
#else
|
||||
/* libavcodec < 0.8 needs an aligned buffer */
|
||||
uint8_t audio_buf[(AVCODEC_MAX_AUDIO_FRAME_SIZE * 3) / 2 + 16];
|
||||
size_t buffer_size = sizeof(audio_buf);
|
||||
int16_t *aligned_buffer = align16(audio_buf, &buffer_size);
|
||||
#endif
|
||||
|
||||
enum decoder_command cmd = DECODE_COMMAND_NONE;
|
||||
while (
|
||||
@@ -245,7 +317,22 @@ ffmpeg_send_packet(struct decoder *decoder, struct input_stream *is,
|
||||
#endif
|
||||
cmd == DECODE_COMMAND_NONE) {
|
||||
int audio_size = buffer_size;
|
||||
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(52,25,0)
|
||||
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(53,25,0)
|
||||
AVFrame frame;
|
||||
int got_frame = 0;
|
||||
int len = avcodec_decode_audio4(codec_context,
|
||||
&frame, &got_frame,
|
||||
&packet2);
|
||||
if (len >= 0 && got_frame) {
|
||||
audio_size = copy_interleave_frame(codec_context,
|
||||
&frame,
|
||||
aligned_buffer,
|
||||
buffer_size);
|
||||
if (audio_size < 0)
|
||||
len = audio_size;
|
||||
} else if (len >= 0)
|
||||
len = -1;
|
||||
#elif LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(52,25,0)
|
||||
int len = avcodec_decode_audio3(codec_context,
|
||||
aligned_buffer, &audio_size,
|
||||
&packet2);
|
||||
@@ -284,10 +371,18 @@ ffmpeg_sample_format(G_GNUC_UNUSED const AVCodecContext *codec_context)
|
||||
{
|
||||
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(51, 41, 0)
|
||||
switch (codec_context->sample_fmt) {
|
||||
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(52, 94, 1)
|
||||
case AV_SAMPLE_FMT_S16:
|
||||
#else
|
||||
case SAMPLE_FMT_S16:
|
||||
#endif
|
||||
return SAMPLE_FORMAT_S16;
|
||||
|
||||
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(52, 94, 1)
|
||||
case AV_SAMPLE_FMT_S32:
|
||||
#else
|
||||
case SAMPLE_FMT_S32:
|
||||
#endif
|
||||
return SAMPLE_FORMAT_S32;
|
||||
|
||||
default:
|
||||
@@ -360,9 +455,19 @@ ffmpeg_decode(struct decoder *decoder, struct input_stream *input)
|
||||
return;
|
||||
}
|
||||
|
||||
if (av_find_stream_info(format_context)<0) {
|
||||
#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,6,0)
|
||||
const int find_result =
|
||||
avformat_find_stream_info(format_context, NULL);
|
||||
#else
|
||||
const int find_result = av_find_stream_info(format_context);
|
||||
#endif
|
||||
if (find_result < 0) {
|
||||
g_warning("Couldn't find stream info\n");
|
||||
#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,17,0)
|
||||
avformat_close_input(&format_context);
|
||||
#else
|
||||
av_close_input_stream(format_context);
|
||||
#endif
|
||||
mpd_ffmpeg_stream_close(stream);
|
||||
return;
|
||||
}
|
||||
@@ -370,13 +475,18 @@ ffmpeg_decode(struct decoder *decoder, struct input_stream *input)
|
||||
int audio_stream = ffmpeg_find_audio_stream(format_context);
|
||||
if (audio_stream == -1) {
|
||||
g_warning("No audio stream inside\n");
|
||||
#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,17,0)
|
||||
avformat_close_input(&format_context);
|
||||
#else
|
||||
av_close_input_stream(format_context);
|
||||
#endif
|
||||
mpd_ffmpeg_stream_close(stream);
|
||||
return;
|
||||
}
|
||||
|
||||
AVCodecContext *codec_context =
|
||||
format_context->streams[audio_stream]->codec;
|
||||
AVStream *av_stream = format_context->streams[audio_stream];
|
||||
|
||||
AVCodecContext *codec_context = av_stream->codec;
|
||||
if (codec_context->codec_name[0] != 0)
|
||||
g_debug("codec '%s'", codec_context->codec_name);
|
||||
|
||||
@@ -384,14 +494,11 @@ ffmpeg_decode(struct decoder *decoder, struct input_stream *input)
|
||||
|
||||
if (!codec) {
|
||||
g_warning("Unsupported audio codec\n");
|
||||
#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,17,0)
|
||||
avformat_close_input(&format_context);
|
||||
#else
|
||||
av_close_input_stream(format_context);
|
||||
mpd_ffmpeg_stream_close(stream);
|
||||
return;
|
||||
}
|
||||
|
||||
if (avcodec_open(codec_context, codec)<0) {
|
||||
g_warning("Could not open codec\n");
|
||||
av_close_input_stream(format_context);
|
||||
#endif
|
||||
mpd_ffmpeg_stream_close(stream);
|
||||
return;
|
||||
}
|
||||
@@ -404,8 +511,32 @@ ffmpeg_decode(struct decoder *decoder, struct input_stream *input)
|
||||
codec_context->channels, &error)) {
|
||||
g_warning("%s", error->message);
|
||||
g_error_free(error);
|
||||
avcodec_close(codec_context);
|
||||
#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,17,0)
|
||||
avformat_close_input(&format_context);
|
||||
#else
|
||||
av_close_input_stream(format_context);
|
||||
#endif
|
||||
mpd_ffmpeg_stream_close(stream);
|
||||
return;
|
||||
}
|
||||
|
||||
/* the audio format must be read from AVCodecContext by now,
|
||||
because avcodec_open() has been demonstrated to fill bogus
|
||||
values into AVCodecContext.channels - a change that will be
|
||||
reverted later by avcodec_decode_audio3() */
|
||||
|
||||
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(53,6,0)
|
||||
const int open_result = avcodec_open2(codec_context, codec, NULL);
|
||||
#else
|
||||
const int open_result = avcodec_open(codec_context, codec);
|
||||
#endif
|
||||
if (open_result < 0) {
|
||||
g_warning("Could not open codec\n");
|
||||
#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,17,0)
|
||||
avformat_close_input(&format_context);
|
||||
#else
|
||||
av_close_input_stream(format_context);
|
||||
#endif
|
||||
mpd_ffmpeg_stream_close(stream);
|
||||
return;
|
||||
}
|
||||
@@ -427,7 +558,7 @@ ffmpeg_decode(struct decoder *decoder, struct input_stream *input)
|
||||
if (packet.stream_index == audio_stream)
|
||||
cmd = ffmpeg_send_packet(decoder, input,
|
||||
&packet, codec_context,
|
||||
&format_context->streams[audio_stream]->time_base);
|
||||
&av_stream->time_base);
|
||||
else
|
||||
cmd = decoder_get_command(decoder);
|
||||
|
||||
@@ -435,17 +566,25 @@ ffmpeg_decode(struct decoder *decoder, struct input_stream *input)
|
||||
|
||||
if (cmd == DECODE_COMMAND_SEEK) {
|
||||
int64_t where =
|
||||
decoder_seek_where(decoder) * AV_TIME_BASE;
|
||||
time_to_ffmpeg(decoder_seek_where(decoder),
|
||||
av_stream->time_base);
|
||||
|
||||
if (av_seek_frame(format_context, -1, where, 0) < 0)
|
||||
if (av_seek_frame(format_context, audio_stream, where,
|
||||
AV_TIME_BASE) < 0)
|
||||
decoder_seek_error(decoder);
|
||||
else
|
||||
else {
|
||||
avcodec_flush_buffers(codec_context);
|
||||
decoder_command_finished(decoder);
|
||||
}
|
||||
}
|
||||
} while (cmd != DECODE_COMMAND_STOP);
|
||||
|
||||
avcodec_close(codec_context);
|
||||
#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,17,0)
|
||||
avformat_close_input(&format_context);
|
||||
#else
|
||||
av_close_input_stream(format_context);
|
||||
#endif
|
||||
mpd_ffmpeg_stream_close(stream);
|
||||
}
|
||||
|
||||
@@ -456,48 +595,44 @@ typedef struct ffmpeg_tag_map {
|
||||
} ffmpeg_tag_map;
|
||||
|
||||
static const ffmpeg_tag_map ffmpeg_tag_maps[] = {
|
||||
{ TAG_TITLE, "title" },
|
||||
#if LIBAVFORMAT_VERSION_INT >= ((52<<16)+(50<<8))
|
||||
{ TAG_ARTIST, "artist" },
|
||||
{ TAG_DATE, "date" },
|
||||
#else
|
||||
#if LIBAVFORMAT_VERSION_INT < ((52<<16)+(50<<8))
|
||||
{ TAG_ARTIST, "author" },
|
||||
{ TAG_DATE, "year" },
|
||||
#endif
|
||||
{ TAG_ALBUM, "album" },
|
||||
{ TAG_COMMENT, "comment" },
|
||||
{ TAG_GENRE, "genre" },
|
||||
{ TAG_TRACK, "track" },
|
||||
{ TAG_DATE, "year" },
|
||||
{ TAG_ARTIST_SORT, "author-sort" },
|
||||
{ TAG_ALBUM_ARTIST, "album_artist" },
|
||||
{ TAG_ALBUM_ARTIST_SORT, "album_artist-sort" },
|
||||
{ TAG_COMPOSER, "composer" },
|
||||
{ TAG_PERFORMER, "performer" },
|
||||
{ TAG_DISC, "disc" },
|
||||
|
||||
/* sentinel */
|
||||
{ TAG_NUM_OF_ITEM_TYPES, NULL }
|
||||
};
|
||||
|
||||
static bool
|
||||
ffmpeg_copy_metadata(struct tag *tag,
|
||||
#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,1,0)
|
||||
AVDictionary *m,
|
||||
#else
|
||||
AVMetadata *m,
|
||||
#if LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(53,1,0)
|
||||
#define AVDictionary AVMetadata
|
||||
#define AVDictionaryEntry AVMetadataTag
|
||||
#define av_dict_get av_metadata_get
|
||||
#endif
|
||||
const ffmpeg_tag_map tag_map)
|
||||
|
||||
static void
|
||||
ffmpeg_copy_metadata(struct tag *tag, enum tag_type type,
|
||||
AVDictionary *m, const char *name)
|
||||
{
|
||||
#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,1,0)
|
||||
AVDictionaryEntry *mt = NULL;
|
||||
|
||||
while ((mt = av_dict_get(m, tag_map.name, mt, 0)) != NULL)
|
||||
tag_add_item(tag, tag_map.type, mt->value);
|
||||
#else
|
||||
AVMetadataTag *mt = NULL;
|
||||
while ((mt = av_dict_get(m, name, mt, 0)) != NULL)
|
||||
tag_add_item(tag, type, mt->value);
|
||||
}
|
||||
|
||||
while ((mt = av_metadata_get(m, tag_map.name, mt, 0)) != NULL)
|
||||
tag_add_item(tag, tag_map.type, mt->value);
|
||||
#endif
|
||||
static void
|
||||
ffmpeg_copy_dictionary(struct tag *tag, AVDictionary *dict)
|
||||
{
|
||||
for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i)
|
||||
ffmpeg_copy_metadata(tag, i,
|
||||
dict, tag_item_names[i]);
|
||||
|
||||
return mt != NULL;
|
||||
for (const struct ffmpeg_tag_map *i = ffmpeg_tag_maps;
|
||||
i->name != NULL; ++i)
|
||||
ffmpeg_copy_metadata(tag, i->type, dict, i->name);
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -521,8 +656,18 @@ ffmpeg_stream_tag(struct input_stream *is)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (av_find_stream_info(f) < 0) {
|
||||
#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,6,0)
|
||||
const int find_result =
|
||||
avformat_find_stream_info(f, NULL);
|
||||
#else
|
||||
const int find_result = av_find_stream_info(f);
|
||||
#endif
|
||||
if (find_result < 0) {
|
||||
#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,17,0)
|
||||
avformat_close_input(&f);
|
||||
#else
|
||||
av_close_input_stream(f);
|
||||
#endif
|
||||
mpd_ffmpeg_stream_close(stream);
|
||||
return NULL;
|
||||
}
|
||||
@@ -534,14 +679,14 @@ ffmpeg_stream_tag(struct input_stream *is)
|
||||
: 0;
|
||||
|
||||
#if LIBAVFORMAT_VERSION_INT >= ((52<<16)+(31<<8)+0)
|
||||
#if LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(52,101,0)
|
||||
av_metadata_conv(f, NULL, f->iformat->metadata_conv);
|
||||
#endif
|
||||
|
||||
for (unsigned i = 0; i < sizeof(ffmpeg_tag_maps)/sizeof(ffmpeg_tag_map); i++) {
|
||||
int idx = ffmpeg_find_audio_stream(f);
|
||||
ffmpeg_copy_metadata(tag, f->metadata, ffmpeg_tag_maps[i]);
|
||||
if (idx >= 0)
|
||||
ffmpeg_copy_metadata(tag, f->streams[idx]->metadata, ffmpeg_tag_maps[i]);
|
||||
}
|
||||
ffmpeg_copy_dictionary(tag, f->metadata);
|
||||
int idx = ffmpeg_find_audio_stream(f);
|
||||
if (idx >= 0)
|
||||
ffmpeg_copy_dictionary(tag, f->streams[idx]->metadata);
|
||||
#else
|
||||
if (f->author[0])
|
||||
tag_add_item(tag, TAG_ARTIST, f->author);
|
||||
@@ -568,7 +713,11 @@ ffmpeg_stream_tag(struct input_stream *is)
|
||||
|
||||
#endif
|
||||
|
||||
#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,17,0)
|
||||
avformat_close_input(&f);
|
||||
#else
|
||||
av_close_input_stream(f);
|
||||
#endif
|
||||
mpd_ffmpeg_stream_close(stream);
|
||||
|
||||
return tag;
|
||||
|
||||
@@ -94,6 +94,12 @@ mp4_read(void *user_data, void *buffer, uint32_t length)
|
||||
{
|
||||
struct mp4ff_input_stream *mis = user_data;
|
||||
|
||||
if (length == 0)
|
||||
/* libmp4ff is known to attempt to read 0 bytes - make
|
||||
this a special case, because the input_stream API
|
||||
would not allow this */
|
||||
return 0;
|
||||
|
||||
return decoder_read(mis->decoder, mis->input_stream, buffer, length);
|
||||
}
|
||||
|
||||
|
||||
@@ -176,7 +176,7 @@ wavpack_decode(struct decoder *decoder, WavpackContext *wpc, bool can_seek)
|
||||
return;
|
||||
}
|
||||
|
||||
if ((WavpackGetMode(wpc) & MODE_FLOAT) == MODE_FLOAT) {
|
||||
if (is_float) {
|
||||
format_samples = format_samples_float;
|
||||
} else {
|
||||
format_samples = format_samples_int;
|
||||
|
||||
@@ -78,15 +78,76 @@ decoder_initialized(struct decoder *decoder,
|
||||
&af_string));
|
||||
}
|
||||
|
||||
enum decoder_command decoder_get_command(G_GNUC_UNUSED struct decoder * decoder)
|
||||
/**
|
||||
* Checks if we need an "initial seek". If so, then the initial seek
|
||||
* is prepared, and the function returns true.
|
||||
*/
|
||||
G_GNUC_PURE
|
||||
static bool
|
||||
decoder_prepare_initial_seek(struct decoder *decoder)
|
||||
{
|
||||
const struct decoder_control *dc = decoder->dc;
|
||||
|
||||
assert(dc->pipe != NULL);
|
||||
|
||||
if (dc->state != DECODE_STATE_DECODE)
|
||||
/* wait until the decoder has finished initialisation
|
||||
(reading file headers etc.) before emitting the
|
||||
virtual "SEEK" command */
|
||||
return false;
|
||||
|
||||
if (decoder->initial_seek_running)
|
||||
/* initial seek has already begun - override any other
|
||||
command */
|
||||
return true;
|
||||
|
||||
if (decoder->initial_seek_pending) {
|
||||
if (!dc->seekable) {
|
||||
/* seeking is not possible */
|
||||
decoder->initial_seek_pending = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (dc->command == DECODE_COMMAND_NONE) {
|
||||
/* begin initial seek */
|
||||
|
||||
decoder->initial_seek_pending = false;
|
||||
decoder->initial_seek_running = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
/* skip initial seek when there's another command
|
||||
(e.g. STOP) */
|
||||
|
||||
decoder->initial_seek_pending = false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current decoder command. May return a "virtual"
|
||||
* synthesized command, e.g. to seek to the beginning of the CUE
|
||||
* track.
|
||||
*/
|
||||
G_GNUC_PURE
|
||||
static enum decoder_command
|
||||
decoder_get_virtual_command(struct decoder *decoder)
|
||||
{
|
||||
const struct decoder_control *dc = decoder->dc;
|
||||
assert(dc->pipe != NULL);
|
||||
|
||||
if (decoder_prepare_initial_seek(decoder))
|
||||
return DECODE_COMMAND_SEEK;
|
||||
|
||||
return dc->command;
|
||||
}
|
||||
|
||||
enum decoder_command
|
||||
decoder_get_command(struct decoder *decoder)
|
||||
{
|
||||
return decoder_get_virtual_command(decoder);
|
||||
}
|
||||
|
||||
void
|
||||
decoder_command_finished(struct decoder *decoder)
|
||||
{
|
||||
@@ -94,11 +155,24 @@ decoder_command_finished(struct decoder *decoder)
|
||||
|
||||
decoder_lock(dc);
|
||||
|
||||
assert(dc->command != DECODE_COMMAND_NONE);
|
||||
assert(dc->command != DECODE_COMMAND_NONE ||
|
||||
decoder->initial_seek_running);
|
||||
assert(dc->command != DECODE_COMMAND_SEEK ||
|
||||
decoder->initial_seek_running ||
|
||||
dc->seek_error || decoder->seeking);
|
||||
assert(dc->pipe != NULL);
|
||||
|
||||
if (decoder->initial_seek_running) {
|
||||
assert(!decoder->seeking);
|
||||
assert(decoder->chunk == NULL);
|
||||
assert(music_pipe_empty(dc->pipe));
|
||||
|
||||
decoder->initial_seek_running = false;
|
||||
decoder->timestamp = dc->start_ms / 1000.;
|
||||
decoder_unlock(dc);
|
||||
return;
|
||||
}
|
||||
|
||||
if (decoder->seeking) {
|
||||
decoder->seeking = false;
|
||||
|
||||
@@ -124,9 +198,13 @@ double decoder_seek_where(G_GNUC_UNUSED struct decoder * decoder)
|
||||
{
|
||||
const struct decoder_control *dc = decoder->dc;
|
||||
|
||||
assert(dc->command == DECODE_COMMAND_SEEK);
|
||||
assert(dc->pipe != NULL);
|
||||
|
||||
if (decoder->initial_seek_running)
|
||||
return dc->start_ms / 1000.;
|
||||
|
||||
assert(dc->command == DECODE_COMMAND_SEEK);
|
||||
|
||||
decoder->seeking = true;
|
||||
|
||||
return dc->seek_where;
|
||||
@@ -136,9 +214,17 @@ void decoder_seek_error(struct decoder * decoder)
|
||||
{
|
||||
struct decoder_control *dc = decoder->dc;
|
||||
|
||||
assert(dc->command == DECODE_COMMAND_SEEK);
|
||||
assert(dc->pipe != NULL);
|
||||
|
||||
if (decoder->initial_seek_running) {
|
||||
/* d'oh, we can't seek to the sub-song start position,
|
||||
what now? - no idea, ignoring the problem for now. */
|
||||
decoder->initial_seek_running = false;
|
||||
return;
|
||||
}
|
||||
|
||||
assert(dc->command == DECODE_COMMAND_SEEK);
|
||||
|
||||
dc->seek_error = true;
|
||||
decoder->seeking = false;
|
||||
|
||||
@@ -270,7 +356,7 @@ decoder_data(struct decoder *decoder,
|
||||
assert(length % audio_format_frame_size(&dc->in_audio_format) == 0);
|
||||
|
||||
decoder_lock(dc);
|
||||
cmd = dc->command;
|
||||
cmd = decoder_get_virtual_command(decoder);
|
||||
decoder_unlock(dc);
|
||||
|
||||
if (cmd == DECODE_COMMAND_STOP || cmd == DECODE_COMMAND_SEEK ||
|
||||
@@ -357,8 +443,8 @@ decoder_data(struct decoder *decoder,
|
||||
decoder->timestamp += (double)nbytes /
|
||||
audio_format_time_to_size(&dc->out_audio_format);
|
||||
|
||||
if (dc->song->end_ms > 0 &&
|
||||
decoder->timestamp >= dc->song->end_ms / 1000.0)
|
||||
if (dc->end_ms > 0 &&
|
||||
decoder->timestamp >= dc->end_ms / 1000.0)
|
||||
/* the end of this range has been reached:
|
||||
stop decoding */
|
||||
return DECODE_COMMAND_STOP;
|
||||
@@ -388,6 +474,14 @@ decoder_tag(G_GNUC_UNUSED struct decoder *decoder, struct input_stream *is,
|
||||
|
||||
update_stream_tag(decoder, is);
|
||||
|
||||
/* check if we're seeking */
|
||||
|
||||
if (decoder_prepare_initial_seek(decoder))
|
||||
/* during initial seek, no music chunk must be created
|
||||
until seeking is finished; skip the rest of the
|
||||
function here */
|
||||
return DECODE_COMMAND_SEEK;
|
||||
|
||||
/* send tag to music pipe */
|
||||
|
||||
if (decoder->stream_tag != NULL) {
|
||||
|
||||
@@ -102,6 +102,7 @@ dc_command_async(struct decoder_control *dc, enum decoder_command cmd)
|
||||
|
||||
void
|
||||
dc_start(struct decoder_control *dc, struct song *song,
|
||||
unsigned start_ms, unsigned end_ms,
|
||||
struct music_buffer *buffer, struct music_pipe *pipe)
|
||||
{
|
||||
assert(song != NULL);
|
||||
@@ -110,6 +111,8 @@ dc_start(struct decoder_control *dc, struct song *song,
|
||||
assert(music_pipe_empty(pipe));
|
||||
|
||||
dc->song = song;
|
||||
dc->start_ms = start_ms;
|
||||
dc->end_ms = end_ms;
|
||||
dc->buffer = buffer;
|
||||
dc->pipe = pipe;
|
||||
dc_command(dc, DECODE_COMMAND_START);
|
||||
|
||||
@@ -79,6 +79,23 @@ struct decoder_control {
|
||||
*/
|
||||
const struct song *song;
|
||||
|
||||
/**
|
||||
* The initial seek position (in milliseconds), e.g. to the
|
||||
* start of a sub-track described by a CUE file.
|
||||
*
|
||||
* This attribute is set by dc_start().
|
||||
*/
|
||||
unsigned start_ms;
|
||||
|
||||
/**
|
||||
* The decoder will stop when it reaches this position (in
|
||||
* milliseconds). 0 means don't stop before the end of the
|
||||
* file.
|
||||
*
|
||||
* This attribute is set by dc_start().
|
||||
*/
|
||||
unsigned end_ms;
|
||||
|
||||
float total_time;
|
||||
|
||||
/** the #music_chunk allocator */
|
||||
@@ -225,11 +242,14 @@ dc_command_wait(struct decoder_control *dc);
|
||||
*
|
||||
* @param the decoder
|
||||
* @param song the song to be decoded
|
||||
* @param start_ms see #decoder_control
|
||||
* @param end_ms see #decoder_control
|
||||
* @param pipe the pipe which receives the decoded chunks (owned by
|
||||
* the caller)
|
||||
*/
|
||||
void
|
||||
dc_start(struct decoder_control *dc, struct song *song,
|
||||
unsigned start_ms, unsigned end_ms,
|
||||
struct music_buffer *buffer, struct music_pipe *pipe);
|
||||
|
||||
void
|
||||
|
||||
@@ -36,6 +36,25 @@ struct decoder {
|
||||
*/
|
||||
double timestamp;
|
||||
|
||||
/**
|
||||
* Is the initial seek (to the start position of the sub-song)
|
||||
* pending, or has it been performed already?
|
||||
*/
|
||||
bool initial_seek_pending;
|
||||
|
||||
/**
|
||||
* Is the initial seek currently running? During this time,
|
||||
* the decoder command is SEEK. This flag is set by
|
||||
* decoder_get_virtual_command(), when the virtual SEEK
|
||||
* command is generated for the first time.
|
||||
*/
|
||||
bool initial_seek_running;
|
||||
|
||||
/**
|
||||
* This flag is set by decoder_seek_where(), and checked by
|
||||
* decoder_command_finished(). It is used to clean up after
|
||||
* seeking.
|
||||
*/
|
||||
bool seeking;
|
||||
|
||||
/**
|
||||
|
||||
@@ -38,6 +38,7 @@
|
||||
#include <glib.h>
|
||||
|
||||
#include <unistd.h>
|
||||
#include <stdio.h> /* for SEEK_SET */
|
||||
|
||||
#undef G_LOG_DOMAIN
|
||||
#define G_LOG_DOMAIN "decoder_thread"
|
||||
@@ -369,6 +370,8 @@ decoder_run_song(struct decoder_control *dc,
|
||||
{
|
||||
struct decoder decoder = {
|
||||
.dc = dc,
|
||||
.initial_seek_pending = dc->start_ms > 0,
|
||||
.initial_seek_running = false,
|
||||
};
|
||||
int ret;
|
||||
|
||||
|
||||
@@ -68,7 +68,15 @@ directory_free(struct directory *directory)
|
||||
const char *
|
||||
directory_get_name(const struct directory *directory)
|
||||
{
|
||||
return g_basename(directory->path);
|
||||
assert(!directory_is_root(directory));
|
||||
assert(directory->path != NULL);
|
||||
|
||||
const char *slash = strrchr(directory->path, '/');
|
||||
assert((slash == NULL) == directory_is_root(directory->parent));
|
||||
|
||||
return slash != NULL
|
||||
? slash + 1
|
||||
: directory->path;
|
||||
}
|
||||
|
||||
void
|
||||
|
||||
@@ -43,7 +43,7 @@ struct directory {
|
||||
time_t mtime;
|
||||
ino_t inode;
|
||||
dev_t device;
|
||||
unsigned stat; /* not needed if ino_t == dev_t == 0 is impossible */
|
||||
bool have_stat; /* not needed if ino_t == dev_t == 0 is impossible */
|
||||
char path[sizeof(long)];
|
||||
};
|
||||
|
||||
|
||||
@@ -22,6 +22,8 @@
|
||||
#include "encoder_plugin.h"
|
||||
#include "audio_format.h"
|
||||
#include "pcm_buffer.h"
|
||||
#include "fifo_buffer.h"
|
||||
#include "growing_fifo.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
@@ -38,8 +40,11 @@ struct flac_encoder {
|
||||
|
||||
struct pcm_buffer expand_buffer;
|
||||
|
||||
struct pcm_buffer buffer;
|
||||
size_t buffer_length;
|
||||
/**
|
||||
* This buffer will hold encoded data from libFLAC until it is
|
||||
* picked up with flac_encoder_read().
|
||||
*/
|
||||
struct fifo_buffer *output_buffer;
|
||||
};
|
||||
|
||||
extern const struct encoder_plugin flac_encoder_plugin;
|
||||
@@ -140,11 +145,8 @@ flac_write_callback(G_GNUC_UNUSED const FLAC__StreamEncoder *fse,
|
||||
{
|
||||
struct flac_encoder *encoder = (struct flac_encoder *) client_data;
|
||||
|
||||
char *buffer = pcm_buffer_get(&encoder->buffer, encoder->buffer_length + bytes);
|
||||
|
||||
//transfer data to buffer
|
||||
memcpy( buffer + encoder->buffer_length, data, bytes);
|
||||
encoder->buffer_length += bytes;
|
||||
growing_fifo_append(&encoder->output_buffer, data, bytes);
|
||||
|
||||
return FLAC__STREAM_ENCODER_WRITE_STATUS_OK;
|
||||
}
|
||||
@@ -156,8 +158,8 @@ flac_encoder_close(struct encoder *_encoder)
|
||||
|
||||
FLAC__stream_encoder_delete(encoder->fse);
|
||||
|
||||
pcm_buffer_deinit(&encoder->buffer);
|
||||
pcm_buffer_deinit(&encoder->expand_buffer);
|
||||
fifo_buffer_free(encoder->output_buffer);
|
||||
}
|
||||
|
||||
static bool
|
||||
@@ -201,10 +203,10 @@ flac_encoder_open(struct encoder *_encoder, struct audio_format *audio_format,
|
||||
return false;
|
||||
}
|
||||
|
||||
encoder->buffer_length = 0;
|
||||
pcm_buffer_init(&encoder->buffer);
|
||||
pcm_buffer_init(&encoder->expand_buffer);
|
||||
|
||||
encoder->output_buffer = growing_fifo_new();
|
||||
|
||||
/* this immediatelly outputs data throught callback */
|
||||
|
||||
#if !defined(FLAC_API_VERSION_CURRENT) || FLAC_API_VERSION_CURRENT <= 7
|
||||
@@ -325,16 +327,18 @@ static size_t
|
||||
flac_encoder_read(struct encoder *_encoder, void *dest, size_t length)
|
||||
{
|
||||
struct flac_encoder *encoder = (struct flac_encoder *)_encoder;
|
||||
char *buffer = pcm_buffer_get(&encoder->buffer, encoder->buffer_length);
|
||||
|
||||
if (length > encoder->buffer_length)
|
||||
length = encoder->buffer_length;
|
||||
size_t max_length;
|
||||
const char *src = fifo_buffer_read(encoder->output_buffer,
|
||||
&max_length);
|
||||
if (src == NULL)
|
||||
return 0;
|
||||
|
||||
memcpy(dest, buffer, length);
|
||||
|
||||
encoder->buffer_length -= length;
|
||||
memmove(buffer, buffer + length, encoder->buffer_length);
|
||||
if (length > max_length)
|
||||
length = max_length;
|
||||
|
||||
memcpy(dest, src, length);
|
||||
fifo_buffer_consume(encoder->output_buffer, length);
|
||||
return length;
|
||||
}
|
||||
|
||||
@@ -350,6 +354,7 @@ const struct encoder_plugin flac_encoder_plugin = {
|
||||
.finish = flac_encoder_finish,
|
||||
.open = flac_encoder_open,
|
||||
.close = flac_encoder_close,
|
||||
.end = flac_encoder_flush,
|
||||
.flush = flac_encoder_flush,
|
||||
.write = flac_encoder_write,
|
||||
.read = flac_encoder_read,
|
||||
|
||||
@@ -20,7 +20,8 @@
|
||||
#include "config.h"
|
||||
#include "encoder_api.h"
|
||||
#include "encoder_plugin.h"
|
||||
#include "pcm_buffer.h"
|
||||
#include "fifo_buffer.h"
|
||||
#include "growing_fifo.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
@@ -28,8 +29,7 @@
|
||||
struct null_encoder {
|
||||
struct encoder encoder;
|
||||
|
||||
struct pcm_buffer buffer;
|
||||
size_t buffer_length;
|
||||
struct fifo_buffer *buffer;
|
||||
};
|
||||
|
||||
extern const struct encoder_plugin null_encoder_plugin;
|
||||
@@ -65,7 +65,7 @@ null_encoder_close(struct encoder *_encoder)
|
||||
{
|
||||
struct null_encoder *encoder = (struct null_encoder *)_encoder;
|
||||
|
||||
pcm_buffer_deinit(&encoder->buffer);
|
||||
fifo_buffer_free(encoder->buffer);
|
||||
}
|
||||
|
||||
|
||||
@@ -76,9 +76,7 @@ null_encoder_open(struct encoder *_encoder,
|
||||
{
|
||||
struct null_encoder *encoder = (struct null_encoder *)_encoder;
|
||||
|
||||
encoder->buffer_length = 0;
|
||||
pcm_buffer_init(&encoder->buffer);
|
||||
|
||||
encoder->buffer = growing_fifo_new();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -88,28 +86,26 @@ null_encoder_write(struct encoder *_encoder,
|
||||
G_GNUC_UNUSED GError **error)
|
||||
{
|
||||
struct null_encoder *encoder = (struct null_encoder *)_encoder;
|
||||
char *buffer = pcm_buffer_get(&encoder->buffer, encoder->buffer_length + length);
|
||||
|
||||
memcpy(buffer+encoder->buffer_length, data, length);
|
||||
|
||||
encoder->buffer_length += length;
|
||||
return true;
|
||||
growing_fifo_append(&encoder->buffer, data, length);
|
||||
return length;
|
||||
}
|
||||
|
||||
static size_t
|
||||
null_encoder_read(struct encoder *_encoder, void *dest, size_t length)
|
||||
{
|
||||
struct null_encoder *encoder = (struct null_encoder *)_encoder;
|
||||
char *buffer = pcm_buffer_get(&encoder->buffer, encoder->buffer_length);
|
||||
|
||||
if (length > encoder->buffer_length)
|
||||
length = encoder->buffer_length;
|
||||
size_t max_length;
|
||||
const void *src = fifo_buffer_read(encoder->buffer, &max_length);
|
||||
if (src == NULL)
|
||||
return 0;
|
||||
|
||||
memcpy(dest, buffer, length);
|
||||
|
||||
encoder->buffer_length -= length;
|
||||
memmove(buffer, buffer + length, encoder->buffer_length);
|
||||
if (length > max_length)
|
||||
length = max_length;
|
||||
|
||||
memcpy(dest, src, length);
|
||||
fifo_buffer_consume(encoder->buffer, length);
|
||||
return length;
|
||||
}
|
||||
|
||||
|
||||
@@ -300,6 +300,7 @@ const struct encoder_plugin twolame_encoder_plugin = {
|
||||
.finish = twolame_encoder_finish,
|
||||
.open = twolame_encoder_open,
|
||||
.close = twolame_encoder_close,
|
||||
.end = twolame_encoder_flush,
|
||||
.flush = twolame_encoder_flush,
|
||||
.write = twolame_encoder_write,
|
||||
.read = twolame_encoder_read,
|
||||
|
||||
@@ -285,8 +285,6 @@ vorbis_encoder_pre_tag(struct encoder *_encoder, G_GNUC_UNUSED GError **error)
|
||||
vorbis_analysis_init(&encoder->vd, &encoder->vi);
|
||||
vorbis_block_init(&encoder->vd, &encoder->vb);
|
||||
|
||||
ogg_stream_reset(&encoder->os);
|
||||
|
||||
encoder->flush = true;
|
||||
return true;
|
||||
}
|
||||
@@ -407,6 +405,7 @@ const struct encoder_plugin vorbis_encoder_plugin = {
|
||||
.finish = vorbis_encoder_finish,
|
||||
.open = vorbis_encoder_open,
|
||||
.close = vorbis_encoder_close,
|
||||
.end = vorbis_encoder_pre_tag,
|
||||
.flush = vorbis_encoder_flush,
|
||||
.pre_tag = vorbis_encoder_pre_tag,
|
||||
.tag = vorbis_encoder_tag,
|
||||
|
||||
@@ -20,7 +20,8 @@
|
||||
#include "config.h"
|
||||
#include "encoder_api.h"
|
||||
#include "encoder_plugin.h"
|
||||
#include "pcm_buffer.h"
|
||||
#include "fifo_buffer.h"
|
||||
#include "growing_fifo.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
@@ -29,8 +30,7 @@ struct wave_encoder {
|
||||
struct encoder encoder;
|
||||
unsigned bits;
|
||||
|
||||
struct pcm_buffer buffer;
|
||||
size_t buffer_length;
|
||||
struct fifo_buffer *buffer;
|
||||
};
|
||||
|
||||
struct wave_header {
|
||||
@@ -92,7 +92,6 @@ wave_encoder_init(G_GNUC_UNUSED const struct config_param *param,
|
||||
|
||||
encoder = g_new(struct wave_encoder, 1);
|
||||
encoder_struct_init(&encoder->encoder, &wave_encoder_plugin);
|
||||
pcm_buffer_init(&encoder->buffer);
|
||||
|
||||
return &encoder->encoder;
|
||||
}
|
||||
@@ -102,7 +101,6 @@ wave_encoder_finish(struct encoder *_encoder)
|
||||
{
|
||||
struct wave_encoder *encoder = (struct wave_encoder *)_encoder;
|
||||
|
||||
pcm_buffer_deinit(&encoder->buffer);
|
||||
g_free(encoder);
|
||||
}
|
||||
|
||||
@@ -112,7 +110,6 @@ wave_encoder_open(struct encoder *_encoder,
|
||||
G_GNUC_UNUSED GError **error)
|
||||
{
|
||||
struct wave_encoder *encoder = (struct wave_encoder *)_encoder;
|
||||
void *buffer;
|
||||
|
||||
assert(audio_format_valid(audio_format));
|
||||
|
||||
@@ -125,6 +122,11 @@ wave_encoder_open(struct encoder *_encoder,
|
||||
encoder->bits = 16;
|
||||
break;
|
||||
|
||||
case SAMPLE_FORMAT_S24:
|
||||
audio_format->format = SAMPLE_FORMAT_S24_P32;
|
||||
encoder->bits = 24;
|
||||
break;
|
||||
|
||||
case SAMPLE_FORMAT_S24_P32:
|
||||
encoder->bits = 24;
|
||||
break;
|
||||
@@ -139,19 +141,29 @@ wave_encoder_open(struct encoder *_encoder,
|
||||
break;
|
||||
}
|
||||
|
||||
buffer = pcm_buffer_get(&encoder->buffer, sizeof(struct wave_header) );
|
||||
encoder->buffer = growing_fifo_new();
|
||||
struct wave_header *header =
|
||||
growing_fifo_write(&encoder->buffer, sizeof(*header));
|
||||
|
||||
/* create PCM wave header in initial buffer */
|
||||
fill_wave_header((struct wave_header *) buffer,
|
||||
fill_wave_header(header,
|
||||
audio_format->channels,
|
||||
encoder->bits,
|
||||
audio_format->sample_rate,
|
||||
(encoder->bits / 8) * audio_format->channels );
|
||||
fifo_buffer_append(encoder->buffer, sizeof(*header));
|
||||
|
||||
encoder->buffer_length = sizeof(struct wave_header);
|
||||
return true;
|
||||
}
|
||||
|
||||
static void
|
||||
wave_encoder_close(struct encoder *_encoder)
|
||||
{
|
||||
struct wave_encoder *encoder = (struct wave_encoder *)_encoder;
|
||||
|
||||
fifo_buffer_free(encoder->buffer);
|
||||
}
|
||||
|
||||
static inline size_t
|
||||
pcm16_to_wave(uint16_t *dst16, const uint16_t *src16, size_t length)
|
||||
{
|
||||
@@ -198,9 +210,8 @@ wave_encoder_write(struct encoder *_encoder,
|
||||
G_GNUC_UNUSED GError **error)
|
||||
{
|
||||
struct wave_encoder *encoder = (struct wave_encoder *)_encoder;
|
||||
void *dst;
|
||||
|
||||
dst = pcm_buffer_get(&encoder->buffer, encoder->buffer_length + length);
|
||||
void *dst = growing_fifo_write(&encoder->buffer, length);
|
||||
|
||||
#if (G_BYTE_ORDER == G_LITTLE_ENDIAN)
|
||||
switch (encoder->bits) {
|
||||
@@ -232,7 +243,7 @@ wave_encoder_write(struct encoder *_encoder,
|
||||
#error G_BYTE_ORDER set to G_PDP_ENDIAN is not supported by wave_encoder
|
||||
#endif
|
||||
|
||||
encoder->buffer_length += length;
|
||||
fifo_buffer_append(encoder->buffer, length);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -240,16 +251,17 @@ static size_t
|
||||
wave_encoder_read(struct encoder *_encoder, void *dest, size_t length)
|
||||
{
|
||||
struct wave_encoder *encoder = (struct wave_encoder *)_encoder;
|
||||
uint8_t *buffer = pcm_buffer_get(&encoder->buffer, encoder->buffer_length );
|
||||
|
||||
if (length > encoder->buffer_length)
|
||||
length = encoder->buffer_length;
|
||||
size_t max_length;
|
||||
const void *src = fifo_buffer_read(encoder->buffer, &max_length);
|
||||
if (src == NULL)
|
||||
return 0;
|
||||
|
||||
memcpy(dest, buffer, length);
|
||||
|
||||
encoder->buffer_length -= length;
|
||||
memmove(buffer, buffer + length, encoder->buffer_length);
|
||||
if (length > max_length)
|
||||
length = max_length;
|
||||
|
||||
memcpy(dest, src, length);
|
||||
fifo_buffer_consume(encoder->buffer, length);
|
||||
return length;
|
||||
}
|
||||
|
||||
@@ -264,6 +276,7 @@ const struct encoder_plugin wave_encoder_plugin = {
|
||||
.init = wave_encoder_init,
|
||||
.finish = wave_encoder_finish,
|
||||
.open = wave_encoder_open,
|
||||
.close = wave_encoder_close,
|
||||
.write = wave_encoder_write,
|
||||
.read = wave_encoder_read,
|
||||
.get_mime_type = wave_encoder_get_mime_type,
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
|
||||
@@ -32,6 +33,10 @@ struct tag;
|
||||
|
||||
struct encoder {
|
||||
const struct encoder_plugin *plugin;
|
||||
|
||||
#ifndef NDEBUG
|
||||
bool open, pre_tag, tag, end;
|
||||
#endif
|
||||
};
|
||||
|
||||
struct encoder_plugin {
|
||||
@@ -48,6 +53,8 @@ struct encoder_plugin {
|
||||
|
||||
void (*close)(struct encoder *encoder);
|
||||
|
||||
bool (*end)(struct encoder *encoder, GError **error);
|
||||
|
||||
bool (*flush)(struct encoder *encoder, GError **error);
|
||||
|
||||
bool (*pre_tag)(struct encoder *encoder, GError **error);
|
||||
@@ -73,6 +80,10 @@ encoder_struct_init(struct encoder *encoder,
|
||||
const struct encoder_plugin *plugin)
|
||||
{
|
||||
encoder->plugin = plugin;
|
||||
|
||||
#ifndef NDEBUG
|
||||
encoder->open = false;
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -98,6 +109,8 @@ encoder_init(const struct encoder_plugin *plugin,
|
||||
static inline void
|
||||
encoder_finish(struct encoder *encoder)
|
||||
{
|
||||
assert(!encoder->open);
|
||||
|
||||
encoder->plugin->finish(encoder);
|
||||
}
|
||||
|
||||
@@ -116,7 +129,14 @@ static inline bool
|
||||
encoder_open(struct encoder *encoder, struct audio_format *audio_format,
|
||||
GError **error)
|
||||
{
|
||||
return encoder->plugin->open(encoder, audio_format, error);
|
||||
assert(!encoder->open);
|
||||
|
||||
bool success = encoder->plugin->open(encoder, audio_format, error);
|
||||
#ifndef NDEBUG
|
||||
encoder->open = success;
|
||||
encoder->pre_tag = encoder->tag = encoder->end = false;
|
||||
#endif
|
||||
return success;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -128,8 +148,43 @@ encoder_open(struct encoder *encoder, struct audio_format *audio_format,
|
||||
static inline void
|
||||
encoder_close(struct encoder *encoder)
|
||||
{
|
||||
assert(encoder->open);
|
||||
|
||||
if (encoder->plugin->close != NULL)
|
||||
encoder->plugin->close(encoder);
|
||||
|
||||
#ifndef NDEBUG
|
||||
encoder->open = false;
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* Ends the stream: flushes the encoder object, generate an
|
||||
* end-of-stream marker (if applicable), make everything which might
|
||||
* currently be buffered available by encoder_read().
|
||||
*
|
||||
* After this function has been called, the encoder may not be usable
|
||||
* for more data, and only encoder_read() and encoder_close() can be
|
||||
* called.
|
||||
*
|
||||
* @param encoder the encoder
|
||||
* @param error location to store the error occuring, or NULL to ignore errors.
|
||||
* @return true on success
|
||||
*/
|
||||
static inline bool
|
||||
encoder_end(struct encoder *encoder, GError **error)
|
||||
{
|
||||
assert(encoder->open);
|
||||
assert(!encoder->end);
|
||||
|
||||
#ifndef NDEBUG
|
||||
encoder->end = true;
|
||||
#endif
|
||||
|
||||
/* this method is optional */
|
||||
return encoder->plugin->end != NULL
|
||||
? encoder->plugin->end(encoder, error)
|
||||
: true;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -143,6 +198,11 @@ encoder_close(struct encoder *encoder)
|
||||
static inline bool
|
||||
encoder_flush(struct encoder *encoder, GError **error)
|
||||
{
|
||||
assert(encoder->open);
|
||||
assert(!encoder->pre_tag);
|
||||
assert(!encoder->tag);
|
||||
assert(!encoder->end);
|
||||
|
||||
/* this method is optional */
|
||||
return encoder->plugin->flush != NULL
|
||||
? encoder->plugin->flush(encoder, error)
|
||||
@@ -162,10 +222,20 @@ encoder_flush(struct encoder *encoder, GError **error)
|
||||
static inline bool
|
||||
encoder_pre_tag(struct encoder *encoder, GError **error)
|
||||
{
|
||||
assert(encoder->open);
|
||||
assert(!encoder->pre_tag);
|
||||
assert(!encoder->tag);
|
||||
assert(!encoder->end);
|
||||
|
||||
/* this method is optional */
|
||||
return encoder->plugin->pre_tag != NULL
|
||||
bool success = encoder->plugin->pre_tag != NULL
|
||||
? encoder->plugin->pre_tag(encoder, error)
|
||||
: true;
|
||||
|
||||
#ifndef NDEBUG
|
||||
encoder->pre_tag = success;
|
||||
#endif
|
||||
return success;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -182,6 +252,15 @@ encoder_pre_tag(struct encoder *encoder, GError **error)
|
||||
static inline bool
|
||||
encoder_tag(struct encoder *encoder, const struct tag *tag, GError **error)
|
||||
{
|
||||
assert(encoder->open);
|
||||
assert(!encoder->pre_tag);
|
||||
assert(encoder->tag);
|
||||
assert(!encoder->end);
|
||||
|
||||
#ifndef NDEBUG
|
||||
encoder->tag = false;
|
||||
#endif
|
||||
|
||||
/* this method is optional */
|
||||
return encoder->plugin->tag != NULL
|
||||
? encoder->plugin->tag(encoder, tag, error)
|
||||
@@ -201,6 +280,11 @@ static inline bool
|
||||
encoder_write(struct encoder *encoder, const void *data, size_t length,
|
||||
GError **error)
|
||||
{
|
||||
assert(encoder->open);
|
||||
assert(!encoder->pre_tag);
|
||||
assert(!encoder->tag);
|
||||
assert(!encoder->end);
|
||||
|
||||
return encoder->plugin->write(encoder, data, length, error);
|
||||
}
|
||||
|
||||
@@ -215,6 +299,16 @@ encoder_write(struct encoder *encoder, const void *data, size_t length,
|
||||
static inline size_t
|
||||
encoder_read(struct encoder *encoder, void *dest, size_t length)
|
||||
{
|
||||
assert(encoder->open);
|
||||
assert(!encoder->pre_tag || !encoder->tag);
|
||||
|
||||
#ifndef NDEBUG
|
||||
if (encoder->pre_tag) {
|
||||
encoder->pre_tag = false;
|
||||
encoder->tag = true;
|
||||
}
|
||||
#endif
|
||||
|
||||
return encoder->plugin->read(encoder, dest, length);
|
||||
}
|
||||
|
||||
|
||||
@@ -159,5 +159,6 @@ void event_pipe_emit_fast(enum pipe_event event)
|
||||
assert((unsigned)event < PIPE_EVENT_MAX);
|
||||
|
||||
pipe_events[event] = true;
|
||||
(void)write(event_pipe[1], "", 1);
|
||||
|
||||
G_GNUC_UNUSED ssize_t nbytes = write(event_pipe[1], "", 1);
|
||||
}
|
||||
|
||||
@@ -304,3 +304,13 @@ inotify_init_cloexec(void)
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
int
|
||||
close_socket(int fd)
|
||||
{
|
||||
#ifdef WIN32
|
||||
return closesocket(fd);
|
||||
#else
|
||||
return close(fd);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -36,6 +36,8 @@
|
||||
#ifndef FD_UTIL_H
|
||||
#define FD_UTIL_H
|
||||
|
||||
#include "check.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
|
||||
@@ -120,6 +122,8 @@ recvmsg_cloexec(int sockfd, struct msghdr *msg, int flags);
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_INOTIFY_INIT
|
||||
|
||||
/**
|
||||
* Wrapper for inotify_init(), which sets the CLOEXEC flag (atomically
|
||||
* if supported by the OS).
|
||||
@@ -128,3 +132,11 @@ int
|
||||
inotify_init_cloexec(void);
|
||||
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Portable wrapper for close(); use closesocket() on WIN32/WinSock.
|
||||
*/
|
||||
int
|
||||
close_socket(int fd);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2010 The Music Player Daemon Project
|
||||
* Copyright (C) 2003-2011 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
@@ -58,6 +58,39 @@ fifo_buffer_new(size_t size)
|
||||
return buffer;
|
||||
}
|
||||
|
||||
static void
|
||||
fifo_buffer_move(struct fifo_buffer *buffer);
|
||||
|
||||
struct fifo_buffer *
|
||||
fifo_buffer_realloc(struct fifo_buffer *buffer, size_t new_size)
|
||||
{
|
||||
if (buffer == NULL)
|
||||
return new_size > 0
|
||||
? fifo_buffer_new(new_size)
|
||||
: NULL;
|
||||
|
||||
/* existing data must fit in new size */
|
||||
assert(new_size >= buffer->end - buffer->start);
|
||||
|
||||
if (new_size == 0) {
|
||||
fifo_buffer_free(buffer);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* compress the buffer when we're shrinking and the tail of
|
||||
the buffer would exceed the new size */
|
||||
if (buffer->end > new_size)
|
||||
fifo_buffer_move(buffer);
|
||||
|
||||
/* existing data must fit in new size: second check */
|
||||
assert(buffer->end <= new_size);
|
||||
|
||||
buffer = g_realloc(buffer, sizeof(*buffer) - sizeof(buffer->buffer) +
|
||||
new_size);
|
||||
buffer->size = new_size;
|
||||
return buffer;
|
||||
}
|
||||
|
||||
void
|
||||
fifo_buffer_free(struct fifo_buffer *buffer)
|
||||
{
|
||||
@@ -66,6 +99,22 @@ fifo_buffer_free(struct fifo_buffer *buffer)
|
||||
g_free(buffer);
|
||||
}
|
||||
|
||||
size_t
|
||||
fifo_buffer_capacity(const struct fifo_buffer *buffer)
|
||||
{
|
||||
assert(buffer != NULL);
|
||||
|
||||
return buffer->size;
|
||||
}
|
||||
|
||||
size_t
|
||||
fifo_buffer_available(const struct fifo_buffer *buffer)
|
||||
{
|
||||
assert(buffer != NULL);
|
||||
|
||||
return buffer->end - buffer->start;
|
||||
}
|
||||
|
||||
void
|
||||
fifo_buffer_clear(struct fifo_buffer *buffer)
|
||||
{
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2010 The Music Player Daemon Project
|
||||
* Copyright (C) 2003-2011 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
@@ -56,12 +56,37 @@ struct fifo_buffer;
|
||||
struct fifo_buffer *
|
||||
fifo_buffer_new(size_t size);
|
||||
|
||||
/**
|
||||
* Change the capacity of the #fifo_buffer, while preserving existing
|
||||
* data.
|
||||
*
|
||||
* @param buffer the old buffer, may be NULL
|
||||
* @param new_size the requested new size of the #fifo_buffer; must
|
||||
* not be smaller than the data which is stored in the old buffer
|
||||
* @return the new buffer, may be NULL if the requested new size is 0
|
||||
*/
|
||||
struct fifo_buffer *
|
||||
fifo_buffer_realloc(struct fifo_buffer *buffer, size_t new_size);
|
||||
|
||||
/**
|
||||
* Frees the resources consumed by this #fifo_buffer object.
|
||||
*/
|
||||
void
|
||||
fifo_buffer_free(struct fifo_buffer *buffer);
|
||||
|
||||
/**
|
||||
* Return the capacity of the buffer, i.e. the size that was passed to
|
||||
* fifo_buffer_new().
|
||||
*/
|
||||
size_t
|
||||
fifo_buffer_capacity(const struct fifo_buffer *buffer);
|
||||
|
||||
/**
|
||||
* Return the number of bytes currently stored in the buffer.
|
||||
*/
|
||||
size_t
|
||||
fifo_buffer_available(const struct fifo_buffer *buffer);
|
||||
|
||||
/**
|
||||
* Clears all data currently in this #fifo_buffer object. This does
|
||||
* not overwrite the actuall buffer; it just resets the internal
|
||||
|
||||
40
src/glib_socket.h
Normal file
40
src/glib_socket.h
Normal file
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2011 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_GLIB_SOCKET_H
|
||||
#define MPD_GLIB_SOCKET_H
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
/**
|
||||
* Portable wrapper for g_io_channel_unix_new() or
|
||||
* g_io_channel_win32_new_socket().
|
||||
*/
|
||||
G_GNUC_MALLOC
|
||||
static inline GIOChannel *
|
||||
g_io_channel_new_socket(int fd)
|
||||
{
|
||||
#ifdef G_OS_WIN32
|
||||
return g_io_channel_win32_new_socket(fd);
|
||||
#else
|
||||
return g_io_channel_unix_new(fd);
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif
|
||||
90
src/growing_fifo.c
Normal file
90
src/growing_fifo.c
Normal file
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2011 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* - Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* - Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the
|
||||
* distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
* FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
|
||||
* OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "growing_fifo.h"
|
||||
#include "fifo_buffer.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
|
||||
/**
|
||||
* Align buffer sizes at 8 kB boundaries. Must be a power of two.
|
||||
*/
|
||||
static const size_t GROWING_FIFO_ALIGN = 8192;
|
||||
|
||||
/**
|
||||
* Align the specified size to the next #GROWING_FIFO_ALIGN boundary.
|
||||
*/
|
||||
static size_t
|
||||
align(size_t size)
|
||||
{
|
||||
return ((size - 1) | (GROWING_FIFO_ALIGN - 1)) + 1;
|
||||
}
|
||||
|
||||
struct fifo_buffer *
|
||||
growing_fifo_new(void)
|
||||
{
|
||||
return fifo_buffer_new(GROWING_FIFO_ALIGN);
|
||||
}
|
||||
|
||||
void *
|
||||
growing_fifo_write(struct fifo_buffer **buffer_p, size_t length)
|
||||
{
|
||||
assert(buffer_p != NULL);
|
||||
|
||||
struct fifo_buffer *buffer = *buffer_p;
|
||||
assert(buffer != NULL);
|
||||
|
||||
size_t max_length;
|
||||
void *p = fifo_buffer_write(buffer, &max_length);
|
||||
if (p != NULL && max_length >= length)
|
||||
return p;
|
||||
|
||||
/* grow */
|
||||
size_t new_size = fifo_buffer_available(buffer) + length;
|
||||
assert(new_size > fifo_buffer_capacity(buffer));
|
||||
*buffer_p = buffer = fifo_buffer_realloc(buffer, align(new_size));
|
||||
|
||||
/* try again */
|
||||
p = fifo_buffer_write(buffer, &max_length);
|
||||
assert(p != NULL);
|
||||
assert(max_length >= length);
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
void
|
||||
growing_fifo_append(struct fifo_buffer **buffer_p,
|
||||
const void *data, size_t length)
|
||||
{
|
||||
void *p = growing_fifo_write(buffer_p, length);
|
||||
memcpy(p, data, length);
|
||||
fifo_buffer_append(*buffer_p, length);
|
||||
}
|
||||
73
src/growing_fifo.h
Normal file
73
src/growing_fifo.h
Normal file
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2011 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* - Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* - Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the
|
||||
* distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
* FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
|
||||
* OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
/** \file
|
||||
*
|
||||
* Helper functions for our FIFO buffer library (fifo_buffer.h) that
|
||||
* allows growing the buffer on demand.
|
||||
*
|
||||
* This library is not thread safe.
|
||||
*/
|
||||
|
||||
#ifndef MPD_GROWING_FIFO_H
|
||||
#define MPD_GROWING_FIFO_H
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
struct fifo_buffer;
|
||||
|
||||
/**
|
||||
* Allocate a new #fifo_buffer with the default size.
|
||||
*/
|
||||
struct fifo_buffer *
|
||||
growing_fifo_new(void);
|
||||
|
||||
/**
|
||||
* Prepares writing to the buffer, see fifo_buffer_write() for
|
||||
* details. The difference is that this function will automatically
|
||||
* grow the buffer if it is too small.
|
||||
*
|
||||
* The caller is responsible for limiting the capacity of the buffer.
|
||||
*
|
||||
* @param length the number of bytes that will be written
|
||||
* @return a pointer to the end of the buffer (will not be NULL)
|
||||
*/
|
||||
void *
|
||||
growing_fifo_write(struct fifo_buffer **buffer_p, size_t length);
|
||||
|
||||
/**
|
||||
* A helper function that combines growing_fifo_write(), memcpy(),
|
||||
* fifo_buffer_append().
|
||||
*/
|
||||
void
|
||||
growing_fifo_append(struct fifo_buffer **buffer_p,
|
||||
const void *data, size_t length);
|
||||
|
||||
#endif
|
||||
@@ -177,7 +177,7 @@ buffer_free_callback(gpointer data, G_GNUC_UNUSED gpointer user_data)
|
||||
|
||||
assert(buffer->consumed <= buffer->size);
|
||||
|
||||
g_free(data);
|
||||
g_free(buffer);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -32,10 +32,18 @@
|
||||
#undef G_LOG_DOMAIN
|
||||
#define G_LOG_DOMAIN "input_ffmpeg"
|
||||
|
||||
#ifndef AV_VERSION_INT
|
||||
#define AV_VERSION_INT(a, b, c) (a<<16 | b<<8 | c)
|
||||
#endif
|
||||
|
||||
struct input_ffmpeg {
|
||||
struct input_stream base;
|
||||
|
||||
#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,0,0)
|
||||
AVIOContext *h;
|
||||
#else
|
||||
URLContext *h;
|
||||
#endif
|
||||
|
||||
bool eof;
|
||||
};
|
||||
@@ -46,6 +54,17 @@ ffmpeg_quark(void)
|
||||
return g_quark_from_static_string("ffmpeg");
|
||||
}
|
||||
|
||||
static inline bool
|
||||
input_ffmpeg_supported(void)
|
||||
{
|
||||
#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,0,0)
|
||||
void *opaque = NULL;
|
||||
return avio_enum_protocols(&opaque, 0) != NULL;
|
||||
#else
|
||||
return av_protocol_next(NULL) != NULL;
|
||||
#endif
|
||||
}
|
||||
|
||||
static bool
|
||||
input_ffmpeg_init(G_GNUC_UNUSED const struct config_param *param,
|
||||
G_GNUC_UNUSED GError **error_r)
|
||||
@@ -54,7 +73,7 @@ input_ffmpeg_init(G_GNUC_UNUSED const struct config_param *param,
|
||||
|
||||
#if LIBAVFORMAT_VERSION_MAJOR >= 52
|
||||
/* disable this plugin if there's no registered protocol */
|
||||
if (av_protocol_next(NULL) == NULL) {
|
||||
if (!input_ffmpeg_supported()) {
|
||||
g_set_error(error_r, ffmpeg_quark(), 0,
|
||||
"No protocol");
|
||||
return false;
|
||||
@@ -80,7 +99,11 @@ input_ffmpeg_open(const char *uri, GError **error_r)
|
||||
i = g_new(struct input_ffmpeg, 1);
|
||||
input_stream_init(&i->base, &input_plugin_ffmpeg, uri);
|
||||
|
||||
#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,0,0)
|
||||
int ret = avio_open(&i->h, uri, AVIO_FLAG_READ);
|
||||
#else
|
||||
int ret = url_open(&i->h, uri, URL_RDONLY);
|
||||
#endif
|
||||
if (ret != 0) {
|
||||
g_free(i);
|
||||
g_set_error(error_r, ffmpeg_quark(), ret,
|
||||
@@ -91,8 +114,13 @@ input_ffmpeg_open(const char *uri, GError **error_r)
|
||||
i->eof = false;
|
||||
|
||||
i->base.ready = true;
|
||||
#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,0,0)
|
||||
i->base.seekable = (i->h->seekable & AVIO_SEEKABLE_NORMAL) != 0;
|
||||
i->base.size = avio_size(i->h);
|
||||
#else
|
||||
i->base.seekable = !i->h->is_streamed;
|
||||
i->base.size = url_filesize(i->h);
|
||||
#endif
|
||||
|
||||
/* hack to make MPD select the "ffmpeg" decoder plugin - since
|
||||
avio.h doesn't tell us the MIME type of the resource, we
|
||||
@@ -109,7 +137,11 @@ input_ffmpeg_read(struct input_stream *is, void *ptr, size_t size,
|
||||
{
|
||||
struct input_ffmpeg *i = (struct input_ffmpeg *)is;
|
||||
|
||||
#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,0,0)
|
||||
int ret = avio_read(i->h, ptr, size);
|
||||
#else
|
||||
int ret = url_read(i->h, ptr, size);
|
||||
#endif
|
||||
if (ret <= 0) {
|
||||
if (ret < 0)
|
||||
g_set_error(error_r, ffmpeg_quark(), 0,
|
||||
@@ -128,7 +160,11 @@ input_ffmpeg_close(struct input_stream *is)
|
||||
{
|
||||
struct input_ffmpeg *i = (struct input_ffmpeg *)is;
|
||||
|
||||
#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,0,0)
|
||||
avio_close(i->h);
|
||||
#else
|
||||
url_close(i->h);
|
||||
#endif
|
||||
input_stream_deinit(&i->base);
|
||||
g_free(i);
|
||||
}
|
||||
@@ -146,7 +182,11 @@ input_ffmpeg_seek(struct input_stream *is, goffset offset, int whence,
|
||||
G_GNUC_UNUSED GError **error_r)
|
||||
{
|
||||
struct input_ffmpeg *i = (struct input_ffmpeg *)is;
|
||||
#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,0,0)
|
||||
int64_t ret = avio_seek(i->h, offset, whence);
|
||||
#else
|
||||
int64_t ret = url_seek(i->h, offset, whence);
|
||||
#endif
|
||||
|
||||
if (ret >= 0) {
|
||||
i->eof = false;
|
||||
|
||||
@@ -83,12 +83,14 @@ copy_attributes(struct input_rewind *r)
|
||||
assert(dest != src);
|
||||
assert(src->mime == NULL || dest->mime != src->mime);
|
||||
|
||||
bool dest_ready = dest->ready;
|
||||
|
||||
dest->ready = src->ready;
|
||||
dest->seekable = src->seekable;
|
||||
dest->size = src->size;
|
||||
dest->offset = src->offset;
|
||||
|
||||
if (src->mime != NULL) {
|
||||
if (!dest_ready && src->ready) {
|
||||
g_free(dest->mime);
|
||||
dest->mime = g_strdup(src->mime);
|
||||
}
|
||||
|
||||
@@ -139,8 +139,8 @@ log_init_file(const char *path, unsigned line)
|
||||
out_filename = path;
|
||||
out_fd = open_log_file();
|
||||
if (out_fd < 0)
|
||||
MPD_ERROR("problem opening log file \"%s\" (config line %u) "
|
||||
"for writing\n", path, line);
|
||||
MPD_ERROR("failed to open log file \"%s\" (config line %u): %s",
|
||||
path, line, g_strerror(errno));
|
||||
|
||||
g_log_set_default_handler(file_log_func, NULL);
|
||||
}
|
||||
|
||||
@@ -27,7 +27,6 @@
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
#define WINVER 0x0501
|
||||
#include <windows.h>
|
||||
|
||||
static int service_argc;
|
||||
|
||||
46
src/mapper.c
46
src/mapper.c
@@ -31,6 +31,10 @@
|
||||
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <dirent.h>
|
||||
|
||||
static char *music_dir;
|
||||
static size_t music_dir_length;
|
||||
@@ -51,25 +55,51 @@ strdup_chop_slash(const char *path_fs)
|
||||
return g_strndup(path_fs, length);
|
||||
}
|
||||
|
||||
static void
|
||||
check_directory(const char *path)
|
||||
{
|
||||
struct stat st;
|
||||
if (stat(path, &st) < 0) {
|
||||
g_warning("Failed to stat directory \"%s\": %s",
|
||||
path, g_strerror(errno));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!S_ISDIR(st.st_mode)) {
|
||||
g_warning("Not a directory: %s", path);
|
||||
return;
|
||||
}
|
||||
|
||||
#ifndef WIN32
|
||||
char *x = g_build_filename(path, ".", NULL);
|
||||
if (stat(x, &st) < 0 && errno == EACCES)
|
||||
g_warning("No permission to traverse (\"execute\") directory: %s",
|
||||
path);
|
||||
g_free(x);
|
||||
#endif
|
||||
|
||||
DIR *dir = opendir(path);
|
||||
if (dir == NULL && errno == EACCES)
|
||||
g_warning("No permission to read directory: %s", path);
|
||||
else
|
||||
closedir(dir);
|
||||
}
|
||||
|
||||
static void
|
||||
mapper_set_music_dir(const char *path)
|
||||
{
|
||||
check_directory(path);
|
||||
|
||||
music_dir = strdup_chop_slash(path);
|
||||
music_dir_length = strlen(music_dir);
|
||||
|
||||
if (!g_file_test(music_dir, G_FILE_TEST_IS_DIR))
|
||||
g_warning("music directory is not a directory: \"%s\"",
|
||||
music_dir);
|
||||
}
|
||||
|
||||
static void
|
||||
mapper_set_playlist_dir(const char *path)
|
||||
{
|
||||
playlist_dir = g_strdup(path);
|
||||
check_directory(path);
|
||||
|
||||
if (!g_file_test(playlist_dir, G_FILE_TEST_IS_DIR))
|
||||
g_warning("playlist directory is not a directory: \"%s\"",
|
||||
playlist_dir);
|
||||
playlist_dir = g_strdup(path);
|
||||
}
|
||||
|
||||
void mapper_init(const char *_music_dir, const char *_playlist_dir)
|
||||
|
||||
@@ -186,6 +186,9 @@ static snd_pcm_format_t
|
||||
get_bitformat(enum sample_format sample_format)
|
||||
{
|
||||
switch (sample_format) {
|
||||
case SAMPLE_FORMAT_UNDEFINED:
|
||||
return SND_PCM_FORMAT_UNKNOWN;
|
||||
|
||||
case SAMPLE_FORMAT_S8:
|
||||
return SND_PCM_FORMAT_S8;
|
||||
|
||||
@@ -202,10 +205,10 @@ get_bitformat(enum sample_format sample_format)
|
||||
|
||||
case SAMPLE_FORMAT_S32:
|
||||
return SND_PCM_FORMAT_S32;
|
||||
|
||||
default:
|
||||
return SND_PCM_FORMAT_UNKNOWN;
|
||||
}
|
||||
|
||||
assert(false);
|
||||
return SND_PCM_FORMAT_UNKNOWN;
|
||||
}
|
||||
|
||||
static snd_pcm_format_t
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
#include "page.h"
|
||||
#include "icy_server.h"
|
||||
#include "glib_compat.h"
|
||||
#include "glib_socket.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <assert.h>
|
||||
@@ -459,11 +460,7 @@ httpd_client_new(struct httpd_output *httpd, int fd, bool metadata_supported)
|
||||
|
||||
client->httpd = httpd;
|
||||
|
||||
#ifndef G_OS_WIN32
|
||||
client->channel = g_io_channel_unix_new(fd);
|
||||
#else
|
||||
client->channel = g_io_channel_win32_new_socket(fd);
|
||||
#endif
|
||||
client->channel = g_io_channel_new_socket(fd);
|
||||
|
||||
/* GLib is responsible for closing the file descriptor */
|
||||
g_io_channel_set_close_on_unref(client->channel, true);
|
||||
|
||||
@@ -201,7 +201,7 @@ httpd_listen_in_event(int fd, const struct sockaddr *address,
|
||||
g_warning("libwrap refused connection (libwrap=%s) from %s",
|
||||
progname, hostaddr);
|
||||
g_free(hostaddr);
|
||||
close(fd);
|
||||
close_socket(fd);
|
||||
g_mutex_unlock(httpd->mutex);
|
||||
return;
|
||||
}
|
||||
@@ -222,7 +222,7 @@ httpd_listen_in_event(int fd, const struct sockaddr *address,
|
||||
httpd->clients_cnt < httpd->clients_max))
|
||||
httpd_client_add(httpd, fd);
|
||||
else
|
||||
close(fd);
|
||||
close_socket(fd);
|
||||
} else if (fd < 0 && errno != EINTR) {
|
||||
g_warning("accept() failed: %s", g_strerror(errno));
|
||||
}
|
||||
|
||||
@@ -143,6 +143,13 @@ mpd_jack_process(jack_nframes_t nframes, void *arg)
|
||||
|
||||
for (unsigned i = 0; i < jd->audio_format.channels; ++i) {
|
||||
out = jack_port_get_buffer(jd->ports[i], nframes);
|
||||
if (out == NULL)
|
||||
/* workaround for libjack1 bug: if the server
|
||||
connection fails, the process callback is
|
||||
invoked anyway, but unable to get a
|
||||
buffer */
|
||||
continue;
|
||||
|
||||
jack_ringbuffer_read(jd->ringbuffer[i],
|
||||
(char *)out, available * jack_sample_size);
|
||||
|
||||
@@ -156,6 +163,12 @@ mpd_jack_process(jack_nframes_t nframes, void *arg)
|
||||
for (unsigned i = jd->audio_format.channels;
|
||||
i < jd->num_source_ports; ++i) {
|
||||
out = jack_port_get_buffer(jd->ports[i], nframes);
|
||||
if (out == NULL)
|
||||
/* workaround for libjack1 bug: if the server
|
||||
connection fails, the process callback is
|
||||
invoked anyway, but unable to get a
|
||||
buffer */
|
||||
continue;
|
||||
|
||||
for (jack_nframes_t f = 0; f < nframes; ++f)
|
||||
out[f] = 0.0;
|
||||
@@ -563,6 +576,9 @@ mpd_jack_open(void *data, struct audio_format *audio_format, GError **error_r)
|
||||
|
||||
jd->pause = false;
|
||||
|
||||
if (jd->client != NULL && jd->shutdown)
|
||||
mpd_jack_disconnect(jd);
|
||||
|
||||
if (jd->client == NULL && !mpd_jack_connect(jd, error_r))
|
||||
return false;
|
||||
|
||||
|
||||
@@ -58,32 +58,26 @@ openal_output_quark(void)
|
||||
static ALenum
|
||||
openal_audio_format(struct audio_format *audio_format)
|
||||
{
|
||||
/* note: cannot map SAMPLE_FORMAT_S8 to AL_FORMAT_STEREO8 or
|
||||
AL_FORMAT_MONO8 since OpenAL expects unsigned 8 bit
|
||||
samples, while MPD uses signed samples */
|
||||
|
||||
switch (audio_format->format) {
|
||||
case SAMPLE_FORMAT_S16:
|
||||
if (audio_format->channels == 2)
|
||||
return AL_FORMAT_STEREO16;
|
||||
if (audio_format->channels == 1)
|
||||
return AL_FORMAT_MONO16;
|
||||
break;
|
||||
|
||||
case SAMPLE_FORMAT_S8:
|
||||
if (audio_format->channels == 2)
|
||||
return AL_FORMAT_STEREO8;
|
||||
if (audio_format->channels == 1)
|
||||
return AL_FORMAT_MONO8;
|
||||
break;
|
||||
/* fall back to mono */
|
||||
audio_format->channels = 1;
|
||||
return openal_audio_format(audio_format);
|
||||
|
||||
default:
|
||||
/* fall back to 16 bit */
|
||||
audio_format->format = SAMPLE_FORMAT_S16;
|
||||
if (audio_format->channels == 2)
|
||||
return AL_FORMAT_STEREO16;
|
||||
if (audio_format->channels == 1)
|
||||
return AL_FORMAT_MONO16;
|
||||
break;
|
||||
return openal_audio_format(audio_format);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
#include "config.h"
|
||||
#include "output_api.h"
|
||||
#include "fifo_buffer.h"
|
||||
|
||||
#include <glib.h>
|
||||
#include <AudioUnit/AudioUnit.h>
|
||||
@@ -31,10 +32,8 @@ struct osx_output {
|
||||
AudioUnit au;
|
||||
GMutex *mutex;
|
||||
GCond *condition;
|
||||
char *buffer;
|
||||
size_t buffer_size;
|
||||
size_t pos;
|
||||
size_t len;
|
||||
|
||||
struct fifo_buffer *buffer;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -64,11 +63,6 @@ osx_output_init(G_GNUC_UNUSED const struct audio_format *audio_format,
|
||||
oo->mutex = g_mutex_new();
|
||||
oo->condition = g_cond_new();
|
||||
|
||||
oo->pos = 0;
|
||||
oo->len = 0;
|
||||
oo->buffer = NULL;
|
||||
oo->buffer_size = 0;
|
||||
|
||||
return oo;
|
||||
}
|
||||
|
||||
@@ -76,7 +70,6 @@ static void osx_output_finish(void *data)
|
||||
{
|
||||
struct osx_output *od = data;
|
||||
|
||||
g_free(od->buffer);
|
||||
g_mutex_free(od->mutex);
|
||||
g_cond_free(od->condition);
|
||||
g_free(od);
|
||||
@@ -87,7 +80,7 @@ static void osx_output_cancel(void *data)
|
||||
struct osx_output *od = data;
|
||||
|
||||
g_mutex_lock(od->mutex);
|
||||
od->len = 0;
|
||||
fifo_buffer_clear(od->buffer);
|
||||
g_mutex_unlock(od->mutex);
|
||||
}
|
||||
|
||||
@@ -98,6 +91,8 @@ static void osx_output_close(void *data)
|
||||
AudioOutputUnitStop(od->au);
|
||||
AudioUnitUninitialize(od->au);
|
||||
CloseComponent(od->au);
|
||||
|
||||
fifo_buffer_free(od->buffer);
|
||||
}
|
||||
|
||||
static OSStatus
|
||||
@@ -111,40 +106,29 @@ osx_render(void *vdata,
|
||||
struct osx_output *od = (struct osx_output *) vdata;
|
||||
AudioBuffer *buffer = &buffer_list->mBuffers[0];
|
||||
size_t buffer_size = buffer->mDataByteSize;
|
||||
size_t bytes_to_copy;
|
||||
size_t trailer_length;
|
||||
size_t dest_pos = 0;
|
||||
|
||||
assert(od->buffer != NULL);
|
||||
|
||||
g_mutex_lock(od->mutex);
|
||||
|
||||
bytes_to_copy = MIN(od->len, buffer_size);
|
||||
buffer_size = bytes_to_copy;
|
||||
od->len -= bytes_to_copy;
|
||||
size_t nbytes;
|
||||
const void *src = fifo_buffer_read(od->buffer, &nbytes);
|
||||
|
||||
trailer_length = od->buffer_size - od->pos;
|
||||
if (bytes_to_copy > trailer_length) {
|
||||
memcpy((unsigned char*)buffer->mData + dest_pos,
|
||||
od->buffer + od->pos, trailer_length);
|
||||
od->pos = 0;
|
||||
dest_pos += trailer_length;
|
||||
bytes_to_copy -= trailer_length;
|
||||
}
|
||||
if (src != NULL) {
|
||||
if (nbytes > buffer_size)
|
||||
nbytes = buffer_size;
|
||||
|
||||
memcpy((unsigned char*)buffer->mData + dest_pos,
|
||||
od->buffer + od->pos, bytes_to_copy);
|
||||
od->pos += bytes_to_copy;
|
||||
|
||||
if (od->pos >= od->buffer_size)
|
||||
od->pos = 0;
|
||||
memcpy(buffer->mData, src, nbytes);
|
||||
fifo_buffer_consume(od->buffer, nbytes);
|
||||
} else
|
||||
nbytes = 0;
|
||||
|
||||
g_cond_signal(od->condition);
|
||||
g_mutex_unlock(od->mutex);
|
||||
|
||||
buffer->mDataByteSize = buffer_size;
|
||||
|
||||
if (!buffer_size) {
|
||||
g_usleep(1000);
|
||||
}
|
||||
if (nbytes < buffer_size)
|
||||
memset((unsigned char*)buffer->mData + nbytes, 0,
|
||||
buffer_size - nbytes);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -247,15 +231,12 @@ osx_output_open(void *data, struct audio_format *audio_format, GError **error)
|
||||
}
|
||||
|
||||
/* create a buffer of 1s */
|
||||
od->buffer_size = (audio_format->sample_rate) *
|
||||
audio_format_frame_size(audio_format);
|
||||
od->buffer = g_realloc(od->buffer, od->buffer_size);
|
||||
|
||||
od->pos = 0;
|
||||
od->len = 0;
|
||||
od->buffer = fifo_buffer_new(audio_format->sample_rate *
|
||||
audio_format_frame_size(audio_format));
|
||||
|
||||
status = AudioOutputUnitStart(od->au);
|
||||
if (status != 0) {
|
||||
fifo_buffer_free(od->buffer);
|
||||
g_set_error(error, osx_output_quark(), 0,
|
||||
"unable to start audio output: %s",
|
||||
GetMacOSStatusCommentString(status));
|
||||
@@ -270,33 +251,30 @@ osx_output_play(void *data, const void *chunk, size_t size,
|
||||
G_GNUC_UNUSED GError **error)
|
||||
{
|
||||
struct osx_output *od = data;
|
||||
size_t start, nbytes;
|
||||
|
||||
g_mutex_lock(od->mutex);
|
||||
|
||||
while (od->len >= od->buffer_size)
|
||||
void *dest;
|
||||
size_t max_length;
|
||||
|
||||
while (true) {
|
||||
dest = fifo_buffer_write(od->buffer, &max_length);
|
||||
if (dest != NULL)
|
||||
break;
|
||||
|
||||
/* wait for some free space in the buffer */
|
||||
g_cond_wait(od->condition, od->mutex);
|
||||
}
|
||||
|
||||
start = od->pos + od->len;
|
||||
if (start >= od->buffer_size)
|
||||
start -= od->buffer_size;
|
||||
if (size > max_length)
|
||||
size = max_length;
|
||||
|
||||
nbytes = start < od->pos
|
||||
? od->pos - start
|
||||
: od->buffer_size - start;
|
||||
|
||||
assert(nbytes > 0);
|
||||
|
||||
if (nbytes > size)
|
||||
nbytes = size;
|
||||
|
||||
memcpy(od->buffer + start, chunk, nbytes);
|
||||
od->len += nbytes;
|
||||
memcpy(dest, chunk, size);
|
||||
fifo_buffer_append(od->buffer, size);
|
||||
|
||||
g_mutex_unlock(od->mutex);
|
||||
|
||||
return nbytes;
|
||||
return size;
|
||||
}
|
||||
|
||||
const struct audio_output_plugin osxPlugin = {
|
||||
|
||||
@@ -72,7 +72,8 @@ pulse_output_set_mixer(struct pulse_output *po, struct pulse_mixer *pm)
|
||||
}
|
||||
|
||||
void
|
||||
pulse_output_clear_mixer(struct pulse_output *po, struct pulse_mixer *pm)
|
||||
pulse_output_clear_mixer(struct pulse_output *po,
|
||||
G_GNUC_UNUSED struct pulse_mixer *pm)
|
||||
{
|
||||
assert(po != NULL);
|
||||
assert(pm != NULL);
|
||||
@@ -247,6 +248,8 @@ pulse_output_delete_stream(struct pulse_output *po)
|
||||
|
||||
/**
|
||||
* Frees and clears the context.
|
||||
*
|
||||
* Caller must lock the main loop.
|
||||
*/
|
||||
static void
|
||||
pulse_output_delete_context(struct pulse_output *po)
|
||||
@@ -265,6 +268,8 @@ pulse_output_delete_context(struct pulse_output *po)
|
||||
/**
|
||||
* Create, set up and connect a context.
|
||||
*
|
||||
* Caller must lock the main loop.
|
||||
*
|
||||
* @return true on success, false on error
|
||||
*/
|
||||
static bool
|
||||
@@ -355,12 +360,8 @@ pulse_output_enable(void *data, GError **error_r)
|
||||
return false;
|
||||
}
|
||||
|
||||
pa_threaded_mainloop_unlock(po->mainloop);
|
||||
|
||||
/* create the libpulse context and connect it */
|
||||
|
||||
pa_threaded_mainloop_lock(po->mainloop);
|
||||
|
||||
if (!pulse_output_setup_context(po, error_r)) {
|
||||
pa_threaded_mainloop_unlock(po->mainloop);
|
||||
pa_threaded_mainloop_stop(po->mainloop);
|
||||
@@ -392,6 +393,8 @@ pulse_output_disable(void *data)
|
||||
* Check if the context is (already) connected, and waits if not. If
|
||||
* the context has been disconnected, retry to connect.
|
||||
*
|
||||
* Caller must lock the main loop.
|
||||
*
|
||||
* @return true on success, false on error
|
||||
*/
|
||||
static bool
|
||||
@@ -401,8 +404,6 @@ pulse_output_wait_connection(struct pulse_output *po, GError **error_r)
|
||||
|
||||
pa_context_state_t state;
|
||||
|
||||
pa_threaded_mainloop_lock(po->mainloop);
|
||||
|
||||
if (po->context == NULL && !pulse_output_setup_context(po, error_r))
|
||||
return false;
|
||||
|
||||
@@ -411,7 +412,6 @@ pulse_output_wait_connection(struct pulse_output *po, GError **error_r)
|
||||
switch (state) {
|
||||
case PA_CONTEXT_READY:
|
||||
/* nothing to do */
|
||||
pa_threaded_mainloop_unlock(po->mainloop);
|
||||
return true;
|
||||
|
||||
case PA_CONTEXT_UNCONNECTED:
|
||||
@@ -422,7 +422,6 @@ pulse_output_wait_connection(struct pulse_output *po, GError **error_r)
|
||||
"failed to connect: %s",
|
||||
pa_strerror(pa_context_errno(po->context)));
|
||||
pulse_output_delete_context(po);
|
||||
pa_threaded_mainloop_unlock(po->mainloop);
|
||||
return false;
|
||||
|
||||
case PA_CONTEXT_CONNECTING:
|
||||
@@ -505,6 +504,8 @@ pulse_output_open(void *data, struct audio_format *audio_format,
|
||||
|
||||
assert(po->mainloop != NULL);
|
||||
|
||||
pa_threaded_mainloop_lock(po->mainloop);
|
||||
|
||||
if (po->context != NULL) {
|
||||
switch (pa_context_get_state(po->context)) {
|
||||
case PA_CONTEXT_UNCONNECTED:
|
||||
@@ -524,8 +525,10 @@ pulse_output_open(void *data, struct audio_format *audio_format,
|
||||
}
|
||||
}
|
||||
|
||||
if (!pulse_output_wait_connection(po, error_r))
|
||||
if (!pulse_output_wait_connection(po, error_r)) {
|
||||
pa_threaded_mainloop_unlock(po->mainloop);
|
||||
return false;
|
||||
}
|
||||
|
||||
/* MPD doesn't support the other pulseaudio sample formats, so
|
||||
we just force MPD to send us everything as 16 bit */
|
||||
@@ -535,8 +538,6 @@ pulse_output_open(void *data, struct audio_format *audio_format,
|
||||
ss.rate = audio_format->sample_rate;
|
||||
ss.channels = audio_format->channels;
|
||||
|
||||
pa_threaded_mainloop_lock(po->mainloop);
|
||||
|
||||
/* create a stream .. */
|
||||
|
||||
po->stream = pa_stream_new(po->context, po->name, &ss, NULL);
|
||||
|
||||
@@ -191,7 +191,7 @@ recorder_output_close(void *data)
|
||||
|
||||
/* flush the encoder and write the rest to the file */
|
||||
|
||||
if (encoder_flush(recorder->encoder, NULL))
|
||||
if (encoder_end(recorder->encoder, NULL))
|
||||
recorder_output_encoder_to_file(recorder, NULL);
|
||||
|
||||
/* now really close everything */
|
||||
|
||||
@@ -358,7 +358,7 @@ static void close_shout_conn(struct shout_data * sd)
|
||||
sd->buf.len = 0;
|
||||
|
||||
if (sd->encoder != NULL) {
|
||||
if (encoder_flush(sd->encoder, NULL))
|
||||
if (encoder_end(sd->encoder, NULL))
|
||||
write_page(sd, NULL);
|
||||
|
||||
encoder_close(sd->encoder);
|
||||
|
||||
@@ -23,7 +23,6 @@
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
#include <sys/audio.h>
|
||||
#include <sys/stropts.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
@@ -31,6 +30,25 @@
|
||||
#include <fcntl.h>
|
||||
#include <errno.h>
|
||||
|
||||
#ifdef __sun
|
||||
#include <sys/audio.h>
|
||||
#else
|
||||
|
||||
/* some fake declarations that allow build this plugin on systems
|
||||
other than Solaris, just to see if it compiles */
|
||||
|
||||
#define AUDIO_GETINFO 0
|
||||
#define AUDIO_SETINFO 0
|
||||
#define AUDIO_ENCODING_LINEAR 0
|
||||
|
||||
struct audio_info {
|
||||
struct {
|
||||
unsigned sample_rate, channels, precision, encoding;
|
||||
} play;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
#undef G_LOG_DOMAIN
|
||||
#define G_LOG_DOMAIN "solaris_output"
|
||||
|
||||
|
||||
@@ -200,11 +200,7 @@ winmm_set_buffer(struct winmm_output *wo, struct winmm_buffer *buffer,
|
||||
GError **error_r)
|
||||
{
|
||||
void *dest = pcm_buffer_get(&buffer->buffer, size);
|
||||
if (dest == NULL) {
|
||||
g_set_error(error_r, winmm_output_quark(), 0,
|
||||
"Out of memory");
|
||||
return false;
|
||||
}
|
||||
assert(dest != NULL);
|
||||
|
||||
memcpy(dest, data, size);
|
||||
|
||||
|
||||
@@ -102,9 +102,6 @@ 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.
|
||||
*/
|
||||
|
||||
16
src/path.c
16
src/path.c
@@ -27,6 +27,11 @@
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
|
||||
#ifdef G_OS_WIN32
|
||||
#include <windows.h> // for GetACP()
|
||||
#include <stdio.h> // for sprintf()
|
||||
#endif
|
||||
|
||||
#undef G_LOG_DOMAIN
|
||||
#define G_LOG_DOMAIN "path"
|
||||
|
||||
@@ -85,11 +90,22 @@ void path_global_init(void)
|
||||
|
||||
charset = config_get_string(CONF_FS_CHARSET, NULL);
|
||||
if (charset == NULL) {
|
||||
#ifndef G_OS_WIN32
|
||||
const gchar **encodings;
|
||||
g_get_filename_charsets(&encodings);
|
||||
|
||||
if (encodings[0] != NULL && *encodings[0] != '\0')
|
||||
charset = encodings[0];
|
||||
#else /* G_OS_WIN32 */
|
||||
/* Glib claims that file system encoding is always utf-8
|
||||
* on native Win32 (i.e. not Cygwin).
|
||||
* However this is true only if <gstdio.h> helpers are used.
|
||||
* MPD uses regular <stdio.h> functions.
|
||||
* Those functions use encoding determined by GetACP(). */
|
||||
char win_charset[13];
|
||||
sprintf(win_charset, "cp%u", GetACP());
|
||||
charset = win_charset;
|
||||
#endif
|
||||
}
|
||||
|
||||
if (charset) {
|
||||
|
||||
53
src/pcm_buffer.c
Normal file
53
src/pcm_buffer.c
Normal file
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* 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 "pcm_buffer.h"
|
||||
|
||||
/**
|
||||
* Align the specified size to the next 8k boundary.
|
||||
*/
|
||||
G_GNUC_CONST
|
||||
static size_t
|
||||
align_8k(size_t size)
|
||||
{
|
||||
return ((size - 1) | 0x1fff) + 1;
|
||||
}
|
||||
|
||||
void *
|
||||
pcm_buffer_get(struct pcm_buffer *buffer, size_t size)
|
||||
{
|
||||
assert(buffer != NULL);
|
||||
|
||||
if (size == 0)
|
||||
/* never return NULL, because NULL would be assumed to
|
||||
be an error condition */
|
||||
size = 1;
|
||||
|
||||
if (buffer->size < size) {
|
||||
/* free the old buffer */
|
||||
g_free(buffer->buffer);
|
||||
|
||||
buffer->size = align_8k(size);
|
||||
buffer->buffer = g_malloc(buffer->size);
|
||||
}
|
||||
|
||||
assert(buffer->size >= size);
|
||||
|
||||
return buffer->buffer;
|
||||
}
|
||||
@@ -22,6 +22,8 @@
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
/**
|
||||
* Manager for a temporary buffer which grows as needed. We could
|
||||
* allocate a new buffer every time pcm_convert() is called, but that
|
||||
@@ -39,6 +41,8 @@ struct pcm_buffer {
|
||||
static inline void
|
||||
pcm_buffer_init(struct pcm_buffer *buffer)
|
||||
{
|
||||
assert(buffer != NULL);
|
||||
|
||||
buffer->buffer = NULL;
|
||||
buffer->size = 0;
|
||||
}
|
||||
@@ -49,6 +53,8 @@ pcm_buffer_init(struct pcm_buffer *buffer)
|
||||
static inline void
|
||||
pcm_buffer_deinit(struct pcm_buffer *buffer)
|
||||
{
|
||||
assert(buffer != NULL);
|
||||
|
||||
g_free(buffer->buffer);
|
||||
|
||||
buffer->buffer = NULL;
|
||||
@@ -57,20 +63,13 @@ pcm_buffer_deinit(struct pcm_buffer *buffer)
|
||||
/**
|
||||
* Get the buffer, and guarantee a minimum size. This buffer becomes
|
||||
* invalid with the next pcm_buffer_get() call.
|
||||
*
|
||||
* This function will never return NULL, even if size is zero, because
|
||||
* the PCM library uses the NULL return value to signal "error". An
|
||||
* empty destination buffer is not always an error.
|
||||
*/
|
||||
static inline void *
|
||||
pcm_buffer_get(struct pcm_buffer *buffer, size_t size)
|
||||
{
|
||||
if (buffer->size < size) {
|
||||
/* free the old buffer */
|
||||
g_free(buffer->buffer);
|
||||
|
||||
/* allocate a new buffer; align at 8 kB boundaries */
|
||||
buffer->size = ((size - 1) | 0x1fff) + 1;
|
||||
buffer->buffer = g_malloc(buffer->size);
|
||||
}
|
||||
|
||||
return buffer->buffer;
|
||||
}
|
||||
G_GNUC_MALLOC
|
||||
void *
|
||||
pcm_buffer_get(struct pcm_buffer *buffer, size_t size);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -222,7 +222,7 @@ pcm_convert_32(struct pcm_convert_state *state,
|
||||
src_buffer, src_size, &len);
|
||||
if (buf == NULL) {
|
||||
g_set_error(error_r, pcm_convert_quark(), 0,
|
||||
"Conversion from %s to 24 bit is not implemented",
|
||||
"Conversion from %s to 32 bit is not implemented",
|
||||
sample_format_to_string(src_format->format));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -143,7 +143,7 @@ pcm_convert_16_to_24(int32_t *out, const int16_t *in,
|
||||
}
|
||||
|
||||
static void
|
||||
pcm_convert_32_to_24(int32_t *out, const int16_t *in,
|
||||
pcm_convert_32_to_24(int32_t *out, const int32_t *in,
|
||||
unsigned num_samples)
|
||||
{
|
||||
while (num_samples > 0) {
|
||||
@@ -197,7 +197,7 @@ pcm_convert_to_24(struct pcm_buffer *buffer,
|
||||
*dest_size_r = num_samples * sizeof(*dest);
|
||||
dest = pcm_buffer_get(buffer, *dest_size_r);
|
||||
|
||||
pcm_convert_32_to_24(dest, (const int16_t *)src,
|
||||
pcm_convert_32_to_24(dest, (const int32_t *)src,
|
||||
num_samples);
|
||||
return dest;
|
||||
}
|
||||
|
||||
@@ -309,9 +309,6 @@ pc_seek(struct song *song, float seek_time)
|
||||
{
|
||||
assert(song != NULL);
|
||||
|
||||
if (pc.state == PLAYER_STATE_STOP)
|
||||
return false;
|
||||
|
||||
player_lock();
|
||||
pc.next_song = song;
|
||||
pc.seek_where = seek_time;
|
||||
|
||||
@@ -73,6 +73,14 @@ struct player {
|
||||
*/
|
||||
bool queued;
|
||||
|
||||
/**
|
||||
* Was any audio output opened successfully? It might have
|
||||
* failed meanwhile, but was not explicitly closed by the
|
||||
* player thread. When this flag is unset, some output
|
||||
* methods must not be called.
|
||||
*/
|
||||
bool output_open;
|
||||
|
||||
/**
|
||||
* the song currently being played
|
||||
*/
|
||||
@@ -145,7 +153,13 @@ player_dc_start(struct player *player, struct music_pipe *pipe)
|
||||
assert(player->queued || pc.command == PLAYER_COMMAND_SEEK);
|
||||
assert(pc.next_song != NULL);
|
||||
|
||||
dc_start(dc, pc.next_song, player_buffer, pipe);
|
||||
unsigned start_ms = pc.next_song->start_ms;
|
||||
if (pc.command == PLAYER_COMMAND_SEEK)
|
||||
start_ms += (unsigned)(pc.seek_where * 1000);
|
||||
|
||||
dc_start(dc, pc.next_song,
|
||||
start_ms, pc.next_song->end_ms,
|
||||
player_buffer, pipe);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -270,6 +284,44 @@ real_song_duration(const struct song *song, double decoder_duration)
|
||||
return decoder_duration - song->start_ms / 1000.0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper for audio_output_all_open(). Upon failure, it pauses the
|
||||
* player.
|
||||
*
|
||||
* @return true on success
|
||||
*/
|
||||
static bool
|
||||
player_open_output(struct player *player)
|
||||
{
|
||||
assert(audio_format_defined(&player->play_audio_format));
|
||||
assert(pc.state == PLAYER_STATE_PLAY ||
|
||||
pc.state == PLAYER_STATE_PAUSE);
|
||||
|
||||
if (audio_output_all_open(&player->play_audio_format, player_buffer)) {
|
||||
player->output_open = true;
|
||||
player->paused = false;
|
||||
|
||||
player_lock();
|
||||
pc.state = PLAYER_STATE_PLAY;
|
||||
player_unlock();
|
||||
|
||||
return true;
|
||||
} else {
|
||||
player->output_open = false;
|
||||
|
||||
/* pause: the user may resume playback as soon as an
|
||||
audio output becomes available */
|
||||
player->paused = true;
|
||||
|
||||
player_lock();
|
||||
pc.error = PLAYER_ERROR_AUDIO;
|
||||
pc.state = PLAYER_STATE_PAUSE;
|
||||
player_unlock();
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The decoder has acknowledged the "START" command (see
|
||||
* player_wait_for_decoder()). This function checks if the decoder
|
||||
@@ -301,7 +353,7 @@ player_check_decoder_startup(struct player *player)
|
||||
|
||||
decoder_unlock(dc);
|
||||
|
||||
if (audio_format_defined(&player->play_audio_format) &&
|
||||
if (player->output_open &&
|
||||
!audio_output_all_wait(1))
|
||||
/* the output devices havn't finished playing
|
||||
all chunks yet - wait for that */
|
||||
@@ -315,23 +367,12 @@ player_check_decoder_startup(struct player *player)
|
||||
player->play_audio_format = dc->out_audio_format;
|
||||
player->decoder_starting = false;
|
||||
|
||||
if (!player->paused &&
|
||||
!audio_output_all_open(&dc->out_audio_format,
|
||||
player_buffer)) {
|
||||
if (!player->paused && !player_open_output(player)) {
|
||||
char *uri = song_get_uri(dc->song);
|
||||
g_warning("problems opening audio device "
|
||||
"while playing \"%s\"", uri);
|
||||
g_free(uri);
|
||||
|
||||
player_lock();
|
||||
pc.error = PLAYER_ERROR_AUDIO;
|
||||
|
||||
/* pause: the user may resume playback as soon
|
||||
as an audio output becomes available */
|
||||
pc.state = PLAYER_STATE_PAUSE;
|
||||
player_unlock();
|
||||
|
||||
player->paused = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -356,6 +397,7 @@ player_check_decoder_startup(struct player *player)
|
||||
static bool
|
||||
player_send_silence(struct player *player)
|
||||
{
|
||||
assert(player->output_open);
|
||||
assert(audio_format_defined(&player->play_audio_format));
|
||||
|
||||
struct music_chunk *chunk = music_buffer_allocate(player_buffer);
|
||||
@@ -510,18 +552,9 @@ static void player_process_command(struct player *player)
|
||||
yet - don't open the audio device yet */
|
||||
player_lock();
|
||||
|
||||
pc.state = PLAYER_STATE_PLAY;
|
||||
} else if (audio_output_all_open(&player->play_audio_format, player_buffer)) {
|
||||
/* unpaused, continue playing */
|
||||
player_lock();
|
||||
|
||||
pc.state = PLAYER_STATE_PLAY;
|
||||
} else {
|
||||
/* the audio device has failed - rollback to
|
||||
pause mode */
|
||||
pc.error = PLAYER_ERROR_AUDIO;
|
||||
|
||||
player->paused = true;
|
||||
player_open_output(player);
|
||||
|
||||
player_lock();
|
||||
}
|
||||
@@ -558,8 +591,7 @@ static void player_process_command(struct player *player)
|
||||
break;
|
||||
|
||||
case PLAYER_COMMAND_REFRESH:
|
||||
if (audio_format_defined(&player->play_audio_format) &&
|
||||
!player->paused) {
|
||||
if (player->output_open && !player->paused) {
|
||||
player_unlock();
|
||||
audio_output_all_check();
|
||||
player_lock();
|
||||
@@ -810,6 +842,7 @@ static void do_play(struct decoder_control *dc)
|
||||
.decoder_starting = false,
|
||||
.paused = false,
|
||||
.queued = true,
|
||||
.output_open = false,
|
||||
.song = NULL,
|
||||
.xfade = XFADE_UNKNOWN,
|
||||
.cross_fading = false,
|
||||
@@ -833,6 +866,10 @@ static void do_play(struct decoder_control *dc)
|
||||
}
|
||||
|
||||
player_lock();
|
||||
|
||||
if (pc.command == PLAYER_COMMAND_SEEK)
|
||||
player.elapsed_time = pc.seek_where;
|
||||
|
||||
pc.state = PLAYER_STATE_PLAY;
|
||||
player_command_finished_locked();
|
||||
|
||||
@@ -858,7 +895,7 @@ static void do_play(struct decoder_control *dc)
|
||||
/* not enough decoded buffer space yet */
|
||||
|
||||
if (!player.paused &&
|
||||
audio_format_defined(&player.play_audio_format) &&
|
||||
player.output_open &&
|
||||
audio_output_all_check() < 4 &&
|
||||
!player_send_silence(&player))
|
||||
break;
|
||||
@@ -881,16 +918,6 @@ static void do_play(struct decoder_control *dc)
|
||||
if (!player_check_decoder_startup(&player))
|
||||
break;
|
||||
|
||||
/* seek to the beginning of the range */
|
||||
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);
|
||||
|
||||
player_lock();
|
||||
continue;
|
||||
}
|
||||
@@ -973,7 +1000,7 @@ static void do_play(struct decoder_control *dc)
|
||||
audio_output_all_drain();
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
} else if (player.output_open) {
|
||||
/* the decoder is too busy and hasn't provided
|
||||
new PCM data in time: send silence (if the
|
||||
output pipe is empty) */
|
||||
@@ -1021,6 +1048,7 @@ static gpointer player_task(G_GNUC_UNUSED gpointer arg)
|
||||
|
||||
while (1) {
|
||||
switch (pc.command) {
|
||||
case PLAYER_COMMAND_SEEK:
|
||||
case PLAYER_COMMAND_QUEUE:
|
||||
assert(pc.next_song != NULL);
|
||||
|
||||
@@ -1034,7 +1062,6 @@ static gpointer player_task(G_GNUC_UNUSED gpointer arg)
|
||||
|
||||
/* fall through */
|
||||
|
||||
case PLAYER_COMMAND_SEEK:
|
||||
case PLAYER_COMMAND_PAUSE:
|
||||
pc.next_song = NULL;
|
||||
player_command_finished_locked();
|
||||
|
||||
@@ -356,7 +356,7 @@ playlist_move_range(struct playlist *playlist,
|
||||
playlist->current)
|
||||
: -1;
|
||||
if (to < 0 && playlist->current >= 0) {
|
||||
if (start <= (unsigned)currentSong && (unsigned)currentSong <= end)
|
||||
if (start <= (unsigned)currentSong && (unsigned)currentSong < end)
|
||||
/* no-op, can't be moved to offset of itself */
|
||||
return PLAYLIST_RESULT_SUCCESS;
|
||||
to = (currentSong + abs(to)) % queue_length(&playlist->queue);
|
||||
|
||||
@@ -114,9 +114,7 @@ playlist_check_translate_song(struct song *song, const char *base_uri)
|
||||
|
||||
if (g_path_is_absolute(uri)) {
|
||||
/* XXX fs_charset vs utf8? */
|
||||
char *prefix = base_uri != NULL
|
||||
? map_uri_fs(base_uri)
|
||||
: map_directory_fs(db_get_root());
|
||||
char *prefix = map_directory_fs(db_get_root());
|
||||
|
||||
if (prefix == NULL || !g_str_has_prefix(uri, prefix) ||
|
||||
uri[strlen(prefix)] != '/') {
|
||||
@@ -127,6 +125,7 @@ playlist_check_translate_song(struct song *song, const char *base_uri)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
base_uri = NULL;
|
||||
uri += strlen(prefix) + 1;
|
||||
g_free(prefix);
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#include "socket_util.h"
|
||||
#include "fd_util.h"
|
||||
#include "glib_compat.h"
|
||||
#include "glib_socket.h"
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
@@ -33,7 +34,6 @@
|
||||
#include <assert.h>
|
||||
|
||||
#ifdef WIN32
|
||||
#define WINVER 0x0501
|
||||
#include <ws2tcpip.h>
|
||||
#include <winsock.h>
|
||||
#else
|
||||
@@ -218,7 +218,7 @@ server_socket_open(struct server_socket *ss, GError **error_r)
|
||||
|
||||
/* register in the GLib main loop */
|
||||
|
||||
GIOChannel *channel = g_io_channel_unix_new(s->fd);
|
||||
GIOChannel *channel = g_io_channel_new_socket(s->fd);
|
||||
s->source_id = g_io_add_watch(channel, G_IO_IN,
|
||||
server_socket_in_event, s);
|
||||
g_io_channel_unref(channel);
|
||||
@@ -252,7 +252,7 @@ server_socket_close(struct server_socket *ss)
|
||||
continue;
|
||||
|
||||
g_source_remove(s->source_id);
|
||||
close(s->fd);
|
||||
close_socket(s->fd);
|
||||
s->fd = -1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,6 @@
|
||||
#include <sys/socket.h>
|
||||
#include <netdb.h>
|
||||
#else /* G_OS_WIN32 */
|
||||
#define WINVER 0x0501
|
||||
#include <ws2tcpip.h>
|
||||
#include <winsock.h>
|
||||
#endif /* G_OS_WIN32 */
|
||||
@@ -122,7 +121,7 @@ socket_bind_listen(int domain, int type, int protocol,
|
||||
if (ret < 0) {
|
||||
g_set_error(error, listen_quark(), errno,
|
||||
"setsockopt() failed: %s", g_strerror(errno));
|
||||
close(fd);
|
||||
close_socket(fd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -130,7 +129,7 @@ socket_bind_listen(int domain, int type, int protocol,
|
||||
if (ret < 0) {
|
||||
g_set_error(error, listen_quark(), errno,
|
||||
"%s", g_strerror(errno));
|
||||
close(fd);
|
||||
close_socket(fd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -138,7 +137,7 @@ socket_bind_listen(int domain, int type, int protocol,
|
||||
if (ret < 0) {
|
||||
g_set_error(error, listen_quark(), errno,
|
||||
"listen() failed: %s", g_strerror(errno));
|
||||
close(fd);
|
||||
close_socket(fd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
@@ -116,6 +116,6 @@ int stats_print(struct client *client)
|
||||
(long)g_timer_elapsed(stats.timer, NULL),
|
||||
(long)(pc_get_total_play_time() + 0.5),
|
||||
stats.song_duration,
|
||||
db_get_mtime());
|
||||
(long)db_get_mtime());
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
|
||||
struct text_input_stream {
|
||||
@@ -67,7 +68,12 @@ text_input_stream_read(struct text_input_stream *tis)
|
||||
|
||||
do {
|
||||
dest = fifo_buffer_write(tis->buffer, &length);
|
||||
if (dest != NULL) {
|
||||
if (dest != NULL && length >= 2) {
|
||||
/* reserve one byte for the null terminator if
|
||||
the last line is not terminated by a
|
||||
newline character */
|
||||
--length;
|
||||
|
||||
nbytes = input_stream_read(tis->is, dest, length,
|
||||
&error);
|
||||
if (nbytes > 0)
|
||||
@@ -77,13 +83,22 @@ text_input_stream_read(struct text_input_stream *tis)
|
||||
g_error_free(error);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
} else
|
||||
nbytes = 0;
|
||||
|
||||
src = fifo_buffer_read(tis->buffer, &length);
|
||||
if (src == NULL)
|
||||
return NULL;
|
||||
|
||||
p = memchr(src, '\n', length);
|
||||
if (p == NULL && nbytes == 0) {
|
||||
/* end of file (or line too long): terminate
|
||||
the current line */
|
||||
dest = fifo_buffer_write(tis->buffer, &nbytes);
|
||||
assert(dest != NULL);
|
||||
*(char *)dest = '\n';
|
||||
fifo_buffer_append(tis->buffer, 1);
|
||||
}
|
||||
} while (p == NULL);
|
||||
|
||||
length = p - src + 1;
|
||||
|
||||
@@ -81,7 +81,7 @@ timer_delay(const Timer *timer)
|
||||
if (delay > G_MAXINT)
|
||||
delay = G_MAXINT;
|
||||
|
||||
return delay / 1000;
|
||||
return delay;
|
||||
}
|
||||
|
||||
void timer_sync(Timer *timer)
|
||||
|
||||
@@ -49,7 +49,7 @@ song_remove_event(void)
|
||||
assert(removed_song != NULL);
|
||||
|
||||
uri = song_get_uri(removed_song);
|
||||
g_debug("removing: %s", uri);
|
||||
g_message("removing %s", uri);
|
||||
g_free(uri);
|
||||
|
||||
#ifdef ENABLE_SQLITE
|
||||
|
||||
@@ -86,7 +86,7 @@ directory_set_stat(struct directory *dir, const struct stat *st)
|
||||
{
|
||||
dir->inode = st->st_ino;
|
||||
dir->device = st->st_dev;
|
||||
dir->stat = 1;
|
||||
dir->have_stat = true;
|
||||
}
|
||||
|
||||
static void
|
||||
@@ -346,7 +346,7 @@ inodeFoundInParent(struct directory *parent, ino_t inode, dev_t device)
|
||||
{
|
||||
#ifndef G_OS_WIN32
|
||||
while (parent) {
|
||||
if (!parent->stat && statDirectory(parent) < 0)
|
||||
if (!parent->have_stat && statDirectory(parent) < 0)
|
||||
return -1;
|
||||
if (parent->inode == inode && parent->device == device) {
|
||||
g_debug("recursive directory found");
|
||||
@@ -616,6 +616,8 @@ update_regular_file(struct directory *directory,
|
||||
}
|
||||
|
||||
if (song == NULL) {
|
||||
g_debug("reading %s/%s",
|
||||
directory_get_path(directory), name);
|
||||
song = song_file_load(name, directory);
|
||||
if (song == NULL) {
|
||||
g_debug("ignoring unrecognized file %s/%s",
|
||||
@@ -846,6 +848,9 @@ directory_make_child_checked(struct directory *parent, const char *path)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (skip_symlink(parent, path))
|
||||
return NULL;
|
||||
|
||||
/* if we're adding directory paths, make sure to delete filenames
|
||||
with potentially the same name */
|
||||
conflicting = songvec_find(&parent->songs, base);
|
||||
@@ -894,7 +899,8 @@ updatePath(const char *path)
|
||||
|
||||
name = g_path_get_basename(path);
|
||||
|
||||
if (stat_directory_child(parent, name, &st) == 0)
|
||||
if (!skip_symlink(parent, name) &&
|
||||
stat_directory_child(parent, name, &st) == 0)
|
||||
updateInDirectory(parent, name, &st);
|
||||
else
|
||||
delete_name_in(parent, name);
|
||||
|
||||
@@ -34,13 +34,13 @@ bool uri_has_scheme(const char *uri)
|
||||
const char *
|
||||
uri_get_suffix(const char *uri)
|
||||
{
|
||||
const char *suffix = strrchr(g_basename(uri), '.');
|
||||
const char *suffix = strrchr(uri, '.');
|
||||
if (suffix == NULL)
|
||||
return NULL;
|
||||
|
||||
++suffix;
|
||||
|
||||
if (strchr(suffix, '/') != NULL)
|
||||
if (strpbrk(suffix, "/\\") != NULL)
|
||||
return NULL;
|
||||
|
||||
return suffix;
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
#include <stdbool.h>
|
||||
|
||||
/**
|
||||
* Checks whether the specified URI has a schema in the form
|
||||
* Checks whether the specified URI has a scheme in the form
|
||||
* "scheme://".
|
||||
*/
|
||||
G_GNUC_PURE
|
||||
|
||||
@@ -33,7 +33,11 @@
|
||||
#include <pwd.h>
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_IPV6
|
||||
#if HAVE_IPV6 && WIN32
|
||||
#include <winsock2.h>
|
||||
#endif
|
||||
|
||||
#if HAVE_IPV6 && ! WIN32
|
||||
#include <sys/socket.h>
|
||||
#endif
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
#include "input_stream.h"
|
||||
#include "audio_format.h"
|
||||
#include "pcm_volume.h"
|
||||
#include "tag_pool.h"
|
||||
#include "tag_ape.h"
|
||||
#include "tag_id3.h"
|
||||
#include "idle.h"
|
||||
@@ -103,7 +104,7 @@ decoder_data(G_GNUC_UNUSED struct decoder *decoder,
|
||||
const void *data, size_t datalen,
|
||||
G_GNUC_UNUSED uint16_t bit_rate)
|
||||
{
|
||||
write(1, data, datalen);
|
||||
G_GNUC_UNUSED ssize_t nbytes = write(1, data, datalen);
|
||||
return DECODE_COMMAND_NONE;
|
||||
}
|
||||
|
||||
@@ -164,6 +165,9 @@ int main(int argc, char **argv)
|
||||
decoder_name = argv[1];
|
||||
path = argv[2];
|
||||
|
||||
g_thread_init(NULL);
|
||||
tag_pool_init();
|
||||
|
||||
if (!input_stream_global_init(&error)) {
|
||||
g_warning("%s", error->message);
|
||||
g_error_free(error);
|
||||
@@ -215,5 +219,7 @@ int main(int argc, char **argv)
|
||||
}
|
||||
}
|
||||
|
||||
tag_pool_deinit();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -115,7 +115,7 @@ int main(int argc, char **argv)
|
||||
return 2;
|
||||
}
|
||||
|
||||
write(1, output, length);
|
||||
G_GNUC_UNUSED ssize_t ignored = write(1, output, length);
|
||||
}
|
||||
|
||||
pcm_convert_deinit(&state);
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#include "config.h"
|
||||
#include "decoder_list.h"
|
||||
#include "decoder_api.h"
|
||||
#include "tag_pool.h"
|
||||
#include "input_init.h"
|
||||
#include "input_stream.h"
|
||||
#include "audio_format.h"
|
||||
@@ -125,7 +126,7 @@ decoder_data(G_GNUC_UNUSED struct decoder *decoder,
|
||||
const void *data, size_t datalen,
|
||||
G_GNUC_UNUSED uint16_t kbit_rate)
|
||||
{
|
||||
write(1, data, datalen);
|
||||
G_GNUC_UNUSED ssize_t nbytes = write(1, data, datalen);
|
||||
return DECODE_COMMAND_NONE;
|
||||
}
|
||||
|
||||
@@ -178,8 +179,11 @@ int main(int argc, char **argv)
|
||||
decoder_name = argv[1];
|
||||
decoder.uri = argv[2];
|
||||
|
||||
g_thread_init(NULL);
|
||||
g_log_set_default_handler(my_log_func, NULL);
|
||||
|
||||
tag_pool_init();
|
||||
|
||||
if (!input_stream_global_init(&error)) {
|
||||
g_warning("%s", error->message);
|
||||
g_error_free(error);
|
||||
@@ -228,5 +232,7 @@ int main(int argc, char **argv)
|
||||
return 1;
|
||||
}
|
||||
|
||||
tag_pool_deinit();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -36,8 +36,9 @@ encoder_to_stdout(struct encoder *encoder)
|
||||
size_t length;
|
||||
static char buffer[32768];
|
||||
|
||||
while ((length = encoder_read(encoder, buffer, sizeof(buffer))) > 0)
|
||||
write(1, buffer, length);
|
||||
while ((length = encoder_read(encoder, buffer, sizeof(buffer))) > 0) {
|
||||
G_GNUC_UNUSED ssize_t ignored = write(1, buffer, length);
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
@@ -120,7 +121,7 @@ int main(int argc, char **argv)
|
||||
encoder_to_stdout(encoder);
|
||||
}
|
||||
|
||||
ret = encoder_flush(encoder, &error);
|
||||
ret = encoder_end(encoder, &error);
|
||||
if (!ret) {
|
||||
g_printerr("encoder_flush() failed: %s\n",
|
||||
error->message);
|
||||
|
||||
@@ -65,7 +65,8 @@ int main(int argc, char **argv)
|
||||
while ((nbytes = read(0, buffer, sizeof(buffer))) > 0) {
|
||||
Compressor_Process_int16(compressor,
|
||||
(int16_t *)buffer, nbytes / 2);
|
||||
write(1, buffer, nbytes);
|
||||
|
||||
G_GNUC_UNUSED ssize_t ignored = write(1, buffer, nbytes);
|
||||
}
|
||||
|
||||
Compressor_delete(compressor);
|
||||
|
||||
@@ -65,6 +65,6 @@ int main(int argc, char **argv)
|
||||
return 2;
|
||||
}
|
||||
|
||||
write(1, buffer, nbytes);
|
||||
G_GNUC_UNUSED ssize_t ignored = write(1, buffer, nbytes);
|
||||
}
|
||||
}
|
||||
|
||||
109
test/test_vorbis_encoder.c
Normal file
109
test/test_vorbis_encoder.c
Normal file
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2012 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "encoder_list.h"
|
||||
#include "encoder_plugin.h"
|
||||
#include "audio_format.h"
|
||||
#include "conf.h"
|
||||
#include "stdbin.h"
|
||||
#include "tag.h"
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
#include <stddef.h>
|
||||
#include <unistd.h>
|
||||
|
||||
static uint8_t zero[256];
|
||||
|
||||
static void
|
||||
encoder_to_stdout(struct encoder *encoder)
|
||||
{
|
||||
size_t length;
|
||||
static char buffer[32768];
|
||||
|
||||
while ((length = encoder_read(encoder, buffer, sizeof(buffer))) > 0) {
|
||||
G_GNUC_UNUSED ssize_t ignored = write(1, buffer, length);
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
main(G_GNUC_UNUSED int argc, G_GNUC_UNUSED char **argv)
|
||||
{
|
||||
G_GNUC_UNUSED bool success;
|
||||
|
||||
/* create the encoder */
|
||||
|
||||
const struct encoder_plugin *plugin = encoder_plugin_get("vorbis");
|
||||
assert(plugin != NULL);
|
||||
|
||||
struct config_param *param = config_new_param(NULL, -1);
|
||||
config_add_block_param(param, "quality", "5.0", -1, NULL);
|
||||
|
||||
struct encoder *encoder = encoder_init(plugin, param, NULL);
|
||||
assert(encoder != NULL);
|
||||
|
||||
/* open the encoder */
|
||||
|
||||
struct audio_format audio_format;
|
||||
|
||||
audio_format_init(&audio_format, 44100, SAMPLE_FORMAT_S16, 2);
|
||||
success = encoder_open(encoder, &audio_format, NULL);
|
||||
assert(success);
|
||||
|
||||
/* write a block of data */
|
||||
|
||||
success = encoder_write(encoder, zero, sizeof(zero), NULL);
|
||||
assert(success);
|
||||
|
||||
encoder_to_stdout(encoder);
|
||||
|
||||
/* write a tag */
|
||||
|
||||
success = encoder_pre_tag(encoder, NULL);
|
||||
assert(success);
|
||||
|
||||
encoder_to_stdout(encoder);
|
||||
|
||||
struct tag *tag = tag_new();
|
||||
tag_add_item(tag, TAG_ARTIST, "Foo");
|
||||
tag_add_item(tag, TAG_TITLE, "Bar");
|
||||
|
||||
success = encoder_tag(encoder, tag, NULL);
|
||||
assert(success);
|
||||
|
||||
tag_free(tag);
|
||||
|
||||
encoder_to_stdout(encoder);
|
||||
|
||||
/* write another block of data */
|
||||
|
||||
success = encoder_write(encoder, zero, sizeof(zero), NULL);
|
||||
assert(success);
|
||||
|
||||
/* finish */
|
||||
|
||||
success = encoder_end(encoder, NULL);
|
||||
assert(success);
|
||||
|
||||
encoder_to_stdout(encoder);
|
||||
|
||||
encoder_close(encoder);
|
||||
encoder_finish(encoder);
|
||||
}
|
||||
Reference in New Issue
Block a user