aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore6
-rw-r--r--INSTALL17
-rw-r--r--Makefile.am755
-rw-r--r--NEWS41
-rwxr-xr-xautogen.sh2
-rw-r--r--configure.ac337
-rw-r--r--doc/doxygen.conf.in (renamed from doc/doxygen.conf)6
-rw-r--r--doc/mpd.conf.521
-rw-r--r--doc/mpdconf.example6
-rw-r--r--doc/protocol.xml277
-rw-r--r--doc/user.xml414
-rw-r--r--m4/faad.m418
-rw-r--r--m4/ucred.m42
-rw-r--r--src/ack.h14
-rw-r--r--src/aiff.c4
-rw-r--r--src/aiff.h2
-rw-r--r--src/ape.c2
-rw-r--r--src/ape.h2
-rw-r--r--src/archive/bz2_archive_plugin.c21
-rw-r--r--src/archive/bz2_archive_plugin.h2
-rw-r--r--src/archive/iso9660_archive_plugin.c11
-rw-r--r--src/archive/iso9660_archive_plugin.h2
-rw-r--r--src/archive/zzip_archive_plugin.c10
-rw-r--r--src/archive/zzip_archive_plugin.h2
-rw-r--r--src/archive_api.c2
-rw-r--r--src/archive_api.h2
-rw-r--r--src/archive_internal.h2
-rw-r--r--src/archive_list.c4
-rw-r--r--src/archive_list.h2
-rw-r--r--src/archive_plugin.c10
-rw-r--r--src/archive_plugin.h10
-rw-r--r--src/audio_check.c2
-rw-r--r--src/audio_check.h3
-rw-r--r--src/audio_config.c (renamed from src/audio.c)4
-rw-r--r--src/audio_config.h (renamed from src/audio.h)6
-rw-r--r--src/audio_format.c37
-rw-r--r--src/audio_format.h93
-rw-r--r--src/audio_parser.c22
-rw-r--r--src/audio_parser.h4
-rw-r--r--src/buffer.c2
-rw-r--r--src/buffer.h2
-rw-r--r--src/check.h2
-rw-r--r--src/chunk.c2
-rw-r--r--src/chunk.h2
-rw-r--r--src/client.c2
-rw-r--r--src/client.h33
-rw-r--r--src/client_event.c2
-rw-r--r--src/client_expire.c2
-rw-r--r--src/client_file.c70
-rw-r--r--src/client_file.h43
-rw-r--r--src/client_global.c2
-rw-r--r--src/client_idle.c19
-rw-r--r--src/client_idle.h45
-rw-r--r--src/client_internal.h32
-rw-r--r--src/client_list.c2
-rw-r--r--src/client_message.c96
-rw-r--r--src/client_message.h72
-rw-r--r--src/client_new.c14
-rw-r--r--src/client_process.c2
-rw-r--r--src/client_read.c2
-rw-r--r--src/client_subscribe.c123
-rw-r--r--src/client_subscribe.h59
-rw-r--r--src/client_write.c2
-rw-r--r--src/cmdline.c4
-rw-r--r--src/cmdline.h2
-rw-r--r--src/command.c1038
-rw-r--r--src/command.h2
-rw-r--r--src/conf.c155
-rw-r--r--src/conf.h48
-rw-r--r--src/crossfade.c3
-rw-r--r--src/crossfade.h2
-rw-r--r--src/cue/cue_parser.c327
-rw-r--r--src/cue/cue_parser.h58
-rw-r--r--src/cue/cue_tag.c235
-rw-r--r--src/cue/cue_tag.h23
-rw-r--r--src/daemon.c4
-rw-r--r--src/daemon.h2
-rw-r--r--src/database.c359
-rw-r--r--src/database.h42
-rw-r--r--src/db/simple_db_plugin.c358
-rw-r--r--src/db/simple_db_plugin.h42
-rw-r--r--src/dbUtils.c324
-rw-r--r--src/dbUtils.h45
-rw-r--r--src/db_error.h45
-rw-r--r--src/db_internal.h35
-rw-r--r--src/db_lock.c33
-rw-r--r--src/db_lock.h84
-rw-r--r--src/db_plugin.h156
-rw-r--r--src/db_print.c393
-rw-r--r--src/db_print.h71
-rw-r--r--src/db_save.c179
-rw-r--r--src/db_save.h35
-rw-r--r--src/db_selection.h56
-rw-r--r--src/db_visitor.h54
-rw-r--r--src/decoder/_flac_common.c19
-rw-r--r--src/decoder/_flac_common.h5
-rw-r--r--src/decoder/_ogg_common.c2
-rw-r--r--src/decoder/_ogg_common.h2
-rw-r--r--src/decoder/audiofile_decoder_plugin.c23
-rw-r--r--src/decoder/dsdiff_decoder_plugin.c458
-rw-r--r--src/decoder/dsdiff_decoder_plugin.h (renamed from src/playlist/flac_playlist_plugin.h)8
-rw-r--r--src/decoder/faad_decoder_plugin.c22
-rw-r--r--src/decoder/ffmpeg_decoder_plugin.c135
-rw-r--r--src/decoder/ffmpeg_metadata.c85
-rw-r--r--src/decoder/ffmpeg_metadata.h41
-rw-r--r--src/decoder/flac_compat.h2
-rw-r--r--src/decoder/flac_decoder_plugin.c185
-rw-r--r--src/decoder/flac_metadata.c110
-rw-r--r--src/decoder/flac_metadata.h12
-rw-r--r--src/decoder/flac_pcm.c5
-rw-r--r--src/decoder/flac_pcm.h2
-rw-r--r--src/decoder/fluidsynth_decoder_plugin.c16
-rw-r--r--src/decoder/gme_decoder_plugin.c66
-rw-r--r--src/decoder/mad_decoder_plugin.c20
-rw-r--r--src/decoder/mikmod_decoder_plugin.c21
-rw-r--r--src/decoder/modplug_decoder_plugin.c32
-rw-r--r--src/decoder/mp4ff_decoder_plugin.c41
-rw-r--r--src/decoder/mpcdec_decoder_plugin.c20
-rw-r--r--src/decoder/mpg123_decoder_plugin.c68
-rw-r--r--src/decoder/oggflac_decoder_plugin.c354
-rw-r--r--src/decoder/pcm_decoder_plugin.c105
-rw-r--r--src/decoder/pcm_decoder_plugin.h33
-rw-r--r--src/decoder/sidplay_decoder_plugin.cxx29
-rw-r--r--src/decoder/sndfile_decoder_plugin.c34
-rw-r--r--src/decoder/vorbis_comments.c156
-rw-r--r--src/decoder/vorbis_comments.h40
-rw-r--r--src/decoder/vorbis_decoder_plugin.c131
-rw-r--r--src/decoder/wavpack_decoder_plugin.c113
-rw-r--r--src/decoder/wildmidi_decoder_plugin.c20
-rw-r--r--src/decoder_api.c105
-rw-r--r--src/decoder_api.h4
-rw-r--r--src/decoder_buffer.c2
-rw-r--r--src/decoder_buffer.h2
-rw-r--r--src/decoder_command.h2
-rw-r--r--src/decoder_control.c28
-rw-r--r--src/decoder_control.h18
-rw-r--r--src/decoder_internal.c37
-rw-r--r--src/decoder_internal.h4
-rw-r--r--src/decoder_list.c8
-rw-r--r--src/decoder_list.h2
-rw-r--r--src/decoder_plugin.c4
-rw-r--r--src/decoder_plugin.h46
-rw-r--r--src/decoder_print.c2
-rw-r--r--src/decoder_print.h2
-rw-r--r--src/decoder_thread.c65
-rw-r--r--src/decoder_thread.h2
-rw-r--r--src/despotify_utils.c121
-rw-r--r--src/despotify_utils.h67
-rw-r--r--src/directory.c236
-rw-r--r--src/directory.h176
-rw-r--r--src/directory_print.c74
-rw-r--r--src/directory_save.c40
-rw-r--r--src/directory_save.h4
-rw-r--r--src/dirvec.c153
-rw-r--r--src/dsd2pcm/dsd2pcm.c184
-rw-r--r--src/dsd2pcm/dsd2pcm.h64
-rw-r--r--src/dsd2pcm/dsd2pcm.hpp41
-rw-r--r--src/dsd2pcm/info.txt38
-rw-r--r--src/dsd2pcm/main.cpp120
-rw-r--r--src/dsd2pcm/noiseshape.c83
-rw-r--r--src/dsd2pcm/noiseshape.h57
-rw-r--r--src/dsd2pcm/noiseshape.hpp46
-rw-r--r--src/dummy.cxx30
-rw-r--r--src/encoder/flac_encoder.c4
-rw-r--r--src/encoder/lame_encoder.c2
-rw-r--r--src/encoder/null_encoder.c2
-rw-r--r--src/encoder/twolame_encoder.c2
-rw-r--r--src/encoder/vorbis_encoder.c2
-rw-r--r--src/encoder/wave_encoder.c7
-rw-r--r--src/encoder_api.h2
-rw-r--r--src/encoder_list.c2
-rw-r--r--src/encoder_list.h2
-rw-r--r--src/encoder_plugin.h12
-rw-r--r--src/event_pipe.c2
-rw-r--r--src/event_pipe.h2
-rw-r--r--src/exclude.c2
-rw-r--r--src/exclude.h2
-rw-r--r--src/fd_util.c25
-rw-r--r--src/fd_util.h9
-rw-r--r--src/filter/autoconvert_filter_plugin.c2
-rw-r--r--src/filter/autoconvert_filter_plugin.h2
-rw-r--r--src/filter/chain_filter_plugin.c2
-rw-r--r--src/filter/chain_filter_plugin.h2
-rw-r--r--src/filter/convert_filter_plugin.c3
-rw-r--r--src/filter/convert_filter_plugin.h2
-rw-r--r--src/filter/normalize_filter_plugin.c3
-rw-r--r--src/filter/null_filter_plugin.c2
-rw-r--r--src/filter/replay_gain_filter_plugin.c6
-rw-r--r--src/filter/replay_gain_filter_plugin.h2
-rw-r--r--src/filter/route_filter_plugin.c2
-rw-r--r--src/filter/volume_filter_plugin.c6
-rw-r--r--src/filter/volume_filter_plugin.h2
-rw-r--r--src/filter_config.c2
-rw-r--r--src/filter_config.h2
-rw-r--r--src/filter_internal.h2
-rw-r--r--src/filter_plugin.c2
-rw-r--r--src/filter_plugin.h10
-rw-r--r--src/filter_registry.c2
-rw-r--r--src/filter_registry.h2
-rw-r--r--src/gcc.h49
-rw-r--r--src/glib_compat.h37
-rw-r--r--src/icy_metadata.c2
-rw-r--r--src/icy_metadata.h2
-rw-r--r--src/icy_server.c4
-rw-r--r--src/icy_server.h2
-rw-r--r--src/idle.c4
-rw-r--r--src/idle.h8
-rw-r--r--src/inotify_queue.c2
-rw-r--r--src/inotify_queue.h2
-rw-r--r--src/inotify_source.c2
-rw-r--r--src/inotify_source.h2
-rw-r--r--src/inotify_update.c35
-rw-r--r--src/inotify_update.h2
-rw-r--r--src/input/archive_input_plugin.c9
-rw-r--r--src/input/archive_input_plugin.h2
-rw-r--r--src/input/cdio_paranoia_input_plugin.c371
-rw-r--r--src/input/cdio_paranoia_input_plugin.h28
-rw-r--r--src/input/curl_input_plugin.c912
-rw-r--r--src/input/curl_input_plugin.h10
-rw-r--r--src/input/despotify_input_plugin.c230
-rw-r--r--src/input/despotify_input_plugin.h25
-rw-r--r--src/input/ffmpeg_input_plugin.c21
-rw-r--r--src/input/ffmpeg_input_plugin.h2
-rw-r--r--src/input/file_input_plugin.c18
-rw-r--r--src/input/file_input_plugin.h2
-rw-r--r--src/input/mms_input_plugin.c10
-rw-r--r--src/input/mms_input_plugin.h2
-rw-r--r--src/input/rewind_input_plugin.c38
-rw-r--r--src/input/rewind_input_plugin.h2
-rw-r--r--src/input/soup_input_plugin.c473
-rw-r--r--src/input/soup_input_plugin.h25
-rw-r--r--src/input_init.c8
-rw-r--r--src/input_init.h4
-rw-r--r--src/input_internal.c73
-rw-r--r--src/input_internal.h43
-rw-r--r--src/input_plugin.h34
-rw-r--r--src/input_registry.c23
-rw-r--r--src/input_registry.h2
-rw-r--r--src/input_stream.c148
-rw-r--r--src/input_stream.h165
-rw-r--r--src/io_thread.c200
-rw-r--r--src/io_thread.h80
-rw-r--r--src/listen.c41
-rw-r--r--src/listen.h2
-rw-r--r--src/locate.c15
-rw-r--r--src/locate.h14
-rw-r--r--src/log.c44
-rw-r--r--src/log.h12
-rw-r--r--src/ls.c10
-rw-r--r--src/ls.h2
-rw-r--r--src/main.c173
-rw-r--r--src/main.h4
-rw-r--r--src/main_win32.c2
-rw-r--r--src/mapper.c8
-rw-r--r--src/mapper.h23
-rw-r--r--src/mixer/alsa_mixer_plugin.c275
-rw-r--r--src/mixer/oss_mixer_plugin.c2
-rw-r--r--src/mixer/pulse_mixer_plugin.c14
-rw-r--r--src/mixer/pulse_mixer_plugin.h2
-rw-r--r--src/mixer/raop_mixer_plugin.c67
-rw-r--r--src/mixer/roar_mixer_plugin.c104
-rw-r--r--src/mixer/software_mixer_plugin.c2
-rw-r--r--src/mixer/software_mixer_plugin.h2
-rw-r--r--src/mixer/winmm_mixer_plugin.c2
-rw-r--r--src/mixer_all.c2
-rw-r--r--src/mixer_all.h2
-rw-r--r--src/mixer_api.c2
-rw-r--r--src/mixer_api.h2
-rw-r--r--src/mixer_control.c2
-rw-r--r--src/mixer_control.h2
-rw-r--r--src/mixer_list.h4
-rw-r--r--src/mixer_plugin.h10
-rw-r--r--src/mixer_type.c2
-rw-r--r--src/mixer_type.h2
-rw-r--r--src/mpd_error.h2
-rw-r--r--src/notify.c2
-rw-r--r--src/notify.h2
-rw-r--r--src/ntp_server.c131
-rw-r--r--src/ntp_server.h44
-rw-r--r--src/open.h2
-rw-r--r--src/output/alsa_output_plugin.c (renamed from src/output/alsa_plugin.c)313
-rw-r--r--src/output/alsa_output_plugin.h25
-rw-r--r--src/output/ao_output_plugin.c (renamed from src/output/ao_plugin.c)40
-rw-r--r--src/output/ao_output_plugin.h25
-rw-r--r--src/output/ffado_output_plugin.c38
-rw-r--r--src/output/ffado_output_plugin.h25
-rw-r--r--src/output/fifo_output_plugin.c92
-rw-r--r--src/output/fifo_output_plugin.h25
-rw-r--r--src/output/httpd_client.c42
-rw-r--r--src/output/httpd_client.h2
-rw-r--r--src/output/httpd_internal.h9
-rw-r--r--src/output/httpd_output_plugin.c73
-rw-r--r--src/output/httpd_output_plugin.h25
-rw-r--r--src/output/jack_output_plugin.c52
-rw-r--r--src/output/jack_output_plugin.h25
-rw-r--r--src/output/mvp_output_plugin.c (renamed from src/output/mvp_plugin.c)58
-rw-r--r--src/output/mvp_output_plugin.h25
-rw-r--r--src/output/null_output_plugin.c (renamed from src/output/null_plugin.c)64
-rw-r--r--src/output/null_output_plugin.h25
-rw-r--r--src/output/openal_output_plugin.c (renamed from src/output/openal_plugin.c)128
-rw-r--r--src/output/openal_output_plugin.h25
-rw-r--r--src/output/oss_output_plugin.c (renamed from src/output/oss_plugin.c)246
-rw-r--r--src/output/oss_output_plugin.h25
-rw-r--r--src/output/osx_output_plugin.c434
-rw-r--r--src/output/osx_output_plugin.h25
-rw-r--r--src/output/osx_plugin.c289
-rw-r--r--src/output/pipe_output_plugin.c35
-rw-r--r--src/output/pipe_output_plugin.h25
-rw-r--r--src/output/pulse_output_plugin.c149
-rw-r--r--src/output/pulse_output_plugin.h39
-rw-r--r--src/output/raop_output_plugin.c1056
-rw-r--r--src/output/raop_output_plugin.h37
-rw-r--r--src/output/recorder_output_plugin.c37
-rw-r--r--src/output/recorder_output_plugin.h25
-rw-r--r--src/output/roar_output_plugin.c401
-rw-r--r--src/output/roar_output_plugin.h35
-rw-r--r--src/output/shout_output_plugin.c (renamed from src/output/shout_plugin.c)77
-rw-r--r--src/output/shout_output_plugin.h25
-rw-r--r--src/output/solaris_output_plugin.c40
-rw-r--r--src/output/solaris_output_plugin.h25
-rw-r--r--src/output/winmm_output_plugin.c85
-rw-r--r--src/output/winmm_output_plugin.h10
-rw-r--r--src/output_all.c67
-rw-r--r--src/output_all.h7
-rw-r--r--src/output_api.h3
-rw-r--r--src/output_command.c6
-rw-r--r--src/output_command.h2
-rw-r--r--src/output_control.c73
-rw-r--r--src/output_control.h7
-rw-r--r--src/output_finish.c60
-rw-r--r--src/output_init.c171
-rw-r--r--src/output_internal.h32
-rw-r--r--src/output_list.c55
-rw-r--r--src/output_list.h2
-rw-r--r--src/output_plugin.c109
-rw-r--r--src/output_plugin.h145
-rw-r--r--src/output_print.c2
-rw-r--r--src/output_print.h2
-rw-r--r--src/output_state.c2
-rw-r--r--src/output_state.h2
-rw-r--r--src/output_thread.c44
-rw-r--r--src/output_thread.h2
-rw-r--r--src/page.c2
-rw-r--r--src/page.h2
-rw-r--r--src/path.c2
-rw-r--r--src/path.h2
-rw-r--r--src/pcm_buffer.c7
-rw-r--r--src/pcm_buffer.h4
-rw-r--r--src/pcm_byteswap.c70
-rw-r--r--src/pcm_byteswap.h50
-rw-r--r--src/pcm_channels.c118
-rw-r--r--src/pcm_channels.h14
-rw-r--r--src/pcm_convert.c208
-rw-r--r--src/pcm_convert.h20
-rw-r--r--src/pcm_dither.c12
-rw-r--r--src/pcm_dither.h8
-rw-r--r--src/pcm_dsd.c85
-rw-r--r--src/pcm_dsd.h52
-rw-r--r--src/pcm_dsd_usb.c97
-rw-r--r--src/pcm_dsd_usb.h (renamed from src/songvec.h)47
-rw-r--r--src/pcm_export.c135
-rw-r--r--src/pcm_export.h139
-rw-r--r--src/pcm_format.c479
-rw-r--r--src/pcm_format.h17
-rw-r--r--src/pcm_mix.c96
-rw-r--r--src/pcm_mix.h16
-rw-r--r--src/pcm_pack.c44
-rw-r--r--src/pcm_pack.h10
-rw-r--r--src/pcm_prng.h2
-rw-r--r--src/pcm_resample.c81
-rw-r--r--src/pcm_resample.h39
-rw-r--r--src/pcm_resample_fallback.c12
-rw-r--r--src/pcm_resample_internal.h30
-rw-r--r--src/pcm_resample_libsamplerate.c196
-rw-r--r--src/pcm_utils.h41
-rw-r--r--src/pcm_volume.c64
-rw-r--r--src/pcm_volume.h15
-rw-r--r--src/permission.c2
-rw-r--r--src/permission.h2
-rw-r--r--src/pipe.c2
-rw-r--r--src/pipe.h2
-rw-r--r--src/player_control.c268
-rw-r--r--src/player_control.h84
-rw-r--r--src/player_thread.c345
-rw-r--r--src/player_thread.h7
-rw-r--r--src/playlist.c75
-rw-r--r--src/playlist.h114
-rw-r--r--src/playlist/asx_playlist_plugin.c5
-rw-r--r--src/playlist/asx_playlist_plugin.h2
-rw-r--r--src/playlist/cue_playlist_plugin.c90
-rw-r--r--src/playlist/cue_playlist_plugin.h2
-rw-r--r--src/playlist/despotify_playlist_plugin.c217
-rw-r--r--src/playlist/despotify_playlist_plugin.h25
-rw-r--r--src/playlist/embcue_playlist_plugin.c181
-rw-r--r--src/playlist/embcue_playlist_plugin.h25
-rw-r--r--src/playlist/extm3u_playlist_plugin.c5
-rw-r--r--src/playlist/extm3u_playlist_plugin.h2
-rw-r--r--src/playlist/flac_playlist_plugin.c170
-rw-r--r--src/playlist/lastfm_playlist_plugin.c49
-rw-r--r--src/playlist/lastfm_playlist_plugin.h2
-rw-r--r--src/playlist/m3u_playlist_plugin.c2
-rw-r--r--src/playlist/m3u_playlist_plugin.h2
-rw-r--r--src/playlist/pls_playlist_plugin.c5
-rw-r--r--src/playlist/pls_playlist_plugin.h2
-rw-r--r--src/playlist/rss_playlist_plugin.c5
-rw-r--r--src/playlist/rss_playlist_plugin.h2
-rw-r--r--src/playlist/soundcloud_playlist_plugin.c448
-rw-r--r--src/playlist/soundcloud_playlist_plugin.h25
-rw-r--r--src/playlist/xspf_playlist_plugin.c5
-rw-r--r--src/playlist/xspf_playlist_plugin.h2
-rw-r--r--src/playlist_any.c17
-rw-r--r--src/playlist_any.h7
-rw-r--r--src/playlist_control.c83
-rw-r--r--src/playlist_database.c13
-rw-r--r--src/playlist_database.h8
-rw-r--r--src/playlist_edit.c167
-rw-r--r--src/playlist_error.h49
-rw-r--r--src/playlist_global.c5
-rw-r--r--src/playlist_internal.h8
-rw-r--r--src/playlist_list.c65
-rw-r--r--src/playlist_list.h9
-rw-r--r--src/playlist_mapper.c29
-rw-r--r--src/playlist_mapper.h7
-rw-r--r--src/playlist_plugin.h12
-rw-r--r--src/playlist_print.c24
-rw-r--r--src/playlist_print.h6
-rw-r--r--src/playlist_queue.c44
-rw-r--r--src/playlist_queue.h18
-rw-r--r--src/playlist_save.c26
-rw-r--r--src/playlist_save.h15
-rw-r--r--src/playlist_song.c24
-rw-r--r--src/playlist_song.h10
-rw-r--r--src/playlist_state.c60
-rw-r--r--src/playlist_state.h11
-rw-r--r--src/playlist_vector.c65
-rw-r--r--src/playlist_vector.h49
-rw-r--r--src/poison.h2
-rw-r--r--src/protocol/argparser.c191
-rw-r--r--src/protocol/argparser.h49
-rw-r--r--src/protocol/result.c57
-rw-r--r--src/protocol/result.h44
-rw-r--r--src/queue.c276
-rw-r--r--src/queue.h35
-rw-r--r--src/queue_print.c6
-rw-r--r--src/queue_print.h2
-rw-r--r--src/queue_save.c2
-rw-r--r--src/queue_save.h2
-rw-r--r--src/refcount.h3
-rw-r--r--src/replay_gain_ape.c2
-rw-r--r--src/replay_gain_ape.h2
-rw-r--r--src/replay_gain_config.c2
-rw-r--r--src/replay_gain_config.h2
-rw-r--r--src/replay_gain_info.c2
-rw-r--r--src/replay_gain_info.h2
-rw-r--r--src/resolver.c141
-rw-r--r--src/resolver.h64
-rw-r--r--src/riff.c4
-rw-r--r--src/riff.h2
-rw-r--r--src/rtsp_client.c732
-rw-r--r--src/rtsp_client.h124
-rw-r--r--src/server_socket.c96
-rw-r--r--src/server_socket.h16
-rw-r--r--src/sig_handlers.c2
-rw-r--r--src/sig_handlers.h2
-rw-r--r--src/socket_util.c75
-rw-r--r--src/socket_util.h20
-rw-r--r--src/song.c14
-rw-r--r--src/song.h23
-rw-r--r--src/song_print.c18
-rw-r--r--src/song_print.h5
-rw-r--r--src/song_save.c25
-rw-r--r--src/song_save.h8
-rw-r--r--src/song_sort.c (renamed from src/songvec.c)129
-rw-r--r--src/song_sort.h (renamed from src/directory_print.h)11
-rw-r--r--src/song_sticker.c2
-rw-r--r--src/song_sticker.h4
-rw-r--r--src/song_update.c81
-rw-r--r--src/state_file.c37
-rw-r--r--src/state_file.h8
-rw-r--r--src/stats.c19
-rw-r--r--src/stats.h2
-rw-r--r--src/sticker.c2
-rw-r--r--src/sticker.h4
-rw-r--r--src/sticker_print.c2
-rw-r--r--src/sticker_print.h2
-rw-r--r--src/stored_playlist.c328
-rw-r--r--src/stored_playlist.h39
-rw-r--r--src/string_util.c47
-rw-r--r--src/string_util.h77
-rw-r--r--src/strset.c2
-rw-r--r--src/strset.h2
-rw-r--r--src/tag.c4
-rw-r--r--src/tag.h10
-rw-r--r--src/tag_ape.c59
-rw-r--r--src/tag_ape.h14
-rw-r--r--src/tag_file.c90
-rw-r--r--src/tag_file.h37
-rw-r--r--src/tag_handler.c60
-rw-r--r--src/tag_handler.h101
-rw-r--r--src/tag_id3.c166
-rw-r--r--src/tag_id3.h20
-rw-r--r--src/tag_internal.h4
-rw-r--r--src/tag_pool.c2
-rw-r--r--src/tag_pool.h2
-rw-r--r--src/tag_print.c2
-rw-r--r--src/tag_print.h2
-rw-r--r--src/tag_rva2.c2
-rw-r--r--src/tag_rva2.h2
-rw-r--r--src/tag_save.c5
-rw-r--r--src/tag_save.h2
-rw-r--r--src/tag_table.h34
-rw-r--r--src/tcp_connect.c251
-rw-r--r--src/tcp_connect.h96
-rw-r--r--src/tcp_socket.c377
-rw-r--r--src/tcp_socket.h61
-rw-r--r--src/text_file.c2
-rw-r--r--src/text_file.h2
-rw-r--r--src/text_input_stream.c6
-rw-r--r--src/text_input_stream.h2
-rw-r--r--src/timer.c18
-rw-r--r--src/timer.h20
-rw-r--r--src/tokenizer.c9
-rw-r--r--src/tokenizer.h2
-rw-r--r--src/udp_server.c140
-rw-r--r--src/udp_server.h (renamed from src/dirvec.h)49
-rw-r--r--src/update.c13
-rw-r--r--src/update.h2
-rw-r--r--src/update_db.c104
-rw-r--r--src/update_db.h52
-rw-r--r--src/update_internal.h16
-rw-r--r--src/update_io.c116
-rw-r--r--src/update_io.h51
-rw-r--r--src/update_queue.c2
-rw-r--r--src/update_remove.c32
-rw-r--r--src/update_remove.h41
-rw-r--r--src/update_walk.c500
-rw-r--r--src/uri.c2
-rw-r--r--src/uri.h2
-rw-r--r--src/util/bit_reverse.c31
-rw-r--r--src/util/bit_reverse.h35
-rw-r--r--src/util/byte_reverse.c123
-rw-r--r--src/util/byte_reverse.h55
-rw-r--r--src/util/list.h608
-rw-r--r--src/util/list_sort.c162
-rw-r--r--src/util/list_sort.h33
-rw-r--r--src/utils.c71
-rw-r--r--src/utils.h17
-rw-r--r--src/volume.c2
-rw-r--r--src/volume.h2
-rw-r--r--src/win/mpd.icobin0 -> 353118 bytes
-rw-r--r--src/win/mpd_win32_rc.rc.in34
-rw-r--r--src/zeroconf-avahi.c2
-rw-r--r--src/zeroconf-bonjour.c2
-rw-r--r--src/zeroconf-internal.h2
-rw-r--r--src/zeroconf.c9
-rw-r--r--src/zeroconf.h2
-rw-r--r--test/dump_playlist.c130
-rw-r--r--test/dump_text_file.c170
-rw-r--r--test/read_conf.c2
-rw-r--r--test/read_mixer.c53
-rw-r--r--test/read_tags.c88
-rw-r--r--test/run_convert.c10
-rw-r--r--test/run_decoder.c26
-rw-r--r--test/run_encoder.c4
-rw-r--r--test/run_filter.c4
-rw-r--r--test/run_inotify.c2
-rw-r--r--test/run_input.c49
-rw-r--r--test/run_normalize.c2
-rw-r--r--test/run_ntp_server.c71
-rw-r--r--test/run_output.c156
-rw-r--r--test/run_resolver.c66
-rw-r--r--test/run_tcp_connect.c164
-rw-r--r--test/signals.c62
-rw-r--r--test/signals.h29
-rw-r--r--test/software_volume.c4
-rw-r--r--test/stdbin.h2
-rw-r--r--test/test_byte_reverse.c83
-rw-r--r--test/test_glib_compat.h60
-rw-r--r--test/test_pcm_all.h41
-rw-r--r--test/test_pcm_channels.c102
-rw-r--r--test/test_pcm_dither.c80
-rw-r--r--test/test_pcm_main.c37
-rw-r--r--test/test_pcm_pack.c90
-rw-r--r--test/test_queue_priority.c175
-rw-r--r--test/test_vorbis_encoder.c2
-rw-r--r--valgrind.suppressions710
586 files changed, 26217 insertions, 8741 deletions
diff --git a/.gitignore b/.gitignore
index 83a1bf623..67b261032 100644
--- a/.gitignore
+++ b/.gitignore
@@ -40,6 +40,7 @@ tags
*~
.#*
.stgit*
+src/dsd2pcm/dsd2pcm
doc/doxygen.conf
doc/protocol.html
doc/protocol
@@ -61,3 +62,8 @@ test/dump_playlist
test/run_normalize
test/tmp
test/run_inotify
+test/test_queue_priority
+test/run_ntp_server
+test/run_resolver
+test/run_tcp_connect
+test/test_pcm
diff --git a/INSTALL b/INSTALL
index 54ade434d..b07e1284c 100644
--- a/INSTALL
+++ b/INSTALL
@@ -84,11 +84,6 @@ For Ogg Vorbis support. You will need libogg and libvorbis.
FLAC - http://flac.sourceforge.net/
For FLAC support. You will need version 1.1.0 or higher of libflac.
-OggFLAC - http://www.xiph.org/ogg/vorbis/ and http://flac.sourceforge.net/
-For OggFLAC support. You will need liboggflac, which can be built from the
-FLAC sources if libogg is already installed. Versions of flac 1.1.3 and
-greater will automatically detect and use OggFLAC if it's available.
-
Audio File - http://www.68k.org/~michael/audiofile/
For WAVE, AIFF, and AU support. You will need libaudiofile.
@@ -101,7 +96,7 @@ For Musepack support.
MikMod - http://mikmod.raphnet.net/
For MOD support. You will need libmikmod.
-libavcodec, libavformat (ffmpeg) - http://ffmpeg.mplayerhq.hu/
+libavcodec, libavformat (ffmpeg or libav) - http://ffmpeg.mplayerhq.hu/ http://libav.org/
Multi-codec library.
libsidplay2 - http://sidplay2.sourceforge.net/
@@ -119,6 +114,9 @@ WAVE, AIFF, and many others.
libwavpack - http://www.wavpack.com/
For WavPack playback.
+despotify - https://github.com/SimonKagstrom/despotify
+For Spotify playback.
+
Optional Miscellaneous Dependencies
-----------------------------------
@@ -138,8 +136,11 @@ For playing MMS streams.
SQLite - http://www.sqlite.org/
For the sticker database.
-libcue - http://libcue.sourceforge.net/
-For CUE sheet support.
+libcdio - http://www.gnu.org/software/libcdio/
+For playing audio CDs.
+
+libsystemd-daemon - http://freedesktop.org/wiki/Software/systemd/
+For systemd activation.
pkg-config
diff --git a/Makefile.am b/Makefile.am
index ca7d8c717..38d08f098 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -1,5 +1,5 @@
ACLOCAL_AMFLAGS = -I m4
-AUTOMAKE_OPTIONS = foreign 1.10 dist-bzip2 subdir-objects
+AUTOMAKE_OPTIONS = foreign 1.11 dist-bzip2 subdir-objects
AM_CPPFLAGS += -I$(srcdir)/src $(GLIB_CFLAGS)
@@ -7,31 +7,36 @@ AM_CPPFLAGS += -DSYSTEM_CONFIG_FILE_LOCATION='"$(sysconfdir)/mpd.conf"'
bin_PROGRAMS = src/mpd
-noinst_LIBRARIES =
+noinst_LIBRARIES = \
+ libutil.a \
+ libpcm.a \
+ libtag.a \
+ libinput.a \
+ libplaylist_plugins.a \
+ libdecoder_plugins.a \
+ libfilter_plugins.a \
+ libmixer_plugins.a \
+ liboutput_plugins.a
-src_mpd_CFLAGS = $(AM_CFLAGS) $(MPD_CFLAGS)
src_mpd_CPPFLAGS = $(AM_CPPFLAGS) \
$(AVAHI_CFLAGS) \
$(LIBWRAP_CFLAGS) \
- $(SQLITE_CFLAGS) \
- $(ARCHIVE_CFLAGS) \
- $(INPUT_CFLAGS) \
- $(TAG_CFLAGS) \
- $(DECODER_CFLAGS) \
- $(ENCODER_CFLAGS) \
- $(FILTER_CFLAGS) \
- $(OUTPUT_CFLAGS)
-src_mpd_LDADD = $(MPD_LIBS) \
+ $(SQLITE_CFLAGS)
+src_mpd_LDADD = \
+ $(PLAYLIST_LIBS) \
$(AVAHI_LIBS) \
$(LIBWRAP_LDFLAGS) \
$(SQLITE_LIBS) \
- $(ARCHIVE_LIBS) \
+ $(DECODER_LIBS) \
$(INPUT_LIBS) \
+ $(ARCHIVE_LIBS) \
$(TAG_LIBS) \
- $(DECODER_LIBS) \
- $(ENCODER_LIBS) \
$(OUTPUT_LIBS) \
$(FILTER_LIBS) \
+ $(ENCODER_LIBS) \
+ $(MIXER_LIBS) \
+ libutil.a \
+ $(SYSTEMD_DAEMON_LIBS) \
$(GLIB_LIBS)
mpd_headers = \
@@ -39,13 +44,11 @@ mpd_headers = \
src/notify.h \
src/ack.h \
src/ape.h \
- src/audio.h \
src/audio_format.h \
src/audio_check.h \
src/audio_parser.h \
src/output_internal.h \
src/output_api.h \
- src/output_plugin.h \
src/output_list.h \
src/output_all.h \
src/output_thread.h \
@@ -78,7 +81,6 @@ mpd_headers = \
src/decoder_internal.h \
src/directory.h \
src/directory_save.h \
- src/directory_print.h \
src/database.h \
src/encoder_plugin.h \
src/encoder_list.h \
@@ -91,7 +93,6 @@ mpd_headers = \
src/inotify_source.h \
src/inotify_queue.h \
src/inotify_update.h \
- src/dirvec.h \
src/gcc.h \
src/decoder_list.h \
src/decoder_print.h \
@@ -100,6 +101,7 @@ mpd_headers = \
src/decoder/flac_pcm.h \
src/decoder/_flac_common.h \
src/decoder/_ogg_common.h \
+ src/decoder/pcm_decoder_plugin.h \
src/input_init.h \
src/input_plugin.h \
src/input_registry.h \
@@ -109,6 +111,9 @@ mpd_headers = \
src/input/curl_input_plugin.h \
src/input/rewind_input_plugin.h \
src/input/mms_input_plugin.h \
+ src/input/despotify_input_plugin.h \
+ src/input/cdio_paranoia_input_plugin.h \
+ src/despotify_utils.h \
src/text_file.h \
src/text_input_stream.h \
src/icy_server.h \
@@ -140,25 +145,12 @@ mpd_headers = \
src/open.h \
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_utils.h \
- src/pcm_convert.h \
- src/pcm_volume.h \
- src/pcm_mix.h \
- src/pcm_byteswap.h \
- src/pcm_channels.h \
- src/pcm_format.h \
- src/pcm_resample.h \
- src/pcm_resample_internal.h \
- src/pcm_dither.h \
- src/pcm_pack.h \
- src/pcm_prng.h \
src/permission.h \
src/player_thread.h \
src/player_control.h \
src/playlist.h \
+ src/playlist_error.h \
src/playlist_internal.h \
src/playlist_print.h \
src/playlist_save.h \
@@ -178,8 +170,8 @@ mpd_headers = \
src/playlist/asx_playlist_plugin.h \
src/playlist/rss_playlist_plugin.h \
src/playlist/lastfm_playlist_plugin.h \
+ src/playlist/despotify_playlist_plugin.h \
src/playlist/cue_playlist_plugin.h \
- src/playlist/flac_playlist_plugin.h \
src/poison.h \
src/riff.h \
src/aiff.h \
@@ -195,7 +187,7 @@ mpd_headers = \
src/song_print.h \
src/song_save.h \
src/song_sticker.h \
- src/songvec.h \
+ src/song_sort.c src/song_sort.h \
src/socket_util.h \
src/state_file.h \
src/stats.h \
@@ -214,6 +206,7 @@ mpd_headers = \
src/strset.h \
src/uri.h \
src/utils.h \
+ src/string_util.h \
src/volume.h \
src/zeroconf.h src/zeroconf-internal.h \
src/locate.h \
@@ -227,31 +220,27 @@ 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/mpd_error.h
src_mpd_SOURCES = \
$(mpd_headers) \
- $(ARCHIVE_SRC) \
- $(INPUT_SRC) \
- $(PLAYLIST_SRC) \
- $(TAG_SRC) \
$(DECODER_SRC) \
- $(ENCODER_SRC) \
- $(OUTPUT_API_SRC) $(OUTPUT_SRC) \
- $(MIXER_API_SRC) $(MIXER_SRC) \
- $(FILTER_SRC) \
+ $(OUTPUT_API_SRC) \
+ $(MIXER_API_SRC) \
src/glib_socket.h \
src/notify.c \
- src/audio.c \
+ src/audio_config.c src/audio_config.h \
src/audio_check.c \
src/audio_format.c \
src/audio_parser.c \
+ src/protocol/argparser.c src/protocol/argparser.h \
+ src/protocol/result.c src/protocol/result.h \
src/command.c \
src/idle.c \
src/cmdline.c \
src/conf.c \
src/crossfade.c \
+ src/cue/cue_parser.c src/cue/cue_parser.h \
src/dbUtils.c \
src/decoder_thread.c \
src/decoder_control.c \
@@ -260,9 +249,15 @@ src_mpd_SOURCES = \
src/decoder_print.c \
src/directory.c \
src/directory_save.c \
- src/directory_print.c \
src/database.c \
- src/dirvec.c \
+ src/db_error.h \
+ src/db_lock.c src/db_lock.h \
+ src/db_save.c src/db_save.h \
+ src/db_print.c src/db_print.h \
+ src/db_plugin.h \
+ src/db_visitor.h \
+ src/db_selection.h \
+ src/db/simple_db_plugin.c src/db/simple_db_plugin.h \
src/exclude.c \
src/fd_util.c \
src/fifo_buffer.c src/fifo_buffer.h \
@@ -272,22 +267,34 @@ src_mpd_SOURCES = \
src/filter_registry.c \
src/update.c \
src/update_queue.c \
+ src/update_io.c src/update_io.h \
+ src/update_db.c src/update_db.h \
src/update_walk.c \
src/update_remove.c \
src/client.c \
src/client_event.c \
src/client_expire.c \
src/client_global.c \
+ src/client_idle.h \
src/client_idle.c \
src/client_list.c \
src/client_new.c \
src/client_process.c \
src/client_read.c \
src/client_write.c \
+ src/client_message.h \
+ src/client_message.c \
+ src/client_subscribe.h \
+ src/client_subscribe.c \
+ src/client_file.c src/client_file.h \
+ src/tcp_connect.c src/tcp_connect.h \
+ src/tcp_socket.c src/tcp_socket.h \
+ src/udp_server.c src/udp_server.h \
src/server_socket.c \
src/listen.c \
src/log.c \
src/ls.c \
+ src/io_thread.c src/io_thread.h \
src/main.c \
src/main_win32.c \
src/event_pipe.c \
@@ -299,17 +306,6 @@ src_mpd_SOURCES = \
src/path.c \
src/mapper.c \
src/page.c \
- src/pcm_buffer.c src/pcm_buffer.h \
- src/pcm_convert.c \
- src/pcm_volume.c \
- src/pcm_mix.c \
- src/pcm_byteswap.c \
- src/pcm_channels.c \
- src/pcm_pack.c \
- src/pcm_format.c \
- src/pcm_resample.c \
- src/pcm_resample_fallback.c \
- src/pcm_dither.c \
src/permission.c \
src/player_thread.c \
src/player_control.c \
@@ -336,7 +332,7 @@ src_mpd_SOURCES = \
src/song_update.c \
src/song_print.c \
src/song_save.c \
- src/songvec.c \
+ src/resolver.c src/resolver.h \
src/socket_util.c \
src/state_file.c \
src/stats.c \
@@ -344,17 +340,39 @@ src_mpd_SOURCES = \
src/tag_pool.c \
src/tag_print.c \
src/tag_save.c \
+ src/tag_handler.c src/tag_handler.h \
+ src/tag_file.c src/tag_file.h \
src/tokenizer.c \
src/text_file.c \
src/text_input_stream.c \
src/strset.c \
src/uri.c \
src/utils.c \
+ src/string_util.c \
src/volume.c \
src/locate.c \
src/stored_playlist.c \
src/timer.c
+#
+# Windows resource file
+#
+
+src/win/mpd_win32_rc.$(OBJEXT): src/win/mpd_win32_rc.rc
+ $(WINDRES) -i $< -o $@
+
+if HAVE_WINDOWS
+noinst_DATA = src/win/mpd_win32_rc.rc
+
+src_mpd_DEPENDENCIES = src/win/mpd_win32_rc.$(OBJEXT)
+src_mpd_LDFLAGS = -Wl,src/win/mpd_win32_rc.$(OBJEXT)
+endif
+
+if ENABLE_DESPOTIFY
+src_mpd_SOURCES += \
+ src/despotify_utils.c
+endif
+
if ENABLE_INOTIFY
src_mpd_SOURCES += \
src/inotify_source.c \
@@ -369,69 +387,114 @@ src_mpd_SOURCES += \
src/song_sticker.c
endif
-FILTER_CFLAGS = \
+# Generic utility library
+
+libutil_a_SOURCES = \
+ src/util/list.h \
+ src/util/list_sort.c src/util/list_sort.h \
+ src/util/byte_reverse.c src/util/byte_reverse.h \
+ src/util/bit_reverse.c src/util/bit_reverse.h
+
+# PCM library
+
+libpcm_a_SOURCES = \
+ src/pcm_buffer.c src/pcm_buffer.h \
+ src/pcm_export.c src/pcm_export.h \
+ src/pcm_convert.c src/pcm_convert.h \
+ src/dsd2pcm/dsd2pcm.c src/dsd2pcm/dsd2pcm.h \
+ src/pcm_dsd.c src/pcm_dsd.h \
+ src/pcm_dsd_usb.c src/pcm_dsd_usb.h \
+ src/pcm_volume.c src/pcm_volume.h \
+ src/pcm_mix.c src/pcm_mix.h \
+ src/pcm_channels.c src/pcm_channels.h \
+ src/pcm_pack.c src/pcm_pack.h \
+ src/pcm_format.c src/pcm_format.h \
+ src/pcm_resample.c src/pcm_resample.h \
+ src/pcm_resample_fallback.c \
+ src/pcm_resample_internal.h \
+ src/pcm_dither.c src/pcm_dither.h \
+ src/pcm_prng.h \
+ src/pcm_utils.h
+libpcm_a_CPPFLAGS = $(AM_CPPFLAGS) \
$(SAMPLERATE_CFLAGS)
-FILTER_LIBS = \
+
+PCM_LIBS = \
+ libpcm.a \
$(SAMPLERATE_LIBS)
if HAVE_LIBSAMPLERATE
-src_mpd_SOURCES += src/pcm_resample_libsamplerate.c
+libpcm_a_SOURCES += src/pcm_resample_libsamplerate.c
endif
# archive plugins
-ARCHIVE_CFLAGS = \
+if ENABLE_ARCHIVE
+
+noinst_LIBRARIES += libarchive.a
+
+libarchive_a_SOURCES = \
+ src/archive_api.c \
+ src/archive_list.c \
+ src/archive_plugin.c \
+ src/input/archive_input_plugin.c
+libarchive_a_CPPFLAGS = $(AM_CPPFLAGS) \
+ $(BZ2_CFLAGS) \
$(ISO9660_CFLAGS) \
$(ZZIP_CFLAGS)
ARCHIVE_LIBS = \
+ libarchive.a \
+ $(BZ2_LIBS) \
$(ISO9660_LIBS) \
$(ZZIP_LIBS)
-ARCHIVE_SRC =
-
if HAVE_BZ2
-ARCHIVE_SRC += src/archive/bz2_archive_plugin.c
+libarchive_a_SOURCES += src/archive/bz2_archive_plugin.c
endif
if HAVE_ZZIP
-ARCHIVE_SRC += src/archive/zzip_archive_plugin.c
+libarchive_a_SOURCES += src/archive/zzip_archive_plugin.c
endif
if HAVE_ISO9660
-ARCHIVE_SRC += src/archive/iso9660_archive_plugin.c
+libarchive_a_SOURCES += src/archive/iso9660_archive_plugin.c
endif
-if ENABLE_ARCHIVE
-ARCHIVE_SRC += \
- src/archive_api.c \
- src/archive_list.c \
- src/archive_plugin.c \
- src/input/archive_input_plugin.c
+else
+ARCHIVE_LIBS =
endif
# tag plugins
-TAG_CFLAGS = \
+libtag_a_CPPFLAGS = $(AM_CPPFLAGS) \
$(ID3TAG_CFLAGS)
TAG_LIBS = \
+ libtag.a \
$(ID3TAG_LIBS)
-TAG_SRC = \
+libtag_a_SOURCES =\
src/ape.c \
src/replay_gain_ape.c \
src/tag_ape.c
if HAVE_ID3TAG
-TAG_SRC += src/tag_id3.c \
+libtag_a_SOURCES += \
+ src/tag_id3.c \
src/tag_rva2.c \
src/riff.c src/aiff.c
endif
# decoder plugins
-DECODER_CFLAGS = \
+libdecoder_plugins_a_SOURCES = \
+ src/decoder/pcm_decoder_plugin.c \
+ src/decoder/dsdiff_decoder_plugin.c \
+ src/decoder/dsdiff_decoder_plugin.h \
+ src/decoder_buffer.c \
+ src/decoder_plugin.c \
+ src/decoder_list.c
+libdecoder_plugins_a_CPPFLAGS = $(AM_CPPFLAGS) \
$(VORBIS_CFLAGS) $(TREMOR_CFLAGS) \
$(patsubst -I%/FLAC,-I%,$(FLAC_CFLAGS)) \
$(SNDFILE_CFLAGS) \
@@ -445,9 +508,11 @@ DECODER_CFLAGS = \
$(MAD_CFLAGS) \
$(MPG123_CFLAGS) \
$(FFMPEG_CFLAGS) \
- $(CUE_CFLAGS)
+ $(MPCDEC_CFLAGS) \
+ $(FAAD_CFLAGS)
DECODER_LIBS = \
+ libdecoder_plugins.a \
$(VORBIS_LIBS) $(TREMOR_LIBS) \
$(FLAC_LIBS) \
$(SNDFILE_LIBS) \
@@ -461,66 +526,63 @@ DECODER_LIBS = \
$(MPG123_LIBS) \
$(MP4FF_LIBS) \
$(FFMPEG_LIBS) \
- $(CUE_LIBS)
+ $(MPCDEC_LIBS) \
+ $(FAAD_LIBS)
-DECODER_SRC = \
- src/decoder_buffer.c \
- src/decoder_plugin.c \
- src/decoder_list.c
+DECODER_SRC =
if HAVE_MAD
-DECODER_SRC += src/decoder/mad_decoder_plugin.c
+libdecoder_plugins_a_SOURCES += src/decoder/mad_decoder_plugin.c
endif
if HAVE_MPG123
-DECODER_SRC += src/decoder/mpg123_decoder_plugin.c
+libdecoder_plugins_a_SOURCES += src/decoder/mpg123_decoder_plugin.c
endif
if HAVE_MPCDEC
-DECODER_SRC += src/decoder/mpcdec_decoder_plugin.c
+libdecoder_plugins_a_SOURCES += src/decoder/mpcdec_decoder_plugin.c
endif
if HAVE_WAVPACK
-DECODER_SRC += src/decoder/wavpack_decoder_plugin.c
+libdecoder_plugins_a_SOURCES += src/decoder/wavpack_decoder_plugin.c
endif
if HAVE_FAAD
-DECODER_SRC += src/decoder/faad_decoder_plugin.c
+libdecoder_plugins_a_SOURCES += src/decoder/faad_decoder_plugin.c
endif
if HAVE_MP4
-DECODER_SRC += src/decoder/mp4ff_decoder_plugin.c
+libdecoder_plugins_a_SOURCES += src/decoder/mp4ff_decoder_plugin.c
endif
if HAVE_OGG_COMMON
-DECODER_SRC += src/decoder/_ogg_common.c
+libdecoder_plugins_a_SOURCES += src/decoder/_ogg_common.c
endif
if HAVE_FLAC_COMMON
-DECODER_SRC += \
+libdecoder_plugins_a_SOURCES += \
src/decoder/flac_metadata.c \
src/decoder/flac_pcm.c \
src/decoder/_flac_common.c
endif
if ENABLE_VORBIS_DECODER
-DECODER_SRC += src/decoder/vorbis_decoder_plugin.c
+libdecoder_plugins_a_SOURCES += \
+ src/decoder/vorbis_comments.c \
+ src/decoder/vorbis_comments.h \
+ src/decoder/vorbis_decoder_plugin.c
endif
if HAVE_FLAC
-DECODER_SRC += src/decoder/flac_decoder_plugin.c
-endif
-
-if HAVE_OGGFLAC
-DECODER_SRC += src/decoder/oggflac_decoder_plugin.c
+libdecoder_plugins_a_SOURCES += src/decoder/flac_decoder_plugin.c
endif
if HAVE_AUDIOFILE
-DECODER_SRC += src/decoder/audiofile_decoder_plugin.c
+libdecoder_plugins_a_SOURCES += src/decoder/audiofile_decoder_plugin.c
endif
if ENABLE_MIKMOD_DECODER
-DECODER_SRC += src/decoder/mikmod_decoder_plugin.c
+libdecoder_plugins_a_SOURCES += src/decoder/mikmod_decoder_plugin.c
endif
if HAVE_MODPLUG
@@ -532,68 +594,79 @@ DECODER_LIBS += libmodplug_decoder_plugin.a $(MODPLUG_LIBS)
endif
if ENABLE_SIDPLAY
-DECODER_SRC += src/decoder/sidplay_decoder_plugin.cxx
+libdecoder_plugins_a_SOURCES += src/decoder/sidplay_decoder_plugin.cxx
+DECODER_SRC += src/dummy.cxx
endif
if ENABLE_FLUIDSYNTH
-DECODER_SRC += src/decoder/fluidsynth_decoder_plugin.c
+libdecoder_plugins_a_SOURCES += src/decoder/fluidsynth_decoder_plugin.c
endif
if ENABLE_WILDMIDI
-DECODER_SRC += src/decoder/wildmidi_decoder_plugin.c
+libdecoder_plugins_a_SOURCES += src/decoder/wildmidi_decoder_plugin.c
endif
if HAVE_FFMPEG
-DECODER_SRC += src/decoder/ffmpeg_decoder_plugin.c
+libdecoder_plugins_a_SOURCES += \
+ src/decoder/ffmpeg_metadata.c \
+ src/decoder/ffmpeg_metadata.h \
+ src/decoder/ffmpeg_decoder_plugin.c
endif
if ENABLE_SNDFILE
-DECODER_SRC += src/decoder/sndfile_decoder_plugin.c
+libdecoder_plugins_a_SOURCES += src/decoder/sndfile_decoder_plugin.c
endif
if HAVE_GME
-DECODER_SRC += src/decoder/gme_decoder_plugin.c
+libdecoder_plugins_a_SOURCES += src/decoder/gme_decoder_plugin.c
endif
# encoder plugins
-ENCODER_CFLAGS = \
+if ENABLE_ENCODER
+
+noinst_LIBRARIES += libencoder_plugins.a
+
+libencoder_plugins_a_CPPFLAGS = $(AM_CPPFLAGS) \
$(LAME_CFLAGS) \
$(TWOLAME_CFLAGS) \
$(patsubst -I%/FLAC,-I%,$(FLAC_CFLAGS)) \
$(VORBISENC_CFLAGS)
ENCODER_LIBS = \
+ libencoder_plugins.a \
$(LAME_LIBS) \
$(TWOLAME_LIBS) \
$(FLAC_LIBS) \
$(VORBISENC_LIBS)
-ENCODER_SRC =
+libencoder_plugins_a_SOURCES =
-if ENABLE_ENCODER
-ENCODER_SRC += src/encoder_list.c
-ENCODER_SRC += src/encoder/null_encoder.c
+libencoder_plugins_a_SOURCES += src/encoder_list.c
+libencoder_plugins_a_SOURCES += src/encoder/null_encoder.c
if ENABLE_WAVE_ENCODER
-ENCODER_SRC += src/encoder/wave_encoder.c
+libencoder_plugins_a_SOURCES += src/encoder/wave_encoder.c
endif
if ENABLE_VORBIS_ENCODER
-ENCODER_SRC += src/encoder/vorbis_encoder.c
+libencoder_plugins_a_SOURCES += src/encoder/vorbis_encoder.c
endif
if ENABLE_LAME_ENCODER
-ENCODER_SRC += src/encoder/lame_encoder.c
+libencoder_plugins_a_SOURCES += src/encoder/lame_encoder.c
endif
if ENABLE_TWOLAME_ENCODER
-ENCODER_SRC += src/encoder/twolame_encoder.c
+libencoder_plugins_a_SOURCES += src/encoder/twolame_encoder.c
endif
if ENABLE_FLAC_ENCODER
-ENCODER_SRC += src/encoder/flac_encoder.c
+libencoder_plugins_a_SOURCES += src/encoder/flac_encoder.c
endif
+
+else
+ENCODER_LIBS =
endif
@@ -609,58 +682,79 @@ src_mpd_SOURCES += src/zeroconf-bonjour.c
endif
endif
-if HAVE_CUE
-DECODER_SRC += src/cue/cue_tag.c
-endif
-
#
# input plugins
#
-INPUT_CFLAGS = \
+libinput_a_SOURCES = \
+ src/input_init.c \
+ src/input_registry.c \
+ src/input_stream.c \
+ src/input_internal.c src/input_internal.h \
+ src/input/rewind_input_plugin.c \
+ src/input/file_input_plugin.c
+
+libinput_a_CPPFLAGS = $(AM_CPPFLAGS) \
$(CURL_CFLAGS) \
+ $(SOUP_CFLAGS) \
+ $(CDIO_PARANOIA_CFLAGS) \
$(FFMPEG_CFLAGS) \
+ $(DESPOTIFY_CFLAGS) \
$(MMS_CFLAGS)
INPUT_LIBS = \
+ libinput.a \
$(CURL_LIBS) \
+ $(SOUP_LIBS) \
+ $(CDIO_PARANOIA_LIBS) \
$(FFMPEG_LIBS) \
+ $(DESPOTIFY_LIBS) \
$(MMS_LIBS)
-INPUT_SRC = \
- src/input_init.c \
- src/input_registry.c \
- src/input_stream.c \
- src/input/rewind_input_plugin.c \
- src/input/file_input_plugin.c
-
if ENABLE_CURL
-INPUT_SRC += src/input/curl_input_plugin.c \
+libinput_a_SOURCES += src/input/curl_input_plugin.c \
src/icy_metadata.c
endif
+if ENABLE_SOUP
+libinput_a_SOURCES += \
+ src/input/soup_input_plugin.c \
+ src/input/soup_input_plugin.h
+endif
+
+if ENABLE_CDIO_PARANOIA
+libinput_a_SOURCES += src/input/cdio_paranoia_input_plugin.c
+endif
+
if HAVE_FFMPEG
-INPUT_SRC += src/input/ffmpeg_input_plugin.c
+libinput_a_SOURCES += src/input/ffmpeg_input_plugin.c
endif
if ENABLE_MMS
-INPUT_SRC += src/input/mms_input_plugin.c
+libinput_a_SOURCES += src/input/mms_input_plugin.c
+endif
+
+if ENABLE_DESPOTIFY
+libinput_a_SOURCES += src/input/despotify_input_plugin.c
endif
-OUTPUT_CFLAGS = \
+liboutput_plugins_a_CPPFLAGS = $(AM_CPPFLAGS) \
$(AO_CFLAGS) \
$(ALSA_CFLAGS) \
$(FFADO_CFLAGS) \
$(JACK_CFLAGS) \
$(OPENAL_CFLAGS) \
+ $(OPENSSL_CFLAGS) \
$(PULSE_CFLAGS) \
$(SHOUT_CFLAGS)
OUTPUT_LIBS = \
+ liboutput_plugins.a \
$(LIBWRAP_LDFLAGS) \
$(AO_LIBS) \
$(ALSA_LIBS) \
+ $(ROAR_LIBS) \
$(FFADO_LIBS) \
$(JACK_LIBS) \
$(OPENAL_LIBS) \
@@ -675,10 +769,16 @@ OUTPUT_API_SRC = \
src/output_state.c \
src/output_print.c \
src/output_command.c \
+ src/output_plugin.c src/output_plugin.h \
+ src/output_finish.c \
src/output_init.c
-OUTPUT_SRC = \
- src/output/null_plugin.c
+liboutput_plugins_a_SOURCES = \
+ src/output/null_output_plugin.c
+
+MIXER_LIBS = \
+ libmixer_plugins.a \
+ $(PULSE_LIBS)
MIXER_API_SRC = \
src/mixer_control.c \
@@ -686,78 +786,99 @@ MIXER_API_SRC = \
src/mixer_all.c \
src/mixer_api.c
-MIXER_SRC = \
+libmixer_plugins_a_SOURCES = \
src/mixer/software_mixer_plugin.c
+libmixer_plugins_a_CPPFLAGS = $(AM_CPPFLAGS) \
+ $(ALSA_CFLAGS) \
+ $(PULSE_CFLAGS)
if HAVE_ALSA
-OUTPUT_SRC += src/output/alsa_plugin.c
-MIXER_SRC += src/mixer/alsa_mixer_plugin.c
+liboutput_plugins_a_SOURCES += \
+ src/output/alsa_output_plugin.c src/output/alsa_output_plugin.h
+libmixer_plugins_a_SOURCES += src/mixer/alsa_mixer_plugin.c
+endif
+
+if HAVE_ROAR
+liboutput_plugins_a_SOURCES += \
+ src/output/roar_output_plugin.c src/output/roar_output_plugin.h
+libmixer_plugins_a_SOURCES += src/mixer/roar_mixer_plugin.c
endif
if ENABLE_FFADO_OUTPUT
-OUTPUT_SRC += src/output/ffado_output_plugin.c
+liboutput_plugins_a_SOURCES += src/output/ffado_output_plugin.c
endif
if HAVE_AO
-OUTPUT_SRC += src/output/ao_plugin.c
+liboutput_plugins_a_SOURCES += src/output/ao_output_plugin.c
endif
if HAVE_FIFO
-OUTPUT_SRC += src/output/fifo_output_plugin.c
+liboutput_plugins_a_SOURCES += src/output/fifo_output_plugin.c
endif
if ENABLE_PIPE_OUTPUT
-OUTPUT_SRC += src/output/pipe_output_plugin.c
+liboutput_plugins_a_SOURCES += src/output/pipe_output_plugin.c
endif
if HAVE_JACK
-OUTPUT_SRC += src/output/jack_output_plugin.c
+liboutput_plugins_a_SOURCES += src/output/jack_output_plugin.c
endif
if HAVE_MVP
-OUTPUT_SRC += src/output/mvp_plugin.c
+liboutput_plugins_a_SOURCES += src/output/mvp_output_plugin.c
endif
if HAVE_OSS
-OUTPUT_SRC += src/output/oss_plugin.c
-MIXER_SRC += src/mixer/oss_mixer_plugin.c
+liboutput_plugins_a_SOURCES += src/output/oss_output_plugin.c
+libmixer_plugins_a_SOURCES += src/mixer/oss_mixer_plugin.c
endif
if HAVE_OPENAL
-OUTPUT_SRC += src/output/openal_plugin.c
+liboutput_plugins_a_SOURCES += src/output/openal_output_plugin.c
endif
if HAVE_OSX
-OUTPUT_SRC += src/output/osx_plugin.c
+liboutput_plugins_a_SOURCES += src/output/osx_output_plugin.c
+endif
+
+if ENABLE_RAOP_OUTPUT
+liboutput_plugins_a_SOURCES += \
+ src/ntp_server.c src/ntp_server.h \
+ src/rtsp_client.c src/rtsp_client.h \
+ src/output/raop_output_plugin.c
+libmixer_plugins_a_SOURCES += src/mixer/raop_mixer_plugin.c
+OUTPUT_LIBS += $(OPENSSL_LIBS)
endif
if HAVE_PULSE
-OUTPUT_SRC += src/output/pulse_output_plugin.c
-MIXER_SRC += src/mixer/pulse_mixer_plugin.c
+liboutput_plugins_a_SOURCES += \
+ src/output/pulse_output_plugin.c src/output/pulse_output_plugin.h
+libmixer_plugins_a_SOURCES += src/mixer/pulse_mixer_plugin.c
endif
if HAVE_SHOUT
-OUTPUT_SRC += src/output/shout_plugin.c
+liboutput_plugins_a_SOURCES += src/output/shout_output_plugin.c
endif
if ENABLE_RECORDER_OUTPUT
-OUTPUT_SRC += src/output/recorder_output_plugin.c
+liboutput_plugins_a_SOURCES += src/output/recorder_output_plugin.c
endif
if ENABLE_HTTPD_OUTPUT
-OUTPUT_SRC += \
+liboutput_plugins_a_SOURCES += \
src/icy_server.c \
src/output/httpd_client.c \
src/output/httpd_output_plugin.c
endif
if ENABLE_SOLARIS_OUTPUT
-OUTPUT_SRC += src/output/solaris_output_plugin.c
+liboutput_plugins_a_SOURCES += src/output/solaris_output_plugin.c
endif
if ENABLE_WINMM_OUTPUT
-OUTPUT_SRC += src/output/winmm_output_plugin.c
-MIXER_SRC += src/mixer/winmm_mixer_plugin.c
+liboutput_plugins_a_SOURCES += \
+ src/output/winmm_output_plugin.c src/output/winmm_output_plugin.h
+libmixer_plugins_a_SOURCES += src/mixer/winmm_mixer_plugin.c
endif
@@ -765,33 +886,45 @@ endif
# Playlist plugins
#
-PLAYLIST_SRC = \
+libplaylist_plugins_a_SOURCES = \
src/playlist/extm3u_playlist_plugin.c \
src/playlist/m3u_playlist_plugin.c \
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/cue_playlist_plugin.c \
+ src/playlist/embcue_playlist_plugin.c \
+ src/playlist/embcue_playlist_plugin.h \
src/playlist_list.c
+libplaylist_plugins_a_CPPFLAGS = $(AM_CPPFLAGS) \
+ $(YAJL_CFLAGS) \
+ $(patsubst -I%/FLAC,-I%,$(FLAC_CFLAGS))
+
+PLAYLIST_LIBS = \
+ libplaylist_plugins.a \
+ $(FLAC_LIBS)
if ENABLE_LASTFM
-PLAYLIST_SRC += src/playlist/lastfm_playlist_plugin.c
+libplaylist_plugins_a_SOURCES += src/playlist/lastfm_playlist_plugin.c
endif
-if HAVE_CUE
-PLAYLIST_SRC += src/playlist/cue_playlist_plugin.c
+if ENABLE_DESPOTIFY
+libplaylist_plugins_a_SOURCES += src/playlist/despotify_playlist_plugin.c
endif
-if HAVE_FLAC
-PLAYLIST_SRC += src/playlist/flac_playlist_plugin.c
+if ENABLE_SOUNDCLOUD
+libplaylist_plugins_a_SOURCES += \
+ src/playlist/soundcloud_playlist_plugin.h \
+ src/playlist/soundcloud_playlist_plugin.c
+PLAYLIST_LIBS += $(YAJL_LIBS)
endif
-
#
# Filter plugins
#
-FILTER_SRC = \
+libfilter_plugins_a_SOURCES = \
src/filter/null_filter_plugin.c \
src/filter/chain_filter_plugin.c \
src/filter/autoconvert_filter_plugin.c \
@@ -801,6 +934,10 @@ FILTER_SRC = \
src/filter/replay_gain_filter_plugin.c \
src/filter/volume_filter_plugin.c
+FILTER_LIBS = \
+ libfilter_plugins.a \
+ $(PCM_LIBS)
+
#
# systemd unit
@@ -825,8 +962,9 @@ SPARSE_CPPFLAGS = $(DEFAULT_INCLUDES) \
-I$(shell $(CC) -print-file-name=include-fixed)
SPARSE_CPPFLAGS += -D__SCHAR_MAX__=127 -D__SHRT_MAX__=32767 \
-D__INT_MAX__=2147483647 -D__LONG_MAX__=2147483647
+SPARSE_SRC = $(addprefix $(top_srcdir)/,$(filter %.c,$(src_mpd_SOURCES)))
sparse-check:
- $(SPARSE) -I. $(src_mpd_CFLAGS) $(src_mpd_CPPFLAGS) $(SPARSE_FLAGS) $(SPARSE_CPPFLAGS) $(filter-out %.cxx,$(src_mpd_SOURCES))
+ $(SPARSE) -I. $(src_mpd_CFLAGS) $(src_mpd_CPPFLAGS) $(SPARSE_FLAGS) $(SPARSE_CPPFLAGS) $(SPARSE_SRC)
.PHONY: sparse-check
@@ -837,14 +975,24 @@ sparse-check:
if ENABLE_TEST
-TESTS =
+C_TESTS = \
+ test/test_byte_reverse \
+ test/test_pcm \
+ test/test_queue_priority
+
+TESTS = $(C_TESTS)
noinst_PROGRAMS = \
+ $(C_TESTS) \
test/read_conf \
+ test/run_resolver \
+ test/run_tcp_connect \
test/run_input \
+ test/dump_text_file \
test/dump_playlist \
test/run_decoder \
test/read_tags \
+ test/run_ntp_server \
test/run_filter \
test/run_output \
test/run_convert \
@@ -856,52 +1004,68 @@ if HAVE_ALSA
noinst_PROGRAMS += test/read_mixer
endif
-test_read_conf_CPPFLAGS = $(AM_CPPFLAGS) \
- $(GLIB_CFLAGS)
-test_read_conf_LDADD = $(MPD_LIBS) \
+test_read_conf_LDADD = \
$(GLIB_LIBS)
test_read_conf_SOURCES = test/read_conf.c \
- src/conf.c src/tokenizer.c src/utils.c
+ src/conf.c src/tokenizer.c src/utils.c src/string_util.c
-test_run_input_CPPFLAGS = $(AM_CPPFLAGS) \
- $(ARCHIVE_CFLAGS) \
- $(INPUT_CFLAGS)
-test_run_input_LDADD = $(MPD_LIBS) \
- $(ARCHIVE_LIBS) \
+test_run_resolver_LDADD = \
+ $(GLIB_LIBS)
+test_run_resolver_SOURCES = test/run_resolver.c \
+ src/resolver.c
+
+test_run_tcp_connect_LDADD = \
+ $(GLIB_LIBS)
+test_run_tcp_connect_SOURCES = test/run_tcp_connect.c \
+ src/io_thread.c src/io_thread.h \
+ src/fd_util.c \
+ src/resolver.c \
+ src/tcp_connect.c
+
+test_run_input_LDADD = \
$(INPUT_LIBS) \
+ $(ARCHIVE_LIBS) \
$(GLIB_LIBS)
test_run_input_SOURCES = test/run_input.c \
test/stdbin.h \
- src/conf.c src/tokenizer.c src/utils.c \
+ src/io_thread.c src/io_thread.h \
+ src/conf.c src/tokenizer.c src/utils.c src/string_util.c\
src/tag.c src/tag_pool.c src/tag_save.c \
- src/fd_util.c \
- $(ARCHIVE_SRC) \
- $(INPUT_SRC)
+ src/fd_util.c
-test_dump_playlist_CPPFLAGS = $(AM_CPPFLAGS) \
- $(CUE_CFLAGS) \
- $(patsubst -I%/FLAC,-I%,$(FLAC_CFLAGS)) \
- $(ARCHIVE_CFLAGS) \
- $(INPUT_CFLAGS)
-test_dump_playlist_LDADD = $(MPD_LIBS) \
- $(CUE_LIBS) \
- $(FLAC_LIBS) \
+test_dump_text_file_LDADD = \
+ $(INPUT_LIBS) \
$(ARCHIVE_LIBS) \
+ $(GLIB_LIBS)
+test_dump_text_file_SOURCES = test/dump_text_file.c \
+ test/stdbin.h \
+ src/io_thread.c src/io_thread.h \
+ src/conf.c src/tokenizer.c src/utils.c src/string_util.c\
+ src/tag.c src/tag_pool.c \
+ src/text_input_stream.c src/fifo_buffer.c \
+ src/fd_util.c
+
+test_dump_playlist_LDADD = \
+ $(PLAYLIST_LIBS) \
+ $(FLAC_LIBS) \
$(INPUT_LIBS) \
+ $(ARCHIVE_LIBS) \
+ $(DECODER_LIBS) \
+ $(TAG_LIBS) \
+ libutil.a \
$(GLIB_LIBS)
test_dump_playlist_SOURCES = test/dump_playlist.c \
- src/conf.c src/tokenizer.c src/utils.c \
+ $(DECODER_SRC) \
+ src/io_thread.c src/io_thread.h \
+ src/conf.c src/tokenizer.c src/utils.c src/string_util.c\
src/uri.c \
src/song.c src/tag.c src/tag_pool.c src/tag_save.c \
+ src/tag_handler.c src/tag_file.c \
+ src/audio_check.c src/pcm_buffer.c \
src/text_input_stream.c src/fifo_buffer.c \
- src/fd_util.c \
- $(ARCHIVE_SRC) \
- $(INPUT_SRC) \
- $(PLAYLIST_SRC)
-
-if HAVE_CUE
-test_dump_playlist_SOURCES += src/cue/cue_tag.c
-endif
+ src/cue/cue_parser.c src/cue/cue_parser.h \
+ src/timer.c \
+ src/fd_util.c
if HAVE_FLAC
test_dump_playlist_SOURCES += \
@@ -909,97 +1073,100 @@ test_dump_playlist_SOURCES += \
src/decoder/flac_metadata.c
endif
-test_run_decoder_CPPFLAGS = $(AM_CPPFLAGS) \
- $(TAG_CFLAGS) \
- $(ARCHIVE_CFLAGS) \
- $(INPUT_CFLAGS) $(DECODER_CFLAGS)
-test_run_decoder_LDADD = $(MPD_LIBS) \
- $(TAG_LIBS) \
+test_run_decoder_LDADD = \
+ $(DECODER_LIBS) \
+ libpcm.a \
+ $(INPUT_LIBS) \
$(ARCHIVE_LIBS) \
- $(INPUT_LIBS) $(DECODER_LIBS) \
+ $(TAG_LIBS) \
+ libutil.a \
$(GLIB_LIBS)
test_run_decoder_SOURCES = test/run_decoder.c \
test/stdbin.h \
- src/conf.c src/tokenizer.c src/utils.c src/log.c \
- src/tag.c src/tag_pool.c \
+ src/io_thread.c src/io_thread.h \
+ src/conf.c src/tokenizer.c src/utils.c src/string_util.c src/log.c \
+ src/tag.c src/tag_pool.c src/tag_handler.c \
src/replay_gain_info.c \
src/uri.c \
src/fd_util.c \
src/audio_check.c \
src/audio_format.c \
src/timer.c \
- src/pcm_buffer.c \
$(ARCHIVE_SRC) \
$(INPUT_SRC) \
$(TAG_SRC) \
$(DECODER_SRC)
-test_read_tags_CPPFLAGS = $(AM_CPPFLAGS) \
- $(TAG_CFLAGS) \
- $(ARCHIVE_CFLAGS) \
- $(INPUT_CFLAGS) $(DECODER_CFLAGS)
-test_read_tags_LDADD = $(MPD_LIBS) \
- $(TAG_LIBS) \
+test_read_tags_LDADD = \
+ $(DECODER_LIBS) \
+ libpcm.a \
+ $(INPUT_LIBS) \
$(ARCHIVE_LIBS) \
- $(INPUT_LIBS) $(DECODER_LIBS) \
+ $(TAG_LIBS) \
+ libutil.a \
$(GLIB_LIBS)
test_read_tags_SOURCES = test/read_tags.c \
- src/conf.c src/tokenizer.c src/utils.c src/log.c \
- src/tag.c src/tag_pool.c \
+ src/io_thread.c src/io_thread.h \
+ src/conf.c src/tokenizer.c src/utils.c src/string_util.c src/log.c \
+ src/tag.c src/tag_pool.c src/tag_handler.c \
src/replay_gain_info.c \
src/uri.c \
src/fd_util.c \
src/audio_check.c \
src/timer.c \
- src/pcm_buffer.c \
- $(ARCHIVE_SRC) \
- $(INPUT_SRC) \
- $(TAG_SRC) \
$(DECODER_SRC)
-test_run_filter_CPPFLAGS = $(AM_CPPFLAGS)
-test_run_filter_LDADD = $(MPD_LIBS) \
- $(SAMPLERATE_LIBS) \
+test_run_ntp_server_LDADD = \
+ $(GLIB_LIBS)
+test_run_ntp_server_SOURCES = test/run_ntp_server.c \
+ test/signals.c test/signals.h \
+ src/io_thread.c src/io_thread.h \
+ src/udp_server.c src/udp_server.h \
+ src/ntp_server.c src/ntp_server.h
+
+test_run_filter_LDADD = \
+ $(FILTER_LIBS) \
$(GLIB_LIBS)
test_run_filter_SOURCES = test/run_filter.c \
test/stdbin.h \
src/filter_plugin.c \
src/filter_registry.c \
- src/conf.c src/tokenizer.c src/utils.c \
- src/pcm_volume.c src/pcm_convert.c src/pcm_byteswap.c \
- src/pcm_format.c src/pcm_channels.c src/pcm_dither.c \
- src/pcm_pack.c \
- src/pcm_resample.c src/pcm_resample_fallback.c \
- src/pcm_buffer.c \
+ src/conf.c src/tokenizer.c src/utils.c src/string_util.c \
src/audio_check.c \
src/audio_format.c \
src/audio_parser.c \
src/replay_gain_config.c \
src/replay_gain_info.c \
- src/AudioCompress/compress.c \
- $(FILTER_SRC)
+ src/AudioCompress/compress.c
-if HAVE_LIBSAMPLERATE
-test_run_filter_SOURCES += src/pcm_resample_libsamplerate.c
+if ENABLE_DESPOTIFY
+test_read_tags_SOURCES += \
+ src/despotify_utils.c
+test_run_input_SOURCES += \
+ src/despotify_utils.c
+test_dump_text_file_SOURCES += \
+ src/despotify_utils.c
+test_dump_playlist_SOURCES += \
+ src/despotify_utils.c
+test_run_decoder_SOURCES += \
+ src/despotify_utils.c
endif
if ENABLE_ENCODER
noinst_PROGRAMS += test/run_encoder
test_run_encoder_SOURCES = test/run_encoder.c \
test/stdbin.h \
+ src/fifo_buffer.c src/growing_fifo.c \
src/conf.c src/tokenizer.c \
- src/utils.c \
+ src/utils.c src/string_util.c \
src/tag.c src/tag_pool.c \
src/audio_check.c \
src/audio_format.c \
- src/audio_parser.c \
- src/pcm_buffer.c \
- src/fifo_buffer.c src/growing_fifo.c \
- $(ENCODER_SRC)
-test_run_encoder_CPPFLAGS = $(AM_CPPFLAGS) \
- $(ENCODER_CFLAGS)
-test_run_encoder_LDADD = $(MPD_LIBS) \
+ src/audio_parser.c
+test_run_encoder_LDADD = \
$(ENCODER_LIBS) \
+ libpcm.a \
+ $(TAG_LIBS) \
$(GLIB_LIBS)
endif
@@ -1009,6 +1176,7 @@ test_test_vorbis_encoder_SOURCES = test/test_vorbis_encoder.c \
test/stdbin.h \
src/conf.c src/tokenizer.c \
src/utils.c \
+ src/string_util.c \
src/tag.c src/tag_pool.c \
src/audio_check.c \
src/audio_format.c \
@@ -1026,9 +1194,9 @@ endif
test_software_volume_SOURCES = test/software_volume.c \
test/stdbin.h \
src/audio_check.c \
- src/audio_parser.c \
- src/pcm_volume.c
+ src/audio_parser.c
test_software_volume_LDADD = \
+ $(PCM_LIBS) \
$(GLIB_LIBS)
test_run_normalize_SOURCES = test/run_normalize.c \
@@ -1040,39 +1208,29 @@ test_run_normalize_LDADD = \
$(GLIB_LIBS)
test_run_convert_SOURCES = test/run_convert.c \
+ src/dsd2pcm/dsd2pcm.c \
src/fifo_buffer.c \
src/audio_format.c \
src/audio_check.c \
- src/audio_parser.c \
- src/pcm_buffer.c \
- src/pcm_channels.c \
- src/pcm_format.c \
- src/pcm_pack.c \
- src/pcm_dither.c \
- src/pcm_byteswap.c \
- src/pcm_resample.c \
- src/pcm_resample_fallback.c \
- src/pcm_convert.c
-test_run_convert_CPPFLAGS = $(AM_CPPFLAGS) $(SAMPLERATE_CFLAGS)
+ src/audio_parser.c
test_run_convert_LDADD = \
- $(SAMPLERATE_LIBS) \
+ $(PCM_LIBS) \
+ libutil.a \
$(GLIB_LIBS)
-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)
test_run_output_LDADD = $(MPD_LIBS) \
- $(ENCODER_LIBS) \
$(OUTPUT_LIBS) \
+ $(ENCODER_LIBS) \
+ libmixer_plugins.a \
+ $(FILTER_LIBS) \
+ libutil.a \
$(GLIB_LIBS)
test_run_output_SOURCES = test/run_output.c \
test/stdbin.h \
- src/conf.c src/tokenizer.c src/utils.c src/log.c \
+ src/conf.c src/tokenizer.c src/utils.c src/string_util.c src/log.c \
+ src/io_thread.c src/io_thread.h \
+ src/udp_server.c src/udp_server.h \
+ src/tcp_socket.c src/tcp_socket.h \
src/audio_check.c \
src/audio_format.c \
src/audio_parser.c \
@@ -1081,41 +1239,31 @@ test_run_output_SOURCES = test/run_output.c \
src/fifo_buffer.c src/growing_fifo.c \
src/page.c \
src/socket_util.c \
- src/output_init.c src/output_list.c \
- $(ENCODER_SRC) \
+ src/resolver.c \
+ src/output_init.c src/output_finish.c src/output_list.c \
+ src/output_plugin.c \
src/mixer_api.c \
src/mixer_control.c \
src/mixer_type.c \
- $(MIXER_SRC) \
- src/filter_plugin.c src/filter/chain_filter_plugin.c \
+ src/filter_plugin.c \
src/filter_config.c \
- src/filter/autoconvert_filter_plugin.c \
- src/filter/convert_filter_plugin.c \
- src/filter/replay_gain_filter_plugin.c \
- src/filter/normalize_filter_plugin.c \
- src/filter/volume_filter_plugin.c \
- src/pcm_volume.c \
- src/pcm_buffer.c \
src/AudioCompress/compress.c \
src/replay_gain_info.c \
src/replay_gain_config.c \
src/fd_util.c \
- src/server_socket.c \
- $(OUTPUT_SRC)
+ src/server_socket.c
-test_read_mixer_CPPFLAGS = $(AM_CPPFLAGS) \
- $(OUTPUT_CFLAGS)
-test_read_mixer_LDADD = $(MPD_LIBS) \
+test_read_mixer_LDADD = \
+ libpcm.a \
+ libmixer_plugins.a \
$(OUTPUT_LIBS) \
$(GLIB_LIBS)
test_read_mixer_SOURCES = test/read_mixer.c \
- src/conf.c src/tokenizer.c src/utils.c src/log.c \
+ src/conf.c src/tokenizer.c src/utils.c src/string_util.c src/log.c \
src/mixer_control.c src/mixer_api.c \
src/filter_plugin.c \
src/filter/volume_filter_plugin.c \
- src/fd_util.c \
- src/pcm_buffer.c \
- $(MIXER_SRC)
+ src/fd_util.c
if ENABLE_BZIP2_TEST
TESTS += test/test_archive_bzip2.sh
@@ -1138,6 +1286,41 @@ test_run_inotify_SOURCES = test/run_inotify.c \
test_run_inotify_LDADD = $(GLIB_LIBS)
endif
+test_test_byte_reverse_SOURCES = \
+ test/test_glib_compat.h \
+ test/test_byte_reverse.c
+test_test_byte_reverse_LDADD = \
+ libutil.a \
+ $(GLIB_LIBS)
+
+test_test_pcm_SOURCES = \
+ test/test_glib_compat.h \
+ test/test_pcm_dither.c \
+ test/test_pcm_pack.c \
+ test/test_pcm_channels.c \
+ test/test_pcm_all.h \
+ test/test_pcm_main.c
+test_test_pcm_LDADD = \
+ $(PCM_LIBS) \
+ libutil.a \
+ $(GLIB_LIBS)
+
+test_test_queue_priority_SOURCES = \
+ src/queue.c \
+ test/test_queue_priority.c
+test_test_queue_priority_LDADD = \
+ $(GLIB_LIBS)
+
+if HAVE_CXX
+noinst_PROGRAMS += src/dsd2pcm/dsd2pcm
+
+src_dsd2pcm_dsd2pcm_SOURCES = \
+ src/dsd2pcm/dsd2pcm.c src/dsd2pcm/dsd2pcm.h \
+ src/dsd2pcm/noiseshape.c src/dsd2pcm/noiseshape.h \
+ src/dsd2pcm/main.cpp
+src_dsd2pcm_dsd2pcm_LDADD = libutil.a
+endif
+
endif
@@ -1175,8 +1358,7 @@ endif
doc/api/html/index.html: doc/doxygen.conf
@mkdir -p $(@D)
- [ "$(srcdir)" = "." ] || sed '/INPUT *=/ s/\([^ ]\+\/\)/$(subst /,\/,$(srcdir))\/\1/g' $(srcdir)/doc/doxygen.conf >doc/doxygen.conf
- $(DOXYGEN) doc/doxygen.conf
+ $(DOXYGEN) $<
all-local: $(DOCBOOK_HTML) doc/api/html/index.html
@@ -1210,4 +1392,5 @@ endif
EXTRA_DIST = $(doc_DATA) autogen.sh \
$(wildcard scripts/*.sh) \
- $(man_MANS) $(DOCBOOK_FILES) doc/mpdconf.example doc/doxygen.conf
+ $(man_MANS) $(DOCBOOK_FILES) doc/mpdconf.example doc/doxygen.conf \
+ src/win/mpd_win32_rc.rc.in
diff --git a/NEWS b/NEWS
index 5a215dadd..524b439c1 100644
--- a/NEWS
+++ b/NEWS
@@ -1,3 +1,44 @@
+ver 0.17 (2011/??/??)
+* protocol:
+ - support client-to-client communication
+ - "update" and "rescan" need only "CONTROL" permission
+ - new command "seekcur" for simpler seeking within current song
+ - new command "config" dumps location of music directory
+ - add range parameter to command "load"
+ - print extra "playlist" object for embedded CUE sheets
+* input:
+ - cdio_paranoia: new input plugin to play audio CDs
+ - curl: enable CURLOPT_NETRC
+ - curl: non-blocking I/O
+ - soup: new input plugin based on libsoup
+* decoder:
+ - mpg123: implement seeking
+ - ffmpeg: drop support for pre-0.5 ffmpeg
+ - oggflac: delete this obsolete plugin
+ - dsdiff: new decoder plugin
+* output:
+ - alsa: support DSD-over-USB (dCS suggested standard)
+ - httpd: support for streaming to a DLNA client
+ - openal: improve buffer cancellation
+ - osx: allow user to specify other audio devices
+ - osx: implement 32 bit playback
+ - raop: new output plugin
+ - shout: add possibility to set url
+ - roar: new output plugin for RoarAudio
+ - winmm: fail if wrong device specified instead of using default device
+* mixer:
+ - alsa: listen for external volume changes
+* playlist:
+ - allow references to songs outside the music directory
+ - new CUE parser, without libcue
+ - soundcloud: new plugin for accessing soundcloud.com
+* state_file: add option "restore_paused"
+* cue: show CUE track numbers
+* allow port specification in "bind_to_address" settings
+* support floating point samples
+* systemd socket activation
+
+
ver 0.16.8 (2012/??/??)
* fix for libsamplerate assertion failure
* decoder:
diff --git a/autogen.sh b/autogen.sh
index 28a89c674..f163e35a7 100755
--- a/autogen.sh
+++ b/autogen.sh
@@ -16,7 +16,7 @@ if test -n "$AM_FORCE_VERSION"
then
AM_VERSIONS="$AM_FORCE_VERSION"
else
- AM_VERSIONS='1.11 1.10'
+ AM_VERSIONS='1.11'
fi
if test -n "$AC_FORCE_VERSION"
then
diff --git a/configure.ac b/configure.ac
index bbea25621..a84d6f698 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1,11 +1,19 @@
AC_PREREQ(2.60)
-AC_INIT(mpd, 0.16.8~git, musicpd-dev-team@lists.sourceforge.net)
+
+AC_INIT(mpd, 0.17~git, musicpd-dev-team@lists.sourceforge.net)
+
+VERSION_MAJOR=0
+VERSION_MINOR=17
+VERSION_REVISION=0
+VERSION_EXTRA=0
+
AC_CONFIG_SRCDIR([src/main.c])
-AM_INIT_AUTOMAKE([foreign 1.10 dist-bzip2 subdir-objects])
-AM_CONFIG_HEADER(config.h)
+AM_INIT_AUTOMAKE([foreign 1.11 dist-bzip2 subdir-objects])
+AM_SILENT_RULES
+AC_CONFIG_HEADERS(config.h)
AC_CONFIG_MACRO_DIR([m4])
-AC_DEFINE(PROTOCOL_VERSION, "0.16.0", [The MPD protocol version])
+AC_DEFINE(PROTOCOL_VERSION, "0.17.0", [The MPD protocol version])
dnl ---------------------------------------------------------------------------
@@ -29,6 +37,7 @@ if test x$CXX = xg++; then
HAVE_CXX=no
fi
fi
+AM_CONDITIONAL(HAVE_CXX, test x$HAVE_CXX = xyes)
AC_PROG_INSTALL
AC_PROG_MAKE_SET
@@ -56,10 +65,11 @@ AC_SUBST(AM_CPPFLAGS,"")
AC_SUBST(AM_CFLAGS,"")
AC_SUBST(AM_CXXFLAGS,"")
-AC_SUBST(MPD_LIBS)
-AC_SUBST(MPD_CFLAGS)
-MPD_LIBS=""
-MPD_CFLAGS=""
+## Used for the windows resource file
+AC_SUBST(VERSION_MAJOR)
+AC_SUBST(VERSION_MINOR)
+AC_SUBST(VERSION_REVISION)
+AC_SUBST(VERSION_EXTRA)
dnl ---------------------------------------------------------------------------
dnl OS Specific Defaults
@@ -68,10 +78,16 @@ AC_CANONICAL_HOST
case "$host_os" in
mingw32* | windows*)
+ AC_CONFIG_FILES([
+ src/win/mpd_win32_rc.rc
+ ])
+ AC_CHECK_TOOL(WINDRES, windres)
AM_CPPFLAGS="$AM_CPPFLAGS -DWINVER=0x0501"
- MPD_LIBS="$MPD_LIBS -lws2_32"
+ LIBS="$LIBS -lws2_32"
+ HAVE_WINDOWS=1
;;
esac
+AM_CONDITIONAL([HAVE_WINDOWS], [test $HAVE_WINDOWS -eq 1])
if test -z "$prefix" || test "x$prefix" = xNONE; then
local_lib=
@@ -112,22 +128,18 @@ fi
dnl ---------------------------------------------------------------------------
dnl Header/Library Checks
dnl ---------------------------------------------------------------------------
-AC_CHECK_FUNCS(daemon fork syslog)
-if test $ac_cv_func_syslog = no; then
- # syslog is not in the default libraries. See if it's in some other.
- for lib in bsd socket inet; do
- AC_CHECK_LIB($lib, syslog,
- [AC_DEFINE(HAVE_SYSLOG)
- LIBS="$LIBS -l$lib"; break])
- done
-fi
+AC_CHECK_FUNCS(daemon fork)
-AC_CHECK_LIB(socket,socket,MPD_LIBS="$MPD_LIBS -lsocket",)
-AC_CHECK_LIB(nsl,gethostbyname,MPD_LIBS="$MPD_LIBS -lnsl",)
+AC_SEARCH_LIBS([syslog], [bsd socket inet],
+ [AC_DEFINE(HAVE_SYSLOG, 1, [Define if syslog() is available])])
+
+AC_SEARCH_LIBS([socket], [socket])
+AC_SEARCH_LIBS([gethostbyname], [nsl])
AC_CHECK_FUNCS(pipe2 accept4)
-AC_CHECK_LIB(m,exp,MPD_LIBS="$MPD_LIBS -lm",)
+AC_SEARCH_LIBS([exp], [m],,
+ [AC_MSG_ERROR([exp() not found])])
AC_CHECK_HEADERS(locale.h)
AC_CHECK_HEADERS(valgrind/memcheck.h)
@@ -139,6 +151,11 @@ AC_ARG_ENABLE(alsa,
AS_HELP_STRING([--enable-alsa], [enable ALSA support]),,
[enable_alsa=auto])
+AC_ARG_ENABLE(roar,
+ AS_HELP_STRING([--enable-roar],
+ [enable support for RoarAudio]),,
+ [enable_roar=auto])
+
AC_ARG_ENABLE(ao,
AS_HELP_STRING([--enable-ao],
[enable support for libao]),,
@@ -154,16 +171,21 @@ AC_ARG_ENABLE(bzip2,
[enable bzip2 archive support (default: disabled)]),,
enable_bzip2=no)
-AC_ARG_ENABLE(cue,
- AS_HELP_STRING([--enable-cue],
- [enable support for libcue support]),,
- enable_cue=auto)
+AC_ARG_ENABLE(cdio-paranoia,
+ AS_HELP_STRING([--enable-cdio-paranoia],
+ [enable support for audio CD support]),,
+ enable_cdio_paranoia=auto)
AC_ARG_ENABLE(curl,
AS_HELP_STRING([--enable-curl],
[enable support for libcurl HTTP streaming (default: auto)]),,
[enable_curl=auto])
+AC_ARG_ENABLE(soup,
+ AS_HELP_STRING([--enable-soup],
+ [enable support for libsoup HTTP streaming (default: auto)]),,
+ [enable_soup=auto])
+
AC_ARG_ENABLE(debug,
AS_HELP_STRING([--enable-debug],
[enable debugging (default: disabled)]),,
@@ -203,16 +225,16 @@ AC_ARG_ENABLE(gme,
[enable Blargg's game music emulator plugin]),,
enable_gme=auto)
-AC_ARG_ENABLE(gprof,
- AS_HELP_STRING([--enable-gprof],
- [enable profiling via gprof (default: disabled)]),,
- enable_gprof=no)
-
AC_ARG_ENABLE(httpd-output,
AS_HELP_STRING([--enable-httpd-output],
[enables the HTTP server output]),,
[enable_httpd_output=auto])
+AC_ARG_ENABLE(raop-output,
+ AS_HELP_STRING([--enable-raop-output],
+ [enables the RAOP output]),,
+ [enable_raop_output=auto])
+
AC_ARG_ENABLE(id3,
AS_HELP_STRING([--enable-id3],
[enable id3 support]),,
@@ -245,6 +267,16 @@ AC_ARG_ENABLE(lastfm,
[enable support for last.fm radio (default: disable)]),,
[enable_lastfm=no])
+AC_ARG_ENABLE(despotify,
+ AS_HELP_STRING([--enable-despotify],
+ [enable support for despotify (default: disable)]),,
+ [enable_despotify=no])
+
+AC_ARG_ENABLE(soundcloud,
+ AS_HELP_STRING([--enable-soundcloud],
+ [enable support for soundcloud.com]),,
+ [enable_soundcloud=auto])
+
AC_ARG_ENABLE(lame-encoder,
AS_HELP_STRING([--enable-lame-encoder],
[enable the LAME mp3 encoder]),,
@@ -294,11 +326,6 @@ AC_ARG_ENABLE(mvp,
[enable support for Hauppauge Media MVP (default: disable)]),,
enable_mvp=no)
-AC_ARG_ENABLE(oggflac,
- AS_HELP_STRING([--disable-oggflac],
- [disable OggFLAC support (default: enable)]),,
- enable_oggflac=yes)
-
AC_ARG_ENABLE(openal,
AS_HELP_STRING([--enable-openal],
[enable OpenAL support (default: disable)]),,
@@ -350,6 +377,11 @@ AC_ARG_ENABLE(sqlite,
[enable support for the SQLite database]),,
[enable_sqlite=auto])
+AC_ARG_ENABLE(systemd-daemon,
+ AS_HELP_STRING([--enable-systemd-daemon],
+ [use the systemd daemon library (default=auto)]),,
+ [enable_systemd_daemon=auto])
+
AC_ARG_ENABLE(tcp,
AS_HELP_STRING([--disable-tcp],
[disable support for clients connecting via TCP (default: enable)]),,
@@ -492,6 +524,13 @@ if
AC_MSG_ERROR([No client interfaces configured!])
fi
+MPD_AUTO_PKG(systemd_daemon, SYSTEMD_DAEMON, libsystemd-daemon,
+ [systemd activation], [libsystemd-daemon not found])
+AM_CONDITIONAL(ENABLE_SYSTEMD_DAEMON, test x$enable_systemd_daemon = xyes)
+if test x$enable_systemd_daemon = xyes; then
+ AC_DEFINE([ENABLE_SYSTEMD_DAEMON], 1, [Define to use the systemd daemon library])
+fi
+
dnl ---------------------------------------------------------------------------
dnl LIBC Features
dnl ---------------------------------------------------------------------------
@@ -531,16 +570,6 @@ dnl ---------------------------------------------------------------------------
dnl Metadata Plugins
dnl ---------------------------------------------------------------------------
-dnl ---------------------------------- libcue ---------------------------------
-MPD_AUTO_PKG(cue, CUE, [libcue],
- [libcue parsing library], [libcue not found])
-if test x$enable_cue = xyes; then
- AC_DEFINE([HAVE_CUE], 1,
- [Define to enable libcue support])
-fi
-
-AM_CONDITIONAL(HAVE_CUE, test x$enable_cue = xyes)
-
dnl -------------------------------- libid3tag --------------------------------
MPD_AUTO_PKG_LIB(id3, ID3TAG, id3tag, id3tag, id3_file_open, [-lid3tag -lz], [],
[id3tag], [libid3tag not found])
@@ -585,8 +614,7 @@ if test x$with_zeroconf != xno; then
if test x$with_zeroconf = xbonjour || test x$with_zeroconf = xauto; then
AC_CHECK_HEADER(dns_sd.h,
[enable_bonjour=yes;AC_DEFINE([HAVE_BONJOUR], 1, [Define to enable Bonjour Zeroconf support])])
- AC_CHECK_LIB(dns_sd, DNSServiceRegister,
- MPD_LIBS="$MPD_LIBS -ldns_sd")
+ AC_CHECK_LIB([dns_sd], [DNSServiceRegister])
fi
if test x$enable_bonjour = xyes; then
@@ -653,6 +681,14 @@ if test x$enable_curl = xyes; then
fi
AM_CONDITIONAL(ENABLE_CURL, test x$enable_curl = xyes)
+dnl ----------------------------------- SOUP ----------------------------------
+MPD_AUTO_PKG(soup, SOUP, [libsoup-2.4],
+ [libsoup HTTP streaming], [libsoup not found])
+if test x$enable_soup = xyes; then
+ AC_DEFINE(ENABLE_SOUP, 1, [Define when libsoup is used for HTTP streaming])
+fi
+AM_CONDITIONAL(ENABLE_SOUP, test x$enable_soup = xyes)
+
dnl --------------------------------- Last.FM ---------------------------------
if test x$enable_lastfm = xyes; then
if test x$enable_curl != xyes; then
@@ -663,6 +699,39 @@ if test x$enable_lastfm = xyes; then
fi
AM_CONDITIONAL(ENABLE_LASTFM, test x$enable_lastfm = xyes)
+dnl --------------------------------- Despotify ---------------------------------
+MPD_AUTO_PKG(despotify, DESPOTIFY, [despotify],
+ [Despotify support], [despotify not found])
+if test x$enable_despotify = xyes; then
+ AC_DEFINE(ENABLE_DESPOTIFY, 1, [Define when despotify is enabled])
+fi
+AM_CONDITIONAL(ENABLE_DESPOTIFY, test x$enable_despotify = xyes)
+
+dnl --------------------------------- Soundcloud ------------------------------
+if test x$enable_soundcloud != xno; then
+ PKG_CHECK_MODULES([YAJL], [yajl >= 2.0],
+ [found_soundcloud=yes],
+ AC_CHECK_LIB([yajl], [yajl_alloc],
+ [found_soundcloud=yes YAJL_CFLAGS=-DHAVE_YAJL1 YAJL_LIBS=-lyajl],
+ [found_soundcloud=no]))
+fi
+MPD_AUTO_RESULT([soundcloud], [soundcloud.com support], [libyajl not found])
+if test x$enable_soundcloud = xyes; then
+ AC_DEFINE(ENABLE_SOUNDCLOUD, 1, [Define when soundcloud is enabled])
+fi
+AM_CONDITIONAL(ENABLE_SOUNDCLOUD, test x$enable_soundcloud = xyes)
+AC_SUBST(YAJL_LIBS)
+
+dnl ---------------------------------- cdio ---------------------------------
+MPD_AUTO_PKG(cdio_paranoia, CDIO_PARANOIA, [libcdio_paranoia],
+ [libcdio_paranoia audio CD library], [libcdio_paranoia not found])
+if test x$enable_cdio_paranoia = xyes; then
+ AC_DEFINE([ENABLE_CDIO_PARANOIA], 1,
+ [Define to enable libcdio_paranoia support])
+fi
+
+AM_CONDITIONAL(ENABLE_CDIO_PARANOIA, test x$enable_cdio_paranoia = xyes)
+
dnl ---------------------------------- libmms ---------------------------------
MPD_AUTO_PKG(mms, MMS, [libmms >= 0.4],
[libmms mms:// protocol support], [libmms not found])
@@ -694,9 +763,10 @@ AM_CONDITIONAL(ENABLE_ISO9660_TEST, test x$MKISOFS != xno)
dnl ---------------------------------- libbz2 ---------------------------------
if test x$enable_bzip2 = xyes; then
AC_CHECK_LIB(bz2, BZ2_bzDecompressInit,
- [MPD_LIBS="$MPD_LIBS -lbz2"],
+ [BZ2_LIBS="-lbz2"],
[AC_MSG_ERROR([libbz2 not found])])
fi
+AC_SUBST(BZ2_LIBS)
AM_CONDITIONAL(HAVE_BZ2, test x$enable_bzip2 = xyes)
if test x$enable_bzip2 = xyes; then
@@ -756,21 +826,10 @@ AM_CONDITIONAL(HAVE_FAAD, test x$enable_aac = xyes)
AM_CONDITIONAL(HAVE_MP4, test x$enable_mp4 = xyes)
dnl ---------------------------------- ffmpeg ---------------------------------
-MPD_AUTO_PKG(ffmpeg, FFMPEG, [libavformat >= 52 libavcodec >= 51 libavutil >= 49],
+MPD_AUTO_PKG(ffmpeg, FFMPEG, [libavformat >= 52.31 libavcodec >= 52.20 libavutil >= 49.15],
[ffmpeg decoder library], [libavformat+libavcodec+libavutil not found])
if test x$enable_ffmpeg = xyes; then
- # prior to ffmpeg svn12865, you had to specify include files
- # without path prefix
- old_CPPCFLAGS=$CPPFLAGS
- CPPFLAGS="$CPPFLAGS $FFMPEG_CFLAGS"
- AC_CHECK_HEADER(libavcodec/avcodec.h,,
- AC_DEFINE(OLD_FFMPEG_INCLUDES, 1,
- [Define if avcodec.h instead of libavcodec/avcodec.h should be included]))
- CPPCFLAGS=$old_CPPFLAGS
-fi
-
-if test x$enable_ffmpeg = xyes; then
AC_DEFINE(HAVE_FFMPEG, 1, [Define for FFMPEG support])
fi
@@ -783,25 +842,6 @@ MPD_AUTO_PKG(flac, FLAC, [flac >= 1.1],
if test x$enable_flac = xyes; then
AC_DEFINE(HAVE_FLAC, 1, [Define for FLAC support])
-
- oldcflags="$CFLAGS"
- oldlibs="$LIBS"
- CFLAGS="$CFLAGS $FLAC_CFLAGS"
- LIBS="$LIBS $FLAC_LIBS"
- if test x$enable_flac = xyes && test x$enable_oggflac = xyes; then
- AC_CHECK_DECL(FLAC_API_SUPPORTS_OGG_FLAC,
- [enable_oggflac=flac], [],
- [#include <FLAC/export.h>])
- fi
- CFLAGS="$oldcflags"
- LIBS="$oldlibs"
-
- if test x$enable_oggflac = xflac; then
- PKG_CHECK_MODULES(OGG, [ogg],
- [FLAC_LIBS="${FLAC_LIBS} ${OGG_LIBS}" FLAC_CFLAGS="${FLAC_CFLAGS} ${OGG_CFLAGS}"],
- [enable_oggflac=yes;
- AC_MSG_WARN("FLAC has the ogg API built in, but couldn't find ogg. Disabling oggflac.")])
- fi
fi
AM_CONDITIONAL(HAVE_FLAC, test x$enable_flac = xyes)
@@ -887,52 +927,32 @@ AM_CONDITIONAL(ENABLE_SNDFILE, test x$enable_sndfile = xyes)
dnl --------------------------------- musepack --------------------------------
if test x$enable_mpc = xyes; then
- if test "x$mpcdec_libraries" != "x" ; then
- MPCDEC_LIBS="-L$mpcdec_libraries"
- elif test "x$mpcdec_prefix" != "x" ; then
- MPCDEC_LIBS="-L$mpcdec_prefix/lib"
- fi
-
- MPCDEC_LIBS="$MPCDEC_LIBS -lmpcdec"
-
- if test "x$mpcdec_includes" != "x" ; then
- MPCDEC_CFLAGS="-I$mpcdec_includes"
- elif test "x$mpcdec_prefix" != "x" ; then
- MPCDEC_CFLAGS="-I$mpcdec_prefix/include"
- fi
-
oldcflags=$CFLAGS
oldlibs=$LIBS
oldcppflags=$CPPFLAGS
- CFLAGS="$CFLAGS $MPD_CFLAGS $MPCDEC_CFLAGS -I."
- LIBS="$LIBS $MPD_LIBS $MPCDEC_LIBS"
- CPPFLAGS=$CFLAGS
- AC_CHECK_HEADER(mpc/mpcdec.h,
- old_mpcdec=no,
- [AC_CHECK_HEADER(mpcdec/mpcdec.h,
- old_mpcdec=yes,
- enable_mpc=no)])
- if test x$enable_mpc = xyes; then
- AC_CHECK_LIB(mpcdec,main,
- [MPD_LIBS="$MPD_LIBS $MPCDEC_LIBS";
- MPD_CFLAGS="$MPD_CFLAGS $MPCDEC_CFLAGS";],
+ AC_CHECK_LIB(mpcdec,main,
+ MPCDEC_LIBS="$MPCDEC_LIBS -lmpcdec",
enable_mpc=no)
- fi
+ CFLAGS=$oldcflags
+ LIBS=$oldlibs
+ CPPFLAGS=$oldcppflags
+
if test x$enable_mpc = xyes; then
- AC_DEFINE(HAVE_MPCDEC,1,
- [Define to use libmpcdec for MPC decoding])
- if test x$old_mpcdec = xyes; then
- AC_DEFINE(MPC_IS_OLD_API, 1,
- [Define if an old pre-SV8 libmpcdec is used])
- fi
+ AC_CHECK_HEADER([mpc/mpcdec.h],
+ [AC_DEFINE(HAVE_MPCDEC,1,
+ [Define to use libmpcdec for MPC decoding])],
+ [AC_CHECK_HEADER(mpcdec/mpcdec.h,
+ [AC_DEFINE(MPC_IS_OLD_API, 1,
+ [Define if an old pre-SV8 libmpcdec is used])]
+ )]
+ )
else
AC_MSG_WARN([mpcdec lib needed for MPC support -- disabling MPC support])
fi
- CFLAGS=$oldcflags
- LIBS=$oldlibs
- CPPFLAGS=$oldcppflags
fi
+AC_SUBST(MPCDEC_LIBS)
+AC_SUBST(MPCDEC_CFLAGS)
AM_CONDITIONAL(HAVE_MPCDEC, test x$enable_mpc = xyes)
dnl -------------------------------- Ogg Tremor -------------------------------
@@ -977,25 +997,6 @@ fi
AC_SUBST(TREMOR_CFLAGS)
AC_SUBST(TREMOR_LIBS)
-dnl --------------------------------- OggFLAC ---------------------------------
-dnl OggFLAC must go after Ogg Tremor
-
-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
-
-if test x$enable_oggflac = xyes; then
- AC_CHECK_HEADER([OggFLAC/stream_decoder.h],, enable_oggflac=no)
-fi
-
-if test x$enable_oggflac = xyes; then
- AC_DEFINE(HAVE_OGGFLAC,1,[Define for OggFLAC support])
- MPD_LIBS="$MPD_LIBS -lOggFLAC -lFLAC -lm"
-fi
-
-AM_CONDITIONAL(HAVE_OGGFLAC, test x$enable_oggflac = xyes)
-
dnl -------------------------------- Ogg Vorbis -------------------------------
if test x$enable_tremor = xyes; then
@@ -1020,19 +1021,18 @@ MPD_AUTO_PRE(sidplay, [sidplay decoder plugin], [No C++ compiler found])
if test x$enable_sidplay != xno; then
# we're not using pkg-config here
# because libsidplay2's .pc file requires libtool
- AC_HAVE_LIBRARY(sidplay2, [found_sidplay=yes], [found_sidplay=no])
+ AC_CHECK_LIB([sidplay2],[main],[found_sidplay=yes],[found_sidplay=no],[])
+
MPD_AUTO_PRE(sidplay, [sidplay decoder plugin],
[libsidplay2 not found])
fi
if test x$enable_sidplay != xno; then
- # can't use AC_HAVE_LIBRARY here, because the dash in the
- # library name triggers an autoconf bug
- AC_CHECK_LIB(resid-builder, main,
+ AC_CHECK_LIB([resid-builder], [main],
[found_sidplay=yes], [found_sidplay=no])
if test x$found_sidplay = xyes; then
- AC_HAVE_LIBRARY(sidutils,, [found_sidplay=no])
+ AC_CHECK_LIB([sidutils],[main],[],[found_sidplay=no],[])
fi
MPD_AUTO_RESULT(sidplay, [sidplay decoder plugin],
@@ -1095,7 +1095,6 @@ if
test x$enable_mp4 = xno &&
test x$enable_mpc = xno &&
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 &&
@@ -1106,10 +1105,10 @@ if
fi
AM_CONDITIONAL(HAVE_OGG_COMMON,
- test x$enable_vorbis = xyes || test x$enable_tremor = xyes || test x$enable_oggflac = xyes || test x$enable_flac = xyes)
+ test x$enable_vorbis = xyes || test x$enable_tremor = xyes || test x$enable_flac = xyes)
AM_CONDITIONAL(HAVE_FLAC_COMMON,
- test x$enable_flac = xyes || test x$enable_oggflac = xyes)
+ test x$enable_flac = xyes)
dnl ---------------------------------------------------------------------------
dnl Encoders for Streaming Audio Output Plugins
@@ -1232,6 +1231,16 @@ fi
AM_CONDITIONAL(HAVE_ALSA, test x$enable_alsa = xyes)
+dnl ----------------------------------- ROAR ----------------------------------
+MPD_AUTO_PKG(roar, ROAR, [libroar >= 0.4.0],
+ [ROAR output plugin], [libroar not found])
+
+if test x$enable_roar = xyes; then
+ AC_DEFINE(HAVE_ROAR, 1, [Define to enable ROAR support])
+fi
+
+AM_CONDITIONAL(HAVE_ROAR, test x$enable_roar = xyes)
+
dnl ----------------------------------- FFADO ---------------------------------
MPD_AUTO_PKG(ffado, FFADO, [libffado],
@@ -1342,7 +1351,7 @@ enable_osx=no
case "$host_os" in
darwin*)
AC_DEFINE(HAVE_OSX, 1, [Define for compiling OS X support])
- MPD_LIBS="$MPD_LIBS -framework AudioUnit -framework CoreServices"
+ LIBS="$LIBS -framework AudioUnit -framework CoreAudio -framework CoreServices"
enable_osx=yes ;;
esac
@@ -1419,13 +1428,24 @@ fi
AM_CONDITIONAL(ENABLE_SOLARIS_OUTPUT, test x$enable_solaris_output = xyes)
+dnl --------------------------------- RAOP ------------------------------------
+
+MPD_AUTO_PKG(raop_output, OPENSSL, [openssl],
+ [RAOP output], [OpenSSL not found])
+
+if test x$enable_raop_output = xyes; then
+ AC_DEFINE(ENABLE_RAOP_OUTPUT, 1, [Define for compiling RAOP support])
+fi
+
+AM_CONDITIONAL(ENABLE_RAOP_OUTPUT, test x$enable_raop_output = xyes)
+
dnl --------------------------------- WinMM ---------------------------------
case "$host_os" in
mingw32* | windows*)
AC_DEFINE(ENABLE_WINMM_OUTPUT, 1, [Define to enable WinMM support])
enable_winmm_output=yes
- MPD_LIBS="$MPD_LIBS -lwinmm"
+ LIBS="$LIBS -lwinmm"
;;
*)
@@ -1438,6 +1458,7 @@ 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_roar = xno &&
test x$enable_ao = xno &&
test x$enable_ffado = xno &&
test x$enable_fifo = xno &&
@@ -1447,6 +1468,7 @@ if
test x$enable_openal = xno &&
test x$enable_oss = xno &&
test x$enable_osx = xno &&
+ test x$enable_raop_output = xno &&
test x$enable_pipe_output = xno &&
test x$enable_pulse = xno &&
test x$enable_recorder_output = xno &&
@@ -1505,12 +1527,6 @@ then
MPD_CHECK_FLAG([-pedantic])
fi
-dnl ------------------------------ gprof profiler -----------------------------
-if test "x$enable_gprof" = xyes; then
- MPD_CFLAGS="$MPD_CFLAGS -pg"
- MPD_LIBS="$MPD_LIBS -pg"
-fi
-
dnl ---------------------------- warnings as errors ---------------------------
if test "x$enable_werror" = xyes; then
AM_CFLAGS="$AM_CFLAGS -Werror -pedantic-errors"
@@ -1554,7 +1570,6 @@ results(mad, [MAD])
results(mpg123, [MPG123])
results(mp4, [MP4])
results(mpc, [Musepack])
-results(oggflac, [OggFLAC], flac)
printf '\n\t'
results(tremor, [OggTremor])
results(vorbis, [OggVorbis])
@@ -1568,7 +1583,6 @@ results(inotify, [inotify])
results(sqlite, [SQLite])
printf '\nMetadata support:\n\t'
-results(cue,[cue])
results(id3,[ID3])
printf '\nPlayback support:\n\t'
@@ -1578,16 +1592,18 @@ results(fifo,FIFO)
results(recorder_output,[File Recorder])
results(httpd_output,[HTTP Daemon])
results(jack,[JACK])
+printf '\n\t'
results(ao,[libao])
+results(mvp, [Media MVP])
results(oss,[OSS])
-printf '\n\t'
results(openal,[OpenAL])
results(osx, [OS X])
results(pipe_output, [Pipeline])
+printf '\n\t'
results(pulse, [PulseAudio])
-results(mvp, [Media MVP])
+results(raop_output, [RAOP])
+results(roar,[ROAR])
results(shout, [SHOUTcast])
-printf '\n\t'
results(solaris_output, [Solaris])
results(winmm_output, [WinMM])
@@ -1604,9 +1620,14 @@ if
fi
printf '\nStreaming support:\n\t'
+results(cdio_paranoia, [CDIO_PARANOIA])
results(curl,[CURL])
+results(despotify,[Despotify])
results(lastfm,[Last.FM])
+results(soundcloud,[Soundcloud])
+printf '\n\t'
results(mms,[MMS])
+results(soup, [SOUP])
printf '\n\n##########################################\n\n'
@@ -1615,7 +1636,9 @@ echo 'Generating files needed for compilation'
dnl ---------------------------------------------------------------------------
dnl Generate files
dnl ---------------------------------------------------------------------------
-AC_OUTPUT(Makefile)
-AC_OUTPUT(mpd.service)
+AC_CONFIG_FILES(Makefile)
+AC_CONFIG_FILES(doc/doxygen.conf)
+AC_CONFIG_FILES(mpd.service)
+AC_OUTPUT
echo 'MPD is ready for compilation, type "make" to begin.'
diff --git a/doc/doxygen.conf b/doc/doxygen.conf.in
index ddece77e8..95dca9a3c 100644
--- a/doc/doxygen.conf
+++ b/doc/doxygen.conf.in
@@ -31,7 +31,7 @@ PROJECT_NAME = MPD
# This could be handy for archiving the generated documentation or
# if some version control system is used.
-PROJECT_NUMBER =
+PROJECT_NUMBER = @VERSION@
# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute)
# base path where the generated documentation will be put.
@@ -481,7 +481,7 @@ FILE_VERSION_FILTER =
# The QUIET tag can be used to turn on/off the messages that are generated
# by doxygen. Possible values are YES and NO. If left blank NO is used.
-QUIET = NO
+QUIET = YES
# The WARNINGS tag can be used to turn on/off the warning messages that are
# generated by doxygen. Possible values are YES and NO. If left blank
@@ -534,7 +534,7 @@ WARN_LOGFILE =
# directories like "/usr/src/myproject". Separate the files or directories
# with spaces.
-INPUT = src/
+INPUT = @abs_top_srcdir@/src/
# This tag can be used to specify the character encoding of the source files
# that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is
diff --git a/doc/mpd.conf.5 b/doc/mpd.conf.5
index 4cddd7ba9..bd890228b 100644
--- a/doc/mpd.conf.5
+++ b/doc/mpd.conf.5
@@ -69,6 +69,9 @@ mpd will be saved to this file when mpd is terminated by a TERM signal or by
the "kill" command. When mpd is restarted, it will read the state file and
restore the state of mpd (including the playlist).
.TP
+.B restore_paused <yes or no>
+Put MPD into pause mode instead of starting playback after startup.
+.TP
.B user <username>
This specifies the user that MPD will run as, if set. MPD should
never run as root, and you may use this option to make MPD change its
@@ -80,6 +83,10 @@ This specifies which address mpd binds to and listens on. Multiple
bind_to_address parameters may be specified. The default is "any", which binds
to all available addresses.
+You can set a port that is different from the global port setting,
+e.g. "localhost:6602". IPv6 addresses must be enclosed in square
+brackets if you want to configure a port, e.g. "[::1]:6602".
+
To bind to a Unix domain socket, specify an absolute path. For a
system-wide MPD, we suggest the path "\fB/var/run/mpd/socket\fP".
.TP
@@ -259,6 +266,17 @@ of database.
.B auto_update_depth <N>
Limit the depth of the directories being watched, 0 means only watch
the music directory itself. There is no limit by default.
+.TP
+.B despotify_user <name>
+This specifies the user to use when logging in to Spotify using the despotify plugins.
+.TP
+.B despotify_password <name>
+This specifies the password to use when logging in to Spotify using the despotify plugins.
+.TP
+.B despotify_high_bitrate <yes or no>
+This specifies if the requested bitrate for Spotify should be high or not. Higher sounds
+better but requires more processing and higher bandwidth. Default is yes.
+.TP
.SH REQUIRED AUDIO OUTPUT PARAMETERS
.TP
.B type <type>
@@ -464,6 +482,9 @@ connect to the icecast server. The default is 2 seconds.
.B description <description>
This specifies a description of the stream.
.TP
+.B url <url>
+This specifies a URL associated with the stream.
+.TP
.B genre <genre>
This specifies the genre(s) of the stream.
.SH FILES
diff --git a/doc/mpdconf.example b/doc/mpdconf.example
index b14337c76..1aa9cf1dc 100644
--- a/doc/mpdconf.example
+++ b/doc/mpdconf.example
@@ -103,6 +103,11 @@
#
#gapless_mp3_playback "yes"
#
+# Setting "restore_paused" to "yes" puts MPD into pause mode instead
+# of starting playback after startup.
+#
+#restore_paused "no"
+#
# This setting enables MPD to create playlists in a format usable by other
# music players.
#
@@ -235,6 +240,7 @@ input {
## protocol "icecast2" # optional
## user "source" # optional
## description "My Stream Description" # optional
+## url "http://example.com" # optional
## genre "jazz" # optional
## public "no" # optional
## timeout "2" # optional
diff --git a/doc/protocol.xml b/doc/protocol.xml
index 0b4f0d175..56ff33b1e 100644
--- a/doc/protocol.xml
+++ b/doc/protocol.xml
@@ -59,7 +59,7 @@
<para>
All data between the client and the server is encoded in
UTF-8. (Note: In UTF-8 all standard ansi characters, 0-127 are
- the same as a standard ansi encoding. Also, no ansi character
+ the same as in standard ansi encoding. Also, no ansi character
appears in any multi-byte characters. So, you can use
standard C functions like <function>strlen</function>, and
<function>strcpy</function> just fine with UTF-8 encoded
@@ -204,6 +204,47 @@
</chapter>
<chapter>
+ <title>Recipes</title>
+
+ <section>
+ <title>Queuing</title>
+
+ <para>
+ Often, users run MPD with "<link
+ linkend="command_random">random</link>" enabled, but want to
+ be able to insert songs "before" the rest of the playlist.
+ That is commonly called "queuing".
+ </para>
+
+ <para>
+ MPD implements this by allowing the client to specify a
+ "priority" for each song in the playlist (commands <link
+ linkend="command_prio"><command>prio</command></link> and
+ <link
+ linkend="command_prioid"><command>prioid</command></link>). A
+ higher priority means that the song is going to be played
+ before the other songs.
+ </para>
+
+ <para>
+ In "random" mode, MPD maintains an internal randomized
+ sequence of songs. In this sequence, songs with a higher
+ priority come first, and all songs with the same priority are
+ shuffled (by default, all songs are shuffled, because all have
+ the same priority "0"). When you increase the priority of a
+ song, it is moved to the front of the sequence according to
+ its new priority, but always after the current one. A song
+ that has been played already (it's "before" the current song
+ in that sequence) will only be scheduled for repeated playback
+ if its priority has become bigger than the priority of the
+ current song. Decreasing the priority of a song will moved it
+ farther to the end of the sequence. Changing the priority of
+ the current song has no effect on the sequence.
+ </para>
+ </section>
+ </chapter>
+
+ <chapter>
<title>Command reference</title>
<note>
@@ -318,6 +359,25 @@
<option>crossfade</option>, replay gain
</para>
</listitem>
+ <listitem>
+ <para>
+ <returnvalue>sticker</returnvalue>: the sticker database
+ has been modified.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ <returnvalue>subscription</returnvalue>: a client
+ has subscribed or unsubscribed to a channel
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ <returnvalue>message</returnvalue>: a message was
+ received on a channel this client is subscribed to;
+ this event is only emitted when the queue is empty
+ </para>
+ </listitem>
</itemizedlist>
<para>
While a client is waiting for <command>idle</command>
@@ -677,7 +737,11 @@
Sets the replay gain mode. One of
<parameter>off</parameter>,
<parameter>track</parameter>,
- <parameter>album</parameter>.
+ <parameter>album</parameter>,
+ <parameter>auto</parameter><footnote
+ id="replay_gain_auto_since_0_16">
+ <simpara>added in MPD 0.16</simpara>
+ </footnote>.
</para>
<para>
Changing the mode during playback may take several
@@ -814,6 +878,23 @@
</para>
</listitem>
</varlistentry>
+
+ <varlistentry id="command_seekcur">
+ <term>
+ <cmdsynopsis>
+ <command>seekcur</command>
+ <arg choice="req"><replaceable>TIME</replaceable></arg>
+ </cmdsynopsis>
+ </term>
+ <listitem>
+ <para>
+ Seeks to the position <varname>TIME</varname> within the
+ current song. If prefixed by '+' or '-', then the time
+ is relative to the current playing position.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="command_stop">
<term>
<cmdsynopsis>
@@ -1073,6 +1154,46 @@ OK
</para>
</listitem>
</varlistentry>
+
+ <varlistentry id="command_prio">
+ <term>
+ <cmdsynopsis>
+ <command>prio</command>
+ <arg choice="req"><replaceable>PRIORITY</replaceable></arg>
+ <arg choice="req" rep="repeat"><replaceable>START:END</replaceable></arg>
+ </cmdsynopsis>
+ </term>
+ <listitem>
+ <para>
+ Set the priority of the specified songs. A higher
+ priority means that it will be played first when
+ "random" mode is enabled.
+ </para>
+
+ <para>
+ A priority is an integer between 0 and 255. The default
+ priority of new songs is 0.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="command_prioid">
+ <term>
+ <cmdsynopsis>
+ <command>prioid</command>
+ <arg choice="req"><replaceable>PRIORITY</replaceable></arg>
+ <arg choice="req" rep="repeat"><replaceable>ID</replaceable></arg>
+ </cmdsynopsis>
+ </term>
+ <listitem>
+ <para>
+ Same as <link
+ linkend="command_prio"><command>prio</command></link>,
+ but address the songs with their id.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="command_shuffle">
<term>
<cmdsynopsis>
@@ -1191,12 +1312,14 @@ OK
<cmdsynopsis>
<command>load</command>
<arg choice="req"><replaceable>NAME</replaceable></arg>
+ <arg choice="opt"><replaceable>START:END</replaceable></arg>
</cmdsynopsis>
</term>
<listitem>
<para>
Loads the playlist into the current queue. Playlist
- plugins are supported.
+ plugins are supported. A range may be specified to load
+ only a part of the playlist.
</para>
</listitem>
</varlistentry>
@@ -1433,6 +1556,11 @@ OK
the list of stored playlists. This behavior is
deprecated; use "listplaylists" instead.
</para>
+ <para>
+ Clients that are connected via UNIX domain socket may
+ use this command to read the tags of an arbitrary local
+ file (URI beginning with "file:///").
+ </para>
</listitem>
</varlistentry>
<varlistentry id="command_search">
@@ -1681,6 +1809,7 @@ OK
<term>
<cmdsynopsis>
<command>disableoutput</command>
+ <arg choice="req"><replaceable>ID</replaceable></arg>
</cmdsynopsis>
</term>
<listitem>
@@ -1693,6 +1822,7 @@ OK
<term>
<cmdsynopsis>
<command>enableoutput</command>
+ <arg choice="req"><replaceable>ID</replaceable></arg>
</cmdsynopsis>
</term>
<listitem>
@@ -1720,6 +1850,47 @@ OK
<title>Reflection</title>
<variablelist>
+ <varlistentry id="command_config">
+ <term>
+ <cmdsynopsis>
+ <command>config</command>
+ </cmdsynopsis>
+ </term>
+ <listitem>
+ <para>
+ Dumps configuration values that may be interesting for
+ the client. This command is only permitted to "local"
+ clients (connected via UNIX domain socket).
+ </para>
+ <para>
+ The following response attributes are available:
+ </para>
+ <informaltable>
+ <tgroup cols="2">
+ <thead>
+ <row>
+ <entry>
+ Name
+ </entry>
+ <entry>
+ Description
+ </entry>
+ </row>
+ </thead>
+ <tbody>
+ <row>
+ <entry>
+ <varname>music_directory</varname>
+ </entry>
+ <entry>
+ The absolute path of the music directory.
+ </entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </informaltable>
+ </listitem>
+ </varlistentry>
<varlistentry id="command_commands">
<term>
<cmdsynopsis>
@@ -1790,5 +1961,105 @@ suffix: mpc</programlisting>
</varlistentry>
</variablelist>
</section>
+
+ <section>
+ <title>Client to client</title>
+
+ <para>
+ Clients can communicate with each others over "channels". A
+ channel is created by a client subscribing to it. More than
+ one client can be subscribed to a channel at a time; all of
+ them will receive the messages which get sent to it.
+ </para>
+
+ <para>
+ Each time a client subscribes or unsubscribes, the global idle
+ event <varname>subscription</varname> is generated. In
+ conjunction with the <command>channels</command> command, this
+ may be used to auto-detect clients providing additional
+ services.
+ </para>
+
+ <para>
+ New messages are indicated by the <varname>message</varname>
+ idle event.
+ </para>
+
+ <variablelist>
+ <varlistentry id="command_subscribe">
+ <term>
+ <cmdsynopsis>
+ <command>subscribe</command>
+ <arg choice="req"><replaceable>NAME</replaceable></arg>
+ </cmdsynopsis>
+ </term>
+ <listitem>
+ <para>
+ Subscribe to a channel. The channel is created if it
+ does not exist already. The name may consist of
+ alphanumeric ASCII characters plus underscore, dash, dot
+ and colon.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="command_unsubscribe">
+ <term>
+ <cmdsynopsis>
+ <command>unsubscribe</command>
+ <arg choice="req"><replaceable>NAME</replaceable></arg>
+ </cmdsynopsis>
+ </term>
+ <listitem>
+ <para>
+ Unsubscribe from a channel.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="command_channels">
+ <term>
+ <cmdsynopsis>
+ <command>channels</command>
+ </cmdsynopsis>
+ </term>
+ <listitem>
+ <para>
+ Obtain a list of all channels. The response is a list
+ of "channel:" lines.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="command_readmessages">
+ <term>
+ <cmdsynopsis>
+ <command>readmessages</command>
+ </cmdsynopsis>
+ </term>
+ <listitem>
+ <para>
+ Reads messages for this client. The response is a list
+ of "channel:" and "message:" lines.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="command_sendmessage">
+ <term>
+ <cmdsynopsis>
+ <command>sendmessage</command>
+ <arg choice="req"><replaceable>CHANNEL</replaceable></arg>
+ <arg choice="req"><replaceable>TEXT</replaceable></arg>
+ </cmdsynopsis>
+ </term>
+ <listitem>
+ <para>
+ Send a message to the specified channel.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </section>
</chapter>
</book>
diff --git a/doc/user.xml b/doc/user.xml
index 6a9871007..cd36528d5 100644
--- a/doc/user.xml
+++ b/doc/user.xml
@@ -99,6 +99,47 @@ cd mpd-version</programlisting>
<programlisting>make install</programlisting>
</section>
+
+ <section>
+ <title><filename>systemd</filename> socket activation</title>
+
+ <para>
+ Using <filename>systemd</filename>, you can launch
+ <filename>mpd</filename> on demand when the first client
+ attempts to connect. Create two files in
+ <filename>/etc/systemd/system/</filename>; first
+ <filename>mpd.socket</filename>:
+ </para>
+
+ <programlisting>[Socket]
+ListenStream=/run/mpd.socket
+ListenStream=6600
+[Install]
+WantedBy=sockets.target</programlisting>
+
+ <para>
+ Now create <filename>mpd.service</filename>:
+ </para>
+
+ <programlisting>[Unit]
+Description=Music Player Daemon
+After=sound.target
+[Service]
+ExecStart=/usr/bin/mpd --stdout --no-daemon</programlisting>
+
+ <para>
+ Start the socket:
+ </para>
+
+ <programlisting>systemctl enable mpd.socket
+systemctl start mpd.socket</programlisting>
+
+ <para>
+ In this configuration, <filename>mpd</filename> will ignore
+ the <varname>bind_to_address</varname> and
+ <varname>port</varname> settings.
+ </para>
+ </section>
</chapter>
<chapter>
@@ -236,6 +277,16 @@ cd mpd-version</programlisting>
</section>
<section>
+ <title>Configuring encoder plugins</title>
+
+ <para>
+ Encoders are used by some of the output plugins (such as
+ <varname>shout</varname>). The encoder settings are included
+ in the <varname>audio_output</varname> section.
+ </para>
+ </section>
+
+ <section>
<title>Configuring audio outputs</title>
<para>
@@ -322,7 +373,8 @@ cd mpd-version</programlisting>
<varname>24_3</varname> (signed 24 bit integer
samples, no padding, 3 bytes per sample),
<varname>32</varname> (signed 32 bit integer
- samples).
+ samples), <varname>f</varname> (32 bit floating
+ point, -1.0 to 1.0).
</para>
</entry>
</row>
@@ -346,7 +398,7 @@ cd mpd-version</programlisting>
If set to "yes", then MPD attempts to keep this audio
output always open. This may be useful for streaming
servers, when you don't want to disconnect all
- listeners even when playback is accidently stopped.
+ listeners even when playback is accidentally stopped.
</entry>
</row>
<row>
@@ -621,12 +673,143 @@ cd mpd-version</programlisting>
Plays streams with the MMS protocol.
</para>
</section>
+
+ <section>
+ <title><varname>cdio_paranoia</varname></title>
+
+ <para>
+ Plays audio CDs. The URI has the form:
+ "<filename>cdda://[DEVICE][/TRACK]</filename>". The
+ simplest form <filename>cdda://</filename> plays the whole
+ disc in the default drive.
+ </para>
+ </section>
+
+ <section>
+ <title><varname>despotify</varname></title>
+
+ <para>
+ Plays <ulink url="http://www.spotify.com">Spotify</ulink> tracks using the despotify
+ library. The despotify plugin uses a <filename>spt://</filename> URI and a Spotify
+ URL. So for example, you can add a song with:
+ </para>
+
+ <para>
+ <filename>mpc add spt://spotify:track:5qENVY0YEdZ7fiuOax70x1</filename>
+ </para>
+
+ <para>
+ You need a Spotify premium account to use this plugin, and you need
+ to setup username and password in the configuration file. The
+ configuration settings are global since the despotify playlist plugin
+ use the same settings.
+ </para>
+
+ <informaltable>
+ <tgroup cols="2">
+ <thead>
+ <row>
+ <entry>Setting</entry>
+ <entry>Description</entry>
+ </row>
+ </thead>
+ <tbody>
+ <row>
+ <entry>
+ <varname>despotify_user</varname>
+ </entry>
+ <entry>
+ Sets up the Spotify username (required)
+ </entry>
+ </row>
+ <row>
+ <entry>
+ <varname>despotify_password</varname>
+ </entry>
+ <entry>
+ Sets up the Spotify password (required)
+ </entry>
+ </row>
+ <row>
+ <entry>
+ <varname>despotify_high_bitrate</varname>
+ </entry>
+ <entry>
+ Set up if high bitrate should be used for Spotify tunes.
+ High bitrate sounds better but slow systems can have problems
+ with playback (default yes).
+ </entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </informaltable>
+ </section>
+
+ <section>
+ <title><varname>soup</varname></title>
+
+ <para>
+ Opens remote files or streams over HTTP.
+ </para>
+
+ <informaltable>
+ <tgroup cols="2">
+ <thead>
+ <row>
+ <entry>Setting</entry>
+ <entry>Description</entry>
+ </row>
+ </thead>
+ <tbody>
+ <row>
+ <entry>
+ <varname>proxy</varname>
+ </entry>
+ <entry>
+ Sets the address of the HTTP proxy server.
+ </entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </informaltable>
+ </section>
</section>
<section>
<title>Decoder plugins</title>
<section>
+ <title><varname>dsdiff</varname></title>
+
+ <para>
+ Decodes DFF files containing DSDIFF data (e.g. SACD rips).
+ </para>
+
+ <informaltable>
+ <tgroup cols="2">
+ <thead>
+ <row>
+ <entry>Setting</entry>
+ <entry>Description</entry>
+ </row>
+ </thead>
+ <tbody>
+ <row>
+ <entry>
+ <varname>lsbitfirst</varname>
+ <parameter>yes|no</parameter>
+ </entry>
+ <entry>
+ Decode the least significant bit first. Default is
+ "no".
+ </entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </informaltable>
+ </section>
+
+ <section>
<title><varname>mikmod</varname></title>
<para>
@@ -658,6 +841,178 @@ cd mpd-version</programlisting>
</section>
<section>
+ <title>Encoder plugins</title>
+
+ <section>
+ <title><varname>flac</varname></title>
+
+ <para>
+ Encodes into FLAC (lossless).
+ </para>
+
+ <informaltable>
+ <tgroup cols="2">
+ <thead>
+ <row>
+ <entry>Setting</entry>
+ <entry>Description</entry>
+ </row>
+ </thead>
+ <tbody>
+ <row>
+ <entry>
+ <varname>compression</varname>
+ </entry>
+ <entry>
+ Sets the <filename>libFLAC</filename> compression
+ level. The levels range from 0 (fastest, least
+ compression) to 8 (slowest, most compression).
+ </entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </informaltable>
+ </section>
+
+ <section>
+ <title><varname>lame</varname></title>
+
+ <para>
+ Encodes into MP3 using the LAME library.
+ </para>
+
+ <informaltable>
+ <tgroup cols="2">
+ <thead>
+ <row>
+ <entry>Setting</entry>
+ <entry>Description</entry>
+ </row>
+ </thead>
+ <tbody>
+ <row>
+ <entry>
+ <varname>quality</varname>
+ </entry>
+ <entry>
+ Sets the quality for VBR. 0 is the highest quality,
+ 9 is the lowest quality. Cannot be used with
+ <varname>bitrate</varname>.
+ </entry>
+ </row>
+ <row>
+ <entry>
+ <varname>bitrate</varname>
+ </entry>
+ <entry>
+ Sets the bit rate in kilobit per second. Cannot be
+ used with <varname>quality</varname>.
+ </entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </informaltable>
+ </section>
+
+ <section>
+ <title><varname>null</varname></title>
+
+ <para>
+ Does not encode anything, passes the input PCM data as-is.
+ </para>
+ </section>
+
+ <section>
+ <title><varname>twolame</varname></title>
+
+ <para>
+ Encodes into MP2 using the <filename>twolame</filename>
+ library.
+ </para>
+
+ <informaltable>
+ <tgroup cols="2">
+ <thead>
+ <row>
+ <entry>Setting</entry>
+ <entry>Description</entry>
+ </row>
+ </thead>
+ <tbody>
+ <row>
+ <entry>
+ <varname>quality</varname>
+ </entry>
+ <entry>
+ Sets the quality for VBR. 0 is the highest quality,
+ 9 is the lowest quality. Cannot be used with
+ <varname>bitrate</varname>.
+ </entry>
+ </row>
+ <row>
+ <entry>
+ <varname>bitrate</varname>
+ </entry>
+ <entry>
+ Sets the bit rate in kilobit per second. Cannot be
+ used with <varname>quality</varname>.
+ </entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </informaltable>
+ </section>
+
+ <section>
+ <title><varname>vorbis</varname></title>
+
+ <para>
+ Encodes into Ogg Vorbis.
+ </para>
+
+ <informaltable>
+ <tgroup cols="2">
+ <thead>
+ <row>
+ <entry>Setting</entry>
+ <entry>Description</entry>
+ </row>
+ </thead>
+ <tbody>
+ <row>
+ <entry>
+ <varname>quality</varname>
+ </entry>
+ <entry>
+ Sets the quality for VBR. -1 is the lowest quality,
+ 10 is the highest quality. Cannot be used with
+ <varname>bitrate</varname>.
+ </entry>
+ </row>
+ <row>
+ <entry>
+ <varname>bitrate</varname>
+ </entry>
+ <entry>
+ Sets the bit rate in kilobit per second. Cannot be
+ used with <varname>quality</varname>.
+ </entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </informaltable>
+ </section>
+
+ <section>
+ <title><varname>wave</varname></title>
+
+ <para>
+ Encodes into WAV (lossless).
+ </para>
+ </section>
+ </section>
+
+ <section>
<title>Output plugins</title>
<section>
@@ -764,6 +1119,23 @@ cd mpd-version</programlisting>
bit, floating point, ...).
</entry>
</row>
+ <row>
+ <entry>
+ <varname>dsd_usb</varname>
+ <parameter>yes|no</parameter>
+ </entry>
+ <entry>
+ If set to <parameter>yes</parameter>, then DSD over
+ USB according to the <ulink
+ url="http://www.sonore.us/DoP_openStandard_1v1.pdf">pro
+ posed standard by dCS and others</ulink> is enabled. This wraps
+ DSD samples in fake 24 bit PCM, and is understood by
+ some DSD capable products, but may be harmful to
+ other hardware. Therefore, the default is
+ <parameter>no</parameter> and you can enable the
+ option at your own risk.
+ </entry>
+ </row>
</tbody>
</tgroup>
</informaltable>
@@ -1366,6 +1738,15 @@ cd mpd-version</programlisting>
</row>
<row>
<entry>
+ <varname>url</varname>
+ <parameter>URL</parameter>
+ </entry>
+ <entry>
+ Sets a URL associated with the stream (optional).
+ </entry>
+ </row>
+ <row>
+ <entry>
<varname>public</varname>
<parameter>yes|no</parameter>
</entry>
@@ -1467,6 +1848,14 @@ cd mpd-version</programlisting>
</section>
<section>
+ <title><varname>embcue</varname></title>
+
+ <para>
+ Reads CUE sheets from the "CUESHEET" tag of song files.
+ </para>
+ </section>
+
+ <section>
<title><varname>m3u</varname></title>
<para>
@@ -1498,6 +1887,27 @@ cd mpd-version</programlisting>
playlist files.
</para>
</section>
+
+ <section>
+ <title><varname>despotify</varname></title>
+
+ <para>
+ Adds <ulink url="http://www.spotify.com/">Spotify</ulink>
+ playlists. Spotify playlists use the <filename>spt://</filename> URI,
+ and a Spotify playlist URL. So for example, you can load a playlist
+ with
+ </para>
+
+ <para>
+ <filename>mpc load spt://spotify:user:simon.kagstrom:playlist:3SUwkOe5VbVHysZcidEZtH</filename>
+ </para>
+
+ <para>
+ See the despotify input plugin for configuration options (username
+ and password needs to be setup)
+ </para>
+ </section>
+
</section>
</chapter>
</book>
diff --git a/m4/faad.m4 b/m4/faad.m4
index 007787557..1048c566c 100644
--- a/m4/faad.m4
+++ b/m4/faad.m4
@@ -39,8 +39,8 @@ if test x$enable_aac = xyes; then
oldcflags=$CFLAGS
oldlibs=$LIBS
oldcppflags=$CPPFLAGS
- CFLAGS="$CFLAGS $MPD_CFLAGS $FAAD_CFLAGS -I."
- LIBS="$LIBS $MPD_LIBS $FAAD_LIBS"
+ CFLAGS="$CFLAGS $FAAD_CFLAGS -I."
+ LIBS="$LIBS $FAAD_LIBS"
CPPFLAGS=$CFLAGS
AC_CHECK_HEADER(faad.h,,enable_aac=no)
if test x$enable_aac = xyes; then
@@ -50,10 +50,10 @@ if test x$enable_aac = xyes; then
AC_CHECK_DECL(faacDecInit2,,enable_aac=no,[#include <faad.h>])
fi
if test x$enable_aac = xyes; then
- AC_CHECK_LIB(faad,faacDecInit2,[MPD_LIBS="$MPD_LIBS $FAAD_LIBS";MPD_CFLAGS="$MPD_CFLAGS $FAAD_CFLAGS"],enable_aac=no)
+ AC_CHECK_LIB(faad,faacDecInit2,,enable_aac=no)
if test x$enable_aac = xno; then
enable_aac=yes
- AC_CHECK_LIB(faad,NeAACDecInit2,[MPD_LIBS="$MPD_LIBS $FAAD_LIBS";MPD_CFLAGS="$MPD_CFLAGS $FAAD_CFLAGS"],enable_aac=no)
+ AC_CHECK_LIB(faad,NeAACDecInit2,,enable_aac=no)
fi
fi
if test x$enable_aac = xyes; then
@@ -131,8 +131,8 @@ if test x$enable_aac = xyes; then
oldcflags=$CFLAGS
oldlibs=$LIBS
oldcppflags=$CPPFLAGS
- CFLAGS="$CFLAGS $MPD_CFLAGS $FAAD_CFLAGS -Werror"
- LIBS="$LIBS $MPD_LIBS $FAAD_LIBS"
+ CFLAGS="$CFLAGS $FAAD_CFLAGS -Werror"
+ LIBS="$LIBS $FAAD_LIBS"
CPPFLAGS=$CFLAGS
AC_MSG_CHECKING(for broken libfaad headers)
@@ -188,5 +188,11 @@ if test x$enable_aac = xyes; then
CPPFLAGS=$oldcppflags
else
enable_mp4=no
+ FAAD_CFLAGS=""
+ FAAD_LIBS=""
fi
+
+AC_SUBST(FAAD_CFLAGS)
+AC_SUBST(FAAD_LIBS)
+
])
diff --git a/m4/ucred.m4 b/m4/ucred.m4
index 3a8245245..cdc6ea3b3 100644
--- a/m4/ucred.m4
+++ b/m4/ucred.m4
@@ -20,7 +20,7 @@ AC_DEFUN([STRUCT_UCRED],[
mpd_cv_have_struct_ucred=no)
if test x$mpd_cv_have_struct_ucred = xyes; then
# :(
- MPD_CFLAGS="$MPD_CFLAGS -D_GNU_SOURCE"
+ CFLAGS="$CFLAGS -D_GNU_SOURCE"
fi
fi
])
diff --git a/src/ack.h b/src/ack.h
index af3d1be9a..440bc27d8 100644
--- a/src/ack.h
+++ b/src/ack.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -20,6 +20,8 @@
#ifndef MPD_ACK_H
#define MPD_ACK_H
+#include <glib.h>
+
enum ack {
ACK_ERROR_NOT_LIST = 1,
ACK_ERROR_ARG = 2,
@@ -36,4 +38,14 @@ enum ack {
ACK_ERROR_EXIST = 56,
};
+/**
+ * Quark for GError.domain; the code is an enum #ack.
+ */
+G_GNUC_CONST
+static inline GQuark
+ack_quark(void)
+{
+ return g_quark_from_static_string("ack");
+}
+
#endif
diff --git a/src/aiff.c b/src/aiff.c
index e2ca0dfe4..06de9ffe7 100644
--- a/src/aiff.c
+++ b/src/aiff.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -57,7 +57,7 @@ aiff_seek_id3(FILE *file)
ret = fstat(fileno(file), &st);
if (ret < 0) {
g_warning("Failed to stat file descriptor: %s",
- strerror(errno));
+ g_strerror(errno));
return 0;
}
diff --git a/src/aiff.h b/src/aiff.h
index 52c0a73ec..a0ae2d41a 100644
--- a/src/aiff.h
+++ b/src/aiff.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/ape.c b/src/ape.c
index 5f4da3f2e..6257fe6b3 100644
--- a/src/ape.c
+++ b/src/ape.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/ape.h b/src/ape.h
index 754b9bb2d..c2b271b15 100644
--- a/src/ape.h
+++ b/src/ape.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/archive/bz2_archive_plugin.c b/src/archive/bz2_archive_plugin.c
index 2414eb519..e2420048b 100644
--- a/src/archive/bz2_archive_plugin.c
+++ b/src/archive/bz2_archive_plugin.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -24,6 +24,7 @@
#include "config.h"
#include "archive/bz2_archive_plugin.h"
#include "archive_api.h"
+#include "input_internal.h"
#include "input_plugin.h"
#include "refcount.h"
@@ -102,6 +103,11 @@ bz2_destroy(struct bz2_input_stream *data)
/* archive open && listing routine */
+#if GCC_CHECK_VERSION(4, 2)
+/* workaround for a warning caused by G_STATIC_MUTEX_INIT */
+#pragma GCC diagnostic ignored "-Wmissing-field-initializers"
+#endif
+
static struct archive_file *
bz2_open(const char *pathname, GError **error_r)
{
@@ -113,7 +119,11 @@ bz2_open(const char *pathname, GError **error_r)
refcount_init(&context->ref);
//open archive
- context->istream = input_stream_open(pathname, error_r);
+ static GStaticMutex mutex = G_STATIC_MUTEX_INIT;
+ context->istream = input_stream_open(pathname,
+ g_static_mutex_get_mutex(&mutex),
+ NULL,
+ error_r);
if (context->istream == NULL) {
g_free(context);
return NULL;
@@ -168,12 +178,15 @@ bz2_close(struct archive_file *file)
/* single archive handling */
static struct input_stream *
-bz2_open_stream(struct archive_file *file, const char *path, GError **error_r)
+bz2_open_stream(struct archive_file *file, const char *path,
+ GMutex *mutex, GCond *cond,
+ GError **error_r)
{
struct bz2_archive_file *context = (struct bz2_archive_file *) file;
struct bz2_input_stream *bis = g_new(struct bz2_input_stream, 1);
- input_stream_init(&bis->base, &bz2_inputplugin, path);
+ input_stream_init(&bis->base, &bz2_inputplugin, path,
+ mutex, cond);
bis->archive = context;
diff --git a/src/archive/bz2_archive_plugin.h b/src/archive/bz2_archive_plugin.h
index 199049008..46c69a66c 100644
--- a/src/archive/bz2_archive_plugin.h
+++ b/src/archive/bz2_archive_plugin.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/archive/iso9660_archive_plugin.c b/src/archive/iso9660_archive_plugin.c
index 142fa10e0..bb6cb9588 100644
--- a/src/archive/iso9660_archive_plugin.c
+++ b/src/archive/iso9660_archive_plugin.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -24,6 +24,7 @@
#include "config.h"
#include "archive/iso9660_archive_plugin.h"
#include "archive_api.h"
+#include "input_internal.h"
#include "input_plugin.h"
#include "refcount.h"
@@ -172,15 +173,17 @@ struct iso9660_input_stream {
};
static struct input_stream *
-iso9660_archive_open_stream(struct archive_file *file,
- const char *pathname, GError **error_r)
+iso9660_archive_open_stream(struct archive_file *file, const char *pathname,
+ GMutex *mutex, GCond *cond,
+ GError **error_r)
{
struct iso9660_archive_file *context =
(struct iso9660_archive_file *)file;
struct iso9660_input_stream *iis;
iis = g_new(struct iso9660_input_stream, 1);
- input_stream_init(&iis->base, &iso9660_input_plugin, pathname);
+ input_stream_init(&iis->base, &iso9660_input_plugin, pathname,
+ mutex, cond);
iis->archive = context;
iis->statbuf = iso9660_ifs_stat_translate(context->iso, pathname);
diff --git a/src/archive/iso9660_archive_plugin.h b/src/archive/iso9660_archive_plugin.h
index 2a3864cee..47dc6e474 100644
--- a/src/archive/iso9660_archive_plugin.h
+++ b/src/archive/iso9660_archive_plugin.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/archive/zzip_archive_plugin.c b/src/archive/zzip_archive_plugin.c
index 3c2b80318..ad96b5f89 100644
--- a/src/archive/zzip_archive_plugin.c
+++ b/src/archive/zzip_archive_plugin.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -25,6 +25,7 @@
#include "archive/zzip_archive_plugin.h"
#include "archive_api.h"
#include "archive_api.h"
+#include "input_internal.h"
#include "input_plugin.h"
#include "refcount.h"
@@ -134,14 +135,17 @@ struct zzip_input_stream {
static struct input_stream *
zzip_archive_open_stream(struct archive_file *file,
- const char *pathname, GError **error_r)
+ const char *pathname,
+ GMutex *mutex, GCond *cond,
+ GError **error_r)
{
struct zzip_archive *context = (struct zzip_archive *) file;
struct zzip_input_stream *zis;
ZZIP_STAT z_stat;
zis = g_new(struct zzip_input_stream, 1);
- input_stream_init(&zis->base, &zzip_input_plugin, pathname);
+ input_stream_init(&zis->base, &zzip_input_plugin, pathname,
+ mutex, cond);
zis->archive = context;
zis->file = zzip_file_open(context->dir, pathname, 0);
diff --git a/src/archive/zzip_archive_plugin.h b/src/archive/zzip_archive_plugin.h
index 6d5037eef..2b2c01e5a 100644
--- a/src/archive/zzip_archive_plugin.h
+++ b/src/archive/zzip_archive_plugin.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/archive_api.c b/src/archive_api.c
index b15810f1b..be3c35f7e 100644
--- a/src/archive_api.c
+++ b/src/archive_api.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/archive_api.h b/src/archive_api.h
index f08960c72..4e0f603f5 100644
--- a/src/archive_api.h
+++ b/src/archive_api.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/archive_internal.h b/src/archive_internal.h
index 03439e826..0d885e91c 100644
--- a/src/archive_internal.h
+++ b/src/archive_internal.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/archive_list.c b/src/archive_list.c
index 2656726b5..24aa060c9 100644
--- a/src/archive_list.c
+++ b/src/archive_list.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -20,7 +20,7 @@
#include "config.h"
#include "archive_list.h"
#include "archive_plugin.h"
-#include "utils.h"
+#include "string_util.h"
#include "archive/bz2_archive_plugin.h"
#include "archive/iso9660_archive_plugin.h"
#include "archive/zzip_archive_plugin.h"
diff --git a/src/archive_list.h b/src/archive_list.h
index b65245ce9..24e4063bf 100644
--- a/src/archive_list.h
+++ b/src/archive_list.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/archive_plugin.c b/src/archive_plugin.c
index 60da4d283..cf23e6393 100644
--- a/src/archive_plugin.c
+++ b/src/archive_plugin.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -81,12 +81,14 @@ archive_file_scan_next(struct archive_file *file)
}
struct input_stream *
-archive_file_open_stream(struct archive_file *file,
- const char *path, GError **error_r)
+archive_file_open_stream(struct archive_file *file, const char *path,
+ GMutex *mutex, GCond *cond,
+ GError **error_r)
{
assert(file != NULL);
assert(file->plugin != NULL);
assert(file->plugin->open_stream != NULL);
- return file->plugin->open_stream(file, path, error_r);
+ return file->plugin->open_stream(file, path, mutex, cond,
+ error_r);
}
diff --git a/src/archive_plugin.h b/src/archive_plugin.h
index b08c93389..b7b92446d 100644
--- a/src/archive_plugin.h
+++ b/src/archive_plugin.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -68,11 +68,12 @@ struct archive_plugin {
* Opens an input_stream of a file within the archive.
*
* @param path the path within the archive
- * @param error_r location to store the error occuring, or
+ * @param error_r location to store the error occurring, or
* NULL to ignore errors
*/
struct input_stream *(*open_stream)(struct archive_file *af,
const char *path,
+ GMutex *mutex, GCond *cond,
GError **error_r);
/**
@@ -101,7 +102,8 @@ char *
archive_file_scan_next(struct archive_file *file);
struct input_stream *
-archive_file_open_stream(struct archive_file *file,
- const char *path, GError **error_r);
+archive_file_open_stream(struct archive_file *file, const char *path,
+ GMutex *mutex, GCond *cond,
+ GError **error_r);
#endif
diff --git a/src/audio_check.c b/src/audio_check.c
index 61d2c5833..a9aa2dd82 100644
--- a/src/audio_check.c
+++ b/src/audio_check.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/audio_check.h b/src/audio_check.h
index 4862e7f15..9f71cf9c0 100644
--- a/src/audio_check.h
+++ b/src/audio_check.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -28,6 +28,7 @@
/**
* The GLib quark used for errors reported by this library.
*/
+G_GNUC_CONST
static inline GQuark
audio_format_quark(void)
{
diff --git a/src/audio.c b/src/audio_config.c
index f9894cf3c..72869c384 100644
--- a/src/audio.c
+++ b/src/audio_config.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -18,7 +18,7 @@
*/
#include "config.h"
-#include "audio.h"
+#include "audio_config.h"
#include "audio_format.h"
#include "audio_parser.h"
#include "output_internal.h"
diff --git a/src/audio.h b/src/audio_config.h
index cb3ab7bbe..85143247f 100644
--- a/src/audio.h
+++ b/src/audio_config.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -17,8 +17,8 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
-#ifndef MPD_AUDIO_H
-#define MPD_AUDIO_H
+#ifndef MPD_AUDIO_CONFIG_H
+#define MPD_AUDIO_CONFIG_H
#include <stdbool.h>
diff --git a/src/audio_format.c b/src/audio_format.c
index 13403fbc1..45d94a853 100644
--- a/src/audio_format.c
+++ b/src/audio_format.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -22,11 +22,24 @@
#include <assert.h>
#include <stdio.h>
-#if G_BYTE_ORDER == G_BIG_ENDIAN
-#define REVERSE_ENDIAN_SUFFIX "_le"
-#else
-#define REVERSE_ENDIAN_SUFFIX "_be"
-#endif
+void
+audio_format_mask_apply(struct audio_format *af,
+ const struct audio_format *mask)
+{
+ assert(audio_format_valid(af));
+ assert(audio_format_mask_valid(mask));
+
+ if (mask->sample_rate != 0)
+ af->sample_rate = mask->sample_rate;
+
+ if (mask->format != SAMPLE_FORMAT_UNDEFINED)
+ af->format = mask->format;
+
+ if (mask->channels != 0)
+ af->channels = mask->channels;
+
+ assert(audio_format_valid(af));
+}
const char *
sample_format_to_string(enum sample_format format)
@@ -41,14 +54,17 @@ sample_format_to_string(enum sample_format format)
case SAMPLE_FORMAT_S16:
return "16";
- case SAMPLE_FORMAT_S24:
- return "24_3";
-
case SAMPLE_FORMAT_S24_P32:
return "24";
case SAMPLE_FORMAT_S32:
return "32";
+
+ case SAMPLE_FORMAT_FLOAT:
+ return "f";
+
+ case SAMPLE_FORMAT_DSD:
+ return "dsd";
}
/* unreachable */
@@ -63,9 +79,8 @@ audio_format_to_string(const struct audio_format *af,
assert(af != NULL);
assert(s != NULL);
- snprintf(s->buffer, sizeof(s->buffer), "%u:%s%s:%u",
+ snprintf(s->buffer, sizeof(s->buffer), "%u:%s:%u",
af->sample_rate, sample_format_to_string(af->format),
- af->reverse_endian ? REVERSE_ENDIAN_SUFFIX : "",
af->channels);
return s->buffer;
diff --git a/src/audio_format.h b/src/audio_format.h
index a4450ad71..bf77add3b 100644
--- a/src/audio_format.h
+++ b/src/audio_format.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -20,6 +20,7 @@
#ifndef MPD_AUDIO_FORMAT_H
#define MPD_AUDIO_FORMAT_H
+#include <glib.h>
#include <stdint.h>
#include <stdbool.h>
#include <assert.h>
@@ -31,19 +32,28 @@ enum sample_format {
SAMPLE_FORMAT_S16,
/**
- * Signed 24 bit integer samples, without padding.
- */
- SAMPLE_FORMAT_S24,
-
- /**
* Signed 24 bit integer samples, packed in 32 bit integers
* (the most significant byte is filled with the sign bit).
*/
SAMPLE_FORMAT_S24_P32,
SAMPLE_FORMAT_S32,
+
+ /**
+ * 32 bit floating point samples in the host's format. The
+ * range is -1.0f to +1.0f.
+ */
+ SAMPLE_FORMAT_FLOAT,
+
+ /**
+ * Direct Stream Digital. 1-bit samples; each frame has one
+ * byte (8 samples) per channel.
+ */
+ SAMPLE_FORMAT_DSD,
};
+static const unsigned MAX_CHANNELS = 8;
+
/**
* This structure describes the format of a raw PCM stream.
*/
@@ -66,13 +76,6 @@ struct audio_format {
* fully supported currently.
*/
uint8_t channels;
-
- /**
- * If zero, then samples are stored in host byte order. If
- * nonzero, then samples are stored in the reverse host byte
- * order.
- */
- uint8_t reverse_endian;
};
/**
@@ -91,7 +94,6 @@ static inline void audio_format_clear(struct audio_format *af)
af->sample_rate = 0;
af->format = SAMPLE_FORMAT_UNDEFINED;
af->channels = 0;
- af->reverse_endian = 0;
}
/**
@@ -105,7 +107,6 @@ static inline void audio_format_init(struct audio_format *af,
af->sample_rate = sample_rate;
af->format = (uint8_t)format;
af->channels = channels;
- af->reverse_endian = 0;
}
/**
@@ -162,9 +163,10 @@ audio_valid_sample_format(enum sample_format format)
switch (format) {
case SAMPLE_FORMAT_S8:
case SAMPLE_FORMAT_S16:
- case SAMPLE_FORMAT_S24:
case SAMPLE_FORMAT_S24_P32:
case SAMPLE_FORMAT_S32:
+ case SAMPLE_FORMAT_FLOAT:
+ case SAMPLE_FORMAT_DSD:
return true;
case SAMPLE_FORMAT_UNDEFINED:
@@ -180,13 +182,14 @@ audio_valid_sample_format(enum sample_format format)
static inline bool
audio_valid_channel_count(unsigned channels)
{
- return channels >= 1 && channels <= 8;
+ return channels >= 1 && channels <= MAX_CHANNELS;
}
/**
* Returns false if the format is not valid for playback with MPD.
* This function performs some basic validity checks.
*/
+G_GNUC_PURE
static inline bool audio_format_valid(const struct audio_format *af)
{
return audio_valid_sample_rate(af->sample_rate) &&
@@ -198,6 +201,7 @@ static inline bool audio_format_valid(const struct audio_format *af)
* Returns false if the format mask is not valid for playback with
* MPD. This function performs some basic validity checks.
*/
+G_GNUC_PURE
static inline bool audio_format_mask_valid(const struct audio_format *af)
{
return (af->sample_rate == 0 ||
@@ -212,58 +216,54 @@ static inline bool audio_format_equals(const struct audio_format *a,
{
return a->sample_rate == b->sample_rate &&
a->format == b->format &&
- a->channels == b->channels &&
- a->reverse_endian == b->reverse_endian;
+ a->channels == b->channels;
}
-static inline void
+void
audio_format_mask_apply(struct audio_format *af,
- const struct audio_format *mask)
-{
- assert(audio_format_valid(af));
- assert(audio_format_mask_valid(mask));
-
- if (mask->sample_rate != 0)
- af->sample_rate = mask->sample_rate;
-
- if (mask->format != SAMPLE_FORMAT_UNDEFINED)
- af->format = mask->format;
-
- if (mask->channels != 0)
- af->channels = mask->channels;
-
- assert(audio_format_valid(af));
-}
+ const struct audio_format *mask);
-/**
- * Returns the size of each (mono) sample in bytes.
- */
-static inline unsigned audio_format_sample_size(const struct audio_format *af)
+G_GNUC_CONST
+static inline unsigned
+sample_format_size(enum sample_format format)
{
- switch (af->format) {
+ switch (format) {
case SAMPLE_FORMAT_S8:
return 1;
case SAMPLE_FORMAT_S16:
return 2;
- case SAMPLE_FORMAT_S24:
- return 3;
-
case SAMPLE_FORMAT_S24_P32:
case SAMPLE_FORMAT_S32:
+ case SAMPLE_FORMAT_FLOAT:
return 4;
+ case SAMPLE_FORMAT_DSD:
+ /* each frame has 8 samples per channel */
+ return 1;
+
case SAMPLE_FORMAT_UNDEFINED:
- break;
+ return 0;
}
+ assert(false);
return 0;
}
/**
+ * Returns the size of each (mono) sample in bytes.
+ */
+G_GNUC_PURE
+static inline unsigned audio_format_sample_size(const struct audio_format *af)
+{
+ return sample_format_size((enum sample_format)af->format);
+}
+
+/**
* Returns the size of each full frame in bytes.
*/
+G_GNUC_PURE
static inline unsigned
audio_format_frame_size(const struct audio_format *af)
{
@@ -274,6 +274,7 @@ audio_format_frame_size(const struct audio_format *af)
* Returns the floating point factor which converts a time span to a
* storage size in bytes.
*/
+G_GNUC_PURE
static inline double audio_format_time_to_size(const struct audio_format *af)
{
return af->sample_rate * audio_format_frame_size(af);
@@ -286,6 +287,7 @@ static inline double audio_format_time_to_size(const struct audio_format *af)
* @param format a #sample_format enum value
* @return the string
*/
+G_GNUC_PURE G_GNUC_MALLOC
const char *
sample_format_to_string(enum sample_format format);
@@ -297,6 +299,7 @@ sample_format_to_string(enum sample_format format);
* @param s a buffer to print into
* @return the string, or NULL if the #audio_format object is invalid
*/
+G_GNUC_PURE G_GNUC_MALLOC
const char *
audio_format_to_string(const struct audio_format *af,
struct audio_format_string *s);
diff --git a/src/audio_parser.c b/src/audio_parser.c
index 139cf1c04..b1be8887a 100644
--- a/src/audio_parser.c
+++ b/src/audio_parser.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -81,6 +81,18 @@ parse_sample_format(const char *src, bool mask,
return true;
}
+ if (*src == 'f') {
+ *sample_format_r = SAMPLE_FORMAT_FLOAT;
+ *endptr_r = src + 1;
+ return true;
+ }
+
+ if (memcmp(src, "dsd", 3) == 0) {
+ *sample_format_r = SAMPLE_FORMAT_DSD;
+ *endptr_r = src + 3;
+ return true;
+ }
+
value = strtoul(src, &endptr, 10);
if (endptr == src) {
g_set_error(error_r, audio_parser_quark(), 0,
@@ -98,11 +110,11 @@ parse_sample_format(const char *src, bool mask,
break;
case 24:
- if (memcmp(endptr, "_3", 2) == 0) {
- sample_format = SAMPLE_FORMAT_S24;
+ if (memcmp(endptr, "_3", 2) == 0)
+ /* for backwards compatibility */
endptr += 2;
- } else
- sample_format = SAMPLE_FORMAT_S24_P32;
+
+ sample_format = SAMPLE_FORMAT_S24_P32;
break;
case 32:
diff --git a/src/audio_parser.h b/src/audio_parser.h
index 214ec5eb1..a963eb467 100644
--- a/src/audio_parser.h
+++ b/src/audio_parser.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -38,7 +38,7 @@ struct audio_format;
* @param dest the destination #audio_format struct
* @param src the input string
* @param mask if true, then "*" is allowed for any number of items
- * @param error_r location to store the error occuring, or NULL to
+ * @param error_r location to store the error occurring, or NULL to
* ignore errors
* @return true on success
*/
diff --git a/src/buffer.c b/src/buffer.c
index bee871700..559f39a9a 100644
--- a/src/buffer.c
+++ b/src/buffer.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/buffer.h b/src/buffer.h
index 75e5bc6e6..f860231e7 100644
--- a/src/buffer.h
+++ b/src/buffer.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/check.h b/src/check.h
index 56061621f..0642a4b91 100644
--- a/src/check.h
+++ b/src/check.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/chunk.c b/src/chunk.c
index 79597506d..1eb96f4b9 100644
--- a/src/chunk.c
+++ b/src/chunk.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/chunk.h b/src/chunk.h
index 02e7b3650..a06a203eb 100644
--- a/src/chunk.h
+++ b/src/chunk.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/client.c b/src/client.c
index 9668c9249..3fa2c9be4 100644
--- a/src/client.c
+++ b/src/client.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/client.h b/src/client.h
index d46747b4f..0302a2e0a 100644
--- a/src/client.h
+++ b/src/client.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -27,20 +27,36 @@
struct client;
struct sockaddr;
+struct player_control;
void client_manager_init(void);
void client_manager_deinit(void);
-void client_new(int fd, const struct sockaddr *sa, size_t sa_length, int uid);
+void client_new(struct player_control *player_control,
+ int fd, const struct sockaddr *sa, size_t sa_length, int uid);
+G_GNUC_PURE
bool client_is_expired(const struct client *client);
/**
* returns the uid of the client process, or a negative value if the
* uid is unknown
*/
+G_GNUC_PURE
int client_get_uid(const struct client *client);
+/**
+ * Is this client running on the same machine, connected with a local
+ * (UNIX domain) socket?
+ */
+G_GNUC_PURE
+static inline bool
+client_is_local(const struct client *client)
+{
+ return client_get_uid(client) > 0;
+}
+
+G_GNUC_PURE
unsigned client_get_permission(const struct client *client);
void client_set_permission(struct client *client, unsigned permission);
@@ -60,17 +76,4 @@ void client_vprintf(struct client *client, const char *fmt, va_list args);
*/
G_GNUC_PRINTF(2, 3) void client_printf(struct client *client, const char *fmt, ...);
-/**
- * Adds the specified idle flags to all clients and immediately sends
- * notifications to all waiting clients.
- */
-void client_manager_idle_add(unsigned flags);
-
-/**
- * Checks whether the client has pending idle flags. If yes, they are
- * sent immediately and "true" is returned". If no, it puts the
- * client into waiting mode and returns false.
- */
-bool client_idle_wait(struct client *client, unsigned flags);
-
#endif
diff --git a/src/client_event.c b/src/client_event.c
index 93f5a9df7..4f54ae0a7 100644
--- a/src/client_event.c
+++ b/src/client_event.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/client_expire.c b/src/client_expire.c
index a5b0be047..1ca32ebcc 100644
--- a/src/client_expire.c
+++ b/src/client_expire.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/client_file.c b/src/client_file.c
new file mode 100644
index 000000000..2ee433308
--- /dev/null
+++ b/src/client_file.c
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2003-2012 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "client_file.h"
+#include "client.h"
+#include "ack.h"
+
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <errno.h>
+#include <unistd.h>
+
+bool
+client_allow_file(const struct client *client, const char *path_fs,
+ GError **error_r)
+{
+#ifdef WIN32
+ (void)client;
+ (void)path_fs;
+
+ g_set_error(error_r, ack_quark(), ACK_ERROR_PERMISSION,
+ "Access denied");
+ return false;
+#else
+ const int uid = client_get_uid(client);
+ if (uid >= 0 && (uid_t)uid == geteuid())
+ /* always allow access if user runs his own MPD
+ instance */
+ return true;
+
+ if (uid <= 0) {
+ /* unauthenticated client */
+ g_set_error(error_r, ack_quark(), ACK_ERROR_PERMISSION,
+ "Access denied");
+ return false;
+ }
+
+ struct stat st;
+ if (stat(path_fs, &st) < 0) {
+ g_set_error(error_r, g_file_error_quark(), errno,
+ "%s", g_strerror(errno));
+ return false;
+ }
+
+ if (st.st_uid != (uid_t)uid && (st.st_mode & 0444) != 0444) {
+ /* client is not owner */
+ g_set_error(error_r, ack_quark(), ACK_ERROR_PERMISSION,
+ "Access denied");
+ return false;
+ }
+
+ return true;
+#endif
+}
diff --git a/src/client_file.h b/src/client_file.h
new file mode 100644
index 000000000..3dcbe7500
--- /dev/null
+++ b/src/client_file.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2003-2012 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_CLIENT_FILE_H
+#define MPD_CLIENT_FILE_H
+
+#include <glib.h>
+#include <stdbool.h>
+
+struct client;
+
+/**
+ * Is this client allowed to use the specified local file?
+ *
+ * Note that this function is vulnerable to timing/symlink attacks.
+ * We cannot fix this as long as there are plugins that open a file by
+ * its name, and not by file descriptor / callbacks.
+ *
+ * @param path_fs the absolute path name in filesystem encoding
+ * @return true if access is allowed
+ */
+G_GNUC_PURE
+bool
+client_allow_file(const struct client *client, const char *path_fs,
+ GError **error_r);
+
+#endif
diff --git a/src/client_global.c b/src/client_global.c
index fc5adedba..adf3b2f9e 100644
--- a/src/client_global.c
+++ b/src/client_global.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/client_idle.c b/src/client_idle.c
index 10be4d430..930911d6e 100644
--- a/src/client_idle.c
+++ b/src/client_idle.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -18,6 +18,7 @@
*/
#include "config.h"
+#include "client_idle.h"
#include "client_internal.h"
#include "idle.h"
@@ -50,12 +51,9 @@ client_idle_notify(struct client *client)
g_timer_start(client->last_activity);
}
-static void
-client_idle_callback(gpointer data, gpointer user_data)
+void
+client_idle_add(struct client *client, unsigned flags)
{
- struct client *client = data;
- unsigned flags = GPOINTER_TO_UINT(user_data);
-
if (client_is_expired(client))
return;
@@ -67,6 +65,15 @@ client_idle_callback(gpointer data, gpointer user_data)
}
}
+static void
+client_idle_callback(gpointer data, gpointer user_data)
+{
+ struct client *client = data;
+ unsigned flags = GPOINTER_TO_UINT(user_data);
+
+ client_idle_add(client, flags);
+}
+
void client_manager_idle_add(unsigned flags)
{
assert(flags != 0);
diff --git a/src/client_idle.h b/src/client_idle.h
new file mode 100644
index 000000000..c56fd014c
--- /dev/null
+++ b/src/client_idle.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_CLIENT_IDLE_H
+#define MPD_CLIENT_IDLE_H
+
+#include <stdbool.h>
+
+struct client;
+
+void
+client_idle_add(struct client *client, unsigned flags);
+
+/**
+ * Adds the specified idle flags to all clients and immediately sends
+ * notifications to all waiting clients.
+ */
+void
+client_manager_idle_add(unsigned flags);
+
+/**
+ * Checks whether the client has pending idle flags. If yes, they are
+ * sent immediately and "true" is returned". If no, it puts the
+ * client into waiting mode and returns false.
+ */
+bool
+client_idle_wait(struct client *client, unsigned flags);
+
+#endif
diff --git a/src/client_internal.h b/src/client_internal.h
index 2b1b92433..ba97e4b8f 100644
--- a/src/client_internal.h
+++ b/src/client_internal.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -21,17 +21,25 @@
#define MPD_CLIENT_INTERNAL_H
#include "client.h"
+#include "client_message.h"
#include "command.h"
#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "client"
+enum {
+ CLIENT_MAX_SUBSCRIPTIONS = 16,
+ CLIENT_MAX_MESSAGES = 64,
+};
+
struct deferred_buffer {
size_t size;
char data[sizeof(long)];
};
struct client {
+ struct player_control *player_control;
+
GIOChannel *channel;
guint source_id;
@@ -67,6 +75,28 @@ struct client {
/** idle flags that the client wants to receive */
unsigned idle_subscriptions;
+
+ /**
+ * A list of channel names this client is subscribed to.
+ */
+ GSList *subscriptions;
+
+ /**
+ * The number of subscriptions in #subscriptions. Used to
+ * limit the number of subscriptions.
+ */
+ unsigned num_subscriptions;
+
+ /**
+ * A list of messages this client has received in reverse
+ * order (latest first).
+ */
+ GSList *messages;
+
+ /**
+ * The number of messages in #messages.
+ */
+ unsigned num_messages;
};
extern unsigned int client_max_connections;
diff --git a/src/client_list.c b/src/client_list.c
index 5332ed65f..2c7f37aff 100644
--- a/src/client_list.c
+++ b/src/client_list.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/client_message.c b/src/client_message.c
new file mode 100644
index 000000000..b681b4e7f
--- /dev/null
+++ b/src/client_message.c
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "client_message.h"
+
+#include <assert.h>
+#include <glib.h>
+
+G_GNUC_PURE
+static bool
+valid_channel_char(const char ch)
+{
+ return g_ascii_isalnum(ch) ||
+ ch == '_' || ch == '-' || ch == '.' || ch == ':';
+}
+
+bool
+client_message_valid_channel_name(const char *name)
+{
+ do {
+ if (!valid_channel_char(*name))
+ return false;
+ } while (*++name != 0);
+
+ return true;
+}
+
+void
+client_message_init_null(struct client_message *msg)
+{
+ assert(msg != NULL);
+
+ msg->channel = NULL;
+ msg->message = NULL;
+}
+
+void
+client_message_init(struct client_message *msg,
+ const char *channel, const char *message)
+{
+ assert(msg != NULL);
+
+ msg->channel = g_strdup(channel);
+ msg->message = g_strdup(message);
+}
+
+void
+client_message_copy(struct client_message *dest,
+ const struct client_message *src)
+{
+ assert(dest != NULL);
+ assert(src != NULL);
+ assert(client_message_defined(src));
+
+ client_message_init(dest, src->channel, src->message);
+}
+
+struct client_message *
+client_message_dup(const struct client_message *src)
+{
+ struct client_message *dest = g_slice_new(struct client_message);
+ client_message_copy(dest, src);
+ return dest;
+}
+
+void
+client_message_deinit(struct client_message *msg)
+{
+ assert(msg != NULL);
+
+ g_free(msg->channel);
+ g_free(msg->message);
+}
+
+void
+client_message_free(struct client_message *msg)
+{
+ client_message_deinit(msg);
+ g_slice_free(struct client_message, msg);
+}
diff --git a/src/client_message.h b/src/client_message.h
new file mode 100644
index 000000000..38c2e7615
--- /dev/null
+++ b/src/client_message.h
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_CLIENT_MESSAGE_H
+#define MPD_CLIENT_MESSAGE_H
+
+#include <assert.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <glib.h>
+
+/**
+ * A client-to-client message.
+ */
+struct client_message {
+ char *channel;
+
+ char *message;
+};
+
+G_GNUC_PURE
+bool
+client_message_valid_channel_name(const char *name);
+
+G_GNUC_PURE
+static inline bool
+client_message_defined(const struct client_message *msg)
+{
+ assert(msg != NULL);
+ assert((msg->channel == NULL) == (msg->message == NULL));
+
+ return msg->channel != NULL;
+}
+
+void
+client_message_init_null(struct client_message *msg);
+
+void
+client_message_init(struct client_message *msg,
+ const char *channel, const char *message);
+
+void
+client_message_copy(struct client_message *dest,
+ const struct client_message *src);
+
+G_GNUC_MALLOC
+struct client_message *
+client_message_dup(const struct client_message *src);
+
+void
+client_message_deinit(struct client_message *msg);
+
+void
+client_message_free(struct client_message *msg);
+
+#endif
diff --git a/src/client_new.c b/src/client_new.c
index e764a6550..cf28c43c5 100644
--- a/src/client_new.c
+++ b/src/client_new.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -21,7 +21,7 @@
#include "client_internal.h"
#include "fd_util.h"
#include "fifo_buffer.h"
-#include "socket_util.h"
+#include "resolver.h"
#include "permission.h"
#include "glib_socket.h"
@@ -43,12 +43,15 @@
static const char GREETING[] = "OK MPD " PROTOCOL_VERSION "\n";
-void client_new(int fd, const struct sockaddr *sa, size_t sa_length, int uid)
+void
+client_new(struct player_control *player_control,
+ int fd, const struct sockaddr *sa, size_t sa_length, int uid)
{
static unsigned int next_client_num;
struct client *client;
char *remote;
+ assert(player_control != NULL);
assert(fd >= 0);
#ifdef HAVE_LIBWRAP
@@ -83,6 +86,7 @@ void client_new(int fd, const struct sockaddr *sa, size_t sa_length, int uid)
}
client = g_new0(struct client, 1);
+ client->player_control = player_control;
client->channel = g_io_channel_new_socket(fd);
/* GLib is responsible for closing the file descriptor */
@@ -115,6 +119,10 @@ void client_new(int fd, const struct sockaddr *sa, size_t sa_length, int uid)
client->send_buf_used = 0;
+ client->subscriptions = NULL;
+ client->messages = NULL;
+ client->num_messages = 0;
+
(void)send(fd, GREETING, sizeof(GREETING) - 1, 0);
client_list_add(client);
diff --git a/src/client_process.c b/src/client_process.c
index aeb75bb57..57a8a7824 100644
--- a/src/client_process.c
+++ b/src/client_process.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/client_read.c b/src/client_read.c
index 7a6bd3d5e..26ade264e 100644
--- a/src/client_read.c
+++ b/src/client_read.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/client_subscribe.c b/src/client_subscribe.c
new file mode 100644
index 000000000..c65a7ed31
--- /dev/null
+++ b/src/client_subscribe.c
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "client_subscribe.h"
+#include "client_internal.h"
+#include "client_idle.h"
+#include "idle.h"
+
+#include <string.h>
+
+G_GNUC_PURE
+static GSList *
+client_find_subscription(const struct client *client, const char *channel)
+{
+ for (GSList *i = client->subscriptions; i != NULL; i = g_slist_next(i))
+ if (strcmp((const char *)i->data, channel) == 0)
+ return i;
+
+ return NULL;
+}
+
+enum client_subscribe_result
+client_subscribe(struct client *client, const char *channel)
+{
+ assert(client != NULL);
+ assert(channel != NULL);
+
+ if (!client_message_valid_channel_name(channel))
+ return CLIENT_SUBSCRIBE_INVALID;
+
+ if (client_find_subscription(client, channel) != NULL)
+ return CLIENT_SUBSCRIBE_ALREADY;
+
+ if (client->num_subscriptions >= CLIENT_MAX_SUBSCRIPTIONS)
+ return CLIENT_SUBSCRIBE_FULL;
+
+ client->subscriptions = g_slist_prepend(client->subscriptions,
+ g_strdup(channel));
+ ++client->num_subscriptions;
+
+ idle_add(IDLE_SUBSCRIPTION);
+
+ return CLIENT_SUBSCRIBE_OK;
+}
+
+bool
+client_unsubscribe(struct client *client, const char *channel)
+{
+ GSList *i = client_find_subscription(client, channel);
+ if (i == NULL)
+ return false;
+
+ assert(client->num_subscriptions > 0);
+
+ client->subscriptions = g_slist_remove(client->subscriptions, i->data);
+ --client->num_subscriptions;
+
+ idle_add(IDLE_SUBSCRIPTION);
+
+ assert((client->num_subscriptions == 0) ==
+ (client->subscriptions == NULL));
+
+ return true;
+}
+
+void
+client_unsubscribe_all(struct client *client)
+{
+ for (GSList *i = client->subscriptions; i != NULL; i = g_slist_next(i))
+ g_free(i->data);
+
+ g_slist_free(client->subscriptions);
+ client->subscriptions = NULL;
+ client->num_subscriptions = 0;
+}
+
+bool
+client_push_message(struct client *client, const struct client_message *msg)
+{
+ assert(client != NULL);
+ assert(msg != NULL);
+ assert(client_message_defined(msg));
+
+ if (client->num_messages >= CLIENT_MAX_MESSAGES ||
+ client_find_subscription(client, msg->channel) == NULL)
+ return false;
+
+ if (client->messages == NULL)
+ client_idle_add(client, IDLE_MESSAGE);
+
+ client->messages = g_slist_prepend(client->messages,
+ client_message_dup(msg));
+ ++client->num_messages;
+
+ return true;
+}
+
+GSList *
+client_read_messages(struct client *client)
+{
+ GSList *messages = g_slist_reverse(client->messages);
+
+ client->messages = NULL;
+ client->num_messages = 0;
+
+ return messages;
+}
diff --git a/src/client_subscribe.h b/src/client_subscribe.h
new file mode 100644
index 000000000..09f864417
--- /dev/null
+++ b/src/client_subscribe.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_CLIENT_SUBSCRIBE_H
+#define MPD_CLIENT_SUBSCRIBE_H
+
+#include <stdbool.h>
+#include <glib.h>
+
+struct client;
+struct client_message;
+
+enum client_subscribe_result {
+ /** success */
+ CLIENT_SUBSCRIBE_OK,
+
+ /** invalid channel name */
+ CLIENT_SUBSCRIBE_INVALID,
+
+ /** already subscribed to this channel */
+ CLIENT_SUBSCRIBE_ALREADY,
+
+ /** too many subscriptions */
+ CLIENT_SUBSCRIBE_FULL,
+};
+
+enum client_subscribe_result
+client_subscribe(struct client *client, const char *channel);
+
+bool
+client_unsubscribe(struct client *client, const char *channel);
+
+void
+client_unsubscribe_all(struct client *client);
+
+bool
+client_push_message(struct client *client, const struct client_message *msg);
+
+G_GNUC_MALLOC
+GSList *
+client_read_messages(struct client *client);
+
+#endif
diff --git a/src/client_write.c b/src/client_write.c
index 543cdbb6c..78cfca8a1 100644
--- a/src/client_write.c
+++ b/src/client_write.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/cmdline.c b/src/cmdline.c
index d986c8eb8..8ab317730 100644
--- a/src/cmdline.c
+++ b/src/cmdline.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -79,7 +79,7 @@ static void version(void)
puts(PACKAGE " (MPD: Music Player Daemon) " VERSION " \n"
"\n"
"Copyright (C) 2003-2007 Warren Dukes <warren.dukes@gmail.com>\n"
- "Copyright (C) 2008-2010 Max Kellermann <max@duempel.org>\n"
+ "Copyright (C) 2008-2011 Max Kellermann <max@duempel.org>\n"
"This is free software; see the source for copying conditions. There is NO\n"
"warranty; not even MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
"\n"
diff --git a/src/cmdline.h b/src/cmdline.h
index b7af63c5a..68f625a6c 100644
--- a/src/cmdline.h
+++ b/src/cmdline.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/command.c b/src/command.c
index 64f161805..7d4403399 100644
--- a/src/command.c
+++ b/src/command.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -19,17 +19,19 @@
#include "config.h"
#include "command.h"
+#include "protocol/argparser.h"
+#include "protocol/result.h"
#include "player_control.h"
#include "playlist.h"
#include "playlist_print.h"
#include "playlist_save.h"
#include "playlist_queue.h"
+#include "playlist_error.h"
#include "queue_print.h"
#include "ls.h"
#include "uri.h"
#include "decoder_print.h"
#include "directory.h"
-#include "directory_print.h"
#include "database.h"
#include "update.h"
#include "volume.h"
@@ -42,18 +44,28 @@
#include "output_print.h"
#include "locate.h"
#include "dbUtils.h"
+#include "db_error.h"
+#include "db_print.h"
+#include "db_selection.h"
+#include "db_lock.h"
#include "tag.h"
#include "client.h"
+#include "client_idle.h"
+#include "client_internal.h"
+#include "client_subscribe.h"
+#include "client_file.h"
#include "tag_print.h"
#include "path.h"
#include "replay_gain_config.h"
#include "idle.h"
+#include "mapper.h"
+#include "song.h"
+#include "song_print.h"
#ifdef ENABLE_SQLITE
#include "sticker.h"
#include "sticker_print.h"
#include "song_sticker.h"
-#include "song_print.h"
#endif
#include <assert.h>
@@ -98,221 +110,6 @@ struct command {
enum command_return (*handler)(struct client *client, int argc, char **argv);
};
-/* this should really be "need a non-negative integer": */
-static const char need_positive[] = "need a positive integer"; /* no-op */
-static const char need_range[] = "need a range";
-
-/* FIXME: redundant error messages */
-static const char check_integer[] = "\"%s\" is not a integer";
-static const char need_integer[] = "need an integer";
-
-static const char *current_command;
-static int command_list_num;
-
-void command_success(struct client *client)
-{
- client_puts(client, "OK\n");
-}
-
-static void command_error_v(struct client *client, enum ack error,
- const char *fmt, va_list args)
-{
- assert(client != NULL);
- assert(current_command != NULL);
-
- client_printf(client, "ACK [%i@%i] {%s} ",
- (int)error, command_list_num, current_command);
- client_vprintf(client, fmt, args);
- client_puts(client, "\n");
-
- current_command = NULL;
-}
-
-G_GNUC_PRINTF(3, 4) static void command_error(struct client *client, enum ack error,
- const char *fmt, ...)
-{
- va_list args;
- va_start(args, fmt);
- command_error_v(client, error, fmt, args);
- va_end(args);
-}
-
-static bool G_GNUC_PRINTF(4, 5)
-check_uint32(struct client *client, uint32_t *dst,
- const char *s, const char *fmt, ...)
-{
- char *test;
-
- *dst = strtoul(s, &test, 10);
- if (*test != '\0') {
- va_list args;
- va_start(args, fmt);
- command_error_v(client, ACK_ERROR_ARG, fmt, args);
- va_end(args);
- return false;
- }
- return true;
-}
-
-static bool G_GNUC_PRINTF(4, 5)
-check_int(struct client *client, int *value_r,
- const char *s, const char *fmt, ...)
-{
- char *test;
- long value;
-
- value = strtol(s, &test, 10);
- if (*test != '\0') {
- va_list args;
- va_start(args, fmt);
- command_error_v(client, ACK_ERROR_ARG, fmt, args);
- va_end(args);
- return false;
- }
-
-#if G_MAXLONG > G_MAXINT
- if (value < G_MININT || value > G_MAXINT) {
- command_error(client, ACK_ERROR_ARG,
- "Number too large: %s", s);
- return false;
- }
-#endif
-
- *value_r = (int)value;
- return true;
-}
-
-static bool G_GNUC_PRINTF(5, 6)
-check_range(struct client *client, unsigned *value_r1, unsigned *value_r2,
- const char *s, const char *fmt, ...)
-{
- char *test, *test2;
- long value;
-
- value = strtol(s, &test, 10);
- if (*test != '\0' && *test != ':') {
- va_list args;
- va_start(args, fmt);
- command_error_v(client, ACK_ERROR_ARG, fmt, args);
- va_end(args);
- return false;
- }
-
- if (value == -1 && *test == 0) {
- /* compatibility with older MPD versions: specifying
- "-1" makes MPD display the whole list */
- *value_r1 = 0;
- *value_r2 = G_MAXUINT;
- return true;
- }
-
- if (value < 0) {
- command_error(client, ACK_ERROR_ARG,
- "Number is negative: %s", s);
- return false;
- }
-
-#if G_MAXLONG > G_MAXUINT
- if (value > G_MAXUINT) {
- command_error(client, ACK_ERROR_ARG,
- "Number too large: %s", s);
- return false;
- }
-#endif
-
- *value_r1 = (unsigned)value;
-
- if (*test == ':') {
- value = strtol(++test, &test2, 10);
- if (*test2 != '\0') {
- va_list args;
- va_start(args, fmt);
- command_error_v(client, ACK_ERROR_ARG, fmt, args);
- va_end(args);
- return false;
- }
-
- if (test == test2)
- value = G_MAXUINT;
-
- if (value < 0) {
- command_error(client, ACK_ERROR_ARG,
- "Number is negative: %s", s);
- return false;
- }
-
-#if G_MAXLONG > G_MAXUINT
- if (value > G_MAXUINT) {
- command_error(client, ACK_ERROR_ARG,
- "Number too large: %s", s);
- return false;
- }
-#endif
- *value_r2 = (unsigned)value;
- } else {
- *value_r2 = (unsigned)value + 1;
- }
-
- return true;
-}
-
-static bool
-check_unsigned(struct client *client, unsigned *value_r, const char *s)
-{
- unsigned long value;
- char *endptr;
-
- value = strtoul(s, &endptr, 10);
- if (*endptr != 0) {
- command_error(client, ACK_ERROR_ARG,
- "Integer expected: %s", s);
- return false;
- }
-
- if (value > G_MAXUINT) {
- command_error(client, ACK_ERROR_ARG,
- "Number too large: %s", s);
- return false;
- }
-
- *value_r = (unsigned)value;
- return true;
-}
-
-static bool
-check_bool(struct client *client, bool *value_r, const char *s)
-{
- long value;
- char *endptr;
-
- value = strtol(s, &endptr, 10);
- if (*endptr != 0 || (value != 0 && value != 1)) {
- command_error(client, ACK_ERROR_ARG,
- "Boolean (0/1) expected: %s", s);
- return false;
- }
-
- *value_r = !!value;
- return true;
-}
-
-static bool
-check_float(struct client *client, float *value_r, const char *s)
-{
- float value;
- char *endptr;
-
- value = strtof(s, &endptr);
- if (*endptr != 0 && endptr == s) {
- command_error(client, ACK_ERROR_ARG,
- "Float expected: %s", s);
- return false;
- }
-
- *value_r = value;
- return true;
-}
-
static enum command_return
print_playlist_result(struct client *client,
enum playlist_result result)
@@ -322,11 +119,12 @@ print_playlist_result(struct client *client,
return COMMAND_RETURN_OK;
case PLAYLIST_RESULT_ERRNO:
- command_error(client, ACK_ERROR_SYSTEM, "%s", strerror(errno));
+ command_error(client, ACK_ERROR_SYSTEM, "%s",
+ g_strerror(errno));
return COMMAND_RETURN_ERROR;
case PLAYLIST_RESULT_DENIED:
- command_error(client, ACK_ERROR_NO_EXIST, "Access denied");
+ command_error(client, ACK_ERROR_PERMISSION, "Access denied");
return COMMAND_RETURN_ERROR;
case PLAYLIST_RESULT_NO_SUCH_SONG:
@@ -372,6 +170,50 @@ print_playlist_result(struct client *client,
return COMMAND_RETURN_ERROR;
}
+/**
+ * Send the GError to the client and free the GError.
+ */
+static enum command_return
+print_error(struct client *client, GError *error)
+{
+ assert(client != NULL);
+ assert(error != NULL);
+
+ g_warning("%s", error->message);
+
+ if (error->domain == playlist_quark()) {
+ enum playlist_result result = error->code;
+ g_error_free(error);
+ return print_playlist_result(client, result);
+ } else if (error->domain == ack_quark()) {
+ command_error(client, error->code, "%s", error->message);
+ g_error_free(error);
+ return COMMAND_RETURN_ERROR;
+ } else if (error->domain == db_quark()) {
+ switch ((enum db_error)error->code) {
+ case DB_DISABLED:
+ command_error(client, ACK_ERROR_NO_EXIST, "%s",
+ error->message);
+ g_error_free(error);
+ return COMMAND_RETURN_ERROR;
+
+ case DB_NOT_FOUND:
+ g_error_free(error);
+ command_error(client, ACK_ERROR_NO_EXIST, "Not found");
+ return COMMAND_RETURN_ERROR;
+ }
+ } else if (error->domain == g_file_error_quark()) {
+ command_error(client, ACK_ERROR_SYSTEM, "%s",
+ g_strerror(error->code));
+ g_error_free(error);
+ return COMMAND_RETURN_ERROR;
+ }
+
+ g_error_free(error);
+ command_error(client, ACK_ERROR_UNKNOWN, "error");
+ return COMMAND_RETURN_ERROR;
+}
+
static void
print_spl_list(struct client *client, GPtrArray *list)
{
@@ -404,7 +246,7 @@ static enum command_return
handle_urlhandlers(struct client *client,
G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[])
{
- if (client_get_uid(client) > 0)
+ if (client_is_local(client))
client_puts(client, "handler: file://\n");
print_supported_uri_schemes(client);
return COMMAND_RETURN_OK;
@@ -432,9 +274,9 @@ handle_play(struct client *client, int argc, char *argv[])
int song = -1;
enum playlist_result result;
- if (argc == 2 && !check_int(client, &song, argv[1], need_positive))
+ if (argc == 2 && !check_int(client, &song, argv[1]))
return COMMAND_RETURN_ERROR;
- result = playlist_play(&g_playlist, song);
+ result = playlist_play(&g_playlist, client->player_control, song);
return print_playlist_result(client, result);
}
@@ -444,10 +286,10 @@ handle_playid(struct client *client, int argc, char *argv[])
int id = -1;
enum playlist_result result;
- if (argc == 2 && !check_int(client, &id, argv[1], need_positive))
+ if (argc == 2 && !check_int(client, &id, argv[1]))
return COMMAND_RETURN_ERROR;
- result = playlist_play_id(&g_playlist, id);
+ result = playlist_play_id(&g_playlist, client->player_control, id);
return print_playlist_result(client, result);
}
@@ -455,7 +297,7 @@ static enum command_return
handle_stop(G_GNUC_UNUSED struct client *client,
G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[])
{
- playlist_stop(&g_playlist);
+ playlist_stop(&g_playlist, client->player_control);
return COMMAND_RETURN_OK;
}
@@ -476,9 +318,9 @@ handle_pause(struct client *client,
if (!check_bool(client, &pause_flag, argv[1]))
return COMMAND_RETURN_ERROR;
- pc_set_pause(pause_flag);
+ pc_set_pause(client->player_control, pause_flag);
} else
- pc_pause();
+ pc_pause(client->player_control);
return COMMAND_RETURN_OK;
}
@@ -493,7 +335,7 @@ handle_status(struct client *client,
char *error;
int song;
- pc_get_status(&player_status);
+ pc_get_status(client->player_control, &player_status);
switch (player_status.state) {
case PLAYER_STATE_STOP:
@@ -526,9 +368,9 @@ handle_status(struct client *client,
playlist_get_consume(&g_playlist),
playlist_get_version(&g_playlist),
playlist_get_length(&g_playlist),
- (int)(pc_get_cross_fade() + 0.5),
- pc_get_mixramp_db(),
- pc_get_mixramp_delay(),
+ (int)(pc_get_cross_fade(client->player_control) + 0.5),
+ pc_get_mixramp_db(client->player_control),
+ pc_get_mixramp_delay(client->player_control),
state);
song = playlist_get_current_song(&g_playlist);
@@ -561,7 +403,7 @@ handle_status(struct client *client,
updateJobId);
}
- error = pc_get_error_message();
+ error = pc_get_error_message(client->player_control);
if (error != NULL) {
client_printf(client,
COMMAND_STATUS_ERROR ": %s\n",
@@ -601,13 +443,16 @@ handle_add(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
enum playlist_result result;
if (strncmp(uri, "file:///", 8) == 0) {
-#ifdef WIN32
- result = PLAYLIST_RESULT_DENIED;
-#else
+ const char *path = uri + 7;
+
+ GError *error = NULL;
+ if (!client_allow_file(client, path, &error))
+ return print_error(client, error);
+
result = playlist_append_file(&g_playlist,
- uri + 7, client_get_uid(client),
+ client->player_control,
+ path,
NULL);
-#endif
return print_playlist_result(client, result);
}
@@ -618,18 +463,16 @@ handle_add(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
return COMMAND_RETURN_ERROR;
}
- result = playlist_append_uri(&g_playlist, uri, NULL);
+ result = playlist_append_uri(&g_playlist,
+ client->player_control,
+ uri, NULL);
return print_playlist_result(client, result);
}
- result = addAllIn(uri);
- if (result == (enum playlist_result)-1) {
- command_error(client, ACK_ERROR_NO_EXIST,
- "directory or file not found");
- return COMMAND_RETURN_ERROR;
- }
-
- return print_playlist_result(client, result);
+ GError *error = NULL;
+ return addAllIn(client->player_control, uri, &error)
+ ? COMMAND_RETURN_OK
+ : print_error(client, error);
}
static enum command_return
@@ -640,13 +483,16 @@ handle_addid(struct client *client, int argc, char *argv[])
enum playlist_result result;
if (strncmp(uri, "file:///", 8) == 0) {
-#ifdef WIN32
- result = PLAYLIST_RESULT_DENIED;
-#else
- result = playlist_append_file(&g_playlist, uri + 7,
- client_get_uid(client),
+ const char *path = uri + 7;
+
+ GError *error = NULL;
+ if (!client_allow_file(client, path, &error))
+ return print_error(client, error);
+
+ result = playlist_append_file(&g_playlist,
+ client->player_control,
+ path,
&added_id);
-#endif
} else {
if (uri_has_scheme(uri) && !uri_supported_scheme(uri)) {
command_error(client, ACK_ERROR_NO_EXIST,
@@ -654,21 +500,25 @@ handle_addid(struct client *client, int argc, char *argv[])
return COMMAND_RETURN_ERROR;
}
- result = playlist_append_uri(&g_playlist, uri, &added_id);
+ result = playlist_append_uri(&g_playlist,
+ client->player_control,
+ uri, &added_id);
}
if (result != PLAYLIST_RESULT_SUCCESS)
return print_playlist_result(client, result);
if (argc == 3) {
- int to;
- if (!check_int(client, &to, argv[2], check_integer, argv[2]))
+ unsigned to;
+ if (!check_unsigned(client, &to, argv[2]))
return COMMAND_RETURN_ERROR;
- result = playlist_move_id(&g_playlist, added_id, to);
+ result = playlist_move_id(&g_playlist, client->player_control,
+ added_id, to);
if (result != PLAYLIST_RESULT_SUCCESS) {
enum command_return ret =
print_playlist_result(client, result);
- playlist_delete_id(&g_playlist, added_id);
+ playlist_delete_id(&g_playlist, client->player_control,
+ added_id);
return ret;
}
}
@@ -683,23 +533,24 @@ handle_delete(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
unsigned start, end;
enum playlist_result result;
- if (!check_range(client, &start, &end, argv[1], need_range))
+ if (!check_range(client, &start, &end, argv[1]))
return COMMAND_RETURN_ERROR;
- result = playlist_delete_range(&g_playlist, start, end);
+ result = playlist_delete_range(&g_playlist, client->player_control,
+ start, end);
return print_playlist_result(client, result);
}
static enum command_return
handle_deleteid(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
{
- int id;
+ unsigned id;
enum playlist_result result;
- if (!check_int(client, &id, argv[1], need_positive))
+ if (!check_unsigned(client, &id, argv[1]))
return COMMAND_RETURN_ERROR;
- result = playlist_delete_id(&g_playlist, id);
+ result = playlist_delete_id(&g_playlist, client->player_control, id);
return print_playlist_result(client, result);
}
@@ -716,11 +567,10 @@ handle_shuffle(G_GNUC_UNUSED struct client *client,
G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[])
{
unsigned start = 0, end = queue_length(&g_playlist.queue);
- if (argc == 2 && !check_range(client, &start, &end,
- argv[1], need_range))
+ if (argc == 2 && !check_range(client, &start, &end, argv[1]))
return COMMAND_RETURN_ERROR;
- playlist_shuffle(&g_playlist, start, end);
+ playlist_shuffle(&g_playlist, client->player_control, start, end);
return COMMAND_RETURN_OK;
}
@@ -728,7 +578,7 @@ static enum command_return
handle_clear(G_GNUC_UNUSED struct client *client,
G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[])
{
- playlist_clear(&g_playlist);
+ playlist_clear(&g_playlist, client->player_control);
return COMMAND_RETURN_OK;
}
@@ -743,16 +593,40 @@ handle_save(struct client *client,
}
static enum command_return
-handle_load(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
+handle_load(struct client *client, int argc, char *argv[])
{
+ unsigned start_index, end_index;
+
+ if (argc < 3) {
+ start_index = 0;
+ end_index = G_MAXUINT;
+ } else if (!check_range(client, &start_index, &end_index, argv[2]))
+ return COMMAND_RETURN_ERROR;
+
enum playlist_result result;
- result = playlist_open_into_queue(argv[1], &g_playlist);
+ result = playlist_open_into_queue(argv[1],
+ start_index, end_index,
+ &g_playlist,
+ client->player_control, true);
if (result != PLAYLIST_RESULT_NO_SUCH_LIST)
return print_playlist_result(client, result);
- result = playlist_load_spl(&g_playlist, argv[1]);
- return print_playlist_result(client, result);
+ GError *error = NULL;
+ if (playlist_load_spl(&g_playlist, client->player_control,
+ argv[1], start_index, end_index,
+ &error))
+ return COMMAND_RETURN_OK;
+
+ if (error->domain == playlist_quark() &&
+ error->code == PLAYLIST_RESULT_BAD_NAME)
+ /* the message for BAD_NAME is confusing when the
+ client wants to load a playlist file from the music
+ directory; patch the GError object to show "no such
+ playlist" instead */
+ error->code = PLAYLIST_RESULT_NO_SUCH_LIST;
+
+ return print_error(client, error);
}
static enum command_return
@@ -761,15 +635,10 @@ handle_listplaylist(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
if (playlist_file_print(client, argv[1], false))
return COMMAND_RETURN_OK;
- bool ret;
-
- ret = spl_print(client, argv[1], false);
- if (!ret) {
- command_error(client, ACK_ERROR_NO_EXIST, "No such playlist");
- return COMMAND_RETURN_ERROR;
- }
-
- return COMMAND_RETURN_OK;
+ GError *error = NULL;
+ return spl_print(client, argv[1], false, &error)
+ ? COMMAND_RETURN_OK
+ : print_error(client, error);
}
static enum command_return
@@ -779,22 +648,16 @@ handle_listplaylistinfo(struct client *client,
if (playlist_file_print(client, argv[1], true))
return COMMAND_RETURN_OK;
- bool ret;
-
- ret = spl_print(client, argv[1], true);
- if (!ret) {
- command_error(client, ACK_ERROR_NO_EXIST, "No such playlist");
- return COMMAND_RETURN_ERROR;
- }
-
- return COMMAND_RETURN_OK;
+ GError *error = NULL;
+ return spl_print(client, argv[1], true, &error)
+ ? COMMAND_RETURN_OK
+ : print_error(client, error);
}
static enum command_return
handle_lsinfo(struct client *client, int argc, char *argv[])
{
const char *uri;
- const struct directory *directory;
if (argc == 2)
uri = argv[1];
@@ -802,17 +665,35 @@ handle_lsinfo(struct client *client, int argc, char *argv[])
/* default is root directory */
uri = "";
- directory = db_get_directory(uri);
- if (directory == NULL) {
- command_error(client, ACK_ERROR_NO_EXIST,
- "directory not found");
- return COMMAND_RETURN_ERROR;
+ if (strncmp(uri, "file:///", 8) == 0) {
+ /* print information about an arbitrary local file */
+ const char *path = uri + 7;
+
+ GError *error = NULL;
+ if (!client_allow_file(client, path, &error))
+ return print_error(client, error);
+
+ struct song *song = song_file_load(path, NULL);
+ if (song == NULL) {
+ command_error(client, ACK_ERROR_NO_EXIST,
+ "No such file");
+ return COMMAND_RETURN_ERROR;
+ }
+
+ song_print_info(client, song);
+ song_free(song);
+ return COMMAND_RETURN_OK;
}
- directory_print(client, directory);
+ struct db_selection selection;
+ db_selection_init(&selection, uri, false);
+
+ GError *error = NULL;
+ if (!db_selection_print(client, &selection, true, &error))
+ return print_error(client, error);
if (isRootDirectory(uri)) {
- GPtrArray *list = spl_list();
+ GPtrArray *list = spl_list(NULL);
if (list != NULL) {
print_spl_list(client, list);
spl_list_free(list);
@@ -825,19 +706,19 @@ handle_lsinfo(struct client *client, int argc, char *argv[])
static enum command_return
handle_rm(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
{
- enum playlist_result result;
-
- result = spl_delete(argv[1]);
- return print_playlist_result(client, result);
+ GError *error = NULL;
+ return spl_delete(argv[1], &error)
+ ? COMMAND_RETURN_OK
+ : print_error(client, error);
}
static enum command_return
handle_rename(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
{
- enum playlist_result result;
-
- result = spl_rename(argv[1], argv[2]);
- return print_playlist_result(client, result);
+ GError *error = NULL;
+ return spl_rename(argv[1], argv[2], &error)
+ ? COMMAND_RETURN_OK
+ : print_error(client, error);
}
static enum command_return
@@ -845,7 +726,7 @@ handle_plchanges(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
{
uint32_t version;
- if (!check_uint32(client, &version, argv[1], need_positive))
+ if (!check_uint32(client, &version, argv[1]))
return COMMAND_RETURN_ERROR;
playlist_print_changes_info(client, &g_playlist, version);
@@ -857,7 +738,7 @@ handle_plchangesposid(struct client *client, G_GNUC_UNUSED int argc, char *argv[
{
uint32_t version;
- if (!check_uint32(client, &version, argv[1], need_positive))
+ if (!check_uint32(client, &version, argv[1]))
return COMMAND_RETURN_ERROR;
playlist_print_changes_position(client, &g_playlist, version);
@@ -870,8 +751,7 @@ handle_playlistinfo(struct client *client, int argc, char *argv[])
unsigned start = 0, end = G_MAXUINT;
bool ret;
- if (argc == 2 && !check_range(client, &start, &end,
- argv[1], need_range))
+ if (argc == 2 && !check_range(client, &start, &end, argv[1]))
return COMMAND_RETURN_ERROR;
ret = playlist_print_info(client, &g_playlist, start, end);
@@ -885,12 +765,11 @@ handle_playlistinfo(struct client *client, int argc, char *argv[])
static enum command_return
handle_playlistid(struct client *client, int argc, char *argv[])
{
- int id = -1;
-
- if (argc == 2 && !check_int(client, &id, argv[1], need_positive))
- return COMMAND_RETURN_ERROR;
+ if (argc >= 2) {
+ unsigned id;
+ if (!check_unsigned(client, &id, argv[1]))
+ return COMMAND_RETURN_ERROR;
- if (id >= 0) {
bool ret = playlist_print_id(client, &g_playlist, id);
if (!ret)
return print_playlist_result(client,
@@ -905,7 +784,6 @@ handle_playlistid(struct client *client, int argc, char *argv[])
static enum command_return
handle_find(struct client *client, int argc, char *argv[])
{
- int ret;
struct locate_item_list *list =
locate_item_list_parse(argv + 1, argc - 1);
@@ -917,10 +795,10 @@ handle_find(struct client *client, int argc, char *argv[])
return COMMAND_RETURN_ERROR;
}
- ret = findSongsIn(client, NULL, list);
- if (ret == -1)
- command_error(client, ACK_ERROR_NO_EXIST,
- "directory or file not found");
+ GError *error = NULL;
+ enum command_return ret = findSongsIn(client, "", list, &error)
+ ? COMMAND_RETURN_OK
+ : print_error(client, error);
locate_item_list_free(list);
@@ -930,7 +808,6 @@ handle_find(struct client *client, int argc, char *argv[])
static enum command_return
handle_findadd(struct client *client, int argc, char *argv[])
{
- int ret;
struct locate_item_list *list =
locate_item_list_parse(argv + 1, argc - 1);
if (list == NULL || list->length == 0) {
@@ -941,10 +818,11 @@ handle_findadd(struct client *client, int argc, char *argv[])
return COMMAND_RETURN_ERROR;
}
- ret = findAddIn(client, NULL, list);
- if (ret == -1)
- command_error(client, ACK_ERROR_NO_EXIST,
- "directory or file not found");
+ GError *error = NULL;
+ enum command_return ret =
+ findAddIn(client->player_control, "", list, &error)
+ ? COMMAND_RETURN_OK
+ : print_error(client, error);
locate_item_list_free(list);
@@ -954,7 +832,6 @@ handle_findadd(struct client *client, int argc, char *argv[])
static enum command_return
handle_search(struct client *client, int argc, char *argv[])
{
- int ret;
struct locate_item_list *list =
locate_item_list_parse(argv + 1, argc - 1);
@@ -966,10 +843,10 @@ handle_search(struct client *client, int argc, char *argv[])
return COMMAND_RETURN_ERROR;
}
- ret = searchForSongsIn(client, NULL, list);
- if (ret == -1)
- command_error(client, ACK_ERROR_NO_EXIST,
- "directory or file not found");
+ GError *error = NULL;
+ enum command_return ret = searchForSongsIn(client, "", list, &error)
+ ? COMMAND_RETURN_OK
+ : print_error(client, error);
locate_item_list_free(list);
@@ -979,7 +856,6 @@ handle_search(struct client *client, int argc, char *argv[])
static enum command_return
handle_count(struct client *client, int argc, char *argv[])
{
- int ret;
struct locate_item_list *list =
locate_item_list_parse(argv + 1, argc - 1);
@@ -991,10 +867,11 @@ handle_count(struct client *client, int argc, char *argv[])
return COMMAND_RETURN_ERROR;
}
- ret = searchStatsForSongsIn(client, NULL, list);
- if (ret == -1)
- command_error(client, ACK_ERROR_NO_EXIST,
- "directory or file not found");
+ GError *error = NULL;
+ enum command_return ret =
+ searchStatsForSongsIn(client, "", list, &error)
+ ? COMMAND_RETURN_OK
+ : print_error(client, error);
locate_item_list_free(list);
@@ -1047,30 +924,32 @@ static enum command_return
handle_playlistdelete(struct client *client,
G_GNUC_UNUSED int argc, char *argv[]) {
char *playlist = argv[1];
- int from;
- enum playlist_result result;
+ unsigned from;
- if (!check_int(client, &from, argv[2], check_integer, argv[2]))
+ if (!check_unsigned(client, &from, argv[2]))
return COMMAND_RETURN_ERROR;
- result = spl_remove_index(playlist, from);
- return print_playlist_result(client, result);
+ GError *error = NULL;
+ return spl_remove_index(playlist, from, &error)
+ ? COMMAND_RETURN_OK
+ : print_error(client, error);
}
static enum command_return
handle_playlistmove(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
{
char *playlist = argv[1];
- int from, to;
- enum playlist_result result;
+ unsigned from, to;
- if (!check_int(client, &from, argv[2], check_integer, argv[2]))
+ if (!check_unsigned(client, &from, argv[2]))
return COMMAND_RETURN_ERROR;
- if (!check_int(client, &to, argv[3], check_integer, argv[3]))
+ if (!check_unsigned(client, &to, argv[3]))
return COMMAND_RETURN_ERROR;
- result = spl_move_index(playlist, from, to);
- return print_playlist_result(client, result);
+ GError *error = NULL;
+ return spl_move_index(playlist, from, to, &error)
+ ? COMMAND_RETURN_OK
+ : print_error(client, error);
}
static enum command_return
@@ -1141,7 +1020,7 @@ handle_next(G_GNUC_UNUSED struct client *client,
int single = g_playlist.queue.single;
g_playlist.queue.single = false;
- playlist_next(&g_playlist);
+ playlist_next(&g_playlist, client->player_control);
g_playlist.queue.single = single;
return COMMAND_RETURN_OK;
@@ -1151,37 +1030,96 @@ static enum command_return
handle_previous(G_GNUC_UNUSED struct client *client,
G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[])
{
- playlist_previous(&g_playlist);
+ playlist_previous(&g_playlist, client->player_control);
+ return COMMAND_RETURN_OK;
+}
+
+static enum command_return
+handle_prio(struct client *client, int argc, char *argv[])
+{
+ unsigned priority;
+
+ if (!check_unsigned(client, &priority, argv[1]))
+ return COMMAND_RETURN_ERROR;
+
+ if (priority > 0xff) {
+ command_error(client, ACK_ERROR_ARG,
+ "Priority out of range: %s", argv[1]);
+ return COMMAND_RETURN_ERROR;
+ }
+
+ for (int i = 2; i < argc; ++i) {
+ unsigned start_position, end_position;
+ if (!check_range(client, &start_position, &end_position,
+ argv[i]))
+ return COMMAND_RETURN_ERROR;
+
+ enum playlist_result result =
+ playlist_set_priority(&g_playlist,
+ client->player_control,
+ start_position, end_position,
+ priority);
+ if (result != PLAYLIST_RESULT_SUCCESS)
+ return print_playlist_result(client, result);
+ }
+
+ return COMMAND_RETURN_OK;
+}
+
+static enum command_return
+handle_prioid(struct client *client, int argc, char *argv[])
+{
+ unsigned priority;
+
+ if (!check_unsigned(client, &priority, argv[1]))
+ return COMMAND_RETURN_ERROR;
+
+ if (priority > 0xff) {
+ command_error(client, ACK_ERROR_ARG,
+ "Priority out of range: %s", argv[1]);
+ return COMMAND_RETURN_ERROR;
+ }
+
+ for (int i = 2; i < argc; ++i) {
+ unsigned song_id;
+ if (!check_unsigned(client, &song_id, argv[i]))
+ return COMMAND_RETURN_ERROR;
+
+ enum playlist_result result =
+ playlist_set_priority_id(&g_playlist,
+ client->player_control,
+ song_id, priority);
+ if (result != PLAYLIST_RESULT_SUCCESS)
+ return print_playlist_result(client, result);
+ }
+
return COMMAND_RETURN_OK;
}
static enum command_return
handle_listall(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
{
- char *directory = NULL;
- int ret;
+ const char *directory = "";
if (argc == 2)
directory = argv[1];
- ret = printAllIn(client, directory);
- if (ret == -1)
- command_error(client, ACK_ERROR_NO_EXIST,
- "directory or file not found");
-
- return ret;
+ GError *error = NULL;
+ return printAllIn(client, directory, &error)
+ ? COMMAND_RETURN_OK
+ : print_error(client, error);
}
static enum command_return
handle_setvol(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
{
- int level;
+ unsigned level;
bool success;
- if (!check_int(client, &level, argv[1], need_integer))
+ if (!check_unsigned(client, &level, argv[1]))
return COMMAND_RETURN_ERROR;
- if (level < 0 || level > 100) {
+ if (level > 100) {
command_error(client, ACK_ERROR_ARG, "Invalid volume value");
return COMMAND_RETURN_ERROR;
}
@@ -1199,52 +1137,31 @@ handle_setvol(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
static enum command_return
handle_repeat(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
{
- int status;
-
- if (!check_int(client, &status, argv[1], need_integer))
- return COMMAND_RETURN_ERROR;
-
- if (status != 0 && status != 1) {
- command_error(client, ACK_ERROR_ARG,
- "\"%i\" is not 0 or 1", status);
+ bool status;
+ if (!check_bool(client, &status, argv[1]))
return COMMAND_RETURN_ERROR;
- }
- playlist_set_repeat(&g_playlist, status);
+ playlist_set_repeat(&g_playlist, client->player_control, status);
return COMMAND_RETURN_OK;
}
static enum command_return
handle_single(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
{
- int status;
-
- if (!check_int(client, &status, argv[1], need_integer))
+ bool status;
+ if (!check_bool(client, &status, argv[1]))
return COMMAND_RETURN_ERROR;
- if (status != 0 && status != 1) {
- command_error(client, ACK_ERROR_ARG,
- "\"%i\" is not 0 or 1", status);
- return COMMAND_RETURN_ERROR;
- }
-
- playlist_set_single(&g_playlist, status);
+ playlist_set_single(&g_playlist, client->player_control, status);
return COMMAND_RETURN_OK;
}
static enum command_return
handle_consume(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
{
- int status;
-
- if (!check_int(client, &status, argv[1], need_integer))
- return COMMAND_RETURN_ERROR;
-
- if (status != 0 && status != 1) {
- command_error(client, ACK_ERROR_ARG,
- "\"%i\" is not 0 or 1", status);
+ bool status;
+ if (!check_bool(client, &status, argv[1]))
return COMMAND_RETURN_ERROR;
- }
playlist_set_consume(&g_playlist, status);
return COMMAND_RETURN_OK;
@@ -1253,18 +1170,11 @@ handle_consume(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
static enum command_return
handle_random(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
{
- int status;
-
- if (!check_int(client, &status, argv[1], need_integer))
+ bool status;
+ if (!check_bool(client, &status, argv[1]))
return COMMAND_RETURN_ERROR;
- if (status != 0 && status != 1) {
- command_error(client, ACK_ERROR_ARG,
- "\"%i\" is not 0 or 1", status);
- return COMMAND_RETURN_ERROR;
- }
-
- playlist_set_random(&g_playlist, status);
+ playlist_set_random(&g_playlist, client->player_control, status);
return COMMAND_RETURN_OK;
}
@@ -1279,7 +1189,7 @@ static enum command_return
handle_clearerror(G_GNUC_UNUSED struct client *client,
G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[])
{
- pc_clear_error();
+ pc_clear_error(client->player_control);
return COMMAND_RETURN_OK;
}
@@ -1288,7 +1198,6 @@ handle_list(struct client *client, int argc, char *argv[])
{
struct locate_item_list *conditionals;
int tagType = locate_parse_type(argv[1]);
- int ret;
if (tagType < 0) {
command_error(client, ACK_ERROR_ARG, "\"%s\" is not known", argv[1]);
@@ -1325,14 +1234,14 @@ handle_list(struct client *client, int argc, char *argv[])
}
}
- ret = listAllUniqueTags(client, tagType, conditionals);
+ GError *error = NULL;
+ enum command_return ret =
+ listAllUniqueTags(client, tagType, conditionals, &error)
+ ? COMMAND_RETURN_OK
+ : print_error(client, error);
locate_item_list_free(conditionals);
- if (ret == -1)
- command_error(client, ACK_ERROR_NO_EXIST,
- "directory or file not found");
-
return ret;
}
@@ -1343,102 +1252,120 @@ handle_move(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
int to;
enum playlist_result result;
- if (!check_range(client, &start, &end,
- argv[1], need_range))
+ if (!check_range(client, &start, &end, argv[1]))
return COMMAND_RETURN_ERROR;
- if (!check_int(client, &to, argv[2], check_integer, argv[2]))
+ if (!check_int(client, &to, argv[2]))
return COMMAND_RETURN_ERROR;
- result = playlist_move_range(&g_playlist, start, end, to);
+ result = playlist_move_range(&g_playlist, client->player_control,
+ start, end, to);
return print_playlist_result(client, result);
}
static enum command_return
handle_moveid(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
{
- int id, to;
+ unsigned id;
+ int to;
enum playlist_result result;
- if (!check_int(client, &id, argv[1], check_integer, argv[1]))
+ if (!check_unsigned(client, &id, argv[1]))
return COMMAND_RETURN_ERROR;
- if (!check_int(client, &to, argv[2], check_integer, argv[2]))
+ if (!check_int(client, &to, argv[2]))
return COMMAND_RETURN_ERROR;
- result = playlist_move_id(&g_playlist, id, to);
+ result = playlist_move_id(&g_playlist, client->player_control,
+ id, to);
return print_playlist_result(client, result);
}
static enum command_return
handle_swap(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
{
- int song1, song2;
+ unsigned song1, song2;
enum playlist_result result;
- if (!check_int(client, &song1, argv[1], check_integer, argv[1]))
+ if (!check_unsigned(client, &song1, argv[1]))
return COMMAND_RETURN_ERROR;
- if (!check_int(client, &song2, argv[2], check_integer, argv[2]))
+ if (!check_unsigned(client, &song2, argv[2]))
return COMMAND_RETURN_ERROR;
- result = playlist_swap_songs(&g_playlist, song1, song2);
+ result = playlist_swap_songs(&g_playlist, client->player_control,
+ song1, song2);
return print_playlist_result(client, result);
}
static enum command_return
handle_swapid(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
{
- int id1, id2;
+ unsigned id1, id2;
enum playlist_result result;
- if (!check_int(client, &id1, argv[1], check_integer, argv[1]))
+ if (!check_unsigned(client, &id1, argv[1]))
return COMMAND_RETURN_ERROR;
- if (!check_int(client, &id2, argv[2], check_integer, argv[2]))
+ if (!check_unsigned(client, &id2, argv[2]))
return COMMAND_RETURN_ERROR;
- result = playlist_swap_songs_id(&g_playlist, id1, id2);
+ result = playlist_swap_songs_id(&g_playlist, client->player_control,
+ id1, id2);
return print_playlist_result(client, result);
}
static enum command_return
handle_seek(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
{
- int song, seek_time;
+ unsigned song, seek_time;
enum playlist_result result;
- if (!check_int(client, &song, argv[1], check_integer, argv[1]))
+ if (!check_unsigned(client, &song, argv[1]))
return COMMAND_RETURN_ERROR;
- if (!check_int(client, &seek_time, argv[2], check_integer, argv[2]))
+ if (!check_unsigned(client, &seek_time, argv[2]))
return COMMAND_RETURN_ERROR;
- result = playlist_seek_song(&g_playlist, song, seek_time);
+ result = playlist_seek_song(&g_playlist, client->player_control,
+ song, seek_time);
return print_playlist_result(client, result);
}
static enum command_return
handle_seekid(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
{
- int id, seek_time;
+ unsigned id, seek_time;
enum playlist_result result;
- if (!check_int(client, &id, argv[1], check_integer, argv[1]))
+ if (!check_unsigned(client, &id, argv[1]))
return COMMAND_RETURN_ERROR;
- if (!check_int(client, &seek_time, argv[2], check_integer, argv[2]))
+ if (!check_unsigned(client, &seek_time, argv[2]))
+ return COMMAND_RETURN_ERROR;
+
+ result = playlist_seek_song_id(&g_playlist, client->player_control,
+ id, seek_time);
+ return print_playlist_result(client, result);
+}
+
+static enum command_return
+handle_seekcur(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
+{
+ const char *p = argv[1];
+ bool relative = *p == '+' || *p == '-';
+ int seek_time;
+ if (!check_int(client, &seek_time, p))
return COMMAND_RETURN_ERROR;
- result = playlist_seek_song_id(&g_playlist, id, seek_time);
+ enum playlist_result result =
+ playlist_seek_current(&g_playlist, client->player_control,
+ seek_time, relative);
return print_playlist_result(client, result);
}
static enum command_return
handle_listallinfo(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
{
- char *directory = NULL;
- int ret;
+ const char *directory = "";
if (argc == 2)
directory = argv[1];
- ret = printInfoForAllIn(client, directory);
- if (ret == -1)
- command_error(client, ACK_ERROR_NO_EXIST,
- "directory or file not found");
-
- return ret;
+ GError *error = NULL;
+ return printInfoForAllIn(client, directory, &error)
+ ? COMMAND_RETURN_OK
+ : print_error(client, error);
}
static enum command_return
@@ -1470,7 +1397,7 @@ handle_crossfade(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
if (!check_unsigned(client, &xfade_time, argv[1]))
return COMMAND_RETURN_ERROR;
- pc_set_cross_fade(xfade_time);
+ pc_set_cross_fade(client->player_control, xfade_time);
return COMMAND_RETURN_OK;
}
@@ -1482,7 +1409,7 @@ handle_mixrampdb(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
if (!check_float(client, &db, argv[1]))
return COMMAND_RETURN_ERROR;
- pc_set_mixramp_db(db);
+ pc_set_mixramp_db(client->player_control, db);
return COMMAND_RETURN_OK;
}
@@ -1494,7 +1421,7 @@ handle_mixrampdelay(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
if (!check_float(client, &delay_secs, argv[1]))
return COMMAND_RETURN_ERROR;
- pc_set_mixramp_delay(delay_secs);
+ pc_set_mixramp_delay(client->player_control, delay_secs);
return COMMAND_RETURN_OK;
}
@@ -1556,12 +1483,29 @@ handle_not_commands(struct client *client,
G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]);
static enum command_return
-handle_playlistclear(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
+handle_config(struct client *client,
+ G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[])
{
- enum playlist_result result;
+ if (!client_is_local(client)) {
+ command_error(client, ACK_ERROR_PERMISSION,
+ "Command only permitted to local clients");
+ return COMMAND_RETURN_ERROR;
+ }
- result = spl_clear(argv[1]);
- return print_playlist_result(client, result);
+ const char *path = mapper_get_music_directory();
+ if (path != NULL)
+ client_printf(client, "music_directory: %s\n", path);
+
+ return COMMAND_RETURN_OK;
+}
+
+static enum command_return
+handle_playlistclear(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
+{
+ GError *error = NULL;
+ return spl_clear(argv[1], &error)
+ ? COMMAND_RETURN_OK
+ : print_error(client, error);
}
static enum command_return
@@ -1569,8 +1513,9 @@ handle_playlistadd(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
{
char *playlist = argv[1];
char *uri = argv[2];
- enum playlist_result result;
+ bool success;
+ GError *error = NULL;
if (uri_has_scheme(uri)) {
if (!uri_supported_scheme(uri)) {
command_error(client, ACK_ERROR_NO_EXIST,
@@ -1578,29 +1523,27 @@ handle_playlistadd(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
return COMMAND_RETURN_ERROR;
}
- result = spl_append_uri(uri, playlist);
+ success = spl_append_uri(argv[1], playlist, &error);
} else
- result = addAllInToStoredPlaylist(uri, playlist);
+ success = addAllInToStoredPlaylist(uri, playlist, &error);
- if (result == (enum playlist_result)-1) {
+ if (!success && error == NULL) {
command_error(client, ACK_ERROR_NO_EXIST,
"directory or file not found");
return COMMAND_RETURN_ERROR;
}
- return print_playlist_result(client, result);
+ return success ? COMMAND_RETURN_OK : print_error(client, error);
}
static enum command_return
handle_listplaylists(struct client *client,
G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[])
{
- GPtrArray *list = spl_list();
- if (list == NULL) {
- command_error(client, ACK_ERROR_SYSTEM,
- "failed to get list of stored playlists");
- return COMMAND_RETURN_ERROR;
- }
+ GError *error = NULL;
+ GPtrArray *list = spl_list(&error);
+ if (list == NULL)
+ return print_error(client, error);
print_spl_list(client, list);
spl_list_free(list);
@@ -1774,8 +1717,10 @@ handle_sticker_song(struct client *client, int argc, char *argv[])
.name = argv[4],
};
+ db_lock();
directory = db_get_directory(argv[3]);
if (directory == NULL) {
+ db_unlock();
command_error(client, ACK_ERROR_NO_EXIST,
"no such directory");
return COMMAND_RETURN_ERROR;
@@ -1783,6 +1728,7 @@ handle_sticker_song(struct client *client, int argc, char *argv[])
success = sticker_song_find(directory, data.name,
sticker_song_find_print_cb, &data);
+ db_unlock();
if (!success) {
command_error(client, ACK_ERROR_SYSTEM,
"failed to set search sticker database");
@@ -1817,6 +1763,172 @@ handle_sticker(struct client *client, int argc, char *argv[])
}
#endif
+static enum command_return
+handle_subscribe(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
+{
+ assert(argc == 2);
+
+ switch (client_subscribe(client, argv[1])) {
+ case CLIENT_SUBSCRIBE_OK:
+ return COMMAND_RETURN_OK;
+
+ case CLIENT_SUBSCRIBE_INVALID:
+ command_error(client, ACK_ERROR_ARG,
+ "invalid channel name");
+ return COMMAND_RETURN_ERROR;
+
+ case CLIENT_SUBSCRIBE_ALREADY:
+ command_error(client, ACK_ERROR_EXIST,
+ "already subscribed to this channel");
+ return COMMAND_RETURN_ERROR;
+
+ case CLIENT_SUBSCRIBE_FULL:
+ command_error(client, ACK_ERROR_EXIST,
+ "subscription list is full");
+ return COMMAND_RETURN_ERROR;
+ }
+
+ /* unreachable */
+ return COMMAND_RETURN_OK;
+}
+
+static enum command_return
+handle_unsubscribe(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
+{
+ assert(argc == 2);
+
+ if (client_unsubscribe(client, argv[1]))
+ return COMMAND_RETURN_OK;
+ else {
+ command_error(client, ACK_ERROR_NO_EXIST,
+ "not subscribed to this channel");
+ return COMMAND_RETURN_ERROR;
+ }
+}
+
+struct channels_context {
+ GStringChunk *chunk;
+
+ GHashTable *channels;
+};
+
+static void
+collect_channels(gpointer data, gpointer user_data)
+{
+ struct channels_context *context = user_data;
+ const struct client *client = data;
+
+ for (GSList *i = client->subscriptions; i != NULL;
+ i = g_slist_next(i)) {
+ const char *channel = i->data;
+
+ if (g_hash_table_lookup(context->channels, channel) == NULL) {
+ char *channel2 = g_string_chunk_insert(context->chunk,
+ channel);
+ g_hash_table_insert(context->channels, channel2,
+ context);
+ }
+ }
+}
+
+static void
+print_channel(gpointer key, G_GNUC_UNUSED gpointer value, gpointer user_data)
+{
+ struct client *client = user_data;
+ const char *channel = key;
+
+ client_printf(client, "channel: %s\n", channel);
+}
+
+static enum command_return
+handle_channels(struct client *client,
+ G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[])
+{
+ assert(argc == 1);
+
+ struct channels_context context = {
+ .chunk = g_string_chunk_new(1024),
+ .channels = g_hash_table_new(g_str_hash, g_str_equal),
+ };
+
+ client_list_foreach(collect_channels, &context);
+
+ g_hash_table_foreach(context.channels, print_channel, client);
+
+ g_hash_table_destroy(context.channels);
+ g_string_chunk_free(context.chunk);
+
+ return COMMAND_RETURN_OK;
+}
+
+static enum command_return
+handle_read_messages(struct client *client,
+ G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[])
+{
+ assert(argc == 1);
+
+ GSList *messages = client_read_messages(client);
+
+ for (GSList *i = messages; i != NULL; i = g_slist_next(i)) {
+ struct client_message *msg = i->data;
+
+ client_printf(client, "channel: %s\nmessage: %s\n",
+ msg->channel, msg->message);
+ client_message_free(msg);
+ }
+
+ g_slist_free(messages);
+
+ return COMMAND_RETURN_OK;
+}
+
+struct send_message_context {
+ struct client_message msg;
+
+ bool sent;
+};
+
+static void
+send_message(gpointer data, gpointer user_data)
+{
+ struct send_message_context *context = user_data;
+ struct client *client = data;
+
+ if (client_push_message(client, &context->msg))
+ context->sent = true;
+}
+
+static enum command_return
+handle_send_message(struct client *client,
+ G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[])
+{
+ assert(argc == 3);
+
+ if (!client_message_valid_channel_name(argv[1])) {
+ command_error(client, ACK_ERROR_ARG,
+ "invalid channel name");
+ return COMMAND_RETURN_ERROR;
+ }
+
+ struct send_message_context context = {
+ .sent = false,
+ };
+
+ client_message_init(&context.msg, argv[1], argv[2]);
+
+ client_list_foreach(send_message, &context);
+
+ client_message_deinit(&context.msg);
+
+ if (context.sent)
+ return COMMAND_RETURN_OK;
+ else {
+ command_error(client, ACK_ERROR_NO_EXIST,
+ "nobody is subscribed to this channel");
+ return COMMAND_RETURN_ERROR;
+ }
+}
+
/**
* The command registry.
*
@@ -1825,10 +1937,12 @@ handle_sticker(struct client *client, int argc, char *argv[])
static const struct command commands[] = {
{ "add", PERMISSION_ADD, 1, 1, handle_add },
{ "addid", PERMISSION_ADD, 1, 2, handle_addid },
+ { "channels", PERMISSION_READ, 0, 0, handle_channels },
{ "clear", PERMISSION_CONTROL, 0, 0, handle_clear },
{ "clearerror", PERMISSION_CONTROL, 0, 0, handle_clearerror },
{ "close", PERMISSION_NONE, -1, -1, handle_close },
{ "commands", PERMISSION_NONE, 0, 0, handle_commands },
+ { "config", PERMISSION_ADMIN, 0, 0, handle_config },
{ "consume", PERMISSION_CONTROL, 1, 1, handle_consume },
{ "count", PERMISSION_READ, 2, -1, handle_count },
{ "crossfade", PERMISSION_CONTROL, 1, 1, handle_crossfade },
@@ -1848,7 +1962,7 @@ static const struct command commands[] = {
{ "listplaylist", PERMISSION_READ, 1, 1, handle_listplaylist },
{ "listplaylistinfo", PERMISSION_READ, 1, 1, handle_listplaylistinfo },
{ "listplaylists", PERMISSION_READ, 0, 0, handle_listplaylists },
- { "load", PERMISSION_ADD, 1, 1, handle_load },
+ { "load", PERMISSION_ADD, 1, 2, handle_load },
{ "lsinfo", PERMISSION_READ, 0, 1, handle_lsinfo },
{ "mixrampdb", PERMISSION_CONTROL, 1, 1, handle_mixrampdb },
{ "mixrampdelay", PERMISSION_CONTROL, 1, 1, handle_mixrampdelay },
@@ -1874,19 +1988,24 @@ static const struct command commands[] = {
{ "plchanges", PERMISSION_READ, 1, 1, handle_plchanges },
{ "plchangesposid", PERMISSION_READ, 1, 1, handle_plchangesposid },
{ "previous", PERMISSION_CONTROL, 0, 0, handle_previous },
+ { "prio", PERMISSION_CONTROL, 2, -1, handle_prio },
+ { "prioid", PERMISSION_CONTROL, 2, -1, handle_prioid },
{ "random", PERMISSION_CONTROL, 1, 1, handle_random },
+ { "readmessages", PERMISSION_READ, 0, 0, handle_read_messages },
{ "rename", PERMISSION_CONTROL, 2, 2, handle_rename },
{ "repeat", PERMISSION_CONTROL, 1, 1, handle_repeat },
{ "replay_gain_mode", PERMISSION_CONTROL, 1, 1,
handle_replay_gain_mode },
{ "replay_gain_status", PERMISSION_READ, 0, 0,
handle_replay_gain_status },
- { "rescan", PERMISSION_ADMIN, 0, 1, handle_rescan },
+ { "rescan", PERMISSION_CONTROL, 0, 1, handle_rescan },
{ "rm", PERMISSION_CONTROL, 1, 1, handle_rm },
{ "save", PERMISSION_CONTROL, 1, 1, handle_save },
{ "search", PERMISSION_READ, 2, -1, handle_search },
{ "seek", PERMISSION_CONTROL, 2, 2, handle_seek },
+ { "seekcur", PERMISSION_CONTROL, 1, 1, handle_seekcur },
{ "seekid", PERMISSION_CONTROL, 2, 2, handle_seekid },
+ { "sendmessage", PERMISSION_CONTROL, 2, 2, handle_send_message },
{ "setvol", PERMISSION_CONTROL, 1, 1, handle_setvol },
{ "shuffle", PERMISSION_CONTROL, 0, 1, handle_shuffle },
{ "single", PERMISSION_CONTROL, 1, 1, handle_single },
@@ -1896,10 +2015,12 @@ static const struct command commands[] = {
{ "sticker", PERMISSION_ADMIN, 3, -1, handle_sticker },
#endif
{ "stop", PERMISSION_CONTROL, 0, 0, handle_stop },
+ { "subscribe", PERMISSION_READ, 1, 1, handle_subscribe },
{ "swap", PERMISSION_CONTROL, 2, 2, handle_swap },
{ "swapid", PERMISSION_CONTROL, 2, 2, handle_swapid },
{ "tagtypes", PERMISSION_READ, 0, 0, handle_tagtypes },
- { "update", PERMISSION_ADMIN, 0, 1, handle_update },
+ { "unsubscribe", PERMISSION_READ, 1, 1, handle_unsubscribe },
+ { "update", PERMISSION_CONTROL, 0, 1, handle_update },
{ "urlhandlers", PERMISSION_READ, 0, 0, handle_urlhandlers },
};
@@ -2029,10 +2150,9 @@ static const struct command *
command_checked_lookup(struct client *client, unsigned permission,
int argc, char *argv[])
{
- static char unknown[] = "";
const struct command *cmd;
- current_command = unknown;
+ current_command = "";
if (argc == 0)
return NULL;
diff --git a/src/command.h b/src/command.h
index 39389385d..68d1f95e4 100644
--- a/src/command.h
+++ b/src/command.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/conf.c b/src/conf.c
index 14dac93a6..549ff51ef 100644
--- a/src/conf.c
+++ b/src/conf.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -20,6 +20,7 @@
#include "config.h"
#include "conf.h"
#include "utils.h"
+#include "string_util.h"
#include "tokenizer.h"
#include "path.h"
#include "glib_compat.h"
@@ -58,6 +59,7 @@ static struct config_entry config_entries[] = {
{ .name = CONF_LOG_FILE, false, false },
{ .name = CONF_PID_FILE, false, false },
{ .name = CONF_STATE_FILE, false, false },
+ { .name = "restore_paused", false, false },
{ .name = CONF_USER, false, false },
{ .name = CONF_GROUP, false, false },
{ .name = CONF_BIND_TO_ADDRESS, true, false },
@@ -97,6 +99,9 @@ static struct config_entry config_entries[] = {
{ .name = CONF_PLAYLIST_PLUGIN, true, true },
{ .name = CONF_AUTO_UPDATE, false, false },
{ .name = CONF_AUTO_UPDATE_DEPTH, false, false },
+ { .name = CONF_DESPOTIFY_USER, false, false },
+ { .name = CONF_DESPOTIFY_PASSWORD, false, false},
+ { .name = CONF_DESPOTIFY_HIGH_BITRATE, false, false },
{ .name = "filter", true, true },
};
@@ -138,7 +143,7 @@ config_new_param(const char *value, int line)
return ret;
}
-static void
+void
config_param_free(struct config_param *param)
{
g_free(param->value);
@@ -218,20 +223,13 @@ void config_global_check(void)
}
}
-bool
+void
config_add_block_param(struct config_param * param, const char *name,
- const char *value, int line, GError **error_r)
+ const char *value, int line)
{
struct block_param *bp;
- bp = config_get_block_param(param, name);
- if (bp != NULL) {
- g_set_error(error_r, config_quark(), 0,
- "\"%s\" first defined on line %i, and "
- "redefined on line %i\n", name,
- bp->line, line);
- return false;
- }
+ assert(config_get_block_param(param, name) == NULL);
param->num_block_params++;
@@ -245,7 +243,46 @@ config_add_block_param(struct config_param * param, const char *name,
bp->value = g_strdup(value);
bp->line = line;
bp->used = false;
+}
+static bool
+config_read_name_value(struct config_param *param, char *input, unsigned line,
+ GError **error_r)
+{
+ const char *name = tokenizer_next_word(&input, error_r);
+ if (name == NULL) {
+ assert(*input != 0);
+ return false;
+ }
+
+ const char *value = tokenizer_next_string(&input, error_r);
+ if (value == NULL) {
+ if (*input == 0) {
+ assert(error_r == NULL || *error_r == NULL);
+ g_set_error(error_r, config_quark(), 0,
+ "Value missing");
+ } else {
+ assert(error_r == NULL || *error_r != NULL);
+ }
+
+ return false;
+ }
+
+ if (*input != 0 && *input != CONF_COMMENT) {
+ g_set_error(error_r, config_quark(), 0,
+ "Unknown tokens after value");
+ return false;
+ }
+
+ const struct block_param *bp = config_get_block_param(param, name);
+ if (bp != NULL) {
+ g_set_error(error_r, config_quark(), 0,
+ "\"%s\" is duplicate, first defined on line %i",
+ name, bp->line);
+ return false;
+ }
+
+ config_add_block_param(param, name, value, line);
return true;
}
@@ -254,11 +291,9 @@ config_read_block(FILE *fp, int *count, char *string, GError **error_r)
{
struct config_param *ret = config_new_param(NULL, *count);
GError *error = NULL;
- bool success;
while (true) {
char *line;
- const char *name, *value;
line = fgets(string, MAX_STRING_SIZE, fp);
if (line == NULL) {
@@ -269,7 +304,7 @@ config_read_block(FILE *fp, int *count, char *string, GError **error_r)
}
(*count)++;
- line = g_strchug(line);
+ line = strchug_fast(line);
if (*line == 0 || *line == CONF_COMMENT)
continue;
@@ -277,7 +312,7 @@ config_read_block(FILE *fp, int *count, char *string, GError **error_r)
/* end of this block; return from the function
(and from this "while" loop) */
- line = g_strchug(line + 1);
+ line = strchug_fast(line + 1);
if (*line != 0 && *line != CONF_COMMENT) {
config_param_free(ret);
g_set_error(error_r, config_quark(), 0,
@@ -291,42 +326,13 @@ config_read_block(FILE *fp, int *count, char *string, GError **error_r)
/* parse name and value */
- name = tokenizer_next_word(&line, &error);
- if (name == NULL) {
+ if (!config_read_name_value(ret, line, *count, &error)) {
assert(*line != 0);
config_param_free(ret);
g_propagate_prefixed_error(error_r, error,
"line %i: ", *count);
return NULL;
}
-
- value = tokenizer_next_string(&line, &error);
- if (value == NULL) {
- config_param_free(ret);
- if (*line == 0)
- g_set_error(error_r, config_quark(), 0,
- "line %i: Value missing", *count);
- else
- g_propagate_prefixed_error(error_r, error,
- "line %i: ",
- *count);
- return NULL;
- }
-
- if (*line != 0 && *line != CONF_COMMENT) {
- config_param_free(ret);
- g_set_error(error_r, config_quark(), 0,
- "line %i: Unknown tokens after value",
- *count);
- return NULL;
- }
-
- success = config_add_block_param(ret, name, value, *count,
- error_r);
- if (!success) {
- config_param_free(ret);
- return false;
- }
}
}
@@ -344,7 +350,7 @@ config_read_file(const char *file, GError **error_r)
if (!(fp = fopen(file, "r"))) {
g_set_error(error_r, config_quark(), errno,
"Failed to open %s: %s",
- file, strerror(errno));
+ file, g_strerror(errno));
return false;
}
@@ -355,7 +361,7 @@ config_read_file(const char *file, GError **error_r)
count++;
- line = g_strchug(string);
+ line = strchug_fast(string);
if (*line == 0 || *line == CONF_COMMENT)
continue;
@@ -405,7 +411,7 @@ config_read_file(const char *file, GError **error_r)
return false;
}
- line = g_strchug(line + 1);
+ line = strchug_fast(line + 1);
if (*line != 0 && *line != CONF_COMMENT) {
g_set_error(error_r, config_quark(), 0,
"line %i: Unknown tokens after '{'",
@@ -457,7 +463,7 @@ config_read_file(const char *file, GError **error_r)
return true;
}
-struct config_param *
+const struct config_param *
config_get_next_param(const char *name, const struct config_param * last)
{
struct config_entry *entry;
@@ -497,22 +503,23 @@ config_get_string(const char *name, const char *default_value)
return param->value;
}
-const char *
-config_get_path(const char *name)
+char *
+config_dup_path(const char *name, GError **error_r)
{
- struct config_param *param = config_get_param(name);
- char *path;
+ assert(error_r != NULL);
+ assert(*error_r == NULL);
+ const struct config_param *param = config_get_param(name);
if (param == NULL)
return NULL;
- path = parsePath(param->value);
- if (path == NULL)
- MPD_ERROR("error parsing \"%s\" at line %i\n",
- name, param->line);
+ char *path = parsePath(param->value, error_r);
+ if (G_UNLIKELY(path == NULL))
+ g_prefix_error(error_r,
+ "Invalid path in \"%s\" at line %i: ",
+ name, param->line);
- g_free(param->value);
- return param->value = path;
+ return path;
}
unsigned
@@ -553,7 +560,7 @@ config_get_positive(const char *name, unsigned default_value)
return (unsigned)value;
}
-struct block_param *
+const struct block_param *
config_get_block_param(const struct config_param * param, const char *name)
{
if (param == NULL)
@@ -591,7 +598,7 @@ const char *
config_get_block_string(const struct config_param *param, const char *name,
const char *default_value)
{
- struct block_param *bp = config_get_block_param(param, name);
+ const struct block_param *bp = config_get_block_param(param, name);
if (bp == NULL)
return default_value;
@@ -599,11 +606,31 @@ config_get_block_string(const struct config_param *param, const char *name,
return bp->value;
}
+char *
+config_dup_block_path(const struct config_param *param, const char *name,
+ GError **error_r)
+{
+ assert(error_r != NULL);
+ assert(*error_r == NULL);
+
+ const struct block_param *bp = config_get_block_param(param, name);
+ if (bp == NULL)
+ return NULL;
+
+ char *path = parsePath(bp->value, error_r);
+ if (G_UNLIKELY(path == NULL))
+ g_prefix_error(error_r,
+ "Invalid path in \"%s\" at line %i: ",
+ name, bp->line);
+
+ return path;
+}
+
unsigned
config_get_block_unsigned(const struct config_param *param, const char *name,
unsigned default_value)
{
- struct block_param *bp = config_get_block_param(param, name);
+ const struct block_param *bp = config_get_block_param(param, name);
long value;
char *endptr;
@@ -624,7 +651,7 @@ bool
config_get_block_bool(const struct config_param *param, const char *name,
bool default_value)
{
- struct block_param *bp = config_get_block_param(param, name);
+ const struct block_param *bp = config_get_block_param(param, name);
bool success, value;
if (bp == NULL)
diff --git a/src/conf.h b/src/conf.h
index 8a0678312..815c739b1 100644
--- a/src/conf.h
+++ b/src/conf.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -72,6 +72,9 @@
#define CONF_PLAYLIST_PLUGIN "playlist_plugin"
#define CONF_AUTO_UPDATE "auto_update"
#define CONF_AUTO_UPDATE_DEPTH "auto_update_depth"
+#define CONF_DESPOTIFY_USER "despotify_user"
+#define CONF_DESPOTIFY_PASSWORD "despotify_password"
+#define CONF_DESPOTIFY_HIGH_BITRATE "despotify_high_bitrate"
#define DEFAULT_PLAYLIST_MAX_LENGTH (1024*16)
#define DEFAULT_PLAYLIST_SAVE_ABSOLUTE_PATHS false
@@ -108,6 +111,7 @@ struct config_param {
* A GQuark for GError instances, resulting from malformed
* configuration.
*/
+G_GNUC_CONST
static inline GQuark
config_quark(void)
{
@@ -129,11 +133,11 @@ config_read_file(const char *file, GError **error_r);
/* don't free the returned value
set _last_ to NULL to get first entry */
G_GNUC_PURE
-struct config_param *
+const struct config_param *
config_get_next_param(const char *name, const struct config_param *last);
G_GNUC_PURE
-static inline struct config_param *
+static inline const struct config_param *
config_get_param(const char *name)
{
return config_get_next_param(name, NULL);
@@ -152,17 +156,15 @@ config_get_string(const char *name, const char *default_value);
/**
* Returns an optional configuration variable which contains an
- * absolute path. If there is a tilde prefix, it is expanded. Aborts
- * MPD if the path is not a valid absolute path.
+ * absolute path. If there is a tilde prefix, it is expanded.
+ * Returns NULL if the value is not present. If the path could not be
+ * parsed, returns NULL and sets the error.
+ *
+ * The return value must be freed with g_free().
*/
-/* We lie here really. This function is not pure as it has side
- effects -- it parse the value and creates new string freeing
- previous one. However, because this works the very same way each
- time (ie. from the outside it appears as if function had no side
- effects) we should be in the clear declaring it pure. */
-G_GNUC_PURE
-const char *
-config_get_path(const char *name);
+G_GNUC_MALLOC
+char *
+config_dup_path(const char *name, GError **error_r);
G_GNUC_PURE
unsigned
@@ -173,7 +175,7 @@ unsigned
config_get_positive(const char *name, unsigned default_value);
G_GNUC_PURE
-struct block_param *
+const struct block_param *
config_get_block_param(const struct config_param *param, const char *name);
G_GNUC_PURE
@@ -184,6 +186,7 @@ const char *
config_get_block_string(const struct config_param *param, const char *name,
const char *default_value);
+G_GNUC_MALLOC
static inline char *
config_dup_block_string(const struct config_param *param, const char *name,
const char *default_value)
@@ -191,6 +194,15 @@ config_dup_block_string(const struct config_param *param, const char *name,
return g_strdup(config_get_block_string(param, name, default_value));
}
+/**
+ * Same as config_dup_path(), but looks up the setting in the
+ * specified block.
+ */
+G_GNUC_MALLOC
+char *
+config_dup_block_path(const struct config_param *param, const char *name,
+ GError **error_r);
+
G_GNUC_PURE
unsigned
config_get_block_unsigned(const struct config_param *param, const char *name,
@@ -201,11 +213,15 @@ bool
config_get_block_bool(const struct config_param *param, const char *name,
bool default_value);
+G_GNUC_MALLOC
struct config_param *
config_new_param(const char *value, int line);
-bool
+void
+config_param_free(struct config_param *param);
+
+void
config_add_block_param(struct config_param * param, const char *name,
- const char *value, int line, GError **error_r);
+ const char *value, int line);
#endif
diff --git a/src/crossfade.c b/src/crossfade.c
index cdfd82879..46a0dff30 100644
--- a/src/crossfade.c
+++ b/src/crossfade.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -19,7 +19,6 @@
#include "config.h"
#include "crossfade.h"
-#include "pcm_mix.h"
#include "chunk.h"
#include "audio_format.h"
#include "tag.h"
diff --git a/src/crossfade.h b/src/crossfade.h
index 096a62020..d581dbfe0 100644
--- a/src/crossfade.h
+++ b/src/crossfade.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/cue/cue_parser.c b/src/cue/cue_parser.c
new file mode 100644
index 000000000..034d4a1f9
--- /dev/null
+++ b/src/cue/cue_parser.c
@@ -0,0 +1,327 @@
+/*
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "cue_parser.h"
+#include "string_util.h"
+#include "song.h"
+#include "tag.h"
+
+#include <assert.h>
+#include <stdlib.h>
+
+struct cue_parser {
+ enum {
+ /**
+ * Parsing the CUE header.
+ */
+ HEADER,
+
+ /**
+ * Parsing a "FILE ... WAVE".
+ */
+ WAVE,
+
+ /**
+ * Ignore everything until the next "FILE".
+ */
+ IGNORE_FILE,
+
+ /**
+ * Parsing a "TRACK ... AUDIO".
+ */
+ TRACK,
+
+ /**
+ * Ignore everything until the next "TRACK".
+ */
+ IGNORE_TRACK,
+ } state;
+
+ struct tag *tag;
+
+ char *filename;
+
+ struct song *current, *previous, *finished;
+
+ bool last_updated;
+};
+
+struct cue_parser *
+cue_parser_new(void)
+{
+ struct cue_parser *parser = g_new(struct cue_parser, 1);
+ parser->state = HEADER;
+ parser->tag = tag_new();
+ parser->filename = NULL;
+ parser->current = NULL;
+ parser->previous = NULL;
+ parser->finished = NULL;
+ return parser;
+}
+
+void
+cue_parser_free(struct cue_parser *parser)
+{
+ tag_free(parser->tag);
+ g_free(parser->filename);
+
+ if (parser->current != NULL)
+ song_free(parser->current);
+
+ if (parser->finished != NULL)
+ song_free(parser->finished);
+
+ g_free(parser);
+}
+
+static const char *
+cue_next_word(char *p, char **pp)
+{
+ assert(p >= *pp);
+ assert(!g_ascii_isspace(*p));
+
+ const char *word = p;
+ while (*p != 0 && !g_ascii_isspace(*p))
+ ++p;
+
+ *p = 0;
+ *pp = p + 1;
+ return word;
+}
+
+static const char *
+cue_next_quoted(char *p, char **pp)
+{
+ assert(p >= *pp);
+ assert(p[-1] == '"');
+
+ char *end = strchr(p, '"');
+ if (end == NULL) {
+ /* syntax error - ignore it silently */
+ *pp = p + strlen(p);
+ return p;
+ }
+
+ *end = 0;
+ *pp = end + 1;
+
+ return p;
+}
+
+static const char *
+cue_next_token(char **pp)
+{
+ char *p = strchug_fast(*pp);
+ if (*p == 0)
+ return NULL;
+
+ return cue_next_word(p, pp);
+}
+
+static const char *
+cue_next_value(char **pp)
+{
+ char *p = strchug_fast(*pp);
+ if (*p == 0)
+ return NULL;
+
+ if (*p == '"')
+ return cue_next_quoted(p + 1, pp);
+ else
+ return cue_next_word(p, pp);
+}
+
+static void
+cue_add_tag(struct tag *tag, enum tag_type type, char *p)
+{
+ const char *value = cue_next_value(&p);
+ if (value != NULL)
+ tag_add_item(tag, type, value);
+
+}
+
+static void
+cue_parse_rem(char *p, struct tag *tag)
+{
+ const char *type = cue_next_token(&p);
+ if (type == NULL)
+ return;
+
+ enum tag_type type2 = tag_name_parse_i(type);
+ if (type2 != TAG_NUM_OF_ITEM_TYPES)
+ cue_add_tag(tag, type2, p);
+}
+
+static struct tag *
+cue_current_tag(struct cue_parser *parser)
+{
+ if (parser->state == HEADER)
+ return parser->tag;
+ else if (parser->state == TRACK)
+ return parser->current->tag;
+ else
+ return NULL;
+}
+
+static int
+cue_parse_position(const char *p)
+{
+ char *endptr;
+ unsigned long minutes = strtoul(p, &endptr, 10);
+ if (endptr == p || *endptr != ':')
+ return -1;
+
+ p = endptr + 1;
+ unsigned long seconds = strtoul(p, &endptr, 10);
+ if (endptr == p || *endptr != ':')
+ return -1;
+
+ p = endptr + 1;
+ unsigned long frames = strtoul(p, &endptr, 10);
+ if (endptr == p || *endptr != 0)
+ return -1;
+
+ return minutes * 60000 + seconds * 1000 + frames * 1000 / 75;
+}
+
+static void
+cue_parser_feed2(struct cue_parser *parser, char *p)
+{
+ assert(parser != NULL);
+ assert(p != NULL);
+
+ const char *command = cue_next_token(&p);
+ if (command == NULL)
+ return;
+
+ if (strcmp(command, "REM") == 0) {
+ struct tag *tag = cue_current_tag(parser);
+ if (tag != NULL)
+ cue_parse_rem(p, tag);
+ } else if (strcmp(command, "PERFORMER") == 0) {
+ struct tag *tag = cue_current_tag(parser);
+ if (tag != NULL)
+ cue_add_tag(tag, TAG_PERFORMER, p);
+ } else if (strcmp(command, "TITLE") == 0) {
+ if (parser->state == HEADER)
+ cue_add_tag(parser->tag, TAG_ALBUM, p);
+ else if (parser->state == TRACK)
+ cue_add_tag(parser->current->tag, TAG_TITLE, p);
+ } else if (strcmp(command, "FILE") == 0) {
+ cue_parser_finish(parser);
+
+ const char *filename = cue_next_value(&p);
+ if (filename == NULL)
+ return;
+
+ const char *type = cue_next_token(&p);
+ if (type == NULL)
+ return;
+
+ if (strcmp(type, "WAVE") != 0) {
+ parser->state = IGNORE_FILE;
+ return;
+ }
+
+ parser->state = WAVE;
+ g_free(parser->filename);
+ parser->filename = g_strdup(filename);
+ } else if (parser->state == IGNORE_FILE) {
+ return;
+ } else if (strcmp(command, "TRACK") == 0) {
+ cue_parser_finish(parser);
+
+ const char *nr = cue_next_token(&p);
+ if (nr == NULL)
+ return;
+
+ const char *type = cue_next_token(&p);
+ if (type == NULL)
+ return;
+
+ if (strcmp(type, "AUDIO") != 0) {
+ parser->state = IGNORE_TRACK;
+ return;
+ }
+
+ parser->state = TRACK;
+ parser->current = song_remote_new(parser->filename);
+ assert(parser->current->tag == NULL);
+ parser->current->tag = tag_dup(parser->tag);
+ tag_add_item(parser->current->tag, TAG_TRACK, nr);
+ parser->last_updated = false;
+ } else if (parser->state == IGNORE_TRACK) {
+ return;
+ } else if (parser->state == TRACK && strcmp(command, "INDEX") == 0) {
+ const char *nr = cue_next_token(&p);
+ if (nr == NULL)
+ return;
+
+ const char *position = cue_next_token(&p);
+ if (position == NULL)
+ return;
+
+ int position_ms = cue_parse_position(position);
+ if (position_ms < 0)
+ return;
+
+ if (!parser->last_updated && parser->previous != NULL &&
+ parser->previous->start_ms < (unsigned)position_ms) {
+ parser->last_updated = true;
+ parser->previous->end_ms = position_ms;
+ parser->previous->tag->time =
+ (parser->previous->end_ms - parser->previous->start_ms + 500) / 1000;
+ }
+
+ parser->current->start_ms = position_ms;
+ }
+}
+
+void
+cue_parser_feed(struct cue_parser *parser, const char *line)
+{
+ assert(parser != NULL);
+ assert(line != NULL);
+
+ char *allocated = g_strdup(line);
+ cue_parser_feed2(parser, allocated);
+ g_free(allocated);
+}
+
+void
+cue_parser_finish(struct cue_parser *parser)
+{
+ if (parser->finished != NULL)
+ song_free(parser->finished);
+
+ parser->finished = parser->previous;
+ parser->previous = parser->current;
+ parser->current = NULL;
+}
+
+struct song *
+cue_parser_get(struct cue_parser *parser)
+{
+ assert(parser != NULL);
+
+ struct song *song = parser->finished;
+ parser->finished = NULL;
+ return song;
+}
diff --git a/src/cue/cue_parser.h b/src/cue/cue_parser.h
new file mode 100644
index 000000000..d8d695739
--- /dev/null
+++ b/src/cue/cue_parser.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_CUE_PARSER_H
+#define MPD_CUE_PARSER_H
+
+#include "check.h"
+
+#include <stdbool.h>
+
+struct cue_parser *
+cue_parser_new(void);
+
+void
+cue_parser_free(struct cue_parser *parser);
+
+/**
+ * Feed a text line from the CUE file into the parser. Call
+ * cue_parser_get() after this to see if a song has been finished.
+ */
+void
+cue_parser_feed(struct cue_parser *parser, const char *line);
+
+/**
+ * Tell the parser that the end of the file has been reached. Call
+ * cue_parser_get() after this to see if a song has been finished.
+ * This procedure must be done twice!
+ */
+void
+cue_parser_finish(struct cue_parser *parser);
+
+/**
+ * Check if a song was finished by the last cue_parser_feed() or
+ * cue_parser_finish() call.
+ *
+ * @return a song object that must be freed by the caller, or NULL if
+ * no song was finished at this time
+ */
+struct song *
+cue_parser_get(struct cue_parser *parser);
+
+#endif
diff --git a/src/cue/cue_tag.c b/src/cue/cue_tag.c
deleted file mode 100644
index ba1172559..000000000
--- a/src/cue/cue_tag.c
+++ /dev/null
@@ -1,235 +0,0 @@
-#include "config.h"
-#include "cue_tag.h"
-#include "tag.h"
-
-#include <libcue/libcue.h>
-#include <assert.h>
-
-static struct tag *
-cue_tag_cd(struct Cdtext *cdtext, struct Rem *rem)
-{
- struct tag *tag;
- char *tmp;
-
- assert(cdtext != NULL);
-
- tag = tag_new();
-
- tag_begin_add(tag);
-
- /* TAG_ALBUM_ARTIST */
- if ((tmp = cdtext_get(PTI_PERFORMER, cdtext)) != NULL)
- tag_add_item(tag, TAG_ALBUM_ARTIST, tmp);
-
- else if ((tmp = cdtext_get(PTI_SONGWRITER, cdtext)) != NULL)
- tag_add_item(tag, TAG_ALBUM_ARTIST, tmp);
-
- else if ((tmp = cdtext_get(PTI_COMPOSER, cdtext)) != NULL)
- tag_add_item(tag, TAG_ALBUM_ARTIST, tmp);
-
- else if ((tmp = cdtext_get(PTI_ARRANGER, cdtext)) != NULL)
- tag_add_item(tag, TAG_ALBUM_ARTIST, tmp);
-
- /* TAG_ARTIST */
- if ((tmp = cdtext_get(PTI_PERFORMER, cdtext)) != NULL)
- tag_add_item(tag, TAG_ARTIST, tmp);
-
- else if ((tmp = cdtext_get(PTI_SONGWRITER, cdtext)) != NULL)
- tag_add_item(tag, TAG_ARTIST, tmp);
-
- else if ((tmp = cdtext_get(PTI_COMPOSER, cdtext)) != NULL)
- tag_add_item(tag, TAG_ARTIST, tmp);
-
- else if ((tmp = cdtext_get(PTI_ARRANGER, cdtext)) != NULL)
- tag_add_item(tag, TAG_ARTIST, tmp);
-
- /* TAG_PERFORMER */
- if ((tmp = cdtext_get(PTI_PERFORMER, cdtext)) != NULL)
- tag_add_item(tag, TAG_PERFORMER, tmp);
-
- /* TAG_COMPOSER */
- if ((tmp = cdtext_get(PTI_COMPOSER, cdtext)) != NULL)
- tag_add_item(tag, TAG_COMPOSER, tmp);
-
- /* TAG_ALBUM */
- if ((tmp = cdtext_get(PTI_TITLE, cdtext)) != NULL)
- tag_add_item(tag, TAG_ALBUM, tmp);
-
- /* TAG_GENRE */
- if ((tmp = cdtext_get(PTI_GENRE, cdtext)) != NULL)
- tag_add_item(tag, TAG_GENRE, tmp);
-
- /* TAG_DATE */
- if ((tmp = rem_get(REM_DATE, rem)) != NULL)
- tag_add_item(tag, TAG_DATE, tmp);
-
- /* TAG_COMMENT */
- if ((tmp = cdtext_get(PTI_MESSAGE, cdtext)) != NULL)
- tag_add_item(tag, TAG_COMMENT, tmp);
-
- /* TAG_DISC */
- if ((tmp = cdtext_get(PTI_DISC_ID, cdtext)) != NULL)
- tag_add_item(tag, TAG_DISC, tmp);
-
- /* stream name, usually empty
- * tag_add_item(tag, TAG_NAME,);
- */
-
- /* REM MUSICBRAINZ entry?
- tag_add_item(tag, TAG_MUSICBRAINZ_ARTISTID,);
- tag_add_item(tag, TAG_MUSICBRAINZ_ALBUMID,);
- tag_add_item(tag, TAG_MUSICBRAINZ_ALBUMARTISTID,);
- tag_add_item(tag, TAG_MUSICBRAINZ_TRACKID,);
- */
-
- tag_end_add(tag);
-
- if (tag_is_empty(tag)) {
- tag_free(tag);
- return NULL;
- }
-
- return tag;
-}
-
-static struct tag *
-cue_tag_track(struct Cdtext *cdtext, struct Rem *rem)
-{
- struct tag *tag;
- char *tmp;
-
- assert(cdtext != NULL);
-
- tag = tag_new();
-
- tag_begin_add(tag);
-
- /* TAG_ARTIST */
- if ((tmp = cdtext_get(PTI_PERFORMER, cdtext)) != NULL)
- tag_add_item(tag, TAG_ARTIST, tmp);
-
- else if ((tmp = cdtext_get(PTI_SONGWRITER, cdtext)) != NULL)
- tag_add_item(tag, TAG_ARTIST, tmp);
-
- else if ((tmp = cdtext_get(PTI_COMPOSER, cdtext)) != NULL)
- tag_add_item(tag, TAG_ARTIST, tmp);
-
- else if ((tmp = cdtext_get(PTI_ARRANGER, cdtext)) != NULL)
- tag_add_item(tag, TAG_ARTIST, tmp);
-
- /* TAG_TITLE */
- if ((tmp = cdtext_get(PTI_TITLE, cdtext)) != NULL)
- tag_add_item(tag, TAG_TITLE, tmp);
-
- /* TAG_GENRE */
- if ((tmp = cdtext_get(PTI_GENRE, cdtext)) != NULL)
- tag_add_item(tag, TAG_GENRE, tmp);
-
- /* TAG_DATE */
- if ((tmp = rem_get(REM_DATE, rem)) != NULL)
- tag_add_item(tag, TAG_DATE, tmp);
-
- /* TAG_COMPOSER */
- if ((tmp = cdtext_get(PTI_COMPOSER, cdtext)) != NULL)
- tag_add_item(tag, TAG_COMPOSER, tmp);
-
- /* TAG_PERFORMER */
- if ((tmp = cdtext_get(PTI_PERFORMER, cdtext)) != NULL)
- tag_add_item(tag, TAG_PERFORMER, tmp);
-
- /* TAG_COMMENT */
- if ((tmp = cdtext_get(PTI_MESSAGE, cdtext)) != NULL)
- tag_add_item(tag, TAG_COMMENT, tmp);
-
- /* TAG_DISC */
- if ((tmp = cdtext_get(PTI_DISC_ID, cdtext)) != NULL)
- tag_add_item(tag, TAG_DISC, tmp);
-
- tag_end_add(tag);
-
- if (tag_is_empty(tag)) {
- tag_free(tag);
- return NULL;
- }
-
- return tag;
-}
-
-struct tag *
-cue_tag(struct Cd *cd, unsigned tnum)
-{
- struct tag *cd_tag, *track_tag, *tag;
- struct Track *track;
-
- assert(cd != NULL);
-
- track = cd_get_track(cd, tnum);
- if (track == NULL)
- return NULL;
-
- /* tag from CDtext info */
- cd_tag = cue_tag_cd(cd_get_cdtext(cd), cd_get_rem(cd));
-
- /* tag from TRACKtext info */
- track_tag = cue_tag_track(track_get_cdtext(track),
- track_get_rem(track));
-
- tag = tag_merge_replace(cd_tag, track_tag);
- if (tag == NULL)
- return NULL;
-
- tag->time = track_get_length(track)
- - track_get_index(track, 1)
- + track_get_zero_pre(track);
- track = cd_get_track(cd, tnum + 1);
- if (track != NULL)
- tag->time += track_get_index(track, 1)
- - track_get_zero_pre(track);
- /* libcue returns the track duration in frames, and there are
- 75 frames per second; this formula rounds down */
- tag->time = tag->time / 75;
-
- return tag;
-}
-
-struct tag *
-cue_tag_file(FILE *fp, unsigned tnum)
-{
- struct Cd *cd;
- struct tag *tag;
-
- assert(fp != NULL);
-
- if (tnum > 256)
- return NULL;
-
- cd = cue_parse_file(fp);
- if (cd == NULL)
- return NULL;
-
- tag = cue_tag(cd, tnum);
- cd_delete(cd);
-
- return tag;
-}
-
-struct tag *
-cue_tag_string(const char *str, unsigned tnum)
-{
- struct Cd *cd;
- struct tag *tag;
-
- assert(str != NULL);
-
- if (tnum > 256)
- return NULL;
-
- cd = cue_parse_string(str);
- if (cd == NULL)
- return NULL;
-
- tag = cue_tag(cd, tnum);
- cd_delete(cd);
-
- return tag;
-}
diff --git a/src/cue/cue_tag.h b/src/cue/cue_tag.h
deleted file mode 100644
index 1ddaa59c8..000000000
--- a/src/cue/cue_tag.h
+++ /dev/null
@@ -1,23 +0,0 @@
-#ifndef MPD_CUE_TAG_H
-#define MPD_CUE_TAG_H
-
-#include "check.h"
-
-#ifdef HAVE_CUE /* libcue */
-
-#include <stdio.h>
-
-struct tag;
-struct Cd;
-
-struct tag *
-cue_tag(struct Cd *cd, unsigned tnum);
-
-struct tag *
-cue_tag_file(FILE *file, unsigned tnum);
-
-struct tag *
-cue_tag_string(const char *str, unsigned tnum);
-
-#endif /* libcue */
-#endif
diff --git a/src/daemon.c b/src/daemon.c
index 852541375..8bca9095a 100644
--- a/src/daemon.c
+++ b/src/daemon.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -80,7 +80,7 @@ daemonize_kill(void)
ret = kill(pid, SIGTERM);
if (ret < 0)
- MPD_ERROR("unable to kill proccess %i: %s",
+ MPD_ERROR("unable to kill process %i: %s",
pid, g_strerror(errno));
exit(EXIT_SUCCESS);
diff --git a/src/daemon.h b/src/daemon.h
index 71039543c..c43a74cfd 100644
--- a/src/daemon.h
+++ b/src/daemon.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/database.c b/src/database.c
index 9f29f95e1..8c903bb45 100644
--- a/src/database.c
+++ b/src/database.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -19,14 +19,16 @@
#include "config.h"
#include "database.h"
+#include "db_error.h"
+#include "db_save.h"
+#include "db_selection.h"
+#include "db_visitor.h"
+#include "db_plugin.h"
+#include "db/simple_db_plugin.h"
#include "directory.h"
-#include "directory_save.h"
-#include "song.h"
-#include "path.h"
#include "stats.h"
-#include "text_file.h"
-#include "tag.h"
-#include "tag_internal.h"
+#include "conf.h"
+#include "glib_compat.h"
#include <glib.h>
@@ -35,85 +37,64 @@
#include <unistd.h>
#include <assert.h>
#include <string.h>
-#include <stdlib.h>
#include <errno.h>
#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "database"
-#define DIRECTORY_INFO_BEGIN "info_begin"
-#define DIRECTORY_INFO_END "info_end"
-#define DB_FORMAT_PREFIX "format: "
-#define DIRECTORY_MPD_VERSION "mpd_version: "
-#define DIRECTORY_FS_CHARSET "fs_charset: "
-#define DB_TAG_PREFIX "tag: "
+static struct db *db;
+static bool db_is_open;
-enum {
- DB_FORMAT = 1,
-};
-
-static char *database_path;
-
-static struct directory *music_root;
-
-static time_t database_mtime;
-
-/**
- * The quark used for GError.domain.
- */
-static inline GQuark
-db_quark(void)
+bool
+db_init(const struct config_param *path, GError **error_r)
{
- return g_quark_from_static_string("database");
-}
+ assert(db == NULL);
+ assert(!db_is_open);
-void
-db_init(const char *path)
-{
- database_path = g_strdup(path);
+ if (path == NULL)
+ return true;
- if (path != NULL)
- music_root = directory_new("", NULL);
-}
+ struct config_param *param = config_new_param("database", path->line);
+ config_add_block_param(param, "path", path->value, path->line);
-void
-db_finish(void)
-{
- assert((database_path == NULL) == (music_root == NULL));
+ db = db_plugin_new(&simple_db_plugin, param, error_r);
- if (music_root != NULL)
- directory_free(music_root);
+ config_param_free(param);
- g_free(database_path);
+ return db != NULL;
}
void
-db_clear(void)
+db_finish(void)
{
- assert(music_root != NULL);
+ if (db_is_open)
+ db_plugin_close(db);
- directory_free(music_root);
- music_root = directory_new("", NULL);
+ if (db != NULL)
+ db_plugin_free(db);
}
struct directory *
db_get_root(void)
{
- assert(music_root != NULL);
+ assert(db != NULL);
- return music_root;
+ return simple_db_get_root(db);
}
struct directory *
db_get_directory(const char *name)
{
- if (music_root == NULL)
+ if (db == NULL)
return NULL;
+ struct directory *music_root = db_get_root();
if (name == NULL)
return music_root;
- return directory_lookup_directory(music_root, name);
+ struct directory *directory =
+ directory_lookup_directory(music_root, name);
+ return directory;
}
struct song *
@@ -123,281 +104,67 @@ db_get_song(const char *file)
g_debug("get song: %s", file);
- if (music_root == NULL)
+ if (db == NULL)
return NULL;
- return directory_lookup_song(music_root, file);
+ return db_plugin_get_song(db, file, NULL);
}
-int
-db_walk(const char *name,
- int (*forEachSong)(struct song *, void *),
- int (*forEachDir)(struct directory *, void *), void *data)
+bool
+db_visit(const struct db_selection *selection,
+ const struct db_visitor *visitor, void *ctx,
+ GError **error_r)
{
- struct directory *directory;
-
- if (music_root == NULL)
- return -1;
-
- if ((directory = db_get_directory(name)) == NULL) {
- struct song *song;
- if ((song = db_get_song(name)) && forEachSong) {
- return forEachSong(song, data);
- }
- return -1;
+ if (db == NULL) {
+ g_set_error_literal(error_r, db_quark(), DB_DISABLED,
+ "No database");
+ return false;
}
- return directory_walk(directory, forEachSong, forEachDir, data);
+ return db_plugin_visit(db, selection, visitor, ctx, error_r);
}
bool
-db_check(void)
+db_walk(const char *uri,
+ const struct db_visitor *visitor, void *ctx,
+ GError **error_r)
{
- struct stat st;
-
- assert(database_path != NULL);
-
- /* Check if the file exists */
- if (access(database_path, F_OK)) {
- /* If the file doesn't exist, we can't check if we can write
- * it, so we are going to try to get the directory path, and
- * see if we can write a file in that */
- char *dirPath = g_path_get_dirname(database_path);
-
- /* Check that the parent part of the path is a directory */
- if (stat(dirPath, &st) < 0) {
- g_free(dirPath);
- g_warning("Couldn't stat parent directory of db file "
- "\"%s\": %s", database_path, strerror(errno));
- return false;
- }
-
- if (!S_ISDIR(st.st_mode)) {
- g_free(dirPath);
- g_warning("Couldn't create db file \"%s\" because the "
- "parent path is not a directory", database_path);
- return false;
- }
-
- /* Check if we can write to the directory */
- if (access(dirPath, X_OK | W_OK)) {
- g_warning("Can't create db file in \"%s\": %s",
- dirPath, strerror(errno));
- g_free(dirPath);
- return false;
- }
-
- g_free(dirPath);
-
- return true;
- }
-
- /* Path exists, now check if it's a regular file */
- if (stat(database_path, &st) < 0) {
- g_warning("Couldn't stat db file \"%s\": %s",
- database_path, strerror(errno));
- return false;
- }
+ struct db_selection selection;
+ db_selection_init(&selection, uri, true);
- if (!S_ISREG(st.st_mode)) {
- g_warning("db file \"%s\" is not a regular file", database_path);
- return false;
- }
-
- /* And check that we can write to it */
- if (access(database_path, R_OK | W_OK)) {
- g_warning("Can't open db file \"%s\" for reading/writing: %s",
- database_path, strerror(errno));
- return false;
- }
-
- return true;
+ return db_visit(&selection, visitor, ctx, error_r);
}
bool
-db_save(void)
+db_save(GError **error_r)
{
- FILE *fp;
- struct stat st;
-
- assert(database_path != NULL);
- assert(music_root != NULL);
+ assert(db != NULL);
+ assert(db_is_open);
- g_debug("removing empty directories from DB");
- directory_prune_empty(music_root);
-
- g_debug("sorting DB");
-
- directory_sort(music_root);
-
- g_debug("writing DB");
-
- fp = fopen(database_path, "w");
- if (!fp) {
- g_warning("unable to write to db file \"%s\": %s",
- database_path, strerror(errno));
- return false;
- }
-
- fprintf(fp, "%s\n", DIRECTORY_INFO_BEGIN);
- fprintf(fp, DB_FORMAT_PREFIX "%u\n", DB_FORMAT);
- fprintf(fp, "%s%s\n", DIRECTORY_MPD_VERSION, VERSION);
- fprintf(fp, "%s%s\n", DIRECTORY_FS_CHARSET, path_get_fs_charset());
-
- for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i)
- if (!ignore_tag_items[i])
- fprintf(fp, DB_TAG_PREFIX "%s\n", tag_item_names[i]);
-
- fprintf(fp, "%s\n", DIRECTORY_INFO_END);
-
- directory_save(fp, music_root);
-
- if (ferror(fp)) {
- g_warning("Failed to write to database file: %s",
- strerror(errno));
- fclose(fp);
- return false;
- }
-
- fclose(fp);
-
- if (stat(database_path, &st) == 0)
- database_mtime = st.st_mtime;
-
- return true;
+ return simple_db_save(db, error_r);
}
bool
db_load(GError **error)
{
- FILE *fp = NULL;
- struct stat st;
- GString *buffer = g_string_sized_new(1024);
- char *line;
- int format = 0;
- bool found_charset = false, found_version = false;
- bool success;
- bool tags[TAG_NUM_OF_ITEM_TYPES];
-
- assert(database_path != NULL);
- assert(music_root != NULL);
-
- fp = fopen(database_path, "r");
- if (fp == NULL) {
- g_set_error(error, db_quark(), errno,
- "Failed to open database file \"%s\": %s",
- database_path, strerror(errno));
- g_string_free(buffer, true);
- return false;
- }
-
- /* get initial info */
- line = read_text_line(fp, buffer);
- if (line == NULL || strcmp(DIRECTORY_INFO_BEGIN, line) != 0) {
- fclose(fp);
- g_set_error(error, db_quark(), 0, "Database corrupted");
- g_string_free(buffer, true);
- return false;
- }
-
- memset(tags, false, sizeof(tags));
-
- while ((line = read_text_line(fp, buffer)) != NULL &&
- strcmp(line, DIRECTORY_INFO_END) != 0) {
- if (g_str_has_prefix(line, DB_FORMAT_PREFIX)) {
- format = atoi(line + sizeof(DB_FORMAT_PREFIX) - 1);
- } else if (g_str_has_prefix(line, DIRECTORY_MPD_VERSION)) {
- if (found_version) {
- fclose(fp);
- g_set_error(error, db_quark(), 0,
- "Duplicate version line");
- g_string_free(buffer, true);
- return false;
- }
-
- found_version = true;
- } else if (g_str_has_prefix(line, DIRECTORY_FS_CHARSET)) {
- const char *new_charset, *old_charset;
-
- if (found_charset) {
- fclose(fp);
- g_set_error(error, db_quark(), 0,
- "Duplicate charset line");
- g_string_free(buffer, true);
- return false;
- }
-
- found_charset = true;
-
- new_charset = line + sizeof(DIRECTORY_FS_CHARSET) - 1;
- old_charset = path_get_fs_charset();
- if (old_charset != NULL
- && strcmp(new_charset, old_charset)) {
- fclose(fp);
- g_set_error(error, db_quark(), 0,
- "Existing database has charset "
- "\"%s\" instead of \"%s\"; "
- "discarding database file",
- new_charset, old_charset);
- g_string_free(buffer, true);
- return false;
- }
- } else if (g_str_has_prefix(line, DB_TAG_PREFIX)) {
- const char *name = line + sizeof(DB_TAG_PREFIX) - 1;
- enum tag_type tag = tag_name_parse(name);
- if (tag == TAG_NUM_OF_ITEM_TYPES) {
- g_set_error(error, db_quark(), 0,
- "Unrecognized tag '%s', "
- "discarding database file",
- name);
- return false;
- }
-
- tags[tag] = true;
- } else {
- fclose(fp);
- g_set_error(error, db_quark(), 0,
- "Malformed line: %s", line);
- g_string_free(buffer, true);
- return false;
- }
- }
+ assert(db != NULL);
+ assert(!db_is_open);
- if (format != DB_FORMAT) {
- g_set_error(error, db_quark(), 0,
- "Database format mismatch, "
- "discarding database file");
+ if (!db_plugin_open(db, error))
return false;
- }
-
- for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) {
- if (!ignore_tag_items[i] && !tags[i]) {
- g_set_error(error, db_quark(), 0,
- "Tag list mismatch, "
- "discarding database file");
- return false;
- }
- }
-
- g_debug("reading DB");
- success = directory_load(fp, music_root, buffer, error);
- g_string_free(buffer, true);
- fclose(fp);
-
- if (!success)
- return false;
+ db_is_open = true;
stats_update();
- if (stat(database_path, &st) == 0)
- database_mtime = st.st_mtime;
-
return true;
}
time_t
db_get_mtime(void)
{
- return database_mtime;
+ assert(db != NULL);
+ assert(db_is_open);
+
+ return simple_db_get_mtime(db);
}
diff --git a/src/database.h b/src/database.h
index 67149b20b..f877b74d3 100644
--- a/src/database.h
+++ b/src/database.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -20,62 +20,76 @@
#ifndef MPD_DATABASE_H
#define MPD_DATABASE_H
+#include "gcc.h"
+
#include <glib.h>
#include <sys/time.h>
#include <stdbool.h>
+struct config_param;
struct directory;
+struct db_selection;
+struct db_visitor;
/**
* Initialize the database library.
*
* @param path the absolute path of the database file
*/
-void
-db_init(const char *path);
+bool
+db_init(const struct config_param *path, GError **error_r);
void
db_finish(void);
/**
- * Clear the database.
- */
-void
-db_clear(void);
-
-/**
* Returns the root directory object. Returns NULL if there is no
* configured music directory.
*/
+G_GNUC_PURE
struct directory *
db_get_root(void);
+/**
+ * Caller must lock the #db_mutex.
+ */
+gcc_nonnull(1)
+G_GNUC_PURE
struct directory *
db_get_directory(const char *name);
+gcc_nonnull(1)
+G_GNUC_PURE
struct song *
db_get_song(const char *file);
-int db_walk(const char *name,
- int (*forEachSong)(struct song *, void *),
- int (*forEachDir)(struct directory *, void *), void *data);
+gcc_nonnull(1,2)
+bool
+db_visit(const struct db_selection *selection,
+ const struct db_visitor *visitor, void *ctx,
+ GError **error_r);
+gcc_nonnull(1,2)
bool
-db_check(void);
+db_walk(const char *uri,
+ const struct db_visitor *visitor, void *ctx,
+ GError **error_r);
bool
-db_save(void);
+db_save(GError **error_r);
bool
db_load(GError **error);
+G_GNUC_PURE
time_t
db_get_mtime(void);
/**
* Returns true if there is a valid database file on the disk.
*/
+G_GNUC_PURE
static inline bool
db_exists(void)
{
diff --git a/src/db/simple_db_plugin.c b/src/db/simple_db_plugin.c
new file mode 100644
index 000000000..f11090828
--- /dev/null
+++ b/src/db/simple_db_plugin.c
@@ -0,0 +1,358 @@
+/*
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "simple_db_plugin.h"
+#include "db_internal.h"
+#include "db_error.h"
+#include "db_selection.h"
+#include "db_visitor.h"
+#include "db_save.h"
+#include "db_lock.h"
+#include "conf.h"
+#include "glib_compat.h"
+#include "directory.h"
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <errno.h>
+
+struct simple_db {
+ struct db base;
+
+ char *path;
+
+ struct directory *root;
+
+ time_t mtime;
+};
+
+G_GNUC_CONST
+static inline GQuark
+simple_db_quark(void)
+{
+ return g_quark_from_static_string("simple_db");
+}
+
+G_GNUC_PURE
+static const struct directory *
+simple_db_lookup_directory(const struct simple_db *db, const char *uri)
+{
+ assert(db != NULL);
+ assert(db->root != NULL);
+ assert(uri != NULL);
+
+ db_lock();
+ struct directory *directory =
+ directory_lookup_directory(db->root, uri);
+ db_unlock();
+ return directory;
+}
+
+static struct db *
+simple_db_init(const struct config_param *param, GError **error_r)
+{
+ struct simple_db *db = g_malloc(sizeof(*db));
+ db_base_init(&db->base, &simple_db_plugin);
+
+ GError *error = NULL;
+ db->path = config_dup_block_path(param, "path", &error);
+ if (db->path == NULL) {
+ g_free(db);
+ if (error != NULL)
+ g_propagate_error(error_r, error);
+ else
+ g_set_error(error_r, simple_db_quark(), 0,
+ "No \"path\" parameter specified");
+ return NULL;
+ }
+
+ return &db->base;
+}
+
+static void
+simple_db_finish(struct db *_db)
+{
+ struct simple_db *db = (struct simple_db *)_db;
+
+ g_free(db->path);
+ g_free(db);
+}
+
+static bool
+simple_db_check(struct simple_db *db, GError **error_r)
+{
+ assert(db != NULL);
+ assert(db->path != NULL);
+
+ /* Check if the file exists */
+ if (access(db->path, F_OK)) {
+ /* If the file doesn't exist, we can't check if we can write
+ * it, so we are going to try to get the directory path, and
+ * see if we can write a file in that */
+ char *dirPath = g_path_get_dirname(db->path);
+
+ /* Check that the parent part of the path is a directory */
+ struct stat st;
+ if (stat(dirPath, &st) < 0) {
+ g_free(dirPath);
+ g_set_error(error_r, simple_db_quark(), errno,
+ "Couldn't stat parent directory of db file "
+ "\"%s\": %s",
+ db->path, g_strerror(errno));
+ return false;
+ }
+
+ if (!S_ISDIR(st.st_mode)) {
+ g_free(dirPath);
+ g_set_error(error_r, simple_db_quark(), 0,
+ "Couldn't create db file \"%s\" because the "
+ "parent path is not a directory",
+ db->path);
+ return false;
+ }
+
+ /* Check if we can write to the directory */
+ if (access(dirPath, X_OK | W_OK)) {
+ g_set_error(error_r, simple_db_quark(), errno,
+ "Can't create db file in \"%s\": %s",
+ dirPath, g_strerror(errno));
+ g_free(dirPath);
+ return false;
+ }
+
+ g_free(dirPath);
+
+ return true;
+ }
+
+ /* Path exists, now check if it's a regular file */
+ struct stat st;
+ if (stat(db->path, &st) < 0) {
+ g_set_error(error_r, simple_db_quark(), errno,
+ "Couldn't stat db file \"%s\": %s",
+ db->path, g_strerror(errno));
+ return false;
+ }
+
+ if (!S_ISREG(st.st_mode)) {
+ g_set_error(error_r, simple_db_quark(), 0,
+ "db file \"%s\" is not a regular file",
+ db->path);
+ return false;
+ }
+
+ /* And check that we can write to it */
+ if (access(db->path, R_OK | W_OK)) {
+ g_set_error(error_r, simple_db_quark(), errno,
+ "Can't open db file \"%s\" for reading/writing: %s",
+ db->path, g_strerror(errno));
+ return false;
+ }
+
+ return true;
+}
+
+static bool
+simple_db_load(struct simple_db *db, GError **error_r)
+{
+ assert(db != NULL);
+ assert(db->path != NULL);
+ assert(db->root != NULL);
+
+ FILE *fp = fopen(db->path, "r");
+ if (fp == NULL) {
+ g_set_error(error_r, simple_db_quark(), errno,
+ "Failed to open database file \"%s\": %s",
+ db->path, g_strerror(errno));
+ return false;
+ }
+
+ if (!db_load_internal(fp, db->root, error_r)) {
+ fclose(fp);
+ return false;
+ }
+
+ fclose(fp);
+
+ struct stat st;
+ if (stat(db->path, &st) == 0)
+ db->mtime = st.st_mtime;
+
+ return true;
+}
+
+static bool
+simple_db_open(struct db *_db, G_GNUC_UNUSED GError **error_r)
+{
+ struct simple_db *db = (struct simple_db *)_db;
+
+ db->root = directory_new_root();
+ db->mtime = 0;
+
+ GError *error = NULL;
+ if (!simple_db_load(db, &error)) {
+ directory_free(db->root);
+
+ g_warning("Failed to load database: %s", error->message);
+ g_error_free(error);
+
+ if (!simple_db_check(db, error_r))
+ return false;
+
+ db->root = directory_new_root();
+ }
+
+ return true;
+}
+
+static void
+simple_db_close(struct db *_db)
+{
+ struct simple_db *db = (struct simple_db *)_db;
+
+ assert(db->root != NULL);
+
+ directory_free(db->root);
+}
+
+static struct song *
+simple_db_get_song(struct db *_db, const char *uri, GError **error_r)
+{
+ struct simple_db *db = (struct simple_db *)_db;
+
+ assert(db->root != NULL);
+
+ db_lock();
+ struct song *song = directory_lookup_song(db->root, uri);
+ db_unlock();
+ if (song == NULL)
+ g_set_error(error_r, db_quark(), DB_NOT_FOUND,
+ "No such song: %s", uri);
+
+ return song;
+}
+
+static bool
+simple_db_visit(struct db *_db, const struct db_selection *selection,
+ const struct db_visitor *visitor, void *ctx,
+ GError **error_r)
+{
+ const struct simple_db *db = (const struct simple_db *)_db;
+ const struct directory *directory =
+ simple_db_lookup_directory(db, selection->uri);
+ if (directory == NULL) {
+ struct song *song;
+ if (visitor->song != NULL &&
+ (song = simple_db_get_song(_db, selection->uri, NULL)) != NULL)
+ return visitor->song(song, ctx, error_r);
+
+ g_set_error(error_r, db_quark(), DB_NOT_FOUND,
+ "No such directory");
+ return false;
+ }
+
+ if (selection->recursive && visitor->directory != NULL &&
+ !visitor->directory(directory, ctx, error_r))
+ return false;
+
+ db_lock();
+ bool ret = directory_walk(directory, selection->recursive,
+ visitor, ctx, error_r);
+ db_unlock();
+ return ret;
+}
+
+const struct db_plugin simple_db_plugin = {
+ .name = "simple",
+ .init = simple_db_init,
+ .finish = simple_db_finish,
+ .open = simple_db_open,
+ .close = simple_db_close,
+ .get_song = simple_db_get_song,
+ .visit = simple_db_visit,
+};
+
+struct directory *
+simple_db_get_root(struct db *_db)
+{
+ struct simple_db *db = (struct simple_db *)_db;
+
+ assert(db != NULL);
+ assert(db->root != NULL);
+
+ return db->root;
+}
+
+bool
+simple_db_save(struct db *_db, GError **error_r)
+{
+ struct simple_db *db = (struct simple_db *)_db;
+ struct directory *music_root = db->root;
+
+ db_lock();
+
+ g_debug("removing empty directories from DB");
+ directory_prune_empty(music_root);
+
+ g_debug("sorting DB");
+ directory_sort(music_root);
+
+ db_unlock();
+
+ g_debug("writing DB");
+
+ FILE *fp = fopen(db->path, "w");
+ if (!fp) {
+ g_set_error(error_r, simple_db_quark(), errno,
+ "unable to write to db file \"%s\": %s",
+ db->path, g_strerror(errno));
+ return false;
+ }
+
+ db_save_internal(fp, music_root);
+
+ if (ferror(fp)) {
+ g_set_error(error_r, simple_db_quark(), errno,
+ "Failed to write to database file: %s",
+ g_strerror(errno));
+ fclose(fp);
+ return false;
+ }
+
+ fclose(fp);
+
+ struct stat st;
+ if (stat(db->path, &st) == 0)
+ db->mtime = st.st_mtime;
+
+ return true;
+}
+
+time_t
+simple_db_get_mtime(const struct db *_db)
+{
+ const struct simple_db *db = (const struct simple_db *)_db;
+
+ assert(db != NULL);
+ assert(db->root != NULL);
+
+ return db->mtime;
+}
diff --git a/src/db/simple_db_plugin.h b/src/db/simple_db_plugin.h
new file mode 100644
index 000000000..511505846
--- /dev/null
+++ b/src/db/simple_db_plugin.h
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_SIMPLE_DB_PLUGIN_H
+#define MPD_SIMPLE_DB_PLUGIN_H
+
+#include <glib.h>
+#include <stdbool.h>
+#include <time.h>
+
+extern const struct db_plugin simple_db_plugin;
+
+struct db;
+
+G_GNUC_PURE
+struct directory *
+simple_db_get_root(struct db *db);
+
+bool
+simple_db_save(struct db *db, GError **error_r);
+
+G_GNUC_PURE
+time_t
+simple_db_get_mtime(const struct db *db);
+
+#endif
diff --git a/src/dbUtils.c b/src/dbUtils.c
index f950d42cc..827d0a0c1 100644
--- a/src/dbUtils.c
+++ b/src/dbUtils.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -20,311 +20,105 @@
#include "config.h"
#include "dbUtils.h"
#include "locate.h"
-#include "directory.h"
#include "database.h"
-#include "client.h"
+#include "db_visitor.h"
#include "playlist.h"
-#include "song.h"
-#include "song_print.h"
-#include "tag.h"
-#include "strset.h"
#include "stored_playlist.h"
#include <glib.h>
-#include <stdlib.h>
-
-typedef struct _ListCommandItem {
- int8_t tagType;
- const struct locate_item_list *criteria;
-} ListCommandItem;
-
-typedef struct _SearchStats {
- const struct locate_item_list *criteria;
- int numberOfSongs;
- unsigned long playTime;
-} SearchStats;
-
-static int
-printDirectoryInDirectory(struct directory *directory, void *data)
-{
- struct client *client = data;
-
- if (!directory_is_root(directory))
- client_printf(client, "directory: %s\n", directory_get_path(directory));
-
- return 0;
-}
-
-static int
-printSongInDirectory(struct song *song, G_GNUC_UNUSED void *data)
-{
- struct client *client = data;
- song_print_uri(client, song);
- return 0;
-}
-
-struct search_data {
- struct client *client;
- const struct locate_item_list *criteria;
-};
-
-static int
-searchInDirectory(struct song *song, void *_data)
-{
- struct search_data *data = _data;
-
- if (locate_song_search(song, data->criteria))
- song_print_info(data->client, song);
-
- return 0;
-}
-
-int
-searchForSongsIn(struct client *client, const char *name,
- const struct locate_item_list *criteria)
-{
- int ret;
- struct locate_item_list *new_list
- = locate_item_list_casefold(criteria);
- struct search_data data;
-
- data.client = client;
- data.criteria = new_list;
-
- ret = db_walk(name, searchInDirectory, NULL, &data);
-
- locate_item_list_free(new_list);
-
- return ret;
-}
-
-static int
-findInDirectory(struct song *song, void *_data)
+static bool
+add_to_queue_song(struct song *song, void *ctx, GError **error_r)
{
- struct search_data *data = _data;
-
- if (locate_song_match(song, data->criteria))
- song_print_info(data->client, song);
+ struct player_control *pc = ctx;
- return 0;
-}
-
-int
-findSongsIn(struct client *client, const char *name,
- const struct locate_item_list *criteria)
-{
- struct search_data data;
-
- data.client = client;
- data.criteria = criteria;
-
- return db_walk(name, findInDirectory, NULL, &data);
-}
-
-static void printSearchStats(struct client *client, SearchStats *stats)
-{
- client_printf(client, "songs: %i\n", stats->numberOfSongs);
- client_printf(client, "playtime: %li\n", stats->playTime);
-}
-
-static int
-searchStatsInDirectory(struct song *song, void *data)
-{
- SearchStats *stats = data;
-
- if (locate_song_match(song, stats->criteria)) {
- stats->numberOfSongs++;
- stats->playTime += song_get_duration(song);
+ enum playlist_result result =
+ playlist_append_song(&g_playlist, pc, song, NULL);
+ if (result != PLAYLIST_RESULT_SUCCESS) {
+ g_set_error(error_r, playlist_quark(), result,
+ "Playlist error");
+ return false;
}
- return 0;
-}
-
-int
-searchStatsForSongsIn(struct client *client, const char *name,
- const struct locate_item_list *criteria)
-{
- SearchStats stats;
- int ret;
-
- stats.criteria = criteria;
- stats.numberOfSongs = 0;
- stats.playTime = 0;
-
- ret = db_walk(name, searchStatsInDirectory, NULL, &stats);
- if (ret == 0)
- printSearchStats(client, &stats);
-
- return ret;
+ return true;
}
-int printAllIn(struct client *client, const char *name)
-{
- return db_walk(name, printSongInDirectory,
- printDirectoryInDirectory, client);
-}
+static const struct db_visitor add_to_queue_visitor = {
+ .song = add_to_queue_song,
+};
-static int
-directoryAddSongToPlaylist(struct song *song, G_GNUC_UNUSED void *data)
+bool
+addAllIn(struct player_control *pc, const char *uri, GError **error_r)
{
- return playlist_append_song(&g_playlist, song, NULL);
+ return db_walk(uri, &add_to_queue_visitor, pc, error_r);
}
struct add_data {
const char *path;
};
-static int
-directoryAddSongToStoredPlaylist(struct song *song, void *_data)
+static bool
+add_to_spl_song(struct song *song, void *ctx, GError **error_r)
{
- struct add_data *data = _data;
+ struct add_data *data = ctx;
- if (spl_append_song(data->path, song) != 0)
- return -1;
- return 0;
-}
+ if (!spl_append_song(data->path, song, error_r))
+ return false;
-int addAllIn(const char *name)
-{
- return db_walk(name, directoryAddSongToPlaylist, NULL, NULL);
+ return true;
}
-int addAllInToStoredPlaylist(const char *name, const char *utf8file)
+static const struct db_visitor add_to_spl_visitor = {
+ .song = add_to_spl_song,
+};
+
+bool
+addAllInToStoredPlaylist(const char *uri_utf8, const char *path_utf8,
+ GError **error_r)
{
struct add_data data = {
- .path = utf8file,
+ .path = path_utf8,
};
- return db_walk(name, directoryAddSongToStoredPlaylist, NULL, &data);
+ return db_walk(uri_utf8, &add_to_spl_visitor, &data, error_r);
}
-static int
-findAddInDirectory(struct song *song, void *_data)
-{
- struct search_data *data = _data;
-
- if (locate_song_match(song, data->criteria))
- return directoryAddSongToPlaylist(song, data);
-
- return 0;
-}
-
-int findAddIn(struct client *client, const char *name,
- const struct locate_item_list *criteria)
-{
- struct search_data data;
-
- data.client = client;
- data.criteria = criteria;
-
- return db_walk(name, findAddInDirectory, NULL, &data);
-}
-
-static int
-directoryPrintSongInfo(struct song *song, void *data)
-{
- struct client *client = data;
- song_print_info(client, song);
- return 0;
-}
-
-int printInfoForAllIn(struct client *client, const char *name)
-{
- return db_walk(name, directoryPrintSongInfo,
- printDirectoryInDirectory, client);
-}
-
-static ListCommandItem *
-newListCommandItem(int tagType, const struct locate_item_list *criteria)
-{
- ListCommandItem *item = g_new(ListCommandItem, 1);
-
- item->tagType = tagType;
- item->criteria = criteria;
-
- return item;
-}
-
-static void freeListCommandItem(ListCommandItem * item)
-{
- g_free(item);
-}
+struct find_add_data {
+ struct player_control *pc;
+ const struct locate_item_list *criteria;
+};
-static void
-visitTag(struct client *client, struct strset *set,
- struct song *song, enum tag_type tagType)
+static bool
+find_add_song(struct song *song, void *ctx, GError **error_r)
{
- struct tag *tag = song->tag;
- bool found = false;
+ struct find_add_data *data = ctx;
- if (tagType == LOCATE_TAG_FILE_TYPE) {
- song_print_uri(client, song);
- return;
- }
-
- if (!tag)
- return;
+ if (!locate_song_match(song, data->criteria))
+ return true;
- for (unsigned i = 0; i < tag->num_items; i++) {
- if (tag->items[i]->type == tagType) {
- strset_add(set, tag->items[i]->value);
- found = true;
- }
+ enum playlist_result result =
+ playlist_append_song(&g_playlist, data->pc,
+ song, NULL);
+ if (result != PLAYLIST_RESULT_SUCCESS) {
+ g_set_error(error_r, playlist_quark(), result,
+ "Playlist error");
+ return false;
}
- if (!found)
- strset_add(set, "");
+ return true;
}
-struct list_tags_data {
- struct client *client;
- ListCommandItem *item;
- struct strset *set;
+static const struct db_visitor find_add_visitor = {
+ .song = find_add_song,
};
-static int
-listUniqueTagsInDirectory(struct song *song, void *_data)
-{
- struct list_tags_data *data = _data;
- ListCommandItem *item = data->item;
-
- if (locate_song_match(song, item->criteria))
- visitTag(data->client, data->set, song, item->tagType);
-
- return 0;
-}
-
-int listAllUniqueTags(struct client *client, int type,
- const struct locate_item_list *criteria)
+bool
+findAddIn(struct player_control *pc, const char *name,
+ const struct locate_item_list *criteria, GError **error_r)
{
- int ret;
- ListCommandItem *item = newListCommandItem(type, criteria);
- struct list_tags_data data = {
- .client = client,
- .item = item,
- };
-
- if (type >= 0 && type <= TAG_NUM_OF_ITEM_TYPES) {
- data.set = strset_new();
- }
-
- ret = db_walk(NULL, listUniqueTagsInDirectory, NULL, &data);
-
- if (type >= 0 && type <= TAG_NUM_OF_ITEM_TYPES) {
- const char *value;
-
- strset_rewind(data.set);
-
- while ((value = strset_next(data.set)) != NULL)
- client_printf(client, "%s: %s\n",
- tag_item_names[type],
- value);
-
- strset_free(data.set);
- }
-
- freeListCommandItem(item);
+ struct find_add_data data;
+ data.pc = pc;
+ data.criteria = criteria;
- return ret;
+ return db_walk(name, &find_add_visitor, &data, error_r);
}
diff --git a/src/dbUtils.h b/src/dbUtils.h
index bba253154..40594652b 100644
--- a/src/dbUtils.h
+++ b/src/dbUtils.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -20,37 +20,26 @@
#ifndef MPD_DB_UTILS_H
#define MPD_DB_UTILS_H
-struct client;
-struct locate_item_list;
-
-int printAllIn(struct client *client, const char *name);
-
-int addAllIn(const char *name);
-
-int addAllInToStoredPlaylist(const char *name, const char *utf8file);
+#include "gcc.h"
-int printInfoForAllIn(struct client *client, const char *name);
+#include <glib.h>
+#include <stdbool.h>
-int
-searchForSongsIn(struct client *client, const char *name,
- const struct locate_item_list *criteria);
-
-int
-findSongsIn(struct client *client, const char *name,
- const struct locate_item_list *criteria);
-
-int
-findAddIn(struct client *client, const char *name,
- const struct locate_item_list *criteria);
+struct locate_item_list;
+struct player_control;
-int
-searchStatsForSongsIn(struct client *client, const char *name,
- const struct locate_item_list *criteria);
+gcc_nonnull(1,2)
+bool
+addAllIn(struct player_control *pc, const char *uri, GError **error_r);
-unsigned long sumSongTimesIn(const char *name);
+gcc_nonnull(1,2)
+bool
+addAllInToStoredPlaylist(const char *uri_utf8, const char *path_utf8,
+ GError **error_r);
-int
-listAllUniqueTags(struct client *client, int type,
- const struct locate_item_list *criteria);
+gcc_nonnull(1,2,3)
+bool
+findAddIn(struct player_control *pc, const char *name,
+ const struct locate_item_list *criteria, GError **error_r);
#endif
diff --git a/src/db_error.h b/src/db_error.h
new file mode 100644
index 000000000..d3be582cf
--- /dev/null
+++ b/src/db_error.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_DB_ERROR_H
+#define MPD_DB_ERROR_H
+
+#include <glib.h>
+
+enum db_error {
+ /**
+ * The database is disabled, i.e. none is configured in this
+ * MPD instance.
+ */
+ DB_DISABLED,
+
+ DB_NOT_FOUND,
+};
+
+/**
+ * Quark for GError.domain; the code is an enum #db_error.
+ */
+G_GNUC_CONST
+static inline GQuark
+db_quark(void)
+{
+ return g_quark_from_static_string("db");
+}
+
+#endif
diff --git a/src/db_internal.h b/src/db_internal.h
new file mode 100644
index 000000000..a33351524
--- /dev/null
+++ b/src/db_internal.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_DB_INTERNAL_H
+#define MPD_DB_INTERNAL_H
+
+#include "db_plugin.h"
+
+#include <assert.h>
+
+static inline void
+db_base_init(struct db *db, const struct db_plugin *plugin)
+{
+ assert(plugin != NULL);
+
+ db->plugin = plugin;
+}
+
+#endif
diff --git a/src/db_lock.c b/src/db_lock.c
new file mode 100644
index 000000000..53759673d
--- /dev/null
+++ b/src/db_lock.c
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "db_lock.h"
+#include "gcc.h"
+
+#if GCC_CHECK_VERSION(4, 2)
+/* workaround for a warning caused by G_STATIC_MUTEX_INIT */
+#pragma GCC diagnostic ignored "-Wmissing-field-initializers"
+#endif
+
+GStaticMutex db_mutex = G_STATIC_MUTEX_INIT;
+
+#ifndef NDEBUG
+GThread *db_mutex_holder;
+#endif
diff --git a/src/db_lock.h b/src/db_lock.h
new file mode 100644
index 000000000..4640502f3
--- /dev/null
+++ b/src/db_lock.h
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/** \file
+ *
+ * Support for locking data structures from the database, for safe
+ * multi-threading.
+ */
+
+#ifndef MPD_DB_LOCK_H
+#define MPD_DB_LOCK_H
+
+#include "check.h"
+
+#include <glib.h>
+#include <assert.h>
+#include <stdbool.h>
+
+extern GStaticMutex db_mutex;
+
+#ifndef NDEBUG
+
+extern GThread *db_mutex_holder;
+
+/**
+ * Does the current thread hold the database lock?
+ */
+G_GNUC_PURE
+static inline bool
+holding_db_lock(void)
+{
+ return db_mutex_holder == g_thread_self();
+}
+
+#endif
+
+/**
+ * Obtain the global database lock. This is needed before
+ * dereferencing a #song or #directory. It is not recursive.
+ */
+static inline void
+db_lock(void)
+{
+ assert(!holding_db_lock());
+
+ g_static_mutex_lock(&db_mutex);
+
+ assert(db_mutex_holder == NULL);
+#ifndef NDEBUG
+ db_mutex_holder = g_thread_self();
+#endif
+}
+
+/**
+ * Release the global database lock.
+ */
+static inline void
+db_unlock(void)
+{
+ assert(holding_db_lock());
+#ifndef NDEBUG
+ db_mutex_holder = NULL;
+#endif
+
+ g_static_mutex_unlock(&db_mutex);
+}
+
+#endif
diff --git a/src/db_plugin.h b/src/db_plugin.h
new file mode 100644
index 000000000..1c7e14ede
--- /dev/null
+++ b/src/db_plugin.h
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/** \file
+ *
+ * This header declares the db_plugin class. It describes a
+ * plugin API for databases of song metadata.
+ */
+
+#ifndef MPD_DB_PLUGIN_H
+#define MPD_DB_PLUGIN_H
+
+#include <glib.h>
+#include <assert.h>
+#include <stdbool.h>
+
+struct config_param;
+struct db_selection;
+struct db_visitor;
+
+struct db {
+ const struct db_plugin *plugin;
+};
+
+struct db_plugin {
+ const char *name;
+
+ /**
+ * Allocates and configures a database.
+ */
+ struct db *(*init)(const struct config_param *param, GError **error_r);
+
+ /**
+ * Free instance data.
+ */
+ void (*finish)(struct db *db);
+
+ /**
+ * Open the database. Read it into memory if applicable.
+ */
+ bool (*open)(struct db *db, GError **error_r);
+
+ /**
+ * Close the database, free allocated memory.
+ */
+ void (*close)(struct db *db);
+
+ /**
+ * Look up a song (including tag data) in the database.
+ *
+ * @param the URI of the song within the music directory
+ * (UTF-8)
+ */
+ struct song *(*get_song)(struct db *db, const char *uri,
+ GError **error_r);
+
+ /**
+ * Visit the selected entities.
+ */
+ bool (*visit)(struct db *db, const struct db_selection *selection,
+ const struct db_visitor *visitor, void *ctx,
+ GError **error_r);
+};
+
+G_GNUC_MALLOC
+static inline struct db *
+db_plugin_new(const struct db_plugin *plugin, const struct config_param *param,
+ GError **error_r)
+{
+ assert(plugin != NULL);
+ assert(plugin->init != NULL);
+ assert(plugin->finish != NULL);
+ assert(plugin->get_song != NULL);
+ assert(plugin->visit != NULL);
+ assert(error_r == NULL || *error_r == NULL);
+
+ struct db *db = plugin->init(param, error_r);
+ assert(db == NULL || db->plugin == plugin);
+ assert(db != NULL || error_r == NULL || *error_r != NULL);
+
+ return db;
+}
+
+static inline void
+db_plugin_free(struct db *db)
+{
+ assert(db != NULL);
+ assert(db->plugin != NULL);
+ assert(db->plugin->finish != NULL);
+
+ db->plugin->finish(db);
+}
+
+static inline bool
+db_plugin_open(struct db *db, GError **error_r)
+{
+ assert(db != NULL);
+ assert(db->plugin != NULL);
+
+ return db->plugin->open != NULL
+ ? db->plugin->open(db, error_r)
+ : true;
+}
+
+static inline void
+db_plugin_close(struct db *db)
+{
+ assert(db != NULL);
+ assert(db->plugin != NULL);
+
+ if (db->plugin->close != NULL)
+ db->plugin->close(db);
+}
+
+static inline struct song *
+db_plugin_get_song(struct db *db, const char *uri, GError **error_r)
+{
+ assert(db != NULL);
+ assert(db->plugin != NULL);
+ assert(db->plugin->get_song != NULL);
+ assert(uri != NULL);
+
+ return db->plugin->get_song(db, uri, error_r);
+}
+
+static inline bool
+db_plugin_visit(struct db *db, const struct db_selection *selection,
+ const struct db_visitor *visitor, void *ctx,
+ GError **error_r)
+{
+ assert(db != NULL);
+ assert(db->plugin != NULL);
+ assert(selection != NULL);
+ assert(visitor != NULL);
+ assert(error_r == NULL || *error_r == NULL);
+
+ return db->plugin->visit(db, selection, visitor, ctx, error_r);
+}
+
+#endif
diff --git a/src/db_print.c b/src/db_print.c
new file mode 100644
index 000000000..4d7e3f5ef
--- /dev/null
+++ b/src/db_print.c
@@ -0,0 +1,393 @@
+/*
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "db_print.h"
+#include "db_selection.h"
+#include "db_visitor.h"
+#include "locate.h"
+#include "directory.h"
+#include "database.h"
+#include "client.h"
+#include "song.h"
+#include "song_print.h"
+#include "playlist_vector.h"
+#include "tag.h"
+#include "strset.h"
+
+#include <glib.h>
+
+typedef struct _ListCommandItem {
+ int8_t tagType;
+ const struct locate_item_list *criteria;
+} ListCommandItem;
+
+typedef struct _SearchStats {
+ const struct locate_item_list *criteria;
+ int numberOfSongs;
+ unsigned long playTime;
+} SearchStats;
+
+static bool
+print_visitor_directory(const struct directory *directory, void *data,
+ G_GNUC_UNUSED GError **error_r)
+{
+ struct client *client = data;
+
+ if (!directory_is_root(directory))
+ client_printf(client, "directory: %s\n", directory_get_path(directory));
+
+ return true;
+}
+
+static void
+print_playlist_in_directory(struct client *client,
+ const struct directory *directory,
+ const char *name_utf8)
+{
+ if (directory_is_root(directory))
+ client_printf(client, "playlist: %s\n", name_utf8);
+ else
+ client_printf(client, "playlist: %s/%s\n",
+ directory_get_path(directory), name_utf8);
+}
+
+static bool
+print_visitor_song(struct song *song, void *data,
+ G_GNUC_UNUSED GError **error_r)
+{
+ assert(song != NULL);
+ assert(song->parent != NULL);
+
+ struct client *client = data;
+ song_print_uri(client, song);
+
+ if (song->tag != NULL && song->tag->has_playlist)
+ /* this song file has an embedded CUE sheet */
+ print_playlist_in_directory(client, song->parent,
+ song->uri);
+
+ return true;
+}
+
+static bool
+print_visitor_song_info(struct song *song, void *data,
+ G_GNUC_UNUSED GError **error_r)
+{
+ assert(song != NULL);
+ assert(song->parent != NULL);
+
+ struct client *client = data;
+ song_print_info(client, song);
+
+ if (song->tag != NULL && song->tag->has_playlist)
+ /* this song file has an embedded CUE sheet */
+ print_playlist_in_directory(client, song->parent,
+ song->uri);
+
+ return true;
+}
+
+static bool
+print_visitor_playlist(const struct playlist_metadata *playlist,
+ const struct directory *directory, void *ctx,
+ G_GNUC_UNUSED GError **error_r)
+{
+ struct client *client = ctx;
+ print_playlist_in_directory(client, directory, playlist->name);
+ return true;
+}
+
+static bool
+print_visitor_playlist_info(const struct playlist_metadata *playlist,
+ const struct directory *directory,
+ void *ctx, G_GNUC_UNUSED GError **error_r)
+{
+ struct client *client = ctx;
+ print_playlist_in_directory(client, directory, playlist->name);
+
+#ifndef G_OS_WIN32
+ struct tm tm;
+#endif
+ char timestamp[32];
+ time_t t = playlist->mtime;
+ strftime(timestamp, sizeof(timestamp),
+#ifdef G_OS_WIN32
+ "%Y-%m-%dT%H:%M:%SZ",
+ gmtime(&t)
+#else
+ "%FT%TZ",
+ gmtime_r(&t, &tm)
+#endif
+ );
+ client_printf(client, "Last-Modified: %s\n", timestamp);
+
+ return true;
+}
+
+static const struct db_visitor print_visitor = {
+ .directory = print_visitor_directory,
+ .song = print_visitor_song,
+ .playlist = print_visitor_playlist,
+};
+
+static const struct db_visitor print_info_visitor = {
+ .directory = print_visitor_directory,
+ .song = print_visitor_song_info,
+ .playlist = print_visitor_playlist_info,
+};
+
+bool
+db_selection_print(struct client *client, const struct db_selection *selection,
+ bool full, GError **error_r)
+{
+ return db_visit(selection, full ? &print_info_visitor : &print_visitor,
+ client, error_r);
+}
+
+struct search_data {
+ struct client *client;
+ const struct locate_item_list *criteria;
+};
+
+static bool
+search_visitor_song(struct song *song, void *_data,
+ G_GNUC_UNUSED GError **error_r)
+{
+ struct search_data *data = _data;
+
+ if (locate_song_search(song, data->criteria))
+ song_print_info(data->client, song);
+
+ return true;
+}
+
+static const struct db_visitor search_visitor = {
+ .song = search_visitor_song,
+};
+
+bool
+searchForSongsIn(struct client *client, const char *name,
+ const struct locate_item_list *criteria,
+ GError **error_r)
+{
+ struct locate_item_list *new_list
+ = locate_item_list_casefold(criteria);
+ struct search_data data;
+
+ data.client = client;
+ data.criteria = new_list;
+
+ bool success = db_walk(name, &search_visitor, &data, error_r);
+
+ locate_item_list_free(new_list);
+
+ return success;
+}
+
+static bool
+find_visitor_song(struct song *song, void *_data,
+ G_GNUC_UNUSED GError **error_r)
+{
+ struct search_data *data = _data;
+
+ if (locate_song_match(song, data->criteria))
+ song_print_info(data->client, song);
+
+ return true;
+}
+
+static const struct db_visitor find_visitor = {
+ .song = find_visitor_song,
+};
+
+bool
+findSongsIn(struct client *client, const char *name,
+ const struct locate_item_list *criteria,
+ GError **error_r)
+{
+ struct search_data data;
+
+ data.client = client;
+ data.criteria = criteria;
+
+ return db_walk(name, &find_visitor, &data, error_r);
+}
+
+static void printSearchStats(struct client *client, SearchStats *stats)
+{
+ client_printf(client, "songs: %i\n", stats->numberOfSongs);
+ client_printf(client, "playtime: %li\n", stats->playTime);
+}
+
+static bool
+stats_visitor_song(struct song *song, void *data,
+ G_GNUC_UNUSED GError **error_r)
+{
+ SearchStats *stats = data;
+
+ if (locate_song_match(song, stats->criteria)) {
+ stats->numberOfSongs++;
+ stats->playTime += song_get_duration(song);
+ }
+
+ return true;
+}
+
+static const struct db_visitor stats_visitor = {
+ .song = stats_visitor_song,
+};
+
+bool
+searchStatsForSongsIn(struct client *client, const char *name,
+ const struct locate_item_list *criteria,
+ GError **error_r)
+{
+ SearchStats stats;
+
+ stats.criteria = criteria;
+ stats.numberOfSongs = 0;
+ stats.playTime = 0;
+
+ if (!db_walk(name, &stats_visitor, &stats, error_r))
+ return false;
+
+ printSearchStats(client, &stats);
+ return true;
+}
+
+bool
+printAllIn(struct client *client, const char *uri_utf8, GError **error_r)
+{
+ struct db_selection selection;
+ db_selection_init(&selection, uri_utf8, true);
+ return db_selection_print(client, &selection, false, error_r);
+}
+
+bool
+printInfoForAllIn(struct client *client, const char *uri_utf8,
+ GError **error_r)
+{
+ struct db_selection selection;
+ db_selection_init(&selection, uri_utf8, true);
+ return db_selection_print(client, &selection, true, error_r);
+}
+
+static ListCommandItem *
+newListCommandItem(int tagType, const struct locate_item_list *criteria)
+{
+ ListCommandItem *item = g_new(ListCommandItem, 1);
+
+ item->tagType = tagType;
+ item->criteria = criteria;
+
+ return item;
+}
+
+static void freeListCommandItem(ListCommandItem * item)
+{
+ g_free(item);
+}
+
+static void
+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_uri(client, song);
+ return;
+ }
+
+ if (!tag)
+ return;
+
+ for (unsigned i = 0; i < tag->num_items; i++) {
+ if (tag->items[i]->type == tagType) {
+ strset_add(set, tag->items[i]->value);
+ found = true;
+ }
+ }
+
+ if (!found)
+ strset_add(set, "");
+}
+
+struct list_tags_data {
+ struct client *client;
+ ListCommandItem *item;
+ struct strset *set;
+};
+
+static bool
+unique_tags_visitor_song(struct song *song, void *_data,
+ G_GNUC_UNUSED GError **error_r)
+{
+ struct list_tags_data *data = _data;
+ ListCommandItem *item = data->item;
+
+ if (locate_song_match(song, item->criteria))
+ visitTag(data->client, data->set, song, item->tagType);
+
+ return true;
+}
+
+static const struct db_visitor unique_tags_visitor = {
+ .song = unique_tags_visitor_song,
+};
+
+bool
+listAllUniqueTags(struct client *client, int type,
+ const struct locate_item_list *criteria,
+ GError **error_r)
+{
+ ListCommandItem *item = newListCommandItem(type, criteria);
+ struct list_tags_data data = {
+ .client = client,
+ .item = item,
+ };
+
+ if (type >= 0 && type <= TAG_NUM_OF_ITEM_TYPES) {
+ data.set = strset_new();
+ }
+
+ if (!db_walk("", &unique_tags_visitor, &data, error_r)) {
+ freeListCommandItem(item);
+ return false;
+ }
+
+ if (type >= 0 && type <= TAG_NUM_OF_ITEM_TYPES) {
+ const char *value;
+
+ strset_rewind(data.set);
+
+ while ((value = strset_next(data.set)) != NULL)
+ client_printf(client, "%s: %s\n",
+ tag_item_names[type],
+ value);
+
+ strset_free(data.set);
+ }
+
+ freeListCommandItem(item);
+
+ return true;
+}
diff --git a/src/db_print.h b/src/db_print.h
new file mode 100644
index 000000000..1b957da18
--- /dev/null
+++ b/src/db_print.h
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_DB_PRINT_H
+#define MPD_DB_PRINT_H
+
+#include "gcc.h"
+
+#include <glib.h>
+#include <stdbool.h>
+
+struct client;
+struct locate_item_list;
+struct db_selection;
+struct db_visitor;
+
+gcc_nonnull(1,2)
+bool
+db_selection_print(struct client *client, const struct db_selection *selection,
+ bool full, GError **error_r);
+
+gcc_nonnull(1,2)
+bool
+printAllIn(struct client *client, const char *uri_utf8, GError **error_r);
+
+gcc_nonnull(1,2)
+bool
+printInfoForAllIn(struct client *client, const char *uri_utf8,
+ GError **error_r);
+
+gcc_nonnull(1,2,3)
+bool
+searchForSongsIn(struct client *client, const char *name,
+ const struct locate_item_list *criteria,
+ GError **error_r);
+
+gcc_nonnull(1,2,3)
+bool
+findSongsIn(struct client *client, const char *name,
+ const struct locate_item_list *criteria,
+ GError **error_r);
+
+gcc_nonnull(1,2,3)
+bool
+searchStatsForSongsIn(struct client *client, const char *name,
+ const struct locate_item_list *criteria,
+ GError **error_r);
+
+gcc_nonnull(1,3)
+bool
+listAllUniqueTags(struct client *client, int type,
+ const struct locate_item_list *criteria,
+ GError **error_r);
+
+#endif
diff --git a/src/db_save.c b/src/db_save.c
new file mode 100644
index 000000000..4af9d58b8
--- /dev/null
+++ b/src/db_save.c
@@ -0,0 +1,179 @@
+/*
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "db_save.h"
+#include "db_lock.h"
+#include "directory.h"
+#include "directory_save.h"
+#include "song.h"
+#include "path.h"
+#include "text_file.h"
+#include "tag.h"
+#include "tag_internal.h"
+
+#include <glib.h>
+
+#include <assert.h>
+#include <stdlib.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "database"
+
+#define DIRECTORY_INFO_BEGIN "info_begin"
+#define DIRECTORY_INFO_END "info_end"
+#define DB_FORMAT_PREFIX "format: "
+#define DIRECTORY_MPD_VERSION "mpd_version: "
+#define DIRECTORY_FS_CHARSET "fs_charset: "
+#define DB_TAG_PREFIX "tag: "
+
+enum {
+ DB_FORMAT = 1,
+};
+
+G_GNUC_CONST
+static GQuark
+db_quark(void)
+{
+ return g_quark_from_static_string("database");
+}
+
+void
+db_save_internal(FILE *fp, const struct directory *music_root)
+{
+ assert(music_root != NULL);
+
+ fprintf(fp, "%s\n", DIRECTORY_INFO_BEGIN);
+ fprintf(fp, DB_FORMAT_PREFIX "%u\n", DB_FORMAT);
+ fprintf(fp, "%s%s\n", DIRECTORY_MPD_VERSION, VERSION);
+ fprintf(fp, "%s%s\n", DIRECTORY_FS_CHARSET, path_get_fs_charset());
+
+ for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i)
+ if (!ignore_tag_items[i])
+ fprintf(fp, DB_TAG_PREFIX "%s\n", tag_item_names[i]);
+
+ fprintf(fp, "%s\n", DIRECTORY_INFO_END);
+
+ directory_save(fp, music_root);
+}
+
+bool
+db_load_internal(FILE *fp, struct directory *music_root, GError **error)
+{
+ GString *buffer = g_string_sized_new(1024);
+ char *line;
+ int format = 0;
+ bool found_charset = false, found_version = false;
+ bool success;
+ bool tags[TAG_NUM_OF_ITEM_TYPES];
+
+ assert(music_root != NULL);
+
+ /* get initial info */
+ line = read_text_line(fp, buffer);
+ if (line == NULL || strcmp(DIRECTORY_INFO_BEGIN, line) != 0) {
+ g_set_error(error, db_quark(), 0, "Database corrupted");
+ g_string_free(buffer, true);
+ return false;
+ }
+
+ memset(tags, false, sizeof(tags));
+
+ while ((line = read_text_line(fp, buffer)) != NULL &&
+ strcmp(line, DIRECTORY_INFO_END) != 0) {
+ if (g_str_has_prefix(line, DB_FORMAT_PREFIX)) {
+ format = atoi(line + sizeof(DB_FORMAT_PREFIX) - 1);
+ } else if (g_str_has_prefix(line, DIRECTORY_MPD_VERSION)) {
+ if (found_version) {
+ g_set_error(error, db_quark(), 0,
+ "Duplicate version line");
+ g_string_free(buffer, true);
+ return false;
+ }
+
+ found_version = true;
+ } else if (g_str_has_prefix(line, DIRECTORY_FS_CHARSET)) {
+ const char *new_charset, *old_charset;
+
+ if (found_charset) {
+ g_set_error(error, db_quark(), 0,
+ "Duplicate charset line");
+ g_string_free(buffer, true);
+ return false;
+ }
+
+ found_charset = true;
+
+ new_charset = line + sizeof(DIRECTORY_FS_CHARSET) - 1;
+ old_charset = path_get_fs_charset();
+ if (old_charset != NULL
+ && strcmp(new_charset, old_charset)) {
+ g_set_error(error, db_quark(), 0,
+ "Existing database has charset "
+ "\"%s\" instead of \"%s\"; "
+ "discarding database file",
+ new_charset, old_charset);
+ g_string_free(buffer, true);
+ return false;
+ }
+ } else if (g_str_has_prefix(line, DB_TAG_PREFIX)) {
+ const char *name = line + sizeof(DB_TAG_PREFIX) - 1;
+ enum tag_type tag = tag_name_parse(name);
+ if (tag == TAG_NUM_OF_ITEM_TYPES) {
+ g_set_error(error, db_quark(), 0,
+ "Unrecognized tag '%s', "
+ "discarding database file",
+ name);
+ return false;
+ }
+
+ tags[tag] = true;
+ } else {
+ g_set_error(error, db_quark(), 0,
+ "Malformed line: %s", line);
+ g_string_free(buffer, true);
+ return false;
+ }
+ }
+
+ if (format != DB_FORMAT) {
+ g_set_error(error, db_quark(), 0,
+ "Database format mismatch, "
+ "discarding database file");
+ return false;
+ }
+
+ for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) {
+ if (!ignore_tag_items[i] && !tags[i]) {
+ g_set_error(error, db_quark(), 0,
+ "Tag list mismatch, "
+ "discarding database file");
+ return false;
+ }
+ }
+
+ g_debug("reading DB");
+
+ db_lock();
+ success = directory_load(fp, music_root, buffer, error);
+ db_unlock();
+ g_string_free(buffer, true);
+
+ return success;
+}
diff --git a/src/db_save.h b/src/db_save.h
new file mode 100644
index 000000000..e760ec881
--- /dev/null
+++ b/src/db_save.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_DB_SAVE_H
+#define MPD_DB_SAVE_H
+
+#include <glib.h>
+#include <stdbool.h>
+#include <stdio.h>
+
+struct directory;
+
+void
+db_save_internal(FILE *file, const struct directory *root);
+
+bool
+db_load_internal(FILE *file, struct directory *root, GError **error);
+
+#endif
diff --git a/src/db_selection.h b/src/db_selection.h
new file mode 100644
index 000000000..2cebb4907
--- /dev/null
+++ b/src/db_selection.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_DB_SELECTION_H
+#define MPD_DB_SELECTION_H
+
+#include "gcc.h"
+
+#include <assert.h>
+
+struct directory;
+struct song;
+
+struct db_selection {
+ /**
+ * The base URI of the search (UTF-8). Must not begin or end
+ * with a slash. NULL or an empty string searches the whole
+ * database.
+ */
+ const char *uri;
+
+ /**
+ * Recursively search all sub directories?
+ */
+ bool recursive;
+};
+
+gcc_nonnull(1,2)
+static inline void
+db_selection_init(struct db_selection *selection,
+ const char *uri, bool recursive)
+{
+ assert(selection != NULL);
+ assert(uri != NULL);
+
+ selection->uri = uri;
+ selection->recursive = recursive;
+}
+
+#endif
diff --git a/src/db_visitor.h b/src/db_visitor.h
new file mode 100644
index 000000000..6b90c1868
--- /dev/null
+++ b/src/db_visitor.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_DB_VISITOR_H
+#define MPD_DB_VISITOR_H
+
+struct directory;
+struct song;
+struct playlist_metadata;
+
+struct db_visitor {
+ /**
+ * Visit a directory. Optional method.
+ *
+ * @return true to continue the operation, false on error (set error_r)
+ */
+ bool (*directory)(const struct directory *directory, void *ctx,
+ GError **error_r);
+
+ /**
+ * Visit a song. Optional method.
+ *
+ * @return true to continue the operation, false on error (set error_r)
+ */
+ bool (*song)(struct song *song, void *ctx, GError **error_r);
+
+ /**
+ * Visit a playlist. Optional method.
+ *
+ * @param directory the directory the playlist resides in
+ * @return true to continue the operation, false on error (set error_r)
+ */
+ bool (*playlist)(const struct playlist_metadata *playlist,
+ const struct directory *directory, void *ctx,
+ GError **error_r);
+};
+
+#endif
diff --git a/src/decoder/_flac_common.c b/src/decoder/_flac_common.c
index 8dd22a253..bab3995f0 100644
--- a/src/decoder/_flac_common.c
+++ b/src/decoder/_flac_common.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -139,26 +139,13 @@ void flac_metadata_common_cb(const FLAC__StreamMetadata * block,
}
}
-void flac_error_common_cb(const char *plugin,
- const FLAC__StreamDecoderErrorStatus status,
+void flac_error_common_cb(const FLAC__StreamDecoderErrorStatus status,
struct flac_data *data)
{
if (decoder_get_command(data->decoder) == DECODE_COMMAND_STOP)
return;
- switch (status) {
- case FLAC__STREAM_DECODER_ERROR_STATUS_LOST_SYNC:
- g_warning("%s lost sync\n", plugin);
- break;
- case FLAC__STREAM_DECODER_ERROR_STATUS_BAD_HEADER:
- g_warning("bad %s header\n", plugin);
- break;
- case FLAC__STREAM_DECODER_ERROR_STATUS_FRAME_CRC_MISMATCH:
- g_warning("%s crc mismatch\n", plugin);
- break;
- default:
- g_warning("unknown %s error\n", plugin);
- }
+ g_warning("%s", FLAC__StreamDecoderErrorStatusString[status]);
}
/**
diff --git a/src/decoder/_flac_common.h b/src/decoder/_flac_common.h
index 5c59ee123..0d90ba656 100644
--- a/src/decoder/_flac_common.h
+++ b/src/decoder/_flac_common.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -94,8 +94,7 @@ flac_data_deinit(struct flac_data *data);
void flac_metadata_common_cb(const FLAC__StreamMetadata * block,
struct flac_data *data);
-void flac_error_common_cb(const char *plugin,
- FLAC__StreamDecoderErrorStatus status,
+void flac_error_common_cb(FLAC__StreamDecoderErrorStatus status,
struct flac_data *data);
FLAC__StreamDecoderWriteStatus
diff --git a/src/decoder/_ogg_common.c b/src/decoder/_ogg_common.c
index bd0650ac4..bedd3de61 100644
--- a/src/decoder/_ogg_common.c
+++ b/src/decoder/_ogg_common.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/decoder/_ogg_common.h b/src/decoder/_ogg_common.h
index f8446c69c..85e4ebba6 100644
--- a/src/decoder/_ogg_common.h
+++ b/src/decoder/_ogg_common.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/decoder/audiofile_decoder_plugin.c b/src/decoder/audiofile_decoder_plugin.c
index de236a6e2..b344795e7 100644
--- a/src/decoder/audiofile_decoder_plugin.c
+++ b/src/decoder/audiofile_decoder_plugin.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -20,6 +20,7 @@
#include "config.h"
#include "decoder_api.h"
#include "audio_check.h"
+#include "tag_handler.h"
#include <audiofile.h>
#include <af_vfs.h>
@@ -54,7 +55,7 @@ audiofile_file_read(AFvirtualfile *vfile, void *data, size_t length)
GError *error = NULL;
size_t nbytes;
- nbytes = input_stream_read(is, data, length, &error);
+ nbytes = input_stream_lock_read(is, data, length, &error);
if (nbytes == 0 && error != NULL) {
g_warning("%s", error->message);
g_error_free(error);
@@ -91,7 +92,7 @@ audiofile_file_seek(AFvirtualfile *vfile, AFfileoffset offset, int is_relative)
{
struct input_stream *is = (struct input_stream *) vfile->closure;
int whence = (is_relative ? SEEK_CUR : SEEK_SET);
- if (input_stream_seek(is, offset, whence, NULL)) {
+ if (input_stream_lock_seek(is, offset, whence, NULL)) {
return is->offset;
} else {
return -1;
@@ -222,20 +223,20 @@ audiofile_stream_decode(struct decoder *decoder, struct input_stream *is)
afCloseFile(af_fp);
}
-static struct tag *audiofile_tag_dup(const char *file)
+static bool
+audiofile_scan_file(const char *file,
+ const struct tag_handler *handler, void *handler_ctx)
{
- struct tag *ret = NULL;
int total_time = audiofile_get_duration(file);
- if (total_time >= 0) {
- ret = tag_new();
- ret->time = total_time;
- } else {
+ if (total_time < 0) {
g_debug("Failed to get total song time from: %s\n",
file);
+ return false;
}
- return ret;
+ tag_handler_invoke_duration(handler, handler_ctx, total_time);
+ return true;
}
static const char *const audiofile_suffixes[] = {
@@ -251,7 +252,7 @@ static const char *const audiofile_mime_types[] = {
const struct decoder_plugin audiofile_decoder_plugin = {
.name = "audiofile",
.stream_decode = audiofile_stream_decode,
- .tag_dup = audiofile_tag_dup,
+ .scan_file = audiofile_scan_file,
.suffixes = audiofile_suffixes,
.mime_types = audiofile_mime_types,
};
diff --git a/src/decoder/dsdiff_decoder_plugin.c b/src/decoder/dsdiff_decoder_plugin.c
new file mode 100644
index 000000000..ae42002dd
--- /dev/null
+++ b/src/decoder/dsdiff_decoder_plugin.c
@@ -0,0 +1,458 @@
+/*
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/* \file
+ *
+ * This plugin decodes DSDIFF data (SACD) embedded in DFF files. It
+ * was modeled after the specification found here:
+ * http://www.sonicstudio.com/pdf/dsd/DSDIFF_1.5_Spec.pdf
+ */
+
+#include "config.h"
+#include "dsdiff_decoder_plugin.h"
+#include "decoder_api.h"
+#include "audio_check.h"
+#include "util/bit_reverse.h"
+
+#include <unistd.h>
+#include <stdio.h> /* for SEEK_SET, SEEK_CUR */
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "dsdiff"
+
+struct dsdiff_id {
+ char value[4];
+};
+
+struct dsdiff_header {
+ struct dsdiff_id id;
+ uint32_t size_high, size_low;
+ struct dsdiff_id format;
+};
+
+struct dsdiff_chunk_header {
+ struct dsdiff_id id;
+ uint32_t size_high, size_low;
+};
+
+struct dsdiff_metadata {
+ unsigned sample_rate, channels;
+};
+
+static bool lsbitfirst;
+
+static bool
+dsdiff_init(const struct config_param *param)
+{
+ lsbitfirst = config_get_block_bool(param, "lsbitfirst", false);
+ return true;
+}
+
+static bool
+dsdiff_id_equals(const struct dsdiff_id *id, const char *s)
+{
+ assert(id != NULL);
+ assert(s != NULL);
+ assert(strlen(s) == sizeof(id->value));
+
+ return memcmp(id->value, s, sizeof(id->value)) == 0;
+}
+
+/**
+ * Read the "size" attribute from the specified header, converting it
+ * to the host byte order if needed.
+ */
+G_GNUC_CONST
+static uint64_t
+dsdiff_chunk_size(const struct dsdiff_chunk_header *header)
+{
+ return (((uint64_t)GUINT32_FROM_BE(header->size_high)) << 32) |
+ ((uint64_t)GUINT32_FROM_BE(header->size_low));
+}
+
+static bool
+dsdiff_read(struct decoder *decoder, struct input_stream *is,
+ void *data, size_t length)
+{
+ size_t nbytes = decoder_read(decoder, is, data, length);
+ return nbytes == length;
+}
+
+static bool
+dsdiff_read_id(struct decoder *decoder, struct input_stream *is,
+ struct dsdiff_id *id)
+{
+ return dsdiff_read(decoder, is, id, sizeof(*id));
+}
+
+static bool
+dsdiff_read_chunk_header(struct decoder *decoder, struct input_stream *is,
+ struct dsdiff_chunk_header *header)
+{
+ return dsdiff_read(decoder, is, header, sizeof(*header));
+}
+
+static bool
+dsdiff_read_payload(struct decoder *decoder, struct input_stream *is,
+ const struct dsdiff_chunk_header *header,
+ void *data, size_t length)
+{
+ uint64_t size = dsdiff_chunk_size(header);
+ if (size != (uint64_t)length)
+ return false;
+
+ size_t nbytes = decoder_read(decoder, is, data, length);
+ return nbytes == length;
+}
+
+/**
+ * Skip the #input_stream to the specified offset.
+ */
+static bool
+dsdiff_skip_to(struct decoder *decoder, struct input_stream *is,
+ goffset offset)
+{
+ if (is->seekable)
+ return input_stream_seek(is, offset, SEEK_SET, NULL);
+
+ if (is->offset > offset)
+ return false;
+
+ char buffer[8192];
+ while (is->offset < offset) {
+ size_t length = sizeof(buffer);
+ if (offset - is->offset < (goffset)length)
+ length = offset - is->offset;
+
+ size_t nbytes = decoder_read(decoder, is, buffer, length);
+ if (nbytes == 0)
+ return false;
+ }
+
+ assert(is->offset == offset);
+ return true;
+}
+
+/**
+ * Skip some bytes from the #input_stream.
+ */
+static bool
+dsdiff_skip(struct decoder *decoder, struct input_stream *is,
+ goffset delta)
+{
+ assert(delta >= 0);
+
+ if (delta == 0)
+ return true;
+
+ if (is->seekable)
+ return input_stream_seek(is, delta, SEEK_CUR, NULL);
+
+ char buffer[8192];
+ while (delta > 0) {
+ size_t length = sizeof(buffer);
+ if ((goffset)length > delta)
+ length = delta;
+
+ size_t nbytes = decoder_read(decoder, is, buffer, length);
+ if (nbytes == 0)
+ return false;
+
+ delta -= nbytes;
+ }
+
+ return true;
+}
+
+/**
+ * Read and parse a "SND" chunk inside "PROP".
+ */
+static bool
+dsdiff_read_prop_snd(struct decoder *decoder, struct input_stream *is,
+ struct dsdiff_metadata *metadata,
+ goffset end_offset)
+{
+ struct dsdiff_chunk_header header;
+ while ((goffset)(is->offset + sizeof(header)) <= end_offset) {
+ if (!dsdiff_read_chunk_header(decoder, is, &header))
+ return false;
+
+ goffset chunk_end_offset =
+ is->offset + dsdiff_chunk_size(&header);
+ if (chunk_end_offset > end_offset)
+ return false;
+
+ if (dsdiff_id_equals(&header.id, "FS ")) {
+ uint32_t sample_rate;
+ if (!dsdiff_read_payload(decoder, is, &header,
+ &sample_rate,
+ sizeof(sample_rate)))
+ return false;
+
+ metadata->sample_rate = GUINT32_FROM_BE(sample_rate);
+ } else if (dsdiff_id_equals(&header.id, "CHNL")) {
+ uint16_t channels;
+ if (dsdiff_chunk_size(&header) < sizeof(channels) ||
+ !dsdiff_read(decoder, is,
+ &channels, sizeof(channels)) ||
+ !dsdiff_skip_to(decoder, is, chunk_end_offset))
+ return false;
+
+ metadata->channels = GUINT16_FROM_BE(channels);
+ } else if (dsdiff_id_equals(&header.id, "CMPR")) {
+ struct dsdiff_id type;
+ if (dsdiff_chunk_size(&header) < sizeof(type) ||
+ !dsdiff_read(decoder, is,
+ &type, sizeof(type)) ||
+ !dsdiff_skip_to(decoder, is, chunk_end_offset))
+ return false;
+
+ if (!dsdiff_id_equals(&type, "DSD "))
+ /* only uincompressed DSD audio data
+ is implemented */
+ return false;
+ } else {
+ /* ignore unknown chunk */
+
+ if (!dsdiff_skip_to(decoder, is, chunk_end_offset))
+ return false;
+ }
+ }
+
+ return is->offset == end_offset;
+}
+
+/**
+ * Read and parse a "PROP" chunk.
+ */
+static bool
+dsdiff_read_prop(struct decoder *decoder, struct input_stream *is,
+ struct dsdiff_metadata *metadata,
+ const struct dsdiff_chunk_header *prop_header)
+{
+ uint64_t prop_size = dsdiff_chunk_size(prop_header);
+ goffset end_offset = is->offset + prop_size;
+
+ struct dsdiff_id prop_id;
+ if (prop_size < sizeof(prop_id) ||
+ !dsdiff_read_id(decoder, is, &prop_id))
+ return false;
+
+ if (dsdiff_id_equals(&prop_id, "SND "))
+ return dsdiff_read_prop_snd(decoder, is, metadata, end_offset);
+ else
+ /* ignore unknown PROP chunk */
+ return dsdiff_skip_to(decoder, is, end_offset);
+}
+
+/**
+ * Read and parse all metadata chunks at the beginning. Stop when the
+ * first "DSD" chunk is seen, and return its header in the
+ * "chunk_header" parameter.
+ */
+static bool
+dsdiff_read_metadata(struct decoder *decoder, struct input_stream *is,
+ struct dsdiff_metadata *metadata,
+ struct dsdiff_chunk_header *chunk_header)
+{
+ struct dsdiff_header header;
+ if (!dsdiff_read(decoder, is, &header, sizeof(header)) ||
+ !dsdiff_id_equals(&header.id, "FRM8") ||
+ !dsdiff_id_equals(&header.format, "DSD "))
+ return false;
+
+ while (true) {
+ if (!dsdiff_read_chunk_header(decoder, is, chunk_header))
+ return false;
+
+ if (dsdiff_id_equals(&chunk_header->id, "PROP")) {
+ if (!dsdiff_read_prop(decoder, is, metadata,
+ chunk_header))
+ return false;
+ } else if (dsdiff_id_equals(&chunk_header->id, "DSD ")) {
+ /* done with metadata */
+ return true;
+ } else {
+ /* ignore unknown chunk */
+
+ uint64_t chunk_size = dsdiff_chunk_size(chunk_header);
+ goffset chunk_end_offset = is->offset + chunk_size;
+
+ if (!dsdiff_skip_to(decoder, is, chunk_end_offset))
+ return false;
+ }
+ }
+}
+
+static void
+bit_reverse_buffer(uint8_t *p, uint8_t *end)
+{
+ for (; p < end; ++p)
+ *p = bit_reverse(*p);
+}
+
+/**
+ * Decode one "DSD" chunk.
+ */
+static bool
+dsdiff_decode_chunk(struct decoder *decoder, struct input_stream *is,
+ unsigned channels,
+ uint64_t chunk_size)
+{
+ uint8_t buffer[8192];
+ const size_t sample_size = sizeof(buffer[0]);
+ const size_t frame_size = channels * sample_size;
+ const unsigned buffer_frames = sizeof(buffer) / frame_size;
+ const unsigned buffer_samples = buffer_frames * frame_size;
+ const size_t buffer_size = buffer_samples * sample_size;
+
+ while (chunk_size > 0) {
+ /* see how much aligned data from the remaining chunk
+ fits into the local buffer */
+ unsigned now_frames = buffer_frames;
+ size_t now_size = buffer_size;
+ if (chunk_size < (uint64_t)now_size) {
+ now_frames = (unsigned)chunk_size / frame_size;
+ now_size = now_frames * frame_size;
+ }
+
+ size_t nbytes = decoder_read(decoder, is, buffer, now_size);
+ if (nbytes != now_size)
+ return false;
+
+ chunk_size -= nbytes;
+
+ if (lsbitfirst)
+ bit_reverse_buffer(buffer, buffer + nbytes);
+
+ enum decoder_command cmd =
+ decoder_data(decoder, is, buffer, nbytes, 0);
+ switch (cmd) {
+ case DECODE_COMMAND_NONE:
+ break;
+
+ case DECODE_COMMAND_START:
+ case DECODE_COMMAND_STOP:
+ return false;
+
+ case DECODE_COMMAND_SEEK:
+ /* not implemented yet */
+ decoder_seek_error(decoder);
+ break;
+ }
+ }
+
+ return dsdiff_skip(decoder, is, chunk_size);
+}
+
+static void
+dsdiff_stream_decode(struct decoder *decoder, struct input_stream *is)
+{
+ struct dsdiff_metadata metadata = {
+ .sample_rate = 0,
+ .channels = 0,
+ };
+
+ struct dsdiff_chunk_header chunk_header;
+ if (!dsdiff_read_metadata(decoder, is, &metadata, &chunk_header))
+ return;
+
+ GError *error = NULL;
+ struct audio_format audio_format;
+ if (!audio_format_init_checked(&audio_format, metadata.sample_rate / 8,
+ SAMPLE_FORMAT_DSD,
+ metadata.channels, &error)) {
+ g_warning("%s", error->message);
+ g_error_free(error);
+ return;
+ }
+
+ /* success: file was recognized */
+
+ decoder_initialized(decoder, &audio_format, false, -1);
+
+ /* every iteration of the following loop decodes one "DSD"
+ chunk */
+
+ while (true) {
+ uint64_t chunk_size = dsdiff_chunk_size(&chunk_header);
+
+ if (dsdiff_id_equals(&chunk_header.id, "DSD ")) {
+ if (!dsdiff_decode_chunk(decoder, is,
+ metadata.channels,
+ chunk_size))
+ break;
+ } else {
+ /* ignore other chunks */
+
+ if (!dsdiff_skip(decoder, is, chunk_size))
+ break;
+ }
+
+ /* read next chunk header; the first one was read by
+ dsdiff_read_metadata() */
+
+ if (!dsdiff_read_chunk_header(decoder, is, &chunk_header))
+ break;
+ }
+}
+
+static bool
+dsdiff_scan_stream(struct input_stream *is,
+ G_GNUC_UNUSED const struct tag_handler *handler,
+ G_GNUC_UNUSED void *handler_ctx)
+{
+ struct dsdiff_metadata metadata = {
+ .sample_rate = 0,
+ .channels = 0,
+ };
+
+ struct dsdiff_chunk_header chunk_header;
+ if (!dsdiff_read_metadata(NULL, is, &metadata, &chunk_header))
+ return false;
+
+ struct audio_format audio_format;
+ if (!audio_format_init_checked(&audio_format, metadata.sample_rate / 8,
+ SAMPLE_FORMAT_DSD,
+ metadata.channels, NULL))
+ /* refuse to parse files which we cannot play anyway */
+ return false;
+
+ /* no total time estimate, no tags implemented yet */
+ return true;
+}
+
+static const char *const dsdiff_suffixes[] = {
+ "dff",
+ NULL
+};
+
+static const char *const dsdiff_mime_types[] = {
+ "application/x-dff",
+ NULL
+};
+
+const struct decoder_plugin dsdiff_decoder_plugin = {
+ .name = "dsdiff",
+ .init = dsdiff_init,
+ .stream_decode = dsdiff_stream_decode,
+ .scan_stream = dsdiff_scan_stream,
+ .suffixes = dsdiff_suffixes,
+ .mime_types = dsdiff_mime_types,
+};
diff --git a/src/playlist/flac_playlist_plugin.h b/src/decoder/dsdiff_decoder_plugin.h
index 7b141264f..34e1438de 100644
--- a/src/playlist/flac_playlist_plugin.h
+++ b/src/decoder/dsdiff_decoder_plugin.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -17,9 +17,9 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
-#ifndef MPD_PLAYLIST_FLAC_PLAYLIST_PLUGIN_H
-#define MPD_PLAYLIST_FLAC_PLAYLIST_PLUGIN_H
+#ifndef MPD_DECODER_DSDIFF_H
+#define MPD_DECODER_DSDIFF_H
-extern const struct playlist_plugin flac_playlist_plugin;
+extern const struct decoder_plugin dsdiff_decoder_plugin;
#endif
diff --git a/src/decoder/faad_decoder_plugin.c b/src/decoder/faad_decoder_plugin.c
index 8f932ad58..911f033b8 100644
--- a/src/decoder/faad_decoder_plugin.c
+++ b/src/decoder/faad_decoder_plugin.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -21,6 +21,7 @@
#include "decoder_api.h"
#include "decoder_buffer.h"
#include "audio_check.h"
+#include "tag_handler.h"
#define AAC_MAX_CHANNELS 6
@@ -205,7 +206,7 @@ faad_song_duration(struct decoder_buffer *buffer, struct input_stream *is)
/* obtain the duration from the ADTS header */
float song_length = adts_song_duration(buffer);
- input_stream_seek(is, tagsize, SEEK_SET, NULL);
+ input_stream_lock_seek(is, tagsize, SEEK_SET, NULL);
data = decoder_buffer_read(buffer, &length);
if (data != NULL)
@@ -406,7 +407,7 @@ faad_stream_decode(struct decoder *mpd_decoder, struct input_stream *is)
faacDecSetConfiguration(decoder, config);
while (!decoder_buffer_is_full(buffer) &&
- !input_stream_eof(is) &&
+ !input_stream_lock_eof(is) &&
decoder_get_command(mpd_decoder) == DECODE_COMMAND_NONE) {
adts_find_frame(buffer);
decoder_buffer_fill(buffer);
@@ -487,18 +488,17 @@ faad_stream_decode(struct decoder *mpd_decoder, struct input_stream *is)
faacDecClose(decoder);
}
-static struct tag *
-faad_stream_tag(struct input_stream *is)
+static bool
+faad_scan_stream(struct input_stream *is,
+ const struct tag_handler *handler, void *handler_ctx)
{
int file_time = faad_get_file_time(is);
- struct tag *tag;
if (file_time < 0)
- return NULL;
+ return false;
- tag = tag_new();
- tag->time = file_time;
- return tag;
+ tag_handler_invoke_duration(handler, handler_ctx, file_time);
+ return true;
}
static const char *const faad_suffixes[] = { "aac", NULL };
@@ -509,7 +509,7 @@ static const char *const faad_mime_types[] = {
const struct decoder_plugin faad_decoder_plugin = {
.name = "faad",
.stream_decode = faad_stream_decode,
- .stream_tag = faad_stream_tag,
+ .scan_stream = faad_scan_stream,
.suffixes = faad_suffixes,
.mime_types = faad_mime_types,
};
diff --git a/src/decoder/ffmpeg_decoder_plugin.c b/src/decoder/ffmpeg_decoder_plugin.c
index 2929d316a..6ad10026a 100644
--- a/src/decoder/ffmpeg_decoder_plugin.c
+++ b/src/decoder/ffmpeg_decoder_plugin.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -20,6 +20,8 @@
#include "config.h"
#include "decoder_api.h"
#include "audio_check.h"
+#include "ffmpeg_metadata.h"
+#include "tag_handler.h"
#include <glib.h>
@@ -32,11 +34,6 @@
#include <sys/stat.h>
#include <unistd.h>
-#ifdef OLD_FFMPEG_INCLUDES
-#include <avcodec.h>
-#include <avformat.h>
-#include <avio.h>
-#else
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavformat/avio.h>
@@ -46,13 +43,10 @@
#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(51,5,0)
#include <libavutil/dict.h>
#endif
-#endif
#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "ffmpeg"
-#ifndef OLD_FFMPEG_INCLUDES
-
static GLogLevelFlags
level_ffmpeg_to_glib(int level)
{
@@ -84,12 +78,6 @@ mpd_ffmpeg_log_callback(G_GNUC_UNUSED void *ptr, int level,
}
}
-#endif /* !OLD_FFMPEG_INCLUDES */
-
-#ifndef AV_VERSION_INT
-#define AV_VERSION_INT(a, b, c) (a<<16 | b<<8 | c)
-#endif
-
struct mpd_ffmpeg_stream {
struct decoder *decoder;
struct input_stream *input;
@@ -119,7 +107,7 @@ mpd_ffmpeg_stream_seek(void *opaque, int64_t pos, int whence)
if (whence == AVSEEK_SIZE)
return stream->input->size;
- if (!input_stream_seek(stream->input, pos, whence, NULL))
+ if (!input_stream_lock_seek(stream->input, pos, whence, NULL))
return -1;
return stream->input->offset;
@@ -189,9 +177,7 @@ mpd_ffmpeg_stream_close(struct mpd_ffmpeg_stream *stream)
static bool
ffmpeg_init(G_GNUC_UNUSED const struct config_param *param)
{
-#ifndef OLD_FFMPEG_INCLUDES
av_log_set_callback(mpd_ffmpeg_log_callback);
-#endif
av_register_all();
return true;
@@ -369,7 +355,6 @@ ffmpeg_send_packet(struct decoder *decoder, struct input_stream *is,
static enum sample_format
ffmpeg_sample_format(G_GNUC_UNUSED const AVCodecContext *codec_context)
{
-#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(51, 41, 0)
switch (codec_context->sample_fmt) {
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(52, 94, 1)
case AV_SAMPLE_FMT_S16:
@@ -390,10 +375,6 @@ ffmpeg_sample_format(G_GNUC_UNUSED const AVCodecContext *codec_context)
codec_context->sample_fmt);
return SAMPLE_FORMAT_UNDEFINED;
}
-#else
- /* XXX fixme 16-bit for older ffmpeg (13 Aug 2007) */
- return SAMPLE_FORMAT_S16;
-#endif
}
static AVInputFormat *
@@ -406,7 +387,8 @@ ffmpeg_probe(struct decoder *decoder, struct input_stream *is)
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, NULL)) {
+ if (nbytes <= PADDING ||
+ !input_stream_lock_seek(is, 0, SEEK_SET, NULL)) {
g_free(buffer);
return NULL;
}
@@ -588,72 +570,24 @@ ffmpeg_decode(struct decoder *decoder, struct input_stream *input)
mpd_ffmpeg_stream_close(stream);
}
-#if LIBAVFORMAT_VERSION_INT >= ((52<<16)+(31<<8)+0)
-typedef struct ffmpeg_tag_map {
- enum tag_type type;
- const char *name;
-} ffmpeg_tag_map;
-
-static const ffmpeg_tag_map ffmpeg_tag_maps[] = {
-#if LIBAVFORMAT_VERSION_INT < ((52<<16)+(50<<8))
- { TAG_ARTIST, "author" },
-#endif
- { TAG_DATE, "year" },
- { TAG_ARTIST_SORT, "author-sort" },
- { TAG_ALBUM_ARTIST, "album_artist" },
- { TAG_ALBUM_ARTIST_SORT, "album_artist-sort" },
-
- /* sentinel */
- { TAG_NUM_OF_ITEM_TYPES, NULL }
-};
-
-#if LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(53,1,0)
-#define AVDictionary AVMetadata
-#define AVDictionaryEntry AVMetadataTag
-#define av_dict_get av_metadata_get
-#endif
-
-static void
-ffmpeg_copy_metadata(struct tag *tag, enum tag_type type,
- AVDictionary *m, const char *name)
-{
- AVDictionaryEntry *mt = NULL;
-
- while ((mt = av_dict_get(m, name, mt, 0)) != NULL)
- tag_add_item(tag, type, mt->value);
-}
-
-static void
-ffmpeg_copy_dictionary(struct tag *tag, AVDictionary *dict)
-{
- for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i)
- ffmpeg_copy_metadata(tag, i,
- dict, tag_item_names[i]);
-
- for (const struct ffmpeg_tag_map *i = ffmpeg_tag_maps;
- i->name != NULL; ++i)
- ffmpeg_copy_metadata(tag, i->type, dict, i->name);
-}
-
-#endif
-
//no tag reading in ffmpeg, check if playable
-static struct tag *
-ffmpeg_stream_tag(struct input_stream *is)
+static bool
+ffmpeg_scan_stream(struct input_stream *is,
+ const struct tag_handler *handler, void *handler_ctx)
{
AVInputFormat *input_format = ffmpeg_probe(NULL, is);
if (input_format == NULL)
- return NULL;
+ return false;
struct mpd_ffmpeg_stream *stream = mpd_ffmpeg_stream_open(NULL, is);
if (stream == NULL)
- return NULL;
+ return false;
AVFormatContext *f = NULL;
if (mpd_ffmpeg_open_input(&f, stream->io, is->uri,
input_format) != 0) {
mpd_ffmpeg_stream_close(stream);
- return NULL;
+ return false;
}
#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,6,0)
@@ -669,49 +603,22 @@ ffmpeg_stream_tag(struct input_stream *is)
av_close_input_stream(f);
#endif
mpd_ffmpeg_stream_close(stream);
- return NULL;
+ return false;
}
- struct tag *tag = tag_new();
-
- tag->time = f->duration != (int64_t)AV_NOPTS_VALUE
- ? f->duration / AV_TIME_BASE
- : 0;
+ if (f->duration != (int64_t)AV_NOPTS_VALUE)
+ tag_handler_invoke_duration(handler, handler_ctx,
+ f->duration / AV_TIME_BASE);
-#if LIBAVFORMAT_VERSION_INT >= ((52<<16)+(31<<8)+0)
#if LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(52,101,0)
av_metadata_conv(f, NULL, f->iformat->metadata_conv);
#endif
- ffmpeg_copy_dictionary(tag, f->metadata);
+ ffmpeg_scan_dictionary(f->metadata, handler, handler_ctx);
int idx = ffmpeg_find_audio_stream(f);
if (idx >= 0)
- ffmpeg_copy_dictionary(tag, f->streams[idx]->metadata);
-#else
- if (f->author[0])
- tag_add_item(tag, TAG_ARTIST, f->author);
- if (f->title[0])
- tag_add_item(tag, TAG_TITLE, f->title);
- if (f->album[0])
- tag_add_item(tag, TAG_ALBUM, f->album);
-
- if (f->track > 0) {
- char buffer[16];
- snprintf(buffer, sizeof(buffer), "%d", f->track);
- tag_add_item(tag, TAG_TRACK, buffer);
- }
-
- if (f->comment[0])
- tag_add_item(tag, TAG_COMMENT, f->comment);
- if (f->genre[0])
- tag_add_item(tag, TAG_GENRE, f->genre);
- if (f->year > 0) {
- char buffer[16];
- snprintf(buffer, sizeof(buffer), "%d", f->year);
- tag_add_item(tag, TAG_DATE, buffer);
- }
-
-#endif
+ ffmpeg_scan_dictionary(f->streams[idx]->metadata,
+ handler, handler_ctx);
#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,17,0)
avformat_close_input(&f);
@@ -720,7 +627,7 @@ ffmpeg_stream_tag(struct input_stream *is)
#endif
mpd_ffmpeg_stream_close(stream);
- return tag;
+ return true;
}
/**
@@ -839,7 +746,7 @@ const struct decoder_plugin ffmpeg_decoder_plugin = {
.name = "ffmpeg",
.init = ffmpeg_init,
.stream_decode = ffmpeg_decode,
- .stream_tag = ffmpeg_stream_tag,
+ .scan_stream = ffmpeg_scan_stream,
.suffixes = ffmpeg_suffixes,
.mime_types = ffmpeg_mime_types
};
diff --git a/src/decoder/ffmpeg_metadata.c b/src/decoder/ffmpeg_metadata.c
new file mode 100644
index 000000000..3ef774f63
--- /dev/null
+++ b/src/decoder/ffmpeg_metadata.c
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2003-2012 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "ffmpeg_metadata.h"
+#include "tag_table.h"
+#include "tag_handler.h"
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "ffmpeg"
+
+static const struct tag_table ffmpeg_tags[] = {
+#if LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(52,50,0)
+ { "author", TAG_ARTIST },
+#endif
+ { "year", TAG_DATE },
+ { "author-sort", TAG_ARTIST_SORT },
+ { "album_artist", TAG_ALBUM_ARTIST },
+ { "album_artist-sort", TAG_ALBUM_ARTIST_SORT },
+
+ /* sentinel */
+ { NULL, TAG_NUM_OF_ITEM_TYPES }
+};
+
+static void
+ffmpeg_copy_metadata(enum tag_type type,
+ AVDictionary *m, const char *name,
+ const struct tag_handler *handler, void *handler_ctx)
+{
+ AVDictionaryEntry *mt = NULL;
+
+ while ((mt = av_dict_get(m, name, mt, 0)) != NULL)
+ tag_handler_invoke_tag(handler, handler_ctx,
+ type, mt->value);
+}
+
+#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(51,5,0)
+
+static void
+ffmpeg_scan_pairs(AVDictionary *dict,
+ const struct tag_handler *handler, void *handler_ctx)
+{
+ AVDictionaryEntry *i = NULL;
+
+ while ((i = av_dict_get(dict, "", i, AV_DICT_IGNORE_SUFFIX)) != NULL)
+ tag_handler_invoke_pair(handler, handler_ctx,
+ i->key, i->value);
+}
+
+#endif
+
+void
+ffmpeg_scan_dictionary(AVDictionary *dict,
+ const struct tag_handler *handler, void *handler_ctx)
+{
+ for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i)
+ ffmpeg_copy_metadata(i, dict, tag_item_names[i],
+ handler, handler_ctx);
+
+ for (const struct tag_table *i = ffmpeg_tags;
+ i->name != NULL; ++i)
+ ffmpeg_copy_metadata(i->type, dict, i->name,
+ handler, handler_ctx);
+
+#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(51,5,0)
+ if (handler->pair != NULL)
+ ffmpeg_scan_pairs(dict, handler, handler_ctx);
+#endif
+}
diff --git a/src/decoder/ffmpeg_metadata.h b/src/decoder/ffmpeg_metadata.h
new file mode 100644
index 000000000..60658f479
--- /dev/null
+++ b/src/decoder/ffmpeg_metadata.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2003-2012 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_FFMPEG_METADATA_H
+#define MPD_FFMPEG_METADATA_H
+
+#include <libavformat/avformat.h>
+#include <libavutil/avutil.h>
+#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(51,5,0)
+#include <libavutil/dict.h>
+#endif
+
+#if LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(53,1,0)
+#define AVDictionary AVMetadata
+#define AVDictionaryEntry AVMetadataTag
+#define av_dict_get av_metadata_get
+#endif
+
+struct tag_handler;
+
+void
+ffmpeg_scan_dictionary(AVDictionary *dict,
+ const struct tag_handler *handler, void *handler_ctx);
+
+#endif
diff --git a/src/decoder/flac_compat.h b/src/decoder/flac_compat.h
index d597690a0..9a30acc26 100644
--- a/src/decoder/flac_compat.h
+++ b/src/decoder/flac_compat.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/decoder/flac_decoder_plugin.c b/src/decoder/flac_decoder_plugin.c
index 9d980b79d..fb0b3502d 100644
--- a/src/decoder/flac_decoder_plugin.c
+++ b/src/decoder/flac_decoder_plugin.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -50,7 +50,7 @@ flac_read_cb(G_GNUC_UNUSED const FLAC__StreamDecoder *fd,
if (r == 0) {
if (decoder_get_command(data->decoder) != DECODE_COMMAND_NONE ||
- input_stream_eof(data->input_stream))
+ input_stream_lock_eof(data->input_stream))
return FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM;
else
return FLAC__STREAM_DECODER_READ_STATUS_ABORT;
@@ -68,7 +68,8 @@ flac_seek_cb(G_GNUC_UNUSED const FLAC__StreamDecoder *fd,
if (!data->input_stream->seekable)
return FLAC__STREAM_DECODER_SEEK_STATUS_UNSUPPORTED;
- if (!input_stream_seek(data->input_stream, offset, SEEK_SET, NULL))
+ if (!input_stream_lock_seek(data->input_stream, offset, SEEK_SET,
+ NULL))
return FLAC__STREAM_DECODER_SEEK_STATUS_ERROR;
return FLAC__STREAM_DECODER_SEEK_STATUS_OK;
@@ -109,53 +110,40 @@ flac_eof_cb(G_GNUC_UNUSED const FLAC__StreamDecoder *fd, void *fdata)
return (decoder_get_command(data->decoder) != DECODE_COMMAND_NONE &&
decoder_get_command(data->decoder) != DECODE_COMMAND_SEEK) ||
- input_stream_eof(data->input_stream);
+ input_stream_lock_eof(data->input_stream);
}
static void
flac_error_cb(G_GNUC_UNUSED const FLAC__StreamDecoder *fd,
FLAC__StreamDecoderErrorStatus status, void *fdata)
{
- flac_error_common_cb("flac", status, (struct flac_data *) fdata);
+ flac_error_common_cb(status, (struct flac_data *) fdata);
}
#if !defined(FLAC_API_VERSION_CURRENT) || FLAC_API_VERSION_CURRENT <= 7
static void flacPrintErroredState(FLAC__SeekableStreamDecoderState state)
{
- const char *str = ""; /* "" to silence compiler warning */
switch (state) {
case FLAC__SEEKABLE_STREAM_DECODER_OK:
case FLAC__SEEKABLE_STREAM_DECODER_SEEKING:
case FLAC__SEEKABLE_STREAM_DECODER_END_OF_STREAM:
return;
+
case FLAC__SEEKABLE_STREAM_DECODER_MEMORY_ALLOCATION_ERROR:
- str = "allocation error";
- break;
case FLAC__SEEKABLE_STREAM_DECODER_READ_ERROR:
- str = "read error";
- break;
case FLAC__SEEKABLE_STREAM_DECODER_SEEK_ERROR:
- str = "seek error";
- break;
case FLAC__SEEKABLE_STREAM_DECODER_STREAM_DECODER_ERROR:
- str = "seekable stream error";
- break;
case FLAC__SEEKABLE_STREAM_DECODER_ALREADY_INITIALIZED:
- str = "decoder already initialized";
- break;
case FLAC__SEEKABLE_STREAM_DECODER_INVALID_CALLBACK:
- str = "invalid callback";
- break;
case FLAC__SEEKABLE_STREAM_DECODER_UNINITIALIZED:
- str = "decoder uninitialized";
+ break;
}
- g_warning("%s\n", str);
+ g_warning("%s\n", FLAC__SeekableStreamDecoderStateString[state]);
}
#else /* FLAC_API_VERSION_CURRENT >= 7 */
static void flacPrintErroredState(FLAC__StreamDecoderState state)
{
- const char *str = ""; /* "" to silence compiler warning */
switch (state) {
case FLAC__STREAM_DECODER_SEARCH_FOR_METADATA:
case FLAC__STREAM_DECODER_READ_METADATA:
@@ -163,23 +151,16 @@ static void flacPrintErroredState(FLAC__StreamDecoderState state)
case FLAC__STREAM_DECODER_READ_FRAME:
case FLAC__STREAM_DECODER_END_OF_STREAM:
return;
+
case FLAC__STREAM_DECODER_OGG_ERROR:
- str = "error in the Ogg layer";
- break;
case FLAC__STREAM_DECODER_SEEK_ERROR:
- str = "seek error";
- break;
case FLAC__STREAM_DECODER_ABORTED:
- str = "decoder aborted by read";
- break;
case FLAC__STREAM_DECODER_MEMORY_ALLOCATION_ERROR:
- str = "allocation error";
- break;
case FLAC__STREAM_DECODER_UNINITIALIZED:
- str = "decoder uninitialized";
+ break;
}
- g_warning("%s\n", str);
+ g_warning("%s\n", FLAC__StreamDecoderStateString[state]);
}
#endif /* FLAC_API_VERSION_CURRENT >= 7 */
@@ -210,10 +191,11 @@ flac_write_cb(const FLAC__StreamDecoder *dec, const FLAC__Frame *frame,
return flac_common_write(data, frame, buf, nbytes);
}
-static struct tag *
-flac_tag_dup(const char *file)
+static bool
+flac_scan_file(const char *file,
+ const struct tag_handler *handler, void *handler_ctx)
{
- return flac_tag_load(file, NULL);
+ return flac_scan_file2(file, NULL, handler, handler_ctx);
}
/**
@@ -305,17 +287,56 @@ flac_decoder_loop(struct flac_data *data, FLAC__StreamDecoder *flac_dec,
/* end of this sub track */
break;
- if (!FLAC__stream_decoder_process_single(flac_dec)) {
- cmd = decoder_get_command(decoder);
- if (cmd != DECODE_COMMAND_SEEK)
- break;
+ if (!FLAC__stream_decoder_process_single(flac_dec) &&
+ decoder_get_command(decoder) == DECODE_COMMAND_NONE) {
+ /* a failure that was not triggered by a
+ decoder command */
+ flacPrintErroredState(FLAC__stream_decoder_get_state(flac_dec));
+ break;
}
}
+}
- if (cmd != DECODE_COMMAND_STOP) {
- flacPrintErroredState(FLAC__stream_decoder_get_state(flac_dec));
- FLAC__stream_decoder_finish(flac_dec);
- }
+static FLAC__StreamDecoderInitStatus
+stream_init_oggflac(FLAC__StreamDecoder *flac_dec, struct flac_data *data)
+{
+#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7
+ return FLAC__stream_decoder_init_ogg_stream(flac_dec,
+ flac_read_cb,
+ flac_seek_cb,
+ flac_tell_cb,
+ flac_length_cb,
+ flac_eof_cb,
+ flac_write_cb,
+ flacMetadata,
+ flac_error_cb,
+ data);
+#else
+ (void)flac_dec;
+ (void)data;
+
+ return FLAC__STREAM_DECODER_INIT_STATUS_ERROR;
+#endif
+}
+
+static FLAC__StreamDecoderInitStatus
+stream_init_flac(FLAC__StreamDecoder *flac_dec, struct flac_data *data)
+{
+ return FLAC__stream_decoder_init_stream(flac_dec,
+ flac_read_cb, flac_seek_cb,
+ flac_tell_cb, flac_length_cb,
+ flac_eof_cb, flac_write_cb,
+ flacMetadata,
+ flac_error_cb,
+ data);
+}
+
+static FLAC__StreamDecoderInitStatus
+stream_init(FLAC__StreamDecoder *flac_dec, struct flac_data *data, bool is_ogg)
+{
+ return is_ogg
+ ? stream_init_oggflac(flac_dec, data)
+ : stream_init_flac(flac_dec, data);
}
static void
@@ -325,7 +346,6 @@ flac_decode_internal(struct decoder * decoder,
{
FLAC__StreamDecoder *flac_dec;
struct flac_data data;
- const char *err = NULL;
flac_dec = flac_decoder_new();
if (flac_dec == NULL)
@@ -334,58 +354,30 @@ flac_decode_internal(struct decoder * decoder,
flac_data_init(&data, decoder, input_stream);
data.tag = tag_new();
- if (is_ogg) {
+ FLAC__StreamDecoderInitStatus status =
+ stream_init(flac_dec, &data, is_ogg);
+ if (status != FLAC__STREAM_DECODER_INIT_STATUS_OK) {
+ flac_data_deinit(&data);
+ FLAC__stream_decoder_delete(flac_dec);
#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7
- FLAC__StreamDecoderInitStatus status =
- FLAC__stream_decoder_init_ogg_stream(flac_dec,
- flac_read_cb,
- flac_seek_cb,
- flac_tell_cb,
- flac_length_cb,
- flac_eof_cb,
- flac_write_cb,
- flacMetadata,
- flac_error_cb,
- (void *)&data);
- if (status != FLAC__STREAM_DECODER_INIT_STATUS_OK) {
- err = "doing Ogg init()";
- goto fail;
- }
-#else
- goto fail;
+ g_warning("%s", FLAC__StreamDecoderInitStatusString[status]);
#endif
- } else {
- FLAC__StreamDecoderInitStatus status =
- FLAC__stream_decoder_init_stream(flac_dec,
- flac_read_cb,
- flac_seek_cb,
- flac_tell_cb,
- flac_length_cb,
- flac_eof_cb,
- flac_write_cb,
- flacMetadata,
- flac_error_cb,
- (void *)&data);
- if (status != FLAC__STREAM_DECODER_INIT_STATUS_OK) {
- err = "doing init()";
- goto fail;
- }
+ return;
}
if (!flac_decoder_initialize(&data, flac_dec, 0)) {
flac_data_deinit(&data);
+ FLAC__stream_decoder_finish(flac_dec);
FLAC__stream_decoder_delete(flac_dec);
return;
}
flac_decoder_loop(&data, flac_dec, 0, 0);
-fail:
flac_data_deinit(&data);
- FLAC__stream_decoder_delete(flac_dec);
- if (err)
- g_warning("%s\n", err);
+ FLAC__stream_decoder_finish(flac_dec);
+ FLAC__stream_decoder_delete(flac_dec);
}
static void
@@ -409,36 +401,33 @@ oggflac_init(G_GNUC_UNUSED const struct config_param *param)
#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7
-static struct tag *
-oggflac_tag_dup(const char *file)
+static bool
+oggflac_scan_file(const char *file,
+ const struct tag_handler *handler, void *handler_ctx)
{
- struct tag *ret = NULL;
FLAC__Metadata_Iterator *it;
FLAC__StreamMetadata *block;
FLAC__Metadata_Chain *chain = FLAC__metadata_chain_new();
- if (!(FLAC__metadata_chain_read_ogg(chain, file)))
- goto out;
+ if (!(FLAC__metadata_chain_read_ogg(chain, file))) {
+ FLAC__metadata_chain_delete(chain);
+ return false;
+ }
+
it = FLAC__metadata_iterator_new();
FLAC__metadata_iterator_init(it, chain);
- ret = tag_new();
do {
if (!(block = FLAC__metadata_iterator_get_block(it)))
break;
- flac_tag_apply_metadata(ret, NULL, block);
+ flac_scan_metadata(NULL, block,
+ handler, handler_ctx);
} while (FLAC__metadata_iterator_next(it));
FLAC__metadata_iterator_delete(it);
- if (!tag_is_defined(ret)) {
- tag_free(ret);
- ret = NULL;
- }
-
-out:
FLAC__metadata_chain_delete(chain);
- return ret;
+ return true;
}
static void
@@ -449,7 +438,7 @@ oggflac_decode(struct decoder *decoder, struct input_stream *input_stream)
/* rewind the stream, because ogg_stream_type_detect() has
moved it */
- input_stream_seek(input_stream, 0, SEEK_SET, NULL);
+ input_stream_lock_seek(input_stream, 0, SEEK_SET, NULL);
flac_decode_internal(decoder, input_stream, true);
}
@@ -471,7 +460,7 @@ const struct decoder_plugin oggflac_decoder_plugin = {
.init = oggflac_init,
#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7
.stream_decode = oggflac_decode,
- .tag_dup = oggflac_tag_dup,
+ .scan_file = oggflac_scan_file,
.suffixes = oggflac_suffixes,
.mime_types = oggflac_mime_types
#endif
@@ -491,7 +480,7 @@ static const char *const flac_mime_types[] = {
const struct decoder_plugin flac_decoder_plugin = {
.name = "flac",
.stream_decode = flac_decode,
- .tag_dup = flac_tag_dup,
+ .scan_file = flac_scan_file,
.suffixes = flac_suffixes,
.mime_types = flac_mime_types,
};
diff --git a/src/decoder/flac_metadata.c b/src/decoder/flac_metadata.c
index 5b94fd426..bd1eaf323 100644
--- a/src/decoder/flac_metadata.c
+++ b/src/decoder/flac_metadata.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -21,6 +21,8 @@
#include "flac_metadata.h"
#include "replay_gain_info.h"
#include "tag.h"
+#include "tag_handler.h"
+#include "tag_table.h"
#include <glib.h>
@@ -163,69 +165,87 @@ flac_comment_value(const FLAC__StreamMetadata_VorbisComment_Entry *entry,
* the comment value into the tag.
*/
static bool
-flac_copy_comment(struct tag *tag,
- const FLAC__StreamMetadata_VorbisComment_Entry *entry,
+flac_copy_comment(const FLAC__StreamMetadata_VorbisComment_Entry *entry,
const char *name, enum tag_type tag_type,
- const char *char_tnum)
+ const char *char_tnum,
+ const struct tag_handler *handler, void *handler_ctx)
{
const char *value;
size_t value_length;
value = flac_comment_value(entry, name, char_tnum, &value_length);
if (value != NULL) {
- tag_add_item_n(tag, tag_type, value, value_length);
+ char *p = g_strndup(value, value_length);
+ tag_handler_invoke_tag(handler, handler_ctx, tag_type, p);
+ g_free(p);
return true;
}
return false;
}
-/* tracknumber is used in VCs, MPD uses "track" ..., all the other
- * tag names match */
-static const char *VORBIS_COMMENT_TRACK_KEY = "tracknumber";
-static const char *VORBIS_COMMENT_DISC_KEY = "discnumber";
+static const struct tag_table flac_tags[] = {
+ { "tracknumber", TAG_TRACK },
+ { "discnumber", TAG_DISC },
+ { "album artist", TAG_ALBUM_ARTIST },
+ { NULL, TAG_NUM_OF_ITEM_TYPES }
+};
static void
-flac_parse_comment(struct tag *tag, const char *char_tnum,
- const FLAC__StreamMetadata_VorbisComment_Entry *entry)
+flac_scan_comment(const char *char_tnum,
+ const FLAC__StreamMetadata_VorbisComment_Entry *entry,
+ const struct tag_handler *handler, void *handler_ctx)
{
- assert(tag != NULL);
+ if (handler->pair != NULL) {
+ char *name = g_strdup((const char*)entry->entry);
+ char *value = strchr(name, '=');
+
+ if (value != NULL && value > name) {
+ *value++ = 0;
+ tag_handler_invoke_pair(handler, handler_ctx,
+ name, value);
+ }
- if (flac_copy_comment(tag, entry, VORBIS_COMMENT_TRACK_KEY,
- TAG_TRACK, char_tnum) ||
- flac_copy_comment(tag, entry, VORBIS_COMMENT_DISC_KEY,
- TAG_DISC, char_tnum) ||
- flac_copy_comment(tag, entry, "album artist",
- TAG_ALBUM_ARTIST, char_tnum))
- return;
+ g_free(name);
+ }
+
+ for (const struct tag_table *i = flac_tags; i->name != NULL; ++i)
+ if (flac_copy_comment(entry, i->name, i->type, char_tnum,
+ handler, handler_ctx))
+ return;
for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i)
- if (flac_copy_comment(tag, entry,
- tag_item_names[i], i, char_tnum))
+ if (flac_copy_comment(entry,
+ tag_item_names[i], i, char_tnum,
+ handler, handler_ctx))
return;
}
-void
-flac_vorbis_comments_to_tag(struct tag *tag, const char *char_tnum,
- const FLAC__StreamMetadata_VorbisComment *comment)
+static void
+flac_scan_comments(const char *char_tnum,
+ const FLAC__StreamMetadata_VorbisComment *comment,
+ const struct tag_handler *handler, void *handler_ctx)
{
for (unsigned i = 0; i < comment->num_comments; ++i)
- flac_parse_comment(tag, char_tnum, &comment->comments[i]);
+ flac_scan_comment(char_tnum, &comment->comments[i],
+ handler, handler_ctx);
}
void
-flac_tag_apply_metadata(struct tag *tag, const char *track,
- const FLAC__StreamMetadata *block)
+flac_scan_metadata(const char *track,
+ const FLAC__StreamMetadata *block,
+ const struct tag_handler *handler, void *handler_ctx)
{
switch (block->type) {
case FLAC__METADATA_TYPE_VORBIS_COMMENT:
- flac_vorbis_comments_to_tag(tag, track,
- &block->data.vorbis_comment);
+ flac_scan_comments(track, &block->data.vorbis_comment,
+ handler, handler_ctx);
break;
case FLAC__METADATA_TYPE_STREAMINFO:
if (block->data.stream_info.sample_rate > 0)
- tag->time = flac_duration(&block->data.stream_info);
+ tag_handler_invoke_duration(handler, handler_ctx,
+ flac_duration(&block->data.stream_info));
break;
default:
@@ -233,10 +253,18 @@ flac_tag_apply_metadata(struct tag *tag, const char *track,
}
}
-struct tag *
-flac_tag_load(const char *file, const char *char_tnum)
+void
+flac_vorbis_comments_to_tag(struct tag *tag, const char *char_tnum,
+ const FLAC__StreamMetadata_VorbisComment *comment)
+{
+ flac_scan_comments(char_tnum, comment,
+ &add_tag_handler, tag);
+}
+
+bool
+flac_scan_file2(const char *file, const char *char_tnum,
+ const struct tag_handler *handler, void *handler_ctx)
{
- struct tag *tag;
FLAC__Metadata_SimpleIterator *it;
FLAC__StreamMetadata *block = NULL;
@@ -263,22 +291,30 @@ flac_tag_load(const char *file, const char *char_tnum)
g_debug("Reading '%s' metadata gave the following error: %s\n",
file, err);
FLAC__metadata_simple_iterator_delete(it);
- return NULL;
+ return false;
}
- tag = tag_new();
do {
block = FLAC__metadata_simple_iterator_get_block(it);
if (!block)
break;
- flac_tag_apply_metadata(tag, char_tnum, block);
+ flac_scan_metadata(char_tnum, block, handler, handler_ctx);
FLAC__metadata_object_delete(block);
} while (FLAC__metadata_simple_iterator_next(it));
FLAC__metadata_simple_iterator_delete(it);
- if (!tag_is_defined(tag)) {
+ return true;
+}
+
+struct tag *
+flac_tag_load(const char *file, const char *char_tnum)
+{
+ struct tag *tag = tag_new();
+
+ if (!flac_scan_file2(file, char_tnum, &add_tag_handler, tag) ||
+ tag_is_empty(tag)) {
tag_free(tag);
tag = NULL;
}
diff --git a/src/decoder/flac_metadata.h b/src/decoder/flac_metadata.h
index e52b0fb82..3c463d5d6 100644
--- a/src/decoder/flac_metadata.h
+++ b/src/decoder/flac_metadata.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -24,6 +24,7 @@
#include <stdbool.h>
#include <FLAC/metadata.h>
+struct tag_handler;
struct tag;
struct replay_gain_info;
@@ -49,8 +50,13 @@ flac_vorbis_comments_to_tag(struct tag *tag, const char *char_tnum,
const FLAC__StreamMetadata_VorbisComment *comment);
void
-flac_tag_apply_metadata(struct tag *tag, const char *track,
- const FLAC__StreamMetadata *block);
+flac_scan_metadata(const char *track,
+ const FLAC__StreamMetadata *block,
+ const struct tag_handler *handler, void *handler_ctx);
+
+bool
+flac_scan_file2(const char *file, const char *char_tnum,
+ const struct tag_handler *handler, void *handler_ctx);
struct tag *
flac_tag_load(const char *file, const char *char_tnum);
diff --git a/src/decoder/flac_pcm.c b/src/decoder/flac_pcm.c
index bf6e2612c..6964d8ac6 100644
--- a/src/decoder/flac_pcm.c
+++ b/src/decoder/flac_pcm.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -101,7 +101,8 @@ flac_convert(void *dest,
position, end);
break;
- case SAMPLE_FORMAT_S24:
+ case SAMPLE_FORMAT_FLOAT:
+ case SAMPLE_FORMAT_DSD:
case SAMPLE_FORMAT_UNDEFINED:
/* unreachable */
assert(false);
diff --git a/src/decoder/flac_pcm.h b/src/decoder/flac_pcm.h
index bccfc645c..a931998c1 100644
--- a/src/decoder/flac_pcm.h
+++ b/src/decoder/flac_pcm.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/decoder/fluidsynth_decoder_plugin.c b/src/decoder/fluidsynth_decoder_plugin.c
index b9a2d0d99..085f84f14 100644
--- a/src/decoder/fluidsynth_decoder_plugin.c
+++ b/src/decoder/fluidsynth_decoder_plugin.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -102,7 +102,7 @@ fluidsynth_file_decode(struct decoder *decoder, const char *path_fs)
fluid_player_t *player;
char *path_dup;
int ret;
- Timer *timer;
+ struct timer *timer;
enum decoder_command cmd;
soundfont_path =
@@ -219,15 +219,15 @@ fluidsynth_file_decode(struct decoder *decoder, const char *path_fs)
delete_fluid_settings(settings);
}
-static struct tag *
-fluidsynth_tag_dup(const char *file)
+static bool
+fluidsynth_scan_file(const char *file,
+ G_GNUC_UNUSED const struct tag_handler *handler,
+ G_GNUC_UNUSED void *handler_ctx)
{
- struct tag *tag = tag_new();
-
/* to be implemented */
(void)file;
- return tag;
+ return true;
}
static const char *const fluidsynth_suffixes[] = {
@@ -239,6 +239,6 @@ const struct decoder_plugin fluidsynth_decoder_plugin = {
.name = "fluidsynth",
.init = fluidsynth_init,
.file_decode = fluidsynth_file_decode,
- .tag_dup = fluidsynth_tag_dup,
+ .scan_file = fluidsynth_scan_file,
.suffixes = fluidsynth_suffixes,
};
diff --git a/src/decoder/gme_decoder_plugin.c b/src/decoder/gme_decoder_plugin.c
index e14a52d32..237a1deb1 100644
--- a/src/decoder/gme_decoder_plugin.c
+++ b/src/decoder/gme_decoder_plugin.c
@@ -2,6 +2,7 @@
#include "../decoder_api.h"
#include "audio_check.h"
#include "uri.h"
+#include "tag_handler.h"
#include <glib.h>
#include <assert.h>
@@ -180,8 +181,9 @@ gme_file_decode(struct decoder *decoder, const char *path_fs)
gme_delete(emu);
}
-static struct tag *
-gme_tag_dup(const char *path_fs)
+static bool
+gme_scan_file(const char *path_fs,
+ const struct tag_handler *handler, void *handler_ctx)
{
Music_Emu *emu;
gme_info_t *ti;
@@ -194,41 +196,49 @@ gme_tag_dup(const char *path_fs)
g_free(path_container);
if (gme_err != NULL) {
g_warning("%s", gme_err);
- return NULL;
+ return false;
}
if((gme_err = gme_track_info(emu, &ti, song_num)) != NULL){
g_warning("%s", gme_err);
gme_delete(emu);
- return NULL;
+ return false;
}
- struct tag *tag = tag_new();
- if(ti != NULL){
- if(ti->length > 0)
- tag->time = ti->length / 1000;
- 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)
- tag_add_item(tag, TAG_ALBUM, ti->game);
- if(ti->comment != NULL)
- tag_add_item(tag, TAG_COMMENT, ti->comment);
- if(ti->copyright != NULL)
- tag_add_item(tag, TAG_DATE, ti->copyright);
+ assert(ti != NULL);
+
+ if(ti->length > 0)
+ tag_handler_invoke_duration(handler, handler_ctx,
+ ti->length / 100);
+
+ 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_handler_invoke_tag(handler, handler_ctx,
+ TAG_TITLE, tag_title);
+ g_free(tag_title);
+ }else
+ tag_handler_invoke_tag(handler, handler_ctx,
+ TAG_TITLE, ti->song);
}
+ if(ti->author != NULL)
+ tag_handler_invoke_tag(handler, handler_ctx,
+ TAG_ARTIST, ti->author);
+ if(ti->game != NULL)
+ tag_handler_invoke_tag(handler, handler_ctx,
+ TAG_ALBUM, ti->game);
+ if(ti->comment != NULL)
+ tag_handler_invoke_tag(handler, handler_ctx,
+ TAG_COMMENT, ti->comment);
+ if(ti->copyright != NULL)
+ tag_handler_invoke_tag(handler, handler_ctx,
+ TAG_DATE, ti->copyright);
gme_free_info(ti);
gme_delete(emu);
- return tag;
+
+ return true;
}
static const char *const gme_suffixes[] = {
@@ -241,7 +251,7 @@ extern const struct decoder_plugin gme_decoder_plugin;
const struct decoder_plugin gme_decoder_plugin = {
.name = "gme",
.file_decode = gme_file_decode,
- .tag_dup = gme_tag_dup,
+ .scan_file = gme_scan_file,
.suffixes = gme_suffixes,
.container_scan = gme_container_scan,
};
diff --git a/src/decoder/mad_decoder_plugin.c b/src/decoder/mad_decoder_plugin.c
index 2c2906c5c..a69284be5 100644
--- a/src/decoder/mad_decoder_plugin.c
+++ b/src/decoder/mad_decoder_plugin.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -22,6 +22,7 @@
#include "conf.h"
#include "tag_id3.h"
#include "tag_rva2.h"
+#include "tag_handler.h"
#include "audio_check.h"
#include <assert.h>
@@ -168,7 +169,7 @@ mp3_data_init(struct mp3_data *data, struct decoder *decoder,
static bool mp3_seek(struct mp3_data *data, long offset)
{
- if (!input_stream_seek(data->input_stream, offset, SEEK_SET, NULL))
+ if (!input_stream_lock_seek(data->input_stream, offset, SEEK_SET, NULL))
return false;
mad_stream_buffer(&data->stream, data->input_buffer, 0);
@@ -1176,19 +1177,18 @@ mp3_decode(struct decoder *decoder, struct input_stream *input_stream)
mp3_data_finish(&data);
}
-static struct tag *
-mad_decoder_stream_tag(struct input_stream *is)
+static bool
+mad_decoder_scan_stream(struct input_stream *is,
+ const struct tag_handler *handler, void *handler_ctx)
{
- struct tag *tag;
int total_time;
total_time = mad_decoder_total_file_time(is);
if (total_time < 0)
- return NULL;
+ return false;
- tag = tag_new();
- tag->time = total_time;
- return tag;
+ tag_handler_invoke_duration(handler, handler_ctx, total_time);
+ return true;
}
static const char *const mp3_suffixes[] = { "mp3", "mp2", NULL };
@@ -1198,7 +1198,7 @@ const struct decoder_plugin mad_decoder_plugin = {
.name = "mad",
.init = mp3_plugin_init,
.stream_decode = mp3_decode,
- .stream_tag = mad_decoder_stream_tag,
+ .scan_stream = mad_decoder_scan_stream,
.suffixes = mp3_suffixes,
.mime_types = mp3_mime_types
};
diff --git a/src/decoder/mikmod_decoder_plugin.c b/src/decoder/mikmod_decoder_plugin.c
index 91478e86f..5681a7a57 100644
--- a/src/decoder/mikmod_decoder_plugin.c
+++ b/src/decoder/mikmod_decoder_plugin.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -20,6 +20,7 @@
#include "config.h"
#include "decoder_api.h"
#include "mpd_error.h"
+#include "tag_handler.h"
#include <glib.h>
#include <mikmod.h>
@@ -177,8 +178,9 @@ mikmod_decoder_file_decode(struct decoder *decoder, const char *path_fs)
Player_Free(handle);
}
-static struct tag *
-mikmod_decoder_tag_dup(const char *path_fs)
+static bool
+mikmod_decoder_scan_file(const char *path_fs,
+ const struct tag_handler *handler, void *handler_ctx)
{
char *path2 = g_strdup(path_fs);
MODULE *handle = Player_Load(path2, 128, 0);
@@ -186,25 +188,22 @@ mikmod_decoder_tag_dup(const char *path_fs)
if (handle == NULL) {
g_free(path2);
g_debug("Failed to open file: %s", path_fs);
- return NULL;
+ return false;
}
Player_Free(handle);
- struct tag *tag = tag_new();
-
- tag->time = 0;
-
char *title = Player_LoadTitle(path2);
g_free(path2);
if (title != NULL) {
- tag_add_item(tag, TAG_TITLE, title);
+ tag_handler_invoke_tag(handler, handler_ctx,
+ TAG_TITLE, title);
free(title);
}
- return tag;
+ return true;
}
static const char *const mikmod_decoder_suffixes[] = {
@@ -231,6 +230,6 @@ const struct decoder_plugin mikmod_decoder_plugin = {
.init = mikmod_decoder_init,
.finish = mikmod_decoder_finish,
.file_decode = mikmod_decoder_file_decode,
- .tag_dup = mikmod_decoder_tag_dup,
+ .scan_file = mikmod_decoder_scan_file,
.suffixes = mikmod_decoder_suffixes,
};
diff --git a/src/decoder/modplug_decoder_plugin.c b/src/decoder/modplug_decoder_plugin.c
index 037c2fd74..21ee79e7e 100644
--- a/src/decoder/modplug_decoder_plugin.c
+++ b/src/decoder/modplug_decoder_plugin.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -19,6 +19,7 @@
#include "config.h"
#include "decoder_api.h"
+#include "tag_handler.h"
#include <glib.h>
#include <modplug.h>
@@ -62,7 +63,7 @@ static GByteArray *mod_loadfile(struct decoder *decoder, struct input_stream *is
while (true) {
ret = decoder_read(decoder, is, data, MODPLUG_READ_BLOCK);
if (ret == 0) {
- if (input_stream_eof(is))
+ if (input_stream_lock_eof(is))
/* end of file */
break;
@@ -149,34 +150,33 @@ mod_decode(struct decoder *decoder, struct input_stream *is)
ModPlug_Unload(f);
}
-static struct tag *
-modplug_stream_tag(struct input_stream *is)
+static bool
+modplug_scan_stream(struct input_stream *is,
+ const struct tag_handler *handler, void *handler_ctx)
{
ModPlugFile *f;
- struct tag *ret = NULL;
GByteArray *bdatas;
- char *title;
bdatas = mod_loadfile(NULL, is);
if (!bdatas)
- return NULL;
+ return false;
f = ModPlug_Load(bdatas->data, bdatas->len);
g_byte_array_free(bdatas, TRUE);
if (f == NULL)
- return NULL;
+ return false;
- ret = tag_new();
- ret->time = ModPlug_GetLength(f) / 1000;
+ tag_handler_invoke_duration(handler, handler_ctx,
+ ModPlug_GetLength(f) / 1000);
- title = g_strdup(ModPlug_GetName(f));
- if (title)
- tag_add_item(ret, TAG_TITLE, title);
- g_free(title);
+ const char *title = ModPlug_GetName(f);
+ if (title != NULL)
+ tag_handler_invoke_tag(handler, handler_ctx,
+ TAG_TITLE, title);
ModPlug_Unload(f);
- return ret;
+ return true;
}
static const char *const mod_suffixes[] = {
@@ -189,6 +189,6 @@ static const char *const mod_suffixes[] = {
const struct decoder_plugin modplug_decoder_plugin = {
.name = "modplug",
.stream_decode = mod_decode,
- .stream_tag = modplug_stream_tag,
+ .scan_stream = modplug_scan_stream,
.suffixes = mod_suffixes,
};
diff --git a/src/decoder/mp4ff_decoder_plugin.c b/src/decoder/mp4ff_decoder_plugin.c
index cd85778f8..ca78a22d0 100644
--- a/src/decoder/mp4ff_decoder_plugin.c
+++ b/src/decoder/mp4ff_decoder_plugin.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -21,6 +21,7 @@
#include "decoder_api.h"
#include "audio_check.h"
#include "tag_table.h"
+#include "tag_handler.h"
#include <glib.h>
@@ -108,7 +109,8 @@ mp4_seek(void *user_data, uint64_t position)
{
struct mp4ff_input_stream *mis = user_data;
- return input_stream_seek(mis->input_stream, position, SEEK_SET, NULL)
+ return input_stream_lock_seek(mis->input_stream, position, SEEK_SET,
+ NULL)
? 0 : -1;
}
@@ -355,16 +357,17 @@ 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_ALBUM_ARTIST] = "album artist",
- [TAG_COMPOSER] = "writer",
- [TAG_PERFORMER] = "band",
+static const struct tag_table mp4ff_tags[] = {
+ { "album artist", TAG_ALBUM_ARTIST },
+ { "writer", TAG_COMPOSER },
+ { "band", TAG_PERFORMER },
+ { NULL, TAG_NUM_OF_ITEM_TYPES }
};
static enum tag_type
mp4ff_tag_name_parse(const char *name)
{
- enum tag_type type = tag_table_lookup(mp4ff_tag_names, name);
+ enum tag_type type = tag_table_lookup_i(mp4ff_tags, name);
if (type == TAG_NUM_OF_ITEM_TYPES)
type = tag_name_parse_i(name);
@@ -375,8 +378,9 @@ mp4ff_tag_name_parse(const char *name)
return type;
}
-static struct tag *
-mp4_stream_tag(struct input_stream *is)
+static bool
+mp4ff_scan_stream(struct input_stream *is,
+ const struct tag_handler *handler, void *handler_ctx)
{
struct mp4ff_input_stream mis;
int32_t track;
@@ -386,23 +390,23 @@ mp4_stream_tag(struct input_stream *is)
mp4ff_t *mp4fh = mp4ff_input_stream_open(&mis, NULL, is);
if (mp4fh == NULL)
- return NULL;
+ return false;
track = mp4_get_aac_track(mp4fh, NULL, NULL, NULL);
if (track < 0) {
mp4ff_close(mp4fh);
- return NULL;
+ return false;
}
file_time = mp4ff_get_track_duration_use_offsets(mp4fh, track);
scale = mp4ff_time_scale(mp4fh, track);
if (scale < 0) {
mp4ff_close(mp4fh);
- return NULL;
+ return false;
}
- struct tag *tag = tag_new();
- tag->time = ((float)file_time) / scale + 0.5;
+ tag_handler_invoke_duration(handler, handler_ctx,
+ ((float)file_time) / scale + 0.5);
for (i = 0; i < mp4ff_meta_get_num_items(mp4fh); i++) {
char *item;
@@ -410,9 +414,12 @@ mp4_stream_tag(struct input_stream *is)
mp4ff_meta_get_by_index(mp4fh, i, &item, &value);
+ tag_handler_invoke_pair(handler, handler_ctx, item, value);
+
enum tag_type type = mp4ff_tag_name_parse(item);
if (type != TAG_NUM_OF_ITEM_TYPES)
- tag_add_item(tag, type, value);
+ tag_handler_invoke_tag(handler, handler_ctx,
+ type, value);
free(item);
free(value);
@@ -420,7 +427,7 @@ mp4_stream_tag(struct input_stream *is)
mp4ff_close(mp4fh);
- return tag;
+ return true;
}
static const char *const mp4_suffixes[] = {
@@ -435,7 +442,7 @@ static const char *const mp4_mime_types[] = { "audio/mp4", "audio/m4a", NULL };
const struct decoder_plugin mp4ff_decoder_plugin = {
.name = "mp4ff",
.stream_decode = mp4_decode,
- .stream_tag = mp4_stream_tag,
+ .scan_stream = mp4ff_scan_stream,
.suffixes = mp4_suffixes,
.mime_types = mp4_mime_types,
};
diff --git a/src/decoder/mpcdec_decoder_plugin.c b/src/decoder/mpcdec_decoder_plugin.c
index eaf470a40..d4768b35b 100644
--- a/src/decoder/mpcdec_decoder_plugin.c
+++ b/src/decoder/mpcdec_decoder_plugin.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -20,6 +20,7 @@
#include "config.h"
#include "decoder_api.h"
#include "audio_check.h"
+#include "tag_handler.h"
#ifdef MPC_IS_OLD_API
#include <mpcdec/mpcdec.h>
@@ -61,7 +62,7 @@ mpc_seek_cb(cb_first_arg, mpc_int32_t offset)
{
struct mpc_decoder_data *data = (struct mpc_decoder_data *) cb_data;
- return input_stream_seek(data->is, offset, SEEK_SET, NULL);
+ return input_stream_lock_seek(data->is, offset, SEEK_SET, NULL);
}
static mpc_int32_t
@@ -323,18 +324,17 @@ mpcdec_get_file_duration(struct input_stream *is)
return total_time;
}
-static struct tag *
-mpcdec_stream_tag(struct input_stream *is)
+static bool
+mpcdec_scan_stream(struct input_stream *is,
+ const struct tag_handler *handler, void *handler_ctx)
{
float total_time = mpcdec_get_file_duration(is);
- struct tag *tag;
if (total_time < 0)
- return NULL;
+ return false;
- tag = tag_new();
- tag->time = total_time;
- return tag;
+ tag_handler_invoke_duration(handler, handler_ctx, total_time);
+ return true;
}
static const char *const mpcdec_suffixes[] = { "mpc", NULL };
@@ -342,6 +342,6 @@ static const char *const mpcdec_suffixes[] = { "mpc", NULL };
const struct decoder_plugin mpcdec_decoder_plugin = {
.name = "mpcdec",
.stream_decode = mpcdec_decode,
- .stream_tag = mpcdec_stream_tag,
+ .scan_stream = mpcdec_scan_stream,
.suffixes = mpcdec_suffixes,
};
diff --git a/src/decoder/mpg123_decoder_plugin.c b/src/decoder/mpg123_decoder_plugin.c
index 7b48ebfaf..657a9c889 100644
--- a/src/decoder/mpg123_decoder_plugin.c
+++ b/src/decoder/mpg123_decoder_plugin.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -20,10 +20,12 @@
#include "config.h" /* must be first for large file support */
#include "decoder_api.h"
#include "audio_check.h"
+#include "tag_handler.h"
#include <glib.h>
#include <mpg123.h>
+#include <stdio.h>
#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "mpg123"
@@ -105,6 +107,7 @@ mpd_mpg123_file_decode(struct decoder *decoder, const char *path_fs)
int error;
off_t num_samples;
enum decoder_command cmd;
+ struct mpg123_frameinfo info;
/* open the file */
@@ -124,10 +127,25 @@ mpd_mpg123_file_decode(struct decoder *decoder, const char *path_fs)
/* tell MPD core we're ready */
- decoder_initialized(decoder, &audio_format, false,
+ decoder_initialized(decoder, &audio_format, true,
(float)num_samples /
(float)audio_format.sample_rate);
+ if (mpg123_info(handle, &info) != MPG123_OK) {
+ info.vbr = MPG123_CBR;
+ info.bitrate = 0;
+ }
+
+ switch (info.vbr) {
+ case MPG123_ABR:
+ info.bitrate = info.abr_rate;
+ break;
+ case MPG123_CBR:
+ break;
+ default:
+ info.bitrate = 0;
+ }
+
/* the decoder main loop */
do {
@@ -144,11 +162,30 @@ mpd_mpg123_file_decode(struct decoder *decoder, const char *path_fs)
break;
}
+ /* update bitrate for ABR/VBR */
+ if (info.vbr != MPG123_CBR) {
+ /* FIXME: maybe skip, as too expensive? */
+ /* FIXME: maybe, (info.vbr == MPG123_VBR) ? */
+ if (mpg123_info (handle, &info) != MPG123_OK)
+ info.bitrate = 0;
+ }
+
/* send to MPD */
- cmd = decoder_data(decoder, NULL, buffer, nbytes, 0);
+ cmd = decoder_data(decoder, NULL, buffer, nbytes, info.bitrate);
- /* seeking not yet implemented */
+ if (cmd == DECODE_COMMAND_SEEK) {
+ off_t c = decoder_seek_where(decoder)*audio_format.sample_rate;
+ c = mpg123_seek(handle, c, SEEK_SET);
+ if (c < 0)
+ decoder_seek_error(decoder);
+ else {
+ decoder_command_finished(decoder);
+ decoder_timestamp(decoder, c/(double)audio_format.sample_rate);
+ }
+
+ cmd = DECODE_COMMAND_NONE;
+ }
} while (cmd == DECODE_COMMAND_NONE);
/* cleanup */
@@ -156,41 +193,40 @@ mpd_mpg123_file_decode(struct decoder *decoder, const char *path_fs)
mpg123_delete(handle);
}
-static struct tag *
-mpd_mpg123_tag_dup(const char *path_fs)
+static bool
+mpd_mpg123_scan_file(const char *path_fs,
+ const struct tag_handler *handler, void *handler_ctx)
{
struct audio_format audio_format;
mpg123_handle *handle;
int error;
off_t num_samples;
- struct tag *tag;
handle = mpg123_new(NULL, &error);
if (handle == NULL) {
g_warning("mpg123_new() failed: %s",
mpg123_plain_strerror(error));
- return NULL;
+ return false;
}
if (!mpd_mpg123_open(handle, path_fs, &audio_format)) {
mpg123_delete(handle);
- return NULL;
+ return false;
}
num_samples = mpg123_length(handle);
if (num_samples <= 0) {
mpg123_delete(handle);
- return NULL;
+ return false;
}
- tag = tag_new();
-
- tag->time = num_samples / audio_format.sample_rate;
-
/* ID3 tag support not yet implemented */
mpg123_delete(handle);
- return tag;
+
+ tag_handler_invoke_duration(handler, handler_ctx,
+ num_samples / audio_format.sample_rate);
+ return true;
}
static const char *const mpg123_suffixes[] = {
@@ -204,6 +240,6 @@ const struct decoder_plugin mpg123_decoder_plugin = {
.finish = mpd_mpg123_finish,
.file_decode = mpd_mpg123_file_decode,
/* streaming not yet implemented */
- .tag_dup = mpd_mpg123_tag_dup,
+ .scan_file = mpd_mpg123_scan_file,
.suffixes = mpg123_suffixes,
};
diff --git a/src/decoder/oggflac_decoder_plugin.c b/src/decoder/oggflac_decoder_plugin.c
deleted file mode 100644
index 7e5f48318..000000000
--- a/src/decoder/oggflac_decoder_plugin.c
+++ /dev/null
@@ -1,354 +0,0 @@
-/*
- * 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.
- */
-
-/*
- * OggFLAC support (half-stolen from flac_plugin.c :))
- */
-
-#include "config.h" /* must be first for large file support */
-#include "_flac_common.h"
-#include "_ogg_common.h"
-#include "flac_metadata.h"
-
-#include <glib.h>
-#include <OggFLAC/seekable_stream_decoder.h>
-#include <assert.h>
-#include <unistd.h>
-
-static void oggflac_cleanup(OggFLAC__SeekableStreamDecoder * decoder)
-{
- if (decoder)
- OggFLAC__seekable_stream_decoder_delete(decoder);
-}
-
-static OggFLAC__SeekableStreamDecoderReadStatus of_read_cb(G_GNUC_UNUSED const
- OggFLAC__SeekableStreamDecoder
- * decoder,
- FLAC__byte buf[],
- unsigned *bytes,
- void *fdata)
-{
- struct flac_data *data = (struct flac_data *) fdata;
- size_t r;
-
- r = decoder_read(data->decoder, data->input_stream,
- (void *)buf, *bytes);
- *bytes = r;
-
- if (r == 0 && !input_stream_eof(data->input_stream) &&
- decoder_get_command(data->decoder) == DECODE_COMMAND_NONE)
- return OggFLAC__SEEKABLE_STREAM_DECODER_READ_STATUS_ERROR;
-
- return OggFLAC__SEEKABLE_STREAM_DECODER_READ_STATUS_OK;
-}
-
-static OggFLAC__SeekableStreamDecoderSeekStatus of_seek_cb(G_GNUC_UNUSED const
- OggFLAC__SeekableStreamDecoder
- * decoder,
- FLAC__uint64 offset,
- void *fdata)
-{
- struct flac_data *data = (struct flac_data *) fdata;
-
- if (!input_stream_seek(data->input_stream, offset, SEEK_SET, NULL))
- return OggFLAC__SEEKABLE_STREAM_DECODER_SEEK_STATUS_ERROR;
-
- return OggFLAC__SEEKABLE_STREAM_DECODER_SEEK_STATUS_OK;
-}
-
-static OggFLAC__SeekableStreamDecoderTellStatus of_tell_cb(G_GNUC_UNUSED const
- OggFLAC__SeekableStreamDecoder
- * decoder,
- FLAC__uint64 *
- offset, void *fdata)
-{
- struct flac_data *data = (struct flac_data *) fdata;
-
- *offset = (long)(data->input_stream->offset);
-
- return OggFLAC__SEEKABLE_STREAM_DECODER_TELL_STATUS_OK;
-}
-
-static OggFLAC__SeekableStreamDecoderLengthStatus of_length_cb(G_GNUC_UNUSED const
- OggFLAC__SeekableStreamDecoder
- * decoder,
- FLAC__uint64 *
- length,
- void *fdata)
-{
- struct flac_data *data = (struct flac_data *) fdata;
-
- if (data->input_stream->size < 0)
- return OggFLAC__SEEKABLE_STREAM_DECODER_LENGTH_STATUS_ERROR;
-
- *length = (size_t) (data->input_stream->size);
-
- return OggFLAC__SEEKABLE_STREAM_DECODER_LENGTH_STATUS_OK;
-}
-
-static FLAC__bool of_EOF_cb(G_GNUC_UNUSED const OggFLAC__SeekableStreamDecoder * decoder,
- void *fdata)
-{
- struct flac_data *data = (struct flac_data *) fdata;
-
- return (decoder_get_command(data->decoder) != DECODE_COMMAND_NONE &&
- decoder_get_command(data->decoder) != DECODE_COMMAND_SEEK) ||
- input_stream_eof(data->input_stream);
-}
-
-static void of_error_cb(G_GNUC_UNUSED const OggFLAC__SeekableStreamDecoder * decoder,
- FLAC__StreamDecoderErrorStatus status, void *fdata)
-{
- flac_error_common_cb("oggflac", status, (struct flac_data *) fdata);
-}
-
-static void oggflacPrintErroredState(OggFLAC__SeekableStreamDecoderState state)
-{
- switch (state) {
- case OggFLAC__SEEKABLE_STREAM_DECODER_MEMORY_ALLOCATION_ERROR:
- g_warning("oggflac allocation error\n");
- break;
- case OggFLAC__SEEKABLE_STREAM_DECODER_READ_ERROR:
- g_warning("oggflac read error\n");
- break;
- case OggFLAC__SEEKABLE_STREAM_DECODER_SEEK_ERROR:
- g_warning("oggflac seek error\n");
- break;
- case OggFLAC__SEEKABLE_STREAM_DECODER_STREAM_DECODER_ERROR:
- g_warning("oggflac seekable stream error\n");
- break;
- case OggFLAC__SEEKABLE_STREAM_DECODER_ALREADY_INITIALIZED:
- g_warning("oggflac decoder already initialized\n");
- break;
- case OggFLAC__SEEKABLE_STREAM_DECODER_INVALID_CALLBACK:
- g_warning("invalid oggflac callback\n");
- break;
- case OggFLAC__SEEKABLE_STREAM_DECODER_UNINITIALIZED:
- g_warning("oggflac decoder uninitialized\n");
- break;
- case OggFLAC__SEEKABLE_STREAM_DECODER_OK:
- case OggFLAC__SEEKABLE_STREAM_DECODER_SEEKING:
- case OggFLAC__SEEKABLE_STREAM_DECODER_END_OF_STREAM:
- break;
- }
-}
-
-static FLAC__StreamDecoderWriteStatus
-oggflac_write_cb(G_GNUC_UNUSED const OggFLAC__SeekableStreamDecoder *decoder,
- const FLAC__Frame *frame, const FLAC__int32 *const buf[],
- void *vdata)
-{
- struct flac_data *data = (struct flac_data *) vdata;
-
- return flac_common_write(data, frame, buf, 0);
-}
-
-/* used by TagDup */
-static void of_metadata_dup_cb(G_GNUC_UNUSED const OggFLAC__SeekableStreamDecoder * decoder,
- const FLAC__StreamMetadata * block, void *vdata)
-{
- struct flac_data *data = (struct flac_data *) vdata;
-
- assert(data->tag != NULL);
-
- flac_tag_apply_metadata(data->tag, NULL, block);
-}
-
-/* used by decode */
-static void of_metadata_decode_cb(G_GNUC_UNUSED const OggFLAC__SeekableStreamDecoder * dec,
- const FLAC__StreamMetadata * block,
- void *vdata)
-{
- flac_metadata_common_cb(block, (struct flac_data *) vdata);
-}
-
-static OggFLAC__SeekableStreamDecoder *
-full_decoder_init_and_read_metadata(struct flac_data *data,
- unsigned int metadata_only)
-{
- OggFLAC__SeekableStreamDecoder *decoder = NULL;
- unsigned int s = 1;
-
- if (!(decoder = OggFLAC__seekable_stream_decoder_new()))
- return NULL;
-
- if (metadata_only) {
- s &= OggFLAC__seekable_stream_decoder_set_metadata_callback
- (decoder, of_metadata_dup_cb);
- s &= OggFLAC__seekable_stream_decoder_set_metadata_respond
- (decoder, FLAC__METADATA_TYPE_STREAMINFO);
- } else {
- s &= OggFLAC__seekable_stream_decoder_set_metadata_callback
- (decoder, of_metadata_decode_cb);
- }
-
- s &= OggFLAC__seekable_stream_decoder_set_read_callback(decoder,
- of_read_cb);
- s &= OggFLAC__seekable_stream_decoder_set_seek_callback(decoder,
- of_seek_cb);
- s &= OggFLAC__seekable_stream_decoder_set_tell_callback(decoder,
- of_tell_cb);
- s &= OggFLAC__seekable_stream_decoder_set_length_callback(decoder,
- of_length_cb);
- s &= OggFLAC__seekable_stream_decoder_set_eof_callback(decoder,
- of_EOF_cb);
- s &= OggFLAC__seekable_stream_decoder_set_write_callback(decoder,
- oggflac_write_cb);
- s &= OggFLAC__seekable_stream_decoder_set_metadata_respond(decoder,
- FLAC__METADATA_TYPE_VORBIS_COMMENT);
- s &= OggFLAC__seekable_stream_decoder_set_error_callback(decoder,
- of_error_cb);
- s &= OggFLAC__seekable_stream_decoder_set_client_data(decoder,
- (void *)data);
-
- if (!s) {
- g_warning("oggflac problem before init()\n");
- goto fail;
- }
- if (OggFLAC__seekable_stream_decoder_init(decoder) !=
- OggFLAC__SEEKABLE_STREAM_DECODER_OK) {
- g_warning("oggflac problem doing init()\n");
- goto fail;
- }
- if (!OggFLAC__seekable_stream_decoder_process_until_end_of_metadata
- (decoder)) {
- g_warning("oggflac problem reading metadata\n");
- goto fail;
- }
-
- return decoder;
-
-fail:
- oggflacPrintErroredState(OggFLAC__seekable_stream_decoder_get_state
- (decoder));
- OggFLAC__seekable_stream_decoder_delete(decoder);
- return NULL;
-}
-
-/* public functions: */
-static struct tag *
-oggflac_stream_tag(struct input_stream *is)
-{
- OggFLAC__SeekableStreamDecoder *decoder;
- struct flac_data data;
- struct tag *tag;
-
- if (ogg_stream_type_detect(is) != FLAC)
- return NULL;
-
- /* rewind the stream, because ogg_stream_type_detect() has
- moved it */
- input_stream_seek(is, 0, SEEK_SET, NULL);
-
- flac_data_init(&data, NULL, is);
-
- data.tag = tag_new();
-
- /* errors here won't matter,
- * data.tag will be set or unset, that's all we care about */
- decoder = full_decoder_init_and_read_metadata(&data, 1);
-
- oggflac_cleanup(decoder);
-
- if (tag_is_defined(data.tag)) {
- tag = data.tag;
- data.tag = NULL;
- } else
- tag = NULL;
-
- flac_data_deinit(&data);
-
- return tag;
-}
-
-static void
-oggflac_decode(struct decoder * mpd_decoder, struct input_stream *input_stream)
-{
- OggFLAC__SeekableStreamDecoder *decoder = NULL;
- struct flac_data data;
- struct audio_format audio_format;
-
- if (ogg_stream_type_detect(input_stream) != FLAC)
- return;
-
- /* rewind the stream, because ogg_stream_type_detect() has
- moved it */
- input_stream_seek(input_stream, 0, SEEK_SET, NULL);
-
- flac_data_init(&data, mpd_decoder, input_stream);
-
- if (!(decoder = full_decoder_init_and_read_metadata(&data, 0))) {
- goto fail;
- }
-
- if (!data.initialized)
- goto fail;
-
- decoder_initialized(mpd_decoder, &audio_format,
- input_stream->seekable,
- (float)data.total_frames /
- (float)data.audio_format.sample_rate);
-
- while (true) {
- OggFLAC__seekable_stream_decoder_process_single(decoder);
- if (OggFLAC__seekable_stream_decoder_get_state(decoder) !=
- OggFLAC__SEEKABLE_STREAM_DECODER_OK) {
- break;
- }
- if (decoder_get_command(mpd_decoder) == DECODE_COMMAND_SEEK) {
- FLAC__uint64 seek_sample = decoder_seek_where(mpd_decoder) *
- data.audio_format.sample_rate;
- if (OggFLAC__seekable_stream_decoder_seek_absolute
- (decoder, seek_sample)) {
- data.next_frame = seek_sample;
- data.position = 0;
- decoder_command_finished(mpd_decoder);
- } else
- decoder_seek_error(mpd_decoder);
- }
- }
-
- if (decoder_get_command(mpd_decoder) == DECODE_COMMAND_NONE) {
- oggflacPrintErroredState
- (OggFLAC__seekable_stream_decoder_get_state(decoder));
- OggFLAC__seekable_stream_decoder_finish(decoder);
- }
-
-fail:
- oggflac_cleanup(decoder);
- flac_data_deinit(&data);
-}
-
-static const char *const oggflac_suffixes[] = { "ogg", "oga", NULL };
-static const char *const oggflac_mime_types[] = {
- "application/ogg",
- "application/x-ogg",
- "audio/ogg",
- "audio/x-ogg",
- "audio/x-flac+ogg",
- NULL
-};
-
-const struct decoder_plugin oggflac_decoder_plugin = {
- .name = "oggflac",
- .stream_decode = oggflac_decode,
- .stream_tag = oggflac_stream_tag,
- .suffixes = oggflac_suffixes,
- .mime_types = oggflac_mime_types
-};
diff --git a/src/decoder/pcm_decoder_plugin.c b/src/decoder/pcm_decoder_plugin.c
new file mode 100644
index 000000000..fc7dffc05
--- /dev/null
+++ b/src/decoder/pcm_decoder_plugin.c
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "decoder/pcm_decoder_plugin.h"
+#include "decoder_api.h"
+#include "util/byte_reverse.h"
+
+#include <glib.h>
+#include <unistd.h>
+#include <stdio.h> /* for SEEK_SET */
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "pcm"
+
+static void
+pcm_stream_decode(struct decoder *decoder, struct input_stream *is)
+{
+ static const struct audio_format audio_format = {
+ .sample_rate = 44100,
+ .format = SAMPLE_FORMAT_S16,
+ .channels = 2,
+ };
+
+ const bool reverse_endian = is->mime != NULL &&
+ strcmp(is->mime, "audio/x-mpd-cdda-pcm-reverse") == 0;
+
+ GError *error = NULL;
+ enum decoder_command cmd;
+
+ double time_to_size = audio_format_time_to_size(&audio_format);
+
+ float total_time = -1;
+ if (is->size >= 0)
+ total_time = is->size / time_to_size;
+
+ decoder_initialized(decoder, &audio_format, is->seekable, total_time);
+
+ do {
+ char buffer[4096];
+
+ size_t nbytes = decoder_read(decoder, is,
+ buffer, sizeof(buffer));
+
+ if (nbytes == 0 && input_stream_lock_eof(is))
+ break;
+
+ if (reverse_endian)
+ /* make sure we deliver samples in host byte order */
+ reverse_bytes_16((uint16_t *)buffer,
+ (uint16_t *)buffer,
+ (uint16_t *)(buffer + nbytes));
+
+ cmd = nbytes > 0
+ ? decoder_data(decoder, is,
+ buffer, nbytes, 0)
+ : decoder_get_command(decoder);
+ if (cmd == DECODE_COMMAND_SEEK) {
+ goffset offset = (goffset)(time_to_size *
+ decoder_seek_where(decoder));
+ if (input_stream_lock_seek(is, offset, SEEK_SET,
+ &error)) {
+ decoder_command_finished(decoder);
+ } else {
+ g_warning("seeking failed: %s", error->message);
+ g_error_free(error);
+ decoder_seek_error(decoder);
+ }
+
+ cmd = DECODE_COMMAND_NONE;
+ }
+ } while (cmd == DECODE_COMMAND_NONE);
+}
+
+static const char *const pcm_mime_types[] = {
+ /* for streams obtained by the cdio_paranoia input plugin */
+ "audio/x-mpd-cdda-pcm",
+
+ /* same as above, but with reverse byte order */
+ "audio/x-mpd-cdda-pcm-reverse",
+
+ NULL
+};
+
+const struct decoder_plugin pcm_decoder_plugin = {
+ .name = "pcm",
+ .stream_decode = pcm_stream_decode,
+ .mime_types = pcm_mime_types,
+};
diff --git a/src/decoder/pcm_decoder_plugin.h b/src/decoder/pcm_decoder_plugin.h
new file mode 100644
index 000000000..11df80155
--- /dev/null
+++ b/src/decoder/pcm_decoder_plugin.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/** \file
+ *
+ * Not really a decoder; this plugin forwards its input data "as-is".
+ *
+ * It was written only to support the "cdio_paranoia" input plugin,
+ * which does not need a decoder.
+ */
+
+#ifndef MPD_DECODER_PCM_H
+#define MPD_DECODER_PCM_H
+
+extern const struct decoder_plugin pcm_decoder_plugin;
+
+#endif
diff --git a/src/decoder/sidplay_decoder_plugin.cxx b/src/decoder/sidplay_decoder_plugin.cxx
index 6fceeb30f..5d162f179 100644
--- a/src/decoder/sidplay_decoder_plugin.cxx
+++ b/src/decoder/sidplay_decoder_plugin.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -21,6 +21,7 @@
extern "C" {
#include "../decoder_api.h"
+#include "tag_handler.h"
}
#include <errno.h>
@@ -200,7 +201,6 @@ get_song_length(const char *path_fs)
static void
sidplay_file_decode(struct decoder *decoder, const char *path_fs)
{
- int ret;
int channels;
/* load the tune */
@@ -336,8 +336,9 @@ sidplay_file_decode(struct decoder *decoder, const char *path_fs)
} while (cmd != DECODE_COMMAND_STOP);
}
-static struct tag *
-sidplay_tag_dup(const char *path_fs)
+static bool
+sidplay_scan_file(const char *path_fs,
+ const struct tag_handler *handler, void *handler_ctx)
{
int song_num=get_song_num(path_fs);
char *path_container=get_container_name(path_fs);
@@ -345,10 +346,9 @@ sidplay_tag_dup(const char *path_fs)
SidTune tune(path_container, NULL, true);
g_free(path_container);
if (!tune)
- return NULL;
+ return false;
const SidTuneInfo &info = tune.getInfo();
- struct tag *tag = tag_new();
/* title */
const char *title;
@@ -360,25 +360,28 @@ sidplay_tag_dup(const char *path_fs)
if(info.songs>1) {
char *tag_title=g_strdup_printf("%s (%d/%d)",
title, song_num, info.songs);
- tag_add_item(tag, TAG_TITLE, tag_title);
+ tag_handler_invoke_tag(handler, handler_ctx,
+ TAG_TITLE, tag_title);
g_free(tag_title);
} else
- tag_add_item(tag, TAG_TITLE, title);
+ tag_handler_invoke_tag(handler, handler_ctx, TAG_TITLE, title);
/* artist */
if (info.numberOfInfoStrings > 1 && info.infoString[1] != NULL)
- tag_add_item(tag, TAG_ARTIST, info.infoString[1]);
+ tag_handler_invoke_tag(handler, handler_ctx, TAG_ARTIST,
+ info.infoString[1]);
/* track */
char *track=g_strdup_printf("%d", song_num);
- tag_add_item(tag, TAG_TRACK, track);
+ tag_handler_invoke_tag(handler, handler_ctx, TAG_TRACK, track);
g_free(track);
/* time */
int song_len=get_song_length(path_fs);
- if(song_len!=-1) tag->time=song_len;
+ if (song_len >= 0)
+ tag_handler_invoke_duration(handler, handler_ctx, song_len);
- return tag;
+ return true;
}
static char *
@@ -421,7 +424,7 @@ const struct decoder_plugin sidplay_decoder_plugin = {
sidplay_finish,
NULL, /* stream_decode() */
sidplay_file_decode,
- sidplay_tag_dup,
+ sidplay_scan_file,
NULL, /* stream_tag() */
sidplay_container_scan,
sidplay_suffixes,
diff --git a/src/decoder/sndfile_decoder_plugin.c b/src/decoder/sndfile_decoder_plugin.c
index af68f117d..8dd98236f 100644
--- a/src/decoder/sndfile_decoder_plugin.c
+++ b/src/decoder/sndfile_decoder_plugin.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -20,6 +20,7 @@
#include "config.h"
#include "decoder_api.h"
#include "audio_check.h"
+#include "tag_handler.h"
#include <sndfile.h>
@@ -40,7 +41,7 @@ sndfile_vio_seek(sf_count_t offset, int whence, void *user_data)
struct input_stream *is = user_data;
bool success;
- success = input_stream_seek(is, offset, whence, NULL);
+ success = input_stream_lock_seek(is, offset, whence, NULL);
if (!success)
return -1;
@@ -54,7 +55,7 @@ sndfile_vio_read(void *ptr, sf_count_t count, void *user_data)
GError *error = NULL;
size_t nbytes;
- nbytes = input_stream_read(is, ptr, count, &error);
+ nbytes = input_stream_lock_read(is, ptr, count, &error);
if (nbytes == 0 && error != NULL) {
g_warning("%s", error->message);
g_error_free(error);
@@ -172,44 +173,47 @@ sndfile_stream_decode(struct decoder *decoder, struct input_stream *is)
sf_close(sf);
}
-static struct tag *
-sndfile_tag_dup(const char *path_fs)
+static bool
+sndfile_scan_file(const char *path_fs,
+ const struct tag_handler *handler, void *handler_ctx)
{
SNDFILE *sf;
SF_INFO info;
- struct tag *tag;
const char *p;
info.format = 0;
sf = sf_open(path_fs, SFM_READ, &info);
if (sf == NULL)
- return NULL;
+ return false;
if (!audio_valid_sample_rate(info.samplerate)) {
sf_close(sf);
g_warning("Invalid sample rate in %s\n", path_fs);
- return NULL;
+ return false;
}
- tag = tag_new();
- tag->time = info.frames / info.samplerate;
+ tag_handler_invoke_duration(handler, handler_ctx,
+ info.frames / info.samplerate);
p = sf_get_string(sf, SF_STR_TITLE);
if (p != NULL)
- tag_add_item(tag, TAG_TITLE, p);
+ tag_handler_invoke_tag(handler, handler_ctx,
+ TAG_TITLE, p);
p = sf_get_string(sf, SF_STR_ARTIST);
if (p != NULL)
- tag_add_item(tag, TAG_ARTIST, p);
+ tag_handler_invoke_tag(handler, handler_ctx,
+ TAG_ARTIST, p);
p = sf_get_string(sf, SF_STR_DATE);
if (p != NULL)
- tag_add_item(tag, TAG_DATE, p);
+ tag_handler_invoke_tag(handler, handler_ctx,
+ TAG_DATE, p);
sf_close(sf);
- return tag;
+ return true;
}
static const char *const sndfile_suffixes[] = {
@@ -245,7 +249,7 @@ static const char *const sndfile_mime_types[] = {
const struct decoder_plugin sndfile_decoder_plugin = {
.name = "sndfile",
.stream_decode = sndfile_stream_decode,
- .tag_dup = sndfile_tag_dup,
+ .scan_file = sndfile_scan_file,
.suffixes = sndfile_suffixes,
.mime_types = sndfile_mime_types,
};
diff --git a/src/decoder/vorbis_comments.c b/src/decoder/vorbis_comments.c
new file mode 100644
index 000000000..6c2d57b72
--- /dev/null
+++ b/src/decoder/vorbis_comments.c
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2003-2012 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "vorbis_comments.h"
+#include "tag.h"
+#include "tag_table.h"
+#include "tag_handler.h"
+#include "replay_gain_info.h"
+
+#include <glib.h>
+#include <assert.h>
+#include <stddef.h>
+#include <string.h>
+#include <stdlib.h>
+
+static const char *
+vorbis_comment_value(const char *comment, const char *needle)
+{
+ size_t len = strlen(needle);
+
+ if (g_ascii_strncasecmp(comment, needle, len) == 0 &&
+ comment[len] == '=')
+ return comment + len + 1;
+
+ return NULL;
+}
+
+bool
+vorbis_comments_to_replay_gain(struct replay_gain_info *rgi, char **comments)
+{
+ const char *temp;
+ bool found = false;
+
+ replay_gain_info_init(rgi);
+
+ while (*comments) {
+ if ((temp =
+ vorbis_comment_value(*comments, "replaygain_track_gain"))) {
+ rgi->tuples[REPLAY_GAIN_TRACK].gain = atof(temp);
+ found = true;
+ } else if ((temp = vorbis_comment_value(*comments,
+ "replaygain_album_gain"))) {
+ rgi->tuples[REPLAY_GAIN_ALBUM].gain = atof(temp);
+ found = true;
+ } else if ((temp = vorbis_comment_value(*comments,
+ "replaygain_track_peak"))) {
+ rgi->tuples[REPLAY_GAIN_TRACK].peak = atof(temp);
+ found = true;
+ } else if ((temp = vorbis_comment_value(*comments,
+ "replaygain_album_peak"))) {
+ rgi->tuples[REPLAY_GAIN_ALBUM].peak = atof(temp);
+ found = true;
+ }
+
+ comments++;
+ }
+
+ return found;
+}
+
+/**
+ * Check if the comment's name equals the passed name, and if so, copy
+ * the comment value into the tag.
+ */
+static bool
+vorbis_copy_comment(const char *comment,
+ const char *name, enum tag_type tag_type,
+ const struct tag_handler *handler, void *handler_ctx)
+{
+ const char *value;
+
+ value = vorbis_comment_value(comment, name);
+ if (value != NULL) {
+ tag_handler_invoke_tag(handler, handler_ctx, tag_type, value);
+ return true;
+ }
+
+ return false;
+}
+
+static const struct tag_table vorbis_tags[] = {
+ { "tracknumber", TAG_TRACK },
+ { "discnumber", TAG_DISC },
+ { "album artist", TAG_ALBUM_ARTIST },
+ { NULL, TAG_NUM_OF_ITEM_TYPES }
+};
+
+static void
+vorbis_scan_comment(const char *comment,
+ const struct tag_handler *handler, void *handler_ctx)
+{
+ if (handler->pair != NULL) {
+ char *name = g_strdup((const char*)comment);
+ char *value = strchr(name, '=');
+
+ if (value != NULL && value > name) {
+ *value++ = 0;
+ tag_handler_invoke_pair(handler, handler_ctx,
+ name, value);
+ }
+
+ g_free(name);
+ }
+
+ for (const struct tag_table *i = vorbis_tags; i->name != NULL; ++i)
+ if (vorbis_copy_comment(comment, i->name, i->type,
+ handler, handler_ctx))
+ return;
+
+ for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i)
+ if (vorbis_copy_comment(comment,
+ tag_item_names[i], i,
+ handler, handler_ctx))
+ return;
+}
+
+void
+vorbis_comments_scan(char **comments,
+ const struct tag_handler *handler, void *handler_ctx)
+{
+ while (*comments)
+ vorbis_scan_comment(*comments++,
+ handler, handler_ctx);
+
+}
+
+struct tag *
+vorbis_comments_to_tag(char **comments)
+{
+ struct tag *tag = tag_new();
+ vorbis_comments_scan(comments, &add_tag_handler, tag);
+
+ if (tag_is_empty(tag)) {
+ tag_free(tag);
+ tag = NULL;
+ }
+
+ return tag;
+}
diff --git a/src/decoder/vorbis_comments.h b/src/decoder/vorbis_comments.h
new file mode 100644
index 000000000..c15096930
--- /dev/null
+++ b/src/decoder/vorbis_comments.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2003-2012 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_VORBIS_COMMENTS_H
+#define MPD_VORBIS_COMMENTS_H
+
+#include "check.h"
+
+#include <stdbool.h>
+
+struct replay_gain_info;
+struct tag_handler;
+
+bool
+vorbis_comments_to_replay_gain(struct replay_gain_info *rgi, char **comments);
+
+void
+vorbis_comments_scan(char **comments,
+ const struct tag_handler *handler, void *handler_ctx);
+
+struct tag *
+vorbis_comments_to_tag(char **comments);
+
+#endif
diff --git a/src/decoder/vorbis_decoder_plugin.c b/src/decoder/vorbis_decoder_plugin.c
index 0a3944ad6..15cdc0ca9 100644
--- a/src/decoder/vorbis_decoder_plugin.c
+++ b/src/decoder/vorbis_decoder_plugin.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -18,9 +18,11 @@
*/
#include "config.h"
+#include "vorbis_comments.h"
#include "_ogg_common.h"
#include "audio_check.h"
#include "uri.h"
+#include "tag_handler.h"
#ifndef HAVE_TREMOR
#define OV_EXCLUDE_STATIC_CALLBACKS
@@ -42,7 +44,6 @@
#include <assert.h>
#include <errno.h>
-#include <stdlib.h>
#include <unistd.h>
#undef G_LOG_DOMAIN
@@ -80,7 +81,7 @@ static int ogg_seek_cb(void *data, ogg_int64_t offset, int whence)
return vis->seekable &&
(!vis->decoder || decoder_get_command(vis->decoder) != DECODE_COMMAND_STOP) &&
- input_stream_seek(vis->input_stream, offset, whence, NULL)
+ input_stream_lock_seek(vis->input_stream, offset, whence, NULL)
? 0 : -1;
}
@@ -150,108 +151,6 @@ vorbis_is_open(struct vorbis_input_stream *vis, OggVorbis_File *vf,
return true;
}
-static const char *
-vorbis_comment_value(const char *comment, const char *needle)
-{
- size_t len = strlen(needle);
-
- if (g_ascii_strncasecmp(comment, needle, len) == 0 &&
- comment[len] == '=')
- return comment + len + 1;
-
- return NULL;
-}
-
-static bool
-vorbis_comments_to_replay_gain(struct replay_gain_info *rgi, char **comments)
-{
- const char *temp;
- bool found = false;
-
- replay_gain_info_init(rgi);
-
- while (*comments) {
- if ((temp =
- vorbis_comment_value(*comments, "replaygain_track_gain"))) {
- rgi->tuples[REPLAY_GAIN_TRACK].gain = atof(temp);
- found = true;
- } else if ((temp = vorbis_comment_value(*comments,
- "replaygain_album_gain"))) {
- rgi->tuples[REPLAY_GAIN_ALBUM].gain = atof(temp);
- found = true;
- } else if ((temp = vorbis_comment_value(*comments,
- "replaygain_track_peak"))) {
- rgi->tuples[REPLAY_GAIN_TRACK].peak = atof(temp);
- found = true;
- } else if ((temp = vorbis_comment_value(*comments,
- "replaygain_album_peak"))) {
- rgi->tuples[REPLAY_GAIN_ALBUM].peak = atof(temp);
- found = true;
- }
-
- comments++;
- }
-
- return found;
-}
-
-static const char *VORBIS_COMMENT_TRACK_KEY = "tracknumber";
-static const char *VORBIS_COMMENT_DISC_KEY = "discnumber";
-
-/**
- * Check if the comment's name equals the passed name, and if so, copy
- * the comment value into the tag.
- */
-static bool
-vorbis_copy_comment(struct tag *tag, const char *comment,
- const char *name, enum tag_type tag_type)
-{
- const char *value;
-
- value = vorbis_comment_value(comment, name);
- if (value != NULL) {
- tag_add_item(tag, tag_type, value);
- return true;
- }
-
- return false;
-}
-
-static void
-vorbis_parse_comment(struct tag *tag, const char *comment)
-{
- assert(tag != NULL);
-
- if (vorbis_copy_comment(tag, comment, VORBIS_COMMENT_TRACK_KEY,
- TAG_TRACK) ||
- vorbis_copy_comment(tag, comment, VORBIS_COMMENT_DISC_KEY,
- TAG_DISC) ||
- vorbis_copy_comment(tag, comment, "album artist",
- TAG_ALBUM_ARTIST))
- return;
-
- for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i)
- if (vorbis_copy_comment(tag, comment,
- tag_item_names[i], i))
- return;
-}
-
-static struct tag *
-vorbis_comments_to_tag(char **comments)
-{
- struct tag *tag = tag_new();
-
- while (*comments)
- vorbis_parse_comment(tag, *comments++);
-
- if (tag_is_empty(tag)) {
- tag_free(tag);
- tag = NULL;
- }
-
- return tag;
-}
-
static void
vorbis_send_comments(struct decoder *decoder, struct input_stream *is,
char **comments)
@@ -290,7 +189,7 @@ vorbis_stream_decode(struct decoder *decoder,
/* rewind the stream, because ogg_stream_type_detect() has
moved it */
- input_stream_seek(input_stream, 0, SEEK_SET, NULL);
+ input_stream_lock_seek(input_stream, 0, SEEK_SET, NULL);
if (!vorbis_is_open(&vis, &vf, decoder, input_stream))
return;
@@ -370,24 +269,24 @@ vorbis_stream_decode(struct decoder *decoder,
ov_clear(&vf);
}
-static struct tag *
-vorbis_stream_tag(struct input_stream *is)
+static bool
+vorbis_scan_stream(struct input_stream *is,
+ const struct tag_handler *handler, void *handler_ctx)
{
struct vorbis_input_stream vis;
OggVorbis_File vf;
if (!vorbis_is_open(&vis, &vf, NULL, is))
- return NULL;
+ return false;
- struct tag *tag = vorbis_comments_to_tag(ov_comment(&vf, -1)->user_comments);
+ tag_handler_invoke_duration(handler, handler_ctx,
+ (int)(ov_time_total(&vf, -1) + 0.5));
- if (tag == NULL)
- tag = tag_new();
- tag->time = (int)(ov_time_total(&vf, -1) + 0.5);
+ vorbis_comments_scan(ov_comment(&vf, -1)->user_comments,
+ handler, handler_ctx);
ov_clear(&vf);
-
- return tag;
+ return true;
}
static const char *const vorbis_suffixes[] = {
@@ -409,7 +308,7 @@ static const char *const vorbis_mime_types[] = {
const struct decoder_plugin vorbis_decoder_plugin = {
.name = "vorbis",
.stream_decode = vorbis_stream_decode,
- .stream_tag = vorbis_stream_tag,
+ .scan_stream = vorbis_scan_stream,
.suffixes = vorbis_suffixes,
.mime_types = vorbis_mime_types
};
diff --git a/src/decoder/wavpack_decoder_plugin.c b/src/decoder/wavpack_decoder_plugin.c
index 61026842b..ae85b0e27 100644
--- a/src/decoder/wavpack_decoder_plugin.c
+++ b/src/decoder/wavpack_decoder_plugin.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -22,6 +22,8 @@
#include "audio_check.h"
#include "path.h"
#include "utils.h"
+#include "tag_table.h"
+#include "tag_handler.h"
#include <wavpack/wavpack.h>
#include <glib.h>
@@ -36,10 +38,7 @@
#define ERRORLEN 80
-static struct {
- const char *name;
- enum tag_type type;
-} tagtypes[] = {
+static const struct tag_table wavpack_tags[] = {
{ "artist", TAG_ARTIST },
{ "album", TAG_ALBUM },
{ "title", TAG_TITLE },
@@ -51,6 +50,7 @@ static struct {
{ "performer", TAG_PERFORMER },
{ "comment", TAG_COMMENT },
{ "disc", TAG_DISC },
+ { NULL, TAG_NUM_OF_ITEM_TYPES }
};
/** A pointer type for format converter function. */
@@ -111,12 +111,11 @@ static void
format_samples_float(G_GNUC_UNUSED int bytes_per_sample, void *buffer,
uint32_t count)
{
- int32_t *dst = buffer;
- float *src = buffer;
- assert_static(sizeof(*dst) <= sizeof(*src));
+ float *p = buffer;
while (count--) {
- *dst++ = (int32_t)(*src++ + 0.5f);
+ *p /= (1 << 23);
+ ++p;
}
}
@@ -127,7 +126,7 @@ static enum sample_format
wavpack_bits_to_sample_format(bool is_float, int bytes_per_sample)
{
if (is_float)
- return SAMPLE_FORMAT_S24_P32;
+ return SAMPLE_FORMAT_FLOAT;
switch (bytes_per_sample) {
case 1:
@@ -273,17 +272,41 @@ wavpack_replaygain(struct replay_gain_info *replay_gain_info,
return found;
}
+static void
+wavpack_scan_tag_item(WavpackContext *wpc, const char *name,
+ enum tag_type type,
+ const struct tag_handler *handler, void *handler_ctx)
+{
+ char buffer[1024];
+ int len = WavpackGetTagItem(wpc, name, buffer, sizeof(buffer));
+ if (len <= 0 || (unsigned)len >= sizeof(buffer))
+ return;
+
+ tag_handler_invoke_tag(handler, handler_ctx, type, buffer);
+
+}
+
+static void
+wavpack_scan_pair(WavpackContext *wpc, const char *name,
+ const struct tag_handler *handler, void *handler_ctx)
+{
+ char buffer[8192];
+ int len = WavpackGetTagItem(wpc, name, buffer, sizeof(buffer));
+ if (len <= 0 || (unsigned)len >= sizeof(buffer))
+ return;
+
+ tag_handler_invoke_pair(handler, handler_ctx, name, buffer);
+}
+
/*
* Reads metainfo from the specified file.
*/
-static struct tag *
-wavpack_tagdup(const char *fname)
+static bool
+wavpack_scan_file(const char *fname,
+ const struct tag_handler *handler, void *handler_ctx)
{
WavpackContext *wpc;
- struct tag *tag;
char error[ERRORLEN];
- char *s;
- int size, allocated_size;
wpc = WavpackOpenFileInput(fname, error, OPEN_TAGS, 0);
if (wpc == NULL) {
@@ -291,40 +314,34 @@ wavpack_tagdup(const char *fname)
"failed to open WavPack file \"%s\": %s\n",
fname, error
);
- return NULL;
+ return false;
}
- tag = tag_new();
- tag->time = WavpackGetNumSamples(wpc);
- tag->time /= WavpackGetSampleRate(wpc);
-
- allocated_size = 0;
- s = NULL;
-
- for (unsigned i = 0; i < G_N_ELEMENTS(tagtypes); ++i) {
- size = WavpackGetTagItem(wpc, tagtypes[i].name, NULL, 0);
- if (size > 0) {
- ++size; /* EOS */
-
- if (s == NULL) {
- s = g_malloc(size);
- allocated_size = size;
- } else if (size > allocated_size) {
- char *t = (char *)g_realloc(s, size);
- allocated_size = size;
- s = t;
- }
+ tag_handler_invoke_duration(handler, handler_ctx,
+ WavpackGetNumSamples(wpc) /
+ WavpackGetSampleRate(wpc));
- WavpackGetTagItem(wpc, tagtypes[i].name, s, size);
- tag_add_item(tag, tagtypes[i].type, s);
+ for (const struct tag_table *i = wavpack_tags; i->name != NULL; ++i)
+ wavpack_scan_tag_item(wpc, i->name, i->type,
+ handler, handler_ctx);
+
+ if (handler->pair != NULL) {
+ char name[64];
+
+ for (int i = 0, n = WavpackGetNumTagItems(wpc);
+ i < n; ++i) {
+ int len = WavpackGetTagItemIndexed(wpc, i, name,
+ sizeof(name));
+ if (len <= 0 || (unsigned)len >= sizeof(name))
+ continue;
+
+ wavpack_scan_pair(wpc, name, handler, handler_ctx);
}
}
- g_free(s);
-
WavpackCloseFile(wpc);
- return tag;
+ return true;
}
/*
@@ -390,13 +407,15 @@ wavpack_input_get_pos(void *id)
static int
wavpack_input_set_pos_abs(void *id, uint32_t pos)
{
- return input_stream_seek(wpin(id)->is, pos, SEEK_SET, NULL) ? 0 : -1;
+ return input_stream_lock_seek(wpin(id)->is, pos, SEEK_SET, NULL)
+ ? 0 : -1;
}
static int
wavpack_input_set_pos_rel(void *id, int32_t delta, int mode)
{
- return input_stream_seek(wpin(id)->is, delta, mode, NULL) ? 0 : -1;
+ return input_stream_lock_seek(wpin(id)->is, delta, mode, NULL)
+ ? 0 : -1;
}
static int
@@ -447,6 +466,7 @@ wavpack_input_init(struct wavpack_input *isp, struct decoder *decoder,
static struct input_stream *
wavpack_open_wvc(struct decoder *decoder, const char *uri,
+ GMutex *mutex, GCond *cond,
struct wavpack_input *wpi)
{
struct input_stream *is_wvc;
@@ -462,7 +482,7 @@ wavpack_open_wvc(struct decoder *decoder, const char *uri,
return false;
wvc_url = g_strconcat(uri, "c", NULL);
- is_wvc = input_stream_open(wvc_url, NULL);
+ is_wvc = input_stream_open(wvc_url, mutex, cond, NULL);
g_free(wvc_url);
if (is_wvc == NULL)
@@ -499,7 +519,8 @@ wavpack_streamdecode(struct decoder * decoder, struct input_stream *is)
struct wavpack_input isp, isp_wvc;
bool can_seek = is->seekable;
- is_wvc = wavpack_open_wvc(decoder, is->uri, &isp_wvc);
+ is_wvc = wavpack_open_wvc(decoder, is->uri, is->mutex, is->cond,
+ &isp_wvc);
if (is_wvc != NULL) {
open_flags |= OPEN_WVC;
can_seek &= is_wvc->seekable;
@@ -573,7 +594,7 @@ const struct decoder_plugin wavpack_decoder_plugin = {
.name = "wavpack",
.stream_decode = wavpack_streamdecode,
.file_decode = wavpack_filedecode,
- .tag_dup = wavpack_tagdup,
+ .scan_file = wavpack_scan_file,
.suffixes = wavpack_suffixes,
.mime_types = wavpack_mime_types
};
diff --git a/src/decoder/wildmidi_decoder_plugin.c b/src/decoder/wildmidi_decoder_plugin.c
index 66e6c61cf..a2224940d 100644
--- a/src/decoder/wildmidi_decoder_plugin.c
+++ b/src/decoder/wildmidi_decoder_plugin.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -19,6 +19,7 @@
#include "config.h"
#include "decoder_api.h"
+#include "tag_handler.h"
#include <glib.h>
@@ -111,25 +112,26 @@ wildmidi_file_decode(struct decoder *decoder, const char *path_fs)
WildMidi_Close(wm);
}
-static struct tag *
-wildmidi_tag_dup(const char *path_fs)
+static bool
+wildmidi_scan_file(const char *path_fs,
+ const struct tag_handler *handler, void *handler_ctx)
{
midi *wm = WildMidi_Open(path_fs);
if (wm == NULL)
- return NULL;
+ return false;
const struct _WM_Info *info = WildMidi_GetInfo(wm);
if (info == NULL) {
WildMidi_Close(wm);
- return NULL;
+ return false;
}
- struct tag *tag = tag_new();
- tag->time = info->approx_total_samples / WILDMIDI_SAMPLE_RATE;
+ int duration = info->approx_total_samples / WILDMIDI_SAMPLE_RATE;
+ tag_handler_invoke_duration(handler, handler_ctx, duration);
WildMidi_Close(wm);
- return tag;
+ return true;
}
static const char *const wildmidi_suffixes[] = {
@@ -142,6 +144,6 @@ const struct decoder_plugin wildmidi_decoder_plugin = {
.init = wildmidi_init,
.finish = wildmidi_finish,
.file_decode = wildmidi_file_decode,
- .tag_dup = wildmidi_tag_dup,
+ .scan_file = wildmidi_scan_file,
.suffixes = wildmidi_suffixes,
};
diff --git a/src/decoder_api.c b/src/decoder_api.c
index 19de47855..a45d0f1e6 100644
--- a/src/decoder_api.c
+++ b/src/decoder_api.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -21,8 +21,7 @@
#include "decoder_api.h"
#include "decoder_internal.h"
#include "decoder_control.h"
-#include "player_control.h"
-#include "audio.h"
+#include "audio_config.h"
#include "song.h"
#include "buffer.h"
#include "pipe.h"
@@ -63,10 +62,9 @@ decoder_initialized(struct decoder *decoder,
decoder_lock(dc);
dc->state = DECODE_STATE_DECODE;
+ g_cond_signal(dc->client_cond);
decoder_unlock(dc);
- player_lock_signal();
-
g_debug("audio_format=%s, seekable=%s",
audio_format_to_string(&dc->in_audio_format, &af_string),
seekable ? "true" : "false");
@@ -189,9 +187,8 @@ decoder_command_finished(struct decoder *decoder)
}
dc->command = DECODE_COMMAND_NONE;
+ g_cond_signal(dc->client_cond);
decoder_unlock(dc);
-
- player_lock_signal();
}
double decoder_seek_where(G_GNUC_UNUSED struct decoder * decoder)
@@ -231,50 +228,73 @@ void decoder_seek_error(struct decoder * decoder)
decoder_command_finished(decoder);
}
+/**
+ * Should be read operation be cancelled? That is the case when the
+ * player thread has sent a command such as "STOP".
+ */
+G_GNUC_PURE
+static inline bool
+decoder_check_cancel_read(const struct decoder *decoder)
+{
+ if (decoder == NULL)
+ return false;
+
+ const struct decoder_control *dc = decoder->dc;
+ if (dc->command == DECODE_COMMAND_NONE)
+ return false;
+
+ /* ignore the SEEK command during initialization, the plugin
+ should handle that after it has initialized successfully */
+ if (dc->command == DECODE_COMMAND_SEEK &&
+ (dc->state == DECODE_STATE_START || decoder->seeking))
+ return false;
+
+ return true;
+}
+
size_t decoder_read(struct decoder *decoder,
struct input_stream *is,
void *buffer, size_t length)
{
- const struct decoder_control *dc =
- decoder != NULL ? decoder->dc : NULL;
+ /* XXX don't allow decoder==NULL */
GError *error = NULL;
size_t nbytes;
assert(decoder == NULL ||
- dc->state == DECODE_STATE_START ||
- dc->state == DECODE_STATE_DECODE);
+ decoder->dc->state == DECODE_STATE_START ||
+ decoder->dc->state == DECODE_STATE_DECODE);
assert(is != NULL);
assert(buffer != NULL);
if (length == 0)
return 0;
+ input_stream_lock(is);
+
while (true) {
- /* XXX don't allow decoder==NULL */
- if (decoder != NULL &&
- /* ignore the SEEK command during initialization,
- the plugin should handle that after it has
- initialized successfully */
- (dc->command != DECODE_COMMAND_SEEK ||
- (dc->state != DECODE_STATE_START && !decoder->seeking)) &&
- dc->command != DECODE_COMMAND_NONE)
+ if (decoder_check_cancel_read(decoder)) {
+ input_stream_unlock(is);
return 0;
+ }
- nbytes = input_stream_read(is, buffer, length, &error);
+ if (input_stream_available(is))
+ break;
- if (G_UNLIKELY(nbytes == 0 && error != NULL)) {
- g_warning("%s", error->message);
- g_error_free(error);
- return 0;
- }
+ g_cond_wait(is->cond, is->mutex);
+ }
- if (nbytes > 0 || input_stream_eof(is))
- return nbytes;
+ nbytes = input_stream_read(is, buffer, length, &error);
+ assert(nbytes == 0 || error == NULL);
+ assert(nbytes > 0 || error != NULL || input_stream_eof(is));
- /* sleep for a fraction of a second! */
- /* XXX don't sleep, wait for an event instead */
- g_usleep(10000);
+ if (G_UNLIKELY(nbytes == 0 && error != NULL)) {
+ g_warning("%s", error->message);
+ g_error_free(error);
}
+
+ input_stream_unlock(is);
+
+ return nbytes;
}
void
@@ -291,8 +311,7 @@ decoder_timestamp(struct decoder *decoder, double t)
* (decoder.chunk) if there is one.
*/
static enum decoder_command
-do_send_tag(struct decoder *decoder, struct input_stream *is,
- const struct tag *tag)
+do_send_tag(struct decoder *decoder, const struct tag *tag)
{
struct music_chunk *chunk;
@@ -300,12 +319,12 @@ do_send_tag(struct decoder *decoder, struct input_stream *is,
/* there is a partial chunk - flush it, we want the
tag in a new chunk */
decoder_flush_chunk(decoder);
- player_lock_signal();
+ g_cond_signal(decoder->dc->client_cond);
}
assert(decoder->chunk == NULL);
- chunk = decoder_get_chunk(decoder, is);
+ chunk = decoder_get_chunk(decoder);
if (chunk == NULL) {
assert(decoder->dc->command != DECODE_COMMAND_NONE);
return decoder->dc->command;
@@ -321,7 +340,7 @@ update_stream_tag(struct decoder *decoder, struct input_stream *is)
struct tag *tag;
tag = is != NULL
- ? input_stream_tag(is)
+ ? input_stream_lock_tag(is)
: NULL;
if (tag == NULL) {
tag = decoder->song_tag;
@@ -372,11 +391,11 @@ decoder_data(struct decoder *decoder,
tag = tag_merge(decoder->decoder_tag,
decoder->stream_tag);
- cmd = do_send_tag(decoder, is, tag);
+ cmd = do_send_tag(decoder, tag);
tag_free(tag);
} else
/* send only the stream tag */
- cmd = do_send_tag(decoder, is, decoder->stream_tag);
+ cmd = do_send_tag(decoder, decoder->stream_tag);
if (cmd != DECODE_COMMAND_NONE)
return cmd;
@@ -402,7 +421,7 @@ decoder_data(struct decoder *decoder,
size_t nbytes;
bool full;
- chunk = decoder_get_chunk(decoder, is);
+ chunk = decoder_get_chunk(decoder);
if (chunk == NULL) {
assert(dc->command != DECODE_COMMAND_NONE);
return dc->command;
@@ -415,7 +434,7 @@ decoder_data(struct decoder *decoder,
if (dest == NULL) {
/* the chunk is full, flush it */
decoder_flush_chunk(decoder);
- player_lock_signal();
+ g_cond_signal(dc->client_cond);
continue;
}
@@ -434,7 +453,7 @@ decoder_data(struct decoder *decoder,
if (full) {
/* the chunk is full, flush it */
decoder_flush_chunk(decoder);
- player_lock_signal();
+ g_cond_signal(dc->client_cond);
}
data += nbytes;
@@ -489,11 +508,11 @@ decoder_tag(G_GNUC_UNUSED struct decoder *decoder, struct input_stream *is,
struct tag *merged;
merged = tag_merge(decoder->stream_tag, decoder->decoder_tag);
- cmd = do_send_tag(decoder, is, merged);
+ cmd = do_send_tag(decoder, merged);
tag_free(merged);
} else
/* send only the decoder tag */
- cmd = do_send_tag(decoder, is, tag);
+ cmd = do_send_tag(decoder, tag);
return cmd;
}
@@ -526,7 +545,7 @@ decoder_replay_gain(struct decoder *decoder,
replay gain values affect the following
samples */
decoder_flush_chunk(decoder);
- player_lock_signal();
+ g_cond_signal(decoder->dc->client_cond);
}
} else
decoder->replay_gain_serial = 0;
diff --git a/src/decoder_api.h b/src/decoder_api.h
index 8b5f3d82b..6e011c395 100644
--- a/src/decoder_api.h
+++ b/src/decoder_api.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -83,7 +83,7 @@ double
decoder_seek_where(struct decoder *decoder);
/**
- * Call this right before decoder_command_finished() when seeking has
+ * Call this instead of decoder_command_finished() when seeking has
* failed.
*
* @param decoder the decoder object
diff --git a/src/decoder_buffer.c b/src/decoder_buffer.c
index 8f8eb8545..fcb135976 100644
--- a/src/decoder_buffer.c
+++ b/src/decoder_buffer.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/decoder_buffer.h b/src/decoder_buffer.h
index b6051e122..77eff5dd1 100644
--- a/src/decoder_buffer.h
+++ b/src/decoder_buffer.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/decoder_command.h b/src/decoder_command.h
index 4a2e49f3e..795e13fb2 100644
--- a/src/decoder_command.h
+++ b/src/decoder_command.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/decoder_control.c b/src/decoder_control.c
index 85c2e5ba8..70f34b331 100644
--- a/src/decoder_control.c
+++ b/src/decoder_control.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -19,7 +19,6 @@
#include "config.h"
#include "decoder_control.h"
-#include "player_control.h"
#include "pipe.h"
#include <assert.h>
@@ -27,13 +26,16 @@
#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "decoder_control"
-void
-dc_init(struct decoder_control *dc)
+struct decoder_control *
+dc_new(GCond *client_cond)
{
+ struct decoder_control *dc = g_new(struct decoder_control, 1);
+
dc->thread = NULL;
dc->mutex = g_mutex_new();
dc->cond = g_cond_new();
+ dc->client_cond = client_cond;
dc->state = DECODE_STATE_STOP;
dc->command = DECODE_COMMAND_NONE;
@@ -43,34 +45,26 @@ dc_init(struct decoder_control *dc)
dc->mixramp_start = NULL;
dc->mixramp_end = NULL;
dc->mixramp_prev_end = NULL;
+
+ return dc;
}
void
-dc_deinit(struct decoder_control *dc)
+dc_free(struct decoder_control *dc)
{
g_cond_free(dc->cond);
g_mutex_free(dc->mutex);
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;
+ g_free(dc);
}
static void
dc_command_wait_locked(struct decoder_control *dc)
{
while (dc->command != DECODE_COMMAND_NONE)
- player_wait_decoder(dc);
-}
-
-void
-dc_command_wait(struct decoder_control *dc)
-{
- decoder_lock(dc);
- dc_command_wait_locked(dc);
- decoder_unlock(dc);
+ g_cond_wait(dc->client_cond, dc->mutex);
}
static void
diff --git a/src/decoder_control.h b/src/decoder_control.h
index 64c7c302e..566b153ee 100644
--- a/src/decoder_control.h
+++ b/src/decoder_control.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -58,6 +58,12 @@ struct decoder_control {
*/
GCond *cond;
+ /**
+ * The trigger of this object's client. It is signalled
+ * whenever an event occurs.
+ */
+ GCond *client_cond;
+
enum decoder_state state;
enum decoder_command command;
@@ -114,11 +120,12 @@ struct decoder_control {
char *mixramp_prev_end;
};
-void
-dc_init(struct decoder_control *dc);
+G_GNUC_MALLOC
+struct decoder_control *
+dc_new(GCond *client_cond);
void
-dc_deinit(struct decoder_control *dc);
+dc_free(struct decoder_control *dc);
/**
* Locks the #decoder_control object.
@@ -234,9 +241,6 @@ decoder_current_song(const struct decoder_control *dc)
return NULL;
}
-void
-dc_command_wait(struct decoder_control *dc);
-
/**
* Start the decoder.
*
diff --git a/src/decoder_internal.c b/src/decoder_internal.c
index 990d728e9..bc349f2ff 100644
--- a/src/decoder_internal.c
+++ b/src/decoder_internal.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -20,7 +20,6 @@
#include "config.h"
#include "decoder_internal.h"
#include "decoder_control.h"
-#include "player_control.h"
#include "pipe.h"
#include "input_stream.h"
#include "buffer.h"
@@ -29,43 +28,19 @@
#include <assert.h>
/**
- * This is a wrapper for input_stream_buffer(). It assumes that the
- * decoder is currently locked, and temporarily unlocks it while
- * calling input_stream_buffer(). We shouldn't hold the lock during a
- * potentially blocking operation.
- */
-static bool
-decoder_input_buffer(struct decoder_control *dc, struct input_stream *is)
-{
- GError *error = NULL;
- int ret;
-
- decoder_unlock(dc);
- ret = input_stream_buffer(is, &error);
- if (ret < 0) {
- g_warning("%s", error->message);
- g_error_free(error);
- }
-
- decoder_lock(dc);
-
- return ret > 0;
-}
-
-/**
* All chunks are full of decoded data; wait for the player to free
* one.
*/
static enum decoder_command
-need_chunks(struct decoder_control *dc, struct input_stream *is, bool do_wait)
+need_chunks(struct decoder_control *dc, bool do_wait)
{
if (dc->command == DECODE_COMMAND_STOP ||
dc->command == DECODE_COMMAND_SEEK)
return dc->command;
- if ((is == NULL || !decoder_input_buffer(dc, is)) && do_wait) {
+ if (do_wait) {
decoder_wait(dc);
- player_signal();
+ g_cond_signal(dc->client_cond);
return dc->command;
}
@@ -74,7 +49,7 @@ need_chunks(struct decoder_control *dc, struct input_stream *is, bool do_wait)
}
struct music_chunk *
-decoder_get_chunk(struct decoder *decoder, struct input_stream *is)
+decoder_get_chunk(struct decoder *decoder)
{
struct decoder_control *dc = decoder->dc;
enum decoder_command cmd;
@@ -97,7 +72,7 @@ decoder_get_chunk(struct decoder *decoder, struct input_stream *is)
}
decoder_lock(dc);
- cmd = need_chunks(dc, is, true);
+ cmd = need_chunks(dc, true);
decoder_unlock(dc);
} while (cmd == DECODE_COMMAND_NONE);
diff --git a/src/decoder_internal.h b/src/decoder_internal.h
index 5818632e5..d89e68cfc 100644
--- a/src/decoder_internal.h
+++ b/src/decoder_internal.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -89,7 +89,7 @@ struct decoder {
* @return the chunk, or NULL if we have received a decoder command
*/
struct music_chunk *
-decoder_get_chunk(struct decoder *decoder, struct input_stream *is);
+decoder_get_chunk(struct decoder *decoder);
/**
* Flushes the current chunk.
diff --git a/src/decoder_list.c b/src/decoder_list.c
index d76050023..2029b3e62 100644
--- a/src/decoder_list.c
+++ b/src/decoder_list.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -23,6 +23,8 @@
#include "utils.h"
#include "conf.h"
#include "mpd_error.h"
+#include "decoder/pcm_decoder_plugin.h"
+#include "decoder/dsdiff_decoder_plugin.h"
#include <glib.h>
@@ -57,7 +59,7 @@ const struct decoder_plugin *const decoder_plugins[] = {
#ifdef ENABLE_VORBIS_DECODER
&vorbis_decoder_plugin,
#endif
-#if defined(HAVE_FLAC) || defined(HAVE_OGGFLAC)
+#if defined(HAVE_FLAC)
&oggflac_decoder_plugin,
#endif
#ifdef HAVE_FLAC
@@ -69,6 +71,7 @@ const struct decoder_plugin *const decoder_plugins[] = {
#ifdef HAVE_AUDIOFILE
&audiofile_decoder_plugin,
#endif
+ &dsdiff_decoder_plugin,
#ifdef HAVE_FAAD
&faad_decoder_plugin,
#endif
@@ -102,6 +105,7 @@ const struct decoder_plugin *const decoder_plugins[] = {
#ifdef HAVE_GME
&gme_decoder_plugin,
#endif
+ &pcm_decoder_plugin,
NULL
};
diff --git a/src/decoder_list.h b/src/decoder_list.h
index 7041db0c9..d259cb195 100644
--- a/src/decoder_list.h
+++ b/src/decoder_list.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/decoder_plugin.c b/src/decoder_plugin.c
index 062dad364..d32043f0e 100644
--- a/src/decoder_plugin.c
+++ b/src/decoder_plugin.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -19,7 +19,7 @@
#include "config.h"
#include "decoder_plugin.h"
-#include "utils.h"
+#include "string_util.h"
#include <assert.h>
diff --git a/src/decoder_plugin.h b/src/decoder_plugin.h
index d8371ddb8..933ba6751 100644
--- a/src/decoder_plugin.h
+++ b/src/decoder_plugin.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -26,6 +26,7 @@
struct config_param;
struct input_stream;
struct tag;
+struct tag_handler;
/**
* Opaque handle which the decoder plugin passes to the functions in
@@ -70,18 +71,22 @@ struct decoder_plugin {
void (*file_decode)(struct decoder *decoder, const char *path_fs);
/**
- * Read the tags of a local file.
+ * Scan metadata of a file.
*
- * @return NULL if the operation has failed
+ * @return false if the operation has failed
*/
- struct tag *(*tag_dup)(const char *path_fs);
+ bool (*scan_file)(const char *path_fs,
+ const struct tag_handler *handler,
+ void *handler_ctx);
/**
- * Read the tags of a stream.
+ * Scan metadata of a file.
*
- * @return NULL if the operation has failed
+ * @return false if the operation has failed
*/
- struct tag *(*stream_tag)(struct input_stream *is);
+ bool (*scan_stream)(struct input_stream *is,
+ const struct tag_handler *handler,
+ void *handler_ctx);
/**
* @brief Return a "virtual" filename for subtracks in
@@ -150,25 +155,28 @@ decoder_plugin_file_decode(const struct decoder_plugin *plugin,
/**
* Read the tag of a file.
*/
-static inline struct tag *
-decoder_plugin_tag_dup(const struct decoder_plugin *plugin,
- const char *path_fs)
+static inline bool
+decoder_plugin_scan_file(const struct decoder_plugin *plugin,
+ const char *path_fs,
+ const struct tag_handler *handler, void *handler_ctx)
{
- return plugin->tag_dup != NULL
- ? plugin->tag_dup(path_fs)
- : NULL;
+ return plugin->scan_file != NULL
+ ? plugin->scan_file(path_fs, handler, handler_ctx)
+ : false;
}
/**
* Read the tag of a stream.
*/
-static inline struct tag *
-decoder_plugin_stream_tag(const struct decoder_plugin *plugin,
- struct input_stream *is)
+static inline bool
+decoder_plugin_scan_stream(const struct decoder_plugin *plugin,
+ struct input_stream *is,
+ const struct tag_handler *handler,
+ void *handler_ctx)
{
- return plugin->stream_tag != NULL
- ? plugin->stream_tag(is)
- : NULL;
+ return plugin->scan_stream != NULL
+ ? plugin->scan_stream(is, handler, handler_ctx)
+ : false;
}
/**
diff --git a/src/decoder_print.c b/src/decoder_print.c
index a1c2da2e5..72c40ac75 100644
--- a/src/decoder_print.c
+++ b/src/decoder_print.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/decoder_print.h b/src/decoder_print.h
index 520438871..31713d5d8 100644
--- a/src/decoder_print.h
+++ b/src/decoder_print.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/decoder_thread.c b/src/decoder_thread.c
index bb3ef9348..421efd32a 100644
--- a/src/decoder_thread.c
+++ b/src/decoder_thread.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -26,7 +26,6 @@
#include "decoder_api.h"
#include "replay_gain_ape.h"
#include "input_stream.h"
-#include "player_control.h"
#include "pipe.h"
#include "song.h"
#include "tag.h"
@@ -43,16 +42,20 @@
#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "decoder_thread"
-static enum decoder_command
-decoder_lock_get_command(struct decoder_control *dc)
+/**
+ * Marks the current decoder command as "finished" and notifies the
+ * player thread.
+ *
+ * @param dc the #decoder_control object; must be locked
+ */
+static void
+decoder_command_finished_locked(struct decoder_control *dc)
{
- enum decoder_command command;
+ assert(dc->command != DECODE_COMMAND_NONE);
- decoder_lock(dc);
- command = dc->command;
- decoder_unlock(dc);
+ dc->command = DECODE_COMMAND_NONE;
- return command;
+ g_cond_signal(dc->client_cond);
}
/**
@@ -72,7 +75,7 @@ decoder_input_stream_open(struct decoder_control *dc, const char *uri)
GError *error = NULL;
struct input_stream *is;
- is = input_stream_open(uri, &error);
+ is = input_stream_open(uri, dc->mutex, dc->cond, &error);
if (is == NULL) {
if (error != NULL) {
g_warning("%s", error->message);
@@ -85,19 +88,27 @@ decoder_input_stream_open(struct decoder_control *dc, const char *uri)
/* wait for the input stream to become ready; its metadata
will be available then */
+ decoder_lock(dc);
+
+ input_stream_update(is);
while (!is->ready &&
- decoder_lock_get_command(dc) != DECODE_COMMAND_STOP) {
- int ret;
+ dc->command != DECODE_COMMAND_STOP) {
+ decoder_wait(dc);
- ret = input_stream_buffer(is, &error);
- if (ret < 0) {
- input_stream_close(is);
- g_warning("%s", error->message);
- g_error_free(error);
- return NULL;
- }
+ input_stream_update(is);
}
+ if (!input_stream_check(is, &error)) {
+ decoder_unlock(dc);
+
+ g_warning("%s", error->message);
+ g_error_free(error);
+
+ return NULL;
+ }
+
+ decoder_unlock(dc);
+
return is;
}
@@ -118,11 +129,11 @@ decoder_stream_decode(const struct decoder_plugin *plugin,
if (decoder->dc->command == DECODE_COMMAND_STOP)
return true;
- decoder_unlock(decoder->dc);
-
/* rewind the stream, so each plugin gets a fresh start */
input_stream_seek(input_stream, 0, SEEK_SET, NULL);
+ decoder_unlock(decoder->dc);
+
decoder_plugin_stream_decode(plugin, decoder, input_stream);
decoder_lock(decoder->dc);
@@ -384,9 +395,8 @@ decoder_run_song(struct decoder_control *dc,
decoder.chunk = NULL;
dc->state = DECODE_STATE_START;
- dc->command = DECODE_COMMAND_NONE;
- player_signal();
+ decoder_command_finished_locked(dc);
pcm_convert_init(&decoder.conv_state);
@@ -432,6 +442,7 @@ decoder_run(struct decoder_control *dc)
if (uri == NULL) {
dc->state = DECODE_STATE_ERROR;
+ decoder_command_finished_locked(dc);
return;
}
@@ -464,16 +475,10 @@ decoder_task(gpointer arg)
case DECODE_COMMAND_SEEK:
decoder_run(dc);
-
- dc->command = DECODE_COMMAND_NONE;
-
- player_signal();
break;
case DECODE_COMMAND_STOP:
- dc->command = DECODE_COMMAND_NONE;
-
- player_signal();
+ decoder_command_finished_locked(dc);
break;
case DECODE_COMMAND_NONE:
diff --git a/src/decoder_thread.h b/src/decoder_thread.h
index 28042d7f8..78f12a54a 100644
--- a/src/decoder_thread.h
+++ b/src/decoder_thread.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/despotify_utils.c b/src/despotify_utils.c
new file mode 100644
index 000000000..7555d105d
--- /dev/null
+++ b/src/despotify_utils.c
@@ -0,0 +1,121 @@
+#include <glib.h>
+#include <despotify.h>
+
+#include "tag.h"
+#include "conf.h"
+#include "despotify_utils.h"
+
+static struct despotify_session *g_session;
+static void (*registered_callbacks[8])(struct despotify_session *,
+ int, void *, void *);
+static void *registered_callback_data[8];
+
+static void callback(struct despotify_session* ds, int sig,
+ void* data, G_GNUC_UNUSED void* callback_data)
+{
+ size_t i;
+
+ for (i = 0; i < sizeof(registered_callbacks) / sizeof(registered_callbacks[0]); i++) {
+ void (*cb)(struct despotify_session *, int, void *, void *) = registered_callbacks[i];
+ void *cb_data = registered_callback_data[i];
+
+ if (cb)
+ cb(ds, sig, data, cb_data);
+ }
+}
+
+bool mpd_despotify_register_callback(void (*cb)(struct despotify_session *, int, void *, void *),
+ void *cb_data)
+{
+ size_t i;
+
+ for (i = 0; i < sizeof(registered_callbacks) / sizeof(registered_callbacks[0]); i++) {
+
+ if (!registered_callbacks[i]) {
+ registered_callbacks[i] = cb;
+ registered_callback_data[i] = cb_data;
+
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void mpd_despotify_unregister_callback(void (*cb)(struct despotify_session *, int, void *, void *))
+{
+ size_t i;
+
+ for (i = 0; i < sizeof(registered_callbacks) / sizeof(registered_callbacks[0]); i++) {
+
+ if (registered_callbacks[i] == cb) {
+ registered_callbacks[i] = NULL;
+ }
+ }
+}
+
+
+struct tag *mpd_despotify_tag_from_track(struct ds_track *track)
+{
+ char tracknum[20];
+ char comment[80];
+ char date[20];
+ struct tag *tag;
+
+ tag = tag_new();
+
+ if (!track->has_meta_data)
+ return tag;
+
+ g_snprintf(tracknum, sizeof(tracknum), "%d", track->tracknumber);
+ g_snprintf(date, sizeof(date), "%d", track->year);
+ g_snprintf(comment, sizeof(comment), "Bitrate %d Kbps, %sgeo restricted",
+ track->file_bitrate / 1000, track->geo_restricted ? "" : "not ");
+ tag_add_item(tag, TAG_TITLE, track->title);
+ tag_add_item(tag, TAG_ARTIST, track->artist->name);
+ tag_add_item(tag, TAG_TRACK, tracknum);
+ tag_add_item(tag, TAG_ALBUM, track->album);
+ tag_add_item(tag, TAG_DATE, date);
+ tag_add_item(tag, TAG_COMMENT, comment);
+ tag->time = track->length / 1000;
+
+ return tag;
+}
+
+struct despotify_session *mpd_despotify_get_session(void)
+{
+ const char *user;
+ const char *passwd;
+ bool high_bitrate;
+
+ if (g_session)
+ return g_session;
+
+ user = config_get_string(CONF_DESPOTIFY_USER, NULL);
+ passwd = config_get_string(CONF_DESPOTIFY_PASSWORD, NULL);
+ high_bitrate = config_get_bool(CONF_DESPOTIFY_HIGH_BITRATE, true);
+
+ if (user == NULL || passwd == NULL) {
+ g_debug("disabling despotify because account is not configured");
+ return NULL;
+ }
+ if (!despotify_init()) {
+ g_debug("Can't initialize despotify\n");
+ return false;
+ }
+
+ g_session = despotify_init_client(callback, NULL,
+ high_bitrate, true);
+ if (!g_session) {
+ g_debug("Can't initialize despotify client\n");
+ return false;
+ }
+
+ if (!despotify_authenticate(g_session, user, passwd)) {
+ g_debug("Can't authenticate despotify session\n");
+ despotify_exit(g_session);
+ return false;
+ }
+
+ return g_session;
+}
diff --git a/src/despotify_utils.h b/src/despotify_utils.h
new file mode 100644
index 000000000..7e35edc3c
--- /dev/null
+++ b/src/despotify_utils.h
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_DESPOTIFY_H
+#define MPD_DESPOTIFY_H
+
+struct despotify_session;
+struct ds_track;
+
+/**
+ * Return the current despotify session.
+ *
+ * If the session isn't initialized, this function will initialize
+ * it and connect to Spotify.
+ *
+ * @return a pointer to the despotify session, or NULL if it can't
+ * be initialized (e.g., if the configuration isn't supplied)
+ */
+struct despotify_session *mpd_despotify_get_session(void);
+
+/**
+ * Create a MPD tags structure from a spotify track
+ *
+ * @param track the track to convert
+ *
+ * @return a pointer to the filled in tags structure
+ */
+struct tag *mpd_despotify_tag_from_track(struct ds_track *track);
+
+/**
+ * Register a despotify callback.
+ *
+ * Despotify calls this e.g., when a track ends.
+ *
+ * @param cb the callback
+ * @param cb_data the data to pass to the callback
+ *
+ * @return true if the callback could be registered
+ */
+bool mpd_despotify_register_callback(void (*cb)(struct despotify_session *, int, void *, void *),
+ void *cb_data);
+
+/**
+ * Unregister a despotify callback.
+ *
+ * @param cb the callback to unregister.
+ */
+void mpd_despotify_unregister_callback(void (*cb)(struct despotify_session *, int, void *, void *));
+
+#endif
+
diff --git a/src/directory.c b/src/directory.c
index 1e3a4cf28..930881129 100644
--- a/src/directory.c
+++ b/src/directory.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -20,7 +20,12 @@
#include "config.h"
#include "directory.h"
#include "song.h"
+#include "song_sort.h"
+#include "playlist_vector.h"
#include "path.h"
+#include "util/list_sort.h"
+#include "db_visitor.h"
+#include "db_lock.h"
#include <glib.h>
@@ -39,11 +44,13 @@ directory_new(const char *path, struct directory *parent)
directory = g_malloc0(sizeof(*directory) -
sizeof(directory->path) + pathlen + 1);
+ INIT_LIST_HEAD(&directory->children);
+ INIT_LIST_HEAD(&directory->songs);
+ INIT_LIST_HEAD(&directory->playlists);
+
directory->parent = parent;
memcpy(directory->path, path, pathlen + 1);
- playlist_vector_init(&directory->playlists);
-
return directory;
}
@@ -52,19 +59,30 @@ directory_free(struct directory *directory)
{
playlist_vector_deinit(&directory->playlists);
- for (unsigned i = 0; i < directory->songs.nr; ++i)
- song_free(directory->songs.base[i]);
+ struct song *song, *ns;
+ directory_for_each_song_safe(song, ns, directory)
+ song_free(song);
- for (unsigned i = 0; i < directory->children.nr; ++i)
- directory_free(directory->children.base[i]);
+ struct directory *child, *n;
+ directory_for_each_child_safe(child, n, directory)
+ directory_free(child);
- dirvec_destroy(&directory->children);
- songvec_destroy(&directory->songs);
g_free(directory);
/* this resets last dir returned */
/*directory_get_path(NULL); */
}
+void
+directory_delete(struct directory *directory)
+{
+ assert(holding_db_lock());
+ assert(directory != NULL);
+ assert(directory->parent != NULL);
+
+ list_del(&directory->siblings);
+ directory_free(directory);
+}
+
const char *
directory_get_name(const struct directory *directory)
{
@@ -79,65 +97,137 @@ directory_get_name(const struct directory *directory)
: directory->path;
}
+struct directory *
+directory_new_child(struct directory *parent, const char *name_utf8)
+{
+ assert(holding_db_lock());
+ assert(parent != NULL);
+ assert(name_utf8 != NULL);
+ assert(*name_utf8 != 0);
+
+ char *allocated;
+ const char *path_utf8;
+ if (directory_is_root(parent)) {
+ allocated = NULL;
+ path_utf8 = name_utf8;
+ } else {
+ allocated = g_strconcat(directory_get_path(parent),
+ "/", name_utf8, NULL);
+ path_utf8 = allocated;
+ }
+
+ struct directory *directory = directory_new(path_utf8, parent);
+ g_free(allocated);
+
+ list_add_tail(&directory->siblings, &parent->children);
+ return directory;
+}
+
+struct directory *
+directory_get_child(const struct directory *directory, const char *name)
+{
+ assert(holding_db_lock());
+
+ struct directory *child;
+ directory_for_each_child(child, directory)
+ if (strcmp(directory_get_name(child), name) == 0)
+ return child;
+
+ return NULL;
+}
+
void
directory_prune_empty(struct directory *directory)
{
- int i;
- struct dirvec *dv = &directory->children;
-
- for (i = dv->nr; --i >= 0; ) {
- struct directory *child = dv->base[i];
+ assert(holding_db_lock());
+ struct directory *child, *n;
+ directory_for_each_child_safe(child, n, directory) {
directory_prune_empty(child);
- if (directory_is_empty(child)) {
- dirvec_delete(dv, child);
- directory_free(child);
- }
+ if (directory_is_empty(child))
+ directory_delete(child);
}
- if (!dv->nr)
- dirvec_destroy(dv);
}
struct directory *
directory_lookup_directory(struct directory *directory, const char *uri)
{
- struct directory *cur = directory;
- struct directory *found = NULL;
- char *duplicated;
- char *locate;
-
+ assert(holding_db_lock());
assert(uri != NULL);
if (isRootDirectory(uri))
return directory;
- duplicated = g_strdup(uri);
- locate = strchr(duplicated, '/');
+ char *duplicated = g_strdup(uri), *name = duplicated;
+
while (1) {
- if (locate)
- *locate = '\0';
- if (!(found = directory_get_child(cur, duplicated)))
+ char *slash = strchr(name, '/');
+ if (slash == name) {
+ directory = NULL;
break;
- assert(cur == found->parent);
- cur = found;
- if (!locate)
+ }
+
+ if (slash != NULL)
+ *slash = '\0';
+
+ directory = directory_get_child(directory, name);
+ if (directory == NULL || slash == NULL)
break;
- *locate = '/';
- locate = strchr(locate + 1, '/');
+
+ name = slash + 1;
}
g_free(duplicated);
- return found;
+ return directory;
+}
+
+void
+directory_add_song(struct directory *directory, struct song *song)
+{
+ assert(directory != NULL);
+ assert(song != NULL);
+ assert(song->parent == directory);
+
+ list_add_tail(&song->siblings, &directory->songs);
+}
+
+void
+directory_remove_song(G_GNUC_UNUSED struct directory *directory,
+ struct song *song)
+{
+ assert(directory != NULL);
+ assert(song != NULL);
+ assert(song->parent == directory);
+
+ list_del(&song->siblings);
+}
+
+struct song *
+directory_get_song(const struct directory *directory, const char *name_utf8)
+{
+ assert(holding_db_lock());
+ assert(directory != NULL);
+ assert(name_utf8 != NULL);
+
+ struct song *song;
+ directory_for_each_song(song, directory) {
+ assert(song->parent == directory);
+
+ if (strcmp(song->uri, name_utf8) == 0)
+ return song;
+ }
+
+ return NULL;
}
struct song *
directory_lookup_song(struct directory *directory, const char *uri)
{
char *duplicated, *base;
- struct song *song;
+ assert(holding_db_lock());
assert(directory != NULL);
assert(uri != NULL);
@@ -154,7 +244,7 @@ directory_lookup_song(struct directory *directory, const char *uri)
} else
base = duplicated;
- song = songvec_find(&directory->songs, base);
+ struct song *song = directory_get_song(directory, base);
assert(song == NULL || song->parent == directory);
g_free(duplicated);
@@ -162,41 +252,61 @@ directory_lookup_song(struct directory *directory, const char *uri)
}
+static int
+directory_cmp(G_GNUC_UNUSED void *priv,
+ struct list_head *_a, struct list_head *_b)
+{
+ const struct directory *a = (const struct directory *)_a;
+ const struct directory *b = (const struct directory *)_b;
+ return g_utf8_collate(a->path, b->path);
+}
+
void
directory_sort(struct directory *directory)
{
- int i;
- struct dirvec *dv = &directory->children;
+ assert(holding_db_lock());
- dirvec_sort(dv);
- songvec_sort(&directory->songs);
+ list_sort(NULL, &directory->children, directory_cmp);
+ song_list_sort(&directory->songs);
- for (i = dv->nr; --i >= 0; )
- directory_sort(dv->base[i]);
+ struct directory *child;
+ directory_for_each_child(child, directory)
+ directory_sort(child);
}
-int
-directory_walk(struct directory *directory,
- int (*forEachSong)(struct song *, void *),
- int (*forEachDir)(struct directory *, void *),
- void *data)
+bool
+directory_walk(const struct directory *directory, bool recursive,
+ const struct db_visitor *visitor, void *ctx,
+ GError **error_r)
{
- struct dirvec *dv = &directory->children;
- int err = 0;
- size_t j;
-
- if (forEachDir && (err = forEachDir(directory, data)) < 0)
- return err;
+ assert(directory != NULL);
+ assert(visitor != NULL);
+ assert(error_r == NULL || *error_r == NULL);
+
+ if (visitor->song != NULL) {
+ struct song *song;
+ directory_for_each_song(song, directory)
+ if (!visitor->song(song, ctx, error_r))
+ return false;
+ }
- if (forEachSong) {
- err = songvec_for_each(&directory->songs, forEachSong, data);
- if (err < 0)
- return err;
+ if (visitor->playlist != NULL) {
+ struct playlist_metadata *i;
+ directory_for_each_playlist(i, directory)
+ if (!visitor->playlist(i, directory, ctx, error_r))
+ return false;
}
- for (j = 0; err >= 0 && j < dv->nr; ++j)
- err = directory_walk(dv->base[j], forEachSong,
- forEachDir, data);
+ struct directory *child;
+ directory_for_each_child(child, directory) {
+ if (visitor->directory != NULL &&
+ !visitor->directory(child, ctx, error_r))
+ return false;
+
+ if (recursive &&
+ !directory_walk(child, recursive, visitor, ctx, error_r))
+ return false;
+ }
- return err;
+ return true;
}
diff --git a/src/directory.h b/src/directory.h
index 9c0a9b567..b3cd9c8c9 100644
--- a/src/directory.h
+++ b/src/directory.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -21,23 +21,64 @@
#define MPD_DIRECTORY_H
#include "check.h"
-#include "dirvec.h"
-#include "songvec.h"
-#include "playlist_vector.h"
+#include "util/list.h"
+#include <glib.h>
#include <stdbool.h>
#include <sys/types.h>
-#define DIRECTORY_DIR "directory: "
-
#define DEVICE_INARCHIVE (dev_t)(-1)
#define DEVICE_CONTAINER (dev_t)(-2)
+#define directory_for_each_child(pos, directory) \
+ list_for_each_entry(pos, &directory->children, siblings)
+
+#define directory_for_each_child_safe(pos, n, directory) \
+ list_for_each_entry_safe(pos, n, &directory->children, siblings)
+
+#define directory_for_each_song(pos, directory) \
+ list_for_each_entry(pos, &directory->songs, siblings)
+
+#define directory_for_each_song_safe(pos, n, directory) \
+ list_for_each_entry_safe(pos, n, &directory->songs, siblings)
+
+#define directory_for_each_playlist(pos, directory) \
+ list_for_each_entry(pos, &directory->playlists, siblings)
+
+#define directory_for_each_playlist_safe(pos, n, directory) \
+ list_for_each_entry_safe(pos, n, &directory->playlists, siblings)
+
+struct song;
+struct db_visitor;
+
struct directory {
- struct dirvec children;
- struct songvec songs;
+ /**
+ * Pointers to the siblings of this directory within the
+ * parent directory. It is unused (undefined) in the root
+ * directory.
+ *
+ * This attribute is protected with the global #db_mutex.
+ * Read access in the update thread does not need protection.
+ */
+ struct list_head siblings;
- struct playlist_vector playlists;
+ /**
+ * A doubly linked list of child directories.
+ *
+ * This attribute is protected with the global #db_mutex.
+ * Read access in the update thread does not need protection.
+ */
+ struct list_head children;
+
+ /**
+ * A doubly linked list of songs within this directory.
+ *
+ * This attribute is protected with the global #db_mutex.
+ * Read access in the update thread does not need protection.
+ */
+ struct list_head songs;
+
+ struct list_head playlists;
struct directory *parent;
time_t mtime;
@@ -53,17 +94,45 @@ isRootDirectory(const char *name)
return name[0] == 0 || (name[0] == '/' && name[1] == 0);
}
+/**
+ * Generic constructor for #directory object.
+ */
+G_GNUC_MALLOC
struct directory *
directory_new(const char *dirname, struct directory *parent);
+/**
+ * Create a new root #directory object.
+ */
+G_GNUC_MALLOC
+static inline struct directory *
+directory_new_root(void)
+{
+ return directory_new("", NULL);
+}
+
+/**
+ * Free this #directory object (and the whole object tree within it),
+ * assuming it was already removed from the parent.
+ */
void
directory_free(struct directory *directory);
+/**
+ * Remove this #directory object from its parent and free it. This
+ * must not be called with the root directory.
+ *
+ * Caller must lock the #db_mutex.
+ */
+void
+directory_delete(struct directory *directory);
+
static inline bool
directory_is_empty(const struct directory *directory)
{
- return directory->children.nr == 0 && directory->songs.nr == 0 &&
- playlist_vector_is_empty(&directory->playlists);
+ return list_empty(&directory->children) &&
+ list_empty(&directory->songs) &&
+ list_empty(&directory->playlists);
}
static inline const char *
@@ -84,23 +153,47 @@ directory_is_root(const struct directory *directory)
/**
* Returns the base name of the directory.
*/
+G_GNUC_PURE
const char *
directory_get_name(const struct directory *directory);
-static inline struct directory *
-directory_get_child(const struct directory *directory, const char *name)
-{
- return dirvec_find(&directory->children, name);
-}
+/**
+ * Caller must lock the #db_mutex.
+ */
+G_GNUC_PURE
+struct directory *
+directory_get_child(const struct directory *directory, const char *name);
+/**
+ * Create a new #directory object as a child of the given one.
+ *
+ * Caller must lock the #db_mutex.
+ *
+ * @param parent the parent directory the new one will be added to
+ * @param name_utf8 the UTF-8 encoded name of the new sub directory
+ */
+G_GNUC_MALLOC
+struct directory *
+directory_new_child(struct directory *parent, const char *name_utf8);
+
+/**
+ * Look up a sub directory, and create the object if it does not
+ * exist.
+ *
+ * Caller must lock the #db_mutex.
+ */
static inline struct directory *
-directory_new_child(struct directory *directory, const char *name)
+directory_make_child(struct directory *directory, const char *name_utf8)
{
- struct directory *subdir = directory_new(name, directory);
- dirvec_add(&directory->children, subdir);
- return subdir;
+ struct directory *child = directory_get_child(directory, name_utf8);
+ if (child == NULL)
+ child = directory_new_child(directory, name_utf8);
+ return child;
}
+/**
+ * Caller must lock the #db_mutex.
+ */
void
directory_prune_empty(struct directory *directory);
@@ -115,8 +208,34 @@ struct directory *
directory_lookup_directory(struct directory *directory, const char *uri);
/**
+ * Add a song object to this directory. Its "parent" attribute must
+ * be set already.
+ */
+void
+directory_add_song(struct directory *directory, struct song *song);
+
+/**
+ * Remove a song object from this directory (which effectively
+ * invalidates the song object, because the "parent" attribute becomes
+ * stale), but does not free it.
+ */
+void
+directory_remove_song(struct directory *directory, struct song *song);
+
+/**
+ * Look up a song in this directory by its name.
+ *
+ * Caller must lock the #db_mutex.
+ */
+G_GNUC_PURE
+struct song *
+directory_get_song(const struct directory *directory, const char *name_utf8);
+
+/**
* Looks up a song by its relative URI.
*
+ * Caller must lock the #db_mutex.
+ *
* @param directory the parent (or grandparent, ...) directory
* @param uri the relative URI
* @return the song, or NULL if none was found
@@ -124,13 +243,20 @@ directory_lookup_directory(struct directory *directory, const char *uri);
struct song *
directory_lookup_song(struct directory *directory, const char *uri);
+/**
+ * Sort all directory entries recursively.
+ *
+ * Caller must lock the #db_mutex.
+ */
void
directory_sort(struct directory *directory);
-int
-directory_walk(struct directory *directory,
- int (*forEachSong)(struct song *, void *),
- int (*forEachDir)(struct directory *, void *),
- void *data);
+/**
+ * Caller must lock #db_mutex.
+ */
+bool
+directory_walk(const struct directory *directory, bool recursive,
+ const struct db_visitor *visitor, void *ctx,
+ GError **error_r);
#endif
diff --git a/src/directory_print.c b/src/directory_print.c
deleted file mode 100644
index 74ff26cb3..000000000
--- a/src/directory_print.c
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * 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 "directory_print.h"
-#include "directory.h"
-#include "client.h"
-#include "song_print.h"
-#include "mapper.h"
-#include "decoder_list.h"
-#include "path.h"
-#include "uri.h"
-#include "input_stream.h"
-
-#include <sys/types.h>
-#include <dirent.h>
-
-static void
-dirvec_print(struct client *client, const struct dirvec *dv)
-{
- size_t i;
-
- for (i = 0; i < dv->nr; ++i)
- client_printf(client, DIRECTORY_DIR "%s\n",
- directory_get_path(dv->base[i]));
-}
-
-static void
-print_playlist_in_directory(struct client *client,
- const struct directory *directory,
- const char *name_utf8)
-{
- if (directory_is_root(directory))
- client_printf(client, "playlist: %s\n", name_utf8);
- else
- client_printf(client, "playlist: %s/%s\n",
- directory_get_path(directory), name_utf8);
-}
-
-/**
- * Print a list of playlists in the specified directory.
- */
-static void
-directory_print_playlists(struct client *client,
- const struct directory *directory)
-{
- for (const struct playlist_metadata *pm = directory->playlists.head;
- pm != NULL; pm = pm->next)
- print_playlist_in_directory(client, directory, pm->name);
-}
-
-void
-directory_print(struct client *client, const struct directory *directory)
-{
- dirvec_print(client, &directory->children);
- songvec_print(client, &directory->songs);
- directory_print_playlists(client, directory);
-}
diff --git a/src/directory_save.c b/src/directory_save.c
index 55896c289..de1df050a 100644
--- a/src/directory_save.c
+++ b/src/directory_save.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -28,6 +28,7 @@
#include <assert.h>
#include <string.h>
+#define DIRECTORY_DIR "directory: "
#define DIRECTORY_MTIME "mtime: "
#define DIRECTORY_BEGIN "begin: "
#define DIRECTORY_END "end: "
@@ -42,11 +43,8 @@ directory_quark(void)
}
void
-directory_save(FILE *fp, struct directory *directory)
+directory_save(FILE *fp, const struct directory *directory)
{
- struct dirvec *children = &directory->children;
- size_t i;
-
if (!directory_is_root(directory)) {
fprintf(fp, DIRECTORY_MTIME "%lu\n",
(unsigned long)directory->mtime);
@@ -55,8 +53,8 @@ directory_save(FILE *fp, struct directory *directory)
directory_get_path(directory));
}
- for (i = 0; i < children->nr; ++i) {
- struct directory *cur = children->base[i];
+ struct directory *cur;
+ directory_for_each_child(cur, directory) {
char *base = g_path_get_basename(cur->path);
fprintf(fp, DIRECTORY_DIR "%s\n", base);
@@ -68,7 +66,9 @@ directory_save(FILE *fp, struct directory *directory)
return;
}
- songvec_save(fp, &directory->songs);
+ struct song *song;
+ directory_for_each_song(song, directory)
+ song_save(fp, song);
playlist_vector_save(fp, &directory->playlists);
@@ -81,7 +81,6 @@ static struct directory *
directory_load_subdir(FILE *fp, struct directory *parent, const char *name,
GString *buffer, GError **error_r)
{
- struct directory *directory;
const char *line;
bool success;
@@ -91,20 +90,13 @@ directory_load_subdir(FILE *fp, struct directory *parent, const char *name,
return NULL;
}
- if (directory_is_root(parent)) {
- directory = directory_new(name, parent);
- } else {
- char *path = g_strconcat(directory_get_path(parent), "/",
- name, NULL);
- directory = directory_new(path, parent);
- g_free(path);
- }
+ struct directory *directory = directory_new_child(parent, name);
line = read_text_line(fp, buffer);
if (line == NULL) {
g_set_error(error_r, directory_quark(), 0,
"Unexpected end of file");
- directory_free(directory);
+ directory_delete(directory);
return NULL;
}
@@ -117,7 +109,7 @@ directory_load_subdir(FILE *fp, struct directory *parent, const char *name,
if (line == NULL) {
g_set_error(error_r, directory_quark(), 0,
"Unexpected end of file");
- directory_free(directory);
+ directory_delete(directory);
return NULL;
}
}
@@ -125,13 +117,13 @@ directory_load_subdir(FILE *fp, struct directory *parent, const char *name,
if (!g_str_has_prefix(line, DIRECTORY_BEGIN)) {
g_set_error(error_r, directory_quark(), 0,
"Malformed line: %s", line);
- directory_free(directory);
+ directory_delete(directory);
return NULL;
}
success = directory_load(fp, directory, buffer, error_r);
if (!success) {
- directory_free(directory);
+ directory_delete(directory);
return NULL;
}
@@ -153,13 +145,11 @@ directory_load(FILE *fp, struct directory *directory,
buffer, error);
if (subdir == NULL)
return false;
-
- dirvec_add(&directory->children, subdir);
} else if (g_str_has_prefix(line, SONG_BEGIN)) {
const char *name = line + sizeof(SONG_BEGIN) - 1;
struct song *song;
- if (songvec_find(&directory->songs, name) != NULL) {
+ if (directory_get_song(directory, name) != NULL) {
g_set_error(error, directory_quark(), 0,
"Duplicate song '%s'", name);
return NULL;
@@ -170,7 +160,7 @@ directory_load(FILE *fp, struct directory *directory,
if (song == NULL)
return false;
- songvec_add(&directory->songs, song);
+ directory_add_song(directory, song);
} else if (g_str_has_prefix(line, PLAYLIST_META_BEGIN)) {
/* duplicate the name, because
playlist_metadata_load() will overwrite the
diff --git a/src/directory_save.h b/src/directory_save.h
index 9193b6c41..2d0056830 100644
--- a/src/directory_save.h
+++ b/src/directory_save.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -28,7 +28,7 @@
struct directory;
void
-directory_save(FILE *fp, struct directory *directory);
+directory_save(FILE *fp, const struct directory *directory);
bool
directory_load(FILE *fp, struct directory *directory,
diff --git a/src/dirvec.c b/src/dirvec.c
deleted file mode 100644
index 89b32a4f4..000000000
--- a/src/dirvec.c
+++ /dev/null
@@ -1,153 +0,0 @@
-/*
- * 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 "dirvec.h"
-#include "directory.h"
-
-#include <glib.h>
-
-#include <assert.h>
-#include <string.h>
-#include <stdlib.h>
-
-static GMutex *nr_lock = NULL;
-
-static size_t dv_size(const struct dirvec *dv)
-{
- return dv->nr * sizeof(struct directory *);
-}
-
-/* Only used for sorting/searching a dirvec, not general purpose compares */
-static int dirvec_cmp(const void *d1, const void *d2)
-{
- const struct directory *a = ((const struct directory * const *)d1)[0];
- const struct directory *b = ((const struct directory * const *)d2)[0];
- return g_utf8_collate(a->path, b->path);
-}
-
-void dirvec_init(void)
-{
- g_assert(nr_lock == NULL);
- nr_lock = g_mutex_new();
-}
-
-void dirvec_deinit(void)
-{
- g_assert(nr_lock != NULL);
- g_mutex_free(nr_lock);
- nr_lock = NULL;
-}
-
-void dirvec_sort(struct dirvec *dv)
-{
- g_mutex_lock(nr_lock);
- qsort(dv->base, dv->nr, sizeof(struct directory *), dirvec_cmp);
- g_mutex_unlock(nr_lock);
-}
-
-struct directory *dirvec_find(const struct dirvec *dv, const char *path)
-{
- char *base;
- int i;
- struct directory *ret = NULL;
-
- base = g_path_get_basename(path);
-
- g_mutex_lock(nr_lock);
- for (i = dv->nr; --i >= 0; )
- if (!strcmp(directory_get_name(dv->base[i]), base)) {
- ret = dv->base[i];
- break;
- }
- g_mutex_unlock(nr_lock);
-
- g_free(base);
- return ret;
-}
-
-int dirvec_delete(struct dirvec *dv, struct directory *del)
-{
- size_t i;
-
- g_mutex_lock(nr_lock);
- for (i = 0; i < dv->nr; ++i) {
- if (dv->base[i] != del)
- continue;
- /* we _don't_ call directory_free() here */
- if (!--dv->nr) {
- g_mutex_unlock(nr_lock);
- g_free(dv->base);
- dv->base = NULL;
- return i;
- } else {
- memmove(&dv->base[i], &dv->base[i + 1],
- (dv->nr - i) * sizeof(struct directory *));
- dv->base = g_realloc(dv->base, dv_size(dv));
- }
- break;
- }
- g_mutex_unlock(nr_lock);
-
- return i;
-}
-
-void dirvec_add(struct dirvec *dv, struct directory *add)
-{
- g_mutex_lock(nr_lock);
- ++dv->nr;
- dv->base = g_realloc(dv->base, dv_size(dv));
- dv->base[dv->nr - 1] = add;
- g_mutex_unlock(nr_lock);
-}
-
-void dirvec_destroy(struct dirvec *dv)
-{
- g_mutex_lock(nr_lock);
- dv->nr = 0;
- g_mutex_unlock(nr_lock);
- if (dv->base) {
- g_free(dv->base);
- dv->base = NULL;
- }
-}
-
-int dirvec_for_each(const struct dirvec *dv,
- int (*fn)(struct directory *, void *), void *arg)
-{
- size_t i;
- size_t prev_nr;
-
- g_mutex_lock(nr_lock);
- for (i = 0; i < dv->nr; ) {
- struct directory *dir = dv->base[i];
-
- assert(dir);
- prev_nr = dv->nr;
- g_mutex_unlock(nr_lock);
- if (fn(dir, arg) < 0)
- return -1;
- g_mutex_lock(nr_lock); /* dv->nr may change in fn() */
- if (prev_nr == dv->nr)
- ++i;
- }
- g_mutex_unlock(nr_lock);
-
- return 0;
-}
diff --git a/src/dsd2pcm/dsd2pcm.c b/src/dsd2pcm/dsd2pcm.c
new file mode 100644
index 000000000..4c7640853
--- /dev/null
+++ b/src/dsd2pcm/dsd2pcm.c
@@ -0,0 +1,184 @@
+#include "util/bit_reverse.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "dsd2pcm.h"
+
+#define HTAPS 48 /* number of FIR constants */
+#define FIFOSIZE 16 /* must be a power of two */
+#define FIFOMASK (FIFOSIZE-1) /* bit mask for FIFO offsets */
+#define CTABLES ((HTAPS+7)/8) /* number of "8 MACs" lookup tables */
+
+#if FIFOSIZE*8 < HTAPS*2
+#error "FIFOSIZE too small"
+#endif
+
+/*
+ * Properties of this 96-tap lowpass filter when applied on a signal
+ * with sampling rate of 44100*64 Hz:
+ *
+ * () has a delay of 17 microseconds.
+ *
+ * () flat response up to 48 kHz
+ *
+ * () if you downsample afterwards by a factor of 8, the
+ * spectrum below 70 kHz is practically alias-free.
+ *
+ * () stopband rejection is about 160 dB
+ *
+ * The coefficient tables ("ctables") take only 6 Kibi Bytes and
+ * should fit into a modern processor's fast cache.
+ */
+
+/*
+ * The 2nd half (48 coeffs) of a 96-tap symmetric lowpass filter
+ */
+static const double htaps[HTAPS] = {
+ 0.09950731974056658,
+ 0.09562845727714668,
+ 0.08819647126516944,
+ 0.07782552527068175,
+ 0.06534876523171299,
+ 0.05172629311427257,
+ 0.0379429484910187,
+ 0.02490921351762261,
+ 0.0133774746265897,
+ 0.003883043418804416,
+ -0.003284703416210726,
+ -0.008080250212687497,
+ -0.01067241812471033,
+ -0.01139427235000863,
+ -0.0106813877974587,
+ -0.009007905078766049,
+ -0.006828859761015335,
+ -0.004535184322001496,
+ -0.002425035959059578,
+ -0.0006922187080790708,
+ 0.0005700762133516592,
+ 0.001353838005269448,
+ 0.001713709169690937,
+ 0.001742046839472948,
+ 0.001545601648013235,
+ 0.001226696225277855,
+ 0.0008704322683580222,
+ 0.0005381636200535649,
+ 0.000266446345425276,
+ 7.002968738383528e-05,
+ -5.279407053811266e-05,
+ -0.0001140625650874684,
+ -0.0001304796361231895,
+ -0.0001189970287491285,
+ -9.396247155265073e-05,
+ -6.577634378272832e-05,
+ -4.07492895872535e-05,
+ -2.17407957554587e-05,
+ -9.163058931391722e-06,
+ -2.017460145032201e-06,
+ 1.249721855219005e-06,
+ 2.166655190537392e-06,
+ 1.930520892991082e-06,
+ 1.319400334374195e-06,
+ 7.410039764949091e-07,
+ 3.423230509967409e-07,
+ 1.244182214744588e-07,
+ 3.130441005359396e-08
+};
+
+static float ctables[CTABLES][256];
+static int precalculated = 0;
+
+static void precalc(void)
+{
+ int t, e, m, k;
+ double acc;
+ if (precalculated) return;
+ for (t=0; t<CTABLES; ++t) {
+ k = HTAPS - t*8;
+ if (k>8) k=8;
+ for (e=0; e<256; ++e) {
+ acc = 0.0;
+ for (m=0; m<k; ++m) {
+ acc += (((e >> (7-m)) & 1)*2-1) * htaps[t*8+m];
+ }
+ ctables[CTABLES-1-t][e] = (float)acc;
+ }
+ }
+ precalculated = 1;
+}
+
+struct dsd2pcm_ctx_s
+{
+ unsigned char fifo[FIFOSIZE];
+ unsigned fifopos;
+};
+
+extern dsd2pcm_ctx* dsd2pcm_init(void)
+{
+ dsd2pcm_ctx* ptr;
+ if (!precalculated) precalc();
+ ptr = (dsd2pcm_ctx*) malloc(sizeof(dsd2pcm_ctx));
+ if (ptr) dsd2pcm_reset(ptr);
+ return ptr;
+}
+
+extern void dsd2pcm_destroy(dsd2pcm_ctx* ptr)
+{
+ free(ptr);
+}
+
+extern dsd2pcm_ctx* dsd2pcm_clone(dsd2pcm_ctx* ptr)
+{
+ dsd2pcm_ctx* p2;
+ p2 = (dsd2pcm_ctx*) malloc(sizeof(dsd2pcm_ctx));
+ if (p2) {
+ memcpy(p2,ptr,sizeof(dsd2pcm_ctx));
+ }
+ return p2;
+}
+
+extern void dsd2pcm_reset(dsd2pcm_ctx* ptr)
+{
+ int i;
+ for (i=0; i<FIFOSIZE; ++i)
+ ptr->fifo[i] = 0x69; /* my favorite silence pattern */
+ ptr->fifopos = 0;
+ /* 0x69 = 01101001
+ * This pattern "on repeat" makes a low energy 352.8 kHz tone
+ * and a high energy 1.0584 MHz tone which should be filtered
+ * out completely by any playback system --> silence
+ */
+}
+
+extern void dsd2pcm_translate(
+ dsd2pcm_ctx* ptr,
+ size_t samples,
+ const unsigned char *src, ptrdiff_t src_stride,
+ int lsbf,
+ float *dst, ptrdiff_t dst_stride)
+{
+ unsigned ffp;
+ unsigned i;
+ unsigned bite1, bite2;
+ unsigned char* p;
+ double acc;
+ ffp = ptr->fifopos;
+ lsbf = lsbf ? 1 : 0;
+ while (samples-- > 0) {
+ bite1 = *src & 0xFFu;
+ if (lsbf) bite1 = bit_reverse(bite1);
+ ptr->fifo[ffp] = bite1; src += src_stride;
+ p = ptr->fifo + ((ffp-CTABLES) & FIFOMASK);
+ *p = bit_reverse(*p);
+ acc = 0;
+ for (i=0; i<CTABLES; ++i) {
+ bite1 = ptr->fifo[(ffp -i) & FIFOMASK] & 0xFF;
+ bite2 = ptr->fifo[(ffp-(CTABLES*2-1)+i) & FIFOMASK] & 0xFF;
+ acc += ctables[i][bite1] + ctables[i][bite2];
+ }
+ *dst = (float)acc; dst += dst_stride;
+ ffp = (ffp + 1) & FIFOMASK;
+ }
+ ptr->fifopos = ffp;
+}
+
diff --git a/src/dsd2pcm/dsd2pcm.h b/src/dsd2pcm/dsd2pcm.h
new file mode 100644
index 000000000..80e8ce0cc
--- /dev/null
+++ b/src/dsd2pcm/dsd2pcm.h
@@ -0,0 +1,64 @@
+#ifndef DSD2PCM_H_INCLUDED
+#define DSD2PCM_H_INCLUDED
+
+#include <stddef.h>
+#include <string.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct dsd2pcm_ctx_s;
+
+typedef struct dsd2pcm_ctx_s dsd2pcm_ctx;
+
+/**
+ * initializes a "dsd2pcm engine" for one channel
+ * (precomputes tables and allocates memory)
+ *
+ * This is the only function that is not thread-safe in terms of the
+ * POSIX thread-safety definition because it modifies global state
+ * (lookup tables are computed during the first call)
+ */
+extern dsd2pcm_ctx* dsd2pcm_init(void);
+
+/**
+ * deinitializes a "dsd2pcm engine"
+ * (releases memory, don't forget!)
+ */
+extern void dsd2pcm_destroy(dsd2pcm_ctx *ctx);
+
+/**
+ * clones the context and returns a pointer to the
+ * newly allocated copy
+ */
+extern dsd2pcm_ctx* dsd2pcm_clone(dsd2pcm_ctx *ctx);
+
+/**
+ * resets the internal state for a fresh new stream
+ */
+extern void dsd2pcm_reset(dsd2pcm_ctx *ctx);
+
+/**
+ * "translates" a stream of octets to a stream of floats
+ * (8:1 decimation)
+ * @param ctx -- pointer to abstract context (buffers)
+ * @param samples -- number of octets/samples to "translate"
+ * @param src -- pointer to first octet (input)
+ * @param src_stride -- src pointer increment
+ * @param lsbitfirst -- bitorder, 0=msb first, 1=lsbfirst
+ * @param dst -- pointer to first float (output)
+ * @param dst_stride -- dst pointer increment
+ */
+extern void dsd2pcm_translate(dsd2pcm_ctx *ctx,
+ size_t samples,
+ const unsigned char *src, ptrdiff_t src_stride,
+ int lsbitfirst,
+ float *dst, ptrdiff_t dst_stride);
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* include guard DSD2PCM_H_INCLUDED */
+
diff --git a/src/dsd2pcm/dsd2pcm.hpp b/src/dsd2pcm/dsd2pcm.hpp
new file mode 100644
index 000000000..b1b2ae1c5
--- /dev/null
+++ b/src/dsd2pcm/dsd2pcm.hpp
@@ -0,0 +1,41 @@
+#ifndef DSD2PCM_HXX_INCLUDED
+#define DSD2PCM_HXX_INCLUDED
+
+#include <algorithm>
+#include <stdexcept>
+#include "dsd2pcm.h"
+
+/**
+ * C++ PImpl Wrapper for the dsd2pcm C library
+ */
+
+class dxd
+{
+ dsd2pcm_ctx *handle;
+public:
+ dxd() : handle(dsd2pcm_init())
+ { if (!handle) throw std::runtime_error("wtf?!"); }
+
+ dxd(dxd const& x) : handle(dsd2pcm_clone(x.handle))
+ { if (!handle) throw std::runtime_error("wtf?!"); }
+
+ ~dxd() { dsd2pcm_destroy(handle); }
+
+ friend void swap(dxd & a, dxd & b)
+ { std::swap(a.handle,b.handle); }
+
+ dxd& operator=(dxd x)
+ { swap(*this,x); return *this; }
+
+ void translate(size_t samples,
+ const unsigned char *src, ptrdiff_t src_stride,
+ bool lsbitfirst,
+ float *dst, ptrdiff_t dst_stride)
+ {
+ dsd2pcm_translate(handle,samples,src,src_stride,
+ lsbitfirst,dst,dst_stride);
+ }
+};
+
+#endif // DSD2PCM_HXX_INCLUDED
+
diff --git a/src/dsd2pcm/info.txt b/src/dsd2pcm/info.txt
new file mode 100644
index 000000000..15ff29245
--- /dev/null
+++ b/src/dsd2pcm/info.txt
@@ -0,0 +1,38 @@
+You downloaded the source code for "dsd2pcm" which is a simple little
+"filter" program, that takes a DSD data stream on stdin and converts
+it to a PCM stream (352.8 kHz, either 16 or 24 bits) and writes it to
+stdout. The code is split into three modules:
+
+ (1) dsd2pcm
+
+ This is where the 8:1 decimation magic happens. It's an
+ implementation of a symmetric 96-taps FIR lowpass filter
+ optimized for DSD inputs. If you feed this converter with
+ DSD64 you get a PCM stream at 352.8 kHz and floating point
+ samples. This module is independent and can be reused.
+
+ (2) noiseshape
+
+ A module for applying generic noise shaping filters. It's
+ used for the 16-bit output mode in "main" to preserve the
+ dynamic range. This module is independent and can be reused.
+
+ (3) main.cpp (file contains the main function and handles I/O)
+
+The first two modules are pure C for maximum portability. In addition,
+there are C++ wrapper headers for convenient use of these modules in
+C++. The main application is a C++ application and makes use of the
+C++ headers to access the functionality of the first two modules.
+
+
+Under Linux this program is easily compiled by typing
+
+ g++ *.c *.cpp -O3 -o dsd2pcm
+
+provided you have GCC installed. That's why I didn't bother writing
+any makefiles. :-p
+
+
+Cheers!
+SG
+
diff --git a/src/dsd2pcm/main.cpp b/src/dsd2pcm/main.cpp
new file mode 100644
index 000000000..0b58888a8
--- /dev/null
+++ b/src/dsd2pcm/main.cpp
@@ -0,0 +1,120 @@
+#include <iostream>
+#include <vector>
+#include <cstring>
+
+#include "dsd2pcm.hpp"
+#include "noiseshape.hpp"
+
+namespace {
+
+const float my_ns_coeffs[] = {
+// b1 b2 a1 a2
+ -1.62666423, 0.79410094, 0.61367127, 0.23311013, // section 1
+ -1.44870017, 0.54196219, 0.03373857, 0.70316556 // section 2
+};
+
+const int my_ns_soscount = sizeof(my_ns_coeffs)/(sizeof(my_ns_coeffs[0])*4);
+
+inline long myround(float x)
+{
+ return static_cast<long>(x + (x>=0 ? 0.5f : -0.5f));
+}
+
+template<typename T>
+struct id { typedef T type; };
+
+template<typename T>
+inline T clip(
+ typename id<T>::type min,
+ T v,
+ typename id<T>::type max)
+{
+ if (v<min) return min;
+ if (v>max) return max;
+ return v;
+}
+
+inline void write_intel16(unsigned char * ptr, unsigned word)
+{
+ ptr[0] = word & 0xFF;
+ ptr[1] = (word >> 8) & 0xFF;
+}
+
+inline void write_intel24(unsigned char * ptr, unsigned long word)
+{
+ ptr[0] = word & 0xFF;
+ ptr[1] = (word >> 8) & 0xFF;
+ ptr[2] = (word >> 16) & 0xFF;
+}
+
+} // anonymous namespace
+
+using std::vector;
+using std::cin;
+using std::cout;
+using std::cerr;
+
+int main(int argc, char *argv[])
+{
+ const int block = 16384;
+ int channels = -1;
+ int lsbitfirst = -1;
+ int bits = -1;
+ if (argc==4) {
+ if ('1'<=argv[1][0] && argv[1][0]<='9') channels = 1 + (argv[1][0]-'1');
+ if (argv[2][0]=='m' || argv[2][0]=='M') lsbitfirst=0;
+ if (argv[2][0]=='l' || argv[2][0]=='L') lsbitfirst=1;
+ if (!strcmp(argv[3],"16")) bits = 16;
+ if (!strcmp(argv[3],"24")) bits = 24;
+ }
+ if (channels<1 || lsbitfirst<0 || bits<0) {
+ cerr << "\n"
+ "DSD2PCM filter (raw DSD64 --> 352 kHz raw PCM)\n"
+ "(c) 2009 Sebastian Gesemann\n\n"
+ "(filter as in \"reads data from stdin and writes to stdout\")\n\n"
+ "Syntax: dsd2pcm <channels> <bitorder> <bitdepth>\n"
+ "channels = 1,2,3,...,9 (number of channels in DSD stream)\n"
+ "bitorder = L (lsb first), M (msb first) (DSD stream option)\n"
+ "bitdepth = 16 or 24 (intel byte order, output option)\n\n"
+ "Note: At 16 bits/sample a noise shaper kicks in that can preserve\n"
+ "a dynamic range of 135 dB below 30 kHz.\n\n";
+ return 1;
+ }
+ int bytespersample = bits/8;
+ vector<dxd> dxds (channels);
+ vector<noise_shaper> ns;
+ if (bits==16) {
+ ns.resize(channels, noise_shaper(my_ns_soscount, my_ns_coeffs) );
+ }
+ vector<unsigned char> dsd_data (block * channels);
+ vector<float> float_data (block);
+ vector<unsigned char> pcm_data (block * channels * bytespersample);
+ char * const dsd_in = reinterpret_cast<char*>(&dsd_data[0]);
+ char * const pcm_out = reinterpret_cast<char*>(&pcm_data[0]);
+ while (cin.read(dsd_in,block * channels)) {
+ for (int c=0; c<channels; ++c) {
+ dxds[c].translate(block,&dsd_data[0]+c,channels,
+ lsbitfirst,
+ &float_data[0],1);
+ unsigned char * out = &pcm_data[0] + c*bytespersample;
+ if (bits==16) {
+ for (int s=0; s<block; ++s) {
+ float r = float_data[s]*32768 + ns[c].get();
+ long smp = clip(-32768,myround(r),32767);
+ ns[c].update( clip(-1,smp-r,1) );
+ write_intel16(out,smp);
+ out += channels*bytespersample;
+ }
+ } else {
+ for (int s=0; s<block; ++s) {
+ float r = float_data[s]*8388608;
+ long smp = clip(-8388608,myround(r),8388607);
+ write_intel24(out,smp);
+ out += channels*bytespersample;
+ }
+ }
+ }
+ cout.write(pcm_out,block*channels*bytespersample);
+ }
+}
+
diff --git a/src/dsd2pcm/noiseshape.c b/src/dsd2pcm/noiseshape.c
new file mode 100644
index 000000000..ecd2f251d
--- /dev/null
+++ b/src/dsd2pcm/noiseshape.c
@@ -0,0 +1,83 @@
+#include <stdlib.h>
+#include <string.h>
+
+#include "noiseshape.h"
+
+extern int noise_shape_init(
+ noise_shape_ctx *ctx,
+ int sos_count,
+ const float *coeffs)
+{
+ int i;
+ ctx->sos_count = sos_count;
+ ctx->bbaa = coeffs;
+ ctx->t1 = (float*) malloc(sizeof(float)*sos_count);
+ if (!ctx->t1) goto escape1;
+ ctx->t2 = (float*) malloc(sizeof(float)*sos_count);
+ if (!ctx->t2) goto escape2;
+ for (i=0; i<sos_count; ++i) {
+ ctx->t1[i] = 0.f;
+ ctx->t2[i] = 0.f;
+ }
+ return 0;
+escape2:
+ free(ctx->t1);
+escape1:
+ return -1;
+}
+
+extern void noise_shape_destroy(
+ noise_shape_ctx *ctx)
+{
+ free(ctx->t1);
+ free(ctx->t2);
+}
+
+extern int noise_shape_clone(
+ const noise_shape_ctx *from,
+ noise_shape_ctx *to)
+{
+ to->sos_count = from->sos_count;
+ to->bbaa = from->bbaa;
+ to->t1 = (float*) malloc(sizeof(float)*to->sos_count);
+ if (!to->t1) goto error1;
+ to->t2 = (float*) malloc(sizeof(float)*to->sos_count);
+ if (!to->t2) goto error2;
+ memcpy(to->t1,from->t1,sizeof(float)*to->sos_count);
+ memcpy(to->t2,from->t2,sizeof(float)*to->sos_count);
+ return 0;
+error2:
+ free(to->t1);
+error1:
+ return -1;
+}
+
+extern float noise_shape_get(noise_shape_ctx *ctx)
+{
+ int i;
+ float acc;
+ const float *c;
+ acc = 0.0;
+ c = ctx->bbaa;
+ for (i=0; i<ctx->sos_count; ++i) {
+ float t1i = ctx->t1[i];
+ float t2i = ctx->t2[i];
+ ctx->t2[i] = acc -= t1i * c[2] + t2i * c[3];
+ acc += t1i * c[0] + t2i * c[1];
+ c += 4;
+ }
+ return acc;
+}
+
+extern void noise_shape_update(noise_shape_ctx *ctx, float qerror)
+{
+ float *p;
+ int i;
+ for (i=0; i<ctx->sos_count; ++i) {
+ ctx->t2[i] += qerror;
+ }
+ p = ctx->t1;
+ ctx->t1 = ctx->t2;
+ ctx->t2 = p;
+}
+
diff --git a/src/dsd2pcm/noiseshape.h b/src/dsd2pcm/noiseshape.h
new file mode 100644
index 000000000..6075f0d88
--- /dev/null
+++ b/src/dsd2pcm/noiseshape.h
@@ -0,0 +1,57 @@
+#ifndef NOISE_SHAPE_H_INCLUDED
+#define NOISE_SHAPE_H_INCLUDED
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct noise_shape_ctx_s {
+ int sos_count; /* number of second order sections */
+ const float *bbaa; /* filter coefficients, owned by user */
+ float *t1, *t2; /* filter state, owned by ns library */
+} noise_shape_ctx;
+
+/**
+ * initializes a noise_shaper context
+ * returns an error code or 0
+ */
+extern int noise_shape_init(
+ noise_shape_ctx *ctx,
+ int sos_count,
+ const float *coeffs);
+
+/**
+ * destroys a noise_shaper context
+ */
+extern void noise_shape_destroy(
+ noise_shape_ctx *ctx);
+
+/**
+ * initializes a noise_shaper context so that its state
+ * is a copy of a given context
+ * returns an error code or 0
+ */
+extern int noise_shape_clone(
+ const noise_shape_ctx *from, noise_shape_ctx *to);
+
+/**
+ * computes the next "noise shaping sample". Note: This call
+ * alters the internal state. xxx_get and xxx_update must be
+ * called in an alternating manner.
+ */
+extern float noise_shape_get(
+ noise_shape_ctx *ctx);
+
+/**
+ * updates the noise shaper's state with the
+ * last quantization error
+ */
+extern void noise_shape_update(
+ noise_shape_ctx *ctx, float qerror);
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* NOISE_SHAPE_H_INCLUDED */
+
diff --git a/src/dsd2pcm/noiseshape.hpp b/src/dsd2pcm/noiseshape.hpp
new file mode 100644
index 000000000..726272f91
--- /dev/null
+++ b/src/dsd2pcm/noiseshape.hpp
@@ -0,0 +1,46 @@
+#ifndef NOISE_SHAPE_HXX_INCLUDED
+#define NOISE_SHAPE_HXX_INCLUDED
+
+#include <stdexcept>
+#include "noiseshape.h"
+
+/**
+ * C++ wrapper for the noiseshape C library
+ */
+
+class noise_shaper
+{
+ noise_shape_ctx ctx;
+public:
+ noise_shaper(int sos_count, const float *bbaa)
+ {
+ if (noise_shape_init(&ctx,sos_count,bbaa))
+ throw std::runtime_error("noise shaper initialization failed");
+ }
+
+ noise_shaper(noise_shaper const& x)
+ {
+ if (noise_shape_clone(&x.ctx,&ctx))
+ throw std::runtime_error("noise shaper initialization failed");
+ }
+
+ ~noise_shaper()
+ { noise_shape_destroy(&ctx); }
+
+ noise_shaper& operator=(noise_shaper const& x)
+ {
+ if (this != &x) {
+ noise_shape_destroy(&ctx);
+ if (noise_shape_clone(&x.ctx,&ctx))
+ throw std::runtime_error("noise shaper initialization failed");
+ }
+ return *this;
+ }
+
+ float get() { return noise_shape_get(&ctx); }
+
+ void update(float error) { noise_shape_update(&ctx,error); }
+};
+
+#endif /* NOISE_SHAPE_HXX_INCLUDED */
+
diff --git a/src/dummy.cxx b/src/dummy.cxx
new file mode 100644
index 000000000..b21555d06
--- /dev/null
+++ b/src/dummy.cxx
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/*
+ * Just a dummy C++ file that is linked to work around an automake
+ * bug: automake uses CXXLD when at least one source is C++, but when
+ * you link a static library with a C++ source, it uses CCLD. This
+ * causes linker problems (undefined reference to 'operator
+ * delete(void*)'), because CCLD does not link with libstdc++.
+ *
+ * By linking with this empty C++ source, automake decides to use
+ * CXXLD.
+ *
+ */
diff --git a/src/encoder/flac_encoder.c b/src/encoder/flac_encoder.c
index e2c455e3a..e32588e29 100644
--- a/src/encoder/flac_encoder.c
+++ b/src/encoder/flac_encoder.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -207,7 +207,7 @@ flac_encoder_open(struct encoder *_encoder, struct audio_format *audio_format,
encoder->output_buffer = growing_fifo_new();
- /* this immediatelly outputs data throught callback */
+ /* this immediately outputs data through callback */
#if !defined(FLAC_API_VERSION_CURRENT) || FLAC_API_VERSION_CURRENT <= 7
{
diff --git a/src/encoder/lame_encoder.c b/src/encoder/lame_encoder.c
index df843471b..3bb99ea28 100644
--- a/src/encoder/lame_encoder.c
+++ b/src/encoder/lame_encoder.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/encoder/null_encoder.c b/src/encoder/null_encoder.c
index 4e45f4345..48cdf139b 100644
--- a/src/encoder/null_encoder.c
+++ b/src/encoder/null_encoder.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/encoder/twolame_encoder.c b/src/encoder/twolame_encoder.c
index 073c3128f..934b2ab24 100644
--- a/src/encoder/twolame_encoder.c
+++ b/src/encoder/twolame_encoder.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/encoder/vorbis_encoder.c b/src/encoder/vorbis_encoder.c
index 9f09b2ac7..fcf2b5135 100644
--- a/src/encoder/vorbis_encoder.c
+++ b/src/encoder/vorbis_encoder.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/encoder/wave_encoder.c b/src/encoder/wave_encoder.c
index 3a64c5197..9eeb4d513 100644
--- a/src/encoder/wave_encoder.c
+++ b/src/encoder/wave_encoder.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -122,11 +122,6 @@ wave_encoder_open(struct encoder *_encoder,
encoder->bits = 16;
break;
- case SAMPLE_FORMAT_S24:
- audio_format->format = SAMPLE_FORMAT_S24_P32;
- encoder->bits = 24;
- break;
-
case SAMPLE_FORMAT_S24_P32:
encoder->bits = 24;
break;
diff --git a/src/encoder_api.h b/src/encoder_api.h
index 5df486ebd..46c8d10c8 100644
--- a/src/encoder_api.h
+++ b/src/encoder_api.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/encoder_list.c b/src/encoder_list.c
index f49ad48f7..d98e617b4 100644
--- a/src/encoder_list.c
+++ b/src/encoder_list.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/encoder_list.h b/src/encoder_list.h
index 95f853004..6316d5d2f 100644
--- a/src/encoder_list.h
+++ b/src/encoder_list.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/encoder_plugin.h b/src/encoder_plugin.h
index 70eee51a2..33a379115 100644
--- a/src/encoder_plugin.h
+++ b/src/encoder_plugin.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -91,7 +91,7 @@ encoder_struct_init(struct encoder *encoder,
*
* @param plugin the encoder plugin
* @param param optional configuration
- * @param error location to store the error occuring, or NULL to ignore errors.
+ * @param error location to store the error occurring, or NULL to ignore errors.
* @return an encoder object on success, NULL on failure
*/
static inline struct encoder *
@@ -122,7 +122,7 @@ encoder_finish(struct encoder *encoder)
* @param encoder the encoder
* @param audio_format the encoder's input audio format; the plugin
* may modify the struct to adapt it to its abilities
- * @param error location to store the error occuring, or NULL to ignore errors.
+ * @param error location to store the error occurring, or NULL to ignore errors.
* @return true on success
*/
static inline bool
@@ -192,7 +192,7 @@ encoder_end(struct encoder *encoder, GError **error)
* buffered available by encoder_read().
*
* @param encoder the encoder
- * @param error location to store the error occuring, or NULL to ignore errors.
+ * @param error location to store the error occurring, or NULL to ignore errors.
* @return true on success
*/
static inline bool
@@ -246,7 +246,7 @@ encoder_pre_tag(struct encoder *encoder, GError **error)
*
* @param encoder the encoder
* @param tag the tag object
- * @param error location to store the error occuring, or NULL to ignore errors.
+ * @param error location to store the error occurring, or NULL to ignore errors.
* @return true on success
*/
static inline bool
@@ -273,7 +273,7 @@ encoder_tag(struct encoder *encoder, const struct tag *tag, GError **error)
* @param encoder the encoder
* @param data the buffer containing PCM samples
* @param length the length of the buffer in bytes
- * @param error location to store the error occuring, or NULL to ignore errors.
+ * @param error location to store the error occurring, or NULL to ignore errors.
* @return true on success
*/
static inline bool
diff --git a/src/event_pipe.c b/src/event_pipe.c
index 484b7a625..d5c3b9564 100644
--- a/src/event_pipe.c
+++ b/src/event_pipe.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/event_pipe.h b/src/event_pipe.h
index 923544bf4..3734bb86c 100644
--- a/src/event_pipe.h
+++ b/src/event_pipe.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/exclude.c b/src/exclude.c
index dd46b58c7..438039d30 100644
--- a/src/exclude.c
+++ b/src/exclude.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/exclude.h b/src/exclude.h
index fd7cf8795..5b1229e29 100644
--- a/src/exclude.h
+++ b/src/exclude.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/fd_util.c b/src/fd_util.c
index 4636c19e4..882b4c7d5 100644
--- a/src/fd_util.c
+++ b/src/fd_util.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* Redistribution and use in source and binary forms, with or without
@@ -206,6 +206,29 @@ socketpair_cloexec(int domain, int type, int protocol, int sv[2])
return ret;
}
+int
+socketpair_cloexec_nonblock(int domain, int type, int protocol, int sv[2])
+{
+ int ret;
+
+#if defined(SOCK_CLOEXEC) && defined(SOCK_NONBLOCK)
+ ret = socketpair(domain, type | SOCK_CLOEXEC | SOCK_NONBLOCK, 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_nonblock(sv[0]);
+ fd_set_cloexec(sv[1], true);
+ fd_set_nonblock(sv[1]);
+ }
+
+ return ret;
+}
+
#endif
int
diff --git a/src/fd_util.h b/src/fd_util.h
index d74bb30d8..dd4df7a13 100644
--- a/src/fd_util.h
+++ b/src/fd_util.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* Redistribution and use in source and binary forms, with or without
@@ -91,6 +91,13 @@ pipe_cloexec_nonblock(int fd[2]);
int
socketpair_cloexec(int domain, int type, int protocol, int sv[2]);
+/**
+ * Wrapper for socketpair(), which sets the flags CLOEXEC and NONBLOCK
+ * (atomically if supported by the OS).
+ */
+int
+socketpair_cloexec_nonblock(int domain, int type, int protocol, int sv[2]);
+
#endif
/**
diff --git a/src/filter/autoconvert_filter_plugin.c b/src/filter/autoconvert_filter_plugin.c
index 9e197a5f6..3826a0fb3 100644
--- a/src/filter/autoconvert_filter_plugin.c
+++ b/src/filter/autoconvert_filter_plugin.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/filter/autoconvert_filter_plugin.h b/src/filter/autoconvert_filter_plugin.h
index 730db197d..def08ab7e 100644
--- a/src/filter/autoconvert_filter_plugin.h
+++ b/src/filter/autoconvert_filter_plugin.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/filter/chain_filter_plugin.c b/src/filter/chain_filter_plugin.c
index 06d4d0e6b..2c785a36f 100644
--- a/src/filter/chain_filter_plugin.c
+++ b/src/filter/chain_filter_plugin.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/filter/chain_filter_plugin.h b/src/filter/chain_filter_plugin.h
index 42c6a9b78..1dba46667 100644
--- a/src/filter/chain_filter_plugin.h
+++ b/src/filter/chain_filter_plugin.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/filter/convert_filter_plugin.c b/src/filter/convert_filter_plugin.c
index cb9e0940a..c55b69af2 100644
--- a/src/filter/convert_filter_plugin.c
+++ b/src/filter/convert_filter_plugin.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -141,7 +141,6 @@ convert_filter_set(struct filter *_filter,
assert(audio_format_valid(&filter->out_audio_format));
assert(out_audio_format != NULL);
assert(audio_format_valid(out_audio_format));
- assert(filter->in_audio_format.reverse_endian == 0);
filter->out_audio_format = *out_audio_format;
}
diff --git a/src/filter/convert_filter_plugin.h b/src/filter/convert_filter_plugin.h
index ba9180e64..156adf8e3 100644
--- a/src/filter/convert_filter_plugin.h
+++ b/src/filter/convert_filter_plugin.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/filter/normalize_filter_plugin.c b/src/filter/normalize_filter_plugin.c
index 63bbb6e4f..2151482e4 100644
--- a/src/filter/normalize_filter_plugin.c
+++ b/src/filter/normalize_filter_plugin.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -67,7 +67,6 @@ normalize_filter_open(struct filter *_filter,
struct normalize_filter *filter = (struct normalize_filter *)_filter;
audio_format->format = SAMPLE_FORMAT_S16;
- audio_format->reverse_endian = false;
filter->compressor = Compressor_new(0);
diff --git a/src/filter/null_filter_plugin.c b/src/filter/null_filter_plugin.c
index 650f95bc4..e7c998827 100644
--- a/src/filter/null_filter_plugin.c
+++ b/src/filter/null_filter_plugin.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/filter/replay_gain_filter_plugin.c b/src/filter/replay_gain_filter_plugin.c
index 3a0af66ff..583a09f90 100644
--- a/src/filter/replay_gain_filter_plugin.c
+++ b/src/filter/replay_gain_filter_plugin.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -140,8 +140,6 @@ replay_gain_filter_open(struct filter *_filter,
struct replay_gain_filter *filter =
(struct replay_gain_filter *)_filter;
- audio_format->reverse_endian = false;
-
filter->audio_format = *audio_format;
pcm_buffer_init(&filter->buffer);
@@ -195,7 +193,7 @@ replay_gain_filter_filter(struct filter *_filter,
memcpy(dest, src, src_size);
- success = pcm_volume(dest, src_size, &filter->audio_format,
+ success = pcm_volume(dest, src_size, filter->audio_format.format,
filter->volume);
if (!success) {
g_set_error(error_r, replay_gain_quark(), 0,
diff --git a/src/filter/replay_gain_filter_plugin.h b/src/filter/replay_gain_filter_plugin.h
index 348b4f50c..45b738e40 100644
--- a/src/filter/replay_gain_filter_plugin.h
+++ b/src/filter/replay_gain_filter_plugin.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/filter/route_filter_plugin.c b/src/filter/route_filter_plugin.c
index 6b9aa2a2f..3bf8677e5 100644
--- a/src/filter/route_filter_plugin.c
+++ b/src/filter/route_filter_plugin.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/filter/volume_filter_plugin.c b/src/filter/volume_filter_plugin.c
index 42311ca5e..e52c0a463 100644
--- a/src/filter/volume_filter_plugin.c
+++ b/src/filter/volume_filter_plugin.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -74,8 +74,6 @@ volume_filter_open(struct filter *_filter, struct audio_format *audio_format,
{
struct volume_filter *filter = (struct volume_filter *)_filter;
- audio_format->reverse_endian = false;
-
filter->audio_format = *audio_format;
pcm_buffer_init(&filter->buffer);
@@ -116,7 +114,7 @@ volume_filter_filter(struct filter *_filter, const void *src, size_t src_size,
memcpy(dest, src, src_size);
- success = pcm_volume(dest, src_size, &filter->audio_format,
+ success = pcm_volume(dest, src_size, filter->audio_format.format,
filter->volume);
if (!success) {
g_set_error(error_r, volume_quark(), 0,
diff --git a/src/filter/volume_filter_plugin.h b/src/filter/volume_filter_plugin.h
index ad3b2c6f1..5b16f7e57 100644
--- a/src/filter/volume_filter_plugin.h
+++ b/src/filter/volume_filter_plugin.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/filter_config.c b/src/filter_config.c
index 90de199b7..ab9bdb0c5 100644
--- a/src/filter_config.c
+++ b/src/filter_config.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/filter_config.h b/src/filter_config.h
index 9ed4d204b..920cbc07c 100644
--- a/src/filter_config.h
+++ b/src/filter_config.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/filter_internal.h b/src/filter_internal.h
index 8dd6da491..4e94599a2 100644
--- a/src/filter_internal.h
+++ b/src/filter_internal.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/filter_plugin.c b/src/filter_plugin.c
index 492d703ac..7173134b3 100644
--- a/src/filter_plugin.c
+++ b/src/filter_plugin.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/filter_plugin.h b/src/filter_plugin.h
index ac6b34522..58e34dfb2 100644
--- a/src/filter_plugin.h
+++ b/src/filter_plugin.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -79,7 +79,7 @@ struct filter_plugin {
*
* @param plugin the filter plugin
* @param param optional configuration section
- * @param error location to store the error occuring, or NULL to
+ * @param error location to store the error occurring, or NULL to
* ignore errors.
* @return a new filter object, or NULL on error
*/
@@ -92,7 +92,7 @@ filter_new(const struct filter_plugin *plugin,
* the specified configuration section.
*
* @param param the configuration section
- * @param error location to store the error occuring, or NULL to
+ * @param error location to store the error occurring, or NULL to
* ignore errors.
* @return a new filter object, or NULL on error
*/
@@ -114,7 +114,7 @@ filter_free(struct filter *filter);
* @param filter the filter object
* @param audio_format the audio format of incoming data; the plugin
* may modify the object to enforce another input format
- * @param error location to store the error occuring, or NULL to
+ * @param error location to store the error occurring, or NULL to
* ignore errors.
* @return the format of outgoing data
*/
@@ -137,7 +137,7 @@ filter_close(struct filter *filter);
* @param src the input buffer
* @param src_size the size of #src_buffer in bytes
* @param dest_size_r the size of the returned buffer
- * @param error location to store the error occuring, or NULL to
+ * @param error location to store the error occurring, or NULL to
* ignore errors.
* @return the destination buffer on success (will be invalidated by
* filter_close() or filter_filter()), NULL on error
diff --git a/src/filter_registry.c b/src/filter_registry.c
index 150043cc5..dc1889398 100644
--- a/src/filter_registry.c
+++ b/src/filter_registry.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/filter_registry.h b/src/filter_registry.h
index 551a7afa1..d3949c7c4 100644
--- a/src/filter_registry.h
+++ b/src/filter_registry.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/gcc.h b/src/gcc.h
index 085a8a5f6..45f7101f3 100644
--- a/src/gcc.h
+++ b/src/gcc.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -20,35 +20,44 @@
#ifndef MPD_GCC_H
#define MPD_GCC_H
+#define GCC_CHECK_VERSION(major, minor) \
+ (defined(__GNUC__) && \
+ (__GNUC__ > (major) || \
+ (__GNUC__ == (major) && __GNUC_MINOR__ >= (minor))))
+
/* this allows us to take advantage of special gcc features while still
* allowing other compilers to compile:
*
* example taken from: http://rlove.org/log/2005102601
*/
-#if defined(__GNUC__) && (__GNUC__ >= 3)
-# define mpd_must_check __attribute__ ((warn_unused_result))
-# define mpd_packed __attribute__ ((packed))
+#if GCC_CHECK_VERSION(3,0)
+# define gcc_must_check __attribute__ ((warn_unused_result))
+# define gcc_packed __attribute__ ((packed))
/* these are very useful for type checking */
-# define mpd_printf __attribute__ ((format(printf,1,2)))
-# define mpd_fprintf __attribute__ ((format(printf,2,3)))
-# define mpd_fprintf_ __attribute__ ((format(printf,3,4)))
-# define mpd_fprintf__ __attribute__ ((format(printf,4,5)))
-# define mpd_scanf __attribute__ ((format(scanf,1,2)))
-# define mpd_used __attribute__ ((used))
+# define gcc_printf __attribute__ ((format(printf,1,2)))
+# define gcc_fprintf __attribute__ ((format(printf,2,3)))
+# define gcc_fprintf_ __attribute__ ((format(printf,3,4)))
+# define gcc_fprintf__ __attribute__ ((format(printf,4,5)))
+# define gcc_scanf __attribute__ ((format(scanf,1,2)))
+# define gcc_used __attribute__ ((used))
/* # define inline inline __attribute__ ((always_inline)) */
-# define mpd_noinline __attribute__ ((noinline))
+# define gcc_noinline __attribute__ ((noinline))
+# define gcc_nonnull(...) __attribute__((nonnull(__VA_ARGS__)))
+# define gcc_nonnull_all __attribute__((nonnull))
#else
-# define mpd_must_check
-# define mpd_packed
-# define mpd_printf
-# define mpd_fprintf
-# define mpd_fprintf_
-# define mpd_fprintf__
-# define mpd_scanf
-# define mpd_used
+# define gcc_must_check
+# define gcc_packed
+# define gcc_printf
+# define gcc_fprintf
+# define gcc_fprintf_
+# define gcc_fprintf__
+# define gcc_scanf
+# define gcc_used
/* # define inline */
-# define mpd_noinline
+# define gcc_noinline
+# define gcc_nonnull(...)
+# define gcc_nonnull_all
#endif
#endif /* MPD_GCC_H */
diff --git a/src/glib_compat.h b/src/glib_compat.h
index 4d0e7040d..330c9e779 100644
--- a/src/glib_compat.h
+++ b/src/glib_compat.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -32,6 +32,12 @@
#define g_queue_clear(q) do { g_queue_free(q); q = g_queue_new(); } while (0)
+static inline GSource *
+g_timeout_source_new_seconds(guint interval)
+{
+ return g_timeout_source_new(interval * 1000);
+}
+
static inline guint
g_timeout_add_seconds(guint interval, GSourceFunc function, gpointer data)
{
@@ -43,6 +49,12 @@ g_timeout_add_seconds(guint interval, GSourceFunc function, gpointer data)
#if !GLIB_CHECK_VERSION(2,16,0)
static inline void
+g_prefix_error(G_GNUC_UNUSED GError **error_r,
+ G_GNUC_UNUSED const gchar *format, ...)
+{
+}
+
+static inline void
g_propagate_prefixed_error(GError **dest_r, GError *src,
G_GNUC_UNUSED const gchar *format, ...)
{
@@ -74,4 +86,27 @@ g_uri_parse_scheme(const char *uri)
#endif
+#if !GLIB_CHECK_VERSION(2,18,0)
+
+static inline void
+g_set_error_literal(GError **err, GQuark domain, gint code,
+ const gchar *message)
+{
+ g_set_error(err, domain, code, "%s", message);
+}
+
+#endif
+
+#if !GLIB_CHECK_VERSION(2,28,0)
+
+static inline gint64
+g_source_get_time(GSource *source)
+{
+ GTimeVal tv;
+ g_source_get_current_time(source, &tv);
+ return tv.tv_sec * 1000000 + tv.tv_usec;
+}
+
+#endif
+
#endif
diff --git a/src/icy_metadata.c b/src/icy_metadata.c
index 6a79121cf..32953e69f 100644
--- a/src/icy_metadata.c
+++ b/src/icy_metadata.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/icy_metadata.h b/src/icy_metadata.h
index 4a51b4cf0..9797122ca 100644
--- a/src/icy_metadata.h
+++ b/src/icy_metadata.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/icy_server.c b/src/icy_server.c
index 62a2c67af..b6c89eaf6 100644
--- a/src/icy_server.c
+++ b/src/icy_server.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -70,7 +70,7 @@ icy_server_metadata_string(const char *stream_title, const char* stream_url)
meta_length = strlen(icy_metadata);
- meta_length--; // substract placeholder
+ meta_length--; // subtract placeholder
meta_length = ((int)meta_length / 16) + 1;
diff --git a/src/icy_server.h b/src/icy_server.h
index 3ce4ab635..04f21d2ad 100644
--- a/src/icy_server.h
+++ b/src/icy_server.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/idle.c b/src/idle.c
index eccb62322..2d174d78a 100644
--- a/src/idle.c
+++ b/src/idle.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -42,6 +42,8 @@ static const char *const idle_names[] = {
"options",
"sticker",
"update",
+ "subscription",
+ "message",
NULL
};
diff --git a/src/idle.h b/src/idle.h
index 7caeb4a8c..0156933c0 100644
--- a/src/idle.h
+++ b/src/idle.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -53,6 +53,12 @@ enum {
/** a database update has started or finished. */
IDLE_UPDATE = 0x100,
+
+ /** a client has subscribed or unsubscribed to/from a channel */
+ IDLE_SUBSCRIPTION = 0x200,
+
+ /** a message on the subscribed channel was receivedd */
+ IDLE_MESSAGE = 0x400,
};
/**
diff --git a/src/inotify_queue.c b/src/inotify_queue.c
index 5391a1715..d5e2228c3 100644
--- a/src/inotify_queue.c
+++ b/src/inotify_queue.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/inotify_queue.h b/src/inotify_queue.h
index 2e43d2f25..cfc28ebfe 100644
--- a/src/inotify_queue.h
+++ b/src/inotify_queue.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/inotify_source.c b/src/inotify_source.c
index 3a986cbad..e415f5e72 100644
--- a/src/inotify_source.c
+++ b/src/inotify_source.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/inotify_source.h b/src/inotify_source.h
index e78b92c0f..f92e18e39 100644
--- a/src/inotify_source.h
+++ b/src/inotify_source.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/inotify_update.c b/src/inotify_update.c
index 8d9657961..02e55ee0b 100644
--- a/src/inotify_update.c
+++ b/src/inotify_update.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -266,18 +266,19 @@ mpd_inotify_callback(int wd, unsigned mask,
(mask & IN_ISDIR) != 0) {
/* a sub directory was changed: register those in
inotify */
- char *root = map_directory_fs(db_get_root());
- char *path_fs;
-
- if (uri_fs != NULL) {
- path_fs = g_strconcat(root, "/", uri_fs, NULL);
- g_free(root);
- } else
+ const char *root = mapper_get_music_directory();
+ const char *path_fs;
+ char *allocated = NULL;
+
+ if (uri_fs != NULL)
+ path_fs = allocated =
+ g_strconcat(root, "/", uri_fs, NULL);
+ else
path_fs = root;
recursive_watch_subdirectories(directory, path_fs,
watch_directory_depth(directory));
- g_free(path_fs);
+ g_free(allocated);
}
if ((mask & (IN_CLOSE_WRITE|IN_MOVE|IN_DELETE)) != 0 ||
@@ -303,21 +304,13 @@ mpd_inotify_callback(int wd, unsigned mask,
void
mpd_inotify_init(unsigned max_depth)
{
- struct directory *root;
- char *path;
GError *error = NULL;
g_debug("initializing inotify");
- root = db_get_root();
- if (root == NULL) {
- g_debug("no music directory configured");
- return;
- }
-
- path = map_directory_fs(root);
+ const char *path = mapper_get_music_directory();
if (path == NULL) {
- g_warning("mapper has failed");
+ g_debug("no music directory configured");
return;
}
@@ -326,13 +319,12 @@ mpd_inotify_init(unsigned max_depth)
if (inotify_source == NULL) {
g_warning("%s", error->message);
g_error_free(error);
- g_free(path);
return;
}
inotify_max_depth = max_depth;
- inotify_root.name = path;
+ inotify_root.name = g_strdup(path);
inotify_root.descriptor = mpd_inotify_source_add(inotify_source, path,
IN_MASK, &error);
if (inotify_root.descriptor < 0) {
@@ -340,7 +332,6 @@ mpd_inotify_init(unsigned max_depth)
g_error_free(error);
mpd_inotify_source_free(inotify_source);
inotify_source = NULL;
- g_free(path);
return;
}
diff --git a/src/inotify_update.h b/src/inotify_update.h
index 92b4e0cc6..ca75c0f45 100644
--- a/src/inotify_update.h
+++ b/src/inotify_update.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/input/archive_input_plugin.c b/src/input/archive_input_plugin.c
index 97e4836ff..4a038b9e2 100644
--- a/src/input/archive_input_plugin.c
+++ b/src/input/archive_input_plugin.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -34,7 +34,9 @@
* plugin and gzip fetches file from disk
*/
static struct input_stream *
-input_archive_open(const char *pathname, GError **error_r)
+input_archive_open(const char *pathname,
+ GMutex *mutex, GCond *cond,
+ GError **error_r)
{
const struct archive_plugin *arplug;
struct archive_file *file;
@@ -65,7 +67,8 @@ input_archive_open(const char *pathname, GError **error_r)
return NULL;
//setup fileops
- is = archive_file_open_stream(file, filename, error_r);
+ is = archive_file_open_stream(file, filename, mutex, cond,
+ error_r);
archive_file_close(file);
g_free(pname);
diff --git a/src/input/archive_input_plugin.h b/src/input/archive_input_plugin.h
index 20568cfbe..51095f37f 100644
--- a/src/input/archive_input_plugin.h
+++ b/src/input/archive_input_plugin.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/input/cdio_paranoia_input_plugin.c b/src/input/cdio_paranoia_input_plugin.c
new file mode 100644
index 000000000..1de7623a1
--- /dev/null
+++ b/src/input/cdio_paranoia_input_plugin.c
@@ -0,0 +1,371 @@
+/*
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/**
+ * CD-Audio handling (requires libcdio_paranoia)
+ */
+
+#include "config.h"
+#include "input/cdio_paranoia_input_plugin.h"
+#include "input_internal.h"
+#include "input_plugin.h"
+#include "refcount.h"
+
+#include <stdio.h>
+#include <stdint.h>
+#include <stddef.h>
+#include <string.h>
+#include <stdlib.h>
+#include <glib.h>
+#include <assert.h>
+
+#include <cdio/paranoia.h>
+#include <cdio/cd_types.h>
+
+struct input_cdio_paranoia {
+ struct input_stream base;
+
+ cdrom_drive_t *drv;
+ CdIo_t *cdio;
+ cdrom_paranoia_t *para;
+
+ lsn_t lsn_from, lsn_to;
+ int lsn_relofs;
+
+ int trackno;
+
+ char buffer[CDIO_CD_FRAMESIZE_RAW];
+ int buffer_lsn;
+};
+
+static inline GQuark
+cdio_quark(void)
+{
+ return g_quark_from_static_string("cdio");
+}
+
+static void
+input_cdio_close(struct input_stream *is)
+{
+ struct input_cdio_paranoia *i = (struct input_cdio_paranoia *)is;
+
+ if (i->para)
+ cdio_paranoia_free(i->para);
+ if (i->drv)
+ cdio_cddap_close_no_free_cdio( i->drv);
+ if (i->cdio)
+ cdio_destroy( i->cdio );
+
+ input_stream_deinit(&i->base);
+ g_free(i);
+}
+
+struct cdio_uri {
+ char device[64];
+ int track;
+};
+
+static bool
+parse_cdio_uri(struct cdio_uri *dest, const char *src, GError **error_r)
+{
+ if (!g_str_has_prefix(src, "cdda://"))
+ return false;
+
+ src += 7;
+
+ if (*src == 0) {
+ /* play the whole CD in the default drive */
+ dest->device[0] = 0;
+ dest->track = -1;
+ return true;
+ }
+
+ const char *slash = strrchr(src, '/');
+ if (slash == NULL) {
+ /* play the whole CD in the specified drive */
+ g_strlcpy(dest->device, src, sizeof(dest->device));
+ dest->track = -1;
+ return true;
+ }
+
+ size_t device_length = slash - src;
+ if (device_length >= sizeof(dest->device))
+ device_length = sizeof(dest->device) - 1;
+
+ memcpy(dest->device, src, device_length);
+ dest->device[device_length] = 0;
+
+ const char *track = slash + 1;
+
+ char *endptr;
+ dest->track = strtoul(track, &endptr, 10);
+ if (*endptr != 0) {
+ g_set_error(error_r, cdio_quark(), 0,
+ "Malformed track number");
+ return false;
+ }
+
+ if (endptr == track)
+ /* play the whole CD */
+ dest->track = -1;
+
+ return true;
+}
+
+static char *
+cdio_detect_device(void)
+{
+ char **devices = cdio_get_devices_with_cap(NULL, CDIO_FS_AUDIO, false);
+ if (devices == NULL)
+ return NULL;
+
+ char *device = g_strdup(devices[0]);
+ cdio_free_device_list(devices);
+
+ return device;
+}
+
+static struct input_stream *
+input_cdio_open(const char *uri,
+ GMutex *mutex, GCond *cond,
+ GError **error_r)
+{
+ struct input_cdio_paranoia *i;
+
+ struct cdio_uri parsed_uri;
+ if (!parse_cdio_uri(&parsed_uri, uri, error_r))
+ return NULL;
+
+ i = g_new(struct input_cdio_paranoia, 1);
+ input_stream_init(&i->base, &input_plugin_cdio_paranoia, uri,
+ mutex, cond);
+
+ /* initialize everything (should be already) */
+ i->drv = NULL;
+ i->cdio = NULL;
+ i->para = NULL;
+ i->trackno = parsed_uri.track;
+
+ /* get list of CD's supporting CD-DA */
+ char *device = parsed_uri.device[0] != 0
+ ? g_strdup(parsed_uri.device)
+ : cdio_detect_device();
+ if (device == NULL) {
+ g_set_error(error_r, cdio_quark(), 0,
+ "Unable find or access a CD-ROM drive with an audio CD in it.");
+ input_cdio_close(&i->base);
+ return NULL;
+ }
+
+ /* Found such a CD-ROM with a CD-DA loaded. Use the first drive in the list. */
+ i->cdio = cdio_open(device, DRIVER_UNKNOWN);
+ g_free(device);
+
+ i->drv = cdio_cddap_identify_cdio(i->cdio, 1, NULL);
+
+ if ( !i->drv ) {
+ g_set_error(error_r, cdio_quark(), 0,
+ "Unable to identify audio CD disc.");
+ input_cdio_close(&i->base);
+ return NULL;
+ }
+
+ cdda_verbose_set(i->drv, CDDA_MESSAGE_FORGETIT, CDDA_MESSAGE_FORGETIT);
+
+ if ( 0 != cdio_cddap_open(i->drv) ) {
+ g_set_error(error_r, cdio_quark(), 0, "Unable to open disc.");
+ input_cdio_close(&i->base);
+ return NULL;
+ }
+
+ bool reverse_endian;
+ switch (data_bigendianp(i->drv)) {
+ case -1:
+ g_debug("cdda: drive returns unknown audio data");
+ reverse_endian = false;
+ break;
+ case 0:
+ g_debug("cdda: drive returns audio data Little Endian.");
+ reverse_endian = G_BYTE_ORDER == G_BIG_ENDIAN;
+ break;
+ case 1:
+ g_debug("cdda: drive returns audio data Big Endian.");
+ reverse_endian = G_BYTE_ORDER == G_LITTLE_ENDIAN;
+ break;
+ default:
+ g_set_error(error_r, cdio_quark(), 0,
+ "Drive returns unknown data type %d",
+ data_bigendianp(i->drv));
+ input_cdio_close(&i->base);
+ return NULL;
+ }
+
+ i->lsn_relofs = 0;
+
+ if (i->trackno >= 0) {
+ i->lsn_from = cdio_get_track_lsn(i->cdio, i->trackno);
+ i->lsn_to = cdio_get_track_last_lsn(i->cdio, i->trackno);
+ } else {
+ i->lsn_from = 0;
+ i->lsn_to = cdio_get_disc_last_lsn(i->cdio);
+ }
+
+ i->para = cdio_paranoia_init(i->drv);
+
+ /* Set reading mode for full paranoia, but allow skipping sectors. */
+ paranoia_modeset(i->para, PARANOIA_MODE_FULL^PARANOIA_MODE_NEVERSKIP);
+
+ /* seek to beginning of the track */
+ cdio_paranoia_seek(i->para, i->lsn_from, SEEK_SET);
+
+ i->base.ready = true;
+ i->base.seekable = true;
+ i->base.size = (i->lsn_to - i->lsn_from + 1) * CDIO_CD_FRAMESIZE_RAW;
+
+ /* hack to make MPD select the "pcm" decoder plugin */
+ i->base.mime = g_strdup(reverse_endian
+ ? "audio/x-mpd-cdda-pcm-reverse"
+ : "audio/x-mpd-cdda-pcm");
+
+ return &i->base;
+}
+
+static bool
+input_cdio_seek(struct input_stream *is,
+ goffset offset, int whence, GError **error_r)
+{
+ struct input_cdio_paranoia *cis = (struct input_cdio_paranoia *)is;
+
+ /* calculate absolute offset */
+ switch (whence) {
+ case SEEK_SET:
+ break;
+ case SEEK_CUR:
+ offset += cis->base.offset;
+ break;
+ case SEEK_END:
+ offset += cis->base.size;
+ break;
+ }
+
+ if (offset < 0 || offset > cis->base.size) {
+ g_set_error(error_r, cdio_quark(), 0,
+ "Invalid offset to seek %ld (%ld)",
+ (long int)offset, (long int)cis->base.size);
+ return false;
+ }
+
+ /* simple case */
+ if (offset == cis->base.offset)
+ return true;
+
+ /* calculate current LSN */
+ cis->lsn_relofs = offset / CDIO_CD_FRAMESIZE_RAW;
+ cis->base.offset = offset;
+
+ cdio_paranoia_seek(cis->para, cis->lsn_from + cis->lsn_relofs, SEEK_SET);
+
+ return true;
+}
+
+static size_t
+input_cdio_read(struct input_stream *is, void *ptr, size_t length,
+ GError **error_r)
+{
+ struct input_cdio_paranoia *cis = (struct input_cdio_paranoia *)is;
+ size_t nbytes = 0;
+ int diff;
+ size_t len, maxwrite;
+ int16_t *rbuf;
+ char *s_err, *s_mess;
+ char *wptr = (char *) ptr;
+
+ while (length > 0) {
+
+
+ /* end of track ? */
+ if (cis->lsn_from + cis->lsn_relofs > cis->lsn_to)
+ break;
+
+ //current sector was changed ?
+ if (cis->lsn_relofs != cis->buffer_lsn) {
+ rbuf = cdio_paranoia_read(cis->para, NULL);
+
+ s_err = cdda_errors(cis->drv);
+ if (s_err) {
+ g_warning("paranoia_read: %s", s_err );
+ free(s_err);
+ }
+ s_mess = cdda_messages(cis->drv);
+ if (s_mess) {
+ free(s_mess);
+ }
+ if (!rbuf) {
+ g_set_error(error_r, cdio_quark(), 0,
+ "paranoia read error. Stopping.");
+ return 0;
+ }
+ //store current buffer
+ memcpy(cis->buffer, rbuf, CDIO_CD_FRAMESIZE_RAW);
+ cis->buffer_lsn = cis->lsn_relofs;
+ } else {
+ //use cached sector
+ rbuf = (int16_t*) cis->buffer;
+ }
+
+ //correct offset
+ diff = cis->base.offset - cis->lsn_relofs * CDIO_CD_FRAMESIZE_RAW;
+
+ assert(diff >= 0 && diff < CDIO_CD_FRAMESIZE_RAW);
+
+ maxwrite = CDIO_CD_FRAMESIZE_RAW - diff; //# of bytes pending in current buffer
+ len = (length < maxwrite? length : maxwrite);
+
+ //skip diff bytes from this lsn
+ memcpy(wptr, ((char*)rbuf) + diff, len);
+ //update pointer
+ wptr += len;
+ nbytes += len;
+
+ //update offset
+ cis->base.offset += len;
+ cis->lsn_relofs = cis->base.offset / CDIO_CD_FRAMESIZE_RAW;
+ //update length
+ length -= len;
+ }
+
+ return nbytes;
+}
+
+static bool
+input_cdio_eof(struct input_stream *is)
+{
+ struct input_cdio_paranoia *cis = (struct input_cdio_paranoia *)is;
+
+ return (cis->lsn_from + cis->lsn_relofs > cis->lsn_to);
+}
+
+const struct input_plugin input_plugin_cdio_paranoia = {
+ .name = "cdio_paranoia",
+ .open = input_cdio_open,
+ .close = input_cdio_close,
+ .seek = input_cdio_seek,
+ .read = input_cdio_read,
+ .eof = input_cdio_eof
+};
diff --git a/src/input/cdio_paranoia_input_plugin.h b/src/input/cdio_paranoia_input_plugin.h
new file mode 100644
index 000000000..71c5cbe8d
--- /dev/null
+++ b/src/input/cdio_paranoia_input_plugin.h
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_CDIO_PARANOIA_INPUT_PLUGIN_H
+#define MPD_CDIO_PARANOIA_INPUT_PLUGIN_H
+
+/**
+ * An input plugin based on libcdio_paranoia library.
+ */
+extern const struct input_plugin input_plugin_cdio_paranoia;
+
+#endif
diff --git a/src/input/curl_input_plugin.c b/src/input/curl_input_plugin.c
index 604965dd1..3f191141e 100644
--- a/src/input/curl_input_plugin.c
+++ b/src/input/curl_input_plugin.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -19,10 +19,12 @@
#include "config.h"
#include "input/curl_input_plugin.h"
+#include "input_internal.h"
#include "input_plugin.h"
#include "conf.h"
#include "tag.h"
#include "icy_metadata.h"
+#include "io_thread.h"
#include "glib_compat.h"
#include <assert.h>
@@ -50,6 +52,11 @@
static const size_t CURL_MAX_BUFFERED = 512 * 1024;
/**
+ * Resume the stream at this number of bytes after it has been paused.
+ */
+static const size_t CURL_RESUME_AT = 384 * 1024;
+
+/**
* Buffers created by input_curl_writefunction().
*/
struct buffer {
@@ -73,17 +80,29 @@ struct input_curl {
/** the curl handles */
CURL *easy;
- CURLM *multi;
+
+ /** the GMainLoop source used to poll all CURL file
+ descriptors */
+ GSource *source;
+
+ /** the source id of #source */
+ guint source_id;
+
+ /** a linked list of all registered GPollFD objects */
+ GSList *fds;
/** list of buffers, where input_curl_writefunction() appends
to, and input_curl_read() reads from them */
GQueue *buffers;
- /** has something been added to the buffers list? */
- bool buffered;
-
- /** did libcurl tell us the we're at the end of the response body? */
- bool eof;
+#if LIBCURL_VERSION_NUM >= 0x071200
+ /**
+ * Is the connection currently paused? That happens when the
+ * buffer was getting too large. It will be unpaused when the
+ * buffer is below the threshold again.
+ */
+ bool paused;
+#endif
/** error message provided by libcurl */
char error[CURL_ERROR_SIZE];
@@ -97,6 +116,8 @@ struct input_curl {
/** the tag object ready to be requested via
input_stream_tag() */
struct tag *tag;
+
+ GError *postponed_error;
};
/** libcurl should accept "ICY 200 OK" */
@@ -106,20 +127,520 @@ static struct curl_slist *http_200_aliases;
static const char *proxy, *proxy_user, *proxy_password;
static unsigned proxy_port;
+static struct {
+ CURLM *multi;
+
+ /**
+ * A linked list of all active HTTP requests. An active
+ * request is one that doesn't have the "eof" flag set.
+ */
+ GSList *requests;
+
+ /**
+ * The GMainLoop source used to poll all CURL file
+ * descriptors.
+ */
+ GSource *source;
+
+ /**
+ * The source id of #source.
+ */
+ guint source_id;
+
+ GSList *fds;
+
+#if LIBCURL_VERSION_NUM >= 0x070f04
+ /**
+ * Did CURL give us a timeout? If yes, then we need to call
+ * curl_multi_perform(), even if there was no event on any
+ * file descriptor.
+ */
+ bool timeout;
+
+ /**
+ * The absolute time stamp when the timeout expires. This is
+ * used in the GSource method check().
+ */
+ gint64 absolute_timeout;
+#endif
+} curl;
+
static inline GQuark
curl_quark(void)
{
return g_quark_from_static_string("curl");
}
+/**
+ * Find a request by its CURL "easy" handle.
+ *
+ * Runs in the I/O thread. No lock needed.
+ */
+static struct input_curl *
+input_curl_find_request(CURL *easy)
+{
+ assert(io_thread_inside());
+
+ for (GSList *i = curl.requests; i != NULL; i = g_slist_next(i)) {
+ struct input_curl *c = i->data;
+ if (c->easy == easy)
+ return c;
+ }
+
+ return NULL;
+}
+
+#if LIBCURL_VERSION_NUM >= 0x071200
+
+static gpointer
+input_curl_resume(gpointer data)
+{
+ assert(io_thread_inside());
+
+ struct input_curl *c = data;
+
+ if (c->paused) {
+ c->paused = false;
+ curl_easy_pause(c->easy, CURLPAUSE_CONT);
+ }
+
+ return NULL;
+}
+
+#endif
+
+/**
+ * Calculates the GLib event bit mask for one file descriptor,
+ * obtained from three #fd_set objects filled by curl_multi_fdset().
+ */
+static gushort
+input_curl_fd_events(int fd, fd_set *rfds, fd_set *wfds, fd_set *efds)
+{
+ gushort events = 0;
+
+ if (FD_ISSET(fd, rfds)) {
+ events |= G_IO_IN | G_IO_HUP | G_IO_ERR;
+ FD_CLR(fd, rfds);
+ }
+
+ if (FD_ISSET(fd, wfds)) {
+ events |= G_IO_OUT | G_IO_ERR;
+ FD_CLR(fd, wfds);
+ }
+
+ if (FD_ISSET(fd, efds)) {
+ events |= G_IO_HUP | G_IO_ERR;
+ FD_CLR(fd, efds);
+ }
+
+ return events;
+}
+
+/**
+ * Updates all registered GPollFD objects, unregisters old ones,
+ * registers new ones.
+ *
+ * Runs in the I/O thread. No lock needed.
+ */
+static void
+curl_update_fds(void)
+{
+ assert(io_thread_inside());
+
+ fd_set rfds, wfds, efds;
+
+ FD_ZERO(&rfds);
+ FD_ZERO(&wfds);
+ FD_ZERO(&efds);
+
+ int max_fd;
+ CURLMcode mcode = curl_multi_fdset(curl.multi, &rfds, &wfds,
+ &efds, &max_fd);
+ if (mcode != CURLM_OK) {
+ g_warning("curl_multi_fdset() failed: %s\n",
+ curl_multi_strerror(mcode));
+ return;
+ }
+
+ GSList *fds = curl.fds;
+ curl.fds = NULL;
+
+ while (fds != NULL) {
+ GPollFD *poll_fd = fds->data;
+ gushort events = input_curl_fd_events(poll_fd->fd, &rfds,
+ &wfds, &efds);
+
+ assert(poll_fd->events != 0);
+
+ fds = g_slist_remove(fds, poll_fd);
+
+ if (events != poll_fd->events)
+ g_source_remove_poll(curl.source, poll_fd);
+
+ if (events != 0) {
+ if (events != poll_fd->events) {
+ poll_fd->events = events;
+ g_source_add_poll(curl.source, poll_fd);
+ }
+
+ curl.fds = g_slist_prepend(curl.fds, poll_fd);
+ } else {
+ g_free(poll_fd);
+ }
+ }
+
+ for (int fd = 0; fd <= max_fd; ++fd) {
+ gushort events = input_curl_fd_events(fd, &rfds, &wfds, &efds);
+ if (events != 0) {
+ GPollFD *poll_fd = g_new(GPollFD, 1);
+ poll_fd->fd = fd;
+ poll_fd->events = events;
+ g_source_add_poll(curl.source, poll_fd);
+ curl.fds = g_slist_prepend(curl.fds, poll_fd);
+ }
+ }
+}
+
+/**
+ * Runs in the I/O thread. No lock needed.
+ */
+static bool
+input_curl_easy_add(struct input_curl *c, GError **error_r)
+{
+ assert(io_thread_inside());
+ assert(c != NULL);
+ assert(c->easy != NULL);
+ assert(input_curl_find_request(c->easy) == NULL);
+
+ curl.requests = g_slist_prepend(curl.requests, c);
+
+ CURLMcode mcode = curl_multi_add_handle(curl.multi, c->easy);
+ if (mcode != CURLM_OK) {
+ g_set_error(error_r, curl_quark(), mcode,
+ "curl_multi_add_handle() failed: %s",
+ curl_multi_strerror(mcode));
+ return false;
+ }
+
+ curl_update_fds();
+
+ return true;
+}
+
+struct easy_add_params {
+ struct input_curl *c;
+ GError **error_r;
+};
+
+static gpointer
+input_curl_easy_add_callback(gpointer data)
+{
+ const struct easy_add_params *params = data;
+
+ bool success = input_curl_easy_add(params->c, params->error_r);
+ return GUINT_TO_POINTER(success);
+}
+
+/**
+ * Call input_curl_easy_add() in the I/O thread. May be called from
+ * any thread. Caller must not hold a mutex.
+ */
+static bool
+input_curl_easy_add_indirect(struct input_curl *c, GError **error_r)
+{
+ assert(c != NULL);
+ assert(c->easy != NULL);
+
+ struct easy_add_params params = {
+ .c = c,
+ .error_r = error_r,
+ };
+
+ gpointer result =
+ io_thread_call(input_curl_easy_add_callback, &params);
+ return GPOINTER_TO_UINT(result);
+}
+
+/**
+ * Frees the current "libcurl easy" handle, and everything associated
+ * with it.
+ *
+ * Runs in the I/O thread.
+ */
+static void
+input_curl_easy_free(struct input_curl *c)
+{
+ assert(io_thread_inside());
+ assert(c != NULL);
+
+ if (c->easy == NULL)
+ return;
+
+ curl.requests = g_slist_remove(curl.requests, c);
+
+ curl_multi_remove_handle(curl.multi, c->easy);
+ curl_easy_cleanup(c->easy);
+ c->easy = NULL;
+
+ curl_slist_free_all(c->request_headers);
+ c->request_headers = NULL;
+
+ g_free(c->range);
+ c->range = NULL;
+}
+
+static gpointer
+input_curl_easy_free_callback(gpointer data)
+{
+ struct input_curl *c = data;
+
+ input_curl_easy_free(c);
+ curl_update_fds();
+
+ return NULL;
+}
+
+/**
+ * Frees the current "libcurl easy" handle, and everything associated
+ * with it.
+ *
+ * The mutex must not be locked.
+ */
+static void
+input_curl_easy_free_indirect(struct input_curl *c)
+{
+ io_thread_call(input_curl_easy_free_callback, c);
+ assert(c->easy == NULL);
+}
+
+/**
+ * Abort and free all HTTP requests.
+ *
+ * Runs in the I/O thread. The caller must not hold locks.
+ */
+static void
+input_curl_abort_all_requests(GError *error)
+{
+ assert(io_thread_inside());
+ assert(error != NULL);
+
+ while (curl.requests != NULL) {
+ struct input_curl *c = curl.requests->data;
+ assert(c->postponed_error == NULL);
+
+ input_curl_easy_free(c);
+
+ g_mutex_lock(c->base.mutex);
+ c->postponed_error = g_error_copy(error);
+ c->base.ready = true;
+ g_cond_broadcast(c->base.cond);
+ g_mutex_unlock(c->base.mutex);
+ }
+
+ g_error_free(error);
+
+}
+
+/**
+ * A HTTP request is finished.
+ *
+ * Runs in the I/O thread. The caller must not hold locks.
+ */
+static void
+input_curl_request_done(struct input_curl *c, CURLcode result, long status)
+{
+ assert(io_thread_inside());
+ assert(c != NULL);
+ assert(c->easy == NULL);
+ assert(c->postponed_error == NULL);
+
+ g_mutex_lock(c->base.mutex);
+
+ if (result != CURLE_OK) {
+ c->postponed_error = g_error_new(curl_quark(), result,
+ "curl failed: %s",
+ c->error);
+ } else if (status < 200 || status >= 300) {
+ c->postponed_error = g_error_new(curl_quark(), 0,
+ "got HTTP status %ld",
+ status);
+ }
+
+ c->base.ready = true;
+ g_cond_broadcast(c->base.cond);
+ g_mutex_unlock(c->base.mutex);
+}
+
+static void
+input_curl_handle_done(CURL *easy_handle, CURLcode result)
+{
+ struct input_curl *c = input_curl_find_request(easy_handle);
+ assert(c != NULL);
+
+ long status = 0;
+ curl_easy_getinfo(easy_handle, CURLINFO_RESPONSE_CODE, &status);
+
+ input_curl_easy_free(c);
+ input_curl_request_done(c, result, status);
+}
+
+/**
+ * Check for finished HTTP responses.
+ *
+ * Runs in the I/O thread. The caller must not hold locks.
+ */
+static void
+input_curl_info_read(void)
+{
+ assert(io_thread_inside());
+
+ CURLMsg *msg;
+ int msgs_in_queue;
+
+ while ((msg = curl_multi_info_read(curl.multi,
+ &msgs_in_queue)) != NULL) {
+ if (msg->msg == CURLMSG_DONE)
+ input_curl_handle_done(msg->easy_handle, msg->data.result);
+ }
+}
+
+/**
+ * Give control to CURL.
+ *
+ * Runs in the I/O thread. The caller must not hold locks.
+ */
+static bool
+input_curl_perform(void)
+{
+ assert(io_thread_inside());
+
+ CURLMcode mcode;
+
+ do {
+ int running_handles;
+ mcode = curl_multi_perform(curl.multi, &running_handles);
+ } while (mcode == CURLM_CALL_MULTI_PERFORM);
+
+ if (mcode != CURLM_OK && mcode != CURLM_CALL_MULTI_PERFORM) {
+ GError *error = g_error_new(curl_quark(), mcode,
+ "curl_multi_perform() failed: %s",
+ curl_multi_strerror(mcode));
+ input_curl_abort_all_requests(error);
+ return false;
+ }
+
+ return true;
+}
+
+/*
+ * GSource methods
+ *
+ */
+
+/**
+ * The GSource prepare() method implementation.
+ */
+static gboolean
+input_curl_source_prepare(G_GNUC_UNUSED GSource *source, gint *timeout_r)
+{
+ curl_update_fds();
+
+#if LIBCURL_VERSION_NUM >= 0x070f04
+ curl.timeout = false;
+
+ long timeout2;
+ CURLMcode mcode = curl_multi_timeout(curl.multi, &timeout2);
+ if (mcode == CURLM_OK) {
+ if (timeout2 >= 0)
+ curl.absolute_timeout = g_source_get_time(source)
+ + timeout2 * 1000;
+
+ if (timeout2 >= 0 && timeout2 < 10)
+ /* CURL 7.21.1 likes to report "timeout=0",
+ which means we're running in a busy loop.
+ Quite a bad idea to waste so much CPU.
+ Let's use a lower limit of 10ms. */
+ timeout2 = 10;
+
+ *timeout_r = timeout2;
+
+ curl.timeout = timeout2 >= 0;
+ } else
+ g_warning("curl_multi_timeout() failed: %s\n",
+ curl_multi_strerror(mcode));
+#else
+ (void)timeout_r;
+#endif
+
+ return false;
+}
+
+/**
+ * The GSource check() method implementation.
+ */
+static gboolean
+input_curl_source_check(G_GNUC_UNUSED GSource *source)
+{
+#if LIBCURL_VERSION_NUM >= 0x070f04
+ if (curl.timeout) {
+ /* when a timeout has expired, we need to call
+ curl_multi_perform(), even if there was no file
+ descriptor event */
+
+ if (g_source_get_time(source) >= curl.absolute_timeout)
+ return true;
+ }
+#endif
+
+ for (GSList *i = curl.fds; i != NULL; i = i->next) {
+ GPollFD *poll_fd = i->data;
+ if (poll_fd->revents != 0)
+ return true;
+ }
+
+ return false;
+}
+
+/**
+ * The GSource dispatch() method implementation. The callback isn't
+ * used, because we're handling all events directly.
+ */
+static gboolean
+input_curl_source_dispatch(G_GNUC_UNUSED GSource *source,
+ G_GNUC_UNUSED GSourceFunc callback,
+ G_GNUC_UNUSED gpointer user_data)
+{
+ if (input_curl_perform())
+ input_curl_info_read();
+
+ return true;
+}
+
+/**
+ * The vtable for our GSource implementation. Unfortunately, we
+ * cannot declare it "const", because g_source_new() takes a non-const
+ * pointer, for whatever reason.
+ */
+static GSourceFuncs curl_source_funcs = {
+ .prepare = input_curl_source_prepare,
+ .check = input_curl_source_check,
+ .dispatch = input_curl_source_dispatch,
+};
+
+/*
+ * input_plugin methods
+ *
+ */
+
static bool
input_curl_init(const struct config_param *param,
G_GNUC_UNUSED GError **error_r)
{
CURLcode code = curl_global_init(CURL_GLOBAL_ALL);
if (code != CURLE_OK) {
- g_warning("curl_global_init() failed: %s\n",
- curl_easy_strerror(code));
+ g_set_error(error_r, curl_quark(), code,
+ "curl_global_init() failed: %s\n",
+ curl_easy_strerror(code));
return false;
}
@@ -140,20 +661,48 @@ input_curl_init(const struct config_param *param,
"");
}
+ curl.multi = curl_multi_init();
+ if (curl.multi == NULL) {
+ g_set_error(error_r, curl_quark(), 0,
+ "curl_multi_init() failed");
+ return false;
+ }
+
+ curl.source = g_source_new(&curl_source_funcs, sizeof(*curl.source));
+ curl.source_id = g_source_attach(curl.source, io_thread_context());
+
return true;
}
+static gpointer
+curl_destroy_sources(G_GNUC_UNUSED gpointer data)
+{
+ g_source_destroy(curl.source);
+
+ return NULL;
+}
+
static void
input_curl_finish(void)
{
+ assert(curl.requests == NULL);
+
+ io_thread_call(curl_destroy_sources, NULL);
+
+ curl_multi_cleanup(curl.multi);
+
curl_slist_free_all(http_200_aliases);
curl_global_cleanup();
}
+#if LIBCURL_VERSION_NUM >= 0x071200
+
/**
* Determine the total sizes of all buffers, including portions that
* have already been consumed.
+ *
+ * The caller must lock the mutex.
*/
G_GNUC_PURE
static size_t
@@ -170,6 +719,8 @@ curl_total_buffer_size(const struct input_curl *c)
return total;
}
+#endif
+
static void
buffer_free_callback(gpointer data, G_GNUC_UNUSED gpointer user_data)
{
@@ -180,31 +731,15 @@ buffer_free_callback(gpointer data, G_GNUC_UNUSED gpointer user_data)
g_free(buffer);
}
-/**
- * Frees the current "libcurl easy" handle, and everything associated
- * with it.
- */
static void
-input_curl_easy_free(struct input_curl *c)
+input_curl_flush_buffers(struct input_curl *c)
{
- if (c->easy != NULL) {
- curl_multi_remove_handle(c->multi, c->easy);
- curl_easy_cleanup(c->easy);
- c->easy = NULL;
- }
-
- curl_slist_free_all(c->request_headers);
- c->request_headers = NULL;
-
- g_free(c->range);
- c->range = NULL;
-
g_queue_foreach(c->buffers, buffer_free_callback, NULL);
g_queue_clear(c->buffers);
}
/**
- * Frees this stream (but not the input_stream struct itself).
+ * Frees this stream, including the input_stream struct.
*/
static void
input_curl_free(struct input_curl *c)
@@ -213,142 +748,53 @@ input_curl_free(struct input_curl *c)
tag_free(c->tag);
g_free(c->meta_name);
- input_curl_easy_free(c);
-
- if (c->multi != NULL)
- curl_multi_cleanup(c->multi);
+ input_curl_easy_free_indirect(c);
+ input_curl_flush_buffers(c);
g_queue_free(c->buffers);
+ if (c->postponed_error != NULL)
+ g_error_free(c->postponed_error);
+
g_free(c->url);
input_stream_deinit(&c->base);
g_free(c);
}
-static struct tag *
-input_curl_tag(struct input_stream *is)
-{
- struct input_curl *c = (struct input_curl *)is;
- struct tag *tag = c->tag;
-
- c->tag = NULL;
- return tag;
-}
-
static bool
-input_curl_multi_info_read(struct input_curl *c, GError **error_r)
+input_curl_check(struct input_stream *is, GError **error_r)
{
- CURLMsg *msg;
- int msgs_in_queue;
+ struct input_curl *c = (struct input_curl *)is;
- while ((msg = curl_multi_info_read(c->multi,
- &msgs_in_queue)) != NULL) {
- if (msg->msg == CURLMSG_DONE) {
- c->eof = true;
- c->base.ready = true;
-
- if (msg->data.result != CURLE_OK) {
- g_set_error(error_r, curl_quark(),
- msg->data.result,
- "curl failed: %s", c->error);
- return false;
- }
- }
+ bool success = c->postponed_error == NULL;
+ if (!success) {
+ g_propagate_error(error_r, c->postponed_error);
+ c->postponed_error = NULL;
}
- return true;
+ return success;
}
-/**
- * Wait for the libcurl socket.
- *
- * @return -1 on error, 0 if no data is available yet, 1 if data is
- * available
- */
-static int
-input_curl_select(struct input_curl *c, GError **error_r)
+static struct tag *
+input_curl_tag(struct input_stream *is)
{
- fd_set rfds, wfds, efds;
- int max_fd, ret;
- CURLMcode mcode;
- struct timeval timeout = {
- .tv_sec = 1,
- .tv_usec = 0,
- };
-
- assert(!c->eof);
-
- FD_ZERO(&rfds);
- FD_ZERO(&wfds);
- FD_ZERO(&efds);
-
- mcode = curl_multi_fdset(c->multi, &rfds, &wfds, &efds, &max_fd);
- if (mcode != CURLM_OK) {
- g_set_error(error_r, curl_quark(), mcode,
- "curl_multi_fdset() failed: %s",
- curl_multi_strerror(mcode));
- return -1;
- }
-
-#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)
- g_set_error(error_r, g_quark_from_static_string("errno"),
- errno,
- "select() failed: %s\n", g_strerror(errno));
+ struct input_curl *c = (struct input_curl *)is;
+ struct tag *tag = c->tag;
- return ret;
+ c->tag = NULL;
+ return tag;
}
static bool
-fill_buffer(struct input_stream *is, GError **error_r)
+fill_buffer(struct input_curl *c, GError **error_r)
{
- struct input_curl *c = (struct input_curl *)is;
- CURLMcode mcode = CURLM_CALL_MULTI_PERFORM;
-
- while (!c->eof && g_queue_is_empty(c->buffers)) {
- int running_handles;
- bool bret;
-
- if (mcode != CURLM_CALL_MULTI_PERFORM) {
- /* if we're still here, there is no input yet
- - wait for input */
- int ret = input_curl_select(c, error_r);
- if (ret <= 0)
- /* no data yet or error */
- return false;
- }
-
- mcode = curl_multi_perform(c->multi, &running_handles);
- if (mcode != CURLM_OK && mcode != CURLM_CALL_MULTI_PERFORM) {
- g_set_error(error_r, curl_quark(), mcode,
- "curl_multi_perform() failed: %s",
- curl_multi_strerror(mcode));
- c->eof = true;
- is->ready = true;
- return false;
- }
+ while (c->easy != NULL && g_queue_is_empty(c->buffers))
+ g_cond_wait(c->base.cond, c->base.mutex);
- bret = input_curl_multi_info_read(c, error_r);
- if (!bret)
- return false;
+ if (c->postponed_error != NULL) {
+ g_propagate_error(error_r, c->postponed_error);
+ c->postponed_error = NULL;
+ return false;
}
return !g_queue_is_empty(c->buffers);
@@ -444,6 +890,15 @@ copy_icy_tag(struct input_curl *c)
c->tag = tag;
}
+static bool
+input_curl_available(struct input_stream *is)
+{
+ struct input_curl *c = (struct input_curl *)is;
+
+ return c->postponed_error != NULL || c->easy == NULL ||
+ !g_queue_is_empty(c->buffers);
+}
+
static size_t
input_curl_read(struct input_stream *is, void *ptr, size_t size,
GError **error_r)
@@ -456,7 +911,7 @@ input_curl_read(struct input_stream *is, void *ptr, size_t size,
do {
/* fill the buffer */
- success = fill_buffer(is, error_r);
+ success = fill_buffer(c, error_r);
if (!success)
return 0;
@@ -476,6 +931,14 @@ input_curl_read(struct input_stream *is, void *ptr, size_t size,
is->offset += (goffset)nbytes;
+#if LIBCURL_VERSION_NUM >= 0x071200
+ if (c->paused && curl_total_buffer_size(c) < CURL_RESUME_AT) {
+ g_mutex_unlock(c->base.mutex);
+ io_thread_call(input_curl_resume, c);
+ g_mutex_lock(c->base.mutex);
+ }
+#endif
+
return nbytes;
}
@@ -492,49 +955,7 @@ input_curl_eof(G_GNUC_UNUSED struct input_stream *is)
{
struct input_curl *c = (struct input_curl *)is;
- return c->eof && g_queue_is_empty(c->buffers);
-}
-
-static int
-input_curl_buffer(struct input_stream *is, GError **error_r)
-{
- struct input_curl *c = (struct input_curl *)is;
-
- if (curl_total_buffer_size(c) >= CURL_MAX_BUFFERED)
- return 0;
-
- CURLMcode mcode;
- int running_handles;
- bool ret;
-
- c->buffered = false;
-
- if (!is->ready && !c->eof)
- /* not ready yet means the caller is waiting in a busy
- loop; relax that by calling select() on the
- socket */
- if (input_curl_select(c, error_r) < 0)
- return -1;
-
- do {
- mcode = curl_multi_perform(c->multi, &running_handles);
- } while (mcode == CURLM_CALL_MULTI_PERFORM &&
- g_queue_is_empty(c->buffers));
-
- if (mcode != CURLM_OK && mcode != CURLM_CALL_MULTI_PERFORM) {
- g_set_error(error_r, curl_quark(), mcode,
- "curl_multi_perform() failed: %s",
- curl_multi_strerror(mcode));
- c->eof = true;
- is->ready = true;
- return -1;
- }
-
- ret = input_curl_multi_info_read(c, error_r);
- if (!ret)
- return -1;
-
- return c->buffered;
+ return c->easy == NULL && g_queue_is_empty(c->buffers);
}
/** called by curl when new data is available */
@@ -632,15 +1053,27 @@ input_curl_writefunction(void *ptr, size_t size, size_t nmemb, void *stream)
if (size == 0)
return 0;
+ g_mutex_lock(c->base.mutex);
+
+#if LIBCURL_VERSION_NUM >= 0x071200
+ if (curl_total_buffer_size(c) + size >= CURL_MAX_BUFFERED) {
+ c->paused = true;
+ g_mutex_unlock(c->base.mutex);
+ return CURL_WRITEFUNC_PAUSE;
+ }
+#endif
+
buffer = g_malloc(sizeof(*buffer) - sizeof(buffer->data) + size);
buffer->size = size;
buffer->consumed = 0;
memcpy(buffer->data, ptr, size);
- g_queue_push_tail(c->buffers, buffer);
- c->buffered = true;
+ g_queue_push_tail(c->buffers, buffer);
c->base.ready = true;
+ g_cond_broadcast(c->base.cond);
+ g_mutex_unlock(c->base.mutex);
+
return size;
}
@@ -648,9 +1081,6 @@ static bool
input_curl_easy_init(struct input_curl *c, GError **error_r)
{
CURLcode code;
- CURLMcode mcode;
-
- c->eof = false;
c->easy = curl_easy_init();
if (c->easy == NULL) {
@@ -659,14 +1089,6 @@ input_curl_easy_init(struct input_curl *c, GError **error_r)
return false;
}
- mcode = curl_multi_add_handle(c->multi, c->easy);
- if (mcode != CURLM_OK) {
- g_set_error(error_r, curl_quark(), mcode,
- "curl_multi_add_handle() failed: %s",
- curl_multi_strerror(mcode));
- return false;
- }
-
curl_easy_setopt(c->easy, CURLOPT_USERAGENT,
"Music Player Daemon " VERSION);
curl_easy_setopt(c->easy, CURLOPT_HEADERFUNCTION,
@@ -677,6 +1099,7 @@ input_curl_easy_init(struct input_curl *c, GError **error_r)
curl_easy_setopt(c->easy, CURLOPT_WRITEDATA, c);
curl_easy_setopt(c->easy, CURLOPT_HTTP200ALIASES, http_200_aliases);
curl_easy_setopt(c->easy, CURLOPT_FOLLOWLOCATION, 1);
+ curl_easy_setopt(c->easy, CURLOPT_NETRC, 1);
curl_easy_setopt(c->easy, CURLOPT_MAXREDIRS, 5);
curl_easy_setopt(c->easy, CURLOPT_FAILONERROR, true);
curl_easy_setopt(c->easy, CURLOPT_ERRORBUFFER, c->error);
@@ -713,38 +1136,6 @@ input_curl_easy_init(struct input_curl *c, GError **error_r)
return true;
}
-void
-input_curl_reinit(struct input_stream *is)
-{
- struct input_curl *c = (struct input_curl *)is;
-
- assert(c->base.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, GError **error_r)
-{
- CURLMcode mcode;
- int running_handles;
-
- do {
- mcode = curl_multi_perform(c->multi, &running_handles);
- } while (mcode == CURLM_CALL_MULTI_PERFORM);
-
- if (mcode != CURLM_OK) {
- g_set_error(error_r, curl_quark(), mcode,
- "curl_multi_perform() failed: %s",
- curl_multi_strerror(mcode));
- return false;
- }
-
- return true;
-}
-
static bool
input_curl_seek(struct input_stream *is, goffset offset, int whence,
GError **error_r)
@@ -810,14 +1201,16 @@ input_curl_seek(struct input_stream *is, goffset offset, int whence,
/* close the old connection and open a new one */
- input_curl_easy_free(c);
+ g_mutex_unlock(c->base.mutex);
+
+ input_curl_easy_free_indirect(c);
+ input_curl_flush_buffers(c);
is->offset = offset;
if (is->offset == is->size) {
/* seek to EOF: simulate empty result; avoid
triggering a "416 Requested Range Not Satisfiable"
response */
- c->eof = true;
return true;
}
@@ -832,53 +1225,59 @@ input_curl_seek(struct input_stream *is, goffset offset, int whence,
curl_easy_setopt(c->easy, CURLOPT_RANGE, c->range);
}
- ret = input_curl_send_request(c, error_r);
- if (!ret)
+ c->base.ready = false;
+
+ if (!input_curl_easy_add_indirect(c, error_r))
return false;
- return input_curl_multi_info_read(c, error_r);
+ g_mutex_lock(c->base.mutex);
+
+ while (!c->base.ready)
+ g_cond_wait(c->base.cond, c->base.mutex);
+
+ if (c->postponed_error != NULL) {
+ g_propagate_error(error_r, c->postponed_error);
+ c->postponed_error = NULL;
+ return false;
+ }
+
+ return true;
}
static struct input_stream *
-input_curl_open(const char *url, GError **error_r)
+input_curl_open(const char *url, GMutex *mutex, GCond *cond,
+ GError **error_r)
{
+ assert(mutex != NULL);
+ assert(cond != NULL);
+
struct input_curl *c;
- bool ret;
if (strncmp(url, "http://", 7) != 0)
return NULL;
c = g_new0(struct input_curl, 1);
- input_stream_init(&c->base, &input_plugin_curl, url);
+ input_stream_init(&c->base, &input_plugin_curl, url,
+ mutex, cond);
c->url = g_strdup(url);
c->buffers = g_queue_new();
- c->multi = curl_multi_init();
- if (c->multi == NULL) {
- g_set_error(error_r, curl_quark(), 0,
- "curl_multi_init() failed");
- input_curl_free(c);
- return NULL;
- }
-
icy_clear(&c->icy_metadata);
c->tag = NULL;
- ret = input_curl_easy_init(c, error_r);
- if (!ret) {
- input_curl_free(c);
- return NULL;
- }
+ c->postponed_error = NULL;
+
+#if LIBCURL_VERSION_NUM >= 0x071200
+ c->paused = false;
+#endif
- ret = input_curl_send_request(c, error_r);
- if (!ret) {
+ if (!input_curl_easy_init(c, error_r)) {
input_curl_free(c);
return NULL;
}
- ret = input_curl_multi_info_read(c, error_r);
- if (!ret) {
+ if (!input_curl_easy_add_indirect(c, error_r)) {
input_curl_free(c);
return NULL;
}
@@ -893,8 +1292,9 @@ const struct input_plugin input_plugin_curl = {
.open = input_curl_open,
.close = input_curl_close,
+ .check = input_curl_check,
.tag = input_curl_tag,
- .buffer = input_curl_buffer,
+ .available = input_curl_available,
.read = input_curl_read,
.eof = input_curl_eof,
.seek = input_curl_seek,
diff --git a/src/input/curl_input_plugin.h b/src/input/curl_input_plugin.h
index be7db4e26..c6e71bf40 100644
--- a/src/input/curl_input_plugin.h
+++ b/src/input/curl_input_plugin.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -24,12 +24,4 @@ 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
diff --git a/src/input/despotify_input_plugin.c b/src/input/despotify_input_plugin.c
new file mode 100644
index 000000000..200a0afd6
--- /dev/null
+++ b/src/input/despotify_input_plugin.c
@@ -0,0 +1,230 @@
+/*
+ * Copyright (C) 2011 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "input/despotify_input_plugin.h"
+#include "input_internal.h"
+#include "input_plugin.h"
+#include "tag.h"
+#include "despotify_utils.h"
+
+#include <glib.h>
+
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <despotify.h>
+
+#include <stdio.h>
+
+struct input_despotify {
+ struct input_stream base;
+
+ struct despotify_session *session;
+ struct ds_track *track;
+ struct tag *tag;
+ struct ds_pcm_data pcm;
+ size_t len_available;
+ bool eof;
+};
+
+
+static void
+refill_buffer(struct input_despotify *ctx)
+{
+ /* Wait until there is data */
+ while (1) {
+ int rc = despotify_get_pcm(ctx->session, &ctx->pcm);
+
+ if (rc == 0 && ctx->pcm.len) {
+ ctx->len_available = ctx->pcm.len;
+ break;
+ }
+ if (ctx->eof == true)
+ break;
+
+ if (rc < 0) {
+ g_debug("despotify_get_pcm error\n");
+ ctx->eof = true;
+ break;
+ }
+
+ /* Wait a while until next iteration */
+ usleep(50 * 1000);
+ }
+}
+
+static void callback(G_GNUC_UNUSED struct despotify_session* ds,
+ int sig, G_GNUC_UNUSED void* data, void* callback_data)
+{
+ struct input_despotify *ctx = (struct input_despotify *)callback_data;
+
+ switch (sig) {
+ case DESPOTIFY_NEW_TRACK:
+ break;
+
+ case DESPOTIFY_TIME_TELL:
+ break;
+
+ case DESPOTIFY_TRACK_PLAY_ERROR:
+ g_debug("Track play error\n");
+ ctx->eof = true;
+ ctx->len_available = 0;
+ break;
+
+ case DESPOTIFY_END_OF_PLAYLIST:
+ ctx->eof = true;
+ g_debug("End of playlist: %d\n", ctx->eof);
+ break;
+ }
+}
+
+
+static struct input_stream *
+input_despotify_open(const char *url,
+ GMutex *mutex, GCond *cond,
+ G_GNUC_UNUSED GError **error_r)
+{
+ struct input_despotify *ctx;
+ struct despotify_session *session;
+ struct ds_link *ds_link;
+ struct ds_track *track;
+
+ if (!g_str_has_prefix(url, "spt://"))
+ return NULL;
+
+ session = mpd_despotify_get_session();
+ if (!session)
+ return NULL;
+
+ ds_link = despotify_link_from_uri(url + 6);
+ if (!ds_link) {
+ g_debug("Can't find %s\n", url);
+ return NULL;
+ }
+ if (ds_link->type != LINK_TYPE_TRACK) {
+ despotify_free_link(ds_link);
+ return NULL;
+ }
+
+ ctx = g_new(struct input_despotify, 1);
+ memset(ctx, 0, sizeof(*ctx));
+
+ track = despotify_link_get_track(session, ds_link);
+ despotify_free_link(ds_link);
+ if (!track) {
+ g_free(ctx);
+ return NULL;
+ }
+
+ input_stream_init(&ctx->base, &input_plugin_despotify, url,
+ mutex, cond);
+ ctx->session = session;
+ ctx->track = track;
+ ctx->tag = mpd_despotify_tag_from_track(track);
+ ctx->eof = false;
+ /* Despotify outputs pcm data */
+ ctx->base.mime = g_strdup("audio/x-mpd-cdda-pcm");
+ ctx->base.ready = true;
+
+ if (!mpd_despotify_register_callback(callback, ctx)) {
+ despotify_free_link(ds_link);
+
+ return NULL;
+ }
+
+ if (despotify_play(ctx->session, ctx->track, false) == false) {
+ despotify_free_track(ctx->track);
+ g_free(ctx);
+ return NULL;
+ }
+
+ return &ctx->base;
+}
+
+static size_t
+input_despotify_read(struct input_stream *is, void *ptr, size_t size,
+ G_GNUC_UNUSED GError **error_r)
+{
+ struct input_despotify *ctx = (struct input_despotify *)is;
+ size_t to_cpy = size;
+
+ if (ctx->len_available == 0)
+ refill_buffer(ctx);
+
+ if (ctx->len_available < size)
+ to_cpy = ctx->len_available;
+ memcpy(ptr, ctx->pcm.buf, to_cpy);
+ ctx->len_available -= to_cpy;
+
+ is->offset += to_cpy;
+
+ return to_cpy;
+}
+
+static void
+input_despotify_close(struct input_stream *is)
+{
+ struct input_despotify *ctx = (struct input_despotify *)is;
+
+ if (ctx->tag != NULL)
+ tag_free(ctx->tag);
+
+ mpd_despotify_unregister_callback(callback);
+ despotify_free_track(ctx->track);
+ input_stream_deinit(&ctx->base);
+ g_free(ctx);
+}
+
+static bool
+input_despotify_eof(struct input_stream *is)
+{
+ struct input_despotify *ctx = (struct input_despotify *)is;
+
+ return ctx->eof;
+}
+
+static bool
+input_despotify_seek(G_GNUC_UNUSED struct input_stream *is,
+ G_GNUC_UNUSED goffset offset, G_GNUC_UNUSED int whence,
+ G_GNUC_UNUSED GError **error_r)
+{
+ return false;
+}
+
+static struct tag *
+input_despotify_tag(struct input_stream *is)
+{
+ struct input_despotify *ctx = (struct input_despotify *)is;
+ struct tag *tag = ctx->tag;
+
+ ctx->tag = NULL;
+
+ return tag;
+}
+
+const struct input_plugin input_plugin_despotify = {
+ .name = "spt",
+ .open = input_despotify_open,
+ .close = input_despotify_close,
+ .read = input_despotify_read,
+ .eof = input_despotify_eof,
+ .seek = input_despotify_seek,
+ .tag = input_despotify_tag,
+};
diff --git a/src/input/despotify_input_plugin.h b/src/input/despotify_input_plugin.h
new file mode 100644
index 000000000..4c070d882
--- /dev/null
+++ b/src/input/despotify_input_plugin.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2011 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef INPUT_DESPOTIFY_H
+#define INPUT_DESPOTIFY_H
+
+extern const struct input_plugin input_plugin_despotify;
+
+#endif
diff --git a/src/input/ffmpeg_input_plugin.c b/src/input/ffmpeg_input_plugin.c
index 8ff66c5b4..d71b3d4c0 100644
--- a/src/input/ffmpeg_input_plugin.c
+++ b/src/input/ffmpeg_input_plugin.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -19,15 +19,11 @@
#include "config.h"
#include "input/ffmpeg_input_plugin.h"
+#include "input_internal.h"
#include "input_plugin.h"
-#ifdef OLD_FFMPEG_INCLUDES
-#include <avio.h>
-#include <avformat.h>
-#else
#include <libavformat/avio.h>
#include <libavformat/avformat.h>
-#endif
#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "input_ffmpeg"
@@ -71,20 +67,20 @@ input_ffmpeg_init(G_GNUC_UNUSED const struct config_param *param,
{
av_register_all();
-#if LIBAVFORMAT_VERSION_MAJOR >= 52
/* disable this plugin if there's no registered protocol */
if (!input_ffmpeg_supported()) {
g_set_error(error_r, ffmpeg_quark(), 0,
"No protocol");
return false;
}
-#endif
return true;
}
static struct input_stream *
-input_ffmpeg_open(const char *uri, GError **error_r)
+input_ffmpeg_open(const char *uri,
+ GMutex *mutex, GCond *cond,
+ GError **error_r)
{
struct input_ffmpeg *i;
@@ -97,10 +93,13 @@ input_ffmpeg_open(const char *uri, GError **error_r)
return NULL;
i = g_new(struct input_ffmpeg, 1);
- input_stream_init(&i->base, &input_plugin_ffmpeg, uri);
+ input_stream_init(&i->base, &input_plugin_ffmpeg, uri,
+ mutex, cond);
-#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,0,0)
+#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,1,0)
int ret = avio_open(&i->h, uri, AVIO_FLAG_READ);
+#elif LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,0,0)
+ int ret = avio_open(&i->h, uri, AVIO_RDONLY);
#else
int ret = url_open(&i->h, uri, URL_RDONLY);
#endif
diff --git a/src/input/ffmpeg_input_plugin.h b/src/input/ffmpeg_input_plugin.h
index ff87064be..393836ca5 100644
--- a/src/input/ffmpeg_input_plugin.h
+++ b/src/input/ffmpeg_input_plugin.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/input/file_input_plugin.c b/src/input/file_input_plugin.c
index 3646c656e..5ee3f200b 100644
--- a/src/input/file_input_plugin.c
+++ b/src/input/file_input_plugin.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -19,6 +19,7 @@
#include "config.h" /* must be first for large file support */
#include "input/file_input_plugin.h"
+#include "input_internal.h"
#include "input_plugin.h"
#include "fd_util.h"
#include "open.h"
@@ -45,14 +46,16 @@ file_quark(void)
}
static struct input_stream *
-input_file_open(const char *filename, GError **error_r)
+input_file_open(const char *filename,
+ GMutex *mutex, GCond *cond,
+ GError **error_r)
{
int fd, ret;
struct stat st;
struct file_input_stream *fis;
if (!g_path_is_absolute(filename))
- return false;
+ return NULL;
fd = open_cloexec(filename, O_RDONLY|O_BINARY, 0);
if (fd < 0) {
@@ -60,7 +63,7 @@ input_file_open(const char *filename, GError **error_r)
g_set_error(error_r, file_quark(), errno,
"Failed to open \"%s\": %s",
filename, g_strerror(errno));
- return false;
+ return NULL;
}
ret = fstat(fd, &st);
@@ -69,14 +72,14 @@ input_file_open(const char *filename, GError **error_r)
"Failed to stat \"%s\": %s",
filename, g_strerror(errno));
close(fd);
- return false;
+ return NULL;
}
if (!S_ISREG(st.st_mode)) {
g_set_error(error_r, file_quark(), 0,
"Not a regular file: %s", filename);
close(fd);
- return false;
+ return NULL;
}
#ifdef POSIX_FADV_SEQUENTIAL
@@ -84,7 +87,8 @@ input_file_open(const char *filename, GError **error_r)
#endif
fis = g_new(struct file_input_stream, 1);
- input_stream_init(&fis->base, &input_plugin_file, filename);
+ input_stream_init(&fis->base, &input_plugin_file, filename,
+ mutex, cond);
fis->base.size = st.st_size;
fis->base.seekable = true;
diff --git a/src/input/file_input_plugin.h b/src/input/file_input_plugin.h
index 40340e8bd..f24769d57 100644
--- a/src/input/file_input_plugin.h
+++ b/src/input/file_input_plugin.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/input/mms_input_plugin.c b/src/input/mms_input_plugin.c
index 834d111b8..cff15125b 100644
--- a/src/input/mms_input_plugin.c
+++ b/src/input/mms_input_plugin.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -19,6 +19,7 @@
#include "config.h"
#include "input/mms_input_plugin.h"
+#include "input_internal.h"
#include "input_plugin.h"
#include <glib.h>
@@ -45,7 +46,9 @@ mms_quark(void)
}
static struct input_stream *
-input_mms_open(const char *url, GError **error_r)
+input_mms_open(const char *url,
+ GMutex *mutex, GCond *cond,
+ GError **error_r)
{
struct input_mms *m;
@@ -56,7 +59,8 @@ input_mms_open(const char *url, GError **error_r)
return NULL;
m = g_new(struct input_mms, 1);
- input_stream_init(&m->base, &input_plugin_mms, url);
+ input_stream_init(&m->base, &input_plugin_mms, url,
+ mutex, cond);
m->mms = mmsx_connect(NULL, NULL, url, 128 * 1024);
if (m->mms == NULL) {
diff --git a/src/input/mms_input_plugin.h b/src/input/mms_input_plugin.h
index 2e10cfbb9..d6aa593f2 100644
--- a/src/input/mms_input_plugin.h
+++ b/src/input/mms_input_plugin.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/input/rewind_input_plugin.c b/src/input/rewind_input_plugin.c
index f0d533bc8..cf06fc57b 100644
--- a/src/input/rewind_input_plugin.c
+++ b/src/input/rewind_input_plugin.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -19,7 +19,7 @@
#include "config.h"
#include "input/rewind_input_plugin.h"
-#include "input/curl_input_plugin.h"
+#include "input_internal.h"
#include "input_plugin.h"
#include "tag.h"
@@ -107,6 +107,23 @@ input_rewind_close(struct input_stream *is)
g_free(r);
}
+static bool
+input_rewind_check(struct input_stream *is, GError **error_r)
+{
+ struct input_rewind *r = (struct input_rewind *)is;
+
+ return input_stream_check(r->input, error_r);
+}
+
+static void
+input_rewind_update(struct input_stream *is)
+{
+ struct input_rewind *r = (struct input_rewind *)is;
+
+ if (!reading_from_buffer(r))
+ copy_attributes(r);
+}
+
static struct tag *
input_rewind_tag(struct input_stream *is)
{
@@ -115,16 +132,12 @@ input_rewind_tag(struct input_stream *is)
return input_stream_tag(r->input);
}
-static int
-input_rewind_buffer(struct input_stream *is, GError **error_r)
+static bool
+input_rewind_available(struct input_stream *is)
{
struct input_rewind *r = (struct input_rewind *)is;
- int ret = input_stream_buffer(r->input, error_r);
- if (ret < 0 || !reading_from_buffer(r))
- copy_attributes(r);
-
- return ret;
+ return input_stream_available(r->input);
}
static size_t
@@ -212,8 +225,10 @@ input_rewind_seek(struct input_stream *is, goffset offset, int whence,
static const struct input_plugin rewind_input_plugin = {
.close = input_rewind_close,
+ .check = input_rewind_check,
+ .update = input_rewind_update,
.tag = input_rewind_tag,
- .buffer = input_rewind_buffer,
+ .available = input_rewind_available,
.read = input_rewind_read,
.eof = input_rewind_eof,
.seek = input_rewind_seek,
@@ -232,7 +247,8 @@ input_rewind_open(struct input_stream *is)
return is;
c = g_new(struct input_rewind, 1);
- input_stream_init(&c->base, &rewind_input_plugin, is->uri);
+ input_stream_init(&c->base, &rewind_input_plugin, is->uri,
+ is->mutex, is->cond);
c->tail = 0;
c->input = is;
diff --git a/src/input/rewind_input_plugin.h b/src/input/rewind_input_plugin.h
index 23d25d94d..83abe257a 100644
--- a/src/input/rewind_input_plugin.h
+++ b/src/input/rewind_input_plugin.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/input/soup_input_plugin.c b/src/input/soup_input_plugin.c
new file mode 100644
index 000000000..fc903b48c
--- /dev/null
+++ b/src/input/soup_input_plugin.c
@@ -0,0 +1,473 @@
+/*
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "input/soup_input_plugin.h"
+#include "input_internal.h"
+#include "input_plugin.h"
+#include "io_thread.h"
+#include "conf.h"
+
+#include <libsoup/soup-uri.h>
+#include <libsoup/soup-session-async.h>
+
+#include <assert.h>
+#include <string.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "input_soup"
+
+/**
+ * Do not buffer more than this number of bytes. It should be a
+ * reasonable limit that doesn't make low-end machines suffer too
+ * much, but doesn't cause stuttering on high-latency lines.
+ */
+static const size_t SOUP_MAX_BUFFERED = 512 * 1024;
+
+/**
+ * Resume the stream at this number of bytes after it has been paused.
+ */
+static const size_t SOUP_RESUME_AT = 384 * 1024;
+
+static SoupURI *soup_proxy;
+static SoupSession *soup_session;
+
+struct input_soup {
+ struct input_stream base;
+
+ SoupMessage *msg;
+
+ GQueue *buffers;
+
+ size_t current_consumed;
+
+ size_t total_buffered;
+
+ bool alive, pause, eof;
+
+ /**
+ * Set when the session callback has been invoked, when it is
+ * safe to free this object.
+ */
+ bool completed;
+
+ GError *postponed_error;
+};
+
+static inline GQuark
+soup_quark(void)
+{
+ return g_quark_from_static_string("soup");
+}
+
+static bool
+input_soup_init(const struct config_param *param, GError **error_r)
+{
+ assert(soup_proxy == NULL);
+ assert(soup_session == NULL);
+
+ g_type_init();
+
+ const char *proxy = config_get_block_string(param, "proxy", NULL);
+
+ if (proxy != NULL) {
+ soup_proxy = soup_uri_new(proxy);
+ if (soup_proxy == NULL) {
+ g_set_error(error_r, soup_quark(), 0,
+ "failed to parse proxy setting");
+ return false;
+ }
+ }
+
+ soup_session =
+ soup_session_async_new_with_options(SOUP_SESSION_PROXY_URI,
+ soup_proxy,
+ SOUP_SESSION_ASYNC_CONTEXT,
+ io_thread_context(),
+ NULL);
+
+ return true;
+}
+
+static void
+input_soup_finish(void)
+{
+ assert(soup_session != NULL);
+
+ soup_session_abort(soup_session);
+ g_object_unref(G_OBJECT(soup_session));
+
+ if (soup_proxy != NULL)
+ soup_uri_free(soup_proxy);
+}
+
+/**
+ * Copy the error from the SoupMessage object to
+ * input_soup::postponed_error.
+ *
+ * @return true if there was no error
+ */
+static bool
+input_soup_copy_error(struct input_soup *s, const SoupMessage *msg)
+{
+ if (SOUP_STATUS_IS_SUCCESSFUL(msg->status_code))
+ return true;
+
+ if (msg->status_code == SOUP_STATUS_CANCELLED)
+ /* failure, but don't generate a GError, because this
+ status was caused by _close() */
+ return false;
+
+ if (s->postponed_error != NULL)
+ /* there's already a GError, don't overwrite it */
+ return false;
+
+ if (SOUP_STATUS_IS_TRANSPORT_ERROR(msg->status_code))
+ s->postponed_error =
+ g_error_new(soup_quark(), msg->status_code,
+ "HTTP client error: %s",
+ msg->reason_phrase);
+ else
+ s->postponed_error =
+ g_error_new(soup_quark(), msg->status_code,
+ "got HTTP status: %d %s",
+ msg->status_code, msg->reason_phrase);
+
+ return false;
+}
+
+static void
+input_soup_session_callback(G_GNUC_UNUSED SoupSession *session,
+ SoupMessage *msg, gpointer user_data)
+{
+ struct input_soup *s = user_data;
+
+ assert(msg == s->msg);
+ assert(!s->completed);
+
+ g_mutex_lock(s->base.mutex);
+
+ if (!s->base.ready)
+ input_soup_copy_error(s, msg);
+
+ s->base.ready = true;
+ s->alive = false;
+ s->completed = true;
+
+ g_cond_broadcast(s->base.cond);
+ g_mutex_unlock(s->base.mutex);
+}
+
+static void
+input_soup_got_headers(SoupMessage *msg, gpointer user_data)
+{
+ struct input_soup *s = user_data;
+
+ g_mutex_lock(s->base.mutex);
+
+ if (!input_soup_copy_error(s, msg)) {
+ g_mutex_unlock(s->base.mutex);
+
+ soup_session_cancel_message(soup_session, msg,
+ SOUP_STATUS_CANCELLED);
+ return;
+ }
+
+ s->base.ready = true;
+ g_cond_broadcast(s->base.cond);
+ g_mutex_unlock(s->base.mutex);
+
+ soup_message_body_set_accumulate(msg->response_body, false);
+}
+
+static void
+input_soup_got_chunk(SoupMessage *msg, SoupBuffer *chunk, gpointer user_data)
+{
+ struct input_soup *s = user_data;
+
+ assert(msg == s->msg);
+
+ g_mutex_lock(s->base.mutex);
+
+ g_queue_push_tail(s->buffers, soup_buffer_copy(chunk));
+ s->total_buffered += chunk->length;
+
+ if (s->total_buffered >= SOUP_MAX_BUFFERED && !s->pause) {
+ s->pause = true;
+ soup_session_pause_message(soup_session, msg);
+ }
+
+ g_cond_broadcast(s->base.cond);
+ g_mutex_unlock(s->base.mutex);
+}
+
+static void
+input_soup_got_body(G_GNUC_UNUSED SoupMessage *msg, gpointer user_data)
+{
+ struct input_soup *s = user_data;
+
+ assert(msg == s->msg);
+
+ g_mutex_lock(s->base.mutex);
+
+ s->base.ready = true;
+ s->eof = true;
+ s->alive = false;
+
+ g_cond_broadcast(s->base.cond);
+ g_mutex_unlock(s->base.mutex);
+}
+
+static bool
+input_soup_wait_data(struct input_soup *s)
+{
+ while (true) {
+ if (s->eof)
+ return true;
+
+ if (!s->alive)
+ return false;
+
+ if (!g_queue_is_empty(s->buffers))
+ return true;
+
+ assert(s->current_consumed == 0);
+
+ g_cond_wait(s->base.cond, s->base.mutex);
+ }
+}
+
+static gpointer
+input_soup_queue(gpointer data)
+{
+ struct input_soup *s = data;
+
+ soup_session_queue_message(soup_session, s->msg,
+ input_soup_session_callback, s);
+
+ return NULL;
+}
+
+static struct input_stream *
+input_soup_open(const char *uri,
+ GMutex *mutex, GCond *cond,
+ G_GNUC_UNUSED GError **error_r)
+{
+ if (strncmp(uri, "http://", 7) != 0)
+ return NULL;
+
+ struct input_soup *s = g_new(struct input_soup, 1);
+ input_stream_init(&s->base, &input_plugin_soup, uri,
+ mutex, cond);
+
+ s->buffers = g_queue_new();
+ s->current_consumed = 0;
+ s->total_buffered = 0;
+
+#if GCC_CHECK_VERSION(4,6)
+#pragma GCC diagnostic push
+ /* the libsoup macro SOUP_METHOD_GET discards the "const"
+ attribute of the g_intern_static_string() return value;
+ don't make the gcc warning fatal: */
+#pragma GCC diagnostic ignored "-Wcast-qual"
+#endif
+
+ s->msg = soup_message_new(SOUP_METHOD_GET, uri);
+
+#if GCC_CHECK_VERSION(4,6)
+#pragma GCC diagnostic pop
+#endif
+
+ soup_message_set_flags(s->msg, SOUP_MESSAGE_NO_REDIRECT);
+
+ soup_message_headers_append(s->msg->request_headers, "User-Agent",
+ "Music Player Daemon " VERSION);
+
+ g_signal_connect(s->msg, "got-headers",
+ G_CALLBACK(input_soup_got_headers), s);
+ g_signal_connect(s->msg, "got-chunk",
+ G_CALLBACK(input_soup_got_chunk), s);
+ g_signal_connect(s->msg, "got-body",
+ G_CALLBACK(input_soup_got_body), s);
+
+ s->alive = true;
+ s->pause = false;
+ s->eof = false;
+ s->completed = false;
+ s->postponed_error = NULL;
+
+ io_thread_call(input_soup_queue, s);
+
+ return &s->base;
+}
+
+static gpointer
+input_soup_cancel(gpointer data)
+{
+ struct input_soup *s = data;
+
+ if (!s->completed)
+ soup_session_cancel_message(soup_session, s->msg,
+ SOUP_STATUS_CANCELLED);
+
+ return NULL;
+}
+
+static void
+input_soup_close(struct input_stream *is)
+{
+ struct input_soup *s = (struct input_soup *)is;
+
+ g_mutex_lock(s->base.mutex);
+
+ if (!s->completed) {
+ /* the messages's session callback hasn't been invoked
+ yet; cancel it and wait for completion */
+
+ g_mutex_unlock(s->base.mutex);
+
+ io_thread_call(input_soup_cancel, s);
+
+ g_mutex_lock(s->base.mutex);
+ while (!s->completed)
+ g_cond_wait(s->base.cond, s->base.mutex);
+ }
+
+ g_mutex_unlock(s->base.mutex);
+
+ SoupBuffer *buffer;
+ while ((buffer = g_queue_pop_head(s->buffers)) != NULL)
+ soup_buffer_free(buffer);
+ g_queue_free(s->buffers);
+
+ if (s->postponed_error != NULL)
+ g_error_free(s->postponed_error);
+
+ input_stream_deinit(&s->base);
+ g_free(s);
+}
+
+static bool
+input_soup_check(struct input_stream *is, GError **error_r)
+{
+ struct input_soup *s = (struct input_soup *)is;
+
+ bool success = s->postponed_error == NULL;
+ if (!success) {
+ g_propagate_error(error_r, s->postponed_error);
+ s->postponed_error = NULL;
+ }
+
+ return success;
+}
+
+static bool
+input_soup_available(struct input_stream *is)
+{
+ struct input_soup *s = (struct input_soup *)is;
+
+ return s->eof || !s->alive || !g_queue_is_empty(s->buffers);
+}
+
+static size_t
+input_soup_read(struct input_stream *is, void *ptr, size_t size,
+ G_GNUC_UNUSED GError **error_r)
+{
+ struct input_soup *s = (struct input_soup *)is;
+
+ if (!input_soup_wait_data(s)) {
+ assert(!s->alive);
+
+ if (s->postponed_error != NULL) {
+ g_propagate_error(error_r, s->postponed_error);
+ s->postponed_error = NULL;
+ } else
+ g_set_error_literal(error_r, soup_quark(), 0,
+ "HTTP failure");
+ return 0;
+ }
+
+ char *p0 = ptr, *p = p0, *p_end = p0 + size;
+
+ while (p < p_end) {
+ SoupBuffer *buffer = g_queue_pop_head(s->buffers);
+ if (buffer == NULL) {
+ assert(s->current_consumed == 0);
+ break;
+ }
+
+ assert(s->current_consumed < buffer->length);
+ assert(s->total_buffered >= buffer->length);
+
+ const char *q = buffer->data;
+ q += s->current_consumed;
+
+ size_t remaining = buffer->length - s->current_consumed;
+ size_t nbytes = p_end - p;
+ if (nbytes > remaining)
+ nbytes = remaining;
+
+ memcpy(p, q, nbytes);
+ p += nbytes;
+
+ s->current_consumed += remaining;
+ if (s->current_consumed >= buffer->length) {
+ /* done with this buffer */
+ s->total_buffered -= buffer->length;
+ soup_buffer_free(buffer);
+ s->current_consumed = 0;
+ } else {
+ /* partial read */
+ assert(p == p_end);
+
+ g_queue_push_head(s->buffers, buffer);
+ }
+ }
+
+ if (s->pause && s->total_buffered < SOUP_RESUME_AT) {
+ s->pause = false;
+ soup_session_unpause_message(soup_session, s->msg);
+ }
+
+ size_t nbytes = p - p0;
+ s->base.offset += nbytes;
+
+ return nbytes;
+}
+
+static bool
+input_soup_eof(G_GNUC_UNUSED struct input_stream *is)
+{
+ struct input_soup *s = (struct input_soup *)is;
+
+ return !s->alive && g_queue_is_empty(s->buffers);
+}
+
+const struct input_plugin input_plugin_soup = {
+ .name = "soup",
+ .init = input_soup_init,
+ .finish = input_soup_finish,
+
+ .open = input_soup_open,
+ .close = input_soup_close,
+ .check = input_soup_check,
+ .available = input_soup_available,
+ .read = input_soup_read,
+ .eof = input_soup_eof,
+};
diff --git a/src/input/soup_input_plugin.h b/src/input/soup_input_plugin.h
new file mode 100644
index 000000000..689b2d971
--- /dev/null
+++ b/src/input/soup_input_plugin.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_INPUT_SOUP_H
+#define MPD_INPUT_SOUP_H
+
+extern const struct input_plugin input_plugin_soup;
+
+#endif
diff --git a/src/input_init.c b/src/input_init.c
index 1438c3e52..cf5affb4e 100644
--- a/src/input_init.c
+++ b/src/input_init.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -24,6 +24,7 @@
#include "conf.h"
#include "glib_compat.h"
+#include <assert.h>
#include <string.h>
static inline GQuark
@@ -67,6 +68,11 @@ input_stream_global_init(GError **error_r)
for (unsigned i = 0; input_plugins[i] != NULL; ++i) {
const struct input_plugin *plugin = input_plugins[i];
+
+ assert(plugin->name != NULL);
+ assert(*plugin->name != 0);
+ assert(plugin->open != NULL);
+
const struct config_param *param =
input_plugin_config(plugin->name, &error);
if (param == NULL && error != NULL) {
diff --git a/src/input_init.h b/src/input_init.h
index eded15fa9..ad92cda08 100644
--- a/src/input_init.h
+++ b/src/input_init.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -28,7 +28,7 @@
/**
* Initializes this library and all input_stream implementations.
*
- * @param error_r location to store the error occuring, or NULL to
+ * @param error_r location to store the error occurring, or NULL to
* ignore errors
*/
bool
diff --git a/src/input_internal.c b/src/input_internal.c
new file mode 100644
index 000000000..92a71856e
--- /dev/null
+++ b/src/input_internal.c
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "input_internal.h"
+#include "input_stream.h"
+
+#include <assert.h>
+
+void
+input_stream_init(struct input_stream *is, const struct input_plugin *plugin,
+ const char *uri, GMutex *mutex, GCond *cond)
+{
+ assert(is != NULL);
+ assert(plugin != NULL);
+ assert(uri != NULL);
+
+ is->plugin = plugin;
+ is->uri = g_strdup(uri);
+ is->mutex = mutex;
+ is->cond = cond;
+ is->ready = false;
+ is->seekable = false;
+ is->size = -1;
+ is->offset = 0;
+ is->mime = NULL;
+}
+
+void
+input_stream_deinit(struct input_stream *is)
+{
+ assert(is != NULL);
+ assert(is->plugin != NULL);
+
+ g_free(is->uri);
+ g_free(is->mime);
+}
+
+void
+input_stream_signal_client(struct input_stream *is)
+{
+ if (is->cond != NULL)
+ g_cond_broadcast(is->cond);
+}
+
+void
+input_stream_set_ready(struct input_stream *is)
+{
+ g_mutex_lock(is->mutex);
+
+ if (!is->ready) {
+ is->ready = true;
+ input_stream_signal_client(is);
+ }
+
+ g_mutex_unlock(is->mutex);
+}
diff --git a/src/input_internal.h b/src/input_internal.h
new file mode 100644
index 000000000..d95142e46
--- /dev/null
+++ b/src/input_internal.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_INPUT_INTERNAL_H
+#define MPD_INPUT_INTERNAL_H
+
+#include "check.h"
+
+#include <glib.h>
+
+struct input_stream;
+struct input_plugin;
+
+void
+input_stream_init(struct input_stream *is, const struct input_plugin *plugin,
+ const char *uri, GMutex *mutex, GCond *cond);
+
+void
+input_stream_deinit(struct input_stream *is);
+
+void
+input_stream_signal_client(struct input_stream *is);
+
+void
+input_stream_set_ready(struct input_stream *is);
+
+#endif
diff --git a/src/input_plugin.h b/src/input_plugin.h
index 10be48dbb..6b0c77c85 100644
--- a/src/input_plugin.h
+++ b/src/input_plugin.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -35,7 +35,7 @@ struct input_plugin {
/**
* Global initialization. This method is called when MPD starts.
*
- * @param error_r location to store the error occuring, or
+ * @param error_r location to store the error occurring, or
* NULL to ignore errors
* @return true on success, false if the plugin should be
* disabled
@@ -48,11 +48,37 @@ struct input_plugin {
*/
void (*finish)(void);
- struct input_stream *(*open)(const char *uri, GError **error_r);
+ struct input_stream *(*open)(const char *uri,
+ GMutex *mutex, GCond *cond,
+ GError **error_r);
void (*close)(struct input_stream *is);
+ /**
+ * Check for errors that may have occurred in the I/O thread.
+ * May be unimplemented for synchronous plugins.
+ *
+ * @return false on error
+ */
+ bool (*check)(struct input_stream *is, GError **error_r);
+
+ /**
+ * Update the public attributes. Call before access. Can be
+ * NULL if the plugin always keeps its attributes up to date.
+ */
+ void (*update)(struct input_stream *is);
+
struct tag *(*tag)(struct input_stream *is);
- int (*buffer)(struct input_stream *is, GError **error_r);
+
+ /**
+ * Returns true if the next read operation will not block:
+ * either data is available, or end-of-stream has been
+ * reached, or an error has occurred.
+ *
+ * If this method is unimplemented, then it is assumed that
+ * reading will never block.
+ */
+ bool (*available)(struct input_stream *is);
+
size_t (*read)(struct input_stream *is, void *ptr, size_t size,
GError **error_r);
bool (*eof)(struct input_stream *is);
diff --git a/src/input_registry.c b/src/input_registry.c
index 0b9b47d10..5987d5da2 100644
--- a/src/input_registry.c
+++ b/src/input_registry.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -29,6 +29,10 @@
#include "input/curl_input_plugin.h"
#endif
+#ifdef ENABLE_SOUP
+#include "input/soup_input_plugin.h"
+#endif
+
#ifdef HAVE_FFMPEG
#include "input/ffmpeg_input_plugin.h"
#endif
@@ -37,6 +41,14 @@
#include "input/mms_input_plugin.h"
#endif
+#ifdef ENABLE_CDIO_PARANOIA
+#include "input/cdio_paranoia_input_plugin.h"
+#endif
+
+#ifdef ENABLE_DESPOTIFY
+#include "input/despotify_input_plugin.h"
+#endif
+
#include <glib.h>
const struct input_plugin *const input_plugins[] = {
@@ -47,12 +59,21 @@ const struct input_plugin *const input_plugins[] = {
#ifdef ENABLE_CURL
&input_plugin_curl,
#endif
+#ifdef ENABLE_SOUP
+ &input_plugin_soup,
+#endif
#ifdef HAVE_FFMPEG
&input_plugin_ffmpeg,
#endif
#ifdef ENABLE_MMS
&input_plugin_mms,
#endif
+#ifdef ENABLE_CDIO_PARANOIA
+ &input_plugin_cdio_paranoia,
+#endif
+#ifdef ENABLE_DESPOTIFY
+ &input_plugin_despotify,
+#endif
NULL
};
diff --git a/src/input_registry.h b/src/input_registry.h
index e85d6be8e..a1b057469 100644
--- a/src/input_registry.h
+++ b/src/input_registry.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/input_stream.c b/src/input_stream.c
index e769adb92..60a1559ba 100644
--- a/src/input_stream.c
+++ b/src/input_stream.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -33,10 +33,13 @@ input_quark(void)
}
struct input_stream *
-input_stream_open(const char *url, GError **error_r)
+input_stream_open(const char *url,
+ GMutex *mutex, GCond *cond,
+ GError **error_r)
{
GError *error = NULL;
+ assert(mutex != NULL);
assert(error_r == NULL || *error_r == NULL);
for (unsigned i = 0; input_plugins[i] != NULL; ++i) {
@@ -46,7 +49,7 @@ input_stream_open(const char *url, GError **error_r)
if (!input_plugins_enabled[i])
continue;
- is = plugin->open(url, &error);
+ is = plugin->open(url, mutex, cond, &error);
if (is != NULL) {
assert(is->plugin != NULL);
assert(is->plugin->close != NULL);
@@ -64,29 +67,131 @@ input_stream_open(const char *url, GError **error_r)
}
g_set_error(error_r, input_quark(), 0, "Unrecognized URI");
- return false;
+ return NULL;
+}
+
+bool
+input_stream_check(struct input_stream *is, GError **error_r)
+{
+ assert(is != NULL);
+ assert(is->plugin != NULL);
+
+ return is->plugin->check == NULL ||
+ is->plugin->check(is, error_r);
+}
+
+void
+input_stream_update(struct input_stream *is)
+{
+ assert(is != NULL);
+ assert(is->plugin != NULL);
+
+ if (is->plugin->update != NULL)
+ is->plugin->update(is);
+}
+
+void
+input_stream_wait_ready(struct input_stream *is)
+{
+ assert(is != NULL);
+ assert(is->mutex != NULL);
+ assert(is->cond != NULL);
+
+ while (true) {
+ input_stream_update(is);
+ if (is->ready)
+ break;
+
+ g_cond_wait(is->cond, is->mutex);
+ }
+}
+
+void
+input_stream_lock_wait_ready(struct input_stream *is)
+{
+ assert(is != NULL);
+ assert(is->mutex != NULL);
+ assert(is->cond != NULL);
+
+ g_mutex_lock(is->mutex);
+ input_stream_wait_ready(is);
+ g_mutex_unlock(is->mutex);
}
bool
input_stream_seek(struct input_stream *is, goffset offset, int whence,
GError **error_r)
{
+ assert(is != NULL);
+ assert(is->plugin != NULL);
+
if (is->plugin->seek == NULL)
return false;
return is->plugin->seek(is, offset, whence, error_r);
}
+bool
+input_stream_lock_seek(struct input_stream *is, goffset offset, int whence,
+ GError **error_r)
+{
+ assert(is != NULL);
+ assert(is->plugin != NULL);
+
+ if (is->plugin->seek == NULL)
+ return false;
+
+ if (is->mutex == NULL)
+ /* no locking */
+ return input_stream_seek(is, offset, whence, error_r);
+
+ g_mutex_lock(is->mutex);
+ bool success = input_stream_seek(is, offset, whence, error_r);
+ g_mutex_unlock(is->mutex);
+ return success;
+}
+
struct tag *
input_stream_tag(struct input_stream *is)
{
assert(is != NULL);
+ assert(is->plugin != NULL);
return is->plugin->tag != NULL
? is->plugin->tag(is)
: NULL;
}
+struct tag *
+input_stream_lock_tag(struct input_stream *is)
+{
+ assert(is != NULL);
+ assert(is->plugin != NULL);
+
+ if (is->plugin->tag == NULL)
+ return false;
+
+ if (is->mutex == NULL)
+ /* no locking */
+ return input_stream_tag(is);
+
+ g_mutex_lock(is->mutex);
+ struct tag *tag = input_stream_tag(is);
+ g_mutex_unlock(is->mutex);
+ return tag;
+}
+
+bool
+input_stream_available(struct input_stream *is)
+{
+ assert(is != NULL);
+ assert(is->plugin != NULL);
+
+ return is->plugin->available != NULL
+ ? is->plugin->available(is)
+ : true;
+}
+
size_t
input_stream_read(struct input_stream *is, void *ptr, size_t size,
GError **error_r)
@@ -97,6 +202,23 @@ input_stream_read(struct input_stream *is, void *ptr, size_t size,
return is->plugin->read(is, ptr, size, error_r);
}
+size_t
+input_stream_lock_read(struct input_stream *is, void *ptr, size_t size,
+ GError **error_r)
+{
+ assert(ptr != NULL);
+ assert(size > 0);
+
+ if (is->mutex == NULL)
+ /* no locking */
+ return input_stream_read(is, ptr, size, error_r);
+
+ g_mutex_lock(is->mutex);
+ size_t nbytes = input_stream_read(is, ptr, size, error_r);
+ g_mutex_unlock(is->mutex);
+ return nbytes;
+}
+
void input_stream_close(struct input_stream *is)
{
is->plugin->close(is);
@@ -107,11 +229,19 @@ bool input_stream_eof(struct input_stream *is)
return is->plugin->eof(is);
}
-int
-input_stream_buffer(struct input_stream *is, GError **error_r)
+bool
+input_stream_lock_eof(struct input_stream *is)
{
- if (is->plugin->buffer == NULL)
- return 0;
+ assert(is != NULL);
+ assert(is->plugin != NULL);
+
+ if (is->mutex == NULL)
+ /* no locking */
+ return input_stream_eof(is);
- return is->plugin->buffer(is, error_r);
+ g_mutex_lock(is->mutex);
+ bool eof = input_stream_eof(is);
+ g_mutex_unlock(is->mutex);
+ return eof;
}
+
diff --git a/src/input_stream.h b/src/input_stream.h
index 056d008a7..6a10831d2 100644
--- a/src/input_stream.h
+++ b/src/input_stream.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -21,6 +21,7 @@
#define MPD_INPUT_STREAM_H
#include "check.h"
+#include "gcc.h"
#include <glib.h>
@@ -45,6 +46,26 @@ struct input_stream {
char *uri;
/**
+ * A mutex that protects the mutable attributes of this object
+ * and its implementation. It must be locked before calling
+ * any of the public methods.
+ *
+ * This object is allocated by the client, and the client is
+ * responsible for freeing it.
+ */
+ GMutex *mutex;
+
+ /**
+ * A cond that gets signalled when the state of this object
+ * changes from the I/O thread. The client of this object may
+ * wait on it. Optional, may be NULL.
+ *
+ * This object is allocated by the client, and the client is
+ * responsible for freeing it.
+ */
+ GCond *cond;
+
+ /**
* indicates whether the stream is ready for reading and
* whether the other attributes in this struct are valid
*/
@@ -71,88 +92,180 @@ struct input_stream {
char *mime;
};
+/**
+ * Opens a new input stream. You may not access it until the "ready"
+ * flag is set.
+ *
+ * @param mutex a mutex that is used to protect this object; must be
+ * locked before calling any of the public methods
+ * @param cond a cond that gets signalled when the state of
+ * this object changes; may be NULL if the caller doesn't want to get
+ * notifications
+ * @return an #input_stream object on success, NULL on error
+ */
+gcc_nonnull(1, 2)
+G_GNUC_MALLOC
+struct input_stream *
+input_stream_open(const char *uri,
+ GMutex *mutex, GCond *cond,
+ GError **error_r);
+
+/**
+ * Close the input stream and free resources.
+ *
+ * The caller must not lock the mutex.
+ */
+gcc_nonnull(1)
+void
+input_stream_close(struct input_stream *is);
+
+gcc_nonnull(1)
static inline void
-input_stream_init(struct input_stream *is, const struct input_plugin *plugin,
- const char *uri)
+input_stream_lock(struct input_stream *is)
{
- is->plugin = plugin;
- is->uri = g_strdup(uri);
- is->ready = false;
- is->seekable = false;
- is->size = -1;
- is->offset = 0;
- is->mime = NULL;
+ g_mutex_lock(is->mutex);
}
+gcc_nonnull(1)
static inline void
-input_stream_deinit(struct input_stream *is)
+input_stream_unlock(struct input_stream *is)
{
- g_free(is->uri);
- g_free(is->mime);
+ g_mutex_unlock(is->mutex);
}
/**
- * Opens a new input stream. You may not access it until the "ready"
- * flag is set.
+ * Check for errors that may have occurred in the I/O thread.
*
- * @return an #input_stream object on success, NULL on error
+ * @return false on error
*/
-struct input_stream *
-input_stream_open(const char *uri, GError **error_r);
+gcc_nonnull(1)
+bool
+input_stream_check(struct input_stream *is, GError **error_r);
/**
- * Close the input stream and free resources.
+ * Update the public attributes. Call before accessing attributes
+ * such as "ready" or "offset".
*/
+gcc_nonnull(1)
void
-input_stream_close(struct input_stream *is);
+input_stream_update(struct input_stream *is);
+
+/**
+ * Wait until the stream becomes ready.
+ *
+ * The caller must lock the mutex.
+ */
+gcc_nonnull(1)
+void
+input_stream_wait_ready(struct input_stream *is);
+
+/**
+ * Wrapper for input_stream_wait_locked() which locks and unlocks the
+ * mutex; the caller must not be holding it already.
+ */
+gcc_nonnull(1)
+void
+input_stream_lock_wait_ready(struct input_stream *is);
/**
* Seeks to the specified position in the stream. This will most
* likely fail if the "seekable" flag is false.
*
+ * The caller must lock the mutex.
+ *
* @param is the input_stream object
* @param offset the relative offset
* @param whence the base of the seek, one of SEEK_SET, SEEK_CUR, SEEK_END
*/
+gcc_nonnull(1)
bool
input_stream_seek(struct input_stream *is, goffset offset, int whence,
GError **error_r);
/**
+ * Wrapper for input_stream_seek() which locks and unlocks the
+ * mutex; the caller must not be holding it already.
+ */
+gcc_nonnull(1)
+bool
+input_stream_lock_seek(struct input_stream *is, goffset offset, int whence,
+ GError **error_r);
+
+/**
* Returns true if the stream has reached end-of-file.
+ *
+ * The caller must lock the mutex.
*/
+gcc_nonnull(1)
+G_GNUC_PURE
bool input_stream_eof(struct input_stream *is);
/**
+ * Wrapper for input_stream_eof() which locks and unlocks the mutex;
+ * the caller must not be holding it already.
+ */
+gcc_nonnull(1)
+G_GNUC_PURE
+bool
+input_stream_lock_eof(struct input_stream *is);
+
+/**
* Reads the tag from the stream.
*
+ * The caller must lock the mutex.
+ *
* @return a tag object which must be freed with tag_free(), or NULL
* if the tag has not changed since the last call
*/
+gcc_nonnull(1)
+G_GNUC_MALLOC
struct tag *
input_stream_tag(struct input_stream *is);
/**
- * Reads some of the stream into its buffer. The following return
- * codes are defined: -1 = error, 1 = something was buffered, 0 =
- * nothing was buffered.
+ * Wrapper for input_stream_tag() which locks and unlocks the
+ * mutex; the caller must not be holding it already.
+ */
+gcc_nonnull(1)
+G_GNUC_MALLOC
+struct tag *
+input_stream_lock_tag(struct input_stream *is);
+
+/**
+ * Returns true if the next read operation will not block: either data
+ * is available, or end-of-stream has been reached, or an error has
+ * occurred.
*
- * The semantics of this function are not well-defined, and it will
- * eventually be removed.
+ * The caller must lock the mutex.
*/
-int input_stream_buffer(struct input_stream *is, GError **error_r);
+gcc_nonnull(1)
+G_GNUC_PURE
+bool
+input_stream_available(struct input_stream *is);
/**
* Reads data from the stream into the caller-supplied buffer.
* Returns 0 on error or eof (check with input_stream_eof()).
*
+ * The caller must lock the mutex.
+ *
* @param is the input_stream object
* @param ptr the buffer to read into
* @param size the maximum number of bytes to read
* @return the number of bytes read
*/
+gcc_nonnull(1, 2)
size_t
input_stream_read(struct input_stream *is, void *ptr, size_t size,
GError **error_r);
+/**
+ * Wrapper for input_stream_tag() which locks and unlocks the
+ * mutex; the caller must not be holding it already.
+ */
+gcc_nonnull(1, 2)
+size_t
+input_stream_lock_read(struct input_stream *is, void *ptr, size_t size,
+ GError **error_r);
+
#endif
diff --git a/src/io_thread.c b/src/io_thread.c
new file mode 100644
index 000000000..2091ecd05
--- /dev/null
+++ b/src/io_thread.c
@@ -0,0 +1,200 @@
+/*
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "io_thread.h"
+#include "glib_compat.h"
+
+#include <assert.h>
+
+static struct {
+ GMutex *mutex;
+ GCond *cond;
+
+ GMainContext *context;
+ GMainLoop *loop;
+ GThread *thread;
+} io;
+
+void
+io_thread_run(void)
+{
+ assert(io_thread_inside());
+ assert(io.context != NULL);
+ assert(io.loop != NULL);
+
+ g_main_loop_run(io.loop);
+}
+
+static gpointer
+io_thread_func(G_GNUC_UNUSED gpointer arg)
+{
+ /* lock+unlock to synchronize with io_thread_start(), to be
+ sure that io.thread is set */
+ g_mutex_lock(io.mutex);
+ g_mutex_unlock(io.mutex);
+
+ io_thread_run();
+ return NULL;
+}
+
+void
+io_thread_init(void)
+{
+ assert(io.context == NULL);
+ assert(io.loop == NULL);
+ assert(io.thread == NULL);
+
+ io.mutex = g_mutex_new();
+ io.cond = g_cond_new();
+ io.context = g_main_context_new();
+ io.loop = g_main_loop_new(io.context, false);
+}
+
+bool
+io_thread_start(GError **error_r)
+{
+ assert(io.context != NULL);
+ assert(io.loop != NULL);
+ assert(io.thread == NULL);
+
+ g_mutex_lock(io.mutex);
+ io.thread = g_thread_create(io_thread_func, NULL, true, error_r);
+ g_mutex_unlock(io.mutex);
+ if (io.thread == NULL)
+ return false;
+
+ return true;
+}
+
+void
+io_thread_quit(void)
+{
+ assert(io.loop != NULL);
+
+ g_main_loop_quit(io.loop);
+}
+
+void
+io_thread_deinit(void)
+{
+ if (io.thread != NULL) {
+ io_thread_quit();
+
+ g_thread_join(io.thread);
+ }
+
+ if (io.loop != NULL)
+ g_main_loop_unref(io.loop);
+
+ if (io.context != NULL)
+ g_main_context_unref(io.context);
+
+ g_cond_free(io.cond);
+ g_mutex_free(io.mutex);
+}
+
+GMainContext *
+io_thread_context(void)
+{
+ return io.context;
+}
+
+bool
+io_thread_inside(void)
+{
+ return io.thread != NULL && g_thread_self() == io.thread;
+}
+
+guint
+io_thread_idle_add(GSourceFunc function, gpointer data)
+{
+ GSource *source = g_idle_source_new();
+ g_source_set_callback(source, function, data, NULL);
+ guint id = g_source_attach(source, io.context);
+ g_source_unref(source);
+ return id;
+}
+
+GSource *
+io_thread_timeout_add(guint interval_ms, GSourceFunc function, gpointer data)
+{
+ GSource *source = g_timeout_source_new(interval_ms);
+ g_source_set_callback(source, function, data, NULL);
+ g_source_attach(source, io.context);
+ return source;
+}
+
+GSource *
+io_thread_timeout_add_seconds(guint interval,
+ GSourceFunc function, gpointer data)
+{
+ GSource *source = g_timeout_source_new_seconds(interval);
+ g_source_set_callback(source, function, data, NULL);
+ g_source_attach(source, io.context);
+ return source;
+}
+
+struct call_data {
+ GThreadFunc function;
+ gpointer data;
+ bool done;
+ gpointer result;
+};
+
+static gboolean
+io_thread_call_func(gpointer _data)
+{
+ struct call_data *data = _data;
+
+ gpointer result = data->function(data->data);
+
+ g_mutex_lock(io.mutex);
+ data->done = true;
+ data->result = result;
+ g_cond_broadcast(io.cond);
+ g_mutex_unlock(io.mutex);
+
+ return false;
+}
+
+gpointer
+io_thread_call(GThreadFunc function, gpointer _data)
+{
+ assert(io.thread != NULL);
+
+ if (io_thread_inside())
+ /* we're already in the I/O thread - no
+ synchronization needed */
+ return function(_data);
+
+ struct call_data data = {
+ .function = function,
+ .data = _data,
+ .done = false,
+ };
+
+ io_thread_idle_add(io_thread_call_func, &data);
+
+ g_mutex_lock(io.mutex);
+ while (!data.done)
+ g_cond_wait(io.cond, io.mutex);
+ g_mutex_unlock(io.mutex);
+
+ return data.result;
+}
diff --git a/src/io_thread.h b/src/io_thread.h
new file mode 100644
index 000000000..8ff5a71e5
--- /dev/null
+++ b/src/io_thread.h
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_IO_THREAD_H
+#define MPD_IO_THREAD_H
+
+#include <glib.h>
+#include <stdbool.h>
+
+void
+io_thread_init(void);
+
+bool
+io_thread_start(GError **error_r);
+
+/**
+ * Run the I/O event loop synchronously in the current thread. This
+ * can be called instead of io_thread_start(). For testing purposes
+ * only.
+ */
+void
+io_thread_run(void);
+
+/**
+ * Ask the I/O thread to quit, but does not wait for it. Usually, you
+ * don't need to call this function, because io_thread_deinit()
+ * includes this.
+ */
+void
+io_thread_quit(void);
+
+void
+io_thread_deinit(void);
+
+G_GNUC_PURE
+GMainContext *
+io_thread_context(void);
+
+/**
+ * Is the current thread the I/O thread?
+ */
+G_GNUC_PURE
+bool
+io_thread_inside(void);
+
+guint
+io_thread_idle_add(GSourceFunc function, gpointer data);
+
+G_GNUC_MALLOC
+GSource *
+io_thread_timeout_add(guint interval_ms, GSourceFunc function, gpointer data);
+
+G_GNUC_MALLOC
+GSource *
+io_thread_timeout_add_seconds(guint interval,
+ GSourceFunc function, gpointer data);
+
+/**
+ * Call a function synchronously in the I/O thread.
+ */
+gpointer
+io_thread_call(GThreadFunc function, gpointer data);
+
+#endif
diff --git a/src/listen.c b/src/listen.c
index da2e79909..e2a40e93f 100644
--- a/src/listen.c
+++ b/src/listen.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -23,10 +23,15 @@
#include "client.h"
#include "conf.h"
#include "glib_compat.h"
+#include "main.h"
#include <string.h>
#include <assert.h>
+#ifdef ENABLE_SYSTEMD_DAEMON
+#include <systemd/sd-daemon.h>
+#endif
+
#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "listen"
@@ -39,7 +44,7 @@ static void
listen_callback(int fd, const struct sockaddr *address,
size_t address_length, int uid, G_GNUC_UNUSED void *ctx)
{
- client_new(fd, address, address_length, uid);
+ client_new(global_player_control, fd, address, address_length, uid);
}
static bool
@@ -60,6 +65,30 @@ listen_add_config_param(unsigned int port,
}
}
+static bool
+listen_systemd_activation(GError **error_r)
+{
+#ifdef ENABLE_SYSTEMD_DAEMON
+ int n = sd_listen_fds(true);
+ if (n <= 0) {
+ if (n < 0)
+ g_warning("sd_listen_fds() failed: %s",
+ g_strerror(-n));
+ return false;
+ }
+
+ for (int i = SD_LISTEN_FDS_START, end = SD_LISTEN_FDS_START + n;
+ i != end; ++i)
+ if (!server_socket_add_fd(listen_socket, i, error_r))
+ return false;
+
+ return true;
+#else
+ (void)error_r;
+ return false;
+#endif
+}
+
bool
listen_global_init(GError **error_r)
{
@@ -71,6 +100,14 @@ listen_global_init(GError **error_r)
listen_socket = server_socket_new(listen_callback, NULL);
+ if (listen_systemd_activation(&error))
+ return true;
+
+ if (error != NULL) {
+ g_propagate_error(error_r, error);
+ return false;
+ }
+
if (param != NULL) {
/* "bind_to_address" is configured, create listeners
for all values */
diff --git a/src/listen.h b/src/listen.h
index 449b5ebae..246e83706 100644
--- a/src/listen.h
+++ b/src/listen.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/locate.c b/src/locate.c
index e27858a0e..96acb3a39 100644
--- a/src/locate.c
+++ b/src/locate.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -64,19 +64,6 @@ locate_item_init(struct locate_item *item,
return true;
}
-struct locate_item *
-locate_item_new(const char *type_string, const char *needle)
-{
- struct locate_item *ret = g_new(struct locate_item, 1);
-
- if (!locate_item_init(ret, type_string, needle)) {
- g_free(ret);
- ret = NULL;
- }
-
- return ret;
-}
-
void
locate_item_list_free(struct locate_item_list *list)
{
diff --git a/src/locate.h b/src/locate.h
index 0283f551b..ec20ded24 100644
--- a/src/locate.h
+++ b/src/locate.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -20,6 +20,8 @@
#ifndef MPD_LOCATE_H
#define MPD_LOCATE_H
+#include "gcc.h"
+
#include <stdint.h>
#include <stdbool.h>
@@ -49,10 +51,6 @@ struct locate_item_list {
int
locate_parse_type(const char *str);
-/* returns NULL if not a known type */
-struct locate_item *
-locate_item_new(const char *type_string, const char *needle);
-
/**
* Allocates a new struct locate_item_list, and initializes all
* members with zero bytes.
@@ -61,6 +59,7 @@ struct locate_item_list *
locate_item_list_new(unsigned length);
/* return number of items or -1 on error */
+gcc_nonnull(1)
struct locate_item_list *
locate_item_list_parse(char *argv[], int argc);
@@ -68,19 +67,24 @@ locate_item_list_parse(char *argv[], int argc);
* Duplicate the struct locate_item_list object and convert all
* needles with g_utf8_casefold().
*/
+gcc_nonnull(1)
struct locate_item_list *
locate_item_list_casefold(const struct locate_item_list *list);
+gcc_nonnull(1)
void
locate_item_list_free(struct locate_item_list *list);
+gcc_nonnull(1)
void
locate_item_free(struct locate_item *item);
+gcc_nonnull(1,2)
bool
locate_song_search(const struct song *song,
const struct locate_item_list *criteria);
+gcc_nonnull(1,2)
bool
locate_song_match(const struct song *song,
const struct locate_item_list *criteria);
diff --git a/src/log.c b/src/log.c
index 99a9a8f6d..86dd86eaa 100644
--- a/src/log.c
+++ b/src/log.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -133,16 +133,20 @@ open_log_file(void)
return open_cloexec(out_filename, O_CREAT | O_WRONLY | O_APPEND, 0666);
}
-static void
-log_init_file(const char *path, unsigned line)
+static bool
+log_init_file(const char *path, unsigned line, GError **error_r)
{
out_filename = path;
out_fd = open_log_file();
- if (out_fd < 0)
- MPD_ERROR("failed to open log file \"%s\" (config line %u): %s",
- path, line, g_strerror(errno));
+ if (out_fd < 0) {
+ g_set_error(error_r, log_quark(), errno,
+ "failed to open log file \"%s\" (config line %u): %s",
+ path, line, g_strerror(errno));
+ return false;
+ }
g_log_set_default_handler(file_log_func, NULL);
+ return true;
}
#ifdef HAVE_SYSLOG
@@ -232,7 +236,8 @@ log_early_init(bool verbose)
log_init_stdout();
}
-void log_init(bool verbose, bool use_stdout)
+bool
+log_init(bool verbose, bool use_stdout, GError **error_r)
{
const struct config_param *param;
@@ -245,6 +250,7 @@ void log_init(bool verbose, bool use_stdout)
if (use_stdout) {
log_init_stdout();
+ return true;
} else {
param = config_get_param(CONF_LOG_FILE);
if (param == NULL) {
@@ -252,19 +258,31 @@ void log_init(bool verbose, bool use_stdout)
/* no configuration: default to syslog (if
available) */
log_init_syslog();
+ return true;
#else
- MPD_ERROR("config parameter \"%s\" not found\n",
- CONF_LOG_FILE);
+ g_set_error(error_r, log_quark(), 0,
+ "config parameter \"%s\" not found",
+ CONF_LOG_FILE);
+ return false;
#endif
#ifdef HAVE_SYSLOG
} else if (strcmp(param->value, "syslog") == 0) {
log_init_syslog();
+ return true;
#endif
} else {
- const char *path = config_get_path(CONF_LOG_FILE);
- assert(path != NULL);
-
- log_init_file(path, param->line);
+ GError *error = NULL;
+ char *path = config_dup_path(CONF_LOG_FILE, &error);
+ if (path == NULL) {
+ assert(error != NULL);
+ g_propagate_error(error_r, error);
+ return false;
+ }
+
+ bool success = log_init_file(path, param->line,
+ error_r);
+ g_free(path);
+ return success;
}
}
}
diff --git a/src/log.h b/src/log.h
index e9daf1113..75e386b25 100644
--- a/src/log.h
+++ b/src/log.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -23,6 +23,13 @@
#include <glib.h>
#include <stdbool.h>
+G_GNUC_CONST
+static inline GQuark
+log_quark(void)
+{
+ return g_quark_from_static_string("log");
+}
+
/**
* Configure a logging destination for daemon startup, before the
* configuration file is read. This allows the daemon to use the
@@ -34,7 +41,8 @@
void
log_early_init(bool verbose);
-void log_init(bool verbose, bool use_stdout);
+bool
+log_init(bool verbose, bool use_stdout, GError **error_r);
void setup_log_output(bool use_stdout);
diff --git a/src/ls.c b/src/ls.c
index c30765c62..5ed9fc579 100644
--- a/src/ls.c
+++ b/src/ls.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -32,7 +32,7 @@
* connected by IPC socket.
*/
static const char *remoteUrlPrefixes[] = {
-#ifdef ENABLE_CURL
+#if defined(ENABLE_CURL) || defined(ENABLE_SOUP)
"http://",
#endif
#ifdef ENABLE_MMS
@@ -49,6 +49,12 @@ static const char *remoteUrlPrefixes[] = {
"rtmpt://",
"rtmps://",
#endif
+#ifdef ENABLE_CDIO_PARANOIA
+ "cdda://",
+#endif
+#ifdef ENABLE_DESPOTIFY
+ "spt://",
+#endif
NULL
};
diff --git a/src/ls.h b/src/ls.h
index d29e20a46..15cb01160 100644
--- a/src/ls.h
+++ b/src/ls.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/main.c b/src/main.c
index a500e2934..4a10f1433 100644
--- a/src/main.c
+++ b/src/main.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -20,7 +20,9 @@
#include "config.h"
#include "main.h"
#include "daemon.h"
+#include "io_thread.h"
#include "client.h"
+#include "client_idle.h"
#include "idle.h"
#include "command.h"
#include "playlist.h"
@@ -37,11 +39,12 @@
#include "player_control.h"
#include "stats.h"
#include "sig_handlers.h"
-#include "audio.h"
+#include "audio_config.h"
#include "output_all.h"
#include "volume.h"
#include "log.h"
#include "permission.h"
+#include "pcm_resample.h"
#include "replay_gain_config.h"
#include "decoder_list.h"
#include "input_init.h"
@@ -51,8 +54,6 @@
#include "dbUtils.h"
#include "zeroconf.h"
#include "event_pipe.h"
-#include "dirvec.h"
-#include "songvec.h"
#include "tag_pool.h"
#include "mpd_error.h"
@@ -94,31 +95,56 @@ GMainLoop *main_loop;
GCond *main_cond;
-static void
-glue_daemonize_init(const struct options *options)
+struct player_control *global_player_control;
+
+static bool
+glue_daemonize_init(const struct options *options, GError **error_r)
{
+ GError *error = NULL;
+
+ char *pid_file = config_dup_path(CONF_PID_FILE, &error);
+ if (pid_file == NULL && error != NULL) {
+ g_propagate_error(error_r, error);
+ return false;
+ }
+
daemonize_init(config_get_string(CONF_USER, NULL),
config_get_string(CONF_GROUP, NULL),
- config_get_path(CONF_PID_FILE));
+ pid_file);
+ g_free(pid_file);
if (options->kill)
daemonize_kill();
+
+ return true;
}
-static void
-glue_mapper_init(void)
+static bool
+glue_mapper_init(GError **error_r)
{
- const char *music_dir, *playlist_dir;
+ GError *error = NULL;
+ char *music_dir = config_dup_path(CONF_MUSIC_DIR, &error);
+ if (music_dir == NULL && error != NULL) {
+ g_propagate_error(error_r, error);
+ return false;
+ }
+
+ char *playlist_dir = config_dup_path(CONF_PLAYLIST_DIR, &error);
+ if (playlist_dir == NULL && error != NULL) {
+ g_propagate_error(error_r, error);
+ return false;
+ }
- music_dir = config_get_path(CONF_MUSIC_DIR);
#if GLIB_CHECK_VERSION(2,14,0)
if (music_dir == NULL)
- music_dir = g_get_user_special_dir(G_USER_DIRECTORY_MUSIC);
+ music_dir = g_strdup(g_get_user_special_dir(G_USER_DIRECTORY_MUSIC));
#endif
- playlist_dir = config_get_path(CONF_PLAYLIST_DIR);
-
mapper_init(music_dir, playlist_dir);
+
+ g_free(music_dir);
+ g_free(playlist_dir);
+ return true;
}
/**
@@ -129,38 +155,31 @@ glue_mapper_init(void)
static bool
glue_db_init_and_load(void)
{
- const char *path = config_get_path(CONF_DB_FILE);
- bool ret;
+ const struct config_param *path = config_get_param(CONF_DB_FILE);
+
GError *error = NULL;
+ bool ret;
if (!mapper_has_music_directory()) {
if (path != NULL)
g_message("Found " CONF_DB_FILE " setting without "
CONF_MUSIC_DIR " - disabling database");
- db_init(NULL);
+ db_init(NULL, NULL);
return true;
}
if (path == NULL)
MPD_ERROR(CONF_DB_FILE " setting missing");
- db_init(path);
+ if (!db_init(path, &error))
+ MPD_ERROR("%s", error->message);
ret = db_load(&error);
- if (!ret) {
- g_warning("Failed to load database: %s", error->message);
- g_error_free(error);
-
- if (!db_check())
- exit(EXIT_FAILURE);
-
- db_clear();
-
- /* run database update after daemonization */
- return false;
- }
+ if (!ret)
+ MPD_ERROR("%s", error->message);
- return true;
+ /* run database update after daemonization? */
+ return db_exists();
}
/**
@@ -170,20 +189,33 @@ static void
glue_sticker_init(void)
{
#ifdef ENABLE_SQLITE
- bool success;
GError *error = NULL;
+ char *sticker_file = config_dup_path(CONF_STICKER_FILE, &error);
+ if (sticker_file == NULL && error != NULL)
+ MPD_ERROR("%s", error->message);
- success = sticker_global_init(config_get_path(CONF_STICKER_FILE),
- &error);
- if (!success)
+ if (!sticker_global_init(sticker_file, &error))
MPD_ERROR("%s", error->message);
+
+ g_free(sticker_file);
#endif
}
-static void
-glue_state_file_init(void)
+static bool
+glue_state_file_init(GError **error_r)
{
- state_file_init(config_get_path(CONF_STATE_FILE));
+ GError *error = NULL;
+
+ char *path = config_dup_path(CONF_STATE_FILE, &error);
+ if (path == NULL && error != NULL) {
+ g_propagate_error(error_r, error);
+ return false;
+ }
+
+ state_file_init(path, global_player_control);
+ g_free(path);
+
+ return true;
}
/**
@@ -225,10 +257,11 @@ initialize_decoder_and_player(void)
param = config_get_param(CONF_AUDIO_BUFFER_SIZE);
if (param != NULL) {
- buffer_size = strtol(param->value, &test, 10);
- if (*test != '\0' || buffer_size <= 0)
+ long tmp = strtol(param->value, &test, 10);
+ if (*test != '\0' || tmp <= 0 || tmp == LONG_MAX)
MPD_ERROR("buffer size \"%s\" is not a positive integer, "
"line %i\n", param->value, param->line);
+ buffer_size = tmp;
} else
buffer_size = DEFAULT_BUFFER_SIZE;
@@ -254,7 +287,7 @@ initialize_decoder_and_player(void)
if (buffered_before_play > buffered_chunks)
buffered_before_play = buffered_chunks;
- pc_init(buffered_chunks, buffered_before_play);
+ global_player_control = pc_new(buffered_chunks, buffered_before_play);
}
/**
@@ -308,10 +341,9 @@ int mpd_main(int argc, char *argv[])
/* enable GLib's thread safety code */
g_thread_init(NULL);
+ io_thread_init();
winsock_init();
idle_init();
- dirvec_init();
- songvec_init();
tag_pool_init();
config_global_init();
@@ -322,11 +354,20 @@ int mpd_main(int argc, char *argv[])
return EXIT_FAILURE;
}
- glue_daemonize_init(&options);
+ if (!glue_daemonize_init(&options, &error)) {
+ g_warning("%s", error->message);
+ g_error_free(error);
+ return EXIT_FAILURE;
+ }
stats_global_init();
tag_lib_init();
- log_init(options.verbose, options.log_stderr);
+
+ if (!log_init(options.verbose, options.log_stderr, &error)) {
+ g_warning("%s", error->message);
+ g_error_free(error);
+ return EXIT_FAILURE;
+ }
success = listen_global_init(&error);
if (!success) {
@@ -346,13 +387,26 @@ int mpd_main(int argc, char *argv[])
event_pipe_register(PIPE_EVENT_SHUTDOWN, shutdown_event_emitted);
path_global_init();
- glue_mapper_init();
+
+ if (!glue_mapper_init(&error)) {
+ g_warning("%s", error->message);
+ g_error_free(error);
+ return EXIT_FAILURE;
+ }
+
initPermissions();
playlist_global_init();
spl_global_init();
#ifdef ENABLE_ARCHIVE
archive_plugin_init_all();
#endif
+
+ if (!pcm_resample_global_init(&error)) {
+ g_warning("%s", error->message);
+ g_error_free(error);
+ return EXIT_FAILURE;
+ }
+
decoder_plugin_init_all();
update_global_init();
@@ -364,7 +418,7 @@ int mpd_main(int argc, char *argv[])
initialize_decoder_and_player();
volume_init();
initAudioConfig();
- audio_output_all_init();
+ audio_output_all_init(global_player_control);
client_manager_init();
replay_gain_global_init();
@@ -382,9 +436,15 @@ int mpd_main(int argc, char *argv[])
initSigHandlers();
+ if (!io_thread_start(&error)) {
+ g_warning("%s", error->message);
+ g_error_free(error);
+ return EXIT_FAILURE;
+ }
+
initZeroconf();
- player_create();
+ player_create(global_player_control);
if (create_db) {
/* the database failed to load: recreate the
@@ -394,7 +454,11 @@ int mpd_main(int argc, char *argv[])
MPD_ERROR("directory update failed");
}
- glue_state_file_init();
+ if (!glue_state_file_init(&error)) {
+ g_warning("%s", error->message);
+ g_error_free(error);
+ return EXIT_FAILURE;
+ }
success = config_get_bool(CONF_AUTO_UPDATE, false);
#ifdef ENABLE_INOTIFY
@@ -410,7 +474,7 @@ int mpd_main(int argc, char *argv[])
/* enable all audio outputs (if not already done by
playlist_state_restore() */
- pc_update_audio();
+ pc_update_audio(global_player_control);
#ifdef WIN32
win32_app_started();
@@ -431,8 +495,8 @@ int mpd_main(int argc, char *argv[])
mpd_inotify_finish();
#endif
- state_file_finish();
- pc_kill();
+ state_file_finish(global_player_control);
+ pc_kill(global_player_control);
finishZeroconf();
client_manager_deinit();
listen_global_finish();
@@ -457,7 +521,7 @@ int mpd_main(int argc, char *argv[])
mapper_finish();
path_global_finish();
finishPermissions();
- pc_deinit();
+ pc_free(global_player_control);
command_finish();
update_global_finish();
decoder_plugin_deinit_all();
@@ -466,10 +530,9 @@ int mpd_main(int argc, char *argv[])
#endif
config_global_finish();
tag_pool_deinit();
- songvec_deinit();
- dirvec_deinit();
idle_deinit();
stats_global_finish();
+ io_thread_deinit();
daemonize_finish();
#ifdef WIN32
WSACleanup();
diff --git a/src/main.h b/src/main.h
index 9b9cba018..2a7d75910 100644
--- a/src/main.h
+++ b/src/main.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -28,6 +28,8 @@ extern GMainLoop *main_loop;
extern GCond *main_cond;
+extern struct player_control *global_player_control;
+
/**
* A entry point for application.
* On non-Windows platforms this is called directly from main()
diff --git a/src/main_win32.c b/src/main_win32.c
index e1ddb53f5..aac7ad886 100644
--- a/src/main_win32.c
+++ b/src/main_win32.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/mapper.c b/src/mapper.c
index 3cee41eb3..d230f5d92 100644
--- a/src/mapper.c
+++ b/src/mapper.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -117,10 +117,10 @@ void mapper_finish(void)
g_free(playlist_dir);
}
-bool
-mapper_has_music_directory(void)
+const char *
+mapper_get_music_directory(void)
{
- return music_dir != NULL;
+ return music_dir;
}
const char *
diff --git a/src/mapper.h b/src/mapper.h
index 9f84f96fe..ed4a60b56 100644
--- a/src/mapper.h
+++ b/src/mapper.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -24,6 +24,7 @@
#ifndef MPD_MAPPER_H
#define MPD_MAPPER_H
+#include <glib.h>
#include <stdbool.h>
#define PLAYLIST_FILE_SUFFIX ".m3u"
@@ -35,17 +36,26 @@ void mapper_init(const char *_music_dir, const char *_playlist_dir);
void mapper_finish(void);
+G_GNUC_CONST
+const char *
+mapper_get_music_directory(void);
+
/**
* Returns true if a music directory was configured.
*/
-bool
-mapper_has_music_directory(void);
+G_GNUC_CONST
+static inline bool
+mapper_has_music_directory(void)
+{
+ return mapper_get_music_directory() != NULL;
+}
/**
* If the specified absolute path points inside the music directory,
* this function converts it to a relative path. If not, it returns
* the unmodified string pointer.
*/
+G_GNUC_PURE
const char *
map_to_relative_path(const char *path_utf8);
@@ -54,6 +64,7 @@ map_to_relative_path(const char *path_utf8);
* is basically done by converting the URI to the file system charset
* and prepending the music directory.
*/
+G_GNUC_MALLOC
char *
map_uri_fs(const char *uri);
@@ -63,6 +74,7 @@ map_uri_fs(const char *uri);
* @param directory the directory object
* @return the path in file system encoding, or NULL if mapping failed
*/
+G_GNUC_MALLOC
char *
map_directory_fs(const struct directory *directory);
@@ -74,6 +86,7 @@ map_directory_fs(const struct directory *directory);
* @param name the child's name in UTF-8
* @return the path in file system encoding, or NULL if mapping failed
*/
+G_GNUC_MALLOC
char *
map_directory_child_fs(const struct directory *directory, const char *name);
@@ -84,6 +97,7 @@ map_directory_child_fs(const struct directory *directory, const char *name);
* @param song the song object
* @return the path in file system encoding, or NULL if mapping failed
*/
+G_GNUC_MALLOC
char *
map_song_fs(const struct song *song);
@@ -94,12 +108,14 @@ map_song_fs(const struct song *song);
* @param path_fs a path in file system encoding
* @return the relative path in UTF-8, or NULL if mapping failed
*/
+G_GNUC_MALLOC
char *
map_fs_to_utf8(const char *path_fs);
/**
* Returns the playlist directory.
*/
+G_GNUC_CONST
const char *
map_spl_path(void);
@@ -110,6 +126,7 @@ map_spl_path(void);
*
* @return the path in file system encoding, or NULL if mapping failed
*/
+G_GNUC_PURE
char *
map_spl_utf8_to_fs(const char *name);
diff --git a/src/mixer/alsa_mixer_plugin.c b/src/mixer/alsa_mixer_plugin.c
index 38f36cb8f..22e4e22bd 100644
--- a/src/mixer/alsa_mixer_plugin.c
+++ b/src/mixer/alsa_mixer_plugin.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -20,6 +20,7 @@
#include "config.h"
#include "mixer_api.h"
#include "output_api.h"
+#include "event_pipe.h"
#include <glib.h>
#include <alsa/asoundlib.h>
@@ -28,6 +29,15 @@
#define VOLUME_MIXER_ALSA_CONTROL_DEFAULT "PCM"
#define VOLUME_MIXER_ALSA_INDEX_DEFAULT 0
+struct alsa_mixer_source {
+ GSource source;
+
+ snd_mixer_t *mixer;
+
+ /** a linked list of all registered GPollFD objects */
+ GSList *fds;
+};
+
struct alsa_mixer {
/** the base mixer class */
struct mixer base;
@@ -41,6 +51,8 @@ struct alsa_mixer {
long volume_min;
long volume_max;
int volume_set;
+
+ struct alsa_mixer_source *source;
};
/**
@@ -52,6 +64,161 @@ alsa_mixer_quark(void)
return g_quark_from_static_string("alsa_mixer");
}
+/*
+ * GSource helper functions
+ *
+ */
+
+static GSList **
+find_fd(GSList **list_r, int fd)
+{
+ while (true) {
+ GSList *list = *list_r;
+ if (list == NULL)
+ return NULL;
+
+ GPollFD *p = list->data;
+ if (p->fd == fd)
+ return list_r;
+
+ list_r = &list->next;
+ }
+}
+
+static void
+alsa_mixer_update_fd(struct alsa_mixer_source *source, const struct pollfd *p,
+ GSList **old_r)
+{
+ GSList **found_r = find_fd(old_r, p->fd);
+ if (found_r == NULL) {
+ /* new fd */
+ GPollFD *q = g_new(GPollFD, 1);
+ q->fd = p->fd;
+ q->events = p->events;
+ g_source_add_poll(&source->source, q);
+ source->fds = g_slist_prepend(source->fds, q);
+ return;
+ }
+
+ GSList *found = *found_r;
+ *found_r = found->next;
+
+ GPollFD *q = found->data;
+ if (q->events != p->events) {
+ /* refresh events */
+ g_source_remove_poll(&source->source, q);
+ q->events = p->events;
+ g_source_add_poll(&source->source, q);
+ }
+
+ found->next = source->fds;
+ source->fds = found;
+}
+
+static void
+alsa_mixer_update_fds(struct alsa_mixer_source *source)
+{
+ int count = snd_mixer_poll_descriptors_count(source->mixer);
+ if (count < 0)
+ count = 0;
+
+ struct pollfd *pfds = g_new(struct pollfd, count);
+ count = snd_mixer_poll_descriptors(source->mixer, pfds, count);
+ if (count < 0)
+ count = 0;
+
+ GSList *old = source->fds;
+ source->fds = NULL;
+
+ for (int i = 0; i < count; ++i)
+ alsa_mixer_update_fd(source, &pfds[i], &old);
+ g_free(pfds);
+
+ for (; old != NULL; old = old->next) {
+ GPollFD *q = old->data;
+ g_source_remove_poll(&source->source, q);
+ g_free(q);
+ }
+
+ g_slist_free(old);
+}
+
+/*
+ * GSource methods
+ *
+ */
+
+static gboolean
+alsa_mixer_source_prepare(GSource *_source, G_GNUC_UNUSED gint *timeout_r)
+{
+ struct alsa_mixer_source *source = (struct alsa_mixer_source *)_source;
+ alsa_mixer_update_fds(source);
+
+ return false;
+}
+
+static gboolean
+alsa_mixer_source_check(GSource *_source)
+{
+ struct alsa_mixer_source *source = (struct alsa_mixer_source *)_source;
+
+ for (const GSList *i = source->fds; i != NULL; i = i->next) {
+ const GPollFD *poll_fd = i->data;
+ if (poll_fd->revents != 0)
+ return true;
+ }
+
+ return false;
+}
+
+static gboolean
+alsa_mixer_source_dispatch(GSource *_source,
+ G_GNUC_UNUSED GSourceFunc callback,
+ G_GNUC_UNUSED gpointer user_data)
+{
+ struct alsa_mixer_source *source = (struct alsa_mixer_source *)_source;
+
+ snd_mixer_handle_events(source->mixer);
+ return true;
+}
+
+static void
+alsa_mixer_source_finalize(GSource *_source)
+{
+ struct alsa_mixer_source *source = (struct alsa_mixer_source *)_source;
+
+ for (GSList *i = source->fds; i != NULL; i = i->next)
+ g_free(i->data);
+
+ g_slist_free(source->fds);
+}
+
+static GSourceFuncs alsa_mixer_source_funcs = {
+ .prepare = alsa_mixer_source_prepare,
+ .check = alsa_mixer_source_check,
+ .dispatch = alsa_mixer_source_dispatch,
+ .finalize = alsa_mixer_source_finalize,
+};
+
+/*
+ * libasound callbacks
+ *
+ */
+
+static int
+alsa_mixer_elem_callback(G_GNUC_UNUSED snd_mixer_elem_t *elem, unsigned mask)
+{
+ if (mask & SND_CTL_EVENT_MASK_VALUE)
+ event_pipe_emit(PIPE_EVENT_MIXER);
+
+ return 0;
+}
+
+/*
+ * mixer_plugin methods
+ *
+ */
+
static struct mixer *
alsa_mixer_init(G_GNUC_UNUSED void *ao, const struct config_param *param,
G_GNUC_UNUSED GError **error_r)
@@ -81,34 +248,28 @@ alsa_mixer_finish(struct mixer *data)
snd_config_update_free_global();
}
-static void
-alsa_mixer_close(struct mixer *data)
+G_GNUC_PURE
+static snd_mixer_elem_t *
+alsa_mixer_lookup_elem(snd_mixer_t *handle, const char *name, unsigned idx)
{
- struct alsa_mixer *am = (struct alsa_mixer *)data;
-
- assert(am->handle != NULL);
+ for (snd_mixer_elem_t *elem = snd_mixer_first_elem(handle);
+ elem != NULL; elem = snd_mixer_elem_next(elem)) {
+ if (snd_mixer_elem_get_type(elem) == SND_MIXER_ELEM_SIMPLE &&
+ g_ascii_strcasecmp(snd_mixer_selem_get_name(elem),
+ name) == 0 &&
+ snd_mixer_selem_get_index(elem) == idx)
+ return elem;
+ }
- snd_mixer_close(am->handle);
+ return NULL;
}
static bool
-alsa_mixer_open(struct mixer *data, GError **error_r)
+alsa_mixer_setup(struct alsa_mixer *am, GError **error_r)
{
- struct alsa_mixer *am = (struct alsa_mixer *)data;
int err;
- snd_mixer_elem_t *elem;
-
- am->volume_set = -1;
-
- err = snd_mixer_open(&am->handle, 0);
- if (err < 0) {
- g_set_error(error_r, alsa_mixer_quark(), err,
- "snd_mixer_open() failed: %s", snd_strerror(err));
- return false;
- }
if ((err = snd_mixer_attach(am->handle, am->device)) < 0) {
- alsa_mixer_close(data);
g_set_error(error_r, alsa_mixer_quark(), err,
"failed to attach to %s: %s",
am->device, snd_strerror(err));
@@ -117,7 +278,6 @@ alsa_mixer_open(struct mixer *data, GError **error_r)
if ((err = snd_mixer_selem_register(am->handle, NULL,
NULL)) < 0) {
- alsa_mixer_close(data);
g_set_error(error_r, alsa_mixer_quark(), err,
"snd_mixer_selem_register() failed: %s",
snd_strerror(err));
@@ -125,38 +285,69 @@ alsa_mixer_open(struct mixer *data, GError **error_r)
}
if ((err = snd_mixer_load(am->handle)) < 0) {
- alsa_mixer_close(data);
g_set_error(error_r, alsa_mixer_quark(), err,
"snd_mixer_load() failed: %s\n",
snd_strerror(err));
return false;
}
- elem = snd_mixer_first_elem(am->handle);
+ am->elem = alsa_mixer_lookup_elem(am->handle, am->control, am->index);
+ if (am->elem == NULL) {
+ g_set_error(error_r, alsa_mixer_quark(), 0,
+ "no such mixer control: %s", am->control);
+ return false;
+ }
+
+ snd_mixer_selem_get_playback_volume_range(am->elem,
+ &am->volume_min,
+ &am->volume_max);
+
+ snd_mixer_elem_set_callback(am->elem, alsa_mixer_elem_callback);
- while (elem) {
- if (snd_mixer_elem_get_type(elem) == SND_MIXER_ELEM_SIMPLE) {
- if ((g_ascii_strcasecmp(am->control,
- snd_mixer_selem_get_name(elem)) == 0) &&
- (am->index == snd_mixer_selem_get_index(elem))) {
- break;
- }
- }
- elem = snd_mixer_elem_next(elem);
+ am->source = (struct alsa_mixer_source *)
+ g_source_new(&alsa_mixer_source_funcs, sizeof(*am->source));
+ am->source->mixer = am->handle;
+ am->source->fds = NULL;
+ g_source_attach(&am->source->source, g_main_context_default());
+
+ return true;
+}
+
+static bool
+alsa_mixer_open(struct mixer *data, GError **error_r)
+{
+ struct alsa_mixer *am = (struct alsa_mixer *)data;
+ int err;
+
+ am->volume_set = -1;
+
+ err = snd_mixer_open(&am->handle, 0);
+ if (err < 0) {
+ g_set_error(error_r, alsa_mixer_quark(), err,
+ "snd_mixer_open() failed: %s", snd_strerror(err));
+ return false;
}
- if (elem) {
- am->elem = elem;
- snd_mixer_selem_get_playback_volume_range(am->elem,
- &am->volume_min,
- &am->volume_max);
- return true;
+ if (!alsa_mixer_setup(am, error_r)) {
+ snd_mixer_close(am->handle);
+ return false;
}
- alsa_mixer_close(data);
- g_set_error(error_r, alsa_mixer_quark(), 0,
- "no such mixer control: %s", am->control);
- return false;
+ return true;
+}
+
+static void
+alsa_mixer_close(struct mixer *data)
+{
+ struct alsa_mixer *am = (struct alsa_mixer *)data;
+
+ assert(am->handle != NULL);
+
+ g_source_destroy(&am->source->source);
+ g_source_unref(&am->source->source);
+
+ snd_mixer_elem_set_callback(am->elem, NULL);
+ snd_mixer_close(am->handle);
}
static int
diff --git a/src/mixer/oss_mixer_plugin.c b/src/mixer/oss_mixer_plugin.c
index 418068ac2..608f1f9b8 100644
--- a/src/mixer/oss_mixer_plugin.c
+++ b/src/mixer/oss_mixer_plugin.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/mixer/pulse_mixer_plugin.c b/src/mixer/pulse_mixer_plugin.c
index 2be0b8266..a82c032b3 100644
--- a/src/mixer/pulse_mixer_plugin.c
+++ b/src/mixer/pulse_mixer_plugin.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -191,13 +191,13 @@ pulse_mixer_get_volume(struct mixer *mixer, G_GNUC_UNUSED GError **error_r)
struct pulse_mixer *pm = (struct pulse_mixer *) mixer;
int ret;
- pa_threaded_mainloop_lock(pm->output->mainloop);
+ pulse_output_lock(pm->output);
ret = pm->online
? (int)((100*(pa_cvolume_avg(&pm->volume)+1))/PA_VOLUME_NORM)
: -1;
- pa_threaded_mainloop_unlock(pm->output->mainloop);
+ pulse_output_unlock(pm->output);
return ret;
}
@@ -209,9 +209,10 @@ pulse_mixer_set_volume(struct mixer *mixer, unsigned volume, GError **error_r)
struct pa_cvolume cvolume;
bool success;
- pa_threaded_mainloop_lock(pm->output->mainloop);
+ pulse_output_lock(pm->output);
+
if (!pm->online) {
- pa_threaded_mainloop_unlock(pm->output->mainloop);
+ pulse_output_unlock(pm->output);
g_set_error(error_r, pulse_mixer_quark(), 0, "disconnected");
return false;
}
@@ -221,7 +222,8 @@ pulse_mixer_set_volume(struct mixer *mixer, unsigned volume, GError **error_r)
success = pulse_output_set_volume(pm->output, &cvolume, error_r);
if (success)
pm->volume = cvolume;
- pa_threaded_mainloop_unlock(pm->output->mainloop);
+
+ pulse_output_unlock(pm->output);
return success;
}
diff --git a/src/mixer/pulse_mixer_plugin.h b/src/mixer/pulse_mixer_plugin.h
index be199f688..461633d37 100644
--- a/src/mixer/pulse_mixer_plugin.h
+++ b/src/mixer/pulse_mixer_plugin.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/mixer/raop_mixer_plugin.c b/src/mixer/raop_mixer_plugin.c
new file mode 100644
index 000000000..b05671212
--- /dev/null
+++ b/src/mixer/raop_mixer_plugin.c
@@ -0,0 +1,67 @@
+/*
+ * 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 "../output/raop_output_plugin.h"
+#include "output_plugin.h"
+#include "mixer_api.h"
+
+struct raop_mixer_plugin {
+ struct mixer base;
+ struct raop_data *rd;
+};
+
+static struct mixer *
+raop_mixer_init(void *ao, G_GNUC_UNUSED const struct config_param *param,
+ G_GNUC_UNUSED GError **error_r)
+{
+ struct raop_mixer_plugin *rm = g_new(struct raop_mixer_plugin, 1);
+ rm->rd = (struct raop_data *) ao;
+ mixer_init(&rm->base, &raop_mixer_plugin);
+
+ return &rm->base;
+}
+
+static void
+raop_mixer_finish(struct mixer *data)
+{
+ struct raop_mixer_plugin *rm = (struct raop_mixer_plugin *) data;
+
+ g_free(rm);
+}
+
+static int
+raop_mixer_get_volume(struct mixer *mixer, G_GNUC_UNUSED GError **error_r)
+{
+ struct raop_mixer_plugin *rm = (struct raop_mixer_plugin *)mixer;
+ return raop_get_volume(rm->rd);
+}
+
+static bool
+raop_mixer_set_volume(struct mixer *mixer, unsigned volume, GError **error_r)
+{
+ struct raop_mixer_plugin *rm = (struct raop_mixer_plugin *)mixer;
+ return raop_set_volume(rm->rd, volume, error_r);
+}
+
+const struct mixer_plugin raop_mixer_plugin = {
+ .init = raop_mixer_init,
+ .finish = raop_mixer_finish,
+ .get_volume = raop_mixer_get_volume,
+ .set_volume = raop_mixer_set_volume,
+};
diff --git a/src/mixer/roar_mixer_plugin.c b/src/mixer/roar_mixer_plugin.c
new file mode 100644
index 000000000..47d3c17f9
--- /dev/null
+++ b/src/mixer/roar_mixer_plugin.c
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2010-2011 Philipp 'ph3-der-loewe' Schafft
+ * Copyright (C) 2010-2011 Hans-Kristian 'maister' Arntzen
+ *
+ * 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/roar_output_plugin.h"
+
+#include <glib.h>
+
+#include <assert.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+typedef struct roar_mpd_mixer
+{
+ /** the base mixer class */
+ struct mixer base;
+ struct roar *self;
+} roar_mixer_t;
+
+/**
+ * The quark used for GError.domain.
+ */
+static inline GQuark
+roar_mixer_quark(void)
+{
+ return g_quark_from_static_string("roar_mixer");
+}
+
+static struct mixer *
+roar_mixer_init(void *ao, G_GNUC_UNUSED const struct config_param *param,
+ G_GNUC_UNUSED GError **error_r)
+{
+ roar_mixer_t *self = g_new(roar_mixer_t, 1);
+ self->self = ao;
+
+ mixer_init(&self->base, &roar_mixer_plugin);
+
+ return &self->base;
+}
+
+static void
+roar_mixer_finish(struct mixer *data)
+{
+ roar_mixer_t *self = (roar_mixer_t *) data;
+
+ g_free(self);
+}
+
+static void
+roar_mixer_close(G_GNUC_UNUSED struct mixer *data)
+{
+}
+
+static bool
+roar_mixer_open(G_GNUC_UNUSED struct mixer *data,
+ G_GNUC_UNUSED GError **error_r)
+{
+ return true;
+}
+
+static int
+roar_mixer_get_volume(struct mixer *mixer, G_GNUC_UNUSED GError **error_r)
+{
+ roar_mixer_t *self = (roar_mixer_t *)mixer;
+ return roar_output_get_volume(self->self);
+}
+
+static bool
+roar_mixer_set_volume(struct mixer *mixer, unsigned volume,
+ G_GNUC_UNUSED GError **error_r)
+{
+ roar_mixer_t *self = (roar_mixer_t *)mixer;
+ return roar_output_set_volume(self->self, volume);
+}
+
+const struct mixer_plugin roar_mixer_plugin = {
+ .init = roar_mixer_init,
+ .finish = roar_mixer_finish,
+ .open = roar_mixer_open,
+ .close = roar_mixer_close,
+ .get_volume = roar_mixer_get_volume,
+ .set_volume = roar_mixer_set_volume,
+ .global = false,
+};
diff --git a/src/mixer/software_mixer_plugin.c b/src/mixer/software_mixer_plugin.c
index 93802e977..0206c3b99 100644
--- a/src/mixer/software_mixer_plugin.c
+++ b/src/mixer/software_mixer_plugin.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/mixer/software_mixer_plugin.h b/src/mixer/software_mixer_plugin.h
index 3bd07ac62..ee2b2023c 100644
--- a/src/mixer/software_mixer_plugin.h
+++ b/src/mixer/software_mixer_plugin.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/mixer/winmm_mixer_plugin.c b/src/mixer/winmm_mixer_plugin.c
index 5ab3e7525..ceddf6afd 100644
--- a/src/mixer/winmm_mixer_plugin.c
+++ b/src/mixer/winmm_mixer_plugin.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/mixer_all.c b/src/mixer_all.c
index ffe610b91..95ba90793 100644
--- a/src/mixer_all.c
+++ b/src/mixer_all.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/mixer_all.h b/src/mixer_all.h
index cece23292..fe873e713 100644
--- a/src/mixer_all.h
+++ b/src/mixer_all.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/mixer_api.c b/src/mixer_api.c
index 4c8959fb8..c85916c94 100644
--- a/src/mixer_api.c
+++ b/src/mixer_api.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/mixer_api.h b/src/mixer_api.h
index 26c001703..29c1e00ca 100644
--- a/src/mixer_api.h
+++ b/src/mixer_api.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/mixer_control.c b/src/mixer_control.c
index 458b3abc1..3e984dd04 100644
--- a/src/mixer_control.c
+++ b/src/mixer_control.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/mixer_control.h b/src/mixer_control.h
index 1f48e8ca5..6c3468aca 100644
--- a/src/mixer_control.h
+++ b/src/mixer_control.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/mixer_list.h b/src/mixer_list.h
index a472c8807..95ded5c23 100644
--- a/src/mixer_list.h
+++ b/src/mixer_list.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -28,7 +28,9 @@
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 roar_mixer_plugin;
extern const struct mixer_plugin pulse_mixer_plugin;
+extern const struct mixer_plugin raop_mixer_plugin;
extern const struct mixer_plugin winmm_mixer_plugin;
#endif
diff --git a/src/mixer_plugin.h b/src/mixer_plugin.h
index 0915a03f3..9532b95cb 100644
--- a/src/mixer_plugin.h
+++ b/src/mixer_plugin.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -41,7 +41,7 @@ struct mixer_plugin {
* @param ao the pointer returned by audio_output_plugin.init
* @param param the configuration section, or NULL if there is
* no configuration
- * @param error_r location to store the error occuring, or
+ * @param error_r location to store the error occurring, or
* NULL to ignore errors
* @return a mixer object, or NULL on error
*/
@@ -56,7 +56,7 @@ struct mixer_plugin {
/**
* Open mixer device
*
- * @param error_r location to store the error occuring, or
+ * @param error_r location to store the error occurring, or
* NULL to ignore errors
* @return true on success, false on error
*/
@@ -70,7 +70,7 @@ struct mixer_plugin {
/**
* Reads the current volume.
*
- * @param error_r location to store the error occuring, or
+ * @param error_r location to store the error occurring, or
* NULL to ignore errors
* @return the current volume (0..100 including) or -1 if
* unavailable or on error (error_r set, mixer will be closed)
@@ -80,7 +80,7 @@ struct mixer_plugin {
/**
* Sets the volume.
*
- * @param error_r location to store the error occuring, or
+ * @param error_r location to store the error occurring, or
* NULL to ignore errors
* @param volume the new volume (0..100 including)
* @return true on success, false on error
diff --git a/src/mixer_type.c b/src/mixer_type.c
index 4f347dd94..a479caf16 100644
--- a/src/mixer_type.c
+++ b/src/mixer_type.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/mixer_type.h b/src/mixer_type.h
index fd1c5576c..15d136b5b 100644
--- a/src/mixer_type.h
+++ b/src/mixer_type.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/mpd_error.h b/src/mpd_error.h
index 47618d03c..219738ced 100644
--- a/src/mpd_error.h
+++ b/src/mpd_error.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/notify.c b/src/notify.c
index d148a4bfc..3c0112c91 100644
--- a/src/notify.c
+++ b/src/notify.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/notify.h b/src/notify.h
index 0c657f2fb..40821690c 100644
--- a/src/notify.h
+++ b/src/notify.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/ntp_server.c b/src/ntp_server.c
new file mode 100644
index 000000000..f31a2d845
--- /dev/null
+++ b/src/ntp_server.c
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "ntp_server.h"
+#include "udp_server.h"
+
+#include <glib.h>
+#include <assert.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/time.h>
+
+#ifdef WIN32
+#include <ws2tcpip.h>
+#include <winsock.h>
+#else
+#include <sys/socket.h>
+#endif
+
+/*
+ * Calculate the current NTP time, store it in the buffer.
+ */
+static void
+fill_int(unsigned char *buffer, uint32_t value)
+{
+ uint32_t be = GINT32_TO_BE(value);
+ memcpy(buffer, &be, sizeof(be));
+}
+
+/*
+ * Store time in the NTP format in the buffer
+ */
+static void
+fill_time_buffer_with_time(unsigned char *buffer, struct timeval *tout)
+{
+ unsigned long secs_to_baseline = 964697997;
+ double fraction;
+ unsigned long long_fraction;
+ unsigned long secs;
+
+ fraction = ((double) tout->tv_usec) / 1000000.0;
+ long_fraction = (unsigned long) (fraction * 256.0 * 256.0 * 256.0 * 256.0);
+ secs = secs_to_baseline + tout->tv_sec;
+ fill_int(buffer, secs);
+ fill_int(buffer + 4, long_fraction);
+}
+
+/*
+ * Calculate the current NTP time, store it in the buffer.
+ */
+static void
+fill_time_buffer(unsigned char *buffer)
+{
+ struct timeval current_time;
+
+ gettimeofday(&current_time,NULL);
+ fill_time_buffer_with_time(buffer, &current_time);
+}
+
+static void
+ntp_server_datagram(int fd, const void *data, size_t num_bytes,
+ const struct sockaddr *source_address,
+ size_t source_address_length, G_GNUC_UNUSED void *ctx)
+{
+ unsigned char buf[32];
+ int iter;
+
+ if (num_bytes > sizeof(buf))
+ num_bytes = sizeof(buf);
+ memcpy(buf, data, num_bytes);
+
+ fill_time_buffer(buf + 16);
+ // set to response
+ buf[1] = 0xd3;
+ // copy request
+ for (iter = 0; iter < 8; iter++) {
+ buf[8 + iter] = buf[24 + iter];
+ }
+ fill_time_buffer(buf + 24);
+
+ sendto(fd, (void *)buf, num_bytes, 0,
+ source_address, source_address_length);
+}
+
+static const struct udp_server_handler ntp_server_handler = {
+ .datagram = ntp_server_datagram,
+};
+
+void
+ntp_server_init(struct ntp_server *ntp)
+{
+ ntp->port = 6002;
+ ntp->udp = NULL;
+}
+
+bool
+ntp_server_open(struct ntp_server *ntp, GError **error_r)
+{
+ assert(ntp->udp == NULL);
+
+ ntp->udp = udp_server_new(ntp->port, &ntp_server_handler, ntp,
+ error_r);
+ return ntp->udp != NULL;
+}
+
+void
+ntp_server_close(struct ntp_server *ntp)
+{
+ if (ntp->udp != NULL) {
+ udp_server_free(ntp->udp);
+ ntp->udp = NULL;
+ }
+}
diff --git a/src/ntp_server.h b/src/ntp_server.h
new file mode 100644
index 000000000..fe6f8083b
--- /dev/null
+++ b/src/ntp_server.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_NTP_SERVER_H
+#define MPD_NTP_SERVER_H
+
+#include <glib.h>
+
+#include <stdbool.h>
+
+struct timeval;
+
+struct ntp_server {
+ unsigned short port;
+
+ struct udp_server *udp;
+};
+
+void
+ntp_server_init(struct ntp_server *ntp);
+
+bool
+ntp_server_open(struct ntp_server *ntp, GError **error_r);
+
+void
+ntp_server_close(struct ntp_server *ntp);
+
+#endif
diff --git a/src/open.h b/src/open.h
index e39c64a97..1fe245dfa 100644
--- a/src/open.h
+++ b/src/open.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/output/alsa_plugin.c b/src/output/alsa_output_plugin.c
index ae06847c2..a6dc92fa0 100644
--- a/src/output/alsa_plugin.c
+++ b/src/output/alsa_output_plugin.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -18,8 +18,10 @@
*/
#include "config.h"
+#include "alsa_output_plugin.h"
#include "output_api.h"
#include "mixer_list.h"
+#include "pcm_export.h"
#include <glib.h>
#include <alsa/asoundlib.h>
@@ -42,6 +44,10 @@ typedef snd_pcm_sframes_t alsa_writei_t(snd_pcm_t * pcm, const void *buffer,
snd_pcm_uframes_t size);
struct alsa_data {
+ struct audio_output base;
+
+ struct pcm_export_state export;
+
/** the configured name of the ALSA device; NULL for the
default device */
char *device;
@@ -49,6 +55,14 @@ struct alsa_data {
/** use memory mapped I/O? */
bool use_mmap;
+ /**
+ * Enable DSD over USB according to the dCS suggested
+ * standard?
+ *
+ * @see http://www.dcsltd.co.uk/page/assets/DSDoverUSB.pdf
+ */
+ bool dsd_usb;
+
/** libasound's buffer_time setting (in microseconds) */
unsigned int buffer_time;
@@ -68,8 +82,15 @@ struct alsa_data {
*/
alsa_writei_t *writei;
- /** the size of one audio frame */
- size_t frame_size;
+ /**
+ * The size of one audio frame passed to method play().
+ */
+ size_t in_frame_size;
+
+ /**
+ * The size of one audio frame passed to libasound.
+ */
+ size_t out_frame_size;
/**
* The size of one period, in number of frames.
@@ -109,19 +130,14 @@ alsa_data_new(void)
}
static void
-alsa_data_free(struct alsa_data *ad)
-{
- g_free(ad->device);
- g_free(ad);
-}
-
-static void
alsa_configure(struct alsa_data *ad, const struct config_param *param)
{
ad->device = config_dup_block_string(param, "device", NULL);
ad->use_mmap = config_get_block_bool(param, "use_mmap", false);
+ ad->dsd_usb = config_get_block_bool(param, "dsd_usb", false);
+
ad->buffer_time = config_get_block_unsigned(param, "buffer_time",
MPD_ALSA_BUFFER_TIME_US);
ad->period_time = config_get_block_unsigned(param, "period_time", 0);
@@ -142,30 +158,53 @@ alsa_configure(struct alsa_data *ad, const struct config_param *param)
#endif
}
-static void *
-alsa_init(G_GNUC_UNUSED const struct audio_format *audio_format,
- const struct config_param *param,
- G_GNUC_UNUSED GError **error)
+static struct audio_output *
+alsa_init(const struct config_param *param, GError **error_r)
{
struct alsa_data *ad = alsa_data_new();
+ if (!ao_base_init(&ad->base, &alsa_output_plugin, param, error_r)) {
+ g_free(ad);
+ return NULL;
+ }
+
alsa_configure(ad, param);
- return ad;
+ return &ad->base;
}
static void
-alsa_finish(void *data)
+alsa_finish(struct audio_output *ao)
{
- struct alsa_data *ad = data;
+ struct alsa_data *ad = (struct alsa_data *)ao;
- alsa_data_free(ad);
+ ao_base_finish(&ad->base);
+
+ g_free(ad->device);
+ g_free(ad);
/* free libasound's config cache */
snd_config_update_free_global();
}
static bool
+alsa_output_enable(struct audio_output *ao, G_GNUC_UNUSED GError **error_r)
+{
+ struct alsa_data *ad = (struct alsa_data *)ao;
+
+ pcm_export_init(&ad->export);
+ return true;
+}
+
+static void
+alsa_output_disable(struct audio_output *ao)
+{
+ struct alsa_data *ad = (struct alsa_data *)ao;
+
+ pcm_export_deinit(&ad->export);
+}
+
+static bool
alsa_test_default_device(void)
{
snd_pcm_t *handle;
@@ -187,6 +226,7 @@ get_bitformat(enum sample_format sample_format)
{
switch (sample_format) {
case SAMPLE_FORMAT_UNDEFINED:
+ case SAMPLE_FORMAT_DSD:
return SND_PCM_FORMAT_UNKNOWN;
case SAMPLE_FORMAT_S8:
@@ -198,13 +238,11 @@ get_bitformat(enum sample_format sample_format)
case SAMPLE_FORMAT_S24_P32:
return SND_PCM_FORMAT_S24;
- case SAMPLE_FORMAT_S24:
- return G_BYTE_ORDER == G_BIG_ENDIAN
- ? SND_PCM_FORMAT_S24_3BE
- : SND_PCM_FORMAT_S24_3LE;
-
case SAMPLE_FORMAT_S32:
return SND_PCM_FORMAT_S32;
+
+ case SAMPLE_FORMAT_FLOAT:
+ return SND_PCM_FORMAT_FLOAT;
}
assert(false);
@@ -232,44 +270,39 @@ byteswap_bitformat(snd_pcm_format_t fmt)
}
}
-/**
- * Attempts to configure the specified sample format.
- */
-static int
-alsa_output_try_format(snd_pcm_t *pcm, snd_pcm_hw_params_t *hwparams,
- struct audio_format *audio_format,
- enum sample_format sample_format)
+static snd_pcm_format_t
+alsa_to_packed_format(snd_pcm_format_t fmt)
{
- snd_pcm_format_t alsa_format = get_bitformat(sample_format);
- if (alsa_format == SND_PCM_FORMAT_UNKNOWN)
- return -EINVAL;
+ switch (fmt) {
+ case SND_PCM_FORMAT_S24_LE:
+ return SND_PCM_FORMAT_S24_3LE;
- int err = snd_pcm_hw_params_set_format(pcm, hwparams, alsa_format);
- if (err == 0)
- audio_format->format = sample_format;
+ case SND_PCM_FORMAT_S24_BE:
+ return SND_PCM_FORMAT_S24_3BE;
- return err;
+ default:
+ return SND_PCM_FORMAT_UNKNOWN;
+ }
}
-/**
- * Attempts to configure the specified sample format with reversed
- * host byte order.
- */
static int
-alsa_output_try_reverse(snd_pcm_t *pcm, snd_pcm_hw_params_t *hwparams,
- struct audio_format *audio_format,
- enum sample_format sample_format)
+alsa_try_format_or_packed(snd_pcm_t *pcm, snd_pcm_hw_params_t *hwparams,
+ snd_pcm_format_t fmt, bool *packed_r)
{
- snd_pcm_format_t alsa_format =
- byteswap_bitformat(get_bitformat(sample_format));
- if (alsa_format == SND_PCM_FORMAT_UNKNOWN)
+ int err = snd_pcm_hw_params_set_format(pcm, hwparams, fmt);
+ if (err == 0)
+ *packed_r = false;
+
+ if (err != -EINVAL)
+ return err;
+
+ fmt = alsa_to_packed_format(fmt);
+ if (fmt == SND_PCM_FORMAT_UNKNOWN)
return -EINVAL;
- int err = snd_pcm_hw_params_set_format(pcm, hwparams, alsa_format);
- if (err == 0) {
- audio_format->format = sample_format;
- audio_format->reverse_endian = true;
- }
+ err = snd_pcm_hw_params_set_format(pcm, hwparams, fmt);
+ if (err == 0)
+ *packed_r = true;
return err;
}
@@ -279,15 +312,29 @@ alsa_output_try_reverse(snd_pcm_t *pcm, snd_pcm_hw_params_t *hwparams,
* reversed host byte order if was not supported.
*/
static int
-alsa_output_try_format_both(snd_pcm_t *pcm, snd_pcm_hw_params_t *hwparams,
- struct audio_format *audio_format,
- enum sample_format sample_format)
+alsa_output_try_format(snd_pcm_t *pcm, snd_pcm_hw_params_t *hwparams,
+ enum sample_format sample_format,
+ bool *packed_r, bool *reverse_endian_r)
{
- int err = alsa_output_try_format(pcm, hwparams, audio_format,
- sample_format);
- if (err == -EINVAL)
- err = alsa_output_try_reverse(pcm, hwparams, audio_format,
- sample_format);
+ snd_pcm_format_t alsa_format = get_bitformat(sample_format);
+ if (alsa_format == SND_PCM_FORMAT_UNKNOWN)
+ return -EINVAL;
+
+ int err = alsa_try_format_or_packed(pcm, hwparams, alsa_format,
+ packed_r);
+ if (err == 0)
+ *reverse_endian_r = false;
+
+ if (err != -EINVAL)
+ return err;
+
+ alsa_format = byteswap_bitformat(alsa_format);
+ if (alsa_format == SND_PCM_FORMAT_UNKNOWN)
+ return -EINVAL;
+
+ err = alsa_try_format_or_packed(pcm, hwparams, alsa_format, packed_r);
+ if (err == 0)
+ *reverse_endian_r = true;
return err;
}
@@ -297,37 +344,38 @@ alsa_output_try_format_both(snd_pcm_t *pcm, snd_pcm_hw_params_t *hwparams,
*/
static int
alsa_output_setup_format(snd_pcm_t *pcm, snd_pcm_hw_params_t *hwparams,
- struct audio_format *audio_format)
+ struct audio_format *audio_format,
+ bool *packed_r, bool *reverse_endian_r)
{
/* try the input format first */
- int err = alsa_output_try_format_both(pcm, hwparams, audio_format,
- audio_format->format);
- if (err != -EINVAL)
- return err;
+ int err = alsa_output_try_format(pcm, hwparams, audio_format->format,
+ packed_r, reverse_endian_r);
/* if unsupported by the hardware, try other formats */
static const enum sample_format probe_formats[] = {
SAMPLE_FORMAT_S24_P32,
SAMPLE_FORMAT_S32,
- SAMPLE_FORMAT_S24,
SAMPLE_FORMAT_S16,
SAMPLE_FORMAT_S8,
SAMPLE_FORMAT_UNDEFINED,
};
- for (unsigned i = 0; probe_formats[i] != SAMPLE_FORMAT_UNDEFINED; ++i) {
- if (probe_formats[i] == audio_format->format)
+ for (unsigned i = 0;
+ err == -EINVAL && probe_formats[i] != SAMPLE_FORMAT_UNDEFINED;
+ ++i) {
+ const enum sample_format mpd_format = probe_formats[i];
+ if (mpd_format == audio_format->format)
continue;
- err = alsa_output_try_format_both(pcm, hwparams, audio_format,
- probe_formats[i]);
- if (err != -EINVAL)
- return err;
+ err = alsa_output_try_format(pcm, hwparams, mpd_format,
+ packed_r, reverse_endian_r);
+ if (err == 0)
+ audio_format->format = mpd_format;
}
- return -EINVAL;
+ return err;
}
/**
@@ -336,7 +384,7 @@ alsa_output_setup_format(snd_pcm_t *pcm, snd_pcm_hw_params_t *hwparams,
*/
static bool
alsa_setup(struct alsa_data *ad, struct audio_format *audio_format,
- GError **error)
+ bool *packed_r, bool *reverse_endian_r, GError **error)
{
snd_pcm_hw_params_t *hwparams;
snd_pcm_sw_params_t *swparams;
@@ -380,7 +428,8 @@ configure_hw:
ad->writei = snd_pcm_writei;
}
- err = alsa_output_setup_format(ad->pcm, hwparams, audio_format);
+ err = alsa_output_setup_format(ad->pcm, hwparams, audio_format,
+ packed_r, reverse_endian_r);
if (err < 0) {
g_set_error(error, alsa_output_quark(), err,
"ALSA device \"%s\" does not support format %s: %s",
@@ -390,6 +439,11 @@ configure_hw:
return false;
}
+ snd_pcm_format_t format;
+ if (snd_pcm_hw_params_get_format(hwparams, &format) == 0)
+ g_debug("format=%s (%s)", snd_pcm_format_name(format),
+ snd_pcm_format_description(format));
+
err = snd_pcm_hw_params_set_channels_near(ad->pcm, hwparams,
&channels);
if (err < 0) {
@@ -532,9 +586,72 @@ error:
}
static bool
-alsa_open(void *data, struct audio_format *audio_format, GError **error)
+alsa_setup_dsd(struct alsa_data *ad, struct audio_format *audio_format,
+ bool *shift8_r, bool *packed_r, bool *reverse_endian_r,
+ GError **error_r)
{
- struct alsa_data *ad = data;
+ assert(ad->dsd_usb);
+ assert(audio_format->format == SAMPLE_FORMAT_DSD);
+
+ /* pass 24 bit to alsa_setup() */
+
+ struct audio_format usb_format = *audio_format;
+ usb_format.format = SAMPLE_FORMAT_S24_P32;
+ usb_format.sample_rate /= 2;
+
+ const struct audio_format check = usb_format;
+
+ if (!alsa_setup(ad, &usb_format, packed_r, reverse_endian_r, error_r))
+ return false;
+
+ /* if the device allows only 32 bit, shift all DSD-over-USB
+ samples left by 8 bit and leave the lower 8 bit cleared;
+ the DSD-over-USB documentation does not specify whether
+ this is legal, but there is anecdotical evidence that this
+ is possible (and the only option for some devices) */
+ *shift8_r = usb_format.format == SAMPLE_FORMAT_S32;
+ if (usb_format.format == SAMPLE_FORMAT_S32)
+ usb_format.format = SAMPLE_FORMAT_S24_P32;
+
+ if (!audio_format_equals(&usb_format, &check)) {
+ /* no bit-perfect playback, which is required
+ for DSD over USB */
+ g_set_error(error_r, alsa_output_quark(), 0,
+ "Failed to configure DSD-over-USB on ALSA device \"%s\"",
+ alsa_device(ad));
+ return false;
+ }
+
+ return true;
+}
+
+static bool
+alsa_setup_or_dsd(struct alsa_data *ad, struct audio_format *audio_format,
+ GError **error_r)
+{
+ bool shift8 = false, packed, reverse_endian;
+
+ const bool dsd_usb = ad->dsd_usb &&
+ audio_format->format == SAMPLE_FORMAT_DSD;
+ const bool success = dsd_usb
+ ? alsa_setup_dsd(ad, audio_format,
+ &shift8, &packed, &reverse_endian,
+ error_r)
+ : alsa_setup(ad, audio_format, &packed, &reverse_endian,
+ error_r);
+ if (!success)
+ return false;
+
+ pcm_export_open(&ad->export,
+ audio_format->format, audio_format->channels,
+ dsd_usb, shift8, packed, reverse_endian);
+ return true;
+}
+
+static bool
+alsa_open(struct audio_output *ao, struct audio_format *audio_format, GError **error)
+{
+ struct alsa_data *ad = (struct alsa_data *)ao;
int err;
bool success;
@@ -547,13 +664,17 @@ alsa_open(void *data, struct audio_format *audio_format, GError **error)
return false;
}
- success = alsa_setup(ad, audio_format, error);
+ g_debug("opened %s type=%s", snd_pcm_name(ad->pcm),
+ snd_pcm_type_name(snd_pcm_type(ad->pcm)));
+
+ success = alsa_setup_or_dsd(ad, audio_format, error);
if (!success) {
snd_pcm_close(ad->pcm);
return false;
}
- ad->frame_size = audio_format_frame_size(audio_format);
+ ad->in_frame_size = audio_format_frame_size(audio_format);
+ ad->out_frame_size = ad->export.pack24 ? 3 : ad->in_frame_size;
return true;
}
@@ -596,9 +717,9 @@ alsa_recover(struct alsa_data *ad, int err)
}
static void
-alsa_drain(void *data)
+alsa_drain(struct audio_output *ao)
{
- struct alsa_data *ad = data;
+ struct alsa_data *ad = (struct alsa_data *)ao;
if (snd_pcm_state(ad->pcm) != SND_PCM_STATE_RUNNING)
return;
@@ -608,7 +729,7 @@ alsa_drain(void *data)
period */
snd_pcm_uframes_t nframes =
ad->period_frames - ad->period_position;
- size_t nbytes = nframes * ad->frame_size;
+ size_t nbytes = nframes * ad->out_frame_size;
void *buffer = g_malloc(nbytes);
snd_pcm_hw_params_t *params;
snd_pcm_format_t format;
@@ -630,9 +751,9 @@ alsa_drain(void *data)
}
static void
-alsa_cancel(void *data)
+alsa_cancel(struct audio_output *ao)
{
- struct alsa_data *ad = data;
+ struct alsa_data *ad = (struct alsa_data *)ao;
ad->period_position = 0;
@@ -640,26 +761,34 @@ alsa_cancel(void *data)
}
static void
-alsa_close(void *data)
+alsa_close(struct audio_output *ao)
{
- struct alsa_data *ad = data;
+ struct alsa_data *ad = (struct alsa_data *)ao;
snd_pcm_close(ad->pcm);
}
static size_t
-alsa_play(void *data, const void *chunk, size_t size, GError **error)
+alsa_play(struct audio_output *ao, const void *chunk, size_t size,
+ GError **error)
{
- struct alsa_data *ad = data;
+ struct alsa_data *ad = (struct alsa_data *)ao;
+
+ assert(size % ad->in_frame_size == 0);
+
+ chunk = pcm_export(&ad->export, chunk, size, &size);
+
+ assert(size % ad->out_frame_size == 0);
- size /= ad->frame_size;
+ size /= ad->out_frame_size;
while (true) {
snd_pcm_sframes_t ret = ad->writei(ad->pcm, chunk, size);
if (ret > 0) {
ad->period_position = (ad->period_position + ret)
% ad->period_frames;
- return ret * ad->frame_size;
+ return pcm_export_source_size(&ad->export,
+ ret * ad->in_frame_size);
}
if (ret < 0 && ret != -EAGAIN && ret != -EINTR &&
@@ -671,11 +800,13 @@ alsa_play(void *data, const void *chunk, size_t size, GError **error)
}
}
-const struct audio_output_plugin alsaPlugin = {
+const struct audio_output_plugin alsa_output_plugin = {
.name = "alsa",
.test_default_device = alsa_test_default_device,
.init = alsa_init,
.finish = alsa_finish,
+ .enable = alsa_output_enable,
+ .disable = alsa_output_disable,
.open = alsa_open,
.play = alsa_play,
.drain = alsa_drain,
diff --git a/src/output/alsa_output_plugin.h b/src/output/alsa_output_plugin.h
new file mode 100644
index 000000000..daa1f3615
--- /dev/null
+++ b/src/output/alsa_output_plugin.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_ALSA_OUTPUT_PLUGIN_H
+#define MPD_ALSA_OUTPUT_PLUGIN_H
+
+extern const struct audio_output_plugin alsa_output_plugin;
+
+#endif
diff --git a/src/output/ao_plugin.c b/src/output/ao_output_plugin.c
index 42ece5a3a..d7e577fa4 100644
--- a/src/output/ao_plugin.c
+++ b/src/output/ao_output_plugin.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -18,6 +18,7 @@
*/
#include "config.h"
+#include "ao_output_plugin.h"
#include "output_api.h"
#include <ao/ao.h>
@@ -32,6 +33,8 @@ static const ao_sample_format OUR_AO_FORMAT_INITIALIZER;
static unsigned ao_output_ref;
struct ao_data {
+ struct audio_output base;
+
size_t write_size;
int driver;
ao_option *options;
@@ -71,19 +74,24 @@ ao_output_error(GError **error_r)
break;
default:
- error = strerror(errno);
+ error = g_strerror(errno);
}
g_set_error(error_r, ao_output_quark(), errno,
"%s", error);
}
-static void *
-ao_output_init(G_GNUC_UNUSED const struct audio_format *audio_format,
- const struct config_param *param,
+static struct audio_output *
+ao_output_init(const struct config_param *param,
GError **error)
{
struct ao_data *ad = g_new(struct ao_data, 1);
+
+ if (!ao_base_init(&ad->base, &ao_output_plugin, param, error)) {
+ g_free(ad);
+ return NULL;
+ }
+
ao_info *ai;
const char *value;
@@ -106,6 +114,7 @@ ao_output_init(G_GNUC_UNUSED const struct audio_format *audio_format,
g_set_error(error, ao_output_quark(), 0,
"\"%s\" is not a valid ao driver",
value);
+ ao_base_finish(&ad->base);
g_free(ad);
return NULL;
}
@@ -113,6 +122,7 @@ ao_output_init(G_GNUC_UNUSED const struct audio_format *audio_format,
if ((ai = ao_driver_info(ad->driver)) == NULL) {
g_set_error(error, ao_output_quark(), 0,
"problems getting driver info");
+ ao_base_finish(&ad->base);
g_free(ad);
return NULL;
}
@@ -131,6 +141,7 @@ ao_output_init(G_GNUC_UNUSED const struct audio_format *audio_format,
g_set_error(error, ao_output_quark(), 0,
"problems parsing options \"%s\"",
options[i]);
+ ao_base_finish(&ad->base);
g_free(ad);
return NULL;
}
@@ -144,15 +155,16 @@ ao_output_init(G_GNUC_UNUSED const struct audio_format *audio_format,
g_strfreev(options);
}
- return ad;
+ return &ad->base;
}
static void
-ao_output_finish(void *data)
+ao_output_finish(struct audio_output *ao)
{
- struct ao_data *ad = (struct ao_data *)data;
+ struct ao_data *ad = (struct ao_data *)ao;
ao_free_options(ad->options);
+ ao_base_finish(&ad->base);
g_free(ad);
ao_output_ref--;
@@ -162,19 +174,19 @@ ao_output_finish(void *data)
}
static void
-ao_output_close(void *data)
+ao_output_close(struct audio_output *ao)
{
- struct ao_data *ad = (struct ao_data *)data;
+ struct ao_data *ad = (struct ao_data *)ao;
ao_close(ad->device);
}
static bool
-ao_output_open(void *data, struct audio_format *audio_format,
+ao_output_open(struct audio_output *ao, struct audio_format *audio_format,
GError **error)
{
ao_sample_format format = OUR_AO_FORMAT_INITIALIZER;
- struct ao_data *ad = (struct ao_data *)data;
+ struct ao_data *ad = (struct ao_data *)ao;
switch (audio_format->format) {
case SAMPLE_FORMAT_S8:
@@ -226,10 +238,10 @@ static int ao_play_deconst(ao_device *device, const void *output_samples,
}
static size_t
-ao_output_play(void *data, const void *chunk, size_t size,
+ao_output_play(struct audio_output *ao, const void *chunk, size_t size,
GError **error)
{
- struct ao_data *ad = (struct ao_data *)data;
+ struct ao_data *ad = (struct ao_data *)ao;
if (size > ad->write_size)
size = ad->write_size;
diff --git a/src/output/ao_output_plugin.h b/src/output/ao_output_plugin.h
new file mode 100644
index 000000000..9a3a47c05
--- /dev/null
+++ b/src/output/ao_output_plugin.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_AO_OUTPUT_PLUGIN_H
+#define MPD_AO_OUTPUT_PLUGIN_H
+
+extern const struct audio_output_plugin ao_output_plugin;
+
+#endif
diff --git a/src/output/ffado_output_plugin.c b/src/output/ffado_output_plugin.c
index 723698ed0..ba239a4ad 100644
--- a/src/output/ffado_output_plugin.c
+++ b/src/output/ffado_output_plugin.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -30,6 +30,7 @@
*/
#include "config.h"
+#include "ffado_output_plugin.h"
#include "output_api.h"
#include "timer.h"
@@ -53,6 +54,8 @@ struct mpd_ffado_stream {
};
struct mpd_ffado_device {
+ struct audio_output base;
+
char *device_name;
int verbose;
unsigned period_size, nb_buffers;
@@ -82,21 +85,26 @@ 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,
+static struct audio_output *
+ffado_init(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);
+ if (!ao_base_init(&fd->base, &ffado_output_plugin, param, error_r)) {
+ g_free(fd);
+ return NULL;
+ }
+
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) {
+ ao_base_finish(&fd->base);
g_set_error(error_r, ffado_output_quark(), 0,
"invalid period_size setting");
return false;
@@ -104,20 +112,22 @@ ffado_init(G_GNUC_UNUSED const struct audio_format *audio_format,
fd->nb_buffers = config_get_block_unsigned(param, "nb_buffers", 3);
if (fd->nb_buffers == 0 || fd->nb_buffers > 1024) {
+ ao_base_finish(&fd->base);
g_set_error(error_r, ffado_output_quark(), 0,
"invalid nb_buffers setting");
return false;
}
- return fd;
+ return &fd->base;
}
static void
-ffado_finish(void *data)
+ffado_finish(struct audio_output *ao)
{
- struct mpd_ffado_device *fd = data;
+ struct mpd_ffado_device *fd = (struct mpd_ffado_device *)ao;
g_free(fd->device_name);
+ ao_base_finish(&fd->base);
g_free(fd);
}
@@ -227,9 +237,10 @@ ffado_configure(struct mpd_ffado_device *fd, struct audio_format *audio_format,
}
static bool
-ffado_open(void *data, struct audio_format *audio_format, GError **error_r)
+ffado_open(struct audio_output *ao, struct audio_format *audio_format,
+ GError **error_r)
{
- struct mpd_ffado_device *fd = data;
+ struct mpd_ffado_device *fd = (struct mpd_ffado_device *)ao;
/* will be converted to floating point, choose best input
format */
@@ -273,9 +284,9 @@ ffado_open(void *data, struct audio_format *audio_format, GError **error_r)
}
static void
-ffado_close(void *data)
+ffado_close(struct audio_output *ao)
{
- struct mpd_ffado_device *fd = data;
+ struct mpd_ffado_device *fd = (struct mpd_ffado_device *)ao;
ffado_streaming_stop(fd->dev);
ffado_streaming_finish(fd->dev);
@@ -287,9 +298,10 @@ ffado_close(void *data)
}
static size_t
-ffado_play(void *data, const void *chunk, size_t size, GError **error_r)
+ffado_play(struct audio_output *ao, const void *chunk, size_t size,
+ GError **error_r)
{
- struct mpd_ffado_device *fd = data;
+ struct mpd_ffado_device *fd = (struct mpd_ffado_device *)ao;
/* wait for prefious buffer to finish (if it was full) */
diff --git a/src/output/ffado_output_plugin.h b/src/output/ffado_output_plugin.h
new file mode 100644
index 000000000..4dde01859
--- /dev/null
+++ b/src/output/ffado_output_plugin.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_FFADO_OUTPUT_PLUGIN_H
+#define MPD_FFADO_OUTPUT_PLUGIN_H
+
+extern const struct audio_output_plugin ffado_output_plugin;
+
+#endif
diff --git a/src/output/fifo_output_plugin.c b/src/output/fifo_output_plugin.c
index f4217ec4d..022be0b4a 100644
--- a/src/output/fifo_output_plugin.c
+++ b/src/output/fifo_output_plugin.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -18,6 +18,7 @@
*/
#include "config.h"
+#include "fifo_output_plugin.h"
#include "output_api.h"
#include "utils.h"
#include "timer.h"
@@ -38,11 +39,13 @@
#define FIFO_BUFFER_SIZE 65536 /* pipe capacity on Linux >= 2.6.11 */
struct fifo_data {
+ struct audio_output base;
+
char *path;
int input;
int output;
bool created;
- Timer *timer;
+ struct timer *timer;
};
/**
@@ -80,7 +83,7 @@ static void fifo_delete(struct fifo_data *fd)
if (unlink(fd->path) < 0) {
g_warning("Could not remove FIFO \"%s\": %s",
- fd->path, strerror(errno));
+ fd->path, g_strerror(errno));
return;
}
@@ -112,7 +115,7 @@ fifo_make(struct fifo_data *fd, GError **error)
if (mkfifo(fd->path, 0666) < 0) {
g_set_error(error, fifo_output_quark(), errno,
"Couldn't create FIFO \"%s\": %s",
- fd->path, strerror(errno));
+ fd->path, g_strerror(errno));
return false;
}
@@ -134,7 +137,7 @@ fifo_check(struct fifo_data *fd, GError **error)
g_set_error(error, fifo_output_quark(), errno,
"Failed to stat FIFO \"%s\": %s",
- fd->path, strerror(errno));
+ fd->path, g_strerror(errno));
return false;
}
@@ -158,7 +161,7 @@ fifo_open(struct fifo_data *fd, GError **error)
if (fd->input < 0) {
g_set_error(error, fifo_output_quark(), errno,
"Could not open FIFO \"%s\" for reading: %s",
- fd->path, strerror(errno));
+ fd->path, g_strerror(errno));
fifo_close(fd);
return false;
}
@@ -167,7 +170,7 @@ fifo_open(struct fifo_data *fd, GError **error)
if (fd->output < 0) {
g_set_error(error, fifo_output_quark(), errno,
"Could not open FIFO \"%s\" for writing: %s",
- fd->path, strerror(errno));
+ fd->path, g_strerror(errno));
fifo_close(fd);
return false;
}
@@ -175,54 +178,55 @@ fifo_open(struct fifo_data *fd, GError **error)
return true;
}
-static void *
-fifo_output_init(G_GNUC_UNUSED const struct audio_format *audio_format,
- const struct config_param *param,
- GError **error)
+static struct audio_output *
+fifo_output_init(const struct config_param *param,
+ GError **error_r)
{
struct fifo_data *fd;
- char *value, *path;
-
- value = config_dup_block_string(param, "path", NULL);
- if (value == NULL) {
- g_set_error(error, fifo_output_quark(), errno,
- "No \"path\" parameter specified");
- return NULL;
- }
- path = parsePath(value);
- g_free(value);
+ GError *error = NULL;
+ char *path = config_dup_block_path(param, "path", &error);
if (!path) {
- g_set_error(error, fifo_output_quark(), errno,
- "Could not parse \"path\" parameter");
+ if (error != NULL)
+ g_propagate_error(error_r, error);
+ else
+ g_set_error(error_r, fifo_output_quark(), 0,
+ "No \"path\" parameter specified");
return NULL;
}
fd = fifo_data_new();
fd->path = path;
- if (!fifo_open(fd, error)) {
+ if (!ao_base_init(&fd->base, &fifo_output_plugin, param, error_r)) {
+ fifo_data_free(fd);
+ return NULL;
+ }
+
+ if (!fifo_open(fd, error_r)) {
+ ao_base_finish(&fd->base);
fifo_data_free(fd);
return NULL;
}
- return fd;
+ return &fd->base;
}
static void
-fifo_output_finish(void *data)
+fifo_output_finish(struct audio_output *ao)
{
- struct fifo_data *fd = (struct fifo_data *)data;
+ struct fifo_data *fd = (struct fifo_data *)ao;
fifo_close(fd);
+ ao_base_finish(&fd->base);
fifo_data_free(fd);
}
static bool
-fifo_output_open(void *data, struct audio_format *audio_format,
+fifo_output_open(struct audio_output *ao, struct audio_format *audio_format,
G_GNUC_UNUSED GError **error)
{
- struct fifo_data *fd = (struct fifo_data *)data;
+ struct fifo_data *fd = (struct fifo_data *)ao;
fd->timer = timer_new(audio_format);
@@ -230,17 +234,17 @@ fifo_output_open(void *data, struct audio_format *audio_format,
}
static void
-fifo_output_close(void *data)
+fifo_output_close(struct audio_output *ao)
{
- struct fifo_data *fd = (struct fifo_data *)data;
+ struct fifo_data *fd = (struct fifo_data *)ao;
timer_free(fd->timer);
}
static void
-fifo_output_cancel(void *data)
+fifo_output_cancel(struct audio_output *ao)
{
- struct fifo_data *fd = (struct fifo_data *)data;
+ struct fifo_data *fd = (struct fifo_data *)ao;
char buf[FIFO_BUFFER_SIZE];
int bytes = 1;
@@ -251,22 +255,29 @@ fifo_output_cancel(void *data)
if (bytes < 0 && errno != EAGAIN) {
g_warning("Flush of FIFO \"%s\" failed: %s",
- fd->path, strerror(errno));
+ fd->path, g_strerror(errno));
}
}
+static unsigned
+fifo_output_delay(struct audio_output *ao)
+{
+ struct fifo_data *fd = (struct fifo_data *)ao;
+
+ return fd->timer->started
+ ? timer_delay(fd->timer)
+ : 0;
+}
+
static size_t
-fifo_output_play(void *data, const void *chunk, size_t size,
+fifo_output_play(struct audio_output *ao, const void *chunk, size_t size,
GError **error)
{
- struct fifo_data *fd = (struct fifo_data *)data;
+ struct fifo_data *fd = (struct fifo_data *)ao;
ssize_t bytes;
if (!fd->timer->started)
timer_start(fd->timer);
- else
- timer_sync(fd->timer);
-
timer_add(fd->timer, size);
while (true) {
@@ -278,7 +289,7 @@ fifo_output_play(void *data, const void *chunk, size_t size,
switch (errno) {
case EAGAIN:
/* The pipe is full, so empty it */
- fifo_output_cancel(fd);
+ fifo_output_cancel(&fd->base);
continue;
case EINTR:
continue;
@@ -298,6 +309,7 @@ const struct audio_output_plugin fifo_output_plugin = {
.finish = fifo_output_finish,
.open = fifo_output_open,
.close = fifo_output_close,
+ .delay = fifo_output_delay,
.play = fifo_output_play,
.cancel = fifo_output_cancel,
};
diff --git a/src/output/fifo_output_plugin.h b/src/output/fifo_output_plugin.h
new file mode 100644
index 000000000..85f7985e1
--- /dev/null
+++ b/src/output/fifo_output_plugin.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_FIFO_OUTPUT_PLUGIN_H
+#define MPD_FIFO_OUTPUT_PLUGIN_H
+
+extern const struct audio_output_plugin fifo_output_plugin;
+
+#endif
diff --git a/src/output/httpd_client.c b/src/output/httpd_client.c
index 995c1f659..8efedc2b3 100644
--- a/src/output/httpd_client.c
+++ b/src/output/httpd_client.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -93,6 +93,11 @@ struct httpd_client {
*/
size_t current_position;
+ /**
+ * If DLNA streaming was an option.
+ */
+ bool dlna_streaming_requested;
+
/* ICY */
/**
@@ -234,6 +239,15 @@ httpd_client_handle_line(struct httpd_client *client, const char *line)
return true;
}
+ if (g_ascii_strncasecmp(line, "transferMode.dlna.org: Streaming", 32) == 0) {
+ /* Send as dlna */
+ client->dlna_streaming_requested = true;
+ /* metadata is not supported by dlna streaming, so disable it */
+ client->metadata_supported = false;
+ client->metadata_requested = false;
+ return true;
+ }
+
/* expect more request headers */
return true;
}
@@ -285,16 +299,21 @@ httpd_client_send_response(struct httpd_client *client)
assert(client != NULL);
assert(client->state == RESPONSE);
- if (!client->metadata_requested) {
+ if (client->dlna_streaming_requested) {
g_snprintf(buffer, sizeof(buffer),
- "HTTP/1.1 200 OK\r\n"
+ "HTTP/1.1 206 OK\r\n"
"Content-Type: %s\r\n"
+ "Content-Length: 10000\r\n"
+ "Content-RangeX: 0-1000000/1000000\r\n"
+ "transferMode.dlna.org: Streaming\r\n"
+ "Accept-Ranges: bytes\r\n"
"Connection: close\r\n"
- "Pragma: no-cache\r\n"
- "Cache-Control: no-cache, no-store\r\n"
+ "realTimeInfo.dlna.org: DLNA.ORG_TLAG=*\r\n"
+ "contentFeatures.dlna.org: DLNA.ORG_OP=01;DLNA.ORG_CI=0\r\n"
"\r\n",
client->httpd->content_type);
- } else {
+
+ } else if (client->metadata_requested) {
gchar *metadata_header;
metadata_header = icy_server_metadata_header(
@@ -307,6 +326,16 @@ httpd_client_send_response(struct httpd_client *client)
g_strlcpy(buffer, metadata_header, sizeof(buffer));
g_free(metadata_header);
+
+ } else { /* revert to a normal HTTP request */
+ g_snprintf(buffer, sizeof(buffer),
+ "HTTP/1.1 200 OK\r\n"
+ "Content-Type: %s\r\n"
+ "Connection: close\r\n"
+ "Pragma: no-cache\r\n"
+ "Cache-Control: no-cache, no-store\r\n"
+ "\r\n",
+ client->httpd->content_type);
}
status = g_io_channel_write_chars(client->channel,
@@ -476,6 +505,7 @@ httpd_client_new(struct httpd_output *httpd, int fd, bool metadata_supported)
client->input = fifo_buffer_new(4096);
client->state = REQUEST;
+ client->dlna_streaming_requested = false;
client->metadata_supported = metadata_supported;
client->metadata_requested = false;
client->metadata_sent = true;
diff --git a/src/output/httpd_client.h b/src/output/httpd_client.h
index 7ebd0bbc0..739163f42 100644
--- a/src/output/httpd_client.h
+++ b/src/output/httpd_client.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/output/httpd_internal.h b/src/output/httpd_internal.h
index 277e70f11..5dcb8ab9b 100644
--- a/src/output/httpd_internal.h
+++ b/src/output/httpd_internal.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -25,6 +25,7 @@
#ifndef MPD_OUTPUT_HTTPD_INTERNAL_H
#define MPD_OUTPUT_HTTPD_INTERNAL_H
+#include "output_internal.h"
#include "timer.h"
#include <glib.h>
@@ -34,6 +35,8 @@
struct httpd_client;
struct httpd_output {
+ struct audio_output base;
+
/**
* True if the audio output is open and accepts client
* connections.
@@ -65,10 +68,10 @@ struct httpd_output {
GMutex *mutex;
/**
- * A #Timer object to synchronize this output with the
+ * A #timer object to synchronize this output with the
* wallclock.
*/
- Timer *timer;
+ struct timer *timer;
/**
* The listener socket.
diff --git a/src/output/httpd_output_plugin.c b/src/output/httpd_output_plugin.c
index 2c140a300..e7344320c 100644
--- a/src/output/httpd_output_plugin.c
+++ b/src/output/httpd_output_plugin.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -18,12 +18,13 @@
*/
#include "config.h"
+#include "httpd_output_plugin.h"
#include "httpd_internal.h"
#include "httpd_client.h"
#include "output_api.h"
#include "encoder_plugin.h"
#include "encoder_list.h"
-#include "socket_util.h"
+#include "resolver.h"
#include "page.h"
#include "icy_server.h"
#include "fd_util.h"
@@ -78,12 +79,16 @@ httpd_output_unbind(struct httpd_output *httpd)
g_mutex_unlock(httpd->mutex);
}
-static void *
-httpd_output_init(G_GNUC_UNUSED const struct audio_format *audio_format,
- const struct config_param *param,
+static struct audio_output *
+httpd_output_init(const struct config_param *param,
GError **error)
{
struct httpd_output *httpd = g_new(struct httpd_output, 1);
+ if (!ao_base_init(&httpd->base, &httpd_output_plugin, param, error)) {
+ g_free(httpd);
+ return NULL;
+ }
+
const char *encoder_name, *bind_to_address;
const struct encoder_plugin *encoder_plugin;
guint port;
@@ -103,6 +108,7 @@ httpd_output_init(G_GNUC_UNUSED const struct audio_format *audio_format,
if (encoder_plugin == NULL) {
g_set_error(error, httpd_output_quark(), 0,
"No such encoder: %s", encoder_name);
+ ao_base_finish(&httpd->base);
g_free(httpd);
return NULL;
}
@@ -120,8 +126,11 @@ httpd_output_init(G_GNUC_UNUSED const struct audio_format *audio_format,
? server_socket_add_host(httpd->server_socket, bind_to_address,
port, error)
: server_socket_add_port(httpd->server_socket, port, error);
- if (!success)
+ if (!success) {
+ ao_base_finish(&httpd->base);
+ g_free(httpd);
return NULL;
+ }
/* initialize metadata */
httpd->metadata = NULL;
@@ -130,8 +139,11 @@ httpd_output_init(G_GNUC_UNUSED const struct audio_format *audio_format,
/* initialize encoder */
httpd->encoder = encoder_init(encoder_plugin, param, error);
- if (httpd->encoder == NULL)
+ if (httpd->encoder == NULL) {
+ ao_base_finish(&httpd->base);
+ g_free(httpd);
return NULL;
+ }
/* determine content type */
httpd->content_type = encoder_get_mime_type(httpd->encoder);
@@ -141,13 +153,13 @@ httpd_output_init(G_GNUC_UNUSED const struct audio_format *audio_format,
httpd->mutex = g_mutex_new();
- return httpd;
+ return &httpd->base;
}
static void
-httpd_output_finish(void *data)
+httpd_output_finish(struct audio_output *ao)
{
- struct httpd_output *httpd = data;
+ struct httpd_output *httpd = (struct httpd_output *)ao;
if (httpd->metadata)
page_unref(httpd->metadata);
@@ -155,6 +167,7 @@ httpd_output_finish(void *data)
encoder_finish(httpd->encoder);
server_socket_free(httpd->server_socket);
g_mutex_free(httpd->mutex);
+ ao_base_finish(&httpd->base);
g_free(httpd);
}
@@ -286,26 +299,26 @@ httpd_output_encoder_open(struct httpd_output *httpd,
}
static bool
-httpd_output_enable(void *data, GError **error_r)
+httpd_output_enable(struct audio_output *ao, GError **error_r)
{
- struct httpd_output *httpd = data;
+ struct httpd_output *httpd = (struct httpd_output *)ao;
return httpd_output_bind(httpd, error_r);
}
static void
-httpd_output_disable(void *data)
+httpd_output_disable(struct audio_output *ao)
{
- struct httpd_output *httpd = data;
+ struct httpd_output *httpd = (struct httpd_output *)ao;
httpd_output_unbind(httpd);
}
static bool
-httpd_output_open(void *data, struct audio_format *audio_format,
+httpd_output_open(struct audio_output *ao, struct audio_format *audio_format,
GError **error)
{
- struct httpd_output *httpd = data;
+ struct httpd_output *httpd = (struct httpd_output *)ao;
bool success;
g_mutex_lock(httpd->mutex);
@@ -338,9 +351,10 @@ httpd_client_delete(gpointer data, G_GNUC_UNUSED gpointer user_data)
httpd_client_free(client);
}
-static void httpd_output_close(void *data)
+static void
+httpd_output_close(struct audio_output *ao)
{
- struct httpd_output *httpd = data;
+ struct httpd_output *httpd = (struct httpd_output *)ao;
g_mutex_lock(httpd->mutex);
@@ -379,9 +393,9 @@ httpd_output_send_header(struct httpd_output *httpd,
}
static unsigned
-httpd_output_delay(void *data)
+httpd_output_delay(struct audio_output *ao)
{
- struct httpd_output *httpd = data;
+ struct httpd_output *httpd = (struct httpd_output *)ao;
return httpd->timer->started
? timer_delay(httpd->timer)
@@ -457,9 +471,10 @@ httpd_output_encode_and_play(struct httpd_output *httpd,
}
static size_t
-httpd_output_play(void *data, const void *chunk, size_t size, GError **error)
+httpd_output_play(struct audio_output *ao, const void *chunk, size_t size,
+ GError **error)
{
- struct httpd_output *httpd = data;
+ struct httpd_output *httpd = (struct httpd_output *)ao;
bool has_clients;
g_mutex_lock(httpd->mutex);
@@ -483,9 +498,9 @@ httpd_output_play(void *data, const void *chunk, size_t size, GError **error)
}
static bool
-httpd_output_pause(void *data)
+httpd_output_pause(struct audio_output *ao)
{
- struct httpd_output *httpd = data;
+ struct httpd_output *httpd = (struct httpd_output *)ao;
g_mutex_lock(httpd->mutex);
bool has_clients = httpd->clients != NULL;
@@ -493,7 +508,7 @@ httpd_output_pause(void *data)
if (has_clients) {
static const char silence[1020];
- return httpd_output_play(data, silence, sizeof(silence),
+ return httpd_output_play(ao, silence, sizeof(silence),
NULL) > 0;
} else {
g_usleep(100000);
@@ -511,9 +526,9 @@ httpd_send_metadata(gpointer data, gpointer user_data)
}
static void
-httpd_output_tag(void *data, const struct tag *tag)
+httpd_output_tag(struct audio_output *ao, const struct tag *tag)
{
- struct httpd_output *httpd = data;
+ struct httpd_output *httpd = (struct httpd_output *)ao;
assert(tag != NULL);
@@ -570,9 +585,9 @@ httpd_client_cancel_callback(gpointer data, G_GNUC_UNUSED gpointer user_data)
}
static void
-httpd_output_cancel(void *data)
+httpd_output_cancel(struct audio_output *ao)
{
- struct httpd_output *httpd = data;
+ struct httpd_output *httpd = (struct httpd_output *)ao;
g_mutex_lock(httpd->mutex);
g_list_foreach(httpd->clients, httpd_client_cancel_callback, NULL);
diff --git a/src/output/httpd_output_plugin.h b/src/output/httpd_output_plugin.h
new file mode 100644
index 000000000..d0eb1533f
--- /dev/null
+++ b/src/output/httpd_output_plugin.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_HTTPD_OUTPUT_PLUGIN_H
+#define MPD_HTTPD_OUTPUT_PLUGIN_H
+
+extern const struct audio_output_plugin httpd_output_plugin;
+
+#endif
diff --git a/src/output/jack_output_plugin.c b/src/output/jack_output_plugin.c
index c67fcd38a..a24cb8557 100644
--- a/src/output/jack_output_plugin.c
+++ b/src/output/jack_output_plugin.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -18,6 +18,7 @@
*/
#include "config.h"
+#include "jack_output_plugin.h"
#include "output_api.h"
#include <assert.h>
@@ -43,6 +44,8 @@ enum {
static const size_t jack_sample_size = sizeof(jack_default_audio_sample_t);
struct jack_data {
+ struct audio_output base;
+
/**
* libjack options passed to jack_client_open().
*/
@@ -304,14 +307,18 @@ parse_port_list(int line, const char *source, char **dest, GError **error_r)
return n;
}
-static void *
-mpd_jack_init(G_GNUC_UNUSED const struct audio_format *audio_format,
- const struct config_param *param, GError **error_r)
+static struct audio_output *
+mpd_jack_init(const struct config_param *param, GError **error_r)
{
- struct jack_data *jd;
+ struct jack_data *jd = g_new(struct jack_data, 1);
+
+ if (!ao_base_init(&jd->base, &jack_output_plugin, param, error_r)) {
+ g_free(jd);
+ return NULL;
+ }
+
const char *value;
- jd = g_new(struct jack_data, 1);
jd->options = JackNullOption;
jd->name = config_get_block_string(param, "client_name", NULL);
@@ -374,13 +381,13 @@ mpd_jack_init(G_GNUC_UNUSED const struct audio_format *audio_format,
jack_set_info_function(mpd_jack_info);
#endif
- return jd;
+ return &jd->base;
}
static void
-mpd_jack_finish(void *data)
+mpd_jack_finish(struct audio_output *ao)
{
- struct jack_data *jd = data;
+ struct jack_data *jd = (struct jack_data *)ao;
for (unsigned i = 0; i < jd->num_source_ports; ++i)
g_free(jd->source_ports[i]);
@@ -388,13 +395,14 @@ mpd_jack_finish(void *data)
for (unsigned i = 0; i < jd->num_destination_ports; ++i)
g_free(jd->destination_ports[i]);
+ ao_base_finish(&jd->base);
g_free(jd);
}
static bool
-mpd_jack_enable(void *data, GError **error_r)
+mpd_jack_enable(struct audio_output *ao, GError **error_r)
{
- struct jack_data *jd = (struct jack_data *)data;
+ struct jack_data *jd = (struct jack_data *)ao;
for (unsigned i = 0; i < jd->num_source_ports; ++i)
jd->ringbuffer[i] = NULL;
@@ -403,9 +411,9 @@ mpd_jack_enable(void *data, GError **error_r)
}
static void
-mpd_jack_disable(void *data)
+mpd_jack_disable(struct audio_output *ao)
{
- struct jack_data *jd = (struct jack_data *)data;
+ struct jack_data *jd = (struct jack_data *)ao;
if (jd->client != NULL)
mpd_jack_disconnect(jd);
@@ -568,9 +576,10 @@ mpd_jack_start(struct jack_data *jd, GError **error_r)
}
static bool
-mpd_jack_open(void *data, struct audio_format *audio_format, GError **error_r)
+mpd_jack_open(struct audio_output *ao, struct audio_format *audio_format,
+ GError **error_r)
{
- struct jack_data *jd = data;
+ struct jack_data *jd = (struct jack_data *)ao;
assert(jd != NULL);
@@ -592,9 +601,9 @@ mpd_jack_open(void *data, struct audio_format *audio_format, GError **error_r)
}
static void
-mpd_jack_close(G_GNUC_UNUSED void *data)
+mpd_jack_close(G_GNUC_UNUSED struct audio_output *ao)
{
- struct jack_data *jd = data;
+ struct jack_data *jd = (struct jack_data *)ao;
mpd_jack_stop(jd);
}
@@ -664,9 +673,10 @@ mpd_jack_write_samples(struct jack_data *jd, const void *src,
}
static size_t
-mpd_jack_play(void *data, const void *chunk, size_t size, GError **error_r)
+mpd_jack_play(struct audio_output *ao, const void *chunk, size_t size,
+ GError **error_r)
{
- struct jack_data *jd = data;
+ struct jack_data *jd = (struct jack_data *)ao;
const size_t frame_size = audio_format_frame_size(&jd->audio_format);
size_t space = 0, space1;
@@ -708,9 +718,9 @@ mpd_jack_play(void *data, const void *chunk, size_t size, GError **error_r)
}
static bool
-mpd_jack_pause(void *data)
+mpd_jack_pause(struct audio_output *ao)
{
- struct jack_data *jd = data;
+ struct jack_data *jd = (struct jack_data *)ao;
if (jd->shutdown)
return false;
diff --git a/src/output/jack_output_plugin.h b/src/output/jack_output_plugin.h
new file mode 100644
index 000000000..2f94ae7dc
--- /dev/null
+++ b/src/output/jack_output_plugin.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_JACK_OUTPUT_PLUGIN_H
+#define MPD_JACK_OUTPUT_PLUGIN_H
+
+extern const struct audio_output_plugin jack_output_plugin;
+
+#endif
diff --git a/src/output/mvp_plugin.c b/src/output/mvp_output_plugin.c
index 6cc8fa34e..37e0f7c93 100644
--- a/src/output/mvp_plugin.c
+++ b/src/output/mvp_output_plugin.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -23,6 +23,7 @@
*/
#include "config.h"
+#include "mvp_output_plugin.h"
#include "output_api.h"
#include "fd_util.h"
@@ -69,6 +70,8 @@ typedef struct {
#define MVP_GET_AUD_REGS _IOW('a',28,aud_ctl_regs_t*)
struct mvp_data {
+ struct audio_output base;
+
struct audio_format audio_format;
int fd;
};
@@ -125,26 +128,31 @@ mvp_output_test_default_device(void)
}
g_warning("Error opening PCM device \"/dev/adec_pcm\": %s\n",
- strerror(errno));
+ g_strerror(errno));
return false;
}
-static void *
-mvp_output_init(G_GNUC_UNUSED const struct audio_format *audio_format,
- G_GNUC_UNUSED const struct config_param *param,
- G_GNUC_UNUSED GError **error)
+static struct audio_output *
+mvp_output_init(G_GNUC_UNUSED const struct config_param *param, GError **error)
{
struct mvp_data *md = g_new(struct mvp_data, 1);
+
+ if (!ao_base_init(&md->base, &mvp_output_plugin, param, error)) {
+ g_free(md);
+ return NULL;
+ }
+
md->fd = -1;
- return md;
+ return &md->base;
}
static void
-mvp_output_finish(void *data)
+mvp_output_finish(struct audio_output *ao)
{
- struct mvp_data *md = data;
+ struct mvp_data *md = (struct mvp_data *)ao;
+ ao_base_finish(&md->base);
g_free(md);
}
@@ -225,9 +233,10 @@ mvp_set_pcm_params(struct mvp_data *md, struct audio_format *audio_format,
}
static bool
-mvp_output_open(void *data, struct audio_format *audio_format, GError **error)
+mvp_output_open(struct audio_output *ao, struct audio_format *audio_format,
+ GError **error)
{
- struct mvp_data *md = data;
+ struct mvp_data *md = (struct mvp_data *)ao;
long long int stc = 0;
int mix[5] = { 0, 2, 7, 1, 0 };
bool success;
@@ -236,32 +245,32 @@ mvp_output_open(void *data, struct audio_format *audio_format, GError **error)
if (md->fd < 0) {
g_set_error(error, mvp_output_quark(), errno,
"Error opening /dev/adec_pcm: %s",
- strerror(errno));
+ g_strerror(errno));
return false;
}
if (ioctl(md->fd, MVP_SET_AUD_SRC, 1) < 0) {
g_set_error(error, mvp_output_quark(), errno,
"Error setting audio source: %s",
- strerror(errno));
+ g_strerror(errno));
return false;
}
if (ioctl(md->fd, MVP_SET_AUD_STREAMTYPE, 0) < 0) {
g_set_error(error, mvp_output_quark(), errno,
"Error setting audio streamtype: %s",
- strerror(errno));
+ g_strerror(errno));
return false;
}
if (ioctl(md->fd, MVP_SET_AUD_FORMAT, &mix) < 0) {
g_set_error(error, mvp_output_quark(), errno,
"Error setting audio format: %s",
- strerror(errno));
+ g_strerror(errno));
return false;
}
ioctl(md->fd, MVP_SET_AUD_STC, &stc);
if (ioctl(md->fd, MVP_SET_AUD_BYPASS, 1) < 0) {
g_set_error(error, mvp_output_quark(), errno,
"Error setting audio streamtype: %s",
- strerror(errno));
+ g_strerror(errno));
return false;
}
@@ -273,17 +282,17 @@ mvp_output_open(void *data, struct audio_format *audio_format, GError **error)
return true;
}
-static void mvp_output_close(void *data)
+static void mvp_output_close(struct audio_output *ao)
{
- struct mvp_data *md = data;
+ struct mvp_data *md = (struct mvp_data *)ao;
if (md->fd >= 0)
close(md->fd);
md->fd = -1;
}
-static void mvp_output_cancel(void *data)
+static void mvp_output_cancel(struct audio_output *ao)
{
- struct mvp_data *md = data;
+ struct mvp_data *md = (struct mvp_data *)ao;
if (md->fd >= 0) {
ioctl(md->fd, MVP_SET_AUD_RESET, 0x11);
close(md->fd);
@@ -292,16 +301,17 @@ static void mvp_output_cancel(void *data)
}
static size_t
-mvp_output_play(void *data, const void *chunk, size_t size, GError **error)
+mvp_output_play(struct audio_output *ao, const void *chunk, size_t size,
+ GError **error)
{
- struct mvp_data *md = data;
+ struct mvp_data *md = (struct mvp_data *)ao;
ssize_t ret;
/* reopen the device since it was closed by dropBufferedAudio */
if (md->fd < 0) {
bool success;
- success = mvp_output_open(md, &md->audio_format, error);
+ success = mvp_output_open(ao, &md->audio_format, error);
if (!success)
return 0;
}
@@ -316,7 +326,7 @@ mvp_output_play(void *data, const void *chunk, size_t size, GError **error)
continue;
g_set_error(error, mvp_output_quark(), errno,
- "Failed to write: %s", strerror(errno));
+ "Failed to write: %s", g_strerror(errno));
return 0;
}
}
diff --git a/src/output/mvp_output_plugin.h b/src/output/mvp_output_plugin.h
new file mode 100644
index 000000000..e403de2b7
--- /dev/null
+++ b/src/output/mvp_output_plugin.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_MVP_OUTPUT_PLUGIN_H
+#define MPD_MVP_OUTPUT_PLUGIN_H
+
+extern const struct audio_output_plugin mvp_output_plugin;
+
+#endif
diff --git a/src/output/null_plugin.c b/src/output/null_output_plugin.c
index 89abbd91f..9d7588fff 100644
--- a/src/output/null_plugin.c
+++ b/src/output/null_output_plugin.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -18,6 +18,7 @@
*/
#include "config.h"
+#include "null_output_plugin.h"
#include "output_api.h"
#include "timer.h"
@@ -26,39 +27,42 @@
#include <assert.h>
struct null_data {
+ struct audio_output base;
+
bool sync;
- Timer *timer;
+ struct timer *timer;
};
-static void *
-null_init(G_GNUC_UNUSED const struct audio_format *audio_format,
- G_GNUC_UNUSED const struct config_param *param,
- G_GNUC_UNUSED GError **error)
+static struct audio_output *
+null_init(const struct config_param *param, GError **error_r)
{
struct null_data *nd = g_new(struct null_data, 1);
+ if (!ao_base_init(&nd->base, &null_output_plugin, param, error_r)) {
+ g_free(nd);
+ return NULL;
+ }
+
nd->sync = config_get_block_bool(param, "sync", true);
- nd->timer = NULL;
- return nd;
+ return &nd->base;
}
static void
-null_finish(void *data)
+null_finish(struct audio_output *ao)
{
- struct null_data *nd = data;
-
- assert(nd->timer == NULL);
+ struct null_data *nd = (struct null_data *)ao;
+ ao_base_finish(&nd->base);
g_free(nd);
}
static bool
-null_open(void *data, struct audio_format *audio_format,
+null_open(struct audio_output *ao, struct audio_format *audio_format,
G_GNUC_UNUSED GError **error)
{
- struct null_data *nd = data;
+ struct null_data *nd = (struct null_data *)ao;
if (nd->sync)
nd->timer = timer_new(audio_format);
@@ -67,40 +71,45 @@ null_open(void *data, struct audio_format *audio_format,
}
static void
-null_close(void *data)
+null_close(struct audio_output *ao)
{
- struct null_data *nd = data;
+ struct null_data *nd = (struct null_data *)ao;
- if (nd->timer != NULL) {
+ if (nd->sync)
timer_free(nd->timer);
- nd->timer = NULL;
- }
+}
+
+static unsigned
+null_delay(struct audio_output *ao)
+{
+ struct null_data *nd = (struct null_data *)ao;
+
+ return nd->sync && nd->timer->started
+ ? timer_delay(nd->timer)
+ : 0;
}
static size_t
-null_play(void *data, G_GNUC_UNUSED const void *chunk, size_t size,
+null_play(struct audio_output *ao, G_GNUC_UNUSED const void *chunk, size_t size,
G_GNUC_UNUSED GError **error)
{
- struct null_data *nd = data;
- Timer *timer = nd->timer;
+ struct null_data *nd = (struct null_data *)ao;
+ struct timer *timer = nd->timer;
if (!nd->sync)
return size;
if (!timer->started)
timer_start(timer);
- else
- timer_sync(timer);
-
timer_add(timer, size);
return size;
}
static void
-null_cancel(void *data)
+null_cancel(struct audio_output *ao)
{
- struct null_data *nd = data;
+ struct null_data *nd = (struct null_data *)ao;
if (!nd->sync)
return;
@@ -114,6 +123,7 @@ const struct audio_output_plugin null_output_plugin = {
.finish = null_finish,
.open = null_open,
.close = null_close,
+ .delay = null_delay,
.play = null_play,
.cancel = null_cancel,
};
diff --git a/src/output/null_output_plugin.h b/src/output/null_output_plugin.h
new file mode 100644
index 000000000..392bf0aa3
--- /dev/null
+++ b/src/output/null_output_plugin.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_NULL_OUTPUT_PLUGIN_H
+#define MPD_NULL_OUTPUT_PLUGIN_H
+
+extern const struct audio_output_plugin null_output_plugin;
+
+#endif
diff --git a/src/output/openal_plugin.c b/src/output/openal_output_plugin.c
index e5db8ac34..ebd35ef12 100644
--- a/src/output/openal_plugin.c
+++ b/src/output/openal_output_plugin.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -18,8 +18,8 @@
*/
#include "config.h"
+#include "openal_output_plugin.h"
#include "output_api.h"
-#include "timer.h"
#include <glib.h>
@@ -38,12 +38,13 @@
#define NUM_BUFFERS 16
struct openal_data {
+ struct audio_output base;
+
const char *device_name;
ALCdevice *device;
ALCcontext *context;
- Timer *timer;
ALuint buffers[NUM_BUFFERS];
- int filled;
+ unsigned filled;
ALuint source;
ALenum format;
ALuint frequency;
@@ -80,6 +81,29 @@ openal_audio_format(struct audio_format *audio_format)
}
}
+G_GNUC_PURE
+static inline ALint
+openal_get_source_i(const struct openal_data *od, ALenum param)
+{
+ ALint value;
+ alGetSourcei(od->source, param, &value);
+ return value;
+}
+
+G_GNUC_PURE
+static inline bool
+openal_has_processed(const struct openal_data *od)
+{
+ return openal_get_source_i(od, AL_BUFFERS_PROCESSED) > 0;
+}
+
+G_GNUC_PURE
+static inline ALint
+openal_is_playing(const struct openal_data *od)
+{
+ return openal_get_source_i(od, AL_SOURCE_STATE) == AL_PLAYING;
+}
+
static bool
openal_setup_context(struct openal_data *od,
GError **error)
@@ -106,23 +130,8 @@ openal_setup_context(struct openal_data *od,
return true;
}
-static void
-openal_unqueue_buffers(struct openal_data *od)
-{
- ALint num;
- ALuint buffer;
-
- alGetSourcei(od->source, AL_BUFFERS_QUEUED, &num);
-
- while (num--) {
- alSourceUnqueueBuffers(od->source, 1, &buffer);
- }
-}
-
-static void *
-openal_init(G_GNUC_UNUSED const struct audio_format *audio_format,
- const struct config_param *param,
- G_GNUC_UNUSED GError **error)
+static struct audio_output *
+openal_init(const struct config_param *param, GError **error_r)
{
const char *device_name = config_get_block_string(param, "device", NULL);
struct openal_data *od;
@@ -132,35 +141,33 @@ openal_init(G_GNUC_UNUSED const struct audio_format *audio_format,
}
od = g_new(struct openal_data, 1);
+ if (!ao_base_init(&od->base, &openal_output_plugin, param, error_r)) {
+ g_free(od);
+ return NULL;
+ }
+
od->device_name = device_name;
- return od;
+ return &od->base;
}
static void
-openal_finish(void *data)
+openal_finish(struct audio_output *ao)
{
- struct openal_data *od = data;
+ struct openal_data *od = (struct openal_data *)ao;
+ ao_base_finish(&od->base);
g_free(od);
}
static bool
-openal_open(void *data, struct audio_format *audio_format,
+openal_open(struct audio_output *ao, struct audio_format *audio_format,
GError **error)
{
- struct openal_data *od = data;
+ struct openal_data *od = (struct openal_data *)ao;
od->format = openal_audio_format(audio_format);
- if (!od->format) {
- struct audio_format_string s;
- g_set_error(error, openal_output_quark(), 0,
- "Unsupported audio format: %s",
- audio_format_to_string(audio_format, &s));
- return false;
- }
-
if (!openal_setup_context(od, error)) {
return false;
}
@@ -184,18 +191,16 @@ openal_open(void *data, struct audio_format *audio_format,
}
od->filled = 0;
- od->timer = timer_new(audio_format);
od->frequency = audio_format->sample_rate;
return true;
}
static void
-openal_close(void *data)
+openal_close(struct audio_output *ao)
{
- struct openal_data *od = data;
+ struct openal_data *od = (struct openal_data *)ao;
- timer_free(od->timer);
alcMakeContextCurrent(od->context);
alDeleteSources(1, &od->source);
alDeleteBuffers(NUM_BUFFERS, od->buffers);
@@ -203,61 +208,63 @@ openal_close(void *data)
alcCloseDevice(od->device);
}
+static unsigned
+openal_delay(struct audio_output *ao)
+{
+ struct openal_data *od = (struct openal_data *)ao;
+
+ return od->filled < NUM_BUFFERS || openal_has_processed(od)
+ ? 0
+ /* we don't know exactly how long we must wait for the
+ next buffer to finish, so this is a random
+ guess: */
+ : 50;
+}
+
static size_t
-openal_play(void *data, const void *chunk, size_t size,
+openal_play(struct audio_output *ao, const void *chunk, size_t size,
G_GNUC_UNUSED GError **error)
{
- struct openal_data *od = data;
+ struct openal_data *od = (struct openal_data *)ao;
ALuint buffer;
- ALint num, state;
if (alcGetCurrentContext() != od->context) {
alcMakeContextCurrent(od->context);
}
- alGetSourcei(od->source, AL_BUFFERS_PROCESSED, &num);
-
if (od->filled < NUM_BUFFERS) {
/* fill all buffers */
buffer = od->buffers[od->filled];
od->filled++;
} else {
/* wait for processed buffer */
- while (num < 1) {
- if (!od->timer->started) {
- timer_start(od->timer);
- } else {
- timer_sync(od->timer);
- }
-
- timer_add(od->timer, size);
-
- alGetSourcei(od->source, AL_BUFFERS_PROCESSED, &num);
- }
+ while (!openal_has_processed(od))
+ g_usleep(10);
alSourceUnqueueBuffers(od->source, 1, &buffer);
}
alBufferData(buffer, od->format, chunk, size, od->frequency);
alSourceQueueBuffers(od->source, 1, &buffer);
- alGetSourcei(od->source, AL_SOURCE_STATE, &state);
- if (state != AL_PLAYING) {
+ if (!openal_is_playing(od))
alSourcePlay(od->source);
- }
return size;
}
static void
-openal_cancel(void *data)
+openal_cancel(struct audio_output *ao)
{
- struct openal_data *od = data;
+ struct openal_data *od = (struct openal_data *)ao;
od->filled = 0;
alcMakeContextCurrent(od->context);
alSourceStop(od->source);
- openal_unqueue_buffers(od);
+
+ /* force-unqueue all buffers */
+ alSourcei(od->source, AL_BUFFER, 0);
+ od->filled = 0;
}
const struct audio_output_plugin openal_output_plugin = {
@@ -266,6 +273,7 @@ const struct audio_output_plugin openal_output_plugin = {
.finish = openal_finish,
.open = openal_open,
.close = openal_close,
+ .delay = openal_delay,
.play = openal_play,
.cancel = openal_cancel,
};
diff --git a/src/output/openal_output_plugin.h b/src/output/openal_output_plugin.h
new file mode 100644
index 000000000..25f6ccf46
--- /dev/null
+++ b/src/output/openal_output_plugin.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_OPENAL_OUTPUT_PLUGIN_H
+#define MPD_OPENAL_OUTPUT_PLUGIN_H
+
+extern const struct audio_output_plugin openal_output_plugin;
+
+#endif
diff --git a/src/output/oss_plugin.c b/src/output/oss_output_plugin.c
index 9261b423c..e366a4537 100644
--- a/src/output/oss_plugin.c
+++ b/src/output/oss_output_plugin.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -18,9 +18,11 @@
*/
#include "config.h"
+#include "oss_output_plugin.h"
#include "output_api.h"
#include "mixer_list.h"
#include "fd_util.h"
+#include "glib_compat.h"
#include <glib.h>
@@ -50,7 +52,17 @@
#undef AFMT_S24_NE
#endif
+#ifdef AFMT_S24_PACKED
+#include "pcm_export.h"
+#endif
+
struct oss_data {
+ struct audio_output base;
+
+#ifdef AFMT_S24_PACKED
+ struct pcm_export_state export;
+#endif
+
int fd;
const char *device;
@@ -59,6 +71,12 @@ struct oss_data {
* the device after cancel().
*/
struct audio_format audio_format;
+
+ /**
+ * The current OSS audio format. This is needed to reopen the
+ * device after cancel().
+ */
+ int oss_format;
};
/**
@@ -136,13 +154,13 @@ oss_output_test_default_device(void)
return true;
}
g_warning("Error opening OSS device \"%s\": %s\n",
- default_devices[i], strerror(errno));
+ default_devices[i], g_strerror(errno));
}
return false;
}
-static void *
+static struct audio_output *
oss_open_default(GError **error)
{
int i;
@@ -153,8 +171,14 @@ oss_open_default(GError **error)
ret[i] = oss_stat_device(default_devices[i], &err[i]);
if (ret[i] == OSS_STAT_NO_ERROR) {
struct oss_data *od = oss_data_new();
+ if (!ao_base_init(&od->base, &oss_output_plugin, NULL,
+ error)) {
+ g_free(od);
+ return NULL;
+ }
+
od->device = default_devices[i];
- return od;
+ return &od->base;
}
}
@@ -175,7 +199,7 @@ oss_open_default(GError **error)
break;
case OSS_STAT_OTHER:
g_warning("Error accessing %s: %s\n",
- dev, strerror(err[i]));
+ dev, g_strerror(err[i]));
}
}
@@ -184,29 +208,55 @@ oss_open_default(GError **error)
return NULL;
}
-static void *
-oss_output_init(G_GNUC_UNUSED const struct audio_format *audio_format,
- const struct config_param *param,
- GError **error)
+static struct audio_output *
+oss_output_init(const struct config_param *param, GError **error)
{
const char *device = config_get_block_string(param, "device", NULL);
if (device != NULL) {
struct oss_data *od = oss_data_new();
+ if (!ao_base_init(&od->base, &oss_output_plugin, param,
+ error)) {
+ g_free(od);
+ return NULL;
+ }
+
od->device = device;
- return od;
+ return &od->base;
}
return oss_open_default(error);
}
static void
-oss_output_finish(void *data)
+oss_output_finish(struct audio_output *ao)
{
- struct oss_data *od = data;
+ struct oss_data *od = (struct oss_data *)ao;
+ ao_base_finish(&od->base);
oss_data_free(od);
}
+#ifdef AFMT_S24_PACKED
+
+static bool
+oss_output_enable(struct audio_output *ao, G_GNUC_UNUSED GError **error_r)
+{
+ struct oss_data *od = (struct oss_data *)ao;
+
+ pcm_export_init(&od->export);
+ return true;
+}
+
+static void
+oss_output_disable(struct audio_output *ao)
+{
+ struct oss_data *od = (struct oss_data *)ao;
+
+ pcm_export_deinit(&od->export);
+}
+
+#endif
+
static void
oss_close(struct oss_data *od)
{
@@ -381,6 +431,8 @@ sample_format_to_oss(enum sample_format format)
{
switch (format) {
case SAMPLE_FORMAT_UNDEFINED:
+ case SAMPLE_FORMAT_FLOAT:
+ case SAMPLE_FORMAT_DSD:
return AFMT_QUERY;
case SAMPLE_FORMAT_S8:
@@ -389,13 +441,6 @@ sample_format_to_oss(enum sample_format format)
case SAMPLE_FORMAT_S16:
return AFMT_S16_NE;
- case SAMPLE_FORMAT_S24:
-#ifdef AFMT_S24_PACKED
- return AFMT_S24_PACKED;
-#else
- return AFMT_QUERY;
-#endif
-
case SAMPLE_FORMAT_S24_P32:
#ifdef AFMT_S24_NE
return AFMT_S24_NE;
@@ -430,7 +475,7 @@ sample_format_from_oss(int format)
#ifdef AFMT_S24_PACKED
case AFMT_S24_PACKED:
- return SAMPLE_FORMAT_S24;
+ return SAMPLE_FORMAT_S24_P32;
#endif
#ifdef AFMT_S24_NE
@@ -449,33 +494,83 @@ sample_format_from_oss(int format)
}
/**
+ * Probe one sample format.
+ *
+ * @return the selected sample format or SAMPLE_FORMAT_UNDEFINED on
+ * error
+ */
+static enum oss_setup_result
+oss_probe_sample_format(int fd, enum sample_format sample_format,
+ enum sample_format *sample_format_r,
+ int *oss_format_r,
+#ifdef AFMT_S24_PACKED
+ struct pcm_export_state *export,
+#endif
+ GError **error_r)
+{
+ int oss_format = sample_format_to_oss(sample_format);
+ if (oss_format == AFMT_QUERY)
+ return UNSUPPORTED;
+
+ enum oss_setup_result result =
+ oss_try_ioctl_r(fd, SNDCTL_DSP_SAMPLESIZE,
+ &oss_format,
+ "Failed to set sample format", error_r);
+
+#ifdef AFMT_S24_PACKED
+ if (result == UNSUPPORTED && sample_format == SAMPLE_FORMAT_S24_P32) {
+ /* if the driver doesn't support padded 24 bit, try
+ packed 24 bit */
+ oss_format = AFMT_S24_PACKED;
+ result = oss_try_ioctl_r(fd, SNDCTL_DSP_SAMPLESIZE,
+ &oss_format,
+ "Failed to set sample format", error_r);
+ }
+#endif
+
+ if (result != SUCCESS)
+ return result;
+
+ sample_format = sample_format_from_oss(oss_format);
+ if (sample_format == SAMPLE_FORMAT_UNDEFINED)
+ return UNSUPPORTED;
+
+ *sample_format_r = sample_format;
+ *oss_format_r = oss_format;
+
+#ifdef AFMT_S24_PACKED
+ pcm_export_open(export, sample_format, 0, false, false,
+ oss_format == AFMT_S24_PACKED,
+ oss_format == AFMT_S24_PACKED &&
+ G_BYTE_ORDER != G_LITTLE_ENDIAN);
+#endif
+
+ return SUCCESS;
+}
+
+/**
* Set up the sample format, and attempts to find alternatives if the
* specified format is not supported.
*/
static bool
oss_setup_sample_format(int fd, struct audio_format *audio_format,
+ int *oss_format_r,
+#ifdef AFMT_S24_PACKED
+ struct pcm_export_state *export,
+#endif
GError **error_r)
{
- const char *const msg = "Failed to set sample format";
- int oss_format = sample_format_to_oss(audio_format->format);
- enum oss_setup_result result = oss_format != AFMT_QUERY
- ? oss_try_ioctl_r(fd, SNDCTL_DSP_SAMPLESIZE,
- &oss_format, msg, error_r)
- : UNSUPPORTED;
enum sample_format mpd_format;
+ enum oss_setup_result result =
+ oss_probe_sample_format(fd, audio_format->format,
+ &mpd_format, oss_format_r,
+#ifdef AFMT_S24_PACKED
+ export,
+#endif
+ error_r);
switch (result) {
case SUCCESS:
- mpd_format = sample_format_from_oss(oss_format);
- if (mpd_format == SAMPLE_FORMAT_UNDEFINED)
- break;
-
audio_format->format = mpd_format;
-
-#ifdef AFMT_S24_PACKED
- if (oss_format == AFMT_S24_PACKED)
- audio_format->reverse_endian =
- G_BYTE_ORDER != G_LITTLE_ENDIAN;
-#endif
return true;
case ERROR:
@@ -485,13 +580,15 @@ oss_setup_sample_format(int fd, struct audio_format *audio_format,
break;
}
+ if (result != UNSUPPORTED)
+ return result == SUCCESS;
+
/* the requested sample format is not available - probe for
other formats supported by MPD */
static const enum sample_format sample_formats[] = {
SAMPLE_FORMAT_S24_P32,
SAMPLE_FORMAT_S32,
- SAMPLE_FORMAT_S24,
SAMPLE_FORMAT_S16,
SAMPLE_FORMAT_S8,
SAMPLE_FORMAT_UNDEFINED /* sentinel */
@@ -503,26 +600,15 @@ oss_setup_sample_format(int fd, struct audio_format *audio_format,
/* don't try that again */
continue;
- oss_format = sample_format_to_oss(mpd_format);
- if (oss_format == AFMT_QUERY)
- /* not supported by this OSS version */
- continue;
-
- result = oss_try_ioctl_r(fd, SNDCTL_DSP_SAMPLESIZE,
- &oss_format, msg, error_r);
+ result = oss_probe_sample_format(fd, mpd_format,
+ &mpd_format, oss_format_r,
+#ifdef AFMT_S24_PACKED
+ export,
+#endif
+ error_r);
switch (result) {
case SUCCESS:
- mpd_format = sample_format_from_oss(oss_format);
- if (mpd_format == SAMPLE_FORMAT_UNDEFINED)
- break;
-
audio_format->format = mpd_format;
-
-#ifdef AFMT_S24_PACKED
- if (oss_format == AFMT_S24_PACKED)
- audio_format->reverse_endian =
- G_BYTE_ORDER != G_LITTLE_ENDIAN;
-#endif
return true;
case ERROR:
@@ -533,7 +619,8 @@ oss_setup_sample_format(int fd, struct audio_format *audio_format,
}
}
- g_set_error(error_r, oss_output_quark(), EINVAL, "%s", msg);
+ g_set_error_literal(error_r, oss_output_quark(), EINVAL,
+ "Failed to set sample format");
return false;
}
@@ -546,7 +633,11 @@ oss_setup(struct oss_data *od, struct audio_format *audio_format,
{
return oss_setup_channels(od->fd, audio_format, error_r) &&
oss_setup_sample_rate(od->fd, audio_format, error_r) &&
- oss_setup_sample_format(od->fd, audio_format, error_r);
+ oss_setup_sample_format(od->fd, audio_format, &od->oss_format,
+#ifdef AFMT_S24_PACKED
+ &od->export,
+#endif
+ error_r);
}
/**
@@ -561,7 +652,7 @@ oss_reopen(struct oss_data *od, GError **error_r)
if (od->fd < 0) {
g_set_error(error_r, oss_output_quark(), errno,
"Error opening OSS device \"%s\": %s",
- od->device, strerror(errno));
+ od->device, g_strerror(errno));
return false;
}
@@ -590,9 +681,8 @@ oss_reopen(struct oss_data *od, GError **error_r)
}
const char *const msg3 = "Failed to set sample format";
- assert(sample_format_to_oss(od->audio_format.format) != AFMT_QUERY);
result = oss_try_ioctl(od->fd, SNDCTL_DSP_SAMPLESIZE,
- sample_format_to_oss(od->audio_format.format),
+ od->oss_format,
msg3, error_r);
if (result != SUCCESS) {
oss_close(od);
@@ -606,15 +696,16 @@ oss_reopen(struct oss_data *od, GError **error_r)
}
static bool
-oss_output_open(void *data, struct audio_format *audio_format, GError **error)
+oss_output_open(struct audio_output *ao, struct audio_format *audio_format,
+ GError **error)
{
- struct oss_data *od = data;
+ struct oss_data *od = (struct oss_data *)ao;
od->fd = open_cloexec(od->device, O_WRONLY, 0);
if (od->fd < 0) {
g_set_error(error, oss_output_quark(), errno,
"Error opening OSS device \"%s\": %s",
- od->device, strerror(errno));
+ od->device, g_strerror(errno));
return false;
}
@@ -628,17 +719,17 @@ oss_output_open(void *data, struct audio_format *audio_format, GError **error)
}
static void
-oss_output_close(void *data)
+oss_output_close(struct audio_output *ao)
{
- struct oss_data *od = data;
+ struct oss_data *od = (struct oss_data *)ao;
oss_close(od);
}
static void
-oss_output_cancel(void *data)
+oss_output_cancel(struct audio_output *ao)
{
- struct oss_data *od = data;
+ struct oss_data *od = (struct oss_data *)ao;
if (od->fd >= 0) {
ioctl(od->fd, SNDCTL_DSP_RESET, 0);
@@ -647,24 +738,33 @@ oss_output_cancel(void *data)
}
static size_t
-oss_output_play(void *data, const void *chunk, size_t size, GError **error)
+oss_output_play(struct audio_output *ao, const void *chunk, size_t size,
+ GError **error)
{
- struct oss_data *od = data;
+ struct oss_data *od = (struct oss_data *)ao;
ssize_t ret;
/* reopen the device since it was closed by dropBufferedAudio */
if (od->fd < 0 && !oss_reopen(od, error))
return 0;
+#ifdef AFMT_S24_PACKED
+ chunk = pcm_export(&od->export, chunk, size, &size);
+#endif
+
while (true) {
ret = write(od->fd, chunk, size);
- if (ret > 0)
- return (size_t)ret;
+ if (ret > 0) {
+#ifdef AFMT_S24_PACKED
+ ret = pcm_export_source_size(&od->export, ret);
+#endif
+ return ret;
+ }
if (ret < 0 && errno != EINTR) {
g_set_error(error, oss_output_quark(), errno,
"Write error on %s: %s",
- od->device, strerror(errno));
+ od->device, g_strerror(errno));
return 0;
}
}
@@ -675,6 +775,10 @@ const struct audio_output_plugin oss_output_plugin = {
.test_default_device = oss_output_test_default_device,
.init = oss_output_init,
.finish = oss_output_finish,
+#ifdef AFMT_S24_PACKED
+ .enable = oss_output_enable,
+ .disable = oss_output_disable,
+#endif
.open = oss_output_open,
.close = oss_output_close,
.play = oss_output_play,
diff --git a/src/output/oss_output_plugin.h b/src/output/oss_output_plugin.h
new file mode 100644
index 000000000..2aecc2b3a
--- /dev/null
+++ b/src/output/oss_output_plugin.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_OSS_OUTPUT_PLUGIN_H
+#define MPD_OSS_OUTPUT_PLUGIN_H
+
+extern const struct audio_output_plugin oss_output_plugin;
+
+#endif
diff --git a/src/output/osx_output_plugin.c b/src/output/osx_output_plugin.c
new file mode 100644
index 000000000..fbba81749
--- /dev/null
+++ b/src/output/osx_output_plugin.c
@@ -0,0 +1,434 @@
+/*
+ * Copyright (C) 2003-2012 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "osx_output_plugin.h"
+#include "output_api.h"
+#include "fifo_buffer.h"
+
+#include <glib.h>
+#include <CoreAudio/AudioHardware.h>
+#include <AudioUnit/AudioUnit.h>
+#include <CoreServices/CoreServices.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "osx"
+
+struct osx_output {
+ struct audio_output base;
+
+ /* configuration settings */
+ OSType component_subtype;
+ /* only applicable with kAudioUnitSubType_HALOutput */
+ const char *device_name;
+
+ AudioUnit au;
+ GMutex *mutex;
+ GCond *condition;
+
+ struct fifo_buffer *buffer;
+};
+
+/**
+ * The quark used for GError.domain.
+ */
+static inline GQuark
+osx_output_quark(void)
+{
+ return g_quark_from_static_string("osx_output");
+}
+
+static bool
+osx_output_test_default_device(void)
+{
+ /* on a Mac, this is always the default plugin, if nothing
+ else is configured */
+ return true;
+}
+
+static void
+osx_output_configure(struct osx_output *oo, const struct config_param *param)
+{
+ const char *device = config_get_block_string(param, "device", NULL);
+
+ if (device == NULL || 0 == strcmp(device, "default")) {
+ oo->component_subtype = kAudioUnitSubType_DefaultOutput;
+ oo->device_name = NULL;
+ }
+ else if (0 == strcmp(device, "system")) {
+ oo->component_subtype = kAudioUnitSubType_SystemOutput;
+ oo->device_name = NULL;
+ }
+ else {
+ oo->component_subtype = kAudioUnitSubType_HALOutput;
+ /* XXX am I supposed to g_strdup() this? */
+ oo->device_name = device;
+ }
+}
+
+static struct audio_output *
+osx_output_init(const struct config_param *param, GError **error_r)
+{
+ struct osx_output *oo = g_new(struct osx_output, 1);
+ if (!ao_base_init(&oo->base, &osx_output_plugin, param, error_r)) {
+ g_free(oo);
+ return NULL;
+ }
+
+ osx_output_configure(oo, param);
+ oo->mutex = g_mutex_new();
+ oo->condition = g_cond_new();
+
+ return &oo->base;
+}
+
+static void
+osx_output_finish(struct audio_output *ao)
+{
+ struct osx_output *od = (struct osx_output *)ao;
+
+ g_mutex_free(od->mutex);
+ g_cond_free(od->condition);
+ g_free(od);
+}
+
+static bool
+osx_output_set_device(struct osx_output *oo, GError **error)
+{
+ bool ret = true;
+ OSStatus status;
+ UInt32 size, numdevices;
+ AudioDeviceID *deviceids = NULL;
+ char name[256];
+ unsigned int i;
+
+ if (oo->component_subtype != kAudioUnitSubType_HALOutput)
+ goto done;
+
+ /* how many audio devices are there? */
+ status = AudioHardwareGetPropertyInfo(kAudioHardwarePropertyDevices,
+ &size,
+ NULL);
+ if (status != noErr) {
+ g_set_error(error, osx_output_quark(), status,
+ "Unable to determine number of OS X audio devices: %s",
+ GetMacOSStatusCommentString(status));
+ ret = false;
+ goto done;
+ }
+
+ /* what are the available audio device IDs? */
+ numdevices = size / sizeof(AudioDeviceID);
+ deviceids = g_malloc(size);
+ status = AudioHardwareGetProperty(kAudioHardwarePropertyDevices,
+ &size,
+ deviceids);
+ if (status != noErr) {
+ g_set_error(error, osx_output_quark(), status,
+ "Unable to determine OS X audio device IDs: %s",
+ GetMacOSStatusCommentString(status));
+ ret = false;
+ goto done;
+ }
+
+ /* which audio device matches oo->device_name? */
+ for (i = 0; i < numdevices; i++) {
+ size = sizeof(name);
+ status = AudioDeviceGetProperty(deviceids[i], 0, false,
+ kAudioDevicePropertyDeviceName,
+ &size, name);
+ if (status != noErr) {
+ g_set_error(error, osx_output_quark(), status,
+ "Unable to determine OS X device name "
+ "(device %u): %s",
+ (unsigned int) deviceids[i],
+ GetMacOSStatusCommentString(status));
+ ret = false;
+ goto done;
+ }
+ if (strcmp(oo->device_name, name) == 0) {
+ g_debug("found matching device: ID=%u, name=%s",
+ (unsigned int) deviceids[i], name);
+ break;
+ }
+ }
+ if (i == numdevices) {
+ g_warning("Found no audio device with name '%s' "
+ "(will use default audio device)",
+ oo->device_name);
+ goto done;
+ }
+
+ status = AudioUnitSetProperty(oo->au,
+ kAudioOutputUnitProperty_CurrentDevice,
+ kAudioUnitScope_Global,
+ 0,
+ &(deviceids[i]),
+ sizeof(AudioDeviceID));
+ if (status != noErr) {
+ g_set_error(error, osx_output_quark(), status,
+ "Unable to set OS X audio output device: %s",
+ GetMacOSStatusCommentString(status));
+ ret = false;
+ goto done;
+ }
+ g_debug("set OS X audio output device ID=%u, name=%s",
+ (unsigned int) deviceids[i], name);
+
+done:
+ if (deviceids != NULL)
+ g_free(deviceids);
+ return ret;
+}
+
+static OSStatus
+osx_render(void *vdata,
+ G_GNUC_UNUSED AudioUnitRenderActionFlags *io_action_flags,
+ G_GNUC_UNUSED const AudioTimeStamp *in_timestamp,
+ G_GNUC_UNUSED UInt32 in_bus_number,
+ G_GNUC_UNUSED UInt32 in_number_frames,
+ AudioBufferList *buffer_list)
+{
+ struct osx_output *od = (struct osx_output *) vdata;
+ AudioBuffer *buffer = &buffer_list->mBuffers[0];
+ size_t buffer_size = buffer->mDataByteSize;
+
+ assert(od->buffer != NULL);
+
+ g_mutex_lock(od->mutex);
+
+ size_t nbytes;
+ const void *src = fifo_buffer_read(od->buffer, &nbytes);
+
+ if (src != NULL) {
+ if (nbytes > buffer_size)
+ nbytes = buffer_size;
+
+ memcpy(buffer->mData, src, nbytes);
+ fifo_buffer_consume(od->buffer, nbytes);
+ } else
+ nbytes = 0;
+
+ g_cond_signal(od->condition);
+ g_mutex_unlock(od->mutex);
+
+ if (nbytes < buffer_size)
+ memset((unsigned char*)buffer->mData + nbytes, 0,
+ buffer_size - nbytes);
+
+ return 0;
+}
+
+static bool
+osx_output_enable(struct audio_output *ao, GError **error_r)
+{
+ struct osx_output *oo = (struct osx_output *)ao;
+
+ ComponentDescription desc;
+ desc.componentType = kAudioUnitType_Output;
+ desc.componentSubType = oo->component_subtype;
+ desc.componentManufacturer = kAudioUnitManufacturer_Apple;
+ desc.componentFlags = 0;
+ desc.componentFlagsMask = 0;
+
+ Component comp = FindNextComponent(NULL, &desc);
+ if (comp == 0) {
+ g_set_error(error_r, osx_output_quark(), 0,
+ "Error finding OS X component");
+ return false;
+ }
+
+ OSStatus status = OpenAComponent(comp, &oo->au);
+ if (status != noErr) {
+ g_set_error(error_r, osx_output_quark(), status,
+ "Unable to open OS X component: %s",
+ GetMacOSStatusCommentString(status));
+ return false;
+ }
+
+ if (!osx_output_set_device(oo, error_r)) {
+ CloseComponent(oo->au);
+ return false;
+ }
+
+ AURenderCallbackStruct callback;
+ callback.inputProc = osx_render;
+ callback.inputProcRefCon = oo;
+
+ ComponentResult result =
+ AudioUnitSetProperty(oo->au,
+ kAudioUnitProperty_SetRenderCallback,
+ kAudioUnitScope_Input, 0,
+ &callback, sizeof(callback));
+ if (result != noErr) {
+ CloseComponent(oo->au);
+ g_set_error(error_r, osx_output_quark(), result,
+ "unable to set callback for OS X audio unit");
+ return false;
+ }
+
+ return true;
+}
+
+static void
+osx_output_disable(struct audio_output *ao)
+{
+ struct osx_output *oo = (struct osx_output *)ao;
+
+ CloseComponent(oo->au);
+}
+
+static void
+osx_output_cancel(struct audio_output *ao)
+{
+ struct osx_output *od = (struct osx_output *)ao;
+
+ g_mutex_lock(od->mutex);
+ fifo_buffer_clear(od->buffer);
+ g_mutex_unlock(od->mutex);
+}
+
+static void
+osx_output_close(struct audio_output *ao)
+{
+ struct osx_output *od = (struct osx_output *)ao;
+
+ AudioOutputUnitStop(od->au);
+ AudioUnitUninitialize(od->au);
+
+ fifo_buffer_free(od->buffer);
+}
+
+static bool
+osx_output_open(struct audio_output *ao, struct audio_format *audio_format, GError **error)
+{
+ struct osx_output *od = (struct osx_output *)ao;
+
+ AudioStreamBasicDescription stream_description;
+ stream_description.mSampleRate = audio_format->sample_rate;
+ stream_description.mFormatID = kAudioFormatLinearPCM;
+ stream_description.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger;
+
+ switch (audio_format->format) {
+ case SAMPLE_FORMAT_S8:
+ stream_description.mBitsPerChannel = 8;
+ break;
+
+ case SAMPLE_FORMAT_S16:
+ stream_description.mBitsPerChannel = 16;
+ break;
+
+ case SAMPLE_FORMAT_S32:
+ stream_description.mBitsPerChannel = 32;
+ break;
+
+ default:
+ audio_format->format = SAMPLE_FORMAT_S32;
+ stream_description.mBitsPerChannel = 32;
+ 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;
+
+ ComponentResult result =
+ AudioUnitSetProperty(od->au, kAudioUnitProperty_StreamFormat,
+ kAudioUnitScope_Input, 0,
+ &stream_description,
+ sizeof(stream_description));
+ if (result != noErr) {
+ g_set_error(error, osx_output_quark(), result,
+ "Unable to set format on OS X device");
+ return false;
+ }
+
+ OSStatus status = AudioUnitInitialize(od->au);
+ if (status != noErr) {
+ g_set_error(error, osx_output_quark(), status,
+ "Unable to initialize OS X audio unit: %s",
+ GetMacOSStatusCommentString(status));
+ return false;
+ }
+
+ /* create a buffer of 1s */
+ od->buffer = fifo_buffer_new(audio_format->sample_rate *
+ audio_format_frame_size(audio_format));
+
+ status = AudioOutputUnitStart(od->au);
+ if (status != 0) {
+ AudioUnitUninitialize(od->au);
+ g_set_error(error, osx_output_quark(), status,
+ "unable to start audio output: %s",
+ GetMacOSStatusCommentString(status));
+ return false;
+ }
+
+ return true;
+}
+
+static size_t
+osx_output_play(struct audio_output *ao, const void *chunk, size_t size,
+ G_GNUC_UNUSED GError **error)
+{
+ struct osx_output *od = (struct osx_output *)ao;
+
+ g_mutex_lock(od->mutex);
+
+ void *dest;
+ size_t max_length;
+
+ while (true) {
+ dest = fifo_buffer_write(od->buffer, &max_length);
+ if (dest != NULL)
+ break;
+
+ /* wait for some free space in the buffer */
+ g_cond_wait(od->condition, od->mutex);
+ }
+
+ if (size > max_length)
+ size = max_length;
+
+ memcpy(dest, chunk, size);
+ fifo_buffer_append(od->buffer, size);
+
+ g_mutex_unlock(od->mutex);
+
+ return size;
+}
+
+const struct audio_output_plugin osx_output_plugin = {
+ .name = "osx",
+ .test_default_device = osx_output_test_default_device,
+ .init = osx_output_init,
+ .finish = osx_output_finish,
+ .enable = osx_output_enable,
+ .disable = osx_output_disable,
+ .open = osx_output_open,
+ .close = osx_output_close,
+ .play = osx_output_play,
+ .cancel = osx_output_cancel,
+};
diff --git a/src/output/osx_output_plugin.h b/src/output/osx_output_plugin.h
new file mode 100644
index 000000000..814702d4f
--- /dev/null
+++ b/src/output/osx_output_plugin.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_OSX_OUTPUT_PLUGIN_H
+#define MPD_OSX_OUTPUT_PLUGIN_H
+
+extern const struct audio_output_plugin osx_output_plugin;
+
+#endif
diff --git a/src/output/osx_plugin.c b/src/output/osx_plugin.c
deleted file mode 100644
index 501dcec10..000000000
--- a/src/output/osx_plugin.c
+++ /dev/null
@@ -1,289 +0,0 @@
-/*
- * 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 "output_api.h"
-#include "fifo_buffer.h"
-
-#include <glib.h>
-#include <AudioUnit/AudioUnit.h>
-#include <CoreServices/CoreServices.h>
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "osx"
-
-struct osx_output {
- AudioUnit au;
- GMutex *mutex;
- GCond *condition;
-
- struct fifo_buffer *buffer;
-};
-
-/**
- * The quark used for GError.domain.
- */
-static inline GQuark
-osx_output_quark(void)
-{
- return g_quark_from_static_string("osx_output");
-}
-
-static bool
-osx_output_test_default_device(void)
-{
- /* on a Mac, this is always the default plugin, if nothing
- else is configured */
- return true;
-}
-
-static void *
-osx_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 osx_output *oo = g_new(struct osx_output, 1);
-
- oo->mutex = g_mutex_new();
- oo->condition = g_cond_new();
-
- return oo;
-}
-
-static void osx_output_finish(void *data)
-{
- struct osx_output *od = data;
-
- g_mutex_free(od->mutex);
- g_cond_free(od->condition);
- g_free(od);
-}
-
-static void osx_output_cancel(void *data)
-{
- struct osx_output *od = data;
-
- g_mutex_lock(od->mutex);
- fifo_buffer_clear(od->buffer);
- g_mutex_unlock(od->mutex);
-}
-
-static void osx_output_close(void *data)
-{
- struct osx_output *od = data;
-
- AudioOutputUnitStop(od->au);
- AudioUnitUninitialize(od->au);
- CloseComponent(od->au);
-
- fifo_buffer_free(od->buffer);
-}
-
-static OSStatus
-osx_render(void *vdata,
- G_GNUC_UNUSED AudioUnitRenderActionFlags *io_action_flags,
- G_GNUC_UNUSED const AudioTimeStamp *in_timestamp,
- G_GNUC_UNUSED UInt32 in_bus_number,
- G_GNUC_UNUSED UInt32 in_number_frames,
- AudioBufferList *buffer_list)
-{
- struct osx_output *od = (struct osx_output *) vdata;
- AudioBuffer *buffer = &buffer_list->mBuffers[0];
- size_t buffer_size = buffer->mDataByteSize;
-
- assert(od->buffer != NULL);
-
- g_mutex_lock(od->mutex);
-
- size_t nbytes;
- const void *src = fifo_buffer_read(od->buffer, &nbytes);
-
- if (src != NULL) {
- if (nbytes > buffer_size)
- nbytes = buffer_size;
-
- memcpy(buffer->mData, src, nbytes);
- fifo_buffer_consume(od->buffer, nbytes);
- } else
- nbytes = 0;
-
- g_cond_signal(od->condition);
- g_mutex_unlock(od->mutex);
-
- if (nbytes < buffer_size)
- memset((unsigned char*)buffer->mData + nbytes, 0,
- buffer_size - nbytes);
-
- return 0;
-}
-
-static bool
-osx_output_open(void *data, struct audio_format *audio_format, GError **error)
-{
- struct osx_output *od = data;
- ComponentDescription desc;
- Component comp;
- AURenderCallbackStruct callback;
- AudioStreamBasicDescription stream_description;
- OSStatus status;
- ComponentResult result;
-
- desc.componentType = kAudioUnitType_Output;
- desc.componentSubType = kAudioUnitSubType_DefaultOutput;
- desc.componentManufacturer = kAudioUnitManufacturer_Apple;
- desc.componentFlags = 0;
- desc.componentFlagsMask = 0;
-
- comp = FindNextComponent(NULL, &desc);
- if (comp == 0) {
- g_set_error(error, osx_output_quark(), 0,
- "Error finding OS X component");
- return false;
- }
-
- status = OpenAComponent(comp, &od->au);
- if (status != noErr) {
- g_set_error(error, osx_output_quark(), 0,
- "Unable to open OS X component: %s",
- GetMacOSStatusCommentString(status));
- return false;
- }
-
- status = AudioUnitInitialize(od->au);
- if (status != noErr) {
- CloseComponent(od->au);
- g_set_error(error, osx_output_quark(), 0,
- "Unable to initialize OS X audio unit: %s",
- GetMacOSStatusCommentString(status));
- return false;
- }
-
- callback.inputProc = osx_render;
- callback.inputProcRefCon = od;
-
- result = AudioUnitSetProperty(od->au,
- kAudioUnitProperty_SetRenderCallback,
- kAudioUnitScope_Input, 0,
- &callback, sizeof(callback));
- if (result != noErr) {
- AudioUnitUninitialize(od->au);
- CloseComponent(od->au);
- g_set_error(error, osx_output_quark(), 0,
- "unable to set callback for OS X audio unit");
- return false;
- }
-
- stream_description.mSampleRate = audio_format->sample_rate;
- stream_description.mFormatID = kAudioFormatLinearPCM;
- stream_description.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger;
-
- switch (audio_format->format) {
- case SAMPLE_FORMAT_S8:
- stream_description.mBitsPerChannel = 8;
- break;
-
- case SAMPLE_FORMAT_S16:
- stream_description.mBitsPerChannel = 16;
- break;
-
- default:
- audio_format->format = SAMPLE_FORMAT_S16;
- stream_description.mBitsPerChannel = 16;
- 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,
- sizeof(stream_description));
- if (result != noErr) {
- AudioUnitUninitialize(od->au);
- CloseComponent(od->au);
- g_set_error(error, osx_output_quark(), 0,
- "Unable to set format on OS X device");
- return false;
- }
-
- /* create a buffer of 1s */
- od->buffer = fifo_buffer_new(audio_format->sample_rate *
- audio_format_frame_size(audio_format));
-
- status = AudioOutputUnitStart(od->au);
- if (status != 0) {
- fifo_buffer_free(od->buffer);
- g_set_error(error, osx_output_quark(), 0,
- "unable to start audio output: %s",
- GetMacOSStatusCommentString(status));
- return false;
- }
-
- return true;
-}
-
-static size_t
-osx_output_play(void *data, const void *chunk, size_t size,
- G_GNUC_UNUSED GError **error)
-{
- struct osx_output *od = data;
-
- g_mutex_lock(od->mutex);
-
- void *dest;
- size_t max_length;
-
- while (true) {
- dest = fifo_buffer_write(od->buffer, &max_length);
- if (dest != NULL)
- break;
-
- /* wait for some free space in the buffer */
- g_cond_wait(od->condition, od->mutex);
- }
-
- if (size > max_length)
- size = max_length;
-
- memcpy(dest, chunk, size);
- fifo_buffer_append(od->buffer, size);
-
- g_mutex_unlock(od->mutex);
-
- return size;
-}
-
-const struct audio_output_plugin osxPlugin = {
- .name = "osx",
- .test_default_device = osx_output_test_default_device,
- .init = osx_output_init,
- .finish = osx_output_finish,
- .open = osx_output_open,
- .close = osx_output_close,
- .play = osx_output_play,
- .cancel = osx_output_cancel,
-};
diff --git a/src/output/pipe_output_plugin.c b/src/output/pipe_output_plugin.c
index 1d1aec7b1..90c5a5331 100644
--- a/src/output/pipe_output_plugin.c
+++ b/src/output/pipe_output_plugin.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -18,12 +18,15 @@
*/
#include "config.h"
+#include "pipe_output_plugin.h"
#include "output_api.h"
#include <stdio.h>
#include <errno.h>
struct pipe_output {
+ struct audio_output base;
+
char *cmd;
FILE *fh;
};
@@ -37,13 +40,17 @@ pipe_output_quark(void)
return g_quark_from_static_string("pipe_output");
}
-static void *
-pipe_output_init(G_GNUC_UNUSED const struct audio_format *audio_format,
- const struct config_param *param,
+static struct audio_output *
+pipe_output_init(const struct config_param *param,
GError **error)
{
struct pipe_output *pd = g_new(struct pipe_output, 1);
+ if (!ao_base_init(&pd->base, &pipe_output_plugin, param, error)) {
+ g_free(pd);
+ return NULL;
+ }
+
pd->cmd = config_dup_block_string(param, "command", NULL);
if (pd->cmd == NULL) {
g_set_error(error, pipe_output_quark(), 0,
@@ -51,23 +58,25 @@ pipe_output_init(G_GNUC_UNUSED const struct audio_format *audio_format,
return NULL;
}
- return pd;
+ return &pd->base;
}
static void
-pipe_output_finish(void *data)
+pipe_output_finish(struct audio_output *ao)
{
- struct pipe_output *pd = data;
+ struct pipe_output *pd = (struct pipe_output *)ao;
g_free(pd->cmd);
+ ao_base_finish(&pd->base);
g_free(pd);
}
static bool
-pipe_output_open(void *data, G_GNUC_UNUSED struct audio_format *audio_format,
+pipe_output_open(struct audio_output *ao,
+ G_GNUC_UNUSED struct audio_format *audio_format,
G_GNUC_UNUSED GError **error)
{
- struct pipe_output *pd = data;
+ struct pipe_output *pd = (struct pipe_output *)ao;
pd->fh = popen(pd->cmd, "w");
if (pd->fh == NULL) {
@@ -81,17 +90,17 @@ pipe_output_open(void *data, G_GNUC_UNUSED struct audio_format *audio_format,
}
static void
-pipe_output_close(void *data)
+pipe_output_close(struct audio_output *ao)
{
- struct pipe_output *pd = data;
+ struct pipe_output *pd = (struct pipe_output *)ao;
pclose(pd->fh);
}
static size_t
-pipe_output_play(void *data, const void *chunk, size_t size, GError **error)
+pipe_output_play(struct audio_output *ao, const void *chunk, size_t size, GError **error)
{
- struct pipe_output *pd = data;
+ struct pipe_output *pd = (struct pipe_output *)ao;
size_t ret;
ret = fwrite(chunk, 1, size, pd->fh);
diff --git a/src/output/pipe_output_plugin.h b/src/output/pipe_output_plugin.h
new file mode 100644
index 000000000..9f014f829
--- /dev/null
+++ b/src/output/pipe_output_plugin.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_PIPE_OUTPUT_PLUGIN_H
+#define MPD_PIPE_OUTPUT_PLUGIN_H
+
+extern const struct audio_output_plugin pipe_output_plugin;
+
+#endif
diff --git a/src/output/pulse_output_plugin.c b/src/output/pulse_output_plugin.c
index 5fe2f572e..0dc9be0e4 100644
--- a/src/output/pulse_output_plugin.c
+++ b/src/output/pulse_output_plugin.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -31,11 +31,44 @@
#include <pulse/introspect.h>
#include <pulse/subscribe.h>
#include <pulse/error.h>
+#include <pulse/version.h>
#include <assert.h>
+#include <stddef.h>
#define MPD_PULSE_NAME "Music Player Daemon"
+#if !defined(PA_CHECK_VERSION)
+/**
+ * This macro was implemented in libpulse 0.9.16.
+ */
+#define PA_CHECK_VERSION(a,b,c) false
+#endif
+
+struct pulse_output {
+ struct audio_output base;
+
+ const char *name;
+ const char *server;
+ const char *sink;
+
+ struct pulse_mixer *mixer;
+
+ struct pa_threaded_mainloop *mainloop;
+ struct pa_context *context;
+ struct pa_stream *stream;
+
+ size_t writable;
+
+#if !PA_CHECK_VERSION(0,9,11)
+ /**
+ * We need this variable because pa_stream_is_corked() wasn't
+ * added before 0.9.11.
+ */
+ bool pause;
+#endif
+};
+
/**
* The quark used for GError.domain.
*/
@@ -46,6 +79,18 @@ pulse_output_quark(void)
}
void
+pulse_output_lock(struct pulse_output *po)
+{
+ pa_threaded_mainloop_lock(po->mainloop);
+}
+
+void
+pulse_output_unlock(struct pulse_output *po)
+{
+ pa_threaded_mainloop_unlock(po->mainloop);
+}
+
+void
pulse_output_set_mixer(struct pulse_output *po, struct pulse_mixer *pm)
{
assert(po != NULL);
@@ -299,16 +344,19 @@ pulse_output_setup_context(struct pulse_output *po, GError **error_r)
return true;
}
-static void *
-pulse_output_init(G_GNUC_UNUSED const struct audio_format *audio_format,
- const struct config_param *param,
- G_GNUC_UNUSED GError **error_r)
+static struct audio_output *
+pulse_output_init(const struct config_param *param, GError **error_r)
{
struct pulse_output *po;
g_setenv("PULSE_PROP_media.role", "music", true);
po = g_new(struct pulse_output, 1);
+ if (!ao_base_init(&po->base, &pulse_output_plugin, param, error_r)) {
+ g_free(po);
+ return NULL;
+ }
+
po->name = config_get_block_string(param, "name", "mpd_pulse");
po->server = config_get_block_string(param, "server", NULL);
po->sink = config_get_block_string(param, "sink", NULL);
@@ -318,21 +366,22 @@ pulse_output_init(G_GNUC_UNUSED const struct audio_format *audio_format,
po->context = NULL;
po->stream = NULL;
- return po;
+ return &po->base;
}
static void
-pulse_output_finish(void *data)
+pulse_output_finish(struct audio_output *ao)
{
- struct pulse_output *po = data;
+ struct pulse_output *po = (struct pulse_output *)ao;
+ ao_base_finish(&po->base);
g_free(po);
}
static bool
-pulse_output_enable(void *data, GError **error_r)
+pulse_output_enable(struct audio_output *ao, GError **error_r)
{
- struct pulse_output *po = data;
+ struct pulse_output *po = (struct pulse_output *)ao;
assert(po->mainloop == NULL);
assert(po->context == NULL);
@@ -376,9 +425,9 @@ pulse_output_enable(void *data, GError **error_r)
}
static void
-pulse_output_disable(void *data)
+pulse_output_disable(struct audio_output *ao)
{
- struct pulse_output *po = data;
+ struct pulse_output *po = (struct pulse_output *)ao;
assert(po->mainloop != NULL);
@@ -494,11 +543,46 @@ pulse_output_stream_write_cb(G_GNUC_UNUSED pa_stream *stream, size_t nbytes,
pa_threaded_mainloop_signal(po->mainloop, 0);
}
+/**
+ * Create, set up and connect a context.
+ *
+ * Caller must lock the main loop.
+ *
+ * @return true on success, false on error
+ */
+static bool
+pulse_output_setup_stream(struct pulse_output *po, const pa_sample_spec *ss,
+ GError **error_r)
+{
+ assert(po != NULL);
+ assert(po->context != NULL);
+
+ po->stream = pa_stream_new(po->context, po->name, ss, NULL);
+ if (po->stream == NULL) {
+ g_set_error(error_r, pulse_output_quark(), 0,
+ "pa_stream_new() has failed: %s",
+ pa_strerror(pa_context_errno(po->context)));
+ return false;
+ }
+
+#if PA_CHECK_VERSION(0,9,8)
+ pa_stream_set_suspended_callback(po->stream,
+ pulse_output_stream_suspended_cb, po);
+#endif
+
+ pa_stream_set_state_callback(po->stream,
+ pulse_output_stream_state_cb, po);
+ pa_stream_set_write_callback(po->stream,
+ pulse_output_stream_write_cb, po);
+
+ return true;
+}
+
static bool
-pulse_output_open(void *data, struct audio_format *audio_format,
+pulse_output_open(struct audio_output *ao, struct audio_format *audio_format,
GError **error_r)
{
- struct pulse_output *po = data;
+ struct pulse_output *po = (struct pulse_output *)ao;
pa_sample_spec ss;
int error;
@@ -540,25 +624,11 @@ pulse_output_open(void *data, struct audio_format *audio_format,
/* create a stream .. */
- po->stream = pa_stream_new(po->context, po->name, &ss, NULL);
- if (po->stream == NULL) {
- g_set_error(error_r, pulse_output_quark(), 0,
- "pa_stream_new() has failed: %s",
- pa_strerror(pa_context_errno(po->context)));
+ if (!pulse_output_setup_stream(po, &ss, error_r)) {
pa_threaded_mainloop_unlock(po->mainloop);
return false;
}
-#if PA_CHECK_VERSION(0,9,8)
- pa_stream_set_suspended_callback(po->stream,
- pulse_output_stream_suspended_cb, po);
-#endif
-
- pa_stream_set_state_callback(po->stream,
- pulse_output_stream_state_cb, po);
- pa_stream_set_write_callback(po->stream,
- pulse_output_stream_write_cb, po);
-
/* .. and connect it (asynchronously) */
error = pa_stream_connect_playback(po->stream, po->sink,
@@ -583,9 +653,9 @@ pulse_output_open(void *data, struct audio_format *audio_format,
}
static void
-pulse_output_close(void *data)
+pulse_output_close(struct audio_output *ao)
{
- struct pulse_output *po = data;
+ struct pulse_output *po = (struct pulse_output *)ao;
pa_operation *o;
assert(po->mainloop != NULL);
@@ -732,9 +802,10 @@ pulse_output_stream_pause(struct pulse_output *po, bool pause,
}
static size_t
-pulse_output_play(void *data, const void *chunk, size_t size, GError **error_r)
+pulse_output_play(struct audio_output *ao, const void *chunk, size_t size,
+ GError **error_r)
{
- struct pulse_output *po = data;
+ struct pulse_output *po = (struct pulse_output *)ao;
int error;
assert(po->mainloop != NULL);
@@ -802,9 +873,9 @@ pulse_output_play(void *data, const void *chunk, size_t size, GError **error_r)
}
static void
-pulse_output_cancel(void *data)
+pulse_output_cancel(struct audio_output *ao)
{
- struct pulse_output *po = data;
+ struct pulse_output *po = (struct pulse_output *)ao;
pa_operation *o;
assert(po->mainloop != NULL);
@@ -834,9 +905,9 @@ pulse_output_cancel(void *data)
}
static bool
-pulse_output_pause(void *data)
+pulse_output_pause(struct audio_output *ao)
{
- struct pulse_output *po = data;
+ struct pulse_output *po = (struct pulse_output *)ao;
GError *error = NULL;
assert(po->mainloop != NULL);
@@ -881,12 +952,12 @@ pulse_output_test_default_device(void)
struct pulse_output *po;
bool success;
- po = pulse_output_init(NULL, NULL, NULL);
+ po = (struct pulse_output *)pulse_output_init(NULL, NULL);
if (po == NULL)
return false;
success = pulse_output_wait_connection(po, NULL);
- pulse_output_finish(po);
+ pulse_output_finish(&po->base);
return success;
}
diff --git a/src/output/pulse_output_plugin.h b/src/output/pulse_output_plugin.h
index 06e3aec43..02a51f27b 100644
--- a/src/output/pulse_output_plugin.h
+++ b/src/output/pulse_output_plugin.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -21,43 +21,20 @@
#define MPD_PULSE_OUTPUT_PLUGIN_H
#include <stdbool.h>
-#include <stddef.h>
#include <glib.h>
-#include <pulse/version.h>
-
-#if !defined(PA_CHECK_VERSION)
-/**
- * This macro was implemented in libpulse 0.9.16.
- */
-#define PA_CHECK_VERSION(a,b,c) false
-#endif
-
-struct pa_operation;
+struct pulse_output;
+struct pulse_mixer;
struct pa_cvolume;
-struct pulse_output {
- const char *name;
- const char *server;
- const char *sink;
-
- struct pulse_mixer *mixer;
+extern const struct audio_output_plugin pulse_output_plugin;
- struct pa_threaded_mainloop *mainloop;
- struct pa_context *context;
- struct pa_stream *stream;
-
- size_t writable;
+void
+pulse_output_lock(struct pulse_output *po);
-#if !PA_CHECK_VERSION(0,9,11)
- /**
- * We need this variable because pa_stream_is_corked() wasn't
- * added before 0.9.11.
- */
- bool pause;
-#endif
-};
+void
+pulse_output_unlock(struct pulse_output *po);
void
pulse_output_set_mixer(struct pulse_output *po, struct pulse_mixer *pm);
diff --git a/src/output/raop_output_plugin.c b/src/output/raop_output_plugin.c
new file mode 100644
index 000000000..6177b9b7d
--- /dev/null
+++ b/src/output/raop_output_plugin.c
@@ -0,0 +1,1056 @@
+/*
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "raop_output_plugin.h"
+#include "output_api.h"
+#include "mixer_list.h"
+#include "fd_util.h"
+#include "ntp_server.h"
+#include "rtsp_client.h"
+#include "glib_compat.h"
+
+#include <glib.h>
+#include <unistd.h>
+#include <sys/time.h>
+#include <openssl/aes.h>
+#include <openssl/err.h>
+#include <openssl/rand.h>
+#include <openssl/rsa.h>
+#include <openssl/engine.h>
+
+#ifndef WIN32
+#include <arpa/inet.h>
+#include <sys/socket.h>
+#include <netdb.h>
+#endif
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "raop"
+
+struct play_state {
+ bool playing;
+ unsigned short seq_num;
+ unsigned int rtptime;
+ unsigned int sync_src;
+ unsigned int start_rtptime;
+ struct timeval start_time;
+ struct timeval last_send;
+};
+
+/*********************************************************************/
+
+enum pause_state {
+ NO_PAUSE = 0,
+ OP_PAUSE,
+ NODATA_PAUSE,
+};
+
+#define MINIMUM_SAMPLE_SIZE 32
+
+#define RAOP_FD_READ (1<<0)
+#define RAOP_FD_WRITE (1<<1)
+
+/*********************************************************************/
+
+struct encrypt_data {
+ AES_KEY ctx;
+ unsigned char iv[16]; // initialization vector for aes-cbc
+ unsigned char nv[16]; // next vector for aes-cbc
+ unsigned char key[16]; // key for aes-cbc
+};
+
+/*********************************************************************/
+
+struct raop_data {
+ struct audio_output base;
+
+ struct rtspcl_data *rtspcl;
+ const char *addr; // target host address
+ short rtsp_port;
+ struct sockaddr_in ctrl_addr;
+ struct sockaddr_in data_addr;
+
+ bool is_master;
+ struct raop_data *next;
+
+ unsigned volume;
+
+ GMutex *control_mutex;
+
+ bool started;
+ bool paused;
+};
+
+/*********************************************************************/
+
+struct control_data {
+ unsigned short port;
+ int fd;
+};
+
+/*********************************************************************/
+
+#define NUMSAMPLES 352
+#define RAOP_BUFFER_SIZE NUMSAMPLES * 4
+#define RAOP_HEADER_SIZE 12
+#define ALAC_MAX_HEADER_SIZE 8
+#define RAOP_MAX_PACKET_SIZE RAOP_BUFFER_SIZE + RAOP_HEADER_SIZE + ALAC_MAX_HEADER_SIZE
+
+// session
+struct raop_session_data {
+ struct raop_data *raop_list;
+ struct ntp_server ntp;
+ struct control_data ctrl;
+ struct encrypt_data encrypt;
+ struct play_state play_state;
+
+ int data_fd;
+
+ unsigned char buffer[RAOP_BUFFER_SIZE];
+ size_t bufferSize;
+
+ unsigned char data[RAOP_MAX_PACKET_SIZE];
+ int wblk_wsize;
+ int wblk_remsize;
+
+ GMutex *data_mutex;
+ GMutex *list_mutex;
+};
+
+/*********************************************************************/
+
+static struct raop_session_data *raop_session = NULL;
+
+/**
+ * The quark used for GError.domain.
+ */
+static inline GQuark
+raop_output_quark(void)
+{
+ return g_quark_from_static_string("raop_output");
+}
+
+static void
+raop_session_free(struct raop_session_data *session)
+{
+ assert(session != NULL);
+ assert(session->raop_list == NULL);
+
+ ntp_server_close(&session->ntp);
+
+ if (session->data_mutex != NULL)
+ g_mutex_free(session->data_mutex);
+
+ if (session->list_mutex != NULL)
+ g_mutex_free(session->list_mutex);
+
+ if (raop_session->data_fd >= 0)
+ close_socket(raop_session->data_fd);
+
+ if (raop_session->ctrl.fd >= 0)
+ close_socket(raop_session->ctrl.fd);
+
+ g_free(session);
+}
+
+static struct raop_session_data *
+raop_session_new(GError **error_r)
+{
+ struct raop_session_data *session = g_new(struct raop_session_data, 1);
+ session->raop_list = NULL;
+
+ session->data_mutex = g_mutex_new();
+ session->list_mutex = g_mutex_new();
+
+ ntp_server_init(&session->ntp);
+ session->ctrl.port = 6001;
+ session->ctrl.fd = -1;
+ session->play_state.playing = false;
+ session->play_state.seq_num = (short) g_random_int();
+ session->play_state.rtptime = g_random_int();
+ session->play_state.sync_src = g_random_int();
+ session->play_state.last_send.tv_sec = 0;
+ session->play_state.last_send.tv_usec = 0;
+ session->data_fd = -1;
+
+ if (!RAND_bytes(session->encrypt.iv, sizeof(session->encrypt.iv)) ||
+ !RAND_bytes(session->encrypt.key, sizeof(session->encrypt.key))) {
+ raop_session_free(session);
+ g_set_error(error_r, raop_output_quark(), 0,
+ "RAND_bytes error code=%ld", ERR_get_error());
+ return NULL;
+ }
+ memcpy(session->encrypt.nv, session->encrypt.iv, sizeof(session->encrypt.nv));
+ for (unsigned i = 0; i < 16; i++) {
+ printf("0x%x ", session->encrypt.key[i]);
+ }
+ printf("\n");
+ AES_set_encrypt_key(session->encrypt.key, 128, &session->encrypt.ctx);
+
+ memset(session->buffer, 0, RAOP_BUFFER_SIZE);
+ session->bufferSize = 0;
+
+ return session;
+}
+
+static struct raop_data *
+new_raop_data(const struct config_param *param, GError **error_r)
+{
+ struct raop_data *ret = g_new(struct raop_data, 1);
+ if (!ao_base_init(&ret->base, &raop_output_plugin, param, error_r)) {
+ g_free(ret);
+ return NULL;
+ }
+
+ ret->control_mutex = g_mutex_new();
+
+ ret->next = NULL;
+ ret->is_master = 0;
+ ret->started = 0;
+ ret->paused = 0;
+
+ if (raop_session == NULL &&
+ (raop_session = raop_session_new(error_r)) == NULL) {
+ g_mutex_free(ret->control_mutex);
+ ao_base_finish(&ret->base);
+ g_free(ret);
+ return NULL;
+ }
+
+ return ret;
+}
+
+/*
+ * remove one character from a string
+ * return the number of deleted characters
+ */
+static int
+remove_char_from_string(char *str, char c)
+{
+ char *src, *dst;
+
+ /* skip all characters that don't need to be copied */
+ src = strchr(str, c);
+ if (!src)
+ return 0;
+
+ for (dst = src; *src; src++)
+ if (*src != c)
+ *(dst++) = *src;
+
+ *dst = '\0';
+
+ return src - dst;
+}
+
+/* bind an opened socket to specified hostname and port.
+ * if hostname=NULL, use INADDR_ANY.
+ * if *port=0, use dynamically assigned port
+ */
+static int bind_host(int sd, char *hostname, unsigned long ulAddr,
+ unsigned short *port, GError **error_r)
+{
+ struct sockaddr_in my_addr;
+ socklen_t nlen = sizeof(struct sockaddr);
+ struct hostent *h;
+
+ memset(&my_addr, 0, sizeof(my_addr));
+ /* use specified hostname */
+ if (hostname) {
+ /* get server IP address (no check if input is IP address or DNS name) */
+ h = gethostbyname(hostname);
+ if (h == NULL) {
+ if (strstr(hostname, "255.255.255.255") == hostname) {
+ my_addr.sin_addr.s_addr=-1;
+ } else {
+ if ((my_addr.sin_addr.s_addr = inet_addr(hostname)) == 0xFFFFFFFF) {
+ g_set_error(error_r, raop_output_quark(), 0,
+ "failed to resolve host '%s'",
+ hostname);
+ return -1;
+ }
+ }
+ my_addr.sin_family = AF_INET;
+ } else {
+ my_addr.sin_family = h->h_addrtype;
+ memcpy((char *) &my_addr.sin_addr.s_addr,
+ h->h_addr_list[0], h->h_length);
+ }
+ } else {
+ // if hostname=NULL, use INADDR_ANY
+ if (ulAddr)
+ my_addr.sin_addr.s_addr = ulAddr;
+ else
+ my_addr.sin_addr.s_addr = htonl(INADDR_ANY);
+ my_addr.sin_family = AF_INET;
+ }
+
+ /* bind a specified port */
+ my_addr.sin_port = htons(*port);
+
+ if (bind(sd, (struct sockaddr *) &my_addr, sizeof(my_addr)) < 0) {
+ g_set_error(error_r, raop_output_quark(), errno,
+ "failed to bind socket: %s",
+ g_strerror(errno));
+ return -1;
+ }
+
+ if (*port == 0) {
+ getsockname(sd, (struct sockaddr *) &my_addr, &nlen);
+ *port = ntohs(my_addr.sin_port);
+ }
+
+ return 0;
+}
+
+/*
+ * open udp port
+ */
+static int
+open_udp_socket(char *hostname, unsigned short *port,
+ GError **error_r)
+{
+ int sd;
+ const int size = 30000;
+
+ /* socket creation */
+ sd = socket(PF_INET, SOCK_DGRAM, 0);
+ if (sd < 0) {
+ g_set_error(error_r, raop_output_quark(), errno,
+ "failed to create UDP socket: %s",
+ g_strerror(errno));
+ return -1;
+ }
+ if (setsockopt(sd, SOL_SOCKET, SO_SNDBUF, (const char *) &size, sizeof(size)) < 0) {
+ g_set_error(error_r, raop_output_quark(), errno,
+ "failed to set UDP buffer size: %s",
+ g_strerror(errno));
+ return -1;
+ }
+ if (bind_host(sd, hostname, 0, port, error_r)) {
+ close_socket(sd);
+ return -1;
+ }
+
+ return sd;
+}
+
+static bool
+get_sockaddr_by_host(const char *host, short destport,
+ struct sockaddr_in *addr,
+ GError **error_r)
+{
+ struct hostent *h;
+
+ h = gethostbyname(host);
+ if (h) {
+ addr->sin_family = h->h_addrtype;
+ memcpy((char *) &addr->sin_addr.s_addr, h->h_addr_list[0], h->h_length);
+ } else {
+ addr->sin_family = AF_INET;
+ if ((addr->sin_addr.s_addr=inet_addr(host))==0xFFFFFFFF) {
+ g_set_error(error_r, rtsp_client_quark(), 0,
+ "failed to resolve host '%s'", host);
+ return false;
+ }
+ }
+ addr->sin_port = htons(destport);
+ return true;
+}
+
+/*
+ * Calculate the current NTP time, store it in the buffer.
+ */
+static void
+fill_int(unsigned char *buffer, uint32_t value)
+{
+ uint32_t be = GINT32_TO_BE(value);
+ memcpy(buffer, &be, sizeof(be));
+}
+
+/*
+ * Store time in the NTP format in the buffer
+ */
+static void
+fill_time_buffer_with_time(unsigned char *buffer, struct timeval *tout)
+{
+ unsigned long secs_to_baseline = 964697997;
+ double fraction;
+ unsigned long long_fraction;
+ unsigned long secs;
+
+ fraction = ((double) tout->tv_usec) / 1000000.0;
+ long_fraction = (unsigned long) (fraction * 256.0 * 256.0 * 256.0 * 256.0);
+ secs = secs_to_baseline + tout->tv_sec;
+ fill_int(buffer, secs);
+ fill_int(buffer + 4, long_fraction);
+}
+
+static void
+get_time_for_rtp(struct play_state *state, struct timeval *tout)
+{
+ unsigned long rtp_diff = state->rtptime - state->start_rtptime;
+ unsigned long add_secs = rtp_diff / 44100;
+ unsigned long add_usecs = (((rtp_diff % 44100) * 10000) / 441) % 1000000;
+ tout->tv_sec = state->start_time.tv_sec + add_secs;
+ tout->tv_usec = state->start_time.tv_usec + add_usecs;
+ if (tout->tv_usec >= 1000000) {
+ tout->tv_sec++;
+ tout->tv_usec = tout->tv_usec % 1000000;
+ }
+}
+
+/*
+ * Send a control command
+ */
+static bool
+send_control_command(struct control_data *ctrl, struct raop_data *rd,
+ struct play_state *state,
+ GError **error_r)
+{
+ unsigned char buf[20];
+ int diff;
+ int num_bytes;
+ struct timeval ctrl_time;
+
+ diff = 88200;
+ if (rd->started) {
+ buf[0] = 0x80;
+ diff += NUMSAMPLES;
+ } else {
+ buf[0] = 0x90;
+ state->playing = true;
+ state->start_rtptime = state->rtptime;
+ }
+ buf[1] = 0xd4;
+ buf[2] = 0x00;
+ buf[3] = 0x07;
+ fill_int(buf + 4, state->rtptime - diff);
+ get_time_for_rtp(state, &ctrl_time);
+ fill_time_buffer_with_time(buf + 8, &ctrl_time);
+ fill_int(buf + 16, state->rtptime);
+
+ num_bytes = sendto(ctrl->fd, (const void *)buf, sizeof(buf), 0,
+ (struct sockaddr *)&rd->ctrl_addr,
+ sizeof(rd->ctrl_addr));
+ if (num_bytes < 0) {
+ g_set_error(error_r, raop_output_quark(), errno,
+ "Unable to send control command: %s",
+ g_strerror(errno));
+ return false;
+ }
+
+ return true;
+}
+
+static int rsa_encrypt(const unsigned char *text, int len, unsigned char *res)
+{
+ RSA *rsa;
+ gsize usize;
+ unsigned char *modulus;
+ unsigned char *exponent;
+ int size;
+
+ char n[] =
+ "59dE8qLieItsH1WgjrcFRKj6eUWqi+bGLOX1HL3U3GhC/j0Qg90u3sG/1CUtwC"
+ "5vOYvfDmFI6oSFXi5ELabWJmT2dKHzBJKa3k9ok+8t9ucRqMd6DZHJ2YCCLlDR"
+ "KSKv6kDqnw4UwPdpOMXziC/AMj3Z/lUVX1G7WSHCAWKf1zNS1eLvqr+boEjXuB"
+ "OitnZ/bDzPHrTOZz0Dew0uowxf/+sG+NCK3eQJVxqcaJ/vEHKIVd2M+5qL71yJ"
+ "Q+87X6oV3eaYvt3zWZYD6z5vYTcrtij2VZ9Zmni/UAaHqn9JdsBWLUEpVviYnh"
+ "imNVvYFZeCXg/IdTQ+x4IRdiXNv5hEew==";
+ char e[] = "AQAB";
+
+ rsa = RSA_new();
+
+ modulus = g_base64_decode(n, &usize);
+ rsa->n = BN_bin2bn(modulus, usize, NULL);
+ exponent = g_base64_decode(e, &usize);
+ rsa->e = BN_bin2bn(exponent, usize, NULL);
+ g_free(modulus);
+ g_free(exponent);
+ size = RSA_public_encrypt(len, text, res, rsa, RSA_PKCS1_OAEP_PADDING);
+
+ RSA_free(rsa);
+ return size;
+}
+
+static int
+raop_encrypt(struct encrypt_data *encryp, unsigned char *data, int size)
+{
+ // any bytes that fall beyond the last 16 byte page should be sent
+ // in the clear
+ int alt_size = size - (size % 16);
+
+ memcpy(encryp->nv, encryp->iv, 16);
+
+ AES_cbc_encrypt(data, data, alt_size, &encryp->ctx, encryp->nv, 1);
+
+ return size;
+}
+
+/* write bits filed data, *bpos=0 for msb, *bpos=7 for lsb
+ d=data, blen=length of bits field
+*/
+static inline void
+bits_write(unsigned char **p, unsigned char d, int blen, int *bpos)
+{
+ int lb, rb, bd;
+ lb =7 - *bpos;
+ rb = lb - blen + 1;
+ if (rb >= 0) {
+ bd = d << rb;
+ if (*bpos)
+ **p |= bd;
+ else
+ **p = bd;
+ *bpos += blen;
+ } else {
+ bd = d >> -rb;
+ **p |= bd;
+ *p += 1;
+ **p = d << (8 + rb);
+ *bpos = -rb;
+ }
+}
+
+static bool
+wrap_pcm(unsigned char *buffer, int bsize, int *size, unsigned char *inData, int inSize)
+{
+ unsigned char one[4];
+ int count = 0;
+ int bpos = 0;
+ unsigned char *bp = buffer;
+ int i, nodata = 0;
+ bits_write(&bp, 1, 3, &bpos); // channel=1, stereo
+ bits_write(&bp, 0, 4, &bpos); // unknown
+ bits_write(&bp, 0, 8, &bpos); // unknown
+ bits_write(&bp, 0, 4, &bpos); // unknown
+ if (bsize != 4096 && false)
+ bits_write(&bp, 1, 1, &bpos); // hassize
+ else
+ bits_write(&bp, 0, 1, &bpos); // hassize
+ bits_write(&bp, 0, 2, &bpos); // unused
+ bits_write(&bp, 1, 1, &bpos); // is-not-compressed
+ if (bsize != 4096 && false) {
+ // size of data, integer, big endian
+ bits_write(&bp, (bsize >> 24) & 0xff, 8, &bpos);
+ bits_write(&bp, (bsize >> 16) & 0xff, 8, &bpos);
+ bits_write(&bp, (bsize >> 8) & 0xff, 8, &bpos);
+ bits_write(&bp, bsize&0xff, 8, &bpos);
+ }
+ while (1) {
+ if (inSize <= count * 4) nodata = 1;
+ if (nodata) break;
+ one[0] = inData[count * 4];
+ one[1] = inData[count * 4 + 1];
+ one[2] = inData[count * 4 + 2];
+ one[3] = inData[count * 4 + 3];
+
+#if BYTE_ORDER == BIG_ENDIAN
+ bits_write(&bp, one[0], 8, &bpos);
+ bits_write(&bp, one[1], 8, &bpos);
+ bits_write(&bp, one[2], 8, &bpos);
+ bits_write(&bp, one[3], 8, &bpos);
+#else
+ bits_write(&bp, one[1], 8, &bpos);
+ bits_write(&bp, one[0], 8, &bpos);
+ bits_write(&bp, one[3], 8, &bpos);
+ bits_write(&bp, one[2], 8, &bpos);
+#endif
+
+ if (++count == bsize) break;
+ }
+ if (!count) return false; // when no data at all, it should stop playing
+ /* when readable size is less than bsize, fill 0 at the bottom */
+ for(i = 0; i < (bsize - count) * 4; i++) {
+ bits_write(&bp, 0, 8, &bpos);
+ }
+ *size = (int)(bp - buffer);
+ if (bpos) *size += 1;
+ return true;
+}
+
+static bool
+raopcl_connect(struct raop_data *rd, GError **error_r)
+{
+ unsigned char buf[4 + 8 + 16];
+ char sid[16];
+ char sci[24];
+ char act_r[17];
+ char *sac=NULL, *key = NULL, *iv = NULL;
+ char sdp[1024];
+ int rval = false;
+ unsigned char rsakey[512];
+ struct timeval current_time;
+ unsigned int sessionNum;
+ int i;
+
+
+ gettimeofday(&current_time,NULL);
+ sessionNum = current_time.tv_sec + 2082844804;
+
+ RAND_bytes(buf, sizeof(buf));
+ sprintf(act_r, "%u", (unsigned int) g_random_int());
+ sprintf(sid, "%u", sessionNum);
+ sprintf(sci, "%08x%08x", *((int *)(buf + 4)), *((int *)(buf + 8)));
+ sac = g_base64_encode(buf + 12, 16);
+ rd->rtspcl = rtspcl_open();
+ rtspcl_set_useragent(rd->rtspcl, "iTunes/8.1.1 (Macintosh; U; PPC Mac OS X 10.4)");
+ rtspcl_add_exthds(rd->rtspcl, "Client-Instance", sci);
+ rtspcl_add_exthds(rd->rtspcl, "DACP-ID", sci);
+ rtspcl_add_exthds(rd->rtspcl, "Active-Remote", act_r);
+ if (!rtspcl_connect(rd->rtspcl, rd->addr, rd->rtsp_port, sid, error_r))
+ goto erexit;
+
+ i = rsa_encrypt(raop_session->encrypt.key, 16, rsakey);
+ key = g_base64_encode(rsakey, i);
+ remove_char_from_string(key, '=');
+ iv = g_base64_encode(raop_session->encrypt.iv, 16);
+ remove_char_from_string(iv, '=');
+ sprintf(sdp,
+ "v=0\r\n"
+ "o=iTunes %s 0 IN IP4 %s\r\n"
+ "s=iTunes\r\n"
+ "c=IN IP4 %s\r\n"
+ "t=0 0\r\n"
+ "m=audio 0 RTP/AVP 96\r\n"
+ "a=rtpmap:96 AppleLossless\r\n"
+ "a=fmtp:96 %d 0 16 40 10 14 2 255 0 0 44100\r\n"
+ "a=rsaaeskey:%s\r\n"
+ "a=aesiv:%s\r\n",
+ sid, rtspcl_local_ip(rd->rtspcl), rd->addr, NUMSAMPLES, key, iv);
+ remove_char_from_string(sac, '=');
+ // rtspcl_add_exthds(rd->rtspcl, "Apple-Challenge", sac);
+ if (!rtspcl_announce_sdp(rd->rtspcl, sdp, error_r))
+ goto erexit;
+ // if (!rtspcl_mark_del_exthds(rd->rtspcl, "Apple-Challenge")) goto erexit;
+ if (!rtspcl_setup(rd->rtspcl, NULL,
+ raop_session->ctrl.port, raop_session->ntp.port,
+ error_r))
+ goto erexit;
+
+ if (!get_sockaddr_by_host(rd->addr, rd->rtspcl->control_port,
+ &rd->ctrl_addr, error_r))
+ goto erexit;
+
+ if (!get_sockaddr_by_host(rd->addr, rd->rtspcl->server_port,
+ &rd->data_addr, error_r))
+ goto erexit;
+
+ if (!rtspcl_record(rd->rtspcl,
+ raop_session->play_state.seq_num,
+ raop_session->play_state.rtptime,
+ error_r))
+ goto erexit;
+
+ rval = true;
+
+ erexit:
+ g_free(sac);
+ g_free(key);
+ g_free(iv);
+ return rval;
+}
+
+static int
+difference (struct timeval *t1, struct timeval *t2)
+{
+ int ret = 150000000;
+ if (t1->tv_sec - t2->tv_sec < 150) {
+ ret = (t1->tv_sec - t2->tv_sec) * 1000000;
+ ret += t1->tv_usec - t2->tv_usec;
+ }
+ return ret;
+}
+
+/*
+ * With airtunes version 2, we don't get responses back when we send audio
+ * data. The only requests we get from the airtunes device are timing
+ * requests.
+ */
+static bool
+send_audio_data(int fd, GError **error_r)
+{
+ int i = 0;
+ struct timeval current_time, rtp_time;
+ struct raop_data *rd = raop_session->raop_list;
+
+ get_time_for_rtp(&raop_session->play_state, &rtp_time);
+ gettimeofday(&current_time, NULL);
+ int diff = difference(&rtp_time, &current_time);
+ if (diff > 0)
+ g_usleep(diff);
+
+ gettimeofday(&raop_session->play_state.last_send, NULL);
+ while (rd) {
+ if (rd->started) {
+ raop_session->data[1] = 0x60;
+ } else {
+ rd->started = true;
+ raop_session->data[1] = 0xe0;
+ }
+ i = sendto(fd, (const void *)(raop_session->data + raop_session->wblk_wsize),
+ raop_session->wblk_remsize, 0, (struct sockaddr *) &rd->data_addr,
+ sizeof(rd->data_addr));
+ if (i < 0) {
+ g_set_error(error_r, raop_output_quark(), errno,
+ "write error: %s",
+ g_strerror(errno));
+ return false;
+ }
+ if (i == 0) {
+ g_set_error_literal(error_r, raop_output_quark(), 0,
+ "disconnected on the other end");
+ return false;
+ }
+ rd = rd->next;
+ }
+ raop_session->wblk_wsize += i;
+ raop_session->wblk_remsize -= i;
+
+ return true;
+}
+
+static struct audio_output *
+raop_output_init(const struct config_param *param, GError **error_r)
+{
+ const char *host = config_get_block_string(param, "host", NULL);
+ if (host == NULL) {
+ g_set_error_literal(error_r, raop_output_quark(), 0,
+ "missing option 'host'");
+ return NULL;
+ }
+
+ struct raop_data *rd;
+
+ rd = new_raop_data(param, error_r);
+ if (rd == NULL)
+ return NULL;
+
+ rd->addr = host;
+ rd->rtsp_port = config_get_block_unsigned(param, "port", 5000);
+ rd->volume = config_get_block_unsigned(param, "volume", 75);
+ return &rd->base;
+}
+
+static bool
+raop_set_volume_local(struct raop_data *rd, int volume, GError **error_r)
+{
+ char vol_str[128];
+ sprintf(vol_str, "volume: %d.000000\r\n", volume);
+ return rtspcl_set_parameter(rd->rtspcl, vol_str, error_r);
+}
+
+
+static void
+raop_output_finish(struct audio_output *ao)
+{
+ struct raop_data *rd = (struct raop_data *)ao;
+
+ if (rd->rtspcl)
+ rtspcl_close(rd->rtspcl);
+
+ g_mutex_free(rd->control_mutex);
+ ao_base_finish(&rd->base);
+ g_free(rd);
+
+ if (raop_session->raop_list == NULL) {
+ raop_session_free(raop_session);
+ raop_session = NULL;
+ }
+}
+
+#define RAOP_VOLUME_MIN -30
+#define RAOP_VOLUME_MAX 0
+
+int
+raop_get_volume(struct raop_data *rd)
+{
+ return rd->volume;
+}
+
+bool
+raop_set_volume(struct raop_data *rd, unsigned volume, GError **error_r)
+{
+ int raop_volume;
+ bool rval;
+
+ //set parameter volume
+ if (volume == 0) {
+ raop_volume = -144;
+ } else {
+ raop_volume = RAOP_VOLUME_MIN +
+ (RAOP_VOLUME_MAX - RAOP_VOLUME_MIN) * volume / 100;
+ }
+ g_mutex_lock(rd->control_mutex);
+ rval = raop_set_volume_local(rd, raop_volume, error_r);
+ if (rval) rd->volume = volume;
+ g_mutex_unlock(rd->control_mutex);
+
+ return rval;
+}
+
+static void
+raop_output_cancel(struct audio_output *ao)
+{
+ //flush
+ struct key_data kd;
+ struct raop_data *rd = (struct raop_data *)ao;
+ int flush_diff = 1;
+
+ rd->started = 0;
+ if (rd->is_master) {
+ raop_session->play_state.playing = false;
+ }
+ if (rd->paused) {
+ return;
+ }
+
+ g_mutex_lock(rd->control_mutex);
+ static char rtp_key[] = "RTP-Info";
+ kd.key = rtp_key;
+ char buf[128];
+ sprintf(buf, "seq=%d; rtptime=%d", raop_session->play_state.seq_num + flush_diff, raop_session->play_state.rtptime + NUMSAMPLES * flush_diff);
+ kd.data = buf;
+ kd.next = NULL;
+ exec_request(rd->rtspcl, "FLUSH", NULL, NULL, 1,
+ &kd, NULL, NULL);
+ g_mutex_unlock(rd->control_mutex);
+}
+
+static bool
+raop_output_pause(struct audio_output *ao)
+{
+ struct raop_data *rd = (struct raop_data *)ao;
+
+ rd->paused = true;
+ return true;
+}
+
+/**
+ * Remove the output from the session's list. Caller must not lock
+ * the list_mutex.
+ */
+static void
+raop_output_remove(struct raop_data *rd)
+{
+ struct raop_data *iter = raop_session->raop_list;
+ struct raop_data *prev = NULL;
+
+ g_mutex_lock(raop_session->list_mutex);
+ while (iter) {
+ if (iter == rd) {
+ if (prev != NULL) {
+ prev->next = rd->next;
+ } else {
+ raop_session->raop_list = rd->next;
+ }
+ if (rd->is_master && raop_session->raop_list != NULL) {
+ raop_session->raop_list->is_master = true;
+ }
+ rd->next = NULL;
+ rd->is_master = false;
+ break;
+ }
+ prev = iter;
+ iter = iter->next;
+ }
+ g_mutex_unlock(raop_session->list_mutex);
+
+ if (raop_session->raop_list == NULL) {
+ ntp_server_close(&raop_session->ntp);
+ close(raop_session->ctrl.fd);
+ raop_session->ctrl.fd = -1;
+ }
+}
+
+static void
+raop_output_close(struct audio_output *ao)
+{
+ //teardown
+ struct raop_data *rd = (struct raop_data *)ao;
+
+ raop_output_remove(rd);
+
+ g_mutex_lock(rd->control_mutex);
+ exec_request(rd->rtspcl, "TEARDOWN", NULL, NULL, 0,
+ NULL, NULL, NULL);
+ g_mutex_unlock(rd->control_mutex);
+
+ rd->started = 0;
+}
+
+
+static bool
+raop_output_open(struct audio_output *ao, struct audio_format *audio_format, GError **error_r)
+{
+ //setup, etc.
+ struct raop_data *rd = (struct raop_data *)ao;
+
+ g_mutex_lock(raop_session->list_mutex);
+ if (raop_session->raop_list == NULL) {
+ // first raop, need to initialize session data
+ unsigned short myport = 0;
+ raop_session->raop_list = rd;
+ rd->is_master = true;
+
+ raop_session->data_fd = open_udp_socket(NULL, &myport,
+ error_r);
+ if (raop_session->data_fd < 0)
+ return false;
+
+ if (!ntp_server_open(&raop_session->ntp, error_r))
+ return false;
+
+ raop_session->ctrl.fd =
+ open_udp_socket(NULL, &raop_session->ctrl.port,
+ error_r);
+ if (raop_session->ctrl.fd < 0) {
+ ntp_server_close(&raop_session->ntp);
+ raop_session->ctrl.fd = -1;
+ g_mutex_unlock(raop_session->list_mutex);
+ return false;
+ }
+ }
+ g_mutex_unlock(raop_session->list_mutex);
+
+ audio_format->format = SAMPLE_FORMAT_S16;
+ if (!raopcl_connect(rd, error_r)) {
+ raop_output_remove(rd);
+ return false;
+ }
+
+ if (!raop_set_volume(rd, rd->volume, error_r)) {
+ raop_output_remove(rd);
+ return false;
+ }
+
+ g_mutex_lock(raop_session->list_mutex);
+ if (!rd->is_master) {
+ rd->next = raop_session->raop_list;
+ raop_session->raop_list = rd;
+ }
+ g_mutex_unlock(raop_session->list_mutex);
+ return true;
+}
+
+static size_t
+raop_output_play(struct audio_output *ao, const void *chunk, size_t size,
+ GError **error_r)
+{
+ //raopcl_send_sample
+ struct raop_data *rd = (struct raop_data *)ao;
+ size_t rval = 0, orig_size = size;
+
+ rd->paused = false;
+ if (!rd->is_master) {
+ // only process data for the master raop
+ return size;
+ }
+
+ g_mutex_lock(raop_session->data_mutex);
+
+ if (raop_session->play_state.rtptime <= NUMSAMPLES) {
+ // looped over, need new reference point to calculate correct times
+ raop_session->play_state.playing = false;
+ }
+
+ while (raop_session->bufferSize + size >= RAOP_BUFFER_SIZE) {
+ // ntp header
+ unsigned char header[] = {
+ 0x80, 0x60, 0x00, 0x00,
+ // rtptime
+ 0x00, 0x00, 0x00, 0x00,
+ // device
+ 0x7e, 0xad, 0xd2, 0xd3,
+ };
+
+
+ int count = 0;
+ int copyBytes = RAOP_BUFFER_SIZE - raop_session->bufferSize;
+
+ if (!raop_session->play_state.playing ||
+ raop_session->play_state.seq_num % (44100 / NUMSAMPLES + 1) == 0) {
+ struct raop_data *iter;
+ g_mutex_lock(raop_session->list_mutex);
+ if (!raop_session->play_state.playing) {
+ gettimeofday(&raop_session->play_state.start_time,NULL);
+ }
+ iter = raop_session->raop_list;
+ while (iter) {
+ if (!send_control_command(&raop_session->ctrl, iter,
+ &raop_session->play_state,
+ error_r))
+ goto erexit;
+
+ iter = iter->next;
+ }
+ g_mutex_unlock(raop_session->list_mutex);
+ }
+
+ fill_int(header + 8, raop_session->play_state.sync_src);
+
+ memcpy(raop_session->buffer + raop_session->bufferSize, chunk, copyBytes);
+ raop_session->bufferSize += copyBytes;
+ chunk = ((const char *)chunk) + copyBytes;
+ size -= copyBytes;
+
+ if (!wrap_pcm(raop_session->data + RAOP_HEADER_SIZE, NUMSAMPLES, &count, raop_session->buffer, RAOP_BUFFER_SIZE)) {
+ g_warning("unable to encode %d bytes properly\n", RAOP_BUFFER_SIZE);
+ }
+
+ memcpy(raop_session->data, header, RAOP_HEADER_SIZE);
+ raop_session->data[2] = raop_session->play_state.seq_num >> 8;
+ raop_session->data[3] = raop_session->play_state.seq_num & 0xff;
+ raop_session->play_state.seq_num ++;
+
+ fill_int(raop_session->data + 4, raop_session->play_state.rtptime);
+ raop_session->play_state.rtptime += NUMSAMPLES;
+
+ raop_encrypt(&raop_session->encrypt, raop_session->data + RAOP_HEADER_SIZE, count);
+ raop_session->wblk_remsize = count + RAOP_HEADER_SIZE;
+ raop_session->wblk_wsize = 0;
+
+ if (!send_audio_data(raop_session->data_fd, error_r))
+ goto erexit;
+
+ raop_session->bufferSize = 0;
+ }
+ if (size > 0) {
+ memcpy(raop_session->buffer + raop_session->bufferSize, chunk, size);
+ raop_session->bufferSize += size;
+ }
+ rval = orig_size;
+ erexit:
+ g_mutex_unlock(raop_session->data_mutex);
+ return rval;
+}
+
+const struct audio_output_plugin raop_output_plugin = {
+ .name = "raop",
+ .init = raop_output_init,
+ .finish = raop_output_finish,
+ .open = raop_output_open,
+ .play = raop_output_play,
+ .cancel = raop_output_cancel,
+ .pause = raop_output_pause,
+ .close = raop_output_close,
+ .mixer_plugin = &raop_mixer_plugin,
+};
diff --git a/src/output/raop_output_plugin.h b/src/output/raop_output_plugin.h
new file mode 100644
index 000000000..6aca97836
--- /dev/null
+++ b/src/output/raop_output_plugin.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_RAOP_OUTPUT_PLUGIN_H
+#define MPD_RAOP_OUTPUT_PLUGIN_H
+
+#include <glib.h>
+
+#include <stdbool.h>
+
+struct raop_data;
+
+extern const struct audio_output_plugin raop_output_plugin;
+
+bool
+raop_set_volume(struct raop_data *rd, unsigned volume, GError **error_r);
+
+int
+raop_get_volume(struct raop_data *rd);
+
+#endif
diff --git a/src/output/recorder_output_plugin.c b/src/output/recorder_output_plugin.c
index 2f088a107..ea299468b 100644
--- a/src/output/recorder_output_plugin.c
+++ b/src/output/recorder_output_plugin.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -18,6 +18,7 @@
*/
#include "config.h"
+#include "recorder_output_plugin.h"
#include "output_api.h"
#include "encoder_plugin.h"
#include "encoder_list.h"
@@ -34,6 +35,8 @@
#define G_LOG_DOMAIN "recorder"
struct recorder_output {
+ struct audio_output base;
+
/**
* The configured encoder plugin.
*/
@@ -64,11 +67,16 @@ recorder_output_quark(void)
return g_quark_from_static_string("recorder_output");
}
-static void *
-recorder_output_init(G_GNUC_UNUSED const struct audio_format *audio_format,
- const struct config_param *param, GError **error_r)
+static struct audio_output *
+recorder_output_init(const struct config_param *param, GError **error_r)
{
struct recorder_output *recorder = g_new(struct recorder_output, 1);
+ if (!ao_base_init(&recorder->base, &recorder_output_plugin, param,
+ error_r)) {
+ g_free(recorder);
+ return NULL;
+ }
+
const char *encoder_name;
const struct encoder_plugin *encoder_plugin;
@@ -95,19 +103,21 @@ recorder_output_init(G_GNUC_UNUSED const struct audio_format *audio_format,
if (recorder->encoder == NULL)
goto failure;
- return recorder;
+ return &recorder->base;
failure:
+ ao_base_finish(&recorder->base);
g_free(recorder);
return NULL;
}
static void
-recorder_output_finish(void *data)
+recorder_output_finish(struct audio_output *ao)
{
- struct recorder_output *recorder = data;
+ struct recorder_output *recorder = (struct recorder_output *)ao;
encoder_finish(recorder->encoder);
+ ao_base_finish(&recorder->base);
g_free(recorder);
}
@@ -154,10 +164,11 @@ recorder_output_encoder_to_file(struct recorder_output *recorder,
}
static bool
-recorder_output_open(void *data, struct audio_format *audio_format,
+recorder_output_open(struct audio_output *ao,
+ struct audio_format *audio_format,
GError **error_r)
{
- struct recorder_output *recorder = data;
+ struct recorder_output *recorder = (struct recorder_output *)ao;
bool success;
/* create the output file */
@@ -185,9 +196,9 @@ recorder_output_open(void *data, struct audio_format *audio_format,
}
static void
-recorder_output_close(void *data)
+recorder_output_close(struct audio_output *ao)
{
- struct recorder_output *recorder = data;
+ struct recorder_output *recorder = (struct recorder_output *)ao;
/* flush the encoder and write the rest to the file */
@@ -202,10 +213,10 @@ recorder_output_close(void *data)
}
static size_t
-recorder_output_play(void *data, const void *chunk, size_t size,
+recorder_output_play(struct audio_output *ao, const void *chunk, size_t size,
GError **error_r)
{
- struct recorder_output *recorder = data;
+ struct recorder_output *recorder = (struct recorder_output *)ao;
return encoder_write(recorder->encoder, chunk, size, error_r) &&
recorder_output_encoder_to_file(recorder, error_r)
diff --git a/src/output/recorder_output_plugin.h b/src/output/recorder_output_plugin.h
new file mode 100644
index 000000000..a9bf755bd
--- /dev/null
+++ b/src/output/recorder_output_plugin.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_RECORDER_OUTPUT_PLUGIN_H
+#define MPD_RECORDER_OUTPUT_PLUGIN_H
+
+extern const struct audio_output_plugin recorder_output_plugin;
+
+#endif
diff --git a/src/output/roar_output_plugin.c b/src/output/roar_output_plugin.c
new file mode 100644
index 000000000..1c2c48321
--- /dev/null
+++ b/src/output/roar_output_plugin.c
@@ -0,0 +1,401 @@
+/*
+ * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2010-2011 Philipp 'ph3-der-loewe' Schafft
+ * Copyright (C) 2010-2011 Hans-Kristian 'maister' Arntzen
+ *
+ * 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 "roar_output_plugin.h"
+#include "output_api.h"
+#include "mixer_list.h"
+#include "roar_output_plugin.h"
+
+#include <glib.h>
+#include <stdint.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdint.h>
+
+#include <roaraudio.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "roaraudio"
+
+typedef struct roar
+{
+ struct audio_output base;
+
+ roar_vs_t * vss;
+ int err;
+ char *host;
+ char *name;
+ int role;
+ struct roar_connection con;
+ struct roar_audio_info info;
+ GMutex *lock;
+ volatile bool alive;
+} roar_t;
+
+static inline GQuark
+roar_output_quark(void)
+{
+ return g_quark_from_static_string("roar_output");
+}
+
+static int
+roar_output_get_volume_locked(struct roar *roar)
+{
+ if (roar->vss == NULL || !roar->alive)
+ return -1;
+
+ float l, r;
+ int error;
+ if (roar_vs_volume_get(roar->vss, &l, &r, &error) < 0)
+ return -1;
+
+ return (l + r) * 50;
+}
+
+int
+roar_output_get_volume(struct roar *roar)
+{
+ g_mutex_lock(roar->lock);
+ int volume = roar_output_get_volume_locked(roar);
+ g_mutex_unlock(roar->lock);
+ return volume;
+}
+
+static bool
+roar_output_set_volume_locked(struct roar *roar, unsigned volume)
+{
+ assert(volume <= 100);
+
+ if (roar->vss == NULL || !roar->alive)
+ return false;
+
+ int error;
+ float level = volume / 100.0;
+
+ roar_vs_volume_mono(roar->vss, level, &error);
+ return true;
+}
+
+bool
+roar_output_set_volume(struct roar *roar, unsigned volume)
+{
+ g_mutex_lock(roar->lock);
+ bool success = roar_output_set_volume_locked(roar, volume);
+ g_mutex_unlock(roar->lock);
+ return success;
+}
+
+static void
+roar_configure(struct roar * self, const struct config_param *param)
+{
+ self->host = config_dup_block_string(param, "server", NULL);
+ self->name = config_dup_block_string(param, "name", "MPD");
+
+ const char *role = config_get_block_string(param, "role", "music");
+ self->role = role != NULL
+ ? roar_str2role(role)
+ : ROAR_ROLE_MUSIC;
+}
+
+static struct audio_output *
+roar_init(const struct config_param *param, GError **error_r)
+{
+ struct roar *self = g_new0(struct roar, 1);
+
+ if (!ao_base_init(&self->base, &roar_output_plugin, param, error_r)) {
+ g_free(self);
+ return NULL;
+ }
+
+ self->lock = g_mutex_new();
+ self->err = ROAR_ERROR_NONE;
+ roar_configure(self, param);
+ return &self->base;
+}
+
+static void
+roar_finish(struct audio_output *ao)
+{
+ struct roar *self = (struct roar *)ao;
+
+ g_free(self->host);
+ g_free(self->name);
+ g_mutex_free(self->lock);
+
+ ao_base_finish(&self->base);
+ g_free(self);
+}
+
+static void
+roar_use_audio_format(struct roar_audio_info *info,
+ struct audio_format *audio_format)
+{
+ info->rate = audio_format->sample_rate;
+ info->channels = audio_format->channels;
+ info->codec = ROAR_CODEC_PCM_S;
+
+ switch (audio_format->format) {
+ case SAMPLE_FORMAT_UNDEFINED:
+ info->bits = 16;
+ audio_format->format = SAMPLE_FORMAT_S16;
+ break;
+
+ case SAMPLE_FORMAT_S8:
+ info->bits = 8;
+ break;
+
+ case SAMPLE_FORMAT_S16:
+ info->bits = 16;
+ break;
+
+ case SAMPLE_FORMAT_S24_P32:
+ info->bits = 32;
+ audio_format->format = SAMPLE_FORMAT_S32;
+ break;
+
+ case SAMPLE_FORMAT_S32:
+ info->bits = 32;
+ break;
+ }
+}
+
+static bool
+roar_open(struct audio_output *ao, struct audio_format *audio_format, GError **error)
+{
+ struct roar *self = (struct roar *)ao;
+ g_mutex_lock(self->lock);
+
+ if (roar_simple_connect(&(self->con), self->host, self->name) < 0)
+ {
+ g_set_error(error, roar_output_quark(), 0,
+ "Failed to connect to Roar server");
+ g_mutex_unlock(self->lock);
+ return false;
+ }
+
+ self->vss = roar_vs_new_from_con(&(self->con), &(self->err));
+
+ if (self->vss == NULL || self->err != ROAR_ERROR_NONE)
+ {
+ g_set_error(error, roar_output_quark(), 0,
+ "Failed to connect to server");
+ g_mutex_unlock(self->lock);
+ return false;
+ }
+
+ roar_use_audio_format(&self->info, audio_format);
+
+ if (roar_vs_stream(self->vss, &(self->info), ROAR_DIR_PLAY,
+ &(self->err)) < 0)
+ {
+ g_set_error(error, roar_output_quark(), 0, "Failed to start stream");
+ g_mutex_unlock(self->lock);
+ return false;
+ }
+ roar_vs_role(self->vss, self->role, &(self->err));
+ self->alive = true;
+
+ g_mutex_unlock(self->lock);
+ return true;
+}
+
+static void
+roar_close(struct audio_output *ao)
+{
+ struct roar *self = (struct roar *)ao;
+ g_mutex_lock(self->lock);
+ self->alive = false;
+
+ if (self->vss != NULL)
+ roar_vs_close(self->vss, ROAR_VS_TRUE, &(self->err));
+ self->vss = NULL;
+ roar_disconnect(&(self->con));
+ g_mutex_unlock(self->lock);
+}
+
+static void
+roar_cancel_locked(struct roar *self)
+{
+ if (self->vss == NULL)
+ return;
+
+ roar_vs_t *vss = self->vss;
+ self->vss = NULL;
+ roar_vs_close(vss, ROAR_VS_TRUE, &(self->err));
+ self->alive = false;
+
+ vss = roar_vs_new_from_con(&(self->con), &(self->err));
+ if (vss == NULL)
+ return;
+
+ if (roar_vs_stream(vss, &(self->info), ROAR_DIR_PLAY,
+ &(self->err)) < 0) {
+ roar_vs_close(vss, ROAR_VS_TRUE, &(self->err));
+ g_warning("Failed to start stream");
+ return;
+ }
+
+ roar_vs_role(vss, self->role, &(self->err));
+ self->vss = vss;
+ self->alive = true;
+}
+
+static void
+roar_cancel(struct audio_output *ao)
+{
+ struct roar *self = (struct roar *)ao;
+
+ g_mutex_lock(self->lock);
+ roar_cancel_locked(self);
+ g_mutex_unlock(self->lock);
+}
+
+static size_t
+roar_play(struct audio_output *ao, const void *chunk, size_t size, GError **error)
+{
+ struct roar *self = (struct roar *)ao;
+ ssize_t rc;
+
+ if (self->vss == NULL)
+ {
+ g_set_error(error, roar_output_quark(), 0, "Connection is invalid");
+ return 0;
+ }
+
+ rc = roar_vs_write(self->vss, chunk, size, &(self->err));
+ if ( rc <= 0 )
+ {
+ g_set_error(error, roar_output_quark(), 0, "Failed to play data");
+ return 0;
+ }
+
+ return rc;
+}
+
+static const char*
+roar_tag_convert(enum tag_type type, bool *is_uuid)
+{
+ *is_uuid = false;
+ switch (type)
+ {
+ case TAG_ARTIST:
+ case TAG_ALBUM_ARTIST:
+ return "AUTHOR";
+ case TAG_ALBUM:
+ return "ALBUM";
+ case TAG_TITLE:
+ return "TITLE";
+ case TAG_TRACK:
+ return "TRACK";
+ case TAG_NAME:
+ return "NAME";
+ case TAG_GENRE:
+ return "GENRE";
+ case TAG_DATE:
+ return "DATE";
+ case TAG_PERFORMER:
+ return "PERFORMER";
+ case TAG_COMMENT:
+ return "COMMENT";
+ case TAG_DISC:
+ return "DISCID";
+ case TAG_COMPOSER:
+#ifdef ROAR_META_TYPE_COMPOSER
+ return "COMPOSER";
+#else
+ return "AUTHOR";
+#endif
+ case TAG_MUSICBRAINZ_ARTISTID:
+ case TAG_MUSICBRAINZ_ALBUMID:
+ case TAG_MUSICBRAINZ_ALBUMARTISTID:
+ case TAG_MUSICBRAINZ_TRACKID:
+ *is_uuid = true;
+ return "HASH";
+
+ default:
+ return NULL;
+ }
+}
+
+static void
+roar_send_tag(struct audio_output *ao, const struct tag *meta)
+{
+ struct roar *self = (struct roar *)ao;
+
+ if (self->vss == NULL)
+ return;
+
+ g_mutex_lock(self->lock);
+ size_t cnt = 1;
+ struct roar_keyval vals[32];
+ memset(vals, 0, sizeof(vals));
+ char uuid_buf[32][64];
+
+ char timebuf[16];
+ snprintf(timebuf, sizeof(timebuf), "%02d:%02d:%02d",
+ meta->time / 3600, (meta->time % 3600) / 60, meta->time % 60);
+
+ vals[0].key = g_strdup("LENGTH");
+ vals[0].value = timebuf;
+
+ for (unsigned i = 0; i < meta->num_items && cnt < 32; i++)
+ {
+ bool is_uuid = false;
+ const char *key = roar_tag_convert(meta->items[i]->type, &is_uuid);
+ if (key != NULL)
+ {
+ if (is_uuid)
+ {
+ snprintf(uuid_buf[cnt], sizeof(uuid_buf[0]), "{UUID}%s",
+ meta->items[i]->value);
+ vals[cnt].key = g_strdup(key);
+ vals[cnt].value = uuid_buf[cnt];
+ }
+ else
+ {
+ vals[cnt].key = g_strdup(key);
+ vals[cnt].value = meta->items[i]->value;
+ }
+ cnt++;
+ }
+ }
+
+ roar_vs_meta(self->vss, vals, cnt, &(self->err));
+
+ for (unsigned i = 0; i < 32; i++)
+ g_free(vals[i].key);
+
+ g_mutex_unlock(self->lock);
+}
+
+const struct audio_output_plugin roar_output_plugin = {
+ .name = "roar",
+ .init = roar_init,
+ .finish = roar_finish,
+ .open = roar_open,
+ .play = roar_play,
+ .cancel = roar_cancel,
+ .close = roar_close,
+ .send_tag = roar_send_tag,
+
+ .mixer_plugin = &roar_mixer_plugin
+};
diff --git a/src/output/roar_output_plugin.h b/src/output/roar_output_plugin.h
new file mode 100644
index 000000000..78b628cc4
--- /dev/null
+++ b/src/output/roar_output_plugin.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_ROAR_OUTPUT_PLUGIN_H
+#define MPD_ROAR_OUTPUT_PLUGIN_H
+
+#include <stdbool.h>
+
+struct roar;
+
+extern const struct audio_output_plugin roar_output_plugin;
+
+int
+roar_output_get_volume(struct roar *roar);
+
+bool
+roar_output_set_volume(struct roar *roar, unsigned volume);
+
+#endif
diff --git a/src/output/shout_plugin.c b/src/output/shout_output_plugin.c
index 27ef3b993..7867ae63c 100644
--- a/src/output/shout_plugin.c
+++ b/src/output/shout_output_plugin.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -18,6 +18,7 @@
*/
#include "config.h"
+#include "shout_output_plugin.h"
#include "output_api.h"
#include "encoder_plugin.h"
#include "encoder_list.h"
@@ -41,6 +42,8 @@ struct shout_buffer {
};
struct shout_data {
+ struct audio_output base;
+
shout_t *shout_conn;
shout_metadata_t *shout_meta;
@@ -107,9 +110,8 @@ static void free_shout_data(struct shout_data *sd)
} \
}
-static void *
-my_shout_init_driver(const struct audio_format *audio_format,
- const struct config_param *param,
+static struct audio_output *
+my_shout_init_driver(const struct config_param *param,
GError **error)
{
struct shout_data *sd;
@@ -125,18 +127,26 @@ my_shout_init_driver(const struct audio_format *audio_format,
const char *user;
char *name;
const char *value;
- struct block_param *block_param;
+ const struct block_param *block_param;
int public;
- if (audio_format == NULL ||
- !audio_format_fully_defined(audio_format)) {
+ sd = new_shout_data();
+
+ if (!ao_base_init(&sd->base, &shout_output_plugin, param, error)) {
+ free_shout_data(sd);
+ return NULL;
+ }
+
+ const struct audio_format *audio_format =
+ &sd->base.config_audio_format;
+ if (!audio_format_fully_defined(audio_format)) {
g_set_error(error, shout_output_quark(), 0,
"Need full audio format specification");
+ ao_base_finish(&sd->base);
+ free_shout_data(sd);
return NULL;
}
- sd = new_shout_data();
-
if (shout_init_count == 0)
shout_init();
@@ -277,6 +287,13 @@ my_shout_init_driver(const struct audio_format *audio_format,
goto failure;
}
+ value = config_get_block_string(param, "url", NULL);
+ if (value != NULL && shout_set_url(sd->shout_conn, value)) {
+ g_set_error(error, shout_output_quark(), 0,
+ "%s", shout_get_error(sd->shout_conn));
+ goto failure;
+ }
+
{
char temp[11];
memset(temp, 0, sizeof(temp));
@@ -299,9 +316,10 @@ my_shout_init_driver(const struct audio_format *audio_format,
}
}
- return sd;
+ return &sd->base;
failure:
+ ao_base_finish(&sd->base);
free_shout_data(sd);
return NULL;
}
@@ -371,12 +389,14 @@ static void close_shout_conn(struct shout_data * sd)
}
}
-static void my_shout_finish_driver(void *data)
+static void
+my_shout_finish_driver(struct audio_output *ao)
{
- struct shout_data *sd = (struct shout_data *)data;
+ struct shout_data *sd = (struct shout_data *)ao;
encoder_finish(sd->encoder);
+ ao_base_finish(&sd->base);
free_shout_data(sd);
shout_init_count--;
@@ -385,17 +405,19 @@ static void my_shout_finish_driver(void *data)
shout_shutdown();
}
-static void my_shout_drop_buffered_audio(void *data)
+static void
+my_shout_drop_buffered_audio(struct audio_output *ao)
{
G_GNUC_UNUSED
- struct shout_data *sd = (struct shout_data *)data;
+ struct shout_data *sd = (struct shout_data *)ao;
/* needs to be implemented for shout */
}
-static void my_shout_close_device(void *data)
+static void
+my_shout_close_device(struct audio_output *ao)
{
- struct shout_data *sd = (struct shout_data *)data;
+ struct shout_data *sd = (struct shout_data *)ao;
close_shout_conn(sd);
}
@@ -422,10 +444,10 @@ shout_connect(struct shout_data *sd, GError **error)
}
static bool
-my_shout_open_device(void *data, struct audio_format *audio_format,
+my_shout_open_device(struct audio_output *ao, struct audio_format *audio_format,
GError **error)
{
- struct shout_data *sd = (struct shout_data *)data;
+ struct shout_data *sd = (struct shout_data *)ao;
bool ret;
ret = shout_connect(sd, error);
@@ -445,9 +467,9 @@ my_shout_open_device(void *data, struct audio_format *audio_format,
}
static unsigned
-my_shout_delay(void *data)
+my_shout_delay(struct audio_output *ao)
{
- struct shout_data *sd = (struct shout_data *)data;
+ struct shout_data *sd = (struct shout_data *)ao;
int delay = shout_delay(sd->shout_conn);
if (delay < 0)
@@ -457,9 +479,10 @@ my_shout_delay(void *data)
}
static size_t
-my_shout_play(void *data, const void *chunk, size_t size, GError **error)
+my_shout_play(struct audio_output *ao, const void *chunk, size_t size,
+ GError **error)
{
- struct shout_data *sd = (struct shout_data *)data;
+ struct shout_data *sd = (struct shout_data *)ao;
return encoder_write(sd->encoder, chunk, size, error) &&
write_page(sd, error)
@@ -468,11 +491,11 @@ my_shout_play(void *data, const void *chunk, size_t size, GError **error)
}
static bool
-my_shout_pause(void *data)
+my_shout_pause(struct audio_output *ao)
{
static const char silence[1020];
- return my_shout_play(data, silence, sizeof(silence), NULL);
+ return my_shout_play(ao, silence, sizeof(silence), NULL);
}
static void
@@ -501,10 +524,10 @@ shout_tag_to_metadata(const struct tag *tag, char *dest, size_t size)
snprintf(dest, size, "%s - %s", artist, title);
}
-static void my_shout_set_tag(void *data,
+static void my_shout_set_tag(struct audio_output *ao,
const struct tag *tag)
{
- struct shout_data *sd = (struct shout_data *)data;
+ struct shout_data *sd = (struct shout_data *)ao;
bool ret;
GError *error = NULL;
@@ -543,7 +566,7 @@ static void my_shout_set_tag(void *data,
write_page(sd, NULL);
}
-const struct audio_output_plugin shoutPlugin = {
+const struct audio_output_plugin shout_output_plugin = {
.name = "shout",
.init = my_shout_init_driver,
.finish = my_shout_finish_driver,
diff --git a/src/output/shout_output_plugin.h b/src/output/shout_output_plugin.h
new file mode 100644
index 000000000..9a7378803
--- /dev/null
+++ b/src/output/shout_output_plugin.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_SHOUT_OUTPUT_PLUGIN_H
+#define MPD_SHOUT_OUTPUT_PLUGIN_H
+
+extern const struct audio_output_plugin shout_output_plugin;
+
+#endif
diff --git a/src/output/solaris_output_plugin.c b/src/output/solaris_output_plugin.c
index 6f6f507b3..ce726009a 100644
--- a/src/output/solaris_output_plugin.c
+++ b/src/output/solaris_output_plugin.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -18,6 +18,7 @@
*/
#include "config.h"
+#include "solaris_output_plugin.h"
#include "output_api.h"
#include "fd_util.h"
@@ -53,6 +54,8 @@ struct audio_info {
#define G_LOG_DOMAIN "solaris_output"
struct solaris_output {
+ struct audio_output base;
+
/* configuration */
const char *device;
@@ -77,31 +80,35 @@ solaris_output_test_default_device(void)
access("/dev/audio", W_OK) == 0;
}
-static void *
-solaris_output_init(G_GNUC_UNUSED const struct audio_format *audio_format,
- const struct config_param *param,
- G_GNUC_UNUSED GError **error)
+static struct audio_output *
+solaris_output_init(const struct config_param *param, GError **error_r)
{
struct solaris_output *so = g_new(struct solaris_output, 1);
+ if (!ao_base_init(&so->base, &solaris_output_plugin, param, error_r)) {
+ g_free(so);
+ return NULL;
+ }
+
so->device = config_get_block_string(param, "device", "/dev/audio");
- return so;
+ return &so->base;
}
static void
-solaris_output_finish(void *data)
+solaris_output_finish(struct audio_output *ao)
{
- struct solaris_output *so = data;
+ struct solaris_output *so = (struct solaris_output *)ao;
+ ao_base_finish(&so->base);
g_free(so);
}
static bool
-solaris_output_open(void *data, struct audio_format *audio_format,
+solaris_output_open(struct audio_output *ao, struct audio_format *audio_format,
GError **error)
{
- struct solaris_output *so = data;
+ struct solaris_output *so = (struct solaris_output *)ao;
struct audio_info info;
int ret, flags;
@@ -152,17 +159,18 @@ solaris_output_open(void *data, struct audio_format *audio_format,
}
static void
-solaris_output_close(void *data)
+solaris_output_close(struct audio_output *ao)
{
- struct solaris_output *so = data;
+ struct solaris_output *so = (struct solaris_output *)ao;
close(so->fd);
}
static size_t
-solaris_output_play(void *data, const void *chunk, size_t size, GError **error)
+solaris_output_play(struct audio_output *ao, const void *chunk, size_t size,
+ GError **error)
{
- struct solaris_output *so = data;
+ struct solaris_output *so = (struct solaris_output *)ao;
ssize_t nbytes;
nbytes = write(so->fd, chunk, size);
@@ -176,9 +184,9 @@ solaris_output_play(void *data, const void *chunk, size_t size, GError **error)
}
static void
-solaris_output_cancel(void *data)
+solaris_output_cancel(struct audio_output *ao)
{
- struct solaris_output *so = data;
+ struct solaris_output *so = (struct solaris_output *)ao;
ioctl(so->fd, I_FLUSH);
}
diff --git a/src/output/solaris_output_plugin.h b/src/output/solaris_output_plugin.h
new file mode 100644
index 000000000..600aea8c2
--- /dev/null
+++ b/src/output/solaris_output_plugin.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_SOLARIS_OUTPUT_PLUGIN_H
+#define MPD_SOLARIS_OUTPUT_PLUGIN_H
+
+extern const struct audio_output_plugin solaris_output_plugin;
+
+#endif
diff --git a/src/output/winmm_output_plugin.c b/src/output/winmm_output_plugin.c
index 4312c635e..4d95834b9 100644
--- a/src/output/winmm_output_plugin.c
+++ b/src/output/winmm_output_plugin.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -18,6 +18,7 @@
*/
#include "config.h"
+#include "winmm_output_plugin.h"
#include "output_api.h"
#include "pcm_buffer.h"
#include "mixer_list.h"
@@ -37,6 +38,8 @@ struct winmm_buffer {
};
struct winmm_output {
+ struct audio_output base;
+
UINT device_id;
HWAVEOUT handle;
@@ -71,59 +74,80 @@ winmm_output_test_default_device(void)
return waveOutGetNumDevs() > 0;
}
-static UINT
-get_device_id(const char *device_name)
+static bool
+get_device_id(const char *device_name, UINT *device_id, GError **error_r)
{
/* if device is not specified use wave mapper */
- if (device_name == NULL)
- return WAVE_MAPPER;
+ if (device_name == NULL) {
+ *device_id = WAVE_MAPPER;
+ return true;
+ }
+
+ UINT numdevs = waveOutGetNumDevs();
/* check for device id */
char *endptr;
UINT id = strtoul(device_name, &endptr, 0);
- if (endptr > device_name && *endptr == 0)
- return id;
+ if (endptr > device_name && *endptr == 0) {
+ if (id >= numdevs)
+ goto fail;
+ *device_id = id;
+ return true;
+ }
/* check for device name */
- for (UINT i = 0; i < waveOutGetNumDevs(); i++) {
+ for (UINT i = 0; i < numdevs; 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;
+ if (strstr(device_name, caps.szPname) == device_name) {
+ *device_id = i;
+ return true;
+ }
}
- /* fallback to wave mapper */
- return WAVE_MAPPER;
+fail:
+ g_set_error(error_r, winmm_output_quark(), 0,
+ "device \"%s\" is not found", device_name);
+ return false;
}
-static void *
-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)
+static struct audio_output *
+winmm_output_init(const struct config_param *param, GError **error_r)
{
struct winmm_output *wo = g_new(struct winmm_output, 1);
+ if (!ao_base_init(&wo->base, &winmm_output_plugin, param, error_r)) {
+ g_free(wo);
+ return NULL;
+ }
+
const char *device = config_get_block_string(param, "device", NULL);
- wo->device_id = get_device_id(device);
- return wo;
+ if (!get_device_id(device, &wo->device_id, error_r)) {
+ ao_base_finish(&wo->base);
+ g_free(wo);
+ return NULL;
+ }
+
+ return &wo->base;
}
static void
-winmm_output_finish(void *data)
+winmm_output_finish(struct audio_output *ao)
{
- struct winmm_output *wo = data;
+ struct winmm_output *wo = (struct winmm_output *)ao;
+ ao_base_finish(&wo->base);
g_free(wo);
}
static bool
-winmm_output_open(void *data, struct audio_format *audio_format,
+winmm_output_open(struct audio_output *ao, struct audio_format *audio_format,
GError **error_r)
{
- struct winmm_output *wo = data;
+ struct winmm_output *wo = (struct winmm_output *)ao;
wo->event = CreateEvent(NULL, false, false, NULL);
if (wo->event == NULL) {
@@ -137,7 +161,6 @@ winmm_output_open(void *data, struct audio_format *audio_format,
case SAMPLE_FORMAT_S16:
break;
- case SAMPLE_FORMAT_S24:
case SAMPLE_FORMAT_S24_P32:
case SAMPLE_FORMAT_S32:
case SAMPLE_FORMAT_UNDEFINED:
@@ -179,9 +202,9 @@ winmm_output_open(void *data, struct audio_format *audio_format,
}
static void
-winmm_output_close(void *data)
+winmm_output_close(struct audio_output *ao)
{
- struct winmm_output *wo = data;
+ struct winmm_output *wo = (struct winmm_output *)ao;
for (unsigned i = 0; i < G_N_ELEMENTS(wo->buffers); ++i)
pcm_buffer_deinit(&wo->buffers[i].buffer);
@@ -248,9 +271,9 @@ winmm_drain_buffer(struct winmm_output *wo, struct winmm_buffer *buffer,
}
static size_t
-winmm_output_play(void *data, const void *chunk, size_t size, GError **error_r)
+winmm_output_play(struct audio_output *ao, const void *chunk, size_t size, GError **error_r)
{
- struct winmm_output *wo = data;
+ struct winmm_output *wo = (struct winmm_output *)ao;
/* get the next buffer from the ring and prepare it */
struct winmm_buffer *buffer = &wo->buffers[wo->next_buffer];
@@ -303,18 +326,18 @@ winmm_stop(struct winmm_output *wo)
}
static void
-winmm_output_drain(void *data)
+winmm_output_drain(struct audio_output *ao)
{
- struct winmm_output *wo = data;
+ struct winmm_output *wo = (struct winmm_output *)ao;
if (!winmm_drain_all_buffers(wo, NULL))
winmm_stop(wo);
}
static void
-winmm_output_cancel(void *data)
+winmm_output_cancel(struct audio_output *ao)
{
- struct winmm_output *wo = data;
+ struct winmm_output *wo = (struct winmm_output *)ao;
winmm_stop(wo);
}
diff --git a/src/output/winmm_output_plugin.h b/src/output/winmm_output_plugin.h
index 39507275a..0605530e1 100644
--- a/src/output/winmm_output_plugin.h
+++ b/src/output/winmm_output_plugin.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -20,10 +20,18 @@
#ifndef MPD_WINMM_OUTPUT_PLUGIN_H
#define MPD_WINMM_OUTPUT_PLUGIN_H
+#include "check.h"
+
+#ifdef ENABLE_WINMM_OUTPUT
+
#include <windows.h>
struct winmm_output;
+extern const struct audio_output_plugin winmm_output_plugin;
+
HWAVEOUT winmm_output_get_handle(struct winmm_output*);
#endif
+
+#endif
diff --git a/src/output_all.c b/src/output_all.c
index 4e0b2eb22..f56cd04ee 100644
--- a/src/output_all.c
+++ b/src/output_all.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -27,6 +27,7 @@
#include "buffer.h"
#include "player_control.h"
#include "mpd_error.h"
+#include "notify.h"
#ifndef NDEBUG
#include "chunk.h"
@@ -40,7 +41,7 @@
static struct audio_format input_audio_format;
-static struct audio_output *audio_outputs;
+static struct audio_output **audio_outputs;
static unsigned int num_audio_outputs;
/**
@@ -69,7 +70,9 @@ audio_output_get(unsigned i)
{
assert(i < num_audio_outputs);
- return &audio_outputs[i];
+ assert(audio_outputs[i] != NULL);
+
+ return audio_outputs[i];
}
struct audio_output *
@@ -100,7 +103,7 @@ audio_output_config_count(void)
}
void
-audio_output_all_init(void)
+audio_output_all_init(struct player_control *pc)
{
const struct config_param *param = NULL;
unsigned int i;
@@ -109,11 +112,10 @@ audio_output_all_init(void)
notify_init(&audio_output_client_notify);
num_audio_outputs = audio_output_config_count();
- audio_outputs = g_new(struct audio_output, num_audio_outputs);
+ audio_outputs = g_new(struct audio_output *, num_audio_outputs);
for (i = 0; i < num_audio_outputs; i++)
{
- struct audio_output *output = &audio_outputs[i];
unsigned int j;
param = config_get_next_param(CONF_AUDIO_OUTPUT, param);
@@ -121,7 +123,8 @@ audio_output_all_init(void)
/* only allow param to be NULL if there just one audioOutput */
assert(param || (num_audio_outputs == 1));
- if (!audio_output_init(output, param, &error)) {
+ struct audio_output *output = audio_output_new(param, pc, &error);
+ if (output == NULL) {
if (param != NULL)
MPD_ERROR("line %i: %s",
param->line, error->message);
@@ -129,9 +132,11 @@ audio_output_all_init(void)
MPD_ERROR("%s", error->message);
}
+ audio_outputs[i] = output;
+
/* require output names to be unique: */
for (j = 0; j < i; j++) {
- if (!strcmp(output->name, audio_outputs[j].name)) {
+ if (!strcmp(output->name, audio_outputs[j]->name)) {
MPD_ERROR("output devices with identical "
"names: %s\n", output->name);
}
@@ -145,8 +150,8 @@ 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]);
+ audio_output_disable(audio_outputs[i]);
+ audio_output_finish(audio_outputs[i]);
}
g_free(audio_outputs);
@@ -160,7 +165,7 @@ void
audio_output_all_enable_disable(void)
{
for (unsigned i = 0; i < num_audio_outputs; i++) {
- struct audio_output *ao = &audio_outputs[i];
+ struct audio_output *ao = audio_outputs[i];
bool enabled;
g_mutex_lock(ao->mutex);
@@ -184,7 +189,7 @@ static bool
audio_output_all_finished(void)
{
for (unsigned i = 0; i < num_audio_outputs; ++i) {
- struct audio_output *ao = &audio_outputs[i];
+ struct audio_output *ao = audio_outputs[i];
bool not_finished;
g_mutex_lock(ao->mutex);
@@ -212,7 +217,7 @@ static void
audio_output_allow_play_all(void)
{
for (unsigned i = 0; i < num_audio_outputs; ++i)
- audio_output_allow_play(&audio_outputs[i]);
+ audio_output_allow_play(audio_outputs[i]);
}
static void
@@ -237,7 +242,7 @@ static void
audio_output_all_reset_reopen(void)
{
for (unsigned i = 0; i < num_audio_outputs; ++i) {
- struct audio_output *ao = &audio_outputs[i];
+ struct audio_output *ao = audio_outputs[i];
audio_output_reset_reopen(ao);
}
@@ -258,7 +263,7 @@ audio_output_all_update(void)
return false;
for (i = 0; i < num_audio_outputs; ++i)
- ret = audio_output_update(&audio_outputs[i],
+ ret = audio_output_update(audio_outputs[i],
&input_audio_format, g_mp) || ret;
return ret;
@@ -282,7 +287,7 @@ audio_output_all_play(struct music_chunk *chunk)
music_pipe_push(g_mp, chunk);
for (i = 0; i < num_audio_outputs; ++i)
- audio_output_play(&audio_outputs[i]);
+ audio_output_play(audio_outputs[i]);
return true;
}
@@ -321,10 +326,10 @@ audio_output_all_open(const struct audio_format *audio_format,
audio_output_all_update();
for (i = 0; i < num_audio_outputs; ++i) {
- if (audio_outputs[i].enabled)
+ if (audio_outputs[i]->enabled)
enabled = true;
- if (audio_outputs[i].open)
+ if (audio_outputs[i]->open)
ret = true;
}
@@ -368,7 +373,7 @@ static bool
chunk_is_consumed(const struct music_chunk *chunk)
{
for (unsigned i = 0; i < num_audio_outputs; ++i) {
- const struct audio_output *ao = &audio_outputs[i];
+ const struct audio_output *ao = audio_outputs[i];
bool consumed;
g_mutex_lock(ao->mutex);
@@ -393,7 +398,7 @@ clear_tail_chunk(G_GNUC_UNUSED const struct music_chunk *chunk, bool *locked)
assert(music_pipe_contains(g_mp, chunk));
for (unsigned i = 0; i < num_audio_outputs; ++i) {
- struct audio_output *ao = &audio_outputs[i];
+ struct audio_output *ao = audio_outputs[i];
/* this mutex will be unlocked by the caller when it's
ready */
@@ -450,7 +455,7 @@ audio_output_all_check(void)
by clear_tail_chunk() */
for (unsigned i = 0; i < num_audio_outputs; ++i)
if (locked[i])
- g_mutex_unlock(audio_outputs[i].mutex);
+ g_mutex_unlock(audio_outputs[i]->mutex);
/* return the chunk to the buffer */
music_buffer_return(g_music_buffer, shifted);
@@ -460,17 +465,17 @@ audio_output_all_check(void)
}
bool
-audio_output_all_wait(unsigned threshold)
+audio_output_all_wait(struct player_control *pc, unsigned threshold)
{
- player_lock();
+ player_lock(pc);
if (audio_output_all_check() < threshold) {
- player_unlock();
+ player_unlock(pc);
return true;
}
- player_wait();
- player_unlock();
+ player_wait(pc);
+ player_unlock(pc);
return audio_output_all_check() < threshold;
}
@@ -483,7 +488,7 @@ audio_output_all_pause(void)
audio_output_all_update();
for (i = 0; i < num_audio_outputs; ++i)
- audio_output_pause(&audio_outputs[i]);
+ audio_output_pause(audio_outputs[i]);
audio_output_wait_all();
}
@@ -492,7 +497,7 @@ void
audio_output_all_drain(void)
{
for (unsigned i = 0; i < num_audio_outputs; ++i)
- audio_output_drain_async(&audio_outputs[i]);
+ audio_output_drain_async(audio_outputs[i]);
audio_output_wait_all();
}
@@ -505,7 +510,7 @@ audio_output_all_cancel(void)
/* send the cancel() command to all audio outputs */
for (i = 0; i < num_audio_outputs; ++i)
- audio_output_cancel(&audio_outputs[i]);
+ audio_output_cancel(audio_outputs[i]);
audio_output_wait_all();
@@ -530,7 +535,7 @@ audio_output_all_close(void)
unsigned int i;
for (i = 0; i < num_audio_outputs; ++i)
- audio_output_close(&audio_outputs[i]);
+ audio_output_close(audio_outputs[i]);
if (g_mp != NULL) {
assert(g_music_buffer != NULL);
@@ -553,7 +558,7 @@ audio_output_all_release(void)
unsigned int i;
for (i = 0; i < num_audio_outputs; ++i)
- audio_output_release(&audio_outputs[i]);
+ audio_output_release(audio_outputs[i]);
if (g_mp != NULL) {
assert(g_music_buffer != NULL);
diff --git a/src/output_all.h b/src/output_all.h
index a579bf5f1..4eeb94f13 100644
--- a/src/output_all.h
+++ b/src/output_all.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -32,13 +32,14 @@
struct audio_format;
struct music_buffer;
struct music_chunk;
+struct player_control;
/**
* Global initialization: load audio outputs from the configuration
* file and initialize them.
*/
void
-audio_output_all_init(void);
+audio_output_all_init(struct player_control *pc);
/**
* Global finalization: free memory occupied by audio outputs. All
@@ -127,7 +128,7 @@ audio_output_all_check(void);
* @return true if there are less than #threshold chunks in the pipe
*/
bool
-audio_output_all_wait(unsigned threshold);
+audio_output_all_wait(struct player_control *pc, unsigned threshold);
/**
* Puts all audio outputs into pause mode. Most implementations will
diff --git a/src/output_api.h b/src/output_api.h
index 8e002dd48..dfeef3518 100644
--- a/src/output_api.h
+++ b/src/output_api.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -21,6 +21,7 @@
#define MPD_OUTPUT_API_H
#include "output_plugin.h"
+#include "output_internal.h"
#include "audio_format.h"
#include "tag.h"
#include "conf.h"
diff --git a/src/output_command.c b/src/output_command.c
index 825884e8e..3988f350a 100644
--- a/src/output_command.c
+++ b/src/output_command.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -50,7 +50,7 @@ audio_output_enable_index(unsigned idx)
ao->enabled = true;
idle_add(IDLE_OUTPUT);
- pc_update_audio();
+ pc_update_audio(ao->player_control);
++audio_output_state_version;
@@ -79,7 +79,7 @@ audio_output_disable_index(unsigned idx)
idle_add(IDLE_MIXER);
}
- pc_update_audio();
+ pc_update_audio(ao->player_control);
++audio_output_state_version;
diff --git a/src/output_command.h b/src/output_command.h
index fab015c3f..eda30acc8 100644
--- a/src/output_command.h
+++ b/src/output_command.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/output_control.c b/src/output_control.c
index 24485f1c2..7b95be49b 100644
--- a/src/output_control.c
+++ b/src/output_control.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -38,6 +38,11 @@ enum {
struct notify audio_output_client_notify;
+/**
+ * Waits for command completion.
+ *
+ * @param ao the #audio_output instance; must be locked
+ */
static void ao_command_wait(struct audio_output *ao)
{
while (ao->command != AO_COMMAND_NONE) {
@@ -47,20 +52,43 @@ static void ao_command_wait(struct audio_output *ao)
}
}
-static void ao_command(struct audio_output *ao, enum audio_output_command cmd)
+/**
+ * Sends a command to the #audio_output object, but does not wait for
+ * completion.
+ *
+ * @param ao the #audio_output instance; must be locked
+ */
+static void ao_command_async(struct audio_output *ao,
+ enum audio_output_command cmd)
{
assert(ao->command == AO_COMMAND_NONE);
ao->command = cmd;
g_cond_signal(ao->cond);
+}
+
+/**
+ * Sends a command to the #audio_output object and waits for
+ * completion.
+ *
+ * @param ao the #audio_output instance; must be locked
+ */
+static void
+ao_command(struct audio_output *ao, enum audio_output_command cmd)
+{
+ ao_command_async(ao, cmd);
ao_command_wait(ao);
}
-static void ao_command_async(struct audio_output *ao,
- enum audio_output_command cmd)
+/**
+ * Lock the #audio_output object and execute the command
+ * synchronously.
+ */
+static void
+ao_lock_command(struct audio_output *ao, enum audio_output_command cmd)
{
- assert(ao->command == AO_COMMAND_NONE);
- ao->command = cmd;
- g_cond_signal(ao->cond);
+ g_mutex_lock(ao->mutex);
+ ao_command(ao, cmd);
+ g_mutex_unlock(ao->mutex);
}
void
@@ -78,9 +106,7 @@ audio_output_enable(struct audio_output *ao)
audio_output_thread_start(ao);
}
- g_mutex_lock(ao->mutex);
- ao_command(ao, AO_COMMAND_ENABLE);
- g_mutex_unlock(ao->mutex);
+ ao_lock_command(ao, AO_COMMAND_ENABLE);
}
void
@@ -97,9 +123,7 @@ audio_output_disable(struct audio_output *ao)
return;
}
- g_mutex_lock(ao->mutex);
- ao_command(ao, AO_COMMAND_DISABLE);
- g_mutex_unlock(ao->mutex);
+ ao_lock_command(ao, AO_COMMAND_DISABLE);
}
/**
@@ -302,28 +326,11 @@ void audio_output_finish(struct audio_output *ao)
assert(ao->fail_timer == NULL);
if (ao->thread != NULL) {
- g_mutex_lock(ao->mutex);
assert(ao->allow_play);
- ao_command(ao, AO_COMMAND_KILL);
- g_mutex_unlock(ao->mutex);
+ ao_lock_command(ao, AO_COMMAND_KILL);
g_thread_join(ao->thread);
+ ao->thread = NULL;
}
- if (ao->mixer != NULL)
- mixer_free(ao->mixer);
-
- ao_plugin_finish(ao->plugin, ao->data);
-
- g_cond_free(ao->cond);
- g_mutex_free(ao->mutex);
-
- if (ao->replay_gain_filter != NULL)
- filter_free(ao->replay_gain_filter);
-
- if (ao->other_replay_gain_filter != NULL)
- filter_free(ao->other_replay_gain_filter);
-
- filter_free(ao->filter);
-
- pcm_buffer_deinit(&ao->cross_fade_buffer);
+ audio_output_free(ao);
}
diff --git a/src/output_control.h b/src/output_control.h
index f0e317d6e..874a53518 100644
--- a/src/output_control.h
+++ b/src/output_control.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -29,6 +29,7 @@ struct audio_output;
struct audio_format;
struct config_param;
struct music_pipe;
+struct player_control;
static inline GQuark
audio_output_quark(void)
@@ -36,10 +37,6 @@ audio_output_quark(void)
return g_quark_from_static_string("audio_output");
}
-bool
-audio_output_init(struct audio_output *ao, const struct config_param *param,
- GError **error_r);
-
/**
* Enables the device.
*/
diff --git a/src/output_finish.c b/src/output_finish.c
new file mode 100644
index 000000000..e11b43675
--- /dev/null
+++ b/src/output_finish.c
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "output_internal.h"
+#include "output_plugin.h"
+#include "mixer_control.h"
+#include "filter_plugin.h"
+
+#include <assert.h>
+
+void
+ao_base_finish(struct audio_output *ao)
+{
+ assert(!ao->open);
+ assert(ao->fail_timer == NULL);
+ assert(ao->thread == NULL);
+
+ if (ao->mixer != NULL)
+ mixer_free(ao->mixer);
+
+ g_cond_free(ao->cond);
+ g_mutex_free(ao->mutex);
+
+ if (ao->replay_gain_filter != NULL)
+ filter_free(ao->replay_gain_filter);
+
+ if (ao->other_replay_gain_filter != NULL)
+ filter_free(ao->other_replay_gain_filter);
+
+ filter_free(ao->filter);
+
+ pcm_buffer_deinit(&ao->cross_fade_buffer);
+}
+
+void
+audio_output_free(struct audio_output *ao)
+{
+ assert(!ao->open);
+ assert(ao->fail_timer == NULL);
+ assert(ao->thread == NULL);
+
+ ao_plugin_finish(ao);
+}
diff --git a/src/output_init.c b/src/output_init.c
index 96f87f512..3b8a7a9f1 100644
--- a/src/output_init.c
+++ b/src/output_init.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -94,7 +94,8 @@ audio_output_mixer_type(const struct config_param *param)
}
static struct mixer *
-audio_output_load_mixer(void *ao, const struct config_param *param,
+audio_output_load_mixer(struct audio_output *ao,
+ const struct config_param *param,
const struct mixer_plugin *plugin,
struct filter *filter_chain,
GError **error_r)
@@ -126,29 +127,22 @@ audio_output_load_mixer(void *ao, const struct config_param *param,
}
bool
-audio_output_init(struct audio_output *ao, const struct config_param *param,
- GError **error_r)
+ao_base_init(struct audio_output *ao,
+ const struct audio_output_plugin *plugin,
+ const struct config_param *param, GError **error_r)
{
- const struct audio_output_plugin *plugin = NULL;
+ assert(ao != NULL);
+ assert(plugin != NULL);
+ assert(plugin->finish != NULL);
+ assert(plugin->open != NULL);
+ assert(plugin->close != NULL);
+ assert(plugin->play != NULL);
+
GError *error = NULL;
if (param) {
const char *p;
- p = config_get_block_string(param, AUDIO_OUTPUT_TYPE, NULL);
- if (p == NULL) {
- g_set_error(error_r, audio_output_quark(), 0,
- "Missing \"type\" configuration");
- return false;
- }
-
- plugin = audio_output_plugin_get(p);
- if (plugin == NULL) {
- g_set_error(error_r, audio_output_quark(), 0,
- "No such audio output plugin: %s", p);
- return false;
- }
-
ao->name = config_get_block_string(param, AUDIO_OUTPUT_NAME,
NULL);
if (ao->name == NULL) {
@@ -168,16 +162,6 @@ audio_output_init(struct audio_output *ao, const struct config_param *param,
} else
audio_format_clear(&ao->config_audio_format);
} else {
- g_warning("No \"%s\" defined in config file\n",
- CONF_AUDIO_OUTPUT);
-
- plugin = audio_output_detect(error_r);
- if (plugin == NULL)
- return false;
-
- g_message("Successfully detected a %s audio device",
- plugin->name);
-
ao->name = "default detected output";
audio_format_clear(&ao->config_audio_format);
@@ -199,29 +183,6 @@ audio_output_init(struct audio_output *ao, const struct config_param *param,
ao->filter = filter_chain_new();
assert(ao->filter != NULL);
- /* create the replay_gain filter */
-
- const char *replay_gain_handler =
- config_get_block_string(param, "replay_gain_handler",
- "software");
-
- if (strcmp(replay_gain_handler, "none") != 0) {
- ao->replay_gain_filter = filter_new(&replay_gain_filter_plugin,
- param, NULL);
- assert(ao->replay_gain_filter != NULL);
-
- ao->replay_gain_serial = 0;
-
- ao->other_replay_gain_filter = filter_new(&replay_gain_filter_plugin,
- param, NULL);
- assert(ao->other_replay_gain_filter != NULL);
-
- ao->other_replay_gain_serial = 0;
- } else {
- ao->replay_gain_filter = NULL;
- ao->other_replay_gain_filter = NULL;
- }
-
/* create the normalization filter (if configured) */
if (config_get_bool(CONF_VOLUME_NORMALIZATION, false)) {
@@ -251,14 +212,55 @@ audio_output_init(struct audio_output *ao, const struct config_param *param,
ao->mutex = g_mutex_new();
ao->cond = g_cond_new();
- ao->data = ao_plugin_init(plugin,
- &ao->config_audio_format,
- param, error_r);
- if (ao->data == NULL)
- return false;
+ ao->mixer = NULL;
+ ao->replay_gain_filter = NULL;
+ ao->other_replay_gain_filter = NULL;
+
+ /* the "convert" filter must be the last one in the chain */
+
+ ao->convert_filter = filter_new(&convert_filter_plugin, NULL, NULL);
+ assert(ao->convert_filter != NULL);
+
+ filter_chain_append(ao->filter, ao->convert_filter);
+
+ /* done */
+
+ return true;
+}
+
+static bool
+audio_output_setup(struct audio_output *ao, const struct config_param *param,
+ GError **error_r)
+{
+
+ /* create the replay_gain filter */
- ao->mixer = audio_output_load_mixer(ao->data, param,
- plugin->mixer_plugin,
+ const char *replay_gain_handler =
+ config_get_block_string(param, "replay_gain_handler",
+ "software");
+
+ if (strcmp(replay_gain_handler, "none") != 0) {
+ ao->replay_gain_filter = filter_new(&replay_gain_filter_plugin,
+ param, NULL);
+ assert(ao->replay_gain_filter != NULL);
+
+ ao->replay_gain_serial = 0;
+
+ ao->other_replay_gain_filter = filter_new(&replay_gain_filter_plugin,
+ param, NULL);
+ assert(ao->other_replay_gain_filter != NULL);
+
+ ao->other_replay_gain_serial = 0;
+ } else {
+ ao->replay_gain_filter = NULL;
+ ao->other_replay_gain_filter = NULL;
+ }
+
+ /* set up the mixer */
+
+ GError *error = NULL;
+ ao->mixer = audio_output_load_mixer(ao, param,
+ ao->plugin->mixer_plugin,
ao->filter, &error);
if (ao->mixer == NULL && error != NULL) {
g_warning("Failed to initialize hardware mixer for '%s': %s",
@@ -281,14 +283,53 @@ audio_output_init(struct audio_output *ao, const struct config_param *param,
return false;
}
- /* the "convert" filter must be the last one in the chain */
+ return true;
+}
- ao->convert_filter = filter_new(&convert_filter_plugin, NULL, NULL);
- assert(ao->convert_filter != NULL);
+struct audio_output *
+audio_output_new(const struct config_param *param,
+ struct player_control *pc,
+ GError **error_r)
+{
+ const struct audio_output_plugin *plugin;
- filter_chain_append(ao->filter, ao->convert_filter);
+ if (param) {
+ const char *p;
- /* done */
+ p = config_get_block_string(param, AUDIO_OUTPUT_TYPE, NULL);
+ if (p == NULL) {
+ g_set_error(error_r, audio_output_quark(), 0,
+ "Missing \"type\" configuration");
+ return false;
+ }
- return true;
+ plugin = audio_output_plugin_get(p);
+ if (plugin == NULL) {
+ g_set_error(error_r, audio_output_quark(), 0,
+ "No such audio output plugin: %s", p);
+ return false;
+ }
+ } else {
+ g_warning("No \"%s\" defined in config file\n",
+ CONF_AUDIO_OUTPUT);
+
+ plugin = audio_output_detect(error_r);
+ if (plugin == NULL)
+ return false;
+
+ g_message("Successfully detected a %s audio device",
+ plugin->name);
+ }
+
+ struct audio_output *ao = ao_plugin_init(plugin, param, error_r);
+ if (ao == NULL)
+ return NULL;
+
+ if (!audio_output_setup(ao, param, error_r)) {
+ ao_plugin_finish(ao);
+ return NULL;
+ }
+
+ ao->player_control = pc;
+ return ao;
}
diff --git a/src/output_internal.h b/src/output_internal.h
index 7102ea5cd..9d975d789 100644
--- a/src/output_internal.h
+++ b/src/output_internal.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -27,6 +27,8 @@
#include <time.h>
+struct config_param;
+
enum audio_output_command {
AO_COMMAND_NONE = 0,
AO_COMMAND_ENABLE,
@@ -64,12 +66,6 @@ struct audio_output {
const struct audio_output_plugin *plugin;
/**
- * The plugin's internal data. It is passed to every plugin
- * method.
- */
- void *data;
-
- /**
* The #mixer object associated with this audio output device.
* May be NULL if none is available, or if software volume is
* configured.
@@ -217,6 +213,12 @@ struct audio_output {
GCond *cond;
/**
+ * The player_control object which "owns" this output. This
+ * object is needed to signal command completion.
+ */
+ struct player_control *player_control;
+
+ /**
* The #music_chunk which is currently being played. All
* chunks before this one may be returned to the
* #music_buffer, because they are not going to be used by
@@ -248,4 +250,20 @@ audio_output_command_is_finished(const struct audio_output *ao)
return ao->command == AO_COMMAND_NONE;
}
+struct audio_output *
+audio_output_new(const struct config_param *param,
+ struct player_control *pc,
+ GError **error_r);
+
+bool
+ao_base_init(struct audio_output *ao,
+ const struct audio_output_plugin *plugin,
+ const struct config_param *param, GError **error_r);
+
+void
+ao_base_finish(struct audio_output *ao);
+
+void
+audio_output_free(struct audio_output *ao);
+
#endif
diff --git a/src/output_list.c b/src/output_list.c
index 8238f581b..e269086cf 100644
--- a/src/output_list.c
+++ b/src/output_list.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -20,28 +20,29 @@
#include "config.h"
#include "output_list.h"
#include "output_api.h"
-
-extern const struct audio_output_plugin shoutPlugin;
-extern const struct audio_output_plugin null_output_plugin;
-extern const struct audio_output_plugin fifo_output_plugin;
-extern const struct audio_output_plugin pipe_output_plugin;
-extern const struct audio_output_plugin alsaPlugin;
-extern const struct audio_output_plugin ao_output_plugin;
-extern const struct audio_output_plugin oss_output_plugin;
-extern const struct audio_output_plugin openal_output_plugin;
-extern const struct audio_output_plugin osxPlugin;
-extern const struct audio_output_plugin solaris_output_plugin;
-extern const struct audio_output_plugin pulse_output_plugin;
-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 winmm_output_plugin;
-extern const struct audio_output_plugin ffado_output_plugin;
+#include "output/alsa_output_plugin.h"
+#include "output/ao_output_plugin.h"
+#include "output/ffado_output_plugin.h"
+#include "output/fifo_output_plugin.h"
+#include "output/httpd_output_plugin.h"
+#include "output/jack_output_plugin.h"
+#include "output/mvp_output_plugin.h"
+#include "output/null_output_plugin.h"
+#include "output/openal_output_plugin.h"
+#include "output/oss_output_plugin.h"
+#include "output/osx_output_plugin.h"
+#include "output/pipe_output_plugin.h"
+#include "output/pulse_output_plugin.h"
+#include "output/raop_output_plugin.h"
+#include "output/recorder_output_plugin.h"
+#include "output/roar_output_plugin.h"
+#include "output/shout_output_plugin.h"
+#include "output/solaris_output_plugin.h"
+#include "output/winmm_output_plugin.h"
const struct audio_output_plugin *audio_output_plugins[] = {
#ifdef HAVE_SHOUT
- &shoutPlugin,
+ &shout_output_plugin,
#endif
&null_output_plugin,
#ifdef HAVE_FIFO
@@ -51,7 +52,10 @@ const struct audio_output_plugin *audio_output_plugins[] = {
&pipe_output_plugin,
#endif
#ifdef HAVE_ALSA
- &alsaPlugin,
+ &alsa_output_plugin,
+#endif
+#ifdef HAVE_ROAR
+ &roar_output_plugin,
#endif
#ifdef HAVE_AO
&ao_output_plugin,
@@ -63,7 +67,10 @@ const struct audio_output_plugin *audio_output_plugins[] = {
&openal_output_plugin,
#endif
#ifdef HAVE_OSX
- &osxPlugin,
+ &osx_output_plugin,
+#endif
+#ifdef ENABLE_RAOP_OUTPUT
+ &raop_output_plugin,
#endif
#ifdef ENABLE_SOLARIS_OUTPUT
&solaris_output_plugin,
@@ -99,8 +106,8 @@ audio_output_plugin_get(const char *name)
const struct audio_output_plugin *plugin;
audio_output_plugins_for_each(plugin, i)
- if (strcmp(audio_output_plugins[i]->name, name) == 0)
- return audio_output_plugins[i];
+ if (strcmp(plugin->name, name) == 0)
+ return plugin;
return NULL;
}
diff --git a/src/output_list.h b/src/output_list.h
index d72bc224b..3deb31c00 100644
--- a/src/output_list.h
+++ b/src/output_list.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/output_plugin.c b/src/output_plugin.c
new file mode 100644
index 000000000..221570c1c
--- /dev/null
+++ b/src/output_plugin.c
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "output_plugin.h"
+#include "output_internal.h"
+
+struct audio_output *
+ao_plugin_init(const struct audio_output_plugin *plugin,
+ const struct config_param *param,
+ GError **error)
+{
+ assert(plugin != NULL);
+ assert(plugin->init != NULL);
+
+ return plugin->init(param, error);
+}
+
+void
+ao_plugin_finish(struct audio_output *ao)
+{
+ ao->plugin->finish(ao);
+}
+
+bool
+ao_plugin_enable(struct audio_output *ao, GError **error_r)
+{
+ return ao->plugin->enable != NULL
+ ? ao->plugin->enable(ao, error_r)
+ : true;
+}
+
+void
+ao_plugin_disable(struct audio_output *ao)
+{
+ if (ao->plugin->disable != NULL)
+ ao->plugin->disable(ao);
+}
+
+bool
+ao_plugin_open(struct audio_output *ao, struct audio_format *audio_format,
+ GError **error)
+{
+ return ao->plugin->open(ao, audio_format, error);
+}
+
+void
+ao_plugin_close(struct audio_output *ao)
+{
+ ao->plugin->close(ao);
+}
+
+unsigned
+ao_plugin_delay(struct audio_output *ao)
+{
+ return ao->plugin->delay != NULL
+ ? ao->plugin->delay(ao)
+ : 0;
+}
+
+void
+ao_plugin_send_tag(struct audio_output *ao, const struct tag *tag)
+{
+ if (ao->plugin->send_tag != NULL)
+ ao->plugin->send_tag(ao, tag);
+}
+
+size_t
+ao_plugin_play(struct audio_output *ao, const void *chunk, size_t size,
+ GError **error)
+{
+ return ao->plugin->play(ao, chunk, size, error);
+}
+
+void
+ao_plugin_drain(struct audio_output *ao)
+{
+ if (ao->plugin->drain != NULL)
+ ao->plugin->drain(ao);
+}
+
+void
+ao_plugin_cancel(struct audio_output *ao)
+{
+ if (ao->plugin->cancel != NULL)
+ ao->plugin->cancel(ao);
+}
+
+bool
+ao_plugin_pause(struct audio_output *ao)
+{
+ return ao->plugin->pause != NULL && ao->plugin->pause(ao);
+}
diff --git a/src/output_plugin.h b/src/output_plugin.h
index 36e17ed1b..209ca6221 100644
--- a/src/output_plugin.h
+++ b/src/output_plugin.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -48,23 +48,20 @@ struct audio_output_plugin {
* Configure and initialize the device, but do not open it
* yet.
*
- * @param audio_format the configured audio format, or NULL if
- * none is configured
* @param param the configuration section, or NULL if there is
* no configuration
- * @param error location to store the error occuring, or NULL
+ * @param error location to store the error occurring, or NULL
* to ignore errors
* @return NULL on error, or an opaque pointer to the plugin's
* data
*/
- void *(*init)(const struct audio_format *audio_format,
- const struct config_param *param,
- GError **error);
+ struct audio_output *(*init)(const struct config_param *param,
+ GError **error);
/**
* Free resources allocated by this device.
*/
- void (*finish)(void *data);
+ void (*finish)(struct audio_output *data);
/**
* Enable the device. This may allocate resources, preparing
@@ -72,33 +69,33 @@ struct audio_output_plugin {
* fail: if an error occurs during that, it should be reported
* by the open() method.
*
- * @param error_r location to store the error occuring, or
+ * @param error_r location to store the error occurring, or
* NULL to ignore errors
* @return true on success, false on error
*/
- bool (*enable)(void *data, GError **error_r);
+ bool (*enable)(struct audio_output *data, GError **error_r);
/**
* Disables the device. It is closed before this method is
* called.
*/
- void (*disable)(void *data);
+ void (*disable)(struct audio_output *data);
/**
* Really open the device.
*
* @param audio_format the audio format in which data is going
* to be delivered; may be modified by the plugin
- * @param error location to store the error occuring, or NULL
+ * @param error location to store the error occurring, or NULL
* to ignore errors
*/
- bool (*open)(void *data, struct audio_format *audio_format,
+ bool (*open)(struct audio_output *data, struct audio_format *audio_format,
GError **error);
/**
* Close the device.
*/
- void (*close)(void *data);
+ void (*close)(struct audio_output *data);
/**
* Returns a positive number if the output thread shall delay
@@ -108,34 +105,35 @@ struct audio_output_plugin {
*
* @return the number of milliseconds to wait
*/
- unsigned (*delay)(void *data);
+ unsigned (*delay)(struct audio_output *data);
/**
* Display metadata for the next chunk. Optional method,
* because not all devices can display metadata.
*/
- void (*send_tag)(void *data, const struct tag *tag);
+ void (*send_tag)(struct audio_output *data, const struct tag *tag);
/**
* Play a chunk of audio data.
*
- * @param error location to store the error occuring, or NULL
+ * @param error location to store the error occurring, or NULL
* to ignore errors
* @return the number of bytes played, or 0 on error
*/
- size_t (*play)(void *data, const void *chunk, size_t size,
+ size_t (*play)(struct audio_output *data,
+ const void *chunk, size_t size,
GError **error);
/**
* Wait until the device has finished playing.
*/
- void (*drain)(void *data);
+ void (*drain)(struct audio_output *data);
/**
* Try to cancel data which may still be in the device's
* buffers.
*/
- void (*cancel)(void *data);
+ void (*cancel)(struct audio_output *data);
/**
* Pause the device. If supported, it may perform a special
@@ -148,7 +146,7 @@ struct audio_output_plugin {
* @return false on error (output will be closed then), true
* for continue to pause
*/
- bool (*pause)(void *data);
+ bool (*pause)(struct audio_output *data);
/**
* The mixer plugin associated with this output plugin. This
@@ -167,95 +165,46 @@ ao_plugin_test_default_device(const struct audio_output_plugin *plugin)
: false;
}
-static inline void *
+G_GNUC_MALLOC
+struct audio_output *
ao_plugin_init(const struct audio_output_plugin *plugin,
- const struct audio_format *audio_format,
const struct config_param *param,
- GError **error)
-{
- return plugin->init(audio_format, param, error);
-}
+ GError **error);
-static inline void
-ao_plugin_finish(const struct audio_output_plugin *plugin, void *data)
-{
- plugin->finish(data);
-}
+void
+ao_plugin_finish(struct audio_output *ao);
-static inline bool
-ao_plugin_enable(const struct audio_output_plugin *plugin, void *data,
- GError **error_r)
-{
- return plugin->enable != NULL
- ? plugin->enable(data, error_r)
- : true;
-}
+bool
+ao_plugin_enable(struct audio_output *ao, GError **error_r);
-static inline void
-ao_plugin_disable(const struct audio_output_plugin *plugin, void *data)
-{
- if (plugin->disable != NULL)
- plugin->disable(data);
-}
+void
+ao_plugin_disable(struct audio_output *ao);
-static inline bool
-ao_plugin_open(const struct audio_output_plugin *plugin,
- void *data, struct audio_format *audio_format,
- GError **error)
-{
- return plugin->open(data, audio_format, error);
-}
+bool
+ao_plugin_open(struct audio_output *ao, struct audio_format *audio_format,
+ GError **error);
-static inline void
-ao_plugin_close(const struct audio_output_plugin *plugin, void *data)
-{
- plugin->close(data);
-}
+void
+ao_plugin_close(struct audio_output *ao);
-static inline unsigned
-ao_plugin_delay(const struct audio_output_plugin *plugin, void *data)
-{
- return plugin->delay != NULL
- ? plugin->delay(data)
- : 0;
-}
+G_GNUC_PURE
+unsigned
+ao_plugin_delay(struct audio_output *ao);
-static inline void
-ao_plugin_send_tag(const struct audio_output_plugin *plugin,
- void *data, const struct tag *tag)
-{
- if (plugin->send_tag != NULL)
- plugin->send_tag(data, tag);
-}
+void
+ao_plugin_send_tag(struct audio_output *ao, const struct tag *tag);
-static inline size_t
-ao_plugin_play(const struct audio_output_plugin *plugin,
- void *data, const void *chunk, size_t size,
- GError **error)
-{
- return plugin->play(data, chunk, size, error);
-}
+size_t
+ao_plugin_play(struct audio_output *ao, const void *chunk, size_t size,
+ GError **error);
-static inline void
-ao_plugin_drain(const struct audio_output_plugin *plugin, void *data)
-{
- if (plugin->drain != NULL)
- plugin->drain(data);
-}
+void
+ao_plugin_drain(struct audio_output *ao);
-static inline void
-ao_plugin_cancel(const struct audio_output_plugin *plugin, void *data)
-{
- if (plugin->cancel != NULL)
- plugin->cancel(data);
-}
+void
+ao_plugin_cancel(struct audio_output *ao);
-static inline bool
-ao_plugin_pause(const struct audio_output_plugin *plugin, void *data)
-{
- return plugin->pause != NULL
- ? plugin->pause(data)
- : false;
-}
+bool
+ao_plugin_pause(struct audio_output *ao);
#endif
diff --git a/src/output_print.c b/src/output_print.c
index 7a747ad2f..483648ca2 100644
--- a/src/output_print.c
+++ b/src/output_print.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/output_print.h b/src/output_print.h
index 5ad7e34c7..e02f4e9f5 100644
--- a/src/output_print.h
+++ b/src/output_print.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/output_state.c b/src/output_state.c
index e1187b951..7bcafb36b 100644
--- a/src/output_state.c
+++ b/src/output_state.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/output_state.h b/src/output_state.h
index 962ccd97a..320a3520a 100644
--- a/src/output_state.h
+++ b/src/output_state.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/output_thread.c b/src/output_thread.c
index bf56ca971..02c315af9 100644
--- a/src/output_thread.c
+++ b/src/output_thread.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -29,6 +29,7 @@
#include "filter/convert_filter_plugin.h"
#include "filter/replay_gain_filter_plugin.h"
#include "mpd_error.h"
+#include "notify.h"
#include <glib.h>
@@ -59,7 +60,7 @@ ao_enable(struct audio_output *ao)
return true;
g_mutex_unlock(ao->mutex);
- success = ao_plugin_enable(ao->plugin, ao->data, &error);
+ success = ao_plugin_enable(ao, &error);
g_mutex_lock(ao->mutex);
if (!success) {
g_warning("Failed to enable \"%s\" [%s]: %s\n",
@@ -85,7 +86,7 @@ ao_disable(struct audio_output *ao)
ao->really_enabled = false;
g_mutex_unlock(ao->mutex);
- ao_plugin_disable(ao->plugin, ao->data);
+ ao_plugin_disable(ao);
g_mutex_lock(ao->mutex);
}
}
@@ -174,9 +175,7 @@ ao_open(struct audio_output *ao)
&ao->config_audio_format);
g_mutex_unlock(ao->mutex);
- success = ao_plugin_open(ao->plugin, ao->data,
- &ao->out_audio_format,
- &error);
+ success = ao_plugin_open(ao, &ao->out_audio_format, &error);
g_mutex_lock(ao->mutex);
assert(!ao->open);
@@ -220,11 +219,11 @@ ao_close(struct audio_output *ao, bool drain)
g_mutex_unlock(ao->mutex);
if (drain)
- ao_plugin_drain(ao->plugin, ao->data);
+ ao_plugin_drain(ao);
else
- ao_plugin_cancel(ao->plugin, ao->data);
+ ao_plugin_cancel(ao);
- ao_plugin_close(ao->plugin, ao->data);
+ ao_plugin_close(ao);
ao_filter_close(ao);
g_mutex_lock(ao->mutex);
@@ -256,7 +255,7 @@ ao_reopen_filter(struct audio_output *ao)
ao->fail_timer = g_timer_new();
g_mutex_unlock(ao->mutex);
- ao_plugin_close(ao->plugin, ao->data);
+ ao_plugin_close(ao);
g_mutex_lock(ao->mutex);
return;
@@ -301,7 +300,7 @@ static bool
ao_wait(struct audio_output *ao)
{
while (true) {
- unsigned delay = ao_plugin_delay(ao->plugin, ao->data);
+ unsigned delay = ao_plugin_delay(ao);
if (delay == 0)
return true;
@@ -402,8 +401,12 @@ ao_filter_chunk(struct audio_output *ao, const struct music_chunk *chunk,
char *dest = pcm_buffer_get(&ao->cross_fade_buffer,
other_length);
memcpy(dest, other_data, other_length);
- pcm_mix(dest, data, length, &ao->in_audio_format,
- 1.0 - chunk->mix_ratio);
+ if (!pcm_mix(dest, data, length, ao->in_audio_format.format,
+ 1.0 - chunk->mix_ratio)) {
+ g_warning("Cannot cross-fade format %s",
+ sample_format_to_string(ao->in_audio_format.format));
+ return NULL;
+ }
data = dest;
length = other_length;
@@ -433,7 +436,7 @@ ao_play_chunk(struct audio_output *ao, const struct music_chunk *chunk)
if (chunk->tag != NULL) {
g_mutex_unlock(ao->mutex);
- ao_plugin_send_tag(ao->plugin, ao->data, chunk->tag);
+ ao_plugin_send_tag(ao, chunk->tag);
g_mutex_lock(ao->mutex);
}
@@ -455,8 +458,7 @@ ao_play_chunk(struct audio_output *ao, const struct music_chunk *chunk)
break;
g_mutex_unlock(ao->mutex);
- nbytes = ao_plugin_play(ao->plugin, ao->data, data, size,
- &error);
+ nbytes = ao_plugin_play(ao, data, size, &error);
g_mutex_lock(ao->mutex);
if (nbytes == 0) {
/* play()==0 means failure */
@@ -535,7 +537,7 @@ ao_play(struct audio_output *ao)
ao->chunk_finished = true;
g_mutex_unlock(ao->mutex);
- player_lock_signal();
+ player_lock_signal(ao->player_control);
g_mutex_lock(ao->mutex);
return true;
@@ -546,7 +548,7 @@ static void ao_pause(struct audio_output *ao)
bool ret;
g_mutex_unlock(ao->mutex);
- ao_plugin_cancel(ao->plugin, ao->data);
+ ao_plugin_cancel(ao);
g_mutex_lock(ao->mutex);
ao->pause = true;
@@ -557,7 +559,7 @@ static void ao_pause(struct audio_output *ao)
break;
g_mutex_unlock(ao->mutex);
- ret = ao_plugin_pause(ao->plugin, ao->data);
+ ret = ao_plugin_pause(ao);
g_mutex_lock(ao->mutex);
if (!ret) {
@@ -631,7 +633,7 @@ static gpointer audio_output_task(gpointer arg)
assert(music_pipe_peek(ao->pipe) == NULL);
g_mutex_unlock(ao->mutex);
- ao_plugin_drain(ao->plugin, ao->data);
+ ao_plugin_drain(ao);
g_mutex_lock(ao->mutex);
}
@@ -643,7 +645,7 @@ static gpointer audio_output_task(gpointer arg)
if (ao->open) {
g_mutex_unlock(ao->mutex);
- ao_plugin_cancel(ao->plugin, ao->data);
+ ao_plugin_cancel(ao);
g_mutex_lock(ao->mutex);
}
diff --git a/src/output_thread.h b/src/output_thread.h
index 1ee0856f2..5ad9a7527 100644
--- a/src/output_thread.h
+++ b/src/output_thread.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/page.c b/src/page.c
index 59369cb34..e2e22791f 100644
--- a/src/page.c
+++ b/src/page.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/page.h b/src/page.h
index 652c4ad6e..8a3aaf396 100644
--- a/src/page.h
+++ b/src/page.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/path.c b/src/path.c
index d1b9ad6ee..59a91a0f7 100644
--- a/src/path.c
+++ b/src/path.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/path.h b/src/path.h
index 512cd13ea..00c368e70 100644
--- a/src/path.h
+++ b/src/path.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/pcm_buffer.c b/src/pcm_buffer.c
index 60a699b20..4b1eb875a 100644
--- a/src/pcm_buffer.c
+++ b/src/pcm_buffer.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -17,7 +17,9 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "pcm_buffer.h"
+#include "poison.h"
/**
* Align the specified size to the next 8k boundary.
@@ -45,6 +47,9 @@ pcm_buffer_get(struct pcm_buffer *buffer, size_t size)
buffer->size = align_8k(size);
buffer->buffer = g_malloc(buffer->size);
+ } else {
+ /* discard old buffer contents */
+ poison_undefined(buffer->buffer, buffer->size);
}
assert(buffer->size >= size);
diff --git a/src/pcm_buffer.h b/src/pcm_buffer.h
index b132d4fd2..4502976f6 100644
--- a/src/pcm_buffer.h
+++ b/src/pcm_buffer.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -20,6 +20,8 @@
#ifndef PCM_BUFFER_H
#define PCM_BUFFER_H
+#include "check.h"
+
#include <glib.h>
#include <assert.h>
diff --git a/src/pcm_byteswap.c b/src/pcm_byteswap.c
deleted file mode 100644
index 6577319d4..000000000
--- a/src/pcm_byteswap.c
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * 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 "pcm_byteswap.h"
-#include "pcm_buffer.h"
-
-#include <glib.h>
-
-#include <assert.h>
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "pcm"
-
-static inline uint16_t swab16(uint16_t x)
-{
- return (x << 8) | (x >> 8);
-}
-
-const int16_t *pcm_byteswap_16(struct pcm_buffer *buffer,
- const int16_t *src, size_t len)
-{
- unsigned i;
- int16_t *buf = pcm_buffer_get(buffer, len);
-
- assert(buf != NULL);
-
- for (i = 0; i < len / 2; i++)
- buf[i] = swab16(src[i]);
-
- return buf;
-}
-
-static inline uint32_t swab32(uint32_t x)
-{
- return (x << 24) |
- ((x & 0xff00) << 8) |
- ((x & 0xff0000) >> 8) |
- (x >> 24);
-}
-
-const int32_t *pcm_byteswap_32(struct pcm_buffer *buffer,
- const int32_t *src, size_t len)
-{
- unsigned i;
- int32_t *buf = pcm_buffer_get(buffer, len);
-
- assert(buf != NULL);
-
- for (i = 0; i < len / 4; i++)
- buf[i] = swab32(src[i]);
-
- return buf;
-}
diff --git a/src/pcm_byteswap.h b/src/pcm_byteswap.h
deleted file mode 100644
index 005e75ded..000000000
--- a/src/pcm_byteswap.h
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * 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_PCM_BYTESWAP_H
-#define MPD_PCM_BYTESWAP_H
-
-#include <stdint.h>
-#include <stddef.h>
-
-struct pcm_buffer;
-
-/**
- * Changes the endianness of 16 bit PCM data.
- *
- * @param buffer the destination pcm_buffer object
- * @param src the source PCM buffer
- * @param src_size the number of bytes in #src
- * @return the destination buffer
- */
-const int16_t *pcm_byteswap_16(struct pcm_buffer *buffer,
- const int16_t *src, size_t len);
-
-/**
- * Changes the endianness of 32-bit (or 24-bit) PCM data.
- *
- * @param buffer the destination pcm_buffer object
- * @param src the source PCM buffer
- * @param src_size the number of bytes in #src
- * @return the destination buffer
- */
-const int32_t *pcm_byteswap_32(struct pcm_buffer *buffer,
- const int32_t *src, size_t len);
-
-#endif
diff --git a/src/pcm_channels.c b/src/pcm_channels.c
index 34e72ca4e..ec2bd69a5 100644
--- a/src/pcm_channels.c
+++ b/src/pcm_channels.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -20,14 +20,16 @@
#include "config.h"
#include "pcm_channels.h"
#include "pcm_buffer.h"
+#include "pcm_utils.h"
#include <assert.h>
static void
-pcm_convert_channels_16_1_to_2(int16_t *dest, const int16_t *src,
- unsigned num_frames)
+pcm_convert_channels_16_1_to_2(int16_t *restrict dest,
+ const int16_t *restrict src,
+ const int16_t *restrict src_end)
{
- while (num_frames-- > 0) {
+ while (src < src_end) {
int16_t value = *src++;
*dest++ = value;
@@ -36,10 +38,11 @@ pcm_convert_channels_16_1_to_2(int16_t *dest, const int16_t *src,
}
static void
-pcm_convert_channels_16_2_to_1(int16_t *dest, const int16_t *src,
- unsigned num_frames)
+pcm_convert_channels_16_2_to_1(int16_t *restrict dest,
+ const int16_t *restrict src,
+ const int16_t *restrict src_end)
{
- while (num_frames-- > 0) {
+ while (src < src_end) {
int32_t a = *src++, b = *src++;
*dest++ = (a + b) / 2;
@@ -47,15 +50,16 @@ pcm_convert_channels_16_2_to_1(int16_t *dest, const int16_t *src,
}
static void
-pcm_convert_channels_16_n_to_2(int16_t *dest,
- unsigned src_channels, const int16_t *src,
- unsigned num_frames)
+pcm_convert_channels_16_n_to_2(int16_t *restrict dest,
+ unsigned src_channels,
+ const int16_t *restrict src,
+ const int16_t *restrict src_end)
{
unsigned c;
assert(src_channels > 0);
- while (num_frames-- > 0) {
+ while (src < src_end) {
int32_t sum = 0;
int16_t value;
@@ -71,23 +75,25 @@ pcm_convert_channels_16_n_to_2(int16_t *dest,
const int16_t *
pcm_convert_channels_16(struct pcm_buffer *buffer,
- uint8_t dest_channels,
- uint8_t src_channels, const int16_t *src,
+ unsigned dest_channels,
+ unsigned src_channels, const int16_t *src,
size_t src_size, size_t *dest_size_r)
{
- unsigned num_frames = src_size / src_channels / sizeof(*src);
- unsigned dest_size = num_frames * dest_channels * sizeof(*src);
- int16_t *dest = pcm_buffer_get(buffer, dest_size);
+ assert(src_size % (sizeof(*src) * src_channels) == 0);
+ size_t dest_size = src_size / src_channels * dest_channels;
*dest_size_r = dest_size;
+ int16_t *dest = pcm_buffer_get(buffer, dest_size);
+ const int16_t *src_end = pcm_end_pointer(src, src_size);
+
if (src_channels == 1 && dest_channels == 2)
- pcm_convert_channels_16_1_to_2(dest, src, num_frames);
+ pcm_convert_channels_16_1_to_2(dest, src, src_end);
else if (src_channels == 2 && dest_channels == 1)
- pcm_convert_channels_16_2_to_1(dest, src, num_frames);
+ pcm_convert_channels_16_2_to_1(dest, src, src_end);
else if (dest_channels == 2)
pcm_convert_channels_16_n_to_2(dest, src_channels, src,
- num_frames);
+ src_end);
else
return NULL;
@@ -95,10 +101,11 @@ pcm_convert_channels_16(struct pcm_buffer *buffer,
}
static void
-pcm_convert_channels_24_1_to_2(int32_t *dest, const int32_t *src,
- unsigned num_frames)
+pcm_convert_channels_24_1_to_2(int32_t *restrict dest,
+ const int32_t *restrict src,
+ const int32_t *restrict src_end)
{
- while (num_frames-- > 0) {
+ while (src < src_end) {
int32_t value = *src++;
*dest++ = value;
@@ -107,10 +114,11 @@ pcm_convert_channels_24_1_to_2(int32_t *dest, const int32_t *src,
}
static void
-pcm_convert_channels_24_2_to_1(int32_t *dest, const int32_t *src,
- unsigned num_frames)
+pcm_convert_channels_24_2_to_1(int32_t *restrict dest,
+ const int32_t *restrict src,
+ const int32_t *restrict src_end)
{
- while (num_frames-- > 0) {
+ while (src < src_end) {
int32_t a = *src++, b = *src++;
*dest++ = (a + b) / 2;
@@ -118,15 +126,16 @@ pcm_convert_channels_24_2_to_1(int32_t *dest, const int32_t *src,
}
static void
-pcm_convert_channels_24_n_to_2(int32_t *dest,
- unsigned src_channels, const int32_t *src,
- unsigned num_frames)
+pcm_convert_channels_24_n_to_2(int32_t *restrict dest,
+ unsigned src_channels,
+ const int32_t *restrict src,
+ const int32_t *restrict src_end)
{
unsigned c;
assert(src_channels > 0);
- while (num_frames-- > 0) {
+ while (src < src_end) {
int32_t sum = 0;
int32_t value;
@@ -142,23 +151,25 @@ pcm_convert_channels_24_n_to_2(int32_t *dest,
const int32_t *
pcm_convert_channels_24(struct pcm_buffer *buffer,
- uint8_t dest_channels,
- uint8_t src_channels, const int32_t *src,
+ unsigned dest_channels,
+ unsigned src_channels, const int32_t *src,
size_t src_size, size_t *dest_size_r)
{
- unsigned num_frames = src_size / src_channels / sizeof(*src);
- unsigned dest_size = num_frames * dest_channels * sizeof(*src);
- int32_t *dest = pcm_buffer_get(buffer, dest_size);
+ assert(src_size % (sizeof(*src) * src_channels) == 0);
+ size_t dest_size = src_size / src_channels * dest_channels;
*dest_size_r = dest_size;
+ int32_t *dest = pcm_buffer_get(buffer, dest_size);
+ const int32_t *src_end = pcm_end_pointer(src, src_size);
+
if (src_channels == 1 && dest_channels == 2)
- pcm_convert_channels_24_1_to_2(dest, src, num_frames);
+ pcm_convert_channels_24_1_to_2(dest, src, src_end);
else if (src_channels == 2 && dest_channels == 1)
- pcm_convert_channels_24_2_to_1(dest, src, num_frames);
+ pcm_convert_channels_24_2_to_1(dest, src, src_end);
else if (dest_channels == 2)
pcm_convert_channels_24_n_to_2(dest, src_channels, src,
- num_frames);
+ src_end);
else
return NULL;
@@ -167,16 +178,17 @@ pcm_convert_channels_24(struct pcm_buffer *buffer,
static void
pcm_convert_channels_32_1_to_2(int32_t *dest, const int32_t *src,
- unsigned num_frames)
+ const int32_t *src_end)
{
- pcm_convert_channels_24_1_to_2(dest, src, num_frames);
+ pcm_convert_channels_24_1_to_2(dest, src, src_end);
}
static void
-pcm_convert_channels_32_2_to_1(int32_t *dest, const int32_t *src,
- unsigned num_frames)
+pcm_convert_channels_32_2_to_1(int32_t *restrict dest,
+ const int32_t *restrict src,
+ const int32_t *restrict src_end)
{
- while (num_frames-- > 0) {
+ while (src < src_end) {
int64_t a = *src++, b = *src++;
*dest++ = (a + b) / 2;
@@ -186,13 +198,13 @@ pcm_convert_channels_32_2_to_1(int32_t *dest, const int32_t *src,
static void
pcm_convert_channels_32_n_to_2(int32_t *dest,
unsigned src_channels, const int32_t *src,
- unsigned num_frames)
+ const int32_t *src_end)
{
unsigned c;
assert(src_channels > 0);
- while (num_frames-- > 0) {
+ while (src < src_end) {
int64_t sum = 0;
int32_t value;
@@ -208,23 +220,25 @@ pcm_convert_channels_32_n_to_2(int32_t *dest,
const int32_t *
pcm_convert_channels_32(struct pcm_buffer *buffer,
- uint8_t dest_channels,
- uint8_t src_channels, const int32_t *src,
+ unsigned dest_channels,
+ unsigned src_channels, const int32_t *src,
size_t src_size, size_t *dest_size_r)
{
- unsigned num_frames = src_size / src_channels / sizeof(*src);
- unsigned dest_size = num_frames * dest_channels * sizeof(*src);
- int32_t *dest = pcm_buffer_get(buffer, dest_size);
+ assert(src_size % (sizeof(*src) * src_channels) == 0);
+ size_t dest_size = src_size / src_channels * dest_channels;
*dest_size_r = dest_size;
+ int32_t *dest = pcm_buffer_get(buffer, dest_size);
+ const int32_t *src_end = pcm_end_pointer(src, src_size);
+
if (src_channels == 1 && dest_channels == 2)
- pcm_convert_channels_32_1_to_2(dest, src, num_frames);
+ pcm_convert_channels_32_1_to_2(dest, src, src_end);
else if (src_channels == 2 && dest_channels == 1)
- pcm_convert_channels_32_2_to_1(dest, src, num_frames);
+ pcm_convert_channels_32_2_to_1(dest, src, src_end);
else if (dest_channels == 2)
pcm_convert_channels_32_n_to_2(dest, src_channels, src,
- num_frames);
+ src_end);
else
return NULL;
diff --git a/src/pcm_channels.h b/src/pcm_channels.h
index a23cbd364..1e4a0991f 100644
--- a/src/pcm_channels.h
+++ b/src/pcm_channels.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -38,8 +38,8 @@ struct pcm_buffer;
*/
const int16_t *
pcm_convert_channels_16(struct pcm_buffer *buffer,
- uint8_t dest_channels,
- uint8_t src_channels, const int16_t *src,
+ unsigned dest_channels,
+ unsigned src_channels, const int16_t *src,
size_t src_size, size_t *dest_size_r);
/**
@@ -56,8 +56,8 @@ pcm_convert_channels_16(struct pcm_buffer *buffer,
*/
const int32_t *
pcm_convert_channels_24(struct pcm_buffer *buffer,
- uint8_t dest_channels,
- uint8_t src_channels, const int32_t *src,
+ unsigned dest_channels,
+ unsigned src_channels, const int32_t *src,
size_t src_size, size_t *dest_size_r);
/**
@@ -73,8 +73,8 @@ pcm_convert_channels_24(struct pcm_buffer *buffer,
*/
const int32_t *
pcm_convert_channels_32(struct pcm_buffer *buffer,
- uint8_t dest_channels,
- uint8_t src_channels, const int32_t *src,
+ unsigned dest_channels,
+ unsigned src_channels, const int32_t *src,
size_t src_size, size_t *dest_size_r);
#endif
diff --git a/src/pcm_convert.c b/src/pcm_convert.c
index 7bd4d7215..63f9a1b98 100644
--- a/src/pcm_convert.c
+++ b/src/pcm_convert.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -21,9 +21,9 @@
#include "pcm_convert.h"
#include "pcm_channels.h"
#include "pcm_format.h"
-#include "pcm_byteswap.h"
#include "pcm_pack.h"
#include "audio_format.h"
+#include "glib_compat.h"
#include <assert.h>
#include <string.h>
@@ -37,23 +37,77 @@ void pcm_convert_init(struct pcm_convert_state *state)
{
memset(state, 0, sizeof(*state));
+ pcm_dsd_init(&state->dsd);
pcm_resample_init(&state->resample);
pcm_dither_24_init(&state->dither);
pcm_buffer_init(&state->format_buffer);
- pcm_buffer_init(&state->pack_buffer);
pcm_buffer_init(&state->channels_buffer);
- pcm_buffer_init(&state->byteswap_buffer);
}
void pcm_convert_deinit(struct pcm_convert_state *state)
{
+ pcm_dsd_deinit(&state->dsd);
pcm_resample_deinit(&state->resample);
pcm_buffer_deinit(&state->format_buffer);
- pcm_buffer_deinit(&state->pack_buffer);
pcm_buffer_deinit(&state->channels_buffer);
- pcm_buffer_deinit(&state->byteswap_buffer);
+}
+
+void
+pcm_convert_reset(struct pcm_convert_state *state)
+{
+ pcm_dsd_reset(&state->dsd);
+ pcm_resample_reset(&state->resample);
+}
+
+static const void *
+pcm_convert_channels(struct pcm_buffer *buffer, enum sample_format format,
+ uint8_t dest_channels,
+ uint8_t src_channels, const void *src,
+ size_t src_size, size_t *dest_size_r,
+ GError **error_r)
+{
+ const void *dest = NULL;
+
+ switch (format) {
+ case SAMPLE_FORMAT_UNDEFINED:
+ case SAMPLE_FORMAT_S8:
+ case SAMPLE_FORMAT_FLOAT:
+ case SAMPLE_FORMAT_DSD:
+ g_set_error(error_r, pcm_convert_quark(), 0,
+ "Channel conversion not implemented for format '%s'",
+ sample_format_to_string(format));
+ return NULL;
+
+ case SAMPLE_FORMAT_S16:
+ dest = pcm_convert_channels_16(buffer, dest_channels,
+ src_channels, src,
+ src_size, dest_size_r);
+ break;
+
+ case SAMPLE_FORMAT_S24_P32:
+ dest = pcm_convert_channels_24(buffer, dest_channels,
+ src_channels, src,
+ src_size, dest_size_r);
+ break;
+
+ case SAMPLE_FORMAT_S32:
+ dest = pcm_convert_channels_32(buffer, dest_channels,
+ src_channels, src,
+ src_size, dest_size_r);
+ break;
+ }
+
+ if (dest == NULL) {
+ g_set_error(error_r, pcm_convert_quark(), 0,
+ "Conversion from %u to %u channels "
+ "is not implemented",
+ src_channels, dest_channels);
+ return NULL;
+ }
+
+ return dest;
}
static const int16_t *
@@ -103,11 +157,6 @@ pcm_convert_16(struct pcm_convert_state *state,
return NULL;
}
- if (dest_format->reverse_endian) {
- buf = pcm_byteswap_16(&state->byteswap_buffer, buf, len);
- assert(buf != NULL);
- }
-
*dest_size_r = len;
return buf;
}
@@ -158,54 +207,10 @@ pcm_convert_24(struct pcm_convert_state *state,
return NULL;
}
- if (dest_format->reverse_endian) {
- buf = pcm_byteswap_32(&state->byteswap_buffer, buf, len);
- assert(buf != NULL);
- }
-
*dest_size_r = len;
return buf;
}
-/**
- * Convert to 24 bit packed samples (aka S24_3LE / S24_3BE).
- */
-static const void *
-pcm_convert_24_packed(struct pcm_convert_state *state,
- const struct audio_format *src_format,
- const void *src_buffer, size_t src_size,
- const struct audio_format *dest_format,
- size_t *dest_size_r,
- GError **error_r)
-{
- assert(dest_format->format == SAMPLE_FORMAT_S24);
-
- /* use the normal 24 bit conversion first */
-
- struct audio_format audio_format;
- audio_format_init(&audio_format, dest_format->sample_rate,
- SAMPLE_FORMAT_S24_P32, dest_format->channels);
-
- const int32_t *buffer;
- size_t buffer_size;
-
- buffer = pcm_convert_24(state, src_format, src_buffer, src_size,
- &audio_format, &buffer_size, error_r);
- if (buffer == NULL)
- return NULL;
-
- /* now convert to packed 24 bit */
-
- unsigned num_samples = buffer_size / 4;
- size_t dest_size = num_samples * 3;
-
- uint8_t *dest = pcm_buffer_get(&state->pack_buffer, dest_size);
- pcm_pack_24(dest, buffer, num_samples, dest_format->reverse_endian);
-
- *dest_size_r = dest_size;
- return dest;
-}
-
static const int32_t *
pcm_convert_32(struct pcm_convert_state *state,
const struct audio_format *src_format,
@@ -252,15 +257,65 @@ pcm_convert_32(struct pcm_convert_state *state,
return buf;
}
- if (dest_format->reverse_endian) {
- buf = pcm_byteswap_32(&state->byteswap_buffer, buf, len);
- assert(buf != NULL);
- }
-
*dest_size_r = len;
return buf;
}
+static const float *
+pcm_convert_float(struct pcm_convert_state *state,
+ const struct audio_format *src_format,
+ const void *src_buffer, size_t src_size,
+ const struct audio_format *dest_format, size_t *dest_size_r,
+ GError **error_r)
+{
+ const float *buffer = src_buffer;
+ size_t size = src_size;
+
+ assert(dest_format->format == SAMPLE_FORMAT_FLOAT);
+
+ /* convert channels first, hoping the source format is
+ supported (float is not) */
+
+ if (dest_format->channels != src_format->channels) {
+ buffer = pcm_convert_channels(&state->channels_buffer,
+ src_format->format,
+ dest_format->channels,
+ src_format->channels,
+ buffer, size, &size, error_r);
+ if (buffer == NULL)
+ return NULL;
+ }
+
+ /* convert to float now */
+
+ buffer = pcm_convert_to_float(&state->format_buffer,
+ src_format->format,
+ buffer, size, &size);
+ if (buffer == NULL) {
+ g_set_error(error_r, pcm_convert_quark(), 0,
+ "Conversion from %s to float is not implemented",
+ sample_format_to_string(src_format->format));
+ return NULL;
+ }
+
+ /* resample with float, because this is the best format for
+ libsamplerate */
+
+ if (src_format->sample_rate != dest_format->sample_rate) {
+ buffer = pcm_resample_float(&state->resample,
+ dest_format->channels,
+ src_format->sample_rate,
+ buffer, size,
+ dest_format->sample_rate, &size,
+ error_r);
+ if (buffer == NULL)
+ return NULL;
+ }
+
+ *dest_size_r = size;
+ return buffer;
+}
+
const void *
pcm_convert(struct pcm_convert_state *state,
const struct audio_format *src_format,
@@ -269,6 +324,27 @@ pcm_convert(struct pcm_convert_state *state,
size_t *dest_size_r,
GError **error_r)
{
+ struct audio_format float_format;
+ if (src_format->format == SAMPLE_FORMAT_DSD) {
+ size_t f_size;
+ const float *f = pcm_dsd_to_float(&state->dsd,
+ src_format->channels,
+ false, src, src_size,
+ &f_size);
+ if (f == NULL) {
+ g_set_error_literal(error_r, pcm_convert_quark(), 0,
+ "DSD to PCM conversion failed");
+ return NULL;
+ }
+
+ float_format = *src_format;
+ float_format.format = SAMPLE_FORMAT_FLOAT;
+
+ src_format = &float_format;
+ src = f;
+ src_size = f_size;
+ }
+
switch (dest_format->format) {
case SAMPLE_FORMAT_S16:
return pcm_convert_16(state,
@@ -276,12 +352,6 @@ pcm_convert(struct pcm_convert_state *state,
dest_format, dest_size_r,
error_r);
- case SAMPLE_FORMAT_S24:
- return pcm_convert_24_packed(state,
- src_format, src, src_size,
- dest_format, dest_size_r,
- error_r);
-
case SAMPLE_FORMAT_S24_P32:
return pcm_convert_24(state,
src_format, src, src_size,
@@ -294,6 +364,12 @@ pcm_convert(struct pcm_convert_state *state,
dest_format, dest_size_r,
error_r);
+ case SAMPLE_FORMAT_FLOAT:
+ return pcm_convert_float(state,
+ src_format, src, src_size,
+ dest_format, dest_size_r,
+ error_r);
+
default:
g_set_error(error_r, pcm_convert_quark(), 0,
"PCM conversion to %s is not implemented",
diff --git a/src/pcm_convert.h b/src/pcm_convert.h
index 01ba2c787..be11a6e41 100644
--- a/src/pcm_convert.h
+++ b/src/pcm_convert.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -20,6 +20,7 @@
#ifndef PCM_CONVERT_H
#define PCM_CONVERT_H
+#include "pcm_dsd.h"
#include "pcm_resample.h"
#include "pcm_dither.h"
#include "pcm_buffer.h"
@@ -32,6 +33,8 @@ struct audio_format;
* conversions.
*/
struct pcm_convert_state {
+ struct pcm_dsd dsd;
+
struct pcm_resample_state resample;
struct pcm_dither dither;
@@ -39,14 +42,8 @@ struct pcm_convert_state {
/** the buffer for converting the sample format */
struct pcm_buffer format_buffer;
- /** the buffer for converting to/from packed samples */
- struct pcm_buffer pack_buffer;
-
/** the buffer for converting the channel count */
struct pcm_buffer channels_buffer;
-
- /** the buffer for swapping the byte order */
- struct pcm_buffer byteswap_buffer;
};
static inline GQuark
@@ -67,6 +64,13 @@ void pcm_convert_init(struct pcm_convert_state *state);
void pcm_convert_deinit(struct pcm_convert_state *state);
/**
+ * Reset the pcm_convert_state object. Use this at the boundary
+ * between two distinct songs and each time the format changes.
+ */
+void
+pcm_convert_reset(struct pcm_convert_state *state);
+
+/**
* Converts PCM data between two audio formats.
*
* @param state an initialized pcm_convert_state object
@@ -75,7 +79,7 @@ void pcm_convert_deinit(struct pcm_convert_state *state);
* @param src_size the size of #src in bytes
* @param dest_format the requested destination audio format
* @param dest_size_r returns the number of bytes of the destination buffer
- * @param error_r location to store the error occuring, or NULL to
+ * @param error_r location to store the error occurring, or NULL to
* ignore errors
* @return the destination buffer, or NULL on error
*/
diff --git a/src/pcm_dither.c b/src/pcm_dither.c
index 03388f0e0..4811946c8 100644
--- a/src/pcm_dither.c
+++ b/src/pcm_dither.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -72,10 +72,9 @@ pcm_dither_sample_24_to_16(int32_t sample, struct pcm_dither *dither)
void
pcm_dither_24_to_16(struct pcm_dither *dither,
- int16_t *dest, const int32_t *src,
- unsigned num_samples)
+ int16_t *dest, const int32_t *src, const int32_t *src_end)
{
- while (num_samples-- > 0)
+ while (src < src_end)
*dest++ = pcm_dither_sample_24_to_16(*src++, dither);
}
@@ -87,9 +86,8 @@ pcm_dither_sample_32_to_16(int32_t sample, struct pcm_dither *dither)
void
pcm_dither_32_to_16(struct pcm_dither *dither,
- int16_t *dest, const int32_t *src,
- unsigned num_samples)
+ int16_t *dest, const int32_t *src, const int32_t *src_end)
{
- while (num_samples-- > 0)
+ while (src < src_end)
*dest++ = pcm_dither_sample_32_to_16(*src++, dither);
}
diff --git a/src/pcm_dither.h b/src/pcm_dither.h
index dafae957f..046dea21e 100644
--- a/src/pcm_dither.h
+++ b/src/pcm_dither.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -36,12 +36,10 @@ pcm_dither_24_init(struct pcm_dither *dither)
void
pcm_dither_24_to_16(struct pcm_dither *dither,
- int16_t *dest, const int32_t *src,
- unsigned num_samples);
+ int16_t *dest, const int32_t *src, const int32_t *src_end);
void
pcm_dither_32_to_16(struct pcm_dither *dither,
- int16_t *dest, const int32_t *src,
- unsigned num_samples);
+ int16_t *dest, const int32_t *src, const int32_t *src_end);
#endif
diff --git a/src/pcm_dsd.c b/src/pcm_dsd.c
new file mode 100644
index 000000000..76266b4cc
--- /dev/null
+++ b/src/pcm_dsd.c
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2003-2012 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "pcm_dsd.h"
+#include "dsd2pcm/dsd2pcm.h"
+
+#include <glib.h>
+#include <string.h>
+
+void
+pcm_dsd_init(struct pcm_dsd *dsd)
+{
+ pcm_buffer_init(&dsd->buffer);
+
+ memset(dsd->dsd2pcm, 0, sizeof(dsd->dsd2pcm));
+}
+
+void
+pcm_dsd_deinit(struct pcm_dsd *dsd)
+{
+ pcm_buffer_deinit(&dsd->buffer);
+
+ for (unsigned i = 0; i < G_N_ELEMENTS(dsd->dsd2pcm); ++i)
+ if (dsd->dsd2pcm[i] != NULL)
+ dsd2pcm_destroy(dsd->dsd2pcm[i]);
+}
+
+void
+pcm_dsd_reset(struct pcm_dsd *dsd)
+{
+ for (unsigned i = 0; i < G_N_ELEMENTS(dsd->dsd2pcm); ++i)
+ if (dsd->dsd2pcm[i] != NULL)
+ dsd2pcm_reset(dsd->dsd2pcm[i]);
+}
+
+const float *
+pcm_dsd_to_float(struct pcm_dsd *dsd, unsigned channels, bool lsbfirst,
+ const uint8_t *src, size_t src_size,
+ size_t *dest_size_r)
+{
+ assert(dsd != NULL);
+ assert(src != NULL);
+ assert(src_size > 0);
+ assert(src_size % channels == 0);
+ assert(channels <= G_N_ELEMENTS(dsd->dsd2pcm));
+
+ const unsigned num_samples = src_size;
+ const unsigned num_frames = src_size / channels;
+
+ float *dest;
+ const size_t dest_size = num_samples * sizeof(*dest);
+ *dest_size_r = dest_size;
+ dest = pcm_buffer_get(&dsd->buffer, dest_size);
+
+ for (unsigned c = 0; c < channels; ++c) {
+ if (dsd->dsd2pcm[c] == NULL) {
+ dsd->dsd2pcm[c] = dsd2pcm_init();
+ if (dsd->dsd2pcm[c] == NULL)
+ return NULL;
+ }
+
+ dsd2pcm_translate(dsd->dsd2pcm[c], num_frames,
+ src + c, channels,
+ lsbfirst, dest + c, channels);
+ }
+
+ return dest;
+}
diff --git a/src/pcm_dsd.h b/src/pcm_dsd.h
new file mode 100644
index 000000000..85c2455aa
--- /dev/null
+++ b/src/pcm_dsd.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2003-2012 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_PCM_DSD_H
+#define MPD_PCM_DSD_H
+
+#include "check.h"
+#include "pcm_buffer.h"
+
+#include <stdbool.h>
+#include <stdint.h>
+
+/**
+ * Wrapper for the dsd2pcm library.
+ */
+struct pcm_dsd {
+ struct pcm_buffer buffer;
+
+ struct dsd2pcm_ctx_s *dsd2pcm[32];
+};
+
+void
+pcm_dsd_init(struct pcm_dsd *dsd);
+
+void
+pcm_dsd_deinit(struct pcm_dsd *dsd);
+
+void
+pcm_dsd_reset(struct pcm_dsd *dsd);
+
+const float *
+pcm_dsd_to_float(struct pcm_dsd *dsd, unsigned channels, bool lsbfirst,
+ const uint8_t *src, size_t src_size,
+ size_t *dest_size_r);
+
+#endif
diff --git a/src/pcm_dsd_usb.c b/src/pcm_dsd_usb.c
new file mode 100644
index 000000000..4b5e39f39
--- /dev/null
+++ b/src/pcm_dsd_usb.c
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2003-2012 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "pcm_dsd_usb.h"
+#include "pcm_buffer.h"
+#include "audio_format.h"
+
+G_GNUC_CONST
+static inline uint32_t
+pcm_two_dsd_to_usb_marker1(uint8_t a, uint8_t b)
+{
+ return 0xff050000 | (a << 8) | b;
+}
+
+G_GNUC_CONST
+static inline uint32_t
+pcm_two_dsd_to_usb_marker2(uint8_t a, uint8_t b)
+{
+ return 0xfffa0000 | (a << 8) | b;
+}
+
+
+const uint32_t *
+pcm_dsd_to_usb(struct pcm_buffer *buffer, unsigned channels,
+ const uint8_t *src, size_t src_size,
+ size_t *dest_size_r)
+{
+ assert(buffer != NULL);
+ assert(audio_valid_channel_count(channels));
+ assert(src != NULL);
+ assert(src_size > 0);
+ assert(src_size % channels == 0);
+
+ const unsigned num_src_samples = src_size;
+ const unsigned num_src_frames = num_src_samples / channels;
+
+ /* this rounds down and discards the last odd frame; not
+ elegant, but good enough for now */
+ const unsigned num_frames = num_src_frames / 2;
+ const unsigned num_samples = num_frames * channels;
+
+ const size_t dest_size = num_samples * 4;
+ *dest_size_r = dest_size;
+ uint32_t *const dest0 = pcm_buffer_get(buffer, dest_size),
+ *dest = dest0;
+
+ for (unsigned i = num_frames / 2; i > 0; --i) {
+ for (unsigned c = channels; c > 0; --c) {
+ /* each 24 bit sample has 16 DSD sample bits
+ plus the magic 0x05 marker */
+
+ *dest++ = pcm_two_dsd_to_usb_marker1(src[0], src[channels]);
+
+ /* seek the source pointer to the next
+ channel */
+ ++src;
+ }
+
+ /* skip the second byte of each channel, because we
+ have already copied it */
+ src += channels;
+
+ for (unsigned c = channels; c > 0; --c) {
+ /* each 24 bit sample has 16 DSD sample bits
+ plus the magic 0xfa marker */
+
+ *dest++ = pcm_two_dsd_to_usb_marker2(src[0], src[channels]);
+
+ /* seek the source pointer to the next
+ channel */
+ ++src;
+ }
+
+ /* skip the second byte of each channel, because we
+ have already copied it */
+ src += channels;
+ }
+
+ return dest0;
+}
diff --git a/src/songvec.h b/src/pcm_dsd_usb.h
index 8a50b974b..389358459 100644
--- a/src/songvec.h
+++ b/src/pcm_dsd_usb.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2012 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -17,35 +17,26 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
-#ifndef MPD_SONGVEC_H
-#define MPD_SONGVEC_H
+#ifndef MPD_PCM_DSD_USB_H
+#define MPD_PCM_DSD_USB_H
-#include <stddef.h>
-
-struct songvec {
- struct song **base;
- size_t nr;
-};
-
-void songvec_init(void);
-
-void songvec_deinit(void);
-
-void songvec_sort(struct songvec *sv);
+#include "check.h"
-struct song *
-songvec_find(const struct songvec *sv, const char *uri);
-
-int
-songvec_delete(struct songvec *sv, const struct song *del);
-
-void
-songvec_add(struct songvec *sv, struct song *add);
+#include <stdbool.h>
+#include <stdint.h>
+#include <stddef.h>
-void songvec_destroy(struct songvec *sv);
+struct pcm_buffer;
-int
-songvec_for_each(const struct songvec *sv,
- int (*fn)(struct song *, void *), void *arg);
+/**
+ * Pack DSD 1 bit samples into (padded) 24 bit PCM samples for
+ * playback over USB, according to the proposed standard by
+ * dCS and others:
+ * http://www.sonore.us/DoP_openStandard_1v1.pdf
+ */
+const uint32_t *
+pcm_dsd_to_usb(struct pcm_buffer *buffer, unsigned channels,
+ const uint8_t *src, size_t src_size,
+ size_t *dest_size_r);
-#endif /* SONGVEC_H */
+#endif
diff --git a/src/pcm_export.c b/src/pcm_export.c
new file mode 100644
index 000000000..e586b51d2
--- /dev/null
+++ b/src/pcm_export.c
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2003-2012 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "pcm_export.h"
+#include "pcm_dsd_usb.h"
+#include "pcm_pack.h"
+#include "util/byte_reverse.h"
+
+void
+pcm_export_init(struct pcm_export_state *state)
+{
+ pcm_buffer_init(&state->reverse_buffer);
+ pcm_buffer_init(&state->pack_buffer);
+ pcm_buffer_init(&state->dsd_buffer);
+}
+
+void pcm_export_deinit(struct pcm_export_state *state)
+{
+ pcm_buffer_deinit(&state->reverse_buffer);
+ pcm_buffer_deinit(&state->pack_buffer);
+ pcm_buffer_deinit(&state->dsd_buffer);
+}
+
+void
+pcm_export_open(struct pcm_export_state *state,
+ enum sample_format sample_format, unsigned channels,
+ bool dsd_usb, bool shift8, bool pack, bool reverse_endian)
+{
+ assert(audio_valid_sample_format(sample_format));
+ assert(!dsd_usb || audio_valid_channel_count(channels));
+
+ state->channels = channels;
+ state->dsd_usb = dsd_usb && sample_format == SAMPLE_FORMAT_DSD;
+ if (state->dsd_usb)
+ /* after the conversion to DSD-over-USB, the DSD
+ samples are stuffed inside fake 24 bit samples */
+ sample_format = SAMPLE_FORMAT_S24_P32;
+
+ state->shift8 = shift8 && sample_format == SAMPLE_FORMAT_S24_P32;
+ state->pack24 = pack && sample_format == SAMPLE_FORMAT_S24_P32;
+
+ assert(!state->shift8 || !state->pack24);
+
+ state->reverse_endian = 0;
+ if (reverse_endian) {
+ size_t sample_size = state->pack24
+ ? 3
+ : sample_format_size(sample_format);
+ assert(sample_size <= 0xff);
+
+ if (sample_size > 1)
+ state->reverse_endian = sample_size;
+ }
+}
+
+const void *
+pcm_export(struct pcm_export_state *state, const void *data, size_t size,
+ size_t *dest_size_r)
+{
+ if (state->dsd_usb)
+ data = pcm_dsd_to_usb(&state->dsd_buffer, state->channels,
+ data, size, &size);
+
+ if (state->pack24) {
+ assert(size % 4 == 0);
+
+ const size_t num_samples = size / 4;
+ const size_t dest_size = num_samples * 3;
+
+ const uint8_t *src8 = data, *src_end8 = src8 + size;
+ uint8_t *dest = pcm_buffer_get(&state->pack_buffer, dest_size);
+ assert(dest != NULL);
+
+ pcm_pack_24(dest, (const int32_t *)src8,
+ (const int32_t *)src_end8);
+
+ data = dest;
+ size = dest_size;
+ } else if (state->shift8) {
+ assert(size % 4 == 0);
+
+ const uint8_t *src8 = data, *src_end8 = src8 + size;
+ const uint32_t *src = (const uint32_t *)src8;
+ const uint32_t *const src_end = (const uint32_t *)src_end8;
+
+ uint32_t *dest = pcm_buffer_get(&state->pack_buffer, size);
+ data = dest;
+
+ while (src < src_end)
+ *dest++ = *src++ << 8;
+ }
+
+
+ if (state->reverse_endian > 0) {
+ assert(state->reverse_endian >= 2);
+
+ void *dest = pcm_buffer_get(&state->reverse_buffer, size);
+ assert(dest != NULL);
+
+ const uint8_t *src = data, *src_end = src + size;
+ reverse_bytes(dest, src, src_end, state->reverse_endian);
+
+ data = dest;
+ }
+
+ *dest_size_r = size;
+ return data;
+}
+
+size_t
+pcm_export_source_size(const struct pcm_export_state *state, size_t size)
+{
+ if (state->dsd_usb)
+ /* DSD over USB doubles the transport size */
+ size /= 2;
+
+ return size;
+}
diff --git a/src/pcm_export.h b/src/pcm_export.h
new file mode 100644
index 000000000..418dcdfa3
--- /dev/null
+++ b/src/pcm_export.h
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2003-2012 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef PCM_EXPORT_H
+#define PCM_EXPORT_H
+
+#include "check.h"
+#include "pcm_buffer.h"
+#include "audio_format.h"
+
+#include <stdbool.h>
+
+struct audio_format;
+
+/**
+ * An object that handles export of PCM samples to some instance
+ * outside of MPD. It has a few more options to tweak the binary
+ * representation which are not supported by the pcm_convert library.
+ */
+struct pcm_export_state {
+ /**
+ * The buffer is used to convert DSD samples to the
+ * DSD-over-USB format.
+ *
+ * @see #dsd_usb
+ */
+ struct pcm_buffer dsd_buffer;
+
+ /**
+ * The buffer is used to pack samples, removing padding.
+ *
+ * @see #pack24
+ */
+ struct pcm_buffer pack_buffer;
+
+ /**
+ * The buffer is used to reverse the byte order.
+ *
+ * @see #reverse_endian
+ */
+ struct pcm_buffer reverse_buffer;
+
+ /**
+ * The number of channels.
+ */
+ uint8_t channels;
+
+ /**
+ * Convert DSD to DSD-over-USB? Input format must be
+ * SAMPLE_FORMAT_DSD and output format must be
+ * SAMPLE_FORMAT_S24_P32.
+ */
+ bool dsd_usb;
+
+ /**
+ * Convert (padded) 24 bit samples to 32 bit by shifting 8
+ * bits to the left?
+ */
+ bool shift8;
+
+ /**
+ * Pack 24 bit samples?
+ */
+ bool pack24;
+
+ /**
+ * Export the samples in reverse byte order? A non-zero value
+ * means the option is enabled and represents the size of each
+ * sample (2 or bigger).
+ */
+ uint8_t reverse_endian;
+};
+
+/**
+ * Initialize a #pcm_export_state object.
+ */
+void
+pcm_export_init(struct pcm_export_state *state);
+
+/**
+ * Deinitialize a #pcm_export_state object and free allocated memory.
+ */
+void
+pcm_export_deinit(struct pcm_export_state *state);
+
+/**
+ * Open the #pcm_export_state object.
+ *
+ * There is no "close" method. This function may be called multiple
+ * times to reuse the object, until pcm_export_deinit() is called.
+ *
+ * This function cannot fail.
+ *
+ * @param channels the number of channels; ignored unless dsd_usb is set
+ */
+void
+pcm_export_open(struct pcm_export_state *state,
+ enum sample_format sample_format, unsigned channels,
+ bool dsd_usb, bool shift8, bool pack, bool reverse_endian);
+
+/**
+ * Export a PCM buffer.
+ *
+ * @param state an initialized and open pcm_export_state object
+ * @param src the source PCM buffer
+ * @param src_size the size of #src in bytes
+ * @param dest_size_r returns the number of bytes of the destination buffer
+ * @return the destination buffer (may be a pointer to the source buffer)
+ */
+const void *
+pcm_export(struct pcm_export_state *state, const void *src, size_t src_size,
+ size_t *dest_size_r);
+
+/**
+ * Converts the number of consumed bytes from the pcm_export()
+ * destination buffer to the according number of bytes from the
+ * pcm_export() source buffer.
+ */
+G_GNUC_PURE
+size_t
+pcm_export_source_size(const struct pcm_export_state *state, size_t dest_size);
+
+#endif
diff --git a/src/pcm_format.c b/src/pcm_format.c
index 1e4b8d705..d3ea3acb0 100644
--- a/src/pcm_format.c
+++ b/src/pcm_format.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -22,39 +22,92 @@
#include "pcm_dither.h"
#include "pcm_buffer.h"
#include "pcm_pack.h"
+#include "pcm_utils.h"
static void
-pcm_convert_8_to_16(int16_t *out, const int8_t *in,
- unsigned num_samples)
+pcm_convert_8_to_16(int16_t *out, const int8_t *in, const int8_t *in_end)
{
- while (num_samples > 0) {
+ while (in < in_end) {
*out++ = *in++ << 8;
- --num_samples;
}
}
static void
pcm_convert_24_to_16(struct pcm_dither *dither,
- int16_t *out, const int32_t *in,
- unsigned num_samples)
+ int16_t *out, const int32_t *in, const int32_t *in_end)
{
- pcm_dither_24_to_16(dither, out, in, num_samples);
+ pcm_dither_24_to_16(dither, out, in, in_end);
}
static void
pcm_convert_32_to_16(struct pcm_dither *dither,
- int16_t *out, const int32_t *in,
- unsigned num_samples)
+ int16_t *out, const int32_t *in, const int32_t *in_end)
{
- pcm_dither_32_to_16(dither, out, in, num_samples);
+ pcm_dither_32_to_16(dither, out, in, in_end);
}
-static int32_t *
-pcm_convert_24_to_24p32(struct pcm_buffer *buffer, const uint8_t *src,
- unsigned num_samples)
+static void
+pcm_convert_float_to_16(int16_t *out, const float *in, const float *in_end)
+{
+ const unsigned OUT_BITS = 16;
+ const float factor = 1 << (OUT_BITS - 1);
+
+ while (in < in_end) {
+ int sample = *in++ * factor;
+ *out++ = pcm_clamp_16(sample);
+ }
+}
+
+static int16_t *
+pcm_allocate_8_to_16(struct pcm_buffer *buffer,
+ const int8_t *src, size_t src_size, size_t *dest_size_r)
+{
+ int16_t *dest;
+ *dest_size_r = src_size / sizeof(*src) * sizeof(*dest);
+ dest = pcm_buffer_get(buffer, *dest_size_r);
+ pcm_convert_8_to_16(dest, src, pcm_end_pointer(src, src_size));
+ return dest;
+}
+
+static int16_t *
+pcm_allocate_24p32_to_16(struct pcm_buffer *buffer, struct pcm_dither *dither,
+ const int32_t *src, size_t src_size,
+ size_t *dest_size_r)
+{
+ int16_t *dest;
+ *dest_size_r = src_size / 2;
+ assert(*dest_size_r == src_size / sizeof(*src) * sizeof(*dest));
+ dest = pcm_buffer_get(buffer, *dest_size_r);
+ pcm_convert_24_to_16(dither, dest, src,
+ pcm_end_pointer(src, src_size));
+ return dest;
+}
+
+static int16_t *
+pcm_allocate_32_to_16(struct pcm_buffer *buffer, struct pcm_dither *dither,
+ const int32_t *src, size_t src_size,
+ size_t *dest_size_r)
+{
+ int16_t *dest;
+ *dest_size_r = src_size / 2;
+ assert(*dest_size_r == src_size / sizeof(*src) * sizeof(*dest));
+ dest = pcm_buffer_get(buffer, *dest_size_r);
+ pcm_convert_32_to_16(dither, dest, src,
+ pcm_end_pointer(src, src_size));
+ return dest;
+}
+
+static int16_t *
+pcm_allocate_float_to_16(struct pcm_buffer *buffer,
+ const float *src, size_t src_size,
+ size_t *dest_size_r)
{
- int32_t *dest = pcm_buffer_get(buffer, num_samples * 4);
- pcm_unpack_24(dest, src, num_samples, false);
+ int16_t *dest;
+ *dest_size_r = src_size / 2;
+ assert(*dest_size_r == src_size / sizeof(*src) * sizeof(*dest));
+ dest = pcm_buffer_get(buffer, *dest_size_r);
+ pcm_convert_float_to_16(dest, src,
+ pcm_end_pointer(src, src_size));
return dest;
}
@@ -63,176 +116,221 @@ pcm_convert_to_16(struct pcm_buffer *buffer, struct pcm_dither *dither,
enum sample_format src_format, const void *src,
size_t src_size, size_t *dest_size_r)
{
- unsigned num_samples;
- int16_t *dest;
- int32_t *dest32;
+ assert(src_size % sample_format_size(src_format) == 0);
switch (src_format) {
case SAMPLE_FORMAT_UNDEFINED:
+ case SAMPLE_FORMAT_DSD:
break;
case SAMPLE_FORMAT_S8:
- num_samples = src_size;
- *dest_size_r = src_size * sizeof(*dest);
- dest = pcm_buffer_get(buffer, *dest_size_r);
-
- pcm_convert_8_to_16(dest,
- (const int8_t *)src,
- num_samples);
- return dest;
+ return pcm_allocate_8_to_16(buffer,
+ src, src_size, dest_size_r);
case SAMPLE_FORMAT_S16:
*dest_size_r = src_size;
return src;
- case SAMPLE_FORMAT_S24:
- /* convert to S24_P32 first */
- num_samples = src_size / 3;
-
- dest32 = pcm_convert_24_to_24p32(buffer, src, num_samples);
- dest = (int16_t *)dest32;
-
- /* convert to 16 bit in-place */
- *dest_size_r = num_samples * sizeof(*dest);
- pcm_convert_24_to_16(dither, dest, dest32,
- num_samples);
- return dest;
-
case SAMPLE_FORMAT_S24_P32:
- num_samples = src_size / 4;
- *dest_size_r = num_samples * sizeof(*dest);
- dest = pcm_buffer_get(buffer, *dest_size_r);
-
- pcm_convert_24_to_16(dither, dest,
- (const int32_t *)src,
- num_samples);
- return dest;
+ return pcm_allocate_24p32_to_16(buffer, dither, src, src_size,
+ dest_size_r);
case SAMPLE_FORMAT_S32:
- num_samples = src_size / 4;
- *dest_size_r = num_samples * sizeof(*dest);
- dest = pcm_buffer_get(buffer, *dest_size_r);
-
- pcm_convert_32_to_16(dither, dest,
- (const int32_t *)src,
- num_samples);
- return dest;
+ return pcm_allocate_32_to_16(buffer, dither, src, src_size,
+ dest_size_r);
+
+ case SAMPLE_FORMAT_FLOAT:
+ return pcm_allocate_float_to_16(buffer, src, src_size,
+ dest_size_r);
}
return NULL;
}
static void
-pcm_convert_8_to_24(int32_t *out, const int8_t *in,
- unsigned num_samples)
+pcm_convert_8_to_24(int32_t *out, const int8_t *in, const int8_t *in_end)
{
- while (num_samples > 0) {
+ while (in < in_end)
*out++ = *in++ << 16;
- --num_samples;
- }
}
static void
-pcm_convert_16_to_24(int32_t *out, const int16_t *in,
- unsigned num_samples)
+pcm_convert_16_to_24(int32_t *out, const int16_t *in, const int16_t *in_end)
{
- while (num_samples > 0) {
+ while (in < in_end)
*out++ = *in++ << 8;
- --num_samples;
- }
}
static void
-pcm_convert_32_to_24(int32_t *out, const int32_t *in,
- unsigned num_samples)
+pcm_convert_32_to_24(int32_t *restrict out,
+ const int32_t *restrict in,
+ const int32_t *restrict in_end)
{
- while (num_samples > 0) {
+ while (in < in_end)
*out++ = *in++ >> 8;
- --num_samples;
+}
+
+static void
+pcm_convert_float_to_24(int32_t *out, const float *in, const float *in_end)
+{
+ const unsigned OUT_BITS = 24;
+ const float factor = 1 << (OUT_BITS - 1);
+
+ while (in < in_end) {
+ int sample = *in++ * factor;
+ *out++ = pcm_clamp_24(sample);
}
}
+static int32_t *
+pcm_allocate_8_to_24(struct pcm_buffer *buffer,
+ const int8_t *src, size_t src_size, size_t *dest_size_r)
+{
+ int32_t *dest;
+ *dest_size_r = src_size / sizeof(*src) * sizeof(*dest);
+ dest = pcm_buffer_get(buffer, *dest_size_r);
+ pcm_convert_8_to_24(dest, src, pcm_end_pointer(src, src_size));
+ return dest;
+}
+
+static int32_t *
+pcm_allocate_16_to_24(struct pcm_buffer *buffer,
+ const int16_t *src, size_t src_size, size_t *dest_size_r)
+{
+ int32_t *dest;
+ *dest_size_r = src_size * 2;
+ assert(*dest_size_r == src_size / sizeof(*src) * sizeof(*dest));
+ dest = pcm_buffer_get(buffer, *dest_size_r);
+ pcm_convert_16_to_24(dest, src, pcm_end_pointer(src, src_size));
+ return dest;
+}
+
+static int32_t *
+pcm_allocate_32_to_24(struct pcm_buffer *buffer,
+ const int32_t *src, size_t src_size, size_t *dest_size_r)
+{
+ *dest_size_r = src_size;
+ int32_t *dest = pcm_buffer_get(buffer, *dest_size_r);
+ pcm_convert_32_to_24(dest, src, pcm_end_pointer(src, src_size));
+ return dest;
+}
+
+static int32_t *
+pcm_allocate_float_to_24(struct pcm_buffer *buffer,
+ const float *src, size_t src_size,
+ size_t *dest_size_r)
+{
+ *dest_size_r = src_size;
+ int32_t *dest = pcm_buffer_get(buffer, *dest_size_r);
+ pcm_convert_float_to_24(dest, src, pcm_end_pointer(src, src_size));
+ return dest;
+}
+
const int32_t *
pcm_convert_to_24(struct pcm_buffer *buffer,
enum sample_format src_format, const void *src,
size_t src_size, size_t *dest_size_r)
{
- unsigned num_samples;
- int32_t *dest;
+ assert(src_size % sample_format_size(src_format) == 0);
switch (src_format) {
case SAMPLE_FORMAT_UNDEFINED:
+ case SAMPLE_FORMAT_DSD:
break;
case SAMPLE_FORMAT_S8:
- num_samples = src_size;
- *dest_size_r = src_size * sizeof(*dest);
- dest = pcm_buffer_get(buffer, *dest_size_r);
-
- pcm_convert_8_to_24(dest, (const int8_t *)src,
- num_samples);
- return dest;
+ return pcm_allocate_8_to_24(buffer,
+ src, src_size, dest_size_r);
case SAMPLE_FORMAT_S16:
- num_samples = src_size / 2;
- *dest_size_r = num_samples * sizeof(*dest);
- dest = pcm_buffer_get(buffer, *dest_size_r);
-
- pcm_convert_16_to_24(dest, (const int16_t *)src,
- num_samples);
- return dest;
-
- case SAMPLE_FORMAT_S24:
- num_samples = src_size / 3;
- *dest_size_r = num_samples * sizeof(*dest);
-
- return pcm_convert_24_to_24p32(buffer, src, num_samples);
+ return pcm_allocate_16_to_24(buffer,
+ src, src_size, dest_size_r);
case SAMPLE_FORMAT_S24_P32:
*dest_size_r = src_size;
return src;
case SAMPLE_FORMAT_S32:
- num_samples = src_size / 4;
- *dest_size_r = num_samples * sizeof(*dest);
- dest = pcm_buffer_get(buffer, *dest_size_r);
+ return pcm_allocate_32_to_24(buffer, src, src_size,
+ dest_size_r);
- pcm_convert_32_to_24(dest, (const int32_t *)src,
- num_samples);
- return dest;
+ case SAMPLE_FORMAT_FLOAT:
+ return pcm_allocate_float_to_24(buffer, src, src_size,
+ dest_size_r);
}
return NULL;
}
static void
-pcm_convert_8_to_32(int32_t *out, const int8_t *in,
- unsigned num_samples)
+pcm_convert_8_to_32(int32_t *out, const int8_t *in, const int8_t *in_end)
{
- while (num_samples > 0) {
+ while (in < in_end)
*out++ = *in++ << 24;
- --num_samples;
- }
}
static void
-pcm_convert_16_to_32(int32_t *out, const int16_t *in,
- unsigned num_samples)
+pcm_convert_16_to_32(int32_t *out, const int16_t *in, const int16_t *in_end)
{
- while (num_samples > 0) {
+ while (in < in_end)
*out++ = *in++ << 16;
- --num_samples;
- }
}
static void
-pcm_convert_24_to_32(int32_t *out, const int32_t *in,
- unsigned num_samples)
+pcm_convert_24_to_32(int32_t *restrict out,
+ const int32_t *restrict in,
+ const int32_t *restrict in_end)
{
- while (num_samples > 0) {
+ while (in < in_end)
*out++ = *in++ << 8;
- --num_samples;
- }
+}
+
+static int32_t *
+pcm_allocate_8_to_32(struct pcm_buffer *buffer,
+ const int8_t *src, size_t src_size, size_t *dest_size_r)
+{
+ int32_t *dest;
+ *dest_size_r = src_size / sizeof(*src) * sizeof(*dest);
+ dest = pcm_buffer_get(buffer, *dest_size_r);
+ pcm_convert_8_to_32(dest, src, pcm_end_pointer(src, src_size));
+ return dest;
+}
+
+static int32_t *
+pcm_allocate_16_to_32(struct pcm_buffer *buffer,
+ const int16_t *src, size_t src_size, size_t *dest_size_r)
+{
+ int32_t *dest;
+ *dest_size_r = src_size * 2;
+ assert(*dest_size_r == src_size / sizeof(*src) * sizeof(*dest));
+ dest = pcm_buffer_get(buffer, *dest_size_r);
+ pcm_convert_16_to_32(dest, src, pcm_end_pointer(src, src_size));
+ return dest;
+}
+
+static int32_t *
+pcm_allocate_24p32_to_32(struct pcm_buffer *buffer,
+ const int32_t *src, size_t src_size,
+ size_t *dest_size_r)
+{
+ *dest_size_r = src_size;
+ int32_t *dest = pcm_buffer_get(buffer, *dest_size_r);
+ pcm_convert_24_to_32(dest, src, pcm_end_pointer(src, src_size));
+ return dest;
+}
+
+static int32_t *
+pcm_allocate_float_to_32(struct pcm_buffer *buffer,
+ const float *src, size_t src_size,
+ size_t *dest_size_r)
+{
+ /* convert to S24_P32 first */
+ int32_t *dest = pcm_allocate_float_to_24(buffer, src, src_size,
+ dest_size_r);
+
+ /* convert to 32 bit in-place */
+ pcm_convert_24_to_32(dest, dest, pcm_end_pointer(dest, *dest_size_r));
+ return dest;
}
const int32_t *
@@ -240,52 +338,147 @@ pcm_convert_to_32(struct pcm_buffer *buffer,
enum sample_format src_format, const void *src,
size_t src_size, size_t *dest_size_r)
{
- unsigned num_samples;
- int32_t *dest;
+ assert(src_size % sample_format_size(src_format) == 0);
switch (src_format) {
case SAMPLE_FORMAT_UNDEFINED:
+ case SAMPLE_FORMAT_DSD:
break;
case SAMPLE_FORMAT_S8:
- num_samples = src_size;
- *dest_size_r = src_size * sizeof(*dest);
- dest = pcm_buffer_get(buffer, *dest_size_r);
-
- pcm_convert_8_to_32(dest, (const int8_t *)src,
- num_samples);
- return dest;
+ return pcm_allocate_8_to_32(buffer, src, src_size,
+ dest_size_r);
case SAMPLE_FORMAT_S16:
- num_samples = src_size / 2;
- *dest_size_r = num_samples * sizeof(*dest);
- dest = pcm_buffer_get(buffer, *dest_size_r);
+ return pcm_allocate_16_to_32(buffer, src, src_size,
+ dest_size_r);
- pcm_convert_16_to_32(dest, (const int16_t *)src,
- num_samples);
- return dest;
+ case SAMPLE_FORMAT_S24_P32:
+ return pcm_allocate_24p32_to_32(buffer, src, src_size,
+ dest_size_r);
- case SAMPLE_FORMAT_S24:
- /* convert to S24_P32 first */
- num_samples = src_size / 3;
+ case SAMPLE_FORMAT_S32:
+ *dest_size_r = src_size;
+ return src;
- dest = pcm_convert_24_to_24p32(buffer, src, num_samples);
+ case SAMPLE_FORMAT_FLOAT:
+ return pcm_allocate_float_to_32(buffer, src, src_size,
+ dest_size_r);
+ }
- /* convert to 32 bit in-place */
- *dest_size_r = num_samples * sizeof(*dest);
- pcm_convert_24_to_32(dest, dest, num_samples);
- return dest;
+ return NULL;
+}
- case SAMPLE_FORMAT_S24_P32:
- num_samples = src_size / 4;
- *dest_size_r = num_samples * sizeof(*dest);
- dest = pcm_buffer_get(buffer, *dest_size_r);
+static void
+pcm_convert_8_to_float(float *out, const int8_t *in, const int8_t *in_end)
+{
+ enum { in_bits = sizeof(*in) * 8 };
+ static const float factor = 2.0f / (1 << in_bits);
+ while (in < in_end)
+ *out++ = (float)*in++ * factor;
+}
- pcm_convert_24_to_32(dest, (const int32_t *)src,
- num_samples);
- return dest;
+static void
+pcm_convert_16_to_float(float *out, const int16_t *in, const int16_t *in_end)
+{
+ enum { in_bits = sizeof(*in) * 8 };
+ static const float factor = 2.0f / (1 << in_bits);
+ while (in < in_end)
+ *out++ = (float)*in++ * factor;
+}
+
+static void
+pcm_convert_24_to_float(float *out, const int32_t *in, const int32_t *in_end)
+{
+ enum { in_bits = 24 };
+ static const float factor = 2.0f / (1 << in_bits);
+ while (in < in_end)
+ *out++ = (float)*in++ * factor;
+}
+
+static void
+pcm_convert_32_to_float(float *out, const int32_t *in, const int32_t *in_end)
+{
+ enum { in_bits = sizeof(*in) * 8 };
+ static const float factor = 0.5f / (1 << (in_bits - 2));
+ while (in < in_end)
+ *out++ = (float)*in++ * factor;
+}
+
+static float *
+pcm_allocate_8_to_float(struct pcm_buffer *buffer,
+ const int8_t *src, size_t src_size,
+ size_t *dest_size_r)
+{
+ float *dest;
+ *dest_size_r = src_size / sizeof(*src) * sizeof(*dest);
+ dest = pcm_buffer_get(buffer, *dest_size_r);
+ pcm_convert_8_to_float(dest, src, pcm_end_pointer(src, src_size));
+ return dest;
+}
+
+static float *
+pcm_allocate_16_to_float(struct pcm_buffer *buffer,
+ const int16_t *src, size_t src_size,
+ size_t *dest_size_r)
+{
+ float *dest;
+ *dest_size_r = src_size * 2;
+ assert(*dest_size_r == src_size / sizeof(*src) * sizeof(*dest));
+ dest = pcm_buffer_get(buffer, *dest_size_r);
+ pcm_convert_16_to_float(dest, src, pcm_end_pointer(src, src_size));
+ return dest;
+}
+
+static float *
+pcm_allocate_24p32_to_float(struct pcm_buffer *buffer,
+ const int32_t *src, size_t src_size,
+ size_t *dest_size_r)
+{
+ *dest_size_r = src_size;
+ float *dest = pcm_buffer_get(buffer, *dest_size_r);
+ pcm_convert_24_to_float(dest, src, pcm_end_pointer(src, src_size));
+ return dest;
+}
+
+static float *
+pcm_allocate_32_to_float(struct pcm_buffer *buffer,
+ const int32_t *src, size_t src_size,
+ size_t *dest_size_r)
+{
+ *dest_size_r = src_size;
+ float *dest = pcm_buffer_get(buffer, *dest_size_r);
+ pcm_convert_32_to_float(dest, src, pcm_end_pointer(src, src_size));
+ return dest;
+}
+
+const float *
+pcm_convert_to_float(struct pcm_buffer *buffer,
+ enum sample_format src_format, const void *src,
+ size_t src_size, size_t *dest_size_r)
+{
+ switch (src_format) {
+ case SAMPLE_FORMAT_UNDEFINED:
+ case SAMPLE_FORMAT_DSD:
+ break;
+
+ case SAMPLE_FORMAT_S8:
+ return pcm_allocate_8_to_float(buffer,
+ src, src_size, dest_size_r);
+
+ case SAMPLE_FORMAT_S16:
+ return pcm_allocate_16_to_float(buffer,
+ src, src_size, dest_size_r);
+
+ case SAMPLE_FORMAT_S24_P32:
+ return pcm_allocate_24p32_to_float(buffer,
+ src, src_size, dest_size_r);
case SAMPLE_FORMAT_S32:
+ return pcm_allocate_32_to_float(buffer,
+ src, src_size, dest_size_r);
+
+ case SAMPLE_FORMAT_FLOAT:
*dest_size_r = src_size;
return src;
}
diff --git a/src/pcm_format.h b/src/pcm_format.h
index 3e96fc65f..48bcd0662 100644
--- a/src/pcm_format.h
+++ b/src/pcm_format.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -75,4 +75,19 @@ pcm_convert_to_32(struct pcm_buffer *buffer,
enum sample_format src_format, const void *src,
size_t src_size, size_t *dest_size_r);
+/**
+ * Converts PCM samples to 32 bit floating point.
+ *
+ * @param buffer a pcm_buffer object
+ * @param bits the number of in the source buffer
+ * @param src the source PCM buffer
+ * @param src_size the size of #src in bytes
+ * @param dest_size_r returns the number of bytes of the destination buffer
+ * @return the destination buffer
+ */
+const float *
+pcm_convert_to_float(struct pcm_buffer *buffer,
+ enum sample_format src_format, const void *src,
+ size_t src_size, size_t *dest_size_r);
+
#endif
diff --git a/src/pcm_mix.c b/src/pcm_mix.c
index 3145c07be..6c6d1b4ab 100644
--- a/src/pcm_mix.c
+++ b/src/pcm_mix.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -22,7 +22,6 @@
#include "pcm_volume.h"
#include "pcm_utils.h"
#include "audio_format.h"
-#include "mpd_error.h"
#include <glib.h>
@@ -100,35 +99,60 @@ pcm_add_vol_32(int32_t *buffer1, const int32_t *buffer2,
}
static void
+pcm_add_vol_float(float *buffer1, const float *buffer2,
+ unsigned num_samples, float volume1, float volume2)
+{
+ while (num_samples > 0) {
+ float sample1 = *buffer1;
+ float sample2 = *buffer2++;
+
+ sample1 = (sample1 * volume1 + sample2 * volume2);
+ *buffer1++ = sample1;
+ --num_samples;
+ }
+}
+
+static bool
pcm_add_vol(void *buffer1, const void *buffer2, size_t size,
int vol1, int vol2,
- const struct audio_format *format)
+ enum sample_format format)
{
- switch (format->format) {
+ switch (format) {
+ case SAMPLE_FORMAT_UNDEFINED:
+ case SAMPLE_FORMAT_DSD:
+ /* not implemented */
+ return false;
+
case SAMPLE_FORMAT_S8:
pcm_add_vol_8((int8_t *)buffer1, (const int8_t *)buffer2,
size, vol1, vol2);
- break;
+ return true;
case SAMPLE_FORMAT_S16:
pcm_add_vol_16((int16_t *)buffer1, (const int16_t *)buffer2,
size / 2, vol1, vol2);
- break;
+ return true;
case SAMPLE_FORMAT_S24_P32:
pcm_add_vol_24((int32_t *)buffer1, (const int32_t *)buffer2,
size / 4, vol1, vol2);
- break;
+ return true;
case SAMPLE_FORMAT_S32:
pcm_add_vol_32((int32_t *)buffer1, (const int32_t *)buffer2,
size / 4, vol1, vol2);
- break;
+ return true;
- default:
- MPD_ERROR("format %s not supported by pcm_add_vol",
- sample_format_to_string(format->format));
+ case SAMPLE_FORMAT_FLOAT:
+ pcm_add_vol_float(buffer1, buffer2, size / 4,
+ pcm_volume_to_float(vol1),
+ pcm_volume_to_float(vol2));
+ return true;
}
+
+ /* unreachable */
+ assert(false);
+ return false;
}
static void
@@ -188,45 +212,63 @@ pcm_add_32(int32_t *buffer1, const int32_t *buffer2, unsigned num_samples)
}
static void
+pcm_add_float(float *buffer1, const float *buffer2, unsigned num_samples)
+{
+ while (num_samples > 0) {
+ float sample1 = *buffer1;
+ float sample2 = *buffer2++;
+ *buffer1++ = sample1 + sample2;
+ --num_samples;
+ }
+}
+
+static bool
pcm_add(void *buffer1, const void *buffer2, size_t size,
- const struct audio_format *format)
+ enum sample_format format)
{
- switch (format->format) {
+ switch (format) {
+ case SAMPLE_FORMAT_UNDEFINED:
+ case SAMPLE_FORMAT_DSD:
+ /* not implemented */
+ return false;
+
case SAMPLE_FORMAT_S8:
pcm_add_8((int8_t *)buffer1, (const int8_t *)buffer2, size);
- break;
+ return true;
case SAMPLE_FORMAT_S16:
pcm_add_16((int16_t *)buffer1, (const int16_t *)buffer2, size / 2);
- break;
+ return true;
case SAMPLE_FORMAT_S24_P32:
pcm_add_24((int32_t *)buffer1, (const int32_t *)buffer2, size / 4);
- break;
+ return true;
case SAMPLE_FORMAT_S32:
pcm_add_32((int32_t *)buffer1, (const int32_t *)buffer2, size / 4);
- break;
+ return true;
- default:
- MPD_ERROR("format %s not supported by pcm_add",
- sample_format_to_string(format->format));
+ case SAMPLE_FORMAT_FLOAT:
+ pcm_add_float(buffer1, buffer2, size / 4);
+ return true;
}
+
+ /* unreachable */
+ assert(false);
+ return false;
}
-void
+bool
pcm_mix(void *buffer1, const void *buffer2, size_t size,
- const struct audio_format *format, float portion1)
+ enum sample_format format, float portion1)
{
int vol1;
float s;
/* portion1 is between 0.0 and 1.0 for crossfading, MixRamp uses NaN
* to signal mixing rather than fading */
- if (isnan(portion1)) {
- pcm_add(buffer1, buffer2, size, format);
- return;
- }
+ if (isnan(portion1))
+ return pcm_add(buffer1, buffer2, size, format);
s = sin(M_PI_2 * portion1);
s *= s;
@@ -234,5 +276,5 @@ pcm_mix(void *buffer1, const void *buffer2, size_t size,
vol1 = s * PCM_VOLUME_1 + 0.5;
vol1 = vol1 > PCM_VOLUME_1 ? PCM_VOLUME_1 : (vol1 < 0 ? 0 : vol1);
- pcm_add_vol(buffer1, buffer2, size, vol1, PCM_VOLUME_1 - vol1, format);
+ return pcm_add_vol(buffer1, buffer2, size, vol1, PCM_VOLUME_1 - vol1, format);
}
diff --git a/src/pcm_mix.h b/src/pcm_mix.h
index 086d5501e..0e58d01ee 100644
--- a/src/pcm_mix.h
+++ b/src/pcm_mix.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -20,9 +20,10 @@
#ifndef PCM_MIX_H
#define PCM_MIX_H
-#include <stddef.h>
+#include "audio_format.h"
-struct audio_format;
+#include <stdbool.h>
+#include <stddef.h>
/*
* Linearly mixes two PCM buffers. Both must have the same length and
@@ -33,13 +34,16 @@ struct audio_format;
* @param buffer1 the first PCM buffer, and the destination buffer
* @param buffer2 the second PCM buffer
* @param size the size of both buffers in bytes
- * @param format the audio format of both buffers
+ * @param format the sample format of both buffers
* @param portion1 a number between 0.0 and 1.0 specifying the portion
* of the first buffer in the mix; portion2 = (1.0 - portion1). The value
* NaN is used by the MixRamp code to specify that simple addition is required.
+ *
+ * @return true on success, false if the format is not supported
*/
-void
+G_GNUC_WARN_UNUSED_RESULT
+bool
pcm_mix(void *buffer1, const void *buffer2, size_t size,
- const struct audio_format *format, float portion1);
+ enum sample_format format, float portion1);
#endif
diff --git a/src/pcm_pack.c b/src/pcm_pack.c
index 9af0ab1ed..921d880c0 100644
--- a/src/pcm_pack.c
+++ b/src/pcm_pack.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -22,11 +22,11 @@
#include <glib.h>
static void
-pack_sample(uint8_t *dest, const int32_t *src0, bool reverse_endian)
+pack_sample(uint8_t *dest, const int32_t *src0)
{
const uint8_t *src = (const uint8_t *)src0;
- if ((G_BYTE_ORDER == G_BIG_ENDIAN) != reverse_endian)
+ if (G_BYTE_ORDER == G_BIG_ENDIAN)
++src;
*dest++ = *src++;
@@ -35,31 +35,23 @@ pack_sample(uint8_t *dest, const int32_t *src0, bool reverse_endian)
}
void
-pcm_pack_24(uint8_t *dest, const int32_t *src, unsigned num_samples,
- bool reverse_endian)
+pcm_pack_24(uint8_t *dest, const int32_t *src, const int32_t *src_end)
{
/* duplicate loop to help the compiler's optimizer (constant
parameter to the pack_sample() inline function) */
- if (G_LIKELY(!reverse_endian)) {
- while (num_samples-- > 0) {
- pack_sample(dest, src++, false);
- dest += 3;
- }
- } else {
- while (num_samples-- > 0) {
- pack_sample(dest, src++, true);
- dest += 3;
- }
+ while (src < src_end) {
+ pack_sample(dest, src++);
+ dest += 3;
}
}
static void
-unpack_sample(int32_t *dest0, const uint8_t *src, bool reverse_endian)
+unpack_sample(int32_t *dest0, const uint8_t *src)
{
uint8_t *dest = (uint8_t *)dest0;
- if ((G_BYTE_ORDER == G_BIG_ENDIAN) != reverse_endian)
+ if (G_BYTE_ORDER == G_BIG_ENDIAN)
/* extend the sign bit to the most fourth byte */
*dest++ = *src & 0x80 ? 0xff : 0x00;
@@ -67,27 +59,19 @@ unpack_sample(int32_t *dest0, const uint8_t *src, bool reverse_endian)
*dest++ = *src++;
*dest++ = *src;
- if ((G_BYTE_ORDER == G_LITTLE_ENDIAN) != reverse_endian)
+ if (G_BYTE_ORDER == G_LITTLE_ENDIAN)
/* extend the sign bit to the most fourth byte */
*dest++ = *src & 0x80 ? 0xff : 0x00;
}
void
-pcm_unpack_24(int32_t *dest, const uint8_t *src, unsigned num_samples,
- bool reverse_endian)
+pcm_unpack_24(int32_t *dest, const uint8_t *src, const uint8_t *src_end)
{
/* duplicate loop to help the compiler's optimizer (constant
parameter to the unpack_sample() inline function) */
- if (G_LIKELY(!reverse_endian)) {
- while (num_samples-- > 0) {
- unpack_sample(dest++, src, false);
- src += 3;
- }
- } else {
- while (num_samples-- > 0) {
- unpack_sample(dest++, src, true);
- src += 3;
- }
+ while (src < src_end) {
+ unpack_sample(dest++, src);
+ src += 3;
}
}
diff --git a/src/pcm_pack.h b/src/pcm_pack.h
index 3c99eaa35..f3184b403 100644
--- a/src/pcm_pack.h
+++ b/src/pcm_pack.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -37,11 +37,9 @@
* @param dest the destination buffer (array of triples)
* @param src the source buffer
* @param num_samples the number of samples to convert
- * @param reverse_endian is src and dest in non-host byte order?
*/
void
-pcm_pack_24(uint8_t *dest, const int32_t *src, unsigned num_samples,
- bool reverse_endian);
+pcm_pack_24(uint8_t *dest, const int32_t *src, const int32_t *src_end);
/**
* Converts packed 24 bit samples (3 bytes per sample) to padded 24
@@ -50,10 +48,8 @@ pcm_pack_24(uint8_t *dest, const int32_t *src, unsigned num_samples,
* @param dest the destination buffer
* @param src the source buffer (array of triples)
* @param num_samples the number of samples to convert
- * @param reverse_endian is src and dest in non-host byte order?
*/
void
-pcm_unpack_24(int32_t *dest, const uint8_t *src, unsigned num_samples,
- bool reverse_endian);
+pcm_unpack_24(int32_t *dest, const uint8_t *src, const uint8_t *src_end);
#endif
diff --git a/src/pcm_prng.h b/src/pcm_prng.h
index 186ed9d0e..457ba4b66 100644
--- a/src/pcm_prng.h
+++ b/src/pcm_prng.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/pcm_resample.c b/src/pcm_resample.c
index 4a7578e09..4bc057a7e 100644
--- a/src/pcm_resample.c
+++ b/src/pcm_resample.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -27,26 +27,43 @@
#include <string.h>
#ifdef HAVE_LIBSAMPLERATE
+static bool lsr_enabled;
+#endif
+
+#ifdef HAVE_LIBSAMPLERATE
static bool
pcm_resample_lsr_enabled(void)
{
- return strcmp(config_get_string(CONF_SAMPLERATE_CONVERTER, ""),
- "internal") != 0;
+ return lsr_enabled;
}
#endif
-void pcm_resample_init(struct pcm_resample_state *state)
+bool
+pcm_resample_global_init(GError **error_r)
{
- memset(state, 0, sizeof(*state));
-
#ifdef HAVE_LIBSAMPLERATE
- if (pcm_resample_lsr_enabled()) {
- pcm_buffer_init(&state->in);
- pcm_buffer_init(&state->out);
- }
+ const char *converter =
+ config_get_string(CONF_SAMPLERATE_CONVERTER, "");
+
+ lsr_enabled = strcmp(converter, "internal") != 0;
+ if (lsr_enabled)
+ return pcm_resample_lsr_global_init(converter, error_r);
+ else
+ return true;
+#else
+ (void)error_r;
+ return true;
#endif
+}
- pcm_buffer_init(&state->buffer);
+void pcm_resample_init(struct pcm_resample_state *state)
+{
+#ifdef HAVE_LIBSAMPLERATE
+ if (pcm_resample_lsr_enabled())
+ pcm_resample_lsr_init(state);
+ else
+#endif
+ pcm_resample_fallback_init(state);
}
void pcm_resample_deinit(struct pcm_resample_state *state)
@@ -59,9 +76,47 @@ void pcm_resample_deinit(struct pcm_resample_state *state)
pcm_resample_fallback_deinit(state);
}
+void
+pcm_resample_reset(struct pcm_resample_state *state)
+{
+#ifdef HAVE_LIBSAMPLERATE
+ pcm_resample_lsr_reset(state);
+#else
+ (void)state;
+#endif
+}
+
+const float *
+pcm_resample_float(struct pcm_resample_state *state,
+ unsigned channels,
+ unsigned src_rate,
+ const float *src_buffer, size_t src_size,
+ unsigned dest_rate, size_t *dest_size_r,
+ GError **error_r)
+{
+#ifdef HAVE_LIBSAMPLERATE
+ if (pcm_resample_lsr_enabled())
+ return pcm_resample_lsr_float(state, channels,
+ src_rate, src_buffer, src_size,
+ dest_rate, dest_size_r,
+ error_r);
+#else
+ (void)error_r;
+#endif
+
+ /* sizeof(float)==sizeof(int32_t); the fallback resampler does
+ not do any math on the sample values, so this hack is
+ possible: */
+ return (const float *)
+ pcm_resample_fallback_32(state, channels,
+ src_rate, (const int32_t *)src_buffer,
+ src_size,
+ dest_rate, dest_size_r);
+}
+
const int16_t *
pcm_resample_16(struct pcm_resample_state *state,
- uint8_t channels,
+ unsigned channels,
unsigned src_rate, const int16_t *src_buffer, size_t src_size,
unsigned dest_rate, size_t *dest_size_r,
GError **error_r)
@@ -83,7 +138,7 @@ pcm_resample_16(struct pcm_resample_state *state,
const int32_t *
pcm_resample_32(struct pcm_resample_state *state,
- uint8_t channels,
+ unsigned channels,
unsigned src_rate, const int32_t *src_buffer, size_t src_size,
unsigned dest_rate, size_t *dest_size_r,
GError **error_r)
diff --git a/src/pcm_resample.h b/src/pcm_resample.h
index 24d17ff9b..a49a24142 100644
--- a/src/pcm_resample.h
+++ b/src/pcm_resample.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -45,7 +45,7 @@ struct pcm_resample_state {
struct {
unsigned src_rate;
unsigned dest_rate;
- uint8_t channels;
+ unsigned channels;
} prev;
int error;
@@ -54,6 +54,9 @@ struct pcm_resample_state {
struct pcm_buffer buffer;
};
+bool
+pcm_resample_global_init(GError **error_r);
+
/**
* Initializes a pcm_resample_state object.
*/
@@ -66,6 +69,32 @@ void pcm_resample_init(struct pcm_resample_state *state);
void pcm_resample_deinit(struct pcm_resample_state *state);
/**
+ * @see pcm_convert_reset()
+ */
+void
+pcm_resample_reset(struct pcm_resample_state *state);
+
+/**
+ * Resamples 32 bit float data.
+ *
+ * @param state an initialized pcm_resample_state object
+ * @param channels the number of channels
+ * @param src_rate the source sample rate
+ * @param src the source PCM buffer
+ * @param src_size the size of #src in bytes
+ * @param dest_rate the requested destination sample rate
+ * @param dest_size_r returns the number of bytes of the destination buffer
+ * @return the destination buffer
+ */
+const float *
+pcm_resample_float(struct pcm_resample_state *state,
+ unsigned channels,
+ unsigned src_rate,
+ const float *src_buffer, size_t src_size,
+ unsigned dest_rate, size_t *dest_size_r,
+ GError **error_r);
+
+/**
* Resamples 16 bit PCM data.
*
* @param state an initialized pcm_resample_state object
@@ -79,7 +108,7 @@ void pcm_resample_deinit(struct pcm_resample_state *state);
*/
const int16_t *
pcm_resample_16(struct pcm_resample_state *state,
- uint8_t channels,
+ unsigned channels,
unsigned src_rate,
const int16_t *src_buffer, size_t src_size,
unsigned dest_rate, size_t *dest_size_r,
@@ -99,7 +128,7 @@ pcm_resample_16(struct pcm_resample_state *state,
*/
const int32_t *
pcm_resample_32(struct pcm_resample_state *state,
- uint8_t channels,
+ unsigned channels,
unsigned src_rate,
const int32_t *src_buffer, size_t src_size,
unsigned dest_rate, size_t *dest_size_r,
@@ -119,7 +148,7 @@ pcm_resample_32(struct pcm_resample_state *state,
*/
static inline const int32_t *
pcm_resample_24(struct pcm_resample_state *state,
- uint8_t channels,
+ unsigned channels,
unsigned src_rate,
const int32_t *src_buffer, size_t src_size,
unsigned dest_rate, size_t *dest_size_r,
diff --git a/src/pcm_resample_fallback.c b/src/pcm_resample_fallback.c
index 0c75d8ba4..1d1dfdf59 100644
--- a/src/pcm_resample_fallback.c
+++ b/src/pcm_resample_fallback.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -23,6 +23,12 @@
#include <assert.h>
void
+pcm_resample_fallback_init(struct pcm_resample_state *state)
+{
+ pcm_buffer_init(&state->buffer);
+}
+
+void
pcm_resample_fallback_deinit(struct pcm_resample_state *state)
{
pcm_buffer_deinit(&state->buffer);
@@ -31,7 +37,7 @@ pcm_resample_fallback_deinit(struct pcm_resample_state *state)
/* resampling code blatantly ripped from ESD */
const int16_t *
pcm_resample_fallback_16(struct pcm_resample_state *state,
- uint8_t channels,
+ unsigned channels,
unsigned src_rate,
const int16_t *src_buffer, size_t src_size,
unsigned dest_rate,
@@ -72,7 +78,7 @@ pcm_resample_fallback_16(struct pcm_resample_state *state,
const int32_t *
pcm_resample_fallback_32(struct pcm_resample_state *state,
- uint8_t channels,
+ unsigned channels,
unsigned src_rate,
const int32_t *src_buffer, size_t src_size,
unsigned dest_rate,
diff --git a/src/pcm_resample_internal.h b/src/pcm_resample_internal.h
index 26acc809d..a0e108d4b 100644
--- a/src/pcm_resample_internal.h
+++ b/src/pcm_resample_internal.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -32,12 +32,29 @@
#ifdef HAVE_LIBSAMPLERATE
+bool
+pcm_resample_lsr_global_init(const char *converter, GError **error_r);
+
+void
+pcm_resample_lsr_init(struct pcm_resample_state *state);
+
void
pcm_resample_lsr_deinit(struct pcm_resample_state *state);
+void
+pcm_resample_lsr_reset(struct pcm_resample_state *state);
+
+const float *
+pcm_resample_lsr_float(struct pcm_resample_state *state,
+ unsigned channels,
+ unsigned src_rate,
+ const float *src_buffer, size_t src_size,
+ unsigned dest_rate, size_t *dest_size_r,
+ GError **error_r);
+
const int16_t *
pcm_resample_lsr_16(struct pcm_resample_state *state,
- uint8_t channels,
+ unsigned channels,
unsigned src_rate,
const int16_t *src_buffer, size_t src_size,
unsigned dest_rate, size_t *dest_size_r,
@@ -45,7 +62,7 @@ pcm_resample_lsr_16(struct pcm_resample_state *state,
const int32_t *
pcm_resample_lsr_32(struct pcm_resample_state *state,
- uint8_t channels,
+ unsigned channels,
unsigned src_rate,
const int32_t *src_buffer,
G_GNUC_UNUSED size_t src_size,
@@ -55,11 +72,14 @@ pcm_resample_lsr_32(struct pcm_resample_state *state,
#endif
void
+pcm_resample_fallback_init(struct pcm_resample_state *state);
+
+void
pcm_resample_fallback_deinit(struct pcm_resample_state *state);
const int16_t *
pcm_resample_fallback_16(struct pcm_resample_state *state,
- uint8_t channels,
+ unsigned channels,
unsigned src_rate,
const int16_t *src_buffer, size_t src_size,
unsigned dest_rate,
@@ -67,7 +87,7 @@ pcm_resample_fallback_16(struct pcm_resample_state *state,
const int32_t *
pcm_resample_fallback_32(struct pcm_resample_state *state,
- uint8_t channels,
+ unsigned channels,
unsigned src_rate,
const int32_t *src_buffer,
G_GNUC_UNUSED size_t src_size,
diff --git a/src/pcm_resample_libsamplerate.c b/src/pcm_resample_libsamplerate.c
index 99ca53da4..f957e5155 100644
--- a/src/pcm_resample_libsamplerate.c
+++ b/src/pcm_resample_libsamplerate.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -30,12 +30,69 @@
#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "pcm"
+static int lsr_converter = SRC_SINC_FASTEST;
+
static inline GQuark
libsamplerate_quark(void)
{
return g_quark_from_static_string("libsamplerate");
}
+static bool
+lsr_parse_converter(const char *s)
+{
+ assert(s != NULL);
+
+ if (*s == 0)
+ return true;
+
+ char *endptr;
+ long l = strtol(s, &endptr, 10);
+ if (*endptr == 0 && src_get_name(l) != NULL) {
+ lsr_converter = l;
+ return true;
+ }
+
+ size_t length = strlen(s);
+ for (int i = 0;; ++i) {
+ const char *name = src_get_name(i);
+ if (name == NULL)
+ break;
+
+ if (g_ascii_strncasecmp(s, name, length) == 0) {
+ lsr_converter = i;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool
+pcm_resample_lsr_global_init(const char *converter, GError **error_r)
+{
+ if (!lsr_parse_converter(converter)) {
+ g_set_error(error_r, libsamplerate_quark(), 0,
+ "unknown samplerate converter '%s'", converter);
+ return false;
+ }
+
+ g_debug("libsamplerate converter '%s'",
+ src_get_name(lsr_converter));
+
+ return true;
+}
+
+void
+pcm_resample_lsr_init(struct pcm_resample_state *state)
+{
+ memset(state, 0, sizeof(*state));
+
+ pcm_buffer_init(&state->in);
+ pcm_buffer_init(&state->out);
+ pcm_buffer_init(&state->buffer);
+}
+
void
pcm_resample_lsr_deinit(struct pcm_resample_state *state)
{
@@ -47,54 +104,21 @@ pcm_resample_lsr_deinit(struct pcm_resample_state *state)
pcm_buffer_deinit(&state->buffer);
}
-static int pcm_resample_get_converter(void)
+void
+pcm_resample_lsr_reset(struct pcm_resample_state *state)
{
- const char *conf = config_get_string(CONF_SAMPLERATE_CONVERTER, NULL);
- long convalgo;
- char *test;
- const char *test2;
- size_t len;
-
- if (!conf) {
- convalgo = SRC_SINC_FASTEST;
- goto out;
- }
-
- convalgo = strtol(conf, &test, 10);
- if (*test == '\0' && src_get_name(convalgo))
- goto out;
-
- len = strlen(conf);
- for (convalgo = 0 ; ; convalgo++) {
- test2 = src_get_name(convalgo);
- if (!test2) {
- convalgo = SRC_SINC_FASTEST;
- break;
- }
- if (g_ascii_strncasecmp(test2, conf, len) == 0)
- goto out;
- }
-
- g_warning("unknown samplerate converter \"%s\"", conf);
-out:
- g_debug("selecting samplerate converter \"%s\"",
- src_get_name(convalgo));
-
- return convalgo;
+ if (state->state != NULL)
+ src_reset(state->state);
}
static bool
pcm_resample_set(struct pcm_resample_state *state,
- uint8_t channels, unsigned src_rate, unsigned dest_rate,
+ unsigned channels, unsigned src_rate, unsigned dest_rate,
GError **error_r)
{
- static int convalgo = -1;
int error;
SRC_DATA *data = &state->data;
- if (convalgo < 0)
- convalgo = pcm_resample_get_converter();
-
/* (re)set the state/ratio if the in or out format changed */
if (channels == state->prev.channels &&
src_rate == state->prev.src_rate &&
@@ -109,7 +133,7 @@ pcm_resample_set(struct pcm_resample_state *state,
if (state->state)
state->state = src_delete(state->state);
- state->state = src_new(convalgo, channels, &error);
+ state->state = src_new(lsr_converter, channels, &error);
if (!state->state) {
g_set_error(error_r, libsamplerate_quark(), state->error,
"libsamplerate initialization has failed: %s",
@@ -125,9 +149,63 @@ pcm_resample_set(struct pcm_resample_state *state,
return true;
}
+static bool
+lsr_process(struct pcm_resample_state *state, GError **error_r)
+{
+ if (state->error == 0)
+ state->error = src_process(state->state, &state->data);
+ if (state->error) {
+ g_set_error(error_r, libsamplerate_quark(), state->error,
+ "libsamplerate has failed: %s",
+ src_strerror(state->error));
+ return false;
+ }
+
+ return true;
+}
+
+static float *
+deconst_float_buffer(const float *in)
+{
+ union {
+ const float *in;
+ float *out;
+ } u = { .in = in };
+ return u.out;
+}
+
+const float *
+pcm_resample_lsr_float(struct pcm_resample_state *state,
+ unsigned channels,
+ unsigned src_rate,
+ const float *src_buffer, size_t src_size,
+ unsigned dest_rate, size_t *dest_size_r,
+ GError **error_r)
+{
+ assert((src_size % (sizeof(*src_buffer) * channels)) == 0);
+
+ if (!pcm_resample_set(state, channels, src_rate, dest_rate, error_r))
+ return NULL;
+
+ SRC_DATA *data = &state->data;
+ data->input_frames = src_size / sizeof(*src_buffer) / channels;
+ data->data_in = deconst_float_buffer(src_buffer);
+
+ data->output_frames = (src_size * dest_rate + src_rate - 1) / src_rate;
+ size_t data_out_size = data->output_frames * sizeof(float) * channels;
+ data->data_out = pcm_buffer_get(&state->out, data_out_size);
+
+ if (!lsr_process(state, error_r))
+ return NULL;
+
+ *dest_size_r = data->output_frames_gen *
+ sizeof(*data->data_out) * channels;
+ return data->data_out;
+}
+
const int16_t *
pcm_resample_lsr_16(struct pcm_resample_state *state,
- uint8_t channels,
+ unsigned channels,
unsigned src_rate,
const int16_t *src_buffer, size_t src_size,
unsigned dest_rate, size_t *dest_size_r,
@@ -137,7 +215,6 @@ pcm_resample_lsr_16(struct pcm_resample_state *state,
SRC_DATA *data = &state->data;
size_t data_in_size;
size_t data_out_size;
- int error;
int16_t *dest_buffer;
assert((src_size % (sizeof(*src_buffer) * channels)) == 0);
@@ -147,14 +224,6 @@ pcm_resample_lsr_16(struct pcm_resample_state *state,
if (!success)
return NULL;
- /* there was an error previously, and nothing has changed */
- if (state->error) {
- g_set_error(error_r, libsamplerate_quark(), state->error,
- "libsamplerate has failed: %s",
- src_strerror(state->error));
- return NULL;
- }
-
data->input_frames = src_size / sizeof(*src_buffer) / channels;
data_in_size = data->input_frames * sizeof(float) * channels;
data->data_in = pcm_buffer_get(&state->in, data_in_size);
@@ -166,14 +235,8 @@ pcm_resample_lsr_16(struct pcm_resample_state *state,
src_short_to_float_array(src_buffer, data->data_in,
data->input_frames * channels);
- error = src_process(state->state, data);
- if (error) {
- g_set_error(error_r, libsamplerate_quark(), error,
- "libsamplerate has failed: %s",
- src_strerror(error));
- state->error = error;
+ if (!lsr_process(state, error_r))
return NULL;
- }
*dest_size_r = data->output_frames_gen *
sizeof(*dest_buffer) * channels;
@@ -206,7 +269,7 @@ src_float_to_int_array (const float *in, int *out, int len)
const int32_t *
pcm_resample_lsr_32(struct pcm_resample_state *state,
- uint8_t channels,
+ unsigned channels,
unsigned src_rate,
const int32_t *src_buffer, size_t src_size,
unsigned dest_rate, size_t *dest_size_r,
@@ -216,7 +279,6 @@ pcm_resample_lsr_32(struct pcm_resample_state *state,
SRC_DATA *data = &state->data;
size_t data_in_size;
size_t data_out_size;
- int error;
int32_t *dest_buffer;
assert((src_size % (sizeof(*src_buffer) * channels)) == 0);
@@ -226,14 +288,6 @@ pcm_resample_lsr_32(struct pcm_resample_state *state,
if (!success)
return NULL;
- /* there was an error previously, and nothing has changed */
- if (state->error) {
- g_set_error(error_r, libsamplerate_quark(), state->error,
- "libsamplerate has failed: %s",
- src_strerror(state->error));
- return NULL;
- }
-
data->input_frames = src_size / sizeof(*src_buffer) / channels;
data_in_size = data->input_frames * sizeof(float) * channels;
data->data_in = pcm_buffer_get(&state->in, data_in_size);
@@ -245,14 +299,8 @@ pcm_resample_lsr_32(struct pcm_resample_state *state,
src_int_to_float_array(src_buffer, data->data_in,
data->input_frames * channels);
- error = src_process(state->state, data);
- if (error) {
- g_set_error(error_r, libsamplerate_quark(), error,
- "libsamplerate has failed: %s",
- src_strerror(error));
- state->error = error;
+ if (!lsr_process(state, error_r))
return NULL;
- }
*dest_size_r = data->output_frames_gen *
sizeof(*dest_buffer) * channels;
diff --git a/src/pcm_utils.h b/src/pcm_utils.h
index 15f9e1b10..4ad896570 100644
--- a/src/pcm_utils.h
+++ b/src/pcm_utils.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -25,6 +25,17 @@
#include <stdint.h>
/**
+ * Add a byte count to the specified pointer. This is a utility
+ * function to convert a source pointer and a byte count to an "end"
+ * pointer for use in loops.
+ */
+static inline const void *
+pcm_end_pointer(const void *p, size_t size)
+{
+ return (const char *)p + size;
+}
+
+/**
* Check if the value is within the range of the provided bit size,
* and caps it if necessary.
*/
@@ -52,4 +63,32 @@ pcm_range_64(int64_t sample, unsigned bits)
return sample;
}
+G_GNUC_CONST
+static inline int16_t
+pcm_clamp_16(int x)
+{
+ static const int32_t MIN_VALUE = G_MININT16;
+ static const int32_t MAX_VALUE = G_MAXINT16;
+
+ if (G_UNLIKELY(x < MIN_VALUE))
+ return MIN_VALUE;
+ if (G_UNLIKELY(x > MAX_VALUE))
+ return MAX_VALUE;
+ return x;
+}
+
+G_GNUC_CONST
+static inline int32_t
+pcm_clamp_24(int x)
+{
+ static const int32_t MIN_VALUE = -(1 << 23);
+ static const int32_t MAX_VALUE = (1 << 23) - 1;
+
+ if (G_UNLIKELY(x < MIN_VALUE))
+ return MIN_VALUE;
+ if (G_UNLIKELY(x > MAX_VALUE))
+ return MAX_VALUE;
+ return x;
+}
+
#endif
diff --git a/src/pcm_volume.c b/src/pcm_volume.c
index 240c779d8..49c86026f 100644
--- a/src/pcm_volume.c
+++ b/src/pcm_volume.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -31,9 +31,9 @@
#define G_LOG_DOMAIN "pcm_volume"
static void
-pcm_volume_change_8(int8_t *buffer, unsigned num_samples, int volume)
+pcm_volume_change_8(int8_t *buffer, const int8_t *end, int volume)
{
- while (num_samples > 0) {
+ while (buffer < end) {
int32_t sample = *buffer;
sample = (sample * volume + pcm_volume_dither() +
@@ -41,14 +41,13 @@ pcm_volume_change_8(int8_t *buffer, unsigned num_samples, int volume)
/ PCM_VOLUME_1;
*buffer++ = pcm_range(sample, 8);
- --num_samples;
}
}
static void
-pcm_volume_change_16(int16_t *buffer, unsigned num_samples, int volume)
+pcm_volume_change_16(int16_t *buffer, const int16_t *end, int volume)
{
- while (num_samples > 0) {
+ while (buffer < end) {
int32_t sample = *buffer;
sample = (sample * volume + pcm_volume_dither() +
@@ -56,7 +55,6 @@ pcm_volume_change_16(int16_t *buffer, unsigned num_samples, int volume)
/ PCM_VOLUME_1;
*buffer++ = pcm_range(sample, 16);
- --num_samples;
}
}
@@ -92,9 +90,9 @@ pcm_volume_sample_24(int32_t sample, int32_t volume, G_GNUC_UNUSED int32_t dithe
#endif
static void
-pcm_volume_change_24(int32_t *buffer, unsigned num_samples, int volume)
+pcm_volume_change_24(int32_t *buffer, const int32_t *end, int volume)
{
- while (num_samples > 0) {
+ while (buffer < end) {
#ifdef __i386__
/* assembly version for i386 */
int32_t sample = *buffer;
@@ -110,14 +108,13 @@ pcm_volume_change_24(int32_t *buffer, unsigned num_samples, int volume)
/ PCM_VOLUME_1;
#endif
*buffer++ = pcm_range(sample, 24);
- --num_samples;
}
}
static void
-pcm_volume_change_32(int32_t *buffer, unsigned num_samples, int volume)
+pcm_volume_change_32(int32_t *buffer, const int32_t *end, int volume)
{
- while (num_samples > 0) {
+ while (buffer < end) {
#ifdef __i386__
/* assembly version for i386 */
int32_t sample = *buffer;
@@ -132,14 +129,22 @@ pcm_volume_change_32(int32_t *buffer, unsigned num_samples, int volume)
/ PCM_VOLUME_1;
*buffer++ = pcm_range_64(sample, 32);
#endif
+ }
+}
- --num_samples;
+static void
+pcm_volume_change_float(float *buffer, const float *end, float volume)
+{
+ while (buffer < end) {
+ float sample = *buffer;
+ sample *= volume;
+ *buffer++ = sample;
}
}
bool
-pcm_volume(void *buffer, int length,
- const struct audio_format *format,
+pcm_volume(void *buffer, size_t length,
+ enum sample_format format,
int volume)
{
if (volume == PCM_VOLUME_1)
@@ -150,27 +155,36 @@ pcm_volume(void *buffer, int length,
return true;
}
- switch (format->format) {
+ const void *end = pcm_end_pointer(buffer, length);
+ switch (format) {
+ case SAMPLE_FORMAT_UNDEFINED:
+ case SAMPLE_FORMAT_DSD:
+ /* not implemented */
+ return false;
+
case SAMPLE_FORMAT_S8:
- pcm_volume_change_8((int8_t *)buffer, length, volume);
+ pcm_volume_change_8(buffer, end, volume);
return true;
case SAMPLE_FORMAT_S16:
- pcm_volume_change_16((int16_t *)buffer, length / 2,
- volume);
+ pcm_volume_change_16(buffer, end, volume);
return true;
case SAMPLE_FORMAT_S24_P32:
- pcm_volume_change_24((int32_t*)buffer, length / 4,
- volume);
+ pcm_volume_change_24(buffer, end, volume);
return true;
case SAMPLE_FORMAT_S32:
- pcm_volume_change_32((int32_t*)buffer, length / 4,
- volume);
+ pcm_volume_change_32(buffer, end, volume);
return true;
- default:
- return false;
+ case SAMPLE_FORMAT_FLOAT:
+ pcm_volume_change_float(buffer, end,
+ pcm_volume_to_float(volume));
+ return true;
}
+
+ /* unreachable */
+ assert(false);
+ return false;
}
diff --git a/src/pcm_volume.h b/src/pcm_volume.h
index eb61e9526..64e3c7641 100644
--- a/src/pcm_volume.h
+++ b/src/pcm_volume.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -21,6 +21,7 @@
#define PCM_VOLUME_H
#include "pcm_prng.h"
+#include "audio_format.h"
#include <stdint.h>
#include <stdbool.h>
@@ -42,6 +43,12 @@ pcm_float_to_volume(float volume)
return volume * PCM_VOLUME_1 + 0.5;
}
+static inline float
+pcm_volume_to_float(int volume)
+{
+ return (float)volume / (float)PCM_VOLUME_1;
+}
+
/**
* Returns the next volume dithering number, between -511 and +511.
* This number is taken from a global PRNG, see pcm_prng().
@@ -62,13 +69,13 @@ pcm_volume_dither(void)
*
* @param buffer the PCM buffer
* @param length the length of the PCM buffer
- * @param format the audio format of the PCM buffer
+ * @param format the sample format of the PCM buffer
* @param volume the volume between 0 and #PCM_VOLUME_1
* @return true on success, false if the audio format is not supported
*/
bool
-pcm_volume(void *buffer, int length,
- const struct audio_format *format,
+pcm_volume(void *buffer, size_t length,
+ enum sample_format format,
int volume);
#endif
diff --git a/src/permission.c b/src/permission.c
index 17b443e0a..cd52b9c86 100644
--- a/src/permission.c
+++ b/src/permission.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/permission.h b/src/permission.h
index 9b3a60a66..6c3771362 100644
--- a/src/permission.h
+++ b/src/permission.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/pipe.c b/src/pipe.c
index 2f5f70e43..d8131432f 100644
--- a/src/pipe.c
+++ b/src/pipe.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/pipe.h b/src/pipe.h
index efa7a84f0..84b9869e0 100644
--- a/src/pipe.h
+++ b/src/pipe.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/player_control.c b/src/player_control.c
index e3e6b7739..d8d54dfd6 100644
--- a/src/player_control.c
+++ b/src/player_control.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -32,237 +32,247 @@
#include <stdio.h>
#include <math.h>
-struct player_control pc;
-
static void
-pc_enqueue_song_locked(struct song *song);
+pc_enqueue_song_locked(struct player_control *pc, struct song *song);
-void pc_init(unsigned buffer_chunks, unsigned int buffered_before_play)
+struct player_control *
+pc_new(unsigned buffer_chunks, unsigned int buffered_before_play)
{
- pc.buffer_chunks = buffer_chunks;
- pc.buffered_before_play = buffered_before_play;
-
- pc.mutex = g_mutex_new();
- pc.cond = g_cond_new();
-
- pc.command = PLAYER_COMMAND_NONE;
- pc.error = PLAYER_ERROR_NOERROR;
- pc.state = PLAYER_STATE_STOP;
- pc.cross_fade_seconds = 0;
- pc.mixramp_db = 0;
- pc.mixramp_delay_seconds = nanf("");
+ struct player_control *pc = g_new0(struct player_control, 1);
+
+ pc->buffer_chunks = buffer_chunks;
+ pc->buffered_before_play = buffered_before_play;
+
+ pc->mutex = g_mutex_new();
+ pc->cond = g_cond_new();
+
+ pc->command = PLAYER_COMMAND_NONE;
+ pc->error = PLAYER_ERROR_NOERROR;
+ pc->state = PLAYER_STATE_STOP;
+ pc->cross_fade_seconds = 0;
+ pc->mixramp_db = 0;
+ pc->mixramp_delay_seconds = nanf("");
+
+ return pc;
}
-void pc_deinit(void)
+void
+pc_free(struct player_control *pc)
{
- g_cond_free(pc.cond);
- g_mutex_free(pc.mutex);
+ g_cond_free(pc->cond);
+ g_mutex_free(pc->mutex);
+ g_free(pc);
}
void
-player_wait_decoder(struct decoder_control *dc)
+player_wait_decoder(struct player_control *pc, struct decoder_control *dc)
{
+ assert(pc != NULL);
+ assert(dc != NULL);
+ assert(dc->client_cond == pc->cond);
+
/* during this function, the decoder lock is held, because
we're waiting for the decoder thread */
- g_cond_wait(pc.cond, dc->mutex);
+ g_cond_wait(pc->cond, dc->mutex);
}
void
-pc_song_deleted(const struct song *song)
+pc_song_deleted(struct player_control *pc, const struct song *song)
{
- if (pc.errored_song == song) {
- pc.error = PLAYER_ERROR_NOERROR;
- pc.errored_song = NULL;
+ if (pc->errored_song == song) {
+ pc->error = PLAYER_ERROR_NOERROR;
+ pc->errored_song = NULL;
}
}
static void
-player_command_wait_locked(void)
+player_command_wait_locked(struct player_control *pc)
{
- while (pc.command != PLAYER_COMMAND_NONE)
- g_cond_wait(main_cond, pc.mutex);
+ while (pc->command != PLAYER_COMMAND_NONE)
+ g_cond_wait(main_cond, pc->mutex);
}
static void
-player_command_locked(enum player_command cmd)
+player_command_locked(struct player_control *pc, enum player_command cmd)
{
- assert(pc.command == PLAYER_COMMAND_NONE);
+ assert(pc->command == PLAYER_COMMAND_NONE);
- pc.command = cmd;
- player_signal();
- player_command_wait_locked();
+ pc->command = cmd;
+ player_signal(pc);
+ player_command_wait_locked(pc);
}
static void
-player_command(enum player_command cmd)
+player_command(struct player_control *pc, enum player_command cmd)
{
- player_lock();
- player_command_locked(cmd);
- player_unlock();
+ player_lock(pc);
+ player_command_locked(pc, cmd);
+ player_unlock(pc);
}
void
-pc_play(struct song *song)
+pc_play(struct player_control *pc, struct song *song)
{
assert(song != NULL);
- player_lock();
+ player_lock(pc);
- if (pc.state != PLAYER_STATE_STOP)
- player_command_locked(PLAYER_COMMAND_STOP);
+ if (pc->state != PLAYER_STATE_STOP)
+ player_command_locked(pc, PLAYER_COMMAND_STOP);
- assert(pc.next_song == NULL);
+ assert(pc->next_song == NULL);
- pc_enqueue_song_locked(song);
+ pc_enqueue_song_locked(pc, song);
- assert(pc.next_song == NULL);
+ assert(pc->next_song == NULL);
- player_unlock();
+ player_unlock(pc);
idle_add(IDLE_PLAYER);
}
-void pc_cancel(void)
+void
+pc_cancel(struct player_control *pc)
{
- player_command(PLAYER_COMMAND_CANCEL);
- assert(pc.next_song == NULL);
+ player_command(pc, PLAYER_COMMAND_CANCEL);
+ assert(pc->next_song == NULL);
}
void
-pc_stop(void)
+pc_stop(struct player_control *pc)
{
- player_command(PLAYER_COMMAND_CLOSE_AUDIO);
- assert(pc.next_song == NULL);
+ player_command(pc, PLAYER_COMMAND_CLOSE_AUDIO);
+ assert(pc->next_song == NULL);
idle_add(IDLE_PLAYER);
}
void
-pc_update_audio(void)
+pc_update_audio(struct player_control *pc)
{
- player_command(PLAYER_COMMAND_UPDATE_AUDIO);
+ player_command(pc, PLAYER_COMMAND_UPDATE_AUDIO);
}
void
-pc_kill(void)
+pc_kill(struct player_control *pc)
{
- assert(pc.thread != NULL);
+ assert(pc->thread != NULL);
- player_command(PLAYER_COMMAND_EXIT);
- g_thread_join(pc.thread);
- pc.thread = NULL;
+ player_command(pc, PLAYER_COMMAND_EXIT);
+ g_thread_join(pc->thread);
+ pc->thread = NULL;
idle_add(IDLE_PLAYER);
}
void
-pc_pause(void)
+pc_pause(struct player_control *pc)
{
- player_lock();
+ player_lock(pc);
- if (pc.state != PLAYER_STATE_STOP) {
- player_command_locked(PLAYER_COMMAND_PAUSE);
+ if (pc->state != PLAYER_STATE_STOP) {
+ player_command_locked(pc, PLAYER_COMMAND_PAUSE);
idle_add(IDLE_PLAYER);
}
- player_unlock();
+ player_unlock(pc);
}
static void
-pc_pause_locked(void)
+pc_pause_locked(struct player_control *pc)
{
- if (pc.state != PLAYER_STATE_STOP) {
- player_command_locked(PLAYER_COMMAND_PAUSE);
+ if (pc->state != PLAYER_STATE_STOP) {
+ player_command_locked(pc, PLAYER_COMMAND_PAUSE);
idle_add(IDLE_PLAYER);
}
}
void
-pc_set_pause(bool pause_flag)
+pc_set_pause(struct player_control *pc, bool pause_flag)
{
- player_lock();
+ player_lock(pc);
- switch (pc.state) {
+ switch (pc->state) {
case PLAYER_STATE_STOP:
break;
case PLAYER_STATE_PLAY:
if (pause_flag)
- pc_pause_locked();
+ pc_pause_locked(pc);
break;
case PLAYER_STATE_PAUSE:
if (!pause_flag)
- pc_pause_locked();
+ pc_pause_locked(pc);
break;
}
- player_unlock();
+ player_unlock(pc);
}
void
-pc_get_status(struct player_status *status)
+pc_get_status(struct player_control *pc, struct player_status *status)
{
- player_lock();
- player_command_locked(PLAYER_COMMAND_REFRESH);
+ player_lock(pc);
+ player_command_locked(pc, PLAYER_COMMAND_REFRESH);
- status->state = pc.state;
+ status->state = pc->state;
- if (pc.state != PLAYER_STATE_STOP) {
- status->bit_rate = pc.bit_rate;
- status->audio_format = pc.audio_format;
- status->total_time = pc.total_time;
- status->elapsed_time = pc.elapsed_time;
+ if (pc->state != PLAYER_STATE_STOP) {
+ status->bit_rate = pc->bit_rate;
+ status->audio_format = pc->audio_format;
+ status->total_time = pc->total_time;
+ status->elapsed_time = pc->elapsed_time;
}
- player_unlock();
+ player_unlock(pc);
}
enum player_state
-pc_get_state(void)
+pc_get_state(struct player_control *pc)
{
- return pc.state;
+ return pc->state;
}
void
-pc_clear_error(void)
+pc_clear_error(struct player_control *pc)
{
- player_lock();
- pc.error = PLAYER_ERROR_NOERROR;
- pc.errored_song = NULL;
- player_unlock();
+ player_lock(pc);
+ pc->error = PLAYER_ERROR_NOERROR;
+ pc->errored_song = NULL;
+ player_unlock(pc);
}
enum player_error
-pc_get_error(void)
+pc_get_error(struct player_control *pc)
{
- return pc.error;
+ return pc->error;
}
static char *
-pc_errored_song_uri(void)
+pc_errored_song_uri(struct player_control *pc)
{
- return song_get_uri(pc.errored_song);
+ return song_get_uri(pc->errored_song);
}
char *
-pc_get_error_message(void)
+pc_get_error_message(struct player_control *pc)
{
char *error;
char *uri;
- switch (pc.error) {
+ switch (pc->error) {
case PLAYER_ERROR_NOERROR:
return NULL;
case PLAYER_ERROR_FILENOTFOUND:
- uri = pc_errored_song_uri();
+ uri = pc_errored_song_uri(pc);
error = g_strdup_printf("file \"%s\" does not exist or is inaccessible", uri);
g_free(uri);
return error;
case PLAYER_ERROR_FILE:
- uri = pc_errored_song_uri();
+ uri = pc_errored_song_uri(pc);
error = g_strdup_printf("problems decoding \"%s\"", uri);
g_free(uri);
return error;
@@ -271,10 +281,10 @@ pc_get_error_message(void)
return g_strdup("problems opening audio device");
case PLAYER_ERROR_SYSTEM:
- return g_strdup("system error occured");
+ return g_strdup("system error occurred");
case PLAYER_ERROR_UNKTYPE:
- uri = pc_errored_song_uri();
+ uri = pc_errored_song_uri(pc);
error = g_strdup_printf("file type of \"%s\" is unknown", uri);
g_free(uri);
return error;
@@ -285,37 +295,37 @@ pc_get_error_message(void)
}
static void
-pc_enqueue_song_locked(struct song *song)
+pc_enqueue_song_locked(struct player_control *pc, struct song *song)
{
assert(song != NULL);
- assert(pc.next_song == NULL);
+ assert(pc->next_song == NULL);
- pc.next_song = song;
- player_command_locked(PLAYER_COMMAND_QUEUE);
+ pc->next_song = song;
+ player_command_locked(pc, PLAYER_COMMAND_QUEUE);
}
void
-pc_enqueue_song(struct song *song)
+pc_enqueue_song(struct player_control *pc, struct song *song)
{
assert(song != NULL);
- player_lock();
- pc_enqueue_song_locked(song);
- player_unlock();
+ player_lock(pc);
+ pc_enqueue_song_locked(pc, song);
+ player_unlock(pc);
}
bool
-pc_seek(struct song *song, float seek_time)
+pc_seek(struct player_control *pc, struct song *song, float seek_time)
{
assert(song != NULL);
- player_lock();
- pc.next_song = song;
- pc.seek_where = seek_time;
- player_command_locked(PLAYER_COMMAND_SEEK);
- player_unlock();
+ player_lock(pc);
+ pc->next_song = song;
+ pc->seek_where = seek_time;
+ player_command_locked(pc, PLAYER_COMMAND_SEEK);
+ player_unlock(pc);
- assert(pc.next_song == NULL);
+ assert(pc->next_song == NULL);
idle_add(IDLE_PLAYER);
@@ -323,51 +333,51 @@ pc_seek(struct song *song, float seek_time)
}
float
-pc_get_cross_fade(void)
+pc_get_cross_fade(const struct player_control *pc)
{
- return pc.cross_fade_seconds;
+ return pc->cross_fade_seconds;
}
void
-pc_set_cross_fade(float cross_fade_seconds)
+pc_set_cross_fade(struct player_control *pc, float cross_fade_seconds)
{
if (cross_fade_seconds < 0)
cross_fade_seconds = 0;
- pc.cross_fade_seconds = cross_fade_seconds;
+ pc->cross_fade_seconds = cross_fade_seconds;
idle_add(IDLE_OPTIONS);
}
float
-pc_get_mixramp_db(void)
+pc_get_mixramp_db(const struct player_control *pc)
{
- return pc.mixramp_db;
+ return pc->mixramp_db;
}
void
-pc_set_mixramp_db(float mixramp_db)
+pc_set_mixramp_db(struct player_control *pc, float mixramp_db)
{
- pc.mixramp_db = mixramp_db;
+ pc->mixramp_db = mixramp_db;
idle_add(IDLE_OPTIONS);
}
float
-pc_get_mixramp_delay(void)
+pc_get_mixramp_delay(const struct player_control *pc)
{
- return pc.mixramp_delay_seconds;
+ return pc->mixramp_delay_seconds;
}
void
-pc_set_mixramp_delay(float mixramp_delay_seconds)
+pc_set_mixramp_delay(struct player_control *pc, float mixramp_delay_seconds)
{
- pc.mixramp_delay_seconds = mixramp_delay_seconds;
+ pc->mixramp_delay_seconds = mixramp_delay_seconds;
idle_add(IDLE_OPTIONS);
}
double
-pc_get_total_play_time(void)
+pc_get_total_play_time(const struct player_control *pc)
{
- return pc.total_play_time;
+ return pc->total_play_time;
}
diff --git a/src/player_control.h b/src/player_control.h
index 76c47609a..5a04ab0f9 100644
--- a/src/player_control.h
+++ b/src/player_control.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -20,9 +20,10 @@
#ifndef MPD_PLAYER_H
#define MPD_PLAYER_H
-#include "notify.h"
#include "audio_format.h"
+#include <glib.h>
+
#include <stdint.h>
struct decoder_control;
@@ -116,28 +117,28 @@ struct player_control {
double total_play_time;
};
-extern struct player_control pc;
-
-void pc_init(unsigned buffer_chunks, unsigned buffered_before_play);
+struct player_control *
+pc_new(unsigned buffer_chunks, unsigned buffered_before_play);
-void pc_deinit(void);
+void
+pc_free(struct player_control *pc);
/**
* Locks the #player_control object.
*/
static inline void
-player_lock(void)
+player_lock(struct player_control *pc)
{
- g_mutex_lock(pc.mutex);
+ g_mutex_lock(pc->mutex);
}
/**
* Unlocks the #player_control object.
*/
static inline void
-player_unlock(void)
+player_unlock(struct player_control *pc)
{
- g_mutex_unlock(pc.mutex);
+ g_mutex_unlock(pc->mutex);
}
/**
@@ -146,9 +147,9 @@ player_unlock(void)
* to calling this function.
*/
static inline void
-player_wait(void)
+player_wait(struct player_control *pc)
{
- g_cond_wait(pc.cond, pc.mutex);
+ g_cond_wait(pc->cond, pc->mutex);
}
/**
@@ -159,16 +160,16 @@ player_wait(void)
* Note the small difference to the player_wait() function!
*/
void
-player_wait_decoder(struct decoder_control *dc);
+player_wait_decoder(struct player_control *pc, struct decoder_control *dc);
/**
* Signals the #player_control object. The object should be locked
* prior to calling this function.
*/
static inline void
-player_signal(void)
+player_signal(struct player_control *pc)
{
- g_cond_signal(pc.cond);
+ g_cond_signal(pc->cond);
}
/**
@@ -176,11 +177,11 @@ player_signal(void)
* locked by this function.
*/
static inline void
-player_lock_signal(void)
+player_lock_signal(struct player_control *pc)
{
- player_lock();
- player_signal();
- player_unlock();
+ player_lock(pc);
+ player_signal(pc);
+ player_unlock(pc);
}
/**
@@ -189,33 +190,34 @@ player_lock_signal(void)
* not point to an invalid pointer.
*/
void
-pc_song_deleted(const struct song *song);
+pc_song_deleted(struct player_control *pc, const struct song *song);
void
-pc_play(struct song *song);
+pc_play(struct player_control *pc, struct song *song);
/**
* see PLAYER_COMMAND_CANCEL
*/
-void pc_cancel(void);
+void
+pc_cancel(struct player_control *pc);
void
-pc_set_pause(bool pause_flag);
+pc_set_pause(struct player_control *pc, bool pause_flag);
void
-pc_pause(void);
+pc_pause(struct player_control *pc);
void
-pc_kill(void);
+pc_kill(struct player_control *pc);
void
-pc_get_status(struct player_status *status);
+pc_get_status(struct player_control *pc, struct player_status *status);
enum player_state
-pc_get_state(void);
+pc_get_state(struct player_control *pc);
void
-pc_clear_error(void);
+pc_clear_error(struct player_control *pc);
/**
* Returns the human-readable message describing the last error during
@@ -223,19 +225,19 @@ pc_clear_error(void);
* returned string.
*/
char *
-pc_get_error_message(void);
+pc_get_error_message(struct player_control *pc);
enum player_error
-pc_get_error(void);
+pc_get_error(struct player_control *pc);
void
-pc_stop(void);
+pc_stop(struct player_control *pc);
void
-pc_update_audio(void);
+pc_update_audio(struct player_control *pc);
void
-pc_enqueue_song(struct song *song);
+pc_enqueue_song(struct player_control *pc, struct song *song);
/**
* Makes the player thread seek the specified song to a position.
@@ -244,27 +246,27 @@ pc_enqueue_song(struct song *song);
* playing currently)
*/
bool
-pc_seek(struct song *song, float seek_time);
+pc_seek(struct player_control *pc, struct song *song, float seek_time);
void
-pc_set_cross_fade(float cross_fade_seconds);
+pc_set_cross_fade(struct player_control *pc, float cross_fade_seconds);
float
-pc_get_cross_fade(void);
+pc_get_cross_fade(const struct player_control *pc);
void
-pc_set_mixramp_db(float mixramp_db);
+pc_set_mixramp_db(struct player_control *pc, float mixramp_db);
float
-pc_get_mixramp_db(void);
+pc_get_mixramp_db(const struct player_control *pc);
void
-pc_set_mixramp_delay(float mixramp_delay_seconds);
+pc_set_mixramp_delay(struct player_control *pc, float mixramp_delay_seconds);
float
-pc_get_mixramp_delay(void);
+pc_get_mixramp_delay(const struct player_control *pc);
double
-pc_get_total_play_time(void);
+pc_get_total_play_time(const struct player_control *pc);
#endif
diff --git a/src/player_thread.c b/src/player_thread.c
index 52788e518..c0243fa00 100644
--- a/src/player_thread.c
+++ b/src/player_thread.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -48,6 +48,8 @@ enum xfade_state {
};
struct player {
+ struct player_control *pc;
+
struct decoder_control *dc;
struct music_pipe *pipe;
@@ -118,26 +120,28 @@ struct player {
* output thread. This attribute is only used if
* audio_output_all_get_elapsed_time() didn't return a usable
* value; the output thread can estimate the elapsed time more
- * precisly.
+ * precisely.
*/
float elapsed_time;
};
static struct music_buffer *player_buffer;
-static void player_command_finished_locked(void)
+static void
+player_command_finished_locked(struct player_control *pc)
{
- assert(pc.command != PLAYER_COMMAND_NONE);
+ assert(pc->command != PLAYER_COMMAND_NONE);
- pc.command = PLAYER_COMMAND_NONE;
+ pc->command = PLAYER_COMMAND_NONE;
g_cond_signal(main_cond);
}
-static void player_command_finished(void)
+static void
+player_command_finished(struct player_control *pc)
{
- player_lock();
- player_command_finished_locked();
- player_unlock();
+ player_lock(pc);
+ player_command_finished_locked(pc);
+ player_unlock(pc);
}
/**
@@ -148,17 +152,18 @@ static void player_command_finished(void)
static void
player_dc_start(struct player *player, struct music_pipe *pipe)
{
+ struct player_control *pc = player->pc;
struct decoder_control *dc = player->dc;
- assert(player->queued || pc.command == PLAYER_COMMAND_SEEK);
- assert(pc.next_song != NULL);
+ assert(player->queued || pc->command == PLAYER_COMMAND_SEEK);
+ assert(pc->next_song != NULL);
- unsigned start_ms = pc.next_song->start_ms;
- if (pc.command == PLAYER_COMMAND_SEEK)
- start_ms += (unsigned)(pc.seek_where * 1000);
+ unsigned start_ms = pc->next_song->start_ms;
+ if (pc->command == PLAYER_COMMAND_SEEK)
+ start_ms += (unsigned)(pc->seek_where * 1000);
- dc_start(dc, pc.next_song,
- start_ms, pc.next_song->end_ms,
+ dc_start(dc, pc->next_song,
+ start_ms, pc->next_song->end_ms,
player_buffer, pipe);
}
@@ -222,41 +227,42 @@ player_dc_stop(struct player *player)
static bool
player_wait_for_decoder(struct player *player)
{
+ struct player_control *pc = player->pc;
struct decoder_control *dc = player->dc;
- assert(player->queued || pc.command == PLAYER_COMMAND_SEEK);
- assert(pc.next_song != NULL);
+ assert(player->queued || pc->command == PLAYER_COMMAND_SEEK);
+ assert(pc->next_song != NULL);
player->queued = false;
if (decoder_lock_has_failed(dc)) {
- player_lock();
- pc.errored_song = dc->song;
- pc.error = PLAYER_ERROR_FILE;
- pc.next_song = NULL;
- player_unlock();
+ player_lock(pc);
+ pc->errored_song = dc->song;
+ pc->error = PLAYER_ERROR_FILE;
+ pc->next_song = NULL;
+ player_unlock(pc);
return false;
}
- player->song = pc.next_song;
+ player->song = pc->next_song;
player->elapsed_time = 0.0;
/* set the "starting" flag, which will be cleared by
player_check_decoder_startup() */
player->decoder_starting = true;
- player_lock();
+ player_lock(pc);
/* update player_control's song information */
- pc.total_time = song_get_duration(pc.next_song);
- pc.bit_rate = 0;
- audio_format_clear(&pc.audio_format);
+ pc->total_time = song_get_duration(pc->next_song);
+ pc->bit_rate = 0;
+ audio_format_clear(&pc->audio_format);
/* clear the queued song */
- pc.next_song = NULL;
+ pc->next_song = NULL;
- player_unlock();
+ player_unlock(pc);
/* call syncPlaylistWithQueue() in the main thread */
event_pipe_emit(PIPE_EVENT_PLAYLIST);
@@ -293,17 +299,19 @@ real_song_duration(const struct song *song, double decoder_duration)
static bool
player_open_output(struct player *player)
{
+ struct player_control *pc = player->pc;
+
assert(audio_format_defined(&player->play_audio_format));
- assert(pc.state == PLAYER_STATE_PLAY ||
- pc.state == PLAYER_STATE_PAUSE);
+ assert(pc->state == PLAYER_STATE_PLAY ||
+ pc->state == PLAYER_STATE_PAUSE);
if (audio_output_all_open(&player->play_audio_format, player_buffer)) {
player->output_open = true;
player->paused = false;
- player_lock();
- pc.state = PLAYER_STATE_PLAY;
- player_unlock();
+ player_lock(pc);
+ pc->state = PLAYER_STATE_PLAY;
+ player_unlock(pc);
return true;
} else {
@@ -313,10 +321,10 @@ player_open_output(struct player *player)
audio output becomes available */
player->paused = true;
- player_lock();
- pc.error = PLAYER_ERROR_AUDIO;
- pc.state = PLAYER_STATE_PAUSE;
- player_unlock();
+ player_lock(pc);
+ pc->error = PLAYER_ERROR_AUDIO;
+ pc->state = PLAYER_STATE_PAUSE;
+ player_unlock(pc);
return false;
}
@@ -332,6 +340,7 @@ player_open_output(struct player *player)
static bool
player_check_decoder_startup(struct player *player)
{
+ struct player_control *pc = player->pc;
struct decoder_control *dc = player->dc;
assert(player->decoder_starting);
@@ -342,10 +351,10 @@ player_check_decoder_startup(struct player *player)
/* the decoder failed */
decoder_unlock(dc);
- player_lock();
- pc.errored_song = dc->song;
- pc.error = PLAYER_ERROR_FILE;
- player_unlock();
+ player_lock(pc);
+ pc->errored_song = dc->song;
+ pc->error = PLAYER_ERROR_FILE;
+ player_unlock(pc);
return false;
} else if (!decoder_is_starting(dc)) {
@@ -354,15 +363,15 @@ player_check_decoder_startup(struct player *player)
decoder_unlock(dc);
if (player->output_open &&
- !audio_output_all_wait(1))
+ !audio_output_all_wait(pc, 1))
/* the output devices havn't finished playing
all chunks yet - wait for that */
return true;
- player_lock();
- pc.total_time = real_song_duration(dc->song, dc->total_time);
- pc.audio_format = dc->in_audio_format;
- player_unlock();
+ player_lock(pc);
+ pc->total_time = real_song_duration(dc->song, dc->total_time);
+ pc->audio_format = dc->in_audio_format;
+ player_unlock(pc);
player->play_audio_format = dc->out_audio_format;
player->decoder_starting = false;
@@ -380,7 +389,7 @@ player_check_decoder_startup(struct player *player)
} else {
/* the decoder is not yet ready; wait
some more */
- player_wait_decoder(dc);
+ player_wait_decoder(pc, dc);
decoder_unlock(dc);
return true;
@@ -435,10 +444,11 @@ player_send_silence(struct player *player)
*/
static bool player_seek_decoder(struct player *player)
{
- struct song *song = pc.next_song;
+ struct player_control *pc = player->pc;
+ struct song *song = pc->next_song;
struct decoder_control *dc = player->dc;
- assert(pc.next_song != NULL);
+ assert(pc->next_song != NULL);
if (decoder_current_song(dc) != song) {
/* the decoder is already decoding the "next" song -
@@ -454,7 +464,7 @@ static bool player_seek_decoder(struct player *player)
player_dc_start(player, player->pipe);
if (!player_wait_for_decoder(player)) {
/* decoder failure */
- player_command_finished();
+ player_command_finished(pc);
return false;
}
} else {
@@ -466,7 +476,7 @@ static bool player_seek_decoder(struct player *player)
player->pipe = dc->pipe;
}
- pc.next_song = NULL;
+ pc->next_song = NULL;
player->queued = false;
}
@@ -475,28 +485,28 @@ static bool player_seek_decoder(struct player *player)
while (player->decoder_starting) {
if (!player_check_decoder_startup(player)) {
/* decoder failure */
- player_command_finished();
+ player_command_finished(pc);
return false;
}
}
/* send the SEEK command */
- double where = pc.seek_where;
- if (where > pc.total_time)
- where = pc.total_time - 0.1;
+ double where = pc->seek_where;
+ if (where > pc->total_time)
+ where = pc->total_time - 0.1;
if (where < 0.0)
where = 0.0;
if (!dc_seek(dc, where + song->start_ms / 1000.0)) {
/* decoder failure */
- player_command_finished();
+ player_command_finished(pc);
return false;
}
player->elapsed_time = where;
- player_command_finished();
+ player_command_finished(pc);
player->xfade = XFADE_UNKNOWN;
@@ -513,9 +523,10 @@ static bool player_seek_decoder(struct player *player)
*/
static void player_process_command(struct player *player)
{
+ struct player_control *pc = player->pc;
G_GNUC_UNUSED struct decoder_control *dc = player->dc;
- switch (pc.command) {
+ switch (pc->command) {
case PLAYER_COMMAND_NONE:
case PLAYER_COMMAND_STOP:
case PLAYER_COMMAND_EXIT:
@@ -523,85 +534,85 @@ static void player_process_command(struct player *player)
break;
case PLAYER_COMMAND_UPDATE_AUDIO:
- player_unlock();
+ player_unlock(pc);
audio_output_all_enable_disable();
- player_lock();
- player_command_finished_locked();
+ player_lock(pc);
+ player_command_finished_locked(pc);
break;
case PLAYER_COMMAND_QUEUE:
- assert(pc.next_song != NULL);
+ assert(pc->next_song != NULL);
assert(!player->queued);
assert(!player_dc_at_next_song(player));
player->queued = true;
- player_command_finished_locked();
+ player_command_finished_locked(pc);
break;
case PLAYER_COMMAND_PAUSE:
- player_unlock();
+ player_unlock(pc);
player->paused = !player->paused;
if (player->paused) {
audio_output_all_pause();
- player_lock();
+ player_lock(pc);
- pc.state = PLAYER_STATE_PAUSE;
+ pc->state = PLAYER_STATE_PAUSE;
} else if (!audio_format_defined(&player->play_audio_format)) {
/* the decoder hasn't provided an audio format
yet - don't open the audio device yet */
- player_lock();
+ player_lock(pc);
- pc.state = PLAYER_STATE_PLAY;
+ pc->state = PLAYER_STATE_PLAY;
} else {
player_open_output(player);
- player_lock();
+ player_lock(pc);
}
- player_command_finished_locked();
+ player_command_finished_locked(pc);
break;
case PLAYER_COMMAND_SEEK:
- player_unlock();
+ player_unlock(pc);
player_seek_decoder(player);
- player_lock();
+ player_lock(pc);
break;
case PLAYER_COMMAND_CANCEL:
- if (pc.next_song == NULL) {
+ if (pc->next_song == NULL) {
/* the cancel request arrived too late, we're
already playing the queued song... stop
everything now */
- pc.command = PLAYER_COMMAND_STOP;
+ pc->command = PLAYER_COMMAND_STOP;
return;
}
if (player_dc_at_next_song(player)) {
/* the decoder is already decoding the song -
stop it and reset the position */
- player_unlock();
+ player_unlock(pc);
player_dc_stop(player);
- player_lock();
+ player_lock(pc);
}
- pc.next_song = NULL;
+ pc->next_song = NULL;
player->queued = false;
- player_command_finished_locked();
+ player_command_finished_locked(pc);
break;
case PLAYER_COMMAND_REFRESH:
if (player->output_open && !player->paused) {
- player_unlock();
+ player_unlock(pc);
audio_output_all_check();
- player_lock();
+ player_lock(pc);
}
- pc.elapsed_time = audio_output_all_get_elapsed_time();
- if (pc.elapsed_time < 0.0)
- pc.elapsed_time = player->elapsed_time;
+ pc->elapsed_time = audio_output_all_get_elapsed_time();
+ if (pc->elapsed_time < 0.0)
+ pc->elapsed_time = player->elapsed_time;
- player_command_finished_locked();
+ player_command_finished_locked(pc);
break;
}
}
@@ -637,7 +648,8 @@ update_song_tag(struct song *song, const struct tag *new_tag)
* Player lock is not held.
*/
static bool
-play_chunk(struct song *song, struct music_chunk *chunk,
+play_chunk(struct player_control *pc,
+ struct song *song, struct music_chunk *chunk,
const struct audio_format *format)
{
assert(music_chunk_check_format(chunk, format));
@@ -650,16 +662,16 @@ play_chunk(struct song *song, struct music_chunk *chunk,
return true;
}
- player_lock();
- pc.bit_rate = chunk->bit_rate;
- player_unlock();
+ player_lock(pc);
+ pc->bit_rate = chunk->bit_rate;
+ player_unlock(pc);
/* send the chunk to the audio outputs */
if (!audio_output_all_play(chunk))
return false;
- pc.total_play_time += (double)chunk->length /
+ pc->total_play_time += (double)chunk->length /
audio_format_time_to_size(format);
return true;
}
@@ -673,9 +685,10 @@ play_chunk(struct song *song, struct music_chunk *chunk,
static bool
play_next_chunk(struct player *player)
{
+ struct player_control *pc = player->pc;
struct decoder_control *dc = player->dc;
- if (!audio_output_all_wait(64))
+ if (!audio_output_all_wait(pc, 64))
/* the output pipe is still large enough, don't send
another chunk */
return true;
@@ -712,7 +725,7 @@ play_next_chunk(struct player *player)
other_chunk->tag);
other_chunk->tag = NULL;
- if (isnan(pc.mixramp_delay_seconds)) {
+ if (isnan(pc->mixramp_delay_seconds)) {
chunk->mix_ratio = ((float)cross_fade_position)
/ player->cross_fade_chunks;
} else {
@@ -747,7 +760,7 @@ play_next_chunk(struct player *player)
} else {
/* wait for the decoder */
decoder_signal(dc);
- player_wait_decoder(dc);
+ player_wait_decoder(pc, dc);
decoder_unlock(dc);
return true;
@@ -770,19 +783,20 @@ play_next_chunk(struct player *player)
/* play the current chunk */
- if (!play_chunk(player->song, chunk, &player->play_audio_format)) {
+ if (!play_chunk(player->pc, player->song, chunk,
+ &player->play_audio_format)) {
music_buffer_return(player_buffer, chunk);
- player_lock();
+ player_lock(pc);
- pc.error = PLAYER_ERROR_AUDIO;
+ pc->error = PLAYER_ERROR_AUDIO;
/* pause: the user may resume playback as soon as an
audio output becomes available */
- pc.state = PLAYER_STATE_PAUSE;
+ pc->state = PLAYER_STATE_PAUSE;
player->paused = true;
- player_unlock();
+ player_unlock(pc);
return false;
}
@@ -792,7 +806,7 @@ play_next_chunk(struct player *player)
larger block at a time */
decoder_lock(dc);
if (!decoder_is_idle(dc) &&
- music_pipe_size(dc->pipe) <= (pc.buffered_before_play +
+ music_pipe_size(dc->pipe) <= (pc->buffered_before_play +
music_buffer_size(player_buffer) * 3) / 4)
decoder_signal(dc);
decoder_unlock(dc);
@@ -834,9 +848,10 @@ player_song_border(struct player *player)
* basically a state machine, which multiplexes data between the
* decoder thread and the output threads.
*/
-static void do_play(struct decoder_control *dc)
+static void do_play(struct player_control *pc, struct decoder_control *dc)
{
struct player player = {
+ .pc = pc,
.dc = dc,
.buffering = true,
.decoder_starting = false,
@@ -851,46 +866,46 @@ static void do_play(struct decoder_control *dc)
.elapsed_time = 0.0,
};
- player_unlock();
+ player_unlock(pc);
player.pipe = music_pipe_new();
player_dc_start(&player, player.pipe);
if (!player_wait_for_decoder(&player)) {
player_dc_stop(&player);
- player_command_finished();
+ player_command_finished(pc);
music_pipe_free(player.pipe);
event_pipe_emit(PIPE_EVENT_PLAYLIST);
- player_lock();
+ player_lock(pc);
return;
}
- player_lock();
+ player_lock(pc);
+ pc->state = PLAYER_STATE_PLAY;
- if (pc.command == PLAYER_COMMAND_SEEK)
- player.elapsed_time = pc.seek_where;
+ if (pc->command == PLAYER_COMMAND_SEEK)
+ player.elapsed_time = pc->seek_where;
- pc.state = PLAYER_STATE_PLAY;
- player_command_finished_locked();
+ player_command_finished_locked(pc);
while (true) {
player_process_command(&player);
- if (pc.command == PLAYER_COMMAND_STOP ||
- pc.command == PLAYER_COMMAND_EXIT ||
- pc.command == PLAYER_COMMAND_CLOSE_AUDIO) {
- player_unlock();
+ if (pc->command == PLAYER_COMMAND_STOP ||
+ pc->command == PLAYER_COMMAND_EXIT ||
+ pc->command == PLAYER_COMMAND_CLOSE_AUDIO) {
+ player_unlock(pc);
audio_output_all_cancel();
break;
}
- player_unlock();
+ player_unlock(pc);
if (player.buffering) {
/* buffering at the start of the song - wait
until the buffer is large enough, to
prevent stuttering on slow machines */
- if (music_pipe_size(player.pipe) < pc.buffered_before_play &&
+ if (music_pipe_size(player.pipe) < pc->buffered_before_play &&
!decoder_lock_is_idle(dc)) {
/* not enough decoded buffer space yet */
@@ -902,9 +917,9 @@ static void do_play(struct decoder_control *dc)
decoder_lock(dc);
/* XXX race condition: check decoder again */
- player_wait_decoder(dc);
+ player_wait_decoder(pc, dc);
decoder_unlock(dc);
- player_lock();
+ player_lock(pc);
continue;
} else {
/* buffering is complete */
@@ -918,7 +933,7 @@ static void do_play(struct decoder_control *dc)
if (!player_check_decoder_startup(&player))
break;
- player_lock();
+ player_lock(pc);
continue;
}
@@ -947,9 +962,9 @@ static void do_play(struct decoder_control *dc)
calculate how many chunks will be required
for it */
player.cross_fade_chunks =
- cross_fade_calc(pc.cross_fade_seconds, dc->total_time,
- pc.mixramp_db,
- pc.mixramp_delay_seconds,
+ cross_fade_calc(pc->cross_fade_seconds, dc->total_time,
+ pc->mixramp_db,
+ pc->mixramp_delay_seconds,
dc->replay_gain_db,
dc->replay_gain_prev_db,
dc->mixramp_start,
@@ -957,7 +972,7 @@ static void do_play(struct decoder_control *dc)
&dc->out_audio_format,
&player.play_audio_format,
music_buffer_size(player_buffer) -
- pc.buffered_before_play);
+ pc->buffered_before_play);
if (player.cross_fade_chunks > 0) {
player.xfade = XFADE_ENABLED;
player.cross_fading = false;
@@ -968,10 +983,10 @@ static void do_play(struct decoder_control *dc)
}
if (player.paused) {
- player_lock();
+ player_lock(pc);
- if (pc.command == PLAYER_COMMAND_NONE)
- player_wait();
+ if (pc->command == PLAYER_COMMAND_NONE)
+ player_wait(pc);
continue;
} else if (!music_pipe_empty(player.pipe)) {
/* at least one music chunk is ready - send it
@@ -1008,7 +1023,7 @@ static void do_play(struct decoder_control *dc)
break;
}
- player_lock();
+ player_lock(pc);
}
player_dc_stop(&player);
@@ -1019,113 +1034,115 @@ static void do_play(struct decoder_control *dc)
if (player.cross_fade_tag != NULL)
tag_free(player.cross_fade_tag);
- player_lock();
+ player_lock(pc);
if (player.queued) {
- assert(pc.next_song != NULL);
- pc.next_song = NULL;
+ assert(pc->next_song != NULL);
+ pc->next_song = NULL;
}
- pc.state = PLAYER_STATE_STOP;
+ pc->state = PLAYER_STATE_STOP;
- player_unlock();
+ player_unlock(pc);
event_pipe_emit(PIPE_EVENT_PLAYLIST);
- player_lock();
+ player_lock(pc);
}
-static gpointer player_task(G_GNUC_UNUSED gpointer arg)
+static gpointer
+player_task(gpointer arg)
{
- struct decoder_control dc;
+ struct player_control *pc = arg;
- dc_init(&dc);
- decoder_thread_start(&dc);
+ struct decoder_control *dc = dc_new(pc->cond);
+ decoder_thread_start(dc);
- player_buffer = music_buffer_new(pc.buffer_chunks);
+ player_buffer = music_buffer_new(pc->buffer_chunks);
- player_lock();
+ player_lock(pc);
while (1) {
- switch (pc.command) {
+ switch (pc->command) {
case PLAYER_COMMAND_SEEK:
case PLAYER_COMMAND_QUEUE:
- assert(pc.next_song != NULL);
+ assert(pc->next_song != NULL);
- do_play(&dc);
+ do_play(pc, dc);
break;
case PLAYER_COMMAND_STOP:
- player_unlock();
+ player_unlock(pc);
audio_output_all_cancel();
- player_lock();
+ player_lock(pc);
/* fall through */
case PLAYER_COMMAND_PAUSE:
- pc.next_song = NULL;
- player_command_finished_locked();
+ pc->next_song = NULL;
+ player_command_finished_locked(pc);
break;
case PLAYER_COMMAND_CLOSE_AUDIO:
- player_unlock();
+ player_unlock(pc);
audio_output_all_release();
- player_lock();
- player_command_finished_locked();
+ player_lock(pc);
+ player_command_finished_locked(pc);
#ifndef NDEBUG
/* in the DEBUG build, check for leaked
music_chunk objects by freeing the
music_buffer */
music_buffer_free(player_buffer);
- player_buffer = music_buffer_new(pc.buffer_chunks);
+ player_buffer = music_buffer_new(pc->buffer_chunks);
#endif
break;
case PLAYER_COMMAND_UPDATE_AUDIO:
- player_unlock();
+ player_unlock(pc);
audio_output_all_enable_disable();
- player_lock();
- player_command_finished_locked();
+ player_lock(pc);
+ player_command_finished_locked(pc);
break;
case PLAYER_COMMAND_EXIT:
- player_unlock();
+ player_unlock(pc);
- dc_quit(&dc);
- dc_deinit(&dc);
+ dc_quit(dc);
+ dc_free(dc);
audio_output_all_close();
music_buffer_free(player_buffer);
- player_command_finished();
+ player_command_finished(pc);
return NULL;
case PLAYER_COMMAND_CANCEL:
- pc.next_song = NULL;
- player_command_finished_locked();
+ pc->next_song = NULL;
+ player_command_finished_locked(pc);
break;
case PLAYER_COMMAND_REFRESH:
/* no-op when not playing */
- player_command_finished_locked();
+ player_command_finished_locked(pc);
break;
case PLAYER_COMMAND_NONE:
- player_wait();
+ player_wait(pc);
break;
}
}
}
-void player_create(void)
+void
+player_create(struct player_control *pc)
{
- assert(pc.thread == NULL);
+ assert(pc->thread == NULL);
GError *e = NULL;
- pc.thread = g_thread_create(player_task, NULL, true, &e);
- if (pc.thread == NULL)
+ pc->thread = g_thread_create(player_task, pc, true, &e);
+ if (pc->thread == NULL)
MPD_ERROR("Failed to spawn player task: %s", e->message);
}
diff --git a/src/player_thread.h b/src/player_thread.h
index e645b1d09..7373eb438 100644
--- a/src/player_thread.h
+++ b/src/player_thread.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -37,6 +37,9 @@
#ifndef MPD_PLAYER_THREAD_H
#define MPD_PLAYER_THREAD_H
-void player_create(void);
+struct player_control;
+
+void
+player_create(struct player_control *pc);
#endif
diff --git a/src/playlist.c b/src/playlist.c
index 4a1e54814..0c9eea92d 100644
--- a/src/playlist.c
+++ b/src/playlist.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -75,7 +75,8 @@ playlist_finish(struct playlist *playlist)
* Queue a song, addressed by its order number.
*/
static void
-playlist_queue_song_order(struct playlist *playlist, unsigned order)
+playlist_queue_song_order(struct playlist *playlist, struct player_control *pc,
+ unsigned order)
{
struct song *song;
char *uri;
@@ -89,16 +90,16 @@ playlist_queue_song_order(struct playlist *playlist, unsigned order)
g_debug("queue song %i:\"%s\"", playlist->queued, uri);
g_free(uri);
- pc_enqueue_song(song);
+ pc_enqueue_song(pc, song);
}
/**
* Called if the player thread has started playing the "queued" song.
*/
static void
-playlist_song_started(struct playlist *playlist)
+playlist_song_started(struct playlist *playlist, struct player_control *pc)
{
- assert(pc.next_song == NULL);
+ assert(pc->next_song == NULL);
assert(playlist->queued >= -1);
/* queued song has started: copy queued to current,
@@ -110,11 +111,13 @@ playlist_song_started(struct playlist *playlist)
/* Pause if we are in single mode. */
if(playlist->queue.single && !playlist->queue.repeat) {
- pc_set_pause(true);
+ pc_set_pause(pc, true);
}
if(playlist->queue.consume)
- playlist_delete(playlist, queue_order_to_position(&playlist->queue, current));
+ playlist_delete(playlist, pc,
+ queue_order_to_position(&playlist->queue,
+ current));
idle_add(IDLE_PLAYER);
}
@@ -129,7 +132,9 @@ playlist_get_queued_song(struct playlist *playlist)
}
void
-playlist_update_queued_song(struct playlist *playlist, const struct song *prev)
+playlist_update_queued_song(struct playlist *playlist,
+ struct player_control *pc,
+ const struct song *prev)
{
int next_order;
const struct song *next_song;
@@ -170,20 +175,21 @@ playlist_update_queued_song(struct playlist *playlist, const struct song *prev)
if (prev != NULL && next_song != prev) {
/* clear the currently queued song */
- pc_cancel();
+ pc_cancel(pc);
playlist->queued = -1;
}
if (next_order >= 0) {
if (next_song != prev)
- playlist_queue_song_order(playlist, next_order);
+ playlist_queue_song_order(playlist, pc, next_order);
else
playlist->queued = next_order;
}
}
void
-playlist_play_order(struct playlist *playlist, int orderNum)
+playlist_play_order(struct playlist *playlist, struct player_control *pc,
+ int orderNum)
{
struct song *song;
char *uri;
@@ -197,46 +203,46 @@ playlist_play_order(struct playlist *playlist, int orderNum)
g_debug("play %i:\"%s\"", orderNum, uri);
g_free(uri);
- pc_play(song);
+ pc_play(pc, song);
playlist->current = orderNum;
}
static void
-playlist_resume_playback(struct playlist *playlist);
+playlist_resume_playback(struct playlist *playlist, struct player_control *pc);
/**
* This is the "PLAYLIST" event handler. It is invoked by the player
* thread whenever it requests a new queued song, or when it exits.
*/
void
-playlist_sync(struct playlist *playlist)
+playlist_sync(struct playlist *playlist, struct player_control *pc)
{
if (!playlist->playing)
/* this event has reached us out of sync: we aren't
playing anymore; ignore the event */
return;
- player_lock();
- enum player_state pc_state = pc_get_state();
- const struct song *pc_next_song = pc.next_song;
- player_unlock();
+ player_lock(pc);
+ enum player_state pc_state = pc_get_state(pc);
+ const struct song *pc_next_song = pc->next_song;
+ player_unlock(pc);
if (pc_state == PLAYER_STATE_STOP)
/* the player thread has stopped: check if playback
should be restarted with the next song. That can
happen if the playlist isn't filling the queue fast
enough */
- playlist_resume_playback(playlist);
+ playlist_resume_playback(playlist, pc);
else {
/* check if the player thread has already started
playing the queued song */
if (pc_next_song == NULL && playlist->queued != -1)
- playlist_song_started(playlist);
+ playlist_song_started(playlist, pc);
/* make sure the queued song is always set (if
possible) */
- if (pc.next_song == NULL && playlist->queued < 0)
- playlist_update_queued_song(playlist, NULL);
+ if (pc->next_song == NULL && playlist->queued < 0)
+ playlist_update_queued_song(playlist, pc, NULL);
}
}
@@ -245,14 +251,14 @@ playlist_sync(struct playlist *playlist)
* decide whether to re-start playback
*/
static void
-playlist_resume_playback(struct playlist *playlist)
+playlist_resume_playback(struct playlist *playlist, struct player_control *pc)
{
enum player_error error;
assert(playlist->playing);
- assert(pc_get_state() == PLAYER_STATE_STOP);
+ assert(pc_get_state(pc) == PLAYER_STATE_STOP);
- error = pc_get_error();
+ error = pc_get_error(pc);
if (error == PLAYER_ERROR_NOERROR)
playlist->error_count = 0;
else
@@ -263,10 +269,10 @@ playlist_resume_playback(struct playlist *playlist)
playlist->error_count >= queue_length(&playlist->queue))
/* too many errors, or critical error: stop
playback */
- playlist_stop(playlist);
+ playlist_stop(playlist, pc);
else
/* continue playback at the next song */
- playlist_next(playlist);
+ playlist_next(playlist, pc);
}
bool
@@ -294,7 +300,8 @@ playlist_get_consume(const struct playlist *playlist)
}
void
-playlist_set_repeat(struct playlist *playlist, bool status)
+playlist_set_repeat(struct playlist *playlist, struct player_control *pc,
+ bool status)
{
if (status == playlist->queue.repeat)
return;
@@ -303,7 +310,7 @@ playlist_set_repeat(struct playlist *playlist, bool status)
/* if the last song is currently being played, the "next song"
might change when repeat mode is toggled */
- playlist_update_queued_song(playlist,
+ playlist_update_queued_song(playlist, pc,
playlist_get_queued_song(playlist));
idle_add(IDLE_OPTIONS);
@@ -321,7 +328,8 @@ playlist_order(struct playlist *playlist)
}
void
-playlist_set_single(struct playlist *playlist, bool status)
+playlist_set_single(struct playlist *playlist, struct player_control *pc,
+ bool status)
{
if (status == playlist->queue.single)
return;
@@ -330,7 +338,7 @@ playlist_set_single(struct playlist *playlist, bool status)
/* if the last song is currently being played, the "next song"
might change when single mode is toggled */
- playlist_update_queued_song(playlist,
+ playlist_update_queued_song(playlist, pc,
playlist_get_queued_song(playlist));
idle_add(IDLE_OPTIONS);
@@ -347,7 +355,8 @@ playlist_set_consume(struct playlist *playlist, bool status)
}
void
-playlist_set_random(struct playlist *playlist, bool status)
+playlist_set_random(struct playlist *playlist, struct player_control *pc,
+ bool status)
{
const struct song *queued;
@@ -384,7 +393,7 @@ playlist_set_random(struct playlist *playlist, bool status)
} else
playlist_order(playlist);
- playlist_update_queued_song(playlist, queued);
+ playlist_update_queued_song(playlist, pc, queued);
idle_add(IDLE_OPTIONS);
}
diff --git a/src/playlist.h b/src/playlist.h
index 3ba90ff91..a21bdf24a 100644
--- a/src/playlist.h
+++ b/src/playlist.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -21,24 +21,11 @@
#define MPD_PLAYLIST_H
#include "queue.h"
+#include "playlist_error.h"
#include <stdbool.h>
-#define PLAYLIST_COMMENT '#'
-
-enum playlist_result {
- PLAYLIST_RESULT_SUCCESS,
- PLAYLIST_RESULT_ERRNO,
- PLAYLIST_RESULT_DENIED,
- PLAYLIST_RESULT_NO_SUCH_SONG,
- PLAYLIST_RESULT_NO_SUCH_LIST,
- PLAYLIST_RESULT_LIST_EXISTS,
- PLAYLIST_RESULT_BAD_NAME,
- PLAYLIST_RESULT_BAD_RANGE,
- PLAYLIST_RESULT_NOT_PLAYING,
- PLAYLIST_RESULT_TOO_LARGE,
- PLAYLIST_RESULT_DISABLED,
-};
+struct player_control;
struct playlist {
/**
@@ -111,28 +98,28 @@ playlist_get_queue(const struct playlist *playlist)
}
void
-playlist_clear(struct playlist *playlist);
+playlist_clear(struct playlist *playlist, struct player_control *pc);
-#ifndef WIN32
/**
- * Appends a local file (outside the music database) to the playlist,
- * but only if the file's owner is equal to the specified uid.
+ * Appends a local file (outside the music database) to the playlist.
+ *
+ * Note: the caller is responsible for checking permissions.
*/
enum playlist_result
-playlist_append_file(struct playlist *playlist, const char *path, int uid,
- unsigned *added_id);
-#endif
+playlist_append_file(struct playlist *playlist, struct player_control *pc,
+ const char *path_fs, unsigned *added_id);
enum playlist_result
-playlist_append_uri(struct playlist *playlist, const char *file,
- unsigned *added_id);
+playlist_append_uri(struct playlist *playlist, struct player_control *pc,
+ const char *file, unsigned *added_id);
enum playlist_result
-playlist_append_song(struct playlist *playlist,
+playlist_append_song(struct playlist *playlist, struct player_control *pc,
struct song *song, unsigned *added_id);
enum playlist_result
-playlist_delete(struct playlist *playlist, unsigned song);
+playlist_delete(struct playlist *playlist, struct player_control *pc,
+ unsigned song);
/**
* Deletes a range of songs from the playlist.
@@ -141,64 +128,86 @@ playlist_delete(struct playlist *playlist, unsigned song);
* @param end the position after the last song to delete
*/
enum playlist_result
-playlist_delete_range(struct playlist *playlist, unsigned start, unsigned end);
+playlist_delete_range(struct playlist *playlist, struct player_control *pc,
+ unsigned start, unsigned end);
enum playlist_result
-playlist_delete_id(struct playlist *playlist, unsigned song);
+playlist_delete_id(struct playlist *playlist, struct player_control *pc,
+ unsigned song);
void
-playlist_stop(struct playlist *playlist);
+playlist_stop(struct playlist *playlist, struct player_control *pc);
enum playlist_result
-playlist_play(struct playlist *playlist, int song);
+playlist_play(struct playlist *playlist, struct player_control *pc,
+ int song);
enum playlist_result
-playlist_play_id(struct playlist *playlist, int song);
+playlist_play_id(struct playlist *playlist, struct player_control *pc,
+ int song);
void
-playlist_next(struct playlist *playlist);
+playlist_next(struct playlist *playlist, struct player_control *pc);
void
-playlist_sync(struct playlist *playlist);
+playlist_sync(struct playlist *playlist, struct player_control *pc);
void
-playlist_previous(struct playlist *playlist);
+playlist_previous(struct playlist *playlist, struct player_control *pc);
void
-playlist_shuffle(struct playlist *playlist, unsigned start, unsigned end);
+playlist_shuffle(struct playlist *playlist, struct player_control *pc,
+ unsigned start, unsigned end);
void
-playlist_delete_song(struct playlist *playlist, const struct song *song);
+playlist_delete_song(struct playlist *playlist, struct player_control *pc,
+ const struct song *song);
+
+enum playlist_result
+playlist_move_range(struct playlist *playlist, struct player_control *pc,
+ unsigned start, unsigned end, int to);
+
+enum playlist_result
+playlist_move_id(struct playlist *playlist, struct player_control *pc,
+ unsigned id, int to);
enum playlist_result
-playlist_move_range(struct playlist *playlist, unsigned start, unsigned end, int to);
+playlist_swap_songs(struct playlist *playlist, struct player_control *pc,
+ unsigned song1, unsigned song2);
enum playlist_result
-playlist_move_id(struct playlist *playlist, unsigned id, int to);
+playlist_swap_songs_id(struct playlist *playlist, struct player_control *pc,
+ unsigned id1, unsigned id2);
enum playlist_result
-playlist_swap_songs(struct playlist *playlist, unsigned song1, unsigned song2);
+playlist_set_priority(struct playlist *playlist, struct player_control *pc,
+ unsigned start_position, unsigned end_position,
+ uint8_t priority);
enum playlist_result
-playlist_swap_songs_id(struct playlist *playlist, unsigned id1, unsigned id2);
+playlist_set_priority_id(struct playlist *playlist, struct player_control *pc,
+ unsigned song_id, uint8_t priority);
bool
playlist_get_repeat(const struct playlist *playlist);
void
-playlist_set_repeat(struct playlist *playlist, bool status);
+playlist_set_repeat(struct playlist *playlist, struct player_control *pc,
+ bool status);
bool
playlist_get_random(const struct playlist *playlist);
void
-playlist_set_random(struct playlist *playlist, bool status);
+playlist_set_random(struct playlist *playlist, struct player_control *pc,
+ bool status);
bool
playlist_get_single(const struct playlist *playlist);
void
-playlist_set_single(struct playlist *playlist, bool status);
+playlist_set_single(struct playlist *playlist, struct player_control *pc,
+ bool status);
bool
playlist_get_consume(const struct playlist *playlist);
@@ -222,12 +231,25 @@ unsigned long
playlist_get_version(const struct playlist *playlist);
enum playlist_result
-playlist_seek_song(struct playlist *playlist, unsigned song, float seek_time);
+playlist_seek_song(struct playlist *playlist, struct player_control *pc,
+ unsigned song, float seek_time);
enum playlist_result
-playlist_seek_song_id(struct playlist *playlist,
+playlist_seek_song_id(struct playlist *playlist, struct player_control *pc,
unsigned id, float seek_time);
+/**
+ * Seek within the current song. Fails if MPD is not currently
+ * playing.
+ *
+ * @param time the time in seconds
+ * @param relative if true, then the specified time is relative to the
+ * current position
+ */
+enum playlist_result
+playlist_seek_current(struct playlist *playlist, struct player_control *pc,
+ float seek_time, bool relative);
+
void
playlist_increment_version_all(struct playlist *playlist);
diff --git a/src/playlist/asx_playlist_plugin.c b/src/playlist/asx_playlist_plugin.c
index 39513e710..298687859 100644
--- a/src/playlist/asx_playlist_plugin.c
+++ b/src/playlist/asx_playlist_plugin.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -233,7 +233,8 @@ asx_open_stream(struct input_stream *is)
&parser, asx_parser_destroy);
while (true) {
- nbytes = input_stream_read(is, buffer, sizeof(buffer), &error);
+ nbytes = input_stream_lock_read(is, buffer, sizeof(buffer),
+ &error);
if (nbytes == 0) {
if (error != NULL) {
g_markup_parse_context_free(context);
diff --git a/src/playlist/asx_playlist_plugin.h b/src/playlist/asx_playlist_plugin.h
index 7ce91aa41..6c01c1209 100644
--- a/src/playlist/asx_playlist_plugin.h
+++ b/src/playlist/asx_playlist_plugin.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/playlist/cue_playlist_plugin.c b/src/playlist/cue_playlist_plugin.c
index b22712bc7..b85de77d3 100644
--- a/src/playlist/cue_playlist_plugin.c
+++ b/src/playlist/cue_playlist_plugin.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -22,10 +22,11 @@
#include "playlist_plugin.h"
#include "tag.h"
#include "song.h"
-#include "cue/cue_tag.h"
+#include "cue/cue_parser.h"
+#include "input_stream.h"
+#include "text_input_stream.h"
#include <glib.h>
-#include <libcue/libcue.h>
#include <assert.h>
#include <string.h>
@@ -35,31 +36,21 @@
struct cue_playlist {
struct playlist_provider base;
- struct Cd *cd;
-
- unsigned next;
+ struct input_stream *is;
+ struct text_input_stream *tis;
+ struct cue_parser *parser;
};
static struct playlist_provider *
-cue_playlist_open_uri(const char *uri)
+cue_playlist_open_stream(struct input_stream *is)
{
- struct cue_playlist *playlist;
- FILE *file;
- struct Cd *cd;
-
- file = fopen(uri, "rt");
- if (file == NULL)
- return NULL;
+ struct cue_playlist *playlist = g_new(struct cue_playlist, 1);
+ playlist_provider_init(&playlist->base, &cue_playlist_plugin);
- cd = cue_parse_file(file);
- fclose(file);
- if (cd == NULL)
- return NULL;
+ playlist->is = is;
+ playlist->tis = text_input_stream_new(is);
+ playlist->parser = cue_parser_new();
- playlist = g_new(struct cue_playlist, 1);
- playlist_provider_init(&playlist->base, &cue_playlist_plugin);
- playlist->cd = cd;
- playlist->next = 1;
return &playlist->base;
}
@@ -69,7 +60,8 @@ cue_playlist_close(struct playlist_provider *_playlist)
{
struct cue_playlist *playlist = (struct cue_playlist *)_playlist;
- cd_delete(playlist->cd);
+ cue_parser_free(playlist->parser);
+ text_input_stream_free(playlist->tis);
g_free(playlist);
}
@@ -77,45 +69,21 @@ static struct song *
cue_playlist_read(struct playlist_provider *_playlist)
{
struct cue_playlist *playlist = (struct cue_playlist *)_playlist;
- struct Track *track;
- struct tag *tag;
- const char *filename;
- struct song *song;
-
- track = cd_get_track(playlist->cd, playlist->next);
- if (track == NULL)
- return NULL;
-
- tag = cue_tag(playlist->cd, playlist->next);
- if (tag == NULL)
- return NULL;
-
- ++playlist->next;
-
- filename = track_get_filename(track);
- if (*filename == 0 || filename[0] == '.' ||
- strchr(filename, '/') != NULL) {
- /* unsafe characters found, bail out */
- tag_free(tag);
- return NULL;
+
+ struct song *song = cue_parser_get(playlist->parser);
+ if (song != NULL)
+ return song;
+
+ const char *line;
+ while ((line = text_input_stream_read(playlist->tis)) != NULL) {
+ cue_parser_feed(playlist->parser, line);
+ song = cue_parser_get(playlist->parser);
+ if (song != NULL)
+ return song;
}
- song = song_remote_new(filename);
- song->tag = tag;
- song->start_ms = ((track_get_start(track)
- + track_get_index(track, 1)
- - track_get_zero_pre(track)) * 1000) / 75;
-
- /* append pregap of the next track to the end of this one */
- track = cd_get_track(playlist->cd, playlist->next);
- if (track != NULL)
- song->end_ms = ((track_get_start(track)
- + track_get_index(track, 1)
- - track_get_zero_pre(track)) * 1000) / 75;
- else
- song->end_ms = 0;
-
- return song;
+ cue_parser_finish(playlist->parser);
+ return cue_parser_get(playlist->parser);
}
static const char *const cue_playlist_suffixes[] = {
@@ -131,7 +99,7 @@ static const char *const cue_playlist_mime_types[] = {
const struct playlist_plugin cue_playlist_plugin = {
.name = "cue",
- .open_uri = cue_playlist_open_uri,
+ .open_stream = cue_playlist_open_stream,
.close = cue_playlist_close,
.read = cue_playlist_read,
diff --git a/src/playlist/cue_playlist_plugin.h b/src/playlist/cue_playlist_plugin.h
index c89ec55c5..c02e2235a 100644
--- a/src/playlist/cue_playlist_plugin.h
+++ b/src/playlist/cue_playlist_plugin.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/playlist/despotify_playlist_plugin.c b/src/playlist/despotify_playlist_plugin.c
new file mode 100644
index 000000000..08a32d79d
--- /dev/null
+++ b/src/playlist/despotify_playlist_plugin.c
@@ -0,0 +1,217 @@
+/*
+ * Copyright (C) 2011 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#include "config.h"
+#include "playlist/despotify_playlist_plugin.h"
+#include "playlist_plugin.h"
+#include "playlist_list.h"
+#include "conf.h"
+#include "uri.h"
+#include "tag.h"
+#include "song.h"
+#include "input_stream.h"
+#include "glib_compat.h"
+#include "despotify_utils.h"
+
+#include <glib.h>
+
+#include <assert.h>
+#include <string.h>
+#include <stdlib.h>
+#include <despotify.h>
+
+struct despotify_playlist {
+ struct playlist_provider base;
+
+ struct despotify_session *session;
+ GSList *list;
+};
+
+static void
+add_song(struct despotify_playlist *ctx, struct ds_track *track)
+{
+ const char *dsp_scheme = despotify_playlist_plugin.schemes[0];
+ struct song *song;
+ char uri[128];
+ char *ds_uri;
+
+ /* Create a spt://... URI for MPD */
+ g_snprintf(uri, sizeof(uri), "%s://", dsp_scheme);
+ ds_uri = uri + strlen(dsp_scheme) + 3;
+
+ if (despotify_track_to_uri(track, ds_uri) != ds_uri) {
+ /* Should never really fail, but let's be sure */
+ g_debug("Can't add track %s\n", track->title);
+ return;
+ }
+
+ song = song_remote_new(uri);
+ song->tag = mpd_despotify_tag_from_track(track);
+
+ ctx->list = g_slist_prepend(ctx->list, song);
+}
+
+static bool
+parse_track(struct despotify_playlist *ctx,
+ struct ds_link *link)
+{
+ struct ds_track *track;
+
+ track = despotify_link_get_track(ctx->session, link);
+ if (!track)
+ return false;
+ add_song(ctx, track);
+
+ return true;
+}
+
+static bool
+parse_playlist(struct despotify_playlist *ctx,
+ struct ds_link *link)
+{
+ struct ds_playlist *playlist;
+ struct ds_track *track;
+
+ playlist = despotify_link_get_playlist(ctx->session, link);
+ if (!playlist)
+ return false;
+
+ for (track = playlist->tracks; track; track = track->next)
+ add_song(ctx, track);
+
+ return true;
+}
+
+static bool
+despotify_playlist_init(G_GNUC_UNUSED const struct config_param *param)
+{
+ return true;
+}
+
+static void
+despotify_playlist_finish(void)
+{
+}
+
+
+static struct playlist_provider *
+despotify_playlist_open_uri(const char *url, G_GNUC_UNUSED GMutex *mutex,
+ G_GNUC_UNUSED GCond *cond)
+{
+ struct despotify_playlist *ctx;
+ struct despotify_session *session;
+ struct ds_link *link;
+ bool parse_result;
+
+ session = mpd_despotify_get_session();
+ if (!session)
+ goto clean_none;
+
+ /* Get link without spt:// */
+ link = despotify_link_from_uri(url + strlen(despotify_playlist_plugin.schemes[0]) + 3);
+ if (!link) {
+ g_debug("Can't find %s\n", url);
+ goto clean_none;
+ }
+
+ ctx = g_new(struct despotify_playlist, 1);
+
+ ctx->list = NULL;
+ ctx->session = session;
+ playlist_provider_init(&ctx->base, &despotify_playlist_plugin);
+
+ switch (link->type)
+ {
+ case LINK_TYPE_TRACK:
+ parse_result = parse_track(ctx, link);
+ break;
+ case LINK_TYPE_PLAYLIST:
+ parse_result = parse_playlist(ctx, link);
+ break;
+ default:
+ parse_result = false;
+ break;
+ }
+ despotify_free_link(link);
+ if (!parse_result)
+ goto clean_playlist;
+
+ ctx->list = g_slist_reverse(ctx->list);
+
+ return &ctx->base;
+
+clean_playlist:
+ g_slist_free(ctx->list);
+clean_none:
+
+ return NULL;
+}
+
+static void
+track_free_callback(gpointer data, G_GNUC_UNUSED gpointer user_data)
+{
+ struct song *song = (struct song *)data;
+
+ song_free(song);
+}
+
+static void
+despotify_playlist_close(struct playlist_provider *_playlist)
+{
+ struct despotify_playlist *ctx = (struct despotify_playlist *)_playlist;
+
+ g_slist_foreach(ctx->list, track_free_callback, NULL);
+ g_slist_free(ctx->list);
+
+ g_free(ctx);
+}
+
+
+static struct song *
+despotify_playlist_read(struct playlist_provider *_playlist)
+{
+ struct despotify_playlist *ctx = (struct despotify_playlist *)_playlist;
+ struct song *out;
+
+ if (!ctx->list)
+ return NULL;
+
+ /* Remove the current track */
+ out = ctx->list->data;
+ ctx->list = g_slist_remove(ctx->list, out);
+
+ return out;
+}
+
+
+static const char *const despotify_schemes[] = {
+ "spt",
+ NULL
+};
+
+const struct playlist_plugin despotify_playlist_plugin = {
+ .name = "despotify",
+
+ .init = despotify_playlist_init,
+ .finish = despotify_playlist_finish,
+ .open_uri = despotify_playlist_open_uri,
+ .read = despotify_playlist_read,
+ .close = despotify_playlist_close,
+
+ .schemes = despotify_schemes,
+};
diff --git a/src/playlist/despotify_playlist_plugin.h b/src/playlist/despotify_playlist_plugin.h
new file mode 100644
index 000000000..f8ee20de0
--- /dev/null
+++ b/src/playlist/despotify_playlist_plugin.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2011 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_PLAYLIST_DESPOTIFY_PLAYLIST_PLUGIN_H
+#define MPD_PLAYLIST_DESPOTIFY_PLAYLIST_PLUGIN_H
+
+extern const struct playlist_plugin despotify_playlist_plugin;
+
+#endif
diff --git a/src/playlist/embcue_playlist_plugin.c b/src/playlist/embcue_playlist_plugin.c
new file mode 100644
index 000000000..6d9a957f9
--- /dev/null
+++ b/src/playlist/embcue_playlist_plugin.c
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2003-2012 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/** \file
+ *
+ * Playlist plugin that reads embedded cue sheets from the "CUESHEET"
+ * tag of a music file.
+ */
+
+#include "config.h"
+#include "playlist/embcue_playlist_plugin.h"
+#include "playlist_plugin.h"
+#include "tag.h"
+#include "tag_handler.h"
+#include "tag_file.h"
+#include "tag_ape.h"
+#include "tag_id3.h"
+#include "song.h"
+#include "cue/cue_parser.h"
+
+#include <glib.h>
+#include <assert.h>
+#include <string.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "cue"
+
+struct embcue_playlist {
+ struct playlist_provider base;
+
+ /**
+ * This is an override for the CUE's "FILE". An embedded CUE
+ * sheet must always point to the song file it is contained
+ * in.
+ */
+ char *filename;
+
+ /**
+ * The value of the file's "CUESHEET" tag.
+ */
+ char *cuesheet;
+
+ /**
+ * The offset of the next line within "cuesheet".
+ */
+ char *next;
+
+ struct cue_parser *parser;
+};
+
+static void
+embcue_tag_pair(const char *name, const char *value, void *ctx)
+{
+ struct embcue_playlist *playlist = ctx;
+
+ if (playlist->cuesheet == NULL &&
+ g_ascii_strcasecmp(name, "cuesheet") == 0)
+ playlist->cuesheet = g_strdup(value);
+}
+
+static const struct tag_handler embcue_tag_handler = {
+ .pair = embcue_tag_pair,
+};
+
+static struct playlist_provider *
+embcue_playlist_open_uri(const char *uri,
+ G_GNUC_UNUSED GMutex *mutex,
+ G_GNUC_UNUSED GCond *cond)
+{
+ if (!g_path_is_absolute(uri))
+ /* only local files supported */
+ return NULL;
+
+ struct embcue_playlist *playlist = g_new(struct embcue_playlist, 1);
+ playlist_provider_init(&playlist->base, &embcue_playlist_plugin);
+ playlist->cuesheet = NULL;
+
+ tag_file_scan(uri, &embcue_tag_handler, playlist);
+ if (playlist->cuesheet == NULL) {
+ tag_ape_scan2(uri, &embcue_tag_handler, playlist);
+ if (playlist->cuesheet == NULL)
+ tag_id3_scan(uri, &embcue_tag_handler, playlist);
+ }
+
+ if (playlist->cuesheet == NULL) {
+ /* no "CUESHEET" tag found */
+ g_free(playlist);
+ return NULL;
+ }
+
+ playlist->filename = g_path_get_basename(uri);
+
+ playlist->next = playlist->cuesheet;
+ playlist->parser = cue_parser_new();
+
+ return &playlist->base;
+}
+
+static void
+embcue_playlist_close(struct playlist_provider *_playlist)
+{
+ struct embcue_playlist *playlist = (struct embcue_playlist *)_playlist;
+
+ cue_parser_free(playlist->parser);
+ g_free(playlist->cuesheet);
+ g_free(playlist->filename);
+ g_free(playlist);
+}
+
+static struct song *
+embcue_playlist_read(struct playlist_provider *_playlist)
+{
+ struct embcue_playlist *playlist = (struct embcue_playlist *)_playlist;
+
+ struct song *song = cue_parser_get(playlist->parser);
+ if (song != NULL)
+ return song;
+
+ while (*playlist->next != 0) {
+ const char *line = playlist->next;
+ char *eol = strpbrk(playlist->next, "\r\n");
+ if (eol != NULL) {
+ /* null-terminate the line */
+ *eol = 0;
+ playlist->next = eol + 1;
+ } else
+ /* last line; put the "next" pointer to the
+ end of the buffer */
+ playlist->next += strlen(line);
+
+ cue_parser_feed(playlist->parser, line);
+ song = cue_parser_get(playlist->parser);
+ if (song != NULL)
+ return song_replace_uri(song, playlist->filename);
+ }
+
+ cue_parser_finish(playlist->parser);
+ song = cue_parser_get(playlist->parser);
+ if (song != NULL)
+ song = song_replace_uri(song, playlist->filename);
+ return song;
+}
+
+static const char *const embcue_playlist_suffixes[] = {
+ /* a few codecs that are known to be supported; there are
+ probably many more */
+ "flac",
+ "mp3", "mp2",
+ "mp4", "mp4a", "m4b",
+ "ape",
+ "wv",
+ "ogg", "oga",
+ NULL
+};
+
+const struct playlist_plugin embcue_playlist_plugin = {
+ .name = "cue",
+
+ .open_uri = embcue_playlist_open_uri,
+ .close = embcue_playlist_close,
+ .read = embcue_playlist_read,
+
+ .suffixes = embcue_playlist_suffixes,
+ .mime_types = NULL,
+};
diff --git a/src/playlist/embcue_playlist_plugin.h b/src/playlist/embcue_playlist_plugin.h
new file mode 100644
index 000000000..c5f21b27e
--- /dev/null
+++ b/src/playlist/embcue_playlist_plugin.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2012 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_PLAYLIST_EMBCUE_PLAYLIST_PLUGIN_H
+#define MPD_PLAYLIST_EMBCUE_PLAYLIST_PLUGIN_H
+
+extern const struct playlist_plugin embcue_playlist_plugin;
+
+#endif
diff --git a/src/playlist/extm3u_playlist_plugin.c b/src/playlist/extm3u_playlist_plugin.c
index 9a04aa066..19be8d1c4 100644
--- a/src/playlist/extm3u_playlist_plugin.c
+++ b/src/playlist/extm3u_playlist_plugin.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -24,6 +24,7 @@
#include "uri.h"
#include "song.h"
#include "tag.h"
+#include "string_util.h"
#include <glib.h>
@@ -89,7 +90,7 @@ extm3u_parse_tag(const char *line)
/* 0 means unknown duration */
duration = 0;
- name = g_strchug(endptr + 1);
+ name = strchug_fast_c(endptr + 1);
if (*name == 0 && duration == 0)
/* no information available; don't allocate a tag
object */
diff --git a/src/playlist/extm3u_playlist_plugin.h b/src/playlist/extm3u_playlist_plugin.h
index fa726c5f6..5f611ac9c 100644
--- a/src/playlist/extm3u_playlist_plugin.h
+++ b/src/playlist/extm3u_playlist_plugin.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/playlist/flac_playlist_plugin.c b/src/playlist/flac_playlist_plugin.c
deleted file mode 100644
index 9d66fb331..000000000
--- a/src/playlist/flac_playlist_plugin.c
+++ /dev/null
@@ -1,170 +0,0 @@
-/*
- * 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/flac_playlist_plugin.h"
-#include "playlist_plugin.h"
-#include "tag.h"
-#include "song.h"
-#include "decoder/flac_metadata.h"
-
-#include <FLAC/metadata.h>
-
-#include <glib.h>
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "flac"
-
-#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7
-
-struct flac_playlist {
- struct playlist_provider base;
-
- char *uri;
-
- FLAC__StreamMetadata *cuesheet;
- FLAC__StreamMetadata streaminfo;
-
- unsigned next_track;
-};
-
-static struct playlist_provider *
-flac_playlist_open_uri(const char *uri)
-{
- if (!g_path_is_absolute(uri))
- /* only local files supported */
- return NULL;
-
- FLAC__StreamMetadata *cuesheet;
- if (!FLAC__metadata_get_cuesheet(uri, &cuesheet))
- return NULL;
-
- struct flac_playlist *playlist = g_new(struct flac_playlist, 1);
- playlist_provider_init(&playlist->base, &flac_playlist_plugin);
-
- if (!FLAC__metadata_get_streaminfo(uri, &playlist->streaminfo)) {
- FLAC__metadata_object_delete(playlist->cuesheet);
- g_free(playlist);
- return NULL;
- }
-
- playlist->uri = g_strdup(uri);
- playlist->cuesheet = cuesheet;
- playlist->next_track = 0;
-
- return &playlist->base;
-}
-
-static void
-flac_playlist_close(struct playlist_provider *_playlist)
-{
- struct flac_playlist *playlist = (struct flac_playlist *)_playlist;
-
- g_free(playlist->uri);
- FLAC__metadata_object_delete(playlist->cuesheet);
- g_free(playlist);
-}
-
-static struct song *
-flac_playlist_read(struct playlist_provider *_playlist)
-{
- struct flac_playlist *playlist = (struct flac_playlist *)_playlist;
- const FLAC__StreamMetadata_CueSheet *cs =
- &playlist->cuesheet->data.cue_sheet;
-
- /* find the next audio track */
-
- while (playlist->next_track < cs->num_tracks &&
- (cs->tracks[playlist->next_track].number > cs->num_tracks ||
- cs->tracks[playlist->next_track].type != 0))
- ++playlist->next_track;
-
- if (playlist->next_track >= cs->num_tracks)
- return NULL;
-
- FLAC__uint64 start = cs->tracks[playlist->next_track].offset;
- ++playlist->next_track;
- FLAC__uint64 end = playlist->next_track < cs->num_tracks
- ? cs->tracks[playlist->next_track].offset
- : playlist->streaminfo.data.stream_info.total_samples;
-
- struct song *song = song_file_new(playlist->uri, NULL);
- song->start_ms = start * 1000 /
- playlist->streaminfo.data.stream_info.sample_rate;
- song->end_ms = end * 1000 /
- playlist->streaminfo.data.stream_info.sample_rate;
-
- char track[16];
- g_snprintf(track, sizeof(track), "%u", playlist->next_track);
- song->tag = flac_tag_load(playlist->uri, track);
- if (song->tag == NULL)
- song->tag = tag_new();
-
- song->tag->time = end > start
- ? ((end - start - 1 +
- playlist->streaminfo.data.stream_info.sample_rate) /
- playlist->streaminfo.data.stream_info.sample_rate)
- : 0;
-
- tag_clear_items_by_type(song->tag, TAG_TRACK);
- tag_add_item(song->tag, TAG_TRACK, track);
-
- return song;
-}
-
-static const char *const flac_playlist_suffixes[] = {
- "flac",
- NULL
-};
-
-static const char *const flac_playlist_mime_types[] = {
- "application/flac",
- "application/x-flac",
- "audio/flac",
- "audio/x-flac",
- NULL
-};
-
-const struct playlist_plugin flac_playlist_plugin = {
- .name = "flac",
-
- .open_uri = flac_playlist_open_uri,
- .close = flac_playlist_close,
- .read = flac_playlist_read,
-
- .suffixes = flac_playlist_suffixes,
- .mime_types = flac_playlist_mime_types,
-};
-
-#else /* FLAC_API_VERSION_CURRENT <= 7 */
-
-static bool
-flac_playlist_init(G_GNUC_UNUSED const struct config_param *param)
-{
- /* this libFLAC version does not support embedded CUE sheets;
- disable this plugin */
- return false;
-}
-
-const struct playlist_plugin flac_playlist_plugin = {
- .name = "flac",
- .init = flac_playlist_init,
-};
-
-#endif
diff --git a/src/playlist/lastfm_playlist_plugin.c b/src/playlist/lastfm_playlist_plugin.c
index afb3979d9..86113643c 100644
--- a/src/playlist/lastfm_playlist_plugin.c
+++ b/src/playlist/lastfm_playlist_plugin.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -83,15 +83,14 @@ lastfm_finish(void)
* @return data fetched, or NULL on error. Must be freed with g_free.
*/
static char *
-lastfm_get(const char *url)
+lastfm_get(const char *url, GMutex *mutex, GCond *cond)
{
struct input_stream *input_stream;
GError *error = NULL;
- int ret;
char buffer[4096];
size_t length = 0, nbytes;
- input_stream = input_stream_open(url, &error);
+ input_stream = input_stream_open(url, mutex, cond, &error);
if (input_stream == NULL) {
if (error != NULL) {
g_warning("%s", error->message);
@@ -101,15 +100,9 @@ lastfm_get(const char *url)
return NULL;
}
- while (!input_stream->ready) {
- ret = input_stream_buffer(input_stream, &error);
- if (ret < 0) {
- input_stream_close(input_stream);
- g_warning("%s", error->message);
- g_error_free(error);
- return NULL;
- }
- }
+ g_mutex_lock(mutex);
+
+ input_stream_wait_ready(input_stream);
do {
nbytes = input_stream_read(input_stream, buffer + length,
@@ -124,6 +117,7 @@ lastfm_get(const char *url)
break;
/* I/O error */
+ g_mutex_unlock(mutex);
input_stream_close(input_stream);
return NULL;
}
@@ -131,6 +125,8 @@ lastfm_get(const char *url)
length += nbytes;
} while (length < sizeof(buffer));
+ g_mutex_unlock(mutex);
+
input_stream_close(input_stream);
return g_strndup(buffer, length);
}
@@ -139,7 +135,7 @@ lastfm_get(const char *url)
* Ini-style value fetcher.
* @param response data through which to search.
* @param name name of value to search for.
- * @return value for param name in param reponse or NULL on error. Free with g_free.
+ * @return value for param name in param response or NULL on error. Free with g_free.
*/
static char *
lastfm_find(const char *response, const char *name)
@@ -162,7 +158,7 @@ lastfm_find(const char *response, const char *name)
}
static struct playlist_provider *
-lastfm_open_uri(const char *uri)
+lastfm_open_uri(const char *uri, GMutex *mutex, GCond *cond)
{
struct lastfm_playlist *playlist;
GError *error = NULL;
@@ -175,7 +171,7 @@ lastfm_open_uri(const char *uri)
"username=", lastfm_config.user, "&"
"passwordmd5=", lastfm_config.md5, "&"
"debug=0&partner=", NULL);
- response = lastfm_get(p);
+ response = lastfm_get(p, mutex, cond);
g_free(p);
if (response == NULL)
return NULL;
@@ -207,7 +203,7 @@ lastfm_open_uri(const char *uri)
NULL);
g_free(escaped_uri);
- response = lastfm_get(p);
+ response = lastfm_get(p, mutex, cond);
g_free(response);
g_free(p);
@@ -229,7 +225,7 @@ lastfm_open_uri(const char *uri)
NULL);
g_free(session);
- playlist->is = input_stream_open(p, &error);
+ playlist->is = input_stream_open(p, mutex, cond, &error);
g_free(p);
if (playlist->is == NULL) {
@@ -243,26 +239,17 @@ lastfm_open_uri(const char *uri)
return NULL;
}
- while (!playlist->is->ready) {
- int ret = input_stream_buffer(playlist->is, &error);
- if (ret < 0) {
- input_stream_close(playlist->is);
- g_free(playlist);
- g_warning("%s", error->message);
- g_error_free(error);
- return NULL;
- }
+ g_mutex_lock(mutex);
- if (ret == 0)
- /* nothing was buffered - wait */
- g_usleep(10000);
- }
+ input_stream_wait_ready(playlist->is);
/* last.fm does not send a MIME type, we have to fake it here
:-( */
g_free(playlist->is->mime);
playlist->is->mime = g_strdup("application/xspf+xml");
+ g_mutex_unlock(mutex);
+
/* parse the XSPF playlist */
playlist->xspf = playlist_list_open_stream(playlist->is, NULL);
diff --git a/src/playlist/lastfm_playlist_plugin.h b/src/playlist/lastfm_playlist_plugin.h
index 363377c21..46a8b0caf 100644
--- a/src/playlist/lastfm_playlist_plugin.h
+++ b/src/playlist/lastfm_playlist_plugin.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/playlist/m3u_playlist_plugin.c b/src/playlist/m3u_playlist_plugin.c
index 221c27277..45b70d2b1 100644
--- a/src/playlist/m3u_playlist_plugin.c
+++ b/src/playlist/m3u_playlist_plugin.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/playlist/m3u_playlist_plugin.h b/src/playlist/m3u_playlist_plugin.h
index 98dcc4729..3890a5fc2 100644
--- a/src/playlist/m3u_playlist_plugin.h
+++ b/src/playlist/m3u_playlist_plugin.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/playlist/pls_playlist_plugin.c b/src/playlist/pls_playlist_plugin.c
index 2a36f12f5..c4e5492af 100644
--- a/src/playlist/pls_playlist_plugin.c
+++ b/src/playlist/pls_playlist_plugin.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -115,7 +115,8 @@ pls_open_stream(struct input_stream *is)
GString *kf_data = g_string_new("");
do {
- nbytes = input_stream_read(is, buffer, sizeof(buffer), &error);
+ nbytes = input_stream_lock_read(is, buffer, sizeof(buffer),
+ &error);
if (nbytes == 0) {
if (error != NULL) {
g_string_free(kf_data, TRUE);
diff --git a/src/playlist/pls_playlist_plugin.h b/src/playlist/pls_playlist_plugin.h
index c3bcf3f05..d03435f6d 100644
--- a/src/playlist/pls_playlist_plugin.h
+++ b/src/playlist/pls_playlist_plugin.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/playlist/rss_playlist_plugin.c b/src/playlist/rss_playlist_plugin.c
index b5787bb68..6740cba7e 100644
--- a/src/playlist/rss_playlist_plugin.c
+++ b/src/playlist/rss_playlist_plugin.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -231,7 +231,8 @@ rss_open_stream(struct input_stream *is)
&parser, rss_parser_destroy);
while (true) {
- nbytes = input_stream_read(is, buffer, sizeof(buffer), &error);
+ nbytes = input_stream_lock_read(is, buffer, sizeof(buffer),
+ &error);
if (nbytes == 0) {
if (error != NULL) {
g_markup_parse_context_free(context);
diff --git a/src/playlist/rss_playlist_plugin.h b/src/playlist/rss_playlist_plugin.h
index d8992f2e5..3b376de79 100644
--- a/src/playlist/rss_playlist_plugin.h
+++ b/src/playlist/rss_playlist_plugin.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/playlist/soundcloud_playlist_plugin.c b/src/playlist/soundcloud_playlist_plugin.c
new file mode 100644
index 000000000..7c79f880a
--- /dev/null
+++ b/src/playlist/soundcloud_playlist_plugin.c
@@ -0,0 +1,448 @@
+/*
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "playlist/soundcloud_playlist_plugin.h"
+#include "conf.h"
+#include "input_stream.h"
+#include "playlist_plugin.h"
+#include "song.h"
+#include "tag.h"
+
+#include <glib.h>
+#include <yajl/yajl_parse.h>
+
+#include <string.h>
+
+struct soundcloud_playlist {
+ struct playlist_provider base;
+
+ GSList *songs;
+};
+
+static struct {
+ char *apikey;
+} soundcloud_config;
+
+static bool
+soundcloud_init(const struct config_param *param)
+{
+ soundcloud_config.apikey =
+ config_dup_block_string(param, "apikey", NULL);
+ if (soundcloud_config.apikey == NULL) {
+ g_debug("disabling the soundcloud playlist plugin "
+ "because API key is not set");
+ return false;
+ }
+
+ return true;
+}
+
+static void
+soundcloud_finish(void)
+{
+ g_free(soundcloud_config.apikey);
+}
+
+/**
+ * Construct a full soundcloud resolver URL from the given fragment.
+ * @param uri uri of a soundcloud page (or just the path)
+ * @return Constructed URL. Must be freed with g_free.
+ */
+static char *
+soundcloud_resolve(const char* uri) {
+ char *u, *ru;
+
+ if (g_str_has_prefix(uri, "http://")) {
+ u = g_strdup(uri);
+ } else if (g_str_has_prefix(uri, "soundcloud.com")) {
+ u = g_strconcat("http://", uri, NULL);
+ } else {
+ /* assume it's just a path on soundcloud.com */
+ u = g_strconcat("http://soundcloud.com/", uri, NULL);
+ }
+
+ ru = g_strconcat("http://api.soundcloud.com/resolve.json?url=",
+ u, "&client_id=", soundcloud_config.apikey, NULL);
+ g_free(u);
+
+ return ru;
+}
+
+/* YAJL parser for track data from both /tracks/ and /playlists/ JSON */
+
+enum key {
+ Duration,
+ Title,
+ Stream_URL,
+ Other,
+};
+
+const char* key_str[] = {
+ "duration",
+ "title",
+ "stream_url",
+ NULL,
+};
+
+struct parse_data {
+ int key;
+ char* stream_url;
+ long duration;
+ char* title;
+ int got_url; /* nesting level of last stream_url */
+ GSList* songs;
+};
+
+static int handle_integer(void *ctx,
+ long
+#ifndef HAVE_YAJL1
+ long
+#endif
+ intval)
+{
+ struct parse_data *data = (struct parse_data *) ctx;
+
+ switch (data->key) {
+ case Duration:
+ data->duration = intval;
+ break;
+ default:
+ break;
+ }
+
+ return 1;
+}
+
+static int handle_string(void *ctx, const unsigned char* stringval,
+#ifdef HAVE_YAJL1
+ unsigned int
+#else
+ size_t
+#endif
+ stringlen)
+{
+ struct parse_data *data = (struct parse_data *) ctx;
+ const char *s = (const char *) stringval;
+
+ switch (data->key) {
+ case Title:
+ if (data->title != NULL)
+ g_free(data->title);
+ data->title = g_strndup(s, stringlen);
+ break;
+ case Stream_URL:
+ if (data->stream_url != NULL)
+ g_free(data->stream_url);
+ data->stream_url = g_strndup(s, stringlen);
+ data->got_url = 1;
+ break;
+ default:
+ break;
+ }
+
+ return 1;
+}
+
+static int handle_mapkey(void *ctx, const unsigned char* stringval,
+#ifdef HAVE_YAJL1
+ unsigned int
+#else
+ size_t
+#endif
+ stringlen)
+{
+ struct parse_data *data = (struct parse_data *) ctx;
+
+ int i;
+ data->key = Other;
+
+ for (i = 0; i < Other; ++i) {
+ if (strncmp((const char *)stringval, key_str[i], stringlen) == 0) {
+ data->key = i;
+ break;
+ }
+ }
+
+ return 1;
+}
+
+static int handle_start_map(void *ctx)
+{
+ struct parse_data *data = (struct parse_data *) ctx;
+
+ if (data->got_url > 0)
+ data->got_url++;
+
+ return 1;
+}
+
+static int handle_end_map(void *ctx)
+{
+ struct parse_data *data = (struct parse_data *) ctx;
+
+ if (data->got_url > 1) {
+ data->got_url--;
+ return 1;
+ }
+
+ if (data->got_url == 0)
+ return 1;
+
+ /* got_url == 1, track finished, make it into a song */
+ data->got_url = 0;
+
+ struct song *s;
+ struct tag *t;
+ char *u;
+
+ u = g_strconcat(data->stream_url, "?client_id=", soundcloud_config.apikey, NULL);
+ s = song_remote_new(u);
+ g_free(u);
+ t = tag_new();
+ t->time = data->duration / 1000;
+ if (data->title != NULL)
+ tag_add_item(t, TAG_NAME, data->title);
+ s->tag = t;
+
+ data->songs = g_slist_prepend(data->songs, s);
+
+ return 1;
+}
+
+static yajl_callbacks parse_callbacks = {
+ NULL,
+ NULL,
+ handle_integer,
+ NULL,
+ NULL,
+ handle_string,
+ handle_start_map,
+ handle_mapkey,
+ handle_end_map,
+ NULL,
+ NULL,
+};
+
+/**
+ * Read JSON data and parse it using the given YAJL parser.
+ * @param url URL of the JSON data.
+ * @param hand YAJL parser handle.
+ * @return -1 on error, 0 on success.
+ */
+static int
+soundcloud_parse_json(const char *url, yajl_handle hand, GMutex* mutex, GCond* cond)
+{
+ struct input_stream *input_stream;
+ GError *error = NULL;
+ char buffer[4096];
+ unsigned char *ubuffer = (unsigned char *)buffer;
+ size_t nbytes;
+
+ input_stream = input_stream_open(url, mutex, cond, &error);
+ if (input_stream == NULL) {
+ if (error != NULL) {
+ g_warning("%s", error->message);
+ g_error_free(error);
+ }
+ return -1;
+ }
+
+ g_mutex_lock(mutex);
+ input_stream_wait_ready(input_stream);
+
+ yajl_status stat;
+ int done = 0;
+
+ while (!done) {
+ nbytes = input_stream_read(input_stream, buffer, sizeof(buffer), &error);
+ if (nbytes == 0) {
+ if (error != NULL) {
+ g_warning("%s", error->message);
+ g_error_free(error);
+ }
+ if (input_stream_eof(input_stream)) {
+ done = true;
+ } else {
+ g_mutex_unlock(mutex);
+ input_stream_close(input_stream);
+ return -1;
+ }
+ }
+
+ if (done) {
+#ifdef HAVE_YAJL1
+ stat = yajl_parse_complete(hand);
+#else
+ stat = yajl_complete_parse(hand);
+#endif
+ } else
+ stat = yajl_parse(hand, ubuffer, nbytes);
+
+ if (stat != yajl_status_ok
+#ifdef HAVE_YAJL1
+ && stat != yajl_status_insufficient_data
+#endif
+ )
+ {
+ unsigned char *str = yajl_get_error(hand, 1, ubuffer, nbytes);
+ g_warning("%s", str);
+ yajl_free_error(hand, str);
+ break;
+ }
+ }
+
+ g_mutex_unlock(mutex);
+ input_stream_close(input_stream);
+
+ return 0;
+}
+
+/**
+ * Parse a soundcloud:// URL and create a playlist.
+ * @param uri A soundcloud URL. Accepted forms:
+ * soundcloud://track/<track-id>
+ * soundcloud://playlist/<playlist-id>
+ * soundcloud://url/<url or path of soundcloud page>
+ */
+
+static struct playlist_provider *
+soundcloud_open_uri(const char *uri, GMutex *mutex, GCond *cond)
+{
+ struct soundcloud_playlist *playlist = NULL;
+
+ char *s, *p;
+ char *scheme, *arg, *rest;
+ s = g_strdup(uri);
+ scheme = s;
+ for (p = s; *p; p++) {
+ if (*p == ':' && *(p+1) == '/' && *(p+2) == '/') {
+ *p = 0;
+ p += 3;
+ break;
+ }
+ }
+ arg = p;
+ for (; *p; p++) {
+ if (*p == '/') {
+ *p = 0;
+ p++;
+ break;
+ }
+ }
+ rest = p;
+
+ if (strcmp(scheme, "soundcloud") != 0) {
+ g_warning("incompatible scheme for soundcloud plugin: %s", scheme);
+ g_free(s);
+ return NULL;
+ }
+
+ char *u = NULL;
+ if (strcmp(arg, "track") == 0) {
+ u = g_strconcat("http://api.soundcloud.com/tracks/",
+ rest, ".json?client_id=", soundcloud_config.apikey, NULL);
+ } else if (strcmp(arg, "playlist") == 0) {
+ u = g_strconcat("http://api.soundcloud.com/playlists/",
+ rest, ".json?client_id=", soundcloud_config.apikey, NULL);
+ } else if (strcmp(arg, "url") == 0) {
+ /* Translate to soundcloud resolver call. libcurl will automatically
+ follow the redirect to the right resource. */
+ u = soundcloud_resolve(rest);
+ }
+ g_free(s);
+
+ if (u == NULL) {
+ g_warning("unknown soundcloud URI");
+ return NULL;
+ }
+
+ yajl_handle hand;
+ struct parse_data data;
+
+ data.got_url = 0;
+ data.songs = NULL;
+ data.title = NULL;
+ data.stream_url = NULL;
+#ifdef HAVE_YAJL1
+ hand = yajl_alloc(&parse_callbacks, NULL, NULL, (void *) &data);
+#else
+ hand = yajl_alloc(&parse_callbacks, NULL, (void *) &data);
+#endif
+
+ int ret = soundcloud_parse_json(u, hand, mutex, cond);
+
+ g_free(u);
+ yajl_free(hand);
+ if (data.title != NULL)
+ g_free(data.title);
+ if (data.stream_url != NULL)
+ g_free(data.stream_url);
+
+ if (ret == -1)
+ return NULL;
+
+ playlist = g_new(struct soundcloud_playlist, 1);
+ playlist_provider_init(&playlist->base, &soundcloud_playlist_plugin);
+ playlist->songs = g_slist_reverse(data.songs);
+
+ return &playlist->base;
+}
+
+static void
+soundcloud_close(struct playlist_provider *_playlist)
+{
+ struct soundcloud_playlist *playlist = (struct soundcloud_playlist *)_playlist;
+
+ g_free(playlist);
+}
+
+
+static struct song *
+soundcloud_read(struct playlist_provider *_playlist)
+{
+ struct soundcloud_playlist *playlist = (struct soundcloud_playlist *)_playlist;
+
+ if (playlist->songs == NULL)
+ return NULL;
+
+ struct song* s;
+ s = (struct song *)playlist->songs->data;
+ playlist->songs = g_slist_remove(playlist->songs, s);
+ return s;
+}
+
+static const char *const soundcloud_schemes[] = {
+ "soundcloud",
+ NULL
+};
+
+const struct playlist_plugin soundcloud_playlist_plugin = {
+ .name = "soundcloud",
+
+ .init = soundcloud_init,
+ .finish = soundcloud_finish,
+ .open_uri = soundcloud_open_uri,
+ .close = soundcloud_close,
+ .read = soundcloud_read,
+
+ .schemes = soundcloud_schemes,
+};
+
+
diff --git a/src/playlist/soundcloud_playlist_plugin.h b/src/playlist/soundcloud_playlist_plugin.h
new file mode 100644
index 000000000..e09e2dd46
--- /dev/null
+++ b/src/playlist/soundcloud_playlist_plugin.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_PLAYLIST_SOUNDCLOUD_PLAYLIST_PLUGIN_H
+#define MPD_PLAYLIST_SOUNDCLOUD_PLAYLIST_PLUGIN_H
+
+extern const struct playlist_plugin soundcloud_playlist_plugin;
+
+#endif
diff --git a/src/playlist/xspf_playlist_plugin.c b/src/playlist/xspf_playlist_plugin.c
index 50f6bd1e7..17d9040e2 100644
--- a/src/playlist/xspf_playlist_plugin.c
+++ b/src/playlist/xspf_playlist_plugin.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -253,7 +253,8 @@ xspf_open_stream(struct input_stream *is)
&parser, xspf_parser_destroy);
while (true) {
- nbytes = input_stream_read(is, buffer, sizeof(buffer), &error);
+ nbytes = input_stream_lock_read(is, buffer, sizeof(buffer),
+ &error);
if (nbytes == 0) {
if (error != NULL) {
g_markup_parse_context_free(context);
diff --git a/src/playlist/xspf_playlist_plugin.h b/src/playlist/xspf_playlist_plugin.h
index ea832207d..4636d7e83 100644
--- a/src/playlist/xspf_playlist_plugin.h
+++ b/src/playlist/xspf_playlist_plugin.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/playlist_any.c b/src/playlist_any.c
index 39e21b178..450ca5932 100644
--- a/src/playlist_any.c
+++ b/src/playlist_any.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -27,18 +27,20 @@
#include <assert.h>
static struct playlist_provider *
-playlist_open_remote(const char *uri, struct input_stream **is_r)
+playlist_open_remote(const char *uri, GMutex *mutex, GCond *cond,
+ struct input_stream **is_r)
{
assert(uri_has_scheme(uri));
- struct playlist_provider *playlist = playlist_list_open_uri(uri);
+ struct playlist_provider *playlist =
+ playlist_list_open_uri(uri, mutex, cond);
if (playlist != NULL) {
*is_r = NULL;
return playlist;
}
GError *error = NULL;
- struct input_stream *is = input_stream_open(uri, &error);
+ struct input_stream *is = input_stream_open(uri, mutex, cond, &error);
if (is == NULL) {
if (error != NULL) {
g_warning("Failed to open %s: %s",
@@ -60,9 +62,10 @@ playlist_open_remote(const char *uri, struct input_stream **is_r)
}
struct playlist_provider *
-playlist_open_any(const char *uri, struct input_stream **is_r)
+playlist_open_any(const char *uri, GMutex *mutex, GCond *cond,
+ struct input_stream **is_r)
{
return uri_has_scheme(uri)
- ? playlist_open_remote(uri, is_r)
- : playlist_mapper_open(uri, is_r);
+ ? playlist_open_remote(uri, mutex, cond, is_r)
+ : playlist_mapper_open(uri, mutex, cond, is_r);
}
diff --git a/src/playlist_any.h b/src/playlist_any.h
index 6fed97d15..310913de9 100644
--- a/src/playlist_any.h
+++ b/src/playlist_any.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -20,7 +20,7 @@
#ifndef MPD_PLAYLIST_ANY_H
#define MPD_PLAYLIST_ANY_H
-#include <stdbool.h>
+#include <glib.h>
struct playlist_provider;
struct input_stream;
@@ -35,6 +35,7 @@ struct input_stream;
* freed
*/
struct playlist_provider *
-playlist_open_any(const char *uri, struct input_stream **is_r);
+playlist_open_any(const char *uri, GMutex *mutex, GCond *cond,
+ struct input_stream **is_r);
#endif
diff --git a/src/playlist_control.c b/src/playlist_control.c
index 76066d274..0dea7676a 100644
--- a/src/playlist_control.c
+++ b/src/playlist_control.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -32,7 +32,8 @@
#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "playlist"
-void playlist_stop(struct playlist *playlist)
+void
+playlist_stop(struct playlist *playlist, struct player_control *pc)
{
if (!playlist->playing)
return;
@@ -40,7 +41,7 @@ void playlist_stop(struct playlist *playlist)
assert(playlist->current >= 0);
g_debug("stop");
- pc_stop();
+ pc_stop(pc);
playlist->queued = -1;
playlist->playing = false;
@@ -62,11 +63,13 @@ void playlist_stop(struct playlist *playlist)
}
}
-enum playlist_result playlist_play(struct playlist *playlist, int song)
+enum playlist_result
+playlist_play(struct playlist *playlist, struct player_control *pc,
+ int song)
{
unsigned i = song;
- pc_clear_error();
+ pc_clear_error(pc);
if (song == -1) {
/* play any song ("current" song, or the first song */
@@ -77,7 +80,7 @@ enum playlist_result playlist_play(struct playlist *playlist, int song)
if (playlist->playing) {
/* already playing: unpause playback, just in
case it was paused, and return */
- pc_set_pause(false);
+ pc_set_pause(pc, false);
return PLAYLIST_RESULT_SUCCESS;
}
@@ -109,28 +112,29 @@ enum playlist_result playlist_play(struct playlist *playlist, int song)
playlist->stop_on_error = false;
playlist->error_count = 0;
- playlist_play_order(playlist, i);
+ playlist_play_order(playlist, pc, i);
return PLAYLIST_RESULT_SUCCESS;
}
enum playlist_result
-playlist_play_id(struct playlist *playlist, int id)
+playlist_play_id(struct playlist *playlist, struct player_control *pc,
+ int id)
{
int song;
if (id == -1) {
- return playlist_play(playlist, id);
+ return playlist_play(playlist, pc, id);
}
song = queue_id_to_position(&playlist->queue, id);
if (song < 0)
return PLAYLIST_RESULT_NO_SUCH_SONG;
- return playlist_play(playlist, song);
+ return playlist_play(playlist, pc, song);
}
void
-playlist_next(struct playlist *playlist)
+playlist_next(struct playlist *playlist, struct player_control *pc)
{
int next_order;
int current;
@@ -149,7 +153,7 @@ playlist_next(struct playlist *playlist)
next_order = queue_next_order(&playlist->queue, playlist->current);
if (next_order < 0) {
/* no song after this one: stop playback */
- playlist_stop(playlist);
+ playlist_stop(playlist, pc);
/* reset "current song" */
playlist->current = -1;
@@ -170,15 +174,18 @@ playlist_next(struct playlist *playlist)
discard them anyway */
}
- playlist_play_order(playlist, next_order);
+ playlist_play_order(playlist, pc, next_order);
}
/* Consume mode removes each played songs. */
if(playlist->queue.consume)
- playlist_delete(playlist, queue_order_to_position(&playlist->queue, current));
+ playlist_delete(playlist, pc,
+ queue_order_to_position(&playlist->queue,
+ current));
}
-void playlist_previous(struct playlist *playlist)
+void
+playlist_previous(struct playlist *playlist, struct player_control *pc)
{
if (!playlist->playing)
return;
@@ -187,21 +194,22 @@ void playlist_previous(struct playlist *playlist)
if (playlist->current > 0) {
/* play the preceding song */
- playlist_play_order(playlist,
+ playlist_play_order(playlist, pc,
playlist->current - 1);
} else if (playlist->queue.repeat) {
/* play the last song in "repeat" mode */
- playlist_play_order(playlist,
+ playlist_play_order(playlist, pc,
queue_length(&playlist->queue) - 1);
} else {
/* re-start playing the current song if it's
the first one */
- playlist_play_order(playlist, playlist->current);
+ playlist_play_order(playlist, pc, playlist->current);
}
}
enum playlist_result
-playlist_seek_song(struct playlist *playlist, unsigned song, float seek_time)
+playlist_seek_song(struct playlist *playlist, struct player_control *pc,
+ unsigned song, float seek_time)
{
const struct song *queued;
unsigned i;
@@ -217,7 +225,7 @@ playlist_seek_song(struct playlist *playlist, unsigned song, float seek_time)
else
i = song;
- pc_clear_error();
+ pc_clear_error(pc);
playlist->stop_on_error = true;
playlist->error_count = 0;
@@ -231,25 +239,50 @@ playlist_seek_song(struct playlist *playlist, unsigned song, float seek_time)
queued = NULL;
}
- success = pc_seek(queue_get_order(&playlist->queue, i), seek_time);
+ success = pc_seek(pc, queue_get_order(&playlist->queue, i), seek_time);
if (!success) {
- playlist_update_queued_song(playlist, queued);
+ playlist_update_queued_song(playlist, pc, queued);
return PLAYLIST_RESULT_NOT_PLAYING;
}
playlist->queued = -1;
- playlist_update_queued_song(playlist, NULL);
+ playlist_update_queued_song(playlist, pc, NULL);
return PLAYLIST_RESULT_SUCCESS;
}
enum playlist_result
-playlist_seek_song_id(struct playlist *playlist, unsigned id, float seek_time)
+playlist_seek_song_id(struct playlist *playlist, struct player_control *pc,
+ unsigned id, float seek_time)
{
int song = queue_id_to_position(&playlist->queue, id);
if (song < 0)
return PLAYLIST_RESULT_NO_SUCH_SONG;
- return playlist_seek_song(playlist, song, seek_time);
+ return playlist_seek_song(playlist, pc, song, seek_time);
+}
+
+enum playlist_result
+playlist_seek_current(struct playlist *playlist, struct player_control *pc,
+ float seek_time, bool relative)
+{
+ if (!playlist->playing)
+ return PLAYLIST_RESULT_NOT_PLAYING;
+
+ if (relative) {
+ struct player_status status;
+ pc_get_status(pc, &status);
+
+ if (status.state != PLAYER_STATE_PLAY &&
+ status.state != PLAYER_STATE_PAUSE)
+ return PLAYLIST_RESULT_NOT_PLAYING;
+
+ seek_time += (int)status.elapsed_time;
+ }
+
+ if (seek_time < 0)
+ seek_time = 0;
+
+ return playlist_seek_song(playlist, pc, playlist->current, seek_time);
}
diff --git a/src/playlist_database.c b/src/playlist_database.c
index 0a8a6f139..6b9d87155 100644
--- a/src/playlist_database.c
+++ b/src/playlist_database.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -21,6 +21,7 @@
#include "playlist_database.h"
#include "playlist_vector.h"
#include "text_file.h"
+#include "string_util.h"
#include <string.h>
#include <stdlib.h>
@@ -32,10 +33,10 @@ playlist_database_quark(void)
}
void
-playlist_vector_save(FILE *fp, const struct playlist_vector *pv)
+playlist_vector_save(FILE *fp, const struct list_head *pv)
{
- for (const struct playlist_metadata *pm = pv->head;
- pm != NULL; pm = pm->next)
+ struct playlist_metadata *pm;
+ playlist_vector_for_each(pm, pv)
fprintf(fp, PLAYLIST_META_BEGIN "%s\n"
"mtime: %li\n"
"playlist_end\n",
@@ -43,7 +44,7 @@ playlist_vector_save(FILE *fp, const struct playlist_vector *pv)
}
bool
-playlist_metadata_load(FILE *fp, struct playlist_vector *pv, const char *name,
+playlist_metadata_load(FILE *fp, struct list_head *pv, const char *name,
GString *buffer, GError **error_r)
{
struct playlist_metadata pm = {
@@ -62,7 +63,7 @@ playlist_metadata_load(FILE *fp, struct playlist_vector *pv, const char *name,
}
*colon++ = 0;
- value = g_strchug(colon);
+ value = strchug_fast_c(colon);
if (strcmp(line, "mtime") == 0)
pm.mtime = strtol(value, NULL, 10);
diff --git a/src/playlist_database.h b/src/playlist_database.h
index 7e114abdd..3238fa06b 100644
--- a/src/playlist_database.h
+++ b/src/playlist_database.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -28,13 +28,13 @@
#define PLAYLIST_META_BEGIN "playlist_begin: "
-struct playlist_vector;
+struct list_head;
void
-playlist_vector_save(FILE *fp, const struct playlist_vector *pv);
+playlist_vector_save(FILE *fp, const struct list_head *pv);
bool
-playlist_metadata_load(FILE *fp, struct playlist_vector *pv, const char *name,
+playlist_metadata_load(FILE *fp, struct list_head *pv, const char *name,
GString *buffer, GError **error_r);
#endif
diff --git a/src/playlist_edit.c b/src/playlist_edit.c
index 3bcb2ce14..7adbccd7c 100644
--- a/src/playlist_edit.c
+++ b/src/playlist_edit.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -31,9 +31,6 @@
#include "song.h"
#include "idle.h"
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <unistd.h>
#include <stdlib.h>
static void playlist_increment_version(struct playlist *playlist)
@@ -43,16 +40,17 @@ static void playlist_increment_version(struct playlist *playlist)
idle_add(IDLE_PLAYLIST);
}
-void playlist_clear(struct playlist *playlist)
+void
+playlist_clear(struct playlist *playlist, struct player_control *pc)
{
- playlist_stop(playlist);
+ playlist_stop(playlist, pc);
/* make sure there are no references to allocated songs
anymore */
for (unsigned i = 0; i < queue_length(&playlist->queue); i++) {
const struct song *song = queue_get(&playlist->queue, i);
if (!song_in_database(song))
- pc_song_deleted(song);
+ pc_song_deleted(pc, song);
}
queue_clear(&playlist->queue);
@@ -62,37 +60,19 @@ void playlist_clear(struct playlist *playlist)
playlist_increment_version(playlist);
}
-#ifndef WIN32
enum playlist_result
-playlist_append_file(struct playlist *playlist, const char *path, int uid,
- unsigned *added_id)
+playlist_append_file(struct playlist *playlist, struct player_control *pc,
+ const char *path_fs, unsigned *added_id)
{
- int ret;
- struct stat st;
- struct song *song;
-
- if (uid <= 0)
- /* unauthenticated client */
- return PLAYLIST_RESULT_DENIED;
-
- ret = stat(path, &st);
- if (ret < 0)
- return PLAYLIST_RESULT_ERRNO;
-
- if (st.st_uid != (uid_t)uid && (st.st_mode & 0444) != 0444)
- /* client is not owner */
- return PLAYLIST_RESULT_DENIED;
-
- song = song_file_load(path, NULL);
+ struct song *song = song_file_load(path_fs, NULL);
if (song == NULL)
return PLAYLIST_RESULT_NO_SUCH_SONG;
- return playlist_append_song(playlist, song, added_id);
+ return playlist_append_song(playlist, pc, song, added_id);
}
-#endif
enum playlist_result
-playlist_append_song(struct playlist *playlist,
+playlist_append_song(struct playlist *playlist, struct player_control *pc,
struct song *song, unsigned *added_id)
{
const struct song *queued;
@@ -121,7 +101,7 @@ playlist_append_song(struct playlist *playlist,
playlist_increment_version(playlist);
- playlist_update_queued_song(playlist, queued);
+ playlist_update_queued_song(playlist, pc, queued);
if (added_id)
*added_id = id;
@@ -145,8 +125,8 @@ song_by_uri(const char *uri)
}
enum playlist_result
-playlist_append_uri(struct playlist *playlist, const char *uri,
- unsigned *added_id)
+playlist_append_uri(struct playlist *playlist, struct player_control *pc,
+ const char *uri, unsigned *added_id)
{
struct song *song;
@@ -156,11 +136,12 @@ playlist_append_uri(struct playlist *playlist, const char *uri,
if (song == NULL)
return PLAYLIST_RESULT_NO_SUCH_SONG;
- return playlist_append_song(playlist, song, added_id);
+ return playlist_append_song(playlist, pc, song, added_id);
}
enum playlist_result
-playlist_swap_songs(struct playlist *playlist, unsigned song1, unsigned song2)
+playlist_swap_songs(struct playlist *playlist, struct player_control *pc,
+ unsigned song1, unsigned song2)
{
const struct song *queued;
@@ -192,13 +173,14 @@ playlist_swap_songs(struct playlist *playlist, unsigned song1, unsigned song2)
playlist_increment_version(playlist);
- playlist_update_queued_song(playlist, queued);
+ playlist_update_queued_song(playlist, pc, queued);
return PLAYLIST_RESULT_SUCCESS;
}
enum playlist_result
-playlist_swap_songs_id(struct playlist *playlist, unsigned id1, unsigned id2)
+playlist_swap_songs_id(struct playlist *playlist, struct player_control *pc,
+ unsigned id1, unsigned id2)
{
int song1 = queue_id_to_position(&playlist->queue, id1);
int song2 = queue_id_to_position(&playlist->queue, id2);
@@ -206,12 +188,67 @@ playlist_swap_songs_id(struct playlist *playlist, unsigned id1, unsigned id2)
if (song1 < 0 || song2 < 0)
return PLAYLIST_RESULT_NO_SUCH_SONG;
- return playlist_swap_songs(playlist, song1, song2);
+ return playlist_swap_songs(playlist, pc, song1, song2);
+}
+
+enum playlist_result
+playlist_set_priority(struct playlist *playlist, struct player_control *pc,
+ unsigned start, unsigned end,
+ uint8_t priority)
+{
+ if (start >= queue_length(&playlist->queue))
+ return PLAYLIST_RESULT_BAD_RANGE;
+
+ if (end > queue_length(&playlist->queue))
+ end = queue_length(&playlist->queue);
+
+ if (start >= end)
+ return PLAYLIST_RESULT_SUCCESS;
+
+ /* remember "current" and "queued" */
+
+ int current_position = playlist->current >= 0
+ ? (int)queue_order_to_position(&playlist->queue,
+ playlist->current)
+ : -1;
+
+ const struct song *queued = playlist_get_queued_song(playlist);
+
+ /* apply the priority changes */
+
+ queue_set_priority_range(&playlist->queue, start, end, priority,
+ playlist->current);
+
+ playlist_increment_version(playlist);
+
+ /* restore "current" and choose a new "queued" */
+
+ if (current_position >= 0)
+ playlist->current = queue_position_to_order(&playlist->queue,
+ current_position);
+
+ playlist_update_queued_song(playlist, pc, queued);
+
+ return PLAYLIST_RESULT_SUCCESS;
+}
+
+enum playlist_result
+playlist_set_priority_id(struct playlist *playlist, struct player_control *pc,
+ unsigned song_id, uint8_t priority)
+{
+ int song_position = queue_id_to_position(&playlist->queue, song_id);
+ if (song_position < 0)
+ return PLAYLIST_RESULT_NO_SUCH_SONG;
+
+ return playlist_set_priority(playlist, pc,
+ song_position, song_position + 1,
+ priority);
+
}
static void
-playlist_delete_internal(struct playlist *playlist, unsigned song,
- const struct song **queued_p)
+playlist_delete_internal(struct playlist *playlist, struct player_control *pc,
+ unsigned song, const struct song **queued_p)
{
unsigned songOrder;
@@ -220,11 +257,11 @@ playlist_delete_internal(struct playlist *playlist, unsigned song,
songOrder = queue_position_to_order(&playlist->queue, song);
if (playlist->playing && playlist->current == (int)songOrder) {
- bool paused = pc_get_state() == PLAYER_STATE_PAUSE;
+ bool paused = pc_get_state(pc) == PLAYER_STATE_PAUSE;
/* the current song is going to be deleted: stop the player */
- pc_stop();
+ pc_stop(pc);
playlist->playing = false;
/* see which song is going to be played instead */
@@ -236,11 +273,11 @@ playlist_delete_internal(struct playlist *playlist, unsigned song,
if (playlist->current >= 0 && !paused)
/* play the song after the deleted one */
- playlist_play_order(playlist, playlist->current);
+ playlist_play_order(playlist, pc, playlist->current);
else
/* no songs left to play, stop playback
completely */
- playlist_stop(playlist);
+ playlist_stop(playlist, pc);
*queued_p = NULL;
} else if (playlist->current == (int)songOrder)
@@ -251,7 +288,7 @@ playlist_delete_internal(struct playlist *playlist, unsigned song,
/* now do it: remove the song */
if (!song_in_database(queue_get(&playlist->queue, song)))
- pc_song_deleted(queue_get(&playlist->queue, song));
+ pc_song_deleted(pc, queue_get(&playlist->queue, song));
queue_delete(&playlist->queue, song);
@@ -263,7 +300,8 @@ playlist_delete_internal(struct playlist *playlist, unsigned song,
}
enum playlist_result
-playlist_delete(struct playlist *playlist, unsigned song)
+playlist_delete(struct playlist *playlist, struct player_control *pc,
+ unsigned song)
{
const struct song *queued;
@@ -272,16 +310,17 @@ playlist_delete(struct playlist *playlist, unsigned song)
queued = playlist_get_queued_song(playlist);
- playlist_delete_internal(playlist, song, &queued);
+ playlist_delete_internal(playlist, pc, song, &queued);
playlist_increment_version(playlist);
- playlist_update_queued_song(playlist, queued);
+ playlist_update_queued_song(playlist, pc, queued);
return PLAYLIST_RESULT_SUCCESS;
}
enum playlist_result
-playlist_delete_range(struct playlist *playlist, unsigned start, unsigned end)
+playlist_delete_range(struct playlist *playlist, struct player_control *pc,
+ unsigned start, unsigned end)
{
const struct song *queued;
@@ -297,37 +336,39 @@ playlist_delete_range(struct playlist *playlist, unsigned start, unsigned end)
queued = playlist_get_queued_song(playlist);
do {
- playlist_delete_internal(playlist, --end, &queued);
+ playlist_delete_internal(playlist, pc, --end, &queued);
} while (end != start);
playlist_increment_version(playlist);
- playlist_update_queued_song(playlist, queued);
+ playlist_update_queued_song(playlist, pc, queued);
return PLAYLIST_RESULT_SUCCESS;
}
enum playlist_result
-playlist_delete_id(struct playlist *playlist, unsigned id)
+playlist_delete_id(struct playlist *playlist, struct player_control *pc,
+ unsigned id)
{
int song = queue_id_to_position(&playlist->queue, id);
if (song < 0)
return PLAYLIST_RESULT_NO_SUCH_SONG;
- return playlist_delete(playlist, song);
+ return playlist_delete(playlist, pc, song);
}
void
-playlist_delete_song(struct playlist *playlist, const struct song *song)
+playlist_delete_song(struct playlist *playlist, struct player_control *pc,
+ const struct song *song)
{
for (int i = queue_length(&playlist->queue) - 1; i >= 0; --i)
if (song == queue_get(&playlist->queue, i))
- playlist_delete(playlist, i);
+ playlist_delete(playlist, pc, i);
- pc_song_deleted(song);
+ pc_song_deleted(pc, song);
}
enum playlist_result
-playlist_move_range(struct playlist *playlist,
+playlist_move_range(struct playlist *playlist, struct player_control *pc,
unsigned start, unsigned end, int to)
{
const struct song *queued;
@@ -382,23 +423,25 @@ playlist_move_range(struct playlist *playlist,
playlist_increment_version(playlist);
- playlist_update_queued_song(playlist, queued);
+ playlist_update_queued_song(playlist, pc, queued);
return PLAYLIST_RESULT_SUCCESS;
}
enum playlist_result
-playlist_move_id(struct playlist *playlist, unsigned id1, int to)
+playlist_move_id(struct playlist *playlist, struct player_control *pc,
+ unsigned id1, int to)
{
int song = queue_id_to_position(&playlist->queue, id1);
if (song < 0)
return PLAYLIST_RESULT_NO_SUCH_SONG;
- return playlist_move_range(playlist, song, song+1, to);
+ return playlist_move_range(playlist, pc, song, song+1, to);
}
void
-playlist_shuffle(struct playlist *playlist, unsigned start, unsigned end)
+playlist_shuffle(struct playlist *playlist, struct player_control *pc,
+ unsigned start, unsigned end)
{
const struct song *queued;
@@ -440,5 +483,5 @@ playlist_shuffle(struct playlist *playlist, unsigned start, unsigned end)
playlist_increment_version(playlist);
- playlist_update_queued_song(playlist, queued);
+ playlist_update_queued_song(playlist, pc, queued);
}
diff --git a/src/playlist_error.h b/src/playlist_error.h
new file mode 100644
index 000000000..ad9c62cf1
--- /dev/null
+++ b/src/playlist_error.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_PLAYLIST_ERROR_H
+#define MPD_PLAYLIST_ERROR_H
+
+#include <glib.h>
+
+enum playlist_result {
+ PLAYLIST_RESULT_SUCCESS,
+ PLAYLIST_RESULT_ERRNO,
+ PLAYLIST_RESULT_DENIED,
+ PLAYLIST_RESULT_NO_SUCH_SONG,
+ PLAYLIST_RESULT_NO_SUCH_LIST,
+ PLAYLIST_RESULT_LIST_EXISTS,
+ PLAYLIST_RESULT_BAD_NAME,
+ PLAYLIST_RESULT_BAD_RANGE,
+ PLAYLIST_RESULT_NOT_PLAYING,
+ PLAYLIST_RESULT_TOO_LARGE,
+ PLAYLIST_RESULT_DISABLED,
+};
+
+/**
+ * Quark for GError.domain; the code is an enum #playlist_result.
+ */
+G_GNUC_CONST
+static inline GQuark
+playlist_quark(void)
+{
+ return g_quark_from_static_string("playlist");
+}
+
+#endif
diff --git a/src/playlist_global.c b/src/playlist_global.c
index 2833b62ed..650b88bb8 100644
--- a/src/playlist_global.c
+++ b/src/playlist_global.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -26,6 +26,7 @@
#include "playlist.h"
#include "playlist_state.h"
#include "event_pipe.h"
+#include "main.h"
struct playlist g_playlist;
@@ -38,7 +39,7 @@ playlist_tag_event(void)
static void
playlist_event(void)
{
- playlist_sync(&g_playlist);
+ playlist_sync(&g_playlist, global_player_control);
}
void
diff --git a/src/playlist_internal.h b/src/playlist_internal.h
index 9d205188f..81b175176 100644
--- a/src/playlist_internal.h
+++ b/src/playlist_internal.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -27,6 +27,8 @@
#include "playlist.h"
+struct player_control;
+
/**
* Returns the song object which is currently queued. Returns none if
* there is none (yet?) or if MPD isn't playing.
@@ -44,9 +46,11 @@ playlist_get_queued_song(struct playlist *playlist);
*/
void
playlist_update_queued_song(struct playlist *playlist,
+ struct player_control *pc,
const struct song *prev);
void
-playlist_play_order(struct playlist *playlist, int orderNum);
+playlist_play_order(struct playlist *playlist, struct player_control *pc,
+ int orderNum);
#endif
diff --git a/src/playlist_list.c b/src/playlist_list.c
index 019654bfc..64f03d46d 100644
--- a/src/playlist_list.c
+++ b/src/playlist_list.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -24,14 +24,16 @@
#include "playlist/m3u_playlist_plugin.h"
#include "playlist/xspf_playlist_plugin.h"
#include "playlist/lastfm_playlist_plugin.h"
+#include "playlist/despotify_playlist_plugin.h"
+#include "playlist/soundcloud_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 "playlist/embcue_playlist_plugin.h"
#include "input_stream.h"
#include "uri.h"
-#include "utils.h"
+#include "string_util.h"
#include "conf.h"
#include "glib_compat.h"
#include "mpd_error.h"
@@ -47,15 +49,17 @@ static const struct playlist_plugin *const playlist_plugins[] = {
&pls_playlist_plugin,
&asx_playlist_plugin,
&rss_playlist_plugin,
+#ifdef ENABLE_DESPOTIFY
+ &despotify_playlist_plugin,
+#endif
#ifdef ENABLE_LASTFM
&lastfm_playlist_plugin,
#endif
-#ifdef HAVE_CUE
- &cue_playlist_plugin,
-#endif
-#ifdef HAVE_FLAC
- &flac_playlist_plugin,
+#ifdef ENABLE_SOUNDCLOUD
+ &soundcloud_playlist_plugin,
#endif
+ &cue_playlist_plugin,
+ &embcue_playlist_plugin,
NULL
};
@@ -115,7 +119,8 @@ playlist_list_global_finish(void)
}
static struct playlist_provider *
-playlist_list_open_uri_scheme(const char *uri, bool *tried)
+playlist_list_open_uri_scheme(const char *uri, GMutex *mutex, GCond *cond,
+ bool *tried)
{
char *scheme;
struct playlist_provider *playlist = NULL;
@@ -134,7 +139,8 @@ playlist_list_open_uri_scheme(const char *uri, bool *tried)
if (playlist_plugins_enabled[i] && plugin->open_uri != NULL &&
plugin->schemes != NULL &&
string_array_contains(plugin->schemes, scheme)) {
- playlist = playlist_plugin_open_uri(plugin, uri);
+ playlist = playlist_plugin_open_uri(plugin, uri,
+ mutex, cond);
if (playlist != NULL)
break;
@@ -147,7 +153,8 @@ playlist_list_open_uri_scheme(const char *uri, bool *tried)
}
static struct playlist_provider *
-playlist_list_open_uri_suffix(const char *uri, const bool *tried)
+playlist_list_open_uri_suffix(const char *uri, GMutex *mutex, GCond *cond,
+ const bool *tried)
{
const char *suffix;
struct playlist_provider *playlist = NULL;
@@ -164,7 +171,8 @@ playlist_list_open_uri_suffix(const char *uri, const bool *tried)
if (playlist_plugins_enabled[i] && !tried[i] &&
plugin->open_uri != NULL && plugin->suffixes != NULL &&
string_array_contains(plugin->suffixes, suffix)) {
- playlist = playlist_plugin_open_uri(plugin, uri);
+ playlist = playlist_plugin_open_uri(plugin, uri,
+ mutex, cond);
if (playlist != NULL)
break;
}
@@ -174,7 +182,7 @@ playlist_list_open_uri_suffix(const char *uri, const bool *tried)
}
struct playlist_provider *
-playlist_list_open_uri(const char *uri)
+playlist_list_open_uri(const char *uri, GMutex *mutex, GCond *cond)
{
struct playlist_provider *playlist;
/** this array tracks which plugins have already been tried by
@@ -185,9 +193,10 @@ playlist_list_open_uri(const char *uri)
memset(tried, false, sizeof(tried));
- playlist = playlist_list_open_uri_scheme(uri, tried);
+ playlist = playlist_list_open_uri_scheme(uri, mutex, cond, tried);
if (playlist == NULL)
- playlist = playlist_list_open_uri_suffix(uri, tried);
+ playlist = playlist_list_open_uri_suffix(uri, mutex, cond,
+ tried);
return playlist;
}
@@ -274,16 +283,7 @@ playlist_list_open_stream(struct input_stream *is, const char *uri)
const char *suffix;
struct playlist_provider *playlist;
- GError *error = NULL;
- while (!is->ready) {
- int ret = input_stream_buffer(is, &error);
- if (ret < 0) {
- input_stream_close(is);
- g_warning("%s", error->message);
- g_error_free(error);
- return NULL;
- }
- }
+ input_stream_lock_wait_ready(is);
if (is->mime != NULL) {
playlist = playlist_list_open_stream_mime(is);
@@ -318,7 +318,8 @@ playlist_suffix_supported(const char *suffix)
}
struct playlist_provider *
-playlist_list_open_path(const char *path_fs, struct input_stream **is_r)
+playlist_list_open_path(const char *path_fs, GMutex *mutex, GCond *cond,
+ struct input_stream **is_r)
{
GError *error = NULL;
const char *suffix;
@@ -331,7 +332,7 @@ playlist_list_open_path(const char *path_fs, struct input_stream **is_r)
if (suffix == NULL || !playlist_suffix_supported(suffix))
return NULL;
- is = input_stream_open(path_fs, &error);
+ is = input_stream_open(path_fs, mutex, cond, &error);
if (is == NULL) {
if (error != NULL) {
g_warning("%s", error->message);
@@ -341,15 +342,7 @@ playlist_list_open_path(const char *path_fs, struct input_stream **is_r)
return NULL;
}
- while (!is->ready) {
- int ret = input_stream_buffer(is, &error);
- if (ret < 0) {
- input_stream_close(is);
- g_warning("%s", error->message);
- g_error_free(error);
- return NULL;
- }
- }
+ input_stream_lock_wait_ready(is);
playlist = playlist_list_open_stream_suffix(is, suffix);
if (playlist != NULL)
diff --git a/src/playlist_list.h b/src/playlist_list.h
index 3710589a2..4a2485303 100644
--- a/src/playlist_list.h
+++ b/src/playlist_list.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -20,6 +20,8 @@
#ifndef MPD_PLAYLIST_LIST_H
#define MPD_PLAYLIST_LIST_H
+#include <glib.h>
+
#include <stdbool.h>
struct playlist_provider;
@@ -41,7 +43,7 @@ playlist_list_global_finish(void);
* Opens a playlist by its URI.
*/
struct playlist_provider *
-playlist_list_open_uri(const char *uri);
+playlist_list_open_uri(const char *uri, GMutex *mutex, GCond *cond);
/**
* Opens a playlist from an input stream.
@@ -69,6 +71,7 @@ playlist_suffix_supported(const char *suffix);
* @return a playlist, or NULL on error
*/
struct playlist_provider *
-playlist_list_open_path(const char *path_fs, struct input_stream **is_r);
+playlist_list_open_path(const char *path_fs, GMutex *mutex, GCond *cond,
+ struct input_stream **is_r);
#endif
diff --git a/src/playlist_mapper.c b/src/playlist_mapper.c
index 99b322073..13adb80d0 100644
--- a/src/playlist_mapper.c
+++ b/src/playlist_mapper.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -27,15 +27,16 @@
#include <assert.h>
static struct playlist_provider *
-playlist_open_path(const char *path_fs, struct input_stream **is_r)
+playlist_open_path(const char *path_fs, GMutex *mutex, GCond *cond,
+ struct input_stream **is_r)
{
struct playlist_provider *playlist;
- playlist = playlist_list_open_uri(path_fs);
+ playlist = playlist_list_open_uri(path_fs, mutex, cond);
if (playlist != NULL)
*is_r = NULL;
else
- playlist = playlist_list_open_path(path_fs, is_r);
+ playlist = playlist_list_open_path(path_fs, mutex, cond, is_r);
return playlist;
}
@@ -44,7 +45,8 @@ playlist_open_path(const char *path_fs, struct input_stream **is_r)
* Load a playlist from the configured playlist directory.
*/
static struct playlist_provider *
-playlist_open_in_playlist_dir(const char *uri, struct input_stream **is_r)
+playlist_open_in_playlist_dir(const char *uri, GMutex *mutex, GCond *cond,
+ struct input_stream **is_r)
{
char *path_fs;
@@ -56,7 +58,8 @@ playlist_open_in_playlist_dir(const char *uri, struct input_stream **is_r)
path_fs = g_build_filename(playlist_directory_fs, uri, NULL);
- struct playlist_provider *playlist = playlist_open_path(path_fs, is_r);
+ struct playlist_provider *playlist =
+ playlist_open_path(path_fs, mutex, cond, is_r);
g_free(path_fs);
return playlist;
@@ -66,7 +69,8 @@ playlist_open_in_playlist_dir(const char *uri, struct input_stream **is_r)
* Load a playlist from the configured music directory.
*/
static struct playlist_provider *
-playlist_open_in_music_dir(const char *uri, struct input_stream **is_r)
+playlist_open_in_music_dir(const char *uri, GMutex *mutex, GCond *cond,
+ struct input_stream **is_r)
{
char *path_fs;
@@ -76,25 +80,28 @@ playlist_open_in_music_dir(const char *uri, struct input_stream **is_r)
if (path_fs == NULL)
return NULL;
- struct playlist_provider *playlist = playlist_open_path(path_fs, is_r);
+ struct playlist_provider *playlist =
+ playlist_open_path(path_fs, mutex, cond, is_r);
g_free(path_fs);
return playlist;
}
struct playlist_provider *
-playlist_mapper_open(const char *uri, struct input_stream **is_r)
+playlist_mapper_open(const char *uri, GMutex *mutex, GCond *cond,
+ struct input_stream **is_r)
{
struct playlist_provider *playlist;
if (spl_valid_name(uri)) {
- playlist = playlist_open_in_playlist_dir(uri, is_r);
+ playlist = playlist_open_in_playlist_dir(uri, mutex, cond,
+ is_r);
if (playlist != NULL)
return playlist;
}
if (uri_safe_local(uri)) {
- playlist = playlist_open_in_music_dir(uri, is_r);
+ playlist = playlist_open_in_music_dir(uri, mutex, cond, is_r);
if (playlist != NULL)
return playlist;
}
diff --git a/src/playlist_mapper.h b/src/playlist_mapper.h
index b98af1b13..9a7187d93 100644
--- a/src/playlist_mapper.h
+++ b/src/playlist_mapper.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -20,6 +20,8 @@
#ifndef MPD_PLAYLIST_MAPPER_H
#define MPD_PLAYLIST_MAPPER_H
+#include <glib.h>
+
struct input_stream;
/**
@@ -31,6 +33,7 @@ struct input_stream;
* freed
*/
struct playlist_provider *
-playlist_mapper_open(const char *uri, struct input_stream **is_r);
+playlist_mapper_open(const char *uri, GMutex *mutex, GCond *cond,
+ struct input_stream **is_r);
#endif
diff --git a/src/playlist_plugin.h b/src/playlist_plugin.h
index 3d840573e..a27f651c0 100644
--- a/src/playlist_plugin.h
+++ b/src/playlist_plugin.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -20,6 +20,8 @@
#ifndef MPD_PLAYLIST_PLUGIN_H
#define MPD_PLAYLIST_PLUGIN_H
+#include <glib.h>
+
#include <stdbool.h>
#include <stddef.h>
@@ -64,7 +66,8 @@ struct playlist_plugin {
* Opens the playlist on the specified URI. This URI has
* either matched one of the schemes or one of the suffixes.
*/
- struct playlist_provider *(*open_uri)(const char *uri);
+ struct playlist_provider *(*open_uri)(const char *uri,
+ GMutex *mutex, GCond *cond);
/**
* Opens the playlist in the specified input stream. It has
@@ -110,9 +113,10 @@ playlist_plugin_finish(const struct playlist_plugin *plugin)
}
static inline struct playlist_provider *
-playlist_plugin_open_uri(const struct playlist_plugin *plugin, const char *uri)
+playlist_plugin_open_uri(const struct playlist_plugin *plugin, const char *uri,
+ GMutex *mutex, GCond *cond)
{
- return plugin->open_uri(uri);
+ return plugin->open_uri(uri, mutex, cond);
}
static inline struct playlist_provider *
diff --git a/src/playlist_print.c b/src/playlist_print.c
index 89ab2e5ab..a6bf84ccd 100644
--- a/src/playlist_print.c
+++ b/src/playlist_print.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -23,6 +23,7 @@
#include "playlist_plugin.h"
#include "playlist_any.h"
#include "playlist_song.h"
+#include "playlist.h"
#include "queue_print.h"
#include "stored_playlist.h"
#include "song_print.h"
@@ -116,11 +117,12 @@ playlist_print_changes_position(struct client *client,
}
bool
-spl_print(struct client *client, const char *name_utf8, bool detail)
+spl_print(struct client *client, const char *name_utf8, bool detail,
+ GError **error_r)
{
GPtrArray *list;
- list = spl_load(name_utf8);
+ list = spl_load(name_utf8, error_r);
if (list == NULL)
return false;
@@ -153,7 +155,7 @@ playlist_provider_print(struct client *client, const char *uri,
char *base_uri = uri != NULL ? g_path_get_dirname(uri) : NULL;
while ((song = playlist_plugin_read(playlist)) != NULL) {
- song = playlist_check_translate_song(song, base_uri);
+ song = playlist_check_translate_song(song, base_uri, false);
if (song == NULL)
continue;
@@ -169,10 +171,17 @@ playlist_provider_print(struct client *client, const char *uri,
bool
playlist_file_print(struct client *client, const char *uri, bool detail)
{
+ GMutex *mutex = g_mutex_new();
+ GCond *cond = g_cond_new();
+
struct input_stream *is;
- struct playlist_provider *playlist = playlist_open_any(uri, &is);
- if (playlist == NULL)
+ struct playlist_provider *playlist =
+ playlist_open_any(uri, mutex, cond, &is);
+ if (playlist == NULL) {
+ g_cond_free(cond);
+ g_mutex_free(mutex);
return false;
+ }
playlist_provider_print(client, uri, playlist, detail);
playlist_plugin_close(playlist);
@@ -180,5 +189,8 @@ playlist_file_print(struct client *client, const char *uri, bool detail)
if (is != NULL)
input_stream_close(is);
+ g_cond_free(cond);
+ g_mutex_free(mutex);
+
return true;
}
diff --git a/src/playlist_print.h b/src/playlist_print.h
index b3a0446ed..d4f1911d2 100644
--- a/src/playlist_print.h
+++ b/src/playlist_print.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -20,6 +20,7 @@
#ifndef PLAYLIST_PRINT_H
#define PLAYLIST_PRINT_H
+#include <glib.h>
#include <stdbool.h>
#include <stdint.h>
@@ -99,7 +100,8 @@ playlist_print_changes_position(struct client *client,
* @return true on success, false if the playlist does not exist
*/
bool
-spl_print(struct client *client, const char *name_utf8, bool detail);
+spl_print(struct client *client, const char *name_utf8, bool detail,
+ GError **error_r);
/**
* Send the playlist file to the client.
diff --git a/src/playlist_queue.c b/src/playlist_queue.c
index 635e23a28..aada94984 100644
--- a/src/playlist_queue.c
+++ b/src/playlist_queue.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -22,23 +22,35 @@
#include "playlist_plugin.h"
#include "playlist_any.h"
#include "playlist_song.h"
+#include "playlist.h"
#include "song.h"
#include "input_stream.h"
enum playlist_result
playlist_load_into_queue(const char *uri, struct playlist_provider *source,
- struct playlist *dest)
+ unsigned start_index, unsigned end_index,
+ struct playlist *dest, struct player_control *pc,
+ bool secure)
{
enum playlist_result result;
struct song *song;
char *base_uri = uri != NULL ? g_path_get_dirname(uri) : NULL;
- while ((song = playlist_plugin_read(source)) != NULL) {
- song = playlist_check_translate_song(song, base_uri);
+ for (unsigned i = 0;
+ i < end_index && (song = playlist_plugin_read(source)) != NULL;
+ ++i) {
+ if (i < start_index) {
+ /* skip songs before the start index */
+ if (!song_in_database(song))
+ song_free(song);
+ continue;
+ }
+
+ song = playlist_check_translate_song(song, base_uri, secure);
if (song == NULL)
continue;
- result = playlist_append_song(dest, song, NULL);
+ result = playlist_append_song(dest, pc, song, NULL);
if (result != PLAYLIST_RESULT_SUCCESS) {
if (!song_in_database(song))
song_free(song);
@@ -53,19 +65,33 @@ playlist_load_into_queue(const char *uri, struct playlist_provider *source,
}
enum playlist_result
-playlist_open_into_queue(const char *uri, struct playlist *dest)
+playlist_open_into_queue(const char *uri,
+ unsigned start_index, unsigned end_index,
+ struct playlist *dest, struct player_control *pc,
+ bool secure)
{
+ GMutex *mutex = g_mutex_new();
+ GCond *cond = g_cond_new();
+
struct input_stream *is;
- struct playlist_provider *playlist = playlist_open_any(uri, &is);
- if (playlist == NULL)
+ struct playlist_provider *playlist =
+ playlist_open_any(uri, mutex, cond, &is);
+ if (playlist == NULL) {
+ g_cond_free(cond);
+ g_mutex_free(mutex);
return PLAYLIST_RESULT_NO_SUCH_LIST;
+ }
enum playlist_result result =
- playlist_load_into_queue(uri, playlist, dest);
+ playlist_load_into_queue(uri, playlist, start_index, end_index,
+ dest, pc, secure);
playlist_plugin_close(playlist);
if (is != NULL)
input_stream_close(is);
+ g_cond_free(cond);
+ g_mutex_free(mutex);
+
return result;
}
diff --git a/src/playlist_queue.h b/src/playlist_queue.h
index 530d4b4be..24a851aab 100644
--- a/src/playlist_queue.h
+++ b/src/playlist_queue.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -24,10 +24,13 @@
#ifndef MPD_PLAYLIST_QUEUE_H
#define MPD_PLAYLIST_QUEUE_H
-#include "playlist.h"
+#include "playlist_error.h"
+
+#include <stdbool.h>
struct playlist_provider;
struct playlist;
+struct player_control;
/**
* Loads the contents of a playlist and append it to the specified
@@ -35,17 +38,24 @@ struct playlist;
*
* @param uri the URI of the playlist, used to resolve relative song
* URIs
+ * @param start_index the index of the first song
+ * @param end_index the index of the last song (excluding)
*/
enum playlist_result
playlist_load_into_queue(const char *uri, struct playlist_provider *source,
- struct playlist *dest);
+ unsigned start_index, unsigned end_index,
+ struct playlist *dest, struct player_control *pc,
+ bool secure);
/**
* Opens a playlist with a playlist plugin and append to the specified
* play queue.
*/
enum playlist_result
-playlist_open_into_queue(const char *uri, struct playlist *dest);
+playlist_open_into_queue(const char *uri,
+ unsigned start_index, unsigned end_index,
+ struct playlist *dest, struct player_control *pc,
+ bool secure);
#endif
diff --git a/src/playlist_save.c b/src/playlist_save.c
index 8ddc93ec9..6571e286c 100644
--- a/src/playlist_save.c
+++ b/src/playlist_save.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -19,7 +19,9 @@
#include "config.h"
#include "playlist_save.h"
+#include "playlist.h"
#include "stored_playlist.h"
+#include "queue.h"
#include "song.h"
#include "mapper.h"
#include "path.h"
@@ -108,18 +110,24 @@ spl_save_playlist(const char *name_utf8, const struct playlist *playlist)
return spl_save_queue(name_utf8, &playlist->queue);
}
-enum playlist_result
-playlist_load_spl(struct playlist *playlist, const char *name_utf8)
+bool
+playlist_load_spl(struct playlist *playlist, struct player_control *pc,
+ const char *name_utf8,
+ unsigned start_index, unsigned end_index,
+ GError **error_r)
{
GPtrArray *list;
- list = spl_load(name_utf8);
+ list = spl_load(name_utf8, error_r);
if (list == NULL)
- return PLAYLIST_RESULT_NO_SUCH_LIST;
+ return false;
+
+ if (list->len < end_index)
+ end_index = list->len;
- for (unsigned i = 0; i < list->len; ++i) {
+ for (unsigned i = start_index; i < end_index; ++i) {
const char *temp = g_ptr_array_index(list, i);
- if ((playlist_append_uri(playlist, temp, NULL)) != PLAYLIST_RESULT_SUCCESS) {
+ if ((playlist_append_uri(playlist, pc, temp, NULL)) != PLAYLIST_RESULT_SUCCESS) {
/* for windows compatibility, convert slashes */
char *temp2 = g_strdup(temp);
char *p = temp2;
@@ -128,7 +136,7 @@ playlist_load_spl(struct playlist *playlist, const char *name_utf8)
*p = '/';
p++;
}
- if ((playlist_append_uri(playlist, temp, NULL)) != PLAYLIST_RESULT_SUCCESS) {
+ if ((playlist_append_uri(playlist, pc, temp, NULL)) != PLAYLIST_RESULT_SUCCESS) {
g_warning("can't add file \"%s\"", temp2);
}
g_free(temp2);
@@ -136,5 +144,5 @@ playlist_load_spl(struct playlist *playlist, const char *name_utf8)
}
spl_free(list);
- return PLAYLIST_RESULT_SUCCESS;
+ return true;
}
diff --git a/src/playlist_save.h b/src/playlist_save.h
index a0131cf7f..a6c31a9a6 100644
--- a/src/playlist_save.h
+++ b/src/playlist_save.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -20,11 +20,15 @@
#ifndef MPD_PLAYLIST_SAVE_H
#define MPD_PLAYLIST_SAVE_H
-#include "playlist.h"
+#include "playlist_error.h"
+#include <stdbool.h>
#include <stdio.h>
struct song;
+struct queue;
+struct playlist;
+struct player_control;
void
playlist_print_song(FILE *fp, const struct song *song);
@@ -48,7 +52,10 @@ spl_save_playlist(const char *name_utf8, const struct playlist *playlist);
* Loads a stored playlist file, and append all songs to the global
* playlist.
*/
-enum playlist_result
-playlist_load_spl(struct playlist *playlist, const char *name_utf8);
+bool
+playlist_load_spl(struct playlist *playlist, struct player_control *pc,
+ const char *name_utf8,
+ unsigned start_index, unsigned end_index,
+ GError **error_r);
#endif
diff --git a/src/playlist_song.c b/src/playlist_song.c
index 827098655..8a3ba303e 100644
--- a/src/playlist_song.c
+++ b/src/playlist_song.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -84,7 +84,8 @@ apply_song_metadata(struct song *dest, const struct song *src)
}
struct song *
-playlist_check_translate_song(struct song *song, const char *base_uri)
+playlist_check_translate_song(struct song *song, const char *base_uri,
+ bool secure)
{
struct song *dest;
@@ -114,20 +115,19 @@ playlist_check_translate_song(struct song *song, const char *base_uri)
if (g_path_is_absolute(uri)) {
/* XXX fs_charset vs utf8? */
- char *prefix = map_directory_fs(db_get_root());
+ const char *prefix = mapper_get_music_directory();
- if (prefix == NULL || !g_str_has_prefix(uri, prefix) ||
- uri[strlen(prefix)] != '/') {
+ if (prefix != NULL && g_str_has_prefix(uri, prefix) &&
+ uri[strlen(prefix)] == '/')
+ uri += strlen(prefix) + 1;
+ else if (!secure) {
/* local files must be relative to the music
- directory */
- g_free(prefix);
+ directory when "secure" is enabled */
song_free(song);
return NULL;
}
base_uri = NULL;
- uri += strlen(prefix) + 1;
- g_free(prefix);
}
if (base_uri != NULL)
@@ -138,6 +138,12 @@ playlist_check_translate_song(struct song *song, const char *base_uri)
if (uri_has_scheme(uri)) {
dest = song_remote_new(uri);
g_free(uri);
+ } else if (g_path_is_absolute(uri) && secure) {
+ dest = song_file_load(uri, NULL);
+ if (dest == NULL) {
+ song_free(song);
+ return NULL;
+ }
} else {
dest = db_get_song(uri);
g_free(uri);
diff --git a/src/playlist_song.h b/src/playlist_song.h
index 5a2e4c2b0..ea8786912 100644
--- a/src/playlist_song.h
+++ b/src/playlist_song.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -20,12 +20,18 @@
#ifndef MPD_PLAYLIST_SONG_H
#define MPD_PLAYLIST_SONG_H
+#include <stdbool.h>
+
/**
* Verifies the song, returns NULL if it is unsafe. Translate the
* song to a new song object within the database, if it is a local
* file. The old song object is freed.
+ *
+ * @param secure if true, then local files are only allowed if they
+ * are relative to base_uri
*/
struct song *
-playlist_check_translate_song(struct song *song, const char *base_uri);
+playlist_check_translate_song(struct song *song, const char *base_uri,
+ bool secure);
#endif
diff --git a/src/playlist_state.c b/src/playlist_state.c
index bb9897e01..4aa2c2c92 100644
--- a/src/playlist_state.c
+++ b/src/playlist_state.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -29,6 +29,7 @@
#include "queue_save.h"
#include "path.h"
#include "text_file.h"
+#include "conf.h"
#include <string.h>
#include <stdlib.h>
@@ -53,11 +54,12 @@
#define PLAYLIST_BUFFER_SIZE 2*MPD_PATH_MAX
void
-playlist_state_save(FILE *fp, const struct playlist *playlist)
+playlist_state_save(FILE *fp, const struct playlist *playlist,
+ struct player_control *pc)
{
struct player_status player_status;
- pc_get_status(&player_status);
+ pc_get_status(pc, &player_status);
fputs(PLAYLIST_STATE_FILE_STATE, fp);
@@ -89,10 +91,11 @@ playlist_state_save(FILE *fp, const struct playlist *playlist)
fprintf(fp, PLAYLIST_STATE_FILE_CONSUME "%i\n",
playlist->queue.consume);
fprintf(fp, PLAYLIST_STATE_FILE_CROSSFADE "%i\n",
- (int)(pc_get_cross_fade()));
- fprintf(fp, PLAYLIST_STATE_FILE_MIXRAMPDB "%f\n", pc_get_mixramp_db());
+ (int)(pc_get_cross_fade(pc)));
+ fprintf(fp, PLAYLIST_STATE_FILE_MIXRAMPDB "%f\n",
+ pc_get_mixramp_db(pc));
fprintf(fp, PLAYLIST_STATE_FILE_MIXRAMPDELAY "%f\n",
- pc_get_mixramp_delay());
+ pc_get_mixramp_delay(pc));
fputs(PLAYLIST_STATE_FILE_PLAYLIST_BEGIN "\n", fp);
queue_save(fp, &playlist->queue);
fputs(PLAYLIST_STATE_FILE_PLAYLIST_END "\n", fp);
@@ -123,11 +126,11 @@ playlist_state_load(FILE *fp, GString *buffer, struct playlist *playlist)
bool
playlist_state_restore(const char *line, FILE *fp, GString *buffer,
- struct playlist *playlist)
+ struct playlist *playlist, struct player_control *pc)
{
int current = -1;
int seek_time = 0;
- int state = PLAYER_STATE_STOP;
+ enum player_state state = PLAYER_STATE_STOP;
bool random_mode = false;
if (!g_str_has_prefix(line, PLAYLIST_STATE_FILE_STATE))
@@ -148,16 +151,16 @@ playlist_state_restore(const char *line, FILE *fp, GString *buffer,
if (strcmp
(&(line[strlen(PLAYLIST_STATE_FILE_REPEAT)]),
"1") == 0) {
- playlist_set_repeat(playlist, true);
+ playlist_set_repeat(playlist, pc, true);
} else
- playlist_set_repeat(playlist, false);
+ playlist_set_repeat(playlist, pc, false);
} else if (g_str_has_prefix(line, PLAYLIST_STATE_FILE_SINGLE)) {
if (strcmp
(&(line[strlen(PLAYLIST_STATE_FILE_SINGLE)]),
"1") == 0) {
- playlist_set_single(playlist, true);
+ playlist_set_single(playlist, pc, true);
} else
- playlist_set_single(playlist, false);
+ playlist_set_single(playlist, pc, false);
} else if (g_str_has_prefix(line, PLAYLIST_STATE_FILE_CONSUME)) {
if (strcmp
(&(line[strlen(PLAYLIST_STATE_FILE_CONSUME)]),
@@ -166,11 +169,14 @@ playlist_state_restore(const char *line, FILE *fp, GString *buffer,
} else
playlist_set_consume(playlist, false);
} else if (g_str_has_prefix(line, PLAYLIST_STATE_FILE_CROSSFADE)) {
- pc_set_cross_fade(atoi(line + strlen(PLAYLIST_STATE_FILE_CROSSFADE)));
+ pc_set_cross_fade(pc,
+ atoi(line + strlen(PLAYLIST_STATE_FILE_CROSSFADE)));
} else if (g_str_has_prefix(line, PLAYLIST_STATE_FILE_MIXRAMPDB)) {
- pc_set_mixramp_db(atof(line + strlen(PLAYLIST_STATE_FILE_MIXRAMPDB)));
+ pc_set_mixramp_db(pc,
+ atof(line + strlen(PLAYLIST_STATE_FILE_MIXRAMPDB)));
} else if (g_str_has_prefix(line, PLAYLIST_STATE_FILE_MIXRAMPDELAY)) {
- pc_set_mixramp_delay(atof(line + strlen(PLAYLIST_STATE_FILE_MIXRAMPDELAY)));
+ pc_set_mixramp_delay(pc,
+ atof(line + strlen(PLAYLIST_STATE_FILE_MIXRAMPDELAY)));
} else if (g_str_has_prefix(line, PLAYLIST_STATE_FILE_RANDOM)) {
random_mode =
strcmp(line + strlen(PLAYLIST_STATE_FILE_RANDOM),
@@ -185,38 +191,46 @@ playlist_state_restore(const char *line, FILE *fp, GString *buffer,
}
}
- playlist_set_random(playlist, random_mode);
+ playlist_set_random(playlist, pc, random_mode);
if (!queue_is_empty(&playlist->queue)) {
if (!queue_valid_position(&playlist->queue, current))
current = 0;
+ if (state == PLAYER_STATE_PLAY &&
+ config_get_bool("restore_paused", false))
+ /* the user doesn't want MPD to auto-start
+ playback after startup; fall back to
+ "pause" */
+ state = PLAYER_STATE_PAUSE;
+
/* enable all devices for the first time; this must be
called here, after the audio output states were
restored, before playback begins */
if (state != PLAYER_STATE_STOP)
- pc_update_audio();
+ pc_update_audio(pc);
if (state == PLAYER_STATE_STOP /* && config_option */)
playlist->current = current;
else if (seek_time == 0)
- playlist_play(playlist, current);
+ playlist_play(playlist, pc, current);
else
- playlist_seek_song(playlist, current, seek_time);
+ playlist_seek_song(playlist, pc, current, seek_time);
if (state == PLAYER_STATE_PAUSE)
- pc_pause();
+ pc_pause(pc);
}
return true;
}
unsigned
-playlist_state_get_hash(const struct playlist *playlist)
+playlist_state_get_hash(const struct playlist *playlist,
+ struct player_control *pc)
{
struct player_status player_status;
- pc_get_status(&player_status);
+ pc_get_status(pc, &player_status);
return playlist->queue.version ^
(player_status.state != PLAYER_STATE_STOP
@@ -226,7 +240,7 @@ playlist_state_get_hash(const struct playlist *playlist)
? (queue_order_to_position(&playlist->queue,
playlist->current) << 16)
: 0) ^
- ((int)pc_get_cross_fade() << 20) ^
+ ((int)pc_get_cross_fade(pc) << 20) ^
(player_status.state << 24) ^
(playlist->queue.random << 27) ^
(playlist->queue.repeat << 28) ^
diff --git a/src/playlist_state.h b/src/playlist_state.h
index 8ca3657f2..f67d01d2c 100644
--- a/src/playlist_state.h
+++ b/src/playlist_state.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -30,13 +30,15 @@
#include <stdio.h>
struct playlist;
+struct player_control;
void
-playlist_state_save(FILE *fp, const struct playlist *playlist);
+playlist_state_save(FILE *fp, const struct playlist *playlist,
+ struct player_control *pc);
bool
playlist_state_restore(const char *line, FILE *fp, GString *buffer,
- struct playlist *playlist);
+ struct playlist *playlist, struct player_control *pc);
/**
* Generates a hash number for the current state of the playlist and
@@ -45,6 +47,7 @@ playlist_state_restore(const char *line, FILE *fp, GString *buffer,
* be saved.
*/
unsigned
-playlist_state_get_hash(const struct playlist *playlist);
+playlist_state_get_hash(const struct playlist *playlist,
+ struct player_control *pc);
#endif
diff --git a/src/playlist_vector.c b/src/playlist_vector.c
index 7c1765a98..74c7bf089 100644
--- a/src/playlist_vector.c
+++ b/src/playlist_vector.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -19,6 +19,7 @@
#include "config.h"
#include "playlist_vector.h"
+#include "db_lock.h"
#include <assert.h>
#include <string.h>
@@ -46,60 +47,48 @@ playlist_metadata_free(struct playlist_metadata *pm)
}
void
-playlist_vector_deinit(struct playlist_vector *pv)
+playlist_vector_deinit(struct list_head *pv)
{
assert(pv != NULL);
- while (pv->head != NULL) {
- struct playlist_metadata *pm = pv->head;
- pv->head = pm->next;
+ struct playlist_metadata *pm, *n;
+ playlist_vector_for_each_safe(pm, n, pv)
playlist_metadata_free(pm);
- }
}
-static struct playlist_metadata **
-playlist_vector_find_p(struct playlist_vector *pv, const char *name)
+struct playlist_metadata *
+playlist_vector_find(struct list_head *pv, const char *name)
{
+ assert(holding_db_lock());
assert(pv != NULL);
assert(name != NULL);
- struct playlist_metadata **pmp = &pv->head;
-
- for (;;) {
- struct playlist_metadata *pm = *pmp;
- if (pm == NULL)
- return NULL;
-
+ struct playlist_metadata *pm;
+ playlist_vector_for_each(pm, pv)
if (strcmp(pm->name, name) == 0)
- return pmp;
+ return pm;
- pmp = &pm->next;
- }
-}
-
-struct playlist_metadata *
-playlist_vector_find(struct playlist_vector *pv, const char *name)
-{
- struct playlist_metadata **pmp = playlist_vector_find_p(pv, name);
- return pmp != NULL ? *pmp : NULL;
+ return NULL;
}
void
-playlist_vector_add(struct playlist_vector *pv,
+playlist_vector_add(struct list_head *pv,
const char *name, time_t mtime)
{
+ assert(holding_db_lock());
+
struct playlist_metadata *pm = playlist_metadata_new(name, mtime);
- pm->next = pv->head;
- pv->head = pm;
+ list_add_tail(&pm->siblings, pv);
}
bool
-playlist_vector_update_or_add(struct playlist_vector *pv,
+playlist_vector_update_or_add(struct list_head *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;
+ assert(holding_db_lock());
+
+ struct playlist_metadata *pm = playlist_vector_find(pv, name);
+ if (pm != NULL) {
if (mtime == pm->mtime)
return false;
@@ -111,15 +100,15 @@ playlist_vector_update_or_add(struct playlist_vector *pv,
}
bool
-playlist_vector_remove(struct playlist_vector *pv, const char *name)
+playlist_vector_remove(struct list_head *pv, const char *name)
{
- struct playlist_metadata **pmp = playlist_vector_find_p(pv, name);
- if (pmp == NULL)
- return false;
+ assert(holding_db_lock());
- struct playlist_metadata *pm = *pmp;
- *pmp = pm->next;
+ struct playlist_metadata *pm = playlist_vector_find(pv, name);
+ if (pm == NULL)
+ return false;
+ list_del(&pm->siblings);
playlist_metadata_free(pm);
return true;
}
diff --git a/src/playlist_vector.h b/src/playlist_vector.h
index 62861ae49..0af6df8b4 100644
--- a/src/playlist_vector.h
+++ b/src/playlist_vector.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -20,15 +20,23 @@
#ifndef MPD_PLAYLIST_VECTOR_H
#define MPD_PLAYLIST_VECTOR_H
+#include "util/list.h"
+
#include <stdbool.h>
#include <stddef.h>
#include <sys/time.h>
+#define playlist_vector_for_each(pos, head) \
+ list_for_each_entry(pos, head, siblings)
+
+#define playlist_vector_for_each_safe(pos, n, head) \
+ list_for_each_entry_safe(pos, n, head, siblings)
+
/**
* A directory entry pointing to a playlist file.
*/
struct playlist_metadata {
- struct playlist_metadata *next;
+ struct list_head siblings;
/**
* The UTF-8 encoded name of the playlist file.
@@ -38,40 +46,35 @@ struct playlist_metadata {
time_t mtime;
};
-struct playlist_vector {
- struct playlist_metadata *head;
-};
-
-static inline void
-playlist_vector_init(struct playlist_vector *pv)
-{
- pv->head = NULL;
-}
-
void
-playlist_vector_deinit(struct playlist_vector *pv);
-
-static inline bool
-playlist_vector_is_empty(const struct playlist_vector *pv)
-{
- return pv->head == NULL;
-}
+playlist_vector_deinit(struct list_head *pv);
+/**
+ * Caller must lock the #db_mutex.
+ */
struct playlist_metadata *
-playlist_vector_find(struct playlist_vector *pv, const char *name);
+playlist_vector_find(struct list_head *pv, const char *name);
+/**
+ * Caller must lock the #db_mutex.
+ */
void
-playlist_vector_add(struct playlist_vector *pv,
+playlist_vector_add(struct list_head *pv,
const char *name, time_t mtime);
/**
+ * Caller must lock the #db_mutex.
+ *
* @return true if the vector or one of its items was modified
*/
bool
-playlist_vector_update_or_add(struct playlist_vector *pv,
+playlist_vector_update_or_add(struct list_head *pv,
const char *name, time_t mtime);
+/**
+ * Caller must lock the #db_mutex.
+ */
bool
-playlist_vector_remove(struct playlist_vector *pv, const char *name);
+playlist_vector_remove(struct list_head *pv, const char *name);
#endif /* SONGVEC_H */
diff --git a/src/poison.h b/src/poison.h
index 3654f2e9c..c95b5d005 100644
--- a/src/poison.h
+++ b/src/poison.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/protocol/argparser.c b/src/protocol/argparser.c
new file mode 100644
index 000000000..b21d4c53c
--- /dev/null
+++ b/src/protocol/argparser.c
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) 2003-2012 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "argparser.h"
+#include "result.h"
+
+#include <glib.h>
+#include <stdlib.h>
+
+bool
+check_uint32(struct client *client, uint32_t *dst, const char *s)
+{
+ char *test;
+
+ *dst = strtoul(s, &test, 10);
+ if (test == s || *test != '\0') {
+ command_error(client, ACK_ERROR_ARG,
+ "Integer expected: %s", s);
+ return false;
+ }
+ return true;
+}
+
+bool
+check_int(struct client *client, int *value_r, const char *s)
+{
+ char *test;
+ long value;
+
+ value = strtol(s, &test, 10);
+ if (test == s || *test != '\0') {
+ command_error(client, ACK_ERROR_ARG,
+ "Integer expected: %s", s);
+ return false;
+ }
+
+#if G_MAXLONG > G_MAXINT
+ if (value < G_MININT || value > G_MAXINT) {
+ command_error(client, ACK_ERROR_ARG,
+ "Number too large: %s", s);
+ return false;
+ }
+#endif
+
+ *value_r = (int)value;
+ return true;
+}
+
+bool
+check_range(struct client *client, unsigned *value_r1, unsigned *value_r2,
+ const char *s)
+{
+ char *test, *test2;
+ long value;
+
+ value = strtol(s, &test, 10);
+ if (test == s || (*test != '\0' && *test != ':')) {
+ command_error(client, ACK_ERROR_ARG,
+ "Integer or range expected: %s", s);
+ return false;
+ }
+
+ if (value == -1 && *test == 0) {
+ /* compatibility with older MPD versions: specifying
+ "-1" makes MPD display the whole list */
+ *value_r1 = 0;
+ *value_r2 = G_MAXUINT;
+ return true;
+ }
+
+ if (value < 0) {
+ command_error(client, ACK_ERROR_ARG,
+ "Number is negative: %s", s);
+ return false;
+ }
+
+#if G_MAXLONG > G_MAXUINT
+ if (value > G_MAXUINT) {
+ command_error(client, ACK_ERROR_ARG,
+ "Number too large: %s", s);
+ return false;
+ }
+#endif
+
+ *value_r1 = (unsigned)value;
+
+ if (*test == ':') {
+ value = strtol(++test, &test2, 10);
+ if (test2 == test || *test2 != '\0') {
+ command_error(client, ACK_ERROR_ARG,
+ "Integer or range expected: %s", s);
+ return false;
+ }
+
+ if (test == test2)
+ value = G_MAXUINT;
+
+ if (value < 0) {
+ command_error(client, ACK_ERROR_ARG,
+ "Number is negative: %s", s);
+ return false;
+ }
+
+#if G_MAXLONG > G_MAXUINT
+ if (value > G_MAXUINT) {
+ command_error(client, ACK_ERROR_ARG,
+ "Number too large: %s", s);
+ return false;
+ }
+#endif
+ *value_r2 = (unsigned)value;
+ } else {
+ *value_r2 = (unsigned)value + 1;
+ }
+
+ return true;
+}
+
+bool
+check_unsigned(struct client *client, unsigned *value_r, const char *s)
+{
+ unsigned long value;
+ char *endptr;
+
+ value = strtoul(s, &endptr, 10);
+ if (endptr == s || *endptr != 0) {
+ command_error(client, ACK_ERROR_ARG,
+ "Integer expected: %s", s);
+ return false;
+ }
+
+ if (value > G_MAXUINT) {
+ command_error(client, ACK_ERROR_ARG,
+ "Number too large: %s", s);
+ return false;
+ }
+
+ *value_r = (unsigned)value;
+ return true;
+}
+
+bool
+check_bool(struct client *client, bool *value_r, const char *s)
+{
+ long value;
+ char *endptr;
+
+ value = strtol(s, &endptr, 10);
+ if (endptr == s || *endptr != 0 || (value != 0 && value != 1)) {
+ command_error(client, ACK_ERROR_ARG,
+ "Boolean (0/1) expected: %s", s);
+ return false;
+ }
+
+ *value_r = !!value;
+ return true;
+}
+
+bool
+check_float(struct client *client, float *value_r, const char *s)
+{
+ float value;
+ char *endptr;
+
+ value = strtof(s, &endptr);
+ if (endptr == s || *endptr != 0) {
+ command_error(client, ACK_ERROR_ARG,
+ "Float expected: %s", s);
+ return false;
+ }
+
+ *value_r = value;
+ return true;
+}
diff --git a/src/protocol/argparser.h b/src/protocol/argparser.h
new file mode 100644
index 000000000..e88aea478
--- /dev/null
+++ b/src/protocol/argparser.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2003-2012 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_PROTOCOL_ARGPARSER_H
+#define MPD_PROTOCOL_ARGPARSER_H
+
+#include "check.h"
+
+#include <stdbool.h>
+#include <stdint.h>
+
+struct client;
+
+bool
+check_uint32(struct client *client, uint32_t *dst, const char *s);
+
+bool
+check_int(struct client *client, int *value_r, const char *s);
+
+bool
+check_range(struct client *client, unsigned *value_r1, unsigned *value_r2,
+ const char *s);
+
+bool
+check_unsigned(struct client *client, unsigned *value_r, const char *s);
+
+bool
+check_bool(struct client *client, bool *value_r, const char *s);
+
+bool
+check_float(struct client *client, float *value_r, const char *s);
+
+#endif
diff --git a/src/protocol/result.c b/src/protocol/result.c
new file mode 100644
index 000000000..30cd0a266
--- /dev/null
+++ b/src/protocol/result.c
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2003-2012 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "result.h"
+#include "client.h"
+
+#include <assert.h>
+
+const char *current_command;
+int command_list_num;
+
+void
+command_success(struct client *client)
+{
+ client_puts(client, "OK\n");
+}
+
+void
+command_error_v(struct client *client, enum ack error,
+ const char *fmt, va_list args)
+{
+ assert(client != NULL);
+ assert(current_command != NULL);
+
+ client_printf(client, "ACK [%i@%i] {%s} ",
+ (int)error, command_list_num, current_command);
+ client_vprintf(client, fmt, args);
+ client_puts(client, "\n");
+
+ current_command = NULL;
+}
+
+void
+command_error(struct client *client, enum ack error, const char *fmt, ...)
+{
+ va_list args;
+ va_start(args, fmt);
+ command_error_v(client, error, fmt, args);
+ va_end(args);
+}
diff --git a/src/protocol/result.h b/src/protocol/result.h
new file mode 100644
index 000000000..8b9e44bfd
--- /dev/null
+++ b/src/protocol/result.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2003-2012 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_PROTOCOL_RESULT_H
+#define MPD_PROTOCOL_RESULT_H
+
+#include "check.h"
+#include "ack.h"
+
+#include <glib.h>
+
+struct client;
+
+extern const char *current_command;
+extern int command_list_num;
+
+void
+command_success(struct client *client);
+
+void
+command_error_v(struct client *client, enum ack error,
+ const char *fmt, va_list args);
+
+G_GNUC_PRINTF(3, 4)
+void
+command_error(struct client *client, enum ack error, const char *fmt, ...);
+
+#endif
diff --git a/src/queue.c b/src/queue.c
index dd0b48cb5..cd932875e 100644
--- a/src/queue.c
+++ b/src/queue.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -21,6 +21,8 @@
#include "queue.h"
#include "song.h"
+#include <stdlib.h>
+
/**
* Generate a non-existing id number.
*/
@@ -104,6 +106,7 @@ queue_append(struct queue *queue, struct song *song)
.song = song,
.id = id,
.version = queue->version,
+ .priority = 0,
};
queue->order[queue->length] = queue->length;
@@ -220,6 +223,30 @@ queue_move_range(struct queue *queue, unsigned start, unsigned end, unsigned to)
}
}
+/**
+ * Moves a song to a new position in the "order" list.
+ */
+static void
+queue_move_order(struct queue *queue, unsigned from_order, unsigned to_order)
+{
+ assert(queue != NULL);
+ assert(from_order < queue->length);
+ assert(to_order <= queue->length);
+
+ const unsigned from_position =
+ queue_order_to_position(queue, from_order);
+
+ if (from_order < to_order) {
+ for (unsigned i = from_order; i < to_order; ++i)
+ queue->order[i] = queue->order[i + 1];
+ } else {
+ for (unsigned i = from_order; i > to_order; --i)
+ queue->order[i] = queue->order[i - 1];
+ }
+
+ queue->order[to_order] = from_position;
+}
+
void
queue_delete(struct queue *queue, unsigned position)
{
@@ -308,15 +335,123 @@ queue_finish(struct queue *queue)
g_rand_free(queue->rand);
}
-void
-queue_shuffle_order(struct queue *queue)
+static const struct queue_item *
+queue_get_order_item_const(const struct queue *queue, unsigned order)
+{
+ assert(queue != NULL);
+ assert(order < queue->length);
+
+ return &queue->items[queue->order[order]];
+}
+
+static uint8_t
+queue_get_order_priority(const struct queue *queue, unsigned order)
+{
+ return queue_get_order_item_const(queue, order)->priority;
+}
+
+static gint
+queue_item_compare_order_priority(gconstpointer av, gconstpointer bv,
+ gpointer user_data)
+{
+ const struct queue *queue = user_data;
+ const unsigned *const ap = av;
+ const unsigned *const bp = bv;
+ assert(ap >= queue->order && ap < queue->order + queue->length);
+ assert(bp >= queue->order && bp < queue->order + queue->length);
+ uint8_t a = queue->items[*ap].priority;
+ uint8_t b = queue->items[*bp].priority;
+
+ if (G_LIKELY(a == b))
+ return 0;
+ else if (a > b)
+ return -1;
+ else
+ return 1;
+}
+
+static void
+queue_sort_order_by_priority(struct queue *queue, unsigned start, unsigned end)
{
+ assert(queue != NULL);
assert(queue->random);
+ assert(start <= end);
+ assert(end <= queue->length);
- for (unsigned i = 0; i < queue->length; i++)
+ g_qsort_with_data(&queue->order[start], end - start,
+ sizeof(queue->order[0]),
+ queue_item_compare_order_priority,
+ queue);
+}
+
+/**
+ * Shuffle the order of items in the specified range, ignoring their
+ * priorities.
+ */
+static void
+queue_shuffle_order_range(struct queue *queue, unsigned start, unsigned end)
+{
+ assert(queue != NULL);
+ assert(queue->random);
+ assert(start <= end);
+ assert(end <= queue->length);
+
+ for (unsigned i = start; i < end; ++i)
queue_swap_order(queue, i,
- g_rand_int_range(queue->rand, i,
- queue->length));
+ g_rand_int_range(queue->rand, i, end));
+}
+
+/**
+ * Sort the "order" of items by priority, and then shuffle each
+ * priority group.
+ */
+void
+queue_shuffle_order_range_with_priority(struct queue *queue,
+ unsigned start, unsigned end)
+{
+ assert(queue != NULL);
+ assert(queue->random);
+ assert(start <= end);
+ assert(end <= queue->length);
+
+ if (start == end)
+ return;
+
+ /* first group the range by priority */
+ queue_sort_order_by_priority(queue, start, end);
+
+ /* now shuffle each priority group */
+ unsigned group_start = start;
+ uint8_t group_priority = queue_get_order_priority(queue, start);
+
+ for (unsigned i = start + 1; i < end; ++i) {
+ uint8_t priority = queue_get_order_priority(queue, i);
+ assert(priority <= group_priority);
+
+ if (priority != group_priority) {
+ /* start of a new group - shuffle the one that
+ has just ended */
+ queue_shuffle_order_range(queue, group_start, i);
+ group_start = i;
+ group_priority = priority;
+ }
+ }
+
+ /* shuffle the last group */
+ queue_shuffle_order_range(queue, group_start, end);
+}
+
+void
+queue_shuffle_order(struct queue *queue)
+{
+ queue_shuffle_order_range_with_priority(queue, 0, queue->length);
+}
+
+static void
+queue_shuffle_order_first(struct queue *queue, unsigned start, unsigned end)
+{
+ queue_swap_order(queue, start,
+ g_rand_int_range(queue->rand, start, end));
}
void
@@ -337,3 +472,132 @@ queue_shuffle_range(struct queue *queue, unsigned start, unsigned end)
queue_swap(queue, i, ri);
}
}
+
+/**
+ * Find the first item that has this specified priority or higher.
+ */
+G_GNUC_PURE
+static unsigned
+queue_find_priority_order(const struct queue *queue, unsigned start_order,
+ uint8_t priority, unsigned exclude_order)
+{
+ assert(queue != NULL);
+ assert(queue->random);
+ assert(start_order <= queue->length);
+
+ for (unsigned order = start_order; order < queue->length; ++order) {
+ const unsigned position = queue_order_to_position(queue, order);
+ const struct queue_item *item = &queue->items[position];
+ if (item->priority <= priority && order != exclude_order)
+ return order;
+ }
+
+ return queue->length;
+}
+
+G_GNUC_PURE
+static unsigned
+queue_count_same_priority(const struct queue *queue, unsigned start_order,
+ uint8_t priority)
+{
+ assert(queue != NULL);
+ assert(queue->random);
+ assert(start_order <= queue->length);
+
+ for (unsigned order = start_order; order < queue->length; ++order) {
+ const unsigned position = queue_order_to_position(queue, order);
+ const struct queue_item *item = &queue->items[position];
+ if (item->priority != priority)
+ return order - start_order;
+ }
+
+ return queue->length - start_order;
+}
+
+bool
+queue_set_priority(struct queue *queue, unsigned position, uint8_t priority,
+ int after_order)
+{
+ assert(queue != NULL);
+ assert(position < queue->length);
+
+ struct queue_item *item = &queue->items[position];
+ uint8_t old_priority = item->priority;
+ if (old_priority == priority)
+ return false;
+
+ item->version = queue->version;
+ item->priority = priority;
+
+ if (!queue->random)
+ /* don't reorder if not in random mode */
+ return true;
+
+ unsigned order = queue_position_to_order(queue, position);
+ if (after_order >= 0) {
+ if (order == (unsigned)after_order)
+ /* don't reorder the current song */
+ return true;
+
+ if (order < (unsigned)after_order) {
+ /* the specified song has been played already
+ - enqueue it only if its priority has just
+ become bigger than the current one's */
+
+ const unsigned after_position =
+ queue_order_to_position(queue, after_order);
+ const struct queue_item *after_item =
+ &queue->items[after_position];
+ if (old_priority > after_item->priority ||
+ priority <= after_item->priority)
+ /* priority hasn't become bigger */
+ return true;
+ }
+ }
+
+ /* move the item to the beginning of the priority group (or
+ create a new priority group) */
+
+ const unsigned before_order =
+ queue_find_priority_order(queue, after_order + 1, priority,
+ order);
+ const unsigned new_order = before_order > order
+ ? before_order - 1
+ : before_order;
+ queue_move_order(queue, order, new_order);
+
+ /* shuffle the song within that priority group */
+
+ const unsigned priority_count =
+ queue_count_same_priority(queue, new_order, priority);
+ assert(priority_count >= 1);
+ queue_shuffle_order_first(queue, new_order,
+ new_order + priority_count);
+
+ return true;
+}
+
+bool
+queue_set_priority_range(struct queue *queue,
+ unsigned start_position, unsigned end_position,
+ uint8_t priority, int after_order)
+{
+ assert(queue != NULL);
+ assert(start_position <= end_position);
+ assert(end_position <= queue->length);
+
+ bool modified = false;
+ int after_position = after_order >= 0
+ ? (int)queue_order_to_position(queue, after_order)
+ : -1;
+ for (unsigned i = start_position; i < end_position; ++i) {
+ after_order = after_position >= 0
+ ? (int)queue_position_to_order(queue, after_position)
+ : -1;
+
+ modified |= queue_set_priority(queue, i, priority,
+ after_order);
+ }
+
+ return modified;
+}
diff --git a/src/queue.h b/src/queue.h
index 05eeafa22..5cb5c196b 100644
--- a/src/queue.h
+++ b/src/queue.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -46,6 +46,13 @@ struct queue_item {
/** when was this item last changed? */
uint32_t version;
+
+ /**
+ * The priority of this item, between 0 and 255. High
+ * priority value means that this song gets played first in
+ * "random" mode.
+ */
+ uint8_t priority;
};
/**
@@ -181,6 +188,15 @@ queue_position_to_order(const struct queue *queue, unsigned position)
}
}
+G_GNUC_PURE
+static inline uint8_t
+queue_get_priority_at_position(const struct queue *queue, unsigned position)
+{
+ assert(position < queue->length);
+
+ return queue->items[position].priority;
+}
+
/**
* Returns the song at the specified position.
*/
@@ -320,6 +336,14 @@ queue_restore_order(struct queue *queue)
}
/**
+ * Shuffle the order of items in the specified range, taking their
+ * priorities into account.
+ */
+void
+queue_shuffle_order_range_with_priority(struct queue *queue,
+ unsigned start, unsigned end);
+
+/**
* Shuffles the virtual order of songs, but does not move them
* physically. This is used in random mode.
*/
@@ -341,4 +365,13 @@ queue_shuffle_order_last(struct queue *queue, unsigned start, unsigned end);
void
queue_shuffle_range(struct queue *queue, unsigned start, unsigned end);
+bool
+queue_set_priority(struct queue *queue, unsigned position,
+ uint8_t priority, int after_order);
+
+bool
+queue_set_priority_range(struct queue *queue,
+ unsigned start_position, unsigned end_position,
+ uint8_t priority, int after_order);
+
#endif
diff --git a/src/queue_print.c b/src/queue_print.c
index 53ddfb689..d149e8b6f 100644
--- a/src/queue_print.c
+++ b/src/queue_print.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -41,6 +41,10 @@ queue_print_song_info(struct client *client, const struct queue *queue,
song_print_info(client, queue_get(queue, position));
client_printf(client, "Pos: %u\nId: %u\n",
position, queue_position_to_id(queue, position));
+
+ uint8_t priority = queue_get_priority_at_position(queue, position);
+ if (priority != 0)
+ client_printf(client, "Prio: %u\n", priority);
}
void
diff --git a/src/queue_print.h b/src/queue_print.h
index d754a9673..371e20416 100644
--- a/src/queue_print.h
+++ b/src/queue_print.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/queue_save.c b/src/queue_save.c
index afe04ca2d..a7c511c0e 100644
--- a/src/queue_save.c
+++ b/src/queue_save.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/queue_save.h b/src/queue_save.h
index 287683390..5526d615d 100644
--- a/src/queue_save.h
+++ b/src/queue_save.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/refcount.h b/src/refcount.h
index 87a2715a4..a882d76b0 100644
--- a/src/refcount.h
+++ b/src/refcount.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* Redistribution and use in source and binary forms, with or without
@@ -37,6 +37,7 @@
#define MPD_REFCOUNT_H
#include <glib.h>
+#include <stdbool.h>
struct refcount {
gint n;
diff --git a/src/replay_gain_ape.c b/src/replay_gain_ape.c
index 9ae47468f..0b59e3c02 100644
--- a/src/replay_gain_ape.c
+++ b/src/replay_gain_ape.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/replay_gain_ape.h b/src/replay_gain_ape.h
index 8525ac85e..35760a0aa 100644
--- a/src/replay_gain_ape.h
+++ b/src/replay_gain_ape.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/replay_gain_config.c b/src/replay_gain_config.c
index bbfe127a7..2181387b7 100644
--- a/src/replay_gain_config.c
+++ b/src/replay_gain_config.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/replay_gain_config.h b/src/replay_gain_config.h
index 8fb77a5f6..18747cef2 100644
--- a/src/replay_gain_config.h
+++ b/src/replay_gain_config.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/replay_gain_info.c b/src/replay_gain_info.c
index 3b4ab4577..1f09e7a1a 100644
--- a/src/replay_gain_info.c
+++ b/src/replay_gain_info.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/replay_gain_info.h b/src/replay_gain_info.h
index 83b46df84..9097c3e02 100644
--- a/src/replay_gain_info.h
+++ b/src/replay_gain_info.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/resolver.c b/src/resolver.c
new file mode 100644
index 000000000..5d8d299c6
--- /dev/null
+++ b/src/resolver.c
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "resolver.h"
+#include "glib_compat.h"
+
+#ifndef G_OS_WIN32
+#include <sys/socket.h>
+#include <netdb.h>
+#else /* G_OS_WIN32 */
+#include <ws2tcpip.h>
+#include <winsock.h>
+#endif /* G_OS_WIN32 */
+
+#include <string.h>
+
+char *
+sockaddr_to_string(const struct sockaddr *sa, size_t length, GError **error)
+{
+#if defined(HAVE_IPV6) && defined(IN6_IS_ADDR_V4MAPPED)
+ const struct sockaddr_in6 *a6 = (const struct sockaddr_in6 *)sa;
+ struct sockaddr_in a4;
+#endif
+ int ret;
+ char host[NI_MAXHOST], serv[NI_MAXSERV];
+
+#if defined(HAVE_IPV6) && defined(IN6_IS_ADDR_V4MAPPED)
+ if (sa->sa_family == AF_INET6 &&
+ IN6_IS_ADDR_V4MAPPED(&a6->sin6_addr)) {
+ /* convert "::ffff:127.0.0.1" to "127.0.0.1" */
+
+ memset(&a4, 0, sizeof(a4));
+ a4.sin_family = AF_INET;
+ memcpy(&a4.sin_addr, ((const char *)&a6->sin6_addr) + 12,
+ sizeof(a4.sin_addr));
+ a4.sin_port = a6->sin6_port;
+
+ sa = (const struct sockaddr *)&a4;
+ length = sizeof(a4);
+ }
+#endif
+
+ ret = getnameinfo(sa, length, host, sizeof(host), serv, sizeof(serv),
+ NI_NUMERICHOST|NI_NUMERICSERV);
+ if (ret != 0) {
+ g_set_error(error, g_quark_from_static_string("netdb"), ret,
+ "%s", gai_strerror(ret));
+ return NULL;
+ }
+
+#ifdef HAVE_UN
+ if (sa->sa_family == AF_UNIX)
+ /* "serv" contains corrupt information with unix
+ sockets */
+ return g_strdup(host);
+#endif
+
+#ifdef HAVE_IPV6
+ if (strchr(host, ':') != NULL)
+ return g_strconcat("[", host, "]:", serv, NULL);
+#endif
+
+ return g_strconcat(host, ":", serv, NULL);
+}
+
+struct addrinfo *
+resolve_host_port(const char *host_port, unsigned default_port,
+ int flags, int socktype,
+ GError **error_r)
+{
+ char *p = g_strdup(host_port);
+ const char *host = p, *port = NULL;
+
+ if (host_port[0] == '[') {
+ /* IPv6 needs enclosing square braces, to
+ differentiate between IP colons and the port
+ separator */
+
+ char *q = strchr(p + 1, ']');
+ if (q != NULL && q[1] == ':' && q[2] != 0) {
+ *q = 0;
+ ++host;
+ port = q + 2;
+ }
+ }
+
+ if (port == NULL) {
+ /* port is after the colon, but only if it's the only
+ colon (don't split IPv6 addresses) */
+
+ char *q = strchr(p, ':');
+ if (q != NULL && q[1] != 0 && strchr(q + 1, ':') == NULL) {
+ *q = 0;
+ port = q + 1;
+ }
+ }
+
+ char buffer[32];
+ if (port == NULL && default_port != 0) {
+ g_snprintf(buffer, sizeof(buffer), "%u", default_port);
+ port = buffer;
+ }
+
+ if ((flags & AI_PASSIVE) != 0 && strcmp(host, "*") == 0)
+ host = NULL;
+
+ const struct addrinfo hints = {
+ .ai_flags = flags,
+ .ai_family = AF_UNSPEC,
+ .ai_socktype = socktype,
+ };
+
+ struct addrinfo *ai;
+ int ret = getaddrinfo(host, port, &hints, &ai);
+ g_free(p);
+ if (ret != 0) {
+ g_set_error(error_r, resolver_quark(), ret,
+ "Failed to look up '%s': %s",
+ host_port, gai_strerror(ret));
+ return NULL;
+ }
+
+ return ai;
+}
diff --git a/src/resolver.h b/src/resolver.h
new file mode 100644
index 000000000..e5ad06754
--- /dev/null
+++ b/src/resolver.h
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_RESOLVER_H
+#define MPD_RESOLVER_H
+
+#include <glib.h>
+
+struct sockaddr;
+struct addrinfo;
+
+G_GNUC_CONST
+static inline GQuark
+resolver_quark(void)
+{
+ return g_quark_from_static_string("resolver");
+}
+
+/**
+ * Converts the specified socket address into a string in the form
+ * "IP:PORT". The return value must be freed with g_free() when you
+ * don't need it anymore.
+ *
+ * @param sa the sockaddr struct
+ * @param length the length of #sa in bytes
+ * @param error location to store the error occurring, or NULL to
+ * ignore errors
+ */
+G_GNUC_MALLOC
+char *
+sockaddr_to_string(const struct sockaddr *sa, size_t length, GError **error);
+
+/**
+ * Resolve a specification in the form "host", "host:port",
+ * "[host]:port". This is a convenience wrapper for getaddrinfo().
+ *
+ * @param default_port a default port number that will be used if none
+ * is given in the string (if applicable); pass 0 to go without a
+ * default
+ * @return an #addrinfo linked list that must be freed with
+ * freeaddrinfo(), or NULL on error
+ */
+struct addrinfo *
+resolve_host_port(const char *host_port, unsigned default_port,
+ int flags, int socktype,
+ GError **error_r);
+
+#endif
diff --git a/src/riff.c b/src/riff.c
index 2e8648ff6..9ee916971 100644
--- a/src/riff.c
+++ b/src/riff.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -57,7 +57,7 @@ riff_seek_id3(FILE *file)
ret = fstat(fileno(file), &st);
if (ret < 0) {
g_warning("Failed to stat file descriptor: %s",
- strerror(errno));
+ g_strerror(errno));
return 0;
}
diff --git a/src/riff.h b/src/riff.h
index bfcb69a7d..7b35e092a 100644
--- a/src/riff.h
+++ b/src/riff.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/rtsp_client.c b/src/rtsp_client.c
new file mode 100644
index 000000000..ea993a163
--- /dev/null
+++ b/src/rtsp_client.c
@@ -0,0 +1,732 @@
+/*
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/*
+ * Based on the RTSP client by Shiro Ninomiya <shiron@snino.com>
+ */
+
+#include "config.h"
+#include "rtsp_client.h"
+#include "tcp_socket.h"
+#include "fd_util.h"
+#include "glib_compat.h"
+
+#include <assert.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <sys/time.h>
+
+#ifdef WIN32
+#include <ws2tcpip.h>
+#include <winsock.h>
+#else
+#include <arpa/inet.h>
+#include <sys/socket.h>
+#include <netdb.h>
+#endif
+
+/*
+ * Free all memory associated with key_data
+ */
+void
+free_kd(struct key_data *kd)
+{
+ struct key_data *iter = kd;
+ while (iter) {
+ g_free(iter->key);
+ g_free(iter->data);
+ iter = iter->next;
+ g_free(kd);
+ kd = iter;
+ }
+}
+
+/*
+ * key_data type data look up
+ */
+char *
+kd_lookup(struct key_data *kd, const char *key)
+{
+ while (kd) {
+ if (!strcmp(kd->key, key)) {
+ return kd->data;
+ }
+ kd = kd->next;
+ }
+ return NULL;
+}
+
+struct rtspcl_data *
+rtspcl_open(void)
+{
+ struct rtspcl_data *rtspcld;
+ rtspcld = g_new0(struct rtspcl_data, 1);
+ rtspcld->mutex = g_mutex_new();
+ rtspcld->cond = g_cond_new();
+ rtspcld->received_lines = g_queue_new();
+ rtspcld->useragent = "RTSPClient";
+ return rtspcld;
+}
+
+/* bind an opened socket to specified hostname and port.
+ * if hostname=NULL, use INADDR_ANY.
+ * if *port=0, use dynamically assigned port
+ */
+static int bind_host(int sd, char *hostname, unsigned long ulAddr,
+ unsigned short *port, GError **error_r)
+{
+ struct sockaddr_in my_addr;
+ socklen_t nlen = sizeof(struct sockaddr);
+ struct hostent *h;
+
+ memset(&my_addr, 0, sizeof(my_addr));
+ /* use specified hostname */
+ if (hostname) {
+ /* get server IP address (no check if input is IP address or DNS name) */
+ h = gethostbyname(hostname);
+ if (h == NULL) {
+ if (strstr(hostname, "255.255.255.255") == hostname) {
+ my_addr.sin_addr.s_addr=-1;
+ } else {
+ if ((my_addr.sin_addr.s_addr = inet_addr(hostname)) == 0xFFFFFFFF) {
+ g_set_error(error_r, rtsp_client_quark(), 0,
+ "failed to resolve host '%s'",
+ hostname);
+ return -1;
+ }
+ }
+ my_addr.sin_family = AF_INET;
+ } else {
+ my_addr.sin_family = h->h_addrtype;
+ memcpy((char *) &my_addr.sin_addr.s_addr,
+ h->h_addr_list[0], h->h_length);
+ }
+ } else {
+ // if hostname=NULL, use INADDR_ANY
+ if (ulAddr)
+ my_addr.sin_addr.s_addr = ulAddr;
+ else
+ my_addr.sin_addr.s_addr = htonl(INADDR_ANY);
+ my_addr.sin_family = AF_INET;
+ }
+
+ /* bind a specified port */
+ my_addr.sin_port = htons(*port);
+
+ if (bind(sd, (struct sockaddr *) &my_addr, sizeof(my_addr)) < 0) {
+ g_set_error(error_r, rtsp_client_quark(), errno,
+ "failed to bind socket: %s",
+ g_strerror(errno));
+ return -1;
+ }
+
+ if (*port == 0) {
+ getsockname(sd, (struct sockaddr *) &my_addr, &nlen);
+ *port = ntohs(my_addr.sin_port);
+ }
+
+ return 0;
+}
+
+/*
+ * open tcp port
+ */
+static int
+open_tcp_socket(char *hostname, unsigned short *port,
+ GError **error_r)
+{
+ int sd;
+
+ /* socket creation */
+ sd = socket(AF_INET, SOCK_STREAM, 0);
+ if (sd < 0) {
+ g_set_error(error_r, rtsp_client_quark(), errno,
+ "failed to create TCP socket: %s",
+ g_strerror(errno));
+ return -1;
+ }
+ if (bind_host(sd, hostname, 0, port, error_r)) {
+ close_socket(sd);
+ return -1;
+ }
+
+ return sd;
+}
+
+static bool
+get_sockaddr_by_host(const char *host, short destport,
+ struct sockaddr_in *addr,
+ GError **error_r)
+{
+ struct hostent *h;
+
+ h = gethostbyname(host);
+ if (h) {
+ addr->sin_family = h->h_addrtype;
+ memcpy((char *) &addr->sin_addr.s_addr, h->h_addr_list[0], h->h_length);
+ } else {
+ addr->sin_family = AF_INET;
+ if ((addr->sin_addr.s_addr=inet_addr(host))==0xFFFFFFFF) {
+ g_set_error(error_r, rtsp_client_quark(), 0,
+ "failed to resolve host '%s'", host);
+ return false;
+ }
+ }
+ addr->sin_port = htons(destport);
+ return true;
+}
+
+/*
+ * create tcp connection
+ * as long as the socket is not non-blocking, this can block the process
+ * nsport is network byte order
+ */
+static bool
+get_tcp_connect(int sd, struct sockaddr_in dest_addr, GError **error_r)
+{
+ if (connect(sd, (struct sockaddr *)&dest_addr, sizeof(struct sockaddr))){
+ g_usleep(100000);
+ // try one more time
+ if (connect(sd, (struct sockaddr *)&dest_addr, sizeof(struct sockaddr))) {
+ g_set_error(error_r, rtsp_client_quark(), errno,
+ "failed to connect to %s:%d: %s",
+ inet_ntoa(dest_addr.sin_addr),
+ ntohs(dest_addr.sin_port),
+ g_strerror(errno));
+ return false;
+ }
+ }
+ return true;
+}
+
+static bool
+get_tcp_connect_by_host(int sd, const char *host, short destport,
+ GError **error_r)
+{
+ struct sockaddr_in addr;
+
+ return get_sockaddr_by_host(host, destport, &addr, error_r) &&
+ get_tcp_connect(sd, addr, error_r);
+}
+
+static void
+rtsp_client_flush_received(struct rtspcl_data *rtspcld)
+{
+ char *line;
+ while ((line = g_queue_pop_head(rtspcld->received_lines)) != NULL)
+ g_free(line);
+}
+
+static size_t
+rtsp_client_socket_data(const void *_data, size_t length, void *ctx)
+{
+ struct rtspcl_data *rtspcld = ctx;
+
+ g_mutex_lock(rtspcld->mutex);
+
+ if (rtspcld->tcp_socket == NULL) {
+ g_mutex_unlock(rtspcld->mutex);
+ return 0;
+ }
+
+ const bool was_empty = g_queue_is_empty(rtspcld->received_lines);
+ bool added = false;
+ const char *data = _data, *end = data + length, *p = data, *eol;
+ while ((eol = memchr(p, '\n', end - p)) != NULL) {
+ const char *next = eol + 1;
+
+ if (rtspcld->received_lines->length < 64) {
+ if (eol > p && eol[-1] == '\r')
+ --eol;
+
+ g_queue_push_tail(rtspcld->received_lines,
+ g_strndup(p, eol - p));
+ added = true;
+ }
+
+ p = next;
+ }
+
+ if (was_empty && added)
+ g_cond_broadcast(rtspcld->cond);
+
+ g_mutex_unlock(rtspcld->mutex);
+
+ return p - data;
+}
+
+static void
+rtsp_client_socket_error(GError *error, void *ctx)
+{
+ struct rtspcl_data *rtspcld = ctx;
+
+ g_warning("%s", error->message);
+ g_error_free(error);
+
+ g_mutex_lock(rtspcld->mutex);
+
+ rtsp_client_flush_received(rtspcld);
+
+ struct tcp_socket *s = rtspcld->tcp_socket;
+ rtspcld->tcp_socket = NULL;
+
+ g_cond_broadcast(rtspcld->cond);
+
+ g_mutex_unlock(rtspcld->mutex);
+
+ if (s != NULL)
+ tcp_socket_free(s);
+}
+
+static void
+rtsp_client_socket_disconnected(void *ctx)
+{
+ struct rtspcl_data *rtspcld = ctx;
+
+ g_mutex_lock(rtspcld->mutex);
+
+ rtsp_client_flush_received(rtspcld);
+
+ struct tcp_socket *s = rtspcld->tcp_socket;
+ rtspcld->tcp_socket = NULL;
+
+ g_cond_broadcast(rtspcld->cond);
+
+ g_mutex_unlock(rtspcld->mutex);
+
+ if (s != NULL)
+ tcp_socket_free(s);
+}
+
+static const struct tcp_socket_handler rtsp_client_socket_handler = {
+ .data = rtsp_client_socket_data,
+ .error = rtsp_client_socket_error,
+ .disconnected = rtsp_client_socket_disconnected,
+};
+
+bool
+rtspcl_connect(struct rtspcl_data *rtspcld, const char *host, short destport,
+ const char *sid, GError **error_r)
+{
+ assert(rtspcld->tcp_socket == NULL);
+
+ unsigned short myport = 0;
+ struct sockaddr_in name;
+ socklen_t namelen = sizeof(name);
+
+ int fd = open_tcp_socket(NULL, &myport, error_r);
+ if (fd < 0)
+ return false;
+
+ if (!get_tcp_connect_by_host(fd, host, destport, error_r))
+ return false;
+
+ getsockname(fd, (struct sockaddr*)&name, &namelen);
+ memcpy(&rtspcld->local_addr, &name.sin_addr,sizeof(struct in_addr));
+ sprintf(rtspcld->url, "rtsp://%s/%s", inet_ntoa(name.sin_addr), sid);
+ getpeername(fd, (struct sockaddr*)&name, &namelen);
+ memcpy(&rtspcld->host_addr, &name.sin_addr, sizeof(struct in_addr));
+
+ rtspcld->tcp_socket = tcp_socket_new(fd, &rtsp_client_socket_handler,
+ rtspcld);
+
+ return true;
+}
+
+static void
+rtspcl_disconnect(struct rtspcl_data *rtspcld)
+{
+ g_mutex_lock(rtspcld->mutex);
+ rtsp_client_flush_received(rtspcld);
+ g_mutex_unlock(rtspcld->mutex);
+
+ if (rtspcld->tcp_socket != NULL) {
+ tcp_socket_free(rtspcld->tcp_socket);
+ rtspcld->tcp_socket = NULL;
+ }
+}
+
+static void
+rtspcl_remove_all_exthds(struct rtspcl_data *rtspcld)
+{
+ free_kd(rtspcld->exthds);
+ rtspcld->exthds = NULL;
+}
+
+void
+rtspcl_close(struct rtspcl_data *rtspcld)
+{
+ rtspcl_disconnect(rtspcld);
+ g_queue_free(rtspcld->received_lines);
+ rtspcl_remove_all_exthds(rtspcld);
+ g_free(rtspcld->session);
+ g_cond_free(rtspcld->cond);
+ g_mutex_free(rtspcld->mutex);
+ g_free(rtspcld);
+}
+
+void
+rtspcl_add_exthds(struct rtspcl_data *rtspcld, const char *key, char *data)
+{
+ struct key_data *new_kd;
+ new_kd = g_new(struct key_data, 1);
+ new_kd->key = g_strdup(key);
+ new_kd->data = g_strdup(data);
+ new_kd->next = NULL;
+ if (!rtspcld->exthds) {
+ rtspcld->exthds = new_kd;
+ } else {
+ struct key_data *iter = rtspcld->exthds;
+ while (iter->next) {
+ iter = iter->next;
+ }
+ iter->next = new_kd;
+ }
+}
+
+/*
+ * read one line from the file descriptor
+ * timeout: msec unit, -1 for infinite
+ * if CR comes then following LF is expected
+ * returned string in line is always null terminated, maxlen-1 is maximum string length
+ */
+static int
+read_line(struct rtspcl_data *rtspcld, char *line, int maxlen,
+ int timeout)
+{
+ g_mutex_lock(rtspcld->mutex);
+
+ GTimeVal end_time;
+ if (timeout >= 0) {
+ g_get_current_time(&end_time);
+
+ end_time.tv_sec += timeout / 1000;
+ timeout %= 1000;
+ end_time.tv_usec = timeout * 1000;
+ if (end_time.tv_usec > 1000000) {
+ end_time.tv_usec -= 1000000;
+ ++end_time.tv_sec;
+ }
+ }
+
+ while (true) {
+ if (!g_queue_is_empty(rtspcld->received_lines)) {
+ /* success, copy to buffer */
+
+ char *p = g_queue_pop_head(rtspcld->received_lines);
+ g_mutex_unlock(rtspcld->mutex);
+
+ g_strlcpy(line, p, maxlen);
+ g_free(p);
+
+ return strlen(line);
+ }
+
+ if (rtspcld->tcp_socket == NULL) {
+ /* error */
+ g_mutex_unlock(rtspcld->mutex);
+ return -1;
+ }
+
+ if (timeout < 0) {
+ g_cond_wait(rtspcld->cond, rtspcld->mutex);
+ } else if (!g_cond_timed_wait(rtspcld->cond, rtspcld->mutex,
+ &end_time)) {
+ g_mutex_unlock(rtspcld->mutex);
+ return 0;
+ }
+ }
+}
+
+/*
+ * send RTSP request, and get response if it's needed
+ * if this gets a success, *kd is allocated or reallocated (if *kd is not NULL)
+ */
+bool
+exec_request(struct rtspcl_data *rtspcld, const char *cmd,
+ const char *content_type, const char *content,
+ int get_response,
+ const struct key_data *hds, struct key_data **kd,
+ GError **error_r)
+{
+ char line[1024];
+ char req[1024];
+ char reql[128];
+ const char delimiters[] = " ";
+ char *token, *dp;
+ int dsize = 0;
+ int timeout = 5000; // msec unit
+
+ if (!rtspcld) {
+ g_set_error_literal(error_r, rtsp_client_quark(), 0,
+ "not connected");
+ return false;
+ }
+
+ sprintf(req, "%s %s RTSP/1.0\r\nCSeq: %d\r\n", cmd, rtspcld->url, ++rtspcld->cseq );
+
+ if ( rtspcld->session != NULL ) {
+ sprintf(reql,"Session: %s\r\n", rtspcld->session );
+ g_strlcat(req, reql, sizeof(req));
+ }
+
+ const struct key_data *hd_iter = hds;
+ while (hd_iter) {
+ sprintf(reql, "%s: %s\r\n", hd_iter->key, hd_iter->data);
+ g_strlcat(req, reql, sizeof(req));
+ hd_iter = hd_iter->next;
+ }
+
+ if (content_type && content) {
+ sprintf(reql, "Content-Type: %s\r\nContent-Length: %d\r\n",
+ content_type, (int) strlen(content));
+ g_strlcat(req, reql, sizeof(req));
+ }
+
+ sprintf(reql, "User-Agent: %s\r\n", rtspcld->useragent);
+ g_strlcat(req, reql, sizeof(req));
+
+ hd_iter = rtspcld->exthds;
+ while (hd_iter) {
+ sprintf(reql, "%s: %s\r\n", hd_iter->key, hd_iter->data);
+ g_strlcat(req, reql, sizeof(req));
+ hd_iter = hd_iter->next;
+ }
+ g_strlcat(req, "\r\n", sizeof(req));
+
+ if (content_type && content)
+ g_strlcat(req, content, sizeof(req));
+
+ if (!tcp_socket_send(rtspcld->tcp_socket, req, strlen(req))) {
+ g_set_error(error_r, rtsp_client_quark(), errno,
+ "write error: %s",
+ g_strerror(errno));
+ return false;
+ }
+
+ if (!get_response) return true;
+
+ if (read_line(rtspcld, line, sizeof(line), timeout) <= 0) {
+ g_set_error_literal(error_r, rtsp_client_quark(), 0,
+ "request failed");
+ return false;
+ }
+
+ token = strtok(line, delimiters);
+ token = strtok(NULL, delimiters);
+ if (token == NULL) {
+ g_set_error_literal(error_r, rtsp_client_quark(), 0,
+ "request failed");
+ return false;
+ }
+
+ if (strcmp(token, "200") != 0) {
+ g_set_error(error_r, rtsp_client_quark(), 0,
+ "request failed: %s", token);
+ return false;
+ }
+
+ /* if the caller isn't interested in response headers, put
+ them on the trash, which is freed before returning from
+ this function */
+ struct key_data *trash = NULL;
+ if (kd == NULL)
+ kd = &trash;
+
+ struct key_data *cur_kd = *kd;
+
+ struct key_data *new_kd = NULL;
+ while (read_line(rtspcld, line, sizeof(line), timeout) > 0) {
+ timeout = 1000; // once it started, it shouldn't take a long time
+ if (new_kd != NULL && line[0] == ' ') {
+ const char *j = line;
+ while (*j == ' ')
+ ++j;
+
+ dsize += strlen(j);
+ new_kd->data = g_realloc(new_kd->data, dsize);
+ strcat(new_kd->data, j);
+ continue;
+ }
+ dp = strstr(line, ":");
+ if (!dp) {
+ free_kd(*kd);
+ *kd = NULL;
+
+ g_set_error_literal(error_r, rtsp_client_quark(), 0,
+ "request failed, bad header");
+ return false;
+ }
+
+ *dp++ = 0;
+ new_kd = g_new(struct key_data, 1);
+ new_kd->key = g_strdup(line);
+ dsize = strlen(dp) + 1;
+ new_kd->data = g_strdup(dp);
+ new_kd->next = NULL;
+ if (cur_kd == NULL) {
+ cur_kd = *kd = new_kd;
+ } else {
+ cur_kd->next = new_kd;
+ cur_kd = new_kd;
+ }
+ }
+
+ free_kd(trash);
+
+ return true;
+}
+
+bool
+rtspcl_set_parameter(struct rtspcl_data *rtspcld, const char *parameter,
+ GError **error_r)
+{
+ return exec_request(rtspcld, "SET_PARAMETER", "text/parameters",
+ parameter, 1, NULL, NULL, error_r);
+}
+
+void
+rtspcl_set_useragent(struct rtspcl_data *rtspcld, const char *name)
+{
+ rtspcld->useragent = name;
+}
+
+bool
+rtspcl_announce_sdp(struct rtspcl_data *rtspcld, const char *sdp,
+ GError **error_r)
+{
+ return exec_request(rtspcld, "ANNOUNCE", "application/sdp", sdp, 1,
+ NULL, NULL, error_r);
+}
+
+bool
+rtspcl_setup(struct rtspcl_data *rtspcld, struct key_data **kd,
+ int control_port, int ntp_port,
+ GError **error_r)
+{
+ struct key_data *rkd = NULL, hds;
+ const char delimiters[] = ";";
+ char *buf = NULL;
+ char *token, *pc;
+ int rval = false;
+
+ static char transport_key[] = "Transport";
+
+ char transport_value[256];
+ snprintf(transport_value, sizeof(transport_value),
+ "RTP/AVP/UDP;unicast;interleaved=0-1;mode=record;control_port=%d;timing_port=%d",
+ control_port, ntp_port);
+
+ hds.key = transport_key;
+ hds.data = transport_value;
+ hds.next = NULL;
+ if (!exec_request(rtspcld, "SETUP", NULL, NULL, 1,
+ &hds, &rkd, error_r))
+ return false;
+
+ if (!(rtspcld->session = g_strdup(kd_lookup(rkd, "Session")))) {
+ g_set_error_literal(error_r, rtsp_client_quark(), 0,
+ "no session in response");
+ goto erexit;
+ }
+ if (!(rtspcld->transport = kd_lookup(rkd, "Transport"))) {
+ g_set_error_literal(error_r, rtsp_client_quark(), 0,
+ "no transport in response");
+ goto erexit;
+ }
+ buf = g_strdup(rtspcld->transport);
+ token = strtok(buf, delimiters);
+ rtspcld->server_port = 0;
+ rtspcld->control_port = 0;
+ while (token) {
+ if ((pc = strstr(token, "="))) {
+ *pc = 0;
+ if (!strcmp(token,"server_port")) {
+ rtspcld->server_port=atoi(pc + 1);
+ }
+ if (!strcmp(token,"control_port")) {
+ rtspcld->control_port=atoi(pc + 1);
+ }
+ }
+ token = strtok(NULL, delimiters);
+ }
+ if (rtspcld->server_port == 0) {
+ g_set_error_literal(error_r, rtsp_client_quark(), 0,
+ "no server_port in response");
+ goto erexit;
+ }
+ if (rtspcld->control_port == 0) {
+ g_set_error_literal(error_r, rtsp_client_quark(), 0,
+ "no control_port in response");
+ goto erexit;
+ }
+ rval = true;
+ erexit:
+ g_free(buf);
+
+ if (!rval || kd == NULL) {
+ free_kd(rkd);
+ rkd = NULL;
+ }
+
+ if (kd != NULL)
+ *kd = rkd;
+
+ return rval;
+}
+
+bool
+rtspcl_record(struct rtspcl_data *rtspcld,
+ int seq_num, int rtptime,
+ GError **error_r)
+{
+ if (!rtspcld->session) {
+ g_set_error_literal(error_r, rtsp_client_quark(), 0,
+ "no session in progress");
+ return false;
+ }
+
+ char buf[128];
+ sprintf(buf, "seq=%d,rtptime=%u", seq_num, rtptime);
+
+ struct key_data rtp;
+ static char rtp_key[] = "RTP-Info";
+ rtp.key = rtp_key;
+ rtp.data = buf;
+ rtp.next = NULL;
+
+ struct key_data range;
+ static char range_key[] = "Range";
+ range.key = range_key;
+ static char range_value[] = "npt=0-";
+ range.data = range_value;
+ range.next = &rtp;
+
+ return exec_request(rtspcld, "RECORD", NULL, NULL, 1, &range,
+ NULL, error_r);
+}
+
+char *
+rtspcl_local_ip(struct rtspcl_data *rtspcld)
+{
+ return inet_ntoa(rtspcld->local_addr);
+}
diff --git a/src/rtsp_client.h b/src/rtsp_client.h
new file mode 100644
index 000000000..21660e609
--- /dev/null
+++ b/src/rtsp_client.h
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/*
+ * Based on the RTSP client by Shiro Ninomiya <shiron@snino.com>
+ */
+
+#ifndef MPD_RTSP_CLIENT_H
+#define MPD_RTSP_CLIENT_H
+
+#include <stdbool.h>
+#include <glib.h>
+
+#ifdef WIN32
+#include <ws2tcpip.h>
+#include <winsock.h>
+#else
+#include <netinet/in.h>
+#endif
+
+struct key_data {
+ char *key;
+ char *data;
+ struct key_data *next;
+};
+
+struct rtspcl_data {
+ GMutex *mutex;
+ GCond *cond;
+
+ GQueue *received_lines;
+
+ struct tcp_socket *tcp_socket;
+
+ char url[128];
+ int cseq;
+ struct key_data *exthds;
+ char *session;
+ char *transport;
+ unsigned short server_port;
+ unsigned short control_port;
+ struct in_addr host_addr;
+ struct in_addr local_addr;
+ const char *useragent;
+
+};
+
+/**
+ * The quark used for GError.domain.
+ */
+static inline GQuark
+rtsp_client_quark(void)
+{
+ return g_quark_from_static_string("rtsp_client");
+}
+
+void
+free_kd(struct key_data *kd);
+
+char *
+kd_lookup(struct key_data *kd, const char *key);
+
+G_GNUC_MALLOC
+struct rtspcl_data *
+rtspcl_open(void);
+
+bool
+rtspcl_connect(struct rtspcl_data *rtspcld, const char *host, short destport,
+ const char *sid, GError **error_r);
+
+void
+rtspcl_close(struct rtspcl_data *rtspcld);
+
+void
+rtspcl_add_exthds(struct rtspcl_data *rtspcld, const char *key, char *data);
+
+bool
+exec_request(struct rtspcl_data *rtspcld, const char *cmd,
+ const char *content_type, const char *content,
+ int get_response,
+ const struct key_data *hds, struct key_data **kd,
+ GError **error_r);
+
+bool
+rtspcl_set_parameter(struct rtspcl_data *rtspcld, const char *parameter,
+ GError **error_r);
+
+void
+rtspcl_set_useragent(struct rtspcl_data *rtspcld, const char *name);
+
+bool
+rtspcl_announce_sdp(struct rtspcl_data *rtspcld, const char *sdp,
+ GError **error_r);
+
+bool
+rtspcl_setup(struct rtspcl_data *rtspcld, struct key_data **kd,
+ int control_port, int ntp_port,
+ GError **error_r);
+
+bool
+rtspcl_record(struct rtspcl_data *rtspcld,
+ int seq_num, int rtptime,
+ GError **error_r);
+
+char *
+rtspcl_local_ip(struct rtspcl_data *rtspcld);
+
+#endif
diff --git a/src/server_socket.c b/src/server_socket.c
index 482e0cda1..e4b5e3ece 100644
--- a/src/server_socket.c
+++ b/src/server_socket.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -18,8 +18,14 @@
*/
#include "config.h"
+
+#ifdef HAVE_STRUCT_UCRED
+#define _GNU_SOURCE 1
+#endif
+
#include "server_socket.h"
#include "socket_util.h"
+#include "resolver.h"
#include "fd_util.h"
#include "glib_compat.h"
#include "glib_socket.h"
@@ -155,16 +161,35 @@ server_socket_in_event(G_GNUC_UNUSED GIOChannel *source,
size_t address_length = sizeof(address);
int fd = accept_cloexec_nonblock(s->fd, (struct sockaddr*)&address,
&address_length);
- if (fd >= 0)
+ if (fd >= 0) {
+ if (socket_keepalive(fd))
+ g_warning("Could not set TCP keepalive option: %s",
+ g_strerror(errno));
s->parent->callback(fd, (const struct sockaddr*)&address,
address_length, get_remote_uid(fd),
s->parent->callback_ctx);
- else
+ } else {
g_warning("accept() failed: %s", g_strerror(errno));
+ }
return true;
}
+static void
+set_fd(struct one_socket *s, int fd)
+{
+ assert(s != NULL);
+ assert(s->fd < 0);
+ assert(fd >= 0);
+
+ s->fd = fd;
+
+ GIOChannel *channel = g_io_channel_new_socket(s->fd);
+ s->source_id = g_io_add_watch(channel, G_IO_IN,
+ server_socket_in_event, s);
+ g_io_channel_unref(channel);
+}
+
bool
server_socket_open(struct server_socket *ss, GError **error_r)
{
@@ -183,10 +208,11 @@ server_socket_open(struct server_socket *ss, GError **error_r)
}
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) {
+ int fd = socket_bind_listen(s->address.sa_family,
+ SOCK_STREAM, 0,
+ &s->address, s->address_length, 5,
+ &error);
+ if (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);
@@ -218,10 +244,7 @@ server_socket_open(struct server_socket *ss, GError **error_r)
/* register in the GLib main loop */
- GIOChannel *channel = g_io_channel_new_socket(s->fd);
- s->source_id = g_io_add_watch(channel, G_IO_IN,
- server_socket_in_event, s);
- g_io_channel_unref(channel);
+ set_fd(s, fd);
/* mark this socket as "good", and clear previous
errors */
@@ -276,6 +299,36 @@ one_socket_new(unsigned serial, const struct sockaddr *address,
return s;
}
+bool
+server_socket_add_fd(struct server_socket *ss, int fd, GError **error_r)
+{
+ assert(ss != NULL);
+ assert(ss->sockets_tail_r != NULL);
+ assert(*ss->sockets_tail_r == NULL);
+ assert(fd >= 0);
+
+ struct sockaddr_storage address;
+ socklen_t address_length;
+ if (getsockname(fd, (struct sockaddr *)&address,
+ &address_length) < 0) {
+ g_set_error(error_r, server_socket_quark(), errno,
+ "Failed to get socket address: %s",
+ g_strerror(errno));
+ return false;
+ }
+
+ struct one_socket *s = one_socket_new(ss->next_serial,
+ (struct sockaddr *)&address,
+ address_length);
+ s->parent = ss;
+ *ss->sockets_tail_r = s;
+ ss->sockets_tail_r = &s->next;
+
+ set_fd(s, fd);
+
+ return true;
+}
+
static struct one_socket *
server_socket_add_address(struct server_socket *ss,
const struct sockaddr *address,
@@ -369,24 +422,11 @@ 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));
+ struct addrinfo *ai = resolve_host_port(hostname, port,
+ AI_PASSIVE, SOCK_STREAM,
+ error_r);
+ if (ai == NULL)
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);
diff --git a/src/server_socket.h b/src/server_socket.h
index ae0ce0c8d..7caa4bbf2 100644
--- a/src/server_socket.h
+++ b/src/server_socket.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -44,10 +44,18 @@ void
server_socket_close(struct server_socket *ss);
/**
+ * Add a socket descriptor that is accepting connections. After this
+ * has been called, don't call server_socket_open(), because the
+ * socket is already open.
+ */
+bool
+server_socket_add_fd(struct server_socket *ss, int fd, GError **error_r);
+
+/**
* 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
+ * @param error_r location to store the error occurring, or NULL to
* ignore errors
* @return true on success
*/
@@ -61,7 +69,7 @@ server_socket_add_port(struct server_socket *ss, unsigned port,
*
* @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
+ * @param error_r location to store the error occurring, or NULL to
* ignore errors
* @return true on success
*/
@@ -73,7 +81,7 @@ server_socket_add_host(struct server_socket *ss, const char *hostname,
* 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
+ * @param error_r location to store the error occurring, or NULL to
* ignore errors
* @return true on success
*/
diff --git a/src/sig_handlers.c b/src/sig_handlers.c
index 8aa85cf88..b23f9e778 100644
--- a/src/sig_handlers.c
+++ b/src/sig_handlers.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/sig_handlers.h b/src/sig_handlers.h
index a578cd243..32e9bad95 100644
--- a/src/sig_handlers.h
+++ b/src/sig_handlers.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/socket_util.c b/src/socket_util.c
index d1651066f..a06a0cbd5 100644
--- a/src/socket_util.c
+++ b/src/socket_util.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -26,7 +26,6 @@
#ifndef G_OS_WIN32
#include <sys/socket.h>
-#include <netdb.h>
#else /* G_OS_WIN32 */
#include <ws2tcpip.h>
#include <winsock.h>
@@ -42,55 +41,6 @@ listen_quark(void)
return g_quark_from_static_string("listen");
}
-char *
-sockaddr_to_string(const struct sockaddr *sa, size_t length, GError **error)
-{
-#if defined(HAVE_IPV6) && defined(IN6_IS_ADDR_V4MAPPED)
- const struct sockaddr_in6 *a6 = (const struct sockaddr_in6 *)sa;
- struct sockaddr_in a4;
-#endif
- int ret;
- char host[NI_MAXHOST], serv[NI_MAXSERV];
-
-#if defined(HAVE_IPV6) && defined(IN6_IS_ADDR_V4MAPPED)
- if (sa->sa_family == AF_INET6 &&
- IN6_IS_ADDR_V4MAPPED(&a6->sin6_addr)) {
- /* convert "::ffff:127.0.0.1" to "127.0.0.1" */
-
- memset(&a4, 0, sizeof(a4));
- a4.sin_family = AF_INET;
- memcpy(&a4.sin_addr, ((const char *)&a6->sin6_addr) + 12,
- sizeof(a4.sin_addr));
- a4.sin_port = a6->sin6_port;
-
- sa = (const struct sockaddr *)&a4;
- length = sizeof(a4);
- }
-#endif
-
- ret = getnameinfo(sa, length, host, sizeof(host), serv, sizeof(serv),
- NI_NUMERICHOST|NI_NUMERICSERV);
- if (ret != 0) {
- g_set_error(error, g_quark_from_static_string("netdb"), ret,
- "%s", gai_strerror(ret));
- return NULL;
- }
-
-#ifdef HAVE_UN
- if (sa->sa_family == AF_UNIX)
- /* "serv" contains corrupt information with unix
- sockets */
- return g_strdup(host);
-#endif
-
-#ifdef HAVE_IPV6
- if (strchr(host, ':') != NULL)
- return g_strconcat("[", host, "]:", serv, NULL);
-#endif
-
- return g_strconcat(host, ":", serv, NULL);
-}
-
int
socket_bind_listen(int domain, int type, int protocol,
const struct sockaddr *address, size_t address_length,
@@ -99,9 +49,6 @@ socket_bind_listen(int domain, int type, int protocol,
{
int fd, ret;
const int reuse = 1;
-#ifdef HAVE_STRUCT_UCRED
- int passcred = 1;
-#endif
fd = socket_cloexec_nonblock(domain, type, protocol);
if (fd < 0) {
@@ -110,14 +57,8 @@ socket_bind_listen(int domain, int type, int protocol,
return -1;
}
-#ifdef WIN32
- const char *optval = (const char *)&reuse;
-#else
- const void *optval = &reuse;
-#endif
-
ret = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR,
- optval, sizeof(reuse));
+ (const char *) &reuse, sizeof(reuse));
if (ret < 0) {
g_set_error(error, listen_quark(), errno,
"setsockopt() failed: %s", g_strerror(errno));
@@ -142,8 +83,18 @@ socket_bind_listen(int domain, int type, int protocol,
}
#ifdef HAVE_STRUCT_UCRED
- setsockopt(fd, SOL_SOCKET, SO_PASSCRED, &passcred, sizeof(passcred));
+ setsockopt(fd, SOL_SOCKET, SO_PASSCRED,
+ (const char *) &reuse, sizeof(reuse));
#endif
return fd;
}
+
+int
+socket_keepalive(int fd)
+{
+ const int reuse = 1;
+
+ return setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE,
+ (const char *)&reuse, sizeof(reuse));
+}
diff --git a/src/socket_util.h b/src/socket_util.h
index 7ef081362..93bd27362 100644
--- a/src/socket_util.h
+++ b/src/socket_util.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -31,19 +31,6 @@
struct sockaddr;
/**
- * Converts the specified socket address into a string in the form
- * "IP:PORT". The return value must be freed with g_free() when you
- * don't need it anymore.
- *
- * @param sa the sockaddr struct
- * @param length the length of #sa in bytes
- * @param error location to store the error occuring, or NULL to
- * ignore errors
- */
-char *
-sockaddr_to_string(const struct sockaddr *sa, size_t length, GError **error);
-
-/**
* Creates a socket listening on the specified address. This is a
* shortcut for socket(), bind() and listen().
*
@@ -53,7 +40,7 @@ sockaddr_to_string(const struct sockaddr *sa, size_t length, GError **error);
* @param address the address to listen on
* @param address_length the size of #address
* @param backlog the backlog parameter for the listen() system call
- * @param error location to store the error occuring, or NULL to
+ * @param error location to store the error occurring, or NULL to
* ignore errors
* @return the socket file descriptor or -1 on error
*/
@@ -63,4 +50,7 @@ socket_bind_listen(int domain, int type, int protocol,
int backlog,
GError **error);
+int
+socket_keepalive(int fd);
+
#endif
diff --git a/src/song.c b/src/song.c
index 13fd476b9..f5cc7c35a 100644
--- a/src/song.c
+++ b/src/song.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -61,6 +61,18 @@ song_file_new(const char *path, struct directory *parent)
return song_alloc(path, parent);
}
+struct song *
+song_replace_uri(struct song *old_song, const char *uri)
+{
+ struct song *new_song = song_alloc(uri, old_song->parent);
+ new_song->tag = old_song->tag;
+ new_song->mtime = old_song->mtime;
+ new_song->start_ms = old_song->start_ms;
+ new_song->end_ms = old_song->end_ms;
+ g_free(old_song);
+ return new_song;
+}
+
void
song_free(struct song *song)
{
diff --git a/src/song.h b/src/song.h
index 26a1dc806..8b97d45d0 100644
--- a/src/song.h
+++ b/src/song.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -20,6 +20,8 @@
#ifndef MPD_SONG_H
#define MPD_SONG_H
+#include "util/list.h"
+
#include <stddef.h>
#include <stdbool.h>
#include <sys/time.h>
@@ -28,6 +30,16 @@
#define SONG_TIME "Time: "
struct song {
+ /**
+ * Pointers to the siblings of this directory within the
+ * parent directory. It is unused (undefined) if this song is
+ * not in the database.
+ *
+ * This attribute is protected with the global #db_mutex.
+ * Read access in the update thread does not need protection.
+ */
+ struct list_head siblings;
+
struct tag *tag;
struct directory *parent;
time_t mtime;
@@ -62,6 +74,15 @@ song_file_new(const char *path, struct directory *parent);
struct song *
song_file_load(const char *path, struct directory *parent);
+/**
+ * Replaces the URI of a song object. The given song object is
+ * destroyed, and a newly allocated one is returned. It does not
+ * update the reference within the parent directory; the caller is
+ * responsible for doing that.
+ */
+struct song *
+song_replace_uri(struct song *song, const char *uri);
+
void
song_free(struct song *song);
diff --git a/src/song_print.c b/src/song_print.c
index 16239e03b..fb608a8b2 100644
--- a/src/song_print.c
+++ b/src/song_print.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -20,7 +20,6 @@
#include "config.h"
#include "song_print.h"
#include "song.h"
-#include "songvec.h"
#include "directory.h"
#include "tag_print.h"
#include "client.h"
@@ -94,18 +93,3 @@ song_print_info(struct client *client, struct song *song)
if (song->tag)
tag_print(client, song->tag);
}
-
-static int
-song_print_info_x(struct song *song, void *data)
-{
- struct client *client = data;
- song_print_info(client, song);
-
- return 0;
-}
-
-void
-songvec_print(struct client *client, const struct songvec *sv)
-{
- songvec_for_each(sv, song_print_info_x, client);
-}
diff --git a/src/song_print.h b/src/song_print.h
index cb83f4711..8f1f0cc65 100644
--- a/src/song_print.h
+++ b/src/song_print.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -28,9 +28,6 @@ void
song_print_info(struct client *client, struct song *song);
void
-songvec_print(struct client *client, const struct songvec *sv);
-
-void
song_print_uri(struct client *client, struct song *song);
#endif
diff --git a/src/song_save.c b/src/song_save.c
index a1a573298..4fcb46e22 100644
--- a/src/song_save.c
+++ b/src/song_save.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -24,6 +24,7 @@
#include "directory.h"
#include "tag.h"
#include "text_file.h"
+#include "string_util.h"
#include <glib.h>
@@ -58,19 +59,6 @@ song_save(FILE *fp, const struct song *song)
fprintf(fp, SONG_END "\n");
}
-static int
-song_save_callback(struct song *song, void *data)
-{
- FILE *fp = data;
- song_save(fp, song);
- return 0;
-}
-
-void songvec_save(FILE *fp, const struct songvec *sv)
-{
- songvec_for_each(sv, song_save_callback, fp);
-}
-
struct song *
song_load(FILE *fp, struct directory *parent, const char *uri,
GString *buffer, GError **error_r)
@@ -96,7 +84,7 @@ song_load(FILE *fp, struct directory *parent, const char *uri,
}
*colon++ = 0;
- value = g_strchug(colon);
+ value = strchug_fast_c(colon);
if ((type = tag_name_parse(line)) != TAG_NUM_OF_ITEM_TYPES) {
if (!song->tag) {
@@ -112,6 +100,13 @@ song_load(FILE *fp, struct directory *parent, const char *uri,
}
song->tag->time = atoi(value);
+ } else if (strcmp(line, "Playlist") == 0) {
+ if (!song->tag) {
+ song->tag = tag_new();
+ tag_begin_add(song->tag);
+ }
+
+ song->tag->has_playlist = strcmp(value, "yes") == 0;
} else if (strcmp(line, SONG_MTIME) == 0) {
song->mtime = atoi(value);
} else if (strcmp(line, "Range") == 0) {
diff --git a/src/song_save.h b/src/song_save.h
index 03285015a..f6ecbbfeb 100644
--- a/src/song_save.h
+++ b/src/song_save.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -27,20 +27,16 @@
#define SONG_BEGIN "song_begin: "
struct song;
-struct songvec;
struct directory;
void
song_save(FILE *fp, const struct song *song);
-void
-songvec_save(FILE *fp, const struct songvec *sv);
-
/**
* Loads a song from the input file. Reading stops after the
* "song_end" line.
*
- * @param error_r location to store the error occuring, or NULL to
+ * @param error_r location to store the error occurring, or NULL to
* ignore errors
* @return true on success, false on error
*/
diff --git a/src/songvec.c b/src/song_sort.c
index 38bcbac88..397d2c7a9 100644
--- a/src/songvec.c
+++ b/src/song_sort.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2012 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -18,18 +18,17 @@
*/
#include "config.h"
-#include "songvec.h"
+#include "song_sort.h"
#include "song.h"
+#include "util/list.h"
+#include "util/list_sort.h"
#include "tag.h"
#include <glib.h>
#include <assert.h>
-#include <string.h>
#include <stdlib.h>
-static GMutex *nr_lock = NULL;
-
static const char *
tag_get_value_checked(const struct tag *tag, enum tag_type type)
{
@@ -89,10 +88,11 @@ compare_tag_item(const struct tag *a, const struct tag *b, enum tag_type type)
}
/* Only used for sorting/searchin a songvec, not general purpose compares */
-static int songvec_cmp(const void *s1, const void *s2)
+static int
+song_cmp(G_GNUC_UNUSED void *priv, struct list_head *_a, struct list_head *_b)
{
- const struct song *a = ((const struct song * const *)s1)[0];
- const struct song *b = ((const struct song * const *)s2)[0];
+ const struct song *a = (const struct song *)_a;
+ const struct song *b = (const struct song *)_b;
int ret;
/* first sort by album */
@@ -114,117 +114,8 @@ static int songvec_cmp(const void *s1, const void *s2)
return g_utf8_collate(a->uri, b->uri);
}
-static size_t sv_size(const struct songvec *sv)
-{
- return sv->nr * sizeof(struct song *);
-}
-
-void songvec_init(void)
-{
- g_assert(nr_lock == NULL);
- nr_lock = g_mutex_new();
-}
-
-void songvec_deinit(void)
-{
- g_assert(nr_lock != NULL);
- g_mutex_free(nr_lock);
- nr_lock = NULL;
-}
-
-void songvec_sort(struct songvec *sv)
-{
- g_mutex_lock(nr_lock);
- qsort(sv->base, sv->nr, sizeof(struct song *), songvec_cmp);
- g_mutex_unlock(nr_lock);
-}
-
-struct song *
-songvec_find(const struct songvec *sv, const char *uri)
-{
- int i;
- struct song *ret = NULL;
-
- g_mutex_lock(nr_lock);
- for (i = sv->nr; --i >= 0; ) {
- if (strcmp(sv->base[i]->uri, uri))
- continue;
- ret = sv->base[i];
- break;
- }
- g_mutex_unlock(nr_lock);
- return ret;
-}
-
-int
-songvec_delete(struct songvec *sv, const struct song *del)
-{
- size_t i;
-
- g_mutex_lock(nr_lock);
- for (i = 0; i < sv->nr; ++i) {
- if (sv->base[i] != del)
- continue;
- /* we _don't_ call song_free() here */
- if (!--sv->nr) {
- g_free(sv->base);
- sv->base = NULL;
- } else {
- memmove(&sv->base[i], &sv->base[i + 1],
- (sv->nr - i) * sizeof(struct song *));
- sv->base = g_realloc(sv->base, sv_size(sv));
- }
- g_mutex_unlock(nr_lock);
- return i;
- }
- g_mutex_unlock(nr_lock);
-
- return -1; /* not found */
-}
-
void
-songvec_add(struct songvec *sv, struct song *add)
-{
- g_mutex_lock(nr_lock);
- ++sv->nr;
- sv->base = g_realloc(sv->base, sv_size(sv));
- sv->base[sv->nr - 1] = add;
- g_mutex_unlock(nr_lock);
-}
-
-void songvec_destroy(struct songvec *sv)
-{
- g_mutex_lock(nr_lock);
- sv->nr = 0;
- g_mutex_unlock(nr_lock);
-
- g_free(sv->base);
- sv->base = NULL;
-}
-
-int
-songvec_for_each(const struct songvec *sv,
- int (*fn)(struct song *, void *), void *arg)
+song_list_sort(struct list_head *songs)
{
- size_t i;
- size_t prev_nr;
-
- g_mutex_lock(nr_lock);
- for (i = 0; i < sv->nr; ) {
- struct song *song = sv->base[i];
-
- assert(song);
- assert(*song->uri);
-
- prev_nr = sv->nr;
- g_mutex_unlock(nr_lock); /* fn() may block */
- if (fn(song, arg) < 0)
- return -1;
- g_mutex_lock(nr_lock); /* sv->nr may change in fn() */
- if (prev_nr == sv->nr)
- ++i;
- }
- g_mutex_unlock(nr_lock);
-
- return 0;
+ list_sort(NULL, songs, song_cmp);
}
diff --git a/src/directory_print.h b/src/song_sort.h
index 0933f5a97..ec124cf4a 100644
--- a/src/directory_print.h
+++ b/src/song_sort.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2012 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -17,13 +17,12 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
-#ifndef MPD_DIRECTORY_PRINT_H
-#define MPD_DIRECTORY_PRINT_H
+#ifndef MPD_SONG_SORT_H
+#define MPD_SONG_SORT_H
-struct client;
-struct directory;
+struct list_head;
void
-directory_print(struct client *client, const struct directory *directory);
+song_list_sort(struct list_head *songs);
#endif
diff --git a/src/song_sticker.c b/src/song_sticker.c
index c3c64c8d1..78025906e 100644
--- a/src/song_sticker.c
+++ b/src/song_sticker.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/song_sticker.h b/src/song_sticker.h
index 6318ccf48..20ae68ce9 100644
--- a/src/song_sticker.h
+++ b/src/song_sticker.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -68,6 +68,8 @@ sticker_song_get(const struct song *song);
* Finds stickers with the specified name below the specified
* directory.
*
+ * Caller must lock the #db_mutex.
+ *
* @param directory the base directory to search in
* @param name the name of the sticker
* @return true on success (even if no sticker was found), false on
diff --git a/src/song_update.c b/src/song_update.c
index b418b600e..37f502a20 100644
--- a/src/song_update.c
+++ b/src/song_update.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -27,6 +27,7 @@
#include "tag_ape.h"
#include "tag_id3.h"
#include "tag.h"
+#include "tag_handler.h"
#include "input_stream.h"
#include <glib.h>
@@ -65,33 +66,12 @@ song_file_load(const char *path, struct directory *parent)
/**
* Attempts to load APE or ID3 tags from the specified file.
*/
-static struct tag *
-tag_load_fallback(const char *path)
+static bool
+tag_scan_fallback(const char *path,
+ const struct tag_handler *handler, void *handler_ctx)
{
- struct tag *tag = tag_ape_load(path);
- if (tag == NULL)
- tag = tag_id3_load(path);
- return tag;
-}
-
-/**
- * The decoder plugin failed to load any tags: fall back to the APE or
- * ID3 tag loader.
- */
-static struct tag *
-tag_fallback(const char *path, struct tag *tag)
-{
- struct tag *fallback = tag_load_fallback(path);
-
- if (fallback != NULL) {
- /* tag was successfully loaded: copy the song
- duration, and destroy the old (empty) tag */
- fallback->time = tag->time;
- tag_free(tag);
- return fallback;
- } else
- /* no APE/ID3 tag found: return the empty tag */
- return tag;
+ return tag_ape_scan2(path, handler, handler_ctx) ||
+ tag_id3_scan(path, handler, handler_ctx);
}
bool
@@ -131,27 +111,47 @@ song_file_update(struct song *song)
song->mtime = st.st_mtime;
+ GMutex *mutex = NULL;
+ GCond *cond;
+#if !GCC_CHECK_VERSION(4, 2)
+ /* work around "may be used uninitialized in this function"
+ false positive */
+ cond = NULL;
+#endif
+
do {
/* load file tag */
- song->tag = decoder_plugin_tag_dup(plugin, path_fs);
- if (song->tag != NULL)
+ song->tag = tag_new();
+ if (decoder_plugin_scan_file(plugin, path_fs,
+ &full_tag_handler, song->tag))
break;
+ tag_free(song->tag);
+ song->tag = NULL;
+
/* fall back to stream tag */
- if (plugin->stream_tag != NULL) {
+ if (plugin->scan_stream != NULL) {
/* open the input_stream (if not already
open) */
- if (is == NULL)
- is = input_stream_open(path_fs, NULL);
+ if (is == NULL) {
+ mutex = g_mutex_new();
+ cond = g_cond_new();
+ is = input_stream_open(path_fs, mutex, cond,
+ NULL);
+ }
/* now try the stream_tag() method */
if (is != NULL) {
- song->tag = decoder_plugin_stream_tag(plugin,
- is);
- if (song->tag != NULL)
+ song->tag = tag_new();
+ if (decoder_plugin_scan_stream(plugin, is,
+ &full_tag_handler,
+ song->tag))
break;
- input_stream_seek(is, 0, SEEK_SET, NULL);
+ tag_free(song->tag);
+ song->tag = NULL;
+
+ input_stream_lock_seek(is, 0, SEEK_SET, NULL);
}
}
@@ -161,8 +161,13 @@ song_file_update(struct song *song)
if (is != NULL)
input_stream_close(is);
+ if (mutex != NULL) {
+ g_cond_free(cond);
+ g_mutex_free(mutex);
+ }
+
if (song->tag != NULL && tag_is_empty(song->tag))
- song->tag = tag_fallback(path_fs, song->tag);
+ tag_scan_fallback(path_fs, &full_tag_handler, song->tag);
g_free(path_fs);
return song->tag != NULL;
@@ -190,7 +195,7 @@ song_file_update_inarchive(struct song *song)
tag_free(song->tag);
//accept every file that has music suffix
- //because we dont support tag reading throught
+ //because we don't support tag reading through
//input streams
song->tag = tag_new();
diff --git a/src/state_file.c b/src/state_file.c
index 55af25d5c..de7fa2d02 100644
--- a/src/state_file.c
+++ b/src/state_file.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -47,7 +47,7 @@ static unsigned prev_volume_version, prev_output_version,
prev_playlist_version;
static void
-state_file_write(void)
+state_file_write(struct player_control *pc)
{
FILE *fp;
@@ -58,23 +58,23 @@ state_file_write(void)
fp = fopen(state_file_path, "w");
if (G_UNLIKELY(!fp)) {
g_warning("failed to create %s: %s",
- state_file_path, strerror(errno));
+ state_file_path, g_strerror(errno));
return;
}
save_sw_volume_state(fp);
audio_output_state_save(fp);
- playlist_state_save(fp, &g_playlist);
+ playlist_state_save(fp, &g_playlist, pc);
fclose(fp);
prev_volume_version = sw_volume_state_get_hash();
prev_output_version = audio_output_state_get_version();
- prev_playlist_version = playlist_state_get_hash(&g_playlist);
+ prev_playlist_version = playlist_state_get_hash(&g_playlist, pc);
}
static void
-state_file_read(void)
+state_file_read(struct player_control *pc)
{
FILE *fp;
bool success;
@@ -86,7 +86,7 @@ state_file_read(void)
fp = fopen(state_file_path, "r");
if (G_UNLIKELY(!fp)) {
g_warning("failed to open %s: %s",
- state_file_path, strerror(errno));
+ state_file_path, g_strerror(errno));
return;
}
@@ -95,7 +95,8 @@ state_file_read(void)
while ((line = read_text_line(fp, buffer)) != NULL) {
success = read_sw_volume_state(line) ||
audio_output_state_read(line) ||
- playlist_state_restore(line, fp, buffer, &g_playlist);
+ playlist_state_restore(line, fp, buffer,
+ &g_playlist, pc);
if (!success)
g_warning("Unrecognized line in state file: %s", line);
}
@@ -104,7 +105,7 @@ state_file_read(void)
prev_volume_version = sw_volume_state_get_hash();
prev_output_version = audio_output_state_get_version();
- prev_playlist_version = playlist_state_get_hash(&g_playlist);
+ prev_playlist_version = playlist_state_get_hash(&g_playlist, pc);
g_string_free(buffer, true);
@@ -115,21 +116,23 @@ state_file_read(void)
* saves the state file.
*/
static gboolean
-timer_save_state_file(G_GNUC_UNUSED gpointer data)
+timer_save_state_file(gpointer data)
{
+ struct player_control *pc = data;
+
if (prev_volume_version == sw_volume_state_get_hash() &&
prev_output_version == audio_output_state_get_version() &&
- prev_playlist_version == playlist_state_get_hash(&g_playlist))
+ prev_playlist_version == playlist_state_get_hash(&g_playlist, pc))
/* nothing has changed - don't save the state file,
don't spin up the hard disk */
return true;
- state_file_write();
+ state_file_write(pc);
return true;
}
void
-state_file_init(const char *path)
+state_file_init(const char *path, struct player_control *pc)
{
assert(state_file_path == NULL);
@@ -137,15 +140,15 @@ state_file_init(const char *path)
return;
state_file_path = g_strdup(path);
- state_file_read();
+ state_file_read(pc);
save_state_source_id = g_timeout_add_seconds(5 * 60,
timer_save_state_file,
- NULL);
+ pc);
}
void
-state_file_finish(void)
+state_file_finish(struct player_control *pc)
{
if (state_file_path == NULL)
/* no state file configured, no cleanup required */
@@ -154,7 +157,7 @@ state_file_finish(void)
if (save_state_source_id != 0)
g_source_remove(save_state_source_id);
- state_file_write();
+ state_file_write(pc);
g_free(state_file_path);
}
diff --git a/src/state_file.h b/src/state_file.h
index ec01fcbed..4c4f881cc 100644
--- a/src/state_file.h
+++ b/src/state_file.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -20,11 +20,13 @@
#ifndef MPD_STATE_FILE_H
#define MPD_STATE_FILE_H
+struct player_control;
+
void
-state_file_init(const char *path);
+state_file_init(const char *path, struct player_control *pc);
void
-state_file_finish(void);
+state_file_finish(struct player_control *pc);
void write_state_file(void);
diff --git a/src/stats.c b/src/stats.c
index 718d8633a..fe6a064a6 100644
--- a/src/stats.c
+++ b/src/stats.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -20,11 +20,13 @@
#include "config.h"
#include "stats.h"
#include "database.h"
+#include "db_visitor.h"
#include "tag.h"
#include "song.h"
#include "client.h"
#include "player_control.h"
#include "strset.h"
+#include "client_internal.h"
struct stats stats;
@@ -67,8 +69,9 @@ visit_tag(struct visit_data *data, const struct tag *tag)
}
}
-static int
-stats_collect_song(struct song *song, void *_data)
+static bool
+collect_stats_song(struct song *song, void *_data,
+ G_GNUC_UNUSED GError **error_r)
{
struct visit_data *data = _data;
@@ -77,9 +80,13 @@ stats_collect_song(struct song *song, void *_data)
if (song->tag != NULL)
visit_tag(data, song->tag);
- return 0;
+ return true;
}
+static const struct db_visitor collect_stats_visitor = {
+ .song = collect_stats_song,
+};
+
void stats_update(void)
{
struct visit_data data;
@@ -91,7 +98,7 @@ void stats_update(void)
data.artists = strset_new();
data.albums = strset_new();
- db_walk(NULL, stats_collect_song, NULL, &data);
+ db_walk("", &collect_stats_visitor, &data, NULL);
stats.artist_count = strset_size(data.artists);
stats.album_count = strset_size(data.albums);
@@ -114,7 +121,7 @@ int stats_print(struct client *client)
stats.album_count,
stats.song_count,
(long)g_timer_elapsed(stats.timer, NULL),
- (long)(pc_get_total_play_time() + 0.5),
+ (long)(pc_get_total_play_time(client->player_control) + 0.5),
stats.song_duration,
(long)db_get_mtime());
return 0;
diff --git a/src/stats.h b/src/stats.h
index fbb2e4a46..a686477de 100644
--- a/src/stats.h
+++ b/src/stats.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/sticker.c b/src/sticker.c
index f6cd04346..346a827a5 100644
--- a/src/sticker.c
+++ b/src/sticker.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/sticker.h b/src/sticker.h
index 6cc0ebcee..5545206a5 100644
--- a/src/sticker.h
+++ b/src/sticker.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -51,7 +51,7 @@ struct sticker;
/**
* Opens the sticker database (if path is not NULL).
*
- * @param error_r location to store the error occuring, or NULL to
+ * @param error_r location to store the error occurring, or NULL to
* ignore errors
* @return true on success, false on error
*/
diff --git a/src/sticker_print.c b/src/sticker_print.c
index b158c8af3..65e79513c 100644
--- a/src/sticker_print.c
+++ b/src/sticker_print.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/sticker_print.h b/src/sticker_print.h
index ac542709c..7398c8083 100644
--- a/src/sticker_print.h
+++ b/src/sticker_print.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/stored_playlist.c b/src/stored_playlist.c
index cd2818522..39ba2bac1 100644
--- a/src/stored_playlist.c
+++ b/src/stored_playlist.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -20,6 +20,7 @@
#include "config.h"
#include "stored_playlist.h"
#include "playlist_save.h"
+#include "text_file.h"
#include "song.h"
#include "mapper.h"
#include "path.h"
@@ -27,6 +28,7 @@
#include "database.h"
#include "idle.h"
#include "conf.h"
+#include "glib_compat.h"
#include <assert.h>
#include <sys/types.h>
@@ -36,6 +38,8 @@
#include <string.h>
#include <errno.h>
+static const char PLAYLIST_COMMENT = '#';
+
static unsigned playlist_max_length;
bool playlist_saveAbsolutePaths = DEFAULT_PLAYLIST_SAVE_ABSOLUTE_PATHS;
@@ -69,6 +73,67 @@ spl_valid_name(const char *name_utf8)
strchr(name_utf8, '\r') == NULL;
}
+static const char *
+spl_map(GError **error_r)
+{
+ const char *path_fs = map_spl_path();
+ if (path_fs == NULL)
+ g_set_error_literal(error_r, playlist_quark(),
+ PLAYLIST_RESULT_DISABLED,
+ "Stored playlists are disabled");
+
+ return path_fs;
+}
+
+static bool
+spl_check_name(const char *name_utf8, GError **error_r)
+{
+ if (!spl_valid_name(name_utf8)) {
+ g_set_error_literal(error_r, playlist_quark(),
+ PLAYLIST_RESULT_BAD_NAME,
+ "Bad playlist name");
+ return false;
+ }
+
+ return true;
+}
+
+static char *
+spl_map_to_fs(const char *name_utf8, GError **error_r)
+{
+ if (spl_map(error_r) == NULL ||
+ !spl_check_name(name_utf8, error_r))
+ return NULL;
+
+ char *path_fs = map_spl_utf8_to_fs(name_utf8);
+ if (path_fs == NULL)
+ g_set_error_literal(error_r, playlist_quark(),
+ PLAYLIST_RESULT_BAD_NAME,
+ "Bad playlist name");
+
+ return path_fs;
+}
+
+/**
+ * Create a GError for the current errno.
+ */
+static void
+playlist_errno(GError **error_r)
+{
+ switch (errno) {
+ case ENOENT:
+ g_set_error_literal(error_r, playlist_quark(),
+ PLAYLIST_RESULT_NO_SUCH_LIST,
+ "No such playlist");
+ break;
+
+ default:
+ g_set_error_literal(error_r, g_file_error_quark(), errno,
+ g_strerror(errno));
+ break;
+ }
+}
+
static struct stored_playlist_info *
load_playlist_info(const char *parent_path_fs, const char *name_fs)
{
@@ -105,9 +170,9 @@ load_playlist_info(const char *parent_path_fs, const char *name_fs)
}
GPtrArray *
-spl_list(void)
+spl_list(GError **error_r)
{
- const char *parent_path_fs = map_spl_path();
+ const char *parent_path_fs = spl_map(error_r);
DIR *dir;
struct dirent *ent;
GPtrArray *list;
@@ -117,8 +182,11 @@ spl_list(void)
return NULL;
dir = opendir(parent_path_fs);
- if (dir == NULL)
+ if (dir == NULL) {
+ g_set_error_literal(error_r, g_file_error_quark(), errno,
+ g_strerror(errno));
return NULL;
+ }
list = g_ptr_array_new();
@@ -145,25 +213,26 @@ spl_list_free(GPtrArray *list)
g_ptr_array_free(list, true);
}
-static enum playlist_result
-spl_save(GPtrArray *list, const char *utf8path)
+static bool
+spl_save(GPtrArray *list, const char *utf8path, GError **error_r)
{
FILE *file;
- char *path_fs;
assert(utf8path != NULL);
- if (map_spl_path() == NULL)
- return PLAYLIST_RESULT_DISABLED;
+ if (spl_map(error_r) == NULL)
+ return false;
- path_fs = map_spl_utf8_to_fs(utf8path);
+ char *path_fs = spl_map_to_fs(utf8path, error_r);
if (path_fs == NULL)
- return PLAYLIST_RESULT_BAD_NAME;
+ return false;
file = fopen(path_fs, "w");
g_free(path_fs);
- if (file == NULL)
- return PLAYLIST_RESULT_ERRNO;
+ if (file == NULL) {
+ playlist_errno(error_r);
+ return false;
+ }
for (unsigned i = 0; i < list->len; ++i) {
const char *uri = g_ptr_array_index(list, i);
@@ -171,53 +240,46 @@ spl_save(GPtrArray *list, const char *utf8path)
}
fclose(file);
- return PLAYLIST_RESULT_SUCCESS;
+ return true;
}
GPtrArray *
-spl_load(const char *utf8path)
+spl_load(const char *utf8path, GError **error_r)
{
FILE *file;
GPtrArray *list;
- char buffer[MPD_PATH_MAX];
char *path_fs;
- if (!spl_valid_name(utf8path) || map_spl_path() == NULL)
+ if (spl_map(error_r) == NULL)
return NULL;
- path_fs = map_spl_utf8_to_fs(utf8path);
+ path_fs = spl_map_to_fs(utf8path, error_r);
if (path_fs == NULL)
return NULL;
file = fopen(path_fs, "r");
g_free(path_fs);
- if (file == NULL)
+ if (file == NULL) {
+ playlist_errno(error_r);
return NULL;
+ }
list = g_ptr_array_new();
- while (fgets(buffer, sizeof(buffer), file)) {
- char *s = buffer;
-
- if (*s == PLAYLIST_COMMENT)
+ GString *buffer = g_string_sized_new(1024);
+ char *s;
+ while ((s = read_text_line(file, buffer)) != NULL) {
+ if (*s == 0 || *s == PLAYLIST_COMMENT)
continue;
- g_strchomp(buffer);
-
if (!uri_has_scheme(s)) {
char *path_utf8;
- struct song *song;
path_utf8 = map_fs_to_utf8(s);
if (path_utf8 == NULL)
continue;
- song = db_get_song(path_utf8);
- g_free(path_utf8);
- if (song == NULL)
- continue;
-
- s = song_get_uri(song);
+ s = path_utf8;
} else
s = g_strdup(s);
@@ -266,30 +328,33 @@ spl_insert_index_internal(GPtrArray *list, unsigned idx, char *uri)
g_ptr_array_index(list, idx) = uri;
}
-enum playlist_result
-spl_move_index(const char *utf8path, unsigned src, unsigned dest)
+bool
+spl_move_index(const char *utf8path, unsigned src, unsigned dest,
+ GError **error_r)
{
- GPtrArray *list;
char *uri;
- enum playlist_result result;
if (src == dest)
/* this doesn't check whether the playlist exists, but
what the hell.. */
- return PLAYLIST_RESULT_SUCCESS;
+ return true;
- if (!(list = spl_load(utf8path)))
- return PLAYLIST_RESULT_NO_SUCH_LIST;
+ GPtrArray *list = spl_load(utf8path, error_r);
+ if (list == NULL)
+ return false;
if (src >= list->len || dest >= list->len) {
spl_free(list);
- return PLAYLIST_RESULT_BAD_RANGE;
+ g_set_error_literal(error_r, playlist_quark(),
+ PLAYLIST_RESULT_BAD_RANGE,
+ "Bad range");
+ return false;
}
uri = spl_remove_index_internal(list, src);
spl_insert_index_internal(list, dest, uri);
- result = spl_save(list, utf8path);
+ bool result = spl_save(list, utf8path, error_r);
spl_free(list);
@@ -297,78 +362,72 @@ spl_move_index(const char *utf8path, unsigned src, unsigned dest)
return result;
}
-enum playlist_result
-spl_clear(const char *utf8path)
+bool
+spl_clear(const char *utf8path, GError **error_r)
{
- char *path_fs;
FILE *file;
- if (map_spl_path() == NULL)
- return PLAYLIST_RESULT_DISABLED;
+ if (spl_map(error_r) == NULL)
+ return false;
- if (!spl_valid_name(utf8path))
- return PLAYLIST_RESULT_BAD_NAME;
-
- path_fs = map_spl_utf8_to_fs(utf8path);
+ char *path_fs = spl_map_to_fs(utf8path, error_r);
if (path_fs == NULL)
- return PLAYLIST_RESULT_BAD_NAME;
+ return false;
file = fopen(path_fs, "w");
g_free(path_fs);
- if (file == NULL)
- return PLAYLIST_RESULT_ERRNO;
+ if (file == NULL) {
+ playlist_errno(error_r);
+ return false;
+ }
fclose(file);
idle_add(IDLE_STORED_PLAYLIST);
- return PLAYLIST_RESULT_SUCCESS;
+ return true;
}
-enum playlist_result
-spl_delete(const char *name_utf8)
+bool
+spl_delete(const char *name_utf8, GError **error_r)
{
char *path_fs;
int ret;
- if (map_spl_path() == NULL)
- return PLAYLIST_RESULT_DISABLED;
-
- if (!spl_valid_name(name_utf8))
- return PLAYLIST_RESULT_BAD_NAME;
-
- path_fs = map_spl_utf8_to_fs(name_utf8);
+ path_fs = spl_map_to_fs(name_utf8, error_r);
if (path_fs == NULL)
- return PLAYLIST_RESULT_BAD_NAME;
+ return false;
ret = unlink(path_fs);
g_free(path_fs);
- if (ret < 0)
- return errno == ENOENT
- ? PLAYLIST_RESULT_NO_SUCH_LIST
- : PLAYLIST_RESULT_ERRNO;
+ if (ret < 0) {
+ playlist_errno(error_r);
+ return false;
+ }
idle_add(IDLE_STORED_PLAYLIST);
- return PLAYLIST_RESULT_SUCCESS;
+ return true;
}
-enum playlist_result
-spl_remove_index(const char *utf8path, unsigned pos)
+bool
+spl_remove_index(const char *utf8path, unsigned pos, GError **error_r)
{
- GPtrArray *list;
char *uri;
- enum playlist_result result;
- if (!(list = spl_load(utf8path)))
- return PLAYLIST_RESULT_NO_SUCH_LIST;
+ GPtrArray *list = spl_load(utf8path, error_r);
+ if (list == NULL)
+ return false;
if (pos >= list->len) {
spl_free(list);
- return PLAYLIST_RESULT_BAD_RANGE;
+ g_set_error_literal(error_r, playlist_quark(),
+ PLAYLIST_RESULT_BAD_RANGE,
+ "Bad range");
+ return false;
}
uri = spl_remove_index_internal(list, pos);
g_free(uri);
- result = spl_save(list, utf8path);
+ bool result = spl_save(list, utf8path, error_r);
spl_free(list);
@@ -376,38 +435,38 @@ spl_remove_index(const char *utf8path, unsigned pos)
return result;
}
-enum playlist_result
-spl_append_song(const char *utf8path, struct song *song)
+bool
+spl_append_song(const char *utf8path, struct song *song, GError **error_r)
{
FILE *file;
struct stat st;
- char *path_fs;
-
- if (map_spl_path() == NULL)
- return PLAYLIST_RESULT_DISABLED;
- if (!spl_valid_name(utf8path))
- return PLAYLIST_RESULT_BAD_NAME;
+ if (spl_map(error_r) == NULL)
+ return false;
- path_fs = map_spl_utf8_to_fs(utf8path);
+ char *path_fs = spl_map_to_fs(utf8path, error_r);
if (path_fs == NULL)
- return PLAYLIST_RESULT_BAD_NAME;
+ return false;
file = fopen(path_fs, "a");
g_free(path_fs);
- if (file == NULL)
- return PLAYLIST_RESULT_ERRNO;
+ if (file == NULL) {
+ playlist_errno(error_r);
+ return false;
+ }
if (fstat(fileno(file), &st) < 0) {
- int save_errno = errno;
+ playlist_errno(error_r);
fclose(file);
- errno = save_errno;
- return PLAYLIST_RESULT_ERRNO;
+ return false;
}
if (st.st_size / (MPD_PATH_MAX + 1) >= (off_t)playlist_max_length) {
fclose(file);
- return PLAYLIST_RESULT_TOO_LARGE;
+ g_set_error_literal(error_r, playlist_quark(),
+ PLAYLIST_RESULT_TOO_LARGE,
+ "Stored playlist is too large");
+ return false;
}
playlist_print_song(file, song);
@@ -415,68 +474,79 @@ spl_append_song(const char *utf8path, struct song *song)
fclose(file);
idle_add(IDLE_STORED_PLAYLIST);
- return PLAYLIST_RESULT_SUCCESS;
+ return true;
}
-enum playlist_result
-spl_append_uri(const char *url, const char *utf8file)
+bool
+spl_append_uri(const char *url, const char *utf8file, GError **error_r)
{
struct song *song;
if (uri_has_scheme(url)) {
- enum playlist_result ret;
-
song = song_remote_new(url);
- ret = spl_append_song(utf8file, song);
+ bool success = spl_append_song(utf8file, song, error_r);
song_free(song);
- return ret;
+ return success;
} else {
song = db_get_song(url);
- if (song == NULL)
- return PLAYLIST_RESULT_NO_SUCH_SONG;
-
- return spl_append_song(utf8file, song);
+ if (song == NULL) {
+ g_set_error_literal(error_r, playlist_quark(),
+ PLAYLIST_RESULT_NO_SUCH_SONG,
+ "No such song");
+ return false;
+ }
+
+ return spl_append_song(utf8file, song, error_r);
}
}
-static enum playlist_result
-spl_rename_internal(const char *from_path_fs, const char *to_path_fs)
+static bool
+spl_rename_internal(const char *from_path_fs, const char *to_path_fs,
+ GError **error_r)
{
- if (!g_file_test(from_path_fs, G_FILE_TEST_IS_REGULAR))
- return PLAYLIST_RESULT_NO_SUCH_LIST;
+ if (!g_file_test(from_path_fs, G_FILE_TEST_IS_REGULAR)) {
+ g_set_error_literal(error_r, playlist_quark(),
+ PLAYLIST_RESULT_NO_SUCH_LIST,
+ "No such playlist");
+ return false;
+ }
- if (g_file_test(to_path_fs, G_FILE_TEST_EXISTS))
- return PLAYLIST_RESULT_LIST_EXISTS;
+ if (g_file_test(to_path_fs, G_FILE_TEST_EXISTS)) {
+ g_set_error_literal(error_r, playlist_quark(),
+ PLAYLIST_RESULT_LIST_EXISTS,
+ "Playlist exists already");
+ return false;
+ }
- if (rename(from_path_fs, to_path_fs) < 0)
- return PLAYLIST_RESULT_ERRNO;
+ if (rename(from_path_fs, to_path_fs) < 0) {
+ playlist_errno(error_r);
+ return false;
+ }
idle_add(IDLE_STORED_PLAYLIST);
- return PLAYLIST_RESULT_SUCCESS;
+ return true;
}
-enum playlist_result
-spl_rename(const char *utf8from, const char *utf8to)
+bool
+spl_rename(const char *utf8from, const char *utf8to, GError **error_r)
{
- char *from_path_fs, *to_path_fs;
- static enum playlist_result ret;
-
- if (map_spl_path() == NULL)
- return PLAYLIST_RESULT_DISABLED;
+ if (spl_map(error_r) == NULL)
+ return false;
- if (!spl_valid_name(utf8from) || !spl_valid_name(utf8to))
- return PLAYLIST_RESULT_BAD_NAME;
+ char *from_path_fs = spl_map_to_fs(utf8from, error_r);
+ if (from_path_fs == NULL)
+ return false;
- from_path_fs = map_spl_utf8_to_fs(utf8from);
- to_path_fs = map_spl_utf8_to_fs(utf8to);
+ char *to_path_fs = spl_map_to_fs(utf8to, error_r);
+ if (to_path_fs == NULL) {
+ g_free(from_path_fs);
+ return false;
+ }
- if (from_path_fs != NULL && to_path_fs != NULL)
- ret = spl_rename_internal(from_path_fs, to_path_fs);
- else
- ret = PLAYLIST_RESULT_BAD_NAME;
+ bool success = spl_rename_internal(from_path_fs, to_path_fs, error_r);
g_free(from_path_fs);
g_free(to_path_fs);
- return ret;
+ return success;
}
diff --git a/src/stored_playlist.h b/src/stored_playlist.h
index 3afdbb0f0..cfe49633c 100644
--- a/src/stored_playlist.h
+++ b/src/stored_playlist.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -20,8 +20,6 @@
#ifndef MPD_STORED_PLAYLIST_H
#define MPD_STORED_PLAYLIST_H
-#include "playlist.h"
-
#include <glib.h>
#include <stdbool.h>
#include <time.h>
@@ -51,39 +49,40 @@ spl_valid_name(const char *name_utf8);
/**
* Returns a list of stored_playlist_info struct pointers. Returns
- * NULL if an error occured.
+ * NULL if an error occurred.
*/
GPtrArray *
-spl_list(void);
+spl_list(GError **error_r);
void
spl_list_free(GPtrArray *list);
GPtrArray *
-spl_load(const char *utf8path);
+spl_load(const char *utf8path, GError **error_r);
void
spl_free(GPtrArray *list);
-enum playlist_result
-spl_move_index(const char *utf8path, unsigned src, unsigned dest);
+bool
+spl_move_index(const char *utf8path, unsigned src, unsigned dest,
+ GError **error_r);
-enum playlist_result
-spl_clear(const char *utf8path);
+bool
+spl_clear(const char *utf8path, GError **error_r);
-enum playlist_result
-spl_delete(const char *name_utf8);
+bool
+spl_delete(const char *name_utf8, GError **error_r);
-enum playlist_result
-spl_remove_index(const char *utf8path, unsigned pos);
+bool
+spl_remove_index(const char *utf8path, unsigned pos, GError **error_r);
-enum playlist_result
-spl_append_song(const char *utf8path, struct song *song);
+bool
+spl_append_song(const char *utf8path, struct song *song, GError **error_r);
-enum playlist_result
-spl_append_uri(const char *file, const char *utf8file);
+bool
+spl_append_uri(const char *file, const char *utf8file, GError **error_r);
-enum playlist_result
-spl_rename(const char *utf8from, const char *utf8to);
+bool
+spl_rename(const char *utf8from, const char *utf8to, GError **error_r);
#endif
diff --git a/src/string_util.c b/src/string_util.c
new file mode 100644
index 000000000..6e5429076
--- /dev/null
+++ b/src/string_util.c
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "string_util.h"
+
+#include <glib.h>
+
+#include <assert.h>
+
+const char *
+strchug_fast_c(const char *p)
+{
+ while (*p != 0 && g_ascii_isspace(*p))
+ ++p;
+
+ return p;
+}
+
+bool
+string_array_contains(const char *const* haystack, const char *needle)
+{
+ assert(haystack != NULL);
+ assert(needle != NULL);
+
+ for (; *haystack != NULL; ++haystack)
+ if (g_ascii_strcasecmp(*haystack, needle) == 0)
+ return true;
+
+ return false;
+}
diff --git a/src/string_util.h b/src/string_util.h
new file mode 100644
index 000000000..dc80a46ef
--- /dev/null
+++ b/src/string_util.h
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_STRING_UTIL_H
+#define MPD_STRING_UTIL_H
+
+#include <glib.h>
+
+#include <stdbool.h>
+
+/**
+ * Remove the "const" attribute from a string pointer. This is a
+ * dirty hack, don't use it unless you know what you're doing!
+ */
+G_GNUC_CONST
+static inline char *
+deconst_string(const char *p)
+{
+ union {
+ const char *in;
+ char *out;
+ } u = {
+ .in = p,
+ };
+
+ return u.out;
+}
+
+/**
+ * Returns a pointer to the first non-whitespace character in the
+ * string, or to the end of the string.
+ *
+ * This is a faster version of g_strchug(), because it does not move
+ * data.
+ */
+G_GNUC_PURE
+const char *
+strchug_fast_c(const char *p);
+
+/**
+ * Same as strchug_fast_c(), but works with a writable pointer.
+ */
+G_GNUC_PURE
+static inline char *
+strchug_fast(char *p)
+{
+ return deconst_string(strchug_fast_c(p));
+}
+
+/**
+ * Checks whether a string array contains the specified string.
+ *
+ * @param haystack a NULL terminated list of strings
+ * @param needle the string to search for; the comparison is
+ * case-insensitive for ASCII characters
+ * @return true if found
+ */
+bool
+string_array_contains(const char *const* haystack, const char *needle);
+
+#endif
diff --git a/src/strset.c b/src/strset.c
index e071fbc98..5862e4075 100644
--- a/src/strset.c
+++ b/src/strset.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/strset.h b/src/strset.h
index 9a7aa45e5..5382e59b8 100644
--- a/src/strset.h
+++ b/src/strset.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/tag.c b/src/tag.c
index 6ce46a831..c0faa7ab2 100644
--- a/src/tag.c
+++ b/src/tag.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -158,6 +158,7 @@ struct tag *tag_new(void)
struct tag *ret = g_new(struct tag, 1);
ret->items = NULL;
ret->time = -1;
+ ret->has_playlist = false;
ret->num_items = 0;
return ret;
}
@@ -226,6 +227,7 @@ struct tag *tag_dup(const struct tag *tag)
ret = tag_new();
ret->time = tag->time;
+ ret->has_playlist = tag->has_playlist;
ret->num_items = tag->num_items;
ret->items = ret->num_items > 0 ? g_malloc(items_size(tag)) : NULL;
diff --git a/src/tag.h b/src/tag.h
index 6931453f7..2556cf3a7 100644
--- a/src/tag.h
+++ b/src/tag.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -73,7 +73,7 @@ struct tag_item {
* the value of this tag; this is a variable length string
*/
char value[sizeof(long)];
-} mpd_packed;
+} gcc_packed;
/**
* The meta information about a song file. It is a MPD specific
@@ -88,6 +88,12 @@ struct tag {
*/
int time;
+ /**
+ * Does this file have an embedded playlist (e.g. embedded CUE
+ * sheet)?
+ */
+ bool has_playlist;
+
/** an array of tag items */
struct tag_item **items;
diff --git a/src/tag_ape.c b/src/tag_ape.c
index 79facba1b..31c177aa7 100644
--- a/src/tag_ape.c
+++ b/src/tag_ape.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -21,37 +21,39 @@
#include "tag_ape.h"
#include "tag.h"
#include "tag_table.h"
+#include "tag_handler.h"
#include "ape.h"
-static const char *const ape_tag_names[TAG_NUM_OF_ITEM_TYPES] = {
- [TAG_ALBUM_ARTIST] = "album artist",
- [TAG_DATE] = "year",
+static const struct tag_table ape_tags[] = {
+ { "album artist", TAG_ALBUM_ARTIST },
+ { "year", TAG_DATE },
+ { NULL, TAG_NUM_OF_ITEM_TYPES }
};
static enum tag_type
tag_ape_name_parse(const char *name)
{
- enum tag_type type = tag_table_lookup(ape_tag_names, name);
+ enum tag_type type = tag_table_lookup_i(ape_tags, 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)
+static void
+tag_ape_import_item(unsigned long flags,
+ const char *key, const char *value, size_t value_length,
+ const struct tag_handler *handler, void *handler_ctx)
{
/* we only care about utf-8 text tags */
if ((flags & (0x3 << 1)) != 0)
- return tag;
+ return;
+
+ tag_handler_invoke_pair(handler, handler_ctx, key, value);
enum tag_type type = tag_ape_name_parse(key);
if (type == TAG_NUM_OF_ITEM_TYPES)
- return tag;
-
- if (tag == NULL)
- tag = tag_new();
+ return;
const char *end = value + value_length;
while (true) {
@@ -59,20 +61,22 @@ tag_ape_import_item(struct tag *tag, unsigned long flags,
const char *n = memchr(value, 0, end - value);
if (n != NULL) {
if (n > value)
- tag_add_item_n(tag, type, value, n - value);
+ tag_handler_invoke_tag(handler, handler_ctx,
+ type, value);
value = n + 1;
} else {
- if (end > value)
- tag_add_item_n(tag, type, value, end - value);
+ char *p = g_strndup(value, end - value);
+ tag_handler_invoke_tag(handler, handler_ctx,
+ type, p);
+ g_free(p);
break;
}
}
-
- return tag;
}
struct tag_ape_ctx {
- struct tag *tag;
+ const struct tag_handler *handler;
+ void *handler_ctx;
};
static bool
@@ -81,16 +85,19 @@ tag_ape_callback(unsigned long flags, const char *key,
{
struct tag_ape_ctx *ctx = _ctx;
- ctx->tag = tag_ape_import_item(ctx->tag, flags, key,
- value, value_length);
+ tag_ape_import_item(flags, key, value, value_length,
+ ctx->handler, ctx->handler_ctx);
return true;
}
-struct tag *
-tag_ape_load(const char *file)
+bool
+tag_ape_scan2(const char *path_fs,
+ const struct tag_handler *handler, void *handler_ctx)
{
- struct tag_ape_ctx ctx = { .tag = NULL };
+ struct tag_ape_ctx ctx = {
+ .handler = handler,
+ .handler_ctx = handler_ctx,
+ };
- tag_ape_scan(file, tag_ape_callback, &ctx);
- return ctx.tag;
+ return tag_ape_scan(path_fs, tag_ape_callback, &ctx);
}
diff --git a/src/tag_ape.h b/src/tag_ape.h
index 150659685..9b5856115 100644
--- a/src/tag_ape.h
+++ b/src/tag_ape.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -20,13 +20,17 @@
#ifndef MPD_TAG_APE_H
#define MPD_TAG_APE_H
+#include <stdbool.h>
+
+struct tag_handler;
+
/**
- * Loads the APE tag from a file.
+ * Scan the APE tags of a file.
*
* @param path_fs the path of the file in filesystem encoding
- * @return a tag object, or NULL if the file has no APE tag
*/
-struct tag *
-tag_ape_load(const char *path_fs);
+bool
+tag_ape_scan2(const char *path_fs,
+ const struct tag_handler *handler, void *handler_ctx);
#endif
diff --git a/src/tag_file.c b/src/tag_file.c
new file mode 100644
index 000000000..8d8a0f5fb
--- /dev/null
+++ b/src/tag_file.c
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2003-2012 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "tag_file.h"
+#include "uri.h"
+#include "decoder_list.h"
+#include "decoder_plugin.h"
+#include "input_stream.h"
+
+#include <assert.h>
+#include <unistd.h> /* for SEEK_SET */
+
+bool
+tag_file_scan(const char *path_fs,
+ const struct tag_handler *handler, void *handler_ctx)
+{
+ assert(path_fs != NULL);
+ assert(handler != NULL);
+
+ /* check if there's a suffix and a plugin */
+
+ const char *suffix = uri_get_suffix(path_fs);
+ if (suffix == NULL)
+ return false;
+
+ const struct decoder_plugin *plugin =
+ decoder_plugin_from_suffix(suffix, NULL);
+ if (plugin == NULL)
+ return false;
+
+ struct input_stream *is = NULL;
+ GMutex *mutex = NULL;
+ GCond *cond = NULL;
+
+ do {
+ /* load file tag */
+ if (decoder_plugin_scan_file(plugin, path_fs,
+ handler, handler_ctx))
+ break;
+
+ /* fall back to stream tag */
+ if (plugin->scan_stream != NULL) {
+ /* open the input_stream (if not already
+ open) */
+ if (is == NULL) {
+ mutex = g_mutex_new();
+ cond = g_cond_new();
+ is = input_stream_open(path_fs, mutex, cond,
+ NULL);
+ }
+
+ /* now try the stream_tag() method */
+ if (is != NULL) {
+ if (decoder_plugin_scan_stream(plugin, is,
+ handler,
+ handler_ctx))
+ break;
+
+ input_stream_lock_seek(is, 0, SEEK_SET, NULL);
+ }
+ }
+
+ plugin = decoder_plugin_from_suffix(suffix, plugin);
+ } while (plugin != NULL);
+
+ if (is != NULL) {
+ input_stream_close(is);
+ g_cond_free(cond);
+ g_mutex_free(mutex);
+ }
+
+ return plugin != NULL;
+}
diff --git a/src/tag_file.h b/src/tag_file.h
new file mode 100644
index 000000000..8cf1af3cb
--- /dev/null
+++ b/src/tag_file.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2003-2012 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_TAG_FILE_H
+#define MPD_TAG_FILE_H
+
+#include "check.h"
+
+#include <stdbool.h>
+
+struct tag_handler;
+
+/**
+ * Scan the tags of a song file. Invokes matching decoder plugins,
+ * but does not invoke the special "APE" and "ID3" scanners.
+ */
+bool
+tag_file_scan(const char *path_fs,
+ const struct tag_handler *handler, void *handler_ctx);
+
+#endif
diff --git a/src/tag_handler.c b/src/tag_handler.c
new file mode 100644
index 000000000..316715e87
--- /dev/null
+++ b/src/tag_handler.c
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "tag_handler.h"
+
+#include <glib.h>
+
+static void
+add_tag_duration(unsigned seconds, void *ctx)
+{
+ struct tag *tag = ctx;
+
+ tag->time = seconds;
+}
+
+static void
+add_tag_tag(enum tag_type type, const char *value, void *ctx)
+{
+ struct tag *tag = ctx;
+
+ tag_add_item(tag, type, value);
+}
+
+const struct tag_handler add_tag_handler = {
+ .duration = add_tag_duration,
+ .tag = add_tag_tag,
+};
+
+static void
+full_tag_pair(const char *name, G_GNUC_UNUSED const char *value, void *ctx)
+{
+ struct tag *tag = ctx;
+
+ if (g_ascii_strcasecmp(name, "cuesheet") == 0)
+ tag->has_playlist = true;
+}
+
+const struct tag_handler full_tag_handler = {
+ .duration = add_tag_duration,
+ .tag = add_tag_tag,
+ .pair = full_tag_pair,
+};
+
diff --git a/src/tag_handler.h b/src/tag_handler.h
new file mode 100644
index 000000000..7f15c2a48
--- /dev/null
+++ b/src/tag_handler.h
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_TAG_HANDLER_H
+#define MPD_TAG_HANDLER_H
+
+#include "check.h"
+#include "tag.h"
+
+#include <assert.h>
+
+/**
+ * A callback table for receiving metadata of a song.
+ */
+struct tag_handler {
+ /**
+ * Declare the duration of a song, in seconds. Do not call
+ * this when the duration could not be determined, because
+ * there is no magic value for "unknown duration".
+ */
+ void (*duration)(unsigned seconds, void *ctx);
+
+ /**
+ * A tag has been read.
+ *
+ * @param the value of the tag; the pointer will become
+ * invalid after returning
+ */
+ void (*tag)(enum tag_type type, const char *value, void *ctx);
+
+ /**
+ * A name-value pair has been read. It is the codec specific
+ * representation of tags.
+ */
+ void (*pair)(const char *key, const char *value, void *ctx);
+};
+
+static inline void
+tag_handler_invoke_duration(const struct tag_handler *handler, void *ctx,
+ unsigned seconds)
+{
+ assert(handler != NULL);
+
+ if (handler->duration != NULL)
+ handler->duration(seconds, ctx);
+}
+
+static inline void
+tag_handler_invoke_tag(const struct tag_handler *handler, void *ctx,
+ enum tag_type type, const char *value)
+{
+ assert(handler != NULL);
+ assert((unsigned)type < TAG_NUM_OF_ITEM_TYPES);
+ assert(value != NULL);
+
+ if (handler->tag != NULL)
+ handler->tag(type, value, ctx);
+}
+
+static inline void
+tag_handler_invoke_pair(const struct tag_handler *handler, void *ctx,
+ const char *name, const char *value)
+{
+ assert(handler != NULL);
+ assert(name != NULL);
+ assert(value != NULL);
+
+ if (handler->pair != NULL)
+ handler->pair(name, value, ctx);
+}
+
+/**
+ * This #tag_handler implementation adds tag values to a #tag object
+ * (casted from the context pointer).
+ */
+extern const struct tag_handler add_tag_handler;
+
+/**
+ * This #tag_handler implementation adds tag values to a #tag object
+ * (casted from the context pointer), and supports the has_playlist
+ * attribute.
+ */
+extern const struct tag_handler full_tag_handler;
+
+#endif
diff --git a/src/tag_id3.c b/src/tag_id3.c
index 9c0a98d40..c98179f0a 100644
--- a/src/tag_id3.c
+++ b/src/tag_id3.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -19,6 +19,8 @@
#include "config.h"
#include "tag_id3.h"
+#include "tag_handler.h"
+#include "tag_table.h"
#include "tag.h"
#include "riff.h"
#include "aiff.h"
@@ -126,9 +128,9 @@ import_id3_string(bool is_id3v1, const id3_ucs4_t *ucs4)
* - string list
*/
static void
-tag_id3_import_text_frame(struct tag *dest, struct id3_tag *tag,
- const struct id3_frame *frame,
- enum tag_type type)
+tag_id3_import_text_frame(struct id3_tag *tag, const struct id3_frame *frame,
+ enum tag_type type,
+ const struct tag_handler *handler, void *handler_ctx)
{
id3_ucs4_t const *ucs4;
id3_utf8_t *utf8;
@@ -164,7 +166,8 @@ tag_id3_import_text_frame(struct tag *dest, struct id3_tag *tag,
if (utf8 == NULL)
continue;
- tag_add_item(dest, type, (char *)utf8);
+ tag_handler_invoke_tag(handler, handler_ctx,
+ type, (const char *)utf8);
g_free(utf8);
}
}
@@ -174,13 +177,14 @@ tag_id3_import_text_frame(struct tag *dest, struct id3_tag *tag,
* 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)
+tag_id3_import_text(struct id3_tag *tag, const char *id, enum tag_type type,
+ const struct tag_handler *handler, void *handler_ctx)
{
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);
+ tag_id3_import_text_frame(tag, frame, type,
+ handler, handler_ctx);
}
/**
@@ -193,9 +197,10 @@ 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_frame(struct tag *dest, struct id3_tag *tag,
- const struct id3_frame *frame,
- enum tag_type type)
+tag_id3_import_comment_frame(struct id3_tag *tag,
+ const struct id3_frame *frame, enum tag_type type,
+ const struct tag_handler *handler,
+ void *handler_ctx)
{
id3_ucs4_t const *ucs4;
id3_utf8_t *utf8;
@@ -217,7 +222,7 @@ tag_id3_import_comment_frame(struct tag *dest, struct id3_tag *tag,
if (utf8 == NULL)
return;
- tag_add_item(dest, type, (char *)utf8);
+ tag_handler_invoke_tag(handler, handler_ctx, type, (const char *)utf8);
g_free(utf8);
}
@@ -226,13 +231,14 @@ tag_id3_import_comment_frame(struct tag *dest, struct id3_tag *tag,
* 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)
+tag_id3_import_comment(struct id3_tag *tag, const char *id, enum tag_type type,
+ const struct tag_handler *handler, void *handler_ctx)
{
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);
+ tag_id3_import_comment_frame(tag, frame, type,
+ handler, handler_ctx);
}
/**
@@ -242,30 +248,26 @@ tag_id3_import_comment(struct tag *dest, struct id3_tag *tag, const char *id,
static enum tag_type
tag_id3_parse_txxx_name(const char *name)
{
- static const struct {
- enum tag_type type;
- const char *name;
- } musicbrainz_txxx[] = {
- { TAG_ALBUM_ARTIST_SORT, "ALBUMARTISTSORT" },
- { TAG_MUSICBRAINZ_ARTISTID, "MusicBrainz Artist Id" },
- { TAG_MUSICBRAINZ_ALBUMID, "MusicBrainz Album Id" },
- { TAG_MUSICBRAINZ_ALBUMARTISTID,
- "MusicBrainz Album Artist Id" },
- { TAG_MUSICBRAINZ_TRACKID, "MusicBrainz Track Id" },
+ static const struct tag_table txxx_tags[] = {
+ { "ALBUMARTISTSORT", TAG_ALBUM_ARTIST_SORT },
+ { "MusicBrainz Artist Id", TAG_MUSICBRAINZ_ARTISTID },
+ { "MusicBrainz Album Id", TAG_MUSICBRAINZ_ALBUMID },
+ { "MusicBrainz Album Artist Id",
+ TAG_MUSICBRAINZ_ALBUMARTISTID },
+ { "MusicBrainz Track Id", TAG_MUSICBRAINZ_TRACKID },
+ { NULL, TAG_NUM_OF_ITEM_TYPES }
};
- for (unsigned i = 0; i < G_N_ELEMENTS(musicbrainz_txxx); ++i)
- if (strcmp(name, musicbrainz_txxx[i].name) == 0)
- return musicbrainz_txxx[i].type;
-
- return TAG_NUM_OF_ITEM_TYPES;
+ return tag_table_lookup(txxx_tags, name);
}
/**
* Import all known MusicBrainz tags from TXXX frames.
*/
static void
-tag_id3_import_musicbrainz(struct tag *mpd_tag, struct id3_tag *id3_tag)
+tag_id3_import_musicbrainz(struct id3_tag *id3_tag,
+ const struct tag_handler *handler,
+ void *handler_ctx)
{
for (unsigned i = 0;; ++i) {
const struct id3_frame *frame;
@@ -280,17 +282,21 @@ tag_id3_import_musicbrainz(struct tag *mpd_tag, struct id3_tag *id3_tag)
if (name == NULL)
continue;
- type = tag_id3_parse_txxx_name((const char*)name);
- free(name);
-
- if (type == TAG_NUM_OF_ITEM_TYPES)
- continue;
-
value = tag_id3_getstring(frame, 2);
if (value == NULL)
continue;
- tag_add_item(mpd_tag, type, (const char*)value);
+ tag_handler_invoke_pair(handler, handler_ctx,
+ (const char *)name,
+ (const char *)value);
+
+ type = tag_id3_parse_txxx_name((const char*)name);
+ free(name);
+
+ if (type != TAG_NUM_OF_ITEM_TYPES)
+ tag_handler_invoke_tag(handler, handler_ctx,
+ type, (const char*)value);
+
free(value);
}
}
@@ -299,7 +305,8 @@ tag_id3_import_musicbrainz(struct tag *mpd_tag, struct id3_tag *id3_tag)
* Imports the MusicBrainz TrackId from the UFID tag.
*/
static void
-tag_id3_import_ufid(struct tag *mpd_tag, struct id3_tag *id3_tag)
+tag_id3_import_ufid(struct id3_tag *id3_tag,
+ const struct tag_handler *handler, void *handler_ctx)
{
for (unsigned i = 0;; ++i) {
const struct id3_frame *frame;
@@ -329,35 +336,54 @@ tag_id3_import_ufid(struct tag *mpd_tag, struct id3_tag *id3_tag)
if (value == NULL || length == 0)
continue;
- tag_add_item_n(mpd_tag, TAG_MUSICBRAINZ_TRACKID,
- (const char*)value, length);
+ char *p = g_strndup((const char *)value, length);
+ tag_handler_invoke_tag(handler, handler_ctx,
+ TAG_MUSICBRAINZ_TRACKID, p);
+ g_free(p);
}
}
+static void
+scan_id3_tag(struct id3_tag *tag,
+ const struct tag_handler *handler, void *handler_ctx)
+{
+ tag_id3_import_text(tag, ID3_FRAME_ARTIST, TAG_ARTIST,
+ handler, handler_ctx);
+ tag_id3_import_text(tag, ID3_FRAME_ALBUM_ARTIST,
+ TAG_ALBUM_ARTIST, handler, handler_ctx);
+ tag_id3_import_text(tag, ID3_FRAME_ARTIST_SORT,
+ TAG_ARTIST_SORT, handler, handler_ctx);
+ tag_id3_import_text(tag, ID3_FRAME_ALBUM_ARTIST_SORT,
+ TAG_ALBUM_ARTIST_SORT, handler, handler_ctx);
+ tag_id3_import_text(tag, ID3_FRAME_TITLE, TAG_TITLE,
+ handler, handler_ctx);
+ tag_id3_import_text(tag, ID3_FRAME_ALBUM, TAG_ALBUM,
+ handler, handler_ctx);
+ tag_id3_import_text(tag, ID3_FRAME_TRACK, TAG_TRACK,
+ handler, handler_ctx);
+ tag_id3_import_text(tag, ID3_FRAME_YEAR, TAG_DATE,
+ handler, handler_ctx);
+ tag_id3_import_text(tag, ID3_FRAME_GENRE, TAG_GENRE,
+ handler, handler_ctx);
+ tag_id3_import_text(tag, ID3_FRAME_COMPOSER, TAG_COMPOSER,
+ handler, handler_ctx);
+ tag_id3_import_text(tag, "TPE3", TAG_PERFORMER,
+ handler, handler_ctx);
+ tag_id3_import_text(tag, "TPE4", TAG_PERFORMER, handler, handler_ctx);
+ tag_id3_import_comment(tag, ID3_FRAME_COMMENT, TAG_COMMENT,
+ handler, handler_ctx);
+ tag_id3_import_text(tag, ID3_FRAME_DISC, TAG_DISC,
+ handler, handler_ctx);
+
+ tag_id3_import_musicbrainz(tag, handler, handler_ctx);
+ tag_id3_import_ufid(tag, handler, handler_ctx);
+}
+
struct tag *tag_id3_import(struct id3_tag * tag)
{
struct tag *ret = tag_new();
- tag_id3_import_text(ret, tag, ID3_FRAME_ARTIST, TAG_ARTIST);
- tag_id3_import_text(ret, tag, ID3_FRAME_ALBUM_ARTIST,
- TAG_ALBUM_ARTIST);
- tag_id3_import_text(ret, tag, ID3_FRAME_ARTIST_SORT,
- TAG_ARTIST_SORT);
- tag_id3_import_text(ret, tag, ID3_FRAME_ALBUM_ARTIST_SORT,
- TAG_ALBUM_ARTIST_SORT);
- tag_id3_import_text(ret, tag, ID3_FRAME_TITLE, TAG_TITLE);
- tag_id3_import_text(ret, tag, ID3_FRAME_ALBUM, TAG_ALBUM);
- tag_id3_import_text(ret, tag, ID3_FRAME_TRACK, TAG_TRACK);
- tag_id3_import_text(ret, tag, ID3_FRAME_YEAR, TAG_DATE);
- tag_id3_import_text(ret, tag, ID3_FRAME_GENRE, TAG_GENRE);
- tag_id3_import_text(ret, tag, ID3_FRAME_COMPOSER, TAG_COMPOSER);
- tag_id3_import_text(ret, tag, "TPE3", TAG_PERFORMER);
- tag_id3_import_text(ret, tag, "TPE4", TAG_PERFORMER);
- tag_id3_import_comment(ret, tag, ID3_FRAME_COMMENT, TAG_COMMENT);
- tag_id3_import_text(ret, tag, ID3_FRAME_DISC, TAG_DISC);
-
- tag_id3_import_musicbrainz(ret, tag);
- tag_id3_import_ufid(ret, tag);
+ scan_id3_tag(tag, &add_tag_handler, ret);
if (tag_is_empty(ret)) {
tag_free(ret);
@@ -515,17 +541,18 @@ tag_id3_riff_aiff_load(FILE *file)
return tag;
}
-struct tag *tag_id3_load(const char *file)
+bool
+tag_id3_scan(const char *path_fs,
+ const struct tag_handler *handler, void *handler_ctx)
{
- struct tag *ret;
struct id3_tag *tag;
FILE *stream;
- stream = fopen(file, "rb");
+ stream = fopen(path_fs, "rb");
if (!stream) {
g_debug("tag_id3_load: Failed to open file: '%s', %s",
- file, strerror(errno));
- return NULL;
+ path_fs, g_strerror(errno));
+ return false;
}
tag = tag_id3_find_from_beginning(stream);
@@ -537,8 +564,9 @@ struct tag *tag_id3_load(const char *file)
fclose(stream);
if (!tag)
- return NULL;
- ret = tag_id3_import(tag);
+ return false;
+
+ scan_id3_tag(tag, handler, handler_ctx);
id3_tag_delete(tag);
- return ret;
+ return true;
}
diff --git a/src/tag_id3.h b/src/tag_id3.h
index 43f9678b4..b99b4480f 100644
--- a/src/tag_id3.h
+++ b/src/tag_id3.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -22,22 +22,30 @@
#include "check.h"
+#include <stdbool.h>
+
+struct tag_handler;
struct tag;
#ifdef HAVE_ID3TAG
+
+bool
+tag_id3_scan(const char *path_fs,
+ const struct tag_handler *handler, void *handler_ctx);
+
struct id3_tag;
struct tag *tag_id3_import(struct id3_tag *);
-struct tag *tag_id3_load(const char *file);
-
#else
#include <glib.h>
-static inline struct tag *
-tag_id3_load(G_GNUC_UNUSED const char *file)
+static inline bool
+tag_id3_scan(G_GNUC_UNUSED const char *path_fs,
+ G_GNUC_UNUSED const struct tag_handler *handler,
+ G_GNUC_UNUSED void *handler_ctx)
{
- return NULL;
+ return false;
}
#endif
diff --git a/src/tag_internal.h b/src/tag_internal.h
index 9d76efed1..af05cc6b6 100644
--- a/src/tag_internal.h
+++ b/src/tag_internal.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -20,6 +20,8 @@
#ifndef MPD_TAG_INTERNAL_H
#define MPD_TAG_INTERNAL_H
+#include "tag.h"
+
#include <stdbool.h>
extern bool ignore_tag_items[TAG_NUM_OF_ITEM_TYPES];
diff --git a/src/tag_pool.c b/src/tag_pool.c
index 6ad1e1f2d..eabf3e369 100644
--- a/src/tag_pool.c
+++ b/src/tag_pool.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/tag_pool.h b/src/tag_pool.h
index 289d6fe5f..a96c00d85 100644
--- a/src/tag_pool.h
+++ b/src/tag_pool.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/tag_print.c b/src/tag_print.c
index 493fa89b5..9a46b247a 100644
--- a/src/tag_print.c
+++ b/src/tag_print.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/tag_print.h b/src/tag_print.h
index e16e2c441..b9eeeaecf 100644
--- a/src/tag_print.h
+++ b/src/tag_print.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/tag_rva2.c b/src/tag_rva2.c
index 35f12118f..68ae9d5e5 100644
--- a/src/tag_rva2.c
+++ b/src/tag_rva2.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/tag_rva2.h b/src/tag_rva2.h
index a92c97912..8aac2fe9f 100644
--- a/src/tag_rva2.h
+++ b/src/tag_rva2.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/tag_save.c b/src/tag_save.c
index 9b90d1b92..2fdaef56c 100644
--- a/src/tag_save.c
+++ b/src/tag_save.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -28,6 +28,9 @@ void tag_save(FILE *file, const struct tag *tag)
if (tag->time >= 0)
fprintf(file, SONG_TIME "%i\n", tag->time);
+ if (tag->has_playlist)
+ fprintf(file, "Playlist: yes\n");
+
for (unsigned i = 0; i < tag->num_items; i++)
fprintf(file, "%s: %s\n",
tag_item_names[tag->items[i]->type],
diff --git a/src/tag_save.h b/src/tag_save.h
index 2e8924c20..9f6a580c8 100644
--- a/src/tag_save.h
+++ b/src/tag_save.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/tag_table.h b/src/tag_table.h
index ce47d69fc..d87d4869a 100644
--- a/src/tag_table.h
+++ b/src/tag_table.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -24,18 +24,40 @@
#include <glib.h>
+struct tag_table {
+ const char *name;
+
+ enum tag_type type;
+};
+
+/**
+ * Looks up a string in a tag translation table (case sensitive).
+ * Returns TAG_NUM_OF_ITEM_TYPES if the specified name was not found
+ * in the table.
+ */
+G_GNUC_PURE
+static inline enum tag_type
+tag_table_lookup(const struct tag_table *table, const char *name)
+{
+ for (; table->name != NULL; ++table)
+ if (strcmp(name, table->name) == 0)
+ return table->type;
+
+ return TAG_NUM_OF_ITEM_TYPES;
+}
+
/**
* 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.
*/
+G_GNUC_PURE
static inline enum tag_type
-tag_table_lookup(const char *const* table, const char *name)
+tag_table_lookup_i(const struct tag_table *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;
+ for (; table->name != NULL; ++table)
+ if (g_ascii_strcasecmp(name, table->name) == 0)
+ return table->type;
return TAG_NUM_OF_ITEM_TYPES;
}
diff --git a/src/tcp_connect.c b/src/tcp_connect.c
new file mode 100644
index 000000000..88e2348e6
--- /dev/null
+++ b/src/tcp_connect.c
@@ -0,0 +1,251 @@
+/*
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "tcp_connect.h"
+#include "fd_util.h"
+#include "io_thread.h"
+#include "glib_compat.h"
+#include "glib_socket.h"
+
+#include <assert.h>
+#include <errno.h>
+
+#ifdef WIN32
+#include <ws2tcpip.h>
+#include <winsock.h>
+#else
+#include <sys/socket.h>
+#include <unistd.h>
+#endif
+
+struct tcp_connect {
+ const struct tcp_connect_handler *handler;
+ void *handler_ctx;
+
+ int fd;
+ GSource *source;
+
+ unsigned timeout_ms;
+ GSource *timeout_source;
+};
+
+static bool
+is_in_progress_errno(int e)
+{
+#ifdef WIN32
+ return e == WSAEINPROGRESS || e == WSAEWOULDBLOCK;
+#else
+ return e == EINPROGRESS;
+#endif
+}
+
+static gboolean
+tcp_connect_event(G_GNUC_UNUSED GIOChannel *source,
+ G_GNUC_UNUSED GIOCondition condition,
+ gpointer data)
+{
+ struct tcp_connect *c = data;
+
+ assert(c->source != NULL);
+ assert(c->timeout_source != NULL);
+
+ /* clear the socket source */
+ g_source_unref(c->source);
+ c->source = NULL;
+
+ /* delete the timeout source */
+ g_source_destroy(c->timeout_source);
+ g_source_unref(c->timeout_source);
+ c->timeout_source = NULL;
+
+ /* obtain the connect result */
+ int s_err = 0;
+ socklen_t s_err_size = sizeof(s_err);
+ if (getsockopt(c->fd, SOL_SOCKET, SO_ERROR,
+ (char*)&s_err, &s_err_size) < 0)
+ s_err = errno;
+
+ if (s_err == 0) {
+ /* connection established successfully */
+
+ c->handler->success(c->fd, c->handler_ctx);
+ } else {
+ /* there was an I/O error; close the socket and pass
+ the error to the handler */
+
+ close_socket(c->fd);
+
+ GError *error =
+ g_error_new_literal(g_file_error_quark(), s_err,
+ g_strerror(s_err));
+ c->handler->error(error, c->handler_ctx);
+ }
+
+ return false;
+}
+
+static gboolean
+tcp_connect_timeout(gpointer data)
+{
+ struct tcp_connect *c = data;
+
+ assert(c->source != NULL);
+ assert(c->timeout_source != NULL);
+
+ /* clear the timeout source */
+ g_source_unref(c->timeout_source);
+ c->timeout_source = NULL;
+
+ /* delete the socket source */
+ g_source_destroy(c->source);
+ g_source_unref(c->source);
+ c->source = NULL;
+
+ /* report timeout to handler */
+ c->handler->timeout(c->handler_ctx);
+
+ return false;
+}
+
+static gpointer
+tcp_connect_init(gpointer data)
+{
+ struct tcp_connect *c = data;
+
+ /* create a connect source */
+ GIOChannel *channel = g_io_channel_new_socket(c->fd);
+ c->source = g_io_create_watch(channel, G_IO_OUT);
+ g_io_channel_unref(channel);
+
+ g_source_set_callback(c->source, (GSourceFunc)tcp_connect_event, c,
+ NULL);
+ g_source_attach(c->source, io_thread_context());
+
+ /* create a timeout source */
+ if (c->timeout_ms > 0)
+ c->timeout_source =
+ io_thread_timeout_add(c->timeout_ms,
+ tcp_connect_timeout, c);
+
+ return NULL;
+}
+
+void
+tcp_connect_address(const struct sockaddr *address, size_t address_length,
+ unsigned timeout_ms,
+ const struct tcp_connect_handler *handler, void *ctx,
+ struct tcp_connect **handle_r)
+{
+ assert(address != NULL);
+ assert(address_length > 0);
+ assert(handler != NULL);
+ assert(handler->success != NULL);
+ assert(handler->error != NULL);
+ assert(handler->canceled != NULL);
+ assert(handler->timeout != NULL || timeout_ms == 0);
+ assert(handle_r != NULL);
+ assert(*handle_r == NULL);
+
+ int fd = socket_cloexec_nonblock(address->sa_family, SOCK_STREAM, 0);
+ if (fd < 0) {
+ GError *error =
+ g_error_new_literal(g_file_error_quark(), errno,
+ g_strerror(errno));
+ handler->error(error, ctx);
+ return;
+ }
+
+ int ret = connect(fd, address, address_length);
+ if (ret >= 0) {
+ /* quick connect, no I/O thread */
+ handler->success(fd, ctx);
+ return;
+ }
+
+ if (!is_in_progress_errno(errno)) {
+ GError *error =
+ g_error_new_literal(g_file_error_quark(), errno,
+ g_strerror(errno));
+ close_socket(fd);
+ handler->error(error, ctx);
+ return;
+ }
+
+ /* got EINPROGRESS, use the I/O thread to wait for the
+ operation to finish */
+
+ struct tcp_connect *c = g_new(struct tcp_connect, 1);
+ c->handler = handler;
+ c->handler_ctx = ctx;
+ c->fd = fd;
+ c->source = NULL;
+ c->timeout_ms = timeout_ms;
+ c->timeout_source = NULL;
+
+ *handle_r = c;
+
+ io_thread_call(tcp_connect_init, c);
+}
+
+static gpointer
+tcp_connect_cancel_callback(gpointer data)
+{
+ struct tcp_connect *c = data;
+
+ assert((c->source == NULL) == (c->timeout_source == NULL));
+
+ if (c->source == NULL)
+ return NULL;
+
+ /* delete the socket source */
+ g_source_destroy(c->source);
+ g_source_unref(c->source);
+ c->source = NULL;
+
+ /* delete the timeout source */
+ g_source_destroy(c->timeout_source);
+ g_source_unref(c->timeout_source);
+ c->timeout_source = NULL;
+
+ /* close the socket */
+ close_socket(c->fd);
+
+ /* notify the handler */
+ c->handler->canceled(c->handler_ctx);
+
+ return NULL;
+}
+
+void
+tcp_connect_cancel(struct tcp_connect *c)
+{
+ if (c->source == NULL)
+ return;
+
+ io_thread_call(tcp_connect_cancel_callback, c);
+}
+
+void
+tcp_connect_free(struct tcp_connect *c)
+{
+ assert(c->source == NULL);
+
+ g_free(c);
+}
diff --git a/src/tcp_connect.h b/src/tcp_connect.h
new file mode 100644
index 000000000..bdbe85c14
--- /dev/null
+++ b/src/tcp_connect.h
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_TCP_CONNECT_H
+#define MPD_TCP_CONNECT_H
+
+#include <glib.h>
+
+struct sockaddr;
+
+struct tcp_connect_handler {
+ /**
+ * The connection was established successfully.
+ *
+ * @param fd a file descriptor that must be closed with
+ * close_socket() when finished
+ */
+ void (*success)(int fd, void *ctx);
+
+ /**
+ * An error has occurred. The method is responsible for
+ * freeing the GError.
+ */
+ void (*error)(GError *error, void *ctx);
+
+ /**
+ * The connection could not be established in the specified
+ * time span.
+ */
+ void (*timeout)(void *ctx);
+
+ /**
+ * The operation was canceled before a result was available.
+ */
+ void (*canceled)(void *ctx);
+};
+
+struct tcp_connect;
+
+/**
+ * Establish a TCP connection to the specified address.
+ *
+ * Note that the result may be available before this function returns.
+ *
+ * The caller must free this object with tcp_connect_free().
+ *
+ * @param timeout_ms time out after this number of milliseconds; 0
+ * means no timeout
+ * @param handle_r a handle that can be used to cancel the operation;
+ * the caller must initialize it to NULL
+ */
+void
+tcp_connect_address(const struct sockaddr *address, size_t address_length,
+ unsigned timeout_ms,
+ const struct tcp_connect_handler *handler, void *ctx,
+ struct tcp_connect **handle_r);
+
+/**
+ * Cancel the operation. It is possible that the result is delivered
+ * before the operation has been canceled; in that case, the
+ * canceled() handler method will not be invoked.
+ *
+ * Even after calling this function, tcp_connect_free() must still be
+ * called to free memory.
+ */
+void
+tcp_connect_cancel(struct tcp_connect *handle);
+
+/**
+ * Free memory used by this object.
+ *
+ * This function is not thread safe. It must not be called while
+ * other threads are still working with it. If no callback has been
+ * invoked so far, then you must call tcp_connect_cancel() to release
+ * I/O thread resources, before calling this function.
+ */
+void
+tcp_connect_free(struct tcp_connect *handle);
+
+#endif
diff --git a/src/tcp_socket.c b/src/tcp_socket.c
new file mode 100644
index 000000000..bfed4dc56
--- /dev/null
+++ b/src/tcp_socket.c
@@ -0,0 +1,377 @@
+/*
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "tcp_socket.h"
+#include "fifo_buffer.h"
+#include "io_thread.h"
+#include "glib_socket.h"
+
+#include <assert.h>
+#include <string.h>
+
+#ifdef WIN32
+#include <ws2tcpip.h>
+#include <winsock.h>
+#else
+#include <sys/socket.h>
+#include <netinet/in.h>
+#endif
+
+struct tcp_socket {
+ const struct tcp_socket_handler *handler;
+ void *handler_ctx;
+
+ GMutex *mutex;
+
+ GIOChannel *channel;
+ GSource *in_source, *out_source;
+
+ struct fifo_buffer *input, *output;
+};
+
+static gboolean
+tcp_event(GIOChannel *source, GIOCondition condition, gpointer data);
+
+static void
+tcp_socket_schedule_read(struct tcp_socket *s)
+{
+ assert(s->input != NULL);
+ assert(!fifo_buffer_is_full(s->input));
+
+ if (s->in_source != NULL)
+ return;
+
+ s->in_source = g_io_create_watch(s->channel,
+ G_IO_IN|G_IO_ERR|G_IO_HUP);
+ g_source_set_callback(s->in_source, (GSourceFunc)tcp_event, s, NULL);
+ g_source_attach(s->in_source, io_thread_context());
+}
+
+static void
+tcp_socket_unschedule_read(struct tcp_socket *s)
+{
+ if (s->in_source == NULL)
+ return;
+
+ g_source_destroy(s->in_source);
+ g_source_unref(s->in_source);
+ s->in_source = NULL;
+}
+
+static void
+tcp_socket_schedule_write(struct tcp_socket *s)
+{
+ assert(s->output != NULL);
+ assert(!fifo_buffer_is_empty(s->output));
+
+ if (s->out_source != NULL)
+ return;
+
+ s->out_source = g_io_create_watch(s->channel, G_IO_OUT);
+ g_source_set_callback(s->out_source, (GSourceFunc)tcp_event, s, NULL);
+ g_source_attach(s->out_source, io_thread_context());
+}
+
+static void
+tcp_socket_unschedule_write(struct tcp_socket *s)
+{
+ if (s->out_source == NULL)
+ return;
+
+ g_source_destroy(s->out_source);
+ g_source_unref(s->out_source);
+ s->out_source = NULL;
+}
+
+/**
+ * Close the socket. Caller must lock the mutex.
+ */
+static void
+tcp_socket_close(struct tcp_socket *s)
+{
+ tcp_socket_unschedule_read(s);
+ tcp_socket_unschedule_write(s);
+
+ if (s->channel != NULL) {
+ g_io_channel_unref(s->channel);
+ s->channel = NULL;
+ }
+
+ if (s->input != NULL) {
+ fifo_buffer_free(s->input);
+ s->input = NULL;
+ }
+
+ if (s->output != NULL) {
+ fifo_buffer_free(s->output);
+ s->output = NULL;
+ }
+}
+
+static gpointer
+tcp_socket_close_callback(gpointer data)
+{
+ struct tcp_socket *s = data;
+
+ g_mutex_lock(s->mutex);
+ tcp_socket_close(s);
+ g_mutex_unlock(s->mutex);
+
+ return NULL;
+}
+
+static void
+tcp_socket_close_indirect(struct tcp_socket *s)
+{
+ io_thread_call(tcp_socket_close_callback, s);
+
+ assert(s->channel == NULL);
+ assert(s->in_source == NULL);
+ assert(s->out_source == NULL);
+}
+
+static void
+tcp_handle_input(struct tcp_socket *s)
+{
+ size_t length;
+ const void *p = fifo_buffer_read(s->input, &length);
+ if (p == NULL)
+ return;
+
+ g_mutex_unlock(s->mutex);
+ size_t consumed = s->handler->data(p, length, s->handler_ctx);
+ g_mutex_lock(s->mutex);
+ if (consumed > 0 && s->input != NULL)
+ fifo_buffer_consume(s->input, consumed);
+}
+
+static bool
+tcp_in_event(struct tcp_socket *s)
+{
+ assert(s != NULL);
+ assert(s->channel != NULL);
+
+ g_mutex_lock(s->mutex);
+
+ size_t max_length;
+ void *p = fifo_buffer_write(s->input, &max_length);
+ if (p == NULL) {
+ GError *error = g_error_new_literal(tcp_socket_quark(), 0,
+ "buffer overflow");
+ tcp_socket_close(s);
+ g_mutex_unlock(s->mutex);
+ s->handler->error(error, s->handler_ctx);
+ return false;
+ }
+
+ gsize bytes_read;
+ GError *error = NULL;
+ GIOStatus status = g_io_channel_read_chars(s->channel,
+ p, max_length,
+ &bytes_read, &error);
+ switch (status) {
+ case G_IO_STATUS_NORMAL:
+ fifo_buffer_append(s->input, bytes_read);
+ tcp_handle_input(s);
+ g_mutex_unlock(s->mutex);
+ return true;
+
+ case G_IO_STATUS_AGAIN:
+ /* try again later */
+ g_mutex_unlock(s->mutex);
+ return true;
+
+ case G_IO_STATUS_EOF:
+ /* peer disconnected */
+ tcp_socket_close(s);
+ g_mutex_unlock(s->mutex);
+ s->handler->disconnected(s->handler_ctx);
+ return false;
+
+ case G_IO_STATUS_ERROR:
+ /* I/O error */
+ tcp_socket_close(s);
+ g_mutex_unlock(s->mutex);
+ s->handler->error(error, s->handler_ctx);
+ return false;
+ }
+
+ /* unreachable */
+ assert(false);
+ return true;
+}
+
+static bool
+tcp_out_event(struct tcp_socket *s)
+{
+ assert(s != NULL);
+ assert(s->channel != NULL);
+
+ g_mutex_lock(s->mutex);
+
+ size_t length;
+ const void *p = fifo_buffer_read(s->output, &length);
+ if (p == NULL) {
+ /* no more data in the output buffer, remove the
+ output event */
+ tcp_socket_unschedule_write(s);
+ g_mutex_unlock(s->mutex);
+ return false;
+ }
+
+ gsize bytes_written;
+ GError *error = NULL;
+ GIOStatus status = g_io_channel_write_chars(s->channel, p, length,
+ &bytes_written, &error);
+ switch (status) {
+ case G_IO_STATUS_NORMAL:
+ fifo_buffer_consume(s->output, bytes_written);
+ g_mutex_unlock(s->mutex);
+ return true;
+
+ case G_IO_STATUS_AGAIN:
+ tcp_socket_schedule_write(s);
+ g_mutex_unlock(s->mutex);
+ return true;
+
+ case G_IO_STATUS_EOF:
+ /* peer disconnected */
+ tcp_socket_close(s);
+ g_mutex_unlock(s->mutex);
+ s->handler->disconnected(s->handler_ctx);
+ return false;
+
+ case G_IO_STATUS_ERROR:
+ /* I/O error */
+ tcp_socket_close(s);
+ g_mutex_unlock(s->mutex);
+ s->handler->error(error, s->handler_ctx);
+ return false;
+ }
+
+ /* unreachable */
+ g_mutex_unlock(s->mutex);
+ assert(false);
+ return true;
+}
+
+static gboolean
+tcp_event(G_GNUC_UNUSED GIOChannel *source, GIOCondition condition,
+ gpointer data)
+{
+ struct tcp_socket *s = data;
+
+ assert(source == s->channel);
+
+ switch (condition) {
+ case G_IO_IN:
+ case G_IO_PRI:
+ return tcp_in_event(s);
+
+ case G_IO_OUT:
+ return tcp_out_event(s);
+
+ case G_IO_ERR:
+ case G_IO_HUP:
+ case G_IO_NVAL:
+ tcp_socket_close(s);
+ s->handler->disconnected(s->handler_ctx);
+ return false;
+ }
+
+ /* unreachable */
+ assert(false);
+ return false;
+}
+
+struct tcp_socket *
+tcp_socket_new(int fd,
+ const struct tcp_socket_handler *handler, void *ctx)
+{
+ assert(fd >= 0);
+ assert(handler != NULL);
+ assert(handler->data != NULL);
+ assert(handler->error != NULL);
+ assert(handler->disconnected != NULL);
+
+ struct tcp_socket *s = g_new(struct tcp_socket, 1);
+ s->handler = handler;
+ s->handler_ctx = ctx;
+ s->mutex = g_mutex_new();
+
+ g_mutex_lock(s->mutex);
+
+ s->channel = g_io_channel_new_socket(fd);
+ /* GLib is responsible for closing the file descriptor */
+ g_io_channel_set_close_on_unref(s->channel, true);
+ /* NULL encoding means the stream is binary safe */
+ g_io_channel_set_encoding(s->channel, NULL, NULL);
+ /* no buffering */
+ g_io_channel_set_buffered(s->channel, false);
+
+ s->input = fifo_buffer_new(4096);
+ s->output = fifo_buffer_new(4096);
+
+ s->in_source = NULL;
+ s->out_source = NULL;
+
+ tcp_socket_schedule_read(s);
+
+ g_mutex_unlock(s->mutex);
+
+ return s;
+}
+
+void
+tcp_socket_free(struct tcp_socket *s)
+{
+ tcp_socket_close_indirect(s);
+ g_mutex_free(s->mutex);
+ g_free(s);
+}
+
+bool
+tcp_socket_send(struct tcp_socket *s, const void *data, size_t length)
+{
+ assert(s != NULL);
+
+ g_mutex_lock(s->mutex);
+
+ if (s->output == NULL || s->channel == NULL) {
+ /* already disconnected */
+ g_mutex_unlock(s->mutex);
+ return false;
+ }
+
+ size_t max_length;
+ void *p = fifo_buffer_write(s->output, &max_length);
+ if (p == NULL || max_length < length) {
+ /* buffer is full */
+ g_mutex_unlock(s->mutex);
+ return false;
+ }
+
+ memcpy(p, data, length);
+ fifo_buffer_append(s->output, length);
+ tcp_socket_schedule_write(s);
+
+ g_mutex_unlock(s->mutex);
+ return true;
+}
+
diff --git a/src/tcp_socket.h b/src/tcp_socket.h
new file mode 100644
index 000000000..b6b367b86
--- /dev/null
+++ b/src/tcp_socket.h
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_TCP_SOCKET_H
+#define MPD_TCP_SOCKET_H
+
+#include <glib.h>
+
+#include <stdbool.h>
+#include <stddef.h>
+
+struct sockaddr;
+
+struct tcp_socket_handler {
+ /**
+ * New data has arrived.
+ *
+ * @return the number of bytes consumed; 0 if more data is
+ * needed
+ */
+ size_t (*data)(const void *data, size_t length, void *ctx);
+
+ void (*error)(GError *error, void *ctx);
+
+ void (*disconnected)(void *ctx);
+};
+
+static inline GQuark
+tcp_socket_quark(void)
+{
+ return g_quark_from_static_string("tcp_socket");
+}
+
+G_GNUC_MALLOC
+struct tcp_socket *
+tcp_socket_new(int fd,
+ const struct tcp_socket_handler *handler, void *ctx);
+
+void
+tcp_socket_free(struct tcp_socket *s);
+
+bool
+tcp_socket_send(struct tcp_socket *s, const void *data, size_t length);
+
+#endif
diff --git a/src/text_file.c b/src/text_file.c
index 355217aba..3674e5ce2 100644
--- a/src/text_file.c
+++ b/src/text_file.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/text_file.h b/src/text_file.h
index d016f8f7a..9dd810943 100644
--- a/src/text_file.h
+++ b/src/text_file.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/text_input_stream.c b/src/text_input_stream.c
index 6d0436d41..4a858fc85 100644
--- a/src/text_input_stream.c
+++ b/src/text_input_stream.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -74,8 +74,8 @@ text_input_stream_read(struct text_input_stream *tis)
newline character */
--length;
- nbytes = input_stream_read(tis->is, dest, length,
- &error);
+ nbytes = input_stream_lock_read(tis->is, dest, length,
+ &error);
if (nbytes > 0)
fifo_buffer_append(tis->buffer, nbytes);
else if (error != NULL) {
diff --git a/src/text_input_stream.h b/src/text_input_stream.h
index a1fda065d..9b3245689 100644
--- a/src/text_input_stream.h
+++ b/src/text_input_stream.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/timer.c b/src/timer.c
index ba82fc522..691ab76be 100644
--- a/src/timer.c
+++ b/src/timer.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -37,9 +37,9 @@ static uint64_t now(void)
return ((uint64_t)tv.tv_sec * 1000000) + tv.tv_usec;
}
-Timer *timer_new(const struct audio_format *af)
+struct timer *timer_new(const struct audio_format *af)
{
- Timer *timer = g_new(Timer, 1);
+ struct timer *timer = g_new(struct timer, 1);
timer->time = 0;
timer->started = 0;
timer->rate = af->sample_rate * audio_format_frame_size(af);
@@ -47,24 +47,24 @@ Timer *timer_new(const struct audio_format *af)
return timer;
}
-void timer_free(Timer *timer)
+void timer_free(struct timer *timer)
{
g_free(timer);
}
-void timer_start(Timer *timer)
+void timer_start(struct timer *timer)
{
timer->time = now();
timer->started = 1;
}
-void timer_reset(Timer *timer)
+void timer_reset(struct timer *timer)
{
timer->time = 0;
timer->started = 0;
}
-void timer_add(Timer *timer, int size)
+void timer_add(struct timer *timer, int size)
{
assert(timer->started);
@@ -72,7 +72,7 @@ void timer_add(Timer *timer, int size)
}
unsigned
-timer_delay(const Timer *timer)
+timer_delay(const struct timer *timer)
{
int64_t delay = (int64_t)(timer->time - now()) / 1000;
if (delay < 0)
@@ -84,7 +84,7 @@ timer_delay(const Timer *timer)
return delay;
}
-void timer_sync(Timer *timer)
+void timer_sync(struct timer *timer)
{
int64_t sleep_duration;
diff --git a/src/timer.h b/src/timer.h
index bbd895b31..184881249 100644
--- a/src/timer.h
+++ b/src/timer.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -24,28 +24,28 @@
struct audio_format;
-typedef struct _Timer {
+struct timer {
uint64_t time;
int started;
int rate;
-} Timer;
+};
-Timer *timer_new(const struct audio_format *af);
+struct timer *timer_new(const struct audio_format *af);
-void timer_free(Timer *timer);
+void timer_free(struct timer *timer);
-void timer_start(Timer *timer);
+void timer_start(struct timer *timer);
-void timer_reset(Timer *timer);
+void timer_reset(struct timer *timer);
-void timer_add(Timer *timer, int size);
+void timer_add(struct timer *timer, int size);
/**
* Returns the number of milliseconds to sleep to get back to sync.
*/
unsigned
-timer_delay(const Timer *timer);
+timer_delay(const struct timer *timer);
-void timer_sync(Timer *timer);
+void timer_sync(struct timer *timer);
#endif
diff --git a/src/tokenizer.c b/src/tokenizer.c
index 2b9e05070..bbb34e100 100644
--- a/src/tokenizer.c
+++ b/src/tokenizer.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -19,6 +19,7 @@
#include "config.h"
#include "tokenizer.h"
+#include "string_util.h"
#include <stdbool.h>
#include <assert.h>
@@ -72,7 +73,7 @@ tokenizer_next_word(char **input_p, GError **error_r)
/* a whitespace: the word ends here */
*input = 0;
/* skip all following spaces, too */
- input = g_strchug(input + 1);
+ input = strchug_fast(input + 1);
break;
}
@@ -126,7 +127,7 @@ tokenizer_next_unquoted(char **input_p, GError **error_r)
/* a whitespace: the word ends here */
*input = 0;
/* skip all following spaces, too */
- input = g_strchug(input + 1);
+ input = strchug_fast(input + 1);
break;
}
@@ -205,7 +206,7 @@ tokenizer_next_string(char **input_p, GError **error_r)
/* finish the string and return it */
*dest = 0;
- *input_p = g_strchug(input);
+ *input_p = strchug_fast(input);
return word;
}
diff --git a/src/tokenizer.h b/src/tokenizer.h
index 61ff398a4..d55eb3ca6 100644
--- a/src/tokenizer.h
+++ b/src/tokenizer.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/udp_server.c b/src/udp_server.c
new file mode 100644
index 000000000..f96b40b6b
--- /dev/null
+++ b/src/udp_server.c
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "udp_server.h"
+#include "io_thread.h"
+#include "glib_socket.h"
+#include "gcc.h"
+
+#include <glib.h>
+#include <unistd.h>
+#include <sys/time.h>
+#include <errno.h>
+
+#ifdef WIN32
+#include <ws2tcpip.h>
+#include <winsock.h>
+#else
+#include <sys/socket.h>
+#include <netinet/in.h>
+#endif
+
+#if GCC_CHECK_VERSION(4, 2)
+/* allow C99 initialisers on struct sockaddr_in, even if the
+ (non-portable) attribute "sin_zero" is missing */
+#pragma GCC diagnostic ignored "-Wmissing-field-initializers"
+#endif
+
+struct udp_server {
+ const struct udp_server_handler *handler;
+ void *handler_ctx;
+
+ int fd;
+ GIOChannel *channel;
+ GSource *source;
+
+ char buffer[8192];
+};
+
+static gboolean
+udp_in_event(G_GNUC_UNUSED GIOChannel *source,
+ G_GNUC_UNUSED GIOCondition condition,
+ gpointer data)
+{
+ struct udp_server *udp = data;
+
+ struct sockaddr_storage address_storage;
+ struct sockaddr *address = (struct sockaddr *)&address_storage;
+ socklen_t address_length = sizeof(address_storage);
+
+ ssize_t nbytes = recvfrom(udp->fd, udp->buffer, sizeof(udp->buffer),
+#ifdef WIN32
+ 0,
+#else
+ MSG_DONTWAIT,
+#endif
+ address, &address_length);
+ if (nbytes <= 0)
+ return true;
+
+ udp->handler->datagram(udp->fd, udp->buffer, nbytes,
+ address, address_length, udp->handler_ctx);
+ return true;
+}
+
+struct udp_server *
+udp_server_new(unsigned port,
+ const struct udp_server_handler *handler, void *ctx,
+ GError **error_r)
+{
+ int fd = socket(PF_INET, SOCK_DGRAM, 0);
+ if (fd < 0) {
+ g_set_error(error_r, udp_server_quark(), errno,
+ "failed to create UDP socket: %s",
+ g_strerror(errno));
+ return NULL;
+ }
+
+ const struct sockaddr_in address = {
+ .sin_family = AF_INET,
+ .sin_addr = {
+ .s_addr = htonl(INADDR_ANY),
+ },
+ .sin_port = htons(port),
+#if defined(__linux__) && !GCC_CHECK_VERSION(4, 2)
+ .sin_zero = { 0 },
+#endif
+ };
+
+ if (bind(fd, (const struct sockaddr *)&address, sizeof(address)) < 0) {
+ g_set_error(error_r, udp_server_quark(), errno,
+ "failed to bind UDP port %u: %s",
+ port, g_strerror(errno));
+ close(fd);
+ return NULL;
+ }
+
+ struct udp_server *udp = g_new(struct udp_server, 1);
+ udp->handler = handler;
+ udp->handler_ctx = ctx;
+
+ udp->fd = fd;
+ udp->channel = g_io_channel_new_socket(fd);
+ /* NULL encoding means the stream is binary safe */
+ g_io_channel_set_encoding(udp->channel, NULL, NULL);
+ /* no buffering */
+ g_io_channel_set_buffered(udp->channel, false);
+
+ udp->source = g_io_create_watch(udp->channel, G_IO_IN);
+ g_source_set_callback(udp->source, (GSourceFunc)udp_in_event, udp,
+ NULL);
+ g_source_attach(udp->source, io_thread_context());
+
+ return udp;
+}
+
+void
+udp_server_free(struct udp_server *udp)
+{
+ g_source_destroy(udp->source);
+ g_source_unref(udp->source);
+ g_io_channel_unref(udp->channel);
+ close(udp->fd);
+ g_free(udp);
+}
diff --git a/src/dirvec.h b/src/udp_server.h
index a1a97d9f1..9e3471a45 100644
--- a/src/dirvec.h
+++ b/src/udp_server.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -17,37 +17,36 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
-#ifndef MPD_DIRVEC_H
-#define MPD_DIRVEC_H
+#ifndef MPD_UDP_SERVER_H
+#define MPD_UDP_SERVER_H
-#include <stddef.h>
-
-struct dirvec {
- struct directory **base;
- size_t nr;
-};
-
-void dirvec_init(void);
+#include <glib.h>
-void dirvec_deinit(void);
-
-void dirvec_sort(struct dirvec *dv);
-
-struct directory *dirvec_find(const struct dirvec *dv, const char *path);
+#include <stddef.h>
-int dirvec_delete(struct dirvec *dv, struct directory *del);
+struct sockaddr;
-void dirvec_add(struct dirvec *dv, struct directory *add);
+struct udp_server_handler {
+ /**
+ * A datagram was received.
+ */
+ void (*datagram)(int fd, const void *data, size_t length,
+ const struct sockaddr *source_address,
+ size_t source_address_length, void *ctx);
+};
-static inline void
-dirvec_clear(struct dirvec *dv)
+static inline GQuark
+udp_server_quark(void)
{
- dv->nr = 0;
+ return g_quark_from_static_string("udp_server");
}
-void dirvec_destroy(struct dirvec *dv);
+struct udp_server *
+udp_server_new(unsigned port,
+ const struct udp_server_handler *handler, void *ctx,
+ GError **error_r);
-int dirvec_for_each(const struct dirvec *dv,
- int (*fn)(struct directory *, void *), void *arg);
+void
+udp_server_free(struct udp_server *udp);
-#endif /* DIRVEC_H */
+#endif
diff --git a/src/update.c b/src/update.c
index d57fb114d..2df1a5bf6 100644
--- a/src/update.c
+++ b/src/update.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -19,6 +19,7 @@
#include "config.h"
#include "update_internal.h"
+#include "update_remove.h"
#include "update.h"
#include "database.h"
#include "mapper.h"
@@ -68,8 +69,14 @@ static void * update_task(void *_path)
modified = update_walk(path, discard);
- if (modified || !db_exists())
- db_save();
+ if (modified || !db_exists()) {
+ GError *error = NULL;
+ if (!db_save(&error)) {
+ g_warning("Failed to save database: %s",
+ error->message);
+ g_error_free(error);
+ }
+ }
if (path != NULL && *path != 0)
g_debug("finished: %s", path);
diff --git a/src/update.h b/src/update.h
index 3f8a6f6a4..3d586b694 100644
--- a/src/update.h
+++ b/src/update.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/update_db.c b/src/update_db.c
new file mode 100644
index 000000000..8982a53e2
--- /dev/null
+++ b/src/update_db.c
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2003-2012 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h" /* must be first for large file support */
+#include "update_db.h"
+#include "update_remove.h"
+#include "directory.h"
+#include "song.h"
+#include "playlist_vector.h"
+#include "db_lock.h"
+
+#include <glib.h>
+#include <assert.h>
+
+void
+delete_song(struct directory *dir, struct song *del)
+{
+ assert(del->parent == dir);
+
+ /* first, prevent traversers in main task from getting this */
+ directory_remove_song(dir, del);
+
+ db_unlock(); /* temporary unlock, because update_remove_song() blocks */
+
+ /* now take it out of the playlist (in the main_task) */
+ update_remove_song(del);
+
+ /* finally, all possible references gone, free it */
+ song_free(del);
+
+ db_lock();
+}
+
+/**
+ * Recursively remove all sub directories and songs from a directory,
+ * leaving an empty directory.
+ *
+ * Caller must lock the #db_mutex.
+ */
+static void
+clear_directory(struct directory *directory)
+{
+ struct directory *child, *n;
+ directory_for_each_child_safe(child, n, directory)
+ delete_directory(child);
+
+ struct song *song, *ns;
+ directory_for_each_song_safe(song, ns, directory) {
+ assert(song->parent == directory);
+ delete_song(directory, song);
+ }
+}
+
+void
+delete_directory(struct directory *directory)
+{
+ assert(directory->parent != NULL);
+
+ clear_directory(directory);
+
+ directory_delete(directory);
+}
+
+bool
+delete_name_in(struct directory *parent, const char *name)
+{
+ bool modified = false;
+
+ db_lock();
+ struct directory *directory = directory_get_child(parent, name);
+
+ if (directory != NULL) {
+ delete_directory(directory);
+ modified = true;
+ }
+
+ struct song *song = directory_get_song(parent, name);
+ if (song != NULL) {
+ delete_song(parent, song);
+ modified = true;
+ }
+
+ playlist_vector_remove(&parent->playlists, name);
+
+ db_unlock();
+
+ return modified;
+}
diff --git a/src/update_db.h b/src/update_db.h
new file mode 100644
index 000000000..0a9e46b05
--- /dev/null
+++ b/src/update_db.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2003-2012 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_UPDATE_DB_H
+#define MPD_UPDATE_DB_H
+
+#include "check.h"
+
+#include <stdbool.h>
+
+struct directory;
+struct song;
+
+/**
+ * Caller must lock the #db_mutex.
+ */
+void
+delete_song(struct directory *parent, struct song *song);
+
+/**
+ * Recursively free a directory and all its contents.
+ *
+ * Caller must lock the #db_mutex.
+ */
+void
+delete_directory(struct directory *directory);
+
+/**
+ * Caller must NOT lock the #db_mutex.
+ *
+ * @return true if the database was modified
+ */
+bool
+delete_name_in(struct directory *parent, const char *name);
+
+#endif
diff --git a/src/update_internal.h b/src/update_internal.h
index 65744f0d6..9da74d523 100644
--- a/src/update_internal.h
+++ b/src/update_internal.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -47,18 +47,4 @@ update_walk_global_finish(void);
bool
update_walk(const char *path, bool discard);
-void
-update_remove_global_init(void);
-
-void
-update_remove_global_finish(void);
-
-/**
- * Sends a signal to the main thread which will in turn remove the
- * song: from the sticker database and from the playlist. This
- * serialized access is implemented to avoid excessive locking.
- */
-void
-update_remove_song(const struct song *song);
-
#endif
diff --git a/src/update_io.c b/src/update_io.c
new file mode 100644
index 000000000..887ebe2e5
--- /dev/null
+++ b/src/update_io.c
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2003-2012 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h" /* must be first for large file support */
+#include "update_io.h"
+#include "mapper.h"
+#include "directory.h"
+
+#include <glib.h>
+
+#include <errno.h>
+#include <unistd.h>
+
+int
+stat_directory(const struct directory *directory, struct stat *st)
+{
+ char *path_fs = map_directory_fs(directory);
+ if (path_fs == NULL)
+ return -1;
+
+ int 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;
+}
+
+int
+stat_directory_child(const struct directory *parent, const char *name,
+ struct stat *st)
+{
+ char *path_fs = map_directory_child_fs(parent, name);
+ if (path_fs == NULL)
+ return -1;
+
+ int 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;
+}
+
+bool
+directory_exists(const struct directory *directory)
+{
+ char *path_fs = map_directory_fs(directory);
+ if (path_fs == NULL)
+ /* invalid path: cannot exist */
+ return false;
+
+ GFileTest test = directory->device == DEVICE_INARCHIVE ||
+ directory->device == DEVICE_CONTAINER
+ ? G_FILE_TEST_IS_REGULAR
+ : G_FILE_TEST_IS_DIR;
+
+ bool exists = g_file_test(path_fs, test);
+ g_free(path_fs);
+
+ return exists;
+}
+
+bool
+directory_child_is_regular(const struct directory *directory,
+ const char *name_utf8)
+{
+ char *path_fs = map_directory_child_fs(directory, name_utf8);
+ if (path_fs == NULL)
+ return false;
+
+ struct stat st;
+ bool is_regular = stat(path_fs, &st) == 0 && S_ISREG(st.st_mode);
+ g_free(path_fs);
+
+ return is_regular;
+}
+
+bool
+directory_child_access(const struct directory *directory,
+ const char *name, int mode)
+{
+#ifdef WIN32
+ /* access() is useless on WIN32 */
+ (void)directory;
+ (void)name;
+ (void)mode;
+ 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
+}
diff --git a/src/update_io.h b/src/update_io.h
new file mode 100644
index 000000000..6ff1ccebd
--- /dev/null
+++ b/src/update_io.h
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2003-2012 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_UPDATE_IO_H
+#define MPD_UPDATE_IO_H
+
+#include "check.h"
+
+#include <stdbool.h>
+#include <sys/stat.h>
+
+struct directory;
+
+int
+stat_directory(const struct directory *directory, struct stat *st);
+
+int
+stat_directory_child(const struct directory *parent, const char *name,
+ struct stat *st);
+
+bool
+directory_exists(const struct directory *directory);
+
+bool
+directory_child_is_regular(const struct directory *directory,
+ const char *name_utf8);
+
+/**
+ * Checks if the given permissions on the mapped file are given.
+ */
+bool
+directory_child_access(const struct directory *directory,
+ const char *name, int mode);
+
+#endif
diff --git a/src/update_queue.c b/src/update_queue.c
index d7b2d4e5f..4de250cc2 100644
--- a/src/update_queue.c
+++ b/src/update_queue.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/update_remove.c b/src/update_remove.c
index 8d60be222..f443f5eb2 100644
--- a/src/update_remove.c
+++ b/src/update_remove.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -18,11 +18,11 @@
*/
#include "config.h" /* must be first for large file support */
-#include "update_internal.h"
-#include "notify.h"
+#include "update_remove.h"
#include "event_pipe.h"
#include "song.h"
#include "playlist.h"
+#include "main.h"
#ifdef ENABLE_SQLITE
#include "sticker.h"
@@ -35,7 +35,8 @@
static const struct song *removed_song;
-static struct notify remove_notify;
+static GMutex *remove_mutex;
+static GCond *remove_cond;
/**
* Safely remove a song from the database. This must be done in the
@@ -58,16 +59,20 @@ song_remove_event(void)
sticker_song_delete(removed_song);
#endif
- playlist_delete_song(&g_playlist, removed_song);
- removed_song = NULL;
+ playlist_delete_song(&g_playlist, global_player_control, removed_song);
- notify_signal(&remove_notify);
+ /* clear "removed_song" and send signal to update thread */
+ g_mutex_lock(remove_mutex);
+ removed_song = NULL;
+ g_cond_signal(remove_cond);
+ g_mutex_unlock(remove_mutex);
}
void
update_remove_global_init(void)
{
- notify_init(&remove_notify);
+ remove_mutex = g_mutex_new();
+ remove_cond = g_cond_new();
event_pipe_register(PIPE_EVENT_DELETE, song_remove_event);
}
@@ -75,7 +80,8 @@ update_remove_global_init(void)
void
update_remove_global_finish(void)
{
- notify_deinit(&remove_notify);
+ g_mutex_free(remove_mutex);
+ g_cond_free(remove_cond);
}
void
@@ -87,8 +93,10 @@ update_remove_song(const struct song *song)
event_pipe_emit(PIPE_EVENT_DELETE);
- do {
- notify_wait(&remove_notify);
- } while (removed_song != NULL);
+ g_mutex_lock(remove_mutex);
+
+ while (removed_song != NULL)
+ g_cond_wait(remove_cond, remove_mutex);
+ g_mutex_unlock(remove_mutex);
}
diff --git a/src/update_remove.h b/src/update_remove.h
new file mode 100644
index 000000000..479ef83ff
--- /dev/null
+++ b/src/update_remove.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2003-2012 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_UPDATE_REMOVE_H
+#define MPD_UPDATE_REMOVE_H
+
+#include "check.h"
+
+struct song;
+
+void
+update_remove_global_init(void);
+
+void
+update_remove_global_finish(void);
+
+/**
+ * Sends a signal to the main thread which will in turn remove the
+ * song: from the sticker database and from the playlist. This
+ * serialized access is implemented to avoid excessive locking.
+ */
+void
+update_remove_song(const struct song *song);
+
+#endif
diff --git a/src/update_walk.c b/src/update_walk.c
index 5d2f778ff..9ca9115bd 100644
--- a/src/update_walk.c
+++ b/src/update_walk.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -19,10 +19,14 @@
#include "config.h" /* must be first for large file support */
#include "update_internal.h"
+#include "update_io.h"
+#include "update_db.h"
#include "database.h"
+#include "db_lock.h"
#include "exclude.h"
#include "directory.h"
#include "song.h"
+#include "playlist_vector.h"
#include "uri.h"
#include "mapper.h"
#include "path.h"
@@ -30,6 +34,8 @@
#include "decoder_plugin.h"
#include "playlist_list.h"
#include "conf.h"
+#include "tag.h"
+#include "tag_handler.h"
#ifdef ENABLE_ARCHIVE
#include "archive_list.h"
@@ -90,108 +96,13 @@ directory_set_stat(struct directory *dir, const struct stat *st)
}
static void
-delete_song(struct directory *dir, struct song *del)
-{
- /* first, prevent traversers in main task from getting this */
- songvec_delete(&dir->songs, del);
-
- /* now take it out of the playlist (in the main_task) */
- update_remove_song(del);
-
- /* finally, all possible references gone, free it */
- song_free(del);
-}
-
-static int
-delete_each_song(struct song *song, G_GNUC_UNUSED void *data)
-{
- struct directory *directory = data;
- assert(song->parent == directory);
- delete_song(directory, song);
- return 0;
-}
-
-static void
-delete_directory(struct directory *directory);
-
-/**
- * Recursively remove all sub directories and songs from a directory,
- * leaving an empty directory.
- */
-static void
-clear_directory(struct directory *directory)
-{
- int i;
-
- for (i = directory->children.nr; --i >= 0;)
- delete_directory(directory->children.base[i]);
-
- assert(directory->children.nr == 0);
-
- songvec_for_each(&directory->songs, delete_each_song, directory);
-}
-
-/**
- * Recursively free a directory and all its contents.
- */
-static void
-delete_directory(struct directory *directory)
-{
- assert(directory->parent != NULL);
-
- clear_directory(directory);
-
- dirvec_delete(&directory->parent->children, directory);
- directory_free(directory);
-}
-
-static void
-delete_name_in(struct directory *parent, const char *name)
-{
- struct directory *directory = directory_get_child(parent, name);
- struct song *song = songvec_find(&parent->songs, name);
-
- if (directory != NULL) {
- delete_directory(directory);
- modified = true;
- }
-
- if (song != NULL) {
- delete_song(parent, song);
- modified = true;
- }
-
- playlist_vector_remove(&parent->playlists, name);
-}
-
-/* passed to songvec_for_each */
-static int
-delete_song_if_excluded(struct song *song, void *_data)
-{
- GSList *exclude_list = _data;
- char *name_fs;
-
- assert(song->parent != NULL);
-
- name_fs = utf8_to_fs_charset(song->uri);
- if (exclude_list_check(exclude_list, name_fs)) {
- delete_song(song->parent, song);
- modified = true;
- }
-
- g_free(name_fs);
- return 0;
-}
-
-static void
remove_excluded_from_directory(struct directory *directory,
GSList *exclude_list)
{
- int i;
- struct dirvec *dv = &directory->children;
+ db_lock();
- for (i = dv->nr; --i >= 0; ) {
- struct directory *child = dv->base[i];
+ struct directory *child, *n;
+ directory_for_each_child_safe(child, n, directory) {
char *name_fs = utf8_to_fs_charset(directory_get_name(child));
if (exclude_list_check(exclude_list, name_fs)) {
@@ -202,128 +113,61 @@ remove_excluded_from_directory(struct directory *directory,
g_free(name_fs);
}
- songvec_for_each(&directory->songs,
- delete_song_if_excluded, exclude_list);
-}
+ struct song *song, *ns;
+ directory_for_each_song_safe(song, ns, directory) {
+ assert(song->parent == directory);
-/* passed to songvec_for_each */
-static int
-delete_song_if_removed(struct song *song, void *_data)
-{
- struct directory *dir = _data;
- char *path;
- struct stat st;
+ char *name_fs = utf8_to_fs_charset(song->uri);
+ if (exclude_list_check(exclude_list, name_fs)) {
+ delete_song(directory, song);
+ modified = true;
+ }
- if ((path = map_song_fs(song)) == NULL ||
- stat(path, &st) < 0 || !S_ISREG(st.st_mode)) {
- delete_song(dir, song);
- modified = true;
+ g_free(name_fs);
}
- g_free(path);
- return 0;
-}
-
-static bool
-directory_exists(const struct directory *directory)
-{
- char *path_fs;
- GFileTest test;
- bool exists;
-
- path_fs = map_directory_fs(directory);
- if (path_fs == NULL)
- /* invalid path: cannot exist */
- return false;
-
- test = directory->device == DEVICE_INARCHIVE ||
- directory->device == DEVICE_CONTAINER
- ? G_FILE_TEST_IS_REGULAR
- : G_FILE_TEST_IS_DIR;
-
- exists = g_file_test(path_fs, test);
- g_free(path_fs);
-
- return exists;
-}
-
-static bool
-directory_child_is_regular(const struct directory *directory,
- const char *name_utf8)
-{
- char *path_fs = map_directory_child_fs(directory, name_utf8);
- if (path_fs == NULL)
- return false;
-
- struct stat st;
- bool is_regular = stat(path_fs, &st) == 0 && S_ISREG(st.st_mode);
- g_free(path_fs);
-
- return is_regular;
+ db_unlock();
}
static void
removeDeletedFromDirectory(struct directory *directory)
{
- int i;
- struct dirvec *dv = &directory->children;
-
- for (i = dv->nr; --i >= 0; ) {
- if (directory_exists(dv->base[i]))
+ struct directory *child, *n;
+ directory_for_each_child_safe(child, n, directory) {
+ if (directory_exists(child))
continue;
- g_debug("removing directory: %s", dv->base[i]->path);
- delete_directory(dv->base[i]);
+ db_lock();
+ delete_directory(child);
+ db_unlock();
+
modified = true;
}
- songvec_for_each(&directory->songs, delete_song_if_removed, directory);
-
- for (const struct playlist_metadata *pm = directory->playlists.head;
- pm != NULL;) {
- const struct playlist_metadata *next = pm->next;
+ struct song *song, *ns;
+ directory_for_each_song_safe(song, ns, directory) {
+ char *path;
+ struct stat st;
+ if ((path = map_song_fs(song)) == NULL ||
+ stat(path, &st) < 0 || !S_ISREG(st.st_mode)) {
+ db_lock();
+ delete_song(directory, song);
+ db_unlock();
- if (!directory_child_is_regular(directory, pm->name))
- playlist_vector_remove(&directory->playlists, pm->name);
+ modified = true;
+ }
- pm = next;
+ g_free(path);
}
-}
-
-static int
-stat_directory(const struct directory *directory, struct stat *st)
-{
- char *path_fs;
- int ret;
- path_fs = map_directory_fs(directory);
- 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;
-}
-
-static int
-stat_directory_child(const struct directory *parent, const char *name,
- struct stat *st)
-{
- char *path_fs;
- int ret;
-
- path_fs = map_directory_child_fs(parent, name);
- 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;
+ struct playlist_metadata *pm, *np;
+ directory_for_each_playlist_safe(pm, np, directory) {
+ if (!directory_child_is_regular(directory, pm->name)) {
+ db_lock();
+ playlist_vector_remove(&directory->playlists, pm->name);
+ db_unlock();
+ }
+ }
}
#ifndef G_OS_WIN32
@@ -363,45 +207,21 @@ inodeFoundInParent(struct directory *parent, ino_t inode, dev_t device)
return 0;
}
-static struct directory *
-make_subdir(struct directory *parent, const char *name)
-{
- struct directory *directory;
-
- directory = directory_get_child(parent, name);
- if (directory == NULL) {
- char *path;
-
- if (directory_is_root(parent))
- path = NULL;
- else
- name = path = g_strconcat(directory_get_path(parent),
- "/", name, NULL);
-
- directory = directory_new_child(parent, name);
- g_free(path);
- }
-
- return directory;
-}
-
#ifdef ENABLE_ARCHIVE
static void
update_archive_tree(struct directory *directory, char *name)
{
struct directory *subdir;
- struct song *song;
char *tmp;
tmp = strchr(name, '/');
if (tmp) {
*tmp = 0;
//add dir is not there already
- if ((subdir = dirvec_find(&directory->children, name)) == NULL) {
- //create new directory
- subdir = make_subdir(directory, name);
- subdir->device = DEVICE_INARCHIVE;
- }
+ db_lock();
+ subdir = directory_make_child(directory, name);
+ subdir->device = DEVICE_INARCHIVE;
+ db_unlock();
//create directories first
update_archive_tree(subdir, tmp+1);
} else {
@@ -410,11 +230,13 @@ update_archive_tree(struct directory *directory, char *name)
return;
}
//add file
- song = songvec_find(&directory->songs, name);
+ db_lock();
+ struct song *song = directory_get_song(directory, name);
+ db_unlock();
if (song == NULL) {
song = song_file_load(name, directory);
if (song != NULL) {
- songvec_add(&directory->songs, song);
+ directory_add_song(directory, song);
modified = true;
g_message("added %s/%s",
directory_get_path(directory), name);
@@ -442,7 +264,9 @@ update_archive_file(struct directory *parent, const char *name,
struct directory *directory;
char *filepath;
- directory = dirvec_find(&parent->children, name);
+ db_lock();
+ directory = directory_get_child(parent, name);
+ db_unlock();
if (directory != NULL && directory->mtime == st->st_mtime &&
!walk_discard)
/* MPD has already scanned the archive, and it hasn't
@@ -465,10 +289,12 @@ update_archive_file(struct directory *parent, const char *name,
if (directory == NULL) {
g_debug("creating archive directory: %s", name);
- directory = make_subdir(parent, name);
+ db_lock();
+ directory = directory_new_child(parent, name);
/* mark this directory as archive (we use device for
this) */
directory->device = DEVICE_INARCHIVE;
+ db_unlock();
}
directory->mtime = st->st_mtime;
@@ -494,7 +320,9 @@ update_container_file( struct directory* directory,
char* vtrack = NULL;
unsigned int tnum = 0;
char* pathname = map_directory_child_fs(directory, name);
- struct directory* contdir = dirvec_find(&directory->children, name);
+
+ db_lock();
+ struct directory *contdir = directory_get_child(directory, name);
// directory exists already
if (contdir != NULL)
@@ -510,14 +338,16 @@ update_container_file( struct directory* directory,
modified = true;
}
else {
+ db_unlock();
g_free(pathname);
return true;
}
}
- contdir = make_subdir(directory, name);
+ contdir = directory_make_child(directory, name);
contdir->mtime = st->st_mtime;
contdir->device = DEVICE_CONTAINER;
+ db_unlock();
while ((vtrack = plugin->container_scan(pathname, ++tnum)) != NULL)
{
@@ -529,10 +359,12 @@ update_container_file( struct directory* directory,
child_path_fs = map_directory_child_fs(contdir, vtrack);
- song->tag = plugin->tag_dup(child_path_fs);
+ song->tag = tag_new();
+ decoder_plugin_scan_file(plugin, child_path_fs,
+ &add_tag_handler, song->tag);
g_free(child_path_fs);
- songvec_add(&contdir->songs, song);
+ directory_add_song(contdir, song);
modified = true;
@@ -545,37 +377,76 @@ update_container_file( struct directory* directory,
if (tnum == 1)
{
+ db_lock();
delete_directory(contdir);
+ db_unlock();
return false;
}
else
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;
- (void)mode;
- 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;
+static void
+update_song_file(struct directory *directory,
+ const char *name, const struct stat *st,
+ const struct decoder_plugin *plugin)
+{
+ db_lock();
+ struct song *song = directory_get_song(directory, name);
+ db_unlock();
+
+ if (!directory_child_access(directory, name, R_OK)) {
+ g_warning("no read permissions on %s/%s",
+ directory_get_path(directory), name);
+ if (song != NULL) {
+ db_lock();
+ delete_song(directory, song);
+ db_unlock();
+ }
- bool success = access(path, mode) == 0 || errno != EACCES;
- g_free(path);
- return success;
-#endif
+ return;
+ }
+
+ if (!(song != NULL && st->st_mtime == song->mtime &&
+ !walk_discard) &&
+ plugin->container_scan != NULL &&
+ update_container_file(directory, name, st, plugin)) {
+ if (song != NULL) {
+ db_lock();
+ delete_song(directory, song);
+ db_unlock();
+ }
+
+ return;
+ }
+
+ if (song == NULL) {
+ g_debug("reading %s/%s",
+ directory_get_path(directory), name);
+ song = song_file_load(name, directory);
+ if (song == NULL) {
+ g_debug("ignoring unrecognized file %s/%s",
+ directory_get_path(directory), name);
+ return;
+ }
+
+ directory_add_song(directory, song);
+ modified = true;
+ g_message("added %s/%s",
+ directory_get_path(directory), name);
+ } else if (st->st_mtime != song->mtime || walk_discard) {
+ g_message("updating %s/%s",
+ directory_get_path(directory), name);
+ if (!song_file_update(song)) {
+ g_debug("deleting unrecognized file %s/%s",
+ directory_get_path(directory), name);
+ db_lock();
+ delete_song(directory, song);
+ db_unlock();
+ }
+
+ modified = true;
+ }
}
static void
@@ -592,63 +463,18 @@ update_regular_file(struct directory *directory,
if ((plugin = decoder_plugin_from_suffix(suffix, false)) != NULL)
{
- 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)
- {
- if (update_container_file(directory, name, st, plugin))
- {
- if (song != NULL)
- delete_song(directory, song);
-
- return;
- }
- }
-
- if (song == NULL) {
- g_debug("reading %s/%s",
- directory_get_path(directory), name);
- song = song_file_load(name, directory);
- if (song == NULL) {
- g_debug("ignoring unrecognized file %s/%s",
- directory_get_path(directory), name);
- return;
- }
-
- songvec_add(&directory->songs, song);
- modified = true;
- g_message("added %s/%s",
- directory_get_path(directory), name);
- } else if (st->st_mtime != song->mtime || walk_discard) {
- g_message("updating %s/%s",
- directory_get_path(directory), name);
- if (!song_file_update(song)) {
- g_debug("deleting unrecognized file %s/%s",
- directory_get_path(directory), name);
- delete_song(directory, song);
- }
-
- modified = true;
- }
+ update_song_file(directory, name, st, plugin);
#ifdef ENABLE_ARCHIVE
} else if ((archive = archive_plugin_from_suffix(suffix))) {
update_archive_file(directory, name, st, archive);
#endif
} else if (playlist_suffix_supported(suffix)) {
+ db_lock();
if (playlist_vector_update_or_add(&directory->playlists, name,
st->st_mtime))
modified = true;
+ db_unlock();
}
}
@@ -670,12 +496,18 @@ updateInDirectory(struct directory *directory,
if (inodeFoundInParent(directory, st->st_ino, st->st_dev))
return;
- subdir = make_subdir(directory, name);
+ db_lock();
+ subdir = directory_make_child(directory, name);
+ db_unlock();
+
assert(directory == subdir->parent);
ret = updateDirectory(subdir, st);
- if (!ret)
+ if (!ret) {
+ db_lock();
delete_directory(subdir);
+ db_unlock();
+ }
} else {
g_debug("update: %s is not a directory, archive or music", name);
}
@@ -806,7 +638,7 @@ updateDirectory(struct directory *directory, const struct stat *st)
continue;
if (skip_symlink(directory, utf8)) {
- delete_name_in(directory, utf8);
+ modified |= delete_name_in(directory, utf8);
g_free(utf8);
continue;
}
@@ -814,7 +646,7 @@ updateDirectory(struct directory *directory, const struct stat *st)
if (stat_directory_child(directory, utf8, &st2) == 0)
updateInDirectory(directory, utf8, &st2);
else
- delete_name_in(directory, utf8);
+ modified |= delete_name_in(directory, utf8);
g_free(utf8);
}
@@ -829,37 +661,34 @@ updateDirectory(struct directory *directory, const struct stat *st)
}
static struct directory *
-directory_make_child_checked(struct directory *parent, const char *path)
+directory_make_child_checked(struct directory *parent, const char *name_utf8)
{
struct directory *directory;
- char *base;
struct stat st;
- struct song *conflicting;
- directory = directory_get_child(parent, path);
+ db_lock();
+ directory = directory_get_child(parent, name_utf8);
+ db_unlock();
if (directory != NULL)
return directory;
- base = g_path_get_basename(path);
-
- if (stat_directory_child(parent, base, &st) < 0 ||
- inodeFoundInParent(parent, st.st_ino, st.st_dev)) {
- g_free(base);
+ if (stat_directory_child(parent, name_utf8, &st) < 0 ||
+ inodeFoundInParent(parent, st.st_ino, st.st_dev))
return NULL;
- }
- if (skip_symlink(parent, path))
+ if (skip_symlink(parent, name_utf8))
return NULL;
/* if we're adding directory paths, make sure to delete filenames
with potentially the same name */
- conflicting = songvec_find(&parent->songs, base);
+ db_lock();
+ struct song *conflicting = directory_get_song(parent, name_utf8);
if (conflicting)
delete_song(parent, conflicting);
- g_free(base);
+ directory = directory_new_child(parent, name_utf8);
+ db_unlock();
- directory = directory_new_child(parent, path);
directory_set_stat(directory, &st);
return directory;
}
@@ -869,17 +698,20 @@ addParentPathToDB(const char *utf8path)
{
struct directory *directory = db_get_root();
char *duplicated = g_strdup(utf8path);
- char *slash = duplicated;
+ char *name_utf8 = duplicated, *slash;
- while ((slash = strchr(slash, '/')) != NULL) {
+ while ((slash = strchr(name_utf8, '/')) != NULL) {
*slash = 0;
+ if (*name_utf8 == 0)
+ continue;
+
directory = directory_make_child_checked(directory,
- duplicated);
- if (directory == NULL || slash == NULL)
+ name_utf8);
+ if (directory == NULL)
break;
- *slash++ = '/';
+ name_utf8 = slash + 1;
}
g_free(duplicated);
@@ -903,7 +735,7 @@ updatePath(const char *path)
stat_directory_child(parent, name, &st) == 0)
updateInDirectory(parent, name, &st);
else
- delete_name_in(parent, name);
+ modified |= delete_name_in(parent, name);
g_free(name);
}
diff --git a/src/uri.c b/src/uri.c
index bd5aa0249..2a0ca6ca6 100644
--- a/src/uri.c
+++ b/src/uri.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/uri.h b/src/uri.h
index 00c63c039..5a9b472f5 100644
--- a/src/uri.h
+++ b/src/uri.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/util/bit_reverse.c b/src/util/bit_reverse.c
new file mode 100644
index 000000000..ba8a23ef1
--- /dev/null
+++ b/src/util/bit_reverse.c
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2003-2012 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "bit_reverse.h"
+
+/**
+ * @see http://graphics.stanford.edu/~seander/bithacks.html#BitReverseTable
+ */
+const uint8_t bit_reverse_table[256] =
+{
+#define R2(n) n, n + 2*64, n + 1*64, n + 3*64
+#define R4(n) R2(n), R2(n + 2*16), R2(n + 1*16), R2(n + 3*16)
+#define R6(n) R4(n), R4(n + 2*4 ), R4(n + 1*4 ), R4(n + 3*4 )
+ R6(0), R6(2), R6(1), R6(3)
+};
diff --git a/src/util/bit_reverse.h b/src/util/bit_reverse.h
new file mode 100644
index 000000000..e44693b1d
--- /dev/null
+++ b/src/util/bit_reverse.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2003-2012 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_BIT_REVERSE_H
+#define MPD_BIT_REVERSE_H
+
+#include <glib.h>
+#include <stdint.h>
+
+extern const uint8_t bit_reverse_table[256];
+
+G_GNUC_CONST
+static inline uint8_t
+bit_reverse(uint8_t x)
+{
+ return bit_reverse_table[x];
+}
+
+#endif
diff --git a/src/util/byte_reverse.c b/src/util/byte_reverse.c
new file mode 100644
index 000000000..e96af14b9
--- /dev/null
+++ b/src/util/byte_reverse.c
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2003-2012 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "byte_reverse.h"
+
+#include <glib.h>
+#include <assert.h>
+
+void
+reverse_bytes_16(uint16_t *dest, const uint16_t *src, const uint16_t *src_end)
+{
+ assert(dest != NULL);
+ assert(src != NULL);
+ assert(src_end >= src);
+
+ while (src < src_end) {
+ const uint16_t x = *src++;
+ *dest++ = GUINT16_SWAP_LE_BE(x);
+ }
+}
+
+void
+reverse_bytes_32(uint32_t *dest, const uint32_t *src, const uint32_t *src_end)
+{
+ assert(dest != NULL);
+ assert(src != NULL);
+ assert(src_end >= src);
+
+ while (src < src_end) {
+ const uint32_t x = *src++;
+ *dest++ = GUINT32_SWAP_LE_BE(x);
+ }
+}
+
+void
+reverse_bytes_64(uint64_t *dest, const uint64_t *src, const uint64_t *src_end)
+{
+ assert(dest != NULL);
+ assert(src != NULL);
+ assert(src_end >= src);
+
+ while (src < src_end) {
+ const uint64_t x = *src++;
+ *dest++ = GUINT64_SWAP_LE_BE(x);
+ }
+}
+
+static void
+reverse_bytes_linear(uint8_t *dest, const uint8_t *src, size_t n)
+{
+ src += n;
+
+ while (n-- > 0)
+ *dest++ = *--src;
+}
+
+static void
+reverse_bytes_generic(uint8_t *dest,
+ const uint8_t *src, const uint8_t *src_end,
+ size_t frame_size)
+{
+ assert(dest != NULL);
+ assert(src != NULL);
+ assert(src_end >= src);
+ assert(frame_size > 0);
+ assert((src_end - src) % frame_size == 0);
+
+ while (src < src_end) {
+ reverse_bytes_linear(dest, src, frame_size);
+ dest += frame_size;
+ src += frame_size;
+ }
+}
+
+void
+reverse_bytes(uint8_t *dest, const uint8_t *src, const uint8_t *src_end,
+ size_t frame_size)
+{
+ assert(dest != NULL);
+ assert(src != NULL);
+ assert(src_end >= src);
+ assert(frame_size > 0);
+ assert((src_end - src) % frame_size == 0);
+
+ switch (frame_size) {
+ case 2:
+ reverse_bytes_16((uint16_t *)dest,
+ (const uint16_t *)src,
+ (const uint16_t *)src_end);
+ break;
+
+ case 4:
+ reverse_bytes_32((uint32_t *)dest,
+ (const uint32_t *)src,
+ (const uint32_t *)src_end);
+ break;
+
+ case 8:
+ reverse_bytes_64((uint64_t *)dest,
+ (const uint64_t *)src,
+ (const uint64_t *)src_end);
+ break;
+
+ default:
+ reverse_bytes_generic(dest, src, src_end, frame_size);
+ }
+}
diff --git a/src/util/byte_reverse.h b/src/util/byte_reverse.h
new file mode 100644
index 000000000..63213d6c2
--- /dev/null
+++ b/src/util/byte_reverse.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2003-2012 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_BYTE_REVERSE_H
+#define MPD_BYTE_REVERSE_H
+
+#include <stdint.h>
+#include <stddef.h>
+
+/**
+ * Reverse the bytes in each 16 bit "frame". This function can be
+ * used for in-place operation.
+ */
+void
+reverse_bytes_16(uint16_t *dest, const uint16_t *src, const uint16_t *src_end);
+
+/**
+ * Reverse the bytes in each 32 bit "frame". This function can be
+ * used for in-place operation.
+ */
+void
+reverse_bytes_32(uint32_t *dest, const uint32_t *src, const uint32_t *src_end);
+
+/**
+ * Reverse the bytes in each 64 bit "frame". This function can be
+ * used for in-place operation.
+ */
+void
+reverse_bytes_64(uint64_t *dest, const uint64_t *src, const uint64_t *src_end);
+
+/**
+ * Reverse the bytes in each "frame". This function cannot be used
+ * for in-place operation.
+ */
+void
+reverse_bytes(uint8_t *dest, const uint8_t *src, const uint8_t *src_end,
+ size_t frame_size);
+
+#endif
diff --git a/src/util/list.h b/src/util/list.h
new file mode 100644
index 000000000..fdab47675
--- /dev/null
+++ b/src/util/list.h
@@ -0,0 +1,608 @@
+/*
+ * Copyright (C) 2003-2012 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/*
+ * This code was imported from the Linux kernel.
+ *
+ */
+
+#ifndef _LINUX_LIST_H
+#define _LINUX_LIST_H
+
+#include <glib.h>
+
+#ifdef __clang__
+/* allow typeof() */
+#pragma GCC diagnostic ignored "-Wlanguage-extension-token"
+#endif
+
+/**
+ * container_of - cast a member of a structure out to the containing structure
+ * @ptr: the pointer to the member.
+ * @type: the type of the container struct this is embedded in.
+ * @member: the name of the member within the struct.
+ *
+ */
+#define container_of(ptr, type, member) \
+ (&G_STRUCT_MEMBER(type, ptr, -G_STRUCT_OFFSET(type, member)))
+
+/*
+ * These are non-NULL pointers that will result in page faults
+ * under normal circumstances, used to verify that nobody uses
+ * non-initialized list entries.
+ */
+#define LIST_POISON1 ((void *) 0x00100100)
+#define LIST_POISON2 ((void *) 0x00200200)
+
+/*
+ * Simple doubly linked list implementation.
+ *
+ * Some of the internal functions ("__xxx") are useful when
+ * manipulating whole lists rather than single entries, as
+ * sometimes we already know the next/prev entries and we can
+ * generate better code by using them directly rather than
+ * using the generic single-entry routines.
+ */
+
+struct list_head {
+ struct list_head *next, *prev;
+};
+
+#define LIST_HEAD_INIT(name) { &(name), &(name) }
+
+#define LIST_HEAD(name) \
+ struct list_head name = LIST_HEAD_INIT(name)
+
+static inline void INIT_LIST_HEAD(struct list_head *list)
+{
+ list->next = list;
+ list->prev = list;
+}
+
+/*
+ * Insert a new entry between two known consecutive entries.
+ *
+ * This is only for internal list manipulation where we know
+ * the prev/next entries already!
+ */
+#ifndef CONFIG_DEBUG_LIST
+static inline void __list_add(struct list_head *new,
+ struct list_head *prev,
+ struct list_head *next)
+{
+ next->prev = new;
+ new->next = next;
+ new->prev = prev;
+ prev->next = new;
+}
+#else
+extern void __list_add(struct list_head *new,
+ struct list_head *prev,
+ struct list_head *next);
+#endif
+
+/**
+ * list_add - add a new entry
+ * @new: new entry to be added
+ * @head: list head to add it after
+ *
+ * Insert a new entry after the specified head.
+ * This is good for implementing stacks.
+ */
+static inline void list_add(struct list_head *new, struct list_head *head)
+{
+ __list_add(new, head, head->next);
+}
+
+
+/**
+ * list_add_tail - add a new entry
+ * @new: new entry to be added
+ * @head: list head to add it before
+ *
+ * Insert a new entry before the specified head.
+ * This is useful for implementing queues.
+ */
+static inline void list_add_tail(struct list_head *new, struct list_head *head)
+{
+ __list_add(new, head->prev, head);
+}
+
+/*
+ * Delete a list entry by making the prev/next entries
+ * point to each other.
+ *
+ * This is only for internal list manipulation where we know
+ * the prev/next entries already!
+ */
+static inline void __list_del(struct list_head * prev, struct list_head * next)
+{
+ next->prev = prev;
+ prev->next = next;
+}
+
+/**
+ * list_del - deletes entry from list.
+ * @entry: the element to delete from the list.
+ * Note: list_empty() on entry does not return true after this, the entry is
+ * in an undefined state.
+ */
+#ifndef CONFIG_DEBUG_LIST
+static inline void __list_del_entry(struct list_head *entry)
+{
+ __list_del(entry->prev, entry->next);
+}
+
+static inline void list_del(struct list_head *entry)
+{
+ __list_del(entry->prev, entry->next);
+ entry->next = LIST_POISON1;
+ entry->prev = LIST_POISON2;
+}
+#else
+extern void __list_del_entry(struct list_head *entry);
+extern void list_del(struct list_head *entry);
+#endif
+
+/**
+ * list_replace - replace old entry by new one
+ * @old : the element to be replaced
+ * @new : the new element to insert
+ *
+ * If @old was empty, it will be overwritten.
+ */
+static inline void list_replace(struct list_head *old,
+ struct list_head *new)
+{
+ new->next = old->next;
+ new->next->prev = new;
+ new->prev = old->prev;
+ new->prev->next = new;
+}
+
+static inline void list_replace_init(struct list_head *old,
+ struct list_head *new)
+{
+ list_replace(old, new);
+ INIT_LIST_HEAD(old);
+}
+
+/**
+ * list_del_init - deletes entry from list and reinitialize it.
+ * @entry: the element to delete from the list.
+ */
+static inline void list_del_init(struct list_head *entry)
+{
+ __list_del_entry(entry);
+ INIT_LIST_HEAD(entry);
+}
+
+/**
+ * list_move - delete from one list and add as another's head
+ * @list: the entry to move
+ * @head: the head that will precede our entry
+ */
+static inline void list_move(struct list_head *list, struct list_head *head)
+{
+ __list_del_entry(list);
+ list_add(list, head);
+}
+
+/**
+ * list_move_tail - delete from one list and add as another's tail
+ * @list: the entry to move
+ * @head: the head that will follow our entry
+ */
+static inline void list_move_tail(struct list_head *list,
+ struct list_head *head)
+{
+ __list_del_entry(list);
+ list_add_tail(list, head);
+}
+
+/**
+ * list_is_last - tests whether @list is the last entry in list @head
+ * @list: the entry to test
+ * @head: the head of the list
+ */
+static inline int list_is_last(const struct list_head *list,
+ const struct list_head *head)
+{
+ return list->next == head;
+}
+
+/**
+ * list_empty - tests whether a list is empty
+ * @head: the list to test.
+ */
+static inline int list_empty(const struct list_head *head)
+{
+ return head->next == head;
+}
+
+/**
+ * list_empty_careful - tests whether a list is empty and not being modified
+ * @head: the list to test
+ *
+ * Description:
+ * tests whether a list is empty _and_ checks that no other CPU might be
+ * in the process of modifying either member (next or prev)
+ *
+ * NOTE: using list_empty_careful() without synchronization
+ * can only be safe if the only activity that can happen
+ * to the list entry is list_del_init(). Eg. it cannot be used
+ * if another CPU could re-list_add() it.
+ */
+static inline int list_empty_careful(const struct list_head *head)
+{
+ struct list_head *next = head->next;
+ return (next == head) && (next == head->prev);
+}
+
+/**
+ * list_rotate_left - rotate the list to the left
+ * @head: the head of the list
+ */
+static inline void list_rotate_left(struct list_head *head)
+{
+ struct list_head *first;
+
+ if (!list_empty(head)) {
+ first = head->next;
+ list_move_tail(first, head);
+ }
+}
+
+/**
+ * list_is_singular - tests whether a list has just one entry.
+ * @head: the list to test.
+ */
+static inline int list_is_singular(const struct list_head *head)
+{
+ return !list_empty(head) && (head->next == head->prev);
+}
+
+static inline void __list_cut_position(struct list_head *list,
+ struct list_head *head, struct list_head *entry)
+{
+ struct list_head *new_first = entry->next;
+ list->next = head->next;
+ list->next->prev = list;
+ list->prev = entry;
+ entry->next = list;
+ head->next = new_first;
+ new_first->prev = head;
+}
+
+/**
+ * list_cut_position - cut a list into two
+ * @list: a new list to add all removed entries
+ * @head: a list with entries
+ * @entry: an entry within head, could be the head itself
+ * and if so we won't cut the list
+ *
+ * This helper moves the initial part of @head, up to and
+ * including @entry, from @head to @list. You should
+ * pass on @entry an element you know is on @head. @list
+ * should be an empty list or a list you do not care about
+ * losing its data.
+ *
+ */
+static inline void list_cut_position(struct list_head *list,
+ struct list_head *head, struct list_head *entry)
+{
+ if (list_empty(head))
+ return;
+ if (list_is_singular(head) &&
+ (head->next != entry && head != entry))
+ return;
+ if (entry == head)
+ INIT_LIST_HEAD(list);
+ else
+ __list_cut_position(list, head, entry);
+}
+
+static inline void __list_splice(const struct list_head *list,
+ struct list_head *prev,
+ struct list_head *next)
+{
+ struct list_head *first = list->next;
+ struct list_head *last = list->prev;
+
+ first->prev = prev;
+ prev->next = first;
+
+ last->next = next;
+ next->prev = last;
+}
+
+/**
+ * list_splice - join two lists, this is designed for stacks
+ * @list: the new list to add.
+ * @head: the place to add it in the first list.
+ */
+static inline void list_splice(const struct list_head *list,
+ struct list_head *head)
+{
+ if (!list_empty(list))
+ __list_splice(list, head, head->next);
+}
+
+/**
+ * list_splice_tail - join two lists, each list being a queue
+ * @list: the new list to add.
+ * @head: the place to add it in the first list.
+ */
+static inline void list_splice_tail(struct list_head *list,
+ struct list_head *head)
+{
+ if (!list_empty(list))
+ __list_splice(list, head->prev, head);
+}
+
+/**
+ * list_splice_init - join two lists and reinitialise the emptied list.
+ * @list: the new list to add.
+ * @head: the place to add it in the first list.
+ *
+ * The list at @list is reinitialised
+ */
+static inline void list_splice_init(struct list_head *list,
+ struct list_head *head)
+{
+ if (!list_empty(list)) {
+ __list_splice(list, head, head->next);
+ INIT_LIST_HEAD(list);
+ }
+}
+
+/**
+ * list_splice_tail_init - join two lists and reinitialise the emptied list
+ * @list: the new list to add.
+ * @head: the place to add it in the first list.
+ *
+ * Each of the lists is a queue.
+ * The list at @list is reinitialised
+ */
+static inline void list_splice_tail_init(struct list_head *list,
+ struct list_head *head)
+{
+ if (!list_empty(list)) {
+ __list_splice(list, head->prev, head);
+ INIT_LIST_HEAD(list);
+ }
+}
+
+/**
+ * list_entry - get the struct for this entry
+ * @ptr: the &struct list_head pointer.
+ * @type: the type of the struct this is embedded in.
+ * @member: the name of the list_struct within the struct.
+ */
+#define list_entry(ptr, type, member) \
+ container_of(ptr, type, member)
+
+/**
+ * list_first_entry - get the first element from a list
+ * @ptr: the list head to take the element from.
+ * @type: the type of the struct this is embedded in.
+ * @member: the name of the list_struct within the struct.
+ *
+ * Note, that list is expected to be not empty.
+ */
+#define list_first_entry(ptr, type, member) \
+ list_entry((ptr)->next, type, member)
+
+/**
+ * list_for_each - iterate over a list
+ * @pos: the &struct list_head to use as a loop cursor.
+ * @head: the head for your list.
+ */
+#define list_for_each(pos, head) \
+ for (pos = (head)->next; pos != (head); pos = pos->next)
+
+/**
+ * __list_for_each - iterate over a list
+ * @pos: the &struct list_head to use as a loop cursor.
+ * @head: the head for your list.
+ *
+ * This variant doesn't differ from list_for_each() any more.
+ * We don't do prefetching in either case.
+ */
+#define __list_for_each(pos, head) \
+ for (pos = (head)->next; pos != (head); pos = pos->next)
+
+/**
+ * list_for_each_prev - iterate over a list backwards
+ * @pos: the &struct list_head to use as a loop cursor.
+ * @head: the head for your list.
+ */
+#define list_for_each_prev(pos, head) \
+ for (pos = (head)->prev; pos != (head); pos = pos->prev)
+
+/**
+ * list_for_each_safe - iterate over a list safe against removal of list entry
+ * @pos: the &struct list_head to use as a loop cursor.
+ * @n: another &struct list_head to use as temporary storage
+ * @head: the head for your list.
+ */
+#define list_for_each_safe(pos, n, head) \
+ for (pos = (head)->next, n = pos->next; pos != (head); \
+ pos = n, n = pos->next)
+
+/**
+ * list_for_each_prev_safe - iterate over a list backwards safe against removal of list entry
+ * @pos: the &struct list_head to use as a loop cursor.
+ * @n: another &struct list_head to use as temporary storage
+ * @head: the head for your list.
+ */
+#define list_for_each_prev_safe(pos, n, head) \
+ for (pos = (head)->prev, n = pos->prev; \
+ pos != (head); \
+ pos = n, n = pos->prev)
+
+/**
+ * list_for_each_entry - iterate over list of given type
+ * @pos: the type * to use as a loop cursor.
+ * @head: the head for your list.
+ * @member: the name of the list_struct within the struct.
+ */
+#define list_for_each_entry(pos, head, member) \
+ for (pos = list_entry((head)->next, typeof(*pos), member); \
+ &pos->member != (head); \
+ pos = list_entry(pos->member.next, typeof(*pos), member))
+
+/**
+ * list_for_each_entry_reverse - iterate backwards over list of given type.
+ * @pos: the type * to use as a loop cursor.
+ * @head: the head for your list.
+ * @member: the name of the list_struct within the struct.
+ */
+#define list_for_each_entry_reverse(pos, head, member) \
+ for (pos = list_entry((head)->prev, typeof(*pos), member); \
+ &pos->member != (head); \
+ pos = list_entry(pos->member.prev, typeof(*pos), member))
+
+/**
+ * list_prepare_entry - prepare a pos entry for use in list_for_each_entry_continue()
+ * @pos: the type * to use as a start point
+ * @head: the head of the list
+ * @member: the name of the list_struct within the struct.
+ *
+ * Prepares a pos entry for use as a start point in list_for_each_entry_continue().
+ */
+#define list_prepare_entry(pos, head, member) \
+ ((pos) ? : list_entry(head, typeof(*pos), member))
+
+/**
+ * list_for_each_entry_continue - continue iteration over list of given type
+ * @pos: the type * to use as a loop cursor.
+ * @head: the head for your list.
+ * @member: the name of the list_struct within the struct.
+ *
+ * Continue to iterate over list of given type, continuing after
+ * the current position.
+ */
+#define list_for_each_entry_continue(pos, head, member) \
+ for (pos = list_entry(pos->member.next, typeof(*pos), member); \
+ &pos->member != (head); \
+ pos = list_entry(pos->member.next, typeof(*pos), member))
+
+/**
+ * list_for_each_entry_continue_reverse - iterate backwards from the given point
+ * @pos: the type * to use as a loop cursor.
+ * @head: the head for your list.
+ * @member: the name of the list_struct within the struct.
+ *
+ * Start to iterate over list of given type backwards, continuing after
+ * the current position.
+ */
+#define list_for_each_entry_continue_reverse(pos, head, member) \
+ for (pos = list_entry(pos->member.prev, typeof(*pos), member); \
+ &pos->member != (head); \
+ pos = list_entry(pos->member.prev, typeof(*pos), member))
+
+/**
+ * list_for_each_entry_from - iterate over list of given type from the current point
+ * @pos: the type * to use as a loop cursor.
+ * @head: the head for your list.
+ * @member: the name of the list_struct within the struct.
+ *
+ * Iterate over list of given type, continuing from current position.
+ */
+#define list_for_each_entry_from(pos, head, member) \
+ for (; &pos->member != (head); \
+ pos = list_entry(pos->member.next, typeof(*pos), member))
+
+/**
+ * list_for_each_entry_safe - iterate over list of given type safe against removal of list entry
+ * @pos: the type * to use as a loop cursor.
+ * @n: another type * to use as temporary storage
+ * @head: the head for your list.
+ * @member: the name of the list_struct within the struct.
+ */
+#define list_for_each_entry_safe(pos, n, head, member) \
+ for (pos = list_entry((head)->next, typeof(*pos), member), \
+ n = list_entry(pos->member.next, typeof(*pos), member); \
+ &pos->member != (head); \
+ pos = n, n = list_entry(n->member.next, typeof(*n), member))
+
+/**
+ * list_for_each_entry_safe_continue - continue list iteration safe against removal
+ * @pos: the type * to use as a loop cursor.
+ * @n: another type * to use as temporary storage
+ * @head: the head for your list.
+ * @member: the name of the list_struct within the struct.
+ *
+ * Iterate over list of given type, continuing after current point,
+ * safe against removal of list entry.
+ */
+#define list_for_each_entry_safe_continue(pos, n, head, member) \
+ for (pos = list_entry(pos->member.next, typeof(*pos), member), \
+ n = list_entry(pos->member.next, typeof(*pos), member); \
+ &pos->member != (head); \
+ pos = n, n = list_entry(n->member.next, typeof(*n), member))
+
+/**
+ * list_for_each_entry_safe_from - iterate over list from current point safe against removal
+ * @pos: the type * to use as a loop cursor.
+ * @n: another type * to use as temporary storage
+ * @head: the head for your list.
+ * @member: the name of the list_struct within the struct.
+ *
+ * Iterate over list of given type from current point, safe against
+ * removal of list entry.
+ */
+#define list_for_each_entry_safe_from(pos, n, head, member) \
+ for (n = list_entry(pos->member.next, typeof(*pos), member); \
+ &pos->member != (head); \
+ pos = n, n = list_entry(n->member.next, typeof(*n), member))
+
+/**
+ * list_for_each_entry_safe_reverse - iterate backwards over list safe against removal
+ * @pos: the type * to use as a loop cursor.
+ * @n: another type * to use as temporary storage
+ * @head: the head for your list.
+ * @member: the name of the list_struct within the struct.
+ *
+ * Iterate backwards over list of given type, safe against removal
+ * of list entry.
+ */
+#define list_for_each_entry_safe_reverse(pos, n, head, member) \
+ for (pos = list_entry((head)->prev, typeof(*pos), member), \
+ n = list_entry(pos->member.prev, typeof(*pos), member); \
+ &pos->member != (head); \
+ pos = n, n = list_entry(n->member.prev, typeof(*n), member))
+
+/**
+ * list_safe_reset_next - reset a stale list_for_each_entry_safe loop
+ * @pos: the loop cursor used in the list_for_each_entry_safe loop
+ * @n: temporary storage used in list_for_each_entry_safe
+ * @member: the name of the list_struct within the struct.
+ *
+ * list_safe_reset_next is not safe to use in general if the list may be
+ * modified concurrently (eg. the lock is dropped in the loop body). An
+ * exception to this is if the cursor element (pos) is pinned in the list,
+ * and list_safe_reset_next is called after re-taking the lock and before
+ * completing the current iteration of the loop body.
+ */
+#define list_safe_reset_next(pos, n, member) \
+ n = list_entry(pos->member.next, typeof(*pos), member)
+
+#endif
diff --git a/src/util/list_sort.c b/src/util/list_sort.c
new file mode 100644
index 000000000..ddf3208e9
--- /dev/null
+++ b/src/util/list_sort.c
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2003-2012 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/*
+ * This code was imported from the Linux kernel.
+ *
+ */
+
+#include "list_sort.h"
+#include "list.h"
+
+#include <glib.h>
+#include <string.h>
+
+#define unlikely G_UNLIKELY
+#define ARRAY_SIZE G_N_ELEMENTS
+
+#define MAX_LIST_LENGTH_BITS 20
+
+/*
+ * Returns a list organized in an intermediate format suited
+ * to chaining of merge() calls: null-terminated, no reserved or
+ * sentinel head node, "prev" links not maintained.
+ */
+static struct list_head *merge(void *priv,
+ int (*cmp)(void *priv, struct list_head *a,
+ struct list_head *b),
+ struct list_head *a, struct list_head *b)
+{
+ struct list_head head, *tail = &head;
+
+ while (a && b) {
+ /* if equal, take 'a' -- important for sort stability */
+ if ((*cmp)(priv, a, b) <= 0) {
+ tail->next = a;
+ a = a->next;
+ } else {
+ tail->next = b;
+ b = b->next;
+ }
+ tail = tail->next;
+ }
+ tail->next = a?a:b;
+ return head.next;
+}
+
+/*
+ * Combine final list merge with restoration of standard doubly-linked
+ * list structure. This approach duplicates code from merge(), but
+ * runs faster than the tidier alternatives of either a separate final
+ * prev-link restoration pass, or maintaining the prev links
+ * throughout.
+ */
+static void merge_and_restore_back_links(void *priv,
+ int (*cmp)(void *priv, struct list_head *a,
+ struct list_head *b),
+ struct list_head *head,
+ struct list_head *a, struct list_head *b)
+{
+ struct list_head *tail = head;
+
+ while (a && b) {
+ /* if equal, take 'a' -- important for sort stability */
+ if ((*cmp)(priv, a, b) <= 0) {
+ tail->next = a;
+ a->prev = tail;
+ a = a->next;
+ } else {
+ tail->next = b;
+ b->prev = tail;
+ b = b->next;
+ }
+ tail = tail->next;
+ }
+ tail->next = a ? a : b;
+
+ do {
+ /*
+ * In worst cases this loop may run many iterations.
+ * Continue callbacks to the client even though no
+ * element comparison is needed, so the client's cmp()
+ * routine can invoke cond_resched() periodically.
+ */
+ (*cmp)(priv, tail->next, tail->next);
+
+ tail->next->prev = tail;
+ tail = tail->next;
+ } while (tail->next);
+
+ tail->next = head;
+ head->prev = tail;
+}
+
+/**
+ * list_sort - sort a list
+ * @priv: private data, opaque to list_sort(), passed to @cmp
+ * @head: the list to sort
+ * @cmp: the elements comparison function
+ *
+ * This function implements "merge sort", which has O(nlog(n))
+ * complexity.
+ *
+ * The comparison function @cmp must return a negative value if @a
+ * should sort before @b, and a positive value if @a should sort after
+ * @b. If @a and @b are equivalent, and their original relative
+ * ordering is to be preserved, @cmp must return 0.
+ */
+void list_sort(void *priv, struct list_head *head,
+ int (*cmp)(void *priv, struct list_head *a,
+ struct list_head *b))
+{
+ struct list_head *part[MAX_LIST_LENGTH_BITS+1]; /* sorted partial lists
+ -- last slot is a sentinel */
+ int lev; /* index into part[] */
+ int max_lev = 0;
+ struct list_head *list;
+
+ if (list_empty(head))
+ return;
+
+ memset(part, 0, sizeof(part));
+
+ head->prev->next = NULL;
+ list = head->next;
+
+ while (list) {
+ struct list_head *cur = list;
+ list = list->next;
+ cur->next = NULL;
+
+ for (lev = 0; part[lev]; lev++) {
+ cur = merge(priv, cmp, part[lev], cur);
+ part[lev] = NULL;
+ }
+ if (lev > max_lev) {
+ max_lev = lev;
+ }
+ part[lev] = cur;
+ }
+
+ for (lev = 0; lev < max_lev; lev++)
+ if (part[lev])
+ list = merge(priv, cmp, part[lev], list);
+
+ merge_and_restore_back_links(priv, cmp, head, part[max_lev], list);
+}
diff --git a/src/util/list_sort.h b/src/util/list_sort.h
new file mode 100644
index 000000000..7a65020b9
--- /dev/null
+++ b/src/util/list_sort.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2003-2012 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/*
+ * This code was imported from the Linux kernel.
+ *
+ */
+
+#ifndef _LINUX_LIST_SORT_H
+#define _LINUX_LIST_SORT_H
+
+struct list_head;
+
+void list_sort(void *priv, struct list_head *head,
+ int (*cmp)(void *priv, struct list_head *a,
+ struct list_head *b));
+#endif
diff --git a/src/utils.c b/src/utils.c
index 6e85dd5ff..a2de3212e 100644
--- a/src/utils.c
+++ b/src/utils.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -19,6 +19,7 @@
#include "config.h"
#include "utils.h"
+#include "glib_compat.h"
#include "conf.h"
#include <glib.h>
@@ -45,14 +46,25 @@
#include <windows.h>
#endif
-char *parsePath(char *path)
+G_GNUC_CONST
+static inline GQuark
+parse_path_quark(void)
{
+ return g_quark_from_static_string("path");
+}
+
+char *
+parsePath(const char *path, G_GNUC_UNUSED GError **error_r)
+{
+ assert(path != NULL);
+ assert(error_r == NULL || *error_r == NULL);
+
#ifndef WIN32
if (!g_path_is_absolute(path) && path[0] != '~') {
- g_warning("\"%s\" is not an absolute path", path);
+ g_set_error(error_r, parse_path_quark(), 0,
+ "not an absolute path: %s", path);
return NULL;
} else if (path[0] == '~') {
- size_t pos = 1;
const char *home;
if (path[1] == '/' || path[1] == '\0') {
@@ -60,7 +72,8 @@ char *parsePath(char *path)
if (user != NULL) {
struct passwd *passwd = getpwnam(user);
if (!passwd) {
- g_warning("no such user %s", user);
+ g_set_error(error_r, parse_path_quark(), 0,
+ "no such user: %s", user);
return NULL;
}
@@ -68,36 +81,37 @@ char *parsePath(char *path)
} else {
home = g_get_home_dir();
if (home == NULL) {
- g_warning("problems getting home "
- "for current user");
+ g_set_error_literal(error_r, parse_path_quark(), 0,
+ "problems getting home "
+ "for current user");
return NULL;
}
}
+
+ ++path;
} else {
- bool foundSlash = false;
- struct passwd *passwd;
- char *c;
-
- for (c = path + 1; *c != '\0' && *c != '/'; c++);
- if (*c == '/') {
- foundSlash = true;
- *c = '\0';
- }
- pos = c - path;
+ ++path;
- passwd = getpwnam(path + 1);
+ const char *slash = strchr(path, '/');
+ char *user = slash != NULL
+ ? g_strndup(path, slash - path)
+ : g_strdup(path);
+
+ struct passwd *passwd = getpwnam(user);
if (!passwd) {
- g_warning("user \"%s\" not found", path + 1);
+ g_set_error(error_r, parse_path_quark(), 0,
+ "no such user: %s", user);
+ g_free(user);
return NULL;
}
- if (foundSlash)
- *c = '/';
+ g_free(user);
home = passwd->pw_dir;
+ path = slash;
}
- return g_strconcat(home, path + pos, NULL);
+ return g_strconcat(home, path, NULL);
} else {
#endif
return g_strdup(path);
@@ -105,16 +119,3 @@ char *parsePath(char *path)
}
#endif
}
-
-bool
-string_array_contains(const char *const* haystack, const char *needle)
-{
- assert(haystack != NULL);
- assert(needle != NULL);
-
- for (; *haystack != NULL; ++haystack)
- if (g_ascii_strcasecmp(*haystack, needle) == 0)
- return true;
-
- return false;
-}
diff --git a/src/utils.h b/src/utils.h
index 629056637..f8d6657f2 100644
--- a/src/utils.h
+++ b/src/utils.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -20,6 +20,7 @@
#ifndef MPD_UTILS_H
#define MPD_UTILS_H
+#include <glib.h>
#include <stdbool.h>
#ifndef assert_static
@@ -31,17 +32,7 @@
} while (0)
#endif /* !assert_static */
-char *parsePath(char *path);
-
-/**
- * Checks whether a string array contains the specified string.
- *
- * @param haystack a NULL terminated list of strings
- * @param needle the string to search for; the comparison is
- * case-insensitive for ASCII characters
- * @return true if found
- */
-bool
-string_array_contains(const char *const* haystack, const char *needle);
+char *
+parsePath(const char *path, GError **error_r);
#endif
diff --git a/src/volume.c b/src/volume.c
index d7b72dd56..819e6fbfa 100644
--- a/src/volume.c
+++ b/src/volume.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/volume.h b/src/volume.h
index db266fec9..b08899a84 100644
--- a/src/volume.h
+++ b/src/volume.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/win/mpd.ico b/src/win/mpd.ico
new file mode 100644
index 000000000..86fd9fe43
--- /dev/null
+++ b/src/win/mpd.ico
Binary files differ
diff --git a/src/win/mpd_win32_rc.rc.in b/src/win/mpd_win32_rc.rc.in
new file mode 100644
index 000000000..a31118a0c
--- /dev/null
+++ b/src/win/mpd_win32_rc.rc.in
@@ -0,0 +1,34 @@
+#include <windows.h>
+
+#define VERSION_NUMBER @VERSION_MAJOR@,@VERSION_MINOR@,@VERSION_REVISION@,@VERSION_EXTRA@
+#define VERSION_NUMBER_STR "@VERSION_MAJOR@,@VERSION_MINOR@,@VERSION_REVISION@,@VERSION_EXTRA@"
+
+MPD_ICON ICON "@top_srcdir@/src/win/mpd.ico"
+
+1 VERSIONINFO
+FILETYPE VFT_APP
+FILEOS VOS__WINDOWS32
+PRODUCTVERSION VERSION_NUMBER
+
+FILEVERSION VERSION_NUMBER
+BEGIN
+ BLOCK "StringFileInfo"
+ BEGIN
+ BLOCK "040904B0"
+ BEGIN
+ VALUE "CompanyName", "Music Player Daemon Project"
+ VALUE "ProductName", "Music Player Daemon"
+ VALUE "ProductVersion", VERSION_NUMBER_STR
+ VALUE "InternalName", "mpd"
+ VALUE "OriginalFilename", "mpd.exe"
+ VALUE "FileVersion", "@VERSION@"
+ VALUE "FileDescription", "Music Player Daemon @VERSION@"
+ VALUE "LegalCopyright", "Copyright \251 The Music Player Daemon Project"
+ END
+ END
+
+ BLOCK "VarFileInfo"
+ BEGIN
+ VALUE "Translation", 0x409, 1200
+ END
+END
diff --git a/src/zeroconf-avahi.c b/src/zeroconf-avahi.c
index 518a7a481..f2cc5359b 100644
--- a/src/zeroconf-avahi.c
+++ b/src/zeroconf-avahi.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/zeroconf-bonjour.c b/src/zeroconf-bonjour.c
index 84f777c50..0f216aade 100644
--- a/src/zeroconf-bonjour.c
+++ b/src/zeroconf-bonjour.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/zeroconf-internal.h b/src/zeroconf-internal.h
index 7cb962431..983e5c556 100644
--- a/src/zeroconf-internal.h
+++ b/src/zeroconf-internal.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/zeroconf.c b/src/zeroconf.c
index 7b00789b6..4a399e4a2 100644
--- a/src/zeroconf.c
+++ b/src/zeroconf.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -21,6 +21,7 @@
#include "zeroconf.h"
#include "zeroconf-internal.h"
#include "conf.h"
+#include "listen.h"
#include <glib.h>
@@ -42,6 +43,12 @@ void initZeroconf(void)
if (!zeroconfEnabled)
return;
+ if (listen_port <= 0) {
+ g_warning("No global port, disabling zeroconf");
+ zeroconfEnabled = false;
+ return;
+ }
+
serviceName = config_get_string(CONF_ZEROCONF_NAME, SERVICE_NAME);
#ifdef HAVE_AVAHI
diff --git a/src/zeroconf.h b/src/zeroconf.h
index 23354f87d..8e33a3d89 100644
--- a/src/zeroconf.h
+++ b/src/zeroconf.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/test/dump_playlist.c b/test/dump_playlist.c
index a8cb4d750..0570f6e49 100644
--- a/test/dump_playlist.c
+++ b/test/dump_playlist.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -18,18 +18,22 @@
*/
#include "config.h"
+#include "io_thread.h"
#include "input_init.h"
#include "input_stream.h"
#include "tag_pool.h"
#include "tag_save.h"
#include "conf.h"
#include "song.h"
+#include "decoder_api.h"
+#include "decoder_list.h"
#include "playlist_list.h"
#include "playlist_plugin.h"
#include <glib.h>
#include <unistd.h>
+#include <stdlib.h>
static void
my_log_func(const gchar *log_domain, G_GNUC_UNUSED GLogLevelFlags log_level,
@@ -41,6 +45,95 @@ my_log_func(const gchar *log_domain, G_GNUC_UNUSED GLogLevelFlags log_level,
g_printerr("%s\n", message);
}
+void
+decoder_initialized(G_GNUC_UNUSED struct decoder *decoder,
+ G_GNUC_UNUSED const struct audio_format *audio_format,
+ G_GNUC_UNUSED bool seekable,
+ G_GNUC_UNUSED float total_time)
+{
+}
+
+enum decoder_command
+decoder_get_command(G_GNUC_UNUSED struct decoder *decoder)
+{
+ return DECODE_COMMAND_NONE;
+}
+
+void
+decoder_command_finished(G_GNUC_UNUSED struct decoder *decoder)
+{
+}
+
+double
+decoder_seek_where(G_GNUC_UNUSED struct decoder *decoder)
+{
+ return 1.0;
+}
+
+void
+decoder_seek_error(G_GNUC_UNUSED struct decoder *decoder)
+{
+}
+
+size_t
+decoder_read(G_GNUC_UNUSED struct decoder *decoder,
+ struct input_stream *is,
+ void *buffer, size_t length)
+{
+ return input_stream_lock_read(is, buffer, length, NULL);
+}
+
+void
+decoder_timestamp(G_GNUC_UNUSED struct decoder *decoder,
+ G_GNUC_UNUSED double t)
+{
+}
+
+enum decoder_command
+decoder_data(G_GNUC_UNUSED struct decoder *decoder,
+ G_GNUC_UNUSED struct input_stream *is,
+ const void *data, size_t datalen,
+ G_GNUC_UNUSED uint16_t kbit_rate)
+{
+ G_GNUC_UNUSED ssize_t nbytes = write(1, data, datalen);
+ return DECODE_COMMAND_NONE;
+}
+
+enum decoder_command
+decoder_tag(G_GNUC_UNUSED struct decoder *decoder,
+ G_GNUC_UNUSED struct input_stream *is,
+ G_GNUC_UNUSED const struct tag *tag)
+{
+ return DECODE_COMMAND_NONE;
+}
+
+float
+decoder_replay_gain(G_GNUC_UNUSED struct decoder *decoder,
+ const struct replay_gain_info *replay_gain_info)
+{
+ const struct replay_gain_tuple *tuple =
+ &replay_gain_info->tuples[REPLAY_GAIN_ALBUM];
+ if (replay_gain_tuple_defined(tuple))
+ g_printerr("replay_gain[album]: gain=%f peak=%f\n",
+ tuple->gain, tuple->peak);
+
+ tuple = &replay_gain_info->tuples[REPLAY_GAIN_TRACK];
+ if (replay_gain_tuple_defined(tuple))
+ g_printerr("replay_gain[track]: gain=%f peak=%f\n",
+ tuple->gain, tuple->peak);
+
+ return 0.0;
+}
+
+void
+decoder_mixramp(G_GNUC_UNUSED struct decoder *decoder,
+ G_GNUC_UNUSED float replay_gain_db,
+ char *mixramp_start, char *mixramp_end)
+{
+ g_free(mixramp_start);
+ g_free(mixramp_end);
+}
+
int main(int argc, char **argv)
{
const char *uri;
@@ -73,6 +166,13 @@ int main(int argc, char **argv)
return 1;
}
+ io_thread_init();
+ if (!io_thread_start(&error)) {
+ g_warning("%s", error->message);
+ g_error_free(error);
+ return EXIT_FAILURE;
+ }
+
if (!input_stream_global_init(&error)) {
g_warning("%s", error->message);
g_error_free(error);
@@ -80,14 +180,18 @@ int main(int argc, char **argv)
}
playlist_list_global_init();
+ decoder_plugin_init_all();
/* open the playlist */
- playlist = playlist_list_open_uri(uri);
+ GMutex *mutex = g_mutex_new();
+ GCond *cond = g_cond_new();
+
+ playlist = playlist_list_open_uri(uri, mutex, cond);
if (playlist == NULL) {
/* open the stream and wait until it becomes ready */
- is = input_stream_open(uri, &error);
+ is = input_stream_open(uri, mutex, cond, &error);
if (is == NULL) {
if (error != NULL) {
g_warning("%s", error->message);
@@ -97,19 +201,7 @@ int main(int argc, char **argv)
return 2;
}
- while (!is->ready) {
- int ret = input_stream_buffer(is, &error);
- if (ret < 0) {
- /* error */
- g_warning("%s", error->message);
- g_error_free(error);
- return 2;
- }
-
- if (ret == 0)
- /* nothing was buffered - wait */
- g_usleep(10000);
- }
+ input_stream_lock_wait_ready(is);
/* open the playlist */
@@ -148,8 +240,14 @@ int main(int argc, char **argv)
playlist_plugin_close(playlist);
if (is != NULL)
input_stream_close(is);
+
+ g_cond_free(cond);
+ g_mutex_free(mutex);
+
+ decoder_plugin_deinit_all();
playlist_list_global_finish();
input_stream_global_finish();
+ io_thread_deinit();
config_global_finish();
tag_pool_deinit();
diff --git a/test/dump_text_file.c b/test/dump_text_file.c
new file mode 100644
index 000000000..f14371441
--- /dev/null
+++ b/test/dump_text_file.c
@@ -0,0 +1,170 @@
+/*
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "io_thread.h"
+#include "input_init.h"
+#include "input_stream.h"
+#include "text_input_stream.h"
+#include "tag_pool.h"
+#include "conf.h"
+#include "stdbin.h"
+
+#ifdef ENABLE_ARCHIVE
+#include "archive_list.h"
+#endif
+
+#include <glib.h>
+
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+static void
+my_log_func(const gchar *log_domain, G_GNUC_UNUSED GLogLevelFlags log_level,
+ const gchar *message, G_GNUC_UNUSED gpointer user_data)
+{
+ if (log_domain != NULL)
+ g_printerr("%s: %s\n", log_domain, message);
+ else
+ g_printerr("%s\n", message);
+}
+
+static void
+dump_text_file(struct text_input_stream *is)
+{
+ const char *line;
+ while ((line = text_input_stream_read(is)) != NULL)
+ printf("'%s'\n", line);
+}
+
+static int
+dump_input_stream(struct input_stream *is)
+{
+ GError *error = NULL;
+
+ input_stream_lock(is);
+
+ /* wait until the stream becomes ready */
+
+ input_stream_wait_ready(is);
+
+ if (!input_stream_check(is, &error)) {
+ g_warning("%s", error->message);
+ g_error_free(error);
+ input_stream_unlock(is);
+ return EXIT_FAILURE;
+ }
+
+ /* read data and tags from the stream */
+
+ input_stream_unlock(is);
+
+ struct text_input_stream *tis = text_input_stream_new(is);
+ dump_text_file(tis);
+ text_input_stream_free(tis);
+
+ input_stream_lock(is);
+
+ if (!input_stream_check(is, &error)) {
+ g_warning("%s", error->message);
+ g_error_free(error);
+ input_stream_unlock(is);
+ return EXIT_FAILURE;
+ }
+
+ input_stream_unlock(is);
+
+ return 0;
+}
+
+int main(int argc, char **argv)
+{
+ GError *error = NULL;
+ struct input_stream *is;
+ int ret;
+
+ if (argc != 2) {
+ g_printerr("Usage: run_input URI\n");
+ return 1;
+ }
+
+ /* initialize GLib */
+
+ g_thread_init(NULL);
+ g_log_set_default_handler(my_log_func, NULL);
+
+ /* initialize MPD */
+
+ tag_pool_init();
+ config_global_init();
+
+ io_thread_init();
+ if (!io_thread_start(&error)) {
+ g_warning("%s", error->message);
+ g_error_free(error);
+ return EXIT_FAILURE;
+ }
+
+#ifdef ENABLE_ARCHIVE
+ archive_plugin_init_all();
+#endif
+
+ if (!input_stream_global_init(&error)) {
+ g_warning("%s", error->message);
+ g_error_free(error);
+ return 2;
+ }
+
+ /* open the stream and dump it */
+
+ GMutex *mutex = g_mutex_new();
+ GCond *cond = g_cond_new();
+
+ is = input_stream_open(argv[1], mutex, cond, &error);
+ if (is != NULL) {
+ ret = dump_input_stream(is);
+ input_stream_close(is);
+ } else {
+ if (error != NULL) {
+ g_warning("%s", error->message);
+ g_error_free(error);
+ } else
+ g_printerr("input_stream_open() failed\n");
+ ret = 2;
+ }
+
+ g_cond_free(cond);
+ g_mutex_free(mutex);
+
+ /* deinitialize everything */
+
+ input_stream_global_finish();
+
+#ifdef ENABLE_ARCHIVE
+ archive_plugin_deinit_all();
+#endif
+
+ io_thread_deinit();
+
+ config_global_finish();
+ tag_pool_deinit();
+
+ return ret;
+}
diff --git a/test/read_conf.c b/test/read_conf.c
index f1b38cafe..4f6005c6f 100644
--- a/test/read_conf.c
+++ b/test/read_conf.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/test/read_mixer.c b/test/read_mixer.c
index 1b5b093a3..0a0719460 100644
--- a/test/read_mixer.c
+++ b/test/read_mixer.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -34,6 +34,16 @@
#include "output/pulse_output_plugin.h"
void
+pulse_output_lock(G_GNUC_UNUSED struct pulse_output *po)
+{
+}
+
+void
+pulse_output_unlock(G_GNUC_UNUSED struct pulse_output *po)
+{
+}
+
+void
pulse_output_set_mixer(G_GNUC_UNUSED struct pulse_output *po,
G_GNUC_UNUSED struct pulse_mixer *pm)
{
@@ -55,6 +65,43 @@ pulse_output_set_volume(G_GNUC_UNUSED struct pulse_output *po,
#endif
+#ifdef HAVE_ROAR
+#include "output/roar_output_plugin.h"
+
+int
+roar_output_get_volume(G_GNUC_UNUSED struct roar *roar)
+{
+ return -1;
+}
+
+bool
+roar_output_set_volume(G_GNUC_UNUSED struct roar *roar,
+ G_GNUC_UNUSED unsigned volume)
+{
+ return true;
+}
+
+#endif
+
+#ifdef ENABLE_RAOP_OUTPUT
+#include "output/raop_output_plugin.h"
+
+bool
+raop_set_volume(G_GNUC_UNUSED struct raop_data *rd,
+ G_GNUC_UNUSED unsigned volume,
+ G_GNUC_UNUSED GError **error_r)
+{
+ return false;
+}
+
+int
+raop_get_volume(G_GNUC_UNUSED struct raop_data *rd)
+{
+ return -1;
+}
+
+#endif
+
void
event_pipe_emit(G_GNUC_UNUSED enum pipe_event event)
{
@@ -68,8 +115,8 @@ filter_plugin_by_name(G_GNUC_UNUSED const char *name)
}
bool
-pcm_volume(G_GNUC_UNUSED void *buffer, G_GNUC_UNUSED int length,
- G_GNUC_UNUSED const struct audio_format *format,
+pcm_volume(G_GNUC_UNUSED void *buffer, G_GNUC_UNUSED size_t length,
+ G_GNUC_UNUSED enum sample_format format,
G_GNUC_UNUSED int volume)
{
assert(false);
diff --git a/test/read_tags.c b/test/read_tags.c
index 8906e1c8a..faf9a45c0 100644
--- a/test/read_tags.c
+++ b/test/read_tags.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -18,21 +18,23 @@
*/
#include "config.h"
+#include "io_thread.h"
#include "decoder_list.h"
#include "decoder_api.h"
#include "input_init.h"
#include "input_stream.h"
#include "audio_format.h"
#include "pcm_volume.h"
-#include "tag_pool.h"
#include "tag_ape.h"
#include "tag_id3.h"
+#include "tag_handler.h"
#include "idle.h"
#include <glib.h>
#include <assert.h>
#include <unistd.h>
+#include <stdlib.h>
#ifdef HAVE_LOCALE_H
#include <locale.h>
@@ -50,8 +52,8 @@ idle_add(G_GNUC_UNUSED unsigned flags)
* No-op dummy.
*/
bool
-pcm_volume(G_GNUC_UNUSED void *buffer, G_GNUC_UNUSED int length,
- G_GNUC_UNUSED const struct audio_format *format,
+pcm_volume(G_GNUC_UNUSED void *buffer, G_GNUC_UNUSED size_t length,
+ G_GNUC_UNUSED enum sample_format format,
G_GNUC_UNUSED int volume)
{
return true;
@@ -89,7 +91,7 @@ decoder_read(G_GNUC_UNUSED struct decoder *decoder,
struct input_stream *is,
void *buffer, size_t length)
{
- return input_stream_read(is, buffer, length, NULL);
+ return input_stream_lock_read(is, buffer, length, NULL);
}
void
@@ -132,25 +134,38 @@ decoder_mixramp(G_GNUC_UNUSED struct decoder *decoder,
g_free(mixramp_end);
}
+static bool empty = true;
+
+static void
+print_duration(unsigned seconds, G_GNUC_UNUSED void *ctx)
+{
+ g_print("duration=%d\n", seconds);
+}
+
static void
-print_tag(const struct tag *tag)
+print_tag(enum tag_type type, const char *value, G_GNUC_UNUSED void *ctx)
{
- if (tag->time >= 0)
- g_print("time=%d\n", tag->time);
+ g_print("[%s]=%s\n", tag_item_names[type], value);
+ empty = false;
+}
- for (unsigned i = 0; i < tag->num_items; ++i)
- g_print("%s=%s\n",
- tag_item_names[tag->items[i]->type],
- tag->items[i]->value);
+static void
+print_pair(const char *name, const char *value, G_GNUC_UNUSED void *ctx)
+{
+ g_print("\"%s\"=%s\n", name, value);
}
+static const struct tag_handler print_handler = {
+ .duration = print_duration,
+ .tag = print_tag,
+ .pair = print_pair,
+};
+
int main(int argc, char **argv)
{
GError *error = NULL;
const char *decoder_name, *path;
const struct decoder_plugin *plugin;
- struct tag *tag;
- bool empty;
#ifdef HAVE_LOCALE_H
/* initialize locale */
@@ -166,7 +181,12 @@ int main(int argc, char **argv)
path = argv[2];
g_thread_init(NULL);
- tag_pool_init();
+ io_thread_init();
+ if (!io_thread_start(&error)) {
+ g_warning("%s", error->message);
+ g_error_free(error);
+ return EXIT_FAILURE;
+ }
if (!input_stream_global_init(&error)) {
g_warning("%s", error->message);
@@ -182,9 +202,14 @@ int main(int argc, char **argv)
return 1;
}
- tag = decoder_plugin_tag_dup(plugin, path);
- if (tag == NULL && plugin->stream_tag != NULL) {
- struct input_stream *is = input_stream_open(path, &error);
+ bool success = decoder_plugin_scan_file(plugin, path,
+ &print_handler, NULL);
+ if (!success && plugin->scan_stream != NULL) {
+ GMutex *mutex = g_mutex_new();
+ GCond *cond = g_cond_new();
+
+ struct input_stream *is =
+ input_stream_open(path, mutex, cond, &error);
if (is == NULL) {
g_printerr("Failed to open %s: %s\n",
@@ -193,33 +218,28 @@ int main(int argc, char **argv)
return 1;
}
- tag = decoder_plugin_stream_tag(plugin, is);
+ success = decoder_plugin_scan_stream(plugin, is,
+ &print_handler, NULL);
input_stream_close(is);
+
+ g_cond_free(cond);
+ g_mutex_free(mutex);
}
decoder_plugin_deinit_all();
input_stream_global_finish();
- if (tag == NULL) {
+ io_thread_deinit();
+
+ if (!success) {
g_printerr("Failed to read tags\n");
return 1;
}
- print_tag(tag);
-
- empty = tag_is_empty(tag);
- tag_free(tag);
-
if (empty) {
- tag = tag_ape_load(path);
- if (tag == NULL)
- tag = tag_id3_load(path);
- if (tag != NULL) {
- print_tag(tag);
- tag_free(tag);
- }
+ tag_ape_scan2(path, &print_handler, NULL);
+ if (empty)
+ tag_id3_scan(path, &print_handler, NULL);
}
- tag_pool_deinit();
-
return 0;
}
diff --git a/test/run_convert.c b/test/run_convert.c
index ae76bc367..4f57400fd 100644
--- a/test/run_convert.c
+++ b/test/run_convert.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -76,13 +76,17 @@ int main(int argc, char **argv)
return 1;
}
- if (!audio_format_parse(&out_audio_format, argv[2],
- false, &error)) {
+ struct audio_format out_audio_format_mask;
+ if (!audio_format_parse(&out_audio_format_mask, argv[2],
+ true, &error)) {
g_printerr("Failed to parse audio format: %s\n",
error->message);
return 1;
}
+ out_audio_format = in_audio_format;
+ audio_format_mask_apply(&out_audio_format, &out_audio_format_mask);
+
const size_t in_frame_size = audio_format_frame_size(&in_audio_format);
pcm_convert_init(&state);
diff --git a/test/run_decoder.c b/test/run_decoder.c
index 154b47324..e6712c75b 100644
--- a/test/run_decoder.c
+++ b/test/run_decoder.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -18,6 +18,7 @@
*/
#include "config.h"
+#include "io_thread.h"
#include "decoder_list.h"
#include "decoder_api.h"
#include "tag_pool.h"
@@ -32,6 +33,7 @@
#include <assert.h>
#include <unistd.h>
+#include <stdlib.h>
static void
my_log_func(const gchar *log_domain, G_GNUC_UNUSED GLogLevelFlags log_level,
@@ -55,8 +57,8 @@ idle_add(G_GNUC_UNUSED unsigned flags)
* No-op dummy.
*/
bool
-pcm_volume(G_GNUC_UNUSED void *buffer, G_GNUC_UNUSED int length,
- G_GNUC_UNUSED const struct audio_format *format,
+pcm_volume(G_GNUC_UNUSED void *buffer, G_GNUC_UNUSED size_t length,
+ G_GNUC_UNUSED enum sample_format format,
G_GNUC_UNUSED int volume)
{
return true;
@@ -111,7 +113,7 @@ decoder_read(G_GNUC_UNUSED struct decoder *decoder,
struct input_stream *is,
void *buffer, size_t length)
{
- return input_stream_read(is, buffer, length, NULL);
+ return input_stream_lock_read(is, buffer, length, NULL);
}
void
@@ -182,6 +184,13 @@ int main(int argc, char **argv)
g_thread_init(NULL);
g_log_set_default_handler(my_log_func, NULL);
+ io_thread_init();
+ if (!io_thread_start(&error)) {
+ g_warning("%s", error->message);
+ g_error_free(error);
+ return EXIT_FAILURE;
+ }
+
tag_pool_init();
if (!input_stream_global_init(&error)) {
@@ -204,8 +213,11 @@ int main(int argc, char **argv)
decoder_plugin_file_decode(decoder.plugin, &decoder,
decoder.uri);
} else if (decoder.plugin->stream_decode != NULL) {
+ GMutex *mutex = g_mutex_new();
+ GCond *cond = g_cond_new();
+
struct input_stream *is =
- input_stream_open(decoder.uri, &error);
+ input_stream_open(decoder.uri, mutex, cond, &error);
if (is == NULL) {
if (error != NULL) {
g_warning("%s", error->message);
@@ -219,6 +231,9 @@ int main(int argc, char **argv)
decoder_plugin_stream_decode(decoder.plugin, &decoder, is);
input_stream_close(is);
+
+ g_cond_free(cond);
+ g_mutex_free(mutex);
} else {
g_printerr("Decoder plugin is not usable\n");
return 1;
@@ -226,6 +241,7 @@ int main(int argc, char **argv)
decoder_plugin_deinit_all();
input_stream_global_finish();
+ io_thread_deinit();
if (!decoder.initialized) {
g_printerr("Decoding failed\n");
diff --git a/test/run_encoder.c b/test/run_encoder.c
index 4c05a06c7..6a4108620 100644
--- a/test/run_encoder.c
+++ b/test/run_encoder.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -76,7 +76,7 @@ int main(int argc, char **argv)
}
param = config_new_param(NULL, -1);
- config_add_block_param(param, "quality", "5.0", -1, NULL);
+ config_add_block_param(param, "quality", "5.0", -1);
encoder = encoder_init(plugin, param, &error);
if (encoder == NULL) {
diff --git a/test/run_filter.c b/test/run_filter.c
index 3758eb5bb..8e793b768 100644
--- a/test/run_filter.c
+++ b/test/run_filter.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -183,7 +183,7 @@ int main(int argc, char **argv)
nbytes = write(1, dest, length);
if (nbytes < 0) {
- g_printerr("Failed to write: %s\n", strerror(errno));
+ g_printerr("Failed to write: %s\n", g_strerror(errno));
filter_close(filter);
filter_free(filter);
return 1;
diff --git a/test/run_inotify.c b/test/run_inotify.c
index 9f3c30b8c..3e7c70dba 100644
--- a/test/run_inotify.c
+++ b/test/run_inotify.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/test/run_input.c b/test/run_input.c
index a50cd70ab..676e4e618 100644
--- a/test/run_input.c
+++ b/test/run_input.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -18,6 +18,7 @@
*/
#include "config.h"
+#include "io_thread.h"
#include "input_init.h"
#include "input_stream.h"
#include "tag_pool.h"
@@ -32,6 +33,7 @@
#include <glib.h>
#include <unistd.h>
+#include <stdlib.h>
static void
my_log_func(const gchar *log_domain, G_GNUC_UNUSED GLogLevelFlags log_level,
@@ -51,20 +53,17 @@ dump_input_stream(struct input_stream *is)
size_t num_read;
ssize_t num_written;
+ input_stream_lock(is);
+
/* wait until the stream becomes ready */
- while (!is->ready) {
- int ret = input_stream_buffer(is, &error);
- if (ret < 0) {
- /* error */
- g_warning("%s", error->message);
- g_error_free(error);
- return 2;
- }
+ input_stream_wait_ready(is);
- if (ret == 0)
- /* nothing was buffered - wait */
- g_usleep(10000);
+ if (!input_stream_check(is, &error)) {
+ g_warning("%s", error->message);
+ g_error_free(error);
+ input_stream_unlock(is);
+ return EXIT_FAILURE;
}
/* print meta data */
@@ -98,6 +97,15 @@ dump_input_stream(struct input_stream *is)
break;
}
+ if (!input_stream_check(is, &error)) {
+ g_warning("%s", error->message);
+ g_error_free(error);
+ input_stream_unlock(is);
+ return EXIT_FAILURE;
+ }
+
+ input_stream_unlock(is);
+
return 0;
}
@@ -122,6 +130,13 @@ int main(int argc, char **argv)
tag_pool_init();
config_global_init();
+ io_thread_init();
+ if (!io_thread_start(&error)) {
+ g_warning("%s", error->message);
+ g_error_free(error);
+ return EXIT_FAILURE;
+ }
+
#ifdef ENABLE_ARCHIVE
archive_plugin_init_all();
#endif
@@ -134,7 +149,10 @@ int main(int argc, char **argv)
/* open the stream and dump it */
- is = input_stream_open(argv[1], &error);
+ GMutex *mutex = g_mutex_new();
+ GCond *cond = g_cond_new();
+
+ is = input_stream_open(argv[1], mutex, cond, &error);
if (is != NULL) {
ret = dump_input_stream(is);
input_stream_close(is);
@@ -147,6 +165,9 @@ int main(int argc, char **argv)
ret = 2;
}
+ g_cond_free(cond);
+ g_mutex_free(mutex);
+
/* deinitialize everything */
input_stream_global_finish();
@@ -155,6 +176,8 @@ int main(int argc, char **argv)
archive_plugin_deinit_all();
#endif
+ io_thread_deinit();
+
config_global_finish();
tag_pool_deinit();
diff --git a/test/run_normalize.c b/test/run_normalize.c
index 958015973..fc26829ed 100644
--- a/test/run_normalize.c
+++ b/test/run_normalize.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/test/run_ntp_server.c b/test/run_ntp_server.c
new file mode 100644
index 000000000..842d1852e
--- /dev/null
+++ b/test/run_ntp_server.c
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "ntp_server.h"
+#include "signals.h"
+#include "io_thread.h"
+
+#include <glib.h>
+
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+
+#ifdef WIN32
+#include <ws2tcpip.h>
+#include <winsock.h>
+#else
+#include <sys/socket.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#endif
+
+void
+on_quit(void)
+{
+ io_thread_quit();
+}
+
+int
+main(G_GNUC_UNUSED int argc, G_GNUC_UNUSED char **argv)
+{
+ g_thread_init(NULL);
+ signals_init();
+ io_thread_init();
+
+ struct ntp_server ntp;
+ ntp_server_init(&ntp);
+
+ GError *error = NULL;
+ if (!ntp_server_open(&ntp, &error)) {
+ io_thread_deinit();
+ g_printerr("%s\n", error->message);
+ g_error_free(error);
+ return EXIT_FAILURE;
+ }
+
+ io_thread_run();
+
+ ntp_server_close(&ntp);
+ io_thread_deinit();
+ return EXIT_SUCCESS;
+}
diff --git a/test/run_output.c b/test/run_output.c
index 5028068ff..bbb1be7d2 100644
--- a/test/run_output.c
+++ b/test/run_output.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -18,6 +18,7 @@
*/
#include "config.h"
+#include "io_thread.h"
#include "output_plugin.h"
#include "output_internal.h"
#include "output_control.h"
@@ -28,6 +29,7 @@
#include "event_pipe.h"
#include "idle.h"
#include "playlist.h"
+#include "player_control.h"
#include "stdbin.h"
#include <glib.h>
@@ -35,6 +37,7 @@
#include <assert.h>
#include <string.h>
#include <unistd.h>
+#include <stdlib.h>
struct playlist g_playlist;
@@ -91,11 +94,10 @@ find_named_config_block(const char *block, const char *name)
return NULL;
}
-static bool
-load_audio_output(struct audio_output *ao, const char *name)
+static struct audio_output *
+load_audio_output(const char *name)
{
const struct config_param *param;
- bool success;
GError *error = NULL;
param = find_named_config_block(CONF_AUDIO_OUTPUT, name);
@@ -104,25 +106,91 @@ load_audio_output(struct audio_output *ao, const char *name)
return false;
}
- success = audio_output_init(ao, param, &error);
- if (!success) {
+ static struct player_control dummy_player_control;
+
+ struct audio_output *ao =
+ audio_output_new(param, &dummy_player_control, &error);
+ if (ao == NULL) {
g_printerr("%s\n", error->message);
g_error_free(error);
}
- return success;
+ return ao;
+}
+
+static bool
+run_output(struct audio_output *ao, struct audio_format *audio_format)
+{
+ /* open the audio output */
+
+ GError *error = NULL;
+ if (!ao_plugin_enable(ao, &error)) {
+ g_printerr("Failed to enable audio output: %s\n",
+ error->message);
+ g_error_free(error);
+ return false;
+ }
+
+ if (!ao_plugin_open(ao, audio_format, &error)) {
+ ao_plugin_disable(ao);
+ g_printerr("Failed to open audio output: %s\n",
+ error->message);
+ g_error_free(error);
+ return false;
+ }
+
+ struct audio_format_string af_string;
+ g_printerr("audio_format=%s\n",
+ audio_format_to_string(audio_format, &af_string));
+
+ size_t frame_size = audio_format_frame_size(audio_format);
+
+ /* play */
+
+ size_t length = 0;
+ char buffer[4096];
+ while (true) {
+ if (length < sizeof(buffer)) {
+ ssize_t nbytes = read(0, buffer + length,
+ sizeof(buffer) - length);
+ if (nbytes <= 0)
+ break;
+
+ length += (size_t)nbytes;
+ }
+
+ size_t play_length = (length / frame_size) * frame_size;
+ if (play_length > 0) {
+ size_t consumed = ao_plugin_play(ao,
+ buffer, play_length,
+ &error);
+ if (consumed == 0) {
+ ao_plugin_close(ao);
+ ao_plugin_disable(ao);
+ g_printerr("Failed to play: %s\n",
+ error->message);
+ g_error_free(error);
+ return false;
+ }
+
+ assert(consumed <= length);
+ assert(consumed % frame_size == 0);
+
+ length -= consumed;
+ memmove(buffer, buffer + consumed, length);
+ }
+ }
+
+ ao_plugin_close(ao);
+ ao_plugin_disable(ao);
+ return true;
}
int main(int argc, char **argv)
{
- struct audio_output ao;
struct audio_format audio_format;
- struct audio_format_string af_string;
bool success;
GError *error = NULL;
- char buffer[4096];
- ssize_t nbytes;
- size_t frame_size, length = 0, play_length, consumed;
if (argc < 3 || argc > 4) {
g_printerr("Usage: run_output CONFIG NAME [FORMAT] <IN\n");
@@ -143,9 +211,17 @@ int main(int argc, char **argv)
return 1;
}
+ io_thread_init();
+ if (!io_thread_start(&error)) {
+ g_warning("%s", error->message);
+ g_error_free(error);
+ return EXIT_FAILURE;
+ }
+
/* initialize the audio output */
- if (!load_audio_output(&ao, argv[2]))
+ struct audio_output *ao = load_audio_output(argv[2]);
+ if (ao == NULL)
return 1;
/* parse the audio format */
@@ -161,59 +237,17 @@ int main(int argc, char **argv)
}
}
- /* open the audio output */
-
- success = ao_plugin_open(ao.plugin, ao.data, &audio_format, &error);
- if (!success) {
- g_printerr("Failed to open audio output: %s\n",
- error->message);
- g_error_free(error);
- return 1;
- }
-
- g_printerr("audio_format=%s\n",
- audio_format_to_string(&audio_format, &af_string));
-
- frame_size = audio_format_frame_size(&audio_format);
-
- /* play */
-
- while (true) {
- if (length < sizeof(buffer)) {
- nbytes = read(0, buffer + length, sizeof(buffer) - length);
- if (nbytes <= 0)
- break;
+ /* do it */
- length += (size_t)nbytes;
- }
-
- play_length = (length / frame_size) * frame_size;
- if (play_length > 0) {
- consumed = ao_plugin_play(ao.plugin, ao.data,
- buffer, play_length,
- &error);
- if (consumed == 0) {
- g_printerr("Failed to play: %s\n",
- error->message);
- g_error_free(error);
- return 1;
- }
-
- assert(consumed <= length);
- assert(consumed % frame_size == 0);
-
- length -= consumed;
- memmove(buffer, buffer + consumed, length);
- }
- }
+ success = run_output(ao, &audio_format);
/* cleanup and exit */
- ao_plugin_close(ao.plugin, ao.data);
- ao_plugin_finish(ao.plugin, ao.data);
- g_mutex_free(ao.mutex);
+ audio_output_free(ao);
+
+ io_thread_deinit();
config_global_finish();
- return 0;
+ return success ? EXIT_SUCCESS : EXIT_FAILURE;
}
diff --git a/test/run_resolver.c b/test/run_resolver.c
new file mode 100644
index 000000000..ee0bc0172
--- /dev/null
+++ b/test/run_resolver.c
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "resolver.h"
+
+#ifdef WIN32
+#include <ws2tcpip.h>
+#include <winsock.h>
+#else
+#include <sys/socket.h>
+#include <netdb.h>
+#endif
+
+#include <stdlib.h>
+
+int main(int argc, char **argv)
+{
+ if (argc != 2) {
+ g_printerr("Usage: run_resolver HOST\n");
+ return EXIT_FAILURE;
+ }
+
+ GError *error = NULL;
+ struct addrinfo *ai =
+ resolve_host_port(argv[1], 80, AI_PASSIVE, SOCK_STREAM,
+ &error);
+ if (ai == NULL) {
+ g_printerr("%s\n", error->message);
+ g_error_free(error);
+ return EXIT_FAILURE;
+ }
+
+ for (const struct addrinfo *i = ai; i != NULL; i = i->ai_next) {
+ char *p = sockaddr_to_string(i->ai_addr, i->ai_addrlen,
+ &error);
+ if (p == NULL) {
+ freeaddrinfo(ai);
+ g_printerr("%s\n", error->message);
+ g_error_free(error);
+ return EXIT_FAILURE;
+ }
+
+ g_print("%s\n", p);
+ g_free(p);
+ }
+
+ freeaddrinfo(ai);
+ return EXIT_SUCCESS;
+}
diff --git a/test/run_tcp_connect.c b/test/run_tcp_connect.c
new file mode 100644
index 000000000..bf8d9b82f
--- /dev/null
+++ b/test/run_tcp_connect.c
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "resolver.h"
+#include "io_thread.h"
+#include "tcp_connect.h"
+#include "fd_util.h"
+
+#include <assert.h>
+#include <stdlib.h>
+
+#ifdef WIN32
+#include <ws2tcpip.h>
+#include <winsock.h>
+#else
+#include <sys/socket.h>
+#include <netdb.h>
+#endif
+
+static struct tcp_connect *handle;
+static GMutex *mutex;
+static GCond *cond;
+static bool done, success;
+
+static void
+my_tcp_connect_success(int fd, G_GNUC_UNUSED void *ctx)
+{
+ assert(!done);
+ assert(!success);
+
+ close_socket(fd);
+ g_print("success\n");
+
+ g_mutex_lock(mutex);
+ done = success = true;
+ g_cond_signal(cond);
+ g_mutex_unlock(mutex);
+}
+
+static void
+my_tcp_connect_error(GError *error, G_GNUC_UNUSED void *ctx)
+{
+ assert(!done);
+ assert(!success);
+
+ g_printerr("error: %s\n", error->message);
+ g_error_free(error);
+
+ g_mutex_lock(mutex);
+ done = true;
+ g_cond_signal(cond);
+ g_mutex_unlock(mutex);
+}
+
+static void
+my_tcp_connect_timeout(G_GNUC_UNUSED void *ctx)
+{
+ assert(!done);
+ assert(!success);
+
+ g_printerr("timeout\n");
+
+ g_mutex_lock(mutex);
+ done = true;
+ g_cond_signal(cond);
+ g_mutex_unlock(mutex);
+}
+
+static void
+my_tcp_connect_canceled(G_GNUC_UNUSED void *ctx)
+{
+ assert(!done);
+ assert(!success);
+
+ g_printerr("canceled\n");
+
+ g_mutex_lock(mutex);
+ done = true;
+ g_cond_signal(cond);
+ g_mutex_unlock(mutex);
+}
+
+static const struct tcp_connect_handler my_tcp_connect_handler = {
+ .success = my_tcp_connect_success,
+ .error = my_tcp_connect_error,
+ .timeout = my_tcp_connect_timeout,
+ .canceled = my_tcp_connect_canceled,
+};
+
+int main(int argc, char **argv)
+{
+ if (argc != 2) {
+ g_printerr("Usage: run_tcp_connect IP:PORT\n");
+ return 1;
+ }
+
+ GError *error = NULL;
+ struct addrinfo *ai = resolve_host_port(argv[1], 80, 0, SOCK_STREAM,
+ &error);
+ if (ai == NULL) {
+ g_printerr("%s\n", error->message);
+ g_error_free(error);
+ return EXIT_FAILURE;
+ }
+
+ /* initialize GLib */
+
+ g_thread_init(NULL);
+
+ /* initialize MPD */
+
+ io_thread_init();
+ if (!io_thread_start(&error)) {
+ freeaddrinfo(ai);
+ g_printerr("%s", error->message);
+ g_error_free(error);
+ return EXIT_FAILURE;
+ }
+
+ /* open the connection */
+
+ mutex = g_mutex_new();
+ cond = g_cond_new();
+
+ tcp_connect_address(ai->ai_addr, ai->ai_addrlen, 5000,
+ &my_tcp_connect_handler, NULL,
+ &handle);
+ freeaddrinfo(ai);
+
+ if (handle != NULL) {
+ g_mutex_lock(mutex);
+ while (!done)
+ g_cond_wait(cond, mutex);
+ g_mutex_unlock(mutex);
+
+ tcp_connect_free(handle);
+ }
+
+ g_cond_free(cond);
+ g_mutex_free(mutex);
+
+ /* deinitialize everything */
+
+ io_thread_deinit();
+
+ return EXIT_SUCCESS;
+}
diff --git a/test/signals.c b/test/signals.c
new file mode 100644
index 000000000..15761f6b0
--- /dev/null
+++ b/test/signals.c
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "signals.h"
+#ifndef WIN32
+
+#include "mpd_error.h"
+
+#include <glib.h>
+
+#include <signal.h>
+#include <errno.h>
+#include <string.h>
+
+static void
+quit_signal_handler(G_GNUC_UNUSED int signum)
+{
+ on_quit();
+}
+
+static void
+x_sigaction(int signum, const struct sigaction *act)
+{
+ if (sigaction(signum, act, NULL) < 0)
+ MPD_ERROR("sigaction() failed: %s", g_strerror(errno));
+}
+
+#endif
+
+void
+signals_init(void)
+{
+#ifndef WIN32
+ struct sigaction sa;
+
+ sa.sa_flags = 0;
+ sigemptyset(&sa.sa_mask);
+ sa.sa_handler = SIG_IGN;
+ x_sigaction(SIGPIPE, &sa);
+
+ sa.sa_handler = quit_signal_handler;
+ x_sigaction(SIGINT, &sa);
+ x_sigaction(SIGTERM, &sa);
+#endif
+}
diff --git a/test/signals.h b/test/signals.h
new file mode 100644
index 000000000..e524d35e2
--- /dev/null
+++ b/test/signals.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_SIGNALS_H
+#define MPD_SIGNALS_H
+
+void
+on_quit(void);
+
+void
+signals_init(void);
+
+#endif
diff --git a/test/software_volume.c b/test/software_volume.c
index 789fffe61..2357da672 100644
--- a/test/software_volume.c
+++ b/test/software_volume.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -59,7 +59,7 @@ int main(int argc, char **argv)
audio_format_init(&audio_format, 48000, SAMPLE_FORMAT_S16, 2);
while ((nbytes = read(0, buffer, sizeof(buffer))) > 0) {
- if (!pcm_volume(buffer, nbytes, &audio_format,
+ if (!pcm_volume(buffer, nbytes, audio_format.format,
PCM_VOLUME_1 / 2)) {
g_printerr("pcm_volume() has failed\n");
return 2;
diff --git a/test/stdbin.h b/test/stdbin.h
index 362605ad9..48cac7338 100644
--- a/test/stdbin.h
+++ b/test/stdbin.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/test/test_byte_reverse.c b/test/test_byte_reverse.c
new file mode 100644
index 000000000..7678e9cef
--- /dev/null
+++ b/test/test_byte_reverse.c
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2003-2012 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "util/byte_reverse.h"
+#include "test_glib_compat.h"
+
+#include <glib.h>
+
+static void
+test_byte_reverse_2(void)
+{
+ static const char src[] = "123456";
+ static const char result[] = "214365";
+ static uint8_t dest[G_N_ELEMENTS(src)];
+
+ reverse_bytes(dest, (const uint8_t *)src,
+ (const uint8_t *)(src + G_N_ELEMENTS(src) - 1), 2);
+ g_assert_cmpstr((const char *)dest, ==, result);
+}
+
+static void
+test_byte_reverse_3(void)
+{
+ static const char src[] = "123456";
+ static const char result[] = "321654";
+ static uint8_t dest[G_N_ELEMENTS(src)];
+
+ reverse_bytes(dest, (const uint8_t *)src,
+ (const uint8_t *)(src + G_N_ELEMENTS(src) - 1), 3);
+ g_assert_cmpstr((const char *)dest, ==, result);
+}
+
+static void
+test_byte_reverse_4(void)
+{
+ static const char src[] = "12345678";
+ static const char result[] = "43218765";
+ static uint8_t dest[G_N_ELEMENTS(src)];
+
+ reverse_bytes(dest, (const uint8_t *)src,
+ (const uint8_t *)(src + G_N_ELEMENTS(src) - 1), 4);
+ g_assert_cmpstr((const char *)dest, ==, result);
+}
+
+static void
+test_byte_reverse_5(void)
+{
+ static const char src[] = "1234567890";
+ static const char result[] = "5432109876";
+ static uint8_t dest[G_N_ELEMENTS(src)];
+
+ reverse_bytes(dest, (const uint8_t *)src,
+ (const uint8_t *)(src + G_N_ELEMENTS(src) - 1), 5);
+ g_assert_cmpstr((const char *)dest, ==, result);
+}
+
+int
+main(int argc, char **argv)
+{
+ g_test_init (&argc, &argv, NULL);
+ g_test_add_func("/util/byte_reverse/2", test_byte_reverse_2);
+ g_test_add_func("/util/byte_reverse/3", test_byte_reverse_3);
+ g_test_add_func("/util/byte_reverse/4", test_byte_reverse_4);
+ g_test_add_func("/util/byte_reverse/5", test_byte_reverse_5);
+
+ g_test_run();
+}
diff --git a/test/test_glib_compat.h b/test/test_glib_compat.h
new file mode 100644
index 000000000..2e4ab123c
--- /dev/null
+++ b/test/test_glib_compat.h
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/*
+ * Compatibility header for GLib before 2.16.
+ */
+
+#ifndef MPD_TEST_GLIB_COMPAT_H
+#define MPD_TEST_GLIB_COMPAT_H
+
+#include <glib.h>
+
+#if !GLIB_CHECK_VERSION(2,16,0)
+
+#include <string.h>
+
+#define g_assert_cmpint(n1, cmp, n2) g_assert((n1) cmp (n2))
+#define g_assert_cmpstr(a, cmp, b) g_assert(strcmp(a, b) cmp 0)
+
+static void (*test_functions[256])(void);
+static unsigned num_test_functions;
+
+static inline void
+g_test_init(G_GNUC_UNUSED int *argc, G_GNUC_UNUSED char ***argv, ...)
+{
+}
+
+static inline void
+g_test_add_func(G_GNUC_UNUSED const char *testpath, void (test_funcvoid)(void))
+{
+ test_functions[num_test_functions++] = test_funcvoid;
+}
+
+static inline int
+g_test_run(void)
+{
+ for (unsigned i = 0; i < num_test_functions; ++i)
+ test_functions[i]();
+ return 0;
+}
+
+#endif /* !2.16 */
+
+#endif
diff --git a/test/test_pcm_all.h b/test/test_pcm_all.h
new file mode 100644
index 000000000..14e4c2980
--- /dev/null
+++ b/test/test_pcm_all.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_TEST_PCM_ALL_H
+#define MPD_TEST_PCM_ALL_H
+
+void
+test_pcm_dither_24(void);
+
+void
+test_pcm_dither_32(void);
+
+void
+test_pcm_pack_24(void);
+
+void
+test_pcm_unpack_24(void);
+
+void
+test_pcm_channels_16(void);
+
+void
+test_pcm_channels_32(void);
+
+#endif
diff --git a/test/test_pcm_channels.c b/test/test_pcm_channels.c
new file mode 100644
index 000000000..877ae3262
--- /dev/null
+++ b/test/test_pcm_channels.c
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "test_pcm_all.h"
+#include "test_glib_compat.h"
+#include "pcm_channels.h"
+#include "pcm_buffer.h"
+
+#include <glib.h>
+
+void
+test_pcm_channels_16(void)
+{
+ enum { N = 256 };
+ int16_t src[N * 2];
+
+ for (unsigned i = 0; i < G_N_ELEMENTS(src); ++i)
+ src[i] = g_random_int();
+
+ struct pcm_buffer buffer;
+ pcm_buffer_init(&buffer);
+
+ /* stereo to mono */
+
+ size_t dest_size;
+ const int16_t *dest =
+ pcm_convert_channels_16(&buffer, 1, 2, src, sizeof(src),
+ &dest_size);
+ g_assert(dest != NULL);
+ g_assert_cmpint(dest_size, ==, sizeof(src) / 2);
+ for (unsigned i = 0; i < N; ++i)
+ g_assert_cmpint(dest[i], ==,
+ (src[i * 2] + src[i * 2 + 1]) / 2);
+
+ /* mono to stereo */
+
+ dest = pcm_convert_channels_16(&buffer, 2, 1, src, sizeof(src),
+ &dest_size);
+ g_assert(dest != NULL);
+ g_assert_cmpint(dest_size, ==, sizeof(src) * 2);
+ for (unsigned i = 0; i < N; ++i) {
+ g_assert_cmpint(dest[i * 2], ==, src[i]);
+ g_assert_cmpint(dest[i * 2 + 1], ==, src[i]);
+ }
+
+ pcm_buffer_deinit(&buffer);
+}
+
+void
+test_pcm_channels_32(void)
+{
+ enum { N = 256 };
+ int32_t src[N * 2];
+
+ for (unsigned i = 0; i < G_N_ELEMENTS(src); ++i)
+ src[i] = g_random_int();
+
+ struct pcm_buffer buffer;
+ pcm_buffer_init(&buffer);
+
+ /* stereo to mono */
+
+ size_t dest_size;
+ const int32_t *dest =
+ pcm_convert_channels_32(&buffer, 1, 2, src, sizeof(src),
+ &dest_size);
+ g_assert(dest != NULL);
+ g_assert_cmpint(dest_size, ==, sizeof(src) / 2);
+ for (unsigned i = 0; i < N; ++i)
+ g_assert_cmpint(dest[i], ==,
+ ((int64_t)src[i * 2] + (int64_t)src[i * 2 + 1]) / 2);
+
+ /* mono to stereo */
+
+ dest = pcm_convert_channels_32(&buffer, 2, 1, src, sizeof(src),
+ &dest_size);
+ g_assert(dest != NULL);
+ g_assert_cmpint(dest_size, ==, sizeof(src) * 2);
+ for (unsigned i = 0; i < N; ++i) {
+ g_assert_cmpint(dest[i * 2], ==, src[i]);
+ g_assert_cmpint(dest[i * 2 + 1], ==, src[i]);
+ }
+
+ pcm_buffer_deinit(&buffer);
+}
diff --git a/test/test_pcm_dither.c b/test/test_pcm_dither.c
new file mode 100644
index 000000000..24b0dd040
--- /dev/null
+++ b/test/test_pcm_dither.c
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "test_pcm_all.h"
+#include "test_glib_compat.h"
+#include "pcm_dither.h"
+
+#include <glib.h>
+
+/**
+ * Generate a random 24 bit PCM sample.
+ */
+static int32_t
+random24(void)
+{
+ int32_t x = g_random_int() & 0xffffff;
+ if (x & 0x800000)
+ x |= 0xff000000;
+ return x;
+}
+
+void
+test_pcm_dither_24(void)
+{
+ struct pcm_dither dither;
+
+ pcm_dither_24_init(&dither);
+
+ enum { N = 256 };
+ int32_t src[N];
+ for (unsigned i = 0; i < N; ++i)
+ src[i] = random24();
+
+ int16_t dest[N];
+
+ pcm_dither_24_to_16(&dither, dest, src, src + N);
+
+ for (unsigned i = 0; i < N; ++i) {
+ g_assert_cmpint(dest[i], >=, (src[i] >> 8) - 8);
+ g_assert_cmpint(dest[i], <, (src[i] >> 8) + 8);
+ }
+}
+
+void
+test_pcm_dither_32(void)
+{
+ struct pcm_dither dither;
+
+ pcm_dither_24_init(&dither);
+
+ enum { N = 256 };
+ int32_t src[N];
+ for (unsigned i = 0; i < N; ++i)
+ src[i] = g_random_int();
+
+ int16_t dest[N];
+
+ pcm_dither_32_to_16(&dither, dest, src, src + N);
+
+ for (unsigned i = 0; i < N; ++i) {
+ g_assert_cmpint(dest[i], >=, (src[i] >> 16) - 8);
+ g_assert_cmpint(dest[i], <, (src[i] >> 16) + 8);
+ }
+}
diff --git a/test/test_pcm_main.c b/test/test_pcm_main.c
new file mode 100644
index 000000000..7047b5251
--- /dev/null
+++ b/test/test_pcm_main.c
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "test_pcm_all.h"
+#include "test_glib_compat.h"
+
+#include <glib.h>
+
+int
+main(int argc, char **argv)
+{
+ g_test_init (&argc, &argv, NULL);
+ g_test_add_func("/pcm/dither/24", test_pcm_dither_24);
+ g_test_add_func("/pcm/dither/32", test_pcm_dither_32);
+ g_test_add_func("/pcm/pack/pack24", test_pcm_pack_24);
+ g_test_add_func("/pcm/pack/unpack24", test_pcm_unpack_24);
+ g_test_add_func("/pcm/channels/16", test_pcm_channels_16);
+ g_test_add_func("/pcm/channels/32", test_pcm_channels_32);
+
+ g_test_run();
+}
diff --git a/test/test_pcm_pack.c b/test/test_pcm_pack.c
new file mode 100644
index 000000000..de5d8b6e5
--- /dev/null
+++ b/test/test_pcm_pack.c
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2003-2011 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "test_pcm_all.h"
+#include "pcm_pack.h"
+#include "test_glib_compat.h"
+
+#include <glib.h>
+
+/**
+ * Generate a random 24 bit PCM sample.
+ */
+static int32_t
+random24(void)
+{
+ int32_t x = g_random_int() & 0xffffff;
+ if (x & 0x800000)
+ x |= 0xff000000;
+ return x;
+}
+
+void
+test_pcm_pack_24(void)
+{
+ enum { N = 256 };
+ int32_t src[N * 3];
+ for (unsigned i = 0; i < N; ++i)
+ src[i] = random24();
+
+ uint8_t dest[N * 3];
+
+ pcm_pack_24(dest, src, src + N);
+
+ for (unsigned i = 0; i < N; ++i) {
+ int32_t d;
+ if (G_BYTE_ORDER == G_BIG_ENDIAN)
+ d = (dest[i * 3] << 16) | (dest[i * 3 + 1] << 8)
+ | dest[i * 3 + 2];
+ else
+ d = (dest[i * 3 + 2] << 16) | (dest[i * 3 + 1] << 8)
+ | dest[i * 3];
+ if (d & 0x800000)
+ d |= 0xff000000;
+
+ g_assert_cmpint(d, ==, src[i]);
+ }
+}
+
+void
+test_pcm_unpack_24(void)
+{
+ enum { N = 256 };
+ uint8_t src[N * 3];
+ for (unsigned i = 0; i < G_N_ELEMENTS(src); ++i)
+ src[i] = g_random_int_range(0, 256);
+
+ int32_t dest[N];
+
+ pcm_unpack_24(dest, src, src + G_N_ELEMENTS(src));
+
+ for (unsigned i = 0; i < N; ++i) {
+ int32_t s;
+ if (G_BYTE_ORDER == G_BIG_ENDIAN)
+ s = (src[i * 3] << 16) | (src[i * 3 + 1] << 8)
+ | src[i * 3 + 2];
+ else
+ s = (src[i * 3 + 2] << 16) | (src[i * 3 + 1] << 8)
+ | src[i * 3];
+ if (s & 0x800000)
+ s |= 0xff000000;
+
+ g_assert_cmpint(s, ==, dest[i]);
+ }
+}
diff --git a/test/test_queue_priority.c b/test/test_queue_priority.c
new file mode 100644
index 000000000..a7106a8e9
--- /dev/null
+++ b/test/test_queue_priority.c
@@ -0,0 +1,175 @@
+#include "queue.h"
+#include "song.h"
+
+void
+song_free(G_GNUC_UNUSED struct song *song)
+{
+}
+
+G_GNUC_UNUSED
+static void
+dump_order(const struct queue *queue)
+{
+ g_printerr("queue length=%u, order:\n", queue_length(queue));
+ for (unsigned i = 0; i < queue_length(queue); ++i)
+ g_printerr(" [%u] -> %u (prio=%u)\n", i, queue->order[i],
+ queue->items[queue->order[i]].priority);
+}
+
+static void
+check_descending_priority(G_GNUC_UNUSED const struct queue *queue,
+ unsigned start_order)
+{
+ assert(start_order < queue_length(queue));
+
+ uint8_t last_priority = 0xff;
+ for (unsigned order = start_order; order < queue_length(queue); ++order) {
+ unsigned position = queue_order_to_position(queue, order);
+ uint8_t priority = queue->items[position].priority;
+ assert(priority <= last_priority);
+ (void)last_priority;
+ last_priority = priority;
+ }
+}
+
+int
+main(G_GNUC_UNUSED int argc, G_GNUC_UNUSED char **argv)
+{
+ struct song songs[16];
+
+ struct queue queue;
+ queue_init(&queue, 32);
+
+ for (unsigned i = 0; i < G_N_ELEMENTS(songs); ++i)
+ queue_append(&queue, &songs[i]);
+
+ assert(queue_length(&queue) == G_N_ELEMENTS(songs));
+
+ /* priority=10 for 4 items */
+
+ queue_set_priority_range(&queue, 4, 8, 10, -1);
+
+ queue.random = true;
+ queue_shuffle_order(&queue);
+ check_descending_priority(&queue, 0);
+
+ for (unsigned i = 0; i < 4; ++i) {
+ assert(queue_position_to_order(&queue, i) >= 4);
+ }
+
+ for (unsigned i = 4; i < 8; ++i) {
+ assert(queue_position_to_order(&queue, i) < 4);
+ }
+
+ for (unsigned i = 8; i < G_N_ELEMENTS(songs); ++i) {
+ assert(queue_position_to_order(&queue, i) >= 4);
+ }
+
+ /* priority=50 one more item */
+
+ queue_set_priority_range(&queue, 15, 16, 50, -1);
+ check_descending_priority(&queue, 0);
+
+ assert(queue_position_to_order(&queue, 15) == 0);
+
+ for (unsigned i = 0; i < 4; ++i) {
+ assert(queue_position_to_order(&queue, i) >= 4);
+ }
+
+ for (unsigned i = 4; i < 8; ++i) {
+ assert(queue_position_to_order(&queue, i) >= 1 &&
+ queue_position_to_order(&queue, i) < 5);
+ }
+
+ for (unsigned i = 8; i < 15; ++i) {
+ assert(queue_position_to_order(&queue, i) >= 5);
+ }
+
+ /* priority=20 for one of the 4 priority=10 items */
+
+ queue_set_priority_range(&queue, 3, 4, 20, -1);
+ check_descending_priority(&queue, 0);
+
+ assert(queue_position_to_order(&queue, 3) == 1);
+ assert(queue_position_to_order(&queue, 15) == 0);
+
+ for (unsigned i = 0; i < 3; ++i) {
+ assert(queue_position_to_order(&queue, i) >= 5);
+ }
+
+ for (unsigned i = 4; i < 8; ++i) {
+ assert(queue_position_to_order(&queue, i) >= 2 &&
+ queue_position_to_order(&queue, i) < 6);
+ }
+
+ for (unsigned i = 8; i < 15; ++i) {
+ assert(queue_position_to_order(&queue, i) >= 6);
+ }
+
+ /* priority=20 for another one of the 4 priority=10 items;
+ pass "after_order" (with priority=10) and see if it's moved
+ after that one */
+
+ unsigned current_order = 4;
+ unsigned current_position =
+ queue_order_to_position(&queue, current_order);
+
+ unsigned a_order = 3;
+ unsigned a_position = queue_order_to_position(&queue, a_order);
+ assert(queue.items[a_position].priority == 10);
+ queue_set_priority(&queue, a_position, 20, current_order);
+
+ current_order = queue_position_to_order(&queue, current_position);
+ assert(current_order == 3);
+
+ a_order = queue_position_to_order(&queue, a_position);
+ assert(a_order == 4);
+
+ check_descending_priority(&queue, current_order + 1);
+
+ /* priority=70 for one of the last items; must be inserted
+ right after the current song, before the priority=20 one we
+ just created */
+
+ unsigned b_order = 10;
+ unsigned b_position = queue_order_to_position(&queue, b_order);
+ assert(queue.items[b_position].priority == 0);
+ queue_set_priority(&queue, b_position, 70, current_order);
+
+ current_order = queue_position_to_order(&queue, current_position);
+ assert(current_order == 3);
+
+ b_order = queue_position_to_order(&queue, b_position);
+ assert(b_order == 4);
+
+ check_descending_priority(&queue, current_order + 1);
+
+ /* priority=60 for the old prio50 item; must not be moved,
+ because it's before the current song, and it's status
+ hasn't changed (it was already higher before) */
+
+ unsigned c_order = 0;
+ unsigned c_position = queue_order_to_position(&queue, c_order);
+ assert(queue.items[c_position].priority == 50);
+ queue_set_priority(&queue, c_position, 60, current_order);
+
+ current_order = queue_position_to_order(&queue, current_position);
+ assert(current_order == 3);
+
+ c_order = queue_position_to_order(&queue, c_position);
+ assert(c_order == 0);
+
+ /* move the prio=20 item back */
+
+ a_order = queue_position_to_order(&queue, a_position);
+ assert(a_order == 5);
+ assert(queue.items[a_position].priority == 20);
+ queue_set_priority(&queue, a_position, 5, current_order);
+
+
+ current_order = queue_position_to_order(&queue, current_position);
+ assert(current_order == 3);
+
+ a_order = queue_position_to_order(&queue, a_position);
+ assert(a_order == 6);
+}
diff --git a/test/test_vorbis_encoder.c b/test/test_vorbis_encoder.c
index 969ab7687..048d26f0a 100644
--- a/test/test_vorbis_encoder.c
+++ b/test/test_vorbis_encoder.c
@@ -54,7 +54,7 @@ main(G_GNUC_UNUSED int argc, G_GNUC_UNUSED char **argv)
assert(plugin != NULL);
struct config_param *param = config_new_param(NULL, -1);
- config_add_block_param(param, "quality", "5.0", -1, NULL);
+ config_add_block_param(param, "quality", "5.0", -1);
struct encoder *encoder = encoder_init(plugin, param, NULL);
assert(encoder != NULL);
diff --git a/valgrind.suppressions b/valgrind.suppressions
index 8d687f7b8..a6ffa13c9 100644
--- a/valgrind.suppressions
+++ b/valgrind.suppressions
@@ -4,66 +4,29 @@
# bogus messages.
{
- g_main_context_dispatch
- Memcheck:Leak
- fun:malloc
- fun:g_malloc
- fun:g_slice_alloc
- fun:g_slice_alloc0
- fun:get_dispatch
- fun:g_main_context_dispatch
-}
-
-{
- g_main_context_default
- Memcheck:Leak
- fun:malloc
- fun:g_malloc
- fun:g_slice_alloc
- fun:g_slist_append
- fun:g_main_context_new
- fun:g_main_context_default
-}
-
-{
- g_main_context_default
- Memcheck:Leak
- fun:malloc
- fun:g_malloc
- fun:g_slice_alloc
- fun:g_ptr_array_sized_new
- fun:g_main_context_new
- fun:g_main_context_default
-}
-
-{
- g_main_context_default
+ <insert_a_suppression_name_here>
Memcheck:Leak
- fun:calloc
- fun:g_malloc0
- fun:g_main_context_new
- fun:g_main_context_default
+ fun:*alloc
+ ...
+ fun:g_random_int
}
{
- g_main_context_default
+ g_main_context_dispatch
Memcheck:Leak
fun:malloc
fun:g_malloc
fun:g_slice_alloc
- fun:g_main_context_add_poll_unlocked
- fun:g_main_context_new
- fun:g_main_context_default
+ fun:g_slice_alloc0
+ fun:get_dispatch
+ fun:g_main_context_dispatch
}
{
g_main_context_default
Memcheck:Leak
- fun:malloc
- fun:g_malloc
- fun:g_slice_alloc
- fun:g_slist_prepend
- fun:g_main_context_new
+ fun:?alloc
+ ...
fun:g_main_context_default
}
@@ -90,108 +53,34 @@
}
{
- g_get_language_names
- Memcheck:Leak
- fun:malloc
- fun:g_malloc
- fun:g_strdup
- fun:g_get_language_names
-}
-
-{
- g_get_language_names
- Memcheck:Leak
- fun:malloc
- fun:g_malloc
- fun:g_strconcat
- fun:_g_compute_locale_variants
- fun:g_get_language_names
-}
-
-{
- g_get_language_names
- Memcheck:Leak
- fun:memalign
- fun:posix_memalign
- fun:slab_allocator_alloc_chunk
- fun:g_slice_alloc
- fun:g_hash_table_new_full
- fun:g_get_language_names
-}
-
-{
- g_get_language_names
- Memcheck:Leak
- fun:calloc
- fun:g_malloc0
- fun:g_get_language_names
-}
-
-{
- g_get_language_names
- Memcheck:Leak
- fun:malloc
- fun:g_malloc
- fun:g_get_language_names
-}
-
-{
g_static_private_set
Memcheck:Leak
- fun:realloc
- fun:g_realloc
- fun:g_array_maybe_expand
- fun:g_array_set_size
+ fun:memalign
+ ...
fun:g_static_private_set
}
{
g_static_private_set
Memcheck:Leak
- fun:malloc
- fun:realloc
- fun:g_realloc
- fun:g_array_maybe_expand
- fun:g_array_set_size
+ fun:*alloc
+ ...
fun:g_static_private_set
}
{
- g_get_language_names
- Memcheck:Leak
- fun:calloc
- fun:g_malloc0
- fun:g_hash_table_insert_internal
- fun:g_get_language_names
-}
-
-{
- g_get_language_names
- Memcheck:Leak
- fun:malloc
- fun:g_malloc
- fun:g_slice_alloc
- fun:g_hash_table_insert_internal
- fun:g_get_language_names
-}
-
-{
- g_get_language_names
+ g_static_private_set
Memcheck:Leak
- fun:calloc
- fun:g_malloc0
- fun:g_hash_table_resize
- fun:g_hash_table_insert_internal
- fun:g_get_language_names
+ fun:*alloc
+ ...
+ fun:g_intern_static_string
}
{
g_get_language_names
Memcheck:Leak
- fun:malloc
- fun:g_malloc
- fun:g_slice_alloc
- fun:g_hash_table_new_full
+ fun:*alloc
+ ...
fun:g_get_language_names
}
@@ -199,49 +88,31 @@
g_get_language_names
Memcheck:Leak
fun:memalign
- fun:posix_memalign
- fun:slab_allocator_alloc_chunk
- fun:g_slice_alloc
- fun:g_slist_prepend
- fun:g_strsplit
+ ...
fun:g_get_language_names
}
{
g_set_prgname
Memcheck:Leak
- fun:malloc
- fun:g_malloc
- fun:g_strdup
+ fun:*alloc
+ ...
fun:g_set_prgname
}
{
g_set_application_name
Memcheck:Leak
- fun:malloc
- fun:g_malloc
- fun:g_strdup
+ fun:*alloc
+ ...
fun:g_set_application_name
}
{
g_thread_init_glib
Memcheck:Leak
- fun:malloc
- fun:g_malloc
- fun:g_private_new_posix_impl
- fun:_g_messages_thread_init_nomessage
- fun:g_thread_init_glib
-}
-
-{
- g_thread_init_glib
- Memcheck:Leak
- fun:malloc
- fun:g_malloc
- fun:g_private_new_posix_impl
- fun:_g_slice_thread_init_nomessage
+ fun:*alloc
+ ...
fun:g_thread_init_glib
}
@@ -254,197 +125,43 @@
}
{
- g_thread_init_glib
- Memcheck:Leak
- fun:malloc
- fun:g_malloc
- fun:g_private_new_posix_impl
- fun:g_thread_init_glib
-}
-
-{
- g_thread_init_glib
- Memcheck:Leak
- fun:malloc
- fun:g_malloc
- fun:g_mutex_new_posix_impl
- fun:_g_messages_thread_init_nomessage
- fun:g_thread_init_glib
-}
-
-{
- g_thread_init_glib
- Memcheck:Leak
- fun:calloc
- fun:g_malloc0
- fun:g_thread_self
- fun:g_thread_init_glib
-}
-
-{
- g_thread_init_glib
- Memcheck:Leak
- fun:malloc
- fun:g_malloc
- fun:g_mutex_new_posix_impl
- fun:_g_slice_thread_init_nomessage
- fun:g_thread_init_glib
-}
-
-{
- g_thread_init_glib
- Memcheck:Leak
- fun:malloc
- fun:g_malloc
- fun:g_cond_new_posix_impl
- fun:g_thread_init_glib
-}
-
-{
- g_thread_init_glib
- Memcheck:Leak
- fun:malloc
- fun:g_malloc
- fun:g_mutex_new_posix_impl
- fun:g_thread_init_glib
-}
-
-{
- g_thread_init_glib
- Memcheck:Leak
- fun:malloc
- fun:g_malloc
- fun:g_mutex_new_posix_impl
- fun:_g_mem_thread_init_noprivate_nomessage
- fun:g_thread_init_glib
-}
-
-{
- g_get_filename_charsets
- Memcheck:Leak
- fun:malloc
- fun:g_malloc
- fun:g_strdup
- fun:g_get_filename_charsets
-}
-
-{
g_get_filename_charsets
Memcheck:Leak
- fun:calloc
- fun:g_malloc0
+ fun:*alloc
+ ...
fun:g_get_filename_charsets
}
{
- g_get_filename_charsets
- Memcheck:Leak
- fun:memalign
- fun:posix_memalign
- fun:slab_allocator_alloc_chunk
- fun:g_slice_alloc
- fun:g_array_sized_new
- fun:g_static_private_set
- fun:g_get_filename_charsets
-}
-
-{
- g_get_filename_charsets
- Memcheck:Leak
- fun:calloc
- fun:g_malloc0
- fun:g_slice_alloc
- fun:g_array_sized_new
- fun:g_static_private_set
- fun:g_get_filename_charsets
-}
-
-{
- g_static_private_set
- Memcheck:Leak
- fun:malloc
- fun:g_malloc
- fun:g_slice_alloc
- fun:g_array_sized_new
- fun:g_static_private_set
-}
-
-{
- g_static_private_get
- Memcheck:Leak
- fun:calloc
- fun:g_malloc0
- fun:g_thread_self
- fun:g_static_private_get
-}
-
-{
g_get_charset
Memcheck:Leak
- fun:malloc
- fun:g_malloc
- fun:g_strdup
- fun:g_get_charset
-}
-
-{
- g_get_charset
- Memcheck:Leak
- fun:calloc
- fun:g_malloc0
- fun:g_get_charset
-}
-
-{
- g_get_charset
- Memcheck:Leak
- fun:calloc
- fun:g_malloc0
- fun:g_slice_alloc
- fun:g_array_sized_new
- fun:g_static_private_set
- fun:g_get_charset
-}
-
-{
- g_get_charset
- Memcheck:Leak
- fun:memalign
- fun:posix_memalign
- fun:slab_allocator_alloc_chunk
- fun:g_slice_alloc
- fun:g_array_sized_new
- fun:g_static_private_set
+ fun:*alloc
+ ...
fun:g_get_charset
}
{
openssl
Memcheck:Leak
- fun:malloc
- fun:CRYPTO_malloc
- fun:engine_cleanup_add_last
- fun:ENGINE_add
+ fun:*alloc
+ ...
fun:ENGINE_load_dynamic
}
{
- openssl
+ <insert_a_suppression_name_here>
Memcheck:Leak
- fun:malloc
- fun:CRYPTO_malloc
- fun:ENGINE_new
- fun:ENGINE_load_dynamic
+ fun:*alloc
+ ...
+ fun:g_data_initialize
}
{
- openssl
+ <insert_a_suppression_name_here>
Memcheck:Leak
- fun:malloc
- fun:CRYPTO_malloc
- obj:/usr/lib/libssl.so.0.9.8
- fun:SSL_COMP_get_compression_methods
- fun:SSL_library_init
+ fun:*alloc
+ ...
+ fun:g_resolver_get_default
}
{
@@ -452,358 +169,285 @@
Memcheck:Leak
fun:malloc
fun:CRYPTO_malloc
- fun:sk_new
- obj:/usr/lib/libssl.so.0.9.8
+ ...
fun:SSL_COMP_get_compression_methods
fun:SSL_library_init
}
{
- openssl
+ <insert_a_suppression_name_here>
Memcheck:Leak
- fun:malloc
+ fun:*alloc
fun:CRYPTO_malloc
- fun:sk_new
- fun:engine_cleanup_add_last
- fun:ENGINE_add
- fun:ENGINE_load_dynamic
+ ...
+ fun:ERR_get_state
}
{
<insert_a_suppression_name_here>
Memcheck:Leak
- fun:malloc
+ fun:*alloc
fun:CRYPTO_malloc
- fun:ERR_get_state
- fun:ERR_clear_error
- fun:Curl_ossl_init
- fun:curl_global_init
+ ...
+ fun:RSA_new_method
}
{
- openssl
+ <insert_a_suppression_name_here>
Memcheck:Leak
- fun:malloc
- fun:CRYPTO_malloc
- fun:lh_new
- obj:/usr/lib/libcrypto.so.0.9.8
- obj:/usr/lib/libcrypto.so.0.9.8
- fun:ERR_get_state
- fun:ERR_clear_error
- fun:Curl_ossl_init
- fun:curl_global_init
+ fun:?alloc
+ ...
+ fun:do_dlopen
}
{
- openssl
+ <insert_a_suppression_name_here>
Memcheck:Leak
- fun:malloc
- fun:CRYPTO_malloc
- fun:lh_insert
- obj:/usr/lib/libcrypto.so.0.9.8
- fun:ERR_get_state
- fun:ERR_clear_error
+ fun:?alloc
+ ...
+ fun:dlopen*
}
{
- <insert a suppression name here>
+ <insert_a_suppression_name_here>
Memcheck:Leak
fun:malloc
- fun:_dl_map_object_deps
- fun:dl_open_worker
- fun:_dl_catch_error
- fun:_dl_open
- fun:do_dlopen
- fun:_dl_catch_error
- fun:dlerror_run
- fun:__libc_dlopen_mode
- fun:pthread_cancel_init
+ ...
+ fun:dlclose
}
+# is that a leak in libdbus?
+
{
- <insert_a_suppression_name_here>
+ <insert a suppression name here>
Memcheck:Leak
- fun:calloc
- fun:_dl_new_object
- fun:_dl_map_object_from_fd
- fun:_dl_map_object
- fun:dl_open_worker
- fun:_dl_catch_error
- fun:_dl_open
+ fun:?alloc
+ ...
+ obj:*/libdbus-*.so.*
+ fun:avahi_client_new
}
{
- <insert_a_suppression_name_here>
+ <insert a suppression name here>
Memcheck:Leak
fun:malloc
- fun:_dl_new_object
- fun:_dl_map_object_from_fd
- fun:_dl_map_object
- fun:dl_open_worker
- fun:_dl_catch_error
- fun:_dl_open
+ obj:/usr/lib/libdbus-1.so.3.4.0
+ fun:dbus_message_new_error
+ obj:/usr/lib/libdbus-1.so.3.4.0
+ fun:dbus_connection_send_with_reply
+ fun:dbus_connection_send_with_reply_and_block
+ obj:/usr/lib/libavahi-client.so.3.2.4
+ fun:avahi_entry_group_new
+ fun:avahiRegisterService
+ fun:avahiClientCallback
+ obj:/usr/lib/libavahi-client.so.3.2.4
+ fun:avahi_client_new
}
{
- <insert_a_suppression_name_here>
+ inet_ntoa
Memcheck:Leak
fun:malloc
- fun:local_strdup
- fun:_dl_map_object
- fun:dl_open_worker
- fun:_dl_catch_error
- fun:_dl_open
+ fun:inet_ntoa
}
{
- <insert_a_suppression_name_here>
+ wildmidi
Memcheck:Leak
- fun:calloc
- fun:_dl_check_map_versions
- fun:dl_open_worker
- fun:_dl_catch_error
- fun:_dl_open
+ fun:malloc
+ fun:realloc
+ fun:init_gauss
+ fun:WildMidi_Init
}
{
- <insert_a_suppression_name_here>
+ g_quark_from_string
Memcheck:Leak
- fun:malloc
- fun:_dl_map_object_deps
- fun:dl_open_worker
- fun:_dl_catch_error
- fun:_dl_open
+ fun:*alloc
+ ...
+ fun:g_quark_from_*
}
{
- <insert_a_suppression_name_here>
+ g_get_any_init_do
Memcheck:Leak
fun:malloc
- fun:_dl_map_object_deps
- fun:dl_open_worker
- fun:_dl_catch_error
- fun:_dl_open
+ fun:g_malloc
+ fun:g_strdup
+ fun:g_get_any_init_do
}
{
- <insert_a_suppression_name_here>
+ g_get_any_init_do
Memcheck:Leak
fun:malloc
- fun:_dl_new_object
- fun:_dl_map_object_from_fd
- fun:_dl_map_object
- fun:openaux
- fun:_dl_catch_error
- fun:_dl_map_object_deps
- fun:dl_open_worker
- fun:_dl_catch_error
- fun:_dl_open
+ fun:g_malloc
+ fun:g_strjoinv
+ fun:g_get_any_init_do
}
{
- <insert_a_suppression_name_here>
+ nss
Memcheck:Leak
fun:malloc
- fun:local_strdup
- fun:_dl_map_object
- fun:openaux
- fun:_dl_catch_error
- fun:_dl_map_object_deps
- fun:dl_open_worker
- fun:_dl_catch_error
- fun:_dl_open
+ fun:__nss_lookup_function
}
{
- dlopen
+ nss
Memcheck:Leak
- fun:calloc
- fun:_dlerror_run
+ fun:malloc
+ fun:tsearch
+ fun:__nss_lookup_function
}
{
- dlopen
+ <insert_a_suppression_name_here>
Memcheck:Leak
- fun:malloc
- fun:_dl_scope_free
- fun:_dl_map_object_deps
- fun:dl_open_worker
- fun:_dl_catch_error
- fun:_dl_open
- fun:do_dlopen
- fun:_dl_catch_error
- fun:dlerror_run
+ fun:*alloc
+ ...
+ fun:g_type_init_with_debug_flags
}
-# is that a leak in libdbus?
-
{
- <insert a suppression name here>
+ <insert_a_suppression_name_here>
Memcheck:Leak
- fun:malloc
- obj:/usr/lib/libdbus-1.so.3.4.0
- obj:/usr/lib/libdbus-1.so.3.4.0
- fun:avahi_client_new
+ fun:*alloc
+ ...
+ fun:g_type_register_static
}
{
- <insert a suppression name here>
+ <insert_a_suppression_name_here>
Memcheck:Leak
- fun:malloc
- obj:/usr/lib/libdbus-1.so.3.4.0
- fun:dbus_message_unref
- obj:/usr/lib/libdbus-1.so.3.4.0
- fun:dbus_connection_send_with_reply_and_block
- fun:dbus_bus_register
- obj:/usr/lib/libdbus-1.so.3.4.0
- fun:avahi_client_new
+ fun:*alloc
+ ...
+ fun:g_type_add_interface_static
}
{
- <insert a suppression name here>
+ <insert_a_suppression_name_here>
Memcheck:Leak
- fun:malloc
- obj:/usr/lib/libdbus-1.so.3.4.0
- fun:dbus_message_new_method_call
- fun:dbus_bus_register
- obj:/usr/lib/libdbus-1.so.3.4.0
- fun:avahi_client_new
+ fun:*alloc
+ ...
+ fun:g_type_add_interface_check
}
{
- <insert a suppression name here>
+ <insert_a_suppression_name_here>
Memcheck:Leak
- fun:malloc
- obj:/usr/lib/libdbus-1.so.3.4.0
- obj:/usr/lib/libdbus-1.so.3.4.0
- fun:avahi_client_new
+ fun:*alloc
+ ...
+ fun:g_type_interface_add_prerequisite
}
{
- <insert a suppression name here>
+ <insert_a_suppression_name_here>
Memcheck:Leak
- fun:malloc
- obj:/usr/lib/libdbus-1.so.3.4.0
- fun:dbus_message_new_error
- obj:/usr/lib/libdbus-1.so.3.4.0
- fun:dbus_connection_send_with_reply
- fun:dbus_connection_send_with_reply_and_block
- obj:/usr/lib/libavahi-client.so.3.2.4
- fun:avahi_entry_group_new
- fun:avahiRegisterService
- fun:avahiClientCallback
- obj:/usr/lib/libavahi-client.so.3.2.4
- fun:avahi_client_new
+ fun:calloc
+ fun:g_malloc0
+ fun:g_type_class_ref
}
{
- inet_ntoa
+ <insert_a_suppression_name_here>
Memcheck:Leak
- fun:malloc
- fun:inet_ntoa
+ fun:*alloc
+ ...
+ fun:g_*_class_intern_init
}
{
- wildmidi
+ <insert_a_suppression_name_here>
Memcheck:Leak
- fun:malloc
- fun:realloc
- fun:init_gauss
- fun:WildMidi_Init
+ fun:*alloc
+ ...
+ fun:type_iface_vtable_base_init_Wm
}
{
- g_quark_from_static_string
+ <insert_a_suppression_name_here>
Memcheck:Leak
- fun:calloc
- fun:g_malloc0
- fun:g_hash_table_new_full
- fun:g_quark_from_static_string
+ fun:*alloc
+ ...
+ fun:g_object_do_class_init
}
{
- g_quark_from_static_string
+ <insert_a_suppression_name_here>
Memcheck:Leak
- fun:malloc
- fun:realloc
- fun:g_realloc
- fun:g_quark_from_static_string
+ fun:*alloc
+ ...
+ fun:g_object_base_class_init
}
{
- g_quark_from_string
+ <insert_a_suppression_name_here>
Memcheck:Leak
- fun:malloc
- fun:g_malloc
- fun:g_strdup
- fun:g_quark_from_string
+ fun:*alloc
+ ...
+ fun:g_object_class_install_property
}
{
- g_quark_from_string
+ <insert_a_suppression_name_here>
Memcheck:Leak
- fun:calloc
- fun:g_malloc0
- fun:g_hash_table_new_full
- fun:g_quark_from_string
+ fun:*alloc
+ ...
+ fun:soup_*_class_intern_init
}
{
- g_quark_from_string
+ <insert_a_suppression_name_here>
Memcheck:Leak
- fun:malloc
- fun:realloc
- fun:g_realloc
- fun:g_quark_from_string
+ fun:*alloc
+ ...
+ fun:soup_auth_manager_add_type
}
{
- g_quark_from_string
+ <insert_a_suppression_name_here>
Memcheck:Leak
- fun:malloc
- fun:g_malloc
- fun:g_slice_alloc
- fun:g_hash_table_new_full
- fun:g_quark_from_string
+ fun:*alloc
+ ...
+ fun:soup_auth_manager_class_intern_init
}
{
- g_get_any_init_do
+ <insert_a_suppression_name_here>
Memcheck:Leak
- fun:malloc
- fun:g_malloc
- fun:g_strdup
- fun:g_get_any_init_do
+ fun:*alloc
+ ...
+ fun:soup_auth_manager_ntlm_class_intern_init
}
{
- g_get_any_init_do
+ <insert_a_suppression_name_here>
Memcheck:Leak
- fun:malloc
- fun:g_malloc
- fun:g_strjoinv
- fun:g_get_any_init_do
+ fun:*alloc
+ ...
+ fun:intern_header_name
}
{
nss
Memcheck:Leak
fun:malloc
- fun:__nss_lookup_function
+ fun:nss_parse_service_list
+ fun:__nss_database_lookup
}
{
- nss
+ <insert_a_suppression_name_here>
Memcheck:Leak
- fun:malloc
- fun:tsearch
- fun:__nss_lookup_function
+ fun:?alloc
+ ...
+ fun:xmlInitParser
}
{
- nss
+ <insert_a_suppression_name_here>
Memcheck:Leak
- fun:malloc
- fun:nss_parse_service_list
- fun:__nss_database_lookup
+ fun:?alloc
+ fun:snd1_dlobj_cache_get
}