Compare commits

..

127 Commits

Author SHA1 Message Date
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
100 changed files with 3171 additions and 965 deletions

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,6 +7,8 @@ AM_CPPFLAGS += -DSYSTEM_CONFIG_FILE_LOCATION='"$(sysconfdir)/mpd.conf"'
bin_PROGRAMS = src/mpd
noinst_LIBRARIES =
src_mpd_CFLAGS = $(AM_CFLAGS) $(MPD_CFLAGS)
src_mpd_CPPFLAGS = $(AM_CPPFLAGS) \
$(LIBWRAP_CFLAGS) \
@@ -34,6 +36,7 @@ mpd_headers = \
src/check.h \
src/notify.h \
src/ack.h \
src/ape.h \
src/audio.h \
src/audio_format.h \
src/audio_check.h \
@@ -111,6 +114,7 @@ mpd_headers = \
src/icy_metadata.h \
src/client.h \
src/client_internal.h \
src/server_socket.h \
src/listen.h \
src/log.h \
src/ls.h \
@@ -136,6 +140,7 @@ mpd_headers = \
src/output/httpd_client.h \
src/output/httpd_internal.h \
src/output/pulse_output_plugin.h \
src/output/winmm_output_plugin.h \
src/page.h \
src/pcm_buffer.h \
src/pcm_utils.h \
@@ -171,6 +176,7 @@ mpd_headers = \
src/playlist/pls_playlist_plugin.h \
src/playlist/xspf_playlist_plugin.h \
src/playlist/asx_playlist_plugin.h \
src/playlist/rss_playlist_plugin.h \
src/playlist/lastfm_playlist_plugin.h \
src/playlist/cue_playlist_plugin.h \
src/playlist/flac_playlist_plugin.h \
@@ -183,6 +189,7 @@ mpd_headers = \
src/refcount.h \
src/replay_gain_config.h \
src/replay_gain_info.h \
src/replay_gain_ape.h \
src/sig_handlers.h \
src/song.h \
src/song_print.h \
@@ -220,7 +227,8 @@ mpd_headers = \
src/archive/iso9660_archive_plugin.h \
src/archive/zzip_archive_plugin.h \
src/input/archive_input_plugin.h \
src/cue/cue_tag.h
src/cue/cue_tag.h\
src/mpd_error.h
src_mpd_SOURCES = \
$(mpd_headers) \
@@ -274,10 +282,12 @@ src_mpd_SOURCES = \
src/client_process.c \
src/client_read.c \
src/client_write.c \
src/server_socket.c \
src/listen.c \
src/log.c \
src/ls.c \
src/main.c \
src/main_win32.c \
src/event_pipe.c \
src/daemon.c \
src/AudioCompress/compress.c \
@@ -406,6 +416,8 @@ TAG_LIBS = \
$(ID3TAG_LIBS)
TAG_SRC = \
src/ape.c \
src/replay_gain_ape.c \
src/tag_ape.c
if HAVE_ID3TAG
@@ -422,7 +434,6 @@ DECODER_CFLAGS = \
$(SNDFILE_CFLAGS) \
$(AUDIOFILE_CFLAGS) \
$(LIBMIKMOD_CFLAGS) \
$(MODPLUG_CFLAGS) \
$(GME_CFLAGS) \
$(SIDPLAY_CFLAGS) \
$(FLUIDSYNTH_CFLAGS) \
@@ -438,7 +449,6 @@ DECODER_LIBS = \
$(FLAC_LIBS) \
$(SNDFILE_LIBS) \
$(AUDIOFILE_LIBS) $(LIBMIKMOD_LIBS) \
$(MODPLUG_LIBS) \
$(GME_LIBS) \
$(SIDPLAY_LIBS) \
$(FLUIDSYNTH_LIBS) \
@@ -511,7 +521,11 @@ DECODER_SRC += src/decoder/mikmod_decoder_plugin.c
endif
if HAVE_MODPLUG
DECODER_SRC += src/decoder/modplug_decoder_plugin.c
libmodplug_decoder_plugin_a_SOURCES = src/decoder/modplug_decoder_plugin.c
libmodplug_decoder_plugin_a_CFLAGS = $(src_mpd_CFLAGS) $(MODPLUG_CFLAGS)
libmodplug_decoder_plugin_a_CPPFLAGS = $(src_mpd_CPPFLAGS)
noinst_LIBRARIES += libmodplug_decoder_plugin.a
DECODER_LIBS += libmodplug_decoder_plugin.a $(MODPLUG_LIBS)
endif
if ENABLE_SIDPLAY
@@ -634,6 +648,7 @@ endif
OUTPUT_CFLAGS = \
$(AO_CFLAGS) \
$(ALSA_CFLAGS) \
$(FFADO_CFLAGS) \
$(JACK_CFLAGS) \
$(OPENAL_CFLAGS) \
$(PULSE_CFLAGS) \
@@ -643,6 +658,7 @@ OUTPUT_LIBS = \
$(LIBWRAP_LDFLAGS) \
$(AO_LIBS) \
$(ALSA_LIBS) \
$(FFADO_LIBS) \
$(JACK_LIBS) \
$(OPENAL_LIBS) \
$(PULSE_LIBS) \
@@ -675,6 +691,10 @@ OUTPUT_SRC += src/output/alsa_plugin.c
MIXER_SRC += src/mixer/alsa_mixer_plugin.c
endif
if ENABLE_FFADO_OUTPUT
OUTPUT_SRC += src/output/ffado_output_plugin.c
endif
if HAVE_AO
OUTPUT_SRC += src/output/ao_plugin.c
endif
@@ -732,8 +752,9 @@ if ENABLE_SOLARIS_OUTPUT
OUTPUT_SRC += src/output/solaris_output_plugin.c
endif
if ENABLE_WIN32_OUTPUT
OUTPUT_SRC += src/output/win32_output_plugin.c
if ENABLE_WINMM_OUTPUT
OUTPUT_SRC += src/output/winmm_output_plugin.c
MIXER_SRC += src/mixer/winmm_mixer_plugin.c
endif
@@ -747,6 +768,7 @@ PLAYLIST_SRC = \
src/playlist/pls_playlist_plugin.c \
src/playlist/xspf_playlist_plugin.c \
src/playlist/asx_playlist_plugin.c \
src/playlist/rss_playlist_plugin.c \
src/playlist_list.c
if ENABLE_LASTFM
@@ -994,6 +1016,7 @@ if HAVE_LIBSAMPLERATE
test_run_convert_SOURCES += src/pcm_resample_libsamplerate.c
endif
test_run_output_CFLAGS = $(AM_CFLAGS) $(MPD_CFLAGS)
test_run_output_CPPFLAGS = $(AM_CPPFLAGS) \
$(ENCODER_CFLAGS) \
$(OUTPUT_CFLAGS)
@@ -1029,6 +1052,7 @@ test_run_output_SOURCES = test/run_output.c \
src/replay_gain_info.c \
src/replay_gain_config.c \
src/fd_util.c \
src/server_socket.c \
$(OUTPUT_SRC)
test_read_mixer_CPPFLAGS = $(AM_CPPFLAGS) \

58
NEWS

@@ -1,4 +1,20 @@
ver 0.16 (20??/??/??)
ver 0.16.1 (2010/01/09)
* audio_check: fix parameter in prototype
* add void casts to suppress "result unused" warnings (clang)
* input:
- ffado: disable by default
* decoder:
- mad: work around build failure on Solaris
- resolve modplug vs. libsndfile cflags/headers conflict
* output:
- solaris: add missing parameter to open_cloexec() cal
- osx: fix up audio format first, then apply it to device
* player_thread: discard empty chunks while cross-fading
* player_thread: fix assertion failure due to early seek
* output_thread: fix double lock
ver 0.16 (2010/12/11)
* protocol:
- send song modification time to client
- added "update" idle event
@@ -20,7 +36,9 @@ ver 0.16 (20??/??/??)
* tags:
- added tags "ArtistSort", "AlbumArtistSort"
- id3: revised "performer" tag support
- id3: support multiple values
- ape: MusicBrainz tags
- ape: support multiple values
* decoders:
- don't try a plugin twice (MIME type & suffix)
- don't fall back to "mad" unless no plugin matches
@@ -35,6 +53,8 @@ ver 0.16 (20??/??/??)
- sidplay: support sub-tunes
- sidplay: implemented songlength database
- sidplay: support seeking
- sidplay: play monaural SID tunes in mono
- sidplay: play mus, str, prg, x00 files
- wavpack: activate 32 bit support
- wavpack: allow more than 2 channels
- mp4ff: rename plugin "mp4" to "mp4ff"
@@ -59,8 +79,11 @@ ver 0.16 (20??/??/??)
- jack: support more than two audio channels
- httpd: bind port when output is enabled
- httpd: added name/genre/website configuration
- httpd: implement "pause"
- httpd: bind_to_address support (including IPv6)
- oss: 24 bit support via OSS4
- win32: new output plugin for Windows Wave
- shout, httpd: more responsive to control commands
- wildcards allowed in audio_format configuration
- consistently lock audio output objects
* player:
@@ -83,6 +106,7 @@ ver 0.16 (20??/??/??)
- fall back to track gain if album gain is unavailable
- optionally use hardware mixer to apply replay gain
- added mode "auto"
- parse replay gain from APE tags
* log unused/unknown block parameters
* removed the deprecated "error_file" option
* save state when stopped
@@ -101,6 +125,38 @@ ver 0.16 (20??/??/??)
* added test suite ("make check")
* require GLib 2.12
* added libwrap support
* make single mode 'sticky'
ver 0.15.16 (2010/??/??)
* encoders:
- lame: explicitly configure the output sample rate
ver 0.15.15 (2010/11/08)
* input:
- rewind: fix assertion failure
* output:
- shout: artist comes first in stream title
ver 0.15.14 (2010/11/06)
* player_thread: fix assertion failure due to wrong music pipe on seek
* output_thread: fix assertion failure due to race condition in OPEN
* input:
- rewind: fix double free bug
* decoders:
- mp4ff, ffmpeg: add extension ".m4b" (audio book)
ver 0.15.13 (2010/10/10)
* output_thread: fix race condition after CANCEL command
* output:
- httpd: fix random data in stream title
- httpd: MIME type audio/ogg for Ogg Vorbis
* input:
- rewind: update MIME not only once
- rewind: enable for MMS
ver 0.15.12 (2010/07/20)

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

@@ -1,5 +1,5 @@
AC_PREREQ(2.60)
AC_INIT(mpd, 0.16~alpha2, musicpd-dev-team@lists.sourceforge.net)
AC_INIT(mpd, 0.16.1, musicpd-dev-team@lists.sourceforge.net)
AC_CONFIG_SRCDIR([src/main.c])
AM_INIT_AUTOMAKE([foreign 1.10 dist-bzip2 subdir-objects])
AM_CONFIG_HEADER(config.h)
@@ -13,6 +13,7 @@ dnl Programs
dnl ---------------------------------------------------------------------------
AC_PROG_CC_C99
AC_PROG_CXX
AC_PROG_RANLIB
HAVE_CXX=yes
if test x$CXX = xg++; then
@@ -155,6 +156,10 @@ AC_ARG_ENABLE(documentation,
[build documentation (default: disable)]),,
[enable_documentation=no])
AC_ARG_ENABLE(ffado,
AS_HELP_STRING([--enable-ffado], [enable libffado (FireWire) support]),,
[enable_ffado=no])
AC_ARG_ENABLE(ffmpeg,
AS_HELP_STRING([--enable-ffmpeg],
[enable FFMPEG support]),,
@@ -437,7 +442,7 @@ if test x$enable_tcp = xyes; then
fi
case "$host_os" in
mingw* | windows*)
mingw* | windows* | cygwin*)
enable_un=no
;;
esac
@@ -630,7 +635,9 @@ fi
AM_CONDITIONAL(ENABLE_LASTFM, test x$enable_lastfm = xyes)
dnl ---------------------------------- libogg ---------------------------------
PKG_CHECK_MODULES(OGG, [ogg], enable_ogg=yes, enable_ogg=no)
if test x$with_tremor == xno || test -z $with_tremor; then
PKG_CHECK_MODULES(OGG, [ogg], enable_ogg=yes, enable_ogg=no)
fi
dnl ---------------------------------- libmms ---------------------------------
MPD_AUTO_PKG(mms, MMS, [libmms >= 0.4],
@@ -843,15 +850,6 @@ if test x$enable_modplug = xyes; then
fi
AM_CONDITIONAL(HAVE_MODPLUG, test x$enable_modplug = xyes)
dnl --------------------------- sndfile/modplug test --------------------------
if test x$enable_sndfile = xauto && test x$enable_modplug = xyes; then
dnl If modplug is enabled, enable sndfile only if explicitly
dnl requested - modplug's modplug/sndfile.h is known to
dnl conflict with libsndfile's sndfile.h.
AC_MSG_NOTICE([disabling libsndfile auto-detection, because the modplug decoder is enabled])
enable_sndfile=no
fi
dnl -------------------------------- libsndfile -------------------------------
dnl See above test, which may disable this.
MPD_AUTO_PKG(sndfile, SNDFILE, [sndfile],
@@ -914,13 +912,13 @@ AM_CONDITIONAL(HAVE_MPCDEC, test x$enable_mpc = xyes)
dnl -------------------------------- Ogg Tremor -------------------------------
if test x$with_tremor = xyes || test x$with_tremor = xno; then
use_tremor="$with_tremor"
enable_tremor="$with_tremor"
else
tremor_prefix="$with_tremor"
use_tremor=yes
enable_tremor=yes
fi
if test x$use_tremor = xyes; then
if test x$enable_tremor = xyes; then
if test "x$tremor_libraries" != "x" ; then
TREMOR_LIBS="-L$tremor_libraries"
elif test "x$tremor_prefix" != "x" ; then
@@ -951,7 +949,7 @@ AC_SUBST(TREMOR_LIBS)
dnl --------------------------------- OggFLAC ---------------------------------
dnl OggFLAC must go after Ogg Tremor
if test x$use_tremor = xyes && test $xenable_oggflac = xyes; then
if test x$enable_tremor = xyes && test x$enable_oggflac = xyes; then
AC_MSG_WARN([disabling OggFLAC support because it is incompatible with tremor])
enable_oggflac=no
fi
@@ -968,8 +966,11 @@ fi
AM_CONDITIONAL(HAVE_OGGFLAC, test x$enable_oggflac = xyes)
dnl -------------------------------- Ogg Vorbis -------------------------------
if test x$enable_tremor != xno && test x$enable_vorbis = xyes; then
if test x$enable_ogg = xyes; then
if test x$enable_vorbis = xyes; then
if test x$enable_tremor = xyes; then
AC_MSG_WARN(["OggTremor detected, could not enable Vorbis."])
enable_vorbis=no
elif test x$enable_ogg = xyes; then
PKG_CHECK_MODULES(VORBIS, [vorbis vorbisfile],
AC_DEFINE(ENABLE_VORBIS_DECODER, 1, [Define for Ogg Vorbis support]),
enable_vorbis=no)
@@ -1065,6 +1066,7 @@ if
test x$enable_mpg123 = xno &&
test x$enable_oggflac = xno &&
test x$enable_sidplay = xno &&
test x$enable_tremor = xno &&
test x$enable_vorbis = xno &&
test x$enable_wavpack = xno &&
test x$enable_wildmidi = xno &&
@@ -1199,6 +1201,17 @@ fi
AM_CONDITIONAL(HAVE_ALSA, test x$enable_alsa = xyes)
dnl ----------------------------------- FFADO ---------------------------------
MPD_AUTO_PKG(ffado, FFADO, [libffado],
[libffado output plugin], [libffado not found])
if test x$enable_ffado = xyes; then
AC_DEFINE(ENABLE_FFADO_OUTPUT, 1, [Define to enable the libffado output plugin])
fi
AM_CONDITIONAL(ENABLE_FFADO_OUTPUT, test x$enable_ffado = xyes)
dnl ----------------------------------- FIFO ----------------------------------
if test x$enable_fifo = xyes; then
AC_CHECK_FUNC([mkfifo],
@@ -1369,26 +1382,27 @@ esac
AM_CONDITIONAL(ENABLE_SOLARIS_OUTPUT, test x$enable_solaris_output = xyes)
dnl --------------------------------- Solaris ---------------------------------
dnl --------------------------------- WinMM ---------------------------------
case "$host_os" in
mingw32* | windows*)
AC_DEFINE(ENABLE_WIN32_OUTPUT, 1, [Define to enable WIN32 wave support])
enable_win32_output=yes
AC_DEFINE(ENABLE_WINMM_OUTPUT, 1, [Define to enable WinMM support])
enable_winmm_output=yes
MPD_LIBS="$MPD_LIBS -lwinmm"
;;
*)
enable_win32_output=no
enable_winmm_output=no
;;
esac
AM_CONDITIONAL(ENABLE_WIN32_OUTPUT, test x$enable_win32_output = xyes)
AM_CONDITIONAL(ENABLE_WINMM_OUTPUT, test x$enable_winmm_output = xyes)
dnl --------------------- Post Audio Output Plugins Tests ---------------------
if
test x$enable_alsa = xno &&
test x$enable_ao = xno &&
test x$enable_ffado = xno &&
test x$enable_fifo = xno &&
test x$enable_httpd_output = xno &&
test x$enable_jack = xno &&
@@ -1401,7 +1415,7 @@ if
test x$enable_recorder_output = xno &&
test x$enable_shout = xno &&
test x$enable_solaris_output = xno &&
test x$enable_win32_output = xno &&
test x$enable_winmm_output = xno &&
AC_MSG_ERROR([No Audio Output types configured!])
fi
@@ -1473,23 +1487,23 @@ dnl ---------------------------------------------------------------------------
echo ''
echo '########### MPD CONFIGURATION ############'
echo -ne '\nArchive support:\n\t'
printf '\nArchive support:\n\t'
results(bzip2,[bzip2])
results(iso9660,[ISO9660])
results(zzip,[ZIP])
if test x$with_zeroconf != xno; then
echo -ne '\nAutodiscovery support:\n\t'
printf '\nAutodiscovery support:\n\t'
results(avahi, [Avahi])
results(bonjour, [Bonjour])
fi
echo -ne '\nClient support:\n\t'
printf '\nClient support:\n\t'
results(ipv6, "IPv6")
results(tcp, "TCP")
results(un,[UNIX Domain Sockets])
echo -ne '\nFile format support:\n\t'
printf '\nFile format support:\n\t'
results(aac, [AAC])
results(sidplay, [C64 SID])
results(ffmpeg, [FFMPEG])
@@ -1497,7 +1511,7 @@ results(flac, [FLAC])
results(fluidsynth, [FluidSynth])
results(gme, [GME])
results(sndfile, [libsndfile])
echo -ne '\n\t'
printf '\n\t'
results(mikmod, [MikMod])
results(modplug, [MODPLUG])
results(mad, [MAD])
@@ -1505,46 +1519,47 @@ results(mpg123, [MPG123])
results(mp4, [MP4])
results(mpc, [Musepack])
results(oggflac, [OggFLAC], flac)
echo -ne '\n\t'
results(with_tremor, [OggTremor])
printf '\n\t'
results(tremor, [OggTremor])
results(vorbis, [OggVorbis])
results(audiofile, [WAVE])
results(wavpack, [WavPack])
results(wildmidi, [WildMidi])
echo -en '\nOther features:\n\t'
printf '\nOther features:\n\t'
results(lsr, [libsamplerate])
results(inotify, [inotify])
results(sqlite, [SQLite])
echo -en '\nMetadata support:\n\t'
printf '\nMetadata support:\n\t'
results(cue,[cue])
results(id3,[ID3])
echo -en '\nPlayback support:\n\t'
printf '\nPlayback support:\n\t'
results(alsa,ALSA)
results(ffado,FFADO)
results(fifo,FIFO)
results(recorder_output,[File Recorder])
results(httpd_output,[HTTP Daemon])
results(jack,[JACK])
results(ao,[libao])
results(oss,[OSS])
echo -ne '\n\t'
printf '\n\t'
results(openal,[OpenAL])
results(osx, [OS X])
results(pipe_output, [Pipeline])
results(pulse, [PulseAudio])
results(mvp, [Media MVP])
results(shout, [SHOUTcast])
echo -ne '\n\t'
printf '\n\t'
results(solaris, [Solaris])
results(win32_output, [WIN32 wave])
results(winmm_output, [WinMM])
if
test x$enable_shout = xyes ||
test x$enable_recorder = xyes ||
test x$enable_httpd_output = xyes; then
echo -en '\nStreaming encoder support:\n\t'
printf '\nStreaming encoder support:\n\t'
results(flac_encoder, [FLAC])
results(lame_encoder, [LAME])
results(vorbis_encoder, [Ogg Vorbis])
@@ -1552,19 +1567,14 @@ if
results(wave_encoder, [WAVE])
fi
echo -en '\nStreaming support:\n\t'
printf '\nStreaming support:\n\t'
results(curl,[CURL])
results(lastfm,[Last.FM])
results(mms,[MMS])
echo -ne '\n\n##########################################\n\n'
printf '\n\n##########################################\n\n'
if test x$enable_sndfile = xyes && test x$enable_modplug = xyes; then
AC_MSG_WARN([compilation may fail, because libmodplug conflicts with libsndfile])
AC_MSG_WARN([libmodplug ships modplug/sndfile.h, which hides libsndfile's sndfile.h])
fi
echo -ne 'Generating files needed for compilation\n'
echo 'Generating files needed for compilation'
dnl ---------------------------------------------------------------------------
dnl Generate files

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

@@ -240,6 +240,12 @@
<returnvalue>0 or 1</returnvalue>
</para>
</listitem>
<listitem>
<para>
<varname>random</varname>:
<returnvalue>0 or 1</returnvalue>
</para>
</listitem>
<listitem>
<para>
<varname>single</varname>:
@@ -1208,16 +1214,18 @@ OK
<command>find</command>
<arg choice="req"><replaceable>TYPE</replaceable></arg>
<arg choice="req"><replaceable>WHAT</replaceable></arg>
<arg choice="opt"><replaceable>...</replaceable></arg>
</cmdsynopsis>
</term>
<listitem>
<para>
Finds songs in the db that are exactly
<varname>WHAT</varname>. <varname>TYPE</varname> should
be <parameter>album</parameter>,
<parameter>artist</parameter>, or
<parameter>title</parameter>. <varname>WHAT</varname>
is what to find.
<varname>WHAT</varname>. <varname>TYPE</varname> can
be any tag supported by MPD, or one of the two special
parameters — <parameter>file</parameter> to search by
full path (relative to database root), and
<parameter>any</parameter> to match against all
available tags. <varname>WHAT</varname> is what to find.
</para>
</listitem>
</varlistentry>
@@ -1227,14 +1235,14 @@ OK
<command>findadd</command>
<arg choice="req"><replaceable>TYPE</replaceable></arg>
<arg choice="req"><replaceable>WHAT</replaceable></arg>
<arg choice="opt"><replaceable>...</replaceable></arg>
</cmdsynopsis>
</term>
<listitem>
<para>
Finds songs in the db that are exactly
<varname>WHAT</varname> and adds them to current playlist.
<varname>TYPE</varname> can be any tag supported by MPD.
<varname>WHAT</varname> is what to find.
Parameters have the same meaning as for <command>find</command>.
</para>
</listitem>
</varlistentry>
@@ -1249,7 +1257,8 @@ OK
<listitem>
<para>
Lists all tags of the specified type.
<varname>TYPE</varname> should be album or artist.
<varname>TYPE</varname> can be any tag supported by MPD or
<parameter>file</parameter>.
</para>
<para>
<varname>ARTIST</varname> is an optional parameter when
@@ -1312,17 +1321,15 @@ OK
<command>search</command>
<arg choice="req"><replaceable>TYPE</replaceable></arg>
<arg choice="req"><replaceable>WHAT</replaceable></arg>
<arg choice="opt"><replaceable>...</replaceable></arg>
</cmdsynopsis>
</term>
<listitem>
<para>
Searches for any song that contains
<varname>WHAT</varname>. <varname>TYPE</varname> can be
<parameter>title</parameter>,
<parameter>artist</parameter>,
<parameter>album</parameter> or
<parameter>filename</parameter>. Search is not case
sensitive.
<varname>WHAT</varname>. Parameters have the same meaning
as for <command>find</command>, except that search is not
case sensitive.
</para>
</listitem>
</varlistentry>

@@ -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])])

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

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

113
src/ape.c Normal file

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

42
src/ape.h Normal file

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

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

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

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

@@ -1715,15 +1715,11 @@ handle_sticker_song(struct client *client, int argc, char *argv[])
}
sticker = sticker_song_get(song);
if (NULL == sticker) {
command_error(client, ACK_ERROR_NO_EXIST,
"no stickers found");
return COMMAND_RETURN_ERROR;
if (sticker) {
sticker_print(client, sticker);
sticker_free(sticker);
}
sticker_print(client, sticker);
sticker_free(sticker);
return COMMAND_RETURN_OK;
/* set song song_id id key */
} else if (argc == 6 && strcmp(argv[1], "set") == 0) {

@@ -23,6 +23,7 @@
#include "tokenizer.h"
#include "path.h"
#include "glib_compat.h"
#include "mpd_error.h"
#include <glib.h>
@@ -498,8 +499,8 @@ config_get_path(const char *name)
path = parsePath(param->value);
if (path == NULL)
g_error("error parsing \"%s\" at line %i\n",
name, param->line);
MPD_ERROR("error parsing \"%s\" at line %i\n",
name, param->line);
g_free(param->value);
return param->value = path;
@@ -517,7 +518,8 @@ config_get_unsigned(const char *name, unsigned default_value)
value = strtol(param->value, &endptr, 0);
if (*endptr != 0 || value < 0)
g_error("Not a valid non-negative number in line %i", param->line);
MPD_ERROR("Not a valid non-negative number in line %i",
param->line);
return (unsigned)value;
}
@@ -534,10 +536,10 @@ config_get_positive(const char *name, unsigned default_value)
value = strtol(param->value, &endptr, 0);
if (*endptr != 0)
g_error("Not a valid number in line %i", param->line);
MPD_ERROR("Not a valid number in line %i", param->line);
if (value <= 0)
g_error("Not a positive number in line %i", param->line);
MPD_ERROR("Not a positive number in line %i", param->line);
return (unsigned)value;
}
@@ -569,9 +571,9 @@ bool config_get_bool(const char *name, bool default_value)
success = get_bool(param->value, &value);
if (!success)
g_error("%s is not a boolean value (yes, true, 1) or "
"(no, false, 0) on line %i\n",
name, param->line);
MPD_ERROR("%s is not a boolean value (yes, true, 1) or "
"(no, false, 0) on line %i\n",
name, param->line);
return value;
}
@@ -601,10 +603,10 @@ config_get_block_unsigned(const struct config_param *param, const char *name,
value = strtol(bp->value, &endptr, 0);
if (*endptr != 0)
g_error("Not a valid number in line %i", bp->line);
MPD_ERROR("Not a valid number in line %i", bp->line);
if (value < 0)
g_error("Not a positive number in line %i", bp->line);
MPD_ERROR("Not a positive number in line %i", bp->line);
return (unsigned)value;
}
@@ -621,9 +623,9 @@ config_get_block_bool(const struct config_param *param, const char *name,
success = get_bool(bp->value, &value);
if (!success)
g_error("%s is not a boolean value (yes, true, 1) or "
"(no, false, 0) on line %i\n",
name, bp->line);
MPD_ERROR("%s is not a boolean value (yes, true, 1) or "
"(no, false, 0) on line %i\n",
name, bp->line);
return value;
}

@@ -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
/**

@@ -231,16 +231,18 @@ static enum sample_format
ffmpeg_sample_format(G_GNUC_UNUSED const AVCodecContext *codec_context)
{
#if LIBAVCODEC_VERSION_INT >= ((51<<16)+(41<<8)+0)
int bits = (uint8_t) av_get_bits_per_sample_format(codec_context->sample_fmt);
/* XXX implement & test other sample formats */
switch (bits) {
case 16:
switch (codec_context->sample_fmt) {
case SAMPLE_FMT_S16:
return SAMPLE_FORMAT_S16;
}
return SAMPLE_FORMAT_UNDEFINED;
case SAMPLE_FMT_S32:
return SAMPLE_FORMAT_S32;
default:
g_warning("Unsupported libavcodec SampleFormat value: %d",
codec_context->sample_fmt);
return SAMPLE_FORMAT_UNDEFINED;
}
#else
/* XXX fixme 16-bit for older ffmpeg (13 Aug 2007) */
return SAMPLE_FORMAT_S16;
@@ -522,7 +524,9 @@ static const char *const ffmpeg_suffixes[] = {
"atrac", "au", "aud", "avi", "avm2", "avs", "bap", "bfi", "c93", "cak",
"cin", "cmv", "cpk", "daud", "dct", "divx", "dts", "dv", "dvd", "dxa",
"eac3", "film", "flac", "flc", "fli", "fll", "flx", "flv", "g726",
"gsm", "gxf", "iss", "m1v", "m2v", "m2t", "m2ts", "m4a", "m4v", "mad",
"gsm", "gxf", "iss", "m1v", "m2v", "m2t", "m2ts",
"m4a", "m4b", "m4v",
"mad",
"mj2", "mjpeg", "mjpg", "mka", "mkv", "mlp", "mm", "mmf", "mov", "mp+",
"mp1", "mp2", "mp3", "mp4", "mpc", "mpeg", "mpg", "mpga", "mpp", "mpu",
"mve", "mvi", "mxf", "nc", "nsv", "nut", "nuv", "oga", "ogm", "ogv",

@@ -87,7 +87,7 @@ flac_find_string_comment(const FLAC__StreamMetadata *block,
int offset;
size_t pos;
int len;
unsigned char tmp, *p;
const unsigned char *p;
*str = NULL;
offset = FLAC__metadata_object_vorbiscomment_find_entry_from(block, 0,
@@ -101,10 +101,7 @@ flac_find_string_comment(const FLAC__StreamMetadata *block,
return false;
p = &block->data.vorbis_comment.comments[offset].entry[pos];
tmp = p[len];
p[len] = '\0';
*str = strdup((char *)p);
p[len] = tmp;
*str = g_strndup((const char *)p, len);
return true;
}

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

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

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

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

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

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

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

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

@@ -30,8 +30,8 @@
#define DIRECTORY_DIR "directory: "
#define DEVICE_INARCHIVE (unsigned)(-1)
#define DEVICE_CONTAINER (unsigned)(-2)
#define DEVICE_INARCHIVE (dev_t)(-1)
#define DEVICE_CONTAINER (dev_t)(-2)
struct directory {
struct dirvec children;

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

@@ -22,6 +22,7 @@
#include "encoder_plugin.h"
#include "tag.h"
#include "audio_format.h"
#include "mpd_error.h"
#include <vorbis/vorbisenc.h>
@@ -374,7 +375,7 @@ vorbis_encoder_read(struct encoder *_encoder, void *_dest, size_t length)
if (nbytes > length)
/* XXX better error handling */
g_error("buffer too small");
MPD_ERROR("buffer too small");
memcpy(dest, page.header, page.header_len);
memcpy(dest + page.header_len, page.body, page.body_len);
@@ -385,7 +386,7 @@ vorbis_encoder_read(struct encoder *_encoder, void *_dest, size_t length)
static const char *
vorbis_encoder_get_mime_type(G_GNUC_UNUSED struct encoder *_encoder)
{
return "application/x-ogg";
return "audio/ogg";
}
const struct encoder_plugin vorbis_encoder_plugin = {

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

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

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

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

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

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

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

@@ -264,7 +264,7 @@ input_curl_select(struct input_curl *c, GError **error_r)
return -1;
}
#if LIBCURL_VERSION_NUM >= 0x070f00
#if LIBCURL_VERSION_NUM >= 0x070f04
long timeout2;
mcode = curl_multi_timeout(c->multi, &timeout2);
if (mcode != CURLM_OK) {

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

@@ -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";

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

@@ -29,6 +29,9 @@
#include <assert.h>
#include <string.h>
#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "httpd_output"
struct httpd_client {
/**
* The httpd output object this client is connected to.

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

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

@@ -214,15 +214,6 @@ osx_output_open(void *data, struct audio_format *audio_format, GError **error)
stream_description.mSampleRate = audio_format->sample_rate;
stream_description.mFormatID = kAudioFormatLinearPCM;
stream_description.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger;
#if G_BYTE_ORDER == G_BIG_ENDIAN
stream_description.mFormatFlags |= kLinearPCMFormatFlagIsBigEndian;
#endif
stream_description.mBytesPerPacket =
audio_format_frame_size(audio_format);
stream_description.mFramesPerPacket = 1;
stream_description.mBytesPerFrame = stream_description.mBytesPerPacket;
stream_description.mChannelsPerFrame = audio_format->channels;
switch (audio_format->format) {
case SAMPLE_FORMAT_S8:
@@ -239,6 +230,16 @@ osx_output_open(void *data, struct audio_format *audio_format, GError **error)
break;
}
#if G_BYTE_ORDER == G_BIG_ENDIAN
stream_description.mFormatFlags |= kLinearPCMFormatFlagIsBigEndian;
#endif
stream_description.mBytesPerPacket =
audio_format_frame_size(audio_format);
stream_description.mFramesPerPacket = 1;
stream_description.mBytesPerFrame = stream_description.mBytesPerPacket;
stream_description.mChannelsPerFrame = audio_format->channels;
result = AudioUnitSetProperty(od->au, kAudioUnitProperty_StreamFormat,
kAudioUnitScope_Input, 0,
&stream_description,

@@ -21,6 +21,7 @@
#include "output_api.h"
#include "encoder_plugin.h"
#include "encoder_list.h"
#include "mpd_error.h"
#include <shout/shout.h>
#include <glib.h>
@@ -101,8 +102,8 @@ static void free_shout_data(struct shout_data *sd)
#define check_block_param(name) { \
block_param = config_get_block_param(param, name); \
if (!block_param) { \
g_error("no \"%s\" defined for shout device defined at line " \
"%i\n", name, param->line); \
MPD_ERROR("no \"%s\" defined for shout device defined at line " \
"%i\n", name, param->line); \
} \
}
@@ -341,7 +342,6 @@ write_page(struct shout_data *sd, GError **error)
if (sd->buf.len == 0)
return true;
shout_sync(sd->shout_conn);
err = shout_send(sd->shout_conn, sd->buf.data, sd->buf.len);
if (!handle_shout_error(sd, err, error))
return false;
@@ -440,6 +440,18 @@ my_shout_open_device(void *data, struct audio_format *audio_format,
return true;
}
static unsigned
my_shout_delay(void *data)
{
struct shout_data *sd = (struct shout_data *)data;
int delay = shout_delay(sd->shout_conn);
if (delay < 0)
delay = 0;
return delay;
}
static size_t
my_shout_play(void *data, const void *chunk, size_t size, GError **error)
{
@@ -454,15 +466,8 @@ my_shout_play(void *data, const void *chunk, size_t size, GError **error)
static bool
my_shout_pause(void *data)
{
struct shout_data *sd = (struct shout_data *)data;
static const char silence[1020];
if (shout_delay(sd->shout_conn) > 500) {
/* cap the latency for unpause */
g_usleep(500000);
return true;
}
return my_shout_play(data, silence, sizeof(silence), NULL);
}
@@ -489,7 +494,7 @@ shout_tag_to_metadata(const struct tag *tag, char *dest, size_t size)
}
}
snprintf(dest, size, "%s - %s", title, artist);
snprintf(dest, size, "%s - %s", artist, title);
}
static void my_shout_set_tag(void *data,
@@ -539,6 +544,7 @@ const struct audio_output_plugin shoutPlugin = {
.init = my_shout_init_driver,
.finish = my_shout_finish_driver,
.open = my_shout_open_device,
.delay = my_shout_delay,
.play = my_shout_play,
.pause = my_shout_pause,
.cancel = my_shout_drop_buffered_audio,

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

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

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

@@ -26,6 +26,7 @@
#include "pipe.h"
#include "buffer.h"
#include "player_control.h"
#include "mpd_error.h"
#ifndef NDEBUG
#include "chunk.h"
@@ -122,17 +123,17 @@ audio_output_all_init(void)
if (!audio_output_init(output, param, &error)) {
if (param != NULL)
g_error("line %i: %s",
param->line, error->message);
MPD_ERROR("line %i: %s",
param->line, error->message);
else
g_error("%s", error->message);
MPD_ERROR("%s", error->message);
}
/* require output names to be unique: */
for (j = 0; j < i; j++) {
if (!strcmp(output->name, audio_outputs[j].name)) {
g_error("output devices with identical "
"names: %s\n", output->name);
MPD_ERROR("output devices with identical "
"names: %s\n", output->name);
}
}
}
@@ -144,6 +145,7 @@ audio_output_all_finish(void)
unsigned int i;
for (i = 0; i < num_audio_outputs; i++) {
audio_output_disable(&audio_outputs[i]);
audio_output_finish(&audio_outputs[i]);
}
@@ -321,7 +323,7 @@ audio_output_all_open(const struct audio_format *audio_format,
else
/* if the pipe hasn't been cleared, the the audio
format must not have changed */
assert(music_pipe_size(g_mp) == 0 ||
assert(music_pipe_empty(g_mp) ||
audio_format_equals(audio_format,
&input_audio_format));
@@ -434,7 +436,7 @@ audio_output_all_check(void)
assert(g_mp != NULL);
while ((chunk = music_pipe_peek(g_mp)) != NULL) {
assert(music_pipe_size(g_mp) > 0);
assert(!music_pipe_empty(g_mp));
if (!chunk_is_consumed(chunk))
/* at least one output is not finished playing

@@ -102,6 +102,12 @@ audio_output_disable(struct audio_output *ao)
g_mutex_unlock(ao->mutex);
}
static void
audio_output_close_locked(struct audio_output *ao);
/**
* Object must be locked (and unlocked) by the caller.
*/
static bool
audio_output_open(struct audio_output *ao,
const struct audio_format *audio_format,
@@ -173,6 +179,8 @@ audio_output_open(struct audio_output *ao,
static void
audio_output_close_locked(struct audio_output *ao)
{
assert(ao != NULL);
if (ao->mixer != NULL)
mixer_auto_close(ao->mixer);
@@ -251,25 +259,6 @@ void audio_output_cancel(struct audio_output *ao)
g_mutex_unlock(ao->mutex);
}
void audio_output_close(struct audio_output *ao)
{
if (ao->mixer != NULL)
mixer_auto_close(ao->mixer);
g_mutex_lock(ao->mutex);
assert(!ao->open || ao->fail_timer == NULL);
if (ao->open)
ao_command(ao, AO_COMMAND_CLOSE);
else if (ao->fail_timer != NULL) {
g_timer_destroy(ao->fail_timer);
ao->fail_timer = NULL;
}
g_mutex_unlock(ao->mutex);
}
void
audio_output_release(struct audio_output *ao)
{
@@ -279,6 +268,16 @@ audio_output_release(struct audio_output *ao)
audio_output_close(ao);
}
void audio_output_close(struct audio_output *ao)
{
assert(ao != NULL);
assert(!ao->open || ao->fail_timer == NULL);
g_mutex_lock(ao->mutex);
audio_output_close_locked(ao);
g_mutex_unlock(ao->mutex);
}
void audio_output_finish(struct audio_output *ao)
{
audio_output_close(ao);

@@ -196,7 +196,8 @@ struct audio_output {
const struct music_pipe *pipe;
/**
* This mutex protects #open, #chunk and #chunk_finished.
* This mutex protects #open, #fail_timer, #chunk and
* #chunk_finished.
*/
GMutex *mutex;

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

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

@@ -28,6 +28,7 @@
#include "filter_plugin.h"
#include "filter/convert_filter_plugin.h"
#include "filter/replay_gain_filter_plugin.h"
#include "mpd_error.h"
#include <glib.h>
@@ -133,10 +134,18 @@ ao_open(struct audio_output *ao)
struct audio_format_string af_string;
assert(!ao->open);
assert(ao->fail_timer == NULL);
assert(ao->pipe != NULL);
assert(ao->chunk == NULL);
if (ao->fail_timer != NULL) {
/* this can only happen when this
output thread fails while
audio_output_open() is run in the
player thread */
g_timer_destroy(ao->fail_timer);
ao->fail_timer = NULL;
}
/* enable the device (just in case the last enable has failed) */
if (!ao_enable(ao))
@@ -277,6 +286,30 @@ ao_reopen(struct audio_output *ao)
ao_open(ao);
}
/**
* Wait until the output's delay reaches zero.
*
* @return true if playback should be continued, false if a command
* was issued
*/
static bool
ao_wait(struct audio_output *ao)
{
while (true) {
unsigned delay = ao_plugin_delay(ao->plugin, ao->data);
if (delay == 0)
return true;
GTimeVal tv;
g_get_current_time(&tv);
g_time_val_add(&tv, delay * 1000);
(void)g_cond_timed_wait(ao->cond, ao->mutex, &tv);
if (ao->command != AO_COMMAND_NONE)
return false;
}
}
static const char *
ao_chunk_data(struct audio_output *ao, const struct music_chunk *chunk,
struct filter *replay_gain_filter,
@@ -413,6 +446,9 @@ ao_play_chunk(struct audio_output *ao, const struct music_chunk *chunk)
while (size > 0 && ao->command == AO_COMMAND_NONE) {
size_t nbytes;
if (!ao_wait(ao))
break;
g_mutex_unlock(ao->mutex);
nbytes = ao_plugin_play(ao->plugin, ao->data, data, size,
&error);
@@ -427,7 +463,9 @@ ao_play_chunk(struct audio_output *ao, const struct music_chunk *chunk)
/* don't automatically reopen this device for
10 seconds */
assert(ao->fail_timer == NULL);
ao->fail_timer = g_timer_new();
return false;
}
@@ -510,6 +548,9 @@ static void ao_pause(struct audio_output *ao)
ao_command_finished(ao);
do {
if (!ao_wait(ao))
break;
g_mutex_unlock(ao->mutex);
ret = ao_plugin_pause(ao->plugin, ao->data);
g_mutex_lock(ao->mutex);
@@ -629,5 +670,5 @@ void audio_output_thread_start(struct audio_output *ao)
assert(ao->command == AO_COMMAND_NONE);
if (!(ao->thread = g_thread_create(audio_output_task, ao, true, &e)))
g_error("Failed to spawn output task: %s\n", e->message);
MPD_ERROR("Failed to spawn output task: %s\n", e->message);
}

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

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

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

@@ -99,4 +99,10 @@ music_pipe_push(struct music_pipe *mp, struct music_chunk *chunk);
unsigned
music_pipe_size(const struct music_pipe *mp);
static inline bool
music_pipe_empty(const struct music_pipe *mp)
{
return music_pipe_size(mp) == 0;
}
#endif

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

@@ -108,11 +108,8 @@ playlist_song_started(struct playlist *playlist)
playlist->current = playlist->queued;
playlist->queued = -1;
/* Set pause and remove the single mode. */
/* Pause if we are in single mode. */
if(playlist->queue.single && !playlist->queue.repeat) {
playlist->queue.single = false;
idle_add(IDLE_OPTIONS);
pc_set_pause(true);
}
@@ -238,7 +235,7 @@ playlist_sync(struct playlist *playlist)
/* make sure the queued song is always set (if
possible) */
if (pc.next_song == NULL && playlist->queued != -1)
if (pc.next_song == NULL && playlist->queued < 0)
playlist_update_queued_song(playlist, NULL);
}
}

@@ -0,0 +1,321 @@
/*
* Copyright (C) 2003-2010 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "config.h"
#include "playlist/rss_playlist_plugin.h"
#include "playlist_plugin.h"
#include "input_stream.h"
#include "song.h"
#include "tag.h"
#include <glib.h>
#include <assert.h>
#include <string.h>
#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "rss"
/**
* This is the state object for the GLib XML parser.
*/
struct rss_parser {
/**
* The list of songs (in reverse order because that's faster
* while adding).
*/
GSList *songs;
/**
* The current position in the XML file.
*/
enum {
ROOT, ITEM,
} state;
/**
* The current tag within the "entry" element. This is only
* valid if state==ITEM. TAG_NUM_OF_ITEM_TYPES means there
* is no (known) tag.
*/
enum tag_type tag;
/**
* The current song. It is allocated after the "location"
* element.
*/
struct song *song;
};
static const gchar *
get_attribute(const gchar **attribute_names, const gchar **attribute_values,
const gchar *name)
{
for (unsigned i = 0; attribute_names[i] != NULL; ++i)
if (g_ascii_strcasecmp(attribute_names[i], name) == 0)
return attribute_values[i];
return NULL;
}
static void
rss_start_element(G_GNUC_UNUSED GMarkupParseContext *context,
const gchar *element_name,
const gchar **attribute_names,
const gchar **attribute_values,
gpointer user_data, G_GNUC_UNUSED GError **error)
{
struct rss_parser *parser = user_data;
switch (parser->state) {
case ROOT:
if (g_ascii_strcasecmp(element_name, "item") == 0) {
parser->state = ITEM;
parser->song = song_remote_new("rss:");
parser->tag = TAG_NUM_OF_ITEM_TYPES;
}
break;
case ITEM:
if (g_ascii_strcasecmp(element_name, "enclosure") == 0) {
const gchar *href = get_attribute(attribute_names,
attribute_values,
"url");
if (href != NULL) {
/* create new song object, and copy
the existing tag over; we cannot
replace the existing song's URI,
because that attribute is
immutable */
struct song *song = song_remote_new(href);
if (parser->song != NULL) {
song->tag = parser->song->tag;
parser->song->tag = NULL;
song_free(parser->song);
}
parser->song = song;
}
} else if (g_ascii_strcasecmp(element_name, "title") == 0)
parser->tag = TAG_TITLE;
else if (g_ascii_strcasecmp(element_name, "itunes:author") == 0)
parser->tag = TAG_ARTIST;
break;
}
}
static void
rss_end_element(G_GNUC_UNUSED GMarkupParseContext *context,
const gchar *element_name,
gpointer user_data, G_GNUC_UNUSED GError **error)
{
struct rss_parser *parser = user_data;
switch (parser->state) {
case ROOT:
break;
case ITEM:
if (g_ascii_strcasecmp(element_name, "item") == 0) {
if (strcmp(parser->song->uri, "rss:") != 0)
parser->songs = g_slist_prepend(parser->songs,
parser->song);
else
song_free(parser->song);
parser->state = ROOT;
} else
parser->tag = TAG_NUM_OF_ITEM_TYPES;
break;
}
}
static void
rss_text(G_GNUC_UNUSED GMarkupParseContext *context,
const gchar *text, gsize text_len,
gpointer user_data, G_GNUC_UNUSED GError **error)
{
struct rss_parser *parser = user_data;
switch (parser->state) {
case ROOT:
break;
case ITEM:
if (parser->tag != TAG_NUM_OF_ITEM_TYPES) {
if (parser->song->tag == NULL)
parser->song->tag = tag_new();
tag_add_item_n(parser->song->tag, parser->tag,
text, text_len);
}
break;
}
}
static const GMarkupParser rss_parser = {
.start_element = rss_start_element,
.end_element = rss_end_element,
.text = rss_text,
};
static void
song_free_callback(gpointer data, G_GNUC_UNUSED gpointer user_data)
{
struct song *song = data;
song_free(song);
}
static void
rss_parser_destroy(gpointer data)
{
struct rss_parser *parser = data;
if (parser->state >= ITEM)
song_free(parser->song);
g_slist_foreach(parser->songs, song_free_callback, NULL);
g_slist_free(parser->songs);
}
/*
* The playlist object
*
*/
struct rss_playlist {
struct playlist_provider base;
GSList *songs;
};
static struct playlist_provider *
rss_open_stream(struct input_stream *is)
{
struct rss_parser parser = {
.songs = NULL,
.state = ROOT,
};
struct rss_playlist *playlist;
GMarkupParseContext *context;
char buffer[1024];
size_t nbytes;
bool success;
GError *error = NULL;
/* parse the RSS XML file */
context = g_markup_parse_context_new(&rss_parser,
G_MARKUP_TREAT_CDATA_AS_TEXT,
&parser, rss_parser_destroy);
while (true) {
nbytes = input_stream_read(is, buffer, sizeof(buffer), &error);
if (nbytes == 0) {
if (error != NULL) {
g_markup_parse_context_free(context);
g_warning("%s", error->message);
g_error_free(error);
return NULL;
}
break;
}
success = g_markup_parse_context_parse(context, buffer, nbytes,
&error);
if (!success) {
g_warning("XML parser failed: %s", error->message);
g_error_free(error);
g_markup_parse_context_free(context);
return NULL;
}
}
success = g_markup_parse_context_end_parse(context, &error);
if (!success) {
g_warning("XML parser failed: %s", error->message);
g_error_free(error);
g_markup_parse_context_free(context);
return NULL;
}
/* create a #rss_playlist object from the parsed song list */
playlist = g_new(struct rss_playlist, 1);
playlist_provider_init(&playlist->base, &rss_playlist_plugin);
playlist->songs = g_slist_reverse(parser.songs);
parser.songs = NULL;
g_markup_parse_context_free(context);
return &playlist->base;
}
static void
rss_close(struct playlist_provider *_playlist)
{
struct rss_playlist *playlist = (struct rss_playlist *)_playlist;
g_slist_foreach(playlist->songs, song_free_callback, NULL);
g_slist_free(playlist->songs);
g_free(playlist);
}
static struct song *
rss_read(struct playlist_provider *_playlist)
{
struct rss_playlist *playlist = (struct rss_playlist *)_playlist;
struct song *song;
if (playlist->songs == NULL)
return NULL;
song = playlist->songs->data;
playlist->songs = g_slist_remove(playlist->songs, song);
return song;
}
static const char *const rss_suffixes[] = {
"rss",
NULL
};
static const char *const rss_mime_types[] = {
"application/rss+xml",
"text/xml",
NULL
};
const struct playlist_plugin rss_playlist_plugin = {
.name = "rss",
.open_stream = rss_open_stream,
.close = rss_close,
.read = rss_read,
.suffixes = rss_suffixes,
.mime_types = rss_mime_types,
};

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

@@ -46,7 +46,9 @@ bool
playlist_metadata_load(FILE *fp, struct playlist_vector *pv, const char *name,
GString *buffer, GError **error_r)
{
struct playlist_metadata pm;
struct playlist_metadata pm = {
.mtime = 0,
};
char *line, *colon;
const char *value;

@@ -26,6 +26,7 @@
#include "playlist/lastfm_playlist_plugin.h"
#include "playlist/pls_playlist_plugin.h"
#include "playlist/asx_playlist_plugin.h"
#include "playlist/rss_playlist_plugin.h"
#include "playlist/cue_playlist_plugin.h"
#include "playlist/flac_playlist_plugin.h"
#include "input_stream.h"
@@ -33,6 +34,7 @@
#include "utils.h"
#include "conf.h"
#include "glib_compat.h"
#include "mpd_error.h"
#include <assert.h>
#include <string.h>
@@ -44,6 +46,7 @@ static const struct playlist_plugin *const playlist_plugins[] = {
&xspf_playlist_plugin,
&pls_playlist_plugin,
&asx_playlist_plugin,
&rss_playlist_plugin,
#ifdef ENABLE_LASTFM
&lastfm_playlist_plugin,
#endif
@@ -76,7 +79,7 @@ playlist_plugin_config(const char *plugin_name)
const char *name =
config_get_block_string(param, "name", NULL);
if (name == NULL)
g_error("playlist configuration without 'plugin' name in line %d",
MPD_ERROR("playlist configuration without 'plugin' name in line %d",
param->line);
if (strcmp(name, plugin_name) == 0)

@@ -72,6 +72,14 @@ apply_song_metadata(struct song *dest, const struct song *src)
song_free(dest);
}
if (dest->tag != NULL && dest->tag->time > 0 &&
src->start_ms > 0 && src->end_ms == 0 &&
src->start_ms / 1000 < (unsigned)dest->tag->time)
/* the range is open-ended, and the playlist plugin
did not know the total length of the song file
(e.g. last track on a CUE file); fix it up here */
tmp->tag->time = dest->tag->time - src->start_ms / 1000;
return tmp;
}

@@ -59,7 +59,7 @@ playlist_state_save(FILE *fp, const struct playlist *playlist)
pc_get_status(&player_status);
fputs(PLAYLIST_STATE_FILE_STATE "\n", fp);
fputs(PLAYLIST_STATE_FILE_STATE, fp);
if (playlist->playing) {
switch (player_status.state) {

@@ -93,16 +93,21 @@ playlist_vector_add(struct playlist_vector *pv,
pv->head = pm;
}
void
bool
playlist_vector_update_or_add(struct playlist_vector *pv,
const char *name, time_t mtime)
{
struct playlist_metadata **pmp = playlist_vector_find_p(pv, name);
if (pmp != NULL) {
struct playlist_metadata *pm = *pmp;
if (mtime == pm->mtime)
return false;
pm->mtime = mtime;
} else
playlist_vector_add(pv, name, mtime);
return true;
}
bool

@@ -58,7 +58,10 @@ void
playlist_vector_add(struct playlist_vector *pv,
const char *name, time_t mtime);
void
/**
* @return true if the vector or one of its items was modified
*/
bool
playlist_vector_update_or_add(struct playlist_vector *pv,
const char *name, time_t mtime);

@@ -47,7 +47,7 @@ poison_noaccess(void *p, size_t length)
memset(p, 0x01, length);
#ifdef HAVE_VALGRIND_MEMCHECK_H
VALGRIND_MAKE_MEM_NOACCESS(p, length);
(void)VALGRIND_MAKE_MEM_NOACCESS(p, length);
#endif
#endif
}
@@ -68,7 +68,7 @@ poison_undefined(void *p, size_t length)
memset(p, 0x02, length);
#ifdef HAVE_VALGRIND_MEMCHECK_H
VALGRIND_MAKE_MEM_UNDEFINED(p, length);
(void)VALGRIND_MAKE_MEM_UNDEFINED(p, length);
#endif
#endif
}

78
src/replay_gain_ape.c Normal file

@@ -0,0 +1,78 @@
/*
* Copyright (C) 2003-2010 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "config.h"
#include "replay_gain_ape.h"
#include "replay_gain_info.h"
#include "ape.h"
#include <glib.h>
#include <string.h>
#include <stdlib.h>
struct rg_ape_ctx {
struct replay_gain_info *info;
bool found;
};
static bool
replay_gain_ape_callback(unsigned long flags, const char *key,
const char *_value, size_t value_length, void *_ctx)
{
struct rg_ape_ctx *ctx = _ctx;
/* we only care about utf-8 text tags */
if ((flags & (0x3 << 1)) != 0)
return true;
char value[16];
if (value_length >= sizeof(value))
return true;
memcpy(value, _value, value_length);
value[value_length] = 0;
if (g_ascii_strcasecmp(key, "replaygain_track_gain") == 0) {
ctx->info->tuples[REPLAY_GAIN_TRACK].gain = atof(value);
ctx->found = true;
} else if (g_ascii_strcasecmp(key, "replaygain_album_gain") == 0) {
ctx->info->tuples[REPLAY_GAIN_ALBUM].gain = atof(value);
ctx->found = true;
} else if (g_ascii_strcasecmp(key, "replaygain_track_peak") == 0) {
ctx->info->tuples[REPLAY_GAIN_TRACK].peak = atof(value);
ctx->found = true;
} else if (g_ascii_strcasecmp(key, "replaygain_album_peak") == 0) {
ctx->info->tuples[REPLAY_GAIN_ALBUM].peak = atof(value);
ctx->found = true;
}
return true;
}
bool
replay_gain_ape_read(const char *path_fs, struct replay_gain_info *info)
{
struct rg_ape_ctx ctx = {
.info = info,
.found = false,
};
return tag_ape_scan(path_fs, replay_gain_ape_callback, &ctx) &&
ctx.found;
}

32
src/replay_gain_ape.h Normal file

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

@@ -22,6 +22,7 @@
#include "playlist.h"
#include "conf.h"
#include "idle.h"
#include "mpd_error.h"
#include <glib.h>
@@ -91,8 +92,8 @@ void replay_gain_global_init(void)
const struct config_param *param = config_get_param(CONF_REPLAYGAIN);
if (param != NULL && !replay_gain_set_mode_string(param->value)) {
g_error("replaygain value \"%s\" at line %i is invalid\n",
param->value, param->line);
MPD_ERROR("replaygain value \"%s\" at line %i is invalid\n",
param->value, param->line);
}
param = config_get_param(CONF_REPLAYGAIN_PREAMP);
@@ -102,13 +103,13 @@ void replay_gain_global_init(void)
float f = strtod(param->value, &test);
if (*test != '\0') {
g_error("Replaygain preamp \"%s\" is not a number at "
"line %i\n", param->value, param->line);
MPD_ERROR("Replaygain preamp \"%s\" is not a number at "
"line %i\n", param->value, param->line);
}
if (f < -15 || f > 15) {
g_error("Replaygain preamp \"%s\" is not between -15 and"
"15 at line %i\n", param->value, param->line);
MPD_ERROR("Replaygain preamp \"%s\" is not between -15 and"
"15 at line %i\n", param->value, param->line);
}
replay_gain_preamp = pow(10, f / 20.0);
@@ -121,13 +122,13 @@ void replay_gain_global_init(void)
float f = strtod(param->value, &test);
if (*test != '\0') {
g_error("Replaygain missing preamp \"%s\" is not a number at "
"line %i\n", param->value, param->line);
MPD_ERROR("Replaygain missing preamp \"%s\" is not a number at "
"line %i\n", param->value, param->line);
}
if (f < -15 || f > 15) {
g_error("Replaygain missing preamp \"%s\" is not between -15 and"
"15 at line %i\n", param->value, param->line);
MPD_ERROR("Replaygain missing preamp \"%s\" is not between -15 and"
"15 at line %i\n", param->value, param->line);
}
replay_gain_missing_preamp = pow(10, f / 20.0);

444
src/server_socket.c Normal file

@@ -0,0 +1,444 @@
/*
* Copyright (C) 2003-2010 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "config.h"
#include "server_socket.h"
#include "socket_util.h"
#include "fd_util.h"
#include "glib_compat.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <assert.h>
#ifdef WIN32
#define WINVER 0x0501
#include <ws2tcpip.h>
#include <winsock.h>
#else
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <netdb.h>
#endif
#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "listen"
#define DEFAULT_PORT 6600
struct one_socket {
struct one_socket *next;
struct server_socket *parent;
unsigned serial;
int fd;
guint source_id;
char *path;
size_t address_length;
struct sockaddr address;
};
struct server_socket {
server_socket_callback_t callback;
void *callback_ctx;
struct one_socket *sockets, **sockets_tail_r;
unsigned next_serial;
};
static GQuark
server_socket_quark(void)
{
return g_quark_from_static_string("server_socket");
}
struct server_socket *
server_socket_new(server_socket_callback_t callback, void *callback_ctx)
{
struct server_socket *ss = g_new(struct server_socket, 1);
ss->callback = callback;
ss->callback_ctx = callback_ctx;
ss->sockets = NULL;
ss->sockets_tail_r = &ss->sockets;
ss->next_serial = 1;
return ss;
}
void
server_socket_free(struct server_socket *ss)
{
server_socket_close(ss);
while (ss->sockets != NULL) {
struct one_socket *s = ss->sockets;
ss->sockets = s->next;
assert(s->fd < 0);
g_free(s->path);
g_free(s);
}
g_free(ss);
}
/**
* Wraper for sockaddr_to_string() which never fails.
*/
static char *
one_socket_to_string(const struct one_socket *s)
{
char *p = sockaddr_to_string(&s->address, s->address_length, NULL);
if (p == NULL)
p = g_strdup("[unknown]");
return p;
}
static int
get_remote_uid(int fd)
{
#ifdef HAVE_STRUCT_UCRED
struct ucred cred;
socklen_t len = sizeof (cred);
if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &cred, &len) < 0)
return 0;
return cred.uid;
#else
#ifdef HAVE_GETPEEREID
uid_t euid;
gid_t egid;
if (getpeereid(fd, &euid, &egid) == 0)
return euid;
#else
(void)fd;
#endif
return -1;
#endif
}
static gboolean
server_socket_in_event(G_GNUC_UNUSED GIOChannel *source,
G_GNUC_UNUSED GIOCondition condition,
gpointer data)
{
struct one_socket *s = data;
struct sockaddr_storage address;
size_t address_length = sizeof(address);
int fd = accept_cloexec_nonblock(s->fd, (struct sockaddr*)&address,
&address_length);
if (fd >= 0)
s->parent->callback(fd, (const struct sockaddr*)&address,
address_length, get_remote_uid(fd),
s->parent->callback_ctx);
else
g_warning("accept() failed: %s", g_strerror(errno));
return true;
}
bool
server_socket_open(struct server_socket *ss, GError **error_r)
{
struct one_socket *good = NULL, *bad = NULL;
GError *last_error = NULL;
for (struct one_socket *s = ss->sockets; s != NULL; s = s->next) {
assert(s->serial > 0);
assert(good == NULL || s->serial >= good->serial);
assert(s->fd < 0);
if (bad != NULL && s->serial != bad->serial) {
server_socket_close(ss);
g_propagate_error(error_r, last_error);
return false;
}
GError *error = NULL;
s->fd = socket_bind_listen(s->address.sa_family, SOCK_STREAM, 0,
&s->address, s->address_length, 5,
&error);
if (s->fd < 0) {
if (good != NULL && good->serial == s->serial) {
char *address_string = one_socket_to_string(s);
char *good_string = one_socket_to_string(good);
g_warning("bind to '%s' failed: %s "
"(continuing anyway, because "
"binding to '%s' succeeded)",
address_string, error->message,
good_string);
g_free(address_string);
g_free(good_string);
g_error_free(error);
} else if (bad == NULL) {
bad = s;
char *address_string = one_socket_to_string(s);
g_propagate_prefixed_error(&last_error, error,
"Failed to bind to '%s': ",
address_string);
g_free(address_string);
} else
g_error_free(error);
continue;
}
/* allow everybody to connect */
if (s->path != NULL)
chmod(s->path, 0666);
/* register in the GLib main loop */
GIOChannel *channel = g_io_channel_unix_new(s->fd);
s->source_id = g_io_add_watch(channel, G_IO_IN,
server_socket_in_event, s);
g_io_channel_unref(channel);
/* mark this socket as "good", and clear previous
errors */
good = s;
if (bad != NULL) {
bad = NULL;
g_error_free(last_error);
last_error = NULL;
}
}
if (bad != NULL) {
server_socket_close(ss);
g_propagate_error(error_r, last_error);
return false;
}
return true;
}
void
server_socket_close(struct server_socket *ss)
{
for (struct one_socket *s = ss->sockets; s != NULL; s = s->next) {
if (s->fd < 0)
continue;
g_source_remove(s->source_id);
close(s->fd);
s->fd = -1;
}
}
static struct one_socket *
one_socket_new(unsigned serial, const struct sockaddr *address,
size_t address_length)
{
assert(address != NULL);
assert(address_length > 0);
struct one_socket *s = g_malloc(sizeof(*s) - sizeof(s->address) +
address_length);
s->next = NULL;
s->serial = serial;
s->fd = -1;
s->path = NULL;
s->address_length = address_length;
memcpy(&s->address, address, address_length);
return s;
}
static struct one_socket *
server_socket_add_address(struct server_socket *ss,
const struct sockaddr *address,
size_t address_length)
{
assert(ss != NULL);
assert(ss->sockets_tail_r != NULL);
assert(*ss->sockets_tail_r == NULL);
struct one_socket *s = one_socket_new(ss->next_serial,
address, address_length);
s->parent = ss;
*ss->sockets_tail_r = s;
ss->sockets_tail_r = &s->next;
return s;
}
#ifdef HAVE_TCP
/**
* Add a listener on a port on all IPv4 interfaces.
*
* @param port the TCP port
*/
static void
server_socket_add_port_ipv4(struct server_socket *ss, unsigned port)
{
struct sockaddr_in sin;
memset(&sin, 0, sizeof(sin));
sin.sin_port = htons(port);
sin.sin_family = AF_INET;
sin.sin_addr.s_addr = INADDR_ANY;
server_socket_add_address(ss, (const struct sockaddr *)&sin,
sizeof(sin));
}
#ifdef HAVE_IPV6
/**
* Add a listener on a port on all IPv6 interfaces.
*
* @param port the TCP port
*/
static void
server_socket_add_port_ipv6(struct server_socket *ss, unsigned port)
{
struct sockaddr_in6 sin;
memset(&sin, 0, sizeof(sin));
sin.sin6_port = htons(port);
sin.sin6_family = AF_INET6;
server_socket_add_address(ss, (const struct sockaddr *)&sin,
sizeof(sin));
}
#endif /* HAVE_IPV6 */
#endif /* HAVE_TCP */
bool
server_socket_add_port(struct server_socket *ss, unsigned port,
GError **error_r)
{
#ifdef HAVE_TCP
if (port == 0 || port > 0xffff) {
g_set_error(error_r, server_socket_quark(), 0,
"Invalid TCP port");
return false;
}
#ifdef HAVE_IPV6
server_socket_add_port_ipv6(ss, port);
#endif
server_socket_add_port_ipv4(ss, port);
++ss->next_serial;
return true;
#else /* HAVE_TCP */
(void)ss;
(void)port;
g_set_error(error_r, server_socket_quark(), 0,
"TCP support is disabled");
return false;
#endif /* HAVE_TCP */
}
bool
server_socket_add_host(struct server_socket *ss, const char *hostname,
unsigned port, GError **error_r)
{
#ifdef HAVE_TCP
struct addrinfo hints;
memset(&hints, 0, sizeof(hints));
hints.ai_flags = AI_PASSIVE;
hints.ai_family = PF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
char service[20];
g_snprintf(service, sizeof(service), "%u", port);
struct addrinfo *ai;
int ret = getaddrinfo(hostname, service, &hints, &ai);
if (ret != 0) {
g_set_error(error_r, server_socket_quark(), ret,
"Failed to look up host \"%s\": %s",
hostname, gai_strerror(ret));
return false;
}
for (const struct addrinfo *i = ai; i != NULL; i = i->ai_next)
server_socket_add_address(ss, i->ai_addr, i->ai_addrlen);
freeaddrinfo(ai);
++ss->next_serial;
return true;
#else /* HAVE_TCP */
(void)ss;
(void)hostname;
(void)port;
g_set_error(error_r, server_socket_quark(), 0,
"TCP support is disabled");
return false;
#endif /* HAVE_TCP */
}
bool
server_socket_add_path(struct server_socket *ss, const char *path,
GError **error_r)
{
#ifdef HAVE_UN
struct sockaddr_un s_un;
size_t path_length = strlen(path);
if (path_length >= sizeof(s_un.sun_path)) {
g_set_error(error_r, server_socket_quark(), 0,
"UNIX socket path is too long");
return false;
}
unlink(path);
s_un.sun_family = AF_UNIX;
memcpy(s_un.sun_path, path, path_length + 1);
struct one_socket *s =
server_socket_add_address(ss, (const struct sockaddr *)&s_un,
sizeof(s_un));
s->path = g_strdup(path);
return true;
#else /* !HAVE_UN */
(void)ss;
(void)path;
g_set_error(error_r, server_socket_quark(), 0,
"UNIX domain socket support is disabled");
return false;
#endif /* !HAVE_UN */
}

84
src/server_socket.h Normal file

@@ -0,0 +1,84 @@
/*
* Copyright (C) 2003-2010 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef MPD_SERVER_SOCKET_H
#define MPD_SERVER_SOCKET_H
#include <stdbool.h>
#include <glib.h>
struct sockaddr;
typedef void (*server_socket_callback_t)(int fd,
const struct sockaddr *address,
size_t address_length, int uid,
void *ctx);
struct server_socket *
server_socket_new(server_socket_callback_t callback, void *callback_ctx);
void
server_socket_free(struct server_socket *ss);
bool
server_socket_open(struct server_socket *ss, GError **error_r);
void
server_socket_close(struct server_socket *ss);
/**
* Add a listener on a port on all interfaces.
*
* @param port the TCP port
* @param error_r location to store the error occuring, or NULL to
* ignore errors
* @return true on success
*/
bool
server_socket_add_port(struct server_socket *ss, unsigned port,
GError **error_r);
/**
* Resolves a host name, and adds listeners on all addresses in the
* result set.
*
* @param hostname the host name to be resolved
* @param port the TCP port
* @param error_r location to store the error occuring, or NULL to
* ignore errors
* @return true on success
*/
bool
server_socket_add_host(struct server_socket *ss, const char *hostname,
unsigned port, GError **error_r);
/**
* Add a listener on a Unix domain socket.
*
* @param path the absolute socket path
* @param error_r location to store the error occuring, or NULL to
* ignore errors
* @return true on success
*/
bool
server_socket_add_path(struct server_socket *ss, const char *path,
GError **error_r);
#endif

@@ -25,6 +25,7 @@
#include "log.h"
#include "main.h"
#include "event_pipe.h"
#include "mpd_error.h"
#include <glib.h>
@@ -46,7 +47,7 @@ static void
x_sigaction(int signum, const struct sigaction *act)
{
if (sigaction(signum, act, NULL) < 0)
g_error("sigaction() failed: %s", strerror(errno));
MPD_ERROR("sigaction() failed: %s", strerror(errno));
}
static void

@@ -23,6 +23,7 @@
#include "tag_pool.h"
#include "conf.h"
#include "song.h"
#include "mpd_error.h"
#include <glib.h>
#include <assert.h>
@@ -138,8 +139,8 @@ void tag_lib_init(void)
type = tag_name_parse_i(c);
if (type == TAG_NUM_OF_ITEM_TYPES)
g_error("error parsing metadata item \"%s\"",
c);
MPD_ERROR("error parsing metadata item \"%s\"",
c);
ignore_tag_items[type] = false;

@@ -21,11 +21,7 @@
#include "tag_ape.h"
#include "tag.h"
#include "tag_table.h"
#include <glib.h>
#include <assert.h>
#include <stdio.h>
#include "ape.h"
static const char *const ape_tag_names[TAG_NUM_OF_ITEM_TYPES] = {
[TAG_ALBUM_ARTIST] = "album artist",
@@ -56,101 +52,45 @@ tag_ape_import_item(struct tag *tag, unsigned long flags,
if (tag == NULL)
tag = tag_new();
tag_add_item_n(tag, type, value, value_length);
const char *end = value + value_length;
while (true) {
/* multiple values are separated by null bytes */
const char *n = memchr(value, 0, end - value);
if (n != NULL) {
if (n > value)
tag_add_item_n(tag, type, value, n - value);
value = n + 1;
} else {
if (end > value)
tag_add_item_n(tag, type, value, end - value);
break;
}
}
return tag;
}
struct tag_ape_ctx {
struct tag *tag;
};
static bool
tag_ape_callback(unsigned long flags, const char *key,
const char *value, size_t value_length, void *_ctx)
{
struct tag_ape_ctx *ctx = _ctx;
ctx->tag = tag_ape_import_item(ctx->tag, flags, key,
value, value_length);
return true;
}
struct tag *
tag_ape_load(const char *file)
{
struct tag *ret = NULL;
FILE *fp;
int tagCount;
char *buffer = NULL;
char *p;
size_t tagLen;
size_t size;
unsigned long flags;
char *key;
struct tag_ape_ctx ctx = { .tag = NULL };
struct {
unsigned char id[8];
uint32_t version;
uint32_t length;
uint32_t tagCount;
unsigned char flags[4];
unsigned char reserved[8];
} footer;
fp = fopen(file, "rb");
if (!fp)
return NULL;
/* determine if file has an apeV2 tag */
if (fseek(fp, 0, SEEK_END))
goto fail;
size = (size_t)ftell(fp);
if (fseek(fp, size - sizeof(footer), SEEK_SET))
goto fail;
if (fread(&footer, 1, sizeof(footer), fp) != sizeof(footer))
goto fail;
if (memcmp(footer.id, "APETAGEX", sizeof(footer.id)) != 0)
goto fail;
if (GUINT32_FROM_LE(footer.version) != 2000)
goto fail;
/* find beginning of ape tag */
tagLen = GUINT32_FROM_LE(footer.length);
if (tagLen <= sizeof(footer) + 10)
goto fail;
if (tagLen > 1024 * 1024)
/* refuse to load more than one megabyte of tag data */
goto fail;
if (fseek(fp, size - tagLen, SEEK_SET))
goto fail;
/* read tag into buffer */
tagLen -= sizeof(footer);
assert(tagLen > 10);
buffer = g_malloc(tagLen);
if (fread(buffer, 1, tagLen, fp) != tagLen)
goto fail;
/* read tags */
tagCount = GUINT32_FROM_LE(footer.tagCount);
p = buffer;
while (tagCount-- && tagLen > 10) {
size = GUINT32_FROM_LE(*(const uint32_t *)p);
p += 4;
tagLen -= 4;
flags = GUINT32_FROM_LE(*(const uint32_t *)p);
p += 4;
tagLen -= 4;
/* get the key */
key = p;
while (tagLen > size && *p != '\0') {
p++;
tagLen--;
}
p++;
tagLen--;
/* get the value */
if (tagLen < size)
goto fail;
ret = tag_ape_import_item(ret, flags, key, p, size);
p += size;
tagLen -= size;
}
fail:
if (fp)
fclose(fp);
g_free(buffer);
return ret;
tag_ape_scan(file, tag_ape_callback, &ctx);
return ctx.tag;
}

@@ -126,17 +126,16 @@ import_id3_string(bool is_id3v1, const id3_ucs4_t *ucs4)
* - string list
*/
static void
tag_id3_import_text(struct tag *dest, struct id3_tag *tag, const char *id,
enum tag_type type)
tag_id3_import_text_frame(struct tag *dest, struct id3_tag *tag,
const struct id3_frame *frame,
enum tag_type type)
{
struct id3_frame const *frame;
id3_ucs4_t const *ucs4;
id3_utf8_t *utf8;
union id3_field const *field;
unsigned int nstrings, i;
frame = id3_tag_findframe(tag, id, 0);
if (frame == NULL || frame->nfields != 2)
if (frame->nfields != 2)
return;
/* check the encoding field */
@@ -170,6 +169,20 @@ tag_id3_import_text(struct tag *dest, struct id3_tag *tag, const char *id,
}
}
/**
* Import all text frames with the specified id (ID3v2.4.0 section
* 4.2). This is a wrapper for tag_id3_import_text_frame().
*/
static void
tag_id3_import_text(struct tag *dest, struct id3_tag *tag, const char *id,
enum tag_type type)
{
const struct id3_frame *frame;
for (unsigned i = 0;
(frame = id3_tag_findframe(tag, id, i)) != NULL; ++i)
tag_id3_import_text_frame(dest, tag, frame, type);
}
/**
* Import a "Comment frame" (ID3v2.4.0 section 4.10). It
* contains 4 fields:
@@ -180,16 +193,15 @@ tag_id3_import_text(struct tag *dest, struct id3_tag *tag, const char *id,
* - full string (we use this one)
*/
static void
tag_id3_import_comment(struct tag *dest, struct id3_tag *tag, const char *id,
enum tag_type type)
tag_id3_import_comment_frame(struct tag *dest, struct id3_tag *tag,
const struct id3_frame *frame,
enum tag_type type)
{
struct id3_frame const *frame;
id3_ucs4_t const *ucs4;
id3_utf8_t *utf8;
union id3_field const *field;
frame = id3_tag_findframe(tag, id, 0);
if (frame == NULL || frame->nfields != 4)
if (frame->nfields != 4)
return;
/* for now I only read the 4th field, with the fullstring */
@@ -209,6 +221,20 @@ tag_id3_import_comment(struct tag *dest, struct id3_tag *tag, const char *id,
g_free(utf8);
}
/**
* Import all comment frames (ID3v2.4.0 section 4.10). This is a
* wrapper for tag_id3_import_comment_frame().
*/
static void
tag_id3_import_comment(struct tag *dest, struct id3_tag *tag, const char *id,
enum tag_type type)
{
const struct id3_frame *frame;
for (unsigned i = 0;
(frame = id3_tag_findframe(tag, id, i)) != NULL; ++i)
tag_id3_import_comment_frame(dest, tag, frame, type);
}
/**
* Parse a TXXX name, and convert it to a tag_type enum value.
* Returns TAG_NUM_OF_ITEM_TYPES if the TXXX name is not understood.

@@ -71,6 +71,19 @@ void timer_add(Timer *timer, int size)
timer->time += ((uint64_t)size * 1000000) / timer->rate;
}
unsigned
timer_delay(const Timer *timer)
{
int64_t delay = (int64_t)(timer->time - now()) / 1000;
if (delay < 0)
return 0;
if (delay > G_MAXINT)
delay = G_MAXINT;
return delay / 1000;
}
void timer_sync(Timer *timer)
{
int64_t sleep_duration;

@@ -40,6 +40,12 @@ void timer_reset(Timer *timer);
void timer_add(Timer *timer, int size);
/**
* Returns the number of milliseconds to sleep to get back to sync.
*/
unsigned
timer_delay(const Timer *timer);
void timer_sync(Timer *timer);
#endif

@@ -28,6 +28,7 @@
#include "idle.h"
#include "stats.h"
#include "main.h"
#include "mpd_error.h"
#include <glib.h>
@@ -93,7 +94,7 @@ spawn_update_task(const char *path)
update_thr = g_thread_create(update_task, g_strdup(path), TRUE, &e);
if (update_thr == NULL)
g_error("Failed to spawn update task: %s", e->message);
MPD_ERROR("Failed to spawn update task: %s", e->message);
if (++update_task_id > update_task_id_max)
update_task_id = 1;

@@ -546,6 +546,31 @@ update_container_file( struct directory* directory,
return true;
}
/**
* Checks if the given permissions on the mapped file are given.
*/
static bool
directory_child_access(const struct directory *directory,
const char *name, int mode)
{
#ifdef WIN32
/* access() is useless on WIN32 */
(void)directory;
(void)name;
return true;
#else
char *path = map_directory_child_fs(directory, name);
if (path == NULL)
/* something went wrong, but that isn't a permission
problem */
return true;
bool success = access(path, mode) == 0 || errno != EACCES;
g_free(path);
return success;
#endif
}
static void
update_regular_file(struct directory *directory,
const char *name, const struct stat *st)
@@ -562,6 +587,14 @@ update_regular_file(struct directory *directory,
{
struct song* song = songvec_find(&directory->songs, name);
if (!directory_child_access(directory, name, R_OK)) {
g_warning("no read permissions on %s/%s",
directory_get_path(directory), name);
if (song != NULL)
delete_song(directory, song);
return;
}
if (!(song != NULL && st->st_mtime == song->mtime &&
!walk_discard) &&
plugin->container_scan != NULL)
@@ -604,7 +637,9 @@ update_regular_file(struct directory *directory,
#endif
} else if (playlist_suffix_supported(suffix)) {
playlist_vector_add(&directory->playlists, name, st->st_mtime);
if (playlist_vector_update_or_add(&directory->playlists, name,
st->st_mtime))
modified = true;
}
}

@@ -20,6 +20,7 @@
#include "config.h"
#include "zeroconf-internal.h"
#include "listen.h"
#include "mpd_error.h"
#include <glib.h>
@@ -218,7 +219,7 @@ void init_avahi(const char *serviceName)
g_debug("Initializing interface");
if (!avahi_is_valid_service_name(serviceName))
g_error("Invalid zeroconf_name \"%s\"", serviceName);
MPD_ERROR("Invalid zeroconf_name \"%s\"", serviceName);
avahiName = avahi_strdup(serviceName);

@@ -63,7 +63,7 @@ void init_zeroconf_osx(const char *serviceName)
DNSServiceErrorType error = DNSServiceRegister(&dnsReference,
0, 0, serviceName,
SERVICE_TYPE, NULL, NULL,
htons(listen_port), 0,
g_htons(listen_port), 0,
NULL,
dnsRegisterCallback,
NULL);