Compare commits

...

102 Commits

Author SHA1 Message Date
Avuton Olrich
4d4b7e3de0 mpd version 0.15.16 2011-03-13 20:27:33 -07:00
Max Kellermann
1674a4ec82 output/jack: fix crash with mono playback
With mono sound, jack_sample_size is smaller than frame_size (4 vs 2
bytes), and "space/jack_sample_size==0".  That means mpd_jack_play()
will return 0, although no error has occurred.
2011-02-27 23:26:50 +01:00
Max Kellermann
ce370bee60 output/jack: rename variable sample_size to jack_sample_size 2011-02-25 10:46:44 +01:00
Christopher Brannon
2a1f4539f6 Insure proper initialization of stack-allocated struct.
Version 1.0.0 of the libao library added a new field to the
ao_sample_format struct.  It is a char * named matrix.  When
an ao_sample_format is allocated on the stack, this field contains
garbage.  The proper course is to insure that is initialized to NULL.
NULL indicates that we do not want any mapping.
The struct is now initialized using a static initializer, and this
technique is compatible with all known versions of libao.
2011-02-15 12:16:25 +01:00
Max Kellermann
03018611f8 update: log all file permission problems 2011-01-31 09:39:24 +01:00
Andreas Wiese
e6c3acaa6f Fix NDEBUG test
<stdbool.h> needs to be included unconditionally from definition of
NDEBUG, since »bool« doesn't get defined otherwise.

Signed-off-by: Andreas Wiese <aw-devel@meterriblecrew.net>
2011-01-14 16:22:25 +01:00
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
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
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
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
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
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
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
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
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
Thomas Jansen
e3f4c7b91c input/rewind: enable for MMS 2010-09-28 12:56:47 +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
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
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
Avuton Olrich
676739c426 Modify version string to post-release version 0.15.13~git 2010-07-21 06:40:33 +02:00
Max Kellermann
0fec8e0864 mpd version 0.15.12 2010-07-20 22:57:57 +02:00
Max Kellermann
1f976d6e54 input/curl: remove assertion after curl_multi_fdset()
Some users reported that MPD crashes when using a new CURL version
with the threaded DNS resolver enabled.  It seems that
curl_multi_fdset() returns no file descriptor when the DNS resolver
runs in another thread, so MPD does not have any event to wait for.

On the CURL mailing list, somebody suggested to sleep for a fixed
amount of time.  This is not an elegant solution, because daemons
should never have to sleep without waiting for an event.  I hope the
CURL developers will review the API and remove the threaded DNS
resolver.

Meanwhile, I'm removing the assertion in question, to allow those
unfortunate users running the latest CURL version to continue using
MPD.
2010-07-20 22:55:13 +02:00
Max Kellermann
a4908dca42 input/curl: query timeout from CURL
Use curl_multi_timeout() to determine the select() timeout, instead of
hard-coding one second.
2010-07-20 22:37:34 +02:00
Max Kellermann
8b055c3127 tag_rva2: set "gain", not "peak"
RVA2 tags only store the "gain" value, there is no "peak" attribute.
2010-07-20 22:32:55 +02:00
Max Kellermann
172182b18f decoder/mad: parse_rva2() returns bool 2010-07-20 18:27:11 +02:00
Max Kellermann
898a13f196 decoder/wildmidi: support version 0.2.3
In libwildmidi 0.2.3, the function WildMidi_SampledSeek() was removed,
without changing the SO name.  This patch adds an autoconf check for
that function.  Fall back to WildMidi_FastSeek() if
WildMidi_SampledSeek() is not available anymore.
2010-07-20 18:23:56 +02:00
Avuton Olrich
b97e92468f Modify version string to post-release version 0.15.12~git 2010-07-14 17:33:29 -07:00
Avuton Olrich
56bf4ede18 mpd version 0.15.11 2010-07-14 17:33:28 -07:00
Max Kellermann
49bc317fb8 decoder/ffmpeg: fix libavformat 0.6 by using av_open_input_stream()
libavformat 0.6 does not pass the original URI pointer to the "open"
method, which leads to a crash because MPD was using a dirty hack to
pass a pointer to that method.

This patch switches to av_open_input_stream() with a custom
ByteIOContext class, instead of doing the URI string hack with
av_open_input_file().

Loosely based on a patch from Jasper St. Pierre.
2010-06-30 23:41:32 +02:00
Max Kellermann
375a09d6f6 decoder/ffmpeg: manual format probing
Use the libavformat function av_probe_input_format() to probe the
AVInputFormat, instead of letting av_open_input_file() do it
implicitly.  We will switch to av_open_input_stream() very soon, which
does not have the probing code.

Loosely based on a patch from Jasper St. Pierre.
2010-06-30 23:40:31 +02:00
Max Kellermann
0265c34bed decoder/ffmpeg: free URI, fix memory leak
Free the string allocated by decoder_get_uri().
2010-06-30 23:40:04 +02:00
Max Kellermann
a1882f48be decoder/vorbis: handle uri==NULL
This fixes a theoretical crash, which has never occurred in practice.
2010-06-30 23:38:49 +02:00
Anton Khirnov
c3569814bd ffmpeg: read more metadata. 2010-06-30 23:18:45 +02:00
Max Kellermann
814daac5ba decoder/ffmpeg: free AVFormatContext on error
Fix a memory leak in some code paths.
2010-06-30 23:14:43 +02:00
Max Kellermann
0d03bdce6d configure.ac: check ffmpeg version number with pkg-config
Replace the check for avcodec_decode_audio2(), assume it's present in
libavcodec version 51.
2010-06-30 21:56:04 +02:00
Max Kellermann
768be22f7c pcm_buffer: make the buffer pointer "void" 2010-06-30 21:55:46 +02:00
Max Kellermann
ec89ce5a8a decoder/mp4ff: support tag "album artist"
We already supported "albumartist", but it seems some folks also use
"album artist" (with a space).
2010-06-30 21:55:03 +02:00
Andreas Vögele
34415bf0b6 Make get_remote_uid() work on BSD
I've attached a patch that will make file URIs work on operating systems
that provide the getpeereid() function call to check the user ID of the
peer connected to a UNIX domain socket.
2010-06-30 21:42:01 +02:00
Max Kellermann
0a0c78674f playlist: emit IDLE_OPTIONS when resetting single mode 2010-06-30 21:40:33 +02:00
Max Kellermann
1bffdabe41 directory_print: return void
There is no useful return value here.
2010-06-30 21:39:34 +02:00
Max Kellermann
77e6810c14 decoder/mikmod: fix memory leak
The return value of Player_LoadTitle() is allocated with malloc(), and
must be freed by the caller.
2010-06-30 19:37:36 +00:00
Max Kellermann
5ebe33653c decoder/mp4ff: remove duplicate entries in the tag name table
Reuse the function tag_name_parse_i().
2010-06-30 21:36:15 +02:00
Max Kellermann
8e3eace289 decoder/mp4ff: moved code to mp4ff_tag_name_parse() 2010-06-30 21:36:00 +02:00
Max Kellermann
284659034d tag_ape: remove duplicate entries in the tag name table
Reuse the function tag_name_parse_i().
2010-06-30 21:33:19 +02:00
Max Kellermann
9550c87327 tag: added function tag_name_parse()
Convert a string into a tag_type enum.
2010-06-30 21:31:45 +02:00
Max Kellermann
e223e8a5b5 tag_ape: move code to tag_ape_name_parse() 2010-06-30 21:30:21 +02:00
Max Kellermann
4d6d372a5b decoder/vorbis: use single global ov_callbacks constant
Initialize the ov_callbacks struct at compile time.
2010-06-30 19:24:41 +00:00
Max Kellermann
0aeec90590 decoder/mp4ff: support tags "albumartist", "band"
I'm not sure if mapping "band" to TAG_PERFORMER is correct, but it
might be better than nothing.
2010-06-30 21:22:13 +02:00
Max Kellermann
cfcd84655c decoder/mp4ff: use tag_table.h to parse tag names
Convert if/else/else/... to a loop.
2010-06-30 21:19:30 +02:00
Max Kellermann
5092eaf1cc tag_ape: move table lookup to tag_table.h
Allow code sharing.
2010-06-30 21:18:27 +02:00
Max Kellermann
9328558fc7 tag_ape: support album artist
I took this tag name from a MusePack sample file I got from a user.
It is not documented in the APE specification:

 http://wiki.hydrogenaudio.org/index.php?title=APE_key

People seem to be using undocumented extensions to the specification
anyway, and the best we can do is attempt to support them.
2010-06-30 21:16:28 +02:00
Max Kellermann
026bd15872 tag_ape: simplified the apeItems array
Make "enum tag_type" the array index, and convert apeItems to a sparse
array.
2010-06-30 21:15:52 +02:00
Max Kellermann
7cca55549b tag_ape: moved code to tag_ape_import_item()
Improve code readability.
2010-06-30 21:15:39 +02:00
Max Kellermann
c7e89ea3a3 tag_ape: converted apeItems and tagItems to global vars
Don't initialize those arrays each time tag_ape_load() is called.
2010-06-30 21:15:14 +02:00
Avuton Olrich
65ad298460 Modify version string to post-release version 0.15.11~git 2010-05-30 08:59:00 -07:00
Avuton Olrich
57e95ea6f4 mpd version 0.15.10 2010-05-30 08:59:00 -07:00
Max Kellermann
442d2e74e3 decoder/mad: fix buffer variable name on !HAVE_ID3TAG 2010-05-30 17:27:03 +02:00
Max Kellermann
28736414a8 input/mms: initialize the "eof" attribute 2010-05-18 21:11:00 +02:00
Max Kellermann
e98bd55cbf input/mms: fix memory leak in error handler 2010-05-18 20:57:57 +02:00
Max Kellermann
a1a03deed2 decoder/mad: properly calculate ID3 size without libid3tag
Without libid3tag, we were trying to skip the ID3 frame (since
0.15.2).  Its length however was not calculated at all, we were just
dropping everything from the current input buffer.  This lead to the
first few seconds of the file being skipped.  This patch attempts to
calculate the ID3v2 frame size with the formula from:

 http://www.id3.org/id3v2.4.0-structure 3.1 and 6.2
2010-04-13 08:51:29 +02:00
Avuton Olrich
0dcd865c2e Modify version string to post-release version 0.15.10~git 2010-03-21 17:25:18 -07:00
Avuton Olrich
d612e5e0ab mpd version 0.15.9 2010-03-21 17:25:18 -07:00
Aleksei Kaveshnikov
73ba4ea3da decoder/mpcdec: fix replay gain formula with v8
"When playing musepack files with mpd v0.15.8, rg seems to have no effect.

Using sample file below, mpd says 'computing ReplayGain album scale with gain 122.879997, peak 0.549150'.

One thing though, if I build mpd against old libmpcdec-1.2.6, rg works
as expected: 'computing ReplayGain album scale with gain 16.820000,
peak 0.099765'"
2010-03-19 10:26:08 +01:00
Max Kellermann
cbfaa4a266 player_thread: postpone song tags during cross-fade
Previously, tags of the new song being cross-faded in were sent
immediately.  That can cause wrong information being displayed,
because the "previous" song might send its tag at the end again,
overriding the "next" song's tag.  This patch saves & merges the tag
of the next song, and sends it when cross-fading is finished, and the
next song really starts.
2010-03-17 23:14:54 +01:00
Max Kellermann
2e72a9b262 tag: added function tag_merge_replace()
Like tag_merge(), but can deal with NULL parameters, and frees both
tag objects.
2010-03-17 23:12:21 +01:00
Piotr Gozdur
96033e4b4e decoder/mpcdec: fix negative shift on fixed-point samples
"There is a bug in fixed-point musepack (musepack_src_r435) playback.
In floating-point audio is OK but in fixed audio is distorted.  I have
made a patch for this"
2010-03-17 17:54:21 +01:00
Max Kellermann
9134169e37 playlist: fix single+repeat in random mode
With single+repeat enabled, it is expected that MPD repeats the
current song over andd over.  With random mode also enabled, this
didn't work, because the song order was shuffled internally.  This
patch adds a special check for this case.
2010-03-07 18:58:44 +01:00
Max Kellermann
35c5a371ea decoder/mad: fix crash when seeking at end of song
Removed the decoder_command_finished() call at the end of
mp3_decode().  This is invalid, because decoder_command_finished() has
already been called in mp3_read().
2010-02-27 18:35:31 +01:00
Avuton Olrich
728c66e7e3 Modify version string to post-release version 0.15.9~git 2010-01-18 07:41:15 +01:00
Avuton Olrich
760569fc66 mpd version 0.15.8 2010-01-18 07:41:08 +01:00
Max Kellermann
2579a2f924 decoder/ffmpeg: added more MIME types
Taken from the ffmpeg sources.
2010-01-17 16:00:14 +01:00
Max Kellermann
a7664b98ba Makefile.am: link test/run_decoder and test/read_tags with timer.c
Needed for the fluidsynth decoder plugin.
2010-01-17 11:21:35 +01:00
Max Kellermann
1b441837f1 decoder/ffmpeg: append file name suffix to virtual stream URL
To allow libavformat to detect the format of the input file, append
the suffix of the input file to the URL of the virtual stream.  This
specifically enables the "shorten" codec, which is supported by
libavformat/raw.c, detected only by the suffix.
2010-01-17 02:36:07 +01:00
Max Kellermann
e43bf52cbb configure.ac: disable -Wdeclaration-after-statement
Allow declaration after statement.
2010-01-17 02:36:03 +01:00
Max Kellermann
8ba08edd0e queue: don't repeat current song in consume mode
Check consume mode in queue_next_order(), because the current song
would be deleted as soon as it's finished; it cannot be played again.
2010-01-16 20:58:24 +01:00
Max Kellermann
90d16af66a decoder_thread: fix CUE track playback
The patch "input/file: don't fall back to parent directory" introduced
a regression: when trying to play a CUE track, decoder_run_song()
tries to open the file as a stream first, but this fails, because the
path is virtual.

This patch fixes decoder_run_song() (instead of reverting the previous
patch) to accept input_stream_open() failures if the song is a local
file.  It passes the responsibility to handle non-existing files to
the decoder's file_decode() method.
2010-01-16 19:20:11 +01:00
Max Kellermann
777bd7c1e1 NEWS: added missing entry 2010-01-16 18:41:34 +01:00
Max Kellermann
959f94b06c dbUtils: return empty tag value only if no value was found
This fixes a regression in the patch "return multiple tag values per
song": even when the song has values for the specified tag type, the
empty string gets added to the set, because the "return" was removed.
This patch adds a flag which remembers whether at least one value was
found.
2010-01-02 19:24:31 +01:00
Max Kellermann
4419e5b90d input/curl: removed the built-in rewinding code
This has been reimplemented in the "rewind" input plugin.
2009-12-30 22:52:24 +01:00
Max Kellermann
c88f95a2ea input/rewind: new input_stream wrapper to allow stream rewinding
This replaces the rewinding buffer code from the CURL input plugin.
It is more generic, and allows rewinding even when the server sends
Icy-Metadata (which would have been too difficult to implement within
the CURL plugin).

This is a rather complex patch for the stable branch (v0.15.x), but it
fixes a serious problem: the "vorbis" decoder plugin was unable to
play streams with Icy-Metadata, because it couldn't rewind the stream
after detecting the codec (Vorbis vs. FLAC).
2009-12-29 23:55:40 +01:00
Max Kellermann
c7d099c757 decoder/{ffmpeg,flac,vorbis}: added more flac/vorbis MIME types
Support deprecated MIME types such as "audio/x-ogg".  Support new
types such as "audio/flac".
2009-12-29 22:33:46 +01:00
Avuton Olrich
d38c09051c Modify version string to post-release version 0.15.8~git 2009-12-27 08:31:47 -08:00
55 changed files with 1108 additions and 443 deletions

@@ -75,6 +75,7 @@ mpd_headers = \
src/input_stream.h \
src/input/file_input_plugin.h \
src/input/curl_input_plugin.h \
src/input/rewind_input_plugin.h \
src/input/lastfm_input_plugin.h \
src/input/mms_input_plugin.h \
src/icy_server.h \
@@ -141,6 +142,7 @@ mpd_headers = \
src/tag.h \
src/tag_internal.h \
src/tag_pool.h \
src/tag_table.h \
src/tag_ape.h \
src/tag_id3.h \
src/tag_print.h \
@@ -467,7 +469,9 @@ INPUT_SRC = \
src/input/file_input_plugin.c
if HAVE_CURL
INPUT_SRC += src/input/curl_input_plugin.c src/icy_metadata.c
INPUT_SRC += src/input/curl_input_plugin.c \
src/input/rewind_input_plugin.c \
src/icy_metadata.c
endif
if ENABLE_LASTFM
@@ -637,6 +641,7 @@ test_run_decoder_SOURCES = test/run_decoder.c \
src/tag.c src/tag_pool.c \
src/replay_gain.c \
src/uri.c \
src/timer.c \
$(ARCHIVE_SRC) \
$(INPUT_SRC) \
$(TAG_SRC) \
@@ -656,6 +661,7 @@ test_read_tags_SOURCES = test/read_tags.c \
src/tag.c src/tag_pool.c \
src/replay_gain.c \
src/uri.c \
src/timer.c \
$(ARCHIVE_SRC) \
$(INPUT_SRC) \
$(TAG_SRC) \

87
NEWS

@@ -1,3 +1,90 @@
ver 0.15.16 (2011/03/13)
* output:
- ao: initialize the ao_sample_format struct
- jack: fix crash with mono playback
* encoders:
- lame: explicitly configure the output sample rate
* update: log all file permission problems
ver 0.15.15 (2010/11/08)
* input:
- rewind: fix assertion failure
* output:
- shout: artist comes first in stream title
ver 0.15.14 (2010/11/06)
* player_thread: fix assertion failure due to wrong music pipe on seek
* output_thread: fix assertion failure due to race condition in OPEN
* input:
- rewind: fix double free bug
* decoders:
- mp4ff, ffmpeg: add extension ".m4b" (audio book)
ver 0.15.13 (2010/10/10)
* output_thread: fix race condition after CANCEL command
* output:
- httpd: fix random data in stream title
- httpd: MIME type audio/ogg for Ogg Vorbis
* input:
- rewind: update MIME not only once
- rewind: enable for MMS
ver 0.15.12 (2010/07/20)
* input:
- curl: remove assertion after curl_multi_fdset()
* tags:
- rva2: set "gain", not "peak"
* decoders:
- wildmidi: support version 0.2.3
ver 0.15.11 (2010/06/14)
* tags:
- ape: support album artist
* decoders:
- mp4ff: support tags "album artist", "albumartist", "band"
- mikmod: fix memory leak
- vorbis: handle uri==NULL
- ffmpeg: fix memory leak
- ffmpeg: free AVFormatContext on error
- ffmpeg: read more metadata
- ffmpeg: fix libavformat 0.6 by using av_open_input_stream()
* playlist: emit IDLE_OPTIONS when resetting single mode
* listen: make get_remote_uid() work on BSD
ver 0.15.10 (2010/05/30)
* input:
- mms: fix memory leak in error handler
- mms: initialize the "eof" attribute
* decoders:
- mad: properly calculate ID3 size without libid3tag
ver 0.15.9 (2010/03/21)
* decoders:
- mad: fix crash when seeking at end of song
- mpcdec: fix negative shift on fixed-point samples
- mpcdec: fix replay gain formula with v8
* playlist: fix single+repeat in random mode
* player: postpone song tags during cross-fade
ver 0.15.8 (2010/01/17)
* input:
- curl: allow rewinding with Icy-Metadata
* decoders:
- ffmpeg, flac, vorbis: added more flac/vorbis MIME types
- ffmpeg: enabled libavformat's file name extension detection
* dbUtils: return empty tag value only if no value was found
* decoder_thread: fix CUE track playback
* queue: don't repeat current song in consume mode
ver 0.15.7 (2009/12/27)
* archive:
- close archive when stream is closed

@@ -1,5 +1,5 @@
AC_PREREQ(2.60)
AC_INIT(mpd, 0.15.7, musicpd-dev-team@lists.sourceforge.net)
AC_INIT(mpd, 0.15.16, musicpd-dev-team@lists.sourceforge.net)
AC_CONFIG_SRCDIR([src/main.c])
AM_INIT_AUTOMAKE([foreign 1.9 dist-bzip2])
AM_CONFIG_HEADER(config.h)
@@ -177,6 +177,7 @@ AC_ARG_ENABLE(un,
if test x$enable_un = xyes; then
AC_DEFINE(HAVE_UN, 1, [Define if unix domain socket support is enabled])
STRUCT_UCRED
AC_CHECK_FUNCS(getpeereid)
fi
@@ -913,17 +914,9 @@ fi
AM_CONDITIONAL(HAVE_AUDIOFILE, test x$enable_audiofile = xyes)
MPD_AUTO_PKG(ffmpeg, FFMPEG, [libavformat libavcodec libavutil],
MPD_AUTO_PKG(ffmpeg, FFMPEG, [libavformat >= 52 libavcodec >= 51 libavutil >= 49],
[ffmpeg decoder library], [libavformat+libavcodec+libavutil not found])
if test x$enable_ffmpeg = xyes; then
old_LIBS=$LIBS
LIBS="$LIBS $FFMPEG_LIBS"
AC_CHECK_LIB(avcodec, avcodec_decode_audio2,,
enable_ffmpeg=no)
LIBS=$old_LIBS
fi
if test x$enable_ffmpeg = xyes; then
# prior to ffmpeg svn12865, you had to specify include files
# without path prefix
@@ -957,6 +950,10 @@ if test x$enable_wildmidi = xyes; then
AC_CHECK_LIB(WildMidi, WildMidi_Init,,
AC_MSG_ERROR([libwildmidi not found]))
AC_CHECK_LIB(WildMidi, WildMidi_SampledSeek,
[AC_DEFINE(HAVE_WILDMIDI_SAMPLED_SEEK, 1,
[Defined if WildMidi_SampledSeek() is available (libwildmidi <= 0.2.2)])])
CFLAGS=$oldcflags
LIBS=$oldlibs
CPPFLAGS=$oldcppflags
@@ -1139,7 +1136,6 @@ then
MPD_CHECK_FLAG([-Wextra])
MPD_CHECK_FLAG([-Wno-deprecated-declarations])
MPD_CHECK_FLAG([-Wmissing-prototypes])
MPD_CHECK_FLAG([-Wdeclaration-after-statement])
MPD_CHECK_FLAG([-Wshadow])
MPD_CHECK_FLAG([-Wpointer-arith])
MPD_CHECK_FLAG([-Wstrict-prototypes])

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

@@ -173,7 +173,6 @@ cue_tag_file( FILE* fp,
{
struct tag* cd_tag = NULL;
struct tag* track_tag = NULL;
struct tag* merge_tag = NULL;
struct Cd* cd = NULL;
if (tnum > 256)
@@ -199,26 +198,7 @@ cue_tag_file( FILE* fp,
cd_delete(cd);
}
if ((cd_tag != NULL) && (track_tag != NULL))
{
merge_tag = tag_merge(cd_tag, track_tag);
tag_free(cd_tag);
tag_free(track_tag);
return merge_tag;
}
else if (cd_tag != NULL)
{
return cd_tag;
}
else if (track_tag != NULL)
{
return track_tag;
}
else
return NULL;
return tag_merge_replace(cd_tag, track_tag);
}
struct tag*

@@ -234,6 +234,7 @@ visitTag(struct client *client, struct strset *set,
struct song *song, enum tag_type tagType)
{
struct tag *tag = song->tag;
bool found = false;
if (tagType == LOCATE_TAG_FILE_TYPE) {
song_print_url(client, song);
@@ -246,10 +247,12 @@ visitTag(struct client *client, struct strset *set,
for (unsigned i = 0; i < tag->num_items; i++) {
if (tag->items[i]->type == tagType) {
strset_add(set, tag->items[i]->value);
found = true;
}
}
strset_add(set, "");
if (!found)
strset_add(set, "");
}
struct list_tags_data {

@@ -53,48 +53,27 @@ struct ffmpeg_context {
struct tag *tag;
};
struct ffmpeg_stream {
/** hack - see url_to_struct() */
char url[8];
struct mpd_ffmpeg_stream {
struct decoder *decoder;
struct input_stream *input;
ByteIOContext *io;
unsigned char buffer[8192];
};
/**
* Convert a faked mpd:// URL to a ffmpeg_stream structure. This is a
* hack because ffmpeg does not provide a nice API for passing a
* user-defined pointer to mpdurl_open().
*/
static struct ffmpeg_stream *url_to_struct(const char *url)
static int
mpd_ffmpeg_stream_read(void *opaque, uint8_t *buf, int size)
{
union {
const char *in;
struct ffmpeg_stream *out;
} u = { .in = url };
return u.out;
}
static int mpd_ffmpeg_open(URLContext *h, const char *filename,
G_GNUC_UNUSED int flags)
{
struct ffmpeg_stream *stream = url_to_struct(filename);
h->priv_data = stream;
h->is_streamed = stream->input->seekable ? 0 : 1;
return 0;
}
static int mpd_ffmpeg_read(URLContext *h, unsigned char *buf, int size)
{
struct ffmpeg_stream *stream = (struct ffmpeg_stream *) h->priv_data;
struct mpd_ffmpeg_stream *stream = opaque;
return decoder_read(stream->decoder, stream->input,
(void *)buf, size);
}
static int64_t mpd_ffmpeg_seek(URLContext *h, int64_t pos, int whence)
static int64_t
mpd_ffmpeg_stream_seek(void *opaque, int64_t pos, int whence)
{
struct ffmpeg_stream *stream = (struct ffmpeg_stream *) h->priv_data;
struct mpd_ffmpeg_stream *stream = opaque;
bool ret;
if (whence == AVSEEK_SIZE)
@@ -107,25 +86,36 @@ static int64_t mpd_ffmpeg_seek(URLContext *h, int64_t pos, int whence)
return stream->input->offset;
}
static int mpd_ffmpeg_close(URLContext *h)
static struct mpd_ffmpeg_stream *
mpd_ffmpeg_stream_open(struct decoder *decoder, struct input_stream *input)
{
h->priv_data = NULL;
return 0;
struct mpd_ffmpeg_stream *stream = g_new(struct mpd_ffmpeg_stream, 1);
stream->decoder = decoder;
stream->input = input;
stream->io = av_alloc_put_byte(stream->buffer, sizeof(stream->buffer),
false, stream,
mpd_ffmpeg_stream_read, NULL,
input->seekable
? mpd_ffmpeg_stream_seek : NULL);
if (stream->io == NULL) {
g_free(stream);
return NULL;
}
return stream;
}
static URLProtocol mpd_ffmpeg_fileops = {
.name = "mpd",
.url_open = mpd_ffmpeg_open,
.url_read = mpd_ffmpeg_read,
.url_seek = mpd_ffmpeg_seek,
.url_close = mpd_ffmpeg_close,
};
static void
mpd_ffmpeg_stream_close(struct mpd_ffmpeg_stream *stream)
{
av_free(stream->io);
g_free(stream);
}
static bool
ffmpeg_init(G_GNUC_UNUSED const struct config_param *param)
{
av_register_all();
register_protocol(&mpd_ffmpeg_fileops);
return true;
}
@@ -140,41 +130,86 @@ ffmpeg_find_audio_stream(const AVFormatContext *format_context)
return -1;
}
static AVInputFormat *
ffmpeg_probe(struct decoder *decoder, struct input_stream *is,
const char *uri)
{
enum {
BUFFER_SIZE = 16384,
PADDING = 16,
};
unsigned char *buffer = g_malloc(BUFFER_SIZE);
size_t nbytes = decoder_read(decoder, is, buffer, BUFFER_SIZE);
if (nbytes <= PADDING || !input_stream_seek(is, 0, SEEK_SET)) {
g_free(buffer);
return NULL;
}
/* some ffmpeg parsers (e.g. ac3_parser.c) read a few bytes
beyond the declared buffer limit, which makes valgrind
angry; this workaround removes some padding from the buffer
size */
nbytes -= PADDING;
AVProbeData avpd = {
.buf = buffer,
.buf_size = nbytes,
.filename = uri,
};
AVInputFormat *format = av_probe_input_format(&avpd, true);
g_free(buffer);
return format;
}
static bool
ffmpeg_helper(struct input_stream *input,
ffmpeg_helper(const char *uri,
struct decoder *decoder, struct input_stream *input,
bool (*callback)(struct ffmpeg_context *ctx),
struct ffmpeg_context *ctx)
{
AVInputFormat *input_format = ffmpeg_probe(decoder, input, uri);
if (input_format == NULL)
return false;
g_debug("detected input format '%s' (%s)",
input_format->name, input_format->long_name);
struct mpd_ffmpeg_stream *stream =
mpd_ffmpeg_stream_open(decoder, input);
if (stream == NULL) {
g_warning("Failed to open stream");
return false;
}
AVFormatContext *format_context;
AVCodecContext *codec_context;
AVCodec *codec;
int audio_stream;
struct ffmpeg_stream stream = {
.url = "mpd://X", /* only the mpd:// prefix matters */
};
bool ret;
stream.input = input;
if (ctx && ctx->decoder) {
stream.decoder = ctx->decoder; //are we in decoding loop ?
} else {
stream.decoder = NULL;
}
//ffmpeg works with ours "fileops" helper
if (av_open_input_file(&format_context, stream.url, NULL, 0, NULL) != 0) {
if (av_open_input_stream(&format_context, stream->io, uri,
input_format, NULL) != 0) {
g_warning("Open failed\n");
mpd_ffmpeg_stream_close(stream);
return false;
}
if (av_find_stream_info(format_context)<0) {
g_warning("Couldn't find stream info\n");
av_close_input_stream(format_context);
mpd_ffmpeg_stream_close(stream);
return false;
}
audio_stream = ffmpeg_find_audio_stream(format_context);
if (audio_stream == -1) {
g_warning("No audio stream inside\n");
av_close_input_stream(format_context);
mpd_ffmpeg_stream_close(stream);
return false;
}
@@ -186,11 +221,15 @@ ffmpeg_helper(struct input_stream *input,
if (!codec) {
g_warning("Unsupported audio codec\n");
av_close_input_stream(format_context);
mpd_ffmpeg_stream_close(stream);
return false;
}
if (avcodec_open(codec_context, codec)<0) {
g_warning("Could not open codec\n");
av_close_input_stream(format_context);
mpd_ffmpeg_stream_close(stream);
return false;
}
@@ -204,7 +243,8 @@ ffmpeg_helper(struct input_stream *input,
ret = true;
avcodec_close(codec_context);
av_close_input_file(format_context);
av_close_input_stream(format_context);
mpd_ffmpeg_stream_close(stream);
return ret;
}
@@ -349,7 +389,10 @@ ffmpeg_decode(struct decoder *decoder, struct input_stream *input)
ctx.input = input;
ctx.decoder = decoder;
ffmpeg_helper(input, ffmpeg_decode_internal, &ctx);
char *uri = decoder_get_uri(decoder);
ffmpeg_helper(uri, decoder, input,
ffmpeg_decode_internal, &ctx);
g_free(uri);
}
#if LIBAVFORMAT_VERSION_INT >= ((52<<16)+(31<<8)+0)
@@ -377,12 +420,21 @@ static bool ffmpeg_tag_internal(struct ffmpeg_context *ctx)
av_metadata_conv(f, NULL, f->iformat->metadata_conv);
ffmpeg_copy_metadata(tag, f->metadata, TAG_ITEM_TITLE, "title");
#if LIBAVFORMAT_VERSION_INT >= ((52<<16)+(50<<8))
ffmpeg_copy_metadata(tag, f->metadata, TAG_ITEM_ARTIST, "artist");
ffmpeg_copy_metadata(tag, f->metadata, TAG_ITEM_DATE, "date");
#else
ffmpeg_copy_metadata(tag, f->metadata, TAG_ITEM_ARTIST, "author");
ffmpeg_copy_metadata(tag, f->metadata, TAG_ITEM_DATE, "year");
#endif
ffmpeg_copy_metadata(tag, f->metadata, TAG_ITEM_ALBUM, "album");
ffmpeg_copy_metadata(tag, f->metadata, TAG_ITEM_COMMENT, "comment");
ffmpeg_copy_metadata(tag, f->metadata, TAG_ITEM_GENRE, "genre");
ffmpeg_copy_metadata(tag, f->metadata, TAG_ITEM_TRACK, "track");
ffmpeg_copy_metadata(tag, f->metadata, TAG_ITEM_DATE, "year");
ffmpeg_copy_metadata(tag, f->metadata, TAG_ITEM_ALBUM_ARTIST, "album_artist");
ffmpeg_copy_metadata(tag, f->metadata, TAG_ITEM_COMPOSER, "composer");
ffmpeg_copy_metadata(tag, f->metadata, TAG_ITEM_PERFORMER, "performer");
ffmpeg_copy_metadata(tag, f->metadata, TAG_ITEM_DISC, "disc");
#else
if (f->author[0])
tag_add_item(tag, TAG_ITEM_ARTIST, f->author);
@@ -426,7 +478,7 @@ static struct tag *ffmpeg_tag(const char *file)
ctx.decoder = NULL;
ctx.tag = tag_new();
ret = ffmpeg_helper(&input, ffmpeg_tag_internal, &ctx);
ret = ffmpeg_helper(file, NULL, &input, ffmpeg_tag_internal, &ctx);
if (!ret) {
tag_free(ctx.tag);
ctx.tag = NULL;
@@ -449,7 +501,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",
@@ -462,25 +516,31 @@ static const char *const ffmpeg_suffixes[] = {
};
static const char *const ffmpeg_mime_types[] = {
"application/m4a",
"application/mp4",
"application/octet-stream",
"application/ogg",
"application/x-ms-wmz",
"application/x-ms-wmd",
"application/x-ogg",
"application/x-shockwave-flash",
"application/x-shorten",
"audio/8svx",
"audio/16sv",
"audio/aac",
"audio/ac3",
"audio/aiff"
"audio/amr",
"audio/basic",
"audio/flac",
"audio/m4a",
"audio/mp4",
"audio/mpeg",
"audio/musepack",
"audio/ogg",
"audio/qcelp",
"audio/vorbis",
"audio/vorbis+ogg",
"audio/x-8svx",
"audio/x-16sv",
"audio/x-aac",
@@ -493,15 +553,20 @@ static const char *const ffmpeg_mime_types[] = {
"audio/x-flac",
"audio/x-gsm",
"audio/x-mace",
"audio/x-matroska",
"audio/x-monkeys-audio",
"audio/x-mpeg",
"audio/x-ms-wma",
"audio/x-ms-wax",
"audio/x-musepack",
"audio/x-ogg",
"audio/x-vorbis",
"audio/x-vorbis+ogg",
"audio/x-pn-realaudio",
"audio/x-pn-multirate-realaudio",
"audio/x-speex",
"audio/x-tta"
"audio/x-voc",
"audio/x-wav",
"audio/x-wma",
"audio/x-wv",

@@ -871,9 +871,11 @@ oggflac_decode(struct decoder *decoder, struct input_stream *input_stream)
static const char *const oggflac_suffixes[] = { "ogg", "oga", NULL };
static const char *const oggflac_mime_types[] = {
"audio/x-flac+ogg",
"application/ogg",
"application/x-ogg",
"audio/ogg",
"audio/x-flac+ogg",
"audio/x-ogg",
NULL
};
@@ -894,7 +896,11 @@ const struct decoder_plugin oggflac_decoder_plugin = {
static const char *const flac_suffixes[] = { "flac", NULL };
static const char *const flac_mime_types[] = {
"audio/x-flac", "application/x-flac", NULL
"application/flac",
"application/x-flac",
"audio/flac",
"audio/x-flac",
NULL
};
const struct decoder_plugin flac_decoder_plugin = {

@@ -209,14 +209,14 @@ mp3_fill_buffer(struct mp3_data *data)
#ifdef HAVE_ID3TAG
/* Parse mp3 RVA2 frame. Shamelessly stolen from madplay. */
static int parse_rva2(struct id3_tag * tag, struct replay_gain_info * replay_gain_info)
static bool
parse_rva2(struct id3_tag *tag, struct replay_gain_info *replay_gain_info)
{
struct id3_frame const * frame;
id3_latin1_t const *id;
id3_byte_t const *data;
id3_length_t length;
int found;
enum {
CHANNEL_OTHER = 0x00,
@@ -230,18 +230,18 @@ static int parse_rva2(struct id3_tag * tag, struct replay_gain_info * replay_gai
CHANNEL_SUBWOOFER = 0x08
};
found = 0;
/* relative volume adjustment information */
frame = id3_tag_findframe(tag, "RVA2", 0);
if (!frame) return 0;
if (frame == NULL)
return false;
id = id3_field_getlatin1(id3_frame_field(frame, 0));
data = id3_field_getbinarydata(id3_frame_field(frame, 1),
&length);
if (!id || !data) return 0;
if (id == NULL || data == NULL)
return false;
/*
* "The 'identification' string is used to identify the
@@ -277,22 +277,21 @@ static int parse_rva2(struct id3_tag * tag, struct replay_gain_info * replay_gai
voladj_float = (double) voladj_fixed / 512;
replay_gain_info->tuples[REPLAY_GAIN_TRACK].peak = voladj_float;
replay_gain_info->tuples[REPLAY_GAIN_ALBUM].peak = voladj_float;
replay_gain_info->tuples[REPLAY_GAIN_TRACK].gain = voladj_float;
replay_gain_info->tuples[REPLAY_GAIN_ALBUM].gain = voladj_float;
g_debug("parseRVA2: Relative Volume "
"%+.1f dB adjustment (%s)\n",
voladj_float, id);
found = 1;
break;
return true;
}
data += 4 + peak_bytes;
length -= 4 + peak_bytes;
}
return found;
return false;
}
#endif
@@ -425,7 +424,27 @@ static void mp3_parse_id3(struct mp3_data *data, size_t tagsize,
/* This code is enabled when libid3tag is disabled. Instead
of parsing the ID3 frame, it just skips it. */
mad_stream_skip(&data->stream, tagsize);
size_t count = data->stream.bufend - data->stream.this_frame;
if (tagsize <= count) {
mad_stream_skip(&data->stream, tagsize);
} else {
mad_stream_skip(&data->stream, count);
while (count < tagsize) {
size_t len = tagsize - count;
char ignored[1024];
if (len > sizeof(ignored))
len = sizeof(ignored);
len = decoder_read(data->decoder, data->input_stream,
ignored, len);
if (len == 0)
break;
else
count += len;
}
}
#endif
}
@@ -433,16 +452,16 @@ static void mp3_parse_id3(struct mp3_data *data, size_t tagsize,
/**
* This function emulates libid3tag when it is disabled. Instead of
* doing a real analyzation of the frame, it just checks whether the
* frame begins with the string "ID3". If so, it returns the full
* length.
* frame begins with the string "ID3". If so, it returns the length
* of the ID3 frame.
*/
static signed long
id3_tag_query(const void *p0, size_t length)
{
const char *p = p0;
return length > 3 && memcmp(p, "ID3", 3) == 0
? length
return length >= 10 && memcmp(p, "ID3", 3) == 0
? (p[8] << 7) + p[9] + 10
: 0;
}
#endif /* !HAVE_ID3TAG */
@@ -1207,10 +1226,6 @@ mp3_decode(struct decoder *decoder, struct input_stream *input_stream)
if (replay_gain_info)
replay_gain_info_free(replay_gain_info);
if (decoder_get_command(decoder) == DECODE_COMMAND_SEEK &&
data.mute_frame == MUTEFRAME_SEEK)
decoder_command_finished(decoder);
mp3_data_finish(&data);
}

@@ -219,10 +219,12 @@ static struct tag *modTagDup(const char *file)
ret->time = 0;
path2 = g_strdup(file);
title = g_strdup(Player_LoadTitle(path2));
title = Player_LoadTitle(path2);
g_free(path2);
if (title)
if (title) {
tag_add_item(ret, TAG_ITEM_TITLE, title);
free(title);
}
return ret;
}

@@ -19,6 +19,7 @@
#include "../decoder_api.h"
#include "config.h"
#include "tag_table.h"
#include <glib.h>
@@ -339,6 +340,22 @@ mp4_decode(struct decoder *mpd_decoder, struct input_stream *input_stream)
mp4ff_close(mp4fh);
}
static const char *const mp4ff_tag_names[TAG_NUM_OF_ITEM_TYPES] = {
[TAG_ITEM_ALBUM_ARTIST] = "album artist",
[TAG_ITEM_COMPOSER] = "writer",
[TAG_ITEM_PERFORMER] = "band",
};
static enum tag_type
mp4ff_tag_name_parse(const char *name)
{
enum tag_type type = tag_table_lookup(mp4ff_tag_names, name);
if (type == TAG_NUM_OF_ITEM_TYPES)
type = tag_name_parse_i(name);
return type;
}
static struct tag *
mp4_tag_dup(const char *file)
{
@@ -394,24 +411,9 @@ mp4_tag_dup(const char *file)
mp4ff_meta_get_by_index(mp4fh, i, &item, &value);
if (0 == g_ascii_strcasecmp("artist", item)) {
tag_add_item(ret, TAG_ITEM_ARTIST, value);
} else if (0 == g_ascii_strcasecmp("title", item)) {
tag_add_item(ret, TAG_ITEM_TITLE, value);
} else if (0 == g_ascii_strcasecmp("album", item)) {
tag_add_item(ret, TAG_ITEM_ALBUM, value);
} else if (0 == g_ascii_strcasecmp("track", item)) {
tag_add_item(ret, TAG_ITEM_TRACK, value);
} else if (0 == g_ascii_strcasecmp("disc", item)) {
/* Is that the correct id? */
tag_add_item(ret, TAG_ITEM_DISC, value);
} else if (0 == g_ascii_strcasecmp("genre", item)) {
tag_add_item(ret, TAG_ITEM_GENRE, value);
} else if (0 == g_ascii_strcasecmp("date", item)) {
tag_add_item(ret, TAG_ITEM_DATE, value);
} else if (0 == g_ascii_strcasecmp("writer", item)) {
tag_add_item(ret, TAG_ITEM_COMPOSER, value);
}
enum tag_type type = mp4ff_tag_name_parse(item);
if (type != TAG_NUM_OF_ITEM_TYPES)
tag_add_item(ret, type, value);
free(item);
free(value);
@@ -423,7 +425,13 @@ mp4_tag_dup(const char *file)
return ret;
}
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 = {

@@ -24,6 +24,7 @@
#include <mpcdec/mpcdec.h>
#else
#include <mpc/mpcdec.h>
#include <math.h>
#endif
#include <glib.h>
@@ -103,7 +104,7 @@ mpc_to_mpd_sample(MPC_SAMPLE_FORMAT sample)
const int shift = bits - MPC_FIXED_POINT_SCALE_SHIFT;
if (shift < 0)
val = sample << -shift;
val = sample >> -shift;
else
val = sample << shift;
#else
@@ -209,10 +210,17 @@ mpcdec_decode(struct decoder *mpd_decoder, struct input_stream *is)
}
replay_gain_info = replay_gain_info_new();
#ifdef MPC_IS_OLD_API
replay_gain_info->tuples[REPLAY_GAIN_ALBUM].gain = info.gain_album * 0.01;
replay_gain_info->tuples[REPLAY_GAIN_ALBUM].peak = info.peak_album / 32767.0;
replay_gain_info->tuples[REPLAY_GAIN_TRACK].gain = info.gain_title * 0.01;
replay_gain_info->tuples[REPLAY_GAIN_TRACK].peak = info.peak_title / 32767.0;
#else
replay_gain_info->tuples[REPLAY_GAIN_ALBUM].gain = MPC_OLD_GAIN_REF - (info.gain_album / 256.);
replay_gain_info->tuples[REPLAY_GAIN_ALBUM].peak = pow(10, info.peak_album / 256. / 20) / 32767;
replay_gain_info->tuples[REPLAY_GAIN_TRACK].gain = MPC_OLD_GAIN_REF - (info.gain_title / 256.);
replay_gain_info->tuples[REPLAY_GAIN_TRACK].peak = pow(10, info.peak_title / 256. / 20) / 32767;
#endif
decoder_initialized(mpd_decoder, &audio_format,
is->seekable,

@@ -357,9 +357,11 @@ fail:
static const char *const oggflac_suffixes[] = { "ogg", "oga", NULL };
static const char *const oggflac_mime_types[] = {
"audio/x-flac+ogg",
"application/ogg",
"application/x-ogg",
"audio/ogg",
"audio/x-ogg",
"audio/x-flac+ogg",
NULL
};

@@ -97,6 +97,13 @@ static long ogg_tell_cb(void *vdata)
return (long)data->input_stream->offset;
}
static const ov_callbacks vorbis_is_callbacks = {
.read_func = ogg_read_cb,
.seek_func = ogg_seek_cb,
.close_func = ogg_close_cb,
.tell_func = ogg_tell_cb,
};
static const char *
vorbis_comment_value(const char *comment, const char *needle)
{
@@ -226,6 +233,9 @@ oggvorbis_seekable(struct decoder *decoder)
bool seekable;
uri = decoder_get_uri(decoder);
if (uri == NULL)
return false;
/* disable seeking on remote streams, because libvorbis seeks
around like crazy, and due to being very expensive, this
delays song playback my 10 or 20 seconds */
@@ -241,7 +251,6 @@ vorbis_stream_decode(struct decoder *decoder,
struct input_stream *input_stream)
{
OggVorbis_File vf;
ov_callbacks callbacks;
OggCallbackData data;
struct audio_format audio_format;
int current_section;
@@ -266,13 +275,9 @@ vorbis_stream_decode(struct decoder *decoder,
data.input_stream = input_stream;
data.seekable = input_stream->seekable && oggvorbis_seekable(decoder);
callbacks.read_func = ogg_read_cb;
callbacks.seek_func = ogg_seek_cb;
callbacks.close_func = ogg_close_cb;
callbacks.tell_func = ogg_tell_cb;
if ((ret = ov_open_callbacks(&data, &vf, NULL, 0, callbacks)) < 0) {
if ((ret = ov_open_callbacks(&data, &vf, NULL, 0,
vorbis_is_callbacks)) < 0) {
const char *error;
if (decoder_get_command(decoder) != DECODE_COMMAND_NONE)
return;
@@ -405,8 +410,13 @@ static const char *const vorbis_suffixes[] = {
static const char *const vorbis_mime_types[] = {
"application/ogg",
"audio/x-vorbis+ogg",
"application/x-ogg",
"audio/ogg",
"audio/vorbis",
"audio/vorbis+ogg",
"audio/x-ogg",
"audio/x-vorbis",
"audio/x-vorbis+ogg",
NULL
};

@@ -99,7 +99,11 @@ wildmidi_file_decode(struct decoder *decoder, const char *path_fs)
unsigned long seek_where = WILDMIDI_SAMPLE_RATE *
decoder_seek_where(decoder);
#ifdef HAVE_WILDMIDI_SAMPLED_SEEK
WildMidi_SampledSeek(wm, &seek_where);
#else
WildMidi_FastSeek(wm, &seek_where);
#endif
decoder_command_finished(decoder);
cmd = DECODE_COMMAND_NONE;
}

@@ -18,6 +18,7 @@
*/
#include "decoder_control.h"
#include "pipe.h"
#include <assert.h>
@@ -58,22 +59,28 @@ static void dc_command_async(enum decoder_command cmd)
}
void
dc_start(struct notify *notify, struct song *song)
dc_start(struct notify *notify, struct song *song, struct music_pipe *pipe)
{
assert(dc.pipe != NULL);
assert(dc.pipe == NULL);
assert(song != NULL);
assert(pipe != NULL);
assert(music_pipe_empty(pipe));
dc.next_song = song;
dc.pipe = pipe;
dc_command(notify, DECODE_COMMAND_START);
}
void
dc_start_async(struct song *song)
dc_start_async(struct song *song, struct music_pipe *pipe)
{
assert(dc.pipe != NULL);
assert(dc.pipe == NULL);
assert(song != NULL);
assert(pipe != NULL);
assert(music_pipe_empty(pipe));
dc.next_song = song;
dc.pipe = pipe;
dc_command_async(DECODE_COMMAND_START);
}

@@ -118,10 +118,10 @@ void
dc_command_wait(struct notify *notify);
void
dc_start(struct notify *notify, struct song *song);
dc_start(struct notify *notify, struct song *song, struct music_pipe *pipe);
void
dc_start_async(struct song *song);
dc_start_async(struct song *song, struct music_pipe *pipe);
void
dc_stop(struct notify *notify);

@@ -89,7 +89,8 @@ static void decoder_run_song(const struct song *song, const char *uri)
struct input_stream input_stream;
const struct decoder_plugin *plugin;
if (!input_stream_open(&input_stream, uri)) {
close_instream = input_stream_open(&input_stream, uri);
if (!close_instream && !song_is_file(song)) {
dc.state = DECODE_STATE_ERROR;
return;
}
@@ -108,7 +109,7 @@ static void decoder_run_song(const struct song *song, const char *uri)
/* wait for the input stream to become ready; its metadata
will be available then */
while (!input_stream.ready) {
while (close_instream && !input_stream.ready) {
if (dc.command == DECODE_COMMAND_STOP) {
input_stream_close(&input_stream);
dc.state = DECODE_STATE_STOP;
@@ -124,7 +125,8 @@ static void decoder_run_song(const struct song *song, const char *uri)
}
if (dc.command == DECODE_COMMAND_STOP) {
input_stream_close(&input_stream);
if (close_instream)
input_stream_close(&input_stream);
dc.state = DECODE_STATE_STOP;
return;
}
@@ -179,8 +181,11 @@ static void decoder_run_song(const struct song *song, const char *uri)
const char *s = uri_get_suffix(uri);
while ((plugin = decoder_plugin_from_suffix(s, next++))) {
if (plugin->file_decode != NULL) {
input_stream_close(&input_stream);
close_instream = false;
if (close_instream) {
input_stream_close(&input_stream);
close_instream = false;
}
ret = decoder_file_decode(plugin,
&decoder, uri);
if (ret)

@@ -28,8 +28,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;

@@ -22,7 +22,7 @@
#include "client.h"
#include "song_print.h"
static int
static void
dirvec_print(struct client *client, const struct dirvec *dv)
{
size_t i;
@@ -30,15 +30,11 @@ dirvec_print(struct client *client, const struct dirvec *dv)
for (i = 0; i < dv->nr; ++i)
client_printf(client, DIRECTORY_DIR "%s\n",
directory_get_path(dv->base[i]));
return 0;
}
int
void
directory_print(struct client *client, const struct directory *directory)
{
dirvec_print(client, &directory->children);
songvec_print(client, &directory->songs);
return 0;
}

@@ -23,7 +23,7 @@
struct client;
struct directory;
int
void
directory_print(struct client *client, const struct directory *directory);
#endif

@@ -169,6 +169,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");

@@ -95,6 +95,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;

@@ -41,9 +41,6 @@
#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "input_curl"
/** rewinding is possible after up to 64 kB */
static const off_t max_rewind_size = 64 * 1024;
/**
* Buffers created by input_curl_writefunction().
*/
@@ -78,9 +75,6 @@ struct input_curl {
/** did libcurl tell us the we're at the end of the response body? */
bool eof;
/** limited list of old buffers, for rewinding */
GQueue *rewind;
/** error message provided by libcurl */
char error[CURL_ERROR_SIZE];
@@ -176,11 +170,6 @@ input_curl_easy_free(struct input_curl *c)
g_queue_foreach(c->buffers, buffer_free_callback, NULL);
g_queue_clear(c->buffers);
if (c->rewind != NULL) {
g_queue_foreach(c->rewind, buffer_free_callback, NULL);
g_queue_clear(c->rewind);
}
}
/**
@@ -201,8 +190,6 @@ input_curl_free(struct input_stream *is)
curl_multi_cleanup(c->multi);
g_queue_free(c->buffers);
if (c->rewind != NULL)
g_queue_free(c->rewind);
g_free(c->url);
g_free(c);
@@ -254,7 +241,6 @@ input_curl_select(struct input_curl *c)
fd_set rfds, wfds, efds;
int max_fd, ret;
CURLMcode mcode;
/* XXX hard coded timeout value.. */
struct timeval timeout = {
.tv_sec = 1,
.tv_usec = 0,
@@ -273,7 +259,23 @@ input_curl_select(struct input_curl *c)
return -1;
}
assert(max_fd >= 0);
#if LIBCURL_VERSION_NUM >= 0x070f04
long timeout2;
mcode = curl_multi_timeout(c->multi, &timeout2);
if (mcode != CURLM_OK) {
g_warning("curl_multi_timeout() failed: %s\n",
curl_multi_strerror(mcode));
return -1;
}
if (timeout2 >= 0) {
if (timeout2 > 10000)
timeout2 = 10000;
timeout.tv_sec = timeout2 / 1000;
timeout.tv_usec = (timeout2 % 1000) * 1000;
}
#endif
ret = select(max_fd + 1, &rfds, &wfds, &efds, &timeout);
if (ret < 0)
@@ -322,7 +324,7 @@ fill_buffer(struct input_stream *is)
* Mark a part of the buffer object as consumed.
*/
static struct buffer *
consume_buffer(struct buffer *buffer, size_t length, GQueue *rewind_buffers)
consume_buffer(struct buffer *buffer, size_t length)
{
assert(buffer != NULL);
assert(buffer->consumed < buffer->size);
@@ -333,19 +335,14 @@ consume_buffer(struct buffer *buffer, size_t length, GQueue *rewind_buffers)
assert(buffer->consumed == buffer->size);
if (rewind_buffers != NULL)
/* append this buffer to the rewind buffer list */
g_queue_push_tail(rewind_buffers, buffer);
else
g_free(buffer);
g_free(buffer);
return NULL;
}
static size_t
read_from_buffer(struct icy_metadata *icy_metadata, GQueue *buffers,
void *dest0, size_t length,
GQueue *rewind_buffers)
void *dest0, size_t length)
{
struct buffer *buffer = g_queue_pop_head(buffers);
uint8_t *dest = dest0;
@@ -364,7 +361,7 @@ read_from_buffer(struct icy_metadata *icy_metadata, GQueue *buffers,
if (chunk > 0) {
memcpy(dest, buffer->data + buffer->consumed,
chunk);
buffer = consume_buffer(buffer, chunk, rewind_buffers);
buffer = consume_buffer(buffer, chunk);
nbytes += chunk;
dest += chunk;
@@ -379,7 +376,7 @@ read_from_buffer(struct icy_metadata *icy_metadata, GQueue *buffers,
chunk = icy_meta(icy_metadata, buffer->data + buffer->consumed,
length);
if (chunk > 0) {
buffer = consume_buffer(buffer, chunk, rewind_buffers);
buffer = consume_buffer(buffer, chunk);
length -= chunk;
@@ -418,31 +415,9 @@ input_curl_read(struct input_stream *is, void *ptr, size_t size)
{
struct input_curl *c = is->data;
bool success;
GQueue *rewind_buffers;
size_t nbytes = 0;
char *dest = ptr;
#ifndef NDEBUG
if (c->rewind != NULL &&
(!g_queue_is_empty(c->rewind) || is->offset == 0)) {
off_t offset = 0;
struct buffer *buffer;
for (GList *list = g_queue_peek_head_link(c->rewind);
list != NULL; list = g_list_next(list)) {
buffer = list->data;
offset += buffer->consumed;
assert(offset <= is->offset);
}
buffer = g_queue_peek_head(c->buffers);
if (buffer != NULL)
offset += buffer->consumed;
assert(offset == is->offset);
}
#endif
do {
/* fill the buffer */
@@ -452,19 +427,9 @@ input_curl_read(struct input_stream *is, void *ptr, size_t size)
/* send buffer contents */
if (c->rewind != NULL &&
(!g_queue_is_empty(c->rewind) || is->offset == 0))
/* at the beginning or already writing the rewind
buffer list */
rewind_buffers = c->rewind;
else
/* we don't need the rewind buffers anymore */
rewind_buffers = NULL;
while (size > 0 && !g_queue_is_empty(c->buffers)) {
size_t copy = read_from_buffer(&c->icy_metadata, c->buffers,
dest + nbytes, size,
rewind_buffers);
dest + nbytes, size);
nbytes += copy;
size -= copy;
@@ -476,33 +441,6 @@ input_curl_read(struct input_stream *is, void *ptr, size_t size)
is->offset += (off_t)nbytes;
#ifndef NDEBUG
if (rewind_buffers != NULL) {
off_t offset = 0;
struct buffer *buffer;
for (GList *list = g_queue_peek_head_link(c->rewind);
list != NULL; list = g_list_next(list)) {
buffer = list->data;
offset += buffer->consumed;
assert(offset <= is->offset);
}
buffer = g_queue_peek_head(c->buffers);
if (buffer != NULL)
offset += buffer->consumed;
assert(offset == is->offset);
}
#endif
if (rewind_buffers != NULL && is->offset > max_rewind_size) {
/* drop the rewind buffer, it has grown too large */
g_queue_foreach(c->rewind, buffer_free_callback, NULL);
g_queue_clear(c->rewind);
}
return nbytes;
}
@@ -635,15 +573,6 @@ input_curl_headerfunction(void *ptr, size_t size, size_t nmemb, void *stream)
/* a stream with icy-metadata is not
seekable */
is->seekable = false;
if (c->rewind != NULL) {
/* rewinding with icy-metadata is too
hairy for me .. */
assert(g_queue_is_empty(c->rewind));
g_queue_free(c->rewind);
c->rewind = NULL;
}
}
}
@@ -732,6 +661,18 @@ input_curl_easy_init(struct input_stream *is)
return true;
}
void
input_curl_reinit(struct input_stream *is)
{
struct input_curl *c = is->data;
assert(is->plugin == &input_plugin_curl);
assert(c->easy != NULL);
curl_easy_setopt(c->easy, CURLOPT_WRITEHEADER, is);
curl_easy_setopt(c->easy, CURLOPT_WRITEDATA, is);
}
static bool
input_curl_send_request(struct input_curl *c)
{
@@ -751,72 +692,6 @@ input_curl_send_request(struct input_curl *c)
return true;
}
static bool
input_curl_can_rewind(struct input_stream *is)
{
struct input_curl *c = is->data;
struct buffer *buffer;
if (c->rewind == NULL)
return false;
if (!g_queue_is_empty(c->rewind))
/* the rewind buffer hasn't been wiped yet */
return true;
if (g_queue_is_empty(c->buffers))
/* there are no buffers at all - cheap rewind not
possible */
return false;
/* rewind is possible if this is the very first buffer of the
resource */
buffer = (struct buffer*)g_queue_peek_head(c->buffers);
return (off_t)buffer->consumed == is->offset;
}
static void
input_curl_rewind(struct input_stream *is)
{
struct input_curl *c = is->data;
#ifndef NDEBUG
off_t offset = 0;
#endif
assert(c->rewind != NULL);
/* rewind the current buffer */
if (!g_queue_is_empty(c->buffers)) {
struct buffer *buffer =
(struct buffer*)g_queue_peek_head(c->buffers);
#ifndef NDEBUG
offset += buffer->consumed;
#endif
buffer->consumed = 0;
}
/* reset and move all rewind buffers back to the regular buffer list */
while (!g_queue_is_empty(c->rewind)) {
struct buffer *buffer =
(struct buffer*)g_queue_pop_tail(c->rewind);
#ifndef NDEBUG
offset += buffer->consumed;
#endif
buffer->consumed = 0;
g_queue_push_head(c->buffers, buffer);
}
assert(offset == is->offset);
is->offset = 0;
/* rewind the icy_metadata object */
icy_reset(&c->icy_metadata);
}
static bool
input_curl_seek(struct input_stream *is, off_t offset, int whence)
{
@@ -825,17 +700,9 @@ input_curl_seek(struct input_stream *is, off_t offset, int whence)
assert(is->ready);
if (whence == SEEK_SET && offset == 0) {
if (is->offset == 0)
/* no-op */
return true;
if (input_curl_can_rewind(is)) {
/* we have enough rewind buffers left */
input_curl_rewind(is);
return true;
}
}
if (whence == SEEK_SET && offset == is->offset)
/* no-op */
return true;
if (!is->seekable)
return false;
@@ -868,26 +735,16 @@ input_curl_seek(struct input_stream *is, off_t offset, int whence)
/* check if we can fast-forward the buffer */
while (offset > is->offset && !g_queue_is_empty(c->buffers)) {
GQueue *rewind_buffers;
struct buffer *buffer;
size_t length;
if (c->rewind != NULL &&
(!g_queue_is_empty(c->rewind) || is->offset == 0))
/* at the beginning or already writing the rewind
buffer list */
rewind_buffers = c->rewind;
else
/* we don't need the rewind buffers anymore */
rewind_buffers = NULL;
buffer = (struct buffer *)g_queue_pop_head(c->buffers);
length = buffer->size - buffer->consumed;
if (offset - is->offset < (off_t)length)
length = offset - is->offset;
buffer = consume_buffer(buffer, length, rewind_buffers);
buffer = consume_buffer(buffer, length);
if (buffer != NULL)
g_queue_push_head(c->buffers, buffer);
@@ -940,7 +797,6 @@ input_curl_open(struct input_stream *is, const char *url)
c = g_new0(struct input_curl, 1);
c->url = g_strdup(url);
c->buffers = g_queue_new();
c->rewind = g_queue_new();
is->plugin = &input_plugin_curl;
is->data = c;

@@ -20,6 +20,16 @@
#ifndef MPD_INPUT_CURL_H
#define MPD_INPUT_CURL_H
struct input_stream;
extern const struct input_plugin input_plugin_curl;
/**
* This is a workaround for an input_stream API deficiency; after
* exchanging the input_stream pointer in input_rewind_open(), this
* function is called to reinitialize CURL's data pointers.
*/
void
input_curl_reinit(struct input_stream *is);
#endif

@@ -49,10 +49,13 @@ input_mms_open(struct input_stream *is, const char *url)
m = g_new(struct input_mms, 1);
m->mms = mmsx_connect(NULL, NULL, url, 128 * 1024);
if (m->mms == NULL) {
g_free(m);
g_warning("mmsx_connect() failed");
return false;
}
m->eof = false;
/* XX is this correct? at least this selects the ffmpeg
decoder, which seems to work fine*/
is->mime = g_strdup("audio/x-ms-wma");

@@ -0,0 +1,250 @@
/*
* Copyright (C) 2003-2009 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 "input/rewind_input_plugin.h"
#include "input/curl_input_plugin.h"
#ifdef ENABLE_MMS
#include "input/mms_input_plugin.h"
#endif
#include "input_plugin.h"
#include "tag.h"
#include <glib.h>
#include <assert.h>
#include <stdio.h>
#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "input_rewind"
struct input_rewind {
struct input_stream input;
/**
* The read position within the buffer. Undefined as long as
* reading_from_buffer() returns false.
*/
size_t head;
/**
* The write/append position within the buffer.
*/
size_t tail;
/**
* The size of this buffer is the maximum number of bytes
* which can be rewinded cheaply without passing the "seek"
* call to CURL.
*
* The origin of this buffer is always the beginning of the
* stream (offset 0).
*/
char buffer[64 * 1024];
};
/**
* Are we currently reading from the buffer, and does the buffer
* contain more data for the next read operation?
*/
static bool
reading_from_buffer(const struct input_stream *is)
{
const struct input_rewind *r = is->data;
return r->tail > 0 && is->offset < r->input.offset;
}
/**
* Copy public attributes from the underlying input stream to the
* "rewind" input stream. This function is called when a method of
* the underlying stream has returned, which may have modified these
* attributes.
*/
static void
copy_attributes(struct input_stream *dest)
{
const struct input_rewind *r = dest->data;
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->error = src->error;
dest->size = src->size;
dest->offset = src->offset;
if (src->mime != NULL) {
g_free(dest->mime);
dest->mime = g_strdup(src->mime);
}
}
static void
input_rewind_close(struct input_stream *is)
{
struct input_rewind *r = is->data;
input_stream_close(&r->input);
g_free(r);
}
static struct tag *
input_rewind_tag(struct input_stream *is)
{
struct input_rewind *r = is->data;
return input_stream_tag(&r->input);
}
static int
input_rewind_buffer(struct input_stream *is)
{
struct input_rewind *r = is->data;
int ret = input_stream_buffer(&r->input);
if (ret < 0 || !reading_from_buffer(is))
copy_attributes(is);
return ret;
}
static size_t
input_rewind_read(struct input_stream *is, void *ptr, size_t size)
{
struct input_rewind *r = is->data;
if (reading_from_buffer(is)) {
/* buffered read */
assert(r->head == (size_t)is->offset);
assert(r->tail == (size_t)r->input.offset);
if (size > r->tail - r->head)
size = r->tail - r->head;
memcpy(ptr, r->buffer + r->head, size);
r->head += size;
is->offset += size;
return size;
} else {
/* pass method call to underlying stream */
size_t nbytes = input_stream_read(&r->input, ptr, size);
if (r->input.offset > (off_t)sizeof(r->buffer))
/* disable buffering */
r->tail = 0;
else if (r->tail == (size_t)is->offset) {
/* append to buffer */
memcpy(r->buffer + r->tail, ptr, nbytes);
r->tail += nbytes;
assert(r->tail == (size_t)r->input.offset);
}
copy_attributes(is);
return nbytes;
}
}
static bool
input_rewind_eof(G_GNUC_UNUSED struct input_stream *is)
{
struct input_rewind *r = is->data;
return !reading_from_buffer(is) && input_stream_eof(&r->input);
}
static bool
input_rewind_seek(struct input_stream *is, off_t offset, int whence)
{
struct input_rewind *r = is->data;
assert(is->ready);
if (whence == SEEK_SET && r->tail > 0 && offset <= (off_t)r->tail) {
/* buffered seek */
assert(!reading_from_buffer(is) ||
r->head == (size_t)is->offset);
assert(r->tail == (size_t)r->input.offset);
r->head = (size_t)offset;
is->offset = offset;
return true;
} else {
bool success = input_stream_seek(&r->input, offset, whence);
copy_attributes(is);
/* disable the buffer, because r->input has left the
buffered range now */
r->tail = 0;
return success;
}
}
static const struct input_plugin rewind_input_plugin = {
.close = input_rewind_close,
.tag = input_rewind_tag,
.buffer = input_rewind_buffer,
.read = input_rewind_read,
.eof = input_rewind_eof,
.seek = input_rewind_seek,
};
void
input_rewind_open(struct input_stream *is)
{
struct input_rewind *c;
assert(is != NULL);
assert(is->offset == 0);
if (is->plugin != &input_plugin_curl
#ifdef ENABLE_MMS
&& is->plugin != &input_plugin_mms
#endif
)
/* due to limitations in the input_plugin API, we only
(explicitly) support the CURL input plugin */
return;
c = g_new(struct input_rewind, 1);
c->tail = 0;
/* move the CURL input stream to c->input */
c->input = *is;
if (is->plugin == &input_plugin_curl)
input_curl_reinit(&c->input);
/* convert the existing input_stream pointer to a "rewind"
input stream */
is->plugin = &rewind_input_plugin;
is->data = c;
is->mime = g_strdup(c->input.mime);
}

@@ -0,0 +1,49 @@
/*
* Copyright (C) 2003-2009 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.
*/
/** \file
*
* A wrapper for an input_stream object which allows cheap buffered
* rewinding. This is useful while detecting the stream codec (let
* each decoder plugin peek a portion from the stream).
*/
#ifndef MPD_INPUT_REWIND_H
#define MPD_INPUT_REWIND_H
#include "config.h"
struct input_stream;
#ifdef HAVE_CURL
void
input_rewind_open(struct input_stream *is);
#else
static inline void
input_rewind_open(struct input_stream *is)
{
(void)is;
}
#endif
#endif

@@ -22,6 +22,7 @@
#include "conf.h"
#include "input/file_input_plugin.h"
#include "input/rewind_input_plugin.h"
#ifdef ENABLE_ARCHIVE
#include "input/archive_input_plugin.h"
@@ -131,6 +132,8 @@ input_stream_open(struct input_stream *is, const char *url)
assert(is->plugin->eof != NULL);
assert(!is->seekable || is->plugin->seek != NULL);
input_rewind_open(is);
return true;
}
}

@@ -407,7 +407,13 @@ static int get_remote_uid(int fd)
return cred.uid;
#else
(void)fd;
#ifdef HAVE_GETPEEREID
uid_t euid;
gid_t egid;
if (getpeereid(fd, &euid, &egid) == 0)
return euid;
#endif
return -1;
#endif
}

@@ -42,9 +42,9 @@ locate_parse_type(const char *str)
if (0 == g_ascii_strcasecmp(str, LOCATE_TAG_ANY_KEY))
return LOCATE_TAG_ANY_TYPE;
for (i = 0; i < TAG_NUM_OF_ITEM_TYPES; i++)
if (0 == g_ascii_strcasecmp(str, tag_item_names[i]))
return i;
i = tag_name_parse_i(str);
if (i != TAG_NUM_OF_ITEM_TYPES)
return i;
return -1;
}

@@ -48,3 +48,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

@@ -25,6 +25,9 @@
#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "ao"
/* An ao_sample_format, with all fields set to zero: */
static const ao_sample_format OUR_AO_FORMAT_INITIALIZER;
static unsigned ao_output_ref;
struct ao_data {
@@ -166,7 +169,7 @@ static bool
ao_output_open(void *data, struct audio_format *audio_format,
GError **error)
{
ao_sample_format format;
ao_sample_format format = OUR_AO_FORMAT_INITIALIZER;
struct ao_data *ad = (struct ao_data *)data;
/* support for 24 bit samples in libao is currently dubious,

@@ -27,6 +27,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.

@@ -70,7 +70,7 @@ httpd_output_init(G_GNUC_UNUSED const struct audio_format *audio_format,
}
if (strcmp(encoder_name, "vorbis") == 0)
httpd->content_type = "application/x-ogg";
httpd->content_type = "audio/ogg";
else if (strcmp(encoder_name, "lame") == 0)
httpd->content_type = "audio/mpeg";
else

@@ -36,7 +36,7 @@
#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "jack"
static const size_t sample_size = sizeof(jack_default_audio_sample_t);
static const size_t jack_sample_size = sizeof(jack_default_audio_sample_t);
static const char *const port_names[2] = {
"left", "right",
@@ -118,14 +118,15 @@ mpd_jack_process(jack_nframes_t nframes, void *arg)
for (unsigned i = 0; i < G_N_ELEMENTS(jd->ringbuffer); ++i) {
available = jack_ringbuffer_read_space(jd->ringbuffer[i]);
assert(available % sample_size == 0);
available /= sample_size;
assert(available % jack_sample_size == 0);
available /= jack_sample_size;
if (available > nframes)
available = nframes;
out = jack_port_get_buffer(jd->ports[i], nframes);
jack_ringbuffer_read(jd->ringbuffer[i],
(char *)out, available * sample_size);
(char *)out,
available * jack_sample_size);
while (available < nframes)
/* ringbuffer underrun, fill with silence */
@@ -422,7 +423,7 @@ mpd_jack_play(void *data, const void *chunk, size_t size, GError **error)
/* send data symmetrically */
space = space1;
if (space >= frame_size)
if (space >= jack_sample_size)
break;
/* XXX do something more intelligent to
@@ -430,7 +431,7 @@ mpd_jack_play(void *data, const void *chunk, size_t size, GError **error)
g_usleep(1000);
}
space /= sample_size;
space /= jack_sample_size;
if (space < size)
size = space;

@@ -483,7 +483,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,

@@ -266,7 +266,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));
@@ -378,7 +378,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

@@ -50,6 +50,20 @@ static void ao_command(struct audio_output *ao, enum audio_output_command cmd)
ao_command_wait(ao);
}
/**
* Like ao_command(), but assumes the object is locked by the caller.
*/
static void
ao_command_locked(struct audio_output *ao, enum audio_output_command cmd)
{
assert(ao->command == AO_COMMAND_NONE);
ao->command = cmd;
g_mutex_unlock(ao->mutex);
ao_command_wait(ao);
g_mutex_lock(ao->mutex);
}
static void ao_command_async(struct audio_output *ao,
enum audio_output_command cmd)
{
@@ -58,6 +72,12 @@ static void ao_command_async(struct audio_output *ao,
notify_signal(&ao->notify);
}
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,
@@ -84,7 +104,7 @@ audio_output_open(struct audio_output *ao,
/* we're not using audio_output_cancel() here,
because that function is asynchronous */
ao_command(ao, AO_COMMAND_CANCEL);
ao_command_locked(ao, AO_COMMAND_CANCEL);
}
return true;
@@ -95,7 +115,7 @@ audio_output_open(struct audio_output *ao,
if (!ao->config_audio_format) {
if (ao->open)
audio_output_close(ao);
audio_output_close_locked(ao);
/* no audio format is configured: copy in->out, let
the output's open() method determine the effective
@@ -110,7 +130,7 @@ audio_output_open(struct audio_output *ao,
open = ao->open;
if (!open) {
ao_command(ao, AO_COMMAND_OPEN);
ao_command_locked(ao, AO_COMMAND_OPEN);
open = ao->open;
}
@@ -127,12 +147,19 @@ audio_output_update(struct audio_output *ao,
{
assert(mp != NULL);
g_mutex_lock(ao->mutex);
if (ao->enabled) {
if (ao->fail_timer == NULL ||
g_timer_elapsed(ao->fail_timer, NULL) > REOPEN_AFTER)
return audio_output_open(ao, audio_format, mp);
g_timer_elapsed(ao->fail_timer, NULL) > REOPEN_AFTER) {
bool ret = audio_output_open(ao, audio_format, mp);
g_mutex_unlock(ao->mutex);
return ret;
}
} else if (audio_output_is_open(ao))
audio_output_close(ao);
audio_output_close_locked(ao);
g_mutex_unlock(ao->mutex);
return false;
}
@@ -162,21 +189,33 @@ void audio_output_cancel(struct audio_output *ao)
ao_command_async(ao, AO_COMMAND_CANCEL);
}
void audio_output_close(struct audio_output *ao)
static void
audio_output_close_locked(struct audio_output *ao)
{
assert(ao != NULL);
assert(!ao->open || ao->fail_timer == NULL);
if (ao->mixer != NULL)
mixer_auto_close(ao->mixer);
if (ao->open)
ao_command(ao, AO_COMMAND_CLOSE);
ao_command_locked(ao, AO_COMMAND_CLOSE);
else if (ao->fail_timer != NULL) {
g_timer_destroy(ao->fail_timer);
ao->fail_timer = NULL;
}
}
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);

@@ -131,7 +131,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;

@@ -105,7 +105,12 @@ ao_play_chunk(struct audio_output *ao, const struct music_chunk *chunk)
/* don't automatically reopen this device for
10 seconds */
g_mutex_lock(ao->mutex);
assert(ao->fail_timer == NULL);
ao->fail_timer = g_timer_new();
g_mutex_unlock(ao->mutex);
return false;
}
@@ -192,10 +197,18 @@ static gpointer audio_output_task(gpointer arg)
case AO_COMMAND_OPEN:
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;
}
error = NULL;
ret = ao_plugin_open(ao->plugin, ao->data,
&ao->out_audio_format,
@@ -268,6 +281,16 @@ static gpointer audio_output_task(gpointer arg)
ao->chunk = NULL;
if (ao->open)
ao_plugin_cancel(ao->plugin, ao->data);
/* we must clear the notification now, because
the notify_wait() call below must wait
until audio_output_all_cancel() has cleared
the pipe; if another notification happens
to be still pending, we get a race
condition with a crash or an assertion
failure */
notify_clear(&ao->notify);
ao_command_finished(ao);
/* the player thread will now clear our music

@@ -28,7 +28,7 @@
* would put too much stress on the allocator.
*/
struct pcm_buffer {
char *buffer;
void *buffer;
size_t size;
};

@@ -20,9 +20,9 @@
#ifndef MPD_PIPE_H
#define MPD_PIPE_H
#ifndef NDEBUG
#include <stdbool.h>
#ifndef NDEBUG
struct audio_format;
#endif
@@ -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

@@ -89,6 +89,13 @@ struct player {
*/
unsigned cross_fade_chunks;
/**
* The tag of the "next" song during cross-fade. It is
* postponed, and sent to the output thread when the new song
* really begins.
*/
struct tag *cross_fade_tag;
/**
* The current audio format for the audio outputs.
*/
@@ -111,6 +118,33 @@ static void player_command_finished(void)
notify_signal(&main_notify);
}
/**
* 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 dc.pipe == player->pipe;
}
/**
* Has the decoder already begun decoding the next song?
*
* Note: this function does not check if the decoder is already
* finished.
*/
static bool
player_dc_at_next_song(const struct player *player)
{
return dc.pipe != NULL && !player_dc_at_current_song(player);
}
/**
* Stop the decoder and clears (and frees) its music pipe.
*/
@@ -290,10 +324,9 @@ static bool player_seek_decoder(struct player *player)
/* clear music chunks which might still reside in the
pipe */
music_pipe_clear(player->pipe, player_buffer);
dc.pipe = player->pipe;
/* re-start the decoder */
dc_start_async(pc.next_song);
dc_start_async(pc.next_song, player->pipe);
ret = player_wait_for_decoder(player);
if (!ret) {
/* decoder failure */
@@ -301,6 +334,14 @@ static bool player_seek_decoder(struct player *player)
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;
}
@@ -357,7 +398,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();
@@ -402,7 +443,7 @@ static void player_process_command(struct player *player)
return;
}
if (dc.pipe != NULL && dc.pipe != player->pipe)
if (player_dc_at_next_song(player))
/* the decoder is already decoding the song -
stop it and reset the position */
player_dc_stop(player);
@@ -498,7 +539,7 @@ play_next_chunk(struct player *player)
return true;
if (player->xfade == XFADE_ENABLED &&
dc.pipe != NULL && dc.pipe != player->pipe &&
player_dc_at_next_song(player) &&
(cross_fade_position = music_pipe_size(player->pipe))
<= player->cross_fade_chunks) {
/* perform cross fade */
@@ -518,6 +559,14 @@ play_next_chunk(struct player *player)
chunk = music_pipe_shift(player->pipe);
assert(chunk != NULL);
/* don't send the tags of the new song (which
is being faded in) yet; postpone it until
the current song is faded out */
player->cross_fade_tag =
tag_merge_replace(player->cross_fade_tag,
other_chunk->tag);
other_chunk->tag = NULL;
cross_fade_apply(chunk, other_chunk,
&dc.out_audio_format,
cross_fade_position,
@@ -544,6 +593,14 @@ play_next_chunk(struct player *player)
assert(chunk != NULL);
/* insert the postponed tag if cross-fading is finished */
if (player->xfade != XFADE_ENABLED && player->cross_fade_tag != NULL) {
chunk->tag = tag_merge_replace(chunk->tag,
player->cross_fade_tag);
player->cross_fade_tag = NULL;
}
/* play the current chunk */
success = play_chunk(player->song, chunk, &player->play_audio_format,
@@ -608,14 +665,14 @@ static void do_play(void)
.xfade = XFADE_UNKNOWN,
.cross_fading = false,
.cross_fade_chunks = 0,
.cross_fade_tag = NULL,
.size_to_time = 0.0,
};
player.pipe = music_pipe_new();
dc.buffer = player_buffer;
dc.pipe = player.pipe;
dc_start(&pc.notify, pc.next_song);
dc_start(&pc.notify, pc.next_song, player.pipe);
if (!player_wait_for_decoder(&player)) {
player_dc_stop(&player);
player_command_finished();
@@ -682,14 +739,15 @@ static void do_play(void)
/* the decoder has finished the current song;
make it decode the next song */
assert(pc.next_song != NULL);
assert(dc.pipe == NULL || dc.pipe == player.pipe);
assert(!player_dc_at_next_song(&player));
dc.pipe = NULL;
player.queued = false;
dc.pipe = music_pipe_new();
dc_start_async(pc.next_song);
dc_start_async(pc.next_song, music_pipe_new());
}
if (dc.pipe != NULL && dc.pipe != player.pipe &&
if (player_dc_at_next_song(&player) &&
player.xfade == XFADE_UNKNOWN &&
!decoder_is_starting()) {
/* enable cross fading in this song? if yes,
@@ -712,7 +770,7 @@ static void do_play(void)
if (player.paused)
notify_wait(&pc.notify);
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 */
@@ -724,7 +782,7 @@ static void do_play(void)
/* XXX synchronize in a better way */
g_usleep(10000);
} else if (dc.pipe != NULL && dc.pipe != player.pipe) {
} else if (player_dc_at_next_song(&player)) {
/* at the beginning of a new song */
if (!player_song_border(&player))
@@ -733,7 +791,7 @@ static void do_play(void)
/* 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))
break;
} else {
/* the decoder is too busy and hasn't provided
@@ -754,6 +812,9 @@ static void do_play(void)
music_pipe_clear(player.pipe, player_buffer);
music_pipe_free(player.pipe);
if (player.cross_fade_tag != NULL)
tag_free(player.cross_fade_tag);
pc.state = PLAYER_STATE_STOP;
event_pipe_emit(PIPE_EVENT_PLAYLIST);
}

@@ -140,7 +140,8 @@ playlist_update_queued_song(struct playlist *playlist, const struct song *prev)
? queue_next_order(&playlist->queue, playlist->current)
: 0;
if (next_order == 0 && playlist->queue.random) {
if (next_order == 0 && playlist->queue.random &&
!playlist->queue.single) {
/* shuffle the song order again, so we get a different
order each time the playlist is played
completely */

@@ -24,6 +24,7 @@
#include "playlist_internal.h"
#include "player_control.h"
#include "idle.h"
#include <glib.h>
@@ -156,6 +157,8 @@ nextSongInPlaylist(struct playlist *playlist)
if (next_order < 0) {
/* cancel single */
playlist->queue.single = false;
idle_add(IDLE_OPTIONS);
/* no song after this one: stop playback */
stopPlaylist(playlist);

@@ -45,14 +45,14 @@ queue_next_order(const struct queue *queue, unsigned order)
if (queue->single)
{
if (queue->repeat)
if (queue->repeat && !queue->consume)
return order;
else
return -1;
}
if (order + 1 < queue->length)
return order + 1;
else if (queue->repeat)
else if (queue->repeat && (order > 0 || !queue->consume))
/* restart at first song */
return 0;
else

@@ -64,6 +64,36 @@ const char *tag_item_names[TAG_NUM_OF_ITEM_TYPES] = {
bool ignore_tag_items[TAG_NUM_OF_ITEM_TYPES];
enum tag_type
tag_name_parse(const char *name)
{
assert(name != NULL);
for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) {
assert(tag_item_names[i] != NULL);
if (strcmp(name, tag_item_names[i]) == 0)
return (enum tag_type)i;
}
return TAG_NUM_OF_ITEM_TYPES;
}
enum tag_type
tag_name_parse_i(const char *name)
{
assert(name != NULL);
for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) {
assert(tag_item_names[i] != NULL);
if (g_ascii_strcasecmp(name, tag_item_names[i]) == 0)
return (enum tag_type)i;
}
return TAG_NUM_OF_ITEM_TYPES;
}
static size_t items_size(const struct tag *tag)
{
return tag->num_items * sizeof(struct tag_item *);
@@ -76,7 +106,7 @@ void tag_lib_init(void)
char *temp;
char *s;
char *c;
int i;
enum tag_type type;
/* parse the "metadata_to_use" config parameter below */
@@ -98,16 +128,18 @@ void tag_lib_init(void)
if (*s == '\0')
quit = 1;
*s = '\0';
for (i = 0; i < TAG_NUM_OF_ITEM_TYPES; i++) {
if (g_ascii_strcasecmp(c, tag_item_names[i]) == 0) {
ignore_tag_items[i] = false;
break;
}
}
if (strlen(c) && i == TAG_NUM_OF_ITEM_TYPES) {
c = g_strstrip(c);
if (*c == 0)
continue;
type = tag_name_parse_i(c);
if (type == TAG_NUM_OF_ITEM_TYPES)
g_error("error parsing metadata item \"%s\"",
c);
}
ignore_tag_items[type] = false;
s++;
c = s;
}
@@ -247,6 +279,22 @@ tag_merge(const struct tag *base, const struct tag *add)
return ret;
}
struct tag *
tag_merge_replace(struct tag *base, struct tag *add)
{
if (add == NULL)
return base;
if (base == NULL)
return add;
struct tag *tag = tag_merge(base, add);
tag_free(base);
tag_free(add);
return tag;
}
const char *
tag_get_value(const struct tag *tag, enum tag_type type)
{

@@ -93,6 +93,22 @@ struct tag {
unsigned num_items;
};
/**
* Parse the string, and convert it into a #tag_type. Returns
* #TAG_NUM_OF_ITEM_TYPES if the string could not be recognized.
*/
enum tag_type
tag_name_parse(const char *name);
/**
* Parse the string, and convert it into a #tag_type. Returns
* #TAG_NUM_OF_ITEM_TYPES if the string could not be recognized.
*
* Case does not matter.
*/
enum tag_type
tag_name_parse_i(const char *name);
/**
* Creates an empty #tag.
*/
@@ -165,6 +181,15 @@ struct tag *tag_dup(const struct tag *tag);
struct tag *
tag_merge(const struct tag *base, const struct tag *add);
/**
* Merges the data from two tags. Any of the two may be NULL. Both
* are freed by this function.
*
* @return a newly allocated tag, which must be freed with tag_free()
*/
struct tag *
tag_merge_replace(struct tag *base, struct tag *add);
/**
* Returns true if the tag contains no items. This ignores the "time"
* attribute.

@@ -19,12 +19,47 @@
#include "tag_ape.h"
#include "tag.h"
#include "tag_table.h"
#include <glib.h>
#include <assert.h>
#include <stdio.h>
static const char *const ape_tag_names[TAG_NUM_OF_ITEM_TYPES] = {
[TAG_ITEM_ALBUM_ARTIST] = "album artist",
[TAG_ITEM_DATE] = "year"
};
static enum tag_type
tag_ape_name_parse(const char *name)
{
enum tag_type type = tag_table_lookup(ape_tag_names, name);
if (type == TAG_NUM_OF_ITEM_TYPES)
type = tag_name_parse_i(name);
return type;
}
static struct tag *
tag_ape_import_item(struct tag *tag, unsigned long flags,
const char *key, const char *value, size_t value_length)
{
/* we only care about utf-8 text tags */
if ((flags & (0x3 << 1)) != 0)
return tag;
enum tag_type type = tag_ape_name_parse(key);
if (type == TAG_NUM_OF_ITEM_TYPES)
return tag;
if (tag == NULL)
tag = tag_new();
tag_add_item_n(tag, type, value, value_length);
return tag;
}
struct tag *
tag_ape_load(const char *file)
{
@@ -36,7 +71,6 @@ tag_ape_load(const char *file)
size_t tagLen;
size_t size;
unsigned long flags;
int i;
char *key;
struct {
@@ -48,26 +82,6 @@ tag_ape_load(const char *file)
unsigned char reserved[8];
} footer;
const char *apeItems[7] = {
"title",
"artist",
"album",
"comment",
"genre",
"track",
"year"
};
int tagItems[7] = {
TAG_ITEM_TITLE,
TAG_ITEM_ARTIST,
TAG_ITEM_ALBUM,
TAG_ITEM_COMMENT,
TAG_ITEM_GENRE,
TAG_ITEM_TRACK,
TAG_ITEM_DATE,
};
fp = fopen(file, "r");
if (!fp)
return NULL;
@@ -127,17 +141,8 @@ tag_ape_load(const char *file)
if (tagLen < size)
goto fail;
/* we only care about utf-8 text tags */
if (!(flags & (0x3 << 1))) {
for (i = 0; i < 7; i++) {
if (g_ascii_strcasecmp(key, apeItems[i]) == 0) {
if (!ret)
ret = tag_new();
tag_add_item_n(ret, tagItems[i],
p, size);
}
}
}
ret = tag_ape_import_item(ret, flags, key, p, size);
p += size;
tagLen -= size;
}

43
src/tag_table.h Normal file

@@ -0,0 +1,43 @@
/*
* 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_TAG_TABLE_H
#define MPD_TAG_TABLE_H
#include "tag.h"
#include <glib.h>
/**
* Looks up a string in a tag translation table (case insensitive).
* Returns TAG_NUM_OF_ITEM_TYPES if the specified name was not found
* in the table.
*/
static inline enum tag_type
tag_table_lookup(const char *const* table, const char *name)
{
for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; i++)
if (table[i] != NULL &&
g_ascii_strcasecmp(name, table[i]) == 0)
return (enum tag_type)i;
return TAG_NUM_OF_ITEM_TYPES;
}
#endif

@@ -254,6 +254,9 @@ stat_directory(const struct directory *directory, struct stat *st)
if (path_fs == NULL)
return -1;
ret = stat(path_fs, st);
if (ret < 0)
g_warning("Failed to stat %s: %s", path_fs, g_strerror(errno));
g_free(path_fs);
return ret;
}
@@ -270,6 +273,9 @@ stat_directory_child(const struct directory *parent, const char *name,
return -1;
ret = stat(path_fs, st);
if (ret < 0)
g_warning("Failed to stat %s: %s", path_fs, g_strerror(errno));
g_free(path_fs);
return ret;
}

@@ -62,7 +62,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);