Compare commits

...

226 Commits

Author SHA1 Message Date
Avuton Olrich
e888c9e827 mpd version 0.16.4 2011-09-01 17:58:28 -07:00
Max Kellermann
2556449b36 configure.ac: fail if Vorbis was enabled explicitly, but not found
.. and a minor tweak for libFLAC+libogg detection.
2011-09-01 19:02:23 +02:00
Max Kellermann
446f9973cc configure.ac: fail if FLAC was enabled explicitly, but not found 2011-09-01 19:02:22 +02:00
Max Kellermann
596f36bb78 output/osx: don't drain the buffer when closing
Eliminate an unnecessary source of deadlocks.
2011-09-01 18:21:46 +02:00
Max Kellermann
e7abdab58d output/osx: signal the GCond while mutex is locked 2011-09-01 18:21:46 +02:00
Max Kellermann
13cdc9a9f8 configure.ac: auto-detect libmad without pkg-config
The pkg-config file was added by the Debian package maintainers, and
unfortunately, the rest of the world doesn't have it.
2011-09-01 11:06:08 +02:00
Max Kellermann
a1b8806422 configure.ac: fail if libid3tag was enabled explicitly, but not found
Add M4 function MPD_AUTO_PKG_LIB for pkg-config with AC_CHECK_LIB
fallback.
2011-09-01 10:58:36 +02:00
Max Kellermann
e635d47912 configure.ac: use MPD_AUTO_PKG to detect avahi
Don't abort the configure script when avahi could not be
auto-detected.  It previously did, because there was no custom "fail"
action for PKG_CHECK_MODULES.
2011-09-01 10:11:23 +02:00
Max Kellermann
53ac72a878 Makefile.am: use AVAHI_CFLAGS, AVAHI_LIBS
Don't add those to MPD_CFLAGS / MPD_LIBS.
2011-09-01 10:09:46 +02:00
Max Kellermann
2be6184c8d output_all: move _lock_signal() to output_control.c
Better name, better documentation.
2011-09-01 07:59:15 +02:00
Max Kellermann
8b0b4ff086 output_thread: reimplement CANCEL synchronization
The output thread could hang indefinitely after finishing CANCEL,
because it could have missed the signal while the output was not
unlocked in ao_command_finished().

This patch removes the wait() call after CANCEL, and adds the flag
"allow_play" instead.  While this flag is set, playback is skipped.
With this flag, there will not be any excess wait() call after the
pipe has been cleared.

This patch fixes a bug that causes mpd to discontinue playback after
seeking, due to the race condition described above.
2011-09-01 07:13:21 +02:00
Max Kellermann
60f7ff3de5 output/pulse: reset callbacks before closing stream/context
Fixes assertion failure when a stream callback is invoked too late
after a format change.
2011-08-31 21:01:34 +02:00
Max Kellermann
e76c752987 output/pulse: add function _delete_stream()
Merge common code.
2011-08-31 21:01:22 +02:00
Max Kellermann
042c1abc6e output/pulse: use _delete_context()
Eliminate duplicate code.
2011-08-31 20:58:36 +02:00
Jonathan Neuschäfer
3d12d7de62 doc/developer.xml: change the coing style example return type to int 2011-08-27 11:27:32 +02:00
Jonathan Neuschäfer
87593f95d4 scripts/makedist.sh: fix test usage
Checkbashisms (part of the Debian devscripts) pionted this out.
2011-08-27 11:27:16 +02:00
Max Kellermann
11626e48bf input/curl: implement a hard-coded timeout of 10 seconds
Be sure to stop the operation at some point when the server isn't
responding.
2011-08-26 19:28:09 +02:00
Max Kellermann
b3df4dc2c9 output/pulse: fix deadlock when the stream was suspended
Check if the stream is suspended; wake up the main loop when it
becomes suspended.
2011-08-23 23:02:13 +02:00
Max Kellermann
3db9ab82ea output/pulse: add assertions 2011-08-23 22:48:22 +02:00
Max Kellermann
2dc3acc5f0 output/pulse: return 0 on error
Not a bool.
2011-08-23 22:48:22 +02:00
Max Kellermann
25686e5bce pulse/output: fix deadlock when resuming the stream
Unlock the mainloop in all code paths.
2011-08-23 22:45:47 +02:00
Max Kellermann
8d70f808d9 input/curl: limit the receive buffer size 2011-08-23 20:46:51 +02:00
Max Kellermann
7c887af1ea output/httpd: add assertions 2011-08-23 18:14:39 +02:00
Max Kellermann
b7f435b50e output/httpd: don't warn on client disconnect
This warning should only be logged when we really received something.
When the client disconnects, G_IO_IN is triggered, and the read
returns G_IO_STATUS_EOF.
2011-08-23 18:02:56 +02:00
Max Kellermann
d3b15f8fda decoder/mpcdec: fix gcc warning
Move the variable "vbr_update_acc" into the #ifdef block.
2011-08-23 17:58:56 +02:00
Max Kellermann
838f7cd210 encoder_plugin: add method pre_tag()
In the "vorbis" plugin, this is a copy of the old flush() method,
while flush() gets a lot of code remove, it just sets the "flush" flag
and nothing else.  It doesn't start a new stream now, which should fix
a few problems in some players.
2011-07-20 20:54:34 +02:00
Max Kellermann
13539961b2 output/httpd: explicitly convert size_t to bool in pause() 2011-07-20 19:16:47 +02:00
Max Kellermann
a26f2ef17d pipe: lock the mutex in music_pipe_size() 2011-07-20 19:05:32 +02:00
Max Kellermann
d97c46bcdc pipe: make read-only functions "pure"
Enable gcc optimizations.
2011-07-20 19:05:26 +02:00
Max Kellermann
2b6542467c output_thread: unlock the mutex while calling cancel()
The method may take longer, and we shouldn't be holding the lock.
2011-07-20 19:05:08 +02:00
Max Kellermann
8fa51faa38 player_thread: lock the player while setting the bite_rate 2011-07-20 19:04:54 +02:00
Max Kellermann
b2175629fd update_walk: apply follow_inside_symlinks to absolute symlinks 2011-07-20 14:15:20 +02:00
Max Kellermann
2e28ed8f81 wavpack: obey all decoder commands, stop at CUE track border
It used to ignore the decoder_data() return value.
2011-07-20 12:54:30 +02:00
Max Kellermann
4c4f8bf02a decoder/wavpack: use the correct integer types
libwavpack provides int32_t samples, and wants uin32_t for sample
counts.
2011-07-20 12:54:22 +02:00
Max Kellermann
e464be5f39 decoder/wavpack: simplify the WavpackUnpackSamples()==0 check
.. and remove one indent level.
2011-07-20 12:32:48 +02:00
Max Kellermann
d7d717f2ce playlist_control: don't resume playback when seeking to another song while paused
Use a shortcut in playlist_seek_song(), don't call
playlist_play_order() because that would reset the "paused" state.
2011-07-20 11:33:51 +02:00
Max Kellermann
d1eeed6a5b output/alsa: fix SIGFPE when alsa announces a period size of 0 2011-07-20 06:54:51 +02:00
Max Kellermann
736fd0e293 decoder/ffmpeg: use avformat_open_input() if available
av_open_input_stream() has been deprecated.
2011-07-18 23:31:47 +02:00
Max Kellermann
6592ca9f88 decoder: use AVDictionary instead of AVMetadata
AVMetadata has been deprecated.
2011-07-18 23:31:31 +02:00
Max Kellermann
762712c756 database: require X_OK on parent directory, not R_OK
For accessing the child of a directory, one needs X_OK on the
directory.
2011-07-18 22:48:07 +02:00
Max Kellermann
73f9e17951 NEWS: fix memory leaks 2011-07-18 22:47:51 +02:00
Jonathan Neuschäfer
7d6a605a85 output/shout: fix a memory leak 2011-07-18 22:04:48 +02:00
Jonathan Neuschäfer
a6a8bdffc3 output/recorder: fix a memory leak 2011-07-18 22:04:10 +02:00
Jonathan Neuschäfer
296085ff23 output/httpd: add missing g_free in error path 2011-07-18 22:04:06 +02:00
Jonathan Neuschäfer
36aa8ce3c9 output/ao: add missing g_free in error path 2011-07-18 22:03:48 +02:00
Jonathan Neuschäfer
c49c69d6ea conf: add missing fclose in error path
This patch seems a bit ugly, maybe it would be a bit cleaner with gotos.
2011-07-18 22:03:40 +02:00
Jonathan Neuschäfer
d5684f7444 sticker: fix a memory leak 2011-07-18 22:03:37 +02:00
Jonathan Neuschäfer
affb4bd923 ape: add missing g_free in error path 2011-07-18 22:03:34 +02:00
Max Kellermann
65772a74e0 configure: correct avahi/bonjour state on result page
Was always displayed as "no", even if one was found.
2011-07-03 15:42:22 +02:00
Max Kellermann
cca2c2f4ca test/run_filter: remove unused variable "frame_size" 2011-07-03 15:21:40 +02:00
Max Kellermann
52e2fa91c4 test/read_conf: make variables more local 2011-07-03 15:20:39 +02:00
Max Kellermann
dca405a746 test/read_conf: fix -Wunused-but-set-variable 2011-07-03 15:20:28 +02:00
Jonathan Ballet
3680a6bbbb doc/protocol: add some missing specifications 2011-07-03 15:05:04 +02:00
Max Kellermann
6aa6a9c272 decoder/flac: validate the sample rate when scanning the tag
Don't calculate the song duration when the sample rate is 0 (division
by zero crash).
2011-07-03 14:57:56 +02:00
oblique
8d1c7ca206 ffmpeg: workaround for semantic API change in recent ffmpeg versions 2011-07-03 14:54:56 +02:00
Tony Miller
52b8e0f9ec doc/user: Typo in playlist plugin documentation, 'playlist plugin' not 'filter'.
This patch fixes a typo in doc/user about playlist plugins.

Its in the top commit in my repository in a branch called 'doc_fix':
git://github.com/mcfiredrill/mpd.git
2011-07-03 14:53:37 +02:00
Avuton Olrich
3c4f4793b5 Modify version string to post-release version 0.16.4~git 2011-06-04 07:37:34 -07:00
Avuton Olrich
e2950a7e4d mpd version 0.16.3 2011-06-04 07:37:33 -07:00
Max Kellermann
4b4aa64261 directory: allow directories with just playlists
Keep those when scanning for empty directories.  The check in
playlist_vector_is_empty() was missing.
2011-05-09 21:37:43 +02:00
Max Kellermann
26735390ff playlist_song: fix playlist files in base music directory
g_path_get_dirname() returns "." when there is no directory name in
the given path.  This patch adds a workaround for that.
2011-05-09 18:05:11 +02:00
Max Kellermann
9402b23dd5 playlist_song: fix NULL pointer dereference 2011-05-09 18:03:54 +02:00
Max Kellermann
246db3d565 decoder/ffmpeg: use avcodec_decode_audio3() if available
avcodec_decode_audio3() has been added in libavformat 52.25.0, and the
predecessor avcodec_decode_audio2() has been deprecated.
2011-05-09 09:24:17 +02:00
Max Kellermann
eaf414cbc8 decoder/ffmpeg: make variables more local 2011-05-09 09:24:15 +02:00
Anton Khirnov
327d41c00f decoder/ffmpeg: don't use deprecated CODEC_TYPE_AUDIO with new lavc
fixes build with lavc 53.
2011-05-09 08:00:45 +02:00
Max Kellermann
05d8ce3bcd decoder/ffmpeg: define fallback macro AV_VERSION_INT()
For ffmpeg < 0.5.  Copied from libavutil 0.5.
2011-05-09 08:00:45 +02:00
Max Kellermann
def2fe8805 Merge branch 'v0.15.x' into v0.16.x
Conflicts:
	NEWS
	configure.ac
	src/listen.c
2011-04-12 07:39:01 +02:00
Max Kellermann
f680b0a431 decoder/flac: fix enum mismatch in flac_tell_cb()
Fix clang warning.
2011-03-23 22:31:40 +01:00
Max Kellermann
d4b00ff11c listen: suppress "unused variable" warning 2011-03-23 22:27:31 +01:00
Max Kellermann
532f94a187 audio_parser: fix assertion failure in audio format mask parser
Use audio_format_mask_valid() to verify a mask.
2011-03-23 22:22:51 +01:00
Max Kellermann
87ad2f8542 command: fix return value of handle_currentsong()
Thanks to clang for complaining.
2011-03-23 22:16:46 +01:00
Simon Kagstrom
a8f891efcd configure.ac: Enable HAVE_OGG_COMMON when using libtremor
Otherwise OGGs can't be played.
2011-03-23 22:09:58 +01:00
Avuton Olrich
b5fc2419e8 Modify version string to post-release version 0.16.3~git 2011-03-18 17:43:11 -07:00
Avuton Olrich
fe588a255b mpd version 0.16.2 2011-03-18 17:43:11 -07:00
Max Kellermann
1fc571088b command: print playlist load error
Call print_playlist_result() instead of casting the enum implicitly.
2011-03-18 19:45:59 +01:00
Max Kellermann
8d83914f05 output/httpd: include sys/socket.h only when building with libwrap
Fixes build failure on WIN32.
2011-03-18 19:44:12 +01:00
Max Kellermann
0fdcd381bc update_walk: ignore parameter "mode" on WIN32
Fix compiler warning.
2011-03-18 19:43:26 +01:00
Max Kellermann
4f293ecd6f audio_format, output_thread: add more audio_format_valid() assertions 2011-03-16 23:37:41 +01:00
Max Kellermann
b6303313f0 encoder/vorbis: reset the Ogg stream after flush
Without the ogg_stream_reset() call, the "e_o_s" flag never gets
reset, and libogg writes EOS packets over and over.
2011-03-16 19:16:06 +01:00
Max Kellermann
a28449a123 encoder/vorbis: reset the Ogg stream after flush
Without the ogg_stream_reset() call, the "e_o_s" flag never gets
reset, and libogg writes EOS packets over and over.
2011-03-16 19:13:46 +01:00
Max Kellermann
6dcec36621 Merge release 0.15.16 into v0.16.x
Conflicts:
	NEWS
	configure.ac
	src/output/jack_plugin.c
	src/update.c
2011-03-16 18:08:54 +01:00
Avuton Olrich
84d0fd39a3 Modify version string to post-release version 0.15.17~git 2011-03-13 20:27:33 -07:00
Avuton Olrich
4d4b7e3de0 mpd version 0.15.16 2011-03-13 20:27:33 -07:00
Ulrich Spörlein
e2aea6bce5 output/httpd: include sys/socket.h for AF_UNIX 2011-03-09 19:53:48 +01:00
Ulrich Spörlein
5779146a7f configure.ac: fix bashism in tremor test
This makes FreeBSD detect libogg correctly. The '==' operator is an
undocumented GNU extension to test(1) and cannot be relied upon to
exist and do the right thing. POSIX mandates string comparisons to be
done using "test foo = bar".
2011-03-09 19:50:54 +01:00
Max Kellermann
a1d1c2beaa output/oss: disable 24 bit playback on FreeBSD
See code comment.
2011-02-28 00:09:45 +01:00
Max Kellermann
ee9c60fad4 output/oss: AFMT_S24_PACKED is little-endian
According to the Solaris dsp manpage, AFMT_S24_PACKED is
little-endian:

 http://download.oracle.com/docs/cd/E19963-01/821-1475/6nmf5baot/index.html

The Minix soundcard.h header says the same.
2011-02-28 00:00:41 +01:00
Max Kellermann
1674a4ec82 output/jack: fix crash with mono playback
With mono sound, jack_sample_size is smaller than frame_size (4 vs 2
bytes), and "space/jack_sample_size==0".  That means mpd_jack_play()
will return 0, although no error has occurred.
2011-02-27 23:26:50 +01:00
Max Kellermann
ce370bee60 output/jack: rename variable sample_size to jack_sample_size 2011-02-25 10:46:44 +01:00
Max Kellermann
e257484870 Makefile.am: distribute test/stdbin.h 2011-02-18 08:19:37 +01:00
Christopher Brannon
2a1f4539f6 Insure proper initialization of stack-allocated struct.
Version 1.0.0 of the libao library added a new field to the
ao_sample_format struct.  It is a char * named matrix.  When
an ao_sample_format is allocated on the stack, this field contains
garbage.  The proper course is to insure that is initialized to NULL.
NULL indicates that we do not want any mapping.
The struct is now initialized using a static initializer, and this
technique is compatible with all known versions of libao.
2011-02-15 12:16:25 +01:00
Max Kellermann
906efdd320 Makefile.am: compile test/run_encoder with ENCODER_CFLAGS 2011-02-13 23:22:57 +01:00
Thomas Jansen
948b8f35e6 general: whitespace cleanup
Remove trailing whitespace found by this command:
find -name '*.[ch]' | xargs grep "[[:space:]]$"
2011-02-09 22:42:31 +01:00
Thomas Jansen
e776c605ad output/httpd: initialize unflushed_input
This fixes the following valgrind warning occuring on the first call of
httpd_output_read_page:
==20124== Conditional jump or move depends on uninitialised value(s)
==20124==    at 0x425E65: httpd_output_read_page (httpd_output_plugin.c:240)
==20124==    by 0x426087: httpd_output_open (httpd_output_plugin.c:279)
==20124==    by 0x41D862: ao_open (output_plugin.h:206)
==20124==    by 0x41E133: audio_output_task (output_thread.c:590)
2011-02-09 22:41:36 +01:00
Tony Miller
8b2f4fc823 Set fadeout in gme_decoder_plugin. Due to the nature of the gme library,
this needs to be done for the end of songs to be detected.
2011-02-03 00:25:35 +01:00
Max Kellermann
03018611f8 update: log all file permission problems 2011-01-31 09:39:24 +01:00
Max Kellermann
8f99c954ad NEWS: fix 0.16.1 release year 2011-01-28 21:12:17 +01:00
Max Kellermann
5735c9efc1 configure.ac: fix tremor configure test
When the configure options were moved around for 0.16, the order was
changed, and the Tremor check broke.
2011-01-28 12:21:03 +01:00
Andreas Wiese
e6c3acaa6f Fix NDEBUG test
<stdbool.h> needs to be included unconditionally from definition of
NDEBUG, since »bool« doesn't get defined otherwise.

Signed-off-by: Andreas Wiese <aw-devel@meterriblecrew.net>
2011-01-14 16:22:25 +01:00
Avuton Olrich
44b4b50949 Modify version string to post-release version 0.16.2~git 2011-01-09 18:00:12 -08:00
Avuton Olrich
9ad862d3a6 mpd version 0.16.1 2011-01-09 18:00:12 -08:00
Yuriy Kaminskiy
77d71c4ee6 Makefile.am: resolve modplug vs. libsndfile cflags/headers conflict
A bit of automake magic (see info automake "Per-Object Flags").
Compile-tested.
2011-01-09 18:21:27 +01:00
Max Kellermann
8c0afd8557 Merge branch 'v0.15.x' into v0.16.x
Conflicts:
	NEWS
	configure.ac
	src/directory.h
2011-01-07 23:50:23 +01:00
Max Kellermann
2a56300f7b player_thread: discard empty chunks while cross-fading
When a music_chunk to be crossfaded consists only of a tag,
cross-fading is not possible, and led to an assertion failure.  This
patch just discards those, as if cross-fading was not enabled.
2011-01-07 23:45:51 +01:00
Max Kellermann
5f06999686 output_thread: fix double lock
During the whole output thread, the audio_output object is locked, and
it is only unlocked while waiting for the GCond and while running a
plugin method.  The error handler in ao_play_chunk() attempted to lock
the object again, which was code from MPD 0.15.x which should have
been removed a long time ago.
2011-01-07 23:08:18 +01:00
Max Kellermann
4c09aeb5a1 player_thread: fix assertion failure due to early seek
Until the decoder plugin has called decoder_initialized(), the player
may not submit seek commands.  This however could occur with a slow
decoder and a CUE file with a virtual song offset.  This patch adds
another check.
2011-01-07 22:52:50 +01:00
Max Kellermann
af892e7e80 player_thread: make variables more local 2011-01-07 22:33:10 +01:00
Max Kellermann
0022fb100b encoder/lame: explicitly configure the output sample rate
When you don't explicitly set an output sample rate, liblame tries to
guess an output sample rate from the input sample rate.  You would
think that this "guessing" consists of just setting both equal, but
that is not the case.  For 44.1kHz at 96kbit/s, liblame chooses
32kHz.  This patch explicitly configures the output sample rate, to
stop the bad guessing.
2011-01-07 19:37:39 +01:00
Max Kellermann
4f2d67dfb0 output/httpd: define G_LOG_DOMAIN in httpd_client.c 2011-01-07 18:00:12 +01:00
Max Kellermann
b5645ab29f output/osx: fix up audio format first, then apply it to device
This is a MPD 0.16 regression: when playing a 24 bit file, the switch
to 16 bit was made only partially, after mBytesPerPacket and
mBytesPerFrame had already been applied.

That means mBytesPerFrame referred to 24 bit, and mBitsPerChannel
referred to 16 bits.  Of course, that cannot work.
2011-01-07 17:31:36 +01:00
Max Kellermann
3149d1abf9 configure.ac: eliminate bashism "echo -n"
Use "printf" instead.
2011-01-07 17:31:30 +01:00
Max Kellermann
59a417fc84 configure.ac: avoid GNU extension in "expr match" call 2011-01-07 17:29:19 +01:00
Max Kellermann
b75d53413d configure.ac: use AC_LANG_SOURCE
Fixes autotools warnings.
2011-01-07 17:25:52 +01:00
Max Kellermann
c44a744c0b fix version number in NEWS 2011-01-07 17:25:25 +01:00
Max Kellermann
76cddfab90 configure.ac: disable the FFADO plugin by default
It is known to crash instantly.
2010-12-22 07:31:17 +01:00
Max Kellermann
60b4f6b3eb directory: fix warning "comparison between signed and unsigned"
Cast the constant to dev_t, not to unsigned.
2010-12-21 20:21:22 +01:00
Max Kellermann
546232b1c0 zeroconf-bonjour: use g_htons() instead of htons()
Fixes the gcc warning "implicit declaration of function 'htons'".
2010-12-21 20:21:20 +01:00
Max Kellermann
42c5788de3 Modify version string to post-release version 0.15.16~git 2010-12-21 20:19:49 +01:00
Max Kellermann
fb00e7fddc add void casts to suppress "result unused" warnings (clang) 2010-12-21 08:06:02 +01:00
Alex Viskovatoff
41fdcf328c decoder/mad: work around build failure on Solaris
Rename the "version" struct, because it seems to be a reserved name on
Solaris:

 "src/decoder/mad_decoder_plugin.c", line 550: (enum) tag redeclared: version
 cc: acomp failed for src/decoder/mad_decoder_plugin.c
2010-12-21 07:57:07 +01:00
Alex Viskovatoff
144ad7992e output/solaris: add missing parameter to open_cloexec() call 2010-12-21 07:31:08 +01:00
Alex Viskovatoff
a0dd1a1b8b audio_check: fix parameter in prototype 2010-12-21 07:29:58 +01:00
Max Kellermann
c360e69162 Modify version string to post-release version 0.16.1~git 2010-12-21 07:29:31 +01:00
Avuton Olrich
da01c6ef5b mpd version 0.16 2010-12-11 04:19:49 -08:00
Max Kellermann
fcd2355f4f Merge branch 'master' of git://git.musicpd.org/avuton/mpd 2010-12-07 18:18:19 +01:00
Max Kellermann
748a8a6f42 tag_id3: support multiple values
Loop over all frames with a specific id, and import all of them - not
just the first one (index 0).
2010-12-07 18:05:44 +01:00
Anton Khirnov
cb9965bab5 command: don't error when sticker list is run on song with no stickers
this is inconsistent with other commands (e.g. find) and seems wrong --
a song with no stickers attached is a perfectly valid state and an empty
list of stickers is also perfectly valid.
2010-12-07 17:32:52 +01:00
Max Kellermann
429ed24c99 tag_ape: support multiple values
One APE tag may contain more than one value, separated by null bytes.
2010-11-24 08:59:04 +01:00
Max Kellermann
1ab46472ab decoder_thread: load APE replay gain from music files 2010-11-18 23:02:30 +01:00
Max Kellermann
f6bbe1332f replay_gain_ape: parse replay gain from APE tags
Based on the APE reader.
2010-11-18 22:26:06 +01:00
Max Kellermann
11613347be tag_ape: move code to ape.c
Generic library for scanning APE tags.  Eliminated one "goto"!
2010-11-18 21:44:24 +01:00
Max Kellermann
8f46f1520c timer: fix integer overflow in timer_delay()
Fixes a regression: for output_plugin.delay(), we added a method to
the timer class which returns the delay in milliseconds.  This fails
to detect negative values, because the unsigned integer is divided by
1000, and then casted to signed.
2010-11-18 21:29:03 +01:00
Avuton Olrich
f2893b0d0f Modify version string to post-release version 0.16~git 2010-11-08 18:57:09 -08:00
Avuton Olrich
c7265f9689 mpd version 0.16~alpha4 2010-11-08 18:57:09 -08:00
Max Kellermann
46ab8d18e2 playlist_song: calculate duration of last CUE track 2010-11-08 20:16:26 +01:00
Max Kellermann
f384f8da93 Merge release 0.15.15 from branch 'v0.15.x'
Conflicts:
	NEWS
	configure.ac
2010-11-08 18:50:22 +01:00
Max Kellermann
23cd8a74be mpd version 0.15.15 2010-11-08 18:48:28 +01:00
Max Kellermann
cc1debc948 output/shout: artist comes first in stream title
After popular demand, I've switched the order of "artist" and "title"
in the stream title.  There is no standard, and there is no reliable
way to parse those from the stream title.
2010-11-08 18:46:14 +01:00
Max Kellermann
5a3aa1262a update_walk: explicitly check for permission problems
Call access() and print an extra error message when EACCES is
returned.  Hopefully this will reduce the number of support requests
due to wrong file permissions.
2010-11-08 18:24:19 +01:00
Max Kellermann
ad52eb236d input/rewind: fix assertion failure
The assertion added in MPD 0.15.14 was too much, it failed when the
MIME type of a stream was NULL.
2010-11-08 10:37:09 +01:00
Avuton Olrich
d2c2cbd0ae Modify version string to post-release version 0.16~git 2010-11-07 06:39:31 -08:00
Avuton Olrich
af4a93dbcf mpd version 0.16~alpha3 2010-11-07 06:39:31 -08:00
Max Kellermann
4478b3ef74 Merge release 0.15.14 from branch 'v0.15.x'
Conflicts:
	NEWS
	configure.ac
	src/decoder_control.c
	src/decoder_control.h
	src/input/rewind_input_plugin.c
	src/output_control.c
	src/output_thread.c
	src/player_thread.c
2010-11-07 15:30:18 +01:00
Avuton Olrich
462bba8e2f Modify version string to post-release version 0.15.15~git 2010-11-06 14:42:03 -07:00
Avuton Olrich
dec7090198 mpd version 0.15.14 2010-11-06 14:42:02 -07:00
Max Kellermann
83ec0e5552 player_thread: fix assertion failure due to wrong music pipe on seek
When one song is played twice, and the decoder is working on the
second "instance", but the first should be seeked, the check in
player_seek_decoder() may assume that it can reuse the decoder without
exchanging pipes.  The last thing was the mistake: the pipe pointer
was different, which led to an assertion failure.  This patch adds
another check which exchanges the player pipe.
2010-11-05 19:24:42 +01:00
Max Kellermann
cc261872c2 decoder_control: pass music_pipe to dc_start()
More abstraction for decoder_control.pipe.
2010-11-05 19:18:44 +01:00
Max Kellermann
5223261f19 player_thread: add helper function player_dc_at_next_song()
Some abstraction for decoder_control.pipe access.
2010-11-05 19:08:59 +01:00
Max Kellermann
c594afeee7 pipe: add helper function music_pipe_empty() 2010-11-05 18:40:23 +01:00
Max Kellermann
32d10eedbd input/rewind: remove redundant NULL check before g_free() call 2010-11-05 18:40:14 +01:00
Max Kellermann
dfd98eede7 input/rewind: add two assertions 2010-11-05 18:40:07 +01:00
Max Kellermann
a728d7a026 input/rewind: fix double free bug
Duplicate the "mime" attribute of the inner input_stream object,
instead of copying the pointer.
2010-11-05 18:39:40 +01:00
Max Kellermann
5a26320680 output/alsa: dump buffer and period limits 2010-11-05 10:35:46 +01:00
Max Kellermann
90dc880e67 output/httpd: implement delay() 2010-11-05 09:49:22 +01:00
Max Kellermann
e11ff967d0 output/shout: implement delay()
This makes the plugin more responsive to control commands, because it
will listen to control events while waiting.
2010-11-05 09:49:20 +01:00
Max Kellermann
2dc6ed7b3a output_plugin: add method delay()
This method is used to reduce the delay of commands issued to the
shout plugin.
2010-11-05 09:47:43 +01:00
Max Kellermann
ad430c6617 timer: add function timer_delay() 2010-11-05 09:39:56 +01:00
Max Kellermann
e8d8bd4c0d decoder/{mp4ff,ffmpeg}: add extension ".m4b" (audio book)
Same as ".m4a".
2010-11-05 02:01:35 +01:00
Max Kellermann
8d5fa754e8 output_thread: fix assertion failure due to race condition in OPEN
Change the assertion on "fail_timer==NULL" in OPEN to a runtime check.
This assertion crashed when the output thread failed while the player
thread was calling audio_output_open().
2010-11-04 23:44:23 +01:00
Max Kellermann
2ee047a1dd output_internal: protect attribute "fail_timer" with mutex 2010-11-04 23:40:43 +01:00
Max Kellermann
9562f66741 output_control: lock object in audio_output_open()
Protect the attributes "open" and "fail_timer".
2010-11-04 23:28:18 +01:00
Max Kellermann
21223154aa output_control: lock object in audio_output_close()
Protect the attributes "open" and "fail_timer".
2010-11-04 21:51:02 +01:00
Mantas Mikulenas
ec48b5ea3a server_socket: remove AI_ADDRCONFIG
When you pass the flag AI_ADDRCONFIG to getaddrinfo(), it does not
consider address families on the loopback device.  When run on a
machine without an external network card, just with "lo", it was
unable to look up any address.
2010-11-04 20:17:45 +01:00
Max Kellermann
754015544f output/ffado: transfer_playback_buffers() returns a boolean
libffado documentation says this function returns -1 on error, but
that is a lie - it returns a boolean value, and "false" means error.
2010-11-04 20:08:04 +01:00
Max Kellermann
3f89f77429 decoder/ffmpeg: check AVCodecContext.sample_fmt value
.. instead of av_get_bits_per_sample_format().  The SampleFormat enum
value is authoritative.
2010-11-04 20:04:15 +01:00
Denis Krjuchkov
9dee419b7c winmm_output: handle empty string case when parsing device id 2010-11-04 11:09:50 +05:00
Denis Krjuchkov
7612bf1bfa winmm_output: added "device" configuration option
Device can be specified either by magic index (starting with 0)
or by device name.
2010-11-04 00:51:18 +05:00
Denis Krjuchkov
ad56e10e5b winmm_output: improved test_default_device
If no device is available test_default_device returns false.
2010-11-03 23:31:49 +05:00
Max Kellermann
75f4772ba2 output: new output plugin "ffado"
Using libffado, to play on firewire audio devices.

Warning: this plugin was not tested successfully.  I just couldn't
keep libffado2 from crashing.  Use at your own risk.

For details, see my Debian bug reports:

  http://bugs.debian.org/601657
  http://bugs.debian.org/601659
2010-10-27 21:25:41 +02:00
Alder Hornbridge
fe1b626f76 decoder/sidplay: play mus, str, prg, x00 files 2010-10-27 21:18:43 +02:00
Alder Hornbridge
4e94516912 decoder/sidplay: play monaural SID tunes in mono 2010-10-27 21:16:24 +02:00
Tony Miller
dadb6747ad Container support for gme decoder. 2010-10-14 17:11:59 +02:00
Max Kellermann
188e1b440e playlist/rss: new playlist plugin for RSS feeds 2010-10-11 20:33:41 +02:00
Max Kellermann
a57f9e712d Merge release 0.15.13 from branch 'v0.15.x'
Conflicts:
	NEWS
	configure.ac
	src/input/rewind_input_plugin.c
	src/output/httpd_output_plugin.c
2010-10-11 20:33:17 +02:00
Avuton Olrich
a549d871f3 Modify version string to post-release version 0.15.14~git 2010-10-10 09:57:57 -07:00
Avuton Olrich
b552e9a120 mpd version 0.15.13 2010-10-10 09:57:52 -07:00
Denis Krjuchkov
e6fc88a758 mixer: winmm_mixer implemented 2010-10-09 02:45:08 +06:00
Denis Krjuchkov
20004b7ee0 win32_output: renamed win32 output plugin to winmm
Win32 has many audio APIs. New name is slightly more correct.
2010-10-08 23:55:14 +06:00
Max Kellermann
84e037631d output/httpd: use the new server_socket library 2010-10-05 21:18:54 +02:00
Max Kellermann
18e3d0b504 listen: move generic code to server_socket.c 2010-10-05 21:18:54 +02:00
Max Kellermann
04c4398bfc output/httpd: don't close socket in open() failure
This cleanup call is obsolete, since we moved the binding code to
enable()/disable().
2010-10-05 21:18:54 +02:00
Max Kellermann
39e42394bd output_all: disable outputs on shutdown
Call output_plugin.disable() before output_plugin.finish().  This
ensures that all outputs are properly cleaned up, to make valgrind
happy.
2010-10-05 21:18:54 +02:00
Qball Cow
a39e6b43e8 add mpd_error.h to sources. 2010-10-03 19:46:36 +02:00
Max Kellermann
5923cfcde3 output/httpd: MIME type audio/ogg for Ogg Vorbis
RFC 5334 10.3 defines the MIME type "audio/ogg".  We could use
"application/ogg" as well, but we know for sure that we only emit
audio data.
2010-10-03 16:22:03 +02:00
Tony Miller
e69df36e4a configure.ac: Disable unix domain sockets by default if we're on cygwin. 2010-10-02 13:27:02 -07:00
Denis Krjuchkov
e10b872fc3 main_win: replaced g_error usages with MPD_ERROR 2010-09-28 22:38:57 +06:00
Denis Krjuchkov
2b78358af5 mpd_error: more correct MPD_ERROR implementation
Original implementation does not handle
	if (...)
		MPD_ERROR("die");
	else
		...
case well. This change fixes handling of such cases.
2010-09-28 18:12:14 +02:00
Thomas Jansen
e3f4c7b91c input/rewind: enable for MMS 2010-09-28 12:56:47 +02:00
Andrew Morgan
a59ab3e2ee playlist: make single mode 'sticky' 2010-09-28 12:52:52 +02:00
Thomas Jansen
28bcb8bdf5 eliminate g_error() usage
Replaced all occurrences of g_error() with MPD_ERROR() located in a new header
file 'mpd_error.h'. This macro uses g_critical() to print the error message
and then exits gracefully in contrast to g_error() which would internally call
abort() to produce a core dump.

The macro name is distinctive and allows to find all places with dubious error
handling. The long-term goal is to get rid of MPD_ERROR() altogether. To
facilitate the eventual removal of this macro it was added in a new header
file rather than to an existing header file.

This fixes  and .
2010-09-25 15:00:43 +02:00
Thomas Jansen
9af9fd1400 output/httpd: bind_to_address support (including IPv6)
Added support for a new optional configuration setting for the httpd output
named "bind_to_address". Setting it to a specific IP address (v4 or v6) will
cause the httpd output to bind to that address exclusively. Supporting
multiple addresses in parallel is future work.

This implements the feature requests  and .
2010-09-25 15:00:43 +02:00
Thomas Jansen
0c80bd5fc0 conf: Whitespace cosmetics 2010-09-25 15:00:43 +02:00
Denis Krjuchkov
7563ece236 .gitignore: added mpd.exe to ignore list 2010-09-25 15:01:53 +06:00
Max Kellermann
a14cd97f56 playlist: fix "queued" check in playlist_sync()
The check was meant to fix an assertion failure, but it was the wrong
way around.  This broke cross-fading most of the time.
2010-09-23 23:29:36 +02:00
Max Kellermann
0955f33a86 decoder/mp4ff: support more variations of "album artist"
According to the mantis bug report 2847, there are several possible
variations of the "album artist" tag:

- "album artist"
- "album_artist"
- "albumartist"

This patch adds support for the latter two.
2010-09-23 21:19:41 +02:00
Max Kellermann
a016fb4048 listen: fix "unused parameter" warning on WIN32 2010-09-23 20:51:23 +02:00
Denis Krjuchkov
e8ebb1af91 main: Add Windows Service support
I've added PIPE_EVENT_SHUTDOWN because calling g_main_loop_quit() do not work when called from another thread.
Main thread was sleeping in g_poll() so I needed some way to wake it up.

By some strange reason call close(event_pipe[0]) in event_pipe_deinit() hangs.
In current implementation that code never reached so that was not a problem :-)
I've added a conditional to leave event_pipe[0] open on Win32.
2010-09-23 20:42:33 +02:00
Thomas Jansen
9fa3d7c4fa playlist_state: Fix the "state" line in the output
An '\n' was erroneously inserted in the line containing the state, e.g.
"state: \nplay" instead of "state: play".

Fix for bug .
2010-09-23 20:41:23 +02:00
Thomas Jansen
54294366d5 rewind_input_plugin: Update MIME not only once
The assumption that MIME type is set only once is not valid with CURL,
as URL redirections may update the MIME type.

This fixes bug .
2010-09-23 20:39:13 +02:00
Max Kellermann
9423b456a1 zeroconf-bonjour: use g_htons() instead of htons()
htons() is not available if netinet/in.h is not included.
2010-09-23 09:01:37 +02:00
Max Kellermann
64209749fb directory: cast DEVICE_INARCHIVE, DEVICE_CONTAINER to dev_t
Fix gcc warning.
2010-09-23 09:01:25 +02:00
Max Kellermann
586b7601c6 playlist_database: initialize pm.mtime 2010-09-23 09:01:23 +02:00
Max Kellermann
4425989898 fd_util: work around aliasing warning in recvmsg_cloexec() 2010-09-23 09:01:20 +02:00
Max Kellermann
5b996ab880 output/httpd: access sockaddr_storage object directly
Work around aliasing warning.
2010-09-23 09:01:17 +02:00
Max Kellermann
635cfbae13 decoder_control: use g_free() to manage mixramp allocations
Be consistent with the rest of MPD, and don't use the non-portable
header "malloc.h".
2010-09-23 08:49:21 +02:00
Max Kellermann
922e51e8a9 autogen.sh: enable automake 1.11 2010-09-23 08:37:26 +02:00
Avuton Olrich
6d9f1ad61f configure.ac: Add enable_tremor to post decoder plugin tests. 2010-09-11 20:08:35 -07:00
Avuton Olrich
841b9b3d63 configure.ac: Move use_tremor to enable_tremor. 2010-09-11 20:07:55 -07:00
Avuton Olrich
a3745ae7f3 configure.ac: Correct and clean up tremor check in oggvorbis test. 2010-09-11 20:06:05 -07:00
Avuton Olrich
27d7013ff8 configure.ac: Only enable libogg if OggTremor path/opt has not been specified. 2010-09-11 19:57:44 -07:00
Avuton Olrich
27d3340af2 configure.ac: Fix OggFLAC/tremor test. 2010-09-11 19:28:30 -07:00
Qball Cow
4a7abc9d44 Correctly terminate stream_title.
This caused random data to be send via icy-server if the played
song had no tags.
2010-09-08 13:19:59 +02:00
Max Kellermann
589bb54111 input/curl: fix version check for curl_multi_timeout()
According to the CURL web site, curl_multi_timeout() was added in
version 7.15.4:

 http://curl.haxx.se/libcurl/c/curl_multi_timeout.html
2010-09-07 21:40:56 +02:00
Max Kellermann
d953225531 update_walk: update existing playlist entry
Fixes duplicate playlist entries.
2010-09-07 20:22:05 +02:00
Max Kellermann
663815ead8 playlist_vector: update_or_add() returns bool
False if the vector was not modified.
2010-09-07 20:21:19 +02:00
Anton Khirnov
bc87ec0059 doc/protocol: update descriptions of the searching commands 2010-08-31 06:56:54 +02:00
Max Kellermann
917434269c output/httpd: implement "pause"
Send silence to all connected clients while paused, to avoid
connection interruption.
2010-08-31 06:50:14 +02:00
Max Kellermann
a77506ae21 output/httpd: forced flush after 32 kB of input data
Avoid buffer underruns on the streaming client, if the encoder is "too
efficient" (e.g. when encoding silence while paused).
2010-08-31 06:49:06 +02:00
Johan Kiviniemi
ed5d297301 ReplayGain filter: allow gain > 100 %
The ReplayGain filter clamped the gain to max. 100 % even if the
algorithm determined the signal needed a boost. That would result in any
such tracks being played with too low volume, effectively defeating the
purpose of the filter.
2010-08-23 16:34:11 +03:00
Max Kellermann
64dacd175a output_thread: fix race condition after CANCEL command
Clear the notification before finishing the CANCEL command, so the
notify_wait() after that will always wait for the right notification,
sent by audio_output_all_cancel().
2010-08-19 11:05:24 +02:00
Max Kellermann
625e4755d1 notify: add function notify_clear() 2010-08-19 11:03:53 +02:00
Anton Khirnov
92b6ba9eff doc/protocol: mention that 'status' command also returns 'random' 2010-08-15 11:35:21 +02:00
Max Kellermann
68c02fc95a fd_util: add function dup_cloexec()
Unfortunately, there's no "optimized" implementation here.  We can't
use Linux's proprietary system call dup3(), because it would require
us to specify the new descriptor.
2010-08-03 18:03:55 +02:00
Max Kellermann
d18c1b1a0a fd_util: add function recvmsg_cloexec() 2010-08-03 17:51:35 +02:00
Max Kellermann
c980fc653d fd_util: add function socketpair_cloexec() 2010-08-03 17:51:35 +02:00
Avuton Olrich
36782a977a Modify version string to post-release version 0.16~git 2010-07-25 07:44:45 -07:00
Avuton Olrich
676739c426 Modify version string to post-release version 0.15.13~git 2010-07-21 06:40:33 +02:00
129 changed files with 3999 additions and 1226 deletions
.gitignoreINSTALLMakefile.amNEWSautogen.shconfigure.ac
doc
m4
scripts
src
AudioCompress
ape.cape.haudio.caudio_check.haudio_format.haudio_parser.ccmdline.ccommand.cconf.cconf.hdaemon.cdaemon.hdatabase.c
decoder
decoder_control.cdecoder_list.cdecoder_thread.cdirectory.h
encoder
encoder_plugin.hevent_pipe.cevent_pipe.hfd_util.cfd_util.h
filter
icy_server.cinotify_source.c
input
listen.clog.cmain.cmain.hmain_win32.c
mixer
mixer_list.hmpd_error.hnotify.cnotify.h
output
output_all.coutput_control.coutput_control.houtput_init.coutput_internal.houtput_list.coutput_plugin.houtput_thread.cpath.cpcm_byteswap.cpcm_mix.cpermission.cpipe.cpipe.hplayer_thread.cplaylist.c
playlist
playlist_control.cplaylist_database.cplaylist_list.cplaylist_song.cplaylist_state.cplaylist_vector.cplaylist_vector.hpoison.hreplay_gain_ape.creplay_gain_ape.hreplay_gain_config.cserver_socket.cserver_socket.hsig_handlers.csticker.ctag.ctag_ape.ctag_id3.ctimer.ctimer.hupdate.cupdate_walk.czeroconf-avahi.czeroconf-bonjour.c
test

1
.gitignore vendored

@@ -33,6 +33,7 @@ ltmain.sh
missing
mkinstalldirs
mpd
mpd.exe
stamp-h1
tags
*~

@@ -59,6 +59,9 @@ You also need an encoder: either libvorbisenc (ogg), or liblame (mp3).
OpenAL - http://kcat.strangesoft.net/openal.html
Open Audio Library
libffado - http://www.ffado.org/
For FireWire audio devices.
Optional Input Dependencies
---------------------------

@@ -7,8 +7,11 @@ AM_CPPFLAGS += -DSYSTEM_CONFIG_FILE_LOCATION='"$(sysconfdir)/mpd.conf"'
bin_PROGRAMS = src/mpd
noinst_LIBRARIES =
src_mpd_CFLAGS = $(AM_CFLAGS) $(MPD_CFLAGS)
src_mpd_CPPFLAGS = $(AM_CPPFLAGS) \
$(AVAHI_CFLAGS) \
$(LIBWRAP_CFLAGS) \
$(SQLITE_CFLAGS) \
$(ARCHIVE_CFLAGS) \
@@ -19,6 +22,7 @@ src_mpd_CPPFLAGS = $(AM_CPPFLAGS) \
$(FILTER_CFLAGS) \
$(OUTPUT_CFLAGS)
src_mpd_LDADD = $(MPD_LIBS) \
$(AVAHI_LIBS) \
$(LIBWRAP_LDFLAGS) \
$(SQLITE_LIBS) \
$(ARCHIVE_LIBS) \
@@ -34,6 +38,7 @@ mpd_headers = \
src/check.h \
src/notify.h \
src/ack.h \
src/ape.h \
src/audio.h \
src/audio_format.h \
src/audio_check.h \
@@ -111,6 +116,7 @@ mpd_headers = \
src/icy_metadata.h \
src/client.h \
src/client_internal.h \
src/server_socket.h \
src/listen.h \
src/log.h \
src/ls.h \
@@ -136,6 +142,7 @@ mpd_headers = \
src/output/httpd_client.h \
src/output/httpd_internal.h \
src/output/pulse_output_plugin.h \
src/output/winmm_output_plugin.h \
src/page.h \
src/pcm_buffer.h \
src/pcm_utils.h \
@@ -171,6 +178,7 @@ mpd_headers = \
src/playlist/pls_playlist_plugin.h \
src/playlist/xspf_playlist_plugin.h \
src/playlist/asx_playlist_plugin.h \
src/playlist/rss_playlist_plugin.h \
src/playlist/lastfm_playlist_plugin.h \
src/playlist/cue_playlist_plugin.h \
src/playlist/flac_playlist_plugin.h \
@@ -183,6 +191,7 @@ mpd_headers = \
src/refcount.h \
src/replay_gain_config.h \
src/replay_gain_info.h \
src/replay_gain_ape.h \
src/sig_handlers.h \
src/song.h \
src/song_print.h \
@@ -220,7 +229,8 @@ mpd_headers = \
src/archive/iso9660_archive_plugin.h \
src/archive/zzip_archive_plugin.h \
src/input/archive_input_plugin.h \
src/cue/cue_tag.h
src/cue/cue_tag.h\
src/mpd_error.h
src_mpd_SOURCES = \
$(mpd_headers) \
@@ -274,10 +284,12 @@ src_mpd_SOURCES = \
src/client_process.c \
src/client_read.c \
src/client_write.c \
src/server_socket.c \
src/listen.c \
src/log.c \
src/ls.c \
src/main.c \
src/main_win32.c \
src/event_pipe.c \
src/daemon.c \
src/AudioCompress/compress.c \
@@ -406,6 +418,8 @@ TAG_LIBS = \
$(ID3TAG_LIBS)
TAG_SRC = \
src/ape.c \
src/replay_gain_ape.c \
src/tag_ape.c
if HAVE_ID3TAG
@@ -422,7 +436,6 @@ DECODER_CFLAGS = \
$(SNDFILE_CFLAGS) \
$(AUDIOFILE_CFLAGS) \
$(LIBMIKMOD_CFLAGS) \
$(MODPLUG_CFLAGS) \
$(GME_CFLAGS) \
$(SIDPLAY_CFLAGS) \
$(FLUIDSYNTH_CFLAGS) \
@@ -438,7 +451,6 @@ DECODER_LIBS = \
$(FLAC_LIBS) \
$(SNDFILE_LIBS) \
$(AUDIOFILE_LIBS) $(LIBMIKMOD_LIBS) \
$(MODPLUG_LIBS) \
$(GME_LIBS) \
$(SIDPLAY_LIBS) \
$(FLUIDSYNTH_LIBS) \
@@ -511,7 +523,11 @@ DECODER_SRC += src/decoder/mikmod_decoder_plugin.c
endif
if HAVE_MODPLUG
DECODER_SRC += src/decoder/modplug_decoder_plugin.c
libmodplug_decoder_plugin_a_SOURCES = src/decoder/modplug_decoder_plugin.c
libmodplug_decoder_plugin_a_CFLAGS = $(src_mpd_CFLAGS) $(MODPLUG_CFLAGS)
libmodplug_decoder_plugin_a_CPPFLAGS = $(src_mpd_CPPFLAGS)
noinst_LIBRARIES += libmodplug_decoder_plugin.a
DECODER_LIBS += libmodplug_decoder_plugin.a $(MODPLUG_LIBS)
endif
if ENABLE_SIDPLAY
@@ -634,6 +650,7 @@ endif
OUTPUT_CFLAGS = \
$(AO_CFLAGS) \
$(ALSA_CFLAGS) \
$(FFADO_CFLAGS) \
$(JACK_CFLAGS) \
$(OPENAL_CFLAGS) \
$(PULSE_CFLAGS) \
@@ -643,6 +660,7 @@ OUTPUT_LIBS = \
$(LIBWRAP_LDFLAGS) \
$(AO_LIBS) \
$(ALSA_LIBS) \
$(FFADO_LIBS) \
$(JACK_LIBS) \
$(OPENAL_LIBS) \
$(PULSE_LIBS) \
@@ -675,6 +693,10 @@ OUTPUT_SRC += src/output/alsa_plugin.c
MIXER_SRC += src/mixer/alsa_mixer_plugin.c
endif
if ENABLE_FFADO_OUTPUT
OUTPUT_SRC += src/output/ffado_output_plugin.c
endif
if HAVE_AO
OUTPUT_SRC += src/output/ao_plugin.c
endif
@@ -732,8 +754,9 @@ if ENABLE_SOLARIS_OUTPUT
OUTPUT_SRC += src/output/solaris_output_plugin.c
endif
if ENABLE_WIN32_OUTPUT
OUTPUT_SRC += src/output/win32_output_plugin.c
if ENABLE_WINMM_OUTPUT
OUTPUT_SRC += src/output/winmm_output_plugin.c
MIXER_SRC += src/mixer/winmm_mixer_plugin.c
endif
@@ -747,6 +770,7 @@ PLAYLIST_SRC = \
src/playlist/pls_playlist_plugin.c \
src/playlist/xspf_playlist_plugin.c \
src/playlist/asx_playlist_plugin.c \
src/playlist/rss_playlist_plugin.c \
src/playlist_list.c
if ENABLE_LASTFM
@@ -837,6 +861,7 @@ test_run_input_LDADD = $(MPD_LIBS) \
$(INPUT_LIBS) \
$(GLIB_LIBS)
test_run_input_SOURCES = test/run_input.c \
test/stdbin.h \
src/conf.c src/tokenizer.c src/utils.c \
src/tag.c src/tag_pool.c src/tag_save.c \
src/fd_util.c \
@@ -884,6 +909,7 @@ test_run_decoder_LDADD = $(MPD_LIBS) \
$(INPUT_LIBS) $(DECODER_LIBS) \
$(GLIB_LIBS)
test_run_decoder_SOURCES = test/run_decoder.c \
test/stdbin.h \
src/conf.c src/tokenizer.c src/utils.c src/log.c \
src/tag.c src/tag_pool.c \
src/replay_gain_info.c \
@@ -924,6 +950,7 @@ test_run_filter_LDADD = $(MPD_LIBS) \
$(SAMPLERATE_LIBS) \
$(GLIB_LIBS)
test_run_filter_SOURCES = test/run_filter.c \
test/stdbin.h \
src/filter_plugin.c \
src/filter_registry.c \
src/conf.c src/tokenizer.c src/utils.c \
@@ -946,6 +973,7 @@ endif
if ENABLE_ENCODER
noinst_PROGRAMS += test/run_encoder
test_run_encoder_SOURCES = test/run_encoder.c \
test/stdbin.h \
src/conf.c src/tokenizer.c \
src/utils.c \
src/tag.c src/tag_pool.c \
@@ -953,12 +981,15 @@ test_run_encoder_SOURCES = test/run_encoder.c \
src/audio_format.c \
src/audio_parser.c \
$(ENCODER_SRC)
test_run_encoder_CPPFLAGS = $(AM_CPPFLAGS) \
$(ENCODER_CFLAGS)
test_run_encoder_LDADD = $(MPD_LIBS) \
$(ENCODER_LIBS) \
$(GLIB_LIBS)
endif
test_software_volume_SOURCES = test/software_volume.c \
test/stdbin.h \
src/audio_check.c \
src/audio_parser.c \
src/pcm_volume.c
@@ -966,6 +997,7 @@ test_software_volume_LDADD = \
$(GLIB_LIBS)
test_run_normalize_SOURCES = test/run_normalize.c \
test/stdbin.h \
src/audio_check.c \
src/audio_parser.c \
src/AudioCompress/compress.c
@@ -994,6 +1026,7 @@ if HAVE_LIBSAMPLERATE
test_run_convert_SOURCES += src/pcm_resample_libsamplerate.c
endif
test_run_output_CFLAGS = $(AM_CFLAGS) $(MPD_CFLAGS)
test_run_output_CPPFLAGS = $(AM_CPPFLAGS) \
$(ENCODER_CFLAGS) \
$(OUTPUT_CFLAGS)
@@ -1002,6 +1035,7 @@ test_run_output_LDADD = $(MPD_LIBS) \
$(OUTPUT_LIBS) \
$(GLIB_LIBS)
test_run_output_SOURCES = test/run_output.c \
test/stdbin.h \
src/conf.c src/tokenizer.c src/utils.c src/log.c \
src/audio_check.c \
src/audio_format.c \
@@ -1029,6 +1063,7 @@ test_run_output_SOURCES = test/run_output.c \
src/replay_gain_info.c \
src/replay_gain_config.c \
src/fd_util.c \
src/server_socket.c \
$(OUTPUT_SRC)
test_read_mixer_CPPFLAGS = $(AM_CPPFLAGS) \

117
NEWS

@@ -1,4 +1,68 @@
ver 0.16 (20??/??/??)
ver 0.16.4 (2011/09/01)
* don't abort configure when avahi is not found
* auto-detect libmad without pkg-config
* fix memory leaks
* don't resume playback when seeking to another song while paused
* apply follow_inside_symlinks to absolute symlinks
* fix playback discontinuation after seeking
* input:
- curl: limit the receive buffer size
- curl: implement a hard-coded timeout of 10 seconds
* decoder:
- ffmpeg: workaround for semantic API change in recent ffmpeg versions
- flac: validate the sample rate when scanning the tag
- wavpack: obey all decoder commands, stop at CUE track border
* encoder:
- vorbis: don't send end-of-stream on flush
* output:
- alsa: fix SIGFPE when alsa announces a period size of 0
- httpd: don't warn on client disconnect
- osx: don't drain the buffer when closing
- pulse: fix deadlock when resuming the stream
- pulse: fix deadlock when the stream was suspended
ver 0.16.3 (2011/06/04)
* fix assertion failure in audio format mask parser
* fix NULL pointer dereference in playlist parser
* fix playlist files in base music directory
* database: allow directories with just playlists
* decoder:
- ffmpeg: support libavcodec 0.7
ver 0.16.2 (2011/03/18)
* configure.ac:
- fix bashism in tremor test
* decoder:
- tremor: fix configure test
- gme: detect end of song
* encoder:
- vorbis: reset the Ogg stream after flush
* output:
- httpd: fix uninitialized variable
- httpd: include sys/socket.h
- oss: AFMT_S24_PACKED is little-endian
- oss: disable 24 bit playback on FreeBSD
ver 0.16.1 (2011/01/09)
* audio_check: fix parameter in prototype
* add void casts to suppress "result unused" warnings (clang)
* input:
- ffado: disable by default
* decoder:
- mad: work around build failure on Solaris
- resolve modplug vs. libsndfile cflags/headers conflict
* output:
- solaris: add missing parameter to open_cloexec() cal
- osx: fix up audio format first, then apply it to device
* player_thread: discard empty chunks while cross-fading
* player_thread: fix assertion failure due to early seek
* output_thread: fix double lock
ver 0.16 (2010/12/11)
* protocol:
- send song modification time to client
- added "update" idle event
@@ -20,7 +84,9 @@ ver 0.16 (20??/??/??)
* tags:
- added tags "ArtistSort", "AlbumArtistSort"
- id3: revised "performer" tag support
- id3: support multiple values
- ape: MusicBrainz tags
- ape: support multiple values
* decoders:
- don't try a plugin twice (MIME type & suffix)
- don't fall back to "mad" unless no plugin matches
@@ -35,6 +101,8 @@ ver 0.16 (20??/??/??)
- sidplay: support sub-tunes
- sidplay: implemented songlength database
- sidplay: support seeking
- sidplay: play monaural SID tunes in mono
- sidplay: play mus, str, prg, x00 files
- wavpack: activate 32 bit support
- wavpack: allow more than 2 channels
- mp4ff: rename plugin "mp4" to "mp4ff"
@@ -59,8 +127,11 @@ ver 0.16 (20??/??/??)
- jack: support more than two audio channels
- httpd: bind port when output is enabled
- httpd: added name/genre/website configuration
- httpd: implement "pause"
- httpd: bind_to_address support (including IPv6)
- oss: 24 bit support via OSS4
- win32: new output plugin for Windows Wave
- shout, httpd: more responsive to control commands
- wildcards allowed in audio_format configuration
- consistently lock audio output objects
* player:
@@ -83,6 +154,7 @@ ver 0.16 (20??/??/??)
- fall back to track gain if album gain is unavailable
- optionally use hardware mixer to apply replay gain
- added mode "auto"
- parse replay gain from APE tags
* log unused/unknown block parameters
* removed the deprecated "error_file" option
* save state when stopped
@@ -101,6 +173,49 @@ ver 0.16 (20??/??/??)
* added test suite ("make check")
* require GLib 2.12
* added libwrap support
* make single mode 'sticky'
ver 0.15.17 (2011/??/??)
* encoder:
- vorbis: reset the Ogg stream after flush
* decoders:
- vorbis: fix tremor support
ver 0.15.16 (2011/03/13)
* output:
- ao: initialize the ao_sample_format struct
- jack: fix crash with mono playback
* encoders:
- lame: explicitly configure the output sample rate
* update: log all file permission problems
ver 0.15.15 (2010/11/08)
* input:
- rewind: fix assertion failure
* output:
- shout: artist comes first in stream title
ver 0.15.14 (2010/11/06)
* player_thread: fix assertion failure due to wrong music pipe on seek
* output_thread: fix assertion failure due to race condition in OPEN
* input:
- rewind: fix double free bug
* decoders:
- mp4ff, ffmpeg: add extension ".m4b" (audio book)
ver 0.15.13 (2010/10/10)
* output_thread: fix race condition after CANCEL command
* output:
- httpd: fix random data in stream title
- httpd: MIME type audio/ogg for Ogg Vorbis
* input:
- rewind: update MIME not only once
- rewind: enable for MMS
ver 0.15.12 (2010/07/20)

@@ -16,7 +16,7 @@ if test -n "$AM_FORCE_VERSION"
then
AM_VERSIONS="$AM_FORCE_VERSION"
else
AM_VERSIONS='1.10'
AM_VERSIONS='1.11 1.10'
fi
if test -n "$AC_FORCE_VERSION"
then

@@ -1,5 +1,5 @@
AC_PREREQ(2.60)
AC_INIT(mpd, 0.16~alpha2, musicpd-dev-team@lists.sourceforge.net)
AC_INIT(mpd, 0.16.4, musicpd-dev-team@lists.sourceforge.net)
AC_CONFIG_SRCDIR([src/main.c])
AM_INIT_AUTOMAKE([foreign 1.10 dist-bzip2 subdir-objects])
AM_CONFIG_HEADER(config.h)
@@ -13,6 +13,7 @@ dnl Programs
dnl ---------------------------------------------------------------------------
AC_PROG_CC_C99
AC_PROG_CXX
AC_PROG_RANLIB
HAVE_CXX=yes
if test x$CXX = xg++; then
@@ -155,6 +156,10 @@ AC_ARG_ENABLE(documentation,
[build documentation (default: disable)]),,
[enable_documentation=no])
AC_ARG_ENABLE(ffado,
AS_HELP_STRING([--enable-ffado], [enable libffado (FireWire) support]),,
[enable_ffado=no])
AC_ARG_ENABLE(ffmpeg,
AS_HELP_STRING([--enable-ffmpeg],
[enable FFMPEG support]),,
@@ -166,9 +171,9 @@ AC_ARG_ENABLE(fifo,
enable_fifo=yes)
AC_ARG_ENABLE(flac,
AS_HELP_STRING([--disable-flac],
[disable flac support (default: enable)]),,
enable_flac=yes)
AS_HELP_STRING([--enable-flac],
[enable FLAC decoder]),,
enable_flac=auto)
AC_ARG_ENABLE(fluidsynth,
AS_HELP_STRING([--enable-fluidsynth],
@@ -191,9 +196,9 @@ AC_ARG_ENABLE(httpd-output,
[enable_httpd_output=auto])
AC_ARG_ENABLE(id3,
AS_HELP_STRING([--disable-id3],
[disable id3 support (default: enable)]),,
enable_id3=yes)
AS_HELP_STRING([--enable-id3],
[disable id3 support]),,
enable_id3=auto)
AC_ARG_ENABLE(inotify,
AS_HELP_STRING([--disable-inotify],
@@ -348,9 +353,9 @@ AC_ARG_ENABLE(un,
[enable_un=yes])
AC_ARG_ENABLE(vorbis,
AS_HELP_STRING([--disable-vorbis],
[disable Ogg Vorbis support (default: enable)]),,
enable_vorbis=yes)
AS_HELP_STRING([--enable-vorbis],
[enable Ogg Vorbis decoder]),,
enable_vorbis=auto)
AC_ARG_ENABLE(vorbis-encoder,
AS_HELP_STRING([--enable-vorbis-encoder],
@@ -437,7 +442,7 @@ if test x$enable_tcp = xyes; then
fi
case "$host_os" in
mingw* | windows*)
mingw* | windows* | cygwin*)
enable_un=no
;;
esac
@@ -505,13 +510,8 @@ fi
AM_CONDITIONAL(HAVE_CUE, test x$enable_cue = xyes)
dnl -------------------------------- libid3tag --------------------------------
if test x$enable_id3 = xyes; then
PKG_CHECK_MODULES([ID3TAG], [id3tag],,
AC_CHECK_LIB(id3tag, id3_file_open,
[ID3TAG_LIBS="-lid3tag -lz" ID3TAG_CFLAGS=""],
enable_id3=no))
fi
MPD_AUTO_PKG_LIB(id3, ID3TAG, id3tag, id3tag, id3_file_open, [-lid3tag -lz], [],
[id3tag], [libid3tag not found])
if test x$enable_id3 = xyes; then
AC_DEFINE(HAVE_ID3TAG, 1, [Define to use id3tag])
fi
@@ -525,36 +525,39 @@ dnl ---------------------------------------------------------------------------
dnl --------------------------------- zeroconf --------------------------------
case $with_zeroconf in
no|avahi|bonjour)
no|bonjour)
enable_avahi=no
;;
avahi)
enable_avahi=yes
;;
*)
with_zeroconf=auto
enable_avahi=auto
;;
esac
MPD_AUTO_PKG(avahi, AVAHI, [avahi-client avahi-glib],
[avahi client library], [avahi client+glib not found])
if test x$enable_avahi = xyes; then
AC_DEFINE([HAVE_AVAHI], 1, [Define to enable Avahi Zeroconf support])
with_zeroconf=avahi
fi
AM_CONDITIONAL(HAVE_AVAHI, test x$enable_avahi = xyes)
enable_bounjour=no
if test x$with_zeroconf != xno; then
if test x$with_zeroconf = xavahi || test x$with_zeroconf = xauto; then
PKG_CHECK_MODULES([AVAHI], [avahi-client avahi-glib],
[found_avahi=1;AC_DEFINE([HAVE_AVAHI], 1, [Define to enable Avahi Zeroconf support])]
MPD_LIBS="$MPD_LIBS $AVAHI_LIBS" MPD_CFLAGS="$MPD_CFLAGS $AVAHI_CFLAGS",
[found_avahi=0])
fi
if test x$found_avahi = x1; then
with_zeroconf=avahi
elif test x$with_zeroconf = xavahi; then
AC_MSG_ERROR([Avahi support requested but not found])
fi
if test x$with_zeroconf = xbonjour || test x$with_zeroconf = xauto; then
AC_CHECK_HEADER(dns_sd.h,
[found_bonjour=1;AC_DEFINE([HAVE_BONJOUR], 1, [Define to enable Bonjour Zeroconf support])],
[found_bonjour=0])
[enable_bonjour=yes;AC_DEFINE([HAVE_BONJOUR], 1, [Define to enable Bonjour Zeroconf support])])
AC_CHECK_LIB(dns_sd, DNSServiceRegister,
MPD_LIBS="$MPD_LIBS -ldns_sd")
fi
if test x$found_bonjour = x1; then
if test x$enable_bonjour = xyes; then
with_zeroconf=bonjour
elif test x$with_zeroconf = xbonjour; then
AC_MSG_ERROR([Bonjour support requested but not found])
@@ -569,7 +572,6 @@ if test x$with_zeroconf != xno; then
fi
AM_CONDITIONAL(HAVE_ZEROCONF, test x$with_zeroconf != xno)
AM_CONDITIONAL(HAVE_AVAHI, test x$with_zeroconf = xavahi)
AM_CONDITIONAL(HAVE_BONJOUR, test x$with_zeroconf = xbonjour)
dnl ---------------------------------------------------------------------------
@@ -629,9 +631,6 @@ if test x$enable_lastfm = xyes; then
fi
AM_CONDITIONAL(ENABLE_LASTFM, test x$enable_lastfm = xyes)
dnl ---------------------------------- libogg ---------------------------------
PKG_CHECK_MODULES(OGG, [ogg], enable_ogg=yes, enable_ogg=no)
dnl ---------------------------------- libmms ---------------------------------
MPD_AUTO_PKG(mms, MMS, [libmms >= 0.4],
[libmms mms:// protocol support], [libmms not found])
@@ -746,10 +745,12 @@ fi
AM_CONDITIONAL(HAVE_FFMPEG, test x$enable_ffmpeg = xyes)
dnl ----------------------------------- FLAC ----------------------------------
MPD_AUTO_PKG(flac, FLAC, [flac >= 1.1],
[FLAC decoder], [libFLAC not found])
if test x$enable_flac = xyes; then
PKG_CHECK_MODULES(FLAC, [flac >= 1.1],
AC_DEFINE(HAVE_FLAC, 1, [Define for FLAC support]),
enable_flac=no)
AC_DEFINE(HAVE_FLAC, 1, [Define for FLAC support])
oldcflags="$CFLAGS"
oldlibs="$LIBS"
@@ -764,12 +765,10 @@ if test x$enable_flac = xyes; then
LIBS="$oldlibs"
if test x$enable_oggflac = xflac; then
if test x$enable_ogg = xyes; then
FLAC_LIBS="${FLAC_LIBS} -logg"
else
enable_oggflac=yes
AC_MSG_WARN("FLAC has the ogg API built in, but couldn't find ogg. Disabling oggflac.")
fi
PKG_CHECK_MODULES(OGG, [ogg],
[FLAC_LIBS="${FLAC_LIBS} ${OGG_LIBS}" FLAC_CFLAGS="${FLAC_CFLAGS} ${OGG_CFLAGS}"],
[enable_oggflac=yes;
AC_MSG_WARN("FLAC has the ogg API built in, but couldn't find ogg. Disabling oggflac.")])
fi
fi
@@ -795,7 +794,8 @@ if test x$enable_gme = xyes; then
fi
dnl ---------------------------------- libmad ---------------------------------
MPD_AUTO_PKG(mad, MAD, [mad],
MPD_AUTO_PKG_LIB(mad, MAD, [mad],
mad, mad_stream_init, [-lmad], [],
[libmad MP3 decoder plugin], [libmad not found])
if test x$enable_mad = xyes; then
AC_DEFINE(HAVE_MAD, 1, [Define to use libmad])
@@ -843,15 +843,6 @@ if test x$enable_modplug = xyes; then
fi
AM_CONDITIONAL(HAVE_MODPLUG, test x$enable_modplug = xyes)
dnl --------------------------- sndfile/modplug test --------------------------
if test x$enable_sndfile = xauto && test x$enable_modplug = xyes; then
dnl If modplug is enabled, enable sndfile only if explicitly
dnl requested - modplug's modplug/sndfile.h is known to
dnl conflict with libsndfile's sndfile.h.
AC_MSG_NOTICE([disabling libsndfile auto-detection, because the modplug decoder is enabled])
enable_sndfile=no
fi
dnl -------------------------------- libsndfile -------------------------------
dnl See above test, which may disable this.
MPD_AUTO_PKG(sndfile, SNDFILE, [sndfile],
@@ -914,13 +905,13 @@ AM_CONDITIONAL(HAVE_MPCDEC, test x$enable_mpc = xyes)
dnl -------------------------------- Ogg Tremor -------------------------------
if test x$with_tremor = xyes || test x$with_tremor = xno; then
use_tremor="$with_tremor"
enable_tremor="$with_tremor"
else
tremor_prefix="$with_tremor"
use_tremor=yes
enable_tremor=yes
fi
if test x$use_tremor = xyes; then
if test x$enable_tremor = xyes; then
if test "x$tremor_libraries" != "x" ; then
TREMOR_LIBS="-L$tremor_libraries"
elif test "x$tremor_prefix" != "x" ; then
@@ -936,13 +927,19 @@ if test x$use_tremor = xyes; then
ac_save_LIBS="$LIBS"
CFLAGS="$CFLAGS $TREMOR_CFLAGS"
LIBS="$LIBS $TREMOR_LIBS"
AC_CHECK_LIB(vorbisidec,ov_read,enable_vorbis=yes,enable_vorbis=no;
AC_CHECK_LIB(vorbisidec,ov_read,,enable_tremor=no;
AC_MSG_WARN([vorbisidec lib needed for ogg support with tremor -- disabling ogg support]))
CFLAGS="$ac_save_CFLAGS"
LIBS="$ac_save_LIBS"
fi
if test x$enable_tremor = xyes; then
AC_DEFINE(HAVE_TREMOR,1,
[Define to use tremor (libvorbisidec) for ogg support])
AC_DEFINE(ENABLE_VORBIS_DECODER, 1, [Define for Ogg Vorbis support])
else
TREMOR_CFLAGS=
TREMOR_LIBS=
fi
AC_SUBST(TREMOR_CFLAGS)
@@ -951,7 +948,7 @@ AC_SUBST(TREMOR_LIBS)
dnl --------------------------------- OggFLAC ---------------------------------
dnl OggFLAC must go after Ogg Tremor
if test x$use_tremor = xyes && test $xenable_oggflac = xyes; then
if test x$enable_tremor = xyes && test x$enable_oggflac = xyes; then
AC_MSG_WARN([disabling OggFLAC support because it is incompatible with tremor])
enable_oggflac=no
fi
@@ -968,18 +965,21 @@ fi
AM_CONDITIONAL(HAVE_OGGFLAC, test x$enable_oggflac = xyes)
dnl -------------------------------- Ogg Vorbis -------------------------------
if test x$enable_tremor != xno && test x$enable_vorbis = xyes; then
if test x$enable_ogg = xyes; then
PKG_CHECK_MODULES(VORBIS, [vorbis vorbisfile],
AC_DEFINE(ENABLE_VORBIS_DECODER, 1, [Define for Ogg Vorbis support]),
enable_vorbis=no)
else
AC_MSG_WARN(["Ogg not detected, could not enable Vorbis."])
enable_vorbis=no
if test x$enable_tremor = xyes; then
if test x$enable_vorbis = xyes; then
AC_MSG_WARN(["OggTremor detected, could not enable Vorbis."])
fi
enable_vorbis=no
fi
AM_CONDITIONAL(ENABLE_VORBIS_DECODER, test x$enable_vorbis = xyes)
MPD_AUTO_PKG(vorbis, VORBIS, [vorbis vorbisfile ogg],
[Ogg Vorbis decoder], [libvorbis not found])
if test x$enable_vorbis = xyes; then
AC_DEFINE(ENABLE_VORBIS_DECODER, 1, [Define for Ogg Vorbis support])
fi
AM_CONDITIONAL(ENABLE_VORBIS_DECODER, test x$enable_vorbis = xyes || test x$enable_tremor = xyes)
dnl --------------------------------- sidplay ---------------------------------
found_sidplay=$HAVE_CXX
@@ -1065,6 +1065,7 @@ if
test x$enable_mpg123 = xno &&
test x$enable_oggflac = xno &&
test x$enable_sidplay = xno &&
test x$enable_tremor = xno &&
test x$enable_vorbis = xno &&
test x$enable_wavpack = xno &&
test x$enable_wildmidi = xno &&
@@ -1073,7 +1074,7 @@ if
fi
AM_CONDITIONAL(HAVE_OGG_COMMON,
test x$enable_vorbis = xyes || test x$enable_oggflac = xyes || test x$enable_flac = xyes)
test x$enable_vorbis = xyes || test x$enable_tremor = xyes || test x$enable_oggflac = xyes || test x$enable_flac = xyes)
AM_CONDITIONAL(HAVE_FLAC_COMMON,
test x$enable_flac = xyes || test x$enable_oggflac = xyes)
@@ -1199,6 +1200,17 @@ fi
AM_CONDITIONAL(HAVE_ALSA, test x$enable_alsa = xyes)
dnl ----------------------------------- FFADO ---------------------------------
MPD_AUTO_PKG(ffado, FFADO, [libffado],
[libffado output plugin], [libffado not found])
if test x$enable_ffado = xyes; then
AC_DEFINE(ENABLE_FFADO_OUTPUT, 1, [Define to enable the libffado output plugin])
fi
AM_CONDITIONAL(ENABLE_FFADO_OUTPUT, test x$enable_ffado = xyes)
dnl ----------------------------------- FIFO ----------------------------------
if test x$enable_fifo = xyes; then
AC_CHECK_FUNC([mkfifo],
@@ -1369,26 +1381,27 @@ esac
AM_CONDITIONAL(ENABLE_SOLARIS_OUTPUT, test x$enable_solaris_output = xyes)
dnl --------------------------------- Solaris ---------------------------------
dnl --------------------------------- WinMM ---------------------------------
case "$host_os" in
mingw32* | windows*)
AC_DEFINE(ENABLE_WIN32_OUTPUT, 1, [Define to enable WIN32 wave support])
enable_win32_output=yes
AC_DEFINE(ENABLE_WINMM_OUTPUT, 1, [Define to enable WinMM support])
enable_winmm_output=yes
MPD_LIBS="$MPD_LIBS -lwinmm"
;;
*)
enable_win32_output=no
enable_winmm_output=no
;;
esac
AM_CONDITIONAL(ENABLE_WIN32_OUTPUT, test x$enable_win32_output = xyes)
AM_CONDITIONAL(ENABLE_WINMM_OUTPUT, test x$enable_winmm_output = xyes)
dnl --------------------- Post Audio Output Plugins Tests ---------------------
if
test x$enable_alsa = xno &&
test x$enable_ao = xno &&
test x$enable_ffado = xno &&
test x$enable_fifo = xno &&
test x$enable_httpd_output = xno &&
test x$enable_jack = xno &&
@@ -1401,7 +1414,7 @@ if
test x$enable_recorder_output = xno &&
test x$enable_shout = xno &&
test x$enable_solaris_output = xno &&
test x$enable_win32_output = xno &&
test x$enable_winmm_output = xno &&
AC_MSG_ERROR([No Audio Output types configured!])
fi
@@ -1473,23 +1486,23 @@ dnl ---------------------------------------------------------------------------
echo ''
echo '########### MPD CONFIGURATION ############'
echo -ne '\nArchive support:\n\t'
printf '\nArchive support:\n\t'
results(bzip2,[bzip2])
results(iso9660,[ISO9660])
results(zzip,[ZIP])
if test x$with_zeroconf != xno; then
echo -ne '\nAutodiscovery support:\n\t'
printf '\nAutodiscovery support:\n\t'
results(avahi, [Avahi])
results(bonjour, [Bonjour])
fi
echo -ne '\nClient support:\n\t'
printf '\nClient support:\n\t'
results(ipv6, "IPv6")
results(tcp, "TCP")
results(un,[UNIX Domain Sockets])
echo -ne '\nFile format support:\n\t'
printf '\nFile format support:\n\t'
results(aac, [AAC])
results(sidplay, [C64 SID])
results(ffmpeg, [FFMPEG])
@@ -1497,7 +1510,7 @@ results(flac, [FLAC])
results(fluidsynth, [FluidSynth])
results(gme, [GME])
results(sndfile, [libsndfile])
echo -ne '\n\t'
printf '\n\t'
results(mikmod, [MikMod])
results(modplug, [MODPLUG])
results(mad, [MAD])
@@ -1505,46 +1518,47 @@ results(mpg123, [MPG123])
results(mp4, [MP4])
results(mpc, [Musepack])
results(oggflac, [OggFLAC], flac)
echo -ne '\n\t'
results(with_tremor, [OggTremor])
printf '\n\t'
results(tremor, [OggTremor])
results(vorbis, [OggVorbis])
results(audiofile, [WAVE])
results(wavpack, [WavPack])
results(wildmidi, [WildMidi])
echo -en '\nOther features:\n\t'
printf '\nOther features:\n\t'
results(lsr, [libsamplerate])
results(inotify, [inotify])
results(sqlite, [SQLite])
echo -en '\nMetadata support:\n\t'
printf '\nMetadata support:\n\t'
results(cue,[cue])
results(id3,[ID3])
echo -en '\nPlayback support:\n\t'
printf '\nPlayback support:\n\t'
results(alsa,ALSA)
results(ffado,FFADO)
results(fifo,FIFO)
results(recorder_output,[File Recorder])
results(httpd_output,[HTTP Daemon])
results(jack,[JACK])
results(ao,[libao])
results(oss,[OSS])
echo -ne '\n\t'
printf '\n\t'
results(openal,[OpenAL])
results(osx, [OS X])
results(pipe_output, [Pipeline])
results(pulse, [PulseAudio])
results(mvp, [Media MVP])
results(shout, [SHOUTcast])
echo -ne '\n\t'
printf '\n\t'
results(solaris, [Solaris])
results(win32_output, [WIN32 wave])
results(winmm_output, [WinMM])
if
test x$enable_shout = xyes ||
test x$enable_recorder = xyes ||
test x$enable_httpd_output = xyes; then
echo -en '\nStreaming encoder support:\n\t'
printf '\nStreaming encoder support:\n\t'
results(flac_encoder, [FLAC])
results(lame_encoder, [LAME])
results(vorbis_encoder, [Ogg Vorbis])
@@ -1552,19 +1566,14 @@ if
results(wave_encoder, [WAVE])
fi
echo -en '\nStreaming support:\n\t'
printf '\nStreaming support:\n\t'
results(curl,[CURL])
results(lastfm,[Last.FM])
results(mms,[MMS])
echo -ne '\n\n##########################################\n\n'
printf '\n\n##########################################\n\n'
if test x$enable_sndfile = xyes && test x$enable_modplug = xyes; then
AC_MSG_WARN([compilation may fail, because libmodplug conflicts with libsndfile])
AC_MSG_WARN([libmodplug ships modplug/sndfile.h, which hides libsndfile's sndfile.h])
fi
echo -ne 'Generating files needed for compilation\n'
echo 'Generating files needed for compilation'
dnl ---------------------------------------------------------------------------
dnl Generate files

@@ -57,7 +57,7 @@
Some example code:
</para>
<programlisting lang="C">static inline bool
<programlisting lang="C">static inline int
foo(const char *abc, int xyz)
{
if (abc == NULL) {

@@ -260,6 +260,7 @@ input {
# name "My HTTP Stream"
# encoder "vorbis" # optional, vorbis or lame
# port "8000"
# bind_to_address "0.0.0.0" # optional, IPv4 or IPv6
## quality "5.0" # do not define if bitrate is defined
# bitrate "128" # do not define if quality is defined
# format "44100:16:1"

@@ -8,18 +8,54 @@
<title>General protocol syntax</title>
<section>
<title>Requests</title>
<title>Protocol overview</title>
<para>
If arguments contain spaces, they should be surrounded by double quotation
marks.
The MPD command protocol exchanges line-based text records
between client and server over TCP. Once the client is
connected to the server, they conduct a conversation until the
client closes the connection. The conversation flow is always
initiated by the client.
</para>
<para>
The client transmits a command sequence, terminated by the
newline character <constant>\n</constant>. The server will
respond with one or more lines, the last of which will be a
completion code.
</para>
<para>
When the client connects to the server, the server will answer
with the following line:
<synopsis>OK MPD version</synopsis>
where <varname>version</varname> is a version identifier such as
0.12.2. This version identifier is the version of the protocol
spoken, not the real version of the daemon. (There is no way to
retrieve this real version identifier from the connection.)
</para>
</section>
<section>
<title>Requests</title>
<cmdsynopsis>
<command>COMMAND</command>
<arg rep="repeat"><replaceable>ARG</replaceable></arg>
</cmdsynopsis>
<para>
If arguments contain spaces, they should be surrounded by double
quotation marks.
</para>
<para>
Argument strings are separated from the command and any other
arguments by linear white-space (' ' or '\t').
</para>
<para>
All data between the client and the server is encoded in
UTF-8. (Note: In UTF-8 all standard ansi characters, 0-127 are
@@ -38,13 +74,97 @@
<title>Responses</title>
<para>
A command returns <returnvalue>OK</returnvalue> on completion
or <returnvalue>ACK some error</returnvalue> on failure.
These denote the end of command execution.
A command returns <returnvalue>OK</returnvalue> on completion or
<returnvalue>ACK some error</returnvalue> on failure. These
denote the end of command execution.
</para>
<section>
<title>Failure responses</title>
<para>
The nature of the error can be gleaned from the information
that follows the <returnvalue>ACK</returnvalue>.
<returnvalue>ACK</returnvalue> lines are of the form:
<synopsis>ACK [error@command_listNum] {current_command} message_text\n</synopsis>
These responses are generated by a call to
<function>commandError</function>. They contain four separate
terms. Let's look at each of them:
<itemizedlist>
<listitem>
<para>
<returnvalue>error</returnvalue>: numeric value of one
of the <constant>ACK_ERROR</constant> constants defined
in <filename>ack.h</filename>.
</para>
</listitem>
<listitem>
<para>
<returnvalue>command_listNum</returnvalue>:
offset of the command that caused the error in a <link
linkend="command_lists">Command List</link>.
An error will always cause a command list to terminate
at the command that causes the error.
</para>
</listitem>
<listitem>
<para>
<returnvalue>current_command</returnvalue>:
name of the command, in a <link
linkend="command_lists">Command List</link>,
that was executing when the error occurred.
</para>
</listitem>
<listitem>
<para>
<returnvalue>message_text</returnvalue>:
some (hopefully) informative text that describes the
nature of the error.
</para>
</listitem>
</itemizedlist>
</para>
<example>
<title>foo</title>
<para>
An example might help. Consider the following sequence
sent from the client to the server.
<synopsis>
command_list_begin
volume 86
play 10240
status
command_list_end
</synopsis>
</para>
<para>
The server responds with:
<returnvalue>
ACK [50@1] {play} song doesn't exist: "10240"
</returnvalue>
</para>
<para>
This tells us that the play command, which was the
second in the list (the first or only command is
numbered 0), failed with error 50. The number 50
translates to <constant>ACK_ERROR_NO_EXIST</constant>--the
song doesn't exist. This is reiterated by the message text
which also tells us which song doesn't exist.
</para>
</example>
</section>
</section>
<section>
<section id="command_lists">
<title>Command lists</title>
<para>
@@ -240,6 +360,12 @@
<returnvalue>0 or 1</returnvalue>
</para>
</listitem>
<listitem>
<para>
<varname>random</varname>:
<returnvalue>0 or 1</returnvalue>
</para>
</listitem>
<listitem>
<para>
<varname>single</varname>:
@@ -1208,16 +1334,18 @@ OK
<command>find</command>
<arg choice="req"><replaceable>TYPE</replaceable></arg>
<arg choice="req"><replaceable>WHAT</replaceable></arg>
<arg choice="opt"><replaceable>...</replaceable></arg>
</cmdsynopsis>
</term>
<listitem>
<para>
Finds songs in the db that are exactly
<varname>WHAT</varname>. <varname>TYPE</varname> should
be <parameter>album</parameter>,
<parameter>artist</parameter>, or
<parameter>title</parameter>. <varname>WHAT</varname>
is what to find.
<varname>WHAT</varname>. <varname>TYPE</varname> can
be any tag supported by MPD, or one of the two special
parameters — <parameter>file</parameter> to search by
full path (relative to database root), and
<parameter>any</parameter> to match against all
available tags. <varname>WHAT</varname> is what to find.
</para>
</listitem>
</varlistentry>
@@ -1227,14 +1355,14 @@ OK
<command>findadd</command>
<arg choice="req"><replaceable>TYPE</replaceable></arg>
<arg choice="req"><replaceable>WHAT</replaceable></arg>
<arg choice="opt"><replaceable>...</replaceable></arg>
</cmdsynopsis>
</term>
<listitem>
<para>
Finds songs in the db that are exactly
<varname>WHAT</varname> and adds them to current playlist.
<varname>TYPE</varname> can be any tag supported by MPD.
<varname>WHAT</varname> is what to find.
Parameters have the same meaning as for <command>find</command>.
</para>
</listitem>
</varlistentry>
@@ -1249,7 +1377,8 @@ OK
<listitem>
<para>
Lists all tags of the specified type.
<varname>TYPE</varname> should be album or artist.
<varname>TYPE</varname> can be any tag supported by MPD or
<parameter>file</parameter>.
</para>
<para>
<varname>ARTIST</varname> is an optional parameter when
@@ -1312,17 +1441,15 @@ OK
<command>search</command>
<arg choice="req"><replaceable>TYPE</replaceable></arg>
<arg choice="req"><replaceable>WHAT</replaceable></arg>
<arg choice="opt"><replaceable>...</replaceable></arg>
</cmdsynopsis>
</term>
<listitem>
<para>
Searches for any song that contains
<varname>WHAT</varname>. <varname>TYPE</varname> can be
<parameter>title</parameter>,
<parameter>artist</parameter>,
<parameter>album</parameter> or
<parameter>filename</parameter>. Search is not case
sensitive.
<varname>WHAT</varname>. Parameters have the same meaning
as for <command>find</command>, except that search is not
case sensitive.
</para>
</listitem>
</varlistentry>

@@ -446,7 +446,7 @@ cd mpd-version</programlisting>
</para>
<para>
To configure a filter, add a
To configure a playlist plugin, add a
<varname>playlist_plugin</varname> block to
<filename>mpd.conf</filename>:
</para>
@@ -788,6 +788,43 @@ cd mpd-version</programlisting>
</para>
</section>
<section>
<title><varname>ffado</varname></title>
<para>
The <varname>ffado</varname> plugin connects to FireWire
audio devices via <filename>libffado</filename>.
</para>
<para>
Warning: this plugin was not tested successfully. I just
couldn't keep libffado2 from crashing. Use at your own
risk.
</para>
<informaltable>
<tgroup cols="2">
<thead>
<row>
<entry>Setting</entry>
<entry>Description</entry>
</row>
</thead>
<tbody>
<row>
<entry>
<varname>device</varname>
<parameter>NAME</parameter>
</entry>
<entry>
Sets the device which should be used, e.g. "hw:0".
</entry>
</row>
</tbody>
</tgroup>
</informaltable>
</section>
<section>
<title><varname>jack</varname></title>
@@ -914,8 +951,17 @@ cd mpd-version</programlisting>
<parameter>P</parameter>
</entry>
<entry>
Binds the HTTP server to the specified port (on all
interfaces).
Binds the HTTP server to the specified port.
</entry>
</row>
<row>
<entry>
<varname>bind_to_address</varname>
<parameter>ADDR</parameter>
</entry>
<entry>
Binds the HTTP server to the specified address (IPv4 or
IPv6). Multiple addresses in parallel are not supported.
</entry>
</row>
<row>

@@ -58,7 +58,7 @@ if test x$enable_aac = xyes; then
fi
if test x$enable_aac = xyes; then
AC_MSG_CHECKING(that FAAD2 uses buffer and bufferlen)
AC_COMPILE_IFELSE([
AC_COMPILE_IFELSE([AC_LANG_SOURCE([
#include <faad.h>
int main() {
@@ -82,9 +82,9 @@ int main() {
return 0;
}
],[AC_MSG_RESULT(yes);AC_DEFINE(HAVE_FAAD_BUFLEN_FUNCS,1,[Define if FAAD2 uses buflen in function calls])],[AC_MSG_RESULT(no);
])],[AC_MSG_RESULT(yes);AC_DEFINE(HAVE_FAAD_BUFLEN_FUNCS,1,[Define if FAAD2 uses buflen in function calls])],[AC_MSG_RESULT(no);
AC_MSG_CHECKING(that FAAD2 can even be used)
AC_COMPILE_IFELSE([
AC_COMPILE_IFELSE([AC_LANG_SOURCE([
#include <faad.h>
int main() {
@@ -113,7 +113,7 @@ int main() {
return 0;
}
],AC_MSG_RESULT(yes),[AC_MSG_RESULT(no);enable_aac=no])
])],AC_MSG_RESULT(yes),[AC_MSG_RESULT(no);enable_aac=no])
])
fi
if test x$enable_aac = xyes; then
@@ -136,7 +136,7 @@ if test x$enable_aac = xyes; then
CPPFLAGS=$CFLAGS
AC_MSG_CHECKING(for broken libfaad headers)
AC_COMPILE_IFELSE([
AC_COMPILE_IFELSE([AC_LANG_SOURCE([
#include <faad.h>
#include <stddef.h>
#include <stdint.h>
@@ -148,7 +148,7 @@ int main() {
faacDecInit2(NULL, NULL, 0, &sample_rate, &channels);
return 0;
}
],
])],
[AC_MSG_RESULT(correct)],
[AC_MSG_RESULT(broken);
AC_DEFINE(HAVE_FAAD_LONG, 1, [Define if faad.h uses the broken "unsigned long" pointers])])

@@ -63,3 +63,18 @@ AC_DEFUN([MPD_AUTO_PKG], [
MPD_AUTO_RESULT([$1], [$4], [$5])
])
dnl Check with pkg-config first, fall back to AC_CHECK_LIB.
dnl
dnl Parameters: varname1, varname2, pkgname, libname, symname, libs, cflags, description, errmsg
AC_DEFUN([MPD_AUTO_PKG_LIB], [
if eval "test x`echo '$'enable_$1` != xno"; then
PKG_CHECK_MODULES([$2], [$3],
[eval "found_$1=yes"],
AC_CHECK_LIB($4, $5,
[eval "found_$1=yes $2_LIBS='$6' $2_CFLAGS='$7'"],
[eval "found_$1=no"]))
fi
MPD_AUTO_RESULT([$1], [$8], [$9])
])

@@ -4,9 +4,9 @@ AC_DEFUN([MPD_CHECK_FLAG],[
[mpd_check_cflag_$var],[
save_CFLAGS="$CFLAGS"
CFLAGS="$CFLAGS $1"
AC_COMPILE_IFELSE([
AC_COMPILE_IFELSE([AC_LANG_SOURCE([
int main(void) { return 0; }
], [ eval "mpd_check_cflag_$var=yes"
])], [ eval "mpd_check_cflag_$var=yes"
], [ eval "mpd_check_cflag_$var=no" ])
CFLAGS="$save_CFLAGS"
])

@@ -1,19 +1,19 @@
AC_DEFUN([results], [
dnl This is a hack to allow "with" names, otherwise "enable".
num=`expr match $1 'with'`
num=`expr $1 : 'with'`
if test "$num" != "0"; then
var="`echo '$'$1`"
else
var="`echo '$'enable_$1`"
fi
echo -n '('
printf '('
if eval "test x$var = xyes"; then
echo -n '+'
printf '+'
elif test -n "$3" && eval "test x$var = x$3"; then
echo -n '+'
printf '+'
else
echo -n '-'
printf '-'
fi
echo -n "$2) "
printf '%s) ' "$2"
])

@@ -3,7 +3,7 @@ PWD=`pwd`
## If we're not in the scripts directory
## assume the base directory.
if test "`basename $PWD`" == "scripts"; then
if test "`basename $PWD`" = "scripts"; then
cd ../
else
MYOLDPWD=`pwd`
@@ -18,7 +18,7 @@ fi
make
make dist
if test "`basename $PWD`" == "scripts"; then
if test "`basename $PWD`" = "scripts"; then
cd contrib/
else
cd $MYOLDPWD

@@ -16,16 +16,16 @@
struct Compressor {
//! The compressor's preferences
struct CompressorConfig prefs;
//! History of the peak values
int *peaks;
//! History of the gain values
int *gain;
//! History of clip amounts
int *clipped;
unsigned int pos;
unsigned int bufsz;
};
@@ -41,9 +41,9 @@ struct Compressor *Compressor_new(unsigned int history)
obj->peaks = obj->gain = obj->clipped = NULL;
obj->bufsz = 0;
obj->pos = 0;
Compressor_setHistory(obj, history);
return obj;
}
@@ -70,7 +70,7 @@ void Compressor_setHistory(struct Compressor *obj, unsigned int history)
{
if (!history)
history = BUCKETS;
obj->peaks = resizeArray(obj->peaks, history, obj->bufsz);
obj->gain = resizeArray(obj->gain, history, obj->bufsz);
obj->clipped = resizeArray(obj->clipped, history, obj->bufsz);
@@ -82,7 +82,7 @@ struct CompressorConfig *Compressor_getConfig(struct Compressor *obj)
return &obj->prefs;
}
void Compressor_Process_int16(struct Compressor *obj, int16_t *audio,
void Compressor_Process_int16(struct Compressor *obj, int16_t *audio,
unsigned int count)
{
struct CompressorConfig *prefs = Compressor_getConfig(obj);
@@ -97,7 +97,7 @@ void Compressor_Process_int16(struct Compressor *obj, int16_t *audio,
int *clipped = obj->clipped + slot;
unsigned int ramp = count;
int delta;
ap = audio;
for (i = 0; i < count; i++)
{
@@ -124,15 +124,15 @@ void Compressor_Process_int16(struct Compressor *obj, int16_t *audio,
//! Determine target gain
newGain = (1 << 10)*prefs->target/peakVal;
//! Adjust the gain with inertia from the previous gain value
newGain = (curGain*((1 << prefs->smooth) - 1) + newGain)
newGain = (curGain*((1 << prefs->smooth) - 1) + newGain)
>> prefs->smooth;
//! Make sure it's no more than the maximum gain value
if (newGain > (prefs->maxgain << 10))
newGain = prefs->maxgain << 10;
//! Make sure it's no less than 1:1
if (newGain < (1 << 10))
newGain = 1 << 10;
@@ -144,7 +144,7 @@ void Compressor_Process_int16(struct Compressor *obj, int16_t *audio,
//! Truncate the ramp time
ramp = peakPos;
}
//! Record the new gain
obj->gain[slot] = newGain;

115
src/ape.c Normal file

@@ -0,0 +1,115 @@
/*
* Copyright (C) 2003-2010 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "config.h"
#include "ape.h"
#include <glib.h>
#include <stdint.h>
#include <assert.h>
#include <stdio.h>
#include <string.h>
struct ape_footer {
unsigned char id[8];
uint32_t version;
uint32_t length;
uint32_t count;
unsigned char flags[4];
unsigned char reserved[8];
};
static bool
ape_scan_internal(FILE *fp, tag_ape_callback_t callback, void *ctx)
{
/* determine if file has an apeV2 tag */
struct ape_footer footer;
if (fseek(fp, -(long)sizeof(footer), SEEK_END) ||
fread(&footer, 1, sizeof(footer), fp) != sizeof(footer) ||
memcmp(footer.id, "APETAGEX", sizeof(footer.id)) != 0 ||
GUINT32_FROM_LE(footer.version) != 2000)
return false;
/* find beginning of ape tag */
size_t remaining = GUINT32_FROM_LE(footer.length);
if (remaining <= sizeof(footer) + 10 ||
/* refuse to load more than one megabyte of tag data */
remaining > 1024 * 1024 ||
fseek(fp, -(long)remaining, SEEK_END))
return false;
/* read tag into buffer */
remaining -= sizeof(footer);
assert(remaining > 10);
char *buffer = g_malloc(remaining);
if (fread(buffer, 1, remaining, fp) != remaining) {
g_free(buffer);
return false;
}
/* read tags */
unsigned n = GUINT32_FROM_LE(footer.count);
const char *p = buffer;
while (n-- && remaining > 10) {
size_t size = GUINT32_FROM_LE(*(const uint32_t *)p);
p += 4;
remaining -= 4;
unsigned long flags = GUINT32_FROM_LE(*(const uint32_t *)p);
p += 4;
remaining -= 4;
/* get the key */
const char *key = p;
while (remaining > size && *p != '\0') {
p++;
remaining--;
}
p++;
remaining--;
/* get the value */
if (remaining < size)
break;
if (!callback(flags, key, p, size, ctx))
break;
p += size;
remaining -= size;
}
g_free(buffer);
return true;
}
bool
tag_ape_scan(const char *path_fs, tag_ape_callback_t callback, void *ctx)
{
FILE *fp;
fp = fopen(path_fs, "rb");
if (fp == NULL)
return false;
bool success = ape_scan_internal(fp, callback, ctx);
fclose(fp);
return success;
}

42
src/ape.h Normal file

@@ -0,0 +1,42 @@
/*
* Copyright (C) 2003-2010 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef MPD_APE_H
#define MPD_APE_H
#include "check.h"
#include <stdbool.h>
#include <stddef.h>
typedef bool (*tag_ape_callback_t)(unsigned long flags, const char *key,
const char *value, size_t value_length,
void *ctx);
/**
* Scans the APE tag values from a file.
*
* @param path_fs the path of the file in filesystem encoding
* @return false if the file could not be opened or if no APE tag is
* present
*/
bool
tag_ape_scan(const char *path_fs, tag_ape_callback_t callback, void *ctx);
#endif

@@ -25,6 +25,7 @@
#include "output_plugin.h"
#include "output_all.h"
#include "conf.h"
#include "mpd_error.h"
#include <glib.h>
@@ -52,6 +53,7 @@ void initAudioConfig(void)
ret = audio_format_parse(&configured_audio_format, param->value,
true, &error);
if (!ret)
g_error("error parsing \"%s\" at line %i: %s",
CONF_AUDIO_OUTPUT_FORMAT, param->line, error->message);
MPD_ERROR("error parsing \"%s\" at line %i: %s",
CONF_AUDIO_OUTPUT_FORMAT, param->line,
error->message);
}

@@ -38,7 +38,7 @@ bool
audio_check_sample_rate(unsigned long sample_rate, GError **error_r);
bool
audio_check_sample_format(unsigned sample_format, GError **error_r);
audio_check_sample_format(enum sample_format, GError **error_r);
bool
audio_check_channel_count(unsigned sample_format, GError **error_r);

@@ -22,6 +22,7 @@
#include <stdint.h>
#include <stdbool.h>
#include <assert.h>
enum sample_format {
SAMPLE_FORMAT_UNDEFINED = 0,
@@ -219,6 +220,9 @@ static inline void
audio_format_mask_apply(struct audio_format *af,
const struct audio_format *mask)
{
assert(audio_format_valid(af));
assert(audio_format_mask_valid(mask));
if (mask->sample_rate != 0)
af->sample_rate = mask->sample_rate;
@@ -227,6 +231,8 @@ audio_format_mask_apply(struct audio_format *af,
if (mask->channels != 0)
af->channels = mask->channels;
assert(audio_format_valid(af));
}
/**

@@ -192,6 +192,8 @@ audio_format_parse(struct audio_format *dest, const char *src,
}
audio_format_init(dest, rate, sample_format, channels);
assert(mask ? audio_format_mask_valid(dest)
: audio_format_valid(dest));
return true;
}

@@ -26,6 +26,7 @@
#include "decoder_plugin.h"
#include "output_list.h"
#include "ls.h"
#include "mpd_error.h"
#ifdef ENABLE_ENCODER
#include "encoder_list.h"
@@ -155,10 +156,8 @@ parse_cmdline(int argc, char **argv, struct options *options,
ret = g_option_context_parse(context, &argc, &argv, &error);
g_option_context_free(context);
if (!ret) {
g_error("option parsing failed: %s\n", error->message);
exit(1);
}
if (!ret)
MPD_ERROR("option parsing failed: %s\n", error->message);
if (option_version)
version();

@@ -464,7 +464,7 @@ handle_currentsong(struct client *client,
G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[])
{
playlist_print_current(client, &g_playlist);
return PLAYLIST_RESULT_SUCCESS;
return COMMAND_RETURN_OK;
}
static enum command_return
@@ -749,7 +749,7 @@ handle_load(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
result = playlist_open_into_queue(argv[1], &g_playlist);
if (result != PLAYLIST_RESULT_NO_SUCH_LIST)
return result;
return print_playlist_result(client, result);
result = playlist_load_spl(&g_playlist, argv[1]);
return print_playlist_result(client, result);
@@ -1715,15 +1715,11 @@ handle_sticker_song(struct client *client, int argc, char *argv[])
}
sticker = sticker_song_get(song);
if (NULL == sticker) {
command_error(client, ACK_ERROR_NO_EXIST,
"no stickers found");
return COMMAND_RETURN_ERROR;
if (sticker) {
sticker_print(client, sticker);
sticker_free(sticker);
}
sticker_print(client, sticker);
sticker_free(sticker);
return COMMAND_RETURN_OK;
/* set song song_id id key */
} else if (argc == 6 && strcmp(argv[1], "set") == 0) {

@@ -23,6 +23,7 @@
#include "tokenizer.h"
#include "path.h"
#include "glib_compat.h"
#include "mpd_error.h"
#include <glib.h>
@@ -366,6 +367,7 @@ config_read_file(const char *file, GError **error_r)
assert(*line != 0);
g_propagate_prefixed_error(error_r, error,
"line %i: ", count);
fclose(fp);
return false;
}
@@ -377,6 +379,7 @@ config_read_file(const char *file, GError **error_r)
g_set_error(error_r, config_quark(), 0,
"unrecognized parameter in config file at "
"line %i: %s\n", count, name);
fclose(fp);
return false;
}
@@ -386,6 +389,7 @@ config_read_file(const char *file, GError **error_r)
"config parameter \"%s\" is first defined "
"on line %i and redefined on line %i\n",
name, param->line, count);
fclose(fp);
return false;
}
@@ -397,6 +401,7 @@ config_read_file(const char *file, GError **error_r)
if (*line != '{') {
g_set_error(error_r, config_quark(), 0,
"line %i: '{' expected", count);
fclose(fp);
return false;
}
@@ -405,12 +410,15 @@ config_read_file(const char *file, GError **error_r)
g_set_error(error_r, config_quark(), 0,
"line %i: Unknown tokens after '{'",
count);
fclose(fp);
return false;
}
param = config_read_block(fp, &count, string, error_r);
if (param == NULL)
if (param == NULL) {
fclose(fp);
return false;
}
} else {
/* a string value */
@@ -427,6 +435,7 @@ config_read_file(const char *file, GError **error_r)
g_error_free(error);
}
fclose(fp);
return false;
}
@@ -434,6 +443,7 @@ config_read_file(const char *file, GError **error_r)
g_set_error(error_r, config_quark(), 0,
"line %i: Unknown tokens after value",
count);
fclose(fp);
return false;
}
@@ -498,8 +508,8 @@ config_get_path(const char *name)
path = parsePath(param->value);
if (path == NULL)
g_error("error parsing \"%s\" at line %i\n",
name, param->line);
MPD_ERROR("error parsing \"%s\" at line %i\n",
name, param->line);
g_free(param->value);
return param->value = path;
@@ -517,7 +527,8 @@ config_get_unsigned(const char *name, unsigned default_value)
value = strtol(param->value, &endptr, 0);
if (*endptr != 0 || value < 0)
g_error("Not a valid non-negative number in line %i", param->line);
MPD_ERROR("Not a valid non-negative number in line %i",
param->line);
return (unsigned)value;
}
@@ -534,10 +545,10 @@ config_get_positive(const char *name, unsigned default_value)
value = strtol(param->value, &endptr, 0);
if (*endptr != 0)
g_error("Not a valid number in line %i", param->line);
MPD_ERROR("Not a valid number in line %i", param->line);
if (value <= 0)
g_error("Not a positive number in line %i", param->line);
MPD_ERROR("Not a positive number in line %i", param->line);
return (unsigned)value;
}
@@ -569,9 +580,9 @@ bool config_get_bool(const char *name, bool default_value)
success = get_bool(param->value, &value);
if (!success)
g_error("%s is not a boolean value (yes, true, 1) or "
"(no, false, 0) on line %i\n",
name, param->line);
MPD_ERROR("%s is not a boolean value (yes, true, 1) or "
"(no, false, 0) on line %i\n",
name, param->line);
return value;
}
@@ -601,10 +612,10 @@ config_get_block_unsigned(const struct config_param *param, const char *name,
value = strtol(bp->value, &endptr, 0);
if (*endptr != 0)
g_error("Not a valid number in line %i", bp->line);
MPD_ERROR("Not a valid number in line %i", bp->line);
if (value < 0)
g_error("Not a positive number in line %i", bp->line);
MPD_ERROR("Not a positive number in line %i", bp->line);
return (unsigned)value;
}
@@ -621,9 +632,9 @@ config_get_block_bool(const struct config_param *param, const char *name,
success = get_bool(bp->value, &value);
if (!success)
g_error("%s is not a boolean value (yes, true, 1) or "
"(no, false, 0) on line %i\n",
name, bp->line);
MPD_ERROR("%s is not a boolean value (yes, true, 1) or "
"(no, false, 0) on line %i\n",
name, bp->line);
return value;
}

@@ -28,7 +28,7 @@
#define CONF_FOLLOW_INSIDE_SYMLINKS "follow_inside_symlinks"
#define CONF_FOLLOW_OUTSIDE_SYMLINKS "follow_outside_symlinks"
#define CONF_DB_FILE "db_file"
#define CONF_STICKER_FILE "sticker_file"
#define CONF_STICKER_FILE "sticker_file"
#define CONF_LOG_FILE "log_file"
#define CONF_PID_FILE "pid_file"
#define CONF_STATE_FILE "state_file"
@@ -38,7 +38,7 @@
#define CONF_PORT "port"
#define CONF_LOG_LEVEL "log_level"
#define CONF_ZEROCONF_NAME "zeroconf_name"
#define CONF_ZEROCONF_ENABLED "zeroconf_enabled"
#define CONF_ZEROCONF_ENABLED "zeroconf_enabled"
#define CONF_PASSWORD "password"
#define CONF_DEFAULT_PERMS "default_permissions"
#define CONF_AUDIO_OUTPUT "audio_output"
@@ -48,7 +48,7 @@
#define CONF_REPLAYGAIN "replaygain"
#define CONF_REPLAYGAIN_PREAMP "replaygain_preamp"
#define CONF_REPLAYGAIN_MISSING_PREAMP "replaygain_missing_preamp"
#define CONF_REPLAYGAIN_LIMIT "replaygain_limit"
#define CONF_REPLAYGAIN_LIMIT "replaygain_limit"
#define CONF_VOLUME_NORMALIZATION "volume_normalization"
#define CONF_SAMPLERATE_CONVERTER "samplerate_converter"
#define CONF_AUDIO_BUFFER_SIZE "audio_buffer_size"
@@ -66,12 +66,12 @@
#define CONF_ID3V1_ENCODING "id3v1_encoding"
#define CONF_METADATA_TO_USE "metadata_to_use"
#define CONF_SAVE_ABSOLUTE_PATHS "save_absolute_paths_in_playlists"
#define CONF_DECODER "decoder"
#define CONF_INPUT "input"
#define CONF_GAPLESS_MP3_PLAYBACK "gapless_mp3_playback"
#define CONF_PLAYLIST_PLUGIN "playlist_plugin"
#define CONF_AUTO_UPDATE "auto_update"
#define CONF_AUTO_UPDATE_DEPTH "auto_update_depth"
#define CONF_DECODER "decoder"
#define CONF_INPUT "input"
#define CONF_GAPLESS_MP3_PLAYBACK "gapless_mp3_playback"
#define CONF_PLAYLIST_PLUGIN "playlist_plugin"
#define CONF_AUTO_UPDATE "auto_update"
#define CONF_AUTO_UPDATE_DEPTH "auto_update_depth"
#define DEFAULT_PLAYLIST_MAX_LENGTH (1024*16)
#define DEFAULT_PLAYLIST_SAVE_ABSOLUTE_PATHS false

@@ -65,23 +65,23 @@ daemonize_kill(void)
int pid, ret;
if (pidfile == NULL)
g_error("no pid_file specified in the config file");
MPD_ERROR("no pid_file specified in the config file");
fp = fopen(pidfile, "r");
if (fp == NULL)
g_error("unable to open pid file \"%s\": %s",
pidfile, g_strerror(errno));
MPD_ERROR("unable to open pid file \"%s\": %s",
pidfile, g_strerror(errno));
if (fscanf(fp, "%i", &pid) != 1) {
g_error("unable to read the pid from file \"%s\"",
pidfile);
MPD_ERROR("unable to read the pid from file \"%s\"",
pidfile);
}
fclose(fp);
ret = kill(pid, SIGTERM);
if (ret < 0)
g_error("unable to kill proccess %i: %s",
pid, g_strerror(errno));
MPD_ERROR("unable to kill proccess %i: %s",
pid, g_strerror(errno));
exit(EXIT_SUCCESS);
}
@@ -102,8 +102,8 @@ daemonize_set_user(void)
/* set gid */
if (user_gid != (gid_t)-1 && user_gid != getgid()) {
if (setgid(user_gid) == -1) {
g_error("cannot setgid to %d: %s",
(int)user_gid, g_strerror(errno));
MPD_ERROR("cannot setgid to %d: %s",
(int)user_gid, g_strerror(errno));
}
}
@@ -121,8 +121,8 @@ daemonize_set_user(void)
/* set uid */
if (user_uid != (uid_t)-1 && user_uid != getuid() &&
setuid(user_uid) == -1) {
g_error("cannot change to uid of user \"%s\": %s",
user_name, g_strerror(errno));
MPD_ERROR("cannot change to uid of user \"%s\": %s",
user_name, g_strerror(errno));
}
}
@@ -136,7 +136,7 @@ daemonize_detach(void)
#ifdef HAVE_DAEMON
if (daemon(0, 1))
g_error("daemon() failed: %s", g_strerror(errno));
MPD_ERROR("daemon() failed: %s", g_strerror(errno));
#elif defined(HAVE_FORK)
@@ -144,7 +144,7 @@ daemonize_detach(void)
switch (fork()) {
case -1:
g_error("fork() failed: %s", g_strerror(errno));
MPD_ERROR("fork() failed: %s", g_strerror(errno));
case 0:
break;
default:
@@ -155,14 +155,14 @@ daemonize_detach(void)
/* release the current working directory */
if (chdir("/") < 0)
g_error("problems changing to root directory");
MPD_ERROR("problems changing to root directory");
/* detach from the current session */
setsid();
#else
g_error("no support for daemonizing");
MPD_ERROR("no support for daemonizing");
#endif
g_debug("daemonized!");
@@ -179,8 +179,8 @@ daemonize(bool detach)
g_debug("opening pid file");
fp = fopen(pidfile, "w+");
if (!fp) {
g_error("could not create pid file \"%s\": %s",
pidfile, g_strerror(errno));
MPD_ERROR("could not create pid file \"%s\": %s",
pidfile, g_strerror(errno));
}
}
@@ -200,7 +200,7 @@ daemonize_init(const char *user, const char *group, const char *_pidfile)
if (user) {
struct passwd *pwd = getpwnam(user);
if (!pwd)
g_error("no such user \"%s\"", user);
MPD_ERROR("no such user \"%s\"", user);
user_uid = pwd->pw_uid;
user_gid = pwd->pw_gid;
@@ -214,7 +214,7 @@ daemonize_init(const char *user, const char *group, const char *_pidfile)
if (group) {
struct group *grp = grp = getgrnam(group);
if (!grp)
g_error("no such group \"%s\"", group);
MPD_ERROR("no such group \"%s\"", group);
user_gid = grp->gr_gid;
had_group = true;
}

@@ -20,6 +20,8 @@
#ifndef DAEMON_H
#define DAEMON_H
#include "mpd_error.h"
#include <stdbool.h>
#ifndef WIN32
@@ -51,7 +53,7 @@ daemonize_kill(void);
#include <glib.h>
static inline void
daemonize_kill(void)
{ g_error("--kill is not available on WIN32"); }
{ MPD_ERROR("--kill is not available on WIN32"); }
#endif
/**

@@ -180,7 +180,7 @@ db_check(void)
}
/* Check if we can write to the directory */
if (access(dirPath, R_OK | W_OK)) {
if (access(dirPath, X_OK | W_OK)) {
g_warning("Can't create db file in \"%s\": %s",
dirPath, strerror(errno));
g_free(dirPath);

@@ -244,7 +244,7 @@ static const char *const audiofile_suffixes[] = {
static const char *const audiofile_mime_types[] = {
"audio/x-wav",
"audio/x-aiff",
NULL
NULL
};
const struct decoder_plugin audiofile_decoder_plugin = {

@@ -81,11 +81,19 @@ mpd_ffmpeg_log_callback(G_GNUC_UNUSED void *ptr, int level,
#endif /* !OLD_FFMPEG_INCLUDES */
#ifndef AV_VERSION_INT
#define AV_VERSION_INT(a, b, c) (a<<16 | b<<8 | c)
#endif
struct mpd_ffmpeg_stream {
struct decoder *decoder;
struct input_stream *input;
#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(52,101,0)
AVIOContext *io;
#else
ByteIOContext *io;
#endif
unsigned char buffer[8192];
};
@@ -102,13 +110,11 @@ static int64_t
mpd_ffmpeg_stream_seek(void *opaque, int64_t pos, int whence)
{
struct mpd_ffmpeg_stream *stream = opaque;
bool ret;
if (whence == AVSEEK_SIZE)
return stream->input->size;
ret = input_stream_seek(stream->input, pos, whence, NULL);
if (!ret)
if (!input_stream_seek(stream->input, pos, whence, NULL))
return -1;
return stream->input->offset;
@@ -133,6 +139,33 @@ mpd_ffmpeg_stream_open(struct decoder *decoder, struct input_stream *input)
return stream;
}
/**
* API compatibility wrapper for av_open_input_stream() and
* avformat_open_input().
*/
static int
mpd_ffmpeg_open_input(AVFormatContext **ic_ptr,
#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(52,101,0)
AVIOContext *pb,
#else
ByteIOContext *pb,
#endif
const char *filename,
AVInputFormat *fmt)
{
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(53,1,3)
AVFormatContext *context = avformat_alloc_context();
if (context == NULL)
return AVERROR(ENOMEM);
context->pb = pb;
*ic_ptr = context;
return avformat_open_input(ic_ptr, filename, fmt, NULL);
#else
return av_open_input_stream(ic_ptr, pb, filename, fmt, NULL);
#endif
}
static void
mpd_ffmpeg_stream_close(struct mpd_ffmpeg_stream *stream)
{
@@ -156,7 +189,11 @@ ffmpeg_find_audio_stream(const AVFormatContext *format_context)
{
for (unsigned i = 0; i < format_context->nb_streams; ++i)
if (format_context->streams[i]->codec->codec_type ==
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(52, 64, 0)
AVMEDIA_TYPE_AUDIO)
#else
CODEC_TYPE_AUDIO)
#endif
return i;
return -1;
@@ -183,30 +220,40 @@ ffmpeg_send_packet(struct decoder *decoder, struct input_stream *is,
AVCodecContext *codec_context,
const AVRational *time_base)
{
enum decoder_command cmd = DECODE_COMMAND_NONE;
uint8_t audio_buf[(AVCODEC_MAX_AUDIO_FRAME_SIZE * 3) / 2 + 16];
int16_t *aligned_buffer;
size_t buffer_size;
int len, audio_size;
uint8_t *packet_data;
int packet_size;
if (packet->pts != (int64_t)AV_NOPTS_VALUE)
decoder_timestamp(decoder,
av_rescale_q(packet->pts, *time_base,
(AVRational){1, 1}));
packet_data = packet->data;
packet_size = packet->size;
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(52,25,0)
AVPacket packet2 = *packet;
#else
const uint8_t *packet_data = packet->data;
int packet_size = packet->size;
#endif
buffer_size = sizeof(audio_buf);
aligned_buffer = align16(audio_buf, &buffer_size);
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);
while ((packet_size > 0) && (cmd == DECODE_COMMAND_NONE)) {
audio_size = buffer_size;
len = avcodec_decode_audio2(codec_context,
aligned_buffer, &audio_size,
packet_data, packet_size);
enum decoder_command cmd = DECODE_COMMAND_NONE;
while (
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(52,25,0)
packet2.size > 0 &&
#else
packet_size > 0 &&
#endif
cmd == DECODE_COMMAND_NONE) {
int audio_size = buffer_size;
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(52,25,0)
int len = avcodec_decode_audio3(codec_context,
aligned_buffer, &audio_size,
&packet2);
#else
int len = avcodec_decode_audio2(codec_context,
aligned_buffer, &audio_size,
packet_data, packet_size);
#endif
if (len < 0) {
/* if error, we skip the frame */
@@ -214,8 +261,13 @@ ffmpeg_send_packet(struct decoder *decoder, struct input_stream *is,
break;
}
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(52,25,0)
packet2.data += len;
packet2.size -= len;
#else
packet_data += len;
packet_size -= len;
#endif
if (audio_size <= 0)
continue;
@@ -230,17 +282,19 @@ ffmpeg_send_packet(struct decoder *decoder, struct input_stream *is,
static enum sample_format
ffmpeg_sample_format(G_GNUC_UNUSED const AVCodecContext *codec_context)
{
#if LIBAVCODEC_VERSION_INT >= ((51<<16)+(41<<8)+0)
int bits = (uint8_t) av_get_bits_per_sample_format(codec_context->sample_fmt);
/* XXX implement & test other sample formats */
switch (bits) {
case 16:
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(51, 41, 0)
switch (codec_context->sample_fmt) {
case SAMPLE_FMT_S16:
return SAMPLE_FORMAT_S16;
}
return SAMPLE_FORMAT_UNDEFINED;
case SAMPLE_FMT_S32:
return SAMPLE_FORMAT_S32;
default:
g_warning("Unsupported libavcodec SampleFormat value: %d",
codec_context->sample_fmt);
return SAMPLE_FORMAT_UNDEFINED;
}
#else
/* XXX fixme 16-bit for older ffmpeg (13 Aug 2007) */
return SAMPLE_FORMAT_S16;
@@ -297,14 +351,10 @@ ffmpeg_decode(struct decoder *decoder, struct input_stream *input)
return;
}
AVFormatContext *format_context;
AVCodecContext *codec_context;
AVCodec *codec;
int audio_stream;
//ffmpeg works with ours "fileops" helper
if (av_open_input_stream(&format_context, stream->io, input->uri,
input_format, NULL) != 0) {
AVFormatContext *format_context = NULL;
if (mpd_ffmpeg_open_input(&format_context, stream->io, input->uri,
input_format) != 0) {
g_warning("Open failed\n");
mpd_ffmpeg_stream_close(stream);
return;
@@ -317,7 +367,7 @@ ffmpeg_decode(struct decoder *decoder, struct input_stream *input)
return;
}
audio_stream = ffmpeg_find_audio_stream(format_context);
int audio_stream = ffmpeg_find_audio_stream(format_context);
if (audio_stream == -1) {
g_warning("No audio stream inside\n");
av_close_input_stream(format_context);
@@ -325,11 +375,12 @@ ffmpeg_decode(struct decoder *decoder, struct input_stream *input)
return;
}
codec_context = format_context->streams[audio_stream]->codec;
AVCodecContext *codec_context =
format_context->streams[audio_stream]->codec;
if (codec_context->codec_name[0] != 0)
g_debug("codec '%s'", codec_context->codec_name);
codec = avcodec_find_decoder(codec_context->codec_id);
AVCodec *codec = avcodec_find_decoder(codec_context->codec_id);
if (!codec) {
g_warning("Unsupported audio codec\n");
@@ -426,13 +477,26 @@ static const ffmpeg_tag_map ffmpeg_tag_maps[] = {
};
static bool
ffmpeg_copy_metadata(struct tag *tag, AVMetadata *m,
ffmpeg_copy_metadata(struct tag *tag,
#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,1,0)
AVDictionary *m,
#else
AVMetadata *m,
#endif
const ffmpeg_tag_map tag_map)
{
#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,1,0)
AVDictionaryEntry *mt = NULL;
while ((mt = av_dict_get(m, tag_map.name, mt, 0)) != NULL)
tag_add_item(tag, tag_map.type, mt->value);
#else
AVMetadataTag *mt = NULL;
while ((mt = av_metadata_get(m, tag_map.name, mt, 0)) != NULL)
tag_add_item(tag, tag_map.type, mt->value);
#endif
return mt != NULL;
}
@@ -450,9 +514,9 @@ ffmpeg_stream_tag(struct input_stream *is)
if (stream == NULL)
return NULL;
AVFormatContext *f;
if (av_open_input_stream(&f, stream->io, is->uri,
input_format, NULL) != 0) {
AVFormatContext *f = NULL;
if (mpd_ffmpeg_open_input(&f, stream->io, is->uri,
input_format) != 0) {
mpd_ffmpeg_stream_close(stream);
return NULL;
}
@@ -522,7 +586,9 @@ static const char *const ffmpeg_suffixes[] = {
"atrac", "au", "aud", "avi", "avm2", "avs", "bap", "bfi", "c93", "cak",
"cin", "cmv", "cpk", "daud", "dct", "divx", "dts", "dv", "dvd", "dxa",
"eac3", "film", "flac", "flc", "fli", "fll", "flx", "flv", "g726",
"gsm", "gxf", "iss", "m1v", "m2v", "m2t", "m2ts", "m4a", "m4v", "mad",
"gsm", "gxf", "iss", "m1v", "m2v", "m2t", "m2ts",
"m4a", "m4b", "m4v",
"mad",
"mj2", "mjpeg", "mjpg", "mka", "mkv", "mlp", "mm", "mmf", "mov", "mp+",
"mp1", "mp2", "mp3", "mp4", "mpc", "mpeg", "mpg", "mpga", "mpp", "mpu",
"mve", "mvi", "mxf", "nc", "nsv", "nut", "nuv", "oga", "ogm", "ogv",

@@ -81,7 +81,7 @@ flac_tell_cb(G_GNUC_UNUSED const FLAC__StreamDecoder *fd,
struct flac_data *data = (struct flac_data *) fdata;
if (!data->input_stream->seekable)
return FLAC__STREAM_DECODER_SEEK_STATUS_UNSUPPORTED;
return FLAC__STREAM_DECODER_TELL_STATUS_UNSUPPORTED;
*offset = (long)(data->input_stream->offset);

@@ -87,7 +87,7 @@ flac_find_string_comment(const FLAC__StreamMetadata *block,
int offset;
size_t pos;
int len;
unsigned char tmp, *p;
const unsigned char *p;
*str = NULL;
offset = FLAC__metadata_object_vorbiscomment_find_entry_from(block, 0,
@@ -101,10 +101,7 @@ flac_find_string_comment(const FLAC__StreamMetadata *block,
return false;
p = &block->data.vorbis_comment.comments[offset].entry[pos];
tmp = p[len];
p[len] = '\0';
*str = strdup((char *)p);
p[len] = tmp;
*str = g_strndup((const char *)p, len);
return true;
}
@@ -227,7 +224,8 @@ flac_tag_apply_metadata(struct tag *tag, const char *track,
break;
case FLAC__METADATA_TYPE_STREAMINFO:
tag->time = flac_duration(&block->data.stream_info);
if (block->data.stream_info.sample_rate > 0)
tag->time = flac_duration(&block->data.stream_info);
break;
default:

@@ -20,6 +20,7 @@
#ifndef MPD_FLAC_METADATA_H
#define MPD_FLAC_METADATA_H
#include <assert.h>
#include <stdbool.h>
#include <FLAC/metadata.h>
@@ -29,6 +30,8 @@ struct replay_gain_info;
static inline unsigned
flac_duration(const FLAC__StreamMetadata_StreamInfo *stream_info)
{
assert(stream_info->sample_rate > 0);
return (stream_info->total_samples + stream_info->sample_rate - 1) /
stream_info->sample_rate;
}

@@ -1,13 +1,21 @@
#include "config.h"
#include "../decoder_api.h"
#include "audio_check.h"
#include "uri.h"
#include <glib.h>
#include <assert.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <gme/gme.h>
#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "gme"
#define SUBTUNE_PREFIX "tune_"
enum {
GME_SAMPLE_RATE = 44100,
GME_CHANNELS = 2,
@@ -15,10 +23,91 @@ enum {
GME_BUFFER_SAMPLES = GME_BUFFER_FRAMES * GME_CHANNELS,
};
/**
* returns the file path stripped of any /tune_xxx.* subtune
* suffix
*/
static char *
get_container_name(const char *path_fs)
{
const char *subtune_suffix = uri_get_suffix(path_fs);
char *path_container = g_strdup(path_fs);
char *pat = g_strconcat("*/" SUBTUNE_PREFIX "???.", subtune_suffix, NULL);
GPatternSpec *path_with_subtune = g_pattern_spec_new(pat);
g_free(pat);
if (!g_pattern_match(path_with_subtune,
strlen(path_container), path_container, NULL)) {
g_pattern_spec_free(path_with_subtune);
return path_container;
}
char *ptr = g_strrstr(path_container, "/" SUBTUNE_PREFIX);
if (ptr != NULL)
*ptr='\0';
g_pattern_spec_free(path_with_subtune);
return path_container;
}
/**
* returns tune number from file.nsf/tune_xxx.* style path or 0 if no subtune
* is appended.
*/
static int
get_song_num(const char *path_fs)
{
const char *subtune_suffix = uri_get_suffix(path_fs);
char *pat = g_strconcat("*/" SUBTUNE_PREFIX "???.", subtune_suffix, NULL);
GPatternSpec *path_with_subtune = g_pattern_spec_new(pat);
g_free(pat);
if (g_pattern_match(path_with_subtune,
strlen(path_fs), path_fs, NULL)) {
char *sub = g_strrstr(path_fs, "/" SUBTUNE_PREFIX);
g_pattern_spec_free(path_with_subtune);
if(!sub)
return 0;
sub += strlen("/" SUBTUNE_PREFIX);
int song_num = strtol(sub, NULL, 10);
return song_num - 1;
} else {
g_pattern_spec_free(path_with_subtune);
return 0;
}
}
static char *
gme_container_scan(const char *path_fs, const unsigned int tnum)
{
Music_Emu *emu;
const char* gme_err;
unsigned int num_songs;
gme_err = gme_open_file(path_fs, &emu, GME_SAMPLE_RATE);
if (gme_err != NULL) {
g_warning("%s", gme_err);
return NULL;
}
num_songs = gme_track_count(emu);
/* if it only contains a single tune, don't treat as container */
if (num_songs < 2)
return NULL;
const char *subtune_suffix = uri_get_suffix(path_fs);
if (tnum <= num_songs){
char *subtune = g_strdup_printf(
SUBTUNE_PREFIX "%03u.%s", tnum, subtune_suffix);
return subtune;
} else
return NULL;
}
static void
gme_file_decode(struct decoder *decoder, const char *path_fs)
{
int track = 0; /* index of track to play */
float song_len;
Music_Emu *emu;
gme_info_t *ti;
@@ -26,13 +115,17 @@ gme_file_decode(struct decoder *decoder, const char *path_fs)
enum decoder_command cmd;
short buf[GME_BUFFER_SAMPLES];
const char* gme_err;
char *path_container = get_container_name(path_fs);
int song_num = get_song_num(path_fs);
gme_err = gme_open_file(path_fs, &emu, GME_SAMPLE_RATE);
gme_err = gme_open_file(path_container, &emu, GME_SAMPLE_RATE);
g_free(path_container);
if (gme_err != NULL) {
g_warning("%s", gme_err);
return;
}
if((gme_err = gme_track_info(emu, &ti, 0)) != NULL){
if((gme_err = gme_track_info(emu, &ti, song_num)) != NULL){
g_warning("%s", gme_err);
gme_delete(emu);
return;
@@ -57,9 +150,12 @@ gme_file_decode(struct decoder *decoder, const char *path_fs)
decoder_initialized(decoder, &audio_format, true, song_len);
if((gme_err = gme_start_track(emu, track)) != NULL)
if((gme_err = gme_start_track(emu, song_num)) != NULL)
g_warning("%s", gme_err);
if(ti->length > 0)
gme_set_fade(emu, ti->length);
/* play */
do {
gme_err = gme_play(emu, GME_BUFFER_SAMPLES, buf);
@@ -90,13 +186,17 @@ gme_tag_dup(const char *path_fs)
Music_Emu *emu;
gme_info_t *ti;
const char* gme_err;
char *path_container=get_container_name(path_fs);
int song_num;
song_num=get_song_num(path_fs);
gme_err = gme_open_file(path_fs, &emu, GME_SAMPLE_RATE);
gme_err = gme_open_file(path_container, &emu, GME_SAMPLE_RATE);
g_free(path_container);
if (gme_err != NULL) {
g_warning("%s", gme_err);
return NULL;
}
if((gme_err = gme_track_info(emu, &ti, 0)) != NULL){
if((gme_err = gme_track_info(emu, &ti, song_num)) != NULL){
g_warning("%s", gme_err);
gme_delete(emu);
return NULL;
@@ -106,8 +206,16 @@ gme_tag_dup(const char *path_fs)
if(ti != NULL){
if(ti->length > 0)
tag->time = ti->length / 1000;
if(ti->song != NULL)
tag_add_item(tag, TAG_TITLE, ti->song);
if(ti->song != NULL){
if(gme_track_count(emu) > 1){
/* start numbering subtunes from 1 */
char *tag_title=g_strdup_printf("%s (%d/%d)",
ti->song, song_num+1, gme_track_count(emu));
tag_add_item(tag, TAG_TITLE, tag_title);
g_free(tag_title);
}else
tag_add_item(tag, TAG_TITLE, ti->song);
}
if(ti->author != NULL)
tag_add_item(tag, TAG_ARTIST, ti->author);
if(ti->game != NULL)
@@ -135,4 +243,5 @@ const struct decoder_plugin gme_decoder_plugin = {
.file_decode = gme_file_decode,
.tag_dup = gme_tag_dup,
.suffixes = gme_suffixes,
.container_scan = gme_container_scan,
};

@@ -285,10 +285,10 @@ parse_id3_mixramp(char **mixramp_start, char **mixramp_end,
(&frame->fields[2]));
if (g_ascii_strcasecmp(key, "mixramp_start") == 0) {
*mixramp_start = strdup(value);
*mixramp_start = g_strdup(value);
found = true;
} else if (g_ascii_strcasecmp(key, "mixramp_end") == 0) {
*mixramp_end = strdup(value);
*mixramp_end = g_strdup(value);
found = true;
}
@@ -547,14 +547,14 @@ enum {
XING_SCALE = 0x00000008L
};
struct version {
struct lame_version {
unsigned major;
unsigned minor;
};
struct lame {
char encoder[10]; /* 9 byte encoder name/version ("LAME3.97b") */
struct version version; /* struct containing just the version */
struct lame_version version; /* struct containing just the version */
float peak; /* replaygain peak */
float track_gain; /* replaygain track gain */
float album_gain; /* replaygain album gain */

@@ -19,6 +19,7 @@
#include "config.h"
#include "decoder_api.h"
#include "mpd_error.h"
#include <glib.h>
#include <mikmod.h>
@@ -110,8 +111,8 @@ mikmod_decoder_init(const struct config_param *param)
mikmod_sample_rate = config_get_block_unsigned(param, "sample_rate",
44100);
if (!audio_valid_sample_rate(mikmod_sample_rate))
g_error("Invalid sample rate in line %d: %u",
param->line, mikmod_sample_rate);
MPD_ERROR("Invalid sample rate in line %d: %u",
param->line, mikmod_sample_rate);
md_device = 0;
md_reverb = 0;

@@ -362,6 +362,10 @@ mp4ff_tag_name_parse(const char *name)
if (type == TAG_NUM_OF_ITEM_TYPES)
type = tag_name_parse_i(name);
if (g_ascii_strcasecmp(name, "albumartist") == 0 ||
g_ascii_strcasecmp(name, "album_artist") == 0)
return TAG_ALBUM_ARTIST;
return type;
}
@@ -413,7 +417,13 @@ mp4_stream_tag(struct input_stream *is)
return tag;
}
static const char *const mp4_suffixes[] = { "m4a", "mp4", NULL };
static const char *const mp4_suffixes[] = {
"m4a",
"m4b",
"mp4",
NULL
};
static const char *const mp4_mime_types[] = { "audio/mp4", "audio/m4a", NULL };
const struct decoder_plugin mp4ff_decoder_plugin = {

@@ -153,7 +153,6 @@ mpcdec_decode(struct decoder *mpd_decoder, struct input_stream *is)
mpc_uint32_t ret;
int32_t chunk[G_N_ELEMENTS(sample_buffer)];
long bit_rate = 0;
mpc_uint32_t vbr_update_acc;
mpc_uint32_t vbr_update_bits;
enum decoder_command cmd = DECODE_COMMAND_NONE;
@@ -243,10 +242,11 @@ mpcdec_decode(struct decoder *mpd_decoder, struct input_stream *is)
decoder_seek_error(mpd_decoder);
}
vbr_update_acc = 0;
vbr_update_bits = 0;
#ifdef MPC_IS_OLD_API
mpc_uint32_t vbr_update_acc = 0;
ret = mpc_decoder_decode(&decoder, sample_buffer,
&vbr_update_acc, &vbr_update_bits);
if (ret == 0 || ret == (mpc_uint32_t)-1)

@@ -201,6 +201,7 @@ static void
sidplay_file_decode(struct decoder *decoder, const char *path_fs)
{
int ret;
int channels;
/* load the tune */
@@ -256,7 +257,7 @@ sidplay_file_decode(struct decoder *decoder, const char *path_fs)
config.clockSpeed = SID2_CLOCK_CORRECT;
config.frequency = 48000;
config.optimisation = SID2_DEFAULT_OPTIMISATION;
config.playback = sid2_stereo;
config.precision = 16;
config.sidDefault = SID2_MOS6581;
config.sidEmulation = &builder;
@@ -267,6 +268,13 @@ sidplay_file_decode(struct decoder *decoder, const char *path_fs)
#else
config.sampleFormat = SID2_BIG_SIGNED;
#endif
if (tune.isStereo()) {
config.playback = sid2_stereo;
channels = 2;
} else {
config.playback = sid2_mono;
channels = 1;
}
iret = player.config(config);
if (iret != 0) {
@@ -277,7 +285,7 @@ sidplay_file_decode(struct decoder *decoder, const char *path_fs)
/* initialize the MPD decoder */
struct audio_format audio_format;
audio_format_init(&audio_format, 48000, SAMPLE_FORMAT_S16, 2);
audio_format_init(&audio_format, 48000, SAMPLE_FORMAT_S16, channels);
assert(audio_format_valid(&audio_format));
decoder_initialized(decoder, &audio_format, true, (float)song_len);
@@ -399,6 +407,10 @@ sidplay_container_scan(const char *path_fs, const unsigned int tnum)
static const char *const sidplay_suffixes[] = {
"sid",
"mus",
"str",
"prg",
"P00",
NULL
};

@@ -34,9 +34,6 @@
#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "wavpack"
/* pick 1020 since its devisible for 8,16,24, and 32-bit audio */
#define CHUNK_SIZE 1020
#define ERRORLEN 80
static struct {
@@ -162,8 +159,6 @@ wavpack_decode(struct decoder *decoder, WavpackContext *wpc, bool can_seek)
enum sample_format sample_format;
struct audio_format audio_format;
format_samples_t format_samples;
char chunk[CHUNK_SIZE];
int samples_requested, samples_got;
float total_time;
int bytes_per_sample, output_sample_size;
@@ -193,12 +188,15 @@ wavpack_decode(struct decoder *decoder, WavpackContext *wpc, bool can_seek)
output_sample_size = audio_format_frame_size(&audio_format);
/* wavpack gives us all kind of samples in a 32-bit space */
samples_requested = sizeof(chunk) / (4 * audio_format.channels);
int32_t chunk[1024];
const uint32_t samples_requested = G_N_ELEMENTS(chunk) /
audio_format.channels;
decoder_initialized(decoder, &audio_format, can_seek, total_time);
do {
if (decoder_get_command(decoder) == DECODE_COMMAND_SEEK) {
enum decoder_command cmd = decoder_get_command(decoder);
while (cmd != DECODE_COMMAND_STOP) {
if (cmd == DECODE_COMMAND_SEEK) {
if (can_seek) {
unsigned where = decoder_seek_where(decoder) *
audio_format.sample_rate;
@@ -213,29 +211,20 @@ wavpack_decode(struct decoder *decoder, WavpackContext *wpc, bool can_seek)
}
}
if (decoder_get_command(decoder) == DECODE_COMMAND_STOP) {
uint32_t samples_got = WavpackUnpackSamples(wpc, chunk,
samples_requested);
if (samples_got == 0)
break;
}
samples_got = WavpackUnpackSamples(
wpc, (int32_t *)chunk, samples_requested
);
if (samples_got > 0) {
int bitrate = (int)(WavpackGetInstantBitrate(wpc) /
1000 + 0.5);
int bitrate = (int)(WavpackGetInstantBitrate(wpc) / 1000 +
0.5);
format_samples(bytes_per_sample, chunk,
samples_got * audio_format.channels);
format_samples(
bytes_per_sample, chunk,
samples_got * audio_format.channels
);
decoder_data(
decoder, NULL, chunk,
samples_got * output_sample_size,
bitrate
);
}
} while (samples_got > 0);
cmd = decoder_data(decoder, NULL, chunk,
samples_got * output_sample_size,
bitrate);
}
}
/**

@@ -20,9 +20,9 @@
#include "config.h"
#include "decoder_control.h"
#include "player_control.h"
#include "pipe.h"
#include <assert.h>
#include <malloc.h>
#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "decoder_control"
@@ -50,12 +50,9 @@ dc_deinit(struct decoder_control *dc)
{
g_cond_free(dc->cond);
g_mutex_free(dc->mutex);
if (dc->mixramp_start)
free(dc->mixramp_start);
if (dc->mixramp_end)
free(dc->mixramp_end);
if (dc->mixramp_prev_end)
free(dc->mixramp_prev_end);
g_free(dc->mixramp_start);
g_free(dc->mixramp_end);
g_free(dc->mixramp_prev_end);
dc->mixramp_start = NULL;
dc->mixramp_end = NULL;
dc->mixramp_prev_end = NULL;
@@ -110,6 +107,7 @@ dc_start(struct decoder_control *dc, struct song *song,
assert(song != NULL);
assert(buffer != NULL);
assert(pipe != NULL);
assert(music_pipe_empty(pipe));
dc->song = song;
dc->buffer = buffer;
@@ -172,8 +170,7 @@ dc_mixramp_start(struct decoder_control *dc, char *mixramp_start)
{
assert(dc != NULL);
if (dc->mixramp_start)
free(dc->mixramp_start);
g_free(dc->mixramp_start);
dc->mixramp_start = mixramp_start;
g_debug("mixramp_start = %s", mixramp_start ? mixramp_start : "NULL");
}
@@ -183,8 +180,7 @@ dc_mixramp_end(struct decoder_control *dc, char *mixramp_end)
{
assert(dc != NULL);
if (dc->mixramp_end)
free(dc->mixramp_end);
g_free(dc->mixramp_end);
dc->mixramp_end = mixramp_end;
g_debug("mixramp_end = %s", mixramp_end ? mixramp_end : "NULL");
}
@@ -194,8 +190,7 @@ dc_mixramp_prev_end(struct decoder_control *dc, char *mixramp_prev_end)
{
assert(dc != NULL);
if (dc->mixramp_prev_end)
free(dc->mixramp_prev_end);
g_free(dc->mixramp_prev_end);
dc->mixramp_prev_end = mixramp_prev_end;
g_debug("mixramp_prev_end = %s", mixramp_prev_end ? mixramp_prev_end : "NULL");
}

@@ -22,6 +22,7 @@
#include "decoder_plugin.h"
#include "utils.h"
#include "conf.h"
#include "mpd_error.h"
#include <glib.h>
@@ -198,8 +199,8 @@ decoder_plugin_config(const char *plugin_name)
const char *name =
config_get_block_string(param, "plugin", NULL);
if (name == NULL)
g_error("decoder configuration without 'plugin' name in line %d",
param->line);
MPD_ERROR("decoder configuration without 'plugin' name in line %d",
param->line);
if (strcmp(name, plugin_name) == 0)
return param;

@@ -24,6 +24,7 @@
#include "decoder_list.h"
#include "decoder_plugin.h"
#include "decoder_api.h"
#include "replay_gain_ape.h"
#include "input_stream.h"
#include "player_control.h"
#include "pipe.h"
@@ -32,6 +33,7 @@
#include "mapper.h"
#include "path.h"
#include "uri.h"
#include "mpd_error.h"
#include <glib.h>
@@ -296,6 +298,18 @@ decoder_run_stream(struct decoder *decoder, const char *uri)
return success;
}
/**
* Attempt to load replay gain data, and pass it to
* decoder_replay_gain().
*/
static void
decoder_load_replay_gain(struct decoder *decoder, const char *path_fs)
{
struct replay_gain_info info;
if (replay_gain_ape_read(path_fs, &info))
decoder_replay_gain(decoder, &info);
}
/**
* Try decoding a file.
*/
@@ -311,6 +325,8 @@ decoder_run_file(struct decoder *decoder, const char *path_fs)
decoder_unlock(dc);
decoder_load_replay_gain(decoder, path_fs);
while ((plugin = decoder_plugin_from_suffix(suffix, plugin)) != NULL) {
if (plugin->file_decode != NULL) {
decoder_lock(dc);
@@ -479,5 +495,5 @@ decoder_thread_start(struct decoder_control *dc)
dc->thread = g_thread_create(decoder_task, dc, true, &e);
if (dc->thread == NULL)
g_error("Failed to spawn decoder task: %s", e->message);
MPD_ERROR("Failed to spawn decoder task: %s", e->message);
}

@@ -30,8 +30,8 @@
#define DIRECTORY_DIR "directory: "
#define DEVICE_INARCHIVE (unsigned)(-1)
#define DEVICE_CONTAINER (unsigned)(-2)
#define DEVICE_INARCHIVE (dev_t)(-1)
#define DEVICE_CONTAINER (dev_t)(-2)
struct directory {
struct dirvec children;
@@ -62,7 +62,8 @@ directory_free(struct directory *directory);
static inline bool
directory_is_empty(const struct directory *directory)
{
return directory->children.nr == 0 && directory->songs.nr == 0;
return directory->children.nr == 0 && directory->songs.nr == 0 &&
playlist_vector_is_empty(&directory->playlists);
}
static inline const char *

@@ -55,7 +55,7 @@ static bool
flac_encoder_configure(struct flac_encoder *encoder,
const struct config_param *param, G_GNUC_UNUSED GError **error)
{
encoder->compression = config_get_block_unsigned(param,
encoder->compression = config_get_block_unsigned(param,
"compression", 5);
return true;
@@ -218,7 +218,7 @@ flac_encoder_open(struct encoder *_encoder, struct audio_format *audio_format,
if (init_status != FLAC__STREAM_ENCODER_OK) {
g_set_error(error, flac_encoder_quark(), 0,
"failed to initialize encoder: %s\n",
"failed to initialize encoder: %s\n",
FLAC__StreamEncoderStateString[init_status]);
flac_encoder_close(_encoder);
return false;
@@ -234,7 +234,7 @@ flac_encoder_open(struct encoder *_encoder, struct audio_format *audio_format,
if(init_status != FLAC__STREAM_ENCODER_INIT_STATUS_OK) {
g_set_error(error, flac_encoder_quark(), 0,
"failed to initialize encoder: %s\n",
"failed to initialize encoder: %s\n",
FLAC__StreamEncoderInitStatusString[init_status]);
flac_encoder_close(_encoder);
return false;

@@ -170,6 +170,13 @@ lame_encoder_setup(struct lame_encoder *encoder, GError **error)
return false;
}
if (0 != lame_set_out_samplerate(encoder->gfp,
encoder->audio_format.sample_rate)) {
g_set_error(error, lame_encoder_quark(), 0,
"error setting lame out sample rate");
return false;
}
if (0 > lame_init_params(encoder->gfp)) {
g_set_error(error, lame_encoder_quark(), 0,
"error initializing lame params");

@@ -22,6 +22,7 @@
#include "encoder_plugin.h"
#include "tag.h"
#include "audio_format.h"
#include "mpd_error.h"
#include <vorbis/vorbisenc.h>
@@ -265,6 +266,15 @@ vorbis_encoder_flush(struct encoder *_encoder, G_GNUC_UNUSED GError **error)
{
struct vorbis_encoder *encoder = (struct vorbis_encoder *)_encoder;
encoder->flush = true;
return true;
}
static bool
vorbis_encoder_pre_tag(struct encoder *_encoder, G_GNUC_UNUSED GError **error)
{
struct vorbis_encoder *encoder = (struct vorbis_encoder *)_encoder;
vorbis_analysis_wrote(&encoder->vd, 0);
vorbis_encoder_blockout(encoder);
@@ -275,6 +285,8 @@ vorbis_encoder_flush(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;
}
@@ -363,6 +375,7 @@ vorbis_encoder_read(struct encoder *_encoder, void *_dest, size_t length)
if (ret == 0 && encoder->flush) {
encoder->flush = false;
ret = ogg_stream_flush(&encoder->os, &page);
}
if (ret == 0)
@@ -374,7 +387,7 @@ vorbis_encoder_read(struct encoder *_encoder, void *_dest, size_t length)
if (nbytes > length)
/* XXX better error handling */
g_error("buffer too small");
MPD_ERROR("buffer too small");
memcpy(dest, page.header, page.header_len);
memcpy(dest + page.header_len, page.body, page.body_len);
@@ -385,7 +398,7 @@ vorbis_encoder_read(struct encoder *_encoder, void *_dest, size_t length)
static const char *
vorbis_encoder_get_mime_type(G_GNUC_UNUSED struct encoder *_encoder)
{
return "application/x-ogg";
return "audio/ogg";
}
const struct encoder_plugin vorbis_encoder_plugin = {
@@ -395,6 +408,7 @@ const struct encoder_plugin vorbis_encoder_plugin = {
.open = vorbis_encoder_open,
.close = vorbis_encoder_close,
.flush = vorbis_encoder_flush,
.pre_tag = vorbis_encoder_pre_tag,
.tag = vorbis_encoder_tag,
.write = vorbis_encoder_write,
.read = vorbis_encoder_read,

@@ -58,7 +58,7 @@ wave_encoder_quark(void)
}
static void
fill_wave_header(struct wave_header *header, int channels, int bits,
fill_wave_header(struct wave_header *header, int channels, int bits,
int freq, int block_size)
{
int data_size = 0x0FFFFFFF;
@@ -142,7 +142,7 @@ wave_encoder_open(struct encoder *_encoder,
buffer = pcm_buffer_get(&encoder->buffer, sizeof(struct wave_header) );
/* create PCM wave header in initial buffer */
fill_wave_header((struct wave_header *) buffer,
fill_wave_header((struct wave_header *) buffer,
audio_format->channels,
encoder->bits,
audio_format->sample_rate,

@@ -50,6 +50,8 @@ struct encoder_plugin {
bool (*flush)(struct encoder *encoder, GError **error);
bool (*pre_tag)(struct encoder *encoder, GError **error);
bool (*tag)(struct encoder *encoder, const struct tag *tag,
GError **error);
@@ -147,9 +149,31 @@ encoder_flush(struct encoder *encoder, GError **error)
: true;
}
/**
* Prepare for sending a tag to the encoder. This is used by some
* encoders to flush the previous sub-stream, in preparation to begin
* a new one.
*
* @param encoder the encoder
* @param tag the tag object
* @param error location to store the error occuring, or NULL to ignore errors.
* @return true on success
*/
static inline bool
encoder_pre_tag(struct encoder *encoder, GError **error)
{
/* this method is optional */
return encoder->plugin->pre_tag != NULL
? encoder->plugin->pre_tag(encoder, error)
: true;
}
/**
* Sends a tag to the encoder.
*
* Instructions: call encoder_pre_tag(); then obtain flushed data with
* encoder_read(); finally call encoder_tag().
*
* @param encoder the encoder
* @param tag the tag object
* @param error location to store the error occuring, or NULL to ignore errors.

@@ -20,6 +20,7 @@
#include "config.h"
#include "event_pipe.h"
#include "fd_util.h"
#include "mpd_error.h"
#include <stdbool.h>
#include <assert.h>
@@ -68,7 +69,7 @@ main_notify_event(G_GNUC_UNUSED GIOChannel *source,
buffer, sizeof(buffer),
&bytes_read, &error);
if (status == G_IO_STATUS_ERROR)
g_error("error reading from pipe: %s", error->message);
MPD_ERROR("error reading from pipe: %s", error->message);
bool events[PIPE_EVENT_MAX];
g_mutex_lock(event_pipe_mutex);
@@ -91,7 +92,7 @@ void event_pipe_init(void)
ret = pipe_cloexec_nonblock(event_pipe);
if (ret < 0)
g_error("Couldn't open pipe: %s", strerror(errno));
MPD_ERROR("Couldn't open pipe: %s", strerror(errno));
#ifndef G_OS_WIN32
channel = g_io_channel_unix_new(event_pipe[0]);
@@ -116,7 +117,10 @@ void event_pipe_deinit(void)
g_source_remove(event_pipe_source_id);
g_io_channel_unref(event_channel);
#ifndef WIN32
/* By some strange reason this call hangs on Win32 */
close(event_pipe[0]);
#endif
close(event_pipe[1]);
}
@@ -147,7 +151,7 @@ void event_pipe_emit(enum pipe_event event)
w = write(event_pipe[1], "", 1);
if (w < 0 && errno != EAGAIN && errno != EINTR)
g_error("error writing to pipe: %s", strerror(errno));
MPD_ERROR("error writing to pipe: %s", strerror(errno));
}
void event_pipe_emit_fast(enum pipe_event event)

@@ -44,6 +44,9 @@ enum pipe_event {
/** a hardware mixer plugin has detected a change */
PIPE_EVENT_MIXER,
/** shutdown requested */
PIPE_EVENT_SHUTDOWN,
PIPE_EVENT_MAX
};

@@ -103,6 +103,16 @@ fd_set_nonblock(int fd)
#endif
}
int
dup_cloexec(int oldfd)
{
int newfd = dup(oldfd);
if (newfd >= 0)
fd_set_nonblock(newfd);
return newfd;
}
int
open_cloexec(const char *path_fs, int flags, int mode)
{
@@ -174,6 +184,30 @@ pipe_cloexec_nonblock(int fd[2])
#endif
}
#ifndef WIN32
int
socketpair_cloexec(int domain, int type, int protocol, int sv[2])
{
int ret;
#ifdef SOCK_CLOEXEC
ret = socketpair(domain, type | SOCK_CLOEXEC, protocol, sv);
if (ret >= 0 || errno != EINVAL)
return ret;
#endif
ret = socketpair(domain, type, protocol, sv);
if (ret >= 0) {
fd_set_cloexec(sv[0], true);
fd_set_cloexec(sv[1], true);
}
return ret;
}
#endif
int
socket_cloexec_nonblock(int domain, int type, int protocol)
{
@@ -222,6 +256,33 @@ accept_cloexec_nonblock(int fd, struct sockaddr *address,
return ret;
}
#ifndef WIN32
ssize_t
recvmsg_cloexec(int sockfd, struct msghdr *msg, int flags)
{
#ifdef MSG_CMSG_CLOEXEC
flags |= MSG_CMSG_CLOEXEC;
#endif
ssize_t result = recvmsg(sockfd, msg, flags);
if (result >= 0) {
struct cmsghdr *cmsg = CMSG_FIRSTHDR(msg);
while (cmsg != NULL) {
if (cmsg->cmsg_type == SCM_RIGHTS) {
const int *fd_p = (const int *)CMSG_DATA(cmsg);
fd_set_cloexec(*fd_p, true);
}
cmsg = CMSG_NXTHDR(msg, cmsg);
}
}
return result;
}
#endif
#ifdef HAVE_INOTIFY_INIT
int

@@ -39,8 +39,23 @@
#include <stdbool.h>
#include <stddef.h>
#ifndef WIN32
#if !defined(_GNU_SOURCE) && (defined(HAVE_PIPE2) || defined(HAVE_ACCEPT4))
#define _GNU_SOURCE
#endif
#include <sys/types.h>
#endif
struct sockaddr;
/**
* Wrapper for dup(), which sets the CLOEXEC flag on the new
* descriptor.
*/
int
dup_cloexec(int oldfd);
/**
* Wrapper for open(), which sets the CLOEXEC flag (atomically if
* supported by the OS).
@@ -65,6 +80,17 @@ pipe_cloexec(int fd[2]);
int
pipe_cloexec_nonblock(int fd[2]);
#ifndef WIN32
/**
* Wrapper for socketpair(), which sets the CLOEXEC flag (atomically
* if supported by the OS).
*/
int
socketpair_cloexec(int domain, int type, int protocol, int sv[2]);
#endif
/**
* Wrapper for socket(), which sets the CLOEXEC and the NONBLOCK flag
* (atomically if supported by the OS).
@@ -80,6 +106,20 @@ int
accept_cloexec_nonblock(int fd, struct sockaddr *address,
size_t *address_length_r);
#ifndef WIN32
struct msghdr;
/**
* Wrapper for recvmsg(), which sets the CLOEXEC flag (atomically if
* supported by the OS).
*/
ssize_t
recvmsg_cloexec(int sockfd, struct msghdr *msg, int flags);
#endif
/**
* Wrapper for inotify_init(), which sets the CLOEXEC flag (atomically
* if supported by the OS).

@@ -55,8 +55,16 @@ struct replay_gain_filter {
struct replay_gain_info info;
/**
* The current volume, between 0 and #PCM_VOLUME_1 (both
* including).
* The current volume, between 0 and a value that may or may not exceed
* #PCM_VOLUME_1.
*
* If the default value of true is used for replaygain_limit, the
* application of the volume to the signal will never cause clipping.
*
* On the other hand, if the user has set replaygain_limit to false,
* the chance of clipping is explicitly preferred if that's required to
* maintain a consistent audio level. Whether clipping will actually
* occur depends on what value the user is using for replaygain_preamp.
*/
unsigned volume;
@@ -171,7 +179,7 @@ replay_gain_filter_filter(struct filter *_filter,
*dest_size_r = src_size;
if (filter->volume >= PCM_VOLUME_1)
if (filter->volume == PCM_VOLUME_1)
/* optimized special case: 100% volume = no-op */
return src;

@@ -96,6 +96,7 @@ icy_server_metadata_page(const struct tag *tag, ...)
gchar stream_title[(1 + 255 - 28) * 16]; // Length + Metadata -
// "StreamTitle='';StreamUrl='';"
// = 4081 - 28
stream_title[0] = '\0';
last_item = -1;

@@ -21,6 +21,7 @@
#include "inotify_source.h"
#include "fifo_buffer.h"
#include "fd_util.h"
#include "mpd_error.h"
#include <sys/inotify.h>
#include <unistd.h>
@@ -68,13 +69,14 @@ mpd_inotify_in_event(G_GNUC_UNUSED GIOChannel *_source,
dest = fifo_buffer_write(source->buffer, &length);
if (dest == NULL)
g_error("buffer full");
MPD_ERROR("buffer full");
nbytes = read(source->fd, dest, length);
if (nbytes < 0)
g_error("failed to read from inotify: %s", g_strerror(errno));
MPD_ERROR("failed to read from inotify: %s",
g_strerror(errno));
if (nbytes == 0)
g_error("end of file from inotify");
MPD_ERROR("end of file from inotify");
fifo_buffer_append(source->buffer, nbytes);

@@ -42,6 +42,13 @@
#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "input_curl"
/**
* Do not buffer more than this number of bytes. It should be a
* reasonable limit that doesn't make low-end machines suffer too
* much, but doesn't cause stuttering on high-latency lines.
*/
static const size_t CURL_MAX_BUFFERED = 512 * 1024;
/**
* Buffers created by input_curl_writefunction().
*/
@@ -144,6 +151,25 @@ input_curl_finish(void)
curl_global_cleanup();
}
/**
* Determine the total sizes of all buffers, including portions that
* have already been consumed.
*/
G_GNUC_PURE
static size_t
curl_total_buffer_size(const struct input_curl *c)
{
size_t total = 0;
for (GList *i = g_queue_peek_head_link(c->buffers);
i != NULL; i = g_list_next(i)) {
struct buffer *buffer = i->data;
total += buffer->size;
}
return total;
}
static void
buffer_free_callback(gpointer data, G_GNUC_UNUSED gpointer user_data)
{
@@ -264,7 +290,7 @@ input_curl_select(struct input_curl *c, GError **error_r)
return -1;
}
#if LIBCURL_VERSION_NUM >= 0x070f00
#if LIBCURL_VERSION_NUM >= 0x070f04
long timeout2;
mcode = curl_multi_timeout(c->multi, &timeout2);
if (mcode != CURLM_OK) {
@@ -473,6 +499,10 @@ static int
input_curl_buffer(struct input_stream *is, GError **error_r)
{
struct input_curl *c = (struct input_curl *)is;
if (curl_total_buffer_size(c) >= CURL_MAX_BUFFERED)
return 0;
CURLMcode mcode;
int running_handles;
bool ret;
@@ -650,6 +680,9 @@ input_curl_easy_init(struct input_curl *c, GError **error_r)
curl_easy_setopt(c->easy, CURLOPT_MAXREDIRS, 5);
curl_easy_setopt(c->easy, CURLOPT_FAILONERROR, true);
curl_easy_setopt(c->easy, CURLOPT_ERRORBUFFER, c->error);
curl_easy_setopt(c->easy, CURLOPT_NOPROGRESS, 1l);
curl_easy_setopt(c->easy, CURLOPT_NOSIGNAL, 1l);
curl_easy_setopt(c->easy, CURLOPT_CONNECTTIMEOUT, 10l);
if (proxy != NULL)
curl_easy_setopt(c->easy, CURLOPT_PROXY, proxy);

@@ -80,15 +80,18 @@ copy_attributes(struct input_rewind *r)
struct input_stream *dest = &r->base;
const struct input_stream *src = r->input;
assert(dest != src);
assert(src->mime == NULL || dest->mime != src->mime);
dest->ready = src->ready;
dest->seekable = src->seekable;
dest->size = src->size;
dest->offset = src->offset;
if (dest->mime == NULL && src->mime != NULL)
/* this is set only once, and the duplicated pointer
is freed by input_stream_close() */
if (src->mime != NULL) {
g_free(dest->mime);
dest->mime = g_strdup(src->mime);
}
}
static void

@@ -19,333 +19,44 @@
#include "config.h"
#include "listen.h"
#include "socket_util.h"
#include "server_socket.h"
#include "client.h"
#include "conf.h"
#include "fd_util.h"
#include "glib_compat.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <assert.h>
#ifdef WIN32
#define WINVER 0x0501
#include <ws2tcpip.h>
#include <winsock.h>
#else
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <netdb.h>
#endif
#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "listen"
#define DEFAULT_PORT 6600
struct listen_socket {
struct listen_socket *next;
int fd;
guint source_id;
};
static struct listen_socket *listen_sockets;
static struct server_socket *listen_socket;
int listen_port;
static GQuark
listen_quark(void)
static void
listen_callback(int fd, const struct sockaddr *address,
size_t address_length, int uid, G_GNUC_UNUSED void *ctx)
{
return g_quark_from_static_string("listen");
client_new(fd, address, address_length, uid);
}
static gboolean
listen_in_event(GIOChannel *source, GIOCondition condition, gpointer data);
static bool
listen_add_address(int pf, const struct sockaddr *addrp, socklen_t addrlen,
GError **error)
{
char *address_string;
int fd;
struct listen_socket *ls;
GIOChannel *channel;
address_string = sockaddr_to_string(addrp, addrlen, NULL);
if (address_string != NULL) {
g_debug("binding to socket address %s", address_string);
g_free(address_string);
}
fd = socket_bind_listen(pf, SOCK_STREAM, 0, addrp, addrlen, 5, error);
if (fd < 0)
return false;
ls = g_new(struct listen_socket, 1);
ls->fd = fd;
channel = g_io_channel_unix_new(fd);
ls->source_id = g_io_add_watch(channel, G_IO_IN,
listen_in_event, GINT_TO_POINTER(fd));
g_io_channel_unref(channel);
ls->next = listen_sockets;
listen_sockets = ls;
return true;
}
#ifdef HAVE_TCP
/**
* Add a listener on a port on all IPv4 interfaces.
*
* @param port the TCP port
* @param error location to store the error occuring, or NULL to ignore errors
* @return true on success
*/
static bool
listen_add_port_ipv4(unsigned int port, GError **error)
{
struct sockaddr_in sin;
const struct sockaddr *addrp = (const struct sockaddr *)&sin;
socklen_t addrlen = sizeof(sin);
memset(&sin, 0, sizeof(sin));
sin.sin_port = htons(port);
sin.sin_family = AF_INET;
sin.sin_addr.s_addr = INADDR_ANY;
return listen_add_address(PF_INET, addrp, addrlen, error);
}
#ifdef HAVE_IPV6
/**
* Add a listener on a port on all IPv6 interfaces.
*
* @param port the TCP port
* @param error location to store the error occuring, or NULL to ignore errors
* @return true on success
*/
static bool
listen_add_port_ipv6(unsigned int port, GError **error)
{
struct sockaddr_in6 sin;
const struct sockaddr *addrp = (const struct sockaddr *)&sin;
socklen_t addrlen = sizeof(sin);
memset(&sin, 0, sizeof(sin));
sin.sin6_port = htons(port);
sin.sin6_family = AF_INET6;
return listen_add_address(PF_INET6, addrp, addrlen, error);
}
#endif /* HAVE_IPV6 */
#endif /* HAVE_TCP */
/**
* Add a listener on a port on all interfaces.
*
* @param port the TCP port
* @param error location to store the error occuring, or NULL to ignore errors
* @return true on success
*/
static bool
listen_add_port(unsigned int port, GError **error)
{
#ifdef HAVE_TCP
bool success;
#ifdef HAVE_IPV6
bool success6;
GError *error2 = NULL;
#endif
g_debug("binding to any address");
#ifdef HAVE_IPV6
success6 = listen_add_port_ipv6(port, &error2);
if (!success6) {
if (error2->domain != listen_quark() ||
(error2->code != EAFNOSUPPORT && error2->code != EINVAL &&
error2->code != EPROTONOSUPPORT)) {
g_propagate_error(error, error2);
return false;
}
/* although MPD was compiled with IPv6 support, this
host does not have it - ignore this error */
g_error_free(error2);
}
#endif
success = listen_add_port_ipv4(port, error);
if (!success) {
#ifdef HAVE_IPV6
if (success6)
/* non-critical: IPv6 listener is
already set up */
g_clear_error(error);
else
#endif
return false;
}
return true;
#else /* HAVE_TCP */
(void)port;
g_set_error(error, listen_quark(), 0,
"TCP support is disabled");
return false;
#endif /* HAVE_TCP */
}
/**
* Resolves a host name, and adds listeners on all addresses in the
* result set.
*
* @param hostname the host name to be resolved
* @param port the TCP port
* @param error location to store the error occuring, or NULL to ignore errors
* @return true on success
*/
static bool
listen_add_host(const char *hostname, unsigned port, GError **error_r)
{
#ifdef HAVE_TCP
struct addrinfo hints, *ai, *i;
char service[20];
int ret;
bool success;
g_debug("binding to address for %s", hostname);
memset(&hints, 0, sizeof(hints));
hints.ai_flags = AI_PASSIVE;
#ifdef AI_ADDRCONFIG
hints.ai_flags |= AI_ADDRCONFIG;
#endif
hints.ai_family = PF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
g_snprintf(service, sizeof(service), "%u", port);
ret = getaddrinfo(hostname, service, &hints, &ai);
if (ret != 0) {
g_set_error(error_r, listen_quark(), ret,
"Failed to look up host \"%s\": %s",
hostname, gai_strerror(ret));
return false;
}
for (i = ai; i != NULL; i = i->ai_next) {
GError *error = NULL;
success = listen_add_address(i->ai_family, i->ai_addr,
i->ai_addrlen, &error);
if (!success) {
if (i == ai) {
/* first bind has failed: fatal
error */
g_propagate_error(error_r, error);
return false;
} else {
char *address_string =
sockaddr_to_string(i->ai_addr,
i->ai_addrlen,
NULL);
if (address_string == NULL)
address_string = g_strdup("[unknown]");
g_warning("bind to %s failed: %s "
"(continuing anyway, because at "
"least one address is bound)",
address_string, error->message);
g_free(address_string);
g_error_free(error);
}
}
}
freeaddrinfo(ai);
return true;
#else /* HAVE_TCP */
(void)hostname;
(void)port;
g_set_error(error_r, listen_quark(), 0,
"TCP support is disabled");
return false;
#endif /* HAVE_TCP */
}
#ifdef HAVE_UN
/**
* Add a listener on a Unix domain socket.
*
* @param path the absolute socket path
* @param error location to store the error occuring, or NULL to ignore errors
* @return true on success
*/
static bool
listen_add_path(const char *path, GError **error)
{
size_t path_length;
struct sockaddr_un s_un;
const struct sockaddr *addrp = (const struct sockaddr *)&s_un;
socklen_t addrlen = sizeof(s_un);
bool success;
path_length = strlen(path);
if (path_length >= sizeof(s_un.sun_path)) {
g_set_error(error, listen_quark(), 0,
"unix socket path is too long");
return false;
}
unlink(path);
s_un.sun_family = AF_UNIX;
memcpy(s_un.sun_path, path, path_length + 1);
success = listen_add_address(PF_UNIX, addrp, addrlen, error);
if (!success)
return false;
/* allow everybody to connect */
chmod(path, 0666);
return true;
}
#endif /* HAVE_UN */
static bool
listen_add_config_param(unsigned int port,
const struct config_param *param,
GError **error)
GError **error_r)
{
assert(param != NULL);
if (0 == strcmp(param->value, "any")) {
return listen_add_port(port, error);
#ifdef HAVE_UN
return server_socket_add_port(listen_socket, port, error_r);
} else if (param->value[0] == '/') {
return listen_add_path(param->value, error);
#endif /* HAVE_UN */
return server_socket_add_path(listen_socket, param->value,
error_r);
} else {
return listen_add_host(param->value, port, error);
return server_socket_add_host(listen_socket, param->value,
port, error_r);
}
}
@@ -358,6 +69,8 @@ listen_global_init(GError **error_r)
bool success;
GError *error = NULL;
listen_socket = server_socket_new(listen_callback, NULL);
if (param != NULL) {
/* "bind_to_address" is configured, create listeners
for all values */
@@ -378,7 +91,7 @@ listen_global_init(GError **error_r)
/* no "bind_to_address" configured, bind the
configured port on all interfaces */
success = listen_add_port(port, &error);
success = server_socket_add_port(listen_socket, port, error_r);
if (!success) {
g_propagate_prefixed_error(error_r, error,
"Failed to listen on *:%d: ",
@@ -387,6 +100,9 @@ listen_global_init(GError **error_r)
}
}
if (!server_socket_open(listen_socket, error_r))
return false;
listen_port = port;
return true;
}
@@ -395,55 +111,7 @@ void listen_global_finish(void)
{
g_debug("listen_global_finish called");
while (listen_sockets != NULL) {
struct listen_socket *ls = listen_sockets;
listen_sockets = ls->next;
assert(listen_socket != NULL);
g_source_remove(ls->source_id);
close(ls->fd);
g_free(ls);
}
}
static int get_remote_uid(int fd)
{
#ifdef HAVE_STRUCT_UCRED
struct ucred cred;
socklen_t len = sizeof (cred);
if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &cred, &len) < 0)
return 0;
return cred.uid;
#else
#ifdef HAVE_GETPEEREID
uid_t euid;
gid_t egid;
if (getpeereid(fd, &euid, &egid) == 0)
return euid;
#endif
return -1;
#endif
}
static gboolean
listen_in_event(G_GNUC_UNUSED GIOChannel *source,
G_GNUC_UNUSED GIOCondition condition,
gpointer data)
{
int listen_fd = GPOINTER_TO_INT(data), fd;
struct sockaddr_storage sa;
size_t sa_length = sizeof(sa);
fd = accept_cloexec_nonblock(listen_fd, (struct sockaddr*)&sa,
&sa_length);
if (fd >= 0) {
client_new(fd, (struct sockaddr*)&sa, sa_length,
get_remote_uid(fd));
} else if (fd < 0 && errno != EINTR) {
g_warning("Problems accept()'ing");
}
return true;
server_socket_free(listen_socket);
}

@@ -22,6 +22,7 @@
#include "conf.h"
#include "utils.h"
#include "fd_util.h"
#include "mpd_error.h"
#include <assert.h>
#include <sys/types.h>
@@ -60,9 +61,9 @@ static void redirect_logs(int fd)
{
assert(fd >= 0);
if (dup2(fd, STDOUT_FILENO) < 0)
g_error("problems dup2 stdout : %s\n", strerror(errno));
MPD_ERROR("problems dup2 stdout : %s\n", strerror(errno));
if (dup2(fd, STDERR_FILENO) < 0)
g_error("problems dup2 stderr : %s\n", strerror(errno));
MPD_ERROR("problems dup2 stderr : %s\n", strerror(errno));
}
static const char *log_date(void)
@@ -138,8 +139,8 @@ log_init_file(const char *path, unsigned line)
out_filename = path;
out_fd = open_log_file();
if (out_fd < 0)
g_error("problem opening log file \"%s\" (config line %u) for "
"writing\n", path, line);
MPD_ERROR("problem opening log file \"%s\" (config line %u) "
"for writing\n", path, line);
g_log_set_default_handler(file_log_func, NULL);
}
@@ -216,8 +217,8 @@ parse_log_level(const char *value, unsigned line)
else if (0 == strcmp(value, "verbose"))
return G_LOG_LEVEL_DEBUG;
else {
g_error("unknown log level \"%s\" at line %u\n",
value, line);
MPD_ERROR("unknown log level \"%s\" at line %u\n",
value, line);
return G_LOG_LEVEL_MESSAGE;
}
}
@@ -252,8 +253,8 @@ void log_init(bool verbose, bool use_stdout)
available) */
log_init_syslog();
#else
g_error("config parameter \"%s\" not found\n",
CONF_LOG_FILE);
MPD_ERROR("config parameter \"%s\" not found\n",
CONF_LOG_FILE);
#endif
#ifdef HAVE_SYSLOG
} else if (strcmp(param->value, "syslog") == 0) {

@@ -54,6 +54,7 @@
#include "dirvec.h"
#include "songvec.h"
#include "tag_pool.h"
#include "mpd_error.h"
#ifdef ENABLE_INOTIFY
#include "inotify_update.h"
@@ -141,7 +142,7 @@ glue_db_init_and_load(void)
}
if (path == NULL)
g_error(CONF_DB_FILE " setting missing");
MPD_ERROR(CONF_DB_FILE " setting missing");
db_init(path);
@@ -175,7 +176,7 @@ glue_sticker_init(void)
success = sticker_global_init(config_get_path(CONF_STICKER_FILE),
&error);
if (!success)
g_error("%s", error->message);
MPD_ERROR("%s", error->message);
#endif
}
@@ -197,14 +198,14 @@ static void winsock_init(void)
retval = WSAStartup(MAKEWORD(2, 2), &sockinfo);
if(retval != 0)
{
g_error("Attempt to open Winsock2 failed; error code %d\n",
MPD_ERROR("Attempt to open Winsock2 failed; error code %d\n",
retval);
}
if (LOBYTE(sockinfo.wVersion) != 2)
{
g_error("We use Winsock2 but your version is either too new or "
"old; please install Winsock 2.x\n");
MPD_ERROR("We use Winsock2 but your version is either too new "
"or old; please install Winsock 2.x\n");
}
#endif
}
@@ -226,8 +227,8 @@ initialize_decoder_and_player(void)
if (param != NULL) {
buffer_size = strtol(param->value, &test, 10);
if (*test != '\0' || buffer_size <= 0)
g_error("buffer size \"%s\" is not a positive integer, "
"line %i\n", param->value, param->line);
MPD_ERROR("buffer size \"%s\" is not a positive integer, "
"line %i\n", param->value, param->line);
} else
buffer_size = DEFAULT_BUFFER_SIZE;
@@ -236,15 +237,15 @@ initialize_decoder_and_player(void)
buffered_chunks = buffer_size / CHUNK_SIZE;
if (buffered_chunks >= 1 << 15)
g_error("buffer size \"%li\" is too big\n", (long)buffer_size);
MPD_ERROR("buffer size \"%li\" is too big\n", (long)buffer_size);
param = config_get_param(CONF_BUFFER_BEFORE_PLAY);
if (param != NULL) {
perc = strtod(param->value, &test);
if (*test != '%' || perc < 0 || perc > 100) {
g_error("buffered before play \"%s\" is not a positive "
"percentage and less than 100 percent, line %i",
param->value, param->line);
MPD_ERROR("buffered before play \"%s\" is not a positive "
"percentage and less than 100 percent, line %i",
param->value, param->line);
}
} else
perc = DEFAULT_BUFFER_BEFORE_PLAY;
@@ -269,7 +270,25 @@ idle_event_emitted(void)
client_manager_idle_add(flags);
}
/**
* event_pipe callback function for PIPE_EVENT_SHUTDOWN
*/
static void
shutdown_event_emitted(void)
{
g_main_loop_quit(main_loop);
}
int main(int argc, char *argv[])
{
#ifdef WIN32
return win32_main(argc, argv);
#else
return mpd_main(argc, argv);
#endif
}
int mpd_main(int argc, char *argv[])
{
struct options options;
clock_t start;
@@ -324,6 +343,7 @@ int main(int argc, char *argv[])
event_pipe_init();
event_pipe_register(PIPE_EVENT_IDLE, idle_event_emitted);
event_pipe_register(PIPE_EVENT_SHUTDOWN, shutdown_event_emitted);
path_global_init();
glue_mapper_init();
@@ -371,7 +391,7 @@ int main(int argc, char *argv[])
database */
unsigned job = update_enqueue(NULL, true);
if (job == 0)
g_error("directory update failed");
MPD_ERROR("directory update failed");
}
glue_state_file_init();
@@ -392,10 +412,17 @@ int main(int argc, char *argv[])
playlist_state_restore() */
pc_update_audio();
/* run the main loop */
#ifdef WIN32
win32_app_started();
#endif
/* run the main loop */
g_main_loop_run(main_loop);
#ifdef WIN32
win32_app_stopping();
#endif
/* cleanup */
g_main_loop_unref(main_loop);

@@ -28,4 +28,45 @@ extern GMainLoop *main_loop;
extern GCond *main_cond;
/**
* A entry point for application.
* On non-Windows platforms this is called directly from main()
* On Windows platform this is called from win32_main()
* after doing some initialization.
*/
int mpd_main(int argc, char *argv[]);
#ifdef WIN32
/**
* If program is run as windows service performs nessesary initialization
* and then calls mpd_main() with specified arguments.
* If program is run as a regular application calls mpd_main() immediately.
*/
int
win32_main(int argc, char *argv[]);
/**
* When running as a service reports to service control manager
* that our service is started.
* When running as a console application enables console handler that will
* trigger PIPE_EVENT_SHUTDOWN when user closes console window
* or presses Ctrl+C.
* This function should be called just before entering main loop.
*/
void
win32_app_started(void);
/**
* When running as a service reports to service control manager
* that our service is about to stop.
* When running as a console application enables console handler that will
* catch all shutdown requests and ignore them.
* This function should be called just after leaving main loop.
*/
void
win32_app_stopping(void);
#endif
#endif

155
src/main_win32.c Normal file

@@ -0,0 +1,155 @@
/*
* Copyright (C) 2003-2010 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "config.h"
#include "main.h"
#ifdef WIN32
#include "mpd_error.h"
#include "event_pipe.h"
#include <glib.h>
#define WINVER 0x0501
#include <windows.h>
static int service_argc;
static char **service_argv;
static char service_name[] = "";
static BOOL ignore_console_events;
static SERVICE_STATUS_HANDLE service_handle;
static void WINAPI
service_main(DWORD argc, CHAR *argv[]);
static SERVICE_TABLE_ENTRY service_registry[] = {
{service_name, service_main},
{NULL, NULL}
};
static void
service_notify_status(DWORD status_code)
{
SERVICE_STATUS current_status;
current_status.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
current_status.dwControlsAccepted = status_code == SERVICE_START_PENDING
? 0
: SERVICE_ACCEPT_SHUTDOWN | SERVICE_ACCEPT_STOP;
current_status.dwCurrentState = status_code;
current_status.dwWin32ExitCode = NO_ERROR;
current_status.dwCheckPoint = 0;
current_status.dwWaitHint = 1000;
SetServiceStatus(service_handle, &current_status);
}
static DWORD WINAPI
service_dispatcher(G_GNUC_UNUSED DWORD control, G_GNUC_UNUSED DWORD event_type,
G_GNUC_UNUSED void *event_data, G_GNUC_UNUSED void *context)
{
switch (control) {
case SERVICE_CONTROL_SHUTDOWN:
case SERVICE_CONTROL_STOP:
event_pipe_emit(PIPE_EVENT_SHUTDOWN);
return NO_ERROR;
default:
return NO_ERROR;
}
}
static void WINAPI
service_main(G_GNUC_UNUSED DWORD argc, G_GNUC_UNUSED CHAR *argv[])
{
DWORD error_code;
gchar* error_message;
service_handle =
RegisterServiceCtrlHandlerEx(service_name,
service_dispatcher, NULL);
if (service_handle == 0) {
error_code = GetLastError();
error_message = g_win32_error_message(error_code);
MPD_ERROR("RegisterServiceCtrlHandlerEx() failed: %s",
error_message);
}
service_notify_status(SERVICE_START_PENDING);
mpd_main(service_argc, service_argv);
service_notify_status(SERVICE_STOPPED);
}
static BOOL WINAPI
console_handler(DWORD event)
{
switch (event) {
case CTRL_C_EVENT:
case CTRL_CLOSE_EVENT:
if (!ignore_console_events)
event_pipe_emit(PIPE_EVENT_SHUTDOWN);
return TRUE;
default:
return FALSE;
}
}
int win32_main(int argc, char *argv[])
{
DWORD error_code;
gchar* error_message;
service_argc = argc;
service_argv = argv;
if (StartServiceCtrlDispatcher(service_registry))
return 0; /* run as service successefully */
error_code = GetLastError();
if (error_code == ERROR_FAILED_SERVICE_CONTROLLER_CONNECT) {
/* running as console app */
SetConsoleTitle("Music Player Daemon");
ignore_console_events = TRUE;
SetConsoleCtrlHandler(console_handler, TRUE);
return mpd_main(argc, argv);
}
error_message = g_win32_error_message(error_code);
MPD_ERROR("StartServiceCtrlDispatcher() failed: %s", error_message);
}
void win32_app_started()
{
if (service_handle != 0)
service_notify_status(SERVICE_RUNNING);
else
ignore_console_events = FALSE;
}
void win32_app_stopping()
{
if (service_handle != 0)
service_notify_status(SERVICE_STOP_PENDING);
else
ignore_console_events = TRUE;
}
#endif

@@ -0,0 +1,114 @@
/*
* Copyright (C) 2003-2010 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "config.h"
#include "mixer_api.h"
#include "output_api.h"
#include "output/winmm_output_plugin.h"
#include <assert.h>
#include <math.h>
#include <windows.h>
#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "winmm_mixer"
struct winmm_mixer {
struct mixer base;
struct winmm_output *output;
};
static inline GQuark
winmm_mixer_quark(void)
{
return g_quark_from_static_string("winmm_mixer");
}
static inline int
winmm_volume_decode(DWORD volume)
{
return lround((volume & 0xFFFF) / 655.35);
}
static inline DWORD
winmm_volume_encode(int volume)
{
int value = lround(volume * 655.35);
return MAKELONG(value, value);
}
static struct mixer *
winmm_mixer_init(void *ao, G_GNUC_UNUSED const struct config_param *param,
G_GNUC_UNUSED GError **error_r)
{
assert(ao != NULL);
struct winmm_mixer *wm = g_new(struct winmm_mixer, 1);
mixer_init(&wm->base, &winmm_mixer_plugin);
wm->output = (struct winmm_output *) ao;
return &wm->base;
}
static void
winmm_mixer_finish(struct mixer *data)
{
g_free(data);
}
static int
winmm_mixer_get_volume(struct mixer *mixer, GError **error_r)
{
struct winmm_mixer *wm = (struct winmm_mixer *) mixer;
DWORD volume;
HWAVEOUT handle = winmm_output_get_handle(wm->output);
MMRESULT result = waveOutGetVolume(handle, &volume);
if (result != MMSYSERR_NOERROR) {
g_set_error(error_r, 0, winmm_mixer_quark(),
"Failed to get winmm volume");
return -1;
}
return winmm_volume_decode(volume);
}
static bool
winmm_mixer_set_volume(struct mixer *mixer, unsigned volume, GError **error_r)
{
struct winmm_mixer *wm = (struct winmm_mixer *) mixer;
DWORD value = winmm_volume_encode(volume);
HWAVEOUT handle = winmm_output_get_handle(wm->output);
MMRESULT result = waveOutSetVolume(handle, value);
if (result != MMSYSERR_NOERROR) {
g_set_error(error_r, 0, winmm_mixer_quark(),
"Failed to set winmm volume");
return false;
}
return true;
}
const struct mixer_plugin winmm_mixer_plugin = {
.init = winmm_mixer_init,
.finish = winmm_mixer_finish,
.get_volume = winmm_mixer_get_volume,
.set_volume = winmm_mixer_set_volume,
};

@@ -29,5 +29,6 @@ extern const struct mixer_plugin software_mixer_plugin;
extern const struct mixer_plugin alsa_mixer_plugin;
extern const struct mixer_plugin oss_mixer_plugin;
extern const struct mixer_plugin pulse_mixer_plugin;
extern const struct mixer_plugin winmm_mixer_plugin;
#endif

36
src/mpd_error.h Normal file

@@ -0,0 +1,36 @@
/*
* Copyright (C) 2003-2010 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef MPD_ERROR_H
#define MPD_ERROR_H
#include <stdlib.h>
/* This macro is used as an intermediate step to a proper error handling
* using GError in mpd. It is used for unrecoverable error conditions
* and exits immediately. The long-term goal is to replace this macro by
* proper error handling. */
#define MPD_ERROR(...) \
do { \
g_critical(__VA_ARGS__); \
exit(EXIT_FAILURE); \
} while(0)
#endif

@@ -49,3 +49,10 @@ void notify_signal(struct notify *notify)
g_cond_signal(notify->cond);
g_mutex_unlock(notify->mutex);
}
void notify_clear(struct notify *notify)
{
g_mutex_lock(notify->mutex);
notify->pending = false;
g_mutex_unlock(notify->mutex);
}

@@ -45,4 +45,9 @@ void notify_wait(struct notify *notify);
*/
void notify_signal(struct notify *notify);
/**
* Clears a pending notification.
*/
void notify_clear(struct notify *notify);
#endif

@@ -408,6 +408,26 @@ configure_hw:
}
audio_format->sample_rate = sample_rate;
snd_pcm_uframes_t buffer_size_min, buffer_size_max;
snd_pcm_hw_params_get_buffer_size_min(hwparams, &buffer_size_min);
snd_pcm_hw_params_get_buffer_size_max(hwparams, &buffer_size_max);
unsigned buffer_time_min, buffer_time_max;
snd_pcm_hw_params_get_buffer_time_min(hwparams, &buffer_time_min, 0);
snd_pcm_hw_params_get_buffer_time_max(hwparams, &buffer_time_max, 0);
g_debug("buffer: size=%u..%u time=%u..%u",
(unsigned)buffer_size_min, (unsigned)buffer_size_max,
buffer_time_min, buffer_time_max);
snd_pcm_uframes_t period_size_min, period_size_max;
snd_pcm_hw_params_get_period_size_min(hwparams, &period_size_min, 0);
snd_pcm_hw_params_get_period_size_max(hwparams, &period_size_max, 0);
unsigned period_time_min, period_time_max;
snd_pcm_hw_params_get_period_time_min(hwparams, &period_time_min, 0);
snd_pcm_hw_params_get_period_time_max(hwparams, &period_time_max, 0);
g_debug("period: size=%u..%u time=%u..%u",
(unsigned)period_size_min, (unsigned)period_size_max,
period_time_min, period_time_max);
if (ad->buffer_time > 0) {
buffer_time = ad->buffer_time;
cmd = "snd_pcm_hw_params_set_buffer_time_near";
@@ -488,6 +508,14 @@ configure_hw:
g_debug("buffer_size=%u period_size=%u",
(unsigned)alsa_buffer_size, (unsigned)alsa_period_size);
if (alsa_period_size == 0)
/* this works around a SIGFPE bug that occurred when
an ALSA driver indicated period_size==0; this
caused a division by zero in alsa_play(). By using
the fallback "1", we make sure that this won't
happen again. */
alsa_period_size = 1;
ad->period_frames = alsa_period_size;
ad->period_position = 0;

@@ -26,6 +26,9 @@
#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "ao"
/* An ao_sample_format, with all fields set to zero: */
static const ao_sample_format OUR_AO_FORMAT_INITIALIZER;
static unsigned ao_output_ref;
struct ao_data {
@@ -103,12 +106,14 @@ ao_output_init(G_GNUC_UNUSED const struct audio_format *audio_format,
g_set_error(error, ao_output_quark(), 0,
"\"%s\" is not a valid ao driver",
value);
g_free(ad);
return NULL;
}
if ((ai = ao_driver_info(ad->driver)) == NULL) {
g_set_error(error, ao_output_quark(), 0,
"problems getting driver info");
g_free(ad);
return NULL;
}
@@ -126,6 +131,7 @@ ao_output_init(G_GNUC_UNUSED const struct audio_format *audio_format,
g_set_error(error, ao_output_quark(), 0,
"problems parsing options \"%s\"",
options[i]);
g_free(ad);
return NULL;
}
@@ -167,7 +173,7 @@ static bool
ao_output_open(void *data, struct audio_format *audio_format,
GError **error)
{
ao_sample_format format;
ao_sample_format format = OUR_AO_FORMAT_INITIALIZER;
struct ao_data *ad = (struct ao_data *)data;
switch (audio_format->format) {

@@ -0,0 +1,347 @@
/*
* Copyright (C) 2003-2010 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
/*
* Warning: this plugin was not tested successfully. I just couldn't
* keep libffado2 from crashing. Use at your own risk.
*
* For details, see my Debian bug reports:
*
* http://bugs.debian.org/601657
* http://bugs.debian.org/601659
* http://bugs.debian.org/601663
*
*/
#include "config.h"
#include "output_api.h"
#include "timer.h"
#include <glib.h>
#include <assert.h>
#include <libffado/ffado.h>
#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "ffado"
enum {
MAX_STREAMS = 8,
};
struct mpd_ffado_stream {
/** libffado's stream number */
int number;
float *buffer;
};
struct mpd_ffado_device {
char *device_name;
int verbose;
unsigned period_size, nb_buffers;
ffado_device_t *dev;
/**
* The current sample position inside the stream buffers. New
* samples get appended at this position on all streams at the
* same time. When the buffers are full
* (buffer_position==period_size),
* ffado_streaming_transfer_playback_buffers() gets called to
* hand them over to libffado.
*/
unsigned buffer_position;
/**
* The number of streams which are really used by MPD.
*/
int num_streams;
struct mpd_ffado_stream streams[MAX_STREAMS];
};
static inline GQuark
ffado_output_quark(void)
{
return g_quark_from_static_string("ffado_output");
}
static void *
ffado_init(G_GNUC_UNUSED const struct audio_format *audio_format,
const struct config_param *param,
GError **error_r)
{
g_debug("using libffado version %s, API=%d",
ffado_get_version(), ffado_get_api_version());
struct mpd_ffado_device *fd = g_new(struct mpd_ffado_device, 1);
fd->device_name = config_dup_block_string(param, "device", NULL);
fd->verbose = config_get_block_unsigned(param, "verbose", 0);
fd->period_size = config_get_block_unsigned(param, "period_size",
1024);
if (fd->period_size == 0 || fd->period_size > 1024 * 1024) {
g_set_error(error_r, ffado_output_quark(), 0,
"invalid period_size setting");
return false;
}
fd->nb_buffers = config_get_block_unsigned(param, "nb_buffers", 3);
if (fd->nb_buffers == 0 || fd->nb_buffers > 1024) {
g_set_error(error_r, ffado_output_quark(), 0,
"invalid nb_buffers setting");
return false;
}
return fd;
}
static void
ffado_finish(void *data)
{
struct mpd_ffado_device *fd = data;
g_free(fd->device_name);
g_free(fd);
}
static bool
ffado_configure_stream(ffado_device_t *dev, struct mpd_ffado_stream *stream,
GError **error_r)
{
char *buffer = (char *)stream->buffer;
if (ffado_streaming_set_playback_stream_buffer(dev, stream->number,
buffer) != 0) {
g_set_error(error_r, ffado_output_quark(), 0,
"failed to configure stream buffer");
return false;
}
if (ffado_streaming_playback_stream_onoff(dev, stream->number,
1) != 0) {
g_set_error(error_r, ffado_output_quark(), 0,
"failed to disable stream");
return false;
}
return true;
}
static bool
ffado_configure(struct mpd_ffado_device *fd, struct audio_format *audio_format,
GError **error_r)
{
assert(fd != NULL);
assert(fd->dev != NULL);
assert(audio_format->channels <= MAX_STREAMS);
if (ffado_streaming_set_audio_datatype(fd->dev,
ffado_audio_datatype_float) != 0) {
g_set_error(error_r, ffado_output_quark(), 0,
"ffado_streaming_set_audio_datatype() failed");
return false;
}
int num_streams = ffado_streaming_get_nb_playback_streams(fd->dev);
if (num_streams < 0) {
g_set_error(error_r, ffado_output_quark(), 0,
"ffado_streaming_get_nb_playback_streams() failed");
return false;
}
g_debug("there are %d playback streams", num_streams);
fd->num_streams = 0;
for (int i = 0; i < num_streams; ++i) {
char name[256];
ffado_streaming_get_playback_stream_name(fd->dev, i, name,
sizeof(name) - 1);
ffado_streaming_stream_type type =
ffado_streaming_get_playback_stream_type(fd->dev, i);
if (type != ffado_stream_type_audio) {
g_debug("stream %d name='%s': not an audio stream",
i, name);
continue;
}
if (fd->num_streams >= audio_format->channels) {
g_debug("stream %d name='%s': ignoring",
i, name);
continue;
}
g_debug("stream %d name='%s'", i, name);
struct mpd_ffado_stream *stream =
&fd->streams[fd->num_streams++];
stream->number = i;
/* allocated buffer is zeroed = silence */
stream->buffer = g_new0(float, fd->period_size);
if (!ffado_configure_stream(fd->dev, stream, error_r))
return false;
}
if (!audio_valid_channel_count(fd->num_streams)) {
g_set_error(error_r, ffado_output_quark(), 0,
"invalid channel count from libffado: %u",
audio_format->channels);
return false;
}
g_debug("configured %d audio streams", fd->num_streams);
if (ffado_streaming_prepare(fd->dev) != 0) {
g_set_error(error_r, ffado_output_quark(), 0,
"ffado_streaming_prepare() failed");
return false;
}
if (ffado_streaming_start(fd->dev) != 0) {
g_set_error(error_r, ffado_output_quark(), 0,
"ffado_streaming_start() failed");
return false;
}
audio_format->channels = fd->num_streams;
return true;
}
static bool
ffado_open(void *data, struct audio_format *audio_format, GError **error_r)
{
struct mpd_ffado_device *fd = data;
/* will be converted to floating point, choose best input
format */
audio_format->format = SAMPLE_FORMAT_S24_P32;
ffado_device_info_t device_info;
memset(&device_info, 0, sizeof(device_info));
if (fd->device_name != NULL) {
device_info.nb_device_spec_strings = 1;
device_info.device_spec_strings = &fd->device_name;
}
ffado_options_t options;
memset(&options, 0, sizeof(options));
options.sample_rate = audio_format->sample_rate;
options.period_size = fd->period_size;
options.nb_buffers = fd->nb_buffers;
options.verbose = fd->verbose;
fd->dev = ffado_streaming_init(device_info, options);
if (fd->dev == NULL) {
g_set_error(error_r, ffado_output_quark(), 0,
"ffado_streaming_init() failed");
return false;
}
if (!ffado_configure(fd, audio_format, error_r)) {
ffado_streaming_finish(fd->dev);
for (int i = 0; i < fd->num_streams; ++i) {
struct mpd_ffado_stream *stream = &fd->streams[i];
g_free(stream->buffer);
}
return false;
}
fd->buffer_position = 0;
return true;
}
static void
ffado_close(void *data)
{
struct mpd_ffado_device *fd = data;
ffado_streaming_stop(fd->dev);
ffado_streaming_finish(fd->dev);
for (int i = 0; i < fd->num_streams; ++i) {
struct mpd_ffado_stream *stream = &fd->streams[i];
g_free(stream->buffer);
}
}
static size_t
ffado_play(void *data, const void *chunk, size_t size, GError **error_r)
{
struct mpd_ffado_device *fd = data;
/* wait for prefious buffer to finish (if it was full) */
if (fd->buffer_position >= fd->period_size) {
switch (ffado_streaming_wait(fd->dev)) {
case ffado_wait_ok:
case ffado_wait_xrun:
break;
default:
g_set_error(error_r, ffado_output_quark(), 0,
"ffado_streaming_wait() failed");
return 0;
}
fd->buffer_position = 0;
}
/* copy samples to stream buffers, non-interleaved */
const int32_t *p = chunk;
unsigned num_frames = size / sizeof(*p) / fd->num_streams;
if (num_frames > fd->period_size - fd->buffer_position)
num_frames = fd->period_size - fd->buffer_position;
for (unsigned i = num_frames; i > 0; --i) {
for (int stream = 0; stream < fd->num_streams; ++stream)
fd->streams[stream].buffer[fd->buffer_position] =
*p++ / (float)(1 << 23);
++fd->buffer_position;
}
/* if buffer full, transfer to device */
if (fd->buffer_position >= fd->period_size &&
/* libffado documentation says this function returns -1 on
error, but that is a lie - it returns a boolean value,
and "false" means error */
!ffado_streaming_transfer_playback_buffers(fd->dev)) {
g_set_error(error_r, ffado_output_quark(), 0,
"ffado_streaming_transfer_playback_buffers() failed");
return 0;
}
return num_frames * sizeof(*p) * fd->num_streams;
}
const struct audio_output_plugin ffado_output_plugin = {
.name = "ffado",
.init = ffado_init,
.finish = ffado_finish,
.open = ffado_open,
.close = ffado_close,
.play = ffado_play,
};

@@ -29,6 +29,9 @@
#include <assert.h>
#include <string.h>
#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "httpd_output"
struct httpd_client {
/**
* The httpd output object this client is connected to.
@@ -140,6 +143,8 @@ httpd_client_unref_page(gpointer data, G_GNUC_UNUSED gpointer user_data)
void
httpd_client_free(struct httpd_client *client)
{
assert(client != NULL);
if (client->state == RESPONSE) {
if (client->write_source_id != 0)
g_source_remove(client->write_source_id);
@@ -166,6 +171,8 @@ httpd_client_free(struct httpd_client *client)
static void
httpd_client_close(struct httpd_client *client)
{
assert(client != NULL);
httpd_output_remove_client(client->httpd, client);
httpd_client_free(client);
}
@@ -176,6 +183,9 @@ httpd_client_close(struct httpd_client *client)
static void
httpd_client_begin_response(struct httpd_client *client)
{
assert(client != NULL);
assert(client->state != RESPONSE);
client->state = RESPONSE;
client->write_source_id = 0;
client->pages = g_queue_new();
@@ -236,6 +246,9 @@ httpd_client_handle_line(struct httpd_client *client, const char *line)
static char *
httpd_client_read_line(struct httpd_client *client)
{
assert(client != NULL);
assert(client->state != RESPONSE);
const char *p, *newline;
size_t length;
char *line;
@@ -268,6 +281,7 @@ httpd_client_send_response(struct httpd_client *client)
GIOStatus status;
gsize bytes_written;
assert(client != NULL);
assert(client->state == RESPONSE);
if (!client->metadata_requested) {
@@ -331,14 +345,19 @@ httpd_client_send_response(struct httpd_client *client)
static bool
httpd_client_received(struct httpd_client *client)
{
assert(client != NULL);
assert(client->state != RESPONSE);
char *line;
bool success;
while ((line = httpd_client_read_line(client)) != NULL) {
success = httpd_client_handle_line(client, line);
g_free(line);
if (!success)
if (!success) {
assert(client->state != RESPONSE);
return false;
}
if (client->state == RESPONSE) {
if (!fifo_buffer_is_empty(client->input)) {
@@ -367,7 +386,14 @@ httpd_client_read(struct httpd_client *client)
if (client->state == RESPONSE) {
/* the client has already sent the request, and he
must not send more */
g_warning("unexpected input from client");
char buffer[1];
status = g_io_channel_read_chars(client->channel, buffer,
sizeof(buffer), &bytes_read,
NULL);
if (status == G_IO_STATUS_NORMAL)
g_warning("unexpected input from client");
return false;
}

@@ -29,12 +29,6 @@
#include <glib.h>
#ifdef WIN32
#include <winsock2.h>
#include <ws2tcpip.h>
#else
#include <sys/socket.h>
#endif
#include <stdbool.h>
struct httpd_client;
@@ -51,21 +45,19 @@ struct httpd_output {
*/
struct encoder *encoder;
/**
* Number of bytes which were fed into the encoder, without
* ever receiving new output. This is used to estimate
* whether MPD should manually flush the encoder, to avoid
* buffer underruns in the client.
*/
size_t unflushed_input;
/**
* The MIME type produced by the #encoder.
*/
const char *content_type;
/**
* The configured address of the listener socket.
*/
struct sockaddr_storage address;
/**
* The size of #address.
*/
socklen_t address_size;
/**
* This mutex protects the listener socket and the client
* list.
@@ -81,12 +73,7 @@ struct httpd_output {
/**
* The listener socket.
*/
int fd;
/**
* A GLib main loop source id for the listener socket.
*/
guint source_id;
struct server_socket *server_socket;
/**
* The header page, which is sent to every client on connect.
@@ -124,7 +111,7 @@ struct httpd_output {
char buffer[32768];
/**
* The maximum and current number of clients connected
* The maximum and current number of clients connected
* at the same time.
*/
guint clients_max, clients_cnt;

@@ -27,21 +27,16 @@
#include "page.h"
#include "icy_server.h"
#include "fd_util.h"
#include "server_socket.h"
#include <assert.h>
#include <sys/types.h>
#ifdef WIN32
#include <winsock2.h>
#include <ws2tcpip.h>
#else
#include <netinet/in.h>
#include <netdb.h>
#endif
#include <unistd.h>
#include <errno.h>
#ifdef HAVE_LIBWRAP
#include <sys/socket.h> /* needed for AF_UNIX */
#include <tcpd.h>
#endif
@@ -57,37 +52,20 @@ httpd_output_quark(void)
return g_quark_from_static_string("httpd_output");
}
static gboolean
httpd_listen_in_event(G_GNUC_UNUSED GIOChannel *source,
G_GNUC_UNUSED GIOCondition condition,
gpointer data);
static void
httpd_listen_in_event(int fd, const struct sockaddr *address,
size_t address_length, int uid, void *ctx);
static bool
httpd_output_bind(struct httpd_output *httpd, GError **error_r)
{
GIOChannel *channel;
httpd->open = false;
/* create and set up listener socket */
httpd->fd = socket_bind_listen(PF_INET, SOCK_STREAM, 0,
(struct sockaddr *)&httpd->address,
httpd->address_size,
16, error_r);
if (httpd->fd < 0)
return false;
g_mutex_lock(httpd->mutex);
channel = g_io_channel_unix_new(httpd->fd);
httpd->source_id = g_io_add_watch(channel, G_IO_IN,
httpd_listen_in_event, httpd);
g_io_channel_unref(channel);
bool success = server_socket_open(httpd->server_socket, error_r);
g_mutex_unlock(httpd->mutex);
return true;
return success;
}
static void
@@ -96,10 +74,7 @@ httpd_output_unbind(struct httpd_output *httpd)
assert(!httpd->open);
g_mutex_lock(httpd->mutex);
g_source_remove(httpd->source_id);
close(httpd->fd);
server_socket_close(httpd->server_socket);
g_mutex_unlock(httpd->mutex);
}
@@ -109,10 +84,9 @@ httpd_output_init(G_GNUC_UNUSED const struct audio_format *audio_format,
GError **error)
{
struct httpd_output *httpd = g_new(struct httpd_output, 1);
const char *encoder_name;
const char *encoder_name, *bind_to_address;
const struct encoder_plugin *encoder_plugin;
guint port;
struct sockaddr_in *sin;
/* read configuration */
httpd->name =
@@ -129,22 +103,29 @@ httpd_output_init(G_GNUC_UNUSED const struct audio_format *audio_format,
if (encoder_plugin == NULL) {
g_set_error(error, httpd_output_quark(), 0,
"No such encoder: %s", encoder_name);
g_free(httpd);
return NULL;
}
httpd->clients_max = config_get_block_unsigned(param,"max_clients", 0);
/* initialize listen address */
/* set up bind_to_address */
sin = (struct sockaddr_in *)&httpd->address;
memset(sin, 0, sizeof(sin));
sin->sin_port = htons(port);
sin->sin_family = AF_INET;
sin->sin_addr.s_addr = INADDR_ANY;
httpd->address_size = sizeof(*sin);
httpd->server_socket = server_socket_new(httpd_listen_in_event, httpd);
bind_to_address =
config_get_block_string(param, "bind_to_address", NULL);
bool success = bind_to_address != NULL &&
strcmp(bind_to_address, "any") != 0
? server_socket_add_host(httpd->server_socket, bind_to_address,
port, error)
: server_socket_add_port(httpd->server_socket, port, error);
if (!success)
return NULL;
/* initialize metadata */
httpd->metadata = NULL;
httpd->unflushed_input = 0;
/* initialize encoder */
@@ -172,6 +153,7 @@ httpd_output_finish(void *data)
page_unref(httpd->metadata);
encoder_finish(httpd->encoder);
server_socket_free(httpd->server_socket);
g_mutex_free(httpd->mutex);
g_free(httpd);
}
@@ -195,27 +177,18 @@ httpd_client_add(struct httpd_output *httpd, int fd)
httpd_client_send_metadata(client, httpd->metadata);
}
static gboolean
httpd_listen_in_event(G_GNUC_UNUSED GIOChannel *source,
G_GNUC_UNUSED GIOCondition condition,
gpointer data)
static void
httpd_listen_in_event(int fd, const struct sockaddr *address,
size_t address_length, G_GNUC_UNUSED int uid, void *ctx)
{
struct httpd_output *httpd = data;
int fd;
struct sockaddr_storage sa;
size_t sa_length = sizeof(sa);
g_mutex_lock(httpd->mutex);
struct httpd_output *httpd = ctx;
/* the listener socket has become readable - a client has
connected */
fd = accept_cloexec_nonblock(httpd->fd, (struct sockaddr*)&sa,
&sa_length);
#ifdef HAVE_LIBWRAP
struct sockaddr *sa_p = (struct sockaddr *)&sa;
if (sa_p->sa_family != AF_UNIX) {
char *hostaddr = sockaddr_to_string(sa_p, sa_length, NULL);
if (address->sa_family != AF_UNIX) {
char *hostaddr = sockaddr_to_string(address, address_length, NULL);
const char *progname = g_get_prgname();
struct request_info req;
@@ -230,12 +203,18 @@ httpd_listen_in_event(G_GNUC_UNUSED GIOChannel *source,
g_free(hostaddr);
close(fd);
g_mutex_unlock(httpd->mutex);
return true;
return;
}
g_free(hostaddr);
}
#else
(void)address;
(void)address_length;
#endif /* HAVE_WRAP */
g_mutex_lock(httpd->mutex);
if (fd >= 0) {
/* can we allow additional client */
if (httpd->open &&
@@ -249,8 +228,6 @@ httpd_listen_in_event(G_GNUC_UNUSED GIOChannel *source,
}
g_mutex_unlock(httpd->mutex);
return true;
}
/**
@@ -262,12 +239,22 @@ httpd_output_read_page(struct httpd_output *httpd)
{
size_t size = 0, nbytes;
if (httpd->unflushed_input >= 65536) {
/* we have fed a lot of input into the encoder, but it
didn't give anything back yet - flush now to avoid
buffer underruns */
encoder_flush(httpd->encoder, NULL);
httpd->unflushed_input = 0;
}
do {
nbytes = encoder_read(httpd->encoder, httpd->buffer + size,
sizeof(httpd->buffer) - size);
if (nbytes == 0)
break;
httpd->unflushed_input = 0;
size += nbytes;
} while (size < sizeof(httpd->buffer));
@@ -292,6 +279,9 @@ httpd_output_encoder_open(struct httpd_output *httpd,
bytes of encoder output after opening it, because it has to
be sent to every new client */
httpd->header = httpd_output_read_page(httpd);
httpd->unflushed_input = 0;
return true;
}
@@ -324,8 +314,6 @@ httpd_output_open(void *data, struct audio_format *audio_format,
success = httpd_output_encoder_open(httpd, audio_format, error);
if (!success) {
g_source_remove(httpd->source_id);
close(httpd->fd);
g_mutex_unlock(httpd->mutex);
return false;
}
@@ -390,6 +378,16 @@ httpd_output_send_header(struct httpd_output *httpd,
httpd_client_send(client, httpd->header);
}
static unsigned
httpd_output_delay(void *data)
{
struct httpd_output *httpd = data;
return httpd->timer->started
? timer_delay(httpd->timer)
: 0;
}
static void
httpd_client_check_queue(gpointer data, G_GNUC_UNUSED gpointer user_data)
{
@@ -451,6 +449,8 @@ httpd_output_encode_and_play(struct httpd_output *httpd,
if (!success)
return false;
httpd->unflushed_input += size;
httpd_output_encoder_to_clients(httpd);
return true;
@@ -477,13 +477,30 @@ httpd_output_play(void *data, const void *chunk, size_t size, GError **error)
if (!httpd->timer->started)
timer_start(httpd->timer);
else
timer_sync(httpd->timer);
timer_add(httpd->timer, size);
return size;
}
static bool
httpd_output_pause(void *data)
{
struct httpd_output *httpd = data;
g_mutex_lock(httpd->mutex);
bool has_clients = httpd->clients != NULL;
g_mutex_unlock(httpd->mutex);
if (has_clients) {
static const char silence[1020];
return httpd_output_play(data, silence, sizeof(silence),
NULL) > 0;
} else {
g_usleep(100000);
return true;
}
}
static void
httpd_send_metadata(gpointer data, gpointer user_data)
{
@@ -506,7 +523,7 @@ httpd_output_tag(void *data, const struct tag *tag)
/* flush the current stream, and end it */
encoder_flush(httpd->encoder, NULL);
encoder_pre_tag(httpd->encoder, NULL);
httpd_output_encoder_to_clients(httpd);
/* send the tag to the encoder - which starts a new
@@ -570,7 +587,9 @@ const struct audio_output_plugin httpd_output_plugin = {
.disable = httpd_output_disable,
.open = httpd_output_open,
.close = httpd_output_close,
.delay = httpd_output_delay,
.send_tag = httpd_output_tag,
.play = httpd_output_play,
.pause = httpd_output_pause,
.cancel = httpd_output_cancel,
};

@@ -40,7 +40,7 @@ enum {
MAX_PORTS = 16,
};
static const size_t sample_size = sizeof(jack_default_audio_sample_t);
static const size_t jack_sample_size = sizeof(jack_default_audio_sample_t);
struct jack_data {
/**
@@ -103,9 +103,9 @@ mpd_jack_available(const struct jack_data *jd)
min = current;
}
assert(min % sample_size == 0);
assert(min % jack_sample_size == 0);
return min / sample_size;
return min / jack_sample_size;
}
static int
@@ -123,7 +123,7 @@ mpd_jack_process(jack_nframes_t nframes, void *arg)
const jack_nframes_t available = mpd_jack_available(jd);
for (unsigned i = 0; i < jd->audio_format.channels; ++i)
jack_ringbuffer_read_advance(jd->ringbuffer[i],
available * sample_size);
available * jack_sample_size);
/* generate silence while MPD is paused */
@@ -144,7 +144,7 @@ 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);
jack_ringbuffer_read(jd->ringbuffer[i],
(char *)out, available * sample_size);
(char *)out, available * jack_sample_size);
for (jack_nframes_t f = available; f < nframes; ++f)
/* ringbuffer underrun, fill with silence */
@@ -675,7 +675,7 @@ mpd_jack_play(void *data, const void *chunk, size_t size, GError **error_r)
space = space1;
}
if (space >= frame_size)
if (space >= jack_sample_size)
break;
/* XXX do something more intelligent to
@@ -683,7 +683,7 @@ mpd_jack_play(void *data, const void *chunk, size_t size, GError **error_r)
g_usleep(1000);
}
space /= sample_size;
space /= jack_sample_size;
if (space < size)
size = space;

@@ -17,7 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
/*
/*
* Media MVP audio output based on code from MVPMC project:
* http://mvpmc.sourceforge.net/
*/

@@ -41,6 +41,15 @@
# include <sys/soundcard.h>
#endif /* !(defined(__OpenBSD__) || defined(__NetBSD__) */
/* We got bug reports from FreeBSD users who said that the two 24 bit
formats generate white noise on FreeBSD, but 32 bit works. This is
a workaround until we know what exactly is expected by the kernel
audio drivers. */
#ifndef __linux__
#undef AFMT_S24_PACKED
#undef AFMT_S24_NE
#endif
struct oss_data {
int fd;
const char *device;
@@ -347,7 +356,7 @@ oss_setup_sample_rate(int fd, struct audio_format *audio_format,
case SUCCESS:
if (!audio_valid_sample_rate(sample_rate))
break;
audio_format->sample_rate = sample_rate;
return true;
@@ -461,6 +470,12 @@ oss_setup_sample_format(int fd, struct audio_format *audio_format,
break;
audio_format->format = mpd_format;
#ifdef AFMT_S24_PACKED
if (oss_format == AFMT_S24_PACKED)
audio_format->reverse_endian =
G_BYTE_ORDER != G_LITTLE_ENDIAN;
#endif
return true;
case ERROR:
@@ -502,6 +517,12 @@ oss_setup_sample_format(int fd, struct audio_format *audio_format,
break;
audio_format->format = mpd_format;
#ifdef AFMT_S24_PACKED
if (oss_format == AFMT_S24_PACKED)
audio_format->reverse_endian =
G_BYTE_ORDER != G_LITTLE_ENDIAN;
#endif
return true;
case ERROR:

@@ -95,12 +95,6 @@ static void osx_output_close(void *data)
{
struct osx_output *od = data;
g_mutex_lock(od->mutex);
while (od->len) {
g_cond_wait(od->condition, od->mutex);
}
g_mutex_unlock(od->mutex);
AudioOutputUnitStop(od->au);
AudioUnitUninitialize(od->au);
CloseComponent(od->au);
@@ -143,8 +137,8 @@ osx_render(void *vdata,
if (od->pos >= od->buffer_size)
od->pos = 0;
g_mutex_unlock(od->mutex);
g_cond_signal(od->condition);
g_mutex_unlock(od->mutex);
buffer->mDataByteSize = buffer_size;
@@ -214,15 +208,6 @@ osx_output_open(void *data, struct audio_format *audio_format, GError **error)
stream_description.mSampleRate = audio_format->sample_rate;
stream_description.mFormatID = kAudioFormatLinearPCM;
stream_description.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger;
#if G_BYTE_ORDER == G_BIG_ENDIAN
stream_description.mFormatFlags |= kLinearPCMFormatFlagIsBigEndian;
#endif
stream_description.mBytesPerPacket =
audio_format_frame_size(audio_format);
stream_description.mFramesPerPacket = 1;
stream_description.mBytesPerFrame = stream_description.mBytesPerPacket;
stream_description.mChannelsPerFrame = audio_format->channels;
switch (audio_format->format) {
case SAMPLE_FORMAT_S8:
@@ -239,6 +224,16 @@ osx_output_open(void *data, struct audio_format *audio_format, GError **error)
break;
}
#if G_BYTE_ORDER == G_BIG_ENDIAN
stream_description.mFormatFlags |= kLinearPCMFormatFlagIsBigEndian;
#endif
stream_description.mBytesPerPacket =
audio_format_frame_size(audio_format);
stream_description.mFramesPerPacket = 1;
stream_description.mBytesPerFrame = stream_description.mBytesPerPacket;
stream_description.mChannelsPerFrame = audio_format->channels;
result = AudioUnitSetProperty(od->au, kAudioUnitProperty_StreamFormat,
kAudioUnitScope_Input, 0,
&stream_description,

@@ -207,6 +207,9 @@ pulse_output_subscribe_cb(pa_context *context,
static bool
pulse_output_connect(struct pulse_output *po, GError **error_r)
{
assert(po != NULL);
assert(po->context != NULL);
int error;
error = pa_context_connect(po->context, po->server,
@@ -221,6 +224,44 @@ pulse_output_connect(struct pulse_output *po, GError **error_r)
return true;
}
/**
* Frees and clears the stream.
*/
static void
pulse_output_delete_stream(struct pulse_output *po)
{
assert(po != NULL);
assert(po->stream != NULL);
#if PA_CHECK_VERSION(0,9,8)
pa_stream_set_suspended_callback(po->stream, NULL, NULL);
#endif
pa_stream_set_state_callback(po->stream, NULL, NULL);
pa_stream_set_write_callback(po->stream, NULL, NULL);
pa_stream_disconnect(po->stream);
pa_stream_unref(po->stream);
po->stream = NULL;
}
/**
* Frees and clears the context.
*/
static void
pulse_output_delete_context(struct pulse_output *po)
{
assert(po != NULL);
assert(po->context != NULL);
pa_context_set_state_callback(po->context, NULL, NULL);
pa_context_set_subscribe_callback(po->context, NULL, NULL);
pa_context_disconnect(po->context);
pa_context_unref(po->context);
po->context = NULL;
}
/**
* Create, set up and connect a context.
*
@@ -229,6 +270,9 @@ pulse_output_connect(struct pulse_output *po, GError **error_r)
static bool
pulse_output_setup_context(struct pulse_output *po, GError **error_r)
{
assert(po != NULL);
assert(po->mainloop != NULL);
po->context = pa_context_new(pa_threaded_mainloop_get_api(po->mainloop),
MPD_PULSE_NAME);
if (po->context == NULL) {
@@ -243,25 +287,13 @@ pulse_output_setup_context(struct pulse_output *po, GError **error_r)
pulse_output_subscribe_cb, po);
if (!pulse_output_connect(po, error_r)) {
pa_context_unref(po->context);
po->context = NULL;
pulse_output_delete_context(po);
return false;
}
return true;
}
/**
* Frees and clears the context.
*/
static void
pulse_output_delete_context(struct pulse_output *po)
{
pa_context_disconnect(po->context);
pa_context_unref(po->context);
po->context = NULL;
}
static void *
pulse_output_init(G_GNUC_UNUSED const struct audio_format *audio_format,
const struct config_param *param,
@@ -347,6 +379,8 @@ pulse_output_disable(void *data)
{
struct pulse_output *po = data;
assert(po->mainloop != NULL);
pa_threaded_mainloop_stop(po->mainloop);
if (po->context != NULL)
pulse_output_delete_context(po);
@@ -363,6 +397,8 @@ pulse_output_disable(void *data)
static bool
pulse_output_wait_connection(struct pulse_output *po, GError **error_r)
{
assert(po->mainloop != NULL);
pa_context_state_t state;
pa_threaded_mainloop_lock(po->mainloop);
@@ -399,11 +435,32 @@ pulse_output_wait_connection(struct pulse_output *po, GError **error_r)
}
}
#if PA_CHECK_VERSION(0,9,8)
static void
pulse_output_stream_suspended_cb(G_GNUC_UNUSED pa_stream *stream, void *userdata)
{
struct pulse_output *po = userdata;
assert(stream == po->stream || po->stream == NULL);
assert(po->mainloop != NULL);
/* wake up the main loop to break out of the loop in
pulse_output_play() */
pa_threaded_mainloop_signal(po->mainloop, 0);
}
#endif
static void
pulse_output_stream_state_cb(pa_stream *stream, void *userdata)
{
struct pulse_output *po = userdata;
assert(stream == po->stream || po->stream == NULL);
assert(po->mainloop != NULL);
assert(po->context != NULL);
switch (pa_stream_get_state(stream)) {
case PA_STREAM_READY:
if (po->mixer != NULL)
@@ -432,6 +489,8 @@ pulse_output_stream_write_cb(G_GNUC_UNUSED pa_stream *stream, size_t nbytes,
{
struct pulse_output *po = userdata;
assert(po->mainloop != NULL);
po->writable = nbytes;
pa_threaded_mainloop_signal(po->mainloop, 0);
}
@@ -444,6 +503,8 @@ pulse_output_open(void *data, struct audio_format *audio_format,
pa_sample_spec ss;
int error;
assert(po->mainloop != NULL);
if (po->context != NULL) {
switch (pa_context_get_state(po->context)) {
case PA_CONTEXT_UNCONNECTED:
@@ -487,6 +548,11 @@ pulse_output_open(void *data, struct audio_format *audio_format,
return false;
}
#if PA_CHECK_VERSION(0,9,8)
pa_stream_set_suspended_callback(po->stream,
pulse_output_stream_suspended_cb, po);
#endif
pa_stream_set_state_callback(po->stream,
pulse_output_stream_state_cb, po);
pa_stream_set_write_callback(po->stream,
@@ -497,8 +563,7 @@ pulse_output_open(void *data, struct audio_format *audio_format,
error = pa_stream_connect_playback(po->stream, po->sink,
NULL, 0, NULL, NULL);
if (error < 0) {
pa_stream_unref(po->stream);
po->stream = NULL;
pulse_output_delete_stream(po);
g_set_error(error_r, pulse_output_quark(), 0,
"pa_stream_connect_playback() has failed: %s",
@@ -522,6 +587,8 @@ pulse_output_close(void *data)
struct pulse_output *po = data;
pa_operation *o;
assert(po->mainloop != NULL);
pa_threaded_mainloop_lock(po->mainloop);
if (pa_stream_get_state(po->stream) == PA_STREAM_READY) {
@@ -534,9 +601,7 @@ pulse_output_close(void *data)
pulse_wait_for_operation(po->mainloop, o);
}
pa_stream_disconnect(po->stream);
pa_stream_unref(po->stream);
po->stream = NULL;
pulse_output_delete_stream(po);
if (po->context != NULL &&
pa_context_get_state(po->context) != PA_CONTEXT_READY)
@@ -556,6 +621,8 @@ pulse_output_check_stream(struct pulse_output *po)
{
pa_stream_state_t state = pa_stream_get_state(po->stream);
assert(po->mainloop != NULL);
switch (state) {
case PA_STREAM_READY:
case PA_STREAM_FAILED:
@@ -637,6 +704,8 @@ pulse_output_stream_pause(struct pulse_output *po, bool pause,
{
pa_operation *o;
assert(po->mainloop != NULL);
assert(po->context != NULL);
assert(po->stream != NULL);
o = pa_stream_cork(po->stream, pause,
@@ -667,6 +736,7 @@ pulse_output_play(void *data, const void *chunk, size_t size, GError **error_r)
struct pulse_output *po = data;
int error;
assert(po->mainloop != NULL);
assert(po->stream != NULL);
pa_threaded_mainloop_lock(po->mainloop);
@@ -683,19 +753,30 @@ pulse_output_play(void *data, const void *chunk, size_t size, GError **error_r)
/* unpause if previously paused */
if (pulse_output_stream_is_paused(po) &&
!pulse_output_stream_pause(po, false, error_r))
!pulse_output_stream_pause(po, false, error_r)) {
pa_threaded_mainloop_unlock(po->mainloop);
return 0;
}
/* wait until the server allows us to write */
while (po->writable == 0) {
#if PA_CHECK_VERSION(0,9,8)
if (pa_stream_is_suspended(po->stream)) {
pa_threaded_mainloop_unlock(po->mainloop);
g_set_error(error_r, pulse_output_quark(), 0,
"suspended");
return 0;
}
#endif
pa_threaded_mainloop_wait(po->mainloop);
if (pa_stream_get_state(po->stream) != PA_STREAM_READY) {
pa_threaded_mainloop_unlock(po->mainloop);
g_set_error(error_r, pulse_output_quark(), 0,
"disconnected");
return false;
return 0;
}
}
@@ -725,6 +806,7 @@ pulse_output_cancel(void *data)
struct pulse_output *po = data;
pa_operation *o;
assert(po->mainloop != NULL);
assert(po->stream != NULL);
pa_threaded_mainloop_lock(po->mainloop);
@@ -756,6 +838,7 @@ pulse_output_pause(void *data)
struct pulse_output *po = data;
GError *error = NULL;
assert(po->mainloop != NULL);
assert(po->stream != NULL);
pa_threaded_mainloop_lock(po->mainloop);

@@ -79,23 +79,27 @@ recorder_output_init(G_GNUC_UNUSED const struct audio_format *audio_format,
if (encoder_plugin == NULL) {
g_set_error(error_r, recorder_output_quark(), 0,
"No such encoder: %s", encoder_name);
return NULL;
goto failure;
}
recorder->path = config_get_block_string(param, "path", NULL);
if (recorder->path == NULL) {
g_set_error(error_r, recorder_output_quark(), 0,
"'path' not configured");
return NULL;
goto failure;
}
/* initialize encoder */
recorder->encoder = encoder_init(encoder_plugin, param, error_r);
if (recorder->encoder == NULL)
return NULL;
goto failure;
return recorder;
failure:
g_free(recorder);
return NULL;
}
static void

@@ -21,6 +21,7 @@
#include "output_api.h"
#include "encoder_plugin.h"
#include "encoder_list.h"
#include "mpd_error.h"
#include <shout/shout.h>
#include <glib.h>
@@ -101,8 +102,8 @@ static void free_shout_data(struct shout_data *sd)
#define check_block_param(name) { \
block_param = config_get_block_param(param, name); \
if (!block_param) { \
g_error("no \"%s\" defined for shout device defined at line " \
"%i\n", name, param->line); \
MPD_ERROR("no \"%s\" defined for shout device defined at line " \
"%i\n", name, param->line); \
} \
}
@@ -151,7 +152,7 @@ my_shout_init_driver(const struct audio_format *audio_format,
if (port == 0) {
g_set_error(error, shout_output_quark(), 0,
"shout port must be configured");
return NULL;
goto failure;
}
check_block_param("password");
@@ -173,21 +174,21 @@ my_shout_init_driver(const struct audio_format *audio_format,
"shout quality \"%s\" is not a number in the "
"range -1 to 10, line %i",
value, param->line);
return NULL;
goto failure;
}
if (config_get_block_string(param, "bitrate", NULL) != NULL) {
g_set_error(error, shout_output_quark(), 0,
"quality and bitrate are "
"both defined");
return NULL;
goto failure;
}
} else {
value = config_get_block_string(param, "bitrate", NULL);
if (value == NULL) {
g_set_error(error, shout_output_quark(), 0,
"neither bitrate nor quality defined");
return NULL;
goto failure;
}
sd->bitrate = strtol(value, &test, 10);
@@ -195,7 +196,7 @@ my_shout_init_driver(const struct audio_format *audio_format,
if (*test != '\0' || sd->bitrate <= 0) {
g_set_error(error, shout_output_quark(), 0,
"bitrate must be a positive integer");
return NULL;
goto failure;
}
}
@@ -205,12 +206,12 @@ my_shout_init_driver(const struct audio_format *audio_format,
g_set_error(error, shout_output_quark(), 0,
"couldn't find shout encoder plugin \"%s\"",
encoding);
return NULL;
goto failure;
}
sd->encoder = encoder_init(encoder_plugin, param, error);
if (sd->encoder == NULL)
return NULL;
goto failure;
if (strcmp(encoding, "mp3") == 0 || strcmp(encoding, "lame") == 0)
shout_format = SHOUT_FORMAT_MP3;
@@ -224,7 +225,7 @@ my_shout_init_driver(const struct audio_format *audio_format,
g_set_error(error, shout_output_quark(), 0,
"you cannot stream \"%s\" to shoutcast, use mp3",
encoding);
return NULL;
goto failure;
} else if (0 == strcmp(value, "shoutcast"))
protocol = SHOUT_PROTOCOL_ICY;
else if (0 == strcmp(value, "icecast1"))
@@ -236,7 +237,7 @@ my_shout_init_driver(const struct audio_format *audio_format,
"shout protocol \"%s\" is not \"shoutcast\" or "
"\"icecast1\"or \"icecast2\"",
value);
return NULL;
goto failure;
}
} else {
protocol = SHOUT_PROTOCOL_HTTP;
@@ -255,7 +256,7 @@ my_shout_init_driver(const struct audio_format *audio_format,
shout_set_agent(sd->shout_conn, "MPD") != SHOUTERR_SUCCESS) {
g_set_error(error, shout_output_quark(), 0,
"%s", shout_get_error(sd->shout_conn));
return NULL;
goto failure;
}
/* optional paramters */
@@ -266,14 +267,14 @@ my_shout_init_driver(const struct audio_format *audio_format,
if (value != NULL && shout_set_genre(sd->shout_conn, value)) {
g_set_error(error, shout_output_quark(), 0,
"%s", shout_get_error(sd->shout_conn));
return NULL;
goto failure;
}
value = config_get_block_string(param, "description", NULL);
if (value != NULL && shout_set_description(sd->shout_conn, value)) {
g_set_error(error, shout_output_quark(), 0,
"%s", shout_get_error(sd->shout_conn));
return NULL;
goto failure;
}
{
@@ -299,6 +300,10 @@ my_shout_init_driver(const struct audio_format *audio_format,
}
return sd;
failure:
free_shout_data(sd);
return NULL;
}
static bool
@@ -341,7 +346,6 @@ write_page(struct shout_data *sd, GError **error)
if (sd->buf.len == 0)
return true;
shout_sync(sd->shout_conn);
err = shout_send(sd->shout_conn, sd->buf.data, sd->buf.len);
if (!handle_shout_error(sd, err, error))
return false;
@@ -440,6 +444,18 @@ my_shout_open_device(void *data, struct audio_format *audio_format,
return true;
}
static unsigned
my_shout_delay(void *data)
{
struct shout_data *sd = (struct shout_data *)data;
int delay = shout_delay(sd->shout_conn);
if (delay < 0)
delay = 0;
return delay;
}
static size_t
my_shout_play(void *data, const void *chunk, size_t size, GError **error)
{
@@ -454,15 +470,8 @@ my_shout_play(void *data, const void *chunk, size_t size, GError **error)
static bool
my_shout_pause(void *data)
{
struct shout_data *sd = (struct shout_data *)data;
static const char silence[1020];
if (shout_delay(sd->shout_conn) > 500) {
/* cap the latency for unpause */
g_usleep(500000);
return true;
}
return my_shout_play(data, silence, sizeof(silence), NULL);
}
@@ -489,7 +498,7 @@ shout_tag_to_metadata(const struct tag *tag, char *dest, size_t size)
}
}
snprintf(dest, size, "%s - %s", title, artist);
snprintf(dest, size, "%s - %s", artist, title);
}
static void my_shout_set_tag(void *data,
@@ -502,7 +511,7 @@ static void my_shout_set_tag(void *data,
if (sd->encoder->plugin->tag != NULL) {
/* encoder plugin supports stream tags */
ret = encoder_flush(sd->encoder, &error);
ret = encoder_pre_tag(sd->encoder, &error);
if (!ret) {
g_warning("%s", error->message);
g_error_free(error);
@@ -539,6 +548,7 @@ const struct audio_output_plugin shoutPlugin = {
.init = my_shout_init_driver,
.finish = my_shout_finish_driver,
.open = my_shout_open_device,
.delay = my_shout_delay,
.play = my_shout_play,
.pause = my_shout_pause,
.cancel = my_shout_drop_buffered_audio,

@@ -93,7 +93,7 @@ solaris_output_open(void *data, struct audio_format *audio_format,
/* open the device in non-blocking mode */
so->fd = open_cloexec(so->device, O_WRONLY|O_NONBLOCK);
so->fd = open_cloexec(so->device, O_WRONLY|O_NONBLOCK, 0);
if (so->fd < 0) {
g_set_error(error, solaris_output_quark(), errno,
"Failed to open %s: %s",

@@ -20,19 +20,24 @@
#include "config.h"
#include "output_api.h"
#include "pcm_buffer.h"
#include "mixer_list.h"
#include "winmm_output_plugin.h"
#include <stdlib.h>
#include <string.h>
#include <windows.h>
#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "win32_output"
#define G_LOG_DOMAIN "winmm_output"
struct win32_buffer {
struct winmm_buffer {
struct pcm_buffer buffer;
WAVEHDR hdr;
};
struct win32_output {
struct winmm_output {
UINT device_id;
HWAVEOUT handle;
/**
@@ -41,7 +46,7 @@ struct win32_output {
*/
HANDLE event;
struct win32_buffer buffers[8];
struct winmm_buffer buffers[8];
unsigned next_buffer;
};
@@ -49,45 +54,80 @@ struct win32_output {
* The quark used for GError.domain.
*/
static inline GQuark
win32_output_quark(void)
winmm_output_quark(void)
{
return g_quark_from_static_string("win32_output");
return g_quark_from_static_string("winmm_output");
}
HWAVEOUT
winmm_output_get_handle(struct winmm_output* output)
{
return output->handle;
}
static bool
win32_output_test_default_device(void)
winmm_output_test_default_device(void)
{
/* we assume that Wave is always available */
return true;
return waveOutGetNumDevs() > 0;
}
static UINT
get_device_id(const char *device_name)
{
/* if device is not specified use wave mapper */
if (device_name == NULL)
return WAVE_MAPPER;
/* check for device id */
char *endptr;
UINT id = strtoul(device_name, &endptr, 0);
if (endptr > device_name && *endptr == 0)
return id;
/* check for device name */
for (UINT i = 0; i < waveOutGetNumDevs(); i++) {
WAVEOUTCAPS caps;
MMRESULT result = waveOutGetDevCaps(i, &caps, sizeof(caps));
if (result != MMSYSERR_NOERROR)
continue;
/* szPname is only 32 chars long, so it is often truncated.
Use partial match to work around this. */
if (strstr(device_name, caps.szPname) == device_name)
return i;
}
/* fallback to wave mapper */
return WAVE_MAPPER;
}
static void *
win32_output_init(G_GNUC_UNUSED const struct audio_format *audio_format,
winmm_output_init(G_GNUC_UNUSED const struct audio_format *audio_format,
G_GNUC_UNUSED const struct config_param *param,
G_GNUC_UNUSED GError **error)
{
struct win32_output *wo = g_new(struct win32_output, 1);
struct winmm_output *wo = g_new(struct winmm_output, 1);
const char *device = config_get_block_string(param, "device", NULL);
wo->device_id = get_device_id(device);
return wo;
}
static void
win32_output_finish(void *data)
winmm_output_finish(void *data)
{
struct win32_output *wo = data;
struct winmm_output *wo = data;
g_free(wo);
}
static bool
win32_output_open(void *data, struct audio_format *audio_format,
winmm_output_open(void *data, struct audio_format *audio_format,
GError **error_r)
{
struct win32_output *wo = data;
struct winmm_output *wo = data;
wo->event = CreateEvent(NULL, false, false, NULL);
if (wo->event == NULL) {
g_set_error(error_r, win32_output_quark(), 0,
g_set_error(error_r, winmm_output_quark(), 0,
"CreateEvent() failed");
return false;
}
@@ -119,11 +159,11 @@ win32_output_open(void *data, struct audio_format *audio_format,
format.wBitsPerSample = audio_format_sample_size(audio_format) * 8;
format.cbSize = 0;
MMRESULT result = waveOutOpen(&wo->handle, WAVE_MAPPER, &format,
MMRESULT result = waveOutOpen(&wo->handle, wo->device_id, &format,
(DWORD_PTR)wo->event, 0, CALLBACK_EVENT);
if (result != MMSYSERR_NOERROR) {
CloseHandle(wo->event);
g_set_error(error_r, win32_output_quark(), result,
g_set_error(error_r, winmm_output_quark(), result,
"waveOutOpen() failed");
return false;
}
@@ -139,9 +179,9 @@ win32_output_open(void *data, struct audio_format *audio_format,
}
static void
win32_output_close(void *data)
winmm_output_close(void *data)
{
struct win32_output *wo = data;
struct winmm_output *wo = data;
for (unsigned i = 0; i < G_N_ELEMENTS(wo->buffers); ++i)
pcm_buffer_deinit(&wo->buffers[i].buffer);
@@ -155,13 +195,13 @@ win32_output_close(void *data)
* Copy data into a buffer, and prepare the wave header.
*/
static bool
win32_set_buffer(struct win32_output *wo, struct win32_buffer *buffer,
winmm_set_buffer(struct winmm_output *wo, struct winmm_buffer *buffer,
const void *data, size_t size,
GError **error_r)
{
void *dest = pcm_buffer_get(&buffer->buffer, size);
if (dest == NULL) {
g_set_error(error_r, win32_output_quark(), 0,
g_set_error(error_r, winmm_output_quark(), 0,
"Out of memory");
return false;
}
@@ -175,7 +215,7 @@ win32_set_buffer(struct win32_output *wo, struct win32_buffer *buffer,
MMRESULT result = waveOutPrepareHeader(wo->handle, &buffer->hdr,
sizeof(buffer->hdr));
if (result != MMSYSERR_NOERROR) {
g_set_error(error_r, win32_output_quark(), result,
g_set_error(error_r, winmm_output_quark(), result,
"waveOutPrepareHeader() failed");
return false;
}
@@ -187,7 +227,7 @@ win32_set_buffer(struct win32_output *wo, struct win32_buffer *buffer,
* Wait until the buffer is finished.
*/
static bool
win32_drain_buffer(struct win32_output *wo, struct win32_buffer *buffer,
winmm_drain_buffer(struct winmm_output *wo, struct winmm_buffer *buffer,
GError **error_r)
{
if ((buffer->hdr.dwFlags & WHDR_DONE) == WHDR_DONE)
@@ -201,7 +241,7 @@ win32_drain_buffer(struct win32_output *wo, struct win32_buffer *buffer,
if (result == MMSYSERR_NOERROR)
return true;
else if (result != WAVERR_STILLPLAYING) {
g_set_error(error_r, win32_output_quark(), result,
g_set_error(error_r, winmm_output_quark(), result,
"waveOutUnprepareHeader() failed");
return false;
}
@@ -212,14 +252,14 @@ win32_drain_buffer(struct win32_output *wo, struct win32_buffer *buffer,
}
static size_t
win32_output_play(void *data, const void *chunk, size_t size, GError **error_r)
winmm_output_play(void *data, const void *chunk, size_t size, GError **error_r)
{
struct win32_output *wo = data;
struct winmm_output *wo = data;
/* get the next buffer from the ring and prepare it */
struct win32_buffer *buffer = &wo->buffers[wo->next_buffer];
if (!win32_drain_buffer(wo, buffer, error_r) ||
!win32_set_buffer(wo, buffer, chunk, size, error_r))
struct winmm_buffer *buffer = &wo->buffers[wo->next_buffer];
if (!winmm_drain_buffer(wo, buffer, error_r) ||
!winmm_set_buffer(wo, buffer, chunk, size, error_r))
return 0;
/* enqueue the buffer */
@@ -228,7 +268,7 @@ win32_output_play(void *data, const void *chunk, size_t size, GError **error_r)
if (result != MMSYSERR_NOERROR) {
waveOutUnprepareHeader(wo->handle, &buffer->hdr,
sizeof(buffer->hdr));
g_set_error(error_r, win32_output_quark(), result,
g_set_error(error_r, winmm_output_quark(), result,
"waveOutWrite() failed");
return 0;
}
@@ -241,56 +281,57 @@ win32_output_play(void *data, const void *chunk, size_t size, GError **error_r)
}
static bool
win32_drain_all_buffers(struct win32_output *wo, GError **error_r)
winmm_drain_all_buffers(struct winmm_output *wo, GError **error_r)
{
for (unsigned i = wo->next_buffer; i < G_N_ELEMENTS(wo->buffers); ++i)
if (!win32_drain_buffer(wo, &wo->buffers[i], error_r))
if (!winmm_drain_buffer(wo, &wo->buffers[i], error_r))
return false;
for (unsigned i = 0; i < wo->next_buffer; ++i)
if (!win32_drain_buffer(wo, &wo->buffers[i], error_r))
if (!winmm_drain_buffer(wo, &wo->buffers[i], error_r))
return false;
return true;
}
static void
win32_stop(struct win32_output *wo)
winmm_stop(struct winmm_output *wo)
{
waveOutReset(wo->handle);
for (unsigned i = 0; i < G_N_ELEMENTS(wo->buffers); ++i) {
struct win32_buffer *buffer = &wo->buffers[i];
struct winmm_buffer *buffer = &wo->buffers[i];
waveOutUnprepareHeader(wo->handle, &buffer->hdr,
sizeof(buffer->hdr));
}
}
static void
win32_output_drain(void *data)
winmm_output_drain(void *data)
{
struct win32_output *wo = data;
struct winmm_output *wo = data;
if (!win32_drain_all_buffers(wo, NULL))
win32_stop(wo);
if (!winmm_drain_all_buffers(wo, NULL))
winmm_stop(wo);
}
static void
win32_output_cancel(void *data)
winmm_output_cancel(void *data)
{
struct win32_output *wo = data;
struct winmm_output *wo = data;
win32_stop(wo);
winmm_stop(wo);
}
const struct audio_output_plugin win32_output_plugin = {
.name = "win32",
.test_default_device = win32_output_test_default_device,
.init = win32_output_init,
.finish = win32_output_finish,
.open = win32_output_open,
.close = win32_output_close,
.play = win32_output_play,
.drain = win32_output_drain,
.cancel = win32_output_cancel,
const struct audio_output_plugin winmm_output_plugin = {
.name = "winmm",
.test_default_device = winmm_output_test_default_device,
.init = winmm_output_init,
.finish = winmm_output_finish,
.open = winmm_output_open,
.close = winmm_output_close,
.play = winmm_output_play,
.drain = winmm_output_drain,
.cancel = winmm_output_cancel,
.mixer_plugin = &winmm_mixer_plugin,
};

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

@@ -26,6 +26,7 @@
#include "pipe.h"
#include "buffer.h"
#include "player_control.h"
#include "mpd_error.h"
#ifndef NDEBUG
#include "chunk.h"
@@ -122,17 +123,17 @@ audio_output_all_init(void)
if (!audio_output_init(output, param, &error)) {
if (param != NULL)
g_error("line %i: %s",
param->line, error->message);
MPD_ERROR("line %i: %s",
param->line, error->message);
else
g_error("%s", error->message);
MPD_ERROR("%s", error->message);
}
/* require output names to be unique: */
for (j = 0; j < i; j++) {
if (!strcmp(output->name, audio_outputs[j].name)) {
g_error("output devices with identical "
"names: %s\n", output->name);
MPD_ERROR("output devices with identical "
"names: %s\n", output->name);
}
}
}
@@ -144,6 +145,7 @@ audio_output_all_finish(void)
unsigned int i;
for (i = 0; i < num_audio_outputs; i++) {
audio_output_disable(&audio_outputs[i]);
audio_output_finish(&audio_outputs[i]);
}
@@ -203,27 +205,14 @@ static void audio_output_wait_all(void)
notify_wait(&audio_output_client_notify);
}
/**
* Signals the audio output if it is open. This function locks the
* mutex.
*/
static void
audio_output_lock_signal(struct audio_output *ao)
{
g_mutex_lock(ao->mutex);
if (audio_output_is_open(ao))
g_cond_signal(ao->cond);
g_mutex_unlock(ao->mutex);
}
/**
* Signals all audio outputs which are open.
*/
static void
audio_output_signal_all(void)
audio_output_allow_play_all(void)
{
for (unsigned i = 0; i < num_audio_outputs; ++i)
audio_output_lock_signal(&audio_outputs[i]);
audio_output_allow_play(&audio_outputs[i]);
}
static void
@@ -321,7 +310,7 @@ audio_output_all_open(const struct audio_format *audio_format,
else
/* if the pipe hasn't been cleared, the the audio
format must not have changed */
assert(music_pipe_size(g_mp) == 0 ||
assert(music_pipe_empty(g_mp) ||
audio_format_equals(audio_format,
&input_audio_format));
@@ -434,7 +423,7 @@ audio_output_all_check(void)
assert(g_mp != NULL);
while ((chunk = music_pipe_peek(g_mp)) != NULL) {
assert(music_pipe_size(g_mp) > 0);
assert(!music_pipe_empty(g_mp));
if (!chunk_is_consumed(chunk))
/* at least one output is not finished playing
@@ -528,7 +517,7 @@ audio_output_all_cancel(void)
/* the audio outputs are now waiting for a signal, to
synchronize the cleared music pipe */
audio_output_signal_all();
audio_output_allow_play_all();
/* invalidate elapsed_time */

@@ -102,6 +102,12 @@ audio_output_disable(struct audio_output *ao)
g_mutex_unlock(ao->mutex);
}
static void
audio_output_close_locked(struct audio_output *ao);
/**
* Object must be locked (and unlocked) by the caller.
*/
static bool
audio_output_open(struct audio_output *ao,
const struct audio_format *audio_format,
@@ -109,6 +115,9 @@ audio_output_open(struct audio_output *ao,
{
bool open;
assert(ao != NULL);
assert(ao->allow_play);
assert(audio_format_valid(audio_format));
assert(mp != NULL);
if (ao->fail_timer != NULL) {
@@ -133,10 +142,6 @@ audio_output_open(struct audio_output *ao,
/* we're not using audio_output_cancel() here,
because that function is asynchronous */
ao_command(ao, AO_COMMAND_CANCEL);
/* the audio output is now waiting for a
signal; wake it up immediately */
g_cond_signal(ao->cond);
}
return true;
@@ -173,6 +178,9 @@ audio_output_open(struct audio_output *ao,
static void
audio_output_close_locked(struct audio_output *ao)
{
assert(ao != NULL);
assert(ao->allow_play);
if (ao->mixer != NULL)
mixer_auto_close(ao->mixer);
@@ -214,6 +222,8 @@ audio_output_play(struct audio_output *ao)
{
g_mutex_lock(ao->mutex);
assert(ao->allow_play);
if (audio_output_is_open(ao))
g_cond_signal(ao->cond);
@@ -229,6 +239,7 @@ void audio_output_pause(struct audio_output *ao)
mixer_auto_close(ao->mixer);
g_mutex_lock(ao->mutex);
assert(ao->allow_play);
if (audio_output_is_open(ao))
ao_command_async(ao, AO_COMMAND_PAUSE);
g_mutex_unlock(ao->mutex);
@@ -238,6 +249,7 @@ void
audio_output_drain_async(struct audio_output *ao)
{
g_mutex_lock(ao->mutex);
assert(ao->allow_play);
if (audio_output_is_open(ao))
ao_command_async(ao, AO_COMMAND_DRAIN);
g_mutex_unlock(ao->mutex);
@@ -246,26 +258,23 @@ audio_output_drain_async(struct audio_output *ao)
void audio_output_cancel(struct audio_output *ao)
{
g_mutex_lock(ao->mutex);
if (audio_output_is_open(ao))
if (audio_output_is_open(ao)) {
ao->allow_play = false;
ao_command_async(ao, AO_COMMAND_CANCEL);
}
g_mutex_unlock(ao->mutex);
}
void audio_output_close(struct audio_output *ao)
void
audio_output_allow_play(struct audio_output *ao)
{
if (ao->mixer != NULL)
mixer_auto_close(ao->mixer);
g_mutex_lock(ao->mutex);
assert(!ao->open || ao->fail_timer == NULL);
if (ao->open)
ao_command(ao, AO_COMMAND_CLOSE);
else if (ao->fail_timer != NULL) {
g_timer_destroy(ao->fail_timer);
ao->fail_timer = NULL;
}
ao->allow_play = true;
if (audio_output_is_open(ao))
g_cond_signal(ao->cond);
g_mutex_unlock(ao->mutex);
}
@@ -279,6 +288,16 @@ audio_output_release(struct audio_output *ao)
audio_output_close(ao);
}
void audio_output_close(struct audio_output *ao)
{
assert(ao != NULL);
assert(!ao->open || ao->fail_timer == NULL);
g_mutex_lock(ao->mutex);
audio_output_close_locked(ao);
g_mutex_unlock(ao->mutex);
}
void audio_output_finish(struct audio_output *ao)
{
audio_output_close(ao);
@@ -287,6 +306,7 @@ void audio_output_finish(struct audio_output *ao)
if (ao->thread != NULL) {
g_mutex_lock(ao->mutex);
assert(ao->allow_play);
ao_command(ao, AO_COMMAND_KILL);
g_mutex_unlock(ao->mutex);
g_thread_join(ao->thread);

@@ -70,8 +70,19 @@ void audio_output_pause(struct audio_output *ao);
void
audio_output_drain_async(struct audio_output *ao);
/**
* Clear the "allow_play" flag and send the "CANCEL" command
* asynchronously. To finish the operation, the caller has to call
* audio_output_allow_play().
*/
void audio_output_cancel(struct audio_output *ao);
/**
* Set the "allow_play" and signal the thread.
*/
void
audio_output_allow_play(struct audio_output *ao);
void audio_output_close(struct audio_output *ao);
/**

@@ -189,6 +189,7 @@ audio_output_init(struct audio_output *ao, const struct config_param *param,
ao->really_enabled = false;
ao->open = false;
ao->pause = false;
ao->allow_play = true;
ao->fail_timer = NULL;
pcm_buffer_init(&ao->cross_fade_buffer);

@@ -109,6 +109,15 @@ struct audio_output {
*/
bool pause;
/**
* When this flag is set, the output thread will not do any
* playback. It will wait until the flag is cleared.
*
* This is used to synchronize the "clear" operation on the
* shared music pipe during the CANCEL command.
*/
bool allow_play;
/**
* If not NULL, the device has failed, and this timer is used
* to estimate how long it should stay disabled (unless
@@ -196,7 +205,8 @@ struct audio_output {
const struct music_pipe *pipe;
/**
* This mutex protects #open, #chunk and #chunk_finished.
* This mutex protects #open, #fail_timer, #chunk and
* #chunk_finished.
*/
GMutex *mutex;

@@ -36,7 +36,8 @@ extern const struct audio_output_plugin mvp_output_plugin;
extern const struct audio_output_plugin jack_output_plugin;
extern const struct audio_output_plugin httpd_output_plugin;
extern const struct audio_output_plugin recorder_output_plugin;
extern const struct audio_output_plugin win32_output_plugin;
extern const struct audio_output_plugin winmm_output_plugin;
extern const struct audio_output_plugin ffado_output_plugin;
const struct audio_output_plugin *audio_output_plugins[] = {
#ifdef HAVE_SHOUT
@@ -82,8 +83,11 @@ const struct audio_output_plugin *audio_output_plugins[] = {
#ifdef ENABLE_RECORDER_OUTPUT
&recorder_output_plugin,
#endif
#ifdef ENABLE_WIN32_OUTPUT
&win32_output_plugin,
#ifdef ENABLE_WINMM_OUTPUT
&winmm_output_plugin,
#endif
#ifdef ENABLE_FFADO_OUTPUT
&ffado_output_plugin,
#endif
NULL
};

@@ -100,6 +100,16 @@ struct audio_output_plugin {
*/
void (*close)(void *data);
/**
* Returns a positive number if the output thread shall delay
* the next call to play() or pause(). This should be
* implemented instead of doing a sleep inside the plugin,
* because this allows MPD to listen to commands meanwhile.
*
* @return the number of milliseconds to wait
*/
unsigned (*delay)(void *data);
/**
* Display metadata for the next chunk. Optional method,
* because not all devices can display metadata.
@@ -202,6 +212,14 @@ ao_plugin_close(const struct audio_output_plugin *plugin, void *data)
plugin->close(data);
}
static inline unsigned
ao_plugin_delay(const struct audio_output_plugin *plugin, void *data)
{
return plugin->delay != NULL
? plugin->delay(data)
: 0;
}
static inline void
ao_plugin_send_tag(const struct audio_output_plugin *plugin,
void *data, const struct tag *tag)

@@ -28,6 +28,7 @@
#include "filter_plugin.h"
#include "filter/convert_filter_plugin.h"
#include "filter/replay_gain_filter_plugin.h"
#include "mpd_error.h"
#include <glib.h>
@@ -94,6 +95,8 @@ ao_filter_open(struct audio_output *ao,
struct audio_format *audio_format,
GError **error_r)
{
assert(audio_format_valid(audio_format));
/* the replay_gain filter cannot fail here */
if (ao->replay_gain_filter != NULL)
filter_open(ao->replay_gain_filter, audio_format, error_r);
@@ -133,9 +136,18 @@ ao_open(struct audio_output *ao)
struct audio_format_string af_string;
assert(!ao->open);
assert(ao->fail_timer == NULL);
assert(ao->pipe != NULL);
assert(ao->chunk == NULL);
assert(audio_format_valid(&ao->in_audio_format));
if (ao->fail_timer != NULL) {
/* this can only happen when this
output thread fails while
audio_output_open() is run in the
player thread */
g_timer_destroy(ao->fail_timer);
ao->fail_timer = NULL;
}
/* enable the device (just in case the last enable has failed) */
@@ -155,6 +167,8 @@ ao_open(struct audio_output *ao)
return;
}
assert(audio_format_valid(filter_audio_format));
ao->out_audio_format = *filter_audio_format;
audio_format_mask_apply(&ao->out_audio_format,
&ao->config_audio_format);
@@ -277,6 +291,30 @@ ao_reopen(struct audio_output *ao)
ao_open(ao);
}
/**
* Wait until the output's delay reaches zero.
*
* @return true if playback should be continued, false if a command
* was issued
*/
static bool
ao_wait(struct audio_output *ao)
{
while (true) {
unsigned delay = ao_plugin_delay(ao->plugin, ao->data);
if (delay == 0)
return true;
GTimeVal tv;
g_get_current_time(&tv);
g_time_val_add(&tv, delay * 1000);
(void)g_cond_timed_wait(ao->cond, ao->mutex, &tv);
if (ao->command != AO_COMMAND_NONE)
return false;
}
}
static const char *
ao_chunk_data(struct audio_output *ao, const struct music_chunk *chunk,
struct filter *replay_gain_filter,
@@ -413,6 +451,9 @@ ao_play_chunk(struct audio_output *ao, const struct music_chunk *chunk)
while (size > 0 && ao->command == AO_COMMAND_NONE) {
size_t nbytes;
if (!ao_wait(ao))
break;
g_mutex_unlock(ao->mutex);
nbytes = ao_plugin_play(ao->plugin, ao->data, data, size,
&error);
@@ -427,7 +468,9 @@ ao_play_chunk(struct audio_output *ao, const struct music_chunk *chunk)
/* don't automatically reopen this device for
10 seconds */
assert(ao->fail_timer == NULL);
ao->fail_timer = g_timer_new();
return false;
}
@@ -510,6 +553,9 @@ static void ao_pause(struct audio_output *ao)
ao_command_finished(ao);
do {
if (!ao_wait(ao))
break;
g_mutex_unlock(ao->mutex);
ret = ao_plugin_pause(ao->plugin, ao->data);
g_mutex_lock(ao->mutex);
@@ -594,15 +640,14 @@ static gpointer audio_output_task(gpointer arg)
case AO_COMMAND_CANCEL:
ao->chunk = NULL;
if (ao->open)
ao_plugin_cancel(ao->plugin, ao->data);
ao_command_finished(ao);
/* the player thread will now clear our music
pipe - wait for a notify, to give it some
time */
if (ao->command == AO_COMMAND_NONE)
g_cond_wait(ao->cond, ao->mutex);
if (ao->open) {
g_mutex_unlock(ao->mutex);
ao_plugin_cancel(ao->plugin, ao->data);
g_mutex_lock(ao->mutex);
}
ao_command_finished(ao);
continue;
case AO_COMMAND_KILL:
@@ -612,7 +657,7 @@ static gpointer audio_output_task(gpointer arg)
return NULL;
}
if (ao->open && ao_play(ao))
if (ao->open && ao->allow_play && ao_play(ao))
/* don't wait for an event if there are more
chunks in the pipe */
continue;
@@ -629,5 +674,5 @@ void audio_output_thread_start(struct audio_output *ao)
assert(ao->command == AO_COMMAND_NONE);
if (!(ao->thread = g_thread_create(audio_output_task, ao, true, &e)))
g_error("Failed to spawn output task: %s\n", e->message);
MPD_ERROR("Failed to spawn output task: %s\n", e->message);
}

@@ -20,6 +20,7 @@
#include "config.h"
#include "path.h"
#include "conf.h"
#include "mpd_error.h"
#include <glib.h>
@@ -64,7 +65,7 @@ path_set_fs_charset(const char *charset)
/* convert a space to ensure that the charset is valid */
test = g_convert(" ", 1, charset, "UTF-8", NULL, NULL, NULL);
if (test == NULL)
g_error("invalid filesystem charset: %s", charset);
MPD_ERROR("invalid filesystem charset: %s", charset);
g_free(test);
g_free(fs_charset);

@@ -49,7 +49,7 @@ const int16_t *pcm_byteswap_16(struct pcm_buffer *buffer,
static inline uint32_t swab32(uint32_t x)
{
return (x << 24) |
return (x << 24) |
((x & 0xff00) << 8) |
((x & 0xff0000) >> 8) |
(x >> 24);

@@ -22,6 +22,7 @@
#include "pcm_volume.h"
#include "pcm_utils.h"
#include "audio_format.h"
#include "mpd_error.h"
#include <glib.h>
@@ -125,8 +126,8 @@ pcm_add_vol(void *buffer1, const void *buffer2, size_t size,
break;
default:
g_error("format %s not supported by pcm_add_vol",
sample_format_to_string(format->format));
MPD_ERROR("format %s not supported by pcm_add_vol",
sample_format_to_string(format->format));
}
}
@@ -208,8 +209,8 @@ pcm_add(void *buffer1, const void *buffer2, size_t size,
break;
default:
g_error("format %s not supported by pcm_add",
sample_format_to_string(format->format));
MPD_ERROR("format %s not supported by pcm_add",
sample_format_to_string(format->format));
}
}

@@ -20,6 +20,7 @@
#include "config.h"
#include "permission.h"
#include "conf.h"
#include "mpd_error.h"
#include <glib.h>
@@ -59,7 +60,7 @@ static unsigned parsePermissions(const char *string)
} else if (strcmp(temp, PERMISSION_ADMIN_STRING) == 0) {
permission |= PERMISSION_ADMIN;
} else {
g_error("unknown permission \"%s\"", temp);
MPD_ERROR("unknown permission \"%s\"", temp);
}
}
@@ -90,7 +91,7 @@ void initPermissions(void)
strchr(param->value, PERMISSION_PASSWORD_CHAR);
if (separator == NULL)
g_error("\"%c\" not found in password string "
MPD_ERROR("\"%c\" not found in password string "
"\"%s\", line %i",
PERMISSION_PASSWORD_CHAR,
param->value, param->line);

@@ -187,5 +187,8 @@ music_pipe_push(struct music_pipe *mp, struct music_chunk *chunk)
unsigned
music_pipe_size(const struct music_pipe *mp)
{
return mp->size;
g_mutex_lock(mp->mutex);
unsigned size = mp->size;
g_mutex_unlock(mp->mutex);
return size;
}

@@ -20,9 +20,10 @@
#ifndef MPD_PIPE_H
#define MPD_PIPE_H
#ifndef NDEBUG
#include <glib.h>
#include <stdbool.h>
#ifndef NDEBUG
struct audio_format;
#endif
@@ -38,6 +39,7 @@ struct music_pipe;
/**
* Creates a new #music_pipe object. It is empty.
*/
G_GNUC_MALLOC
struct music_pipe *
music_pipe_new(void);
@@ -70,6 +72,7 @@ music_pipe_contains(const struct music_pipe *mp,
* Returns the first #music_chunk from the pipe. Returns NULL if the
* pipe is empty.
*/
G_GNUC_PURE
const struct music_chunk *
music_pipe_peek(const struct music_pipe *mp);
@@ -96,7 +99,15 @@ music_pipe_push(struct music_pipe *mp, struct music_chunk *chunk);
/**
* Returns the number of chunks currently in this pipe.
*/
G_GNUC_PURE
unsigned
music_pipe_size(const struct music_pipe *mp);
G_GNUC_PURE
static inline bool
music_pipe_empty(const struct music_pipe *mp)
{
return music_pipe_size(mp) == 0;
}
#endif

@@ -34,6 +34,7 @@
#include "idle.h"
#include "main.h"
#include "buffer.h"
#include "mpd_error.h"
#include <glib.h>
@@ -147,6 +148,32 @@ player_dc_start(struct player *player, struct music_pipe *pipe)
dc_start(dc, pc.next_song, player_buffer, pipe);
}
/**
* Is the decoder still busy on the same song as the player?
*
* Note: this function does not check if the decoder is already
* finished.
*/
static bool
player_dc_at_current_song(const struct player *player)
{
assert(player != NULL);
assert(player->pipe != NULL);
return player->dc->pipe == player->pipe;
}
/**
* Returns true if the decoder is decoding the next song (or has begun
* decoding it, or has finished doing it), and the player hasn't
* switched to that song yet.
*/
static bool
player_dc_at_next_song(const struct player *player)
{
return player->dc->pipe != NULL && !player_dc_at_current_song(player);
}
/**
* Stop the decoder and clears (and frees) its music pipe.
*
@@ -171,17 +198,6 @@ player_dc_stop(struct player *player)
}
}
/**
* Returns true if the decoder is decoding the next song (or has begun
* decoding it, or has finished doing it), and the player hasn't
* switched to that song yet.
*/
static bool
decoding_next_song(const struct player *player)
{
return player->dc->pipe != NULL && player->dc->pipe != player->pipe;
}
/**
* After the decoder has been started asynchronously, wait for the
* "START" command to finish. The decoder may not be initialized yet,
@@ -340,16 +356,9 @@ player_check_decoder_startup(struct player *player)
static bool
player_send_silence(struct player *player)
{
struct music_chunk *chunk;
size_t frame_size =
audio_format_frame_size(&player->play_audio_format);
/* this formula ensures that we don't send
partial frames */
unsigned num_frames = sizeof(chunk->data) / frame_size;
assert(audio_format_defined(&player->play_audio_format));
chunk = music_buffer_allocate(player_buffer);
struct music_chunk *chunk = music_buffer_allocate(player_buffer);
if (chunk == NULL) {
g_warning("Failed to allocate silence buffer");
return false;
@@ -359,6 +368,12 @@ player_send_silence(struct player *player)
chunk->audio_format = player->play_audio_format;
#endif
size_t frame_size =
audio_format_frame_size(&player->play_audio_format);
/* this formula ensures that we don't send
partial frames */
unsigned num_frames = sizeof(chunk->data) / frame_size;
chunk->times = -1.0; /* undefined time stamp */
chunk->length = num_frames * frame_size;
memset(chunk->data, 0, chunk->length);
@@ -380,8 +395,6 @@ static bool player_seek_decoder(struct player *player)
{
struct song *song = pc.next_song;
struct decoder_control *dc = player->dc;
double where;
bool ret;
assert(pc.next_song != NULL);
@@ -397,13 +410,20 @@ static bool player_seek_decoder(struct player *player)
/* re-start the decoder */
player_dc_start(player, player->pipe);
ret = player_wait_for_decoder(player);
if (!ret) {
if (!player_wait_for_decoder(player)) {
/* decoder failure */
player_command_finished();
return false;
}
} else {
if (!player_dc_at_current_song(player)) {
/* the decoder is already decoding the "next" song,
but it is the same song file; exchange the pipe */
music_pipe_clear(player->pipe, player_buffer);
music_pipe_free(player->pipe);
player->pipe = dc->pipe;
}
pc.next_song = NULL;
player->queued = false;
}
@@ -411,8 +431,7 @@ static bool player_seek_decoder(struct player *player)
/* wait for the decoder to complete initialization */
while (player->decoder_starting) {
ret = player_check_decoder_startup(player);
if (!ret) {
if (!player_check_decoder_startup(player)) {
/* decoder failure */
player_command_finished();
return false;
@@ -421,14 +440,13 @@ static bool player_seek_decoder(struct player *player)
/* send the SEEK command */
where = pc.seek_where;
double where = pc.seek_where;
if (where > pc.total_time)
where = pc.total_time - 0.1;
if (where < 0.0)
where = 0.0;
ret = dc_seek(dc, where + song->start_ms / 1000.0);
if (!ret) {
if (!dc_seek(dc, where + song->start_ms / 1000.0)) {
/* decoder failure */
player_command_finished();
return false;
@@ -472,7 +490,7 @@ static void player_process_command(struct player *player)
case PLAYER_COMMAND_QUEUE:
assert(pc.next_song != NULL);
assert(!player->queued);
assert(dc->pipe == NULL || dc->pipe == player->pipe);
assert(!player_dc_at_next_song(player));
player->queued = true;
player_command_finished_locked();
@@ -526,7 +544,7 @@ static void player_process_command(struct player *player)
return;
}
if (decoding_next_song(player)) {
if (player_dc_at_next_song(player)) {
/* the decoder is already decoding the song -
stop it and reset the position */
player_unlock();
@@ -559,14 +577,12 @@ static void player_process_command(struct player *player)
static void
update_song_tag(struct song *song, const struct tag *new_tag)
{
struct tag *old_tag;
if (song_is_file(song))
/* don't update tags of local files, only remote
streams may change tags dynamically */
return;
old_tag = song->tag;
struct tag *old_tag = song->tag;
song->tag = tag_dup(new_tag);
if (old_tag != NULL)
@@ -602,7 +618,9 @@ play_chunk(struct song *song, struct music_chunk *chunk,
return true;
}
player_lock();
pc.bit_rate = chunk->bit_rate;
player_unlock();
/* send the chunk to the audio outputs */
@@ -624,17 +642,16 @@ static bool
play_next_chunk(struct player *player)
{
struct decoder_control *dc = player->dc;
struct music_chunk *chunk = NULL;
unsigned cross_fade_position;
bool success;
if (!audio_output_all_wait(64))
/* the output pipe is still large enough, don't send
another chunk */
return true;
unsigned cross_fade_position;
struct music_chunk *chunk = NULL;
if (player->xfade == XFADE_ENABLED &&
decoding_next_song(player) &&
player_dc_at_next_song(player) &&
(cross_fade_position = music_pipe_size(player->pipe))
<= player->cross_fade_chunks) {
/* perform cross fade */
@@ -670,6 +687,19 @@ play_next_chunk(struct player *player)
chunk->mix_ratio = nan("");
}
if (music_chunk_is_empty(other_chunk)) {
/* the "other" chunk was a music_chunk
which had only a tag, but no music
data - we cannot cross-fade that;
but since this happens only at the
beginning of the new song, we can
easily recover by throwing it away
now */
music_buffer_return(player_buffer,
other_chunk);
other_chunk = NULL;
}
chunk->other = other_chunk;
} else {
/* there are not enough decoded chunks yet */
@@ -708,9 +738,7 @@ play_next_chunk(struct player *player)
/* play the current chunk */
success = play_chunk(player->song, chunk, &player->play_audio_format);
if (!success) {
if (!play_chunk(player->song, chunk, &player->play_audio_format)) {
music_buffer_return(player_buffer, chunk);
player_lock();
@@ -752,11 +780,9 @@ play_next_chunk(struct player *player)
static bool
player_song_border(struct player *player)
{
char *uri;
player->xfade = XFADE_UNKNOWN;
uri = song_get_uri(player->song);
char *uri = song_get_uri(player->song);
g_message("played \"%s\"", uri);
g_free(uri);
@@ -851,16 +877,17 @@ static void do_play(struct decoder_control *dc)
if (player.decoder_starting) {
/* wait until the decoder is initialized completely */
bool success;
const struct song *song;
success = player_check_decoder_startup(&player);
if (!success)
if (!player_check_decoder_startup(&player))
break;
/* seek to the beginning of the range */
song = decoder_current_song(dc);
const struct song *song = decoder_current_song(dc);
if (song != NULL && song->start_ms > 0 &&
/* we must not send a seek command until
the decoder is initialized
completely */
!player.decoder_starting &&
!dc_seek(dc, song->start_ms / 1000.0))
player_dc_stop(&player);
@@ -880,12 +907,13 @@ static void do_play(struct decoder_control *dc)
dc->pipe == player.pipe) {
/* the decoder has finished the current song;
make it decode the next song */
assert(dc->pipe == NULL || dc->pipe == player.pipe);
player_dc_start(&player, music_pipe_new());
}
if (decoding_next_song(&player) &&
if (player_dc_at_next_song(&player) &&
player.xfade == XFADE_UNKNOWN &&
!decoder_lock_is_starting(dc)) {
/* enable cross fading in this song? if yes,
@@ -918,7 +946,7 @@ static void do_play(struct decoder_control *dc)
if (pc.command == PLAYER_COMMAND_NONE)
player_wait();
continue;
} else if (music_pipe_size(player.pipe) > 0) {
} else if (!music_pipe_empty(player.pipe)) {
/* at least one music chunk is ready - send it
to the audio output */
@@ -930,7 +958,7 @@ static void do_play(struct decoder_control *dc)
/* XXX synchronize in a better way */
g_usleep(10000);
} else if (decoding_next_song(&player)) {
} else if (player_dc_at_next_song(&player)) {
/* at the beginning of a new song */
if (!player_song_border(&player))
@@ -939,7 +967,7 @@ static void do_play(struct decoder_control *dc)
/* check the size of the pipe again, because
the decoder thread may have added something
since we last checked */
if (music_pipe_size(player.pipe) == 0) {
if (music_pipe_empty(player.pipe)) {
/* wait for the hardware to finish
playback */
audio_output_all_drain();
@@ -1067,11 +1095,10 @@ static gpointer player_task(G_GNUC_UNUSED gpointer arg)
void player_create(void)
{
GError *e = NULL;
assert(pc.thread == NULL);
GError *e = NULL;
pc.thread = g_thread_create(player_task, NULL, true, &e);
if (pc.thread == NULL)
g_error("Failed to spawn player task: %s", e->message);
MPD_ERROR("Failed to spawn player task: %s", e->message);
}

Some files were not shown because too many files have changed in this diff Show More