aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/AudioCompress/compress.c185
-rw-r--r--src/AudioCompress/compress.h40
-rw-r--r--src/AudioCompress/config.h19
-rw-r--r--src/ack.h2
-rw-r--r--src/aiff.c3
-rw-r--r--src/aiff.h2
-rw-r--r--src/ape.c113
-rw-r--r--src/ape.h42
-rw-r--r--src/archive/bz2_archive_plugin.c301
-rw-r--r--src/archive/bz2_archive_plugin.h (renamed from src/input/lastfm_input_plugin.h)8
-rw-r--r--src/archive/bz2_plugin.c284
-rw-r--r--src/archive/iso9660_archive_plugin.c287
-rw-r--r--src/archive/iso9660_archive_plugin.h25
-rw-r--r--src/archive/iso_plugin.c239
-rw-r--r--src/archive/zip_plugin.c196
-rw-r--r--src/archive/zzip_archive_plugin.c242
-rw-r--r--src/archive/zzip_archive_plugin.h25
-rw-r--r--src/archive_api.c7
-rw-r--r--src/archive_api.h65
-rw-r--r--src/archive_internal.h11
-rw-r--r--src/archive_list.c44
-rw-r--r--src/archive_list.h4
-rw-r--r--src/archive_plugin.c92
-rw-r--r--src/archive_plugin.h107
-rw-r--r--src/audio.c23
-rw-r--r--src/audio.h4
-rw-r--r--src/audio_check.c74
-rw-r--r--src/audio_check.h54
-rw-r--r--src/audio_format.c72
-rw-r--r--src/audio_format.h211
-rw-r--r--src/audio_parser.c166
-rw-r--r--src/audio_parser.h8
-rw-r--r--src/buffer.c6
-rw-r--r--src/buffer.h2
-rw-r--r--src/buffer2array.c133
-rw-r--r--src/check.h47
-rw-r--r--src/chunk.c5
-rw-r--r--src/chunk.h29
-rw-r--r--src/client.c885
-rw-r--r--src/client.h2
-rw-r--r--src/client_event.c108
-rw-r--r--src/client_expire.c90
-rw-r--r--src/client_global.c73
-rw-r--r--src/client_idle.c89
-rw-r--r--src/client_internal.h145
-rw-r--r--src/client_list.c69
-rw-r--r--src/client_new.c159
-rw-r--r--src/client_process.c146
-rw-r--r--src/client_read.c113
-rw-r--r--src/client_write.c284
-rw-r--r--src/cmdline.c141
-rw-r--r--src/cmdline.h15
-rw-r--r--src/command.c451
-rw-r--r--src/command.h8
-rw-r--r--src/compress.c410
-rw-r--r--src/compress.h48
-rw-r--r--src/conf.c535
-rw-r--r--src/conf.h93
-rw-r--r--src/crossfade.c150
-rw-r--r--src/crossfade.h25
-rw-r--r--src/cue/cue_tag.c265
-rw-r--r--src/cue/cue_tag.h21
-rw-r--r--src/daemon.c133
-rw-r--r--src/daemon.h42
-rw-r--r--src/database.c103
-rw-r--r--src/database.h2
-rw-r--r--src/dbUtils.c38
-rw-r--r--src/dbUtils.h6
-rw-r--r--src/decoder/_flac_common.c431
-rw-r--r--src/decoder/_flac_common.h183
-rw-r--r--src/decoder/_ogg_common.c4
-rw-r--r--src/decoder/_ogg_common.h4
-rw-r--r--src/decoder/audiofile_decoder_plugin.c (renamed from src/decoder/audiofile_plugin.c)103
-rw-r--r--src/decoder/faad_decoder_plugin.c (renamed from src/decoder/faad_plugin.c)96
-rw-r--r--src/decoder/ffmpeg_decoder_plugin.c (renamed from src/decoder/ffmpeg_plugin.c)469
-rw-r--r--src/decoder/flac_compat.h114
-rw-r--r--src/decoder/flac_decoder_plugin.c497
-rw-r--r--src/decoder/flac_metadata.c286
-rw-r--r--src/decoder/flac_metadata.h55
-rw-r--r--src/decoder/flac_pcm.c109
-rw-r--r--src/decoder/flac_pcm.h (renamed from src/normalize.c)38
-rw-r--r--src/decoder/flac_plugin.c918
-rw-r--r--src/decoder/fluidsynth_decoder_plugin.c (renamed from src/decoder/fluidsynth_plugin.c)13
-rw-r--r--src/decoder/gme_decoder_plugin.c247
-rw-r--r--src/decoder/mad_decoder_plugin.c (renamed from src/decoder/mad_plugin.c)292
-rw-r--r--src/decoder/mikmod_decoder_plugin.c (renamed from src/decoder/mikmod_plugin.c)170
-rw-r--r--src/decoder/modplug_decoder_plugin.c (renamed from src/decoder/modplug_plugin.c)67
-rw-r--r--src/decoder/mp4ff_decoder_plugin.c (renamed from src/decoder/mp4ff_plugin.c)124
-rw-r--r--src/decoder/mpcdec_decoder_plugin.c (renamed from src/decoder/mpcdec_plugin.c)93
-rw-r--r--src/decoder/mpg123_decoder_plugin.c209
-rw-r--r--src/decoder/oggflac_decoder_plugin.c (renamed from src/decoder/oggflac_plugin.c)84
-rw-r--r--src/decoder/sidplay_decoder_plugin.cxx429
-rw-r--r--src/decoder/sidplay_plugin.cxx163
-rw-r--r--src/decoder/sndfile_decoder_plugin.c251
-rw-r--r--src/decoder/vorbis_decoder_plugin.c (renamed from src/decoder/vorbis_plugin.c)260
-rw-r--r--src/decoder/wavpack_decoder_plugin.c (renamed from src/decoder/wavpack_plugin.c)188
-rw-r--r--src/decoder/wildmidi_decoder_plugin.c (renamed from src/decoder/wildmidi_plugin.c)22
-rw-r--r--src/decoder_api.c277
-rw-r--r--src/decoder_api.h136
-rw-r--r--src/decoder_buffer.c3
-rw-r--r--src/decoder_buffer.h2
-rw-r--r--src/decoder_command.h2
-rw-r--r--src/decoder_control.c169
-rw-r--r--src/decoder_control.h184
-rw-r--r--src/decoder_internal.c65
-rw-r--r--src/decoder_internal.h18
-rw-r--r--src/decoder_list.c94
-rw-r--r--src/decoder_list.h19
-rw-r--r--src/decoder_plugin.c47
-rw-r--r--src/decoder_plugin.h39
-rw-r--r--src/decoder_print.c54
-rw-r--r--src/decoder_print.h28
-rw-r--r--src/decoder_thread.c490
-rw-r--r--src/decoder_thread.h7
-rw-r--r--src/directory.c7
-rw-r--r--src/directory.h7
-rw-r--r--src/directory_print.c36
-rw-r--r--src/directory_print.h2
-rw-r--r--src/directory_save.c179
-rw-r--r--src/directory_save.h7
-rw-r--r--src/dirvec.c3
-rw-r--r--src/dirvec.h2
-rw-r--r--src/encoder/flac_encoder.c358
-rw-r--r--src/encoder/lame_encoder.c12
-rw-r--r--src/encoder/null_encoder.c124
-rw-r--r--src/encoder/twolame_encoder.c307
-rw-r--r--src/encoder/vorbis_encoder.c15
-rw-r--r--src/encoder/wave_encoder.c270
-rw-r--r--src/encoder_api.h2
-rw-r--r--src/encoder_list.c28
-rw-r--r--src/encoder_list.h7
-rw-r--r--src/encoder_plugin.h19
-rw-r--r--src/event_pipe.c48
-rw-r--r--src/event_pipe.h10
-rw-r--r--src/exclude.c95
-rw-r--r--src/exclude.h51
-rw-r--r--src/fd_util.c306
-rw-r--r--src/fd_util.h130
-rw-r--r--src/fifo_buffer.c3
-rw-r--r--src/fifo_buffer.h2
-rw-r--r--src/filter/autoconvert_filter_plugin.c169
-rw-r--r--src/filter/autoconvert_filter_plugin.h (renamed from src/buffer2array.h)21
-rw-r--r--src/filter/chain_filter_plugin.c213
-rw-r--r--src/filter/chain_filter_plugin.h48
-rw-r--r--src/filter/convert_filter_plugin.c147
-rw-r--r--src/filter/convert_filter_plugin.h36
-rw-r--r--src/filter/normalize_filter_plugin.c113
-rw-r--r--src/filter/null_filter_plugin.c93
-rw-r--r--src/filter/replay_gain_filter_plugin.c247
-rw-r--r--src/filter/replay_gain_filter_plugin.h50
-rw-r--r--src/filter/route_filter_plugin.c348
-rw-r--r--src/filter/volume_filter_plugin.c161
-rw-r--r--src/filter/volume_filter_plugin.h31
-rw-r--r--src/filter_config.c119
-rw-r--r--src/filter_config.h47
-rw-r--r--src/filter_internal.h38
-rw-r--r--src/filter_plugin.c116
-rw-r--r--src/filter_plugin.h150
-rw-r--r--src/filter_registry.c44
-rw-r--r--src/filter_registry.h40
-rw-r--r--src/gcc.h2
-rw-r--r--src/glib_compat.h77
-rw-r--r--src/icy_metadata.c5
-rw-r--r--src/icy_metadata.h2
-rw-r--r--src/icy_server.c3
-rw-r--r--src/icy_server.h2
-rw-r--r--src/idle.c4
-rw-r--r--src/idle.h5
-rw-r--r--src/inotify_queue.c135
-rw-r--r--src/inotify_queue.h32
-rw-r--r--src/inotify_source.c167
-rw-r--r--src/inotify_source.h61
-rw-r--r--src/inotify_update.c383
-rw-r--r--src/inotify_update.h47
-rw-r--r--src/input/archive_input_plugin.c34
-rw-r--r--src/input/archive_input_plugin.h2
-rw-r--r--src/input/curl_input_plugin.c200
-rw-r--r--src/input/curl_input_plugin.h2
-rw-r--r--src/input/ffmpeg_input_plugin.c168
-rw-r--r--src/input/ffmpeg_input_plugin.h28
-rw-r--r--src/input/file_input_plugin.c89
-rw-r--r--src/input/file_input_plugin.h2
-rw-r--r--src/input/lastfm_input_plugin.c229
-rw-r--r--src/input/mms_input_plugin.c57
-rw-r--r--src/input/mms_input_plugin.h2
-rw-r--r--src/input/rewind_input_plugin.c106
-rw-r--r--src/input/rewind_input_plugin.h18
-rw-r--r--src/input_init.c100
-rw-r--r--src/input_init.h42
-rw-r--r--src/input_plugin.h16
-rw-r--r--src/input_registry.c59
-rw-r--r--src/input_registry.h35
-rw-r--r--src/input_stream.c141
-rw-r--r--src/input_stream.h71
-rw-r--r--src/listen.c402
-rw-r--r--src/listen.h9
-rw-r--r--src/locate.c3
-rw-r--r--src/locate.h2
-rw-r--r--src/log.c31
-rw-r--r--src/log.h2
-rw-r--r--src/ls.c17
-rw-r--r--src/ls.h2
-rw-r--r--src/main.c225
-rw-r--r--src/main.h45
-rw-r--r--src/main_win32.c155
-rw-r--r--src/mapper.c65
-rw-r--r--src/mapper.h12
-rw-r--r--src/mixer/alsa_mixer_plugin.c (renamed from src/mixer/alsa_mixer.c)66
-rw-r--r--src/mixer/oss_mixer_plugin.c (renamed from src/mixer/oss_mixer.c)57
-rw-r--r--src/mixer/pulse_mixer.c382
-rw-r--r--src/mixer/pulse_mixer_plugin.c234
-rw-r--r--src/mixer/pulse_mixer_plugin.h39
-rw-r--r--src/mixer/software_mixer_plugin.c109
-rw-r--r--src/mixer/software_mixer_plugin.h33
-rw-r--r--src/mixer/winmm_mixer_plugin.c114
-rw-r--r--src/mixer_all.c100
-rw-r--r--src/mixer_all.h23
-rw-r--r--src/mixer_api.c3
-rw-r--r--src/mixer_api.h2
-rw-r--r--src/mixer_control.c56
-rw-r--r--src/mixer_control.h17
-rw-r--r--src/mixer_list.h10
-rw-r--r--src/mixer_plugin.h35
-rw-r--r--src/mixer_type.c39
-rw-r--r--src/mixer_type.h47
-rw-r--r--src/mpd_error.h36
-rw-r--r--src/notify.c3
-rw-r--r--src/notify.h2
-rw-r--r--src/open.h41
-rw-r--r--src/output/alsa_plugin.c277
-rw-r--r--src/output/ao_plugin.c28
-rw-r--r--src/output/ffado_output_plugin.c347
-rw-r--r--src/output/fifo_output_plugin.c (renamed from src/output/fifo_plugin.c)16
-rw-r--r--src/output/httpd_client.c20
-rw-r--r--src/output/httpd_client.h2
-rw-r--r--src/output/httpd_internal.h50
-rw-r--r--src/output/httpd_output_plugin.c239
-rw-r--r--src/output/jack_output_plugin.c722
-rw-r--r--src/output/jack_plugin.c451
-rw-r--r--src/output/mvp_plugin.c25
-rw-r--r--src/output/null_plugin.c7
-rw-r--r--src/output/openal_plugin.c277
-rw-r--r--src/output/oss_plugin.c703
-rw-r--r--src/output/osx_plugin.c25
-rw-r--r--src/output/pipe_output_plugin.c3
-rw-r--r--src/output/pulse_output_plugin.c825
-rw-r--r--src/output/pulse_output_plugin.h72
-rw-r--r--src/output/pulse_plugin.c179
-rw-r--r--src/output/recorder_output_plugin.c218
-rw-r--r--src/output/shout_plugin.c42
-rw-r--r--src/output/solaris_output_plugin.c10
-rw-r--r--src/output/winmm_output_plugin.c337
-rw-r--r--src/output/winmm_output_plugin.h29
-rw-r--r--src/output_all.c160
-rw-r--r--src/output_all.h36
-rw-r--r--src/output_api.h2
-rw-r--r--src/output_command.c18
-rw-r--r--src/output_command.h2
-rw-r--r--src/output_control.c187
-rw-r--r--src/output_control.h28
-rw-r--r--src/output_init.c211
-rw-r--r--src/output_internal.h95
-rw-r--r--src/output_list.c28
-rw-r--r--src/output_list.h2
-rw-r--r--src/output_plugin.h66
-rw-r--r--src/output_print.c3
-rw-r--r--src/output_print.h2
-rw-r--r--src/output_state.c58
-rw-r--r--src/output_state.h17
-rw-r--r--src/output_thread.c580
-rw-r--r--src/output_thread.h2
-rw-r--r--src/page.c3
-rw-r--r--src/page.h2
-rw-r--r--src/path.c6
-rw-r--r--src/path.h2
-rw-r--r--src/pcm_buffer.h6
-rw-r--r--src/pcm_byteswap.c70
-rw-r--r--src/pcm_byteswap.h50
-rw-r--r--src/pcm_channels.c35
-rw-r--r--src/pcm_channels.h14
-rw-r--r--src/pcm_convert.c198
-rw-r--r--src/pcm_convert.h21
-rw-r--r--src/pcm_dither.c3
-rw-r--r--src/pcm_dither.h2
-rw-r--r--src/pcm_format.c94
-rw-r--r--src/pcm_format.h10
-rw-r--r--src/pcm_mix.c160
-rw-r--r--src/pcm_mix.h5
-rw-r--r--src/pcm_pack.c93
-rw-r--r--src/pcm_pack.h59
-rw-r--r--src/pcm_prng.h2
-rw-r--r--src/pcm_resample.c28
-rw-r--r--src/pcm_resample.h20
-rw-r--r--src/pcm_resample_fallback.c7
-rw-r--r--src/pcm_resample_internal.h12
-rw-r--r--src/pcm_resample_libsamplerate.c75
-rw-r--r--src/pcm_utils.h16
-rw-r--r--src/pcm_volume.c39
-rw-r--r--src/pcm_volume.h2
-rw-r--r--src/permission.c10
-rw-r--r--src/permission.h4
-rw-r--r--src/pipe.c3
-rw-r--r--src/pipe.h2
-rw-r--r--src/player_control.c247
-rw-r--r--src/player_control.h178
-rw-r--r--src/player_thread.c569
-rw-r--r--src/player_thread.h2
-rw-r--r--src/playlist.c125
-rw-r--r--src/playlist.h112
-rw-r--r--src/playlist/asx_playlist_plugin.c322
-rw-r--r--src/playlist/asx_playlist_plugin.h25
-rw-r--r--src/playlist/cue_playlist_plugin.c140
-rw-r--r--src/playlist/cue_playlist_plugin.h25
-rw-r--r--src/playlist/extm3u_playlist_plugin.c161
-rw-r--r--src/playlist/extm3u_playlist_plugin.h25
-rw-r--r--src/playlist/flac_playlist_plugin.c170
-rw-r--r--src/playlist/flac_playlist_plugin.h25
-rw-r--r--src/playlist/lastfm_playlist_plugin.c312
-rw-r--r--src/playlist/lastfm_playlist_plugin.h25
-rw-r--r--src/playlist/m3u_playlist_plugin.c92
-rw-r--r--src/playlist/m3u_playlist_plugin.h25
-rw-r--r--src/playlist/pls_playlist_plugin.c219
-rw-r--r--src/playlist/pls_playlist_plugin.h25
-rw-r--r--src/playlist/rss_playlist_plugin.c321
-rw-r--r--src/playlist/rss_playlist_plugin.h25
-rw-r--r--src/playlist/xspf_playlist_plugin.c342
-rw-r--r--src/playlist/xspf_playlist_plugin.h25
-rw-r--r--src/playlist_any.c68
-rw-r--r--src/playlist_any.h40
-rw-r--r--src/playlist_control.c87
-rw-r--r--src/playlist_database.c78
-rw-r--r--src/playlist_database.h40
-rw-r--r--src/playlist_edit.c169
-rw-r--r--src/playlist_global.c21
-rw-r--r--src/playlist_internal.h4
-rw-r--r--src/playlist_list.c361
-rw-r--r--src/playlist_list.h74
-rw-r--r--src/playlist_mapper.c103
-rw-r--r--src/playlist_mapper.h36
-rw-r--r--src/playlist_plugin.h137
-rw-r--r--src/playlist_print.c48
-rw-r--r--src/playlist_print.h13
-rw-r--r--src/playlist_queue.c71
-rw-r--r--src/playlist_queue.h51
-rw-r--r--src/playlist_save.c9
-rw-r--r--src/playlist_save.h2
-rw-r--r--src/playlist_song.c149
-rw-r--r--src/playlist_song.h31
-rw-r--r--src/playlist_state.c207
-rw-r--r--src/playlist_state.h18
-rw-r--r--src/playlist_vector.c125
-rw-r--r--src/playlist_vector.h71
-rw-r--r--src/poison.h9
-rw-r--r--src/queue.c38
-rw-r--r--src/queue.h12
-rw-r--r--src/queue_print.c11
-rw-r--r--src/queue_print.h2
-rw-r--r--src/queue_save.c93
-rw-r--r--src/queue_save.h11
-rw-r--r--src/refcount.h66
-rw-r--r--src/replay_gain.c134
-rw-r--r--src/replay_gain_ape.c78
-rw-r--r--src/replay_gain_ape.h (renamed from src/normalize.h)20
-rw-r--r--src/replay_gain_config.c150
-rw-r--r--src/replay_gain_config.h (renamed from src/replay_gain.h)60
-rw-r--r--src/replay_gain_info.c48
-rw-r--r--src/replay_gain_info.h74
-rw-r--r--src/riff.c3
-rw-r--r--src/riff.h2
-rw-r--r--src/server_socket.c444
-rw-r--r--src/server_socket.h84
-rw-r--r--src/sig_handlers.c8
-rw-r--r--src/sig_handlers.h2
-rw-r--r--src/socket_util.c16
-rw-r--r--src/socket_util.h2
-rw-r--r--src/song.c187
-rw-r--r--src/song.h26
-rw-r--r--src/song_print.c68
-rw-r--r--src/song_print.h9
-rw-r--r--src/song_save.c165
-rw-r--r--src/song_save.h26
-rw-r--r--src/song_sticker.c3
-rw-r--r--src/song_sticker.h2
-rw-r--r--src/song_update.c198
-rw-r--r--src/songvec.c64
-rw-r--r--src/songvec.h4
-rw-r--r--src/state_file.c83
-rw-r--r--src/state_file.h2
-rw-r--r--src/stats.c9
-rw-r--r--src/stats.h2
-rw-r--r--src/sticker.c50
-rw-r--r--src/sticker.h10
-rw-r--r--src/sticker_print.c3
-rw-r--r--src/sticker_print.h2
-rw-r--r--src/stored_playlist.c23
-rw-r--r--src/stored_playlist.h2
-rw-r--r--src/strset.c3
-rw-r--r--src/strset.h2
-rw-r--r--src/tag.c38
-rw-r--r--src/tag.h28
-rw-r--r--src/tag_ape.c135
-rw-r--r--src/tag_ape.h2
-rw-r--r--src/tag_id3.c333
-rw-r--r--src/tag_id3.h4
-rw-r--r--src/tag_internal.h2
-rw-r--r--src/tag_pool.c3
-rw-r--r--src/tag_pool.h2
-rw-r--r--src/tag_print.c3
-rw-r--r--src/tag_print.h2
-rw-r--r--src/tag_rva2.c136
-rw-r--r--src/tag_rva2.h39
-rw-r--r--src/tag_save.c3
-rw-r--r--src/tag_save.h2
-rw-r--r--src/text_file.c68
-rw-r--r--src/text_file.h39
-rw-r--r--src/text_input_stream.c96
-rw-r--r--src/text_input_stream.h52
-rw-r--r--src/timer.c16
-rw-r--r--src/timer.h8
-rw-r--r--src/tokenizer.c222
-rw-r--r--src/tokenizer.h83
-rw-r--r--src/update.c838
-rw-r--r--src/update.h16
-rw-r--r--src/update_internal.h64
-rw-r--r--src/update_queue.c66
-rw-r--r--src/update_remove.c94
-rw-r--r--src/update_walk.c916
-rw-r--r--src/uri.c50
-rw-r--r--src/uri.h19
-rw-r--r--src/utils.c50
-rw-r--r--src/utils.h17
-rw-r--r--src/volume.c224
-rw-r--r--src/volume.h22
-rw-r--r--src/zeroconf-avahi.c6
-rw-r--r--src/zeroconf-bonjour.c3
-rw-r--r--src/zeroconf-internal.h2
-rw-r--r--src/zeroconf.c4
-rw-r--r--src/zeroconf.h4
438 files changed, 31310 insertions, 12099 deletions
diff --git a/src/AudioCompress/compress.c b/src/AudioCompress/compress.c
new file mode 100644
index 000000000..36cdfd8dd
--- /dev/null
+++ b/src/AudioCompress/compress.c
@@ -0,0 +1,185 @@
+/* compress.c
+ * Compressor logic
+ *
+ * (c)2007 busybee (http://beesbuzz.biz/
+ * Licensed under the terms of the LGPL. See the file COPYING for details.
+ */
+
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "config.h"
+#include "compress.h"
+
+struct Compressor {
+ //! The compressor's preferences
+ struct CompressorConfig prefs;
+
+ //! History of the peak values
+ int *peaks;
+
+ //! History of the gain values
+ int *gain;
+
+ //! History of clip amounts
+ int *clipped;
+
+ unsigned int pos;
+ unsigned int bufsz;
+};
+
+struct Compressor *Compressor_new(unsigned int history)
+{
+ struct Compressor *obj = malloc(sizeof(struct Compressor));
+
+ obj->prefs.target = TARGET;
+ obj->prefs.maxgain = GAINMAX;
+ obj->prefs.smooth = GAINSMOOTH;
+
+ obj->peaks = obj->gain = obj->clipped = NULL;
+ obj->bufsz = 0;
+ obj->pos = 0;
+
+ Compressor_setHistory(obj, history);
+
+ return obj;
+}
+
+void Compressor_delete(struct Compressor *obj)
+{
+ if (obj->peaks)
+ free(obj->peaks);
+ if (obj->gain)
+ free(obj->gain);
+ if (obj->clipped)
+ free(obj->clipped);
+ free(obj);
+}
+
+static int *resizeArray(int *data, int newsz, int oldsz)
+{
+ data = realloc(data, newsz*sizeof(int));
+ if (newsz > oldsz)
+ memset(data + oldsz, 0, sizeof(int)*(newsz - oldsz));
+ return data;
+}
+
+void Compressor_setHistory(struct Compressor *obj, unsigned int history)
+{
+ if (!history)
+ history = BUCKETS;
+
+ obj->peaks = resizeArray(obj->peaks, history, obj->bufsz);
+ obj->gain = resizeArray(obj->gain, history, obj->bufsz);
+ obj->clipped = resizeArray(obj->clipped, history, obj->bufsz);
+ obj->bufsz = history;
+}
+
+struct CompressorConfig *Compressor_getConfig(struct Compressor *obj)
+{
+ return &obj->prefs;
+}
+
+void Compressor_Process_int16(struct Compressor *obj, int16_t *audio,
+ unsigned int count)
+{
+ struct CompressorConfig *prefs = Compressor_getConfig(obj);
+ int16_t *ap;
+ unsigned int i;
+ int *peaks = obj->peaks;
+ int curGain = obj->gain[obj->pos];
+ int newGain;
+ int peakVal = 1;
+ int peakPos = 0;
+ int slot = (obj->pos + 1) % obj->bufsz;
+ int *clipped = obj->clipped + slot;
+ unsigned int ramp = count;
+ int delta;
+
+ ap = audio;
+ for (i = 0; i < count; i++)
+ {
+ int val = *ap++;
+ if (val < 0)
+ val = -val;
+ if (val > peakVal)
+ {
+ peakVal = val;
+ peakPos = i;
+ }
+ }
+ peaks[slot] = peakVal;
+
+
+ for (i = 0; i < obj->bufsz; i++)
+ {
+ if (peaks[i] > peakVal)
+ {
+ peakVal = peaks[i];
+ peakPos = 0;
+ }
+ }
+
+ //! Determine target gain
+ newGain = (1 << 10)*prefs->target/peakVal;
+
+ //! Adjust the gain with inertia from the previous gain value
+ newGain = (curGain*((1 << prefs->smooth) - 1) + newGain)
+ >> prefs->smooth;
+
+ //! Make sure it's no more than the maximum gain value
+ if (newGain > (prefs->maxgain << 10))
+ newGain = prefs->maxgain << 10;
+
+ //! Make sure it's no less than 1:1
+ if (newGain < (1 << 10))
+ newGain = 1 << 10;
+
+ //! Make sure the adjusted gain won't cause clipping
+ if ((peakVal*newGain >> 10) > 32767)
+ {
+ newGain = (32767 << 10)/peakVal;
+ //! Truncate the ramp time
+ ramp = peakPos;
+ }
+
+ //! Record the new gain
+ obj->gain[slot] = newGain;
+
+ if (!ramp)
+ ramp = 1;
+ if (!curGain)
+ curGain = 1 << 10;
+ delta = (newGain - curGain) / (int)ramp;
+
+ ap = audio;
+ *clipped = 0;
+ for (i = 0; i < count; i++)
+ {
+ int sample;
+
+ //! Amplify the sample
+ sample = *ap*curGain >> 10;
+ if (sample < -32768)
+ {
+ *clipped += -32768 - sample;
+ sample = -32768;
+ } else if (sample > 32767)
+ {
+ *clipped += sample - 32767;
+ sample = 32767;
+ }
+ *ap++ = sample;
+
+ //! Adjust the gain
+ if (i < ramp)
+ curGain += delta;
+ else
+ curGain = newGain;
+ }
+
+ obj->pos = slot;
+}
+
diff --git a/src/AudioCompress/compress.h b/src/AudioCompress/compress.h
new file mode 100644
index 000000000..073d4af9a
--- /dev/null
+++ b/src/AudioCompress/compress.h
@@ -0,0 +1,40 @@
+/*! compress.h
+ * interface to audio compression
+ *
+ * (c)2007 busybee (http://beesbuzz.biz/)
+ * Licensed under the terms of the LGPL. See the file COPYING for details.
+ */
+
+#ifndef COMPRESS_H
+#define COMPRESS_H
+
+#include <stdint.h>
+
+//! Configuration values for the compressor object
+struct CompressorConfig {
+ int target;
+ int maxgain;
+ int smooth;
+};
+
+struct Compressor;
+
+//! Create a new compressor (use history value of 0 for default)
+struct Compressor *Compressor_new(unsigned int history);
+
+//! Delete a compressor
+void Compressor_delete(struct Compressor *);
+
+//! Set the history length
+void Compressor_setHistory(struct Compressor *, unsigned int history);
+
+//! Get the configuration for a compressor
+struct CompressorConfig *Compressor_getConfig(struct Compressor *);
+
+//! Process 16-bit signed data
+void Compressor_Process_int16(struct Compressor *, int16_t *data, unsigned int count);
+
+//! TODO: Compressor_Process_int32, Compressor_Process_float, others as needed
+
+//! TODO: functions for getting at the peak/gain/clip history buffers (for monitoring)
+#endif
diff --git a/src/AudioCompress/config.h b/src/AudioCompress/config.h
new file mode 100644
index 000000000..25615ee68
--- /dev/null
+++ b/src/AudioCompress/config.h
@@ -0,0 +1,19 @@
+/* config.h
+** Default values for the configuration, and also a few random debug things
+*/
+
+#ifndef AC_CONFIG_H
+#define AC_CONFIG_H
+
+/*** Version information ***/
+#define ACVERSION "2.0"
+
+/*** Default configuration stuff ***/
+#define TARGET 16384 /*!< Target level (on a scale of 0-32767) */
+
+#define GAINMAX 32 /*!< The maximum amount to amplify by */
+#define GAINSMOOTH 8 /*!< How much inertia ramping has*/
+#define BUCKETS 400 /*!< How long of a history to use by default */
+
+#endif
+
diff --git a/src/ack.h b/src/ack.h
index e19e65bcf..af3d1be9a 100644
--- a/src/ack.h
+++ b/src/ack.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
diff --git a/src/aiff.c b/src/aiff.c
index d4bec628b..e2ca0dfe4 100644
--- a/src/aiff.c
+++ b/src/aiff.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -17,6 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h" /* must be first for large file support */
#include "aiff.h"
#include <glib.h>
diff --git a/src/aiff.h b/src/aiff.h
index 3388a52fe..52c0a73ec 100644
--- a/src/aiff.h
+++ b/src/aiff.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
diff --git a/src/ape.c b/src/ape.c
new file mode 100644
index 000000000..5fca98e28
--- /dev/null
+++ b/src/ape.c
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "ape.h"
+
+#include <glib.h>
+
+#include <stdint.h>
+#include <assert.h>
+#include <stdio.h>
+#include <string.h>
+
+struct ape_footer {
+ unsigned char id[8];
+ uint32_t version;
+ uint32_t length;
+ uint32_t count;
+ unsigned char flags[4];
+ unsigned char reserved[8];
+};
+
+static bool
+ape_scan_internal(FILE *fp, tag_ape_callback_t callback, void *ctx)
+{
+ /* determine if file has an apeV2 tag */
+ struct ape_footer footer;
+ if (fseek(fp, -(long)sizeof(footer), SEEK_END) ||
+ fread(&footer, 1, sizeof(footer), fp) != sizeof(footer) ||
+ memcmp(footer.id, "APETAGEX", sizeof(footer.id)) != 0 ||
+ GUINT32_FROM_LE(footer.version) != 2000)
+ return false;
+
+ /* find beginning of ape tag */
+ size_t remaining = GUINT32_FROM_LE(footer.length);
+ if (remaining <= sizeof(footer) + 10 ||
+ /* refuse to load more than one megabyte of tag data */
+ remaining > 1024 * 1024 ||
+ fseek(fp, -(long)remaining, SEEK_END))
+ return false;
+
+ /* read tag into buffer */
+ remaining -= sizeof(footer);
+ assert(remaining > 10);
+
+ char *buffer = g_malloc(remaining);
+ if (fread(buffer, 1, remaining, fp) != remaining)
+ return false;
+
+ /* read tags */
+ unsigned n = GUINT32_FROM_LE(footer.count);
+ const char *p = buffer;
+ while (n-- && remaining > 10) {
+ size_t size = GUINT32_FROM_LE(*(const uint32_t *)p);
+ p += 4;
+ remaining -= 4;
+ unsigned long flags = GUINT32_FROM_LE(*(const uint32_t *)p);
+ p += 4;
+ remaining -= 4;
+
+ /* get the key */
+ const char *key = p;
+ while (remaining > size && *p != '\0') {
+ p++;
+ remaining--;
+ }
+ p++;
+ remaining--;
+
+ /* get the value */
+ if (remaining < size)
+ break;
+
+ if (!callback(flags, key, p, size, ctx))
+ break;
+
+ p += size;
+ remaining -= size;
+ }
+
+ g_free(buffer);
+ return true;
+}
+
+bool
+tag_ape_scan(const char *path_fs, tag_ape_callback_t callback, void *ctx)
+{
+ FILE *fp;
+
+ fp = fopen(path_fs, "rb");
+ if (fp == NULL)
+ return false;
+
+ bool success = ape_scan_internal(fp, callback, ctx);
+ fclose(fp);
+ return success;
+}
diff --git a/src/ape.h b/src/ape.h
new file mode 100644
index 000000000..754b9bb2d
--- /dev/null
+++ b/src/ape.h
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_APE_H
+#define MPD_APE_H
+
+#include "check.h"
+
+#include <stdbool.h>
+#include <stddef.h>
+
+typedef bool (*tag_ape_callback_t)(unsigned long flags, const char *key,
+ const char *value, size_t value_length,
+ void *ctx);
+
+/**
+ * Scans the APE tag values from a file.
+ *
+ * @param path_fs the path of the file in filesystem encoding
+ * @return false if the file could not be opened or if no APE tag is
+ * present
+ */
+bool
+tag_ape_scan(const char *path_fs, tag_ape_callback_t callback, void *ctx);
+
+#endif
diff --git a/src/archive/bz2_archive_plugin.c b/src/archive/bz2_archive_plugin.c
new file mode 100644
index 000000000..2414eb519
--- /dev/null
+++ b/src/archive/bz2_archive_plugin.c
@@ -0,0 +1,301 @@
+/*
+ * 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.
+ */
+
+/**
+ * single bz2 archive handling (requires libbz2)
+ */
+
+#include "config.h"
+#include "archive/bz2_archive_plugin.h"
+#include "archive_api.h"
+#include "input_plugin.h"
+#include "refcount.h"
+
+#include <stdint.h>
+#include <stddef.h>
+#include <string.h>
+#include <glib.h>
+#include <bzlib.h>
+
+#ifdef HAVE_OLDER_BZIP2
+#define BZ2_bzDecompressInit bzDecompressInit
+#define BZ2_bzDecompress bzDecompress
+#endif
+
+struct bz2_archive_file {
+ struct archive_file base;
+
+ struct refcount ref;
+
+ char *name;
+ bool reset;
+ struct input_stream *istream;
+};
+
+struct bz2_input_stream {
+ struct input_stream base;
+
+ struct bz2_archive_file *archive;
+
+ bool eof;
+
+ bz_stream bzstream;
+
+ char buffer[5000];
+};
+
+static const struct input_plugin bz2_inputplugin;
+
+static inline GQuark
+bz2_quark(void)
+{
+ return g_quark_from_static_string("bz2");
+}
+
+/* single archive handling allocation helpers */
+
+static bool
+bz2_alloc(struct bz2_input_stream *data, GError **error_r)
+{
+ int ret;
+
+ data->bzstream.bzalloc = NULL;
+ data->bzstream.bzfree = NULL;
+ data->bzstream.opaque = NULL;
+
+ data->bzstream.next_in = (void *) data->buffer;
+ data->bzstream.avail_in = 0;
+
+ ret = BZ2_bzDecompressInit(&data->bzstream, 0, 0);
+ if (ret != BZ_OK) {
+ g_free(data);
+
+ g_set_error(error_r, bz2_quark(), ret,
+ "BZ2_bzDecompressInit() has failed");
+ return false;
+ }
+
+ return true;
+}
+
+static void
+bz2_destroy(struct bz2_input_stream *data)
+{
+ BZ2_bzDecompressEnd(&data->bzstream);
+}
+
+/* archive open && listing routine */
+
+static struct archive_file *
+bz2_open(const char *pathname, GError **error_r)
+{
+ struct bz2_archive_file *context;
+ int len;
+
+ context = g_malloc(sizeof(*context));
+ archive_file_init(&context->base, &bz2_archive_plugin);
+ refcount_init(&context->ref);
+
+ //open archive
+ context->istream = input_stream_open(pathname, error_r);
+ if (context->istream == NULL) {
+ g_free(context);
+ return NULL;
+ }
+
+ context->name = g_path_get_basename(pathname);
+
+ //remove suffix
+ len = strlen(context->name);
+ if (len > 4) {
+ context->name[len - 4] = 0; //remove .bz2 suffix
+ }
+
+ return &context->base;
+}
+
+static void
+bz2_scan_reset(struct archive_file *file)
+{
+ struct bz2_archive_file *context = (struct bz2_archive_file *) file;
+ context->reset = true;
+}
+
+static char *
+bz2_scan_next(struct archive_file *file)
+{
+ struct bz2_archive_file *context = (struct bz2_archive_file *) file;
+ char *name = NULL;
+
+ if (context->reset) {
+ name = context->name;
+ context->reset = false;
+ }
+
+ return name;
+}
+
+static void
+bz2_close(struct archive_file *file)
+{
+ struct bz2_archive_file *context = (struct bz2_archive_file *) file;
+
+ if (!refcount_dec(&context->ref))
+ return;
+
+ g_free(context->name);
+
+ input_stream_close(context->istream);
+ g_free(context);
+}
+
+/* single archive handling */
+
+static struct input_stream *
+bz2_open_stream(struct archive_file *file, const char *path, 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);
+
+ bis->archive = context;
+
+ bis->base.ready = true;
+ bis->base.seekable = false;
+
+ if (!bz2_alloc(bis, error_r)) {
+ input_stream_deinit(&bis->base);
+ g_free(bis);
+ return NULL;
+ }
+
+ bis->eof = false;
+
+ refcount_inc(&context->ref);
+
+ return &bis->base;
+}
+
+static void
+bz2_is_close(struct input_stream *is)
+{
+ struct bz2_input_stream *bis = (struct bz2_input_stream *)is;
+
+ bz2_destroy(bis);
+
+ bz2_close(&bis->archive->base);
+
+ input_stream_deinit(&bis->base);
+ g_free(bis);
+}
+
+static bool
+bz2_fillbuffer(struct bz2_input_stream *bis, GError **error_r)
+{
+ size_t count;
+ bz_stream *bzstream;
+
+ bzstream = &bis->bzstream;
+
+ if (bzstream->avail_in > 0)
+ return true;
+
+ count = input_stream_read(bis->archive->istream,
+ bis->buffer, sizeof(bis->buffer),
+ error_r);
+ if (count == 0)
+ return false;
+
+ bzstream->next_in = bis->buffer;
+ bzstream->avail_in = count;
+ return true;
+}
+
+static size_t
+bz2_is_read(struct input_stream *is, void *ptr, size_t length,
+ GError **error_r)
+{
+ struct bz2_input_stream *bis = (struct bz2_input_stream *)is;
+ bz_stream *bzstream;
+ int bz_result;
+ size_t nbytes = 0;
+
+ if (bis->eof)
+ return 0;
+
+ bzstream = &bis->bzstream;
+ bzstream->next_out = ptr;
+ bzstream->avail_out = length;
+
+ do {
+ if (!bz2_fillbuffer(bis, error_r))
+ return 0;
+
+ bz_result = BZ2_bzDecompress(bzstream);
+
+ if (bz_result == BZ_STREAM_END) {
+ bis->eof = true;
+ break;
+ }
+
+ if (bz_result != BZ_OK) {
+ g_set_error(error_r, bz2_quark(), bz_result,
+ "BZ2_bzDecompress() has failed");
+ return 0;
+ }
+ } while (bzstream->avail_out == length);
+
+ nbytes = length - bzstream->avail_out;
+ is->offset += nbytes;
+
+ return nbytes;
+}
+
+static bool
+bz2_is_eof(struct input_stream *is)
+{
+ struct bz2_input_stream *bis = (struct bz2_input_stream *)is;
+
+ return bis->eof;
+}
+
+/* exported structures */
+
+static const char *const bz2_extensions[] = {
+ "bz2",
+ NULL
+};
+
+static const struct input_plugin bz2_inputplugin = {
+ .close = bz2_is_close,
+ .read = bz2_is_read,
+ .eof = bz2_is_eof,
+};
+
+const struct archive_plugin bz2_archive_plugin = {
+ .name = "bz2",
+ .open = bz2_open,
+ .scan_reset = bz2_scan_reset,
+ .scan_next = bz2_scan_next,
+ .open_stream = bz2_open_stream,
+ .close = bz2_close,
+ .suffixes = bz2_extensions
+};
+
diff --git a/src/input/lastfm_input_plugin.h b/src/archive/bz2_archive_plugin.h
index d0eaf5a55..199049008 100644
--- a/src/input/lastfm_input_plugin.h
+++ b/src/archive/bz2_archive_plugin.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -17,9 +17,9 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
-#ifndef LASTFM_INPUT_PLUGIN_H
-#define LASTFM_INPUT_PLUGIN_H
+#ifndef MPD_ARCHIVE_BZ2_H
+#define MPD_ARCHIVE_BZ2_H
-extern const struct input_plugin lastfm_input_plugin;
+extern const struct archive_plugin bz2_archive_plugin;
#endif
diff --git a/src/archive/bz2_plugin.c b/src/archive/bz2_plugin.c
deleted file mode 100644
index 0ef042e90..000000000
--- a/src/archive/bz2_plugin.c
+++ /dev/null
@@ -1,284 +0,0 @@
-/*
- * 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.
- */
-
-/**
- * single bz2 archive handling (requires libbz2)
- */
-
-#include "archive_api.h"
-#include "input_plugin.h"
-#include "config.h"
-
-#include <stdint.h>
-#include <stddef.h>
-#include <string.h>
-#include <glib.h>
-#include <bzlib.h>
-
-#ifdef HAVE_OLDER_BZIP2
-#define BZ2_bzDecompressInit bzDecompressInit
-#define BZ2_bzDecompress bzDecompress
-#endif
-
-#define BZ_BUFSIZE 5000
-
-typedef struct {
- char *name;
- bool reset;
- struct input_stream istream;
- int last_bz_result;
- int last_parent_result;
- bz_stream bzstream;
- char *buffer;
-} bz2_context;
-
-
-static const struct input_plugin bz2_inputplugin;
-
-/* single archive handling allocation helpers */
-
-static bool
-bz2_alloc(bz2_context *data)
-{
- data->bzstream.bzalloc = NULL;
- data->bzstream.bzfree = NULL;
- data->bzstream.opaque = NULL;
-
- data->buffer = g_malloc(BZ_BUFSIZE);
- data->bzstream.next_in = (void *) data->buffer;
- data->bzstream.avail_in = 0;
-
- if (BZ2_bzDecompressInit(&data->bzstream, 0, 0) != BZ_OK) {
- g_free(data->buffer);
- g_free(data);
- return false;
- }
-
- data->last_bz_result = BZ_OK;
- data->last_parent_result = 0;
- return true;
-}
-
-static void
-bz2_destroy(bz2_context *data)
-{
- BZ2_bzDecompressEnd(&data->bzstream);
- g_free(data->buffer);
-}
-
-/* archive open && listing routine */
-
-static struct archive_file *
-bz2_open(char * pathname)
-{
- bz2_context *context;
- char *name;
- int len;
-
- context = g_malloc(sizeof(bz2_context));
- if (!context) {
- return NULL;
- }
- //open archive
- if (!input_stream_open(&context->istream, pathname)) {
- g_warning("failed to open an bzip2 archive %s\n",pathname);
- g_free(context);
- return NULL;
- }
- //capture filename
- name = strrchr(pathname, '/');
- if (name == NULL) {
- g_warning("failed to get bzip2 name from %s\n",pathname);
- g_free(context);
- return NULL;
- }
- context->name = g_strdup(name+1);
- //remove suffix
- len = strlen(context->name);
- if (len > 4) {
- context->name[len-4] = 0; //remove .bz2 suffix
- }
- return (struct archive_file *) context;
-}
-
-static void
-bz2_scan_reset(struct archive_file *file)
-{
- bz2_context *context = (bz2_context *) file;
- context->reset = true;
-}
-
-static char *
-bz2_scan_next(struct archive_file *file)
-{
- bz2_context *context = (bz2_context *) file;
- char *name = NULL;
- if (context->reset) {
- name = context->name;
- context->reset = false;
- }
- return name;
-}
-
-static void
-bz2_close(struct archive_file *file)
-{
- bz2_context *context = (bz2_context *) file;
-
- g_free(context->name);
-
- input_stream_close(&context->istream);
- g_free(context);
-}
-
-/* single archive handling */
-
-static bool
-bz2_open_stream(struct archive_file *file, struct input_stream *is,
- G_GNUC_UNUSED const char *path)
-{
- bz2_context *context = (bz2_context *) file;
- //setup file ops
- is->plugin = &bz2_inputplugin;
- //insert back reference
- is->data = context;
- is->seekable = false;
-
- if (!bz2_alloc(context)) {
- g_warning("alloc bz2 failed\n");
- return false;
- }
- return true;
-}
-
-static void
-bz2_is_close(struct input_stream *is)
-{
- bz2_context *context = (bz2_context *) is->data;
- bz2_destroy(context);
- is->data = NULL;
-
- bz2_close((struct archive_file *)context);
-}
-
-static int
-bz2_fillbuffer(bz2_context *context,
- size_t numBytes)
-{
- size_t count;
- bz_stream *bzstream;
-
- bzstream = &context->bzstream;
-
- if (bzstream->avail_in > 0)
- return 0;
-
- count = input_stream_read(&context->istream,
- context->buffer, BZ_BUFSIZE);
-
- if (count == 0) {
- if (bzstream->avail_out == numBytes)
- return -1;
- if (!input_stream_eof(&context->istream))
- context->last_parent_result = 1;
- } else {
- bzstream->next_in = context->buffer;
- bzstream->avail_in = count;
- }
-
- return 0;
-}
-
-static size_t
-bz2_is_read(struct input_stream *is, void *ptr, size_t size)
-{
- bz2_context *context = (bz2_context *) is->data;
- bz_stream *bzstream;
- int bz_result;
- size_t numBytes = size;
- size_t bytesRead = 0;
-
- if (context->last_bz_result != BZ_OK)
- return 0;
- if (context->last_parent_result != 0)
- return 0;
-
- bzstream = &context->bzstream;
- bzstream->next_out = ptr;
- bzstream->avail_out = numBytes;
-
- while (bzstream->avail_out != 0) {
- if (bz2_fillbuffer(context, numBytes) != 0)
- break;
-
- bz_result = BZ2_bzDecompress(bzstream);
-
- if (context->last_bz_result != BZ_OK
- && bzstream->avail_out == numBytes) {
- context->last_bz_result = bz_result;
- break;
- }
-
- if (bz_result == BZ_STREAM_END) {
- context->last_bz_result = bz_result;
- break;
- }
- }
-
- bytesRead = numBytes - bzstream->avail_out;
- is->offset += bytesRead;
-
- return bytesRead;
-}
-
-static bool
-bz2_is_eof(struct input_stream *is)
-{
- bz2_context *context = (bz2_context *) is->data;
-
- if (context->last_bz_result == BZ_STREAM_END) {
- return true;
- }
-
- return false;
-}
-
-/* exported structures */
-
-static const char *const bz2_extensions[] = {
- "bz2",
- NULL
-};
-
-static const struct input_plugin bz2_inputplugin = {
- .close = bz2_is_close,
- .read = bz2_is_read,
- .eof = bz2_is_eof,
-};
-
-const struct archive_plugin bz2_plugin = {
- .name = "bz2",
- .open = bz2_open,
- .scan_reset = bz2_scan_reset,
- .scan_next = bz2_scan_next,
- .open_stream = bz2_open_stream,
- .close = bz2_close,
- .suffixes = bz2_extensions
-};
-
diff --git a/src/archive/iso9660_archive_plugin.c b/src/archive/iso9660_archive_plugin.c
new file mode 100644
index 000000000..142fa10e0
--- /dev/null
+++ b/src/archive/iso9660_archive_plugin.c
@@ -0,0 +1,287 @@
+/*
+ * 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.
+ */
+
+/**
+ * iso archive handling (requires cdio, and iso9660)
+ */
+
+#include "config.h"
+#include "archive/iso9660_archive_plugin.h"
+#include "archive_api.h"
+#include "input_plugin.h"
+#include "refcount.h"
+
+#include <cdio/cdio.h>
+#include <cdio/iso9660.h>
+
+#include <glib.h>
+#include <string.h>
+
+#define CEILING(x, y) ((x+(y-1))/y)
+
+struct iso9660_archive_file {
+ struct archive_file base;
+
+ struct refcount ref;
+
+ iso9660_t *iso;
+ GSList *list;
+ GSList *iter;
+};
+
+static const struct input_plugin iso9660_input_plugin;
+
+static inline GQuark
+iso9660_quark(void)
+{
+ return g_quark_from_static_string("iso9660");
+}
+
+/* archive open && listing routine */
+
+static void
+listdir_recur(const char *psz_path, struct iso9660_archive_file *context)
+{
+ iso9660_t *iso = context->iso;
+ CdioList_t *entlist;
+ CdioListNode_t *entnode;
+ iso9660_stat_t *statbuf;
+ char pathname[4096];
+
+ entlist = iso9660_ifs_readdir (iso, psz_path);
+ if (!entlist) {
+ return;
+ }
+ /* Iterate over the list of nodes that iso9660_ifs_readdir gives */
+ _CDIO_LIST_FOREACH (entnode, entlist) {
+ statbuf = (iso9660_stat_t *) _cdio_list_node_data (entnode);
+
+ strcpy(pathname, psz_path);
+ strcat(pathname, statbuf->filename);
+
+ if (_STAT_DIR == statbuf->type ) {
+ if (strcmp(statbuf->filename, ".") && strcmp(statbuf->filename, "..")) {
+ strcat(pathname, "/");
+ listdir_recur(pathname, context);
+ }
+ } else {
+ //remove leading /
+ context->list = g_slist_prepend( context->list,
+ g_strdup(pathname + 1));
+ }
+ }
+ _cdio_list_free (entlist, true);
+}
+
+static struct archive_file *
+iso9660_archive_open(const char *pathname, GError **error_r)
+{
+ struct iso9660_archive_file *context =
+ g_new(struct iso9660_archive_file, 1);
+
+ archive_file_init(&context->base, &iso9660_archive_plugin);
+ refcount_init(&context->ref);
+
+ context->list = NULL;
+
+ /* open archive */
+ context->iso = iso9660_open (pathname);
+ if (context->iso == NULL) {
+ g_set_error(error_r, iso9660_quark(), 0,
+ "Failed to open ISO9660 file %s", pathname);
+ return NULL;
+ }
+
+ listdir_recur("/", context);
+
+ return &context->base;
+}
+
+static void
+iso9660_archive_scan_reset(struct archive_file *file)
+{
+ struct iso9660_archive_file *context =
+ (struct iso9660_archive_file *)file;
+
+ //reset iterator
+ context->iter = context->list;
+}
+
+static char *
+iso9660_archive_scan_next(struct archive_file *file)
+{
+ struct iso9660_archive_file *context =
+ (struct iso9660_archive_file *)file;
+
+ char *data = NULL;
+ if (context->iter != NULL) {
+ ///fetch data and goto next
+ data = context->iter->data;
+ context->iter = g_slist_next(context->iter);
+ }
+ return data;
+}
+
+static void
+iso9660_archive_close(struct archive_file *file)
+{
+ struct iso9660_archive_file *context =
+ (struct iso9660_archive_file *)file;
+ GSList *tmp;
+
+ if (!refcount_dec(&context->ref))
+ return;
+
+ if (context->list) {
+ //free list
+ for (tmp = context->list; tmp != NULL; tmp = g_slist_next(tmp))
+ g_free(tmp->data);
+ g_slist_free(context->list);
+ }
+ //close archive
+ iso9660_close(context->iso);
+
+ g_free(context);
+}
+
+/* single archive handling */
+
+struct iso9660_input_stream {
+ struct input_stream base;
+
+ struct iso9660_archive_file *archive;
+
+ iso9660_stat_t *statbuf;
+ size_t max_blocks;
+};
+
+static struct input_stream *
+iso9660_archive_open_stream(struct archive_file *file,
+ const char *pathname, 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);
+
+ iis->archive = context;
+ iis->statbuf = iso9660_ifs_stat_translate(context->iso, pathname);
+ if (iis->statbuf == NULL) {
+ g_free(iis);
+ g_set_error(error_r, iso9660_quark(), 0,
+ "not found in the ISO file: %s", pathname);
+ return NULL;
+ }
+
+ iis->base.ready = true;
+ //we are not seekable
+ iis->base.seekable = false;
+
+ iis->base.size = iis->statbuf->size;
+
+ iis->max_blocks = CEILING(iis->statbuf->size, ISO_BLOCKSIZE);
+
+ refcount_inc(&context->ref);
+
+ return &iis->base;
+}
+
+static void
+iso9660_input_close(struct input_stream *is)
+{
+ struct iso9660_input_stream *iis = (struct iso9660_input_stream *)is;
+
+ g_free(iis->statbuf);
+
+ iso9660_archive_close(&iis->archive->base);
+
+ input_stream_deinit(&iis->base);
+ g_free(iis);
+}
+
+
+static size_t
+iso9660_input_read(struct input_stream *is, void *ptr, size_t size, GError **error_r)
+{
+ struct iso9660_input_stream *iis = (struct iso9660_input_stream *)is;
+ int toread, readed = 0;
+ int no_blocks, cur_block;
+ size_t left_bytes = iis->statbuf->size - is->offset;
+
+ size = (size * ISO_BLOCKSIZE) / ISO_BLOCKSIZE;
+
+ if (left_bytes < size) {
+ toread = left_bytes;
+ no_blocks = CEILING(left_bytes,ISO_BLOCKSIZE);
+ } else {
+ toread = size;
+ no_blocks = toread / ISO_BLOCKSIZE;
+ }
+ if (no_blocks > 0) {
+
+ cur_block = is->offset / ISO_BLOCKSIZE;
+
+ readed = iso9660_iso_seek_read (iis->archive->iso, ptr,
+ iis->statbuf->lsn + cur_block, no_blocks);
+
+ if (readed != no_blocks * ISO_BLOCKSIZE) {
+ g_set_error(error_r, iso9660_quark(), 0,
+ "error reading ISO file at lsn %lu",
+ (long unsigned int) cur_block);
+ return 0;
+ }
+ if (left_bytes < size) {
+ readed = left_bytes;
+ }
+
+ is->offset += readed;
+ }
+ return readed;
+}
+
+static bool
+iso9660_input_eof(struct input_stream *is)
+{
+ return is->offset == is->size;
+}
+
+/* exported structures */
+
+static const char *const iso9660_archive_extensions[] = {
+ "iso",
+ NULL
+};
+
+static const struct input_plugin iso9660_input_plugin = {
+ .close = iso9660_input_close,
+ .read = iso9660_input_read,
+ .eof = iso9660_input_eof,
+};
+
+const struct archive_plugin iso9660_archive_plugin = {
+ .name = "iso",
+ .open = iso9660_archive_open,
+ .scan_reset = iso9660_archive_scan_reset,
+ .scan_next = iso9660_archive_scan_next,
+ .open_stream = iso9660_archive_open_stream,
+ .close = iso9660_archive_close,
+ .suffixes = iso9660_archive_extensions
+};
diff --git a/src/archive/iso9660_archive_plugin.h b/src/archive/iso9660_archive_plugin.h
new file mode 100644
index 000000000..2a3864cee
--- /dev/null
+++ b/src/archive/iso9660_archive_plugin.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_ARCHIVE_ISO9660_H
+#define MPD_ARCHIVE_ISO9660_H
+
+extern const struct archive_plugin iso9660_archive_plugin;
+
+#endif
diff --git a/src/archive/iso_plugin.c b/src/archive/iso_plugin.c
deleted file mode 100644
index 9063af0fc..000000000
--- a/src/archive/iso_plugin.c
+++ /dev/null
@@ -1,239 +0,0 @@
-/*
- * 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.
- */
-
-/**
- * iso archive handling (requires cdio, and iso9660)
- */
-
-#include "archive_api.h"
-#include "input_plugin.h"
-
-#include <cdio/cdio.h>
-#include <cdio/iso9660.h>
-
-#include <glib.h>
-#include <string.h>
-
-#define CEILING(x, y) ((x+(y-1))/y)
-
-typedef struct {
- iso9660_t *iso;
- iso9660_stat_t *statbuf;
- size_t cur_ofs;
- size_t max_blocks;
- GSList *list;
- GSList *iter;
-} iso_context;
-
-static const struct input_plugin iso_inputplugin;
-
-/* archive open && listing routine */
-
-static void
-listdir_recur(const char *psz_path, iso_context *context)
-{
- iso9660_t *iso = context->iso;
- CdioList_t *entlist;
- CdioListNode_t *entnode;
- iso9660_stat_t *statbuf;
- char pathname[4096];
-
- entlist = iso9660_ifs_readdir (iso, psz_path);
- if (!entlist) {
- return;
- }
- /* Iterate over the list of nodes that iso9660_ifs_readdir gives */
- _CDIO_LIST_FOREACH (entnode, entlist) {
- statbuf = (iso9660_stat_t *) _cdio_list_node_data (entnode);
-
- strcpy(pathname, psz_path);
- strcat(pathname, statbuf->filename);
-
- if (_STAT_DIR == statbuf->type ) {
- if (strcmp(statbuf->filename, ".") && strcmp(statbuf->filename, "..")) {
- strcat(pathname, "/");
- listdir_recur(pathname, context);
- }
- } else {
- //remove leading /
- context->list = g_slist_prepend( context->list,
- g_strdup(pathname + 1));
- }
- }
- _cdio_list_free (entlist, true);
-}
-
-static struct archive_file *
-iso_open(char * pathname)
-{
- iso_context *context = g_malloc(sizeof(iso_context));
-
- context->list = NULL;
-
- /* open archive */
- context->iso = iso9660_open (pathname);
- if (context->iso == NULL) {
- g_warning("iso %s open failed\n", pathname);
- return NULL;
- }
-
- listdir_recur("/", context);
-
- return (struct archive_file *)context;
-}
-
-static void
-iso_scan_reset(struct archive_file *file)
-{
- iso_context *context = (iso_context *) file;
- //reset iterator
- context->iter = context->list;
-}
-
-static char *
-iso_scan_next(struct archive_file *file)
-{
- iso_context *context = (iso_context *) file;
- char *data = NULL;
- if (context->iter != NULL) {
- ///fetch data and goto next
- data = context->iter->data;
- context->iter = g_slist_next(context->iter);
- }
- return data;
-}
-
-static void
-iso_close(struct archive_file *file)
-{
- iso_context *context = (iso_context *) file;
- GSList *tmp;
- if (context->list) {
- //free list
- for (tmp = context->list; tmp != NULL; tmp = g_slist_next(tmp))
- g_free(tmp->data);
- g_slist_free(context->list);
- }
- //close archive
- iso9660_close(context->iso);
-
- g_free(context);
-}
-
-/* single archive handling */
-
-static bool
-iso_open_stream(struct archive_file *file, struct input_stream *is,
- const char *pathname)
-{
- iso_context *context = (iso_context *) file;
- //setup file ops
- is->plugin = &iso_inputplugin;
- //insert back reference
- is->data = context;
- //we are not seekable
- is->seekable = false;
-
- context->statbuf = iso9660_ifs_stat_translate (context->iso, pathname);
-
- if (context->statbuf == NULL) {
- g_warning("file %s not found in iso\n", pathname);
- return false;
- }
- context->cur_ofs = 0;
- context->max_blocks = CEILING(context->statbuf->size, ISO_BLOCKSIZE);
- return true;
-}
-
-static void
-iso_is_close(struct input_stream *is)
-{
- iso_context *context = (iso_context *) is->data;
- g_free(context->statbuf);
-
- iso_close((struct archive_file *)context);
-}
-
-
-static size_t
-iso_is_read(struct input_stream *is, void *ptr, size_t size)
-{
- iso_context *context = (iso_context *) is->data;
- int toread, readed = 0;
- int no_blocks, cur_block;
- size_t left_bytes = context->statbuf->size - context->cur_ofs;
-
- size = (size * ISO_BLOCKSIZE) / ISO_BLOCKSIZE;
-
- if (left_bytes < size) {
- toread = left_bytes;
- no_blocks = CEILING(left_bytes,ISO_BLOCKSIZE);
- } else {
- toread = size;
- no_blocks = toread / ISO_BLOCKSIZE;
- }
- if (no_blocks > 0) {
-
- cur_block = context->cur_ofs / ISO_BLOCKSIZE;
-
- readed = iso9660_iso_seek_read (context->iso, ptr,
- context->statbuf->lsn + cur_block, no_blocks);
-
- if (readed != no_blocks * ISO_BLOCKSIZE) {
- g_warning("error reading ISO file at lsn %lu\n",
- (long unsigned int) cur_block );
- return -1;
- }
- if (left_bytes < size) {
- readed = left_bytes;
- }
- context->cur_ofs += readed;
- }
- return readed;
-}
-
-static bool
-iso_is_eof(struct input_stream *is)
-{
- iso_context *context = (iso_context *) is->data;
- return (context->cur_ofs == context->statbuf->size);
-}
-
-/* exported structures */
-
-static const char *const iso_extensions[] = {
- "iso",
- NULL
-};
-
-static const struct input_plugin iso_inputplugin = {
- .close = iso_is_close,
- .read = iso_is_read,
- .eof = iso_is_eof,
-};
-
-const struct archive_plugin iso_plugin = {
- .name = "iso",
- .open = iso_open,
- .scan_reset = iso_scan_reset,
- .scan_next = iso_scan_next,
- .open_stream = iso_open_stream,
- .close = iso_close,
- .suffixes = iso_extensions
-};
diff --git a/src/archive/zip_plugin.c b/src/archive/zip_plugin.c
deleted file mode 100644
index 243d46418..000000000
--- a/src/archive/zip_plugin.c
+++ /dev/null
@@ -1,196 +0,0 @@
-/*
- * 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.
- */
-
-/**
- * zip archive handling (requires zziplib)
- */
-
-#include "archive_api.h"
-#include "archive_api.h"
-#include "input_plugin.h"
-
-#include <zzip/zzip.h>
-#include <glib.h>
-#include <string.h>
-
-typedef struct {
- ZZIP_DIR *dir;
- ZZIP_FILE *file;
- size_t length;
- GSList *list;
- GSList *iter;
-} zip_context;
-
-static const struct input_plugin zip_inputplugin;
-
-/* archive open && listing routine */
-
-static struct archive_file *
-zip_open(char * pathname)
-{
- zip_context *context = g_malloc(sizeof(zip_context));
- ZZIP_DIRENT dirent;
-
- // open archive
- context->list = NULL;
- context->dir = zzip_dir_open(pathname, NULL);
- if (context->dir == NULL) {
- g_warning("zipfile %s open failed\n", pathname);
- return NULL;
- }
-
- while (zzip_dir_read(context->dir, &dirent)) {
- //add only files
- if (dirent.st_size > 0) {
- context->list = g_slist_prepend(context->list,
- g_strdup(dirent.d_name));
- }
- }
-
- return (struct archive_file *)context;
-}
-
-static void
-zip_scan_reset(struct archive_file *file)
-{
- zip_context *context = (zip_context *) file;
- //reset iterator
- context->iter = context->list;
-}
-
-static char *
-zip_scan_next(struct archive_file *file)
-{
- zip_context *context = (zip_context *) file;
- char *data = NULL;
- if (context->iter != NULL) {
- ///fetch data and goto next
- data = context->iter->data;
- context->iter = g_slist_next(context->iter);
- }
- return data;
-}
-
-static void
-zip_close(struct archive_file *file)
-{
- zip_context *context = (zip_context *) file;
- if (context->list) {
- //free list
- for (GSList *tmp = context->list; tmp != NULL; tmp = g_slist_next(tmp))
- g_free(tmp->data);
- g_slist_free(context->list);
- }
- //close archive
- zzip_dir_close (context->dir);
-
- g_free(context);
-}
-
-/* single archive handling */
-
-static bool
-zip_open_stream(struct archive_file *file, struct input_stream *is,
- const char *pathname)
-{
- zip_context *context = (zip_context *) file;
- ZZIP_STAT z_stat;
-
- //setup file ops
- is->plugin = &zip_inputplugin;
- //insert back reference
- is->data = context;
- //we are seekable (but its not recommendent to do so)
- is->seekable = true;
-
- context->file = zzip_file_open(context->dir, pathname, 0);
- if (!context->file) {
- g_warning("file %s not found in the zipfile\n", pathname);
- return false;
- }
- zzip_file_stat(context->file, &z_stat);
- context->length = z_stat.st_size;
- return true;
-}
-
-static void
-zip_is_close(struct input_stream *is)
-{
- zip_context *context = (zip_context *) is->data;
- zzip_file_close (context->file);
-
- zip_close((struct archive_file *)context);
-}
-
-static size_t
-zip_is_read(struct input_stream *is, void *ptr, size_t size)
-{
- zip_context *context = (zip_context *) is->data;
- int ret;
- ret = zzip_file_read(context->file, ptr, size);
- if (ret < 0) {
- g_warning("error %d reading zipfile\n", ret);
- return 0;
- }
- return ret;
-}
-
-static bool
-zip_is_eof(struct input_stream *is)
-{
- zip_context *context = (zip_context *) is->data;
- return ((size_t) zzip_tell(context->file) == context->length);
-}
-
-static bool
-zip_is_seek(G_GNUC_UNUSED struct input_stream *is,
- G_GNUC_UNUSED off_t offset, G_GNUC_UNUSED int whence)
-{
- zip_context *context = (zip_context *) is->data;
- zzip_off_t ofs = zzip_seek(context->file, offset, whence);
- if (ofs != -1) {
- is->offset = ofs;
- return true;
- }
- return false;
-}
-
-/* exported structures */
-
-static const char *const zip_extensions[] = {
- "zip",
- NULL
-};
-
-static const struct input_plugin zip_inputplugin = {
- .close = zip_is_close,
- .read = zip_is_read,
- .eof = zip_is_eof,
- .seek = zip_is_seek,
-};
-
-const struct archive_plugin zip_plugin = {
- .name = "zip",
- .open = zip_open,
- .scan_reset = zip_scan_reset,
- .scan_next = zip_scan_next,
- .open_stream = zip_open_stream,
- .close = zip_close,
- .suffixes = zip_extensions
-};
diff --git a/src/archive/zzip_archive_plugin.c b/src/archive/zzip_archive_plugin.c
new file mode 100644
index 000000000..3c2b80318
--- /dev/null
+++ b/src/archive/zzip_archive_plugin.c
@@ -0,0 +1,242 @@
+/*
+ * 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.
+ */
+
+/**
+ * zip archive handling (requires zziplib)
+ */
+
+#include "config.h"
+#include "archive/zzip_archive_plugin.h"
+#include "archive_api.h"
+#include "archive_api.h"
+#include "input_plugin.h"
+#include "refcount.h"
+
+#include <zzip/zzip.h>
+#include <glib.h>
+#include <string.h>
+
+struct zzip_archive {
+ struct archive_file base;
+
+ struct refcount ref;
+
+ ZZIP_DIR *dir;
+ GSList *list;
+ GSList *iter;
+};
+
+static const struct input_plugin zzip_input_plugin;
+
+static inline GQuark
+zzip_quark(void)
+{
+ return g_quark_from_static_string("zzip");
+}
+
+/* archive open && listing routine */
+
+static struct archive_file *
+zzip_archive_open(const char *pathname, GError **error_r)
+{
+ struct zzip_archive *context = g_malloc(sizeof(*context));
+ ZZIP_DIRENT dirent;
+
+ archive_file_init(&context->base, &zzip_archive_plugin);
+ refcount_init(&context->ref);
+
+ // open archive
+ context->list = NULL;
+ context->dir = zzip_dir_open(pathname, NULL);
+ if (context->dir == NULL) {
+ g_set_error(error_r, zzip_quark(), 0,
+ "Failed to open ZIP file %s", pathname);
+ return NULL;
+ }
+
+ while (zzip_dir_read(context->dir, &dirent)) {
+ //add only files
+ if (dirent.st_size > 0) {
+ context->list = g_slist_prepend(context->list,
+ g_strdup(dirent.d_name));
+ }
+ }
+
+ return &context->base;
+}
+
+static void
+zzip_archive_scan_reset(struct archive_file *file)
+{
+ struct zzip_archive *context = (struct zzip_archive *) file;
+ //reset iterator
+ context->iter = context->list;
+}
+
+static char *
+zzip_archive_scan_next(struct archive_file *file)
+{
+ struct zzip_archive *context = (struct zzip_archive *) file;
+ char *data = NULL;
+ if (context->iter != NULL) {
+ ///fetch data and goto next
+ data = context->iter->data;
+ context->iter = g_slist_next(context->iter);
+ }
+ return data;
+}
+
+static void
+zzip_archive_close(struct archive_file *file)
+{
+ struct zzip_archive *context = (struct zzip_archive *) file;
+
+ if (!refcount_dec(&context->ref))
+ return;
+
+ if (context->list) {
+ //free list
+ for (GSList *tmp = context->list; tmp != NULL; tmp = g_slist_next(tmp))
+ g_free(tmp->data);
+ g_slist_free(context->list);
+ }
+ //close archive
+ zzip_dir_close (context->dir);
+
+ g_free(context);
+}
+
+/* single archive handling */
+
+struct zzip_input_stream {
+ struct input_stream base;
+
+ struct zzip_archive *archive;
+
+ ZZIP_FILE *file;
+};
+
+static struct input_stream *
+zzip_archive_open_stream(struct archive_file *file,
+ const char *pathname, 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);
+
+ zis->archive = context;
+ zis->file = zzip_file_open(context->dir, pathname, 0);
+ if (zis->file == NULL) {
+ g_free(zis);
+ g_set_error(error_r, zzip_quark(), 0,
+ "not found in the ZIP file: %s", pathname);
+ return NULL;
+ }
+
+ zis->base.ready = true;
+ //we are seekable (but its not recommendent to do so)
+ zis->base.seekable = true;
+
+ zzip_file_stat(zis->file, &z_stat);
+ zis->base.size = z_stat.st_size;
+
+ refcount_inc(&context->ref);
+
+ return &zis->base;
+}
+
+static void
+zzip_input_close(struct input_stream *is)
+{
+ struct zzip_input_stream *zis = (struct zzip_input_stream *)is;
+
+ zzip_file_close(zis->file);
+ zzip_archive_close(&zis->archive->base);
+ input_stream_deinit(&zis->base);
+ g_free(zis);
+}
+
+static size_t
+zzip_input_read(struct input_stream *is, void *ptr, size_t size,
+ GError **error_r)
+{
+ struct zzip_input_stream *zis = (struct zzip_input_stream *)is;
+ int ret;
+
+ ret = zzip_file_read(zis->file, ptr, size);
+ if (ret < 0) {
+ g_set_error(error_r, zzip_quark(), ret,
+ "zzip_file_read() has failed");
+ return 0;
+ }
+
+ is->offset = zzip_tell(zis->file);
+
+ return ret;
+}
+
+static bool
+zzip_input_eof(struct input_stream *is)
+{
+ struct zzip_input_stream *zis = (struct zzip_input_stream *)is;
+
+ return (goffset)zzip_tell(zis->file) == is->size;
+}
+
+static bool
+zzip_input_seek(struct input_stream *is,
+ goffset offset, int whence, GError **error_r)
+{
+ struct zzip_input_stream *zis = (struct zzip_input_stream *)is;
+ zzip_off_t ofs = zzip_seek(zis->file, offset, whence);
+ if (ofs != -1) {
+ g_set_error(error_r, zzip_quark(), ofs,
+ "zzip_seek() has failed");
+ is->offset = ofs;
+ return true;
+ }
+ return false;
+}
+
+/* exported structures */
+
+static const char *const zzip_archive_extensions[] = {
+ "zip",
+ NULL
+};
+
+static const struct input_plugin zzip_input_plugin = {
+ .close = zzip_input_close,
+ .read = zzip_input_read,
+ .eof = zzip_input_eof,
+ .seek = zzip_input_seek,
+};
+
+const struct archive_plugin zzip_archive_plugin = {
+ .name = "zzip",
+ .open = zzip_archive_open,
+ .scan_reset = zzip_archive_scan_reset,
+ .scan_next = zzip_archive_scan_next,
+ .open_stream = zzip_archive_open_stream,
+ .close = zzip_archive_close,
+ .suffixes = zzip_archive_extensions
+};
diff --git a/src/archive/zzip_archive_plugin.h b/src/archive/zzip_archive_plugin.h
new file mode 100644
index 000000000..6d5037eef
--- /dev/null
+++ b/src/archive/zzip_archive_plugin.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_ARCHIVE_ZZIP_H
+#define MPD_ARCHIVE_ZZIP_H
+
+extern const struct archive_plugin zzip_archive_plugin;
+
+#endif
diff --git a/src/archive_api.c b/src/archive_api.c
index 153afa361..b15810f1b 100644
--- a/src/archive_api.c
+++ b/src/archive_api.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -17,6 +17,9 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h" /* must be first for large file support */
+#include "archive_api.h"
+
#include <stdio.h>
#include <string.h>
@@ -26,8 +29,6 @@
#include <errno.h>
#include <glib.h>
-#include "archive_api.h"
-
/**
*
* archive_lookup is used to determine if part of pathname refers to an regular
diff --git a/src/archive_api.h b/src/archive_api.h
index 2efcc1e6a..f08960c72 100644
--- a/src/archive_api.h
+++ b/src/archive_api.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -27,72 +27,11 @@
*/
#include "archive_internal.h"
+#include "archive_plugin.h"
#include "input_stream.h"
#include <stdbool.h>
-struct archive_file;
-
-struct archive_plugin {
- const char *name;
-
- /**
- * optional, set this to NULL if the archive plugin doesn't
- * have/need one this must false if there is an error and
- * true otherwise
- */
- bool (*init)(void);
-
- /**
- * optional, set this to NULL if the archive plugin doesn't
- * have/need one
- */
- void (*finish)(void);
-
- /**
- * tryes to open archive file and associates handle with archive
- * returns pointer to handle used is all operations with this archive
- * or NULL when opening fails
- */
- struct archive_file *(*open)(char * pathname);
-
- /**
- * reset routine will move current read index in archive to default
- * position and then the filenames from archives can be read
- * via scan_next routine
- */
- void (*scan_reset)(struct archive_file *);
-
- /**
- * the read method will return corresponding files from archive
- * (as pathnames) and move read index to next file. When there is no
- * next file it return NULL.
- */
- char *(*scan_next)(struct archive_file *);
-
- /**
- * Opens an input_stream of a file within the archive.
- *
- * If this function succeeds, then the #input_stream "owns"
- * the archive file and will automatically close it.
- *
- * @param path the path within the archive
- */
- bool (*open_stream)(struct archive_file *, struct input_stream *is,
- const char *path);
-
- /**
- * closes archive file.
- */
- void (*close)(struct archive_file *);
-
- /**
- * suffixes handled by this plugin.
- * last element in these arrays must always be a NULL
- */
- const char *const*suffixes;
-};
-
bool archive_lookup(char *pathname, char **archive, char **inpath, char **suffix);
#endif
diff --git a/src/archive_internal.h b/src/archive_internal.h
index 130d25d65..03439e826 100644
--- a/src/archive_internal.h
+++ b/src/archive_internal.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -21,7 +21,14 @@
#define MPD_ARCHIVE_INTERNAL_H
struct archive_file {
- int placeholder;
+ const struct archive_plugin *plugin;
};
+static inline void
+archive_file_init(struct archive_file *archive_file,
+ const struct archive_plugin *plugin)
+{
+ archive_file->plugin = plugin;
+}
+
#endif
diff --git a/src/archive_list.c b/src/archive_list.c
index 8228fc961..2656726b5 100644
--- a/src/archive_list.c
+++ b/src/archive_list.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -17,50 +17,44 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "archive_list.h"
-#include "archive_api.h"
+#include "archive_plugin.h"
#include "utils.h"
-#include "config.h"
+#include "archive/bz2_archive_plugin.h"
+#include "archive/iso9660_archive_plugin.h"
+#include "archive/zzip_archive_plugin.h"
#include <string.h>
#include <glib.h>
-extern const struct archive_plugin bz2_plugin;
-extern const struct archive_plugin zip_plugin;
-extern const struct archive_plugin iso_plugin;
-
static const struct archive_plugin *const archive_plugins[] = {
#ifdef HAVE_BZ2
- &bz2_plugin,
+ &bz2_archive_plugin,
#endif
-#ifdef HAVE_ZIP
- &zip_plugin,
+#ifdef HAVE_ZZIP
+ &zzip_archive_plugin,
#endif
-#ifdef HAVE_ISO
- &iso_plugin,
+#ifdef HAVE_ISO9660
+ &iso9660_archive_plugin,
#endif
NULL
};
-enum {
- num_archive_plugins = G_N_ELEMENTS(archive_plugins)-1,
-};
-
/** which plugins have been initialized successfully? */
-static bool archive_plugins_enabled[num_archive_plugins+1];
+static bool archive_plugins_enabled[G_N_ELEMENTS(archive_plugins) - 1];
const struct archive_plugin *
archive_plugin_from_suffix(const char *suffix)
{
- unsigned i;
-
if (suffix == NULL)
return NULL;
- for (i=0; i < num_archive_plugins; ++i) {
+ for (unsigned i = 0; archive_plugins[i] != NULL; ++i) {
const struct archive_plugin *plugin = archive_plugins[i];
if (archive_plugins_enabled[i] &&
- stringFoundInStringArray(plugin->suffixes, suffix)) {
+ plugin->suffixes != NULL &&
+ string_array_contains(plugin->suffixes, suffix)) {
++i;
return plugin;
}
@@ -71,7 +65,7 @@ archive_plugin_from_suffix(const char *suffix)
const struct archive_plugin *
archive_plugin_from_name(const char *name)
{
- for (unsigned i = 0; i < num_archive_plugins; ++i) {
+ for (unsigned i = 0; archive_plugins[i] != NULL; ++i) {
const struct archive_plugin *plugin = archive_plugins[i];
if (archive_plugins_enabled[i] &&
strcmp(plugin->name, name) == 0)
@@ -84,7 +78,7 @@ void archive_plugin_print_all_suffixes(FILE * fp)
{
const char *const*suffixes;
- for (unsigned i = 0; i < num_archive_plugins; ++i) {
+ for (unsigned i = 0; archive_plugins[i] != NULL; ++i) {
const struct archive_plugin *plugin = archive_plugins[i];
if (!archive_plugins_enabled[i])
continue;
@@ -101,7 +95,7 @@ void archive_plugin_print_all_suffixes(FILE * fp)
void archive_plugin_init_all(void)
{
- for (unsigned i = 0; i < num_archive_plugins; ++i) {
+ for (unsigned i = 0; archive_plugins[i] != NULL; ++i) {
const struct archive_plugin *plugin = archive_plugins[i];
if (plugin->init == NULL || archive_plugins[i]->init())
archive_plugins_enabled[i] = true;
@@ -110,7 +104,7 @@ void archive_plugin_init_all(void)
void archive_plugin_deinit_all(void)
{
- for (unsigned i = 0; i < num_archive_plugins; ++i) {
+ for (unsigned i = 0; archive_plugins[i] != NULL; ++i) {
const struct archive_plugin *plugin = archive_plugins[i];
if (archive_plugins_enabled[i] && plugin->finish != NULL)
archive_plugins[i]->finish();
diff --git a/src/archive_list.h b/src/archive_list.h
index 55278fbc4..b65245ce9 100644
--- a/src/archive_list.h
+++ b/src/archive_list.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -20,8 +20,6 @@
#ifndef MPD_ARCHIVE_LIST_H
#define MPD_ARCHIVE_LIST_H
-#include "archive_api.h"
-
#include <stdio.h>
struct archive_plugin;
diff --git a/src/archive_plugin.c b/src/archive_plugin.c
new file mode 100644
index 000000000..60da4d283
--- /dev/null
+++ b/src/archive_plugin.c
@@ -0,0 +1,92 @@
+/*
+ * 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 "archive_plugin.h"
+#include "archive_internal.h"
+
+#include <assert.h>
+
+struct archive_file *
+archive_file_open(const struct archive_plugin *plugin, const char *path,
+ GError **error_r)
+{
+ struct archive_file *file;
+
+ assert(plugin != NULL);
+ assert(plugin->open != NULL);
+ assert(path != NULL);
+ assert(error_r == NULL || *error_r == NULL);
+
+ file = plugin->open(path, error_r);
+
+ if (file != NULL) {
+ assert(file->plugin != NULL);
+ assert(file->plugin->close != NULL);
+ assert(file->plugin->scan_reset != NULL);
+ assert(file->plugin->scan_next != NULL);
+ assert(file->plugin->open_stream != NULL);
+ assert(error_r == NULL || *error_r == NULL);
+ } else {
+ assert(error_r == NULL || *error_r != NULL);
+ }
+
+ return file;
+}
+
+void
+archive_file_close(struct archive_file *file)
+{
+ assert(file != NULL);
+ assert(file->plugin != NULL);
+ assert(file->plugin->close != NULL);
+
+ file->plugin->close(file);
+}
+
+void
+archive_file_scan_reset(struct archive_file *file)
+{
+ assert(file != NULL);
+ assert(file->plugin != NULL);
+ assert(file->plugin->scan_reset != NULL);
+ assert(file->plugin->scan_next != NULL);
+
+ file->plugin->scan_reset(file);
+}
+
+char *
+archive_file_scan_next(struct archive_file *file)
+{
+ assert(file != NULL);
+ assert(file->plugin != NULL);
+ assert(file->plugin->scan_next != NULL);
+
+ return file->plugin->scan_next(file);
+}
+
+struct input_stream *
+archive_file_open_stream(struct archive_file *file,
+ const char *path, 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);
+}
diff --git a/src/archive_plugin.h b/src/archive_plugin.h
new file mode 100644
index 000000000..b08c93389
--- /dev/null
+++ b/src/archive_plugin.h
@@ -0,0 +1,107 @@
+/*
+ * 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_ARCHIVE_PLUGIN_H
+#define MPD_ARCHIVE_PLUGIN_H
+
+#include <glib.h>
+
+#include <stdbool.h>
+
+struct input_stream;
+struct archive_file;
+
+struct archive_plugin {
+ const char *name;
+
+ /**
+ * optional, set this to NULL if the archive plugin doesn't
+ * have/need one this must false if there is an error and
+ * true otherwise
+ */
+ bool (*init)(void);
+
+ /**
+ * optional, set this to NULL if the archive plugin doesn't
+ * have/need one
+ */
+ void (*finish)(void);
+
+ /**
+ * tryes to open archive file and associates handle with archive
+ * returns pointer to handle used is all operations with this archive
+ * or NULL when opening fails
+ */
+ struct archive_file *(*open)(const char *path_fs, GError **error_r);
+
+ /**
+ * reset routine will move current read index in archive to default
+ * position and then the filenames from archives can be read
+ * via scan_next routine
+ */
+ void (*scan_reset)(struct archive_file *);
+
+ /**
+ * the read method will return corresponding files from archive
+ * (as pathnames) and move read index to next file. When there is no
+ * next file it return NULL.
+ */
+ char *(*scan_next)(struct archive_file *);
+
+ /**
+ * 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
+ * NULL to ignore errors
+ */
+ struct input_stream *(*open_stream)(struct archive_file *af,
+ const char *path,
+ GError **error_r);
+
+ /**
+ * closes archive file.
+ */
+ void (*close)(struct archive_file *);
+
+ /**
+ * suffixes handled by this plugin.
+ * last element in these arrays must always be a NULL
+ */
+ const char *const*suffixes;
+};
+
+struct archive_file *
+archive_file_open(const struct archive_plugin *plugin, const char *path,
+ GError **error_r);
+
+void
+archive_file_close(struct archive_file *file);
+
+void
+archive_file_scan_reset(struct archive_file *file);
+
+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);
+
+#endif
diff --git a/src/audio.c b/src/audio.c
index d48558e46..f9894cf3c 100644
--- a/src/audio.c
+++ b/src/audio.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -17,6 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "audio.h"
#include "audio_format.h"
#include "audio_parser.h"
@@ -24,6 +25,7 @@
#include "output_plugin.h"
#include "output_all.h"
#include "conf.h"
+#include "mpd_error.h"
#include <glib.h>
@@ -35,9 +37,8 @@ static struct audio_format configured_audio_format;
void getOutputAudioFormat(const struct audio_format *inAudioFormat,
struct audio_format *outAudioFormat)
{
- *outAudioFormat = audio_format_defined(&configured_audio_format)
- ? configured_audio_format
- : *inAudioFormat;
+ *outAudioFormat = *inAudioFormat;
+ audio_format_mask_apply(outAudioFormat, &configured_audio_format);
}
void initAudioConfig(void)
@@ -46,17 +47,13 @@ void initAudioConfig(void)
GError *error = NULL;
bool ret;
- if (NULL == param || NULL == param->value)
+ if (param == NULL)
return;
ret = audio_format_parse(&configured_audio_format, param->value,
- &error);
+ true, &error);
if (!ret)
- g_error("error parsing \"%s\" at line %i: %s",
- CONF_AUDIO_OUTPUT_FORMAT, param->line, error->message);
-}
-
-void finishAudioConfig(void)
-{
- audio_format_clear(&configured_audio_format);
+ MPD_ERROR("error parsing \"%s\" at line %i: %s",
+ CONF_AUDIO_OUTPUT_FORMAT, param->line,
+ error->message);
}
diff --git a/src/audio.h b/src/audio.h
index 4d80ee0e6..cb3ab7bbe 100644
--- a/src/audio.h
+++ b/src/audio.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -30,6 +30,4 @@ void getOutputAudioFormat(const struct audio_format *inFormat,
/* make sure initPlayerData is called before this function!! */
void initAudioConfig(void);
-void finishAudioConfig(void);
-
#endif
diff --git a/src/audio_check.c b/src/audio_check.c
new file mode 100644
index 000000000..61d2c5833
--- /dev/null
+++ b/src/audio_check.c
@@ -0,0 +1,74 @@
+/*
+ * 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 "audio_check.h"
+#include "audio_format.h"
+
+#include <assert.h>
+
+bool
+audio_check_sample_rate(unsigned long sample_rate, GError **error_r)
+{
+ if (!audio_valid_sample_rate(sample_rate)) {
+ g_set_error(error_r, audio_format_quark(), 0,
+ "Invalid sample rate: %lu", sample_rate);
+ return false;
+ }
+
+ return true;
+}
+
+bool
+audio_check_sample_format(enum sample_format sample_format, GError **error_r)
+{
+ if (!audio_valid_sample_format(sample_format)) {
+ g_set_error(error_r, audio_format_quark(), 0,
+ "Invalid sample format: %u", sample_format);
+ return false;
+ }
+
+ return true;
+}
+
+bool
+audio_check_channel_count(unsigned channels, GError **error_r)
+{
+ if (!audio_valid_channel_count(channels)) {
+ g_set_error(error_r, audio_format_quark(), 0,
+ "Invalid channel count: %u", channels);
+ return false;
+ }
+
+ return true;
+}
+
+bool
+audio_format_init_checked(struct audio_format *af, unsigned long sample_rate,
+ enum sample_format sample_format, unsigned channels,
+ GError **error_r)
+{
+ if (audio_check_sample_rate(sample_rate, error_r) &&
+ audio_check_sample_format(sample_format, error_r) &&
+ audio_check_channel_count(channels, error_r)) {
+ audio_format_init(af, sample_rate, sample_format, channels);
+ assert(audio_format_valid(af));
+ return true;
+ } else
+ return false;
+}
diff --git a/src/audio_check.h b/src/audio_check.h
new file mode 100644
index 000000000..4862e7f15
--- /dev/null
+++ b/src/audio_check.h
@@ -0,0 +1,54 @@
+/*
+ * 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_AUDIO_CHECK_H
+#define MPD_AUDIO_CHECK_H
+
+#include "audio_format.h"
+
+#include <glib.h>
+#include <stdbool.h>
+
+/**
+ * The GLib quark used for errors reported by this library.
+ */
+static inline GQuark
+audio_format_quark(void)
+{
+ return g_quark_from_static_string("audio_format");
+}
+
+bool
+audio_check_sample_rate(unsigned long sample_rate, GError **error_r);
+
+bool
+audio_check_sample_format(enum sample_format, GError **error_r);
+
+bool
+audio_check_channel_count(unsigned sample_format, GError **error_r);
+
+/**
+ * Wrapper for audio_format_init(), which checks all attributes.
+ */
+bool
+audio_format_init_checked(struct audio_format *af, unsigned long sample_rate,
+ enum sample_format sample_format, unsigned channels,
+ GError **error_r);
+
+#endif
diff --git a/src/audio_format.c b/src/audio_format.c
new file mode 100644
index 000000000..13403fbc1
--- /dev/null
+++ b/src/audio_format.c
@@ -0,0 +1,72 @@
+/*
+ * 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 "audio_format.h"
+
+#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
+
+const char *
+sample_format_to_string(enum sample_format format)
+{
+ switch (format) {
+ case SAMPLE_FORMAT_UNDEFINED:
+ return "?";
+
+ case SAMPLE_FORMAT_S8:
+ return "8";
+
+ 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";
+ }
+
+ /* unreachable */
+ assert(false);
+ return "?";
+}
+
+const char *
+audio_format_to_string(const struct audio_format *af,
+ struct audio_format_string *s)
+{
+ assert(af != NULL);
+ assert(s != NULL);
+
+ snprintf(s->buffer, sizeof(s->buffer), "%u:%s%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 64087d070..a4450ad71 100644
--- a/src/audio_format.h
+++ b/src/audio_format.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -22,26 +22,125 @@
#include <stdint.h>
#include <stdbool.h>
+#include <assert.h>
+enum sample_format {
+ SAMPLE_FORMAT_UNDEFINED = 0,
+
+ SAMPLE_FORMAT_S8,
+ 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,
+};
+
+/**
+ * This structure describes the format of a raw PCM stream.
+ */
struct audio_format {
+ /**
+ * The sample rate in Hz. A better name for this attribute is
+ * "frame rate", because technically, you have two samples per
+ * frame in stereo sound.
+ */
uint32_t sample_rate;
- uint8_t bits;
+
+ /**
+ * The format samples are stored in. See the #sample_format
+ * enum for valid values.
+ */
+ uint8_t format;
+
+ /**
+ * The number of channels. Only mono (1) and stereo (2) are
+ * 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;
+};
+
+/**
+ * Buffer for audio_format_string().
+ */
+struct audio_format_string {
+ char buffer[24];
};
+/**
+ * Clears the #audio_format object, i.e. sets all attributes to an
+ * undefined (invalid) value.
+ */
static inline void audio_format_clear(struct audio_format *af)
{
af->sample_rate = 0;
- af->bits = 0;
+ af->format = SAMPLE_FORMAT_UNDEFINED;
af->channels = 0;
+ af->reverse_endian = 0;
}
+/**
+ * Initializes an #audio_format object, i.e. sets all
+ * attributes to valid values.
+ */
+static inline void audio_format_init(struct audio_format *af,
+ uint32_t sample_rate,
+ enum sample_format format, uint8_t channels)
+{
+ af->sample_rate = sample_rate;
+ af->format = (uint8_t)format;
+ af->channels = channels;
+ af->reverse_endian = 0;
+}
+
+/**
+ * Checks whether the specified #audio_format object has a defined
+ * value.
+ */
static inline bool audio_format_defined(const struct audio_format *af)
{
return af->sample_rate != 0;
}
/**
+ * Checks whether the specified #audio_format object is full, i.e. all
+ * attributes are defined. This is more complete than
+ * audio_format_defined(), but slower.
+ */
+static inline bool
+audio_format_fully_defined(const struct audio_format *af)
+{
+ return af->sample_rate != 0 && af->format != SAMPLE_FORMAT_UNDEFINED &&
+ af->channels != 0;
+}
+
+/**
+ * Checks whether the specified #audio_format object has at least one
+ * defined value.
+ */
+static inline bool
+audio_format_mask_defined(const struct audio_format *af)
+{
+ return af->sample_rate != 0 || af->format != SAMPLE_FORMAT_UNDEFINED ||
+ af->channels != 0;
+}
+
+/**
* Checks whether the sample rate is valid.
*
* @param sample_rate the sample rate in Hz
@@ -58,9 +157,21 @@ audio_valid_sample_rate(unsigned sample_rate)
* @param bits the number of significant bits per sample
*/
static inline bool
-audio_valid_sample_format(unsigned bits)
+audio_valid_sample_format(enum sample_format format)
{
- return bits == 16 || bits == 24 || bits == 32 || bits == 8;
+ switch (format) {
+ case SAMPLE_FORMAT_S8:
+ case SAMPLE_FORMAT_S16:
+ case SAMPLE_FORMAT_S24:
+ case SAMPLE_FORMAT_S24_P32:
+ case SAMPLE_FORMAT_S32:
+ return true;
+
+ case SAMPLE_FORMAT_UNDEFINED:
+ break;
+ }
+
+ return false;
}
/**
@@ -79,16 +190,49 @@ audio_valid_channel_count(unsigned channels)
static inline bool audio_format_valid(const struct audio_format *af)
{
return audio_valid_sample_rate(af->sample_rate) &&
- audio_valid_sample_format(af->bits) &&
+ audio_valid_sample_format((enum sample_format)af->format) &&
audio_valid_channel_count(af->channels);
}
+/**
+ * Returns false if the format mask is not valid for playback with
+ * MPD. This function performs some basic validity checks.
+ */
+static inline bool audio_format_mask_valid(const struct audio_format *af)
+{
+ return (af->sample_rate == 0 ||
+ audio_valid_sample_rate(af->sample_rate)) &&
+ (af->format == SAMPLE_FORMAT_UNDEFINED ||
+ audio_valid_sample_format((enum sample_format)af->format)) &&
+ (af->channels == 0 || audio_valid_channel_count(af->channels));
+}
+
static inline bool audio_format_equals(const struct audio_format *a,
const struct audio_format *b)
{
return a->sample_rate == b->sample_rate &&
- a->bits == b->bits &&
- a->channels == b->channels;
+ a->format == b->format &&
+ a->channels == b->channels &&
+ a->reverse_endian == b->reverse_endian;
+}
+
+static inline void
+audio_format_mask_apply(struct audio_format *af,
+ const struct audio_format *mask)
+{
+ assert(audio_format_valid(af));
+ assert(audio_format_mask_valid(mask));
+
+ if (mask->sample_rate != 0)
+ af->sample_rate = mask->sample_rate;
+
+ if (mask->format != SAMPLE_FORMAT_UNDEFINED)
+ af->format = mask->format;
+
+ if (mask->channels != 0)
+ af->channels = mask->channels;
+
+ assert(audio_format_valid(af));
}
/**
@@ -96,28 +240,65 @@ static inline bool audio_format_equals(const struct audio_format *a,
*/
static inline unsigned audio_format_sample_size(const struct audio_format *af)
{
- if (af->bits <= 8)
+ switch (af->format) {
+ case SAMPLE_FORMAT_S8:
return 1;
- else if (af->bits <= 16)
+
+ case SAMPLE_FORMAT_S16:
return 2;
- else
+
+ case SAMPLE_FORMAT_S24:
+ return 3;
+
+ case SAMPLE_FORMAT_S24_P32:
+ case SAMPLE_FORMAT_S32:
return 4;
+
+ case SAMPLE_FORMAT_UNDEFINED:
+ break;
+ }
+
+ return 0;
}
+/**
+ * Returns the size of each full frame in bytes.
+ */
static inline unsigned
audio_format_frame_size(const struct audio_format *af)
{
return audio_format_sample_size(af) * af->channels;
}
+/**
+ * Returns the floating point factor which converts a time span to a
+ * storage size in bytes.
+ */
static inline double audio_format_time_to_size(const struct audio_format *af)
{
return af->sample_rate * audio_format_frame_size(af);
}
-static inline double audioFormatSizeToTime(const struct audio_format *af)
-{
- return 1.0 / audio_format_time_to_size(af);
-}
+/**
+ * Renders a #sample_format enum into a string, e.g. for printing it
+ * in a log file.
+ *
+ * @param format a #sample_format enum value
+ * @return the string
+ */
+const char *
+sample_format_to_string(enum sample_format format);
+
+/**
+ * Renders the #audio_format object into a string, e.g. for printing
+ * it in a log file.
+ *
+ * @param af the #audio_format object
+ * @param s a buffer to print into
+ * @return the string, or NULL if the #audio_format object is invalid
+ */
+const char *
+audio_format_to_string(const struct audio_format *af,
+ struct audio_format_string *s);
#endif
diff --git a/src/audio_parser.c b/src/audio_parser.c
index 906b0f819..139cf1c04 100644
--- a/src/audio_parser.c
+++ b/src/audio_parser.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -22,9 +22,13 @@
*
*/
+#include "config.h"
#include "audio_parser.h"
#include "audio_format.h"
+#include "audio_check.h"
+#include <assert.h>
+#include <string.h>
#include <stdlib.h>
/**
@@ -36,64 +40,160 @@ audio_parser_quark(void)
return g_quark_from_static_string("audio_parser");
}
-bool
-audio_format_parse(struct audio_format *dest, const char *src, GError **error)
+static bool
+parse_sample_rate(const char *src, bool mask, uint32_t *sample_rate_r,
+ const char **endptr_r, GError **error_r)
{
- char *endptr;
unsigned long value;
+ char *endptr;
- audio_format_clear(dest);
-
- /* parse sample rate */
+ if (mask && *src == '*') {
+ *sample_rate_r = 0;
+ *endptr_r = src + 1;
+ return true;
+ }
value = strtoul(src, &endptr, 10);
if (endptr == src) {
- g_set_error(error, audio_parser_quark(), 0,
- "Sample rate missing");
+ g_set_error(error_r, audio_parser_quark(), 0,
+ "Failed to parse the sample rate");
return false;
- } else if (*endptr != ':') {
- g_set_error(error, audio_parser_quark(), 0,
- "Sample format missing");
+ } else if (!audio_check_sample_rate(value, error_r))
+ return false;
+
+ *sample_rate_r = value;
+ *endptr_r = endptr;
+ return true;
+}
+
+static bool
+parse_sample_format(const char *src, bool mask,
+ enum sample_format *sample_format_r,
+ const char **endptr_r, GError **error_r)
+{
+ unsigned long value;
+ char *endptr;
+ enum sample_format sample_format;
+
+ if (mask && *src == '*') {
+ *sample_format_r = SAMPLE_FORMAT_UNDEFINED;
+ *endptr_r = src + 1;
+ return true;
+ }
+
+ value = strtoul(src, &endptr, 10);
+ if (endptr == src) {
+ g_set_error(error_r, audio_parser_quark(), 0,
+ "Failed to parse the sample format");
return false;
- } else if (!audio_valid_sample_rate(value)) {
- g_set_error(error, audio_parser_quark(), 0,
- "Invalid sample rate: %lu", value);
+ }
+
+ switch (value) {
+ case 8:
+ sample_format = SAMPLE_FORMAT_S8;
+ break;
+
+ case 16:
+ sample_format = SAMPLE_FORMAT_S16;
+ break;
+
+ case 24:
+ if (memcmp(endptr, "_3", 2) == 0) {
+ sample_format = SAMPLE_FORMAT_S24;
+ endptr += 2;
+ } else
+ sample_format = SAMPLE_FORMAT_S24_P32;
+ break;
+
+ case 32:
+ sample_format = SAMPLE_FORMAT_S32;
+ break;
+
+ default:
+ g_set_error(error_r, audio_parser_quark(), 0,
+ "Invalid sample format: %lu", value);
return false;
}
- dest->sample_rate = value;
+ assert(audio_valid_sample_format(sample_format));
- /* parse sample format */
+ *sample_format_r = sample_format;
+ *endptr_r = endptr;
+ return true;
+}
+
+static bool
+parse_channel_count(const char *src, bool mask, uint8_t *channels_r,
+ const char **endptr_r, GError **error_r)
+{
+ unsigned long value;
+ char *endptr;
+
+ if (mask && *src == '*') {
+ *channels_r = 0;
+ *endptr_r = src + 1;
+ return true;
+ }
- src = endptr + 1;
value = strtoul(src, &endptr, 10);
if (endptr == src) {
- g_set_error(error, audio_parser_quark(), 0,
- "Sample format missing");
+ g_set_error(error_r, audio_parser_quark(), 0,
+ "Failed to parse the channel count");
return false;
- } else if (*endptr != ':') {
- g_set_error(error, audio_parser_quark(), 0,
- "Channel count missing");
+ } else if (!audio_check_channel_count(value, error_r))
return false;
- } else if (!audio_valid_sample_format(value)) {
- g_set_error(error, audio_parser_quark(), 0,
- "Invalid sample format: %lu", value);
+
+ *channels_r = value;
+ *endptr_r = endptr;
+ return true;
+}
+
+bool
+audio_format_parse(struct audio_format *dest, const char *src,
+ bool mask, GError **error_r)
+{
+ uint32_t rate;
+ enum sample_format sample_format;
+ uint8_t channels;
+
+ audio_format_clear(dest);
+
+ /* parse sample rate */
+
+ if (!parse_sample_rate(src, mask, &rate, &src, error_r))
+ return false;
+
+ if (*src++ != ':') {
+ g_set_error(error_r, audio_parser_quark(), 0,
+ "Sample format missing");
return false;
}
- dest->bits = value;
+ /* parse sample format */
+
+ if (!parse_sample_format(src, mask, &sample_format, &src, error_r))
+ return false;
+
+ if (*src++ != ':') {
+ g_set_error(error_r, audio_parser_quark(), 0,
+ "Channel count missing");
+ return false;
+ }
/* parse channel count */
- src = endptr + 1;
- value = strtoul(src, &endptr, 10);
- if (*endptr != 0 || !audio_valid_channel_count(value)) {
- g_set_error(error, audio_parser_quark(), 0,
- "Invalid channel count: %s", src);
+ if (!parse_channel_count(src, mask, &channels, &src, error_r))
+ return false;
+
+ if (*src != 0) {
+ g_set_error(error_r, audio_parser_quark(), 0,
+ "Extra data after channel count: %s", src);
return false;
}
- dest->channels = value;
+ audio_format_init(dest, rate, sample_format, channels);
+ assert(mask ? audio_format_mask_valid(dest)
+ : audio_format_valid(dest));
return true;
}
diff --git a/src/audio_parser.h b/src/audio_parser.h
index 30a927456..214ec5eb1 100644
--- a/src/audio_parser.h
+++ b/src/audio_parser.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -37,11 +37,13 @@ struct audio_format;
*
* @param dest the destination #audio_format struct
* @param src the input string
- * @param error location to store the error occuring, or NULL to
+ * @param mask if true, then "*" is allowed for any number of items
+ * @param error_r location to store the error occuring, or NULL to
* ignore errors
* @return true on success
*/
bool
-audio_format_parse(struct audio_format *dest, const char *src, GError **error);
+audio_format_parse(struct audio_format *dest, const char *src,
+ bool mask, GError **error_r);
#endif
diff --git a/src/buffer.c b/src/buffer.c
index 24715a744..bee871700 100644
--- a/src/buffer.c
+++ b/src/buffer.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -17,6 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "buffer.h"
#include "chunk.h"
#include "poison.h"
@@ -117,6 +118,9 @@ music_buffer_return(struct music_buffer *buffer, struct music_chunk *chunk)
assert(buffer != NULL);
assert(chunk != NULL);
+ if (chunk->other != NULL)
+ music_buffer_return(buffer, chunk->other);
+
g_mutex_lock(buffer->mutex);
music_chunk_free(chunk);
diff --git a/src/buffer.h b/src/buffer.h
index 441e0ea4c..75e5bc6e6 100644
--- a/src/buffer.h
+++ b/src/buffer.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
diff --git a/src/buffer2array.c b/src/buffer2array.c
deleted file mode 100644
index b6029d754..000000000
--- a/src/buffer2array.c
+++ /dev/null
@@ -1,133 +0,0 @@
-/*
- * 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 "buffer2array.h"
-
-#include <glib.h>
-
-#include <string.h>
-
-int buffer2array(char *buffer, char *array[], const int max)
-{
- int i = 0;
- char *c = buffer;
-
- while (*c != '\0' && i < max) {
- if (*c == '\"') {
- array[i++] = ++c;
- while (*c != '\0') {
- if (*c == '\"') {
- *(c++) = '\0';
- break;
- }
- else if (*(c++) == '\\' && *c != '\0') {
- memmove(c - 1, c, strlen(c) + 1);
- }
- }
- } else {
- c = g_strchug(c);
- if (*c == '\0')
- return i;
-
- array[i++] = c++;
-
- while (!g_ascii_isspace(*c) && *c != '\0')
- ++c;
- }
- if (*c == '\0')
- return i;
- *(c++) = '\0';
-
- c = g_strchug(c);
- }
- return i;
-}
-
-#ifdef UNIT_TEST
-
-#include <stdio.h>
-#include <string.h>
-#include <assert.h>
-
-int main()
-{
- char *a[4] = { NULL };
- char *b;
- int max;
-
- b = strdup("lsinfo \"/some/dir/name \\\"test\\\"\"");
- assert(b);
- max = buffer2array(b, a, 4);
- assert( !strcmp("lsinfo", a[0]) );
- assert( !strcmp("/some/dir/name \"test\"", a[1]) );
- assert( !a[2] );
-
- b = strdup("lsinfo \"/some/dir/name \\\"test\\\" something else\"");
- assert(b);
- max = buffer2array(b, a, 4);
- assert( !strcmp("lsinfo", a[0]) );
- assert( !strcmp("/some/dir/name \"test\" something else", a[1]) );
- assert( !a[2] );
-
- b = strdup("lsinfo \"/some/dir\\\\name\"");
- assert(b);
- max = buffer2array(b, a, 4);
- assert( !strcmp("lsinfo", a[0]) );
- assert( !strcmp("/some/dir\\name", a[1]) );
- assert( !a[2] );
-
- b = strdup("lsinfo \"/some/dir name\"");
- assert(b);
- max = buffer2array(b, a, 4);
- assert( !strcmp("lsinfo", a[0]) );
- assert( !strcmp("/some/dir name", a[1]) );
- assert( !a[2] );
-
- b = strdup("lsinfo \"\\\"/some/dir\\\"\"");
- assert(b);
- max = buffer2array(b, a, 4);
- assert( !strcmp("lsinfo", a[0]) );
- assert( !strcmp("\"/some/dir\"", a[1]) );
- assert( !a[2] );
-
- b = strdup("lsinfo \"\\\"/some/dir\\\" x\"");
- assert(b);
- max = buffer2array(b, a, 4);
- assert( !strcmp("lsinfo", a[0]) );
- assert( !strcmp("\"/some/dir\" x", a[1]) );
- assert( !a[2] );
-
- b = strdup("lsinfo \"single quote\\'d from php magicquotes\"");
- assert(b);
- max = buffer2array(b, a, 4);
- assert( !strcmp("lsinfo", a[0]) );
- assert( !strcmp("single quote\'d from php magicquotes", a[1]) );
- assert( !a[2] );
-
- b = strdup("lsinfo \"double quote\\\"d from php magicquotes\"");
- assert(b);
- max = buffer2array(b, a, 4);
- assert( !strcmp("lsinfo", a[0]) );
- assert( !strcmp("double quote\"d from php magicquotes", a[1]) );
- assert( !a[2] );
-
- return 0;
-}
-
-#endif
diff --git a/src/check.h b/src/check.h
new file mode 100644
index 000000000..56061621f
--- /dev/null
+++ b/src/check.h
@@ -0,0 +1,47 @@
+/*
+ * 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_CHECK_H
+#define MPD_CHECK_H
+
+/*
+ * All sources must include config.h on the first line to ensure that
+ * Large File Support is configured properly. This header checks
+ * whether this has happened.
+ *
+ * Usage: include this header before you use any of the above types.
+ * It will stop the compiler if something went wrong.
+ *
+ * This is Linux/glibc specific, and only enabled in the debug build,
+ * so bugs in this headers don't affect users with production builds.
+ *
+ */
+
+#ifndef PACKAGE_VERSION
+#error config.h missing
+#endif
+
+#if defined(__linux__) && !defined(NDEBUG) && defined(ENABLE_LARGEFILE) && \
+ defined(_FEATURES_H) && defined(__i386__) && \
+ !defined(__USE_FILE_OFFSET64)
+/* on i386, check if LFS is enabled */
+#error config.h was included too late
+#endif
+
+#endif
diff --git a/src/chunk.c b/src/chunk.c
index 3ac190633..79597506d 100644
--- a/src/chunk.c
+++ b/src/chunk.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -17,6 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "chunk.h"
#include "audio_format.h"
#include "tag.h"
@@ -26,8 +27,10 @@
void
music_chunk_init(struct music_chunk *chunk)
{
+ chunk->other = NULL;
chunk->length = 0;
chunk->tag = NULL;
+ chunk->replay_gain_serial = 0;
}
void
diff --git a/src/chunk.h b/src/chunk.h
index 51e75d906..02e7b3650 100644
--- a/src/chunk.h
+++ b/src/chunk.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -20,6 +20,8 @@
#ifndef MPD_CHUNK_H
#define MPD_CHUNK_H
+#include "replay_gain_info.h"
+
#ifndef NDEBUG
#include "audio_format.h"
#endif
@@ -42,6 +44,18 @@ struct music_chunk {
/** the next chunk in a linked list */
struct music_chunk *next;
+ /**
+ * An optional chunk which should be mixed into this chunk.
+ * This is used for cross-fading.
+ */
+ struct music_chunk *other;
+
+ /**
+ * The current mix ratio for cross-fading: 1.0 means play 100%
+ * of this chunk, 0.0 means play 100% of the "other" chunk.
+ */
+ float mix_ratio;
+
/** number of bytes stored in this chunk */
uint16_t length;
@@ -59,6 +73,19 @@ struct music_chunk {
*/
struct tag *tag;
+ /**
+ * Replay gain information associated with this chunk.
+ * Only valid if the serial is not 0.
+ */
+ struct replay_gain_info replay_gain_info;
+
+ /**
+ * A serial number for checking if replay gain info has
+ * changed since the last chunk. The magic value 0 indicates
+ * that there is no replay gain info available.
+ */
+ unsigned replay_gain_serial;
+
/** the data (probably PCM) */
char data[CHUNK_SIZE];
diff --git a/src/client.c b/src/client.c
index 6a256998f..9668c9249 100644
--- a/src/client.c
+++ b/src/client.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -17,110 +17,8 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
-#include "client.h"
-#include "fifo_buffer.h"
-#include "command.h"
-#include "conf.h"
-#include "listen.h"
-#include "socket_util.h"
-#include "permission.h"
-#include "event_pipe.h"
-#include "idle.h"
-#include "main.h"
#include "config.h"
-
-#include <glib.h>
-#include <assert.h>
-#include <unistd.h>
-#include <string.h>
-#include <stdlib.h>
-#include <stdio.h>
-#include <errno.h>
-
-#ifdef WIN32
-#include <ws2tcpip.h>
-#include <winsock.h>
-#else
-#include <sys/socket.h>
-#include <netinet/in.h>
-#include <arpa/inet.h>
-#endif
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "client"
-#define LOG_LEVEL_SECURE G_LOG_LEVEL_INFO
-
-static const char GREETING[] = "OK MPD " PROTOCOL_VERSION "\n";
-
-#define CLIENT_LIST_MODE_BEGIN "command_list_begin"
-#define CLIENT_LIST_OK_MODE_BEGIN "command_list_ok_begin"
-#define CLIENT_LIST_MODE_END "command_list_end"
-#define CLIENT_TIMEOUT_DEFAULT (60)
-#define CLIENT_MAX_CONNECTIONS_DEFAULT (10)
-#define CLIENT_MAX_COMMAND_LIST_DEFAULT (2048*1024)
-#define CLIENT_MAX_OUTPUT_BUFFER_SIZE_DEFAULT (8192*1024)
-
-/* set this to zero to indicate we have no possible clients */
-static unsigned int client_max_connections; /*CLIENT_MAX_CONNECTIONS_DEFAULT; */
-static int client_timeout;
-static size_t client_max_command_list_size;
-static size_t client_max_output_buffer_size;
-
-struct deferred_buffer {
- size_t size;
- char data[sizeof(long)];
-};
-
-struct client {
- GIOChannel *channel;
- guint source_id;
-
- /** the buffer for reading lines from the #channel */
- struct fifo_buffer *input;
-
- unsigned permission;
-
- /** the uid of the client process, or -1 if unknown */
- int uid;
-
- /**
- * How long since the last activity from this client?
- */
- GTimer *last_activity;
-
- GSList *cmd_list; /* for when in list mode */
- int cmd_list_OK; /* print OK after each command execution */
- size_t cmd_list_size; /* mem cmd_list consumes */
- GQueue *deferred_send; /* for output if client is slow */
- size_t deferred_bytes; /* mem deferred_send consumes */
- unsigned int num; /* client number */
-
- char send_buf[4096];
- size_t send_buf_used; /* bytes used this instance */
-
- /** is this client waiting for an "idle" response? */
- bool idle_waiting;
-
- /** idle flags pending on this client, to be sent as soon as
- the client enters "idle" */
- unsigned idle_flags;
-
- /** idle flags that the client wants to receive */
- unsigned idle_subscriptions;
-};
-
-static GList *clients;
-static unsigned num_clients;
-static guint expire_source_id;
-
-static void client_write_deferred(struct client *client);
-
-static void client_write_output(struct client *client);
-
-static void client_manager_expire(void);
-
-static gboolean
-client_in_event(GIOChannel *source, GIOCondition condition, gpointer data);
+#include "client_internal.h"
bool client_is_expired(const struct client *client)
{
@@ -141,782 +39,3 @@ void client_set_permission(struct client *client, unsigned permission)
{
client->permission = permission;
}
-
-/**
- * An idle event which calls client_manager_expire().
- */
-static gboolean
-client_manager_expire_event(G_GNUC_UNUSED gpointer data)
-{
- expire_source_id = 0;
- client_manager_expire();
- return false;
-}
-
-static inline void client_set_expired(struct client *client)
-{
- if (expire_source_id == 0 && !client_is_expired(client))
- /* delayed deletion */
- expire_source_id = g_idle_add(client_manager_expire_event,
- NULL);
-
- if (client->source_id != 0) {
- g_source_remove(client->source_id);
- client->source_id = 0;
- }
-
- if (client->channel != NULL) {
- g_io_channel_unref(client->channel);
- client->channel = NULL;
- }
-}
-
-static void client_init(struct client *client, int fd)
-{
- static unsigned int next_client_num;
-
- assert(fd >= 0);
-
- client->cmd_list_size = 0;
- client->cmd_list_OK = -1;
-
-#ifndef G_OS_WIN32
- client->channel = g_io_channel_unix_new(fd);
-#else
- client->channel = g_io_channel_win32_new_socket(fd);
-#endif
- /* GLib is responsible for closing the file descriptor */
- g_io_channel_set_close_on_unref(client->channel, true);
- /* NULL encoding means the stream is binary safe; the MPD
- protocol is UTF-8 only, but we are doing this call anyway
- to prevent GLib from messing around with the stream */
- g_io_channel_set_encoding(client->channel, NULL, NULL);
- /* we prefer to do buffering */
- g_io_channel_set_buffered(client->channel, false);
-
- client->source_id = g_io_add_watch(client->channel,
- G_IO_IN|G_IO_ERR|G_IO_HUP,
- client_in_event, client);
-
- client->input = fifo_buffer_new(4096);
-
- client->cmd_list = NULL;
- client->deferred_send = g_queue_new();
- client->deferred_bytes = 0;
- client->num = next_client_num++;
- client->send_buf_used = 0;
-
- client->permission = getDefaultPermissions();
-
- (void)write(fd, GREETING, sizeof(GREETING) - 1);
-}
-
-static void free_cmd_list(GSList *list)
-{
- for (GSList *tmp = list; tmp != NULL; tmp = g_slist_next(tmp))
- g_free(tmp->data);
-
- g_slist_free(list);
-}
-
-static void new_cmd_list_ptr(struct client *client, char *s)
-{
- client->cmd_list = g_slist_prepend(client->cmd_list, g_strdup(s));
-}
-
-static void
-deferred_buffer_free(gpointer data, G_GNUC_UNUSED gpointer user_data)
-{
- struct deferred_buffer *buffer = data;
- g_free(buffer);
-}
-
-static void client_close(struct client *client)
-{
- assert(num_clients > 0);
- assert(clients != NULL);
-
- clients = g_list_remove(clients, client);
- --num_clients;
-
- client_set_expired(client);
-
- g_timer_destroy(client->last_activity);
-
- if (client->cmd_list) {
- free_cmd_list(client->cmd_list);
- client->cmd_list = NULL;
- }
-
- g_queue_foreach(client->deferred_send, deferred_buffer_free, NULL);
- g_queue_free(client->deferred_send);
-
- fifo_buffer_free(client->input);
-
- g_log(G_LOG_DOMAIN, LOG_LEVEL_SECURE,
- "[%u] closed", client->num);
- g_free(client);
-}
-
-void client_new(int fd, const struct sockaddr *sa, size_t sa_length, int uid)
-{
- struct client *client;
- char *remote;
-
- if (num_clients >= client_max_connections) {
- g_warning("Max Connections Reached!");
- close(fd);
- return;
- }
-
- client = g_new0(struct client, 1);
- clients = g_list_prepend(clients, client);
- ++num_clients;
-
- client_init(client, fd);
- client->uid = uid;
-
- client->last_activity = g_timer_new();
-
- remote = sockaddr_to_string(sa, sa_length, NULL);
- g_log(G_LOG_DOMAIN, LOG_LEVEL_SECURE,
- "[%u] opened from %s", client->num, remote);
- g_free(remote);
-}
-
-static int client_process_line(struct client *client, char *line)
-{
- int ret = 1;
-
- if (strcmp(line, "noidle") == 0) {
- if (client->idle_waiting) {
- /* send empty idle response and leave idle mode */
- client->idle_waiting = false;
- command_success(client);
- client_write_output(client);
- }
-
- /* do nothing if the client wasn't idling: the client
- has already received the full idle response from
- client_idle_notify(), which he can now evaluate */
-
- return 0;
- } else if (client->idle_waiting) {
- /* during idle mode, clients must not send anything
- except "noidle" */
- g_warning("[%u] command \"%s\" during idle",
- client->num, line);
- return COMMAND_RETURN_CLOSE;
- }
-
- if (client->cmd_list_OK >= 0) {
- if (strcmp(line, CLIENT_LIST_MODE_END) == 0) {
- g_debug("[%u] process command list",
- client->num);
-
- /* for scalability reasons, we have prepended
- each new command; now we have to reverse it
- to restore the correct order */
- client->cmd_list = g_slist_reverse(client->cmd_list);
-
- ret = command_process_list(client,
- client->cmd_list_OK,
- client->cmd_list);
- g_debug("[%u] process command "
- "list returned %i", client->num, ret);
-
- if (ret == COMMAND_RETURN_CLOSE ||
- client_is_expired(client))
- return COMMAND_RETURN_CLOSE;
-
- if (ret == 0)
- command_success(client);
-
- client_write_output(client);
- free_cmd_list(client->cmd_list);
- client->cmd_list = NULL;
- client->cmd_list_OK = -1;
- } else {
- size_t len = strlen(line) + 1;
- client->cmd_list_size += len;
- if (client->cmd_list_size >
- client_max_command_list_size) {
- g_warning("[%u] command list size (%lu) "
- "is larger than the max (%lu)",
- client->num,
- (unsigned long)client->cmd_list_size,
- (unsigned long)client_max_command_list_size);
- return COMMAND_RETURN_CLOSE;
- } else
- new_cmd_list_ptr(client, line);
- }
- } else {
- if (strcmp(line, CLIENT_LIST_MODE_BEGIN) == 0) {
- client->cmd_list_OK = 0;
- ret = 1;
- } else if (strcmp(line, CLIENT_LIST_OK_MODE_BEGIN) == 0) {
- client->cmd_list_OK = 1;
- ret = 1;
- } else {
- g_debug("[%u] process command \"%s\"",
- client->num, line);
- ret = command_process(client, line);
- g_debug("[%u] command returned %i",
- client->num, ret);
-
- if (ret == COMMAND_RETURN_CLOSE ||
- client_is_expired(client))
- return COMMAND_RETURN_CLOSE;
-
- if (ret == 0)
- command_success(client);
-
- client_write_output(client);
- }
- }
-
- return ret;
-}
-
-static char *
-client_read_line(struct client *client)
-{
- const char *p, *newline;
- size_t length;
- char *line;
-
- p = fifo_buffer_read(client->input, &length);
- if (p == NULL)
- return NULL;
-
- newline = memchr(p, '\n', length);
- if (newline == NULL)
- return NULL;
-
- line = g_strndup(p, newline - p);
- fifo_buffer_consume(client->input, newline - p + 1);
-
- return g_strchomp(line);
-}
-
-static int client_input_received(struct client *client, size_t bytesRead)
-{
- char *line;
- int ret;
-
- fifo_buffer_append(client->input, bytesRead);
-
- /* process all lines */
-
- while ((line = client_read_line(client)) != NULL) {
- ret = client_process_line(client, line);
- g_free(line);
-
- if (ret == COMMAND_RETURN_KILL ||
- ret == COMMAND_RETURN_CLOSE)
- return ret;
- if (client_is_expired(client))
- return COMMAND_RETURN_CLOSE;
- }
-
- return 0;
-}
-
-static int client_read(struct client *client)
-{
- char *p;
- size_t max_length;
- GError *error = NULL;
- GIOStatus status;
- gsize bytes_read;
-
- assert(client != NULL);
- assert(client->channel != NULL);
-
- p = fifo_buffer_write(client->input, &max_length);
- if (p == NULL) {
- g_warning("[%u] buffer overflow", client->num);
- return COMMAND_RETURN_CLOSE;
- }
-
- status = g_io_channel_read_chars(client->channel, p, max_length,
- &bytes_read, &error);
- switch (status) {
- case G_IO_STATUS_NORMAL:
- return client_input_received(client, bytes_read);
-
- case G_IO_STATUS_AGAIN:
- /* try again later, after select() */
- return 0;
-
- case G_IO_STATUS_EOF:
- /* peer disconnected */
- return COMMAND_RETURN_CLOSE;
-
- case G_IO_STATUS_ERROR:
- /* I/O error */
- g_warning("failed to read from client %d: %s",
- client->num, error->message);
- g_error_free(error);
- return COMMAND_RETURN_CLOSE;
- }
-
- /* unreachable */
- return COMMAND_RETURN_CLOSE;
-}
-
-static gboolean
-client_out_event(G_GNUC_UNUSED GIOChannel *source, GIOCondition condition,
- gpointer data);
-
-static gboolean
-client_in_event(G_GNUC_UNUSED GIOChannel *source,
- GIOCondition condition,
- gpointer data)
-{
- struct client *client = data;
- int ret;
-
- assert(!client_is_expired(client));
-
- if (condition != G_IO_IN) {
- client_set_expired(client);
- return false;
- }
-
- g_timer_start(client->last_activity);
-
- ret = client_read(client);
- switch (ret) {
- case COMMAND_RETURN_KILL:
- client_close(client);
- g_main_loop_quit(main_loop);
- return false;
-
- case COMMAND_RETURN_CLOSE:
- client_close(client);
- return false;
- }
-
- if (client_is_expired(client)) {
- client_close(client);
- return false;
- }
-
- if (!g_queue_is_empty(client->deferred_send)) {
- /* deferred buffers exist: schedule write */
- client->source_id = g_io_add_watch(client->channel,
- G_IO_OUT|G_IO_ERR|G_IO_HUP,
- client_out_event, client);
- return false;
- }
-
- /* read more */
- return true;
-}
-
-static gboolean
-client_out_event(G_GNUC_UNUSED GIOChannel *source, GIOCondition condition,
- gpointer data)
-{
- struct client *client = data;
-
- assert(!client_is_expired(client));
-
- if (condition != G_IO_OUT) {
- client_set_expired(client);
- return false;
- }
-
- client_write_deferred(client);
-
- if (client_is_expired(client)) {
- client_close(client);
- return false;
- }
-
- g_timer_start(client->last_activity);
-
- if (g_queue_is_empty(client->deferred_send)) {
- /* done sending deferred buffers exist: schedule
- read */
- client->source_id = g_io_add_watch(client->channel,
- G_IO_IN|G_IO_ERR|G_IO_HUP,
- client_in_event, client);
- return false;
- }
-
- /* write more */
- return true;
-}
-
-void client_manager_init(void)
-{
- client_timeout = config_get_positive(CONF_CONN_TIMEOUT,
- CLIENT_TIMEOUT_DEFAULT);
- client_max_connections =
- config_get_positive(CONF_MAX_CONN,
- CLIENT_MAX_CONNECTIONS_DEFAULT);
- client_max_command_list_size =
- config_get_positive(CONF_MAX_COMMAND_LIST_SIZE,
- CLIENT_MAX_COMMAND_LIST_DEFAULT / 1024)
- * 1024;
-
- client_max_output_buffer_size =
- config_get_positive(CONF_MAX_OUTPUT_BUFFER_SIZE,
- CLIENT_MAX_OUTPUT_BUFFER_SIZE_DEFAULT / 1024)
- * 1024;
-}
-
-static void client_close_all(void)
-{
- while (clients != NULL) {
- struct client *client = clients->data;
-
- client_close(client);
- }
-
- assert(num_clients == 0);
-}
-
-void client_manager_deinit(void)
-{
- client_close_all();
-
- client_max_connections = 0;
-
- if (expire_source_id != 0)
- g_source_remove(expire_source_id);
-}
-
-static void
-client_check_expired_callback(gpointer data, G_GNUC_UNUSED gpointer user_data)
-{
- struct client *client = data;
-
- if (client_is_expired(client)) {
- g_debug("[%u] expired", client->num);
- client_close(client);
- } else if (!client->idle_waiting && /* idle clients
- never expire */
- (int)g_timer_elapsed(client->last_activity, NULL) >
- client_timeout) {
- g_debug("[%u] timeout", client->num);
- client_close(client);
- }
-}
-
-static void
-client_manager_expire(void)
-{
- g_list_foreach(clients, client_check_expired_callback, NULL);
-}
-
-static size_t
-client_write_deferred_buffer(struct client *client,
- const struct deferred_buffer *buffer)
-{
- GError *error = NULL;
- GIOStatus status;
- gsize bytes_written;
-
- assert(client != NULL);
- assert(client->channel != NULL);
- assert(buffer != NULL);
-
- status = g_io_channel_write_chars
- (client->channel, buffer->data, buffer->size,
- &bytes_written, &error);
- switch (status) {
- case G_IO_STATUS_NORMAL:
- return bytes_written;
-
- case G_IO_STATUS_AGAIN:
- return 0;
-
- case G_IO_STATUS_EOF:
- /* client has disconnected */
-
- client_set_expired(client);
- return 0;
-
- case G_IO_STATUS_ERROR:
- /* I/O error */
-
- client_set_expired(client);
- g_warning("failed to flush buffer for %i: %s",
- client->num, error->message);
- g_error_free(error);
- return 0;
- }
-
- /* unreachable */
- return 0;
-}
-
-static void client_write_deferred(struct client *client)
-{
- size_t ret;
-
- while (!g_queue_is_empty(client->deferred_send)) {
- struct deferred_buffer *buf =
- g_queue_peek_head(client->deferred_send);
-
- assert(buf->size > 0);
- assert(buf->size <= client->deferred_bytes);
-
- ret = client_write_deferred_buffer(client, buf);
- if (ret == 0)
- break;
-
- if (ret < buf->size) {
- assert(client->deferred_bytes >= (size_t)ret);
- client->deferred_bytes -= ret;
- buf->size -= ret;
- memmove(buf->data, buf->data + ret, buf->size);
- break;
- } else {
- size_t decr = sizeof(*buf) -
- sizeof(buf->data) + buf->size;
-
- assert(client->deferred_bytes >= decr);
- client->deferred_bytes -= decr;
- g_free(buf);
- g_queue_pop_head(client->deferred_send);
- }
-
- g_timer_start(client->last_activity);
- }
-
- if (g_queue_is_empty(client->deferred_send)) {
- g_debug("[%u] buffer empty %lu", client->num,
- (unsigned long)client->deferred_bytes);
- assert(client->deferred_bytes == 0);
- }
-}
-
-static void client_defer_output(struct client *client,
- const void *data, size_t length)
-{
- size_t alloc;
- struct deferred_buffer *buf;
-
- assert(length > 0);
-
- alloc = sizeof(*buf) - sizeof(buf->data) + length;
- client->deferred_bytes += alloc;
- if (client->deferred_bytes > client_max_output_buffer_size) {
- g_warning("[%u] output buffer size (%lu) is "
- "larger than the max (%lu)",
- client->num,
- (unsigned long)client->deferred_bytes,
- (unsigned long)client_max_output_buffer_size);
- /* cause client to close */
- client_set_expired(client);
- return;
- }
-
- buf = g_malloc(alloc);
- buf->size = length;
- memcpy(buf->data, data, length);
-
- g_queue_push_tail(client->deferred_send, buf);
-}
-
-static void client_write_direct(struct client *client,
- const char *data, size_t length)
-{
- GError *error = NULL;
- GIOStatus status;
- gsize bytes_written;
-
- assert(client != NULL);
- assert(client->channel != NULL);
- assert(data != NULL);
- assert(length > 0);
- assert(g_queue_is_empty(client->deferred_send));
-
- status = g_io_channel_write_chars(client->channel, data, length,
- &bytes_written, &error);
- switch (status) {
- case G_IO_STATUS_NORMAL:
- case G_IO_STATUS_AGAIN:
- break;
-
- case G_IO_STATUS_EOF:
- /* client has disconnected */
-
- client_set_expired(client);
- return;
-
- case G_IO_STATUS_ERROR:
- /* I/O error */
-
- client_set_expired(client);
- g_warning("failed to write to %i: %s",
- client->num, error->message);
- g_error_free(error);
- return;
- }
-
- if (bytes_written < length)
- client_defer_output(client, data + bytes_written,
- length - bytes_written);
-
- if (!g_queue_is_empty(client->deferred_send))
- g_debug("[%u] buffer created", client->num);
-}
-
-static void client_write_output(struct client *client)
-{
- if (client_is_expired(client) || !client->send_buf_used)
- return;
-
- if (!g_queue_is_empty(client->deferred_send)) {
- client_defer_output(client, client->send_buf,
- client->send_buf_used);
-
- if (client_is_expired(client))
- return;
-
- /* try to flush the deferred buffers now; the current
- server command may take too long to finish, and
- meanwhile try to feed output to the client,
- otherwise it will time out. One reason why
- deferring is slow might be that currently each
- client_write() allocates a new deferred buffer.
- This should be optimized after MPD 0.14. */
- client_write_deferred(client);
- } else
- client_write_direct(client, client->send_buf,
- client->send_buf_used);
-
- client->send_buf_used = 0;
-}
-
-/**
- * Write a block of data to the client.
- */
-static void client_write(struct client *client, const char *buffer, size_t buflen)
-{
- /* if the client is going to be closed, do nothing */
- if (client_is_expired(client))
- return;
-
- while (buflen > 0 && !client_is_expired(client)) {
- size_t copylen;
-
- assert(client->send_buf_used < sizeof(client->send_buf));
-
- copylen = sizeof(client->send_buf) - client->send_buf_used;
- if (copylen > buflen)
- copylen = buflen;
-
- memcpy(client->send_buf + client->send_buf_used, buffer,
- copylen);
- buflen -= copylen;
- client->send_buf_used += copylen;
- buffer += copylen;
- if (client->send_buf_used >= sizeof(client->send_buf))
- client_write_output(client);
- }
-}
-
-void client_puts(struct client *client, const char *s)
-{
- client_write(client, s, strlen(s));
-}
-
-void client_vprintf(struct client *client, const char *fmt, va_list args)
-{
- va_list tmp;
- int length;
- char *buffer;
-
- va_copy(tmp, args);
- length = vsnprintf(NULL, 0, fmt, tmp);
- va_end(tmp);
-
- if (length <= 0)
- /* wtf.. */
- return;
-
- buffer = g_malloc(length + 1);
- vsnprintf(buffer, length + 1, fmt, args);
- client_write(client, buffer, length);
- g_free(buffer);
-}
-
-G_GNUC_PRINTF(2, 3) void client_printf(struct client *client, const char *fmt, ...)
-{
- va_list args;
-
- va_start(args, fmt);
- client_vprintf(client, fmt, args);
- va_end(args);
-}
-
-/**
- * Send "idle" response to this client.
- */
-static void
-client_idle_notify(struct client *client)
-{
- unsigned flags, i;
- const char *const* idle_names;
-
- assert(client->idle_waiting);
- assert(client->idle_flags != 0);
-
- flags = client->idle_flags;
- client->idle_flags = 0;
- client->idle_waiting = false;
-
- idle_names = idle_get_names();
- for (i = 0; idle_names[i]; ++i) {
- if (flags & (1 << i) & client->idle_subscriptions)
- client_printf(client, "changed: %s\n",
- idle_names[i]);
- }
-
- client_puts(client, "OK\n");
- g_timer_start(client->last_activity);
-}
-
-static void
-client_idle_callback(gpointer data, gpointer user_data)
-{
- struct client *client = data;
- unsigned flags = GPOINTER_TO_UINT(user_data);
-
- if (client_is_expired(client))
- return;
-
- client->idle_flags |= flags;
- if (client->idle_waiting
- && (client->idle_flags & client->idle_subscriptions)) {
- client_idle_notify(client);
- client_write_output(client);
- }
-}
-
-void client_manager_idle_add(unsigned flags)
-{
- assert(flags != 0);
-
- g_list_foreach(clients, client_idle_callback, GUINT_TO_POINTER(flags));
-}
-
-bool client_idle_wait(struct client *client, unsigned flags)
-{
- assert(!client->idle_waiting);
-
- client->idle_waiting = true;
- client->idle_subscriptions = flags;
-
- if (client->idle_flags & client->idle_subscriptions) {
- client_idle_notify(client);
- return true;
- } else
- return false;
-}
diff --git a/src/client.h b/src/client.h
index 824497aba..d46747b4f 100644
--- a/src/client.h
+++ b/src/client.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
diff --git a/src/client_event.c b/src/client_event.c
new file mode 100644
index 000000000..93f5a9df7
--- /dev/null
+++ b/src/client_event.c
@@ -0,0 +1,108 @@
+/*
+ * 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 "client_internal.h"
+#include "main.h"
+
+#include <assert.h>
+
+static gboolean
+client_out_event(G_GNUC_UNUSED GIOChannel *source, GIOCondition condition,
+ gpointer data)
+{
+ struct client *client = data;
+
+ assert(!client_is_expired(client));
+
+ if (condition != G_IO_OUT) {
+ client_set_expired(client);
+ return false;
+ }
+
+ client_write_deferred(client);
+
+ if (client_is_expired(client)) {
+ client_close(client);
+ return false;
+ }
+
+ g_timer_start(client->last_activity);
+
+ if (g_queue_is_empty(client->deferred_send)) {
+ /* done sending deferred buffers exist: schedule
+ read */
+ client->source_id = g_io_add_watch(client->channel,
+ G_IO_IN|G_IO_ERR|G_IO_HUP,
+ client_in_event, client);
+ return false;
+ }
+
+ /* write more */
+ return true;
+}
+
+gboolean
+client_in_event(G_GNUC_UNUSED GIOChannel *source, GIOCondition condition,
+ gpointer data)
+{
+ struct client *client = data;
+ enum command_return ret;
+
+ assert(!client_is_expired(client));
+
+ if (condition != G_IO_IN) {
+ client_set_expired(client);
+ return false;
+ }
+
+ g_timer_start(client->last_activity);
+
+ ret = client_read(client);
+ switch (ret) {
+ case COMMAND_RETURN_OK:
+ case COMMAND_RETURN_ERROR:
+ break;
+
+ case COMMAND_RETURN_KILL:
+ client_close(client);
+ g_main_loop_quit(main_loop);
+ return false;
+
+ case COMMAND_RETURN_CLOSE:
+ client_close(client);
+ return false;
+ }
+
+ if (client_is_expired(client)) {
+ client_close(client);
+ return false;
+ }
+
+ if (!g_queue_is_empty(client->deferred_send)) {
+ /* deferred buffers exist: schedule write */
+ client->source_id = g_io_add_watch(client->channel,
+ G_IO_OUT|G_IO_ERR|G_IO_HUP,
+ client_out_event, client);
+ return false;
+ }
+
+ /* read more */
+ return true;
+}
diff --git a/src/client_expire.c b/src/client_expire.c
new file mode 100644
index 000000000..a5b0be047
--- /dev/null
+++ b/src/client_expire.c
@@ -0,0 +1,90 @@
+/*
+ * 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 "client_internal.h"
+
+static guint expire_source_id;
+
+void
+client_set_expired(struct client *client)
+{
+ if (!client_is_expired(client))
+ client_schedule_expire();
+
+ if (client->source_id != 0) {
+ g_source_remove(client->source_id);
+ client->source_id = 0;
+ }
+
+ if (client->channel != NULL) {
+ g_io_channel_unref(client->channel);
+ client->channel = NULL;
+ }
+}
+
+static void
+client_check_expired_callback(gpointer data, G_GNUC_UNUSED gpointer user_data)
+{
+ struct client *client = data;
+
+ if (client_is_expired(client)) {
+ g_debug("[%u] expired", client->num);
+ client_close(client);
+ } else if (!client->idle_waiting && /* idle clients
+ never expire */
+ (int)g_timer_elapsed(client->last_activity, NULL) >
+ client_timeout) {
+ g_debug("[%u] timeout", client->num);
+ client_close(client);
+ }
+}
+
+static void
+client_manager_expire(void)
+{
+ client_list_foreach(client_check_expired_callback, NULL);
+}
+
+/**
+ * An idle event which calls client_manager_expire().
+ */
+static gboolean
+client_manager_expire_event(G_GNUC_UNUSED gpointer data)
+{
+ expire_source_id = 0;
+ client_manager_expire();
+ return false;
+}
+
+void
+client_schedule_expire(void)
+{
+ if (expire_source_id == 0)
+ /* delayed deletion */
+ expire_source_id = g_idle_add(client_manager_expire_event,
+ NULL);
+}
+
+void
+client_deinit_expire(void)
+{
+ if (expire_source_id != 0)
+ g_source_remove(expire_source_id);
+}
diff --git a/src/client_global.c b/src/client_global.c
new file mode 100644
index 000000000..fc5adedba
--- /dev/null
+++ b/src/client_global.c
@@ -0,0 +1,73 @@
+/*
+ * 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 "client_internal.h"
+#include "conf.h"
+
+#include <assert.h>
+
+#define CLIENT_TIMEOUT_DEFAULT (60)
+#define CLIENT_MAX_CONNECTIONS_DEFAULT (10)
+#define CLIENT_MAX_COMMAND_LIST_DEFAULT (2048*1024)
+#define CLIENT_MAX_OUTPUT_BUFFER_SIZE_DEFAULT (8192*1024)
+
+/* set this to zero to indicate we have no possible clients */
+unsigned int client_max_connections;
+int client_timeout;
+size_t client_max_command_list_size;
+size_t client_max_output_buffer_size;
+
+void client_manager_init(void)
+{
+ client_timeout = config_get_positive(CONF_CONN_TIMEOUT,
+ CLIENT_TIMEOUT_DEFAULT);
+ client_max_connections =
+ config_get_positive(CONF_MAX_CONN,
+ CLIENT_MAX_CONNECTIONS_DEFAULT);
+ client_max_command_list_size =
+ config_get_positive(CONF_MAX_COMMAND_LIST_SIZE,
+ CLIENT_MAX_COMMAND_LIST_DEFAULT / 1024)
+ * 1024;
+
+ client_max_output_buffer_size =
+ config_get_positive(CONF_MAX_OUTPUT_BUFFER_SIZE,
+ CLIENT_MAX_OUTPUT_BUFFER_SIZE_DEFAULT / 1024)
+ * 1024;
+}
+
+static void client_close_all(void)
+{
+ while (!client_list_is_empty()) {
+ struct client *client = client_list_get_first();
+
+ client_close(client);
+ }
+
+ assert(client_list_is_empty());
+}
+
+void client_manager_deinit(void)
+{
+ client_close_all();
+
+ client_max_connections = 0;
+
+ client_deinit_expire();
+}
diff --git a/src/client_idle.c b/src/client_idle.c
new file mode 100644
index 000000000..10be4d430
--- /dev/null
+++ b/src/client_idle.c
@@ -0,0 +1,89 @@
+/*
+ * 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 "client_internal.h"
+#include "idle.h"
+
+#include <assert.h>
+
+/**
+ * Send "idle" response to this client.
+ */
+static void
+client_idle_notify(struct client *client)
+{
+ unsigned flags, i;
+ const char *const* idle_names;
+
+ assert(client->idle_waiting);
+ assert(client->idle_flags != 0);
+
+ flags = client->idle_flags;
+ client->idle_flags = 0;
+ client->idle_waiting = false;
+
+ idle_names = idle_get_names();
+ for (i = 0; idle_names[i]; ++i) {
+ if (flags & (1 << i) & client->idle_subscriptions)
+ client_printf(client, "changed: %s\n",
+ idle_names[i]);
+ }
+
+ client_puts(client, "OK\n");
+ g_timer_start(client->last_activity);
+}
+
+static void
+client_idle_callback(gpointer data, gpointer user_data)
+{
+ struct client *client = data;
+ unsigned flags = GPOINTER_TO_UINT(user_data);
+
+ if (client_is_expired(client))
+ return;
+
+ client->idle_flags |= flags;
+ if (client->idle_waiting
+ && (client->idle_flags & client->idle_subscriptions)) {
+ client_idle_notify(client);
+ client_write_output(client);
+ }
+}
+
+void client_manager_idle_add(unsigned flags)
+{
+ assert(flags != 0);
+
+ client_list_foreach(client_idle_callback, GUINT_TO_POINTER(flags));
+}
+
+bool client_idle_wait(struct client *client, unsigned flags)
+{
+ assert(!client->idle_waiting);
+
+ client->idle_waiting = true;
+ client->idle_subscriptions = flags;
+
+ if (client->idle_flags & client->idle_subscriptions) {
+ client_idle_notify(client);
+ return true;
+ } else
+ return false;
+}
diff --git a/src/client_internal.h b/src/client_internal.h
new file mode 100644
index 000000000..2b1b92433
--- /dev/null
+++ b/src/client_internal.h
@@ -0,0 +1,145 @@
+/*
+ * 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_CLIENT_INTERNAL_H
+#define MPD_CLIENT_INTERNAL_H
+
+#include "client.h"
+#include "command.h"
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "client"
+
+struct deferred_buffer {
+ size_t size;
+ char data[sizeof(long)];
+};
+
+struct client {
+ GIOChannel *channel;
+ guint source_id;
+
+ /** the buffer for reading lines from the #channel */
+ struct fifo_buffer *input;
+
+ unsigned permission;
+
+ /** the uid of the client process, or -1 if unknown */
+ int uid;
+
+ /**
+ * How long since the last activity from this client?
+ */
+ GTimer *last_activity;
+
+ GSList *cmd_list; /* for when in list mode */
+ int cmd_list_OK; /* print OK after each command execution */
+ size_t cmd_list_size; /* mem cmd_list consumes */
+ GQueue *deferred_send; /* for output if client is slow */
+ size_t deferred_bytes; /* mem deferred_send consumes */
+ unsigned int num; /* client number */
+
+ char send_buf[16384];
+ size_t send_buf_used; /* bytes used this instance */
+
+ /** is this client waiting for an "idle" response? */
+ bool idle_waiting;
+
+ /** idle flags pending on this client, to be sent as soon as
+ the client enters "idle" */
+ unsigned idle_flags;
+
+ /** idle flags that the client wants to receive */
+ unsigned idle_subscriptions;
+};
+
+extern unsigned int client_max_connections;
+extern int client_timeout;
+extern size_t client_max_command_list_size;
+extern size_t client_max_output_buffer_size;
+
+bool
+client_list_is_empty(void);
+
+bool
+client_list_is_full(void);
+
+struct client *
+client_list_get_first(void);
+
+void
+client_list_add(struct client *client);
+
+void
+client_list_foreach(GFunc func, gpointer user_data);
+
+void
+client_list_remove(struct client *client);
+
+void
+client_close(struct client *client);
+
+static inline void
+new_cmd_list_ptr(struct client *client, const char *s)
+{
+ client->cmd_list = g_slist_prepend(client->cmd_list, g_strdup(s));
+}
+
+static inline void
+free_cmd_list(GSList *list)
+{
+ for (GSList *tmp = list; tmp != NULL; tmp = g_slist_next(tmp))
+ g_free(tmp->data);
+
+ g_slist_free(list);
+}
+
+void
+client_set_expired(struct client *client);
+
+/**
+ * Schedule an "expired" check for all clients: permanently delete
+ * clients which have been set "expired" with client_set_expired().
+ */
+void
+client_schedule_expire(void);
+
+/**
+ * Removes a scheduled "expired" check.
+ */
+void
+client_deinit_expire(void);
+
+enum command_return
+client_read(struct client *client);
+
+enum command_return
+client_process_line(struct client *client, char *line);
+
+void
+client_write_deferred(struct client *client);
+
+void
+client_write_output(struct client *client);
+
+gboolean
+client_in_event(GIOChannel *source, GIOCondition condition,
+ gpointer data);
+
+#endif
diff --git a/src/client_list.c b/src/client_list.c
new file mode 100644
index 000000000..5332ed65f
--- /dev/null
+++ b/src/client_list.c
@@ -0,0 +1,69 @@
+/*
+ * 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 "client_internal.h"
+
+#include <assert.h>
+
+static GList *clients;
+static unsigned num_clients;
+
+bool
+client_list_is_empty(void)
+{
+ return num_clients == 0;
+}
+
+bool
+client_list_is_full(void)
+{
+ return num_clients >= client_max_connections;
+}
+
+struct client *
+client_list_get_first(void)
+{
+ assert(clients != NULL);
+
+ return clients->data;
+}
+
+void
+client_list_add(struct client *client)
+{
+ clients = g_list_prepend(clients, client);
+ ++num_clients;
+}
+
+void
+client_list_foreach(GFunc func, gpointer user_data)
+{
+ g_list_foreach(clients, func, user_data);
+}
+
+void
+client_list_remove(struct client *client)
+{
+ assert(num_clients > 0);
+ assert(clients != NULL);
+
+ clients = g_list_remove(clients, client);
+ --num_clients;
+}
diff --git a/src/client_new.c b/src/client_new.c
new file mode 100644
index 000000000..781a36524
--- /dev/null
+++ b/src/client_new.c
@@ -0,0 +1,159 @@
+/*
+ * 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 "client_internal.h"
+#include "fifo_buffer.h"
+#include "socket_util.h"
+#include "permission.h"
+
+#include <assert.h>
+#include <sys/types.h>
+#ifdef WIN32
+#include <winsock2.h>
+#else
+#include <sys/socket.h>
+#endif
+#include <unistd.h>
+
+#ifdef HAVE_LIBWRAP
+#include <tcpd.h>
+#endif
+
+
+#define LOG_LEVEL_SECURE G_LOG_LEVEL_INFO
+
+static const char GREETING[] = "OK MPD " PROTOCOL_VERSION "\n";
+
+void client_new(int fd, const struct sockaddr *sa, size_t sa_length, int uid)
+{
+ static unsigned int next_client_num;
+ struct client *client;
+ char *remote;
+
+ assert(fd >= 0);
+
+#ifdef HAVE_LIBWRAP
+ if (sa->sa_family != AF_UNIX) {
+ char *hostaddr = sockaddr_to_string(sa, sa_length, NULL);
+ const char *progname = g_get_prgname();
+
+ struct request_info req;
+ request_init(&req, RQ_FILE, fd, RQ_DAEMON, progname, 0);
+
+ fromhost(&req);
+
+ if (!hosts_access(&req)) {
+ /* tcp wrappers says no */
+ g_log(G_LOG_DOMAIN, LOG_LEVEL_SECURE,
+ "libwrap refused connection (libwrap=%s) from %s",
+ progname, hostaddr);
+
+ g_free(hostaddr);
+ close(fd);
+ return;
+ }
+
+ g_free(hostaddr);
+ }
+#endif /* HAVE_WRAP */
+
+ if (client_list_is_full()) {
+ g_warning("Max Connections Reached!");
+ close(fd);
+ return;
+ }
+
+ client = g_new0(struct client, 1);
+
+#ifndef G_OS_WIN32
+ client->channel = g_io_channel_unix_new(fd);
+#else
+ client->channel = g_io_channel_win32_new_socket(fd);
+#endif
+ /* GLib is responsible for closing the file descriptor */
+ g_io_channel_set_close_on_unref(client->channel, true);
+ /* NULL encoding means the stream is binary safe; the MPD
+ protocol is UTF-8 only, but we are doing this call anyway
+ to prevent GLib from messing around with the stream */
+ g_io_channel_set_encoding(client->channel, NULL, NULL);
+ /* we prefer to do buffering */
+ g_io_channel_set_buffered(client->channel, false);
+
+ client->source_id = g_io_add_watch(client->channel,
+ G_IO_IN|G_IO_ERR|G_IO_HUP,
+ client_in_event, client);
+
+ client->input = fifo_buffer_new(4096);
+
+ client->permission = getDefaultPermissions();
+ client->uid = uid;
+
+ client->last_activity = g_timer_new();
+
+ client->cmd_list = NULL;
+ client->cmd_list_OK = -1;
+ client->cmd_list_size = 0;
+
+ client->deferred_send = g_queue_new();
+ client->deferred_bytes = 0;
+ client->num = next_client_num++;
+
+ client->send_buf_used = 0;
+
+ (void)send(fd, GREETING, sizeof(GREETING) - 1, 0);
+
+ client_list_add(client);
+
+ remote = sockaddr_to_string(sa, sa_length, NULL);
+ g_log(G_LOG_DOMAIN, LOG_LEVEL_SECURE,
+ "[%u] opened from %s", client->num, remote);
+ g_free(remote);
+}
+
+static void
+deferred_buffer_free(gpointer data, G_GNUC_UNUSED gpointer user_data)
+{
+ struct deferred_buffer *buffer = data;
+ g_free(buffer);
+}
+
+void
+client_close(struct client *client)
+{
+ client_list_remove(client);
+
+ client_set_expired(client);
+
+ g_timer_destroy(client->last_activity);
+
+ if (client->cmd_list) {
+ free_cmd_list(client->cmd_list);
+ client->cmd_list = NULL;
+ }
+
+ g_queue_foreach(client->deferred_send, deferred_buffer_free, NULL);
+ g_queue_free(client->deferred_send);
+
+ fifo_buffer_free(client->input);
+
+ g_log(G_LOG_DOMAIN, LOG_LEVEL_SECURE,
+ "[%u] closed", client->num);
+ g_free(client);
+}
diff --git a/src/client_process.c b/src/client_process.c
new file mode 100644
index 000000000..aeb75bb57
--- /dev/null
+++ b/src/client_process.c
@@ -0,0 +1,146 @@
+/*
+ * 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 "client_internal.h"
+
+#include <string.h>
+
+#define CLIENT_LIST_MODE_BEGIN "command_list_begin"
+#define CLIENT_LIST_OK_MODE_BEGIN "command_list_ok_begin"
+#define CLIENT_LIST_MODE_END "command_list_end"
+
+static enum command_return
+client_process_command_list(struct client *client, bool list_ok, GSList *list)
+{
+ enum command_return ret = COMMAND_RETURN_OK;
+ unsigned num = 0;
+
+ for (GSList *cur = list; cur != NULL; cur = g_slist_next(cur)) {
+ char *cmd = cur->data;
+
+ g_debug("command_process_list: process command \"%s\"",
+ cmd);
+ ret = command_process(client, num++, cmd);
+ g_debug("command_process_list: command returned %i", ret);
+ if (ret != COMMAND_RETURN_OK || client_is_expired(client))
+ break;
+ else if (list_ok)
+ client_puts(client, "list_OK\n");
+ }
+
+ return ret;
+}
+
+enum command_return
+client_process_line(struct client *client, char *line)
+{
+ enum command_return ret;
+
+ if (strcmp(line, "noidle") == 0) {
+ if (client->idle_waiting) {
+ /* send empty idle response and leave idle mode */
+ client->idle_waiting = false;
+ command_success(client);
+ client_write_output(client);
+ }
+
+ /* do nothing if the client wasn't idling: the client
+ has already received the full idle response from
+ client_idle_notify(), which he can now evaluate */
+
+ return COMMAND_RETURN_OK;
+ } else if (client->idle_waiting) {
+ /* during idle mode, clients must not send anything
+ except "noidle" */
+ g_warning("[%u] command \"%s\" during idle",
+ client->num, line);
+ return COMMAND_RETURN_CLOSE;
+ }
+
+ if (client->cmd_list_OK >= 0) {
+ if (strcmp(line, CLIENT_LIST_MODE_END) == 0) {
+ g_debug("[%u] process command list",
+ client->num);
+
+ /* for scalability reasons, we have prepended
+ each new command; now we have to reverse it
+ to restore the correct order */
+ client->cmd_list = g_slist_reverse(client->cmd_list);
+
+ ret = client_process_command_list(client,
+ client->cmd_list_OK,
+ client->cmd_list);
+ g_debug("[%u] process command "
+ "list returned %i", client->num, ret);
+
+ if (ret == COMMAND_RETURN_CLOSE ||
+ client_is_expired(client))
+ return COMMAND_RETURN_CLOSE;
+
+ if (ret == COMMAND_RETURN_OK)
+ command_success(client);
+
+ client_write_output(client);
+ free_cmd_list(client->cmd_list);
+ client->cmd_list = NULL;
+ client->cmd_list_OK = -1;
+ } else {
+ size_t len = strlen(line) + 1;
+ client->cmd_list_size += len;
+ if (client->cmd_list_size >
+ client_max_command_list_size) {
+ g_warning("[%u] command list size (%lu) "
+ "is larger than the max (%lu)",
+ client->num,
+ (unsigned long)client->cmd_list_size,
+ (unsigned long)client_max_command_list_size);
+ return COMMAND_RETURN_CLOSE;
+ }
+
+ new_cmd_list_ptr(client, line);
+ ret = COMMAND_RETURN_OK;
+ }
+ } else {
+ if (strcmp(line, CLIENT_LIST_MODE_BEGIN) == 0) {
+ client->cmd_list_OK = 0;
+ ret = COMMAND_RETURN_OK;
+ } else if (strcmp(line, CLIENT_LIST_OK_MODE_BEGIN) == 0) {
+ client->cmd_list_OK = 1;
+ ret = COMMAND_RETURN_OK;
+ } else {
+ g_debug("[%u] process command \"%s\"",
+ client->num, line);
+ ret = command_process(client, 0, line);
+ g_debug("[%u] command returned %i",
+ client->num, ret);
+
+ if (ret == COMMAND_RETURN_CLOSE ||
+ client_is_expired(client))
+ return COMMAND_RETURN_CLOSE;
+
+ if (ret == COMMAND_RETURN_OK)
+ command_success(client);
+
+ client_write_output(client);
+ }
+ }
+
+ return ret;
+}
diff --git a/src/client_read.c b/src/client_read.c
new file mode 100644
index 000000000..7a6bd3d5e
--- /dev/null
+++ b/src/client_read.c
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "client_internal.h"
+#include "fifo_buffer.h"
+
+#include <assert.h>
+#include <string.h>
+
+static char *
+client_read_line(struct client *client)
+{
+ const char *p, *newline;
+ size_t length;
+ char *line;
+
+ p = fifo_buffer_read(client->input, &length);
+ if (p == NULL)
+ return NULL;
+
+ newline = memchr(p, '\n', length);
+ if (newline == NULL)
+ return NULL;
+
+ line = g_strndup(p, newline - p);
+ fifo_buffer_consume(client->input, newline - p + 1);
+
+ return g_strchomp(line);
+}
+
+static enum command_return
+client_input_received(struct client *client, size_t bytesRead)
+{
+ char *line;
+
+ fifo_buffer_append(client->input, bytesRead);
+
+ /* process all lines */
+
+ while ((line = client_read_line(client)) != NULL) {
+ enum command_return ret = client_process_line(client, line);
+ g_free(line);
+
+ if (ret == COMMAND_RETURN_KILL ||
+ ret == COMMAND_RETURN_CLOSE)
+ return ret;
+ if (client_is_expired(client))
+ return COMMAND_RETURN_CLOSE;
+ }
+
+ return COMMAND_RETURN_OK;
+}
+
+enum command_return
+client_read(struct client *client)
+{
+ char *p;
+ size_t max_length;
+ GError *error = NULL;
+ GIOStatus status;
+ gsize bytes_read;
+
+ assert(client != NULL);
+ assert(client->channel != NULL);
+
+ p = fifo_buffer_write(client->input, &max_length);
+ if (p == NULL) {
+ g_warning("[%u] buffer overflow", client->num);
+ return COMMAND_RETURN_CLOSE;
+ }
+
+ status = g_io_channel_read_chars(client->channel, p, max_length,
+ &bytes_read, &error);
+ switch (status) {
+ case G_IO_STATUS_NORMAL:
+ return client_input_received(client, bytes_read);
+
+ case G_IO_STATUS_AGAIN:
+ /* try again later, after select() */
+ return COMMAND_RETURN_OK;
+
+ case G_IO_STATUS_EOF:
+ /* peer disconnected */
+ return COMMAND_RETURN_CLOSE;
+
+ case G_IO_STATUS_ERROR:
+ /* I/O error */
+ g_warning("failed to read from client %d: %s",
+ client->num, error->message);
+ g_error_free(error);
+ return COMMAND_RETURN_CLOSE;
+ }
+
+ /* unreachable */
+ return COMMAND_RETURN_CLOSE;
+}
diff --git a/src/client_write.c b/src/client_write.c
new file mode 100644
index 000000000..543cdbb6c
--- /dev/null
+++ b/src/client_write.c
@@ -0,0 +1,284 @@
+/*
+ * 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 "client_internal.h"
+
+#include <assert.h>
+#include <string.h>
+#include <stdio.h>
+
+static size_t
+client_write_deferred_buffer(struct client *client,
+ const struct deferred_buffer *buffer)
+{
+ GError *error = NULL;
+ GIOStatus status;
+ gsize bytes_written;
+
+ assert(client != NULL);
+ assert(client->channel != NULL);
+ assert(buffer != NULL);
+
+ status = g_io_channel_write_chars
+ (client->channel, buffer->data, buffer->size,
+ &bytes_written, &error);
+ switch (status) {
+ case G_IO_STATUS_NORMAL:
+ return bytes_written;
+
+ case G_IO_STATUS_AGAIN:
+ return 0;
+
+ case G_IO_STATUS_EOF:
+ /* client has disconnected */
+
+ client_set_expired(client);
+ return 0;
+
+ case G_IO_STATUS_ERROR:
+ /* I/O error */
+
+ client_set_expired(client);
+ g_warning("failed to flush buffer for %i: %s",
+ client->num, error->message);
+ g_error_free(error);
+ return 0;
+ }
+
+ /* unreachable */
+ return 0;
+}
+
+void
+client_write_deferred(struct client *client)
+{
+ size_t ret;
+
+ while (!g_queue_is_empty(client->deferred_send)) {
+ struct deferred_buffer *buf =
+ g_queue_peek_head(client->deferred_send);
+
+ assert(buf->size > 0);
+ assert(buf->size <= client->deferred_bytes);
+
+ ret = client_write_deferred_buffer(client, buf);
+ if (ret == 0)
+ break;
+
+ if (ret < buf->size) {
+ assert(client->deferred_bytes >= (size_t)ret);
+ client->deferred_bytes -= ret;
+ buf->size -= ret;
+ memmove(buf->data, buf->data + ret, buf->size);
+ break;
+ } else {
+ size_t decr = sizeof(*buf) -
+ sizeof(buf->data) + buf->size;
+
+ assert(client->deferred_bytes >= decr);
+ client->deferred_bytes -= decr;
+ g_free(buf);
+ g_queue_pop_head(client->deferred_send);
+ }
+
+ g_timer_start(client->last_activity);
+ }
+
+ if (g_queue_is_empty(client->deferred_send)) {
+ g_debug("[%u] buffer empty %lu", client->num,
+ (unsigned long)client->deferred_bytes);
+ assert(client->deferred_bytes == 0);
+ }
+}
+
+static void client_defer_output(struct client *client,
+ const void *data, size_t length)
+{
+ size_t alloc;
+ struct deferred_buffer *buf;
+
+ assert(length > 0);
+
+ alloc = sizeof(*buf) - sizeof(buf->data) + length;
+ client->deferred_bytes += alloc;
+ if (client->deferred_bytes > client_max_output_buffer_size) {
+ g_warning("[%u] output buffer size (%lu) is "
+ "larger than the max (%lu)",
+ client->num,
+ (unsigned long)client->deferred_bytes,
+ (unsigned long)client_max_output_buffer_size);
+ /* cause client to close */
+ client_set_expired(client);
+ return;
+ }
+
+ buf = g_malloc(alloc);
+ buf->size = length;
+ memcpy(buf->data, data, length);
+
+ g_queue_push_tail(client->deferred_send, buf);
+}
+
+static void client_write_direct(struct client *client,
+ const char *data, size_t length)
+{
+ GError *error = NULL;
+ GIOStatus status;
+ gsize bytes_written;
+
+ assert(client != NULL);
+ assert(client->channel != NULL);
+ assert(data != NULL);
+ assert(length > 0);
+ assert(g_queue_is_empty(client->deferred_send));
+
+ status = g_io_channel_write_chars(client->channel, data, length,
+ &bytes_written, &error);
+ switch (status) {
+ case G_IO_STATUS_NORMAL:
+ case G_IO_STATUS_AGAIN:
+ break;
+
+ case G_IO_STATUS_EOF:
+ /* client has disconnected */
+
+ client_set_expired(client);
+ return;
+
+ case G_IO_STATUS_ERROR:
+ /* I/O error */
+
+ client_set_expired(client);
+ g_warning("failed to write to %i: %s",
+ client->num, error->message);
+ g_error_free(error);
+ return;
+ }
+
+ if (bytes_written < length)
+ client_defer_output(client, data + bytes_written,
+ length - bytes_written);
+
+ if (!g_queue_is_empty(client->deferred_send))
+ g_debug("[%u] buffer created", client->num);
+}
+
+void
+client_write_output(struct client *client)
+{
+ if (client_is_expired(client) || !client->send_buf_used)
+ return;
+
+ if (!g_queue_is_empty(client->deferred_send)) {
+ client_defer_output(client, client->send_buf,
+ client->send_buf_used);
+
+ if (client_is_expired(client))
+ return;
+
+ /* try to flush the deferred buffers now; the current
+ server command may take too long to finish, and
+ meanwhile try to feed output to the client,
+ otherwise it will time out. One reason why
+ deferring is slow might be that currently each
+ client_write() allocates a new deferred buffer.
+ This should be optimized after MPD 0.14. */
+ client_write_deferred(client);
+ } else
+ client_write_direct(client, client->send_buf,
+ client->send_buf_used);
+
+ client->send_buf_used = 0;
+}
+
+/**
+ * Write a block of data to the client.
+ */
+static void client_write(struct client *client, const char *buffer, size_t buflen)
+{
+ /* if the client is going to be closed, do nothing */
+ if (client_is_expired(client))
+ return;
+
+ while (buflen > 0 && !client_is_expired(client)) {
+ size_t copylen;
+
+ assert(client->send_buf_used < sizeof(client->send_buf));
+
+ copylen = sizeof(client->send_buf) - client->send_buf_used;
+ if (copylen > buflen)
+ copylen = buflen;
+
+ memcpy(client->send_buf + client->send_buf_used, buffer,
+ copylen);
+ buflen -= copylen;
+ client->send_buf_used += copylen;
+ buffer += copylen;
+ if (client->send_buf_used >= sizeof(client->send_buf))
+ client_write_output(client);
+ }
+}
+
+void client_puts(struct client *client, const char *s)
+{
+ client_write(client, s, strlen(s));
+}
+
+void client_vprintf(struct client *client, const char *fmt, va_list args)
+{
+#ifndef G_OS_WIN32
+ va_list tmp;
+ int length;
+ char *buffer;
+
+ va_copy(tmp, args);
+ length = vsnprintf(NULL, 0, fmt, tmp);
+ va_end(tmp);
+
+ if (length <= 0)
+ /* wtf.. */
+ return;
+
+ buffer = g_malloc(length + 1);
+ vsnprintf(buffer, length + 1, fmt, args);
+ client_write(client, buffer, length);
+ g_free(buffer);
+#else
+ /* On mingw32, snprintf() expects a 64 bit integer instead of
+ a "long int" for "%li". This is not consistent with our
+ expectation, so we're using plain sprintf() here, hoping
+ the static buffer is large enough. Sorry for this hack,
+ but WIN32 development is so painful, I'm not in the mood to
+ do it properly now. */
+
+ static char buffer[4096];
+ vsprintf(buffer, fmt, args);
+ client_write(client, buffer, strlen(buffer));
+#endif
+}
+
+G_GNUC_PRINTF(2, 3) void client_printf(struct client *client, const char *fmt, ...)
+{
+ va_list args;
+
+ va_start(args, fmt);
+ client_vprintf(client, fmt, args);
+ va_end(args);
+}
diff --git a/src/cmdline.c b/src/cmdline.c
index e0274ef36..2c1db890b 100644
--- a/src/cmdline.c
+++ b/src/cmdline.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -17,14 +17,20 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "cmdline.h"
#include "path.h"
#include "log.h"
#include "conf.h"
#include "decoder_list.h"
-#include "config.h"
+#include "decoder_plugin.h"
#include "output_list.h"
#include "ls.h"
+#include "mpd_error.h"
+
+#ifdef ENABLE_ENCODER
+#include "encoder_list.h"
+#endif
#ifdef ENABLE_ARCHIVE
#include "archive_list.h"
@@ -35,9 +41,37 @@
#include <stdio.h>
#include <stdlib.h>
-#define SYSTEM_CONFIG_FILE_LOCATION "/etc/mpd.conf"
+#ifdef G_OS_WIN32
+#define CONFIG_FILE_LOCATION "\\mpd\\mpd.conf"
+#else /* G_OS_WIN32 */
#define USER_CONFIG_FILE_LOCATION1 ".mpdconf"
#define USER_CONFIG_FILE_LOCATION2 ".mpd/mpd.conf"
+#endif
+
+static GQuark
+cmdline_quark(void)
+{
+ return g_quark_from_static_string("cmdline");
+}
+
+static void
+print_all_decoders(FILE *fp)
+{
+ for (unsigned i = 0; decoder_plugins[i] != NULL; ++i) {
+ const struct decoder_plugin *plugin = decoder_plugins[i];
+ const char *const*suffixes;
+
+ fprintf(fp, "[%s]", plugin->name);
+
+ for (suffixes = plugin->suffixes;
+ suffixes != NULL && *suffixes != NULL;
+ ++suffixes) {
+ fprintf(fp, " %s", *suffixes);
+ }
+
+ fprintf(fp, "\n");
+ }
+}
G_GNUC_NORETURN
static void version(void)
@@ -45,19 +79,25 @@ 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 Max Kellermann <max@duempel.org>\n"
+ "Copyright (C) 2008-2010 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"
"Supported decoders:\n");
- decoder_plugin_init_all();
- decoder_plugin_print_all_decoders(stdout);
+ print_all_decoders(stdout);
puts("\n"
"Supported outputs:\n");
audio_output_plugin_print_all_types(stdout);
+#ifdef ENABLE_ENCODER
+ puts("\n"
+ "Supported encoders:\n");
+ encoder_plugin_print_all_types(stdout);
+#endif
+
+
#ifdef ENABLE_ARCHIVE
puts("\n"
"Supported archives:\n");
@@ -72,31 +112,29 @@ static void version(void)
exit(EXIT_SUCCESS);
}
-#if GLIB_CHECK_VERSION(2,12,0)
static const char *summary =
"Music Player Daemon - a daemon for playing music.";
-#endif
-void parseOptions(int argc, char **argv, Options *options)
+bool
+parse_cmdline(int argc, char **argv, struct options *options,
+ GError **error_r)
{
GError *error = NULL;
GOptionContext *context;
bool ret;
static gboolean option_version,
- option_create_db, option_no_create_db, option_no_daemon,
+ option_no_daemon,
option_no_config;
const GOptionEntry entries[] = {
- { "create-db", 0, 0, G_OPTION_ARG_NONE, &option_create_db,
- "force (re)creation of database", NULL },
{ "kill", 0, 0, G_OPTION_ARG_NONE, &options->kill,
"kill the currently running mpd session", NULL },
{ "no-config", 0, 0, G_OPTION_ARG_NONE, &option_no_config,
"don't read from config", NULL },
- { "no-create-db", 0, 0, G_OPTION_ARG_NONE, &option_no_create_db,
- "don't create database, even if it doesn't exist", NULL },
{ "no-daemon", 0, 0, G_OPTION_ARG_NONE, &option_no_daemon,
"don't detach from console", NULL },
- { "stdout", 0, 0, G_OPTION_ARG_NONE, &options->stdOutput,
+ { "stdout", 0, 0, G_OPTION_ARG_NONE, &options->log_stderr,
+ NULL, NULL },
+ { "stderr", 0, 0, G_OPTION_ARG_NONE, &options->log_stderr,
"print messages to stderr", NULL },
{ "verbose", 'v', 0, G_OPTION_ARG_NONE, &options->verbose,
"verbose logging", NULL },
@@ -107,24 +145,19 @@ void parseOptions(int argc, char **argv, Options *options)
options->kill = false;
options->daemon = true;
- options->stdOutput = false;
+ options->log_stderr = false;
options->verbose = false;
- options->createDB = 0;
context = g_option_context_new("[path/to/mpd.conf]");
g_option_context_add_main_entries(context, entries, NULL);
-#if GLIB_CHECK_VERSION(2,12,0)
g_option_context_set_summary(context, summary);
-#endif
ret = g_option_context_parse(context, &argc, &argv, &error);
g_option_context_free(context);
- if (!ret) {
- g_error("option parsing failed: %s\n", error->message);
- exit(1);
- }
+ if (!ret)
+ MPD_ERROR("option parsing failed: %s\n", error->message);
if (option_version)
version();
@@ -133,39 +166,71 @@ void parseOptions(int argc, char **argv, Options *options)
parser can use it already */
log_early_init(options->verbose);
- if (option_create_db && option_no_create_db)
- g_error("Cannot use both --create-db and --no-create-db\n");
-
- if (option_no_create_db)
- options->createDB = -1;
- else if (option_create_db)
- options->createDB = 1;
-
options->daemon = !option_no_daemon;
if (option_no_config) {
g_debug("Ignoring config, using daemon defaults\n");
+ return true;
} else if (argc <= 1) {
/* default configuration file path */
char *path1;
- char *path2;
+#ifdef G_OS_WIN32
+ path1 = g_build_filename(g_get_user_config_dir(),
+ CONFIG_FILE_LOCATION, NULL);
+ if (g_file_test(path1, G_FILE_TEST_IS_REGULAR))
+ ret = config_read_file(path1, error_r);
+ else {
+ int i = 0;
+ char *system_path = NULL;
+ const char * const *system_config_dirs;
+
+ system_config_dirs = g_get_system_config_dirs();
+
+ while(system_config_dirs[i] != NULL) {
+ system_path = g_build_filename(system_config_dirs[i],
+ CONFIG_FILE_LOCATION,
+ NULL);
+ if(g_file_test(system_path,
+ G_FILE_TEST_IS_REGULAR)) {
+ ret = config_read_file(system_path,error_r);
+ g_free(system_path);
+ g_free(&system_config_dirs);
+ break;
+ }
+ ++i;;
+ }
+ g_free(system_path);
+ g_free(&system_config_dirs);
+ }
+#else /* G_OS_WIN32 */
+ char *path2;
path1 = g_build_filename(g_get_home_dir(),
USER_CONFIG_FILE_LOCATION1, NULL);
path2 = g_build_filename(g_get_home_dir(),
USER_CONFIG_FILE_LOCATION2, NULL);
if (g_file_test(path1, G_FILE_TEST_IS_REGULAR))
- config_read_file(path1);
+ ret = config_read_file(path1, error_r);
else if (g_file_test(path2, G_FILE_TEST_IS_REGULAR))
- config_read_file(path2);
+ ret = config_read_file(path2, error_r);
else if (g_file_test(SYSTEM_CONFIG_FILE_LOCATION,
G_FILE_TEST_IS_REGULAR))
- config_read_file(SYSTEM_CONFIG_FILE_LOCATION);
+ ret = config_read_file(SYSTEM_CONFIG_FILE_LOCATION,
+ error_r);
+#endif
+
g_free(path1);
+#ifndef G_OS_WIN32
g_free(path2);
+#endif
+
+ return ret;
} else if (argc == 2) {
/* specified configuration file */
- config_read_file(argv[1]);
- } else
- g_error("too many arguments");
+ return config_read_file(argv[1], error_r);
+ } else {
+ g_set_error(error_r, cmdline_quark(), 0,
+ "too many arguments");
+ return false;
+ }
}
diff --git a/src/cmdline.h b/src/cmdline.h
index 673701055..b7af63c5a 100644
--- a/src/cmdline.h
+++ b/src/cmdline.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -22,14 +22,17 @@
#include <glib.h>
-typedef struct _Options {
+#include <stdbool.h>
+
+struct options {
gboolean kill;
gboolean daemon;
- gboolean stdOutput;
+ gboolean log_stderr;
gboolean verbose;
- int createDB;
-} Options;
+};
-void parseOptions(int argc, char **argv, Options *options);
+bool
+parse_cmdline(int argc, char **argv, struct options *options,
+ GError **error_r);
#endif
diff --git a/src/command.c b/src/command.c
index dd812df50..64f161805 100644
--- a/src/command.c
+++ b/src/command.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -17,14 +17,17 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "command.h"
#include "player_control.h"
#include "playlist.h"
#include "playlist_print.h"
#include "playlist_save.h"
+#include "playlist_queue.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"
@@ -32,7 +35,7 @@
#include "volume.h"
#include "stats.h"
#include "permission.h"
-#include "buffer2array.h"
+#include "tokenizer.h"
#include "stored_playlist.h"
#include "ack.h"
#include "output_command.h"
@@ -43,8 +46,8 @@
#include "client.h"
#include "tag_print.h"
#include "path.h"
+#include "replay_gain_config.h"
#include "idle.h"
-#include "config.h"
#ifdef ENABLE_SQLITE
#include "sticker.h"
@@ -58,7 +61,6 @@
#include <stdlib.h>
#include <errno.h>
-#define COMMAND_STATUS_VOLUME "volume"
#define COMMAND_STATUS_STATE "state"
#define COMMAND_STATUS_REPEAT "repeat"
#define COMMAND_STATUS_SINGLE "single"
@@ -74,6 +76,8 @@
#define COMMAND_STATUS_BITRATE "bitrate"
#define COMMAND_STATUS_ERROR "error"
#define COMMAND_STATUS_CROSSFADE "xfade"
+#define COMMAND_STATUS_MIXRAMPDB "mixrampdb"
+#define COMMAND_STATUS_MIXRAMPDELAY "mixrampdelay"
#define COMMAND_STATUS_AUDIO "audio"
#define COMMAND_STATUS_UPDATING_DB "updating_db"
@@ -166,8 +170,8 @@ check_int(struct client *client, int *value_r,
return false;
}
-#if LONG_MAX > INT_MAX
- if (value < INT_MIN || value > INT_MAX) {
+#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;
@@ -198,7 +202,7 @@ check_range(struct client *client, unsigned *value_r1, unsigned *value_r2,
/* compatibility with older MPD versions: specifying
"-1" makes MPD display the whole list */
*value_r1 = 0;
- *value_r2 = UINT_MAX;
+ *value_r2 = G_MAXUINT;
return true;
}
@@ -208,8 +212,8 @@ check_range(struct client *client, unsigned *value_r1, unsigned *value_r2,
return false;
}
-#if LONG_MAX > UINT_MAX
- if (value > UINT_MAX) {
+#if G_MAXLONG > G_MAXUINT
+ if (value > G_MAXUINT) {
command_error(client, ACK_ERROR_ARG,
"Number too large: %s", s);
return false;
@@ -220,7 +224,7 @@ check_range(struct client *client, unsigned *value_r1, unsigned *value_r2,
if (*test == ':') {
value = strtol(++test, &test2, 10);
- if (*test2 != '\0' || test == test2) {
+ if (*test2 != '\0') {
va_list args;
va_start(args, fmt);
command_error_v(client, ACK_ERROR_ARG, fmt, args);
@@ -228,14 +232,17 @@ check_range(struct client *client, unsigned *value_r1, unsigned *value_r2,
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 LONG_MAX > UINT_MAX
- if (value > UINT_MAX) {
+#if G_MAXLONG > G_MAXUINT
+ if (value > G_MAXUINT) {
command_error(client, ACK_ERROR_ARG,
"Number too large: %s", s);
return false;
@@ -262,7 +269,7 @@ check_unsigned(struct client *client, unsigned *value_r, const char *s)
return false;
}
- if (value > UINT_MAX) {
+ if (value > G_MAXUINT) {
command_error(client, ACK_ERROR_ARG,
"Number too large: %s", s);
return false;
@@ -289,6 +296,23 @@ check_bool(struct client *client, bool *value_r, const char *s)
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)
@@ -363,10 +387,12 @@ print_spl_list(struct client *client, GPtrArray *list)
client_printf(client, "playlist: %s\n", playlist->name);
t = playlist->mtime;
- strftime(timestamp, sizeof(timestamp), "%FT%TZ",
-#ifdef WIN32
+ strftime(timestamp, sizeof(timestamp),
+#ifdef G_OS_WIN32
+ "%Y-%m-%dT%H:%M:%SZ",
gmtime(&t)
#else
+ "%FT%TZ",
gmtime_r(&t, &tm)
#endif
);
@@ -385,6 +411,14 @@ handle_urlhandlers(struct client *client,
}
static enum command_return
+handle_decoders(struct client *client,
+ G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[])
+{
+ decoder_list_print(client);
+ return COMMAND_RETURN_OK;
+}
+
+static enum command_return
handle_tagtypes(struct client *client,
G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[])
{
@@ -400,7 +434,7 @@ handle_play(struct client *client, int argc, char *argv[])
if (argc == 2 && !check_int(client, &song, argv[1], need_positive))
return COMMAND_RETURN_ERROR;
- result = playPlaylist(&g_playlist, song);
+ result = playlist_play(&g_playlist, song);
return print_playlist_result(client, result);
}
@@ -413,7 +447,7 @@ handle_playid(struct client *client, int argc, char *argv[])
if (argc == 2 && !check_int(client, &id, argv[1], need_positive))
return COMMAND_RETURN_ERROR;
- result = playPlaylistById(&g_playlist, id);
+ result = playlist_play_id(&g_playlist, id);
return print_playlist_result(client, result);
}
@@ -421,7 +455,7 @@ static enum command_return
handle_stop(G_GNUC_UNUSED struct client *client,
G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[])
{
- stopPlaylist(&g_playlist);
+ playlist_stop(&g_playlist);
return COMMAND_RETURN_OK;
}
@@ -441,11 +475,11 @@ handle_pause(struct client *client,
bool pause_flag;
if (!check_bool(client, &pause_flag, argv[1]))
return COMMAND_RETURN_ERROR;
- playerSetPause(pause_flag);
- return COMMAND_RETURN_OK;
- }
- playerPause();
+ pc_set_pause(pause_flag);
+ } else
+ pc_pause();
+
return COMMAND_RETURN_OK;
}
@@ -454,10 +488,14 @@ handle_status(struct client *client,
G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[])
{
const char *state = NULL;
+ struct player_status player_status;
int updateJobId;
+ char *error;
int song;
- switch (getPlayerState()) {
+ pc_get_status(&player_status);
+
+ switch (player_status.state) {
case PLAYER_STATE_STOP:
state = "stop";
break;
@@ -470,7 +508,7 @@ handle_status(struct client *client,
}
client_printf(client,
- COMMAND_STATUS_VOLUME ": %i\n"
+ "volume: %i\n"
COMMAND_STATUS_REPEAT ": %i\n"
COMMAND_STATUS_RANDOM ": %i\n"
COMMAND_STATUS_SINGLE ": %i\n"
@@ -478,34 +516,43 @@ handle_status(struct client *client,
COMMAND_STATUS_PLAYLIST ": %li\n"
COMMAND_STATUS_PLAYLIST_LENGTH ": %i\n"
COMMAND_STATUS_CROSSFADE ": %i\n"
+ COMMAND_STATUS_MIXRAMPDB ": %f\n"
+ COMMAND_STATUS_MIXRAMPDELAY ": %f\n"
COMMAND_STATUS_STATE ": %s\n",
volume_level_get(),
- getPlaylistRepeatStatus(&g_playlist),
- getPlaylistRandomStatus(&g_playlist),
- getPlaylistSingleStatus(&g_playlist),
- getPlaylistConsumeStatus(&g_playlist),
- getPlaylistVersion(&g_playlist),
- getPlaylistLength(&g_playlist),
- (int)(getPlayerCrossFade() + 0.5),
+ playlist_get_repeat(&g_playlist),
+ playlist_get_random(&g_playlist),
+ playlist_get_single(&g_playlist),
+ 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(),
state);
- song = getPlaylistCurrentSong(&g_playlist);
+ song = playlist_get_current_song(&g_playlist);
if (song >= 0) {
client_printf(client,
COMMAND_STATUS_SONG ": %i\n"
COMMAND_STATUS_SONGID ": %u\n",
- song, getPlaylistSongId(&g_playlist, song));
+ song, playlist_get_song_id(&g_playlist, song));
}
- if (getPlayerState() != PLAYER_STATE_STOP) {
- const struct audio_format *af = player_get_audio_format();
+ if (player_status.state != PLAYER_STATE_STOP) {
+ struct audio_format_string af_string;
+
client_printf(client,
COMMAND_STATUS_TIME ": %i:%i\n"
- COMMAND_STATUS_BITRATE ": %li\n"
- COMMAND_STATUS_AUDIO ": %u:%u:%u\n",
- getPlayerElapsedTime(), getPlayerTotalTime(),
- getPlayerBitRate(),
- af->sample_rate, af->bits, af->channels);
+ "elapsed: %1.3f\n"
+ COMMAND_STATUS_BITRATE ": %u\n"
+ COMMAND_STATUS_AUDIO ": %s\n",
+ (int)(player_status.elapsed_time + 0.5),
+ (int)(player_status.total_time + 0.5),
+ player_status.elapsed_time,
+ player_status.bit_rate,
+ audio_format_to_string(&player_status.audio_format,
+ &af_string));
}
if ((updateJobId = isUpdatingDB())) {
@@ -514,18 +561,20 @@ handle_status(struct client *client,
updateJobId);
}
- if (getPlayerError() != PLAYER_ERROR_NOERROR) {
+ error = pc_get_error_message();
+ if (error != NULL) {
client_printf(client,
COMMAND_STATUS_ERROR ": %s\n",
- getPlayerErrorStr());
+ error);
+ g_free(error);
}
- song = getPlaylistNextSong(&g_playlist);
+ song = playlist_get_next_song(&g_playlist);
if (song >= 0) {
client_printf(client,
COMMAND_STATUS_NEXTSONG ": %i\n"
COMMAND_STATUS_NEXTSONGID ": %u\n",
- song, getPlaylistSongId(&g_playlist, song));
+ song, playlist_get_song_id(&g_playlist, song));
}
return COMMAND_RETURN_OK;
@@ -569,7 +618,7 @@ handle_add(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
return COMMAND_RETURN_ERROR;
}
- result = addToPlaylist(&g_playlist, uri, NULL);
+ result = playlist_append_uri(&g_playlist, uri, NULL);
return print_playlist_result(client, result);
}
@@ -605,7 +654,7 @@ handle_addid(struct client *client, int argc, char *argv[])
return COMMAND_RETURN_ERROR;
}
- result = addToPlaylist(&g_playlist, uri, &added_id);
+ result = playlist_append_uri(&g_playlist, uri, &added_id);
}
if (result != PLAYLIST_RESULT_SUCCESS)
@@ -615,11 +664,11 @@ handle_addid(struct client *client, int argc, char *argv[])
int to;
if (!check_int(client, &to, argv[2], check_integer, argv[2]))
return COMMAND_RETURN_ERROR;
- result = moveSongInPlaylistById(&g_playlist, added_id, to);
+ result = playlist_move_id(&g_playlist, added_id, to);
if (result != PLAYLIST_RESULT_SUCCESS) {
enum command_return ret =
print_playlist_result(client, result);
- deleteFromPlaylistById(&g_playlist, added_id);
+ playlist_delete_id(&g_playlist, added_id);
return ret;
}
}
@@ -631,13 +680,13 @@ handle_addid(struct client *client, int argc, char *argv[])
static enum command_return
handle_delete(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
{
- int song;
+ unsigned start, end;
enum playlist_result result;
- if (!check_int(client, &song, argv[1], need_positive))
+ if (!check_range(client, &start, &end, argv[1], need_range))
return COMMAND_RETURN_ERROR;
- result = deleteFromPlaylist(&g_playlist, song);
+ result = playlist_delete_range(&g_playlist, start, end);
return print_playlist_result(client, result);
}
@@ -650,7 +699,7 @@ handle_deleteid(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
if (!check_int(client, &id, argv[1], need_positive))
return COMMAND_RETURN_ERROR;
- result = deleteFromPlaylistById(&g_playlist, id);
+ result = playlist_delete_id(&g_playlist, id);
return print_playlist_result(client, result);
}
@@ -671,7 +720,7 @@ handle_shuffle(G_GNUC_UNUSED struct client *client,
argv[1], need_range))
return COMMAND_RETURN_ERROR;
- shufflePlaylist(&g_playlist, start, end);
+ playlist_shuffle(&g_playlist, start, end);
return COMMAND_RETURN_OK;
}
@@ -679,7 +728,7 @@ static enum command_return
handle_clear(G_GNUC_UNUSED struct client *client,
G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[])
{
- clearPlaylist(&g_playlist);
+ playlist_clear(&g_playlist);
return COMMAND_RETURN_OK;
}
@@ -698,6 +747,10 @@ handle_load(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
{
enum playlist_result result;
+ result = playlist_open_into_queue(argv[1], &g_playlist);
+ 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);
}
@@ -705,6 +758,9 @@ handle_load(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
static enum command_return
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);
@@ -720,6 +776,9 @@ static enum command_return
handle_listplaylistinfo(struct client *client,
G_GNUC_UNUSED int argc, char *argv[])
{
+ if (playlist_file_print(client, argv[1], true))
+ return COMMAND_RETURN_OK;
+
bool ret;
ret = spl_print(client, argv[1], true);
@@ -808,7 +867,7 @@ handle_plchangesposid(struct client *client, G_GNUC_UNUSED int argc, char *argv[
static enum command_return
handle_playlistinfo(struct client *client, int argc, char *argv[])
{
- unsigned start = 0, end = UINT_MAX;
+ unsigned start = 0, end = G_MAXUINT;
bool ret;
if (argc == 2 && !check_range(client, &start, &end,
@@ -837,7 +896,7 @@ handle_playlistid(struct client *client, int argc, char *argv[])
return print_playlist_result(client,
PLAYLIST_RESULT_NO_SUCH_SONG);
} else {
- playlist_print_info(client, &g_playlist, 0, UINT_MAX);
+ playlist_print_info(client, &g_playlist, 0, G_MAXUINT);
}
return COMMAND_RETURN_OK;
@@ -869,6 +928,30 @@ 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) {
+ if (list != NULL)
+ locate_item_list_free(list);
+
+ command_error(client, ACK_ERROR_ARG, "incorrect arguments");
+ return COMMAND_RETURN_ERROR;
+ }
+
+ ret = findAddIn(client, NULL, list);
+ if (ret == -1)
+ command_error(client, ACK_ERROR_NO_EXIST,
+ "directory or file not found");
+
+ locate_item_list_free(list);
+
+ return ret;
+}
+
+static enum command_return
handle_search(struct client *client, int argc, char *argv[])
{
int ret;
@@ -993,14 +1076,52 @@ handle_playlistmove(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
static enum command_return
handle_update(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
{
- char *path = NULL;
+ const char *path = NULL;
unsigned ret;
assert(argc <= 2);
- if (argc == 2)
- path = g_strdup(argv[1]);
+ if (argc == 2) {
+ path = argv[1];
+
+ if (*path == 0 || strcmp(path, "/") == 0)
+ /* backwards compatibility with MPD 0.15 */
+ path = NULL;
+ else if (!uri_safe_local(path)) {
+ command_error(client, ACK_ERROR_ARG,
+ "Malformed path");
+ return COMMAND_RETURN_ERROR;
+ }
+ }
- ret = directory_update_init(path);
+ ret = update_enqueue(path, false);
+ if (ret > 0) {
+ client_printf(client, "updating_db: %i\n", ret);
+ return COMMAND_RETURN_OK;
+ } else {
+ command_error(client, ACK_ERROR_UPDATE_ALREADY,
+ "already updating");
+ return COMMAND_RETURN_ERROR;
+ }
+}
+
+static enum command_return
+handle_rescan(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
+{
+ const char *path = NULL;
+ unsigned ret;
+
+ assert(argc <= 2);
+ if (argc == 2) {
+ path = argv[1];
+
+ if (!uri_safe_local(path)) {
+ command_error(client, ACK_ERROR_ARG,
+ "Malformed path");
+ return COMMAND_RETURN_ERROR;
+ }
+ }
+
+ ret = update_enqueue(path, true);
if (ret > 0) {
client_printf(client, "updating_db: %i\n", ret);
return COMMAND_RETURN_OK;
@@ -1020,7 +1141,7 @@ handle_next(G_GNUC_UNUSED struct client *client,
int single = g_playlist.queue.single;
g_playlist.queue.single = false;
- nextSongInPlaylist(&g_playlist);
+ playlist_next(&g_playlist);
g_playlist.queue.single = single;
return COMMAND_RETURN_OK;
@@ -1030,7 +1151,7 @@ static enum command_return
handle_previous(G_GNUC_UNUSED struct client *client,
G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[])
{
- previousSongInPlaylist(&g_playlist);
+ playlist_previous(&g_playlist);
return COMMAND_RETURN_OK;
}
@@ -1052,25 +1173,6 @@ handle_listall(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
}
static enum command_return
-handle_volume(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
-{
- int change;
- bool success;
-
- if (!check_int(client, &change, argv[1], need_integer))
- return COMMAND_RETURN_ERROR;
-
- success = volume_level_change(change, true);
- if (!success) {
- command_error(client, ACK_ERROR_SYSTEM,
- "problems setting volume");
- return COMMAND_RETURN_ERROR;
- }
-
- return COMMAND_RETURN_OK;
-}
-
-static enum command_return
handle_setvol(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
{
int level;
@@ -1079,7 +1181,12 @@ handle_setvol(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
if (!check_int(client, &level, argv[1], need_integer))
return COMMAND_RETURN_ERROR;
- success = volume_level_change(level, 0);
+ if (level < 0 || level > 100) {
+ command_error(client, ACK_ERROR_ARG, "Invalid volume value");
+ return COMMAND_RETURN_ERROR;
+ }
+
+ success = volume_level_change(level);
if (!success) {
command_error(client, ACK_ERROR_SYSTEM,
"problems setting volume");
@@ -1103,7 +1210,7 @@ handle_repeat(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
return COMMAND_RETURN_ERROR;
}
- setPlaylistRepeatStatus(&g_playlist, status);
+ playlist_set_repeat(&g_playlist, status);
return COMMAND_RETURN_OK;
}
@@ -1121,7 +1228,7 @@ handle_single(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
return COMMAND_RETURN_ERROR;
}
- setPlaylistSingleStatus(&g_playlist, status);
+ playlist_set_single(&g_playlist, status);
return COMMAND_RETURN_OK;
}
@@ -1139,7 +1246,7 @@ handle_consume(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
return COMMAND_RETURN_ERROR;
}
- setPlaylistConsumeStatus(&g_playlist, status);
+ playlist_set_consume(&g_playlist, status);
return COMMAND_RETURN_OK;
}
@@ -1157,7 +1264,7 @@ handle_random(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
return COMMAND_RETURN_ERROR;
}
- setPlaylistRandomStatus(&g_playlist, status);
+ playlist_set_random(&g_playlist, status);
return COMMAND_RETURN_OK;
}
@@ -1172,7 +1279,7 @@ static enum command_return
handle_clearerror(G_GNUC_UNUSED struct client *client,
G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[])
{
- clearPlayerError();
+ pc_clear_error();
return COMMAND_RETURN_OK;
}
@@ -1196,17 +1303,17 @@ handle_list(struct client *client, int argc, char *argv[])
/* for compatibility with < 0.12.0 */
if (argc == 3) {
- if (tagType != TAG_ITEM_ALBUM) {
+ if (tagType != TAG_ALBUM) {
command_error(client, ACK_ERROR_ARG,
"should be \"%s\" for 3 arguments",
- tag_item_names[TAG_ITEM_ALBUM]);
+ tag_item_names[TAG_ALBUM]);
return COMMAND_RETURN_ERROR;
}
locate_item_list_parse(argv + 1, argc - 1);
conditionals = locate_item_list_new(1);
- conditionals->items[0].tag = TAG_ITEM_ARTIST;
+ conditionals->items[0].tag = TAG_ARTIST;
conditionals->items[0].needle = g_strdup(argv[2]);
} else {
conditionals =
@@ -1241,7 +1348,7 @@ handle_move(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
return COMMAND_RETURN_ERROR;
if (!check_int(client, &to, argv[2], check_integer, argv[2]))
return COMMAND_RETURN_ERROR;
- result = moveSongRangeInPlaylist(&g_playlist, start, end, to);
+ result = playlist_move_range(&g_playlist, start, end, to);
return print_playlist_result(client, result);
}
@@ -1255,7 +1362,7 @@ handle_moveid(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
return COMMAND_RETURN_ERROR;
if (!check_int(client, &to, argv[2], check_integer, argv[2]))
return COMMAND_RETURN_ERROR;
- result = moveSongInPlaylistById(&g_playlist, id, to);
+ result = playlist_move_id(&g_playlist, id, to);
return print_playlist_result(client, result);
}
@@ -1269,7 +1376,7 @@ handle_swap(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
return COMMAND_RETURN_ERROR;
if (!check_int(client, &song2, argv[2], check_integer, argv[2]))
return COMMAND_RETURN_ERROR;
- result = swapSongsInPlaylist(&g_playlist, song1, song2);
+ result = playlist_swap_songs(&g_playlist, song1, song2);
return print_playlist_result(client, result);
}
@@ -1283,7 +1390,7 @@ handle_swapid(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
return COMMAND_RETURN_ERROR;
if (!check_int(client, &id2, argv[2], check_integer, argv[2]))
return COMMAND_RETURN_ERROR;
- result = swapSongsInPlaylistById(&g_playlist, id1, id2);
+ result = playlist_swap_songs_id(&g_playlist, id1, id2);
return print_playlist_result(client, result);
}
@@ -1298,7 +1405,7 @@ handle_seek(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
if (!check_int(client, &seek_time, argv[2], check_integer, argv[2]))
return COMMAND_RETURN_ERROR;
- result = seekSongInPlaylist(&g_playlist, song, seek_time);
+ result = playlist_seek_song(&g_playlist, song, seek_time);
return print_playlist_result(client, result);
}
@@ -1313,7 +1420,7 @@ handle_seekid(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
if (!check_int(client, &seek_time, argv[2], check_integer, argv[2]))
return COMMAND_RETURN_ERROR;
- result = seekSongInPlaylistById(&g_playlist, id, seek_time);
+ result = playlist_seek_song_id(&g_playlist, id, seek_time);
return print_playlist_result(client, result);
}
@@ -1363,7 +1470,31 @@ handle_crossfade(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
if (!check_unsigned(client, &xfade_time, argv[1]))
return COMMAND_RETURN_ERROR;
- setPlayerCrossFade(xfade_time);
+ pc_set_cross_fade(xfade_time);
+
+ return COMMAND_RETURN_OK;
+}
+
+static enum command_return
+handle_mixrampdb(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
+{
+ float db;
+
+ if (!check_float(client, &db, argv[1]))
+ return COMMAND_RETURN_ERROR;
+ pc_set_mixramp_db(db);
+
+ return COMMAND_RETURN_OK;
+}
+
+static enum command_return
+handle_mixrampdelay(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
+{
+ float delay_secs;
+
+ if (!check_float(client, &delay_secs, argv[1]))
+ return COMMAND_RETURN_ERROR;
+ pc_set_mixramp_delay(delay_secs);
return COMMAND_RETURN_OK;
}
@@ -1477,6 +1608,28 @@ handle_listplaylists(struct client *client,
}
static enum command_return
+handle_replay_gain_mode(struct client *client,
+ G_GNUC_UNUSED int argc, char *argv[])
+{
+ if (!replay_gain_set_mode_string(argv[1])) {
+ command_error(client, ACK_ERROR_ARG,
+ "Unrecognized replay gain mode");
+ return COMMAND_RETURN_ERROR;
+ }
+
+ return COMMAND_RETURN_OK;
+}
+
+static enum command_return
+handle_replay_gain_status(struct client *client,
+ G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[])
+{
+ client_printf(client, "replay_gain_mode: %s\n",
+ replay_gain_get_mode_string());
+ return COMMAND_RETURN_OK;
+}
+
+static enum command_return
handle_idle(struct client *client,
G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[])
{
@@ -1519,13 +1672,14 @@ sticker_song_find_print_cb(struct song *song, const char *value,
{
struct sticker_song_find_data *data = user_data;
- song_print_url(data->client, song);
+ song_print_uri(data->client, song);
sticker_print_value(data->client, data->name, value);
}
static enum command_return
handle_sticker_song(struct client *client, int argc, char *argv[])
{
+ /* get song song_id key */
if (argc == 5 && strcmp(argv[1], "get") == 0) {
struct song *song;
char *value;
@@ -1548,6 +1702,7 @@ handle_sticker_song(struct client *client, int argc, char *argv[])
g_free(value);
return COMMAND_RETURN_OK;
+ /* list song song_id */
} else if (argc == 4 && strcmp(argv[1], "list") == 0) {
struct song *song;
struct sticker *sticker;
@@ -1560,16 +1715,13 @@ handle_sticker_song(struct client *client, int argc, char *argv[])
}
sticker = sticker_song_get(song);
- if (NULL == sticker) {
- command_error(client, ACK_ERROR_NO_EXIST,
- "no stickers found");
- return COMMAND_RETURN_ERROR;
+ if (sticker) {
+ sticker_print(client, sticker);
+ sticker_free(sticker);
}
- sticker_print(client, sticker);
- sticker_free(sticker);
-
return COMMAND_RETURN_OK;
+ /* set song song_id id key */
} else if (argc == 6 && strcmp(argv[1], "set") == 0) {
struct song *song;
bool ret;
@@ -1589,6 +1741,7 @@ handle_sticker_song(struct client *client, int argc, char *argv[])
}
return COMMAND_RETURN_OK;
+ /* delete song song_id [key] */
} else if ((argc == 4 || argc == 5) &&
strcmp(argv[1], "delete") == 0) {
struct song *song;
@@ -1611,6 +1764,7 @@ handle_sticker_song(struct client *client, int argc, char *argv[])
}
return COMMAND_RETURN_OK;
+ /* find song dir key */
} else if (argc == 5 && strcmp(argv[1], "find") == 0) {
/* "sticker find song a/directory name" */
struct directory *directory;
@@ -1679,11 +1833,13 @@ static const struct command commands[] = {
{ "count", PERMISSION_READ, 2, -1, handle_count },
{ "crossfade", PERMISSION_CONTROL, 1, 1, handle_crossfade },
{ "currentsong", PERMISSION_READ, 0, 0, handle_currentsong },
+ { "decoders", PERMISSION_READ, 0, 0, handle_decoders },
{ "delete", PERMISSION_CONTROL, 1, 1, handle_delete },
{ "deleteid", PERMISSION_CONTROL, 1, 1, handle_deleteid },
{ "disableoutput", PERMISSION_ADMIN, 1, 1, handle_disableoutput },
{ "enableoutput", PERMISSION_ADMIN, 1, 1, handle_enableoutput },
{ "find", PERMISSION_READ, 2, -1, handle_find },
+ { "findadd", PERMISSION_READ, 2, -1, handle_findadd},
{ "idle", PERMISSION_READ, 0, -1, handle_idle },
{ "kill", PERMISSION_ADMIN, -1, -1, handle_kill },
{ "list", PERMISSION_READ, 1, -1, handle_list },
@@ -1694,6 +1850,8 @@ static const struct command commands[] = {
{ "listplaylists", PERMISSION_READ, 0, 0, handle_listplaylists },
{ "load", PERMISSION_ADD, 1, 1, handle_load },
{ "lsinfo", PERMISSION_READ, 0, 1, handle_lsinfo },
+ { "mixrampdb", PERMISSION_CONTROL, 1, 1, handle_mixrampdb },
+ { "mixrampdelay", PERMISSION_CONTROL, 1, 1, handle_mixrampdelay },
{ "move", PERMISSION_CONTROL, 2, 2, handle_move },
{ "moveid", PERMISSION_CONTROL, 2, 2, handle_moveid },
{ "next", PERMISSION_CONTROL, 0, 0, handle_next },
@@ -1719,6 +1877,11 @@ static const struct command commands[] = {
{ "random", PERMISSION_CONTROL, 1, 1, handle_random },
{ "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 },
{ "rm", PERMISSION_CONTROL, 1, 1, handle_rm },
{ "save", PERMISSION_CONTROL, 1, 1, handle_save },
{ "search", PERMISSION_READ, 2, -1, handle_search },
@@ -1738,7 +1901,6 @@ static const struct command commands[] = {
{ "tagtypes", PERMISSION_READ, 0, 0, handle_tagtypes },
{ "update", PERMISSION_ADMIN, 0, 1, handle_update },
{ "urlhandlers", PERMISSION_READ, 0, 0, handle_urlhandlers },
- { "volume", PERMISSION_CONTROL, 1, 1, handle_volume },
};
static const unsigned num_commands = sizeof(commands) / sizeof(commands[0]);
@@ -1892,48 +2054,71 @@ command_checked_lookup(struct client *client, unsigned permission,
}
enum command_return
-command_process(struct client *client, char *commandString)
+command_process(struct client *client, unsigned num, char *line)
{
+ GError *error = NULL;
int argc;
char *argv[COMMAND_ARGV_MAX] = { NULL };
const struct command *cmd;
enum command_return ret = COMMAND_RETURN_ERROR;
- if (!(argc = buffer2array(commandString, argv, COMMAND_ARGV_MAX)))
- return COMMAND_RETURN_OK;
+ command_list_num = num;
- cmd = command_checked_lookup(client, client_get_permission(client),
- argc, argv);
- if (cmd)
- ret = cmd->handler(client, argc, argv);
+ /* get the command name (first word on the line) */
- current_command = NULL;
+ argv[0] = tokenizer_next_word(&line, &error);
+ if (argv[0] == NULL) {
+ current_command = "";
+ if (*line == 0)
+ command_error(client, ACK_ERROR_UNKNOWN,
+ "No command given");
+ else {
+ command_error(client, ACK_ERROR_UNKNOWN,
+ "%s", error->message);
+ g_error_free(error);
+ }
+ current_command = NULL;
- return ret;
-}
+ return COMMAND_RETURN_ERROR;
+ }
-enum command_return
-command_process_list(struct client *client,
- bool list_ok, GSList *list)
-{
- enum command_return ret = COMMAND_RETURN_OK;
+ argc = 1;
- command_list_num = 0;
+ /* now parse the arguments (quoted or unquoted) */
+
+ while (argc < (int)G_N_ELEMENTS(argv) &&
+ (argv[argc] =
+ tokenizer_next_param(&line, &error)) != NULL)
+ ++argc;
- for (GSList *cur = list; cur != NULL; cur = g_slist_next(cur)) {
- char *cmd = cur->data;
+ /* some error checks; we have to set current_command because
+ command_error() expects it to be set */
- g_debug("command_process_list: process command \"%s\"",
- cmd);
- ret = command_process(client, cmd);
- g_debug("command_process_list: command returned %i", ret);
- if (ret != COMMAND_RETURN_OK || client_is_expired(client))
- break;
- else if (list_ok)
- client_puts(client, "list_OK\n");
- command_list_num++;
+ current_command = argv[0];
+
+ if (argc >= (int)G_N_ELEMENTS(argv)) {
+ command_error(client, ACK_ERROR_ARG, "Too many arguments");
+ current_command = NULL;
+ return COMMAND_RETURN_ERROR;
}
+ if (*line != 0) {
+ command_error(client, ACK_ERROR_ARG,
+ "%s", error->message);
+ current_command = NULL;
+ g_error_free(error);
+ return COMMAND_RETURN_ERROR;
+ }
+
+ /* look up and invoke the command handler */
+
+ cmd = command_checked_lookup(client, client_get_permission(client),
+ argc, argv);
+ if (cmd)
+ ret = cmd->handler(client, argc, argv);
+
+ current_command = NULL;
command_list_num = 0;
+
return ret;
}
diff --git a/src/command.h b/src/command.h
index a7c408ed7..39389385d 100644
--- a/src/command.h
+++ b/src/command.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -39,11 +39,7 @@ void command_init(void);
void command_finish(void);
enum command_return
-command_process_list(struct client *client,
- bool list_ok, GSList *list);
-
-enum command_return
-command_process(struct client *client, char *commandString);
+command_process(struct client *client, unsigned num, char *line);
void command_success(struct client *client);
diff --git a/src/compress.c b/src/compress.c
deleted file mode 100644
index 3a0b4beb0..000000000
--- a/src/compress.c
+++ /dev/null
@@ -1,410 +0,0 @@
-/*
- * 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.
- */
-
-/*
- * Imported from AudioCompress by J. Shagam <fluffy@beesbuzz.biz>
- */
-
-#include "compress.h"
-
-#include <glib.h>
-
-#include <stdint.h>
-#include <string.h>
-
-#ifdef USE_X
-#include <X11/Xlib.h>
-#include <X11/Xutil.h>
-
-static Display *display;
-static Window window;
-static Visual *visual;
-static int screen;
-static GC blackGC, whiteGC, blueGC, yellowGC, dkyellowGC, redGC;
-#endif
-
-static int *peaks;
-static int gainCurrent, gainTarget;
-
-static struct {
- int show_mon;
- int anticlip;
- int target;
- int gainmax;
- int gainsmooth;
- unsigned buckets;
-} prefs;
-
-#ifdef USE_X
-static int mon_init;
-#endif
-
-void CompressCfg(int show_mon, int anticlip, int target, int gainmax,
- int gainsmooth, unsigned buckets)
-{
- static unsigned lastsize;
-
- prefs.show_mon = show_mon;
- prefs.anticlip = anticlip;
- prefs.target = target;
- prefs.gainmax = gainmax;
- prefs.gainsmooth = gainsmooth;
- prefs.buckets = buckets;
-
- /* Allocate the peak structure */
- peaks = g_realloc(peaks, sizeof(int)*prefs.buckets);
-
- if (prefs.buckets > lastsize)
- memset(peaks + lastsize, 0, sizeof(int)*(prefs.buckets
- - lastsize));
- lastsize = prefs.buckets;
-
-#ifdef USE_X
- /* Configure the monitor window if needed */
- if (show_mon && !mon_init)
- {
- display = XOpenDisplay(getenv("DISPLAY"));
-
- /* We really shouldn't try to init X if there's no X */
- if (!display)
- {
- fprintf(stderr,
- "X not detected; disabling monitor window\n");
- show_mon = prefs.show_mon = 0;
- }
- }
-
- if (show_mon && !mon_init)
- {
- XGCValues gcv;
- XColor col;
-
- gainCurrent = gainTarget = (1 << GAINSHIFT);
-
-
-
- screen = DefaultScreen(display);
- visual = DefaultVisual(display, screen);
- window = XCreateSimpleWindow(display,
- RootWindow(display, screen),
- 0, 0, prefs.buckets, 128 + 8, 0,
- BlackPixel(display, screen),
- WhitePixel(display, screen));
- XStoreName(display, window, "AudioCompress monitor");
-
- gcv.foreground = BlackPixel(display, screen);
- blackGC = XCreateGC(display, window, GCForeground, &gcv);
- gcv.foreground = WhitePixel(display, screen);
- whiteGC = XCreateGC(display, window, GCForeground, &gcv);
- col.red = 0;
- col.green = 0;
- col.blue = 65535;
- XAllocColor(display, DefaultColormap(display, screen), &col);
- gcv.foreground = col.pixel;
- blueGC = XCreateGC(display, window, GCForeground, &gcv);
- col.red = 65535;
- col.green = 65535;
- col.blue = 0;
- XAllocColor(display, DefaultColormap(display, screen), &col);
- gcv.foreground = col.pixel;
- yellowGC = XCreateGC(display, window, GCForeground, &gcv);
- col.red = 32767;
- col.green = 32767;
- col.blue = 0;
- XAllocColor(display, DefaultColormap(display, screen), &col);
- gcv.foreground = col.pixel;
- dkyellowGC = XCreateGC(display, window, GCForeground, &gcv);
- col.red = 65535;
- col.green = 0;
- col.blue = 0;
- XAllocColor(display, DefaultColormap(display, screen), &col);
- gcv.foreground = col.pixel;
- redGC = XCreateGC(display, window, GCForeground, &gcv);
- mon_init = 1;
- }
-
- if (mon_init)
- {
- if (show_mon)
- XMapWindow(display, window);
- else
- XUnmapWindow(display, window);
- XResizeWindow(display, window, prefs.buckets, 128 + 8);
- XFlush(display);
- }
-#endif
-}
-
-void CompressFree(void)
-{
-#ifdef USE_X
- if (mon_init)
- {
- XFreeGC(display, blackGC);
- XFreeGC(display, whiteGC);
- XFreeGC(display, blueGC);
- XFreeGC(display, yellowGC);
- XFreeGC(display, dkyellowGC);
- XFreeGC(display, redGC);
- XDestroyWindow(display, window);
- XCloseDisplay(display);
- }
-#endif
-
- g_free(peaks);
-}
-
-void CompressDo(void *data, unsigned int length)
-{
- int16_t *audio = (int16_t *)data, *ap;
- int peak;
- unsigned int i, pos;
- int gr, gf, gn;
- static int pn = -1;
-#ifdef STATS
- static int clip;
-#endif
- static int clipped;
-
- if (!peaks)
- return;
-
- if (pn == -1)
- {
- for (i = 0; i < prefs.buckets; i++)
- peaks[i] = 0;
- }
- pn = (pn + 1)%prefs.buckets;
-
-#ifdef DEBUG
- fprintf(stderr, "modifyNative16(0x%08x, %d)\n",(unsigned int)data,
- length);
-#endif
-
- /* Determine peak's value and position */
- peak = 1;
- pos = 0;
-
-#ifdef DEBUG
- fprintf(stderr, "finding peak(b=%d)\n", pn);
-#endif
-
- ap = audio;
- for (i = 0; i < length/2; i++)
- {
- int val = *ap;
- if (val > peak)
- {
- peak = val;
- pos = i;
- } else if (-val > peak)
- {
- peak = -val;
- pos = i;
- }
- ap++;
- }
- peaks[pn] = peak;
-
- /* Only draw if needed, of course */
-#ifdef USE_X
- if (prefs.show_mon)
- {
- /* current amplitude */
- XDrawLine(display, window, whiteGC,
- pn, 0,
- pn,
- 127 -
- (peaks[pn]*gainCurrent >> (GAINSHIFT + 8)));
-
- /* amplification */
- XDrawLine(display, window, yellowGC,
- pn,
- 127 - (peaks[pn]*gainCurrent
- >> (GAINSHIFT + 8)),
- pn, 127);
-
- /* peak */
- XDrawLine(display, window, blackGC,
- pn, 127 - (peaks[pn] >> 8), pn, 127);
-
- /* clip indicator */
- if (clipped)
- XDrawLine(display, window, redGC,
- (pn + prefs.buckets - 1)%prefs.buckets,
- 126 - clipped/(length*512),
- (pn + prefs.buckets - 1)%prefs.buckets,
- 127);
- clipped = 0;
-
- /* target line */
- /* XDrawPoint(display, window, redGC, */
- /* pn, 127 - TARGET/256); */
- /* amplification edge */
- XDrawLine(display, window, dkyellowGC,
- pn,
- 127 - (peaks[pn]*gainCurrent
- >> (GAINSHIFT + 8)),
- pn - 1,
- 127 -
- (peaks[(pn + prefs.buckets
- - 1)%prefs.buckets]*gainCurrent
- >> (GAINSHIFT + 8)));
- }
-#endif
-
- for (i = 0; i < prefs.buckets; i++)
- {
- if (peaks[i] > peak)
- {
- peak = peaks[i];
- pos = 0;
- }
- }
-
- /* Determine target gain */
- gn = (1 << GAINSHIFT)*prefs.target/peak;
-
- if (gn <(1 << GAINSHIFT))
- gn = 1 << GAINSHIFT;
-
- gainTarget = (gainTarget *((1 << prefs.gainsmooth) - 1) + gn)
- >> prefs.gainsmooth;
-
- /* Give it an extra insignifigant nudge to counteract possible
- ** rounding error
- */
-
- if (gn < gainTarget)
- gainTarget--;
- else if (gn > gainTarget)
- gainTarget++;
-
- if (gainTarget > prefs.gainmax << GAINSHIFT)
- gainTarget = prefs.gainmax << GAINSHIFT;
-
-
-#ifdef USE_X
- if (prefs.show_mon)
- {
- int x;
-
- /* peak*gain */
- XDrawPoint(display, window, redGC,
- pn,
- 127 - (peak*gainCurrent
- >> (GAINSHIFT + 8)));
-
- /* gain indicator */
- XFillRectangle(display, window, whiteGC, 0, 128,
- prefs.buckets, 8);
- x = (gainTarget - (1 << GAINSHIFT))*prefs.buckets
- / ((prefs.gainmax - 1) << GAINSHIFT);
- XDrawLine(display, window, redGC, x,
- 128, x, 128 + 8);
-
- x = (gn - (1 << GAINSHIFT))*prefs.buckets
- / ((prefs.gainmax - 1) << GAINSHIFT);
-
- XDrawLine(display, window, blackGC,
- x, 132 - 1,
- x, 132 + 1);
-
- /* blue peak line */
- XDrawLine(display, window, blueGC,
- 0, 127 - (peak >> 8), prefs.buckets,
- 127 - (peak >> 8));
- XFlush(display);
- XDrawLine(display, window, whiteGC,
- 0, 127 - (peak >> 8), prefs.buckets,
- 127 - (peak >> 8));
- }
-#endif
-
- /* See if a peak is going to clip */
- gn = (1 << GAINSHIFT)*32768/peak;
-
- if (gn < gainTarget)
- {
- gainTarget = gn;
-
- if (prefs.anticlip)
- pos = 0;
-
- } else
- {
- /* We're ramping up, so draw it out over the whole frame */
- pos = length;
- }
-
- /* Determine gain rate necessary to make target */
- if (!pos)
- pos = 1;
-
- gr = ((gainTarget - gainCurrent) << 16)/(int)pos;
-
- /* Do the shiznit */
- gf = gainCurrent << 16;
-
-#ifdef STATS
- fprintf(stderr, "\rgain = %2.2f%+.2e ",
- gainCurrent*1.0/(1 << GAINSHIFT),
- (gainTarget - gainCurrent)*1.0/(1 << GAINSHIFT));
-#endif
-
- ap = audio;
- for (i = 0; i < length/2; i++)
- {
- int sample;
-
- /* Interpolate the gain */
- gainCurrent = gf >> 16;
- if (i < pos)
- gf += gr;
- else if (i == pos)
- gf = gainTarget << 16;
-
- /* Amplify */
- sample = (*ap)*gainCurrent >> GAINSHIFT;
- if (sample < -32768)
- {
-#ifdef STATS
- clip++;
-#endif
- clipped += -32768 - sample;
- sample = -32768;
- } else if (sample > 32767)
- {
-#ifdef STATS
- clip++;
-#endif
- clipped += sample - 32767;
- sample = 32767;
- }
- *ap++ = sample;
- }
-#ifdef STATS
- fprintf(stderr, "clip %d b%-3d ", clip, pn);
-#endif
-
-#ifdef DEBUG
- fprintf(stderr, "\ndone\n");
-#endif
-}
-
diff --git a/src/compress.h b/src/compress.h
deleted file mode 100644
index 3e3afb565..000000000
--- a/src/compress.h
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * 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.
- */
-
-/*
- * Imported from AudioCompress by J. Shagam <fluffy@beesbuzz.biz>
- */
-
-#ifndef MPD_COMPRESS_H
-#define MPD_COMPRESS_H
-
-/* These are copied from the AudioCompress config.h, mainly because CompressDo
- * needs GAINSHIFT defined. The rest are here so they can be used as defaults
- * to pass to CompressCfg(). -- jat */
-#define ANTICLIP 0 /* Strict clipping protection */
-#define TARGET 25000 /* Target level */
-#define GAINMAX 32 /* The maximum amount to amplify by */
-#define GAINSHIFT 10 /* How fine-grained the gain is */
-#define GAINSMOOTH 8 /* How much inertia ramping has*/
-#define BUCKETS 400 /* How long of a history to store */
-
-void CompressCfg(int monitor,
- int anticlip,
- int target,
- int maxgain,
- int smooth,
- unsigned buckets);
-
-void CompressDo(void *data, unsigned int numSamples);
-
-void CompressFree(void);
-
-#endif
diff --git a/src/conf.c b/src/conf.c
index cce7dbf27..705942085 100644
--- a/src/conf.c
+++ b/src/conf.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -17,10 +17,13 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "conf.h"
#include "utils.h"
-#include "buffer2array.h"
+#include "tokenizer.h"
#include "path.h"
+#include "glib_compat.h"
+#include "mpd_error.h"
#include <glib.h>
@@ -36,37 +39,84 @@
#define MAX_STRING_SIZE MPD_PATH_MAX+80
#define CONF_COMMENT '#'
-#define CONF_BLOCK_BEGIN "{"
-#define CONF_BLOCK_END "}"
-
-#define CONF_REPEATABLE_MASK 0x01
-#define CONF_BLOCK_MASK 0x02
-#define CONF_LINE_TOKEN_MAX 3
struct config_entry {
- const char *name;
- unsigned char mask;
+ const char *const name;
+ const bool repeatable;
+ const bool block;
GSList *params;
};
-static GSList *config_entries;
+static struct config_entry config_entries[] = {
+ { .name = CONF_MUSIC_DIR, false, false },
+ { .name = CONF_PLAYLIST_DIR, false, false },
+ { .name = CONF_FOLLOW_INSIDE_SYMLINKS, false, false },
+ { .name = CONF_FOLLOW_OUTSIDE_SYMLINKS, false, false },
+ { .name = CONF_DB_FILE, false, false },
+ { .name = CONF_STICKER_FILE, false, false },
+ { .name = CONF_LOG_FILE, false, false },
+ { .name = CONF_PID_FILE, false, false },
+ { .name = CONF_STATE_FILE, false, false },
+ { .name = CONF_USER, false, false },
+ { .name = CONF_GROUP, false, false },
+ { .name = CONF_BIND_TO_ADDRESS, true, false },
+ { .name = CONF_PORT, false, false },
+ { .name = CONF_LOG_LEVEL, false, false },
+ { .name = CONF_ZEROCONF_NAME, false, false },
+ { .name = CONF_ZEROCONF_ENABLED, false, false },
+ { .name = CONF_PASSWORD, true, false },
+ { .name = CONF_DEFAULT_PERMS, false, false },
+ { .name = CONF_AUDIO_OUTPUT, true, true },
+ { .name = CONF_AUDIO_OUTPUT_FORMAT, false, false },
+ { .name = CONF_MIXER_TYPE, false, false },
+ { .name = CONF_REPLAYGAIN, false, false },
+ { .name = CONF_REPLAYGAIN_PREAMP, false, false },
+ { .name = CONF_REPLAYGAIN_MISSING_PREAMP, false, false },
+ { .name = CONF_REPLAYGAIN_LIMIT, false, false },
+ { .name = CONF_VOLUME_NORMALIZATION, false, false },
+ { .name = CONF_SAMPLERATE_CONVERTER, false, false },
+ { .name = CONF_AUDIO_BUFFER_SIZE, false, false },
+ { .name = CONF_BUFFER_BEFORE_PLAY, false, false },
+ { .name = CONF_HTTP_PROXY_HOST, false, false },
+ { .name = CONF_HTTP_PROXY_PORT, false, false },
+ { .name = CONF_HTTP_PROXY_USER, false, false },
+ { .name = CONF_HTTP_PROXY_PASSWORD, false, false },
+ { .name = CONF_CONN_TIMEOUT, false, false },
+ { .name = CONF_MAX_CONN, false, false },
+ { .name = CONF_MAX_PLAYLIST_LENGTH, false, false },
+ { .name = CONF_MAX_COMMAND_LIST_SIZE, false, false },
+ { .name = CONF_MAX_OUTPUT_BUFFER_SIZE, false, false },
+ { .name = CONF_FS_CHARSET, false, false },
+ { .name = CONF_ID3V1_ENCODING, false, false },
+ { .name = CONF_METADATA_TO_USE, false, false },
+ { .name = CONF_SAVE_ABSOLUTE_PATHS, false, false },
+ { .name = CONF_DECODER, true, true },
+ { .name = CONF_INPUT, true, true },
+ { .name = CONF_GAPLESS_MP3_PLAYBACK, false, false },
+ { .name = CONF_PLAYLIST_PLUGIN, true, true },
+ { .name = CONF_AUTO_UPDATE, false, false },
+ { .name = CONF_AUTO_UPDATE_DEPTH, false, false },
+ { .name = "filter", true, true },
+};
-static int get_bool(const char *value)
+static bool
+get_bool(const char *value, bool *value_r)
{
- const char **x;
static const char *t[] = { "yes", "true", "1", NULL };
static const char *f[] = { "no", "false", "0", NULL };
- for (x = t; *x; x++) {
- if (!g_ascii_strcasecmp(*x, value))
- return 1;
+ if (string_array_contains(t, value)) {
+ *value_r = true;
+ return true;
}
- for (x = f; *x; x++) {
- if (!g_ascii_strcasecmp(*x, value))
- return 0;
+
+ if (string_array_contains(f, value)) {
+ *value_r = false;
+ return true;
}
- return CONF_BOOL_INVALID;
+
+ return false;
}
struct config_param *
@@ -83,15 +133,14 @@ config_new_param(const char *value, int line)
ret->num_block_params = 0;
ret->block_params = NULL;
+ ret->used = false;
return ret;
}
static void
-config_param_free(gpointer data, G_GNUC_UNUSED gpointer user_data)
+config_param_free(struct config_param *param)
{
- struct config_param *param = data;
-
g_free(param->value);
for (unsigned i = 0; i < param->num_block_params; i++) {
@@ -105,42 +154,19 @@ config_param_free(gpointer data, G_GNUC_UNUSED gpointer user_data)
g_free(param);
}
-static struct config_entry *
-newConfigEntry(const char *name, int repeatable, int block)
-{
- struct config_entry *ret = g_new(struct config_entry, 1);
-
- ret->name = name;
- ret->mask = 0;
- ret->params = NULL;
-
- if (repeatable)
- ret->mask |= CONF_REPEATABLE_MASK;
- if (block)
- ret->mask |= CONF_BLOCK_MASK;
-
- return ret;
-}
-
static void
-config_entry_free(gpointer data, G_GNUC_UNUSED gpointer user_data)
+config_param_free_callback(gpointer data, G_GNUC_UNUSED gpointer user_data)
{
- struct config_entry *entry = data;
-
- g_slist_foreach(entry->params, config_param_free, NULL);
- g_slist_free(entry->params);
+ struct config_param *param = data;
- g_free(entry);
+ config_param_free(param);
}
static struct config_entry *
config_entry_get(const char *name)
{
- GSList *list;
-
- for (list = config_entries; list != NULL;
- list = g_slist_next(list)) {
- struct config_entry *entry = list->data;
+ for (unsigned i = 0; i < G_N_ELEMENTS(config_entries); ++i) {
+ struct config_entry *entry = &config_entries[i];
if (strcmp(entry->name, name) == 0)
return entry;
}
@@ -148,82 +174,65 @@ config_entry_get(const char *name)
return NULL;
}
-static void registerConfigParam(const char *name, int repeatable, int block)
+void config_global_finish(void)
{
- struct config_entry *entry;
+ for (unsigned i = 0; i < G_N_ELEMENTS(config_entries); ++i) {
+ struct config_entry *entry = &config_entries[i];
- entry = config_entry_get(name);
- if (entry != NULL)
- g_error("config parameter \"%s\" already registered\n", name);
+ g_slist_foreach(entry->params,
+ config_param_free_callback, NULL);
+ g_slist_free(entry->params);
+ }
+}
- entry = newConfigEntry(name, repeatable, block);
- config_entries = g_slist_prepend(config_entries, entry);
+void config_global_init(void)
+{
}
-void config_global_finish(void)
+static void
+config_param_check(gpointer data, G_GNUC_UNUSED gpointer user_data)
{
- g_slist_foreach(config_entries, config_entry_free, NULL);
- g_slist_free(config_entries);
+ struct config_param *param = data;
+
+ if (!param->used)
+ /* this whole config_param was not queried at all -
+ the feature might be disabled at compile time?
+ Silently ignore it here. */
+ return;
+
+ for (unsigned i = 0; i < param->num_block_params; i++) {
+ struct block_param *bp = &param->block_params[i];
+
+ if (!bp->used)
+ g_warning("option '%s' on line %i was not recognized",
+ bp->name, bp->line);
+ }
}
-void config_global_init(void)
+void config_global_check(void)
{
- config_entries = NULL;
-
- /* registerConfigParam(name, repeatable, block); */
- registerConfigParam(CONF_MUSIC_DIR, 0, 0);
- registerConfigParam(CONF_PLAYLIST_DIR, 0, 0);
- registerConfigParam(CONF_FOLLOW_INSIDE_SYMLINKS, 0, 0);
- registerConfigParam(CONF_FOLLOW_OUTSIDE_SYMLINKS, 0, 0);
- registerConfigParam(CONF_DB_FILE, 0, 0);
- registerConfigParam(CONF_STICKER_FILE, false, false);
- registerConfigParam(CONF_LOG_FILE, 0, 0);
- registerConfigParam(CONF_ERROR_FILE, 0, 0);
- registerConfigParam(CONF_PID_FILE, 0, 0);
- registerConfigParam(CONF_STATE_FILE, 0, 0);
- registerConfigParam(CONF_USER, 0, 0);
- registerConfigParam(CONF_BIND_TO_ADDRESS, 1, 0);
- registerConfigParam(CONF_PORT, 0, 0);
- registerConfigParam(CONF_LOG_LEVEL, 0, 0);
- registerConfigParam(CONF_ZEROCONF_NAME, 0, 0);
- registerConfigParam(CONF_ZEROCONF_ENABLED, 0, 0);
- registerConfigParam(CONF_PASSWORD, 1, 0);
- registerConfigParam(CONF_DEFAULT_PERMS, 0, 0);
- registerConfigParam(CONF_AUDIO_OUTPUT, 1, 1);
- registerConfigParam(CONF_AUDIO_OUTPUT_FORMAT, 0, 0);
- registerConfigParam(CONF_MIXER_TYPE, 0, 0);
- registerConfigParam(CONF_MIXER_DEVICE, 0, 0);
- registerConfigParam(CONF_MIXER_CONTROL, 0, 0);
- registerConfigParam(CONF_REPLAYGAIN, 0, 0);
- registerConfigParam(CONF_REPLAYGAIN_PREAMP, 0, 0);
- registerConfigParam(CONF_VOLUME_NORMALIZATION, 0, 0);
- registerConfigParam(CONF_SAMPLERATE_CONVERTER, 0, 0);
- registerConfigParam(CONF_AUDIO_BUFFER_SIZE, 0, 0);
- registerConfigParam(CONF_BUFFER_BEFORE_PLAY, 0, 0);
- registerConfigParam(CONF_HTTP_PROXY_HOST, 0, 0);
- registerConfigParam(CONF_HTTP_PROXY_PORT, 0, 0);
- registerConfigParam(CONF_HTTP_PROXY_USER, 0, 0);
- registerConfigParam(CONF_HTTP_PROXY_PASSWORD, 0, 0);
- registerConfigParam(CONF_CONN_TIMEOUT, 0, 0);
- registerConfigParam(CONF_MAX_CONN, 0, 0);
- registerConfigParam(CONF_MAX_PLAYLIST_LENGTH, 0, 0);
- registerConfigParam(CONF_MAX_COMMAND_LIST_SIZE, 0, 0);
- registerConfigParam(CONF_MAX_OUTPUT_BUFFER_SIZE, 0, 0);
- registerConfigParam(CONF_FS_CHARSET, 0, 0);
- registerConfigParam(CONF_ID3V1_ENCODING, 0, 0);
- registerConfigParam(CONF_METADATA_TO_USE, 0, 0);
- registerConfigParam(CONF_SAVE_ABSOLUTE_PATHS, 0, 0);
- registerConfigParam(CONF_DECODER, true, true);
- registerConfigParam(CONF_INPUT, true, true);
- registerConfigParam(CONF_GAPLESS_MP3_PLAYBACK, 0, 0);
+ for (unsigned i = 0; i < G_N_ELEMENTS(config_entries); ++i) {
+ struct config_entry *entry = &config_entries[i];
+
+ g_slist_foreach(entry->params, config_param_check, NULL);
+ }
}
-void
+bool
config_add_block_param(struct config_param * param, const char *name,
- const char *value, int line)
+ const char *value, int line, GError **error_r)
{
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;
+ }
+
param->num_block_params++;
param->block_params = g_realloc(param->block_params,
@@ -235,67 +244,97 @@ config_add_block_param(struct config_param * param, const char *name,
bp->name = g_strdup(name);
bp->value = g_strdup(value);
bp->line = line;
+ bp->used = false;
+
+ return true;
}
static struct config_param *
-config_read_block(FILE *fp, int *count, char *string)
+config_read_block(FILE *fp, int *count, char *string, GError **error_r)
{
struct config_param *ret = config_new_param(NULL, *count);
-
- int i;
- int numberOfArgs;
- int argsMinusComment;
-
- while (fgets(string, MAX_STRING_SIZE, fp)) {
- char *array[CONF_LINE_TOKEN_MAX] = { NULL };
+ GError *error = NULL;
+ bool success;
+
+ while (true) {
+ char *line;
+ const char *name, *value;
+
+ line = fgets(string, MAX_STRING_SIZE, fp);
+ if (line == NULL) {
+ config_param_free(ret);
+ g_set_error(error_r, config_quark(), 0,
+ "Expected '}' before end-of-file");
+ return NULL;
+ }
(*count)++;
+ line = g_strchug(line);
+ if (*line == 0 || *line == CONF_COMMENT)
+ continue;
- numberOfArgs = buffer2array(string, array, CONF_LINE_TOKEN_MAX);
+ if (*line == '}') {
+ /* end of this block; return from the function
+ (and from this "while" loop) */
+
+ line = g_strchug(line + 1);
+ if (*line != 0 && *line != CONF_COMMENT) {
+ config_param_free(ret);
+ g_set_error(error_r, config_quark(), 0,
+ "line %i: Unknown tokens after '}'",
+ *count);
+ return false;
+ }
- for (i = 0; i < numberOfArgs; i++) {
- if (array[i][0] == CONF_COMMENT)
- break;
+ return ret;
}
- argsMinusComment = i;
+ /* parse name and value */
- if (0 == argsMinusComment) {
- continue;
+ name = tokenizer_next_word(&line, &error);
+ if (name == NULL) {
+ assert(*line != 0);
+ config_param_free(ret);
+ g_propagate_prefixed_error(error_r, error,
+ "line %i: ", *count);
+ return NULL;
}
- if (1 == argsMinusComment &&
- 0 == strcmp(array[0], CONF_BLOCK_END)) {
- break;
+ 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 (2 != argsMinusComment) {
- g_error("improperly formatted config file at line %i:"
- " %s\n", *count, string);
+ 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;
}
- if (0 == strcmp(array[0], CONF_BLOCK_BEGIN) ||
- 0 == strcmp(array[1], CONF_BLOCK_BEGIN) ||
- 0 == strcmp(array[0], CONF_BLOCK_END) ||
- 0 == strcmp(array[1], CONF_BLOCK_END)) {
- g_error("improperly formatted config file at line %i: %s "
- "in block beginning at line %i\n",
- *count, string, ret->line);
+ success = config_add_block_param(ret, name, value, *count,
+ error_r);
+ if (!success) {
+ config_param_free(ret);
+ return false;
}
-
- config_add_block_param(ret, array[0], array[1], *count);
}
-
- return ret;
}
-void config_read_file(const char *file)
+bool
+config_read_file(const char *file, GError **error_r)
{
FILE *fp;
char string[MAX_STRING_SIZE + 1];
- int i;
- int numberOfArgs;
- int argsMinusComment;
int count = 0;
struct config_entry *entry;
struct config_param *param;
@@ -303,67 +342,110 @@ void config_read_file(const char *file)
g_debug("loading file %s", file);
if (!(fp = fopen(file, "r"))) {
- g_error("problems opening file %s for reading: %s\n",
- file, strerror(errno));
+ g_set_error(error_r, config_quark(), errno,
+ "Failed to open %s: %s",
+ file, strerror(errno));
+ return false;
}
while (fgets(string, MAX_STRING_SIZE, fp)) {
- char *array[CONF_LINE_TOKEN_MAX] = { NULL };
+ char *line;
+ const char *name, *value;
+ GError *error = NULL;
count++;
- numberOfArgs = buffer2array(string, array, CONF_LINE_TOKEN_MAX);
+ line = g_strchug(string);
+ if (*line == 0 || *line == CONF_COMMENT)
+ continue;
+
+ /* the first token in each line is the name, followed
+ by either the value or '{' */
- for (i = 0; i < numberOfArgs; i++) {
- if (array[i][0] == CONF_COMMENT)
- break;
+ name = tokenizer_next_word(&line, &error);
+ if (name == NULL) {
+ assert(*line != 0);
+ g_propagate_prefixed_error(error_r, error,
+ "line %i: ", count);
+ return false;
}
- argsMinusComment = i;
+ /* get the definition of that option, and check the
+ "repeatable" flag */
- if (0 == argsMinusComment) {
- continue;
+ entry = config_entry_get(name);
+ if (entry == NULL) {
+ g_set_error(error_r, config_quark(), 0,
+ "unrecognized parameter in config file at "
+ "line %i: %s\n", count, name);
+ return false;
}
- if (2 != argsMinusComment) {
- g_error("improperly formatted config file at line %i:"
- " %s\n", count, string);
+ if (entry->params != NULL && !entry->repeatable) {
+ param = entry->params->data;
+ g_set_error(error_r, config_quark(), 0,
+ "config parameter \"%s\" is first defined "
+ "on line %i and redefined on line %i\n",
+ name, param->line, count);
+ return false;
}
- entry = config_entry_get(array[0]);
- if (entry == NULL)
- g_error("unrecognized parameter in config file at "
- "line %i: %s\n", count, string);
+ /* now parse the block or the value */
- if (!(entry->mask & CONF_REPEATABLE_MASK) &&
- entry->params != NULL) {
- param = entry->params->data;
- g_error("config parameter \"%s\" is first defined on "
- "line %i and redefined on line %i\n",
- array[0], param->line, count);
- }
+ if (entry->block) {
+ /* it's a block, call config_read_block() */
+
+ if (*line != '{') {
+ g_set_error(error_r, config_quark(), 0,
+ "line %i: '{' expected", count);
+ return false;
+ }
+
+ line = g_strchug(line + 1);
+ if (*line != 0 && *line != CONF_COMMENT) {
+ g_set_error(error_r, config_quark(), 0,
+ "line %i: Unknown tokens after '{'",
+ count);
+ return false;
+ }
+
+ param = config_read_block(fp, &count, string, error_r);
+ if (param == NULL)
+ return false;
+ } else {
+ /* a string value */
+
+ value = tokenizer_next_string(&line, &error);
+ if (value == NULL) {
+ if (*line == 0)
+ g_set_error(error_r, config_quark(), 0,
+ "line %i: Value missing",
+ count);
+ else {
+ g_set_error(error_r, config_quark(), 0,
+ "line %i: %s", count,
+ error->message);
+ g_error_free(error);
+ }
+
+ return false;
+ }
- if (entry->mask & CONF_BLOCK_MASK) {
- if (0 != strcmp(array[1], CONF_BLOCK_BEGIN)) {
- g_error("improperly formatted config file at "
- "line %i: %s\n", count, string);
+ if (*line != 0 && *line != CONF_COMMENT) {
+ g_set_error(error_r, config_quark(), 0,
+ "line %i: Unknown tokens after value",
+ count);
+ return false;
}
- param = config_read_block(fp, &count, string);
- } else
- param = config_new_param(array[1], count);
+
+ param = config_new_param(value, count);
+ }
entry->params = g_slist_append(entry->params, param);
}
fclose(fp);
-}
-
-void
-config_add_param(const char *name, struct config_param *param)
-{
- struct config_entry *entry = config_entry_get(name);
- assert(entry != NULL);
- entry->params = g_slist_append(entry->params, param);
+ return true;
}
struct config_param *
@@ -391,7 +473,7 @@ config_get_next_param(const char *name, const struct config_param * last)
return NULL;
param = node->data;
-
+ param->used = true;
return param;
}
@@ -417,14 +499,32 @@ config_get_path(const char *name)
path = parsePath(param->value);
if (path == NULL)
- g_error("error parsing \"%s\" at line %i\n",
- name, param->line);
+ MPD_ERROR("error parsing \"%s\" at line %i\n",
+ name, param->line);
g_free(param->value);
return param->value = path;
}
unsigned
+config_get_unsigned(const char *name, unsigned default_value)
+{
+ const struct config_param *param = config_get_param(name);
+ long value;
+ char *endptr;
+
+ if (param == NULL)
+ return default_value;
+
+ value = strtol(param->value, &endptr, 0);
+ if (*endptr != 0 || value < 0)
+ MPD_ERROR("Not a valid non-negative number in line %i",
+ param->line);
+
+ return (unsigned)value;
+}
+
+unsigned
config_get_positive(const char *name, unsigned default_value)
{
const struct config_param *param = config_get_param(name);
@@ -436,10 +536,10 @@ config_get_positive(const char *name, unsigned default_value)
value = strtol(param->value, &endptr, 0);
if (*endptr != 0)
- g_error("Not a valid number in line %i", param->line);
+ MPD_ERROR("Not a valid number in line %i", param->line);
if (value <= 0)
- g_error("Not a positive number in line %i", param->line);
+ MPD_ERROR("Not a positive number in line %i", param->line);
return (unsigned)value;
}
@@ -447,43 +547,35 @@ config_get_positive(const char *name, unsigned default_value)
struct block_param *
config_get_block_param(const struct config_param * param, const char *name)
{
- struct block_param *ret = NULL;
-
if (param == NULL)
return NULL;
for (unsigned i = 0; i < param->num_block_params; i++) {
if (0 == strcmp(name, param->block_params[i].name)) {
- if (ret) {
- g_warning("\"%s\" first defined on line %i, and "
- "redefined on line %i\n", name,
- ret->line, param->block_params[i].line);
- }
- ret = param->block_params + i;
+ struct block_param *bp = &param->block_params[i];
+ bp->used = true;
+ return bp;
}
}
- return ret;
+ return NULL;
}
bool config_get_bool(const char *name, bool default_value)
{
const struct config_param *param = config_get_param(name);
- int value;
+ bool success, value;
if (param == NULL)
return default_value;
- value = get_bool(param->value);
- if (value == CONF_BOOL_INVALID)
- g_error("%s is not a boolean value (yes, true, 1) or "
- "(no, false, 0) on line %i\n",
- name, param->line);
-
- if (value == CONF_BOOL_UNSET)
- return default_value;
+ success = get_bool(param->value, &value);
+ if (!success)
+ MPD_ERROR("%s is not a boolean value (yes, true, 1) or "
+ "(no, false, 0) on line %i\n",
+ name, param->line);
- return !!value;
+ return value;
}
const char *
@@ -511,10 +603,10 @@ config_get_block_unsigned(const struct config_param *param, const char *name,
value = strtol(bp->value, &endptr, 0);
if (*endptr != 0)
- g_error("Not a valid number in line %i", bp->line);
+ MPD_ERROR("Not a valid number in line %i", bp->line);
if (value < 0)
- g_error("Not a positive number in line %i", bp->line);
+ MPD_ERROR("Not a positive number in line %i", bp->line);
return (unsigned)value;
}
@@ -524,19 +616,16 @@ 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);
- int value;
+ bool success, value;
if (bp == NULL)
return default_value;
- value = get_bool(bp->value);
- if (value == CONF_BOOL_INVALID)
- g_error("%s is not a boolean value (yes, true, 1) or "
- "(no, false, 0) on line %i\n",
- name, bp->line);
-
- if (value == CONF_BOOL_UNSET)
- return default_value;
+ success = get_bool(bp->value, &value);
+ if (!success)
+ MPD_ERROR("%s is not a boolean value (yes, true, 1) or "
+ "(no, false, 0) on line %i\n",
+ name, bp->line);
- return !!value;
+ return value;
}
diff --git a/src/conf.h b/src/conf.h
index c5e49960e..8a0678312 100644
--- a/src/conf.h
+++ b/src/conf.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -28,26 +28,27 @@
#define CONF_FOLLOW_INSIDE_SYMLINKS "follow_inside_symlinks"
#define CONF_FOLLOW_OUTSIDE_SYMLINKS "follow_outside_symlinks"
#define CONF_DB_FILE "db_file"
-#define CONF_STICKER_FILE "sticker_file"
+#define CONF_STICKER_FILE "sticker_file"
#define CONF_LOG_FILE "log_file"
-#define CONF_ERROR_FILE "error_file"
#define CONF_PID_FILE "pid_file"
#define CONF_STATE_FILE "state_file"
#define CONF_USER "user"
+#define CONF_GROUP "group"
#define CONF_BIND_TO_ADDRESS "bind_to_address"
#define CONF_PORT "port"
#define CONF_LOG_LEVEL "log_level"
#define CONF_ZEROCONF_NAME "zeroconf_name"
-#define CONF_ZEROCONF_ENABLED "zeroconf_enabled"
+#define CONF_ZEROCONF_ENABLED "zeroconf_enabled"
#define CONF_PASSWORD "password"
#define CONF_DEFAULT_PERMS "default_permissions"
#define CONF_AUDIO_OUTPUT "audio_output"
+#define CONF_AUDIO_FILTER "filter"
#define CONF_AUDIO_OUTPUT_FORMAT "audio_output_format"
#define CONF_MIXER_TYPE "mixer_type"
-#define CONF_MIXER_DEVICE "mixer_device"
-#define CONF_MIXER_CONTROL "mixer_control"
#define CONF_REPLAYGAIN "replaygain"
#define CONF_REPLAYGAIN_PREAMP "replaygain_preamp"
+#define CONF_REPLAYGAIN_MISSING_PREAMP "replaygain_missing_preamp"
+#define CONF_REPLAYGAIN_LIMIT "replaygain_limit"
#define CONF_VOLUME_NORMALIZATION "volume_normalization"
#define CONF_SAMPLERATE_CONVERTER "samplerate_converter"
#define CONF_AUDIO_BUFFER_SIZE "audio_buffer_size"
@@ -65,20 +66,28 @@
#define CONF_ID3V1_ENCODING "id3v1_encoding"
#define CONF_METADATA_TO_USE "metadata_to_use"
#define CONF_SAVE_ABSOLUTE_PATHS "save_absolute_paths_in_playlists"
-#define CONF_DECODER "decoder"
-#define CONF_INPUT "input"
-#define CONF_GAPLESS_MP3_PLAYBACK "gapless_mp3_playback"
-
-#define CONF_BOOL_UNSET -1
-#define CONF_BOOL_INVALID -2
+#define CONF_DECODER "decoder"
+#define CONF_INPUT "input"
+#define CONF_GAPLESS_MP3_PLAYBACK "gapless_mp3_playback"
+#define CONF_PLAYLIST_PLUGIN "playlist_plugin"
+#define CONF_AUTO_UPDATE "auto_update"
+#define CONF_AUTO_UPDATE_DEPTH "auto_update_depth"
#define DEFAULT_PLAYLIST_MAX_LENGTH (1024*16)
#define DEFAULT_PLAYLIST_SAVE_ABSOLUTE_PATHS false
+#define MAX_FILTER_CHAIN_LENGTH 255
+
struct block_param {
char *name;
char *value;
int line;
+
+ /**
+ * This flag is false when nobody has queried the value of
+ * this option yet.
+ */
+ bool used;
};
struct config_param {
@@ -87,31 +96,57 @@ struct config_param {
struct block_param *block_params;
unsigned num_block_params;
+
+ /**
+ * This flag is false when nobody has queried the value of
+ * this option yet.
+ */
+ bool used;
};
+/**
+ * A GQuark for GError instances, resulting from malformed
+ * configuration.
+ */
+static inline GQuark
+config_quark(void)
+{
+ return g_quark_from_static_string("config");
+}
+
void config_global_init(void);
void config_global_finish(void);
-void config_read_file(const char *file);
-
/**
- * Adds a new configuration parameter. The name must be registered
- * with registerConfigParam().
+ * Call this function after all configuration has been evaluated. It
+ * checks for unused parameters, and logs warnings.
*/
-void
-config_add_param(const char *name, struct config_param *param);
+void config_global_check(void);
+
+bool
+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 *
config_get_next_param(const char *name, const struct config_param *last);
+G_GNUC_PURE
static inline struct config_param *
config_get_param(const char *name)
{
return config_get_next_param(name, NULL);
}
+/* Note on G_GNUC_PURE: Some of the functions declared pure are not
+ really pure in strict sense. They have side effect such that they
+ validate parameter's value and signal an error if it's invalid.
+ However, if the argument was already validated or we don't care
+ about the argument at all, this may be ignored so in the end, we
+ should be fine with calling those functions pure. */
+
+G_GNUC_PURE
const char *
config_get_string(const char *name, const char *default_value);
@@ -120,17 +155,31 @@ config_get_string(const char *name, const char *default_value);
* absolute path. If there is a tilde prefix, it is expanded. Aborts
* MPD if the path is not a valid absolute path.
*/
+/* 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_PURE
+unsigned
+config_get_unsigned(const char *name, unsigned default_value);
+
+G_GNUC_PURE
unsigned
config_get_positive(const char *name, unsigned default_value);
+G_GNUC_PURE
struct block_param *
config_get_block_param(const struct config_param *param, const char *name);
+G_GNUC_PURE
bool config_get_bool(const char *name, bool default_value);
+G_GNUC_PURE
const char *
config_get_block_string(const struct config_param *param, const char *name,
const char *default_value);
@@ -142,10 +191,12 @@ config_dup_block_string(const struct config_param *param, const char *name,
return g_strdup(config_get_block_string(param, name, default_value));
}
+G_GNUC_PURE
unsigned
config_get_block_unsigned(const struct config_param *param, const char *name,
unsigned default_value);
+G_GNUC_PURE
bool
config_get_block_bool(const struct config_param *param, const char *name,
bool default_value);
@@ -153,8 +204,8 @@ config_get_block_bool(const struct config_param *param, const char *name,
struct config_param *
config_new_param(const char *value, int line);
-void
-config_add_block_param(struct config_param *param, const char *name,
- const char *value, int line);
+bool
+config_add_block_param(struct config_param * param, const char *name,
+ const char *value, int line, GError **error_r);
#endif
diff --git a/src/crossfade.c b/src/crossfade.c
index 01552bf65..cdfd82879 100644
--- a/src/crossfade.c
+++ b/src/crossfade.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -17,6 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "crossfade.h"
#include "pcm_mix.h"
#include "chunk.h"
@@ -25,73 +26,112 @@
#include <assert.h>
#include <string.h>
+#include <stdlib.h>
+#include <glib.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "crossfade"
+
+#ifdef G_OS_WIN32
+static char *
+strtok_r(char *str, const char *delim, G_GNUC_UNUSED char **saveptr)
+{
+ return strtok(str, delim);
+}
+#endif
+
+static float mixramp_interpolate(char *ramp_list, float required_db)
+{
+ float db, secs, last_db = nan(""), last_secs = 0;
+ char *ramp_str, *save_str = NULL;
+
+ /* ramp_list is a string of pairs of dBs and seconds that describe the
+ * volume profile. Delimiters are semi-colons between pairs and spaces
+ * between the dB and seconds of a pair.
+ * The dB values must be monotonically increasing for this to work. */
+
+ while (1) {
+ /* Parse the dB tokens out of the input string. */
+ ramp_str = strtok_r(ramp_list, " ", &save_str);
+
+ /* Tell strtok to continue next time round. */
+ ramp_list = NULL;
+
+ /* Parse the dB value. */
+ if (NULL == ramp_str) {
+ return nan("");
+ }
+ db = (float)atof(ramp_str);
+
+ /* Parse the time. */
+ ramp_str = strtok_r(NULL, ";", &save_str);
+ if (NULL == ramp_str) {
+ return nan("");
+ }
+ secs = (float)atof(ramp_str);
+
+ /* Check for exact match. */
+ if (db == required_db) {
+ return secs;
+ }
+
+ /* Save if too quiet. */
+ if (db < required_db) {
+ last_db = db;
+ last_secs = secs;
+ continue;
+ }
+
+ /* If required db < any stored value, use the least. */
+ if (isnan(last_db)) {
+ return secs;
+ }
+
+ /* Finally, interpolate linearly. */
+ secs = last_secs + (required_db - last_db) * (secs - last_secs) / (db - last_db);
+ return secs;
+ }
+}
unsigned cross_fade_calc(float duration, float total_time,
+ float mixramp_db, float mixramp_delay,
+ float replay_gain_db, float replay_gain_prev_db,
+ char *mixramp_start, char *mixramp_prev_end,
const struct audio_format *af,
const struct audio_format *old_format,
unsigned max_chunks)
{
- unsigned int chunks;
+ unsigned int chunks = 0;
+ float chunks_f;
+ float mixramp_overlap;
- if (duration <= 0 || duration >= total_time ||
+ if (duration < 0 || duration >= total_time ||
/* we can't crossfade when the audio formats are different */
!audio_format_equals(af, old_format))
return 0;
- assert(duration > 0);
- assert(af->bits > 0);
- assert(af->channels > 0);
- assert(af->sample_rate > 0);
-
- chunks = audio_format_time_to_size(af) / CHUNK_SIZE;
- chunks = (chunks * duration + 0.5);
+ assert(duration >= 0);
+ assert(audio_format_valid(af));
+
+ chunks_f = (float)audio_format_time_to_size(af) / (float)CHUNK_SIZE;
+
+ if (isnan(mixramp_delay) || !(mixramp_start) || !(mixramp_prev_end)) {
+ chunks = (chunks_f * duration + 0.5);
+ } else {
+ /* Calculate mixramp overlap. */
+ mixramp_overlap = mixramp_interpolate(mixramp_start, mixramp_db - replay_gain_db)
+ + mixramp_interpolate(mixramp_prev_end, mixramp_db - replay_gain_prev_db);
+ if (!isnan(mixramp_overlap) && (mixramp_delay <= mixramp_overlap)) {
+ chunks = (chunks_f * (mixramp_overlap - mixramp_delay));
+ g_debug("will overlap %d chunks, %fs", chunks,
+ mixramp_overlap - mixramp_delay);
+ }
+ }
- if (chunks > max_chunks)
+ if (chunks > max_chunks) {
chunks = max_chunks;
+ g_warning("audio_buffer_size too small for computed MixRamp overlap");
+ }
return chunks;
}
-
-void cross_fade_apply(struct music_chunk *a, const struct music_chunk *b,
- const struct audio_format *format,
- unsigned int current_chunk, unsigned int num_chunks)
-{
- size_t size;
-
- assert(a != NULL);
- assert(b != NULL);
- assert(a->length == 0 || b->length == 0 ||
- audio_format_equals(&a->audio_format, &b->audio_format));
- assert(current_chunk <= num_chunks);
-
- if (a->tag == NULL && b->tag != NULL)
- /* merge the tag into the destination chunk */
- a->tag = tag_dup(b->tag);
-
- size = b->length > a->length
- ? a->length
- : b->length;
-
- pcm_mix(a->data,
- b->data,
- size,
- format,
- ((float)current_chunk) / num_chunks);
-
- if (b->length > a->length) {
- /* the second buffer is larger than the first one:
- there is unmixed rest at the end. Copy it over.
- The output buffer API guarantees that there is
- enough room in a->data. */
-
-#ifndef NDEBUG
- if (a->length == 0)
- a->audio_format = b->audio_format;
-#endif
-
- memcpy(a->data + a->length,
- b->data + a->length,
- b->length - a->length);
- a->length = b->length;
- }
-}
diff --git a/src/crossfade.h b/src/crossfade.h
index 1a09ede5b..096a62020 100644
--- a/src/crossfade.h
+++ b/src/crossfade.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -28,6 +28,12 @@ struct music_chunk;
*
* @param duration the requested crossfade duration
* @param total_time total_time the duration of the new song
+ * @param mixramp_db the current mixramp_db setting
+ * @param mixramp_delay the current mixramp_delay setting
+ * @param replay_gain_db the ReplayGain adjustment used for this song
+ * @param replay_gain_prev_db the ReplayGain adjustment used on the last song
+ * @param mixramp_start the next songs mixramp_start tag
+ * @param mixramp_prev_end the last songs mixramp_end setting
* @param af the audio format of the new song
* @param old_format the audio format of the current song
* @param max_chunks the maximum number of chunks
@@ -35,22 +41,11 @@ struct music_chunk;
* should be disabled for this song change
*/
unsigned cross_fade_calc(float duration, float total_time,
+ float mixramp_db, float mixramp_delay,
+ float replay_gain_db, float replay_gain_prev_db,
+ char *mixramp_start, char *mixramp_prev_end,
const struct audio_format *af,
const struct audio_format *old_format,
unsigned max_chunks);
-/**
- * Applies cross fading to two chunks, i.e. mixes these chunks.
- * Internally, this calls pcm_mix().
- *
- * @param a the chunk in the current song (and the destination chunk)
- * @param b the according chunk in the new song
- * @param format the audio format of both chunks (must be the same)
- * @param current_chunk the relative index of the current chunk
- * @param num_chunks the number of chunks used for cross fading
- */
-void cross_fade_apply(struct music_chunk *a, const struct music_chunk *b,
- const struct audio_format *format,
- unsigned int current_chunk, unsigned int num_chunks);
-
#endif
diff --git a/src/cue/cue_tag.c b/src/cue/cue_tag.c
index 6251b03e2..ba1172559 100644
--- a/src/cue/cue_tag.c
+++ b/src/cue/cue_tag.c
@@ -1,76 +1,78 @@
+#include "config.h"
#include "cue_tag.h"
+#include "tag.h"
-static struct tag*
-cue_tag_cd(struct Cdtext* cdtext, struct Rem* rem)
+#include <libcue/libcue.h>
+#include <assert.h>
+
+static struct tag *
+cue_tag_cd(struct Cdtext *cdtext, struct Rem *rem)
{
- char* tmp = NULL;
- struct tag* tag = NULL;
+ struct tag *tag;
+ char *tmp;
- //if (cdtext == NULL)
- //return NULL;
+ assert(cdtext != NULL);
tag = tag_new();
tag_begin_add(tag);
- { /* TAG_ITEM_ALBUM_ARTIST */
+ /* TAG_ALBUM_ARTIST */
if ((tmp = cdtext_get(PTI_PERFORMER, cdtext)) != NULL)
- tag_add_item(tag, TAG_ITEM_ALBUM_ARTIST, tmp);
+ tag_add_item(tag, TAG_ALBUM_ARTIST, tmp);
else if ((tmp = cdtext_get(PTI_SONGWRITER, cdtext)) != NULL)
- tag_add_item(tag, TAG_ITEM_ALBUM_ARTIST, tmp);
+ tag_add_item(tag, TAG_ALBUM_ARTIST, tmp);
else if ((tmp = cdtext_get(PTI_COMPOSER, cdtext)) != NULL)
- tag_add_item(tag, TAG_ITEM_ALBUM_ARTIST, tmp);
+ tag_add_item(tag, TAG_ALBUM_ARTIST, tmp);
else if ((tmp = cdtext_get(PTI_ARRANGER, cdtext)) != NULL)
- tag_add_item(tag, TAG_ITEM_ALBUM_ARTIST, tmp);
- /* TAG_ITEM_ALBUM_ARTIST */ }
+ tag_add_item(tag, TAG_ALBUM_ARTIST, tmp);
- { /* TAG_ITEM_ARTIST */
+ /* TAG_ARTIST */
if ((tmp = cdtext_get(PTI_PERFORMER, cdtext)) != NULL)
- tag_add_item(tag, TAG_ITEM_ARTIST, tmp);
+ tag_add_item(tag, TAG_ARTIST, tmp);
else if ((tmp = cdtext_get(PTI_SONGWRITER, cdtext)) != NULL)
- tag_add_item(tag, TAG_ITEM_ARTIST, tmp);
+ tag_add_item(tag, TAG_ARTIST, tmp);
else if ((tmp = cdtext_get(PTI_COMPOSER, cdtext)) != NULL)
- tag_add_item(tag, TAG_ITEM_ARTIST, tmp);
+ tag_add_item(tag, TAG_ARTIST, tmp);
else if ((tmp = cdtext_get(PTI_ARRANGER, cdtext)) != NULL)
- tag_add_item(tag, TAG_ITEM_ARTIST, tmp);
- /* TAG_ITEM_ARTIST */ }
+ tag_add_item(tag, TAG_ARTIST, tmp);
- /* TAG_ITEM_PERFORMER */
+ /* TAG_PERFORMER */
if ((tmp = cdtext_get(PTI_PERFORMER, cdtext)) != NULL)
- tag_add_item(tag, TAG_ITEM_PERFORMER, tmp);
+ tag_add_item(tag, TAG_PERFORMER, tmp);
- /* TAG_ITEM_COMPOSER */
+ /* TAG_COMPOSER */
if ((tmp = cdtext_get(PTI_COMPOSER, cdtext)) != NULL)
- tag_add_item(tag, TAG_ITEM_COMPOSER, tmp);
+ tag_add_item(tag, TAG_COMPOSER, tmp);
- /* TAG_ITEM_ALBUM */
+ /* TAG_ALBUM */
if ((tmp = cdtext_get(PTI_TITLE, cdtext)) != NULL)
- tag_add_item(tag, TAG_ITEM_ALBUM, tmp);
+ tag_add_item(tag, TAG_ALBUM, tmp);
- /* TAG_ITEM_GENRE */
+ /* TAG_GENRE */
if ((tmp = cdtext_get(PTI_GENRE, cdtext)) != NULL)
- tag_add_item(tag, TAG_ITEM_GENRE, tmp);
+ tag_add_item(tag, TAG_GENRE, tmp);
- /* TAG_ITEM_DATE */
+ /* TAG_DATE */
if ((tmp = rem_get(REM_DATE, rem)) != NULL)
- tag_add_item(tag, TAG_ITEM_DATE, tmp);
+ tag_add_item(tag, TAG_DATE, tmp);
- /* TAG_ITEM_COMMENT */
+ /* TAG_COMMENT */
if ((tmp = cdtext_get(PTI_MESSAGE, cdtext)) != NULL)
- tag_add_item(tag, TAG_ITEM_COMMENT, tmp);
+ tag_add_item(tag, TAG_COMMENT, tmp);
- /* TAG_ITEM_DISC */
+ /* TAG_DISC */
if ((tmp = cdtext_get(PTI_DISC_ID, cdtext)) != NULL)
- tag_add_item(tag, TAG_ITEM_DISC, tmp);
+ tag_add_item(tag, TAG_DISC, tmp);
/* stream name, usually empty
- * tag_add_item(tag, TAG_ITEM_NAME,);
+ * tag_add_item(tag, TAG_NAME,);
*/
/* REM MUSICBRAINZ entry?
@@ -82,175 +84,152 @@ cue_tag_cd(struct Cdtext* cdtext, struct Rem* rem)
tag_end_add(tag);
- if (tag != NULL)
- {
- if (tag_is_empty(tag))
- {
- tag_free(tag);
- return NULL;
- }
- else
- return tag;
- }
- else
+ if (tag_is_empty(tag)) {
+ tag_free(tag);
return NULL;
+ }
+
+ return tag;
}
-static struct tag*
-cue_tag_track(struct Cdtext* cdtext, struct Rem* rem)
+static struct tag *
+cue_tag_track(struct Cdtext *cdtext, struct Rem *rem)
{
- char* tmp = NULL;
- struct tag* tag = NULL;
+ struct tag *tag;
+ char *tmp;
- //if (cdtext == NULL)
- //return NULL;
+ assert(cdtext != NULL);
tag = tag_new();
tag_begin_add(tag);
- { /* TAG_ITEM_ARTIST */
+ /* TAG_ARTIST */
if ((tmp = cdtext_get(PTI_PERFORMER, cdtext)) != NULL)
- tag_add_item(tag, TAG_ITEM_ARTIST, tmp);
+ tag_add_item(tag, TAG_ARTIST, tmp);
else if ((tmp = cdtext_get(PTI_SONGWRITER, cdtext)) != NULL)
- tag_add_item(tag, TAG_ITEM_ARTIST, tmp);
+ tag_add_item(tag, TAG_ARTIST, tmp);
else if ((tmp = cdtext_get(PTI_COMPOSER, cdtext)) != NULL)
- tag_add_item(tag, TAG_ITEM_ARTIST, tmp);
+ tag_add_item(tag, TAG_ARTIST, tmp);
else if ((tmp = cdtext_get(PTI_ARRANGER, cdtext)) != NULL)
- tag_add_item(tag, TAG_ITEM_ARTIST, tmp);
- /* TAG_ITEM_ARTIST */ }
+ tag_add_item(tag, TAG_ARTIST, tmp);
- /* TAG_ITEM_TITLE */
+ /* TAG_TITLE */
if ((tmp = cdtext_get(PTI_TITLE, cdtext)) != NULL)
- tag_add_item(tag, TAG_ITEM_TITLE, tmp);
+ tag_add_item(tag, TAG_TITLE, tmp);
- /* TAG_ITEM_GENRE */
+ /* TAG_GENRE */
if ((tmp = cdtext_get(PTI_GENRE, cdtext)) != NULL)
- tag_add_item(tag, TAG_ITEM_GENRE, tmp);
+ tag_add_item(tag, TAG_GENRE, tmp);
- /* TAG_ITEM_DATE */
+ /* TAG_DATE */
if ((tmp = rem_get(REM_DATE, rem)) != NULL)
- tag_add_item(tag, TAG_ITEM_DATE, tmp);
+ tag_add_item(tag, TAG_DATE, tmp);
- /* TAG_ITEM_COMPOSER */
+ /* TAG_COMPOSER */
if ((tmp = cdtext_get(PTI_COMPOSER, cdtext)) != NULL)
- tag_add_item(tag, TAG_ITEM_COMPOSER, tmp);
+ tag_add_item(tag, TAG_COMPOSER, tmp);
- /* TAG_ITEM_PERFORMER */
+ /* TAG_PERFORMER */
if ((tmp = cdtext_get(PTI_PERFORMER, cdtext)) != NULL)
- tag_add_item(tag, TAG_ITEM_PERFORMER, tmp);
+ tag_add_item(tag, TAG_PERFORMER, tmp);
- /* TAG_ITEM_COMMENT */
+ /* TAG_COMMENT */
if ((tmp = cdtext_get(PTI_MESSAGE, cdtext)) != NULL)
- tag_add_item(tag, TAG_ITEM_COMMENT, tmp);
+ tag_add_item(tag, TAG_COMMENT, tmp);
- /* TAG_ITEM_DISC */
+ /* TAG_DISC */
if ((tmp = cdtext_get(PTI_DISC_ID, cdtext)) != NULL)
- tag_add_item(tag, TAG_ITEM_DISC, tmp);
+ tag_add_item(tag, TAG_DISC, tmp);
tag_end_add(tag);
- if (tag != NULL)
- {
- if (tag_is_empty(tag))
- {
- tag_free(tag);
- return NULL;
- }
- else
- return tag;
- }
- else
+ if (tag_is_empty(tag)) {
+ tag_free(tag);
return NULL;
+ }
+
+ return tag;
}
-struct tag*
-cue_tag_file( FILE* fp,
- const unsigned int tnum)
+struct tag *
+cue_tag(struct Cd *cd, unsigned tnum)
{
- struct tag* cd_tag = NULL;
- struct tag* track_tag = NULL;
- struct Cd* cd = NULL;
+ struct tag *cd_tag, *track_tag, *tag;
+ struct Track *track;
- if (tnum > 256)
- return NULL;
+ assert(cd != NULL);
- if (fp == NULL)
+ track = cd_get_track(cd, tnum);
+ if (track == NULL)
return NULL;
- else
- cd = cue_parse_file(fp);
- if (cd == NULL)
- return NULL;
- else
- {
- /* tag from CDtext info */
- cd_tag = cue_tag_cd( cd_get_cdtext(cd),
- cd_get_rem(cd));
+ /* 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( cd_get_track(cd, tnum)),
- track_get_rem( cd_get_track(cd, tnum)));
+ /* tag from TRACKtext info */
+ track_tag = cue_tag_track(track_get_cdtext(track),
+ track_get_rem(track));
- cd_delete(cd);
- }
+ tag = tag_merge_replace(cd_tag, track_tag);
+ if (tag == NULL)
+ return NULL;
- return tag_merge_replace(cd_tag, track_tag);
+ 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_string( char* str,
- const unsigned int tnum)
+struct tag *
+cue_tag_file(FILE *fp, unsigned tnum)
{
- struct tag* cd_tag = NULL;
- struct tag* track_tag = NULL;
- struct tag* merge_tag = NULL;
- struct Cd* cd = NULL;
+ struct Cd *cd;
+ struct tag *tag;
- if (tnum > 256)
- return NULL;
+ assert(fp != NULL);
- if (str == NULL)
+ if (tnum > 256)
return NULL;
- else
- cd = cue_parse_string(str);
+ cd = cue_parse_file(fp);
if (cd == NULL)
return NULL;
- else
- {
- /* 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( cd_get_track(cd, tnum)),
- track_get_rem( cd_get_track(cd, tnum)));
+ tag = cue_tag(cd, tnum);
+ cd_delete(cd);
- cd_delete(cd);
- }
+ return tag;
+}
- if ((cd_tag != NULL) && (track_tag != NULL))
- {
- merge_tag = tag_merge(cd_tag, track_tag);
- tag_free(cd_tag);
- tag_free(track_tag);
- return merge_tag;
- }
+struct tag *
+cue_tag_string(const char *str, unsigned tnum)
+{
+ struct Cd *cd;
+ struct tag *tag;
- else if (cd_tag != NULL)
- {
- return cd_tag;
- }
+ assert(str != NULL);
- else if (track_tag != NULL)
- {
- return track_tag;
- }
+ if (tnum > 256)
+ return NULL;
- else
+ 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
index adc4c466e..1ddaa59c8 100644
--- a/src/cue/cue_tag.h
+++ b/src/cue/cue_tag.h
@@ -1,20 +1,23 @@
#ifndef MPD_CUE_TAG_H
#define MPD_CUE_TAG_H
-#include "config.h"
+#include "check.h"
#ifdef HAVE_CUE /* libcue */
-#include <libcue/libcue.h>
-#include "../tag.h"
+#include <stdio.h>
-struct tag*
-cue_tag_file( FILE*,
- const unsigned int);
+struct tag;
+struct Cd;
-struct tag*
-cue_tag_string( char*,
- const unsigned int);
+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 33b2953a9..852541375 100644
--- a/src/daemon.c
+++ b/src/daemon.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -17,6 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "daemon.h"
#include <glib.h>
@@ -45,78 +46,72 @@
static char *user_name;
/** the Unix user id which MPD runs as */
-static uid_t user_uid;
+static uid_t user_uid = (uid_t)-1;
/** the Unix group id which MPD runs as */
-static gid_t user_gid;
+static gid_t user_gid = (pid_t)-1;
/** the absolute path of the pidfile */
static char *pidfile;
-#endif
+/* whether "group" conf. option was given */
+static bool had_group = false;
+
void
daemonize_kill(void)
{
-#ifndef WIN32
FILE *fp;
int pid, ret;
if (pidfile == NULL)
- g_error("no pid_file specified in the config file");
+ MPD_ERROR("no pid_file specified in the config file");
fp = fopen(pidfile, "r");
if (fp == NULL)
- g_error("unable to open pid file \"%s\": %s",
- pidfile, g_strerror(errno));
+ MPD_ERROR("unable to open pid file \"%s\": %s",
+ pidfile, g_strerror(errno));
if (fscanf(fp, "%i", &pid) != 1) {
- g_error("unable to read the pid from file \"%s\"",
- pidfile);
+ MPD_ERROR("unable to read the pid from file \"%s\"",
+ pidfile);
}
fclose(fp);
ret = kill(pid, SIGTERM);
if (ret < 0)
- g_error("unable to kill proccess %i: %s",
- pid, g_strerror(errno));
+ MPD_ERROR("unable to kill proccess %i: %s",
+ pid, g_strerror(errno));
exit(EXIT_SUCCESS);
-#else
- g_error("--kill is not available on WIN32");
-#endif
}
void
daemonize_close_stdin(void)
{
- int fd = open("/dev/null", O_RDONLY);
-
- if (fd < 0)
- close(STDIN_FILENO);
- else if (fd != STDIN_FILENO) {
- dup2(fd, STDIN_FILENO);
- close(fd);
- }
+ close(STDIN_FILENO);
+ open("/dev/null", O_RDONLY);
}
void
daemonize_set_user(void)
{
-#ifndef WIN32
if (user_name == NULL)
return;
- /* get uid */
- if (setgid(user_gid) == -1) {
- g_error("cannot setgid for user \"%s\": %s",
- user_name, g_strerror(errno));
+ /* set gid */
+ if (user_gid != (gid_t)-1 && user_gid != getgid()) {
+ if (setgid(user_gid) == -1) {
+ MPD_ERROR("cannot setgid to %d: %s",
+ (int)user_gid, g_strerror(errno));
+ }
}
+
#ifdef _BSD_SOURCE
/* init suplementary groups
* (must be done before we change our uid)
*/
- if (initgroups(user_name, user_gid) == -1) {
+ if (!had_group && initgroups(user_name, user_gid) == -1) {
g_warning("cannot init supplementary groups "
"of user \"%s\": %s",
user_name, g_strerror(errno));
@@ -124,50 +119,58 @@ daemonize_set_user(void)
#endif
/* set uid */
- if (setuid(user_uid) == -1) {
- g_error("cannot change to uid of user \"%s\": %s",
- user_name, g_strerror(errno));
+ if (user_uid != (uid_t)-1 && user_uid != getuid() &&
+ setuid(user_uid) == -1) {
+ MPD_ERROR("cannot change to uid of user \"%s\": %s",
+ user_name, g_strerror(errno));
}
-#endif
}
-#ifndef G_OS_WIN32
static void
daemonize_detach(void)
{
- pid_t pid;
-
/* flush all file handles before duplicating the buffers */
fflush(NULL);
- /* detach from parent process */
+#ifdef HAVE_DAEMON
+
+ if (daemon(0, 1))
+ MPD_ERROR("daemon() failed: %s", g_strerror(errno));
- pid = fork();
- if (pid < 0)
- g_error("fork() failed: %s", g_strerror(errno));
+#elif defined(HAVE_FORK)
- if (pid > 0)
+ /* detach from parent process */
+
+ switch (fork()) {
+ case -1:
+ MPD_ERROR("fork() failed: %s", g_strerror(errno));
+ case 0:
+ break;
+ default:
/* exit the parent process */
_exit(EXIT_SUCCESS);
+ }
/* release the current working directory */
if (chdir("/") < 0)
- g_error("problems changing to root directory");
+ MPD_ERROR("problems changing to root directory");
/* detach from the current session */
setsid();
+#else
+ MPD_ERROR("no support for daemonizing");
+#endif
+
g_debug("daemonized!");
}
-#endif
void
daemonize(bool detach)
{
-#ifndef WIN32
FILE *fp = NULL;
if (pidfile != NULL) {
@@ -176,8 +179,8 @@ daemonize(bool detach)
g_debug("opening pid file");
fp = fopen(pidfile, "w+");
if (!fp) {
- g_error("could not create pid file \"%s\": %s",
- pidfile, g_strerror(errno));
+ MPD_ERROR("could not create pid file \"%s\": %s",
+ pidfile, g_strerror(errno));
}
}
@@ -189,47 +192,45 @@ daemonize(bool detach)
fprintf(fp, "%lu\n", (unsigned long)getpid());
fclose(fp);
}
-#else
- /* no daemonization on WIN32 */
- (void)detach;
-#endif
}
void
-daemonize_init(const char *user, const char *_pidfile)
+daemonize_init(const char *user, const char *group, const char *_pidfile)
{
-#ifndef WIN32
- if (user != NULL && strcmp(user, g_get_user_name()) != 0) {
- struct passwd *pwd;
-
- user_name = g_strdup(user);
-
- pwd = getpwnam(user_name);
- if (pwd == NULL)
- g_error("no such user \"%s\"", user_name);
+ if (user) {
+ struct passwd *pwd = getpwnam(user);
+ if (!pwd)
+ MPD_ERROR("no such user \"%s\"", user);
user_uid = pwd->pw_uid;
user_gid = pwd->pw_gid;
+ user_name = g_strdup(user);
+
/* this is needed by libs such as arts */
g_setenv("HOME", pwd->pw_dir, true);
}
+ if (group) {
+ struct group *grp = grp = getgrnam(group);
+ if (!grp)
+ MPD_ERROR("no such group \"%s\"", group);
+ user_gid = grp->gr_gid;
+ had_group = true;
+ }
+
+
pidfile = g_strdup(_pidfile);
-#else
- (void)user;
- (void)_pidfile;
-#endif
}
void
daemonize_finish(void)
{
-#ifndef WIN32
if (pidfile != NULL)
unlink(pidfile);
g_free(user_name);
g_free(pidfile);
-#endif
}
+
+#endif
diff --git a/src/daemon.h b/src/daemon.h
index 5b3f9a7dc..71039543c 100644
--- a/src/daemon.h
+++ b/src/daemon.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -20,34 +20,72 @@
#ifndef DAEMON_H
#define DAEMON_H
+#include "mpd_error.h"
+
#include <stdbool.h>
+#ifndef WIN32
void
-daemonize_init(const char *user, const char *pidfile);
+daemonize_init(const char *user, const char *group, const char *pidfile);
+#else
+static inline void
+daemonize_init(const char *user, const char *group, const char *pidfile)
+{ (void)user; (void)group; (void)pidfile; }
+#endif
+#ifndef WIN32
void
daemonize_finish(void);
+#else
+static inline void
+daemonize_finish(void)
+{ /* nop */ }
+#endif
/**
* Kill the MPD which is currently running, pid determined from the
* pid file.
*/
+#ifndef WIN32
void
daemonize_kill(void);
+#else
+#include <glib.h>
+static inline void
+daemonize_kill(void)
+{ MPD_ERROR("--kill is not available on WIN32"); }
+#endif
/**
* Close stdin (fd 0) and re-open it as /dev/null.
*/
+#ifndef WIN32
void
daemonize_close_stdin(void);
+#else
+static inline void
+daemonize_close_stdin(void) {}
+#endif
/**
* Change to the configured Unix user.
*/
+#ifndef WIN32
void
daemonize_set_user(void);
+#else
+static inline void
+daemonize_set_user(void)
+{ /* nop */ }
+#endif
+#ifndef WIN32
void
daemonize(bool detach);
+#else
+static inline void
+daemonize(bool detach)
+{ (void)detach; }
+#endif
#endif
diff --git a/src/database.c b/src/database.c
index 5a06dda98..f255d6ca5 100644
--- a/src/database.c
+++ b/src/database.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -17,13 +17,16 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "database.h"
#include "directory.h"
#include "directory_save.h"
#include "song.h"
#include "path.h"
#include "stats.h"
-#include "config.h"
+#include "text_file.h"
+#include "tag.h"
+#include "tag_internal.h"
#include <glib.h>
@@ -40,8 +43,14 @@
#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,
+};
static char *database_path;
@@ -230,20 +239,27 @@ db_save(void)
return false;
}
- /* block signals when writing the db so we don't get a corrupted db */
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);
- if (directory_save(fp, music_root) < 0) {
+ directory_save(fp, music_root);
+
+ if (ferror(fp)) {
g_warning("Failed to write to database file: %s",
strerror(errno));
- while (fclose(fp) && errno == EINTR);
+ fclose(fp);
return false;
}
- while (fclose(fp) && errno == EINTR);
+ fclose(fp);
if (stat(database_path, &st) == 0)
database_mtime = st.st_mtime;
@@ -256,64 +272,64 @@ db_load(GError **error)
{
FILE *fp = NULL;
struct stat st;
- char buffer[100];
+ 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);
- if (!music_root)
- music_root = directory_new("", NULL);
- while (!(fp = fopen(database_path, "r")) && errno == EINTR) ;
+ 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 */
- if (!fgets(buffer, sizeof(buffer), fp)) {
- fclose(fp);
- g_set_error(error, db_quark(), 0, "Unexpected end of file");
- return false;
- }
-
- g_strchomp(buffer);
-
- if (0 != strcmp(DIRECTORY_INFO_BEGIN, buffer)) {
+ 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;
}
- while (fgets(buffer, sizeof(buffer), fp) &&
- !g_str_has_prefix(buffer, DIRECTORY_INFO_END)) {
- g_strchomp(buffer);
+ memset(tags, false, sizeof(tags));
- if (g_str_has_prefix(buffer, DIRECTORY_MPD_VERSION)) {
+ 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(buffer, DIRECTORY_FS_CHARSET)) {
+ } 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 = &(buffer[strlen(DIRECTORY_FS_CHARSET)]);
+ new_charset = line + sizeof(DIRECTORY_FS_CHARSET) - 1;
old_charset = path_get_fs_charset();
if (old_charset != NULL
&& strcmp(new_charset, old_charset)) {
@@ -323,20 +339,51 @@ db_load(GError **error)
"\"%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", buffer);
+ "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");
- success = directory_load(fp, music_root, error);
- while (fclose(fp) && errno == EINTR) ;
+ success = directory_load(fp, music_root, buffer, error);
+ g_string_free(buffer, true);
+ fclose(fp);
if (!success)
return false;
diff --git a/src/database.h b/src/database.h
index f4420928d..67149b20b 100644
--- a/src/database.h
+++ b/src/database.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
diff --git a/src/dbUtils.c b/src/dbUtils.c
index 2e2552698..f950d42cc 100644
--- a/src/dbUtils.c
+++ b/src/dbUtils.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -17,6 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "dbUtils.h"
#include "locate.h"
#include "directory.h"
@@ -59,7 +60,7 @@ static int
printSongInDirectory(struct song *song, G_GNUC_UNUSED void *data)
{
struct client *client = data;
- song_print_url(client, song);
+ song_print_uri(client, song);
return 0;
}
@@ -74,7 +75,7 @@ searchInDirectory(struct song *song, void *_data)
struct search_data *data = _data;
if (locate_song_search(song, data->criteria))
- return song_print_info(data->client, song);
+ song_print_info(data->client, song);
return 0;
}
@@ -104,7 +105,7 @@ findInDirectory(struct song *song, void *_data)
struct search_data *data = _data;
if (locate_song_match(song, data->criteria))
- return song_print_info(data->client, song);
+ song_print_info(data->client, song);
return 0;
}
@@ -134,8 +135,7 @@ searchStatsInDirectory(struct song *song, void *data)
if (locate_song_match(song, stats->criteria)) {
stats->numberOfSongs++;
- if (song->tag->time > 0)
- stats->playTime += song->tag->time;
+ stats->playTime += song_get_duration(song);
}
return 0;
@@ -168,7 +168,7 @@ int printAllIn(struct client *client, const char *name)
static int
directoryAddSongToPlaylist(struct song *song, G_GNUC_UNUSED void *data)
{
- return addSongToPlaylist(&g_playlist, song, NULL);
+ return playlist_append_song(&g_playlist, song, NULL);
}
struct add_data {
@@ -200,6 +200,28 @@ int addAllInToStoredPlaylist(const char *name, const char *utf8file)
}
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;
@@ -237,7 +259,7 @@ visitTag(struct client *client, struct strset *set,
bool found = false;
if (tagType == LOCATE_TAG_FILE_TYPE) {
- song_print_url(client, song);
+ song_print_uri(client, song);
return;
}
diff --git a/src/dbUtils.h b/src/dbUtils.h
index 1382c243e..bba253154 100644
--- a/src/dbUtils.h
+++ b/src/dbUtils.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -40,6 +40,10 @@ 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);
+
+int
searchStatsForSongsIn(struct client *client, const char *name,
const struct locate_item_list *criteria);
diff --git a/src/decoder/_flac_common.c b/src/decoder/_flac_common.c
index 7c8fe9875..8dd22a253 100644
--- a/src/decoder/_flac_common.c
+++ b/src/decoder/_flac_common.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -21,7 +21,11 @@
* Common data structures and functions used by FLAC and OggFLAC
*/
+#include "config.h"
#include "_flac_common.h"
+#include "flac_metadata.h"
+#include "flac_pcm.h"
+#include "audio_check.h"
#include <glib.h>
@@ -31,186 +35,104 @@ void
flac_data_init(struct flac_data *data, struct decoder * decoder,
struct input_stream *input_stream)
{
- data->time = 0;
+ pcm_buffer_init(&data->buffer);
+
+ data->unsupported = false;
+ data->initialized = false;
+ data->total_frames = 0;
+ data->first_frame = 0;
+ data->next_frame = 0;
+
data->position = 0;
- data->bit_rate = 0;
data->decoder = decoder;
data->input_stream = input_stream;
- data->replay_gain_info = NULL;
data->tag = NULL;
}
-static void
-flac_find_float_comment(const FLAC__StreamMetadata *block,
- const char *cmnt, float *fl, bool *found_r)
+void
+flac_data_deinit(struct flac_data *data)
{
- int offset;
- size_t pos;
- int len;
- unsigned char tmp, *p;
-
- offset = FLAC__metadata_object_vorbiscomment_find_entry_from(block, 0,
- cmnt);
- if (offset < 0)
- return;
-
- pos = strlen(cmnt) + 1; /* 1 is for '=' */
- len = block->data.vorbis_comment.comments[offset].length - pos;
- if (len <= 0)
- return;
-
- p = &block->data.vorbis_comment.comments[offset].entry[pos];
- tmp = p[len];
- p[len] = '\0';
- *fl = (float)atof((char *)p);
- p[len] = tmp;
-
- *found_r = true;
-}
+ pcm_buffer_deinit(&data->buffer);
-static void
-flac_parse_replay_gain(const FLAC__StreamMetadata *block,
- struct flac_data *data)
-{
- bool found = false;
-
- if (data->replay_gain_info)
- replay_gain_info_free(data->replay_gain_info);
-
- data->replay_gain_info = replay_gain_info_new();
-
- flac_find_float_comment(block, "replaygain_album_gain",
- &data->replay_gain_info->tuples[REPLAY_GAIN_ALBUM].gain,
- &found);
- flac_find_float_comment(block, "replaygain_album_peak",
- &data->replay_gain_info->tuples[REPLAY_GAIN_ALBUM].peak,
- &found);
- flac_find_float_comment(block, "replaygain_track_gain",
- &data->replay_gain_info->tuples[REPLAY_GAIN_TRACK].gain,
- &found);
- flac_find_float_comment(block, "replaygain_track_peak",
- &data->replay_gain_info->tuples[REPLAY_GAIN_TRACK].peak,
- &found);
-
- if (!found) {
- replay_gain_info_free(data->replay_gain_info);
- data->replay_gain_info = NULL;
- }
+ if (data->tag != NULL)
+ tag_free(data->tag);
}
-/**
- * Checks if the specified name matches the entry's name, and if yes,
- * returns the comment value (not null-temrinated).
- */
-static const char *
-flac_comment_value(const FLAC__StreamMetadata_VorbisComment_Entry *entry,
- const char *name, const char *char_tnum, size_t *length_r)
+static enum sample_format
+flac_sample_format(unsigned bits_per_sample)
{
- size_t name_length = strlen(name);
- size_t char_tnum_length = 0;
- const char *comment = (const char*)entry->entry;
-
- if (entry->length <= name_length ||
- g_ascii_strncasecmp(comment, name, name_length) != 0)
- return NULL;
-
- if (char_tnum != NULL) {
- char_tnum_length = strlen(char_tnum);
- if (entry->length > name_length + char_tnum_length + 2 &&
- comment[name_length] == '[' &&
- g_ascii_strncasecmp(comment + name_length + 1,
- char_tnum, char_tnum_length) == 0 &&
- comment[name_length + char_tnum_length + 1] == ']')
- name_length = name_length + char_tnum_length + 2;
- else if (entry->length > name_length + char_tnum_length &&
- g_ascii_strncasecmp(comment + name_length,
- char_tnum, char_tnum_length) == 0)
- name_length = name_length + char_tnum_length;
- }
+ switch (bits_per_sample) {
+ case 8:
+ return SAMPLE_FORMAT_S8;
- if (comment[name_length] == '=') {
- *length_r = entry->length - name_length - 1;
- return comment + name_length + 1;
- }
+ case 16:
+ return SAMPLE_FORMAT_S16;
- return NULL;
-}
+ case 24:
+ return SAMPLE_FORMAT_S24_P32;
-/**
- * Check if the comment's name equals the passed name, and if so, copy
- * the comment value into the tag.
- */
-static bool
-flac_copy_comment(struct tag *tag,
- const FLAC__StreamMetadata_VorbisComment_Entry *entry,
- const char *name, enum tag_type tag_type,
- const char *char_tnum)
-{
- const char *value;
- size_t value_length;
+ case 32:
+ return SAMPLE_FORMAT_S32;
- value = flac_comment_value(entry, name, char_tnum, &value_length);
- if (value != NULL) {
- tag_add_item_n(tag, tag_type, value, value_length);
- return true;
+ default:
+ return SAMPLE_FORMAT_UNDEFINED;
}
-
- 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 void
-flac_parse_comment(struct tag *tag, const char *char_tnum,
- const FLAC__StreamMetadata_VorbisComment_Entry *entry)
+flac_got_stream_info(struct flac_data *data,
+ const FLAC__StreamMetadata_StreamInfo *stream_info)
{
- assert(tag != NULL);
-
- if (flac_copy_comment(tag, entry, VORBIS_COMMENT_TRACK_KEY,
- TAG_ITEM_TRACK, char_tnum) ||
- flac_copy_comment(tag, entry, VORBIS_COMMENT_DISC_KEY,
- TAG_ITEM_DISC, char_tnum) ||
- flac_copy_comment(tag, entry, "album artist",
- TAG_ITEM_ALBUM_ARTIST, char_tnum))
+ if (data->initialized || data->unsupported)
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))
- return;
-}
+ GError *error = NULL;
+ if (!audio_format_init_checked(&data->audio_format,
+ stream_info->sample_rate,
+ flac_sample_format(stream_info->bits_per_sample),
+ stream_info->channels, &error)) {
+ g_warning("%s", error->message);
+ g_error_free(error);
+ data->unsupported = true;
+ return;
+ }
-void
-flac_vorbis_comments_to_tag(struct tag *tag, const char *char_tnum,
- const FLAC__StreamMetadata *block)
-{
- FLAC__StreamMetadata_VorbisComment_Entry *comments =
- block->data.vorbis_comment.comments;
+ data->frame_size = audio_format_frame_size(&data->audio_format);
- for (unsigned i = block->data.vorbis_comment.num_comments; i > 0; --i)
- flac_parse_comment(tag, char_tnum, comments++);
+ if (data->total_frames == 0)
+ data->total_frames = stream_info->total_samples;
+
+ data->initialized = true;
}
void flac_metadata_common_cb(const FLAC__StreamMetadata * block,
struct flac_data *data)
{
- const FLAC__StreamMetadata_StreamInfo *si = &(block->data.stream_info);
+ if (data->unsupported)
+ return;
+
+ struct replay_gain_info rgi;
+ char *mixramp_start;
+ char *mixramp_end;
+ float replay_gain_db = 0;
switch (block->type) {
case FLAC__METADATA_TYPE_STREAMINFO:
- data->audio_format.bits = (int8_t)si->bits_per_sample;
- data->audio_format.sample_rate = si->sample_rate;
- data->audio_format.channels = (int8_t)si->channels;
- data->total_time = ((float)si->total_samples) / (si->sample_rate);
+ flac_got_stream_info(data, &block->data.stream_info);
break;
+
case FLAC__METADATA_TYPE_VORBIS_COMMENT:
- flac_parse_replay_gain(block, data);
+ if (flac_parse_replay_gain(&rgi, block))
+ replay_gain_db = decoder_replay_gain(data->decoder, &rgi);
+ if (flac_parse_mixramp(&mixramp_start, &mixramp_end, block)) {
+ g_debug("setting mixramp_tags");
+ decoder_mixramp(data->decoder, replay_gain_db,
+ mixramp_start, mixramp_end);
+ }
if (data->tag != NULL)
- flac_vorbis_comments_to_tag(data->tag, NULL, block);
+ flac_vorbis_comments_to_tag(data->tag, NULL,
+ &block->data.vorbis_comment);
default:
break;
@@ -239,187 +161,82 @@ void flac_error_common_cb(const char *plugin,
}
}
-static void flac_convert_stereo16(int16_t *dest,
- const FLAC__int32 * const buf[],
- unsigned int position, unsigned int end)
-{
- for (; position < end; ++position) {
- *dest++ = buf[0][position];
- *dest++ = buf[1][position];
- }
-}
-
-static void
-flac_convert_16(int16_t *dest,
- unsigned int num_channels,
- const FLAC__int32 * const buf[],
- unsigned int position, unsigned int end)
-{
- unsigned int c_chan;
-
- for (; position < end; ++position)
- for (c_chan = 0; c_chan < num_channels; c_chan++)
- *dest++ = buf[c_chan][position];
-}
-
/**
- * Note: this function also handles 24 bit files!
+ * This function attempts to call decoder_initialized() in case there
+ * was no STREAMINFO block. This is allowed for nonseekable streams,
+ * where the server sends us only a part of the file, without
+ * providing the STREAMINFO block from the beginning of the file
+ * (e.g. when seeking with SqueezeBox Server).
*/
-static void
-flac_convert_32(int32_t *dest,
- unsigned int num_channels,
- const FLAC__int32 * const buf[],
- unsigned int position, unsigned int end)
-{
- unsigned int c_chan;
-
- for (; position < end; ++position)
- for (c_chan = 0; c_chan < num_channels; c_chan++)
- *dest++ = buf[c_chan][position];
-}
-
-static void
-flac_convert_8(int8_t *dest,
- unsigned int num_channels,
- const FLAC__int32 * const buf[],
- unsigned int position, unsigned int end)
-{
- unsigned int c_chan;
+static bool
+flac_got_first_frame(struct flac_data *data, const FLAC__FrameHeader *header)
+{
+ if (data->unsupported)
+ return false;
+
+ GError *error = NULL;
+ if (!audio_format_init_checked(&data->audio_format,
+ header->sample_rate,
+ flac_sample_format(header->bits_per_sample),
+ header->channels, &error)) {
+ g_warning("%s", error->message);
+ g_error_free(error);
+ data->unsupported = true;
+ return false;
+ }
- for (; position < end; ++position)
- for (c_chan = 0; c_chan < num_channels; c_chan++)
- *dest++ = buf[c_chan][position];
-}
+ data->frame_size = audio_format_frame_size(&data->audio_format);
-static void flac_convert(unsigned char *dest,
- unsigned int num_channels,
- unsigned int bytes_per_sample,
- const FLAC__int32 * const buf[],
- unsigned int position, unsigned int end)
-{
- switch (bytes_per_sample) {
- case 2:
- if (num_channels == 2)
- flac_convert_stereo16((int16_t*)dest, buf,
- position, end);
- else
- flac_convert_16((int16_t*)dest, num_channels, buf,
- position, end);
- break;
+ decoder_initialized(data->decoder, &data->audio_format,
+ data->input_stream->seekable,
+ (float)data->total_frames /
+ (float)data->audio_format.sample_rate);
- case 4:
- flac_convert_32((int32_t*)dest, num_channels, buf,
- position, end);
- break;
+ data->initialized = true;
- case 1:
- flac_convert_8((int8_t*)dest, num_channels, buf,
- position, end);
- break;
- }
+ return true;
}
FLAC__StreamDecoderWriteStatus
flac_common_write(struct flac_data *data, const FLAC__Frame * frame,
- const FLAC__int32 *const buf[])
+ const FLAC__int32 *const buf[],
+ FLAC__uint64 nbytes)
{
- unsigned int c_samp;
- const unsigned int num_channels = frame->header.channels;
- const unsigned int bytes_per_sample =
- audio_format_sample_size(&data->audio_format);
- const unsigned int bytes_per_channel =
- bytes_per_sample * frame->header.channels;
- const unsigned int max_samples = FLAC_CHUNK_SIZE / bytes_per_channel;
- unsigned int num_samples;
enum decoder_command cmd;
+ void *buffer;
+ unsigned bit_rate;
- if (bytes_per_sample != 1 && bytes_per_sample != 2 &&
- bytes_per_sample != 4)
- /* exotic unsupported bit rate */
+ if (!data->initialized && !flac_got_first_frame(data, &frame->header))
return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT;
- for (c_samp = 0; c_samp < frame->header.blocksize;
- c_samp += num_samples) {
- num_samples = frame->header.blocksize - c_samp;
- if (num_samples > max_samples)
- num_samples = max_samples;
-
- flac_convert(data->chunk,
- num_channels, bytes_per_sample, buf,
- c_samp, c_samp + num_samples);
-
- cmd = decoder_data(data->decoder, data->input_stream,
- data->chunk,
- num_samples * bytes_per_channel,
- data->time, data->bit_rate,
- data->replay_gain_info);
- switch (cmd) {
- case DECODE_COMMAND_NONE:
- case DECODE_COMMAND_START:
- break;
-
- case DECODE_COMMAND_STOP:
- return
- FLAC__STREAM_DECODER_WRITE_STATUS_ABORT;
-
- case DECODE_COMMAND_SEEK:
- return
- FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE;
- }
- }
-
- return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE;
-}
-
-#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7
+ size_t buffer_size = frame->header.blocksize * data->frame_size;
+ buffer = pcm_buffer_get(&data->buffer, buffer_size);
-char*
-flac_cue_track( const char* pathname,
- const unsigned int tnum)
-{
- FLAC__bool success;
- FLAC__StreamMetadata* cs;
-
- success = FLAC__metadata_get_cuesheet(pathname, &cs);
- if (!success)
- return NULL;
-
- assert(cs != NULL);
-
- if (cs->data.cue_sheet.num_tracks <= 1)
- {
- FLAC__metadata_object_delete(cs);
- return NULL;
- }
+ flac_convert(buffer, frame->header.channels,
+ data->audio_format.format, buf,
+ 0, frame->header.blocksize);
- if (tnum > 0 && tnum < cs->data.cue_sheet.num_tracks)
- {
- char* track = g_strdup_printf("track_%03u.flac", tnum);
+ if (nbytes > 0)
+ bit_rate = nbytes * 8 * frame->header.sample_rate /
+ (1000 * frame->header.blocksize);
+ else
+ bit_rate = 0;
+
+ cmd = decoder_data(data->decoder, data->input_stream,
+ buffer, buffer_size,
+ bit_rate);
+ data->next_frame += frame->header.blocksize;
+ switch (cmd) {
+ case DECODE_COMMAND_NONE:
+ case DECODE_COMMAND_START:
+ break;
- FLAC__metadata_object_delete(cs);
+ case DECODE_COMMAND_STOP:
+ return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT;
- return track;
- }
- else
- {
- FLAC__metadata_object_delete(cs);
- return NULL;
+ case DECODE_COMMAND_SEEK:
+ return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE;
}
-}
-unsigned int
-flac_vtrack_tnum(const char* fname)
-{
- /* find last occurrence of '_' in fname
- * which is hopefully something like track_xxx.flac
- * another/better way would be to use tag struct
- */
- char* ptr = strrchr(fname, '_');
- if (ptr == NULL)
- return 0;
-
- // copy ascii tracknumber to int
- return (unsigned int)strtol(++ptr, NULL, 10);
+ return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE;
}
-
-#endif /* FLAC_API_VERSION_CURRENT >= 7 */
diff --git a/src/decoder/_flac_common.h b/src/decoder/_flac_common.h
index 68de7e969..5c59ee123 100644
--- a/src/decoder/_flac_common.h
+++ b/src/decoder/_flac_common.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -24,136 +24,62 @@
#ifndef MPD_FLAC_COMMON_H
#define MPD_FLAC_COMMON_H
-#include "../decoder_api.h"
-#include "config.h"
+#include "decoder_api.h"
+#include "pcm_buffer.h"
#include <glib.h>
+#include <FLAC/stream_decoder.h>
+#include <FLAC/metadata.h>
+
#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "flac"
-#include <FLAC/export.h>
-#if !defined(FLAC_API_VERSION_CURRENT) || FLAC_API_VERSION_CURRENT <= 7
-# include <FLAC/seekable_stream_decoder.h>
-# define flac_decoder FLAC__SeekableStreamDecoder
-# define flac_new() FLAC__seekable_stream_decoder_new()
-
-# define flac_ogg_init(a,b,c,d,e,f,g,h,i,j) (0)
-
-# define flac_get_decode_position(x,y) \
- FLAC__seekable_stream_decoder_get_decode_position(x,y)
-# define flac_get_state(x) FLAC__seekable_stream_decoder_get_state(x)
-# define flac_process_single(x) FLAC__seekable_stream_decoder_process_single(x)
-# define flac_process_metadata(x) \
- FLAC__seekable_stream_decoder_process_until_end_of_metadata(x)
-# define flac_seek_absolute(x,y) \
- FLAC__seekable_stream_decoder_seek_absolute(x,y)
-# define flac_finish(x) FLAC__seekable_stream_decoder_finish(x)
-# define flac_delete(x) FLAC__seekable_stream_decoder_delete(x)
-
-# define flac_decoder_eof FLAC__SEEKABLE_STREAM_DECODER_END_OF_STREAM
-
-typedef unsigned flac_read_status_size_t;
-# define flac_read_status FLAC__SeekableStreamDecoderReadStatus
-# define flac_read_status_continue \
- FLAC__SEEKABLE_STREAM_DECODER_READ_STATUS_OK
-# define flac_read_status_eof FLAC__SEEKABLE_STREAM_DECODER_READ_STATUS_OK
-# define flac_read_status_abort \
- FLAC__SEEKABLE_STREAM_DECODER_READ_STATUS_ERROR
-
-# define flac_seek_status FLAC__SeekableStreamDecoderSeekStatus
-# define flac_seek_status_ok FLAC__SEEKABLE_STREAM_DECODER_SEEK_STATUS_OK
-# define flac_seek_status_error FLAC__SEEKABLE_STREAM_DECODER_SEEK_STATUS_ERROR
-
-# define flac_tell_status FLAC__SeekableStreamDecoderTellStatus
-# define flac_tell_status_ok FLAC__SEEKABLE_STREAM_DECODER_TELL_STATUS_OK
-# define flac_tell_status_error \
- FLAC__SEEKABLE_STREAM_DECODER_TELL_STATUS_ERROR
-# define flac_tell_status_unsupported \
- FLAC__SEEKABLE_STREAM_DECODER_TELL_STATUS_ERROR
-
-# define flac_length_status FLAC__SeekableStreamDecoderLengthStatus
-# define flac_length_status_ok FLAC__SEEKABLE_STREAM_DECODER_LENGTH_STATUS_OK
-# define flac_length_status_error \
- FLAC__SEEKABLE_STREAM_DECODER_LENGTH_STATUS_ERROR
-# define flac_length_status_unsupported \
- FLAC__SEEKABLE_STREAM_DECODER_LENGTH_STATUS_ERROR
-
-# ifdef HAVE_OGGFLAC
-# include <OggFLAC/seekable_stream_decoder.h>
-# endif
-#else /* FLAC_API_VERSION_CURRENT > 7 */
+struct flac_data {
+ struct pcm_buffer buffer;
+
+ /**
+ * The size of one frame in the output buffer.
+ */
+ unsigned frame_size;
+
+ /**
+ * Has decoder_initialized() been called yet?
+ */
+ bool initialized;
+
+ /**
+ * Does the FLAC file contain an unsupported audio format?
+ */
+ bool unsupported;
+
+ /**
+ * The validated audio format of the FLAC file. This
+ * attribute is defined if "initialized" is true.
+ */
+ struct audio_format audio_format;
-/*
- * OggFLAC support is handled by our flac_plugin already, and
- * thus we *can* always have it if libFLAC was compiled with it
- */
-# include "_ogg_common.h"
-
-# include <FLAC/stream_decoder.h>
-# define flac_decoder FLAC__StreamDecoder
-# define flac_new() FLAC__stream_decoder_new()
-
-# define flac_init(a,b,c,d,e,f,g,h,i,j) \
- (FLAC__stream_decoder_init_stream(a,b,c,d,e,f,g,h,i,j) \
- == FLAC__STREAM_DECODER_INIT_STATUS_OK)
-# define flac_ogg_init(a,b,c,d,e,f,g,h,i,j) \
- (FLAC__stream_decoder_init_ogg_stream(a,b,c,d,e,f,g,h,i,j) \
- == FLAC__STREAM_DECODER_INIT_STATUS_OK)
-
-# define flac_get_decode_position(x,y) \
- FLAC__stream_decoder_get_decode_position(x,y)
-# define flac_get_state(x) FLAC__stream_decoder_get_state(x)
-# define flac_process_single(x) FLAC__stream_decoder_process_single(x)
-# define flac_process_metadata(x) \
- FLAC__stream_decoder_process_until_end_of_metadata(x)
-# define flac_seek_absolute(x,y) FLAC__stream_decoder_seek_absolute(x,y)
-# define flac_finish(x) FLAC__stream_decoder_finish(x)
-# define flac_delete(x) FLAC__stream_decoder_delete(x)
-
-# define flac_decoder_eof FLAC__STREAM_DECODER_END_OF_STREAM
-
-typedef size_t flac_read_status_size_t;
-# define flac_read_status FLAC__StreamDecoderReadStatus
-# define flac_read_status_continue \
- FLAC__STREAM_DECODER_READ_STATUS_CONTINUE
-# define flac_read_status_eof FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM
-# define flac_read_status_abort FLAC__STREAM_DECODER_READ_STATUS_ABORT
-
-# define flac_seek_status FLAC__StreamDecoderSeekStatus
-# define flac_seek_status_ok FLAC__STREAM_DECODER_SEEK_STATUS_OK
-# define flac_seek_status_error FLAC__STREAM_DECODER_SEEK_STATUS_ERROR
-# define flac_seek_status_unsupported \
- FLAC__STREAM_DECODER_SEEK_STATUS_UNSUPPORTED
-
-# define flac_tell_status FLAC__StreamDecoderTellStatus
-# define flac_tell_status_ok FLAC__STREAM_DECODER_TELL_STATUS_OK
-# define flac_tell_status_error FLAC__STREAM_DECODER_TELL_STATUS_ERROR
-# define flac_tell_status_unsupported \
- FLAC__STREAM_DECODER_TELL_STATUS_UNSUPPORTED
-
-# define flac_length_status FLAC__StreamDecoderLengthStatus
-# define flac_length_status_ok FLAC__STREAM_DECODER_LENGTH_STATUS_OK
-# define flac_length_status_error FLAC__STREAM_DECODER_LENGTH_STATUS_ERROR
-# define flac_length_status_unsupported \
- FLAC__STREAM_DECODER_LENGTH_STATUS_UNSUPPORTED
-
-#endif /* FLAC_API_VERSION_CURRENT >= 7 */
+ /**
+ * The total number of frames in this song. The decoder
+ * plugin may initialize this attribute to override the value
+ * provided by libFLAC (e.g. for sub songs from a CUE sheet).
+ */
+ FLAC__uint64 total_frames;
-#include <FLAC/metadata.h>
+ /**
+ * The number of the first frame in this song. This is only
+ * non-zero if playing sub songs from a CUE sheet.
+ */
+ FLAC__uint64 first_frame;
-#define FLAC_CHUNK_SIZE 4080
+ /**
+ * The number of the next frame which is going to be decoded.
+ */
+ FLAC__uint64 next_frame;
-struct flac_data {
- unsigned char chunk[FLAC_CHUNK_SIZE];
- float time;
- unsigned int bit_rate;
- struct audio_format audio_format;
- float total_time;
FLAC__uint64 position;
struct decoder *decoder;
struct input_stream *input_stream;
- struct replay_gain_info *replay_gain_info;
struct tag *tag;
};
@@ -162,6 +88,9 @@ void
flac_data_init(struct flac_data *data, struct decoder * decoder,
struct input_stream *input_stream);
+void
+flac_data_deinit(struct flac_data *data);
+
void flac_metadata_common_cb(const FLAC__StreamMetadata * block,
struct flac_data *data);
@@ -169,23 +98,9 @@ void flac_error_common_cb(const char *plugin,
FLAC__StreamDecoderErrorStatus status,
struct flac_data *data);
-void
-flac_vorbis_comments_to_tag(struct tag *tag, const char *char_tnum,
- const FLAC__StreamMetadata *block);
-
FLAC__StreamDecoderWriteStatus
flac_common_write(struct flac_data *data, const FLAC__Frame * frame,
- const FLAC__int32 *const buf[]);
-
-#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7
-
-char*
-flac_cue_track( const char* pathname,
- const unsigned int tnum);
-
-unsigned int
-flac_vtrack_tnum( const char*);
-
-#endif /* FLAC_API_VERSION_CURRENT >= 7 */
+ const FLAC__int32 *const buf[],
+ FLAC__uint64 nbytes);
#endif /* _FLAC_COMMON_H */
diff --git a/src/decoder/_ogg_common.c b/src/decoder/_ogg_common.c
index 6c6553422..bd0650ac4 100644
--- a/src/decoder/_ogg_common.c
+++ b/src/decoder/_ogg_common.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -21,8 +21,8 @@
* Common functions used for Ogg data streams (Ogg-Vorbis and OggFLAC)
*/
+#include "config.h"
#include "_ogg_common.h"
-#include "../utils.h"
ogg_stream_type ogg_stream_type_detect(struct input_stream *inStream)
{
diff --git a/src/decoder/_ogg_common.h b/src/decoder/_ogg_common.h
index e650c366d..f8446c69c 100644
--- a/src/decoder/_ogg_common.h
+++ b/src/decoder/_ogg_common.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -24,7 +24,7 @@
#ifndef MPD_OGG_COMMON_H
#define MPD_OGG_COMMON_H
-#include "../decoder_api.h"
+#include "decoder_api.h"
typedef enum _ogg_stream_type { VORBIS, FLAC } ogg_stream_type;
diff --git a/src/decoder/audiofile_plugin.c b/src/decoder/audiofile_decoder_plugin.c
index f66d90dc1..b099cf706 100644
--- a/src/decoder/audiofile_plugin.c
+++ b/src/decoder/audiofile_decoder_plugin.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -17,7 +17,9 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
-#include "../decoder_api.h"
+#include "config.h"
+#include "decoder_api.h"
+#include "audio_check.h"
#include <audiofile.h>
#include <af_vfs.h>
@@ -45,10 +47,20 @@ static int audiofile_get_duration(const char *file)
}
static ssize_t
-audiofile_file_read(AFvirtualfile *vfile, void *data, size_t nbytes)
+audiofile_file_read(AFvirtualfile *vfile, void *data, size_t length)
{
struct input_stream *is = (struct input_stream *) vfile->closure;
- return input_stream_read(is, data, nbytes);
+ GError *error = NULL;
+ size_t nbytes;
+
+ nbytes = input_stream_read(is, data, length, &error);
+ if (nbytes == 0 && error != NULL) {
+ g_warning("%s", error->message);
+ g_error_free(error);
+ return -1;
+ }
+
+ return nbytes;
}
static long
@@ -78,7 +90,7 @@ audiofile_file_seek(AFvirtualfile *vfile, long 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)) {
+ if (input_stream_seek(is, offset, whence, NULL)) {
return is->offset;
} else {
return -1;
@@ -99,17 +111,56 @@ setup_virtual_fops(struct input_stream *stream)
return vf;
}
+static enum sample_format
+audiofile_bits_to_sample_format(int bits)
+{
+ switch (bits) {
+ case 8:
+ return SAMPLE_FORMAT_S8;
+
+ case 16:
+ return SAMPLE_FORMAT_S16;
+
+ case 24:
+ return SAMPLE_FORMAT_S24_P32;
+
+ case 32:
+ return SAMPLE_FORMAT_S32;
+ }
+
+ return SAMPLE_FORMAT_UNDEFINED;
+}
+
+static enum sample_format
+audiofile_setup_sample_format(AFfilehandle af_fp)
+{
+ int fs, bits;
+
+ afGetSampleFormat(af_fp, AF_DEFAULT_TRACK, &fs, &bits);
+ if (!audio_valid_sample_format(audiofile_bits_to_sample_format(bits))) {
+ g_debug("input file has %d bit samples, converting to 16",
+ bits);
+ bits = 16;
+ }
+
+ afSetVirtualSampleFormat(af_fp, AF_DEFAULT_TRACK,
+ AF_SAMPFMT_TWOSCOMP, bits);
+ afGetVirtualSampleFormat(af_fp, AF_DEFAULT_TRACK, &fs, &bits);
+
+ return audiofile_bits_to_sample_format(bits);
+}
+
static void
audiofile_stream_decode(struct decoder *decoder, struct input_stream *is)
{
+ GError *error = NULL;
AFvirtualfile *vf;
int fs, frame_count;
AFfilehandle af_fp;
- int bits;
struct audio_format audio_format;
float total_time;
uint16_t bit_rate;
- int ret, current = 0;
+ int ret;
char chunk[CHUNK_SIZE];
enum decoder_command cmd;
@@ -126,26 +177,13 @@ audiofile_stream_decode(struct decoder *decoder, struct input_stream *is)
return;
}
- afGetSampleFormat(af_fp, AF_DEFAULT_TRACK, &fs, &bits);
- if (!audio_valid_sample_format(bits)) {
- g_debug("input file has %d bit samples, converting to 16",
- bits);
- bits = 16;
- }
-
- afSetVirtualSampleFormat(af_fp, AF_DEFAULT_TRACK,
- AF_SAMPFMT_TWOSCOMP, bits);
- afGetVirtualSampleFormat(af_fp, AF_DEFAULT_TRACK, &fs, &bits);
- audio_format.bits = (uint8_t)bits;
- audio_format.sample_rate =
- (unsigned int)afGetRate(af_fp, AF_DEFAULT_TRACK);
- audio_format.channels =
- (uint8_t)afGetVirtualChannels(af_fp, AF_DEFAULT_TRACK);
-
- if (!audio_format_valid(&audio_format)) {
- g_warning("Invalid audio format: %u:%u:%u\n",
- audio_format.sample_rate, audio_format.bits,
- audio_format.channels);
+ if (!audio_format_init_checked(&audio_format,
+ afGetRate(af_fp, AF_DEFAULT_TRACK),
+ audiofile_setup_sample_format(af_fp),
+ afGetVirtualChannels(af_fp, AF_DEFAULT_TRACK),
+ &error)) {
+ g_warning("%s", error->message);
+ g_error_free(error);
afCloseFile(af_fp);
return;
}
@@ -166,17 +204,14 @@ audiofile_stream_decode(struct decoder *decoder, struct input_stream *is)
if (ret <= 0)
break;
- current += ret;
cmd = decoder_data(decoder, NULL,
chunk, ret * fs,
- (float)current /
- (float)audio_format.sample_rate,
- bit_rate, NULL);
+ bit_rate);
if (cmd == DECODE_COMMAND_SEEK) {
- current = decoder_seek_where(decoder) *
+ AFframecount frame = decoder_seek_where(decoder) *
audio_format.sample_rate;
- afSeekFrame(af_fp, AF_DEFAULT_TRACK, current);
+ afSeekFrame(af_fp, AF_DEFAULT_TRACK, frame);
decoder_command_finished(decoder);
cmd = DECODE_COMMAND_NONE;
@@ -209,7 +244,7 @@ static const char *const audiofile_suffixes[] = {
static const char *const audiofile_mime_types[] = {
"audio/x-wav",
"audio/x-aiff",
- NULL
+ NULL
};
const struct decoder_plugin audiofile_decoder_plugin = {
diff --git a/src/decoder/faad_plugin.c b/src/decoder/faad_decoder_plugin.c
index 7b2806a4c..8f932ad58 100644
--- a/src/decoder/faad_plugin.c
+++ b/src/decoder/faad_decoder_plugin.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -17,9 +17,10 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
-#include "../decoder_api.h"
-#include "decoder_buffer.h"
#include "config.h"
+#include "decoder_api.h"
+#include "decoder_buffer.h"
+#include "audio_check.h"
#define AAC_MAX_CHANNELS 6
@@ -37,6 +38,15 @@ static const unsigned adts_sample_rates[] =
};
/**
+ * The GLib quark used for errors reported by this plugin.
+ */
+static inline GQuark
+faad_decoder_quark(void)
+{
+ return g_quark_from_static_string("faad");
+}
+
+/**
* Check whether the buffer head is an AAC frame, and return the frame
* length. Returns 0 if it is not a frame.
*/
@@ -195,7 +205,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);
+ input_stream_seek(is, tagsize, SEEK_SET, NULL);
data = decoder_buffer_read(buffer, &length);
if (data != NULL)
@@ -232,7 +242,7 @@ faad_song_duration(struct decoder_buffer *buffer, struct input_stream *is)
*/
static bool
faad_decoder_init(faacDecHandle decoder, struct decoder_buffer *buffer,
- struct audio_format *audio_format)
+ struct audio_format *audio_format, GError **error_r)
{
union {
/* deconst hack for libfaad */
@@ -247,32 +257,33 @@ faad_decoder_init(faacDecHandle decoder, struct decoder_buffer *buffer,
/* neaacdec.h declares all arguments as "unsigned long", but
internally expects uint32_t pointers. To avoid gcc
warnings, use this workaround. */
- unsigned long *sample_rate_r = (unsigned long *)(void *)&sample_rate;
+ unsigned long *sample_rate_p = (unsigned long *)(void *)&sample_rate;
#else
- uint32_t *sample_rate_r = &sample_rate;
+ uint32_t *sample_rate_p = &sample_rate;
#endif
u.in = decoder_buffer_read(buffer, &length);
- if (u.in == NULL)
+ if (u.in == NULL) {
+ g_set_error(error_r, faad_decoder_quark(), 0,
+ "Empty file");
return false;
+ }
nbytes = faacDecInit(decoder, u.out,
#ifdef HAVE_FAAD_BUFLEN_FUNCS
length,
#endif
- sample_rate_r, &channels);
- if (nbytes < 0)
+ sample_rate_p, &channels);
+ if (nbytes < 0) {
+ g_set_error(error_r, faad_decoder_quark(), 0,
+ "Not an AAC stream");
return false;
+ }
decoder_buffer_consume(buffer, nbytes);
- *audio_format = (struct audio_format){
- .bits = 16,
- .channels = channels,
- .sample_rate = sample_rate,
- };
-
- return true;
+ return audio_format_init_checked(audio_format, sample_rate,
+ SAMPLE_FORMAT_S16, channels, error_r);
}
/**
@@ -311,20 +322,16 @@ faad_decoder_decode(faacDecHandle decoder, struct decoder_buffer *buffer,
* file is invalid.
*/
static float
-faad_get_file_time_float(const char *file)
+faad_get_file_time_float(struct input_stream *is)
{
struct decoder_buffer *buffer;
float length;
faacDecHandle decoder;
faacDecConfigurationPtr config;
- struct input_stream is;
-
- if (!input_stream_open(&is, file))
- return -1;
- buffer = decoder_buffer_new(NULL, &is,
+ buffer = decoder_buffer_new(NULL, is,
FAAD_MIN_STREAMSIZE * AAC_MAX_CHANNELS);
- length = faad_song_duration(buffer, &is);
+ length = faad_song_duration(buffer, is);
if (length < 0) {
bool ret;
@@ -338,15 +345,14 @@ faad_get_file_time_float(const char *file)
decoder_buffer_fill(buffer);
- ret = faad_decoder_init(decoder, buffer, &audio_format);
- if (ret && audio_format_valid(&audio_format))
+ ret = faad_decoder_init(decoder, buffer, &audio_format, NULL);
+ if (ret)
length = 0;
faacDecClose(decoder);
}
decoder_buffer_free(buffer);
- input_stream_close(&is);
return length;
}
@@ -357,12 +363,12 @@ faad_get_file_time_float(const char *file)
* file is invalid.
*/
static int
-faad_get_file_time(const char *file)
+faad_get_file_time(struct input_stream *is)
{
int file_time = -1;
float length;
- if ((length = faad_get_file_time_float(file)) >= 0)
+ if ((length = faad_get_file_time_float(is)) >= 0)
file_time = length + 0.5;
return file_time;
@@ -371,7 +377,7 @@ faad_get_file_time(const char *file)
static void
faad_stream_decode(struct decoder *mpd_decoder, struct input_stream *is)
{
- float file_time;
+ GError *error = NULL;
float total_time = 0;
faacDecHandle decoder;
struct audio_format audio_format;
@@ -408,15 +414,10 @@ faad_stream_decode(struct decoder *mpd_decoder, struct input_stream *is)
/* initialize it */
- ret = faad_decoder_init(decoder, buffer, &audio_format);
+ ret = faad_decoder_init(decoder, buffer, &audio_format, &error);
if (!ret) {
- g_warning("Error not a AAC stream.\n");
- faacDecClose(decoder);
- return;
- }
-
- if (!audio_format_valid(&audio_format)) {
- g_warning("invalid audio format\n");
+ g_warning("%s", error->message);
+ g_error_free(error);
faacDecClose(decoder);
return;
}
@@ -427,8 +428,6 @@ faad_stream_decode(struct decoder *mpd_decoder, struct input_stream *is)
/* the decoder loop */
- file_time = 0.0;
-
do {
size_t frame_size;
const void *decoded;
@@ -474,16 +473,13 @@ faad_stream_decode(struct decoder *mpd_decoder, struct input_stream *is)
bit_rate = frame_info.bytesconsumed * 8.0 *
frame_info.channels * audio_format.sample_rate /
frame_info.samples / 1000 + 0.5;
- file_time +=
- (float)(frame_info.samples) / frame_info.channels /
- audio_format.sample_rate;
}
/* send PCM samples to MPD */
cmd = decoder_data(mpd_decoder, is, decoded,
- (size_t)frame_info.samples * 2, file_time,
- bit_rate, NULL);
+ (size_t)frame_info.samples * 2,
+ bit_rate);
} while (cmd != DECODE_COMMAND_STOP);
/* cleanup */
@@ -492,15 +488,13 @@ faad_stream_decode(struct decoder *mpd_decoder, struct input_stream *is)
}
static struct tag *
-faad_tag_dup(const char *file)
+faad_stream_tag(struct input_stream *is)
{
- int file_time = faad_get_file_time(file);
+ int file_time = faad_get_file_time(is);
struct tag *tag;
- if (file_time < 0) {
- g_debug("Failed to get total song time from: %s", file);
+ if (file_time < 0)
return NULL;
- }
tag = tag_new();
tag->time = file_time;
@@ -515,7 +509,7 @@ static const char *const faad_mime_types[] = {
const struct decoder_plugin faad_decoder_plugin = {
.name = "faad",
.stream_decode = faad_stream_decode,
- .tag_dup = faad_tag_dup,
+ .stream_tag = faad_stream_tag,
.suffixes = faad_suffixes,
.mime_types = faad_mime_types,
};
diff --git a/src/decoder/ffmpeg_plugin.c b/src/decoder/ffmpeg_decoder_plugin.c
index 10894b633..f9d4eb8a9 100644
--- a/src/decoder/ffmpeg_plugin.c
+++ b/src/decoder/ffmpeg_decoder_plugin.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -17,8 +17,9 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
-#include "../decoder_api.h"
#include "config.h"
+#include "decoder_api.h"
+#include "audio_check.h"
#include <glib.h>
@@ -39,19 +40,46 @@
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavformat/avio.h>
+#include <libavutil/log.h>
#endif
#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "ffmpeg"
-struct ffmpeg_context {
- int audio_stream;
- AVFormatContext *format_context;
- AVCodecContext *codec_context;
- struct decoder *decoder;
- struct input_stream *input;
- struct tag *tag;
-};
+#ifndef OLD_FFMPEG_INCLUDES
+
+static GLogLevelFlags
+level_ffmpeg_to_glib(int level)
+{
+ if (level <= AV_LOG_FATAL)
+ return G_LOG_LEVEL_CRITICAL;
+
+ if (level <= AV_LOG_ERROR)
+ return G_LOG_LEVEL_WARNING;
+
+ if (level <= AV_LOG_INFO)
+ return G_LOG_LEVEL_MESSAGE;
+
+ return G_LOG_LEVEL_DEBUG;
+}
+
+static void
+mpd_ffmpeg_log_callback(G_GNUC_UNUSED void *ptr, int level,
+ const char *fmt, va_list vl)
+{
+ const AVClass * cls = NULL;
+
+ if (ptr != NULL)
+ cls = *(const AVClass *const*)ptr;
+
+ if (cls != NULL) {
+ char *domain = g_strconcat(G_LOG_DOMAIN, "/", cls->item_name(ptr), NULL);
+ g_logv(domain, level_ffmpeg_to_glib(level), fmt, vl);
+ g_free(domain);
+ }
+}
+
+#endif /* !OLD_FFMPEG_INCLUDES */
struct mpd_ffmpeg_stream {
struct decoder *decoder;
@@ -79,7 +107,7 @@ mpd_ffmpeg_stream_seek(void *opaque, int64_t pos, int whence)
if (whence == AVSEEK_SIZE)
return stream->input->size;
- ret = input_stream_seek(stream->input, pos, whence);
+ ret = input_stream_seek(stream->input, pos, whence, NULL);
if (!ret)
return -1;
@@ -115,6 +143,10 @@ 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;
}
@@ -130,9 +162,95 @@ ffmpeg_find_audio_stream(const AVFormatContext *format_context)
return -1;
}
+/**
+ * On some platforms, libavcodec wants the output buffer aligned to 16
+ * bytes (because it uses SSE/Altivec internally). This function
+ * returns the aligned version of the specified buffer, and corrects
+ * the buffer size.
+ */
+static void *
+align16(void *p, size_t *length_p)
+{
+ unsigned add = 16 - (size_t)p % 16;
+
+ *length_p -= add;
+ return (char *)p + add;
+}
+
+static enum decoder_command
+ffmpeg_send_packet(struct decoder *decoder, struct input_stream *is,
+ const AVPacket *packet,
+ AVCodecContext *codec_context,
+ const AVRational *time_base)
+{
+ enum decoder_command cmd = DECODE_COMMAND_NONE;
+ uint8_t audio_buf[(AVCODEC_MAX_AUDIO_FRAME_SIZE * 3) / 2 + 16];
+ int16_t *aligned_buffer;
+ size_t buffer_size;
+ int len, audio_size;
+ uint8_t *packet_data;
+ int packet_size;
+
+ if (packet->pts != (int64_t)AV_NOPTS_VALUE)
+ decoder_timestamp(decoder,
+ av_rescale_q(packet->pts, *time_base,
+ (AVRational){1, 1}));
+
+ packet_data = packet->data;
+ packet_size = packet->size;
+
+ buffer_size = sizeof(audio_buf);
+ aligned_buffer = align16(audio_buf, &buffer_size);
+
+ while ((packet_size > 0) && (cmd == DECODE_COMMAND_NONE)) {
+ audio_size = buffer_size;
+ len = avcodec_decode_audio2(codec_context,
+ aligned_buffer, &audio_size,
+ packet_data, packet_size);
+
+ if (len < 0) {
+ /* if error, we skip the frame */
+ g_message("decoding failed\n");
+ break;
+ }
+
+ packet_data += len;
+ packet_size -= len;
+
+ if (audio_size <= 0)
+ continue;
+
+ cmd = decoder_data(decoder, is,
+ aligned_buffer, audio_size,
+ codec_context->bit_rate / 1000);
+ }
+ return cmd;
+}
+
+static enum sample_format
+ffmpeg_sample_format(G_GNUC_UNUSED const AVCodecContext *codec_context)
+{
+#if LIBAVCODEC_VERSION_INT >= ((51<<16)+(41<<8)+0)
+ switch (codec_context->sample_fmt) {
+ case SAMPLE_FMT_S16:
+ return SAMPLE_FORMAT_S16;
+
+ case SAMPLE_FMT_S32:
+ return SAMPLE_FORMAT_S32;
+
+ default:
+ g_warning("Unsupported libavcodec SampleFormat value: %d",
+ codec_context->sample_fmt);
+ return SAMPLE_FORMAT_UNDEFINED;
+ }
+#else
+ /* XXX fixme 16-bit for older ffmpeg (13 Aug 2007) */
+ return SAMPLE_FORMAT_S16;
+#endif
+}
+
static AVInputFormat *
-ffmpeg_probe(struct decoder *decoder, struct input_stream *is,
- const char *uri)
+ffmpeg_probe(struct decoder *decoder, struct input_stream *is)
{
enum {
BUFFER_SIZE = 16384,
@@ -141,7 +259,7 @@ 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)) {
+ if (nbytes <= PADDING || !input_stream_seek(is, 0, SEEK_SET, NULL)) {
g_free(buffer);
return NULL;
}
@@ -155,7 +273,7 @@ ffmpeg_probe(struct decoder *decoder, struct input_stream *is,
AVProbeData avpd = {
.buf = buffer,
.buf_size = nbytes,
- .filename = uri,
+ .filename = is->uri,
};
AVInputFormat *format = av_probe_input_format(&avpd, true);
@@ -164,15 +282,12 @@ ffmpeg_probe(struct decoder *decoder, struct input_stream *is,
return format;
}
-static bool
-ffmpeg_helper(const char *uri,
- struct decoder *decoder, struct input_stream *input,
- bool (*callback)(struct ffmpeg_context *ctx),
- struct ffmpeg_context *ctx)
+static void
+ffmpeg_decode(struct decoder *decoder, struct input_stream *input)
{
- AVInputFormat *input_format = ffmpeg_probe(decoder, input, uri);
+ AVInputFormat *input_format = ffmpeg_probe(decoder, input);
if (input_format == NULL)
- return false;
+ return;
g_debug("detected input format '%s' (%s)",
input_format->name, input_format->long_name);
@@ -181,28 +296,27 @@ ffmpeg_helper(const char *uri,
mpd_ffmpeg_stream_open(decoder, input);
if (stream == NULL) {
g_warning("Failed to open stream");
- return false;
+ return;
}
AVFormatContext *format_context;
AVCodecContext *codec_context;
AVCodec *codec;
int audio_stream;
- bool ret;
//ffmpeg works with ours "fileops" helper
- if (av_open_input_stream(&format_context, stream->io, uri,
+ if (av_open_input_stream(&format_context, stream->io, input->uri,
input_format, NULL) != 0) {
g_warning("Open failed\n");
mpd_ffmpeg_stream_close(stream);
- return false;
+ return;
}
if (av_find_stream_info(format_context)<0) {
g_warning("Couldn't find stream info\n");
av_close_input_stream(format_context);
mpd_ffmpeg_stream_close(stream);
- return false;
+ return;
}
audio_stream = ffmpeg_find_audio_stream(format_context);
@@ -210,7 +324,7 @@ ffmpeg_helper(const char *uri,
g_warning("No audio stream inside\n");
av_close_input_stream(format_context);
mpd_ffmpeg_stream_close(stream);
- return false;
+ return;
}
codec_context = format_context->streams[audio_stream]->codec;
@@ -223,145 +337,48 @@ ffmpeg_helper(const char *uri,
g_warning("Unsupported audio codec\n");
av_close_input_stream(format_context);
mpd_ffmpeg_stream_close(stream);
- return false;
+ return;
}
if (avcodec_open(codec_context, codec)<0) {
g_warning("Could not open codec\n");
av_close_input_stream(format_context);
mpd_ffmpeg_stream_close(stream);
- return false;
- }
-
- if (callback) {
- ctx->audio_stream = audio_stream;
- ctx->format_context = format_context;
- ctx->codec_context = codec_context;
-
- ret = callback(ctx);
- } else
- ret = true;
-
- avcodec_close(codec_context);
- av_close_input_stream(format_context);
- mpd_ffmpeg_stream_close(stream);
-
- return ret;
-}
-
-/**
- * On some platforms, libavcodec wants the output buffer aligned to 16
- * bytes (because it uses SSE/Altivec internally). This function
- * returns the aligned version of the specified buffer, and corrects
- * the buffer size.
- */
-static void *
-align16(void *p, size_t *length_p)
-{
- unsigned add = 16 - (size_t)p % 16;
-
- *length_p -= add;
- return (char *)p + add;
-}
-
-static enum decoder_command
-ffmpeg_send_packet(struct decoder *decoder, struct input_stream *is,
- const AVPacket *packet,
- AVCodecContext *codec_context,
- const AVRational *time_base)
-{
- enum decoder_command cmd = DECODE_COMMAND_NONE;
- int position;
- uint8_t audio_buf[(AVCODEC_MAX_AUDIO_FRAME_SIZE * 3) / 2 + 16];
- int16_t *aligned_buffer;
- size_t buffer_size;
- int len, audio_size;
- uint8_t *packet_data;
- int packet_size;
-
- packet_data = packet->data;
- packet_size = packet->size;
-
- buffer_size = sizeof(audio_buf);
- aligned_buffer = align16(audio_buf, &buffer_size);
-
- while ((packet_size > 0) && (cmd == DECODE_COMMAND_NONE)) {
- audio_size = buffer_size;
- len = avcodec_decode_audio2(codec_context,
- aligned_buffer, &audio_size,
- packet_data, packet_size);
-
- if (len < 0) {
- /* if error, we skip the frame */
- g_message("decoding failed\n");
- break;
- }
-
- packet_data += len;
- packet_size -= len;
-
- if (audio_size <= 0)
- continue;
-
- position = packet->pts != (int64_t)AV_NOPTS_VALUE
- ? av_rescale_q(packet->pts, *time_base,
- (AVRational){1, 1})
- : 0;
-
- cmd = decoder_data(decoder, is,
- aligned_buffer, audio_size,
- position,
- codec_context->bit_rate / 1000, NULL);
+ return;
}
- return cmd;
-}
-static bool
-ffmpeg_decode_internal(struct ffmpeg_context *ctx)
-{
- struct decoder *decoder = ctx->decoder;
- AVCodecContext *codec_context = ctx->codec_context;
- AVFormatContext *format_context = ctx->format_context;
- AVPacket packet;
+ GError *error = NULL;
struct audio_format audio_format;
- enum decoder_command cmd;
- int total_time;
-
- total_time = 0;
-
-#if LIBAVCODEC_VERSION_INT >= ((51<<16)+(41<<8)+0)
- audio_format.bits = (uint8_t) av_get_bits_per_sample_format(codec_context->sample_fmt);
-#else
- /* XXX fixme 16-bit for older ffmpeg (13 Aug 2007) */
- audio_format.bits = (uint8_t) 16;
-#endif
- audio_format.sample_rate = (unsigned int)codec_context->sample_rate;
- audio_format.channels = codec_context->channels;
-
- if (!audio_format_valid(&audio_format)) {
- g_warning("Invalid audio format: %u:%u:%u\n",
- audio_format.sample_rate, audio_format.bits,
- audio_format.channels);
- return false;
+ if (!audio_format_init_checked(&audio_format,
+ codec_context->sample_rate,
+ ffmpeg_sample_format(codec_context),
+ codec_context->channels, &error)) {
+ g_warning("%s", error->message);
+ g_error_free(error);
+ avcodec_close(codec_context);
+ av_close_input_stream(format_context);
+ mpd_ffmpeg_stream_close(stream);
+ return;
}
- //there is some problem with this on some demux (mp3 at least)
- if (format_context->duration != (int64_t)AV_NOPTS_VALUE) {
- total_time = format_context->duration / AV_TIME_BASE;
- }
+ int total_time = format_context->duration != (int64_t)AV_NOPTS_VALUE
+ ? format_context->duration / AV_TIME_BASE
+ : 0;
decoder_initialized(decoder, &audio_format,
- ctx->input->seekable, total_time);
+ input->seekable, total_time);
+ enum decoder_command cmd;
do {
+ AVPacket packet;
if (av_read_frame(format_context, &packet) < 0)
/* end of file */
break;
- if (packet.stream_index == ctx->audio_stream)
- cmd = ffmpeg_send_packet(decoder, ctx->input,
+ if (packet.stream_index == audio_stream)
+ cmd = ffmpeg_send_packet(decoder, input,
&packet, codec_context,
- &format_context->streams[ctx->audio_stream]->time_base);
+ &format_context->streams[audio_stream]->time_base);
else
cmd = decoder_get_command(decoder);
@@ -378,115 +395,121 @@ ffmpeg_decode_internal(struct ffmpeg_context *ctx)
}
} while (cmd != DECODE_COMMAND_STOP);
- return true;
+ avcodec_close(codec_context);
+ av_close_input_stream(format_context);
+ mpd_ffmpeg_stream_close(stream);
}
-static void
-ffmpeg_decode(struct decoder *decoder, struct input_stream *input)
-{
- struct ffmpeg_context ctx;
-
- ctx.input = input;
- ctx.decoder = decoder;
+#if LIBAVFORMAT_VERSION_INT >= ((52<<16)+(31<<8)+0)
+typedef struct ffmpeg_tag_map {
+ enum tag_type type;
+ const char *name;
+} ffmpeg_tag_map;
- char *uri = decoder_get_uri(decoder);
- ffmpeg_helper(uri, decoder, input,
- ffmpeg_decode_internal, &ctx);
- g_free(uri);
-}
+static const ffmpeg_tag_map ffmpeg_tag_maps[] = {
+ { TAG_TITLE, "title" },
+#if LIBAVFORMAT_VERSION_INT >= ((52<<16)+(50<<8))
+ { TAG_ARTIST, "artist" },
+ { TAG_DATE, "date" },
+#else
+ { TAG_ARTIST, "author" },
+ { TAG_DATE, "year" },
+#endif
+ { TAG_ALBUM, "album" },
+ { TAG_COMMENT, "comment" },
+ { TAG_GENRE, "genre" },
+ { TAG_TRACK, "track" },
+ { TAG_ARTIST_SORT, "author-sort" },
+ { TAG_ALBUM_ARTIST, "album_artist" },
+ { TAG_ALBUM_ARTIST_SORT, "album_artist-sort" },
+ { TAG_COMPOSER, "composer" },
+ { TAG_PERFORMER, "performer" },
+ { TAG_DISC, "disc" },
+};
-#if LIBAVFORMAT_VERSION_INT >= ((52<<16)+(31<<8)+0)
static bool
ffmpeg_copy_metadata(struct tag *tag, AVMetadata *m,
- enum tag_type type, const char *name)
+ const ffmpeg_tag_map tag_map)
{
- AVMetadataTag *mt = av_metadata_get(m, name, NULL, 0);
- if (mt != NULL)
- tag_add_item(tag, type, mt->value);
+ AVMetadataTag *mt = NULL;
+
+ while ((mt = av_metadata_get(m, tag_map.name, mt, 0)) != NULL)
+ tag_add_item(tag, tag_map.type, mt->value);
return mt != NULL;
}
+
#endif
-static bool ffmpeg_tag_internal(struct ffmpeg_context *ctx)
+//no tag reading in ffmpeg, check if playable
+static struct tag *
+ffmpeg_stream_tag(struct input_stream *is)
{
- struct tag *tag = (struct tag *) ctx->tag;
- AVFormatContext *f = ctx->format_context;
+ AVInputFormat *input_format = ffmpeg_probe(NULL, is);
+ if (input_format == NULL)
+ return NULL;
- tag->time = 0;
- if (f->duration != (int64_t)AV_NOPTS_VALUE)
- tag->time = f->duration / AV_TIME_BASE;
+ struct mpd_ffmpeg_stream *stream = mpd_ffmpeg_stream_open(NULL, is);
+ if (stream == NULL)
+ return NULL;
+
+ AVFormatContext *f;
+ if (av_open_input_stream(&f, stream->io, is->uri,
+ input_format, NULL) != 0) {
+ mpd_ffmpeg_stream_close(stream);
+ return NULL;
+ }
+
+ if (av_find_stream_info(f) < 0) {
+ av_close_input_stream(f);
+ mpd_ffmpeg_stream_close(stream);
+ return NULL;
+ }
+
+ struct tag *tag = tag_new();
+
+ tag->time = f->duration != (int64_t)AV_NOPTS_VALUE
+ ? f->duration / AV_TIME_BASE
+ : 0;
#if LIBAVFORMAT_VERSION_INT >= ((52<<16)+(31<<8)+0)
av_metadata_conv(f, NULL, f->iformat->metadata_conv);
- ffmpeg_copy_metadata(tag, f->metadata, TAG_ITEM_TITLE, "title");
-#if LIBAVFORMAT_VERSION_INT >= ((52<<16)+(50<<8))
- ffmpeg_copy_metadata(tag, f->metadata, TAG_ITEM_ARTIST, "artist");
- ffmpeg_copy_metadata(tag, f->metadata, TAG_ITEM_DATE, "date");
-#else
- ffmpeg_copy_metadata(tag, f->metadata, TAG_ITEM_ARTIST, "author");
- ffmpeg_copy_metadata(tag, f->metadata, TAG_ITEM_DATE, "year");
-#endif
- ffmpeg_copy_metadata(tag, f->metadata, TAG_ITEM_ALBUM, "album");
- ffmpeg_copy_metadata(tag, f->metadata, TAG_ITEM_COMMENT, "comment");
- ffmpeg_copy_metadata(tag, f->metadata, TAG_ITEM_GENRE, "genre");
- ffmpeg_copy_metadata(tag, f->metadata, TAG_ITEM_TRACK, "track");
- ffmpeg_copy_metadata(tag, f->metadata, TAG_ITEM_ALBUM_ARTIST, "album_artist");
- ffmpeg_copy_metadata(tag, f->metadata, TAG_ITEM_COMPOSER, "composer");
- ffmpeg_copy_metadata(tag, f->metadata, TAG_ITEM_PERFORMER, "performer");
- ffmpeg_copy_metadata(tag, f->metadata, TAG_ITEM_DISC, "disc");
+ for (unsigned i = 0; i < sizeof(ffmpeg_tag_maps)/sizeof(ffmpeg_tag_map); i++) {
+ int idx = ffmpeg_find_audio_stream(f);
+ ffmpeg_copy_metadata(tag, f->metadata, ffmpeg_tag_maps[i]);
+ if (idx >= 0)
+ ffmpeg_copy_metadata(tag, f->streams[idx]->metadata, ffmpeg_tag_maps[i]);
+ }
#else
if (f->author[0])
- tag_add_item(tag, TAG_ITEM_ARTIST, f->author);
+ tag_add_item(tag, TAG_ARTIST, f->author);
if (f->title[0])
- tag_add_item(tag, TAG_ITEM_TITLE, f->title);
+ tag_add_item(tag, TAG_TITLE, f->title);
if (f->album[0])
- tag_add_item(tag, TAG_ITEM_ALBUM, f->album);
+ 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_ITEM_TRACK, buffer);
+ tag_add_item(tag, TAG_TRACK, buffer);
}
if (f->comment[0])
- tag_add_item(tag, TAG_ITEM_COMMENT, f->comment);
+ tag_add_item(tag, TAG_COMMENT, f->comment);
if (f->genre[0])
- tag_add_item(tag, TAG_ITEM_GENRE, f->genre);
+ 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_ITEM_DATE, buffer);
+ tag_add_item(tag, TAG_DATE, buffer);
}
#endif
- return true;
-}
-
-//no tag reading in ffmpeg, check if playable
-static struct tag *ffmpeg_tag(const char *file)
-{
- struct input_stream input;
- struct ffmpeg_context ctx;
- bool ret;
-
- if (!input_stream_open(&input, file)) {
- g_warning("failed to open %s\n", file);
- return NULL;
- }
-
- ctx.decoder = NULL;
- ctx.tag = tag_new();
-
- ret = ffmpeg_helper(file, NULL, &input, ffmpeg_tag_internal, &ctx);
- if (!ret) {
- tag_free(ctx.tag);
- ctx.tag = NULL;
- }
- input_stream_close(&input);
+ av_close_input_stream(f);
+ mpd_ffmpeg_stream_close(stream);
- return ctx.tag;
+ return tag;
}
/**
@@ -592,6 +615,12 @@ static const char *const ffmpeg_mime_types[] = {
"video/x-vid",
"video/x-wmv",
"video/x-xvid",
+
+ /* special value for the "ffmpeg" input plugin: all streams by
+ the "ffmpeg" input plugin shall be decoded by this
+ plugin */
+ "audio/x-mpd-ffmpeg",
+
NULL
};
@@ -599,7 +628,7 @@ const struct decoder_plugin ffmpeg_decoder_plugin = {
.name = "ffmpeg",
.init = ffmpeg_init,
.stream_decode = ffmpeg_decode,
- .tag_dup = ffmpeg_tag,
+ .stream_tag = ffmpeg_stream_tag,
.suffixes = ffmpeg_suffixes,
.mime_types = ffmpeg_mime_types
};
diff --git a/src/decoder/flac_compat.h b/src/decoder/flac_compat.h
new file mode 100644
index 000000000..d597690a0
--- /dev/null
+++ b/src/decoder/flac_compat.h
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/*
+ * Common data structures and functions used by FLAC and OggFLAC
+ */
+
+#ifndef MPD_FLAC_COMPAT_H
+#define MPD_FLAC_COMPAT_H
+
+#include <FLAC/export.h>
+#if !defined(FLAC_API_VERSION_CURRENT) || FLAC_API_VERSION_CURRENT <= 7
+# include <FLAC/seekable_stream_decoder.h>
+
+/* starting with libFLAC 1.1.3, the SeekableStreamDecoder has been
+ merged into the StreamDecoder. The following macros try to emulate
+ the new API for libFLAC 1.1.2 by mapping MPD's StreamDecoder calls
+ to the old SeekableStreamDecoder API. */
+
+#define FLAC__StreamDecoder FLAC__SeekableStreamDecoder
+#define FLAC__stream_decoder_new FLAC__seekable_stream_decoder_new
+#define FLAC__stream_decoder_get_decode_position FLAC__seekable_stream_decoder_get_decode_position
+#define FLAC__stream_decoder_get_state FLAC__seekable_stream_decoder_get_state
+#define FLAC__stream_decoder_process_single FLAC__seekable_stream_decoder_process_single
+#define FLAC__stream_decoder_process_until_end_of_metadata FLAC__seekable_stream_decoder_process_until_end_of_metadata
+#define FLAC__stream_decoder_seek_absolute FLAC__seekable_stream_decoder_seek_absolute
+#define FLAC__stream_decoder_finish FLAC__seekable_stream_decoder_finish
+#define FLAC__stream_decoder_delete FLAC__seekable_stream_decoder_delete
+
+#define FLAC__STREAM_DECODER_END_OF_STREAM FLAC__SEEKABLE_STREAM_DECODER_END_OF_STREAM
+
+typedef unsigned flac_read_status_size_t;
+
+#define FLAC__StreamDecoderReadStatus FLAC__SeekableStreamDecoderReadStatus
+#define FLAC__STREAM_DECODER_READ_STATUS_CONTINUE FLAC__SEEKABLE_STREAM_DECODER_READ_STATUS_OK
+#define FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM FLAC__SEEKABLE_STREAM_DECODER_READ_STATUS_OK
+#define FLAC__STREAM_DECODER_READ_STATUS_ABORT FLAC__SEEKABLE_STREAM_DECODER_READ_STATUS_ERROR
+
+#define FLAC__StreamDecoderSeekStatus FLAC__SeekableStreamDecoderSeekStatus
+#define FLAC__STREAM_DECODER_SEEK_STATUS_OK FLAC__SEEKABLE_STREAM_DECODER_SEEK_STATUS_OK
+#define FLAC__STREAM_DECODER_SEEK_STATUS_ERROR FLAC__SEEKABLE_STREAM_DECODER_SEEK_STATUS_ERROR
+#define FLAC__STREAM_DECODER_SEEK_STATUS_UNSUPPORTED FLAC__SEEKABLE_STREAM_DECODER_SEEK_STATUS_ERROR
+
+#define FLAC__StreamDecoderTellStatus FLAC__SeekableStreamDecoderTellStatus
+#define FLAC__STREAM_DECODER_TELL_STATUS_OK FLAC__SEEKABLE_STREAM_DECODER_TELL_STATUS_OK
+#define FLAC__STREAM_DECODER_TELL_STATUS_ERROR FLAC__SEEKABLE_STREAM_DECODER_TELL_STATUS_ERROR
+#define FLAC__STREAM_DECODER_TELL_STATUS_UNSUPPORTED FLAC__SEEKABLE_STREAM_DECODER_TELL_STATUS_ERROR
+
+#define FLAC__StreamDecoderLengthStatus FLAC__SeekableStreamDecoderLengthStatus
+#define FLAC__STREAM_DECODER_LENGTH_STATUS_OK FLAC__SEEKABLE_STREAM_DECODER_LENGTH_STATUS_OK
+#define FLAC__STREAM_DECODER_LENGTH_STATUS_ERROR FLAC__SEEKABLE_STREAM_DECODER_LENGTH_STATUS_ERROR
+#define FLAC__STREAM_DECODER_LENGTH_STATUS_UNSUPPORTED FLAC__SEEKABLE_STREAM_DECODER_LENGTH_STATUS_ERROR
+
+typedef enum {
+ FLAC__STREAM_DECODER_INIT_STATUS_OK,
+ FLAC__STREAM_DECODER_INIT_STATUS_ERROR,
+} FLAC__StreamDecoderInitStatus;
+
+static inline FLAC__StreamDecoderInitStatus
+FLAC__stream_decoder_init_stream(FLAC__SeekableStreamDecoder *decoder,
+ FLAC__SeekableStreamDecoderReadCallback read_cb,
+ FLAC__SeekableStreamDecoderSeekCallback seek_cb,
+ FLAC__SeekableStreamDecoderTellCallback tell_cb,
+ FLAC__SeekableStreamDecoderLengthCallback length_cb,
+ FLAC__SeekableStreamDecoderEofCallback eof_cb,
+ FLAC__SeekableStreamDecoderWriteCallback write_cb,
+ FLAC__SeekableStreamDecoderMetadataCallback metadata_cb,
+ FLAC__SeekableStreamDecoderErrorCallback error_cb,
+ void *data)
+{
+ return FLAC__seekable_stream_decoder_set_read_callback(decoder, read_cb) &&
+ FLAC__seekable_stream_decoder_set_seek_callback(decoder, seek_cb) &&
+ FLAC__seekable_stream_decoder_set_tell_callback(decoder, tell_cb) &&
+ FLAC__seekable_stream_decoder_set_length_callback(decoder, length_cb) &&
+ FLAC__seekable_stream_decoder_set_eof_callback(decoder, eof_cb) &&
+ FLAC__seekable_stream_decoder_set_write_callback(decoder, write_cb) &&
+ FLAC__seekable_stream_decoder_set_metadata_callback(decoder, metadata_cb) &&
+ FLAC__seekable_stream_decoder_set_metadata_respond(decoder, FLAC__METADATA_TYPE_VORBIS_COMMENT) &&
+ FLAC__seekable_stream_decoder_set_error_callback(decoder, error_cb) &&
+ FLAC__seekable_stream_decoder_set_client_data(decoder, data) &&
+ FLAC__seekable_stream_decoder_init(decoder) == FLAC__SEEKABLE_STREAM_DECODER_OK
+ ? FLAC__STREAM_DECODER_INIT_STATUS_OK
+ : FLAC__STREAM_DECODER_INIT_STATUS_ERROR;
+}
+
+#else /* FLAC_API_VERSION_CURRENT > 7 */
+
+# include <FLAC/stream_decoder.h>
+
+# define flac_init(a,b,c,d,e,f,g,h,i,j) \
+ (FLAC__stream_decoder_init_stream(a,b,c,d,e,f,g,h,i,j) \
+ == FLAC__STREAM_DECODER_INIT_STATUS_OK)
+
+typedef size_t flac_read_status_size_t;
+
+#endif /* FLAC_API_VERSION_CURRENT >= 7 */
+
+#endif /* _FLAC_COMMON_H */
diff --git a/src/decoder/flac_decoder_plugin.c b/src/decoder/flac_decoder_plugin.c
new file mode 100644
index 000000000..9d980b79d
--- /dev/null
+++ b/src/decoder/flac_decoder_plugin.c
@@ -0,0 +1,497 @@
+/*
+ * 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" /* must be first for large file support */
+#include "_flac_common.h"
+#include "flac_compat.h"
+#include "flac_metadata.h"
+
+#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7
+#include "_ogg_common.h"
+#endif
+
+#include <glib.h>
+
+#include <assert.h>
+#include <unistd.h>
+
+#include <sys/stat.h>
+#include <sys/types.h>
+
+/* this code was based on flac123, from flac-tools */
+
+static FLAC__StreamDecoderReadStatus
+flac_read_cb(G_GNUC_UNUSED const FLAC__StreamDecoder *fd,
+ FLAC__byte buf[], flac_read_status_size_t *bytes,
+ void *fdata)
+{
+ struct flac_data *data = fdata;
+ size_t r;
+
+ r = decoder_read(data->decoder, data->input_stream,
+ (void *)buf, *bytes);
+ *bytes = r;
+
+ if (r == 0) {
+ if (decoder_get_command(data->decoder) != DECODE_COMMAND_NONE ||
+ input_stream_eof(data->input_stream))
+ return FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM;
+ else
+ return FLAC__STREAM_DECODER_READ_STATUS_ABORT;
+ }
+
+ return FLAC__STREAM_DECODER_READ_STATUS_CONTINUE;
+}
+
+static FLAC__StreamDecoderSeekStatus
+flac_seek_cb(G_GNUC_UNUSED const FLAC__StreamDecoder *fd,
+ FLAC__uint64 offset, void *fdata)
+{
+ struct flac_data *data = (struct flac_data *) fdata;
+
+ if (!data->input_stream->seekable)
+ return FLAC__STREAM_DECODER_SEEK_STATUS_UNSUPPORTED;
+
+ if (!input_stream_seek(data->input_stream, offset, SEEK_SET, NULL))
+ return FLAC__STREAM_DECODER_SEEK_STATUS_ERROR;
+
+ return FLAC__STREAM_DECODER_SEEK_STATUS_OK;
+}
+
+static FLAC__StreamDecoderTellStatus
+flac_tell_cb(G_GNUC_UNUSED const FLAC__StreamDecoder *fd,
+ FLAC__uint64 * offset, void *fdata)
+{
+ struct flac_data *data = (struct flac_data *) fdata;
+
+ if (!data->input_stream->seekable)
+ return FLAC__STREAM_DECODER_TELL_STATUS_UNSUPPORTED;
+
+ *offset = (long)(data->input_stream->offset);
+
+ return FLAC__STREAM_DECODER_TELL_STATUS_OK;
+}
+
+static FLAC__StreamDecoderLengthStatus
+flac_length_cb(G_GNUC_UNUSED const FLAC__StreamDecoder *fd,
+ FLAC__uint64 * length, void *fdata)
+{
+ struct flac_data *data = (struct flac_data *) fdata;
+
+ if (data->input_stream->size < 0)
+ return FLAC__STREAM_DECODER_LENGTH_STATUS_UNSUPPORTED;
+
+ *length = (size_t) (data->input_stream->size);
+
+ return FLAC__STREAM_DECODER_LENGTH_STATUS_OK;
+}
+
+static FLAC__bool
+flac_eof_cb(G_GNUC_UNUSED const FLAC__StreamDecoder *fd, 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
+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);
+}
+
+#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";
+ }
+
+ g_warning("%s\n", str);
+}
+#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:
+ case FLAC__STREAM_DECODER_SEARCH_FOR_FRAME_SYNC:
+ 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";
+ }
+
+ g_warning("%s\n", str);
+}
+#endif /* FLAC_API_VERSION_CURRENT >= 7 */
+
+static void flacMetadata(G_GNUC_UNUSED const FLAC__StreamDecoder * dec,
+ const FLAC__StreamMetadata * block, void *vdata)
+{
+ flac_metadata_common_cb(block, (struct flac_data *) vdata);
+}
+
+static FLAC__StreamDecoderWriteStatus
+flac_write_cb(const FLAC__StreamDecoder *dec, const FLAC__Frame *frame,
+ const FLAC__int32 *const buf[], void *vdata)
+{
+ struct flac_data *data = (struct flac_data *) vdata;
+ FLAC__uint64 nbytes = 0;
+
+ if (FLAC__stream_decoder_get_decode_position(dec, &nbytes)) {
+ if (data->position > 0 && nbytes > data->position) {
+ nbytes -= data->position;
+ data->position += nbytes;
+ } else {
+ data->position = nbytes;
+ nbytes = 0;
+ }
+ } else
+ nbytes = 0;
+
+ return flac_common_write(data, frame, buf, nbytes);
+}
+
+static struct tag *
+flac_tag_dup(const char *file)
+{
+ return flac_tag_load(file, NULL);
+}
+
+/**
+ * Some glue code around FLAC__stream_decoder_new().
+ */
+static FLAC__StreamDecoder *
+flac_decoder_new(void)
+{
+ FLAC__StreamDecoder *sd = FLAC__stream_decoder_new();
+ if (sd == NULL) {
+ g_warning("FLAC__stream_decoder_new() failed");
+ return NULL;
+ }
+
+#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7
+ if(!FLAC__stream_decoder_set_metadata_respond(sd, FLAC__METADATA_TYPE_VORBIS_COMMENT))
+ g_debug("FLAC__stream_decoder_set_metadata_respond() has failed");
+#endif
+
+ return sd;
+}
+
+static bool
+flac_decoder_initialize(struct flac_data *data, FLAC__StreamDecoder *sd,
+ FLAC__uint64 duration)
+{
+ data->total_frames = duration;
+
+ if (!FLAC__stream_decoder_process_until_end_of_metadata(sd)) {
+ g_warning("problem reading metadata");
+ return false;
+ }
+
+ if (data->initialized) {
+ /* done */
+ decoder_initialized(data->decoder, &data->audio_format,
+ data->input_stream->seekable,
+ (float)data->total_frames /
+ (float)data->audio_format.sample_rate);
+ return true;
+ }
+
+ if (data->input_stream->seekable)
+ /* allow the workaround below only for nonseekable
+ streams*/
+ return false;
+
+ /* no stream_info packet found; try to initialize the decoder
+ from the first frame header */
+ FLAC__stream_decoder_process_single(sd);
+ return data->initialized;
+}
+
+static void
+flac_decoder_loop(struct flac_data *data, FLAC__StreamDecoder *flac_dec,
+ FLAC__uint64 t_start, FLAC__uint64 t_end)
+{
+ struct decoder *decoder = data->decoder;
+ enum decoder_command cmd;
+
+ data->first_frame = t_start;
+
+ while (true) {
+ if (data->tag != NULL && !tag_is_empty(data->tag)) {
+ cmd = decoder_tag(data->decoder, data->input_stream,
+ data->tag);
+ tag_free(data->tag);
+ data->tag = tag_new();
+ } else
+ cmd = decoder_get_command(decoder);
+
+ if (cmd == DECODE_COMMAND_SEEK) {
+ FLAC__uint64 seek_sample = t_start +
+ decoder_seek_where(decoder) *
+ data->audio_format.sample_rate;
+ if (seek_sample >= t_start &&
+ (t_end == 0 || seek_sample <= t_end) &&
+ FLAC__stream_decoder_seek_absolute(flac_dec, seek_sample)) {
+ data->next_frame = seek_sample;
+ data->position = 0;
+ decoder_command_finished(decoder);
+ } else
+ decoder_seek_error(decoder);
+ } else if (cmd == DECODE_COMMAND_STOP ||
+ FLAC__stream_decoder_get_state(flac_dec) == FLAC__STREAM_DECODER_END_OF_STREAM)
+ break;
+
+ if (t_end != 0 && data->next_frame >= t_end)
+ /* 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 (cmd != DECODE_COMMAND_STOP) {
+ flacPrintErroredState(FLAC__stream_decoder_get_state(flac_dec));
+ FLAC__stream_decoder_finish(flac_dec);
+ }
+}
+
+static void
+flac_decode_internal(struct decoder * decoder,
+ struct input_stream *input_stream,
+ bool is_ogg)
+{
+ FLAC__StreamDecoder *flac_dec;
+ struct flac_data data;
+ const char *err = NULL;
+
+ flac_dec = flac_decoder_new();
+ if (flac_dec == NULL)
+ return;
+
+ flac_data_init(&data, decoder, input_stream);
+ data.tag = tag_new();
+
+ if (is_ogg) {
+#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;
+#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;
+ }
+ }
+
+ if (!flac_decoder_initialize(&data, flac_dec, 0)) {
+ flac_data_deinit(&data);
+ 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);
+}
+
+static void
+flac_decode(struct decoder * decoder, struct input_stream *input_stream)
+{
+ flac_decode_internal(decoder, input_stream, false);
+}
+
+#ifndef HAVE_OGGFLAC
+
+static bool
+oggflac_init(G_GNUC_UNUSED const struct config_param *param)
+{
+#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7
+ return !!FLAC_API_SUPPORTS_OGG_FLAC;
+#else
+ /* disable oggflac when libflac is too old */
+ return false;
+#endif
+}
+
+#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7
+
+static struct tag *
+oggflac_tag_dup(const char *file)
+{
+ 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;
+ 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);
+ } 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;
+}
+
+static void
+oggflac_decode(struct decoder *decoder, struct input_stream *input_stream)
+{
+ 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_decode_internal(decoder, input_stream, true);
+}
+
+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-flac+ogg",
+ "audio/x-ogg",
+ NULL
+};
+
+#endif /* FLAC_API_VERSION_CURRENT >= 7 */
+
+const struct decoder_plugin oggflac_decoder_plugin = {
+ .name = "oggflac",
+ .init = oggflac_init,
+#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7
+ .stream_decode = oggflac_decode,
+ .tag_dup = oggflac_tag_dup,
+ .suffixes = oggflac_suffixes,
+ .mime_types = oggflac_mime_types
+#endif
+};
+
+#endif /* HAVE_OGGFLAC */
+
+static const char *const flac_suffixes[] = { "flac", NULL };
+static const char *const flac_mime_types[] = {
+ "application/flac",
+ "application/x-flac",
+ "audio/flac",
+ "audio/x-flac",
+ NULL
+};
+
+const struct decoder_plugin flac_decoder_plugin = {
+ .name = "flac",
+ .stream_decode = flac_decode,
+ .tag_dup = flac_tag_dup,
+ .suffixes = flac_suffixes,
+ .mime_types = flac_mime_types,
+};
diff --git a/src/decoder/flac_metadata.c b/src/decoder/flac_metadata.c
new file mode 100644
index 000000000..f2f2f954d
--- /dev/null
+++ b/src/decoder/flac_metadata.c
@@ -0,0 +1,286 @@
+/*
+ * 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 "flac_metadata.h"
+#include "replay_gain_info.h"
+#include "tag.h"
+
+#include <glib.h>
+
+#include <assert.h>
+#include <stdbool.h>
+#include <stdlib.h>
+
+static bool
+flac_find_float_comment(const FLAC__StreamMetadata *block,
+ const char *cmnt, float *fl)
+{
+ int offset;
+ size_t pos;
+ int len;
+ unsigned char tmp, *p;
+
+ offset = FLAC__metadata_object_vorbiscomment_find_entry_from(block, 0,
+ cmnt);
+ if (offset < 0)
+ return false;
+
+ pos = strlen(cmnt) + 1; /* 1 is for '=' */
+ len = block->data.vorbis_comment.comments[offset].length - pos;
+ if (len <= 0)
+ return false;
+
+ p = &block->data.vorbis_comment.comments[offset].entry[pos];
+ tmp = p[len];
+ p[len] = '\0';
+ *fl = (float)atof((char *)p);
+ p[len] = tmp;
+
+ return true;
+}
+
+bool
+flac_parse_replay_gain(struct replay_gain_info *rgi,
+ const FLAC__StreamMetadata *block)
+{
+ bool found = false;
+
+ replay_gain_info_init(rgi);
+
+ if (flac_find_float_comment(block, "replaygain_album_gain",
+ &rgi->tuples[REPLAY_GAIN_ALBUM].gain))
+ found = true;
+ if (flac_find_float_comment(block, "replaygain_album_peak",
+ &rgi->tuples[REPLAY_GAIN_ALBUM].peak))
+ found = true;
+ if (flac_find_float_comment(block, "replaygain_track_gain",
+ &rgi->tuples[REPLAY_GAIN_TRACK].gain))
+ found = true;
+ if (flac_find_float_comment(block, "replaygain_track_peak",
+ &rgi->tuples[REPLAY_GAIN_TRACK].peak))
+ found = true;
+
+ return found;
+}
+
+static bool
+flac_find_string_comment(const FLAC__StreamMetadata *block,
+ const char *cmnt, char **str)
+{
+ int offset;
+ size_t pos;
+ int len;
+ const unsigned char *p;
+
+ *str = NULL;
+ offset = FLAC__metadata_object_vorbiscomment_find_entry_from(block, 0,
+ cmnt);
+ if (offset < 0)
+ return false;
+
+ pos = strlen(cmnt) + 1; /* 1 is for '=' */
+ len = block->data.vorbis_comment.comments[offset].length - pos;
+ if (len <= 0)
+ return false;
+
+ p = &block->data.vorbis_comment.comments[offset].entry[pos];
+ *str = g_strndup((const char *)p, len);
+
+ return true;
+}
+
+bool
+flac_parse_mixramp(char **mixramp_start, char **mixramp_end,
+ const FLAC__StreamMetadata *block)
+{
+ bool found = false;
+
+ if (flac_find_string_comment(block, "mixramp_start", mixramp_start))
+ found = true;
+ if (flac_find_string_comment(block, "mixramp_end", mixramp_end))
+ found = true;
+
+ return found;
+}
+
+/**
+ * Checks if the specified name matches the entry's name, and if yes,
+ * returns the comment value (not null-temrinated).
+ */
+static const char *
+flac_comment_value(const FLAC__StreamMetadata_VorbisComment_Entry *entry,
+ const char *name, const char *char_tnum, size_t *length_r)
+{
+ size_t name_length = strlen(name);
+ size_t char_tnum_length = 0;
+ const char *comment = (const char*)entry->entry;
+
+ if (entry->length <= name_length ||
+ g_ascii_strncasecmp(comment, name, name_length) != 0)
+ return NULL;
+
+ if (char_tnum != NULL) {
+ char_tnum_length = strlen(char_tnum);
+ if (entry->length > name_length + char_tnum_length + 2 &&
+ comment[name_length] == '[' &&
+ g_ascii_strncasecmp(comment + name_length + 1,
+ char_tnum, char_tnum_length) == 0 &&
+ comment[name_length + char_tnum_length + 1] == ']')
+ name_length = name_length + char_tnum_length + 2;
+ else if (entry->length > name_length + char_tnum_length &&
+ g_ascii_strncasecmp(comment + name_length,
+ char_tnum, char_tnum_length) == 0)
+ name_length = name_length + char_tnum_length;
+ }
+
+ if (comment[name_length] == '=') {
+ *length_r = entry->length - name_length - 1;
+ return comment + name_length + 1;
+ }
+
+ return NULL;
+}
+
+/**
+ * Check if the comment's name equals the passed name, and if so, copy
+ * the comment value into the tag.
+ */
+static bool
+flac_copy_comment(struct tag *tag,
+ const FLAC__StreamMetadata_VorbisComment_Entry *entry,
+ const char *name, enum tag_type tag_type,
+ const char *char_tnum)
+{
+ 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);
+ 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 void
+flac_parse_comment(struct tag *tag, const char *char_tnum,
+ const FLAC__StreamMetadata_VorbisComment_Entry *entry)
+{
+ assert(tag != NULL);
+
+ 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;
+
+ for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i)
+ if (flac_copy_comment(tag, entry,
+ tag_item_names[i], i, char_tnum))
+ return;
+}
+
+void
+flac_vorbis_comments_to_tag(struct tag *tag, const char *char_tnum,
+ const FLAC__StreamMetadata_VorbisComment *comment)
+{
+ for (unsigned i = 0; i < comment->num_comments; ++i)
+ flac_parse_comment(tag, char_tnum, &comment->comments[i]);
+}
+
+void
+flac_tag_apply_metadata(struct tag *tag, const char *track,
+ const FLAC__StreamMetadata *block)
+{
+ switch (block->type) {
+ case FLAC__METADATA_TYPE_VORBIS_COMMENT:
+ flac_vorbis_comments_to_tag(tag, track,
+ &block->data.vorbis_comment);
+ break;
+
+ case FLAC__METADATA_TYPE_STREAMINFO:
+ tag->time = flac_duration(&block->data.stream_info);
+ break;
+
+ default:
+ break;
+ }
+}
+
+struct tag *
+flac_tag_load(const char *file, const char *char_tnum)
+{
+ struct tag *tag;
+ FLAC__Metadata_SimpleIterator *it;
+ FLAC__StreamMetadata *block = NULL;
+
+ it = FLAC__metadata_simple_iterator_new();
+ if (!FLAC__metadata_simple_iterator_init(it, file, 1, 0)) {
+ const char *err;
+ FLAC_API FLAC__Metadata_SimpleIteratorStatus s;
+
+ s = FLAC__metadata_simple_iterator_status(it);
+
+ switch (s) { /* slightly more human-friendly messages: */
+ case FLAC__METADATA_SIMPLE_ITERATOR_STATUS_ILLEGAL_INPUT:
+ err = "illegal input";
+ break;
+ case FLAC__METADATA_SIMPLE_ITERATOR_STATUS_ERROR_OPENING_FILE:
+ err = "error opening file";
+ break;
+ case FLAC__METADATA_SIMPLE_ITERATOR_STATUS_NOT_A_FLAC_FILE:
+ err = "not a FLAC file";
+ break;
+ default:
+ err = FLAC__Metadata_SimpleIteratorStatusString[s];
+ }
+ g_debug("Reading '%s' metadata gave the following error: %s\n",
+ file, err);
+ FLAC__metadata_simple_iterator_delete(it);
+ return NULL;
+ }
+
+ tag = tag_new();
+ do {
+ block = FLAC__metadata_simple_iterator_get_block(it);
+ if (!block)
+ break;
+
+ flac_tag_apply_metadata(tag, char_tnum, block);
+ FLAC__metadata_object_delete(block);
+ } while (FLAC__metadata_simple_iterator_next(it));
+
+ FLAC__metadata_simple_iterator_delete(it);
+
+ if (!tag_is_defined(tag)) {
+ tag_free(tag);
+ tag = NULL;
+ }
+
+ return tag;
+}
diff --git a/src/decoder/flac_metadata.h b/src/decoder/flac_metadata.h
new file mode 100644
index 000000000..06e691d1d
--- /dev/null
+++ b/src/decoder/flac_metadata.h
@@ -0,0 +1,55 @@
+/*
+ * 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_FLAC_METADATA_H
+#define MPD_FLAC_METADATA_H
+
+#include <stdbool.h>
+#include <FLAC/metadata.h>
+
+struct tag;
+struct replay_gain_info;
+
+static inline unsigned
+flac_duration(const FLAC__StreamMetadata_StreamInfo *stream_info)
+{
+ return (stream_info->total_samples + stream_info->sample_rate - 1) /
+ stream_info->sample_rate;
+}
+
+bool
+flac_parse_replay_gain(struct replay_gain_info *rgi,
+ const FLAC__StreamMetadata *block);
+
+bool
+flac_parse_mixramp(char **mixramp_start, char **mixramp_end,
+ const FLAC__StreamMetadata *block);
+
+void
+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);
+
+struct tag *
+flac_tag_load(const char *file, const char *char_tnum);
+
+#endif
diff --git a/src/decoder/flac_pcm.c b/src/decoder/flac_pcm.c
new file mode 100644
index 000000000..bf6e2612c
--- /dev/null
+++ b/src/decoder/flac_pcm.c
@@ -0,0 +1,109 @@
+/*
+ * 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 "flac_pcm.h"
+
+#include <assert.h>
+
+static void flac_convert_stereo16(int16_t *dest,
+ const FLAC__int32 * const buf[],
+ unsigned int position, unsigned int end)
+{
+ for (; position < end; ++position) {
+ *dest++ = buf[0][position];
+ *dest++ = buf[1][position];
+ }
+}
+
+static void
+flac_convert_16(int16_t *dest,
+ unsigned int num_channels,
+ const FLAC__int32 * const buf[],
+ unsigned int position, unsigned int end)
+{
+ unsigned int c_chan;
+
+ for (; position < end; ++position)
+ for (c_chan = 0; c_chan < num_channels; c_chan++)
+ *dest++ = buf[c_chan][position];
+}
+
+/**
+ * Note: this function also handles 24 bit files!
+ */
+static void
+flac_convert_32(int32_t *dest,
+ unsigned int num_channels,
+ const FLAC__int32 * const buf[],
+ unsigned int position, unsigned int end)
+{
+ unsigned int c_chan;
+
+ for (; position < end; ++position)
+ for (c_chan = 0; c_chan < num_channels; c_chan++)
+ *dest++ = buf[c_chan][position];
+}
+
+static void
+flac_convert_8(int8_t *dest,
+ unsigned int num_channels,
+ const FLAC__int32 * const buf[],
+ unsigned int position, unsigned int end)
+{
+ unsigned int c_chan;
+
+ for (; position < end; ++position)
+ for (c_chan = 0; c_chan < num_channels; c_chan++)
+ *dest++ = buf[c_chan][position];
+}
+
+void
+flac_convert(void *dest,
+ unsigned int num_channels, enum sample_format sample_format,
+ const FLAC__int32 *const buf[],
+ unsigned int position, unsigned int end)
+{
+ switch (sample_format) {
+ case SAMPLE_FORMAT_S16:
+ if (num_channels == 2)
+ flac_convert_stereo16((int16_t*)dest, buf,
+ position, end);
+ else
+ flac_convert_16((int16_t*)dest, num_channels, buf,
+ position, end);
+ break;
+
+ case SAMPLE_FORMAT_S24_P32:
+ case SAMPLE_FORMAT_S32:
+ flac_convert_32((int32_t*)dest, num_channels, buf,
+ position, end);
+ break;
+
+ case SAMPLE_FORMAT_S8:
+ flac_convert_8((int8_t*)dest, num_channels, buf,
+ position, end);
+ break;
+
+ case SAMPLE_FORMAT_S24:
+ case SAMPLE_FORMAT_UNDEFINED:
+ /* unreachable */
+ assert(false);
+ }
+}
diff --git a/src/normalize.c b/src/decoder/flac_pcm.h
index 63c0d15cb..bccfc645c 100644
--- a/src/normalize.c
+++ b/src/decoder/flac_pcm.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -17,33 +17,17 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
-#include "normalize.h"
-#include "compress.h"
-#include "conf.h"
-#include "audio_format.h"
-
-#define DEFAULT_VOLUME_NORMALIZATION 0
-
-int normalizationEnabled;
+#ifndef MPD_FLAC_PCM_H
+#define MPD_FLAC_PCM_H
-void initNormalization(void)
-{
- normalizationEnabled = config_get_bool(CONF_VOLUME_NORMALIZATION,
- DEFAULT_VOLUME_NORMALIZATION);
-
- if (normalizationEnabled)
- CompressCfg(0, ANTICLIP, TARGET, GAINMAX, GAINSMOOTH, BUCKETS);
-}
+#include "audio_format.h"
-void finishNormalization(void)
-{
- if (normalizationEnabled) CompressFree();
-}
+#include <FLAC/ordinals.h>
-void normalizeData(char *buffer, int bufferSize,
- const struct audio_format *format)
-{
- if ((format->bits != 16) || (format->channels != 2)) return;
+void
+flac_convert(void *dest,
+ unsigned int num_channels, enum sample_format sample_format,
+ const FLAC__int32 *const buf[],
+ unsigned int position, unsigned int end);
- CompressDo(buffer, bufferSize);
-}
+#endif
diff --git a/src/decoder/flac_plugin.c b/src/decoder/flac_plugin.c
deleted file mode 100644
index 1e568f70d..000000000
--- a/src/decoder/flac_plugin.c
+++ /dev/null
@@ -1,918 +0,0 @@
-/*
- * 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 "_flac_common.h"
-
-#include <glib.h>
-
-#include <assert.h>
-#include <unistd.h>
-
-#include <sys/stat.h>
-#include <sys/types.h>
-
-#ifdef HAVE_CUE /* libcue */
-#include "../cue/cue_tag.h"
-#endif
-
-/* this code was based on flac123, from flac-tools */
-
-static flac_read_status
-flac_read_cb(G_GNUC_UNUSED const flac_decoder *fd,
- FLAC__byte buf[], flac_read_status_size_t *bytes,
- void *fdata)
-{
- struct flac_data *data = fdata;
- size_t r;
-
- r = decoder_read(data->decoder, data->input_stream,
- (void *)buf, *bytes);
- *bytes = r;
-
- if (r == 0) {
- if (decoder_get_command(data->decoder) != DECODE_COMMAND_NONE ||
- input_stream_eof(data->input_stream))
- return flac_read_status_eof;
- else
- return flac_read_status_abort;
- }
-
- return flac_read_status_continue;
-}
-
-static flac_seek_status
-flac_seek_cb(G_GNUC_UNUSED const flac_decoder *fd,
- FLAC__uint64 offset, void *fdata)
-{
- struct flac_data *data = (struct flac_data *) fdata;
-
- if (!input_stream_seek(data->input_stream, offset, SEEK_SET))
- return flac_seek_status_error;
-
- return flac_seek_status_ok;
-}
-
-static flac_tell_status
-flac_tell_cb(G_GNUC_UNUSED const flac_decoder *fd,
- FLAC__uint64 * offset, void *fdata)
-{
- struct flac_data *data = (struct flac_data *) fdata;
-
- *offset = (long)(data->input_stream->offset);
-
- return flac_tell_status_ok;
-}
-
-static flac_length_status
-flac_length_cb(G_GNUC_UNUSED const flac_decoder *fd,
- FLAC__uint64 * length, void *fdata)
-{
- struct flac_data *data = (struct flac_data *) fdata;
-
- if (data->input_stream->size < 0)
- return flac_length_status_unsupported;
-
- *length = (size_t) (data->input_stream->size);
-
- return flac_length_status_ok;
-}
-
-static FLAC__bool
-flac_eof_cb(G_GNUC_UNUSED const flac_decoder *fd, 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
-flac_error_cb(G_GNUC_UNUSED const flac_decoder *fd,
- FLAC__StreamDecoderErrorStatus status, void *fdata)
-{
- flac_error_common_cb("flac", 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";
- }
-
- g_warning("%s\n", str);
-}
-
-static bool
-flac_init(FLAC__SeekableStreamDecoder *dec,
- FLAC__SeekableStreamDecoderReadCallback read_cb,
- FLAC__SeekableStreamDecoderSeekCallback seek_cb,
- FLAC__SeekableStreamDecoderTellCallback tell_cb,
- FLAC__SeekableStreamDecoderLengthCallback length_cb,
- FLAC__SeekableStreamDecoderEofCallback eof_cb,
- FLAC__SeekableStreamDecoderWriteCallback write_cb,
- FLAC__SeekableStreamDecoderMetadataCallback metadata_cb,
- FLAC__SeekableStreamDecoderErrorCallback error_cb,
- void *data)
-{
- return FLAC__seekable_stream_decoder_set_read_callback(dec, read_cb) &&
- FLAC__seekable_stream_decoder_set_seek_callback(dec, seek_cb) &&
- FLAC__seekable_stream_decoder_set_tell_callback(dec, tell_cb) &&
- FLAC__seekable_stream_decoder_set_length_callback(dec, length_cb) &&
- FLAC__seekable_stream_decoder_set_eof_callback(dec, eof_cb) &&
- FLAC__seekable_stream_decoder_set_write_callback(dec, write_cb) &&
- FLAC__seekable_stream_decoder_set_metadata_callback(dec, metadata_cb) &&
- FLAC__seekable_stream_decoder_set_metadata_respond(dec, FLAC__METADATA_TYPE_VORBIS_COMMENT) &&
- FLAC__seekable_stream_decoder_set_error_callback(dec, error_cb) &&
- FLAC__seekable_stream_decoder_set_client_data(dec, data) &&
- FLAC__seekable_stream_decoder_init(dec) == FLAC__SEEKABLE_STREAM_DECODER_OK;
-}
-#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:
- case FLAC__STREAM_DECODER_SEARCH_FOR_FRAME_SYNC:
- 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";
- }
-
- g_warning("%s\n", str);
-}
-#endif /* FLAC_API_VERSION_CURRENT >= 7 */
-
-static void flacMetadata(G_GNUC_UNUSED const flac_decoder * dec,
- const FLAC__StreamMetadata * block, void *vdata)
-{
- flac_metadata_common_cb(block, (struct flac_data *) vdata);
-}
-
-static FLAC__StreamDecoderWriteStatus
-flac_write_cb(const flac_decoder *dec, const FLAC__Frame *frame,
- const FLAC__int32 *const buf[], void *vdata)
-{
- FLAC__uint32 samples = frame->header.blocksize;
- struct flac_data *data = (struct flac_data *) vdata;
- float timeChange;
- FLAC__uint64 newPosition = 0;
-
- timeChange = ((float)samples) / frame->header.sample_rate;
- data->time += timeChange;
-
- flac_get_decode_position(dec, &newPosition);
- if (data->position && newPosition >= data->position) {
- assert(timeChange >= 0);
-
- data->bit_rate =
- ((newPosition - data->position) * 8.0 / timeChange)
- / 1000 + 0.5;
- }
- data->position = newPosition;
-
- return flac_common_write(data, frame, buf);
-}
-
-static struct tag *
-flac_tag_load(const char *file, const char *char_tnum)
-{
- struct tag *tag;
- FLAC__Metadata_SimpleIterator *it;
- FLAC__StreamMetadata *block = NULL;
-
- it = FLAC__metadata_simple_iterator_new();
- if (!FLAC__metadata_simple_iterator_init(it, file, 1, 0)) {
- const char *err;
- FLAC_API FLAC__Metadata_SimpleIteratorStatus s;
-
- s = FLAC__metadata_simple_iterator_status(it);
-
- switch (s) { /* slightly more human-friendly messages: */
- case FLAC__METADATA_SIMPLE_ITERATOR_STATUS_ILLEGAL_INPUT:
- err = "illegal input";
- break;
- case FLAC__METADATA_SIMPLE_ITERATOR_STATUS_ERROR_OPENING_FILE:
- err = "error opening file";
- break;
- case FLAC__METADATA_SIMPLE_ITERATOR_STATUS_NOT_A_FLAC_FILE:
- err = "not a FLAC file";
- break;
- default:
- err = FLAC__Metadata_SimpleIteratorStatusString[s];
- }
- g_debug("Reading '%s' metadata gave the following error: %s\n",
- file, err);
- FLAC__metadata_simple_iterator_delete(it);
- return NULL;
- }
-
- tag = tag_new();
- do {
- block = FLAC__metadata_simple_iterator_get_block(it);
- if (!block)
- break;
- if (block->type == FLAC__METADATA_TYPE_VORBIS_COMMENT) {
- flac_vorbis_comments_to_tag(tag, char_tnum, block);
- } else if (block->type == FLAC__METADATA_TYPE_STREAMINFO) {
- tag->time = ((float)block->data.stream_info.total_samples) /
- block->data.stream_info.sample_rate + 0.5;
- }
- FLAC__metadata_object_delete(block);
- } while (FLAC__metadata_simple_iterator_next(it));
-
- FLAC__metadata_simple_iterator_delete(it);
-
- if (!tag_is_defined(tag)) {
- tag_free(tag);
- tag = NULL;
- }
-
- return tag;
-}
-
-#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7
-
-static struct tag *
-flac_cue_tag_load(const char *file)
-{
- struct tag* tag = NULL;
- char* char_tnum = NULL;
- char* ptr = NULL;
- unsigned int tnum = 0;
- unsigned int sample_rate = 0;
- FLAC__uint64 track_time = 0;
-#ifdef HAVE_CUE /* libcue */
- FLAC__StreamMetadata* vc;
-#endif /* libcue */
- FLAC__StreamMetadata* si = FLAC__metadata_object_new(FLAC__METADATA_TYPE_STREAMINFO);
- FLAC__StreamMetadata* cs;
-
- tnum = flac_vtrack_tnum(file);
- char_tnum = g_strdup_printf("%u", tnum);
-
- ptr = strrchr(file, '/');
- *ptr = '\0';
-
-#ifdef HAVE_CUE /* libcue */
- if (FLAC__metadata_get_tags(file, &vc))
- {
- for (unsigned i = 0; i < vc->data.vorbis_comment.num_comments;
- i++)
- {
- if ((ptr = (char*)vc->data.vorbis_comment.comments[i].entry) != NULL)
- {
- if (g_ascii_strncasecmp(ptr, "cuesheet", 8) == 0)
- {
- while (*(++ptr) != '=');
- tag = cue_tag_string( ++ptr,
- tnum);
- }
- }
- }
-
- FLAC__metadata_object_delete(vc);
- }
-#endif /* libcue */
-
- if (tag == NULL)
- tag = flac_tag_load(file, char_tnum);
-
- if (char_tnum != NULL)
- {
- tag_add_item( tag,
- TAG_ITEM_TRACK,
- char_tnum);
- g_free(char_tnum);
- }
-
- if (FLAC__metadata_get_streaminfo(file, si))
- {
- sample_rate = si->data.stream_info.sample_rate;
- FLAC__metadata_object_delete(si);
- }
-
- if (FLAC__metadata_get_cuesheet(file, &cs))
- {
- if (cs->data.cue_sheet.tracks != NULL
- && (tnum <= cs->data.cue_sheet.num_tracks - 1))
- {
- track_time = cs->data.cue_sheet.tracks[tnum].offset
- - cs->data.cue_sheet.tracks[tnum - 1].offset;
- }
- FLAC__metadata_object_delete(cs);
- }
-
- if (sample_rate != 0)
- {
- tag->time = (unsigned int)(track_time/sample_rate);
- }
-
- return tag;
-}
-
-#endif /* FLAC_API_VERSION_CURRENT >= 7 */
-
-static struct tag *
-flac_tag_dup(const char *file)
-{
-#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7
- struct stat st;
-
- if (stat(file, &st) < 0)
- return flac_cue_tag_load(file);
- else
-#endif /* FLAC_API_VERSION_CURRENT >= 7 */
- return flac_tag_load(file, NULL);
-}
-
-static void
-flac_decode_internal(struct decoder * decoder,
- struct input_stream *input_stream,
- bool is_ogg)
-{
- flac_decoder *flac_dec;
- struct flac_data data;
- enum decoder_command cmd;
- const char *err = NULL;
-
- if (!(flac_dec = flac_new()))
- return;
- flac_data_init(&data, decoder, input_stream);
- data.tag = tag_new();
-
-#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7
- if(!FLAC__stream_decoder_set_metadata_respond(flac_dec, FLAC__METADATA_TYPE_VORBIS_COMMENT))
- {
- g_debug("Failed to set metadata respond\n");
- }
-#endif
-
- if (is_ogg) {
- if (!flac_ogg_init(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)) {
- err = "doing Ogg init()";
- goto fail;
- }
- } else {
- if (!flac_init(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)) {
- err = "doing init()";
- goto fail;
- }
- }
-
- if (!flac_process_metadata(flac_dec)) {
- err = "problem reading metadata";
- goto fail;
- }
-
- if (!audio_format_valid(&data.audio_format)) {
- g_warning("Invalid audio format: %u:%u:%u\n",
- data.audio_format.sample_rate,
- data.audio_format.bits,
- data.audio_format.channels);
- goto fail;
- }
-
- decoder_initialized(decoder, &data.audio_format,
- input_stream->seekable, data.total_time);
-
- while (true) {
- if (!tag_is_empty(data.tag)) {
- cmd = decoder_tag(decoder, input_stream, data.tag);
- tag_free(data.tag);
- data.tag = tag_new();
- } else
- cmd = decoder_get_command(decoder);
-
- if (cmd == DECODE_COMMAND_SEEK) {
- FLAC__uint64 seek_sample = decoder_seek_where(decoder) *
- data.audio_format.sample_rate + 0.5;
- if (flac_seek_absolute(flac_dec, seek_sample)) {
- data.time = ((float)seek_sample) /
- data.audio_format.sample_rate;
- data.position = 0;
- decoder_command_finished(decoder);
- } else
- decoder_seek_error(decoder);
- } else if (cmd == DECODE_COMMAND_STOP ||
- flac_get_state(flac_dec) == flac_decoder_eof)
- break;
-
- if (!flac_process_single(flac_dec)) {
- cmd = decoder_get_command(decoder);
- if (cmd != DECODE_COMMAND_SEEK)
- break;
- }
- }
- if (cmd != DECODE_COMMAND_STOP) {
- flacPrintErroredState(flac_get_state(flac_dec));
- flac_finish(flac_dec);
- }
-
-fail:
- if (data.replay_gain_info)
- replay_gain_info_free(data.replay_gain_info);
-
- tag_free(data.tag);
-
- if (flac_dec)
- flac_delete(flac_dec);
-
- if (err)
- g_warning("%s\n", err);
-}
-
-static void
-flac_decode(struct decoder * decoder, struct input_stream *input_stream)
-{
- flac_decode_internal(decoder, input_stream, false);
-}
-
-#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7
-
-/**
- * @brief Decode a flac file with embedded cue sheets
- * @param const char* fname filename on fs
- */
-static void
-flac_container_decode(struct decoder* decoder,
- const char* fname,
- bool is_ogg)
-{
- unsigned int tnum = 0;
- FLAC__uint64 t_start = 0;
- FLAC__uint64 t_end = 0;
- FLAC__uint64 track_time = 0;
- FLAC__StreamMetadata* cs = NULL;
-
- flac_decoder *flac_dec;
- struct flac_data data;
- const char *err = NULL;
-
- char* pathname = g_strdup(fname);
- char* slash = strrchr(pathname, '/');
- *slash = '\0';
-
- tnum = flac_vtrack_tnum(fname);
-
- cs = FLAC__metadata_object_new(FLAC__METADATA_TYPE_CUESHEET);
-
- FLAC__metadata_get_cuesheet(pathname, &cs);
-
- if (cs != NULL)
- {
- if (cs->data.cue_sheet.tracks != NULL
- && (tnum <= cs->data.cue_sheet.num_tracks - 1))
- {
- t_start = cs->data.cue_sheet.tracks[tnum - 1].offset;
- t_end = cs->data.cue_sheet.tracks[tnum].offset;
- track_time = cs->data.cue_sheet.tracks[tnum].offset
- - cs->data.cue_sheet.tracks[tnum - 1].offset;
- }
-
- FLAC__metadata_object_delete(cs);
- }
- else
- {
- g_free(pathname);
- return;
- }
-
- if (!(flac_dec = flac_new()))
- {
- g_free(pathname);
- return;
- }
-
- flac_data_init(&data, decoder, NULL);
-
-#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7
- if(!FLAC__stream_decoder_set_metadata_respond(flac_dec, FLAC__METADATA_TYPE_VORBIS_COMMENT))
- {
- g_debug("Failed to set metadata respond\n");
- }
-#endif
-
-
- if (is_ogg)
- {
- if (FLAC__stream_decoder_init_ogg_file( flac_dec,
- pathname,
- flac_write_cb,
- flacMetadata,
- flac_error_cb,
- (void*) &data )
- != FLAC__STREAM_DECODER_INIT_STATUS_OK )
- {
- err = "doing Ogg init()";
- goto fail;
- }
- }
- else
- {
- if (FLAC__stream_decoder_init_file( flac_dec,
- pathname,
- flac_write_cb,
- flacMetadata,
- flac_error_cb,
- (void*) &data )
- != FLAC__STREAM_DECODER_INIT_STATUS_OK )
- {
- err = "doing init()";
- goto fail;
- }
- }
-
- if (!flac_process_metadata(flac_dec))
- {
- err = "problem reading metadata";
- goto fail;
- }
-
- if (!audio_format_valid(&data.audio_format))
- {
- g_warning("Invalid audio format: %u:%u:%u\n",
- data.audio_format.sample_rate,
- data.audio_format.bits,
- data.audio_format.channels);
- goto fail;
- }
-
- // set track time (order is important: after stream init)
- data.total_time = ((float)track_time / (float)data.audio_format.sample_rate);
- data.position = 0;
-
- decoder_initialized(decoder, &data.audio_format,
- true, data.total_time);
-
- // seek to song start (order is important: after decoder init)
- flac_seek_absolute(flac_dec, (FLAC__uint64)t_start);
-
- while (true)
- {
- if (!flac_process_single(flac_dec))
- break;
-
- // we only need to break at the end of track if we are in "cue mode"
- if (data.time >= data.total_time)
- {
- flacPrintErroredState(flac_get_state(flac_dec));
- flac_finish(flac_dec);
- }
-
- if (decoder_get_command(decoder) == DECODE_COMMAND_SEEK)
- {
- FLAC__uint64 seek_sample = t_start +
- (decoder_seek_where(decoder) * data.audio_format.sample_rate);
-
- if (seek_sample >= t_start && seek_sample <= t_end &&
- flac_seek_absolute(flac_dec, (FLAC__uint64)seek_sample)) {
- data.time = (float)(seek_sample - t_start) /
- data.audio_format.sample_rate;
- data.position = 0;
-
- decoder_command_finished(decoder);
- } else
- decoder_seek_error(decoder);
- }
- else if (flac_get_state(flac_dec) == flac_decoder_eof)
- break;
- }
-
- if (decoder_get_command(decoder) != DECODE_COMMAND_STOP)
- {
- flacPrintErroredState(flac_get_state(flac_dec));
- flac_finish(flac_dec);
- }
-
-fail:
- if (pathname)
- g_free(pathname);
-
- if (data.replay_gain_info)
- replay_gain_info_free(data.replay_gain_info);
-
- if (flac_dec)
- flac_delete(flac_dec);
-
- if (err)
- g_warning("%s\n", err);
-}
-
-/**
- * @brief Open a flac file for decoding
- * @param const char* fname filename on fs
- */
-static void
-flac_filedecode_internal(struct decoder* decoder,
- const char* fname,
- bool is_ogg)
-{
- flac_decoder *flac_dec;
- struct flac_data data;
- const char *err = NULL;
- unsigned int flac_err_state = 0;
-
- if (!(flac_dec = flac_new()))
- return;
-
- flac_data_init(&data, decoder, NULL);
-
-#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7
- if(!FLAC__stream_decoder_set_metadata_respond(flac_dec, FLAC__METADATA_TYPE_VORBIS_COMMENT))
- {
- g_debug("Failed to set metadata respond\n");
- }
-#endif
-
-
- if (is_ogg)
- {
- if ( (flac_err_state = FLAC__stream_decoder_init_ogg_file( flac_dec,
- fname,
- flac_write_cb,
- flacMetadata,
- flac_error_cb,
- (void*) &data ))
- == FLAC__STREAM_DECODER_INIT_STATUS_ERROR_OPENING_FILE)
- {
- flac_container_decode(decoder, fname, is_ogg);
- }
- else if (flac_err_state != FLAC__STREAM_DECODER_INIT_STATUS_OK)
- {
- err = "doing Ogg init()";
- goto fail;
- }
- }
- else
- {
- if ( (flac_err_state = FLAC__stream_decoder_init_file( flac_dec,
- fname,
- flac_write_cb,
- flacMetadata,
- flac_error_cb,
- (void*) &data ))
- == FLAC__STREAM_DECODER_INIT_STATUS_ERROR_OPENING_FILE)
- {
- flac_container_decode(decoder, fname, is_ogg);
- }
- else if (flac_err_state != FLAC__STREAM_DECODER_INIT_STATUS_OK)
- {
- err = "doing init()";
- goto fail;
- }
- }
-
- if (!flac_process_metadata(flac_dec))
- {
- err = "problem reading metadata";
- goto fail;
- }
-
- if (!audio_format_valid(&data.audio_format))
- {
- g_warning("Invalid audio format: %u:%u:%u\n",
- data.audio_format.sample_rate,
- data.audio_format.bits,
- data.audio_format.channels);
- goto fail;
- }
-
- decoder_initialized(decoder, &data.audio_format,
- true, data.total_time);
-
- while (true)
- {
- if (!flac_process_single(flac_dec))
- break;
-
- if (decoder_get_command(decoder) == DECODE_COMMAND_SEEK)
- {
- FLAC__uint64 seek_sample = decoder_seek_where(decoder) *
- data.audio_format.sample_rate + 0.5;
- if (flac_seek_absolute(flac_dec, seek_sample))
- {
- data.time = ((float)seek_sample) /
- data.audio_format.sample_rate;
- data.position = 0;
- decoder_command_finished(decoder);
- }
- else
- decoder_seek_error(decoder);
-
- }
- else if (flac_get_state(flac_dec) == flac_decoder_eof)
- break;
- }
-
- if (decoder_get_command(decoder) != DECODE_COMMAND_STOP)
- {
- flacPrintErroredState(flac_get_state(flac_dec));
- flac_finish(flac_dec);
- }
-
-fail:
- if (data.replay_gain_info)
- replay_gain_info_free(data.replay_gain_info);
-
- if (flac_dec)
- flac_delete(flac_dec);
-
- if (err)
- g_warning("%s\n", err);
-}
-
-/**
- * @brief wrapper function for
- * flac_filedecode_internal method
- * for decoding without ogg
- */
-static void
-flac_filedecode(struct decoder *decoder, const char *fname)
-{
- struct stat st;
-
- if (stat(fname, &st) < 0) {
- flac_container_decode(decoder, fname, false);
- } else
- flac_filedecode_internal(decoder, fname, false);
-}
-
-#endif /* FLAC_API_VERSION_CURRENT >= 7 */
-
-#ifndef HAVE_OGGFLAC
-
-static bool
-oggflac_init(G_GNUC_UNUSED const struct config_param *param)
-{
-#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7
- return !!FLAC_API_SUPPORTS_OGG_FLAC;
-#else
- /* disable oggflac when libflac is too old */
- return false;
-#endif
-}
-
-#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7
-
-static struct tag *
-oggflac_tag_dup(const char *file)
-{
- 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;
- it = FLAC__metadata_iterator_new();
- FLAC__metadata_iterator_init(it, chain);
-
- ret = tag_new();
- do {
- if (!(block = FLAC__metadata_iterator_get_block(it)))
- break;
- if (block->type == FLAC__METADATA_TYPE_VORBIS_COMMENT) {
- flac_vorbis_comments_to_tag(ret, NULL, block);
- } else if (block->type == FLAC__METADATA_TYPE_STREAMINFO) {
- ret->time = ((float)block->data.stream_info.
- total_samples) /
- block->data.stream_info.sample_rate + 0.5;
- }
- } 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;
-}
-
-static void
-oggflac_decode(struct decoder *decoder, struct input_stream *input_stream)
-{
- 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);
-
- flac_decode_internal(decoder, input_stream, true);
-}
-
-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-flac+ogg",
- "audio/x-ogg",
- NULL
-};
-
-#endif /* FLAC_API_VERSION_CURRENT >= 7 */
-
-const struct decoder_plugin oggflac_decoder_plugin = {
- .name = "oggflac",
- .init = oggflac_init,
-#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7
- .stream_decode = oggflac_decode,
- .tag_dup = oggflac_tag_dup,
- .suffixes = oggflac_suffixes,
- .mime_types = oggflac_mime_types
-#endif
-};
-
-#endif /* HAVE_OGGFLAC */
-
-static const char *const flac_suffixes[] = { "flac", NULL };
-static const char *const flac_mime_types[] = {
- "application/flac",
- "application/x-flac",
- "audio/flac",
- "audio/x-flac",
- NULL
-};
-
-const struct decoder_plugin flac_decoder_plugin = {
- .name = "flac",
- .stream_decode = flac_decode,
-#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7
- .file_decode = flac_filedecode,
-#endif /* FLAC_API_VERSION_CURRENT >= 7 */
- .tag_dup = flac_tag_dup,
- .suffixes = flac_suffixes,
- .mime_types = flac_mime_types,
-#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7
- .container_scan = flac_cue_track,
-#endif /* FLAC_API_VERSION_CURRENT >= 7 */
-};
diff --git a/src/decoder/fluidsynth_plugin.c b/src/decoder/fluidsynth_decoder_plugin.c
index 99c874c09..b9a2d0d99 100644
--- a/src/decoder/fluidsynth_plugin.c
+++ b/src/decoder/fluidsynth_decoder_plugin.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -26,9 +26,10 @@
*
*/
-#include "../decoder_api.h"
-#include "../timer.h"
-#include "../conf.h"
+#include "config.h"
+#include "decoder_api.h"
+#include "timer.h"
+#include "conf.h"
#include <glib.h>
@@ -87,7 +88,7 @@ fluidsynth_file_decode(struct decoder *decoder, const char *path_fs)
{
static const struct audio_format audio_format = {
.sample_rate = 48000,
- .bits = 16,
+ .format = SAMPLE_FORMAT_S16,
.channels = 2,
};
char setting_sample_rate[] = "synth.sample-rate";
@@ -203,7 +204,7 @@ fluidsynth_file_decode(struct decoder *decoder, const char *path_fs)
break;
cmd = decoder_data(decoder, NULL, buffer, sizeof(buffer),
- 0, 0, NULL);
+ 0);
} while (cmd == DECODE_COMMAND_NONE);
/* clean up */
diff --git a/src/decoder/gme_decoder_plugin.c b/src/decoder/gme_decoder_plugin.c
new file mode 100644
index 000000000..e14a52d32
--- /dev/null
+++ b/src/decoder/gme_decoder_plugin.c
@@ -0,0 +1,247 @@
+#include "config.h"
+#include "../decoder_api.h"
+#include "audio_check.h"
+#include "uri.h"
+
+#include <glib.h>
+#include <assert.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <gme/gme.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "gme"
+
+#define SUBTUNE_PREFIX "tune_"
+
+enum {
+ GME_SAMPLE_RATE = 44100,
+ GME_CHANNELS = 2,
+ GME_BUFFER_FRAMES = 2048,
+ GME_BUFFER_SAMPLES = GME_BUFFER_FRAMES * GME_CHANNELS,
+};
+
+/**
+ * returns the file path stripped of any /tune_xxx.* subtune
+ * suffix
+ */
+static char *
+get_container_name(const char *path_fs)
+{
+ const char *subtune_suffix = uri_get_suffix(path_fs);
+ char *path_container = g_strdup(path_fs);
+ char *pat = g_strconcat("*/" SUBTUNE_PREFIX "???.", subtune_suffix, NULL);
+ GPatternSpec *path_with_subtune = g_pattern_spec_new(pat);
+ g_free(pat);
+ if (!g_pattern_match(path_with_subtune,
+ strlen(path_container), path_container, NULL)) {
+ g_pattern_spec_free(path_with_subtune);
+ return path_container;
+ }
+
+ char *ptr = g_strrstr(path_container, "/" SUBTUNE_PREFIX);
+ if (ptr != NULL)
+ *ptr='\0';
+
+ g_pattern_spec_free(path_with_subtune);
+ return path_container;
+}
+
+/**
+ * returns tune number from file.nsf/tune_xxx.* style path or 0 if no subtune
+ * is appended.
+ */
+static int
+get_song_num(const char *path_fs)
+{
+ const char *subtune_suffix = uri_get_suffix(path_fs);
+ char *pat = g_strconcat("*/" SUBTUNE_PREFIX "???.", subtune_suffix, NULL);
+ GPatternSpec *path_with_subtune = g_pattern_spec_new(pat);
+ g_free(pat);
+
+ if (g_pattern_match(path_with_subtune,
+ strlen(path_fs), path_fs, NULL)) {
+ char *sub = g_strrstr(path_fs, "/" SUBTUNE_PREFIX);
+ g_pattern_spec_free(path_with_subtune);
+ if(!sub)
+ return 0;
+
+ sub += strlen("/" SUBTUNE_PREFIX);
+ int song_num = strtol(sub, NULL, 10);
+
+ return song_num - 1;
+ } else {
+ g_pattern_spec_free(path_with_subtune);
+ return 0;
+ }
+}
+
+static char *
+gme_container_scan(const char *path_fs, const unsigned int tnum)
+{
+ Music_Emu *emu;
+ const char* gme_err;
+ unsigned int num_songs;
+
+ gme_err = gme_open_file(path_fs, &emu, GME_SAMPLE_RATE);
+ if (gme_err != NULL) {
+ g_warning("%s", gme_err);
+ return NULL;
+ }
+
+ num_songs = gme_track_count(emu);
+ /* if it only contains a single tune, don't treat as container */
+ if (num_songs < 2)
+ return NULL;
+
+ const char *subtune_suffix = uri_get_suffix(path_fs);
+ if (tnum <= num_songs){
+ char *subtune = g_strdup_printf(
+ SUBTUNE_PREFIX "%03u.%s", tnum, subtune_suffix);
+ return subtune;
+ } else
+ return NULL;
+}
+
+static void
+gme_file_decode(struct decoder *decoder, const char *path_fs)
+{
+ float song_len;
+ Music_Emu *emu;
+ gme_info_t *ti;
+ struct audio_format audio_format;
+ enum decoder_command cmd;
+ short buf[GME_BUFFER_SAMPLES];
+ const char* gme_err;
+ char *path_container = get_container_name(path_fs);
+ int song_num = get_song_num(path_fs);
+
+ gme_err = gme_open_file(path_container, &emu, GME_SAMPLE_RATE);
+ g_free(path_container);
+ if (gme_err != NULL) {
+ g_warning("%s", gme_err);
+ return;
+ }
+
+ if((gme_err = gme_track_info(emu, &ti, song_num)) != NULL){
+ g_warning("%s", gme_err);
+ gme_delete(emu);
+ return;
+ }
+
+ if(ti->length > 0)
+ song_len = ti->length / 1000.0;
+ else song_len = -1;
+
+ /* initialize the MPD decoder */
+
+ GError *error = NULL;
+ if (!audio_format_init_checked(&audio_format, GME_SAMPLE_RATE,
+ SAMPLE_FORMAT_S16, GME_CHANNELS,
+ &error)) {
+ g_warning("%s", error->message);
+ g_error_free(error);
+ gme_free_info(ti);
+ gme_delete(emu);
+ return;
+ }
+
+ decoder_initialized(decoder, &audio_format, true, song_len);
+
+ if((gme_err = gme_start_track(emu, song_num)) != NULL)
+ g_warning("%s", gme_err);
+
+ if(ti->length > 0)
+ gme_set_fade(emu, ti->length);
+
+ /* play */
+ do {
+ gme_err = gme_play(emu, GME_BUFFER_SAMPLES, buf);
+ if (gme_err != NULL) {
+ g_warning("%s", gme_err);
+ return;
+ }
+ cmd = decoder_data(decoder, NULL, buf, sizeof(buf), 0);
+
+ if(cmd == DECODE_COMMAND_SEEK) {
+ float where = decoder_seek_where(decoder);
+ if((gme_err = gme_seek(emu, (int)where*1000)) != NULL)
+ g_warning("%s", gme_err);
+ decoder_command_finished(decoder);
+ }
+
+ if(gme_track_ended(emu))
+ break;
+ } while(cmd != DECODE_COMMAND_STOP);
+
+ gme_free_info(ti);
+ gme_delete(emu);
+}
+
+static struct tag *
+gme_tag_dup(const char *path_fs)
+{
+ Music_Emu *emu;
+ gme_info_t *ti;
+ const char* gme_err;
+ char *path_container=get_container_name(path_fs);
+ int song_num;
+ song_num=get_song_num(path_fs);
+
+ gme_err = gme_open_file(path_container, &emu, GME_SAMPLE_RATE);
+ g_free(path_container);
+ if (gme_err != NULL) {
+ g_warning("%s", gme_err);
+ return NULL;
+ }
+ if((gme_err = gme_track_info(emu, &ti, song_num)) != NULL){
+ g_warning("%s", gme_err);
+ gme_delete(emu);
+ return NULL;
+ }
+
+ 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);
+ }
+
+ gme_free_info(ti);
+ gme_delete(emu);
+ return tag;
+}
+
+static const char *const gme_suffixes[] = {
+ "ay", "gbs", "gym", "hes", "kss", "nsf",
+ "nsfe", "sap", "spc", "vgm", "vgz",
+ NULL
+};
+
+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,
+ .suffixes = gme_suffixes,
+ .container_scan = gme_container_scan,
+};
diff --git a/src/decoder/mad_plugin.c b/src/decoder/mad_decoder_plugin.c
index 9b3259485..2c2906c5c 100644
--- a/src/decoder/mad_plugin.c
+++ b/src/decoder/mad_decoder_plugin.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -17,10 +17,12 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
-#include "../decoder_api.h"
-#include "../conf.h"
#include "config.h"
+#include "decoder_api.h"
+#include "conf.h"
#include "tag_id3.h"
+#include "tag_rva2.h"
+#include "audio_check.h"
#include <assert.h>
#include <unistd.h>
@@ -125,6 +127,7 @@ struct mp3_data {
unsigned int drop_end_frames;
unsigned int drop_start_samples;
unsigned int drop_end_samples;
+ bool found_replay_gain;
bool found_xing;
bool found_first_frame;
bool decoded_first_frame;
@@ -148,6 +151,7 @@ mp3_data_init(struct mp3_data *data, struct decoder *decoder,
data->drop_end_frames = 0;
data->drop_start_samples = 0;
data->drop_end_samples = 0;
+ data->found_replay_gain = false;
data->found_xing = false;
data->found_first_frame = false;
data->decoded_first_frame = false;
@@ -164,7 +168,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))
+ if (!input_stream_seek(data->input_stream, offset, SEEK_SET, NULL))
return false;
mad_stream_buffer(&data->stream, data->input_buffer, 0);
@@ -208,105 +212,17 @@ mp3_fill_buffer(struct mp3_data *data)
}
#ifdef HAVE_ID3TAG
-/* Parse mp3 RVA2 frame. Shamelessly stolen from madplay. */
static bool
-parse_rva2(struct id3_tag *tag, struct replay_gain_info *replay_gain_info)
-{
- struct id3_frame const * frame;
-
- id3_latin1_t const *id;
- id3_byte_t const *data;
- id3_length_t length;
-
- enum {
- CHANNEL_OTHER = 0x00,
- CHANNEL_MASTER_VOLUME = 0x01,
- CHANNEL_FRONT_RIGHT = 0x02,
- CHANNEL_FRONT_LEFT = 0x03,
- CHANNEL_BACK_RIGHT = 0x04,
- CHANNEL_BACK_LEFT = 0x05,
- CHANNEL_FRONT_CENTRE = 0x06,
- CHANNEL_BACK_CENTRE = 0x07,
- CHANNEL_SUBWOOFER = 0x08
- };
-
- /* relative volume adjustment information */
-
- frame = id3_tag_findframe(tag, "RVA2", 0);
- if (frame == NULL)
- return false;
-
- id = id3_field_getlatin1(id3_frame_field(frame, 0));
- data = id3_field_getbinarydata(id3_frame_field(frame, 1),
- &length);
-
- if (id == NULL || data == NULL)
- return false;
-
- /*
- * "The 'identification' string is used to identify the
- * situation and/or device where this adjustment should apply.
- * The following is then repeated for every channel
- *
- * Type of channel $xx
- * Volume adjustment $xx xx
- * Bits representing peak $xx
- * Peak volume $xx (xx ...)"
- */
-
- while (length >= 4) {
- unsigned int peak_bytes;
-
- peak_bytes = (data[3] + 7) / 8;
- if (4 + peak_bytes > length)
- break;
-
- if (data[0] == CHANNEL_MASTER_VOLUME) {
- signed int voladj_fixed;
- double voladj_float;
-
- /*
- * "The volume adjustment is encoded as a fixed
- * point decibel value, 16 bit signed integer
- * representing (adjustment*512), giving +/- 64
- * dB with a precision of 0.001953125 dB."
- */
-
- voladj_fixed = (data[1] << 8) | (data[2] << 0);
- voladj_fixed |= -(voladj_fixed & 0x8000);
-
- voladj_float = (double) voladj_fixed / 512;
-
- replay_gain_info->tuples[REPLAY_GAIN_TRACK].gain = voladj_float;
- replay_gain_info->tuples[REPLAY_GAIN_ALBUM].gain = voladj_float;
-
- g_debug("parseRVA2: Relative Volume "
- "%+.1f dB adjustment (%s)\n",
- voladj_float, id);
-
- return true;
- }
-
- data += 4 + peak_bytes;
- length -= 4 + peak_bytes;
- }
-
- return false;
-}
-#endif
-
-#ifdef HAVE_ID3TAG
-static struct replay_gain_info *
-parse_id3_replay_gain_info(struct id3_tag *tag)
+parse_id3_replay_gain_info(struct replay_gain_info *replay_gain_info,
+ struct id3_tag *tag)
{
int i;
char *key;
char *value;
struct id3_frame *frame;
bool found = false;
- struct replay_gain_info *replay_gain_info;
- replay_gain_info = replay_gain_info_new();
+ replay_gain_info_init(replay_gain_info);
for (i = 0; (frame = id3_tag_findframe(tag, "TXXX", i)); i++) {
if (frame->nfields < 3)
@@ -337,21 +253,55 @@ parse_id3_replay_gain_info(struct id3_tag *tag)
free(value);
}
- if (!found) {
+ return found ||
/* fall back on RVA2 if no replaygain tags found */
- found = parse_rva2(tag, replay_gain_info);
+ tag_rva2_parse(tag, replay_gain_info);
+}
+#endif
+
+#ifdef HAVE_ID3TAG
+static bool
+parse_id3_mixramp(char **mixramp_start, char **mixramp_end,
+ struct id3_tag *tag)
+{
+ int i;
+ char *key;
+ char *value;
+ struct id3_frame *frame;
+ bool found = false;
+
+ *mixramp_start = NULL;
+ *mixramp_end = NULL;
+
+ for (i = 0; (frame = id3_tag_findframe(tag, "TXXX", i)); i++) {
+ if (frame->nfields < 3)
+ continue;
+
+ key = (char *)
+ id3_ucs4_latin1duplicate(id3_field_getstring
+ (&frame->fields[1]));
+ value = (char *)
+ id3_ucs4_latin1duplicate(id3_field_getstring
+ (&frame->fields[2]));
+
+ if (g_ascii_strcasecmp(key, "mixramp_start") == 0) {
+ *mixramp_start = g_strdup(value);
+ found = true;
+ } else if (g_ascii_strcasecmp(key, "mixramp_end") == 0) {
+ *mixramp_end = g_strdup(value);
+ found = true;
+ }
+
+ free(key);
+ free(value);
}
- if (found)
- return replay_gain_info;
- replay_gain_info_free(replay_gain_info);
- return NULL;
+ return found;
}
#endif
static void mp3_parse_id3(struct mp3_data *data, size_t tagsize,
- struct tag **mpd_tag,
- struct replay_gain_info **replay_gain_info_r)
+ struct tag **mpd_tag)
{
#ifdef HAVE_ID3TAG
struct id3_tag *id3_tag = NULL;
@@ -404,13 +354,20 @@ static void mp3_parse_id3(struct mp3_data *data, size_t tagsize,
}
}
- if (replay_gain_info_r) {
- struct replay_gain_info *tmp_rgi =
- parse_id3_replay_gain_info(id3_tag);
- if (tmp_rgi != NULL) {
- if (*replay_gain_info_r)
- replay_gain_info_free(*replay_gain_info_r);
- *replay_gain_info_r = tmp_rgi;
+ if (data->decoder != NULL) {
+ struct replay_gain_info rgi;
+ char *mixramp_start;
+ char *mixramp_end;
+ float replay_gain_db = 0;
+
+ if (parse_id3_replay_gain_info(&rgi, id3_tag)) {
+ replay_gain_db = decoder_replay_gain(data->decoder, &rgi);
+ data->found_replay_gain = true;
+ }
+ if (parse_id3_mixramp(&mixramp_start, &mixramp_end, id3_tag)) {
+ g_debug("setting mixramp_tags");
+ decoder_mixramp(data->decoder, replay_gain_db,
+ mixramp_start, mixramp_end);
}
}
@@ -419,7 +376,6 @@ static void mp3_parse_id3(struct mp3_data *data, size_t tagsize,
g_free(allocated);
#else /* !HAVE_ID3TAG */
(void)mpd_tag;
- (void)replay_gain_info_r;
/* This code is enabled when libid3tag is disabled. Instead
of parsing the ID3 frame, it just skips it. */
@@ -467,8 +423,7 @@ id3_tag_query(const void *p0, size_t length)
#endif /* !HAVE_ID3TAG */
static enum mp3_action
-decode_next_frame_header(struct mp3_data *data, G_GNUC_UNUSED struct tag **tag,
- G_GNUC_UNUSED struct replay_gain_info **replay_gain_info_r)
+decode_next_frame_header(struct mp3_data *data, G_GNUC_UNUSED struct tag **tag)
{
enum mad_layer layer;
@@ -490,7 +445,7 @@ decode_next_frame_header(struct mp3_data *data, G_GNUC_UNUSED struct tag **tag,
if (tagsize > 0) {
if (tag && !(*tag)) {
mp3_parse_id3(data, (size_t)tagsize,
- tag, replay_gain_info_r);
+ tag);
} else {
mad_stream_skip(&(data->stream),
tagsize);
@@ -592,14 +547,14 @@ enum {
XING_SCALE = 0x00000008L
};
-struct version {
+struct lame_version {
unsigned major;
unsigned minor;
};
struct lame {
char encoder[10]; /* 9 byte encoder name/version ("LAME3.97b") */
- struct version version; /* struct containing just the version */
+ struct lame_version version; /* struct containing just the version */
float peak; /* replaygain peak */
float track_gain; /* replaygain track gain */
float album_gain; /* replaygain album gain */
@@ -798,10 +753,10 @@ mp3_frame_duration(const struct mad_frame *frame)
MAD_UNITS_MILLISECONDS) / 1000.0;
}
-static off_t
+static goffset
mp3_this_frame_offset(const struct mp3_data *data)
{
- off_t offset = data->input_stream->offset;
+ goffset offset = data->input_stream->offset;
if (data->stream.this_frame != NULL)
offset -= data->stream.bufend - data->stream.this_frame;
@@ -811,7 +766,7 @@ mp3_this_frame_offset(const struct mp3_data *data)
return offset;
}
-static off_t
+static goffset
mp3_rest_including_this_frame(const struct mp3_data *data)
{
return data->input_stream->size - mp3_this_frame_offset(data);
@@ -823,7 +778,7 @@ mp3_rest_including_this_frame(const struct mp3_data *data)
static void
mp3_filesize_to_song_length(struct mp3_data *data)
{
- off_t rest = mp3_rest_including_this_frame(data);
+ goffset rest = mp3_rest_including_this_frame(data);
if (rest > 0) {
float frame_duration = mp3_frame_duration(&data->frame);
@@ -838,8 +793,7 @@ mp3_filesize_to_song_length(struct mp3_data *data)
}
static bool
-mp3_decode_first_frame(struct mp3_data *data, struct tag **tag,
- struct replay_gain_info **replay_gain_info_r)
+mp3_decode_first_frame(struct mp3_data *data, struct tag **tag)
{
struct xing xing;
struct lame lame;
@@ -853,8 +807,7 @@ mp3_decode_first_frame(struct mp3_data *data, struct tag **tag,
while (true) {
do {
- ret = decode_next_frame_header(data, tag,
- replay_gain_info_r);
+ ret = decode_next_frame_header(data, tag);
} while (ret == DECODE_CONT);
if (ret == DECODE_BREAK)
return false;
@@ -897,14 +850,17 @@ mp3_decode_first_frame(struct mp3_data *data, struct tag **tag,
/* Album gain isn't currently used. See comment in
* parse_lame() for details. -- jat */
- if (replay_gain_info_r && !*replay_gain_info_r &&
+ if (data->decoder != NULL &&
+ !data->found_replay_gain &&
lame.track_gain) {
- *replay_gain_info_r = replay_gain_info_new();
- (*replay_gain_info_r)->tuples[REPLAY_GAIN_TRACK].gain = lame.track_gain;
- (*replay_gain_info_r)->tuples[REPLAY_GAIN_TRACK].peak = lame.peak;
+ struct replay_gain_info rgi;
+ replay_gain_info_init(&rgi);
+ rgi.tuples[REPLAY_GAIN_TRACK].gain = lame.track_gain;
+ rgi.tuples[REPLAY_GAIN_TRACK].peak = lame.peak;
+ decoder_replay_gain(data->decoder, &rgi);
}
}
- }
+ }
if (!data->max_frames)
return false;
@@ -932,33 +888,29 @@ static void mp3_data_finish(struct mp3_data *data)
}
/* this is primarily used for getting total time for tags */
-static int mp3_total_file_time(const char *file)
+static int
+mad_decoder_total_file_time(struct input_stream *is)
{
- struct input_stream input_stream;
struct mp3_data data;
int ret;
- if (!input_stream_open(&input_stream, file))
- return -1;
- mp3_data_init(&data, NULL, &input_stream);
- if (!mp3_decode_first_frame(&data, NULL, NULL))
+ mp3_data_init(&data, NULL, is);
+ if (!mp3_decode_first_frame(&data, NULL))
ret = -1;
else
ret = data.total_time + 0.5;
mp3_data_finish(&data);
- input_stream_close(&input_stream);
return ret;
}
static bool
mp3_open(struct input_stream *is, struct mp3_data *data,
- struct decoder *decoder, struct tag **tag,
- struct replay_gain_info **replay_gain_info_r)
+ struct decoder *decoder, struct tag **tag)
{
mp3_data_init(data, decoder, is);
*tag = NULL;
- if (!mp3_decode_first_frame(data, tag, replay_gain_info_r)) {
+ if (!mp3_decode_first_frame(data, tag)) {
mp3_data_finish(data);
if (tag && *tag)
tag_free(*tag);
@@ -1017,8 +969,7 @@ mp3_update_timer_next_frame(struct mp3_data *data)
* Sends the synthesized current frame via decoder_data().
*/
static enum decoder_command
-mp3_send_pcm(struct mp3_data *data, unsigned i, unsigned pcm_length,
- struct replay_gain_info *replay_gain_info)
+mp3_send_pcm(struct mp3_data *data, unsigned i, unsigned pcm_length)
{
unsigned max_samples;
@@ -1043,9 +994,7 @@ mp3_send_pcm(struct mp3_data *data, unsigned i, unsigned pcm_length,
cmd = decoder_data(data->decoder, data->input_stream,
data->output_buffer,
sizeof(data->output_buffer[0]) * num_samples,
- data->elapsed_time,
- data->bit_rate / 1000,
- replay_gain_info);
+ data->bit_rate / 1000);
if (cmd != DECODE_COMMAND_NONE)
return cmd;
}
@@ -1057,8 +1006,7 @@ mp3_send_pcm(struct mp3_data *data, unsigned i, unsigned pcm_length,
* Synthesize the current frame and send it via decoder_data().
*/
static enum decoder_command
-mp3_synth_and_send(struct mp3_data *data,
- struct replay_gain_info *replay_gain_info)
+mp3_synth_and_send(struct mp3_data *data)
{
unsigned i, pcm_length;
enum decoder_command cmd;
@@ -1099,7 +1047,7 @@ mp3_synth_and_send(struct mp3_data *data,
pcm_length -= data->drop_end_samples;
}
- cmd = mp3_send_pcm(data, i, pcm_length, replay_gain_info);
+ cmd = mp3_send_pcm(data, i, pcm_length);
if (cmd != DECODE_COMMAND_NONE)
return cmd;
@@ -1113,7 +1061,7 @@ mp3_synth_and_send(struct mp3_data *data,
}
static bool
-mp3_read(struct mp3_data *data, struct replay_gain_info **replay_gain_info_r)
+mp3_read(struct mp3_data *data)
{
struct decoder *decoder = data->decoder;
enum mp3_action ret;
@@ -1130,9 +1078,7 @@ mp3_read(struct mp3_data *data, struct replay_gain_info **replay_gain_info_r)
data->mute_frame = MUTEFRAME_NONE;
break;
case MUTEFRAME_NONE:
- cmd = mp3_synth_and_send(data,
- replay_gain_info_r != NULL
- ? *replay_gain_info_r : NULL);
+ cmd = mp3_synth_and_send(data);
if (cmd == DECODE_COMMAND_SEEK) {
unsigned long j;
@@ -1161,8 +1107,7 @@ mp3_read(struct mp3_data *data, struct replay_gain_info **replay_gain_info_r)
do {
struct tag *tag = NULL;
- ret = decode_next_frame_header(data, &tag,
- replay_gain_info_r);
+ ret = decode_next_frame_header(data, &tag);
if (tag != NULL) {
decoder_tag(decoder, data->input_stream, tag);
@@ -1189,29 +1134,34 @@ mp3_read(struct mp3_data *data, struct replay_gain_info **replay_gain_info_r)
return ret != DECODE_BREAK;
}
-static void mp3_audio_format(struct mp3_data *data, struct audio_format *af)
-{
- af->bits = 24;
- af->sample_rate = (data->frame).header.samplerate;
- af->channels = MAD_NCHANNELS(&(data->frame).header);
-}
-
static void
mp3_decode(struct decoder *decoder, struct input_stream *input_stream)
{
struct mp3_data data;
+ GError *error = NULL;
struct tag *tag = NULL;
- struct replay_gain_info *replay_gain_info = NULL;
struct audio_format audio_format;
- if (!mp3_open(input_stream, &data, decoder, &tag, &replay_gain_info)) {
+ if (!mp3_open(input_stream, &data, decoder, &tag)) {
if (decoder_get_command(decoder) == DECODE_COMMAND_NONE)
g_warning
("Input does not appear to be a mp3 bit stream.\n");
return;
}
- mp3_audio_format(&data, &audio_format);
+ if (!audio_format_init_checked(&audio_format,
+ data.frame.header.samplerate,
+ SAMPLE_FORMAT_S24_P32,
+ MAD_NCHANNELS(&data.frame.header),
+ &error)) {
+ g_warning("%s", error->message);
+ g_error_free(error);
+
+ if (tag != NULL)
+ tag_free(tag);
+ mp3_data_finish(&data);
+ return;
+ }
decoder_initialized(decoder, &audio_format,
data.input_stream->seekable, data.total_time);
@@ -1221,24 +1171,20 @@ mp3_decode(struct decoder *decoder, struct input_stream *input_stream)
tag_free(tag);
}
- while (mp3_read(&data, &replay_gain_info)) ;
-
- if (replay_gain_info)
- replay_gain_info_free(replay_gain_info);
+ while (mp3_read(&data)) ;
mp3_data_finish(&data);
}
-static struct tag *mp3_tag_dup(const char *file)
+static struct tag *
+mad_decoder_stream_tag(struct input_stream *is)
{
struct tag *tag;
int total_time;
- total_time = mp3_total_file_time(file);
- if (total_time < 0) {
- g_debug("Failed to get total song time from: %s", file);
+ total_time = mad_decoder_total_file_time(is);
+ if (total_time < 0)
return NULL;
- }
tag = tag_new();
tag->time = total_time;
@@ -1252,7 +1198,7 @@ const struct decoder_plugin mad_decoder_plugin = {
.name = "mad",
.init = mp3_plugin_init,
.stream_decode = mp3_decode,
- .tag_dup = mp3_tag_dup,
+ .stream_tag = mad_decoder_stream_tag,
.suffixes = mp3_suffixes,
.mime_types = mp3_mime_types
};
diff --git a/src/decoder/mikmod_plugin.c b/src/decoder/mikmod_decoder_plugin.c
index f60dcbc61..91478e86f 100644
--- a/src/decoder/mikmod_plugin.c
+++ b/src/decoder/mikmod_decoder_plugin.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -17,10 +17,13 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
-#include "../decoder_api.h"
+#include "config.h"
+#include "decoder_api.h"
+#include "mpd_error.h"
#include <glib.h>
#include <mikmod.h>
+#include <assert.h>
#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "mikmod"
@@ -29,30 +32,34 @@
#define MIKMOD_FRAME_SIZE 4096
-static BOOL mod_mpd_Init(void)
+static BOOL
+mikmod_mpd_init(void)
{
return VC_Init();
}
-static void mod_mpd_Exit(void)
+static void
+mikmod_mpd_exit(void)
{
VC_Exit();
}
-static void mod_mpd_Update(void)
+static void
+mikmod_mpd_update(void)
{
}
-static BOOL mod_mpd_IsThere(void)
+static BOOL
+mikmod_mpd_is_present(void)
{
- return 1;
+ return true;
}
-static char drv_name[] = "MPD";
-static char drv_version[] = "MPD Output Driver v0.1";
+static char drv_name[] = PACKAGE_NAME;
+static char drv_version[] = VERSION;
#if (LIBMIKMOD_VERSION > 0x030106)
-static char drv_alias[] = "mpd";
+static char drv_alias[] = PACKAGE;
#endif
static MDRIVER drv_mpd = {
@@ -68,18 +75,18 @@ static MDRIVER drv_mpd = {
#endif
NULL, /* CommandLine */
#endif
- mod_mpd_IsThere,
+ mikmod_mpd_is_present,
VC_SampleLoad,
VC_SampleUnload,
VC_SampleSpace,
VC_SampleLength,
- mod_mpd_Init,
- mod_mpd_Exit,
+ mikmod_mpd_init,
+ mikmod_mpd_exit,
NULL,
VC_SetNumVoices,
VC_PlayStart,
VC_PlayStop,
- mod_mpd_Update,
+ mikmod_mpd_update,
NULL,
VC_VoiceSetVolume,
VC_VoiceGetVolume,
@@ -94,11 +101,19 @@ static MDRIVER drv_mpd = {
VC_VoiceRealVolume
};
+static unsigned mikmod_sample_rate;
+
static bool
-mod_initMikMod(G_GNUC_UNUSED const struct config_param *param)
+mikmod_decoder_init(const struct config_param *param)
{
static char params[] = "";
+ mikmod_sample_rate = config_get_block_unsigned(param, "sample_rate",
+ 44100);
+ if (!audio_valid_sample_rate(mikmod_sample_rate))
+ MPD_ERROR("Invalid sample rate in line %d: %u",
+ param->line, mikmod_sample_rate);
+
md_device = 0;
md_reverb = 0;
@@ -106,7 +121,7 @@ mod_initMikMod(G_GNUC_UNUSED const struct config_param *param)
MikMod_RegisterAllLoaders();
md_pansep = 64;
- md_mixfreq = 44100;
+ md_mixfreq = mikmod_sample_rate;
md_mode = (DMODE_SOFT_MUSIC | DMODE_INTERP | DMODE_STEREO |
DMODE_16BITS);
@@ -119,117 +134,80 @@ mod_initMikMod(G_GNUC_UNUSED const struct config_param *param)
return true;
}
-static void mod_finishMikMod(void)
+static void
+mikmod_decoder_finish(void)
{
MikMod_Exit();
}
-typedef struct _mod_Data {
- MODULE *moduleHandle;
- SBYTE audio_buffer[MIKMOD_FRAME_SIZE];
-} mod_Data;
-
-static mod_Data *mod_open(const char *path)
-{
- char *path2;
- MODULE *moduleHandle;
- mod_Data *data;
-
- path2 = g_strdup(path);
- moduleHandle = Player_Load(path2, 128, 0);
- g_free(path2);
-
- if (moduleHandle == NULL)
- return NULL;
-
- /* Prevent module from looping forever */
- moduleHandle->loop = 0;
-
- data = g_new(mod_Data, 1);
- data->moduleHandle = moduleHandle;
-
- Player_Start(data->moduleHandle);
-
- return data;
-}
-
-static void mod_close(mod_Data * data)
-{
- Player_Stop();
- Player_Free(data->moduleHandle);
- g_free(data);
-}
-
static void
-mod_decode(struct decoder *decoder, const char *path)
+mikmod_decoder_file_decode(struct decoder *decoder, const char *path_fs)
{
- mod_Data *data;
+ char *path2;
+ MODULE *handle;
struct audio_format audio_format;
- float total_time = 0.0;
int ret;
- float secPerByte;
+ SBYTE buffer[MIKMOD_FRAME_SIZE];
enum decoder_command cmd = DECODE_COMMAND_NONE;
- if (!(data = mod_open(path))) {
- g_warning("failed to open mod: %s\n", path);
+ path2 = g_strdup(path_fs);
+ handle = Player_Load(path2, 128, 0);
+ g_free(path2);
+
+ if (handle == NULL) {
+ g_warning("failed to open mod: %s", path_fs);
return;
}
- audio_format.bits = 16;
- audio_format.sample_rate = 44100;
- audio_format.channels = 2;
+ /* Prevent module from looping forever */
+ handle->loop = 0;
- secPerByte =
- 1.0 / ((audio_format.bits * audio_format.channels / 8.0) *
- (float)audio_format.sample_rate);
+ audio_format_init(&audio_format, mikmod_sample_rate, SAMPLE_FORMAT_S16, 2);
+ assert(audio_format_valid(&audio_format));
decoder_initialized(decoder, &audio_format, false, 0);
+ Player_Start(handle);
while (cmd == DECODE_COMMAND_NONE && Player_Active()) {
- ret = VC_WriteBytes(data->audio_buffer, MIKMOD_FRAME_SIZE);
- total_time += ret * secPerByte;
- cmd = decoder_data(decoder, NULL,
- data->audio_buffer, ret,
- total_time, 0, NULL);
+ ret = VC_WriteBytes(buffer, sizeof(buffer));
+ cmd = decoder_data(decoder, NULL, buffer, ret, 0);
}
- mod_close(data);
+ Player_Stop();
+ Player_Free(handle);
}
-static struct tag *modTagDup(const char *file)
+static struct tag *
+mikmod_decoder_tag_dup(const char *path_fs)
{
- char *path2;
- struct tag *ret = NULL;
- MODULE *moduleHandle;
- char *title;
+ char *path2 = g_strdup(path_fs);
+ MODULE *handle = Player_Load(path2, 128, 0);
- path2 = g_strdup(file);
- moduleHandle = Player_Load(path2, 128, 0);
- g_free(path2);
-
- if (moduleHandle == NULL) {
- g_debug("Failed to open file: %s", file);
+ if (handle == NULL) {
+ g_free(path2);
+ g_debug("Failed to open file: %s", path_fs);
return NULL;
}
- Player_Free(moduleHandle);
- ret = tag_new();
+ Player_Free(handle);
- ret->time = 0;
+ struct tag *tag = tag_new();
- path2 = g_strdup(file);
- title = Player_LoadTitle(path2);
+ tag->time = 0;
+
+ char *title = Player_LoadTitle(path2);
g_free(path2);
- if (title) {
- tag_add_item(ret, TAG_ITEM_TITLE, title);
+
+ if (title != NULL) {
+ tag_add_item(tag, TAG_TITLE, title);
free(title);
}
- return ret;
+ return tag;
}
-static const char *const modSuffixes[] = {
+static const char *const mikmod_decoder_suffixes[] = {
"amf",
"dsm",
"far",
@@ -250,9 +228,9 @@ static const char *const modSuffixes[] = {
const struct decoder_plugin mikmod_decoder_plugin = {
.name = "mikmod",
- .init = mod_initMikMod,
- .finish = mod_finishMikMod,
- .file_decode = mod_decode,
- .tag_dup = modTagDup,
- .suffixes = modSuffixes,
+ .init = mikmod_decoder_init,
+ .finish = mikmod_decoder_finish,
+ .file_decode = mikmod_decoder_file_decode,
+ .tag_dup = mikmod_decoder_tag_dup,
+ .suffixes = mikmod_decoder_suffixes,
};
diff --git a/src/decoder/modplug_plugin.c b/src/decoder/modplug_decoder_plugin.c
index f636f2fa6..037c2fd74 100644
--- a/src/decoder/modplug_plugin.c
+++ b/src/decoder/modplug_decoder_plugin.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -17,10 +17,12 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
-#include "../decoder_api.h"
+#include "config.h"
+#include "decoder_api.h"
#include <glib.h>
#include <modplug.h>
+#include <assert.h>
#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "modplug"
@@ -92,10 +94,8 @@ mod_decode(struct decoder *decoder, struct input_stream *is)
ModPlug_Settings settings;
GByteArray *bdatas;
struct audio_format audio_format;
- float total_time = 0.0;
- int ret, current;
+ int ret;
char audio_buffer[MODPLUG_FRAME_SIZE];
- float sec_perbyte;
enum decoder_command cmd = DECODE_COMMAND_NONE;
bdatas = mod_loadfile(decoder, is);
@@ -121,37 +121,26 @@ mod_decode(struct decoder *decoder, struct input_stream *is)
return;
}
- audio_format.bits = 16;
- audio_format.sample_rate = 44100;
- audio_format.channels = 2;
-
- sec_perbyte =
- 1.0 / ((audio_format.bits * audio_format.channels / 8.0) *
- (float)audio_format.sample_rate);
-
- total_time = ModPlug_GetLength(f) / 1000;
+ audio_format_init(&audio_format, 44100, SAMPLE_FORMAT_S16, 2);
+ assert(audio_format_valid(&audio_format));
decoder_initialized(decoder, &audio_format,
- is->seekable, total_time);
-
- total_time = 0;
+ is->seekable, ModPlug_GetLength(f) / 1000.0);
do {
ret = ModPlug_Read(f, audio_buffer, MODPLUG_FRAME_SIZE);
-
- if (ret == 0) {
+ if (ret <= 0)
break;
- }
- total_time += ret * sec_perbyte;
cmd = decoder_data(decoder, NULL,
audio_buffer, ret,
- total_time, 0, NULL);
+ 0);
if (cmd == DECODE_COMMAND_SEEK) {
- total_time = decoder_seek_where(decoder);
- current = total_time * 1000;
- ModPlug_Seek(f, current);
+ float where = decoder_seek_where(decoder);
+
+ ModPlug_Seek(f, (int)(where * 1000.0));
+
decoder_command_finished(decoder);
}
@@ -160,43 +149,33 @@ mod_decode(struct decoder *decoder, struct input_stream *is)
ModPlug_Unload(f);
}
-static struct tag *mod_tagdup(const char *file)
+static struct tag *
+modplug_stream_tag(struct input_stream *is)
{
ModPlugFile *f;
struct tag *ret = NULL;
GByteArray *bdatas;
char *title;
- struct input_stream is;
- if (!input_stream_open(&is, file)) {
- g_warning("cant open file %s\n", file);
+ bdatas = mod_loadfile(NULL, is);
+ if (!bdatas)
return NULL;
- }
-
- bdatas = mod_loadfile(NULL, &is);
- if (!bdatas) {
- g_warning("cant load file %s\n", file);
- return NULL;
- }
f = ModPlug_Load(bdatas->data, bdatas->len);
g_byte_array_free(bdatas, TRUE);
- if (!f) {
- g_warning("could not decode file %s\n", file);
+ if (f == NULL)
return NULL;
- }
+
ret = tag_new();
- ret->time = 0;
+ ret->time = ModPlug_GetLength(f) / 1000;
title = g_strdup(ModPlug_GetName(f));
if (title)
- tag_add_item(ret, TAG_ITEM_TITLE, title);
+ tag_add_item(ret, TAG_TITLE, title);
g_free(title);
ModPlug_Unload(f);
- input_stream_close(&is);
-
return ret;
}
@@ -210,6 +189,6 @@ static const char *const mod_suffixes[] = {
const struct decoder_plugin modplug_decoder_plugin = {
.name = "modplug",
.stream_decode = mod_decode,
- .tag_dup = mod_tagdup,
+ .stream_tag = modplug_stream_tag,
.suffixes = mod_suffixes,
};
diff --git a/src/decoder/mp4ff_plugin.c b/src/decoder/mp4ff_decoder_plugin.c
index 4d4d47c6c..861b08194 100644
--- a/src/decoder/mp4ff_plugin.c
+++ b/src/decoder/mp4ff_decoder_plugin.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -17,8 +17,9 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
-#include "../decoder_api.h"
#include "config.h"
+#include "decoder_api.h"
+#include "audio_check.h"
#include "tag_table.h"
#include <glib.h>
@@ -35,7 +36,9 @@
/* all code here is either based on or copied from FAAD2's frontend code */
-struct mp4_context {
+struct mp4ff_input_stream {
+ mp4ff_callback_t callback;
+
struct decoder *decoder;
struct input_stream *input_stream;
};
@@ -89,20 +92,38 @@ mp4_get_aac_track(mp4ff_t * infile, faacDecHandle decoder,
static uint32_t
mp4_read(void *user_data, void *buffer, uint32_t length)
{
- struct mp4_context *ctx = user_data;
+ struct mp4ff_input_stream *mis = user_data;
- return decoder_read(ctx->decoder, ctx->input_stream, buffer, length);
+ return decoder_read(mis->decoder, mis->input_stream, buffer, length);
}
static uint32_t
mp4_seek(void *user_data, uint64_t position)
{
- struct mp4_context *ctx = user_data;
+ struct mp4ff_input_stream *mis = user_data;
- return input_stream_seek(ctx->input_stream, position, SEEK_SET)
+ return input_stream_seek(mis->input_stream, position, SEEK_SET, NULL)
? 0 : -1;
}
+static const mp4ff_callback_t mpd_mp4ff_callback = {
+ .read = mp4_read,
+ .seek = mp4_seek,
+};
+
+static mp4ff_t *
+mp4ff_input_stream_open(struct mp4ff_input_stream *mis,
+ struct decoder *decoder,
+ struct input_stream *input_stream)
+{
+ mis->callback = mpd_mp4ff_callback;
+ mis->callback.user_data = mis;
+ mis->decoder = decoder;
+ mis->input_stream = input_stream;
+
+ return mp4ff_open_read(&mis->callback);
+}
+
static faacDecHandle
mp4_faad_new(mp4ff_t *mp4fh, int *track_r, struct audio_format *audio_format)
{
@@ -111,6 +132,7 @@ mp4_faad_new(mp4ff_t *mp4fh, int *track_r, struct audio_format *audio_format)
int track;
uint32_t sample_rate;
unsigned char channels;
+ GError *error = NULL;
decoder = faacDecOpen();
@@ -131,37 +153,24 @@ mp4_faad_new(mp4ff_t *mp4fh, int *track_r, struct audio_format *audio_format)
return NULL;
}
- *track_r = track;
- *audio_format = (struct audio_format){
- .bits = 16,
- .channels = channels,
- .sample_rate = sample_rate,
- };
-
- if (!audio_format_valid(audio_format)) {
- g_warning("Invalid audio format: %u:%u:%u\n",
- audio_format->sample_rate,
- audio_format->bits,
- audio_format->channels);
+ if (!audio_format_init_checked(audio_format, sample_rate,
+ SAMPLE_FORMAT_S16, channels,
+ &error)) {
+ g_warning("%s", error->message);
+ g_error_free(error);
faacDecClose(decoder);
return NULL;
}
+ *track_r = track;
+
return decoder;
}
static void
mp4_decode(struct decoder *mpd_decoder, struct input_stream *input_stream)
{
- struct mp4_context ctx = {
- .decoder = mpd_decoder,
- .input_stream = input_stream,
- };
- mp4ff_callback_t callback = {
- .read = mp4_read,
- .seek = mp4_seek,
- .user_data = &ctx,
- };
+ struct mp4ff_input_stream mis;
mp4ff_t *mp4fh;
int32_t track;
float file_time, total_time;
@@ -187,7 +196,7 @@ mp4_decode(struct decoder *mpd_decoder, struct input_stream *input_stream)
double seek_where = 0;
enum decoder_command cmd = DECODE_COMMAND_NONE;
- mp4fh = mp4ff_open_read(&callback);
+ mp4fh = mp4ff_input_stream_open(&mis, mpd_decoder, input_stream);
if (!mp4fh) {
g_warning("Input does not appear to be a mp4 stream.\n");
return;
@@ -266,7 +275,7 @@ mp4_decode(struct decoder *mpd_decoder, struct input_stream *input_stream)
dur -= offset;
file_time += ((float)dur) / scale;
- if (seeking && file_time > seek_where)
+ if (seeking && file_time >= seek_where)
seek_position_found = true;
if (seeking && seek_position_found) {
@@ -332,7 +341,7 @@ mp4_decode(struct decoder *mpd_decoder, struct input_stream *input_stream)
cmd = decoder_data(mpd_decoder, input_stream,
sample_buffer, sample_buffer_length,
- file_time, bit_rate, NULL);
+ bit_rate);
}
g_free(seek_table);
@@ -341,9 +350,9 @@ mp4_decode(struct decoder *mpd_decoder, struct input_stream *input_stream)
}
static const char *const mp4ff_tag_names[TAG_NUM_OF_ITEM_TYPES] = {
- [TAG_ITEM_ALBUM_ARTIST] = "album artist",
- [TAG_ITEM_COMPOSER] = "writer",
- [TAG_ITEM_PERFORMER] = "band",
+ [TAG_ALBUM_ARTIST] = "album artist",
+ [TAG_COMPOSER] = "writer",
+ [TAG_PERFORMER] = "band",
};
static enum tag_type
@@ -353,57 +362,41 @@ mp4ff_tag_name_parse(const char *name)
if (type == TAG_NUM_OF_ITEM_TYPES)
type = tag_name_parse_i(name);
+ if (g_ascii_strcasecmp(name, "albumartist") == 0 ||
+ g_ascii_strcasecmp(name, "album_artist") == 0)
+ return TAG_ALBUM_ARTIST;
+
return type;
}
static struct tag *
-mp4_tag_dup(const char *file)
+mp4_stream_tag(struct input_stream *is)
{
- struct tag *ret = NULL;
- struct input_stream input_stream;
- struct mp4_context ctx = {
- .decoder = NULL,
- .input_stream = &input_stream,
- };
- mp4ff_callback_t callback = {
- .read = mp4_read,
- .seek = mp4_seek,
- .user_data = &ctx,
- };
- mp4ff_t *mp4fh;
+ struct mp4ff_input_stream mis;
int32_t track;
int32_t file_time;
int32_t scale;
int i;
- if (!input_stream_open(&input_stream, file)) {
- g_warning("Failed to open file: %s", file);
+ mp4ff_t *mp4fh = mp4ff_input_stream_open(&mis, NULL, is);
+ if (mp4fh == NULL)
return NULL;
- }
-
- mp4fh = mp4ff_open_read(&callback);
- if (!mp4fh) {
- input_stream_close(&input_stream);
- return NULL;
- }
track = mp4_get_aac_track(mp4fh, NULL, NULL, NULL);
if (track < 0) {
mp4ff_close(mp4fh);
- input_stream_close(&input_stream);
return NULL;
}
- ret = tag_new();
file_time = mp4ff_get_track_duration_use_offsets(mp4fh, track);
scale = mp4ff_time_scale(mp4fh, track);
if (scale < 0) {
mp4ff_close(mp4fh);
- input_stream_close(&input_stream);
- tag_free(ret);
return NULL;
}
- ret->time = ((float)file_time) / scale + 0.5;
+
+ struct tag *tag = tag_new();
+ tag->time = ((float)file_time) / scale + 0.5;
for (i = 0; i < mp4ff_meta_get_num_items(mp4fh); i++) {
char *item;
@@ -413,16 +406,15 @@ mp4_tag_dup(const char *file)
enum tag_type type = mp4ff_tag_name_parse(item);
if (type != TAG_NUM_OF_ITEM_TYPES)
- tag_add_item(ret, type, value);
+ tag_add_item(tag, type, value);
free(item);
free(value);
}
mp4ff_close(mp4fh);
- input_stream_close(&input_stream);
- return ret;
+ return tag;
}
static const char *const mp4_suffixes[] = {
@@ -435,9 +427,9 @@ static const char *const mp4_suffixes[] = {
static const char *const mp4_mime_types[] = { "audio/mp4", "audio/m4a", NULL };
const struct decoder_plugin mp4ff_decoder_plugin = {
- .name = "mp4",
+ .name = "mp4ff",
.stream_decode = mp4_decode,
- .tag_dup = mp4_tag_dup,
+ .stream_tag = mp4_stream_tag,
.suffixes = mp4_suffixes,
.mime_types = mp4_mime_types,
};
diff --git a/src/decoder/mpcdec_plugin.c b/src/decoder/mpcdec_decoder_plugin.c
index 72a516f22..4df8dd218 100644
--- a/src/decoder/mpcdec_plugin.c
+++ b/src/decoder/mpcdec_decoder_plugin.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -17,8 +17,9 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
-#include "../decoder_api.h"
#include "config.h"
+#include "decoder_api.h"
+#include "audio_check.h"
#ifdef MPC_IS_OLD_API
#include <mpcdec/mpcdec.h>
@@ -28,6 +29,7 @@
#endif
#include <glib.h>
+#include <assert.h>
#include <unistd.h>
#undef G_LOG_DOMAIN
@@ -59,7 +61,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);
+ return input_stream_seek(data->is, offset, SEEK_SET, NULL);
}
static mpc_int32_t
@@ -141,6 +143,7 @@ mpcdec_decode(struct decoder *mpd_decoder, struct input_stream *is)
#endif
mpc_reader reader;
mpc_streaminfo info;
+ GError *error = NULL;
struct audio_format audio_format;
struct mpc_decoder_data data;
@@ -150,11 +153,8 @@ mpcdec_decode(struct decoder *mpd_decoder, struct input_stream *is)
mpc_uint32_t ret;
int32_t chunk[G_N_ELEMENTS(sample_buffer)];
long bit_rate = 0;
- unsigned long sample_pos = 0;
mpc_uint32_t vbr_update_acc;
mpc_uint32_t vbr_update_bits;
- float total_time;
- struct replay_gain_info *replay_gain_info = NULL;
enum decoder_command cmd = DECODE_COMMAND_NONE;
data.is = is;
@@ -194,50 +194,47 @@ mpcdec_decode(struct decoder *mpd_decoder, struct input_stream *is)
mpc_demux_get_info(demux, &info);
#endif
- audio_format.bits = 24;
- audio_format.channels = info.channels;
- audio_format.sample_rate = info.sample_freq;
-
- if (!audio_format_valid(&audio_format)) {
+ if (!audio_format_init_checked(&audio_format, info.sample_freq,
+ SAMPLE_FORMAT_S24_P32,
+ info.channels, &error)) {
+ g_warning("%s", error->message);
+ g_error_free(error);
#ifndef MPC_IS_OLD_API
mpc_demux_exit(demux);
#endif
- g_warning("Invalid audio format: %u:%u:%u\n",
- audio_format.sample_rate,
- audio_format.bits,
- audio_format.channels);
return;
}
- replay_gain_info = replay_gain_info_new();
+ struct replay_gain_info replay_gain_info;
+ replay_gain_info_init(&replay_gain_info);
#ifdef MPC_IS_OLD_API
- replay_gain_info->tuples[REPLAY_GAIN_ALBUM].gain = info.gain_album * 0.01;
- replay_gain_info->tuples[REPLAY_GAIN_ALBUM].peak = info.peak_album / 32767.0;
- replay_gain_info->tuples[REPLAY_GAIN_TRACK].gain = info.gain_title * 0.01;
- replay_gain_info->tuples[REPLAY_GAIN_TRACK].peak = info.peak_title / 32767.0;
+ replay_gain_info.tuples[REPLAY_GAIN_ALBUM].gain = info.gain_album * 0.01;
+ replay_gain_info.tuples[REPLAY_GAIN_ALBUM].peak = info.peak_album / 32767.0;
+ replay_gain_info.tuples[REPLAY_GAIN_TRACK].gain = info.gain_title * 0.01;
+ replay_gain_info.tuples[REPLAY_GAIN_TRACK].peak = info.peak_title / 32767.0;
#else
- replay_gain_info->tuples[REPLAY_GAIN_ALBUM].gain = MPC_OLD_GAIN_REF - (info.gain_album / 256.);
- replay_gain_info->tuples[REPLAY_GAIN_ALBUM].peak = pow(10, info.peak_album / 256. / 20) / 32767;
- replay_gain_info->tuples[REPLAY_GAIN_TRACK].gain = MPC_OLD_GAIN_REF - (info.gain_title / 256.);
- replay_gain_info->tuples[REPLAY_GAIN_TRACK].peak = pow(10, info.peak_title / 256. / 20) / 32767;
+ replay_gain_info.tuples[REPLAY_GAIN_ALBUM].gain = MPC_OLD_GAIN_REF - (info.gain_album / 256.);
+ replay_gain_info.tuples[REPLAY_GAIN_ALBUM].peak = pow(10, info.peak_album / 256. / 20) / 32767;
+ replay_gain_info.tuples[REPLAY_GAIN_TRACK].gain = MPC_OLD_GAIN_REF - (info.gain_title / 256.);
+ replay_gain_info.tuples[REPLAY_GAIN_TRACK].peak = pow(10, info.peak_title / 256. / 20) / 32767;
#endif
+ decoder_replay_gain(mpd_decoder, &replay_gain_info);
+
decoder_initialized(mpd_decoder, &audio_format,
is->seekable,
mpc_streaminfo_get_length(&info));
do {
if (cmd == DECODE_COMMAND_SEEK) {
- bool success;
-
- sample_pos = decoder_seek_where(mpd_decoder) *
+ mpc_int64_t where = decoder_seek_where(mpd_decoder) *
audio_format.sample_rate;
+ bool success;
#ifdef MPC_IS_OLD_API
- success = mpc_decoder_seek_sample(&decoder,
- sample_pos);
+ success = mpc_decoder_seek_sample(&decoder, where);
#else
- success = mpc_demux_seek_sample(demux, sample_pos)
+ success = mpc_demux_seek_sample(demux, where)
== MPC_STATUS_OK;
#endif
if (success)
@@ -268,33 +265,26 @@ mpcdec_decode(struct decoder *mpd_decoder, struct input_stream *is)
ret = frame.samples;
#endif
- sample_pos += ret;
-
ret *= info.channels;
mpc_to_mpd_buffer(chunk, sample_buffer, ret);
- total_time = ((float)sample_pos) / audio_format.sample_rate;
bit_rate = vbr_update_bits * audio_format.sample_rate
/ 1152 / 1000;
cmd = decoder_data(mpd_decoder, is,
chunk, ret * sizeof(chunk[0]),
- total_time,
- bit_rate, replay_gain_info);
+ bit_rate);
} while (cmd != DECODE_COMMAND_STOP);
- replay_gain_info_free(replay_gain_info);
-
#ifndef MPC_IS_OLD_API
mpc_demux_exit(demux);
#endif
}
static float
-mpcdec_get_file_duration(const char *file)
+mpcdec_get_file_duration(struct input_stream *is)
{
- struct input_stream is;
float total_time = -1;
mpc_reader reader;
@@ -304,10 +294,7 @@ mpcdec_get_file_duration(const char *file)
mpc_streaminfo info;
struct mpc_decoder_data data;
- if (!input_stream_open(&is, file))
- return -1;
-
- data.is = &is;
+ data.is = is;
data.decoder = NULL;
reader.read = mpc_read_cb;
@@ -320,16 +307,12 @@ mpcdec_get_file_duration(const char *file)
#ifdef MPC_IS_OLD_API
mpc_streaminfo_init(&info);
- if (mpc_streaminfo_read(&info, &reader) != ERROR_CODE_OK) {
- input_stream_close(&is);
+ if (mpc_streaminfo_read(&info, &reader) != ERROR_CODE_OK)
return -1;
- }
#else
demux = mpc_demux_init(&reader);
- if (demux == NULL) {
- input_stream_close(&is);
+ if (demux == NULL)
return -1;
- }
mpc_demux_get_info(demux, &info);
mpc_demux_exit(demux);
@@ -337,21 +320,17 @@ mpcdec_get_file_duration(const char *file)
total_time = mpc_streaminfo_get_length(&info);
- input_stream_close(&is);
-
return total_time;
}
static struct tag *
-mpcdec_tag_dup(const char *file)
+mpcdec_stream_tag(struct input_stream *is)
{
- float total_time = mpcdec_get_file_duration(file);
+ float total_time = mpcdec_get_file_duration(is);
struct tag *tag;
- if (total_time < 0) {
- g_debug("Failed to get duration of file: %s", file);
+ if (total_time < 0)
return NULL;
- }
tag = tag_new();
tag->time = total_time;
@@ -363,6 +342,6 @@ static const char *const mpcdec_suffixes[] = { "mpc", NULL };
const struct decoder_plugin mpcdec_decoder_plugin = {
.name = "mpcdec",
.stream_decode = mpcdec_decode,
- .tag_dup = mpcdec_tag_dup,
+ .stream_tag = mpcdec_stream_tag,
.suffixes = mpcdec_suffixes,
};
diff --git a/src/decoder/mpg123_decoder_plugin.c b/src/decoder/mpg123_decoder_plugin.c
new file mode 100644
index 000000000..7b48ebfaf
--- /dev/null
+++ b/src/decoder/mpg123_decoder_plugin.c
@@ -0,0 +1,209 @@
+/*
+ * 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" /* must be first for large file support */
+#include "decoder_api.h"
+#include "audio_check.h"
+
+#include <glib.h>
+
+#include <mpg123.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "mpg123"
+
+static bool
+mpd_mpg123_init(G_GNUC_UNUSED const struct config_param *param)
+{
+ mpg123_init();
+
+ return true;
+}
+
+static void
+mpd_mpg123_finish(void)
+{
+ mpg123_exit();
+}
+
+/**
+ * Opens a file with an existing #mpg123_handle.
+ *
+ * @param handle a handle which was created before; on error, this
+ * function will not free it
+ * @param audio_format this parameter is filled after successful
+ * return
+ * @return true on success
+ */
+static bool
+mpd_mpg123_open(mpg123_handle *handle, const char *path_fs,
+ struct audio_format *audio_format)
+{
+ GError *gerror = NULL;
+ char *path_dup;
+ int error;
+ int channels, encoding;
+ long rate;
+
+ /* mpg123_open() wants a writable string :-( */
+ path_dup = g_strdup(path_fs);
+
+ error = mpg123_open(handle, path_dup);
+ g_free(path_dup);
+ if (error != MPG123_OK) {
+ g_warning("libmpg123 failed to open %s: %s",
+ path_fs, mpg123_plain_strerror(error));
+ return false;
+ }
+
+ /* obtain the audio format */
+
+ error = mpg123_getformat(handle, &rate, &channels, &encoding);
+ if (error != MPG123_OK) {
+ g_warning("mpg123_getformat() failed: %s",
+ mpg123_plain_strerror(error));
+ return false;
+ }
+
+ if (encoding != MPG123_ENC_SIGNED_16) {
+ /* other formats not yet implemented */
+ g_warning("expected MPG123_ENC_SIGNED_16, got %d", encoding);
+ return false;
+ }
+
+ if (!audio_format_init_checked(audio_format, rate, SAMPLE_FORMAT_S16,
+ channels, &gerror)) {
+ g_warning("%s", gerror->message);
+ g_error_free(gerror);
+ return false;
+ }
+
+ return true;
+}
+
+static void
+mpd_mpg123_file_decode(struct decoder *decoder, const char *path_fs)
+{
+ struct audio_format audio_format;
+ mpg123_handle *handle;
+ int error;
+ off_t num_samples;
+ enum decoder_command cmd;
+
+ /* open the file */
+
+ handle = mpg123_new(NULL, &error);
+ if (handle == NULL) {
+ g_warning("mpg123_new() failed: %s",
+ mpg123_plain_strerror(error));
+ return;
+ }
+
+ if (!mpd_mpg123_open(handle, path_fs, &audio_format)) {
+ mpg123_delete(handle);
+ return;
+ }
+
+ num_samples = mpg123_length(handle);
+
+ /* tell MPD core we're ready */
+
+ decoder_initialized(decoder, &audio_format, false,
+ (float)num_samples /
+ (float)audio_format.sample_rate);
+
+ /* the decoder main loop */
+
+ do {
+ unsigned char buffer[8192];
+ size_t nbytes;
+
+ /* decode */
+
+ error = mpg123_read(handle, buffer, sizeof(buffer), &nbytes);
+ if (error != MPG123_OK) {
+ if (error != MPG123_DONE)
+ g_warning("mpg123_read() failed: %s",
+ mpg123_plain_strerror(error));
+ break;
+ }
+
+ /* send to MPD */
+
+ cmd = decoder_data(decoder, NULL, buffer, nbytes, 0);
+
+ /* seeking not yet implemented */
+ } while (cmd == DECODE_COMMAND_NONE);
+
+ /* cleanup */
+
+ mpg123_delete(handle);
+}
+
+static struct tag *
+mpd_mpg123_tag_dup(const char *path_fs)
+{
+ 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;
+ }
+
+ if (!mpd_mpg123_open(handle, path_fs, &audio_format)) {
+ mpg123_delete(handle);
+ return NULL;
+ }
+
+ num_samples = mpg123_length(handle);
+ if (num_samples <= 0) {
+ mpg123_delete(handle);
+ return NULL;
+ }
+
+ tag = tag_new();
+
+ tag->time = num_samples / audio_format.sample_rate;
+
+ /* ID3 tag support not yet implemented */
+
+ mpg123_delete(handle);
+ return tag;
+}
+
+static const char *const mpg123_suffixes[] = {
+ "mp3",
+ NULL
+};
+
+const struct decoder_plugin mpg123_decoder_plugin = {
+ .name = "mpg123",
+ .init = mpd_mpg123_init,
+ .finish = mpd_mpg123_finish,
+ .file_decode = mpd_mpg123_file_decode,
+ /* streaming not yet implemented */
+ .tag_dup = mpd_mpg123_tag_dup,
+ .suffixes = mpg123_suffixes,
+};
diff --git a/src/decoder/oggflac_plugin.c b/src/decoder/oggflac_decoder_plugin.c
index bdd589ccb..7e5f48318 100644
--- a/src/decoder/oggflac_plugin.c
+++ b/src/decoder/oggflac_decoder_plugin.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -21,19 +21,18 @@
* 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(struct flac_data *data,
- OggFLAC__SeekableStreamDecoder * decoder)
+static void oggflac_cleanup(OggFLAC__SeekableStreamDecoder * decoder)
{
- if (data->replay_gain_info)
- replay_gain_info_free(data->replay_gain_info);
if (decoder)
OggFLAC__seekable_stream_decoder_delete(decoder);
}
@@ -67,7 +66,7 @@ static OggFLAC__SeekableStreamDecoderSeekStatus of_seek_cb(G_GNUC_UNUSED const
{
struct flac_data *data = (struct flac_data *) fdata;
- if (!input_stream_seek(data->input_stream, offset, SEEK_SET))
+ 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;
@@ -156,13 +155,8 @@ oggflac_write_cb(G_GNUC_UNUSED const OggFLAC__SeekableStreamDecoder *decoder,
void *vdata)
{
struct flac_data *data = (struct flac_data *) vdata;
- FLAC__uint32 samples = frame->header.blocksize;
- float time_change;
- time_change = ((float)samples) / frame->header.sample_rate;
- data->time += time_change;
-
- return flac_common_write(data, frame, buf);
+ return flac_common_write(data, frame, buf, 0);
}
/* used by TagDup */
@@ -173,17 +167,7 @@ static void of_metadata_dup_cb(G_GNUC_UNUSED const OggFLAC__SeekableStreamDecode
assert(data->tag != NULL);
- switch (block->type) {
- case FLAC__METADATA_TYPE_STREAMINFO:
- data->tag->time = ((float)block->data.stream_info.
- total_samples) /
- block->data.stream_info.sample_rate + 0.5;
- return;
- case FLAC__METADATA_TYPE_VORBIS_COMMENT:
- flac_vorbis_comments_to_tag(data->tag, NULL, block);
- default:
- break;
- }
+ flac_tag_apply_metadata(data->tag, NULL, block);
}
/* used by decode */
@@ -259,24 +243,20 @@ fail:
/* public functions: */
static struct tag *
-oggflac_tag_dup(const char *file)
+oggflac_stream_tag(struct input_stream *is)
{
- struct input_stream input_stream;
OggFLAC__SeekableStreamDecoder *decoder;
struct flac_data data;
+ struct tag *tag;
- if (!input_stream_open(&input_stream, file))
- return NULL;
- if (ogg_stream_type_detect(&input_stream) != FLAC) {
- input_stream_close(&input_stream);
+ if (ogg_stream_type_detect(is) != FLAC)
return NULL;
- }
/* rewind the stream, because ogg_stream_type_detect() has
moved it */
- input_stream_seek(&input_stream, 0, SEEK_SET);
+ input_stream_seek(is, 0, SEEK_SET, NULL);
- flac_data_init(&data, NULL, &input_stream);
+ flac_data_init(&data, NULL, is);
data.tag = tag_new();
@@ -284,15 +264,17 @@ oggflac_tag_dup(const char *file)
* data.tag will be set or unset, that's all we care about */
decoder = full_decoder_init_and_read_metadata(&data, 1);
- oggflac_cleanup(&data, decoder);
- input_stream_close(&input_stream);
+ oggflac_cleanup(decoder);
- if (!tag_is_defined(data.tag)) {
- tag_free(data.tag);
+ if (tag_is_defined(data.tag)) {
+ tag = data.tag;
data.tag = NULL;
- }
+ } else
+ tag = NULL;
- return data.tag;
+ flac_data_deinit(&data);
+
+ return tag;
}
static void
@@ -300,13 +282,14 @@ 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);
+ input_stream_seek(input_stream, 0, SEEK_SET, NULL);
flac_data_init(&data, mpd_decoder, input_stream);
@@ -314,16 +297,13 @@ oggflac_decode(struct decoder * mpd_decoder, struct input_stream *input_stream)
goto fail;
}
- if (!audio_format_valid(&data.audio_format)) {
- g_warning("Invalid audio format: %u:%u:%u\n",
- data.audio_format.sample_rate,
- data.audio_format.bits,
- data.audio_format.channels);
+ if (!data.initialized)
goto fail;
- }
- decoder_initialized(mpd_decoder, &data.audio_format,
- input_stream->seekable, data.total_time);
+ 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);
@@ -333,11 +313,10 @@ oggflac_decode(struct decoder * mpd_decoder, struct input_stream *input_stream)
}
if (decoder_get_command(mpd_decoder) == DECODE_COMMAND_SEEK) {
FLAC__uint64 seek_sample = decoder_seek_where(mpd_decoder) *
- data.audio_format.sample_rate + 0.5;
+ data.audio_format.sample_rate;
if (OggFLAC__seekable_stream_decoder_seek_absolute
(decoder, seek_sample)) {
- data.time = ((float)seek_sample) /
- data.audio_format.sample_rate;
+ data.next_frame = seek_sample;
data.position = 0;
decoder_command_finished(mpd_decoder);
} else
@@ -352,7 +331,8 @@ oggflac_decode(struct decoder * mpd_decoder, struct input_stream *input_stream)
}
fail:
- oggflac_cleanup(&data, decoder);
+ oggflac_cleanup(decoder);
+ flac_data_deinit(&data);
}
static const char *const oggflac_suffixes[] = { "ogg", "oga", NULL };
@@ -368,7 +348,7 @@ static const char *const oggflac_mime_types[] = {
const struct decoder_plugin oggflac_decoder_plugin = {
.name = "oggflac",
.stream_decode = oggflac_decode,
- .tag_dup = oggflac_tag_dup,
+ .stream_tag = oggflac_stream_tag,
.suffixes = oggflac_suffixes,
.mime_types = oggflac_mime_types
};
diff --git a/src/decoder/sidplay_decoder_plugin.cxx b/src/decoder/sidplay_decoder_plugin.cxx
new file mode 100644
index 000000000..6fceeb30f
--- /dev/null
+++ b/src/decoder/sidplay_decoder_plugin.cxx
@@ -0,0 +1,429 @@
+/*
+ * 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"
+
+extern "C" {
+#include "../decoder_api.h"
+}
+
+#include <errno.h>
+#include <stdlib.h>
+#include <glib.h>
+
+#include <sidplay/sidplay2.h>
+#include <sidplay/builders/resid.h>
+#include <sidplay/utils/SidTuneMod.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "sidplay"
+
+#define SUBTUNE_PREFIX "tune_"
+
+static GPatternSpec *path_with_subtune;
+static const char *songlength_file;
+static GKeyFile *songlength_database;
+
+static bool all_files_are_containers;
+static unsigned default_songlength;
+
+static bool filter_setting;
+
+static GKeyFile *
+sidplay_load_songlength_db(const char *path)
+{
+ GError *error = NULL;
+ gchar *data;
+ gsize size;
+
+ if (!g_file_get_contents(path, &data, &size, &error)) {
+ g_warning("unable to read songlengths file %s: %s",
+ path, error->message);
+ g_error_free(error);
+ return NULL;
+ }
+
+ /* replace any ; comment characters with # */
+ for (gsize i = 0; i < size; i++)
+ if (data[i] == ';')
+ data[i] = '#';
+
+ GKeyFile *db = g_key_file_new();
+ bool success = g_key_file_load_from_data(db, data, size,
+ G_KEY_FILE_NONE, &error);
+ g_free(data);
+ if (!success) {
+ g_warning("unable to parse songlengths file %s: %s",
+ path, error->message);
+ g_error_free(error);
+ g_key_file_free(db);
+ return NULL;
+ }
+
+ g_key_file_set_list_separator(db, ' ');
+ return db;
+}
+
+static bool
+sidplay_init(const struct config_param *param)
+{
+ /* read the songlengths database file */
+ songlength_file=config_get_block_string(param,
+ "songlength_database", NULL);
+ if (songlength_file != NULL)
+ songlength_database = sidplay_load_songlength_db(songlength_file);
+
+ default_songlength=config_get_block_unsigned(param,
+ "default_songlength", 0);
+
+ all_files_are_containers=config_get_block_bool(param,
+ "all_files_are_containers", true);
+
+ path_with_subtune=g_pattern_spec_new(
+ "*/" SUBTUNE_PREFIX "???.sid");
+
+ filter_setting=config_get_block_bool(param, "filter", true);
+
+ return true;
+}
+
+void
+sidplay_finish()
+{
+ g_pattern_spec_free(path_with_subtune);
+
+ if(songlength_database)
+ g_key_file_free(songlength_database);
+}
+
+/**
+ * returns the file path stripped of any /tune_xxx.sid subtune
+ * suffix
+ */
+static char *
+get_container_name(const char *path_fs)
+{
+ char *path_container=g_strdup(path_fs);
+
+ if(!g_pattern_match(path_with_subtune,
+ strlen(path_container), path_container, NULL))
+ return path_container;
+
+ char *ptr=g_strrstr(path_container, "/" SUBTUNE_PREFIX);
+ if(ptr) *ptr='\0';
+
+ return path_container;
+}
+
+/**
+ * returns tune number from file.sid/tune_xxx.sid style path or 1 if
+ * no subtune is appended
+ */
+static int
+get_song_num(const char *path_fs)
+{
+ if(g_pattern_match(path_with_subtune,
+ strlen(path_fs), path_fs, NULL)) {
+ char *sub=g_strrstr(path_fs, "/" SUBTUNE_PREFIX);
+ if(!sub) return 1;
+
+ sub+=strlen("/" SUBTUNE_PREFIX);
+ int song_num=strtol(sub, NULL, 10);
+
+ if (errno == EINVAL)
+ return 1;
+ else
+ return song_num;
+ } else
+ return 1;
+}
+
+/* get the song length in seconds */
+static int
+get_song_length(const char *path_fs)
+{
+ if (songlength_database == NULL)
+ return -1;
+
+ gchar *sid_file=get_container_name(path_fs);
+ SidTuneMod tune(sid_file);
+ g_free(sid_file);
+ if(!tune) {
+ g_warning("failed to load file for calculating md5 sum");
+ return -1;
+ }
+ char md5sum[SIDTUNE_MD5_LENGTH+1];
+ tune.createMD5(md5sum);
+
+ int song_num=get_song_num(path_fs);
+
+ gsize num_items;
+ gchar **values=g_key_file_get_string_list(songlength_database,
+ "Database", md5sum, &num_items, NULL);
+ if(!values || song_num>num_items) {
+ g_strfreev(values);
+ return -1;
+ }
+
+ int minutes=strtol(values[song_num-1], NULL, 10);
+ if(errno==EINVAL) minutes=0;
+
+ int seconds;
+ char *ptr=strchr(values[song_num-1], ':');
+ if(ptr) {
+ seconds=strtol(ptr+1, NULL, 10);
+ if(errno==EINVAL) seconds=0;
+ } else
+ seconds=0;
+
+ g_strfreev(values);
+
+ return (minutes*60)+seconds;
+}
+
+static void
+sidplay_file_decode(struct decoder *decoder, const char *path_fs)
+{
+ int ret;
+ int channels;
+
+ /* load the tune */
+
+ char *path_container=get_container_name(path_fs);
+ SidTune tune(path_container, NULL, true);
+ g_free(path_container);
+ if (!tune) {
+ g_warning("failed to load file");
+ return;
+ }
+
+ int song_num=get_song_num(path_fs);
+ tune.selectSong(song_num);
+
+ int song_len=get_song_length(path_fs);
+ if(song_len==-1) song_len=default_songlength;
+
+ /* initialize the player */
+
+ sidplay2 player;
+ int iret = player.load(&tune);
+ if (iret != 0) {
+ g_warning("sidplay2.load() failed: %s", player.error());
+ return;
+ }
+
+ /* initialize the builder */
+
+ ReSIDBuilder builder("ReSID");
+ if (!builder) {
+ g_warning("failed to initialize ReSIDBuilder");
+ return;
+ }
+
+ builder.create(player.info().maxsids);
+ if (!builder) {
+ g_warning("ReSIDBuilder.create() failed");
+ return;
+ }
+
+ builder.filter(filter_setting);
+ if (!builder) {
+ g_warning("ReSIDBuilder.filter() failed");
+ return;
+ }
+
+ /* configure the player */
+
+ sid2_config_t config = player.config();
+
+ config.clockDefault = SID2_CLOCK_PAL;
+ config.clockForced = true;
+ config.clockSpeed = SID2_CLOCK_CORRECT;
+ config.frequency = 48000;
+ config.optimisation = SID2_DEFAULT_OPTIMISATION;
+
+ config.precision = 16;
+ config.sidDefault = SID2_MOS6581;
+ config.sidEmulation = &builder;
+ config.sidModel = SID2_MODEL_CORRECT;
+ config.sidSamples = true;
+#if G_BYTE_ORDER == G_LITTLE_ENDIAN
+ config.sampleFormat = SID2_LITTLE_SIGNED;
+#else
+ config.sampleFormat = SID2_BIG_SIGNED;
+#endif
+ if (tune.isStereo()) {
+ config.playback = sid2_stereo;
+ channels = 2;
+ } else {
+ config.playback = sid2_mono;
+ channels = 1;
+ }
+
+ iret = player.config(config);
+ if (iret != 0) {
+ g_warning("sidplay2.config() failed: %s", player.error());
+ return;
+ }
+
+ /* initialize the MPD decoder */
+
+ struct audio_format audio_format;
+ audio_format_init(&audio_format, 48000, SAMPLE_FORMAT_S16, channels);
+ assert(audio_format_valid(&audio_format));
+
+ decoder_initialized(decoder, &audio_format, true, (float)song_len);
+
+ /* .. and play */
+
+ const unsigned timebase = player.timebase();
+ song_len *= timebase;
+
+ enum decoder_command cmd;
+ do {
+ char buffer[4096];
+ size_t nbytes;
+
+ nbytes = player.play(buffer, sizeof(buffer));
+ if (nbytes == 0)
+ break;
+
+ decoder_timestamp(decoder, (double)player.time() / timebase);
+
+ cmd = decoder_data(decoder, NULL, buffer, nbytes, 0);
+
+ if(cmd==DECODE_COMMAND_SEEK) {
+ unsigned data_time = player.time();
+ unsigned target_time = (unsigned)
+ (decoder_seek_where(decoder) * timebase);
+
+ /* can't rewind so return to zero and seek forward */
+ if(target_time<data_time) {
+ player.stop();
+ data_time=0;
+ }
+
+ /* ignore data until target time is reached */
+ while(data_time<target_time) {
+ nbytes=player.play(buffer, sizeof(buffer));
+ if(nbytes==0)
+ break;
+ data_time = player.time();
+ }
+
+ decoder_command_finished(decoder);
+ }
+
+ if (song_len > 0 && player.time() >= song_len)
+ break;
+
+ } while (cmd != DECODE_COMMAND_STOP);
+}
+
+static struct tag *
+sidplay_tag_dup(const char *path_fs)
+{
+ int song_num=get_song_num(path_fs);
+ char *path_container=get_container_name(path_fs);
+
+ SidTune tune(path_container, NULL, true);
+ g_free(path_container);
+ if (!tune)
+ return NULL;
+
+ const SidTuneInfo &info = tune.getInfo();
+ struct tag *tag = tag_new();
+
+ /* title */
+ const char *title;
+ if (info.numberOfInfoStrings > 0 && info.infoString[0] != NULL)
+ title=info.infoString[0];
+ else
+ title="";
+
+ 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);
+ g_free(tag_title);
+ } else
+ tag_add_item(tag, TAG_TITLE, title);
+
+ /* artist */
+ if (info.numberOfInfoStrings > 1 && info.infoString[1] != NULL)
+ tag_add_item(tag, TAG_ARTIST, info.infoString[1]);
+
+ /* track */
+ char *track=g_strdup_printf("%d", song_num);
+ tag_add_item(tag, TAG_TRACK, track);
+ g_free(track);
+
+ /* time */
+ int song_len=get_song_length(path_fs);
+ if(song_len!=-1) tag->time=song_len;
+
+ return tag;
+}
+
+static char *
+sidplay_container_scan(const char *path_fs, const unsigned int tnum)
+{
+ SidTune tune(path_fs, NULL, true);
+ if (!tune)
+ return NULL;
+
+ const SidTuneInfo &info=tune.getInfo();
+
+ /* Don't treat sids containing a single tune
+ as containers */
+ if(!all_files_are_containers && info.songs<2)
+ return NULL;
+
+ /* Construct container/tune path names, eg.
+ Delta.sid/tune_001.sid */
+ if(tnum<=info.songs) {
+ char *subtune= g_strdup_printf(
+ SUBTUNE_PREFIX "%03u.sid", tnum);
+ return subtune;
+ } else
+ return NULL;
+}
+
+static const char *const sidplay_suffixes[] = {
+ "sid",
+ "mus",
+ "str",
+ "prg",
+ "P00",
+ NULL
+};
+
+extern const struct decoder_plugin sidplay_decoder_plugin;
+const struct decoder_plugin sidplay_decoder_plugin = {
+ "sidplay",
+ sidplay_init,
+ sidplay_finish,
+ NULL, /* stream_decode() */
+ sidplay_file_decode,
+ sidplay_tag_dup,
+ NULL, /* stream_tag() */
+ sidplay_container_scan,
+ sidplay_suffixes,
+ NULL, /* mime_types */
+};
diff --git a/src/decoder/sidplay_plugin.cxx b/src/decoder/sidplay_plugin.cxx
deleted file mode 100644
index c62e6b4b6..000000000
--- a/src/decoder/sidplay_plugin.cxx
+++ /dev/null
@@ -1,163 +0,0 @@
-/*
- * 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.
- */
-
-extern "C" {
-#include "../decoder_api.h"
-}
-
-#include <glib.h>
-
-#include <sidplay/sidplay2.h>
-#include <sidplay/builders/resid.h>
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "sidplay"
-
-static void
-sidplay_file_decode(struct decoder *decoder, const char *path_fs)
-{
- int ret;
-
- /* load the tune */
-
- SidTune tune(path_fs, NULL, true);
- if (!tune) {
- g_warning("failed to load file");
- return;
- }
-
- tune.selectSong(1);
-
- /* initialize the player */
-
- sidplay2 player;
- int iret = player.load(&tune);
- if (iret != 0) {
- g_warning("sidplay2.load() failed: %s", player.error());
- return;
- }
-
- /* initialize the builder */
-
- ReSIDBuilder builder("ReSID");
- if (!builder) {
- g_warning("failed to initialize ReSIDBuilder");
- return;
- }
-
- builder.create(player.info().maxsids);
- if (!builder) {
- g_warning("ReSIDBuilder.create() failed");
- return;
- }
-
- builder.filter(false);
- if (!builder) {
- g_warning("ReSIDBuilder.filter() failed");
- return;
- }
-
- /* configure the player */
-
- sid2_config_t config = player.config();
-
- config.clockDefault = SID2_CLOCK_PAL;
- config.clockForced = true;
- config.clockSpeed = SID2_CLOCK_CORRECT;
- config.frequency = 48000;
- config.optimisation = SID2_DEFAULT_OPTIMISATION;
- config.playback = sid2_stereo;
- config.precision = 16;
- config.sidDefault = SID2_MOS6581;
- config.sidEmulation = &builder;
- config.sidModel = SID2_MODEL_CORRECT;
- config.sidSamples = true;
-#if G_BYTE_ORDER == G_LITTLE_ENDIAN
- config.sampleFormat = SID2_LITTLE_SIGNED;
-#else
- config.sampleFormat = SID2_BIG_SIGNED;
-#endif
-
- iret = player.config(config);
- if (iret != 0) {
- g_warning("sidplay2.config() failed: %s", player.error());
- return;
- }
-
- /* initialize the MPD decoder */
-
- struct audio_format audio_format;
- audio_format.sample_rate = 48000;
- audio_format.bits = 16;
- audio_format.channels = 2;
-
- decoder_initialized(decoder, &audio_format, false, -1);
-
- /* .. and play */
-
- enum decoder_command cmd;
- do {
- char buffer[4096];
- size_t nbytes;
-
- nbytes = player.play(buffer, sizeof(buffer));
- if (nbytes == 0)
- break;
-
- cmd = decoder_data(decoder, NULL, buffer, nbytes,
- 0, 0, NULL);
- } while (cmd == DECODE_COMMAND_NONE);
-}
-
-static struct tag *
-sidplay_tag_dup(const char *path_fs)
-{
- SidTune tune(path_fs, NULL, true);
- if (!tune)
- return NULL;
-
- const SidTuneInfo &info = tune.getInfo();
- struct tag *tag = tag_new();
-
- if (info.numberOfInfoStrings > 0 && info.infoString[0] != NULL)
- tag_add_item(tag, TAG_ITEM_TITLE, info.infoString[0]);
-
- if (info.numberOfInfoStrings > 1 && info.infoString[1] != NULL)
- tag_add_item(tag, TAG_ITEM_ARTIST, info.infoString[1]);
-
- return tag;
-}
-
-static const char *const sidplay_suffixes[] = {
- "sid",
- NULL
-};
-
-extern const struct decoder_plugin sidplay_decoder_plugin;
-const struct decoder_plugin sidplay_decoder_plugin = {
- "sidplay",
- NULL, /* init() */
- NULL, /* finish() */
- NULL, /* stream_decode() */
- sidplay_file_decode,
- sidplay_tag_dup,
- NULL, /* container_scan */
- sidplay_suffixes,
- NULL, /* mime_types */
-};
diff --git a/src/decoder/sndfile_decoder_plugin.c b/src/decoder/sndfile_decoder_plugin.c
new file mode 100644
index 000000000..af68f117d
--- /dev/null
+++ b/src/decoder/sndfile_decoder_plugin.c
@@ -0,0 +1,251 @@
+/*
+ * 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 "decoder_api.h"
+#include "audio_check.h"
+
+#include <sndfile.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "sndfile"
+
+static sf_count_t
+sndfile_vio_get_filelen(void *user_data)
+{
+ const struct input_stream *is = user_data;
+
+ return is->size;
+}
+
+static sf_count_t
+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);
+ if (!success)
+ return -1;
+
+ return is->offset;
+}
+
+static sf_count_t
+sndfile_vio_read(void *ptr, sf_count_t count, void *user_data)
+{
+ struct input_stream *is = user_data;
+ GError *error = NULL;
+ size_t nbytes;
+
+ nbytes = input_stream_read(is, ptr, count, &error);
+ if (nbytes == 0 && error != NULL) {
+ g_warning("%s", error->message);
+ g_error_free(error);
+ return -1;
+ }
+
+ return nbytes;
+}
+
+static sf_count_t
+sndfile_vio_write(G_GNUC_UNUSED const void *ptr,
+ G_GNUC_UNUSED sf_count_t count,
+ G_GNUC_UNUSED void *user_data)
+{
+ /* no writing! */
+ return -1;
+}
+
+static sf_count_t
+sndfile_vio_tell(void *user_data)
+{
+ const struct input_stream *is = user_data;
+
+ return is->offset;
+}
+
+/**
+ * This SF_VIRTUAL_IO implementation wraps MPD's #input_stream to a
+ * libsndfile stream.
+ */
+static SF_VIRTUAL_IO vio = {
+ .get_filelen = sndfile_vio_get_filelen,
+ .seek = sndfile_vio_seek,
+ .read = sndfile_vio_read,
+ .write = sndfile_vio_write,
+ .tell = sndfile_vio_tell,
+};
+
+/**
+ * Converts a frame number to a timestamp (in seconds).
+ */
+static float
+frame_to_time(sf_count_t frame, const struct audio_format *audio_format)
+{
+ return (float)frame / (float)audio_format->sample_rate;
+}
+
+/**
+ * Converts a timestamp (in seconds) to a frame number.
+ */
+static sf_count_t
+time_to_frame(float t, const struct audio_format *audio_format)
+{
+ return (sf_count_t)(t * audio_format->sample_rate);
+}
+
+static void
+sndfile_stream_decode(struct decoder *decoder, struct input_stream *is)
+{
+ GError *error = NULL;
+ SNDFILE *sf;
+ SF_INFO info;
+ struct audio_format audio_format;
+ size_t frame_size;
+ sf_count_t read_frames, num_frames;
+ int buffer[4096];
+ enum decoder_command cmd;
+
+ info.format = 0;
+
+ sf = sf_open_virtual(&vio, SFM_READ, &info, is);
+ if (sf == NULL) {
+ g_warning("sf_open_virtual() failed");
+ return;
+ }
+
+ /* for now, always read 32 bit samples. Later, we could lower
+ MPD's CPU usage by reading 16 bit samples with
+ sf_readf_short() on low-quality source files. */
+ if (!audio_format_init_checked(&audio_format, info.samplerate,
+ SAMPLE_FORMAT_S32,
+ info.channels, &error)) {
+ g_warning("%s", error->message);
+ g_error_free(error);
+ return;
+ }
+
+ decoder_initialized(decoder, &audio_format, info.seekable,
+ frame_to_time(info.frames, &audio_format));
+
+ frame_size = audio_format_frame_size(&audio_format);
+ read_frames = sizeof(buffer) / frame_size;
+
+ do {
+ num_frames = sf_readf_int(sf, buffer, read_frames);
+ if (num_frames <= 0)
+ break;
+
+ cmd = decoder_data(decoder, is,
+ buffer, num_frames * frame_size,
+ 0);
+ if (cmd == DECODE_COMMAND_SEEK) {
+ sf_count_t c =
+ time_to_frame(decoder_seek_where(decoder),
+ &audio_format);
+ c = sf_seek(sf, c, SEEK_SET);
+ if (c < 0)
+ decoder_seek_error(decoder);
+ else
+ decoder_command_finished(decoder);
+ cmd = DECODE_COMMAND_NONE;
+ }
+ } while (cmd == DECODE_COMMAND_NONE);
+
+ sf_close(sf);
+}
+
+static struct tag *
+sndfile_tag_dup(const char *path_fs)
+{
+ 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;
+
+ if (!audio_valid_sample_rate(info.samplerate)) {
+ sf_close(sf);
+ g_warning("Invalid sample rate in %s\n", path_fs);
+ return NULL;
+ }
+
+ tag = tag_new();
+ tag->time = info.frames / info.samplerate;
+
+ p = sf_get_string(sf, SF_STR_TITLE);
+ if (p != NULL)
+ tag_add_item(tag, TAG_TITLE, p);
+
+ p = sf_get_string(sf, SF_STR_ARTIST);
+ if (p != NULL)
+ tag_add_item(tag, TAG_ARTIST, p);
+
+ p = sf_get_string(sf, SF_STR_DATE);
+ if (p != NULL)
+ tag_add_item(tag, TAG_DATE, p);
+
+ sf_close(sf);
+
+ return tag;
+}
+
+static const char *const sndfile_suffixes[] = {
+ "wav", "aiff", "aif", /* Microsoft / SGI / Apple */
+ "au", "snd", /* Sun / DEC / NeXT */
+ "paf", /* Paris Audio File */
+ "iff", "svx", /* Commodore Amiga IFF / SVX */
+ "sf", /* IRCAM */
+ "voc", /* Creative */
+ "w64", /* Soundforge */
+ "pvf", /* Portable Voice Format */
+ "xi", /* Fasttracker */
+ "htk", /* HMM Tool Kit */
+ "caf", /* Apple */
+ "sd2", /* Sound Designer II */
+
+ /* libsndfile also supports FLAC and Ogg Vorbis, but only by
+ linking with libFLAC and libvorbis - we can do better, we
+ have native plugins for these libraries */
+
+ NULL
+};
+
+static const char *const sndfile_mime_types[] = {
+ "audio/x-wav",
+ "audio/x-aiff",
+
+ /* what are the MIME types of the other supported formats? */
+
+ NULL
+};
+
+const struct decoder_plugin sndfile_decoder_plugin = {
+ .name = "sndfile",
+ .stream_decode = sndfile_stream_decode,
+ .tag_dup = sndfile_tag_dup,
+ .suffixes = sndfile_suffixes,
+ .mime_types = sndfile_mime_types,
+};
diff --git a/src/decoder/vorbis_plugin.c b/src/decoder/vorbis_decoder_plugin.c
index 7c782a779..0a3944ad6 100644
--- a/src/decoder/vorbis_plugin.c
+++ b/src/decoder/vorbis_decoder_plugin.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -17,19 +17,19 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
-/* TODO 'ogg' should probably be replaced with 'oggvorbis' in all instances */
-
-#include "_ogg_common.h"
#include "config.h"
+#include "_ogg_common.h"
+#include "audio_check.h"
#include "uri.h"
#ifndef HAVE_TREMOR
+#define OV_EXCLUDE_STATIC_CALLBACKS
#include <vorbis/vorbisfile.h>
#else
#include <tremor/ivorbisfile.h>
/* Macros to make Tremor's API look like libogg. Tremor always
returns host-byte-order 16-bit signed data, and uses integer
- milliseconds where libogg uses double seconds.
+ milliseconds where libogg uses double seconds.
*/
#define ov_read(VF, BUFFER, LENGTH, BIGENDIANP, WORD, SGNED, BITSTREAM) \
ov_read(VF, BUFFER, LENGTH, BITSTREAM)
@@ -55,46 +55,46 @@
#define OGG_DECODE_USE_BIGENDIAN 0
#endif
-typedef struct _OggCallbackData {
+struct vorbis_input_stream {
struct decoder *decoder;
struct input_stream *input_stream;
bool seekable;
-} OggCallbackData;
+};
-static size_t ogg_read_cb(void *ptr, size_t size, size_t nmemb, void *vdata)
+static size_t ogg_read_cb(void *ptr, size_t size, size_t nmemb, void *data)
{
+ struct vorbis_input_stream *vis = data;
size_t ret;
- OggCallbackData *data = (OggCallbackData *) vdata;
- ret = decoder_read(data->decoder, data->input_stream, ptr, size * nmemb);
+ ret = decoder_read(vis->decoder, vis->input_stream, ptr, size * nmemb);
errno = 0;
return ret / size;
}
-static int ogg_seek_cb(void *vdata, ogg_int64_t offset, int whence)
+static int ogg_seek_cb(void *data, ogg_int64_t offset, int whence)
{
- const OggCallbackData *data = (const OggCallbackData *) vdata;
+ struct vorbis_input_stream *vis = data;
- return data->seekable &&
- decoder_get_command(data->decoder) != DECODE_COMMAND_STOP &&
- input_stream_seek(data->input_stream, offset, whence)
+ return vis->seekable &&
+ (!vis->decoder || decoder_get_command(vis->decoder) != DECODE_COMMAND_STOP) &&
+ input_stream_seek(vis->input_stream, offset, whence, NULL)
? 0 : -1;
}
/* TODO: check Ogg libraries API and see if we can just not have this func */
-static int ogg_close_cb(G_GNUC_UNUSED void *vdata)
+static int ogg_close_cb(G_GNUC_UNUSED void *data)
{
return 0;
}
-static long ogg_tell_cb(void *vdata)
+static long ogg_tell_cb(void *data)
{
- const OggCallbackData *data = (const OggCallbackData *) vdata;
+ const struct vorbis_input_stream *vis = data;
- return (long)data->input_stream->offset;
+ return (long)vis->input_stream->offset;
}
static const ov_callbacks vorbis_is_callbacks = {
@@ -105,6 +105,52 @@ static const ov_callbacks vorbis_is_callbacks = {
};
static const char *
+vorbis_strerror(int code)
+{
+ switch (code) {
+ case OV_EREAD:
+ return "read error";
+
+ case OV_ENOTVORBIS:
+ return "not vorbis stream";
+
+ case OV_EVERSION:
+ return "vorbis version mismatch";
+
+ case OV_EBADHEADER:
+ return "invalid vorbis header";
+
+ case OV_EFAULT:
+ return "internal logic error";
+
+ default:
+ return "unknown error";
+ }
+}
+
+static bool
+vorbis_is_open(struct vorbis_input_stream *vis, OggVorbis_File *vf,
+ struct decoder *decoder, struct input_stream *input_stream)
+{
+ vis->decoder = decoder;
+ vis->input_stream = input_stream;
+ vis->seekable = input_stream->seekable &&
+ (input_stream->uri == NULL ||
+ !uri_has_scheme(input_stream->uri));
+
+ int ret = ov_open_callbacks(vis, vf, NULL, 0, vorbis_is_callbacks);
+ if (ret < 0) {
+ if (decoder == NULL ||
+ decoder_get_command(decoder) == DECODE_COMMAND_NONE)
+ g_warning("Failed to open Ogg Vorbis stream: %s",
+ vorbis_strerror(ret));
+ return false;
+ }
+
+ return true;
+}
+
+static const char *
vorbis_comment_value(const char *comment, const char *needle)
{
size_t len = strlen(needle);
@@ -116,14 +162,13 @@ vorbis_comment_value(const char *comment, const char *needle)
return NULL;
}
-static struct replay_gain_info *
-vorbis_comments_to_replay_gain(char **comments)
+static bool
+vorbis_comments_to_replay_gain(struct replay_gain_info *rgi, char **comments)
{
- struct replay_gain_info *rgi;
const char *temp;
bool found = false;
- rgi = replay_gain_info_new();
+ replay_gain_info_init(rgi);
while (*comments) {
if ((temp =
@@ -147,12 +192,7 @@ vorbis_comments_to_replay_gain(char **comments)
comments++;
}
- if (!found) {
- replay_gain_info_free(rgi);
- rgi = NULL;
- }
-
- return rgi;
+ return found;
}
static const char *VORBIS_COMMENT_TRACK_KEY = "tracknumber";
@@ -183,11 +223,11 @@ vorbis_parse_comment(struct tag *tag, const char *comment)
assert(tag != NULL);
if (vorbis_copy_comment(tag, comment, VORBIS_COMMENT_TRACK_KEY,
- TAG_ITEM_TRACK) ||
+ TAG_TRACK) ||
vorbis_copy_comment(tag, comment, VORBIS_COMMENT_DISC_KEY,
- TAG_ITEM_DISC) ||
+ TAG_DISC) ||
vorbis_copy_comment(tag, comment, "album artist",
- TAG_ITEM_ALBUM_ARTIST))
+ TAG_ALBUM_ARTIST))
return;
for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i)
@@ -226,42 +266,23 @@ vorbis_send_comments(struct decoder *decoder, struct input_stream *is,
tag_free(tag);
}
-static bool
-oggvorbis_seekable(struct decoder *decoder)
-{
- char *uri;
- bool seekable;
-
- uri = decoder_get_uri(decoder);
- if (uri == NULL)
- return false;
-
- /* disable seeking on remote streams, because libvorbis seeks
- around like crazy, and due to being very expensive, this
- delays song playback my 10 or 20 seconds */
- seekable = !uri_has_scheme(uri);
- g_free(uri);
-
- return seekable;
-}
-
/* public */
static void
vorbis_stream_decode(struct decoder *decoder,
struct input_stream *input_stream)
{
+ GError *error = NULL;
OggVorbis_File vf;
- OggCallbackData data;
+ struct vorbis_input_stream vis;
struct audio_format audio_format;
+ float total_time;
int current_section;
int prev_section = -1;
long ret;
char chunk[OGG_CHUNK_SIZE];
long bitRate = 0;
long test;
- struct replay_gain_info *replay_gain_info = NULL;
- char **comments;
- bool initialized = false;
+ const vorbis_info *vi;
enum decoder_command cmd = DECODE_COMMAND_NONE;
if (ogg_stream_type_detect(input_stream) != VORBIS)
@@ -269,43 +290,30 @@ 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);
-
- data.decoder = decoder;
- data.input_stream = input_stream;
- data.seekable = input_stream->seekable && oggvorbis_seekable(decoder);
+ input_stream_seek(input_stream, 0, SEEK_SET, NULL);
- if ((ret = ov_open_callbacks(&data, &vf, NULL, 0,
- vorbis_is_callbacks)) < 0) {
- const char *error;
- if (decoder_get_command(decoder) != DECODE_COMMAND_NONE)
- return;
+ if (!vorbis_is_open(&vis, &vf, decoder, input_stream))
+ return;
- switch (ret) {
- case OV_EREAD:
- error = "read error";
- break;
- case OV_ENOTVORBIS:
- error = "not vorbis stream";
- break;
- case OV_EVERSION:
- error = "vorbis version mismatch";
- break;
- case OV_EBADHEADER:
- error = "invalid vorbis header";
- break;
- case OV_EFAULT:
- error = "internal logic error";
- break;
- default:
- error = "unknown error";
- break;
- }
+ vi = ov_info(&vf, -1);
+ if (vi == NULL) {
+ g_warning("ov_info() has failed");
+ return;
+ }
- g_warning("Error decoding Ogg Vorbis stream: %s", error);
+ if (!audio_format_init_checked(&audio_format, vi->rate,
+ SAMPLE_FORMAT_S16,
+ vi->channels, &error)) {
+ g_warning("%s", error->message);
+ g_error_free(error);
return;
}
- audio_format.bits = 16;
+
+ total_time = ov_time_total(&vf, -1);
+ if (total_time < 0)
+ total_time = 0;
+
+ decoder_initialized(decoder, &audio_format, vis.seekable, total_time);
do {
if (cmd == DECODE_COMMAND_SEEK) {
@@ -325,83 +333,61 @@ vorbis_stream_decode(struct decoder *decoder,
break;
if (current_section != prev_section) {
- /*printf("new song!\n"); */
- vorbis_info *vi = ov_info(&vf, -1);
- struct replay_gain_info *new_rgi;
-
- audio_format.channels = vi->channels;
- audio_format.sample_rate = vi->rate;
-
- if (!audio_format_valid(&audio_format)) {
- g_warning("Invalid audio format: %u:%u:%u\n",
- audio_format.sample_rate,
- audio_format.bits,
- audio_format.channels);
+ char **comments;
+
+ vi = ov_info(&vf, -1);
+ if (vi == NULL) {
+ g_warning("ov_info() has failed");
break;
}
- if (!initialized) {
- float total_time = ov_time_total(&vf, -1);
- if (total_time < 0)
- total_time = 0;
- decoder_initialized(decoder, &audio_format,
- data.seekable,
- total_time);
- initialized = true;
+ if (vi->rate != (long)audio_format.sample_rate ||
+ vi->channels != (int)audio_format.channels) {
+ /* we don't support audio format
+ change yet */
+ g_warning("audio format change, stopping here");
+ break;
}
+
comments = ov_comment(&vf, -1)->user_comments;
vorbis_send_comments(decoder, input_stream, comments);
- new_rgi = vorbis_comments_to_replay_gain(comments);
- if (new_rgi != NULL) {
- if (replay_gain_info != NULL)
- replay_gain_info_free(replay_gain_info);
- replay_gain_info = new_rgi;
- }
- }
- prev_section = current_section;
+ struct replay_gain_info rgi;
+ if (vorbis_comments_to_replay_gain(&rgi, comments))
+ decoder_replay_gain(decoder, &rgi);
+
+ prev_section = current_section;
+ }
if ((test = ov_bitrate_instant(&vf)) > 0)
bitRate = test / 1000;
cmd = decoder_data(decoder, input_stream,
chunk, ret,
- ov_pcm_tell(&vf) / audio_format.sample_rate,
- bitRate, replay_gain_info);
+ bitRate);
} while (cmd != DECODE_COMMAND_STOP);
- if (replay_gain_info)
- replay_gain_info_free(replay_gain_info);
-
ov_clear(&vf);
}
static struct tag *
-vorbis_tag_dup(const char *file)
+vorbis_stream_tag(struct input_stream *is)
{
- struct tag *ret;
- FILE *fp;
+ struct vorbis_input_stream vis;
OggVorbis_File vf;
- fp = fopen(file, "r");
- if (!fp) {
+ if (!vorbis_is_open(&vis, &vf, NULL, is))
return NULL;
- }
- if (ov_open(fp, &vf, NULL, 0) < 0) {
- fclose(fp);
- return NULL;
- }
-
- ret = vorbis_comments_to_tag(ov_comment(&vf, -1)->user_comments);
+ struct tag *tag = vorbis_comments_to_tag(ov_comment(&vf, -1)->user_comments);
- if (!ret)
- ret = tag_new();
- ret->time = (int)(ov_time_total(&vf, -1) + 0.5);
+ if (tag == NULL)
+ tag = tag_new();
+ tag->time = (int)(ov_time_total(&vf, -1) + 0.5);
ov_clear(&vf);
- return ret;
+ return tag;
}
static const char *const vorbis_suffixes[] = {
@@ -423,7 +409,7 @@ static const char *const vorbis_mime_types[] = {
const struct decoder_plugin vorbis_decoder_plugin = {
.name = "vorbis",
.stream_decode = vorbis_stream_decode,
- .tag_dup = vorbis_tag_dup,
+ .stream_tag = vorbis_stream_tag,
.suffixes = vorbis_suffixes,
.mime_types = vorbis_mime_types
};
diff --git a/src/decoder/wavpack_plugin.c b/src/decoder/wavpack_decoder_plugin.c
index 7ad3a62b0..efed98851 100644
--- a/src/decoder/wavpack_plugin.c
+++ b/src/decoder/wavpack_decoder_plugin.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -17,9 +17,11 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
-#include "../decoder_api.h"
-#include "../path.h"
-#include "../utils.h"
+#include "config.h"
+#include "decoder_api.h"
+#include "audio_check.h"
+#include "path.h"
+#include "utils.h"
#include <wavpack/wavpack.h>
#include <glib.h>
@@ -41,17 +43,17 @@ static struct {
const char *name;
enum tag_type type;
} tagtypes[] = {
- { "artist", TAG_ITEM_ARTIST },
- { "album", TAG_ITEM_ALBUM },
- { "title", TAG_ITEM_TITLE },
- { "track", TAG_ITEM_TRACK },
- { "name", TAG_ITEM_NAME },
- { "genre", TAG_ITEM_GENRE },
- { "date", TAG_ITEM_DATE },
- { "composer", TAG_ITEM_COMPOSER },
- { "performer", TAG_ITEM_PERFORMER },
- { "comment", TAG_ITEM_COMMENT },
- { "disc", TAG_ITEM_DISC },
+ { "artist", TAG_ARTIST },
+ { "album", TAG_ALBUM },
+ { "title", TAG_TITLE },
+ { "track", TAG_TRACK },
+ { "name", TAG_NAME },
+ { "genre", TAG_GENRE },
+ { "date", TAG_DATE },
+ { "composer", TAG_COMPOSER },
+ { "performer", TAG_PERFORMER },
+ { "comment", TAG_COMMENT },
+ { "disc", TAG_DISC },
};
/** A pointer type for format converter function. */
@@ -97,19 +99,11 @@ format_samples_int(int bytes_per_sample, void *buffer, uint32_t count)
}
break;
}
+
case 3:
+ case 4:
/* do nothing */
break;
- case 4: {
- uint32_t *dst = buffer;
- assert_static(sizeof(*dst) <= sizeof(*src));
-
- /* downsample to 24-bit */
- while (count--) {
- *dst++ = *src++ >> 8;
- }
- break;
- }
}
}
@@ -129,38 +123,61 @@ format_samples_float(G_GNUC_UNUSED int bytes_per_sample, void *buffer,
}
}
+/**
+ * Choose a MPD sample format from libwavpacks' number of bits.
+ */
+static enum sample_format
+wavpack_bits_to_sample_format(bool is_float, int bytes_per_sample)
+{
+ if (is_float)
+ return SAMPLE_FORMAT_S24_P32;
+
+ switch (bytes_per_sample) {
+ case 1:
+ return SAMPLE_FORMAT_S8;
+
+ case 2:
+ return SAMPLE_FORMAT_S16;
+
+ case 3:
+ return SAMPLE_FORMAT_S24_P32;
+
+ case 4:
+ return SAMPLE_FORMAT_S32;
+
+ default:
+ return SAMPLE_FORMAT_UNDEFINED;
+ }
+}
+
/*
* This does the main decoding thing.
* Requires an already opened WavpackContext.
*/
static void
-wavpack_decode(struct decoder *decoder, WavpackContext *wpc, bool can_seek,
- struct replay_gain_info *replay_gain_info)
+wavpack_decode(struct decoder *decoder, WavpackContext *wpc, bool can_seek)
{
+ GError *error = NULL;
+ bool is_float;
+ enum sample_format sample_format;
struct audio_format audio_format;
format_samples_t format_samples;
char chunk[CHUNK_SIZE];
int samples_requested, samples_got;
- float total_time, current_time;
+ float total_time;
int bytes_per_sample, output_sample_size;
- int position;
- audio_format.sample_rate = WavpackGetSampleRate(wpc);
- audio_format.channels = WavpackGetReducedChannels(wpc);
- audio_format.bits = WavpackGetBitsPerSample(wpc);
-
- /* round bitwidth to 8-bit units */
- audio_format.bits = (audio_format.bits + 7) & (~7);
- /* mpd handles max 24-bit samples */
- if (audio_format.bits > 24) {
- audio_format.bits = 24;
- }
-
- if (!audio_format_valid(&audio_format)) {
- g_warning("Invalid audio format: %u:%u:%u\n",
- audio_format.sample_rate,
- audio_format.bits,
- audio_format.channels);
+ is_float = (WavpackGetMode(wpc) & MODE_FLOAT) != 0;
+ sample_format =
+ wavpack_bits_to_sample_format(is_float,
+ WavpackGetBytesPerSample(wpc));
+
+ if (!audio_format_init_checked(&audio_format,
+ WavpackGetSampleRate(wpc),
+ sample_format,
+ WavpackGetNumChannels(wpc), &error)) {
+ g_warning("%s", error->message);
+ g_error_free(error);
return;
}
@@ -180,8 +197,6 @@ wavpack_decode(struct decoder *decoder, WavpackContext *wpc, bool can_seek,
decoder_initialized(decoder, &audio_format, can_seek, total_time);
- position = 0;
-
do {
if (decoder_get_command(decoder) == DECODE_COMMAND_SEEK) {
if (can_seek) {
@@ -189,7 +204,6 @@ wavpack_decode(struct decoder *decoder, WavpackContext *wpc, bool can_seek,
audio_format.sample_rate;
if (WavpackSeekSample(wpc, where)) {
- position = where;
decoder_command_finished(decoder);
} else {
decoder_seek_error(decoder);
@@ -209,9 +223,6 @@ wavpack_decode(struct decoder *decoder, WavpackContext *wpc, bool can_seek,
if (samples_got > 0) {
int bitrate = (int)(WavpackGetInstantBitrate(wpc) /
1000 + 0.5);
- position += samples_got;
- current_time = position;
- current_time /= audio_format.sample_rate;
format_samples(
bytes_per_sample, chunk,
@@ -221,8 +232,7 @@ wavpack_decode(struct decoder *decoder, WavpackContext *wpc, bool can_seek,
decoder_data(
decoder, NULL, chunk,
samples_got * output_sample_size,
- current_time, bitrate,
- replay_gain_info
+ bitrate
);
}
} while (samples_got > 0);
@@ -246,13 +256,13 @@ wavpack_tag_float(WavpackContext *wpc, const char *key, float *value_r)
return true;
}
-static struct replay_gain_info *
-wavpack_replaygain(WavpackContext *wpc)
+static bool
+wavpack_replaygain(struct replay_gain_info *replay_gain_info,
+ WavpackContext *wpc)
{
- struct replay_gain_info *replay_gain_info;
bool found = false;
- replay_gain_info = replay_gain_info_new();
+ replay_gain_info_init(replay_gain_info);
found |= wavpack_tag_float(
wpc, "replaygain_track_gain",
@@ -271,13 +281,7 @@ wavpack_replaygain(WavpackContext *wpc)
&replay_gain_info->tuples[REPLAY_GAIN_ALBUM].peak
);
- if (found) {
- return replay_gain_info;
- }
-
- replay_gain_info_free(replay_gain_info);
-
- return NULL;
+ return found;
}
/*
@@ -397,13 +401,13 @@ 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) ? 0 : -1;
+ return input_stream_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) ? 0 : -1;
+ return input_stream_seek(wpin(id)->is, delta, mode, NULL) ? 0 : -1;
}
static int
@@ -452,13 +456,12 @@ wavpack_input_init(struct wavpack_input *isp, struct decoder *decoder,
isp->last_byte = EOF;
}
-static bool
-wavpack_open_wvc(struct decoder *decoder, struct input_stream *is_wvc,
+static struct input_stream *
+wavpack_open_wvc(struct decoder *decoder, const char *uri,
struct wavpack_input *wpi)
{
- char *utf8url;
+ struct input_stream *is_wvc;
char *wvc_url = NULL;
- bool ret;
char first_byte;
size_t nbytes;
@@ -466,20 +469,15 @@ wavpack_open_wvc(struct decoder *decoder, struct input_stream *is_wvc,
* As we use dc->utf8url, this function will be bad for
* single files. utf8url is not absolute file path :/
*/
- utf8url = decoder_get_uri(decoder);
- if (utf8url == NULL) {
+ if (uri == NULL)
return false;
- }
- wvc_url = g_strconcat(utf8url, "c", NULL);
- g_free(utf8url);
-
- ret = input_stream_open(is_wvc, wvc_url);
+ wvc_url = g_strconcat(uri, "c", NULL);
+ is_wvc = input_stream_open(wvc_url, NULL);
g_free(wvc_url);
- if (!ret) {
- return false;
- }
+ if (is_wvc == NULL)
+ return NULL;
/*
* And we try to buffer in order to get know
@@ -490,13 +488,13 @@ wavpack_open_wvc(struct decoder *decoder, struct input_stream *is_wvc,
);
if (nbytes == 0) {
input_stream_close(is_wvc);
- return false;
+ return NULL;
}
/* push it back */
wavpack_input_init(wpi, decoder, is_wvc);
wpi->last_byte = first_byte;
- return true;
+ return is_wvc;
}
/*
@@ -507,14 +505,15 @@ wavpack_streamdecode(struct decoder * decoder, struct input_stream *is)
{
char error[ERRORLEN];
WavpackContext *wpc;
- struct input_stream is_wvc;
- int open_flags = OPEN_2CH_MAX | OPEN_NORMALIZE;
+ struct input_stream *is_wvc;
+ int open_flags = OPEN_NORMALIZE;
struct wavpack_input isp, isp_wvc;
bool can_seek = is->seekable;
- if (wavpack_open_wvc(decoder, &is_wvc, &isp_wvc)) {
+ is_wvc = wavpack_open_wvc(decoder, is->uri, &isp_wvc);
+ if (is_wvc != NULL) {
open_flags |= OPEN_WVC;
- can_seek &= is_wvc.seekable;
+ can_seek &= is_wvc->seekable;
}
if (!can_seek) {
@@ -533,11 +532,11 @@ wavpack_streamdecode(struct decoder * decoder, struct input_stream *is)
return;
}
- wavpack_decode(decoder, wpc, can_seek, NULL);
+ wavpack_decode(decoder, wpc, can_seek);
WavpackCloseFile(wpc);
if (open_flags & OPEN_WVC) {
- input_stream_close(&is_wvc);
+ input_stream_close(is_wvc);
}
}
@@ -549,11 +548,10 @@ wavpack_filedecode(struct decoder *decoder, const char *fname)
{
char error[ERRORLEN];
WavpackContext *wpc;
- struct replay_gain_info *replay_gain_info;
wpc = WavpackOpenFileInput(
fname, error,
- OPEN_TAGS | OPEN_WVC | OPEN_2CH_MAX | OPEN_NORMALIZE, 23
+ OPEN_TAGS | OPEN_WVC | OPEN_NORMALIZE, 23
);
if (wpc == NULL) {
g_warning(
@@ -563,13 +561,11 @@ wavpack_filedecode(struct decoder *decoder, const char *fname)
return;
}
- replay_gain_info = wavpack_replaygain(wpc);
+ struct replay_gain_info replay_gain_info;
+ if (wavpack_replaygain(&replay_gain_info, wpc))
+ decoder_replay_gain(decoder, &replay_gain_info);
- wavpack_decode(decoder, wpc, true, replay_gain_info);
-
- if (replay_gain_info) {
- replay_gain_info_free(replay_gain_info);
- }
+ wavpack_decode(decoder, wpc, true);
WavpackCloseFile(wpc);
}
diff --git a/src/decoder/wildmidi_plugin.c b/src/decoder/wildmidi_decoder_plugin.c
index b5e9810f9..66e6c61cf 100644
--- a/src/decoder/wildmidi_plugin.c
+++ b/src/decoder/wildmidi_decoder_plugin.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -17,7 +17,8 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
-#include "../decoder_api.h"
+#include "config.h"
+#include "decoder_api.h"
#include <glib.h>
@@ -58,7 +59,7 @@ wildmidi_file_decode(struct decoder *decoder, const char *path_fs)
{
static const struct audio_format audio_format = {
.sample_rate = WILDMIDI_SAMPLE_RATE,
- .bits = 16,
+ .format = SAMPLE_FORMAT_S16,
.channels = 2,
};
midi *wm;
@@ -90,10 +91,7 @@ wildmidi_file_decode(struct decoder *decoder, const char *path_fs)
if (len <= 0)
break;
- cmd = decoder_data(decoder, NULL, buffer, len,
- (float)info->current_sample /
- (float)WILDMIDI_SAMPLE_RATE,
- 0, NULL);
+ cmd = decoder_data(decoder, NULL, buffer, len, 0);
if (cmd == DECODE_COMMAND_SEEK) {
unsigned long seek_where = WILDMIDI_SAMPLE_RATE *
@@ -116,21 +114,17 @@ wildmidi_file_decode(struct decoder *decoder, const char *path_fs)
static struct tag *
wildmidi_tag_dup(const char *path_fs)
{
- midi *wm;
- const struct _WM_Info *info;
- struct tag *tag;
-
- wm = WildMidi_Open(path_fs);
+ midi *wm = WildMidi_Open(path_fs);
if (wm == NULL)
return NULL;
- info = WildMidi_GetInfo(wm);
+ const struct _WM_Info *info = WildMidi_GetInfo(wm);
if (info == NULL) {
WildMidi_Close(wm);
return NULL;
}
- tag = tag_new();
+ struct tag *tag = tag_new();
tag->time = info->approx_total_samples / WILDMIDI_SAMPLE_RATE;
WildMidi_Close(wm);
diff --git a/src/decoder_api.c b/src/decoder_api.c
index c696ba101..fe34ea34a 100644
--- a/src/decoder_api.c
+++ b/src/decoder_api.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -17,6 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "decoder_api.h"
#include "decoder_internal.h"
#include "decoder_control.h"
@@ -24,10 +25,9 @@
#include "audio.h"
#include "song.h"
#include "buffer.h"
-
-#include "normalize.h"
#include "pipe.h"
#include "chunk.h"
+#include "replay_gain_config.h"
#include <glib.h>
@@ -37,12 +37,16 @@
#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "decoder"
-void decoder_initialized(G_GNUC_UNUSED struct decoder * decoder,
- const struct audio_format *audio_format,
- bool seekable, float total_time)
+void
+decoder_initialized(struct decoder *decoder,
+ const struct audio_format *audio_format,
+ bool seekable, float total_time)
{
- assert(dc.state == DECODE_STATE_START);
- assert(dc.pipe != NULL);
+ struct decoder_control *dc = decoder->dc;
+ struct audio_format_string af_string;
+
+ assert(dc->state == DECODE_STATE_START);
+ assert(dc->pipe != NULL);
assert(decoder != NULL);
assert(decoder->stream_tag == NULL);
assert(decoder->decoder_tag == NULL);
@@ -51,47 +55,49 @@ void decoder_initialized(G_GNUC_UNUSED struct decoder * decoder,
assert(audio_format_defined(audio_format));
assert(audio_format_valid(audio_format));
- dc.in_audio_format = *audio_format;
- getOutputAudioFormat(audio_format, &dc.out_audio_format);
+ dc->in_audio_format = *audio_format;
+ getOutputAudioFormat(audio_format, &dc->out_audio_format);
+
+ dc->seekable = seekable;
+ dc->total_time = total_time;
- dc.seekable = seekable;
- dc.total_time = total_time;
+ decoder_lock(dc);
+ dc->state = DECODE_STATE_DECODE;
+ decoder_unlock(dc);
- dc.state = DECODE_STATE_DECODE;
- notify_signal(&pc.notify);
+ player_lock_signal();
- g_debug("audio_format=%u:%u:%u, seekable=%s",
- dc.in_audio_format.sample_rate, dc.in_audio_format.bits,
- dc.in_audio_format.channels,
+ g_debug("audio_format=%s, seekable=%s",
+ audio_format_to_string(&dc->in_audio_format, &af_string),
seekable ? "true" : "false");
- if (!audio_format_equals(&dc.in_audio_format, &dc.out_audio_format))
- g_debug("converting to %u:%u:%u",
- dc.out_audio_format.sample_rate,
- dc.out_audio_format.bits,
- dc.out_audio_format.channels);
+ if (!audio_format_equals(&dc->in_audio_format,
+ &dc->out_audio_format))
+ g_debug("converting to %s",
+ audio_format_to_string(&dc->out_audio_format,
+ &af_string));
}
-char *decoder_get_uri(G_GNUC_UNUSED struct decoder *decoder)
+enum decoder_command decoder_get_command(G_GNUC_UNUSED struct decoder * decoder)
{
- assert(dc.pipe != NULL);
+ const struct decoder_control *dc = decoder->dc;
+
+ assert(dc->pipe != NULL);
- return song_get_uri(dc.current_song);
+ return dc->command;
}
-enum decoder_command decoder_get_command(G_GNUC_UNUSED struct decoder * decoder)
+void
+decoder_command_finished(struct decoder *decoder)
{
- assert(dc.pipe != NULL);
+ struct decoder_control *dc = decoder->dc;
- return dc.command;
-}
+ decoder_lock(dc);
-void decoder_command_finished(G_GNUC_UNUSED struct decoder * decoder)
-{
- assert(dc.command != DECODE_COMMAND_NONE);
- assert(dc.command != DECODE_COMMAND_SEEK ||
- dc.seek_error || decoder->seeking);
- assert(dc.pipe != NULL);
+ assert(dc->command != DECODE_COMMAND_NONE);
+ assert(dc->command != DECODE_COMMAND_SEEK ||
+ dc->seek_error || decoder->seeking);
+ assert(dc->pipe != NULL);
if (decoder->seeking) {
decoder->seeking = false;
@@ -99,33 +105,41 @@ void decoder_command_finished(G_GNUC_UNUSED struct decoder * decoder)
/* delete frames from the old song position */
if (decoder->chunk != NULL) {
- music_buffer_return(dc.buffer, decoder->chunk);
+ music_buffer_return(dc->buffer, decoder->chunk);
decoder->chunk = NULL;
}
- music_pipe_clear(dc.pipe, dc.buffer);
+ music_pipe_clear(dc->pipe, dc->buffer);
+
+ decoder->timestamp = dc->seek_where;
}
- dc.command = DECODE_COMMAND_NONE;
- notify_signal(&pc.notify);
+ dc->command = DECODE_COMMAND_NONE;
+ decoder_unlock(dc);
+
+ player_lock_signal();
}
double decoder_seek_where(G_GNUC_UNUSED struct decoder * decoder)
{
- assert(dc.command == DECODE_COMMAND_SEEK);
- assert(dc.pipe != NULL);
+ const struct decoder_control *dc = decoder->dc;
+
+ assert(dc->command == DECODE_COMMAND_SEEK);
+ assert(dc->pipe != NULL);
decoder->seeking = true;
- return dc.seek_where;
+ return dc->seek_where;
}
void decoder_seek_error(struct decoder * decoder)
{
- assert(dc.command == DECODE_COMMAND_SEEK);
- assert(dc.pipe != NULL);
+ struct decoder_control *dc = decoder->dc;
+
+ assert(dc->command == DECODE_COMMAND_SEEK);
+ assert(dc->pipe != NULL);
- dc.seek_error = true;
+ dc->seek_error = true;
decoder->seeking = false;
decoder_command_finished(decoder);
@@ -135,11 +149,14 @@ 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;
+ GError *error = NULL;
size_t nbytes;
assert(decoder == NULL ||
- dc.state == DECODE_STATE_START ||
- dc.state == DECODE_STATE_DECODE);
+ dc->state == DECODE_STATE_START ||
+ dc->state == DECODE_STATE_DECODE);
assert(is != NULL);
assert(buffer != NULL);
@@ -152,12 +169,19 @@ size_t decoder_read(struct decoder *decoder,
/* 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)
+ (dc->command != DECODE_COMMAND_SEEK ||
+ (dc->state != DECODE_STATE_START && !decoder->seeking)) &&
+ dc->command != DECODE_COMMAND_NONE)
return 0;
- nbytes = input_stream_read(is, buffer, length);
+ nbytes = input_stream_read(is, buffer, length, &error);
+
+ if (G_UNLIKELY(nbytes == 0 && error != NULL)) {
+ g_warning("%s", error->message);
+ g_error_free(error);
+ return 0;
+ }
+
if (nbytes > 0 || input_stream_eof(is))
return nbytes;
@@ -167,6 +191,15 @@ size_t decoder_read(struct decoder *decoder,
}
}
+void
+decoder_timestamp(struct decoder *decoder, double t)
+{
+ assert(decoder != NULL);
+ assert(t >= 0);
+
+ decoder->timestamp = t;
+}
+
/**
* Sends a #tag as-is to the music pipe. Flushes the current chunk
* (decoder.chunk) if there is one.
@@ -181,15 +214,15 @@ 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);
- notify_signal(&pc.notify);
+ player_lock_signal();
}
assert(decoder->chunk == NULL);
chunk = decoder_get_chunk(decoder, is);
if (chunk == NULL) {
- assert(dc.command != DECODE_COMMAND_NONE);
- return dc.command;
+ assert(decoder->dc->command != DECODE_COMMAND_NONE);
+ return decoder->dc->command;
}
chunk->tag = tag_dup(tag);
@@ -225,31 +258,34 @@ enum decoder_command
decoder_data(struct decoder *decoder,
struct input_stream *is,
const void *_data, size_t length,
- float data_time, uint16_t bitRate,
- struct replay_gain_info *replay_gain_info)
+ uint16_t kbit_rate)
{
+ struct decoder_control *dc = decoder->dc;
const char *data = _data;
+ GError *error = NULL;
+ enum decoder_command cmd;
+
+ assert(dc->state == DECODE_STATE_DECODE);
+ assert(dc->pipe != NULL);
+ assert(length % audio_format_frame_size(&dc->in_audio_format) == 0);
- assert(dc.state == DECODE_STATE_DECODE);
- assert(dc.pipe != NULL);
- assert(length % audio_format_frame_size(&dc.in_audio_format) == 0);
+ decoder_lock(dc);
+ cmd = dc->command;
+ decoder_unlock(dc);
- if (dc.command == DECODE_COMMAND_STOP ||
- dc.command == DECODE_COMMAND_SEEK ||
+ if (cmd == DECODE_COMMAND_STOP || cmd == DECODE_COMMAND_SEEK ||
length == 0)
- return dc.command;
+ return cmd;
/* send stream tags */
if (update_stream_tag(decoder, is)) {
- enum decoder_command cmd;
-
if (decoder->decoder_tag != NULL) {
/* merge with tag from decoder plugin */
struct tag *tag;
- tag = tag_merge(decoder->stream_tag,
- decoder->decoder_tag);
+ tag = tag_merge(decoder->decoder_tag,
+ decoder->stream_tag);
cmd = do_send_tag(decoder, is, tag);
tag_free(tag);
} else
@@ -260,17 +296,18 @@ decoder_data(struct decoder *decoder,
return cmd;
}
- if (!audio_format_equals(&dc.in_audio_format, &dc.out_audio_format)) {
+ if (!audio_format_equals(&dc->in_audio_format, &dc->out_audio_format)) {
data = pcm_convert(&decoder->conv_state,
- &dc.in_audio_format, data, length,
- &dc.out_audio_format, &length);
-
- /* under certain circumstances, pcm_convert() may
- return an empty buffer - this condition should be
- investigated further, but for now, do this check as
- a workaround: */
- if (data == NULL)
- return DECODE_COMMAND_NONE;
+ &dc->in_audio_format, data, length,
+ &dc->out_audio_format, &length,
+ &error);
+ if (data == NULL) {
+ /* the PCM conversion has failed - stop
+ playback, since we have no better way to
+ bail out */
+ g_warning("%s", error->message);
+ return DECODE_COMMAND_STOP;
+ }
}
while (length > 0) {
@@ -281,16 +318,18 @@ decoder_data(struct decoder *decoder,
chunk = decoder_get_chunk(decoder, is);
if (chunk == NULL) {
- assert(dc.command != DECODE_COMMAND_NONE);
- return dc.command;
+ assert(dc->command != DECODE_COMMAND_NONE);
+ return dc->command;
}
- dest = music_chunk_write(chunk, &dc.out_audio_format,
- data_time, bitRate, &nbytes);
+ dest = music_chunk_write(chunk, &dc->out_audio_format,
+ decoder->timestamp -
+ dc->song->start_ms / 1000.0,
+ kbit_rate, &nbytes);
if (dest == NULL) {
/* the chunk is full, flush it */
decoder_flush_chunk(decoder);
- notify_signal(&pc.notify);
+ player_lock_signal();
continue;
}
@@ -303,26 +342,26 @@ decoder_data(struct decoder *decoder,
memcpy(dest, data, nbytes);
- /* apply replay gain or normalization */
-
- if (replay_gain_info != NULL &&
- replay_gain_mode != REPLAY_GAIN_OFF)
- replay_gain_apply(replay_gain_info, dest, nbytes,
- &dc.out_audio_format);
- else if (normalizationEnabled)
- normalizeData(dest, nbytes, &dc.out_audio_format);
-
/* expand the music pipe chunk */
- full = music_chunk_expand(chunk, &dc.out_audio_format, nbytes);
+ full = music_chunk_expand(chunk, &dc->out_audio_format, nbytes);
if (full) {
/* the chunk is full, flush it */
decoder_flush_chunk(decoder);
- notify_signal(&pc.notify);
+ player_lock_signal();
}
data += nbytes;
length -= nbytes;
+
+ decoder->timestamp += (double)nbytes /
+ audio_format_time_to_size(&dc->out_audio_format);
+
+ if (dc->song->end_ms > 0 &&
+ decoder->timestamp >= dc->song->end_ms / 1000.0)
+ /* the end of this range has been reached:
+ stop decoding */
+ return DECODE_COMMAND_STOP;
}
return DECODE_COMMAND_NONE;
@@ -332,10 +371,11 @@ enum decoder_command
decoder_tag(G_GNUC_UNUSED struct decoder *decoder, struct input_stream *is,
const struct tag *tag)
{
+ G_GNUC_UNUSED const struct decoder_control *dc = decoder->dc;
enum decoder_command cmd;
- assert(dc.state == DECODE_STATE_DECODE);
- assert(dc.pipe != NULL);
+ assert(dc->state == DECODE_STATE_DECODE);
+ assert(dc->pipe != NULL);
assert(tag != NULL);
/* save the tag */
@@ -363,3 +403,52 @@ decoder_tag(G_GNUC_UNUSED struct decoder *decoder, struct input_stream *is,
return cmd;
}
+
+float
+decoder_replay_gain(struct decoder *decoder,
+ const struct replay_gain_info *replay_gain_info)
+{
+ float return_db = 0;
+ assert(decoder != NULL);
+
+ if (replay_gain_info != NULL) {
+ static unsigned serial;
+ if (++serial == 0)
+ serial = 1;
+
+ if (REPLAY_GAIN_OFF != replay_gain_mode) {
+ return_db = 20.0 * log10f(
+ replay_gain_tuple_scale(
+ &replay_gain_info->tuples[replay_gain_get_real_mode()],
+ replay_gain_preamp, replay_gain_missing_preamp,
+ replay_gain_limit));
+ }
+
+ decoder->replay_gain_info = *replay_gain_info;
+ decoder->replay_gain_serial = serial;
+
+ if (decoder->chunk != NULL) {
+ /* flush the current chunk because the new
+ replay gain values affect the following
+ samples */
+ decoder_flush_chunk(decoder);
+ player_lock_signal();
+ }
+ } else
+ decoder->replay_gain_serial = 0;
+
+ return return_db;
+}
+
+void
+decoder_mixramp(struct decoder *decoder, float replay_gain_db,
+ char *mixramp_start, char *mixramp_end)
+{
+ assert(decoder != NULL);
+ struct decoder_control *dc = decoder->dc;
+ assert(dc != NULL);
+
+ dc->replay_gain_db = replay_gain_db;
+ dc_mixramp_start(dc, mixramp_start);
+ dc_mixramp_end(dc, mixramp_end);
+}
diff --git a/src/decoder_api.h b/src/decoder_api.h
index 37090d8d0..8b5f3d82b 100644
--- a/src/decoder_api.h
+++ b/src/decoder_api.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -17,87 +17,157 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
-#ifndef MPD_DECODER_API_H
-#define MPD_DECODER_API_H
-
-/*
+/*! \file
+ * \brief The MPD Decoder API
+ *
* This is the public API which is used by decoder plugins to
* communicate with the mpd core.
- *
*/
+#ifndef MPD_DECODER_API_H
+#define MPD_DECODER_API_H
+
+#include "check.h"
#include "decoder_command.h"
#include "decoder_plugin.h"
#include "input_stream.h"
-#include "replay_gain.h"
+#include "replay_gain_info.h"
#include "tag.h"
#include "audio_format.h"
#include "conf.h"
#include <stdbool.h>
-
/**
* Notify the player thread that it has finished initialization and
* that it has read the song's meta data.
+ *
+ * @param decoder the decoder object
+ * @param audio_format the audio format which is going to be sent to
+ * decoder_data()
+ * @param seekable true if the song is seekable
+ * @param total_time the total number of seconds in this song; -1 if unknown
*/
-void decoder_initialized(struct decoder * decoder,
- const struct audio_format *audio_format,
- bool seekable, float total_time);
+void
+decoder_initialized(struct decoder *decoder,
+ const struct audio_format *audio_format,
+ bool seekable, float total_time);
/**
- * Returns the URI of the current song in UTF-8 encoding.
+ * Determines the pending decoder command.
*
- * The return value is allocated on the heap, and must be freed by the
- * caller.
+ * @param decoder the decoder object
+ * @return the current command, or DECODE_COMMAND_NONE if there is no
+ * command pending
*/
-char *decoder_get_uri(struct decoder *decoder);
-
-enum decoder_command decoder_get_command(struct decoder * decoder);
+enum decoder_command
+decoder_get_command(struct decoder *decoder);
/**
* Called by the decoder when it has performed the requested command
* (dc->command). This function resets dc->command and wakes up the
* player thread.
+ *
+ * @param decoder the decoder object
+ */
+void
+decoder_command_finished(struct decoder *decoder);
+
+/**
+ * Call this when you have received the DECODE_COMMAND_SEEK command.
+ *
+ * @param decoder the decoder object
+ * @return the destination position for the week
*/
-void decoder_command_finished(struct decoder * decoder);
+double
+decoder_seek_where(struct decoder *decoder);
-double decoder_seek_where(struct decoder * decoder);
+/**
+ * Call this right before decoder_command_finished() when seeking has
+ * failed.
+ *
+ * @param decoder the decoder object
+ */
+void
+decoder_seek_error(struct decoder *decoder);
-void decoder_seek_error(struct decoder * decoder);
+/**
+ * Blocking read from the input stream.
+ *
+ * @param decoder the decoder object
+ * @param is the input stream to read from
+ * @param buffer the destination buffer
+ * @param length the maximum number of bytes to read
+ * @return the number of bytes read, or 0 if one of the following
+ * occurs: end of file; error; command (like SEEK or STOP).
+ */
+size_t
+decoder_read(struct decoder *decoder, struct input_stream *is,
+ void *buffer, size_t length);
/**
- * Blocking read from the input stream. Returns the number of bytes
- * read, or 0 if one of the following occurs: end of file; error;
- * command (like SEEK or STOP).
+ * Sets the time stamp for the next data chunk [seconds]. The MPD
+ * core automatically counts it up, and a decoder plugin only needs to
+ * use this function if it thinks that adding to the time stamp based
+ * on the buffer size won't work.
*/
-size_t decoder_read(struct decoder *decoder,
- struct input_stream *inStream,
- void *buffer, size_t length);
+void
+decoder_timestamp(struct decoder *decoder, double t);
/**
* This function is called by the decoder plugin when it has
* successfully decoded block of input data.
*
- * We send inStream for buffering the inputStream while waiting to
- * send the next chunk
+ * @param decoder the decoder object
+ * @param is an input stream which is buffering while we are waiting
+ * for the player
+ * @param data the source buffer
+ * @param length the number of bytes in the buffer
+ * @return the current command, or DECODE_COMMAND_NONE if there is no
+ * command pending
*/
enum decoder_command
-decoder_data(struct decoder *decoder,
- struct input_stream *inStream,
- const void *data, size_t datalen,
- float data_time, uint16_t bitRate,
- struct replay_gain_info *replay_gain_info);
+decoder_data(struct decoder *decoder, struct input_stream *is,
+ const void *data, size_t length,
+ uint16_t kbit_rate);
/**
* This function is called by the decoder plugin when it has
* successfully decoded a tag.
*
+ * @param decoder the decoder object
* @param is an input stream which is buffering while we are waiting
* for the player
+ * @param tag the tag to send
+ * @return the current command, or DECODE_COMMAND_NONE if there is no
+ * command pending
*/
enum decoder_command
decoder_tag(struct decoder *decoder, struct input_stream *is,
const struct tag *tag);
+/**
+ * Set replay gain values for the following chunks.
+ *
+ * @param decoder the decoder object
+ * @param rgi the replay_gain_info object; may be NULL to invalidate
+ * the previous replay gain values
+ * @return the replay gain adjustment used
+ */
+float
+decoder_replay_gain(struct decoder *decoder,
+ const struct replay_gain_info *replay_gain_info);
+
+/**
+ * Store MixRamp tags.
+ *
+ * @param decoder the decoder object
+ * @param replay_gain_db the ReplayGain adjustment used for this song
+ * @param mixramp_start the mixramp_start tag; may be NULL to invalidate
+ * @param mixramp_end the mixramp_end tag; may be NULL to invalidate
+ */
+void
+decoder_mixramp(struct decoder *decoder, float replay_gain_db,
+ char *mixramp_start, char *mixramp_end);
+
#endif
diff --git a/src/decoder_buffer.c b/src/decoder_buffer.c
index b6fa90004..8f8eb8545 100644
--- a/src/decoder_buffer.c
+++ b/src/decoder_buffer.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -17,6 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "decoder_buffer.h"
#include "decoder_api.h"
diff --git a/src/decoder_buffer.h b/src/decoder_buffer.h
index 411e3bd88..b6051e122 100644
--- a/src/decoder_buffer.h
+++ b/src/decoder_buffer.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
diff --git a/src/decoder_command.h b/src/decoder_command.h
index faabaea09..4a2e49f3e 100644
--- a/src/decoder_command.h
+++ b/src/decoder_command.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
diff --git a/src/decoder_control.c b/src/decoder_control.c
index 618c78e7e..a5e6e4ad3 100644
--- a/src/decoder_control.c
+++ b/src/decoder_control.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -17,115 +17,180 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "decoder_control.h"
+#include "player_control.h"
#include "pipe.h"
#include <assert.h>
-struct decoder_control dc;
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "decoder_control"
-void dc_init(void)
+void
+dc_init(struct decoder_control *dc)
{
- notify_init(&dc.notify);
- dc.state = DECODE_STATE_STOP;
- dc.command = DECODE_COMMAND_NONE;
+ dc->thread = NULL;
+
+ dc->mutex = g_mutex_new();
+ dc->cond = g_cond_new();
+
+ dc->state = DECODE_STATE_STOP;
+ dc->command = DECODE_COMMAND_NONE;
+
+ dc->replay_gain_db = 0;
+ dc->replay_gain_prev_db = 0;
+ dc->mixramp_start = NULL;
+ dc->mixramp_end = NULL;
+ dc->mixramp_prev_end = NULL;
}
-void dc_deinit(void)
+void
+dc_deinit(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;
+}
+
+static void
+dc_command_wait_locked(struct decoder_control *dc)
{
- notify_deinit(&dc.notify);
+ while (dc->command != DECODE_COMMAND_NONE)
+ player_wait_decoder(dc);
}
void
-dc_command_wait(struct notify *notify)
+dc_command_wait(struct decoder_control *dc)
{
- while (dc.command != DECODE_COMMAND_NONE) {
- notify_signal(&dc.notify);
- notify_wait(notify);
- }
+ decoder_lock(dc);
+ dc_command_wait_locked(dc);
+ decoder_unlock(dc);
}
static void
-dc_command(struct notify *notify, enum decoder_command cmd)
+dc_command_locked(struct decoder_control *dc, enum decoder_command cmd)
{
- dc.command = cmd;
- dc_command_wait(notify);
+ dc->command = cmd;
+ decoder_signal(dc);
+ dc_command_wait_locked(dc);
}
-static void dc_command_async(enum decoder_command cmd)
+static void
+dc_command(struct decoder_control *dc, enum decoder_command cmd)
{
- dc.command = cmd;
- notify_signal(&dc.notify);
+ decoder_lock(dc);
+ dc_command_locked(dc, cmd);
+ decoder_unlock(dc);
}
-void
-dc_start(struct notify *notify, struct song *song, struct music_pipe *pipe)
+static void
+dc_command_async(struct decoder_control *dc, enum decoder_command cmd)
{
- assert(dc.pipe == NULL);
- assert(song != NULL);
- assert(pipe != NULL);
- assert(music_pipe_empty(pipe));
+ decoder_lock(dc);
- dc.next_song = song;
- dc.pipe = pipe;
- dc_command(notify, DECODE_COMMAND_START);
+ dc->command = cmd;
+ decoder_signal(dc);
+
+ decoder_unlock(dc);
}
void
-dc_start_async(struct song *song, struct music_pipe *pipe)
+dc_start(struct decoder_control *dc, struct song *song,
+ struct music_buffer *buffer, struct music_pipe *pipe)
{
- assert(dc.pipe == NULL);
assert(song != NULL);
+ assert(buffer != NULL);
assert(pipe != NULL);
assert(music_pipe_empty(pipe));
- dc.next_song = song;
- dc.pipe = pipe;
- dc_command_async(DECODE_COMMAND_START);
+ dc->song = song;
+ dc->buffer = buffer;
+ dc->pipe = pipe;
+ dc_command(dc, DECODE_COMMAND_START);
}
void
-dc_stop(struct notify *notify)
+dc_stop(struct decoder_control *dc)
{
- if (dc.command != DECODE_COMMAND_NONE)
+ decoder_lock(dc);
+
+ if (dc->command != DECODE_COMMAND_NONE)
/* Attempt to cancel the current command. If it's too
late and the decoder thread is already executing
the old command, we'll call STOP again in this
function (see below). */
- dc_command(notify, DECODE_COMMAND_STOP);
+ dc_command_locked(dc, DECODE_COMMAND_STOP);
- if (dc.state != DECODE_STATE_STOP && dc.state != DECODE_STATE_ERROR)
- dc_command(notify, DECODE_COMMAND_STOP);
+ if (dc->state != DECODE_STATE_STOP && dc->state != DECODE_STATE_ERROR)
+ dc_command_locked(dc, DECODE_COMMAND_STOP);
+
+ decoder_unlock(dc);
}
bool
-dc_seek(struct notify *notify, double where)
+dc_seek(struct decoder_control *dc, double where)
{
- assert(dc.state != DECODE_STATE_START);
+ assert(dc->state != DECODE_STATE_START);
assert(where >= 0.0);
- if (dc.state == DECODE_STATE_STOP ||
- dc.state == DECODE_STATE_ERROR || !dc.seekable)
+ if (dc->state == DECODE_STATE_STOP ||
+ dc->state == DECODE_STATE_ERROR || !dc->seekable)
return false;
- dc.seek_where = where;
- dc.seek_error = false;
- dc_command(notify, DECODE_COMMAND_SEEK);
+ dc->seek_where = where;
+ dc->seek_error = false;
+ dc_command(dc, DECODE_COMMAND_SEEK);
- if (dc.seek_error)
+ if (dc->seek_error)
return false;
return true;
}
void
-dc_quit(void)
+dc_quit(struct decoder_control *dc)
+{
+ assert(dc->thread != NULL);
+
+ dc->quit = true;
+ dc_command_async(dc, DECODE_COMMAND_STOP);
+
+ g_thread_join(dc->thread);
+ dc->thread = NULL;
+}
+
+void
+dc_mixramp_start(struct decoder_control *dc, char *mixramp_start)
+{
+ assert(dc != NULL);
+
+ g_free(dc->mixramp_start);
+ dc->mixramp_start = mixramp_start;
+ g_debug("mixramp_start = %s", mixramp_start ? mixramp_start : "NULL");
+}
+
+void
+dc_mixramp_end(struct decoder_control *dc, char *mixramp_end)
{
- assert(dc.thread != NULL);
+ assert(dc != NULL);
- dc.quit = true;
- dc_command_async(DECODE_COMMAND_STOP);
+ g_free(dc->mixramp_end);
+ dc->mixramp_end = mixramp_end;
+ g_debug("mixramp_end = %s", mixramp_end ? mixramp_end : "NULL");
+}
+
+void
+dc_mixramp_prev_end(struct decoder_control *dc, char *mixramp_prev_end)
+{
+ assert(dc != NULL);
- g_thread_join(dc.thread);
- dc.thread = NULL;
+ g_free(dc->mixramp_prev_end);
+ dc->mixramp_prev_end = mixramp_prev_end;
+ g_debug("mixramp_prev_end = %s", mixramp_prev_end ? mixramp_prev_end : "NULL");
}
diff --git a/src/decoder_control.h b/src/decoder_control.h
index febf53335..449e974b7 100644
--- a/src/decoder_control.h
+++ b/src/decoder_control.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -22,7 +22,8 @@
#include "decoder_command.h"
#include "audio_format.h"
-#include "notify.h"
+
+#include <glib.h>
#include <assert.h>
@@ -45,14 +46,25 @@ struct decoder_control {
thread isn't running */
GThread *thread;
- struct notify notify;
+ /**
+ * This lock protects #state and #command.
+ */
+ GMutex *mutex;
+
+ /**
+ * Trigger this object after you have modified #command. This
+ * is also used by the decoder thread to notify the caller
+ * when it has finished a command.
+ */
+ GCond *cond;
+
+ enum decoder_state state;
+ enum decoder_command command;
- volatile enum decoder_state state;
- volatile enum decoder_command command;
bool quit;
bool seek_error;
bool seekable;
- volatile double seek_where;
+ double seek_where;
/** the format of the song file */
struct audio_format in_audio_format;
@@ -60,54 +72,145 @@ struct decoder_control {
/** the format being sent to the music pipe */
struct audio_format out_audio_format;
- struct song *current_song;
- struct song *next_song;
+ /**
+ * The song currently being decoded. This attribute is set by
+ * the player thread, when it sends the #DECODE_COMMAND_START
+ * command.
+ */
+ const struct song *song;
+
float total_time;
/** the #music_chunk allocator */
struct music_buffer *buffer;
- /** the destination pipe for decoded chunks */
+ /**
+ * The destination pipe for decoded chunks. The caller thread
+ * owns this object, and is responsible for freeing it.
+ */
struct music_pipe *pipe;
+
+ float replay_gain_db;
+ float replay_gain_prev_db;
+ char *mixramp_start;
+ char *mixramp_end;
+ char *mixramp_prev_end;
};
-extern struct decoder_control dc;
+void
+dc_init(struct decoder_control *dc);
-void dc_init(void);
+void
+dc_deinit(struct decoder_control *dc);
-void dc_deinit(void);
+/**
+ * Locks the #decoder_control object.
+ */
+static inline void
+decoder_lock(struct decoder_control *dc)
+{
+ g_mutex_lock(dc->mutex);
+}
-static inline bool decoder_is_idle(void)
+/**
+ * Unlocks the #decoder_control object.
+ */
+static inline void
+decoder_unlock(struct decoder_control *dc)
{
- return (dc.state == DECODE_STATE_STOP ||
- dc.state == DECODE_STATE_ERROR) &&
- dc.command != DECODE_COMMAND_START;
+ g_mutex_unlock(dc->mutex);
}
-static inline bool decoder_is_starting(void)
+/**
+ * Waits for a signal on the #decoder_control object. This function
+ * is only valid in the decoder thread. The object must be locked
+ * prior to calling this function.
+ */
+static inline void
+decoder_wait(struct decoder_control *dc)
{
- return dc.command == DECODE_COMMAND_START ||
- dc.state == DECODE_STATE_START;
+ g_cond_wait(dc->cond, dc->mutex);
}
-static inline bool decoder_has_failed(void)
+/**
+ * Signals the #decoder_control object. This function is only valid
+ * in the player thread. The object should be locked prior to calling
+ * this function.
+ */
+static inline void
+decoder_signal(struct decoder_control *dc)
{
- assert(dc.command == DECODE_COMMAND_NONE);
+ g_cond_signal(dc->cond);
+}
+
+static inline bool
+decoder_is_idle(const struct decoder_control *dc)
+{
+ return dc->state == DECODE_STATE_STOP ||
+ dc->state == DECODE_STATE_ERROR;
+}
- return dc.state == DECODE_STATE_ERROR;
+static inline bool
+decoder_is_starting(const struct decoder_control *dc)
+{
+ return dc->state == DECODE_STATE_START;
}
-static inline struct song *
-decoder_current_song(void)
+static inline bool
+decoder_has_failed(const struct decoder_control *dc)
{
- switch (dc.state) {
+ assert(dc->command == DECODE_COMMAND_NONE);
+
+ return dc->state == DECODE_STATE_ERROR;
+}
+
+static inline bool
+decoder_lock_is_idle(struct decoder_control *dc)
+{
+ bool ret;
+
+ decoder_lock(dc);
+ ret = decoder_is_idle(dc);
+ decoder_unlock(dc);
+
+ return ret;
+}
+
+static inline bool
+decoder_lock_is_starting(struct decoder_control *dc)
+{
+ bool ret;
+
+ decoder_lock(dc);
+ ret = decoder_is_starting(dc);
+ decoder_unlock(dc);
+
+ return ret;
+}
+
+static inline bool
+decoder_lock_has_failed(struct decoder_control *dc)
+{
+ bool ret;
+
+ decoder_lock(dc);
+ ret = decoder_has_failed(dc);
+ decoder_unlock(dc);
+
+ return ret;
+}
+
+static inline const struct song *
+decoder_current_song(const struct decoder_control *dc)
+{
+ switch (dc->state) {
case DECODE_STATE_STOP:
case DECODE_STATE_ERROR:
return NULL;
case DECODE_STATE_START:
case DECODE_STATE_DECODE:
- return dc.current_song;
+ return dc->song;
}
assert(false);
@@ -115,21 +218,36 @@ decoder_current_song(void)
}
void
-dc_command_wait(struct notify *notify);
+dc_command_wait(struct decoder_control *dc);
+/**
+ * Start the decoder.
+ *
+ * @param the decoder
+ * @param song the song to be decoded
+ * @param pipe the pipe which receives the decoded chunks (owned by
+ * the caller)
+ */
void
-dc_start(struct notify *notify, struct song *song, struct music_pipe *pipe);
+dc_start(struct decoder_control *dc, struct song *song,
+ struct music_buffer *buffer, struct music_pipe *pipe);
void
-dc_start_async(struct song *song, struct music_pipe *pipe);
+dc_stop(struct decoder_control *dc);
+
+bool
+dc_seek(struct decoder_control *dc, double where);
void
-dc_stop(struct notify *notify);
+dc_quit(struct decoder_control *dc);
-bool
-dc_seek(struct notify *notify, double where);
+void
+dc_mixramp_start(struct decoder_control *dc, char *mixramp_start);
+
+void
+dc_mixramp_end(struct decoder_control *dc, char *mixramp_end);
void
-dc_quit(void);
+dc_mixramp_prev_end(struct decoder_control *dc, char *mixramp_prev_end);
#endif
diff --git a/src/decoder_internal.c b/src/decoder_internal.c
index 4a56fa5f3..990d728e9 100644
--- a/src/decoder_internal.c
+++ b/src/decoder_internal.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -17,6 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "decoder_internal.h"
#include "decoder_control.h"
#include "player_control.h"
@@ -28,21 +29,45 @@
#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 input_stream *is, bool do_wait)
+need_chunks(struct decoder_control *dc, struct input_stream *is, bool do_wait)
{
- if (dc.command == DECODE_COMMAND_STOP ||
- dc.command == DECODE_COMMAND_SEEK)
- return dc.command;
+ if (dc->command == DECODE_COMMAND_STOP ||
+ dc->command == DECODE_COMMAND_SEEK)
+ return dc->command;
- if ((is == NULL || input_stream_buffer(is) <= 0) && do_wait) {
- notify_wait(&dc.notify);
- notify_signal(&pc.notify);
+ if ((is == NULL || !decoder_input_buffer(dc, is)) && do_wait) {
+ decoder_wait(dc);
+ player_signal();
- return dc.command;
+ return dc->command;
}
return DECODE_COMMAND_NONE;
@@ -51,6 +76,7 @@ need_chunks(struct input_stream *is, bool do_wait)
struct music_chunk *
decoder_get_chunk(struct decoder *decoder, struct input_stream *is)
{
+ struct decoder_control *dc = decoder->dc;
enum decoder_command cmd;
assert(decoder != NULL);
@@ -59,11 +85,20 @@ decoder_get_chunk(struct decoder *decoder, struct input_stream *is)
return decoder->chunk;
do {
- decoder->chunk = music_buffer_allocate(dc.buffer);
- if (decoder->chunk != NULL)
+ decoder->chunk = music_buffer_allocate(dc->buffer);
+ if (decoder->chunk != NULL) {
+ decoder->chunk->replay_gain_serial =
+ decoder->replay_gain_serial;
+ if (decoder->replay_gain_serial != 0)
+ decoder->chunk->replay_gain_info =
+ decoder->replay_gain_info;
+
return decoder->chunk;
+ }
- cmd = need_chunks(is, true);
+ decoder_lock(dc);
+ cmd = need_chunks(dc, is, true);
+ decoder_unlock(dc);
} while (cmd == DECODE_COMMAND_NONE);
return NULL;
@@ -72,13 +107,15 @@ decoder_get_chunk(struct decoder *decoder, struct input_stream *is)
void
decoder_flush_chunk(struct decoder *decoder)
{
+ struct decoder_control *dc = decoder->dc;
+
assert(decoder != NULL);
assert(decoder->chunk != NULL);
if (music_chunk_is_empty(decoder->chunk))
- music_buffer_return(dc.buffer, decoder->chunk);
+ music_buffer_return(dc->buffer, decoder->chunk);
else
- music_pipe_push(dc.pipe, decoder->chunk);
+ music_pipe_push(dc->pipe, decoder->chunk);
decoder->chunk = NULL;
}
diff --git a/src/decoder_internal.h b/src/decoder_internal.h
index cf54dbf6d..9e7e2037a 100644
--- a/src/decoder_internal.h
+++ b/src/decoder_internal.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -22,12 +22,20 @@
#include "decoder_command.h"
#include "pcm_convert.h"
+#include "replay_gain_info.h"
struct input_stream;
struct decoder {
+ struct decoder_control *dc;
+
struct pcm_convert_state conv_state;
+ /**
+ * The time stamp of the next data chunk, in seconds.
+ */
+ double timestamp;
+
bool seeking;
/**
@@ -45,6 +53,14 @@ struct decoder {
/** the chunk currently being written to */
struct music_chunk *chunk;
+
+ struct replay_gain_info replay_gain_info;
+
+ /**
+ * A positive serial number for checking if replay gain info
+ * has changed since the last check.
+ */
+ unsigned replay_gain_serial;
};
/**
diff --git a/src/decoder_list.c b/src/decoder_list.c
index a42585e34..d76050023 100644
--- a/src/decoder_list.c
+++ b/src/decoder_list.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -17,20 +17,23 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "decoder_list.h"
#include "decoder_plugin.h"
#include "utils.h"
-#include "config.h"
#include "conf.h"
+#include "mpd_error.h"
#include <glib.h>
#include <string.h>
extern const struct decoder_plugin mad_decoder_plugin;
+extern const struct decoder_plugin mpg123_decoder_plugin;
extern const struct decoder_plugin vorbis_decoder_plugin;
extern const struct decoder_plugin flac_decoder_plugin;
extern const struct decoder_plugin oggflac_decoder_plugin;
+extern const struct decoder_plugin sndfile_decoder_plugin;
extern const struct decoder_plugin audiofile_decoder_plugin;
extern const struct decoder_plugin mp4ff_decoder_plugin;
extern const struct decoder_plugin faad_decoder_plugin;
@@ -42,11 +45,15 @@ extern const struct decoder_plugin sidplay_decoder_plugin;
extern const struct decoder_plugin wildmidi_decoder_plugin;
extern const struct decoder_plugin fluidsynth_decoder_plugin;
extern const struct decoder_plugin ffmpeg_decoder_plugin;
+extern const struct decoder_plugin gme_decoder_plugin;
-static const struct decoder_plugin *const decoder_plugins[] = {
+const struct decoder_plugin *const decoder_plugins[] = {
#ifdef HAVE_MAD
&mad_decoder_plugin,
#endif
+#ifdef HAVE_MPG123
+ &mpg123_decoder_plugin,
+#endif
#ifdef ENABLE_VORBIS_DECODER
&vorbis_decoder_plugin,
#endif
@@ -56,6 +63,9 @@ static const struct decoder_plugin *const decoder_plugins[] = {
#ifdef HAVE_FLAC
&flac_decoder_plugin,
#endif
+#ifdef ENABLE_SNDFILE
+ &sndfile_decoder_plugin,
+#endif
#ifdef HAVE_AUDIOFILE
&audiofile_decoder_plugin,
#endif
@@ -89,32 +99,51 @@ static const struct decoder_plugin *const decoder_plugins[] = {
#ifdef HAVE_FFMPEG
&ffmpeg_decoder_plugin,
#endif
+#ifdef HAVE_GME
+ &gme_decoder_plugin,
+#endif
+ NULL
};
enum {
- num_decoder_plugins = G_N_ELEMENTS(decoder_plugins),
+ num_decoder_plugins = G_N_ELEMENTS(decoder_plugins) - 1,
};
/** which plugins have been initialized successfully? */
-static bool decoder_plugins_enabled[num_decoder_plugins];
+bool decoder_plugins_enabled[num_decoder_plugins];
-const struct decoder_plugin *
-decoder_plugin_from_suffix(const char *suffix, unsigned int next)
+static unsigned
+decoder_plugin_index(const struct decoder_plugin *plugin)
{
- static unsigned i = num_decoder_plugins;
+ unsigned i = 0;
+
+ while (decoder_plugins[i] != plugin)
+ ++i;
+ return i;
+}
+
+static unsigned
+decoder_plugin_next_index(const struct decoder_plugin *plugin)
+{
+ return plugin == 0
+ ? 0 /* start with first plugin */
+ : decoder_plugin_index(plugin) + 1;
+}
+
+const struct decoder_plugin *
+decoder_plugin_from_suffix(const char *suffix,
+ const struct decoder_plugin *plugin)
+{
if (suffix == NULL)
return NULL;
- if (!next)
- i = 0;
- for (; i < num_decoder_plugins; ++i) {
- const struct decoder_plugin *plugin = decoder_plugins[i];
+ for (unsigned i = decoder_plugin_next_index(plugin);
+ decoder_plugins[i] != NULL; ++i) {
+ plugin = decoder_plugins[i];
if (decoder_plugins_enabled[i] &&
- stringFoundInStringArray(plugin->suffixes, suffix)) {
- ++i;
+ decoder_plugin_supports_suffix(plugin, suffix))
return plugin;
- }
}
return NULL;
@@ -130,10 +159,10 @@ decoder_plugin_from_mime_type(const char *mimeType, unsigned int next)
if (!next)
i = 0;
- for (; i < num_decoder_plugins; ++i) {
+ for (; decoder_plugins[i] != NULL; ++i) {
const struct decoder_plugin *plugin = decoder_plugins[i];
if (decoder_plugins_enabled[i] &&
- stringFoundInStringArray(plugin->mime_types, mimeType)) {
+ decoder_plugin_supports_mime_type(plugin, mimeType)) {
++i;
return plugin;
}
@@ -145,7 +174,7 @@ decoder_plugin_from_mime_type(const char *mimeType, unsigned int next)
const struct decoder_plugin *
decoder_plugin_from_name(const char *name)
{
- for (unsigned i = 0; i < num_decoder_plugins; ++i) {
+ for (unsigned i = 0; decoder_plugins[i] != NULL; ++i) {
const struct decoder_plugin *plugin = decoder_plugins[i];
if (decoder_plugins_enabled[i] &&
strcmp(plugin->name, name) == 0)
@@ -155,27 +184,6 @@ decoder_plugin_from_name(const char *name)
return NULL;
}
-void decoder_plugin_print_all_decoders(FILE * fp)
-{
- for (unsigned i = 0; i < num_decoder_plugins; ++i) {
- const struct decoder_plugin *plugin = decoder_plugins[i];
- const char *const*suffixes;
-
- if (!decoder_plugins_enabled[i])
- continue;
-
- fprintf(fp, "[%s]", plugin->name);
-
- for (suffixes = plugin->suffixes;
- suffixes != NULL && *suffixes != NULL;
- ++suffixes) {
- fprintf(fp, " %s", *suffixes);
- }
-
- fprintf(fp, "\n");
- }
-}
-
/**
* Find the "decoder" configuration block for the specified plugin.
*
@@ -191,8 +199,8 @@ decoder_plugin_config(const char *plugin_name)
const char *name =
config_get_block_string(param, "plugin", NULL);
if (name == NULL)
- g_error("decoder configuration without 'plugin' name in line %d",
- param->line);
+ MPD_ERROR("decoder configuration without 'plugin' name in line %d",
+ param->line);
if (strcmp(name, plugin_name) == 0)
return param;
@@ -203,7 +211,7 @@ decoder_plugin_config(const char *plugin_name)
void decoder_plugin_init_all(void)
{
- for (unsigned i = 0; i < num_decoder_plugins; ++i) {
+ for (unsigned i = 0; decoder_plugins[i] != NULL; ++i) {
const struct decoder_plugin *plugin = decoder_plugins[i];
const struct config_param *param =
decoder_plugin_config(plugin->name);
@@ -219,7 +227,7 @@ void decoder_plugin_init_all(void)
void decoder_plugin_deinit_all(void)
{
- for (unsigned i = 0; i < num_decoder_plugins; ++i) {
+ for (unsigned i = 0; decoder_plugins[i] != NULL; ++i) {
const struct decoder_plugin *plugin = decoder_plugins[i];
if (decoder_plugins_enabled[i])
diff --git a/src/decoder_list.h b/src/decoder_list.h
index 23788189c..7041db0c9 100644
--- a/src/decoder_list.h
+++ b/src/decoder_list.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -20,14 +20,25 @@
#ifndef MPD_DECODER_LIST_H
#define MPD_DECODER_LIST_H
-#include <stdio.h>
+#include <stdbool.h>
struct decoder_plugin;
+extern const struct decoder_plugin *const decoder_plugins[];
+extern bool decoder_plugins_enabled[];
+
/* interface for using plugins */
+/**
+ * Find the next enabled decoder plugin which supports the specified suffix.
+ *
+ * @param suffix the file name suffix
+ * @param plugin the previous plugin, or NULL to find the first plugin
+ * @return a plugin, or NULL if none matches
+ */
const struct decoder_plugin *
-decoder_plugin_from_suffix(const char *suffix, unsigned int next);
+decoder_plugin_from_suffix(const char *suffix,
+ const struct decoder_plugin *plugin);
const struct decoder_plugin *
decoder_plugin_from_mime_type(const char *mimeType, unsigned int next);
@@ -35,8 +46,6 @@ decoder_plugin_from_mime_type(const char *mimeType, unsigned int next);
const struct decoder_plugin *
decoder_plugin_from_name(const char *name);
-void decoder_plugin_print_all_decoders(FILE * fp);
-
/* this is where we "load" all the "plugins" ;-) */
void decoder_plugin_init_all(void);
diff --git a/src/decoder_plugin.c b/src/decoder_plugin.c
new file mode 100644
index 000000000..062dad364
--- /dev/null
+++ b/src/decoder_plugin.c
@@ -0,0 +1,47 @@
+/*
+ * 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 "decoder_plugin.h"
+#include "utils.h"
+
+#include <assert.h>
+
+bool
+decoder_plugin_supports_suffix(const struct decoder_plugin *plugin,
+ const char *suffix)
+{
+ assert(plugin != NULL);
+ assert(suffix != NULL);
+
+ return plugin->suffixes != NULL &&
+ string_array_contains(plugin->suffixes, suffix);
+
+}
+
+bool
+decoder_plugin_supports_mime_type(const struct decoder_plugin *plugin,
+ const char *mime_type)
+{
+ assert(plugin != NULL);
+ assert(mime_type != NULL);
+
+ return plugin->mime_types != NULL &&
+ string_array_contains(plugin->mime_types, mime_type);
+}
diff --git a/src/decoder_plugin.h b/src/decoder_plugin.h
index 66501a0a1..d8371ddb8 100644
--- a/src/decoder_plugin.h
+++ b/src/decoder_plugin.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -77,6 +77,13 @@ struct decoder_plugin {
struct tag *(*tag_dup)(const char *path_fs);
/**
+ * Read the tags of a stream.
+ *
+ * @return NULL if the operation has failed
+ */
+ struct tag *(*stream_tag)(struct input_stream *is);
+
+ /**
* @brief Return a "virtual" filename for subtracks in
* container formats like flac
* @param const char* pathname full pathname for the file on fs
@@ -147,7 +154,21 @@ static inline struct tag *
decoder_plugin_tag_dup(const struct decoder_plugin *plugin,
const char *path_fs)
{
- return plugin->tag_dup(path_fs);
+ return plugin->tag_dup != NULL
+ ? plugin->tag_dup(path_fs)
+ : NULL;
+}
+
+/**
+ * Read the tag of a stream.
+ */
+static inline struct tag *
+decoder_plugin_stream_tag(const struct decoder_plugin *plugin,
+ struct input_stream *is)
+{
+ return plugin->stream_tag != NULL
+ ? plugin->stream_tag(is)
+ : NULL;
}
/**
@@ -161,4 +182,18 @@ decoder_plugin_container_scan( const struct decoder_plugin *plugin,
return plugin->container_scan(pathname, tnum);
}
+/**
+ * Does the plugin announce the specified file name suffix?
+ */
+bool
+decoder_plugin_supports_suffix(const struct decoder_plugin *plugin,
+ const char *suffix);
+
+/**
+ * Does the plugin announce the specified MIME type?
+ */
+bool
+decoder_plugin_supports_mime_type(const struct decoder_plugin *plugin,
+ const char *mime_type);
+
#endif
diff --git a/src/decoder_print.c b/src/decoder_print.c
new file mode 100644
index 000000000..a1c2da2e5
--- /dev/null
+++ b/src/decoder_print.c
@@ -0,0 +1,54 @@
+/*
+ * 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 "decoder_print.h"
+#include "decoder_list.h"
+#include "decoder_plugin.h"
+#include "client.h"
+
+#include <assert.h>
+
+static void
+decoder_plugin_print(struct client *client,
+ const struct decoder_plugin *plugin)
+{
+ const char *const*p;
+
+ assert(plugin != NULL);
+ assert(plugin->name != NULL);
+
+ client_printf(client, "plugin: %s\n", plugin->name);
+
+ if (plugin->suffixes != NULL)
+ for (p = plugin->suffixes; *p != NULL; ++p)
+ client_printf(client, "suffix: %s\n", *p);
+
+ if (plugin->mime_types != NULL)
+ for (p = plugin->mime_types; *p != NULL; ++p)
+ client_printf(client, "mime_type: %s\n", *p);
+}
+
+void
+decoder_list_print(struct client *client)
+{
+ for (unsigned i = 0; decoder_plugins[i] != NULL; ++i)
+ if (decoder_plugins_enabled[i])
+ decoder_plugin_print(client, decoder_plugins[i]);
+}
diff --git a/src/decoder_print.h b/src/decoder_print.h
new file mode 100644
index 000000000..520438871
--- /dev/null
+++ b/src/decoder_print.h
@@ -0,0 +1,28 @@
+/*
+ * 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_DECODER_PRINT_H
+#define MPD_DECODER_PRINT_H
+
+struct client;
+
+void
+decoder_list_print(struct client *client);
+
+#endif
diff --git a/src/decoder_thread.c b/src/decoder_thread.c
index cbb670616..10a796967 100644
--- a/src/decoder_thread.c
+++ b/src/decoder_thread.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -17,11 +17,14 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "decoder_thread.h"
#include "decoder_control.h"
#include "decoder_internal.h"
#include "decoder_list.h"
#include "decoder_plugin.h"
+#include "decoder_api.h"
+#include "replay_gain_ape.h"
#include "input_stream.h"
#include "player_control.h"
#include "pipe.h"
@@ -30,11 +33,73 @@
#include "mapper.h"
#include "path.h"
#include "uri.h"
+#include "mpd_error.h"
#include <glib.h>
#include <unistd.h>
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "decoder_thread"
+
+static enum decoder_command
+decoder_lock_get_command(struct decoder_control *dc)
+{
+ enum decoder_command command;
+
+ decoder_lock(dc);
+ command = dc->command;
+ decoder_unlock(dc);
+
+ return command;
+}
+
+/**
+ * Opens the input stream with input_stream_open(), and waits until
+ * the stream gets ready. If a decoder STOP command is received
+ * during that, it cancels the operation (but does not close the
+ * stream).
+ *
+ * Unlock the decoder before calling this function.
+ *
+ * @return an input_stream on success or if #DECODE_COMMAND_STOP is
+ * received, NULL on error
+ */
+static struct input_stream *
+decoder_input_stream_open(struct decoder_control *dc, const char *uri)
+{
+ GError *error = NULL;
+ struct input_stream *is;
+
+ is = input_stream_open(uri, &error);
+ if (is == NULL) {
+ if (error != NULL) {
+ g_warning("%s", error->message);
+ g_error_free(error);
+ }
+
+ return NULL;
+ }
+
+ /* wait for the input stream to become ready; its metadata
+ will be available then */
+
+ while (!is->ready &&
+ decoder_lock_get_command(dc) != DECODE_COMMAND_STOP) {
+ int ret;
+
+ ret = input_stream_buffer(is, &error);
+ if (ret < 0) {
+ input_stream_close(is);
+ g_warning("%s", error->message);
+ g_error_free(error);
+ return NULL;
+ }
+ }
+
+ return is;
+}
+
static bool
decoder_stream_decode(const struct decoder_plugin *plugin,
struct decoder *decoder,
@@ -47,17 +112,24 @@ decoder_stream_decode(const struct decoder_plugin *plugin,
assert(decoder->decoder_tag == NULL);
assert(input_stream != NULL);
assert(input_stream->ready);
- assert(dc.state == DECODE_STATE_START);
+ assert(decoder->dc->state == DECODE_STATE_START);
+
+ 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);
+ input_stream_seek(input_stream, 0, SEEK_SET, NULL);
decoder_plugin_stream_decode(plugin, decoder, input_stream);
- assert(dc.state == DECODE_STATE_START ||
- dc.state == DECODE_STATE_DECODE);
+ decoder_lock(decoder->dc);
+
+ assert(decoder->dc->state == DECODE_STATE_START ||
+ decoder->dc->state == DECODE_STATE_DECODE);
- return dc.state != DECODE_STATE_START;
+ return decoder->dc->state != DECODE_STATE_START;
}
static bool
@@ -70,155 +142,264 @@ decoder_file_decode(const struct decoder_plugin *plugin,
assert(decoder->stream_tag == NULL);
assert(decoder->decoder_tag == NULL);
assert(path != NULL);
- assert(path[0] == '/');
- assert(dc.state == DECODE_STATE_START);
+ assert(g_path_is_absolute(path));
+ assert(decoder->dc->state == DECODE_STATE_START);
+
+ if (decoder->dc->command == DECODE_COMMAND_STOP)
+ return true;
+
+ decoder_unlock(decoder->dc);
decoder_plugin_file_decode(plugin, decoder, path);
- assert(dc.state == DECODE_STATE_START ||
- dc.state == DECODE_STATE_DECODE);
+ decoder_lock(decoder->dc);
- return dc.state != DECODE_STATE_START;
+ assert(decoder->dc->state == DECODE_STATE_START ||
+ decoder->dc->state == DECODE_STATE_DECODE);
+
+ return decoder->dc->state != DECODE_STATE_START;
}
-static void decoder_run_song(const struct song *song, const char *uri)
+/**
+ * Hack to allow tracking const decoder plugins in a GSList.
+ */
+static inline gpointer
+deconst_plugin(const struct decoder_plugin *plugin)
{
- struct decoder decoder;
- int ret;
- bool close_instream = true;
- struct input_stream input_stream;
+ union {
+ const struct decoder_plugin *in;
+ gpointer out;
+ } u = { .in = plugin };
+
+ return u.out;
+}
+
+/**
+ * Try decoding a stream, using plugins matching the stream's MIME type.
+ *
+ * @param tried_r a list of plugins which were tried
+ */
+static bool
+decoder_run_stream_mime_type(struct decoder *decoder, struct input_stream *is,
+ GSList **tried_r)
+{
+ assert(tried_r != NULL);
+
const struct decoder_plugin *plugin;
+ unsigned int next = 0;
- close_instream = input_stream_open(&input_stream, uri);
- if (!close_instream && !song_is_file(song)) {
- dc.state = DECODE_STATE_ERROR;
- return;
+ if (is->mime == NULL)
+ return false;
+
+ while ((plugin = decoder_plugin_from_mime_type(is->mime, next++))) {
+ if (plugin->stream_decode == NULL)
+ continue;
+
+ if (g_slist_find(*tried_r, plugin) != NULL)
+ /* don't try a plugin twice */
+ continue;
+
+ if (decoder_stream_decode(plugin, decoder, is))
+ return true;
+
+ *tried_r = g_slist_prepend(*tried_r, deconst_plugin(plugin));
}
- decoder.seeking = false;
- decoder.song_tag = song->tag != NULL && song_is_file(song)
- ? tag_dup(song->tag) : NULL;
- decoder.stream_tag = NULL;
- decoder.decoder_tag = NULL;
- decoder.chunk = NULL;
+ return false;
+}
- dc.state = DECODE_STATE_START;
- dc.command = DECODE_COMMAND_NONE;
- notify_signal(&pc.notify);
+/**
+ * Try decoding a stream, using plugins matching the stream's URI
+ * suffix.
+ *
+ * @param tried_r a list of plugins which were tried
+ */
+static bool
+decoder_run_stream_suffix(struct decoder *decoder, struct input_stream *is,
+ const char *uri, GSList **tried_r)
+{
+ assert(tried_r != NULL);
- /* wait for the input stream to become ready; its metadata
- will be available then */
+ const char *suffix = uri_get_suffix(uri);
+ const struct decoder_plugin *plugin = NULL;
- while (close_instream && !input_stream.ready) {
- if (dc.command == DECODE_COMMAND_STOP) {
- input_stream_close(&input_stream);
- dc.state = DECODE_STATE_STOP;
- return;
- }
+ if (suffix == NULL)
+ return false;
- ret = input_stream_buffer(&input_stream);
- if (ret < 0) {
- input_stream_close(&input_stream);
- dc.state = DECODE_STATE_ERROR;
- return;
- }
+ while ((plugin = decoder_plugin_from_suffix(suffix, plugin)) != NULL) {
+ if (plugin->stream_decode == NULL)
+ continue;
+
+ if (g_slist_find(*tried_r, plugin) != NULL)
+ /* don't try a plugin twice */
+ continue;
+
+ if (decoder_stream_decode(plugin, decoder, is))
+ return true;
+
+ *tried_r = g_slist_prepend(*tried_r, deconst_plugin(plugin));
}
- if (dc.command == DECODE_COMMAND_STOP) {
- if (close_instream)
- input_stream_close(&input_stream);
- dc.state = DECODE_STATE_STOP;
- return;
+ return false;
+}
+
+/**
+ * Try decoding a stream, using the fallback plugin.
+ */
+static bool
+decoder_run_stream_fallback(struct decoder *decoder, struct input_stream *is)
+{
+ const struct decoder_plugin *plugin;
+
+ plugin = decoder_plugin_from_name("mad");
+ return plugin != NULL && plugin->stream_decode != NULL &&
+ decoder_stream_decode(plugin, decoder, is);
+}
+
+/**
+ * Try decoding a stream.
+ */
+static bool
+decoder_run_stream(struct decoder *decoder, const char *uri)
+{
+ struct decoder_control *dc = decoder->dc;
+ struct input_stream *input_stream;
+ bool success;
+
+ decoder_unlock(dc);
+
+ input_stream = decoder_input_stream_open(dc, uri);
+ if (input_stream == NULL) {
+ decoder_lock(dc);
+ return false;
}
- pcm_convert_init(&decoder.conv_state);
+ decoder_lock(dc);
- ret = false;
- if (!song_is_file(song)) {
- unsigned int next = 0;
+ GSList *tried = NULL;
+ success = dc->command == DECODE_COMMAND_STOP ||
/* first we try mime types: */
- while ((plugin = decoder_plugin_from_mime_type(input_stream.mime, next++))) {
- if (plugin->stream_decode == NULL)
+ decoder_run_stream_mime_type(decoder, input_stream, &tried) ||
+ /* if that fails, try suffix matching the URL: */
+ decoder_run_stream_suffix(decoder, input_stream, uri,
+ &tried) ||
+ /* fallback to mp3: this is needed for bastard streams
+ that don't have a suffix or set the mimeType */
+ (tried == NULL &&
+ decoder_run_stream_fallback(decoder, input_stream));
+
+ g_slist_free(tried);
+
+ decoder_unlock(dc);
+ input_stream_close(input_stream);
+ decoder_lock(dc);
+
+ return success;
+}
+
+/**
+ * Attempt to load replay gain data, and pass it to
+ * decoder_replay_gain().
+ */
+static void
+decoder_load_replay_gain(struct decoder *decoder, const char *path_fs)
+{
+ struct replay_gain_info info;
+ if (replay_gain_ape_read(path_fs, &info))
+ decoder_replay_gain(decoder, &info);
+}
+
+/**
+ * Try decoding a file.
+ */
+static bool
+decoder_run_file(struct decoder *decoder, const char *path_fs)
+{
+ struct decoder_control *dc = decoder->dc;
+ const char *suffix = uri_get_suffix(path_fs);
+ const struct decoder_plugin *plugin = NULL;
+
+ if (suffix == NULL)
+ return false;
+
+ decoder_unlock(dc);
+
+ decoder_load_replay_gain(decoder, path_fs);
+
+ while ((plugin = decoder_plugin_from_suffix(suffix, plugin)) != NULL) {
+ if (plugin->file_decode != NULL) {
+ decoder_lock(dc);
+
+ if (decoder_file_decode(plugin, decoder, path_fs))
+ return true;
+
+ decoder_unlock(dc);
+ } else if (plugin->stream_decode != NULL) {
+ struct input_stream *input_stream;
+ bool success;
+
+ input_stream = decoder_input_stream_open(dc, path_fs);
+ if (input_stream == NULL)
continue;
- ret = decoder_stream_decode(plugin, &decoder,
- &input_stream);
- if (ret)
- break;
- plugin = NULL;
- }
+ decoder_lock(dc);
- /* if that fails, try suffix matching the URL: */
- if (plugin == NULL) {
- const char *s = uri_get_suffix(uri);
- next = 0;
- while ((plugin = decoder_plugin_from_suffix(s, next++))) {
- if (plugin->stream_decode == NULL)
- continue;
- ret = decoder_stream_decode(plugin, &decoder,
- &input_stream);
- if (ret)
- break;
-
- assert(dc.state == DECODE_STATE_START);
- plugin = NULL;
- }
- }
- /* fallback to mp3: */
- /* this is needed for bastard streams that don't have a suffix
- or set the mimeType */
- if (plugin == NULL) {
- /* we already know our mp3Plugin supports streams, no
- * need to check for stream{Types,DecodeFunc} */
- if ((plugin = decoder_plugin_from_name("mad"))) {
- ret = decoder_stream_decode(plugin, &decoder,
- &input_stream);
- }
- }
- } else {
- unsigned int next = 0;
- const char *s = uri_get_suffix(uri);
- while ((plugin = decoder_plugin_from_suffix(s, next++))) {
- if (plugin->file_decode != NULL) {
- if (close_instream) {
- input_stream_close(&input_stream);
- close_instream = false;
- }
-
- ret = decoder_file_decode(plugin,
- &decoder, uri);
- if (ret)
- break;
- } else if (plugin->stream_decode != NULL) {
- if (!close_instream) {
- /* the input_stream object has
- been closed before
- decoder_file_decode() -
- reopen it */
- if (input_stream_open(&input_stream, uri))
- close_instream = true;
- else
- continue;
- }
-
- ret = decoder_stream_decode(plugin, &decoder,
- &input_stream);
- if (ret)
- break;
+ success = decoder_stream_decode(plugin, decoder,
+ input_stream);
+
+ decoder_unlock(dc);
+
+ input_stream_close(input_stream);
+
+ if (success) {
+ decoder_lock(dc);
+ return true;
}
}
}
+ decoder_lock(dc);
+ return false;
+}
+
+static void
+decoder_run_song(struct decoder_control *dc,
+ const struct song *song, const char *uri)
+{
+ struct decoder decoder = {
+ .dc = dc,
+ };
+ int ret;
+
+ decoder.timestamp = 0.0;
+ decoder.seeking = false;
+ decoder.song_tag = song->tag != NULL && song_is_file(song)
+ ? tag_dup(song->tag) : NULL;
+ decoder.stream_tag = NULL;
+ decoder.decoder_tag = NULL;
+ decoder.chunk = NULL;
+
+ dc->state = DECODE_STATE_START;
+ dc->command = DECODE_COMMAND_NONE;
+
+ player_signal();
+
+ pcm_convert_init(&decoder.conv_state);
+
+ ret = song_is_file(song)
+ ? decoder_run_file(&decoder, uri)
+ : decoder_run_stream(&decoder, uri);
+
+ decoder_unlock(dc);
+
pcm_convert_deinit(&decoder.conv_state);
/* flush the last chunk */
+
if (decoder.chunk != NULL)
decoder_flush_chunk(&decoder);
- if (close_instream)
- input_stream_close(&input_stream);
-
if (decoder.song_tag != NULL)
tag_free(decoder.song_tag);
@@ -228,66 +409,91 @@ static void decoder_run_song(const struct song *song, const char *uri)
if (decoder.decoder_tag != NULL)
tag_free(decoder.decoder_tag);
- dc.state = ret ? DECODE_STATE_STOP : DECODE_STATE_ERROR;
+ decoder_lock(dc);
+
+ dc->state = ret ? DECODE_STATE_STOP : DECODE_STATE_ERROR;
}
-static void decoder_run(void)
+static void
+decoder_run(struct decoder_control *dc)
{
- struct song *song = dc.next_song;
+ const struct song *song = dc->song;
char *uri;
+ assert(song != NULL);
+
if (song_is_file(song))
uri = map_song_fs(song);
else
uri = song_get_uri(song);
if (uri == NULL) {
- dc.state = DECODE_STATE_ERROR;
+ dc->state = DECODE_STATE_ERROR;
return;
}
- dc.current_song = dc.next_song; /* NEED LOCK */
- decoder_run_song(song, uri);
+ decoder_run_song(dc, song, uri);
g_free(uri);
}
-static gpointer decoder_task(G_GNUC_UNUSED gpointer arg)
+static gpointer
+decoder_task(gpointer arg)
{
+ struct decoder_control *dc = arg;
+
+ decoder_lock(dc);
+
do {
- assert(dc.state == DECODE_STATE_STOP ||
- dc.state == DECODE_STATE_ERROR);
+ assert(dc->state == DECODE_STATE_STOP ||
+ dc->state == DECODE_STATE_ERROR);
- switch (dc.command) {
+ switch (dc->command) {
case DECODE_COMMAND_START:
+ g_debug("clearing mixramp tags");
+ dc_mixramp_start(dc, NULL);
+ dc_mixramp_prev_end(dc, dc->mixramp_end);
+ dc->mixramp_end = NULL; /* Don't free, it's copied above. */
+ dc->replay_gain_prev_db = dc->replay_gain_db;
+ dc->replay_gain_db = 0;
+
+ /* fall through */
+
case DECODE_COMMAND_SEEK:
- decoder_run();
+ decoder_run(dc);
- dc.command = DECODE_COMMAND_NONE;
- notify_signal(&pc.notify);
+ dc->command = DECODE_COMMAND_NONE;
+
+ player_signal();
break;
case DECODE_COMMAND_STOP:
- dc.command = DECODE_COMMAND_NONE;
- notify_signal(&pc.notify);
+ dc->command = DECODE_COMMAND_NONE;
+
+ player_signal();
break;
case DECODE_COMMAND_NONE:
- notify_wait(&dc.notify);
+ decoder_wait(dc);
break;
}
- } while (dc.command != DECODE_COMMAND_NONE || !dc.quit);
+ } while (dc->command != DECODE_COMMAND_NONE || !dc->quit);
+
+ decoder_unlock(dc);
return NULL;
}
-void decoder_thread_start(void)
+void
+decoder_thread_start(struct decoder_control *dc)
{
GError *e = NULL;
- assert(dc.thread == NULL);
+ assert(dc->thread == NULL);
+
+ dc->quit = false;
- dc.thread = g_thread_create(decoder_task, NULL, true, &e);
- if (dc.thread == NULL)
- g_error("Failed to spawn decoder task: %s", e->message);
+ dc->thread = g_thread_create(decoder_task, dc, true, &e);
+ if (dc->thread == NULL)
+ MPD_ERROR("Failed to spawn decoder task: %s", e->message);
}
diff --git a/src/decoder_thread.h b/src/decoder_thread.h
index 50ed7116e..28042d7f8 100644
--- a/src/decoder_thread.h
+++ b/src/decoder_thread.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -20,6 +20,9 @@
#ifndef MPD_DECODER_THREAD_H
#define MPD_DECODER_THREAD_H
-void decoder_thread_start(void);
+struct decoder_control;
+
+void
+decoder_thread_start(struct decoder_control *dc);
#endif
diff --git a/src/directory.c b/src/directory.c
index ef8c038a3..fa15d41b1 100644
--- a/src/directory.c
+++ b/src/directory.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -17,6 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "directory.h"
#include "song.h"
#include "path.h"
@@ -41,12 +42,16 @@ directory_new(const char *path, struct directory *parent)
directory->parent = parent;
memcpy(directory->path, path, pathlen + 1);
+ playlist_vector_init(&directory->playlists);
+
return directory;
}
void
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]);
diff --git a/src/directory.h b/src/directory.h
index ac2310429..69e2b3638 100644
--- a/src/directory.h
+++ b/src/directory.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -20,8 +20,10 @@
#ifndef MPD_DIRECTORY_H
#define MPD_DIRECTORY_H
+#include "check.h"
#include "dirvec.h"
#include "songvec.h"
+#include "playlist_vector.h"
#include <stdbool.h>
#include <sys/types.h>
@@ -34,6 +36,9 @@
struct directory {
struct dirvec children;
struct songvec songs;
+
+ struct playlist_vector playlists;
+
struct directory *parent;
time_t mtime;
ino_t inode;
diff --git a/src/directory_print.c b/src/directory_print.c
index 1c9f23d69..74ff26cb3 100644
--- a/src/directory_print.c
+++ b/src/directory_print.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -17,10 +17,19 @@
* 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)
@@ -32,9 +41,34 @@ dirvec_print(struct client *client, const struct dirvec *dv)
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_print.h b/src/directory_print.h
index 7c0110502..0933f5a97 100644
--- a/src/directory_print.h
+++ b/src/directory_print.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
diff --git a/src/directory_save.c b/src/directory_save.c
index 132508447..55896c289 100644
--- a/src/directory_save.c
+++ b/src/directory_save.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -17,11 +17,13 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "directory_save.h"
#include "directory.h"
#include "song.h"
-#include "path.h"
+#include "text_file.h"
#include "song_save.h"
+#include "playlist_database.h"
#include <assert.h>
#include <string.h>
@@ -39,109 +41,152 @@ directory_quark(void)
return g_quark_from_static_string("directory");
}
-/* TODO error checking */
-int
+void
directory_save(FILE *fp, struct directory *directory)
{
struct dirvec *children = &directory->children;
size_t i;
- int retv;
if (!directory_is_root(directory)) {
fprintf(fp, DIRECTORY_MTIME "%lu\n",
(unsigned long)directory->mtime);
- retv = fprintf(fp, "%s%s\n", DIRECTORY_BEGIN,
- directory_get_path(directory));
- if (retv < 0)
- return -1;
+ fprintf(fp, "%s%s\n", DIRECTORY_BEGIN,
+ directory_get_path(directory));
}
for (i = 0; i < children->nr; ++i) {
struct directory *cur = children->base[i];
char *base = g_path_get_basename(cur->path);
- retv = fprintf(fp, DIRECTORY_DIR "%s\n", base);
+ fprintf(fp, DIRECTORY_DIR "%s\n", base);
g_free(base);
- if (retv < 0)
- return -1;
- if (directory_save(fp, cur) < 0)
- return -1;
+
+ directory_save(fp, cur);
+
+ if (ferror(fp))
+ return;
}
songvec_save(fp, &directory->songs);
- if (!directory_is_root(directory) &&
- fprintf(fp, DIRECTORY_END "%s\n",
- directory_get_path(directory)) < 0)
- return -1;
- return 0;
+ playlist_vector_save(fp, &directory->playlists);
+
+ if (!directory_is_root(directory))
+ fprintf(fp, DIRECTORY_END "%s\n",
+ directory_get_path(directory));
}
-bool
-directory_load(FILE *fp, struct directory *directory, GError **error)
+static struct directory *
+directory_load_subdir(FILE *fp, struct directory *parent, const char *name,
+ GString *buffer, GError **error_r)
{
- char buffer[MPD_PATH_MAX * 2];
- char key[MPD_PATH_MAX * 2];
- char *name;
+ struct directory *directory;
+ const char *line;
bool success;
- while (fgets(buffer, sizeof(buffer), fp)
- && !g_str_has_prefix(buffer, DIRECTORY_END)) {
- if (g_str_has_prefix(buffer, DIRECTORY_DIR)) {
- struct directory *subdir;
+ if (directory_get_child(parent, name) != NULL) {
+ g_set_error(error_r, directory_quark(), 0,
+ "Duplicate subdirectory '%s'", name);
+ return NULL;
+ }
- g_strchomp(buffer);
- strcpy(key, &(buffer[strlen(DIRECTORY_DIR)]));
- if (!fgets(buffer, sizeof(buffer), fp)) {
- g_set_error(error, directory_quark(), 0,
- "Unexpected end of file");
- return false;
- }
+ 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);
+ }
- if (g_str_has_prefix(buffer, DIRECTORY_MTIME)) {
- directory->mtime =
- g_ascii_strtoull(buffer + sizeof(DIRECTORY_MTIME) - 1,
- NULL, 10);
+ line = read_text_line(fp, buffer);
+ if (line == NULL) {
+ g_set_error(error_r, directory_quark(), 0,
+ "Unexpected end of file");
+ directory_free(directory);
+ return NULL;
+ }
- if (!fgets(buffer, sizeof(buffer), fp)) {
- g_set_error(error, directory_quark(), 0,
- "Unexpected end of file");
- return false;
- }
- }
+ if (g_str_has_prefix(line, DIRECTORY_MTIME)) {
+ directory->mtime =
+ g_ascii_strtoull(line + sizeof(DIRECTORY_MTIME) - 1,
+ NULL, 10);
+
+ line = read_text_line(fp, buffer);
+ if (line == NULL) {
+ g_set_error(error_r, directory_quark(), 0,
+ "Unexpected end of file");
+ directory_free(directory);
+ return NULL;
+ }
+ }
- if (!g_str_has_prefix(buffer, DIRECTORY_BEGIN)) {
- g_set_error(error, directory_quark(), 0,
- "Malformed line: %s", buffer);
+ if (!g_str_has_prefix(line, DIRECTORY_BEGIN)) {
+ g_set_error(error_r, directory_quark(), 0,
+ "Malformed line: %s", line);
+ directory_free(directory);
+ return NULL;
+ }
+
+ success = directory_load(fp, directory, buffer, error_r);
+ if (!success) {
+ directory_free(directory);
+ return NULL;
+ }
+
+ return directory;
+}
+
+bool
+directory_load(FILE *fp, struct directory *directory,
+ GString *buffer, GError **error)
+{
+ const char *line;
+
+ while ((line = read_text_line(fp, buffer)) != NULL &&
+ !g_str_has_prefix(line, DIRECTORY_END)) {
+ if (g_str_has_prefix(line, DIRECTORY_DIR)) {
+ struct directory *subdir =
+ directory_load_subdir(fp, directory,
+ line + sizeof(DIRECTORY_DIR) - 1,
+ buffer, error);
+ if (subdir == NULL)
return false;
- }
- g_strchomp(buffer);
- name = &(buffer[strlen(DIRECTORY_BEGIN)]);
- if (!g_str_has_prefix(name, directory->path) != 0) {
+ 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) {
g_set_error(error, directory_quark(), 0,
- "Wrong path in database: '%s' in '%s'",
- name, directory->path);
- return false;
+ "Duplicate song '%s'", name);
+ return NULL;
}
- subdir = directory_get_child(directory, name);
- if (subdir != NULL) {
- assert(subdir->parent == directory);
- } else {
- subdir = directory_new(name, directory);
- dirvec_add(&directory->children, subdir);
- }
+ song = song_load(fp, directory, name,
+ buffer, error);
+ if (song == NULL)
+ return false;
- success = directory_load(fp, subdir, error);
- if (!success)
+ songvec_add(&directory->songs, song);
+ } else if (g_str_has_prefix(line, PLAYLIST_META_BEGIN)) {
+ /* duplicate the name, because
+ playlist_metadata_load() will overwrite the
+ buffer */
+ char *name = g_strdup(line + sizeof(PLAYLIST_META_BEGIN) - 1);
+
+ if (!playlist_metadata_load(fp, &directory->playlists,
+ name, buffer, error)) {
+ g_free(name);
return false;
- } else if (g_str_has_prefix(buffer, SONG_BEGIN)) {
- readSongInfoIntoList(fp, &directory->songs, directory);
+ }
+
+ g_free(name);
} else {
g_set_error(error, directory_quark(), 0,
- "Malformed line: %s", buffer);
+ "Malformed line: %s", line);
return false;
}
}
diff --git a/src/directory_save.h b/src/directory_save.h
index 28ec094ad..9193b6c41 100644
--- a/src/directory_save.h
+++ b/src/directory_save.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -27,10 +27,11 @@
struct directory;
-int
+void
directory_save(FILE *fp, struct directory *directory);
bool
-directory_load(FILE *fp, struct directory *directory, GError **error);
+directory_load(FILE *fp, struct directory *directory,
+ GString *buffer, GError **error);
#endif
diff --git a/src/dirvec.c b/src/dirvec.c
index 3ccb5d413..89b32a4f4 100644
--- a/src/dirvec.c
+++ b/src/dirvec.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -17,6 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "dirvec.h"
#include "directory.h"
diff --git a/src/dirvec.h b/src/dirvec.h
index d0898e0ca..a1a97d9f1 100644
--- a/src/dirvec.h
+++ b/src/dirvec.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
diff --git a/src/encoder/flac_encoder.c b/src/encoder/flac_encoder.c
new file mode 100644
index 000000000..c34faad00
--- /dev/null
+++ b/src/encoder/flac_encoder.c
@@ -0,0 +1,358 @@
+/*
+ * 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 "encoder_api.h"
+#include "encoder_plugin.h"
+#include "audio_format.h"
+#include "pcm_buffer.h"
+
+#include <assert.h>
+#include <string.h>
+
+#include <FLAC/stream_encoder.h>
+
+struct flac_encoder {
+ struct encoder encoder;
+
+ struct audio_format audio_format;
+ unsigned compression;
+
+ FLAC__StreamEncoder *fse;
+
+ struct pcm_buffer expand_buffer;
+
+ struct pcm_buffer buffer;
+ size_t buffer_length;
+};
+
+extern const struct encoder_plugin flac_encoder_plugin;
+
+
+static inline GQuark
+flac_encoder_quark(void)
+{
+ return g_quark_from_static_string("flac_encoder");
+}
+
+static bool
+flac_encoder_configure(struct flac_encoder *encoder,
+ const struct config_param *param, G_GNUC_UNUSED GError **error)
+{
+ encoder->compression = config_get_block_unsigned(param,
+ "compression", 5);
+
+ return true;
+}
+
+static struct encoder *
+flac_encoder_init(const struct config_param *param, GError **error)
+{
+ struct flac_encoder *encoder;
+
+ encoder = g_new(struct flac_encoder, 1);
+ encoder_struct_init(&encoder->encoder, &flac_encoder_plugin);
+
+ /* load configuration from "param" */
+ if (!flac_encoder_configure(encoder, param, error)) {
+ /* configuration has failed, roll back and return error */
+ g_free(encoder);
+ return NULL;
+ }
+
+ return &encoder->encoder;
+}
+
+static void
+flac_encoder_finish(struct encoder *_encoder)
+{
+ struct flac_encoder *encoder = (struct flac_encoder *)_encoder;
+
+ /* the real libFLAC cleanup was already performed by
+ flac_encoder_close(), so no real work here */
+ g_free(encoder);
+}
+
+static bool
+flac_encoder_setup(struct flac_encoder *encoder, unsigned bits_per_sample,
+ GError **error)
+{
+#if !defined(FLAC_API_VERSION_CURRENT) || FLAC_API_VERSION_CURRENT <= 7
+#else
+ if ( !FLAC__stream_encoder_set_compression_level(encoder->fse,
+ encoder->compression)) {
+ g_set_error(error, flac_encoder_quark(), 0,
+ "error setting flac compression to %d",
+ encoder->compression);
+ return false;
+ }
+#endif
+ if ( !FLAC__stream_encoder_set_channels(encoder->fse,
+ encoder->audio_format.channels)) {
+ g_set_error(error, flac_encoder_quark(), 0,
+ "error setting flac channels num to %d",
+ encoder->audio_format.channels);
+ return false;
+ }
+ if ( !FLAC__stream_encoder_set_bits_per_sample(encoder->fse,
+ bits_per_sample)) {
+ g_set_error(error, flac_encoder_quark(), 0,
+ "error setting flac bit format to %d",
+ bits_per_sample);
+ return false;
+ }
+ if ( !FLAC__stream_encoder_set_sample_rate(encoder->fse,
+ encoder->audio_format.sample_rate)) {
+ g_set_error(error, flac_encoder_quark(), 0,
+ "error setting flac sample rate to %d",
+ encoder->audio_format.sample_rate);
+ return false;
+ }
+ return true;
+}
+
+static FLAC__StreamEncoderWriteStatus
+flac_write_callback(G_GNUC_UNUSED const FLAC__StreamEncoder *fse,
+ const FLAC__byte data[],
+#if !defined(FLAC_API_VERSION_CURRENT) || FLAC_API_VERSION_CURRENT <= 7
+ unsigned bytes,
+#else
+ size_t bytes,
+#endif
+ G_GNUC_UNUSED unsigned samples,
+ G_GNUC_UNUSED unsigned current_frame, void *client_data)
+{
+ struct flac_encoder *encoder = (struct flac_encoder *) client_data;
+
+ char *buffer = pcm_buffer_get(&encoder->buffer, encoder->buffer_length + bytes);
+
+ //transfer data to buffer
+ memcpy( buffer + encoder->buffer_length, data, bytes);
+ encoder->buffer_length += bytes;
+
+ return FLAC__STREAM_ENCODER_WRITE_STATUS_OK;
+}
+
+static void
+flac_encoder_close(struct encoder *_encoder)
+{
+ struct flac_encoder *encoder = (struct flac_encoder *)_encoder;
+
+ FLAC__stream_encoder_delete(encoder->fse);
+
+ pcm_buffer_deinit(&encoder->buffer);
+ pcm_buffer_deinit(&encoder->expand_buffer);
+}
+
+static bool
+flac_encoder_open(struct encoder *_encoder, struct audio_format *audio_format,
+ GError **error)
+{
+ struct flac_encoder *encoder = (struct flac_encoder *)_encoder;
+ unsigned bits_per_sample;
+
+ encoder->audio_format = *audio_format;
+
+ /* FIXME: flac should support 32bit as well */
+ switch (audio_format->format) {
+ case SAMPLE_FORMAT_S8:
+ bits_per_sample = 8;
+ break;
+
+ case SAMPLE_FORMAT_S16:
+ bits_per_sample = 16;
+ break;
+
+ case SAMPLE_FORMAT_S24_P32:
+ bits_per_sample = 24;
+ break;
+
+ default:
+ bits_per_sample = 24;
+ audio_format->format = SAMPLE_FORMAT_S24_P32;
+ }
+
+ /* allocate the encoder */
+ encoder->fse = FLAC__stream_encoder_new();
+ if (encoder->fse == NULL) {
+ g_set_error(error, flac_encoder_quark(), 0,
+ "flac_new() failed");
+ return false;
+ }
+
+ if (!flac_encoder_setup(encoder, bits_per_sample, error)) {
+ FLAC__stream_encoder_delete(encoder->fse);
+ return false;
+ }
+
+ encoder->buffer_length = 0;
+ pcm_buffer_init(&encoder->buffer);
+ pcm_buffer_init(&encoder->expand_buffer);
+
+ /* this immediatelly outputs data throught callback */
+
+#if !defined(FLAC_API_VERSION_CURRENT) || FLAC_API_VERSION_CURRENT <= 7
+ {
+ FLAC__StreamEncoderState init_status;
+
+ FLAC__stream_encoder_set_write_callback(encoder->fse,
+ flac_write_callback);
+
+ init_status = FLAC__stream_encoder_init(encoder->fse);
+
+ if (init_status != FLAC__STREAM_ENCODER_OK) {
+ g_set_error(error, flac_encoder_quark(), 0,
+ "failed to initialize encoder: %s\n",
+ FLAC__StreamEncoderStateString[init_status]);
+ flac_encoder_close(_encoder);
+ return false;
+ }
+ }
+#else
+ {
+ FLAC__StreamEncoderInitStatus init_status;
+
+ init_status = FLAC__stream_encoder_init_stream(encoder->fse,
+ flac_write_callback,
+ NULL, NULL, NULL, encoder);
+
+ if(init_status != FLAC__STREAM_ENCODER_INIT_STATUS_OK) {
+ g_set_error(error, flac_encoder_quark(), 0,
+ "failed to initialize encoder: %s\n",
+ FLAC__StreamEncoderInitStatusString[init_status]);
+ flac_encoder_close(_encoder);
+ return false;
+ }
+ }
+#endif
+
+ return true;
+}
+
+
+static bool
+flac_encoder_flush(struct encoder *_encoder, G_GNUC_UNUSED GError **error)
+{
+ struct flac_encoder *encoder = (struct flac_encoder *)_encoder;
+
+ (void) FLAC__stream_encoder_finish(encoder->fse);
+ return true;
+}
+
+static inline void
+pcm8_to_flac(int32_t *out, const int8_t *in, unsigned num_samples)
+{
+ while (num_samples > 0) {
+ *out++ = *in++;
+ --num_samples;
+ }
+}
+
+static inline void
+pcm16_to_flac(int32_t *out, const int16_t *in, unsigned num_samples)
+{
+ while (num_samples > 0) {
+ *out++ = *in++;
+ --num_samples;
+ }
+}
+
+static bool
+flac_encoder_write(struct encoder *_encoder,
+ const void *data, size_t length,
+ G_GNUC_UNUSED GError **error)
+{
+ struct flac_encoder *encoder = (struct flac_encoder *)_encoder;
+ unsigned num_frames, num_samples;
+ void *exbuffer;
+ const void *buffer = NULL;
+
+ /* format conversion */
+
+ num_frames = length / audio_format_frame_size(&encoder->audio_format);
+ num_samples = num_frames * encoder->audio_format.channels;
+
+ switch (encoder->audio_format.format) {
+ case SAMPLE_FORMAT_S8:
+ exbuffer = pcm_buffer_get(&encoder->expand_buffer, length*4);
+ pcm8_to_flac(exbuffer, data, num_samples);
+ buffer = exbuffer;
+ break;
+
+ case SAMPLE_FORMAT_S16:
+ exbuffer = pcm_buffer_get(&encoder->expand_buffer, length*2);
+ pcm16_to_flac(exbuffer, data, num_samples);
+ buffer = exbuffer;
+ break;
+
+ case SAMPLE_FORMAT_S24_P32:
+ case SAMPLE_FORMAT_S32:
+ /* nothing need to be done; format is the same for
+ both mpd and libFLAC */
+ buffer = data;
+ break;
+ }
+
+ /* feed samples to encoder */
+
+ if (!FLAC__stream_encoder_process_interleaved(encoder->fse, buffer,
+ num_frames)) {
+ g_set_error(error, flac_encoder_quark(), 0,
+ "flac encoder process failed");
+ return false;
+ }
+
+ return true;
+}
+
+static size_t
+flac_encoder_read(struct encoder *_encoder, void *dest, size_t length)
+{
+ struct flac_encoder *encoder = (struct flac_encoder *)_encoder;
+ char *buffer = pcm_buffer_get(&encoder->buffer, encoder->buffer_length);
+
+ if (length > encoder->buffer_length)
+ length = encoder->buffer_length;
+
+ memcpy(dest, buffer, length);
+
+ encoder->buffer_length -= length;
+ memmove(buffer, buffer + length, encoder->buffer_length);
+
+ return length;
+}
+
+static const char *
+flac_encoder_get_mime_type(G_GNUC_UNUSED struct encoder *_encoder)
+{
+ return "audio/flac";
+}
+
+const struct encoder_plugin flac_encoder_plugin = {
+ .name = "flac",
+ .init = flac_encoder_init,
+ .finish = flac_encoder_finish,
+ .open = flac_encoder_open,
+ .close = flac_encoder_close,
+ .flush = flac_encoder_flush,
+ .write = flac_encoder_write,
+ .read = flac_encoder_read,
+ .get_mime_type = flac_encoder_get_mime_type,
+};
+
diff --git a/src/encoder/lame_encoder.c b/src/encoder/lame_encoder.c
index 6c8f20890..df843471b 100644
--- a/src/encoder/lame_encoder.c
+++ b/src/encoder/lame_encoder.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -17,6 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "encoder_api.h"
#include "encoder_plugin.h"
#include "audio_format.h"
@@ -191,7 +192,7 @@ lame_encoder_open(struct encoder *_encoder, struct audio_format *audio_format,
{
struct lame_encoder *encoder = (struct lame_encoder *)_encoder;
- audio_format->bits = 16;
+ audio_format->format = SAMPLE_FORMAT_S16;
audio_format->channels = 2;
encoder->audio_format = *audio_format;
@@ -281,6 +282,12 @@ lame_encoder_read(struct encoder *_encoder, void *dest, size_t length)
return length;
}
+static const char *
+lame_encoder_get_mime_type(G_GNUC_UNUSED struct encoder *_encoder)
+{
+ return "audio/mpeg";
+}
+
const struct encoder_plugin lame_encoder_plugin = {
.name = "lame",
.init = lame_encoder_init,
@@ -289,4 +296,5 @@ const struct encoder_plugin lame_encoder_plugin = {
.close = lame_encoder_close,
.write = lame_encoder_write,
.read = lame_encoder_read,
+ .get_mime_type = lame_encoder_get_mime_type,
};
diff --git a/src/encoder/null_encoder.c b/src/encoder/null_encoder.c
new file mode 100644
index 000000000..bf7e61c3b
--- /dev/null
+++ b/src/encoder/null_encoder.c
@@ -0,0 +1,124 @@
+/*
+ * 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 "encoder_api.h"
+#include "encoder_plugin.h"
+#include "pcm_buffer.h"
+
+#include <assert.h>
+#include <string.h>
+
+struct null_encoder {
+ struct encoder encoder;
+
+ struct pcm_buffer buffer;
+ size_t buffer_length;
+};
+
+extern const struct encoder_plugin null_encoder_plugin;
+
+static inline GQuark
+null_encoder_quark(void)
+{
+ return g_quark_from_static_string("null_encoder");
+}
+
+static struct encoder *
+null_encoder_init(G_GNUC_UNUSED const struct config_param *param,
+ G_GNUC_UNUSED GError **error)
+{
+ struct null_encoder *encoder;
+
+ encoder = g_new(struct null_encoder, 1);
+ encoder_struct_init(&encoder->encoder, &null_encoder_plugin);
+
+ return &encoder->encoder;
+}
+
+static void
+null_encoder_finish(struct encoder *_encoder)
+{
+ struct null_encoder *encoder = (struct null_encoder *)_encoder;
+
+ g_free(encoder);
+}
+
+static void
+null_encoder_close(struct encoder *_encoder)
+{
+ struct null_encoder *encoder = (struct null_encoder *)_encoder;
+
+ pcm_buffer_deinit(&encoder->buffer);
+}
+
+
+static bool
+null_encoder_open(struct encoder *_encoder,
+ G_GNUC_UNUSED struct audio_format *audio_format,
+ G_GNUC_UNUSED GError **error)
+{
+ struct null_encoder *encoder = (struct null_encoder *)_encoder;
+
+ encoder->buffer_length = 0;
+ pcm_buffer_init(&encoder->buffer);
+
+ return true;
+}
+
+static bool
+null_encoder_write(struct encoder *_encoder,
+ const void *data, size_t length,
+ G_GNUC_UNUSED GError **error)
+{
+ struct null_encoder *encoder = (struct null_encoder *)_encoder;
+ char *buffer = pcm_buffer_get(&encoder->buffer, encoder->buffer_length + length);
+
+ memcpy(buffer+encoder->buffer_length, data, length);
+
+ encoder->buffer_length += length;
+ return true;
+}
+
+static size_t
+null_encoder_read(struct encoder *_encoder, void *dest, size_t length)
+{
+ struct null_encoder *encoder = (struct null_encoder *)_encoder;
+ char *buffer = pcm_buffer_get(&encoder->buffer, encoder->buffer_length);
+
+ if (length > encoder->buffer_length)
+ length = encoder->buffer_length;
+
+ memcpy(dest, buffer, length);
+
+ encoder->buffer_length -= length;
+ memmove(buffer, buffer + length, encoder->buffer_length);
+
+ return length;
+}
+
+const struct encoder_plugin null_encoder_plugin = {
+ .name = "null",
+ .init = null_encoder_init,
+ .finish = null_encoder_finish,
+ .open = null_encoder_open,
+ .close = null_encoder_close,
+ .write = null_encoder_write,
+ .read = null_encoder_read,
+};
diff --git a/src/encoder/twolame_encoder.c b/src/encoder/twolame_encoder.c
new file mode 100644
index 000000000..d20af551b
--- /dev/null
+++ b/src/encoder/twolame_encoder.c
@@ -0,0 +1,307 @@
+/*
+ * 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 "encoder_api.h"
+#include "encoder_plugin.h"
+#include "audio_format.h"
+
+#include <twolame.h>
+#include <assert.h>
+#include <string.h>
+
+struct twolame_encoder {
+ struct encoder encoder;
+
+ struct audio_format audio_format;
+ float quality;
+ int bitrate;
+
+ twolame_options *options;
+
+ unsigned char buffer[32768];
+ size_t buffer_length;
+
+ /**
+ * Call libtwolame's flush function when the buffer is empty?
+ */
+ bool flush;
+};
+
+extern const struct encoder_plugin twolame_encoder_plugin;
+
+static inline GQuark
+twolame_encoder_quark(void)
+{
+ return g_quark_from_static_string("twolame_encoder");
+}
+
+static bool
+twolame_encoder_configure(struct twolame_encoder *encoder,
+ const struct config_param *param, GError **error)
+{
+ const char *value;
+ char *endptr;
+
+ value = config_get_block_string(param, "quality", NULL);
+ if (value != NULL) {
+ /* a quality was configured (VBR) */
+
+ encoder->quality = g_ascii_strtod(value, &endptr);
+
+ if (*endptr != '\0' || encoder->quality < -1.0 ||
+ encoder->quality > 10.0) {
+ g_set_error(error, twolame_encoder_quark(), 0,
+ "quality \"%s\" is not a number in the "
+ "range -1 to 10, line %i",
+ value, param->line);
+ return false;
+ }
+
+ if (config_get_block_string(param, "bitrate", NULL) != NULL) {
+ g_set_error(error, twolame_encoder_quark(), 0,
+ "quality and bitrate are "
+ "both defined (line %i)",
+ param->line);
+ return false;
+ }
+ } else {
+ /* a bit rate was configured */
+
+ value = config_get_block_string(param, "bitrate", NULL);
+ if (value == NULL) {
+ g_set_error(error, twolame_encoder_quark(), 0,
+ "neither bitrate nor quality defined "
+ "at line %i",
+ param->line);
+ return false;
+ }
+
+ encoder->quality = -2.0;
+ encoder->bitrate = g_ascii_strtoll(value, &endptr, 10);
+
+ if (*endptr != '\0' || encoder->bitrate <= 0) {
+ g_set_error(error, twolame_encoder_quark(), 0,
+ "bitrate at line %i should be a positive integer",
+ param->line);
+ return false;
+ }
+ }
+
+ return true;
+}
+
+static struct encoder *
+twolame_encoder_init(const struct config_param *param, GError **error)
+{
+ struct twolame_encoder *encoder;
+
+ g_debug("libtwolame version %s", get_twolame_version());
+
+ encoder = g_new(struct twolame_encoder, 1);
+ encoder_struct_init(&encoder->encoder, &twolame_encoder_plugin);
+
+ /* load configuration from "param" */
+ if (!twolame_encoder_configure(encoder, param, error)) {
+ /* configuration has failed, roll back and return error */
+ g_free(encoder);
+ return NULL;
+ }
+
+ return &encoder->encoder;
+}
+
+static void
+twolame_encoder_finish(struct encoder *_encoder)
+{
+ struct twolame_encoder *encoder = (struct twolame_encoder *)_encoder;
+
+ /* the real libtwolame cleanup was already performed by
+ twolame_encoder_close(), so no real work here */
+ g_free(encoder);
+}
+
+static bool
+twolame_encoder_setup(struct twolame_encoder *encoder, GError **error)
+{
+ if (encoder->quality >= -1.0) {
+ /* a quality was configured (VBR) */
+
+ if (0 != twolame_set_VBR(encoder->options, true)) {
+ g_set_error(error, twolame_encoder_quark(), 0,
+ "error setting twolame VBR mode");
+ return false;
+ }
+ if (0 != twolame_set_VBR_q(encoder->options, encoder->quality)) {
+ g_set_error(error, twolame_encoder_quark(), 0,
+ "error setting twolame VBR quality");
+ return false;
+ }
+ } else {
+ /* a bit rate was configured */
+
+ if (0 != twolame_set_brate(encoder->options, encoder->bitrate)) {
+ g_set_error(error, twolame_encoder_quark(), 0,
+ "error setting twolame bitrate");
+ return false;
+ }
+ }
+
+ if (0 != twolame_set_num_channels(encoder->options,
+ encoder->audio_format.channels)) {
+ g_set_error(error, twolame_encoder_quark(), 0,
+ "error setting twolame num channels");
+ return false;
+ }
+
+ if (0 != twolame_set_in_samplerate(encoder->options,
+ encoder->audio_format.sample_rate)) {
+ g_set_error(error, twolame_encoder_quark(), 0,
+ "error setting twolame sample rate");
+ return false;
+ }
+
+ if (0 > twolame_init_params(encoder->options)) {
+ g_set_error(error, twolame_encoder_quark(), 0,
+ "error initializing twolame params");
+ return false;
+ }
+
+ return true;
+}
+
+static bool
+twolame_encoder_open(struct encoder *_encoder, struct audio_format *audio_format,
+ GError **error)
+{
+ struct twolame_encoder *encoder = (struct twolame_encoder *)_encoder;
+
+ audio_format->format = SAMPLE_FORMAT_S16;
+ audio_format->channels = 2;
+
+ encoder->audio_format = *audio_format;
+
+ encoder->options = twolame_init();
+ if (encoder->options == NULL) {
+ g_set_error(error, twolame_encoder_quark(), 0,
+ "twolame_init() failed");
+ return false;
+ }
+
+ if (!twolame_encoder_setup(encoder, error)) {
+ twolame_close(&encoder->options);
+ return false;
+ }
+
+ encoder->buffer_length = 0;
+ encoder->flush = false;
+
+ return true;
+}
+
+static void
+twolame_encoder_close(struct encoder *_encoder)
+{
+ struct twolame_encoder *encoder = (struct twolame_encoder *)_encoder;
+
+ twolame_close(&encoder->options);
+}
+
+static bool
+twolame_encoder_flush(struct encoder *_encoder, G_GNUC_UNUSED GError **error)
+{
+ struct twolame_encoder *encoder = (struct twolame_encoder *)_encoder;
+
+ encoder->flush = true;
+ return true;
+}
+
+static bool
+twolame_encoder_write(struct encoder *_encoder,
+ const void *data, size_t length,
+ G_GNUC_UNUSED GError **error)
+{
+ struct twolame_encoder *encoder = (struct twolame_encoder *)_encoder;
+ unsigned num_frames;
+ const int16_t *src = (const int16_t*)data;
+ int bytes_out;
+
+ assert(encoder->buffer_length == 0);
+
+ num_frames =
+ length / audio_format_frame_size(&encoder->audio_format);
+
+ bytes_out = twolame_encode_buffer_interleaved(encoder->options,
+ src, num_frames,
+ encoder->buffer,
+ sizeof(encoder->buffer));
+ if (bytes_out < 0) {
+ g_set_error(error, twolame_encoder_quark(), 0,
+ "twolame encoder failed");
+ return false;
+ }
+
+ encoder->buffer_length = (size_t)bytes_out;
+ return true;
+}
+
+static size_t
+twolame_encoder_read(struct encoder *_encoder, void *dest, size_t length)
+{
+ struct twolame_encoder *encoder = (struct twolame_encoder *)_encoder;
+
+ if (encoder->buffer_length == 0 && encoder->flush) {
+ int ret = twolame_encode_flush(encoder->options,
+ encoder->buffer,
+ sizeof(encoder->buffer));
+ if (ret > 0)
+ encoder->buffer_length = (size_t)ret;
+
+ encoder->flush = false;
+ }
+
+ if (length > encoder->buffer_length)
+ length = encoder->buffer_length;
+
+ memcpy(dest, encoder->buffer, length);
+
+ encoder->buffer_length -= length;
+ memmove(encoder->buffer, encoder->buffer + length,
+ encoder->buffer_length);
+
+ return length;
+}
+
+static const char *
+twolame_encoder_get_mime_type(G_GNUC_UNUSED struct encoder *_encoder)
+{
+ return "audio/mpeg";
+}
+
+const struct encoder_plugin twolame_encoder_plugin = {
+ .name = "twolame",
+ .init = twolame_encoder_init,
+ .finish = twolame_encoder_finish,
+ .open = twolame_encoder_open,
+ .close = twolame_encoder_close,
+ .flush = twolame_encoder_flush,
+ .write = twolame_encoder_write,
+ .read = twolame_encoder_read,
+ .get_mime_type = twolame_encoder_get_mime_type,
+};
diff --git a/src/encoder/vorbis_encoder.c b/src/encoder/vorbis_encoder.c
index 3fbbe5b61..08147be1c 100644
--- a/src/encoder/vorbis_encoder.c
+++ b/src/encoder/vorbis_encoder.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -17,10 +17,12 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "encoder_api.h"
#include "encoder_plugin.h"
#include "tag.h"
#include "audio_format.h"
+#include "mpd_error.h"
#include <vorbis/vorbisenc.h>
@@ -211,7 +213,7 @@ vorbis_encoder_open(struct encoder *_encoder,
struct vorbis_encoder *encoder = (struct vorbis_encoder *)_encoder;
bool ret;
- audio_format->bits = 16;
+ audio_format->format = SAMPLE_FORMAT_S16;
encoder->audio_format = *audio_format;
@@ -375,7 +377,7 @@ vorbis_encoder_read(struct encoder *_encoder, void *_dest, size_t length)
if (nbytes > length)
/* XXX better error handling */
- g_error("buffer too small");
+ MPD_ERROR("buffer too small");
memcpy(dest, page.header, page.header_len);
memcpy(dest + page.header_len, page.body, page.body_len);
@@ -383,6 +385,12 @@ vorbis_encoder_read(struct encoder *_encoder, void *_dest, size_t length)
return nbytes;
}
+static const char *
+vorbis_encoder_get_mime_type(G_GNUC_UNUSED struct encoder *_encoder)
+{
+ return "audio/ogg";
+}
+
const struct encoder_plugin vorbis_encoder_plugin = {
.name = "vorbis",
.init = vorbis_encoder_init,
@@ -393,4 +401,5 @@ const struct encoder_plugin vorbis_encoder_plugin = {
.tag = vorbis_encoder_tag,
.write = vorbis_encoder_write,
.read = vorbis_encoder_read,
+ .get_mime_type = vorbis_encoder_get_mime_type,
};
diff --git a/src/encoder/wave_encoder.c b/src/encoder/wave_encoder.c
new file mode 100644
index 000000000..938be5e5e
--- /dev/null
+++ b/src/encoder/wave_encoder.c
@@ -0,0 +1,270 @@
+/*
+ * 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 "encoder_api.h"
+#include "encoder_plugin.h"
+#include "pcm_buffer.h"
+
+#include <assert.h>
+#include <string.h>
+
+struct wave_encoder {
+ struct encoder encoder;
+ unsigned bits;
+
+ struct pcm_buffer buffer;
+ size_t buffer_length;
+};
+
+struct wave_header {
+ uint32_t id_riff;
+ uint32_t riff_size;
+ uint32_t id_wave;
+ uint32_t id_fmt;
+ uint32_t fmt_size;
+ uint16_t format;
+ uint16_t channels;
+ uint32_t freq;
+ uint32_t byterate;
+ uint16_t blocksize;
+ uint16_t bits;
+ uint32_t id_data;
+ uint32_t data_size;
+};
+
+extern const struct encoder_plugin wave_encoder_plugin;
+
+static inline GQuark
+wave_encoder_quark(void)
+{
+ return g_quark_from_static_string("wave_encoder");
+}
+
+static void
+fill_wave_header(struct wave_header *header, int channels, int bits,
+ int freq, int block_size)
+{
+ int data_size = 0x0FFFFFFF;
+
+ /* constants */
+ header->id_riff = GUINT32_TO_LE(0x46464952);
+ header->id_wave = GUINT32_TO_LE(0x45564157);
+ header->id_fmt = GUINT32_TO_LE(0x20746d66);
+ header->id_data = GUINT32_TO_LE(0x61746164);
+
+ /* wave format */
+ header->format = GUINT16_TO_LE(1); // PCM_FORMAT
+ header->channels = GUINT16_TO_LE(channels);
+ header->bits = GUINT16_TO_LE(bits);
+ header->freq = GUINT32_TO_LE(freq);
+ header->blocksize = GUINT16_TO_LE(block_size);
+ header->byterate = GUINT32_TO_LE(freq * block_size);
+
+ /* chunk sizes (fake data length) */
+ header->fmt_size = GUINT32_TO_LE(16);
+ header->data_size = GUINT32_TO_LE(data_size);
+ header->riff_size = GUINT32_TO_LE(4 + (8 + 16) +
+ (8 + data_size));
+}
+
+static struct encoder *
+wave_encoder_init(G_GNUC_UNUSED const struct config_param *param,
+ G_GNUC_UNUSED GError **error)
+{
+ struct wave_encoder *encoder;
+
+ encoder = g_new(struct wave_encoder, 1);
+ encoder_struct_init(&encoder->encoder, &wave_encoder_plugin);
+ pcm_buffer_init(&encoder->buffer);
+
+ return &encoder->encoder;
+}
+
+static void
+wave_encoder_finish(struct encoder *_encoder)
+{
+ struct wave_encoder *encoder = (struct wave_encoder *)_encoder;
+
+ pcm_buffer_deinit(&encoder->buffer);
+ g_free(encoder);
+}
+
+static bool
+wave_encoder_open(struct encoder *_encoder,
+ G_GNUC_UNUSED struct audio_format *audio_format,
+ G_GNUC_UNUSED GError **error)
+{
+ struct wave_encoder *encoder = (struct wave_encoder *)_encoder;
+ void *buffer;
+
+ assert(audio_format_valid(audio_format));
+
+ switch (audio_format->format) {
+ case SAMPLE_FORMAT_S8:
+ encoder->bits = 8;
+ break;
+
+ case SAMPLE_FORMAT_S16:
+ encoder->bits = 16;
+ break;
+
+ case SAMPLE_FORMAT_S24_P32:
+ encoder->bits = 24;
+ break;
+
+ case SAMPLE_FORMAT_S32:
+ encoder->bits = 32;
+ break;
+
+ default:
+ audio_format->format = SAMPLE_FORMAT_S16;
+ encoder->bits = 16;
+ break;
+ }
+
+ buffer = pcm_buffer_get(&encoder->buffer, sizeof(struct wave_header) );
+
+ /* create PCM wave header in initial buffer */
+ fill_wave_header((struct wave_header *) buffer,
+ audio_format->channels,
+ encoder->bits,
+ audio_format->sample_rate,
+ (encoder->bits / 8) * audio_format->channels );
+
+ encoder->buffer_length = sizeof(struct wave_header);
+ return true;
+}
+
+static inline size_t
+pcm16_to_wave(uint16_t *dst16, const uint16_t *src16, size_t length)
+{
+ size_t cnt = length >> 1;
+ while (cnt > 0) {
+ *dst16++ = GUINT16_TO_LE(*src16++);
+ cnt--;
+ }
+ return length;
+}
+
+static inline size_t
+pcm32_to_wave(uint32_t *dst32, const uint32_t *src32, size_t length)
+{
+ size_t cnt = length >> 2;
+ while (cnt > 0){
+ *dst32++ = GUINT32_TO_LE(*src32++);
+ cnt--;
+ }
+ return length;
+}
+
+static inline size_t
+pcm24_to_wave(uint8_t *dst8, const uint32_t *src32, size_t length)
+{
+ uint32_t value;
+ uint8_t *dst_old = dst8;
+
+ length = length >> 2;
+ while (length > 0){
+ value = *src32++;
+ *dst8++ = (value) & 0xFF;
+ *dst8++ = (value >> 8) & 0xFF;
+ *dst8++ = (value >> 16) & 0xFF;
+ length--;
+ }
+ //correct buffer length
+ return (dst8 - dst_old);
+}
+
+static bool
+wave_encoder_write(struct encoder *_encoder,
+ const void *src, size_t length,
+ G_GNUC_UNUSED GError **error)
+{
+ struct wave_encoder *encoder = (struct wave_encoder *)_encoder;
+ void *dst;
+
+ dst = pcm_buffer_get(&encoder->buffer, encoder->buffer_length + length);
+
+#if (G_BYTE_ORDER == G_LITTLE_ENDIAN)
+ switch (encoder->bits) {
+ case 8:
+ case 16:
+ case 32:// optimized cases
+ memcpy(dst, src, length);
+ break;
+ case 24:
+ length = pcm24_to_wave(dst, src, length);
+ break;
+ }
+#elif (G_BYTE_ORDER == G_BIG_ENDIAN)
+ switch (encoder->bits) {
+ case 8:
+ memcpy(dst, src, length);
+ break;
+ case 16:
+ length = pcm16_to_wave(dst, src, length);
+ break;
+ case 24:
+ length = pcm24_to_wave(dst, src, length);
+ break;
+ case 32:
+ length = pcm32_to_wave(dst, src, length);
+ break;
+ }
+#else
+#error G_BYTE_ORDER set to G_PDP_ENDIAN is not supported by wave_encoder
+#endif
+
+ encoder->buffer_length += length;
+ return true;
+}
+
+static size_t
+wave_encoder_read(struct encoder *_encoder, void *dest, size_t length)
+{
+ struct wave_encoder *encoder = (struct wave_encoder *)_encoder;
+ uint8_t *buffer = pcm_buffer_get(&encoder->buffer, encoder->buffer_length );
+
+ if (length > encoder->buffer_length)
+ length = encoder->buffer_length;
+
+ memcpy(dest, buffer, length);
+
+ encoder->buffer_length -= length;
+ memmove(buffer, buffer + length, encoder->buffer_length);
+
+ return length;
+}
+
+static const char *
+wave_encoder_get_mime_type(G_GNUC_UNUSED struct encoder *_encoder)
+{
+ return "audio/wav";
+}
+
+const struct encoder_plugin wave_encoder_plugin = {
+ .name = "wave",
+ .init = wave_encoder_init,
+ .finish = wave_encoder_finish,
+ .open = wave_encoder_open,
+ .write = wave_encoder_write,
+ .read = wave_encoder_read,
+ .get_mime_type = wave_encoder_get_mime_type,
+};
diff --git a/src/encoder_api.h b/src/encoder_api.h
index b1d4044ba..5df486ebd 100644
--- a/src/encoder_api.h
+++ b/src/encoder_api.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
diff --git a/src/encoder_list.c b/src/encoder_list.c
index d563b6bc8..f49ad48f7 100644
--- a/src/encoder_list.c
+++ b/src/encoder_list.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -17,22 +17,36 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "encoder_list.h"
#include "encoder_plugin.h"
-#include "config.h"
#include <string.h>
+extern const struct encoder_plugin null_encoder_plugin;
extern const struct encoder_plugin vorbis_encoder_plugin;
extern const struct encoder_plugin lame_encoder_plugin;
+extern const struct encoder_plugin twolame_encoder_plugin;
+extern const struct encoder_plugin wave_encoder_plugin;
+extern const struct encoder_plugin flac_encoder_plugin;
static const struct encoder_plugin *encoder_plugins[] = {
+ &null_encoder_plugin,
#ifdef ENABLE_VORBIS_ENCODER
&vorbis_encoder_plugin,
#endif
#ifdef ENABLE_LAME_ENCODER
&lame_encoder_plugin,
#endif
+#ifdef ENABLE_TWOLAME_ENCODER
+ &twolame_encoder_plugin,
+#endif
+#ifdef ENABLE_WAVE_ENCODER
+ &wave_encoder_plugin,
+#endif
+#ifdef ENABLE_FLAC_ENCODER
+ &flac_encoder_plugin,
+#endif
NULL
};
@@ -45,3 +59,13 @@ encoder_plugin_get(const char *name)
return NULL;
}
+
+void
+encoder_plugin_print_all_types(FILE * fp)
+{
+ for (unsigned i = 0; encoder_plugins[i] != NULL; ++i)
+ fprintf(fp, "%s ", encoder_plugins[i]->name);
+
+ fprintf(fp, "\n");
+ fflush(fp);
+}
diff --git a/src/encoder_list.h b/src/encoder_list.h
index bc20ad8c5..95f853004 100644
--- a/src/encoder_list.h
+++ b/src/encoder_list.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -20,6 +20,8 @@
#ifndef MPD_ENCODER_LIST_H
#define MPD_ENCODER_LIST_H
+#include <stdio.h>
+
struct encoder_plugin;
/**
@@ -32,4 +34,7 @@ struct encoder_plugin;
const struct encoder_plugin *
encoder_plugin_get(const char *name);
+void
+encoder_plugin_print_all_types(FILE * fp);
+
#endif
diff --git a/src/encoder_plugin.h b/src/encoder_plugin.h
index 958fe97cf..13fb231f4 100644
--- a/src/encoder_plugin.h
+++ b/src/encoder_plugin.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -58,6 +58,8 @@ struct encoder_plugin {
GError **error);
size_t (*read)(struct encoder *encoder, void *dest, size_t length);
+
+ const char *(*get_mime_type)(struct encoder *encoder);
};
/**
@@ -192,4 +194,19 @@ encoder_read(struct encoder *encoder, void *dest, size_t length)
return encoder->plugin->read(encoder, dest, length);
}
+/**
+ * Get mime type of encoded content.
+ *
+ * @param plugin the encoder plugin
+ * @return an constant string, NULL on failure
+ */
+static inline const char *
+encoder_get_mime_type(struct encoder *encoder)
+{
+ /* this method is optional */
+ return encoder->plugin->get_mime_type != NULL
+ ? encoder->plugin->get_mime_type(encoder)
+ : NULL;
+}
+
#endif
diff --git a/src/event_pipe.c b/src/event_pipe.c
index 3e5009150..5b519984f 100644
--- a/src/event_pipe.c
+++ b/src/event_pipe.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -17,8 +17,10 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "event_pipe.h"
-#include "utils.h"
+#include "fd_util.h"
+#include "mpd_error.h"
#include <stdbool.h>
#include <assert.h>
@@ -37,6 +39,7 @@
#define G_LOG_DOMAIN "event_pipe"
static int event_pipe[2];
+static GIOChannel *event_channel;
static guint event_pipe_source_id;
static GMutex *event_pipe_mutex;
static bool pipe_events[PIPE_EVENT_MAX];
@@ -60,12 +63,15 @@ main_notify_event(G_GNUC_UNUSED GIOChannel *source,
G_GNUC_UNUSED gpointer data)
{
char buffer[256];
- ssize_t r = read(event_pipe[0], buffer, sizeof(buffer));
- bool events[PIPE_EVENT_MAX];
-
- if (r < 0 && errno != EAGAIN && errno != EINTR)
- g_error("error reading from pipe: %s", strerror(errno));
+ gsize bytes_read;
+ GError *error = NULL;
+ GIOStatus status = g_io_channel_read_chars(event_channel,
+ buffer, sizeof(buffer),
+ &bytes_read, &error);
+ if (status == G_IO_STATUS_ERROR)
+ MPD_ERROR("error reading from pipe: %s", error->message);
+ bool events[PIPE_EVENT_MAX];
g_mutex_lock(event_pipe_mutex);
memcpy(events, pipe_events, sizeof(events));
memset(pipe_events, 0, sizeof(pipe_events));
@@ -84,22 +90,22 @@ void event_pipe_init(void)
GIOChannel *channel;
int ret;
-#ifdef WIN32
- ret = _pipe(event_pipe, 512, _O_BINARY);
-#else
- ret = pipe(event_pipe);
-#endif
+ ret = pipe_cloexec_nonblock(event_pipe);
if (ret < 0)
- g_error("Couldn't open pipe: %s", strerror(errno));
-#ifndef WIN32
- if (set_nonblocking(event_pipe[1]) < 0)
- g_error("Couldn't set non-blocking I/O: %s", strerror(errno));
-#endif
+ MPD_ERROR("Couldn't open pipe: %s", strerror(errno));
+#ifndef G_OS_WIN32
channel = g_io_channel_unix_new(event_pipe[0]);
+#else
+ channel = g_io_channel_win32_new_fd(event_pipe[0]);
+#endif
+ g_io_channel_set_encoding(channel, NULL, NULL);
+ g_io_channel_set_buffered(channel, false);
+
event_pipe_source_id = g_io_add_watch(channel, G_IO_IN,
main_notify_event, NULL);
- g_io_channel_unref(channel);
+
+ event_channel = channel;
event_pipe_mutex = g_mutex_new();
}
@@ -109,8 +115,12 @@ void event_pipe_deinit(void)
g_mutex_free(event_pipe_mutex);
g_source_remove(event_pipe_source_id);
+ g_io_channel_unref(event_channel);
+#ifndef WIN32
+ /* By some strange reason this call hangs on Win32 */
close(event_pipe[0]);
+#endif
close(event_pipe[1]);
}
@@ -141,7 +151,7 @@ void event_pipe_emit(enum pipe_event event)
w = write(event_pipe[1], "", 1);
if (w < 0 && errno != EAGAIN && errno != EINTR)
- g_error("error writing to pipe: %s", strerror(errno));
+ MPD_ERROR("error writing to pipe: %s", strerror(errno));
}
void event_pipe_emit_fast(enum pipe_event event)
diff --git a/src/event_pipe.h b/src/event_pipe.h
index ecb7ec9e8..923544bf4 100644
--- a/src/event_pipe.h
+++ b/src/event_pipe.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -32,7 +32,7 @@ enum pipe_event {
/** an idle event was emitted */
PIPE_EVENT_IDLE,
- /** must call syncPlayerAndPlaylist() */
+ /** must call playlist_sync() */
PIPE_EVENT_PLAYLIST,
/** the current song's tag has changed */
@@ -41,6 +41,12 @@ enum pipe_event {
/** SIGHUP received: reload configuration, roll log file */
PIPE_EVENT_RELOAD,
+ /** a hardware mixer plugin has detected a change */
+ PIPE_EVENT_MIXER,
+
+ /** shutdown requested */
+ PIPE_EVENT_SHUTDOWN,
+
PIPE_EVENT_MAX
};
diff --git a/src/exclude.c b/src/exclude.c
new file mode 100644
index 000000000..dd46b58c7
--- /dev/null
+++ b/src/exclude.c
@@ -0,0 +1,95 @@
+/*
+ * 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.
+ */
+
+/*
+ * The .mpdignore backend code.
+ *
+ */
+
+#include "config.h"
+#include "exclude.h"
+#include "path.h"
+
+#include <assert.h>
+#include <string.h>
+#include <stdio.h>
+#include <errno.h>
+
+GSList *
+exclude_list_load(const char *path_fs)
+{
+ FILE *file;
+ char line[1024];
+ GSList *list = NULL;
+
+ assert(path_fs != NULL);
+
+ file = fopen(path_fs, "r");
+ if (file == NULL) {
+ if (errno != ENOENT) {
+ char *path_utf8 = fs_charset_to_utf8(path_fs);
+ g_debug("Failed to open %s: %s",
+ path_utf8, g_strerror(errno));
+ g_free(path_utf8);
+ }
+
+ return NULL;
+ }
+
+ while (fgets(line, sizeof(line), file) != NULL) {
+ char *p = strchr(line, '#');
+ if (p != NULL)
+ *p = 0;
+
+ p = g_strstrip(line);
+ if (*p != 0)
+ list = g_slist_prepend(list, g_pattern_spec_new(p));
+ }
+
+ fclose(file);
+
+ return list;
+}
+
+void
+exclude_list_free(GSList *list)
+{
+ while (list != NULL) {
+ GPatternSpec *pattern = list->data;
+ g_pattern_spec_free(pattern);
+ list = g_slist_remove(list, list->data);
+ }
+}
+
+bool
+exclude_list_check(GSList *list, const char *name_fs)
+{
+ assert(name_fs != NULL);
+
+ /* XXX include full path name in check */
+
+ for (; list != NULL; list = list->next) {
+ GPatternSpec *pattern = list->data;
+
+ if (g_pattern_match_string(pattern, name_fs))
+ return true;
+ }
+
+ return false;
+}
diff --git a/src/exclude.h b/src/exclude.h
new file mode 100644
index 000000000..fd7cf8795
--- /dev/null
+++ b/src/exclude.h
@@ -0,0 +1,51 @@
+/*
+ * 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.
+ */
+
+/*
+ * The .mpdignore backend code.
+ *
+ */
+
+#ifndef MPD_EXCLUDE_H
+#define MPD_EXCLUDE_H
+
+#include <glib.h>
+
+#include <stdbool.h>
+
+/**
+ * Loads and parses a .mpdignore file.
+ */
+GSList *
+exclude_list_load(const char *path_fs);
+
+/**
+ * Frees a list returned by exclude_list_load().
+ */
+void
+exclude_list_free(GSList *list);
+
+/**
+ * Checks whether one of the patterns in the .mpdignore file matches
+ * the specified file name.
+ */
+bool
+exclude_list_check(GSList *list, const char *name_fs);
+
+#endif
diff --git a/src/fd_util.c b/src/fd_util.c
new file mode 100644
index 000000000..1f3004d0c
--- /dev/null
+++ b/src/fd_util.c
@@ -0,0 +1,306 @@
+/*
+ * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h" /* must be first for large file support */
+#include "fd_util.h"
+
+#if !defined(_GNU_SOURCE) && (defined(HAVE_PIPE2) || defined(HAVE_ACCEPT4))
+#define _GNU_SOURCE
+#endif
+
+#include <assert.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+
+#ifdef WIN32
+#include <ws2tcpip.h>
+#include <winsock2.h>
+#else
+#include <sys/socket.h>
+#endif
+
+#ifdef HAVE_INOTIFY_INIT
+#include <sys/inotify.h>
+#endif
+
+#ifndef WIN32
+
+static int
+fd_mask_flags(int fd, int and_mask, int xor_mask)
+{
+ int ret;
+
+ assert(fd >= 0);
+
+ ret = fcntl(fd, F_GETFD, 0);
+ if (ret < 0)
+ return ret;
+
+ return fcntl(fd, F_SETFD, (ret & and_mask) ^ xor_mask);
+}
+
+#endif /* !WIN32 */
+
+static int
+fd_set_cloexec(int fd, bool enable)
+{
+#ifndef WIN32
+ return fd_mask_flags(fd, ~FD_CLOEXEC, enable ? FD_CLOEXEC : 0);
+#else
+ (void)fd;
+ (void)enable;
+
+ return 0;
+#endif
+}
+
+/**
+ * Enables non-blocking mode for the specified file descriptor. On
+ * WIN32, this function only works for sockets.
+ */
+static int
+fd_set_nonblock(int fd)
+{
+#ifdef WIN32
+ u_long val = 1;
+ return ioctlsocket(fd, FIONBIO, &val);
+#else
+ int flags;
+
+ assert(fd >= 0);
+
+ flags = fcntl(fd, F_GETFL);
+ if (flags < 0)
+ return flags;
+
+ return fcntl(fd, F_SETFL, flags | O_NONBLOCK);
+#endif
+}
+
+int
+dup_cloexec(int oldfd)
+{
+ int newfd = dup(oldfd);
+ if (newfd >= 0)
+ fd_set_nonblock(newfd);
+
+ return newfd;
+}
+
+int
+open_cloexec(const char *path_fs, int flags, int mode)
+{
+ int fd;
+
+#ifdef O_CLOEXEC
+ flags |= O_CLOEXEC;
+#endif
+
+#ifdef O_NOCTTY
+ flags |= O_NOCTTY;
+#endif
+
+ fd = open(path_fs, flags, mode);
+ if (fd >= 0)
+ fd_set_cloexec(fd, true);
+
+ return fd;
+}
+
+int
+pipe_cloexec(int fd[2])
+{
+#ifdef WIN32
+ return _pipe(fd, 512, _O_BINARY);
+#else
+ int ret;
+
+#ifdef HAVE_PIPE2
+ ret = pipe2(fd, O_CLOEXEC);
+ if (ret >= 0 || errno != ENOSYS)
+ return ret;
+#endif
+
+ ret = pipe(fd);
+ if (ret >= 0) {
+ fd_set_cloexec(fd[0], true);
+ fd_set_cloexec(fd[1], true);
+ }
+
+ return ret;
+#endif
+}
+
+int
+pipe_cloexec_nonblock(int fd[2])
+{
+#ifdef WIN32
+ return _pipe(fd, 512, _O_BINARY);
+#else
+ int ret;
+
+#ifdef HAVE_PIPE2
+ ret = pipe2(fd, O_CLOEXEC|O_NONBLOCK);
+ if (ret >= 0 || errno != ENOSYS)
+ return ret;
+#endif
+
+ ret = pipe(fd);
+ if (ret >= 0) {
+ fd_set_cloexec(fd[0], true);
+ fd_set_cloexec(fd[1], true);
+
+ fd_set_nonblock(fd[0]);
+ fd_set_nonblock(fd[1]);
+ }
+
+ return ret;
+#endif
+}
+
+#ifndef WIN32
+
+int
+socketpair_cloexec(int domain, int type, int protocol, int sv[2])
+{
+ int ret;
+
+#ifdef SOCK_CLOEXEC
+ ret = socketpair(domain, type | SOCK_CLOEXEC, protocol, sv);
+ if (ret >= 0 || errno != EINVAL)
+ return ret;
+#endif
+
+ ret = socketpair(domain, type, protocol, sv);
+ if (ret >= 0) {
+ fd_set_cloexec(sv[0], true);
+ fd_set_cloexec(sv[1], true);
+ }
+
+ return ret;
+}
+
+#endif
+
+int
+socket_cloexec_nonblock(int domain, int type, int protocol)
+{
+ int fd;
+
+#if defined(SOCK_CLOEXEC) && defined(SOCK_NONBLOCK)
+ fd = socket(domain, type | SOCK_CLOEXEC | SOCK_NONBLOCK, protocol);
+ if (fd >= 0 || errno != EINVAL)
+ return fd;
+#endif
+
+ fd = socket(domain, type, protocol);
+ if (fd >= 0) {
+ fd_set_cloexec(fd, true);
+ fd_set_nonblock(fd);
+ }
+
+ return fd;
+}
+
+int
+accept_cloexec_nonblock(int fd, struct sockaddr *address,
+ size_t *address_length_r)
+{
+ int ret;
+ socklen_t address_length = *address_length_r;
+
+#ifdef HAVE_ACCEPT4
+ ret = accept4(fd, address, &address_length,
+ SOCK_CLOEXEC|SOCK_NONBLOCK);
+ if (ret >= 0 || errno != ENOSYS) {
+ if (ret >= 0)
+ *address_length_r = address_length;
+
+ return ret;
+ }
+#endif
+
+ ret = accept(fd, address, &address_length);
+ if (ret >= 0) {
+ fd_set_cloexec(ret, true);
+ fd_set_nonblock(ret);
+ *address_length_r = address_length;
+ }
+
+ return ret;
+}
+
+#ifndef WIN32
+
+ssize_t
+recvmsg_cloexec(int sockfd, struct msghdr *msg, int flags)
+{
+#ifdef MSG_CMSG_CLOEXEC
+ flags |= MSG_CMSG_CLOEXEC;
+#endif
+
+ ssize_t result = recvmsg(sockfd, msg, flags);
+ if (result >= 0) {
+ struct cmsghdr *cmsg = CMSG_FIRSTHDR(msg);
+ while (cmsg != NULL) {
+ if (cmsg->cmsg_type == SCM_RIGHTS) {
+ const int *fd_p = (const int *)CMSG_DATA(cmsg);
+ fd_set_cloexec(*fd_p, true);
+ }
+
+ cmsg = CMSG_NXTHDR(msg, cmsg);
+ }
+ }
+
+ return result;
+}
+
+#endif
+
+#ifdef HAVE_INOTIFY_INIT
+
+int
+inotify_init_cloexec(void)
+{
+ int fd;
+
+#ifdef HAVE_INOTIFY_INIT1
+ fd = inotify_init1(IN_CLOEXEC);
+ if (fd >= 0 || errno != ENOSYS)
+ return fd;
+#endif
+
+ fd = inotify_init();
+ if (fd >= 0)
+ fd_set_cloexec(fd, true);
+
+ return fd;
+}
+
+#endif
diff --git a/src/fd_util.h b/src/fd_util.h
new file mode 100644
index 000000000..5b80df2c7
--- /dev/null
+++ b/src/fd_util.h
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*
+ * This library provides easy helper functions for working with file
+ * descriptors. It has wrappers for taking advantage of Linux 2.6
+ * specific features like O_CLOEXEC.
+ *
+ */
+
+#ifndef FD_UTIL_H
+#define FD_UTIL_H
+
+#include <stdbool.h>
+#include <stddef.h>
+
+#ifndef WIN32
+#if !defined(_GNU_SOURCE) && (defined(HAVE_PIPE2) || defined(HAVE_ACCEPT4))
+#define _GNU_SOURCE
+#endif
+
+#include <sys/types.h>
+#endif
+
+struct sockaddr;
+
+/**
+ * Wrapper for dup(), which sets the CLOEXEC flag on the new
+ * descriptor.
+ */
+int
+dup_cloexec(int oldfd);
+
+/**
+ * Wrapper for open(), which sets the CLOEXEC flag (atomically if
+ * supported by the OS).
+ */
+int
+open_cloexec(const char *path_fs, int flags, int mode);
+
+/**
+ * Wrapper for pipe(), which sets the CLOEXEC flag (atomically if
+ * supported by the OS).
+ */
+int
+pipe_cloexec(int fd[2]);
+
+/**
+ * Wrapper for pipe(), which sets the CLOEXEC flag (atomically if
+ * supported by the OS).
+ *
+ * On systems that supports it (everybody except for Windows), it also
+ * sets the NONBLOCK flag.
+ */
+int
+pipe_cloexec_nonblock(int fd[2]);
+
+#ifndef WIN32
+
+/**
+ * Wrapper for socketpair(), which sets the CLOEXEC flag (atomically
+ * if supported by the OS).
+ */
+int
+socketpair_cloexec(int domain, int type, int protocol, int sv[2]);
+
+#endif
+
+/**
+ * Wrapper for socket(), which sets the CLOEXEC and the NONBLOCK flag
+ * (atomically if supported by the OS).
+ */
+int
+socket_cloexec_nonblock(int domain, int type, int protocol);
+
+/**
+ * Wrapper for accept(), which sets the CLOEXEC and the NONBLOCK flags
+ * (atomically if supported by the OS).
+ */
+int
+accept_cloexec_nonblock(int fd, struct sockaddr *address,
+ size_t *address_length_r);
+
+
+#ifndef WIN32
+
+struct msghdr;
+
+/**
+ * Wrapper for recvmsg(), which sets the CLOEXEC flag (atomically if
+ * supported by the OS).
+ */
+ssize_t
+recvmsg_cloexec(int sockfd, struct msghdr *msg, int flags);
+
+#endif
+
+/**
+ * Wrapper for inotify_init(), which sets the CLOEXEC flag (atomically
+ * if supported by the OS).
+ */
+int
+inotify_init_cloexec(void);
+
+#endif
diff --git a/src/fifo_buffer.c b/src/fifo_buffer.c
index adee438c0..9ac7270bb 100644
--- a/src/fifo_buffer.c
+++ b/src/fifo_buffer.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * Copyright (C) 2003-2010 The Music Player Daemon Project
* http://www.musicpd.org
*
* Redistribution and use in source and binary forms, with or without
@@ -28,6 +28,7 @@
* OF THE POSSIBILITY OF SUCH DAMAGE.
*/
+#include "config.h"
#include "fifo_buffer.h"
#include <glib.h>
diff --git a/src/fifo_buffer.h b/src/fifo_buffer.h
index 4af6bde3b..661dfd57e 100644
--- a/src/fifo_buffer.h
+++ b/src/fifo_buffer.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * Copyright (C) 2003-2010 The Music Player Daemon Project
* http://www.musicpd.org
*
* Redistribution and use in source and binary forms, with or without
diff --git a/src/filter/autoconvert_filter_plugin.c b/src/filter/autoconvert_filter_plugin.c
new file mode 100644
index 000000000..9e197a5f6
--- /dev/null
+++ b/src/filter/autoconvert_filter_plugin.c
@@ -0,0 +1,169 @@
+/*
+ * 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 "filter/autoconvert_filter_plugin.h"
+#include "filter/convert_filter_plugin.h"
+#include "filter_plugin.h"
+#include "filter_internal.h"
+#include "filter_registry.h"
+#include "conf.h"
+#include "pcm_convert.h"
+#include "audio_format.h"
+#include "poison.h"
+
+#include <assert.h>
+#include <string.h>
+
+struct autoconvert_filter {
+ struct filter base;
+
+ /**
+ * The audio format being fed to the underlying filter. This
+ * plugin actually doesn't need this variable, we have it here
+ * just so our open() method doesn't return a stack pointer.
+ */
+ struct audio_format in_audio_format;
+
+ /**
+ * The underlying filter.
+ */
+ struct filter *filter;
+
+ /**
+ * A convert_filter, just in case conversion is needed. NULL
+ * if unused.
+ */
+ struct filter *convert;
+};
+
+static void
+autoconvert_filter_finish(struct filter *_filter)
+{
+ struct autoconvert_filter *filter =
+ (struct autoconvert_filter *)_filter;
+
+ filter_free(filter->filter);
+ g_free(filter);
+}
+
+static const struct audio_format *
+autoconvert_filter_open(struct filter *_filter,
+ struct audio_format *in_audio_format,
+ GError **error_r)
+{
+ struct autoconvert_filter *filter =
+ (struct autoconvert_filter *)_filter;
+ const struct audio_format *out_audio_format;
+
+ assert(audio_format_valid(in_audio_format));
+
+ /* open the "real" filter */
+
+ filter->in_audio_format = *in_audio_format;
+
+ out_audio_format = filter_open(filter->filter,
+ &filter->in_audio_format, error_r);
+ if (out_audio_format == NULL)
+ return NULL;
+
+ /* need to convert? */
+
+ if (!audio_format_equals(&filter->in_audio_format, in_audio_format)) {
+ /* yes - create a convert_filter */
+ struct audio_format audio_format2 = *in_audio_format;
+ const struct audio_format *audio_format3;
+
+ filter->convert = filter_new(&convert_filter_plugin, NULL,
+ error_r);
+ if (filter->convert == NULL) {
+ filter_close(filter->filter);
+ return NULL;
+ }
+
+ audio_format3 = filter_open(filter->convert, &audio_format2,
+ error_r);
+ if (audio_format3 == NULL) {
+ filter_free(filter->convert);
+ filter_close(filter->filter);
+ return NULL;
+ }
+
+ assert(audio_format_equals(&audio_format2, in_audio_format));
+
+ convert_filter_set(filter->convert, &filter->in_audio_format);
+ } else
+ /* no */
+ filter->convert = NULL;
+
+ return out_audio_format;
+}
+
+static void
+autoconvert_filter_close(struct filter *_filter)
+{
+ struct autoconvert_filter *filter =
+ (struct autoconvert_filter *)_filter;
+
+ if (filter->convert != NULL) {
+ filter_close(filter->convert);
+ filter_free(filter->convert);
+ }
+
+ filter_close(filter->filter);
+}
+
+static const void *
+autoconvert_filter_filter(struct filter *_filter, const void *src,
+ size_t src_size, size_t *dest_size_r,
+ GError **error_r)
+{
+ struct autoconvert_filter *filter =
+ (struct autoconvert_filter *)_filter;
+
+ if (filter->convert != NULL) {
+ src = filter_filter(filter->convert, src, src_size, &src_size,
+ error_r);
+ if (src == NULL)
+ return NULL;
+ }
+
+ return filter_filter(filter->filter, src, src_size, dest_size_r,
+ error_r);
+}
+
+static const struct filter_plugin autoconvert_filter_plugin = {
+ .name = "convert",
+ .finish = autoconvert_filter_finish,
+ .open = autoconvert_filter_open,
+ .close = autoconvert_filter_close,
+ .filter = autoconvert_filter_filter,
+};
+
+struct filter *
+autoconvert_filter_new(struct filter *_filter)
+{
+ struct autoconvert_filter *filter =
+ g_new(struct autoconvert_filter, 1);
+
+ filter_init(&filter->base, &autoconvert_filter_plugin);
+ filter->filter = _filter;
+
+ return &filter->base;
+}
diff --git a/src/buffer2array.h b/src/filter/autoconvert_filter_plugin.h
index bed23a29f..730db197d 100644
--- a/src/buffer2array.h
+++ b/src/filter/autoconvert_filter_plugin.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -17,15 +17,18 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
-#ifndef MPD_BUFFER_2_ARRAY_H
-#define MPD_BUFFER_2_ARRAY_H
+#ifndef AUTOCONVERT_FILTER_PLUGIN_H
+#define AUTOCONVERT_FILTER_PLUGIN_H
-/* tokenizes up to max elements in buffer (a null-terminated string) and
- * stores the result in array (which must be capable of holding up to
- * max elements). Tokenization is based on C string quoting rules.
- * The arguments buffer and array are modified.
- * Returns the number of elements tokenized.
+struct filter;
+
+/**
+ * Creates a new "autoconvert" filter. When opened, it ensures that
+ * the input audio format isn't changed. If the underlying filter
+ * requests a different format, it automatically creates a
+ * convert_filter.
*/
-int buffer2array(char *buffer, char *array[], const int max);
+struct filter *
+autoconvert_filter_new(struct filter *filter);
#endif
diff --git a/src/filter/chain_filter_plugin.c b/src/filter/chain_filter_plugin.c
new file mode 100644
index 000000000..06d4d0e6b
--- /dev/null
+++ b/src/filter/chain_filter_plugin.c
@@ -0,0 +1,213 @@
+/*
+ * 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 "conf.h"
+#include "filter/chain_filter_plugin.h"
+#include "filter_plugin.h"
+#include "filter_internal.h"
+#include "filter_registry.h"
+#include "audio_format.h"
+
+#include <assert.h>
+
+struct filter_chain {
+ /** the base class */
+ struct filter base;
+
+ GSList *children;
+};
+
+static inline GQuark
+filter_quark(void)
+{
+ return g_quark_from_static_string("filter");
+}
+
+static struct filter *
+chain_filter_init(G_GNUC_UNUSED const struct config_param *param,
+ G_GNUC_UNUSED GError **error_r)
+{
+ struct filter_chain *chain = g_new(struct filter_chain, 1);
+
+ filter_init(&chain->base, &chain_filter_plugin);
+ chain->children = NULL;
+
+ return &chain->base;
+}
+
+static void
+chain_free_child(gpointer data, G_GNUC_UNUSED gpointer user_data)
+{
+ struct filter *filter = data;
+
+ filter_free(filter);
+}
+
+static void
+chain_filter_finish(struct filter *_filter)
+{
+ struct filter_chain *chain = (struct filter_chain *)_filter;
+
+ g_slist_foreach(chain->children, chain_free_child, NULL);
+ g_slist_free(chain->children);
+
+ g_free(chain);
+}
+
+/**
+ * Close all filters in the chain until #until is reached. #until
+ * itself is not closed.
+ */
+static void
+chain_close_until(struct filter_chain *chain, const struct filter *until)
+{
+ GSList *i = chain->children;
+ struct filter *filter;
+
+ while (true) {
+ /* this assertion fails if #until does not exist
+ (anymore) */
+ assert(i != NULL);
+
+ if (i->data == until)
+ /* don't close this filter */
+ break;
+
+ /* close this filter */
+ filter = i->data;
+ filter_close(filter);
+
+ i = g_slist_next(i);
+ }
+}
+
+static const struct audio_format *
+chain_open_child(struct filter *filter,
+ const struct audio_format *prev_audio_format,
+ GError **error_r)
+{
+ struct audio_format conv_audio_format = *prev_audio_format;
+ const struct audio_format *next_audio_format;
+
+ next_audio_format = filter_open(filter, &conv_audio_format, error_r);
+ if (next_audio_format == NULL)
+ return NULL;
+
+ if (!audio_format_equals(&conv_audio_format, prev_audio_format)) {
+ struct audio_format_string s;
+
+ filter_close(filter);
+ g_set_error(error_r, filter_quark(), 0,
+ "Audio format not supported by filter '%s': %s",
+ filter->plugin->name,
+ audio_format_to_string(prev_audio_format, &s));
+ return NULL;
+ }
+
+ return next_audio_format;
+}
+
+static const struct audio_format *
+chain_filter_open(struct filter *_filter, struct audio_format *in_audio_format,
+ GError **error_r)
+{
+ struct filter_chain *chain = (struct filter_chain *)_filter;
+ const struct audio_format *audio_format = in_audio_format;
+
+ for (GSList *i = chain->children; i != NULL; i = g_slist_next(i)) {
+ struct filter *filter = i->data;
+
+ audio_format = chain_open_child(filter, audio_format, error_r);
+ if (audio_format == NULL) {
+ /* rollback, close all children */
+ chain_close_until(chain, filter);
+ return NULL;
+ }
+ }
+
+ /* return the output format of the last filter */
+ return audio_format;
+}
+
+static void
+chain_close_child(gpointer data, G_GNUC_UNUSED gpointer user_data)
+{
+ struct filter *filter = data;
+
+ filter_close(filter);
+}
+
+static void
+chain_filter_close(struct filter *_filter)
+{
+ struct filter_chain *chain = (struct filter_chain *)_filter;
+
+ g_slist_foreach(chain->children, chain_close_child, NULL);
+}
+
+static const void *
+chain_filter_filter(struct filter *_filter,
+ const void *src, size_t src_size,
+ size_t *dest_size_r, GError **error_r)
+{
+ struct filter_chain *chain = (struct filter_chain *)_filter;
+
+ for (GSList *i = chain->children; i != NULL; i = g_slist_next(i)) {
+ struct filter *filter = i->data;
+
+ /* feed the output of the previous filter as input
+ into the current one */
+ src = filter_filter(filter, src, src_size, &src_size, error_r);
+ if (src == NULL)
+ return NULL;
+ }
+
+ /* return the output of the last filter */
+ *dest_size_r = src_size;
+ return src;
+}
+
+const struct filter_plugin chain_filter_plugin = {
+ .name = "chain",
+ .init = chain_filter_init,
+ .finish = chain_filter_finish,
+ .open = chain_filter_open,
+ .close = chain_filter_close,
+ .filter = chain_filter_filter,
+};
+
+struct filter *
+filter_chain_new(void)
+{
+ struct filter *filter = filter_new(&chain_filter_plugin, NULL, NULL);
+ /* chain_filter_init() never fails */
+ assert(filter != NULL);
+
+ return filter;
+}
+
+void
+filter_chain_append(struct filter *_chain, struct filter *filter)
+{
+ struct filter_chain *chain = (struct filter_chain *)_chain;
+
+ chain->children = g_slist_append(chain->children, filter);
+}
+
diff --git a/src/filter/chain_filter_plugin.h b/src/filter/chain_filter_plugin.h
new file mode 100644
index 000000000..42c6a9b78
--- /dev/null
+++ b/src/filter/chain_filter_plugin.h
@@ -0,0 +1,48 @@
+/*
+ * 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.
+ */
+
+/** \file
+ *
+ * A filter chain is a container for several filters. They are
+ * chained together, i.e. called in a row, one filter passing its
+ * output to the next one.
+ */
+
+#ifndef MPD_FILTER_CHAIN_H
+#define MPD_FILTER_CHAIN_H
+
+struct filter;
+
+/**
+ * Creates a new filter chain.
+ */
+struct filter *
+filter_chain_new(void);
+
+/**
+ * Appends a new filter at the end of the filter chain. You must call
+ * this function before the first filter_open() call.
+ *
+ * @param chain the filter chain created with filter_chain_new()
+ * @param filter the filter to be appended to #chain
+ */
+void
+filter_chain_append(struct filter *chain, struct filter *filter);
+
+#endif
diff --git a/src/filter/convert_filter_plugin.c b/src/filter/convert_filter_plugin.c
new file mode 100644
index 000000000..cb9e0940a
--- /dev/null
+++ b/src/filter/convert_filter_plugin.c
@@ -0,0 +1,147 @@
+/*
+ * 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 "filter/convert_filter_plugin.h"
+#include "filter_plugin.h"
+#include "filter_internal.h"
+#include "filter_registry.h"
+#include "conf.h"
+#include "pcm_convert.h"
+#include "audio_format.h"
+#include "poison.h"
+
+#include <assert.h>
+#include <string.h>
+
+struct convert_filter {
+ struct filter base;
+
+ /**
+ * The current convert, from 0 to #PCM_CONVERT_1.
+ */
+ unsigned convert;
+
+ /**
+ * The input audio format; PCM data is passed to the filter()
+ * method in this format.
+ */
+ struct audio_format in_audio_format;
+
+ /**
+ * The output audio format; the consumer of this plugin
+ * expects PCM data in this format. This defaults to
+ * #in_audio_format, and can be set with convert_filter_set().
+ */
+ struct audio_format out_audio_format;
+
+ struct pcm_convert_state state;
+};
+
+static struct filter *
+convert_filter_init(G_GNUC_UNUSED const struct config_param *param,
+ G_GNUC_UNUSED GError **error_r)
+{
+ struct convert_filter *filter = g_new(struct convert_filter, 1);
+
+ filter_init(&filter->base, &convert_filter_plugin);
+ return &filter->base;
+}
+
+static void
+convert_filter_finish(struct filter *filter)
+{
+ g_free(filter);
+}
+
+static const struct audio_format *
+convert_filter_open(struct filter *_filter, struct audio_format *audio_format,
+ G_GNUC_UNUSED GError **error_r)
+{
+ struct convert_filter *filter = (struct convert_filter *)_filter;
+
+ assert(audio_format_valid(audio_format));
+
+ filter->in_audio_format = filter->out_audio_format = *audio_format;
+ pcm_convert_init(&filter->state);
+
+ return &filter->in_audio_format;
+}
+
+static void
+convert_filter_close(struct filter *_filter)
+{
+ struct convert_filter *filter = (struct convert_filter *)_filter;
+
+ pcm_convert_deinit(&filter->state);
+
+ poison_undefined(&filter->in_audio_format,
+ sizeof(filter->in_audio_format));
+ poison_undefined(&filter->out_audio_format,
+ sizeof(filter->out_audio_format));
+}
+
+static const void *
+convert_filter_filter(struct filter *_filter, const void *src, size_t src_size,
+ size_t *dest_size_r, GError **error_r)
+{
+ struct convert_filter *filter = (struct convert_filter *)_filter;
+ const void *dest;
+
+ if (audio_format_equals(&filter->in_audio_format,
+ &filter->out_audio_format)) {
+ /* optimized special case: no-op */
+ *dest_size_r = src_size;
+ return src;
+ }
+
+ dest = pcm_convert(&filter->state, &filter->in_audio_format,
+ src, src_size,
+ &filter->out_audio_format, dest_size_r,
+ error_r);
+ if (dest == NULL)
+ return NULL;
+
+ return dest;
+}
+
+const struct filter_plugin convert_filter_plugin = {
+ .name = "convert",
+ .init = convert_filter_init,
+ .finish = convert_filter_finish,
+ .open = convert_filter_open,
+ .close = convert_filter_close,
+ .filter = convert_filter_filter,
+};
+
+void
+convert_filter_set(struct filter *_filter,
+ const struct audio_format *out_audio_format)
+{
+ struct convert_filter *filter = (struct convert_filter *)_filter;
+
+ assert(filter != NULL);
+ assert(audio_format_valid(&filter->in_audio_format));
+ 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
new file mode 100644
index 000000000..ba9180e64
--- /dev/null
+++ b/src/filter/convert_filter_plugin.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef CONVERT_FILTER_PLUGIN_H
+#define CONVERT_FILTER_PLUGIN_H
+
+struct filter;
+struct audio_format;
+
+/**
+ * Sets the output audio format for the specified filter. You must
+ * call this after the filter has been opened. Since this audio
+ * format switch is a violation of the filter API, this filter must be
+ * the last in a chain.
+ */
+void
+convert_filter_set(struct filter *filter,
+ const struct audio_format *out_audio_format);
+
+#endif
diff --git a/src/filter/normalize_filter_plugin.c b/src/filter/normalize_filter_plugin.c
new file mode 100644
index 000000000..63bbb6e4f
--- /dev/null
+++ b/src/filter/normalize_filter_plugin.c
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "filter_plugin.h"
+#include "filter_internal.h"
+#include "filter_registry.h"
+#include "pcm_buffer.h"
+#include "audio_format.h"
+#include "AudioCompress/compress.h"
+
+#include <assert.h>
+#include <string.h>
+
+struct normalize_filter {
+ struct filter filter;
+
+ struct Compressor *compressor;
+
+ struct pcm_buffer buffer;
+};
+
+static inline GQuark
+normalize_quark(void)
+{
+ return g_quark_from_static_string("normalize");
+}
+
+static struct filter *
+normalize_filter_init(G_GNUC_UNUSED const struct config_param *param,
+ G_GNUC_UNUSED GError **error_r)
+{
+ struct normalize_filter *filter = g_new(struct normalize_filter, 1);
+
+ filter_init(&filter->filter, &normalize_filter_plugin);
+
+ return &filter->filter;
+}
+
+static void
+normalize_filter_finish(struct filter *filter)
+{
+ g_free(filter);
+}
+
+static const struct audio_format *
+normalize_filter_open(struct filter *_filter,
+ struct audio_format *audio_format,
+ G_GNUC_UNUSED GError **error_r)
+{
+ struct normalize_filter *filter = (struct normalize_filter *)_filter;
+
+ audio_format->format = SAMPLE_FORMAT_S16;
+ audio_format->reverse_endian = false;
+
+ filter->compressor = Compressor_new(0);
+
+ pcm_buffer_init(&filter->buffer);
+
+ return audio_format;
+}
+
+static void
+normalize_filter_close(struct filter *_filter)
+{
+ struct normalize_filter *filter = (struct normalize_filter *)_filter;
+
+ pcm_buffer_deinit(&filter->buffer);
+ Compressor_delete(filter->compressor);
+}
+
+static const void *
+normalize_filter_filter(struct filter *_filter,
+ const void *src, size_t src_size, size_t *dest_size_r,
+ G_GNUC_UNUSED GError **error_r)
+{
+ struct normalize_filter *filter = (struct normalize_filter *)_filter;
+ void *dest;
+
+ dest = pcm_buffer_get(&filter->buffer, src_size);
+
+ memcpy(dest, src, src_size);
+
+ Compressor_Process_int16(filter->compressor, dest, src_size / 2);
+
+ *dest_size_r = src_size;
+ return dest;
+}
+
+const struct filter_plugin normalize_filter_plugin = {
+ .name = "normalize",
+ .init = normalize_filter_init,
+ .finish = normalize_filter_finish,
+ .open = normalize_filter_open,
+ .close = normalize_filter_close,
+ .filter = normalize_filter_filter,
+};
diff --git a/src/filter/null_filter_plugin.c b/src/filter/null_filter_plugin.c
new file mode 100644
index 000000000..650f95bc4
--- /dev/null
+++ b/src/filter/null_filter_plugin.c
@@ -0,0 +1,93 @@
+/*
+ * 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.
+ */
+
+/** \file
+ *
+ * This filter plugin does nothing. That is not quite useful, except
+ * for testing the filter core, or as a template for new filter
+ * plugins.
+ */
+
+#include "config.h"
+#include "filter_plugin.h"
+#include "filter_internal.h"
+#include "filter_registry.h"
+
+#include <assert.h>
+
+struct null_filter {
+ struct filter filter;
+};
+
+static struct filter *
+null_filter_init(G_GNUC_UNUSED const struct config_param *param,
+ G_GNUC_UNUSED GError **error_r)
+{
+ struct null_filter *filter = g_new(struct null_filter, 1);
+
+ filter_init(&filter->filter, &null_filter_plugin);
+ return &filter->filter;
+}
+
+static void
+null_filter_finish(struct filter *_filter)
+{
+ struct null_filter *filter = (struct null_filter *)_filter;
+
+ g_free(filter);
+}
+
+static const struct audio_format *
+null_filter_open(struct filter *_filter, struct audio_format *audio_format,
+ G_GNUC_UNUSED GError **error_r)
+{
+ struct null_filter *filter = (struct null_filter *)_filter;
+ (void)filter;
+
+ return audio_format;
+}
+
+static void
+null_filter_close(struct filter *_filter)
+{
+ struct null_filter *filter = (struct null_filter *)_filter;
+ (void)filter;
+}
+
+static const void *
+null_filter_filter(struct filter *_filter,
+ const void *src, size_t src_size,
+ size_t *dest_size_r, G_GNUC_UNUSED GError **error_r)
+{
+ struct null_filter *filter = (struct null_filter *)_filter;
+ (void)filter;
+
+ /* return the unmodified source buffer */
+ *dest_size_r = src_size;
+ return src;
+}
+
+const struct filter_plugin null_filter_plugin = {
+ .name = "null",
+ .init = null_filter_init,
+ .finish = null_filter_finish,
+ .open = null_filter_open,
+ .close = null_filter_close,
+ .filter = null_filter_filter,
+};
diff --git a/src/filter/replay_gain_filter_plugin.c b/src/filter/replay_gain_filter_plugin.c
new file mode 100644
index 000000000..3a0af66ff
--- /dev/null
+++ b/src/filter/replay_gain_filter_plugin.c
@@ -0,0 +1,247 @@
+/*
+ * 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 "filter/replay_gain_filter_plugin.h"
+#include "filter_plugin.h"
+#include "filter_internal.h"
+#include "filter_registry.h"
+#include "audio_format.h"
+#include "pcm_buffer.h"
+#include "pcm_volume.h"
+#include "replay_gain_info.h"
+#include "replay_gain_config.h"
+#include "mixer_control.h"
+
+#include <assert.h>
+#include <string.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "replay_gain"
+
+struct replay_gain_filter {
+ struct filter filter;
+
+ /**
+ * If set, then this hardware mixer is used for applying
+ * replay gain, instead of the software volume library.
+ */
+ struct mixer *mixer;
+
+ /**
+ * The base volume level for scale=1.0, between 1 and 100
+ * (including).
+ */
+ unsigned base;
+
+ enum replay_gain_mode mode;
+
+ struct replay_gain_info info;
+
+ /**
+ * The current volume, between 0 and a value that may or may not exceed
+ * #PCM_VOLUME_1.
+ *
+ * If the default value of true is used for replaygain_limit, the
+ * application of the volume to the signal will never cause clipping.
+ *
+ * On the other hand, if the user has set replaygain_limit to false,
+ * the chance of clipping is explicitly preferred if that's required to
+ * maintain a consistent audio level. Whether clipping will actually
+ * occur depends on what value the user is using for replaygain_preamp.
+ */
+ unsigned volume;
+
+ struct audio_format audio_format;
+
+ struct pcm_buffer buffer;
+};
+
+static inline GQuark
+replay_gain_quark(void)
+{
+ return g_quark_from_static_string("replay_gain");
+}
+
+/**
+ * Recalculates the new volume after a property was changed.
+ */
+static void
+replay_gain_filter_update(struct replay_gain_filter *filter)
+{
+ if (filter->mode != REPLAY_GAIN_OFF) {
+ float scale = replay_gain_tuple_scale(&filter->info.tuples[filter->mode],
+ replay_gain_preamp, replay_gain_missing_preamp, replay_gain_limit);
+ g_debug("scale=%f\n", (double)scale);
+
+ filter->volume = pcm_float_to_volume(scale);
+ } else
+ filter->volume = PCM_VOLUME_1;
+
+ if (filter->mixer != NULL) {
+ /* update the hardware mixer volume */
+
+ unsigned volume = (filter->volume * filter->base) / PCM_VOLUME_1;
+ if (volume > 100)
+ volume = 100;
+
+ GError *error = NULL;
+ if (!mixer_set_volume(filter->mixer, volume, &error)) {
+ g_warning("Failed to update hardware mixer: %s",
+ error->message);
+ g_error_free(error);
+ }
+ }
+}
+
+static struct filter *
+replay_gain_filter_init(G_GNUC_UNUSED const struct config_param *param,
+ G_GNUC_UNUSED GError **error_r)
+{
+ struct replay_gain_filter *filter = g_new(struct replay_gain_filter, 1);
+
+ filter_init(&filter->filter, &replay_gain_filter_plugin);
+ filter->mixer = NULL;
+
+ filter->mode = replay_gain_get_real_mode();
+ replay_gain_info_init(&filter->info);
+ filter->volume = PCM_VOLUME_1;
+
+ return &filter->filter;
+}
+
+static void
+replay_gain_filter_finish(struct filter *filter)
+{
+ g_free(filter);
+}
+
+static const struct audio_format *
+replay_gain_filter_open(struct filter *_filter,
+ struct audio_format *audio_format,
+ G_GNUC_UNUSED GError **error_r)
+{
+ 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);
+
+ return &filter->audio_format;
+}
+
+static void
+replay_gain_filter_close(struct filter *_filter)
+{
+ struct replay_gain_filter *filter =
+ (struct replay_gain_filter *)_filter;
+
+ pcm_buffer_deinit(&filter->buffer);
+}
+
+static const void *
+replay_gain_filter_filter(struct filter *_filter,
+ const void *src, size_t src_size,
+ size_t *dest_size_r, GError **error_r)
+{
+ struct replay_gain_filter *filter =
+ (struct replay_gain_filter *)_filter;
+ bool success;
+ void *dest;
+ enum replay_gain_mode rg_mode;
+
+ /* check if the mode has been changed since the last call */
+ rg_mode = replay_gain_get_real_mode();
+
+ if (filter->mode != rg_mode) {
+ g_debug("replay gain mode has changed %d->%d\n", filter->mode, rg_mode);
+ filter->mode = rg_mode;
+ replay_gain_filter_update(filter);
+ }
+
+ *dest_size_r = src_size;
+
+ if (filter->volume == PCM_VOLUME_1)
+ /* optimized special case: 100% volume = no-op */
+ return src;
+
+ dest = pcm_buffer_get(&filter->buffer, src_size);
+
+ if (filter->volume <= 0) {
+ /* optimized special case: 0% volume = memset(0) */
+ /* XXX is this valid for all sample formats? What
+ about floating point? */
+ memset(dest, 0, src_size);
+ return dest;
+ }
+
+ memcpy(dest, src, src_size);
+
+ success = pcm_volume(dest, src_size, &filter->audio_format,
+ filter->volume);
+ if (!success) {
+ g_set_error(error_r, replay_gain_quark(), 0,
+ "pcm_volume() has failed");
+ return NULL;
+ }
+
+ return dest;
+}
+
+const struct filter_plugin replay_gain_filter_plugin = {
+ .name = "replay_gain",
+ .init = replay_gain_filter_init,
+ .finish = replay_gain_filter_finish,
+ .open = replay_gain_filter_open,
+ .close = replay_gain_filter_close,
+ .filter = replay_gain_filter_filter,
+};
+
+void
+replay_gain_filter_set_mixer(struct filter *_filter, struct mixer *mixer,
+ unsigned base)
+{
+ struct replay_gain_filter *filter =
+ (struct replay_gain_filter *)_filter;
+
+ assert(mixer == NULL || (base > 0 && base <= 100));
+
+ filter->mixer = mixer;
+ filter->base = base;
+
+ replay_gain_filter_update(filter);
+}
+
+void
+replay_gain_filter_set_info(struct filter *_filter,
+ const struct replay_gain_info *info)
+{
+ struct replay_gain_filter *filter =
+ (struct replay_gain_filter *)_filter;
+
+ if (info != NULL) {
+ filter->info = *info;
+ replay_gain_info_complete(&filter->info);
+ } else
+ replay_gain_info_init(&filter->info);
+
+ replay_gain_filter_update(filter);
+}
diff --git a/src/filter/replay_gain_filter_plugin.h b/src/filter/replay_gain_filter_plugin.h
new file mode 100644
index 000000000..348b4f50c
--- /dev/null
+++ b/src/filter/replay_gain_filter_plugin.h
@@ -0,0 +1,50 @@
+/*
+ * 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 REPLAY_GAIN_FILTER_PLUGIN_H
+#define REPLAY_GAIN_FILTER_PLUGIN_H
+
+#include "replay_gain_info.h"
+
+struct filter;
+struct mixer;
+
+/**
+ * Enables or disables the hardware mixer for applying replay gain.
+ *
+ * @param mixer the hardware mixer, or NULL to fall back to software
+ * volume
+ * @param base the base volume level for scale=1.0, between 1 and 100
+ * (including).
+ */
+void
+replay_gain_filter_set_mixer(struct filter *_filter, struct mixer *mixer,
+ unsigned base);
+
+/**
+ * Sets a new #replay_gain_info at the beginning of a new song.
+ *
+ * @param info the new #replay_gain_info value, or NULL if no replay
+ * gain data is available for the current song
+ */
+void
+replay_gain_filter_set_info(struct filter *filter,
+ const struct replay_gain_info *info);
+
+#endif
diff --git a/src/filter/route_filter_plugin.c b/src/filter/route_filter_plugin.c
new file mode 100644
index 000000000..6b9aa2a2f
--- /dev/null
+++ b/src/filter/route_filter_plugin.c
@@ -0,0 +1,348 @@
+/*
+ * 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.
+ */
+
+/** \file
+ *
+ * This filter copies audio data between channels. Useful for
+ * upmixing mono/stereo audio to surround speaker configurations.
+ *
+ * Its configuration consists of a "filter" section with a single
+ * "routes" entry, formatted as: \\
+ * routes "0>1, 1>0, 2>2, 3>3, 3>4" \\
+ * where each pair of numbers signifies a set of channels.
+ * Each source>dest pair leads to the data from channel #source
+ * being copied to channel #dest in the output.
+ *
+ * Example: \\
+ * routes "0>0, 1>1, 0>2, 1>3"\\
+ * upmixes stereo audio to a 4-speaker system, copying the front-left
+ * (0) to front left (0) and rear left (2), copying front-right (1) to
+ * front-right (1) and rear-right (3).
+ *
+ * If multiple sources are copied to the same destination channel, only
+ * one of them takes effect.
+ */
+
+#include "config.h"
+#include "conf.h"
+#include "audio_format.h"
+#include "audio_check.h"
+#include "filter_plugin.h"
+#include "filter_internal.h"
+#include "filter_registry.h"
+#include "pcm_buffer.h"
+
+#include <assert.h>
+#include <string.h>
+#include <stdlib.h>
+
+
+struct route_filter {
+
+ /**
+ * Inherit (and support cast to/from) filter
+ */
+ struct filter base;
+
+ /**
+ * The minimum number of channels we need for output
+ * to be able to perform all the copies the user has specified
+ */
+ unsigned char min_output_channels;
+
+ /**
+ * The minimum number of input channels we need to
+ * copy all the data the user has requested. If fewer
+ * than this many are supplied by the input, undefined
+ * copy operations are given zeroed sources in stead.
+ */
+ unsigned char min_input_channels;
+
+ /**
+ * The set of copy operations to perform on each sample
+ * The index is an output channel to use, the value is
+ * a corresponding input channel from which to take the
+ * data. A -1 means "no source"
+ */
+ signed char* sources;
+
+ /**
+ * The actual input format of our signal, once opened
+ */
+ struct audio_format input_format;
+
+ /**
+ * The decided upon output format, once opened
+ */
+ struct audio_format output_format;
+
+ /**
+ * The size, in bytes, of each multichannel frame in the
+ * input buffer
+ */
+ size_t input_frame_size;
+
+ /**
+ * The size, in bytes, of each multichannel frame in the
+ * output buffer
+ */
+ size_t output_frame_size;
+
+ /**
+ * The output buffer used last time around, can be reused if the size doesn't differ.
+ */
+ struct pcm_buffer output_buffer;
+
+};
+
+/**
+ * Parse the "routes" section, a string on the form
+ * a>b, c>d, e>f, ...
+ * where a... are non-unique, non-negative integers
+ * and input channel a gets copied to output channel b, etc.
+ * @param param the configuration block to read
+ * @param filter a route_filter whose min_channels and sources[] to set
+ * @return true on success, false on error
+ */
+static bool
+route_filter_parse(const struct config_param *param,
+ struct route_filter *filter,
+ GError **error_r) {
+
+ /* TODO:
+ * With a more clever way of marking "don't copy to output N",
+ * This could easily be merged into a single loop with some
+ * dynamic g_realloc() instead of one count run and one g_malloc().
+ */
+
+ gchar **tokens;
+ int number_of_copies;
+
+ // A cowardly default, just passthrough stereo
+ const char *routes =
+ config_get_block_string(param, "routes", "0>0, 1>1");
+
+ filter->min_input_channels = 0;
+ filter->min_output_channels = 0;
+
+ tokens = g_strsplit(routes, ",", 255);
+ number_of_copies = g_strv_length(tokens);
+
+ // Start by figuring out a few basic things about the routing set
+ for (int c=0; c<number_of_copies; ++c) {
+
+ // String and int representations of the source/destination
+ gchar **sd;
+ int source, dest;
+
+ // Squeeze whitespace
+ g_strstrip(tokens[c]);
+
+ // Split the a>b string into source and destination
+ sd = g_strsplit(tokens[c], ">", 2);
+ if (g_strv_length(sd) != 2) {
+ g_set_error(error_r, config_quark(), 1,
+ "Invalid copy around %d in routes spec: %s",
+ param->line, tokens[c]);
+ g_strfreev(sd);
+ g_strfreev(tokens);
+ return false;
+ }
+
+ source = strtol(sd[0], NULL, 10);
+ dest = strtol(sd[1], NULL, 10);
+
+ // Keep track of the highest channel numbers seen
+ // as either in- or outputs
+ if (source >= filter->min_input_channels)
+ filter->min_input_channels = source + 1;
+ if (dest >= filter->min_output_channels)
+ filter->min_output_channels = dest + 1;
+
+ g_strfreev(sd);
+ }
+
+ if (!audio_valid_channel_count(filter->min_output_channels)) {
+ g_strfreev(tokens);
+ g_set_error(error_r, audio_format_quark(), 0,
+ "Invalid number of output channels requested: %d",
+ filter->min_output_channels);
+ return false;
+ }
+
+ // Allocate a map of "copy nothing to me"
+ filter->sources =
+ g_malloc(filter->min_output_channels * sizeof(signed char));
+
+ for (int i=0; i<filter->min_output_channels; ++i)
+ filter->sources[i] = -1;
+
+ // Run through the spec again, and save the
+ // actual mapping output <- input
+ for (int c=0; c<number_of_copies; ++c) {
+
+ // String and int representations of the source/destination
+ gchar **sd;
+ int source, dest;
+
+ // Split the a>b string into source and destination
+ sd = g_strsplit(tokens[c], ">", 2);
+ if (g_strv_length(sd) != 2) {
+ g_set_error(error_r, config_quark(), 1,
+ "Invalid copy around %d in routes spec: %s",
+ param->line, tokens[c]);
+ g_strfreev(sd);
+ g_strfreev(tokens);
+ return false;
+ }
+
+ source = strtol(sd[0], NULL, 10);
+ dest = strtol(sd[1], NULL, 10);
+
+ filter->sources[dest] = source;
+
+ g_strfreev(sd);
+ }
+
+ g_strfreev(tokens);
+
+ return true;
+}
+
+static struct filter *
+route_filter_init(const struct config_param *param,
+ G_GNUC_UNUSED GError **error_r)
+{
+ struct route_filter *filter = g_new(struct route_filter, 1);
+ filter_init(&filter->base, &route_filter_plugin);
+
+ // Allocate and set the filter->sources[] array
+ route_filter_parse(param, filter, error_r);
+
+ return &filter->base;
+}
+
+static void
+route_filter_finish(struct filter *_filter)
+{
+ struct route_filter *filter = (struct route_filter *)_filter;
+
+ g_free(filter->sources);
+ g_free(filter);
+}
+
+static const struct audio_format *
+route_filter_open(struct filter *_filter, struct audio_format *audio_format,
+ G_GNUC_UNUSED GError **error_r)
+{
+ struct route_filter *filter = (struct route_filter *)_filter;
+
+ // Copy the input format for later reference
+ filter->input_format = *audio_format;
+ filter->input_frame_size =
+ audio_format_frame_size(&filter->input_format);
+
+ // Decide on an output format which has enough channels,
+ // and is otherwise identical
+ filter->output_format = *audio_format;
+ filter->output_format.channels = filter->min_output_channels;
+
+ // Precalculate this simple value, to speed up allocation later
+ filter->output_frame_size =
+ audio_format_frame_size(&filter->output_format);
+
+ // This buffer grows as needed
+ pcm_buffer_init(&filter->output_buffer);
+
+ return &filter->output_format;
+}
+
+static void
+route_filter_close(struct filter *_filter)
+{
+ struct route_filter *filter = (struct route_filter *)_filter;
+
+ pcm_buffer_deinit(&filter->output_buffer);
+}
+
+static const void *
+route_filter_filter(struct filter *_filter,
+ const void *src, size_t src_size,
+ size_t *dest_size_r, G_GNUC_UNUSED GError **error_r)
+{
+ struct route_filter *filter = (struct route_filter *)_filter;
+
+ size_t number_of_frames = src_size / filter->input_frame_size;
+
+ size_t bytes_per_frame_per_channel =
+ audio_format_sample_size(&filter->input_format);
+
+ // A moving pointer that always refers to channel 0 in the input, at the currently handled frame
+ const uint8_t *base_source = src;
+
+ // A moving pointer that always refers to the currently filled channel of the currently handled frame, in the output
+ uint8_t *chan_destination;
+
+ // Grow our reusable buffer, if needed, and set the moving pointer
+ *dest_size_r = number_of_frames * filter->output_frame_size;
+ chan_destination = pcm_buffer_get(&filter->output_buffer, *dest_size_r);
+
+
+ // Perform our copy operations, with N input channels and M output channels
+ for (unsigned int s=0; s<number_of_frames; ++s) {
+
+ // Need to perform one copy per output channel
+ for (unsigned int c=0; c<filter->min_output_channels; ++c) {
+ if (filter->sources[c] == -1 ||
+ (unsigned)filter->sources[c] >= filter->input_format.channels) {
+ // No source for this destination output,
+ // give it zeroes as input
+ memset(chan_destination,
+ 0x00,
+ bytes_per_frame_per_channel);
+ } else {
+ // Get the data from channel sources[c]
+ // and copy it to the output
+ const uint8_t *data = base_source +
+ (filter->sources[c] * bytes_per_frame_per_channel);
+ memcpy(chan_destination,
+ data,
+ bytes_per_frame_per_channel);
+ }
+ // Move on to the next output channel
+ chan_destination += bytes_per_frame_per_channel;
+ }
+
+
+ // Go on to the next N input samples
+ base_source += filter->input_frame_size;
+ }
+
+ // Here it is, ladies and gentlemen! Rerouted data!
+ return (void *) filter->output_buffer.buffer;
+}
+
+const struct filter_plugin route_filter_plugin = {
+ .name = "route",
+ .init = route_filter_init,
+ .finish = route_filter_finish,
+ .open = route_filter_open,
+ .close = route_filter_close,
+ .filter = route_filter_filter,
+};
diff --git a/src/filter/volume_filter_plugin.c b/src/filter/volume_filter_plugin.c
new file mode 100644
index 000000000..42311ca5e
--- /dev/null
+++ b/src/filter/volume_filter_plugin.c
@@ -0,0 +1,161 @@
+/*
+ * 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 "filter/volume_filter_plugin.h"
+#include "filter_plugin.h"
+#include "filter_internal.h"
+#include "filter_registry.h"
+#include "conf.h"
+#include "pcm_buffer.h"
+#include "pcm_volume.h"
+#include "audio_format.h"
+#include "player_control.h"
+
+#include <assert.h>
+#include <string.h>
+
+struct volume_filter {
+ struct filter filter;
+
+ /**
+ * The current volume, from 0 to #PCM_VOLUME_1.
+ */
+ unsigned volume;
+
+ struct audio_format audio_format;
+
+ struct pcm_buffer buffer;
+};
+
+static inline GQuark
+volume_quark(void)
+{
+ return g_quark_from_static_string("pcm_volume");
+}
+
+static struct filter *
+volume_filter_init(G_GNUC_UNUSED const struct config_param *param,
+ G_GNUC_UNUSED GError **error_r)
+{
+ struct volume_filter *filter = g_new(struct volume_filter, 1);
+
+ filter_init(&filter->filter, &volume_filter_plugin);
+ filter->volume = PCM_VOLUME_1;
+
+ return &filter->filter;
+}
+
+static void
+volume_filter_finish(struct filter *filter)
+{
+ g_free(filter);
+}
+
+static const struct audio_format *
+volume_filter_open(struct filter *_filter, struct audio_format *audio_format,
+ G_GNUC_UNUSED GError **error_r)
+{
+ struct volume_filter *filter = (struct volume_filter *)_filter;
+
+ audio_format->reverse_endian = false;
+
+ filter->audio_format = *audio_format;
+ pcm_buffer_init(&filter->buffer);
+
+ return &filter->audio_format;
+}
+
+static void
+volume_filter_close(struct filter *_filter)
+{
+ struct volume_filter *filter = (struct volume_filter *)_filter;
+
+ pcm_buffer_deinit(&filter->buffer);
+}
+
+static const void *
+volume_filter_filter(struct filter *_filter, const void *src, size_t src_size,
+ size_t *dest_size_r, GError **error_r)
+{
+ struct volume_filter *filter = (struct volume_filter *)_filter;
+ bool success;
+ void *dest;
+
+ *dest_size_r = src_size;
+
+ if (filter->volume >= PCM_VOLUME_1)
+ /* optimized special case: 100% volume = no-op */
+ return src;
+
+ dest = pcm_buffer_get(&filter->buffer, src_size);
+
+ if (filter->volume <= 0) {
+ /* optimized special case: 0% volume = memset(0) */
+ /* XXX is this valid for all sample formats? What
+ about floating point? */
+ memset(dest, 0, src_size);
+ return dest;
+ }
+
+ memcpy(dest, src, src_size);
+
+ success = pcm_volume(dest, src_size, &filter->audio_format,
+ filter->volume);
+ if (!success) {
+ g_set_error(error_r, volume_quark(), 0,
+ "pcm_volume() has failed");
+ return NULL;
+ }
+
+ return dest;
+}
+
+const struct filter_plugin volume_filter_plugin = {
+ .name = "volume",
+ .init = volume_filter_init,
+ .finish = volume_filter_finish,
+ .open = volume_filter_open,
+ .close = volume_filter_close,
+ .filter = volume_filter_filter,
+};
+
+unsigned
+volume_filter_get(const struct filter *_filter)
+{
+ const struct volume_filter *filter =
+ (const struct volume_filter *)_filter;
+
+ assert(filter->filter.plugin == &volume_filter_plugin);
+ assert(filter->volume <= PCM_VOLUME_1);
+
+ return filter->volume;
+}
+
+void
+volume_filter_set(struct filter *_filter, unsigned volume)
+{
+ struct volume_filter *filter = (struct volume_filter *)_filter;
+
+ assert(filter->filter.plugin == &volume_filter_plugin);
+ assert(volume <= PCM_VOLUME_1);
+
+ filter->volume = volume;
+}
+
diff --git a/src/filter/volume_filter_plugin.h b/src/filter/volume_filter_plugin.h
new file mode 100644
index 000000000..ad3b2c6f1
--- /dev/null
+++ b/src/filter/volume_filter_plugin.h
@@ -0,0 +1,31 @@
+/*
+ * 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 VOLUME_FILTER_PLUGIN_H
+#define VOLUME_FILTER_PLUGIN_H
+
+struct filter;
+
+unsigned
+volume_filter_get(const struct filter *filter);
+
+void
+volume_filter_set(struct filter *filter, unsigned volume);
+
+#endif
diff --git a/src/filter_config.c b/src/filter_config.c
new file mode 100644
index 000000000..90de199b7
--- /dev/null
+++ b/src/filter_config.c
@@ -0,0 +1,119 @@
+/*
+ * 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 "filter_config.h"
+#include "config.h"
+#include "conf.h"
+#include "filter/chain_filter_plugin.h"
+#include "filter_plugin.h"
+#include "filter_internal.h"
+#include "filter_registry.h"
+
+#include <string.h>
+
+
+static GQuark
+filter_quark(void)
+{
+ return g_quark_from_static_string("filter");
+}
+
+/**
+ * Find the "filter" configuration block for the specified name.
+ *
+ * @param filter_template_name the name of the filter template
+ * @param error_r space to return an error description
+ * @return the configuration block, or NULL if none was configured
+ */
+static const struct config_param *
+filter_plugin_config(const char *filter_template_name, GError **error_r)
+{
+ const struct config_param *param = NULL;
+
+ while ((param = config_get_next_param(CONF_AUDIO_FILTER, param)) != NULL) {
+ const char *name =
+ config_get_block_string(param, "name", NULL);
+ if (name == NULL) {
+ g_set_error(error_r, filter_quark(), 1,
+ "filter configuration without 'name' name in line %d",
+ param->line);
+ return NULL;
+ }
+
+ if (strcmp(name, filter_template_name) == 0)
+ return param;
+ }
+
+ g_set_error(error_r, filter_quark(), 1,
+ "filter template not found: %s",
+ filter_template_name);
+
+ return NULL;
+}
+
+/**
+ * Builds a filter chain from a configuration string on the form
+ * "name1, name2, name3, ..." by looking up each name among the
+ * configured filter sections.
+ * @param chain the chain to append filters on
+ * @param spec the filter chain specification
+ * @param error_r space to return an error description
+ * @return the number of filters which were successfully added
+ */
+unsigned int
+filter_chain_parse(struct filter *chain, const char *spec, GError **error_r)
+{
+
+ // Split on comma
+ gchar** tokens = g_strsplit_set(spec, ",", 255);
+
+ int added_filters = 0;
+
+ // Add each name to the filter chain by instantiating an actual filter
+ char **template_names = tokens;
+ while (*template_names != NULL) {
+ struct filter *f;
+ const struct config_param *cfg;
+
+ // Squeeze whitespace
+ g_strstrip(*template_names);
+
+ cfg = filter_plugin_config(*template_names, error_r);
+ if (cfg == NULL) {
+ // The error has already been set, just stop.
+ break;
+ }
+
+ // Instantiate one of those filter plugins with the template name as a hint
+ f = filter_configured_new(cfg, error_r);
+ if (f == NULL) {
+ // The error has already been set, just stop.
+ break;
+ }
+
+ filter_chain_append(chain, f);
+ ++added_filters;
+
+ ++template_names;
+ }
+
+ g_strfreev(tokens);
+
+ return added_filters;
+}
diff --git a/src/filter_config.h b/src/filter_config.h
new file mode 100644
index 000000000..9ed4d204b
--- /dev/null
+++ b/src/filter_config.h
@@ -0,0 +1,47 @@
+/*
+ * 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.
+ */
+
+/** \file
+ *
+ * Utility functions for filter configuration
+ */
+
+#ifndef MPD_FILTER_CONFIG_H
+#define MPD_FILTER_CONFIG_H
+
+#include "conf.h"
+#include "filter/chain_filter_plugin.h"
+#include "filter_plugin.h"
+#include "filter_internal.h"
+#include "filter_registry.h"
+
+
+/**
+ * Builds a filter chain from a configuration string on the form
+ * "name1, name2, name3, ..." by looking up each name among the
+ * configured filter sections.
+ * @param chain the chain to append filters on
+ * @param spec the filter chain specification
+ * @param error_r space to return an error description
+ * @return the number of filters which were successfully added
+ */
+unsigned int
+filter_chain_parse(struct filter *chain, const char *spec, GError **error_r);
+
+#endif
diff --git a/src/filter_internal.h b/src/filter_internal.h
new file mode 100644
index 000000000..8dd6da491
--- /dev/null
+++ b/src/filter_internal.h
@@ -0,0 +1,38 @@
+/*
+ * 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.
+ */
+
+/** \file
+ *
+ * Internal stuff for the filter core and filter plugins.
+ */
+
+#ifndef MPD_FILTER_INTERNAL_H
+#define MPD_FILTER_INTERNAL_H
+
+struct filter {
+ const struct filter_plugin *plugin;
+};
+
+static inline void
+filter_init(struct filter *filter, const struct filter_plugin *plugin)
+{
+ filter->plugin = plugin;
+}
+
+#endif
diff --git a/src/filter_plugin.c b/src/filter_plugin.c
new file mode 100644
index 000000000..492d703ac
--- /dev/null
+++ b/src/filter_plugin.c
@@ -0,0 +1,116 @@
+/*
+ * 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 "filter_plugin.h"
+#include "filter_internal.h"
+#include "filter_registry.h"
+#include "conf.h"
+
+#ifndef NDEBUG
+#include "audio_format.h"
+#endif
+
+#include <assert.h>
+
+struct filter *
+filter_new(const struct filter_plugin *plugin,
+ const struct config_param *param, GError **error_r)
+{
+ assert(plugin != NULL);
+ assert(error_r == NULL || *error_r == NULL);
+
+ return plugin->init(param, error_r);
+}
+
+struct filter *
+filter_configured_new(const struct config_param *param, GError **error_r)
+{
+ const char *plugin_name;
+ const struct filter_plugin *plugin;
+
+ assert(param != NULL);
+ assert(error_r == NULL || *error_r == NULL);
+
+ plugin_name = config_get_block_string(param, "plugin", NULL);
+ if (plugin_name == NULL) {
+ g_set_error(error_r, config_quark(), 0,
+ "No filter plugin specified");
+ return NULL;
+ }
+
+ plugin = filter_plugin_by_name(plugin_name);
+ if (plugin == NULL) {
+ g_set_error(error_r, config_quark(), 0,
+ "No such filter plugin: %s", plugin_name);
+ return NULL;
+ }
+
+ return filter_new(plugin, param, error_r);
+}
+
+void
+filter_free(struct filter *filter)
+{
+ assert(filter != NULL);
+
+ filter->plugin->finish(filter);
+}
+
+const struct audio_format *
+filter_open(struct filter *filter, struct audio_format *audio_format,
+ GError **error_r)
+{
+ const struct audio_format *out_audio_format;
+
+ assert(filter != NULL);
+ assert(audio_format != NULL);
+ assert(audio_format_valid(audio_format));
+ assert(error_r == NULL || *error_r == NULL);
+
+ out_audio_format = filter->plugin->open(filter, audio_format, error_r);
+
+ assert(out_audio_format == NULL || audio_format_valid(audio_format));
+ assert(out_audio_format == NULL ||
+ audio_format_valid(out_audio_format));
+
+ return out_audio_format;
+}
+
+void
+filter_close(struct filter *filter)
+{
+ assert(filter != NULL);
+
+ filter->plugin->close(filter);
+}
+
+const void *
+filter_filter(struct filter *filter, const void *src, size_t src_size,
+ size_t *dest_size_r,
+ GError **error_r)
+{
+ assert(filter != NULL);
+ assert(src != NULL);
+ assert(src_size > 0);
+ assert(dest_size_r != NULL);
+ assert(error_r == NULL || *error_r == NULL);
+
+ return filter->plugin->filter(filter, src, src_size, dest_size_r, error_r);
+}
diff --git a/src/filter_plugin.h b/src/filter_plugin.h
new file mode 100644
index 000000000..ac6b34522
--- /dev/null
+++ b/src/filter_plugin.h
@@ -0,0 +1,150 @@
+/*
+ * 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.
+ */
+
+/** \file
+ *
+ * This header declares the filter_plugin class. It describes a
+ * plugin API for objects which filter raw PCM data.
+ */
+
+#ifndef MPD_FILTER_PLUGIN_H
+#define MPD_FILTER_PLUGIN_H
+
+#include <glib.h>
+
+#include <stdbool.h>
+#include <stddef.h>
+
+struct config_param;
+struct filter;
+
+struct filter_plugin {
+ const char *name;
+
+ /**
+ * Allocates and configures a filter.
+ */
+ struct filter *(*init)(const struct config_param *param,
+ GError **error_r);
+
+ /**
+ * Free instance data.
+ */
+ void (*finish)(struct filter *filter);
+
+ /**
+ * Opens a filter.
+ *
+ * @param audio_format the audio format of incoming data; the
+ * plugin may modify the object to enforce another input
+ * format
+ */
+ const struct audio_format *
+ (*open)(struct filter *filter,
+ struct audio_format *audio_format,
+ GError **error_r);
+
+ /**
+ * Closes a filter.
+ */
+ void (*close)(struct filter *filter);
+
+ /**
+ * Filters a block of PCM data.
+ */
+ const void *(*filter)(struct filter *filter,
+ const void *src, size_t src_size,
+ size_t *dest_buffer_r,
+ GError **error_r);
+};
+
+/**
+ * Creates a new instance of the specified filter plugin.
+ *
+ * @param plugin the filter plugin
+ * @param param optional configuration section
+ * @param error location to store the error occuring, or NULL to
+ * ignore errors.
+ * @return a new filter object, or NULL on error
+ */
+struct filter *
+filter_new(const struct filter_plugin *plugin,
+ const struct config_param *param, GError **error_r);
+
+/**
+ * Creates a new filter, loads configuration and the plugin name from
+ * the specified configuration section.
+ *
+ * @param param the configuration section
+ * @param error location to store the error occuring, or NULL to
+ * ignore errors.
+ * @return a new filter object, or NULL on error
+ */
+struct filter *
+filter_configured_new(const struct config_param *param, GError **error_r);
+
+/**
+ * Deletes a filter. It must be closed prior to calling this
+ * function, see filter_close().
+ *
+ * @param filter the filter object
+ */
+void
+filter_free(struct filter *filter);
+
+/**
+ * Opens the filter, preparing it for 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
+ * ignore errors.
+ * @return the format of outgoing data
+ */
+const struct audio_format *
+filter_open(struct filter *filter, struct audio_format *audio_format,
+ GError **error_r);
+
+/**
+ * Closes the filter. After that, you may call filter_open() again.
+ *
+ * @param filter the filter object
+ */
+void
+filter_close(struct filter *filter);
+
+/**
+ * Filters a block of PCM data.
+ *
+ * @param filter the filter object
+ * @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
+ * ignore errors.
+ * @return the destination buffer on success (will be invalidated by
+ * filter_close() or filter_filter()), NULL on error
+ */
+const void *
+filter_filter(struct filter *filter, const void *src, size_t src_size,
+ size_t *dest_size_r,
+ GError **error_r);
+
+#endif
diff --git a/src/filter_registry.c b/src/filter_registry.c
new file mode 100644
index 000000000..150043cc5
--- /dev/null
+++ b/src/filter_registry.c
@@ -0,0 +1,44 @@
+/*
+ * 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 "filter_registry.h"
+#include "filter_plugin.h"
+
+#include <stddef.h>
+#include <string.h>
+
+const struct filter_plugin *const filter_plugins[] = {
+ &null_filter_plugin,
+ &route_filter_plugin,
+ &normalize_filter_plugin,
+ &volume_filter_plugin,
+ &replay_gain_filter_plugin,
+ NULL,
+};
+
+const struct filter_plugin *
+filter_plugin_by_name(const char *name)
+{
+ for (unsigned i = 0; filter_plugins[i] != NULL; ++i)
+ if (strcmp(filter_plugins[i]->name, name) == 0)
+ return filter_plugins[i];
+
+ return NULL;
+}
diff --git a/src/filter_registry.h b/src/filter_registry.h
new file mode 100644
index 000000000..551a7afa1
--- /dev/null
+++ b/src/filter_registry.h
@@ -0,0 +1,40 @@
+/*
+ * 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.
+ */
+
+/** \file
+ *
+ * This library manages all filter plugins which are enabled at
+ * compile time.
+ */
+
+#ifndef MPD_FILTER_REGISTRY_H
+#define MPD_FILTER_REGISTRY_H
+
+extern const struct filter_plugin null_filter_plugin;
+extern const struct filter_plugin chain_filter_plugin;
+extern const struct filter_plugin convert_filter_plugin;
+extern const struct filter_plugin route_filter_plugin;
+extern const struct filter_plugin normalize_filter_plugin;
+extern const struct filter_plugin volume_filter_plugin;
+extern const struct filter_plugin replay_gain_filter_plugin;
+
+const struct filter_plugin *
+filter_plugin_by_name(const char *name);
+
+#endif
diff --git a/src/gcc.h b/src/gcc.h
index 88cde86a5..085a8a5f6 100644
--- a/src/gcc.h
+++ b/src/gcc.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
diff --git a/src/glib_compat.h b/src/glib_compat.h
new file mode 100644
index 000000000..4d0e7040d
--- /dev/null
+++ b/src/glib_compat.h
@@ -0,0 +1,77 @@
+/*
+ * 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.
+ */
+
+/*
+ * Compatibility with older GLib versions. Some of this isn't
+ * implemented properly, just "good enough" to allow users with older
+ * operating systems to run MPD.
+ */
+
+#ifndef MPD_GLIB_COMPAT_H
+#define MPD_GLIB_COMPAT_H
+
+#include <glib.h>
+
+#if !GLIB_CHECK_VERSION(2,14,0)
+
+#define g_queue_clear(q) do { g_queue_free(q); q = g_queue_new(); } while (0)
+
+static inline guint
+g_timeout_add_seconds(guint interval, GSourceFunc function, gpointer data)
+{
+ return g_timeout_add(interval * 1000, function, data);
+}
+
+#endif /* !2.14 */
+
+#if !GLIB_CHECK_VERSION(2,16,0)
+
+static inline void
+g_propagate_prefixed_error(GError **dest_r, GError *src,
+ G_GNUC_UNUSED const gchar *format, ...)
+{
+ g_propagate_error(dest_r, src);
+}
+
+static inline char *
+g_uri_escape_string(const char *unescaped,
+ G_GNUC_UNUSED const char *reserved_chars_allowed,
+ G_GNUC_UNUSED gboolean allow_utf8)
+{
+ return g_strdup(unescaped);
+}
+
+#endif /* !2.16 */
+
+#if !GLIB_CHECK_VERSION(2,16,0)
+
+#include <string.h>
+
+static inline char *
+g_uri_parse_scheme(const char *uri)
+{
+ const char *end = strstr(uri, "://");
+ if (end == NULL)
+ return NULL;
+ return g_strndup(uri, end - uri);
+}
+
+#endif
+
+#endif
diff --git a/src/icy_metadata.c b/src/icy_metadata.c
index 69aa89092..6a79121cf 100644
--- a/src/icy_metadata.c
+++ b/src/icy_metadata.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -17,6 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "icy_metadata.h"
#include "tag.h"
@@ -95,7 +96,7 @@ icy_parse_tag_item(struct tag *tag, const char *item)
if (p[0] != NULL && p[1] != NULL) {
if (strcmp(p[0], "StreamTitle") == 0)
- icy_add_item(tag, TAG_ITEM_TITLE, p[1]);
+ icy_add_item(tag, TAG_TITLE, p[1]);
else
g_debug("unknown icy-tag: '%s'", p[0]);
}
diff --git a/src/icy_metadata.h b/src/icy_metadata.h
index f8eac4e91..4a51b4cf0 100644
--- a/src/icy_metadata.h
+++ b/src/icy_metadata.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
diff --git a/src/icy_server.c b/src/icy_server.c
index 50b10c6ca..62a2c67af 100644
--- a/src/icy_server.c
+++ b/src/icy_server.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -17,6 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "icy_server.h"
#include <glib.h>
diff --git a/src/icy_server.h b/src/icy_server.h
index b48014c29..3ce4ab635 100644
--- a/src/icy_server.h
+++ b/src/icy_server.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
diff --git a/src/idle.c b/src/idle.c
index 11b57376d..eccb62322 100644
--- a/src/idle.c
+++ b/src/idle.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -22,6 +22,7 @@
*
*/
+#include "config.h"
#include "idle.h"
#include "event_pipe.h"
@@ -40,6 +41,7 @@ static const char *const idle_names[] = {
"output",
"options",
"sticker",
+ "update",
NULL
};
diff --git a/src/idle.h b/src/idle.h
index a69acabb0..7caeb4a8c 100644
--- a/src/idle.h
+++ b/src/idle.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -50,6 +50,9 @@ enum {
/** a sticker has been modified. */
IDLE_STICKER = 0x80,
+
+ /** a database update has started or finished. */
+ IDLE_UPDATE = 0x100,
};
/**
diff --git a/src/inotify_queue.c b/src/inotify_queue.c
new file mode 100644
index 000000000..5391a1715
--- /dev/null
+++ b/src/inotify_queue.c
@@ -0,0 +1,135 @@
+/*
+ * 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 "inotify_queue.h"
+#include "update.h"
+
+#include <glib.h>
+
+#include <string.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "inotify"
+
+enum {
+ /**
+ * Wait this long after the last change before calling
+ * update_enqueue(). This increases the probability that
+ * updates can be bundled.
+ */
+ INOTIFY_UPDATE_DELAY_S = 5,
+};
+
+static GSList *inotify_queue;
+static guint queue_source_id;
+
+void
+mpd_inotify_queue_init(void)
+{
+}
+
+static void
+free_callback(gpointer data, G_GNUC_UNUSED gpointer user_data)
+{
+ g_free(data);
+}
+
+void
+mpd_inotify_queue_finish(void)
+{
+ if (queue_source_id != 0)
+ g_source_remove(queue_source_id);
+
+ g_slist_foreach(inotify_queue, free_callback, NULL);
+ g_slist_free(inotify_queue);
+}
+
+static gboolean
+mpd_inotify_run_update(G_GNUC_UNUSED gpointer data)
+{
+ unsigned id;
+
+ while (inotify_queue != NULL) {
+ char *uri_utf8 = inotify_queue->data;
+
+ id = update_enqueue(uri_utf8, false);
+ if (id == 0)
+ /* retry later */
+ return true;
+
+ g_debug("updating '%s' job=%u", uri_utf8, id);
+
+ g_free(uri_utf8);
+ inotify_queue = g_slist_delete_link(inotify_queue,
+ inotify_queue);
+ }
+
+ /* done, remove the timer event by returning false */
+ queue_source_id = 0;
+ return false;
+}
+
+static bool
+path_in(const char *path, const char *possible_parent)
+{
+ size_t length = strlen(possible_parent);
+
+ return path[0] == 0 ||
+ (memcmp(possible_parent, path, length) == 0 &&
+ (path[length] == 0 || path[length] == '/'));
+}
+
+void
+mpd_inotify_enqueue(char *uri_utf8)
+{
+ GSList *old_queue = inotify_queue;
+
+ if (queue_source_id != 0)
+ g_source_remove(queue_source_id);
+ queue_source_id = g_timeout_add_seconds(INOTIFY_UPDATE_DELAY_S,
+ mpd_inotify_run_update, NULL);
+
+ inotify_queue = NULL;
+ while (old_queue != NULL) {
+ char *current_uri = old_queue->data;
+
+ if (path_in(uri_utf8, current_uri)) {
+ /* already enqueued */
+ g_free(uri_utf8);
+ inotify_queue = g_slist_concat(inotify_queue,
+ old_queue);
+ return;
+ }
+
+ old_queue = g_slist_delete_link(old_queue, old_queue);
+
+ if (path_in(current_uri, uri_utf8))
+ /* existing path is a sub-path of the new
+ path; we can dequeue the existing path and
+ update the new path instead */
+ g_free(current_uri);
+ else
+ /* move the existing path to the new queue */
+ inotify_queue = g_slist_prepend(inotify_queue,
+ current_uri);
+ }
+
+ inotify_queue = g_slist_prepend(inotify_queue, uri_utf8);
+}
diff --git a/src/inotify_queue.h b/src/inotify_queue.h
new file mode 100644
index 000000000..2e43d2f25
--- /dev/null
+++ b/src/inotify_queue.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_INOTIFY_QUEUE_H
+#define MPD_INOTIFY_QUEUE_H
+
+void
+mpd_inotify_queue_init(void);
+
+void
+mpd_inotify_queue_finish(void);
+
+void
+mpd_inotify_enqueue(char *uri_utf8);
+
+#endif
diff --git a/src/inotify_source.c b/src/inotify_source.c
new file mode 100644
index 000000000..3a986cbad
--- /dev/null
+++ b/src/inotify_source.c
@@ -0,0 +1,167 @@
+/*
+ * 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 "inotify_source.h"
+#include "fifo_buffer.h"
+#include "fd_util.h"
+#include "mpd_error.h"
+
+#include <sys/inotify.h>
+#include <unistd.h>
+#include <errno.h>
+#include <stdbool.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "inotify"
+
+struct mpd_inotify_source {
+ int fd;
+
+ GIOChannel *channel;
+
+ /**
+ * The channel's source id in the GLib main loop.
+ */
+ guint id;
+
+ struct fifo_buffer *buffer;
+
+ mpd_inotify_callback_t callback;
+ void *callback_ctx;
+};
+
+/**
+ * A GQuark for GError instances.
+ */
+static inline GQuark
+mpd_inotify_quark(void)
+{
+ return g_quark_from_static_string("inotify");
+}
+
+static gboolean
+mpd_inotify_in_event(G_GNUC_UNUSED GIOChannel *_source,
+ G_GNUC_UNUSED GIOCondition condition,
+ gpointer data)
+{
+ struct mpd_inotify_source *source = data;
+ void *dest;
+ size_t length;
+ ssize_t nbytes;
+ const struct inotify_event *event;
+
+ dest = fifo_buffer_write(source->buffer, &length);
+ if (dest == NULL)
+ MPD_ERROR("buffer full");
+
+ nbytes = read(source->fd, dest, length);
+ if (nbytes < 0)
+ MPD_ERROR("failed to read from inotify: %s",
+ g_strerror(errno));
+ if (nbytes == 0)
+ MPD_ERROR("end of file from inotify");
+
+ fifo_buffer_append(source->buffer, nbytes);
+
+ while (true) {
+ const char *name;
+
+ event = fifo_buffer_read(source->buffer, &length);
+ if (event == NULL || length < sizeof(*event) ||
+ length < sizeof(*event) + event->len)
+ break;
+
+ if (event->len > 0 && event->name[event->len - 1] == 0)
+ name = event->name;
+ else
+ name = NULL;
+
+ source->callback(event->wd, event->mask, name,
+ source->callback_ctx);
+ fifo_buffer_consume(source->buffer,
+ sizeof(*event) + event->len);
+ }
+
+ return true;
+}
+
+struct mpd_inotify_source *
+mpd_inotify_source_new(mpd_inotify_callback_t callback, void *callback_ctx,
+ GError **error_r)
+{
+ struct mpd_inotify_source *source =
+ g_new(struct mpd_inotify_source, 1);
+
+ source->fd = inotify_init_cloexec();
+ if (source->fd < 0) {
+ g_set_error(error_r, mpd_inotify_quark(), errno,
+ "inotify_init() has failed: %s",
+ g_strerror(errno));
+ g_free(source);
+ return NULL;
+ }
+
+ source->buffer = fifo_buffer_new(4096);
+
+ source->channel = g_io_channel_unix_new(source->fd);
+ source->id = g_io_add_watch(source->channel, G_IO_IN,
+ mpd_inotify_in_event, source);
+
+ source->callback = callback;
+ source->callback_ctx = callback_ctx;
+
+ return source;
+}
+
+void
+mpd_inotify_source_free(struct mpd_inotify_source *source)
+{
+ g_source_remove(source->id);
+ g_io_channel_unref(source->channel);
+ fifo_buffer_free(source->buffer);
+ close(source->fd);
+ g_free(source);
+}
+
+int
+mpd_inotify_source_add(struct mpd_inotify_source *source,
+ const char *path_fs, unsigned mask,
+ GError **error_r)
+{
+ int wd = inotify_add_watch(source->fd, path_fs, mask);
+ if (wd < 0)
+ g_set_error(error_r, mpd_inotify_quark(), errno,
+ "inotify_add_watch() has failed: %s",
+ g_strerror(errno));
+
+ return wd;
+}
+
+void
+mpd_inotify_source_rm(struct mpd_inotify_source *source, unsigned wd)
+{
+ int ret = inotify_rm_watch(source->fd, wd);
+ if (ret < 0 && errno != EINVAL)
+ g_warning("inotify_rm_watch() has failed: %s",
+ g_strerror(errno));
+
+ /* EINVAL may happen here when the file has been deleted; the
+ kernel seems to auto-unregister deleted files */
+}
diff --git a/src/inotify_source.h b/src/inotify_source.h
new file mode 100644
index 000000000..e78b92c0f
--- /dev/null
+++ b/src/inotify_source.h
@@ -0,0 +1,61 @@
+/*
+ * 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_INOTIFY_SOURCE_H
+#define MPD_INOTIFY_SOURCE_H
+
+#include <glib.h>
+
+typedef void (*mpd_inotify_callback_t)(int wd, unsigned mask,
+ const char *name, void *ctx);
+
+struct mpd_inotify_source;
+
+/**
+ * Creates a new inotify source and registers it in the GLib main
+ * loop.
+ *
+ * @param a callback invoked for events received from the kernel
+ */
+struct mpd_inotify_source *
+mpd_inotify_source_new(mpd_inotify_callback_t callback, void *callback_ctx,
+ GError **error_r);
+
+void
+mpd_inotify_source_free(struct mpd_inotify_source *source);
+
+/**
+ * Adds a path to the notify list.
+ *
+ * @return a watch descriptor or -1 on error
+ */
+int
+mpd_inotify_source_add(struct mpd_inotify_source *source,
+ const char *path_fs, unsigned mask,
+ GError **error_r);
+
+/**
+ * Removes a path from the notify list.
+ *
+ * @param wd the watch descriptor returned by mpd_inotify_source_add()
+ */
+void
+mpd_inotify_source_rm(struct mpd_inotify_source *source, unsigned wd);
+
+#endif
diff --git a/src/inotify_update.c b/src/inotify_update.c
new file mode 100644
index 000000000..8d9657961
--- /dev/null
+++ b/src/inotify_update.c
@@ -0,0 +1,383 @@
+/*
+ * 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" /* must be first for large file support */
+#include "inotify_update.h"
+#include "inotify_source.h"
+#include "inotify_queue.h"
+#include "database.h"
+#include "mapper.h"
+#include "path.h"
+
+#include <assert.h>
+#include <sys/inotify.h>
+#include <sys/stat.h>
+#include <stdbool.h>
+#include <string.h>
+#include <dirent.h>
+#include <errno.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "inotify"
+
+enum {
+ IN_MASK = IN_ATTRIB|IN_CLOSE_WRITE|IN_CREATE|IN_DELETE|IN_DELETE_SELF
+ |IN_MOVE|IN_MOVE_SELF
+#ifdef IN_ONLYDIR
+ |IN_ONLYDIR
+#endif
+};
+
+struct watch_directory {
+ struct watch_directory *parent;
+
+ char *name;
+
+ int descriptor;
+
+ GList *children;
+};
+
+static struct mpd_inotify_source *inotify_source;
+
+static unsigned inotify_max_depth;
+static struct watch_directory inotify_root;
+static GTree *inotify_directories;
+
+static gint
+compare(gconstpointer a, gconstpointer b)
+{
+ if (a < b)
+ return -1;
+ else if (a > b)
+ return 1;
+ else
+ return 0;
+}
+
+static void
+tree_add_watch_directory(struct watch_directory *directory)
+{
+ g_tree_insert(inotify_directories,
+ GINT_TO_POINTER(directory->descriptor), directory);
+}
+
+static void
+tree_remove_watch_directory(struct watch_directory *directory)
+{
+ G_GNUC_UNUSED
+ bool found = g_tree_remove(inotify_directories,
+ GINT_TO_POINTER(directory->descriptor));
+ assert(found);
+}
+
+static struct watch_directory *
+tree_find_watch_directory(int wd)
+{
+ return g_tree_lookup(inotify_directories, GINT_TO_POINTER(wd));
+}
+
+static void
+remove_watch_directory(struct watch_directory *directory)
+{
+ assert(directory != NULL);
+
+ if (directory->parent == NULL) {
+ g_warning("music directory was removed - "
+ "cannot continue to watch it");
+ return;
+ }
+
+ assert(directory->parent->children != NULL);
+
+ tree_remove_watch_directory(directory);
+
+ while (directory->children != NULL)
+ remove_watch_directory(directory->children->data);
+
+ directory->parent->children =
+ g_list_remove(directory->parent->children, directory);
+
+ mpd_inotify_source_rm(inotify_source, directory->descriptor);
+ g_free(directory->name);
+ g_slice_free(struct watch_directory, directory);
+}
+
+static char *
+watch_directory_get_uri_fs(const struct watch_directory *directory)
+{
+ char *parent_uri, *uri;
+
+ if (directory->parent == NULL)
+ return NULL;
+
+ parent_uri = watch_directory_get_uri_fs(directory->parent);
+ if (parent_uri == NULL)
+ return g_strdup(directory->name);
+
+ uri = g_strconcat(parent_uri, "/", directory->name, NULL);
+ g_free(parent_uri);
+
+ return uri;
+}
+
+/* we don't look at "." / ".." nor files with newlines in their name */
+static bool skip_path(const char *path)
+{
+ return (path[0] == '.' && path[1] == 0) ||
+ (path[0] == '.' && path[1] == '.' && path[2] == 0) ||
+ strchr(path, '\n') != NULL;
+}
+
+static void
+recursive_watch_subdirectories(struct watch_directory *directory,
+ const char *path_fs, unsigned depth)
+{
+ GError *error = NULL;
+ DIR *dir;
+ struct dirent *ent;
+
+ assert(directory != NULL);
+ assert(depth <= inotify_max_depth);
+ assert(path_fs != NULL);
+
+ ++depth;
+
+ if (depth > inotify_max_depth)
+ return;
+
+ dir = opendir(path_fs);
+ if (dir == NULL) {
+ g_warning("Failed to open directory %s: %s",
+ path_fs, g_strerror(errno));
+ return;
+ }
+
+ while ((ent = readdir(dir))) {
+ char *child_path_fs;
+ struct stat st;
+ int ret;
+ struct watch_directory *child;
+
+ if (skip_path(ent->d_name))
+ continue;
+
+ child_path_fs = g_strconcat(path_fs, "/", ent->d_name, NULL);
+ ret = stat(child_path_fs, &st);
+ if (ret < 0) {
+ g_warning("Failed to stat %s: %s",
+ child_path_fs, g_strerror(errno));
+ g_free(child_path_fs);
+ continue;
+ }
+
+ if (!S_ISDIR(st.st_mode)) {
+ g_free(child_path_fs);
+ continue;
+ }
+
+ ret = mpd_inotify_source_add(inotify_source, child_path_fs,
+ IN_MASK, &error);
+ if (ret < 0) {
+ g_warning("Failed to register %s: %s",
+ child_path_fs, error->message);
+ g_error_free(error);
+ error = NULL;
+ g_free(child_path_fs);
+ continue;
+ }
+
+ child = tree_find_watch_directory(ret);
+ if (child != NULL) {
+ /* already being watched */
+ g_free(child_path_fs);
+ continue;
+ }
+
+ child = g_slice_new(struct watch_directory);
+ child->parent = directory;
+ child->name = g_strdup(ent->d_name);
+ child->descriptor = ret;
+ child->children = NULL;
+
+ directory->children = g_list_prepend(directory->children,
+ child);
+
+ tree_add_watch_directory(child);
+
+ recursive_watch_subdirectories(child, child_path_fs, depth);
+ g_free(child_path_fs);
+ }
+
+ closedir(dir);
+}
+
+G_GNUC_PURE
+static unsigned
+watch_directory_depth(const struct watch_directory *d)
+{
+ assert(d != NULL);
+
+ unsigned depth = 0;
+ while ((d = d->parent) != NULL)
+ ++depth;
+
+ return depth;
+}
+
+static void
+mpd_inotify_callback(int wd, unsigned mask,
+ G_GNUC_UNUSED const char *name, G_GNUC_UNUSED void *ctx)
+{
+ struct watch_directory *directory;
+ char *uri_fs;
+
+ /*g_debug("wd=%d mask=0x%x name='%s'", wd, mask, name);*/
+
+ directory = tree_find_watch_directory(wd);
+ if (directory == NULL)
+ return;
+
+ uri_fs = watch_directory_get_uri_fs(directory);
+
+ if ((mask & (IN_DELETE_SELF|IN_MOVE_SELF)) != 0) {
+ g_free(uri_fs);
+ remove_watch_directory(directory);
+ return;
+ }
+
+ if ((mask & (IN_ATTRIB|IN_CREATE|IN_MOVE)) != 0 &&
+ (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
+ path_fs = root;
+
+ recursive_watch_subdirectories(directory, path_fs,
+ watch_directory_depth(directory));
+ g_free(path_fs);
+ }
+
+ if ((mask & (IN_CLOSE_WRITE|IN_MOVE|IN_DELETE)) != 0 ||
+ /* at the maximum depth, we watch out for newly created
+ directories */
+ (watch_directory_depth(directory) == inotify_max_depth &&
+ (mask & (IN_CREATE|IN_ISDIR)) == (IN_CREATE|IN_ISDIR))) {
+ /* a file was changed, or a directory was
+ moved/deleted: queue a database update */
+ char *uri_utf8 = uri_fs != NULL
+ ? fs_charset_to_utf8(uri_fs)
+ : g_strdup("");
+
+ if (uri_utf8 != NULL)
+ /* this function will take care of freeing
+ uri_utf8 */
+ mpd_inotify_enqueue(uri_utf8);
+ }
+
+ g_free(uri_fs);
+}
+
+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);
+ if (path == NULL) {
+ g_warning("mapper has failed");
+ return;
+ }
+
+ inotify_source = mpd_inotify_source_new(mpd_inotify_callback, NULL,
+ &error);
+ 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.descriptor = mpd_inotify_source_add(inotify_source, path,
+ IN_MASK, &error);
+ if (inotify_root.descriptor < 0) {
+ g_warning("%s", error->message);
+ g_error_free(error);
+ mpd_inotify_source_free(inotify_source);
+ inotify_source = NULL;
+ g_free(path);
+ return;
+ }
+
+ inotify_directories = g_tree_new(compare);
+ tree_add_watch_directory(&inotify_root);
+
+ recursive_watch_subdirectories(&inotify_root, path, 0);
+
+ mpd_inotify_queue_init();
+
+ g_debug("watching music directory");
+}
+
+static gboolean
+free_watch_directory(G_GNUC_UNUSED gpointer key, gpointer value,
+ G_GNUC_UNUSED gpointer data)
+{
+ struct watch_directory *directory = value;
+
+ g_free(directory->name);
+ g_list_free(directory->children);
+
+ if (directory != &inotify_root)
+ g_slice_free(struct watch_directory, directory);
+
+ return false;
+}
+
+void
+mpd_inotify_finish(void)
+{
+ if (inotify_source == NULL)
+ return;
+
+ mpd_inotify_queue_finish();
+ mpd_inotify_source_free(inotify_source);
+
+ g_tree_foreach(inotify_directories, free_watch_directory, NULL);
+ g_tree_destroy(inotify_directories);
+}
diff --git a/src/inotify_update.h b/src/inotify_update.h
new file mode 100644
index 000000000..92b4e0cc6
--- /dev/null
+++ b/src/inotify_update.h
@@ -0,0 +1,47 @@
+/*
+ * 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_INOTIFY_UPDATE_H
+#define MPD_INOTIFY_UPDATE_H
+
+#include "check.h"
+
+#ifdef HAVE_INOTIFY_INIT
+
+void
+mpd_inotify_init(unsigned max_depth);
+
+void
+mpd_inotify_finish(void);
+
+#else /* !HAVE_INOTIFY_INIT */
+
+static inline void
+mpd_inotify_init(G_GNUC_UNUSED unsigned max_depth)
+{
+}
+
+static inline void
+mpd_inotify_finish(void)
+{
+}
+
+#endif /* !HAVE_INOTIFY_INIT */
+
+#endif
diff --git a/src/input/archive_input_plugin.c b/src/input/archive_input_plugin.c
index 8e897f0c2..97e4836ff 100644
--- a/src/input/archive_input_plugin.c
+++ b/src/input/archive_input_plugin.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -17,6 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "input/archive_input_plugin.h"
#include "archive_api.h"
#include "archive_list.h"
@@ -32,23 +33,23 @@
* parent_stream so tar plugin fetches file data from gzip
* plugin and gzip fetches file from disk
*/
-static bool
-input_archive_open(struct input_stream *is, const char *pathname)
+static struct input_stream *
+input_archive_open(const char *pathname, GError **error_r)
{
const struct archive_plugin *arplug;
struct archive_file *file;
char *archive, *filename, *suffix, *pname;
- bool opened;
+ struct input_stream *is;
- if (pathname[0] != '/')
- return false;
+ if (!g_path_is_absolute(pathname))
+ return NULL;
pname = g_strdup(pathname);
// archive_lookup will modify pname when true is returned
if (!archive_lookup(pname, &archive, &filename, &suffix)) {
g_debug("not an archive, lookup %s failed\n", pname);
g_free(pname);
- return false;
+ return NULL;
}
//check which archive plugin to use (by ext)
@@ -56,22 +57,19 @@ input_archive_open(struct input_stream *is, const char *pathname)
if (!arplug) {
g_warning("can't handle archive %s\n",archive);
g_free(pname);
- return false;
+ return NULL;
}
- file = arplug->open(archive);
+ file = archive_file_open(arplug, archive, error_r);
+ if (file == NULL)
+ return NULL;
//setup fileops
- opened = arplug->open_stream(file, is, filename);
-
- if (!opened) {
- g_warning("open inarchive file %s failed\n\n",filename);
- arplug->close(file);
- } else {
- is->ready = true;
- }
+ is = archive_file_open_stream(file, filename, error_r);
+ archive_file_close(file);
g_free(pname);
- return opened;
+
+ return is;
}
const struct input_plugin input_plugin_archive = {
diff --git a/src/input/archive_input_plugin.h b/src/input/archive_input_plugin.h
index 482392a01..20568cfbe 100644
--- a/src/input/archive_input_plugin.h
+++ b/src/input/archive_input_plugin.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
diff --git a/src/input/curl_input_plugin.c b/src/input/curl_input_plugin.c
index 3893aef1c..ae645bddf 100644
--- a/src/input/curl_input_plugin.c
+++ b/src/input/curl_input_plugin.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -17,12 +17,13 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "input/curl_input_plugin.h"
#include "input_plugin.h"
#include "conf.h"
-#include "config.h"
#include "tag.h"
#include "icy_metadata.h"
+#include "glib_compat.h"
#include <assert.h>
@@ -56,6 +57,8 @@ struct buffer {
};
struct input_curl {
+ struct input_stream base;
+
/* some buffers which were passed to libcurl, which we have
too free */
char *url, *range;
@@ -96,8 +99,15 @@ static struct curl_slist *http_200_aliases;
static const char *proxy, *proxy_user, *proxy_password;
static unsigned proxy_port;
+static inline GQuark
+curl_quark(void)
+{
+ return g_quark_from_static_string("curl");
+}
+
static bool
-input_curl_init(const struct config_param *param)
+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) {
@@ -144,11 +154,6 @@ buffer_free_callback(gpointer data, G_GNUC_UNUSED gpointer user_data)
g_free(data);
}
-/* g_queue_clear() was introduced in GLib 2.14 */
-#if !GLIB_CHECK_VERSION(2,14,0)
-#define g_queue_clear(q) do { g_queue_free(q); q = g_queue_new(); } while (0)
-#endif
-
/**
* Frees the current "libcurl easy" handle, and everything associated
* with it.
@@ -176,10 +181,8 @@ input_curl_easy_free(struct input_curl *c)
* Frees this stream (but not the input_stream struct itself).
*/
static void
-input_curl_free(struct input_stream *is)
+input_curl_free(struct input_curl *c)
{
- struct input_curl *c = is->data;
-
if (c->tag != NULL)
tag_free(c->tag);
g_free(c->meta_name);
@@ -192,13 +195,14 @@ input_curl_free(struct input_stream *is)
g_queue_free(c->buffers);
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 = is->data;
+ struct input_curl *c = (struct input_curl *)is;
struct tag *tag = c->tag;
c->tag = NULL;
@@ -206,9 +210,8 @@ input_curl_tag(struct input_stream *is)
}
static bool
-input_curl_multi_info_read(struct input_stream *is)
+input_curl_multi_info_read(struct input_curl *c, GError **error_r)
{
- struct input_curl *c = is->data;
CURLMsg *msg;
int msgs_in_queue;
@@ -216,11 +219,12 @@ input_curl_multi_info_read(struct input_stream *is)
&msgs_in_queue)) != NULL) {
if (msg->msg == CURLMSG_DONE) {
c->eof = true;
- is->ready = true;
+ c->base.ready = true;
if (msg->data.result != CURLE_OK) {
- g_warning("curl failed: %s\n", c->error);
- is->error = -1;
+ g_set_error(error_r, curl_quark(),
+ msg->data.result,
+ "curl failed: %s", c->error);
return false;
}
}
@@ -236,7 +240,7 @@ input_curl_multi_info_read(struct input_stream *is)
* available
*/
static int
-input_curl_select(struct input_curl *c)
+input_curl_select(struct input_curl *c, GError **error_r)
{
fd_set rfds, wfds, efds;
int max_fd, ret;
@@ -254,8 +258,9 @@ input_curl_select(struct input_curl *c)
mcode = curl_multi_fdset(c->multi, &rfds, &wfds, &efds, &max_fd);
if (mcode != CURLM_OK) {
- g_warning("curl_multi_fdset() failed: %s\n",
- curl_multi_strerror(mcode));
+ g_set_error(error_r, curl_quark(), mcode,
+ "curl_multi_fdset() failed: %s",
+ curl_multi_strerror(mcode));
return -1;
}
@@ -279,15 +284,17 @@ input_curl_select(struct input_curl *c)
ret = select(max_fd + 1, &rfds, &wfds, &efds, &timeout);
if (ret < 0)
- g_warning("select() failed: %s\n", strerror(errno));
+ g_set_error(error_r, g_quark_from_static_string("errno"),
+ errno,
+ "select() failed: %s\n", g_strerror(errno));
return ret;
}
static bool
-fill_buffer(struct input_stream *is)
+fill_buffer(struct input_stream *is, GError **error_r)
{
- struct input_curl *c = is->data;
+ struct input_curl *c = (struct input_curl *)is;
CURLMcode mcode = CURLM_CALL_MULTI_PERFORM;
while (!c->eof && g_queue_is_empty(c->buffers)) {
@@ -297,7 +304,7 @@ fill_buffer(struct input_stream *is)
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);
+ int ret = input_curl_select(c, error_r);
if (ret <= 0)
/* no data yet or error */
return false;
@@ -305,14 +312,15 @@ fill_buffer(struct input_stream *is)
mcode = curl_multi_perform(c->multi, &running_handles);
if (mcode != CURLM_OK && mcode != CURLM_CALL_MULTI_PERFORM) {
- g_warning("curl_multi_perform() failed: %s\n",
- curl_multi_strerror(mcode));
+ 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;
}
- bret = input_curl_multi_info_read(is);
+ bret = input_curl_multi_info_read(c, error_r);
if (!bret)
return false;
}
@@ -404,16 +412,17 @@ copy_icy_tag(struct input_curl *c)
if (c->tag != NULL)
tag_free(c->tag);
- if (c->meta_name != NULL && !tag_has_type(tag, TAG_ITEM_NAME))
- tag_add_item(tag, TAG_ITEM_NAME, c->meta_name);
+ if (c->meta_name != NULL && !tag_has_type(tag, TAG_NAME))
+ tag_add_item(tag, TAG_NAME, c->meta_name);
c->tag = tag;
}
static size_t
-input_curl_read(struct input_stream *is, void *ptr, size_t size)
+input_curl_read(struct input_stream *is, void *ptr, size_t size,
+ GError **error_r)
{
- struct input_curl *c = is->data;
+ struct input_curl *c = (struct input_curl *)is;
bool success;
size_t nbytes = 0;
char *dest = ptr;
@@ -421,7 +430,7 @@ input_curl_read(struct input_stream *is, void *ptr, size_t size)
do {
/* fill the buffer */
- success = fill_buffer(is);
+ success = fill_buffer(is, error_r);
if (!success)
return 0;
@@ -439,7 +448,7 @@ input_curl_read(struct input_stream *is, void *ptr, size_t size)
if (icy_defined(&c->icy_metadata))
copy_icy_tag(c);
- is->offset += (off_t)nbytes;
+ is->offset += (goffset)nbytes;
return nbytes;
}
@@ -447,21 +456,23 @@ input_curl_read(struct input_stream *is, void *ptr, size_t size)
static void
input_curl_close(struct input_stream *is)
{
- input_curl_free(is);
+ struct input_curl *c = (struct input_curl *)is;
+
+ input_curl_free(c);
}
static bool
input_curl_eof(G_GNUC_UNUSED struct input_stream *is)
{
- struct input_curl *c = is->data;
+ 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)
+input_curl_buffer(struct input_stream *is, GError **error_r)
{
- struct input_curl *c = is->data;
+ struct input_curl *c = (struct input_curl *)is;
CURLMcode mcode;
int running_handles;
bool ret;
@@ -472,7 +483,8 @@ input_curl_buffer(struct input_stream *is)
/* not ready yet means the caller is waiting in a busy
loop; relax that by calling select() on the
socket */
- input_curl_select(c);
+ if (input_curl_select(c, error_r) < 0)
+ return -1;
do {
mcode = curl_multi_perform(c->multi, &running_handles);
@@ -480,14 +492,15 @@ input_curl_buffer(struct input_stream *is)
g_queue_is_empty(c->buffers));
if (mcode != CURLM_OK && mcode != CURLM_CALL_MULTI_PERFORM) {
- g_warning("curl_multi_perform() failed: %s\n",
- curl_multi_strerror(mcode));
+ 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(is);
+ ret = input_curl_multi_info_read(c, error_r);
if (!ret)
return -1;
@@ -498,8 +511,7 @@ input_curl_buffer(struct input_stream *is)
static size_t
input_curl_headerfunction(void *ptr, size_t size, size_t nmemb, void *stream)
{
- struct input_stream *is = stream;
- struct input_curl *c = is->data;
+ struct input_curl *c = (struct input_curl *)stream;
const char *header = ptr, *end, *value;
char name[64];
@@ -528,7 +540,7 @@ input_curl_headerfunction(void *ptr, size_t size, size_t nmemb, void *stream)
if (g_ascii_strcasecmp(name, "accept-ranges") == 0) {
/* a stream with icy-metadata is not seekable */
if (!icy_defined(&c->icy_metadata))
- is->seekable = true;
+ c->base.seekable = true;
} else if (g_ascii_strcasecmp(name, "content-length") == 0) {
char buffer[64];
@@ -538,10 +550,10 @@ input_curl_headerfunction(void *ptr, size_t size, size_t nmemb, void *stream)
memcpy(buffer, value, end - value);
buffer[end - value] = 0;
- is->size = is->offset + g_ascii_strtoull(buffer, NULL, 10);
+ c->base.size = c->base.offset + g_ascii_strtoull(buffer, NULL, 10);
} else if (g_ascii_strcasecmp(name, "content-type") == 0) {
- g_free(is->mime);
- is->mime = g_strndup(value, end - value);
+ g_free(c->base.mime);
+ c->base.mime = g_strndup(value, end - value);
} else if (g_ascii_strcasecmp(name, "icy-name") == 0 ||
g_ascii_strcasecmp(name, "ice-name") == 0 ||
g_ascii_strcasecmp(name, "x-audiocast-name") == 0) {
@@ -552,7 +564,7 @@ input_curl_headerfunction(void *ptr, size_t size, size_t nmemb, void *stream)
tag_free(c->tag);
c->tag = tag_new();
- tag_add_item(c->tag, TAG_ITEM_NAME, c->meta_name);
+ tag_add_item(c->tag, TAG_NAME, c->meta_name);
} else if (g_ascii_strcasecmp(name, "icy-metaint") == 0) {
char buffer[64];
size_t icy_metaint;
@@ -572,7 +584,7 @@ input_curl_headerfunction(void *ptr, size_t size, size_t nmemb, void *stream)
/* a stream with icy-metadata is not
seekable */
- is->seekable = false;
+ c->base.seekable = false;
}
}
@@ -583,8 +595,7 @@ input_curl_headerfunction(void *ptr, size_t size, size_t nmemb, void *stream)
static size_t
input_curl_writefunction(void *ptr, size_t size, size_t nmemb, void *stream)
{
- struct input_stream *is = stream;
- struct input_curl *c = is->data;
+ struct input_curl *c = (struct input_curl *)stream;
struct buffer *buffer;
size *= nmemb;
@@ -598,15 +609,14 @@ input_curl_writefunction(void *ptr, size_t size, size_t nmemb, void *stream)
g_queue_push_tail(c->buffers, buffer);
c->buffered = true;
- is->ready = true;
+ c->base.ready = true;
return size;
}
static bool
-input_curl_easy_init(struct input_stream *is)
+input_curl_easy_init(struct input_curl *c, GError **error_r)
{
- struct input_curl *c = is->data;
CURLcode code;
CURLMcode mcode;
@@ -614,22 +624,27 @@ input_curl_easy_init(struct input_stream *is)
c->easy = curl_easy_init();
if (c->easy == NULL) {
- g_warning("curl_easy_init() failed\n");
+ g_set_error(error_r, curl_quark(), 0,
+ "curl_easy_init() failed");
return false;
}
mcode = curl_multi_add_handle(c->multi, c->easy);
- if (mcode != CURLM_OK)
+ 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,
input_curl_headerfunction);
- curl_easy_setopt(c->easy, CURLOPT_WRITEHEADER, is);
+ curl_easy_setopt(c->easy, CURLOPT_WRITEHEADER, c);
curl_easy_setopt(c->easy, CURLOPT_WRITEFUNCTION,
input_curl_writefunction);
- curl_easy_setopt(c->easy, CURLOPT_WRITEDATA, is);
+ 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_MAXREDIRS, 5);
@@ -650,8 +665,12 @@ input_curl_easy_init(struct input_stream *is)
}
code = curl_easy_setopt(c->easy, CURLOPT_URL, c->url);
- if (code != CURLE_OK)
+ if (code != CURLE_OK) {
+ g_set_error(error_r, curl_quark(), code,
+ "curl_easy_setopt() failed: %s",
+ curl_easy_strerror(code));
return false;
+ }
c->request_headers = NULL;
c->request_headers = curl_slist_append(c->request_headers,
@@ -664,9 +683,9 @@ input_curl_easy_init(struct input_stream *is)
void
input_curl_reinit(struct input_stream *is)
{
- struct input_curl *c = is->data;
+ struct input_curl *c = (struct input_curl *)is;
- assert(is->plugin == &input_plugin_curl);
+ assert(c->base.plugin == &input_plugin_curl);
assert(c->easy != NULL);
curl_easy_setopt(c->easy, CURLOPT_WRITEHEADER, is);
@@ -674,7 +693,7 @@ input_curl_reinit(struct input_stream *is)
}
static bool
-input_curl_send_request(struct input_curl *c)
+input_curl_send_request(struct input_curl *c, GError **error_r)
{
CURLMcode mcode;
int running_handles;
@@ -684,8 +703,9 @@ input_curl_send_request(struct input_curl *c)
} while (mcode == CURLM_CALL_MULTI_PERFORM);
if (mcode != CURLM_OK) {
- g_warning("curl_multi_perform() failed: %s\n",
- curl_multi_strerror(mcode));
+ g_set_error(error_r, curl_quark(), mcode,
+ "curl_multi_perform() failed: %s",
+ curl_multi_strerror(mcode));
return false;
}
@@ -693,9 +713,10 @@ input_curl_send_request(struct input_curl *c)
}
static bool
-input_curl_seek(struct input_stream *is, off_t offset, int whence)
+input_curl_seek(struct input_stream *is, goffset offset, int whence,
+ GError **error_r)
{
- struct input_curl *c = is->data;
+ struct input_curl *c = (struct input_curl *)is;
bool ret;
assert(is->ready);
@@ -741,7 +762,7 @@ input_curl_seek(struct input_stream *is, off_t offset, int whence)
buffer = (struct buffer *)g_queue_pop_head(c->buffers);
length = buffer->size - buffer->consumed;
- if (offset - is->offset < (off_t)length)
+ if (offset - is->offset < (goffset)length)
length = offset - is->offset;
buffer = consume_buffer(buffer, length);
@@ -767,7 +788,7 @@ input_curl_seek(struct input_stream *is, off_t offset, int whence)
return true;
}
- ret = input_curl_easy_init(is);
+ ret = input_curl_easy_init(c, error_r);
if (!ret)
return false;
@@ -778,59 +799,58 @@ input_curl_seek(struct input_stream *is, off_t offset, int whence)
curl_easy_setopt(c->easy, CURLOPT_RANGE, c->range);
}
- ret = input_curl_send_request(c);
+ ret = input_curl_send_request(c, error_r);
if (!ret)
return false;
- return input_curl_multi_info_read(is);
+ return input_curl_multi_info_read(c, error_r);
}
-static bool
-input_curl_open(struct input_stream *is, const char *url)
+static struct input_stream *
+input_curl_open(const char *url, GError **error_r)
{
struct input_curl *c;
bool ret;
if (strncmp(url, "http://", 7) != 0)
- return false;
+ return NULL;
c = g_new0(struct input_curl, 1);
+ input_stream_init(&c->base, &input_plugin_curl, url);
+
c->url = g_strdup(url);
c->buffers = g_queue_new();
- is->plugin = &input_plugin_curl;
- is->data = c;
-
c->multi = curl_multi_init();
if (c->multi == NULL) {
- g_warning("curl_multi_init() failed\n");
-
- input_curl_free(is);
- return false;
+ 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(is);
+ ret = input_curl_easy_init(c, error_r);
if (!ret) {
- input_curl_free(is);
- return false;
+ input_curl_free(c);
+ return NULL;
}
- ret = input_curl_send_request(c);
+ ret = input_curl_send_request(c, error_r);
if (!ret) {
- input_curl_free(is);
- return false;
+ input_curl_free(c);
+ return NULL;
}
- ret = input_curl_multi_info_read(is);
+ ret = input_curl_multi_info_read(c, error_r);
if (!ret) {
- input_curl_free(is);
- return false;
+ input_curl_free(c);
+ return NULL;
}
- return true;
+ return &c->base;
}
const struct input_plugin input_plugin_curl = {
diff --git a/src/input/curl_input_plugin.h b/src/input/curl_input_plugin.h
index 63ac0dc23..be7db4e26 100644
--- a/src/input/curl_input_plugin.h
+++ b/src/input/curl_input_plugin.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
diff --git a/src/input/ffmpeg_input_plugin.c b/src/input/ffmpeg_input_plugin.c
new file mode 100644
index 000000000..0a6be29bc
--- /dev/null
+++ b/src/input/ffmpeg_input_plugin.c
@@ -0,0 +1,168 @@
+/*
+ * 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 "input/ffmpeg_input_plugin.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"
+
+struct input_ffmpeg {
+ struct input_stream base;
+
+ URLContext *h;
+
+ bool eof;
+};
+
+static inline GQuark
+ffmpeg_quark(void)
+{
+ return g_quark_from_static_string("ffmpeg");
+}
+
+static bool
+input_ffmpeg_init(G_GNUC_UNUSED const struct config_param *param,
+ G_GNUC_UNUSED GError **error_r)
+{
+ av_register_all();
+
+#if LIBAVFORMAT_VERSION_MAJOR >= 52
+ /* disable this plugin if there's no registered protocol */
+ if (av_protocol_next(NULL) == NULL) {
+ 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)
+{
+ struct input_ffmpeg *i;
+
+ if (!g_str_has_prefix(uri, "gopher://") &&
+ !g_str_has_prefix(uri, "rtp://") &&
+ !g_str_has_prefix(uri, "rtsp://") &&
+ !g_str_has_prefix(uri, "rtmp://") &&
+ !g_str_has_prefix(uri, "rtmpt://") &&
+ !g_str_has_prefix(uri, "rtmps://"))
+ return NULL;
+
+ i = g_new(struct input_ffmpeg, 1);
+ input_stream_init(&i->base, &input_plugin_ffmpeg, uri);
+
+ int ret = url_open(&i->h, uri, URL_RDONLY);
+ if (ret != 0) {
+ g_free(i);
+ g_set_error(error_r, ffmpeg_quark(), ret,
+ "libavformat failed to open the URI");
+ return NULL;
+ }
+
+ i->eof = false;
+
+ i->base.ready = true;
+ i->base.seekable = !i->h->is_streamed;
+ i->base.size = url_filesize(i->h);
+
+ /* hack to make MPD select the "ffmpeg" decoder plugin - since
+ avio.h doesn't tell us the MIME type of the resource, we
+ can't select a decoder plugin, but the "ffmpeg" plugin is
+ quite good at auto-detection */
+ i->base.mime = g_strdup("audio/x-mpd-ffmpeg");
+
+ return &i->base;
+}
+
+static size_t
+input_ffmpeg_read(struct input_stream *is, void *ptr, size_t size,
+ GError **error_r)
+{
+ struct input_ffmpeg *i = (struct input_ffmpeg *)is;
+
+ int ret = url_read(i->h, ptr, size);
+ if (ret <= 0) {
+ if (ret < 0)
+ g_set_error(error_r, ffmpeg_quark(), 0,
+ "url_read() failed");
+
+ i->eof = true;
+ return false;
+ }
+
+ is->offset += ret;
+ return (size_t)ret;
+}
+
+static void
+input_ffmpeg_close(struct input_stream *is)
+{
+ struct input_ffmpeg *i = (struct input_ffmpeg *)is;
+
+ url_close(i->h);
+ input_stream_deinit(&i->base);
+ g_free(i);
+}
+
+static bool
+input_ffmpeg_eof(struct input_stream *is)
+{
+ struct input_ffmpeg *i = (struct input_ffmpeg *)is;
+
+ return i->eof;
+}
+
+static bool
+input_ffmpeg_seek(struct input_stream *is, goffset offset, int whence,
+ G_GNUC_UNUSED GError **error_r)
+{
+ struct input_ffmpeg *i = (struct input_ffmpeg *)is;
+ int64_t ret = url_seek(i->h, offset, whence);
+
+ if (ret >= 0) {
+ i->eof = false;
+ return true;
+ } else {
+ g_set_error(error_r, ffmpeg_quark(), 0, "url_seek() failed");
+ return false;
+ }
+}
+
+const struct input_plugin input_plugin_ffmpeg = {
+ .name = "ffmpeg",
+ .init = input_ffmpeg_init,
+ .open = input_ffmpeg_open,
+ .close = input_ffmpeg_close,
+ .read = input_ffmpeg_read,
+ .eof = input_ffmpeg_eof,
+ .seek = input_ffmpeg_seek,
+};
diff --git a/src/input/ffmpeg_input_plugin.h b/src/input/ffmpeg_input_plugin.h
new file mode 100644
index 000000000..ff87064be
--- /dev/null
+++ b/src/input/ffmpeg_input_plugin.h
@@ -0,0 +1,28 @@
+/*
+ * 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_FFMPEG_INPUT_PLUGIN_H
+#define MPD_FFMPEG_INPUT_PLUGIN_H
+
+/**
+ * An input plugin based on libavformat's "avio" library.
+ */
+extern const struct input_plugin input_plugin_ffmpeg;
+
+#endif
diff --git a/src/input/file_input_plugin.c b/src/input/file_input_plugin.c
index bda1777ac..3646c656e 100644
--- a/src/input/file_input_plugin.c
+++ b/src/input/file_input_plugin.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -17,11 +17,13 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h" /* must be first for large file support */
#include "input/file_input_plugin.h"
#include "input_plugin.h"
+#include "fd_util.h"
+#include "open.h"
#include <sys/stat.h>
-#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
@@ -30,60 +32,79 @@
#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "input_file"
-static bool
-input_file_open(struct input_stream *is, const char *filename)
+struct file_input_stream {
+ struct input_stream base;
+
+ int fd;
+};
+
+static inline GQuark
+file_quark(void)
+{
+ return g_quark_from_static_string("file");
+}
+
+static struct input_stream *
+input_file_open(const char *filename, GError **error_r)
{
int fd, ret;
struct stat st;
+ struct file_input_stream *fis;
- if (filename[0] != '/')
+ if (!g_path_is_absolute(filename))
return false;
- fd = open(filename, O_RDONLY);
+ fd = open_cloexec(filename, O_RDONLY|O_BINARY, 0);
if (fd < 0) {
- is->error = errno;
- g_debug("Failed to open \"%s\": %s",
- filename, g_strerror(errno));
+ if (errno != ENOENT && errno != ENOTDIR)
+ g_set_error(error_r, file_quark(), errno,
+ "Failed to open \"%s\": %s",
+ filename, g_strerror(errno));
return false;
}
- is->seekable = true;
-
ret = fstat(fd, &st);
if (ret < 0) {
- is->error = errno;
+ g_set_error(error_r, file_quark(), errno,
+ "Failed to stat \"%s\": %s",
+ filename, g_strerror(errno));
close(fd);
return false;
}
if (!S_ISREG(st.st_mode)) {
- g_debug("Not a regular file: %s", filename);
- is->error = EINVAL;
+ g_set_error(error_r, file_quark(), 0,
+ "Not a regular file: %s", filename);
close(fd);
return false;
}
- is->size = st.st_size;
-
#ifdef POSIX_FADV_SEQUENTIAL
- posix_fadvise(fd, (off_t)0, is->size, POSIX_FADV_SEQUENTIAL);
+ posix_fadvise(fd, (off_t)0, st.st_size, POSIX_FADV_SEQUENTIAL);
#endif
- is->plugin = &input_plugin_file;
- is->data = GINT_TO_POINTER(fd);
- is->ready = true;
+ fis = g_new(struct file_input_stream, 1);
+ input_stream_init(&fis->base, &input_plugin_file, filename);
- return true;
+ fis->base.size = st.st_size;
+ fis->base.seekable = true;
+ fis->base.ready = true;
+
+ fis->fd = fd;
+
+ return &fis->base;
}
static bool
-input_file_seek(struct input_stream *is, off_t offset, int whence)
+input_file_seek(struct input_stream *is, goffset offset, int whence,
+ GError **error_r)
{
- int fd = GPOINTER_TO_INT(is->data);
+ struct file_input_stream *fis = (struct file_input_stream *)is;
- offset = lseek(fd, offset, whence);
+ offset = (goffset)lseek(fis->fd, (off_t)offset, whence);
if (offset < 0) {
- is->error = errno;
+ g_set_error(error_r, file_quark(), errno,
+ "Failed to seek: %s", g_strerror(errno));
return false;
}
@@ -92,16 +113,16 @@ input_file_seek(struct input_stream *is, off_t offset, int whence)
}
static size_t
-input_file_read(struct input_stream *is, void *ptr, size_t size)
+input_file_read(struct input_stream *is, void *ptr, size_t size,
+ GError **error_r)
{
- int fd = GPOINTER_TO_INT(is->data);
+ struct file_input_stream *fis = (struct file_input_stream *)is;
ssize_t nbytes;
- nbytes = read(fd, ptr, size);
+ nbytes = read(fis->fd, ptr, size);
if (nbytes < 0) {
- is->error = errno;
- g_debug("input_file_read: error reading: %s\n",
- strerror(is->error));
+ g_set_error(error_r, file_quark(), errno,
+ "Failed to read: %s", g_strerror(errno));
return 0;
}
@@ -112,9 +133,11 @@ input_file_read(struct input_stream *is, void *ptr, size_t size)
static void
input_file_close(struct input_stream *is)
{
- int fd = GPOINTER_TO_INT(is->data);
+ struct file_input_stream *fis = (struct file_input_stream *)is;
- close(fd);
+ close(fis->fd);
+ input_stream_deinit(&fis->base);
+ g_free(fis);
}
static bool
diff --git a/src/input/file_input_plugin.h b/src/input/file_input_plugin.h
index d7610f5d7..40340e8bd 100644
--- a/src/input/file_input_plugin.h
+++ b/src/input/file_input_plugin.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
diff --git a/src/input/lastfm_input_plugin.c b/src/input/lastfm_input_plugin.c
deleted file mode 100644
index 8e13a60a9..000000000
--- a/src/input/lastfm_input_plugin.c
+++ /dev/null
@@ -1,229 +0,0 @@
-/*
- * 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 "input/lastfm_input_plugin.h"
-#include "input/curl_input_plugin.h"
-#include "input_plugin.h"
-#include "conf.h"
-
-#include <string.h>
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "input_lastfm"
-
-static const char *lastfm_user, *lastfm_password;
-
-static bool
-lastfm_input_init(const struct config_param *param)
-{
- lastfm_user = config_get_block_string(param, "user", NULL);
- lastfm_password = config_get_block_string(param, "password", NULL);
-
- return lastfm_user != NULL && lastfm_password != NULL;
-}
-
-static char *
-lastfm_get(const char *url)
-{
- struct input_stream input_stream;
- bool success;
- int ret;
- char buffer[4096];
- size_t length = 0, nbytes;
-
- success = input_stream_open(&input_stream, url);
- if (!success)
- return NULL;
-
- while (!input_stream.ready) {
- ret = input_stream_buffer(&input_stream);
- if (ret < 0) {
- input_stream_close(&input_stream);
- return NULL;
- }
- }
-
- do {
- nbytes = input_stream_read(&input_stream, buffer + length,
- sizeof(buffer) - length);
- if (nbytes == 0) {
- if (input_stream_eof(&input_stream))
- break;
-
- /* I/O error */
- input_stream_close(&input_stream);
- return NULL;
- }
-
- length += nbytes;
- } while (length < sizeof(buffer));
-
- input_stream_close(&input_stream);
- return g_strndup(buffer, length);
-}
-
-static char *
-lastfm_find(const char *response, const char *name)
-{
- size_t name_length = strlen(name);
-
- while (true) {
- const char *eol = strchr(response, '\n');
- if (eol == NULL)
- return NULL;
-
- if (strncmp(response, name, name_length) == 0 &&
- response[name_length] == '=') {
- response += name_length + 1;
- return g_strndup(response, eol - response);
- }
-
- response = eol + 1;
- }
-}
-
-static bool
-lastfm_input_open(struct input_stream *is, const char *url)
-{
- char *md5, *p, *q, *response, *session, *stream_url;
- bool success;
-
- if (strncmp(url, "lastfm://", 9) != 0)
- return false;
-
- /* handshake */
-
-#if GLIB_CHECK_VERSION(2,16,0)
- q = g_uri_escape_string(lastfm_user, NULL, false);
-#else
- q = g_strdup(lastfm_user);
-#endif
-
-#if GLIB_CHECK_VERSION(2,16,0)
- if (strlen(lastfm_password) != 32)
- md5 = g_compute_checksum_for_string(G_CHECKSUM_MD5,
- lastfm_password,
- strlen(lastfm_password));
- else
-#endif
- md5 = g_strdup(lastfm_password);
-
- p = g_strconcat("http://ws.audioscrobbler.com/radio/handshake.php?"
- "version=1.1.1&platform=linux&"
- "username=", q, "&"
- "passwordmd5=", md5, "&debug=0&partner=", NULL);
- g_free(q);
- g_free(md5);
-
- response = lastfm_get(p);
- g_free(p);
- if (response == NULL)
- return false;
-
- /* extract session id from response */
-
- session = lastfm_find(response, "session");
- stream_url = lastfm_find(response, "stream_url");
- g_free(response);
- if (session == NULL || stream_url == NULL) {
- g_free(session);
- g_free(stream_url);
- return false;
- }
-
-#if GLIB_CHECK_VERSION(2,16,0)
- q = g_uri_escape_string(session, NULL, false);
- g_free(session);
- session = q;
-#endif
-
- /* "adjust" last.fm radio */
-
- if (strlen(url) > 9) {
- char *escaped_url;
-
-#if GLIB_CHECK_VERSION(2,16,0)
- escaped_url = g_uri_escape_string(url, NULL, false);
-#else
- escaped_url = g_strdup(url);
-#endif
-
- p = g_strconcat("http://ws.audioscrobbler.com/radio/adjust.php?"
- "session=", session, "&url=", escaped_url, "&debug=0",
- NULL);
- g_free(escaped_url);
-
- response = lastfm_get(p);
- g_free(response);
- g_free(p);
-
- if (response == NULL) {
- g_free(session);
- g_free(stream_url);
- return false;
- }
- }
-
- /* load the last.fm playlist */
-
- p = g_strconcat("http://ws.audioscrobbler.com/radio/xspf.php?"
- "sk=", session, "&discovery=0&desktop=1.5.1.31879",
- NULL);
- g_free(session);
-
- response = lastfm_get(p);
- g_free(p);
-
- if (response == NULL) {
- g_free(stream_url);
- return false;
- }
-
- p = strstr(response, "<location>");
- if (p == NULL) {
- g_free(response);
- g_free(stream_url);
- return false;
- }
-
- p += 10;
- q = strchr(p, '<');
-
- if (q == NULL) {
- g_free(response);
- g_free(stream_url);
- return false;
- }
-
- g_free(stream_url);
- stream_url = g_strndup(p, q - p);
- g_free(response);
-
- /* now really open the last.fm radio stream */
-
- success = input_stream_open(is, stream_url);
- g_free(stream_url);
- return success;
-}
-
-const struct input_plugin lastfm_input_plugin = {
- .name = "lastfm",
- .init = lastfm_input_init,
- .open = lastfm_input_open,
-};
diff --git a/src/input/mms_input_plugin.c b/src/input/mms_input_plugin.c
index 25e3129d9..834d111b8 100644
--- a/src/input/mms_input_plugin.c
+++ b/src/input/mms_input_plugin.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -17,6 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "input/mms_input_plugin.h"
#include "input_plugin.h"
@@ -30,13 +31,21 @@
#define G_LOG_DOMAIN "input_mms"
struct input_mms {
+ struct input_stream base;
+
mmsx_t *mms;
bool eof;
};
-static bool
-input_mms_open(struct input_stream *is, const char *url)
+static inline GQuark
+mms_quark(void)
+{
+ return g_quark_from_static_string("mms");
+}
+
+static struct input_stream *
+input_mms_open(const char *url, GError **error_r)
{
struct input_mms *m;
@@ -44,39 +53,42 @@ input_mms_open(struct input_stream *is, const char *url)
!g_str_has_prefix(url, "mmsh://") &&
!g_str_has_prefix(url, "mmst://") &&
!g_str_has_prefix(url, "mmsu://"))
- return false;
+ return NULL;
m = g_new(struct input_mms, 1);
+ input_stream_init(&m->base, &input_plugin_mms, url);
+
m->mms = mmsx_connect(NULL, NULL, url, 128 * 1024);
if (m->mms == NULL) {
g_free(m);
- g_warning("mmsx_connect() failed");
- return false;
+ g_set_error(error_r, mms_quark(), 0, "mmsx_connect() failed");
+ return NULL;
}
m->eof = false;
/* XX is this correct? at least this selects the ffmpeg
decoder, which seems to work fine*/
- is->mime = g_strdup("audio/x-ms-wma");
+ m->base.mime = g_strdup("audio/x-ms-wma");
+
+ m->base.ready = true;
- is->plugin = &input_plugin_mms;
- is->data = m;
- is->ready = true;
- return true;
+ return &m->base;
}
static size_t
-input_mms_read(struct input_stream *is, void *ptr, size_t size)
+input_mms_read(struct input_stream *is, void *ptr, size_t size,
+ GError **error_r)
{
- struct input_mms *m = is->data;
+ struct input_mms *m = (struct input_mms *)is;
int ret;
ret = mmsx_read(NULL, m->mms, ptr, size);
if (ret <= 0) {
if (ret < 0) {
- is->error = errno;
- g_warning("mmsx_read() failed: %s", g_strerror(errno));
+ g_set_error(error_r, mms_quark(), errno,
+ "mmsx_read() failed: %s",
+ g_strerror(errno));
}
m->eof = true;
@@ -91,29 +103,25 @@ input_mms_read(struct input_stream *is, void *ptr, size_t size)
static void
input_mms_close(struct input_stream *is)
{
- struct input_mms *m = is->data;
+ struct input_mms *m = (struct input_mms *)is;
mmsx_close(m->mms);
+ input_stream_deinit(&m->base);
g_free(m);
}
static bool
input_mms_eof(struct input_stream *is)
{
- struct input_mms *m = is->data;
+ struct input_mms *m = (struct input_mms *)is;
return m->eof;
}
-static int
-input_mms_buffer(G_GNUC_UNUSED struct input_stream *is)
-{
- return 0;
-}
-
static bool
input_mms_seek(G_GNUC_UNUSED struct input_stream *is,
- G_GNUC_UNUSED off_t offset, G_GNUC_UNUSED int whence)
+ G_GNUC_UNUSED goffset offset, G_GNUC_UNUSED int whence,
+ G_GNUC_UNUSED GError **error_r)
{
return false;
}
@@ -122,7 +130,6 @@ const struct input_plugin input_plugin_mms = {
.name = "mms",
.open = input_mms_open,
.close = input_mms_close,
- .buffer = input_mms_buffer,
.read = input_mms_read,
.eof = input_mms_eof,
.seek = input_mms_seek,
diff --git a/src/input/mms_input_plugin.h b/src/input/mms_input_plugin.h
index 3417278c2..2e10cfbb9 100644
--- a/src/input/mms_input_plugin.h
+++ b/src/input/mms_input_plugin.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
diff --git a/src/input/rewind_input_plugin.c b/src/input/rewind_input_plugin.c
index 9e57e6999..6325a978e 100644
--- a/src/input/rewind_input_plugin.c
+++ b/src/input/rewind_input_plugin.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -20,9 +20,6 @@
#include "config.h"
#include "input/rewind_input_plugin.h"
#include "input/curl_input_plugin.h"
-#ifdef ENABLE_MMS
-#include "input/mms_input_plugin.h"
-#endif
#include "input_plugin.h"
#include "tag.h"
@@ -35,7 +32,9 @@
#define G_LOG_DOMAIN "input_rewind"
struct input_rewind {
- struct input_stream input;
+ struct input_stream base;
+
+ struct input_stream *input;
/**
* The read position within the buffer. Undefined as long as
@@ -64,11 +63,9 @@ struct input_rewind {
* contain more data for the next read operation?
*/
static bool
-reading_from_buffer(const struct input_stream *is)
+reading_from_buffer(const struct input_rewind *r)
{
- const struct input_rewind *r = is->data;
-
- return r->tail > 0 && is->offset < r->input.offset;
+ return r->tail > 0 && r->base.offset < r->input->offset;
}
/**
@@ -78,17 +75,16 @@ reading_from_buffer(const struct input_stream *is)
* attributes.
*/
static void
-copy_attributes(struct input_stream *dest)
+copy_attributes(struct input_rewind *r)
{
- const struct input_rewind *r = dest->data;
- const struct input_stream *src = &r->input;
+ struct input_stream *dest = &r->base;
+ const struct input_stream *src = r->input;
assert(dest != src);
assert(src->mime == NULL || dest->mime != src->mime);
dest->ready = src->ready;
dest->seekable = src->seekable;
- dest->error = src->error;
dest->size = src->size;
dest->offset = src->offset;
@@ -101,43 +97,45 @@ copy_attributes(struct input_stream *dest)
static void
input_rewind_close(struct input_stream *is)
{
- struct input_rewind *r = is->data;
+ struct input_rewind *r = (struct input_rewind *)is;
- input_stream_close(&r->input);
+ input_stream_close(r->input);
+ input_stream_deinit(&r->base);
g_free(r);
}
static struct tag *
input_rewind_tag(struct input_stream *is)
{
- struct input_rewind *r = is->data;
+ struct input_rewind *r = (struct input_rewind *)is;
- return input_stream_tag(&r->input);
+ return input_stream_tag(r->input);
}
static int
-input_rewind_buffer(struct input_stream *is)
+input_rewind_buffer(struct input_stream *is, GError **error_r)
{
- struct input_rewind *r = is->data;
+ struct input_rewind *r = (struct input_rewind *)is;
- int ret = input_stream_buffer(&r->input);
- if (ret < 0 || !reading_from_buffer(is))
- copy_attributes(is);
+ int ret = input_stream_buffer(r->input, error_r);
+ if (ret < 0 || !reading_from_buffer(r))
+ copy_attributes(r);
return ret;
}
static size_t
-input_rewind_read(struct input_stream *is, void *ptr, size_t size)
+input_rewind_read(struct input_stream *is, void *ptr, size_t size,
+ GError **error_r)
{
- struct input_rewind *r = is->data;
+ struct input_rewind *r = (struct input_rewind *)is;
- if (reading_from_buffer(is)) {
+ if (reading_from_buffer(r)) {
/* buffered read */
assert(r->head == (size_t)is->offset);
- assert(r->tail == (size_t)r->input.offset);
+ assert(r->tail == (size_t)r->input->offset);
if (size > r->tail - r->head)
size = r->tail - r->head;
@@ -150,9 +148,9 @@ input_rewind_read(struct input_stream *is, void *ptr, size_t size)
} else {
/* pass method call to underlying stream */
- size_t nbytes = input_stream_read(&r->input, ptr, size);
+ size_t nbytes = input_stream_read(r->input, ptr, size, error_r);
- if (r->input.offset > (off_t)sizeof(r->buffer))
+ if (r->input->offset > (goffset)sizeof(r->buffer))
/* disable buffering */
r->tail = 0;
else if (r->tail == (size_t)is->offset) {
@@ -161,44 +159,46 @@ input_rewind_read(struct input_stream *is, void *ptr, size_t size)
memcpy(r->buffer + r->tail, ptr, nbytes);
r->tail += nbytes;
- assert(r->tail == (size_t)r->input.offset);
+ assert(r->tail == (size_t)r->input->offset);
}
- copy_attributes(is);
+ copy_attributes(r);
return nbytes;
}
}
static bool
-input_rewind_eof(G_GNUC_UNUSED struct input_stream *is)
+input_rewind_eof(struct input_stream *is)
{
- struct input_rewind *r = is->data;
+ struct input_rewind *r = (struct input_rewind *)is;
- return !reading_from_buffer(is) && input_stream_eof(&r->input);
+ return !reading_from_buffer(r) && input_stream_eof(r->input);
}
static bool
-input_rewind_seek(struct input_stream *is, off_t offset, int whence)
+input_rewind_seek(struct input_stream *is, goffset offset, int whence,
+ GError **error_r)
{
- struct input_rewind *r = is->data;
+ struct input_rewind *r = (struct input_rewind *)is;
assert(is->ready);
- if (whence == SEEK_SET && r->tail > 0 && offset <= (off_t)r->tail) {
+ if (whence == SEEK_SET && r->tail > 0 && offset <= (goffset)r->tail) {
/* buffered seek */
- assert(!reading_from_buffer(is) ||
+ assert(!reading_from_buffer(r) ||
r->head == (size_t)is->offset);
- assert(r->tail == (size_t)r->input.offset);
+ assert(r->tail == (size_t)r->input->offset);
r->head = (size_t)offset;
is->offset = offset;
return true;
} else {
- bool success = input_stream_seek(&r->input, offset, whence);
- copy_attributes(is);
+ bool success = input_stream_seek(r->input, offset, whence,
+ error_r);
+ copy_attributes(r);
/* disable the buffer, because r->input has left the
buffered range now */
@@ -217,7 +217,7 @@ static const struct input_plugin rewind_input_plugin = {
.seek = input_rewind_seek,
};
-void
+struct input_stream *
input_rewind_open(struct input_stream *is)
{
struct input_rewind *c;
@@ -225,26 +225,14 @@ input_rewind_open(struct input_stream *is)
assert(is != NULL);
assert(is->offset == 0);
- if (is->plugin != &input_plugin_curl
-#ifdef ENABLE_MMS
- && is->plugin != &input_plugin_mms
-#endif
- )
- /* due to limitations in the input_plugin API, we only
- (explicitly) support the CURL input plugin */
- return;
+ if (is->seekable)
+ /* seekable resources don't need this plugin */
+ return is;
c = g_new(struct input_rewind, 1);
+ input_stream_init(&c->base, &rewind_input_plugin, is->uri);
c->tail = 0;
+ c->input = is;
- /* move the CURL input stream to c->input */
- c->input = *is;
- if (is->plugin == &input_plugin_curl)
- input_curl_reinit(&c->input);
-
- /* convert the existing input_stream pointer to a "rewind"
- input stream */
- is->plugin = &rewind_input_plugin;
- is->data = c;
- is->mime = g_strdup(c->input.mime);
+ return &c->base;
}
diff --git a/src/input/rewind_input_plugin.h b/src/input/rewind_input_plugin.h
index 33fedf4e1..23d25d94d 100644
--- a/src/input/rewind_input_plugin.h
+++ b/src/input/rewind_input_plugin.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -27,23 +27,11 @@
#ifndef MPD_INPUT_REWIND_H
#define MPD_INPUT_REWIND_H
-#include "config.h"
+#include "check.h"
struct input_stream;
-#ifdef HAVE_CURL
-
-void
+struct input_stream *
input_rewind_open(struct input_stream *is);
-#else
-
-static inline void
-input_rewind_open(struct input_stream *is)
-{
- (void)is;
-}
-
-#endif
-
#endif
diff --git a/src/input_init.c b/src/input_init.c
new file mode 100644
index 000000000..1438c3e52
--- /dev/null
+++ b/src/input_init.c
@@ -0,0 +1,100 @@
+/*
+ * 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 "input_init.h"
+#include "input_plugin.h"
+#include "input_registry.h"
+#include "conf.h"
+#include "glib_compat.h"
+
+#include <string.h>
+
+static inline GQuark
+input_quark(void)
+{
+ return g_quark_from_static_string("input");
+}
+
+/**
+ * Find the "input" configuration block for the specified plugin.
+ *
+ * @param plugin_name the name of the input plugin
+ * @return the configuration block, or NULL if none was configured
+ */
+static const struct config_param *
+input_plugin_config(const char *plugin_name, GError **error_r)
+{
+ const struct config_param *param = NULL;
+
+ while ((param = config_get_next_param(CONF_INPUT, param)) != NULL) {
+ const char *name =
+ config_get_block_string(param, "plugin", NULL);
+ if (name == NULL) {
+ g_set_error(error_r, input_quark(), 0,
+ "input configuration without 'plugin' name in line %d",
+ param->line);
+ return NULL;
+ }
+
+ if (strcmp(name, plugin_name) == 0)
+ return param;
+ }
+
+ return NULL;
+}
+
+bool
+input_stream_global_init(GError **error_r)
+{
+ GError *error = NULL;
+
+ for (unsigned i = 0; input_plugins[i] != NULL; ++i) {
+ const struct input_plugin *plugin = input_plugins[i];
+ const struct config_param *param =
+ input_plugin_config(plugin->name, &error);
+ if (param == NULL && error != NULL) {
+ g_propagate_error(error_r, error);
+ return false;
+ }
+
+ if (!config_get_block_bool(param, "enabled", true))
+ /* the plugin is disabled in mpd.conf */
+ continue;
+
+ if (plugin->init == NULL || plugin->init(param, &error))
+ input_plugins_enabled[i] = true;
+ else {
+ g_propagate_prefixed_error(error_r, error,
+ "Failed to initialize input plugin '%s': ",
+ plugin->name);
+ return false;
+ }
+ }
+
+ return true;
+}
+
+void input_stream_global_finish(void)
+{
+ for (unsigned i = 0; input_plugins[i] != NULL; ++i)
+ if (input_plugins_enabled[i] &&
+ input_plugins[i]->finish != NULL)
+ input_plugins[i]->finish();
+}
diff --git a/src/input_init.h b/src/input_init.h
new file mode 100644
index 000000000..eded15fa9
--- /dev/null
+++ b/src/input_init.h
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_INPUT_INIT_H
+#define MPD_INPUT_INIT_H
+
+#include "check.h"
+
+#include <glib.h>
+#include <stdbool.h>
+
+/**
+ * Initializes this library and all input_stream implementations.
+ *
+ * @param error_r location to store the error occuring, or NULL to
+ * ignore errors
+ */
+bool
+input_stream_global_init(GError **error_r);
+
+/**
+ * Deinitializes this library and all input_stream implementations.
+ */
+void input_stream_global_finish(void);
+
+#endif
diff --git a/src/input_plugin.h b/src/input_plugin.h
index 8fe852bc6..10be48dbb 100644
--- a/src/input_plugin.h
+++ b/src/input_plugin.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -35,10 +35,12 @@ struct input_plugin {
/**
* Global initialization. This method is called when MPD starts.
*
+ * @param error_r location to store the error occuring, or
+ * NULL to ignore errors
* @return true on success, false if the plugin should be
* disabled
*/
- bool (*init)(const struct config_param *param);
+ bool (*init)(const struct config_param *param, GError **error_r);
/**
* Global deinitialization. Called once before MPD shuts
@@ -46,14 +48,16 @@ struct input_plugin {
*/
void (*finish)(void);
- bool (*open)(struct input_stream *is, const char *url);
+ struct input_stream *(*open)(const char *uri, GError **error_r);
void (*close)(struct input_stream *is);
struct tag *(*tag)(struct input_stream *is);
- int (*buffer)(struct input_stream *is);
- size_t (*read)(struct input_stream *is, void *ptr, size_t size);
+ int (*buffer)(struct input_stream *is, GError **error_r);
+ size_t (*read)(struct input_stream *is, void *ptr, size_t size,
+ GError **error_r);
bool (*eof)(struct input_stream *is);
- bool (*seek)(struct input_stream *is, off_t offset, int whence);
+ bool (*seek)(struct input_stream *is, goffset offset, int whence,
+ GError **error_r);
};
#endif
diff --git a/src/input_registry.c b/src/input_registry.c
new file mode 100644
index 000000000..0b9b47d10
--- /dev/null
+++ b/src/input_registry.c
@@ -0,0 +1,59 @@
+/*
+ * 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 "input_registry.h"
+#include "input/file_input_plugin.h"
+
+#ifdef ENABLE_ARCHIVE
+#include "input/archive_input_plugin.h"
+#endif
+
+#ifdef ENABLE_CURL
+#include "input/curl_input_plugin.h"
+#endif
+
+#ifdef HAVE_FFMPEG
+#include "input/ffmpeg_input_plugin.h"
+#endif
+
+#ifdef ENABLE_MMS
+#include "input/mms_input_plugin.h"
+#endif
+
+#include <glib.h>
+
+const struct input_plugin *const input_plugins[] = {
+ &input_plugin_file,
+#ifdef ENABLE_ARCHIVE
+ &input_plugin_archive,
+#endif
+#ifdef ENABLE_CURL
+ &input_plugin_curl,
+#endif
+#ifdef HAVE_FFMPEG
+ &input_plugin_ffmpeg,
+#endif
+#ifdef ENABLE_MMS
+ &input_plugin_mms,
+#endif
+ NULL
+};
+
+bool input_plugins_enabled[G_N_ELEMENTS(input_plugins) - 1];
diff --git a/src/input_registry.h b/src/input_registry.h
new file mode 100644
index 000000000..e85d6be8e
--- /dev/null
+++ b/src/input_registry.h
@@ -0,0 +1,35 @@
+/*
+ * 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_INPUT_REGISTRY_H
+#define MPD_INPUT_REGISTRY_H
+
+#include "check.h"
+
+#include <stdbool.h>
+
+/**
+ * NULL terminated list of all input plugins which were enabled at
+ * compile time.
+ */
+extern const struct input_plugin *const input_plugins[];
+
+extern bool input_plugins_enabled[];
+
+#endif
diff --git a/src/input_stream.c b/src/input_stream.c
index 6a1b5841b..e769adb92 100644
--- a/src/input_stream.c
+++ b/src/input_stream.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -17,137 +17,64 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
-#include "input_plugin.h"
#include "config.h"
-#include "conf.h"
-
-#include "input/file_input_plugin.h"
+#include "input_stream.h"
+#include "input_registry.h"
+#include "input_plugin.h"
#include "input/rewind_input_plugin.h"
-#ifdef ENABLE_ARCHIVE
-#include "input/archive_input_plugin.h"
-#endif
-
-#ifdef HAVE_CURL
-#include "input/curl_input_plugin.h"
-#endif
-
-#include "input/lastfm_input_plugin.h"
-
-#ifdef ENABLE_MMS
-#include "input/mms_input_plugin.h"
-#endif
-
#include <glib.h>
#include <assert.h>
-#include <string.h>
-
-static const struct input_plugin *const input_plugins[] = {
- &input_plugin_file,
-#ifdef ENABLE_ARCHIVE
- &input_plugin_archive,
-#endif
-#ifdef HAVE_CURL
- &input_plugin_curl,
-#endif
-#ifdef ENABLE_LASTFM
- &lastfm_input_plugin,
-#endif
-#ifdef ENABLE_MMS
- &input_plugin_mms,
-#endif
-};
-
-static bool input_plugins_enabled[G_N_ELEMENTS(input_plugins)];
-
-static const unsigned num_input_plugins =
- sizeof(input_plugins) / sizeof(input_plugins[0]);
-
-/**
- * Find the "input" configuration block for the specified plugin.
- *
- * @param plugin_name the name of the input plugin
- * @return the configuration block, or NULL if none was configured
- */
-static const struct config_param *
-input_plugin_config(const char *plugin_name)
-{
- const struct config_param *param = NULL;
-
- while ((param = config_get_next_param(CONF_INPUT, param)) != NULL) {
- const char *name =
- config_get_block_string(param, "plugin", NULL);
- if (name == NULL)
- g_error("input configuration without 'plugin' name in line %d",
- param->line);
- if (strcmp(name, plugin_name) == 0)
- return param;
- }
-
- return NULL;
-}
-
-void input_stream_global_init(void)
+static inline GQuark
+input_quark(void)
{
- for (unsigned i = 0; i < num_input_plugins; ++i) {
- const struct input_plugin *plugin = input_plugins[i];
- const struct config_param *param =
- input_plugin_config(plugin->name);
-
- if (!config_get_block_bool(param, "enabled", true))
- /* the plugin is disabled in mpd.conf */
- continue;
-
- if (plugin->init == NULL || plugin->init(param))
- input_plugins_enabled[i] = true;
- }
+ return g_quark_from_static_string("input");
}
-void input_stream_global_finish(void)
+struct input_stream *
+input_stream_open(const char *url, GError **error_r)
{
- for (unsigned i = 0; i < num_input_plugins; ++i)
- if (input_plugins_enabled[i] &&
- input_plugins[i]->finish != NULL)
- input_plugins[i]->finish();
-}
+ GError *error = NULL;
-bool
-input_stream_open(struct input_stream *is, const char *url)
-{
- is->seekable = false;
- is->ready = false;
- is->offset = 0;
- is->size = -1;
- is->error = 0;
- is->mime = NULL;
-
- for (unsigned i = 0; i < num_input_plugins; ++i) {
+ assert(error_r == NULL || *error_r == NULL);
+
+ for (unsigned i = 0; input_plugins[i] != NULL; ++i) {
const struct input_plugin *plugin = input_plugins[i];
+ struct input_stream *is;
- if (input_plugins_enabled[i] && plugin->open(is, url)) {
+ if (!input_plugins_enabled[i])
+ continue;
+
+ is = plugin->open(url, &error);
+ if (is != NULL) {
assert(is->plugin != NULL);
assert(is->plugin->close != NULL);
assert(is->plugin->read != NULL);
assert(is->plugin->eof != NULL);
assert(!is->seekable || is->plugin->seek != NULL);
- input_rewind_open(is);
+ is = input_rewind_open(is);
- return true;
+ return is;
+ } else if (error != NULL) {
+ g_propagate_error(error_r, error);
+ return NULL;
}
}
+ g_set_error(error_r, input_quark(), 0, "Unrecognized URI");
return false;
}
bool
-input_stream_seek(struct input_stream *is, off_t offset, int whence)
+input_stream_seek(struct input_stream *is, goffset offset, int whence,
+ GError **error_r)
{
if (is->plugin->seek == NULL)
return false;
- return is->plugin->seek(is, offset, whence);
+ return is->plugin->seek(is, offset, whence, error_r);
}
struct tag *
@@ -161,19 +88,18 @@ input_stream_tag(struct input_stream *is)
}
size_t
-input_stream_read(struct input_stream *is, void *ptr, size_t size)
+input_stream_read(struct input_stream *is, void *ptr, size_t size,
+ GError **error_r)
{
assert(ptr != NULL);
assert(size > 0);
- return is->plugin->read(is, ptr, size);
+ return is->plugin->read(is, ptr, size, error_r);
}
void input_stream_close(struct input_stream *is)
{
is->plugin->close(is);
-
- g_free(is->mime);
}
bool input_stream_eof(struct input_stream *is)
@@ -181,10 +107,11 @@ bool input_stream_eof(struct input_stream *is)
return is->plugin->eof(is);
}
-int input_stream_buffer(struct input_stream *is)
+int
+input_stream_buffer(struct input_stream *is, GError **error_r)
{
if (is->plugin->buffer == NULL)
return 0;
- return is->plugin->buffer(is);
+ return is->plugin->buffer(is, error_r);
}
diff --git a/src/input_stream.h b/src/input_stream.h
index 35b0d44fd..056d008a7 100644
--- a/src/input_stream.h
+++ b/src/input_stream.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -20,11 +20,17 @@
#ifndef MPD_INPUT_STREAM_H
#define MPD_INPUT_STREAM_H
+#include "check.h"
+
+#include <glib.h>
+
#include <stddef.h>
#include <stdbool.h>
#include <sys/types.h>
-struct input_stream;
+#if !GLIB_CHECK_VERSION(2,14,0)
+typedef gint64 goffset;
+#endif
struct input_stream {
/**
@@ -33,9 +39,10 @@ struct input_stream {
const struct input_plugin *plugin;
/**
- * an opaque pointer managed by the plugin
+ * The absolute URI which was used to open this stream. May
+ * be NULL if this is unknown.
*/
- void *data;
+ char *uri;
/**
* indicates whether the stream is ready for reading and
@@ -49,19 +56,14 @@ struct input_stream {
bool seekable;
/**
- * an optional errno error code, set to non-zero after an error occured
- */
- int error;
-
- /**
* the size of the resource, or -1 if unknown
*/
- off_t size;
+ goffset size;
/**
* the current offset within the stream
*/
- off_t offset;
+ goffset offset;
/**
* the MIME content type of the resource, or NULL if unknown
@@ -69,30 +71,37 @@ struct input_stream {
char *mime;
};
-/**
- * Initializes this library and all input_stream implementations.
- */
-void input_stream_global_init(void);
-
-/**
- * Deinitializes this library and all input_stream implementations.
- */
-void input_stream_global_finish(void);
+static inline void
+input_stream_init(struct input_stream *is, const struct input_plugin *plugin,
+ const char *uri)
+{
+ is->plugin = plugin;
+ is->uri = g_strdup(uri);
+ is->ready = false;
+ is->seekable = false;
+ is->size = -1;
+ is->offset = 0;
+ is->mime = NULL;
+}
+
+static inline void
+input_stream_deinit(struct input_stream *is)
+{
+ g_free(is->uri);
+ g_free(is->mime);
+}
/**
* Opens a new input stream. You may not access it until the "ready"
* flag is set.
*
- * @param is the input_stream object allocated by the caller
- * @return true on success
+ * @return an #input_stream object on success, NULL on error
*/
-bool
-input_stream_open(struct input_stream *is, const char *url);
+struct input_stream *
+input_stream_open(const char *uri, GError **error_r);
/**
- * Closes the input stream and free resources. This does not free the
- * input_stream pointer itself, because it is assumed to be allocated
- * by the caller.
+ * Close the input stream and free resources.
*/
void
input_stream_close(struct input_stream *is);
@@ -106,7 +115,8 @@ input_stream_close(struct input_stream *is);
* @param whence the base of the seek, one of SEEK_SET, SEEK_CUR, SEEK_END
*/
bool
-input_stream_seek(struct input_stream *is, off_t offset, int whence);
+input_stream_seek(struct input_stream *is, goffset offset, int whence,
+ GError **error_r);
/**
* Returns true if the stream has reached end-of-file.
@@ -130,7 +140,7 @@ input_stream_tag(struct input_stream *is);
* The semantics of this function are not well-defined, and it will
* eventually be removed.
*/
-int input_stream_buffer(struct input_stream *is);
+int input_stream_buffer(struct input_stream *is, GError **error_r);
/**
* Reads data from the stream into the caller-supplied buffer.
@@ -142,6 +152,7 @@ int input_stream_buffer(struct input_stream *is);
* @return the number of bytes read
*/
size_t
-input_stream_read(struct input_stream *is, void *ptr, size_t size);
+input_stream_read(struct input_stream *is, void *ptr, size_t size,
+ GError **error_r);
#endif
diff --git a/src/listen.c b/src/listen.c
index a4822c104..da2e79909 100644
--- a/src/listen.c
+++ b/src/listen.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -17,337 +17,51 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "listen.h"
-#include "socket_util.h"
+#include "server_socket.h"
#include "client.h"
#include "conf.h"
-#include "utils.h"
-#include "config.h"
+#include "glib_compat.h"
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <fcntl.h>
#include <string.h>
-#include <errno.h>
-#include <unistd.h>
-#include <stdlib.h>
#include <assert.h>
-#ifdef WIN32
-#include <ws2tcpip.h>
-#include <winsock.h>
-#else
-#include <netinet/in.h>
-#include <sys/socket.h>
-#include <sys/un.h>
-#include <netdb.h>
-#endif
-
#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "listen"
#define DEFAULT_PORT 6600
-struct listen_socket {
- struct listen_socket *next;
-
- int fd;
-
- guint source_id;
-};
-
-static struct listen_socket *listen_sockets;
+static struct server_socket *listen_socket;
int listen_port;
-static GQuark
-listen_quark(void)
-{
- return g_quark_from_static_string("listen");
-}
-
-static gboolean
-listen_in_event(GIOChannel *source, GIOCondition condition, gpointer data);
-
-static bool
-listen_add_address(int pf, const struct sockaddr *addrp, socklen_t addrlen,
- GError **error)
-{
- char *address_string;
- int fd;
- struct listen_socket *ls;
- GIOChannel *channel;
-
- address_string = sockaddr_to_string(addrp, addrlen, NULL);
- if (address_string != NULL) {
- g_debug("binding to socket address %s", address_string);
- g_free(address_string);
- }
-
- fd = socket_bind_listen(pf, SOCK_STREAM, 0, addrp, addrlen, 5, error);
- if (fd < 0)
- return false;
-
- ls = g_new(struct listen_socket, 1);
- ls->fd = fd;
-
- channel = g_io_channel_unix_new(fd);
- ls->source_id = g_io_add_watch(channel, G_IO_IN,
- listen_in_event, GINT_TO_POINTER(fd));
- g_io_channel_unref(channel);
-
- ls->next = listen_sockets;
- listen_sockets = ls;
-
- return true;
-}
-
-#ifdef HAVE_TCP
-
-/**
- * Add a listener on a port on all IPv4 interfaces.
- *
- * @param port the TCP port
- * @param error location to store the error occuring, or NULL to ignore errors
- * @return true on success
- */
-static bool
-listen_add_port_ipv4(unsigned int port, GError **error)
-{
- struct sockaddr_in sin;
- const struct sockaddr *addrp = (const struct sockaddr *)&sin;
- socklen_t addrlen = sizeof(sin);
-
- memset(&sin, 0, sizeof(sin));
- sin.sin_port = htons(port);
- sin.sin_family = AF_INET;
- sin.sin_addr.s_addr = INADDR_ANY;
-
- return listen_add_address(PF_INET, addrp, addrlen, error);
-}
-
-#ifdef HAVE_IPV6
-/**
- * Add a listener on a port on all IPv6 interfaces.
- *
- * @param port the TCP port
- * @param error location to store the error occuring, or NULL to ignore errors
- * @return true on success
- */
-static bool
-listen_add_port_ipv6(unsigned int port, GError **error)
-{
- struct sockaddr_in6 sin;
- const struct sockaddr *addrp = (const struct sockaddr *)&sin;
- socklen_t addrlen = sizeof(sin);
-
- memset(&sin, 0, sizeof(sin));
- sin.sin6_port = htons(port);
- sin.sin6_family = AF_INET6;
-
- return listen_add_address(PF_INET6, addrp, addrlen, error);
-}
-#endif /* HAVE_IPV6 */
-
-#endif /* HAVE_TCP */
-
-/**
- * Add a listener on a port on all interfaces.
- *
- * @param port the TCP port
- * @param error location to store the error occuring, or NULL to ignore errors
- * @return true on success
- */
-static bool
-listen_add_port(unsigned int port, GError **error)
-{
-#ifdef HAVE_TCP
- bool success;
-#ifdef HAVE_IPV6
- bool success6;
- GError *error2 = NULL;
-#endif
-
- g_debug("binding to any address");
-
-#ifdef HAVE_IPV6
- success6 = listen_add_port_ipv6(port, &error2);
- if (!success6) {
- if (error2->domain != listen_quark() ||
- (error2->code != EAFNOSUPPORT && error2->code != EINVAL &&
- error2->code != EPROTONOSUPPORT)) {
- g_propagate_error(error, error2);
- return false;
- }
-
- /* although MPD was compiled with IPv6 support, this
- host does not have it - ignore this error */
- g_error_free(error2);
- }
-#endif
-
- success = listen_add_port_ipv4(port, error);
- if (!success) {
-#ifdef HAVE_IPV6
- if (success6)
- /* non-critical: IPv6 listener is
- already set up */
- g_clear_error(error);
- else
-#endif
- return false;
- }
-
- return true;
-#else /* HAVE_TCP */
- (void)port;
-
- g_set_error(error, listen_quark(), 0,
- "TCP support is disabled");
- return false;
-#endif /* HAVE_TCP */
-}
-
-/**
- * Resolves a host name, and adds listeners on all addresses in the
- * result set.
- *
- * @param hostname the host name to be resolved
- * @param port the TCP port
- * @param error location to store the error occuring, or NULL to ignore errors
- * @return true on success
- */
-static bool
-listen_add_host(const char *hostname, unsigned port, GError **error_r)
-{
-#ifdef HAVE_TCP
- struct addrinfo hints, *ai, *i;
- char service[20];
- int ret;
- bool success;
-
- g_debug("binding to address for %s", hostname);
-
- memset(&hints, 0, sizeof(hints));
- hints.ai_flags = AI_PASSIVE;
-#ifdef AI_ADDRCONFIG
- hints.ai_flags |= AI_ADDRCONFIG;
-#endif
- hints.ai_family = PF_UNSPEC;
- hints.ai_socktype = SOCK_STREAM;
- hints.ai_protocol = IPPROTO_TCP;
-
- g_snprintf(service, sizeof(service), "%u", port);
-
- ret = getaddrinfo(hostname, service, &hints, &ai);
- if (ret != 0) {
- g_set_error(error_r, listen_quark(), ret,
- "Failed to look up host \"%s\": %s",
- hostname, gai_strerror(ret));
- return false;
- }
-
- for (i = ai; i != NULL; i = i->ai_next) {
- GError *error = NULL;
-
- success = listen_add_address(i->ai_family, i->ai_addr,
- i->ai_addrlen, &error);
- if (!success) {
- if (i == ai) {
- /* first bind has failed: fatal
- error */
- g_propagate_error(error_r, error);
- return false;
- } else {
- char *address_string =
- sockaddr_to_string(i->ai_addr,
- i->ai_addrlen,
- NULL);
- if (address_string == NULL)
- address_string = g_strdup("[unknown]");
-
- g_warning("bind to %s failed: %s "
- "(continuing anyway, because at "
- "least one address is bound)",
- address_string, error->message);
- g_free(address_string);
- g_error_free(error);
- }
- }
- }
-
- freeaddrinfo(ai);
-
- return true;
-#else /* HAVE_TCP */
-
- (void)hostname;
- (void)port;
-
- g_set_error(error_r, listen_quark(), 0,
- "TCP support is disabled");
- return false;
-#endif /* HAVE_TCP */
-}
-
-#ifdef HAVE_UN
-/**
- * Add a listener on a Unix domain socket.
- *
- * @param path the absolute socket path
- * @param error location to store the error occuring, or NULL to ignore errors
- * @return true on success
- */
-static bool
-listen_add_path(const char *path, GError **error)
+static void
+listen_callback(int fd, const struct sockaddr *address,
+ size_t address_length, int uid, G_GNUC_UNUSED void *ctx)
{
- size_t path_length;
- struct sockaddr_un s_un;
- const struct sockaddr *addrp = (const struct sockaddr *)&s_un;
- socklen_t addrlen = sizeof(s_un);
- bool success;
-
- path_length = strlen(path);
- if (path_length >= sizeof(s_un.sun_path)) {
- g_set_error(error, listen_quark(), 0,
- "unix socket path is too long");
- return false;
- }
-
- unlink(path);
-
- s_un.sun_family = AF_UNIX;
- memcpy(s_un.sun_path, path, path_length + 1);
-
- success = listen_add_address(PF_UNIX, addrp, addrlen, error);
- if (!success)
- return false;
-
- /* allow everybody to connect */
- chmod(path, 0666);
-
- return true;
+ client_new(fd, address, address_length, uid);
}
-#endif /* HAVE_UN */
static bool
listen_add_config_param(unsigned int port,
const struct config_param *param,
- GError **error)
+ GError **error_r)
{
assert(param != NULL);
if (0 == strcmp(param->value, "any")) {
- return listen_add_port(port, error);
-#ifdef HAVE_UN
+ return server_socket_add_port(listen_socket, port, error_r);
} else if (param->value[0] == '/') {
- return listen_add_path(param->value, error);
-#endif /* HAVE_UN */
+ return server_socket_add_path(listen_socket, param->value,
+ error_r);
} else {
- return listen_add_host(param->value, port, error);
+ return server_socket_add_host(listen_socket, param->value,
+ port, error_r);
}
}
-void listen_global_init(void)
+bool
+listen_global_init(GError **error_r)
{
int port = config_get_positive(CONF_PORT, DEFAULT_PORT);
const struct config_param *param =
@@ -355,16 +69,20 @@ void listen_global_init(void)
bool success;
GError *error = NULL;
+ listen_socket = server_socket_new(listen_callback, NULL);
+
if (param != NULL) {
/* "bind_to_address" is configured, create listeners
for all values */
do {
success = listen_add_config_param(port, param, &error);
- if (!success)
- g_error("Failed to listen on %s (line %i): %s",
- param->value, param->line,
- error->message);
+ if (!success) {
+ g_propagate_prefixed_error(error_r, error,
+ "Failed to listen on %s (line %i): ",
+ param->value, param->line);
+ return false;
+ }
param = config_get_next_param(CONF_BIND_TO_ADDRESS,
param);
@@ -373,71 +91,27 @@ void listen_global_init(void)
/* no "bind_to_address" configured, bind the
configured port on all interfaces */
- success = listen_add_port(port, &error);
- if (!success)
- g_error("Failed to listen on *:%d: %s",
- port, error->message);
+ success = server_socket_add_port(listen_socket, port, error_r);
+ if (!success) {
+ g_propagate_prefixed_error(error_r, error,
+ "Failed to listen on *:%d: ",
+ port);
+ return false;
+ }
}
+ if (!server_socket_open(listen_socket, error_r))
+ return false;
+
listen_port = port;
+ return true;
}
void listen_global_finish(void)
{
g_debug("listen_global_finish called");
- while (listen_sockets != NULL) {
- struct listen_socket *ls = listen_sockets;
- listen_sockets = ls->next;
+ assert(listen_socket != NULL);
- g_source_remove(ls->source_id);
- close(ls->fd);
- g_free(ls);
- }
-}
-
-static int get_remote_uid(int fd)
-{
-#ifdef HAVE_STRUCT_UCRED
- struct ucred cred;
- socklen_t len = sizeof (cred);
-
- if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &cred, &len) < 0)
- return 0;
-
- return cred.uid;
-#else
-#ifdef HAVE_GETPEEREID
- uid_t euid;
- gid_t egid;
-
- if (getpeereid(fd, &euid, &egid) == 0)
- return euid;
-#else
- (void)fd;
-#endif
- return -1;
-#endif
-}
-
-static gboolean
-listen_in_event(G_GNUC_UNUSED GIOChannel *source,
- G_GNUC_UNUSED GIOCondition condition,
- gpointer data)
-{
- int listen_fd = GPOINTER_TO_INT(data), fd;
- struct sockaddr_storage sa;
- socklen_t sa_length = sizeof(sa);
-
- fd = accept(listen_fd, (struct sockaddr*)&sa, &sa_length);
- if (fd >= 0) {
- set_nonblocking(fd);
-
- client_new(fd, (struct sockaddr*)&sa, sa_length,
- get_remote_uid(fd));
- } else if (fd < 0 && errno != EINTR) {
- g_warning("Problems accept()'ing");
- }
-
- return true;
+ server_socket_free(listen_socket);
}
diff --git a/src/listen.h b/src/listen.h
index 63253fc53..449b5ebae 100644
--- a/src/listen.h
+++ b/src/listen.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -20,9 +20,14 @@
#ifndef MPD_LISTEN_H
#define MPD_LISTEN_H
+#include <glib.h>
+
+#include <stdbool.h>
+
extern int listen_port;
-void listen_global_init(void);
+bool
+listen_global_init(GError **error_r);
void listen_global_finish(void);
diff --git a/src/locate.c b/src/locate.c
index 7b4721fa9..e27858a0e 100644
--- a/src/locate.c
+++ b/src/locate.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -17,6 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "locate.h"
#include "path.h"
#include "tag.h"
diff --git a/src/locate.h b/src/locate.h
index d0bdfa136..0283f551b 100644
--- a/src/locate.h
+++ b/src/locate.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
diff --git a/src/log.c b/src/log.c
index 94691ab64..556c8b04f 100644
--- a/src/log.c
+++ b/src/log.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -17,10 +17,12 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "log.h"
#include "conf.h"
#include "utils.h"
-#include "config.h"
+#include "fd_util.h"
+#include "mpd_error.h"
#include <assert.h>
#include <sys/types.h>
@@ -59,9 +61,9 @@ static void redirect_logs(int fd)
{
assert(fd >= 0);
if (dup2(fd, STDOUT_FILENO) < 0)
- g_error("problems dup2 stdout : %s\n", strerror(errno));
+ MPD_ERROR("problems dup2 stdout : %s\n", strerror(errno));
if (dup2(fd, STDERR_FILENO) < 0)
- g_error("problems dup2 stderr : %s\n", strerror(errno));
+ MPD_ERROR("problems dup2 stderr : %s\n", strerror(errno));
}
static const char *log_date(void)
@@ -128,7 +130,7 @@ open_log_file(void)
{
assert(out_filename != NULL);
- return open(out_filename, O_CREAT | O_WRONLY | O_APPEND, 0666);
+ return open_cloexec(out_filename, O_CREAT | O_WRONLY | O_APPEND, 0666);
}
static void
@@ -137,8 +139,8 @@ log_init_file(const char *path, unsigned line)
out_filename = path;
out_fd = open_log_file();
if (out_fd < 0)
- g_error("problem opening log file \"%s\" (config line %u) for "
- "writing\n", path, line);
+ MPD_ERROR("problem opening log file \"%s\" (config line %u) "
+ "for writing\n", path, line);
g_log_set_default_handler(file_log_func, NULL);
}
@@ -215,8 +217,8 @@ parse_log_level(const char *value, unsigned line)
else if (0 == strcmp(value, "verbose"))
return G_LOG_LEVEL_DEBUG;
else {
- g_error("unknown log level \"%s\" at line %u\n",
- value, line);
+ MPD_ERROR("unknown log level \"%s\" at line %u\n",
+ value, line);
return G_LOG_LEVEL_MESSAGE;
}
}
@@ -251,8 +253,8 @@ void log_init(bool verbose, bool use_stdout)
available) */
log_init_syslog();
#else
- g_error("config parameter \"%s\" not found\n",
- CONF_LOG_FILE);
+ MPD_ERROR("config parameter \"%s\" not found\n",
+ CONF_LOG_FILE);
#endif
#ifdef HAVE_SYSLOG
} else if (strcmp(param->value, "syslog") == 0) {
@@ -271,7 +273,12 @@ void setup_log_output(bool use_stdout)
{
fflush(NULL);
if (!use_stdout) {
- if (out_filename != NULL) {
+#ifndef WIN32
+ if (out_filename == NULL)
+ out_fd = open("/dev/null", O_WRONLY);
+#endif
+
+ if (out_fd >= 0) {
redirect_logs(out_fd);
close(out_fd);
}
diff --git a/src/log.h b/src/log.h
index 3654fa7a7..e9daf1113 100644
--- a/src/log.h
+++ b/src/log.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
diff --git a/src/ls.c b/src/ls.c
index fd8f22fd4..c30765c62 100644
--- a/src/ls.c
+++ b/src/ls.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -17,10 +17,10 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "ls.h"
#include "uri.h"
#include "client.h"
-#include "config.h"
#include <assert.h>
#include <string.h>
@@ -32,18 +32,23 @@
* connected by IPC socket.
*/
static const char *remoteUrlPrefixes[] = {
-#ifdef HAVE_CURL
+#ifdef ENABLE_CURL
"http://",
#endif
-#ifdef ENABLE_LASTFM
- "lastfm://",
-#endif
#ifdef ENABLE_MMS
"mms://",
"mmsh://",
"mmst://",
"mmsu://",
#endif
+#ifdef HAVE_FFMPEG
+ "gopher://",
+ "rtp://",
+ "rtsp://",
+ "rtmp://",
+ "rtmpt://",
+ "rtmps://",
+#endif
NULL
};
diff --git a/src/ls.h b/src/ls.h
index 58e366d5b..d29e20a46 100644
--- a/src/ls.h
+++ b/src/ls.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
diff --git a/src/main.c b/src/main.c
index 5035a4836..a500e2934 100644
--- a/src/main.c
+++ b/src/main.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -17,6 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "main.h"
#include "daemon.h"
#include "client.h"
@@ -33,7 +34,6 @@
#include "path.h"
#include "mapper.h"
#include "chunk.h"
-#include "decoder_control.h"
#include "player_control.h"
#include "stats.h"
#include "sig_handlers.h"
@@ -42,19 +42,23 @@
#include "volume.h"
#include "log.h"
#include "permission.h"
-#include "replay_gain.h"
+#include "replay_gain_config.h"
#include "decoder_list.h"
-#include "input_stream.h"
+#include "input_init.h"
+#include "playlist_list.h"
#include "state_file.h"
#include "tag.h"
#include "dbUtils.h"
-#include "config.h"
-#include "normalize.h"
#include "zeroconf.h"
#include "event_pipe.h"
#include "dirvec.h"
#include "songvec.h"
#include "tag_pool.h"
+#include "mpd_error.h"
+
+#ifdef ENABLE_INOTIFY
+#include "inotify_update.h"
+#endif
#ifdef ENABLE_SQLITE
#include "sticker.h"
@@ -88,7 +92,34 @@ enum {
GThread *main_task;
GMainLoop *main_loop;
-struct notify main_notify;
+GCond *main_cond;
+
+static void
+glue_daemonize_init(const struct options *options)
+{
+ daemonize_init(config_get_string(CONF_USER, NULL),
+ config_get_string(CONF_GROUP, NULL),
+ config_get_path(CONF_PID_FILE));
+
+ if (options->kill)
+ daemonize_kill();
+}
+
+static void
+glue_mapper_init(void)
+{
+ const char *music_dir, *playlist_dir;
+
+ 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);
+#endif
+
+ playlist_dir = config_get_path(CONF_PLAYLIST_DIR);
+
+ mapper_init(music_dir, playlist_dir);
+}
/**
* Returns the database. If this function returns false, this has not
@@ -96,7 +127,7 @@ struct notify main_notify;
* process has been daemonized.
*/
static bool
-openDB(const Options *options)
+glue_db_init_and_load(void)
{
const char *path = config_get_path(CONF_DB_FILE);
bool ret;
@@ -111,23 +142,15 @@ openDB(const Options *options)
}
if (path == NULL)
- g_error(CONF_DB_FILE " setting missing");
+ MPD_ERROR(CONF_DB_FILE " setting missing");
db_init(path);
- if (options->createDB > 0)
- /* don't attempt to load the old database */
- return false;
-
ret = db_load(&error);
if (!ret) {
g_warning("Failed to load database: %s", error->message);
g_error_free(error);
- if (options->createDB < 0)
- g_error("can't open db file and using "
- "\"--no-create-db\" command line option");
-
if (!db_check())
exit(EXIT_FAILURE);
@@ -141,29 +164,51 @@ openDB(const Options *options)
}
/**
+ * Configure and initialize the sticker subsystem.
+ */
+static void
+glue_sticker_init(void)
+{
+#ifdef ENABLE_SQLITE
+ bool success;
+ GError *error = NULL;
+
+ success = sticker_global_init(config_get_path(CONF_STICKER_FILE),
+ &error);
+ if (!success)
+ MPD_ERROR("%s", error->message);
+#endif
+}
+
+static void
+glue_state_file_init(void)
+{
+ state_file_init(config_get_path(CONF_STATE_FILE));
+}
+
+/**
* Windows-only initialization of the Winsock2 library.
*/
-#ifdef WIN32
static void winsock_init(void)
{
+#ifdef WIN32
WSADATA sockinfo;
int retval;
retval = WSAStartup(MAKEWORD(2, 2), &sockinfo);
if(retval != 0)
{
- g_error("Attempt to open Winsock2 failed; error code %d\n",
+ MPD_ERROR("Attempt to open Winsock2 failed; error code %d\n",
retval);
}
if (LOBYTE(sockinfo.wVersion) != 2)
{
- g_error("We use Winsock2 but your version is either too new or "
- "old; please install Winsock 2.x\n");
+ MPD_ERROR("We use Winsock2 but your version is either too new "
+ "or old; please install Winsock 2.x\n");
}
-
-}
#endif
+}
/**
* Initialize the decoder and player core, including the music pipe.
@@ -182,8 +227,8 @@ initialize_decoder_and_player(void)
if (param != NULL) {
buffer_size = strtol(param->value, &test, 10);
if (*test != '\0' || buffer_size <= 0)
- g_error("buffer size \"%s\" is not a positive integer, "
- "line %i\n", param->value, param->line);
+ MPD_ERROR("buffer size \"%s\" is not a positive integer, "
+ "line %i\n", param->value, param->line);
} else
buffer_size = DEFAULT_BUFFER_SIZE;
@@ -192,15 +237,15 @@ initialize_decoder_and_player(void)
buffered_chunks = buffer_size / CHUNK_SIZE;
if (buffered_chunks >= 1 << 15)
- g_error("buffer size \"%li\" is too big\n", (long)buffer_size);
+ MPD_ERROR("buffer size \"%li\" is too big\n", (long)buffer_size);
param = config_get_param(CONF_BUFFER_BEFORE_PLAY);
if (param != NULL) {
perc = strtod(param->value, &test);
if (*test != '%' || perc < 0 || perc > 100) {
- g_error("buffered before play \"%s\" is not a positive "
- "percentage and less than 100 percent, line %i",
- param->value, param->line);
+ MPD_ERROR("buffered before play \"%s\" is not a positive "
+ "percentage and less than 100 percent, line %i",
+ param->value, param->line);
}
} else
perc = DEFAULT_BUFFER_BEFORE_PLAY;
@@ -210,7 +255,6 @@ initialize_decoder_and_player(void)
buffered_before_play = buffered_chunks;
pc_init(buffered_chunks, buffered_before_play);
- dc_init();
}
/**
@@ -226,11 +270,31 @@ idle_event_emitted(void)
client_manager_idle_add(flags);
}
+/**
+ * event_pipe callback function for PIPE_EVENT_SHUTDOWN
+ */
+static void
+shutdown_event_emitted(void)
+{
+ g_main_loop_quit(main_loop);
+}
+
int main(int argc, char *argv[])
{
- Options options;
+#ifdef WIN32
+ return win32_main(argc, argv);
+#else
+ return mpd_main(argc, argv);
+#endif
+}
+
+int mpd_main(int argc, char *argv[])
+{
+ struct options options;
clock_t start;
bool create_db;
+ GError *error = NULL;
+ bool success;
daemonize_close_stdin();
@@ -239,45 +303,52 @@ int main(int argc, char *argv[])
setlocale(LC_CTYPE,"");
#endif
+ g_set_application_name("Music Player Daemon");
+
/* enable GLib's thread safety code */
g_thread_init(NULL);
-#ifdef WIN32
winsock_init();
-#endif
idle_init();
dirvec_init();
songvec_init();
tag_pool_init();
config_global_init();
- parseOptions(argc, argv, &options);
-
- daemonize_init(config_get_string(CONF_USER, NULL),
- config_get_path(CONF_PID_FILE));
+ success = parse_cmdline(argc, argv, &options, &error);
+ if (!success) {
+ g_warning("%s", error->message);
+ g_error_free(error);
+ return EXIT_FAILURE;
+ }
- if (options.kill)
- daemonize_kill();
+ glue_daemonize_init(&options);
stats_global_init();
tag_lib_init();
- log_init(options.verbose, options.stdOutput);
+ log_init(options.verbose, options.log_stderr);
- listen_global_init();
+ success = listen_global_init(&error);
+ if (!success) {
+ g_warning("%s", error->message);
+ g_error_free(error);
+ return EXIT_FAILURE;
+ }
daemonize_set_user();
main_task = g_thread_self();
main_loop = g_main_loop_new(NULL, FALSE);
- notify_init(&main_notify);
+ main_cond = g_cond_new();
event_pipe_init();
event_pipe_register(PIPE_EVENT_IDLE, idle_event_emitted);
+ event_pipe_register(PIPE_EVENT_SHUTDOWN, shutdown_event_emitted);
path_global_init();
- mapper_init();
+ glue_mapper_init();
initPermissions();
- initPlaylist();
+ playlist_global_init();
spl_global_init();
#ifdef ENABLE_ARCHIVE
archive_plugin_init_all();
@@ -285,11 +356,9 @@ int main(int argc, char *argv[])
decoder_plugin_init_all();
update_global_init();
- create_db = !openDB(&options);
+ create_db = !glue_db_init_and_load();
-#ifdef ENABLE_SQLITE
- sticker_global_init(config_get_path(CONF_STICKER_FILE));
-#endif
+ glue_sticker_init();
command_init();
initialize_decoder_and_player();
@@ -298,12 +367,18 @@ int main(int argc, char *argv[])
audio_output_all_init();
client_manager_init();
replay_gain_global_init();
- initNormalization();
- input_stream_global_init();
+
+ if (!input_stream_global_init(&error)) {
+ g_warning("%s", error->message);
+ g_error_free(error);
+ return EXIT_FAILURE;
+ }
+
+ playlist_list_global_init();
daemonize(options.daemon);
- setup_log_output(options.stdOutput);
+ setup_log_output(options.log_stderr);
initSigHandlers();
@@ -312,30 +387,56 @@ int main(int argc, char *argv[])
player_create();
if (create_db) {
- /* the database failed to load, or MPD was started
- with --create-db: recreate a new database */
- unsigned job = directory_update_init(NULL);
+ /* the database failed to load: recreate the
+ database */
+ unsigned job = update_enqueue(NULL, true);
if (job == 0)
- g_error("directory update failed");
+ MPD_ERROR("directory update failed");
}
+ glue_state_file_init();
- state_file_init(config_get_path(CONF_STATE_FILE));
+ success = config_get_bool(CONF_AUTO_UPDATE, false);
+#ifdef ENABLE_INOTIFY
+ if (success && mapper_has_music_directory())
+ mpd_inotify_init(config_get_unsigned(CONF_AUTO_UPDATE_DEPTH,
+ G_MAXUINT));
+#else
+ if (success)
+ g_warning("inotify: auto_update was disabled. enable during compilation phase");
+#endif
- /* run the main loop */
+ config_global_check();
+ /* enable all audio outputs (if not already done by
+ playlist_state_restore() */
+ pc_update_audio();
+
+#ifdef WIN32
+ win32_app_started();
+#endif
+
+ /* run the main loop */
g_main_loop_run(main_loop);
+#ifdef WIN32
+ win32_app_stopping();
+#endif
+
/* cleanup */
g_main_loop_unref(main_loop);
+#ifdef ENABLE_INOTIFY
+ mpd_inotify_finish();
+#endif
+
state_file_finish();
- playerKill();
+ pc_kill();
finishZeroconf();
client_manager_deinit();
listen_global_finish();
- finishPlaylist();
+ playlist_global_finish();
start = clock();
db_finish();
@@ -346,18 +447,16 @@ int main(int argc, char *argv[])
sticker_global_finish();
#endif
- notify_deinit(&main_notify);
+ g_cond_free(main_cond);
event_pipe_deinit();
+ playlist_list_global_finish();
input_stream_global_finish();
- finishNormalization();
audio_output_all_finish();
- finishAudioConfig();
volume_finish();
mapper_finish();
path_global_finish();
finishPermissions();
- dc_deinit();
pc_deinit();
command_finish();
update_global_finish();
diff --git a/src/main.h b/src/main.h
index 8ed02bf5d..9b9cba018 100644
--- a/src/main.h
+++ b/src/main.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -26,6 +26,47 @@ extern GThread *main_task;
extern GMainLoop *main_loop;
-extern struct notify main_notify;
+extern GCond *main_cond;
+
+/**
+ * A entry point for application.
+ * On non-Windows platforms this is called directly from main()
+ * On Windows platform this is called from win32_main()
+ * after doing some initialization.
+ */
+int mpd_main(int argc, char *argv[]);
+
+#ifdef WIN32
+
+/**
+ * If program is run as windows service performs nessesary initialization
+ * and then calls mpd_main() with specified arguments.
+ * If program is run as a regular application calls mpd_main() immediately.
+ */
+int
+win32_main(int argc, char *argv[]);
+
+/**
+ * When running as a service reports to service control manager
+ * that our service is started.
+ * When running as a console application enables console handler that will
+ * trigger PIPE_EVENT_SHUTDOWN when user closes console window
+ * or presses Ctrl+C.
+ * This function should be called just before entering main loop.
+ */
+void
+win32_app_started(void);
+
+/**
+ * When running as a service reports to service control manager
+ * that our service is about to stop.
+ * When running as a console application enables console handler that will
+ * catch all shutdown requests and ignore them.
+ * This function should be called just after leaving main loop.
+ */
+void
+win32_app_stopping(void);
+
+#endif
#endif
diff --git a/src/main_win32.c b/src/main_win32.c
new file mode 100644
index 000000000..543d8ba81
--- /dev/null
+++ b/src/main_win32.c
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "main.h"
+
+#ifdef WIN32
+
+#include "mpd_error.h"
+#include "event_pipe.h"
+
+#include <glib.h>
+
+#define WINVER 0x0501
+#include <windows.h>
+
+static int service_argc;
+static char **service_argv;
+static char service_name[] = "";
+static BOOL ignore_console_events;
+static SERVICE_STATUS_HANDLE service_handle;
+
+static void WINAPI
+service_main(DWORD argc, CHAR *argv[]);
+
+static SERVICE_TABLE_ENTRY service_registry[] = {
+ {service_name, service_main},
+ {NULL, NULL}
+};
+
+static void
+service_notify_status(DWORD status_code)
+{
+ SERVICE_STATUS current_status;
+
+ current_status.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
+ current_status.dwControlsAccepted = status_code == SERVICE_START_PENDING
+ ? 0
+ : SERVICE_ACCEPT_SHUTDOWN | SERVICE_ACCEPT_STOP;
+
+ current_status.dwCurrentState = status_code;
+ current_status.dwWin32ExitCode = NO_ERROR;
+ current_status.dwCheckPoint = 0;
+ current_status.dwWaitHint = 1000;
+
+ SetServiceStatus(service_handle, &current_status);
+}
+
+static DWORD WINAPI
+service_dispatcher(G_GNUC_UNUSED DWORD control, G_GNUC_UNUSED DWORD event_type,
+ G_GNUC_UNUSED void *event_data, G_GNUC_UNUSED void *context)
+{
+ switch (control) {
+ case SERVICE_CONTROL_SHUTDOWN:
+ case SERVICE_CONTROL_STOP:
+ event_pipe_emit(PIPE_EVENT_SHUTDOWN);
+ return NO_ERROR;
+ default:
+ return NO_ERROR;
+ }
+}
+
+static void WINAPI
+service_main(G_GNUC_UNUSED DWORD argc, G_GNUC_UNUSED CHAR *argv[])
+{
+ DWORD error_code;
+ gchar* error_message;
+
+ service_handle =
+ RegisterServiceCtrlHandlerEx(service_name,
+ service_dispatcher, NULL);
+
+ if (service_handle == 0) {
+ error_code = GetLastError();
+ error_message = g_win32_error_message(error_code);
+ MPD_ERROR("RegisterServiceCtrlHandlerEx() failed: %s",
+ error_message);
+ }
+
+ service_notify_status(SERVICE_START_PENDING);
+ mpd_main(service_argc, service_argv);
+ service_notify_status(SERVICE_STOPPED);
+}
+
+static BOOL WINAPI
+console_handler(DWORD event)
+{
+ switch (event) {
+ case CTRL_C_EVENT:
+ case CTRL_CLOSE_EVENT:
+ if (!ignore_console_events)
+ event_pipe_emit(PIPE_EVENT_SHUTDOWN);
+ return TRUE;
+ default:
+ return FALSE;
+ }
+}
+
+int win32_main(int argc, char *argv[])
+{
+ DWORD error_code;
+ gchar* error_message;
+
+ service_argc = argc;
+ service_argv = argv;
+
+ if (StartServiceCtrlDispatcher(service_registry))
+ return 0; /* run as service successefully */
+
+ error_code = GetLastError();
+ if (error_code == ERROR_FAILED_SERVICE_CONTROLLER_CONNECT) {
+ /* running as console app */
+ SetConsoleTitle("Music Player Daemon");
+ ignore_console_events = TRUE;
+ SetConsoleCtrlHandler(console_handler, TRUE);
+ return mpd_main(argc, argv);
+ }
+
+ error_message = g_win32_error_message(error_code);
+ MPD_ERROR("StartServiceCtrlDispatcher() failed: %s", error_message);
+}
+
+void win32_app_started()
+{
+ if (service_handle != 0)
+ service_notify_status(SERVICE_RUNNING);
+ else
+ ignore_console_events = FALSE;
+}
+
+void win32_app_stopping()
+{
+ if (service_handle != 0)
+ service_notify_status(SERVICE_STOP_PENDING);
+ else
+ ignore_console_events = TRUE;
+}
+
+#endif
diff --git a/src/mapper.c b/src/mapper.c
index 5518cb79e..108de9531 100644
--- a/src/mapper.c
+++ b/src/mapper.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -21,20 +21,16 @@
* Maps directory and song objects to file system paths.
*/
+#include "config.h"
#include "mapper.h"
#include "directory.h"
#include "song.h"
#include "path.h"
-#include "conf.h"
#include <glib.h>
#include <assert.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <unistd.h>
#include <string.h>
-#include <errno.h>
static char *music_dir;
static size_t music_dir_length;
@@ -58,17 +54,10 @@ strdup_chop_slash(const char *path_fs)
static void
mapper_set_music_dir(const char *path)
{
- int ret;
- struct stat st;
-
music_dir = strdup_chop_slash(path);
music_dir_length = strlen(music_dir);
- ret = stat(music_dir, &st);
- if (ret < 0)
- g_warning("failed to stat music directory \"%s\": %s",
- music_dir, g_strerror(errno));
- else if (!S_ISDIR(st.st_mode))
+ if (!g_file_test(music_dir, G_FILE_TEST_IS_DIR))
g_warning("music directory is not a directory: \"%s\"",
music_dir);
}
@@ -76,38 +65,20 @@ mapper_set_music_dir(const char *path)
static void
mapper_set_playlist_dir(const char *path)
{
- int ret;
- struct stat st;
-
playlist_dir = g_strdup(path);
- ret = stat(playlist_dir, &st);
- if (ret < 0)
- g_warning("failed to stat playlist directory \"%s\": %s",
- playlist_dir, g_strerror(errno));
- else if (!S_ISDIR(st.st_mode))
+ if (!g_file_test(playlist_dir, G_FILE_TEST_IS_DIR))
g_warning("playlist directory is not a directory: \"%s\"",
playlist_dir);
}
-void mapper_init(void)
+void mapper_init(const char *_music_dir, const char *_playlist_dir)
{
- const char *path;
-
- path = config_get_path(CONF_MUSIC_DIR);
- if (path != NULL)
- mapper_set_music_dir(path);
-#if GLIB_CHECK_VERSION(2,14,0)
- else {
- path = g_get_user_special_dir(G_USER_DIRECTORY_MUSIC);
- if (path != NULL)
- mapper_set_music_dir(path);
- }
-#endif
+ if (_music_dir != NULL)
+ mapper_set_music_dir(_music_dir);
- path = config_get_path(CONF_PLAYLIST_DIR);
- if (path != NULL)
- mapper_set_playlist_dir(path);
+ if (_playlist_dir != NULL)
+ mapper_set_playlist_dir(_playlist_dir);
}
void mapper_finish(void)
@@ -122,6 +93,16 @@ mapper_has_music_directory(void)
return music_dir != NULL;
}
+const char *
+map_to_relative_path(const char *path_utf8)
+{
+ return music_dir != NULL &&
+ memcmp(path_utf8, music_dir, music_dir_length) == 0 &&
+ G_IS_DIR_SEPARATOR(path_utf8[music_dir_length])
+ ? path_utf8 + music_dir_length + 1
+ : path_utf8;
+}
+
char *
map_uri_fs(const char *uri)
{
@@ -189,9 +170,9 @@ map_song_fs(const struct song *song)
assert(song_is_file(song));
if (song_in_database(song))
- return map_directory_child_fs(song->parent, song->url);
+ return map_directory_child_fs(song->parent, song->uri);
else
- return utf8_to_fs_charset(song->url);
+ return utf8_to_fs_charset(song->uri);
}
char *
@@ -199,10 +180,10 @@ map_fs_to_utf8(const char *path_fs)
{
if (music_dir != NULL &&
strncmp(path_fs, music_dir, music_dir_length) == 0 &&
- path_fs[music_dir_length] == '/')
+ G_IS_DIR_SEPARATOR(path_fs[music_dir_length]))
/* remove musicDir prefix */
path_fs += music_dir_length + 1;
- else if (path_fs[0] == '/')
+ else if (G_IS_DIR_SEPARATOR(path_fs[0]))
/* not within musicDir */
return NULL;
diff --git a/src/mapper.h b/src/mapper.h
index f109de0bd..9f84f96fe 100644
--- a/src/mapper.h
+++ b/src/mapper.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -31,7 +31,7 @@
struct directory;
struct song;
-void mapper_init(void);
+void mapper_init(const char *_music_dir, const char *_playlist_dir);
void mapper_finish(void);
@@ -42,6 +42,14 @@ bool
mapper_has_music_directory(void);
/**
+ * 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.
+ */
+const char *
+map_to_relative_path(const char *path_utf8);
+
+/**
* Determines the absolute file system path of a relative URI. This
* is basically done by converting the URI to the file system charset
* and prepending the music directory.
diff --git a/src/mixer/alsa_mixer.c b/src/mixer/alsa_mixer_plugin.c
index 52e553cc5..38f36cb8f 100644
--- a/src/mixer/alsa_mixer.c
+++ b/src/mixer/alsa_mixer_plugin.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -17,8 +17,9 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
-#include "../output_api.h"
-#include "../mixer_api.h"
+#include "config.h"
+#include "mixer_api.h"
+#include "output_api.h"
#include <glib.h>
#include <alsa/asoundlib.h>
@@ -42,12 +43,22 @@ struct alsa_mixer {
int volume_set;
};
+/**
+ * The quark used for GError.domain.
+ */
+static inline GQuark
+alsa_mixer_quark(void)
+{
+ return g_quark_from_static_string("alsa_mixer");
+}
+
static struct mixer *
-alsa_mixer_init(const struct config_param *param)
+alsa_mixer_init(G_GNUC_UNUSED void *ao, const struct config_param *param,
+ G_GNUC_UNUSED GError **error_r)
{
struct alsa_mixer *am = g_new(struct alsa_mixer, 1);
- mixer_init(&am->base, &alsa_mixer);
+ mixer_init(&am->base, &alsa_mixer_plugin);
am->device = config_get_block_string(param, "mixer_device",
VOLUME_MIXER_ALSA_DEFAULT);
@@ -81,7 +92,7 @@ alsa_mixer_close(struct mixer *data)
}
static bool
-alsa_mixer_open(struct mixer *data)
+alsa_mixer_open(struct mixer *data, GError **error_r)
{
struct alsa_mixer *am = (struct alsa_mixer *)data;
int err;
@@ -91,29 +102,33 @@ alsa_mixer_open(struct mixer *data)
err = snd_mixer_open(&am->handle, 0);
if (err < 0) {
- g_warning("problems opening alsa mixer: %s\n", snd_strerror(err));
+ 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) {
- g_warning("problems attaching alsa mixer: %s\n",
- snd_strerror(err));
alsa_mixer_close(data);
+ g_set_error(error_r, alsa_mixer_quark(), err,
+ "failed to attach to %s: %s",
+ am->device, snd_strerror(err));
return false;
}
if ((err = snd_mixer_selem_register(am->handle, NULL,
NULL)) < 0) {
- g_warning("problems snd_mixer_selem_register'ing: %s\n",
- snd_strerror(err));
alsa_mixer_close(data);
+ g_set_error(error_r, alsa_mixer_quark(), err,
+ "snd_mixer_selem_register() failed: %s",
+ snd_strerror(err));
return false;
}
if ((err = snd_mixer_load(am->handle)) < 0) {
- g_warning("problems snd_mixer_selem_register'ing: %s\n",
- snd_strerror(err));
alsa_mixer_close(data);
+ g_set_error(error_r, alsa_mixer_quark(), err,
+ "snd_mixer_load() failed: %s\n",
+ snd_strerror(err));
return false;
}
@@ -138,14 +153,14 @@ alsa_mixer_open(struct mixer *data)
return true;
}
- g_warning("can't find alsa mixer control \"%s\"\n", am->control);
-
alsa_mixer_close(data);
+ g_set_error(error_r, alsa_mixer_quark(), 0,
+ "no such mixer control: %s", am->control);
return false;
}
static int
-alsa_mixer_get_volume(struct mixer *mixer)
+alsa_mixer_get_volume(struct mixer *mixer, GError **error_r)
{
struct alsa_mixer *am = (struct alsa_mixer *)mixer;
int err;
@@ -156,8 +171,9 @@ alsa_mixer_get_volume(struct mixer *mixer)
err = snd_mixer_handle_events(am->handle);
if (err < 0) {
- g_warning("problems getting alsa volume: %s (snd_mixer_%s)\n",
- snd_strerror(err), "handle_events");
+ g_set_error(error_r, alsa_mixer_quark(), err,
+ "snd_mixer_handle_events() failed: %s",
+ snd_strerror(err));
return false;
}
@@ -165,8 +181,9 @@ alsa_mixer_get_volume(struct mixer *mixer)
SND_MIXER_SCHN_FRONT_LEFT,
&level);
if (err < 0) {
- g_warning("problems getting alsa volume: %s (snd_mixer_%s)\n",
- snd_strerror(err), "selem_get_playback_volume");
+ g_set_error(error_r, alsa_mixer_quark(), err,
+ "failed to read ALSA volume: %s",
+ snd_strerror(err));
return false;
}
@@ -183,7 +200,7 @@ alsa_mixer_get_volume(struct mixer *mixer)
}
static bool
-alsa_mixer_set_volume(struct mixer *mixer, unsigned volume)
+alsa_mixer_set_volume(struct mixer *mixer, unsigned volume, GError **error_r)
{
struct alsa_mixer *am = (struct alsa_mixer *)mixer;
float vol;
@@ -203,15 +220,16 @@ alsa_mixer_set_volume(struct mixer *mixer, unsigned volume)
err = snd_mixer_selem_set_playback_volume_all(am->elem, level);
if (err < 0) {
- g_warning("problems setting alsa volume: %s\n",
- snd_strerror(err));
+ g_set_error(error_r, alsa_mixer_quark(), err,
+ "failed to set ALSA volume: %s",
+ snd_strerror(err));
return false;
}
return true;
}
-const struct mixer_plugin alsa_mixer = {
+const struct mixer_plugin alsa_mixer_plugin = {
.init = alsa_mixer_init,
.finish = alsa_mixer_finish,
.open = alsa_mixer_open,
diff --git a/src/mixer/oss_mixer.c b/src/mixer/oss_mixer_plugin.c
index f2db01ff4..418068ac2 100644
--- a/src/mixer/oss_mixer.c
+++ b/src/mixer/oss_mixer_plugin.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -17,8 +17,10 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
-#include "../output_api.h"
-#include "../mixer_api.h"
+#include "config.h"
+#include "mixer_api.h"
+#include "output_api.h"
+#include "fd_util.h"
#include <glib.h>
@@ -49,6 +51,15 @@ struct oss_mixer {
int volume_control;
};
+/**
+ * The quark used for GError.domain.
+ */
+static inline GQuark
+oss_mixer_quark(void)
+{
+ return g_quark_from_static_string("oss_mixer");
+}
+
static int
oss_find_mixer(const char *name)
{
@@ -65,11 +76,12 @@ oss_find_mixer(const char *name)
}
static struct mixer *
-oss_mixer_init(const struct config_param *param)
+oss_mixer_init(G_GNUC_UNUSED void *ao, const struct config_param *param,
+ GError **error_r)
{
struct oss_mixer *om = g_new(struct oss_mixer, 1);
- mixer_init(&om->base, &oss_mixer);
+ mixer_init(&om->base, &oss_mixer_plugin);
om->device = config_get_block_string(param, "mixer_device",
VOLUME_MIXER_OSS_DEFAULT);
@@ -78,9 +90,9 @@ oss_mixer_init(const struct config_param *param)
if (om->control != NULL) {
om->volume_control = oss_find_mixer(om->control);
if (om->volume_control < 0) {
- g_warning("mixer control \"%s\" not found",
- om->control);
g_free(om);
+ g_set_error(error_r, oss_mixer_quark(), 0,
+ "no such mixer control: %s", om->control);
return NULL;
}
} else
@@ -108,13 +120,15 @@ oss_mixer_close(struct mixer *data)
}
static bool
-oss_mixer_open(struct mixer *data)
+oss_mixer_open(struct mixer *data, GError **error_r)
{
struct oss_mixer *om = (struct oss_mixer *) data;
- om->device_fd = open(om->device, O_RDONLY);
+ om->device_fd = open_cloexec(om->device, O_RDONLY, 0);
if (om->device_fd < 0) {
- g_warning("Unable to open oss mixer \"%s\"\n", om->device);
+ g_set_error(error_r, oss_mixer_quark(), errno,
+ "failed to open %s: %s",
+ om->device, g_strerror(errno));
return false;
}
@@ -122,14 +136,17 @@ oss_mixer_open(struct mixer *data)
int devmask = 0;
if (ioctl(om->device_fd, SOUND_MIXER_READ_DEVMASK, &devmask) < 0) {
- g_warning("errors getting read_devmask for oss mixer\n");
+ g_set_error(error_r, oss_mixer_quark(), errno,
+ "READ_DEVMASK failed: %s",
+ g_strerror(errno));
oss_mixer_close(data);
return false;
}
if (((1 << om->volume_control) & devmask) == 0) {
- g_warning("mixer control \"%s\" not usable\n",
- om->control);
+ g_set_error(error_r, oss_mixer_quark(), 0,
+ "mixer control \"%s\" not usable",
+ om->control);
oss_mixer_close(data);
return false;
}
@@ -138,7 +155,7 @@ oss_mixer_open(struct mixer *data)
}
static int
-oss_mixer_get_volume(struct mixer *mixer)
+oss_mixer_get_volume(struct mixer *mixer, GError **error_r)
{
struct oss_mixer *om = (struct oss_mixer *)mixer;
int left, right, level;
@@ -148,7 +165,9 @@ oss_mixer_get_volume(struct mixer *mixer)
ret = ioctl(om->device_fd, MIXER_READ(om->volume_control), &level);
if (ret < 0) {
- g_warning("unable to read oss volume\n");
+ g_set_error(error_r, oss_mixer_quark(), errno,
+ "failed to read OSS volume: %s",
+ g_strerror(errno));
return false;
}
@@ -164,7 +183,7 @@ oss_mixer_get_volume(struct mixer *mixer)
}
static bool
-oss_mixer_set_volume(struct mixer *mixer, unsigned volume)
+oss_mixer_set_volume(struct mixer *mixer, unsigned volume, GError **error_r)
{
struct oss_mixer *om = (struct oss_mixer *)mixer;
int level;
@@ -177,14 +196,16 @@ oss_mixer_set_volume(struct mixer *mixer, unsigned volume)
ret = ioctl(om->device_fd, MIXER_WRITE(om->volume_control), &level);
if (ret < 0) {
- g_warning("unable to set oss volume\n");
+ g_set_error(error_r, oss_mixer_quark(), errno,
+ "failed to set OSS volume: %s",
+ g_strerror(errno));
return false;
}
return true;
}
-const struct mixer_plugin oss_mixer = {
+const struct mixer_plugin oss_mixer_plugin = {
.init = oss_mixer_init,
.finish = oss_mixer_finish,
.open = oss_mixer_open,
diff --git a/src/mixer/pulse_mixer.c b/src/mixer/pulse_mixer.c
deleted file mode 100644
index 5d9ce0475..000000000
--- a/src/mixer/pulse_mixer.c
+++ /dev/null
@@ -1,382 +0,0 @@
-/*
- * 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 "mixer_api.h"
-#include "conf.h"
-
-#include <glib.h>
-#include <pulse/volume.h>
-#include <pulse/pulseaudio.h>
-
-#include <string.h>
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "pulse_mixer"
-
-struct pulse_mixer {
- struct mixer base;
-
- const char *server;
- const char *sink;
- const char *output_name;
-
- uint32_t index;
- bool online;
-
- struct pa_context *context;
- struct pa_threaded_mainloop *mainloop;
- struct pa_cvolume volume;
-
-};
-
-/**
- * \brief waits for a pulseaudio operation to finish, frees it and
- * unlocks the mainloop
- * \param operation the operation to wait for
- * \return true if operation has finished normally (DONE state),
- * false otherwise
- */
-static bool
-pulse_wait_for_operation(struct pa_threaded_mainloop *mainloop,
- struct pa_operation *operation)
-{
- pa_operation_state_t state;
-
- assert(mainloop != NULL);
- assert(operation != NULL);
-
- state = pa_operation_get_state(operation);
- while (state == PA_OPERATION_RUNNING) {
- pa_threaded_mainloop_wait(mainloop);
- state = pa_operation_get_state(operation);
- }
-
- pa_operation_unref(operation);
-
- return state == PA_OPERATION_DONE;
-}
-
-static void
-sink_input_cb(G_GNUC_UNUSED pa_context *context, const pa_sink_input_info *i,
- int eol, void *userdata)
-{
-
- struct pulse_mixer *pm = userdata;
-
- if (eol) {
- g_debug("eol error sink_input_cb");
- return;
- }
-
- if (i == NULL) {
- g_debug("Sink input callback failure");
- return;
- }
-
- g_debug("sink input cb %s, index %d ",i->name,i->index);
-
- if (strcmp(i->name,pm->output_name) == 0) {
- pm->index = i->index;
- pm->online = true;
- pm->volume = i->volume;
- } else
- g_debug("bad name");
-}
-
-static void
-sink_input_vol(G_GNUC_UNUSED pa_context *context, const pa_sink_input_info *i,
- int eol, void *userdata)
-{
-
- struct pulse_mixer *pm = userdata;
-
- if (eol) {
- g_debug("eol error sink_input_vol");
- return;
- }
-
- if (i == NULL) {
- g_debug("Sink input callback failure");
- return;
- }
-
- g_debug("sink input vol %s, index %d ", i->name, i->index);
-
- pm->volume = i->volume;
-
- pa_threaded_mainloop_signal(pm->mainloop, 0);
-}
-
-static void
-subscribe_cb(pa_context *c, pa_subscription_event_type_t t,
- uint32_t idx, void *userdata)
-{
-
- struct pulse_mixer *pm = userdata;
-
- g_debug("subscribe call back");
-
- switch (t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) {
- case PA_SUBSCRIPTION_EVENT_SINK_INPUT:
- if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) ==
- PA_SUBSCRIPTION_EVENT_REMOVE &&
- pm->index == idx)
- pm->online = false;
- else {
- pa_operation *o;
-
- o = pa_context_get_sink_input_info(c, idx,
- sink_input_cb, pm);
- if (o == NULL) {
- g_debug("pa_context_get_sink_input_info() failed");
- return;
- }
-
- pa_operation_unref(o);
- }
-
- break;
- }
-}
-
-static void
-context_state_cb(pa_context *context, void *userdata)
-{
- struct pulse_mixer *pm = userdata;
-
- switch (pa_context_get_state(context)) {
- case PA_CONTEXT_READY: {
- pa_operation *o;
-
- pa_context_set_subscribe_callback(context, subscribe_cb, pm);
-
- o = pa_context_subscribe(context,
- (pa_subscription_mask_t)PA_SUBSCRIPTION_MASK_SINK_INPUT,
- NULL, NULL);
- if (o == NULL) {
- g_debug("pa_context_subscribe() failed");
- return;
- }
-
- pa_operation_unref(o);
-
- o = pa_context_get_sink_input_info_list(context,
- sink_input_cb, pm);
- if (o == NULL) {
- g_debug("pa_context_get_sink_input_info_list() failed");
- return;
- }
-
- pa_operation_unref(o);
-
- pa_threaded_mainloop_signal(pm->mainloop, 0);
- break;
- }
-
- case PA_CONTEXT_UNCONNECTED:
- case PA_CONTEXT_CONNECTING:
- case PA_CONTEXT_AUTHORIZING:
- case PA_CONTEXT_SETTING_NAME:
- break;
- case PA_CONTEXT_TERMINATED:
- case PA_CONTEXT_FAILED:
- pa_threaded_mainloop_signal(pm->mainloop, 0);
- break;
- }
-}
-
-
-static struct mixer *
-pulse_mixer_init(const struct config_param *param)
-{
- struct pulse_mixer *pm = g_new(struct pulse_mixer,1);
- mixer_init(&pm->base, &pulse_mixer);
-
- pm->online = false;
-
- pm->server = config_get_block_string(param, "server", NULL);
- pm->sink = config_get_block_string(param, "sink", NULL);
- pm->output_name = config_get_block_string(param, "name", NULL);
-
- return &pm->base;
-}
-
-static void
-pulse_mixer_finish(struct mixer *data)
-{
- struct pulse_mixer *pm = (struct pulse_mixer *) data;
-
- g_free(pm);
-}
-
-static bool
-pulse_mixer_setup(struct pulse_mixer *pm)
-{
- pa_context_set_state_callback(pm->context, context_state_cb, pm);
-
- if (pa_context_connect(pm->context, pm->server,
- (pa_context_flags_t)0, NULL) < 0) {
- g_debug("context server fail");
- return false;
- }
-
- pa_threaded_mainloop_lock(pm->mainloop);
-
- if (pa_threaded_mainloop_start(pm->mainloop) < 0) {
- pa_threaded_mainloop_unlock(pm->mainloop);
- g_debug("error start mainloop");
- return false;
- }
-
- pa_threaded_mainloop_wait(pm->mainloop);
-
- if (pa_context_get_state(pm->context) != PA_CONTEXT_READY) {
- pa_threaded_mainloop_unlock(pm->mainloop);
- g_debug("error context not ready");
- return false;
- }
-
- pa_threaded_mainloop_unlock(pm->mainloop);
-
- return true;
-}
-
-static bool
-pulse_mixer_open(struct mixer *data)
-{
- struct pulse_mixer *pm = (struct pulse_mixer *) data;
-
- g_debug("pulse mixer open");
-
- pm->index = 0;
- pm->online = false;
-
- pm->mainloop = pa_threaded_mainloop_new();
- if (pm->mainloop == NULL) {
- g_debug("failed mainloop");
- return false;
- }
-
- pm->context = pa_context_new(pa_threaded_mainloop_get_api(pm->mainloop),
- "Mixer mpd");
- if (pm->context == NULL) {
- pa_threaded_mainloop_stop(pm->mainloop);
- pa_threaded_mainloop_free(pm->mainloop);
- g_debug("failed context");
- return false;
- }
-
- if (!pulse_mixer_setup(pm)) {
- pa_threaded_mainloop_stop(pm->mainloop);
- pa_context_disconnect(pm->context);
- pa_context_unref(pm->context);
- pa_threaded_mainloop_free(pm->mainloop);
- return false;
- }
-
- return true;
-}
-
-static void
-pulse_mixer_close(struct mixer *data)
-{
- struct pulse_mixer *pm = (struct pulse_mixer *) data;
-
- pa_threaded_mainloop_stop(pm->mainloop);
- pa_context_disconnect(pm->context);
- pa_context_unref(pm->context);
- pa_threaded_mainloop_free(pm->mainloop);
-
- pm->online = false;
-}
-
-static int
-pulse_mixer_get_volume(struct mixer *mixer)
-{
- struct pulse_mixer *pm = (struct pulse_mixer *) mixer;
- int ret;
- pa_operation *o;
-
- pa_threaded_mainloop_lock(pm->mainloop);
-
- if (!pm->online) {
- pa_threaded_mainloop_unlock(pm->mainloop);
- return false;
- }
-
- o = pa_context_get_sink_input_info(pm->context, pm->index,
- sink_input_vol, pm);
- if (o == NULL) {
- pa_threaded_mainloop_unlock(pm->mainloop);
- g_debug("pa_context_get_sink_input_info() failed");
- return false;
- }
-
- if (!pulse_wait_for_operation(pm->mainloop, o)) {
- pa_threaded_mainloop_unlock(pm->mainloop);
- return false;
- }
-
- ret = pm->online
- ? (int)((100*(pa_cvolume_avg(&pm->volume)+1))/PA_VOLUME_NORM)
- : -1;
-
- pa_threaded_mainloop_unlock(pm->mainloop);
-
- return ret;
-}
-
-static bool
-pulse_mixer_set_volume(struct mixer *mixer, unsigned volume)
-{
- struct pulse_mixer *pm = (struct pulse_mixer *) mixer;
- struct pa_cvolume cvolume;
- pa_operation *o;
-
- pa_threaded_mainloop_lock(pm->mainloop);
-
- if (!pm->online) {
- pa_threaded_mainloop_unlock(pm->mainloop);
- return false;
- }
-
- pa_cvolume_set(&cvolume, pm->volume.channels,
- (pa_volume_t)volume * PA_VOLUME_NORM / 100 + 0.5);
-
- o = pa_context_set_sink_input_volume(pm->context, pm->index,
- &cvolume, NULL, NULL);
- pa_threaded_mainloop_unlock(pm->mainloop);
- if (o == NULL) {
- g_debug("pa_context_set_sink_input_volume() failed");
- return false;
- }
-
- pa_operation_unref(o);
-
- return true;
-}
-
-const struct mixer_plugin pulse_mixer = {
- .init = pulse_mixer_init,
- .finish = pulse_mixer_finish,
- .open = pulse_mixer_open,
- .close = pulse_mixer_close,
- .get_volume = pulse_mixer_get_volume,
- .set_volume = pulse_mixer_set_volume,
-};
diff --git a/src/mixer/pulse_mixer_plugin.c b/src/mixer/pulse_mixer_plugin.c
new file mode 100644
index 000000000..2be0b8266
--- /dev/null
+++ b/src/mixer/pulse_mixer_plugin.c
@@ -0,0 +1,234 @@
+/*
+ * 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 "pulse_mixer_plugin.h"
+#include "mixer_api.h"
+#include "output/pulse_output_plugin.h"
+#include "conf.h"
+#include "event_pipe.h"
+
+#include <glib.h>
+
+#include <pulse/thread-mainloop.h>
+#include <pulse/context.h>
+#include <pulse/introspect.h>
+#include <pulse/stream.h>
+#include <pulse/subscribe.h>
+#include <pulse/error.h>
+
+#include <assert.h>
+#include <string.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "pulse_mixer"
+
+struct pulse_mixer {
+ struct mixer base;
+
+ struct pulse_output *output;
+
+ bool online;
+ struct pa_cvolume volume;
+
+};
+
+/**
+ * The quark used for GError.domain.
+ */
+static inline GQuark
+pulse_mixer_quark(void)
+{
+ return g_quark_from_static_string("pulse_mixer");
+}
+
+static void
+pulse_mixer_offline(struct pulse_mixer *pm)
+{
+ if (!pm->online)
+ return;
+
+ pm->online = false;
+
+ event_pipe_emit(PIPE_EVENT_MIXER);
+}
+
+/**
+ * Callback invoked by pulse_mixer_update(). Receives the new mixer
+ * value.
+ */
+static void
+pulse_mixer_volume_cb(G_GNUC_UNUSED pa_context *context, const pa_sink_input_info *i,
+ int eol, void *userdata)
+{
+ struct pulse_mixer *pm = userdata;
+
+ if (eol)
+ return;
+
+ if (i == NULL) {
+ pulse_mixer_offline(pm);
+ return;
+ }
+
+ pm->online = true;
+ pm->volume = i->volume;
+
+ event_pipe_emit(PIPE_EVENT_MIXER);
+}
+
+static void
+pulse_mixer_update(struct pulse_mixer *pm,
+ struct pa_context *context, struct pa_stream *stream)
+{
+ pa_operation *o;
+
+ assert(context != NULL);
+ assert(stream != NULL);
+ assert(pa_stream_get_state(stream) == PA_STREAM_READY);
+
+ o = pa_context_get_sink_input_info(context,
+ pa_stream_get_index(stream),
+ pulse_mixer_volume_cb, pm);
+ if (o == NULL) {
+ g_warning("pa_context_get_sink_input_info() failed: %s",
+ pa_strerror(pa_context_errno(context)));
+ pulse_mixer_offline(pm);
+ return;
+ }
+
+ pa_operation_unref(o);
+}
+
+void
+pulse_mixer_on_connect(G_GNUC_UNUSED struct pulse_mixer *pm,
+ struct pa_context *context)
+{
+ pa_operation *o;
+
+ assert(context != NULL);
+
+ o = pa_context_subscribe(context,
+ (pa_subscription_mask_t)PA_SUBSCRIPTION_MASK_SINK_INPUT,
+ NULL, NULL);
+ if (o == NULL) {
+ g_warning("pa_context_subscribe() failed: %s",
+ pa_strerror(pa_context_errno(context)));
+ return;
+ }
+
+ pa_operation_unref(o);
+}
+
+void
+pulse_mixer_on_disconnect(struct pulse_mixer *pm)
+{
+ pulse_mixer_offline(pm);
+}
+
+void
+pulse_mixer_on_change(struct pulse_mixer *pm,
+ struct pa_context *context, struct pa_stream *stream)
+{
+ pulse_mixer_update(pm, context, stream);
+}
+
+static struct mixer *
+pulse_mixer_init(void *ao, G_GNUC_UNUSED const struct config_param *param,
+ GError **error_r)
+{
+ struct pulse_mixer *pm;
+ struct pulse_output *po = ao;
+
+ if (ao == NULL) {
+ g_set_error(error_r, pulse_mixer_quark(), 0,
+ "The pulse mixer cannot work without the audio output");
+ return false;
+ }
+
+ pm = g_new(struct pulse_mixer,1);
+ mixer_init(&pm->base, &pulse_mixer_plugin);
+
+ pm->online = false;
+ pm->output = po;
+
+ pulse_output_set_mixer(po, pm);
+
+ return &pm->base;
+}
+
+static void
+pulse_mixer_finish(struct mixer *data)
+{
+ struct pulse_mixer *pm = (struct pulse_mixer *) data;
+
+ pulse_output_clear_mixer(pm->output, pm);
+
+ /* free resources */
+
+ g_free(pm);
+}
+
+static int
+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);
+
+ ret = pm->online
+ ? (int)((100*(pa_cvolume_avg(&pm->volume)+1))/PA_VOLUME_NORM)
+ : -1;
+
+ pa_threaded_mainloop_unlock(pm->output->mainloop);
+
+ return ret;
+}
+
+static bool
+pulse_mixer_set_volume(struct mixer *mixer, unsigned volume, GError **error_r)
+{
+ struct pulse_mixer *pm = (struct pulse_mixer *) mixer;
+ struct pa_cvolume cvolume;
+ bool success;
+
+ pa_threaded_mainloop_lock(pm->output->mainloop);
+ if (!pm->online) {
+ pa_threaded_mainloop_unlock(pm->output->mainloop);
+ g_set_error(error_r, pulse_mixer_quark(), 0, "disconnected");
+ return false;
+ }
+
+ pa_cvolume_set(&cvolume, pm->volume.channels,
+ (pa_volume_t)volume * PA_VOLUME_NORM / 100 + 0.5);
+ success = pulse_output_set_volume(pm->output, &cvolume, error_r);
+ if (success)
+ pm->volume = cvolume;
+ pa_threaded_mainloop_unlock(pm->output->mainloop);
+
+ return success;
+}
+
+const struct mixer_plugin pulse_mixer_plugin = {
+ .init = pulse_mixer_init,
+ .finish = pulse_mixer_finish,
+ .get_volume = pulse_mixer_get_volume,
+ .set_volume = pulse_mixer_set_volume,
+};
diff --git a/src/mixer/pulse_mixer_plugin.h b/src/mixer/pulse_mixer_plugin.h
new file mode 100644
index 000000000..be199f688
--- /dev/null
+++ b/src/mixer/pulse_mixer_plugin.h
@@ -0,0 +1,39 @@
+/*
+ * 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_PULSE_MIXER_PLUGIN_H
+#define MPD_PULSE_MIXER_PLUGIN_H
+
+#include <pulse/def.h>
+
+struct pulse_mixer;
+struct pa_context;
+struct pa_stream;
+
+void
+pulse_mixer_on_connect(struct pulse_mixer *pm, struct pa_context *context);
+
+void
+pulse_mixer_on_disconnect(struct pulse_mixer *pm);
+
+void
+pulse_mixer_on_change(struct pulse_mixer *pm,
+ struct pa_context *context, struct pa_stream *stream);
+
+#endif
diff --git a/src/mixer/software_mixer_plugin.c b/src/mixer/software_mixer_plugin.c
new file mode 100644
index 000000000..93802e977
--- /dev/null
+++ b/src/mixer/software_mixer_plugin.c
@@ -0,0 +1,109 @@
+/*
+ * 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 "software_mixer_plugin.h"
+#include "mixer_api.h"
+#include "filter_plugin.h"
+#include "filter_registry.h"
+#include "filter/volume_filter_plugin.h"
+#include "pcm_volume.h"
+
+#include <assert.h>
+#include <math.h>
+
+struct software_mixer {
+ /** the base mixer class */
+ struct mixer base;
+
+ struct filter *filter;
+
+ unsigned volume;
+};
+
+static struct mixer *
+software_mixer_init(G_GNUC_UNUSED void *ao,
+ G_GNUC_UNUSED const struct config_param *param,
+ G_GNUC_UNUSED GError **error_r)
+{
+ struct software_mixer *sm = g_new(struct software_mixer, 1);
+
+ mixer_init(&sm->base, &software_mixer_plugin);
+
+ sm->filter = filter_new(&volume_filter_plugin, NULL, NULL);
+ assert(sm->filter != NULL);
+
+ sm->volume = 100;
+
+ return &sm->base;
+}
+
+static void
+software_mixer_finish(struct mixer *data)
+{
+ struct software_mixer *sm = (struct software_mixer *)data;
+
+ g_free(sm);
+}
+
+static int
+software_mixer_get_volume(struct mixer *mixer, G_GNUC_UNUSED GError **error_r)
+{
+ struct software_mixer *sm = (struct software_mixer *)mixer;
+
+ return sm->volume;
+}
+
+static bool
+software_mixer_set_volume(struct mixer *mixer, unsigned volume,
+ G_GNUC_UNUSED GError **error_r)
+{
+ struct software_mixer *sm = (struct software_mixer *)mixer;
+
+ assert(volume <= 100);
+
+ sm->volume = volume;
+
+ if (volume >= 100)
+ volume = PCM_VOLUME_1;
+ else if (volume > 0)
+ volume = pcm_float_to_volume((exp(volume / 25.0) - 1) /
+ (54.5981500331F - 1));
+
+ volume_filter_set(sm->filter, volume);
+ return true;
+}
+
+const struct mixer_plugin software_mixer_plugin = {
+ .init = software_mixer_init,
+ .finish = software_mixer_finish,
+ .get_volume = software_mixer_get_volume,
+ .set_volume = software_mixer_set_volume,
+ .global = true,
+};
+
+struct filter *
+software_mixer_get_filter(struct mixer *mixer)
+{
+ struct software_mixer *sm = (struct software_mixer *)mixer;
+
+ assert(sm->base.plugin == &software_mixer_plugin);
+
+ return sm->filter;
+}
diff --git a/src/mixer/software_mixer_plugin.h b/src/mixer/software_mixer_plugin.h
new file mode 100644
index 000000000..3bd07ac62
--- /dev/null
+++ b/src/mixer/software_mixer_plugin.h
@@ -0,0 +1,33 @@
+/*
+ * 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 SOFTWARE_MIXER_PLUGIN_H
+#define SOFTWARE_MIXER_PLUGIN_H
+
+struct mixer;
+struct filter;
+
+/**
+ * Returns the (volume) filter associated with this mixer. All users
+ * of this mixer plugin should install this filter.
+ */
+struct filter *
+software_mixer_get_filter(struct mixer *mixer);
+
+#endif
diff --git a/src/mixer/winmm_mixer_plugin.c b/src/mixer/winmm_mixer_plugin.c
new file mode 100644
index 000000000..5ab3e7525
--- /dev/null
+++ b/src/mixer/winmm_mixer_plugin.c
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "mixer_api.h"
+#include "output_api.h"
+#include "output/winmm_output_plugin.h"
+
+#include <assert.h>
+#include <math.h>
+#include <windows.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "winmm_mixer"
+
+struct winmm_mixer {
+ struct mixer base;
+ struct winmm_output *output;
+};
+
+static inline GQuark
+winmm_mixer_quark(void)
+{
+ return g_quark_from_static_string("winmm_mixer");
+}
+
+static inline int
+winmm_volume_decode(DWORD volume)
+{
+ return lround((volume & 0xFFFF) / 655.35);
+}
+
+static inline DWORD
+winmm_volume_encode(int volume)
+{
+ int value = lround(volume * 655.35);
+ return MAKELONG(value, value);
+}
+
+static struct mixer *
+winmm_mixer_init(void *ao, G_GNUC_UNUSED const struct config_param *param,
+ G_GNUC_UNUSED GError **error_r)
+{
+ assert(ao != NULL);
+
+ struct winmm_mixer *wm = g_new(struct winmm_mixer, 1);
+ mixer_init(&wm->base, &winmm_mixer_plugin);
+ wm->output = (struct winmm_output *) ao;
+
+ return &wm->base;
+}
+
+static void
+winmm_mixer_finish(struct mixer *data)
+{
+ g_free(data);
+}
+
+static int
+winmm_mixer_get_volume(struct mixer *mixer, GError **error_r)
+{
+ struct winmm_mixer *wm = (struct winmm_mixer *) mixer;
+ DWORD volume;
+ HWAVEOUT handle = winmm_output_get_handle(wm->output);
+ MMRESULT result = waveOutGetVolume(handle, &volume);
+
+ if (result != MMSYSERR_NOERROR) {
+ g_set_error(error_r, 0, winmm_mixer_quark(),
+ "Failed to get winmm volume");
+ return -1;
+ }
+
+ return winmm_volume_decode(volume);
+}
+
+static bool
+winmm_mixer_set_volume(struct mixer *mixer, unsigned volume, GError **error_r)
+{
+ struct winmm_mixer *wm = (struct winmm_mixer *) mixer;
+ DWORD value = winmm_volume_encode(volume);
+ HWAVEOUT handle = winmm_output_get_handle(wm->output);
+ MMRESULT result = waveOutSetVolume(handle, value);
+
+ if (result != MMSYSERR_NOERROR) {
+ g_set_error(error_r, 0, winmm_mixer_quark(),
+ "Failed to set winmm volume");
+ return false;
+ }
+
+ return true;
+}
+
+const struct mixer_plugin winmm_mixer_plugin = {
+ .init = winmm_mixer_init,
+ .finish = winmm_mixer_finish,
+ .get_volume = winmm_mixer_get_volume,
+ .set_volume = winmm_mixer_set_volume,
+};
diff --git a/src/mixer_all.c b/src/mixer_all.c
index 252cb61ab..ffe610b91 100644
--- a/src/mixer_all.c
+++ b/src/mixer_all.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -17,11 +17,15 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "mixer_all.h"
#include "mixer_control.h"
#include "output_all.h"
#include "output_plugin.h"
#include "output_internal.h"
+#include "pcm_volume.h"
+#include "mixer_api.h"
+#include "mixer_list.h"
#include <glib.h>
@@ -35,6 +39,8 @@ output_mixer_get_volume(unsigned i)
{
struct audio_output *output;
struct mixer *mixer;
+ int volume;
+ GError *error = NULL;
assert(i < audio_output_count());
@@ -46,7 +52,14 @@ output_mixer_get_volume(unsigned i)
if (mixer == NULL)
return -1;
- return mixer_get_volume(mixer);
+ volume = mixer_get_volume(mixer, &error);
+ if (volume < 0 && error != NULL) {
+ g_warning("Failed to read mixer for '%s': %s",
+ output->name, error->message);
+ g_error_free(error);
+ }
+
+ return volume;
}
int
@@ -70,12 +83,15 @@ mixer_all_get_volume(void)
}
static bool
-output_mixer_set_volume(unsigned i, int volume, bool relative)
+output_mixer_set_volume(unsigned i, unsigned volume)
{
struct audio_output *output;
struct mixer *mixer;
+ bool success;
+ GError *error = NULL;
assert(i < audio_output_count());
+ assert(volume <= 100);
output = audio_output_get(i);
if (!output->enabled)
@@ -85,31 +101,81 @@ output_mixer_set_volume(unsigned i, int volume, bool relative)
if (mixer == NULL)
return false;
- if (relative) {
- int prev = mixer_get_volume(mixer);
- if (prev < 0)
- return false;
-
- volume += prev;
+ success = mixer_set_volume(mixer, volume, &error);
+ if (!success && error != NULL) {
+ g_warning("Failed to set mixer for '%s': %s",
+ output->name, error->message);
+ g_error_free(error);
}
- if (volume > 100)
- volume = 100;
- else if (volume < 0)
- volume = 0;
-
- return mixer_set_volume(mixer, volume);
+ return success;
}
bool
-mixer_all_set_volume(int volume, bool relative)
+mixer_all_set_volume(unsigned volume)
{
bool success = false;
unsigned count = audio_output_count();
+ assert(volume <= 100);
+
for (unsigned i = 0; i < count; i++)
- success = output_mixer_set_volume(i, volume, relative)
+ success = output_mixer_set_volume(i, volume)
|| success;
return success;
}
+
+static int
+output_mixer_get_software_volume(unsigned i)
+{
+ struct audio_output *output;
+ struct mixer *mixer;
+
+ assert(i < audio_output_count());
+
+ output = audio_output_get(i);
+ if (!output->enabled)
+ return -1;
+
+ mixer = output->mixer;
+ if (mixer == NULL || mixer->plugin != &software_mixer_plugin)
+ return -1;
+
+ return mixer_get_volume(mixer, NULL);
+}
+
+int
+mixer_all_get_software_volume(void)
+{
+ unsigned count = audio_output_count(), ok = 0;
+ int volume, total = 0;
+
+ for (unsigned i = 0; i < count; i++) {
+ volume = output_mixer_get_software_volume(i);
+ if (volume >= 0) {
+ total += volume;
+ ++ok;
+ }
+ }
+
+ if (ok == 0)
+ return -1;
+
+ return total / ok;
+}
+
+void
+mixer_all_set_software_volume(unsigned volume)
+{
+ unsigned count = audio_output_count();
+
+ assert(volume <= PCM_VOLUME_1);
+
+ for (unsigned i = 0; i < count; i++) {
+ struct audio_output *output = audio_output_get(i);
+ if (output->mixer != NULL &&
+ output->mixer->plugin == &software_mixer_plugin)
+ mixer_set_volume(output->mixer, volume, NULL);
+ }
+}
diff --git a/src/mixer_all.h b/src/mixer_all.h
index 66c4988de..cece23292 100644
--- a/src/mixer_all.h
+++ b/src/mixer_all.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -37,11 +37,26 @@ mixer_all_get_volume(void);
/**
* Sets the volume on all available mixers.
*
- * @param volume the volume (range 0..100 or -100..100 if #relative)
- * @param relative if true, then the #volume is added to the current value
+ * @param volume the volume (range 0..100)
* @return true on success, false on failure
*/
bool
-mixer_all_set_volume(int volume, bool relative);
+mixer_all_set_volume(unsigned volume);
+
+/**
+ * Similar to mixer_all_get_volume(), but gets the volume only for
+ * software mixers. See #software_mixer_plugin. This function fails
+ * if no software mixer is configured.
+ */
+int
+mixer_all_get_software_volume(void);
+
+/**
+ * Similar to mixer_all_set_volume(), but sets the volume only for
+ * software mixers. See #software_mixer_plugin. This function cannot
+ * fail, because the underlying software mixers cannot fail either.
+ */
+void
+mixer_all_set_software_volume(unsigned volume);
#endif
diff --git a/src/mixer_api.c b/src/mixer_api.c
index cff23a397..4c8959fb8 100644
--- a/src/mixer_api.c
+++ b/src/mixer_api.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -17,6 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "mixer_api.h"
#undef G_LOG_DOMAIN
diff --git a/src/mixer_api.h b/src/mixer_api.h
index fe27f5119..26c001703 100644
--- a/src/mixer_api.h
+++ b/src/mixer_api.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
diff --git a/src/mixer_control.c b/src/mixer_control.c
index e19b82d65..458b3abc1 100644
--- a/src/mixer_control.c
+++ b/src/mixer_control.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -17,38 +17,26 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "mixer_control.h"
#include "mixer_api.h"
-#include <glib.h>
-
#include <assert.h>
#include <stddef.h>
#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "mixer"
-static bool mixers_enabled = true;
-
-void
-mixer_disable_all(void)
-{
- g_debug("mixer api is disabled");
- mixers_enabled = false;
-}
-
struct mixer *
-mixer_new(const struct mixer_plugin *plugin, const struct config_param *param)
+mixer_new(const struct mixer_plugin *plugin, void *ao,
+ const struct config_param *param,
+ GError **error_r)
{
struct mixer *mixer;
- //mixers are disabled (by using software volume)
- if (!mixers_enabled) {
- return NULL;
- }
assert(plugin != NULL);
- mixer = plugin->init(param);
+ mixer = plugin->init(ao, param, error_r);
assert(mixer == NULL || mixer->plugin == plugin);
@@ -72,7 +60,7 @@ mixer_free(struct mixer *mixer)
}
bool
-mixer_open(struct mixer *mixer)
+mixer_open(struct mixer *mixer, GError **error_r)
{
bool success;
@@ -83,8 +71,10 @@ mixer_open(struct mixer *mixer)
if (mixer->open)
success = true;
+ else if (mixer->plugin->open == NULL)
+ success = mixer->open = true;
else
- success = mixer->open = mixer->plugin->open(mixer);
+ success = mixer->open = mixer->plugin->open(mixer, error_r);
mixer->failed = !success;
@@ -100,7 +90,9 @@ mixer_close_internal(struct mixer *mixer)
assert(mixer->plugin != NULL);
assert(mixer->open);
- mixer->plugin->close(mixer);
+ if (mixer->plugin->close != NULL)
+ mixer->plugin->close(mixer);
+
mixer->open = false;
}
@@ -140,21 +132,26 @@ mixer_failed(struct mixer *mixer)
}
int
-mixer_get_volume(struct mixer *mixer)
+mixer_get_volume(struct mixer *mixer, GError **error_r)
{
int volume;
assert(mixer != NULL);
- if (mixer->plugin->global && !mixer->failed && !mixer_open(mixer))
+ if (mixer->plugin->global && !mixer->failed &&
+ !mixer_open(mixer, error_r))
return -1;
g_mutex_lock(mixer->mutex);
if (mixer->open) {
- volume = mixer->plugin->get_volume(mixer);
- if (volume < 0)
+ GError *error = NULL;
+
+ volume = mixer->plugin->get_volume(mixer, &error);
+ if (volume < 0 && error != NULL) {
+ g_propagate_error(error_r, error);
mixer_failed(mixer);
+ }
} else
volume = -1;
@@ -164,22 +161,21 @@ mixer_get_volume(struct mixer *mixer)
}
bool
-mixer_set_volume(struct mixer *mixer, unsigned volume)
+mixer_set_volume(struct mixer *mixer, unsigned volume, GError **error_r)
{
bool success;
assert(mixer != NULL);
assert(volume <= 100);
- if (mixer->plugin->global && !mixer->failed && !mixer_open(mixer))
+ if (mixer->plugin->global && !mixer->failed &&
+ !mixer_open(mixer, error_r))
return false;
g_mutex_lock(mixer->mutex);
if (mixer->open) {
- success = mixer->plugin->set_volume(mixer, volume);
- if (!success)
- mixer_failed(mixer);
+ success = mixer->plugin->set_volume(mixer, volume, error_r);
} else
success = false;
diff --git a/src/mixer_control.h b/src/mixer_control.h
index 0f73e8f75..1f48e8ca5 100644
--- a/src/mixer_control.h
+++ b/src/mixer_control.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -25,23 +25,24 @@
#ifndef MPD_MIXER_CONTROL_H
#define MPD_MIXER_CONTROL_H
+#include <glib.h>
+
#include <stdbool.h>
struct mixer;
struct mixer_plugin;
struct config_param;
-void
-mixer_disable_all(void);
-
struct mixer *
-mixer_new(const struct mixer_plugin *plugin, const struct config_param *param);
+mixer_new(const struct mixer_plugin *plugin, void *ao,
+ const struct config_param *param,
+ GError **error_r);
void
mixer_free(struct mixer *mixer);
bool
-mixer_open(struct mixer *mixer);
+mixer_open(struct mixer *mixer, GError **error_r);
void
mixer_close(struct mixer *mixer);
@@ -54,9 +55,9 @@ void
mixer_auto_close(struct mixer *mixer);
int
-mixer_get_volume(struct mixer *mixer);
+mixer_get_volume(struct mixer *mixer, GError **error_r);
bool
-mixer_set_volume(struct mixer *mixer, unsigned volume);
+mixer_set_volume(struct mixer *mixer, unsigned volume, GError **error_r);
#endif
diff --git a/src/mixer_list.h b/src/mixer_list.h
index 7db4a00d8..a472c8807 100644
--- a/src/mixer_list.h
+++ b/src/mixer_list.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -25,8 +25,10 @@
#ifndef MPD_MIXER_LIST_H
#define MPD_MIXER_LIST_H
-extern const struct mixer_plugin alsa_mixer;
-extern const struct mixer_plugin oss_mixer;
-extern const struct mixer_plugin pulse_mixer;
+extern const struct mixer_plugin software_mixer_plugin;
+extern const struct mixer_plugin alsa_mixer_plugin;
+extern const struct mixer_plugin oss_mixer_plugin;
+extern const struct mixer_plugin pulse_mixer_plugin;
+extern const struct mixer_plugin winmm_mixer_plugin;
#endif
diff --git a/src/mixer_plugin.h b/src/mixer_plugin.h
index 2b9b440e5..0915a03f3 100644
--- a/src/mixer_plugin.h
+++ b/src/mixer_plugin.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -27,6 +27,8 @@
#ifndef MPD_MIXER_PLUGIN_H
#define MPD_MIXER_PLUGIN_H
+#include <glib.h>
+
#include <stdbool.h>
struct config_param;
@@ -35,8 +37,16 @@ struct mixer;
struct mixer_plugin {
/**
* Alocates and configures a mixer device.
+ *
+ * @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
+ * NULL to ignore errors
+ * @return a mixer object, or NULL on error
*/
- struct mixer *(*init)(const struct config_param *param);
+ struct mixer *(*init)(void *ao, const struct config_param *param,
+ GError **error_r);
/**
* Finish and free mixer data
@@ -45,8 +55,12 @@ struct mixer_plugin {
/**
* Open mixer device
+ *
+ * @param error_r location to store the error occuring, or
+ * NULL to ignore errors
+ * @return true on success, false on error
*/
- bool (*open)(struct mixer *data);
+ bool (*open)(struct mixer *data, GError **error_r);
/**
* Close mixer device
@@ -56,18 +70,23 @@ struct mixer_plugin {
/**
* Reads the current volume.
*
- * @return the current volume (0..100 including) or -1 on
- * error
+ * @param error_r location to store the error occuring, 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)
*/
- int (*get_volume)(struct mixer *mixer);
+ int (*get_volume)(struct mixer *mixer, GError **error_r);
/**
* Sets the volume.
*
+ * @param error_r location to store the error occuring, or
+ * NULL to ignore errors
* @param volume the new volume (0..100 including)
- * @return true on success
+ * @return true on success, false on error
*/
- bool (*set_volume)(struct mixer *mixer, unsigned volume);
+ bool (*set_volume)(struct mixer *mixer, unsigned volume,
+ GError **error_r);
/**
* If true, then the mixer is automatically opened, even if
diff --git a/src/mixer_type.c b/src/mixer_type.c
new file mode 100644
index 000000000..4f347dd94
--- /dev/null
+++ b/src/mixer_type.c
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "mixer_type.h"
+
+#include <assert.h>
+#include <string.h>
+
+enum mixer_type
+mixer_type_parse(const char *input)
+{
+ assert(input != NULL);
+
+ if (strcmp(input, "none") == 0 || strcmp(input, "disabled") == 0)
+ return MIXER_TYPE_NONE;
+ else if (strcmp(input, "hardware") == 0)
+ return MIXER_TYPE_HARDWARE;
+ else if (strcmp(input, "software") == 0)
+ return MIXER_TYPE_SOFTWARE;
+ else
+ return MIXER_TYPE_UNKNOWN;
+}
diff --git a/src/mixer_type.h b/src/mixer_type.h
new file mode 100644
index 000000000..fd1c5576c
--- /dev/null
+++ b/src/mixer_type.h
@@ -0,0 +1,47 @@
+/*
+ * 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_MIXER_TYPE_H
+#define MPD_MIXER_TYPE_H
+
+enum mixer_type {
+ /** parser error */
+ MIXER_TYPE_UNKNOWN,
+
+ /** mixer disabled */
+ MIXER_TYPE_NONE,
+
+ /** software mixer with pcm_volume() */
+ MIXER_TYPE_SOFTWARE,
+
+ /** hardware mixer (output's plugin) */
+ MIXER_TYPE_HARDWARE,
+};
+
+/**
+ * Parses a "mixer_type" setting from the configuration file.
+ *
+ * @param input the configured string value; must not be NULL
+ * @return a #mixer_type value; MIXER_TYPE_UNKNOWN means #input could
+ * not be parsed
+ */
+enum mixer_type
+mixer_type_parse(const char *input);
+
+#endif
diff --git a/src/mpd_error.h b/src/mpd_error.h
new file mode 100644
index 000000000..47618d03c
--- /dev/null
+++ b/src/mpd_error.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_ERROR_H
+#define MPD_ERROR_H
+
+#include <stdlib.h>
+
+/* This macro is used as an intermediate step to a proper error handling
+ * using GError in mpd. It is used for unrecoverable error conditions
+ * and exits immediately. The long-term goal is to replace this macro by
+ * proper error handling. */
+
+#define MPD_ERROR(...) \
+ do { \
+ g_critical(__VA_ARGS__); \
+ exit(EXIT_FAILURE); \
+ } while(0)
+
+#endif
diff --git a/src/notify.c b/src/notify.c
index 3b45c22b4..d148a4bfc 100644
--- a/src/notify.c
+++ b/src/notify.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -17,6 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "notify.h"
void notify_init(struct notify *notify)
diff --git a/src/notify.h b/src/notify.h
index 0655bc6b7..0c657f2fb 100644
--- a/src/notify.h
+++ b/src/notify.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
diff --git a/src/open.h b/src/open.h
new file mode 100644
index 000000000..e39c64a97
--- /dev/null
+++ b/src/open.h
@@ -0,0 +1,41 @@
+/*
+ * 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.
+ */
+
+/** \file
+ *
+ * Portability macros for opening files.
+ */
+
+#ifndef MPD_OPEN_H
+#define MPD_OPEN_H
+
+#include <fcntl.h>
+
+/* On Windows, files are opened in "text" mode by default, and the C
+ library will mangle data being read/written; this must be switched
+ off by specifying the proprietary "O_BINARY" flag. That sucks! */
+#ifndef O_BINARY
+#ifdef _O_BINARY
+#define O_BINARY _O_BINARY
+#else
+#define O_BINARY 0
+#endif
+#endif
+
+#endif
diff --git a/src/output/alsa_plugin.c b/src/output/alsa_plugin.c
index 818c83ca2..9177fabe4 100644
--- a/src/output/alsa_plugin.c
+++ b/src/output/alsa_plugin.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -17,7 +17,8 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
-#include "../output_api.h"
+#include "config.h"
+#include "output_api.h"
#include "mixer_list.h"
#include <glib.h>
@@ -69,6 +70,16 @@ struct alsa_data {
/** the size of one audio frame */
size_t frame_size;
+
+ /**
+ * The size of one period, in number of frames.
+ */
+ snd_pcm_uframes_t period_frames;
+
+ /**
+ * The number of frames written in the current period.
+ */
+ snd_pcm_uframes_t period_position;
};
/**
@@ -172,15 +183,148 @@ alsa_test_default_device(void)
}
static snd_pcm_format_t
-get_bitformat(const struct audio_format *af)
+get_bitformat(enum sample_format sample_format)
+{
+ switch (sample_format) {
+ case SAMPLE_FORMAT_S8:
+ return SND_PCM_FORMAT_S8;
+
+ case SAMPLE_FORMAT_S16:
+ return SND_PCM_FORMAT_S16;
+
+ 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;
+
+ default:
+ return SND_PCM_FORMAT_UNKNOWN;
+ }
+}
+
+static snd_pcm_format_t
+byteswap_bitformat(snd_pcm_format_t fmt)
+{
+ switch(fmt) {
+ case SND_PCM_FORMAT_S16_LE: return SND_PCM_FORMAT_S16_BE;
+ case SND_PCM_FORMAT_S24_LE: return SND_PCM_FORMAT_S24_BE;
+ case SND_PCM_FORMAT_S32_LE: return SND_PCM_FORMAT_S32_BE;
+ case SND_PCM_FORMAT_S16_BE: return SND_PCM_FORMAT_S16_LE;
+ case SND_PCM_FORMAT_S24_BE: return SND_PCM_FORMAT_S24_LE;
+
+ case SND_PCM_FORMAT_S24_3BE:
+ return SND_PCM_FORMAT_S24_3LE;
+
+ case SND_PCM_FORMAT_S24_3LE:
+ return SND_PCM_FORMAT_S24_3BE;
+
+ case SND_PCM_FORMAT_S32_BE: return SND_PCM_FORMAT_S32_LE;
+ default: return SND_PCM_FORMAT_UNKNOWN;
+ }
+}
+
+/**
+ * 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)
+{
+ snd_pcm_format_t alsa_format = get_bitformat(sample_format);
+ if (alsa_format == 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;
+
+ return err;
+}
+
+/**
+ * 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)
+{
+ snd_pcm_format_t alsa_format =
+ byteswap_bitformat(get_bitformat(sample_format));
+ if (alsa_format == 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;
+ }
+
+ return err;
+}
+
+/**
+ * Attempts to configure the specified sample format, and tries the
+ * 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)
+{
+ 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);
+
+ return err;
+}
+
+/**
+ * Configure a sample format, and probe other formats if that fails.
+ */
+static int
+alsa_output_setup_format(snd_pcm_t *pcm, snd_pcm_hw_params_t *hwparams,
+ struct audio_format *audio_format)
{
- switch (af->bits) {
- case 8: return SND_PCM_FORMAT_S8;
- case 16: return SND_PCM_FORMAT_S16;
- case 24: return SND_PCM_FORMAT_S24;
- case 32: return SND_PCM_FORMAT_S32;
+ /* try the input format first */
+
+ int err = alsa_output_try_format_both(pcm, hwparams, audio_format,
+ audio_format->format);
+ if (err != -EINVAL)
+ return err;
+
+ /* 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)
+ continue;
+
+ err = alsa_output_try_format_both(pcm, hwparams, audio_format,
+ probe_formats[i]);
+ if (err != -EINVAL)
+ return err;
}
- return SND_PCM_FORMAT_UNKNOWN;
+
+ return -EINVAL;
}
/**
@@ -189,7 +333,6 @@ get_bitformat(const struct audio_format *af)
*/
static bool
alsa_setup(struct alsa_data *ad, struct audio_format *audio_format,
- snd_pcm_format_t bitformat,
GError **error)
{
snd_pcm_hw_params_t *hwparams;
@@ -208,7 +351,6 @@ alsa_setup(struct alsa_data *ad, struct audio_format *audio_format,
configure_hw:
/* configure HW params */
snd_pcm_hw_params_alloca(&hwparams);
-
cmd = "snd_pcm_hw_params_any";
err = snd_pcm_hw_params_any(ad->pcm, hwparams);
if (err < 0)
@@ -235,31 +377,12 @@ configure_hw:
ad->writei = snd_pcm_writei;
}
- err = snd_pcm_hw_params_set_format(ad->pcm, hwparams, bitformat);
- if (err == -EINVAL && (audio_format->bits == 24 ||
- audio_format->bits == 16)) {
- /* fall back to 32 bit, let pcm_convert.c do the conversion */
- err = snd_pcm_hw_params_set_format(ad->pcm, hwparams,
- SND_PCM_FORMAT_S32);
- if (err == 0)
- audio_format->bits = 32;
- }
-
- if (err == -EINVAL && audio_format->bits != 16) {
- /* fall back to 16 bit, let pcm_convert.c do the conversion */
- err = snd_pcm_hw_params_set_format(ad->pcm, hwparams,
- SND_PCM_FORMAT_S16);
- if (err == 0) {
- g_debug("ALSA device \"%s\": converting %u bit to 16 bit\n",
- alsa_device(ad), audio_format->bits);
- audio_format->bits = 16;
- }
- }
-
+ err = alsa_output_setup_format(ad->pcm, hwparams, audio_format);
if (err < 0) {
g_set_error(error, alsa_output_quark(), err,
- "ALSA device \"%s\" does not support %u bit audio: %s",
- alsa_device(ad), audio_format->bits,
+ "ALSA device \"%s\" does not support format %s: %s",
+ alsa_device(ad),
+ sample_format_to_string(audio_format->format),
snd_strerror(-err));
return false;
}
@@ -285,6 +408,26 @@ configure_hw:
}
audio_format->sample_rate = sample_rate;
+ snd_pcm_uframes_t buffer_size_min, buffer_size_max;
+ snd_pcm_hw_params_get_buffer_size_min(hwparams, &buffer_size_min);
+ snd_pcm_hw_params_get_buffer_size_max(hwparams, &buffer_size_max);
+ unsigned buffer_time_min, buffer_time_max;
+ snd_pcm_hw_params_get_buffer_time_min(hwparams, &buffer_time_min, 0);
+ snd_pcm_hw_params_get_buffer_time_max(hwparams, &buffer_time_max, 0);
+ g_debug("buffer: size=%u..%u time=%u..%u",
+ (unsigned)buffer_size_min, (unsigned)buffer_size_max,
+ buffer_time_min, buffer_time_max);
+
+ snd_pcm_uframes_t period_size_min, period_size_max;
+ snd_pcm_hw_params_get_period_size_min(hwparams, &period_size_min, 0);
+ snd_pcm_hw_params_get_period_size_max(hwparams, &period_size_max, 0);
+ unsigned period_time_min, period_time_max;
+ snd_pcm_hw_params_get_period_time_min(hwparams, &period_time_min, 0);
+ snd_pcm_hw_params_get_period_time_max(hwparams, &period_time_max, 0);
+ g_debug("period: size=%u..%u time=%u..%u",
+ (unsigned)period_size_min, (unsigned)period_size_max,
+ period_time_min, period_time_max);
+
if (ad->buffer_time > 0) {
buffer_time = ad->buffer_time;
cmd = "snd_pcm_hw_params_set_buffer_time_near";
@@ -365,6 +508,9 @@ configure_hw:
g_debug("buffer_size=%u period_size=%u",
(unsigned)alsa_buffer_size, (unsigned)alsa_period_size);
+ ad->period_frames = alsa_period_size;
+ ad->period_position = 0;
+
return true;
error:
@@ -378,19 +524,9 @@ static bool
alsa_open(void *data, struct audio_format *audio_format, GError **error)
{
struct alsa_data *ad = data;
- snd_pcm_format_t bitformat;
int err;
bool success;
- bitformat = get_bitformat(audio_format);
- if (bitformat == SND_PCM_FORMAT_UNKNOWN) {
- /* sample format is not supported by this plugin -
- fall back to 16 bit samples */
-
- audio_format->bits = 16;
- bitformat = SND_PCM_FORMAT_S16;
- }
-
err = snd_pcm_open(&ad->pcm, alsa_device(ad),
SND_PCM_STREAM_PLAYBACK, ad->mode);
if (err < 0) {
@@ -400,7 +536,7 @@ alsa_open(void *data, struct audio_format *audio_format, GError **error)
return false;
}
- success = alsa_setup(ad, audio_format, bitformat, error);
+ success = alsa_setup(ad, audio_format, error);
if (!success) {
snd_pcm_close(ad->pcm);
return false;
@@ -431,6 +567,7 @@ alsa_recover(struct alsa_data *ad, int err)
/* fall-through to snd_pcm_prepare: */
case SND_PCM_STATE_SETUP:
case SND_PCM_STATE_XRUN:
+ ad->period_position = 0;
err = snd_pcm_prepare(ad->pcm);
break;
case SND_PCM_STATE_DISCONNECTED:
@@ -448,11 +585,47 @@ alsa_recover(struct alsa_data *ad, int err)
}
static void
+alsa_drain(void *data)
+{
+ struct alsa_data *ad = data;
+
+ if (snd_pcm_state(ad->pcm) != SND_PCM_STATE_RUNNING)
+ return;
+
+ if (ad->period_position > 0) {
+ /* generate some silence to finish the partial
+ period */
+ snd_pcm_uframes_t nframes =
+ ad->period_frames - ad->period_position;
+ size_t nbytes = nframes * ad->frame_size;
+ void *buffer = g_malloc(nbytes);
+ snd_pcm_hw_params_t *params;
+ snd_pcm_format_t format;
+ unsigned channels;
+
+ snd_pcm_hw_params_alloca(&params);
+ snd_pcm_hw_params_current(ad->pcm, params);
+ snd_pcm_hw_params_get_format(params, &format);
+ snd_pcm_hw_params_get_channels(params, &channels);
+
+ snd_pcm_format_set_silence(format, buffer, nframes * channels);
+ ad->writei(ad->pcm, buffer, nframes);
+ g_free(buffer);
+ }
+
+ snd_pcm_drain(ad->pcm);
+
+ ad->period_position = 0;
+}
+
+static void
alsa_cancel(void *data)
{
struct alsa_data *ad = data;
- alsa_recover(ad, snd_pcm_drop(ad->pcm));
+ ad->period_position = 0;
+
+ snd_pcm_drop(ad->pcm);
}
static void
@@ -460,9 +633,6 @@ alsa_close(void *data)
{
struct alsa_data *ad = data;
- if (snd_pcm_state(ad->pcm) == SND_PCM_STATE_RUNNING)
- snd_pcm_drain(ad->pcm);
-
snd_pcm_close(ad->pcm);
}
@@ -475,8 +645,11 @@ alsa_play(void *data, const void *chunk, size_t size, GError **error)
while (true) {
snd_pcm_sframes_t ret = ad->writei(ad->pcm, chunk, size);
- if (ret > 0)
+ if (ret > 0) {
+ ad->period_position = (ad->period_position + ret)
+ % ad->period_frames;
return ret * ad->frame_size;
+ }
if (ret < 0 && ret != -EAGAIN && ret != -EINTR &&
alsa_recover(ad, ret) < 0) {
@@ -494,7 +667,9 @@ const struct audio_output_plugin alsaPlugin = {
.finish = alsa_finish,
.open = alsa_open,
.play = alsa_play,
+ .drain = alsa_drain,
.cancel = alsa_cancel,
.close = alsa_close,
- .mixer_plugin = &alsa_mixer,
+
+ .mixer_plugin = &alsa_mixer_plugin,
};
diff --git a/src/output/ao_plugin.c b/src/output/ao_plugin.c
index 63a43ddf0..6fedbc6e2 100644
--- a/src/output/ao_plugin.c
+++ b/src/output/ao_plugin.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -17,7 +17,8 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
-#include "../output_api.h"
+#include "config.h"
+#include "output_api.h"
#include <ao/ao.h>
#include <glib.h>
@@ -172,13 +173,24 @@ ao_output_open(void *data, struct audio_format *audio_format,
ao_sample_format format = OUR_AO_FORMAT_INITIALIZER;
struct ao_data *ad = (struct ao_data *)data;
- /* support for 24 bit samples in libao is currently dubious,
- and until we have sorted that out, resample everything to
- 16 bit */
- if (audio_format->bits > 16)
- audio_format->bits = 16;
+ switch (audio_format->format) {
+ case SAMPLE_FORMAT_S8:
+ format.bits = 8;
+ break;
+
+ case SAMPLE_FORMAT_S16:
+ format.bits = 16;
+ break;
+
+ default:
+ /* support for 24 bit samples in libao is currently
+ dubious, and until we have sorted that out,
+ convert everything to 16 bit */
+ audio_format->format = SAMPLE_FORMAT_S16;
+ format.bits = 16;
+ break;
+ }
- format.bits = audio_format->bits;
format.rate = audio_format->sample_rate;
format.byte_format = AO_FMT_NATIVE;
format.channels = audio_format->channels;
diff --git a/src/output/ffado_output_plugin.c b/src/output/ffado_output_plugin.c
new file mode 100644
index 000000000..723698ed0
--- /dev/null
+++ b/src/output/ffado_output_plugin.c
@@ -0,0 +1,347 @@
+/*
+ * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/*
+ * Warning: this plugin was not tested successfully. I just couldn't
+ * keep libffado2 from crashing. Use at your own risk.
+ *
+ * For details, see my Debian bug reports:
+ *
+ * http://bugs.debian.org/601657
+ * http://bugs.debian.org/601659
+ * http://bugs.debian.org/601663
+ *
+ */
+
+#include "config.h"
+#include "output_api.h"
+#include "timer.h"
+
+#include <glib.h>
+#include <assert.h>
+
+#include <libffado/ffado.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "ffado"
+
+enum {
+ MAX_STREAMS = 8,
+};
+
+struct mpd_ffado_stream {
+ /** libffado's stream number */
+ int number;
+
+ float *buffer;
+};
+
+struct mpd_ffado_device {
+ char *device_name;
+ int verbose;
+ unsigned period_size, nb_buffers;
+
+ ffado_device_t *dev;
+
+ /**
+ * The current sample position inside the stream buffers. New
+ * samples get appended at this position on all streams at the
+ * same time. When the buffers are full
+ * (buffer_position==period_size),
+ * ffado_streaming_transfer_playback_buffers() gets called to
+ * hand them over to libffado.
+ */
+ unsigned buffer_position;
+
+ /**
+ * The number of streams which are really used by MPD.
+ */
+ int num_streams;
+ struct mpd_ffado_stream streams[MAX_STREAMS];
+};
+
+static inline GQuark
+ffado_output_quark(void)
+{
+ return g_quark_from_static_string("ffado_output");
+}
+
+static void *
+ffado_init(G_GNUC_UNUSED const struct audio_format *audio_format,
+ const struct config_param *param,
+ GError **error_r)
+{
+ g_debug("using libffado version %s, API=%d",
+ ffado_get_version(), ffado_get_api_version());
+
+ struct mpd_ffado_device *fd = g_new(struct mpd_ffado_device, 1);
+ fd->device_name = config_dup_block_string(param, "device", NULL);
+ fd->verbose = config_get_block_unsigned(param, "verbose", 0);
+
+ fd->period_size = config_get_block_unsigned(param, "period_size",
+ 1024);
+ if (fd->period_size == 0 || fd->period_size > 1024 * 1024) {
+ g_set_error(error_r, ffado_output_quark(), 0,
+ "invalid period_size setting");
+ return false;
+ }
+
+ fd->nb_buffers = config_get_block_unsigned(param, "nb_buffers", 3);
+ if (fd->nb_buffers == 0 || fd->nb_buffers > 1024) {
+ g_set_error(error_r, ffado_output_quark(), 0,
+ "invalid nb_buffers setting");
+ return false;
+ }
+
+ return fd;
+}
+
+static void
+ffado_finish(void *data)
+{
+ struct mpd_ffado_device *fd = data;
+
+ g_free(fd->device_name);
+ g_free(fd);
+}
+
+static bool
+ffado_configure_stream(ffado_device_t *dev, struct mpd_ffado_stream *stream,
+ GError **error_r)
+{
+ char *buffer = (char *)stream->buffer;
+ if (ffado_streaming_set_playback_stream_buffer(dev, stream->number,
+ buffer) != 0) {
+ g_set_error(error_r, ffado_output_quark(), 0,
+ "failed to configure stream buffer");
+ return false;
+ }
+
+ if (ffado_streaming_playback_stream_onoff(dev, stream->number,
+ 1) != 0) {
+ g_set_error(error_r, ffado_output_quark(), 0,
+ "failed to disable stream");
+ return false;
+ }
+
+ return true;
+}
+
+static bool
+ffado_configure(struct mpd_ffado_device *fd, struct audio_format *audio_format,
+ GError **error_r)
+{
+ assert(fd != NULL);
+ assert(fd->dev != NULL);
+ assert(audio_format->channels <= MAX_STREAMS);
+
+ if (ffado_streaming_set_audio_datatype(fd->dev,
+ ffado_audio_datatype_float) != 0) {
+ g_set_error(error_r, ffado_output_quark(), 0,
+ "ffado_streaming_set_audio_datatype() failed");
+ return false;
+ }
+
+ int num_streams = ffado_streaming_get_nb_playback_streams(fd->dev);
+ if (num_streams < 0) {
+ g_set_error(error_r, ffado_output_quark(), 0,
+ "ffado_streaming_get_nb_playback_streams() failed");
+ return false;
+ }
+
+ g_debug("there are %d playback streams", num_streams);
+
+ fd->num_streams = 0;
+ for (int i = 0; i < num_streams; ++i) {
+ char name[256];
+ ffado_streaming_get_playback_stream_name(fd->dev, i, name,
+ sizeof(name) - 1);
+
+ ffado_streaming_stream_type type =
+ ffado_streaming_get_playback_stream_type(fd->dev, i);
+ if (type != ffado_stream_type_audio) {
+ g_debug("stream %d name='%s': not an audio stream",
+ i, name);
+ continue;
+ }
+
+ if (fd->num_streams >= audio_format->channels) {
+ g_debug("stream %d name='%s': ignoring",
+ i, name);
+ continue;
+ }
+
+ g_debug("stream %d name='%s'", i, name);
+
+ struct mpd_ffado_stream *stream =
+ &fd->streams[fd->num_streams++];
+
+ stream->number = i;
+
+ /* allocated buffer is zeroed = silence */
+ stream->buffer = g_new0(float, fd->period_size);
+
+ if (!ffado_configure_stream(fd->dev, stream, error_r))
+ return false;
+ }
+
+ if (!audio_valid_channel_count(fd->num_streams)) {
+ g_set_error(error_r, ffado_output_quark(), 0,
+ "invalid channel count from libffado: %u",
+ audio_format->channels);
+ return false;
+ }
+
+ g_debug("configured %d audio streams", fd->num_streams);
+
+ if (ffado_streaming_prepare(fd->dev) != 0) {
+ g_set_error(error_r, ffado_output_quark(), 0,
+ "ffado_streaming_prepare() failed");
+ return false;
+ }
+
+ if (ffado_streaming_start(fd->dev) != 0) {
+ g_set_error(error_r, ffado_output_quark(), 0,
+ "ffado_streaming_start() failed");
+ return false;
+ }
+
+ audio_format->channels = fd->num_streams;
+ return true;
+}
+
+static bool
+ffado_open(void *data, struct audio_format *audio_format, GError **error_r)
+{
+ struct mpd_ffado_device *fd = data;
+
+ /* will be converted to floating point, choose best input
+ format */
+ audio_format->format = SAMPLE_FORMAT_S24_P32;
+
+ ffado_device_info_t device_info;
+ memset(&device_info, 0, sizeof(device_info));
+ if (fd->device_name != NULL) {
+ device_info.nb_device_spec_strings = 1;
+ device_info.device_spec_strings = &fd->device_name;
+ }
+
+ ffado_options_t options;
+ memset(&options, 0, sizeof(options));
+ options.sample_rate = audio_format->sample_rate;
+ options.period_size = fd->period_size;
+ options.nb_buffers = fd->nb_buffers;
+ options.verbose = fd->verbose;
+
+ fd->dev = ffado_streaming_init(device_info, options);
+ if (fd->dev == NULL) {
+ g_set_error(error_r, ffado_output_quark(), 0,
+ "ffado_streaming_init() failed");
+ return false;
+ }
+
+ if (!ffado_configure(fd, audio_format, error_r)) {
+ ffado_streaming_finish(fd->dev);
+
+ for (int i = 0; i < fd->num_streams; ++i) {
+ struct mpd_ffado_stream *stream = &fd->streams[i];
+ g_free(stream->buffer);
+ }
+
+ return false;
+ }
+
+ fd->buffer_position = 0;
+
+ return true;
+}
+
+static void
+ffado_close(void *data)
+{
+ struct mpd_ffado_device *fd = data;
+
+ ffado_streaming_stop(fd->dev);
+ ffado_streaming_finish(fd->dev);
+
+ for (int i = 0; i < fd->num_streams; ++i) {
+ struct mpd_ffado_stream *stream = &fd->streams[i];
+ g_free(stream->buffer);
+ }
+}
+
+static size_t
+ffado_play(void *data, const void *chunk, size_t size, GError **error_r)
+{
+ struct mpd_ffado_device *fd = data;
+
+ /* wait for prefious buffer to finish (if it was full) */
+
+ if (fd->buffer_position >= fd->period_size) {
+ switch (ffado_streaming_wait(fd->dev)) {
+ case ffado_wait_ok:
+ case ffado_wait_xrun:
+ break;
+
+ default:
+ g_set_error(error_r, ffado_output_quark(), 0,
+ "ffado_streaming_wait() failed");
+ return 0;
+ }
+
+ fd->buffer_position = 0;
+ }
+
+ /* copy samples to stream buffers, non-interleaved */
+
+ const int32_t *p = chunk;
+ unsigned num_frames = size / sizeof(*p) / fd->num_streams;
+ if (num_frames > fd->period_size - fd->buffer_position)
+ num_frames = fd->period_size - fd->buffer_position;
+
+ for (unsigned i = num_frames; i > 0; --i) {
+ for (int stream = 0; stream < fd->num_streams; ++stream)
+ fd->streams[stream].buffer[fd->buffer_position] =
+ *p++ / (float)(1 << 23);
+ ++fd->buffer_position;
+ }
+
+ /* if buffer full, transfer to device */
+
+ if (fd->buffer_position >= fd->period_size &&
+ /* libffado documentation says this function returns -1 on
+ error, but that is a lie - it returns a boolean value,
+ and "false" means error */
+ !ffado_streaming_transfer_playback_buffers(fd->dev)) {
+ g_set_error(error_r, ffado_output_quark(), 0,
+ "ffado_streaming_transfer_playback_buffers() failed");
+ return 0;
+ }
+
+ return num_frames * sizeof(*p) * fd->num_streams;
+}
+
+const struct audio_output_plugin ffado_output_plugin = {
+ .name = "ffado",
+ .init = ffado_init,
+ .finish = ffado_finish,
+ .open = ffado_open,
+ .close = ffado_close,
+ .play = ffado_play,
+};
diff --git a/src/output/fifo_plugin.c b/src/output/fifo_output_plugin.c
index 76bbe8cfa..f4217ec4d 100644
--- a/src/output/fifo_plugin.c
+++ b/src/output/fifo_output_plugin.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -17,15 +17,17 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
-#include "../output_api.h"
-#include "../utils.h"
-#include "../timer.h"
+#include "config.h"
+#include "output_api.h"
+#include "utils.h"
+#include "timer.h"
+#include "fd_util.h"
+#include "open.h"
#include <glib.h>
#include <sys/types.h>
#include <sys/stat.h>
-#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
@@ -152,7 +154,7 @@ fifo_open(struct fifo_data *fd, GError **error)
if (!fifo_check(fd, error))
return false;
- fd->input = open(fd->path, O_RDONLY|O_NONBLOCK);
+ fd->input = open_cloexec(fd->path, O_RDONLY|O_NONBLOCK|O_BINARY, 0);
if (fd->input < 0) {
g_set_error(error, fifo_output_quark(), errno,
"Could not open FIFO \"%s\" for reading: %s",
@@ -161,7 +163,7 @@ fifo_open(struct fifo_data *fd, GError **error)
return false;
}
- fd->output = open(fd->path, O_WRONLY|O_NONBLOCK);
+ fd->output = open_cloexec(fd->path, O_WRONLY|O_NONBLOCK|O_BINARY, 0);
if (fd->output < 0) {
g_set_error(error, fifo_output_quark(), errno,
"Could not open FIFO \"%s\" for writing: %s",
diff --git a/src/output/httpd_client.c b/src/output/httpd_client.c
index adbdf7326..f5e14925b 100644
--- a/src/output/httpd_client.c
+++ b/src/output/httpd_client.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -17,11 +17,13 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "httpd_client.h"
#include "httpd_internal.h"
#include "fifo_buffer.h"
#include "page.h"
#include "icy_server.h"
+#include "glib_compat.h"
#include <stdbool.h>
#include <assert.h>
@@ -283,11 +285,12 @@ httpd_client_send_response(struct httpd_client *client)
} else {
gchar *metadata_header;
- metadata_header = icy_server_metadata_header("Add config information here!", /* TODO */
- "Add config information here!", /* TODO */
- "Add config information here!", /* TODO */
- client->httpd->content_type,
- client->metaint);
+ metadata_header = icy_server_metadata_header(
+ client->httpd->name,
+ client->httpd->genre,
+ client->httpd->website,
+ client->httpd->content_type,
+ client->metaint);
g_strlcpy(buffer, metadata_header, sizeof(buffer));
@@ -485,11 +488,6 @@ httpd_client_queue_size(const struct httpd_client *client)
return size;
}
-/* g_queue_clear() was introduced in GLib 2.14 */
-#if !GLIB_CHECK_VERSION(2,14,0)
-#define g_queue_clear(q) do { g_queue_free(q); q = g_queue_new(); } while (0)
-#endif
-
void
httpd_client_cancel(struct httpd_client *client)
{
diff --git a/src/output/httpd_client.h b/src/output/httpd_client.h
index 4a2912f80..7ebd0bbc0 100644
--- a/src/output/httpd_client.h
+++ b/src/output/httpd_client.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
diff --git a/src/output/httpd_internal.h b/src/output/httpd_internal.h
index 2257e27a2..277e70f11 100644
--- a/src/output/httpd_internal.h
+++ b/src/output/httpd_internal.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -29,30 +29,34 @@
#include <glib.h>
-#include <sys/socket.h>
+#include <stdbool.h>
struct httpd_client;
struct httpd_output {
/**
- * The configured encoder plugin.
+ * True if the audio output is open and accepts client
+ * connections.
*/
- struct encoder *encoder;
+ bool open;
/**
- * The MIME type produced by the #encoder.
+ * The configured encoder plugin.
*/
- const char *content_type;
+ struct encoder *encoder;
/**
- * The configured address of the listener socket.
+ * Number of bytes which were fed into the encoder, without
+ * ever receiving new output. This is used to estimate
+ * whether MPD should manually flush the encoder, to avoid
+ * buffer underruns in the client.
*/
- struct sockaddr_storage address;
+ size_t unflushed_input;
/**
- * The size of #address.
+ * The MIME type produced by the #encoder.
*/
- socklen_t address_size;
+ const char *content_type;
/**
* This mutex protects the listener socket and the client
@@ -69,12 +73,7 @@ struct httpd_output {
/**
* The listener socket.
*/
- int fd;
-
- /**
- * A GLib main loop source id for the listener socket.
- */
- guint source_id;
+ struct server_socket *server_socket;
/**
* The header page, which is sent to every client on connect.
@@ -87,6 +86,19 @@ struct httpd_output {
struct page *metadata;
/**
+ * The configured name.
+ */
+ char const *name;
+ /**
+ * The configured genre.
+ */
+ char const *genre;
+ /**
+ * The configured website address.
+ */
+ char const *website;
+
+ /**
* A linked list containing all clients which are currently
* connected.
*/
@@ -97,6 +109,12 @@ struct httpd_output {
* function.
*/
char buffer[32768];
+
+ /**
+ * The maximum and current number of clients connected
+ * at the same time.
+ */
+ guint clients_max, clients_cnt;
};
/**
diff --git a/src/output/httpd_output_plugin.c b/src/output/httpd_output_plugin.c
index 026e8d9d8..6650d89e3 100644
--- a/src/output/httpd_output_plugin.c
+++ b/src/output/httpd_output_plugin.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -17,6 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "httpd_internal.h"
#include "httpd_client.h"
#include "output_api.h"
@@ -25,15 +26,20 @@
#include "socket_util.h"
#include "page.h"
#include "icy_server.h"
+#include "fd_util.h"
+#include "server_socket.h"
#include <assert.h>
#include <sys/types.h>
-#include <netinet/in.h>
-#include <netdb.h>
#include <unistd.h>
#include <errno.h>
+#ifdef HAVE_LIBWRAP
+#include <sys/socket.h> /* needed for AF_UNIX */
+#include <tcpd.h>
+#endif
+
#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "httpd_output"
@@ -46,18 +52,49 @@ httpd_output_quark(void)
return g_quark_from_static_string("httpd_output");
}
+static void
+httpd_listen_in_event(int fd, const struct sockaddr *address,
+ size_t address_length, int uid, void *ctx);
+
+static bool
+httpd_output_bind(struct httpd_output *httpd, GError **error_r)
+{
+ httpd->open = false;
+
+ g_mutex_lock(httpd->mutex);
+ bool success = server_socket_open(httpd->server_socket, error_r);
+ g_mutex_unlock(httpd->mutex);
+
+ return success;
+}
+
+static void
+httpd_output_unbind(struct httpd_output *httpd)
+{
+ assert(!httpd->open);
+
+ g_mutex_lock(httpd->mutex);
+ server_socket_close(httpd->server_socket);
+ g_mutex_unlock(httpd->mutex);
+}
+
static void *
httpd_output_init(G_GNUC_UNUSED const struct audio_format *audio_format,
const struct config_param *param,
GError **error)
{
struct httpd_output *httpd = g_new(struct httpd_output, 1);
- const char *encoder_name;
+ const char *encoder_name, *bind_to_address;
const struct encoder_plugin *encoder_plugin;
guint port;
- struct sockaddr_in *sin;
/* read configuration */
+ httpd->name =
+ config_get_block_string(param, "name", "Set name in config");
+ httpd->genre =
+ config_get_block_string(param, "genre", "Set genre in config");
+ httpd->website =
+ config_get_block_string(param, "website", "Set website in config");
port = config_get_block_unsigned(param, "port", 8000);
@@ -69,24 +106,25 @@ httpd_output_init(G_GNUC_UNUSED const struct audio_format *audio_format,
return NULL;
}
- if (strcmp(encoder_name, "vorbis") == 0)
- httpd->content_type = "audio/ogg";
- else if (strcmp(encoder_name, "lame") == 0)
- httpd->content_type = "audio/mpeg";
- else
- httpd->content_type = "application/octet-stream";
+ httpd->clients_max = config_get_block_unsigned(param,"max_clients", 0);
+
+ /* set up bind_to_address */
- /* initialize listen address */
+ httpd->server_socket = server_socket_new(httpd_listen_in_event, httpd);
- sin = (struct sockaddr_in *)&httpd->address;
- memset(sin, 0, sizeof(sin));
- sin->sin_port = htons(port);
- sin->sin_family = AF_INET;
- sin->sin_addr.s_addr = INADDR_ANY;
- httpd->address_size = sizeof(*sin);
+ bind_to_address =
+ config_get_block_string(param, "bind_to_address", NULL);
+ bool success = bind_to_address != NULL &&
+ strcmp(bind_to_address, "any") != 0
+ ? server_socket_add_host(httpd->server_socket, bind_to_address,
+ port, error)
+ : server_socket_add_port(httpd->server_socket, port, error);
+ if (!success)
+ return NULL;
/* initialize metadata */
httpd->metadata = NULL;
+ httpd->unflushed_input = 0;
/* initialize encoder */
@@ -94,6 +132,12 @@ httpd_output_init(G_GNUC_UNUSED const struct audio_format *audio_format,
if (httpd->encoder == NULL)
return NULL;
+ /* determine content type */
+ httpd->content_type = encoder_get_mime_type(httpd->encoder);
+ if (httpd->content_type == NULL) {
+ httpd->content_type = "application/octet-stream";
+ }
+
httpd->mutex = g_mutex_new();
return httpd;
@@ -108,6 +152,7 @@ httpd_output_finish(void *data)
page_unref(httpd->metadata);
encoder_finish(httpd->encoder);
+ server_socket_free(httpd->server_socket);
g_mutex_free(httpd->mutex);
g_free(httpd);
}
@@ -124,36 +169,64 @@ httpd_client_add(struct httpd_output *httpd, int fd)
httpd->encoder->plugin->tag == NULL);
httpd->clients = g_list_prepend(httpd->clients, client);
+ httpd->clients_cnt++;
/* pass metadata to client */
if (httpd->metadata)
httpd_client_send_metadata(client, httpd->metadata);
}
-static gboolean
-httpd_listen_in_event(G_GNUC_UNUSED GIOChannel *source,
- G_GNUC_UNUSED GIOCondition condition,
- gpointer data)
+static void
+httpd_listen_in_event(int fd, const struct sockaddr *address,
+ size_t address_length, G_GNUC_UNUSED int uid, void *ctx)
{
- struct httpd_output *httpd = data;
- int fd;
- struct sockaddr_storage sa;
- socklen_t sa_length = sizeof(sa);
-
- g_mutex_lock(httpd->mutex);
+ struct httpd_output *httpd = ctx;
/* the listener socket has become readable - a client has
connected */
- fd = accept(httpd->fd, (struct sockaddr*)&sa, &sa_length);
- if (fd >= 0)
- httpd_client_add(httpd, fd);
- else if (fd < 0 && errno != EINTR)
+#ifdef HAVE_LIBWRAP
+ if (address->sa_family != AF_UNIX) {
+ char *hostaddr = sockaddr_to_string(address, address_length, NULL);
+ const char *progname = g_get_prgname();
+
+ struct request_info req;
+ request_init(&req, RQ_FILE, fd, RQ_DAEMON, progname, 0);
+
+ fromhost(&req);
+
+ if (!hosts_access(&req)) {
+ /* tcp wrappers says no */
+ g_warning("libwrap refused connection (libwrap=%s) from %s",
+ progname, hostaddr);
+ g_free(hostaddr);
+ close(fd);
+ g_mutex_unlock(httpd->mutex);
+ return;
+ }
+
+ g_free(hostaddr);
+ }
+#else
+ (void)address;
+ (void)address_length;
+#endif /* HAVE_WRAP */
+
+ g_mutex_lock(httpd->mutex);
+
+ if (fd >= 0) {
+ /* can we allow additional client */
+ if (httpd->open &&
+ (httpd->clients_max == 0 ||
+ httpd->clients_cnt < httpd->clients_max))
+ httpd_client_add(httpd, fd);
+ else
+ close(fd);
+ } else if (fd < 0 && errno != EINTR) {
g_warning("accept() failed: %s", g_strerror(errno));
+ }
g_mutex_unlock(httpd->mutex);
-
- return true;
}
/**
@@ -165,12 +238,22 @@ httpd_output_read_page(struct httpd_output *httpd)
{
size_t size = 0, nbytes;
+ if (httpd->unflushed_input >= 65536) {
+ /* we have fed a lot of input into the encoder, but it
+ didn't give anything back yet - flush now to avoid
+ buffer underruns */
+ encoder_flush(httpd->encoder, NULL);
+ httpd->unflushed_input = 0;
+ }
+
do {
nbytes = encoder_read(httpd->encoder, httpd->buffer + size,
sizeof(httpd->buffer) - size);
if (nbytes == 0)
break;
+ httpd->unflushed_input = 0;
+
size += nbytes;
} while (size < sizeof(httpd->buffer));
@@ -195,41 +278,41 @@ httpd_output_encoder_open(struct httpd_output *httpd,
bytes of encoder output after opening it, because it has to
be sent to every new client */
httpd->header = httpd_output_read_page(httpd);
+
+ httpd->unflushed_input = 0;
+
return true;
}
static bool
+httpd_output_enable(void *data, GError **error_r)
+{
+ struct httpd_output *httpd = data;
+
+ return httpd_output_bind(httpd, error_r);
+}
+
+static void
+httpd_output_disable(void *data)
+{
+ struct httpd_output *httpd = data;
+
+ httpd_output_unbind(httpd);
+}
+
+static bool
httpd_output_open(void *data, struct audio_format *audio_format,
GError **error)
{
struct httpd_output *httpd = data;
bool success;
- GIOChannel *channel;
g_mutex_lock(httpd->mutex);
- /* create and set up listener socket */
-
- httpd->fd = socket_bind_listen(PF_INET, SOCK_STREAM, 0,
- (struct sockaddr *)&httpd->address,
- httpd->address_size,
- 16, error);
- if (httpd->fd < 0) {
- g_mutex_unlock(httpd->mutex);
- return false;
- }
-
- channel = g_io_channel_unix_new(httpd->fd);
- httpd->source_id = g_io_add_watch(channel, G_IO_IN,
- httpd_listen_in_event, httpd);
- g_io_channel_unref(channel);
-
/* open the encoder */
success = httpd_output_encoder_open(httpd, audio_format, error);
if (!success) {
- g_source_remove(httpd->source_id);
- close(httpd->fd);
g_mutex_unlock(httpd->mutex);
return false;
}
@@ -237,8 +320,11 @@ httpd_output_open(void *data, struct audio_format *audio_format,
/* initialize other attributes */
httpd->clients = NULL;
+ httpd->clients_cnt = 0;
httpd->timer = timer_new(audio_format);
+ httpd->open = true;
+
g_mutex_unlock(httpd->mutex);
return true;
}
@@ -257,6 +343,8 @@ static void httpd_output_close(void *data)
g_mutex_lock(httpd->mutex);
+ httpd->open = false;
+
timer_free(httpd->timer);
g_list_foreach(httpd->clients, httpd_client_delete, NULL);
@@ -267,9 +355,6 @@ static void httpd_output_close(void *data)
encoder_close(httpd->encoder);
- g_source_remove(httpd->source_id);
- close(httpd->fd);
-
g_mutex_unlock(httpd->mutex);
}
@@ -281,6 +366,7 @@ httpd_output_remove_client(struct httpd_output *httpd,
assert(client != NULL);
httpd->clients = g_list_remove(httpd->clients, client);
+ httpd->clients_cnt--;
}
void
@@ -291,6 +377,16 @@ httpd_output_send_header(struct httpd_output *httpd,
httpd_client_send(client, httpd->header);
}
+static unsigned
+httpd_output_delay(void *data)
+{
+ struct httpd_output *httpd = data;
+
+ return httpd->timer->started
+ ? timer_delay(httpd->timer)
+ : 0;
+}
+
static void
httpd_client_check_queue(gpointer data, G_GNUC_UNUSED gpointer user_data)
{
@@ -352,6 +448,8 @@ httpd_output_encode_and_play(struct httpd_output *httpd,
if (!success)
return false;
+ httpd->unflushed_input += size;
+
httpd_output_encoder_to_clients(httpd);
return true;
@@ -378,13 +476,29 @@ httpd_output_play(void *data, const void *chunk, size_t size, GError **error)
if (!httpd->timer->started)
timer_start(httpd->timer);
- else
- timer_sync(httpd->timer);
timer_add(httpd->timer, size);
return size;
}
+static bool
+httpd_output_pause(void *data)
+{
+ struct httpd_output *httpd = data;
+
+ g_mutex_lock(httpd->mutex);
+ bool has_clients = httpd->clients != NULL;
+ g_mutex_unlock(httpd->mutex);
+
+ if (has_clients) {
+ static const char silence[1020];
+ return httpd_output_play(data, silence, sizeof(silence), NULL);
+ } else {
+ g_usleep(100000);
+ return true;
+ }
+}
+
static void
httpd_send_metadata(gpointer data, gpointer user_data)
{
@@ -433,9 +547,8 @@ httpd_output_tag(void *data, const struct tag *tag)
page_unref (httpd->metadata);
httpd->metadata =
- icy_server_metadata_page(tag, TAG_ITEM_ALBUM,
- TAG_ITEM_ARTIST,
- TAG_ITEM_TITLE,
+ icy_server_metadata_page(tag, TAG_ALBUM,
+ TAG_ARTIST, TAG_TITLE,
TAG_NUM_OF_ITEM_TYPES);
if (httpd->metadata != NULL) {
g_mutex_lock(httpd->mutex);
@@ -468,9 +581,13 @@ const struct audio_output_plugin httpd_output_plugin = {
.name = "httpd",
.init = httpd_output_init,
.finish = httpd_output_finish,
+ .enable = httpd_output_enable,
+ .disable = httpd_output_disable,
.open = httpd_output_open,
.close = httpd_output_close,
+ .delay = httpd_output_delay,
.send_tag = httpd_output_tag,
.play = httpd_output_play,
+ .pause = httpd_output_pause,
.cancel = httpd_output_cancel,
};
diff --git a/src/output/jack_output_plugin.c b/src/output/jack_output_plugin.c
new file mode 100644
index 000000000..2767d4eb8
--- /dev/null
+++ b/src/output/jack_output_plugin.c
@@ -0,0 +1,722 @@
+/*
+ * 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 <assert.h>
+
+#include <glib.h>
+#include <jack/jack.h>
+#include <jack/types.h>
+#include <jack/ringbuffer.h>
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <errno.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "jack"
+
+enum {
+ MAX_PORTS = 16,
+};
+
+static const size_t jack_sample_size = sizeof(jack_default_audio_sample_t);
+
+struct jack_data {
+ /**
+ * libjack options passed to jack_client_open().
+ */
+ jack_options_t options;
+
+ const char *name;
+
+ const char *server_name;
+
+ /* configuration */
+
+ char *source_ports[MAX_PORTS];
+ unsigned num_source_ports;
+
+ char *destination_ports[MAX_PORTS];
+ unsigned num_destination_ports;
+
+ size_t ringbuffer_size;
+
+ /* the current audio format */
+ struct audio_format audio_format;
+
+ /* jack library stuff */
+ jack_port_t *ports[MAX_PORTS];
+ jack_client_t *client;
+ jack_ringbuffer_t *ringbuffer[MAX_PORTS];
+
+ bool shutdown;
+
+ /**
+ * While this flag is set, the "process" callback generates
+ * silence.
+ */
+ bool pause;
+};
+
+/**
+ * The quark used for GError.domain.
+ */
+static inline GQuark
+jack_output_quark(void)
+{
+ return g_quark_from_static_string("jack_output");
+}
+
+/**
+ * Determine the number of frames guaranteed to be available on all
+ * channels.
+ */
+static jack_nframes_t
+mpd_jack_available(const struct jack_data *jd)
+{
+ size_t min = jack_ringbuffer_read_space(jd->ringbuffer[0]);
+
+ for (unsigned i = 1; i < jd->audio_format.channels; ++i) {
+ size_t current = jack_ringbuffer_read_space(jd->ringbuffer[i]);
+ if (current < min)
+ min = current;
+ }
+
+ assert(min % jack_sample_size == 0);
+
+ return min / jack_sample_size;
+}
+
+static int
+mpd_jack_process(jack_nframes_t nframes, void *arg)
+{
+ struct jack_data *jd = (struct jack_data *) arg;
+ jack_default_audio_sample_t *out;
+
+ if (nframes <= 0)
+ return 0;
+
+ if (jd->pause) {
+ /* empty the ring buffers */
+
+ const jack_nframes_t available = mpd_jack_available(jd);
+ for (unsigned i = 0; i < jd->audio_format.channels; ++i)
+ jack_ringbuffer_read_advance(jd->ringbuffer[i],
+ available * jack_sample_size);
+
+ /* generate silence while MPD is paused */
+
+ for (unsigned i = 0; i < jd->audio_format.channels; ++i) {
+ out = jack_port_get_buffer(jd->ports[i], nframes);
+
+ for (jack_nframes_t f = 0; f < nframes; ++f)
+ out[f] = 0.0;
+ }
+
+ return 0;
+ }
+
+ jack_nframes_t available = mpd_jack_available(jd);
+ if (available > nframes)
+ available = nframes;
+
+ for (unsigned i = 0; i < jd->audio_format.channels; ++i) {
+ out = jack_port_get_buffer(jd->ports[i], nframes);
+ jack_ringbuffer_read(jd->ringbuffer[i],
+ (char *)out, available * jack_sample_size);
+
+ for (jack_nframes_t f = available; f < nframes; ++f)
+ /* ringbuffer underrun, fill with silence */
+ out[f] = 0.0;
+ }
+
+ /* generate silence for the unused source ports */
+
+ for (unsigned i = jd->audio_format.channels;
+ i < jd->num_source_ports; ++i) {
+ out = jack_port_get_buffer(jd->ports[i], nframes);
+
+ for (jack_nframes_t f = 0; f < nframes; ++f)
+ out[f] = 0.0;
+ }
+
+ return 0;
+}
+
+static void
+mpd_jack_shutdown(void *arg)
+{
+ struct jack_data *jd = (struct jack_data *) arg;
+ jd->shutdown = true;
+}
+
+static void
+set_audioformat(struct jack_data *jd, struct audio_format *audio_format)
+{
+ audio_format->sample_rate = jack_get_sample_rate(jd->client);
+
+ if (jd->num_source_ports == 1)
+ audio_format->channels = 1;
+ else if (audio_format->channels > jd->num_source_ports)
+ audio_format->channels = 2;
+
+ if (audio_format->format != SAMPLE_FORMAT_S16 &&
+ audio_format->format != SAMPLE_FORMAT_S24_P32)
+ audio_format->format = SAMPLE_FORMAT_S24_P32;
+}
+
+static void
+mpd_jack_error(const char *msg)
+{
+ g_warning("%s", msg);
+}
+
+#ifdef HAVE_JACK_SET_INFO_FUNCTION
+static void
+mpd_jack_info(const char *msg)
+{
+ g_message("%s", msg);
+}
+#endif
+
+/**
+ * Disconnect the JACK client.
+ */
+static void
+mpd_jack_disconnect(struct jack_data *jd)
+{
+ assert(jd != NULL);
+ assert(jd->client != NULL);
+
+ jack_deactivate(jd->client);
+ jack_client_close(jd->client);
+ jd->client = NULL;
+}
+
+/**
+ * Connect the JACK client and performs some basic setup
+ * (e.g. register callbacks).
+ */
+static bool
+mpd_jack_connect(struct jack_data *jd, GError **error_r)
+{
+ jack_status_t status;
+
+ assert(jd != NULL);
+
+ jd->shutdown = false;
+
+ jd->client = jack_client_open(jd->name, jd->options, &status,
+ jd->server_name);
+ if (jd->client == NULL) {
+ g_set_error(error_r, jack_output_quark(), 0,
+ "Failed to connect to JACK server, status=%d",
+ status);
+ return false;
+ }
+
+ jack_set_process_callback(jd->client, mpd_jack_process, jd);
+ jack_on_shutdown(jd->client, mpd_jack_shutdown, jd);
+
+ for (unsigned i = 0; i < jd->num_source_ports; ++i) {
+ jd->ports[i] = jack_port_register(jd->client,
+ jd->source_ports[i],
+ JACK_DEFAULT_AUDIO_TYPE,
+ JackPortIsOutput, 0);
+ if (jd->ports[i] == NULL) {
+ g_set_error(error_r, jack_output_quark(), 0,
+ "Cannot register output port \"%s\"",
+ jd->source_ports[i]);
+ mpd_jack_disconnect(jd);
+ return false;
+ }
+ }
+
+ return true;
+}
+
+static bool
+mpd_jack_test_default_device(void)
+{
+ return true;
+}
+
+static unsigned
+parse_port_list(int line, const char *source, char **dest, GError **error_r)
+{
+ char **list = g_strsplit(source, ",", 0);
+ unsigned n = 0;
+
+ for (n = 0; list[n] != NULL; ++n) {
+ if (n >= MAX_PORTS) {
+ g_set_error(error_r, jack_output_quark(), 0,
+ "too many port names in line %d",
+ line);
+ return 0;
+ }
+
+ dest[n] = list[n];
+ }
+
+ g_free(list);
+
+ if (n == 0) {
+ g_set_error(error_r, jack_output_quark(), 0,
+ "at least one port name expected in line %d",
+ line);
+ return 0;
+ }
+
+ return n;
+}
+
+static void *
+mpd_jack_init(G_GNUC_UNUSED const struct audio_format *audio_format,
+ const struct config_param *param, GError **error_r)
+{
+ struct jack_data *jd;
+ const char *value;
+
+ jd = g_new(struct jack_data, 1);
+ jd->options = JackNullOption;
+
+ jd->name = config_get_block_string(param, "client_name", NULL);
+ if (jd->name != NULL)
+ jd->options |= JackUseExactName;
+ else
+ /* if there's a no configured client name, we don't
+ care about the JackUseExactName option */
+ jd->name = "Music Player Daemon";
+
+ jd->server_name = config_get_block_string(param, "server_name", NULL);
+ if (jd->server_name != NULL)
+ jd->options |= JackServerName;
+
+ if (!config_get_block_bool(param, "autostart", false))
+ jd->options |= JackNoStartServer;
+
+ /* configure the source ports */
+
+ value = config_get_block_string(param, "source_ports", "left,right");
+ jd->num_source_ports = parse_port_list(param->line, value,
+ jd->source_ports, error_r);
+ if (jd->num_source_ports == 0)
+ return NULL;
+
+ /* configure the destination ports */
+
+ value = config_get_block_string(param, "destination_ports", NULL);
+ if (value == NULL) {
+ /* compatibility with MPD < 0.16 */
+ value = config_get_block_string(param, "ports", NULL);
+ if (value != NULL)
+ g_warning("deprecated option 'ports' in line %d",
+ param->line);
+ }
+
+ if (value != NULL) {
+ jd->num_destination_ports =
+ parse_port_list(param->line, value,
+ jd->destination_ports, error_r);
+ if (jd->num_destination_ports == 0)
+ return NULL;
+ } else {
+ jd->num_destination_ports = 0;
+ }
+
+ if (jd->num_destination_ports > 0 &&
+ jd->num_destination_ports != jd->num_source_ports)
+ g_warning("number of source ports (%u) mismatches the "
+ "number of destination ports (%u) in line %d",
+ jd->num_source_ports, jd->num_destination_ports,
+ param->line);
+
+ jd->ringbuffer_size =
+ config_get_block_unsigned(param, "ringbuffer_size", 32768);
+
+ jack_set_error_function(mpd_jack_error);
+
+#ifdef HAVE_JACK_SET_INFO_FUNCTION
+ jack_set_info_function(mpd_jack_info);
+#endif
+
+ return jd;
+}
+
+static void
+mpd_jack_finish(void *data)
+{
+ struct jack_data *jd = data;
+
+ for (unsigned i = 0; i < jd->num_source_ports; ++i)
+ g_free(jd->source_ports[i]);
+
+ for (unsigned i = 0; i < jd->num_destination_ports; ++i)
+ g_free(jd->destination_ports[i]);
+
+ g_free(jd);
+}
+
+static bool
+mpd_jack_enable(void *data, GError **error_r)
+{
+ struct jack_data *jd = (struct jack_data *)data;
+
+ for (unsigned i = 0; i < jd->num_source_ports; ++i)
+ jd->ringbuffer[i] = NULL;
+
+ return mpd_jack_connect(jd, error_r);
+}
+
+static void
+mpd_jack_disable(void *data)
+{
+ struct jack_data *jd = (struct jack_data *)data;
+
+ if (jd->client != NULL)
+ mpd_jack_disconnect(jd);
+
+ for (unsigned i = 0; i < jd->num_source_ports; ++i) {
+ if (jd->ringbuffer[i] != NULL) {
+ jack_ringbuffer_free(jd->ringbuffer[i]);
+ jd->ringbuffer[i] = NULL;
+ }
+ }
+}
+
+/**
+ * Stops the playback on the JACK connection.
+ */
+static void
+mpd_jack_stop(struct jack_data *jd)
+{
+ assert(jd != NULL);
+
+ if (jd->client == NULL)
+ return;
+
+ if (jd->shutdown)
+ /* the connection has failed; close it */
+ mpd_jack_disconnect(jd);
+ else
+ /* the connection is alive: just stop playback */
+ jack_deactivate(jd->client);
+}
+
+static bool
+mpd_jack_start(struct jack_data *jd, GError **error_r)
+{
+ const char *destination_ports[MAX_PORTS], **jports;
+ const char *duplicate_port = NULL;
+ unsigned num_destination_ports;
+
+ assert(jd->client != NULL);
+ assert(jd->audio_format.channels <= jd->num_source_ports);
+
+ /* allocate the ring buffers on the first open(); these
+ persist until MPD exits. It's too unsafe to delete them
+ because we can never know when mpd_jack_process() gets
+ called */
+ for (unsigned i = 0; i < jd->num_source_ports; ++i) {
+ if (jd->ringbuffer[i] == NULL)
+ jd->ringbuffer[i] =
+ jack_ringbuffer_create(jd->ringbuffer_size);
+
+ /* clear the ring buffer to be sure that data from
+ previous playbacks are gone */
+ jack_ringbuffer_reset(jd->ringbuffer[i]);
+ }
+
+ if ( jack_activate(jd->client) ) {
+ g_set_error(error_r, jack_output_quark(), 0,
+ "cannot activate client");
+ mpd_jack_stop(jd);
+ return false;
+ }
+
+ if (jd->num_destination_ports == 0) {
+ /* no output ports were configured - ask libjack for
+ defaults */
+ jports = jack_get_ports(jd->client, NULL, NULL,
+ JackPortIsPhysical | JackPortIsInput);
+ if (jports == NULL) {
+ g_set_error(error_r, jack_output_quark(), 0,
+ "no ports found");
+ mpd_jack_stop(jd);
+ return false;
+ }
+
+ assert(*jports != NULL);
+
+ for (num_destination_ports = 0;
+ num_destination_ports < MAX_PORTS &&
+ jports[num_destination_ports] != NULL;
+ ++num_destination_ports) {
+ g_debug("destination_port[%u] = '%s'\n",
+ num_destination_ports,
+ jports[num_destination_ports]);
+ destination_ports[num_destination_ports] =
+ jports[num_destination_ports];
+ }
+ } else {
+ /* use the configured output ports */
+
+ num_destination_ports = jd->num_destination_ports;
+ memcpy(destination_ports, jd->destination_ports,
+ num_destination_ports * sizeof(*destination_ports));
+
+ jports = NULL;
+ }
+
+ assert(num_destination_ports > 0);
+
+ if (jd->audio_format.channels >= 2 && num_destination_ports == 1) {
+ /* mix stereo signal on one speaker */
+
+ while (num_destination_ports < jd->audio_format.channels)
+ destination_ports[num_destination_ports++] =
+ destination_ports[0];
+ } else if (num_destination_ports > jd->audio_format.channels) {
+ if (jd->audio_format.channels == 1 && num_destination_ports > 2) {
+ /* mono input file: connect the one source
+ channel to the both destination channels */
+ duplicate_port = destination_ports[1];
+ num_destination_ports = 1;
+ } else
+ /* connect only as many ports as we need */
+ num_destination_ports = jd->audio_format.channels;
+ }
+
+ assert(num_destination_ports <= jd->num_source_ports);
+
+ for (unsigned i = 0; i < num_destination_ports; ++i) {
+ int ret;
+
+ ret = jack_connect(jd->client, jack_port_name(jd->ports[i]),
+ destination_ports[i]);
+ if (ret != 0) {
+ g_set_error(error_r, jack_output_quark(), 0,
+ "Not a valid JACK port: %s",
+ destination_ports[i]);
+
+ if (jports != NULL)
+ free(jports);
+
+ mpd_jack_stop(jd);
+ return false;
+ }
+ }
+
+ if (duplicate_port != NULL) {
+ /* mono input file: connect the one source channel to
+ the both destination channels */
+ int ret;
+
+ ret = jack_connect(jd->client, jack_port_name(jd->ports[0]),
+ duplicate_port);
+ if (ret != 0) {
+ g_set_error(error_r, jack_output_quark(), 0,
+ "Not a valid JACK port: %s",
+ duplicate_port);
+
+ if (jports != NULL)
+ free(jports);
+
+ mpd_jack_stop(jd);
+ return false;
+ }
+ }
+
+ if (jports != NULL)
+ free(jports);
+
+ return true;
+}
+
+static bool
+mpd_jack_open(void *data, struct audio_format *audio_format, GError **error_r)
+{
+ struct jack_data *jd = data;
+
+ assert(jd != NULL);
+
+ jd->pause = false;
+
+ if (jd->client == NULL && !mpd_jack_connect(jd, error_r))
+ return false;
+
+ set_audioformat(jd, audio_format);
+ jd->audio_format = *audio_format;
+
+ if (!mpd_jack_start(jd, error_r))
+ return false;
+
+ return true;
+}
+
+static void
+mpd_jack_close(G_GNUC_UNUSED void *data)
+{
+ struct jack_data *jd = data;
+
+ mpd_jack_stop(jd);
+}
+
+static inline jack_default_audio_sample_t
+sample_16_to_jack(int16_t sample)
+{
+ return sample / (jack_default_audio_sample_t)(1 << (16 - 1));
+}
+
+static void
+mpd_jack_write_samples_16(struct jack_data *jd, const int16_t *src,
+ unsigned num_samples)
+{
+ jack_default_audio_sample_t sample;
+ unsigned i;
+
+ while (num_samples-- > 0) {
+ for (i = 0; i < jd->audio_format.channels; ++i) {
+ sample = sample_16_to_jack(*src++);
+ jack_ringbuffer_write(jd->ringbuffer[i], (void*)&sample,
+ sizeof(sample));
+ }
+ }
+}
+
+static inline jack_default_audio_sample_t
+sample_24_to_jack(int32_t sample)
+{
+ return sample / (jack_default_audio_sample_t)(1 << (24 - 1));
+}
+
+static void
+mpd_jack_write_samples_24(struct jack_data *jd, const int32_t *src,
+ unsigned num_samples)
+{
+ jack_default_audio_sample_t sample;
+ unsigned i;
+
+ while (num_samples-- > 0) {
+ for (i = 0; i < jd->audio_format.channels; ++i) {
+ sample = sample_24_to_jack(*src++);
+ jack_ringbuffer_write(jd->ringbuffer[i], (void*)&sample,
+ sizeof(sample));
+ }
+ }
+}
+
+static void
+mpd_jack_write_samples(struct jack_data *jd, const void *src,
+ unsigned num_samples)
+{
+ switch (jd->audio_format.format) {
+ case SAMPLE_FORMAT_S16:
+ mpd_jack_write_samples_16(jd, (const int16_t*)src,
+ num_samples);
+ break;
+
+ case SAMPLE_FORMAT_S24_P32:
+ mpd_jack_write_samples_24(jd, (const int32_t*)src,
+ num_samples);
+ break;
+
+ default:
+ assert(false);
+ }
+}
+
+static size_t
+mpd_jack_play(void *data, const void *chunk, size_t size, GError **error_r)
+{
+ struct jack_data *jd = data;
+ const size_t frame_size = audio_format_frame_size(&jd->audio_format);
+ size_t space = 0, space1;
+
+ jd->pause = false;
+
+ assert(size % frame_size == 0);
+ size /= frame_size;
+
+ while (true) {
+ if (jd->shutdown) {
+ g_set_error(error_r, jack_output_quark(), 0,
+ "Refusing to play, because "
+ "there is no client thread");
+ return 0;
+ }
+
+ space = jack_ringbuffer_write_space(jd->ringbuffer[0]);
+ for (unsigned i = 1; i < jd->audio_format.channels; ++i) {
+ space1 = jack_ringbuffer_write_space(jd->ringbuffer[i]);
+ if (space > space1)
+ /* send data symmetrically */
+ space = space1;
+ }
+
+ if (space >= jack_sample_size)
+ break;
+
+ /* XXX do something more intelligent to
+ synchronize */
+ g_usleep(1000);
+ }
+
+ space /= jack_sample_size;
+ if (space < size)
+ size = space;
+
+ mpd_jack_write_samples(jd, chunk, size);
+ return size * frame_size;
+}
+
+static bool
+mpd_jack_pause(void *data)
+{
+ struct jack_data *jd = data;
+
+ if (jd->shutdown)
+ return false;
+
+ jd->pause = true;
+
+ /* due to a MPD API limitation, we have to sleep a little bit
+ here, to avoid hogging the CPU */
+ g_usleep(50000);
+
+ return true;
+}
+
+const struct audio_output_plugin jack_output_plugin = {
+ .name = "jack",
+ .test_default_device = mpd_jack_test_default_device,
+ .init = mpd_jack_init,
+ .finish = mpd_jack_finish,
+ .enable = mpd_jack_enable,
+ .disable = mpd_jack_disable,
+ .open = mpd_jack_open,
+ .play = mpd_jack_play,
+ .pause = mpd_jack_pause,
+ .close = mpd_jack_close,
+};
diff --git a/src/output/jack_plugin.c b/src/output/jack_plugin.c
deleted file mode 100644
index 0a0b8377f..000000000
--- a/src/output/jack_plugin.c
+++ /dev/null
@@ -1,451 +0,0 @@
-/*
- * 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_api.h"
-#include "config.h"
-
-#include <assert.h>
-
-#include <glib.h>
-#include <jack/jack.h>
-#include <jack/types.h>
-#include <jack/ringbuffer.h>
-
-#include <stdlib.h>
-#include <stdio.h>
-#include <sys/types.h>
-#include <unistd.h>
-#include <errno.h>
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "jack"
-
-static const size_t jack_sample_size = sizeof(jack_default_audio_sample_t);
-
-static const char *const port_names[2] = {
- "left", "right",
-};
-
-struct jack_data {
- const char *name;
-
- /* configuration */
- char *output_ports[2];
- int ringbuffer_size;
-
- /* the current audio format */
- struct audio_format audio_format;
-
- /* jack library stuff */
- jack_port_t *ports[2];
- jack_client_t *client;
- jack_ringbuffer_t *ringbuffer[2];
-
- bool shutdown;
-};
-
-/**
- * The quark used for GError.domain.
- */
-static inline GQuark
-jack_output_quark(void)
-{
- return g_quark_from_static_string("jack_output");
-}
-
-static void
-mpd_jack_client_free(struct jack_data *jd)
-{
- assert(jd != NULL);
-
- if (jd->client != NULL) {
- jack_deactivate(jd->client);
- jack_client_close(jd->client);
- jd->client = NULL;
- }
-
- for (unsigned i = 0; i < G_N_ELEMENTS(jd->ringbuffer); ++i) {
- if (jd->ringbuffer[i] != NULL) {
- jack_ringbuffer_free(jd->ringbuffer[i]);
- jd->ringbuffer[i] = NULL;
- }
- }
-}
-
-static void
-mpd_jack_free(struct jack_data *jd)
-{
- assert(jd != NULL);
-
- for (unsigned i = 0; i < G_N_ELEMENTS(jd->output_ports); ++i)
- g_free(jd->output_ports[i]);
-
- g_free(jd);
-}
-
-static void
-mpd_jack_finish(void *data)
-{
- struct jack_data *jd = data;
- mpd_jack_free(jd);
-}
-
-static int
-mpd_jack_process(jack_nframes_t nframes, void *arg)
-{
- struct jack_data *jd = (struct jack_data *) arg;
- jack_default_audio_sample_t *out;
- size_t available;
-
- if (nframes <= 0)
- return 0;
-
- for (unsigned i = 0; i < G_N_ELEMENTS(jd->ringbuffer); ++i) {
- available = jack_ringbuffer_read_space(jd->ringbuffer[i]);
- assert(available % jack_sample_size == 0);
- available /= jack_sample_size;
- if (available > nframes)
- available = nframes;
-
- out = jack_port_get_buffer(jd->ports[i], nframes);
- jack_ringbuffer_read(jd->ringbuffer[i],
- (char *)out,
- available * jack_sample_size);
-
- while (available < nframes)
- /* ringbuffer underrun, fill with silence */
- out[available++] = 0.0;
- }
-
- return 0;
-}
-
-static void
-mpd_jack_shutdown(void *arg)
-{
- struct jack_data *jd = (struct jack_data *) arg;
- jd->shutdown = true;
-}
-
-static void
-set_audioformat(struct jack_data *jd, struct audio_format *audio_format)
-{
- audio_format->sample_rate = jack_get_sample_rate(jd->client);
- audio_format->channels = 2;
-
- if (audio_format->bits != 16 && audio_format->bits != 24)
- audio_format->bits = 24;
-}
-
-static void
-mpd_jack_error(const char *msg)
-{
- g_warning("%s", msg);
-}
-
-#ifdef HAVE_JACK_SET_INFO_FUNCTION
-static void
-mpd_jack_info(const char *msg)
-{
- g_message("%s", msg);
-}
-#endif
-
-static void *
-mpd_jack_init(G_GNUC_UNUSED const struct audio_format *audio_format,
- const struct config_param *param, GError **error)
-{
- struct jack_data *jd;
- const char *value;
-
- jd = g_new(struct jack_data, 1);
- jd->name = config_get_block_string(param, "name", "mpd_jack");
-
- g_debug("mpd_jack_init (pid=%d)", getpid());
-
- value = config_get_block_string(param, "ports", NULL);
- if (value != NULL) {
- char **ports = g_strsplit(value, ",", 0);
-
- if (ports[0] == NULL || ports[1] == NULL || ports[2] != NULL) {
- g_set_error(error, jack_output_quark(), 0,
- "two port names expected in line %d",
- param->line);
- return NULL;
- }
-
- jd->output_ports[0] = ports[0];
- jd->output_ports[1] = ports[1];
-
- g_free(ports);
- } else {
- jd->output_ports[0] = NULL;
- jd->output_ports[1] = NULL;
- }
-
- jd->ringbuffer_size =
- config_get_block_unsigned(param, "ringbuffer_size", 32768);
-
- jack_set_error_function(mpd_jack_error);
-
-#ifdef HAVE_JACK_SET_INFO_FUNCTION
- jack_set_info_function(mpd_jack_info);
-#endif
-
- return jd;
-}
-
-static bool
-mpd_jack_test_default_device(void)
-{
- return true;
-}
-
-static bool
-mpd_jack_connect(struct jack_data *jd, GError **error)
-{
- const char *output_ports[2], **jports;
-
- for (unsigned i = 0; i < G_N_ELEMENTS(jd->ringbuffer); ++i)
- jd->ringbuffer[i] =
- jack_ringbuffer_create(jd->ringbuffer_size);
-
- jd->shutdown = false;
-
- if ((jd->client = jack_client_new(jd->name)) == NULL) {
- g_set_error(error, jack_output_quark(), 0,
- "Failed to connect to JACK server");
- return false;
- }
-
- jack_set_process_callback(jd->client, mpd_jack_process, jd);
- jack_on_shutdown(jd->client, mpd_jack_shutdown, jd);
-
- for (unsigned i = 0; i < G_N_ELEMENTS(jd->ports); ++i) {
- jd->ports[i] = jack_port_register(jd->client, port_names[i],
- JACK_DEFAULT_AUDIO_TYPE,
- JackPortIsOutput, 0);
- if (jd->ports[i] == NULL) {
- g_set_error(error, jack_output_quark(), 0,
- "Cannot register output port \"%s\"",
- port_names[i]);
- return false;
- }
- }
-
- if ( jack_activate(jd->client) ) {
- g_set_error(error, jack_output_quark(), 0,
- "cannot activate client");
- return false;
- }
-
- if (jd->output_ports[1] == NULL) {
- /* no output ports were configured - ask libjack for
- defaults */
- jports = jack_get_ports(jd->client, NULL, NULL,
- JackPortIsPhysical | JackPortIsInput);
- if (jports == NULL) {
- g_set_error(error, jack_output_quark(), 0,
- "no ports found");
- return false;
- }
-
- output_ports[0] = jports[0];
- output_ports[1] = jports[1] != NULL ? jports[1] : jports[0];
-
- g_debug("output_ports: %s %s", jports[0], jports[1]);
- } else {
- /* use the configured output ports */
-
- output_ports[0] = jd->output_ports[0];
- output_ports[1] = jd->output_ports[1];
-
- jports = NULL;
- }
-
- for (unsigned i = 0; i < G_N_ELEMENTS(jd->ports); ++i) {
- int ret;
-
- ret = jack_connect(jd->client, jack_port_name(jd->ports[i]),
- output_ports[i]);
- if (ret != 0) {
- g_set_error(error, jack_output_quark(), 0,
- "Not a valid JACK port: %s",
- output_ports[i]);
-
- if (jports != NULL)
- free(jports);
-
- return false;
- }
- }
-
- if (jports != NULL)
- free(jports);
-
- return true;
-}
-
-static bool
-mpd_jack_open(void *data, struct audio_format *audio_format, GError **error)
-{
- struct jack_data *jd = data;
-
- assert(jd != NULL);
-
- if (!mpd_jack_connect(jd, error)) {
- mpd_jack_client_free(jd);
- return false;
- }
-
- set_audioformat(jd, audio_format);
- jd->audio_format = *audio_format;
-
- return true;
-}
-
-static void
-mpd_jack_close(G_GNUC_UNUSED void *data)
-{
- struct jack_data *jd = data;
-
- mpd_jack_client_free(jd);
-}
-
-static void
-mpd_jack_cancel (G_GNUC_UNUSED void *data)
-{
-}
-
-static inline jack_default_audio_sample_t
-sample_16_to_jack(int16_t sample)
-{
- return sample / (jack_default_audio_sample_t)(1 << (16 - 1));
-}
-
-static void
-mpd_jack_write_samples_16(struct jack_data *jd, const int16_t *src,
- unsigned num_samples)
-{
- jack_default_audio_sample_t sample;
-
- while (num_samples-- > 0) {
- sample = sample_16_to_jack(*src++);
- jack_ringbuffer_write(jd->ringbuffer[0], (void*)&sample,
- sizeof(sample));
-
- sample = sample_16_to_jack(*src++);
- jack_ringbuffer_write(jd->ringbuffer[1], (void*)&sample,
- sizeof(sample));
- }
-}
-
-static inline jack_default_audio_sample_t
-sample_24_to_jack(int32_t sample)
-{
- return sample / (jack_default_audio_sample_t)(1 << (24 - 1));
-}
-
-static void
-mpd_jack_write_samples_24(struct jack_data *jd, const int32_t *src,
- unsigned num_samples)
-{
- jack_default_audio_sample_t sample;
-
- while (num_samples-- > 0) {
- sample = sample_24_to_jack(*src++);
- jack_ringbuffer_write(jd->ringbuffer[0], (void*)&sample,
- sizeof(sample));
-
- sample = sample_24_to_jack(*src++);
- jack_ringbuffer_write(jd->ringbuffer[1], (void*)&sample,
- sizeof(sample));
- }
-}
-
-static void
-mpd_jack_write_samples(struct jack_data *jd, const void *src,
- unsigned num_samples)
-{
- switch (jd->audio_format.bits) {
- case 16:
- mpd_jack_write_samples_16(jd, (const int16_t*)src,
- num_samples);
- break;
-
- case 24:
- mpd_jack_write_samples_24(jd, (const int32_t*)src,
- num_samples);
- break;
-
- default:
- assert(false);
- }
-}
-
-static size_t
-mpd_jack_play(void *data, const void *chunk, size_t size, GError **error)
-{
- struct jack_data *jd = data;
- const size_t frame_size = audio_format_frame_size(&jd->audio_format);
- size_t space = 0, space1;
-
- assert(size % frame_size == 0);
- size /= frame_size;
-
- while (true) {
- if (jd->shutdown) {
- g_set_error(error, jack_output_quark(), 0,
- "Refusing to play, because "
- "there is no client thread");
- return 0;
- }
-
- space = jack_ringbuffer_write_space(jd->ringbuffer[0]);
- space1 = jack_ringbuffer_write_space(jd->ringbuffer[1]);
- if (space > space1)
- /* send data symmetrically */
- space = space1;
-
- if (space >= jack_sample_size)
- break;
-
- /* XXX do something more intelligent to
- synchronize */
- g_usleep(1000);
- }
-
- space /= jack_sample_size;
- if (space < size)
- size = space;
-
- mpd_jack_write_samples(jd, chunk, size);
- return size * frame_size;
-}
-
-const struct audio_output_plugin jackPlugin = {
- .name = "jack",
- .test_default_device = mpd_jack_test_default_device,
- .init = mpd_jack_init,
- .finish = mpd_jack_finish,
- .open = mpd_jack_open,
- .play = mpd_jack_play,
- .cancel = mpd_jack_cancel,
- .close = mpd_jack_close,
-};
diff --git a/src/output/mvp_plugin.c b/src/output/mvp_plugin.c
index 96f9435a8..6cc8fa34e 100644
--- a/src/output/mvp_plugin.c
+++ b/src/output/mvp_plugin.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -17,12 +17,14 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
-/*
+/*
* Media MVP audio output based on code from MVPMC project:
* http://mvpmc.sourceforge.net/
*/
-#include "../output_api.h"
+#include "config.h"
+#include "output_api.h"
+#include "fd_util.h"
#include <glib.h>
@@ -115,7 +117,7 @@ mvp_output_test_default_device(void)
{
int fd;
- fd = open("/dev/adec_pcm", O_WRONLY);
+ fd = open_cloexec("/dev/adec_pcm", O_WRONLY, 0);
if (fd >= 0) {
close(fd);
@@ -170,19 +172,19 @@ mvp_set_pcm_params(struct mvp_data *md, struct audio_format *audio_format,
}
/* 0,1=24bit(24) , 2,3=16bit */
- switch (audio_format->bits) {
- case 16:
+ switch (audio_format->format) {
+ case SAMPLE_FORMAT_S16:
mix[1] = 2;
break;
- case 24:
+ case SAMPLE_FORMAT_S24_P32:
mix[1] = 0;
break;
default:
- g_debug("unsupported sample format %u - falling back to stereo",
- audio_format->bits);
- audio_format->bits = 16;
+ g_debug("unsupported sample format %s - falling back to 16 bit",
+ sample_format_to_string(audio_format->format));
+ audio_format->format = SAMPLE_FORMAT_S16;
mix[1] = 2;
break;
}
@@ -230,7 +232,8 @@ mvp_output_open(void *data, struct audio_format *audio_format, GError **error)
int mix[5] = { 0, 2, 7, 1, 0 };
bool success;
- if ((md->fd = open("/dev/adec_pcm", O_RDWR | O_NONBLOCK)) < 0) {
+ md->fd = open_cloexec("/dev/adec_pcm", O_RDWR | O_NONBLOCK, 0);
+ if (md->fd < 0) {
g_set_error(error, mvp_output_quark(), errno,
"Error opening /dev/adec_pcm: %s",
strerror(errno));
diff --git a/src/output/null_plugin.c b/src/output/null_plugin.c
index e9731b019..89abbd91f 100644
--- a/src/output/null_plugin.c
+++ b/src/output/null_plugin.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -17,8 +17,9 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
-#include "../output_api.h"
-#include "../timer.h"
+#include "config.h"
+#include "output_api.h"
+#include "timer.h"
#include <glib.h>
diff --git a/src/output/openal_plugin.c b/src/output/openal_plugin.c
new file mode 100644
index 000000000..767b3eb17
--- /dev/null
+++ b/src/output/openal_plugin.c
@@ -0,0 +1,277 @@
+/*
+ * 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 "timer.h"
+
+#include <glib.h>
+
+#ifndef HAVE_OSX
+#include <AL/al.h>
+#include <AL/alc.h>
+#else
+#include <OpenAL/al.h>
+#include <OpenAL/alc.h>
+#endif
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "openal"
+
+/* should be enough for buffer size = 2048 */
+#define NUM_BUFFERS 16
+
+struct openal_data {
+ const char *device_name;
+ ALCdevice *device;
+ ALCcontext *context;
+ Timer *timer;
+ ALuint buffers[NUM_BUFFERS];
+ int filled;
+ ALuint source;
+ ALenum format;
+ ALuint frequency;
+};
+
+static inline GQuark
+openal_output_quark(void)
+{
+ return g_quark_from_static_string("openal_output");
+}
+
+static ALenum
+openal_audio_format(struct audio_format *audio_format)
+{
+ switch (audio_format->format) {
+ case SAMPLE_FORMAT_S16:
+ if (audio_format->channels == 2)
+ return AL_FORMAT_STEREO16;
+ if (audio_format->channels == 1)
+ return AL_FORMAT_MONO16;
+ break;
+
+ case SAMPLE_FORMAT_S8:
+ if (audio_format->channels == 2)
+ return AL_FORMAT_STEREO8;
+ if (audio_format->channels == 1)
+ return AL_FORMAT_MONO8;
+ break;
+
+ default:
+ /* fall back to 16 bit */
+ audio_format->format = SAMPLE_FORMAT_S16;
+ if (audio_format->channels == 2)
+ return AL_FORMAT_STEREO16;
+ if (audio_format->channels == 1)
+ return AL_FORMAT_MONO16;
+ break;
+ }
+
+ return 0;
+}
+
+static bool
+openal_setup_context(struct openal_data *od,
+ GError **error)
+{
+ od->device = alcOpenDevice(od->device_name);
+
+ if (od->device == NULL) {
+ g_set_error(error, openal_output_quark(), 0,
+ "Error opening OpenAL device \"%s\"\n",
+ od->device_name);
+ return false;
+ }
+
+ od->context = alcCreateContext(od->device, NULL);
+
+ if (od->context == NULL) {
+ g_set_error(error, openal_output_quark(), 0,
+ "Error creating context for \"%s\"\n",
+ od->device_name);
+ alcCloseDevice(od->device);
+ return false;
+ }
+
+ 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)
+{
+ const char *device_name = config_get_block_string(param, "device", NULL);
+ struct openal_data *od;
+
+ if (device_name == NULL) {
+ device_name = alcGetString(NULL, ALC_DEFAULT_DEVICE_SPECIFIER);
+ }
+
+ od = g_new(struct openal_data, 1);
+ od->device_name = device_name;
+
+ return od;
+}
+
+static void
+openal_finish(void *data)
+{
+ struct openal_data *od = data;
+
+ g_free(od);
+}
+
+static bool
+openal_open(void *data, struct audio_format *audio_format,
+ GError **error)
+{
+ struct openal_data *od = data;
+
+ 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;
+ }
+
+ alcMakeContextCurrent(od->context);
+ alGenBuffers(NUM_BUFFERS, od->buffers);
+
+ if (alGetError() != AL_NO_ERROR) {
+ g_set_error(error, openal_output_quark(), 0,
+ "Failed to generate buffers");
+ return false;
+ }
+
+ alGenSources(1, &od->source);
+
+ if (alGetError() != AL_NO_ERROR) {
+ g_set_error(error, openal_output_quark(), 0,
+ "Failed to generate source");
+ alDeleteBuffers(NUM_BUFFERS, od->buffers);
+ return false;
+ }
+
+ od->filled = 0;
+ od->timer = timer_new(audio_format);
+ od->frequency = audio_format->sample_rate;
+
+ return true;
+}
+
+static void
+openal_close(void *data)
+{
+ struct openal_data *od = data;
+
+ timer_free(od->timer);
+ alcMakeContextCurrent(od->context);
+ alDeleteSources(1, &od->source);
+ alDeleteBuffers(NUM_BUFFERS, od->buffers);
+ alcDestroyContext(od->context);
+ alcCloseDevice(od->device);
+}
+
+static size_t
+openal_play(void *data, const void *chunk, size_t size,
+ G_GNUC_UNUSED GError **error)
+{
+ struct openal_data *od = data;
+ 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);
+ }
+
+ 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) {
+ alSourcePlay(od->source);
+ }
+
+ return size;
+}
+
+static void
+openal_cancel(void *data)
+{
+ struct openal_data *od = data;
+
+ od->filled = 0;
+ alcMakeContextCurrent(od->context);
+ alSourceStop(od->source);
+ openal_unqueue_buffers(od);
+}
+
+const struct audio_output_plugin openal_output_plugin = {
+ .name = "openal",
+ .init = openal_init,
+ .finish = openal_finish,
+ .open = openal_open,
+ .close = openal_close,
+ .play = openal_play,
+ .cancel = openal_cancel,
+};
diff --git a/src/output/oss_plugin.c b/src/output/oss_plugin.c
index a66bc0598..9261b423c 100644
--- a/src/output/oss_plugin.c
+++ b/src/output/oss_plugin.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -17,8 +17,10 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
-#include "../output_api.h"
+#include "config.h"
+#include "output_api.h"
#include "mixer_list.h"
+#include "fd_util.h"
#include <glib.h>
@@ -28,6 +30,7 @@
#include <errno.h>
#include <stdlib.h>
#include <unistd.h>
+#include <assert.h>
#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "oss"
@@ -38,33 +41,24 @@
# include <sys/soundcard.h>
#endif /* !(defined(__OpenBSD__) || defined(__NetBSD__) */
-#if G_BYTE_ORDER == G_BIG_ENDIAN
-# define AFMT_S16_MPD AFMT_S16_BE
-#else
-# define AFMT_S16_MPD AFMT_S16_LE
+/* We got bug reports from FreeBSD users who said that the two 24 bit
+ formats generate white noise on FreeBSD, but 32 bit works. This is
+ a workaround until we know what exactly is expected by the kernel
+ audio drivers. */
+#ifndef __linux__
+#undef AFMT_S24_PACKED
+#undef AFMT_S24_NE
#endif
struct oss_data {
int fd;
const char *device;
- struct audio_format audio_format;
- int bitFormat;
- int *supported[3];
- unsigned num_supported[3];
- int *unsupported[3];
- unsigned num_unsupported[3];
-};
-
-enum oss_support {
- OSS_SUPPORTED = 1,
- OSS_UNSUPPORTED = 0,
- OSS_UNKNOWN = -1,
-};
-enum oss_param {
- OSS_RATE = 0,
- OSS_CHANNELS = 1,
- OSS_BITS = 2,
+ /**
+ * The current input audio format. This is needed to reopen
+ * the device after cancel().
+ */
+ struct audio_format audio_format;
};
/**
@@ -76,188 +70,6 @@ oss_output_quark(void)
return g_quark_from_static_string("oss_output");
}
-static enum oss_param
-oss_param_from_ioctl(unsigned param)
-{
- enum oss_param idx = OSS_RATE;
-
- switch (param) {
- case SNDCTL_DSP_SPEED:
- idx = OSS_RATE;
- break;
- case SNDCTL_DSP_CHANNELS:
- idx = OSS_CHANNELS;
- break;
- case SNDCTL_DSP_SAMPLESIZE:
- idx = OSS_BITS;
- break;
- }
-
- return idx;
-}
-
-static bool
-oss_find_supported_param(struct oss_data *od, unsigned param, int val)
-{
- enum oss_param idx = oss_param_from_ioctl(param);
-
- for (unsigned i = 0; i < od->num_supported[idx]; i++)
- if (od->supported[idx][i] == val)
- return true;
-
- return false;
-}
-
-static bool
-oss_can_convert(int idx, int val)
-{
- switch (idx) {
- case OSS_BITS:
- if (val != 16)
- return false;
- break;
- case OSS_CHANNELS:
- if (val != 2)
- return false;
- break;
- }
-
- return true;
-}
-
-static int
-oss_get_supported_param(struct oss_data *od, unsigned param, int val)
-{
- enum oss_param idx = oss_param_from_ioctl(param);
- int ret = -1;
- int least = val;
- int diff;
-
- for (unsigned i = 0; i < od->num_supported[idx]; i++) {
- diff = od->supported[idx][i] - val;
- if (diff < 0)
- diff = -diff;
- if (diff < least) {
- if (!oss_can_convert(idx, od->supported[idx][i]))
- continue;
-
- least = diff;
- ret = od->supported[idx][i];
- }
- }
-
- return ret;
-}
-
-static bool
-oss_find_unsupported_param(struct oss_data *od, unsigned param, int val)
-{
- enum oss_param idx = oss_param_from_ioctl(param);
-
- for (unsigned i = 0; i < od->num_unsupported[idx]; i++) {
- if (od->unsupported[idx][i] == val)
- return true;
- }
-
- return false;
-}
-
-static void
-oss_add_supported_param(struct oss_data *od, unsigned param, int val)
-{
- enum oss_param idx = oss_param_from_ioctl(param);
-
- od->num_supported[idx]++;
- od->supported[idx] = g_realloc(od->supported[idx],
- od->num_supported[idx] * sizeof(int));
- od->supported[idx][od->num_supported[idx] - 1] = val;
-}
-
-static void
-oss_add_unsupported_param(struct oss_data *od, unsigned param, int val)
-{
- enum oss_param idx = oss_param_from_ioctl(param);
-
- od->num_unsupported[idx]++;
- od->unsupported[idx] = g_realloc(od->unsupported[idx],
- od->num_unsupported[idx] *
- sizeof(int));
- od->unsupported[idx][od->num_unsupported[idx] - 1] = val;
-}
-
-static void
-oss_remove_supported_param(struct oss_data *od, unsigned param, int val)
-{
- unsigned j = 0;
- enum oss_param idx = oss_param_from_ioctl(param);
-
- for (unsigned i = 0; i < od->num_supported[idx] - 1; i++) {
- if (od->supported[idx][i] == val)
- j = 1;
- od->supported[idx][i] = od->supported[idx][i + j];
- }
-
- od->num_supported[idx]--;
- od->supported[idx] = g_realloc(od->supported[idx],
- od->num_supported[idx] * sizeof(int));
-}
-
-static void
-oss_remove_unsupported_param(struct oss_data *od, unsigned param, int val)
-{
- unsigned j = 0;
- enum oss_param idx = oss_param_from_ioctl(param);
-
- for (unsigned i = 0; i < od->num_unsupported[idx] - 1; i++) {
- if (od->unsupported[idx][i] == val)
- j = 1;
- od->unsupported[idx][i] = od->unsupported[idx][i + j];
- }
-
- od->num_unsupported[idx]--;
- od->unsupported[idx] = g_realloc(od->unsupported[idx],
- od->num_unsupported[idx] *
- sizeof(int));
-}
-
-static enum oss_support
-oss_param_is_supported(struct oss_data *od, unsigned param, int val)
-{
- if (oss_find_supported_param(od, param, val))
- return OSS_SUPPORTED;
- if (oss_find_unsupported_param(od, param, val))
- return OSS_UNSUPPORTED;
- return OSS_UNKNOWN;
-}
-
-static void
-oss_set_supported(struct oss_data *od, unsigned param, int val)
-{
- enum oss_support supported = oss_param_is_supported(od, param, val);
-
- if (supported == OSS_SUPPORTED)
- return;
-
- if (supported == OSS_UNSUPPORTED)
- oss_remove_unsupported_param(od, param, val);
-
- oss_add_supported_param(od, param, val);
-}
-
-static void
-oss_set_unsupported(struct oss_data *od, unsigned param, int val)
-{
- enum oss_support supported = oss_param_is_supported(od, param, val);
-
- if (supported == OSS_UNSUPPORTED)
- return;
-
- if (supported == OSS_SUPPORTED)
- oss_remove_supported_param(od, param, val);
-
- oss_add_unsupported_param(od, param, val);
-}
-
static struct oss_data *
oss_data_new(void)
{
@@ -266,38 +78,12 @@ oss_data_new(void)
ret->device = NULL;
ret->fd = -1;
- ret->supported[OSS_RATE] = NULL;
- ret->supported[OSS_CHANNELS] = NULL;
- ret->supported[OSS_BITS] = NULL;
- ret->unsupported[OSS_RATE] = NULL;
- ret->unsupported[OSS_CHANNELS] = NULL;
- ret->unsupported[OSS_BITS] = NULL;
-
- ret->num_supported[OSS_RATE] = 0;
- ret->num_supported[OSS_CHANNELS] = 0;
- ret->num_supported[OSS_BITS] = 0;
- ret->num_unsupported[OSS_RATE] = 0;
- ret->num_unsupported[OSS_CHANNELS] = 0;
- ret->num_unsupported[OSS_BITS] = 0;
-
- oss_set_supported(ret, SNDCTL_DSP_SPEED, 48000);
- oss_set_supported(ret, SNDCTL_DSP_SPEED, 44100);
- oss_set_supported(ret, SNDCTL_DSP_CHANNELS, 2);
- oss_set_supported(ret, SNDCTL_DSP_SAMPLESIZE, 16);
-
return ret;
}
static void
oss_data_free(struct oss_data *od)
{
- g_free(od->supported[OSS_RATE]);
- g_free(od->supported[OSS_CHANNELS]);
- g_free(od->supported[OSS_BITS]);
- g_free(od->unsupported[OSS_RATE]);
- g_free(od->unsupported[OSS_CHANNELS]);
- g_free(od->unsupported[OSS_BITS]);
-
g_free(od);
}
@@ -343,7 +129,9 @@ oss_output_test_default_device(void)
int fd, i;
for (i = G_N_ELEMENTS(default_devices); --i >= 0; ) {
- if ((fd = open(default_devices[i], O_WRONLY)) >= 0) {
+ fd = open_cloexec(default_devices[i], O_WRONLY, 0);
+
+ if (fd >= 0) {
close(fd);
return true;
}
@@ -419,113 +207,398 @@ oss_output_finish(void *data)
oss_data_free(od);
}
-static int
-oss_set_param(struct oss_data *od, unsigned param, int *value)
+static void
+oss_close(struct oss_data *od)
{
- int val = *value;
- int copy;
- enum oss_support supported = oss_param_is_supported(od, param, val);
-
- do {
- if (supported == OSS_UNSUPPORTED) {
- val = oss_get_supported_param(od, param, val);
- if (copy < 0)
- return -1;
- }
- copy = val;
- if (ioctl(od->fd, param, &copy)) {
- oss_set_unsupported(od, param, val);
- supported = OSS_UNSUPPORTED;
- } else {
- if (supported == OSS_UNKNOWN) {
- oss_set_supported(od, param, val);
- supported = OSS_SUPPORTED;
- }
- val = copy;
- }
- } while (supported == OSS_UNSUPPORTED);
+ if (od->fd >= 0)
+ close(od->fd);
+ od->fd = -1;
+}
- *value = val;
+/**
+ * A tri-state type for oss_try_ioctl().
+ */
+enum oss_setup_result {
+ SUCCESS,
+ ERROR,
+ UNSUPPORTED,
+};
- return 0;
+/**
+ * Invoke an ioctl on the OSS file descriptor. On success, SUCCESS is
+ * returned. If the parameter is not supported, UNSUPPORTED is
+ * returned. Any other failure returns ERROR and allocates a GError.
+ */
+static enum oss_setup_result
+oss_try_ioctl_r(int fd, unsigned long request, int *value_r,
+ const char *msg, GError **error_r)
+{
+ assert(fd >= 0);
+ assert(value_r != NULL);
+ assert(msg != NULL);
+ assert(error_r == NULL || *error_r == NULL);
+
+ int ret = ioctl(fd, request, value_r);
+ if (ret >= 0)
+ return SUCCESS;
+
+ if (errno == EINVAL)
+ return UNSUPPORTED;
+
+ g_set_error(error_r, oss_output_quark(), errno,
+ "%s: %s", msg, g_strerror(errno));
+ return ERROR;
}
-static void
-oss_close(struct oss_data *od)
+/**
+ * Invoke an ioctl on the OSS file descriptor. On success, SUCCESS is
+ * returned. If the parameter is not supported, UNSUPPORTED is
+ * returned. Any other failure returns ERROR and allocates a GError.
+ */
+static enum oss_setup_result
+oss_try_ioctl(int fd, unsigned long request, int value,
+ const char *msg, GError **error_r)
{
- if (od->fd >= 0)
- while (close(od->fd) && errno == EINTR) ;
- od->fd = -1;
+ return oss_try_ioctl_r(fd, request, &value, msg, error_r);
}
/**
- * Sets up the OSS device which was opened before.
+ * Set up the channel number, and attempts to find alternatives if the
+ * specified number is not supported.
*/
static bool
-oss_setup(struct oss_data *od, GError **error)
+oss_setup_channels(int fd, struct audio_format *audio_format, GError **error_r)
{
- int tmp;
-
- tmp = od->audio_format.channels;
- if (oss_set_param(od, SNDCTL_DSP_CHANNELS, &tmp)) {
- g_set_error(error, oss_output_quark(), errno,
- "OSS device \"%s\" does not support %u channels: %s",
- od->device, od->audio_format.channels,
- strerror(errno));
+ const char *const msg = "Failed to set channel count";
+ int channels = audio_format->channels;
+ enum oss_setup_result result =
+ oss_try_ioctl_r(fd, SNDCTL_DSP_CHANNELS, &channels, msg, error_r);
+ switch (result) {
+ case SUCCESS:
+ if (!audio_valid_channel_count(channels))
+ break;
+
+ audio_format->channels = channels;
+ return true;
+
+ case ERROR:
return false;
+
+ case UNSUPPORTED:
+ break;
}
- od->audio_format.channels = tmp;
- tmp = od->audio_format.sample_rate;
- if (oss_set_param(od, SNDCTL_DSP_SPEED, &tmp)) {
- g_set_error(error, oss_output_quark(), errno,
- "OSS device \"%s\" does not support %u Hz audio: %s",
- od->device, od->audio_format.sample_rate,
- strerror(errno));
- return false;
+ for (unsigned i = 1; i < 2; ++i) {
+ if (i == audio_format->channels)
+ /* don't try that again */
+ continue;
+
+ channels = i;
+ result = oss_try_ioctl_r(fd, SNDCTL_DSP_CHANNELS, &channels,
+ msg, error_r);
+ switch (result) {
+ case SUCCESS:
+ if (!audio_valid_channel_count(channels))
+ break;
+
+ audio_format->channels = channels;
+ return true;
+
+ case ERROR:
+ return false;
+
+ case UNSUPPORTED:
+ break;
+ }
}
- od->audio_format.sample_rate = tmp;
- switch (od->audio_format.bits) {
- case 8:
- tmp = AFMT_S8;
- break;
- case 16:
- tmp = AFMT_S16_MPD;
+ g_set_error(error_r, oss_output_quark(), EINVAL, "%s", msg);
+ return false;
+}
+
+/**
+ * Set up the sample rate, and attempts to find alternatives if the
+ * specified sample rate is not supported.
+ */
+static bool
+oss_setup_sample_rate(int fd, struct audio_format *audio_format,
+ GError **error_r)
+{
+ const char *const msg = "Failed to set sample rate";
+ int sample_rate = audio_format->sample_rate;
+ enum oss_setup_result result =
+ oss_try_ioctl_r(fd, SNDCTL_DSP_SPEED, &sample_rate,
+ msg, error_r);
+ switch (result) {
+ case SUCCESS:
+ if (!audio_valid_sample_rate(sample_rate))
+ break;
+
+ audio_format->sample_rate = sample_rate;
+ return true;
+
+ case ERROR:
+ return false;
+
+ case UNSUPPORTED:
break;
+ }
+
+ static const int sample_rates[] = { 48000, 44100, 0 };
+ for (unsigned i = 0; sample_rates[i] != 0; ++i) {
+ sample_rate = sample_rates[i];
+ if (sample_rate == (int)audio_format->sample_rate)
+ continue;
+
+ result = oss_try_ioctl_r(fd, SNDCTL_DSP_SPEED, &sample_rate,
+ msg, error_r);
+ switch (result) {
+ case SUCCESS:
+ if (!audio_valid_sample_rate(sample_rate))
+ break;
+
+ audio_format->sample_rate = sample_rate;
+ return true;
+
+ case ERROR:
+ return false;
+
+ case UNSUPPORTED:
+ break;
+ }
+ }
+
+ g_set_error(error_r, oss_output_quark(), EINVAL, "%s", msg);
+ return false;
+}
+
+/**
+ * Convert a MPD sample format to its OSS counterpart. Returns
+ * AFMT_QUERY if there is no direct counterpart.
+ */
+static int
+sample_format_to_oss(enum sample_format format)
+{
+ switch (format) {
+ case SAMPLE_FORMAT_UNDEFINED:
+ return AFMT_QUERY;
+
+ case SAMPLE_FORMAT_S8:
+ return AFMT_S8;
+
+ 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;
+#else
+ return AFMT_QUERY;
+#endif
+
+ case SAMPLE_FORMAT_S32:
+#ifdef AFMT_S32_NE
+ return AFMT_S32_NE;
+#else
+ return AFMT_QUERY;
+#endif
+ }
+
+ return AFMT_QUERY;
+}
+
+/**
+ * Convert an OSS sample format to its MPD counterpart. Returns
+ * SAMPLE_FORMAT_UNDEFINED if there is no direct counterpart.
+ */
+static enum sample_format
+sample_format_from_oss(int format)
+{
+ switch (format) {
+ case AFMT_S8:
+ return SAMPLE_FORMAT_S8;
+
+ case AFMT_S16_NE:
+ return SAMPLE_FORMAT_S16;
+
+#ifdef AFMT_S24_PACKED
+ case AFMT_S24_PACKED:
+ return SAMPLE_FORMAT_S24;
+#endif
+
+#ifdef AFMT_S24_NE
+ case AFMT_S24_NE:
+ return SAMPLE_FORMAT_S24_P32;
+#endif
+
+#ifdef AFMT_S32_NE
+ case AFMT_S32_NE:
+ return SAMPLE_FORMAT_S32;
+#endif
default:
- /* not supported by OSS - fall back to 16 bit */
- od->audio_format.bits = 16;
- tmp = AFMT_S16_MPD;
- break;
+ return SAMPLE_FORMAT_UNDEFINED;
}
+}
- if (oss_set_param(od, SNDCTL_DSP_SAMPLESIZE, &tmp)) {
- g_set_error(error, oss_output_quark(), errno,
- "OSS device \"%s\" does not support %u bit audio: %s",
- od->device, tmp, strerror(errno));
+/**
+ * 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,
+ 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;
+ 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:
return false;
+
+ case UNSUPPORTED:
+ break;
}
- return true;
+ /* 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 */
+ };
+
+ for (unsigned i = 0; sample_formats[i] != SAMPLE_FORMAT_UNDEFINED; ++i) {
+ mpd_format = sample_formats[i];
+ if (mpd_format == audio_format->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);
+ 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:
+ return false;
+
+ case UNSUPPORTED:
+ break;
+ }
+ }
+
+ g_set_error(error_r, oss_output_quark(), EINVAL, "%s", msg);
+ return false;
}
+/**
+ * Sets up the OSS device which was opened before.
+ */
static bool
-oss_open(struct oss_data *od, GError **error)
+oss_setup(struct oss_data *od, struct audio_format *audio_format,
+ GError **error_r)
{
- bool success;
+ 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);
+}
- if ((od->fd = open(od->device, O_WRONLY)) < 0) {
- g_set_error(error, oss_output_quark(), errno,
+/**
+ * Reopen the device with the saved audio_format, without any probing.
+ */
+static bool
+oss_reopen(struct oss_data *od, GError **error_r)
+{
+ assert(od->fd < 0);
+
+ od->fd = open_cloexec(od->device, O_WRONLY, 0);
+ if (od->fd < 0) {
+ g_set_error(error_r, oss_output_quark(), errno,
"Error opening OSS device \"%s\": %s",
od->device, strerror(errno));
return false;
}
- success = oss_setup(od, error);
- if (!success) {
+ enum oss_setup_result result;
+
+ const char *const msg1 = "Failed to set channel count";
+ result = oss_try_ioctl(od->fd, SNDCTL_DSP_CHANNELS,
+ od->audio_format.channels, msg1, error_r);
+ if (result != SUCCESS) {
+ oss_close(od);
+ if (result == UNSUPPORTED)
+ g_set_error(error_r, oss_output_quark(), EINVAL,
+ "%s", msg1);
+ return false;
+ }
+
+ const char *const msg2 = "Failed to set sample rate";
+ result = oss_try_ioctl(od->fd, SNDCTL_DSP_SPEED,
+ od->audio_format.sample_rate, msg2, error_r);
+ if (result != SUCCESS) {
+ oss_close(od);
+ if (result == UNSUPPORTED)
+ g_set_error(error_r, oss_output_quark(), EINVAL,
+ "%s", msg2);
+ return false;
+ }
+
+ 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),
+ msg3, error_r);
+ if (result != SUCCESS) {
oss_close(od);
+ if (result == UNSUPPORTED)
+ g_set_error(error_r, oss_output_quark(), EINVAL,
+ "%s", msg3);
return false;
}
@@ -535,18 +608,23 @@ oss_open(struct oss_data *od, GError **error)
static bool
oss_output_open(void *data, struct audio_format *audio_format, GError **error)
{
- bool ret;
struct oss_data *od = data;
- od->audio_format = *audio_format;
-
- ret = oss_open(od, error);
- if (!ret)
+ 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));
return false;
+ }
- *audio_format = od->audio_format;
+ if (!oss_setup(od, audio_format, error)) {
+ oss_close(od);
+ return false;
+ }
- return ret;
+ od->audio_format = *audio_format;
+ return true;
}
static void
@@ -575,7 +653,7 @@ oss_output_play(void *data, const void *chunk, size_t size, GError **error)
ssize_t ret;
/* reopen the device since it was closed by dropBufferedAudio */
- if (od->fd < 0 && !oss_open(od, error))
+ if (od->fd < 0 && !oss_reopen(od, error))
return 0;
while (true) {
@@ -601,5 +679,6 @@ const struct audio_output_plugin oss_output_plugin = {
.close = oss_output_close,
.play = oss_output_play,
.cancel = oss_output_cancel,
- .mixer_plugin = &oss_mixer,
+
+ .mixer_plugin = &oss_mixer_plugin,
};
diff --git a/src/output/osx_plugin.c b/src/output/osx_plugin.c
index 04173bf79..ce82656bd 100644
--- a/src/output/osx_plugin.c
+++ b/src/output/osx_plugin.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -17,7 +17,8 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
-#include "../output_api.h"
+#include "config.h"
+#include "output_api.h"
#include <glib.h>
#include <AudioUnit/AudioUnit.h>
@@ -165,9 +166,6 @@ osx_output_open(void *data, struct audio_format *audio_format, GError **error)
OSStatus status;
ComponentResult result;
- if (audio_format->bits > 16)
- audio_format->bits = 16;
-
desc.componentType = kAudioUnitType_Output;
desc.componentSubType = kAudioUnitSubType_DefaultOutput;
desc.componentManufacturer = kAudioUnitManufacturer_Apple;
@@ -216,6 +214,22 @@ osx_output_open(void *data, struct audio_format *audio_format, GError **error)
stream_description.mSampleRate = audio_format->sample_rate;
stream_description.mFormatID = kAudioFormatLinearPCM;
stream_description.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger;
+
+ 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
@@ -225,7 +239,6 @@ osx_output_open(void *data, struct audio_format *audio_format, GError **error)
stream_description.mFramesPerPacket = 1;
stream_description.mBytesPerFrame = stream_description.mBytesPerPacket;
stream_description.mChannelsPerFrame = audio_format->channels;
- stream_description.mBitsPerChannel = audio_format->bits;
result = AudioUnitSetProperty(od->au, kAudioUnitProperty_StreamFormat,
kAudioUnitScope_Input, 0,
diff --git a/src/output/pipe_output_plugin.c b/src/output/pipe_output_plugin.c
index 610ad9e8d..1d1aec7b1 100644
--- a/src/output/pipe_output_plugin.c
+++ b/src/output/pipe_output_plugin.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -17,6 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "output_api.h"
#include <stdio.h>
diff --git a/src/output/pulse_output_plugin.c b/src/output/pulse_output_plugin.c
new file mode 100644
index 000000000..d29fbd705
--- /dev/null
+++ b/src/output/pulse_output_plugin.c
@@ -0,0 +1,825 @@
+/*
+ * 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 "pulse_output_plugin.h"
+#include "output_api.h"
+#include "mixer_list.h"
+#include "mixer/pulse_mixer_plugin.h"
+
+#include <glib.h>
+
+#include <pulse/thread-mainloop.h>
+#include <pulse/context.h>
+#include <pulse/stream.h>
+#include <pulse/introspect.h>
+#include <pulse/subscribe.h>
+#include <pulse/error.h>
+
+#include <assert.h>
+
+#define MPD_PULSE_NAME "Music Player Daemon"
+
+/**
+ * The quark used for GError.domain.
+ */
+static inline GQuark
+pulse_output_quark(void)
+{
+ return g_quark_from_static_string("pulse_output");
+}
+
+void
+pulse_output_set_mixer(struct pulse_output *po, struct pulse_mixer *pm)
+{
+ assert(po != NULL);
+ assert(po->mixer == NULL);
+ assert(pm != NULL);
+
+ po->mixer = pm;
+
+ if (po->mainloop == NULL)
+ return;
+
+ pa_threaded_mainloop_lock(po->mainloop);
+
+ if (po->context != NULL &&
+ pa_context_get_state(po->context) == PA_CONTEXT_READY) {
+ pulse_mixer_on_connect(pm, po->context);
+
+ if (po->stream != NULL &&
+ pa_stream_get_state(po->stream) == PA_STREAM_READY)
+ pulse_mixer_on_change(pm, po->context, po->stream);
+ }
+
+ pa_threaded_mainloop_unlock(po->mainloop);
+}
+
+void
+pulse_output_clear_mixer(struct pulse_output *po, struct pulse_mixer *pm)
+{
+ assert(po != NULL);
+ assert(pm != NULL);
+ assert(po->mixer == pm);
+
+ po->mixer = NULL;
+}
+
+bool
+pulse_output_set_volume(struct pulse_output *po,
+ const struct pa_cvolume *volume, GError **error_r)
+{
+ pa_operation *o;
+
+ if (po->context == NULL || po->stream == NULL ||
+ pa_stream_get_state(po->stream) != PA_STREAM_READY) {
+ g_set_error(error_r, pulse_output_quark(), 0, "disconnected");
+ return false;
+ }
+
+ o = pa_context_set_sink_input_volume(po->context,
+ pa_stream_get_index(po->stream),
+ volume, NULL, NULL);
+ if (o == NULL) {
+ g_set_error(error_r, pulse_output_quark(), 0,
+ "failed to set PulseAudio volume: %s",
+ pa_strerror(pa_context_errno(po->context)));
+ return false;
+ }
+
+ pa_operation_unref(o);
+ return true;
+}
+
+/**
+ * \brief waits for a pulseaudio operation to finish, frees it and
+ * unlocks the mainloop
+ * \param operation the operation to wait for
+ * \return true if operation has finished normally (DONE state),
+ * false otherwise
+ */
+static bool
+pulse_wait_for_operation(struct pa_threaded_mainloop *mainloop,
+ struct pa_operation *operation)
+{
+ pa_operation_state_t state;
+
+ assert(mainloop != NULL);
+ assert(operation != NULL);
+
+ state = pa_operation_get_state(operation);
+ while (state == PA_OPERATION_RUNNING) {
+ pa_threaded_mainloop_wait(mainloop);
+ state = pa_operation_get_state(operation);
+ }
+
+ pa_operation_unref(operation);
+
+ return state == PA_OPERATION_DONE;
+}
+
+/**
+ * Callback function for stream operation. It just sends a signal to
+ * the caller thread, to wake pulse_wait_for_operation() up.
+ */
+static void
+pulse_output_stream_success_cb(G_GNUC_UNUSED pa_stream *s,
+ G_GNUC_UNUSED int success, void *userdata)
+{
+ struct pulse_output *po = userdata;
+
+ pa_threaded_mainloop_signal(po->mainloop, 0);
+}
+
+static void
+pulse_output_context_state_cb(struct pa_context *context, void *userdata)
+{
+ struct pulse_output *po = userdata;
+
+ switch (pa_context_get_state(context)) {
+ case PA_CONTEXT_READY:
+ if (po->mixer != NULL)
+ pulse_mixer_on_connect(po->mixer, context);
+
+ pa_threaded_mainloop_signal(po->mainloop, 0);
+ break;
+
+ case PA_CONTEXT_TERMINATED:
+ case PA_CONTEXT_FAILED:
+ if (po->mixer != NULL)
+ pulse_mixer_on_disconnect(po->mixer);
+
+ /* the caller thread might be waiting for these
+ states */
+ pa_threaded_mainloop_signal(po->mainloop, 0);
+ break;
+
+ case PA_CONTEXT_UNCONNECTED:
+ case PA_CONTEXT_CONNECTING:
+ case PA_CONTEXT_AUTHORIZING:
+ case PA_CONTEXT_SETTING_NAME:
+ break;
+ }
+}
+
+static void
+pulse_output_subscribe_cb(pa_context *context,
+ pa_subscription_event_type_t t,
+ uint32_t idx, void *userdata)
+{
+ struct pulse_output *po = userdata;
+ pa_subscription_event_type_t facility
+ = t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK;
+ pa_subscription_event_type_t type
+ = t & PA_SUBSCRIPTION_EVENT_TYPE_MASK;
+
+ if (po->mixer != NULL &&
+ facility == PA_SUBSCRIPTION_EVENT_SINK_INPUT &&
+ po->stream != NULL &&
+ pa_stream_get_state(po->stream) == PA_STREAM_READY &&
+ idx == pa_stream_get_index(po->stream) &&
+ (type == PA_SUBSCRIPTION_EVENT_NEW ||
+ type == PA_SUBSCRIPTION_EVENT_CHANGE))
+ pulse_mixer_on_change(po->mixer, context, po->stream);
+}
+
+/**
+ * Attempt to connect asynchronously to the PulseAudio server.
+ *
+ * @return true on success, false on error
+ */
+static bool
+pulse_output_connect(struct pulse_output *po, GError **error_r)
+{
+ int error;
+
+ error = pa_context_connect(po->context, po->server,
+ (pa_context_flags_t)0, NULL);
+ if (error < 0) {
+ g_set_error(error_r, pulse_output_quark(), 0,
+ "pa_context_connect() has failed: %s",
+ pa_strerror(pa_context_errno(po->context)));
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * Create, set up and connect a context.
+ *
+ * @return true on success, false on error
+ */
+static bool
+pulse_output_setup_context(struct pulse_output *po, GError **error_r)
+{
+ po->context = pa_context_new(pa_threaded_mainloop_get_api(po->mainloop),
+ MPD_PULSE_NAME);
+ if (po->context == NULL) {
+ g_set_error(error_r, pulse_output_quark(), 0,
+ "pa_context_new() has failed");
+ return false;
+ }
+
+ pa_context_set_state_callback(po->context,
+ pulse_output_context_state_cb, po);
+ pa_context_set_subscribe_callback(po->context,
+ pulse_output_subscribe_cb, po);
+
+ if (!pulse_output_connect(po, error_r)) {
+ pa_context_unref(po->context);
+ po->context = NULL;
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * Frees and clears the context.
+ */
+static void
+pulse_output_delete_context(struct pulse_output *po)
+{
+ pa_context_disconnect(po->context);
+ pa_context_unref(po->context);
+ po->context = NULL;
+}
+
+static void *
+pulse_output_init(G_GNUC_UNUSED const struct audio_format *audio_format,
+ const struct config_param *param,
+ G_GNUC_UNUSED GError **error_r)
+{
+ struct pulse_output *po;
+
+ g_setenv("PULSE_PROP_media.role", "music", true);
+
+ po = g_new(struct pulse_output, 1);
+ 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);
+
+ po->mixer = NULL;
+ po->mainloop = NULL;
+ po->context = NULL;
+ po->stream = NULL;
+
+ return po;
+}
+
+static void
+pulse_output_finish(void *data)
+{
+ struct pulse_output *po = data;
+
+ g_free(po);
+}
+
+static bool
+pulse_output_enable(void *data, GError **error_r)
+{
+ struct pulse_output *po = data;
+
+ assert(po->mainloop == NULL);
+ assert(po->context == NULL);
+
+ /* create the libpulse mainloop and start the thread */
+
+ po->mainloop = pa_threaded_mainloop_new();
+ if (po->mainloop == NULL) {
+ g_free(po);
+
+ g_set_error(error_r, pulse_output_quark(), 0,
+ "pa_threaded_mainloop_new() has failed");
+ return false;
+ }
+
+ pa_threaded_mainloop_lock(po->mainloop);
+
+ if (pa_threaded_mainloop_start(po->mainloop) < 0) {
+ pa_threaded_mainloop_unlock(po->mainloop);
+ pa_threaded_mainloop_free(po->mainloop);
+ po->mainloop = NULL;
+
+ g_set_error(error_r, pulse_output_quark(), 0,
+ "pa_threaded_mainloop_start() has failed");
+ return false;
+ }
+
+ pa_threaded_mainloop_unlock(po->mainloop);
+
+ /* create the libpulse context and connect it */
+
+ pa_threaded_mainloop_lock(po->mainloop);
+
+ if (!pulse_output_setup_context(po, error_r)) {
+ pa_threaded_mainloop_unlock(po->mainloop);
+ pa_threaded_mainloop_stop(po->mainloop);
+ pa_threaded_mainloop_free(po->mainloop);
+ po->mainloop = NULL;
+ return false;
+ }
+
+ pa_threaded_mainloop_unlock(po->mainloop);
+
+ return true;
+}
+
+static void
+pulse_output_disable(void *data)
+{
+ struct pulse_output *po = data;
+
+ pa_threaded_mainloop_stop(po->mainloop);
+ if (po->context != NULL)
+ pulse_output_delete_context(po);
+ pa_threaded_mainloop_free(po->mainloop);
+ po->mainloop = NULL;
+}
+
+/**
+ * Check if the context is (already) connected, and waits if not. If
+ * the context has been disconnected, retry to connect.
+ *
+ * @return true on success, false on error
+ */
+static bool
+pulse_output_wait_connection(struct pulse_output *po, GError **error_r)
+{
+ pa_context_state_t state;
+
+ pa_threaded_mainloop_lock(po->mainloop);
+
+ if (po->context == NULL && !pulse_output_setup_context(po, error_r))
+ return false;
+
+ while (true) {
+ state = pa_context_get_state(po->context);
+ switch (state) {
+ case PA_CONTEXT_READY:
+ /* nothing to do */
+ pa_threaded_mainloop_unlock(po->mainloop);
+ return true;
+
+ case PA_CONTEXT_UNCONNECTED:
+ case PA_CONTEXT_TERMINATED:
+ case PA_CONTEXT_FAILED:
+ /* failure */
+ g_set_error(error_r, pulse_output_quark(), 0,
+ "failed to connect: %s",
+ pa_strerror(pa_context_errno(po->context)));
+ pulse_output_delete_context(po);
+ pa_threaded_mainloop_unlock(po->mainloop);
+ return false;
+
+ case PA_CONTEXT_CONNECTING:
+ case PA_CONTEXT_AUTHORIZING:
+ case PA_CONTEXT_SETTING_NAME:
+ /* wait some more */
+ pa_threaded_mainloop_wait(po->mainloop);
+ break;
+ }
+ }
+}
+
+static void
+pulse_output_stream_state_cb(pa_stream *stream, void *userdata)
+{
+ struct pulse_output *po = userdata;
+
+ switch (pa_stream_get_state(stream)) {
+ case PA_STREAM_READY:
+ if (po->mixer != NULL)
+ pulse_mixer_on_change(po->mixer, po->context, stream);
+
+ pa_threaded_mainloop_signal(po->mainloop, 0);
+ break;
+
+ case PA_STREAM_FAILED:
+ case PA_STREAM_TERMINATED:
+ if (po->mixer != NULL)
+ pulse_mixer_on_disconnect(po->mixer);
+
+ pa_threaded_mainloop_signal(po->mainloop, 0);
+ break;
+
+ case PA_STREAM_UNCONNECTED:
+ case PA_STREAM_CREATING:
+ break;
+ }
+}
+
+static void
+pulse_output_stream_write_cb(G_GNUC_UNUSED pa_stream *stream, size_t nbytes,
+ void *userdata)
+{
+ struct pulse_output *po = userdata;
+
+ po->writable = nbytes;
+ pa_threaded_mainloop_signal(po->mainloop, 0);
+}
+
+static bool
+pulse_output_open(void *data, struct audio_format *audio_format,
+ GError **error_r)
+{
+ struct pulse_output *po = data;
+ pa_sample_spec ss;
+ int error;
+
+ if (po->context != NULL) {
+ switch (pa_context_get_state(po->context)) {
+ case PA_CONTEXT_UNCONNECTED:
+ case PA_CONTEXT_TERMINATED:
+ case PA_CONTEXT_FAILED:
+ /* the connection was closed meanwhile; delete
+ it, and pulse_output_wait_connection() will
+ reopen it */
+ pulse_output_delete_context(po);
+ break;
+
+ case PA_CONTEXT_READY:
+ case PA_CONTEXT_CONNECTING:
+ case PA_CONTEXT_AUTHORIZING:
+ case PA_CONTEXT_SETTING_NAME:
+ break;
+ }
+ }
+
+ if (!pulse_output_wait_connection(po, error_r))
+ return false;
+
+ /* MPD doesn't support the other pulseaudio sample formats, so
+ we just force MPD to send us everything as 16 bit */
+ audio_format->format = SAMPLE_FORMAT_S16;
+
+ ss.format = PA_SAMPLE_S16NE;
+ ss.rate = audio_format->sample_rate;
+ ss.channels = audio_format->channels;
+
+ pa_threaded_mainloop_lock(po->mainloop);
+
+ /* create a stream .. */
+
+ po->stream = pa_stream_new(po->context, po->name, &ss, NULL);
+ 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)));
+ pa_threaded_mainloop_unlock(po->mainloop);
+ return false;
+ }
+
+ 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,
+ NULL, 0, NULL, NULL);
+ if (error < 0) {
+ pa_stream_unref(po->stream);
+ po->stream = NULL;
+
+ g_set_error(error_r, pulse_output_quark(), 0,
+ "pa_stream_connect_playback() has failed: %s",
+ pa_strerror(pa_context_errno(po->context)));
+ pa_threaded_mainloop_unlock(po->mainloop);
+ return false;
+ }
+
+ pa_threaded_mainloop_unlock(po->mainloop);
+
+#if !PA_CHECK_VERSION(0,9,11)
+ po->pause = false;
+#endif
+
+ return true;
+}
+
+static void
+pulse_output_close(void *data)
+{
+ struct pulse_output *po = data;
+ pa_operation *o;
+
+ pa_threaded_mainloop_lock(po->mainloop);
+
+ if (pa_stream_get_state(po->stream) == PA_STREAM_READY) {
+ o = pa_stream_drain(po->stream,
+ pulse_output_stream_success_cb, po);
+ if (o == NULL) {
+ g_warning("pa_stream_drain() has failed: %s",
+ pa_strerror(pa_context_errno(po->context)));
+ } else
+ pulse_wait_for_operation(po->mainloop, o);
+ }
+
+ pa_stream_disconnect(po->stream);
+ pa_stream_unref(po->stream);
+ po->stream = NULL;
+
+ if (po->context != NULL &&
+ pa_context_get_state(po->context) != PA_CONTEXT_READY)
+ pulse_output_delete_context(po);
+
+ pa_threaded_mainloop_unlock(po->mainloop);
+}
+
+/**
+ * Check if the stream is (already) connected, and waits for a signal
+ * if not. The mainloop must be locked before calling this function.
+ *
+ * @return the current stream state
+ */
+static pa_stream_state_t
+pulse_output_check_stream(struct pulse_output *po)
+{
+ pa_stream_state_t state = pa_stream_get_state(po->stream);
+
+ switch (state) {
+ case PA_STREAM_READY:
+ case PA_STREAM_FAILED:
+ case PA_STREAM_TERMINATED:
+ case PA_STREAM_UNCONNECTED:
+ break;
+
+ case PA_STREAM_CREATING:
+ pa_threaded_mainloop_wait(po->mainloop);
+ state = pa_stream_get_state(po->stream);
+ break;
+ }
+
+ return state;
+}
+
+/**
+ * Check if the stream is (already) connected, and waits if not. The
+ * mainloop must be locked before calling this function.
+ *
+ * @return true on success, false on error
+ */
+static bool
+pulse_output_wait_stream(struct pulse_output *po, GError **error_r)
+{
+ pa_stream_state_t state = pa_stream_get_state(po->stream);
+
+ switch (state) {
+ case PA_STREAM_READY:
+ return true;
+
+ case PA_STREAM_FAILED:
+ case PA_STREAM_TERMINATED:
+ case PA_STREAM_UNCONNECTED:
+ g_set_error(error_r, pulse_output_quark(), 0,
+ "disconnected");
+ return false;
+
+ case PA_STREAM_CREATING:
+ break;
+ }
+
+ do {
+ state = pulse_output_check_stream(po);
+ } while (state == PA_STREAM_CREATING);
+
+ if (state != PA_STREAM_READY) {
+ g_set_error(error_r, pulse_output_quark(), 0,
+ "failed to connect the stream: %s",
+ pa_strerror(pa_context_errno(po->context)));
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * Determines whether the stream is paused. On libpulse older than
+ * 0.9.11, it uses a custom pause flag.
+ */
+static bool
+pulse_output_stream_is_paused(struct pulse_output *po)
+{
+ assert(po->stream != NULL);
+
+#if !defined(PA_CHECK_VERSION) || !PA_CHECK_VERSION(0,9,11)
+ return po->pause;
+#else
+ return pa_stream_is_corked(po->stream);
+#endif
+}
+
+/**
+ * Sets cork mode on the stream.
+ */
+static bool
+pulse_output_stream_pause(struct pulse_output *po, bool pause,
+ GError **error_r)
+{
+ pa_operation *o;
+
+ assert(po->stream != NULL);
+
+ o = pa_stream_cork(po->stream, pause,
+ pulse_output_stream_success_cb, po);
+ if (o == NULL) {
+ g_set_error(error_r, pulse_output_quark(), 0,
+ "pa_stream_cork() has failed: %s",
+ pa_strerror(pa_context_errno(po->context)));
+ return false;
+ }
+
+ if (!pulse_wait_for_operation(po->mainloop, o)) {
+ g_set_error(error_r, pulse_output_quark(), 0,
+ "pa_stream_cork() has failed: %s",
+ pa_strerror(pa_context_errno(po->context)));
+ return false;
+ }
+
+#if !PA_CHECK_VERSION(0,9,11)
+ po->pause = pause;
+#endif
+ return true;
+}
+
+static size_t
+pulse_output_play(void *data, const void *chunk, size_t size, GError **error_r)
+{
+ struct pulse_output *po = data;
+ int error;
+
+ assert(po->stream != NULL);
+
+ pa_threaded_mainloop_lock(po->mainloop);
+
+ /* check if the stream is (already) connected */
+
+ if (!pulse_output_wait_stream(po, error_r)) {
+ pa_threaded_mainloop_unlock(po->mainloop);
+ return 0;
+ }
+
+ assert(po->context != NULL);
+
+ /* unpause if previously paused */
+
+ if (pulse_output_stream_is_paused(po) &&
+ !pulse_output_stream_pause(po, false, error_r))
+ return 0;
+
+ /* wait until the server allows us to write */
+
+ while (po->writable == 0) {
+ pa_threaded_mainloop_wait(po->mainloop);
+
+ if (pa_stream_get_state(po->stream) != PA_STREAM_READY) {
+ pa_threaded_mainloop_unlock(po->mainloop);
+ g_set_error(error_r, pulse_output_quark(), 0,
+ "disconnected");
+ return false;
+ }
+ }
+
+ /* now write */
+
+ if (size > po->writable)
+ /* don't send more than possible */
+ size = po->writable;
+
+ po->writable -= size;
+
+ error = pa_stream_write(po->stream, chunk, size, NULL,
+ 0, PA_SEEK_RELATIVE);
+ pa_threaded_mainloop_unlock(po->mainloop);
+ if (error < 0) {
+ g_set_error(error_r, pulse_output_quark(), error,
+ "%s", pa_strerror(error));
+ return 0;
+ }
+
+ return size;
+}
+
+static void
+pulse_output_cancel(void *data)
+{
+ struct pulse_output *po = data;
+ pa_operation *o;
+
+ assert(po->stream != NULL);
+
+ pa_threaded_mainloop_lock(po->mainloop);
+
+ if (pa_stream_get_state(po->stream) != PA_STREAM_READY) {
+ /* no need to flush when the stream isn't connected
+ yet */
+ pa_threaded_mainloop_unlock(po->mainloop);
+ return;
+ }
+
+ assert(po->context != NULL);
+
+ o = pa_stream_flush(po->stream, pulse_output_stream_success_cb, po);
+ if (o == NULL) {
+ g_warning("pa_stream_flush() has failed: %s",
+ pa_strerror(pa_context_errno(po->context)));
+ pa_threaded_mainloop_unlock(po->mainloop);
+ return;
+ }
+
+ pulse_wait_for_operation(po->mainloop, o);
+ pa_threaded_mainloop_unlock(po->mainloop);
+}
+
+static bool
+pulse_output_pause(void *data)
+{
+ struct pulse_output *po = data;
+ GError *error = NULL;
+
+ assert(po->stream != NULL);
+
+ pa_threaded_mainloop_lock(po->mainloop);
+
+ /* check if the stream is (already/still) connected */
+
+ if (!pulse_output_wait_stream(po, &error)) {
+ pa_threaded_mainloop_unlock(po->mainloop);
+ g_warning("%s", error->message);
+ g_error_free(error);
+ return false;
+ }
+
+ assert(po->context != NULL);
+
+ /* cork the stream */
+
+ if (pulse_output_stream_is_paused(po)) {
+ /* already paused; due to a MPD API limitation, we
+ have to sleep a little bit here, to avoid hogging
+ the CPU */
+
+ g_usleep(50000);
+ } else if (!pulse_output_stream_pause(po, true, &error)) {
+ pa_threaded_mainloop_unlock(po->mainloop);
+ g_warning("%s", error->message);
+ g_error_free(error);
+ return false;
+ }
+
+ pa_threaded_mainloop_unlock(po->mainloop);
+
+ return true;
+}
+
+static bool
+pulse_output_test_default_device(void)
+{
+ struct pulse_output *po;
+ bool success;
+
+ po = pulse_output_init(NULL, NULL, NULL);
+ if (po == NULL)
+ return false;
+
+ success = pulse_output_wait_connection(po, NULL);
+ pulse_output_finish(po);
+
+ return success;
+}
+
+const struct audio_output_plugin pulse_output_plugin = {
+ .name = "pulse",
+
+ .test_default_device = pulse_output_test_default_device,
+ .init = pulse_output_init,
+ .finish = pulse_output_finish,
+ .enable = pulse_output_enable,
+ .disable = pulse_output_disable,
+ .open = pulse_output_open,
+ .play = pulse_output_play,
+ .cancel = pulse_output_cancel,
+ .pause = pulse_output_pause,
+ .close = pulse_output_close,
+
+ .mixer_plugin = &pulse_mixer_plugin,
+};
diff --git a/src/output/pulse_output_plugin.h b/src/output/pulse_output_plugin.h
new file mode 100644
index 000000000..06e3aec43
--- /dev/null
+++ b/src/output/pulse_output_plugin.h
@@ -0,0 +1,72 @@
+/*
+ * 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_PULSE_OUTPUT_PLUGIN_H
+#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 pa_cvolume;
+
+struct pulse_output {
+ 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
+};
+
+void
+pulse_output_set_mixer(struct pulse_output *po, struct pulse_mixer *pm);
+
+void
+pulse_output_clear_mixer(struct pulse_output *po, struct pulse_mixer *pm);
+
+bool
+pulse_output_set_volume(struct pulse_output *po,
+ const struct pa_cvolume *volume, GError **error_r);
+
+#endif
diff --git a/src/output/pulse_plugin.c b/src/output/pulse_plugin.c
deleted file mode 100644
index ffc7abc8b..000000000
--- a/src/output/pulse_plugin.c
+++ /dev/null
@@ -1,179 +0,0 @@
-/*
- * 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_api.h"
-#include "mixer_list.h"
-
-#include <glib.h>
-#include <pulse/simple.h>
-#include <pulse/error.h>
-
-#define MPD_PULSE_NAME "mpd"
-
-struct pulse_data {
- const char *name;
- const char *server;
- const char *sink;
-
- pa_simple *s;
-};
-
-/**
- * The quark used for GError.domain.
- */
-static inline GQuark
-pulse_output_quark(void)
-{
- return g_quark_from_static_string("pulse_output");
-}
-
-static struct pulse_data *pulse_new_data(void)
-{
- struct pulse_data *ret;
-
- ret = g_new(struct pulse_data, 1);
-
- ret->server = NULL;
- ret->sink = NULL;
-
- return ret;
-}
-
-static void pulse_free_data(struct pulse_data *pd)
-{
- g_free(pd);
-}
-
-static void *
-pulse_init(G_GNUC_UNUSED const struct audio_format *audio_format,
- const struct config_param *param, G_GNUC_UNUSED GError **error)
-{
- struct pulse_data *pd;
-
- pd = pulse_new_data();
- pd->name = config_get_block_string(param, "name", "mpd_pulse");
- pd->server = config_get_block_string(param, "server", NULL);
- pd->sink = config_get_block_string(param, "sink", NULL);
-
- return pd;
-}
-
-static void pulse_finish(void *data)
-{
- struct pulse_data *pd = data;
-
- pulse_free_data(pd);
-}
-
-static bool pulse_test_default_device(void)
-{
- pa_simple *s;
- pa_sample_spec ss;
- int error;
-
- ss.format = PA_SAMPLE_S16NE;
- ss.rate = 44100;
- ss.channels = 2;
-
- s = pa_simple_new(NULL, MPD_PULSE_NAME, PA_STREAM_PLAYBACK, NULL,
- MPD_PULSE_NAME, &ss, NULL, NULL, &error);
- if (!s) {
- g_message("Cannot connect to default PulseAudio server: %s\n",
- pa_strerror(error));
- return false;
- }
-
- pa_simple_free(s);
-
- return true;
-}
-
-static bool
-pulse_open(void *data, struct audio_format *audio_format, GError **error_r)
-{
- struct pulse_data *pd = data;
- pa_sample_spec ss;
- int error;
-
- /* MPD doesn't support the other pulseaudio sample formats, so
- we just force MPD to send us everything as 16 bit */
- audio_format->bits = 16;
-
- ss.format = PA_SAMPLE_S16NE;
- ss.rate = audio_format->sample_rate;
- ss.channels = audio_format->channels;
-
- pd->s = pa_simple_new(pd->server, MPD_PULSE_NAME, PA_STREAM_PLAYBACK,
- pd->sink, pd->name,
- &ss, NULL, NULL,
- &error);
- if (!pd->s) {
- g_set_error(error_r, pulse_output_quark(), error,
- "Cannot connect to PulseAudio server: %s",
- pa_strerror(error));
- return false;
- }
-
- return true;
-}
-
-static void pulse_cancel(void *data)
-{
- struct pulse_data *pd = data;
- int error;
-
- if (pa_simple_flush(pd->s, &error) < 0)
- g_warning("Flush failed in PulseAudio output \"%s\": %s\n",
- pd->name, pa_strerror(error));
-}
-
-static void pulse_close(void *data)
-{
- struct pulse_data *pd = data;
-
- pa_simple_drain(pd->s, NULL);
- pa_simple_free(pd->s);
-}
-
-static size_t
-pulse_play(void *data, const void *chunk, size_t size, GError **error_r)
-{
- struct pulse_data *pd = data;
- int error;
-
- if (pa_simple_write(pd->s, chunk, size, &error) < 0) {
- g_set_error(error_r, pulse_output_quark(), error,
- "%s", pa_strerror(error));
- return 0;
- }
-
- return size;
-}
-
-const struct audio_output_plugin pulse_plugin = {
- .name = "pulse",
- .test_default_device = pulse_test_default_device,
- .init = pulse_init,
- .finish = pulse_finish,
- .open = pulse_open,
- .play = pulse_play,
- .cancel = pulse_cancel,
- .close = pulse_close,
- .mixer_plugin = &pulse_mixer,
-};
diff --git a/src/output/recorder_output_plugin.c b/src/output/recorder_output_plugin.c
new file mode 100644
index 000000000..c01d927c4
--- /dev/null
+++ b/src/output/recorder_output_plugin.c
@@ -0,0 +1,218 @@
+/*
+ * 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 "encoder_plugin.h"
+#include "encoder_list.h"
+#include "fd_util.h"
+#include "open.h"
+
+#include <assert.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <errno.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "recorder"
+
+struct recorder_output {
+ /**
+ * The configured encoder plugin.
+ */
+ struct encoder *encoder;
+
+ /**
+ * The destination file name.
+ */
+ const char *path;
+
+ /**
+ * The destination file descriptor.
+ */
+ int fd;
+
+ /**
+ * The buffer for encoder_read().
+ */
+ char buffer[32768];
+};
+
+/**
+ * The quark used for GError.domain.
+ */
+static inline GQuark
+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)
+{
+ struct recorder_output *recorder = g_new(struct recorder_output, 1);
+ const char *encoder_name;
+ const struct encoder_plugin *encoder_plugin;
+
+ /* read configuration */
+
+ encoder_name = config_get_block_string(param, "encoder", "vorbis");
+ encoder_plugin = encoder_plugin_get(encoder_name);
+ if (encoder_plugin == NULL) {
+ g_set_error(error_r, recorder_output_quark(), 0,
+ "No such encoder: %s", encoder_name);
+ return NULL;
+ }
+
+ recorder->path = config_get_block_string(param, "path", NULL);
+ if (recorder->path == NULL) {
+ g_set_error(error_r, recorder_output_quark(), 0,
+ "'path' not configured");
+ return NULL;
+ }
+
+ /* initialize encoder */
+
+ recorder->encoder = encoder_init(encoder_plugin, param, error_r);
+ if (recorder->encoder == NULL)
+ return NULL;
+
+ return recorder;
+}
+
+static void
+recorder_output_finish(void *data)
+{
+ struct recorder_output *recorder = data;
+
+ encoder_finish(recorder->encoder);
+ g_free(recorder);
+}
+
+/**
+ * Writes pending data from the encoder to the output file.
+ */
+static bool
+recorder_output_encoder_to_file(struct recorder_output *recorder,
+ GError **error_r)
+{
+ size_t size = 0, position, nbytes;
+
+ assert(recorder->fd >= 0);
+
+ /* read from the encoder */
+
+ size = encoder_read(recorder->encoder, recorder->buffer,
+ sizeof(recorder->buffer));
+ if (size == 0)
+ return true;
+
+ /* write everything into the file */
+
+ position = 0;
+ while (true) {
+ nbytes = write(recorder->fd, recorder->buffer + position,
+ size - position);
+ if (nbytes > 0) {
+ position += (size_t)nbytes;
+ if (position >= size)
+ return true;
+ } else if (nbytes == 0) {
+ /* shouldn't happen for files */
+ g_set_error(error_r, recorder_output_quark(), 0,
+ "write() returned 0");
+ return false;
+ } else if (errno != EINTR) {
+ g_set_error(error_r, recorder_output_quark(), 0,
+ "Failed to write to '%s': %s",
+ recorder->path, g_strerror(errno));
+ return false;
+ }
+ }
+}
+
+static bool
+recorder_output_open(void *data, struct audio_format *audio_format,
+ GError **error_r)
+{
+ struct recorder_output *recorder = data;
+ bool success;
+
+ /* create the output file */
+
+ recorder->fd = open_cloexec(recorder->path,
+ O_CREAT|O_WRONLY|O_TRUNC|O_BINARY,
+ 0666);
+ if (recorder->fd < 0) {
+ g_set_error(error_r, recorder_output_quark(), 0,
+ "Failed to create '%s': %s",
+ recorder->path, g_strerror(errno));
+ return false;
+ }
+
+ /* open the encoder */
+
+ success = encoder_open(recorder->encoder, audio_format, error_r);
+ if (!success) {
+ close(recorder->fd);
+ unlink(recorder->path);
+ return false;
+ }
+
+ return true;
+}
+
+static void
+recorder_output_close(void *data)
+{
+ struct recorder_output *recorder = data;
+
+ /* flush the encoder and write the rest to the file */
+
+ if (encoder_flush(recorder->encoder, NULL))
+ recorder_output_encoder_to_file(recorder, NULL);
+
+ /* now really close everything */
+
+ encoder_close(recorder->encoder);
+
+ close(recorder->fd);
+}
+
+static size_t
+recorder_output_play(void *data, const void *chunk, size_t size,
+ GError **error_r)
+{
+ struct recorder_output *recorder = data;
+
+ return encoder_write(recorder->encoder, chunk, size, error_r) &&
+ recorder_output_encoder_to_file(recorder, error_r)
+ ? size : 0;
+}
+
+const struct audio_output_plugin recorder_output_plugin = {
+ .name = "recorder",
+ .init = recorder_output_init,
+ .finish = recorder_output_finish,
+ .open = recorder_output_open,
+ .close = recorder_output_close,
+ .play = recorder_output_play,
+};
diff --git a/src/output/shout_plugin.c b/src/output/shout_plugin.c
index dbc56f337..baaeccf92 100644
--- a/src/output/shout_plugin.c
+++ b/src/output/shout_plugin.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -17,9 +17,11 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "output_api.h"
#include "encoder_plugin.h"
#include "encoder_list.h"
+#include "mpd_error.h"
#include <shout/shout.h>
#include <glib.h>
@@ -100,8 +102,8 @@ static void free_shout_data(struct shout_data *sd)
#define check_block_param(name) { \
block_param = config_get_block_param(param, name); \
if (!block_param) { \
- g_error("no \"%s\" defined for shout device defined at line " \
- "%i\n", name, param->line); \
+ MPD_ERROR("no \"%s\" defined for shout device defined at line " \
+ "%i\n", name, param->line); \
} \
}
@@ -126,6 +128,13 @@ my_shout_init_driver(const struct audio_format *audio_format,
struct block_param *block_param;
int public;
+ if (audio_format == NULL ||
+ !audio_format_fully_defined(audio_format)) {
+ g_set_error(error, shout_output_quark(), 0,
+ "Need full audio format specification");
+ return NULL;
+ }
+
sd = new_shout_data();
if (shout_init_count == 0)
@@ -191,8 +200,6 @@ my_shout_init_driver(const struct audio_format *audio_format,
}
}
- check_block_param("format");
-
encoding = config_get_block_string(param, "encoding", "ogg");
encoder_plugin = shout_encoder_plugin_get(encoding);
if (encoder_plugin == NULL) {
@@ -335,7 +342,6 @@ write_page(struct shout_data *sd, GError **error)
if (sd->buf.len == 0)
return true;
- shout_sync(sd->shout_conn);
err = shout_send(sd->shout_conn, sd->buf.data, sd->buf.len);
if (!handle_shout_error(sd, err, error))
return false;
@@ -434,6 +440,18 @@ my_shout_open_device(void *data, struct audio_format *audio_format,
return true;
}
+static unsigned
+my_shout_delay(void *data)
+{
+ struct shout_data *sd = (struct shout_data *)data;
+
+ int delay = shout_delay(sd->shout_conn);
+ if (delay < 0)
+ delay = 0;
+
+ return delay;
+}
+
static size_t
my_shout_play(void *data, const void *chunk, size_t size, GError **error)
{
@@ -448,15 +466,8 @@ my_shout_play(void *data, const void *chunk, size_t size, GError **error)
static bool
my_shout_pause(void *data)
{
- struct shout_data *sd = (struct shout_data *)data;
static const char silence[1020];
- if (shout_delay(sd->shout_conn) > 500) {
- /* cap the latency for unpause */
- g_usleep(500000);
- return true;
- }
-
return my_shout_play(data, silence, sizeof(silence), NULL);
}
@@ -471,10 +482,10 @@ shout_tag_to_metadata(const struct tag *tag, char *dest, size_t size)
for (unsigned i = 0; i < tag->num_items; i++) {
switch (tag->items[i]->type) {
- case TAG_ITEM_ARTIST:
+ case TAG_ARTIST:
strncpy(artist, tag->items[i]->value, size);
break;
- case TAG_ITEM_TITLE:
+ case TAG_TITLE:
strncpy(title, tag->items[i]->value, size);
break;
@@ -533,6 +544,7 @@ const struct audio_output_plugin shoutPlugin = {
.init = my_shout_init_driver,
.finish = my_shout_finish_driver,
.open = my_shout_open_device,
+ .delay = my_shout_delay,
.play = my_shout_play,
.pause = my_shout_pause,
.cancel = my_shout_drop_buffered_audio,
diff --git a/src/output/solaris_output_plugin.c b/src/output/solaris_output_plugin.c
index 5febf0afc..22c583805 100644
--- a/src/output/solaris_output_plugin.c
+++ b/src/output/solaris_output_plugin.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -17,7 +17,9 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "output_api.h"
+#include "fd_util.h"
#include <glib.h>
@@ -87,11 +89,11 @@ solaris_output_open(void *data, struct audio_format *audio_format,
/* support only 16 bit mono/stereo for now; nothing else has
been tested */
- audio_format->bits = 16;
+ audio_format->format = SAMPLE_FORMAT_S16;
/* open the device in non-blocking mode */
- so->fd = open(so->device, O_WRONLY|O_NONBLOCK);
+ so->fd = open_cloexec(so->device, O_WRONLY|O_NONBLOCK, 0);
if (so->fd < 0) {
g_set_error(error, solaris_output_quark(), errno,
"Failed to open %s: %s",
@@ -117,7 +119,7 @@ solaris_output_open(void *data, struct audio_format *audio_format,
info.play.sample_rate = audio_format->sample_rate;
info.play.channels = audio_format->channels;
- info.play.precision = audio_format->bits;
+ info.play.precision = 16;
info.play.encoding = AUDIO_ENCODING_LINEAR;
ret = ioctl(so->fd, AUDIO_SETINFO, &info);
diff --git a/src/output/winmm_output_plugin.c b/src/output/winmm_output_plugin.c
new file mode 100644
index 000000000..b9687874d
--- /dev/null
+++ b/src/output/winmm_output_plugin.c
@@ -0,0 +1,337 @@
+/*
+ * 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 "pcm_buffer.h"
+#include "mixer_list.h"
+#include "winmm_output_plugin.h"
+
+#include <stdlib.h>
+#include <string.h>
+#include <windows.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "winmm_output"
+
+struct winmm_buffer {
+ struct pcm_buffer buffer;
+
+ WAVEHDR hdr;
+};
+
+struct winmm_output {
+ UINT device_id;
+ HWAVEOUT handle;
+
+ /**
+ * This event is triggered by Windows when a buffer is
+ * finished.
+ */
+ HANDLE event;
+
+ struct winmm_buffer buffers[8];
+ unsigned next_buffer;
+};
+
+/**
+ * The quark used for GError.domain.
+ */
+static inline GQuark
+winmm_output_quark(void)
+{
+ return g_quark_from_static_string("winmm_output");
+}
+
+HWAVEOUT
+winmm_output_get_handle(struct winmm_output* output)
+{
+ return output->handle;
+}
+
+static bool
+winmm_output_test_default_device(void)
+{
+ return waveOutGetNumDevs() > 0;
+}
+
+static UINT
+get_device_id(const char *device_name)
+{
+ /* if device is not specified use wave mapper */
+ if (device_name == NULL)
+ return WAVE_MAPPER;
+
+ /* check for device id */
+ char *endptr;
+ UINT id = strtoul(device_name, &endptr, 0);
+ if (endptr > device_name && *endptr == 0)
+ return id;
+
+ /* check for device name */
+ for (UINT i = 0; i < waveOutGetNumDevs(); i++) {
+ WAVEOUTCAPS caps;
+ MMRESULT result = waveOutGetDevCaps(i, &caps, sizeof(caps));
+ if (result != MMSYSERR_NOERROR)
+ continue;
+ /* szPname is only 32 chars long, so it is often truncated.
+ Use partial match to work around this. */
+ if (strstr(device_name, caps.szPname) == device_name)
+ return i;
+ }
+
+ /* fallback to wave mapper */
+ return WAVE_MAPPER;
+}
+
+static void *
+winmm_output_init(G_GNUC_UNUSED const struct audio_format *audio_format,
+ G_GNUC_UNUSED const struct config_param *param,
+ G_GNUC_UNUSED GError **error)
+{
+ struct winmm_output *wo = g_new(struct winmm_output, 1);
+ const char *device = config_get_block_string(param, "device", NULL);
+ wo->device_id = get_device_id(device);
+ return wo;
+}
+
+static void
+winmm_output_finish(void *data)
+{
+ struct winmm_output *wo = data;
+
+ g_free(wo);
+}
+
+static bool
+winmm_output_open(void *data, struct audio_format *audio_format,
+ GError **error_r)
+{
+ struct winmm_output *wo = data;
+
+ wo->event = CreateEvent(NULL, false, false, NULL);
+ if (wo->event == NULL) {
+ g_set_error(error_r, winmm_output_quark(), 0,
+ "CreateEvent() failed");
+ return false;
+ }
+
+ switch (audio_format->format) {
+ case SAMPLE_FORMAT_S8:
+ case SAMPLE_FORMAT_S16:
+ break;
+
+ case SAMPLE_FORMAT_S24:
+ case SAMPLE_FORMAT_S24_P32:
+ case SAMPLE_FORMAT_S32:
+ case SAMPLE_FORMAT_UNDEFINED:
+ /* we havn't tested formats other than S16 */
+ audio_format->format = SAMPLE_FORMAT_S16;
+ break;
+ }
+
+ if (audio_format->channels > 2)
+ /* same here: more than stereo was not tested */
+ audio_format->channels = 2;
+
+ WAVEFORMATEX format;
+ format.wFormatTag = WAVE_FORMAT_PCM;
+ format.nChannels = audio_format->channels;
+ format.nSamplesPerSec = audio_format->sample_rate;
+ format.nBlockAlign = audio_format_frame_size(audio_format);
+ format.nAvgBytesPerSec = format.nSamplesPerSec * format.nBlockAlign;
+ format.wBitsPerSample = audio_format_sample_size(audio_format) * 8;
+ format.cbSize = 0;
+
+ MMRESULT result = waveOutOpen(&wo->handle, wo->device_id, &format,
+ (DWORD_PTR)wo->event, 0, CALLBACK_EVENT);
+ if (result != MMSYSERR_NOERROR) {
+ CloseHandle(wo->event);
+ g_set_error(error_r, winmm_output_quark(), result,
+ "waveOutOpen() failed");
+ return false;
+ }
+
+ for (unsigned i = 0; i < G_N_ELEMENTS(wo->buffers); ++i) {
+ pcm_buffer_init(&wo->buffers[i].buffer);
+ memset(&wo->buffers[i].hdr, 0, sizeof(wo->buffers[i].hdr));
+ }
+
+ wo->next_buffer = 0;
+
+ return true;
+}
+
+static void
+winmm_output_close(void *data)
+{
+ struct winmm_output *wo = data;
+
+ for (unsigned i = 0; i < G_N_ELEMENTS(wo->buffers); ++i)
+ pcm_buffer_deinit(&wo->buffers[i].buffer);
+
+ waveOutClose(wo->handle);
+
+ CloseHandle(wo->event);
+}
+
+/**
+ * Copy data into a buffer, and prepare the wave header.
+ */
+static bool
+winmm_set_buffer(struct winmm_output *wo, struct winmm_buffer *buffer,
+ const void *data, size_t size,
+ GError **error_r)
+{
+ void *dest = pcm_buffer_get(&buffer->buffer, size);
+ if (dest == NULL) {
+ g_set_error(error_r, winmm_output_quark(), 0,
+ "Out of memory");
+ return false;
+ }
+
+ memcpy(dest, data, size);
+
+ memset(&buffer->hdr, 0, sizeof(buffer->hdr));
+ buffer->hdr.lpData = dest;
+ buffer->hdr.dwBufferLength = size;
+
+ MMRESULT result = waveOutPrepareHeader(wo->handle, &buffer->hdr,
+ sizeof(buffer->hdr));
+ if (result != MMSYSERR_NOERROR) {
+ g_set_error(error_r, winmm_output_quark(), result,
+ "waveOutPrepareHeader() failed");
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * Wait until the buffer is finished.
+ */
+static bool
+winmm_drain_buffer(struct winmm_output *wo, struct winmm_buffer *buffer,
+ GError **error_r)
+{
+ if ((buffer->hdr.dwFlags & WHDR_DONE) == WHDR_DONE)
+ /* already finished */
+ return true;
+
+ while (true) {
+ MMRESULT result = waveOutUnprepareHeader(wo->handle,
+ &buffer->hdr,
+ sizeof(buffer->hdr));
+ if (result == MMSYSERR_NOERROR)
+ return true;
+ else if (result != WAVERR_STILLPLAYING) {
+ g_set_error(error_r, winmm_output_quark(), result,
+ "waveOutUnprepareHeader() failed");
+ return false;
+ }
+
+ /* wait some more */
+ WaitForSingleObject(wo->event, INFINITE);
+ }
+}
+
+static size_t
+winmm_output_play(void *data, const void *chunk, size_t size, GError **error_r)
+{
+ struct winmm_output *wo = data;
+
+ /* get the next buffer from the ring and prepare it */
+ struct winmm_buffer *buffer = &wo->buffers[wo->next_buffer];
+ if (!winmm_drain_buffer(wo, buffer, error_r) ||
+ !winmm_set_buffer(wo, buffer, chunk, size, error_r))
+ return 0;
+
+ /* enqueue the buffer */
+ MMRESULT result = waveOutWrite(wo->handle, &buffer->hdr,
+ sizeof(buffer->hdr));
+ if (result != MMSYSERR_NOERROR) {
+ waveOutUnprepareHeader(wo->handle, &buffer->hdr,
+ sizeof(buffer->hdr));
+ g_set_error(error_r, winmm_output_quark(), result,
+ "waveOutWrite() failed");
+ return 0;
+ }
+
+ /* mark our buffer as "used" */
+ wo->next_buffer = (wo->next_buffer + 1) %
+ G_N_ELEMENTS(wo->buffers);
+
+ return size;
+}
+
+static bool
+winmm_drain_all_buffers(struct winmm_output *wo, GError **error_r)
+{
+ for (unsigned i = wo->next_buffer; i < G_N_ELEMENTS(wo->buffers); ++i)
+ if (!winmm_drain_buffer(wo, &wo->buffers[i], error_r))
+ return false;
+
+ for (unsigned i = 0; i < wo->next_buffer; ++i)
+ if (!winmm_drain_buffer(wo, &wo->buffers[i], error_r))
+ return false;
+
+ return true;
+}
+
+static void
+winmm_stop(struct winmm_output *wo)
+{
+ waveOutReset(wo->handle);
+
+ for (unsigned i = 0; i < G_N_ELEMENTS(wo->buffers); ++i) {
+ struct winmm_buffer *buffer = &wo->buffers[i];
+ waveOutUnprepareHeader(wo->handle, &buffer->hdr,
+ sizeof(buffer->hdr));
+ }
+}
+
+static void
+winmm_output_drain(void *data)
+{
+ struct winmm_output *wo = data;
+
+ if (!winmm_drain_all_buffers(wo, NULL))
+ winmm_stop(wo);
+}
+
+static void
+winmm_output_cancel(void *data)
+{
+ struct winmm_output *wo = data;
+
+ winmm_stop(wo);
+}
+
+const struct audio_output_plugin winmm_output_plugin = {
+ .name = "winmm",
+ .test_default_device = winmm_output_test_default_device,
+ .init = winmm_output_init,
+ .finish = winmm_output_finish,
+ .open = winmm_output_open,
+ .close = winmm_output_close,
+ .play = winmm_output_play,
+ .drain = winmm_output_drain,
+ .cancel = winmm_output_cancel,
+ .mixer_plugin = &winmm_mixer_plugin,
+};
diff --git a/src/output/winmm_output_plugin.h b/src/output/winmm_output_plugin.h
new file mode 100644
index 000000000..39507275a
--- /dev/null
+++ b/src/output/winmm_output_plugin.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_WINMM_OUTPUT_PLUGIN_H
+#define MPD_WINMM_OUTPUT_PLUGIN_H
+
+#include <windows.h>
+
+struct winmm_output;
+
+HWAVEOUT winmm_output_get_handle(struct winmm_output*);
+
+#endif
diff --git a/src/output_all.c b/src/output_all.c
index 86e78e13e..19c0f0166 100644
--- a/src/output_all.c
+++ b/src/output_all.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -17,6 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "output_all.h"
#include "output_internal.h"
#include "output_control.h"
@@ -25,6 +26,7 @@
#include "pipe.h"
#include "buffer.h"
#include "player_control.h"
+#include "mpd_error.h"
#ifndef NDEBUG
#include "chunk.h"
@@ -52,6 +54,11 @@ static struct music_buffer *g_music_buffer;
*/
static struct music_pipe *g_mp;
+/**
+ * The "elapsed_time" stamp of the most recently finished chunk.
+ */
+static float audio_output_all_elapsed_time = -1.0;
+
unsigned int audio_output_count(void)
{
return num_audio_outputs;
@@ -116,17 +123,17 @@ audio_output_all_init(void)
if (!audio_output_init(output, param, &error)) {
if (param != NULL)
- g_error("line %i: %s",
- param->line, error->message);
+ MPD_ERROR("line %i: %s",
+ param->line, error->message);
else
- g_error("%s", error->message);
+ MPD_ERROR("%s", error->message);
}
/* require output names to be unique: */
for (j = 0; j < i; j++) {
if (!strcmp(output->name, audio_outputs[j].name)) {
- g_error("output devices with identical "
- "names: %s\n", output->name);
+ MPD_ERROR("output devices with identical "
+ "names: %s\n", output->name);
}
}
}
@@ -138,6 +145,7 @@ audio_output_all_finish(void)
unsigned int i;
for (i = 0; i < num_audio_outputs; i++) {
+ audio_output_disable(&audio_outputs[i]);
audio_output_finish(&audio_outputs[i]);
}
@@ -148,6 +156,25 @@ audio_output_all_finish(void)
notify_deinit(&audio_output_client_notify);
}
+void
+audio_output_all_enable_disable(void)
+{
+ for (unsigned i = 0; i < num_audio_outputs; i++) {
+ struct audio_output *ao = &audio_outputs[i];
+ bool enabled;
+
+ g_mutex_lock(ao->mutex);
+ enabled = ao->really_enabled;
+ g_mutex_unlock(ao->mutex);
+
+ if (ao->enabled != enabled) {
+ if (ao->enabled)
+ audio_output_enable(ao);
+ else
+ audio_output_disable(ao);
+ }
+ }
+}
/**
* Determine if all (active) outputs have finished the current
@@ -156,10 +183,18 @@ audio_output_all_finish(void)
static bool
audio_output_all_finished(void)
{
- for (unsigned i = 0; i < num_audio_outputs; ++i)
- if (audio_output_is_open(&audio_outputs[i]) &&
- !audio_output_command_is_finished(&audio_outputs[i]))
+ for (unsigned i = 0; i < num_audio_outputs; ++i) {
+ struct audio_output *ao = &audio_outputs[i];
+ bool not_finished;
+
+ g_mutex_lock(ao->mutex);
+ not_finished = audio_output_is_open(ao) &&
+ !audio_output_command_is_finished(ao);
+ g_mutex_unlock(ao->mutex);
+
+ if (not_finished)
return false;
+ }
return true;
}
@@ -170,6 +205,29 @@ static void audio_output_wait_all(void)
notify_wait(&audio_output_client_notify);
}
+/**
+ * Signals the audio output if it is open. This function locks the
+ * mutex.
+ */
+static void
+audio_output_lock_signal(struct audio_output *ao)
+{
+ g_mutex_lock(ao->mutex);
+ if (audio_output_is_open(ao))
+ g_cond_signal(ao->cond);
+ g_mutex_unlock(ao->mutex);
+}
+
+/**
+ * Signals all audio outputs which are open.
+ */
+static void
+audio_output_signal_all(void)
+{
+ for (unsigned i = 0; i < num_audio_outputs; ++i)
+ audio_output_lock_signal(&audio_outputs[i]);
+}
+
static void
audio_output_reset_reopen(struct audio_output *ao)
{
@@ -237,8 +295,7 @@ audio_output_all_play(struct music_chunk *chunk)
music_pipe_push(g_mp, chunk);
for (i = 0; i < num_audio_outputs; ++i)
- if (audio_output_is_open(&audio_outputs[i]))
- audio_output_play(&audio_outputs[i]);
+ audio_output_play(&audio_outputs[i]);
return true;
}
@@ -273,6 +330,7 @@ audio_output_all_open(const struct audio_format *audio_format,
input_audio_format = *audio_format;
audio_output_all_reset_reopen();
+ audio_output_all_enable_disable();
audio_output_all_update();
for (i = 0; i < num_audio_outputs; ++i) {
@@ -385,6 +443,11 @@ audio_output_all_check(void)
this chunk */
return music_pipe_size(g_mp);
+ if (chunk->length > 0 && chunk->times >= 0.0)
+ /* only update elapsed_time if the chunk
+ provides a defined value */
+ audio_output_all_elapsed_time = chunk->times;
+
is_tail = chunk->next == NULL;
if (is_tail)
/* this is the tail of the pipe - clear the
@@ -412,10 +475,15 @@ audio_output_all_check(void)
bool
audio_output_all_wait(unsigned threshold)
{
- if (audio_output_all_check() < threshold)
+ player_lock();
+
+ if (audio_output_all_check() < threshold) {
+ player_unlock();
return true;
+ }
- notify_wait(&pc.notify);
+ player_wait();
+ player_unlock();
return audio_output_all_check() < threshold;
}
@@ -428,8 +496,16 @@ audio_output_all_pause(void)
audio_output_all_update();
for (i = 0; i < num_audio_outputs; ++i)
- if (audio_output_is_open(&audio_outputs[i]))
- audio_output_pause(&audio_outputs[i]);
+ audio_output_pause(&audio_outputs[i]);
+
+ audio_output_wait_all();
+}
+
+void
+audio_output_all_drain(void)
+{
+ for (unsigned i = 0; i < num_audio_outputs; ++i)
+ audio_output_drain_async(&audio_outputs[i]);
audio_output_wait_all();
}
@@ -441,10 +517,8 @@ audio_output_all_cancel(void)
/* send the cancel() command to all audio outputs */
- for (i = 0; i < num_audio_outputs; ++i) {
- if (audio_output_is_open(&audio_outputs[i]))
- audio_output_cancel(&audio_outputs[i]);
- }
+ for (i = 0; i < num_audio_outputs; ++i)
+ audio_output_cancel(&audio_outputs[i]);
audio_output_wait_all();
@@ -452,6 +526,15 @@ audio_output_all_cancel(void)
if (g_mp != NULL)
music_pipe_clear(g_mp, g_music_buffer);
+
+ /* the audio outputs are now waiting for a signal, to
+ synchronize the cleared music pipe */
+
+ audio_output_signal_all();
+
+ /* invalidate elapsed_time */
+
+ audio_output_all_elapsed_time = -1.0;
}
void
@@ -473,4 +556,43 @@ audio_output_all_close(void)
g_music_buffer = NULL;
audio_format_clear(&input_audio_format);
+
+ audio_output_all_elapsed_time = -1.0;
+}
+
+void
+audio_output_all_release(void)
+{
+ unsigned int i;
+
+ for (i = 0; i < num_audio_outputs; ++i)
+ audio_output_release(&audio_outputs[i]);
+
+ if (g_mp != NULL) {
+ assert(g_music_buffer != NULL);
+
+ music_pipe_clear(g_mp, g_music_buffer);
+ music_pipe_free(g_mp);
+ g_mp = NULL;
+ }
+
+ g_music_buffer = NULL;
+
+ audio_format_clear(&input_audio_format);
+
+ audio_output_all_elapsed_time = -1.0;
+}
+
+void
+audio_output_all_song_border(void)
+{
+ /* clear the elapsed_time pointer at the beginning of a new
+ song */
+ audio_output_all_elapsed_time = 0.0;
+}
+
+float
+audio_output_all_get_elapsed_time(void)
+{
+ return audio_output_all_elapsed_time;
}
diff --git a/src/output_all.h b/src/output_all.h
index 2a09514b2..a579bf5f1 100644
--- a/src/output_all.h
+++ b/src/output_all.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -66,6 +66,13 @@ struct audio_output *
audio_output_find(const char *name);
/**
+ * Checks the "enabled" flag of all audio outputs, and if one has
+ * changed, commit the change.
+ */
+void
+audio_output_all_enable_disable(void);
+
+/**
* Opens all audio outputs which are not disabled.
*
* @param audio_format the preferred audio format, or NULL to reuse
@@ -85,6 +92,13 @@ void
audio_output_all_close(void);
/**
+ * Closes all audio outputs. Outputs with the "always_on" flag are
+ * put into pause mode.
+ */
+void
+audio_output_all_release(void);
+
+/**
* Enqueue a #music_chunk object for playing, i.e. pushes it to a
* #music_pipe.
*
@@ -123,9 +137,29 @@ void
audio_output_all_pause(void);
/**
+ * Drain all audio outputs.
+ */
+void
+audio_output_all_drain(void);
+
+/**
* Try to cancel data which may still be in the device's buffers.
*/
void
audio_output_all_cancel(void);
+/**
+ * Indicate that a new song will begin now.
+ */
+void
+audio_output_all_song_border(void);
+
+/**
+ * Returns the "elapsed_time" stamp of the most recently finished
+ * chunk. A negative value is returned when no chunk has been
+ * finished yet.
+ */
+float
+audio_output_all_get_elapsed_time(void);
+
#endif
diff --git a/src/output_api.h b/src/output_api.h
index c31893bc6..8e002dd48 100644
--- a/src/output_api.h
+++ b/src/output_api.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
diff --git a/src/output_command.c b/src/output_command.c
index 5da176dde..825884e8e 100644
--- a/src/output_command.c
+++ b/src/output_command.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -24,13 +24,17 @@
*
*/
+#include "config.h"
#include "output_command.h"
#include "output_all.h"
#include "output_internal.h"
#include "output_plugin.h"
#include "mixer_control.h"
+#include "player_control.h"
#include "idle.h"
+extern unsigned audio_output_state_version;
+
bool
audio_output_enable_index(unsigned idx)
{
@@ -40,10 +44,16 @@ audio_output_enable_index(unsigned idx)
return false;
ao = audio_output_get(idx);
+ if (ao->enabled)
+ return true;
ao->enabled = true;
idle_add(IDLE_OUTPUT);
+ pc_update_audio();
+
+ ++audio_output_state_version;
+
return true;
}
@@ -57,6 +67,8 @@ audio_output_disable_index(unsigned idx)
return false;
ao = audio_output_get(idx);
+ if (!ao->enabled)
+ return true;
ao->enabled = false;
idle_add(IDLE_OUTPUT);
@@ -67,5 +79,9 @@ audio_output_disable_index(unsigned idx)
idle_add(IDLE_MIXER);
}
+ pc_update_audio();
+
+ ++audio_output_state_version;
+
return true;
}
diff --git a/src/output_command.h b/src/output_command.h
index d92ff5ec8..fab015c3f 100644
--- a/src/output_command.h
+++ b/src/output_command.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
diff --git a/src/output_control.c b/src/output_control.c
index ecd226821..0823b667b 100644
--- a/src/output_control.c
+++ b/src/output_control.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -17,12 +17,15 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "output_control.h"
#include "output_api.h"
#include "output_internal.h"
#include "output_thread.h"
#include "mixer_control.h"
#include "mixer_plugin.h"
+#include "filter_plugin.h"
+#include "notify.h"
#include <assert.h>
#include <stdlib.h>
@@ -38,8 +41,9 @@ struct notify audio_output_client_notify;
static void ao_command_wait(struct audio_output *ao)
{
while (ao->command != AO_COMMAND_NONE) {
- notify_signal(&ao->notify);
+ g_mutex_unlock(ao->mutex);
notify_wait(&audio_output_client_notify);
+ g_mutex_lock(ao->mutex);
}
}
@@ -47,29 +51,55 @@ static void ao_command(struct audio_output *ao, enum audio_output_command cmd)
{
assert(ao->command == AO_COMMAND_NONE);
ao->command = cmd;
+ g_cond_signal(ao->cond);
ao_command_wait(ao);
}
-/**
- * Like ao_command(), but assumes the object is locked by the caller.
- */
-static void
-ao_command_locked(struct audio_output *ao, enum audio_output_command cmd)
+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);
+}
+
+void
+audio_output_enable(struct audio_output *ao)
+{
+ if (ao->thread == NULL) {
+ if (ao->plugin->enable == NULL) {
+ /* don't bother to start the thread now if the
+ device doesn't even have a enable() method;
+ just assign the variable and we're done */
+ ao->really_enabled = true;
+ return;
+ }
+
+ audio_output_thread_start(ao);
+ }
- g_mutex_unlock(ao->mutex);
- ao_command_wait(ao);
g_mutex_lock(ao->mutex);
+ ao_command(ao, AO_COMMAND_ENABLE);
+ g_mutex_unlock(ao->mutex);
}
-static void ao_command_async(struct audio_output *ao,
- enum audio_output_command cmd)
+void
+audio_output_disable(struct audio_output *ao)
{
- assert(ao->command == AO_COMMAND_NONE);
- ao->command = cmd;
- notify_signal(&ao->notify);
+ if (ao->thread == NULL) {
+ if (ao->plugin->disable == NULL)
+ ao->really_enabled = false;
+ else
+ /* if there's no thread yet, the device cannot
+ be enabled */
+ assert(!ao->really_enabled);
+
+ return;
+ }
+
+ g_mutex_lock(ao->mutex);
+ ao_command(ao, AO_COMMAND_DISABLE);
+ g_mutex_unlock(ao->mutex);
}
static void
@@ -85,6 +115,7 @@ audio_output_open(struct audio_output *ao,
{
bool open;
+ assert(audio_format_valid(audio_format));
assert(mp != NULL);
if (ao->fail_timer != NULL) {
@@ -94,9 +125,13 @@ audio_output_open(struct audio_output *ao,
if (ao->open &&
audio_format_equals(audio_format, &ao->in_audio_format)) {
- assert(ao->pipe == mp);
+ assert(ao->pipe == mp ||
+ (ao->always_on && ao->pause));
if (ao->pause) {
+ ao->chunk = NULL;
+ ao->pipe = mp;
+
/* unpause with the CANCEL command; this is a
hack, but suits well for forcing the thread
to leave the ao_pause() thread, and we need
@@ -104,7 +139,11 @@ audio_output_open(struct audio_output *ao,
/* we're not using audio_output_cancel() here,
because that function is asynchronous */
- ao_command_locked(ao, AO_COMMAND_CANCEL);
+ ao_command(ao, AO_COMMAND_CANCEL);
+
+ /* the audio output is now waiting for a
+ signal; wake it up immediately */
+ g_cond_signal(ao->cond);
}
return true;
@@ -113,33 +152,49 @@ audio_output_open(struct audio_output *ao,
ao->in_audio_format = *audio_format;
ao->chunk = NULL;
- if (!ao->config_audio_format) {
- if (ao->open)
- audio_output_close_locked(ao);
-
- /* no audio format is configured: copy in->out, let
- the output's open() method determine the effective
- out_audio_format */
- ao->out_audio_format = ao->in_audio_format;
- }
-
ao->pipe = mp;
if (ao->thread == NULL)
audio_output_thread_start(ao);
+ ao_command(ao, ao->open ? AO_COMMAND_REOPEN : AO_COMMAND_OPEN);
open = ao->open;
- if (!open) {
- ao_command_locked(ao, AO_COMMAND_OPEN);
- open = ao->open;
- }
- if (open && ao->mixer != NULL)
- mixer_open(ao->mixer);
+ if (open && ao->mixer != NULL) {
+ GError *error = NULL;
+
+ if (!mixer_open(ao->mixer, &error)) {
+ g_warning("Failed to open mixer for '%s': %s",
+ ao->name, error->message);
+ g_error_free(error);
+ }
+ }
return open;
}
+/**
+ * Same as audio_output_close(), but expects the lock to be held by
+ * the caller.
+ */
+static void
+audio_output_close_locked(struct audio_output *ao)
+{
+ assert(ao != NULL);
+
+ if (ao->mixer != NULL)
+ mixer_auto_close(ao->mixer);
+
+ assert(!ao->open || ao->fail_timer == NULL);
+
+ if (ao->open)
+ ao_command(ao, AO_COMMAND_CLOSE);
+ else if (ao->fail_timer != NULL) {
+ g_timer_destroy(ao->fail_timer);
+ ao->fail_timer = NULL;
+ }
+}
+
bool
audio_output_update(struct audio_output *ao,
const struct audio_format *audio_format,
@@ -149,28 +204,29 @@ audio_output_update(struct audio_output *ao,
g_mutex_lock(ao->mutex);
- if (ao->enabled) {
+ if (ao->enabled && ao->really_enabled) {
if (ao->fail_timer == NULL ||
g_timer_elapsed(ao->fail_timer, NULL) > REOPEN_AFTER) {
- bool ret = audio_output_open(ao, audio_format, mp);
+ bool success = audio_output_open(ao, audio_format, mp);
g_mutex_unlock(ao->mutex);
- return ret;
+ return success;
}
} else if (audio_output_is_open(ao))
audio_output_close_locked(ao);
g_mutex_unlock(ao->mutex);
-
return false;
}
void
audio_output_play(struct audio_output *ao)
{
- if (!ao->open)
- return;
+ g_mutex_lock(ao->mutex);
- notify_signal(&ao->notify);
+ if (audio_output_is_open(ao))
+ g_cond_signal(ao->cond);
+
+ g_mutex_unlock(ao->mutex);
}
void audio_output_pause(struct audio_output *ao)
@@ -181,29 +237,36 @@ void audio_output_pause(struct audio_output *ao)
mixer_auto_close()) */
mixer_auto_close(ao->mixer);
- ao_command_async(ao, AO_COMMAND_PAUSE);
+ g_mutex_lock(ao->mutex);
+ if (audio_output_is_open(ao))
+ ao_command_async(ao, AO_COMMAND_PAUSE);
+ g_mutex_unlock(ao->mutex);
}
-void audio_output_cancel(struct audio_output *ao)
+void
+audio_output_drain_async(struct audio_output *ao)
{
- ao_command_async(ao, AO_COMMAND_CANCEL);
+ g_mutex_lock(ao->mutex);
+ if (audio_output_is_open(ao))
+ ao_command_async(ao, AO_COMMAND_DRAIN);
+ g_mutex_unlock(ao->mutex);
}
-static void
-audio_output_close_locked(struct audio_output *ao)
+void audio_output_cancel(struct audio_output *ao)
{
- assert(ao != NULL);
- assert(!ao->open || ao->fail_timer == NULL);
-
- if (ao->mixer != NULL)
- mixer_auto_close(ao->mixer);
+ g_mutex_lock(ao->mutex);
+ if (audio_output_is_open(ao))
+ ao_command_async(ao, AO_COMMAND_CANCEL);
+ g_mutex_unlock(ao->mutex);
+}
- if (ao->open)
- ao_command_locked(ao, AO_COMMAND_CLOSE);
- else if (ao->fail_timer != NULL) {
- g_timer_destroy(ao->fail_timer);
- ao->fail_timer = NULL;
- }
+void
+audio_output_release(struct audio_output *ao)
+{
+ if (ao->always_on)
+ audio_output_pause(ao);
+ else
+ audio_output_close(ao);
}
void audio_output_close(struct audio_output *ao)
@@ -223,7 +286,9 @@ void audio_output_finish(struct audio_output *ao)
assert(ao->fail_timer == NULL);
if (ao->thread != NULL) {
+ g_mutex_lock(ao->mutex);
ao_command(ao, AO_COMMAND_KILL);
+ g_mutex_unlock(ao->mutex);
g_thread_join(ao->thread);
}
@@ -232,6 +297,16 @@ void audio_output_finish(struct audio_output *ao)
ao_plugin_finish(ao->plugin, ao->data);
- notify_deinit(&ao->notify);
+ 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);
}
diff --git a/src/output_control.h b/src/output_control.h
index ce3abe3f6..7f4f4a53c 100644
--- a/src/output_control.h
+++ b/src/output_control.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -38,7 +38,19 @@ audio_output_quark(void)
bool
audio_output_init(struct audio_output *ao, const struct config_param *param,
- GError **error);
+ GError **error_r);
+
+/**
+ * Enables the device.
+ */
+void
+audio_output_enable(struct audio_output *ao);
+
+/**
+ * Disables the device.
+ */
+void
+audio_output_disable(struct audio_output *ao);
/**
* Opens or closes the device, depending on the "enabled" flag.
@@ -55,8 +67,20 @@ audio_output_play(struct audio_output *ao);
void audio_output_pause(struct audio_output *ao);
+void
+audio_output_drain_async(struct audio_output *ao);
+
void audio_output_cancel(struct audio_output *ao);
+
void audio_output_close(struct audio_output *ao);
+
+/**
+ * Closes the audio output, but if the "always_on" flag is set, put it
+ * into pause mode instead.
+ */
+void
+audio_output_release(struct audio_output *ao);
+
void audio_output_finish(struct audio_output *ao);
#endif
diff --git a/src/output_init.c b/src/output_init.c
index 927424324..f4700dfb2 100644
--- a/src/output_init.c
+++ b/src/output_init.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -17,21 +17,34 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "output_control.h"
#include "output_api.h"
#include "output_internal.h"
#include "output_list.h"
#include "audio_parser.h"
#include "mixer_control.h"
+#include "mixer_type.h"
+#include "mixer_list.h"
+#include "mixer/software_mixer_plugin.h"
+#include "filter_plugin.h"
+#include "filter_registry.h"
+#include "filter_config.h"
+#include "filter/chain_filter_plugin.h"
+#include "filter/autoconvert_filter_plugin.h"
+#include "filter/replay_gain_filter_plugin.h"
#include <glib.h>
+#include <assert.h>
+
#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "output"
#define AUDIO_OUTPUT_TYPE "type"
#define AUDIO_OUTPUT_NAME "name"
#define AUDIO_OUTPUT_FORMAT "format"
+#define AUDIO_FILTERS "filters"
static const struct audio_output_plugin *
audio_output_detect(GError **error)
@@ -56,46 +69,109 @@ audio_output_detect(GError **error)
return NULL;
}
+/**
+ * Determines the mixer type which should be used for the specified
+ * configuration block.
+ *
+ * This handles the deprecated options mixer_type (global) and
+ * mixer_enabled, if the mixer_type setting is not configured.
+ */
+static enum mixer_type
+audio_output_mixer_type(const struct config_param *param)
+{
+ /* read the local "mixer_type" setting */
+ const char *p = config_get_block_string(param, "mixer_type", NULL);
+ if (p != NULL)
+ return mixer_type_parse(p);
+
+ /* try the local "mixer_enabled" setting next (deprecated) */
+ if (!config_get_block_bool(param, "mixer_enabled", true))
+ return MIXER_TYPE_NONE;
+
+ /* fall back to the global "mixer_type" setting (also
+ deprecated) */
+ return mixer_type_parse(config_get_string("mixer_type", "hardware"));
+}
+
+static struct mixer *
+audio_output_load_mixer(void *ao, const struct config_param *param,
+ const struct mixer_plugin *plugin,
+ struct filter *filter_chain,
+ GError **error_r)
+{
+ struct mixer *mixer;
+
+ switch (audio_output_mixer_type(param)) {
+ case MIXER_TYPE_NONE:
+ case MIXER_TYPE_UNKNOWN:
+ return NULL;
+
+ case MIXER_TYPE_HARDWARE:
+ if (plugin == NULL)
+ return NULL;
+
+ return mixer_new(plugin, ao, param, error_r);
+
+ case MIXER_TYPE_SOFTWARE:
+ mixer = mixer_new(&software_mixer_plugin, NULL, NULL, NULL);
+ assert(mixer != NULL);
+
+ filter_chain_append(filter_chain,
+ software_mixer_get_filter(mixer));
+ return mixer;
+ }
+
+ assert(false);
+ return NULL;
+}
+
bool
audio_output_init(struct audio_output *ao, const struct config_param *param,
- GError **error)
+ GError **error_r)
{
- const char *format;
const struct audio_output_plugin *plugin = NULL;
+ GError *error = NULL;
if (param) {
- const char *type = NULL;
+ const char *p;
- type = config_get_block_string(param, AUDIO_OUTPUT_TYPE, NULL);
- if (type == NULL) {
- g_set_error(error, audio_output_quark(), 0,
+ 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(type);
+ plugin = audio_output_plugin_get(p);
if (plugin == NULL) {
- g_set_error(error, audio_output_quark(), 0,
- "No such audio output plugin: %s",
- type);
+ 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) {
- g_set_error(error, audio_output_quark(), 0,
+ g_set_error(error_r, audio_output_quark(), 0,
"Missing \"name\" configuration");
return false;
}
- format = config_get_block_string(param, AUDIO_OUTPUT_FORMAT,
+ p = config_get_block_string(param, AUDIO_OUTPUT_FORMAT,
NULL);
+ if (p != NULL) {
+ bool success =
+ audio_format_parse(&ao->config_audio_format,
+ p, true, error_r);
+ if (!success)
+ return false;
+ } 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);
+ plugin = audio_output_detect(error_r);
if (plugin == NULL)
return false;
@@ -103,44 +179,115 @@ audio_output_init(struct audio_output *ao, const struct config_param *param,
plugin->name);
ao->name = "default detected output";
- format = NULL;
+
+ audio_format_clear(&ao->config_audio_format);
}
ao->plugin = plugin;
+ ao->always_on = config_get_block_bool(param, "always_on", false);
ao->enabled = config_get_block_bool(param, "enabled", true);
+ ao->really_enabled = false;
ao->open = false;
ao->pause = false;
ao->fail_timer = NULL;
- pcm_convert_init(&ao->convert_state);
+ pcm_buffer_init(&ao->cross_fade_buffer);
- ao->config_audio_format = format != NULL;
- if (ao->config_audio_format) {
- bool ret;
+ /* set up the filter chain */
- ret = audio_format_parse(&ao->out_audio_format, format,
- error);
- if (!ret)
- return false;
+ 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)) {
+ struct filter *normalize_filter =
+ filter_new(&normalize_filter_plugin, NULL, NULL);
+ assert(normalize_filter != NULL);
+
+ filter_chain_append(ao->filter,
+ autoconvert_filter_new(normalize_filter));
+ }
+
+ filter_chain_parse(ao->filter,
+ config_get_block_string(param, AUDIO_FILTERS, ""),
+ &error
+ );
+
+ // It's not really fatal - Part of the filter chain has been set up already
+ // and even an empty one will work (if only with unexpected behaviour)
+ if (error != NULL) {
+ g_warning("Failed to initialize filter chain for '%s': %s",
+ ao->name, error->message);
+ g_error_free(error);
}
ao->thread = NULL;
- notify_init(&ao->notify);
ao->command = AO_COMMAND_NONE;
ao->mutex = g_mutex_new();
+ ao->cond = g_cond_new();
ao->data = ao_plugin_init(plugin,
- ao->config_audio_format
- ? &ao->out_audio_format : NULL,
- param, error);
+ &ao->config_audio_format,
+ param, error_r);
if (ao->data == NULL)
return false;
- if (plugin->mixer_plugin != NULL &&
- config_get_block_bool(param, "mixer_enabled", true))
- ao->mixer = mixer_new(plugin->mixer_plugin, param);
- else
- ao->mixer = NULL;
+ ao->mixer = audio_output_load_mixer(ao->data, param,
+ plugin->mixer_plugin,
+ ao->filter, &error);
+ if (ao->mixer == NULL && error != NULL) {
+ g_warning("Failed to initialize hardware mixer for '%s': %s",
+ ao->name, error->message);
+ g_error_free(error);
+ }
+
+ /* use the hardware mixer for replay gain? */
+
+ if (strcmp(replay_gain_handler, "mixer") == 0) {
+ if (ao->mixer != NULL)
+ replay_gain_filter_set_mixer(ao->replay_gain_filter,
+ ao->mixer, 100);
+ else
+ g_warning("No such mixer for output '%s'", ao->name);
+ } else if (strcmp(replay_gain_handler, "software") != 0 &&
+ ao->replay_gain_filter != NULL) {
+ g_set_error(error_r, audio_output_quark(), 0,
+ "Invalid \"replay_gain_handler\" value");
+ return false;
+ }
+
+ /* 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;
}
diff --git a/src/output_internal.h b/src/output_internal.h
index b863c9ecc..18d431352 100644
--- a/src/output_internal.h
+++ b/src/output_internal.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -21,16 +21,33 @@
#define MPD_OUTPUT_INTERNAL_H
#include "audio_format.h"
-#include "pcm_convert.h"
-#include "notify.h"
+#include "pcm_buffer.h"
+
+#include <glib.h>
#include <time.h>
enum audio_output_command {
AO_COMMAND_NONE = 0,
+ AO_COMMAND_ENABLE,
+ AO_COMMAND_DISABLE,
AO_COMMAND_OPEN,
+
+ /**
+ * This command is invoked when the input audio format
+ * changes.
+ */
+ AO_COMMAND_REOPEN,
+
AO_COMMAND_CLOSE,
AO_COMMAND_PAUSE,
+
+ /**
+ * Drains the internal (hardware) buffers of the device. This
+ * operation may take a while to complete.
+ */
+ AO_COMMAND_DRAIN,
+
AO_COMMAND_CANCEL,
AO_COMMAND_KILL
};
@@ -60,10 +77,10 @@ struct audio_output {
struct mixer *mixer;
/**
- * This flag is true, when the audio_format of this device is
- * configured in mpd.conf.
+ * Shall this output always play something (i.e. silence),
+ * even when playback is stopped?
*/
- bool config_audio_format;
+ bool always_on;
/**
* Has the user enabled this device?
@@ -71,6 +88,12 @@ struct audio_output {
bool enabled;
/**
+ * Is this device actually enabled, i.e. the "enable" method
+ * has succeeded?
+ */
+ bool really_enabled;
+
+ /**
* Is the device (already) open and functional?
*
* This attribute may only be modified by the output thread.
@@ -94,6 +117,11 @@ struct audio_output {
GTimer *fail_timer;
/**
+ * The configured audio format.
+ */
+ struct audio_format config_audio_format;
+
+ /**
* The audio_format in which audio data is received from the
* player thread (which in turn receives it from the decoder).
*/
@@ -107,18 +135,55 @@ struct audio_output {
*/
struct audio_format out_audio_format;
- struct pcm_convert_state convert_state;
+ /**
+ * The buffer used to allocate the cross-fading result.
+ */
+ struct pcm_buffer cross_fade_buffer;
/**
- * The thread handle, or NULL if the output thread isn't
- * running.
+ * The filter object of this audio output. This is an
+ * instance of chain_filter_plugin.
*/
- GThread *thread;
+ struct filter *filter;
/**
- * Notify object for the thread.
+ * The replay_gain_filter_plugin instance of this audio
+ * output.
*/
- struct notify notify;
+ struct filter *replay_gain_filter;
+
+ /**
+ * The serial number of the last replay gain info. 0 means no
+ * replay gain info was available.
+ */
+ unsigned replay_gain_serial;
+
+ /**
+ * The replay_gain_filter_plugin instance of this audio
+ * output, to be applied to the second chunk during
+ * cross-fading.
+ */
+ struct filter *other_replay_gain_filter;
+
+ /**
+ * The serial number of the last replay gain info by the
+ * "other" chunk during cross-fading.
+ */
+ unsigned other_replay_gain_serial;
+
+ /**
+ * The convert_filter_plugin instance of this audio output.
+ * It is the last item in the filter chain, and is responsible
+ * for converting the input data into the appropriate format
+ * for this audio output.
+ */
+ struct filter *convert_filter;
+
+ /**
+ * The thread handle, or NULL if the output thread isn't
+ * running.
+ */
+ GThread *thread;
/**
* The next command to be performed by the output thread.
@@ -137,6 +202,12 @@ struct audio_output {
GMutex *mutex;
/**
+ * This condition object wakes up the output thread after
+ * #command has been set.
+ */
+ GCond *cond;
+
+ /**
* 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
diff --git a/src/output_list.c b/src/output_list.c
index 81de16649..8238f581b 100644
--- a/src/output_list.c
+++ b/src/output_list.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -17,9 +17,9 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "output_list.h"
#include "output_api.h"
-#include "config.h"
extern const struct audio_output_plugin shoutPlugin;
extern const struct audio_output_plugin null_output_plugin;
@@ -28,12 +28,16 @@ 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_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 jackPlugin;
+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;
const struct audio_output_plugin *audio_output_plugins[] = {
#ifdef HAVE_SHOUT
@@ -55,6 +59,9 @@ const struct audio_output_plugin *audio_output_plugins[] = {
#ifdef HAVE_OSS
&oss_output_plugin,
#endif
+#ifdef HAVE_OPENAL
+ &openal_output_plugin,
+#endif
#ifdef HAVE_OSX
&osxPlugin,
#endif
@@ -62,17 +69,26 @@ const struct audio_output_plugin *audio_output_plugins[] = {
&solaris_output_plugin,
#endif
#ifdef HAVE_PULSE
- &pulse_plugin,
+ &pulse_output_plugin,
#endif
#ifdef HAVE_MVP
&mvp_output_plugin,
#endif
#ifdef HAVE_JACK
- &jackPlugin,
+ &jack_output_plugin,
#endif
#ifdef ENABLE_HTTPD_OUTPUT
&httpd_output_plugin,
#endif
+#ifdef ENABLE_RECORDER_OUTPUT
+ &recorder_output_plugin,
+#endif
+#ifdef ENABLE_WINMM_OUTPUT
+ &winmm_output_plugin,
+#endif
+#ifdef ENABLE_FFADO_OUTPUT
+ &ffado_output_plugin,
+#endif
NULL
};
diff --git a/src/output_list.h b/src/output_list.h
index b6f82163c..d72bc224b 100644
--- a/src/output_list.h
+++ b/src/output_list.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
diff --git a/src/output_plugin.h b/src/output_plugin.h
index 13dba0d0b..36e17ed1b 100644
--- a/src/output_plugin.h
+++ b/src/output_plugin.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -67,6 +67,24 @@ struct audio_output_plugin {
void (*finish)(void *data);
/**
+ * Enable the device. This may allocate resources, preparing
+ * for the device to be opened. Enabling a device cannot
+ * 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
+ * NULL to ignore errors
+ * @return true on success, false on error
+ */
+ bool (*enable)(void *data, GError **error_r);
+
+ /**
+ * Disables the device. It is closed before this method is
+ * called.
+ */
+ void (*disable)(void *data);
+
+ /**
* Really open the device.
*
* @param audio_format the audio format in which data is going
@@ -83,6 +101,16 @@ struct audio_output_plugin {
void (*close)(void *data);
/**
+ * Returns a positive number if the output thread shall delay
+ * the next call to play() or pause(). This should be
+ * implemented instead of doing a sleep inside the plugin,
+ * because this allows MPD to listen to commands meanwhile.
+ *
+ * @return the number of milliseconds to wait
+ */
+ unsigned (*delay)(void *data);
+
+ /**
* Display metadata for the next chunk. Optional method,
* because not all devices can display metadata.
*/
@@ -99,6 +127,11 @@ struct audio_output_plugin {
GError **error);
/**
+ * Wait until the device has finished playing.
+ */
+ void (*drain)(void *data);
+
+ /**
* Try to cancel data which may still be in the device's
* buffers.
*/
@@ -150,6 +183,22 @@ ao_plugin_finish(const struct audio_output_plugin *plugin, void *data)
}
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;
+}
+
+static inline void
+ao_plugin_disable(const struct audio_output_plugin *plugin, void *data)
+{
+ if (plugin->disable != NULL)
+ plugin->disable(data);
+}
+
+static inline bool
ao_plugin_open(const struct audio_output_plugin *plugin,
void *data, struct audio_format *audio_format,
GError **error)
@@ -163,6 +212,14 @@ ao_plugin_close(const struct audio_output_plugin *plugin, void *data)
plugin->close(data);
}
+static inline unsigned
+ao_plugin_delay(const struct audio_output_plugin *plugin, void *data)
+{
+ return plugin->delay != NULL
+ ? plugin->delay(data)
+ : 0;
+}
+
static inline void
ao_plugin_send_tag(const struct audio_output_plugin *plugin,
void *data, const struct tag *tag)
@@ -180,6 +237,13 @@ ao_plugin_play(const struct audio_output_plugin *plugin,
}
static inline void
+ao_plugin_drain(const struct audio_output_plugin *plugin, void *data)
+{
+ if (plugin->drain != NULL)
+ plugin->drain(data);
+}
+
+static inline void
ao_plugin_cancel(const struct audio_output_plugin *plugin, void *data)
{
if (plugin->cancel != NULL)
diff --git a/src/output_print.c b/src/output_print.c
index 11e53c32c..7a747ad2f 100644
--- a/src/output_print.c
+++ b/src/output_print.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -22,6 +22,7 @@
*
*/
+#include "config.h"
#include "output_print.h"
#include "output_internal.h"
#include "output_all.h"
diff --git a/src/output_print.h b/src/output_print.h
index aec6f0f87..5ad7e34c7 100644
--- a/src/output_print.h
+++ b/src/output_print.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
diff --git a/src/output_state.c b/src/output_state.c
index c7e6c8579..e1187b951 100644
--- a/src/output_state.c
+++ b/src/output_state.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -22,6 +22,7 @@
*
*/
+#include "config.h"
#include "output_state.h"
#include "output_internal.h"
#include "output_all.h"
@@ -34,8 +35,10 @@
#define AUDIO_DEVICE_STATE "audio_device_state:"
+unsigned audio_output_state_version;
+
void
-saveAudioDevicesState(FILE *fp)
+audio_output_state_save(FILE *fp)
{
unsigned n = audio_output_count();
@@ -49,35 +52,40 @@ saveAudioDevicesState(FILE *fp)
}
}
-void
-readAudioDevicesState(FILE *fp)
+bool
+audio_output_state_read(const char *line)
{
- char buffer[1024];
+ long value;
+ char *endptr;
+ const char *name;
+ struct audio_output *ao;
- while (fgets(buffer, sizeof(buffer), fp)) {
- char *c, *name;
- struct audio_output *ao;
+ if (!g_str_has_prefix(line, AUDIO_DEVICE_STATE))
+ return false;
- g_strchomp(buffer);
+ line += sizeof(AUDIO_DEVICE_STATE) - 1;
- if (!g_str_has_prefix(buffer, AUDIO_DEVICE_STATE))
- continue;
+ value = strtol(line, &endptr, 10);
+ if (*endptr != ':' || (value != 0 && value != 1))
+ return false;
- c = strchr(buffer, ':');
- if (!c || !(++c))
- goto errline;
+ if (value != 0)
+ /* state is "enabled": no-op */
+ return true;
- name = strchr(c, ':');
- if (!name || !(++name))
- goto errline;
+ name = endptr + 1;
+ ao = audio_output_find(name);
+ if (ao == NULL) {
+ g_debug("Ignoring device state for '%s'", name);
+ return true;
+ }
- ao = audio_output_find(name);
- if (ao != NULL && atoi(c) == 0)
- ao->enabled = false;
+ ao->enabled = false;
+ return true;
+}
- continue;
-errline:
- /* nonfatal */
- g_warning("invalid line in state_file: %s\n", buffer);
- }
+unsigned
+audio_output_state_get_version(void)
+{
+ return audio_output_state_version;
}
diff --git a/src/output_state.h b/src/output_state.h
index 8592574ab..962ccd97a 100644
--- a/src/output_state.h
+++ b/src/output_state.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -25,12 +25,21 @@
#ifndef OUTPUT_STATE_H
#define OUTPUT_STATE_H
+#include <stdbool.h>
#include <stdio.h>
-void
-readAudioDevicesState(FILE *fp);
+bool
+audio_output_state_read(const char *line);
void
-saveAudioDevicesState(FILE *fp);
+audio_output_state_save(FILE *fp);
+
+/**
+ * Generates a version number for the current state of the audio
+ * outputs. This is used by timer_save_state_file() to determine
+ * whether the state has changed and the state file should be saved.
+ */
+unsigned
+audio_output_state_get_version(void);
#endif
diff --git a/src/output_thread.c b/src/output_thread.c
index 035cf99c1..4e0446791 100644
--- a/src/output_thread.c
+++ b/src/output_thread.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -17,12 +17,18 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "output_thread.h"
#include "output_api.h"
#include "output_internal.h"
#include "chunk.h"
#include "pipe.h"
#include "player_control.h"
+#include "pcm_mix.h"
+#include "filter_plugin.h"
+#include "filter/convert_filter_plugin.h"
+#include "filter/replay_gain_filter_plugin.h"
+#include "mpd_error.h"
#include <glib.h>
@@ -37,80 +43,434 @@ static void ao_command_finished(struct audio_output *ao)
{
assert(ao->command != AO_COMMAND_NONE);
ao->command = AO_COMMAND_NONE;
+
+ g_mutex_unlock(ao->mutex);
notify_signal(&audio_output_client_notify);
+ g_mutex_lock(ao->mutex);
+}
+
+static bool
+ao_enable(struct audio_output *ao)
+{
+ GError *error = NULL;
+ bool success;
+
+ if (ao->really_enabled)
+ return true;
+
+ g_mutex_unlock(ao->mutex);
+ success = ao_plugin_enable(ao->plugin, ao->data, &error);
+ g_mutex_lock(ao->mutex);
+ if (!success) {
+ g_warning("Failed to enable \"%s\" [%s]: %s\n",
+ ao->name, ao->plugin->name, error->message);
+ g_error_free(error);
+ return false;
+ }
+
+ ao->really_enabled = true;
+ return true;
+}
+
+static void
+ao_close(struct audio_output *ao, bool drain);
+
+static void
+ao_disable(struct audio_output *ao)
+{
+ if (ao->open)
+ ao_close(ao, false);
+
+ if (ao->really_enabled) {
+ ao->really_enabled = false;
+
+ g_mutex_unlock(ao->mutex);
+ ao_plugin_disable(ao->plugin, ao->data);
+ g_mutex_lock(ao->mutex);
+ }
+}
+
+static const struct audio_format *
+ao_filter_open(struct audio_output *ao,
+ struct audio_format *audio_format,
+ GError **error_r)
+{
+ assert(audio_format_valid(audio_format));
+
+ /* the replay_gain filter cannot fail here */
+ if (ao->replay_gain_filter != NULL)
+ filter_open(ao->replay_gain_filter, audio_format, error_r);
+ if (ao->other_replay_gain_filter != NULL)
+ filter_open(ao->other_replay_gain_filter, audio_format,
+ error_r);
+
+ const struct audio_format *af
+ = filter_open(ao->filter, audio_format, error_r);
+ if (af == NULL) {
+ if (ao->replay_gain_filter != NULL)
+ filter_close(ao->replay_gain_filter);
+ if (ao->other_replay_gain_filter != NULL)
+ filter_close(ao->other_replay_gain_filter);
+ }
+
+ return af;
+}
+
+static void
+ao_filter_close(struct audio_output *ao)
+{
+ if (ao->replay_gain_filter != NULL)
+ filter_close(ao->replay_gain_filter);
+ if (ao->other_replay_gain_filter != NULL)
+ filter_close(ao->other_replay_gain_filter);
+
+ filter_close(ao->filter);
+}
+
+static void
+ao_open(struct audio_output *ao)
+{
+ bool success;
+ GError *error = NULL;
+ const struct audio_format *filter_audio_format;
+ struct audio_format_string af_string;
+
+ assert(!ao->open);
+ assert(ao->pipe != NULL);
+ assert(ao->chunk == NULL);
+ assert(audio_format_valid(&ao->in_audio_format));
+
+ if (ao->fail_timer != NULL) {
+ /* this can only happen when this
+ output thread fails while
+ audio_output_open() is run in the
+ player thread */
+ g_timer_destroy(ao->fail_timer);
+ ao->fail_timer = NULL;
+ }
+
+ /* enable the device (just in case the last enable has failed) */
+
+ if (!ao_enable(ao))
+ /* still no luck */
+ return;
+
+ /* open the filter */
+
+ filter_audio_format = ao_filter_open(ao, &ao->in_audio_format, &error);
+ if (filter_audio_format == NULL) {
+ g_warning("Failed to open filter for \"%s\" [%s]: %s",
+ ao->name, ao->plugin->name, error->message);
+ g_error_free(error);
+
+ ao->fail_timer = g_timer_new();
+ return;
+ }
+
+ assert(audio_format_valid(filter_audio_format));
+
+ ao->out_audio_format = *filter_audio_format;
+ audio_format_mask_apply(&ao->out_audio_format,
+ &ao->config_audio_format);
+
+ g_mutex_unlock(ao->mutex);
+ success = ao_plugin_open(ao->plugin, ao->data,
+ &ao->out_audio_format,
+ &error);
+ g_mutex_lock(ao->mutex);
+
+ assert(!ao->open);
+
+ if (!success) {
+ g_warning("Failed to open \"%s\" [%s]: %s",
+ ao->name, ao->plugin->name, error->message);
+ g_error_free(error);
+
+ ao_filter_close(ao);
+ ao->fail_timer = g_timer_new();
+ return;
+ }
+
+ convert_filter_set(ao->convert_filter, &ao->out_audio_format);
+
+ ao->open = true;
+
+ g_debug("opened plugin=%s name=\"%s\" "
+ "audio_format=%s",
+ ao->plugin->name, ao->name,
+ audio_format_to_string(&ao->out_audio_format, &af_string));
+
+ if (!audio_format_equals(&ao->in_audio_format,
+ &ao->out_audio_format))
+ g_debug("converting from %s",
+ audio_format_to_string(&ao->in_audio_format,
+ &af_string));
}
static void
-ao_close(struct audio_output *ao)
+ao_close(struct audio_output *ao, bool drain)
{
assert(ao->open);
ao->pipe = NULL;
- g_mutex_lock(ao->mutex);
ao->chunk = NULL;
ao->open = false;
+
g_mutex_unlock(ao->mutex);
+ if (drain)
+ ao_plugin_drain(ao->plugin, ao->data);
+ else
+ ao_plugin_cancel(ao->plugin, ao->data);
+
ao_plugin_close(ao->plugin, ao->data);
- pcm_convert_deinit(&ao->convert_state);
+ ao_filter_close(ao);
+
+ g_mutex_lock(ao->mutex);
g_debug("closed plugin=%s name=\"%s\"", ao->plugin->name, ao->name);
}
-static bool
-ao_play_chunk(struct audio_output *ao, const struct music_chunk *chunk)
+static void
+ao_reopen_filter(struct audio_output *ao)
{
- const char *data = chunk->data;
- size_t size = chunk->length;
+ const struct audio_format *filter_audio_format;
GError *error = NULL;
+ ao_filter_close(ao);
+ filter_audio_format = ao_filter_open(ao, &ao->in_audio_format, &error);
+ if (filter_audio_format == NULL) {
+ g_warning("Failed to open filter for \"%s\" [%s]: %s",
+ ao->name, ao->plugin->name, error->message);
+ g_error_free(error);
+
+ /* this is a little code duplication fro ao_close(),
+ but we cannot call this function because we must
+ not call filter_close(ao->filter) again */
+
+ ao->pipe = NULL;
+
+ ao->chunk = NULL;
+ ao->open = false;
+ ao->fail_timer = g_timer_new();
+
+ g_mutex_unlock(ao->mutex);
+ ao_plugin_close(ao->plugin, ao->data);
+ g_mutex_lock(ao->mutex);
+
+ return;
+ }
+
+ convert_filter_set(ao->convert_filter, &ao->out_audio_format);
+}
+
+static void
+ao_reopen(struct audio_output *ao)
+{
+ if (!audio_format_fully_defined(&ao->config_audio_format)) {
+ if (ao->open) {
+ const struct music_pipe *mp = ao->pipe;
+ ao_close(ao, true);
+ ao->pipe = mp;
+ }
+
+ /* no audio format is configured: copy in->out, let
+ the output's open() method determine the effective
+ out_audio_format */
+ ao->out_audio_format = ao->in_audio_format;
+ audio_format_mask_apply(&ao->out_audio_format,
+ &ao->config_audio_format);
+ }
+
+ if (ao->open)
+ /* the audio format has changed, and all filters have
+ to be reconfigured */
+ ao_reopen_filter(ao);
+ else
+ ao_open(ao);
+}
+
+/**
+ * Wait until the output's delay reaches zero.
+ *
+ * @return true if playback should be continued, false if a command
+ * was issued
+ */
+static bool
+ao_wait(struct audio_output *ao)
+{
+ while (true) {
+ unsigned delay = ao_plugin_delay(ao->plugin, ao->data);
+ if (delay == 0)
+ return true;
+
+ GTimeVal tv;
+ g_get_current_time(&tv);
+ g_time_val_add(&tv, delay * 1000);
+ (void)g_cond_timed_wait(ao->cond, ao->mutex, &tv);
+
+ if (ao->command != AO_COMMAND_NONE)
+ return false;
+ }
+}
+
+static const char *
+ao_chunk_data(struct audio_output *ao, const struct music_chunk *chunk,
+ struct filter *replay_gain_filter,
+ unsigned *replay_gain_serial_p,
+ size_t *length_r)
+{
+ assert(chunk != NULL);
assert(!music_chunk_is_empty(chunk));
assert(music_chunk_check_format(chunk, &ao->in_audio_format));
- assert(size % audio_format_frame_size(&ao->in_audio_format) == 0);
- if (chunk->tag != NULL)
+ const char *data = chunk->data;
+ size_t length = chunk->length;
+
+ (void)ao;
+
+ assert(length % audio_format_frame_size(&ao->in_audio_format) == 0);
+
+ if (length > 0 && replay_gain_filter != NULL) {
+ if (chunk->replay_gain_serial != *replay_gain_serial_p) {
+ replay_gain_filter_set_info(replay_gain_filter,
+ chunk->replay_gain_serial != 0
+ ? &chunk->replay_gain_info
+ : NULL);
+ *replay_gain_serial_p = chunk->replay_gain_serial;
+ }
+
+ GError *error = NULL;
+ data = filter_filter(replay_gain_filter, data, length,
+ &length, &error);
+ if (data == NULL) {
+ g_warning("\"%s\" [%s] failed to filter: %s",
+ ao->name, ao->plugin->name, error->message);
+ g_error_free(error);
+ return NULL;
+ }
+ }
+
+ *length_r = length;
+ return data;
+}
+
+static const char *
+ao_filter_chunk(struct audio_output *ao, const struct music_chunk *chunk,
+ size_t *length_r)
+{
+ GError *error = NULL;
+
+ size_t length;
+ const char *data = ao_chunk_data(ao, chunk, ao->replay_gain_filter,
+ &ao->replay_gain_serial, &length);
+ if (data == NULL)
+ return NULL;
+
+ if (length == 0) {
+ /* empty chunk, nothing to do */
+ *length_r = 0;
+ return data;
+ }
+
+ /* cross-fade */
+
+ if (chunk->other != NULL) {
+ size_t other_length;
+ const char *other_data =
+ ao_chunk_data(ao, chunk->other,
+ ao->other_replay_gain_filter,
+ &ao->other_replay_gain_serial,
+ &other_length);
+ if (other_data == NULL)
+ return NULL;
+
+ if (other_length == 0) {
+ *length_r = 0;
+ return data;
+ }
+
+ /* if the "other" chunk is longer, then that trailer
+ is used as-is, without mixing; it is part of the
+ "next" song being faded in, and if there's a rest,
+ it means cross-fading ends here */
+
+ if (length > other_length)
+ length = other_length;
+
+ 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);
+
+ data = dest;
+ length = other_length;
+ }
+
+ /* apply filter chain */
+
+ data = filter_filter(ao->filter, data, length, &length, &error);
+ if (data == NULL) {
+ g_warning("\"%s\" [%s] failed to filter: %s",
+ ao->name, ao->plugin->name, error->message);
+ g_error_free(error);
+ return NULL;
+ }
+
+ *length_r = length;
+ return data;
+}
+
+static bool
+ao_play_chunk(struct audio_output *ao, const struct music_chunk *chunk)
+{
+ GError *error = NULL;
+
+ assert(ao != NULL);
+ assert(ao->filter != NULL);
+
+ if (chunk->tag != NULL) {
+ g_mutex_unlock(ao->mutex);
ao_plugin_send_tag(ao->plugin, ao->data, chunk->tag);
+ g_mutex_lock(ao->mutex);
+ }
- if (size == 0)
- return true;
+ size_t size;
+ const char *data = ao_filter_chunk(ao, chunk, &size);
+ if (data == NULL) {
+ ao_close(ao, false);
- if (!audio_format_equals(&ao->in_audio_format,
- &ao->out_audio_format)) {
- data = pcm_convert(&ao->convert_state,
- &ao->in_audio_format, data, size,
- &ao->out_audio_format, &size);
-
- /* under certain circumstances, pcm_convert() may
- return an empty buffer - this condition should be
- investigated further, but for now, do this check as
- a workaround: */
- if (data == NULL)
- return true;
+ /* don't automatically reopen this device for 10
+ seconds */
+ ao->fail_timer = g_timer_new();
+ return false;
}
while (size > 0 && ao->command == AO_COMMAND_NONE) {
size_t nbytes;
+ if (!ao_wait(ao))
+ break;
+
+ g_mutex_unlock(ao->mutex);
nbytes = ao_plugin_play(ao->plugin, ao->data, data, size,
&error);
+ g_mutex_lock(ao->mutex);
if (nbytes == 0) {
/* play()==0 means failure */
g_warning("\"%s\" [%s] failed to play: %s",
ao->name, ao->plugin->name, error->message);
g_error_free(error);
- ao_plugin_cancel(ao->plugin, ao->data);
- ao_close(ao);
+ ao_close(ao, false);
/* don't automatically reopen this device for
10 seconds */
- g_mutex_lock(ao->mutex);
-
assert(ao->fail_timer == NULL);
ao->fail_timer = g_timer_new();
- g_mutex_unlock(ao->mutex);
return false;
}
@@ -124,32 +484,45 @@ ao_play_chunk(struct audio_output *ao, const struct music_chunk *chunk)
return true;
}
-static void ao_play(struct audio_output *ao)
+static const struct music_chunk *
+ao_next_chunk(struct audio_output *ao)
+{
+ return ao->chunk != NULL
+ /* continue the previous play() call */
+ ? ao->chunk->next
+ /* get the first chunk from the pipe */
+ : music_pipe_peek(ao->pipe);
+}
+
+/**
+ * Plays all remaining chunks, until the tail of the pipe has been
+ * reached (and no more chunks are queued), or until a command is
+ * received.
+ *
+ * @return true if at least one chunk has been available, false if the
+ * tail of the pipe was already reached
+ */
+static bool
+ao_play(struct audio_output *ao)
{
bool success;
const struct music_chunk *chunk;
assert(ao->pipe != NULL);
- g_mutex_lock(ao->mutex);
- chunk = ao->chunk;
- if (chunk != NULL)
- /* continue the previous play() call */
- chunk = chunk->next;
- else
- chunk = music_pipe_peek(ao->pipe);
+ chunk = ao_next_chunk(ao);
+ if (chunk == NULL)
+ /* no chunk available */
+ return false;
+
ao->chunk_finished = false;
while (chunk != NULL && ao->command == AO_COMMAND_NONE) {
assert(!ao->chunk_finished);
ao->chunk = chunk;
- g_mutex_unlock(ao->mutex);
success = ao_play_chunk(ao, chunk);
-
- g_mutex_lock(ao->mutex);
-
if (!success) {
assert(ao->chunk == NULL);
break;
@@ -160,23 +533,35 @@ static void ao_play(struct audio_output *ao)
}
ao->chunk_finished = true;
+
g_mutex_unlock(ao->mutex);
+ player_lock_signal();
+ g_mutex_lock(ao->mutex);
- notify_signal(&pc.notify);
+ return true;
}
static void ao_pause(struct audio_output *ao)
{
bool ret;
+ g_mutex_unlock(ao->mutex);
ao_plugin_cancel(ao->plugin, ao->data);
+ g_mutex_lock(ao->mutex);
+
ao->pause = true;
ao_command_finished(ao);
do {
+ if (!ao_wait(ao))
+ break;
+
+ g_mutex_unlock(ao->mutex);
ret = ao_plugin_pause(ao->plugin, ao->data);
+ g_mutex_lock(ao->mutex);
+
if (!ret) {
- ao_close(ao);
+ ao_close(ao, false);
break;
}
} while (ao->command == AO_COMMAND_NONE);
@@ -187,64 +572,31 @@ static void ao_pause(struct audio_output *ao)
static gpointer audio_output_task(gpointer arg)
{
struct audio_output *ao = arg;
- bool ret;
- GError *error;
+
+ g_mutex_lock(ao->mutex);
while (1) {
switch (ao->command) {
case AO_COMMAND_NONE:
break;
- case AO_COMMAND_OPEN:
- assert(!ao->open);
- assert(ao->pipe != NULL);
- assert(ao->chunk == NULL);
-
- if (ao->fail_timer != NULL) {
- /* this can only happen when this
- output thread fails while
- audio_output_open() is run in the
- player thread */
- g_timer_destroy(ao->fail_timer);
- ao->fail_timer = NULL;
- }
-
- error = NULL;
- ret = ao_plugin_open(ao->plugin, ao->data,
- &ao->out_audio_format,
- &error);
-
- assert(!ao->open);
- if (ret) {
- pcm_convert_init(&ao->convert_state);
+ case AO_COMMAND_ENABLE:
+ ao_enable(ao);
+ ao_command_finished(ao);
+ break;
- g_mutex_lock(ao->mutex);
- ao->open = true;
- g_mutex_unlock(ao->mutex);
+ case AO_COMMAND_DISABLE:
+ ao_disable(ao);
+ ao_command_finished(ao);
+ break;
- g_debug("opened plugin=%s name=\"%s\" "
- "audio_format=%u:%u:%u",
- ao->plugin->name,
- ao->name,
- ao->out_audio_format.sample_rate,
- ao->out_audio_format.bits,
- ao->out_audio_format.channels);
-
- if (!audio_format_equals(&ao->in_audio_format,
- &ao->out_audio_format))
- g_debug("converting from %u:%u:%u",
- ao->in_audio_format.sample_rate,
- ao->in_audio_format.bits,
- ao->in_audio_format.channels);
- } else {
- g_warning("Failed to open \"%s\" [%s]: %s",
- ao->name, ao->plugin->name,
- error->message);
- g_error_free(error);
-
- ao->fail_timer = g_timer_new();
- }
+ case AO_COMMAND_OPEN:
+ ao_open(ao);
+ ao_command_finished(ao);
+ break;
+ case AO_COMMAND_REOPEN:
+ ao_reopen(ao);
ao_command_finished(ao);
break;
@@ -252,11 +604,7 @@ static gpointer audio_output_task(gpointer arg)
assert(ao->open);
assert(ao->pipe != NULL);
- ao->pipe = NULL;
- ao->chunk = NULL;
-
- ao_plugin_cancel(ao->plugin, ao->data);
- ao_close(ao);
+ ao_close(ao, false);
ao_command_finished(ao);
break;
@@ -277,38 +625,46 @@ static gpointer audio_output_task(gpointer arg)
the new command first */
continue;
+ case AO_COMMAND_DRAIN:
+ if (ao->open) {
+ assert(ao->chunk == NULL);
+ assert(music_pipe_peek(ao->pipe) == NULL);
+
+ g_mutex_unlock(ao->mutex);
+ ao_plugin_drain(ao->plugin, ao->data);
+ g_mutex_lock(ao->mutex);
+ }
+
+ ao_command_finished(ao);
+ continue;
+
case AO_COMMAND_CANCEL:
ao->chunk = NULL;
if (ao->open)
ao_plugin_cancel(ao->plugin, ao->data);
-
- /* we must clear the notification now, because
- the notify_wait() call below must wait
- until audio_output_all_cancel() has cleared
- the pipe; if another notification happens
- to be still pending, we get a race
- condition with a crash or an assertion
- failure */
- notify_clear(&ao->notify);
-
ao_command_finished(ao);
/* the player thread will now clear our music
pipe - wait for a notify, to give it some
time */
- notify_wait(&ao->notify);
+ if (ao->command == AO_COMMAND_NONE)
+ g_cond_wait(ao->cond, ao->mutex);
continue;
case AO_COMMAND_KILL:
ao->chunk = NULL;
ao_command_finished(ao);
+ g_mutex_unlock(ao->mutex);
return NULL;
}
- if (ao->open)
- ao_play(ao);
+ if (ao->open && ao_play(ao))
+ /* don't wait for an event if there are more
+ chunks in the pipe */
+ continue;
- notify_wait(&ao->notify);
+ if (ao->command == AO_COMMAND_NONE)
+ g_cond_wait(ao->cond, ao->mutex);
}
}
@@ -319,5 +675,5 @@ void audio_output_thread_start(struct audio_output *ao)
assert(ao->command == AO_COMMAND_NONE);
if (!(ao->thread = g_thread_create(audio_output_task, ao, true, &e)))
- g_error("Failed to spawn output task: %s\n", e->message);
+ MPD_ERROR("Failed to spawn output task: %s\n", e->message);
}
diff --git a/src/output_thread.h b/src/output_thread.h
index a79c3b250..1ee0856f2 100644
--- a/src/output_thread.h
+++ b/src/output_thread.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
diff --git a/src/page.c b/src/page.c
index 5ea03cd02..59369cb34 100644
--- a/src/page.c
+++ b/src/page.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -17,6 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "page.h"
#include <glib.h>
diff --git a/src/page.h b/src/page.h
index a150e3123..652c4ad6e 100644
--- a/src/page.h
+++ b/src/page.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
diff --git a/src/path.c b/src/path.c
index fc73ee7c9..5e39c1636 100644
--- a/src/path.c
+++ b/src/path.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -17,8 +17,10 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "path.h"
#include "conf.h"
+#include "mpd_error.h"
#include <glib.h>
@@ -63,7 +65,7 @@ path_set_fs_charset(const char *charset)
/* convert a space to ensure that the charset is valid */
test = g_convert(" ", 1, charset, "UTF-8", NULL, NULL, NULL);
if (test == NULL)
- g_error("invalid filesystem charset: %s", charset);
+ MPD_ERROR("invalid filesystem charset: %s", charset);
g_free(test);
g_free(fs_charset);
diff --git a/src/path.h b/src/path.h
index be845d9b1..512cd13ea 100644
--- a/src/path.h
+++ b/src/path.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
diff --git a/src/pcm_buffer.h b/src/pcm_buffer.h
index b143bd98f..73959ea03 100644
--- a/src/pcm_buffer.h
+++ b/src/pcm_buffer.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -65,8 +65,8 @@ pcm_buffer_get(struct pcm_buffer *buffer, size_t size)
/* free the old buffer */
g_free(buffer->buffer);
- /* allocate a new buffer; align at 64kB boundaries */
- buffer->size = (size | 0xffff) + 1;
+ /* allocate a new buffer; align at 8 kB boundaries */
+ buffer->size = ((size - 1) | 0x1fff) + 1;
buffer->buffer = g_malloc(buffer->size);
}
diff --git a/src/pcm_byteswap.c b/src/pcm_byteswap.c
new file mode 100644
index 000000000..6577319d4
--- /dev/null
+++ b/src/pcm_byteswap.c
@@ -0,0 +1,70 @@
+/*
+ * 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
new file mode 100644
index 000000000..005e75ded
--- /dev/null
+++ b/src/pcm_byteswap.h
@@ -0,0 +1,50 @@
+/*
+ * 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 969ddff32..34e72ca4e 100644
--- a/src/pcm_channels.c
+++ b/src/pcm_channels.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -17,16 +17,12 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "pcm_channels.h"
#include "pcm_buffer.h"
-#include <glib.h>
-
#include <assert.h>
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "pcm"
-
static void
pcm_convert_channels_16_1_to_2(int16_t *dest, const int16_t *src,
unsigned num_frames)
@@ -75,8 +71,8 @@ pcm_convert_channels_16_n_to_2(int16_t *dest,
const int16_t *
pcm_convert_channels_16(struct pcm_buffer *buffer,
- int8_t dest_channels,
- int8_t src_channels, const int16_t *src,
+ uint8_t dest_channels,
+ uint8_t src_channels, const int16_t *src,
size_t src_size, size_t *dest_size_r)
{
unsigned num_frames = src_size / src_channels / sizeof(*src);
@@ -92,11 +88,8 @@ pcm_convert_channels_16(struct pcm_buffer *buffer,
else if (dest_channels == 2)
pcm_convert_channels_16_n_to_2(dest, src_channels, src,
num_frames);
- else {
- g_warning("conversion %u->%u channels is not supported",
- src_channels, dest_channels);
+ else
return NULL;
- }
return dest;
}
@@ -149,8 +142,8 @@ pcm_convert_channels_24_n_to_2(int32_t *dest,
const int32_t *
pcm_convert_channels_24(struct pcm_buffer *buffer,
- int8_t dest_channels,
- int8_t src_channels, const int32_t *src,
+ uint8_t dest_channels,
+ uint8_t src_channels, const int32_t *src,
size_t src_size, size_t *dest_size_r)
{
unsigned num_frames = src_size / src_channels / sizeof(*src);
@@ -166,11 +159,8 @@ pcm_convert_channels_24(struct pcm_buffer *buffer,
else if (dest_channels == 2)
pcm_convert_channels_24_n_to_2(dest, src_channels, src,
num_frames);
- else {
- g_warning("conversion %u->%u channels is not supported",
- src_channels, dest_channels);
+ else
return NULL;
- }
return dest;
}
@@ -218,8 +208,8 @@ pcm_convert_channels_32_n_to_2(int32_t *dest,
const int32_t *
pcm_convert_channels_32(struct pcm_buffer *buffer,
- int8_t dest_channels,
- int8_t src_channels, const int32_t *src,
+ uint8_t dest_channels,
+ uint8_t src_channels, const int32_t *src,
size_t src_size, size_t *dest_size_r)
{
unsigned num_frames = src_size / src_channels / sizeof(*src);
@@ -235,11 +225,8 @@ pcm_convert_channels_32(struct pcm_buffer *buffer,
else if (dest_channels == 2)
pcm_convert_channels_32_n_to_2(dest, src_channels, src,
num_frames);
- else {
- g_warning("conversion %u->%u channels is not supported",
- src_channels, dest_channels);
+ else
return NULL;
- }
return dest;
}
diff --git a/src/pcm_channels.h b/src/pcm_channels.h
index accf4b07b..a23cbd364 100644
--- a/src/pcm_channels.h
+++ b/src/pcm_channels.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -38,8 +38,8 @@ struct pcm_buffer;
*/
const int16_t *
pcm_convert_channels_16(struct pcm_buffer *buffer,
- int8_t dest_channels,
- int8_t src_channels, const int16_t *src,
+ uint8_t dest_channels,
+ uint8_t 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,
- int8_t dest_channels,
- int8_t src_channels, const int32_t *src,
+ uint8_t dest_channels,
+ uint8_t 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,
- int8_t dest_channels,
- int8_t src_channels, const int32_t *src,
+ uint8_t dest_channels,
+ uint8_t 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 ebb4adff5..5fe89b53a 100644
--- a/src/pcm_convert.c
+++ b/src/pcm_convert.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -17,9 +17,12 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#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 <assert.h>
@@ -38,7 +41,9 @@ void pcm_convert_init(struct pcm_convert_state *state)
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)
@@ -46,42 +51,62 @@ void pcm_convert_deinit(struct pcm_convert_state *state)
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);
}
static const int16_t *
pcm_convert_16(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)
+ const struct audio_format *dest_format, size_t *dest_size_r,
+ GError **error_r)
{
const int16_t *buf;
size_t len;
- assert(dest_format->bits == 16);
+ assert(dest_format->format == SAMPLE_FORMAT_S16);
buf = pcm_convert_to_16(&state->format_buffer, &state->dither,
- src_format->bits, src_buffer, src_size,
+ src_format->format, src_buffer, src_size,
&len);
- if (!buf)
- g_error("pcm_convert_to_16() failed");
+ if (buf == NULL) {
+ g_set_error(error_r, pcm_convert_quark(), 0,
+ "Conversion from %s to 16 bit is not implemented",
+ sample_format_to_string(src_format->format));
+ return NULL;
+ }
if (src_format->channels != dest_format->channels) {
buf = pcm_convert_channels_16(&state->channels_buffer,
dest_format->channels,
src_format->channels,
buf, len, &len);
- if (!buf)
- g_error("pcm_convert_channels_16() failed");
+ if (buf == NULL) {
+ g_set_error(error_r, pcm_convert_quark(), 0,
+ "Conversion from %u to %u channels "
+ "is not implemented",
+ src_format->channels,
+ dest_format->channels);
+ return NULL;
+ }
}
- if (src_format->sample_rate != dest_format->sample_rate)
+ if (src_format->sample_rate != dest_format->sample_rate) {
buf = pcm_resample_16(&state->resample,
dest_format->channels,
src_format->sample_rate, buf, len,
- dest_format->sample_rate,
- &len);
+ dest_format->sample_rate, &len,
+ error_r);
+ if (buf == NULL)
+ 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;
@@ -91,71 +116,146 @@ static const int32_t *
pcm_convert_24(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)
+ const struct audio_format *dest_format, size_t *dest_size_r,
+ GError **error_r)
{
const int32_t *buf;
size_t len;
- assert(dest_format->bits == 24);
+ assert(dest_format->format == SAMPLE_FORMAT_S24_P32);
- buf = pcm_convert_to_24(&state->format_buffer, src_format->bits,
+ buf = pcm_convert_to_24(&state->format_buffer, src_format->format,
src_buffer, src_size, &len);
- if (!buf)
- g_error("pcm_convert_to_24() failed");
+ if (buf == NULL) {
+ g_set_error(error_r, pcm_convert_quark(), 0,
+ "Conversion from %s to 24 bit is not implemented",
+ sample_format_to_string(src_format->format));
+ return NULL;
+ }
if (src_format->channels != dest_format->channels) {
buf = pcm_convert_channels_24(&state->channels_buffer,
dest_format->channels,
src_format->channels,
buf, len, &len);
- if (!buf)
- g_error("pcm_convert_channels_24() failed");
+ if (buf == NULL) {
+ g_set_error(error_r, pcm_convert_quark(), 0,
+ "Conversion from %u to %u channels "
+ "is not implemented",
+ src_format->channels,
+ dest_format->channels);
+ return NULL;
+ }
}
- if (src_format->sample_rate != dest_format->sample_rate)
+ if (src_format->sample_rate != dest_format->sample_rate) {
buf = pcm_resample_24(&state->resample,
dest_format->channels,
src_format->sample_rate, buf, len,
- dest_format->sample_rate,
- &len);
+ dest_format->sample_rate, &len,
+ error_r);
+ if (buf == NULL)
+ 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,
const void *src_buffer, size_t src_size,
- const struct audio_format *dest_format,
- size_t *dest_size_r)
+ const struct audio_format *dest_format, size_t *dest_size_r,
+ GError **error_r)
{
const int32_t *buf;
size_t len;
- assert(dest_format->bits == 32);
+ assert(dest_format->format == SAMPLE_FORMAT_S32);
- buf = pcm_convert_to_32(&state->format_buffer, src_format->bits,
+ buf = pcm_convert_to_32(&state->format_buffer, src_format->format,
src_buffer, src_size, &len);
- if (!buf)
- g_error("pcm_convert_to_32() failed");
+ if (buf == NULL) {
+ g_set_error(error_r, pcm_convert_quark(), 0,
+ "Conversion from %s to 24 bit is not implemented",
+ sample_format_to_string(src_format->format));
+ return NULL;
+ }
if (src_format->channels != dest_format->channels) {
buf = pcm_convert_channels_32(&state->channels_buffer,
dest_format->channels,
src_format->channels,
buf, len, &len);
- if (!buf)
- g_error("pcm_convert_channels_32() failed");
+ if (buf == NULL) {
+ g_set_error(error_r, pcm_convert_quark(), 0,
+ "Conversion from %u to %u channels "
+ "is not implemented",
+ src_format->channels,
+ dest_format->channels);
+ return NULL;
+ }
}
- if (src_format->sample_rate != dest_format->sample_rate)
+ if (src_format->sample_rate != dest_format->sample_rate) {
buf = pcm_resample_32(&state->resample,
dest_format->channels,
src_format->sample_rate, buf, len,
- dest_format->sample_rate,
- &len);
+ dest_format->sample_rate, &len,
+ error_r);
+ if (buf == NULL)
+ 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;
@@ -166,26 +266,38 @@ pcm_convert(struct pcm_convert_state *state,
const struct audio_format *src_format,
const void *src, size_t src_size,
const struct audio_format *dest_format,
- size_t *dest_size_r)
+ size_t *dest_size_r,
+ GError **error_r)
{
- switch (dest_format->bits) {
- case 16:
+ switch (dest_format->format) {
+ case SAMPLE_FORMAT_S16:
return pcm_convert_16(state,
src_format, src, src_size,
- dest_format, dest_size_r);
+ 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 24:
+ case SAMPLE_FORMAT_S24_P32:
return pcm_convert_24(state,
src_format, src, src_size,
- dest_format, dest_size_r);
+ dest_format, dest_size_r,
+ error_r);
- case 32:
+ case SAMPLE_FORMAT_S32:
return pcm_convert_32(state,
src_format, src, src_size,
- dest_format, dest_size_r);
+ dest_format, dest_size_r,
+ error_r);
default:
- g_error("cannot convert to %u bit\n", dest_format->bits);
+ g_set_error(error_r, pcm_convert_quark(), 0,
+ "PCM conversion to %s is not implemented",
+ sample_format_to_string(dest_format->format));
return NULL;
}
}
diff --git a/src/pcm_convert.h b/src/pcm_convert.h
index be08ad8a8..01ba2c787 100644
--- a/src/pcm_convert.h
+++ b/src/pcm_convert.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -39,10 +39,22 @@ 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
+pcm_convert_quark(void)
+{
+ return g_quark_from_static_string("pcm_convert");
+}
+
/**
* Initializes a pcm_convert_state object.
*/
@@ -63,13 +75,16 @@ 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
- * @return the destination buffer
+ * @param error_r location to store the error occuring, or NULL to
+ * ignore errors
+ * @return the destination buffer, or NULL on error
*/
const void *
pcm_convert(struct pcm_convert_state *state,
const struct audio_format *src_format,
const void *src, size_t src_size,
const struct audio_format *dest_format,
- size_t *dest_size_r);
+ size_t *dest_size_r,
+ GError **error_r);
#endif
diff --git a/src/pcm_dither.c b/src/pcm_dither.c
index 45c11790c..03388f0e0 100644
--- a/src/pcm_dither.c
+++ b/src/pcm_dither.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -17,6 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "pcm_dither.h"
#include "pcm_prng.h"
diff --git a/src/pcm_dither.h b/src/pcm_dither.h
index a5c0c3bae..dafae957f 100644
--- a/src/pcm_dither.h
+++ b/src/pcm_dither.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
diff --git a/src/pcm_format.c b/src/pcm_format.c
index 0e686e17c..3fd76a987 100644
--- a/src/pcm_format.c
+++ b/src/pcm_format.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -17,11 +17,11 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "pcm_format.h"
#include "pcm_dither.h"
#include "pcm_buffer.h"
-
-#include <glib.h>
+#include "pcm_pack.h"
static void
pcm_convert_8_to_16(int16_t *out, const int8_t *in,
@@ -49,16 +49,29 @@ pcm_convert_32_to_16(struct pcm_dither *dither,
pcm_dither_32_to_16(dither, out, in, num_samples);
}
+static int32_t *
+pcm_convert_24_to_24p32(struct pcm_buffer *buffer, const uint8_t *src,
+ unsigned num_samples)
+{
+ int32_t *dest = pcm_buffer_get(buffer, num_samples * 4);
+ pcm_unpack_24(dest, src, num_samples, false);
+ return dest;
+}
+
const int16_t *
pcm_convert_to_16(struct pcm_buffer *buffer, struct pcm_dither *dither,
- uint8_t bits, const void *src,
+ 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;
+
+ switch (src_format) {
+ case SAMPLE_FORMAT_UNDEFINED:
+ break;
- switch (bits) {
- case 8:
+ case SAMPLE_FORMAT_S8:
num_samples = src_size;
*dest_size_r = src_size * sizeof(*dest);
dest = pcm_buffer_get(buffer, *dest_size_r);
@@ -68,11 +81,24 @@ pcm_convert_to_16(struct pcm_buffer *buffer, struct pcm_dither *dither,
num_samples);
return dest;
- case 16:
+ case SAMPLE_FORMAT_S16:
*dest_size_r = src_size;
return src;
- case 24:
+ 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);
@@ -82,7 +108,7 @@ pcm_convert_to_16(struct pcm_buffer *buffer, struct pcm_dither *dither,
num_samples);
return dest;
- case 32:
+ case SAMPLE_FORMAT_S32:
num_samples = src_size / 4;
*dest_size_r = num_samples * sizeof(*dest);
dest = pcm_buffer_get(buffer, *dest_size_r);
@@ -93,7 +119,6 @@ pcm_convert_to_16(struct pcm_buffer *buffer, struct pcm_dither *dither,
return dest;
}
- g_warning("only 8 or 16 bits are supported for conversion!\n");
return NULL;
}
@@ -129,14 +154,17 @@ pcm_convert_32_to_24(int32_t *out, const int16_t *in,
const int32_t *
pcm_convert_to_24(struct pcm_buffer *buffer,
- uint8_t bits, const void *src,
+ enum sample_format src_format, const void *src,
size_t src_size, size_t *dest_size_r)
{
unsigned num_samples;
int32_t *dest;
- switch (bits) {
- case 8:
+ switch (src_format) {
+ case SAMPLE_FORMAT_UNDEFINED:
+ break;
+
+ case SAMPLE_FORMAT_S8:
num_samples = src_size;
*dest_size_r = src_size * sizeof(*dest);
dest = pcm_buffer_get(buffer, *dest_size_r);
@@ -145,7 +173,7 @@ pcm_convert_to_24(struct pcm_buffer *buffer,
num_samples);
return dest;
- case 16:
+ case SAMPLE_FORMAT_S16:
num_samples = src_size / 2;
*dest_size_r = num_samples * sizeof(*dest);
dest = pcm_buffer_get(buffer, *dest_size_r);
@@ -154,11 +182,17 @@ pcm_convert_to_24(struct pcm_buffer *buffer,
num_samples);
return dest;
- case 24:
+ 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);
+
+ case SAMPLE_FORMAT_S24_P32:
*dest_size_r = src_size;
return src;
- case 32:
+ case SAMPLE_FORMAT_S32:
num_samples = src_size / 4;
*dest_size_r = num_samples * sizeof(*dest);
dest = pcm_buffer_get(buffer, *dest_size_r);
@@ -168,7 +202,6 @@ pcm_convert_to_24(struct pcm_buffer *buffer,
return dest;
}
- g_warning("only 8 or 24 bits are supported for conversion!\n");
return NULL;
}
@@ -204,14 +237,17 @@ pcm_convert_24_to_32(int32_t *out, const int32_t *in,
const int32_t *
pcm_convert_to_32(struct pcm_buffer *buffer,
- uint8_t bits, const void *src,
+ enum sample_format src_format, const void *src,
size_t src_size, size_t *dest_size_r)
{
unsigned num_samples;
int32_t *dest;
- switch (bits) {
- case 8:
+ switch (src_format) {
+ case SAMPLE_FORMAT_UNDEFINED:
+ break;
+
+ case SAMPLE_FORMAT_S8:
num_samples = src_size;
*dest_size_r = src_size * sizeof(*dest);
dest = pcm_buffer_get(buffer, *dest_size_r);
@@ -220,7 +256,7 @@ pcm_convert_to_32(struct pcm_buffer *buffer,
num_samples);
return dest;
- case 16:
+ case SAMPLE_FORMAT_S16:
num_samples = src_size / 2;
*dest_size_r = num_samples * sizeof(*dest);
dest = pcm_buffer_get(buffer, *dest_size_r);
@@ -229,7 +265,18 @@ pcm_convert_to_32(struct pcm_buffer *buffer,
num_samples);
return dest;
- case 24:
+ case SAMPLE_FORMAT_S24:
+ /* convert to S24_P32 first */
+ num_samples = src_size / 3;
+
+ dest = pcm_convert_24_to_24p32(buffer, src, num_samples);
+
+ /* convert to 32 bit in-place */
+ *dest_size_r = num_samples * sizeof(*dest);
+ pcm_convert_24_to_32(dest, dest, 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);
@@ -238,11 +285,10 @@ pcm_convert_to_32(struct pcm_buffer *buffer,
num_samples);
return dest;
- case 32:
+ case SAMPLE_FORMAT_S32:
*dest_size_r = src_size;
return src;
}
- g_warning("only 8 or 32 bits are supported for conversion!\n");
return NULL;
}
diff --git a/src/pcm_format.h b/src/pcm_format.h
index 350566827..3e96fc65f 100644
--- a/src/pcm_format.h
+++ b/src/pcm_format.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -20,6 +20,8 @@
#ifndef PCM_FORMAT_H
#define PCM_FORMAT_H
+#include "audio_format.h"
+
#include <stdint.h>
#include <stddef.h>
@@ -40,7 +42,7 @@ struct pcm_dither;
*/
const int16_t *
pcm_convert_to_16(struct pcm_buffer *buffer, struct pcm_dither *dither,
- uint8_t bits, const void *src,
+ enum sample_format src_format, const void *src,
size_t src_size, size_t *dest_size_r);
/**
@@ -55,7 +57,7 @@ pcm_convert_to_16(struct pcm_buffer *buffer, struct pcm_dither *dither,
*/
const int32_t *
pcm_convert_to_24(struct pcm_buffer *buffer,
- uint8_t bits, const void *src,
+ enum sample_format src_format, const void *src,
size_t src_size, size_t *dest_size_r);
/**
@@ -70,7 +72,7 @@ pcm_convert_to_24(struct pcm_buffer *buffer,
*/
const int32_t *
pcm_convert_to_32(struct pcm_buffer *buffer,
- uint8_t bits, const void *src,
+ 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 d1e716731..3145c07be 100644
--- a/src/pcm_mix.c
+++ b/src/pcm_mix.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -17,10 +17,12 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "pcm_mix.h"
#include "pcm_volume.h"
#include "pcm_utils.h"
#include "audio_format.h"
+#include "mpd_error.h"
#include <glib.h>
@@ -30,8 +32,8 @@
#define G_LOG_DOMAIN "pcm"
static void
-pcm_add_8(int8_t *buffer1, const int8_t *buffer2,
- unsigned num_samples, int volume1, int volume2)
+pcm_add_vol_8(int8_t *buffer1, const int8_t *buffer2,
+ unsigned num_samples, int volume1, int volume2)
{
while (num_samples > 0) {
int32_t sample1 = *buffer1;
@@ -47,8 +49,8 @@ pcm_add_8(int8_t *buffer1, const int8_t *buffer2,
}
static void
-pcm_add_16(int16_t *buffer1, const int16_t *buffer2,
- unsigned num_samples, int volume1, int volume2)
+pcm_add_vol_16(int16_t *buffer1, const int16_t *buffer2,
+ unsigned num_samples, int volume1, int volume2)
{
while (num_samples > 0) {
int32_t sample1 = *buffer1;
@@ -64,8 +66,8 @@ pcm_add_16(int16_t *buffer1, const int16_t *buffer2,
}
static void
-pcm_add_24(int32_t *buffer1, const int32_t *buffer2,
- unsigned num_samples, unsigned volume1, unsigned volume2)
+pcm_add_vol_24(int32_t *buffer1, const int32_t *buffer2,
+ unsigned num_samples, unsigned volume1, unsigned volume2)
{
while (num_samples > 0) {
int64_t sample1 = *buffer1;
@@ -81,29 +83,134 @@ pcm_add_24(int32_t *buffer1, const int32_t *buffer2,
}
static void
+pcm_add_vol_32(int32_t *buffer1, const int32_t *buffer2,
+ unsigned num_samples, unsigned volume1, unsigned volume2)
+{
+ while (num_samples > 0) {
+ int64_t sample1 = *buffer1;
+ int64_t sample2 = *buffer2++;
+
+ sample1 = ((sample1 * volume1 + sample2 * volume2) +
+ pcm_volume_dither() + PCM_VOLUME_1 / 2)
+ / PCM_VOLUME_1;
+
+ *buffer1++ = pcm_range_64(sample1, 32);
+ --num_samples;
+ }
+}
+
+static void
+pcm_add_vol(void *buffer1, const void *buffer2, size_t size,
+ int vol1, int vol2,
+ const struct audio_format *format)
+{
+ switch (format->format) {
+ case SAMPLE_FORMAT_S8:
+ pcm_add_vol_8((int8_t *)buffer1, (const int8_t *)buffer2,
+ size, vol1, vol2);
+ break;
+
+ case SAMPLE_FORMAT_S16:
+ pcm_add_vol_16((int16_t *)buffer1, (const int16_t *)buffer2,
+ size / 2, vol1, vol2);
+ break;
+
+ case SAMPLE_FORMAT_S24_P32:
+ pcm_add_vol_24((int32_t *)buffer1, (const int32_t *)buffer2,
+ size / 4, vol1, vol2);
+ break;
+
+ case SAMPLE_FORMAT_S32:
+ pcm_add_vol_32((int32_t *)buffer1, (const int32_t *)buffer2,
+ size / 4, vol1, vol2);
+ break;
+
+ default:
+ MPD_ERROR("format %s not supported by pcm_add_vol",
+ sample_format_to_string(format->format));
+ }
+}
+
+static void
+pcm_add_8(int8_t *buffer1, const int8_t *buffer2, unsigned num_samples)
+{
+ while (num_samples > 0) {
+ int32_t sample1 = *buffer1;
+ int32_t sample2 = *buffer2++;
+
+ sample1 += sample2;
+
+ *buffer1++ = pcm_range(sample1, 8);
+ --num_samples;
+ }
+}
+
+static void
+pcm_add_16(int16_t *buffer1, const int16_t *buffer2, unsigned num_samples)
+{
+ while (num_samples > 0) {
+ int32_t sample1 = *buffer1;
+ int32_t sample2 = *buffer2++;
+
+ sample1 += sample2;
+
+ *buffer1++ = pcm_range(sample1, 16);
+ --num_samples;
+ }
+}
+
+static void
+pcm_add_24(int32_t *buffer1, const int32_t *buffer2, unsigned num_samples)
+{
+ while (num_samples > 0) {
+ int64_t sample1 = *buffer1;
+ int64_t sample2 = *buffer2++;
+
+ sample1 += sample2;
+
+ *buffer1++ = pcm_range(sample1, 24);
+ --num_samples;
+ }
+}
+
+static void
+pcm_add_32(int32_t *buffer1, const int32_t *buffer2, unsigned num_samples)
+{
+ while (num_samples > 0) {
+ int64_t sample1 = *buffer1;
+ int64_t sample2 = *buffer2++;
+
+ sample1 += sample2;
+
+ *buffer1++ = pcm_range_64(sample1, 32);
+ --num_samples;
+ }
+}
+
+static void
pcm_add(void *buffer1, const void *buffer2, size_t size,
- int vol1, int vol2,
const struct audio_format *format)
{
- switch (format->bits) {
- case 8:
- pcm_add_8((int8_t *)buffer1, (const int8_t *)buffer2,
- size, vol1, vol2);
+ switch (format->format) {
+ case SAMPLE_FORMAT_S8:
+ pcm_add_8((int8_t *)buffer1, (const int8_t *)buffer2, size);
break;
- case 16:
- pcm_add_16((int16_t *)buffer1, (const int16_t *)buffer2,
- size / 2, vol1, vol2);
+ case SAMPLE_FORMAT_S16:
+ pcm_add_16((int16_t *)buffer1, (const int16_t *)buffer2, size / 2);
break;
- case 24:
- pcm_add_24((int32_t*)buffer1,
- (const int32_t*)buffer2,
- size / 4, vol1, vol2);
+ case SAMPLE_FORMAT_S24_P32:
+ pcm_add_24((int32_t *)buffer1, (const int32_t *)buffer2, size / 4);
+ break;
+
+ case SAMPLE_FORMAT_S32:
+ pcm_add_32((int32_t *)buffer1, (const int32_t *)buffer2, size / 4);
break;
default:
- g_error("%u bits not supported by pcm_add!\n", format->bits);
+ MPD_ERROR("format %s not supported by pcm_add",
+ sample_format_to_string(format->format));
}
}
@@ -112,11 +219,20 @@ pcm_mix(void *buffer1, const void *buffer2, size_t size,
const struct audio_format *format, float portion1)
{
int vol1;
- float s = sin(M_PI_2 * portion1);
+ 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;
+ }
+
+ s = sin(M_PI_2 * portion1);
s *= s;
vol1 = s * PCM_VOLUME_1 + 0.5;
vol1 = vol1 > PCM_VOLUME_1 ? PCM_VOLUME_1 : (vol1 < 0 ? 0 : vol1);
- pcm_add(buffer1, buffer2, size, vol1, PCM_VOLUME_1 - vol1, format);
+ 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 6605585bd..086d5501e 100644
--- a/src/pcm_mix.h
+++ b/src/pcm_mix.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -35,7 +35,8 @@ struct audio_format;
* @param size the size of both buffers in bytes
* @param format the audio 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)
+ * 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.
*/
void
pcm_mix(void *buffer1, const void *buffer2, size_t size,
diff --git a/src/pcm_pack.c b/src/pcm_pack.c
new file mode 100644
index 000000000..9af0ab1ed
--- /dev/null
+++ b/src/pcm_pack.c
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "pcm_pack.h"
+
+#include <glib.h>
+
+static void
+pack_sample(uint8_t *dest, const int32_t *src0, bool reverse_endian)
+{
+ const uint8_t *src = (const uint8_t *)src0;
+
+ if ((G_BYTE_ORDER == G_BIG_ENDIAN) != reverse_endian)
+ ++src;
+
+ *dest++ = *src++;
+ *dest++ = *src++;
+ *dest++ = *src++;
+}
+
+void
+pcm_pack_24(uint8_t *dest, const int32_t *src, unsigned num_samples,
+ bool reverse_endian)
+{
+ /* 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;
+ }
+ }
+}
+
+static void
+unpack_sample(int32_t *dest0, const uint8_t *src, bool reverse_endian)
+{
+ uint8_t *dest = (uint8_t *)dest0;
+
+ if ((G_BYTE_ORDER == G_BIG_ENDIAN) != reverse_endian)
+ /* extend the sign bit to the most fourth byte */
+ *dest++ = *src & 0x80 ? 0xff : 0x00;
+
+ *dest++ = *src++;
+ *dest++ = *src++;
+ *dest++ = *src;
+
+ if ((G_BYTE_ORDER == G_LITTLE_ENDIAN) != reverse_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)
+{
+ /* 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;
+ }
+ }
+}
diff --git a/src/pcm_pack.h b/src/pcm_pack.h
new file mode 100644
index 000000000..3c99eaa35
--- /dev/null
+++ b/src/pcm_pack.h
@@ -0,0 +1,59 @@
+/*
+ * 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.
+ */
+
+/** \file
+ *
+ * Library for working with packed 24 bit samples.
+ */
+
+#ifndef PCM_PACK_H
+#define PCM_PACK_H
+
+#include <stdbool.h>
+#include <stdint.h>
+
+/**
+ * Converts padded 24 bit samples (4 bytes per sample) to packed 24
+ * bit samples (3 bytes per sample).
+ *
+ * This function can be used to convert a buffer in-place.
+ *
+ * @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);
+
+/**
+ * Converts packed 24 bit samples (3 bytes per sample) to padded 24
+ * bit samples (4 bytes per sample).
+ *
+ * @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);
+
+#endif
diff --git a/src/pcm_prng.h b/src/pcm_prng.h
index ea5983588..186ed9d0e 100644
--- a/src/pcm_prng.h
+++ b/src/pcm_prng.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
diff --git a/src/pcm_resample.c b/src/pcm_resample.c
index d1360d02a..4a7578e09 100644
--- a/src/pcm_resample.c
+++ b/src/pcm_resample.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -17,8 +17,8 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
-#include "pcm_resample_internal.h"
#include "config.h"
+#include "pcm_resample_internal.h"
#ifdef HAVE_LIBSAMPLERATE
#include "conf.h"
@@ -62,16 +62,18 @@ void pcm_resample_deinit(struct pcm_resample_state *state)
const int16_t *
pcm_resample_16(struct pcm_resample_state *state,
uint8_t channels,
- unsigned src_rate,
- const int16_t *src_buffer, size_t src_size,
- unsigned dest_rate,
- size_t *dest_size_r)
+ unsigned src_rate, const int16_t *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_16(state, channels,
src_rate, src_buffer, src_size,
- dest_rate, dest_size_r);
+ dest_rate, dest_size_r,
+ error_r);
+#else
+ (void)error_r;
#endif
return pcm_resample_fallback_16(state, channels,
@@ -82,16 +84,18 @@ pcm_resample_16(struct pcm_resample_state *state,
const int32_t *
pcm_resample_32(struct pcm_resample_state *state,
uint8_t channels,
- unsigned src_rate,
- const int32_t *src_buffer, size_t src_size,
- unsigned dest_rate,
- size_t *dest_size_r)
+ unsigned src_rate, const int32_t *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_32(state, channels,
src_rate, src_buffer, src_size,
- dest_rate, dest_size_r);
+ dest_rate, dest_size_r,
+ error_r);
+#else
+ (void)error_r;
#endif
return pcm_resample_fallback_32(state, channels,
diff --git a/src/pcm_resample.h b/src/pcm_resample.h
index 44720f7b2..24d17ff9b 100644
--- a/src/pcm_resample.h
+++ b/src/pcm_resample.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -20,8 +20,8 @@
#ifndef MPD_PCM_RESAMPLE_H
#define MPD_PCM_RESAMPLE_H
+#include "check.h"
#include "pcm_buffer.h"
-#include "config.h"
#include <stdint.h>
#include <stddef.h>
@@ -48,7 +48,7 @@ struct pcm_resample_state {
uint8_t channels;
} prev;
- bool error;
+ int error;
#endif
struct pcm_buffer buffer;
@@ -82,8 +82,8 @@ pcm_resample_16(struct pcm_resample_state *state,
uint8_t channels,
unsigned src_rate,
const int16_t *src_buffer, size_t src_size,
- unsigned dest_rate,
- size_t *dest_size_r);
+ unsigned dest_rate, size_t *dest_size_r,
+ GError **error_r);
/**
* Resamples 32 bit PCM data.
@@ -102,8 +102,8 @@ pcm_resample_32(struct pcm_resample_state *state,
uint8_t channels,
unsigned src_rate,
const int32_t *src_buffer, size_t src_size,
- unsigned dest_rate,
- size_t *dest_size_r);
+ unsigned dest_rate, size_t *dest_size_r,
+ GError **error_r);
/**
* Resamples 24 bit PCM data.
@@ -122,14 +122,14 @@ pcm_resample_24(struct pcm_resample_state *state,
uint8_t channels,
unsigned src_rate,
const int32_t *src_buffer, size_t src_size,
- unsigned dest_rate,
- size_t *dest_size_r)
+ unsigned dest_rate, size_t *dest_size_r,
+ GError **error_r)
{
/* reuse the 32 bit code - the resampler code doesn't care if
the upper 8 bits are actually used */
return pcm_resample_32(state, channels,
src_rate, src_buffer, src_size,
- dest_rate, dest_size_r);
+ dest_rate, dest_size_r, error_r);
}
#endif
diff --git a/src/pcm_resample_fallback.c b/src/pcm_resample_fallback.c
index 36af51ad0..0c75d8ba4 100644
--- a/src/pcm_resample_fallback.c
+++ b/src/pcm_resample_fallback.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -17,10 +17,10 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "pcm_resample_internal.h"
#include <assert.h>
-#include <glib.h>
void
pcm_resample_fallback_deinit(struct pcm_resample_state *state)
@@ -74,8 +74,7 @@ const int32_t *
pcm_resample_fallback_32(struct pcm_resample_state *state,
uint8_t channels,
unsigned src_rate,
- const int32_t *src_buffer,
- G_GNUC_UNUSED size_t src_size,
+ const int32_t *src_buffer, size_t src_size,
unsigned dest_rate,
size_t *dest_size_r)
{
diff --git a/src/pcm_resample_internal.h b/src/pcm_resample_internal.h
index a10ba08cd..26acc809d 100644
--- a/src/pcm_resample_internal.h
+++ b/src/pcm_resample_internal.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -27,8 +27,8 @@
#ifndef MPD_PCM_RESAMPLE_INTERNAL_H
#define MPD_PCM_RESAMPLE_INTERNAL_H
+#include "check.h"
#include "pcm_resample.h"
-#include "config.h"
#ifdef HAVE_LIBSAMPLERATE
@@ -40,8 +40,8 @@ pcm_resample_lsr_16(struct pcm_resample_state *state,
uint8_t channels,
unsigned src_rate,
const int16_t *src_buffer, size_t src_size,
- unsigned dest_rate,
- size_t *dest_size_r);
+ unsigned dest_rate, size_t *dest_size_r,
+ GError **error_r);
const int32_t *
pcm_resample_lsr_32(struct pcm_resample_state *state,
@@ -49,8 +49,8 @@ pcm_resample_lsr_32(struct pcm_resample_state *state,
unsigned src_rate,
const int32_t *src_buffer,
G_GNUC_UNUSED size_t src_size,
- unsigned dest_rate,
- size_t *dest_size_r);
+ unsigned dest_rate, size_t *dest_size_r,
+ GError **error_r);
#endif
diff --git a/src/pcm_resample_libsamplerate.c b/src/pcm_resample_libsamplerate.c
index 6d019e892..99ca53da4 100644
--- a/src/pcm_resample_libsamplerate.c
+++ b/src/pcm_resample_libsamplerate.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -17,9 +17,9 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "pcm_resample_internal.h"
#include "conf.h"
-#include "config.h"
#include <glib.h>
@@ -30,6 +30,12 @@
#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "pcm"
+static inline GQuark
+libsamplerate_quark(void)
+{
+ return g_quark_from_static_string("libsamplerate");
+}
+
void
pcm_resample_lsr_deinit(struct pcm_resample_state *state)
{
@@ -77,9 +83,10 @@ out:
return convalgo;
}
-static void
+static bool
pcm_resample_set(struct pcm_resample_state *state,
- uint8_t channels, unsigned src_rate, unsigned dest_rate)
+ uint8_t channels, unsigned src_rate, unsigned dest_rate,
+ GError **error_r)
{
static int convalgo = -1;
int error;
@@ -92,9 +99,9 @@ pcm_resample_set(struct pcm_resample_state *state,
if (channels == state->prev.channels &&
src_rate == state->prev.src_rate &&
dest_rate == state->prev.dest_rate)
- return;
+ return true;
- state->error = false;
+ state->error = 0;
state->prev.channels = channels;
state->prev.src_rate = src_rate;
state->prev.dest_rate = dest_rate;
@@ -104,16 +111,18 @@ pcm_resample_set(struct pcm_resample_state *state,
state->state = src_new(convalgo, channels, &error);
if (!state->state) {
- g_warning("cannot create new libsamplerate state: %s",
- src_strerror(error));
- state->error = true;
- return;
+ g_set_error(error_r, libsamplerate_quark(), state->error,
+ "libsamplerate initialization has failed: %s",
+ src_strerror(error));
+ return false;
}
data->src_ratio = (double)dest_rate / (double)src_rate;
g_debug("setting samplerate conversion ratio to %.2lf",
data->src_ratio);
src_set_ratio(state->state, data->src_ratio);
+
+ return true;
}
const int16_t *
@@ -121,9 +130,10 @@ pcm_resample_lsr_16(struct pcm_resample_state *state,
uint8_t channels,
unsigned src_rate,
const int16_t *src_buffer, size_t src_size,
- unsigned dest_rate,
- size_t *dest_size_r)
+ unsigned dest_rate, size_t *dest_size_r,
+ GError **error_r)
{
+ bool success;
SRC_DATA *data = &state->data;
size_t data_in_size;
size_t data_out_size;
@@ -132,11 +142,18 @@ pcm_resample_lsr_16(struct pcm_resample_state *state,
assert((src_size % (sizeof(*src_buffer) * channels)) == 0);
- pcm_resample_set(state, channels, src_rate, dest_rate);
+ success = pcm_resample_set(state, channels, src_rate, dest_rate,
+ error_r);
+ if (!success)
+ return NULL;
/* there was an error previously, and nothing has changed */
- if (state->error)
+ 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;
@@ -151,9 +168,10 @@ pcm_resample_lsr_16(struct pcm_resample_state *state,
error = src_process(state->state, data);
if (error) {
- g_warning("error processing samples with libsamplerate: %s",
- src_strerror(error));
- state->error = true;
+ g_set_error(error_r, libsamplerate_quark(), error,
+ "libsamplerate has failed: %s",
+ src_strerror(error));
+ state->error = error;
return NULL;
}
@@ -191,9 +209,10 @@ pcm_resample_lsr_32(struct pcm_resample_state *state,
uint8_t channels,
unsigned src_rate,
const int32_t *src_buffer, size_t src_size,
- unsigned dest_rate,
- size_t *dest_size_r)
+ unsigned dest_rate, size_t *dest_size_r,
+ GError **error_r)
{
+ bool success;
SRC_DATA *data = &state->data;
size_t data_in_size;
size_t data_out_size;
@@ -202,11 +221,18 @@ pcm_resample_lsr_32(struct pcm_resample_state *state,
assert((src_size % (sizeof(*src_buffer) * channels)) == 0);
- pcm_resample_set(state, channels, src_rate, dest_rate);
+ success = pcm_resample_set(state, channels, src_rate, dest_rate,
+ error_r);
+ if (!success)
+ return NULL;
/* there was an error previously, and nothing has changed */
- if (state->error)
+ 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;
@@ -221,9 +247,10 @@ pcm_resample_lsr_32(struct pcm_resample_state *state,
error = src_process(state->state, data);
if (error) {
- g_warning("error processing samples with libsamplerate: %s",
- src_strerror(error));
- state->error = true;
+ g_set_error(error_r, libsamplerate_quark(), error,
+ "libsamplerate has failed: %s",
+ src_strerror(error));
+ state->error = error;
return NULL;
}
diff --git a/src/pcm_utils.h b/src/pcm_utils.h
index 93f414231..15f9e1b10 100644
--- a/src/pcm_utils.h
+++ b/src/pcm_utils.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -38,4 +38,18 @@ pcm_range(int32_t sample, unsigned bits)
return sample;
}
+/**
+ * Check if the value is within the range of the provided bit size,
+ * and caps it if necessary.
+ */
+static inline int64_t
+pcm_range_64(int64_t sample, unsigned bits)
+{
+ if (G_UNLIKELY(sample < ((int64_t)-1 << (bits - 1))))
+ return (int64_t)-1 << (bits - 1);
+ if (G_UNLIKELY(sample >= ((int64_t)1 << (bits - 1))))
+ return ((int64_t)1 << (bits - 1)) - 1;
+ return sample;
+}
+
#endif
diff --git a/src/pcm_volume.c b/src/pcm_volume.c
index 2a94c1890..240c779d8 100644
--- a/src/pcm_volume.c
+++ b/src/pcm_volume.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -17,6 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "pcm_volume.h"
#include "pcm_utils.h"
#include "audio_format.h"
@@ -113,6 +114,29 @@ pcm_volume_change_24(int32_t *buffer, unsigned num_samples, int volume)
}
}
+static void
+pcm_volume_change_32(int32_t *buffer, unsigned num_samples, int volume)
+{
+ while (num_samples > 0) {
+#ifdef __i386__
+ /* assembly version for i386 */
+ int32_t sample = *buffer;
+
+ *buffer++ = pcm_volume_sample_24(sample, volume, 0);
+#else
+ /* portable version */
+ int64_t sample = *buffer;
+
+ sample = (sample * volume + pcm_volume_dither() +
+ PCM_VOLUME_1 / 2)
+ / PCM_VOLUME_1;
+ *buffer++ = pcm_range_64(sample, 32);
+#endif
+
+ --num_samples;
+ }
+}
+
bool
pcm_volume(void *buffer, int length,
const struct audio_format *format,
@@ -126,21 +150,26 @@ pcm_volume(void *buffer, int length,
return true;
}
- switch (format->bits) {
- case 8:
+ switch (format->format) {
+ case SAMPLE_FORMAT_S8:
pcm_volume_change_8((int8_t *)buffer, length, volume);
return true;
- case 16:
+ case SAMPLE_FORMAT_S16:
pcm_volume_change_16((int16_t *)buffer, length / 2,
volume);
return true;
- case 24:
+ case SAMPLE_FORMAT_S24_P32:
pcm_volume_change_24((int32_t*)buffer, length / 4,
volume);
return true;
+ case SAMPLE_FORMAT_S32:
+ pcm_volume_change_32((int32_t*)buffer, length / 4,
+ volume);
+ return true;
+
default:
return false;
}
diff --git a/src/pcm_volume.h b/src/pcm_volume.h
index 5cff35cb8..eb61e9526 100644
--- a/src/pcm_volume.h
+++ b/src/pcm_volume.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
diff --git a/src/permission.c b/src/permission.c
index 7df4e27fc..17b443e0a 100644
--- a/src/permission.c
+++ b/src/permission.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -17,8 +17,10 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "permission.h"
#include "conf.h"
+#include "mpd_error.h"
#include <glib.h>
@@ -58,7 +60,7 @@ static unsigned parsePermissions(const char *string)
} else if (strcmp(temp, PERMISSION_ADMIN_STRING) == 0) {
permission |= PERMISSION_ADMIN;
} else {
- g_error("unknown permission \"%s\"", temp);
+ MPD_ERROR("unknown permission \"%s\"", temp);
}
}
@@ -89,7 +91,7 @@ void initPermissions(void)
strchr(param->value, PERMISSION_PASSWORD_CHAR);
if (separator == NULL)
- g_error("\"%c\" not found in password string "
+ MPD_ERROR("\"%c\" not found in password string "
"\"%s\", line %i",
PERMISSION_PASSWORD_CHAR,
param->value, param->line);
@@ -111,7 +113,7 @@ void initPermissions(void)
permission_default = parsePermissions(param->value);
}
-int getPermissionFromPassword(char *password, unsigned *permission)
+int getPermissionFromPassword(char const* password, unsigned* permission)
{
bool found;
gpointer key, value;
diff --git a/src/permission.h b/src/permission.h
index bad26aa3c..9b3a60a66 100644
--- a/src/permission.h
+++ b/src/permission.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -27,7 +27,7 @@
#define PERMISSION_ADMIN 8
-int getPermissionFromPassword(char *password, unsigned *permission);
+int getPermissionFromPassword(char const* password, unsigned* permission);
void finishPermissions(void);
diff --git a/src/pipe.c b/src/pipe.c
index c9f0d159c..7e4b0d081 100644
--- a/src/pipe.c
+++ b/src/pipe.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -17,6 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "pipe.h"
#include "buffer.h"
#include "chunk.h"
diff --git a/src/pipe.h b/src/pipe.h
index b15cd76c1..f9540a30e 100644
--- a/src/pipe.h
+++ b/src/pipe.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
diff --git a/src/player_control.c b/src/player_control.c
index ac4b006dd..a190bbd8b 100644
--- a/src/player_control.c
+++ b/src/player_control.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -17,7 +17,9 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "player_control.h"
+#include "decoder_control.h"
#include "path.h"
#include "log.h"
#include "tag.h"
@@ -28,24 +30,41 @@
#include <assert.h>
#include <stdio.h>
+#include <math.h>
struct player_control pc;
+static void
+pc_enqueue_song_locked(struct song *song);
+
void pc_init(unsigned buffer_chunks, unsigned int buffered_before_play)
{
pc.buffer_chunks = buffer_chunks;
pc.buffered_before_play = buffered_before_play;
- notify_init(&pc.notify);
+
+ 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.software_volume = PCM_VOLUME_1;
+ pc.mixramp_db = 0;
+ pc.mixramp_delay_seconds = nanf("");
}
void pc_deinit(void)
{
- notify_deinit(&pc.notify);
+ g_cond_free(pc.cond);
+ g_mutex_free(pc.mutex);
+}
+
+void
+player_wait_decoder(struct decoder_control *dc)
+{
+ /* during this function, the decoder lock is held, because
+ we're waiting for the decoder thread */
+ g_cond_wait(pc.cond, dc->mutex);
}
void
@@ -57,27 +76,48 @@ pc_song_deleted(const struct song *song)
}
}
-static void player_command(enum player_command cmd)
+static void
+player_command_wait_locked(void)
+{
+ while (pc.command != PLAYER_COMMAND_NONE)
+ g_cond_wait(main_cond, pc.mutex);
+}
+
+static void
+player_command_locked(enum player_command cmd)
{
assert(pc.command == PLAYER_COMMAND_NONE);
pc.command = cmd;
- while (pc.command != PLAYER_COMMAND_NONE) {
- notify_signal(&pc.notify);
- notify_wait(&main_notify);
- }
+ player_signal();
+ player_command_wait_locked();
+}
+
+static void
+player_command(enum player_command cmd)
+{
+ player_lock();
+ player_command_locked(cmd);
+ player_unlock();
}
void
-playerPlay(struct song *song)
+pc_play(struct song *song)
{
assert(song != NULL);
+ player_lock();
+
if (pc.state != PLAYER_STATE_STOP)
- player_command(PLAYER_COMMAND_STOP);
+ player_command_locked(PLAYER_COMMAND_STOP);
- pc.next_song = song;
- player_command(PLAYER_COMMAND_PLAY);
+ assert(pc.next_song == NULL);
+
+ pc_enqueue_song_locked(song);
+
+ assert(pc.next_song == NULL);
+
+ player_unlock();
idle_add(IDLE_PLAYER);
}
@@ -85,16 +125,26 @@ playerPlay(struct song *song)
void pc_cancel(void)
{
player_command(PLAYER_COMMAND_CANCEL);
+ assert(pc.next_song == NULL);
}
-void playerWait(void)
+void
+pc_stop(void)
{
player_command(PLAYER_COMMAND_CLOSE_AUDIO);
+ assert(pc.next_song == NULL);
idle_add(IDLE_PLAYER);
}
-void playerKill(void)
+void
+pc_update_audio(void)
+{
+ player_command(PLAYER_COMMAND_UPDATE_AUDIO);
+}
+
+void
+pc_kill(void)
{
assert(pc.thread != NULL);
@@ -105,57 +155,86 @@ void playerKill(void)
idle_add(IDLE_PLAYER);
}
-void playerPause(void)
+void
+pc_pause(void)
+{
+ player_lock();
+
+ if (pc.state != PLAYER_STATE_STOP) {
+ player_command_locked(PLAYER_COMMAND_PAUSE);
+ idle_add(IDLE_PLAYER);
+ }
+
+ player_unlock();
+}
+
+static void
+pc_pause_locked(void)
{
if (pc.state != PLAYER_STATE_STOP) {
- player_command(PLAYER_COMMAND_PAUSE);
+ player_command_locked(PLAYER_COMMAND_PAUSE);
idle_add(IDLE_PLAYER);
}
}
-void playerSetPause(int pause_flag)
+void
+pc_set_pause(bool pause_flag)
{
+ player_lock();
+
switch (pc.state) {
case PLAYER_STATE_STOP:
break;
case PLAYER_STATE_PLAY:
if (pause_flag)
- playerPause();
+ pc_pause_locked();
break;
+
case PLAYER_STATE_PAUSE:
if (!pause_flag)
- playerPause();
+ pc_pause_locked();
break;
}
-}
-int getPlayerElapsedTime(void)
-{
- return (int)(pc.elapsed_time + 0.5);
+ player_unlock();
}
-unsigned long getPlayerBitRate(void)
+void
+pc_get_status(struct player_status *status)
{
- return pc.bit_rate;
-}
+ player_lock();
+ player_command_locked(PLAYER_COMMAND_REFRESH);
-int getPlayerTotalTime(void)
-{
- return (int)(pc.total_time + 0.5);
+ 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;
+ }
+
+ player_unlock();
}
-enum player_state getPlayerState(void)
+enum player_state
+pc_get_state(void)
{
return pc.state;
}
-void clearPlayerError(void)
+void
+pc_clear_error(void)
{
- pc.error = 0;
+ player_lock();
+ pc.error = PLAYER_ERROR_NOERROR;
+ pc.errored_song = NULL;
+ player_unlock();
}
-enum player_error getPlayerError(void)
+enum player_error
+pc_get_error(void)
{
return pc.error;
}
@@ -166,58 +245,63 @@ pc_errored_song_uri(void)
return song_get_uri(pc.errored_song);
}
-char *getPlayerErrorStr(void)
+char *
+pc_get_error_message(void)
{
- /* static OK here, only one user in main task */
- static char error[MPD_PATH_MAX + 64]; /* still too much */
- static const size_t errorlen = sizeof(error);
+ char *error;
char *uri;
- *error = '\0'; /* likely */
-
switch (pc.error) {
case PLAYER_ERROR_NOERROR:
- break;
+ return NULL;
case PLAYER_ERROR_FILENOTFOUND:
uri = pc_errored_song_uri();
- snprintf(error, errorlen,
- "file \"%s\" does not exist or is inaccessible", uri);
+ error = g_strdup_printf("file \"%s\" does not exist or is inaccessible", uri);
g_free(uri);
- break;
+ return error;
case PLAYER_ERROR_FILE:
uri = pc_errored_song_uri();
- snprintf(error, errorlen, "problems decoding \"%s\"", uri);
+ error = g_strdup_printf("problems decoding \"%s\"", uri);
g_free(uri);
- break;
+ return error;
case PLAYER_ERROR_AUDIO:
- strcpy(error, "problems opening audio device");
- break;
+ return g_strdup("problems opening audio device");
case PLAYER_ERROR_SYSTEM:
- strcpy(error, "system error occured");
- break;
+ return g_strdup("system error occured");
case PLAYER_ERROR_UNKTYPE:
uri = pc_errored_song_uri();
- snprintf(error, errorlen,
- "file type of \"%s\" is unknown", uri);
+ error = g_strdup_printf("file type of \"%s\" is unknown", uri);
g_free(uri);
- break;
+ return error;
}
- return *error ? error : NULL;
+
+ assert(false);
+ return NULL;
}
-void
-queueSong(struct song *song)
+static void
+pc_enqueue_song_locked(struct song *song)
{
assert(song != NULL);
assert(pc.next_song == NULL);
pc.next_song = song;
- player_command(PLAYER_COMMAND_QUEUE);
+ player_command_locked(PLAYER_COMMAND_QUEUE);
+}
+
+void
+pc_enqueue_song(struct song *song)
+{
+ assert(song != NULL);
+
+ player_lock();
+ pc_enqueue_song_locked(song);
+ player_unlock();
}
bool
@@ -228,9 +312,11 @@ pc_seek(struct song *song, float seek_time)
if (pc.state == PLAYER_STATE_STOP)
return false;
+ player_lock();
pc.next_song = song;
pc.seek_where = seek_time;
- player_command(PLAYER_COMMAND_SEEK);
+ player_command_locked(PLAYER_COMMAND_SEEK);
+ player_unlock();
assert(pc.next_song == NULL);
@@ -239,31 +325,52 @@ pc_seek(struct song *song, float seek_time)
return true;
}
-float getPlayerCrossFade(void)
+float
+pc_get_cross_fade(void)
{
return pc.cross_fade_seconds;
}
-void setPlayerCrossFade(float crossFadeInSeconds)
+void
+pc_set_cross_fade(float cross_fade_seconds)
+{
+ if (cross_fade_seconds < 0)
+ cross_fade_seconds = 0;
+ pc.cross_fade_seconds = cross_fade_seconds;
+
+ idle_add(IDLE_OPTIONS);
+}
+
+float
+pc_get_mixramp_db(void)
+{
+ return pc.mixramp_db;
+}
+
+void
+pc_set_mixramp_db(float mixramp_db)
{
- if (crossFadeInSeconds < 0)
- crossFadeInSeconds = 0;
- pc.cross_fade_seconds = crossFadeInSeconds;
+ pc.mixramp_db = mixramp_db;
idle_add(IDLE_OPTIONS);
}
-void setPlayerSoftwareVolume(int volume)
+float
+pc_get_mixramp_delay(void)
{
- if (volume > PCM_VOLUME_1)
- volume = PCM_VOLUME_1;
- else if (volume < 0)
- volume = 0;
+ return pc.mixramp_delay_seconds;
+}
- pc.software_volume = volume;
+void
+pc_set_mixramp_delay(float mixramp_delay_seconds)
+{
+ pc.mixramp_delay_seconds = mixramp_delay_seconds;
+
+ idle_add(IDLE_OPTIONS);
}
-double getPlayerTotalPlayTime(void)
+double
+pc_get_total_play_time(void)
{
return pc.total_play_time;
}
diff --git a/src/player_control.h b/src/player_control.h
index b1f7481cd..76c47609a 100644
--- a/src/player_control.h
+++ b/src/player_control.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -25,6 +25,8 @@
#include <stdint.h>
+struct decoder_control;
+
enum player_state {
PLAYER_STATE_STOP = 0,
PLAYER_STATE_PAUSE,
@@ -35,11 +37,16 @@ enum player_command {
PLAYER_COMMAND_NONE = 0,
PLAYER_COMMAND_EXIT,
PLAYER_COMMAND_STOP,
- PLAYER_COMMAND_PLAY,
PLAYER_COMMAND_PAUSE,
PLAYER_COMMAND_SEEK,
PLAYER_COMMAND_CLOSE_AUDIO,
+ /**
+ * At least one audio_output.enabled flag has been modified;
+ * commit those changes to the output threads.
+ */
+ PLAYER_COMMAND_UPDATE_AUDIO,
+
/** player_control.next_song has been updated */
PLAYER_COMMAND_QUEUE,
@@ -49,6 +56,12 @@ enum player_command {
* stop
*/
PLAYER_COMMAND_CANCEL,
+
+ /**
+ * Refresh status information in the #player_control struct,
+ * e.g. elapsed_time.
+ */
+ PLAYER_COMMAND_REFRESH,
};
enum player_error {
@@ -60,6 +73,14 @@ enum player_error {
PLAYER_ERROR_FILENOTFOUND,
};
+struct player_status {
+ enum player_state state;
+ uint16_t bit_rate;
+ struct audio_format audio_format;
+ float total_time;
+ float elapsed_time;
+};
+
struct player_control {
unsigned buffer_chunks;
@@ -69,19 +90,29 @@ struct player_control {
thread isn't running */
GThread *thread;
- struct notify notify;
- volatile enum player_command command;
- volatile enum player_state state;
- volatile enum player_error error;
+ /**
+ * This lock protects #command, #state, #error.
+ */
+ GMutex *mutex;
+
+ /**
+ * Trigger this object after you have modified #command.
+ */
+ GCond *cond;
+
+ enum player_command command;
+ enum player_state state;
+ enum player_error error;
uint16_t bit_rate;
struct audio_format audio_format;
float total_time;
float elapsed_time;
- struct song *volatile next_song;
- struct song *errored_song;
- volatile double seek_where;
+ struct song *next_song;
+ const struct song *errored_song;
+ double seek_where;
float cross_fade_seconds;
- uint16_t software_volume;
+ float mixramp_db;
+ float mixramp_delay_seconds;
double total_play_time;
};
@@ -92,6 +123,67 @@ void pc_init(unsigned buffer_chunks, unsigned buffered_before_play);
void pc_deinit(void);
/**
+ * Locks the #player_control object.
+ */
+static inline void
+player_lock(void)
+{
+ g_mutex_lock(pc.mutex);
+}
+
+/**
+ * Unlocks the #player_control object.
+ */
+static inline void
+player_unlock(void)
+{
+ g_mutex_unlock(pc.mutex);
+}
+
+/**
+ * Waits for a signal on the #player_control object. This function is
+ * only valid in the player thread. The object must be locked prior
+ * to calling this function.
+ */
+static inline void
+player_wait(void)
+{
+ g_cond_wait(pc.cond, pc.mutex);
+}
+
+/**
+ * Waits for a signal on the #player_control object. This function is
+ * only valid in the player thread. The #decoder_control object must
+ * be locked prior to calling this function.
+ *
+ * Note the small difference to the player_wait() function!
+ */
+void
+player_wait_decoder(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)
+{
+ g_cond_signal(pc.cond);
+}
+
+/**
+ * Signals the #player_control object. The object is temporarily
+ * locked by this function.
+ */
+static inline void
+player_lock_signal(void)
+{
+ player_lock();
+ player_signal();
+ player_unlock();
+}
+
+/**
* Call this function when the specified song pointer is about to be
* invalidated. This makes sure that player_control.errored_song does
* not point to an invalid pointer.
@@ -100,37 +192,50 @@ void
pc_song_deleted(const struct song *song);
void
-playerPlay(struct song *song);
+pc_play(struct song *song);
/**
* see PLAYER_COMMAND_CANCEL
*/
void pc_cancel(void);
-void playerSetPause(int pause_flag);
-
-void playerPause(void);
+void
+pc_set_pause(bool pause_flag);
-void playerKill(void);
+void
+pc_pause(void);
-int getPlayerTotalTime(void);
+void
+pc_kill(void);
-int getPlayerElapsedTime(void);
+void
+pc_get_status(struct player_status *status);
-unsigned long getPlayerBitRate(void);
+enum player_state
+pc_get_state(void);
-enum player_state getPlayerState(void);
+void
+pc_clear_error(void);
-void clearPlayerError(void);
+/**
+ * Returns the human-readable message describing the last error during
+ * playback, NULL if no error occurred. The caller has to free the
+ * returned string.
+ */
+char *
+pc_get_error_message(void);
-char *getPlayerErrorStr(void);
+enum player_error
+pc_get_error(void);
-enum player_error getPlayerError(void);
+void
+pc_stop(void);
-void playerWait(void);
+void
+pc_update_audio(void);
void
-queueSong(struct song *song);
+pc_enqueue_song(struct song *song);
/**
* Makes the player thread seek the specified song to a position.
@@ -141,20 +246,25 @@ queueSong(struct song *song);
bool
pc_seek(struct song *song, float seek_time);
-void setPlayerCrossFade(float crossFadeInSeconds);
+void
+pc_set_cross_fade(float cross_fade_seconds);
+
+float
+pc_get_cross_fade(void);
-float getPlayerCrossFade(void);
+void
+pc_set_mixramp_db(float mixramp_db);
-void setPlayerSoftwareVolume(int volume);
+float
+pc_get_mixramp_db(void);
-double getPlayerTotalPlayTime(void);
+void
+pc_set_mixramp_delay(float mixramp_delay_seconds);
-static inline const struct audio_format *
-player_get_audio_format(void)
-{
- return &pc.audio_format;
-}
+float
+pc_get_mixramp_delay(void);
-void playerInit(void);
+double
+pc_get_total_play_time(void);
#endif
diff --git a/src/player_thread.c b/src/player_thread.c
index d428484c7..cce51c1a7 100644
--- a/src/player_thread.c
+++ b/src/player_thread.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -17,6 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "player_thread.h"
#include "player_control.h"
#include "decoder_control.h"
@@ -33,6 +34,7 @@
#include "idle.h"
#include "main.h"
#include "buffer.h"
+#include "mpd_error.h"
#include <glib.h>
@@ -46,6 +48,8 @@ enum xfade_state {
};
struct player {
+ struct decoder_control *dc;
+
struct music_pipe *pipe;
/**
@@ -102,20 +106,46 @@ struct player {
struct audio_format play_audio_format;
/**
- * Coefficient for converting a PCM buffer size into a time
- * span.
+ * The time stamp of the chunk most recently sent to the
+ * 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.
*/
- double size_to_time;
+ float elapsed_time;
};
static struct music_buffer *player_buffer;
-static void player_command_finished(void)
+static void player_command_finished_locked(void)
{
assert(pc.command != PLAYER_COMMAND_NONE);
pc.command = PLAYER_COMMAND_NONE;
- notify_signal(&main_notify);
+ g_cond_signal(main_cond);
+}
+
+static void player_command_finished(void)
+{
+ player_lock();
+ player_command_finished_locked();
+ player_unlock();
+}
+
+/**
+ * Start the decoder.
+ *
+ * Player lock is not held.
+ */
+static void
+player_dc_start(struct player *player, struct music_pipe *pipe)
+{
+ struct decoder_control *dc = player->dc;
+
+ assert(player->queued || pc.command == PLAYER_COMMAND_SEEK);
+ assert(pc.next_song != NULL);
+
+ dc_start(dc, pc.next_song, player_buffer, pipe);
}
/**
@@ -130,38 +160,41 @@ player_dc_at_current_song(const struct player *player)
assert(player != NULL);
assert(player->pipe != NULL);
- return dc.pipe == player->pipe;
+ return player->dc->pipe == player->pipe;
}
/**
- * Has the decoder already begun decoding the next song?
- *
- * Note: this function does not check if the decoder is already
- * finished.
+ * Returns true if the decoder is decoding the next song (or has begun
+ * decoding it, or has finished doing it), and the player hasn't
+ * switched to that song yet.
*/
static bool
player_dc_at_next_song(const struct player *player)
{
- return dc.pipe != NULL && !player_dc_at_current_song(player);
+ return player->dc->pipe != NULL && !player_dc_at_current_song(player);
}
/**
* Stop the decoder and clears (and frees) its music pipe.
+ *
+ * Player lock is not held.
*/
static void
player_dc_stop(struct player *player)
{
- dc_stop(&pc.notify);
+ struct decoder_control *dc = player->dc;
- if (dc.pipe != NULL) {
+ dc_stop(dc);
+
+ if (dc->pipe != NULL) {
/* clear and free the decoder pipe */
- music_pipe_clear(dc.pipe, player_buffer);
+ music_pipe_clear(dc->pipe, player_buffer);
- if (dc.pipe != player->pipe)
- music_pipe_free(dc.pipe);
+ if (dc->pipe != player->pipe)
+ music_pipe_free(dc->pipe);
- dc.pipe = NULL;
+ dc->pipe = NULL;
}
}
@@ -169,35 +202,48 @@ player_dc_stop(struct player *player)
* After the decoder has been started asynchronously, wait for the
* "START" command to finish. The decoder may not be initialized yet,
* i.e. there is no audio_format information yet.
+ *
+ * The player lock is not held.
*/
static bool
player_wait_for_decoder(struct player *player)
{
- dc_command_wait(&pc.notify);
+ struct decoder_control *dc = player->dc;
- if (decoder_has_failed()) {
- assert(dc.next_song == NULL || dc.next_song->url != NULL);
- pc.errored_song = dc.next_song;
+ 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->queued = false;
+ player_unlock();
+
return false;
}
- pc.total_time = pc.next_song->tag != NULL
- ? pc.next_song->tag->time : 0;
- pc.bit_rate = 0;
- audio_format_clear(&pc.audio_format);
-
player->song = pc.next_song;
- pc.next_song = NULL;
- pc.elapsed_time = 0;
- player->queued = false;
+ player->elapsed_time = 0.0;
/* set the "starting" flag, which will be cleared by
player_check_decoder_startup() */
player->decoder_starting = true;
+ player_lock();
+
+ /* 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);
+
+ /* clear the queued song */
+ pc.next_song = NULL;
+
+ player_unlock();
+
/* call syncPlaylistWithQueue() in the main thread */
event_pipe_emit(PIPE_EVENT_PLAYLIST);
@@ -205,54 +251,86 @@ player_wait_for_decoder(struct player *player)
}
/**
+ * Returns the real duration of the song, comprising the duration
+ * indicated by the decoder plugin.
+ */
+static double
+real_song_duration(const struct song *song, double decoder_duration)
+{
+ assert(song != NULL);
+
+ if (decoder_duration <= 0.0)
+ /* the decoder plugin didn't provide information; fall
+ back to song_get_duration() */
+ return song_get_duration(song);
+
+ if (song->end_ms > 0 && song->end_ms / 1000.0 < decoder_duration)
+ return (song->end_ms - song->start_ms) / 1000.0;
+
+ return decoder_duration - song->start_ms / 1000.0;
+}
+
+/**
* The decoder has acknowledged the "START" command (see
* player_wait_for_decoder()). This function checks if the decoder
* initialization has completed yet.
+ *
+ * The player lock is not held.
*/
static bool
player_check_decoder_startup(struct player *player)
{
+ struct decoder_control *dc = player->dc;
+
assert(player->decoder_starting);
- if (decoder_has_failed()) {
+ decoder_lock(dc);
+
+ if (decoder_has_failed(dc)) {
/* the decoder failed */
- assert(dc.next_song == NULL || dc.next_song->url != NULL);
+ decoder_unlock(dc);
- pc.errored_song = dc.next_song;
+ player_lock();
+ pc.errored_song = dc->song;
pc.error = PLAYER_ERROR_FILE;
+ player_unlock();
return false;
- } else if (!decoder_is_starting()) {
+ } else if (!decoder_is_starting(dc)) {
/* the decoder is ready and ok */
+ decoder_unlock(dc);
+
if (audio_format_defined(&player->play_audio_format) &&
!audio_output_all_wait(1))
/* the output devices havn't finished playing
all chunks yet - wait for that */
return true;
- pc.total_time = dc.total_time;
- pc.audio_format = dc.in_audio_format;
- player->play_audio_format = dc.out_audio_format;
- player->size_to_time =
- audioFormatSizeToTime(&dc.out_audio_format);
+ player_lock();
+ pc.total_time = real_song_duration(dc->song, dc->total_time);
+ pc.audio_format = dc->in_audio_format;
+ player_unlock();
+
+ player->play_audio_format = dc->out_audio_format;
player->decoder_starting = false;
if (!player->paused &&
- !audio_output_all_open(&dc.out_audio_format,
+ !audio_output_all_open(&dc->out_audio_format,
player_buffer)) {
- char *uri = song_get_uri(dc.next_song);
+ char *uri = song_get_uri(dc->song);
g_warning("problems opening audio device "
"while playing \"%s\"", uri);
g_free(uri);
- assert(dc.next_song == NULL || dc.next_song->url != NULL);
- pc.errored_song = dc.next_song;
+ player_lock();
pc.error = PLAYER_ERROR_AUDIO;
/* pause: the user may resume playback as soon
as an audio output becomes available */
pc.state = PLAYER_STATE_PAUSE;
+ player_unlock();
+
player->paused = true;
return true;
}
@@ -261,7 +339,8 @@ player_check_decoder_startup(struct player *player)
} else {
/* the decoder is not yet ready; wait
some more */
- notify_wait(&pc.notify);
+ player_wait_decoder(dc);
+ decoder_unlock(dc);
return true;
}
@@ -271,20 +350,15 @@ player_check_decoder_startup(struct player *player)
* Sends a chunk of silence to the audio outputs. This is called when
* there is not enough decoded data in the pipe yet, to prevent
* underruns in the hardware buffers.
+ *
+ * The player lock is not held.
*/
static bool
player_send_silence(struct player *player)
{
- struct music_chunk *chunk;
- size_t frame_size =
- audio_format_frame_size(&player->play_audio_format);
- /* this formula ensures that we don't send
- partial frames */
- unsigned num_frames = sizeof(chunk->data) / frame_size;
-
assert(audio_format_defined(&player->play_audio_format));
- chunk = music_buffer_allocate(player_buffer);
+ struct music_chunk *chunk = music_buffer_allocate(player_buffer);
if (chunk == NULL) {
g_warning("Failed to allocate silence buffer");
return false;
@@ -294,6 +368,13 @@ player_send_silence(struct player *player)
chunk->audio_format = player->play_audio_format;
#endif
+ size_t frame_size =
+ audio_format_frame_size(&player->play_audio_format);
+ /* this formula ensures that we don't send
+ partial frames */
+ unsigned num_frames = sizeof(chunk->data) / frame_size;
+
+ chunk->times = -1.0; /* undefined time stamp */
chunk->length = num_frames * frame_size;
memset(chunk->data, 0, chunk->length);
@@ -307,15 +388,17 @@ player_send_silence(struct player *player)
/**
* This is the handler for the #PLAYER_COMMAND_SEEK command.
+ *
+ * The player lock is not held.
*/
static bool player_seek_decoder(struct player *player)
{
- double where;
- bool ret;
+ struct song *song = pc.next_song;
+ struct decoder_control *dc = player->dc;
assert(pc.next_song != NULL);
- if (decoder_current_song() != pc.next_song) {
+ if (decoder_current_song(dc) != song) {
/* the decoder is already decoding the "next" song -
stop it and start the previous song again */
@@ -326,9 +409,8 @@ static bool player_seek_decoder(struct player *player)
music_pipe_clear(player->pipe, player_buffer);
/* re-start the decoder */
- dc_start_async(pc.next_song, player->pipe);
- ret = player_wait_for_decoder(player);
- if (!ret) {
+ player_dc_start(player, player->pipe);
+ if (!player_wait_for_decoder(player)) {
/* decoder failure */
player_command_finished();
return false;
@@ -339,7 +421,7 @@ static bool player_seek_decoder(struct player *player)
but it is the same song file; exchange the pipe */
music_pipe_clear(player->pipe, player_buffer);
music_pipe_free(player->pipe);
- player->pipe = dc.pipe;
+ player->pipe = dc->pipe;
}
pc.next_song = NULL;
@@ -349,8 +431,7 @@ static bool player_seek_decoder(struct player *player)
/* wait for the decoder to complete initialization */
while (player->decoder_starting) {
- ret = player_check_decoder_startup(player);
- if (!ret) {
+ if (!player_check_decoder_startup(player)) {
/* decoder failure */
player_command_finished();
return false;
@@ -359,20 +440,20 @@ static bool player_seek_decoder(struct player *player)
/* send the SEEK command */
- where = pc.seek_where;
+ double where = pc.seek_where;
if (where > pc.total_time)
where = pc.total_time - 0.1;
if (where < 0.0)
where = 0.0;
- ret = dc_seek(&pc.notify, where);
- if (!ret) {
+ if (!dc_seek(dc, where + song->start_ms / 1000.0)) {
/* decoder failure */
player_command_finished();
return false;
}
- pc.elapsed_time = where;
+ player->elapsed_time = where;
+
player_command_finished();
player->xfade = XFADE_UNKNOWN;
@@ -385,53 +466,73 @@ static bool player_seek_decoder(struct player *player)
return true;
}
+/**
+ * Player lock must be held before calling.
+ */
static void player_process_command(struct player *player)
{
+ G_GNUC_UNUSED struct decoder_control *dc = player->dc;
+
switch (pc.command) {
case PLAYER_COMMAND_NONE:
- case PLAYER_COMMAND_PLAY:
case PLAYER_COMMAND_STOP:
case PLAYER_COMMAND_EXIT:
case PLAYER_COMMAND_CLOSE_AUDIO:
break;
+ case PLAYER_COMMAND_UPDATE_AUDIO:
+ player_unlock();
+ audio_output_all_enable_disable();
+ player_lock();
+ player_command_finished_locked();
+ break;
+
case PLAYER_COMMAND_QUEUE:
assert(pc.next_song != NULL);
assert(!player->queued);
assert(!player_dc_at_next_song(player));
player->queued = true;
- player_command_finished();
+ player_command_finished_locked();
break;
case PLAYER_COMMAND_PAUSE:
+ player_unlock();
+
player->paused = !player->paused;
if (player->paused) {
audio_output_all_pause();
+ player_lock();
+
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();
pc.state = PLAYER_STATE_PLAY;
} else if (audio_output_all_open(&player->play_audio_format, player_buffer)) {
/* unpaused, continue playing */
+ player_lock();
+
pc.state = PLAYER_STATE_PLAY;
} else {
/* the audio device has failed - rollback to
pause mode */
- assert(dc.next_song == NULL || dc.next_song->url != NULL);
- pc.errored_song = dc.next_song;
pc.error = PLAYER_ERROR_AUDIO;
player->paused = true;
+
+ player_lock();
}
- player_command_finished();
+ player_command_finished_locked();
break;
case PLAYER_COMMAND_SEEK:
+ player_unlock();
player_seek_decoder(player);
+ player_lock();
break;
case PLAYER_COMMAND_CANCEL:
@@ -443,80 +544,89 @@ static void player_process_command(struct player *player)
return;
}
- if (player_dc_at_next_song(player))
+ if (player_dc_at_next_song(player)) {
/* the decoder is already decoding the song -
stop it and reset the position */
+ player_unlock();
player_dc_stop(player);
+ player_lock();
+ }
pc.next_song = NULL;
player->queued = false;
- player_command_finished();
+ player_command_finished_locked();
+ break;
+
+ case PLAYER_COMMAND_REFRESH:
+ if (audio_format_defined(&player->play_audio_format) &&
+ !player->paused) {
+ player_unlock();
+ audio_output_all_check();
+ player_lock();
+ }
+
+ 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();
break;
}
}
+static void
+update_song_tag(struct song *song, const struct tag *new_tag)
+{
+ if (song_is_file(song))
+ /* don't update tags of local files, only remote
+ streams may change tags dynamically */
+ return;
+
+ struct tag *old_tag = song->tag;
+ song->tag = tag_dup(new_tag);
+
+ if (old_tag != NULL)
+ tag_free(old_tag);
+
+ /* the main thread will update the playlist version when he
+ receives this event */
+ event_pipe_emit(PIPE_EVENT_TAG);
+
+ /* notify all clients that the tag of the current song has
+ changed */
+ idle_add(IDLE_PLAYER);
+}
+
/**
* Plays a #music_chunk object (after applying software volume). If
* it contains a (stream) tag, copy it to the current song, so MPD's
* playlist reflects the new stream tag.
+ *
+ * Player lock is not held.
*/
static bool
play_chunk(struct song *song, struct music_chunk *chunk,
- const struct audio_format *format, double sizeToTime)
+ const struct audio_format *format)
{
- bool success;
-
assert(music_chunk_check_format(chunk, format));
- if (chunk->tag != NULL) {
- if (!song_is_file(song)) {
- /* always update the tag of remote streams */
- struct tag *old_tag = song->tag;
-
- song->tag = tag_dup(chunk->tag);
-
- if (old_tag != NULL)
- tag_free(old_tag);
-
- /* the main thread will update the playlist
- version when he receives this event */
- event_pipe_emit(PIPE_EVENT_TAG);
-
- /* notify all clients that the tag of the
- current song has changed */
- idle_add(IDLE_PLAYER);
- }
- }
+ if (chunk->tag != NULL)
+ update_song_tag(song, chunk->tag);
if (chunk->length == 0) {
music_buffer_return(player_buffer, chunk);
return true;
}
- pc.elapsed_time = chunk->times;
pc.bit_rate = chunk->bit_rate;
- /* apply software volume */
-
- success = pcm_volume(chunk->data, chunk->length,
- format, pc.software_volume);
- if (!success) {
- g_warning("pcm_volume() failed on %u:%u:%u",
- format->sample_rate, format->bits, format->channels);
- pc.errored_song = dc.current_song;
- pc.error = PLAYER_ERROR_AUDIO;
- return false;
- }
-
/* send the chunk to the audio outputs */
- if (!audio_output_all_play(chunk)) {
- pc.errored_song = dc.current_song;
- pc.error = PLAYER_ERROR_AUDIO;
+ if (!audio_output_all_play(chunk))
return false;
- }
- pc.total_play_time += sizeToTime * chunk->length;
+ pc.total_play_time += (double)chunk->length /
+ audio_format_time_to_size(format);
return true;
}
@@ -529,22 +639,22 @@ play_chunk(struct song *song, struct music_chunk *chunk,
static bool
play_next_chunk(struct player *player)
{
- struct music_chunk *chunk = NULL;
- unsigned cross_fade_position;
- bool success;
+ struct decoder_control *dc = player->dc;
if (!audio_output_all_wait(64))
/* the output pipe is still large enough, don't send
another chunk */
return true;
+ unsigned cross_fade_position;
+ struct music_chunk *chunk = NULL;
if (player->xfade == XFADE_ENABLED &&
player_dc_at_next_song(player) &&
(cross_fade_position = music_pipe_size(player->pipe))
<= player->cross_fade_chunks) {
/* perform cross fade */
struct music_chunk *other_chunk =
- music_pipe_shift(dc.pipe);
+ music_pipe_shift(dc->pipe);
if (!player->cross_fading) {
/* beginning of the cross fade - adjust
@@ -558,6 +668,7 @@ play_next_chunk(struct player *player)
if (other_chunk != NULL) {
chunk = music_pipe_shift(player->pipe);
assert(chunk != NULL);
+ assert(chunk->other == NULL);
/* don't send the tags of the new song (which
is being faded in) yet; postpone it until
@@ -567,21 +678,43 @@ play_next_chunk(struct player *player)
other_chunk->tag);
other_chunk->tag = NULL;
- cross_fade_apply(chunk, other_chunk,
- &dc.out_audio_format,
- cross_fade_position,
- player->cross_fade_chunks);
- music_buffer_return(player_buffer, other_chunk);
+ if (isnan(pc.mixramp_delay_seconds)) {
+ chunk->mix_ratio = ((float)cross_fade_position)
+ / player->cross_fade_chunks;
+ } else {
+ chunk->mix_ratio = nan("");
+ }
+
+ if (music_chunk_is_empty(other_chunk)) {
+ /* the "other" chunk was a music_chunk
+ which had only a tag, but no music
+ data - we cannot cross-fade that;
+ but since this happens only at the
+ beginning of the new song, we can
+ easily recover by throwing it away
+ now */
+ music_buffer_return(player_buffer,
+ other_chunk);
+ other_chunk = NULL;
+ }
+
+ chunk->other = other_chunk;
} else {
/* there are not enough decoded chunks yet */
- if (decoder_is_idle()) {
+
+ decoder_lock(dc);
+
+ if (decoder_is_idle(dc)) {
/* the decoder isn't running, abort
cross fading */
+ decoder_unlock(dc);
+
player->xfade = XFADE_DISABLED;
} else {
/* wait for the decoder */
- notify_signal(&dc.notify);
- notify_wait(&pc.notify);
+ decoder_signal(dc);
+ player_wait_decoder(dc);
+ decoder_unlock(dc);
return true;
}
@@ -603,27 +736,32 @@ play_next_chunk(struct player *player)
/* play the current chunk */
- success = play_chunk(player->song, chunk, &player->play_audio_format,
- player->size_to_time);
-
- if (!success) {
+ if (!play_chunk(player->song, chunk, &player->play_audio_format)) {
music_buffer_return(player_buffer, chunk);
+ player_lock();
+
+ pc.error = PLAYER_ERROR_AUDIO;
+
/* pause: the user may resume playback as soon as an
audio output becomes available */
pc.state = PLAYER_STATE_PAUSE;
player->paused = true;
+ player_unlock();
+
return false;
}
/* this formula should prevent that the decoder gets woken up
with each chunk; it is more efficient to make it decode a
larger block at a time */
- if (!decoder_is_idle() &&
- music_pipe_size(dc.pipe) <= (pc.buffered_before_play +
+ decoder_lock(dc);
+ if (!decoder_is_idle(dc) &&
+ music_pipe_size(dc->pipe) <= (pc.buffered_before_play +
music_buffer_size(player_buffer) * 3) / 4)
- notify_signal(&dc.notify);
+ decoder_signal(dc);
+ decoder_unlock(dc);
return true;
}
@@ -633,6 +771,8 @@ play_next_chunk(struct player *player)
* has consumed all chunks of the current song, and we should start
* sending chunks from the next one.
*
+ * The player lock is not held.
+ *
* @return true on success, false on error (playback will be stopped)
*/
static bool
@@ -640,8 +780,14 @@ player_song_border(struct player *player)
{
player->xfade = XFADE_UNKNOWN;
+ char *uri = song_get_uri(player->song);
+ g_message("played \"%s\"", uri);
+ g_free(uri);
+
music_pipe_free(player->pipe);
- player->pipe = dc.pipe;
+ player->pipe = player->dc->pipe;
+
+ audio_output_all_song_border();
if (!player_wait_for_decoder(player))
return false;
@@ -654,53 +800,59 @@ 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(void)
+static void do_play(struct decoder_control *dc)
{
struct player player = {
+ .dc = dc,
.buffering = true,
.decoder_starting = false,
.paused = false,
- .queued = false,
+ .queued = true,
.song = NULL,
.xfade = XFADE_UNKNOWN,
.cross_fading = false,
.cross_fade_chunks = 0,
.cross_fade_tag = NULL,
- .size_to_time = 0.0,
+ .elapsed_time = 0.0,
};
+ player_unlock();
+
player.pipe = music_pipe_new();
- dc.buffer = player_buffer;
- dc_start(&pc.notify, pc.next_song, player.pipe);
+ player_dc_start(&player, player.pipe);
if (!player_wait_for_decoder(&player)) {
player_dc_stop(&player);
player_command_finished();
music_pipe_free(player.pipe);
event_pipe_emit(PIPE_EVENT_PLAYLIST);
+ player_lock();
return;
}
- pc.elapsed_time = 0;
+ player_lock();
pc.state = PLAYER_STATE_PLAY;
- player_command_finished();
+ player_command_finished_locked();
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();
audio_output_all_cancel();
break;
}
+ player_unlock();
+
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 &&
- !decoder_is_idle()) {
+ !decoder_lock_is_idle(dc)) {
/* not enough decoded buffer space yet */
if (!player.paused &&
@@ -709,7 +861,11 @@ static void do_play(void)
!player_send_silence(&player))
break;
- notify_wait(&pc.notify);
+ decoder_lock(dc);
+ /* XXX race condition: check decoder again */
+ player_wait_decoder(dc);
+ decoder_unlock(dc);
+ player_lock();
continue;
} else {
/* buffering is complete */
@@ -719,11 +875,21 @@ static void do_play(void)
if (player.decoder_starting) {
/* wait until the decoder is initialized completely */
- bool success;
- success = player_check_decoder_startup(&player);
- if (!success)
+ if (!player_check_decoder_startup(&player))
break;
+
+ /* seek to the beginning of the range */
+ const struct song *song = decoder_current_song(dc);
+ if (song != NULL && song->start_ms > 0 &&
+ /* we must not send a seek command until
+ the decoder is initialized
+ completely */
+ !player.decoder_starting &&
+ !dc_seek(dc, song->start_ms / 1000.0))
+ player_dc_stop(&player);
+
+ player_lock();
continue;
}
@@ -731,31 +897,35 @@ static void do_play(void)
/*
music_pipe_check_format(&play_audio_format,
player.next_song_chunk,
- &dc.out_audio_format);
+ &dc->out_audio_format);
*/
#endif
- if (decoder_is_idle() && player.queued) {
+ if (decoder_lock_is_idle(dc) && player.queued &&
+ dc->pipe == player.pipe) {
/* the decoder has finished the current song;
make it decode the next song */
- assert(pc.next_song != NULL);
- assert(!player_dc_at_next_song(&player));
- dc.pipe = NULL;
+ assert(dc->pipe == NULL || dc->pipe == player.pipe);
- player.queued = false;
- dc_start_async(pc.next_song, music_pipe_new());
+ player_dc_start(&player, music_pipe_new());
}
if (player_dc_at_next_song(&player) &&
player.xfade == XFADE_UNKNOWN &&
- !decoder_is_starting()) {
+ !decoder_lock_is_starting(dc)) {
/* enable cross fading in this song? if yes,
calculate how many chunks will be required
for it */
player.cross_fade_chunks =
- cross_fade_calc(pc.cross_fade_seconds, dc.total_time,
- &dc.out_audio_format,
+ 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,
+ dc->mixramp_prev_end,
+ &dc->out_audio_format,
&player.play_audio_format,
music_buffer_size(player_buffer) -
pc.buffered_before_play);
@@ -768,9 +938,13 @@ static void do_play(void)
player.xfade = XFADE_DISABLED;
}
- if (player.paused)
- notify_wait(&pc.notify);
- else if (!music_pipe_empty(player.pipe)) {
+ if (player.paused) {
+ player_lock();
+
+ if (pc.command == PLAYER_COMMAND_NONE)
+ player_wait();
+ continue;
+ } else if (!music_pipe_empty(player.pipe)) {
/* at least one music chunk is ready - send it
to the audio output */
@@ -787,12 +961,16 @@ static void do_play(void)
if (!player_song_border(&player))
break;
- } else if (decoder_is_idle()) {
+ } else if (decoder_lock_is_idle(dc)) {
/* check the size of the pipe again, because
the decoder thread may have added something
since we last checked */
- if (music_pipe_empty(player.pipe))
+ if (music_pipe_empty(player.pipe)) {
+ /* wait for the hardware to finish
+ playback */
+ audio_output_all_drain();
break;
+ }
} else {
/* the decoder is too busy and hasn't provided
new PCM data in time: send silence (if the
@@ -800,11 +978,8 @@ static void do_play(void)
if (!player_send_silence(&player))
break;
}
- }
- if (player.queued) {
- assert(pc.next_song != NULL);
- pc.next_song = NULL;
+ player_lock();
}
player_dc_stop(&player);
@@ -815,38 +990,61 @@ static void do_play(void)
if (player.cross_fade_tag != NULL)
tag_free(player.cross_fade_tag);
+ player_lock();
+
+ if (player.queued) {
+ assert(pc.next_song != NULL);
+ pc.next_song = NULL;
+ }
+
pc.state = PLAYER_STATE_STOP;
+
+ player_unlock();
+
event_pipe_emit(PIPE_EVENT_PLAYLIST);
+
+ player_lock();
}
static gpointer player_task(G_GNUC_UNUSED gpointer arg)
{
- decoder_thread_start();
+ struct decoder_control dc;
+
+ dc_init(&dc);
+ decoder_thread_start(&dc);
player_buffer = music_buffer_new(pc.buffer_chunks);
+ player_lock();
+
while (1) {
switch (pc.command) {
- case PLAYER_COMMAND_PLAY:
case PLAYER_COMMAND_QUEUE:
assert(pc.next_song != NULL);
- do_play();
+ do_play(&dc);
break;
case PLAYER_COMMAND_STOP:
+ player_unlock();
audio_output_all_cancel();
+ player_lock();
+
/* fall through */
case PLAYER_COMMAND_SEEK:
case PLAYER_COMMAND_PAUSE:
pc.next_song = NULL;
- player_command_finished();
+ player_command_finished_locked();
break;
case PLAYER_COMMAND_CLOSE_AUDIO:
- audio_output_all_close();
- player_command_finished();
+ player_unlock();
+
+ audio_output_all_release();
+
+ player_lock();
+ player_command_finished_locked();
#ifndef NDEBUG
/* in the DEBUG build, check for leaked
@@ -858,34 +1056,47 @@ static gpointer player_task(G_GNUC_UNUSED gpointer arg)
break;
+ case PLAYER_COMMAND_UPDATE_AUDIO:
+ player_unlock();
+ audio_output_all_enable_disable();
+ player_lock();
+ player_command_finished_locked();
+ break;
+
case PLAYER_COMMAND_EXIT:
- dc_quit();
+ player_unlock();
+
+ dc_quit(&dc);
+ dc_deinit(&dc);
audio_output_all_close();
music_buffer_free(player_buffer);
+
player_command_finished();
- g_thread_exit(NULL);
- break;
+ return NULL;
case PLAYER_COMMAND_CANCEL:
pc.next_song = NULL;
- player_command_finished();
+ player_command_finished_locked();
+ break;
+
+ case PLAYER_COMMAND_REFRESH:
+ /* no-op when not playing */
+ player_command_finished_locked();
break;
case PLAYER_COMMAND_NONE:
- notify_wait(&pc.notify);
+ player_wait();
break;
}
}
- return NULL;
}
void player_create(void)
{
- GError *e = NULL;
-
assert(pc.thread == NULL);
+ GError *e = NULL;
pc.thread = g_thread_create(player_task, NULL, true, &e);
if (pc.thread == NULL)
- g_error("Failed to spawn player task: %s", e->message);
+ MPD_ERROR("Failed to spawn player task: %s", e->message);
}
diff --git a/src/player_thread.h b/src/player_thread.h
index 51ad28fcc..e645b1d09 100644
--- a/src/player_thread.h
+++ b/src/player_thread.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
diff --git a/src/playlist.c b/src/playlist.c
index 660dd6a83..4a1e54814 100644
--- a/src/playlist.c
+++ b/src/playlist.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -17,6 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "playlist_internal.h"
#include "playlist_save.h"
#include "player_control.h"
@@ -34,7 +35,8 @@
#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "playlist"
-void playlistVersionChange(struct playlist *playlist)
+void
+playlist_increment_version_all(struct playlist *playlist)
{
queue_modify_all(&playlist->queue);
idle_add(IDLE_PLAYLIST);
@@ -61,16 +63,12 @@ playlist_init(struct playlist *playlist)
playlist->queued = -1;
playlist->current = -1;
-
- playlist->prev_elapsed = g_timer_new();
}
void
playlist_finish(struct playlist *playlist)
{
queue_finish(&playlist->queue);
-
- g_timer_destroy(playlist->prev_elapsed);
}
/**
@@ -91,28 +89,34 @@ playlist_queue_song_order(struct playlist *playlist, unsigned order)
g_debug("queue song %i:\"%s\"", playlist->queued, uri);
g_free(uri);
- queueSong(song);
+ pc_enqueue_song(song);
}
/**
- * Check if the player thread has already started playing the "queued"
- * song.
+ * Called if the player thread has started playing the "queued" song.
*/
-static void syncPlaylistWithQueue(struct playlist *playlist)
+static void
+playlist_song_started(struct playlist *playlist)
{
- if (pc.next_song == NULL && playlist->queued != -1) {
- /* queued song has started: copy queued to current,
- and notify the clients */
+ assert(pc.next_song == NULL);
+ assert(playlist->queued >= -1);
- int current = playlist->current;
- playlist->current = playlist->queued;
- playlist->queued = -1;
+ /* queued song has started: copy queued to current,
+ and notify the clients */
- if(playlist->queue.consume)
- deleteFromPlaylist(playlist, queue_order_to_position(&playlist->queue, current));
+ int current = playlist->current;
+ playlist->current = playlist->queued;
+ playlist->queued = -1;
- idle_add(IDLE_PLAYER);
+ /* Pause if we are in single mode. */
+ if(playlist->queue.single && !playlist->queue.repeat) {
+ pc_set_pause(true);
}
+
+ if(playlist->queue.consume)
+ playlist_delete(playlist, queue_order_to_position(&playlist->queue, current));
+
+ idle_add(IDLE_PLAYER);
}
const struct song *
@@ -179,7 +183,7 @@ playlist_update_queued_song(struct playlist *playlist, const struct song *prev)
}
void
-playPlaylistOrderNumber(struct playlist *playlist, int orderNum)
+playlist_play_order(struct playlist *playlist, int orderNum)
{
struct song *song;
char *uri;
@@ -193,38 +197,45 @@ playPlaylistOrderNumber(struct playlist *playlist, int orderNum)
g_debug("play %i:\"%s\"", orderNum, uri);
g_free(uri);
- playerPlay(song);
+ pc_play(song);
playlist->current = orderNum;
}
static void
-playPlaylistIfPlayerStopped(struct playlist *playlist);
+playlist_resume_playback(struct playlist *playlist);
/**
* 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 syncPlayerAndPlaylist(struct playlist *playlist)
+void
+playlist_sync(struct playlist *playlist)
{
if (!playlist->playing)
/* this event has reached us out of sync: we aren't
playing anymore; ignore the event */
return;
- if (getPlayerState() == PLAYER_STATE_STOP)
+ player_lock();
+ enum player_state pc_state = pc_get_state();
+ const struct song *pc_next_song = pc.next_song;
+ player_unlock();
+
+ 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 */
- playPlaylistIfPlayerStopped(playlist);
+ playlist_resume_playback(playlist);
else {
/* check if the player thread has already started
playing the queued song */
- syncPlaylistWithQueue(playlist);
+ if (pc_next_song == NULL && playlist->queued != -1)
+ playlist_song_started(playlist);
/* make sure the queued song is always set (if
possible) */
- if (pc.next_song == NULL)
+ if (pc.next_song == NULL && playlist->queued < 0)
playlist_update_queued_song(playlist, NULL);
}
}
@@ -234,14 +245,14 @@ void syncPlayerAndPlaylist(struct playlist *playlist)
* decide whether to re-start playback
*/
static void
-playPlaylistIfPlayerStopped(struct playlist *playlist)
+playlist_resume_playback(struct playlist *playlist)
{
enum player_error error;
assert(playlist->playing);
- assert(getPlayerState() == PLAYER_STATE_STOP);
+ assert(pc_get_state() == PLAYER_STATE_STOP);
- error = getPlayerError();
+ error = pc_get_error();
if (error == PLAYER_ERROR_NOERROR)
playlist->error_count = 0;
else
@@ -252,37 +263,38 @@ playPlaylistIfPlayerStopped(struct playlist *playlist)
playlist->error_count >= queue_length(&playlist->queue))
/* too many errors, or critical error: stop
playback */
- stopPlaylist(playlist);
+ playlist_stop(playlist);
else
/* continue playback at the next song */
- nextSongInPlaylist(playlist);
+ playlist_next(playlist);
}
bool
-getPlaylistRepeatStatus(const struct playlist *playlist)
+playlist_get_repeat(const struct playlist *playlist)
{
return playlist->queue.repeat;
}
bool
-getPlaylistRandomStatus(const struct playlist *playlist)
+playlist_get_random(const struct playlist *playlist)
{
return playlist->queue.random;
}
bool
-getPlaylistSingleStatus(const struct playlist *playlist)
+playlist_get_single(const struct playlist *playlist)
{
return playlist->queue.single;
}
bool
-getPlaylistConsumeStatus(const struct playlist *playlist)
+playlist_get_consume(const struct playlist *playlist)
{
return playlist->queue.consume;
}
-void setPlaylistRepeatStatus(struct playlist *playlist, bool status)
+void
+playlist_set_repeat(struct playlist *playlist, bool status)
{
if (status == playlist->queue.repeat)
return;
@@ -297,7 +309,8 @@ void setPlaylistRepeatStatus(struct playlist *playlist, bool status)
idle_add(IDLE_OPTIONS);
}
-static void orderPlaylist(struct playlist *playlist)
+static void
+playlist_order(struct playlist *playlist)
{
if (playlist->current >= 0)
/* update playlist.current, order==position now */
@@ -307,7 +320,8 @@ static void orderPlaylist(struct playlist *playlist)
queue_restore_order(&playlist->queue);
}
-void setPlaylistSingleStatus(struct playlist *playlist, bool status)
+void
+playlist_set_single(struct playlist *playlist, bool status)
{
if (status == playlist->queue.single)
return;
@@ -322,7 +336,8 @@ void setPlaylistSingleStatus(struct playlist *playlist, bool status)
idle_add(IDLE_OPTIONS);
}
-void setPlaylistConsumeStatus(struct playlist *playlist, bool status)
+void
+playlist_set_consume(struct playlist *playlist, bool status)
{
if (status == playlist->queue.consume)
return;
@@ -331,7 +346,8 @@ void setPlaylistConsumeStatus(struct playlist *playlist, bool status)
idle_add(IDLE_OPTIONS);
}
-void setPlaylistRandomStatus(struct playlist *playlist, bool status)
+void
+playlist_set_random(struct playlist *playlist, bool status)
{
const struct song *queued;
@@ -366,14 +382,15 @@ void setPlaylistRandomStatus(struct playlist *playlist, bool status)
} else
playlist->current = -1;
} else
- orderPlaylist(playlist);
+ playlist_order(playlist);
playlist_update_queued_song(playlist, queued);
idle_add(IDLE_OPTIONS);
}
-int getPlaylistCurrentSong(const struct playlist *playlist)
+int
+playlist_get_current_song(const struct playlist *playlist)
{
if (playlist->current >= 0)
return queue_order_to_position(&playlist->queue,
@@ -382,19 +399,15 @@ int getPlaylistCurrentSong(const struct playlist *playlist)
return -1;
}
-int getPlaylistNextSong(const struct playlist *playlist)
+int
+playlist_get_next_song(const struct playlist *playlist)
{
if (playlist->current >= 0)
{
- if (playlist->queue.single == 1)
- {
- if (playlist->queue.repeat == 1)
- return queue_order_to_position(&playlist->queue,
- playlist->current);
- else
- return -1;
- }
- if (playlist->current + 1 < (int)queue_length(&playlist->queue))
+ if (playlist->queue.single == 1 && playlist->queue.repeat == 1)
+ return queue_order_to_position(&playlist->queue,
+ playlist->current);
+ else if (playlist->current + 1 < (int)queue_length(&playlist->queue))
return queue_order_to_position(&playlist->queue,
playlist->current + 1);
else if (playlist->queue.repeat == 1)
@@ -405,19 +418,19 @@ int getPlaylistNextSong(const struct playlist *playlist)
}
unsigned long
-getPlaylistVersion(const struct playlist *playlist)
+playlist_get_version(const struct playlist *playlist)
{
return playlist->queue.version;
}
int
-getPlaylistLength(const struct playlist *playlist)
+playlist_get_length(const struct playlist *playlist)
{
return queue_length(&playlist->queue);
}
unsigned
-getPlaylistSongId(const struct playlist *playlist, unsigned song)
+playlist_get_song_id(const struct playlist *playlist, unsigned song)
{
return queue_position_to_id(&playlist->queue, song);
}
diff --git a/src/playlist.h b/src/playlist.h
index 57b2450fa..3ba90ff91 100644
--- a/src/playlist.h
+++ b/src/playlist.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -23,7 +23,6 @@
#include "queue.h"
#include <stdbool.h>
-#include <stdio.h>
#define PLAYLIST_COMMENT '#'
@@ -82,21 +81,16 @@ struct playlist {
* This variable is only valid if #playing is true.
*/
int queued;
-
- /**
- * This timer tracks the time elapsed since the last "prev"
- * command. If that is less than one second ago, "prev" jumps
- * to the previous song instead of rewinding the current song.
- */
- GTimer *prev_elapsed;
};
/** the global playlist object */
extern struct playlist g_playlist;
-void initPlaylist(void);
+void
+playlist_global_init(void);
-void finishPlaylist(void);
+void
+playlist_global_finish(void);
void
playlist_init(struct playlist *playlist);
@@ -116,11 +110,8 @@ playlist_get_queue(const struct playlist *playlist)
return &playlist->queue;
}
-void readPlaylistState(FILE *);
-
-void savePlaylistState(FILE *);
-
-void clearPlaylist(struct playlist *playlist);
+void
+playlist_clear(struct playlist *playlist);
#ifndef WIN32
/**
@@ -133,90 +124,111 @@ playlist_append_file(struct playlist *playlist, const char *path, int uid,
#endif
enum playlist_result
-addToPlaylist(struct playlist *playlist, const char *file, unsigned *added_id);
+playlist_append_uri(struct playlist *playlist, const char *file,
+ unsigned *added_id);
enum playlist_result
-addSongToPlaylist(struct playlist *playlist,
+playlist_append_song(struct playlist *playlist,
struct song *song, unsigned *added_id);
enum playlist_result
-deleteFromPlaylist(struct playlist *playlist, unsigned song);
+playlist_delete(struct playlist *playlist, unsigned song);
+
+/**
+ * Deletes a range of songs from the playlist.
+ *
+ * @param start the position of the first song to delete
+ * @param end the position after the last song to delete
+ */
+enum playlist_result
+playlist_delete_range(struct playlist *playlist, unsigned start, unsigned end);
enum playlist_result
-deleteFromPlaylistById(struct playlist *playlist, unsigned song);
+playlist_delete_id(struct playlist *playlist, unsigned song);
-void stopPlaylist(struct playlist *playlist);
+void
+playlist_stop(struct playlist *playlist);
enum playlist_result
-playPlaylist(struct playlist *playlist, int song);
+playlist_play(struct playlist *playlist, int song);
enum playlist_result
-playPlaylistById(struct playlist *playlist, int song);
+playlist_play_id(struct playlist *playlist, int song);
-void nextSongInPlaylist(struct playlist *playlist);
+void
+playlist_next(struct playlist *playlist);
-void syncPlayerAndPlaylist(struct playlist *playlist);
+void
+playlist_sync(struct playlist *playlist);
-void previousSongInPlaylist(struct playlist *playlist);
+void
+playlist_previous(struct playlist *playlist);
-void shufflePlaylist(struct playlist *playlist, unsigned start, unsigned end);
+void
+playlist_shuffle(struct playlist *playlist, unsigned start, unsigned end);
void
-deleteASongFromPlaylist(struct playlist *playlist, const struct song *song);
+playlist_delete_song(struct playlist *playlist, const struct song *song);
enum playlist_result
-moveSongRangeInPlaylist(struct playlist *playlist, unsigned start, unsigned end, int to);
+playlist_move_range(struct playlist *playlist, unsigned start, unsigned end, int to);
enum playlist_result
-moveSongInPlaylistById(struct playlist *playlist, unsigned id, int to);
+playlist_move_id(struct playlist *playlist, unsigned id, int to);
enum playlist_result
-swapSongsInPlaylist(struct playlist *playlist, unsigned song1, unsigned song2);
+playlist_swap_songs(struct playlist *playlist, unsigned song1, unsigned song2);
enum playlist_result
-swapSongsInPlaylistById(struct playlist *playlist, unsigned id1, unsigned id2);
+playlist_swap_songs_id(struct playlist *playlist, unsigned id1, unsigned id2);
bool
-getPlaylistRepeatStatus(const struct playlist *playlist);
+playlist_get_repeat(const struct playlist *playlist);
-void setPlaylistRepeatStatus(struct playlist *playlist, bool status);
+void
+playlist_set_repeat(struct playlist *playlist, bool status);
bool
-getPlaylistRandomStatus(const struct playlist *playlist);
+playlist_get_random(const struct playlist *playlist);
-void setPlaylistRandomStatus(struct playlist *playlist, bool status);
+void
+playlist_set_random(struct playlist *playlist, bool status);
bool
-getPlaylistSingleStatus(const struct playlist *playlist);
+playlist_get_single(const struct playlist *playlist);
-void setPlaylistSingleStatus(struct playlist *playlist, bool status);
+void
+playlist_set_single(struct playlist *playlist, bool status);
bool
-getPlaylistConsumeStatus(const struct playlist *playlist);
+playlist_get_consume(const struct playlist *playlist);
-void setPlaylistConsumeStatus(struct playlist *playlist, bool status);
+void
+playlist_set_consume(struct playlist *playlist, bool status);
-int getPlaylistCurrentSong(const struct playlist *playlist);
+int
+playlist_get_current_song(const struct playlist *playlist);
-int getPlaylistNextSong(const struct playlist *playlist);
+int
+playlist_get_next_song(const struct playlist *playlist);
unsigned
-getPlaylistSongId(const struct playlist *playlist, unsigned song);
+playlist_get_song_id(const struct playlist *playlist, unsigned song);
-int getPlaylistLength(const struct playlist *playlist);
+int
+playlist_get_length(const struct playlist *playlist);
unsigned long
-getPlaylistVersion(const struct playlist *playlist);
+playlist_get_version(const struct playlist *playlist);
enum playlist_result
-seekSongInPlaylist(struct playlist *playlist, unsigned song, float seek_time);
+playlist_seek_song(struct playlist *playlist, unsigned song, float seek_time);
enum playlist_result
-seekSongInPlaylistById(struct playlist *playlist,
+playlist_seek_song_id(struct playlist *playlist,
unsigned id, float seek_time);
-void playlistVersionChange(struct playlist *playlist);
-
-int is_valid_playlist_name(const char *utf8path);
+void
+playlist_increment_version_all(struct playlist *playlist);
#endif
diff --git a/src/playlist/asx_playlist_plugin.c b/src/playlist/asx_playlist_plugin.c
new file mode 100644
index 000000000..39513e710
--- /dev/null
+++ b/src/playlist/asx_playlist_plugin.c
@@ -0,0 +1,322 @@
+/*
+ * 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/asx_playlist_plugin.h"
+#include "playlist_plugin.h"
+#include "input_stream.h"
+#include "song.h"
+#include "tag.h"
+
+#include <glib.h>
+
+#include <assert.h>
+#include <string.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "asx"
+
+/**
+ * This is the state object for the GLib XML parser.
+ */
+struct asx_parser {
+ /**
+ * The list of songs (in reverse order because that's faster
+ * while adding).
+ */
+ GSList *songs;
+
+ /**
+ * The current position in the XML file.
+ */
+ enum {
+ ROOT, ENTRY,
+ } state;
+
+ /**
+ * The current tag within the "entry" element. This is only
+ * valid if state==ENTRY. TAG_NUM_OF_ITEM_TYPES means there
+ * is no (known) tag.
+ */
+ enum tag_type tag;
+
+ /**
+ * The current song. It is allocated after the "location"
+ * element.
+ */
+ struct song *song;
+};
+
+static const gchar *
+get_attribute(const gchar **attribute_names, const gchar **attribute_values,
+ const gchar *name)
+{
+ for (unsigned i = 0; attribute_names[i] != NULL; ++i)
+ if (g_ascii_strcasecmp(attribute_names[i], name) == 0)
+ return attribute_values[i];
+
+ return NULL;
+}
+
+static void
+asx_start_element(G_GNUC_UNUSED GMarkupParseContext *context,
+ const gchar *element_name,
+ const gchar **attribute_names,
+ const gchar **attribute_values,
+ gpointer user_data, G_GNUC_UNUSED GError **error)
+{
+ struct asx_parser *parser = user_data;
+
+ switch (parser->state) {
+ case ROOT:
+ if (g_ascii_strcasecmp(element_name, "entry") == 0) {
+ parser->state = ENTRY;
+ parser->song = song_remote_new("asx:");
+ parser->tag = TAG_NUM_OF_ITEM_TYPES;
+ }
+
+ break;
+
+ case ENTRY:
+ if (g_ascii_strcasecmp(element_name, "ref") == 0) {
+ const gchar *href = get_attribute(attribute_names,
+ attribute_values,
+ "href");
+ if (href != NULL) {
+ /* create new song object, and copy
+ the existing tag over; we cannot
+ replace the existing song's URI,
+ because that attribute is
+ immutable */
+ struct song *song = song_remote_new(href);
+
+ if (parser->song != NULL) {
+ song->tag = parser->song->tag;
+ parser->song->tag = NULL;
+ song_free(parser->song);
+ }
+
+ parser->song = song;
+ }
+ } else if (g_ascii_strcasecmp(element_name, "author") == 0)
+ /* is that correct? or should it be COMPOSER
+ or PERFORMER? */
+ parser->tag = TAG_ARTIST;
+ else if (g_ascii_strcasecmp(element_name, "title") == 0)
+ parser->tag = TAG_TITLE;
+
+ break;
+ }
+}
+
+static void
+asx_end_element(G_GNUC_UNUSED GMarkupParseContext *context,
+ const gchar *element_name,
+ gpointer user_data, G_GNUC_UNUSED GError **error)
+{
+ struct asx_parser *parser = user_data;
+
+ switch (parser->state) {
+ case ROOT:
+ break;
+
+ case ENTRY:
+ if (g_ascii_strcasecmp(element_name, "entry") == 0) {
+ if (strcmp(parser->song->uri, "asx:") != 0)
+ parser->songs = g_slist_prepend(parser->songs,
+ parser->song);
+ else
+ song_free(parser->song);
+
+ parser->state = ROOT;
+ } else
+ parser->tag = TAG_NUM_OF_ITEM_TYPES;
+
+ break;
+ }
+}
+
+static void
+asx_text(G_GNUC_UNUSED GMarkupParseContext *context,
+ const gchar *text, gsize text_len,
+ gpointer user_data, G_GNUC_UNUSED GError **error)
+{
+ struct asx_parser *parser = user_data;
+
+ switch (parser->state) {
+ case ROOT:
+ break;
+
+ case ENTRY:
+ if (parser->tag != TAG_NUM_OF_ITEM_TYPES) {
+ if (parser->song->tag == NULL)
+ parser->song->tag = tag_new();
+ tag_add_item_n(parser->song->tag, parser->tag,
+ text, text_len);
+ }
+
+ break;
+ }
+}
+
+static const GMarkupParser asx_parser = {
+ .start_element = asx_start_element,
+ .end_element = asx_end_element,
+ .text = asx_text,
+};
+
+static void
+song_free_callback(gpointer data, G_GNUC_UNUSED gpointer user_data)
+{
+ struct song *song = data;
+
+ song_free(song);
+}
+
+static void
+asx_parser_destroy(gpointer data)
+{
+ struct asx_parser *parser = data;
+
+ if (parser->state >= ENTRY)
+ song_free(parser->song);
+
+ g_slist_foreach(parser->songs, song_free_callback, NULL);
+ g_slist_free(parser->songs);
+}
+
+/*
+ * The playlist object
+ *
+ */
+
+struct asx_playlist {
+ struct playlist_provider base;
+
+ GSList *songs;
+};
+
+static struct playlist_provider *
+asx_open_stream(struct input_stream *is)
+{
+ struct asx_parser parser = {
+ .songs = NULL,
+ .state = ROOT,
+ };
+ struct asx_playlist *playlist;
+ GMarkupParseContext *context;
+ char buffer[1024];
+ size_t nbytes;
+ bool success;
+ GError *error = NULL;
+
+ /* parse the ASX XML file */
+
+ context = g_markup_parse_context_new(&asx_parser,
+ G_MARKUP_TREAT_CDATA_AS_TEXT,
+ &parser, asx_parser_destroy);
+
+ while (true) {
+ nbytes = input_stream_read(is, buffer, sizeof(buffer), &error);
+ if (nbytes == 0) {
+ if (error != NULL) {
+ g_markup_parse_context_free(context);
+ g_warning("%s", error->message);
+ g_error_free(error);
+ return NULL;
+ }
+
+ break;
+ }
+
+ success = g_markup_parse_context_parse(context, buffer, nbytes,
+ &error);
+ if (!success) {
+ g_warning("XML parser failed: %s", error->message);
+ g_error_free(error);
+ g_markup_parse_context_free(context);
+ return NULL;
+ }
+ }
+
+ success = g_markup_parse_context_end_parse(context, &error);
+ if (!success) {
+ g_warning("XML parser failed: %s", error->message);
+ g_error_free(error);
+ g_markup_parse_context_free(context);
+ return NULL;
+ }
+
+ /* create a #asx_playlist object from the parsed song list */
+
+ playlist = g_new(struct asx_playlist, 1);
+ playlist_provider_init(&playlist->base, &asx_playlist_plugin);
+ playlist->songs = g_slist_reverse(parser.songs);
+ parser.songs = NULL;
+
+ g_markup_parse_context_free(context);
+
+ return &playlist->base;
+}
+
+static void
+asx_close(struct playlist_provider *_playlist)
+{
+ struct asx_playlist *playlist = (struct asx_playlist *)_playlist;
+
+ g_slist_foreach(playlist->songs, song_free_callback, NULL);
+ g_slist_free(playlist->songs);
+ g_free(playlist);
+}
+
+static struct song *
+asx_read(struct playlist_provider *_playlist)
+{
+ struct asx_playlist *playlist = (struct asx_playlist *)_playlist;
+ struct song *song;
+
+ if (playlist->songs == NULL)
+ return NULL;
+
+ song = playlist->songs->data;
+ playlist->songs = g_slist_remove(playlist->songs, song);
+
+ return song;
+}
+
+static const char *const asx_suffixes[] = {
+ "asx",
+ NULL
+};
+
+static const char *const asx_mime_types[] = {
+ "video/x-ms-asf",
+ NULL
+};
+
+const struct playlist_plugin asx_playlist_plugin = {
+ .name = "asx",
+
+ .open_stream = asx_open_stream,
+ .close = asx_close,
+ .read = asx_read,
+
+ .suffixes = asx_suffixes,
+ .mime_types = asx_mime_types,
+};
diff --git a/src/playlist/asx_playlist_plugin.h b/src/playlist/asx_playlist_plugin.h
new file mode 100644
index 000000000..7ce91aa41
--- /dev/null
+++ b/src/playlist/asx_playlist_plugin.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_PLAYLIST_ASX_PLAYLIST_PLUGIN_H
+#define MPD_PLAYLIST_ASX_PLAYLIST_PLUGIN_H
+
+extern const struct playlist_plugin asx_playlist_plugin;
+
+#endif
diff --git a/src/playlist/cue_playlist_plugin.c b/src/playlist/cue_playlist_plugin.c
new file mode 100644
index 000000000..b22712bc7
--- /dev/null
+++ b/src/playlist/cue_playlist_plugin.c
@@ -0,0 +1,140 @@
+/*
+ * 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/cue_playlist_plugin.h"
+#include "playlist_plugin.h"
+#include "tag.h"
+#include "song.h"
+#include "cue/cue_tag.h"
+
+#include <glib.h>
+#include <libcue/libcue.h>
+#include <assert.h>
+#include <string.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "cue"
+
+struct cue_playlist {
+ struct playlist_provider base;
+
+ struct Cd *cd;
+
+ unsigned next;
+};
+
+static struct playlist_provider *
+cue_playlist_open_uri(const char *uri)
+{
+ struct cue_playlist *playlist;
+ FILE *file;
+ struct Cd *cd;
+
+ file = fopen(uri, "rt");
+ if (file == NULL)
+ return NULL;
+
+ cd = cue_parse_file(file);
+ fclose(file);
+ if (cd == NULL)
+ return NULL;
+
+ playlist = g_new(struct cue_playlist, 1);
+ playlist_provider_init(&playlist->base, &cue_playlist_plugin);
+ playlist->cd = cd;
+ playlist->next = 1;
+
+ return &playlist->base;
+}
+
+static void
+cue_playlist_close(struct playlist_provider *_playlist)
+{
+ struct cue_playlist *playlist = (struct cue_playlist *)_playlist;
+
+ cd_delete(playlist->cd);
+ g_free(playlist);
+}
+
+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;
+ }
+
+ 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;
+}
+
+static const char *const cue_playlist_suffixes[] = {
+ "cue",
+ NULL
+};
+
+static const char *const cue_playlist_mime_types[] = {
+ "application/x-cue",
+ NULL
+};
+
+const struct playlist_plugin cue_playlist_plugin = {
+ .name = "cue",
+
+ .open_uri = cue_playlist_open_uri,
+ .close = cue_playlist_close,
+ .read = cue_playlist_read,
+
+ .suffixes = cue_playlist_suffixes,
+ .mime_types = cue_playlist_mime_types,
+};
diff --git a/src/playlist/cue_playlist_plugin.h b/src/playlist/cue_playlist_plugin.h
new file mode 100644
index 000000000..c89ec55c5
--- /dev/null
+++ b/src/playlist/cue_playlist_plugin.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_PLAYLIST_CUE_PLAYLIST_PLUGIN_H
+#define MPD_PLAYLIST_CUE_PLAYLIST_PLUGIN_H
+
+extern const struct playlist_plugin cue_playlist_plugin;
+
+#endif
diff --git a/src/playlist/extm3u_playlist_plugin.c b/src/playlist/extm3u_playlist_plugin.c
new file mode 100644
index 000000000..9a04aa066
--- /dev/null
+++ b/src/playlist/extm3u_playlist_plugin.c
@@ -0,0 +1,161 @@
+/*
+ * 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/extm3u_playlist_plugin.h"
+#include "playlist_plugin.h"
+#include "text_input_stream.h"
+#include "uri.h"
+#include "song.h"
+#include "tag.h"
+
+#include <glib.h>
+
+#include <string.h>
+#include <stdlib.h>
+
+struct extm3u_playlist {
+ struct playlist_provider base;
+
+ struct text_input_stream *tis;
+};
+
+static struct playlist_provider *
+extm3u_open_stream(struct input_stream *is)
+{
+ struct extm3u_playlist *playlist;
+ const char *line;
+
+ playlist = g_new(struct extm3u_playlist, 1);
+ playlist->tis = text_input_stream_new(is);
+
+ line = text_input_stream_read(playlist->tis);
+ if (line == NULL || strcmp(line, "#EXTM3U") != 0) {
+ /* no EXTM3U header: fall back to the plain m3u
+ plugin */
+ text_input_stream_free(playlist->tis);
+ g_free(playlist);
+ return NULL;
+ }
+
+ playlist_provider_init(&playlist->base, &extm3u_playlist_plugin);
+ return &playlist->base;
+}
+
+static void
+extm3u_close(struct playlist_provider *_playlist)
+{
+ struct extm3u_playlist *playlist = (struct extm3u_playlist *)_playlist;
+
+ text_input_stream_free(playlist->tis);
+ g_free(playlist);
+}
+
+/**
+ * Parse a EXTINF line.
+ *
+ * @param line the rest of the input line after the colon
+ */
+static struct tag *
+extm3u_parse_tag(const char *line)
+{
+ long duration;
+ char *endptr;
+ const char *name;
+ struct tag *tag;
+
+ duration = strtol(line, &endptr, 10);
+ if (endptr[0] != ',')
+ /* malformed line */
+ return NULL;
+
+ if (duration < 0)
+ /* 0 means unknown duration */
+ duration = 0;
+
+ name = g_strchug(endptr + 1);
+ if (*name == 0 && duration == 0)
+ /* no information available; don't allocate a tag
+ object */
+ return NULL;
+
+ tag = tag_new();
+ tag->time = duration;
+
+ /* unfortunately, there is no real specification for the
+ EXTM3U format, so we must assume that the string after the
+ comma is opaque, and is just the song name*/
+ if (*name != 0)
+ tag_add_item(tag, TAG_NAME, name);
+
+ return tag;
+}
+
+static struct song *
+extm3u_read(struct playlist_provider *_playlist)
+{
+ struct extm3u_playlist *playlist = (struct extm3u_playlist *)_playlist;
+ struct tag *tag = NULL;
+ const char *line;
+ struct song *song;
+
+ do {
+ line = text_input_stream_read(playlist->tis);
+ if (line == NULL) {
+ if (tag != NULL)
+ tag_free(tag);
+ return NULL;
+ }
+
+ if (g_str_has_prefix(line, "#EXTINF:")) {
+ if (tag != NULL)
+ tag_free(tag);
+ tag = extm3u_parse_tag(line + 8);
+ continue;
+ }
+
+ while (*line != 0 && g_ascii_isspace(*line))
+ ++line;
+ } while (line[0] == '#' || *line == 0);
+
+ song = song_remote_new(line);
+ song->tag = tag;
+ return song;
+}
+
+static const char *const extm3u_suffixes[] = {
+ "m3u",
+ NULL
+};
+
+static const char *const extm3u_mime_types[] = {
+ "audio/x-mpegurl",
+ NULL
+};
+
+const struct playlist_plugin extm3u_playlist_plugin = {
+ .name = "extm3u",
+
+ .open_stream = extm3u_open_stream,
+ .close = extm3u_close,
+ .read = extm3u_read,
+
+ .suffixes = extm3u_suffixes,
+ .mime_types = extm3u_mime_types,
+};
diff --git a/src/playlist/extm3u_playlist_plugin.h b/src/playlist/extm3u_playlist_plugin.h
new file mode 100644
index 000000000..fa726c5f6
--- /dev/null
+++ b/src/playlist/extm3u_playlist_plugin.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_PLAYLIST_EXTM3U_PLAYLIST_PLUGIN_H
+#define MPD_PLAYLIST_EXTM3U_PLAYLIST_PLUGIN_H
+
+extern const struct playlist_plugin extm3u_playlist_plugin;
+
+#endif
diff --git a/src/playlist/flac_playlist_plugin.c b/src/playlist/flac_playlist_plugin.c
new file mode 100644
index 000000000..9d66fb331
--- /dev/null
+++ b/src/playlist/flac_playlist_plugin.c
@@ -0,0 +1,170 @@
+/*
+ * 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/flac_playlist_plugin.h b/src/playlist/flac_playlist_plugin.h
new file mode 100644
index 000000000..7b141264f
--- /dev/null
+++ b/src/playlist/flac_playlist_plugin.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_PLAYLIST_FLAC_PLAYLIST_PLUGIN_H
+#define MPD_PLAYLIST_FLAC_PLAYLIST_PLUGIN_H
+
+extern const struct playlist_plugin flac_playlist_plugin;
+
+#endif
diff --git a/src/playlist/lastfm_playlist_plugin.c b/src/playlist/lastfm_playlist_plugin.c
new file mode 100644
index 000000000..afb3979d9
--- /dev/null
+++ b/src/playlist/lastfm_playlist_plugin.c
@@ -0,0 +1,312 @@
+/*
+ * 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/lastfm_playlist_plugin.h"
+#include "playlist_plugin.h"
+#include "playlist_list.h"
+#include "conf.h"
+#include "uri.h"
+#include "song.h"
+#include "input_stream.h"
+#include "glib_compat.h"
+
+#include <glib.h>
+
+#include <assert.h>
+#include <string.h>
+
+struct lastfm_playlist {
+ struct playlist_provider base;
+
+ struct input_stream *is;
+
+ struct playlist_provider *xspf;
+};
+
+static struct {
+ char *user;
+ char *md5;
+} lastfm_config;
+
+static bool
+lastfm_init(const struct config_param *param)
+{
+ const char *user = config_get_block_string(param, "user", NULL);
+ const char *passwd = config_get_block_string(param, "password", NULL);
+
+ if (user == NULL || passwd == NULL) {
+ g_debug("disabling the last.fm playlist plugin "
+ "because account is not configured");
+ return false;
+ }
+
+ lastfm_config.user = g_uri_escape_string(user, NULL, false);
+
+#if GLIB_CHECK_VERSION(2,16,0)
+ if (strlen(passwd) != 32)
+ lastfm_config.md5 = g_compute_checksum_for_string(G_CHECKSUM_MD5,
+ passwd, strlen(passwd));
+ else
+#endif
+ lastfm_config.md5 = g_strdup(passwd);
+
+ return true;
+}
+
+static void
+lastfm_finish(void)
+{
+ g_free(lastfm_config.user);
+ g_free(lastfm_config.md5);
+}
+
+/**
+ * Simple data fetcher.
+ * @param url path or url of data to fetch.
+ * @return data fetched, or NULL on error. Must be freed with g_free.
+ */
+static char *
+lastfm_get(const char *url)
+{
+ 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);
+ if (input_stream == NULL) {
+ if (error != NULL) {
+ g_warning("%s", error->message);
+ g_error_free(error);
+ }
+
+ 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;
+ }
+ }
+
+ do {
+ nbytes = input_stream_read(input_stream, buffer + length,
+ sizeof(buffer) - length, &error);
+ if (nbytes == 0) {
+ if (error != NULL) {
+ g_warning("%s", error->message);
+ g_error_free(error);
+ }
+
+ if (input_stream_eof(input_stream))
+ break;
+
+ /* I/O error */
+ input_stream_close(input_stream);
+ return NULL;
+ }
+
+ length += nbytes;
+ } while (length < sizeof(buffer));
+
+ input_stream_close(input_stream);
+ return g_strndup(buffer, length);
+}
+
+/**
+ * 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.
+ */
+static char *
+lastfm_find(const char *response, const char *name)
+{
+ size_t name_length = strlen(name);
+
+ while (true) {
+ const char *eol = strchr(response, '\n');
+ if (eol == NULL)
+ return NULL;
+
+ if (strncmp(response, name, name_length) == 0 &&
+ response[name_length] == '=') {
+ response += name_length + 1;
+ return g_strndup(response, eol - response);
+ }
+
+ response = eol + 1;
+ }
+}
+
+static struct playlist_provider *
+lastfm_open_uri(const char *uri)
+{
+ struct lastfm_playlist *playlist;
+ GError *error = NULL;
+ char *p, *q, *response, *session;
+
+ /* handshake */
+
+ p = g_strconcat("http://ws.audioscrobbler.com/radio/handshake.php?"
+ "version=1.1.1&platform=linux&"
+ "username=", lastfm_config.user, "&"
+ "passwordmd5=", lastfm_config.md5, "&"
+ "debug=0&partner=", NULL);
+ response = lastfm_get(p);
+ g_free(p);
+ if (response == NULL)
+ return NULL;
+
+ /* extract session id from response */
+
+ session = lastfm_find(response, "session");
+ g_free(response);
+ if (session == NULL) {
+ g_warning("last.fm handshake failed");
+ return NULL;
+ }
+
+ q = g_uri_escape_string(session, NULL, false);
+ g_free(session);
+ session = q;
+
+ g_debug("session='%s'", session);
+
+ /* "adjust" last.fm radio */
+
+ if (strlen(uri) > 9) {
+ char *escaped_uri;
+
+ escaped_uri = g_uri_escape_string(uri, NULL, false);
+
+ p = g_strconcat("http://ws.audioscrobbler.com/radio/adjust.php?"
+ "session=", session, "&url=", escaped_uri, "&debug=0",
+ NULL);
+ g_free(escaped_uri);
+
+ response = lastfm_get(p);
+ g_free(response);
+ g_free(p);
+
+ if (response == NULL) {
+ g_free(session);
+ return NULL;
+ }
+ }
+
+ /* create the playlist object */
+
+ playlist = g_new(struct lastfm_playlist, 1);
+ playlist_provider_init(&playlist->base, &lastfm_playlist_plugin);
+
+ /* open the last.fm playlist */
+
+ p = g_strconcat("http://ws.audioscrobbler.com/radio/xspf.php?"
+ "sk=", session, "&discovery=0&desktop=1.5.1.31879",
+ NULL);
+ g_free(session);
+
+ playlist->is = input_stream_open(p, &error);
+ g_free(p);
+
+ if (playlist->is == NULL) {
+ if (error != NULL) {
+ g_warning("Failed to load XSPF playlist: %s",
+ error->message);
+ g_error_free(error);
+ } else
+ g_warning("Failed to load XSPF playlist");
+ g_free(playlist);
+ 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;
+ }
+
+ if (ret == 0)
+ /* nothing was buffered - wait */
+ g_usleep(10000);
+ }
+
+ /* 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");
+
+ /* parse the XSPF playlist */
+
+ playlist->xspf = playlist_list_open_stream(playlist->is, NULL);
+ if (playlist->xspf == NULL) {
+ input_stream_close(playlist->is);
+ g_free(playlist);
+ g_warning("Failed to parse XSPF playlist");
+ return NULL;
+ }
+
+ return &playlist->base;
+}
+
+static void
+lastfm_close(struct playlist_provider *_playlist)
+{
+ struct lastfm_playlist *playlist = (struct lastfm_playlist *)_playlist;
+
+ playlist_plugin_close(playlist->xspf);
+ input_stream_close(playlist->is);
+ g_free(playlist);
+}
+
+static struct song *
+lastfm_read(struct playlist_provider *_playlist)
+{
+ struct lastfm_playlist *playlist = (struct lastfm_playlist *)_playlist;
+
+ return playlist_plugin_read(playlist->xspf);
+}
+
+static const char *const lastfm_schemes[] = {
+ "lastfm",
+ NULL
+};
+
+const struct playlist_plugin lastfm_playlist_plugin = {
+ .name = "lastfm",
+
+ .init = lastfm_init,
+ .finish = lastfm_finish,
+ .open_uri = lastfm_open_uri,
+ .close = lastfm_close,
+ .read = lastfm_read,
+
+ .schemes = lastfm_schemes,
+};
diff --git a/src/playlist/lastfm_playlist_plugin.h b/src/playlist/lastfm_playlist_plugin.h
new file mode 100644
index 000000000..363377c21
--- /dev/null
+++ b/src/playlist/lastfm_playlist_plugin.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_PLAYLIST_LASTFM_PLAYLIST_PLUGIN_H
+#define MPD_PLAYLIST_LASTFM_PLAYLIST_PLUGIN_H
+
+extern const struct playlist_plugin lastfm_playlist_plugin;
+
+#endif
diff --git a/src/playlist/m3u_playlist_plugin.c b/src/playlist/m3u_playlist_plugin.c
new file mode 100644
index 000000000..221c27277
--- /dev/null
+++ b/src/playlist/m3u_playlist_plugin.c
@@ -0,0 +1,92 @@
+/*
+ * 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/m3u_playlist_plugin.h"
+#include "playlist_plugin.h"
+#include "text_input_stream.h"
+#include "uri.h"
+#include "song.h"
+
+#include <glib.h>
+
+struct m3u_playlist {
+ struct playlist_provider base;
+
+ struct text_input_stream *tis;
+};
+
+static struct playlist_provider *
+m3u_open_stream(struct input_stream *is)
+{
+ struct m3u_playlist *playlist = g_new(struct m3u_playlist, 1);
+
+ playlist_provider_init(&playlist->base, &m3u_playlist_plugin);
+ playlist->tis = text_input_stream_new(is);
+
+ return &playlist->base;
+}
+
+static void
+m3u_close(struct playlist_provider *_playlist)
+{
+ struct m3u_playlist *playlist = (struct m3u_playlist *)_playlist;
+
+ text_input_stream_free(playlist->tis);
+ g_free(playlist);
+}
+
+static struct song *
+m3u_read(struct playlist_provider *_playlist)
+{
+ struct m3u_playlist *playlist = (struct m3u_playlist *)_playlist;
+ const char *line;
+
+ do {
+ line = text_input_stream_read(playlist->tis);
+ if (line == NULL)
+ return NULL;
+
+ while (*line != 0 && g_ascii_isspace(*line))
+ ++line;
+ } while (line[0] == '#' || *line == 0);
+
+ return song_remote_new(line);
+}
+
+static const char *const m3u_suffixes[] = {
+ "m3u",
+ NULL
+};
+
+static const char *const m3u_mime_types[] = {
+ "audio/x-mpegurl",
+ NULL
+};
+
+const struct playlist_plugin m3u_playlist_plugin = {
+ .name = "m3u",
+
+ .open_stream = m3u_open_stream,
+ .close = m3u_close,
+ .read = m3u_read,
+
+ .suffixes = m3u_suffixes,
+ .mime_types = m3u_mime_types,
+};
diff --git a/src/playlist/m3u_playlist_plugin.h b/src/playlist/m3u_playlist_plugin.h
new file mode 100644
index 000000000..98dcc4729
--- /dev/null
+++ b/src/playlist/m3u_playlist_plugin.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_PLAYLIST_M3U_PLAYLIST_PLUGIN_H
+#define MPD_PLAYLIST_M3U_PLAYLIST_PLUGIN_H
+
+extern const struct playlist_plugin m3u_playlist_plugin;
+
+#endif
diff --git a/src/playlist/pls_playlist_plugin.c b/src/playlist/pls_playlist_plugin.c
new file mode 100644
index 000000000..2a36f12f5
--- /dev/null
+++ b/src/playlist/pls_playlist_plugin.c
@@ -0,0 +1,219 @@
+/*
+ * 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/pls_playlist_plugin.h"
+#include "playlist_plugin.h"
+#include "input_stream.h"
+#include "uri.h"
+#include "song.h"
+#include "tag.h"
+#include <glib.h>
+
+struct pls_playlist {
+ struct playlist_provider base;
+
+ GSList *songs;
+};
+
+static void pls_parser(GKeyFile *keyfile, struct pls_playlist *playlist)
+{
+ gchar *key;
+ gchar *value;
+ int length;
+ GError *error = NULL;
+ int num_entries = g_key_file_get_integer(keyfile, "playlist",
+ "NumberOfEntries", &error);
+ if (error) {
+ g_debug("Invalid PLS file: '%s'", error->message);
+ g_error_free(error);
+ error = NULL;
+
+ /* Hack to work around shoutcast failure to comform to spec */
+ num_entries = g_key_file_get_integer(keyfile, "playlist",
+ "numberofentries", &error);
+ if (error) {
+ g_error_free(error);
+ error = NULL;
+ }
+ }
+
+ while (num_entries > 0) {
+ struct song *song;
+ key = g_strdup_printf("File%i", num_entries);
+ value = g_key_file_get_string(keyfile, "playlist", key,
+ &error);
+ if(error) {
+ g_debug("Invalid PLS entry %s: '%s'",key, error->message);
+ g_error_free(error);
+ g_free(key);
+ return;
+ }
+ g_free(key);
+
+ song = song_remote_new(value);
+ g_free(value);
+
+ key = g_strdup_printf("Title%i", num_entries);
+ value = g_key_file_get_string(keyfile, "playlist", key,
+ &error);
+ g_free(key);
+ if(error == NULL && value){
+ if (song->tag == NULL)
+ song->tag = tag_new();
+ tag_add_item(song->tag,TAG_TITLE, value);
+ }
+ /* Ignore errors? Most likely value not present */
+ if(error) g_error_free(error);
+ error = NULL;
+ g_free(value);
+
+ key = g_strdup_printf("Length%i", num_entries);
+ length = g_key_file_get_integer(keyfile, "playlist", key,
+ &error);
+ g_free(key);
+ if(error == NULL && length > 0){
+ if (song->tag == NULL)
+ song->tag = tag_new();
+ song->tag->time = length;
+ }
+ /* Ignore errors? Most likely value not present */
+ if(error) g_error_free(error);
+ error = NULL;
+
+ playlist->songs = g_slist_prepend(playlist->songs, song);
+ num_entries--;
+ }
+
+}
+
+static struct playlist_provider *
+pls_open_stream(struct input_stream *is)
+{
+ GError *error = NULL;
+ size_t nbytes;
+ char buffer[1024];
+ bool success;
+ GKeyFile *keyfile;
+ struct pls_playlist *playlist;
+ GString *kf_data = g_string_new("");
+
+ do {
+ nbytes = input_stream_read(is, buffer, sizeof(buffer), &error);
+ if (nbytes == 0) {
+ if (error != NULL) {
+ g_string_free(kf_data, TRUE);
+ g_warning("%s", error->message);
+ g_error_free(error);
+ return NULL;
+ }
+
+ break;
+ }
+
+ kf_data = g_string_append_len(kf_data, buffer,nbytes);
+ /* Limit to 64k */
+ } while(kf_data->len < 65536);
+
+ if (kf_data->len == 0) {
+ g_warning("KeyFile parser failed: No Data");
+ g_string_free(kf_data, TRUE);
+ return NULL;
+ }
+
+ keyfile = g_key_file_new();
+ success = g_key_file_load_from_data(keyfile,
+ kf_data->str, kf_data->len,
+ G_KEY_FILE_NONE, &error);
+
+ g_string_free(kf_data, TRUE);
+
+ if (!success) {
+ g_warning("KeyFile parser failed: %s", error->message);
+ g_error_free(error);
+ g_key_file_free(keyfile);
+ return NULL;
+ }
+
+ playlist = g_new(struct pls_playlist, 1);
+ playlist_provider_init(&playlist->base, &pls_playlist_plugin);
+ playlist->songs = NULL;
+
+ pls_parser(keyfile, playlist);
+
+ g_key_file_free(keyfile);
+ return &playlist->base;
+}
+
+
+static void
+song_free_callback(gpointer data, G_GNUC_UNUSED gpointer user_data)
+{
+ struct song *song = data;
+
+ song_free(song);
+}
+
+static void
+pls_close(struct playlist_provider *_playlist)
+{
+ struct pls_playlist *playlist = (struct pls_playlist *)_playlist;
+
+ g_slist_foreach(playlist->songs, song_free_callback, NULL);
+ g_slist_free(playlist->songs);
+
+ g_free(playlist);
+
+}
+
+static struct song *
+pls_read(struct playlist_provider *_playlist)
+{
+ struct pls_playlist *playlist = (struct pls_playlist *)_playlist;
+ struct song *song;
+
+ if (playlist->songs == NULL)
+ return NULL;
+
+ song = playlist->songs->data;
+ playlist->songs = g_slist_remove(playlist->songs, song);
+
+ return song;
+}
+
+static const char *const pls_suffixes[] = {
+ "pls",
+ NULL
+};
+
+static const char *const pls_mime_types[] = {
+ "audio/x-scpls",
+ NULL
+};
+
+const struct playlist_plugin pls_playlist_plugin = {
+ .name = "pls",
+
+ .open_stream = pls_open_stream,
+ .close = pls_close,
+ .read = pls_read,
+
+ .suffixes = pls_suffixes,
+ .mime_types = pls_mime_types,
+};
diff --git a/src/playlist/pls_playlist_plugin.h b/src/playlist/pls_playlist_plugin.h
new file mode 100644
index 000000000..c3bcf3f05
--- /dev/null
+++ b/src/playlist/pls_playlist_plugin.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_PLAYLIST_PLS_PLAYLIST_PLUGIN_H
+#define MPD_PLAYLIST_PLS_PLAYLIST_PLUGIN_H
+
+extern const struct playlist_plugin pls_playlist_plugin;
+
+#endif
diff --git a/src/playlist/rss_playlist_plugin.c b/src/playlist/rss_playlist_plugin.c
new file mode 100644
index 000000000..b5787bb68
--- /dev/null
+++ b/src/playlist/rss_playlist_plugin.c
@@ -0,0 +1,321 @@
+/*
+ * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "playlist/rss_playlist_plugin.h"
+#include "playlist_plugin.h"
+#include "input_stream.h"
+#include "song.h"
+#include "tag.h"
+
+#include <glib.h>
+
+#include <assert.h>
+#include <string.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "rss"
+
+/**
+ * This is the state object for the GLib XML parser.
+ */
+struct rss_parser {
+ /**
+ * The list of songs (in reverse order because that's faster
+ * while adding).
+ */
+ GSList *songs;
+
+ /**
+ * The current position in the XML file.
+ */
+ enum {
+ ROOT, ITEM,
+ } state;
+
+ /**
+ * The current tag within the "entry" element. This is only
+ * valid if state==ITEM. TAG_NUM_OF_ITEM_TYPES means there
+ * is no (known) tag.
+ */
+ enum tag_type tag;
+
+ /**
+ * The current song. It is allocated after the "location"
+ * element.
+ */
+ struct song *song;
+};
+
+static const gchar *
+get_attribute(const gchar **attribute_names, const gchar **attribute_values,
+ const gchar *name)
+{
+ for (unsigned i = 0; attribute_names[i] != NULL; ++i)
+ if (g_ascii_strcasecmp(attribute_names[i], name) == 0)
+ return attribute_values[i];
+
+ return NULL;
+}
+
+static void
+rss_start_element(G_GNUC_UNUSED GMarkupParseContext *context,
+ const gchar *element_name,
+ const gchar **attribute_names,
+ const gchar **attribute_values,
+ gpointer user_data, G_GNUC_UNUSED GError **error)
+{
+ struct rss_parser *parser = user_data;
+
+ switch (parser->state) {
+ case ROOT:
+ if (g_ascii_strcasecmp(element_name, "item") == 0) {
+ parser->state = ITEM;
+ parser->song = song_remote_new("rss:");
+ parser->tag = TAG_NUM_OF_ITEM_TYPES;
+ }
+
+ break;
+
+ case ITEM:
+ if (g_ascii_strcasecmp(element_name, "enclosure") == 0) {
+ const gchar *href = get_attribute(attribute_names,
+ attribute_values,
+ "url");
+ if (href != NULL) {
+ /* create new song object, and copy
+ the existing tag over; we cannot
+ replace the existing song's URI,
+ because that attribute is
+ immutable */
+ struct song *song = song_remote_new(href);
+
+ if (parser->song != NULL) {
+ song->tag = parser->song->tag;
+ parser->song->tag = NULL;
+ song_free(parser->song);
+ }
+
+ parser->song = song;
+ }
+ } else if (g_ascii_strcasecmp(element_name, "title") == 0)
+ parser->tag = TAG_TITLE;
+ else if (g_ascii_strcasecmp(element_name, "itunes:author") == 0)
+ parser->tag = TAG_ARTIST;
+
+ break;
+ }
+}
+
+static void
+rss_end_element(G_GNUC_UNUSED GMarkupParseContext *context,
+ const gchar *element_name,
+ gpointer user_data, G_GNUC_UNUSED GError **error)
+{
+ struct rss_parser *parser = user_data;
+
+ switch (parser->state) {
+ case ROOT:
+ break;
+
+ case ITEM:
+ if (g_ascii_strcasecmp(element_name, "item") == 0) {
+ if (strcmp(parser->song->uri, "rss:") != 0)
+ parser->songs = g_slist_prepend(parser->songs,
+ parser->song);
+ else
+ song_free(parser->song);
+
+ parser->state = ROOT;
+ } else
+ parser->tag = TAG_NUM_OF_ITEM_TYPES;
+
+ break;
+ }
+}
+
+static void
+rss_text(G_GNUC_UNUSED GMarkupParseContext *context,
+ const gchar *text, gsize text_len,
+ gpointer user_data, G_GNUC_UNUSED GError **error)
+{
+ struct rss_parser *parser = user_data;
+
+ switch (parser->state) {
+ case ROOT:
+ break;
+
+ case ITEM:
+ if (parser->tag != TAG_NUM_OF_ITEM_TYPES) {
+ if (parser->song->tag == NULL)
+ parser->song->tag = tag_new();
+ tag_add_item_n(parser->song->tag, parser->tag,
+ text, text_len);
+ }
+
+ break;
+ }
+}
+
+static const GMarkupParser rss_parser = {
+ .start_element = rss_start_element,
+ .end_element = rss_end_element,
+ .text = rss_text,
+};
+
+static void
+song_free_callback(gpointer data, G_GNUC_UNUSED gpointer user_data)
+{
+ struct song *song = data;
+
+ song_free(song);
+}
+
+static void
+rss_parser_destroy(gpointer data)
+{
+ struct rss_parser *parser = data;
+
+ if (parser->state >= ITEM)
+ song_free(parser->song);
+
+ g_slist_foreach(parser->songs, song_free_callback, NULL);
+ g_slist_free(parser->songs);
+}
+
+/*
+ * The playlist object
+ *
+ */
+
+struct rss_playlist {
+ struct playlist_provider base;
+
+ GSList *songs;
+};
+
+static struct playlist_provider *
+rss_open_stream(struct input_stream *is)
+{
+ struct rss_parser parser = {
+ .songs = NULL,
+ .state = ROOT,
+ };
+ struct rss_playlist *playlist;
+ GMarkupParseContext *context;
+ char buffer[1024];
+ size_t nbytes;
+ bool success;
+ GError *error = NULL;
+
+ /* parse the RSS XML file */
+
+ context = g_markup_parse_context_new(&rss_parser,
+ G_MARKUP_TREAT_CDATA_AS_TEXT,
+ &parser, rss_parser_destroy);
+
+ while (true) {
+ nbytes = input_stream_read(is, buffer, sizeof(buffer), &error);
+ if (nbytes == 0) {
+ if (error != NULL) {
+ g_markup_parse_context_free(context);
+ g_warning("%s", error->message);
+ g_error_free(error);
+ return NULL;
+ }
+
+ break;
+ }
+
+ success = g_markup_parse_context_parse(context, buffer, nbytes,
+ &error);
+ if (!success) {
+ g_warning("XML parser failed: %s", error->message);
+ g_error_free(error);
+ g_markup_parse_context_free(context);
+ return NULL;
+ }
+ }
+
+ success = g_markup_parse_context_end_parse(context, &error);
+ if (!success) {
+ g_warning("XML parser failed: %s", error->message);
+ g_error_free(error);
+ g_markup_parse_context_free(context);
+ return NULL;
+ }
+
+ /* create a #rss_playlist object from the parsed song list */
+
+ playlist = g_new(struct rss_playlist, 1);
+ playlist_provider_init(&playlist->base, &rss_playlist_plugin);
+ playlist->songs = g_slist_reverse(parser.songs);
+ parser.songs = NULL;
+
+ g_markup_parse_context_free(context);
+
+ return &playlist->base;
+}
+
+static void
+rss_close(struct playlist_provider *_playlist)
+{
+ struct rss_playlist *playlist = (struct rss_playlist *)_playlist;
+
+ g_slist_foreach(playlist->songs, song_free_callback, NULL);
+ g_slist_free(playlist->songs);
+ g_free(playlist);
+}
+
+static struct song *
+rss_read(struct playlist_provider *_playlist)
+{
+ struct rss_playlist *playlist = (struct rss_playlist *)_playlist;
+ struct song *song;
+
+ if (playlist->songs == NULL)
+ return NULL;
+
+ song = playlist->songs->data;
+ playlist->songs = g_slist_remove(playlist->songs, song);
+
+ return song;
+}
+
+static const char *const rss_suffixes[] = {
+ "rss",
+ NULL
+};
+
+static const char *const rss_mime_types[] = {
+ "application/rss+xml",
+ "text/xml",
+ NULL
+};
+
+const struct playlist_plugin rss_playlist_plugin = {
+ .name = "rss",
+
+ .open_stream = rss_open_stream,
+ .close = rss_close,
+ .read = rss_read,
+
+ .suffixes = rss_suffixes,
+ .mime_types = rss_mime_types,
+};
diff --git a/src/playlist/rss_playlist_plugin.h b/src/playlist/rss_playlist_plugin.h
new file mode 100644
index 000000000..d8992f2e5
--- /dev/null
+++ b/src/playlist/rss_playlist_plugin.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_PLAYLIST_RSS_PLAYLIST_PLUGIN_H
+#define MPD_PLAYLIST_RSS_PLAYLIST_PLUGIN_H
+
+extern const struct playlist_plugin rss_playlist_plugin;
+
+#endif
diff --git a/src/playlist/xspf_playlist_plugin.c b/src/playlist/xspf_playlist_plugin.c
new file mode 100644
index 000000000..50f6bd1e7
--- /dev/null
+++ b/src/playlist/xspf_playlist_plugin.c
@@ -0,0 +1,342 @@
+/*
+ * 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/xspf_playlist_plugin.h"
+#include "playlist_plugin.h"
+#include "input_stream.h"
+#include "uri.h"
+#include "song.h"
+#include "tag.h"
+
+#include <glib.h>
+
+#include <assert.h>
+#include <string.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "xspf"
+
+/**
+ * This is the state object for the GLib XML parser.
+ */
+struct xspf_parser {
+ /**
+ * The list of songs (in reverse order because that's faster
+ * while adding).
+ */
+ GSList *songs;
+
+ /**
+ * The current position in the XML file.
+ */
+ enum {
+ ROOT, PLAYLIST, TRACKLIST, TRACK,
+ LOCATION,
+ } state;
+
+ /**
+ * The current tag within the "track" element. This is only
+ * valid if state==TRACK. TAG_NUM_OF_ITEM_TYPES means there
+ * is no (known) tag.
+ */
+ enum tag_type tag;
+
+ /**
+ * The current song. It is allocated after the "location"
+ * element.
+ */
+ struct song *song;
+};
+
+static void
+xspf_start_element(G_GNUC_UNUSED GMarkupParseContext *context,
+ const gchar *element_name,
+ G_GNUC_UNUSED const gchar **attribute_names,
+ G_GNUC_UNUSED const gchar **attribute_values,
+ gpointer user_data, G_GNUC_UNUSED GError **error)
+{
+ struct xspf_parser *parser = user_data;
+
+ switch (parser->state) {
+ case ROOT:
+ if (strcmp(element_name, "playlist") == 0)
+ parser->state = PLAYLIST;
+
+ break;
+
+ case PLAYLIST:
+ if (strcmp(element_name, "trackList") == 0)
+ parser->state = TRACKLIST;
+
+ break;
+
+ case TRACKLIST:
+ if (strcmp(element_name, "track") == 0) {
+ parser->state = TRACK;
+ parser->song = NULL;
+ parser->tag = TAG_NUM_OF_ITEM_TYPES;
+ }
+
+ break;
+
+ case TRACK:
+ if (strcmp(element_name, "location") == 0)
+ parser->state = LOCATION;
+ else if (strcmp(element_name, "title") == 0)
+ parser->tag = TAG_TITLE;
+ else if (strcmp(element_name, "creator") == 0)
+ /* TAG_COMPOSER would be more correct
+ according to the XSPF spec */
+ parser->tag = TAG_ARTIST;
+ else if (strcmp(element_name, "annotation") == 0)
+ parser->tag = TAG_COMMENT;
+ else if (strcmp(element_name, "album") == 0)
+ parser->tag = TAG_ALBUM;
+ else if (strcmp(element_name, "trackNum") == 0)
+ parser->tag = TAG_TRACK;
+
+ break;
+
+ case LOCATION:
+ break;
+ }
+}
+
+static void
+xspf_end_element(G_GNUC_UNUSED GMarkupParseContext *context,
+ const gchar *element_name,
+ gpointer user_data, G_GNUC_UNUSED GError **error)
+{
+ struct xspf_parser *parser = user_data;
+
+ switch (parser->state) {
+ case ROOT:
+ break;
+
+ case PLAYLIST:
+ if (strcmp(element_name, "playlist") == 0)
+ parser->state = ROOT;
+
+ break;
+
+ case TRACKLIST:
+ if (strcmp(element_name, "tracklist") == 0)
+ parser->state = PLAYLIST;
+
+ break;
+
+ case TRACK:
+ if (strcmp(element_name, "track") == 0) {
+ if (parser->song != NULL)
+ parser->songs = g_slist_prepend(parser->songs,
+ parser->song);
+
+ parser->state = TRACKLIST;
+ } else
+ parser->tag = TAG_NUM_OF_ITEM_TYPES;
+
+ break;
+
+ case LOCATION:
+ parser->state = TRACK;
+ break;
+ }
+}
+
+static void
+xspf_text(G_GNUC_UNUSED GMarkupParseContext *context,
+ const gchar *text, gsize text_len,
+ gpointer user_data, G_GNUC_UNUSED GError **error)
+{
+ struct xspf_parser *parser = user_data;
+
+ switch (parser->state) {
+ case ROOT:
+ case PLAYLIST:
+ case TRACKLIST:
+ break;
+
+ case TRACK:
+ if (parser->song != NULL &&
+ parser->tag != TAG_NUM_OF_ITEM_TYPES) {
+ if (parser->song->tag == NULL)
+ parser->song->tag = tag_new();
+ tag_add_item_n(parser->song->tag, parser->tag,
+ text, text_len);
+ }
+
+ break;
+
+ case LOCATION:
+ if (parser->song == NULL) {
+ char *uri = g_strndup(text, text_len);
+ parser->song = song_remote_new(uri);
+ g_free(uri);
+ }
+
+ break;
+ }
+}
+
+static const GMarkupParser xspf_parser = {
+ .start_element = xspf_start_element,
+ .end_element = xspf_end_element,
+ .text = xspf_text,
+};
+
+static void
+song_free_callback(gpointer data, G_GNUC_UNUSED gpointer user_data)
+{
+ struct song *song = data;
+
+ song_free(song);
+}
+
+static void
+xspf_parser_destroy(gpointer data)
+{
+ struct xspf_parser *parser = data;
+
+ if (parser->state >= TRACK && parser->song != NULL)
+ song_free(parser->song);
+
+ g_slist_foreach(parser->songs, song_free_callback, NULL);
+ g_slist_free(parser->songs);
+}
+
+/*
+ * The playlist object
+ *
+ */
+
+struct xspf_playlist {
+ struct playlist_provider base;
+
+ GSList *songs;
+};
+
+static struct playlist_provider *
+xspf_open_stream(struct input_stream *is)
+{
+ struct xspf_parser parser = {
+ .songs = NULL,
+ .state = ROOT,
+ };
+ struct xspf_playlist *playlist;
+ GMarkupParseContext *context;
+ char buffer[1024];
+ size_t nbytes;
+ bool success;
+ GError *error = NULL;
+
+ /* parse the XSPF XML file */
+
+ context = g_markup_parse_context_new(&xspf_parser,
+ G_MARKUP_TREAT_CDATA_AS_TEXT,
+ &parser, xspf_parser_destroy);
+
+ while (true) {
+ nbytes = input_stream_read(is, buffer, sizeof(buffer), &error);
+ if (nbytes == 0) {
+ if (error != NULL) {
+ g_markup_parse_context_free(context);
+ g_warning("%s", error->message);
+ g_error_free(error);
+ return NULL;
+ }
+
+ break;
+ }
+
+ success = g_markup_parse_context_parse(context, buffer, nbytes,
+ &error);
+ if (!success) {
+ g_warning("XML parser failed: %s", error->message);
+ g_error_free(error);
+ g_markup_parse_context_free(context);
+ return NULL;
+ }
+ }
+
+ success = g_markup_parse_context_end_parse(context, &error);
+ if (!success) {
+ g_warning("XML parser failed: %s", error->message);
+ g_error_free(error);
+ g_markup_parse_context_free(context);
+ return NULL;
+ }
+
+ /* create a #xspf_playlist object from the parsed song list */
+
+ playlist = g_new(struct xspf_playlist, 1);
+ playlist_provider_init(&playlist->base, &xspf_playlist_plugin);
+ playlist->songs = g_slist_reverse(parser.songs);
+ parser.songs = NULL;
+
+ g_markup_parse_context_free(context);
+
+ return &playlist->base;
+}
+
+static void
+xspf_close(struct playlist_provider *_playlist)
+{
+ struct xspf_playlist *playlist = (struct xspf_playlist *)_playlist;
+
+ g_slist_foreach(playlist->songs, song_free_callback, NULL);
+ g_slist_free(playlist->songs);
+ g_free(playlist);
+}
+
+static struct song *
+xspf_read(struct playlist_provider *_playlist)
+{
+ struct xspf_playlist *playlist = (struct xspf_playlist *)_playlist;
+ struct song *song;
+
+ if (playlist->songs == NULL)
+ return NULL;
+
+ song = playlist->songs->data;
+ playlist->songs = g_slist_remove(playlist->songs, song);
+
+ return song;
+}
+
+static const char *const xspf_suffixes[] = {
+ "xspf",
+ NULL
+};
+
+static const char *const xspf_mime_types[] = {
+ "application/xspf+xml",
+ NULL
+};
+
+const struct playlist_plugin xspf_playlist_plugin = {
+ .name = "xspf",
+
+ .open_stream = xspf_open_stream,
+ .close = xspf_close,
+ .read = xspf_read,
+
+ .suffixes = xspf_suffixes,
+ .mime_types = xspf_mime_types,
+};
diff --git a/src/playlist/xspf_playlist_plugin.h b/src/playlist/xspf_playlist_plugin.h
new file mode 100644
index 000000000..ea832207d
--- /dev/null
+++ b/src/playlist/xspf_playlist_plugin.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_PLAYLIST_XSPF_PLAYLIST_PLUGIN_H
+#define MPD_PLAYLIST_XSPF_PLAYLIST_PLUGIN_H
+
+extern const struct playlist_plugin xspf_playlist_plugin;
+
+#endif
diff --git a/src/playlist_any.c b/src/playlist_any.c
new file mode 100644
index 000000000..39e21b178
--- /dev/null
+++ b/src/playlist_any.c
@@ -0,0 +1,68 @@
+/*
+ * 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_any.h"
+#include "playlist_list.h"
+#include "playlist_mapper.h"
+#include "uri.h"
+#include "input_stream.h"
+
+#include <assert.h>
+
+static struct playlist_provider *
+playlist_open_remote(const char *uri, struct input_stream **is_r)
+{
+ assert(uri_has_scheme(uri));
+
+ struct playlist_provider *playlist = playlist_list_open_uri(uri);
+ if (playlist != NULL) {
+ *is_r = NULL;
+ return playlist;
+ }
+
+ GError *error = NULL;
+ struct input_stream *is = input_stream_open(uri, &error);
+ if (is == NULL) {
+ if (error != NULL) {
+ g_warning("Failed to open %s: %s",
+ uri, error->message);
+ g_error_free(error);
+ }
+
+ return NULL;
+ }
+
+ playlist = playlist_list_open_stream(is, uri);
+ if (playlist == NULL) {
+ input_stream_close(is);
+ return NULL;
+ }
+
+ *is_r = is;
+ return playlist;
+}
+
+struct playlist_provider *
+playlist_open_any(const char *uri, struct input_stream **is_r)
+{
+ return uri_has_scheme(uri)
+ ? playlist_open_remote(uri, is_r)
+ : playlist_mapper_open(uri, is_r);
+}
diff --git a/src/playlist_any.h b/src/playlist_any.h
new file mode 100644
index 000000000..6fed97d15
--- /dev/null
+++ b/src/playlist_any.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_PLAYLIST_ANY_H
+#define MPD_PLAYLIST_ANY_H
+
+#include <stdbool.h>
+
+struct playlist_provider;
+struct input_stream;
+
+/**
+ * Opens a playlist from the specified URI, which can be either an
+ * absolute remote URI (with a scheme) or a relative path to the
+ * music orplaylist directory.
+ *
+ * @param is_r on success, an input_stream object may be returned
+ * here, which must be closed after the playlist_provider object is
+ * freed
+ */
+struct playlist_provider *
+playlist_open_any(const char *uri, struct input_stream **is_r);
+
+#endif
diff --git a/src/playlist_control.c b/src/playlist_control.c
index 4c156f0f5..ce9bc8442 100644
--- a/src/playlist_control.c
+++ b/src/playlist_control.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -22,6 +22,7 @@
*
*/
+#include "config.h"
#include "playlist_internal.h"
#include "player_control.h"
#include "idle.h"
@@ -31,15 +32,7 @@
#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "playlist"
-enum {
- /**
- * When the "prev" command is received, rewind the current
- * track if this number of seconds has already elapsed.
- */
- PLAYLIST_PREV_UNLESS_ELAPSED = 10,
-};
-
-void stopPlaylist(struct playlist *playlist)
+void playlist_stop(struct playlist *playlist)
{
if (!playlist->playing)
return;
@@ -47,7 +40,7 @@ void stopPlaylist(struct playlist *playlist)
assert(playlist->current >= 0);
g_debug("stop");
- playerWait();
+ pc_stop();
playlist->queued = -1;
playlist->playing = false;
@@ -69,11 +62,11 @@ void stopPlaylist(struct playlist *playlist)
}
}
-enum playlist_result playPlaylist(struct playlist *playlist, int song)
+enum playlist_result playlist_play(struct playlist *playlist, int song)
{
unsigned i = song;
- clearPlayerError();
+ pc_clear_error();
if (song == -1) {
/* play any song ("current" song, or the first song */
@@ -84,7 +77,7 @@ enum playlist_result playPlaylist(struct playlist *playlist, int song)
if (playlist->playing) {
/* already playing: unpause playback, just in
case it was paused, and return */
- playerSetPause(0);
+ pc_set_pause(false);
return PLAYLIST_RESULT_SUCCESS;
}
@@ -116,28 +109,28 @@ enum playlist_result playPlaylist(struct playlist *playlist, int song)
playlist->stop_on_error = false;
playlist->error_count = 0;
- playPlaylistOrderNumber(playlist, i);
+ playlist_play_order(playlist, i);
return PLAYLIST_RESULT_SUCCESS;
}
enum playlist_result
-playPlaylistById(struct playlist *playlist, int id)
+playlist_play_id(struct playlist *playlist, int id)
{
int song;
if (id == -1) {
- return playPlaylist(playlist, id);
+ return playlist_play(playlist, id);
}
song = queue_id_to_position(&playlist->queue, id);
if (song < 0)
return PLAYLIST_RESULT_NO_SUCH_SONG;
- return playPlaylist(playlist, song);
+ return playlist_play(playlist, song);
}
void
-nextSongInPlaylist(struct playlist *playlist)
+playlist_next(struct playlist *playlist)
{
int next_order;
int current;
@@ -155,12 +148,8 @@ nextSongInPlaylist(struct playlist *playlist)
next_order = queue_next_order(&playlist->queue, playlist->current);
if (next_order < 0) {
- /* cancel single */
- playlist->queue.single = false;
- idle_add(IDLE_OPTIONS);
-
/* no song after this one: stop playback */
- stopPlaylist(playlist);
+ playlist_stop(playlist);
/* reset "current song" */
playlist->current = -1;
@@ -177,50 +166,42 @@ nextSongInPlaylist(struct playlist *playlist)
queue_shuffle_order(&playlist->queue);
/* note that playlist->current and playlist->queued are
- now invalid, but playPlaylistOrderNumber() will
+ now invalid, but playlist_play_order() will
discard them anyway */
}
- playPlaylistOrderNumber(playlist, next_order);
+ playlist_play_order(playlist, next_order);
}
/* Consume mode removes each played songs. */
if(playlist->queue.consume)
- deleteFromPlaylist(playlist, queue_order_to_position(&playlist->queue, current));
+ playlist_delete(playlist, queue_order_to_position(&playlist->queue, current));
}
-void previousSongInPlaylist(struct playlist *playlist)
+void playlist_previous(struct playlist *playlist)
{
if (!playlist->playing)
return;
- if (g_timer_elapsed(playlist->prev_elapsed, NULL) >= 1.0 &&
- getPlayerElapsedTime() > PLAYLIST_PREV_UNLESS_ELAPSED) {
- /* re-start playing the current song (just like the
- "prev" button on CD players) */
+ assert(queue_length(&playlist->queue) > 0);
- playPlaylistOrderNumber(playlist, playlist->current);
+ if (playlist->current > 0) {
+ /* play the preceding song */
+ playlist_play_order(playlist,
+ playlist->current - 1);
+ } else if (playlist->queue.repeat) {
+ /* play the last song in "repeat" mode */
+ playlist_play_order(playlist,
+ queue_length(&playlist->queue) - 1);
} else {
- if (playlist->current > 0) {
- /* play the preceding song */
- playPlaylistOrderNumber(playlist,
- playlist->current - 1);
- } else if (playlist->queue.repeat) {
- /* play the last song in "repeat" mode */
- playPlaylistOrderNumber(playlist,
- queue_length(&playlist->queue) - 1);
- } else {
- /* re-start playing the current song if it's
- the first one */
- playPlaylistOrderNumber(playlist, playlist->current);
- }
+ /* re-start playing the current song if it's
+ the first one */
+ playlist_play_order(playlist, playlist->current);
}
-
- g_timer_start(playlist->prev_elapsed);
}
enum playlist_result
-seekSongInPlaylist(struct playlist *playlist, unsigned song, float seek_time)
+playlist_seek_song(struct playlist *playlist, unsigned song, float seek_time)
{
const struct song *queued;
unsigned i;
@@ -236,7 +217,7 @@ seekSongInPlaylist(struct playlist *playlist, unsigned song, float seek_time)
else
i = song;
- clearPlayerError();
+ pc_clear_error();
playlist->stop_on_error = true;
playlist->error_count = 0;
@@ -244,7 +225,7 @@ seekSongInPlaylist(struct playlist *playlist, unsigned song, float seek_time)
/* seeking is not within the current song - first
start playing the new song */
- playPlaylistOrderNumber(playlist, i);
+ playlist_play_order(playlist, i);
queued = NULL;
}
@@ -262,11 +243,11 @@ seekSongInPlaylist(struct playlist *playlist, unsigned song, float seek_time)
}
enum playlist_result
-seekSongInPlaylistById(struct playlist *playlist, unsigned id, float seek_time)
+playlist_seek_song_id(struct playlist *playlist, unsigned id, float seek_time)
{
int song = queue_id_to_position(&playlist->queue, id);
if (song < 0)
return PLAYLIST_RESULT_NO_SUCH_SONG;
- return seekSongInPlaylist(playlist, song, seek_time);
+ return playlist_seek_song(playlist, song, seek_time);
}
diff --git a/src/playlist_database.c b/src/playlist_database.c
new file mode 100644
index 000000000..0a8a6f139
--- /dev/null
+++ b/src/playlist_database.c
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "playlist_database.h"
+#include "playlist_vector.h"
+#include "text_file.h"
+
+#include <string.h>
+#include <stdlib.h>
+
+static GQuark
+playlist_database_quark(void)
+{
+ return g_quark_from_static_string("playlist_database");
+}
+
+void
+playlist_vector_save(FILE *fp, const struct playlist_vector *pv)
+{
+ for (const struct playlist_metadata *pm = pv->head;
+ pm != NULL; pm = pm->next)
+ fprintf(fp, PLAYLIST_META_BEGIN "%s\n"
+ "mtime: %li\n"
+ "playlist_end\n",
+ pm->name, (long)pm->mtime);
+}
+
+bool
+playlist_metadata_load(FILE *fp, struct playlist_vector *pv, const char *name,
+ GString *buffer, GError **error_r)
+{
+ struct playlist_metadata pm = {
+ .mtime = 0,
+ };
+ char *line, *colon;
+ const char *value;
+
+ while ((line = read_text_line(fp, buffer)) != NULL &&
+ strcmp(line, "playlist_end") != 0) {
+ colon = strchr(line, ':');
+ if (colon == NULL || colon == line) {
+ g_set_error(error_r, playlist_database_quark(), 0,
+ "unknown line in db: %s", line);
+ return false;
+ }
+
+ *colon++ = 0;
+ value = g_strchug(colon);
+
+ if (strcmp(line, "mtime") == 0)
+ pm.mtime = strtol(value, NULL, 10);
+ else {
+ g_set_error(error_r, playlist_database_quark(), 0,
+ "unknown line in db: %s", line);
+ return false;
+ }
+ }
+
+ playlist_vector_update_or_add(pv, name, pm.mtime);
+ return true;
+}
diff --git a/src/playlist_database.h b/src/playlist_database.h
new file mode 100644
index 000000000..7e114abdd
--- /dev/null
+++ b/src/playlist_database.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_PLAYLIST_DATABASE_H
+#define MPD_PLAYLIST_DATABASE_H
+
+#include "check.h"
+
+#include <stdbool.h>
+#include <stdio.h>
+#include <glib.h>
+
+#define PLAYLIST_META_BEGIN "playlist_begin: "
+
+struct playlist_vector;
+
+void
+playlist_vector_save(FILE *fp, const struct playlist_vector *pv);
+
+bool
+playlist_metadata_load(FILE *fp, struct playlist_vector *pv, const char *name,
+ GString *buffer, GError **error_r);
+
+#endif
diff --git a/src/playlist_edit.c b/src/playlist_edit.c
index b83dc0933..c54b72750 100644
--- a/src/playlist_edit.c
+++ b/src/playlist_edit.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -23,6 +23,7 @@
*
*/
+#include "config.h"
#include "playlist_internal.h"
#include "player_control.h"
#include "database.h"
@@ -35,16 +36,16 @@
#include <unistd.h>
#include <stdlib.h>
-static void incrPlaylistVersion(struct playlist *playlist)
+static void playlist_increment_version(struct playlist *playlist)
{
queue_increment_version(&playlist->queue);
idle_add(IDLE_PLAYLIST);
}
-void clearPlaylist(struct playlist *playlist)
+void playlist_clear(struct playlist *playlist)
{
- stopPlaylist(playlist);
+ playlist_stop(playlist);
/* make sure there are no references to allocated songs
anymore */
@@ -58,7 +59,7 @@ void clearPlaylist(struct playlist *playlist)
playlist->current = -1;
- incrPlaylistVersion(playlist);
+ playlist_increment_version(playlist);
}
#ifndef WIN32
@@ -86,41 +87,12 @@ playlist_append_file(struct playlist *playlist, const char *path, int uid,
if (song == NULL)
return PLAYLIST_RESULT_NO_SUCH_SONG;
- return addSongToPlaylist(playlist, song, added_id);
+ return playlist_append_song(playlist, song, added_id);
}
#endif
-static struct song *
-song_by_url(const char *url)
-{
- struct song *song;
-
- song = db_get_song(url);
- if (song != NULL)
- return song;
-
- if (uri_has_scheme(url))
- return song_remote_new(url);
-
- return NULL;
-}
-
-enum playlist_result
-addToPlaylist(struct playlist *playlist, const char *url, unsigned *added_id)
-{
- struct song *song;
-
- g_debug("add to playlist: %s", url);
-
- song = song_by_url(url);
- if (song == NULL)
- return PLAYLIST_RESULT_NO_SUCH_SONG;
-
- return addSongToPlaylist(playlist, song, added_id);
-}
-
enum playlist_result
-addSongToPlaylist(struct playlist *playlist,
+playlist_append_song(struct playlist *playlist,
struct song *song, unsigned *added_id)
{
const struct song *queued;
@@ -147,7 +119,7 @@ addSongToPlaylist(struct playlist *playlist,
queue_length(&playlist->queue));
}
- incrPlaylistVersion(playlist);
+ playlist_increment_version(playlist);
playlist_update_queued_song(playlist, queued);
@@ -157,8 +129,38 @@ addSongToPlaylist(struct playlist *playlist,
return PLAYLIST_RESULT_SUCCESS;
}
+static struct song *
+song_by_uri(const char *uri)
+{
+ struct song *song;
+
+ song = db_get_song(uri);
+ if (song != NULL)
+ return song;
+
+ if (uri_has_scheme(uri))
+ return song_remote_new(uri);
+
+ return NULL;
+}
+
+enum playlist_result
+playlist_append_uri(struct playlist *playlist, const char *uri,
+ unsigned *added_id)
+{
+ struct song *song;
+
+ g_debug("add to playlist: %s", uri);
+
+ song = song_by_uri(uri);
+ if (song == NULL)
+ return PLAYLIST_RESULT_NO_SUCH_SONG;
+
+ return playlist_append_song(playlist, song, added_id);
+}
+
enum playlist_result
-swapSongsInPlaylist(struct playlist *playlist, unsigned song1, unsigned song2)
+playlist_swap_songs(struct playlist *playlist, unsigned song1, unsigned song2)
{
const struct song *queued;
@@ -188,7 +190,7 @@ swapSongsInPlaylist(struct playlist *playlist, unsigned song1, unsigned song2)
playlist->current = song1;
}
- incrPlaylistVersion(playlist);
+ playlist_increment_version(playlist);
playlist_update_queued_song(playlist, queued);
@@ -196,7 +198,7 @@ swapSongsInPlaylist(struct playlist *playlist, unsigned song1, unsigned song2)
}
enum playlist_result
-swapSongsInPlaylistById(struct playlist *playlist, unsigned id1, unsigned id2)
+playlist_swap_songs_id(struct playlist *playlist, unsigned id1, unsigned id2)
{
int song1 = queue_id_to_position(&playlist->queue, id1);
int song2 = queue_id_to_position(&playlist->queue, id2);
@@ -204,28 +206,25 @@ swapSongsInPlaylistById(struct playlist *playlist, unsigned id1, unsigned id2)
if (song1 < 0 || song2 < 0)
return PLAYLIST_RESULT_NO_SUCH_SONG;
- return swapSongsInPlaylist(playlist, song1, song2);
+ return playlist_swap_songs(playlist, song1, song2);
}
-enum playlist_result
-deleteFromPlaylist(struct playlist *playlist, unsigned song)
+static void
+playlist_delete_internal(struct playlist *playlist, unsigned song,
+ const struct song **queued_p)
{
- const struct song *queued;
unsigned songOrder;
- if (song >= queue_length(&playlist->queue))
- return PLAYLIST_RESULT_BAD_RANGE;
-
- queued = playlist_get_queued_song(playlist);
+ assert(song < queue_length(&playlist->queue));
songOrder = queue_position_to_order(&playlist->queue, song);
if (playlist->playing && playlist->current == (int)songOrder) {
- bool paused = getPlayerState() == PLAYER_STATE_PAUSE;
+ bool paused = pc_get_state() == PLAYER_STATE_PAUSE;
/* the current song is going to be deleted: stop the player */
- playerWait();
+ pc_stop();
playlist->playing = false;
/* see which song is going to be played instead */
@@ -237,13 +236,13 @@ deleteFromPlaylist(struct playlist *playlist, unsigned song)
if (playlist->current >= 0 && !paused)
/* play the song after the deleted one */
- playPlaylistOrderNumber(playlist, playlist->current);
+ playlist_play_order(playlist, playlist->current);
else
/* no songs left to play, stop playback
completely */
- stopPlaylist(playlist);
+ playlist_stop(playlist);
- queued = NULL;
+ *queued_p = NULL;
} else if (playlist->current == (int)songOrder)
/* there's a "current song" but we're not playing
currently - clear "current" */
@@ -256,41 +255,80 @@ deleteFromPlaylist(struct playlist *playlist, unsigned song)
queue_delete(&playlist->queue, song);
- incrPlaylistVersion(playlist);
-
/* update the "current" and "queued" variables */
if (playlist->current > (int)songOrder) {
playlist->current--;
}
+}
+
+enum playlist_result
+playlist_delete(struct playlist *playlist, unsigned song)
+{
+ const struct song *queued;
+
+ if (song >= queue_length(&playlist->queue))
+ return PLAYLIST_RESULT_BAD_RANGE;
+
+ queued = playlist_get_queued_song(playlist);
+
+ playlist_delete_internal(playlist, song, &queued);
+ playlist_increment_version(playlist);
playlist_update_queued_song(playlist, queued);
return PLAYLIST_RESULT_SUCCESS;
}
enum playlist_result
-deleteFromPlaylistById(struct playlist *playlist, unsigned id)
+playlist_delete_range(struct playlist *playlist, unsigned start, unsigned end)
+{
+ const struct song *queued;
+
+ 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;
+
+ queued = playlist_get_queued_song(playlist);
+
+ do {
+ playlist_delete_internal(playlist, --end, &queued);
+ } while (end != start);
+
+ playlist_increment_version(playlist);
+ playlist_update_queued_song(playlist, queued);
+
+ return PLAYLIST_RESULT_SUCCESS;
+}
+
+enum playlist_result
+playlist_delete_id(struct playlist *playlist, unsigned id)
{
int song = queue_id_to_position(&playlist->queue, id);
if (song < 0)
return PLAYLIST_RESULT_NO_SUCH_SONG;
- return deleteFromPlaylist(playlist, song);
+ return playlist_delete(playlist, song);
}
void
-deleteASongFromPlaylist(struct playlist *playlist, const struct song *song)
+playlist_delete_song(struct playlist *playlist, const struct song *song)
{
for (int i = queue_length(&playlist->queue) - 1; i >= 0; --i)
if (song == queue_get(&playlist->queue, i))
- deleteFromPlaylist(playlist, i);
+ playlist_delete(playlist, i);
pc_song_deleted(song);
}
enum playlist_result
-moveSongRangeInPlaylist(struct playlist *playlist, unsigned start, unsigned end, int to)
+playlist_move_range(struct playlist *playlist,
+ unsigned start, unsigned end, int to)
{
const struct song *queued;
int currentSong;
@@ -342,7 +380,7 @@ moveSongRangeInPlaylist(struct playlist *playlist, unsigned start, unsigned end,
}
}
- incrPlaylistVersion(playlist);
+ playlist_increment_version(playlist);
playlist_update_queued_song(playlist, queued);
@@ -350,16 +388,17 @@ moveSongRangeInPlaylist(struct playlist *playlist, unsigned start, unsigned end,
}
enum playlist_result
-moveSongInPlaylistById(struct playlist *playlist, unsigned id1, int to)
+playlist_move_id(struct playlist *playlist, unsigned id1, int to)
{
int song = queue_id_to_position(&playlist->queue, id1);
if (song < 0)
return PLAYLIST_RESULT_NO_SUCH_SONG;
- return moveSongRangeInPlaylist(playlist, song, song+1, to);
+ return playlist_move_range(playlist, song, song+1, to);
}
-void shufflePlaylist(struct playlist *playlist, unsigned start, unsigned end)
+void
+playlist_shuffle(struct playlist *playlist, unsigned start, unsigned end)
{
const struct song *queued;
@@ -399,7 +438,7 @@ void shufflePlaylist(struct playlist *playlist, unsigned start, unsigned end)
queue_shuffle_range(&playlist->queue, start, end);
- incrPlaylistVersion(playlist);
+ playlist_increment_version(playlist);
playlist_update_queued_song(playlist, queued);
}
diff --git a/src/playlist_global.c b/src/playlist_global.c
index fa810bbc3..2833b62ed 100644
--- a/src/playlist_global.c
+++ b/src/playlist_global.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -22,6 +22,7 @@
*
*/
+#include "config.h"
#include "playlist.h"
#include "playlist_state.h"
#include "event_pipe.h"
@@ -37,10 +38,11 @@ playlist_tag_event(void)
static void
playlist_event(void)
{
- syncPlayerAndPlaylist(&g_playlist);
+ playlist_sync(&g_playlist);
}
-void initPlaylist(void)
+void
+playlist_global_init(void)
{
playlist_init(&g_playlist);
@@ -48,17 +50,8 @@ void initPlaylist(void)
event_pipe_register(PIPE_EVENT_PLAYLIST, playlist_event);
}
-void finishPlaylist(void)
+void
+playlist_global_finish(void)
{
playlist_finish(&g_playlist);
}
-
-void savePlaylistState(FILE *fp)
-{
- playlist_state_save(fp, &g_playlist);
-}
-
-void readPlaylistState(FILE *fp)
-{
- playlist_state_restore(fp, &g_playlist);
-}
diff --git a/src/playlist_internal.h b/src/playlist_internal.h
index af880691b..9d205188f 100644
--- a/src/playlist_internal.h
+++ b/src/playlist_internal.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -47,6 +47,6 @@ playlist_update_queued_song(struct playlist *playlist,
const struct song *prev);
void
-playPlaylistOrderNumber(struct playlist *playlist, int orderNum);
+playlist_play_order(struct playlist *playlist, int orderNum);
#endif
diff --git a/src/playlist_list.c b/src/playlist_list.c
new file mode 100644
index 000000000..019654bfc
--- /dev/null
+++ b/src/playlist_list.c
@@ -0,0 +1,361 @@
+/*
+ * 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_list.h"
+#include "playlist_plugin.h"
+#include "playlist/extm3u_playlist_plugin.h"
+#include "playlist/m3u_playlist_plugin.h"
+#include "playlist/xspf_playlist_plugin.h"
+#include "playlist/lastfm_playlist_plugin.h"
+#include "playlist/pls_playlist_plugin.h"
+#include "playlist/asx_playlist_plugin.h"
+#include "playlist/rss_playlist_plugin.h"
+#include "playlist/cue_playlist_plugin.h"
+#include "playlist/flac_playlist_plugin.h"
+#include "input_stream.h"
+#include "uri.h"
+#include "utils.h"
+#include "conf.h"
+#include "glib_compat.h"
+#include "mpd_error.h"
+
+#include <assert.h>
+#include <string.h>
+#include <stdio.h>
+
+static const struct playlist_plugin *const playlist_plugins[] = {
+ &extm3u_playlist_plugin,
+ &m3u_playlist_plugin,
+ &xspf_playlist_plugin,
+ &pls_playlist_plugin,
+ &asx_playlist_plugin,
+ &rss_playlist_plugin,
+#ifdef ENABLE_LASTFM
+ &lastfm_playlist_plugin,
+#endif
+#ifdef HAVE_CUE
+ &cue_playlist_plugin,
+#endif
+#ifdef HAVE_FLAC
+ &flac_playlist_plugin,
+#endif
+ NULL
+};
+
+/** which plugins have been initialized successfully? */
+static bool playlist_plugins_enabled[G_N_ELEMENTS(playlist_plugins)];
+
+/**
+ * Find the "playlist" configuration block for the specified plugin.
+ *
+ * @param plugin_name the name of the playlist plugin
+ * @return the configuration block, or NULL if none was configured
+ */
+static const struct config_param *
+playlist_plugin_config(const char *plugin_name)
+{
+ const struct config_param *param = NULL;
+
+ assert(plugin_name != NULL);
+
+ while ((param = config_get_next_param(CONF_PLAYLIST_PLUGIN, param)) != NULL) {
+ const char *name =
+ config_get_block_string(param, "name", NULL);
+ if (name == NULL)
+ MPD_ERROR("playlist configuration without 'plugin' name in line %d",
+ param->line);
+
+ if (strcmp(name, plugin_name) == 0)
+ return param;
+ }
+
+ return NULL;
+}
+
+void
+playlist_list_global_init(void)
+{
+ for (unsigned i = 0; playlist_plugins[i] != NULL; ++i) {
+ const struct playlist_plugin *plugin = playlist_plugins[i];
+ const struct config_param *param =
+ playlist_plugin_config(plugin->name);
+
+ if (!config_get_block_bool(param, "enabled", true))
+ /* the plugin is disabled in mpd.conf */
+ continue;
+
+ playlist_plugins_enabled[i] =
+ playlist_plugin_init(playlist_plugins[i], param);
+ }
+}
+
+void
+playlist_list_global_finish(void)
+{
+ for (unsigned i = 0; playlist_plugins[i] != NULL; ++i)
+ if (playlist_plugins_enabled[i])
+ playlist_plugin_finish(playlist_plugins[i]);
+}
+
+static struct playlist_provider *
+playlist_list_open_uri_scheme(const char *uri, bool *tried)
+{
+ char *scheme;
+ struct playlist_provider *playlist = NULL;
+
+ assert(uri != NULL);
+
+ scheme = g_uri_parse_scheme(uri);
+ if (scheme == NULL)
+ return NULL;
+
+ for (unsigned i = 0; playlist_plugins[i] != NULL; ++i) {
+ const struct playlist_plugin *plugin = playlist_plugins[i];
+
+ assert(!tried[i]);
+
+ 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);
+ if (playlist != NULL)
+ break;
+
+ tried[i] = true;
+ }
+ }
+
+ g_free(scheme);
+ return playlist;
+}
+
+static struct playlist_provider *
+playlist_list_open_uri_suffix(const char *uri, const bool *tried)
+{
+ const char *suffix;
+ struct playlist_provider *playlist = NULL;
+
+ assert(uri != NULL);
+
+ suffix = uri_get_suffix(uri);
+ if (suffix == NULL)
+ return NULL;
+
+ for (unsigned i = 0; playlist_plugins[i] != NULL; ++i) {
+ const struct playlist_plugin *plugin = playlist_plugins[i];
+
+ 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);
+ if (playlist != NULL)
+ break;
+ }
+ }
+
+ return playlist;
+}
+
+struct playlist_provider *
+playlist_list_open_uri(const char *uri)
+{
+ struct playlist_provider *playlist;
+ /** this array tracks which plugins have already been tried by
+ playlist_list_open_uri_scheme() */
+ bool tried[G_N_ELEMENTS(playlist_plugins) - 1];
+
+ assert(uri != NULL);
+
+ memset(tried, false, sizeof(tried));
+
+ playlist = playlist_list_open_uri_scheme(uri, tried);
+ if (playlist == NULL)
+ playlist = playlist_list_open_uri_suffix(uri, tried);
+
+ return playlist;
+}
+
+static struct playlist_provider *
+playlist_list_open_stream_mime2(struct input_stream *is, const char *mime)
+{
+ struct playlist_provider *playlist;
+
+ assert(is != NULL);
+ assert(mime != NULL);
+
+ for (unsigned i = 0; playlist_plugins[i] != NULL; ++i) {
+ const struct playlist_plugin *plugin = playlist_plugins[i];
+
+ if (playlist_plugins_enabled[i] &&
+ plugin->open_stream != NULL &&
+ plugin->mime_types != NULL &&
+ string_array_contains(plugin->mime_types, mime)) {
+ /* rewind the stream, so each plugin gets a
+ fresh start */
+ input_stream_seek(is, 0, SEEK_SET, NULL);
+
+ playlist = playlist_plugin_open_stream(plugin, is);
+ if (playlist != NULL)
+ return playlist;
+ }
+ }
+
+ return NULL;
+}
+
+static struct playlist_provider *
+playlist_list_open_stream_mime(struct input_stream *is)
+{
+ assert(is->mime != NULL);
+
+ const char *semicolon = strchr(is->mime, ';');
+ if (semicolon == NULL)
+ return playlist_list_open_stream_mime2(is, is->mime);
+
+ if (semicolon == is->mime)
+ return NULL;
+
+ /* probe only the portion before the semicolon*/
+ char *mime = g_strndup(is->mime, semicolon - is->mime);
+ struct playlist_provider *playlist =
+ playlist_list_open_stream_mime2(is, mime);
+ g_free(mime);
+ return playlist;
+}
+
+static struct playlist_provider *
+playlist_list_open_stream_suffix(struct input_stream *is, const char *suffix)
+{
+ struct playlist_provider *playlist;
+
+ assert(is != NULL);
+ assert(suffix != NULL);
+
+ for (unsigned i = 0; playlist_plugins[i] != NULL; ++i) {
+ const struct playlist_plugin *plugin = playlist_plugins[i];
+
+ if (playlist_plugins_enabled[i] &&
+ plugin->open_stream != NULL &&
+ plugin->suffixes != NULL &&
+ string_array_contains(plugin->suffixes, suffix)) {
+ /* rewind the stream, so each plugin gets a
+ fresh start */
+ input_stream_seek(is, 0, SEEK_SET, NULL);
+
+ playlist = playlist_plugin_open_stream(plugin, is);
+ if (playlist != NULL)
+ return playlist;
+ }
+ }
+
+ return NULL;
+}
+
+struct playlist_provider *
+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;
+ }
+ }
+
+ if (is->mime != NULL) {
+ playlist = playlist_list_open_stream_mime(is);
+ if (playlist != NULL)
+ return playlist;
+ }
+
+ suffix = uri != NULL ? uri_get_suffix(uri) : NULL;
+ if (suffix != NULL) {
+ playlist = playlist_list_open_stream_suffix(is, suffix);
+ if (playlist != NULL)
+ return playlist;
+ }
+
+ return NULL;
+}
+
+bool
+playlist_suffix_supported(const char *suffix)
+{
+ assert(suffix != NULL);
+
+ for (unsigned i = 0; playlist_plugins[i] != NULL; ++i) {
+ const struct playlist_plugin *plugin = playlist_plugins[i];
+
+ if (playlist_plugins_enabled[i] && plugin->suffixes != NULL &&
+ string_array_contains(plugin->suffixes, suffix))
+ return true;
+ }
+
+ return false;
+}
+
+struct playlist_provider *
+playlist_list_open_path(const char *path_fs, struct input_stream **is_r)
+{
+ GError *error = NULL;
+ const char *suffix;
+ struct input_stream *is;
+ struct playlist_provider *playlist;
+
+ assert(path_fs != NULL);
+
+ suffix = uri_get_suffix(path_fs);
+ if (suffix == NULL || !playlist_suffix_supported(suffix))
+ return NULL;
+
+ is = input_stream_open(path_fs, &error);
+ if (is == NULL) {
+ if (error != NULL) {
+ g_warning("%s", error->message);
+ g_error_free(error);
+ }
+
+ 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;
+ }
+ }
+
+ playlist = playlist_list_open_stream_suffix(is, suffix);
+ if (playlist != NULL)
+ *is_r = is;
+ else
+ input_stream_close(is);
+
+ return playlist;
+}
diff --git a/src/playlist_list.h b/src/playlist_list.h
new file mode 100644
index 000000000..3710589a2
--- /dev/null
+++ b/src/playlist_list.h
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_PLAYLIST_LIST_H
+#define MPD_PLAYLIST_LIST_H
+
+#include <stdbool.h>
+
+struct playlist_provider;
+struct input_stream;
+
+/**
+ * Initializes all playlist plugins.
+ */
+void
+playlist_list_global_init(void);
+
+/**
+ * Deinitializes all playlist plugins.
+ */
+void
+playlist_list_global_finish(void);
+
+/**
+ * Opens a playlist by its URI.
+ */
+struct playlist_provider *
+playlist_list_open_uri(const char *uri);
+
+/**
+ * Opens a playlist from an input stream.
+ *
+ * @param is an #input_stream object which is open and ready
+ * @param uri optional URI which was used to open the stream; may be
+ * used to select the appropriate playlist plugin
+ */
+struct playlist_provider *
+playlist_list_open_stream(struct input_stream *is, const char *uri);
+
+/**
+ * Determines if there is a playlist plugin which can handle the
+ * specified file name suffix.
+ */
+bool
+playlist_suffix_supported(const char *suffix);
+
+/**
+ * Opens a playlist from a local file.
+ *
+ * @param path_fs the path of the playlist file
+ * @param is_r on success, an input_stream object is returned here,
+ * which must be closed after the playlist_provider object is freed
+ * @return a playlist, or NULL on error
+ */
+struct playlist_provider *
+playlist_list_open_path(const char *path_fs, struct input_stream **is_r);
+
+#endif
diff --git a/src/playlist_mapper.c b/src/playlist_mapper.c
new file mode 100644
index 000000000..99b322073
--- /dev/null
+++ b/src/playlist_mapper.c
@@ -0,0 +1,103 @@
+/*
+ * 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_mapper.h"
+#include "playlist_list.h"
+#include "stored_playlist.h"
+#include "mapper.h"
+#include "uri.h"
+
+#include <assert.h>
+
+static struct playlist_provider *
+playlist_open_path(const char *path_fs, struct input_stream **is_r)
+{
+ struct playlist_provider *playlist;
+
+ playlist = playlist_list_open_uri(path_fs);
+ if (playlist != NULL)
+ *is_r = NULL;
+ else
+ playlist = playlist_list_open_path(path_fs, is_r);
+
+ return playlist;
+}
+
+/**
+ * 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)
+{
+ char *path_fs;
+
+ assert(spl_valid_name(uri));
+
+ const char *playlist_directory_fs = map_spl_path();
+ if (playlist_directory_fs == NULL)
+ return NULL;
+
+ path_fs = g_build_filename(playlist_directory_fs, uri, NULL);
+
+ struct playlist_provider *playlist = playlist_open_path(path_fs, is_r);
+ g_free(path_fs);
+
+ return playlist;
+}
+
+/**
+ * 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)
+{
+ char *path_fs;
+
+ assert(uri_safe_local(uri));
+
+ path_fs = map_uri_fs(uri);
+ if (path_fs == NULL)
+ return NULL;
+
+ struct playlist_provider *playlist = playlist_open_path(path_fs, is_r);
+ g_free(path_fs);
+
+ return playlist;
+}
+
+struct playlist_provider *
+playlist_mapper_open(const char *uri, struct input_stream **is_r)
+{
+ struct playlist_provider *playlist;
+
+ if (spl_valid_name(uri)) {
+ playlist = playlist_open_in_playlist_dir(uri, is_r);
+ if (playlist != NULL)
+ return playlist;
+ }
+
+ if (uri_safe_local(uri)) {
+ playlist = playlist_open_in_music_dir(uri, is_r);
+ if (playlist != NULL)
+ return playlist;
+ }
+
+ return NULL;
+}
diff --git a/src/playlist_mapper.h b/src/playlist_mapper.h
new file mode 100644
index 000000000..b98af1b13
--- /dev/null
+++ b/src/playlist_mapper.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_PLAYLIST_MAPPER_H
+#define MPD_PLAYLIST_MAPPER_H
+
+struct input_stream;
+
+/**
+ * Opens a playlist from an URI relative to the playlist or music
+ * directory.
+ *
+ * @param is_r on success, an input_stream object may be returned
+ * here, which must be closed after the playlist_provider object is
+ * freed
+ */
+struct playlist_provider *
+playlist_mapper_open(const char *uri, struct input_stream **is_r);
+
+#endif
diff --git a/src/playlist_plugin.h b/src/playlist_plugin.h
new file mode 100644
index 000000000..3d840573e
--- /dev/null
+++ b/src/playlist_plugin.h
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_PLAYLIST_PLUGIN_H
+#define MPD_PLAYLIST_PLUGIN_H
+
+#include <stdbool.h>
+#include <stddef.h>
+
+struct config_param;
+struct input_stream;
+struct tag;
+
+/**
+ * An object which provides the contents of a playlist.
+ */
+struct playlist_provider {
+ const struct playlist_plugin *plugin;
+};
+
+static inline void
+playlist_provider_init(struct playlist_provider *playlist,
+ const struct playlist_plugin *plugin)
+{
+ playlist->plugin = plugin;
+}
+
+struct playlist_plugin {
+ const char *name;
+
+ /**
+ * Initialize the plugin. Optional method.
+ *
+ * @param param a configuration block for this plugin, or NULL
+ * if none is configured
+ * @return true if the plugin was initialized successfully,
+ * false if the plugin is not available
+ */
+ bool (*init)(const struct config_param *param);
+
+ /**
+ * Deinitialize a plugin which was initialized successfully.
+ * Optional method.
+ */
+ void (*finish)(void);
+
+ /**
+ * 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);
+
+ /**
+ * Opens the playlist in the specified input stream. It has
+ * either matched one of the suffixes or one of the MIME
+ * types.
+ */
+ struct playlist_provider *(*open_stream)(struct input_stream *is);
+
+ void (*close)(struct playlist_provider *playlist);
+
+ struct song *(*read)(struct playlist_provider *playlist);
+
+ const char *const*schemes;
+ const char *const*suffixes;
+ const char *const*mime_types;
+};
+
+/**
+ * Initialize a plugin.
+ *
+ * @param param a configuration block for this plugin, or NULL if none
+ * is configured
+ * @return true if the plugin was initialized successfully, false if
+ * the plugin is not available
+ */
+static inline bool
+playlist_plugin_init(const struct playlist_plugin *plugin,
+ const struct config_param *param)
+{
+ return plugin->init != NULL
+ ? plugin->init(param)
+ : true;
+}
+
+/**
+ * Deinitialize a plugin which was initialized successfully.
+ */
+static inline void
+playlist_plugin_finish(const struct playlist_plugin *plugin)
+{
+ if (plugin->finish != NULL)
+ plugin->finish();
+}
+
+static inline struct playlist_provider *
+playlist_plugin_open_uri(const struct playlist_plugin *plugin, const char *uri)
+{
+ return plugin->open_uri(uri);
+}
+
+static inline struct playlist_provider *
+playlist_plugin_open_stream(const struct playlist_plugin *plugin,
+ struct input_stream *is)
+{
+ return plugin->open_stream(is);
+}
+
+static inline void
+playlist_plugin_close(struct playlist_provider *playlist)
+{
+ playlist->plugin->close(playlist);
+}
+
+static inline struct song *
+playlist_plugin_read(struct playlist_provider *playlist)
+{
+ return playlist->plugin->read(playlist);
+}
+
+#endif
diff --git a/src/playlist_print.c b/src/playlist_print.c
index fd61ab62c..89ab2e5ab 100644
--- a/src/playlist_print.c
+++ b/src/playlist_print.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -17,13 +17,19 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "playlist_print.h"
+#include "playlist_list.h"
+#include "playlist_plugin.h"
+#include "playlist_any.h"
+#include "playlist_song.h"
#include "queue_print.h"
#include "stored_playlist.h"
#include "song_print.h"
#include "song.h"
#include "database.h"
#include "client.h"
+#include "input_stream.h"
void
playlist_print_uris(struct client *client, const struct playlist *playlist)
@@ -69,7 +75,7 @@ playlist_print_id(struct client *client, const struct playlist *playlist,
bool
playlist_print_current(struct client *client, const struct playlist *playlist)
{
- int current_position = getPlaylistCurrentSong(playlist);
+ int current_position = playlist_get_current_song(playlist);
if (current_position < 0)
return false;
@@ -138,3 +144,41 @@ spl_print(struct client *client, const char *name_utf8, bool detail)
spl_free(list);
return true;
}
+
+static void
+playlist_provider_print(struct client *client, const char *uri,
+ struct playlist_provider *playlist, bool detail)
+{
+ struct song *song;
+ 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);
+ if (song == NULL)
+ continue;
+
+ if (detail)
+ song_print_info(client, song);
+ else
+ song_print_uri(client, song);
+ }
+
+ g_free(base_uri);
+}
+
+bool
+playlist_file_print(struct client *client, const char *uri, bool detail)
+{
+ struct input_stream *is;
+ struct playlist_provider *playlist = playlist_open_any(uri, &is);
+ if (playlist == NULL)
+ return false;
+
+ playlist_provider_print(client, uri, playlist, detail);
+ playlist_plugin_close(playlist);
+
+ if (is != NULL)
+ input_stream_close(is);
+
+ return true;
+}
diff --git a/src/playlist_print.h b/src/playlist_print.h
index 0cfe80776..b3a0446ed 100644
--- a/src/playlist_print.h
+++ b/src/playlist_print.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -101,4 +101,15 @@ playlist_print_changes_position(struct client *client,
bool
spl_print(struct client *client, const char *name_utf8, bool detail);
+/**
+ * Send the playlist file to the client.
+ *
+ * @param client the client which requested the playlist
+ * @param uri the URI of the playlist file in UTF-8 encoding
+ * @param detail true if all details should be printed
+ * @return true on success, false if the playlist does not exist
+ */
+bool
+playlist_file_print(struct client *client, const char *uri, bool detail);
+
#endif
diff --git a/src/playlist_queue.c b/src/playlist_queue.c
new file mode 100644
index 000000000..635e23a28
--- /dev/null
+++ b/src/playlist_queue.c
@@ -0,0 +1,71 @@
+/*
+ * 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_queue.h"
+#include "playlist_plugin.h"
+#include "playlist_any.h"
+#include "playlist_song.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)
+{
+ 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);
+ if (song == NULL)
+ continue;
+
+ result = playlist_append_song(dest, song, NULL);
+ if (result != PLAYLIST_RESULT_SUCCESS) {
+ if (!song_in_database(song))
+ song_free(song);
+ g_free(base_uri);
+ return result;
+ }
+ }
+
+ g_free(base_uri);
+
+ return PLAYLIST_RESULT_SUCCESS;
+}
+
+enum playlist_result
+playlist_open_into_queue(const char *uri, struct playlist *dest)
+{
+ struct input_stream *is;
+ struct playlist_provider *playlist = playlist_open_any(uri, &is);
+ if (playlist == NULL)
+ return PLAYLIST_RESULT_NO_SUCH_LIST;
+
+ enum playlist_result result =
+ playlist_load_into_queue(uri, playlist, dest);
+ playlist_plugin_close(playlist);
+
+ if (is != NULL)
+ input_stream_close(is);
+
+ return result;
+}
diff --git a/src/playlist_queue.h b/src/playlist_queue.h
new file mode 100644
index 000000000..530d4b4be
--- /dev/null
+++ b/src/playlist_queue.h
@@ -0,0 +1,51 @@
+/*
+ * 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.
+ */
+
+/*! \file
+ * \brief Glue between playlist plugin and the play queue
+ */
+
+#ifndef MPD_PLAYLIST_QUEUE_H
+#define MPD_PLAYLIST_QUEUE_H
+
+#include "playlist.h"
+
+struct playlist_provider;
+struct playlist;
+
+/**
+ * Loads the contents of a playlist and append it to the specified
+ * play queue.
+ *
+ * @param uri the URI of the playlist, used to resolve relative song
+ * URIs
+ */
+enum playlist_result
+playlist_load_into_queue(const char *uri, struct playlist_provider *source,
+ struct playlist *dest);
+
+/**
+ * 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);
+
+#endif
+
diff --git a/src/playlist_save.c b/src/playlist_save.c
index 13dbc721d..8ddc93ec9 100644
--- a/src/playlist_save.c
+++ b/src/playlist_save.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -17,6 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "playlist_save.h"
#include "stored_playlist.h"
#include "song.h"
@@ -54,7 +55,7 @@ playlist_print_uri(FILE *file, const char *uri)
char *s;
if (playlist_saveAbsolutePaths && !uri_has_scheme(uri) &&
- uri[0] != '/')
+ !g_path_is_absolute(uri))
s = map_uri_fs(uri);
else
s = utf8_to_fs_charset(uri);
@@ -118,7 +119,7 @@ playlist_load_spl(struct playlist *playlist, const char *name_utf8)
for (unsigned i = 0; i < list->len; ++i) {
const char *temp = g_ptr_array_index(list, i);
- if ((addToPlaylist(playlist, temp, NULL)) != PLAYLIST_RESULT_SUCCESS) {
+ if ((playlist_append_uri(playlist, temp, NULL)) != PLAYLIST_RESULT_SUCCESS) {
/* for windows compatibility, convert slashes */
char *temp2 = g_strdup(temp);
char *p = temp2;
@@ -127,7 +128,7 @@ playlist_load_spl(struct playlist *playlist, const char *name_utf8)
*p = '/';
p++;
}
- if ((addToPlaylist(playlist, temp, NULL)) != PLAYLIST_RESULT_SUCCESS) {
+ if ((playlist_append_uri(playlist, temp, NULL)) != PLAYLIST_RESULT_SUCCESS) {
g_warning("can't add file \"%s\"", temp2);
}
g_free(temp2);
diff --git a/src/playlist_save.h b/src/playlist_save.h
index 8669ca025..a0131cf7f 100644
--- a/src/playlist_save.h
+++ b/src/playlist_save.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
diff --git a/src/playlist_song.c b/src/playlist_song.c
new file mode 100644
index 000000000..1e8e98795
--- /dev/null
+++ b/src/playlist_song.c
@@ -0,0 +1,149 @@
+/*
+ * 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_song.h"
+#include "database.h"
+#include "mapper.h"
+#include "song.h"
+#include "uri.h"
+#include "ls.h"
+#include "tag.h"
+
+#include <assert.h>
+#include <string.h>
+
+static void
+merge_song_metadata(struct song *dest, const struct song *base,
+ const struct song *add)
+{
+ dest->tag = base->tag != NULL
+ ? (add->tag != NULL
+ ? tag_merge(base->tag, add->tag)
+ : tag_dup(base->tag))
+ : (add->tag != NULL
+ ? tag_dup(add->tag)
+ : NULL);
+
+ dest->mtime = base->mtime;
+ dest->start_ms = add->start_ms;
+ dest->end_ms = add->end_ms;
+}
+
+static struct song *
+apply_song_metadata(struct song *dest, const struct song *src)
+{
+ struct song *tmp;
+
+ assert(dest != NULL);
+ assert(src != NULL);
+
+ if (src->tag == NULL && src->start_ms == 0 && src->end_ms == 0)
+ return dest;
+
+ if (song_in_database(dest)) {
+ char *path_fs = map_song_fs(dest);
+ if (path_fs == NULL)
+ return dest;
+
+ tmp = song_file_new(path_fs, NULL);
+ g_free(path_fs);
+
+ merge_song_metadata(tmp, dest, src);
+ } else {
+ tmp = song_file_new(dest->uri, NULL);
+ merge_song_metadata(tmp, dest, src);
+ song_free(dest);
+ }
+
+ if (dest->tag != NULL && dest->tag->time > 0 &&
+ src->start_ms > 0 && src->end_ms == 0 &&
+ src->start_ms / 1000 < (unsigned)dest->tag->time)
+ /* the range is open-ended, and the playlist plugin
+ did not know the total length of the song file
+ (e.g. last track on a CUE file); fix it up here */
+ tmp->tag->time = dest->tag->time - src->start_ms / 1000;
+
+ return tmp;
+}
+
+struct song *
+playlist_check_translate_song(struct song *song, const char *base_uri)
+{
+ struct song *dest;
+
+ if (song_in_database(song))
+ /* already ok */
+ return song;
+
+ char *uri = song->uri;
+
+ if (uri_has_scheme(uri)) {
+ if (uri_supported_scheme(uri))
+ /* valid remote song */
+ return song;
+ else {
+ /* unsupported remote song */
+ song_free(song);
+ return NULL;
+ }
+ }
+
+ if (g_path_is_absolute(uri)) {
+ /* XXX fs_charset vs utf8? */
+ char *prefix = base_uri != NULL
+ ? map_uri_fs(base_uri)
+ : map_directory_fs(db_get_root());
+
+ if (prefix == NULL || !g_str_has_prefix(uri, prefix) ||
+ uri[strlen(prefix)] != '/') {
+ /* local files must be relative to the music
+ directory */
+ g_free(prefix);
+ song_free(song);
+ return NULL;
+ }
+
+ uri += strlen(prefix) + 1;
+ g_free(prefix);
+ }
+
+ if (base_uri != NULL)
+ uri = g_build_filename(base_uri, uri, NULL);
+ else
+ uri = g_strdup(uri);
+
+ if (uri_has_scheme(base_uri)) {
+ dest = song_remote_new(uri);
+ g_free(uri);
+ } else {
+ dest = db_get_song(uri);
+ g_free(uri);
+ if (dest == NULL) {
+ /* not found in database */
+ song_free(song);
+ return dest;
+ }
+ }
+
+ dest = apply_song_metadata(dest, song);
+ song_free(song);
+
+ return dest;
+}
diff --git a/src/playlist_song.h b/src/playlist_song.h
new file mode 100644
index 000000000..5a2e4c2b0
--- /dev/null
+++ b/src/playlist_song.h
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_PLAYLIST_SONG_H
+#define MPD_PLAYLIST_SONG_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.
+ */
+struct song *
+playlist_check_translate_song(struct song *song, const char *base_uri);
+
+#endif
diff --git a/src/playlist_state.c b/src/playlist_state.c
index af0f7982b..bb9897e01 100644
--- a/src/playlist_state.c
+++ b/src/playlist_state.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -22,11 +22,13 @@
*
*/
+#include "config.h"
#include "playlist_state.h"
#include "playlist.h"
#include "player_control.h"
#include "queue_save.h"
#include "path.h"
+#include "text_file.h"
#include <string.h>
#include <stdlib.h>
@@ -39,6 +41,8 @@
#define PLAYLIST_STATE_FILE_CURRENT "current: "
#define PLAYLIST_STATE_FILE_TIME "time: "
#define PLAYLIST_STATE_FILE_CROSSFADE "crossfade: "
+#define PLAYLIST_STATE_FILE_MIXRAMPDB "mixrampdb: "
+#define PLAYLIST_STATE_FILE_MIXRAMPDELAY "mixrampdelay: "
#define PLAYLIST_STATE_FILE_PLAYLIST_BEGIN "playlist_begin"
#define PLAYLIST_STATE_FILE_PLAYLIST_END "playlist_end"
@@ -51,57 +55,65 @@
void
playlist_state_save(FILE *fp, const struct playlist *playlist)
{
- fprintf(fp, "%s", PLAYLIST_STATE_FILE_STATE);
+ struct player_status player_status;
+
+ pc_get_status(&player_status);
+
+ fputs(PLAYLIST_STATE_FILE_STATE, fp);
if (playlist->playing) {
- switch (getPlayerState()) {
+ switch (player_status.state) {
case PLAYER_STATE_PAUSE:
- fprintf(fp, "%s\n", PLAYLIST_STATE_FILE_STATE_PAUSE);
+ fputs(PLAYLIST_STATE_FILE_STATE_PAUSE "\n", fp);
break;
default:
- fprintf(fp, "%s\n", PLAYLIST_STATE_FILE_STATE_PLAY);
+ fputs(PLAYLIST_STATE_FILE_STATE_PLAY "\n", fp);
}
- fprintf(fp, "%s%i\n", PLAYLIST_STATE_FILE_CURRENT,
+ fprintf(fp, PLAYLIST_STATE_FILE_CURRENT "%i\n",
queue_order_to_position(&playlist->queue,
playlist->current));
- fprintf(fp, "%s%i\n", PLAYLIST_STATE_FILE_TIME,
- getPlayerElapsedTime());
- } else
- fprintf(fp, "%s\n", PLAYLIST_STATE_FILE_STATE_STOP);
-
- fprintf(fp, "%s%i\n", PLAYLIST_STATE_FILE_RANDOM,
- playlist->queue.random);
- fprintf(fp, "%s%i\n", PLAYLIST_STATE_FILE_REPEAT,
- playlist->queue.repeat);
- fprintf(fp, "%s%i\n", PLAYLIST_STATE_FILE_SINGLE,
- playlist->queue.single);
- fprintf(fp, "%s%i\n", PLAYLIST_STATE_FILE_CONSUME,
+ fprintf(fp, PLAYLIST_STATE_FILE_TIME "%i\n",
+ (int)player_status.elapsed_time);
+ } else {
+ fputs(PLAYLIST_STATE_FILE_STATE_STOP "\n", fp);
+
+ if (playlist->current >= 0)
+ fprintf(fp, PLAYLIST_STATE_FILE_CURRENT "%i\n",
+ queue_order_to_position(&playlist->queue,
+ playlist->current));
+ }
+
+ fprintf(fp, PLAYLIST_STATE_FILE_RANDOM "%i\n", playlist->queue.random);
+ fprintf(fp, PLAYLIST_STATE_FILE_REPEAT "%i\n", playlist->queue.repeat);
+ fprintf(fp, PLAYLIST_STATE_FILE_SINGLE "%i\n", playlist->queue.single);
+ fprintf(fp, PLAYLIST_STATE_FILE_CONSUME "%i\n",
playlist->queue.consume);
- fprintf(fp, "%s%i\n", PLAYLIST_STATE_FILE_CROSSFADE,
- (int)(getPlayerCrossFade()));
- fprintf(fp, "%s\n", PLAYLIST_STATE_FILE_PLAYLIST_BEGIN);
+ 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());
+ fprintf(fp, PLAYLIST_STATE_FILE_MIXRAMPDELAY "%f\n",
+ pc_get_mixramp_delay());
+ fputs(PLAYLIST_STATE_FILE_PLAYLIST_BEGIN "\n", fp);
queue_save(fp, &playlist->queue);
- fprintf(fp, "%s\n", PLAYLIST_STATE_FILE_PLAYLIST_END);
+ fputs(PLAYLIST_STATE_FILE_PLAYLIST_END "\n", fp);
}
static void
-playlist_state_load(FILE *fp, struct playlist *playlist, char *buffer)
+playlist_state_load(FILE *fp, GString *buffer, struct playlist *playlist)
{
- int song;
-
- if (!fgets(buffer, PLAYLIST_BUFFER_SIZE, fp)) {
+ const char *line = read_text_line(fp, buffer);
+ if (line == NULL) {
g_warning("No playlist in state file");
return;
}
- while (!g_str_has_prefix(buffer, PLAYLIST_STATE_FILE_PLAYLIST_END)) {
- g_strchomp(buffer);
-
- song = queue_load_song(&playlist->queue, buffer);
+ while (!g_str_has_prefix(line, PLAYLIST_STATE_FILE_PLAYLIST_END)) {
+ queue_load_song(fp, buffer, line, &playlist->queue);
- if (!fgets(buffer, PLAYLIST_BUFFER_SIZE, fp)) {
- g_warning("'%s' not found in state file",
- PLAYLIST_STATE_FILE_PLAYLIST_END);
+ line = read_text_line(fp, buffer);
+ if (line == NULL) {
+ g_warning("'" PLAYLIST_STATE_FILE_PLAYLIST_END
+ "' not found in state file");
break;
}
}
@@ -109,87 +121,116 @@ playlist_state_load(FILE *fp, struct playlist *playlist, char *buffer)
queue_increment_version(&playlist->queue);
}
-void
-playlist_state_restore(FILE *fp, struct playlist *playlist)
+bool
+playlist_state_restore(const char *line, FILE *fp, GString *buffer,
+ struct playlist *playlist)
{
int current = -1;
int seek_time = 0;
int state = PLAYER_STATE_STOP;
- char buffer[PLAYLIST_BUFFER_SIZE];
bool random_mode = false;
- while (fgets(buffer, sizeof(buffer), fp)) {
- g_strchomp(buffer);
+ if (!g_str_has_prefix(line, PLAYLIST_STATE_FILE_STATE))
+ return false;
- if (g_str_has_prefix(buffer, PLAYLIST_STATE_FILE_STATE)) {
- if (strcmp(&(buffer[strlen(PLAYLIST_STATE_FILE_STATE)]),
- PLAYLIST_STATE_FILE_STATE_PLAY) == 0) {
- state = PLAYER_STATE_PLAY;
- } else
- if (strcmp
- (&(buffer[strlen(PLAYLIST_STATE_FILE_STATE)]),
- PLAYLIST_STATE_FILE_STATE_PAUSE)
- == 0) {
- state = PLAYER_STATE_PAUSE;
- }
- } else if (g_str_has_prefix(buffer, PLAYLIST_STATE_FILE_TIME)) {
+ line += sizeof(PLAYLIST_STATE_FILE_STATE) - 1;
+
+ if (strcmp(line, PLAYLIST_STATE_FILE_STATE_PLAY) == 0)
+ state = PLAYER_STATE_PLAY;
+ else if (strcmp(line, PLAYLIST_STATE_FILE_STATE_PAUSE) == 0)
+ state = PLAYER_STATE_PAUSE;
+
+ while ((line = read_text_line(fp, buffer)) != NULL) {
+ if (g_str_has_prefix(line, PLAYLIST_STATE_FILE_TIME)) {
seek_time =
- atoi(&(buffer[strlen(PLAYLIST_STATE_FILE_TIME)]));
- } else if (g_str_has_prefix(buffer, PLAYLIST_STATE_FILE_REPEAT)) {
+ atoi(&(line[strlen(PLAYLIST_STATE_FILE_TIME)]));
+ } else if (g_str_has_prefix(line, PLAYLIST_STATE_FILE_REPEAT)) {
if (strcmp
- (&(buffer[strlen(PLAYLIST_STATE_FILE_REPEAT)]),
+ (&(line[strlen(PLAYLIST_STATE_FILE_REPEAT)]),
"1") == 0) {
- setPlaylistRepeatStatus(playlist, true);
+ playlist_set_repeat(playlist, true);
} else
- setPlaylistRepeatStatus(playlist, false);
- } else if (g_str_has_prefix(buffer, PLAYLIST_STATE_FILE_SINGLE)) {
+ playlist_set_repeat(playlist, false);
+ } else if (g_str_has_prefix(line, PLAYLIST_STATE_FILE_SINGLE)) {
if (strcmp
- (&(buffer[strlen(PLAYLIST_STATE_FILE_SINGLE)]),
+ (&(line[strlen(PLAYLIST_STATE_FILE_SINGLE)]),
"1") == 0) {
- setPlaylistSingleStatus(playlist, true);
+ playlist_set_single(playlist, true);
} else
- setPlaylistSingleStatus(playlist, false);
- } else if (g_str_has_prefix(buffer, PLAYLIST_STATE_FILE_CONSUME)) {
+ playlist_set_single(playlist, false);
+ } else if (g_str_has_prefix(line, PLAYLIST_STATE_FILE_CONSUME)) {
if (strcmp
- (&(buffer[strlen(PLAYLIST_STATE_FILE_CONSUME)]),
+ (&(line[strlen(PLAYLIST_STATE_FILE_CONSUME)]),
"1") == 0) {
- setPlaylistConsumeStatus(playlist, true);
+ playlist_set_consume(playlist, true);
} else
- setPlaylistConsumeStatus(playlist, false);
- } else if (g_str_has_prefix(buffer, PLAYLIST_STATE_FILE_CROSSFADE)) {
- setPlayerCrossFade(atoi
- (&
- (buffer
- [strlen
- (PLAYLIST_STATE_FILE_CROSSFADE)])));
- } else if (g_str_has_prefix(buffer, PLAYLIST_STATE_FILE_RANDOM)) {
+ 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)));
+ } else if (g_str_has_prefix(line, PLAYLIST_STATE_FILE_MIXRAMPDB)) {
+ pc_set_mixramp_db(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)));
+ } else if (g_str_has_prefix(line, PLAYLIST_STATE_FILE_RANDOM)) {
random_mode =
- strcmp(buffer + strlen(PLAYLIST_STATE_FILE_RANDOM),
+ strcmp(line + strlen(PLAYLIST_STATE_FILE_RANDOM),
"1") == 0;
- } else if (g_str_has_prefix(buffer, PLAYLIST_STATE_FILE_CURRENT)) {
- current = atoi(&(buffer
+ } else if (g_str_has_prefix(line, PLAYLIST_STATE_FILE_CURRENT)) {
+ current = atoi(&(line
[strlen
(PLAYLIST_STATE_FILE_CURRENT)]));
- } else if (g_str_has_prefix(buffer,
+ } else if (g_str_has_prefix(line,
PLAYLIST_STATE_FILE_PLAYLIST_BEGIN)) {
- if (state == PLAYER_STATE_STOP)
- current = -1;
- playlist_state_load(fp, playlist, buffer);
+ playlist_state_load(fp, buffer, playlist);
}
}
- setPlaylistRandomStatus(playlist, random_mode);
+ playlist_set_random(playlist, random_mode);
- if (state != PLAYER_STATE_STOP && !queue_is_empty(&playlist->queue)) {
+ if (!queue_is_empty(&playlist->queue)) {
if (!queue_valid_position(&playlist->queue, current))
current = 0;
- if (seek_time == 0)
- playPlaylist(playlist, current);
+ /* 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();
+
+ if (state == PLAYER_STATE_STOP /* && config_option */)
+ playlist->current = current;
+ else if (seek_time == 0)
+ playlist_play(playlist, current);
else
- seekSongInPlaylist(playlist, current, seek_time);
+ playlist_seek_song(playlist, current, seek_time);
if (state == PLAYER_STATE_PAUSE)
- playerPause();
+ pc_pause();
}
+
+ return true;
+}
+
+unsigned
+playlist_state_get_hash(const struct playlist *playlist)
+{
+ struct player_status player_status;
+
+ pc_get_status(&player_status);
+
+ return playlist->queue.version ^
+ (player_status.state != PLAYER_STATE_STOP
+ ? ((int)player_status.elapsed_time << 8)
+ : 0) ^
+ (playlist->current >= 0
+ ? (queue_order_to_position(&playlist->queue,
+ playlist->current) << 16)
+ : 0) ^
+ ((int)pc_get_cross_fade() << 20) ^
+ (player_status.state << 24) ^
+ (playlist->queue.random << 27) ^
+ (playlist->queue.repeat << 28) ^
+ (playlist->queue.single << 29) ^
+ (playlist->queue.consume << 30) ^
+ (playlist->queue.random << 31);
}
diff --git a/src/playlist_state.h b/src/playlist_state.h
index 989430264..8ca3657f2 100644
--- a/src/playlist_state.h
+++ b/src/playlist_state.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -25,6 +25,8 @@
#ifndef PLAYLIST_STATE_H
#define PLAYLIST_STATE_H
+#include <glib.h>
+#include <stdbool.h>
#include <stdio.h>
struct playlist;
@@ -32,7 +34,17 @@ struct playlist;
void
playlist_state_save(FILE *fp, const struct playlist *playlist);
-void
-playlist_state_restore(FILE *fp, struct playlist *playlist);
+bool
+playlist_state_restore(const char *line, FILE *fp, GString *buffer,
+ struct playlist *playlist);
+
+/**
+ * Generates a hash number for the current state of the playlist and
+ * the playback options. This is used by timer_save_state_file() to
+ * determine whether the state has changed and the state file should
+ * be saved.
+ */
+unsigned
+playlist_state_get_hash(const struct playlist *playlist);
#endif
diff --git a/src/playlist_vector.c b/src/playlist_vector.c
new file mode 100644
index 000000000..7c1765a98
--- /dev/null
+++ b/src/playlist_vector.c
@@ -0,0 +1,125 @@
+/*
+ * 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_vector.h"
+
+#include <assert.h>
+#include <string.h>
+#include <glib.h>
+
+static struct playlist_metadata *
+playlist_metadata_new(const char *name, time_t mtime)
+{
+ assert(name != NULL);
+
+ struct playlist_metadata *pm = g_slice_new(struct playlist_metadata);
+ pm->name = g_strdup(name);
+ pm->mtime = mtime;
+ return pm;
+}
+
+static void
+playlist_metadata_free(struct playlist_metadata *pm)
+{
+ assert(pm != NULL);
+ assert(pm->name != NULL);
+
+ g_free(pm->name);
+ g_slice_free(struct playlist_metadata, pm);
+}
+
+void
+playlist_vector_deinit(struct playlist_vector *pv)
+{
+ assert(pv != NULL);
+
+ while (pv->head != NULL) {
+ struct playlist_metadata *pm = pv->head;
+ pv->head = pm->next;
+ playlist_metadata_free(pm);
+ }
+}
+
+static struct playlist_metadata **
+playlist_vector_find_p(struct playlist_vector *pv, const char *name)
+{
+ assert(pv != NULL);
+ assert(name != NULL);
+
+ struct playlist_metadata **pmp = &pv->head;
+
+ for (;;) {
+ struct playlist_metadata *pm = *pmp;
+ if (pm == NULL)
+ return NULL;
+
+ if (strcmp(pm->name, name) == 0)
+ return pmp;
+
+ 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;
+}
+
+void
+playlist_vector_add(struct playlist_vector *pv,
+ const char *name, time_t mtime)
+{
+ struct playlist_metadata *pm = playlist_metadata_new(name, mtime);
+ pm->next = pv->head;
+ pv->head = pm;
+}
+
+bool
+playlist_vector_update_or_add(struct playlist_vector *pv,
+ const char *name, time_t mtime)
+{
+ struct playlist_metadata **pmp = playlist_vector_find_p(pv, name);
+ if (pmp != NULL) {
+ struct playlist_metadata *pm = *pmp;
+ if (mtime == pm->mtime)
+ return false;
+
+ pm->mtime = mtime;
+ } else
+ playlist_vector_add(pv, name, mtime);
+
+ return true;
+}
+
+bool
+playlist_vector_remove(struct playlist_vector *pv, const char *name)
+{
+ struct playlist_metadata **pmp = playlist_vector_find_p(pv, name);
+ if (pmp == NULL)
+ return false;
+
+ struct playlist_metadata *pm = *pmp;
+ *pmp = pm->next;
+
+ playlist_metadata_free(pm);
+ return true;
+}
diff --git a/src/playlist_vector.h b/src/playlist_vector.h
new file mode 100644
index 000000000..566de5f00
--- /dev/null
+++ b/src/playlist_vector.h
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_PLAYLIST_VECTOR_H
+#define MPD_PLAYLIST_VECTOR_H
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <sys/time.h>
+
+/**
+ * A directory entry pointing to a playlist file.
+ */
+struct playlist_metadata {
+ struct playlist_metadata *next;
+
+ /**
+ * The UTF-8 encoded name of the playlist file.
+ */
+ char *name;
+
+ 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);
+
+struct playlist_metadata *
+playlist_vector_find(struct playlist_vector *pv, const char *name);
+
+void
+playlist_vector_add(struct playlist_vector *pv,
+ const char *name, time_t mtime);
+
+/**
+ * @return true if the vector or one of its items was modified
+ */
+bool
+playlist_vector_update_or_add(struct playlist_vector *pv,
+ const char *name, time_t mtime);
+
+bool
+playlist_vector_remove(struct playlist_vector *pv, const char *name);
+
+#endif /* SONGVEC_H */
diff --git a/src/poison.h b/src/poison.h
index 5919c3cbe..3654f2e9c 100644
--- a/src/poison.h
+++ b/src/poison.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -20,8 +20,9 @@
#ifndef MPD_POISON_H
#define MPD_POISON_H
+#include "check.h"
+
#ifndef NDEBUG
-#include "config.h"
#ifdef HAVE_VALGRIND_MEMCHECK_H
#include <valgrind/memcheck.h>
@@ -46,7 +47,7 @@ poison_noaccess(void *p, size_t length)
memset(p, 0x01, length);
#ifdef HAVE_VALGRIND_MEMCHECK_H
- VALGRIND_MAKE_MEM_NOACCESS(p, length);
+ (void)VALGRIND_MAKE_MEM_NOACCESS(p, length);
#endif
#endif
}
@@ -67,7 +68,7 @@ poison_undefined(void *p, size_t length)
memset(p, 0x02, length);
#ifdef HAVE_VALGRIND_MEMCHECK_H
- VALGRIND_MAKE_MEM_UNDEFINED(p, length);
+ (void)VALGRIND_MAKE_MEM_UNDEFINED(p, length);
#endif
#endif
}
diff --git a/src/queue.c b/src/queue.c
index 16891d0aa..dd0b48cb5 100644
--- a/src/queue.c
+++ b/src/queue.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -17,6 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "queue.h"
#include "song.h"
@@ -33,7 +34,7 @@ queue_generate_id(const struct queue *queue)
if (cur >= queue->max_length * QUEUE_HASH_MULT)
cur = 0;
- } while (queue->idToPosition[cur] != -1);
+ } while (queue->id_to_position[cur] != -1);
return cur;
}
@@ -43,14 +44,9 @@ queue_next_order(const struct queue *queue, unsigned order)
{
assert(order < queue->length);
- if (queue->single)
- {
- if (queue->repeat && !queue->consume)
- return order;
- else
- return -1;
- }
- if (order + 1 < queue->length)
+ if (queue->single && queue->repeat && !queue->consume)
+ return order;
+ else if (order + 1 < queue->length)
return order + 1;
else if (queue->repeat && (order > 0 || !queue->consume))
/* restart at first song */
@@ -111,7 +107,7 @@ queue_append(struct queue *queue, struct song *song)
};
queue->order[queue->length] = queue->length;
- queue->idToPosition[id] = queue->length;
+ queue->id_to_position[id] = queue->length;
++queue->length;
@@ -132,8 +128,8 @@ queue_swap(struct queue *queue, unsigned position1, unsigned position2)
queue->items[position1].version = queue->version;
queue->items[position2].version = queue->version;
- queue->idToPosition[id1] = position2;
- queue->idToPosition[id2] = position1;
+ queue->id_to_position[id1] = position2;
+ queue->id_to_position[id2] = position1;
}
static void
@@ -143,7 +139,7 @@ queue_move_song_to(struct queue *queue, unsigned from, unsigned to)
queue->items[to] = queue->items[from];
queue->items[to].version = queue->version;
- queue->idToPosition[from_id] = to;
+ queue->id_to_position[from_id] = to;
}
void
@@ -163,7 +159,7 @@ queue_move(struct queue *queue, unsigned from, unsigned to)
/* put song at _to_ */
- queue->idToPosition[item.id] = to;
+ queue->id_to_position[item.id] = to;
queue->items[to] = item;
queue->items[to].version = queue->version;
@@ -203,7 +199,7 @@ queue_move_range(struct queue *queue, unsigned start, unsigned end, unsigned to)
// Copy the original block back in, starting at to.
for (unsigned i = start; i< end; i++)
{
- queue->idToPosition[items[i-start].id] = to + i - start;
+ queue->id_to_position[items[i-start].id] = to + i - start;
queue->items[to + i - start] = items[i-start];
queue->items[to + i - start].version = queue->version;
}
@@ -243,7 +239,7 @@ queue_delete(struct queue *queue, unsigned position)
/* release the song id */
- queue->idToPosition[id] = -1;
+ queue->id_to_position[id] = -1;
/* delete song from songs array */
@@ -271,7 +267,7 @@ queue_clear(struct queue *queue)
if (!song_in_database(item->song))
song_free(item->song);
- queue->idToPosition[item->id] = -1;
+ queue->id_to_position[item->id] = -1;
}
queue->length = 0;
@@ -291,11 +287,11 @@ queue_init(struct queue *queue, unsigned max_length)
queue->items = g_new(struct queue_item, max_length);
queue->order = g_malloc(sizeof(queue->order[0]) *
max_length);
- queue->idToPosition = g_malloc(sizeof(queue->idToPosition[0]) *
+ queue->id_to_position = g_malloc(sizeof(queue->id_to_position[0]) *
max_length * QUEUE_HASH_MULT);
for (unsigned i = 0; i < max_length * QUEUE_HASH_MULT; ++i)
- queue->idToPosition[i] = -1;
+ queue->id_to_position[i] = -1;
queue->rand = g_rand_new();
}
@@ -307,7 +303,7 @@ queue_finish(struct queue *queue)
g_free(queue->items);
g_free(queue->order);
- g_free(queue->idToPosition);
+ g_free(queue->id_to_position);
g_rand_free(queue->rand);
}
diff --git a/src/queue.h b/src/queue.h
index 9c7228fd8..05eeafa22 100644
--- a/src/queue.h
+++ b/src/queue.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -74,8 +74,8 @@ struct queue {
/** map order numbers to positions */
unsigned *order;
- /** map song ids to posiitons */
- int *idToPosition;
+ /** map song ids to positions */
+ int *id_to_position;
/** repeat playback when the end of the queue has been
reached? */
@@ -146,10 +146,10 @@ queue_id_to_position(const struct queue *queue, unsigned id)
if (id >= queue->max_length * QUEUE_HASH_MULT)
return -1;
- assert(queue->idToPosition[id] >= -1);
- assert(queue->idToPosition[id] < (int)queue->length);
+ assert(queue->id_to_position[id] >= -1);
+ assert(queue->id_to_position[id] < (int)queue->length);
- return queue->idToPosition[id];
+ return queue->id_to_position[id];
}
static inline int
diff --git a/src/queue_print.c b/src/queue_print.c
index 2ca9ccc34..53ddfb689 100644
--- a/src/queue_print.c
+++ b/src/queue_print.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -17,12 +17,14 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "queue_print.h"
#include "queue.h"
#include "song.h"
#include "song_print.h"
#include "locate.h"
#include "client.h"
+#include "mapper.h"
/**
* Send detailed information about a range of songs in the queue to a
@@ -60,11 +62,8 @@ queue_print_uris(struct client *client, const struct queue *queue,
assert(end <= queue_length(queue));
for (unsigned i = start; i < end; ++i) {
- const struct song *song = queue_get(queue, i);
- char *uri = song_get_uri(song);
-
- client_printf(client, "%i:%s\n", i, uri);
- g_free(uri);
+ client_printf(client, "%i:", i);
+ song_print_uri(client, queue_get(queue, i));
}
}
diff --git a/src/queue_print.h b/src/queue_print.h
index 02cbc8b76..d754a9673 100644
--- a/src/queue_print.h
+++ b/src/queue_print.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
diff --git a/src/queue_save.c b/src/queue_save.c
index 9a5a0e30f..afe04ca2d 100644
--- a/src/queue_save.c
+++ b/src/queue_save.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -17,63 +17,90 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "queue_save.h"
#include "queue.h"
#include "song.h"
#include "uri.h"
#include "database.h"
+#include "song_save.h"
#include <stdlib.h>
-void
-queue_save(FILE *fp, const struct queue *queue)
+static void
+queue_save_database_song(FILE *fp, int idx, const struct song *song)
{
- for (unsigned i = 0; i < queue_length(queue); i++) {
- const struct song *song = queue_get(queue, i);
- char *uri = song_get_uri(song);
+ char *uri = song_get_uri(song);
- fprintf(fp, "%i:%s\n", i, uri);
- g_free(uri);
- }
+ fprintf(fp, "%i:%s\n", idx, uri);
+ g_free(uri);
}
-static struct song *
-get_song(const char *uri)
+static void
+queue_save_full_song(FILE *fp, const struct song *song)
{
- struct song *song;
+ song_save(fp, song);
+}
- song = db_get_song(uri);
- if (song != NULL)
- return song;
+static void
+queue_save_song(FILE *fp, int idx, const struct song *song)
+{
+ if (song_in_database(song))
+ queue_save_database_song(fp, idx, song);
+ else
+ queue_save_full_song(fp, song);
+}
- if (uri_has_scheme(uri))
- return song_remote_new(uri);
+void
+queue_save(FILE *fp, const struct queue *queue)
+{
+ for (unsigned i = 0; i < queue_length(queue); i++)
+ queue_save_song(fp, i, queue_get(queue, i));
+}
- return NULL;
+static struct song *
+get_song(const char *uri)
+{
+ return uri_has_scheme(uri)
+ ? song_remote_new(uri)
+ : db_get_song(uri);
}
-int
-queue_load_song(struct queue *queue, const char *line)
+void
+queue_load_song(FILE *fp, GString *buffer, const char *line,
+ struct queue *queue)
{
- long ret;
- char *endptr;
struct song *song;
if (queue_is_full(queue))
- return -1;
+ return;
- ret = strtol(line, &endptr, 10);
- if (ret < 0 || *endptr != ':' || endptr[1] == 0) {
- g_warning("Malformed playlist line in state file");
- return -1;
- }
+ if (g_str_has_prefix(line, SONG_BEGIN)) {
+ const char *uri = line + sizeof(SONG_BEGIN) - 1;
+ if (!uri_has_scheme(uri) && !g_path_is_absolute(uri))
+ return;
- line = endptr + 1;
+ GError *error = NULL;
+ song = song_load(fp, NULL, uri, buffer, &error);
+ if (song == NULL) {
+ g_warning("%s", error->message);
+ g_error_free(error);
+ return;
+ }
+ } else {
+ char *endptr;
+ long ret = strtol(line, &endptr, 10);
+ if (ret < 0 || *endptr != ':' || endptr[1] == 0) {
+ g_warning("Malformed playlist line in state file");
+ return;
+ }
- song = get_song(line);
- if (song == NULL)
- return -1;
+ line = endptr + 1;
+
+ song = get_song(line);
+ if (song == NULL)
+ return;
+ }
queue_append(queue, song);
- return ret;
}
diff --git a/src/queue_save.h b/src/queue_save.h
index 07209b8d0..287683390 100644
--- a/src/queue_save.h
+++ b/src/queue_save.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -25,6 +25,7 @@
#ifndef QUEUE_SAVE_H
#define QUEUE_SAVE_H
+#include <glib.h>
#include <stdio.h>
struct queue;
@@ -33,10 +34,10 @@ void
queue_save(FILE *fp, const struct queue *queue);
/**
- * Loads one song from the state file line and returns its number.
- * Returns -1 on failure.
+ * Loads one song from the state file and appends it to the queue.
*/
-int
-queue_load_song(struct queue *queue, const char *line);
+void
+queue_load_song(FILE *fp, GString *buffer, const char *line,
+ struct queue *queue);
#endif
diff --git a/src/refcount.h b/src/refcount.h
new file mode 100644
index 000000000..87a2715a4
--- /dev/null
+++ b/src/refcount.h
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the
+ * distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** \file
+ *
+ * A very simple reference counting library.
+ */
+
+#ifndef MPD_REFCOUNT_H
+#define MPD_REFCOUNT_H
+
+#include <glib.h>
+
+struct refcount {
+ gint n;
+};
+
+static inline void
+refcount_init(struct refcount *r)
+{
+ r->n = 1;
+}
+
+static inline void
+refcount_inc(struct refcount *r)
+{
+ g_atomic_int_inc(&r->n);
+}
+
+/**
+ * @return true if the number of references has been dropped to 0
+ */
+static inline bool
+refcount_dec(struct refcount *r)
+{
+ return g_atomic_int_dec_and_test(&r->n);
+}
+
+#endif
diff --git a/src/replay_gain.c b/src/replay_gain.c
deleted file mode 100644
index bcb501e54..000000000
--- a/src/replay_gain.c
+++ /dev/null
@@ -1,134 +0,0 @@
-/*
- * 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.
- */
-/*
- * (c)2004 replayGain code by AliasMrJones
- */
-
-#include "replay_gain.h"
-#include "conf.h"
-#include "audio_format.h"
-#include "pcm_volume.h"
-
-#include <glib.h>
-#include <stdlib.h>
-#include <string.h>
-#include <math.h>
-
-static const char *const replay_gain_mode_names[] = {
- [REPLAY_GAIN_ALBUM] = "album",
- [REPLAY_GAIN_TRACK] = "track",
-};
-
-enum replay_gain_mode replay_gain_mode = REPLAY_GAIN_OFF;
-
-static float replay_gain_preamp = 1.0;
-
-void replay_gain_global_init(void)
-{
- const struct config_param *param = config_get_param(CONF_REPLAYGAIN);
-
- if (!param)
- return;
-
- if (strcmp(param->value, "track") == 0) {
- replay_gain_mode = REPLAY_GAIN_TRACK;
- } else if (strcmp(param->value, "album") == 0) {
- replay_gain_mode = REPLAY_GAIN_ALBUM;
- } else {
- g_error("replaygain value \"%s\" at line %i is invalid\n",
- param->value, param->line);
- }
-
- param = config_get_param(CONF_REPLAYGAIN_PREAMP);
-
- if (param) {
- char *test;
- float f = strtod(param->value, &test);
-
- if (*test != '\0') {
- g_error("Replaygain preamp \"%s\" is not a number at "
- "line %i\n", param->value, param->line);
- }
-
- if (f < -15 || f > 15) {
- g_error("Replaygain preamp \"%s\" is not between -15 and"
- "15 at line %i\n", param->value, param->line);
- }
-
- replay_gain_preamp = pow(10, f / 20.0);
- }
-}
-
-static float calc_replay_gain_scale(float gain, float peak)
-{
- float scale;
-
- if (gain == 0.0)
- return (1);
- scale = pow(10.0, gain / 20.0);
- scale *= replay_gain_preamp;
- if (scale > 15.0)
- scale = 15.0;
-
- if (scale * peak > 1.0) {
- scale = 1.0 / peak;
- }
- return (scale);
-}
-
-struct replay_gain_info *replay_gain_info_new(void)
-{
- struct replay_gain_info *ret = g_new(struct replay_gain_info, 1);
-
- for (unsigned i = 0; i < G_N_ELEMENTS(ret->tuples); ++i) {
- ret->tuples[i].gain = 0.0;
- ret->tuples[i].peak = 0.0;
- }
-
- /* set to -1 so that we know in replay_gain_apply to compute the scale */
- ret->scale = -1.0;
-
- return ret;
-}
-
-void replay_gain_info_free(struct replay_gain_info *info)
-{
- g_free(info);
-}
-
-void
-replay_gain_apply(struct replay_gain_info *info, char *buffer, int size,
- const struct audio_format *format)
-{
- if (replay_gain_mode == REPLAY_GAIN_OFF || !info)
- return;
-
- if (info->scale < 0) {
- const struct replay_gain_tuple *tuple =
- &info->tuples[replay_gain_mode];
-
- g_debug("computing ReplayGain %s scale with gain %f, peak %f\n",
- replay_gain_mode_names[replay_gain_mode],
- tuple->gain, tuple->peak);
-
- info->scale = calc_replay_gain_scale(tuple->gain, tuple->peak);
- }
-
- pcm_volume(buffer, size, format, pcm_float_to_volume(info->scale));
-}
diff --git a/src/replay_gain_ape.c b/src/replay_gain_ape.c
new file mode 100644
index 000000000..9ae47468f
--- /dev/null
+++ b/src/replay_gain_ape.c
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "replay_gain_ape.h"
+#include "replay_gain_info.h"
+#include "ape.h"
+
+#include <glib.h>
+
+#include <string.h>
+#include <stdlib.h>
+
+struct rg_ape_ctx {
+ struct replay_gain_info *info;
+ bool found;
+};
+
+static bool
+replay_gain_ape_callback(unsigned long flags, const char *key,
+ const char *_value, size_t value_length, void *_ctx)
+{
+ struct rg_ape_ctx *ctx = _ctx;
+
+ /* we only care about utf-8 text tags */
+ if ((flags & (0x3 << 1)) != 0)
+ return true;
+
+ char value[16];
+ if (value_length >= sizeof(value))
+ return true;
+ memcpy(value, _value, value_length);
+ value[value_length] = 0;
+
+ if (g_ascii_strcasecmp(key, "replaygain_track_gain") == 0) {
+ ctx->info->tuples[REPLAY_GAIN_TRACK].gain = atof(value);
+ ctx->found = true;
+ } else if (g_ascii_strcasecmp(key, "replaygain_album_gain") == 0) {
+ ctx->info->tuples[REPLAY_GAIN_ALBUM].gain = atof(value);
+ ctx->found = true;
+ } else if (g_ascii_strcasecmp(key, "replaygain_track_peak") == 0) {
+ ctx->info->tuples[REPLAY_GAIN_TRACK].peak = atof(value);
+ ctx->found = true;
+ } else if (g_ascii_strcasecmp(key, "replaygain_album_peak") == 0) {
+ ctx->info->tuples[REPLAY_GAIN_ALBUM].peak = atof(value);
+ ctx->found = true;
+ }
+
+ return true;
+}
+
+bool
+replay_gain_ape_read(const char *path_fs, struct replay_gain_info *info)
+{
+ struct rg_ape_ctx ctx = {
+ .info = info,
+ .found = false,
+ };
+
+ return tag_ape_scan(path_fs, replay_gain_ape_callback, &ctx) &&
+ ctx.found;
+}
diff --git a/src/normalize.h b/src/replay_gain_ape.h
index a8144951d..8525ac85e 100644
--- a/src/normalize.h
+++ b/src/replay_gain_ape.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -17,18 +17,16 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
-#ifndef MPD_NORMALIZE_H
-#define MPD_NORMALIZE_H
+#ifndef MPD_REPLAY_GAIN_APE_H
+#define MPD_REPLAY_GAIN_APE_H
-struct audio_format;
+#include "check.h"
-extern int normalizationEnabled;
+#include <stdbool.h>
-void initNormalization(void);
+struct replay_gain_info;
-void finishNormalization(void);
+bool
+replay_gain_ape_read(const char *path_fs, struct replay_gain_info *info);
-void normalizeData(char *buffer, int bufferSize,
- const struct audio_format *format);
-
-#endif /* !NORMALIZE_H */
+#endif
diff --git a/src/replay_gain_config.c b/src/replay_gain_config.c
new file mode 100644
index 000000000..bbfe127a7
--- /dev/null
+++ b/src/replay_gain_config.c
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "replay_gain_config.h"
+#include "playlist.h"
+#include "conf.h"
+#include "idle.h"
+#include "mpd_error.h"
+
+#include <glib.h>
+
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+
+static const char *const replay_gain_mode_names[] = {
+ [REPLAY_GAIN_ALBUM] = "album",
+ [REPLAY_GAIN_TRACK] = "track",
+};
+
+enum replay_gain_mode replay_gain_mode = REPLAY_GAIN_OFF;
+
+const bool DEFAULT_REPLAYGAIN_LIMIT = true;
+
+float replay_gain_preamp = 1.0;
+float replay_gain_missing_preamp = 1.0;
+bool replay_gain_limit;
+
+const char *
+replay_gain_get_mode_string(void)
+{
+ switch (replay_gain_mode) {
+ case REPLAY_GAIN_AUTO:
+ return "auto";
+
+ case REPLAY_GAIN_OFF:
+ return "off";
+
+ case REPLAY_GAIN_TRACK:
+ return "track";
+
+ case REPLAY_GAIN_ALBUM:
+ return "album";
+ }
+
+ /* unreachable */
+ assert(false);
+ return "off";
+}
+
+bool
+replay_gain_set_mode_string(const char *p)
+{
+ assert(p != NULL);
+
+ if (strcmp(p, "off") == 0)
+ replay_gain_mode = REPLAY_GAIN_OFF;
+ else if (strcmp(p, "track") == 0)
+ replay_gain_mode = REPLAY_GAIN_TRACK;
+ else if (strcmp(p, "album") == 0)
+ replay_gain_mode = REPLAY_GAIN_ALBUM;
+ else if (strcmp(p, "auto") == 0)
+ replay_gain_mode = REPLAY_GAIN_AUTO;
+ else
+ return false;
+
+ idle_add(IDLE_OPTIONS);
+
+ return true;
+}
+
+void replay_gain_global_init(void)
+{
+ const struct config_param *param = config_get_param(CONF_REPLAYGAIN);
+
+ if (param != NULL && !replay_gain_set_mode_string(param->value)) {
+ MPD_ERROR("replaygain value \"%s\" at line %i is invalid\n",
+ param->value, param->line);
+ }
+
+ param = config_get_param(CONF_REPLAYGAIN_PREAMP);
+
+ if (param) {
+ char *test;
+ float f = strtod(param->value, &test);
+
+ if (*test != '\0') {
+ MPD_ERROR("Replaygain preamp \"%s\" is not a number at "
+ "line %i\n", param->value, param->line);
+ }
+
+ if (f < -15 || f > 15) {
+ MPD_ERROR("Replaygain preamp \"%s\" is not between -15 and"
+ "15 at line %i\n", param->value, param->line);
+ }
+
+ replay_gain_preamp = pow(10, f / 20.0);
+ }
+
+ param = config_get_param(CONF_REPLAYGAIN_MISSING_PREAMP);
+
+ if (param) {
+ char *test;
+ float f = strtod(param->value, &test);
+
+ if (*test != '\0') {
+ MPD_ERROR("Replaygain missing preamp \"%s\" is not a number at "
+ "line %i\n", param->value, param->line);
+ }
+
+ if (f < -15 || f > 15) {
+ MPD_ERROR("Replaygain missing preamp \"%s\" is not between -15 and"
+ "15 at line %i\n", param->value, param->line);
+ }
+
+ replay_gain_missing_preamp = pow(10, f / 20.0);
+ }
+
+ replay_gain_limit = config_get_bool(CONF_REPLAYGAIN_LIMIT, DEFAULT_REPLAYGAIN_LIMIT);
+}
+
+enum replay_gain_mode replay_gain_get_real_mode(void)
+{
+ enum replay_gain_mode rgm;
+
+ rgm = replay_gain_mode;
+
+ if (rgm == REPLAY_GAIN_AUTO)
+ rgm = g_playlist.queue.random ? REPLAY_GAIN_TRACK : REPLAY_GAIN_ALBUM;
+
+ return rgm;
+}
diff --git a/src/replay_gain.h b/src/replay_gain_config.h
index aa48f3f14..8fb77a5f6 100644
--- a/src/replay_gain.h
+++ b/src/replay_gain_config.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -16,44 +16,40 @@
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
-/*
- * (c)2004 replayGain code by AliasMrJones
- */
-#ifndef MPD_REPLAY_GAIN_H
-#define MPD_REPLAY_GAIN_H
+#ifndef MPD_REPLAY_GAIN_CONFIG_H
+#define MPD_REPLAY_GAIN_CONFIG_H
-enum replay_gain_mode {
- REPLAY_GAIN_OFF = -1,
- REPLAY_GAIN_ALBUM,
- REPLAY_GAIN_TRACK,
-};
+#include "check.h"
+#include "replay_gain_info.h"
-struct audio_format;
+#include <stdbool.h>
extern enum replay_gain_mode replay_gain_mode;
-
-struct replay_gain_tuple {
- float gain;
- float peak;
-};
-
-struct replay_gain_info {
- struct replay_gain_tuple tuples[2];
-
- /* used internally by mpd, to mess with it */
- float scale;
-};
-
-struct replay_gain_info *
-replay_gain_info_new(void);
-
-void replay_gain_info_free(struct replay_gain_info *info);
+extern float replay_gain_preamp;
+extern float replay_gain_missing_preamp;
+extern bool replay_gain_limit;
void replay_gain_global_init(void);
-void
-replay_gain_apply(struct replay_gain_info *info, char *buffer, int bufferSize,
- const struct audio_format *format);
+/**
+ * Returns the current replay gain mode as a machine-readable string.
+ */
+const char *
+replay_gain_get_mode_string(void);
+
+/**
+ * Sets the replay gain mode, parsed from a string.
+ *
+ * @return true on success, false if the string could not be parsed
+ */
+bool
+replay_gain_set_mode_string(const char *p);
+
+/**
+ * Returns the "real" mode according to the "auto" setting"
+ */
+enum replay_gain_mode
+replay_gain_get_real_mode(void);
#endif
diff --git a/src/replay_gain_info.c b/src/replay_gain_info.c
new file mode 100644
index 000000000..3b4ab4577
--- /dev/null
+++ b/src/replay_gain_info.c
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "replay_gain_info.h"
+
+float
+replay_gain_tuple_scale(const struct replay_gain_tuple *tuple, float preamp, float missing_preamp, bool peak_limit)
+{
+ float scale;
+
+ if (replay_gain_tuple_defined(tuple)) {
+ scale = pow(10.0, tuple->gain / 20.0);
+ scale *= preamp;
+ if (scale > 15.0)
+ scale = 15.0;
+
+ if (peak_limit && scale * tuple->peak > 1.0)
+ scale = 1.0 / tuple->peak;
+ } else
+ scale = missing_preamp;
+
+ return scale;
+}
+
+void
+replay_gain_info_complete(struct replay_gain_info *info)
+{
+ if (!replay_gain_tuple_defined(&info->tuples[REPLAY_GAIN_ALBUM]))
+ info->tuples[REPLAY_GAIN_ALBUM] =
+ info->tuples[REPLAY_GAIN_TRACK];
+}
diff --git a/src/replay_gain_info.h b/src/replay_gain_info.h
new file mode 100644
index 000000000..83b46df84
--- /dev/null
+++ b/src/replay_gain_info.h
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_REPLAY_GAIN_INFO_H
+#define MPD_REPLAY_GAIN_INFO_H
+
+#include "check.h"
+
+#include <stdbool.h>
+#include <math.h>
+
+enum replay_gain_mode {
+ REPLAY_GAIN_AUTO = -2,
+ REPLAY_GAIN_OFF,
+ REPLAY_GAIN_ALBUM,
+ REPLAY_GAIN_TRACK,
+};
+
+struct replay_gain_tuple {
+ float gain;
+ float peak;
+};
+
+struct replay_gain_info {
+ struct replay_gain_tuple tuples[2];
+};
+
+static inline void
+replay_gain_tuple_init(struct replay_gain_tuple *tuple)
+{
+ tuple->gain = INFINITY;
+ tuple->peak = 0.0;
+}
+
+static inline void
+replay_gain_info_init(struct replay_gain_info *info)
+{
+ replay_gain_tuple_init(&info->tuples[REPLAY_GAIN_ALBUM]);
+ replay_gain_tuple_init(&info->tuples[REPLAY_GAIN_TRACK]);
+}
+
+static inline bool
+replay_gain_tuple_defined(const struct replay_gain_tuple *tuple)
+{
+ return !isinf(tuple->gain);
+}
+
+float
+replay_gain_tuple_scale(const struct replay_gain_tuple *tuple, float preamp, float missing_preamp, bool peak_limit);
+
+/**
+ * Attempt to auto-complete missing data. In particular, if album
+ * information is missing, track gain is used.
+ */
+void
+replay_gain_info_complete(struct replay_gain_info *info);
+
+#endif
diff --git a/src/riff.c b/src/riff.c
index a8ea9dd42..2e8648ff6 100644
--- a/src/riff.c
+++ b/src/riff.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -17,6 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h" /* must be first for large file support */
#include "riff.h"
#include <glib.h>
diff --git a/src/riff.h b/src/riff.h
index 470985105..bfcb69a7d 100644
--- a/src/riff.h
+++ b/src/riff.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
diff --git a/src/server_socket.c b/src/server_socket.c
new file mode 100644
index 000000000..bb7a6f097
--- /dev/null
+++ b/src/server_socket.c
@@ -0,0 +1,444 @@
+/*
+ * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "server_socket.h"
+#include "socket_util.h"
+#include "fd_util.h"
+#include "glib_compat.h"
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <assert.h>
+
+#ifdef WIN32
+#define WINVER 0x0501
+#include <ws2tcpip.h>
+#include <winsock.h>
+#else
+#include <netinet/in.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <netdb.h>
+#endif
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "listen"
+
+#define DEFAULT_PORT 6600
+
+struct one_socket {
+ struct one_socket *next;
+ struct server_socket *parent;
+
+ unsigned serial;
+
+ int fd;
+ guint source_id;
+
+ char *path;
+
+ size_t address_length;
+ struct sockaddr address;
+};
+
+struct server_socket {
+ server_socket_callback_t callback;
+ void *callback_ctx;
+
+ struct one_socket *sockets, **sockets_tail_r;
+ unsigned next_serial;
+};
+
+static GQuark
+server_socket_quark(void)
+{
+ return g_quark_from_static_string("server_socket");
+}
+
+struct server_socket *
+server_socket_new(server_socket_callback_t callback, void *callback_ctx)
+{
+ struct server_socket *ss = g_new(struct server_socket, 1);
+ ss->callback = callback;
+ ss->callback_ctx = callback_ctx;
+ ss->sockets = NULL;
+ ss->sockets_tail_r = &ss->sockets;
+ ss->next_serial = 1;
+ return ss;
+}
+
+void
+server_socket_free(struct server_socket *ss)
+{
+ server_socket_close(ss);
+
+ while (ss->sockets != NULL) {
+ struct one_socket *s = ss->sockets;
+ ss->sockets = s->next;
+
+ assert(s->fd < 0);
+
+ g_free(s->path);
+ g_free(s);
+ }
+
+ g_free(ss);
+}
+
+/**
+ * Wraper for sockaddr_to_string() which never fails.
+ */
+static char *
+one_socket_to_string(const struct one_socket *s)
+{
+ char *p = sockaddr_to_string(&s->address, s->address_length, NULL);
+ if (p == NULL)
+ p = g_strdup("[unknown]");
+ return p;
+}
+
+static int
+get_remote_uid(int fd)
+{
+#ifdef HAVE_STRUCT_UCRED
+ struct ucred cred;
+ socklen_t len = sizeof (cred);
+
+ if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &cred, &len) < 0)
+ return 0;
+
+ return cred.uid;
+#else
+#ifdef HAVE_GETPEEREID
+ uid_t euid;
+ gid_t egid;
+
+ if (getpeereid(fd, &euid, &egid) == 0)
+ return euid;
+#else
+ (void)fd;
+#endif
+ return -1;
+#endif
+}
+
+static gboolean
+server_socket_in_event(G_GNUC_UNUSED GIOChannel *source,
+ G_GNUC_UNUSED GIOCondition condition,
+ gpointer data)
+{
+ struct one_socket *s = data;
+
+ struct sockaddr_storage address;
+ size_t address_length = sizeof(address);
+ int fd = accept_cloexec_nonblock(s->fd, (struct sockaddr*)&address,
+ &address_length);
+ if (fd >= 0)
+ s->parent->callback(fd, (const struct sockaddr*)&address,
+ address_length, get_remote_uid(fd),
+ s->parent->callback_ctx);
+ else
+ g_warning("accept() failed: %s", g_strerror(errno));
+
+ return true;
+}
+
+bool
+server_socket_open(struct server_socket *ss, GError **error_r)
+{
+ struct one_socket *good = NULL, *bad = NULL;
+ GError *last_error = NULL;
+
+ for (struct one_socket *s = ss->sockets; s != NULL; s = s->next) {
+ assert(s->serial > 0);
+ assert(good == NULL || s->serial >= good->serial);
+ assert(s->fd < 0);
+
+ if (bad != NULL && s->serial != bad->serial) {
+ server_socket_close(ss);
+ g_propagate_error(error_r, last_error);
+ return false;
+ }
+
+ GError *error = NULL;
+ s->fd = socket_bind_listen(s->address.sa_family, SOCK_STREAM, 0,
+ &s->address, s->address_length, 5,
+ &error);
+ if (s->fd < 0) {
+ if (good != NULL && good->serial == s->serial) {
+ char *address_string = one_socket_to_string(s);
+ char *good_string = one_socket_to_string(good);
+ g_warning("bind to '%s' failed: %s "
+ "(continuing anyway, because "
+ "binding to '%s' succeeded)",
+ address_string, error->message,
+ good_string);
+ g_free(address_string);
+ g_free(good_string);
+ g_error_free(error);
+ } else if (bad == NULL) {
+ bad = s;
+
+ char *address_string = one_socket_to_string(s);
+ g_propagate_prefixed_error(&last_error, error,
+ "Failed to bind to '%s': ",
+ address_string);
+ g_free(address_string);
+ } else
+ g_error_free(error);
+ continue;
+ }
+
+ /* allow everybody to connect */
+
+ if (s->path != NULL)
+ chmod(s->path, 0666);
+
+ /* register in the GLib main loop */
+
+ GIOChannel *channel = g_io_channel_unix_new(s->fd);
+ s->source_id = g_io_add_watch(channel, G_IO_IN,
+ server_socket_in_event, s);
+ g_io_channel_unref(channel);
+
+ /* mark this socket as "good", and clear previous
+ errors */
+
+ good = s;
+
+ if (bad != NULL) {
+ bad = NULL;
+ g_error_free(last_error);
+ last_error = NULL;
+ }
+ }
+
+ if (bad != NULL) {
+ server_socket_close(ss);
+ g_propagate_error(error_r, last_error);
+ return false;
+ }
+
+ return true;
+}
+
+void
+server_socket_close(struct server_socket *ss)
+{
+ for (struct one_socket *s = ss->sockets; s != NULL; s = s->next) {
+ if (s->fd < 0)
+ continue;
+
+ g_source_remove(s->source_id);
+ close(s->fd);
+ s->fd = -1;
+ }
+}
+
+static struct one_socket *
+one_socket_new(unsigned serial, const struct sockaddr *address,
+ size_t address_length)
+{
+ assert(address != NULL);
+ assert(address_length > 0);
+
+ struct one_socket *s = g_malloc(sizeof(*s) - sizeof(s->address) +
+ address_length);
+ s->next = NULL;
+ s->serial = serial;
+ s->fd = -1;
+ s->path = NULL;
+ s->address_length = address_length;
+ memcpy(&s->address, address, address_length);
+
+ return s;
+}
+
+static struct one_socket *
+server_socket_add_address(struct server_socket *ss,
+ const struct sockaddr *address,
+ size_t address_length)
+{
+ assert(ss != NULL);
+ assert(ss->sockets_tail_r != NULL);
+ assert(*ss->sockets_tail_r == NULL);
+
+ struct one_socket *s = one_socket_new(ss->next_serial,
+ address, address_length);
+ s->parent = ss;
+ *ss->sockets_tail_r = s;
+ ss->sockets_tail_r = &s->next;
+
+ return s;
+}
+
+#ifdef HAVE_TCP
+
+/**
+ * Add a listener on a port on all IPv4 interfaces.
+ *
+ * @param port the TCP port
+ */
+static void
+server_socket_add_port_ipv4(struct server_socket *ss, unsigned port)
+{
+ struct sockaddr_in sin;
+ memset(&sin, 0, sizeof(sin));
+ sin.sin_port = htons(port);
+ sin.sin_family = AF_INET;
+ sin.sin_addr.s_addr = INADDR_ANY;
+
+ server_socket_add_address(ss, (const struct sockaddr *)&sin,
+ sizeof(sin));
+}
+
+#ifdef HAVE_IPV6
+/**
+ * Add a listener on a port on all IPv6 interfaces.
+ *
+ * @param port the TCP port
+ */
+static void
+server_socket_add_port_ipv6(struct server_socket *ss, unsigned port)
+{
+ struct sockaddr_in6 sin;
+ memset(&sin, 0, sizeof(sin));
+ sin.sin6_port = htons(port);
+ sin.sin6_family = AF_INET6;
+
+ server_socket_add_address(ss, (const struct sockaddr *)&sin,
+ sizeof(sin));
+}
+#endif /* HAVE_IPV6 */
+
+#endif /* HAVE_TCP */
+
+bool
+server_socket_add_port(struct server_socket *ss, unsigned port,
+ GError **error_r)
+{
+#ifdef HAVE_TCP
+ if (port == 0 || port > 0xffff) {
+ g_set_error(error_r, server_socket_quark(), 0,
+ "Invalid TCP port");
+ return false;
+ }
+
+#ifdef HAVE_IPV6
+ server_socket_add_port_ipv6(ss, port);
+#endif
+ server_socket_add_port_ipv4(ss, port);
+
+ ++ss->next_serial;
+
+ return true;
+#else /* HAVE_TCP */
+ (void)ss;
+ (void)port;
+
+ g_set_error(error_r, server_socket_quark(), 0,
+ "TCP support is disabled");
+ return false;
+#endif /* HAVE_TCP */
+}
+
+bool
+server_socket_add_host(struct server_socket *ss, const char *hostname,
+ unsigned port, GError **error_r)
+{
+#ifdef HAVE_TCP
+ struct addrinfo hints;
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_flags = AI_PASSIVE;
+ hints.ai_family = PF_UNSPEC;
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_protocol = IPPROTO_TCP;
+
+ char service[20];
+ g_snprintf(service, sizeof(service), "%u", port);
+
+ struct addrinfo *ai;
+ int ret = getaddrinfo(hostname, service, &hints, &ai);
+ if (ret != 0) {
+ g_set_error(error_r, server_socket_quark(), ret,
+ "Failed to look up host \"%s\": %s",
+ hostname, gai_strerror(ret));
+ return false;
+ }
+
+ for (const struct addrinfo *i = ai; i != NULL; i = i->ai_next)
+ server_socket_add_address(ss, i->ai_addr, i->ai_addrlen);
+
+ freeaddrinfo(ai);
+
+ ++ss->next_serial;
+
+ return true;
+#else /* HAVE_TCP */
+ (void)ss;
+ (void)hostname;
+ (void)port;
+
+ g_set_error(error_r, server_socket_quark(), 0,
+ "TCP support is disabled");
+ return false;
+#endif /* HAVE_TCP */
+}
+
+bool
+server_socket_add_path(struct server_socket *ss, const char *path,
+ GError **error_r)
+{
+#ifdef HAVE_UN
+ struct sockaddr_un s_un;
+
+ size_t path_length = strlen(path);
+ if (path_length >= sizeof(s_un.sun_path)) {
+ g_set_error(error_r, server_socket_quark(), 0,
+ "UNIX socket path is too long");
+ return false;
+ }
+
+ unlink(path);
+
+ s_un.sun_family = AF_UNIX;
+ memcpy(s_un.sun_path, path, path_length + 1);
+
+ struct one_socket *s =
+ server_socket_add_address(ss, (const struct sockaddr *)&s_un,
+ sizeof(s_un));
+ s->path = g_strdup(path);
+
+ return true;
+#else /* !HAVE_UN */
+ (void)ss;
+ (void)path;
+
+ g_set_error(error_r, server_socket_quark(), 0,
+ "UNIX domain socket support is disabled");
+ return false;
+#endif /* !HAVE_UN */
+}
+
diff --git a/src/server_socket.h b/src/server_socket.h
new file mode 100644
index 000000000..ae0ce0c8d
--- /dev/null
+++ b/src/server_socket.h
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_SERVER_SOCKET_H
+#define MPD_SERVER_SOCKET_H
+
+#include <stdbool.h>
+
+#include <glib.h>
+
+struct sockaddr;
+
+typedef void (*server_socket_callback_t)(int fd,
+ const struct sockaddr *address,
+ size_t address_length, int uid,
+ void *ctx);
+
+struct server_socket *
+server_socket_new(server_socket_callback_t callback, void *callback_ctx);
+
+void
+server_socket_free(struct server_socket *ss);
+
+bool
+server_socket_open(struct server_socket *ss, GError **error_r);
+
+void
+server_socket_close(struct server_socket *ss);
+
+/**
+ * Add a listener on a port on all interfaces.
+ *
+ * @param port the TCP port
+ * @param error_r location to store the error occuring, or NULL to
+ * ignore errors
+ * @return true on success
+ */
+bool
+server_socket_add_port(struct server_socket *ss, unsigned port,
+ GError **error_r);
+
+/**
+ * Resolves a host name, and adds listeners on all addresses in the
+ * result set.
+ *
+ * @param hostname the host name to be resolved
+ * @param port the TCP port
+ * @param error_r location to store the error occuring, or NULL to
+ * ignore errors
+ * @return true on success
+ */
+bool
+server_socket_add_host(struct server_socket *ss, const char *hostname,
+ unsigned port, GError **error_r);
+
+/**
+ * Add a listener on a Unix domain socket.
+ *
+ * @param path the absolute socket path
+ * @param error_r location to store the error occuring, or NULL to
+ * ignore errors
+ * @return true on success
+ */
+bool
+server_socket_add_path(struct server_socket *ss, const char *path,
+ GError **error_r);
+
+#endif
diff --git a/src/sig_handlers.c b/src/sig_handlers.c
index e70e1a159..8aa85cf88 100644
--- a/src/sig_handlers.c
+++ b/src/sig_handlers.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -17,6 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "sig_handlers.h"
#ifndef WIN32
@@ -24,6 +25,7 @@
#include "log.h"
#include "main.h"
#include "event_pipe.h"
+#include "mpd_error.h"
#include <glib.h>
@@ -45,7 +47,7 @@ static void
x_sigaction(int signum, const struct sigaction *act)
{
if (sigaction(signum, act, NULL) < 0)
- g_error("sigaction() failed: %s", strerror(errno));
+ MPD_ERROR("sigaction() failed: %s", strerror(errno));
}
static void
@@ -65,7 +67,7 @@ void initSigHandlers(void)
sa.sa_flags = 0;
sigemptyset(&sa.sa_mask);
sa.sa_handler = SIG_IGN;
- while (sigaction(SIGPIPE, &sa, NULL) < 0 && errno == EINTR) ;
+ x_sigaction(SIGPIPE, &sa);
sa.sa_handler = exit_signal_handler;
x_sigaction(SIGINT, &sa);
diff --git a/src/sig_handlers.h b/src/sig_handlers.h
index efc7f797c..a578cd243 100644
--- a/src/sig_handlers.h
+++ b/src/sig_handlers.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
diff --git a/src/socket_util.c b/src/socket_util.c
index da4e414b6..0909765ba 100644
--- a/src/socket_util.c
+++ b/src/socket_util.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -17,8 +17,9 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
-#include "socket_util.h"
#include "config.h"
+#include "socket_util.h"
+#include "fd_util.h"
#include <errno.h>
#include <unistd.h>
@@ -27,6 +28,7 @@
#include <sys/socket.h>
#include <netdb.h>
#else /* G_OS_WIN32 */
+#define WINVER 0x0501
#include <ws2tcpip.h>
#include <winsock.h>
#endif /* G_OS_WIN32 */
@@ -102,15 +104,21 @@ socket_bind_listen(int domain, int type, int protocol,
int passcred = 1;
#endif
- fd = socket(domain, type, protocol);
+ fd = socket_cloexec_nonblock(domain, type, protocol);
if (fd < 0) {
g_set_error(error, listen_quark(), errno,
"Failed to create socket: %s", g_strerror(errno));
return -1;
}
+#ifdef WIN32
+ const char *optval = (const char *)&reuse;
+#else
+ const void *optval = &reuse;
+#endif
+
ret = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR,
- &reuse, sizeof(reuse));
+ optval, sizeof(reuse));
if (ret < 0) {
g_set_error(error, listen_quark(), errno,
"setsockopt() failed: %s", g_strerror(errno));
diff --git a/src/socket_util.h b/src/socket_util.h
index dc129df40..7ef081362 100644
--- a/src/socket_util.h
+++ b/src/socket_util.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
diff --git a/src/song.c b/src/song.c
index 76c25f44f..13fd476b9 100644
--- a/src/song.c
+++ b/src/song.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -17,47 +17,40 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "song.h"
#include "uri.h"
#include "directory.h"
-#include "mapper.h"
-#include "decoder_list.h"
-#include "decoder_plugin.h"
-#include "tag_ape.h"
-#include "tag_id3.h"
#include "tag.h"
#include <glib.h>
#include <assert.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <unistd.h>
-#include <string.h>
static struct song *
-song_alloc(const char *url, struct directory *parent)
+song_alloc(const char *uri, struct directory *parent)
{
- size_t urllen;
+ size_t uri_length;
struct song *song;
- assert(url);
- urllen = strlen(url);
- assert(urllen);
- song = g_malloc(sizeof(*song) - sizeof(song->url) + urllen + 1);
+ assert(uri);
+ uri_length = strlen(uri);
+ assert(uri_length);
+ song = g_malloc(sizeof(*song) - sizeof(song->uri) + uri_length + 1);
song->tag = NULL;
- memcpy(song->url, url, urllen + 1);
+ memcpy(song->uri, uri, uri_length + 1);
song->parent = parent;
song->mtime = 0;
+ song->start_ms = song->end_ms = 0;
return song;
}
struct song *
-song_remote_new(const char *url)
+song_remote_new(const char *uri)
{
- return song_alloc(url, NULL);
+ return song_alloc(uri, NULL);
}
struct song *
@@ -68,32 +61,6 @@ song_file_new(const char *path, struct directory *parent)
return song_alloc(path, parent);
}
-struct song *
-song_file_load(const char *path, struct directory *parent)
-{
- struct song *song;
- bool ret;
-
- assert((parent == NULL) == (*path == '/'));
- assert(!uri_has_scheme(path));
- assert(strchr(path, '\n') == NULL);
-
- song = song_file_new(path, parent);
-
- //in archive ?
- if (parent != NULL && parent->device == DEVICE_INARCHIVE) {
- ret = song_file_update_inarchive(song);
- } else {
- ret = song_file_update(song);
- }
- if (!ret) {
- song_free(song);
- return NULL;
- }
-
- return song;
-}
-
void
song_free(struct song *song)
{
@@ -102,127 +69,27 @@ song_free(struct song *song)
g_free(song);
}
-/**
- * Attempts to load APE or ID3 tags from the specified file.
- */
-static struct tag *
-tag_load_fallback(const char *path)
-{
- 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;
-}
-
-bool
-song_file_update(struct song *song)
-{
- const char *suffix;
- char *path_fs;
- const struct decoder_plugin *plugin;
- struct stat st;
-
- assert(song_is_file(song));
-
- /* check if there's a suffix and a plugin */
-
- suffix = uri_get_suffix(song->url);
- if (suffix == NULL)
- return false;
-
- plugin = decoder_plugin_from_suffix(suffix, false);
- if (plugin == NULL)
- return false;
-
- path_fs = map_song_fs(song);
- if (path_fs == NULL)
- return false;
-
- if (song->tag != NULL) {
- tag_free(song->tag);
- song->tag = NULL;
- }
-
- if (stat(path_fs, &st) < 0 || !S_ISREG(st.st_mode)) {
- g_free(path_fs);
- return false;
- }
-
- song->mtime = st.st_mtime;
-
- do {
- song->tag = plugin->tag_dup(path_fs);
- if (song->tag != NULL)
- break;
-
- plugin = decoder_plugin_from_suffix(suffix, true);
- } while (plugin != NULL);
-
- if (song->tag != NULL && tag_is_empty(song->tag))
- song->tag = tag_fallback(path_fs, song->tag);
-
- g_free(path_fs);
- return song->tag != NULL;
-}
-
-bool
-song_file_update_inarchive(struct song *song)
-{
- const char *suffix;
- const struct decoder_plugin *plugin;
-
- assert(song_is_file(song));
-
- /* check if there's a suffix and a plugin */
-
- suffix = uri_get_suffix(song->url);
- if (suffix == NULL)
- return false;
-
- plugin = decoder_plugin_from_suffix(suffix, false);
- if (plugin == NULL)
- return false;
-
- if (song->tag != NULL)
- tag_free(song->tag);
-
- //accept every file that has music suffix
- //because we dont support tag reading throught
- //input streams
- song->tag = tag_new();
-
- return true;
-}
-
char *
song_get_uri(const struct song *song)
{
assert(song != NULL);
- assert(*song->url);
+ assert(*song->uri);
if (!song_in_database(song) || directory_is_root(song->parent))
- return g_strdup(song->url);
+ return g_strdup(song->uri);
else
return g_strconcat(directory_get_path(song->parent),
- "/", song->url, NULL);
+ "/", song->uri, NULL);
+}
+
+double
+song_get_duration(const struct song *song)
+{
+ if (song->end_ms > 0)
+ return (song->end_ms - song->start_ms) / 1000.0;
+
+ if (song->tag == NULL)
+ return 0;
+
+ return song->tag->time - song->start_ms / 1000.0;
}
diff --git a/src/song.h b/src/song.h
index 3044e910f..26a1dc806 100644
--- a/src/song.h
+++ b/src/song.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -24,9 +24,6 @@
#include <stdbool.h>
#include <sys/time.h>
-#define SONG_BEGIN "songList begin"
-#define SONG_END "songList end"
-
#define SONG_FILE "file: "
#define SONG_TIME "Time: "
@@ -34,12 +31,24 @@ struct song {
struct tag *tag;
struct directory *parent;
time_t mtime;
- char url[sizeof(int)];
+
+ /**
+ * Start of this sub-song within the file in milliseconds.
+ */
+ unsigned start_ms;
+
+ /**
+ * End of this sub-song within the file in milliseconds.
+ * Unused if zero.
+ */
+ unsigned end_ms;
+
+ char uri[sizeof(int)];
};
/** allocate a new song with a remote URL */
struct song *
-song_remote_new(const char *url);
+song_remote_new(const char *uri);
/** allocate a new song with a local file name */
struct song *
@@ -72,6 +81,9 @@ song_file_update_inarchive(struct song *song);
char *
song_get_uri(const struct song *song);
+double
+song_get_duration(const struct song *song);
+
static inline bool
song_in_database(const struct song *song)
{
@@ -81,7 +93,7 @@ song_in_database(const struct song *song)
static inline bool
song_is_file(const struct song *song)
{
- return song_in_database(song) || song->url[0] == '/';
+ return song_in_database(song) || song->uri[0] == '/';
}
#endif
diff --git a/src/song_print.c b/src/song_print.c
index 64ab9f6b1..16239e03b 100644
--- a/src/song_print.c
+++ b/src/song_print.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -17,6 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "song_print.h"
#include "song.h"
#include "songvec.h"
@@ -24,46 +25,87 @@
#include "tag_print.h"
#include "client.h"
#include "uri.h"
+#include "mapper.h"
void
-song_print_url(struct client *client, struct song *song)
+song_print_uri(struct client *client, struct song *song)
{
if (song_in_database(song) && !directory_is_root(song->parent)) {
client_printf(client, "%s%s/%s\n", SONG_FILE,
- directory_get_path(song->parent), song->url);
+ directory_get_path(song->parent), song->uri);
} else {
char *allocated;
const char *uri;
- uri = allocated = uri_remove_auth(song->url);
+ uri = allocated = uri_remove_auth(song->uri);
if (uri == NULL)
- uri = song->url;
+ uri = song->uri;
- client_printf(client, "%s%s\n", SONG_FILE, uri);
+ client_printf(client, "%s%s\n", SONG_FILE,
+ map_to_relative_path(uri));
g_free(allocated);
}
}
-int
+void
song_print_info(struct client *client, struct song *song)
{
- song_print_url(client, song);
+ song_print_uri(client, song);
+
+ if (song->end_ms > 0)
+ client_printf(client, "Range: %u.%03u-%u.%03u\n",
+ song->start_ms / 1000,
+ song->start_ms % 1000,
+ song->end_ms / 1000,
+ song->end_ms % 1000);
+ else if (song->start_ms > 0)
+ client_printf(client, "Range: %u.%03u-\n",
+ song->start_ms / 1000,
+ song->start_ms % 1000);
+
+ if (song->mtime > 0) {
+#ifndef G_OS_WIN32
+ struct tm tm;
+#endif
+ const struct tm *tm2;
+
+#ifdef G_OS_WIN32
+ tm2 = gmtime(&song->mtime);
+#else
+ tm2 = gmtime_r(&song->mtime, &tm);
+#endif
+
+ if (tm2 != NULL) {
+ char timestamp[32];
+
+ strftime(timestamp, sizeof(timestamp),
+#ifdef G_OS_WIN32
+ "%Y-%m-%dT%H:%M:%SZ",
+#else
+ "%FT%TZ",
+#endif
+ tm2);
+ client_printf(client, "Last-Modified: %s\n",
+ timestamp);
+ }
+ }
if (song->tag)
tag_print(client, song->tag);
-
- return 0;
}
static int
song_print_info_x(struct song *song, void *data)
{
struct client *client = data;
- return song_print_info(client, song);
+ song_print_info(client, song);
+
+ return 0;
}
-int songvec_print(struct client *client, const struct songvec *sv)
+void
+songvec_print(struct client *client, const struct songvec *sv)
{
- return songvec_for_each(sv, song_print_info_x, client);
+ songvec_for_each(sv, song_print_info_x, client);
}
diff --git a/src/song_print.h b/src/song_print.h
index 291fd81c8..cb83f4711 100644
--- a/src/song_print.h
+++ b/src/song_print.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -24,12 +24,13 @@ struct client;
struct song;
struct songvec;
-int
+void
song_print_info(struct client *client, struct song *song);
-int songvec_print(struct client *client, const struct songvec *sv);
+void
+songvec_print(struct client *client, const struct songvec *sv);
void
-song_print_url(struct client *client, struct song *song);
+song_print_uri(struct client *client, struct song *song);
#endif
diff --git a/src/song_save.c b/src/song_save.c
index 2d6297f3e..a1a573298 100644
--- a/src/song_save.c
+++ b/src/song_save.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -17,11 +17,13 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "song_save.h"
#include "song.h"
#include "tag_save.h"
#include "directory.h"
#include "tag.h"
+#include "text_file.h"
#include <glib.h>
@@ -30,136 +32,107 @@
#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "song"
-#define SONG_KEY "key: "
-#define SONG_MTIME "mtime: "
+#define SONG_MTIME "mtime"
+#define SONG_END "song_end"
-static void
-song_save_url(FILE *fp, struct song *song)
+static GQuark
+song_save_quark(void)
{
- if (song->parent != NULL && song->parent->path != NULL)
- fprintf(fp, SONG_FILE "%s/%s\n",
- directory_get_path(song->parent), song->url);
- else
- fprintf(fp, SONG_FILE "%s\n",
- song->url);
+ return g_quark_from_static_string("song_save");
}
-static int
-song_save(struct song *song, void *data)
+void
+song_save(FILE *fp, const struct song *song)
{
- FILE *fp = data;
-
- fprintf(fp, SONG_KEY "%s\n", song->url);
+ fprintf(fp, SONG_BEGIN "%s\n", song->uri);
- song_save_url(fp, song);
+ if (song->end_ms > 0)
+ fprintf(fp, "Range: %u-%u\n", song->start_ms, song->end_ms);
+ else if (song->start_ms > 0)
+ fprintf(fp, "Range: %u-\n", song->start_ms);
if (song->tag != NULL)
tag_save(fp, song->tag);
- fprintf(fp, SONG_MTIME "%li\n", (long)song->mtime);
-
- return 0;
+ fprintf(fp, SONG_MTIME ": %li\n", (long)song->mtime);
+ fprintf(fp, SONG_END "\n");
}
-void songvec_save(FILE *fp, struct songvec *sv)
+static int
+song_save_callback(struct song *song, void *data)
{
- fprintf(fp, "%s\n", SONG_BEGIN);
- songvec_for_each(sv, song_save, fp);
- fprintf(fp, "%s\n", SONG_END);
+ FILE *fp = data;
+ song_save(fp, song);
+ return 0;
}
-static void
-insertSongIntoList(struct songvec *sv, struct song *newsong)
+void songvec_save(FILE *fp, const struct songvec *sv)
{
- struct song *existing = songvec_find(sv, newsong->url);
-
- if (!existing) {
- songvec_add(sv, newsong);
- if (newsong->tag)
- tag_end_add(newsong->tag);
- } else { /* prevent dupes, just update the existing song info */
- if (existing->mtime != newsong->mtime) {
- if (existing->tag != NULL)
- tag_free(existing->tag);
- if (newsong->tag)
- tag_end_add(newsong->tag);
- existing->tag = newsong->tag;
- existing->mtime = newsong->mtime;
- newsong->tag = NULL;
- }
- song_free(newsong);
- }
+ songvec_for_each(sv, song_save_callback, fp);
}
-static char *
-matchesAnMpdTagItemKey(char *buffer, enum tag_type *itemType)
+struct song *
+song_load(FILE *fp, struct directory *parent, const char *uri,
+ GString *buffer, GError **error_r)
{
- int i;
-
- for (i = 0; i < TAG_NUM_OF_ITEM_TYPES; i++) {
- size_t len = strlen(tag_item_names[i]);
+ struct song *song = parent != NULL
+ ? song_file_new(uri, parent)
+ : song_remote_new(uri);
+ char *line, *colon;
+ enum tag_type type;
+ const char *value;
- if (0 == strncmp(tag_item_names[i], buffer, len) &&
- buffer[len] == ':') {
- *itemType = i;
- return g_strchug(buffer + len + 1);
+ while ((line = read_text_line(fp, buffer)) != NULL &&
+ strcmp(line, SONG_END) != 0) {
+ colon = strchr(line, ':');
+ if (colon == NULL || colon == line) {
+ if (song->tag != NULL)
+ tag_end_add(song->tag);
+ song_free(song);
+
+ g_set_error(error_r, song_save_quark(), 0,
+ "unknown line in db: %s", line);
+ return NULL;
}
- }
- return NULL;
-}
+ *colon++ = 0;
+ value = g_strchug(colon);
-void readSongInfoIntoList(FILE *fp, struct songvec *sv,
- struct directory *parent)
-{
- enum {
- buffer_size = 32768,
- };
- char *buffer = g_malloc(buffer_size);
- struct song *song = NULL;
- enum tag_type itemType;
- const char *value;
-
- while (fgets(buffer, buffer_size, fp) &&
- !g_str_has_prefix(buffer, SONG_END)) {
- g_strchomp(buffer);
-
- if (0 == strncmp(SONG_KEY, buffer, strlen(SONG_KEY))) {
- if (song)
- insertSongIntoList(sv, song);
-
- song = song_file_new(buffer + strlen(SONG_KEY),
- parent);
- } else if (*buffer == 0) {
- /* ignore empty lines (starting with '\0') */
- } else if (song == NULL) {
- g_error("Problems reading song info");
- } else if (0 == strncmp(SONG_FILE, buffer, strlen(SONG_FILE))) {
- /* we don't need this info anymore */
- } else if ((value = matchesAnMpdTagItemKey(buffer,
- &itemType)) != NULL) {
+ if ((type = tag_name_parse(line)) != TAG_NUM_OF_ITEM_TYPES) {
if (!song->tag) {
song->tag = tag_new();
tag_begin_add(song->tag);
}
- tag_add_item(song->tag, itemType, value);
- } else if (0 == strncmp(SONG_TIME, buffer, strlen(SONG_TIME))) {
+ tag_add_item(song->tag, type, value);
+ } else if (strcmp(line, "Time") == 0) {
if (!song->tag) {
song->tag = tag_new();
tag_begin_add(song->tag);
}
- song->tag->time = atoi(&(buffer[strlen(SONG_TIME)]));
- } else if (0 == strncmp(SONG_MTIME, buffer, strlen(SONG_MTIME))) {
- song->mtime = atoi(&(buffer[strlen(SONG_MTIME)]));
+ song->tag->time = atoi(value);
+ } else if (strcmp(line, SONG_MTIME) == 0) {
+ song->mtime = atoi(value);
+ } else if (strcmp(line, "Range") == 0) {
+ char *endptr;
+
+ song->start_ms = strtoul(value, &endptr, 10);
+ if (*endptr == '-')
+ song->end_ms = strtoul(endptr + 1, NULL, 10);
+ } else {
+ if (song->tag != NULL)
+ tag_end_add(song->tag);
+ song_free(song);
+
+ g_set_error(error_r, song_save_quark(), 0,
+ "unknown line in db: %s", line);
+ return NULL;
}
- else
- g_error("unknown line in db: %s", buffer);
}
- g_free(buffer);
+ if (song->tag != NULL)
+ tag_end_add(song->tag);
- if (song)
- insertSongIntoList(sv, song);
+ return song;
}
diff --git a/src/song_save.h b/src/song_save.h
index 370e42730..03285015a 100644
--- a/src/song_save.h
+++ b/src/song_save.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -20,14 +20,32 @@
#ifndef MPD_SONG_SAVE_H
#define MPD_SONG_SAVE_H
+#include <glib.h>
+
#include <stdio.h>
+#define SONG_BEGIN "song_begin: "
+
+struct song;
struct songvec;
struct directory;
-void songvec_save(FILE *fp, struct songvec *sv);
+void
+song_save(FILE *fp, const struct song *song);
+
+void
+songvec_save(FILE *fp, const struct songvec *sv);
-void readSongInfoIntoList(FILE * fp, struct songvec *sv,
- struct directory *parent);
+/**
+ * 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
+ * ignore errors
+ * @return true on success, false on error
+ */
+struct song *
+song_load(FILE *fp, struct directory *parent, const char *uri,
+ GString *buffer, GError **error_r);
#endif
diff --git a/src/song_sticker.c b/src/song_sticker.c
index 2758ff534..c3c64c8d1 100644
--- a/src/song_sticker.c
+++ b/src/song_sticker.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -17,6 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "song_sticker.h"
#include "song.h"
#include "directory.h"
diff --git a/src/song_sticker.h b/src/song_sticker.h
index 9652052e0..6318ccf48 100644
--- a/src/song_sticker.h
+++ b/src/song_sticker.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
diff --git a/src/song_update.c b/src/song_update.c
new file mode 100644
index 000000000..b418b600e
--- /dev/null
+++ b/src/song_update.c
@@ -0,0 +1,198 @@
+/*
+ * 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" /* must be first for large file support */
+#include "song.h"
+#include "uri.h"
+#include "directory.h"
+#include "mapper.h"
+#include "decoder_list.h"
+#include "decoder_plugin.h"
+#include "tag_ape.h"
+#include "tag_id3.h"
+#include "tag.h"
+#include "input_stream.h"
+
+#include <glib.h>
+
+#include <assert.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <stdio.h>
+
+struct song *
+song_file_load(const char *path, struct directory *parent)
+{
+ struct song *song;
+ bool ret;
+
+ assert((parent == NULL) == g_path_is_absolute(path));
+ assert(!uri_has_scheme(path));
+ assert(strchr(path, '\n') == NULL);
+
+ song = song_file_new(path, parent);
+
+ //in archive ?
+ if (parent != NULL && parent->device == DEVICE_INARCHIVE) {
+ ret = song_file_update_inarchive(song);
+ } else {
+ ret = song_file_update(song);
+ }
+ if (!ret) {
+ song_free(song);
+ return NULL;
+ }
+
+ return song;
+}
+
+/**
+ * Attempts to load APE or ID3 tags from the specified file.
+ */
+static struct tag *
+tag_load_fallback(const char *path)
+{
+ 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;
+}
+
+bool
+song_file_update(struct song *song)
+{
+ const char *suffix;
+ char *path_fs;
+ const struct decoder_plugin *plugin;
+ struct stat st;
+ struct input_stream *is = NULL;
+
+ assert(song_is_file(song));
+
+ /* check if there's a suffix and a plugin */
+
+ suffix = uri_get_suffix(song->uri);
+ if (suffix == NULL)
+ return false;
+
+ plugin = decoder_plugin_from_suffix(suffix, NULL);
+ if (plugin == NULL)
+ return false;
+
+ path_fs = map_song_fs(song);
+ if (path_fs == NULL)
+ return false;
+
+ if (song->tag != NULL) {
+ tag_free(song->tag);
+ song->tag = NULL;
+ }
+
+ if (stat(path_fs, &st) < 0 || !S_ISREG(st.st_mode)) {
+ g_free(path_fs);
+ return false;
+ }
+
+ song->mtime = st.st_mtime;
+
+ do {
+ /* load file tag */
+ song->tag = decoder_plugin_tag_dup(plugin, path_fs);
+ if (song->tag != NULL)
+ break;
+
+ /* fall back to stream tag */
+ if (plugin->stream_tag != NULL) {
+ /* open the input_stream (if not already
+ open) */
+ if (is == NULL)
+ is = input_stream_open(path_fs, NULL);
+
+ /* now try the stream_tag() method */
+ if (is != NULL) {
+ song->tag = decoder_plugin_stream_tag(plugin,
+ is);
+ if (song->tag != NULL)
+ break;
+
+ input_stream_seek(is, 0, SEEK_SET, NULL);
+ }
+ }
+
+ plugin = decoder_plugin_from_suffix(suffix, plugin);
+ } while (plugin != NULL);
+
+ if (is != NULL)
+ input_stream_close(is);
+
+ if (song->tag != NULL && tag_is_empty(song->tag))
+ song->tag = tag_fallback(path_fs, song->tag);
+
+ g_free(path_fs);
+ return song->tag != NULL;
+}
+
+bool
+song_file_update_inarchive(struct song *song)
+{
+ const char *suffix;
+ const struct decoder_plugin *plugin;
+
+ assert(song_is_file(song));
+
+ /* check if there's a suffix and a plugin */
+
+ suffix = uri_get_suffix(song->uri);
+ if (suffix == NULL)
+ return false;
+
+ plugin = decoder_plugin_from_suffix(suffix, false);
+ if (plugin == NULL)
+ return false;
+
+ if (song->tag != NULL)
+ tag_free(song->tag);
+
+ //accept every file that has music suffix
+ //because we dont support tag reading throught
+ //input streams
+ song->tag = tag_new();
+
+ return true;
+}
diff --git a/src/songvec.c b/src/songvec.c
index efef02216..38bcbac88 100644
--- a/src/songvec.c
+++ b/src/songvec.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -17,6 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "songvec.h"
#include "song.h"
#include "tag.h"
@@ -29,6 +30,38 @@
static GMutex *nr_lock = NULL;
+static const char *
+tag_get_value_checked(const struct tag *tag, enum tag_type type)
+{
+ return tag != NULL
+ ? tag_get_value(tag, type)
+ : NULL;
+}
+
+static int
+compare_utf8_string(const char *a, const char *b)
+{
+ if (a == NULL)
+ return b == NULL ? 0 : -1;
+
+ if (b == NULL)
+ return 1;
+
+ return g_utf8_collate(a, b);
+}
+
+/**
+ * Compare two string tag values, ignoring case. Either one may be
+ * NULL.
+ */
+static int
+compare_string_tag_item(const struct tag *a, const struct tag *b,
+ enum tag_type type)
+{
+ return compare_utf8_string(tag_get_value_checked(a, type),
+ tag_get_value_checked(b, type));
+}
+
/**
* Compare two tag values which should contain an integer value
* (e.g. disc or track number). Either one may be NULL.
@@ -51,14 +84,8 @@ compare_number_string(const char *a, const char *b)
static int
compare_tag_item(const struct tag *a, const struct tag *b, enum tag_type type)
{
- if (a == NULL)
- return b == NULL ? 0 : -1;
-
- if (b == NULL)
- return 1;
-
- return compare_number_string(tag_get_value(a, type),
- tag_get_value(b, type));
+ return compare_number_string(tag_get_value_checked(a, type),
+ tag_get_value_checked(b, type));
}
/* Only used for sorting/searchin a songvec, not general purpose compares */
@@ -68,18 +95,23 @@ static int songvec_cmp(const void *s1, const void *s2)
const struct song *b = ((const struct song * const *)s2)[0];
int ret;
- /* first sort by disc */
- ret = compare_tag_item(a->tag, b->tag, TAG_ITEM_DISC);
+ /* first sort by album */
+ ret = compare_string_tag_item(a->tag, b->tag, TAG_ALBUM);
+ if (ret != 0)
+ return ret;
+
+ /* then sort by disc */
+ ret = compare_tag_item(a->tag, b->tag, TAG_DISC);
if (ret != 0)
return ret;
/* then by track number */
- ret = compare_tag_item(a->tag, b->tag, TAG_ITEM_TRACK);
+ ret = compare_tag_item(a->tag, b->tag, TAG_TRACK);
if (ret != 0)
return ret;
/* still no difference? compare file name */
- return g_utf8_collate(a->url, b->url);
+ return g_utf8_collate(a->uri, b->uri);
}
static size_t sv_size(const struct songvec *sv)
@@ -108,14 +140,14 @@ void songvec_sort(struct songvec *sv)
}
struct song *
-songvec_find(const struct songvec *sv, const char *url)
+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]->url, url))
+ if (strcmp(sv->base[i]->uri, uri))
continue;
ret = sv->base[i];
break;
@@ -182,7 +214,7 @@ songvec_for_each(const struct songvec *sv,
struct song *song = sv->base[i];
assert(song);
- assert(*song->url);
+ assert(*song->uri);
prev_nr = sv->nr;
g_mutex_unlock(nr_lock); /* fn() may block */
diff --git a/src/songvec.h b/src/songvec.h
index 0fd207ed0..8a50b974b 100644
--- a/src/songvec.h
+++ b/src/songvec.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -34,7 +34,7 @@ void songvec_deinit(void);
void songvec_sort(struct songvec *sv);
struct song *
-songvec_find(const struct songvec *sv, const char *url);
+songvec_find(const struct songvec *sv, const char *uri);
int
songvec_delete(struct songvec *sv, const struct song *del);
diff --git a/src/state_file.c b/src/state_file.c
index 9c6475cc8..55af25d5c 100644
--- a/src/state_file.c
+++ b/src/state_file.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -17,10 +17,14 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "state_file.h"
#include "output_state.h"
#include "playlist.h"
+#include "playlist_state.h"
#include "volume.h"
+#include "text_file.h"
+#include "glib_compat.h"
#include <glib.h>
#include <assert.h>
@@ -30,28 +34,26 @@
#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "state_file"
-static struct _sf_cb {
- void (*reader)(FILE *);
- void (*writer)(FILE *);
-} sf_callbacks [] = {
- { read_sw_volume_state, save_sw_volume_state },
- { readAudioDevicesState, saveAudioDevicesState },
- { readPlaylistState, savePlaylistState },
-};
-
static char *state_file_path;
/** the GLib source id for the save timer */
static guint save_state_source_id;
+/**
+ * These version numbers determine whether we need to save the state
+ * file. If nothing has changed, we won't let the hard drive spin up.
+ */
+static unsigned prev_volume_version, prev_output_version,
+ prev_playlist_version;
+
static void
state_file_write(void)
{
- unsigned int i;
FILE *fp;
- if (state_file_path == NULL)
- return;
+ assert(state_file_path != NULL);
+
+ g_debug("Saving state file %s", state_file_path);
fp = fopen(state_file_path, "w");
if (G_UNLIKELY(!fp)) {
@@ -60,21 +62,26 @@ state_file_write(void)
return;
}
- for (i = 0; i < G_N_ELEMENTS(sf_callbacks); i++)
- sf_callbacks[i].writer(fp);
+ save_sw_volume_state(fp);
+ audio_output_state_save(fp);
+ playlist_state_save(fp, &g_playlist);
+
+ fclose(fp);
- while(fclose(fp) && errno == EINTR) /* nothing */;
+ 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);
}
static void
state_file_read(void)
{
- unsigned int i;
FILE *fp;
+ bool success;
assert(state_file_path != NULL);
- g_debug("Saving state file");
+ g_debug("Loading state file %s", state_file_path);
fp = fopen(state_file_path, "r");
if (G_UNLIKELY(!fp)) {
@@ -82,12 +89,25 @@ state_file_read(void)
state_file_path, strerror(errno));
return;
}
- for (i = 0; i < G_N_ELEMENTS(sf_callbacks); i++) {
- sf_callbacks[i].reader(fp);
- rewind(fp);
+
+ GString *buffer = g_string_sized_new(1024);
+ const char *line;
+ 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);
+ if (!success)
+ g_warning("Unrecognized line in state file: %s", line);
}
- while(fclose(fp) && errno == EINTR) /* nothing */;
+ 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);
+
+
+ g_string_free(buffer, true);
}
/**
@@ -97,6 +117,13 @@ state_file_read(void)
static gboolean
timer_save_state_file(G_GNUC_UNUSED gpointer 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))
+ /* nothing has changed - don't save the state file,
+ don't spin up the hard disk */
+ return true;
+
state_file_write();
return true;
}
@@ -112,18 +139,22 @@ state_file_init(const char *path)
state_file_path = g_strdup(path);
state_file_read();
- save_state_source_id = g_timeout_add(5 * 60 * 1000,
- timer_save_state_file, NULL);
+ save_state_source_id = g_timeout_add_seconds(5 * 60,
+ timer_save_state_file,
+ NULL);
}
void
state_file_finish(void)
{
+ if (state_file_path == NULL)
+ /* no state file configured, no cleanup required */
+ return;
+
if (save_state_source_id != 0)
g_source_remove(save_state_source_id);
- if (state_file_path != NULL)
- state_file_write();
+ state_file_write();
g_free(state_file_path);
}
diff --git a/src/state_file.h b/src/state_file.h
index d1e53d005..ec01fcbed 100644
--- a/src/state_file.h
+++ b/src/state_file.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
diff --git a/src/stats.c b/src/stats.c
index 01f6761f3..673d531ec 100644
--- a/src/stats.c
+++ b/src/stats.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -17,6 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "stats.h"
#include "database.h"
#include "tag.h"
@@ -52,11 +53,11 @@ visit_tag(struct visit_data *data, const struct tag *tag)
const struct tag_item *item = tag->items[i];
switch (item->type) {
- case TAG_ITEM_ARTIST:
+ case TAG_ARTIST:
strset_add(data->artists, item->value);
break;
- case TAG_ITEM_ALBUM:
+ case TAG_ALBUM:
strset_add(data->albums, item->value);
break;
@@ -113,7 +114,7 @@ int stats_print(struct client *client)
stats.album_count,
stats.song_count,
(long)g_timer_elapsed(stats.timer, NULL),
- (long)(getPlayerTotalPlayTime() + 0.5),
+ (long)(pc_get_total_play_time() + 0.5),
stats.song_duration,
db_get_mtime());
return 0;
diff --git a/src/stats.h b/src/stats.h
index ee1a3d5d6..fbb2e4a46 100644
--- a/src/stats.h
+++ b/src/stats.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
diff --git a/src/sticker.c b/src/sticker.c
index cded09fca..c59cdd078 100644
--- a/src/sticker.c
+++ b/src/sticker.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -17,6 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "sticker.h"
#include "idle.h"
@@ -76,50 +77,69 @@ static const char sticker_sql_create[] =
static sqlite3 *sticker_db;
static sqlite3_stmt *sticker_stmt[G_N_ELEMENTS(sticker_sql)];
+static GQuark
+sticker_quark(void)
+{
+ return g_quark_from_static_string("sticker");
+}
+
static sqlite3_stmt *
-sticker_prepare(const char *sql)
+sticker_prepare(const char *sql, GError **error_r)
{
int ret;
sqlite3_stmt *stmt;
ret = sqlite3_prepare_v2(sticker_db, sql, -1, &stmt, NULL);
- if (ret != SQLITE_OK)
- g_error("sqlite3_prepare_v2() failed: %s",
- sqlite3_errmsg(sticker_db));
+ if (ret != SQLITE_OK) {
+ g_set_error(error_r, sticker_quark(), ret,
+ "sqlite3_prepare_v2() failed: %s",
+ sqlite3_errmsg(sticker_db));
+ return NULL;
+ }
return stmt;
}
-void
-sticker_global_init(const char *path)
+bool
+sticker_global_init(const char *path, GError **error_r)
{
int ret;
if (path == NULL)
/* not configured */
- return;
+ return true;
/* open/create the sqlite database */
ret = sqlite3_open(path, &sticker_db);
- if (ret != SQLITE_OK)
- g_error("Failed to open sqlite database '%s': %s",
- path, sqlite3_errmsg(sticker_db));
+ if (ret != SQLITE_OK) {
+ g_set_error(error_r, sticker_quark(), ret,
+ "Failed to open sqlite database '%s': %s",
+ path, sqlite3_errmsg(sticker_db));
+ return false;
+ }
/* create the table and index */
ret = sqlite3_exec(sticker_db, sticker_sql_create, NULL, NULL, NULL);
- if (ret != SQLITE_OK)
- g_error("Failed to create sticker table: %s",
- sqlite3_errmsg(sticker_db));
+ if (ret != SQLITE_OK) {
+ g_set_error(error_r, sticker_quark(), ret,
+ "Failed to create sticker table: %s",
+ sqlite3_errmsg(sticker_db));
+ return false;
+ }
/* prepare the statements we're going to use */
for (unsigned i = 0; i < G_N_ELEMENTS(sticker_sql); ++i) {
assert(sticker_sql[i] != NULL);
- sticker_stmt[i] = sticker_prepare(sticker_sql[i]);
+ sticker_stmt[i] = sticker_prepare(sticker_sql[i], error_r);
+ if (sticker_stmt[i] == NULL)
+ return false;
}
+
+ return true;
}
void
diff --git a/src/sticker.h b/src/sticker.h
index 8e6410914..6cc0ebcee 100644
--- a/src/sticker.h
+++ b/src/sticker.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -50,9 +50,13 @@ struct sticker;
/**
* Opens the sticker database (if path is not NULL).
+ *
+ * @param error_r location to store the error occuring, or NULL to
+ * ignore errors
+ * @return true on success, false on error
*/
-void
-sticker_global_init(const char *path);
+bool
+sticker_global_init(const char *path, GError **error_r);
/**
* Close the sticker database.
diff --git a/src/sticker_print.c b/src/sticker_print.c
index 12dafd3f7..b158c8af3 100644
--- a/src/sticker_print.c
+++ b/src/sticker_print.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -17,6 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "sticker_print.h"
#include "sticker.h"
#include "client.h"
diff --git a/src/sticker_print.h b/src/sticker_print.h
index 25f0deae2..ac542709c 100644
--- a/src/sticker_print.h
+++ b/src/sticker_print.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
diff --git a/src/stored_playlist.c b/src/stored_playlist.c
index 5ed7182f6..cd2818522 100644
--- a/src/stored_playlist.c
+++ b/src/stored_playlist.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -17,6 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "stored_playlist.h"
#include "playlist_save.h"
#include "song.h"
@@ -159,7 +160,7 @@ spl_save(GPtrArray *list, const char *utf8path)
if (path_fs == NULL)
return PLAYLIST_RESULT_BAD_NAME;
- while (!(file = fopen(path_fs, "w")) && errno == EINTR);
+ file = fopen(path_fs, "w");
g_free(path_fs);
if (file == NULL)
return PLAYLIST_RESULT_ERRNO;
@@ -169,7 +170,7 @@ spl_save(GPtrArray *list, const char *utf8path)
playlist_print_uri(file, uri);
}
- while (fclose(file) != 0 && errno == EINTR);
+ fclose(file);
return PLAYLIST_RESULT_SUCCESS;
}
@@ -188,7 +189,7 @@ spl_load(const char *utf8path)
if (path_fs == NULL)
return NULL;
- while (!(file = fopen(path_fs, "r")) && errno == EINTR);
+ file = fopen(path_fs, "r");
g_free(path_fs);
if (file == NULL)
return NULL;
@@ -226,7 +227,7 @@ spl_load(const char *utf8path)
break;
}
- while (fclose(file) && errno == EINTR);
+ fclose(file);
return list;
}
@@ -312,12 +313,12 @@ spl_clear(const char *utf8path)
if (path_fs == NULL)
return PLAYLIST_RESULT_BAD_NAME;
- while (!(file = fopen(path_fs, "w")) && errno == EINTR);
+ file = fopen(path_fs, "w");
g_free(path_fs);
if (file == NULL)
return PLAYLIST_RESULT_ERRNO;
- while (fclose(file) != 0 && errno == EINTR);
+ fclose(file);
idle_add(IDLE_STORED_PLAYLIST);
return PLAYLIST_RESULT_SUCCESS;
@@ -392,26 +393,26 @@ spl_append_song(const char *utf8path, struct song *song)
if (path_fs == NULL)
return PLAYLIST_RESULT_BAD_NAME;
- while (!(file = fopen(path_fs, "a")) && errno == EINTR);
+ file = fopen(path_fs, "a");
g_free(path_fs);
if (file == NULL)
return PLAYLIST_RESULT_ERRNO;
if (fstat(fileno(file), &st) < 0) {
int save_errno = errno;
- while (fclose(file) != 0 && errno == EINTR);
+ fclose(file);
errno = save_errno;
return PLAYLIST_RESULT_ERRNO;
}
if (st.st_size / (MPD_PATH_MAX + 1) >= (off_t)playlist_max_length) {
- while (fclose(file) != 0 && errno == EINTR);
+ fclose(file);
return PLAYLIST_RESULT_TOO_LARGE;
}
playlist_print_song(file, song);
- while (fclose(file) != 0 && errno == EINTR);
+ fclose(file);
idle_add(IDLE_STORED_PLAYLIST);
return PLAYLIST_RESULT_SUCCESS;
diff --git a/src/stored_playlist.h b/src/stored_playlist.h
index e78ce293b..3afdbb0f0 100644
--- a/src/stored_playlist.h
+++ b/src/stored_playlist.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
diff --git a/src/strset.c b/src/strset.c
index 474dd6642..e071fbc98 100644
--- a/src/strset.c
+++ b/src/strset.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -17,6 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "strset.h"
#include <assert.h>
diff --git a/src/strset.h b/src/strset.h
index 7b0c38c1a..9a7aa45e5 100644
--- a/src/strset.h
+++ b/src/strset.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
diff --git a/src/tag.c b/src/tag.c
index b228480c8..6ce46a831 100644
--- a/src/tag.c
+++ b/src/tag.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -17,11 +17,13 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "tag.h"
#include "tag_internal.h"
#include "tag_pool.h"
#include "conf.h"
#include "song.h"
+#include "mpd_error.h"
#include <glib.h>
#include <assert.h>
@@ -42,18 +44,20 @@ static struct {
} bulk;
const char *tag_item_names[TAG_NUM_OF_ITEM_TYPES] = {
- "Artist",
- "Album",
- "AlbumArtist",
- "Title",
- "Track",
- "Name",
- "Genre",
- "Date",
- "Composer",
- "Performer",
- "Comment",
- "Disc",
+ [TAG_ARTIST] = "Artist",
+ [TAG_ARTIST_SORT] = "ArtistSort",
+ [TAG_ALBUM] = "Album",
+ [TAG_ALBUM_ARTIST] = "AlbumArtist",
+ [TAG_ALBUM_ARTIST_SORT] = "AlbumArtistSort",
+ [TAG_TITLE] = "Title",
+ [TAG_TRACK] = "Track",
+ [TAG_NAME] = "Name",
+ [TAG_GENRE] = "Genre",
+ [TAG_DATE] = "Date",
+ [TAG_COMPOSER] = "Composer",
+ [TAG_PERFORMER] = "Performer",
+ [TAG_COMMENT] = "Comment",
+ [TAG_DISC] = "Disc",
/* MusicBrainz tags from http://musicbrainz.org/doc/MusicBrainzTag */
[TAG_MUSICBRAINZ_ARTISTID] = "MUSICBRAINZ_ARTISTID",
@@ -111,7 +115,7 @@ void tag_lib_init(void)
/* parse the "metadata_to_use" config parameter below */
/* ignore comments by default */
- ignore_tag_items[TAG_ITEM_COMMENT] = true;
+ ignore_tag_items[TAG_COMMENT] = true;
value = config_get_string(CONF_METADATA_TO_USE, NULL);
if (value == NULL)
@@ -135,8 +139,8 @@ void tag_lib_init(void)
type = tag_name_parse_i(c);
if (type == TAG_NUM_OF_ITEM_TYPES)
- g_error("error parsing metadata item \"%s\"",
- c);
+ MPD_ERROR("error parsing metadata item \"%s\"",
+ c);
ignore_tag_items[type] = false;
@@ -169,7 +173,7 @@ static void tag_delete_item(struct tag *tag, unsigned idx)
if (tag->num_items - idx > 0) {
memmove(tag->items + idx, tag->items + idx + 1,
- tag->num_items - idx);
+ (tag->num_items - idx) * sizeof(tag->items[0]));
}
if (tag->num_items > 0) {
diff --git a/src/tag.h b/src/tag.h
index 8d968c254..6931453f7 100644
--- a/src/tag.h
+++ b/src/tag.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -31,18 +31,20 @@
* Codes for the type of a tag item.
*/
enum tag_type {
- TAG_ITEM_ARTIST,
- TAG_ITEM_ALBUM,
- TAG_ITEM_ALBUM_ARTIST,
- TAG_ITEM_TITLE,
- TAG_ITEM_TRACK,
- TAG_ITEM_NAME,
- TAG_ITEM_GENRE,
- TAG_ITEM_DATE,
- TAG_ITEM_COMPOSER,
- TAG_ITEM_PERFORMER,
- TAG_ITEM_COMMENT,
- TAG_ITEM_DISC,
+ TAG_ARTIST,
+ TAG_ARTIST_SORT,
+ TAG_ALBUM,
+ TAG_ALBUM_ARTIST,
+ TAG_ALBUM_ARTIST_SORT,
+ TAG_TITLE,
+ TAG_TRACK,
+ TAG_NAME,
+ TAG_GENRE,
+ TAG_DATE,
+ TAG_COMPOSER,
+ TAG_PERFORMER,
+ TAG_COMMENT,
+ TAG_DISC,
TAG_MUSICBRAINZ_ARTISTID,
TAG_MUSICBRAINZ_ALBUMID,
diff --git a/src/tag_ape.c b/src/tag_ape.c
index babefcda3..79facba1b 100644
--- a/src/tag_ape.c
+++ b/src/tag_ape.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -17,18 +17,15 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "tag_ape.h"
#include "tag.h"
#include "tag_table.h"
-
-#include <glib.h>
-
-#include <assert.h>
-#include <stdio.h>
+#include "ape.h"
static const char *const ape_tag_names[TAG_NUM_OF_ITEM_TYPES] = {
- [TAG_ITEM_ALBUM_ARTIST] = "album artist",
- [TAG_ITEM_DATE] = "year"
+ [TAG_ALBUM_ARTIST] = "album artist",
+ [TAG_DATE] = "year",
};
static enum tag_type
@@ -55,101 +52,45 @@ tag_ape_import_item(struct tag *tag, unsigned long flags,
if (tag == NULL)
tag = tag_new();
- tag_add_item_n(tag, type, value, value_length);
+
+ const char *end = value + value_length;
+ while (true) {
+ /* multiple values are separated by null bytes */
+ const char *n = memchr(value, 0, end - value);
+ if (n != NULL) {
+ if (n > value)
+ tag_add_item_n(tag, type, value, n - value);
+ value = n + 1;
+ } else {
+ if (end > value)
+ tag_add_item_n(tag, type, value, end - value);
+ break;
+ }
+ }
return tag;
}
-struct tag *
-tag_ape_load(const char *file)
-{
- struct tag *ret = NULL;
- FILE *fp;
- int tagCount;
- char *buffer = NULL;
- char *p;
- size_t tagLen;
- size_t size;
- unsigned long flags;
- char *key;
-
- struct {
- unsigned char id[8];
- uint32_t version;
- uint32_t length;
- uint32_t tagCount;
- unsigned char flags[4];
- unsigned char reserved[8];
- } footer;
-
- fp = fopen(file, "r");
- if (!fp)
- return NULL;
-
- /* determine if file has an apeV2 tag */
- if (fseek(fp, 0, SEEK_END))
- goto fail;
- size = (size_t)ftell(fp);
- if (fseek(fp, size - sizeof(footer), SEEK_SET))
- goto fail;
- if (fread(&footer, 1, sizeof(footer), fp) != sizeof(footer))
- goto fail;
- if (memcmp(footer.id, "APETAGEX", sizeof(footer.id)) != 0)
- goto fail;
- if (GUINT32_FROM_LE(footer.version) != 2000)
- goto fail;
-
- /* find beginning of ape tag */
- tagLen = GUINT32_FROM_LE(footer.length);
- if (tagLen <= sizeof(footer) + 10)
- goto fail;
- if (tagLen > 1024 * 1024)
- /* refuse to load more than one megabyte of tag data */
- goto fail;
- if (fseek(fp, size - tagLen, SEEK_SET))
- goto fail;
-
- /* read tag into buffer */
- tagLen -= sizeof(footer);
- assert(tagLen > 10);
-
- buffer = g_malloc(tagLen);
- if (fread(buffer, 1, tagLen, fp) != tagLen)
- goto fail;
-
- /* read tags */
- tagCount = GUINT32_FROM_LE(footer.tagCount);
- p = buffer;
- while (tagCount-- && tagLen > 10) {
- size = GUINT32_FROM_LE(*(const uint32_t *)p);
- p += 4;
- tagLen -= 4;
- flags = GUINT32_FROM_LE(*(const uint32_t *)p);
- p += 4;
- tagLen -= 4;
-
- /* get the key */
- key = p;
- while (tagLen > size && *p != '\0') {
- p++;
- tagLen--;
- }
- p++;
- tagLen--;
+struct tag_ape_ctx {
+ struct tag *tag;
+};
- /* get the value */
- if (tagLen < size)
- goto fail;
+static bool
+tag_ape_callback(unsigned long flags, const char *key,
+ const char *value, size_t value_length, void *_ctx)
+{
+ struct tag_ape_ctx *ctx = _ctx;
- ret = tag_ape_import_item(ret, flags, key, p, size);
+ ctx->tag = tag_ape_import_item(ctx->tag, flags, key,
+ value, value_length);
+ return true;
+}
- p += size;
- tagLen -= size;
- }
+struct tag *
+tag_ape_load(const char *file)
+{
+ struct tag_ape_ctx ctx = { .tag = NULL };
-fail:
- if (fp)
- fclose(fp);
- g_free(buffer);
- return ret;
+ tag_ape_scan(file, tag_ape_callback, &ctx);
+ return ctx.tag;
}
diff --git a/src/tag_ape.h b/src/tag_ape.h
index dd06d27cf..150659685 100644
--- a/src/tag_ape.h
+++ b/src/tag_ape.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
diff --git a/src/tag_id3.c b/src/tag_id3.c
index a33ebc00b..9c0a98d40 100644
--- a/src/tag_id3.c
+++ b/src/tag_id3.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -17,6 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "tag_id3.h"
#include "tag.h"
#include "riff.h"
@@ -34,25 +35,31 @@
#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "id3"
-# define isId3v1(tag) (id3_tag_options(tag, 0, 0) & ID3_TAG_OPTION_ID3V1)
# ifndef ID3_FRAME_COMPOSER
# define ID3_FRAME_COMPOSER "TCOM"
# endif
-# ifndef ID3_FRAME_PERFORMER
-# define ID3_FRAME_PERFORMER "TOPE"
-# endif
# ifndef ID3_FRAME_DISC
# define ID3_FRAME_DISC "TPOS"
# endif
+#ifndef ID3_FRAME_ARTIST_SORT
+#define ID3_FRAME_ARTIST_SORT "TSOP"
+#endif
+
#ifndef ID3_FRAME_ALBUM_ARTIST_SORT
-#define ID3_FRAME_ALBUM_ARTIST_SORT "TSO2"
+#define ID3_FRAME_ALBUM_ARTIST_SORT "TSO2" /* this one is unofficial, introduced by Itunes */
#endif
#ifndef ID3_FRAME_ALBUM_ARTIST
#define ID3_FRAME_ALBUM_ARTIST "TPE2"
#endif
+static inline bool
+tag_is_id3v1(struct id3_tag *tag)
+{
+ return (id3_tag_options(tag, 0, 0) & ID3_TAG_OPTION_ID3V1) != 0;
+}
+
static id3_utf8_t *
tag_id3_getstring(const struct id3_frame *frame, unsigned i)
{
@@ -72,14 +79,13 @@ tag_id3_getstring(const struct id3_frame *frame, unsigned i)
/* This will try to convert a string to utf-8,
*/
-static id3_utf8_t * processID3FieldString (int is_id3v1, const id3_ucs4_t *ucs4, int type)
+static id3_utf8_t *
+import_id3_string(bool is_id3v1, const id3_ucs4_t *ucs4)
{
id3_utf8_t *utf8, *utf8_stripped;
id3_latin1_t *isostr;
const char *encoding;
- if (type == TAG_ITEM_GENRE)
- ucs4 = id3_genre_name(ucs4);
/* use encoding field here? */
if (is_id3v1 &&
(encoding = config_get_string(CONF_ID3V1_ENCODING, NULL)) != NULL) {
@@ -112,118 +118,121 @@ static id3_utf8_t * processID3FieldString (int is_id3v1, const id3_ucs4_t *ucs4,
return utf8_stripped;
}
+/**
+ * Import a "Text information frame" (ID3v2.4.0 section 4.2). It
+ * contains 2 fields:
+ *
+ * - encoding
+ * - string list
+ */
static void
-getID3Info(struct id3_tag *tag, const char *id, int type, struct tag *mpdTag)
+tag_id3_import_text_frame(struct tag *dest, struct id3_tag *tag,
+ const struct id3_frame *frame,
+ enum tag_type type)
{
- struct id3_frame const *frame;
id3_ucs4_t const *ucs4;
id3_utf8_t *utf8;
union id3_field const *field;
unsigned int nstrings, i;
- frame = id3_tag_findframe(tag, id, 0);
- /* Check frame */
- if (!frame)
- {
+ if (frame->nfields != 2)
return;
- }
- /* Check fields in frame */
- if(frame->nfields == 0)
- {
- g_debug("Frame has no fields");
+
+ /* check the encoding field */
+
+ field = id3_frame_field(frame, 0);
+ if (field == NULL || field->type != ID3_FIELD_TYPE_TEXTENCODING)
return;
- }
- /* Starting with T is a stringlist */
- if (id[0] == 'T')
- {
- /* This one contains 2 fields:
- * 1st: Text encoding
- * 2: Stringlist
- * Shamefully this isn't the RL case.
- * But I am going to enforce it anyway.
- */
- if(frame->nfields != 2)
- {
- g_debug("Invalid number '%i' of fields for TXX frame",
- frame->nfields);
- return;
- }
- field = &frame->fields[0];
- /**
- * First field is encoding field.
- * This is ignored by mpd.
- */
- if(field->type != ID3_FIELD_TYPE_TEXTENCODING)
- {
- g_debug("Expected encoding, found: %i",
- field->type);
- }
- /* Process remaining fields, should be only one */
- field = &frame->fields[1];
- /* Encoding field */
- if(field->type == ID3_FIELD_TYPE_STRINGLIST) {
- /* Get the number of strings available */
- nstrings = id3_field_getnstrings(field);
- for (i = 0; i < nstrings; i++) {
- ucs4 = id3_field_getstrings(field,i);
- if(!ucs4)
- continue;
- utf8 = processID3FieldString(isId3v1(tag),ucs4, type);
- if(!utf8)
- continue;
-
- tag_add_item(mpdTag, type, (char *)utf8);
- g_free(utf8);
- }
- }
- else {
- g_warning("Field type not processed: %i",
- (int)id3_field_gettextencoding(field));
- }
- }
- /* A comment frame */
- else if(!strcmp(ID3_FRAME_COMMENT, id))
- {
- /* A comment frame is different... */
- /* 1st: encoding
- * 2nd: Language
- * 3rd: String
- * 4th: FullString.
- * The 'value' we want is in the 4th field
- */
- if(frame->nfields == 4)
- {
- /* for now I only read the 4th field, with the fullstring */
- field = &frame->fields[3];
- if(field->type == ID3_FIELD_TYPE_STRINGFULL)
- {
- ucs4 = id3_field_getfullstring(field);
- if(ucs4)
- {
- utf8 = processID3FieldString(isId3v1(tag),ucs4, type);
- if(utf8)
- {
- tag_add_item(mpdTag, type, (char *)utf8);
- g_free(utf8);
- }
- }
- }
- else
- {
- g_debug("4th field in comment frame differs from expected, got '%i': ignoring",
- field->type);
- }
- }
- else
- {
- g_debug("Invalid 'comments' tag, got '%i' fields instead of 4",
- frame->nfields);
- }
+ /* process the value(s) */
+
+ field = id3_frame_field(frame, 1);
+ if (field == NULL || field->type != ID3_FIELD_TYPE_STRINGLIST)
+ return;
+
+ /* Get the number of strings available */
+ nstrings = id3_field_getnstrings(field);
+ for (i = 0; i < nstrings; i++) {
+ ucs4 = id3_field_getstrings(field, i);
+ if (ucs4 == NULL)
+ continue;
+
+ if (type == TAG_GENRE)
+ ucs4 = id3_genre_name(ucs4);
+
+ utf8 = import_id3_string(tag_is_id3v1(tag), ucs4);
+ if (utf8 == NULL)
+ continue;
+
+ tag_add_item(dest, type, (char *)utf8);
+ g_free(utf8);
}
- /* Unsupported */
- else
- g_debug("Unsupported tag type requrested");
+}
+
+/**
+ * Import all text frames with the specified id (ID3v2.4.0 section
+ * 4.2). This is a wrapper for tag_id3_import_text_frame().
+ */
+static void
+tag_id3_import_text(struct tag *dest, struct id3_tag *tag, const char *id,
+ enum tag_type type)
+{
+ const struct id3_frame *frame;
+ for (unsigned i = 0;
+ (frame = id3_tag_findframe(tag, id, i)) != NULL; ++i)
+ tag_id3_import_text_frame(dest, tag, frame, type);
+}
+
+/**
+ * Import a "Comment frame" (ID3v2.4.0 section 4.10). It
+ * contains 4 fields:
+ *
+ * - encoding
+ * - language
+ * - string
+ * - 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)
+{
+ id3_ucs4_t const *ucs4;
+ id3_utf8_t *utf8;
+ union id3_field const *field;
+
+ if (frame->nfields != 4)
+ return;
+
+ /* for now I only read the 4th field, with the fullstring */
+ field = id3_frame_field(frame, 3);
+ if (field == NULL)
+ return;
+
+ ucs4 = id3_field_getfullstring(field);
+ if (ucs4 == NULL)
+ return;
+
+ utf8 = import_id3_string(tag_is_id3v1(tag), ucs4);
+ if (utf8 == NULL)
+ return;
+
+ tag_add_item(dest, type, (char *)utf8);
+ g_free(utf8);
+}
+
+/**
+ * Import all comment frames (ID3v2.4.0 section 4.10). This is a
+ * wrapper for tag_id3_import_comment_frame().
+ */
+static void
+tag_id3_import_comment(struct tag *dest, struct id3_tag *tag, const char *id,
+ enum tag_type type)
+{
+ const struct id3_frame *frame;
+ for (unsigned i = 0;
+ (frame = id3_tag_findframe(tag, id, i)) != NULL; ++i)
+ tag_id3_import_comment_frame(dest, tag, frame, type);
}
/**
@@ -237,6 +246,7 @@ tag_id3_parse_txxx_name(const char *name)
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,
@@ -328,20 +338,23 @@ struct tag *tag_id3_import(struct id3_tag * tag)
{
struct tag *ret = tag_new();
- getID3Info(tag, ID3_FRAME_ARTIST, TAG_ITEM_ARTIST, ret);
- getID3Info(tag, ID3_FRAME_ALBUM_ARTIST,
- TAG_ITEM_ALBUM_ARTIST, ret);
- getID3Info(tag, ID3_FRAME_ALBUM_ARTIST_SORT,
- TAG_ITEM_ALBUM_ARTIST, ret);
- getID3Info(tag, ID3_FRAME_TITLE, TAG_ITEM_TITLE, ret);
- getID3Info(tag, ID3_FRAME_ALBUM, TAG_ITEM_ALBUM, ret);
- getID3Info(tag, ID3_FRAME_TRACK, TAG_ITEM_TRACK, ret);
- getID3Info(tag, ID3_FRAME_YEAR, TAG_ITEM_DATE, ret);
- getID3Info(tag, ID3_FRAME_GENRE, TAG_ITEM_GENRE, ret);
- getID3Info(tag, ID3_FRAME_COMPOSER, TAG_ITEM_COMPOSER, ret);
- getID3Info(tag, ID3_FRAME_PERFORMER, TAG_ITEM_PERFORMER, ret);
- getID3Info(tag, ID3_FRAME_COMMENT, TAG_ITEM_COMMENT, ret);
- getID3Info(tag, ID3_FRAME_DISC, TAG_ITEM_DISC, ret);
+ 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);
@@ -354,69 +367,72 @@ struct tag *tag_id3_import(struct id3_tag * tag)
return ret;
}
-static int fillBuffer(void *buf, size_t size, FILE * stream,
- long offset, int whence)
+static int
+fill_buffer(void *buf, size_t size, FILE *stream, long offset, int whence)
{
if (fseek(stream, offset, whence) != 0) return 0;
return fread(buf, 1, size, stream);
}
-static int getId3v2FooterSize(FILE * stream, long offset, int whence)
+static int
+get_id3v2_footer_size(FILE *stream, long offset, int whence)
{
id3_byte_t buf[ID3_TAG_QUERYSIZE];
int bufsize;
- bufsize = fillBuffer(buf, ID3_TAG_QUERYSIZE, stream, offset, whence);
+ bufsize = fill_buffer(buf, ID3_TAG_QUERYSIZE, stream, offset, whence);
if (bufsize <= 0) return 0;
return id3_tag_query(buf, bufsize);
}
-static struct id3_tag *getId3Tag(FILE * stream, long offset, int whence)
+static struct id3_tag *
+tag_id3_read(FILE *stream, long offset, int whence)
{
struct id3_tag *tag;
- id3_byte_t queryBuf[ID3_TAG_QUERYSIZE];
- id3_byte_t *tagBuf;
- int tagSize;
- int queryBufSize;
- int tagBufSize;
+ id3_byte_t query_buffer[ID3_TAG_QUERYSIZE];
+ id3_byte_t *tag_buffer;
+ int tag_size;
+ int query_buffer_size;
+ int tag_buffer_size;
/* It's ok if we get less than we asked for */
- queryBufSize = fillBuffer(queryBuf, ID3_TAG_QUERYSIZE,
- stream, offset, whence);
- if (queryBufSize <= 0) return NULL;
+ query_buffer_size = fill_buffer(query_buffer, ID3_TAG_QUERYSIZE,
+ stream, offset, whence);
+ if (query_buffer_size <= 0) return NULL;
/* Look for a tag header */
- tagSize = id3_tag_query(queryBuf, queryBufSize);
- if (tagSize <= 0) return NULL;
+ tag_size = id3_tag_query(query_buffer, query_buffer_size);
+ if (tag_size <= 0) return NULL;
/* Found a tag. Allocate a buffer and read it in. */
- tagBuf = g_malloc(tagSize);
- if (!tagBuf) return NULL;
+ tag_buffer = g_malloc(tag_size);
+ if (!tag_buffer) return NULL;
- tagBufSize = fillBuffer(tagBuf, tagSize, stream, offset, whence);
- if (tagBufSize < tagSize) {
- g_free(tagBuf);
+ tag_buffer_size = fill_buffer(tag_buffer, tag_size, stream, offset, whence);
+ if (tag_buffer_size < tag_size) {
+ g_free(tag_buffer);
return NULL;
}
- tag = id3_tag_parse(tagBuf, tagBufSize);
+ tag = id3_tag_parse(tag_buffer, tag_buffer_size);
- g_free(tagBuf);
+ g_free(tag_buffer);
return tag;
}
-static struct id3_tag *findId3TagFromBeginning(FILE * stream)
+static struct id3_tag *
+tag_id3_find_from_beginning(FILE *stream)
{
struct id3_tag *tag;
struct id3_tag *seektag;
struct id3_frame *frame;
int seek;
- tag = getId3Tag(stream, 0, SEEK_SET);
+ tag = tag_id3_read(stream, 0, SEEK_SET);
if (!tag) {
return NULL;
- } else if (isId3v1(tag)) {
+ } else if (tag_is_id3v1(tag)) {
/* id3v1 tags don't belong here */
id3_tag_delete(tag);
return NULL;
@@ -430,8 +446,8 @@ static struct id3_tag *findId3TagFromBeginning(FILE * stream)
break;
/* Get the tag specified by the SEEK frame */
- seektag = getId3Tag(stream, seek, SEEK_CUR);
- if (!seektag || isId3v1(seektag))
+ seektag = tag_id3_read(stream, seek, SEEK_CUR);
+ if (!seektag || tag_is_id3v1(seektag))
break;
/* Replace the old tag with the new one */
@@ -442,22 +458,23 @@ static struct id3_tag *findId3TagFromBeginning(FILE * stream)
return tag;
}
-static struct id3_tag *findId3TagFromEnd(FILE * stream)
+static struct id3_tag *
+tag_id3_find_from_end(FILE *stream)
{
struct id3_tag *tag;
struct id3_tag *v1tag;
int tagsize;
/* Get an id3v1 tag from the end of file for later use */
- v1tag = getId3Tag(stream, -128, SEEK_END);
+ v1tag = tag_id3_read(stream, -128, SEEK_END);
/* Get the id3v2 tag size from the footer (located before v1tag) */
- tagsize = getId3v2FooterSize(stream, (v1tag ? -128 : 0) - 10, SEEK_END);
+ tagsize = get_id3v2_footer_size(stream, (v1tag ? -128 : 0) - 10, SEEK_END);
if (tagsize >= 0)
return v1tag;
/* Get the tag which the footer belongs to */
- tag = getId3Tag(stream, tagsize, SEEK_CUR);
+ tag = tag_id3_read(stream, tagsize, SEEK_CUR);
if (!tag)
return v1tag;
@@ -504,18 +521,18 @@ struct tag *tag_id3_load(const char *file)
struct id3_tag *tag;
FILE *stream;
- stream = fopen(file, "r");
+ stream = fopen(file, "rb");
if (!stream) {
g_debug("tag_id3_load: Failed to open file: '%s', %s",
file, strerror(errno));
return NULL;
}
- tag = findId3TagFromBeginning(stream);
+ tag = tag_id3_find_from_beginning(stream);
if (tag == NULL)
tag = tag_id3_riff_aiff_load(stream);
if (!tag)
- tag = findId3TagFromEnd(stream);
+ tag = tag_id3_find_from_end(stream);
fclose(stream);
diff --git a/src/tag_id3.h b/src/tag_id3.h
index 4f51a70b8..43f9678b4 100644
--- a/src/tag_id3.h
+++ b/src/tag_id3.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -20,7 +20,7 @@
#ifndef MPD_TAG_ID3_H
#define MPD_TAG_ID3_H
-#include "config.h"
+#include "check.h"
struct tag;
diff --git a/src/tag_internal.h b/src/tag_internal.h
index 4c3ef41be..9d76efed1 100644
--- a/src/tag_internal.h
+++ b/src/tag_internal.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
diff --git a/src/tag_pool.c b/src/tag_pool.c
index 6aef12941..6ad1e1f2d 100644
--- a/src/tag_pool.c
+++ b/src/tag_pool.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -17,6 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "tag_pool.h"
#include <assert.h>
diff --git a/src/tag_pool.h b/src/tag_pool.h
index 991a32a7e..289d6fe5f 100644
--- a/src/tag_pool.h
+++ b/src/tag_pool.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
diff --git a/src/tag_print.c b/src/tag_print.c
index dddbbbe67..493fa89b5 100644
--- a/src/tag_print.c
+++ b/src/tag_print.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -17,6 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "tag_print.h"
#include "tag.h"
#include "tag_internal.h"
diff --git a/src/tag_print.h b/src/tag_print.h
index 24ffbc914..e16e2c441 100644
--- a/src/tag_print.h
+++ b/src/tag_print.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
diff --git a/src/tag_rva2.c b/src/tag_rva2.c
new file mode 100644
index 000000000..35f12118f
--- /dev/null
+++ b/src/tag_rva2.c
@@ -0,0 +1,136 @@
+/*
+ * 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 "tag_rva2.h"
+#include "replay_gain_info.h"
+
+#include <stdint.h>
+#include <glib.h>
+#include <id3tag.h>
+
+enum rva2_channel {
+ CHANNEL_OTHER = 0x00,
+ CHANNEL_MASTER_VOLUME = 0x01,
+ CHANNEL_FRONT_RIGHT = 0x02,
+ CHANNEL_FRONT_LEFT = 0x03,
+ CHANNEL_BACK_RIGHT = 0x04,
+ CHANNEL_BACK_LEFT = 0x05,
+ CHANNEL_FRONT_CENTRE = 0x06,
+ CHANNEL_BACK_CENTRE = 0x07,
+ CHANNEL_SUBWOOFER = 0x08
+};
+
+struct rva2_data {
+ uint8_t type;
+ uint8_t volume_adjustment[2];
+ uint8_t peak_bits;
+};
+
+static inline id3_length_t
+rva2_peak_bytes(const struct rva2_data *data)
+{
+ return (data->peak_bits + 7) / 8;
+}
+
+static inline int
+rva2_fixed_volume_adjustment(const struct rva2_data *data)
+{
+ signed int voladj_fixed;
+ voladj_fixed = (data->volume_adjustment[0] << 8) |
+ data->volume_adjustment[1];
+ voladj_fixed |= -(voladj_fixed & 0x8000);
+ return voladj_fixed;
+}
+
+static inline float
+rva2_float_volume_adjustment(const struct rva2_data *data)
+{
+ /*
+ * "The volume adjustment is encoded as a fixed point decibel
+ * value, 16 bit signed integer representing (adjustment*512),
+ * giving +/- 64 dB with a precision of 0.001953125 dB."
+ */
+
+ return (float)rva2_fixed_volume_adjustment(data) / (float)512;
+}
+
+static inline bool
+rva2_apply_data(struct replay_gain_info *replay_gain_info,
+ const struct rva2_data *data)
+{
+ if (data->type != CHANNEL_MASTER_VOLUME)
+ return false;
+
+ float volume_adjustment = rva2_float_volume_adjustment(data);
+
+ replay_gain_info->tuples[REPLAY_GAIN_TRACK].gain = volume_adjustment;
+ replay_gain_info->tuples[REPLAY_GAIN_ALBUM].gain = volume_adjustment;
+
+ return true;
+}
+
+bool
+tag_rva2_parse(struct id3_tag *tag, struct replay_gain_info *replay_gain_info)
+{
+ struct id3_frame const * frame;
+
+ id3_latin1_t const *id;
+ id3_byte_t const *data;
+ id3_length_t length;
+
+ /* relative volume adjustment information */
+
+ frame = id3_tag_findframe(tag, "RVA2", 0);
+ if (frame == NULL)
+ return false;
+
+ id = id3_field_getlatin1(id3_frame_field(frame, 0));
+ data = id3_field_getbinarydata(id3_frame_field(frame, 1),
+ &length);
+
+ if (id == NULL || data == NULL)
+ return false;
+
+ /*
+ * "The 'identification' string is used to identify the
+ * situation and/or device where this adjustment should apply.
+ * The following is then repeated for every channel
+ *
+ * Type of channel $xx
+ * Volume adjustment $xx xx
+ * Bits representing peak $xx
+ * Peak volume $xx (xx ...)"
+ */
+
+ while (length >= 4) {
+ const struct rva2_data *d = (const struct rva2_data *)data;
+ unsigned int peak_bytes = rva2_peak_bytes(d);
+ if (4 + peak_bytes > length)
+ break;
+
+ if (rva2_apply_data(replay_gain_info, d))
+ return true;
+
+ data += 4 + peak_bytes;
+ length -= 4 + peak_bytes;
+ }
+
+ return false;
+}
diff --git a/src/tag_rva2.h b/src/tag_rva2.h
new file mode 100644
index 000000000..a92c97912
--- /dev/null
+++ b/src/tag_rva2.h
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_TAG_RVA2_H
+#define MPD_TAG_RVA2_H
+
+#include "check.h"
+
+#include <stdbool.h>
+
+struct id3_tag;
+struct replay_gain_info;
+
+/**
+ * Parse the RVA2 tag, and fill the #replay_gain_info struct. This is
+ * used by decoder plugins with ID3 support.
+ *
+ * @return true on success
+ */
+bool
+tag_rva2_parse(struct id3_tag *tag, struct replay_gain_info *replay_gain_info);
+
+#endif
diff --git a/src/tag_save.c b/src/tag_save.c
index fac948b9f..9b90d1b92 100644
--- a/src/tag_save.c
+++ b/src/tag_save.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -17,6 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "tag_save.h"
#include "tag.h"
#include "tag_internal.h"
diff --git a/src/tag_save.h b/src/tag_save.h
index 687c35beb..2e8924c20 100644
--- a/src/tag_save.h
+++ b/src/tag_save.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
diff --git a/src/text_file.c b/src/text_file.c
new file mode 100644
index 000000000..355217aba
--- /dev/null
+++ b/src/text_file.c
@@ -0,0 +1,68 @@
+/*
+ * 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 "text_file.h"
+
+#include <assert.h>
+#include <string.h>
+
+char *
+read_text_line(FILE *file, GString *buffer)
+{
+ enum {
+ max_length = 512 * 1024,
+ step = 1024,
+ };
+
+ gsize length = 0, i;
+ char *p;
+
+ assert(file != NULL);
+ assert(buffer != NULL);
+
+ if (buffer->allocated_len < step)
+ g_string_set_size(buffer, step);
+
+ while (buffer->len < max_length) {
+ p = fgets(buffer->str + length,
+ buffer->allocated_len - length, file);
+ if (p == NULL) {
+ if (length == 0 || ferror(file))
+ return NULL;
+ break;
+ }
+
+ i = strlen(buffer->str + length);
+ length += i;
+ if (i < step - 1 || buffer->str[length - 1] == '\n')
+ break;
+
+ g_string_set_size(buffer, length + step);
+ }
+
+ /* remove the newline characters */
+ if (buffer->str[length - 1] == '\n')
+ --length;
+ if (buffer->str[length - 1] == '\r')
+ --length;
+
+ g_string_set_size(buffer, length);
+ return buffer->str;
+}
diff --git a/src/text_file.h b/src/text_file.h
new file mode 100644
index 000000000..d016f8f7a
--- /dev/null
+++ b/src/text_file.h
@@ -0,0 +1,39 @@
+/*
+ * 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_TEXT_FILE_H
+#define MPD_TEXT_FILE_H
+
+#include <glib.h>
+
+#include <stdio.h>
+
+/**
+ * Reads a line from the input file, and strips trailing space. There
+ * is a reasonable maximum line length, only to prevent denial of
+ * service.
+ *
+ * @param file the source file, opened in text mode
+ * @param buffer an allocator for the buffer
+ * @return a pointer to the line, or NULL on end-of-file or error
+ */
+char *
+read_text_line(FILE *file, GString *buffer);
+
+#endif
diff --git a/src/text_input_stream.c b/src/text_input_stream.c
new file mode 100644
index 000000000..29fb6dce6
--- /dev/null
+++ b/src/text_input_stream.c
@@ -0,0 +1,96 @@
+/*
+ * 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 "text_input_stream.h"
+#include "input_stream.h"
+#include "fifo_buffer.h"
+
+#include <glib.h>
+
+#include <string.h>
+
+struct text_input_stream {
+ struct input_stream *is;
+
+ struct fifo_buffer *buffer;
+
+ char *line;
+};
+
+struct text_input_stream *
+text_input_stream_new(struct input_stream *is)
+{
+ struct text_input_stream *tis = g_new(struct text_input_stream, 1);
+
+ tis->is = is;
+ tis->buffer = fifo_buffer_new(4096);
+ tis->line = NULL;
+
+ return tis;
+}
+
+void
+text_input_stream_free(struct text_input_stream *tis)
+{
+ fifo_buffer_free(tis->buffer);
+ g_free(tis->line);
+ g_free(tis);
+}
+
+const char *
+text_input_stream_read(struct text_input_stream *tis)
+{
+ GError *error = NULL;
+ void *dest;
+ const char *src, *p;
+ size_t length, nbytes;
+
+ g_free(tis->line);
+ tis->line = NULL;
+
+ do {
+ dest = fifo_buffer_write(tis->buffer, &length);
+ if (dest != NULL) {
+ nbytes = input_stream_read(tis->is, dest, length,
+ &error);
+ if (nbytes > 0)
+ fifo_buffer_append(tis->buffer, nbytes);
+ else if (error != NULL) {
+ g_warning("%s", error->message);
+ g_error_free(error);
+ return NULL;
+ }
+ }
+
+ src = fifo_buffer_read(tis->buffer, &length);
+ if (src == NULL)
+ return NULL;
+
+ p = memchr(src, '\n', length);
+ } while (p == NULL);
+
+ length = p - src + 1;
+ while (p > src && g_ascii_isspace(p[-1]))
+ --p;
+
+ tis->line = g_strndup(src, p - src);
+ fifo_buffer_consume(tis->buffer, length);
+ return tis->line;
+}
diff --git a/src/text_input_stream.h b/src/text_input_stream.h
new file mode 100644
index 000000000..a1fda065d
--- /dev/null
+++ b/src/text_input_stream.h
@@ -0,0 +1,52 @@
+/*
+ * 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_TEXT_INPUT_STREAM_H
+#define MPD_TEXT_INPUT_STREAM_H
+
+struct input_stream;
+struct text_input_stream;
+
+/**
+ * Wraps an existing #input_stream object into a #text_input_stream,
+ * to read its contents as text lines.
+ *
+ * @param is an open #input_stream object
+ * @return the new #text_input_stream object
+ */
+struct text_input_stream *
+text_input_stream_new(struct input_stream *is);
+
+/**
+ * Frees the #text_input_stream object. Does not close or free the
+ * underlying #input_stream.
+ */
+void
+text_input_stream_free(struct text_input_stream *tis);
+
+/**
+ * Reads the next line from the stream.
+ *
+ * @return a line (newline character stripped), or NULL on end of file
+ * or error
+ */
+const char *
+text_input_stream_read(struct text_input_stream *tis);
+
+#endif
diff --git a/src/timer.c b/src/timer.c
index d9a143bcc..0b3b1198a 100644
--- a/src/timer.c
+++ b/src/timer.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -17,6 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "timer.h"
#include "audio_format.h"
@@ -70,6 +71,19 @@ void timer_add(Timer *timer, int size)
timer->time += ((uint64_t)size * 1000000) / timer->rate;
}
+unsigned
+timer_delay(const Timer *timer)
+{
+ int64_t delay = (int64_t)(timer->time - now()) / 1000;
+ if (delay < 0)
+ return 0;
+
+ if (delay > G_MAXINT)
+ delay = G_MAXINT;
+
+ return delay / 1000;
+}
+
void timer_sync(Timer *timer)
{
int64_t sleep_duration;
diff --git a/src/timer.h b/src/timer.h
index 7225fb5ee..bbd895b31 100644
--- a/src/timer.h
+++ b/src/timer.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -40,6 +40,12 @@ void timer_reset(Timer *timer);
void timer_add(Timer *timer, int size);
+/**
+ * Returns the number of milliseconds to sleep to get back to sync.
+ */
+unsigned
+timer_delay(const Timer *timer);
+
void timer_sync(Timer *timer);
#endif
diff --git a/src/tokenizer.c b/src/tokenizer.c
new file mode 100644
index 000000000..2b9e05070
--- /dev/null
+++ b/src/tokenizer.c
@@ -0,0 +1,222 @@
+/*
+ * 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 "tokenizer.h"
+
+#include <stdbool.h>
+#include <assert.h>
+#include <string.h>
+
+G_GNUC_CONST
+static GQuark
+tokenizer_quark(void)
+{
+ return g_quark_from_static_string("tokenizer");
+}
+
+static inline bool
+valid_word_first_char(char ch)
+{
+ return g_ascii_isalpha(ch);
+}
+
+static inline bool
+valid_word_char(char ch)
+{
+ return g_ascii_isalnum(ch) || ch == '_';
+}
+
+char *
+tokenizer_next_word(char **input_p, GError **error_r)
+{
+ char *word, *input;
+
+ assert(input_p != NULL);
+ assert(*input_p != NULL);
+
+ word = input = *input_p;
+
+ if (*input == 0)
+ return NULL;
+
+ /* check the first character */
+
+ if (!valid_word_first_char(*input)) {
+ g_set_error(error_r, tokenizer_quark(), 0,
+ "Letter expected");
+ return NULL;
+ }
+
+ /* now iterate over the other characters until we find a
+ whitespace or end-of-string */
+
+ while (*++input != 0) {
+ if (g_ascii_isspace(*input)) {
+ /* a whitespace: the word ends here */
+ *input = 0;
+ /* skip all following spaces, too */
+ input = g_strchug(input + 1);
+ break;
+ }
+
+ if (!valid_word_char(*input)) {
+ *input_p = input;
+ g_set_error(error_r, tokenizer_quark(), 0,
+ "Invalid word character");
+ return NULL;
+ }
+ }
+
+ /* end of string: the string is already null-terminated
+ here */
+
+ *input_p = input;
+ return word;
+}
+
+static inline bool
+valid_unquoted_char(char ch)
+{
+ return (unsigned char)ch > 0x20 && ch != '"' && ch != '\'';
+}
+
+char *
+tokenizer_next_unquoted(char **input_p, GError **error_r)
+{
+ char *word, *input;
+
+ assert(input_p != NULL);
+ assert(*input_p != NULL);
+
+ word = input = *input_p;
+
+ if (*input == 0)
+ return NULL;
+
+ /* check the first character */
+
+ if (!valid_unquoted_char(*input)) {
+ g_set_error(error_r, tokenizer_quark(), 0,
+ "Invalid unquoted character");
+ return NULL;
+ }
+
+ /* now iterate over the other characters until we find a
+ whitespace or end-of-string */
+
+ while (*++input != 0) {
+ if (g_ascii_isspace(*input)) {
+ /* a whitespace: the word ends here */
+ *input = 0;
+ /* skip all following spaces, too */
+ input = g_strchug(input + 1);
+ break;
+ }
+
+ if (!valid_unquoted_char(*input)) {
+ *input_p = input;
+ g_set_error(error_r, tokenizer_quark(), 0,
+ "Invalid unquoted character");
+ return NULL;
+ }
+ }
+
+ /* end of string: the string is already null-terminated
+ here */
+
+ *input_p = input;
+ return word;
+}
+
+char *
+tokenizer_next_string(char **input_p, GError **error_r)
+{
+ char *word, *dest, *input;
+
+ assert(input_p != NULL);
+ assert(*input_p != NULL);
+
+ word = dest = input = *input_p;
+
+ if (*input == 0)
+ /* end of line */
+ return NULL;
+
+ /* check for the opening " */
+
+ if (*input != '"') {
+ g_set_error(error_r, tokenizer_quark(), 0,
+ "'\"' expected");
+ return NULL;
+ }
+
+ ++input;
+
+ /* copy all characters */
+
+ while (*input != '"') {
+ if (*input == '\\')
+ /* the backslash escapes the following
+ character */
+ ++input;
+
+ if (*input == 0) {
+ /* return input-1 so the caller can see the
+ difference between "end of line" and
+ "error" */
+ *input_p = input - 1;
+ g_set_error(error_r, tokenizer_quark(), 0,
+ "Missing closing '\"'");
+ return NULL;
+ }
+
+ /* copy one character */
+ *dest++ = *input++;
+ }
+
+ /* the following character must be a whitespace (or end of
+ line) */
+
+ ++input;
+ if (*input != 0 && !g_ascii_isspace(*input)) {
+ *input_p = input;
+ g_set_error(error_r, tokenizer_quark(), 0,
+ "Space expected after closing '\"'");
+ return NULL;
+ }
+
+ /* finish the string and return it */
+
+ *dest = 0;
+ *input_p = g_strchug(input);
+ return word;
+}
+
+char *
+tokenizer_next_param(char **input_p, GError **error_r)
+{
+ assert(input_p != NULL);
+ assert(*input_p != NULL);
+
+ if (**input_p == '"')
+ return tokenizer_next_string(input_p, error_r);
+ else
+ return tokenizer_next_unquoted(input_p, error_r);
+}
diff --git a/src/tokenizer.h b/src/tokenizer.h
new file mode 100644
index 000000000..61ff398a4
--- /dev/null
+++ b/src/tokenizer.h
@@ -0,0 +1,83 @@
+/*
+ * 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_TOKENIZER_H
+#define MPD_TOKENIZER_H
+
+#include <glib.h>
+
+/**
+ * Reads the next word from the input string. This function modifies
+ * the input string.
+ *
+ * @param input_p the input string; this function returns a pointer to
+ * the first non-whitespace character of the following token
+ * @param error_r if this function returns NULL and **input_p!=0, it
+ * optionally provides a GError object in this argument
+ * @return a pointer to the null-terminated word, or NULL on error or
+ * end of line
+ */
+char *
+tokenizer_next_word(char **input_p, GError **error_r);
+
+/**
+ * Reads the next unquoted word from the input string. This function
+ * modifies the input string.
+ *
+ * @param input_p the input string; this function returns a pointer to
+ * the first non-whitespace character of the following token
+ * @param error_r if this function returns NULL and **input_p!=0, it
+ * optionally provides a GError object in this argument
+ * @return a pointer to the null-terminated word, or NULL on error or
+ * end of line
+ */
+char *
+tokenizer_next_unquoted(char **input_p, GError **error_r);
+
+/**
+ * Reads the next quoted string from the input string. A backslash
+ * escapes the following character. This function modifies the input
+ * string.
+ *
+ * @param input_p the input string; this function returns a pointer to
+ * the first non-whitespace character of the following token
+ * @param error_r if this function returns NULL and **input_p!=0, it
+ * optionally provides a GError object in this argument
+ * @return a pointer to the null-terminated string, or NULL on error
+ * or end of line
+ */
+char *
+tokenizer_next_string(char **input_p, GError **error_r);
+
+/**
+ * Reads the next unquoted word or quoted string from the input. This
+ * is a wrapper for tokenizer_next_unquoted() and
+ * tokenizer_next_string().
+ *
+ * @param input_p the input string; this function returns a pointer to
+ * the first non-whitespace character of the following token
+ * @param error_r if this function returns NULL and **input_p!=0, it
+ * optionally provides a GError object in this argument
+ * @return a pointer to the null-terminated string, or NULL on error
+ * or end of line
+ */
+char *
+tokenizer_next_param(char **input_p, GError **error_r);
+
+#endif
diff --git a/src/update.c b/src/update.c
index 9a1e7d29b..d57fb114d 100644
--- a/src/update.c
+++ b/src/update.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -17,45 +17,22 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
+#include "update_internal.h"
#include "update.h"
#include "database.h"
-#include "directory.h"
-#include "song.h"
-#include "uri.h"
#include "mapper.h"
-#include "path.h"
-#include "decoder_list.h"
-#include "archive_list.h"
#include "playlist.h"
#include "event_pipe.h"
-#include "notify.h"
#include "update.h"
#include "idle.h"
-#include "conf.h"
#include "stats.h"
#include "main.h"
-#include "config.h"
-
-#ifdef ENABLE_SQLITE
-#include "sticker.h"
-#include "song_sticker.h"
-#endif
+#include "mpd_error.h"
#include <glib.h>
#include <assert.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <unistd.h>
-#include <dirent.h>
-#include <string.h>
-#include <stdlib.h>
-#include <errno.h>
-
-#include "decoder_plugin.h"
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "update"
static enum update_progress {
UPDATE_PROGRESS_IDLE = 0,
@@ -65,32 +42,14 @@ static enum update_progress {
static bool modified;
-/* make this dynamic?, or maybe this is big enough... */
-static char *update_paths[32];
-static size_t update_paths_nr;
-
static GThread *update_thr;
static const unsigned update_task_id_max = 1 << 15;
static unsigned update_task_id;
-static struct song *delete;
-
-/** used by the main thread to notify the update thread */
-static struct notify update_notify;
-
-#ifndef WIN32
-
-enum {
- DEFAULT_FOLLOW_INSIDE_SYMLINKS = true,
- DEFAULT_FOLLOW_OUTSIDE_SYMLINKS = true,
-};
-
-static bool follow_inside_symlinks;
-static bool follow_outside_symlinks;
-
-#endif
+/* XXX this flag is passed to update_task() */
+static bool discard;
unsigned
isUpdatingDB(void)
@@ -98,709 +57,33 @@ isUpdatingDB(void)
return (progress != UPDATE_PROGRESS_IDLE) ? update_task_id : 0;
}
-static void
-directory_set_stat(struct directory *dir, const struct stat *st)
-{
- dir->inode = st->st_ino;
- dir->device = st->st_dev;
- dir->stat = 1;
-}
-
-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) */
- assert(!delete);
- delete = del;
- event_pipe_emit(PIPE_EVENT_DELETE);
-
- do {
- notify_wait(&update_notify);
- } while (delete != NULL);
-
- /* 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;
- }
-}
-
-/* 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;
-
- if ((path = map_song_fs(song)) == NULL ||
- stat(path, &st) < 0 || !S_ISREG(st.st_mode)) {
- delete_song(dir, song);
- modified = true;
- }
-
- 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 void
-removeDeletedFromDirectory(struct directory *directory)
-{
- int i;
- struct dirvec *dv = &directory->children;
-
- for (i = dv->nr; --i >= 0; ) {
- if (directory_exists(dv->base[i]))
- continue;
-
- g_debug("removing directory: %s", dv->base[i]->path);
- delete_directory(dv->base[i]);
- modified = true;
- }
-
- songvec_for_each(&directory->songs, delete_song_if_removed, directory);
-}
-
-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;
-}
-
-static int
-statDirectory(struct directory *dir)
-{
- struct stat st;
-
- if (stat_directory(dir, &st) < 0)
- return -1;
-
- directory_set_stat(dir, &st);
-
- return 0;
-}
-
-static int
-inodeFoundInParent(struct directory *parent, ino_t inode, dev_t device)
-{
- while (parent) {
- if (!parent->stat && statDirectory(parent) < 0)
- return -1;
- if (parent->inode == inode && parent->device == device) {
- g_debug("recursive directory found");
- return 1;
- }
- parent = parent->parent;
- }
-
- 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;
- }
- //create directories first
- update_archive_tree(subdir, tmp+1);
- } else {
- if (strlen(name) == 0) {
- g_warning("archive returned directory only");
- return;
- }
- //add file
- song = songvec_find(&directory->songs, name);
- if (song == NULL) {
- song = song_file_load(name, directory);
- if (song != NULL) {
- songvec_add(&directory->songs, song);
- modified = true;
- g_message("added %s/%s",
- directory_get_path(directory), name);
- }
- }
- }
-}
-
-/**
- * Updates the file listing from an archive file.
- *
- * @param parent the parent directory the archive file resides in
- * @param name the UTF-8 encoded base name of the archive file
- * @param st stat() information on the archive file
- * @param plugin the archive plugin which fits this archive type
- */
-static void
-update_archive_file(struct directory *parent, const char *name,
- const struct stat *st,
- const struct archive_plugin *plugin)
-{
- char *path_fs;
- struct archive_file *file;
- struct directory *directory;
- char *filepath;
-
- directory = dirvec_find(&parent->children, name);
- if (directory != NULL && directory->mtime == st->st_mtime)
- /* MPD has already scanned the archive, and it hasn't
- changed since - don't consider updating it */
- return;
-
- path_fs = map_directory_child_fs(parent, name);
-
- /* open archive */
- file = plugin->open(path_fs);
- if (file == NULL) {
- g_warning("unable to open archive %s", path_fs);
- g_free(path_fs);
- return;
- }
-
- g_debug("archive %s opened", path_fs);
- g_free(path_fs);
-
- if (directory == NULL) {
- g_debug("creating archive directory: %s", name);
- directory = make_subdir(parent, name);
- /* mark this directory as archive (we use device for
- this) */
- directory->device = DEVICE_INARCHIVE;
- }
-
- directory->mtime = st->st_mtime;
-
- plugin->scan_reset(file);
-
- while ((filepath = plugin->scan_next(file)) != NULL) {
- /* split name into directory and file */
- g_debug("adding archive file: %s", filepath);
- update_archive_tree(directory, filepath);
- }
-
- plugin->close(file);
-}
-#endif
-
-static bool
-update_container_file( struct directory* directory,
- const char* name,
- const struct stat* st,
- const struct decoder_plugin* plugin)
+static void * update_task(void *_path)
{
- char* vtrack = NULL;
- unsigned int tnum = 0;
- char* pathname = map_directory_child_fs(directory, name);
- struct directory* contdir = dirvec_find(&directory->children, name);
-
- // directory exists already
- if (contdir != NULL)
- {
- // modification time not eq. file mod. time
- if (contdir->mtime != st->st_mtime)
- {
- g_message("removing container file: %s", pathname);
-
- delete_directory(contdir);
- contdir = NULL;
-
- modified = true;
- }
- else {
- g_free(pathname);
- return true;
- }
- }
-
- contdir = make_subdir(directory, name);
- contdir->mtime = st->st_mtime;
- contdir->device = DEVICE_CONTAINER;
-
- while ((vtrack = plugin->container_scan(pathname, ++tnum)) != NULL)
- {
- struct song* song = song_file_new(vtrack, contdir);
- char *child_path_fs;
-
- // shouldn't be necessary but it's there..
- song->mtime = st->st_mtime;
-
- child_path_fs = map_directory_child_fs(contdir, vtrack);
- g_free(vtrack);
+ const char *path = _path;
- song->tag = plugin->tag_dup(child_path_fs);
- g_free(child_path_fs);
-
- songvec_add(&contdir->songs, song);
-
- modified = true;
- }
-
- g_free(pathname);
-
- if (tnum == 1)
- {
- delete_directory(contdir);
- return false;
- }
+ if (path != NULL && *path != 0)
+ g_debug("starting: %s", path);
else
- return true;
-}
-
-static void
-update_regular_file(struct directory *directory,
- const char *name, const struct stat *st)
-{
- const char *suffix = uri_get_suffix(name);
- const struct decoder_plugin* plugin;
-#ifdef ENABLE_ARCHIVE
- const struct archive_plugin *archive;
-#endif
- if (suffix == NULL)
- return;
-
- if ((plugin = decoder_plugin_from_suffix(suffix, false)) != NULL)
- {
- struct song* song = songvec_find(&directory->songs, name);
-
- if (!(song != NULL && st->st_mtime == song->mtime) &&
- plugin->container_scan != NULL)
- {
- if (update_container_file(directory, name, st, plugin))
- {
- if (song != NULL)
- delete_song(directory, song);
-
- return;
- }
- }
-
- if (song == NULL) {
- song = song_file_load(name, directory);
- if (song == NULL)
- 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) {
- g_message("updating %s/%s",
- directory_get_path(directory), name);
- if (!song_file_update(song))
- delete_song(directory, song);
- modified = true;
- }
-#ifdef ENABLE_ARCHIVE
- } else if ((archive = archive_plugin_from_suffix(suffix))) {
- update_archive_file(directory, name, st, archive);
-#endif
- }
-}
-
-static bool
-updateDirectory(struct directory *directory, const struct stat *st);
-
-static void
-updateInDirectory(struct directory *directory,
- const char *name, const struct stat *st)
-{
- assert(strchr(name, '/') == NULL);
-
- if (S_ISREG(st->st_mode)) {
- update_regular_file(directory, name, st);
- } else if (S_ISDIR(st->st_mode)) {
- struct directory *subdir;
- bool ret;
-
- if (inodeFoundInParent(directory, st->st_ino, st->st_dev))
- return;
-
- subdir = make_subdir(directory, name);
- assert(directory == subdir->parent);
-
- ret = updateDirectory(subdir, st);
- if (!ret)
- delete_directory(subdir);
- } else {
- g_debug("update: %s is not a directory, archive or music", name);
- }
-}
-
-/* we don't look at "." / ".." nor files with newlines in their name */
-static bool skip_path(const char *path)
-{
- return (path[0] == '.' && path[1] == 0) ||
- (path[0] == '.' && path[1] == '.' && path[2] == 0) ||
- strchr(path, '\n') != NULL;
-}
-
-static bool
-skip_symlink(const struct directory *directory, const char *utf8_name)
-{
-#ifndef WIN32
- char buffer[MPD_PATH_MAX];
- char *path_fs;
- const char *p;
- ssize_t ret;
-
- path_fs = map_directory_child_fs(directory, utf8_name);
- if (path_fs == NULL)
- return true;
-
- ret = readlink(path_fs, buffer, sizeof(buffer));
- g_free(path_fs);
- if (ret < 0)
- /* don't skip if this is not a symlink */
- return errno != EINVAL;
-
- if (!follow_inside_symlinks && !follow_outside_symlinks) {
- /* ignore all symlinks */
- return true;
- } else if (follow_inside_symlinks && follow_outside_symlinks) {
- /* consider all symlinks */
- return false;
- }
-
- if (buffer[0] == '/')
- return !follow_outside_symlinks;
-
- p = buffer;
- while (*p == '.') {
- if (p[1] == '.' && p[2] == '/') {
- /* "../" moves to parent directory */
- directory = directory->parent;
- if (directory == NULL) {
- /* we have moved outside the music
- directory - skip this symlink
- if such symlinks are not allowed */
- return !follow_outside_symlinks;
- }
- p += 3;
- } else if (p[1] == '/')
- /* eliminate "./" */
- p += 2;
- else
- break;
- }
-
- /* we are still in the music directory, so this symlink points
- to a song which is already in the database - skip according
- to the follow_inside_symlinks param*/
- return !follow_inside_symlinks;
-#else
- /* no symlink checking on WIN32 */
-
- (void)directory;
- (void)utf8_name;
-
- return false;
-#endif
-}
-
-static bool
-updateDirectory(struct directory *directory, const struct stat *st)
-{
- DIR *dir;
- struct dirent *ent;
- char *path_fs;
-
- assert(S_ISDIR(st->st_mode));
-
- directory_set_stat(directory, st);
-
- path_fs = map_directory_fs(directory);
- if (path_fs == NULL)
- return false;
-
- dir = opendir(path_fs);
- if (!dir) {
- g_warning("Failed to open directory %s: %s",
- path_fs, g_strerror(errno));
- g_free(path_fs);
- return false;
- }
-
- g_free(path_fs);
-
- removeDeletedFromDirectory(directory);
-
- while ((ent = readdir(dir))) {
- char *utf8;
- struct stat st2;
-
- if (skip_path(ent->d_name))
- continue;
-
- utf8 = fs_charset_to_utf8(ent->d_name);
- if (utf8 == NULL)
- continue;
-
- if (skip_symlink(directory, utf8)) {
- delete_name_in(directory, utf8);
- g_free(utf8);
- continue;
- }
-
- if (stat_directory_child(directory, utf8, &st2) == 0)
- updateInDirectory(directory, utf8, &st2);
- else
- delete_name_in(directory, utf8);
-
- g_free(utf8);
- }
-
- closedir(dir);
-
- directory->mtime = st->st_mtime;
+ g_debug("starting");
- return true;
-}
-
-static struct directory *
-directory_make_child_checked(struct directory *parent, const char *path)
-{
- struct directory *directory;
- char *base;
- struct stat st;
- struct song *conflicting;
-
- directory = directory_get_child(parent, path);
- 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);
- return NULL;
- }
-
- /* if we're adding directory paths, make sure to delete filenames
- with potentially the same name */
- conflicting = songvec_find(&parent->songs, base);
- if (conflicting)
- delete_song(parent, conflicting);
+ modified = update_walk(path, discard);
- g_free(base);
-
- directory = directory_new_child(parent, path);
- directory_set_stat(directory, &st);
- return directory;
-}
-
-static struct directory *
-addParentPathToDB(const char *utf8path)
-{
- struct directory *directory = db_get_root();
- char *duplicated = g_strdup(utf8path);
- char *slash = duplicated;
-
- while ((slash = strchr(slash, '/')) != NULL) {
- *slash = 0;
-
- directory = directory_make_child_checked(directory,
- duplicated);
- if (directory == NULL || slash == NULL)
- break;
-
- *slash++ = '/';
- }
-
- g_free(duplicated);
- return directory;
-}
-
-static void
-updatePath(const char *path)
-{
- struct directory *parent;
- char *name;
- struct stat st;
-
- parent = addParentPathToDB(path);
- if (parent == NULL)
- return;
-
- name = g_path_get_basename(path);
+ if (modified || !db_exists())
+ db_save();
- if (stat_directory_child(parent, name, &st) == 0)
- updateInDirectory(parent, name, &st);
+ if (path != NULL && *path != 0)
+ g_debug("finished: %s", path);
else
- delete_name_in(parent, name);
-
- g_free(name);
-}
-
-static void * update_task(void *_path)
-{
- if (_path != NULL && !isRootDirectory(_path)) {
- updatePath((char *)_path);
- } else {
- struct directory *directory = db_get_root();
- struct stat st;
-
- if (stat_directory(directory, &st) == 0)
- updateDirectory(directory, &st);
- }
-
+ g_debug("finished");
g_free(_path);
- if (modified || !db_exists())
- db_save();
-
progress = UPDATE_PROGRESS_DONE;
event_pipe_emit(PIPE_EVENT_UPDATE);
return NULL;
}
-static void spawn_update_task(char *path)
+static void
+spawn_update_task(const char *path)
{
GError *e = NULL;
@@ -808,15 +91,18 @@ static void spawn_update_task(char *path)
progress = UPDATE_PROGRESS_RUNNING;
modified = false;
- if (!(update_thr = g_thread_create(update_task, path, TRUE, &e)))
- g_error("Failed to spawn update task: %s", e->message);
+
+ update_thr = g_thread_create(update_task, g_strdup(path), TRUE, &e);
+ if (update_thr == NULL)
+ MPD_ERROR("Failed to spawn update task: %s", e->message);
+
if (++update_task_id > update_task_id_max)
update_task_id = 1;
g_debug("spawned thread for update job id %i", update_task_id);
}
unsigned
-directory_update_init(char *path)
+update_enqueue(const char *path, bool _discard)
{
assert(g_thread_self() == main_task);
@@ -824,48 +110,20 @@ directory_update_init(char *path)
return 0;
if (progress != UPDATE_PROGRESS_IDLE) {
- unsigned next_task_id;
-
- if (update_paths_nr == G_N_ELEMENTS(update_paths)) {
- g_free(path);
+ unsigned next_task_id =
+ update_queue_push(path, discard, update_task_id);
+ if (next_task_id == 0)
return 0;
- }
-
- assert(update_paths_nr < G_N_ELEMENTS(update_paths));
- update_paths[update_paths_nr++] = path;
- next_task_id = update_task_id + update_paths_nr;
return next_task_id > update_task_id_max ? 1 : next_task_id;
}
- spawn_update_task(path);
- return update_task_id;
-}
-
-/**
- * Safely delete a song from the database. This must be done in the
- * main task, to be sure that there is no pointer left to it.
- */
-static void song_delete_event(void)
-{
- char *uri;
-
- assert(progress == UPDATE_PROGRESS_RUNNING);
- assert(delete != NULL);
- uri = song_get_uri(delete);
- g_debug("removing: %s", uri);
- g_free(uri);
-
-#ifdef ENABLE_SQLITE
- /* if the song has a sticker, delete it */
- if (sticker_enabled())
- sticker_song_delete(delete);
-#endif
+ discard = _discard;
+ spawn_update_task(path);
- deleteASongFromPlaylist(&g_playlist, delete);
- delete = NULL;
+ idle_add(IDLE_UPDATE);
- notify_signal(&update_notify);
+ return update_task_id;
}
/**
@@ -873,22 +131,25 @@ static void song_delete_event(void)
*/
static void update_finished_event(void)
{
+ char *path;
+
assert(progress == UPDATE_PROGRESS_DONE);
g_thread_join(update_thr);
+ idle_add(IDLE_UPDATE);
+
if (modified) {
/* send "idle" events */
- playlistVersionChange(&g_playlist);
+ playlist_increment_version_all(&g_playlist);
idle_add(IDLE_DATABASE);
}
- if (update_paths_nr) {
+ path = update_queue_shift(&discard);
+ if (path != NULL) {
/* schedule the next path */
- char *path = update_paths[0];
- memmove(&update_paths[0], &update_paths[1],
- --update_paths_nr * sizeof(char *));
spawn_update_task(path);
+ g_free(path);
} else {
progress = UPDATE_PROGRESS_IDLE;
@@ -898,23 +159,14 @@ static void update_finished_event(void)
void update_global_init(void)
{
-#ifndef WIN32
- follow_inside_symlinks =
- config_get_bool(CONF_FOLLOW_INSIDE_SYMLINKS,
- DEFAULT_FOLLOW_INSIDE_SYMLINKS);
-
- follow_outside_symlinks =
- config_get_bool(CONF_FOLLOW_OUTSIDE_SYMLINKS,
- DEFAULT_FOLLOW_OUTSIDE_SYMLINKS);
-#endif
-
- notify_init(&update_notify);
-
- event_pipe_register(PIPE_EVENT_DELETE, song_delete_event);
event_pipe_register(PIPE_EVENT_UPDATE, update_finished_event);
+
+ update_remove_global_init();
+ update_walk_global_init();
}
void update_global_finish(void)
{
- notify_deinit(&update_notify);
+ update_walk_global_finish();
+ update_remove_global_finish();
}
diff --git a/src/update.h b/src/update.h
index 3b7a5a332..3f8a6f6a4 100644
--- a/src/update.h
+++ b/src/update.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -20,6 +20,8 @@
#ifndef MPD_UPDATE_H
#define MPD_UPDATE_H
+#include <stdbool.h>
+
void update_global_init(void);
void update_global_finish(void);
@@ -27,12 +29,14 @@ void update_global_finish(void);
unsigned
isUpdatingDB(void);
-/*
- * returns the positive update job ID on success,
- * returns 0 if busy
- * @path will be freed by this function and should not be reused
+/**
+ * Add this path to the database update queue.
+ *
+ * @param path a path to update; if NULL or an empty string,
+ * the whole music directory is updated
+ * @return the job id, or 0 on error
*/
unsigned
-directory_update_init(char *path);
+update_enqueue(const char *path, bool discard);
#endif
diff --git a/src/update_internal.h b/src/update_internal.h
new file mode 100644
index 000000000..65744f0d6
--- /dev/null
+++ b/src/update_internal.h
@@ -0,0 +1,64 @@
+/*
+ * 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_UPDATE_INTERNAL_H
+#define MPD_UPDATE_INTERNAL_H
+
+#include <stdbool.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "update"
+
+struct stat;
+struct song;
+struct directory;
+
+unsigned
+update_queue_push(const char *path, bool discard, unsigned base);
+
+char *
+update_queue_shift(bool *discard_r);
+
+void
+update_walk_global_init(void);
+
+void
+update_walk_global_finish(void);
+
+/**
+ * Returns true if the database was modified.
+ */
+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_queue.c b/src/update_queue.c
new file mode 100644
index 000000000..d7b2d4e5f
--- /dev/null
+++ b/src/update_queue.c
@@ -0,0 +1,66 @@
+/*
+ * 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 "update_internal.h"
+
+#include <glib.h>
+
+#include <assert.h>
+#include <string.h>
+
+/* make this dynamic?, or maybe this is big enough... */
+static struct {
+ char *path;
+ bool discard;
+} update_queue[32];
+
+static size_t update_queue_length;
+
+unsigned
+update_queue_push(const char *path, bool discard, unsigned base)
+{
+ assert(update_queue_length <= G_N_ELEMENTS(update_queue));
+
+ if (update_queue_length == G_N_ELEMENTS(update_queue))
+ return 0;
+
+ update_queue[update_queue_length].path = g_strdup(path);
+ update_queue[update_queue_length].discard = discard;
+
+ ++update_queue_length;
+
+ return base + update_queue_length;
+}
+
+char *
+update_queue_shift(bool *discard_r)
+{
+ char *path;
+
+ if (update_queue_length == 0)
+ return NULL;
+
+ path = update_queue[0].path;
+ *discard_r = update_queue[0].discard;
+
+ memmove(&update_queue[0], &update_queue[1],
+ --update_queue_length * sizeof(update_queue[0]));
+ return path;
+}
diff --git a/src/update_remove.c b/src/update_remove.c
new file mode 100644
index 000000000..f7c2342a2
--- /dev/null
+++ b/src/update_remove.c
@@ -0,0 +1,94 @@
+/*
+ * 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" /* must be first for large file support */
+#include "update_internal.h"
+#include "notify.h"
+#include "event_pipe.h"
+#include "song.h"
+#include "playlist.h"
+
+#ifdef ENABLE_SQLITE
+#include "sticker.h"
+#include "song_sticker.h"
+#endif
+
+#include <glib.h>
+
+#include <assert.h>
+
+static const struct song *removed_song;
+
+static struct notify remove_notify;
+
+/**
+ * Safely remove a song from the database. This must be done in the
+ * main task, to be sure that there is no pointer left to it.
+ */
+static void
+song_remove_event(void)
+{
+ char *uri;
+
+ assert(removed_song != NULL);
+
+ uri = song_get_uri(removed_song);
+ g_debug("removing: %s", uri);
+ g_free(uri);
+
+#ifdef ENABLE_SQLITE
+ /* if the song has a sticker, remove it */
+ if (sticker_enabled())
+ sticker_song_delete(removed_song);
+#endif
+
+ playlist_delete_song(&g_playlist, removed_song);
+ removed_song = NULL;
+
+ notify_signal(&remove_notify);
+}
+
+void
+update_remove_global_init(void)
+{
+ notify_init(&remove_notify);
+
+ event_pipe_register(PIPE_EVENT_DELETE, song_remove_event);
+}
+
+void
+update_remove_global_finish(void)
+{
+ notify_deinit(&remove_notify);
+}
+
+void
+update_remove_song(const struct song *song)
+{
+ assert(removed_song == NULL);
+
+ removed_song = song;
+
+ event_pipe_emit(PIPE_EVENT_DELETE);
+
+ do {
+ notify_wait(&remove_notify);
+ } while (removed_song != NULL);
+
+}
diff --git a/src/update_walk.c b/src/update_walk.c
new file mode 100644
index 000000000..845f152eb
--- /dev/null
+++ b/src/update_walk.c
@@ -0,0 +1,916 @@
+/*
+ * 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" /* must be first for large file support */
+#include "update_internal.h"
+#include "database.h"
+#include "exclude.h"
+#include "directory.h"
+#include "song.h"
+#include "uri.h"
+#include "mapper.h"
+#include "path.h"
+#include "decoder_list.h"
+#include "decoder_plugin.h"
+#include "playlist_list.h"
+#include "conf.h"
+
+#ifdef ENABLE_ARCHIVE
+#include "archive_list.h"
+#include "archive_plugin.h"
+#endif
+
+#include <glib.h>
+
+#include <assert.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <dirent.h>
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+
+static bool walk_discard;
+static bool modified;
+
+#ifndef WIN32
+
+enum {
+ DEFAULT_FOLLOW_INSIDE_SYMLINKS = true,
+ DEFAULT_FOLLOW_OUTSIDE_SYMLINKS = true,
+};
+
+static bool follow_inside_symlinks;
+static bool follow_outside_symlinks;
+
+#endif
+
+void
+update_walk_global_init(void)
+{
+#ifndef WIN32
+ follow_inside_symlinks =
+ config_get_bool(CONF_FOLLOW_INSIDE_SYMLINKS,
+ DEFAULT_FOLLOW_INSIDE_SYMLINKS);
+
+ follow_outside_symlinks =
+ config_get_bool(CONF_FOLLOW_OUTSIDE_SYMLINKS,
+ DEFAULT_FOLLOW_OUTSIDE_SYMLINKS);
+#endif
+}
+
+void
+update_walk_global_finish(void)
+{
+}
+
+static void
+directory_set_stat(struct directory *dir, const struct stat *st)
+{
+ dir->inode = st->st_ino;
+ dir->device = st->st_dev;
+ dir->stat = 1;
+}
+
+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;
+
+ for (i = dv->nr; --i >= 0; ) {
+ struct directory *child = dv->base[i];
+ char *name_fs = utf8_to_fs_charset(directory_get_name(child));
+
+ if (exclude_list_check(exclude_list, name_fs)) {
+ delete_directory(child);
+ modified = true;
+ }
+
+ g_free(name_fs);
+ }
+
+ songvec_for_each(&directory->songs,
+ delete_song_if_excluded, exclude_list);
+}
+
+/* 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;
+
+ if ((path = map_song_fs(song)) == NULL ||
+ stat(path, &st) < 0 || !S_ISREG(st.st_mode)) {
+ delete_song(dir, song);
+ modified = true;
+ }
+
+ 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;
+}
+
+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]))
+ continue;
+
+ g_debug("removing directory: %s", dv->base[i]->path);
+ delete_directory(dv->base[i]);
+ 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;
+
+ if (!directory_child_is_regular(directory, pm->name))
+ playlist_vector_remove(&directory->playlists, pm->name);
+
+ pm = next;
+ }
+}
+
+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;
+}
+
+#ifndef G_OS_WIN32
+static int
+statDirectory(struct directory *dir)
+{
+ struct stat st;
+
+ if (stat_directory(dir, &st) < 0)
+ return -1;
+
+ directory_set_stat(dir, &st);
+
+ return 0;
+}
+#endif
+
+static int
+inodeFoundInParent(struct directory *parent, ino_t inode, dev_t device)
+{
+#ifndef G_OS_WIN32
+ while (parent) {
+ if (!parent->stat && statDirectory(parent) < 0)
+ return -1;
+ if (parent->inode == inode && parent->device == device) {
+ g_debug("recursive directory found");
+ return 1;
+ }
+ parent = parent->parent;
+ }
+#else
+ (void)parent;
+ (void)inode;
+ (void)device;
+#endif
+
+ 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;
+ }
+ //create directories first
+ update_archive_tree(subdir, tmp+1);
+ } else {
+ if (strlen(name) == 0) {
+ g_warning("archive returned directory only");
+ return;
+ }
+ //add file
+ song = songvec_find(&directory->songs, name);
+ if (song == NULL) {
+ song = song_file_load(name, directory);
+ if (song != NULL) {
+ songvec_add(&directory->songs, song);
+ modified = true;
+ g_message("added %s/%s",
+ directory_get_path(directory), name);
+ }
+ }
+ }
+}
+
+/**
+ * Updates the file listing from an archive file.
+ *
+ * @param parent the parent directory the archive file resides in
+ * @param name the UTF-8 encoded base name of the archive file
+ * @param st stat() information on the archive file
+ * @param plugin the archive plugin which fits this archive type
+ */
+static void
+update_archive_file(struct directory *parent, const char *name,
+ const struct stat *st,
+ const struct archive_plugin *plugin)
+{
+ GError *error = NULL;
+ char *path_fs;
+ struct archive_file *file;
+ struct directory *directory;
+ char *filepath;
+
+ directory = dirvec_find(&parent->children, name);
+ if (directory != NULL && directory->mtime == st->st_mtime &&
+ !walk_discard)
+ /* MPD has already scanned the archive, and it hasn't
+ changed since - don't consider updating it */
+ return;
+
+ path_fs = map_directory_child_fs(parent, name);
+
+ /* open archive */
+ file = archive_file_open(plugin, path_fs, &error);
+ if (file == NULL) {
+ g_free(path_fs);
+ g_warning("%s", error->message);
+ g_error_free(error);
+ return;
+ }
+
+ g_debug("archive %s opened", path_fs);
+ g_free(path_fs);
+
+ if (directory == NULL) {
+ g_debug("creating archive directory: %s", name);
+ directory = make_subdir(parent, name);
+ /* mark this directory as archive (we use device for
+ this) */
+ directory->device = DEVICE_INARCHIVE;
+ }
+
+ directory->mtime = st->st_mtime;
+
+ archive_file_scan_reset(file);
+
+ while ((filepath = archive_file_scan_next(file)) != NULL) {
+ /* split name into directory and file */
+ g_debug("adding archive file: %s", filepath);
+ update_archive_tree(directory, filepath);
+ }
+
+ archive_file_close(file);
+}
+#endif
+
+static bool
+update_container_file( struct directory* directory,
+ const char* name,
+ const struct stat* st,
+ const struct decoder_plugin* plugin)
+{
+ char* vtrack = NULL;
+ unsigned int tnum = 0;
+ char* pathname = map_directory_child_fs(directory, name);
+ struct directory* contdir = dirvec_find(&directory->children, name);
+
+ // directory exists already
+ if (contdir != NULL)
+ {
+ // modification time not eq. file mod. time
+ if (contdir->mtime != st->st_mtime || walk_discard)
+ {
+ g_message("removing container file: %s", pathname);
+
+ delete_directory(contdir);
+ contdir = NULL;
+
+ modified = true;
+ }
+ else {
+ g_free(pathname);
+ return true;
+ }
+ }
+
+ contdir = make_subdir(directory, name);
+ contdir->mtime = st->st_mtime;
+ contdir->device = DEVICE_CONTAINER;
+
+ while ((vtrack = plugin->container_scan(pathname, ++tnum)) != NULL)
+ {
+ struct song* song = song_file_new(vtrack, contdir);
+ char *child_path_fs;
+
+ // shouldn't be necessary but it's there..
+ song->mtime = st->st_mtime;
+
+ child_path_fs = map_directory_child_fs(contdir, vtrack);
+
+ song->tag = plugin->tag_dup(child_path_fs);
+ g_free(child_path_fs);
+
+ songvec_add(&contdir->songs, song);
+
+ modified = true;
+
+ g_message("added %s/%s",
+ directory_get_path(directory), vtrack);
+ g_free(vtrack);
+ }
+
+ g_free(pathname);
+
+ if (tnum == 1)
+ {
+ delete_directory(contdir);
+ 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;
+
+ bool success = access(path, mode) == 0 || errno != EACCES;
+ g_free(path);
+ return success;
+#endif
+}
+
+static void
+update_regular_file(struct directory *directory,
+ const char *name, const struct stat *st)
+{
+ const char *suffix = uri_get_suffix(name);
+ const struct decoder_plugin* plugin;
+#ifdef ENABLE_ARCHIVE
+ const struct archive_plugin *archive;
+#endif
+ if (suffix == NULL)
+ return;
+
+ 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) {
+ 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;
+ }
+#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)) {
+ if (playlist_vector_update_or_add(&directory->playlists, name,
+ st->st_mtime))
+ modified = true;
+ }
+}
+
+static bool
+updateDirectory(struct directory *directory, const struct stat *st);
+
+static void
+updateInDirectory(struct directory *directory,
+ const char *name, const struct stat *st)
+{
+ assert(strchr(name, '/') == NULL);
+
+ if (S_ISREG(st->st_mode)) {
+ update_regular_file(directory, name, st);
+ } else if (S_ISDIR(st->st_mode)) {
+ struct directory *subdir;
+ bool ret;
+
+ if (inodeFoundInParent(directory, st->st_ino, st->st_dev))
+ return;
+
+ subdir = make_subdir(directory, name);
+ assert(directory == subdir->parent);
+
+ ret = updateDirectory(subdir, st);
+ if (!ret)
+ delete_directory(subdir);
+ } else {
+ g_debug("update: %s is not a directory, archive or music", name);
+ }
+}
+
+/* we don't look at "." / ".." nor files with newlines in their name */
+static bool skip_path(const char *path)
+{
+ return (path[0] == '.' && path[1] == 0) ||
+ (path[0] == '.' && path[1] == '.' && path[2] == 0) ||
+ strchr(path, '\n') != NULL;
+}
+
+static bool
+skip_symlink(const struct directory *directory, const char *utf8_name)
+{
+#ifndef WIN32
+ char buffer[MPD_PATH_MAX];
+ char *path_fs;
+ const char *p;
+ ssize_t ret;
+
+ path_fs = map_directory_child_fs(directory, utf8_name);
+ if (path_fs == NULL)
+ return true;
+
+ ret = readlink(path_fs, buffer, sizeof(buffer));
+ g_free(path_fs);
+ if (ret < 0)
+ /* don't skip if this is not a symlink */
+ return errno != EINVAL;
+
+ if (!follow_inside_symlinks && !follow_outside_symlinks) {
+ /* ignore all symlinks */
+ return true;
+ } else if (follow_inside_symlinks && follow_outside_symlinks) {
+ /* consider all symlinks */
+ return false;
+ }
+
+ if (buffer[0] == '/')
+ return !follow_outside_symlinks;
+
+ p = buffer;
+ while (*p == '.') {
+ if (p[1] == '.' && G_IS_DIR_SEPARATOR(p[2])) {
+ /* "../" moves to parent directory */
+ directory = directory->parent;
+ if (directory == NULL) {
+ /* we have moved outside the music
+ directory - skip this symlink
+ if such symlinks are not allowed */
+ return !follow_outside_symlinks;
+ }
+ p += 3;
+ } else if (G_IS_DIR_SEPARATOR(p[1]))
+ /* eliminate "./" */
+ p += 2;
+ else
+ break;
+ }
+
+ /* we are still in the music directory, so this symlink points
+ to a song which is already in the database - skip according
+ to the follow_inside_symlinks param*/
+ return !follow_inside_symlinks;
+#else
+ /* no symlink checking on WIN32 */
+
+ (void)directory;
+ (void)utf8_name;
+
+ return false;
+#endif
+}
+
+static bool
+updateDirectory(struct directory *directory, const struct stat *st)
+{
+ DIR *dir;
+ struct dirent *ent;
+ char *path_fs, *exclude_path_fs;
+ GSList *exclude_list;
+
+ assert(S_ISDIR(st->st_mode));
+
+ directory_set_stat(directory, st);
+
+ path_fs = map_directory_fs(directory);
+ if (path_fs == NULL)
+ return false;
+
+ dir = opendir(path_fs);
+ if (!dir) {
+ g_warning("Failed to open directory %s: %s",
+ path_fs, g_strerror(errno));
+ g_free(path_fs);
+ return false;
+ }
+
+ exclude_path_fs = g_build_filename(path_fs, ".mpdignore", NULL);
+ exclude_list = exclude_list_load(exclude_path_fs);
+ g_free(exclude_path_fs);
+
+ g_free(path_fs);
+
+ if (exclude_list != NULL)
+ remove_excluded_from_directory(directory, exclude_list);
+
+ removeDeletedFromDirectory(directory);
+
+ while ((ent = readdir(dir))) {
+ char *utf8;
+ struct stat st2;
+
+ if (skip_path(ent->d_name) ||
+ exclude_list_check(exclude_list, ent->d_name))
+ continue;
+
+ utf8 = fs_charset_to_utf8(ent->d_name);
+ if (utf8 == NULL)
+ continue;
+
+ if (skip_symlink(directory, utf8)) {
+ delete_name_in(directory, utf8);
+ g_free(utf8);
+ continue;
+ }
+
+ if (stat_directory_child(directory, utf8, &st2) == 0)
+ updateInDirectory(directory, utf8, &st2);
+ else
+ delete_name_in(directory, utf8);
+
+ g_free(utf8);
+ }
+
+ exclude_list_free(exclude_list);
+
+ closedir(dir);
+
+ directory->mtime = st->st_mtime;
+
+ return true;
+}
+
+static struct directory *
+directory_make_child_checked(struct directory *parent, const char *path)
+{
+ struct directory *directory;
+ char *base;
+ struct stat st;
+ struct song *conflicting;
+
+ directory = directory_get_child(parent, path);
+ 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);
+ return NULL;
+ }
+
+ /* if we're adding directory paths, make sure to delete filenames
+ with potentially the same name */
+ conflicting = songvec_find(&parent->songs, base);
+ if (conflicting)
+ delete_song(parent, conflicting);
+
+ g_free(base);
+
+ directory = directory_new_child(parent, path);
+ directory_set_stat(directory, &st);
+ return directory;
+}
+
+static struct directory *
+addParentPathToDB(const char *utf8path)
+{
+ struct directory *directory = db_get_root();
+ char *duplicated = g_strdup(utf8path);
+ char *slash = duplicated;
+
+ while ((slash = strchr(slash, '/')) != NULL) {
+ *slash = 0;
+
+ directory = directory_make_child_checked(directory,
+ duplicated);
+ if (directory == NULL || slash == NULL)
+ break;
+
+ *slash++ = '/';
+ }
+
+ g_free(duplicated);
+ return directory;
+}
+
+static void
+updatePath(const char *path)
+{
+ struct directory *parent;
+ char *name;
+ struct stat st;
+
+ parent = addParentPathToDB(path);
+ if (parent == NULL)
+ return;
+
+ name = g_path_get_basename(path);
+
+ if (stat_directory_child(parent, name, &st) == 0)
+ updateInDirectory(parent, name, &st);
+ else
+ delete_name_in(parent, name);
+
+ g_free(name);
+}
+
+bool
+update_walk(const char *path, bool discard)
+{
+ walk_discard = discard;
+ modified = false;
+
+ if (path != NULL && !isRootDirectory(path)) {
+ updatePath(path);
+ } else {
+ struct directory *directory = db_get_root();
+ struct stat st;
+
+ if (stat_directory(directory, &st) == 0)
+ updateDirectory(directory, &st);
+ }
+
+ return modified;
+}
diff --git a/src/uri.c b/src/uri.c
index fb3f708b2..f4d590a60 100644
--- a/src/uri.c
+++ b/src/uri.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -17,10 +17,12 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "uri.h"
#include <glib.h>
+#include <assert.h>
#include <string.h>
bool uri_has_scheme(const char *uri)
@@ -32,9 +34,51 @@ bool uri_has_scheme(const char *uri)
const char *
uri_get_suffix(const char *uri)
{
- const char *dot = strrchr(g_basename(uri), '.');
+ const char *suffix = strrchr(g_basename(uri), '.');
+ if (suffix == NULL)
+ return NULL;
+
+ ++suffix;
+
+ if (strchr(suffix, '/') != NULL)
+ return NULL;
+
+ return suffix;
+}
+
+static const char *
+verify_uri_segment(const char *p)
+{
+ const char *q;
+
+ unsigned dots = 0;
+ while (*p == '.') {
+ ++p;
+ ++dots;
+ }
+
+ if (dots <= 2 && (*p == 0 || *p == '/'))
+ return NULL;
+
+ q = strchr(p + 1, '/');
+ return q != NULL ? q : "";
+}
+
+bool
+uri_safe_local(const char *uri)
+{
+ while (true) {
+ uri = verify_uri_segment(uri);
+ if (uri == NULL)
+ return false;
+
+ if (*uri == 0)
+ return true;
+
+ assert(*uri == '/');
- return dot != NULL ? dot + 1 : NULL;
+ ++uri;
+ }
}
char *
diff --git a/src/uri.h b/src/uri.h
index 1a12cc557..422b959b0 100644
--- a/src/uri.h
+++ b/src/uri.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -20,23 +20,40 @@
#ifndef MPD_URI_H
#define MPD_URI_H
+#include <glib.h>
+
#include <stdbool.h>
/**
* Checks whether the specified URI has a schema in the form
* "scheme://".
*/
+G_GNUC_PURE
bool uri_has_scheme(const char *uri);
+G_GNUC_PURE
const char *
uri_get_suffix(const char *uri);
/**
+ * Returns true if this is a safe "local" URI:
+ *
+ * - non-empty
+ * - does not begin or end with a slash
+ * - no double slashes
+ * - no path component begins with a dot
+ */
+G_GNUC_PURE
+bool
+uri_safe_local(const char *uri);
+
+/**
* Removes HTTP username and password from the URI. This may be
* useful for displaying an URI without disclosing secrets. Returns
* NULL if nothing needs to be removed, or if the URI is not
* recognized.
*/
+G_GNUC_MALLOC
char *
uri_remove_auth(const char *uri);
diff --git a/src/utils.c b/src/utils.c
index fc27b13c9..53494cc5d 100644
--- a/src/utils.c
+++ b/src/utils.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -17,9 +17,9 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "utils.h"
#include "conf.h"
-#include "config.h"
#include <glib.h>
@@ -44,7 +44,7 @@
char *parsePath(char *path)
{
#ifndef WIN32
- if (path[0] != '/' && path[0] != '~') {
+ if (!g_path_is_absolute(path) && path[0] != '~') {
g_warning("\"%s\" is not an absolute path", path);
return NULL;
} else if (path[0] == '~') {
@@ -102,43 +102,15 @@ char *parsePath(char *path)
#endif
}
-int set_nonblocking(int fd)
+bool
+string_array_contains(const char *const* haystack, const char *needle)
{
-#ifdef WIN32
- u_long val = 1;
- int retval;
- int lasterr = 0;
- retval = ioctlsocket(fd, FIONBIO, &val);
- if(retval == SOCKET_ERROR)
- g_error("Error: ioctlsocket could not set FIONBIO;"
- " Error %d on socket %d", lasterr = WSAGetLastError(), fd);
- if(lasterr == 10038)
- g_debug("Code-up error! Attempt to set non-blocking I/O on "
- "something that is not a Winsock2 socket. This can't "
- "be done on Windows!\n");
- return retval;
-#else
- int ret, flags;
-
- assert(fd >= 0);
-
- while ((flags = fcntl(fd, F_GETFL)) < 0 && errno == EINTR) ;
- if (flags < 0)
- return flags;
-
- flags |= O_NONBLOCK;
- while ((ret = fcntl(fd, F_SETFL, flags)) < 0 && errno == EINTR) ;
- return ret;
-#endif
-}
+ assert(haystack != NULL);
+ assert(needle != NULL);
-int stringFoundInStringArray(const char *const*array, const char *suffix)
-{
- while (array && *array) {
- if (g_ascii_strcasecmp(*array, suffix) == 0)
- return 1;
- array++;
- }
+ for (; *haystack != NULL; ++haystack)
+ if (g_ascii_strcasecmp(*haystack, needle) == 0)
+ return true;
- return 0;
+ return false;
}
diff --git a/src/utils.h b/src/utils.h
index d114003be..629056637 100644
--- a/src/utils.h
+++ b/src/utils.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -20,6 +20,8 @@
#ifndef MPD_UTILS_H
#define MPD_UTILS_H
+#include <stdbool.h>
+
#ifndef assert_static
/* Compile time assertion developed by Ralf Holly */
/* http://pera-software.com/articles/compile-time-assertions.pdf */
@@ -31,8 +33,15 @@
char *parsePath(char *path);
-int set_nonblocking(int fd);
-
-int stringFoundInStringArray(const char *const*array, const char *suffix);
+/**
+ * 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/volume.c b/src/volume.c
index e7fa20a62..d7b72dd56 100644
--- a/src/volume.c
+++ b/src/volume.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -17,15 +17,17 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "volume.h"
#include "conf.h"
#include "player_control.h"
#include "idle.h"
#include "pcm_volume.h"
-#include "config.h"
#include "output_all.h"
#include "mixer_control.h"
#include "mixer_all.h"
+#include "mixer_type.h"
+#include "event_pipe.h"
#include <glib.h>
@@ -39,132 +41,39 @@
#define SW_VOLUME_STATE "sw_volume: "
-static enum {
- VOLUME_MIXER_TYPE_SOFTWARE,
- VOLUME_MIXER_TYPE_HARDWARE,
- VOLUME_MIXER_TYPE_DISABLED,
-} volume_mixer_type = VOLUME_MIXER_TYPE_HARDWARE;
-
-static int volume_software_set = 100;
+static unsigned volume_software_set = 100;
/** the cached hardware mixer value; invalid if negative */
static int last_hardware_volume = -1;
/** the age of #last_hardware_volume */
static GTimer *hardware_volume_timer;
-void volume_finish(void)
-{
- if (volume_mixer_type == VOLUME_MIXER_TYPE_HARDWARE)
- g_timer_destroy(hardware_volume_timer);
-}
-
/**
- * Finds the first audio_output configuration section with the
- * specified type.
- */
-static struct config_param *
-find_output_config(const char *type)
-{
- struct config_param *param = NULL;
-
- while ((param = config_get_next_param(CONF_AUDIO_OUTPUT,
- param)) != NULL) {
- const char *param_type =
- config_get_block_string(param, "type", NULL);
- if (param_type != NULL && strcmp(param_type, type) == 0)
- return param;
- }
-
- return NULL;
-}
-
-/**
- * Copy a (top-level) legacy mixer configuration parameter to the
- * audio_output section.
+ * Handler for #PIPE_EVENT_MIXER.
*/
static void
-mixer_copy_legacy_param(const char *type, const char *name)
+mixer_event_callback(void)
{
- const struct config_param *param;
- struct config_param *output;
- const struct block_param *bp;
-
- /* see if the deprecated configuration exists */
-
- param = config_get_param(name);
- if (param == NULL)
- return;
-
- g_warning("deprecated option '%s' found, moving to '%s' audio output",
- name, type);
-
- /* determine the configuration section */
-
- output = find_output_config(type);
- if (output == NULL) {
- /* if there is no output configuration at all, create
- a new and empty configuration section for the
- legacy mixer */
-
- if (config_get_next_param(CONF_AUDIO_OUTPUT, NULL) != NULL)
- /* there is an audio_output configuration, but
- it does not match the mixer_type setting */
- g_error("no '%s' audio output found", type);
-
- output = config_new_param(NULL, param->line);
- config_add_block_param(output, "type", type, param->line);
- config_add_block_param(output, "name", type, param->line);
- config_add_param(CONF_AUDIO_OUTPUT, output);
- }
-
- bp = config_get_block_param(output, name);
- if (bp != NULL)
- g_error("the '%s' audio output already has a '%s' setting",
- type, name);
-
- /* duplicate the parameter in the configuration section */
+ /* flush the hardware volume cache */
+ last_hardware_volume = -1;
- config_add_block_param(output, name, param->value, param->line);
+ /* notify clients */
+ idle_add(IDLE_MIXER);
}
-static void
-mixer_reconfigure(const char *type)
+void volume_finish(void)
{
- mixer_copy_legacy_param(type, CONF_MIXER_DEVICE);
- mixer_copy_legacy_param(type, CONF_MIXER_CONTROL);
+ g_timer_destroy(hardware_volume_timer);
}
void volume_init(void)
{
- const struct config_param *param = config_get_param(CONF_MIXER_TYPE);
- //hw mixing is by default
- if (param) {
- if (strcmp(param->value, VOLUME_MIXER_SOFTWARE) == 0) {
- volume_mixer_type = VOLUME_MIXER_TYPE_SOFTWARE;
- mixer_disable_all();
- } else if (strcmp(param->value, VOLUME_MIXER_DISABLED) == 0) {
- volume_mixer_type = VOLUME_MIXER_TYPE_DISABLED;
- mixer_disable_all();
- } else if (strcmp(param->value, VOLUME_MIXER_HARDWARE) == 0) {
- //nothing to do
- } else {
- //fallback to old config behaviour
- if (strcmp(param->value, VOLUME_MIXER_OSS) == 0) {
- mixer_reconfigure(param->value);
- } else if (strcmp(param->value, VOLUME_MIXER_ALSA) == 0) {
- mixer_reconfigure(param->value);
- } else {
- g_error("unknown mixer type %s at line %i\n",
- param->value, param->line);
- }
- }
- }
+ hardware_volume_timer = g_timer_new();
- if (volume_mixer_type == VOLUME_MIXER_TYPE_HARDWARE)
- hardware_volume_timer = g_timer_new();
+ event_pipe_register(PIPE_EVENT_MIXER, mixer_event_callback);
}
-static int hardware_volume_get(void)
+int volume_level_get(void)
{
assert(hardware_volume_timer != NULL);
@@ -178,101 +87,60 @@ static int hardware_volume_get(void)
return last_hardware_volume;
}
-static int software_volume_get(void)
+static bool software_volume_change(unsigned volume)
{
- return volume_software_set;
-}
-
-int volume_level_get(void)
-{
- switch (volume_mixer_type) {
- case VOLUME_MIXER_TYPE_SOFTWARE:
- return software_volume_get();
- case VOLUME_MIXER_TYPE_HARDWARE:
- return hardware_volume_get();
- case VOLUME_MIXER_TYPE_DISABLED:
- return -1;
- }
-
- /* unreachable */
- assert(false);
- return -1;
-}
-
-static bool software_volume_change(int change, bool rel)
-{
- int new = change;
-
- if (rel)
- new += volume_software_set;
+ assert(volume <= 100);
- if (new > 100)
- new = 100;
- else if (new < 0)
- new = 0;
-
- volume_software_set = new;
-
- /*new = 100.0*(exp(new/50.0)-1)/(M_E*M_E-1)+0.5; */
- if (new >= 100)
- new = PCM_VOLUME_1;
- else if (new <= 0)
- new = 0;
- else
- new = pcm_float_to_volume((exp(new / 25.0) - 1) /
- (54.5981500331F - 1));
-
- setPlayerSoftwareVolume(new);
+ volume_software_set = volume;
+ mixer_all_set_software_volume(volume);
return true;
}
-static bool hardware_volume_change(int change, bool rel)
+static bool hardware_volume_change(unsigned volume)
{
/* reset the cache */
last_hardware_volume = -1;
- return mixer_all_set_volume(change, rel);
+ return mixer_all_set_volume(volume);
}
-bool volume_level_change(int change, bool rel)
+bool volume_level_change(unsigned volume)
{
+ assert(volume <= 100);
+
+ volume_software_set = volume;
+
idle_add(IDLE_MIXER);
- switch (volume_mixer_type) {
- case VOLUME_MIXER_TYPE_HARDWARE:
- return hardware_volume_change(change, rel);
- case VOLUME_MIXER_TYPE_SOFTWARE:
- return software_volume_change(change, rel);
- default:
- return true;
- }
+ return hardware_volume_change(volume);
}
-void read_sw_volume_state(FILE *fp)
+bool
+read_sw_volume_state(const char *line)
{
- char buf[sizeof(SW_VOLUME_STATE) + sizeof("100") - 1];
char *end = NULL;
long int sv;
- if (volume_mixer_type != VOLUME_MIXER_TYPE_SOFTWARE)
- return;
- while (fgets(buf, sizeof(buf), fp)) {
- if (!g_str_has_prefix(buf, SW_VOLUME_STATE))
- continue;
+ if (!g_str_has_prefix(line, SW_VOLUME_STATE))
+ return false;
- g_strchomp(buf);
- sv = strtol(buf + strlen(SW_VOLUME_STATE), &end, 10);
- if (G_LIKELY(!*end))
- software_volume_change(sv, 0);
- else
- g_warning("Can't parse software volume: %s\n", buf);
- return;
- }
+ line += sizeof(SW_VOLUME_STATE) - 1;
+ sv = strtol(line, &end, 10);
+ if (*end == 0 && sv >= 0 && sv <= 100)
+ software_volume_change(sv);
+ else
+ g_warning("Can't parse software volume: %s\n", line);
+ return true;
}
void save_sw_volume_state(FILE *fp)
{
- if (volume_mixer_type == VOLUME_MIXER_TYPE_SOFTWARE)
- fprintf(fp, SW_VOLUME_STATE "%d\n", volume_software_set);
+ fprintf(fp, SW_VOLUME_STATE "%u\n", volume_software_set);
+}
+
+unsigned
+sw_volume_state_get_hash(void)
+{
+ return volume_software_set;
}
diff --git a/src/volume.h b/src/volume.h
index 99d31da4e..db266fec9 100644
--- a/src/volume.h
+++ b/src/volume.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -23,22 +23,26 @@
#include <stdbool.h>
#include <stdio.h>
-#define VOLUME_MIXER_OSS "oss"
-#define VOLUME_MIXER_ALSA "alsa"
-#define VOLUME_MIXER_SOFTWARE "software"
-#define VOLUME_MIXER_HARDWARE "hardware"
-#define VOLUME_MIXER_DISABLED "disabled"
-
void volume_init(void);
void volume_finish(void);
int volume_level_get(void);
-bool volume_level_change(int change, bool rel);
+bool volume_level_change(unsigned volume);
-void read_sw_volume_state(FILE *fp);
+bool
+read_sw_volume_state(const char *line);
void save_sw_volume_state(FILE *fp);
+/**
+ * Generates a hash number for the current state of the software
+ * volume control. This is used by timer_save_state_file() to
+ * determine whether the state has changed and the state file should
+ * be saved.
+ */
+unsigned
+sw_volume_state_get_hash(void);
+
#endif
diff --git a/src/zeroconf-avahi.c b/src/zeroconf-avahi.c
index 648f36e03..518a7a481 100644
--- a/src/zeroconf-avahi.c
+++ b/src/zeroconf-avahi.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -17,8 +17,10 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "zeroconf-internal.h"
#include "listen.h"
+#include "mpd_error.h"
#include <glib.h>
@@ -217,7 +219,7 @@ void init_avahi(const char *serviceName)
g_debug("Initializing interface");
if (!avahi_is_valid_service_name(serviceName))
- g_error("Invalid zeroconf_name \"%s\"", serviceName);
+ MPD_ERROR("Invalid zeroconf_name \"%s\"", serviceName);
avahiName = avahi_strdup(serviceName);
diff --git a/src/zeroconf-bonjour.c b/src/zeroconf-bonjour.c
index 6ad2a2bac..84f777c50 100644
--- a/src/zeroconf-bonjour.c
+++ b/src/zeroconf-bonjour.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -17,6 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "zeroconf-internal.h"
#include "listen.h"
diff --git a/src/zeroconf-internal.h b/src/zeroconf-internal.h
index f5aacebd8..7cb962431 100644
--- a/src/zeroconf-internal.h
+++ b/src/zeroconf-internal.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
diff --git a/src/zeroconf.c b/src/zeroconf.c
index 42e995c45..7b00789b6 100644
--- a/src/zeroconf.c
+++ b/src/zeroconf.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -17,10 +17,10 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "zeroconf.h"
#include "zeroconf-internal.h"
#include "conf.h"
-#include "config.h"
#include <glib.h>
diff --git a/src/zeroconf.h b/src/zeroconf.h
index 6a5934ed5..23354f87d 100644
--- a/src/zeroconf.h
+++ b/src/zeroconf.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * 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
@@ -20,7 +20,7 @@
#ifndef MPD_ZEROCONF_H
#define MPD_ZEROCONF_H
-#include "config.h"
+#include "check.h"
#ifdef HAVE_ZEROCONF